From 431a5218a06e8cf3e2f8f6ef58053575a8dbb0fc Mon Sep 17 00:00:00 2001 From: Sattar Gyulmamedov Date: Thu, 10 Apr 2025 12:44:28 +0300 Subject: [PATCH 01/22] init docs transform to md --- .gitignore | 3 + mddocs/README.md | 657 ++++++++++++++++ mddocs/_sphinx_design_static/design-tabs.js | 101 +++ .../sphinx-design.min.css | 1 + mddocs/_static/autodoc_pydantic.css | 11 + mddocs/_static/icon.svg | 11 + mddocs/_static/logo.svg | 214 +++++ mddocs/_static/logo_wide.svg | 329 ++++++++ mddocs/changelog.md | 31 + mddocs/changelog/0.10.0.md | 359 +++++++++ mddocs/changelog/0.10.1.md | 25 + mddocs/changelog/0.10.2.md | 34 + mddocs/changelog/0.11.0.md | 202 +++++ mddocs/changelog/0.11.1.md | 9 + mddocs/changelog/0.11.2.md | 5 + mddocs/changelog/0.12.0.md | 48 ++ mddocs/changelog/0.12.1.md | 17 + mddocs/changelog/0.12.2.md | 18 + mddocs/changelog/0.12.3.md | 5 + mddocs/changelog/0.12.4.md | 5 + mddocs/changelog/0.12.5.md | 13 + mddocs/changelog/0.13.0.md | 197 +++++ mddocs/changelog/0.13.1.md | 9 + mddocs/changelog/0.13.3.md | 5 + mddocs/changelog/0.13.4.md | 10 + mddocs/changelog/0.7.0.md | 211 +++++ mddocs/changelog/0.7.1.md | 31 + mddocs/changelog/0.7.2.md | 33 + mddocs/changelog/0.8.0.md | 137 ++++ mddocs/changelog/0.8.1.md | 34 + mddocs/changelog/0.9.0.md | 107 +++ mddocs/changelog/0.9.1.md | 7 + mddocs/changelog/0.9.2.md | 21 + mddocs/changelog/0.9.3.md | 5 + mddocs/changelog/0.9.4.md | 24 + mddocs/changelog/0.9.5.md | 14 + mddocs/changelog/DRAFT.md | 0 mddocs/changelog/NEXT_RELEASE.md | 1 + mddocs/changelog/index.md | 29 + mddocs/concepts.md | 340 ++++++++ mddocs/conf.py | 150 ++++ .../db_connection/clickhouse/connection.md | 128 +++ .../db_connection/clickhouse/execute.md | 191 +++++ .../db_connection/clickhouse/index.md | 19 + .../db_connection/clickhouse/prerequisites.md | 73 ++ .../db_connection/clickhouse/read.md | 339 ++++++++ .../db_connection/clickhouse/sql.md | 191 +++++ .../db_connection/clickhouse/types.md | 347 ++++++++ .../db_connection/clickhouse/write.md | 187 +++++ .../db_connection/greenplum/connection.md | 144 ++++ .../db_connection/greenplum/execute.md | 195 +++++ .../db_connection/greenplum/index.md | 18 + .../db_connection/greenplum/prerequisites.md | 358 +++++++++ .../db_connection/greenplum/read.md | 405 ++++++++++ .../db_connection/greenplum/types.md | 318 ++++++++ .../db_connection/greenplum/write.md | 141 ++++ .../db_connection/hive/connection.md | 118 +++ .../connection/db_connection/hive/execute.md | 57 ++ mddocs/connection/db_connection/hive/index.md | 19 + .../db_connection/hive/prerequisites.md | 128 +++ mddocs/connection/db_connection/hive/read.md | 92 +++ mddocs/connection/db_connection/hive/slots.md | 105 +++ mddocs/connection/db_connection/hive/sql.md | 86 ++ mddocs/connection/db_connection/hive/write.md | 394 ++++++++++ mddocs/connection/db_connection/index.md | 16 + mddocs/connection/db_connection/kafka/auth.md | 37 + .../db_connection/kafka/basic_auth.md | 60 ++ .../db_connection/kafka/connection.md | 243 ++++++ .../connection/db_connection/kafka/index.md | 31 + .../db_connection/kafka/kerberos_auth.md | 118 +++ .../db_connection/kafka/plaintext_protocol.md | 48 ++ .../db_connection/kafka/prerequisites.md | 65 ++ .../db_connection/kafka/protocol.md | 37 + mddocs/connection/db_connection/kafka/read.md | 172 ++++ .../db_connection/kafka/scram_auth.md | 79 ++ .../connection/db_connection/kafka/slots.md | 136 ++++ .../db_connection/kafka/ssl_protocol.md | 149 ++++ .../db_connection/kafka/troubleshooting.md | 10 + .../connection/db_connection/kafka/write.md | 118 +++ .../db_connection/mongodb/connection.md | 124 +++ .../connection/db_connection/mongodb/index.md | 18 + .../db_connection/mongodb/pipeline.md | 152 ++++ .../db_connection/mongodb/prerequisites.md | 72 ++ .../connection/db_connection/mongodb/read.md | 157 ++++ .../connection/db_connection/mongodb/types.md | 214 +++++ .../connection/db_connection/mongodb/write.md | 118 +++ .../db_connection/mssql/connection.md | 186 +++++ .../connection/db_connection/mssql/execute.md | 184 +++++ .../connection/db_connection/mssql/index.md | 19 + .../db_connection/mssql/prerequisites.md | 78 ++ mddocs/connection/db_connection/mssql/read.md | 339 ++++++++ mddocs/connection/db_connection/mssql/sql.md | 191 +++++ .../connection/db_connection/mssql/types.md | 281 +++++++ .../connection/db_connection/mssql/write.md | 183 +++++ .../db_connection/mysql/connection.md | 120 +++ .../connection/db_connection/mysql/execute.md | 191 +++++ .../connection/db_connection/mysql/index.md | 19 + .../db_connection/mysql/prerequisites.md | 61 ++ mddocs/connection/db_connection/mysql/read.md | 352 +++++++++ mddocs/connection/db_connection/mysql/sql.md | 192 +++++ .../connection/db_connection/mysql/types.md | 292 +++++++ .../connection/db_connection/mysql/write.md | 187 +++++ .../db_connection/oracle/connection.md | 143 ++++ .../db_connection/oracle/execute.md | 191 +++++ .../connection/db_connection/oracle/index.md | 19 + .../db_connection/oracle/prerequisites.md | 110 +++ .../connection/db_connection/oracle/read.md | 352 +++++++++ mddocs/connection/db_connection/oracle/sql.md | 192 +++++ .../connection/db_connection/oracle/types.md | 303 +++++++ .../connection/db_connection/oracle/write.md | 183 +++++ .../db_connection/postgres/connection.md | 133 ++++ .../db_connection/postgres/execute.md | 189 +++++ .../db_connection/postgres/index.md | 19 + .../db_connection/postgres/prerequisites.md | 71 ++ .../connection/db_connection/postgres/read.md | 350 +++++++++ .../connection/db_connection/postgres/sql.md | 191 +++++ .../db_connection/postgres/types.md | 372 +++++++++ .../db_connection/postgres/write.md | 183 +++++ .../db_connection/teradata/connection.md | 134 ++++ .../db_connection/teradata/execute.md | 189 +++++ .../db_connection/teradata/index.md | 15 + .../db_connection/teradata/prerequisites.md | 57 ++ .../connection/db_connection/teradata/read.md | 379 +++++++++ .../connection/db_connection/teradata/sql.md | 190 +++++ .../db_connection/teradata/write.md | 250 ++++++ mddocs/connection/file_connection/ftp.md | 627 +++++++++++++++ mddocs/connection/file_connection/ftps.md | 627 +++++++++++++++ .../file_connection/hdfs/connection.md | 740 ++++++++++++++++++ .../connection/file_connection/hdfs/index.md | 11 + .../connection/file_connection/hdfs/slots.md | 256 ++++++ mddocs/connection/file_connection/index.md | 13 + mddocs/connection/file_connection/s3.md | 598 ++++++++++++++ mddocs/connection/file_connection/samba.md | 548 +++++++++++++ mddocs/connection/file_connection/sftp.md | 635 +++++++++++++++ mddocs/connection/file_connection/webdav.md | 592 ++++++++++++++ mddocs/connection/file_df_connection/base.md | 63 ++ mddocs/connection/file_df_connection/index.md | 20 + .../spark_hdfs/connection.md | 169 ++++ .../file_df_connection/spark_hdfs/index.md | 12 + .../spark_hdfs/prerequisites.md | 46 ++ .../file_df_connection/spark_hdfs/slots.md | 256 ++++++ .../file_df_connection/spark_local_fs.md | 65 ++ .../file_df_connection/spark_s3/connection.md | 232 ++++++ .../file_df_connection/spark_s3/index.md | 9 + .../spark_s3/prerequisites.md | 61 ++ .../spark_s3/troubleshooting.md | 366 +++++++++ mddocs/connection/index.md | 34 + mddocs/contributing.md | 391 +++++++++ mddocs/db/db_reader.md | 346 ++++++++ mddocs/db/db_writer.md | 100 +++ mddocs/db/index.md | 6 + .../file/file_downloader/file_downloader.md | 352 +++++++++ mddocs/file/file_downloader/index.md | 9 + mddocs/file/file_downloader/options.md | 67 ++ mddocs/file/file_downloader/result.md | 550 +++++++++++++ mddocs/file/file_filters/base.md | 42 + mddocs/file/file_filters/exclude_dir.md | 47 ++ mddocs/file/file_filters/file_filter.md | 77 ++ mddocs/file/file_filters/file_mtime_filter.md | 70 ++ mddocs/file/file_filters/file_size_filter.md | 74 ++ mddocs/file/file_filters/glob.md | 47 ++ mddocs/file/file_filters/index.md | 20 + mddocs/file/file_filters/match_all_filters.md | 37 + mddocs/file/file_filters/regexp.md | 59 ++ mddocs/file/file_limits/base.md | 108 +++ mddocs/file/file_limits/file_limit.md | 114 +++ mddocs/file/file_limits/index.md | 19 + mddocs/file/file_limits/limits_reached.md | 38 + mddocs/file/file_limits/limits_stop_at.md | 37 + mddocs/file/file_limits/max_files_count.md | 114 +++ mddocs/file/file_limits/reset_limits.md | 39 + mddocs/file/file_limits/total_files_size.md | 122 +++ mddocs/file/file_mover/file_mover.md | 243 ++++++ mddocs/file/file_mover/index.md | 9 + mddocs/file/file_mover/options.md | 55 ++ mddocs/file/file_mover/result.md | 550 +++++++++++++ mddocs/file/file_uploader/file_uploader.md | 241 ++++++ mddocs/file/file_uploader/index.md | 9 + mddocs/file/file_uploader/options.md | 67 ++ mddocs/file/file_uploader/result.md | 550 +++++++++++++ mddocs/file/index.md | 9 + .../file_df/file_df_reader/file_df_reader.md | 158 ++++ mddocs/file_df/file_df_reader/index.md | 8 + mddocs/file_df/file_df_reader/options.md | 38 + .../file_df/file_df_writer/file_df_writer.md | 80 ++ mddocs/file_df/file_df_writer/index.md | 8 + mddocs/file_df/file_df_writer/options.md | 140 ++++ mddocs/file_df/file_formats/avro.md | 390 +++++++++ mddocs/file_df/file_formats/base.md | 73 ++ mddocs/file_df/file_formats/csv.md | 539 +++++++++++++ mddocs/file_df/file_formats/excel.md | 232 ++++++ mddocs/file_df/file_formats/index.md | 18 + mddocs/file_df/file_formats/json.md | 414 ++++++++++ mddocs/file_df/file_formats/jsonline.md | 314 ++++++++ mddocs/file_df/file_formats/orc.md | 80 ++ mddocs/file_df/file_formats/parquet.md | 79 ++ mddocs/file_df/file_formats/xml.md | 440 +++++++++++ mddocs/file_df/index.md | 7 + mddocs/hooks/design.md | 642 +++++++++++++++ mddocs/hooks/global_state.md | 101 +++ mddocs/hooks/hook.md | 223 ++++++ mddocs/hooks/index.md | 14 + mddocs/hooks/slot.md | 261 ++++++ mddocs/hooks/support_hooks.md | 161 ++++ mddocs/hwm_store/index.md | 9 + mddocs/hwm_store/yaml_hwm_store.md | 161 ++++ mddocs/index.md | 9 + mddocs/install/files.md | 20 + mddocs/install/full.md | 15 + mddocs/install/index.md | 33 + mddocs/install/kerberos.md | 32 + mddocs/install/spark.md | 342 ++++++++ mddocs/logging.md | 236 ++++++ mddocs/plugins.md | 147 ++++ mddocs/quickstart.md | 365 +++++++++ mddocs/security.md | 25 + mddocs/strategy/incremental_batch_strategy.md | 301 +++++++ mddocs/strategy/incremental_strategy.md | 396 ++++++++++ mddocs/strategy/index.md | 10 + mddocs/strategy/snapshot_batch_strategy.md | 281 +++++++ mddocs/strategy/snapshot_strategy.md | 96 +++ mddocs/troubleshooting/index.md | 17 + mddocs/troubleshooting/spark.md | 72 ++ 223 files changed, 34365 insertions(+) create mode 100644 mddocs/README.md create mode 100644 mddocs/_sphinx_design_static/design-tabs.js create mode 100644 mddocs/_sphinx_design_static/sphinx-design.min.css create mode 100644 mddocs/_static/autodoc_pydantic.css create mode 100644 mddocs/_static/icon.svg create mode 100644 mddocs/_static/logo.svg create mode 100644 mddocs/_static/logo_wide.svg create mode 100644 mddocs/changelog.md create mode 100644 mddocs/changelog/0.10.0.md create mode 100644 mddocs/changelog/0.10.1.md create mode 100644 mddocs/changelog/0.10.2.md create mode 100644 mddocs/changelog/0.11.0.md create mode 100644 mddocs/changelog/0.11.1.md create mode 100644 mddocs/changelog/0.11.2.md create mode 100644 mddocs/changelog/0.12.0.md create mode 100644 mddocs/changelog/0.12.1.md create mode 100644 mddocs/changelog/0.12.2.md create mode 100644 mddocs/changelog/0.12.3.md create mode 100644 mddocs/changelog/0.12.4.md create mode 100644 mddocs/changelog/0.12.5.md create mode 100644 mddocs/changelog/0.13.0.md create mode 100644 mddocs/changelog/0.13.1.md create mode 100644 mddocs/changelog/0.13.3.md create mode 100644 mddocs/changelog/0.13.4.md create mode 100644 mddocs/changelog/0.7.0.md create mode 100644 mddocs/changelog/0.7.1.md create mode 100644 mddocs/changelog/0.7.2.md create mode 100644 mddocs/changelog/0.8.0.md create mode 100644 mddocs/changelog/0.8.1.md create mode 100644 mddocs/changelog/0.9.0.md create mode 100644 mddocs/changelog/0.9.1.md create mode 100644 mddocs/changelog/0.9.2.md create mode 100644 mddocs/changelog/0.9.3.md create mode 100644 mddocs/changelog/0.9.4.md create mode 100644 mddocs/changelog/0.9.5.md create mode 100644 mddocs/changelog/DRAFT.md create mode 100644 mddocs/changelog/NEXT_RELEASE.md create mode 100644 mddocs/changelog/index.md create mode 100644 mddocs/concepts.md create mode 100644 mddocs/conf.py create mode 100644 mddocs/connection/db_connection/clickhouse/connection.md create mode 100644 mddocs/connection/db_connection/clickhouse/execute.md create mode 100644 mddocs/connection/db_connection/clickhouse/index.md create mode 100644 mddocs/connection/db_connection/clickhouse/prerequisites.md create mode 100644 mddocs/connection/db_connection/clickhouse/read.md create mode 100644 mddocs/connection/db_connection/clickhouse/sql.md create mode 100644 mddocs/connection/db_connection/clickhouse/types.md create mode 100644 mddocs/connection/db_connection/clickhouse/write.md create mode 100644 mddocs/connection/db_connection/greenplum/connection.md create mode 100644 mddocs/connection/db_connection/greenplum/execute.md create mode 100644 mddocs/connection/db_connection/greenplum/index.md create mode 100644 mddocs/connection/db_connection/greenplum/prerequisites.md create mode 100644 mddocs/connection/db_connection/greenplum/read.md create mode 100644 mddocs/connection/db_connection/greenplum/types.md create mode 100644 mddocs/connection/db_connection/greenplum/write.md create mode 100644 mddocs/connection/db_connection/hive/connection.md create mode 100644 mddocs/connection/db_connection/hive/execute.md create mode 100644 mddocs/connection/db_connection/hive/index.md create mode 100644 mddocs/connection/db_connection/hive/prerequisites.md create mode 100644 mddocs/connection/db_connection/hive/read.md create mode 100644 mddocs/connection/db_connection/hive/slots.md create mode 100644 mddocs/connection/db_connection/hive/sql.md create mode 100644 mddocs/connection/db_connection/hive/write.md create mode 100644 mddocs/connection/db_connection/index.md create mode 100644 mddocs/connection/db_connection/kafka/auth.md create mode 100644 mddocs/connection/db_connection/kafka/basic_auth.md create mode 100644 mddocs/connection/db_connection/kafka/connection.md create mode 100644 mddocs/connection/db_connection/kafka/index.md create mode 100644 mddocs/connection/db_connection/kafka/kerberos_auth.md create mode 100644 mddocs/connection/db_connection/kafka/plaintext_protocol.md create mode 100644 mddocs/connection/db_connection/kafka/prerequisites.md create mode 100644 mddocs/connection/db_connection/kafka/protocol.md create mode 100644 mddocs/connection/db_connection/kafka/read.md create mode 100644 mddocs/connection/db_connection/kafka/scram_auth.md create mode 100644 mddocs/connection/db_connection/kafka/slots.md create mode 100644 mddocs/connection/db_connection/kafka/ssl_protocol.md create mode 100644 mddocs/connection/db_connection/kafka/troubleshooting.md create mode 100644 mddocs/connection/db_connection/kafka/write.md create mode 100644 mddocs/connection/db_connection/mongodb/connection.md create mode 100644 mddocs/connection/db_connection/mongodb/index.md create mode 100644 mddocs/connection/db_connection/mongodb/pipeline.md create mode 100644 mddocs/connection/db_connection/mongodb/prerequisites.md create mode 100644 mddocs/connection/db_connection/mongodb/read.md create mode 100644 mddocs/connection/db_connection/mongodb/types.md create mode 100644 mddocs/connection/db_connection/mongodb/write.md create mode 100644 mddocs/connection/db_connection/mssql/connection.md create mode 100644 mddocs/connection/db_connection/mssql/execute.md create mode 100644 mddocs/connection/db_connection/mssql/index.md create mode 100644 mddocs/connection/db_connection/mssql/prerequisites.md create mode 100644 mddocs/connection/db_connection/mssql/read.md create mode 100644 mddocs/connection/db_connection/mssql/sql.md create mode 100644 mddocs/connection/db_connection/mssql/types.md create mode 100644 mddocs/connection/db_connection/mssql/write.md create mode 100644 mddocs/connection/db_connection/mysql/connection.md create mode 100644 mddocs/connection/db_connection/mysql/execute.md create mode 100644 mddocs/connection/db_connection/mysql/index.md create mode 100644 mddocs/connection/db_connection/mysql/prerequisites.md create mode 100644 mddocs/connection/db_connection/mysql/read.md create mode 100644 mddocs/connection/db_connection/mysql/sql.md create mode 100644 mddocs/connection/db_connection/mysql/types.md create mode 100644 mddocs/connection/db_connection/mysql/write.md create mode 100644 mddocs/connection/db_connection/oracle/connection.md create mode 100644 mddocs/connection/db_connection/oracle/execute.md create mode 100644 mddocs/connection/db_connection/oracle/index.md create mode 100644 mddocs/connection/db_connection/oracle/prerequisites.md create mode 100644 mddocs/connection/db_connection/oracle/read.md create mode 100644 mddocs/connection/db_connection/oracle/sql.md create mode 100644 mddocs/connection/db_connection/oracle/types.md create mode 100644 mddocs/connection/db_connection/oracle/write.md create mode 100644 mddocs/connection/db_connection/postgres/connection.md create mode 100644 mddocs/connection/db_connection/postgres/execute.md create mode 100644 mddocs/connection/db_connection/postgres/index.md create mode 100644 mddocs/connection/db_connection/postgres/prerequisites.md create mode 100644 mddocs/connection/db_connection/postgres/read.md create mode 100644 mddocs/connection/db_connection/postgres/sql.md create mode 100644 mddocs/connection/db_connection/postgres/types.md create mode 100644 mddocs/connection/db_connection/postgres/write.md create mode 100644 mddocs/connection/db_connection/teradata/connection.md create mode 100644 mddocs/connection/db_connection/teradata/execute.md create mode 100644 mddocs/connection/db_connection/teradata/index.md create mode 100644 mddocs/connection/db_connection/teradata/prerequisites.md create mode 100644 mddocs/connection/db_connection/teradata/read.md create mode 100644 mddocs/connection/db_connection/teradata/sql.md create mode 100644 mddocs/connection/db_connection/teradata/write.md create mode 100644 mddocs/connection/file_connection/ftp.md create mode 100644 mddocs/connection/file_connection/ftps.md create mode 100644 mddocs/connection/file_connection/hdfs/connection.md create mode 100644 mddocs/connection/file_connection/hdfs/index.md create mode 100644 mddocs/connection/file_connection/hdfs/slots.md create mode 100644 mddocs/connection/file_connection/index.md create mode 100644 mddocs/connection/file_connection/s3.md create mode 100644 mddocs/connection/file_connection/samba.md create mode 100644 mddocs/connection/file_connection/sftp.md create mode 100644 mddocs/connection/file_connection/webdav.md create mode 100644 mddocs/connection/file_df_connection/base.md create mode 100644 mddocs/connection/file_df_connection/index.md create mode 100644 mddocs/connection/file_df_connection/spark_hdfs/connection.md create mode 100644 mddocs/connection/file_df_connection/spark_hdfs/index.md create mode 100644 mddocs/connection/file_df_connection/spark_hdfs/prerequisites.md create mode 100644 mddocs/connection/file_df_connection/spark_hdfs/slots.md create mode 100644 mddocs/connection/file_df_connection/spark_local_fs.md create mode 100644 mddocs/connection/file_df_connection/spark_s3/connection.md create mode 100644 mddocs/connection/file_df_connection/spark_s3/index.md create mode 100644 mddocs/connection/file_df_connection/spark_s3/prerequisites.md create mode 100644 mddocs/connection/file_df_connection/spark_s3/troubleshooting.md create mode 100644 mddocs/connection/index.md create mode 100644 mddocs/contributing.md create mode 100644 mddocs/db/db_reader.md create mode 100644 mddocs/db/db_writer.md create mode 100644 mddocs/db/index.md create mode 100644 mddocs/file/file_downloader/file_downloader.md create mode 100644 mddocs/file/file_downloader/index.md create mode 100644 mddocs/file/file_downloader/options.md create mode 100644 mddocs/file/file_downloader/result.md create mode 100644 mddocs/file/file_filters/base.md create mode 100644 mddocs/file/file_filters/exclude_dir.md create mode 100644 mddocs/file/file_filters/file_filter.md create mode 100644 mddocs/file/file_filters/file_mtime_filter.md create mode 100644 mddocs/file/file_filters/file_size_filter.md create mode 100644 mddocs/file/file_filters/glob.md create mode 100644 mddocs/file/file_filters/index.md create mode 100644 mddocs/file/file_filters/match_all_filters.md create mode 100644 mddocs/file/file_filters/regexp.md create mode 100644 mddocs/file/file_limits/base.md create mode 100644 mddocs/file/file_limits/file_limit.md create mode 100644 mddocs/file/file_limits/index.md create mode 100644 mddocs/file/file_limits/limits_reached.md create mode 100644 mddocs/file/file_limits/limits_stop_at.md create mode 100644 mddocs/file/file_limits/max_files_count.md create mode 100644 mddocs/file/file_limits/reset_limits.md create mode 100644 mddocs/file/file_limits/total_files_size.md create mode 100644 mddocs/file/file_mover/file_mover.md create mode 100644 mddocs/file/file_mover/index.md create mode 100644 mddocs/file/file_mover/options.md create mode 100644 mddocs/file/file_mover/result.md create mode 100644 mddocs/file/file_uploader/file_uploader.md create mode 100644 mddocs/file/file_uploader/index.md create mode 100644 mddocs/file/file_uploader/options.md create mode 100644 mddocs/file/file_uploader/result.md create mode 100644 mddocs/file/index.md create mode 100644 mddocs/file_df/file_df_reader/file_df_reader.md create mode 100644 mddocs/file_df/file_df_reader/index.md create mode 100644 mddocs/file_df/file_df_reader/options.md create mode 100644 mddocs/file_df/file_df_writer/file_df_writer.md create mode 100644 mddocs/file_df/file_df_writer/index.md create mode 100644 mddocs/file_df/file_df_writer/options.md create mode 100644 mddocs/file_df/file_formats/avro.md create mode 100644 mddocs/file_df/file_formats/base.md create mode 100644 mddocs/file_df/file_formats/csv.md create mode 100644 mddocs/file_df/file_formats/excel.md create mode 100644 mddocs/file_df/file_formats/index.md create mode 100644 mddocs/file_df/file_formats/json.md create mode 100644 mddocs/file_df/file_formats/jsonline.md create mode 100644 mddocs/file_df/file_formats/orc.md create mode 100644 mddocs/file_df/file_formats/parquet.md create mode 100644 mddocs/file_df/file_formats/xml.md create mode 100644 mddocs/file_df/index.md create mode 100644 mddocs/hooks/design.md create mode 100644 mddocs/hooks/global_state.md create mode 100644 mddocs/hooks/hook.md create mode 100644 mddocs/hooks/index.md create mode 100644 mddocs/hooks/slot.md create mode 100644 mddocs/hooks/support_hooks.md create mode 100644 mddocs/hwm_store/index.md create mode 100644 mddocs/hwm_store/yaml_hwm_store.md create mode 100644 mddocs/index.md create mode 100644 mddocs/install/files.md create mode 100644 mddocs/install/full.md create mode 100644 mddocs/install/index.md create mode 100644 mddocs/install/kerberos.md create mode 100644 mddocs/install/spark.md create mode 100644 mddocs/logging.md create mode 100644 mddocs/plugins.md create mode 100644 mddocs/quickstart.md create mode 100644 mddocs/security.md create mode 100644 mddocs/strategy/incremental_batch_strategy.md create mode 100644 mddocs/strategy/incremental_strategy.md create mode 100644 mddocs/strategy/index.md create mode 100644 mddocs/strategy/snapshot_batch_strategy.md create mode 100644 mddocs/strategy/snapshot_strategy.md create mode 100644 mddocs/troubleshooting/index.md create mode 100644 mddocs/troubleshooting/spark.md diff --git a/.gitignore b/.gitignore index 31223eb62..ea2d3b6d9 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,9 @@ docs/_build/ docs/build docs/*.tar.gz +# Sphinx mddocs +mddocs/_build/ + # PyBuilder target/ diff --git a/mddocs/README.md b/mddocs/README.md new file mode 100644 index 000000000..ffb6fc0c5 --- /dev/null +++ b/mddocs/README.md @@ -0,0 +1,657 @@ +# onETL + +[![Repo status - Active](https://www.repostatus.org/badges/latest/active.svg)](https://github.com/MobileTeleSystems/onetl) [![PyPI - Latest Release](https://img.shields.io/pypi/v/onetl)](https://pypi.org/project/onetl/) [![PyPI - License](https://img.shields.io/pypi/l/onetl.svg)](https://github.com/MobileTeleSystems/onetl/blob/develop/LICENSE.txt) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/onetl.svg)](https://pypi.org/project/onetl/) [![PyPI - Downloads](https://img.shields.io/pypi/dm/onetl)](https://pypi.org/project/onetl/) +[![Documentation - ReadTheDocs](https://readthedocs.org/projects/onetl/badge/?version=stable)](https://onetl.readthedocs.io/) [![Github Actions - latest CI build status](https://github.com/MobileTeleSystems/onetl/workflows/Tests/badge.svg)](https://github.com/MobileTeleSystems/onetl/actions) [![Test coverage - percent](https://codecov.io/gh/MobileTeleSystems/onetl/branch/develop/graph/badge.svg?token=RIO8URKNZJ)](https://codecov.io/gh/MobileTeleSystems/onetl) [![pre-commit.ci - status](https://results.pre-commit.ci/badge/github/MobileTeleSystems/onetl/develop.svg)](https://results.pre-commit.ci/latest/github/MobileTeleSystems/onetl/develop) + + + +![onETL logo](_static/logo_wide.svg) + + + +## What is onETL? + +Python ETL/ELT library powered by [Apache Spark](https://spark.apache.org/) & other open-source tools. + +## Goals + +* Provide unified classes to extract data from (**E**) & load data to (**L**) various stores. +* Provides [Spark DataFrame API](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.DataFrame.html) for performing transformations (**T**) in terms of *ETL*. +* Provide direct assess to database, allowing to execute SQL queries, as well as DDL, DML, and call functions/procedures. This can be used for building up *ELT* pipelines. +* Support different [read strategies](https://onetl.readthedocs.io/en/stable/strategy/index.html) for incremental and batch data fetching. +* Provide [hooks](https://onetl.readthedocs.io/en/stable/hooks/index.html) & [plugins](https://onetl.readthedocs.io/en/stable/plugins.html) mechanism for altering behavior of internal classes. + +## Non-goals + +* onETL is not a Spark replacement. It just provides additional functionality that Spark does not have, and improves UX for end users. +* onETL is not a framework, as it does not have requirements to project structure, naming, the way of running ETL/ELT processes, configuration, etc. All of that should be implemented in some other tool. +* onETL is deliberately developed without any integration with scheduling software like Apache Airflow. All integrations should be implemented as separated tools. +* Only batch operations, no streaming. For streaming prefer [Apache Flink](https://flink.apache.org/). + +## Requirements + +* **Python 3.7 - 3.13** +* PySpark 2.3.x - 3.5.x (depends on used connector) +* Java 8+ (required by Spark, see below) +* Kerberos libs & GCC (required by `Hive`, `HDFS` and `SparkHDFS` connectors) + +## Supported storages + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeStoragePowered by
DatabaseClickhouseApache Spark JDBC Data Source
MSSQL
MySQL
Postgres
Oracle
Teradata
HiveApache Spark Hive integration
KafkaApache Spark Kafka integration
GreenplumVMware Greenplum Spark connector
MongoDBMongoDB Spark connector
FileHDFSHDFS Python client
S3minio-py client
SFTPParamiko library
FTPFTPUtil library
FTPS
WebDAVWebdavClient3 library
Sambapysmb library
Files as DataFrameSparkLocalFSApache Spark File Data Source
SparkHDFS
SparkS3Hadoop AWS library
+ + + + +## Documentation + +See [https://onetl.readthedocs.io/](https://onetl.readthedocs.io/) + +## How to install + + + +### Minimal installation + + + +Base `onetl` package contains: + +* `DBReader`, `DBWriter` and related classes +* `FileDownloader`, `FileUploader`, `FileMover` and related classes, like file filters & limits +* `FileDFReader`, `FileDFWriter` and related classes, like file formats +* Read Strategies & HWM classes +* Plugins support + +It can be installed via: + +```bash +pip install onetl +``` + +```{admonition} WARNING +:class: warning + +This method does NOT include any connections. + +This method is recommended for use in third-party libraries which require for `onetl` to be installed, but do not use its connection classes. +``` + +### With DB and FileDF connections + + + +All DB connection classes (`Clickhouse`, `Greenplum`, `Hive` and others) +and all FileDF connection classes (`SparkHDFS`, `SparkLocalFS`, `SparkS3`) +require Spark to be installed. + + + +Firstly, you should install JDK. The exact installation instruction depends on your OS, here are some examples: + +```bash +yum install java-1.8.0-openjdk-devel # CentOS 7 + Spark 2 +dnf install java-11-openjdk-devel # CentOS 8 + Spark 3 +apt-get install openjdk-11-jdk # Debian-based + Spark 3 +``` + + + +#### Compatibility matrix + +| Spark | Python | Java | Scala | +|-----------------------------------------------------------|------------|------------|---------| +| [2.3.x](https://spark.apache.org/docs/2.3.1/#downloading) | 3.7 only | 8 only | 2.11 | +| [2.4.x](https://spark.apache.org/docs/2.4.8/#downloading) | 3.7 only | 8 only | 2.11 | +| [3.2.x](https://spark.apache.org/docs/3.2.4/#downloading) | 3.7 - 3.10 | 8u201 - 11 | 2.12 | +| [3.3.x](https://spark.apache.org/docs/3.3.4/#downloading) | 3.7 - 3.12 | 8u201 - 17 | 2.12 | +| [3.4.x](https://spark.apache.org/docs/3.4.4/#downloading) | 3.7 - 3.12 | 8u362 - 20 | 2.12 | +| [3.5.x](https://spark.apache.org/docs/3.5.5/#downloading) | 3.8 - 3.13 | 8u371 - 20 | 2.12 | + + + +Then you should install PySpark via passing `spark` to `extras`: + +```bash +pip install onetl[spark] # install latest PySpark +``` + +or install PySpark explicitly: + +```bash +pip install onetl pyspark==3.5.5 # install a specific PySpark version +``` + +or inject PySpark to `sys.path` in some other way BEFORE creating a class instance. +**Otherwise connection object cannot be created.** + +### With File connections + + + +All File (but not *FileDF*) connection classes (`FTP`, `SFTP`, `HDFS` and so on) requires specific Python clients to be installed. + +Each client can be installed explicitly by passing connector name (in lowercase) to `extras`: + +```bash +pip install onetl[ftp] # specific connector +pip install onetl[ftp,ftps,sftp,hdfs,s3,webdav,samba] # multiple connectors +``` + +To install all file connectors at once you can pass `files` to `extras`: + +```bash +pip install onetl[files] +``` + +**Otherwise class import will fail.** + +### With Kerberos support + + + +Most of Hadoop instances set up with Kerberos support, +so some connections require additional setup to work properly. + +* `HDFS` + Uses [requests-kerberos](https://pypi.org/project/requests-kerberos/) and + [GSSApi](https://pypi.org/project/gssapi/) for authentication. + It also uses `kinit` executable to generate Kerberos ticket. +* `Hive` and `SparkHDFS` + require Kerberos ticket to exist before creating Spark session. + +So you need to install OS packages with: + +* `krb5` libs +* Headers for `krb5` +* `gcc` or other compiler for C sources + +The exact installation instruction depends on your OS, here are some examples: + +```bash +apt install libkrb5-dev krb5-user gcc # Debian-based +dnf install krb5-devel krb5-libs krb5-workstation gcc # CentOS, OracleLinux +``` + +Also you should pass `kerberos` to `extras` to install required Python packages: + +```bash +pip install onetl[kerberos] +``` + +### Full bundle + + + +To install all connectors and dependencies, you can pass `all` into `extras`: + +```bash +pip install onetl[all] + +# this is just the same as +pip install onetl[spark,files,kerberos] +``` + + +```{admonition} WARNING +:class: warning + +This method consumes a lot of disk space, and requires for Java & Kerberos libraries to be installed into your OS. +``` + + + +## Quick start + +### MSSQL → Hive + +Read data from MSSQL, transform & write to Hive. + +```bash +# install onETL and PySpark +pip install onetl[spark] +``` + +```python +# Import pyspark to initialize the SparkSession +from pyspark.sql import SparkSession + +# import function to setup onETL logging +from onetl.log import setup_logging + +# Import required connections +from onetl.connection import MSSQL, Hive + +# Import onETL classes to read & write data +from onetl.db import DBReader, DBWriter + +# change logging level to INFO, and set up default logging format and handler +setup_logging() + +# Initialize new SparkSession with MSSQL driver loaded +maven_packages = MSSQL.get_packages() +spark = ( + SparkSession.builder.appName("spark_app_onetl_demo") + .config("spark.jars.packages", ",".join(maven_packages)) + .enableHiveSupport() # for Hive + .getOrCreate() +) + +# Initialize MSSQL connection and check if database is accessible +mssql = MSSQL( + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, + # These options are passed to MSSQL JDBC Driver: + extra={"applicationIntent": "ReadOnly"}, +).check() + +# >>> INFO:|MSSQL| Connection is available + +# Initialize DBReader +reader = DBReader( + connection=mssql, + source="dbo.demo_table", + columns=["on", "etl"], + # Set some MSSQL read options: + options=MSSQL.ReadOptions(fetchsize=10000), +) + +# checks that there is data in the table, otherwise raises exception +reader.raise_if_no_data() + +# Read data to DataFrame +df = reader.run() +df.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) + +# Apply any PySpark transformations +from pyspark.sql.functions import lit + +df_to_write = df.withColumn("engine", lit("onetl")) +df_to_write.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) +# |-- engine: string (nullable = false) + +# Initialize Hive connection +hive = Hive(cluster="rnd-dwh", spark=spark) + +# Initialize DBWriter +db_writer = DBWriter( + connection=hive, + target="dl_sb.demo_table", + # Set some Hive write options: + options=Hive.WriteOptions(if_exists="replace_entire_table"), +) + +# Write data from DataFrame to Hive +db_writer.run(df_to_write) + +# Success! +``` + +### SFTP → HDFS + +Download files from SFTP & upload them to HDFS. + +```bash +# install onETL with SFTP and HDFS clients, and Kerberos support +pip install onetl[hdfs,sftp,kerberos] +``` + +```python +# import function to setup onETL logging +from onetl.log import setup_logging + +# Import required connections +from onetl.connection import SFTP, HDFS + +# Import onETL classes to download & upload files +from onetl.file import FileDownloader, FileUploader + +# import filter & limit classes +from onetl.file.filter import Glob, ExcludeDir +from onetl.file.limit import MaxFilesCount + +# change logging level to INFO, and set up default logging format and handler +setup_logging() + +# Initialize SFTP connection and check it +sftp = SFTP( + host="sftp.test.com", + user="someuser", + password="somepassword", +).check() + +# >>> INFO:|SFTP| Connection is available + +# Initialize downloader +file_downloader = FileDownloader( + connection=sftp, + source_path="/remote/tests/Report", # path on SFTP + local_path="/local/onetl/Report", # local fs path + filters=[ + # download only files matching the glob + Glob("*.csv"), + # exclude files from this directory + ExcludeDir("/remote/tests/Report/exclude_dir/"), + ], + limits=[ + # download max 1000 files per run + MaxFilesCount(1000), + ], + options=FileDownloader.Options( + # delete files from SFTP after successful download + delete_source=True, + # mark file as failed if it already exist in local_path + if_exists="error", + ), +) + +# Download files to local filesystem +download_result = downloader.run() + +# Method run returns a DownloadResult object, +# which contains collection of downloaded files, divided to 4 categories +download_result + +# DownloadResult( +# successful=[ +# LocalPath('/local/onetl/Report/file_1.json'), +# LocalPath('/local/onetl/Report/file_2.json'), +# ], +# failed=[FailedRemoteFile('/remote/onetl/Report/file_3.json')], +# ignored=[RemoteFile('/remote/onetl/Report/file_4.json')], +# missing=[], +# ) + +# Raise exception if there are failed files, or there were no files in the remote filesystem +download_result.raise_if_failed() or download_result.raise_if_empty() + +# Do any kind of magic with files: rename files, remove header for csv files, ... +renamed_files = my_rename_function(download_result.success) + +# function removed "_" from file names +# [ +# LocalPath('/home/onetl/Report/file1.json'), +# LocalPath('/home/onetl/Report/file2.json'), +# ] + +# Initialize HDFS connection +hdfs = HDFS( + host="my.name.node", + user="someuser", + password="somepassword", # or keytab +) + +# Initialize uploader +file_uploader = FileUploader( + connection=hdfs, + target_path="/user/onetl/Report/", # hdfs path +) + +# Upload files from local fs to HDFS +upload_result = file_uploader.run(renamed_files) + +# Method run returns a UploadResult object, +# which contains collection of uploaded files, divided to 4 categories +upload_result + +# UploadResult( +# successful=[RemoteFile('/user/onetl/Report/file1.json')], +# failed=[FailedLocalFile('/local/onetl/Report/file2.json')], +# ignored=[], +# missing=[], +# ) + +# Raise exception if there are failed files, or there were no files in the local filesystem, or some input file is missing +upload_result.raise_if_failed() or upload_result.raise_if_empty() or upload_result.raise_if_missing() + +# Success! +``` + +### S3 → Postgres + +Read files directly from S3 path, convert them to dataframe, transform it and then write to a database. + +```bash +# install onETL and PySpark +pip install onetl[spark] +``` + +```python +# Import pyspark to initialize the SparkSession +from pyspark.sql import SparkSession + +# import function to setup onETL logging +from onetl.log import setup_logging + +# Import required connections +from onetl.connection import Postgres, SparkS3 + +# Import onETL classes to read files +from onetl.file import FileDFReader +from onetl.file.format import CSV + +# Import onETL classes to write data +from onetl.db import DBWriter + +# change logging level to INFO, and set up default logging format and handler +setup_logging() + +# Initialize new SparkSession with Hadoop AWS libraries and Postgres driver loaded +maven_packages = SparkS3.get_packages(spark_version="3.5.5") + Postgres.get_packages() +exclude_packages = SparkS3.get_exclude_packages() +spark = ( + SparkSession.builder.appName("spark_app_onetl_demo") + .config("spark.jars.packages", ",".join(maven_packages)) + .config("spark.jars.excludes", ",".join(exclude_packages)) + .getOrCreate() +) + +# Initialize S3 connection and check it +spark_s3 = SparkS3( + host="s3.test.com", + protocol="https", + bucket="my-bucket", + access_key="somekey", + secret_key="somesecret", + # Access bucket as s3.test.com/my-bucket + extra={"path.style.access": True}, + spark=spark, +).check() + +# >>> INFO:|SparkS3| Connection is available + +# Describe file format and parsing options +csv = CSV( + delimiter=";", + header=True, + encoding="utf-8", +) + +# Describe DataFrame schema of files +from pyspark.sql.types import ( + DateType, + DoubleType, + IntegerType, + StringType, + StructField, + StructType, + TimestampType, +) + +df_schema = StructType( + [ + StructField("id", IntegerType()), + StructField("phone_number", StringType()), + StructField("region", StringType()), + StructField("birth_date", DateType()), + StructField("registered_at", TimestampType()), + StructField("account_balance", DoubleType()), + ], +) + +# Initialize file df reader +reader = FileDFReader( + connection=spark_s3, + source_path="/remote/tests/Report", # path on S3 there *.csv files are located + format=csv, # file format with specific parsing options + df_schema=df_schema, # columns & types +) + +# Read files directly from S3 as Spark DataFrame +df = reader.run() + +# Check that DataFrame schema is same as expected +df.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) + +# Apply any PySpark transformations +from pyspark.sql.functions import lit + +df_to_write = df.withColumn("engine", lit("onetl")) +df_to_write.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) +# |-- engine: string (nullable = false) + +# Initialize Postgres connection +postgres = Postgres( + host="192.169.11.23", + user="onetl", + password="somepassword", + database="mydb", + spark=spark, +) + +# Initialize DBWriter +db_writer = DBWriter( + connection=postgres, + # write to specific table + target="public.my_table", + # with some writing options + options=Postgres.WriteOptions(if_exists="append"), +) + +# Write DataFrame to Postgres table +db_writer.run(df_to_write) + +# Success! +``` diff --git a/mddocs/_sphinx_design_static/design-tabs.js b/mddocs/_sphinx_design_static/design-tabs.js new file mode 100644 index 000000000..b25bd6a4f --- /dev/null +++ b/mddocs/_sphinx_design_static/design-tabs.js @@ -0,0 +1,101 @@ +// @ts-check + +// Extra JS capability for selected tabs to be synced +// The selection is stored in local storage so that it persists across page loads. + +/** + * @type {Record} + */ +let sd_id_to_elements = {}; +const storageKeyPrefix = "sphinx-design-tab-id-"; + +/** + * Create a key for a tab element. + * @param {HTMLElement} el - The tab element. + * @returns {[string, string, string] | null} - The key. + * + */ +function create_key(el) { + let syncId = el.getAttribute("data-sync-id"); + let syncGroup = el.getAttribute("data-sync-group"); + if (!syncId || !syncGroup) return null; + return [syncGroup, syncId, syncGroup + "--" + syncId]; +} + +/** + * Initialize the tab selection. + * + */ +function ready() { + // Find all tabs with sync data + + /** @type {string[]} */ + let groups = []; + + document.querySelectorAll(".sd-tab-label").forEach((label) => { + if (label instanceof HTMLElement) { + let data = create_key(label); + if (data) { + let [group, id, key] = data; + + // add click event listener + // @ts-ignore + label.onclick = onSDLabelClick; + + // store map of key to elements + if (!sd_id_to_elements[key]) { + sd_id_to_elements[key] = []; + } + sd_id_to_elements[key].push(label); + + if (groups.indexOf(group) === -1) { + groups.push(group); + // Check if a specific tab has been selected via URL parameter + const tabParam = new URLSearchParams(window.location.search).get( + group + ); + if (tabParam) { + console.log( + "sphinx-design: Selecting tab id for group '" + + group + + "' from URL parameter: " + + tabParam + ); + window.sessionStorage.setItem(storageKeyPrefix + group, tabParam); + } + } + + // Check is a specific tab has been selected previously + let previousId = window.sessionStorage.getItem( + storageKeyPrefix + group + ); + if (previousId === id) { + // console.log( + // "sphinx-design: Selecting tab from session storage: " + id + // ); + // @ts-ignore + label.previousElementSibling.checked = true; + } + } + } + }); +} + +/** + * Activate other tabs with the same sync id. + * + * @this {HTMLElement} - The element that was clicked. + */ +function onSDLabelClick() { + let data = create_key(this); + if (!data) return; + let [group, id, key] = data; + for (const label of sd_id_to_elements[key]) { + if (label === this) continue; + // @ts-ignore + label.previousElementSibling.checked = true; + } + window.sessionStorage.setItem(storageKeyPrefix + group, id); +} + +document.addEventListener("DOMContentLoaded", ready, false); diff --git a/mddocs/_sphinx_design_static/sphinx-design.min.css b/mddocs/_sphinx_design_static/sphinx-design.min.css new file mode 100644 index 000000000..860c36da0 --- /dev/null +++ b/mddocs/_sphinx_design_static/sphinx-design.min.css @@ -0,0 +1 @@ +.sd-bg-primary{background-color:var(--sd-color-primary) !important}.sd-bg-text-primary{color:var(--sd-color-primary-text) !important}button.sd-bg-primary:focus,button.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}a.sd-bg-primary:focus,a.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}.sd-bg-secondary{background-color:var(--sd-color-secondary) !important}.sd-bg-text-secondary{color:var(--sd-color-secondary-text) !important}button.sd-bg-secondary:focus,button.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}a.sd-bg-secondary:focus,a.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}.sd-bg-success{background-color:var(--sd-color-success) !important}.sd-bg-text-success{color:var(--sd-color-success-text) !important}button.sd-bg-success:focus,button.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}a.sd-bg-success:focus,a.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}.sd-bg-info{background-color:var(--sd-color-info) !important}.sd-bg-text-info{color:var(--sd-color-info-text) !important}button.sd-bg-info:focus,button.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}a.sd-bg-info:focus,a.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}.sd-bg-warning{background-color:var(--sd-color-warning) !important}.sd-bg-text-warning{color:var(--sd-color-warning-text) !important}button.sd-bg-warning:focus,button.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}a.sd-bg-warning:focus,a.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}.sd-bg-danger{background-color:var(--sd-color-danger) !important}.sd-bg-text-danger{color:var(--sd-color-danger-text) !important}button.sd-bg-danger:focus,button.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}a.sd-bg-danger:focus,a.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}.sd-bg-light{background-color:var(--sd-color-light) !important}.sd-bg-text-light{color:var(--sd-color-light-text) !important}button.sd-bg-light:focus,button.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}a.sd-bg-light:focus,a.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}.sd-bg-muted{background-color:var(--sd-color-muted) !important}.sd-bg-text-muted{color:var(--sd-color-muted-text) !important}button.sd-bg-muted:focus,button.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}a.sd-bg-muted:focus,a.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}.sd-bg-dark{background-color:var(--sd-color-dark) !important}.sd-bg-text-dark{color:var(--sd-color-dark-text) !important}button.sd-bg-dark:focus,button.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}a.sd-bg-dark:focus,a.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}.sd-bg-black{background-color:var(--sd-color-black) !important}.sd-bg-text-black{color:var(--sd-color-black-text) !important}button.sd-bg-black:focus,button.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}a.sd-bg-black:focus,a.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}.sd-bg-white{background-color:var(--sd-color-white) !important}.sd-bg-text-white{color:var(--sd-color-white-text) !important}button.sd-bg-white:focus,button.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}a.sd-bg-white:focus,a.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}.sd-text-primary,.sd-text-primary>p{color:var(--sd-color-primary) !important}a.sd-text-primary:focus,a.sd-text-primary:hover{color:var(--sd-color-primary-highlight) !important}.sd-text-secondary,.sd-text-secondary>p{color:var(--sd-color-secondary) !important}a.sd-text-secondary:focus,a.sd-text-secondary:hover{color:var(--sd-color-secondary-highlight) !important}.sd-text-success,.sd-text-success>p{color:var(--sd-color-success) !important}a.sd-text-success:focus,a.sd-text-success:hover{color:var(--sd-color-success-highlight) !important}.sd-text-info,.sd-text-info>p{color:var(--sd-color-info) !important}a.sd-text-info:focus,a.sd-text-info:hover{color:var(--sd-color-info-highlight) !important}.sd-text-warning,.sd-text-warning>p{color:var(--sd-color-warning) !important}a.sd-text-warning:focus,a.sd-text-warning:hover{color:var(--sd-color-warning-highlight) !important}.sd-text-danger,.sd-text-danger>p{color:var(--sd-color-danger) !important}a.sd-text-danger:focus,a.sd-text-danger:hover{color:var(--sd-color-danger-highlight) !important}.sd-text-light,.sd-text-light>p{color:var(--sd-color-light) !important}a.sd-text-light:focus,a.sd-text-light:hover{color:var(--sd-color-light-highlight) !important}.sd-text-muted,.sd-text-muted>p{color:var(--sd-color-muted) !important}a.sd-text-muted:focus,a.sd-text-muted:hover{color:var(--sd-color-muted-highlight) !important}.sd-text-dark,.sd-text-dark>p{color:var(--sd-color-dark) !important}a.sd-text-dark:focus,a.sd-text-dark:hover{color:var(--sd-color-dark-highlight) !important}.sd-text-black,.sd-text-black>p{color:var(--sd-color-black) !important}a.sd-text-black:focus,a.sd-text-black:hover{color:var(--sd-color-black-highlight) !important}.sd-text-white,.sd-text-white>p{color:var(--sd-color-white) !important}a.sd-text-white:focus,a.sd-text-white:hover{color:var(--sd-color-white-highlight) !important}.sd-outline-primary{border-color:var(--sd-color-primary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-primary:focus,a.sd-outline-primary:hover{border-color:var(--sd-color-primary-highlight) !important}.sd-outline-secondary{border-color:var(--sd-color-secondary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-secondary:focus,a.sd-outline-secondary:hover{border-color:var(--sd-color-secondary-highlight) !important}.sd-outline-success{border-color:var(--sd-color-success) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-success:focus,a.sd-outline-success:hover{border-color:var(--sd-color-success-highlight) !important}.sd-outline-info{border-color:var(--sd-color-info) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-info:focus,a.sd-outline-info:hover{border-color:var(--sd-color-info-highlight) !important}.sd-outline-warning{border-color:var(--sd-color-warning) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-warning:focus,a.sd-outline-warning:hover{border-color:var(--sd-color-warning-highlight) !important}.sd-outline-danger{border-color:var(--sd-color-danger) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-danger:focus,a.sd-outline-danger:hover{border-color:var(--sd-color-danger-highlight) !important}.sd-outline-light{border-color:var(--sd-color-light) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-light:focus,a.sd-outline-light:hover{border-color:var(--sd-color-light-highlight) !important}.sd-outline-muted{border-color:var(--sd-color-muted) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-muted:focus,a.sd-outline-muted:hover{border-color:var(--sd-color-muted-highlight) !important}.sd-outline-dark{border-color:var(--sd-color-dark) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-dark:focus,a.sd-outline-dark:hover{border-color:var(--sd-color-dark-highlight) !important}.sd-outline-black{border-color:var(--sd-color-black) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-black:focus,a.sd-outline-black:hover{border-color:var(--sd-color-black-highlight) !important}.sd-outline-white{border-color:var(--sd-color-white) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-white:focus,a.sd-outline-white:hover{border-color:var(--sd-color-white-highlight) !important}.sd-bg-transparent{background-color:transparent !important}.sd-outline-transparent{border-color:transparent !important}.sd-text-transparent{color:transparent !important}.sd-p-0{padding:0 !important}.sd-pt-0,.sd-py-0{padding-top:0 !important}.sd-pr-0,.sd-px-0{padding-right:0 !important}.sd-pb-0,.sd-py-0{padding-bottom:0 !important}.sd-pl-0,.sd-px-0{padding-left:0 !important}.sd-p-1{padding:.25rem !important}.sd-pt-1,.sd-py-1{padding-top:.25rem !important}.sd-pr-1,.sd-px-1{padding-right:.25rem !important}.sd-pb-1,.sd-py-1{padding-bottom:.25rem !important}.sd-pl-1,.sd-px-1{padding-left:.25rem !important}.sd-p-2{padding:.5rem !important}.sd-pt-2,.sd-py-2{padding-top:.5rem !important}.sd-pr-2,.sd-px-2{padding-right:.5rem !important}.sd-pb-2,.sd-py-2{padding-bottom:.5rem !important}.sd-pl-2,.sd-px-2{padding-left:.5rem !important}.sd-p-3{padding:1rem !important}.sd-pt-3,.sd-py-3{padding-top:1rem !important}.sd-pr-3,.sd-px-3{padding-right:1rem !important}.sd-pb-3,.sd-py-3{padding-bottom:1rem !important}.sd-pl-3,.sd-px-3{padding-left:1rem !important}.sd-p-4{padding:1.5rem !important}.sd-pt-4,.sd-py-4{padding-top:1.5rem !important}.sd-pr-4,.sd-px-4{padding-right:1.5rem !important}.sd-pb-4,.sd-py-4{padding-bottom:1.5rem !important}.sd-pl-4,.sd-px-4{padding-left:1.5rem !important}.sd-p-5{padding:3rem !important}.sd-pt-5,.sd-py-5{padding-top:3rem !important}.sd-pr-5,.sd-px-5{padding-right:3rem !important}.sd-pb-5,.sd-py-5{padding-bottom:3rem !important}.sd-pl-5,.sd-px-5{padding-left:3rem !important}.sd-m-auto{margin:auto !important}.sd-mt-auto,.sd-my-auto{margin-top:auto !important}.sd-mr-auto,.sd-mx-auto{margin-right:auto !important}.sd-mb-auto,.sd-my-auto{margin-bottom:auto !important}.sd-ml-auto,.sd-mx-auto{margin-left:auto !important}.sd-m-0{margin:0 !important}.sd-mt-0,.sd-my-0{margin-top:0 !important}.sd-mr-0,.sd-mx-0{margin-right:0 !important}.sd-mb-0,.sd-my-0{margin-bottom:0 !important}.sd-ml-0,.sd-mx-0{margin-left:0 !important}.sd-m-1{margin:.25rem !important}.sd-mt-1,.sd-my-1{margin-top:.25rem !important}.sd-mr-1,.sd-mx-1{margin-right:.25rem !important}.sd-mb-1,.sd-my-1{margin-bottom:.25rem !important}.sd-ml-1,.sd-mx-1{margin-left:.25rem !important}.sd-m-2{margin:.5rem !important}.sd-mt-2,.sd-my-2{margin-top:.5rem !important}.sd-mr-2,.sd-mx-2{margin-right:.5rem !important}.sd-mb-2,.sd-my-2{margin-bottom:.5rem !important}.sd-ml-2,.sd-mx-2{margin-left:.5rem !important}.sd-m-3{margin:1rem !important}.sd-mt-3,.sd-my-3{margin-top:1rem !important}.sd-mr-3,.sd-mx-3{margin-right:1rem !important}.sd-mb-3,.sd-my-3{margin-bottom:1rem !important}.sd-ml-3,.sd-mx-3{margin-left:1rem !important}.sd-m-4{margin:1.5rem !important}.sd-mt-4,.sd-my-4{margin-top:1.5rem !important}.sd-mr-4,.sd-mx-4{margin-right:1.5rem !important}.sd-mb-4,.sd-my-4{margin-bottom:1.5rem !important}.sd-ml-4,.sd-mx-4{margin-left:1.5rem !important}.sd-m-5{margin:3rem !important}.sd-mt-5,.sd-my-5{margin-top:3rem !important}.sd-mr-5,.sd-mx-5{margin-right:3rem !important}.sd-mb-5,.sd-my-5{margin-bottom:3rem !important}.sd-ml-5,.sd-mx-5{margin-left:3rem !important}.sd-w-25{width:25% !important}.sd-w-50{width:50% !important}.sd-w-75{width:75% !important}.sd-w-100{width:100% !important}.sd-w-auto{width:auto !important}.sd-h-25{height:25% !important}.sd-h-50{height:50% !important}.sd-h-75{height:75% !important}.sd-h-100{height:100% !important}.sd-h-auto{height:auto !important}.sd-d-none{display:none !important}.sd-d-inline{display:inline !important}.sd-d-inline-block{display:inline-block !important}.sd-d-block{display:block !important}.sd-d-grid{display:grid !important}.sd-d-flex-row{display:-ms-flexbox !important;display:flex !important;flex-direction:row !important}.sd-d-flex-column{display:-ms-flexbox !important;display:flex !important;flex-direction:column !important}.sd-d-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}@media(min-width: 576px){.sd-d-sm-none{display:none !important}.sd-d-sm-inline{display:inline !important}.sd-d-sm-inline-block{display:inline-block !important}.sd-d-sm-block{display:block !important}.sd-d-sm-grid{display:grid !important}.sd-d-sm-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-sm-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 768px){.sd-d-md-none{display:none !important}.sd-d-md-inline{display:inline !important}.sd-d-md-inline-block{display:inline-block !important}.sd-d-md-block{display:block !important}.sd-d-md-grid{display:grid !important}.sd-d-md-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-md-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 992px){.sd-d-lg-none{display:none !important}.sd-d-lg-inline{display:inline !important}.sd-d-lg-inline-block{display:inline-block !important}.sd-d-lg-block{display:block !important}.sd-d-lg-grid{display:grid !important}.sd-d-lg-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-lg-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 1200px){.sd-d-xl-none{display:none !important}.sd-d-xl-inline{display:inline !important}.sd-d-xl-inline-block{display:inline-block !important}.sd-d-xl-block{display:block !important}.sd-d-xl-grid{display:grid !important}.sd-d-xl-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-xl-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}.sd-align-major-start{justify-content:flex-start !important}.sd-align-major-end{justify-content:flex-end !important}.sd-align-major-center{justify-content:center !important}.sd-align-major-justify{justify-content:space-between !important}.sd-align-major-spaced{justify-content:space-evenly !important}.sd-align-minor-start{align-items:flex-start !important}.sd-align-minor-end{align-items:flex-end !important}.sd-align-minor-center{align-items:center !important}.sd-align-minor-stretch{align-items:stretch !important}.sd-text-justify{text-align:justify !important}.sd-text-left{text-align:left !important}.sd-text-right{text-align:right !important}.sd-text-center{text-align:center !important}.sd-font-weight-light{font-weight:300 !important}.sd-font-weight-lighter{font-weight:lighter !important}.sd-font-weight-normal{font-weight:400 !important}.sd-font-weight-bold{font-weight:700 !important}.sd-font-weight-bolder{font-weight:bolder !important}.sd-font-italic{font-style:italic !important}.sd-text-decoration-none{text-decoration:none !important}.sd-text-lowercase{text-transform:lowercase !important}.sd-text-uppercase{text-transform:uppercase !important}.sd-text-capitalize{text-transform:capitalize !important}.sd-text-wrap{white-space:normal !important}.sd-text-nowrap{white-space:nowrap !important}.sd-text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sd-fs-1,.sd-fs-1>p{font-size:calc(1.375rem + 1.5vw) !important;line-height:unset !important}.sd-fs-2,.sd-fs-2>p{font-size:calc(1.325rem + 0.9vw) !important;line-height:unset !important}.sd-fs-3,.sd-fs-3>p{font-size:calc(1.3rem + 0.6vw) !important;line-height:unset !important}.sd-fs-4,.sd-fs-4>p{font-size:calc(1.275rem + 0.3vw) !important;line-height:unset !important}.sd-fs-5,.sd-fs-5>p{font-size:1.25rem !important;line-height:unset !important}.sd-fs-6,.sd-fs-6>p{font-size:1rem !important;line-height:unset !important}.sd-border-0{border:0 solid !important}.sd-border-top-0{border-top:0 solid !important}.sd-border-bottom-0{border-bottom:0 solid !important}.sd-border-right-0{border-right:0 solid !important}.sd-border-left-0{border-left:0 solid !important}.sd-border-1{border:1px solid !important}.sd-border-top-1{border-top:1px solid !important}.sd-border-bottom-1{border-bottom:1px solid !important}.sd-border-right-1{border-right:1px solid !important}.sd-border-left-1{border-left:1px solid !important}.sd-border-2{border:2px solid !important}.sd-border-top-2{border-top:2px solid !important}.sd-border-bottom-2{border-bottom:2px solid !important}.sd-border-right-2{border-right:2px solid !important}.sd-border-left-2{border-left:2px solid !important}.sd-border-3{border:3px solid !important}.sd-border-top-3{border-top:3px solid !important}.sd-border-bottom-3{border-bottom:3px solid !important}.sd-border-right-3{border-right:3px solid !important}.sd-border-left-3{border-left:3px solid !important}.sd-border-4{border:4px solid !important}.sd-border-top-4{border-top:4px solid !important}.sd-border-bottom-4{border-bottom:4px solid !important}.sd-border-right-4{border-right:4px solid !important}.sd-border-left-4{border-left:4px solid !important}.sd-border-5{border:5px solid !important}.sd-border-top-5{border-top:5px solid !important}.sd-border-bottom-5{border-bottom:5px solid !important}.sd-border-right-5{border-right:5px solid !important}.sd-border-left-5{border-left:5px solid !important}.sd-rounded-0{border-radius:0 !important}.sd-rounded-1{border-radius:.2rem !important}.sd-rounded-2{border-radius:.3rem !important}.sd-rounded-3{border-radius:.5rem !important}.sd-rounded-pill{border-radius:50rem !important}.sd-rounded-circle{border-radius:50% !important}.shadow-none{box-shadow:none !important}.sd-shadow-sm{box-shadow:0 .125rem .25rem var(--sd-color-shadow) !important}.sd-shadow-md{box-shadow:0 .5rem 1rem var(--sd-color-shadow) !important}.sd-shadow-lg{box-shadow:0 1rem 3rem var(--sd-color-shadow) !important}@keyframes sd-slide-from-left{0%{transform:translateX(-100%)}100%{transform:translateX(0)}}@keyframes sd-slide-from-right{0%{transform:translateX(200%)}100%{transform:translateX(0)}}@keyframes sd-grow100{0%{transform:scale(0);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50{0%{transform:scale(0.5);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50-rot20{0%{transform:scale(0.5) rotateZ(-20deg);opacity:.5}75%{transform:scale(1) rotateZ(5deg);opacity:1}95%{transform:scale(1) rotateZ(-1deg);opacity:1}100%{transform:scale(1) rotateZ(0);opacity:1}}.sd-animate-slide-from-left{animation:1s ease-out 0s 1 normal none running sd-slide-from-left}.sd-animate-slide-from-right{animation:1s ease-out 0s 1 normal none running sd-slide-from-right}.sd-animate-grow100{animation:1s ease-out 0s 1 normal none running sd-grow100}.sd-animate-grow50{animation:1s ease-out 0s 1 normal none running sd-grow50}.sd-animate-grow50-rot20{animation:1s ease-out 0s 1 normal none running sd-grow50-rot20}.sd-badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.sd-badge:empty{display:none}a.sd-badge{text-decoration:none}.sd-btn .sd-badge{position:relative;top:-1px}.sd-btn{background-color:transparent;border:1px solid transparent;border-radius:.25rem;cursor:pointer;display:inline-block;font-weight:400;font-size:1rem;line-height:1.5;padding:.375rem .75rem;text-align:center;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;vertical-align:middle;user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none}.sd-btn:hover{text-decoration:none}@media(prefers-reduced-motion: reduce){.sd-btn{transition:none}}.sd-btn-primary,.sd-btn-outline-primary:hover,.sd-btn-outline-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-primary:hover,.sd-btn-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary-highlight) !important;border-color:var(--sd-color-primary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-primary{color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary,.sd-btn-outline-secondary:hover,.sd-btn-outline-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary:hover,.sd-btn-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary-highlight) !important;border-color:var(--sd-color-secondary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-secondary{color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success,.sd-btn-outline-success:hover,.sd-btn-outline-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success:hover,.sd-btn-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success-highlight) !important;border-color:var(--sd-color-success-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-success{color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info,.sd-btn-outline-info:hover,.sd-btn-outline-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info:hover,.sd-btn-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info-highlight) !important;border-color:var(--sd-color-info-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-info{color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning,.sd-btn-outline-warning:hover,.sd-btn-outline-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning:hover,.sd-btn-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning-highlight) !important;border-color:var(--sd-color-warning-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-warning{color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger,.sd-btn-outline-danger:hover,.sd-btn-outline-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger:hover,.sd-btn-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger-highlight) !important;border-color:var(--sd-color-danger-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-danger{color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light,.sd-btn-outline-light:hover,.sd-btn-outline-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light:hover,.sd-btn-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light-highlight) !important;border-color:var(--sd-color-light-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-light{color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted,.sd-btn-outline-muted:hover,.sd-btn-outline-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted:hover,.sd-btn-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted-highlight) !important;border-color:var(--sd-color-muted-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-muted{color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark,.sd-btn-outline-dark:hover,.sd-btn-outline-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark:hover,.sd-btn-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark-highlight) !important;border-color:var(--sd-color-dark-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-dark{color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black,.sd-btn-outline-black:hover,.sd-btn-outline-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black:hover,.sd-btn-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black-highlight) !important;border-color:var(--sd-color-black-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-black{color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white,.sd-btn-outline-white:hover,.sd-btn-outline-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white:hover,.sd-btn-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white-highlight) !important;border-color:var(--sd-color-white-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-white{color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.sd-hide-link-text{font-size:0}.sd-octicon,.sd-material-icon{display:inline-block;fill:currentColor;vertical-align:middle}.sd-avatar-xs{border-radius:50%;object-fit:cover;object-position:center;width:1rem;height:1rem}.sd-avatar-sm{border-radius:50%;object-fit:cover;object-position:center;width:3rem;height:3rem}.sd-avatar-md{border-radius:50%;object-fit:cover;object-position:center;width:5rem;height:5rem}.sd-avatar-lg{border-radius:50%;object-fit:cover;object-position:center;width:7rem;height:7rem}.sd-avatar-xl{border-radius:50%;object-fit:cover;object-position:center;width:10rem;height:10rem}.sd-avatar-inherit{border-radius:50%;object-fit:cover;object-position:center;width:inherit;height:inherit}.sd-avatar-initial{border-radius:50%;object-fit:cover;object-position:center;width:initial;height:initial}.sd-card{background-clip:border-box;background-color:var(--sd-color-card-background);border:1px solid var(--sd-color-card-border);border-radius:.25rem;color:var(--sd-color-card-text);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;position:relative;word-wrap:break-word}.sd-card>hr{margin-left:0;margin-right:0}.sd-card-hover:hover{border-color:var(--sd-color-card-border-hover);transform:scale(1.01)}.sd-card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem 1rem}.sd-card-title{margin-bottom:.5rem}.sd-card-subtitle{margin-top:-0.25rem;margin-bottom:0}.sd-card-text:last-child{margin-bottom:0}.sd-card-link:hover{text-decoration:none}.sd-card-link+.card-link{margin-left:1rem}.sd-card-header{padding:.5rem 1rem;margin-bottom:0;background-color:var(--sd-color-card-header);border-bottom:1px solid var(--sd-color-card-border)}.sd-card-header:first-child{border-radius:calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0}.sd-card-footer{padding:.5rem 1rem;background-color:var(--sd-color-card-footer);border-top:1px solid var(--sd-color-card-border)}.sd-card-footer:last-child{border-radius:0 0 calc(0.25rem - 1px) calc(0.25rem - 1px)}.sd-card-header-tabs{margin-right:-0.5rem;margin-bottom:-0.5rem;margin-left:-0.5rem;border-bottom:0}.sd-card-header-pills{margin-right:-0.5rem;margin-left:-0.5rem}.sd-card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom,.sd-card-img-top{width:100%}.sd-card-img,.sd-card-img-top{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom{border-bottom-left-radius:calc(0.25rem - 1px);border-bottom-right-radius:calc(0.25rem - 1px)}.sd-cards-carousel{width:100%;display:flex;flex-wrap:nowrap;-ms-flex-direction:row;flex-direction:row;overflow-x:hidden;scroll-snap-type:x mandatory}.sd-cards-carousel.sd-show-scrollbar{overflow-x:auto}.sd-cards-carousel:hover,.sd-cards-carousel:focus{overflow-x:auto}.sd-cards-carousel>.sd-card{flex-shrink:0;scroll-snap-align:start}.sd-cards-carousel>.sd-card:not(:last-child){margin-right:3px}.sd-card-cols-1>.sd-card{width:90%}.sd-card-cols-2>.sd-card{width:45%}.sd-card-cols-3>.sd-card{width:30%}.sd-card-cols-4>.sd-card{width:22.5%}.sd-card-cols-5>.sd-card{width:18%}.sd-card-cols-6>.sd-card{width:15%}.sd-card-cols-7>.sd-card{width:12.8571428571%}.sd-card-cols-8>.sd-card{width:11.25%}.sd-card-cols-9>.sd-card{width:10%}.sd-card-cols-10>.sd-card{width:9%}.sd-card-cols-11>.sd-card{width:8.1818181818%}.sd-card-cols-12>.sd-card{width:7.5%}.sd-container,.sd-container-fluid,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container-xl{margin-left:auto;margin-right:auto;padding-left:var(--sd-gutter-x, 0.75rem);padding-right:var(--sd-gutter-x, 0.75rem);width:100%}@media(min-width: 576px){.sd-container-sm,.sd-container{max-width:540px}}@media(min-width: 768px){.sd-container-md,.sd-container-sm,.sd-container{max-width:720px}}@media(min-width: 992px){.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:960px}}@media(min-width: 1200px){.sd-container-xl,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:1140px}}.sd-row{--sd-gutter-x: 1.5rem;--sd-gutter-y: 0;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-top:calc(var(--sd-gutter-y) * -1);margin-right:calc(var(--sd-gutter-x) * -0.5);margin-left:calc(var(--sd-gutter-x) * -0.5)}.sd-row>*{box-sizing:border-box;flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--sd-gutter-x) * 0.5);padding-left:calc(var(--sd-gutter-x) * 0.5);margin-top:var(--sd-gutter-y)}.sd-col{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-auto>*{flex:0 0 auto;width:auto}.sd-row-cols-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}@media(min-width: 576px){.sd-col-sm{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-sm-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-sm-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-sm-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-sm-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-sm-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-sm-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-sm-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-sm-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-sm-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-sm-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-sm-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-sm-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-sm-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 768px){.sd-col-md{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-md-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-md-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-md-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-md-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-md-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-md-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-md-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-md-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-md-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-md-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-md-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-md-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-md-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 992px){.sd-col-lg{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-lg-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-lg-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-lg-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-lg-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-lg-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-lg-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-lg-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-lg-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-lg-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-lg-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-lg-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-lg-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-lg-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 1200px){.sd-col-xl{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-xl-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-xl-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-xl-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-xl-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-xl-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-xl-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-xl-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-xl-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-xl-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-xl-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-xl-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-xl-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-xl-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}.sd-col-auto{flex:0 0 auto;-ms-flex:0 0 auto;width:auto}.sd-col-1{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}.sd-col-2{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-col-3{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-col-4{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-col-5{flex:0 0 auto;-ms-flex:0 0 auto;width:41.6666666667%}.sd-col-6{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-col-7{flex:0 0 auto;-ms-flex:0 0 auto;width:58.3333333333%}.sd-col-8{flex:0 0 auto;-ms-flex:0 0 auto;width:66.6666666667%}.sd-col-9{flex:0 0 auto;-ms-flex:0 0 auto;width:75%}.sd-col-10{flex:0 0 auto;-ms-flex:0 0 auto;width:83.3333333333%}.sd-col-11{flex:0 0 auto;-ms-flex:0 0 auto;width:91.6666666667%}.sd-col-12{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-g-0,.sd-gy-0{--sd-gutter-y: 0}.sd-g-0,.sd-gx-0{--sd-gutter-x: 0}.sd-g-1,.sd-gy-1{--sd-gutter-y: 0.25rem}.sd-g-1,.sd-gx-1{--sd-gutter-x: 0.25rem}.sd-g-2,.sd-gy-2{--sd-gutter-y: 0.5rem}.sd-g-2,.sd-gx-2{--sd-gutter-x: 0.5rem}.sd-g-3,.sd-gy-3{--sd-gutter-y: 1rem}.sd-g-3,.sd-gx-3{--sd-gutter-x: 1rem}.sd-g-4,.sd-gy-4{--sd-gutter-y: 1.5rem}.sd-g-4,.sd-gx-4{--sd-gutter-x: 1.5rem}.sd-g-5,.sd-gy-5{--sd-gutter-y: 3rem}.sd-g-5,.sd-gx-5{--sd-gutter-x: 3rem}@media(min-width: 576px){.sd-col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-sm-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-sm-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-sm-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-sm-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-sm-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-sm-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-sm-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-sm-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-sm-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-sm-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-sm-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-sm-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-sm-0,.sd-gy-sm-0{--sd-gutter-y: 0}.sd-g-sm-0,.sd-gx-sm-0{--sd-gutter-x: 0}.sd-g-sm-1,.sd-gy-sm-1{--sd-gutter-y: 0.25rem}.sd-g-sm-1,.sd-gx-sm-1{--sd-gutter-x: 0.25rem}.sd-g-sm-2,.sd-gy-sm-2{--sd-gutter-y: 0.5rem}.sd-g-sm-2,.sd-gx-sm-2{--sd-gutter-x: 0.5rem}.sd-g-sm-3,.sd-gy-sm-3{--sd-gutter-y: 1rem}.sd-g-sm-3,.sd-gx-sm-3{--sd-gutter-x: 1rem}.sd-g-sm-4,.sd-gy-sm-4{--sd-gutter-y: 1.5rem}.sd-g-sm-4,.sd-gx-sm-4{--sd-gutter-x: 1.5rem}.sd-g-sm-5,.sd-gy-sm-5{--sd-gutter-y: 3rem}.sd-g-sm-5,.sd-gx-sm-5{--sd-gutter-x: 3rem}}@media(min-width: 768px){.sd-col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-md-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-md-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-md-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-md-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-md-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-md-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-md-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-md-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-md-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-md-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-md-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-md-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-md-0,.sd-gy-md-0{--sd-gutter-y: 0}.sd-g-md-0,.sd-gx-md-0{--sd-gutter-x: 0}.sd-g-md-1,.sd-gy-md-1{--sd-gutter-y: 0.25rem}.sd-g-md-1,.sd-gx-md-1{--sd-gutter-x: 0.25rem}.sd-g-md-2,.sd-gy-md-2{--sd-gutter-y: 0.5rem}.sd-g-md-2,.sd-gx-md-2{--sd-gutter-x: 0.5rem}.sd-g-md-3,.sd-gy-md-3{--sd-gutter-y: 1rem}.sd-g-md-3,.sd-gx-md-3{--sd-gutter-x: 1rem}.sd-g-md-4,.sd-gy-md-4{--sd-gutter-y: 1.5rem}.sd-g-md-4,.sd-gx-md-4{--sd-gutter-x: 1.5rem}.sd-g-md-5,.sd-gy-md-5{--sd-gutter-y: 3rem}.sd-g-md-5,.sd-gx-md-5{--sd-gutter-x: 3rem}}@media(min-width: 992px){.sd-col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-lg-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-lg-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-lg-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-lg-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-lg-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-lg-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-lg-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-lg-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-lg-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-lg-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-lg-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-lg-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-lg-0,.sd-gy-lg-0{--sd-gutter-y: 0}.sd-g-lg-0,.sd-gx-lg-0{--sd-gutter-x: 0}.sd-g-lg-1,.sd-gy-lg-1{--sd-gutter-y: 0.25rem}.sd-g-lg-1,.sd-gx-lg-1{--sd-gutter-x: 0.25rem}.sd-g-lg-2,.sd-gy-lg-2{--sd-gutter-y: 0.5rem}.sd-g-lg-2,.sd-gx-lg-2{--sd-gutter-x: 0.5rem}.sd-g-lg-3,.sd-gy-lg-3{--sd-gutter-y: 1rem}.sd-g-lg-3,.sd-gx-lg-3{--sd-gutter-x: 1rem}.sd-g-lg-4,.sd-gy-lg-4{--sd-gutter-y: 1.5rem}.sd-g-lg-4,.sd-gx-lg-4{--sd-gutter-x: 1.5rem}.sd-g-lg-5,.sd-gy-lg-5{--sd-gutter-y: 3rem}.sd-g-lg-5,.sd-gx-lg-5{--sd-gutter-x: 3rem}}@media(min-width: 1200px){.sd-col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-xl-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-xl-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-xl-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-xl-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-xl-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-xl-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-xl-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-xl-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-xl-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-xl-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-xl-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-xl-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-xl-0,.sd-gy-xl-0{--sd-gutter-y: 0}.sd-g-xl-0,.sd-gx-xl-0{--sd-gutter-x: 0}.sd-g-xl-1,.sd-gy-xl-1{--sd-gutter-y: 0.25rem}.sd-g-xl-1,.sd-gx-xl-1{--sd-gutter-x: 0.25rem}.sd-g-xl-2,.sd-gy-xl-2{--sd-gutter-y: 0.5rem}.sd-g-xl-2,.sd-gx-xl-2{--sd-gutter-x: 0.5rem}.sd-g-xl-3,.sd-gy-xl-3{--sd-gutter-y: 1rem}.sd-g-xl-3,.sd-gx-xl-3{--sd-gutter-x: 1rem}.sd-g-xl-4,.sd-gy-xl-4{--sd-gutter-y: 1.5rem}.sd-g-xl-4,.sd-gx-xl-4{--sd-gutter-x: 1.5rem}.sd-g-xl-5,.sd-gy-xl-5{--sd-gutter-y: 3rem}.sd-g-xl-5,.sd-gx-xl-5{--sd-gutter-x: 3rem}}.sd-flex-row-reverse{flex-direction:row-reverse !important}details.sd-dropdown{position:relative;font-size:var(--sd-fontsize-dropdown)}details.sd-dropdown:hover{cursor:pointer}details.sd-dropdown .sd-summary-content{cursor:default}details.sd-dropdown summary.sd-summary-title{padding:.5em .6em .5em 1em;font-size:var(--sd-fontsize-dropdown-title);font-weight:var(--sd-fontweight-dropdown-title);user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;list-style:none;display:inline-flex;justify-content:space-between}details.sd-dropdown summary.sd-summary-title::-webkit-details-marker{display:none}details.sd-dropdown summary.sd-summary-title:focus{outline:none}details.sd-dropdown summary.sd-summary-title .sd-summary-icon{margin-right:.6em;display:inline-flex;align-items:center}details.sd-dropdown summary.sd-summary-title .sd-summary-icon svg{opacity:.8}details.sd-dropdown summary.sd-summary-title .sd-summary-text{flex-grow:1;line-height:1.5;padding-right:.5rem}details.sd-dropdown summary.sd-summary-title .sd-summary-state-marker{pointer-events:none;display:inline-flex;align-items:center}details.sd-dropdown summary.sd-summary-title .sd-summary-state-marker svg{opacity:.6}details.sd-dropdown summary.sd-summary-title:hover .sd-summary-state-marker svg{opacity:1;transform:scale(1.1)}details.sd-dropdown[open] summary .sd-octicon.no-title{visibility:hidden}details.sd-dropdown .sd-summary-chevron-right{transition:.25s}details.sd-dropdown[open]>.sd-summary-title .sd-summary-chevron-right{transform:rotate(90deg)}details.sd-dropdown[open]>.sd-summary-title .sd-summary-chevron-down{transform:rotate(180deg)}details.sd-dropdown:not([open]).sd-card{border:none}details.sd-dropdown:not([open])>.sd-card-header{border:1px solid var(--sd-color-card-border);border-radius:.25rem}details.sd-dropdown.sd-fade-in[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out;animation:sd-fade-in .5s ease-in-out}details.sd-dropdown.sd-fade-in-slide-down[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out}.sd-col>.sd-dropdown{width:100%}.sd-summary-content>.sd-tab-set:first-child{margin-top:0}@keyframes sd-fade-in{0%{opacity:0}100%{opacity:1}}@keyframes sd-slide-down{0%{transform:translate(0, -10px)}100%{transform:translate(0, 0)}}.sd-tab-set{border-radius:.125rem;display:flex;flex-wrap:wrap;margin:1em 0;position:relative}.sd-tab-set>input{opacity:0;position:absolute}.sd-tab-set>input:checked+label{border-color:var(--sd-color-tabs-underline-active);color:var(--sd-color-tabs-label-active)}.sd-tab-set>input:checked+label+.sd-tab-content{display:block}.sd-tab-set>input:not(:checked)+label:hover{color:var(--sd-color-tabs-label-hover);border-color:var(--sd-color-tabs-underline-hover)}.sd-tab-set>input:focus+label{outline-style:auto}.sd-tab-set>input:not(.focus-visible)+label{outline:none;-webkit-tap-highlight-color:transparent}.sd-tab-set>label{border-bottom:.125rem solid transparent;margin-bottom:0;color:var(--sd-color-tabs-label-inactive);border-color:var(--sd-color-tabs-underline-inactive);cursor:pointer;font-size:var(--sd-fontsize-tabs-label);font-weight:700;padding:1em 1.25em .5em;transition:color 250ms;width:auto;z-index:1}html .sd-tab-set>label:hover{color:var(--sd-color-tabs-label-active)}.sd-col>.sd-tab-set{width:100%}.sd-tab-content{box-shadow:0 -0.0625rem var(--sd-color-tabs-overline),0 .0625rem var(--sd-color-tabs-underline);display:none;order:99;padding-bottom:.75rem;padding-top:.75rem;width:100%}.sd-tab-content>:first-child{margin-top:0 !important}.sd-tab-content>:last-child{margin-bottom:0 !important}.sd-tab-content>.sd-tab-set{margin:0}.sd-sphinx-override,.sd-sphinx-override *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.sd-sphinx-override p{margin-top:0}:root{--sd-color-primary: #0071bc;--sd-color-secondary: #6c757d;--sd-color-success: #28a745;--sd-color-info: #17a2b8;--sd-color-warning: #f0b37e;--sd-color-danger: #dc3545;--sd-color-light: #f8f9fa;--sd-color-muted: #6c757d;--sd-color-dark: #212529;--sd-color-black: black;--sd-color-white: white;--sd-color-primary-highlight: #0060a0;--sd-color-secondary-highlight: #5c636a;--sd-color-success-highlight: #228e3b;--sd-color-info-highlight: #148a9c;--sd-color-warning-highlight: #cc986b;--sd-color-danger-highlight: #bb2d3b;--sd-color-light-highlight: #d3d4d5;--sd-color-muted-highlight: #5c636a;--sd-color-dark-highlight: #1c1f23;--sd-color-black-highlight: black;--sd-color-white-highlight: #d9d9d9;--sd-color-primary-bg: rgba(0, 113, 188, 0.2);--sd-color-secondary-bg: rgba(108, 117, 125, 0.2);--sd-color-success-bg: rgba(40, 167, 69, 0.2);--sd-color-info-bg: rgba(23, 162, 184, 0.2);--sd-color-warning-bg: rgba(240, 179, 126, 0.2);--sd-color-danger-bg: rgba(220, 53, 69, 0.2);--sd-color-light-bg: rgba(248, 249, 250, 0.2);--sd-color-muted-bg: rgba(108, 117, 125, 0.2);--sd-color-dark-bg: rgba(33, 37, 41, 0.2);--sd-color-black-bg: rgba(0, 0, 0, 0.2);--sd-color-white-bg: rgba(255, 255, 255, 0.2);--sd-color-primary-text: #fff;--sd-color-secondary-text: #fff;--sd-color-success-text: #fff;--sd-color-info-text: #fff;--sd-color-warning-text: #212529;--sd-color-danger-text: #fff;--sd-color-light-text: #212529;--sd-color-muted-text: #fff;--sd-color-dark-text: #fff;--sd-color-black-text: #fff;--sd-color-white-text: #212529;--sd-color-shadow: rgba(0, 0, 0, 0.15);--sd-color-card-border: rgba(0, 0, 0, 0.125);--sd-color-card-border-hover: hsla(231, 99%, 66%, 1);--sd-color-card-background: transparent;--sd-color-card-text: inherit;--sd-color-card-header: transparent;--sd-color-card-footer: transparent;--sd-color-tabs-label-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-hover: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-inactive: hsl(0, 0%, 66%);--sd-color-tabs-underline-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-underline-hover: rgba(178, 206, 245, 0.62);--sd-color-tabs-underline-inactive: transparent;--sd-color-tabs-overline: rgb(222, 222, 222);--sd-color-tabs-underline: rgb(222, 222, 222);--sd-fontsize-tabs-label: 1rem;--sd-fontsize-dropdown: inherit;--sd-fontsize-dropdown-title: 1rem;--sd-fontweight-dropdown-title: 700} diff --git a/mddocs/_static/autodoc_pydantic.css b/mddocs/_static/autodoc_pydantic.css new file mode 100644 index 000000000..994a3e548 --- /dev/null +++ b/mddocs/_static/autodoc_pydantic.css @@ -0,0 +1,11 @@ +.autodoc_pydantic_validator_arrow { + padding-left: 8px; + } + +.autodoc_pydantic_collapsable_json { + cursor: pointer; + } + +.autodoc_pydantic_collapsable_erd { + cursor: pointer; + } \ No newline at end of file diff --git a/mddocs/_static/icon.svg b/mddocs/_static/icon.svg new file mode 100644 index 000000000..a4d737f81 --- /dev/null +++ b/mddocs/_static/icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/mddocs/_static/logo.svg b/mddocs/_static/logo.svg new file mode 100644 index 000000000..76527ebf1 --- /dev/null +++ b/mddocs/_static/logo.svg @@ -0,0 +1,214 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mddocs/_static/logo_wide.svg b/mddocs/_static/logo_wide.svg new file mode 100644 index 000000000..981bf0148 --- /dev/null +++ b/mddocs/_static/logo_wide.svg @@ -0,0 +1,329 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mddocs/changelog.md b/mddocs/changelog.md new file mode 100644 index 000000000..4ba142635 --- /dev/null +++ b/mddocs/changelog.md @@ -0,0 +1,31 @@ +# Changelog + +# Changelog + +* [0.13.4 (2025-03-20)](changelog/0.13.4.md) +* [0.13.3 (2025-03-11)](changelog/0.13.3.md) +* [0.13.1 (2025-03-06)](changelog/0.13.1.md) +* [0.13.0 (2025-02-24)](changelog/0.13.0.md) +* [0.12.5 (2024-12-03)](changelog/0.12.5.md) +* [0.12.4 (2024-11-27)](changelog/0.12.4.md) +* [0.12.3 (2024-11-22)](changelog/0.12.3.md) +* [0.12.2 (2024-11-12)](changelog/0.12.2.md) +* [0.12.1 (2024-10-28)](changelog/0.12.1.md) +* [0.12.0 (2024-09-03)](changelog/0.12.0.md) +* [0.11.2 (2024-09-02)](changelog/0.11.2.md) +* [0.11.1 (2024-05-29)](changelog/0.11.1.md) +* [0.11.0 (2024-05-27)](changelog/0.11.0.md) +* [0.10.2 (2024-03-21)](changelog/0.10.2.md) +* [0.10.1 (2024-02-05)](changelog/0.10.1.md) +* [0.10.0 (2023-12-18)](changelog/0.10.0.md) +* [0.9.5 (2023-10-10)](changelog/0.9.5.md) +* [0.9.4 (2023-09-26)](changelog/0.9.4.md) +* [0.9.3 (2023-09-06)](changelog/0.9.3.md) +* [0.9.2 (2023-09-06)](changelog/0.9.2.md) +* [0.9.1 (2023-08-17)](changelog/0.9.1.md) +* [0.9.0 (2023-08-17)](changelog/0.9.0.md) +* [0.8.1 (2023-07-10)](changelog/0.8.1.md) +* [0.8.0 (2023-05-31)](changelog/0.8.0.md) +* [0.7.2 (2023-05-24)](changelog/0.7.2.md) +* [0.7.1 (2023-05-23)](changelog/0.7.1.md) +* [0.7.0 (2023-05-15)](changelog/0.7.0.md) diff --git a/mddocs/changelog/0.10.0.md b/mddocs/changelog/0.10.0.md new file mode 100644 index 000000000..ff0fe78bc --- /dev/null +++ b/mddocs/changelog/0.10.0.md @@ -0,0 +1,359 @@ +# 0.10.0 (2023-12-18) + +## Breaking Changes + +- Upgrade `etl-entities` from v1 to v2 ([#172](https://github.com/MobileTeleSystems/onetl/pull/172)). + + This implies that `HWM` classes are now have different internal structure than they used to. + + Before: + ```python + from etl_entities.old_hwm import IntHWM as OldIntHWM + from etl_entities.source import Column, Table + from etl_entities.process import Process + + hwm = OldIntHWM( + process=Process(name="myprocess", task="abc", dag="cde", host="myhost"), + source=Table(name="schema.table", instance="postgres://host:5432/db"), + column=Column(name="col1"), + value=123, + ) + ``` + + After: + ```python + from etl_entities.hwm import ColumnIntHWM + + hwm = ColumnIntHWM( + name="some_unique_name", + description="any value you want", + source="schema.table", + expression="col1", + value=123, + ) + ``` + + **Breaking change:** If you used HWM classes from `etl_entities` module, you should rewrite your code to make it compatible with new version. + + ### More details + + - `HWM` classes used by previous onETL versions were moved from `etl_entities` to `etl_entities.old_hwm` submodule. They are here for compatibility reasons, but are planned to be removed in `etl-entities` v3 release. + - New `HWM` classes have flat structure instead of nested. + - New `HWM` classes have mandatory `name` attribute (it was known as `qualified_name` before). + - Type aliases used while serializing and deserializing `HWM` objects to `dict` representation were changed too: `int` → `column_int`. + + To make migration simpler, you can use new method: + ```python + old_hwm = OldIntHWM(...) + new_hwm = old_hwm.as_new_hwm() + ``` + + Which automatically converts all fields from old structure to new one, including `qualified_name` → `name`. +- **Breaking changes:** + * Methods `BaseHWMStore.get()` and `BaseHWMStore.save()` were renamed to `get_hwm()` and `set_hwm()`. + * They now can be used only with new HWM classes from `etl_entities.hwm`, **old HWM classes are not supported**. + + If you used them in your code, please update it accordingly. +- YAMLHWMStore **CANNOT read files created by older onETL versions** (0.9.x or older). + + ### Update procedure + + ```python + # pip install onetl==0.9.5 + + # Get qualified_name for HWM + + + # Option 1. HWM is built manually + from etl_entities import IntHWM, FileListHWM + from etl_entities.source import Column, Table, RemoteFolder + from etl_entities.process import Process + + # for column HWM + old_column_hwm = IntHWM( + process=Process(name="myprocess", task="abc", dag="cde", host="myhost"), + source=Table(name="schema.table", instance="postgres://host:5432/db"), + column=Column(name="col1"), + ) + qualified_name = old_column_hwm.qualified_name + # "col1#schema.table@postgres://host:5432/db#cde.abc.myprocess@myhost" + + # for file HWM + old_file_hwm = FileListHWM( + process=Process(name="myprocess", task="abc", dag="cde", host="myhost"), + source=RemoteFolder(name="/absolute/path", instance="ftp://ftp.server:21"), + ) + qualified_name = old_file_hwm.qualified_name + # "file_list#/absolute/path@ftp://ftp.server:21#cde.abc.myprocess@myhost" + + + # Option 2. HWM is generated automatically (by DBReader/FileDownloader) + # See onETL logs and search for string like qualified_name = '...' + + qualified_name = "col1#schema.table@postgres://host:5432/db#cde.abc.myprocess@myhost" + + + # Get .yml file path by qualified_name + + import os + from pathlib import PurePosixPath + from onetl.hwm.store import YAMLHWMStore + + # here you should pass the same arguments as used on production, if any + yaml_hwm_store = YAMLHWMStore() + hwm_path = yaml_hwm_store.get_file_path(qualified_name) + print(hwm_path) + + # for column HWM + # LocalPosixPath('/home/maxim/.local/share/onETL/yml_hwm_store/col1__schema.table__postgres_host_5432_db__cde.abc.myprocess__myhost.yml') + + # for file HWM + # LocalPosixPath('/home/maxim/.local/share/onETL/yml_hwm_store/file_list__absolute_path__ftp_ftp.server_21__cde.abc.myprocess__myhost.yml') + + + # Read raw .yml file content + + from yaml import safe_load, dump + + raw_old_hwm_items = safe_load(hwm_path.read_text()) + print(raw_old_hwm_items) + + # for column HWM + # [ + # { + # "column": { "name": "col1", "partition": {} }, + # "modified_time": "2023-12-18T10: 39: 47.377378", + # "process": { "dag": "cde", "host": "myhost", "name": "myprocess", "task": "abc" }, + # "source": { "instance": "postgres: //host:5432/db", "name": "schema.table" }, + # "type": "int", + # "value": "123", + # }, + # ] + + # for file HWM + # [ + # { + # "modified_time": "2023-12-18T11:15:36.478462", + # "process": { "dag": "cde", "host": "myhost", "name": "myprocess", "task": "abc" }, + # "source": { "instance": "ftp://ftp.server:21", "name": "/absolute/path" }, + # "type": "file_list", + # "value": ["file1.txt", "file2.txt"], + # }, + # ] + + + # Convert file content to new structure, compatible with onETL 0.10.x + raw_new_hwm_items = [] + for old_hwm in raw_old_hwm_items: + new_hwm = {"name": qualified_name, "modified_time": old_hwm["modified_time"]} + + if "column" in old_hwm: + new_hwm["expression"] = old_hwm["column"]["name"] + new_hwm["entity"] = old_hwm["source"]["name"] + old_hwm.pop("process", None) + + if old_hwm["type"] == "int": + new_hwm["type"] = "column_int" + new_hwm["value"] = old_hwm["value"] + + elif old_hwm["type"] == "date": + new_hwm["type"] = "column_date" + new_hwm["value"] = old_hwm["value"] + + elif old_hwm["type"] == "datetime": + new_hwm["type"] = "column_datetime" + new_hwm["value"] = old_hwm["value"] + + elif old_hwm["type"] == "file_list": + new_hwm["type"] = "file_list" + new_hwm["value"] = [ + os.fspath(PurePosixPath(old_hwm["source"]["name"]).joinpath(path)) + for path in old_hwm["value"] + ] + + else: + raise ValueError("WAT?") + + raw_new_hwm_items.append(new_hwm) + + + print(raw_new_hwm_items) + # for column HWM + # [ + # { + # "name": "col1#schema.table@postgres://host:5432/db#cde.abc.myprocess@myhost", + # "modified_time": "2023-12-18T10:39:47.377378", + # "expression": "col1", + # "source": "schema.table", + # "type": "column_int", + # "value": 123, + # }, + # ] + + # for file HWM + # [ + # { + # "name": "file_list#/absolute/path@ftp://ftp.server:21#cde.abc.myprocess@myhost", + # "modified_time": "2023-12-18T11:15:36.478462", + # "entity": "/absolute/path", + # "type": "file_list", + # "value": ["/absolute/path/file1.txt", "/absolute/path/file2.txt"], + # }, + # ] + + + # Save file with new content + with open(hwm_path, "w") as file: + dump(raw_new_hwm_items, file) + + + # Stop Python interpreter and update onETL + # pip install onetl==0.10.0 + # Check that new .yml file can be read + + from onetl.hwm.store import YAMLHWMStore + + qualified_name = ... + + # here you should pass the same arguments as used on production, if any + yaml_hwm_store = YAMLHWMStore() + yaml_hwm_store.get_hwm(qualified_name) + + # for column HWM + # ColumnIntHWM( + # name='col1#schema.table@postgres://host:5432/db#cde.abc.myprocess@myhost', + # description='', + # entity='schema.table', + # value=123, + # expression='col1', + # modified_time=datetime.datetime(2023, 12, 18, 10, 39, 47, 377378), + # ) + + # for file HWM + # FileListHWM( + # name='file_list#/absolute/path@ftp://ftp.server:21#cde.abc.myprocess@myhost', + # description='', + # entity=AbsolutePath('/absolute/path'), + # value=frozenset({AbsolutePath('/absolute/path/file1.txt'), AbsolutePath('/absolute/path/file2.txt')}), + # expression=None, + # modified_time=datetime.datetime(2023, 12, 18, 11, 15, 36, 478462) + # ) + + + # That's all! + ``` + + But most of users use other HWM store implementations which do not have such issues. +- Several classes and functions were moved from `onetl` to `etl_entities`: + + | onETL `0.9.x` and older | onETL `0.10.x` and newer | + |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + | ```python
from onetl.hwm.store import (
detect_hwm_store,
BaseHWMStore,
HWMStoreClassRegistry,
register_hwm_store_class,
HWMStoreManager,
MemoryHWMStore,
)
``` | ```python
from etl_entities.hwm_store import (
detect_hwm_store,
BaseHWMStore,
HWMStoreClassRegistry,
register_hwm_store_class,
HWMStoreManager,
MemoryHWMStore,
)
``` | + + They still can be imported from old module, but this is deprecated and will be removed in v1.0.0 release. +- Change the way of passing `HWM` to `DBReader` and `FileDownloader` classes: + + | onETL `0.9.x` and older | onETL `0.10.x` and newer | + |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + | ```python
reader = DBReader(
connection=...,
source=...,
hwm_column="col1",
)
``` | ```python
reader = DBReader(
connection=...,
source=...,
hwm=DBReader.AutoDetectHWM(
# name is mandatory now!
name="my_unique_hwm_name",
expression="col1",
),
)
``` | + | ```python
reader = DBReader(
connection=...,
source=...,
hwm_column=(
"col1",
"cast(col1 as date)",
),
)
``` | ```python
reader = DBReader(
connection=...,
source=...,
hwm=DBReader.AutoDetectHWM(
# name is mandatory now!
name="my_unique_hwm_name",
expression="cast(col1 as date)",
),
)
``` | + | ```python
downloader = FileDownloader(
connection=...,
source_path=...,
target_path=...,
hwm_type="file_list",
)
``` | ```python
downloader = FileDownloader(
connection=...,
source_path=...,
target_path=...,
hwm=FileListHWM(
# name is mandatory now!
name="another_unique_hwm_name",
),
)
``` | + + New HWM classes have **mandatory** `name` attribute which should be passed explicitly, + instead of generating if automatically under the hood. + + Automatic `name` generation using the old `DBReader.hwm_column` / `FileDownloader.hwm_type` + syntax is still supported, but will be removed in v1.0.0 release. ([#179](https://github.com/MobileTeleSystems/onetl/pull/179)) +- Performance of read Incremental and Batch strategies has been drastically improved. ([#182](https://github.com/MobileTeleSystems/onetl/pull/182)). + + ### Before and after in details + + `DBReader.run()` + incremental/batch strategy behavior in versions 0.9.x and older: + - Get table schema by making query `SELECT * FROM table WHERE 1=0` (if `DBReader.columns` has `*`) + - Expand `*` to real column names from table, add here `hwm_column`, remove duplicates (as some RDBMS does not allow that). + - Create dataframe from query like `SELECT hwm_expression AS hwm_column, ...other table columns... FROM table WHERE hwm_expression > prev_hwm.value`. + - Determine HWM class using dataframe schema: `df.schema[hwm_column].dataType`. + - Determine x HWM column value using Spark: `df.select(max(hwm_column)).collect()`. + - Use `max(hwm_column)` as next HWM value, and save it to HWM Store. + - Return dataframe to user. + + This was far from ideal: + - Dataframe content (all rows or just changed ones) was loaded from the source to Spark only to get min/max values of specific column. + - Step of fetching table schema and then substituting column names in the next query caused some unexpected errors. + > For example, source contains columns with mixed name case, like `"CamelColumn"` or `"spaced column"`. + + > Column names were *not* escaped during query generation, leading to queries that cannot be executed by database. + + > So users have to *explicitly* pass column names `DBReader`, wrapping columns with mixed naming with `"`: + > ```python + > reader = DBReader( + > connection=..., + > source=..., + > columns=[ # passing '*' here leads to wrong SQL query generation + > "normal_column", + > '"CamelColumn"', + > '"spaced column"', + > ..., + > ], + > ) + > ``` + - Using `DBReader` with `IncrementalStrategy` could lead to reading rows already read before. + > Dataframe was created from query with WHERE clause like `hwm.expression > prev_hwm.value`, + > not `hwm.expression > prev_hwm.value AND hwm.expression <= current_hwm.value`. + + > So if new rows appeared in the source **after** HWM value is determined, + > they can be read by accessing dataframe content (because Spark dataframes are lazy), + > leading to inconsistencies between HWM value and dataframe content. + + > This may lead to issues then `DBReader.run()` read some data, updated HWM value, and next call of `DBReader.run()` + > will read rows that were already read in previous run. + + `DBReader.run()` + incremental/batch strategy behavior in versions 0.10.x and newer: + - Detect type of HWM expression: `SELECT hwm.expression FROM table WHERE 1=0`. + - Determine corresponding Spark type `df.schema[0]` and when determine matching HWM class (if `DReader.AutoDetectHWM` is used). + - Get min/max values by querying the source: `SELECT MAX(hwm.expression) FROM table WHERE hwm.expression >= prev_hwm.value`. + - Use `max(hwm.expression)` as next HWM value, and save it to HWM Store. + - Create dataframe from query `SELECT ... table columns ... FROM table WHERE hwm.expression > prev_hwm.value AND hwm.expression <= current_hwm.value`, baking new HWM value into the query. + - Return dataframe to user. + + Improvements: + - Allow source to calculate min/max instead of loading everything to Spark. This should be **faster** on large amounts of data (**up to x2**), because we do not transfer all the data from the source to Spark. This can be even faster if source have indexes for HWM column. + - Columns list is passed to source as-is, without any resolving on `DBReader` side. So you can pass `DBReader(columns=["*"])` to read tables with mixed columns naming. + - Restrict dataframe content to always match HWM values, which leads to never reading the same row twice. + + **Breaking change**: HWM column is not being implicitly added to dataframe. It was a part of `SELECT` clause, but now it is mentioned only in `WHERE` clause. + + So if you had code like this, you have to rewrite it: + + | onETL `0.9.x` and older | onETL `0.10.x` and newer | + |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + | ```python
reader = DBReader(
connection=...,
source=...,
columns=[
"col1",
"col2",
],
hwm_column="hwm_col",
)

df = reader.run()
# hwm_column value is in the dataframe
assert df.columns == ["col1", "col2", "hwm_col"]
``` | ```python
reader = DBReader(
connection=...,
source=...,
columns=[
"col1",
"col2",
# add hwm_column explicitly
"hwm_col",
],
hwm_column="hwm_col",
)

df = reader.run()
# if columns list is not updated,
# this fill fail
assert df.columns == ["col1", "col2", "hwm_col"]
``` | + | ```python
reader = DBReader(
connection=...,
source=...,
columns=[
"col1",
"col2",
],
hwm_column=(
"hwm_col",
"cast(hwm_col as int)",
),
)

df = reader.run()
# hwm_expression value is in the dataframe
assert df.columns == ["col1", "col2", "hwm_col"]
``` | ```python
reader = DBReader(
connection=...,
source=...,
columns=[
"col1",
"col2",
# add hwm_expression explicitly
"cast(hwm_col as int) as hwm_col",
],
hwm_column=(
"hwm_col",
"cast(hwm_col as int)",
),
)

df = reader.run()
# if columns list is not updated,
# this fill fail
assert df.columns == ["col1", "col2", "hwm_col"]
``` | + + But most users just use `columns=["*"]` anyway, they won’t see any changes. +- `FileDownloader.run()` now updates HWM in HWM Store not after each file is being successfully downloaded, + but after all files were handled. + + This is because: + * FileDownloader can be used with `DownloadOptions(workers=N)`, which could lead to race condition - one thread can save to HWM store one HWM value when another thread will save different value. + * FileDownloader can download hundreds and thousands of files, and issuing a request to HWM Store for each file could potentially DDoS HWM Store. ([#189](https://github.com/MobileTeleSystems/onetl/pull/189)) + + There is a exception handler which tries to save HWM to HWM store if download process was interrupted. But if it was interrupted by force, like sending `SIGKILL` event, + HWM will not be saved to HWM store, so some already downloaded files may be downloaded again next time. + + But unexpected process kill may produce other negative impact, like some file will be downloaded partially, so this is an expected behavior. + +## Features + +- Add Python 3.12 compatibility. ([#167](https://github.com/MobileTeleSystems/onetl/pull/167)) +- `Excel` file format now can be used with Spark 3.5.0. ([#187](https://github.com/MobileTeleSystems/onetl/pull/187)) +- `SnapshotBatchStagy` and `IncrementalBatchStrategy` does no raise exceptions if source does not contain any data. + Instead they stop at first iteration and return empty dataframe. ([#188](https://github.com/MobileTeleSystems/onetl/pull/188)) +- Cache result of `connection.check()` in high-level classes like `DBReader`, `FileDownloader` and so on. This makes logs less verbose. ([#190](https://github.com/MobileTeleSystems/onetl/pull/190)) + +## Bug Fixes + +- Fix `@slot` and `@hook` decorators returning methods with missing arguments in signature (Pylance, VS Code). ([#183](https://github.com/MobileTeleSystems/onetl/pull/183)) +- Kafka connector documentation said that it does support reading topic data incrementally by passing `group.id` or `groupIdPrefix`. + Actually, this is not true, because Spark does not send information to Kafka which messages were consumed. + So currently users can only read the whole topic, no incremental reads are supported. diff --git a/mddocs/changelog/0.10.1.md b/mddocs/changelog/0.10.1.md new file mode 100644 index 000000000..1bc703cb4 --- /dev/null +++ b/mddocs/changelog/0.10.1.md @@ -0,0 +1,25 @@ +# 0.10.1 (2024-02-05) + +## Features + +- Add support of `Incremental Strategies` for `Kafka` connection: + ```python + reader = DBReader( + connection=Kafka(...), + source="topic_name", + hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="offset"), + ) + + with IncrementalStrategy(): + df = reader.run() + ``` + + This lets you resume reading data from a Kafka topic starting at the last committed offset from your previous run. ([#202](https://github.com/MobileTeleSystems/onetl/pull/202)) +- Add `has_data`, `raise_if_no_data` methods to `DBReader` class. ([#203](https://github.com/MobileTeleSystems/onetl/pull/203)) +- Updare VMware Greenplum connector from `2.1.4` to `2.3.0`. This implies: + : * Greenplum 7.x support + * [Kubernetes support](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/configure.html#k8scfg) + * New read option [gpdb.matchDistributionPolicy](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#distpolmotion) + which allows to match each Spark executor with specific Greenplum segment, avoiding redundant data transfer between Greenplum segments + * Allows overriding [Greenplum optimizer parameters](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#greenplum-gucs) in read/write operations ([#208](https://github.com/MobileTeleSystems/onetl/pull/208)) +- `Greenplum.get_packages()` method now accepts optional arg `package_version` which allows to override version of Greenplum connector package. ([#208](https://github.com/MobileTeleSystems/onetl/pull/208)) diff --git a/mddocs/changelog/0.10.2.md b/mddocs/changelog/0.10.2.md new file mode 100644 index 000000000..3ca2693c2 --- /dev/null +++ b/mddocs/changelog/0.10.2.md @@ -0,0 +1,34 @@ +# 0.10.2 (2024-03-21) + +## Features + +- Add support of Pydantic v2. ([#230](https://github.com/MobileTeleSystems/onetl/pull/230)) + +## Improvements + +- Improve database connections documentation: + : * Add “Types” section describing mapping between Clickhouse and Spark types + * Add “Prerequisites” section describing different aspects of connecting to Clickhouse + * Separate documentation of `DBReader` and `.sql()` / `.pipeline(...)` + * Add examples for `.fetch()` and `.execute()` ([#211](https://github.com/MobileTeleSystems/onetl/pull/211), [#228](https://github.com/MobileTeleSystems/onetl/pull/228), [#229](https://github.com/MobileTeleSystems/onetl/pull/229), [#233](https://github.com/MobileTeleSystems/onetl/pull/233), [#234](https://github.com/MobileTeleSystems/onetl/pull/234), [#235](https://github.com/MobileTeleSystems/onetl/pull/235), [#236](https://github.com/MobileTeleSystems/onetl/pull/236), [#240](https://github.com/MobileTeleSystems/onetl/pull/240)) +- Add notes to Greenplum documentation about issues with IP resolution and building `gpfdist` URL ([#228](https://github.com/MobileTeleSystems/onetl/pull/228)) +- Allow calling `MongoDB.pipeline(...)` with passing just collection name, without explicit aggregation pipeline. ([#237](https://github.com/MobileTeleSystems/onetl/pull/237)) +- Update default `Postgres(extra={...})` to include `{"stringtype": "unspecified"}` option. + This allows to write text data to non-text column (or vice versa), relying to Postgres cast capabilities. + + For example, now it is possible to read column of type `money` as Spark’s `StringType()`, and write it back to the same column, + without using intermediate columns or tables. ([#229](https://github.com/MobileTeleSystems/onetl/pull/229)) + +## Bug Fixes + +- Return back handling of `DBReader(columns="string")`. This was a valid syntax up to v0.10 release, but it was removed because + most of users neved used it. It looks that we were wrong, returning this behavior back, but with deprecation warning. ([#238](https://github.com/MobileTeleSystems/onetl/pull/238)) +- Downgrade Greenplum package version from `2.3.0` to `2.2.0`. ([#239](https://github.com/MobileTeleSystems/onetl/pull/239)) + + This is because version 2.3.0 introduced issues with writing data to Greenplum 6.x. + Connector can open transaction with `SELECT * FROM table LIMIT 0` query, but does not close it, which leads to deadlocks. + + For using this connector with Greenplum 7.x, please pass package version explicitly: + ```python + maven_packages = Greenplum.get_packages(package_version="2.3.0", ...) + ``` diff --git a/mddocs/changelog/0.11.0.md b/mddocs/changelog/0.11.0.md new file mode 100644 index 000000000..2069d081f --- /dev/null +++ b/mddocs/changelog/0.11.0.md @@ -0,0 +1,202 @@ +# 0.11.0 (2024-05-27) + +## Breaking Changes + +There can be some changes in connection behavior, related to version upgrades. So we mark these changes as **breaking** although +most of users will not see any differences. + +- Update Clickhouse JDBC driver to latest version ([#249](https://github.com/MobileTeleSystems/onetl/pull/249)): + : * Package was renamed `ru.yandex.clickhouse:clickhouse-jdbc` → `com.clickhouse:clickhouse-jdbc`. + * Package version changed `0.3.2` → `0.6.0-patch5`. + * Driver name changed `ru.yandex.clickhouse.ClickHouseDriver` → `com.clickhouse.jdbc.ClickHouseDriver`. + + This brings up several fixes for Spark <-> Clickhouse type compatibility, and also Clickhouse clusters support. +- Update other JDBC drivers to latest versions: + : * MSSQL `12.2.0` → `12.6.2` ([#254](https://github.com/MobileTeleSystems/onetl/pull/254)). + * MySQL `8.0.33` → `8.4.0` ([#253](https://github.com/MobileTeleSystems/onetl/pull/253), [#285](https://github.com/MobileTeleSystems/onetl/pull/285)). + * Oracle `23.2.0.0` → `23.4.0.24.05` ([#252](https://github.com/MobileTeleSystems/onetl/pull/252), [#284](https://github.com/MobileTeleSystems/onetl/pull/284)). + * Postgres `42.6.0` → `42.7.3` ([#251](https://github.com/MobileTeleSystems/onetl/pull/251)). +- Update MongoDB connector to latest version: `10.1.1` → `10.3.0` ([#255](https://github.com/MobileTeleSystems/onetl/pull/255), [#283](https://github.com/MobileTeleSystems/onetl/pull/283)). + + This brings up Spark 3.5 support. +- Update `XML` package to latest version: `0.17.0` → `0.18.0` ([#259](https://github.com/MobileTeleSystems/onetl/pull/259)). + + This brings few bugfixes with datetime format handling. +- For JDBC connections add new `SQLOptions` class for `DB.sql(query, options=...)` method ([#272](https://github.com/MobileTeleSystems/onetl/pull/272)). + + Firsly, to keep naming more consistent. + + Secondly, some of options are not supported by `DB.sql(...)` method, but supported by `DBReader`. + For example, `SQLOptions` do not support `partitioning_mode` and require explicit definition of `lower_bound` and `upper_bound` when `num_partitions` is greater than 1. + `ReadOptions` does support `partitioning_mode` and allows skipping `lower_bound` and `upper_bound` values. + + This require some code changes. Before: + ```python + from onetl.connection import Postgres + + postgres = Postgres(...) + df = postgres.sql( + """ + SELECT * + FROM some.mytable + WHERE key = 'something' + """, + options=Postgres.ReadOptions( + partitioning_mode="range", + partition_column="id", + num_partitions=10, + ), + ) + ``` + + After: + ```python + from onetl.connection import Postgres + + postgres = Postgres(...) + df = postgres.sql( + """ + SELECT * + FROM some.mytable + WHERE key = 'something' + """, + options=Postgres.SQLOptions( + # partitioning_mode is not supported! + partition_column="id", + num_partitions=10, + lower_bound=0, # <-- set explicitly + upper_bound=1000, # <-- set explicitly + ), + ) + ``` + + For now, `DB.sql(query, options=...)` can accept `ReadOptions` to keep backward compatibility, but emits deprecation warning. + The support will be removed in `v1.0.0`. +- Split up `JDBCOptions` class into `FetchOptions` and `ExecuteOptions` ([#274](https://github.com/MobileTeleSystems/onetl/pull/274)). + + New classes are used by `DB.fetch(query, options=...)` and `DB.execute(query, options=...)` methods respectively. + This is mostly to keep naming more consistent. + + This require some code changes. Before: + ```python + from onetl.connection import Postgres + + postgres = Postgres(...) + df = postgres.fetch( + "SELECT * FROM some.mytable WHERE key = 'something'", + options=Postgres.JDBCOptions( + fetchsize=1000, + query_timeout=30, + ), + ) + + postgres.execute( + "UPDATE some.mytable SET value = 'new' WHERE key = 'something'", + options=Postgres.JDBCOptions(query_timeout=30), + ) + ``` + + After: + ```python + from onetl.connection import Postgres + + # Using FetchOptions for fetching data + postgres = Postgres(...) + df = postgres.fetch( + "SELECT * FROM some.mytable WHERE key = 'something'", + options=Postgres.FetchOptions( # <-- change class name + fetchsize=1000, + query_timeout=30, + ), + ) + + # Using ExecuteOptions for executing statements + postgres.execute( + "UPDATE some.mytable SET value = 'new' WHERE key = 'something'", + options=Postgres.ExecuteOptions(query_timeout=30), # <-- change class name + ) + ``` + + For now, `DB.fetch(query, options=...)` and `DB.execute(query, options=...)` can accept `JDBCOptions`, to keep backward compatibility, + but emit a deprecation warning. The old class will be removed in `v1.0.0`. +- Serialize `ColumnDatetimeHWM` to Clickhouse’s `DateTime64(6)` (precision up to microseconds) instead of `DateTime` (precision up to seconds) ([#267](https://github.com/MobileTeleSystems/onetl/pull/267)). + + In previous onETL versions, `ColumnDatetimeHWM` value was rounded to the second, and thus reading some rows that were read in previous runs, + producing duplicates. + + For Clickhouse versions below 21.1 comparing column of type `DateTime` with a value of type `DateTime64` is not supported, returning an empty dataframe. + To avoid this, replace: + ```python + DBReader( + ..., + hwm=DBReader.AutoDetectHWM( + name="my_hwm", + expression="hwm_column", # <-- + ), + ) + ``` + + with: + ```python + DBReader( + ..., + hwm=DBReader.AutoDetectHWM( + name="my_hwm", + expression="CAST(hwm_column AS DateTime64)", # <-- add explicit CAST + ), + ) + ``` +- Pass JDBC connection extra params as `properties` dict instead of URL with query part ([#268](https://github.com/MobileTeleSystems/onetl/pull/268)). + + This allows passing custom connection parameters like `Clickhouse(extra={"custom_http_options": "option1=value1,option2=value2"})` + without need to apply urlencode to parameter value, like `option1%3Dvalue1%2Coption2%3Dvalue2`. + +## Features + +Improve user experience with Kafka messages and Database tables with serialized columns, like JSON/XML. + +- Allow passing custom package version as argument for `DB.get_packages(...)` method of several DB connectors: + : * `Clickhouse.get_packages(package_version=..., apache_http_client_version=...)` ([#249](https://github.com/MobileTeleSystems/onetl/pull/249)). + * `MongoDB.get_packages(scala_version=..., spark_version=..., package_version=...)` ([#255](https://github.com/MobileTeleSystems/onetl/pull/255)). + * `MySQL.get_packages(package_version=...)` ([#253](https://github.com/MobileTeleSystems/onetl/pull/253)). + * `MSSQL.get_packages(java_version=..., package_version=...)` ([#254](https://github.com/MobileTeleSystems/onetl/pull/254)). + * `Oracle.get_packages(java_version=..., package_version=...)` ([#252](https://github.com/MobileTeleSystems/onetl/pull/252)). + * `Postgres.get_packages(package_version=...)` ([#251](https://github.com/MobileTeleSystems/onetl/pull/251)). + * `Teradata.get_packages(package_version=...)` ([#256](https://github.com/MobileTeleSystems/onetl/pull/256)). + + Now users can downgrade or upgrade connection without waiting for next onETL release. Previously only `Kafka` and `Greenplum` supported this feature. +- Add `FileFormat.parse_column(...)` method to several classes: + : * `Avro.parse_column(col)` ([#265](https://github.com/MobileTeleSystems/onetl/pull/265)). + * `JSON.parse_column(col, schema=...)` ([#257](https://github.com/MobileTeleSystems/onetl/pull/257)). + * `CSV.parse_column(col, schema=...)` ([#258](https://github.com/MobileTeleSystems/onetl/pull/258)). + * `XML.parse_column(col, schema=...)` ([#269](https://github.com/MobileTeleSystems/onetl/pull/269)). + + This allows parsing data in `value` field of Kafka message or string/binary column of some table as a nested Spark structure. +- Add `FileFormat.serialize_column(...)` method to several classes: + : * `Avro.serialize_column(col)` ([#265](https://github.com/MobileTeleSystems/onetl/pull/265)). + * `JSON.serialize_column(col)` ([#257](https://github.com/MobileTeleSystems/onetl/pull/257)). + * `CSV.serialize_column(col)` ([#258](https://github.com/MobileTeleSystems/onetl/pull/258)). + + This allows saving Spark nested structures or arrays to `value` field of Kafka message or string/binary column of some table. + +## Improvements + +Few documentation improvements. + +- Replace all `assert` in documentation with doctest syntax. This should make documentation more readable ([#273](https://github.com/MobileTeleSystems/onetl/pull/273)). +- Add generic `Troubleshooting` guide ([#275](https://github.com/MobileTeleSystems/onetl/pull/275)). +- Improve Kafka documentation: + : * Add “Prerequisites” page describing different aspects of connecting to Kafka. + * Improve “Reading from” and “Writing to” page of Kafka documentation, add more examples and usage notes. + * Add “Troubleshooting” page ([#276](https://github.com/MobileTeleSystems/onetl/pull/276)). +- Improve Hive documentation: + : * Add “Prerequisites” page describing different aspects of connecting to Hive. + * Improve “Reading from” and “Writing to” page of Hive documentation, add more examples and recommendations. + * Improve “Executing statements in Hive” page of Hive documentation. ([#278](https://github.com/MobileTeleSystems/onetl/pull/278)). +- Add “Prerequisites” page describing different aspects of using SparkHDFS and SparkS3 connectors. ([#279](https://github.com/MobileTeleSystems/onetl/pull/279)). +- Add note about connecting to Clickhouse cluster. ([#280](https://github.com/MobileTeleSystems/onetl/pull/280)). +- Add notes about versions when specific class/method/attribute/argument was added, renamed or changed behavior ([#282](https://github.com/MobileTeleSystems/onetl/pull/282)). + +## Bug Fixes + +- Fix missing `pysmb` package after installing `pip install onetl[files]` . diff --git a/mddocs/changelog/0.11.1.md b/mddocs/changelog/0.11.1.md new file mode 100644 index 000000000..823afe3be --- /dev/null +++ b/mddocs/changelog/0.11.1.md @@ -0,0 +1,9 @@ +# 0.11.1 (2024-05-29) + +## Features + +- Change `MSSQL.port` default from `1433` to `None`, allowing use of `instanceName` to detect port number. ([#287](https://github.com/MobileTeleSystems/onetl/pull/287)) + +## Bug Fixes + +- Remove `fetchsize` from `JDBC.WriteOptions`. ([#288](https://github.com/MobileTeleSystems/onetl/pull/288)) diff --git a/mddocs/changelog/0.11.2.md b/mddocs/changelog/0.11.2.md new file mode 100644 index 000000000..9278d22f8 --- /dev/null +++ b/mddocs/changelog/0.11.2.md @@ -0,0 +1,5 @@ +# 0.11.2 (2024-09-02) + +## Bug Fixes + +- Fix passing `Greenplum(extra={"options": ...})` during read/write operations. ([#308](https://github.com/MobileTeleSystems/onetl/pull/308)) diff --git a/mddocs/changelog/0.12.0.md b/mddocs/changelog/0.12.0.md new file mode 100644 index 000000000..d212c0062 --- /dev/null +++ b/mddocs/changelog/0.12.0.md @@ -0,0 +1,48 @@ +# 0.12.0 (2024-09-03) + +## Breaking Changes + +- Change connection URL used for generating HWM names of S3 and Samba sources: + : * `smb://host:port` -> `smb://host:port/share` + * `s3://host:port` -> `s3://host:port/bucket` ([#304](https://github.com/MobileTeleSystems/onetl/pull/304)) +- Update DB connectors/drivers to latest versions: + : * Clickhouse `0.6.0-patch5` → `0.6.5` + * MongoDB `10.3.0` → `10.4.0` + * MSSQL `12.6.2` → `12.8.1` + * MySQL `8.4.0` → `9.0.0` + * Oracle `23.4.0.24.05` → `23.5.0.24.07` + * Postgres `42.7.3` → `42.7.4` +- Update `Excel` package from `0.20.3` to `0.20.4`, to include Spark 3.5.1 support. ([#306](https://github.com/MobileTeleSystems/onetl/pull/306)) + +## Features + +- Add support for specifying file formats (`ORC`, `Parquet`, `CSV`, etc.) in `HiveWriteOptions.format` ([#292](https://github.com/MobileTeleSystems/onetl/pull/292)): + ```python + Hive.WriteOptions(format=ORC(compression="snappy")) + ``` +- Collect Spark execution metrics in following methods, and log then in DEBUG mode: + : * `DBWriter.run()` + * `FileDFWriter.run()` + * `Hive.sql()` + * `Hive.execute()` + + This is implemented using custom `SparkListener` which wraps the entire method call, and + then report collected metrics. But these metrics sometimes may be missing due to Spark architecture, + so they are not reliable source of information. That’s why logs are printed only in DEBUG mode, and + are not returned as method call result. ([#303](https://github.com/MobileTeleSystems/onetl/pull/303)) +- Generate default `jobDescription` based on currently executed method. Examples: + : * `DBWriter.run(schema.table) -> Postgres[host:5432/database]` + * `MongoDB[localhost:27017/admin] -> DBReader.has_data(mycollection)` + * `Hive[cluster].execute()` + + If user already set custom `jobDescription`, it will left intact. ([#304](https://github.com/MobileTeleSystems/onetl/pull/304)) +- Add log.info about JDBC dialect usage ([#305](https://github.com/MobileTeleSystems/onetl/pull/305)): + ```text + |MySQL| Detected dialect: 'org.apache.spark.sql.jdbc.MySQLDialect' + ``` +- Log estimated size of in-memory dataframe created by `JDBC.fetch` and `JDBC.execute` methods. ([#303](https://github.com/MobileTeleSystems/onetl/pull/303)) + +## Bug Fixes + +- Fix passing `Greenplum(extra={"options": ...})` during read/write operations. ([#308](https://github.com/MobileTeleSystems/onetl/pull/308)) +- Do not raise exception if yield-based hook whas something past (and only one) `yield`. diff --git a/mddocs/changelog/0.12.1.md b/mddocs/changelog/0.12.1.md new file mode 100644 index 000000000..3575634d5 --- /dev/null +++ b/mddocs/changelog/0.12.1.md @@ -0,0 +1,17 @@ +# 0.12.1 (2024-10-28) + +## Features + +- Log detected JDBC dialect while using `DBWriter`. + +## Bug Fixes + +- Fix `SparkMetricsRecorder` failing when receiving `SparkListenerTaskEnd` without `taskMetrics` (e.g. executor was killed by OOM). ([#313](https://github.com/MobileTeleSystems/onetl/pull/313)) +- Call `kinit` before checking for HDFS active namenode. +- Wrap `kinit` with `threading.Lock` to avoid multithreading issues. +- Immediately show `kinit` errors to user, instead of hiding them. +- Use `AttributeError` instead of `ImportError` in module’s `__getattr__` method, to make code compliant with Python spec. + +## Doc only Changes + +- Add note about [spark-dialect-extension](https://github.com/MobileTeleSystems/spark-dialect-extension) package to Clickhouse connector documentation. ([#310](https://github.com/MobileTeleSystems/onetl/pull/310)) diff --git a/mddocs/changelog/0.12.2.md b/mddocs/changelog/0.12.2.md new file mode 100644 index 000000000..23a8d383d --- /dev/null +++ b/mddocs/changelog/0.12.2.md @@ -0,0 +1,18 @@ +# 0.12.2 (2024-11-12) + +## Improvements + +- Change Spark `jobDescription` for DBReader & FileDFReader from `DBReader.run() -> Connection` to `Connection -> DBReader.run()`. + +## Bug Fixes + +- Fix `log_hwm` result for `KeyValueIntHWM` (used by Kafka). ([#316](https://github.com/MobileTeleSystems/onetl/pull/316)) +- Fix `log_collection` hiding values of `Kafka.addresses` in logs with `INFO` level. ([#316](https://github.com/MobileTeleSystems/onetl/pull/316)) + +## Dependencies + +- Allow using [etl-entities==2.4.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.4.0). + +## Doc only Changes + +- Fix links to MSSQL date & time type documentation. diff --git a/mddocs/changelog/0.12.3.md b/mddocs/changelog/0.12.3.md new file mode 100644 index 000000000..741c74e1f --- /dev/null +++ b/mddocs/changelog/0.12.3.md @@ -0,0 +1,5 @@ +# 0.12.3 (2024-11-22) + +## Bug Fixes + +- Allow passing table names in format `schema."table.with.dots"` to `DBReader(source=...)` and `DBWriter(target=...)`. diff --git a/mddocs/changelog/0.12.4.md b/mddocs/changelog/0.12.4.md new file mode 100644 index 000000000..3ebc57a87 --- /dev/null +++ b/mddocs/changelog/0.12.4.md @@ -0,0 +1,5 @@ +# 0.12.4 (2024-11-27) + +## Bug Fixes + +- Fix `DBReader(conn=oracle, options={"partitioning_mode": "hash"})` lead to data skew in last partition due to wrong `ora_hash` usage. ([#319](https://github.com/MobileTeleSystems/onetl/pull/319)) diff --git a/mddocs/changelog/0.12.5.md b/mddocs/changelog/0.12.5.md new file mode 100644 index 000000000..c542a50fd --- /dev/null +++ b/mddocs/changelog/0.12.5.md @@ -0,0 +1,13 @@ +# 0.12.5 (2024-12-03) + +## Improvements + +- Use `sipHash64` instead of `md5` in Clickhouse for reading data with `{"partitioning_mode": "hash"}`, as it is 5 times faster. +- Use `hashtext` instead of `md5` in Postgres for reading data with `{"partitioning_mode": "hash"}`, as it is 3-5 times faster. +- Use `BINARY_CHECKSUM` instead of `HASHBYTES` in MSSQL for reading data with `{"partitioning_mode": "hash"}`, as it is 5 times faster. + +## Big fixes + +- In JDBC sources wrap `MOD(partitionColumn, numPartitions)` with `ABS(...)` to make al returned values positive. This prevents data skew. +- Fix reading table data from MSSQL using `{"partitioning_mode": "hash"}` with `partitionColumn` of integer type. +- Fix reading table data from Postgres using `{"partitioning_mode": "hash"}` lead to data skew (all the data was read into one Spark partition). diff --git a/mddocs/changelog/0.13.0.md b/mddocs/changelog/0.13.0.md new file mode 100644 index 000000000..0b7256299 --- /dev/null +++ b/mddocs/changelog/0.13.0.md @@ -0,0 +1,197 @@ +# 0.13.0 (2025-02-24) + +🎉 3 years since first release 0.1.0 🎉 + +## Breaking Changes + +- Add Python 3.13. support. ([#298](https://github.com/MobileTeleSystems/onetl/pull/298)) +- Change the logic of `FileConnection.walk` and `FileConnection.list_dir`. ([#327](https://github.com/MobileTeleSystems/onetl/pull/327)) + + Previously `limits.stops_at(path) == True` considered as “return current file and stop”, and could lead to exceeding some limit. + Not it means “stop immediately”. +- Change default value for `FileDFWriter.Options(if_exists=...)` from `error` to `append`, + to make it consistent with other `.Options()` classes within onETL. ([#343](https://github.com/MobileTeleSystems/onetl/pull/343)) + +## Features + +- Add support for `FileModifiedTimeHWM` HWM class (see [etl-entities 2.5.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.5.0)): + ```python + from etl_entitites.hwm import FileModifiedTimeHWM + from onetl.file import FileDownloader + from onetl.strategy import IncrementalStrategy + + downloader = FileDownloader( + ..., + hwm=FileModifiedTimeHWM(name="somename"), + ) + + with IncrementalStrategy(): + downloader.run() + ``` +- Introduce `FileSizeRange(min=..., max=...)` filter class. ([#325](https://github.com/MobileTeleSystems/onetl/pull/325)) + + Now users can set `FileDownloader` / `FileMover` to download/move only files with specific file size range: + ```python + from onetl.file import FileDownloader + from onetl.file.filter import FileSizeRange + + downloader = FileDownloader( + ..., + filters=[FileSizeRange(min="10KiB", max="1GiB")], + ) + ``` +- Introduce `TotalFilesSize(...)` limit class. ([#326](https://github.com/MobileTeleSystems/onetl/pull/326)) + + Now users can set `FileDownloader` / `FileMover` to stop downloading/moving files after reaching a certain amount of data: + ```python + from datetime import datetime, timedelta + from onetl.file import FileDownloader + from onetl.file.limit import TotalFilesSize + + downloader = FileDownloader( + ..., + limits=[TotalFilesSize("1GiB")], + ) + ``` +- Implement `FileModifiedTime(since=..., until=...)` file filter. ([#330](https://github.com/MobileTeleSystems/onetl/pull/330)) + + Now users can set `FileDownloader` / `FileMover` to download/move only files with specific file modification time: + ```python + from datetime import datetime, timedelta + from onetl.file import FileDownloader + from onetl.file.filter import FileModifiedTime + + downloader = FileDownloader( + ..., + filters=[FileModifiedTime(before=datetime.now() - timedelta(hours=1))], + ) + ``` +- Add `SparkS3.get_exclude_packages()` and `Kafka.get_exclude_packages()` methods. ([#341](https://github.com/MobileTeleSystems/onetl/pull/341)) + + Using them allows to skip downloading dependencies not required by this specific connector, or which are already a part of Spark/PySpark: + ```python + from onetl.connection import SparkS3, Kafka + + maven_packages = [ + *SparkS3.get_packages(spark_version="3.5.4"), + *Kafka.get_packages(spark_version="3.5.4"), + ] + exclude_packages = SparkS3.get_exclude_packages() + Kafka.get_exclude_packages() + spark = ( + SparkSession.builder.appName("spark_app_onetl_demo") + .config("spark.jars.packages", ",".join(maven_packages)) + .config("spark.jars.excludes", ",".join(exclude_packages)) + .getOrCreate() + ) + ``` + +## Improvements + +- All DB connections opened by `JDBC.fetch(...)`, `JDBC.execute(...)` or `JDBC.check()` + are immediately closed after the statements is executed. ([#334](https://github.com/MobileTeleSystems/onetl/pull/334)) + + Previously Spark session with `master=local[3]` actually opened up to 5 connections to target DB - one for `JDBC.check()`, + another for Spark driver interaction with DB to create tables, and one for each Spark executor. Now only max 4 connections are opened, + as `JDBC.check()` does not hold opened connection. + + This is important for RDBMS like Postgres or Greenplum where number of connections is strictly limited and limit is usually quite low. +- Set up `ApplicationName` (client info) for Clickhouse, MongoDB, MSSQL, MySQL and Oracle. ([#339](https://github.com/MobileTeleSystems/onetl/pull/339), [#248](https://github.com/MobileTeleSystems/onetl/pull/248)) + + Also update `ApplicationName` format for Greenplum, Postgres, Kafka and SparkS3. + Now all connectors have the same `ApplicationName` format: `${spark.applicationId} ${spark.appName} onETL/${onetl.version} Spark/${spark.version}` + + The only connections not sending `ApplicationName` are Teradata and FileConnection implementations. +- Now `DB.check()` will test connection availability not only on Spark driver, but also from some Spark executor. ([#346](https://github.com/MobileTeleSystems/onetl/pull/346)) + + This allows to fail immediately if Spark driver host has network access to target DB, but Spark executors have not. + + #### NOTE + Now `Greenplum.check()` requires the same user grants as `DBReader(connection=greenplum)`: + ```sql + -- yes, "writable" for reading data from GP, it's not a mistake + ALTER USER username CREATEEXTTABLE(type = 'writable', protocol = 'gpfdist'); + + -- for both reading and writing to GP + -- ALTER USER username CREATEEXTTABLE(type = 'readable', protocol = 'gpfdist') CREATEEXTTABLE(type = 'writable', protocol = 'gpfdist'); + ``` + + Please ask your Greenplum administrators to provide these grants. + +## Bug Fixes + +- Avoid suppressing Hive Metastore errors while using `DBWriter`. ([#329](https://github.com/MobileTeleSystems/onetl/pull/329)) + + Previously this was implemented as: + ```python + try: + spark.sql(f"SELECT * FROM {table}") + table_exists = True + except Exception: + table_exists = False + ``` + + If Hive Metastore was overloaded and responded with an exception, it was considered as non-existing table, resulting + to full table override instead of append or override only partitions subset. +- Fix using onETL to write data to PostgreSQL or Greenplum instances behind *pgbouncer* with `pool_mode=transaction`. ([#336](https://github.com/MobileTeleSystems/onetl/pull/336)) + + Previously `Postgres.check()` opened a read-only transaction, pgbouncer changed the entire connection type from read-write to read-only, + and when `DBWriter.run(df)` executed in read-only connection, producing errors like: + ```default + org.postgresql.util.PSQLException: ERROR: cannot execute INSERT in a read-only transaction + org.postgresql.util.PSQLException: ERROR: cannot execute TRUNCATE TABLE in a read-only transaction + ``` + + Added a workaround by passing `readOnly=True` to JDBC params for read-only connections, so pgbouncer may differ read-only and read-write connections properly. + + After upgrading onETL 0.13.x or higher the same error still may appear of pgbouncer still holds read-only connections and returns them for DBWriter. + To this this, user can manually convert read-only connection to read-write: + ```python + postgres.execute("BEGIN READ WRITE;") # <-- add this line + DBWriter(...).run() + ``` + + After all connections in pgbouncer pool were converted from read-only to read-write, and error fixed, this additional line could be removed. + + See [Postgres JDBC driver documentation](https://jdbc.postgresql.org/documentation/use/). +- Fix `MSSQL.fetch(...)` and `MySQL.fetch(...)` opened a read-write connection instead of read-only. ([#337](https://github.com/MobileTeleSystems/onetl/pull/337)) + + Now this is fixed: + : * `MSSQL.fetch(...)` establishes connection with `ApplicationIntent=ReadOnly`. + * `MySQL.fetch(...)` calls `SET SESSION TRANSACTION READ ONLY` statement. +- Fixed passing multiple filters to `FileDownloader` and `FileMover`. ([#338](https://github.com/MobileTeleSystems/onetl/pull/338)) + If was caused by sorting filters list in internal logging method, but `FileFilter` subclasses are not sortable. +- Fix a false warning about a lof of parallel connections to Grenplum. ([#342](https://github.com/MobileTeleSystems/onetl/pull/342)) + + Creating Spark session with `.master("local[5]")` may open up to 6 connections to Greenplum (=number of Spark executors + 1 for driver), + but onETL instead used number of *CPU cores* on the host as a number of parallel connections. + + This lead to showing a false warning that number of Greenplum connections is too high, + which actually should be the case only if number of executors is higher than 30. +- Fix MongoDB trying to use current database name as `authSource`. ([#347](https://github.com/MobileTeleSystems/onetl/pull/347)) + + Use default connector value which is `admin` database. Previous onETL versions could be fixed by: + ```python + from onetl.connection import MongoDB + + mongodb = MongoDB( + ..., + database="mydb", + extra={ + "authSource": "admin", + }, + ) + ``` + +## Dependencies + +- Minimal `etl-entities` version is now [2.5.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.5.0). ([#331](https://github.com/MobileTeleSystems/onetl/pull/331)) +- Update DB connectors/drivers to latest versions: ([#345](https://github.com/MobileTeleSystems/onetl/pull/345)) + : * Clickhouse `0.6.5` → `0.7.2` + * MongoDB `10.4.0` → `10.4.1` + * MySQL `9.0.0` → `9.2.0` + * Oracle `23.5.0.24.07` → `23.7.0.25.01` + * Postgres `42.7.4` → `42.7.5` + +## Doc only Changes + +- Split large code examples to tabs. ([#344](https://github.com/MobileTeleSystems/onetl/pull/344)) diff --git a/mddocs/changelog/0.13.1.md b/mddocs/changelog/0.13.1.md new file mode 100644 index 000000000..e8eb120d2 --- /dev/null +++ b/mddocs/changelog/0.13.1.md @@ -0,0 +1,9 @@ +# 0.13.1 (2025-03-06) + +## Bug Fixes + +In 0.13.0, using `DBWriter(connection=hive, target="SOMEDB.SOMETABLE")` lead to executing `df.write.saveAsTable()` +instead of `df.write.insertInto()` if target table `somedb.sometable` already exist. + +This is caused by table name normalization (Hive uses lower-case names), which wasn’t properly handled by method used for checking table existence. +([#350](https://github.com/MobileTeleSystems/onetl/pull/350)) diff --git a/mddocs/changelog/0.13.3.md b/mddocs/changelog/0.13.3.md new file mode 100644 index 000000000..1aa289b49 --- /dev/null +++ b/mddocs/changelog/0.13.3.md @@ -0,0 +1,5 @@ +# 0.13.3 (2025-03-11) + +## Dependencies + +Allow using [etl-entities 2.6.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.6.0). diff --git a/mddocs/changelog/0.13.4.md b/mddocs/changelog/0.13.4.md new file mode 100644 index 000000000..10f695e0c --- /dev/null +++ b/mddocs/changelog/0.13.4.md @@ -0,0 +1,10 @@ +# 0.13.4 (2025-03-20) + +## Doc only Changes + +- Prefer `ReadOptions(partitionColumn=..., numPartitions=..., queryTimeout=...)` + instead of `ReadOptions(partition_column=..., num_partitions=..., query_timeout=...)`, + to match Spark documentation. ([#352](https://github.com/MobileTeleSystems/onetl/pull/352)) +- Prefer `WriteOptions(if_exists=...)` instead of `WriteOptions(mode=...)` for IDE suggestions. ([#354](https://github.com/MobileTeleSystems/onetl/pull/354)) +- Document all options of supported file formats. + ([#355](https://github.com/MobileTeleSystems/onetl/pull/355), [#356](https://github.com/MobileTeleSystems/onetl/pull/356), [#357](https://github.com/MobileTeleSystems/onetl/pull/357), [#358](https://github.com/MobileTeleSystems/onetl/pull/358), [#359](https://github.com/MobileTeleSystems/onetl/pull/359), [#360](https://github.com/MobileTeleSystems/onetl/pull/360), [#361](https://github.com/MobileTeleSystems/onetl/pull/361), [#362](https://github.com/MobileTeleSystems/onetl/pull/362)) diff --git a/mddocs/changelog/0.7.0.md b/mddocs/changelog/0.7.0.md new file mode 100644 index 000000000..3f896b82b --- /dev/null +++ b/mddocs/changelog/0.7.0.md @@ -0,0 +1,211 @@ +# 0.7.0 (2023-05-15) + +## 🎉 onETL is now open source 🎉 + +That was long road, but we finally did it! + +## Breaking Changes + +* Changed installation method. + + **TL;DR What should I change to restore previous behavior** + + Simple way: + + | onETL < 0.7.0 | onETL >= 0.7.0 | + |-------------------|-----------------------------------| + | pip install onetl | pip install onetl[files,kerberos] | + + Right way - enumerate connectors should be installed: + ```bash + pip install onetl[hdfs,ftp,kerberos] # except DB connections + ``` + + **Details** + + In onetl<0.7 the package installation looks like: + ```bash + pip install onetl + ``` + + But this includes all dependencies for all connectors, even if user does not use them. + This caused some issues, for example user had to install Kerberos libraries to be able to install onETL, even if user uses only S3 (without Kerberos support). + + Since 0.7.0 installation process was changed: + ```bash + pip install onetl # minimal installation, only onETL core + # there is no extras for DB connections because they are using Java packages which are installed in runtime + + pip install onetl[ftp,ftps,hdfs,sftp,s3,webdav] # install dependencies for specified file connections + pip install onetl[files] # install dependencies for all file connections + + pip install onetl[kerberos] # Kerberos auth support + pip install onetl[spark] # install PySpark to use DB connections + + pip install onetl[spark,kerberos,files] # all file connections + Kerberos + PySpark + pip install onetl[all] # alias for previous case + ``` + + There are corresponding documentation items for each extras. + + Also onETL checks that some requirements are missing, and raises exception with recommendation how to install them: + ```text + Cannot import module "pyspark". + + Since onETL v0.7.0 you should install package as follows: + pip install onetl[spark] + + or inject PySpark to sys.path in some other way BEFORE creating MongoDB instance. + ``` + + ```text + Cannot import module "ftputil". + + Since onETL v0.7.0 you should install package as follows: + pip install onetl[ftp] + + or + pip install onetl[files] + ``` +* Added new `cluster` argument to `Hive` and `HDFS` connections. + + `Hive` qualified name (used in HWM) contains cluster name. But in onETL<0.7.0 cluster name had hard coded value `rnd-dwh` which was not OK for some users. + + `HDFS` connection qualified name contains host (active namenode of Hadoop cluster), but its value can change over time, leading to creating of new HWM. + + Since onETL 0.7.0 both `Hive` and `HDFS` connections have `cluster` attribute which can be set to a specific cluster name. + For `Hive` it is mandatory, for `HDFS` it can be omitted (using host as a fallback). + + But passing cluster name every time could lead to errors. + + Now `Hive` and `HDFS` have nested class named `slots` with methods: + * `normalize_cluster_name` + * `get_known_clusters` + * `get_current_cluster` + * `normalize_namenode_host` (only `HDFS`) + * `get_cluster_namenodes` (only `HDFS`) + * `get_webhdfs_port` (only `HDFS`) + * `is_namenode_active` (only `HDFS`) + + And new method `HDFS.get_current` / `Hive.get_current`. + + Developers can implement hooks validating user input or substituting values for automatic cluster detection. + This should improve user experience while using these connectors. + + See slots documentation. +* Update JDBC connection drivers. + * Greenplum `2.1.3` → `2.1.4`. + * MSSQL `10.2.1.jre8` → `12.2.0.jre8`. Minimal supported version of MSSQL is now 2014 instead 2021. + * MySQL `8.0.30` → `8.0.33`: + \* Package was renamed `mysql:mysql-connector-java` → `com.mysql:mysql-connector-j`. + \* Driver class was renamed `com.mysql.jdbc.Driver` → `com.mysql.cj.jdbc.Driver`. + * Oracle `21.6.0.0.1` → `23.2.0.0`. + * Postgres `42.4.0` → `42.6.0`. + * Teradata `17.20.00.08` → `17.20.00.15`: + \* Package was renamed `com.teradata.jdbc:terajdbc4` → `com.teradata.jdbc:terajdbc`. + \* Teradata driver is now published to Maven. + + See [#31](https://github.com/MobileTeleSystems/onetl/pull/31). + +## Features + +* Added MongoDB connection. + + Using official [MongoDB connector for Spark v10](https://www.mongodb.com/docs/spark-connector/current/). Only Spark 3.2+ is supported. + + There are some differences between MongoDB and other database sources: + * Instead of `mongodb.sql` method there is `mongodb.pipeline`. + * No methods `mongodb.fetch` and `mongodb.execute`. + * `DBReader.hint` and `DBReader.where` have different types than in SQL databases: + + ```python + where = { + "col1": { + "$eq": 10, + }, + } + + hint = { + "col1": 1, + } + ``` + + * Because MongoDB does not have schemas of collections, but Spark cannot create dataframe with dynamic schema, new option `DBReader.df_schema` was introduced. + It is mandatory for MongoDB, but optional for other sources. + * Currently DBReader cannot be used with MongoDB and hwm expression, e.g. `hwm_column=("mycolumn", {"$cast": {"col1": "date"}})` + + Because there are no tables in MongoDB, some options were renamed in core classes: + * `DBReader(table=...)` → `DBReader(source=...)` + * `DBWriter(table=...)` → `DBWriter(target=...)` + + Old names can be used too, they are not deprecated ([#30](https://github.com/MobileTeleSystems/onetl/pull/30)). +* Added option for disabling some plugins during import. + + Previously if some plugin were failing during the import, the only way to import onETL would be to disable all plugins + using environment variable. + + Now there are several variables with different behavior: + * `ONETL_PLUGINS_ENABLED=false` - disable all plugins autoimport. Previously it was named `ONETL_ENABLE_PLUGINS`. + * `ONETL_PLUGINS_BLACKLIST=plugin-name,another-plugin` - set list of plugins which should NOT be imported automatically. + * `ONETL_PLUGINS_WHITELIST=plugin-name,another-plugin` - set list of plugins which should ONLY be imported automatically. + + Also we improved exception message with recommendation how to disable a failing plugin: + ```text + Error while importing plugin 'mtspark' from package 'mtspark' v4.0.0. + + Statement: + import mtspark.onetl + + Check if plugin is compatible with current onETL version 0.7.0. + + You can disable loading this plugin by setting environment variable: + ONETL_PLUGINS_BLACKLIST='mtspark,failing-plugin' + + You can also define a whitelist of packages which can be loaded by onETL: + ONETL_PLUGINS_WHITELIST='not-failing-plugin1,not-failing-plugin2' + + Please take into account that plugin name may differ from package or module name. + See package metadata for more details + ``` + +## Improvements + +* Added compatibility with Python 3.11 and PySpark 3.4.0. + + File connections were OK, but `jdbc.fetch` and `jdbc.execute` were failing. Fixed in [#28](https://github.com/MobileTeleSystems/onetl/pull/28). +* Added check for missing Java packages. + + Previously if DB connection tried to use some Java class which were not loaded into Spark version, it raised an exception + with long Java stacktrace. Most users failed to interpret this trace. + + Now onETL shows the following error message: + ```text + |Spark| Cannot import Java class 'com.mongodb.spark.sql.connector.MongoTableProvider'. + + It looks like you've created Spark session without this option: + SparkSession.builder.config("spark.jars.packages", MongoDB.package_spark_3_2) + + Please call `spark.stop()`, restart the interpreter, + and then create new SparkSession with proper options. + ``` +* Documentation improvements. + * Changed documentation site theme - using [furo](https://github.com/pradyunsg/furo) + instead of default [ReadTheDocs](https://github.com/readthedocs/sphinx_rtd_theme). + + New theme supports wide screens and dark mode. + See [#10](https://github.com/MobileTeleSystems/onetl/pull/10). + * Now each connection class have compatibility table for Spark + Java + Python. + * Added global compatibility table for Spark + Java + Python + Scala. + +## Bug Fixes + +* Fixed several SFTP issues. + * If SSH config file `~/.ssh/config` contains some options not recognized by Paramiko (unknown syntax, unknown option name), + previous versions were raising exception until fixing or removing this file. Since 0.7.0 exception is replaced with warning. + * If user passed `host_key_check=False` but server changed SSH keys, previous versions raised exception until new key is accepted. + Since 0.7.0 exception is replaced with warning if option value is `False`. + + Fixed in [#19](https://github.com/MobileTeleSystems/onetl/pull/19). +* Fixed several S3 issues. + + There was a bug in S3 connection which prevented handling files in the root of a bucket - they were invisible for the connector. Fixed in [#29](https://github.com/MobileTeleSystems/onetl/pull/29). diff --git a/mddocs/changelog/0.7.1.md b/mddocs/changelog/0.7.1.md new file mode 100644 index 000000000..fc423f756 --- /dev/null +++ b/mddocs/changelog/0.7.1.md @@ -0,0 +1,31 @@ +# 0.7.1 (2023-05-23) + +## Bug Fixes + +* Fixed `setup_logging` function. + + In onETL==0.7.0 calling `onetl.log.setup_logging()` broke the logging: + ```text + Traceback (most recent call last): + File "/opt/anaconda/envs/py39/lib/python3.9/logging/__init__.py", line 434, in format + return self._format(record) + File "/opt/anaconda/envs/py39/lib/python3.9/logging/__init__.py", line 430, in _format + return self._fmt % record.dict + KeyError: 'levelname:8s' + ``` +* Fixed installation examples. + + In onETL==0.7.0 there are examples of installing onETL with extras: + ```bash + pip install onetl[files, kerberos, spark] + ``` + + But pip fails to install such package: + ```text + ERROR: Invalid requirement: 'onet[files,' + ``` + + This is because of spaces in extras clause. Fixed: + ```bash + pip install onetl[files,kerberos,spark] + ``` diff --git a/mddocs/changelog/0.7.2.md b/mddocs/changelog/0.7.2.md new file mode 100644 index 000000000..80bb2cfc5 --- /dev/null +++ b/mddocs/changelog/0.7.2.md @@ -0,0 +1,33 @@ +# 0.7.2 (2023-05-24) + +## Dependencies + +* Limited `typing-extensions` version. + + `typing-extensions==4.6.0` release contains some breaking changes causing errors like: + ```text + Traceback (most recent call last): + File "/Users/project/lib/python3.9/typing.py", line 852, in __subclasscheck__ + return issubclass(cls, self.__origin__) + TypeError: issubclass() arg 1 must be a class + ``` + + `typing-extensions==4.6.1` was causing another error: + ```text + Traceback (most recent call last): + File "/home/maxim/Repo/typing_extensions/1.py", line 33, in + isinstance(file, ContainsException) + File "/home/maxim/Repo/typing_extensions/src/typing_extensions.py", line 599, in __instancecheck__ + if super().__instancecheck__(instance): + File "/home/maxim/.pyenv/versions/3.7.8/lib/python3.7/abc.py", line 139, in __instancecheck__ + return _abc_instancecheck(cls, instance) + File "/home/maxim/Repo/typing_extensions/src/typing_extensions.py", line 583, in __subclasscheck__ + return super().__subclasscheck__(other) + File "/home/maxim/.pyenv/versions/3.7.8/lib/python3.7/abc.py", line 143, in __subclasscheck__ + return _abc_subclasscheck(cls, subclass) + File "/home/maxim/Repo/typing_extensions/src/typing_extensions.py", line 661, in _proto_hook + and other._is_protocol + AttributeError: type object 'PathWithFailure' has no attribute '_is_protocol' + ``` + + We updated requirements with `typing-extensions<4.6` until fixing compatibility issues. diff --git a/mddocs/changelog/0.8.0.md b/mddocs/changelog/0.8.0.md new file mode 100644 index 000000000..3fe438549 --- /dev/null +++ b/mddocs/changelog/0.8.0.md @@ -0,0 +1,137 @@ +# 0.8.0 (2023-05-31) + +## Breaking Changes + +- Rename methods of `FileConnection` classes: + * `get_directory` → `resolve_dir` + * `get_file` → `resolve_file` + * `listdir` → `list_dir` + * `mkdir` → `create_dir` + * `rmdir` → `remove_dir` + + New naming should be more consistent. + + They were undocumented in previous versions, but someone could use these methods, so this is a breaking change. ([#36](https://github.com/MobileTeleSystems/onetl/pull/36)) +- Deprecate `onetl.core.FileFilter` class, replace it with new classes: + * `onetl.file.filter.Glob` + * `onetl.file.filter.Regexp` + * `onetl.file.filter.ExcludeDir` + + Old class will be removed in v1.0.0. ([#43](https://github.com/MobileTeleSystems/onetl/pull/43)) +- Deprecate `onetl.core.FileLimit` class, replace it with new class `onetl.file.limit.MaxFilesCount`. + + Old class will be removed in v1.0.0. ([#44](https://github.com/MobileTeleSystems/onetl/pull/44)) +- Change behavior of `BaseFileLimit.reset` method. + + This method should now return `self` instead of `None`. + Return value could be the same limit object or a copy, this is an implementation detail. ([#44](https://github.com/MobileTeleSystems/onetl/pull/44)) +- Replaced `FileDownloader.filter` and `.limit` with new options `.filters` and `.limits`: + ```python + FileDownloader( + ..., + filter=FileFilter(glob="*.txt", exclude_dir="/path"), + limit=FileLimit(count_limit=10), + ) + ``` + + ```python + FileDownloader( + ..., + filters=[Glob("*.txt"), ExcludeDir("/path")], + limits=[MaxFilesCount(10)], + ) + ``` + + This allows to developers to implement their own filter and limit classes, and combine them with existing ones. + + Old behavior still supported, but it will be removed in v1.0.0. ([#45](https://github.com/MobileTeleSystems/onetl/pull/45)) +- Removed default value for `FileDownloader.limits`, user should pass limits list explicitly. ([#45](https://github.com/MobileTeleSystems/onetl/pull/45)) +- Move classes from module `onetl.core`: + ```python + from onetl.core import DBReader + from onetl.core import DBWriter + from onetl.core import FileDownloader + from onetl.core import FileUploader + ``` + + with new modules `onetl.db` and `onetl.file`: + ```python + from onetl.db import DBReader + from onetl.db import DBWriter + + from onetl.file import FileDownloader + from onetl.file import FileUploader + ``` + + Imports from old module `onetl.core` still can be used, but marked as deprecated. Module will be removed in v1.0.0. ([#46](https://github.com/MobileTeleSystems/onetl/pull/46)) + +## Features + +- Add `rename_dir` method. + + Method was added to following connections: + * `FTP` + * `FTPS` + * `HDFS` + * `SFTP` + * `WebDAV` + + It allows to rename/move directory to new path with all its content. + + `S3` does not have directories, so there is no such method in that class. ([#40](https://github.com/MobileTeleSystems/onetl/pull/40)) +- Add `onetl.file.FileMover` class. + + It allows to move files between directories of remote file system. + Signature is almost the same as in `FileDownloader`, but without HWM support. ([#42](https://github.com/MobileTeleSystems/onetl/pull/42)) + +## Improvements + +- Document all public methods in `FileConnection` classes: + * `download_file` + * `resolve_dir` + * `resolve_file` + * `get_stat` + * `is_dir` + * `is_file` + * `list_dir` + * `create_dir` + * `path_exists` + * `remove_file` + * `rename_file` + * `remove_dir` + * `upload_file` + * `walk` ([#39](https://github.com/MobileTeleSystems/onetl/pull/39)) +- Update documentation of `check` method of all connections - add usage example and document result type. ([#39](https://github.com/MobileTeleSystems/onetl/pull/39)) +- Add new exception type `FileSizeMismatchError`. + + Methods `connection.download_file` and `connection.upload_file` now raise new exception type instead of `RuntimeError`, + if target file after download/upload has different size than source. ([#39](https://github.com/MobileTeleSystems/onetl/pull/39)) +- Add new exception type `DirectoryExistsError` - it is raised if target directory already exists. ([#40](https://github.com/MobileTeleSystems/onetl/pull/40)) +- Improved `FileDownloader` / `FileUploader` exception logging. + + If `DEBUG` logging is enabled, print exception with stacktrace instead of + printing only exception message. ([#42](https://github.com/MobileTeleSystems/onetl/pull/42)) +- Updated documentation of `FileUploader`. + * Class does not support read strategies, added note to documentation. + * Added examples of using `run` method with explicit files list passing, both absolute and relative paths. + * Fix outdated imports and class names in examples. ([#42](https://github.com/MobileTeleSystems/onetl/pull/42)) +- Updated documentation of `DownloadResult` class - fix outdated imports and class names. ([#42](https://github.com/MobileTeleSystems/onetl/pull/42)) +- Improved file filters documentation section. + + Document interface class `onetl.base.BaseFileFilter` and function `match_all_filters`. ([#43](https://github.com/MobileTeleSystems/onetl/pull/43)) +- Improved file limits documentation section. + + Document interface class `onetl.base.BaseFileLimit` and functions `limits_stop_at` / `limits_reached` / `reset_limits`. ([#44](https://github.com/MobileTeleSystems/onetl/pull/44)) +- Added changelog. + + Changelog is generated from separated news files using [towncrier](https://pypi.org/project/towncrier/). ([#47](https://github.com/MobileTeleSystems/onetl/pull/47)) + +## Misc + +- Improved CI workflow for tests. + * If developer haven’t changed source core of a specific connector or its dependencies, + run tests only against maximum supported versions of Spark, Python, Java and db/file server. + * If developed made some changes in a specific connector, or in core classes, or in dependencies, + run tests for both minimal and maximum versions. + * Once a week run all aganst for minimal and latest versions to detect breaking changes in dependencies + * Minimal tested Spark version is 2.3.1 instead on 2.4.8. ([#32](https://github.com/MobileTeleSystems/onetl/pull/32)) diff --git a/mddocs/changelog/0.8.1.md b/mddocs/changelog/0.8.1.md new file mode 100644 index 000000000..d3bd32564 --- /dev/null +++ b/mddocs/changelog/0.8.1.md @@ -0,0 +1,34 @@ +# 0.8.1 (2023-07-10) + +## Features + +- Add `@slot` decorator to public methods of: + * `DBConnection` + * `FileConnection` + * `DBReader` + * `DBWriter` + * `FileDownloader` + * `FileUploader` + * `FileMover` ([#49](https://github.com/MobileTeleSystems/onetl/pull/49)) +- Add `workers` field to `FileDownloader` / `FileUploader` / `FileMover`. `Options` classes. + + This allows to speed up all file operations using parallel threads. ([#57](https://github.com/MobileTeleSystems/onetl/pull/57)) + +## Improvements + +- Add documentation for HWM store `.get` and `.save` methods. ([#49](https://github.com/MobileTeleSystems/onetl/pull/49)) +- Improve Readme: + * Move `Quick start` section from documentation + * Add `Non-goals` section + * Fix code blocks indentation ([#50](https://github.com/MobileTeleSystems/onetl/pull/50)) +- Improve Contributing guide: + * Move `Develop` section from Readme + * Move `docs/changelog/README.rst` content + * Add `Limitations` section + * Add instruction of creating a fork and building documentation ([#50](https://github.com/MobileTeleSystems/onetl/pull/50)) +- Remove duplicated checks for source file existence in `FileDownloader` / `FileMover`. ([#57](https://github.com/MobileTeleSystems/onetl/pull/57)) +- Update default logging format to include thread name. ([#57](https://github.com/MobileTeleSystems/onetl/pull/57)) + +## Bug Fixes + +- Fix `S3.list_dir('/')` returns empty list on latest Minio version. ([#58](https://github.com/MobileTeleSystems/onetl/pull/58)) diff --git a/mddocs/changelog/0.9.0.md b/mddocs/changelog/0.9.0.md new file mode 100644 index 000000000..b2d10f257 --- /dev/null +++ b/mddocs/changelog/0.9.0.md @@ -0,0 +1,107 @@ +# 0.9.0 (2023-08-17) + +## Breaking Changes + +- Rename methods: + * `DBConnection.read_df` → `DBConnection.read_source_as_df` + * `DBConnection.write_df` → `DBConnection.write_df_to_target` ([#66](https://github.com/MobileTeleSystems/onetl/pull/66)) +- Rename classes: + * `HDFS.slots` → `HDFS.Slots` + * `Hive.slots` → `Hive.Slots` + + Old names are left intact, but will be removed in v1.0.0 ([#103](https://github.com/MobileTeleSystems/onetl/pull/103)) +- Rename options to make them self-explanatory: + * `Hive.WriteOptions(mode="append")` → `Hive.WriteOptions(if_exists="append")` + * `Hive.WriteOptions(mode="overwrite_table")` → `Hive.WriteOptions(if_exists="replace_entire_table")` + * `Hive.WriteOptions(mode="overwrite_partitions")` → `Hive.WriteOptions(if_exists="replace_overlapping_partitions")` + * `JDBC.WriteOptions(mode="append")` → `JDBC.WriteOptions(if_exists="append")` + * `JDBC.WriteOptions(mode="overwrite")` → `JDBC.WriteOptions(if_exists="replace_entire_table")` + * `Greenplum.WriteOptions(mode="append")` → `Greenplum.WriteOptions(if_exists="append")` + * `Greenplum.WriteOptions(mode="overwrite")` → `Greenplum.WriteOptions(if_exists="replace_entire_table")` + * `MongoDB.WriteOptions(mode="append")` → `Greenplum.WriteOptions(if_exists="append")` + * `MongoDB.WriteOptions(mode="overwrite")` → `Greenplum.WriteOptions(if_exists="replace_entire_collection")` + * `FileDownloader.Options(mode="error")` → `FileDownloader.Options(if_exists="error")` + * `FileDownloader.Options(mode="ignore")` → `FileDownloader.Options(if_exists="ignore")` + * `FileDownloader.Options(mode="overwrite")` → `FileDownloader.Options(if_exists="replace_file")` + * `FileDownloader.Options(mode="delete_all")` → `FileDownloader.Options(if_exists="replace_entire_directory")` + * `FileUploader.Options(mode="error")` → `FileUploader.Options(if_exists="error")` + * `FileUploader.Options(mode="ignore")` → `FileUploader.Options(if_exists="ignore")` + * `FileUploader.Options(mode="overwrite")` → `FileUploader.Options(if_exists="replace_file")` + * `FileUploader.Options(mode="delete_all")` → `FileUploader.Options(if_exists="replace_entire_directory")` + * `FileMover.Options(mode="error")` → `FileMover.Options(if_exists="error")` + * `FileMover.Options(mode="ignore")` → `FileMover.Options(if_exists="ignore")` + * `FileMover.Options(mode="overwrite")` → `FileMover.Options(if_exists="replace_file")` + * `FileMover.Options(mode="delete_all")` → `FileMover.Options(if_exists="replace_entire_directory")` + + Old names are left intact, but will be removed in v1.0.0 ([#108](https://github.com/MobileTeleSystems/onetl/pull/108)) +- Rename `onetl.log.disable_clients_logging()` to `onetl.log.setup_clients_logging()`. ([#120](https://github.com/MobileTeleSystems/onetl/pull/120)) + +## Features + +- Add new methods returning Maven packages for specific connection class: + * `Clickhouse.get_packages()` + * `MySQL.get_packages()` + * `Postgres.get_packages()` + * `Teradata.get_packages()` + * `MSSQL.get_packages(java_version="8")` + * `Oracle.get_packages(java_version="8")` + * `Greenplum.get_packages(scala_version="2.12")` + * `MongoDB.get_packages(scala_version="2.12")` + * `Kafka.get_packages(spark_version="3.4.1", scala_version="2.12")` + + Deprecate old syntax: + * `Clickhouse.package` + * `MySQL.package` + * `Postgres.package` + * `Teradata.package` + * `MSSQL.package` + * `Oracle.package` + * `Greenplum.package_spark_2_3` + * `Greenplum.package_spark_2_4` + * `Greenplum.package_spark_3_2` + * `MongoDB.package_spark_3_2` + * `MongoDB.package_spark_3_3` + * `MongoDB.package_spark_3_4` ([#87](https://github.com/MobileTeleSystems/onetl/pull/87)) +- Allow to set client modules log level in `onetl.log.setup_clients_logging()`. + + Allow to enable underlying client modules logging in `onetl.log.setup_logging()` by providing additional argument `enable_clients=True`. + This is useful for debug. ([#120](https://github.com/MobileTeleSystems/onetl/pull/120)) +- Added support for reading and writing data to Kafka topics. + + For these operations, new classes were added. + * `Kafka` ([#54](https://github.com/MobileTeleSystems/onetl/pull/54), [#60](https://github.com/MobileTeleSystems/onetl/pull/60), [#72](https://github.com/MobileTeleSystems/onetl/pull/72), [#84](https://github.com/MobileTeleSystems/onetl/pull/84), [#87](https://github.com/MobileTeleSystems/onetl/pull/87), [#89](https://github.com/MobileTeleSystems/onetl/pull/89), [#93](https://github.com/MobileTeleSystems/onetl/pull/93), [#96](https://github.com/MobileTeleSystems/onetl/pull/96), [#102](https://github.com/MobileTeleSystems/onetl/pull/102), [#104](https://github.com/MobileTeleSystems/onetl/pull/104)) + * `Kafka.PlaintextProtocol` ([#79](https://github.com/MobileTeleSystems/onetl/pull/79)) + * `Kafka.SSLProtocol` ([#118](https://github.com/MobileTeleSystems/onetl/pull/118)) + * `Kafka.BasicAuth` ([#63](https://github.com/MobileTeleSystems/onetl/pull/63), [#77](https://github.com/MobileTeleSystems/onetl/pull/77)) + * `Kafka.KerberosAuth` ([#63](https://github.com/MobileTeleSystems/onetl/pull/63), [#77](https://github.com/MobileTeleSystems/onetl/pull/77), [#110](https://github.com/MobileTeleSystems/onetl/pull/110)) + * `Kafka.ScramAuth` ([#115](https://github.com/MobileTeleSystems/onetl/pull/115)) + * `Kafka.Slots` ([#109](https://github.com/MobileTeleSystems/onetl/pull/109)) + * `Kafka.ReadOptions` ([#68](https://github.com/MobileTeleSystems/onetl/pull/68)) + * `Kafka.WriteOptions` ([#68](https://github.com/MobileTeleSystems/onetl/pull/68)) + + Currently, Kafka does not support incremental read strategies, this will be implemented in future releases. +- Added support for reading files as Spark DataFrame and saving DataFrame as Files. + + For these operations, new classes were added. + + FileDFConnections: + * `SparkHDFS` ([#98](https://github.com/MobileTeleSystems/onetl/pull/98)) + * `SparkS3` ([#94](https://github.com/MobileTeleSystems/onetl/pull/94), [#100](https://github.com/MobileTeleSystems/onetl/pull/100), [#124](https://github.com/MobileTeleSystems/onetl/pull/124)) + * `SparkLocalFS` ([#67](https://github.com/MobileTeleSystems/onetl/pull/67)) + + High-level classes: + * `FileDFReader` ([#73](https://github.com/MobileTeleSystems/onetl/pull/73)) + * `FileDFWriter` ([#81](https://github.com/MobileTeleSystems/onetl/pull/81)) + + File formats: + * `Avro` ([#69](https://github.com/MobileTeleSystems/onetl/pull/69)) + * `CSV` ([#92](https://github.com/MobileTeleSystems/onetl/pull/92)) + * `JSON` ([#83](https://github.com/MobileTeleSystems/onetl/pull/83)) + * `JSONLine` ([#83](https://github.com/MobileTeleSystems/onetl/pull/83)) + * `ORC` ([#86](https://github.com/MobileTeleSystems/onetl/pull/86)) + * `Parquet` ([#88](https://github.com/MobileTeleSystems/onetl/pull/88)) + +## Improvements + +- Remove redundant checks for driver availability in Greenplum and MongoDB connections. ([#67](https://github.com/MobileTeleSystems/onetl/pull/67)) +- Check of Java class availability moved from `.check()` method to connection constructor. ([#97](https://github.com/MobileTeleSystems/onetl/pull/97)) diff --git a/mddocs/changelog/0.9.1.md b/mddocs/changelog/0.9.1.md new file mode 100644 index 000000000..1779274b1 --- /dev/null +++ b/mddocs/changelog/0.9.1.md @@ -0,0 +1,7 @@ +# 0.9.1 (2023-08-17) + +## Bug Fixes + +- Fixed bug then number of threads created by `FileDownloader` / `FileUploader` / `FileMover` was + not `min(workers, len(files))`, but `max(workers, len(files))`. leading to create too much workers + on large files list. diff --git a/mddocs/changelog/0.9.2.md b/mddocs/changelog/0.9.2.md new file mode 100644 index 000000000..4ca4fcff9 --- /dev/null +++ b/mddocs/changelog/0.9.2.md @@ -0,0 +1,21 @@ +# 0.9.2 (2023-09-06) + +## Features + +- Add `if_exists="ignore"` and `error` to `Greenplum.WriteOptions` ([#142](https://github.com/MobileTeleSystems/onetl/pull/142)) + +## Improvements + +- Improve validation messages while writing dataframe to Kafka. ([#131](https://github.com/MobileTeleSystems/onetl/pull/131)) +- Improve documentation: + * Add notes about reading and writing to database connections documentation + * Add notes about executing statements in JDBC and Greenplum connections + +## Bug Fixes + +- Fixed validation of `headers` column is written to Kafka with default `Kafka.WriteOptions()` - default value was `False`, + but instead of raising an exception, column value was just ignored. ([#131](https://github.com/MobileTeleSystems/onetl/pull/131)) +- Fix reading data from Oracle with `partitioningMode="range"` without explicitly set `lowerBound` / `upperBound`. ([#133](https://github.com/MobileTeleSystems/onetl/pull/133)) +- Update Kafka documentation with SSLProtocol usage. ([#136](https://github.com/MobileTeleSystems/onetl/pull/136)) +- Raise exception if someone tries to read data from Kafka topic which does not exist. ([#138](https://github.com/MobileTeleSystems/onetl/pull/138)) +- Allow to pass Kafka topics with name like `some.topic.name` to DBReader. Same for MongoDB collections. ([#139](https://github.com/MobileTeleSystems/onetl/pull/139)) diff --git a/mddocs/changelog/0.9.3.md b/mddocs/changelog/0.9.3.md new file mode 100644 index 000000000..1a8c25d4d --- /dev/null +++ b/mddocs/changelog/0.9.3.md @@ -0,0 +1,5 @@ +# 0.9.3 (2023-09-06) + +## Bug Fixes + +- Fix documentation build diff --git a/mddocs/changelog/0.9.4.md b/mddocs/changelog/0.9.4.md new file mode 100644 index 000000000..cf9288760 --- /dev/null +++ b/mddocs/changelog/0.9.4.md @@ -0,0 +1,24 @@ +# 0.9.4 (2023-09-26) + +## Features + +- Add `Excel` file format support. ([#148](https://github.com/MobileTeleSystems/onetl/pull/148)) +- Add `Samba` file connection. + It is now possible to download and upload files to Samba shared folders using `FileDownloader`/`FileUploader`. ([#150](https://github.com/MobileTeleSystems/onetl/pull/150)) +- Add `if_exists="ignore"` and `error` to `Hive.WriteOptions` ([#143](https://github.com/MobileTeleSystems/onetl/pull/143)) +- Add `if_exists="ignore"` and `error` to `JDBC.WriteOptions` ([#144](https://github.com/MobileTeleSystems/onetl/pull/144)) +- Add `if_exists="ignore"` and `error` to `MongoDB.WriteOptions` ([#145](https://github.com/MobileTeleSystems/onetl/pull/145)) + +## Improvements + +- Add documentation about different ways of passing packages to Spark session. ([#151](https://github.com/MobileTeleSystems/onetl/pull/151)) +- Drastically improve `Greenplum` documentation: + : * Added information about network ports, grants, `pg_hba.conf` and so on. + * Added interaction schemas for reading, writing and executing statements in Greenplum. + * Added recommendations about reading data from views and `JOIN` results from Greenplum. ([#154](https://github.com/MobileTeleSystems/onetl/pull/154)) +- Make `.fetch` and `.execute` methods of DB connections thread-safe. Each thread works with its own connection. ([#156](https://github.com/MobileTeleSystems/onetl/pull/156)) +- Call `.close()` on `FileConnection` then it is removed by garbage collector. ([#156](https://github.com/MobileTeleSystems/onetl/pull/156)) + +## Bug Fixes + +- Fix issue when stopping Python interpreter calls `JDBCMixin.close()`, but it is finished with exceptions. ([#156](https://github.com/MobileTeleSystems/onetl/pull/156)) diff --git a/mddocs/changelog/0.9.5.md b/mddocs/changelog/0.9.5.md new file mode 100644 index 000000000..1d7358c0b --- /dev/null +++ b/mddocs/changelog/0.9.5.md @@ -0,0 +1,14 @@ +# 0.9.5 (2023-10-10) + +## Features + +- Add `XML` file format support. ([#163](https://github.com/MobileTeleSystems/onetl/pull/163)) +- Tested compatibility with Spark 3.5.0. `MongoDB` and `Excel` are not supported yet, but other packages do. ([#159](https://github.com/MobileTeleSystems/onetl/pull/159)) + +## Improvements + +- Add check to all DB and FileDF connections that Spark session is alive. ([#164](https://github.com/MobileTeleSystems/onetl/pull/164)) + +## Bug Fixes + +- Fix `Hive.check()` behavior when Hive Metastore is not available. ([#164](https://github.com/MobileTeleSystems/onetl/pull/164)) diff --git a/mddocs/changelog/DRAFT.md b/mddocs/changelog/DRAFT.md new file mode 100644 index 000000000..e69de29bb diff --git a/mddocs/changelog/NEXT_RELEASE.md b/mddocs/changelog/NEXT_RELEASE.md new file mode 100644 index 000000000..8cf66aa33 --- /dev/null +++ b/mddocs/changelog/NEXT_RELEASE.md @@ -0,0 +1 @@ + diff --git a/mddocs/changelog/index.md b/mddocs/changelog/index.md new file mode 100644 index 000000000..37d5d85ae --- /dev/null +++ b/mddocs/changelog/index.md @@ -0,0 +1,29 @@ +Changelog + +* [0.13.4 (2025-03-20)](0.13.4.md) +* [0.13.3 (2025-03-11)](0.13.3.md) +* [0.13.1 (2025-03-06)](0.13.1.md) +* [0.13.0 (2025-02-24)](0.13.0.md) +* [0.12.5 (2024-12-03)](0.12.5.md) +* [0.12.4 (2024-11-27)](0.12.4.md) +* [0.12.3 (2024-11-22)](0.12.3.md) +* [0.12.2 (2024-11-12)](0.12.2.md) +* [0.12.1 (2024-10-28)](0.12.1.md) +* [0.12.0 (2024-09-03)](0.12.0.md) +* [0.11.2 (2024-09-02)](0.11.2.md) +* [0.11.1 (2024-05-29)](0.11.1.md) +* [0.11.0 (2024-05-27)](0.11.0.md) +* [0.10.2 (2024-03-21)](0.10.2.md) +* [0.10.1 (2024-02-05)](0.10.1.md) +* [0.10.0 (2023-12-18)](0.10.0.md) +* [0.9.5 (2023-10-10)](0.9.5.md) +* [0.9.4 (2023-09-26)](0.9.4.md) +* [0.9.3 (2023-09-06)](0.9.3.md) +* [0.9.2 (2023-09-06)](0.9.2.md) +* [0.9.1 (2023-08-17)](0.9.1.md) +* [0.9.0 (2023-08-17)](0.9.0.md) +* [0.8.1 (2023-07-10)](0.8.1.md) +* [0.8.0 (2023-05-31)](0.8.0.md) +* [0.7.2 (2023-05-24)](0.7.2.md) +* [0.7.1 (2023-05-23)](0.7.1.md) +* [0.7.0 (2023-05-15)](0.7.0.md) diff --git a/mddocs/concepts.md b/mddocs/concepts.md new file mode 100644 index 000000000..cf74e9eaa --- /dev/null +++ b/mddocs/concepts.md @@ -0,0 +1,340 @@ +# Concepts + +Here you can find detailed documentation about each one of the onETL concepts and how to use them. + +## Connection + +### Basics + +onETL is used to pull and push data into other systems, and so it has a first-class `Connection` concept for storing credentials that are used to communicate with external systems. + +A `Connection` is essentially a set of parameters, such as username, password, hostname. + +To create a connection to a specific storage type, you must use a class that matches the storage type. The class name is the same as the storage type name (`Oracle`, `MSSQL`, `SFTP`, etc): + +```python +from onetl.connection import SFTP + +sftp = SFTP( + host="sftp.test.com", + user="onetl", + password="onetl", +) +``` + +All connection types are inherited from the parent class `BaseConnection`. + +### Class diagram + +### DBConnection + +Classes inherited from `DBConnection` could be used for accessing databases. + +A `DBConnection` could be instantiated as follows: + +```python +from onetl.connection import MSSQL + +mssql = MSSQL( + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, +) +``` + +where **spark** is the current SparkSession. +`onETL` uses `Spark` and specific Java connectors under the hood to work with databases. + +For a description of other parameters, see the documentation for the [available DBConnections](connection/db_connection/index.md#db-connections). + +### FileConnection + +Classes inherited from `FileConnection` could be used to access files stored on the different file systems/file servers + +A `FileConnection` could be instantiated as follows: + +```python +from onetl.connection import SFTP + +sftp = SFTP( + host="sftp.test.com", + user="onetl", + password="onetl", +) +``` + +For a description of other parameters, see the documentation for the [available FileConnections](connection/file_connection/index.md#file-connections). + +### FileDFConnection + +Classes inherited from `FileDFConnection` could be used for accessing files as Spark DataFrames. + +A `FileDFConnection` could be instantiated as follows: + +```python +from onetl.connection import SparkHDFS + +spark_hdfs = SparkHDFS( + host="namenode1.domain.com", + cluster="mycluster", + spark=spark, +) +``` + +where **spark** is the current SparkSession. +`onETL` uses `Spark` and specific Java connectors under the hood to work with DataFrames. + +For a description of other parameters, see the documentation for the [available FileDFConnections](connection/file_df_connection/index.md#file-df-connections). + +### Checking connection availability + +Once you have created a connection, you can check the database/filesystem availability using the method `check()`: + +```python +mssql.check() +sftp.check() +spark_hdfs.check() +``` + +It will raise an exception if database/filesystem cannot be accessed. + +This method returns connection itself, so you can create connection and immediately check its availability: + +```Python +mssql = MSSQL( + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, +).check() # <-- +``` + +## Extract/Load data + +### Basics + +As we said above, onETL is used to extract data from and load data into remote systems. + +onETL provides several classes for this: + +> * [DBReader](db/db_reader.md#db-reader) +> * [DBWriter](db/db_writer.md#db-writer) +> * [FileDFReader](file_df/file_df_reader/file_df_reader.md#file-df-reader) +> * [FileDFWriter](file_df/file_df_writer/file_df_writer.md#file-df-writer) +> * [FileDownloader](file/file_downloader/file_downloader.md#file-downloader) +> * [FileUploader](file/file_uploader/file_uploader.md#file-uploader) +> * [FileMover](file/file_mover/file_mover.md#file-mover) + +All of these classes have a method `run()` that starts extracting/loading the data: + +```python +from onetl.db import DBReader, DBWriter + +reader = DBReader( + connection=mssql, + source="dbo.demo_table", + columns=["column_1", "column_2"], +) + +# Read data as Spark DataFrame +df = reader.run() + +db_writer = DBWriter( + connection=hive, + target="dl_sb.demo_table", +) + +# Save Spark DataFrame to Hive table +writer.run(df) +``` + +### Extract data + +To extract data you can use classes: + +| | Use case | Connection | `run()` gets | `run()` returns | +|-------------------------------------------------------------------------|-------------------------------------------|------------------------------------------------------------------------------------|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------| +| [DBReader](db/db_reader.md#db-reader) | Reading data from a database | Any [DBConnection](connection/db_connection/index.md#db-connections) | - | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | +| [FileDFReader](file_df/file_df_reader/file_df_reader.md#file-df-reader) | Read data from a file or set of files | Any [FileDFConnection](connection/file_df_connection/index.md#file-df-connections) | No input, or List[File path on FileSystem] | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | +| [FileDownloader](db/db_reader.md#db-reader) | Download files from remote FS to local FS | Any [FileConnection](connection/file_connection/index.md#file-connections) | No input, or List[File path on remote FileSystem] | [DownloadResult](file/file_downloader/result.md#file-downloader-result) | + +### Load data + +To load data you can use classes: + +| | Use case | Connection | `run()` gets | `run()` returns | +|-------------------------------------------------------------------|----------------------------------------------|------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------| +| [DBWriter](db/db_writer.md#db-writer) | Writing data from a DataFrame to a database | Any [DBConnection](connection/db_connection/index.md#db-connections) | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | None | +| [FileDFWriter](db/db_writer.md#db-writer) | Writing data from a DataFrame to a folder | Any [FileDFConnection](connection/file_df_connection/index.md#file-df-connections) | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | None | +| [FileUploader](file/file_uploader/file_uploader.md#file-uploader) | Uploading files from a local FS to remote FS | Any [FileConnection](connection/file_connection/index.md#file-connections) | List[File path on local FileSystem] | [UploadResult](file/file_uploader/result.md#file-uploader-result) | + +### Manipulate data + +To manipulate data you can use classes: + +| | Use case | Connection | `run()` gets | `run()` returns | +|-------------------------------------------------------|---------------------------------------------|----------------------------------------------------------------------------|--------------------------------------|-----------------------------------------------------------| +| [FileMover](file/file_mover/file_mover.md#file-mover) | Move files between directories in remote FS | Any [FileConnection](connection/file_connection/index.md#file-connections) | List[File path on remote FileSystem] | [MoveResult](file/file_mover/result.md#file-mover-result) | + +### Options + +Extract and load classes have a `options` parameter, which has a special meaning: + +> * all other parameters - *WHAT* we extract / *WHERE* we load to +> * `options` parameter - *HOW* we extract/load data +```python +db_reader = DBReader( + # WHAT do we read: + connection=mssql, + source="dbo.demo_table", # some table from MSSQL + columns=["column_1", "column_2"], # but only specific set of columns + where="column_2 > 1000", # only rows matching the clause + # HOW do we read: + options=MSSQL.ReadOptions( + numPartitions=10, # read in 10 parallel jobs + partitionColumn="id", # balance data read by assigning each job a part of data using `hash(id) mod N` expression + partitioningMode="hash", + fetchsize=1000, # each job will fetch block of 1000 rows each on every read attempt + ), +) + +db_writer = DBWriter( + # WHERE do we write to - to some table in Hive + connection=hive, + target="dl_sb.demo_table", + # HOW do we write - overwrite all the data in the existing table + options=Hive.WriteOptions(if_exists="replace_entire_table"), +) + +file_downloader = FileDownloader( + # WHAT do we download - files from some dir in SFTP + connection=sftp, + source_path="/source", + filters=[Glob("*.csv")], # only CSV files + limits=[MaxFilesCount(1000)], # 1000 files max + # WHERE do we download to - a specific dir on local FS + local_path="/some", + # HOW do we download: + options=FileDownloader.Options( + delete_source=True, # after downloading each file remove it from source_path + if_exists="replace_file", # replace existing files in the local_path + ), +) + +file_uploader = FileUploader( + # WHAT do we upload - files from some local dir + local_path="/source", + # WHERE do we upload to- specific remote dir in HDFS + connection=hdfs, + target_path="/some", + # HOW do we upload: + options=FileUploader.Options( + delete_local=True, # after uploading each file remove it from local_path + if_exists="replace_file", # replace existing files in the target_path + ), +) + +file_mover = FileMover( + # WHAT do we move - files in some remote dir in HDFS + source_path="/source", + connection=hdfs, + # WHERE do we move files to + target_path="/some", # a specific remote dir within the same HDFS connection + # HOW do we load - replace existing files in the target_path + options=FileMover.Options(if_exists="replace_file"), +) + +file_df_reader = FileDFReader( + # WHAT do we read - *.csv files from some dir in S3 + connection=s3, + source_path="/source", + file_format=CSV(), + # HOW do we read - load files from /source/*.csv, not from /source/nested/*.csv + options=FileDFReader.Options(recursive=False), +) + +file_df_writer = FileDFWriter( + # WHERE do we write to - as .csv files in some dir in S3 + connection=s3, + target_path="/target", + file_format=CSV(), + # HOW do we write - replace all existing files in /target, if exists + options=FileDFWriter.Options(if_exists="replace_entire_directory"), +) +``` + +More information about `options` could be found on [DB connection](connection/db_connection/index.md#db-connections). and +[File Downloader](file/file_downloader/file_downloader.md#file-downloader) / [File Uploader](file/file_uploader/file_uploader.md#file-uploader) / [File Mover](file/file_mover/file_mover.md#file-mover) / [FileDF Reader](file_df/file_df_reader/file_df_reader.md#file-df-reader) / [FileDF Writer](file_df/file_df_writer/file_df_writer.md#file-df-writer) documentation + +### Read Strategies + +onETL have several builtin strategies for reading data: + +1. [Snapshot strategy](strategy/snapshot_strategy.html) (default strategy) +2. [Incremental strategy](strategy/incremental_strategy.html) +3. [Snapshot batch strategy](strategy/snapshot_batch_strategy.html) +4. [Incremental batch strategy](strategy/incremental_batch_strategy.html) + +For example, an incremental strategy allows you to get only new data from the table: + +```python +from onetl.strategy import IncrementalStrategy + +reader = DBReader( + connection=mssql, + source="dbo.demo_table", + hwm_column="id", # detect new data based on value of "id" column +) + +# first run +with IncrementalStrategy(): + df = reader.run() + +sleep(3600) + +# second run +with IncrementalStrategy(): + # only rows, that appeared in the source since previous run + df = reader.run() +``` + +or get only files which were not downloaded before: + +```python +from onetl.strategy import IncrementalStrategy + +file_downloader = FileDownloader( + connection=sftp, + source_path="/remote", + local_path="/local", + hwm_type="file_list", # save all downloaded files to a list, and exclude files already present in this list +) + +# first run +with IncrementalStrategy(): + files = file_downloader.run() + +sleep(3600) + +# second run +with IncrementalStrategy(): + # only files, that appeared in the source since previous run + files = file_downloader.run() +``` + +Most of strategies are based on [HWM](hwm_store/index.md#hwm), Please check each strategy documentation for more details + +### Why just not use Connection class for extract/load? + +Connections are very simple, they have only a set of some basic operations, +like `mkdir`, `remove_file`, `get_table_schema`, and so on. + +High-level operations, like +: * [Read Strategies](strategy/index.md#strategy) support + * Handling metadata push/pull + * Handling different options, like `if_exists="replace_file"` in case of file download/upload + +is moved to a separate class which calls the connection object methods to perform some complex logic. diff --git a/mddocs/conf.py b/mddocs/conf.py new file mode 100644 index 000000000..c69e54521 --- /dev/null +++ b/mddocs/conf.py @@ -0,0 +1,150 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. + + +import os +import subprocess +import sys +from pathlib import Path + +from packaging.version import Version + +PROJECT_ROOT_DIR = Path(__file__).parent.parent.resolve() + +sys.path.insert(0, os.fspath(PROJECT_ROOT_DIR)) + +# -- Project information ----------------------------------------------------- + +project = "onETL" +copyright = "2021-2024 MTS PJSC" +author = "DataOps.ETL" + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. + +ver = Version(subprocess.check_output("python ../setup.py --version", shell=True, text=True).strip()) +version = ver.base_version +# The full version, including alpha/beta/rc tags. +release = ver.public + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "numpydoc", + "sphinx_design", + "sphinx_substitution_extensions", + "sphinx_tabs.tabs", + "sphinx_toolbox.more_autodoc.autoprotocol", + "sphinx_toolbox.github", + "sphinx_copybutton", + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinxcontrib.towncrier", # provides `towncrier-draft-entries` directive + "sphinxcontrib.plantuml", + "sphinx.ext.extlinks", + "sphinx_favicon", + "sphinxcontrib.autodoc_pydantic", + "sphinx_last_updated_by_git", + "sphinx_markdown_builder", + "myst_parser", +] +numpydoc_show_class_members = False +autodoc_pydantic_model_show_config = False +autodoc_pydantic_model_show_config_summary = False +autodoc_pydantic_model_show_config_member = False +autodoc_pydantic_model_show_json = False +autodoc_pydantic_model_show_validator_summary = False +autodoc_pydantic_model_show_validator_members = False +autodoc_pydantic_field_list_validators = False +sphinx_tabs_disable_tab_closing = True + +# prevent >>>, ... and doctest outputs from copying +copybutton_prompt_text = r">>> |\.\.\. |\$ |In \[\d*\]: | {2,5}\.\.\.: | {5,8}: " +copybutton_prompt_is_regexp = True +copybutton_copy_empty_lines = False +copybutton_only_copy_prompt_lines = True + +towncrier_draft_autoversion_mode = "draft" +towncrier_draft_include_empty = False +towncrier_draft_working_directory = PROJECT_ROOT_DIR + +github_username = "MobileTeleSystems" +github_repository = "onetl" + +rst_prolog = f""" +.. |support_hooks| image:: https://img.shields.io/badge/%20-support%20hooks-blue + :target: https://onetl.readthedocs.io/en/{ver}/hooks/index.html +""" + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. + +html_theme = "furo" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] +html_extra_path = ["robots.txt"] +html_logo = "./_static/logo.svg" +favicons = [ + {"rel": "icon", "href": "icon.svg", "type": "image/svg+xml"}, +] + +# The master toctree document. +master_doc = "index" + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = "en" + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" + +# Create an alias for etl-entities lib in onetl documentation +extlinks = { + "etl-entities": ("https://etl-entities.readthedocs.io/en/stable/%s", None), +} + + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = "onetl-doc" diff --git a/mddocs/connection/db_connection/clickhouse/connection.md b/mddocs/connection/db_connection/clickhouse/connection.md new file mode 100644 index 000000000..09365e6a8 --- /dev/null +++ b/mddocs/connection/db_connection/clickhouse/connection.md @@ -0,0 +1,128 @@ + + +# Clickhouse connection + +### *class* onetl.connection.db_connection.clickhouse.connection.Clickhouse(\*, spark: SparkSession, user: str, password: SecretStr, host: Host, port: int = 8123, database: str | None = None, extra: ClickhouseExtra = ClickhouseExtra()) + +Clickhouse JDBC connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on Maven package [com.clickhouse:clickhouse-jdbc:0.7.2](https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc/0.7.2) +([official Clickhouse JDBC driver](https://github.com/ClickHouse/clickhouse-jdbc)). + +#### SEE ALSO +Before using this connector please take into account [Prerequisites](prerequisites.md#clickhouse-prerequisites) + +#### Versionadded +Added in version 0.1.0. + +* **Parameters:** + **host** + : Host of Clickhouse database. For example: `test.clickhouse.domain.com` or `193.168.1.11` + + **port** + : Port of Clickhouse database + + **user** + : User, which have proper access to the database. For example: `some_user` + + **password** + : Password for database connection + + **database** + : Database in RDBMS, NOT schema. +
+ See [this page](https://www.educba.com/postgresql-database-vs-schema/) for more details + + **spark** + : Spark session. + + **extra** + : Specifies one or more extra parameters by which clients can connect to the instance. +
+ For example: `{"continueBatchOnError": "false"}`. +
+ See: + : * [Clickhouse JDBC driver properties documentation](https://clickhouse.com/docs/en/integrations/java#configuration) + * [Clickhouse core settings documentation](https://clickhouse.com/docs/en/operations/settings/settings) + * [Clickhouse query complexity documentation](https://clickhouse.com/docs/en/operations/settings/query-complexity) + * [Clickhouse query level settings](https://clickhouse.com/docs/en/operations/settings/query-level) + +### Examples + +Create and check Clickhouse connection: + +```python +from onetl.connection import Clickhouse +from pyspark.sql import SparkSession + +# Create Spark session with Clickhouse driver loaded +maven_packages = Clickhouse.get_packages() +spark = ( + SparkSession.builder.appName("spark-app-name") + .config("spark.jars.packages", ",".join(maven_packages)) + .getOrCreate() +) + +# Create connection +clickhouse = Clickhouse( + host="database.host.or.ip", + user="user", + password="*****", + extra={"continueBatchOnError": "false"}, + spark=spark, +).check() +``` + + + +#### check() + +Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If not, an exception will be raised. + +* **Returns:** + Connection itself +* **Raises:** + RuntimeError + : If the connection is not available + +### Examples + +```python +connection.check() +``` + + + +#### *classmethod* get_packages(package_version: str | None = None, apache_http_client_version: str | None = None) → list[str] + +Get package names to be downloaded by Spark. Allows specifying custom JDBC and Apache HTTP Client versions. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **package_version** + : ClickHouse JDBC version client packages. Defaults to `0.7.2`. +
+ #### Versionadded + Added in version 0.11.0. + + **apache_http_client_version** + : Apache HTTP Client version package. Defaults to `5.4.2`. +
+ Used only if `package_version` is in range `0.5.0-0.7.0`. +
+ #### Versionadded + Added in version 0.11.0. + +### Examples + +```python +from onetl.connection import Clickhouse + +Clickhouse.get_packages(package_version="0.6.0", apache_http_client_version="5.4.2") +``` + + diff --git a/mddocs/connection/db_connection/clickhouse/execute.md b/mddocs/connection/db_connection/clickhouse/execute.md new file mode 100644 index 000000000..c65c26a67 --- /dev/null +++ b/mddocs/connection/db_connection/clickhouse/execute.md @@ -0,0 +1,191 @@ + + +# Executing statements in Clickhouse + +#### WARNING +Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + +Do **NOT** use them to read large amounts of data. Use [DBReader](read.md#clickhouse-read) or [Clickhouse.sql](sql.md#clickhouse-sql) instead. + +## How to + +There are 2 ways to execute some statement in Clickhouse + +### Use `Clickhouse.fetch` + +Use this method to perform some `SELECT` query which returns **small number or rows**, like reading +Clickhouse config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts [`Clickhouse.FetchOptions`](#onetl.connection.db_connection.clickhouse.options.ClickhouseFetchOptions). + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### WARNING +Please take into account [Clickhouse <-> Spark type mapping](types.md#clickhouse-types). + +#### Syntax support + +This method supports **any** query syntax supported by Clickhouse, like: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ✅︎ `SELECT func(arg1, arg2)` - call function +* ✅︎ `SHOW ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Clickhouse + +clickhouse = Clickhouse(...) + +df = clickhouse.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=Clickhouse.FetchOptions(queryTimeout=10), +) +clickhouse.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `Clickhouse.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts [`Clickhouse.ExecuteOptions`](#onetl.connection.db_connection.clickhouse.options.ClickhouseExecuteOptions). + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Clickhouse, like: + +* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +* ✅︎ `ALTER ...` +* ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +* ✅︎ other statements not mentioned here +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Clickhouse + +clickhouse = Clickhouse(...) + +clickhouse.execute("DROP TABLE schema.table") +clickhouse.execute( + """ + CREATE TABLE schema.table ( + id UInt8, + key String, + value Float32 + ) + ENGINE = MergeTree() + ORDER BY id + """, + options=Clickhouse.ExecuteOptions(queryTimeout=10), +) +``` + +## Notes + +These methods **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + +So it should **NOT** be used to read large amounts of data. Use [DBReader](read.md#clickhouse-read) or [Clickhouse.sql](sql.md#clickhouse-sql) instead. + +## Options + +### *pydantic model* onetl.connection.db_connection.clickhouse.options.ClickhouseFetchOptions + +Options related to fetching data from databases via JDBC. + +#### Versionadded +Added in version 0.11.0: Replace `Clickhouse.JDBCOptions` → `Clickhouse.FetchOptions` + +### Examples + +#### NOTE +You can pass any value supported by underlying JDBC driver class, +even if it is not mentioned in this documentation. + +```python +from onetl.connection import Clickhouse + +options = Clickhouse.FetchOptions( + queryTimeout=60_000, + fetchsize=100_000, + customSparkOption="value", +) +``` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int | None* *= None* + +How many rows to fetch per round trip. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value depends on driver. For example, Oracle has +default `fetchsize=10`. + + + +### *pydantic model* onetl.connection.db_connection.clickhouse.options.ClickhouseExecuteOptions + +Options related to executing statements in databases via JDBC. + +#### Versionadded +Added in version 0.11.0: Replace `Clickhouse.JDBCOptions` → `Clickhouse.ExecuteOptions` + +### Examples + +#### NOTE +You can pass any value supported by underlying JDBC driver class, +even if it is not mentioned in this documentation. + +```python +from onetl.connection import Clickhouse + +options = Clickhouse.ExecuteOptions( + queryTimeout=60_000, + customSparkOption="value", +) +``` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int | None* *= None* + +How many rows to fetch per round trip. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value depends on driver. For example, Oracle has +default `fetchsize=10`. + + diff --git a/mddocs/connection/db_connection/clickhouse/index.md b/mddocs/connection/db_connection/clickhouse/index.md new file mode 100644 index 000000000..3b226910d --- /dev/null +++ b/mddocs/connection/db_connection/clickhouse/index.md @@ -0,0 +1,19 @@ + + +# Clickhouse + +# Connection + +* [Prerequisites](prerequisites.md) +* [Clickhouse connection](connection.md) + +# Operations + +* [Reading from Clickhouse using `DBReader`](read.md) +* [Reading from Clickhouse using `Clickhouse.sql`](sql.md) +* [Writing to Clickhouse using `DBWriter`](write.md) +* [Executing statements in Clickhouse](execute.md) + +# Troubleshooting + +* [Clickhouse <-> Spark type mapping](types.md) diff --git a/mddocs/connection/db_connection/clickhouse/prerequisites.md b/mddocs/connection/db_connection/clickhouse/prerequisites.md new file mode 100644 index 000000000..86551b41a --- /dev/null +++ b/mddocs/connection/db_connection/clickhouse/prerequisites.md @@ -0,0 +1,73 @@ + + +# Prerequisites + +## Version Compatibility + +* Clickhouse server versions: + : * Officially declared: 22.8 or higher + * Actually tested: 21.1, 25.1 +* Spark versions: 2.3.x - 3.5.x +* Java versions: 8 - 20 + +See [official documentation](https://clickhouse.com/docs/en/integrations/java#jdbc-driver). + +## Installing PySpark + +To use Clickhouse connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Connecting to Clickhouse + +### Connection port + +Connector can only use **HTTP** (usually `8123` port) or **HTTPS** (usually `8443` port) protocol. + +TCP and GRPC protocols are NOT supported. + +### Connecting to cluster + +It is possible to connect to Clickhouse cluster, and use it’s load balancing capabilities to read or write data in parallel. +Each Spark executor can connect to random Clickhouse nodes, instead of sending all the data to a node specified in connection params. + +This requires all Clickhouse servers to run on different hosts, and **listen the same HTTP port**. +Set `auto_discovery=True` to enable this feature (disabled by default): + +```python +Clickhouse( + host="node1.of.cluster", + port=8123, + extra={ + "auto_discovery": True, + "load_balancing_policy": "roundRobin", + }, +) +``` + +See [official documentation](https://clickhouse.com/docs/en/integrations/java#configuring-node-discovery-load-balancing-and-failover). + +### Required grants + +Ask your Clickhouse cluster administrator to set following grants for a user, +used for creating a connection: + +Read + Write + +```sql +-- allow creating tables in the target schema +GRANT CREATE TABLE ON myschema.* TO username; + +-- allow read & write access to specific table +GRANT SELECT, INSERT ON myschema.mytable TO username; +``` + +Read only + +```sql +-- allow read access to specific table +GRANT SELECT ON myschema.mytable TO username; +``` + +More details can be found in [official documentation](https://clickhouse.com/docs/en/sql-reference/statements/grant). diff --git a/mddocs/connection/db_connection/clickhouse/read.md b/mddocs/connection/db_connection/clickhouse/read.md new file mode 100644 index 000000000..611930634 --- /dev/null +++ b/mddocs/connection/db_connection/clickhouse/read.md @@ -0,0 +1,339 @@ + + +# Reading from Clickhouse using `DBReader` + +[`DBReader`](../../../db/db_reader.md#onetl.db.db_reader.db_reader.DBReader) supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, +but does not support custom queries, like `JOIN`. + +#### WARNING +Please take into account [Clickhouse <-> Spark type mapping](types.md#clickhouse-types) + +## Supported DBReader features + +* ✅︎ `columns` +* ✅︎ `where` +* ✅︎ `hwm`, supported strategies: +* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) +* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) +* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) +* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) +* ❌ `hint` (is not supported by Clickhouse) +* ❌ `df_schema` +* ✅︎ `options` (see [`Clickhouse.ReadOptions`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions)) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Clickhouse +from onetl.db import DBReader + +clickhouse = Clickhouse(...) + +reader = DBReader( + connection=clickhouse, + source="schema.table", + columns=["id", "key", "CAST(value AS String) value", "updated_dt"], + where="key = 'something'", + options=Clickhouse.ReadOptions(partitionColumn="id", numPartitions=10), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Clickhouse +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +clickhouse = Clickhouse(...) + +reader = DBReader( + connection=clickhouse, + source="schema.table", + columns=["id", "key", "CAST(value AS String) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="clickhouse_hwm", expression="updated_dt"), + options=Clickhouse.ReadOptions(partitionColumn="id", numPartitions=10), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Clickhouse to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Clickhouse to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +### *pydantic model* onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions + +Spark JDBC reading options. + +#### Versionadded +Added in version 0.5.0: Replace `Clickhouse.Options` → `Clickhouse.ReadOptions` + +### Examples + +#### NOTE +You can pass any value +[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), +even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! + +The set of supported options depends on Spark version. + +```python +from onetl.connection import Clickhouse + +options = Clickhouse.ReadOptions( + partitioning_mode="range", + partitionColumn="reg_id", + numPartitions=10, + customSparkOption="value", +) +``` + + + +#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* + +Column used to parallelize reading from a table. + +#### WARNING +It is highly recommended to use primary key, or column with an index +to avoid performance issues. + +#### NOTE +Column type depends on [`partitioning_mode`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions.partitioning_mode). + +* `partitioning_mode="range"` requires column to be an integer, date or timestamp (can be NULL, but not recommended). +* `partitioning_mode="hash"` accepts any column type (NOT NULL). +* `partitioning_mode="mod"` requires column to be an integer (NOT NULL). + +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions.partitioning_mode) for more details + + + +#### *field* num_partitions *: PositiveInt* *= 1* *(alias 'numPartitions')* + +Number of jobs created by Spark to read the table content in parallel. +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions.partitioning_mode) for more details + + + +#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* + +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions.partitioning_mode) for more details + + + +#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* + +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions.partitioning_mode) for more details + + + +#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* + +After each database session is opened to the remote DB and before starting to read data, +this option executes a custom SQL statement (or a PL/SQL block). + +Use this to implement session initialization code. + +Example: + +```python +sessionInitStatement = """ + BEGIN + execute immediate + 'alter session set "_serial_direct_read"=true'; + END; +""" +``` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int* *= 100000* + +Fetch N rows from an opened cursor per one read round. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value is different from Spark. + +Spark uses driver’s own value, and it may be different in different drivers, +and even versions of the same driver. For example, Oracle has +default `fetchsize=10`, which is absolutely not usable. + +Thus we’ve overridden default value with `100_000`, which should increase reading performance. + +#### Versionchanged +Changed in version 0.2.0: Set explicit default value to `100_000` + + + +#### *field* partitioning_mode *: JDBCPartitioningMode* *= JDBCPartitioningMode.RANGE* + +Defines how Spark will parallelize reading from table. + +Possible values: + +* `range` (default) + : Allocate each executor a range of values from column passed into [`partition_column`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions.partition_column). +
+ ### Spark generates for each executor an SQL query +
+ Executor 1: + ```sql + SELECT ... FROM table + WHERE (partition_column >= lowerBound + OR partition_column IS NULL) + AND partition_column < (lower_bound + stride) + ``` +
+ Executor 2: + ```sql + SELECT ... FROM table + WHERE partition_column >= (lower_bound + stride) + AND partition_column < (lower_bound + 2 * stride) + ``` +
+ … +
+ Executor N: + ```sql + SELECT ... FROM table + WHERE partition_column >= (lower_bound + (N-1) * stride) + AND partition_column <= upper_bound + ``` +
+ Where `stride=(upper_bound - lower_bound) / num_partitions`. +
+ #### NOTE + Can be used only with columns of integer, date or timestamp types. +
+ #### NOTE + [`lower_bound`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions.lower_bound), [`upper_bound`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions.upper_bound) and [`num_partitions`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions.num_partitions) are used just to + calculate the partition stride, **NOT** for filtering the rows in table. + So all rows in the table will be returned (unlike *Incremental* [Read Strategies](../../../strategy/index.md#strategy)). +
+ #### NOTE + All queries are executed in parallel. To execute them sequentially, use *Batch* [Read Strategies](../../../strategy/index.md#strategy). +* `hash` + : Allocate each executor a set of values based on hash of the [`partition_column`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions.partition_column) column. +
+ ### Spark generates for each executor an SQL query +
+ Executor 1: + ```sql + SELECT ... FROM table + WHERE (some_hash(partition_column) mod num_partitions) = 0 -- lower_bound + ``` +
+ Executor 2: + ```sql + SELECT ... FROM table + WHERE (some_hash(partition_column) mod num_partitions) = 1 -- lower_bound + 1 + ``` +
+ … +
+ Executor N: + ```sql + SELECT ... FROM table + WHERE (some_hash(partition_column) mod num_partitions) = num_partitions-1 -- upper_bound + ``` +
+ #### NOTE + The hash function implementation depends on RDBMS. It can be `MD5` or any other fast hash function, + or expression based on this function call. Usually such functions accepts any column type as an input. +* `mod` + : Allocate each executor a set of values based on modulus of the [`partition_column`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions.partition_column) column. +
+ ### Spark generates for each executor an SQL query +
+ Executor 1: + ```sql + SELECT ... FROM table + WHERE (partition_column mod num_partitions) = 0 -- lower_bound + ``` +
+ Executor 2: + ```sql + SELECT ... FROM table + WHERE (partition_column mod num_partitions) = 1 -- lower_bound + 1 + ``` +
+ Executor N: + ```sql + SELECT ... FROM table + WHERE (partition_column mod num_partitions) = num_partitions-1 -- upper_bound + ``` +
+ #### NOTE + Can be used only with columns of integer type. + +#### Versionadded +Added in version 0.5.0. + +### Examples + +Read data in 10 parallel jobs by range of values in `id_column` column: + +```python +ReadOptions( + partitioning_mode="range", # default mode, can be omitted + partitionColumn="id_column", + numPartitions=10, + # Options below can be discarded because they are + # calculated automatically as MIN and MAX values of `partitionColumn` + lowerBound=0, + upperBound=100_000, +) +``` + +Read data in 10 parallel jobs by hash of values in `some_column` column: + +```python +ReadOptions( + partitioning_mode="hash", + partitionColumn="some_column", + numPartitions=10, + # lowerBound and upperBound are automatically set to `0` and `9` +) +``` + +Read data in 10 parallel jobs by modulus of values in `id_column` column: + +```python +ReadOptions( + partitioning_mode="mod", + partitionColumn="id_column", + numPartitions=10, + # lowerBound and upperBound are automatically set to `0` and `9` +) +``` + + diff --git a/mddocs/connection/db_connection/clickhouse/sql.md b/mddocs/connection/db_connection/clickhouse/sql.md new file mode 100644 index 000000000..e2ce4add5 --- /dev/null +++ b/mddocs/connection/db_connection/clickhouse/sql.md @@ -0,0 +1,191 @@ + + +# Reading from Clickhouse using `Clickhouse.sql` + +`Clickhouse.sql` allows passing custom SQL query, but does not support incremental strategies. + +#### WARNING +Please take into account [Clickhouse <-> Spark type mapping](types.md#clickhouse-types) + +#### WARNING +Statement is executed in **read-write** connection, so if you’re calling some functions/procedures with DDL/DML statements inside, +they can change data in your database. + +## Syntax support + +Only queries with the following syntax are supported: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import Clickhouse + +clickhouse = Clickhouse(...) +df = clickhouse.sql( + """ + SELECT + id, + key, + CAST(value AS String) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """, + options=Clickhouse.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from Clickhouse to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from Clickhouse to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +### *pydantic model* onetl.connection.db_connection.clickhouse.options.ClickhouseSQLOptions + +Options specifically for SQL queries + +These options allow you to specify configurations for executing SQL queries +without relying on Spark’s partitioning mechanisms. + +#### Versionadded +Added in version 0.11.0: Split up `Clickhouse.ReadOptions` to `Clickhouse.SQLOptions` + +### Examples + +#### NOTE +You can pass any JDBC configuration +[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), +tailored to optimize SQL query execution. **Option names should be in** `camelCase`! + +```python +from onetl.connection import Clickhouse + +options = Clickhouse.SQLOptions( + partitionColumn="reg_id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + customSparkOption="value", +) +``` + + + +#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* + +Column used to partition data across multiple executors for parallel query processing. + +#### WARNING +It is highly recommended to use primary key, or column with an index +to avoid performance issues. + +### Example of using `partitionColumn="id"` with `partitioning_mode="range"` + +```sql +-- If partition_column is 'id', with numPartitions=4, lowerBound=1, and upperBound=100: +-- Executor 1 processes IDs from 1 to 25 +SELECT ... FROM table WHERE id >= 1 AND id < 26 +-- Executor 2 processes IDs from 26 to 50 +SELECT ... FROM table WHERE id >= 26 AND id < 51 +-- Executor 3 processes IDs from 51 to 75 +SELECT ... FROM table WHERE id >= 51 AND id < 76 +-- Executor 4 processes IDs from 76 to 100 +SELECT ... FROM table WHERE id >= 76 AND id <= 100 + +-- General case for Executor N +SELECT ... FROM table +WHERE partition_column >= (lowerBound + (N-1) * stride) +AND partition_column <= upperBound +-- Where ``stride`` is calculated as ``(upperBound - lowerBound) / numPartitions``. +``` + + + +#### *field* num_partitions *: int | None* *= None* *(alias 'numPartitions')* + +Number of jobs created by Spark to read the table content in parallel. + + + +#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* + +Defines the starting boundary for partitioning the query’s data. Mandatory if [`partition_column`](#onetl.connection.db_connection.clickhouse.options.ClickhouseSQLOptions.partition_column) is set + + + +#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* + +Sets the ending boundary for data partitioning. Mandatory if [`partition_column`](#onetl.connection.db_connection.clickhouse.options.ClickhouseSQLOptions.partition_column) is set + + + +#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* + +After each database session is opened to the remote DB and before starting to read data, +this option executes a custom SQL statement (or a PL/SQL block). + +Use this to implement session initialization code. + +Example: + +```python +sessionInitStatement = """ + BEGIN + execute immediate + 'alter session set "_serial_direct_read"=true'; + END; +""" +``` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int* *= 100000* + +Fetch N rows from an opened cursor per one read round. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value is different from Spark. + +Spark uses driver’s own value, and it may be different in different drivers, +and even versions of the same driver. For example, Oracle has +default `fetchsize=10`, which is absolutely not usable. + +Thus we’ve overridden default value with `100_000`, which should increase reading performance. + +#### Versionchanged +Changed in version 0.2.0: Set explicit default value to `100_000` + + diff --git a/mddocs/connection/db_connection/clickhouse/types.md b/mddocs/connection/db_connection/clickhouse/types.md new file mode 100644 index 000000000..447a8ae26 --- /dev/null +++ b/mddocs/connection/db_connection/clickhouse/types.md @@ -0,0 +1,347 @@ + + +# Clickhouse <-> Spark type mapping + +#### NOTE +The results below are valid for Spark 3.5.5, and may differ on other Spark versions. + +#### NOTE +It is recommended to use [spark-dialect-extension](https://github.com/MobileTeleSystems/spark-dialect-extension) package, +which implements writing Arrays from Spark to Clickhouse, fixes dropping fractions of seconds in `TimestampType`, +and fixes other type conversion issues. + +## Type detection & casting + +Spark’s DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from Clickhouse + +This is how Clickhouse connector performs this: + +* For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and Clickhouse type. +* Find corresponding `Clickhouse type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* Create DataFrame from query with specific column names and Spark types. + +### Writing to some existing Clickhouse table + +This is how Clickhouse connector performs this: + +* Get names of columns in DataFrame. [1](#id3) +* Perform `SELECT * FROM table LIMIT 0` query. +* Take only columns present in DataFrame (by name, case insensitive). For each found column get Clickhouse type. +* **Find corresponding** `Clickhouse type (read)` → `Spark type` **combination** (see below) for each DataFrame column. If no combination is found, raise exception. [2](#id4) +* Find corresponding `Spark type` → `Clickhousetype (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* If `Clickhousetype (write)` match `Clickhouse type (read)`, no additional casts will be performed, DataFrame column will be written to Clickhouse as is. +* If `Clickhousetype (write)` does not match `Clickhouse type (read)`, DataFrame column will be casted to target column type **on Clickhouse side**. For example, you can write column with text data to `Int32` column, if column contains valid integer values within supported value range and precision. + +* **[1]** This allows to write data to tables with `DEFAULT` columns - if DataFrame has no such column, it will be populated by Clickhouse. +* **[2]** Yes, this is weird. + +### Create new table using Spark + +#### WARNING +ABSOLUTELY NOT RECOMMENDED! + +This is how Clickhouse connector performs this: + +* Find corresponding `Spark type` → `Clickhouse type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* Generate DDL for creating table in Clickhouse, like `CREATE TABLE (col1 ...)`, and run it. +* Write DataFrame to created table as is. + +But Spark does not have specific dialect for Clickhouse, so Generic JDBC dialect is used. +Generic dialect is using SQL ANSI type names while creating tables in target database, not database-specific types. + +If some cases this may lead to using wrong column type. For example, Spark creates column of type `TIMESTAMP` +which corresponds to Clickhouse type `DateTime32` (precision up to seconds) +instead of more precise `DateTime64` (precision up to nanoseconds). +This may lead to incidental precision loss, or sometimes data cannot be written to created table at all. + +So instead of relying on Spark to create tables: + +### See example + +```python +writer = DBWriter( + connection=clickhouse, + target="default.target_tbl", + options=Clickhouse.WriteOptions( + if_exists="append", + # ENGINE is required by Clickhouse + createTableOptions="ENGINE = MergeTree() ORDER BY id", + ), +) +writer.run(df) +``` + +Always prefer creating tables with specific types **BEFORE WRITING DATA**: + +### See example + +```python +clickhouse.execute( + """ + CREATE TABLE default.target_tbl ( + id UInt8, + value DateTime64(6) -- specific type and precision + ) + ENGINE = MergeTree() + ORDER BY id + """, +) + +writer = DBWriter( + connection=clickhouse, + target="default.target_tbl", + options=Clickhouse.WriteOptions(if_exists="append"), +) +writer.run(df) +``` + +### References + +Here you can find source code with type conversions: + +* [Clickhouse -> JDBC](https://github.com/ClickHouse/clickhouse-java/blob/0.3.2/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/JdbcTypeMapping.java#L39-L176) +* [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/jdbc/JdbcUtils.scala#L307) +* [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/jdbc/JdbcUtils.scala#L141-L164) +* [JDBC -> Clickhouse](https://github.com/ClickHouse/clickhouse-java/blob/0.3.2/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/JdbcTypeMapping.java#L185-L311) + +## Supported types + +See [official documentation](https://clickhouse.com/docs/en/sql-reference/data-types) + +### Generic types + +* `LowCardinality(T)` is same as `T` +* `Nullable(T)` is same as `T`, but Spark column is inferred as `nullable=True` + +### Numeric types + +| Clickhouse type (read) | Spark type | Clickhouse type (write) | Clickhouse type (create) | +|------------------------------|----------------------------------|-----------------------------|-----------------------------| +| `Bool` | `BooleanType()` | `Bool` | `UInt64` | +| `Decimal` | `DecimalType(P=10, S=0)` | `Decimal(P=10, S=0)` | `Decimal(P=10, S=0)` | +| `Decimal(P=0..38)` | `DecimalType(P=0..38, S=0)` | `Decimal(P=0..38, S=0)` | `Decimal(P=0..38, S=0)` | +| `Decimal(P=0..38, S=0..38)` | `DecimalType(P=0..38, S=0..38)` | `Decimal(P=0..38, S=0..38)` | `Decimal(P=0..38, S=0..38)` | +| `Decimal(P=39..76, S=0..76)` | unsupported [3](#id9) | | | +| `Decimal32(P=0..9)` | `DecimalType(P=9, S=0..9)` | `Decimal(P=9, S=0..9)` | `Decimal(P=9, S=0..9)` | +| `Decimal64(S=0..18)` | `DecimalType(P=18, S=0..18)` | `Decimal(P=18, S=0..18)` | `Decimal(P=18, S=0..18)` | +| `Decimal128(S=0..38)` | `DecimalType(P=38, S=0..38)` | `Decimal(P=38, S=0..38)` | `Decimal(P=38, S=0..38)` | +| `Decimal256(S=0..76)` | unsupported [3](#id9) | | | +| `Float32` | `FloatType()` | `Float32` | `Float32` | +| `Float64` | `DoubleType()` | `Float64` | `Float64` | +| `Int8` | `IntegerType()` | `Int32` | `Int32` | +| `Int16` | | | | +| `Int32` | | | | +| `Int64` | `LongType()` | `Int64` | `Int64` | +| `Int128` | unsupported [3](#id9) | | | +| `Int256` | | | | +| `-` | `ByteType()` | `Int8` | `Int8` | +| `-` | `ShortType()` | `Int32` | `Int32` | +| `UInt8` | `IntegerType()` | `Int32` | `Int32` | +| `UInt16` | `LongType()` | `Int64` | `Int64` | +| `UInt32` | `DecimalType(20,0)` | `Decimal(20,0)` | `Decimal(20,0)` | +| `UInt64` | | | | +| `UInt128` | unsupported [3](#id9) | | | +| `UInt256` | | | | +* **[3]** Clickhouse support numeric types up to 256 bit - `Int256`, `UInt256`, `Decimal256(S)`, `Decimal(P=39..76, S=0..76)`. But Spark’s `DecimalType(P, S)` supports maximum `P=38` (128 bit). It is impossible to read, write or operate with values of larger precision, this leads to an exception. + +### Temporal types + +Notes: +: * Datetime with timezone has the same precision as without timezone + * `DateTime` is alias for `DateTime32` + * `TIMESTAMP` is alias for `DateTime32`, but `TIMESTAMP(N)` is alias for `DateTime64(N)` + +| Clickhouse type (read) | Spark type | Clickhouse type (write) | Clickhouse type (create) | +|---------------------------------|-------------------------------------------------------------------------------|-------------------------------|---------------------------------------------------------------------| +| `Date` | `DateType()` | `Date` | `Date` | +| `Date32` | `DateType()` | `Date` | `Date`,
**cannot insert data** [4](#id15) | +| `DateTime32`, seconds | `TimestampType()`, microseconds | `DateTime64(6)`, microseconds | `DateTime32`, seconds | +| `DateTime64(3)`, milliseconds | `TimestampType()`, microseconds | `DateTime64(6)`, microseconds | `DateTime32`, seconds,
**precision loss** [5](#id16) | +| `DateTime64(6)`, microseconds | `TimestampType()`, microseconds | | `DateTime32`, seconds,
**precision loss** [7](#id18) | +| `DateTime64(7..9)`, nanoseconds | `TimestampType()`, microseconds,
**precision loss** [6](#id17) | | | +| `-` | `TimestampNTZType()`, microseconds | | | +| `DateTime32(TZ)` | unsupported [7](#id18) | | | +| `DateTime64(P, TZ)` | | | | +| `IntervalNanosecond` | `LongType()` | `Int64` | `Int64` | +| `IntervalMicrosecond` | | | | +| `IntervalMillisecond` | | | | +| `IntervalSecond` | | | | +| `IntervalMinute` | | | | +| `IntervalHour` | | | | +| `IntervalDay` | | | | +| `IntervalMonth` | | | | +| `IntervalQuarter` | | | | +| `IntervalWeek` | | | | +| `IntervalYear` | | | | + +#### WARNING +Note that types in Clickhouse and Spark have different value ranges: + +| Clickhouse type | Min value | Max value | Spark type | Min value | Max value | +|----------------------|---------------------------------|---------------------------------|-------------------|------------------------------|------------------------------| +| `Date` | `1970-01-01` | `2149-06-06` | `DateType()` | `0001-01-01` | `9999-12-31` | +| `DateTime32` | `1970-01-01 00:00:00` | `2106-02-07 06:28:15` | `TimestampType()` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | +| `DateTime64(P=0..8)` | `1900-01-01 00:00:00.00000000` | `2299-12-31 23:59:59.99999999` | | | | +| `DateTime64(P=9)` | `1900-01-01 00:00:00.000000000` | `2262-04-11 23:47:16.999999999` | | | | + +So not all of values in Spark DataFrame can be written to Clickhouse. + +References: +: * [Clickhouse Date documentation](https://clickhouse.com/docs/en/sql-reference/data-types/date) + * [Clickhouse Datetime32 documentation](https://clickhouse.com/docs/en/sql-reference/data-types/datetime) + * [Clickhouse Datetime64 documentation](https://clickhouse.com/docs/en/sql-reference/data-types/datetime64) + * [Spark DateType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/DateType.html) + * [Spark TimestampType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/TimestampType.html) + +* **[4]** `Date32` has different bytes representation than `Date`, and inserting value of type `Date32` to `Date` column leads to errors on Clickhouse side, e.g. `Date(106617) should be between 0 and 65535 inclusive of both values`. Although Spark does properly read the `Date32` column as `DateType()`, and there should be no difference at all. Probably this is some bug in Clickhouse driver. +* **[5]** Generic JDBC dialect generates DDL with Clickhouse type `TIMESTAMP` which is alias for `DateTime32` with precision up to seconds (`23:59:59`). Inserting data with milliseconds precision (`23:59:59.999`) will lead to **throwing away milliseconds**. Solution: create table manually, with proper column type. +* **[6]** Clickhouse support datetime up to nanoseconds precision (`23:59:59.999999999`), but Spark `TimestampType()` supports datetime up to microseconds precision (`23:59:59.999999`). Nanoseconds will be lost during read or write operations. Solution: create table manually, with proper column type. +* **[7]** Clickhouse will raise an exception that data in format `2001-01-01 23:59:59.999999` has data `.999999` which does not match format `YYYY-MM-DD hh:mm:ss` of `DateTime32` column type (see [5](#id16)). So Spark can create Clickhouse table, but cannot write data to column of this type. Solution: create table manually, with proper column type. + +### String types + +| Clickhouse type (read) | Spark type | Clickhousetype (write) | Clickhouse type (create) | +|--------------------------|----------------|--------------------------|----------------------------| +| `FixedString(N)` | `StringType()` | `String` | `String` | +| `String` | | | | +| `Enum8` | | | | +| `Enum16` | | | | +| `IPv4` | | | | +| `IPv6` | | | | +| `UUID` | | | | +| `-` | `BinaryType()` | | | + +## Unsupported types + +Columns of these Clickhouse types cannot be read by Spark: +: * `AggregateFunction(func, T)` + * `Array(T)` + * `JSON` + * `Map(K, V)` + * `MultiPolygon` + * `Nested(field1 T1, ...)` + * `Nothing` + * `Point` + * `Polygon` + * `Ring` + * `SimpleAggregateFunction(func, T)` + * `Tuple(T1, T2, ...)` + +Dataframe with these Spark types cannot be written to Clickhouse: +: * `ArrayType(T)` + * `BinaryType()` + * `CharType(N)` + * `DayTimeIntervalType(P, S)` + * `MapType(K, V)` + * `NullType()` + * `StructType([...])` + * `TimestampNTZType()` + * `VarcharType(N)` + +This is because Spark does not have dedicated Clickhouse dialect, and uses Generic JDBC dialect instead. +This dialect does not have type conversion between some types, like Clickhouse `Array` -> Spark `ArrayType()`, and vice versa. + +The is a way to avoid this - just cast everything to `String`. + +## Explicit type cast + +### `DBReader` + +Use `CAST` or `toJSONString` to get column data as string in JSON format, + +For parsing JSON columns in ClickHouse, [`JSON.parse_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.parse_column) method. + +```python +from pyspark.sql.types import ArrayType, IntegerType + +from onetl.file.format import JSON +from onetl.connection import ClickHouse +from onetl.db import DBReader + +reader = DBReader( + connection=clickhouse, + target="default.source_tbl", + columns=[ + "id", + "toJSONString(array_column) array_column", + ], +) +df = reader.run() + +# Spark requires all columns to have some specific type, describe it +column_type = ArrayType(IntegerType()) + +json = JSON() +df = df.select( + df.id, + json.parse_column("array_column", column_type), +) +``` + +### `DBWriter` + +For writing JSON data to ClickHouse, use the [`JSON.serialize_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.serialize_column) method to convert a DataFrame column to JSON format efficiently and write it as a `String` column in Clickhouse. + +```python +from onetl.file.format import JSON +from onetl.connection import ClickHouse +from onetl.db import DBWriter + +clickhouse = ClickHouse(...) + +clickhouse.execute( + """ + CREATE TABLE default.target_tbl ( + id Int32, + array_column_json String, + ) + ENGINE = MergeTree() + ORDER BY id + """, +) + +json = JSON() +df = df.select( + df.id, + json.serialize_column(df.array_column).alias("array_column_json"), +) + +writer.run(df) +``` + +Then you can parse this column on Clickhouse side - for example, by creating a view: + +```sql +SELECT + id, + JSONExtract(json_column, 'Array(String)') AS array_column +FROM target_tbl +``` + +You can also use [ALIAS](https://clickhouse.com/docs/en/sql-reference/statements/create/table#alias) +or [MATERIALIZED](https://clickhouse.com/docs/en/sql-reference/statements/create/table#materialized) columns +to avoid writing such expression in every `SELECT` clause all the time: + +```sql +CREATE TABLE default.target_tbl ( + id Int32, + array_column_json String, + -- computed column + array_column Array(String) ALIAS JSONExtract(json_column, 'Array(String)') + -- or materialized column + -- array_column Array(String) MATERIALIZED JSONExtract(json_column, 'Array(String)') +) +ENGINE = MergeTree() +ORDER BY id +``` + +Downsides: + +* Using `SELECT JSONExtract(...)` or `ALIAS` column can be expensive, because value is calculated on every row access. This can be especially harmful if such column is used in `WHERE` clause. +* `ALIAS` and `MATERIALIZED` columns are not included in `SELECT *` clause, they should be added explicitly: `SELECT *, calculated_column FROM table`. + +#### WARNING +[EPHEMERAL](https://clickhouse.com/docs/en/sql-reference/statements/create/table#ephemeral) columns are not supported by Spark +because they cannot be selected to determine target column type. diff --git a/mddocs/connection/db_connection/clickhouse/write.md b/mddocs/connection/db_connection/clickhouse/write.md new file mode 100644 index 000000000..dd8ca31cc --- /dev/null +++ b/mddocs/connection/db_connection/clickhouse/write.md @@ -0,0 +1,187 @@ + + +# Writing to Clickhouse using `DBWriter` + +For writing data to Clickhouse, use [`DBWriter`](../../../db/db_writer.md#onetl.db.db_writer.db_writer.DBWriter). + +#### WARNING +Please take into account [Clickhouse <-> Spark type mapping](types.md#clickhouse-types) + +#### WARNING +It is always recommended to create table explicitly using [Clickhouse.execute](execute.md#clickhouse-execute) +instead of relying on Spark’s table DDL generation. + +This is because Spark’s DDL generator can create columns with different precision and types than it is expected, +causing precision loss or other issues. + +## Examples + +```python +from onetl.connection import Clickhouse +from onetl.db import DBWriter + +clickhouse = Clickhouse(...) + +df = ... # data is here + +writer = DBWriter( + connection=clickhouse, + target="schema.table", + options=Clickhouse.WriteOptions( + if_exists="append", + # ENGINE is required by Clickhouse + createTableOptions="ENGINE = MergeTree() ORDER BY id", + ), +) + +writer.run(df) +``` + +## Options + +Method above accepts [`Clickhouse.WriteOptions`](#onetl.connection.db_connection.clickhouse.options.ClickhouseWriteOptions) + +### *pydantic model* onetl.connection.db_connection.clickhouse.options.ClickhouseWriteOptions + +Spark JDBC writing options. + +#### Versionadded +Added in version 0.5.0: Replace `Clickhouse.Options` → `Clickhouse.WriteOptions` + +### Examples + +#### NOTE +You can pass any value +[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), +even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! + +The set of supported options depends on Spark version. + +```python +from onetl.connection import Clickhouse + +options = Clickhouse.WriteOptions( + if_exists="append", + batchsize=20_000, + customSparkOption="value", +) +``` + + + +#### *field* if_exists *: JDBCTableExistBehavior* *= JDBCTableExistBehavior.APPEND* *(alias 'mode')* + +Behavior of writing data into existing table. + +Possible values: +: * `append` (default) + : Adds new rows into existing table. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : Data is appended to a table. Table has the same DDL as before writing data +
+ #### WARNING + This mode does not check whether table already contains + rows from dataframe, so duplicated rows can be created. +
+ Also Spark does not support passing custom options to + insert statement, like `ON CONFLICT`, so don’t try to + implement deduplication using unique indexes or constraints. +
+ Instead, write to staging table and perform deduplication + using `execute` method. + * `replace_entire_table` + : **Table is dropped and then created, or truncated**. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : Table content is replaced with dataframe content. +
+ After writing completed, target table could either have the same DDL as + before writing data (`truncate=True`), or can be recreated (`truncate=False` + or source does not support truncation). + * `ignore` + : Ignores the write operation if the table already exists. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : The write operation is ignored, and no data is written to the table. + * `error` + : Raises an error if the table already exists. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : An error is raised, and no data is written to the table. + +#### Versionchanged +Changed in version 0.9.0: Renamed `mode` → `if_exists` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* batchsize *: int* *= 20000* + +How many rows can be inserted per round trip. + +Tuning this option can influence performance of writing. + +#### WARNING +Default value is different from Spark. + +Spark uses quite small value `1000`, which is absolutely not usable +in BigData world. + +Thus we’ve overridden default value with `20_000`, +which should increase writing performance. + +You can increase it even more, up to `50_000`, +but it depends on your database load and number of columns in the row. +Higher values does not increase performance. + +#### Versionchanged +Changed in version 0.4.0: Changed default value from 1000 to 20_000 + + + +#### *field* isolation_level *: str* *= 'READ_UNCOMMITTED'* *(alias 'isolationLevel')* + +The transaction isolation level, which applies to current connection. + +Possible values: +: * `NONE` (as string, not Python’s `None`) + * `READ_COMMITTED` + * `READ_UNCOMMITTED` + * `REPEATABLE_READ` + * `SERIALIZABLE` + +Values correspond to transaction isolation levels defined by JDBC standard. +Please refer the documentation for +[java.sql.Connection](https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html). + + diff --git a/mddocs/connection/db_connection/greenplum/connection.md b/mddocs/connection/db_connection/greenplum/connection.md new file mode 100644 index 000000000..b180a5fc7 --- /dev/null +++ b/mddocs/connection/db_connection/greenplum/connection.md @@ -0,0 +1,144 @@ + + +# Greenplum connection + +### *class* onetl.connection.db_connection.greenplum.connection.Greenplum(\*, spark: SparkSession, host: Host, user: str, password: SecretStr, database: str, port: int = 5432, extra: GreenplumExtra = GreenplumExtra(tcpKeepAlive='true')) + +Greenplum connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on package `io.pivotal:greenplum-spark:2.2.0` +([VMware Greenplum connector for Spark](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/index.html)). + +#### SEE ALSO +Before using this connector please take into account [Prerequisites](prerequisites.md#greenplum-prerequisites) + +#### Versionadded +Added in version 0.5.0. + +* **Parameters:** + **host** + : Host of Greenplum master. For example: `test.greenplum.domain.com` or `193.168.1.17` + + **port** + : Port of Greenplum master + + **user** + : User, which have proper access to the database. For example: `some_user` + + **password** + : Password for database connection + + **database** + : Database in RDBMS, NOT schema. +
+ See [this page](https://www.educba.com/postgresql-database-vs-schema/) for more details + + **spark** + : Spark session. + + **extra** + : Specifies one or more extra parameters by which clients can connect to the instance. +
+ For example: `{"tcpKeepAlive": "true", "server.port": "50000-65535"}` +
+ Supported options are: + : * All [Postgres JDBC driver properties](https://jdbc.postgresql.org/documentation/use/) + * Properties from [Greenplum connector for Spark documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html) page, but only starting with `server.` or `pool.` + +### Examples + +Create and check Greenplum connection: + +```python +from onetl.connection import Greenplum +from pyspark.sql import SparkSession + +# Create Spark session with Greenplum connector loaded +maven_packages = Greenplum.get_packages(spark_version="3.2") +spark = ( + SparkSession.builder.appName("spark-app-name") + .config("spark.jars.packages", ",".join(maven_packages)) + .config("spark.executor.allowSparkContext", "true") + # IMPORTANT!!! + # Set number of executors according to "Prerequisites" -> "Number of executors" + .config("spark.dynamicAllocation.maxExecutors", 10) + .config("spark.executor.cores", 1) + .getOrCreate() +) + +# IMPORTANT!!! +# Set port range of executors according to "Prerequisites" -> "Network ports" +extra = { + "server.port": "41000-42000", +} + +# Create connection +greenplum = Greenplum( + host="master.host.or.ip", + user="user", + password="*****", + database="target_database", + extra=extra, + spark=spark, +).check() +``` + + + +#### check() + +Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If not, an exception will be raised. + +* **Returns:** + Connection itself +* **Raises:** + RuntimeError + : If the connection is not available + +### Examples + +```python +connection.check() +``` + + + +#### *classmethod* get_packages(\*, scala_version: str | None = None, spark_version: str | None = None, package_version: str | None = None) → list[str] + +Get package names to be downloaded by Spark. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +You should pass either `scala_version` or `spark_version`. + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **scala_version** + : Scala version in format `major.minor`. +
+ If `None`, `spark_version` is used to determine Scala version. + + **spark_version** + : Spark version in format `major.minor`. +
+ Used only if `scala_version=None`. + + **package_version** + : Package version in format `major.minor.patch` +
+ #### Versionadded + Added in version 0.10.1. + +### Examples + +```python +from onetl.connection import Greenplum + +Greenplum.get_packages(scala_version="2.12") +Greenplum.get_packages(spark_version="3.2", package_version="2.3.0") +``` + + diff --git a/mddocs/connection/db_connection/greenplum/execute.md b/mddocs/connection/db_connection/greenplum/execute.md new file mode 100644 index 000000000..c923512a3 --- /dev/null +++ b/mddocs/connection/db_connection/greenplum/execute.md @@ -0,0 +1,195 @@ + + +# Executing statements in Greenplum + +#### WARNING +Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + +Do **NOT** use them to read large amounts of data. Use [DBReader](read.md#greenplum-read) instead. + +## How to + +There are 2 ways to execute some statement in Greenplum + +### Use `Greenplum.fetch` + +Use this method to perform some `SELECT` query which returns **small number or rows**, like reading +Greenplum config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts [`Greenplum.FetchOptions`](#onetl.connection.db_connection.greenplum.options.GreenplumFetchOptions). + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### WARNING +`Greenplum.fetch` is implemented using Postgres JDBC connection, +so types are handled a bit differently than in `DBReader`. See [Postgres <-> Spark type mapping](../postgres/types.md#postgres-types). + +#### Syntax support + +This method supports **any** query syntax supported by Greenplum, like: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ✅︎ `SELECT func(arg1, arg2)` or `{call func(arg1, arg2)}` - special syntax for calling functions +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Greenplum + +greenplum = Greenplum(...) + +df = greenplum.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=Greenplum.FetchOptions(queryTimeout=10), +) +greenplum.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `Greenplum.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts [`Greenplum.ExecuteOptions`](#onetl.connection.db_connection.greenplum.options.GreenplumExecuteOptions). + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Greenplum, like: + +* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +* ✅︎ `ALTER ...` +* ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +* ✅︎ `CALL procedure(arg1, arg2) ...` +* ✅︎ `SELECT func(arg1, arg2)` or `{call func(arg1, arg2)}` - special syntax for calling functions +* ✅︎ other statements not mentioned here +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Greenplum + +greenplum = Greenplum(...) + +greenplum.execute("DROP TABLE schema.table") +greenplum.execute( + """ + CREATE TABLE schema.table ( + id int, + key text, + value real + ) + DISTRIBUTED BY id + """, + options=Greenplum.ExecuteOptions(queryTimeout=10), +) +``` + +## Interaction schema + +Unlike reading & writing, executing statements in Greenplum is done **only** through Greenplum master node, +without any interaction between Greenplum segments and Spark executors. More than that, Spark executors are not used in this case. + +The only port used while interacting with Greenplum in this case is `5432` (Greenplum master port). + +### Spark <-> Greenplum interaction during Greenplum.execute()/Greenplum.fetch() + +## Options + +### *pydantic model* onetl.connection.db_connection.greenplum.options.GreenplumFetchOptions + +Options related to fetching data from databases via JDBC. + +#### Versionadded +Added in version 0.11.0: Replace `Greenplum.JDBCOptions` → `Greenplum.FetchOptions` + +### Examples + +#### NOTE +You can pass any value supported by underlying JDBC driver class, +even if it is not mentioned in this documentation. + +```python +from onetl.connection import Greenplum + +options = Greenplum.FetchOptions( + queryTimeout=60_000, + fetchsize=100_000, + customSparkOption="value", +) +``` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int | None* *= None* + +How many rows to fetch per round trip. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value depends on driver. For example, Oracle has +default `fetchsize=10`. + + + +### *pydantic model* onetl.connection.db_connection.greenplum.options.GreenplumExecuteOptions + +Options related to executing statements in databases via JDBC. + +#### Versionadded +Added in version 0.11.0: Replace `Greenplum.JDBCOptions` → `Greenplum.ExecuteOptions` + +### Examples + +#### NOTE +You can pass any value supported by underlying JDBC driver class, +even if it is not mentioned in this documentation. + +```python +from onetl.connection import Greenplum + +options = Greenplum.ExecuteOptions( + queryTimeout=60_000, + customSparkOption="value", +) +``` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int | None* *= None* + +How many rows to fetch per round trip. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value depends on driver. For example, Oracle has +default `fetchsize=10`. + + diff --git a/mddocs/connection/db_connection/greenplum/index.md b/mddocs/connection/db_connection/greenplum/index.md new file mode 100644 index 000000000..9fcc68f66 --- /dev/null +++ b/mddocs/connection/db_connection/greenplum/index.md @@ -0,0 +1,18 @@ + + +# Greenplum + +# Connection + +* [Prerequisites](prerequisites.md) +* [Greenplum connection](connection.md) + +# Operations + +* [Reading from Greenplum using `DBReader`](read.md) +* [Writing to Greenplum using `DBWriter`](write.md) +* [Executing statements in Greenplum](execute.md) + +# Troubleshooting + +* [Greenplum <-> Spark type mapping](types.md) diff --git a/mddocs/connection/db_connection/greenplum/prerequisites.md b/mddocs/connection/db_connection/greenplum/prerequisites.md new file mode 100644 index 000000000..15c06300b --- /dev/null +++ b/mddocs/connection/db_connection/greenplum/prerequisites.md @@ -0,0 +1,358 @@ + + +# Prerequisites + +## Version Compatibility + +* Greenplum server versions: + : * Officially declared: 5.x, 6.x, and 7.x (which requires `Greenplum.get_packages(package_version="2.3.0")` or higher) + * Actually tested: 6.23, 7.0 +* Spark versions: 2.3.x - 3.2.x (Spark 3.3+ is not supported yet) +* Java versions: 8 - 11 + +See [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.2/greenplum-connector-spark/release_notes.html). + +## Installing PySpark + +To use Greenplum connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Downloading VMware package + +To use Greenplum connector you should download connector `.jar` file from +[VMware website](https://network.tanzu.vmware.com/products/vmware-greenplum#/releases/1413479/file_groups/16966) +and then pass it to Spark session. + +#### WARNING +Please pay attention to [Spark & Scala version compatibility](../../../install/spark.md#spark-compatibility-matrix). + +#### WARNING +There are issues with using package of version 2.3.0/2.3.1 with Greenplum 6.x - connector can +open transaction with `SELECT * FROM table LIMIT 0` query, but does not close it, which leads to deadlocks +during write. + +There are several ways to do that. See [Injecting Java packages](../../../install/spark.md#java-packages) for details. + +#### NOTE +If you’re uploading package to private package repo, use `groupId=io.pivotal` and `artifactoryId=greenplum-spark_2.12` +(`2.12` is Scala version) to give uploaded package a proper name. + +## Connecting to Greenplum + +### Interaction schema + +Spark executors open ports to listen incoming requests. +Greenplum segments are initiating connections to Spark executors using [EXTERNAL TABLE](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-sql_commands-CREATE_EXTERNAL_TABLE.html) +functionality, and send/read data using [gpfdist protocol](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/admin_guide-external-g-using-the-greenplum-parallel-file-server--gpfdist-.html#about-gpfdist-setup-and-performance-1). + +Data is **not** send through Greenplum master. +Greenplum master only receives commands to start reading/writing process, and manages all the metadata (external table location, schema and so on). + +More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/overview.html). + +### Set number of connections + +#### WARNING +This is very important!!! + +If you don’t limit number of connections, you can exceed the [max_connections](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/admin_guide-client_auth.html#limiting-concurrent-connections#limiting-concurrent-connections-2) +limit set on the Greenplum side. It’s usually not so high, e.g. 500-1000 connections max, +depending on your Greenplum instance settings and using connection balancers like `pgbouncer`. + +Consuming all available connections means **nobody** (even admin users) can connect to Greenplum. + +Each job on the Spark executor makes its own connection to Greenplum master node, +so you need to limit number of connections to avoid opening too many of them. + +* Reading about `5-10Gb` of data requires about `3-5` parallel connections. +* Reading about `20-30Gb` of data requires about `5-10` parallel connections. +* Reading about `50Gb` of data requires ~ `10-20` parallel connections. +* Reading about `100+Gb` of data requires `20-30` parallel connections. +* Opening more than `30-50` connections is not recommended. + +Number of connections can be limited by 2 ways: + +* By limiting number of Spark executors and number of cores per-executor. Max number of parallel jobs is `executors * cores`. + +Spark with master=local + +```py +spark = ( + SparkSession.builder + # Spark will run with 5 threads in local mode, allowing up to 5 parallel tasks + .config("spark.master", "local[5]") + .config("spark.executor.cores", 1) +).getOrCreate() +``` + +Spark with master=yarn or master=k8s, dynamic allocation + +```py +spark = ( + SparkSession.builder + .config("spark.master", "yarn") + # Spark will start MAX 10 executors with 1 core each (dynamically), so max number of parallel jobs is 10 + .config("spark.dynamicAllocation.maxExecutors", 10) + .config("spark.executor.cores", 1) +).getOrCreate() +``` + +Spark with master=yarn or master=k8s, static allocation + +```py +spark = ( + SparkSession.builder + .config("spark.master", "yarn") + # Spark will start EXACTLY 10 executors with 1 core each, so max number of parallel jobs is 10 + .config("spark.executor.instances", 10) + .config("spark.executor.cores", 1) +).getOrCreate() +``` + +* By limiting connection pool size user by Spark (**only** for Spark with `master=local`): + +```python +spark = SparkSession.builder.config("spark.master", "local[*]").getOrCreate() + +# No matter how many executors are started and how many cores they have, +# number of connections cannot exceed pool size: +Greenplum( + ..., + extra={ + "pool.maxSize": 10, + }, +) +``` + +See [connection pooling](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/using_the_connector.html#jdbcconnpool) +documentation. + +* By setting [`num_partitions`](read.md#onetl.connection.db_connection.greenplum.options.GreenplumReadOptions.num_partitions) + and [`partition_column`](read.md#onetl.connection.db_connection.greenplum.options.GreenplumReadOptions.partition_column) (not recommended). + +### Allowing connection to Greenplum master + +Ask your Greenplum cluster administrator to allow your user to connect to Greenplum master node, +e.g. by updating `pg_hba.conf` file. + +More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/admin_guide-client_auth.html#limiting-concurrent-connections#allowing-connections-to-greenplum-database-0). + +### Set connection port + +#### Spark with `master=k8s` + +Please follow [the official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/configure.html#k8scfg) + +#### Spark with `master=yarn` or `master=local` + +To read data from Greenplum using Spark, following ports should be opened in firewall between Spark and Greenplum: + +* Spark driver and all Spark executors -> port `5432` on Greenplum master node. + + This port number should be set while connecting to Greenplum: + ```python + greenplum = Greenplum(host="master.host", port=5432, ...) + ``` +* Greenplum segments -> some port range (e.g. `41000-42000`) **listened by Spark executors**. + + This range should be set in `extra` option: + ```python + greenplum = Greenplum( + ..., + extra={ + "server.port": "41000-42000", + }, + ) + ``` + + Number of ports in this range is `number of parallel running Spark sessions` \* `number of parallel connections per session`. + + Number of connections per session (see below) is usually less than `30` (see above). + + Number of session depends on your environment: + : * For `master=local` only few ones-tens sessions can be started on the same host, depends on available RAM and CPU. + * For `master=yarn` hundreds or thousands of sessions can be started simultaneously, + but they are executing on different cluster nodes, so one port can be opened on different nodes at the same time. + +More details can be found in official documentation: +: * [port requirements](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/sys_reqs.html#network-port-requirements) + * [format of server.port value](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#server.port) + * [port troubleshooting](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/troubleshooting.html#port-errors) + +### Set connection host + +#### Spark with `master=k8s` + +Please follow [the official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/configure.html#k8scfg) + +#### Spark with `master=local` + +By default, Greenplum connector tries to resolve IP of current host, and then pass it as `gpfdist` URL to Greenplum segment. +This may fail in some cases. + +For example, IP can be resolved using `/etc/hosts` content like this: + +```text +127.0.0.1 localhost real-host-name +``` + +```bash +$ hostname -f +localhost + +$ hostname -i +127.0.0.1 +``` + +Reading/writing data to Greenplum will fail with following exception: + +```text +org.postgresql.util.PSQLException: ERROR: connection with gpfdist failed for +"gpfdist://127.0.0.1:49152/local-1709739764667/exec/driver", +effective url: "http://127.0.0.1:49152/local-1709739764667/exec/driver": +error code = 111 (Connection refused); (seg3 slice1 12.34.56.78:10003 pid=123456) +``` + +There are 2 ways to fix that: + +* Explicitly pass your host IP address to connector, like this + ```python + import os + + # pass here real host IP (accessible from GP segments) + os.environ["HOST_IP"] = "192.168.1.1" + + greenplum = Greenplum( + ..., + extra={ + # connector will read IP from this environment variable + "server.hostEnv": "env.HOST_IP", + }, + spark=spark, + ) + ``` + + More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#server.hostenv). +* Update `/etc/hosts` file to include real host IP: + ```text + 127.0.0.1 localhost + # this IP should be accessible from GP segments + 192.168.1.1 driver-host-name + ``` + + So Greenplum connector will properly resolve host IP. + +#### Spark with `master=yarn` + +The same issue with resolving IP address can occur on Hadoop cluster node, but it’s tricky to fix, because each node has a different IP. + +There are 3 ways to fix that: + +* Pass node hostname to `gpfdist` URL. So IP will be resolved on segment side: + ```python + greenplum = Greenplum( + ..., + extra={ + "server.useHostname": "true", + }, + ) + ``` + + But this may fail if Hadoop cluster node hostname cannot be resolved from Greenplum segment side. + + More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#server.usehostname). +* Set specific network interface to get IP address from: + ```python + greenplum = Greenplum( + ..., + extra={ + "server.nic": "eth0", + }, + ) + ``` + + You can get list of network interfaces using this command. + + #### NOTE + This command should be executed on Hadoop cluster node, **not** Spark driver host! + + ```bash + $ ip address + 1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 + inet 127.0.0.1/8 scope host lo + valid_lft forever preferred_lft forever + 2: eth0: mtu 1500 qdisc fq_codel state UP group default qlen 1000 + inet 192.168.1.1/24 brd 192.168.1.255 scope global dynamic noprefixroute eth0 + valid_lft 83457sec preferred_lft 83457sec + ``` + + Note that in this case **each** Hadoop cluster node node should have network interface with name `eth0`. + + More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#server.nic). +* Update `/etc/hosts` on each Hadoop cluster node to include real node IP: + ```text + 127.0.0.1 localhost + # this IP should be accessible from GP segments + 192.168.1.1 cluster-node-name + ``` + + So Greenplum connector will properly resolve node IP. + +### Set required grants + +Ask your Greenplum cluster administrator to set following grants for a user, +used for creating a connection: + +Read + Write + +```sql +-- get access to get tables metadata & cluster information +GRANT SELECT ON information_schema.tables TO username; +GRANT SELECT ON pg_attribute TO username; +GRANT SELECT ON pg_class TO username; +GRANT SELECT ON pg_namespace TO username; +GRANT SELECT ON pg_settings TO username; +GRANT SELECT ON pg_stats TO username; +GRANT SELECT ON gp_distributed_xacts TO username; +GRANT SELECT ON gp_segment_configuration TO username; +-- Greenplum 5.x only +GRANT SELECT ON gp_distribution_policy TO username; + +-- allow creating external tables in the same schema as source/target table +GRANT USAGE ON SCHEMA myschema TO username; +GRANT CREATE ON SCHEMA myschema TO username; +ALTER USER username CREATEEXTTABLE(type = 'readable', protocol = 'gpfdist') CREATEEXTTABLE(type = 'writable', protocol = 'gpfdist'); + +-- allow read access to specific table (to get column types) +-- allow write access to specific table +GRANT SELECT, INSERT ON myschema.mytable TO username; +``` + +Read only + +```sql +-- get access to get tables metadata & cluster information +GRANT SELECT ON information_schema.tables TO username; +GRANT SELECT ON pg_attribute TO username; +GRANT SELECT ON pg_class TO username; +GRANT SELECT ON pg_namespace TO username; +GRANT SELECT ON pg_settings TO username; +GRANT SELECT ON pg_stats TO username; +GRANT SELECT ON gp_distributed_xacts TO username; +GRANT SELECT ON gp_segment_configuration TO username; +-- Greenplum 5.x only +GRANT SELECT ON gp_distribution_policy TO username; + +-- allow creating external tables in the same schema as source table +GRANT USAGE ON SCHEMA schema_to_read TO username; +GRANT CREATE ON SCHEMA schema_to_read TO username; +-- yes, ``writable`` for reading from GP, because data is written from Greenplum to Spark executor. +ALTER USER username CREATEEXTTABLE(type = 'writable', protocol = 'gpfdist'); + +-- allow read access to specific table +GRANT SELECT ON schema_to_read.table_to_read TO username; +``` + +More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/install_cfg.html#role-privileges). diff --git a/mddocs/connection/db_connection/greenplum/read.md b/mddocs/connection/db_connection/greenplum/read.md new file mode 100644 index 000000000..476f6f0db --- /dev/null +++ b/mddocs/connection/db_connection/greenplum/read.md @@ -0,0 +1,405 @@ + + +# Reading from Greenplum using `DBReader` + +Data can be read from Greenplum to Spark using [`DBReader`](../../../db/db_reader.md#onetl.db.db_reader.db_reader.DBReader). +It also supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading. + +#### WARNING +Please take into account [Greenplum <-> Spark type mapping](types.md#greenplum-types). + +#### NOTE +Unlike JDBC connectors, *Greenplum connector for Spark* does not support +executing **custom** SQL queries using `.sql` method. Connector can be used to only read data from a table or view. + +## Supported DBReader features + +* ✅︎ `columns` (see note below) +* ✅︎ `where` (see note below) +* ✅︎ `hwm` (see note below), supported strategies: +* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) +* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) +* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) +* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) +* ❌ `hint` (is not supported by Greenplum) +* ❌ `df_schema` +* ✅︎ `options` (see [`Greenplum.ReadOptions`](#onetl.connection.db_connection.greenplum.options.GreenplumReadOptions)) + +#### WARNING +In case of Greenplum connector, `DBReader` does not generate raw `SELECT` query. Instead it relies on Spark SQL syntax +which in some cases (using column projection and predicate pushdown) can be converted to Greenplum SQL. + +So `columns`, `where` and `hwm.expression` should be specified in [Spark SQL](https://spark.apache.org/docs/latest/sql-ref-syntax.html) syntax, +not Greenplum SQL. + +This is OK: + +```python +DBReader( + columns=[ + "some_column", + # this cast is executed on Spark side + "CAST(another_column AS STRING)", + ], + # this predicate is parsed by Spark, and can be pushed down to Greenplum + where="some_column LIKE 'val1%'", +) +``` + +This is will fail: + +```python +DBReader( + columns=[ + "some_column", + # Spark does not have `text` type + "CAST(another_column AS text)", + ], + # Spark does not support ~ syntax for regexp matching + where="some_column ~ 'val1.*'", +) +``` + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Greenplum +from onetl.db import DBReader + +greenplum = Greenplum(...) + +reader = DBReader( + connection=greenplum, + source="schema.table", + columns=["id", "key", "CAST(value AS string) value", "updated_dt"], + where="key = 'something'", +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Greenplum +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +greenplum = Greenplum(...) + +reader = DBReader( + connection=greenplum, + source="schema.table", + columns=["id", "key", "CAST(value AS string) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="greenplum_hwm", expression="updated_dt"), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Interaction schema + +High-level schema is described in [Prerequisites](prerequisites.md#greenplum-prerequisites). You can find detailed interaction schema below. + +### Spark <-> Greenplum interaction during DBReader.run() + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Greenplum to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Greenplum to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +### Read data in parallel + +`DBReader` in case of Greenplum connector requires view or table to have a column which is used by Spark +for parallel reads. + +Choosing proper column allows each Spark executor to read only part of data stored in the specified segment, +avoiding moving large amounts of data between segments, which improves reading performance. + +#### Using `gp_segment_id` + +By default, `DBReader` will use [gp_segment_id](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/troubleshooting.html#reading-from-a-view) +column for parallel data reading. Each DataFrame partition will contain data of a specific Greenplum segment. + +This allows each Spark executor read only data from specific Greenplum segment, avoiding moving large amounts of data between segments. + +If view is used, it is recommended to include `gp_segment_id` column to this view: + +### Reading from view with gp_segment_id column + +```python +from onetl.connection import Greenplum +from onetl.db import DBReader + +greenplum = Greenplum(...) + +greenplum.execute( + """ + CREATE VIEW schema.view_with_gp_segment_id AS + SELECT + id, + some_column, + another_column, + gp_segment_id -- IMPORTANT + FROM schema.some_table + """, +) + +reader = DBReader( + connection=greenplum, + source="schema.view_with_gp_segment_id", +) +df = reader.run() +``` + +#### Using custom `partition_column` + +Sometimes table or view is lack of `gp_segment_id` column, but there is some column +with value range correlated with Greenplum segment distribution. + +In this case, custom column can be used instead: + +### Reading from view with custom partition_column + +```python +from onetl.connection import Greenplum +from onetl.db import DBReader + +greenplum = Greenplum(...) + +greenplum.execute( + """ + CREATE VIEW schema.view_with_partition_column AS + SELECT + id, + some_column, + part_column -- correlated to greenplum segment ID + FROM schema.some_table + """, +) + +reader = DBReader( + connection=greenplum, + source="schema.view_with_partition_column", + options=Greenplum.ReadOptions( + # parallelize data using specified column + partitionColumn="part_column", + # create 10 Spark tasks, each will read only part of table data + partitions=10, + ), +) +df = reader.run() +``` + +#### Reading `DISTRIBUTED REPLICATED` tables + +Replicated tables do not have `gp_segment_id` column at all, so you need to set `partition_column` to some column name +of type integer/bigint/smallint. + +### Parallel `JOIN` execution + +In case of using views which require some data motion between Greenplum segments, like `JOIN` queries, another approach should be used. + +Each Spark executor N will run the same query, so each of N query will start its own JOIN process, leading to really heavy load on Greenplum segments. +**This should be avoided**. + +Instead is recommended to run `JOIN` query on Greenplum side, save the result to an intermediate table, +and then read this table using `DBReader`: + +### Reading from view using intermediate table + +```python +from onetl.connection import Greenplum +from onetl.db import DBReader + +greenplum = Greenplum(...) + +greenplum.execute( + """ + CREATE UNLOGGED TABLE schema.intermediate_table AS + SELECT + id, + tbl1.col1, + tbl1.data, + tbl2.another_data + FROM + schema.table1 as tbl1 + JOIN + schema.table2 as tbl2 + ON + tbl1.col1 = tbl2.col2 + WHERE ... + """, +) + +reader = DBReader( + connection=greenplum, + source="schema.intermediate_table", +) +df = reader.run() + +# write dataframe somethere + +greenplum.execute( + """ + DROP TABLE schema.intermediate_table + """, +) +``` + +#### WARNING +**NEVER** do that: + +```python +df1 = DBReader(connection=greenplum, target="public.table1", ...).run() +df2 = DBReader(connection=greenplum, target="public.table2", ...).run() + +joined_df = df1.join(df2, on="col") +``` + +This will lead to sending all the data from both `table1` and `table2` to Spark executor memory, and then `JOIN` +will be performed on Spark side, not inside Greenplum. This is **VERY** inefficient. + +#### `TEMPORARY` tables notice + +Someone could think that writing data from view or result of `JOIN` to `TEMPORARY` table, +and then passing it to `DBReader`, is an efficient way to read data from Greenplum. This is because temp tables are not generating WAL files, +and are automatically deleted after finishing the transaction. + +That will **NOT** work. Each Spark executor establishes its own connection to Greenplum. +And each connection starts its own transaction which means that every executor will read empty temporary table. + +You should use [UNLOGGED](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-sql_commands-CREATE_TABLE.html) tables +to write data to intermediate table without generating WAL logs. + +## Options + +### *pydantic model* onetl.connection.db_connection.greenplum.options.GreenplumReadOptions + +VMware’s Greenplum Spark connector reading options. + +#### WARNING +Some options, like `url`, `dbtable`, `server.*`, `pool.*`, +etc are populated from connection attributes, and cannot be overridden by the user in `ReadOptions` to avoid issues. + +### Examples + +#### NOTE +You can pass any value +[supported by connector](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/read_from_gpdb.html), +even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! + +The set of supported options depends on connector version. + +```python +from onetl.connection import Greenplum + +options = Greenplum.ReadOptions( + partitionColumn="reg_id", + partitions=10, +) +``` + + + +#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* + +Column used to parallelize reading from a table. + +#### WARNING +You should not change this option, unless you know what you’re doing. + +It’s preferable to use default values to read data parallel by number of segments in Greenplum cluster. + +Possible values: +: * `None` (default): + : Spark generates N jobs (where N == number of segments in Greenplum cluster), + each job is reading only data from a specific segment + (filtering data by `gp_segment_id` column). +
+ This is very effective way to fetch the data from a cluster. + * table column + : Allocate each executor a range of values from a specific column. +
+ Spark generates for each executor an SQL query: +
+ Executor 1: + ```sql + SELECT ... FROM table + WHERE (partition_column >= lowerBound + OR partition_column IS NULL) + AND partition_column < (lower_bound + stride) + ``` +
+ Executor 2: + ```sql + SELECT ... FROM table + WHERE partition_column >= (lower_bound + stride) + AND partition_column < (lower_bound + 2 * stride) + ``` +
+ … +
+ Executor N: + ```sql + SELECT ... FROM table + WHERE partition_column >= (lower_bound + (N-1) * stride) + AND partition_column <= upper_bound + ``` +
+ Where `stride=(upper_bound - lower_bound) / num_partitions`, + `lower_bound=MIN(partition_column)`, `upper_bound=MAX(partition_column)`. +
+ #### NOTE + Column type must be numeric. Other types are not supported. +
+ #### NOTE + [`num_partitions`](#onetl.connection.db_connection.greenplum.options.GreenplumReadOptions.num_partitions) is used just to + calculate the partition stride, **NOT** for filtering the rows in table. + So all rows in the table will be returned (unlike *Incremental* [Read Strategies](../../../strategy/index.md#strategy)). +
+ #### NOTE + All queries are executed in parallel. To execute them sequentially, use *Batch* [Read Strategies](../../../strategy/index.md#strategy). + +#### WARNING +Both options [`partition_column`](#onetl.connection.db_connection.greenplum.options.GreenplumReadOptions.partition_column) and [`num_partitions`](#onetl.connection.db_connection.greenplum.options.GreenplumReadOptions.num_partitions) should have a value, +or both should be `None` + +### Examples + +Read data in 10 parallel jobs by range of values in `id_column` column: + +```python +Greenplum.ReadOptions( + partitionColumn="id_column", + partitions=10, +) +``` + + + +#### *field* num_partitions *: int | None* *= None* *(alias 'partitions')* + +Number of jobs created by Spark to read the table content in parallel. + +See documentation for [`partition_column`](#onetl.connection.db_connection.greenplum.options.GreenplumReadOptions.partition_column) for more details + +#### WARNING +By default connector uses number of segments in the Greenplum cluster. +You should not change this option, unless you know what you’re doing + +#### WARNING +Both options [`partition_column`](#onetl.connection.db_connection.greenplum.options.GreenplumReadOptions.partition_column) and [`num_partitions`](#onetl.connection.db_connection.greenplum.options.GreenplumReadOptions.num_partitions) should have a value, +or both should be `None` + + diff --git a/mddocs/connection/db_connection/greenplum/types.md b/mddocs/connection/db_connection/greenplum/types.md new file mode 100644 index 000000000..6a6c0effb --- /dev/null +++ b/mddocs/connection/db_connection/greenplum/types.md @@ -0,0 +1,318 @@ + + +# Greenplum <-> Spark type mapping + +#### NOTE +The results below are valid for Spark 3.2.4, and may differ on other Spark versions. + +## Type detection & casting + +Spark’s DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from Greenplum + +This is how Greenplum connector performs this: + +* Execute query `SELECT * FROM table LIMIT 0` [1](#id2). +* For each column in query result get column name and Greenplum type. +* Find corresponding `Greenplum type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* Use Spark column projection and predicate pushdown features to build a final query. +* Create DataFrame from generated query with inferred schema. + +* **[1]** Yes, **all columns of a table**, not just selected ones. This means that if source table **contains** columns with unsupported type, the entire table cannot be read. + +### Writing to some existing Greenplum table + +This is how Greenplum connector performs this: + +* Get names of columns in DataFrame. +* Perform `SELECT * FROM table LIMIT 0` query. +* For each column in query result get column name and Greenplum type. +* Match table columns with DataFrame columns (by name, case insensitive). + If some column is present only in target table, but not in DataFrame (like `DEFAULT` or `SERIAL` column), and vice versa, raise an exception. + See [Explicit type cast](). +* Find corresponding `Spark type` → `Greenplumtype (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* If `Greenplumtype (write)` match `Greenplum type (read)`, no additional casts will be performed, DataFrame column will be written to Greenplum as is. +* If `Greenplumtype (write)` does not match `Greenplum type (read)`, DataFrame column will be casted to target column type **on Greenplum side**. For example, you can write column with text data to `json` column which Greenplum connector currently does not support. + +### Create new table using Spark + +#### WARNING +ABSOLUTELY NOT RECOMMENDED! + +This is how Greenplum connector performs this: + +* Find corresponding `Spark type` → `Greenplum type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* Generate DDL for creating table in Greenplum, like `CREATE TABLE (col1 ...)`, and run it. +* Write DataFrame to created table as is. + +More details [can be found here](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/write_to_gpdb.html). + +But Greenplum connector support only limited number of types and almost no custom clauses (like `PARTITION BY`). +So instead of relying on Spark to create tables: + +### See example + +```python +writer = DBWriter( + connection=greenplum, + target="public.table", + options=Greenplum.WriteOptions( + if_exists="append", + # by default distribution is random + distributedBy="id", + # partitionBy is not supported + ), +) +writer.run(df) +``` + +Always prefer creating table with desired DDL **BEFORE WRITING DATA**: + +### See example + +```python +greenplum.execute( + """ + CREATE TABLE public.table ( + id int32, + business_dt timestamp(6), + value json + ) + PARTITION BY RANGE (business_dt) + DISTRIBUTED BY id + """, +) + +writer = DBWriter( + connection=greenplum, + target="public.table", + options=Greenplum.WriteOptions(if_exists="append"), +) +writer.run(df) +``` + +See Greenplum [CREATE TABLE](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-sql_commands-CREATE_TABLE.html) documentation. + +## Supported types + +See: +: * [official connector documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/reference-datatype_mapping.html) + * [list of Greenplum types](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-data_types.html) + +### Numeric types + +| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | +|-----------------------------|----------------------------------|-----------------------------|---------------------------| +| `decimal` | `DecimalType(P=38, S=18)` | `decimal(P=38, S=18)` | `decimal` (unbounded) | +| `decimal(P=0..38)` | `DecimalType(P=0..38, S=0)` | `decimal(P=0..38, S=0)` | | +| `decimal(P=0..38, S=0..38)` | `DecimalType(P=0..38, S=0..38)` | `decimal(P=0..38, S=0..38)` | | +| `decimal(P=39.., S=0..)` | unsupported [2](#id4) | | | +| `real` | `FloatType()` | `real` | `real` | +| `double precision` | `DoubleType()` | `double precision` | `double precision` | +| `-` | `ByteType()` | unsupported | unsupported | +| `smallint` | `ShortType()` | `smallint` | `smallint` | +| `integer` | `IntegerType()` | `integer` | `integer` | +| `bigint` | `LongType()` | `bigint` | `bigint` | +| `money` | unsupported | | | +| `int4range` | | | | +| `int8range` | | | | +| `numrange` | | | | +| `int2vector` | | | | +* **[2]** Greenplum support decimal types with unlimited precision. But Spark’s `DecimalType(P, S)` supports maximum `P=38` (128 bit). It is impossible to read, write or operate with values of larger precision, this leads to an exception. + +### Temporal types + +| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | +|----------------------------------|----------------------------------------------------------------|-------------------------|---------------------------| +| `date` | `DateType()` | `date` | `date` | +| `time` | `TimestampType()`,
time format quirks [3](#id6) | `timestamp` | `timestamp` | +| `time(0..6)` | | | | +| `time with time zone` | | | | +| `time(0..6) with time zone` | | | | +| `timestamp` | `TimestampType()` | `timestamp` | `timestamp` | +| `timestamp(0..6)` | | | | +| `timestamp with time zone` | | | | +| `timestamp(0..6) with time zone` | | | | +| `interval` or any precision | unsupported | | | +| `daterange` | | | | +| `tsrange` | | | | +| `tstzrange` | | | | + +#### WARNING +Note that types in Greenplum and Spark have different value ranges: + +| Greenplum type | Min value | Max value | Spark type | Min value | Max value | +|------------------|-------------------------------|--------------------------------|-------------------|------------------------------|------------------------------| +| `date` | `-4713-01-01` | `5874897-01-01` | `DateType()` | `0001-01-01` | `9999-12-31` | +| `timestamp` | `-4713-01-01 00:00:00.000000` | `294276-12-31 23:59:59.999999` | `TimestampType()` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | +| `time` | `00:00:00.000000` | `24:00:00.000000` | | | | + +So not all of values can be read from Greenplum to Spark. + +References: +: * [Greenplum types documentation](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-data_types.html) + * [Spark DateType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/DateType.html) + * [Spark TimestampType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/TimestampType.html) + +* **[3]** `time` type is the same as `timestamp` with date `1970-01-01`. So instead of reading data from Postgres like `23:59:59` it is actually read `1970-01-01 23:59:59`, and vice versa. + +### String types + +| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | +|---------------------------|----------------|-------------------------|---------------------------| +| `character` | `StringType()` | `text` | `text` | +| `character(N)` | | | | +| `character varying` | | | | +| `character varying(N)` | | | | +| `text` | | | | +| `xml` | | | | +| `CREATE TYPE ... AS ENUM` | | | | +| `json` | unsupported | | | +| `jsonb` | | | | + +### Binary types + +| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | +|-------------------------|----------------------------------|-------------------------|---------------------------| +| `boolean` | `BooleanType()` | `boolean` | `boolean` | +| `bit` | unsupported | | | +| `bit(N)` | | | | +| `bit varying` | | | | +| `bit varying(N)` | | | | +| `bytea` | unsupported [4](#id8) | | | +| `-` | `BinaryType()` | `bytea` | `bytea` | +* **[4]** Yes, that’s weird. + +### Struct types + +| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | +|------------------------------|----------------|-------------------------|---------------------------| +| `T[]` | unsupported | | | +| `-` | `ArrayType()` | unsupported | | +| `CREATE TYPE sometype (...)` | `StringType()` | `text` | `text` | +| `-` | `StructType()` | unsupported | | +| `-` | `MapType()` | | | + +## Unsupported types + +Columns of these types cannot be read/written by Spark: +: * `cidr` + * `inet` + * `macaddr` + * `macaddr8` + * `circle` + * `box` + * `line` + * `lseg` + * `path` + * `point` + * `polygon` + * `tsvector` + * `tsquery` + * `uuid` + +The is a way to avoid this - just cast unsupported types to `text`. But the way this can be done is not a straightforward. + +## Explicit type cast + +### `DBReader` + +Direct casting of Greenplum types is not supported by DBReader due to the connector’s implementation specifics. + +```python +reader = DBReader( + connection=greenplum, + # will fail + columns=["CAST(unsupported_column AS text)"], +) +``` + +But there is a workaround - create a view with casting unsupported column to text (or any other supported type). +For example, you can use [to_json](https://www.postgresql.org/docs/current/functions-json.html) Postgres function to convert column of any type to string representation and then parse this column on Spark side using [`JSON.parse_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.parse_column) method. + +```python +from pyspark.sql.types import ArrayType, IntegerType + +from onetl.connection import Greenplum +from onetl.db import DBReader +from onetl.file.format import JSON + +greenplum = Greenplum(...) + +greenplum.execute( + """ + CREATE VIEW schema.view_with_json_column AS + SELECT + id, + supported_column, + to_json(array_column) array_column_as_json, + gp_segment_id -- ! important ! + FROM + schema.table_with_unsupported_columns + """, +) + +# create dataframe using this view +reader = DBReader( + connection=greenplum, + source="schema.view_with_json_column", +) +df = reader.run() + +# Define the schema for the JSON data +json_scheme = ArrayType(IntegerType()) + +df = df.select( + df.id, + df.supported_column, + JSON().parse_column(df.array_column_as_json, json_scheme).alias("array_column"), +) +``` + +### `DBWriter` + +To write data to a `text` or `json` column in a Greenplum table, use [`JSON.serialize_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.serialize_column) method. + +```python +from onetl.connection import Greenplum +from onetl.db import DBWriter +from onetl.file.format import JSON + +greenplum = Greenplum(...) + +greenplum.execute( + """ + CREATE TABLE schema.target_table ( + id int, + supported_column timestamp, + array_column_as_json jsonb, -- or text + ) + DISTRIBUTED BY id + """, +) + +write_df = df.select( + df.id, + df.supported_column, + JSON().serialize_column(df.array_column).alias("array_column_json"), +) + +writer = DBWriter( + connection=greenplum, + target="schema.target_table", +) +writer.run(write_df) +``` + +Then you can parse this column on Greenplum side: + +```sql +SELECT + id, + supported_column, + -- access first item of an array + array_column_as_json->0 +FROM + schema.target_table +``` diff --git a/mddocs/connection/db_connection/greenplum/write.md b/mddocs/connection/db_connection/greenplum/write.md new file mode 100644 index 000000000..0815e0760 --- /dev/null +++ b/mddocs/connection/db_connection/greenplum/write.md @@ -0,0 +1,141 @@ + + +# Writing to Greenplum using `DBWriter` + +For writing data to Greenplum, use [`DBWriter`](../../../db/db_writer.md#onetl.db.db_writer.db_writer.DBWriter) +with [`GreenplumWriteOptions`](#onetl.connection.db_connection.greenplum.options.GreenplumWriteOptions). + +#### WARNING +Please take into account [Greenplum <-> Spark type mapping](types.md#greenplum-types). + +#### WARNING +It is always recommended to create table explicitly using [Greenplum.execute](execute.md#greenplum-execute) +instead of relying on Spark’s table DDL generation. + +This is because Spark’s DDL generator can create columns with different types than it is expected. + +## Examples + +```python +from onetl.connection import Greenplum +from onetl.db import DBWriter + +greenplum = Greenplum(...) + +df = ... # data is here + +writer = DBWriter( + connection=greenplum, + target="schema.table", + options=Greenplum.WriteOptions( + if_exists="append", + # by default distribution is random + distributedBy="id", + # partitionBy is not supported + ), +) + +writer.run(df) +``` + +## Interaction schema + +High-level schema is described in [Prerequisites](prerequisites.md#greenplum-prerequisites). You can find detailed interaction schema below. + +### Spark <-> Greenplum interaction during DBWriter.run() + +## Options + +### *pydantic model* onetl.connection.db_connection.greenplum.options.GreenplumWriteOptions + +VMware’s Greenplum Spark connector writing options. + +#### WARNING +Some options, like `url`, `dbtable`, `server.*`, `pool.*`, etc +are populated from connection attributes, and cannot be overridden by the user in `WriteOptions` to avoid issues. + +### Examples + +#### NOTE +You can pass any value +[supported by connector](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/write_to_gpdb.html), +even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! + +The set of supported options depends on connector version. + +```python +from onetl.connection import Greenplum + +options = Greenplum.WriteOptions( + if_exists="append", + truncate="false", + distributedBy="mycolumn", +) +``` + + + +#### *field* if_exists *: GreenplumTableExistBehavior* *= GreenplumTableExistBehavior.APPEND* *(alias 'mode')* + +Behavior of writing data into existing table. + +Possible values: +: * `append` (default) + : Adds new rows into existing table. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`distributedBy` and others). + * Table exists + : Data is appended to a table. Table has the same DDL as before writing data. +
+ #### WARNING + This mode does not check whether table already contains + rows from dataframe, so duplicated rows can be created. +
+ Also Spark does not support passing custom options to + insert statement, like `ON CONFLICT`, so don’t try to + implement deduplication using unique indexes or constraints. +
+ Instead, write to staging table and perform deduplication + using `execute` method. + * `replace_entire_table` + : **Table is dropped and then created**. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`distributedBy` and others). + * Table exists + : Table content is replaced with dataframe content. +
+ After writing completed, target table could either have the same DDL as + before writing data (`truncate=True`), or can be recreated (`truncate=False`). + * `ignore` + : Ignores the write operation if the table already exists. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`distributedBy` and others). + * Table exists + : The write operation is ignored, and no data is written to the table. + * `error` + : Raises an error if the table already exists. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`distributedBy` and others). + * Table exists + : An error is raised, and no data is written to the table. + +#### Versionchanged +Changed in version 0.9.0: Renamed `mode` → `if_exists` + + diff --git a/mddocs/connection/db_connection/hive/connection.md b/mddocs/connection/db_connection/hive/connection.md new file mode 100644 index 000000000..7a8470de1 --- /dev/null +++ b/mddocs/connection/db_connection/hive/connection.md @@ -0,0 +1,118 @@ + + +# Hive Connection + +### *class* onetl.connection.db_connection.hive.connection.Hive(\*, spark: SparkSession, cluster: Cluster) + +Spark connection with Hive MetaStore support. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### SEE ALSO +Before using this connector please take into account [Prerequisites](prerequisites.md#hive-prerequisites) + +#### Versionadded +Added in version 0.1.0. + +* **Parameters:** + **cluster** + : Cluster name. Used for HWM and lineage. +
+ #### Versionadded + Added in version 0.7.0. + + **spark** + : Spark session with Hive metastore support enabled + +### Examples + +Create Hive connection with Kerberos auth + +Execute `kinit` consome command before creating Spark Session + +```bash +$ kinit -kt /path/to/keytab user +``` + +```python +from onetl.connection import Hive +from pyspark.sql import SparkSession + +# Create Spark session +# Use names "spark.yarn.access.hadoopFileSystems", "spark.yarn.principal" +# and "spark.yarn.keytab" for Spark 2 + +spark = ( + SparkSession.builder.appName("spark-app-name") + .option("spark.kerberos.access.hadoopFileSystems", "hdfs://cluster.name.node:8020") + .option("spark.kerberos.principal", "user") + .option("spark.kerberos.keytab", "/path/to/keytab") + .enableHiveSupport() + .getOrCreate() +) + +# Create connection +hive = Hive(cluster="rnd-dwh", spark=spark).check() +``` + +Create Hive connection with anonymous auth + +```py +from onetl.connection import Hive +from pyspark.sql import SparkSession + +# Create Spark session +spark = SparkSession.builder.appName("spark-app-name").enableHiveSupport().getOrCreate() + +# Create connection +hive = Hive(cluster="rnd-dwh", spark=spark).check() +``` + + + +#### *classmethod* get_current(spark: SparkSession) + +Create connection for current cluster. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### NOTE +Can be used only if there are some hooks bound to +[`Slots.get_current_cluster`](slots.md#onetl.connection.db_connection.hive.slots.HiveSlots.get_current_cluster) slot. + +#### Versionadded +Added in version 0.7.0. + +* **Parameters:** + **spark** + : Spark session + +### Examples + +```python +from onetl.connection import Hive +from pyspark.sql import SparkSession + +spark = SparkSession.builder.appName("spark-app-name").enableHiveSupport().getOrCreate() + +# injecting current cluster name via hooks mechanism +hive = Hive.get_current(spark=spark) +``` + + + +#### check() + +Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If not, an exception will be raised. + +* **Returns:** + Connection itself +* **Raises:** + RuntimeError + : If the connection is not available + +### Examples + +```python +connection.check() +``` + + diff --git a/mddocs/connection/db_connection/hive/execute.md b/mddocs/connection/db_connection/hive/execute.md new file mode 100644 index 000000000..c1d77874c --- /dev/null +++ b/mddocs/connection/db_connection/hive/execute.md @@ -0,0 +1,57 @@ + + +# Executing statements in Hive + +Use `Hive.execute(...)` to execute DDL and DML operations. + +## Syntax support + +This method supports **any** query syntax supported by Hive, like: + +* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +* ✅︎ `LOAD DATA ...`, and so on +* ✅︎ `ALTER ...` +* ✅︎ `INSERT INTO ... SELECT ...`, and so on +* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, and so on +* ✅︎ `MSCK REPAIR TABLE ...`, and so on +* ✅︎ other statements not mentioned here +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### WARNING +Actually, query should be written using [SparkSQL](https://spark.apache.org/docs/latest/sql-ref-syntax.html#ddl-statements) syntax, not HiveQL. + +## Examples + +```python +from onetl.connection import Hive + +hive = Hive(...) + +hive.execute("DROP TABLE schema.table") +hive.execute( + """ + CREATE TABLE schema.table ( + id NUMBER, + key VARCHAR, + value DOUBLE + ) + PARTITION BY (business_date DATE) + STORED AS orc + """ +) +``` + +### Details + +#### Hive.execute(statement: str) → None + +Execute DDL or DML statement. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.2.0. + +* **Parameters:** + **statement** + : Statement to be executed. + + diff --git a/mddocs/connection/db_connection/hive/index.md b/mddocs/connection/db_connection/hive/index.md new file mode 100644 index 000000000..bc551b745 --- /dev/null +++ b/mddocs/connection/db_connection/hive/index.md @@ -0,0 +1,19 @@ + + +# Hive + +# Connection + +* [Prerequisites](prerequisites.md) +* [Hive Connection](connection.md) + +# Operations + +* [Reading from Hive using `DBReader`](read.md) +* [Reading from Hive using `Hive.sql`](sql.md) +* [Writing to Hive using `DBWriter`](write.md) +* [Executing statements in Hive](execute.md) + +# For developers + +* [Hive Slots](slots.md) diff --git a/mddocs/connection/db_connection/hive/prerequisites.md b/mddocs/connection/db_connection/hive/prerequisites.md new file mode 100644 index 000000000..aa0fb49c7 --- /dev/null +++ b/mddocs/connection/db_connection/hive/prerequisites.md @@ -0,0 +1,128 @@ + + +# Prerequisites + +#### NOTE +onETL’s Hive connection is actually SparkSession with access to [Hive Thrift Metastore](https://docs.cloudera.com/cdw-runtime/1.5.0/hive-hms-overview/topics/hive-hms-introduction.html) +and HDFS/S3. +All data motion is made using Spark. Hive Metastore is used only to store tables and partitions metadata. + +This connector does **NOT** require Hive server. It also does **NOT** use Hive JDBC connector. + +## Version Compatibility + +* Hive Metastore version: + : * Officially declared: 0.12 - 3.1.3 (may require to add proper .jar file explicitly) + * Actually tested: 1.2.100, 2.3.10, 3.1.3 +* Spark versions: 2.3.x - 3.5.x +* Java versions: 8 - 20 + +See [official documentation](https://spark.apache.org/docs/latest/sql-data-sources-hive-tables.html). + +## Installing PySpark + +To use Hive connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Connecting to Hive Metastore + +#### NOTE +If you’re using managed Hadoop cluster, skip this step, as all Spark configs are should already present on the host. + +Create `$SPARK_CONF_DIR/hive-site.xml` with Hive Metastore URL: + +```xml + + + + + hive.metastore.uris + thrift://metastore.host.name:9083 + + +``` + +Create `$SPARK_CONF_DIR/core-site.xml` with warehouse location ,e.g. HDFS IPC port of Hadoop namenode, or S3 bucket address & credentials: + +HDFS + +```xml + + + + + fs.defaultFS + hdfs://myhadoopcluster:9820 + + +``` + +S3 + +```xml + + + + !-- See https://hadoop.apache.org/docs/current/hadoop-aws/tools/hadoop-aws/index.html#General_S3A_Client_configuration + + fs.defaultFS + s3a://mys3bucket/ + + + fs.s3a.bucket.mybucket.endpoint + http://s3.somain + + + fs.s3a.bucket.mybucket.connection.ssl.enabled + false + + + fs.s3a.bucket.mybucket.path.style.access + true + + + fs.s3a.bucket.mybucket.aws.credentials.provider + org.apache.hadoop.fs.s3a.SimpleAWSCredentialsProvider + + + fs.s3a.bucket.mybucket.access.key + some-user + + + fs.s3a.bucket.mybucket.secret.key + mysecrettoken + + +``` + +## Using Kerberos + +Some of Hadoop managed clusters use Kerberos authentication. In this case, you should call [kinit](https://web.mit.edu/kerberos/krb5-1.12/doc/user/user_commands/kinit.html) command +**BEFORE** starting Spark session to generate Kerberos ticket. See [Kerberos support](../../../install/kerberos.md#install-kerberos). + +Sometimes it is also required to pass keytab file to Spark config, allowing Spark executors to generate own Kerberos tickets: + +Spark 3 + +```python +SparkSession.builder + .option("spark.kerberos.access.hadoopFileSystems", "hdfs://namenode1.domain.com:9820,hdfs://namenode2.domain.com:9820") + .option("spark.kerberos.principal", "user") + .option("spark.kerberos.keytab", "/path/to/keytab") + .gerOrCreate() +``` + +Spark 2 + +```python +SparkSession.builder + .option("spark.yarn.access.hadoopFileSystems", "hdfs://namenode1.domain.com:9820,hdfs://namenode2.domain.com:9820") + .option("spark.yarn.principal", "user") + .option("spark.yarn.keytab", "/path/to/keytab") + .gerOrCreate() +``` + +See [Spark security documentation](https://spark.apache.org/docs/latest/security.html#kerberos) +for more details. diff --git a/mddocs/connection/db_connection/hive/read.md b/mddocs/connection/db_connection/hive/read.md new file mode 100644 index 000000000..cbe3469d9 --- /dev/null +++ b/mddocs/connection/db_connection/hive/read.md @@ -0,0 +1,92 @@ + + +# Reading from Hive using `DBReader` + +[`DBReader`](../../../db/db_reader.md#onetl.db.db_reader.db_reader.DBReader) supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, +but does not support custom queries, like `JOIN`. + +## Supported DBReader features + +* ✅︎ `columns` +* ✅︎ `where` +* ✅︎ `hwm`, supported strategies: +* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) +* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) +* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) +* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) +* ❌ `hint` (is not supported by Hive) +* ❌ `df_schema` +* ❌ `options` (only Spark config params are used) + +#### WARNING +Actually, `columns`, `where` and `hwm.expression` should be written using [SparkSQL](https://spark.apache.org/docs/latest/sql-ref-syntax.html#data-retrieval-statements) syntax, +not HiveQL. + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Hive +from onetl.db import DBReader + +hive = Hive(...) + +reader = DBReader( + connection=hive, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Hive +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +hive = Hive(...) + +reader = DBReader( + connection=hive, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="hive_hwm", expression="updated_dt"), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Use column-based write formats + +Prefer these write formats: +: * [ORC](https://spark.apache.org/docs/latest/sql-data-sources-orc.html) + * [Parquet](https://spark.apache.org/docs/latest/sql-data-sources-parquet.html) + * [Iceberg](https://iceberg.apache.org/spark-quickstart/) + * [Hudi](https://hudi.apache.org/docs/quick-start-guide/) + * [Delta](https://docs.delta.io/latest/quick-start.html#set-up-apache-spark-with-delta-lake) + +For colum-based write formats, each file contains separated sections there column data is stored. The file footer contains +location of each column section/group. Spark can use this information to load only sections required by specific query, e.g. only selected columns, +to drastically speed up the query. + +Another advantage is high compression ratio, e.g. 10x-100x in comparison to JSON or CSV. + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. +This drastically reduces the amount of data read by Spark, **if column-based file formats are used**. + +### Use partition columns in `where` clause + +Queries should include `WHERE` clause with filters on Hive partitioning columns. +This allows Spark to read only small set of files (*partition pruning*) instead of scanning the entire table, so this drastically increases performance. + +Supported operators are: `=`, `>`, `<` and `BETWEEN`, and only against some **static** value. diff --git a/mddocs/connection/db_connection/hive/slots.md b/mddocs/connection/db_connection/hive/slots.md new file mode 100644 index 000000000..75bad02ff --- /dev/null +++ b/mddocs/connection/db_connection/hive/slots.md @@ -0,0 +1,105 @@ + + +# Hive Slots + +### *class* onetl.connection.db_connection.hive.slots.HiveSlots + +[Slots](../../../hooks/slot.md#slot-decorator) that could be implemented by third-party plugins. + +#### Versionadded +Added in version 0.7.0. + + + +#### *static* normalize_cluster_name(cluster: str) → str | None + +Normalize cluster name passed into Hive constructor. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If hooks didn’t return anything, cluster name is left intact. + +#### Versionadded +Added in version 0.7.0. + +* **Parameters:** + **cluster** + : Cluster name (raw) +* **Returns:** + str | None + : Normalized cluster name. +
+ If hook cannot be applied to a specific cluster, it should return `None`. + +### Examples + +```python +from onetl.connection import Hive +from onetl.hooks import hook + +@Hive.Slots.normalize_cluster_name.bind +@hook +def normalize_cluster_name(cluster: str) -> str: + return cluster.lower() +``` + + + +#### *static* get_known_clusters() → set[str] | None + +Return collection of known clusters. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Cluster passed into Hive constructor should be present in this list. +If hooks didn’t return anything, no validation will be performed. + +#### Versionadded +Added in version 0.7.0. + +* **Returns:** + set[str] | None + : Collection of cluster names (normalized). +
+ If hook cannot be applied, it should return `None`. + +### Examples + +```python +from onetl.connection import Hive +from onetl.hooks import hook + +@Hive.Slots.get_known_clusters.bind +@hook +def get_known_clusters() -> str[str]: + return {"rnd-dwh", "rnd-prod"} +``` + + + +#### *static* get_current_cluster() → str | None + +Get current cluster name. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Used in `check` method to verify that connection is created only from the same cluster. +If hooks didn’t return anything, no validation will be performed. + +#### Versionadded +Added in version 0.7.0. + +* **Returns:** + str | None + : Current cluster name (normalized). +
+ If hook cannot be applied, it should return `None`. + +### Examples + +```python +from onetl.connection import Hive +from onetl.hooks import hook + +@Hive.Slots.get_current_cluster.bind +@hook +def get_current_cluster() -> str: + # some magic here + return "rnd-dwh" +``` + + diff --git a/mddocs/connection/db_connection/hive/sql.md b/mddocs/connection/db_connection/hive/sql.md new file mode 100644 index 000000000..9e6d15f1c --- /dev/null +++ b/mddocs/connection/db_connection/hive/sql.md @@ -0,0 +1,86 @@ + + +# Reading from Hive using `Hive.sql` + +`Hive.sql` allows passing custom SQL query, but does not support incremental strategies. + +## Syntax support + +Only queries with the following syntax are supported: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### WARNING +Actually, query should be written using [SparkSQL](https://spark.apache.org/docs/latest/sql-ref-syntax.html#data-retrieval-statements) syntax, not HiveQL. + +## Examples + +```python +from onetl.connection import Hive + +hive = Hive(...) +df = hive.sql( + """ + SELECT + id, + key, + CAST(value AS text) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """ +) +``` + +## Recommendations + +### Use column-based write formats + +Prefer these write formats: +: * [ORC](https://spark.apache.org/docs/latest/sql-data-sources-orc.html) + * [Parquet](https://spark.apache.org/docs/latest/sql-data-sources-parquet.html) + * [Iceberg](https://iceberg.apache.org/spark-quickstart/) + * [Hudi](https://hudi.apache.org/docs/quick-start-guide/) + * [Delta](https://docs.delta.io/latest/quick-start.html#set-up-apache-spark-with-delta-lake) + +For colum-based write formats, each file contains separated sections there column data is stored. The file footer contains +location of each column section/group. Spark can use this information to load only sections required by specific query, e.g. only selected columns, +to drastically speed up the query. + +Another advantage is high compression ratio, e.g. 10x-100x in comparison to JSON or CSV. + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This drastically reduces the amount of data read by Spark, **if column-based file formats are used**. + +### Use partition columns in `where` clause + +Queries should include `WHERE` clause with filters on Hive partitioning columns. +This allows Spark to read only small set of files (*partition pruning*) instead of scanning the entire table, so this drastically increases performance. + +Supported operators are: `=`, `>`, `<` and `BETWEEN`, and only against some **static** value. + +## Details + +#### Hive.sql(query: str) → DataFrame + +Lazily execute SELECT statement and return DataFrame. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Same as `spark.sql(query)`. + +#### Versionadded +Added in version 0.2.0. + +* **Parameters:** + **query** + : SQL query to be executed. +* **Returns:** + **df** + : Spark dataframe + + diff --git a/mddocs/connection/db_connection/hive/write.md b/mddocs/connection/db_connection/hive/write.md new file mode 100644 index 000000000..1c61846df --- /dev/null +++ b/mddocs/connection/db_connection/hive/write.md @@ -0,0 +1,394 @@ + + +# Writing to Hive using `DBWriter` + +For writing data to Hive, use [`DBWriter`](../../../db/db_writer.md#onetl.db.db_writer.db_writer.DBWriter). + +## Examples + +```python +from onetl.connection import Hive +from onetl.db import DBWriter + +hive = Hive(...) + +df = ... # data is here + +# Create dataframe with specific number of Spark partitions. +# Use the Hive partitioning columns to group the data. Create max 20 files per Hive partition. +# Also sort the data by column which most data is correlated with (e.g. user_id), reducing files size. + +num_files_per_partition = 20 +partition_columns = ["country", "business_date"] +sort_columns = ["user_id"] +write_df = df.repartition( + num_files_per_partition, + *partition_columns, + *sort_columns, +).sortWithinPartitions(*partition_columns, *sort_columns) + +writer = DBWriter( + connection=hive, + target="schema.table", + options=Hive.WriteOptions( + if_exists="append", + # Hive partitioning columns. + partitionBy=partition_columns, + ), +) + +writer.run(write_df) +``` + +## Recommendations + +### Use column-based write formats + +Prefer these write formats: +: * [ORC](https://spark.apache.org/docs/latest/sql-data-sources-orc.html) (**default**) + * [Parquet](https://spark.apache.org/docs/latest/sql-data-sources-parquet.html) + * [Iceberg](https://iceberg.apache.org/spark-quickstart/) + * [Hudi](https://hudi.apache.org/docs/quick-start-guide/) + * [Delta](https://docs.delta.io/latest/quick-start.html#set-up-apache-spark-with-delta-lake) + +#### WARNING +When using `DBWriter`, the default spark data format configured in `spark.sql.sources.default` is ignored, as `Hive.WriteOptions(format=...)` default value is explicitly set to `orc`. + +For column-based write formats, each file contains separated sections where column data is stored. The file footer contains +location of each column section/group. Spark can use this information to load only sections required by specific query, e.g. only selected columns, +to drastically speed up the query. + +Another advantage is high compression ratio, e.g. 10x-100x in comparison to JSON or CSV. + +### Use partitioning + +#### How does it work + +Hive support splitting data to partitions, which are different directories in filesystem with names like `some_col=value1/another_col=value2`. + +For example, dataframe with content like this: + +| country: string | business_date: date | user_id: int | bytes: long | +|-------------------|-----------------------|----------------|---------------| +| RU | 2024-01-01 | 1234 | 25325253525 | +| RU | 2024-01-01 | 2345 | 23234535243 | +| RU | 2024-01-02 | 1234 | 62346634564 | +| US | 2024-01-01 | 5678 | 4252345354 | +| US | 2024-01-02 | 5678 | 5474575745 | +| US | 2024-01-03 | 5678 | 3464574567 | + +With `partitionBy=["country", "business_dt"]` data will be stored as files in the following subfolders: +: * `/country=RU/business_date=2024-01-01/` + * `/country=RU/business_date=2024-01-02/` + * `/country=US/business_date=2024-01-01/` + * `/country=US/business_date=2024-01-02/` + * `/country=US/business_date=2024-01-03/` + +A separated subdirectory is created for each distinct combination of column values in the dataframe. + +Please do not confuse Spark dataframe partitions (a.k.a batches of data handled by Spark executors, usually in parallel) +and Hive partitioning (store data in different subdirectories). +Number of Spark dataframe partitions is correlated the number of files created in **each** Hive partition. +For example, Spark dataframe with 10 partitions and 5 distinct values of Hive partition columns will be saved as 5 subfolders with 10 files each = 50 files in total. +Without Hive partitioning, all the files are placed into one flat directory. + +#### But why? + +Queries which has `WHERE` clause with filters on Hive partitioning columns, like `WHERE country = 'RU' AND business_date='2024-01-01'`, will +read only files from this exact partitions, like `/country=RU/business_date=2024-01-01/`, and skip files from other partitions. + +This drastically increases performance and reduces the amount of memory used by Spark. +Consider using Hive partitioning in all tables. + +#### Which columns should I use? + +Usually Hive partitioning columns are based on event date or location, like `country: string`, `business_date: date`, `run_date: date` and so on. + +**Partition columns should contain data with low cardinality.** +Dates, small integers, strings with low number of possible values are OK. +But timestamp, float, decimals, longs (like user id), strings with lots oj unique values (like user name or email) should **NOT** be used as Hive partitioning columns. +Unlike some other databases, range and hash-based partitions are not supported. + +Partition column should be a part of a dataframe. If you want to partition values by date component of `business_dt: timestamp` column, +add a new column to dataframe like this: `df.withColumn("business_date", date(df.business_dt))`. + +### Use compression + +Using compression algorithms like `snappy`, `lz4` or `zstd` can reduce the size of files (up to 10x). + +### Prefer creating large files + +Storing millions of small files is not that HDFS and S3 are designed for. Minimal file size should be at least 10Mb, but usually it is like 128Mb+ or 256Mb+ (HDFS block size). +**NEVER** create files with few Kbytes in size. + +Number of files can be different in different cases. +On one hand, Spark Adaptive Query Execution (AQE) can merge small Spark dataframe partitions into one larger. +On the other hand, dataframes with skewed data can produce a larger number of files than expected. + +To create small amount of large files, you can reduce number of Spark dataframe partitions. +Use [df.repartition(N, columns…)](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.DataFrame.repartition.html) function, +like this: `df.repartition(20, "col1", "col2")`. +This creates new Spark dataframe with partitions using `hash(df.col1 + df.col2) mod 20` expression, avoiding data skew. + +Note: larger dataframe partitions requires more resources (CPU, RAM) on Spark executor. The exact number of partitions +should be determined empirically, as it depends on the amount of data and available resources. + +### Sort data before writing + +Dataframe with sorted content: + +| country: string | business_date: date | user_id: int | business_dt: timestamp | bytes: long | +|-------------------|-----------------------|----------------|--------------------------|---------------| +| RU | 2024-01-01 | 1234 | 2024-01-01T11:22:33.456 | 25325253525 | +| RU | 2024-01-01 | 1234 | 2024-01-01T12:23:44.567 | 25325253525 | +| RU | 2024-01-02 | 1234 | 2024-01-01T13:25:56.789 | 34335645635 | +| US | 2024-01-01 | 2345 | 2024-01-01T10:00:00.000 | 12341 | +| US | 2024-01-02 | 2345 | 2024-01-01T15:11:22.345 | 13435 | +| US | 2024-01-03 | 2345 | 2024-01-01T20:22:33.567 | 14564 | + +Has a much better compression rate than unsorted one, e.g. 2x or even higher: + +| country: string | business_date: date | user_id: int | business_dt: timestamp | bytes: long | +|-------------------|-----------------------|----------------|--------------------------|---------------| +| RU | 2024-01-01 | 1234 | 2024-01-01T11:22:33.456 | 25325253525 | +| RU | 2024-01-01 | 6345 | 2024-12-01T23:03:44.567 | 25365 | +| RU | 2024-01-02 | 5234 | 2024-07-01T06:10:56.789 | 45643456747 | +| US | 2024-01-01 | 4582 | 2024-04-01T17:59:00.000 | 362546475 | +| US | 2024-01-02 | 2345 | 2024-09-01T04:24:22.345 | 3235 | +| US | 2024-01-03 | 3575 | 2024-03-01T21:37:33.567 | 346345764 | + +Choosing columns to sort data by is really depends on the data. If data is correlated with some specific +column, like in example above the amount of traffic is correlated with both `user_id` and `timestamp`, +use `df.sortWithinPartitions("user_id", "timestamp")` before writing the data. + +If `df.repartition(N, repartition_columns...)` is used in combination with `df.sortWithinPartitions(sort_columns...)`, +then `sort_columns` should start with `repartition_columns` or be equal to it. + +## Options + +### *pydantic model* onetl.connection.db_connection.hive.options.HiveWriteOptions + +Hive source writing options. + +You can pass here key-value items which then will be converted to calls +of `pyspark.sql.readwriter.DataFrameWriter` methods. + +For example, `Hive.WriteOptions(if_exists="append", partitionBy="reg_id")` will +be converted to `df.write.mode("append").partitionBy("reg_id")` call, and so on. + +### Examples + +#### NOTE +You can pass any method name and its value +[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-load-save-functions.html), +even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! + +The set of supported options depends on Spark version used. + +```python +from onetl.connection import Hive + +options = Hive.WriteOptions( + if_exists="append", + partitionBy="reg_id", + customSparkOption="value", +) +``` + + + +#### *field* if_exists *: HiveTableExistBehavior* *= HiveTableExistBehavior.APPEND* *(alias 'mode')* + +Behavior of writing data into existing table. + +Possible values: +: * `append` (default) + : Appends data into existing partition/table, or create partition/table if it does not exist. +
+ Same as Spark’s `df.write.insertInto(table, overwrite=False)`. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user (`format`, `compression`, etc). + * Table exists, but not partitioned, [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by) is set + : Data is appended to a table. Table is still not partitioned (DDL is unchanged). + * Table exists and partitioned, but has different partitioning schema than [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by) + : Partition is created based on table’s `PARTITIONED BY (...)` options. + Explicit [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by) value is ignored. + * Table exists and partitioned according [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by), but partition is present only in dataframe + : Partition is created. + * Table exists and partitioned according [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by), partition is present in both dataframe and table + : Data is appended to existing partition. +
+ #### WARNING + This mode does not check whether table already contains + rows from dataframe, so duplicated rows can be created. +
+ To implement deduplication, write data to staging table first, + and then perform some deduplication logic using `sql`. + * Table exists and partitioned according [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by), but partition is present only in table, not dataframe + : Existing partition is left intact. + * `replace_overlapping_partitions` + : Overwrites data in the existing partition, or create partition/table if it does not exist. +
+ Same as Spark’s `df.write.insertInto(table, overwrite=True)` + + `spark.sql.sources.partitionOverwriteMode=dynamic`. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user (`format`, `compression`, etc). + * Table exists, but not partitioned, [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by) is set + : Data is **overwritten in all the table**. Table is still not partitioned (DDL is unchanged). + * Table exists and partitioned, but has different partitioning schema than [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by) + : Partition is created based on table’s `PARTITIONED BY (...)` options. + Explicit [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by) value is ignored. + * Table exists and partitioned according [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by), but partition is present only in dataframe + : Partition is created. + * Table exists and partitioned according [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by), partition is present in both dataframe and table + : Existing partition **replaced** with data from dataframe. + * Table exists and partitioned according [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by), but partition is present only in table, not dataframe + : Existing partition is left intact. + * `replace_entire_table` + : **Recreates table** (via `DROP + CREATE`), **deleting all existing data**. + **All existing partitions are dropped.** +
+ Same as Spark’s `df.write.saveAsTable(table, mode="overwrite")` (NOT `insertInto`)! +
+ #### WARNING + Table is recreated using options provided by user (`format`, `compression`, etc) + **instead of using original table options**. Be careful + * `ignore` + : Ignores the write operation if the table/partition already exists. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user (`format`, `compression`, etc). + * Table exists + : If the table exists, **no further action is taken**. This is true whether or not new partition + values are present and whether the partitioning scheme differs or not + * `error` + : Raises an error if the table/partition already exists. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user (`format`, `compression`, etc). + * Table exists + : If the table exists, **raises an error**. This is true whether or not new partition + values are present and whether the partitioning scheme differs or not + +#### NOTE +Unlike using pure Spark, config option `spark.sql.sources.partitionOverwriteMode` +does not affect behavior. + + + +#### *field* format *: str | [BaseWritableFileFormat](../../../file_df/file_formats/base.md#onetl.base.base_file_format.BaseWritableFileFormat)* *= 'orc'* + +Format of files which should be used for storing table data. + +### Examples + +- string format: `"orc"` (default), `"parquet"`, `"csv"` (NOT recommended). +- format class instance: `ORC(compression="snappy")`, `Parquet()`, `CSV(header=True, delimiter=",")`. + +```default +options = Hive.WriteOptions( + if_exists="append", + partitionBy="reg_id", + format="orc", +) + +# or using an ORC format class instance: + +from onetl.file.format import ORC + +options = Hive.WriteOptions( + if_exists="append", + partitionBy="reg_id", + format=ORC(compression="snappy"), +) +``` + +#### NOTE +It’s better to use column-based formats like `orc` or `parquet`, +not row-based (`csv`, `json`) + +#### WARNING +Used **only** while **creating new table**, or in case of `if_exists=replace_entire_table` + + + +#### *field* partition_by *: List[str] | str | None* *= None* *(alias 'partitionBy')* + +List of columns should be used for data partitioning. `None` means partitioning is disabled. + +Examples: `reg_id` or `["reg_id", "business_dt"]` + +#### WARNING +Used **only** while **creating new table**, or in case of `if_exists=replace_entire_table` + + + +#### *field* bucket_by *: Tuple[int, List[str] | str] | None* *= None* *(alias 'bucketBy')* + +Number of buckets plus bucketing columns. `None` means bucketing is disabled. + +Each bucket is created as a set of files with name containing result of calculation `hash(columns) mod num_buckets`. + +This allows to remove shuffle from queries containing `GROUP BY` or `JOIN` or using `=` / `IN` predicates +on specific columns. + +Examples: `(10, "user_id")`, `(10, ["user_id", "user_phone"])` + +#### NOTE +Bucketing should be used on columns containing a lot of unique values, +like `userId`. + +Columns like `date` should **NOT** be used for bucketing +because of too low number of unique values. + +#### WARNING +It is recommended to use this option **ONLY** if you have a large table +(hundreds of Gb or more), which is used mostly for JOINs with other tables, +and you’re inserting data using `if_exists=overwrite_partitions` or `if_exists=replace_entire_table`. + +Otherwise Spark will create a lot of small files +(one file for each bucket and each executor), drastically **decreasing** HDFS performance. + +#### WARNING +Used **only** while **creating new table**, or in case of `if_exists=replace_entire_table` + + + +#### *field* sort_by *: List[str] | str | None* *= None* *(alias 'sortBy')* + +Each file in a bucket will be sorted by these columns value. `None` means sorting is disabled. + +Examples: `user_id` or `["user_id", "user_phone"]` + +#### NOTE +Sorting columns should contain values which are used in `ORDER BY` clauses. + +#### WARNING +Could be used only with [`bucket_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.bucket_by) option + +#### WARNING +Used **only** while **creating new table**, or in case of `if_exists=replace_entire_table` + + + +#### *field* compression *: str | None* *= None* + +Compressing algorithm which should be used for compressing created files in HDFS. +`None` means compression is disabled. + +Examples: `snappy`, `zlib` + +#### WARNING +Used **only** while **creating new table**, or in case of `if_exists=replace_entire_table` + + diff --git a/mddocs/connection/db_connection/index.md b/mddocs/connection/db_connection/index.md new file mode 100644 index 000000000..cd8f76477 --- /dev/null +++ b/mddocs/connection/db_connection/index.md @@ -0,0 +1,16 @@ + + +# DB Connections + +# DB Connections + +* [Clickhouse](clickhouse/index.md) +* [Greenplum](greenplum/index.md) +* [Kafka](kafka/index.md) +* [Hive](hive/index.md) +* [MongoDB](mongodb/index.md) +* [MSSQL](mssql/index.md) +* [MySQL](mysql/index.md) +* [Oracle](oracle/index.md) +* [Postgres](postgres/index.md) +* [Teradata](teradata/index.md) diff --git a/mddocs/connection/db_connection/kafka/auth.md b/mddocs/connection/db_connection/kafka/auth.md new file mode 100644 index 000000000..0bf454521 --- /dev/null +++ b/mddocs/connection/db_connection/kafka/auth.md @@ -0,0 +1,37 @@ + + +# Kafka Auth + +### *class* onetl.connection.db_connection.kafka.kafka_auth.KafkaAuth + +Interface for Kafka connection Auth classes. + +#### Versionadded +Added in version 0.9.0. + + + +#### *abstract* get_options(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → dict + +Get options for Kafka connection + +* **Parameters:** + **kafka** + : Connection instance +* **Returns:** + dict: + : Kafka client options + + + +#### *abstract* cleanup(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → None + +This method is called while closing Kafka connection. + +Implement it to cleanup resources like temporary files. + +* **Parameters:** + **kafka** + : Connection instance + + diff --git a/mddocs/connection/db_connection/kafka/basic_auth.md b/mddocs/connection/db_connection/kafka/basic_auth.md new file mode 100644 index 000000000..586b07500 --- /dev/null +++ b/mddocs/connection/db_connection/kafka/basic_auth.md @@ -0,0 +1,60 @@ + + +# Kafka BasicAuth + +### *pydantic model* onetl.connection.db_connection.kafka.kafka_basic_auth.KafkaBasicAuth + +Connect to Kafka using `sasl.mechanism="PLAIN"`. + +For more details see [Kafka Documentation](https://kafka.apache.org/documentation/#security_sasl_plain). + +#### Versionadded +Added in version 0.9.0. + +### Examples + +Auth in Kafka with user and password: + +```python +from onetl.connection import Kafka + +auth = Kafka.BasicAuth( + user="some_user", + password="abc", +) +``` + + + +#### *field* user *: str* *[Required]* *(alias 'username')* + +#### *field* password *: SecretStr* *[Required]* + +#### get_jaas_conf() → str + + + +#### get_options(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → dict + +Get options for Kafka connection + +* **Parameters:** + **kafka** + : Connection instance +* **Returns:** + dict: + : Kafka client options + + + +#### cleanup(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → None + +This method is called while closing Kafka connection. + +Implement it to cleanup resources like temporary files. + +* **Parameters:** + **kafka** + : Connection instance + + diff --git a/mddocs/connection/db_connection/kafka/connection.md b/mddocs/connection/db_connection/kafka/connection.md new file mode 100644 index 000000000..23a54a42e --- /dev/null +++ b/mddocs/connection/db_connection/kafka/connection.md @@ -0,0 +1,243 @@ + + +# Kafka Connection + +### *class* onetl.connection.db_connection.kafka.connection.Kafka(\*, spark: SparkSession, cluster: Cluster, addresses: List[str], auth: [KafkaAuth](auth.md#onetl.connection.db_connection.kafka.kafka_auth.KafkaAuth) | None = None, protocol: [KafkaProtocol](protocol.md#onetl.connection.db_connection.kafka.kafka_protocol.KafkaProtocol) = KafkaPlaintextProtocol(), extra: KafkaExtra = KafkaExtra()) + +This connector is designed to read and write from Kafka in batch mode. + +Based on [official Kafka Source For Spark](https://spark.apache.org/docs/latest/structured-streaming-kafka-integration.html). + +#### SEE ALSO +Before using this connector please take into account [Prerequisites](prerequisites.md#kafka-prerequisites) + +#### NOTE +This connector is for **batch** ETL processes, not streaming. + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **addresses** + : A list of broker addresses, for example `["192.168.1.10:9092", "192.168.1.11:9092"]`. + + **cluster** + : Cluster name. Used for HWM and lineage. + + **auth** + : Kafka authentication mechanism. `None` means anonymous auth. + + **protocol** + : Kafka security protocol. + + **extra** + : A dictionary of additional properties to be used when connecting to Kafka. +
+ These are Kafka-specific properties that control behavior of the producer or consumer. See: + * [producer options documentation](https://kafka.apache.org/documentation/#producerconfigs) + * [consumer options documentation](https://kafka.apache.org/documentation/#consumerconfigs) + * [Spark Kafka documentation](https://spark.apache.org/docs/latest/structured-streaming-kafka-integration.html#kafka-specific-configurations) +
+ Options are passed without `kafka.` prefix, for example: +
+ For example: + ```python + extra = { + "group.id": "myGroup", + "request.timeout.ms": 120000, + } + ``` +
+ #### WARNING + Options that populated from connection + attributes (like `bootstrap.servers`, `sasl.*`, `ssl.*`) are not allowed to override. + +### Examples + +Create Kafka connection with `PLAINTEXT` protocol and `SCRAM-SHA-256` auth + +```py +from onetl.connection import Kafka +from pyspark.sql import SparkSession + +# Create Spark session with Kafka connector loaded +maven_packages = Kafka.get_packages(spark_version="3.5.5") +exclude_packages = Kafka.get_exclude_packages() +spark = ( + SparkSession.builder.appName("spark-app-name") + .config("spark.jars.packages", ",".join(maven_packages)) + .config("spark.jars.excludes", ",".join(exclude_packages)) + .getOrCreate() +) + +# Create connection +kafka = Kafka( + addresses=["mybroker:9092", "anotherbroker:9092"], + cluster="my-cluster", + auth=Kafka.ScramAuth( + user="me", + password="abc", + digest="SHA-256", + ), + spark=spark, +).check() +``` + +Create Kafka connection with `PLAINTEXT` protocol and Kerberos (`GSSAPI`) auth + +```py +# Create Spark session with Kafka connector loaded +... + +# Create connection +kafka = Kafka( + addresses=["mybroker:9092", "anotherbroker:9092"], + cluster="my-cluster", + auth=Kafka.KerberosAuth( + principal="me@example.com", + keytab="/path/to/keytab", + deploy_keytab=True, + ), + spark=spark, +).check() +``` + +Create Kafka connection with `SASL_SSL` protocol and `SCRAM-SHA-512` auth + +```py +from pathlib import Path + +# Create Spark session with Kafka connector loaded +... + +# Create connection +kafka = Kafka( + addresses=["mybroker:9092", "anotherbroker:9092"], + cluster="my-cluster", + protocol=Kafka.SSLProtocol( + # read client certificate and private key from file + keystore_type="PEM", + keystore_certificate_chain=Path("path/to/user.crt").read_text(), + keystore_key=Path("path/to/user.key").read_text(), + # read server public certificate from file + truststore_type="PEM", + truststore_certificates=Path("/path/to/server.crt").read_text(), + ), + auth=Kafka.ScramAuth( + user="me", + password="abc", + digest="SHA-512", + ), + spark=spark, +).check() +``` + +Create Kafka connection with extra options + +```py +# Create Spark session with Kafka connector loaded +... + +# Create connection +kafka = Kafka( + addresses=["mybroker:9092", "anotherbroker:9092"], + cluster="my-cluster", + protocol=..., + auth=..., + extra={"max.request.size": 1024 * 1024}, # <-- + spark=spark, +).check() +``` + + + +#### check() + +Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If not, an exception will be raised. + +* **Returns:** + Connection itself +* **Raises:** + RuntimeError + : If the connection is not available + +### Examples + +```python +connection.check() +``` + + + +#### close() + +Close all connections created to Kafka. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### NOTE +Connection can be used again after it was closed. + +* **Returns:** + Connection itself + +### Examples + +Close connection automatically: + +```python +with connection: + ... +``` + +Close connection manually: + +```python +connection.close() +``` + + + +#### *classmethod* get_exclude_packages() → list[str] + +Get package names to be excluded by Spark. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.13.0. + +### Examples + +```python +from onetl.connection import Kafka + +Kafka.get_exclude_packages() +``` + + + +#### *classmethod* get_packages(spark_version: str, scala_version: str | None = None) → list[str] + +Get package names to be downloaded by Spark. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +See [Maven package index](https://mvnrepository.com/artifact/org.apache.spark/spark-sql-kafka-0-10) +for all available packages. + +* **Parameters:** + **spark_version** + : Spark version in format `major.minor.patch`. + + **scala_version** + : Scala version in format `major.minor`. +
+ If `None`, `spark_version` is used to determine Scala version. + +### Examples + +```python +from onetl.connection import Kafka + +Kafka.get_packages(spark_version="3.5.5") +Kafka.get_packages(spark_version="3.5.5", scala_version="2.12") +``` + + diff --git a/mddocs/connection/db_connection/kafka/index.md b/mddocs/connection/db_connection/kafka/index.md new file mode 100644 index 000000000..5e5f45ba1 --- /dev/null +++ b/mddocs/connection/db_connection/kafka/index.md @@ -0,0 +1,31 @@ + + +# Kafka + +# Connection + +* [Prerequisites](prerequisites.md) +* [Kafka Connection](connection.md) +* [Kafka Troubleshooting](troubleshooting.md) + +# Protocols + +* [Kafka PlaintextProtocol](plaintext_protocol.md) +* [Kafka SSLProtocol](ssl_protocol.md) + +# Auth methods + +* [Kafka BasicAuth](basic_auth.md) +* [Kafka KerberosAuth](kerberos_auth.md) +* [Kafka ScramAuth](scram_auth.md) + +# Operations + +* [Reading from Kafka](read.md) +* [Writing to Kafka](write.md) + +# For developers + +* [Kafka Auth](auth.md) +* [Kafka Protocol](protocol.md) +* [Kafka Slots](slots.md) diff --git a/mddocs/connection/db_connection/kafka/kerberos_auth.md b/mddocs/connection/db_connection/kafka/kerberos_auth.md new file mode 100644 index 000000000..1fefd2992 --- /dev/null +++ b/mddocs/connection/db_connection/kafka/kerberos_auth.md @@ -0,0 +1,118 @@ + + +# Kafka KerberosAuth + +### *pydantic model* onetl.connection.db_connection.kafka.kafka_kerberos_auth.KafkaKerberosAuth + +Connect to Kafka using `sasl.mechanism="GSSAPI"`. + +For more details see: + +* [Kafka Documentation](https://kafka.apache.org/documentation/#security_sasl_kerberos_clientconfig) +* [Krb5LoginModule documentation](https://docs.oracle.com/javase/8/docs/jre/api/security/jaas/spec/com/sun/security/auth/module/Krb5LoginModule.html) + +#### Versionadded +Added in version 0.9.0. + +### Examples + +Auth in Kafka with keytab, automatically deploy keytab files to all Spark hosts (driver and executors): + +```python +from onetl.connection import Kafka + +auth = Kafka.KerberosAuth( + principal="user", + keytab="/path/to/keytab", + deploy_keytab=True, +) +``` + +Auth in Kafka with keytab, keytab is **already deployed** on all Spark hosts (driver and executors): + +```python +from onetl.connection import Kafka + +auth = Kafka.KerberosAuth( + principal="user", + keytab="/path/to/keytab", + deploy_keytab=False, +) +``` + +Auth in Kafka with existing Kerberos ticket (only Spark session created with `master=local`): + +```python +from onetl.connection import Kafka + +auth = Kafka.KerberosAuth( + principal="user", + use_keytab=False, + use_ticket_cache=True, +) +``` + +Pass custom options for JAAS config and Kafka SASL: + +```python +from onetl.connection import Kafka + +auth = Kafka.KerberosAuth.parse( + { + "principal": "user", + "keytab": "/path/to/keytab", + # options without sasl.kerberos. prefix are passed to JAAS config + # names are in camel case! + "isInitiator": True, + # options with `sasl.kerberos.` prefix are passed to Kafka client config as-is + "sasl.kerberos.kinit.cmd": "/usr/bin/kinit", + } +) +``` + + + +#### *field* principal *: str* *[Required]* + +#### *field* keytab *: LocalPath | None* *= None* *(alias 'keyTab')* + +#### *field* deploy_keytab *: bool* *= True* + +#### *field* service_name *: str* *= 'kafka'* *(alias 'serviceName')* + +#### *field* renew_ticket *: bool* *= True* *(alias 'renewTicket')* + +#### *field* store_key *: bool* *= True* *(alias 'storeKey')* + +#### *field* use_keytab *: bool* *= True* *(alias 'useKeyTab')* + +#### *field* use_ticket_cache *: bool* *= False* *(alias 'useTicketCache')* + +#### get_jaas_conf(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → str + + + +#### get_options(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → dict + +Get options for Kafka connection + +* **Parameters:** + **kafka** + : Connection instance +* **Returns:** + dict: + : Kafka client options + + + +#### cleanup(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → None + +This method is called while closing Kafka connection. + +Implement it to cleanup resources like temporary files. + +* **Parameters:** + **kafka** + : Connection instance + + diff --git a/mddocs/connection/db_connection/kafka/plaintext_protocol.md b/mddocs/connection/db_connection/kafka/plaintext_protocol.md new file mode 100644 index 000000000..f7ebd316d --- /dev/null +++ b/mddocs/connection/db_connection/kafka/plaintext_protocol.md @@ -0,0 +1,48 @@ + + +# Kafka PlaintextProtocol + +### *pydantic model* onetl.connection.db_connection.kafka.kafka_plaintext_protocol.KafkaPlaintextProtocol + +Connect to Kafka using `PLAINTEXT` or `SASL_PLAINTEXT` security protocols. + +#### WARNING +Not recommended to use on production environments. +Prefer [`SSLProtocol`](ssl_protocol.md#onetl.connection.db_connection.kafka.kafka_ssl_protocol.KafkaSSLProtocol). + +#### Versionadded +Added in version 0.9.0. + +### Examples + +```python +# No options +protocol = Kafka.PlaintextProtocol() +``` + + + +#### get_options(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → dict + +Get options for Kafka connection + +* **Parameters:** + **kafka** + : Connection instance +* **Returns:** + dict: + : Kafka client options + + + +#### cleanup(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → None + +This method is called while closing Kafka connection. + +Implement it to cleanup resources like temporary files. + +* **Parameters:** + **kafka** + : Connection instance + + diff --git a/mddocs/connection/db_connection/kafka/prerequisites.md b/mddocs/connection/db_connection/kafka/prerequisites.md new file mode 100644 index 000000000..fcb90f705 --- /dev/null +++ b/mddocs/connection/db_connection/kafka/prerequisites.md @@ -0,0 +1,65 @@ + + +# Prerequisites + +## Version Compatibility + +* Kafka server versions: + : * Officially declared: 0.10 or higher + * Actually tested: 3.2.3, 3.9.0 (only Kafka 3.x supports message headers) +* Spark versions: 2.4.x - 3.5.x +* Java versions: 8 - 17 + +See [official documentation](https://spark.apache.org/docs/latest/structured-streaming-kafka-integration.html). + +## Installing PySpark + +To use Kafka connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Connecting to Kafka + +### Connection address + +Kafka is a distributed service, and usually has a list of brokers you can connect to (unlike other connectors, there only one host+port can be set). +Please contact your Kafka administrator to get addresses of these brokers, as there are no defaults. + +Also Kafka has a feature called *advertised listeners* - client connects to one broker, and received list of other brokers in the clusters. +So you don’t have to pass all brokers to `addresses`, it can be some subset. Other broker addresses will be fetched directly from the cluster. + +### Connection protocol + +Kafka can support different connection protocols. List of currently supported protocols: +: * [`PLAINTEXT`](plaintext_protocol.md#onetl.connection.db_connection.kafka.kafka_plaintext_protocol.KafkaPlaintextProtocol) (not secure) + * [`SSL`](ssl_protocol.md#onetl.connection.db_connection.kafka.kafka_ssl_protocol.KafkaSSLProtocol) (secure, recommended) + +Note that specific port can listen for only one of these protocols, so it is important to set +proper port number + protocol combination. + +### Authentication mechanism + +Kafka can support different authentication mechanism (also known as [SASL](https://en.wikipedia.org/wiki/Simple_Authentication_and_Security_Layer)). + +List of currently supported mechanisms: +: * [`PLAIN`](basic_auth.md#onetl.connection.db_connection.kafka.kafka_basic_auth.KafkaBasicAuth). To no confuse this with `PLAINTEXT` connection protocol, onETL uses name `BasicAuth`. + * [`GSSAPI`](kerberos_auth.md#onetl.connection.db_connection.kafka.kafka_kerberos_auth.KafkaKerberosAuth). To simplify naming, onETL uses name `KerberosAuth`. + * [`SCRAM-SHA-256 or SCRAM-SHA-512`](scram_auth.md#onetl.connection.db_connection.kafka.kafka_scram_auth.KafkaScramAuth) (recommended). + +Different mechanisms use different types of credentials (login + password, keytab file, and so on). + +Note that connection protocol and auth mechanism are set in pairs: +: * If you see `SASL_PLAINTEXT` this means `PLAINTEXT` connection protocol + some auth mechanism. + * If you see `SASL_SSL` this means `SSL` connection protocol + some auth mechanism. + * If you see just `PLAINTEXT` or `SSL` (**no** `SASL`), this means that authentication is disabled (anonymous access). + +Please contact your Kafka administrator to get details about enabled auth mechanism in a specific Kafka instance. + +### Required grants + +Ask your Kafka administrator to set following grants for a user, *if Kafka instance uses ACL*: +: * `Describe` + `Read` for reading data from Kafka (Consumer). + * `Describe` + `Write` for writing data from Kafka (Producer). + +More details can be found in [documentation](https://kafka.apache.org/documentation/#operations_in_kafka). diff --git a/mddocs/connection/db_connection/kafka/protocol.md b/mddocs/connection/db_connection/kafka/protocol.md new file mode 100644 index 000000000..720e5f0b4 --- /dev/null +++ b/mddocs/connection/db_connection/kafka/protocol.md @@ -0,0 +1,37 @@ + + +# Kafka Protocol + +### *class* onetl.connection.db_connection.kafka.kafka_protocol.KafkaProtocol + +Interface for Kafka connection Protocol classes. + +#### Versionadded +Added in version 0.9.0. + + + +#### *abstract* get_options(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → dict + +Get options for Kafka connection + +* **Parameters:** + **kafka** + : Connection instance +* **Returns:** + dict: + : Kafka client options + + + +#### *abstract* cleanup(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → None + +This method is called while closing Kafka connection. + +Implement it to cleanup resources like temporary files. + +* **Parameters:** + **kafka** + : Connection instance + + diff --git a/mddocs/connection/db_connection/kafka/read.md b/mddocs/connection/db_connection/kafka/read.md new file mode 100644 index 000000000..52ec5ff33 --- /dev/null +++ b/mddocs/connection/db_connection/kafka/read.md @@ -0,0 +1,172 @@ + + +# Reading from Kafka + +Data can be read from Kafka to Spark using [`DBReader`](../../../db/db_reader.md#onetl.db.db_reader.db_reader.DBReader). +It also supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading. + +## Supported DBReader features + +* ❌ `columns` (is not supported by Kafka) +* ❌ `where` (is not supported by Kafka) +* ✅︎ `hwm`, supported strategies: +* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) +* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) +* * ❌ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) +* * ❌ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) +* ❌ `hint` (is not supported by Kafka) +* ❌ `df_schema` (see note below) +* ✅︎ `options` (see [`Kafka.ReadOptions`](#onetl.connection.db_connection.kafka.options.KafkaReadOptions)) + +## Dataframe schema + +Unlike other DB connections, Kafka does not have concept of columns. +All the topics messages have the same set of fields, see structure below: + +```text +root +|-- key: binary (nullable = true) +|-- value: binary (nullable = true) +|-- topic: string (nullable = false) +|-- partition: integer (nullable = false) +|-- offset: integer (nullable = false) +|-- timestamp: timestamp (nullable = false) +|-- timestampType: integer (nullable = false) +|-- headers: struct (nullable = true) + |-- key: string (nullable = false) + |-- value: binary (nullable = true) +``` + +`headers` field is present in the dataframe only if `Kafka.ReadOptions(include_headers=True)` is passed (compatibility with Kafka 1.x). + +## Value deserialization + +To read `value` or `key` of other type than bytes (e.g. struct or integer), users have to deserialize values manually. + +This could be done using following methods: +: * [`Avro.parse_column`](../../../file_df/file_formats/avro.md#onetl.file.format.avro.Avro.parse_column) + * [`JSON.parse_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.parse_column) + * [`CSV.parse_column`](../../../file_df/file_formats/csv.md#onetl.file.format.csv.CSV.parse_column) + * [`XML.parse_column`](../../../file_df/file_formats/xml.md#onetl.file.format.xml.XML.parse_column) + +## Examples + +Snapshot strategy, `value` is Avro binary data: + +```python +from onetl.connection import Kafka +from onetl.db import DBReader, DBWriter +from onetl.file.format import Avro +from pyspark.sql.functions import decode + +# read all topic data from Kafka +kafka = Kafka(...) +reader = DBReader(connection=kafka, source="avro_topic") +read_df = reader.run() + +# parse Avro format to Spark struct +avro = Avro( + schema_dict={ + "type": "record", + "name": "Person", + "fields": [ + {"name": "name", "type": "string"}, + {"name": "age", "type": "int"}, + ], + } +) +deserialized_df = read_df.select( + # cast binary key to string + decode("key", "UTF-8").alias("key"), + avro.parse_column("value"), +) +``` + +Incremental strategy, `value` is JSON string: + +#### NOTE +Currently Kafka connector does support only HWMs based on `offset` field. Other fields, like `timestamp`, are not yet supported. + +```python +from onetl.connection import Kafka +from onetl.db import DBReader, DBWriter +from onetl.file.format import JSON +from pyspark.sql.functions import decode + +kafka = Kafka(...) + +# read only new data from Kafka topic +reader = DBReader( + connection=kafka, + source="topic_name", + hwm=DBReader.AutoDetectHWM(name="kafka_hwm", expression="offset"), +) + +with IncrementalStrategy(): + read_df = reader.run() + +# parse JSON format to Spark struct +json = JSON() +schema = StructType( + [ + StructField("name", StringType(), nullable=True), + StructField("age", IntegerType(), nullable=True), + ], +) +deserialized_df = read_df.select( + # cast binary key to string + decode("key", "UTF-8").alias("key"), + json.parse_column("value", json), +) +``` + +## Options + +### *pydantic model* onetl.connection.db_connection.kafka.options.KafkaReadOptions + +Reading options for Kafka connector. + +#### WARNING +Options: +: * `assign` + * `endingOffsets` + * `endingOffsetsByTimestamp` + * `kafka.*` + * `startingOffsets` + * `startingOffsetsByTimestamp` + * `startingTimestamp` + * `subscribe` + * `subscribePattern` + +are populated from connection attributes, and cannot be overridden by the user in `ReadOptions` to avoid issues. + +#### Versionadded +Added in version 0.9.0. + +### Examples + +#### NOTE +You can pass any value +[supported by connector](https://spark.apache.org/docs/latest/structured-streaming-kafka-integration.html), +even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! + +The set of supported options depends on connector version. + +```python +from onetl.connection import Kafka + +options = Kafka.ReadOptions( + includeHeaders=False, + minPartitions=50, +) +``` + + + +#### *field* include_headers *: bool* *= False* *(alias 'includeHeaders')* + +If `True`, add `headers` column to output DataFrame. + +If `False`, column will not be added. + + diff --git a/mddocs/connection/db_connection/kafka/scram_auth.md b/mddocs/connection/db_connection/kafka/scram_auth.md new file mode 100644 index 000000000..c0c974ad3 --- /dev/null +++ b/mddocs/connection/db_connection/kafka/scram_auth.md @@ -0,0 +1,79 @@ + + +# Kafka ScramAuth + +### *pydantic model* onetl.connection.db_connection.kafka.kafka_scram_auth.KafkaScramAuth + +Connect to Kafka using `sasl.mechanism="SCRAM-SHA-256"` or `sasl.mechanism="SCRAM-SHA-512"`. + +For more details see [Kafka Documentation](https://kafka.apache.org/documentation/#security_sasl_scram_clientconfig). + +#### Versionadded +Added in version 0.9.0. + +### Examples + +Auth in Kafka with `SCRAM-SHA-256` mechanism: + +```python +from onetl.connection import Kafka + +auth = Kafka.ScramAuth( + user="me", + password="abc", + digest="SHA-256", +) +``` + +Auth in Kafka with `SCRAM-SHA-512` mechanism and some custom SASL options passed to Kafka client config: + +```python +from onetl.connection import Kafka + +auth = Kafka.ScramAuth.parse( + { + "user": "me", + "password": "abc", + "digest": "SHA-512", + # options with `sasl.login.` prefix are passed to Kafka client config as-is + "sasl.login.class": "com.example.CustomScramLogin", + } +) +``` + + + +#### *field* user *: str* *[Required]* *(alias 'username')* + +#### *field* password *: SecretStr* *[Required]* + +#### *field* digest *: Literal['SHA-256', 'SHA-512']* *[Required]* + +#### get_jaas_conf() → str + + + +#### get_options(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → dict + +Get options for Kafka connection + +* **Parameters:** + **kafka** + : Connection instance +* **Returns:** + dict: + : Kafka client options + + + +#### cleanup(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → None + +This method is called while closing Kafka connection. + +Implement it to cleanup resources like temporary files. + +* **Parameters:** + **kafka** + : Connection instance + + diff --git a/mddocs/connection/db_connection/kafka/slots.md b/mddocs/connection/db_connection/kafka/slots.md new file mode 100644 index 000000000..e4de1772d --- /dev/null +++ b/mddocs/connection/db_connection/kafka/slots.md @@ -0,0 +1,136 @@ + + +# Kafka Slots + +### *class* onetl.connection.db_connection.kafka.slots.KafkaSlots + +Kafka slots that could be implemented by third-party plugins + +#### Versionadded +Added in version 0.9.0. + + + +#### *static* normalize_cluster_name(cluster: str) → str | None + +Normalize the given Kafka cluster name. + +This can be used to ensure that the Kafka cluster name conforms to specific naming conventions. + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **cluster** + : The original Kafka cluster name. +* **Returns:** + str | None + : The normalized Kafka cluster name. If the hook cannot be applied, return `None`. + +### Examples + +```python +from onetl.connection import Kafka +from onetl.hooks import hook + +@Kafka.Slots.normalize_cluster_name.bind +@hook +def normalize_cluster_name(cluster: str) -> str | None: + return cluster.lower() +``` + + + +#### *static* get_known_clusters() → set[str] | None + +Retrieve the collection of known Kafka clusters. + +This can be used to validate if the provided Kafka cluster name is recognized in the system. + +#### Versionadded +Added in version 0.9.0. + +* **Returns:** + set[str] | None + : A collection of known Kafka cluster names. If the hook cannot be applied, return `None`. + +### Examples + +```python +from onetl.connection import Kafka +from onetl.hooks import hook + +@Kafka.Slots.get_known_clusters.bind +@hook +def get_known_clusters() -> set[str] | None: + return {"kafka-cluster", "local"} +``` + + + +#### *static* normalize_address(address: str, cluster: str) → str | None + +Normalize the given broker address for a specific Kafka cluster. + +This can be used to format the broker address according to specific rules, such as adding default ports. + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **address** + : The original broker address. + + **cluster** + : The Kafka cluster name for which the address should be normalized. +* **Returns:** + str | None + : The normalized broker address. If the hook cannot be applied to the specific address, return `None`. + +### Examples + +```python +from onetl.connection import Kafka +from onetl.hooks import hook + +@Kafka.Slots.normalize_address.bind +@hook +def normalize_address(address: str, cluster: str) -> str | None: + if cluster == "kafka-cluster" and ":" not in address: + return f"{address}:9092" + return None +``` + + + +#### *static* get_cluster_addresses(cluster: str) → list[str] | None + +Retrieve a collection of known broker addresses for the specified Kafka cluster. + +This can be used to obtain the broker addresses dynamically. + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **cluster** + : The Kafka cluster name. +* **Returns:** + list[str] | None + : A collection of broker addresses for the specified Kafka cluster. If the hook cannot be applied, return `None`. + +### Examples + +```python +from onetl.connection import Kafka +from onetl.hooks import hook + +@Kafka.Slots.get_cluster_addresses.bind +@hook +def get_cluster_addresses(cluster: str) -> list[str] | None: + if cluster == "kafka_cluster": + return ["192.168.1.1:9092", "192.168.1.2:9092", "192.168.1.3:9092"] + return None +``` + + diff --git a/mddocs/connection/db_connection/kafka/ssl_protocol.md b/mddocs/connection/db_connection/kafka/ssl_protocol.md new file mode 100644 index 000000000..ca2a59b45 --- /dev/null +++ b/mddocs/connection/db_connection/kafka/ssl_protocol.md @@ -0,0 +1,149 @@ + + +# Kafka SSLProtocol + +### *pydantic model* onetl.connection.db_connection.kafka.kafka_ssl_protocol.KafkaSSLProtocol + +Connect to Kafka using `SSL` or `SASL_SSL` security protocols. + +For more details see: + +* [Kafka Documentation](https://kafka.apache.org/documentation/#producerconfigs_ssl.keystore.location) +* [IBM Documentation](https://www.ibm.com/docs/en/cloud-paks/cp-biz-automation/19.0.x?topic=fcee-kafka-using-ssl-kerberos-authentication) +* [How to use PEM Certificates with Kafka](https://codingharbour.com/apache-kafka/using-pem-certificates-with-apache-kafka/) + +#### Versionadded +Added in version 0.9.0. + +### Examples + +Pass PEM key and certificates as files located on Spark driver host: + +```python +from pathlib import Path + +# Just read existing files located on host, and pass key and certificates as strings +protocol = Kafka.SSLProtocol( + keystore_type="PEM", + keystore_certificate_chain=Path("path/to/user.crt").read_text(), + keystore_key=Path("path/to/user.key").read_text(), + truststore_type="PEM", + truststore_certificates=Path("/path/to/server.crt").read_text(), +) +``` + +Pass PEM key and certificates as raw strings: + +```python +protocol = Kafka.SSLProtocol( + keystore_type="PEM", + keystore_certificate_chain="-----BEGIN CERTIFICATE-----\nMIIDZjC...\n-----END CERTIFICATE-----", + keystore_key="-----BEGIN PRIVATE KEY-----\nMIIEvg..\n-----END PRIVATE KEY-----", + truststore_type="PEM", + truststore_certificates="-----BEGIN CERTIFICATE-----\nMICC...\n-----END CERTIFICATE-----", +) +``` + +Pass custom options: + +```python +protocol = Kafka.SSLProtocol.parse( + { + # Just the same options as above, but using Kafka config naming with dots + "ssl.keystore.type": "PEM", + "ssl.keystore.certificate_chain": "-----BEGIN CERTIFICATE-----\nMIIDZjC...\n-----END CERTIFICATE-----", + "ssl.keystore.key": "-----BEGIN PRIVATE KEY-----\nMIIEvg..\n-----END PRIVATE KEY-----", + "ssl.truststore.type": "PEM", + "ssl.truststore.certificates": "-----BEGIN CERTIFICATE-----\nMICC...\n-----END CERTIFICATE-----", + # Any option starting from "ssl." is passed to Kafka client as-is + "ssl.protocol": "TLSv1.3", + } +) +``` + +### Not recommended + +These options are error-prone and have several drawbacks, so it is not recommended to use them. + +Passing PEM certificates as files: + +* ENCRYPT `user.key` file with password `"some password"` [using PKCS#8 scheme](https://www.mkssoftware.com/docs/man1/openssl_pkcs8.1.asp). +* Save encrypted key to file `/path/to/user/encrypted_key_with_certificate_chain.pem`. +* Then append user certificate to the end of this file. +* Deploy this file (and server certificate too) to **EVERY** host Spark could run (both driver and executors). +* Then pass file locations and password for key decryption to options below. + +```python +protocol = Kafka.SSLProtocol( + keystore_type="PEM", + keystore_location="/path/to/user/encrypted_key_with_certificate_chain.pem", + key_password="some password", + truststore_type="PEM", + truststore_location="/path/to/server.crt", +) +``` + +Passing JKS (Java Key Store) location: + +* [Add user key and certificate to JKS keystore](https://stackoverflow.com/a/4326346). +* [Add server certificate to JKS truststore](https://stackoverflow.com/a/373307). +* This should be done on **EVERY** host Spark could run (both driver and executors). +* Pass keystore and truststore paths to options below, as well as passwords for accessing these stores: + +```python +protocol = Kafka.SSLProtocol( + keystore_type="JKS", + keystore_location="/usr/lib/jvm/default/lib/security/keystore.jks", + keystore_password="changeit", + truststore_type="JKS", + truststore_location="/usr/lib/jvm/default/lib/security/truststore.jks", + truststore_password="changeit", +) +``` + + + +#### *field* keystore_type *: str* *[Required]* *(alias 'ssl.keystore.type')* + +#### *field* keystore_location *: LocalPath | None* *= None* *(alias 'ssl.keystore.location')* + +#### *field* keystore_password *: SecretStr | None* *= None* *(alias 'ssl.keystore.password')* + +#### *field* keystore_certificate_chain *: str | None* *= None* *(alias 'ssl.keystore.certificate.chain')* + +#### *field* keystore_key *: SecretStr | None* *= None* *(alias 'ssl.keystore.key')* + +#### *field* key_password *: SecretStr | None* *= None* *(alias 'ssl.key.password')* + +#### *field* truststore_type *: str* *[Required]* *(alias 'ssl.truststore.type')* + +#### *field* truststore_location *: LocalPath | None* *= None* *(alias 'ssl.truststore.location')* + +#### *field* truststore_password *: SecretStr | None* *= None* *(alias 'ssl.truststore.password')* + +#### *field* truststore_certificates *: str | None* *= None* *(alias 'ssl.truststore.certificates')* + +#### get_options(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → dict + +Get options for Kafka connection + +* **Parameters:** + **kafka** + : Connection instance +* **Returns:** + dict: + : Kafka client options + + + +#### cleanup(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → None + +This method is called while closing Kafka connection. + +Implement it to cleanup resources like temporary files. + +* **Parameters:** + **kafka** + : Connection instance + + diff --git a/mddocs/connection/db_connection/kafka/troubleshooting.md b/mddocs/connection/db_connection/kafka/troubleshooting.md new file mode 100644 index 000000000..cd15b5ba7 --- /dev/null +++ b/mddocs/connection/db_connection/kafka/troubleshooting.md @@ -0,0 +1,10 @@ + + +# Kafka Troubleshooting + +#### NOTE +General guide: [Troubleshooting](../../../troubleshooting/index.md#troubleshooting). + +## Cannot connect using `SSL` protocol + +Please check that certificate files are not Base-64 encoded. diff --git a/mddocs/connection/db_connection/kafka/write.md b/mddocs/connection/db_connection/kafka/write.md new file mode 100644 index 000000000..c85f9c3cc --- /dev/null +++ b/mddocs/connection/db_connection/kafka/write.md @@ -0,0 +1,118 @@ + + +# Writing to Kafka + +For writing data to Kafka, use [`DBWriter`](../../../db/db_writer.md#onetl.db.db_writer.db_writer.DBWriter) with specific options (see below). + +## Dataframe schema + +Unlike other DB connections, Kafka does not have concept of columns. +All the topics messages have the same set of fields. Only some of them can be written: + +```text +root +|-- key: binary (nullable = true) +|-- value: binary (nullable = true) +|-- headers: struct (nullable = true) + |-- key: string (nullable = false) + |-- value: binary (nullable = true) +``` + +`headers` can be passed only with `Kafka.WriteOptions(include_headers=True)` (compatibility with Kafka 1.x). + +Field `topic` should not be present in the dataframe, as it is passed to `DBWriter(target=...)`. + +Other fields, like `partition`, `offset`, `timestamp` are set by Kafka, and cannot be passed explicitly. + +## Value serialization + +To write `value` or `key` of other type than bytes (e.g. struct or integer), users have to serialize values manually. + +This could be done using following methods: +: * [`Avro.serialize_column`](../../../file_df/file_formats/avro.md#onetl.file.format.avro.Avro.serialize_column) + * [`JSON.serialize_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.serialize_column) + * [`CSV.serialize_column`](../../../file_df/file_formats/csv.md#onetl.file.format.csv.CSV.serialize_column) + +## Examples + +Convert `value` to JSON string, and write to Kafka: + +```python +from onetl.connection import Kafka +from onetl.db import DBWriter +from onetl.file.format import JSON + +df = ... # original data is here + +# serialize struct data as JSON +json = JSON() +write_df = df.select( + df.key, + json.serialize_column(df.value), +) + +# write data to Kafka +kafka = Kafka(...) + +writer = DBWriter( + connection=kafka, + target="topic_name", +) +writer.run(write_df) +``` + +## Options + +### *pydantic model* onetl.connection.db_connection.kafka.options.KafkaWriteOptions + +Writing options for Kafka connector. + +#### WARNING +Options: +: * `kafka.*` + * `topic` + +are populated from connection attributes, and cannot be overridden by the user in `WriteOptions` to avoid issues. + +#### Versionadded +Added in version 0.9.0. + +### Examples + +#### NOTE +You can pass any value +[supported by connector](https://spark.apache.org/docs/latest/structured-streaming-kafka-integration.html), +even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! + +The set of supported options depends on connector version. + +```python +from onetl.connection import Kafka + +options = Kafka.WriteOptions( + if_exists="append", + includeHeaders=True, +) +``` + + + +#### *field* if_exists *: KafkaTopicExistBehaviorKafka* *= KafkaTopicExistBehaviorKafka.APPEND* + +Behavior of writing data into existing topic. + +Same as `df.write.mode(...)`. + +Possible values: +: * `append` (default) - Adds new objects into existing topic. + * `error` - Raises an error if topic already exists. + + + +#### *field* include_headers *: bool* *= False* *(alias 'includeHeaders')* + +If `True`, `headers` column from dataframe can be written to Kafka (requires Kafka 2.0+). + +If `False` and dataframe contains `headers` column, an exception will be raised. + + diff --git a/mddocs/connection/db_connection/mongodb/connection.md b/mddocs/connection/db_connection/mongodb/connection.md new file mode 100644 index 000000000..8a01a11be --- /dev/null +++ b/mddocs/connection/db_connection/mongodb/connection.md @@ -0,0 +1,124 @@ + + +# MongoDB Connection + +### *class* onetl.connection.db_connection.mongodb.connection.MongoDB(\*, spark: SparkSession, database: str, host: Host, user: str, password: SecretStr, port: int = 27017, extra: MongoDBExtra = MongoDBExtra()) + +MongoDB connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on package [org.mongodb.spark:mongo-spark-connector:10.4.1](https://mvnrepository.com/artifact/org.mongodb.spark/mongo-spark-connector_2.12/10.4.1) +([MongoDB connector for Spark](https://www.mongodb.com/docs/spark-connector/current/)) + +#### SEE ALSO +Before using this connector please take into account [Prerequisites](prerequisites.md#mongodb-prerequisites) + +#### Versionadded +Added in version 0.7.0. + +* **Parameters:** + **host** + : Host of MongoDB. For example: `test.mongodb.com` or `193.168.1.17`. + + **port** + : Port of MongoDB + + **user** + : User, which have proper access to the database. For example: `some_user`. + + **password** + : Password for database connection. + + **database** + : Database in MongoDB. + + **extra** + : Specifies one or more extra parameters by which clients can connect to the instance. +
+ For example: `{"tls": "false"}` +
+ See [Connection string options documentation](https://www.mongodb.com/docs/manual/reference/connection-string/#std-label-connections-connection-options) + for more details + + **spark** + : Spark session. + +### Examples + +```python +from onetl.connection import MongoDB +from pyspark.sql import SparkSession + +# Create Spark session with MongoDB connector loaded +maven_packages = MongoDB.get_packages(spark_version="3.4") +spark = ( + SparkSession.builder.appName("spark-app-name") + .config("spark.jars.packages", ",".join(maven_packages)) + .getOrCreate() +) + +# Create connection +mongo = MongoDB( + host="master.host.or.ip", + user="user", + password="*****", + database="target_database", + spark=spark, +).check() +``` + + + +#### *classmethod* get_packages(scala_version: str | None = None, spark_version: str | None = None, package_version: str | None = None) → list[str] + +Get package names to be downloaded by Spark. Allows specifying custom MongoDB Spark connector versions. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **scala_version** + : Scala version in format `major.minor`. +
+ If `None`, `spark_version` is used to determine Scala version. + + **spark_version** + : Spark version in format `major.minor`. Used only if `scala_version=None`. + + **package_version** + : Specifies the version of the MongoDB Spark connector to use. Defaults to `10.4.1`. +
+ #### Versionadded + Added in version 0.11.0. + +### Examples + +```python +from onetl.connection import MongoDB + +MongoDB.get_packages(scala_version="2.12") + +# specify custom connector version +MongoDB.get_packages(scala_version="2.12", package_version="10.4.1") +``` + + + +#### check() + +Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If not, an exception will be raised. + +* **Returns:** + Connection itself +* **Raises:** + RuntimeError + : If the connection is not available + +### Examples + +```python +connection.check() +``` + + diff --git a/mddocs/connection/db_connection/mongodb/index.md b/mddocs/connection/db_connection/mongodb/index.md new file mode 100644 index 000000000..45ab522a3 --- /dev/null +++ b/mddocs/connection/db_connection/mongodb/index.md @@ -0,0 +1,18 @@ + + +# MongoDB + +# Connection + +* [Prerequisites](prerequisites.md) +* [MongoDB Connection](connection.md) + +# Operations + +* [Reading from MongoDB using `DBReader`](read.md) +* [Reading from MongoDB using `MongoDB.pipeline`](pipeline.md) +* [Writing to MongoDB using `DBWriter`](write.md) + +# Troubleshooting + +* [MongoDB <-> Spark type mapping](types.md) diff --git a/mddocs/connection/db_connection/mongodb/pipeline.md b/mddocs/connection/db_connection/mongodb/pipeline.md new file mode 100644 index 000000000..2bae88c48 --- /dev/null +++ b/mddocs/connection/db_connection/mongodb/pipeline.md @@ -0,0 +1,152 @@ + + +# Reading from MongoDB using `MongoDB.pipeline` + +[`MongoDB.sql`](#onetl.connection.db_connection.mongodb.connection.MongoDB.pipeline) allows passing custom pipeline, +but does not support incremental strategies. + +#### WARNING +Please take into account [MongoDB <-> Spark type mapping](types.md#mongodb-types) + +## Recommendations + +### Pay attention to `pipeline` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `mongodb.pipeline(..., pipeline={"$match": {"column": {"$eq": "value"}}})` value. +This both reduces the amount of data send from MongoDB to Spark, and may also improve performance of the query. +Especially if there are indexes for columns used in `pipeline` value. + +## References + +#### MongoDB.pipeline(collection: str, pipeline: dict | list[dict] | None = None, df_schema: StructType | None = None, options: [MongoDBPipelineOptions](#onetl.connection.db_connection.mongodb.options.MongoDBPipelineOptions) | dict | None = None) + +Execute a pipeline for a specific collection, and return DataFrame. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Almost like [Aggregation pipeline syntax](https://www.mongodb.com/docs/manual/core/aggregation-pipeline/) +in MongoDB: + +```js +db.collection_name.aggregate([{"$match": ...}, {"$group": ...}]) +``` + +but pipeline is executed on Spark executors, in a distributed way. + +#### NOTE +This method does not support [Read Strategies](../../../strategy/index.md#strategy), +use [`DBReader`](../../../db/db_reader.md#onetl.db.db_reader.db_reader.DBReader) instead + +#### Versionadded +Added in version 0.7.0. + +* **Parameters:** + **collection** + : Collection name. + + **pipeline** + : Pipeline containing a database query. + See [Aggregation pipeline syntax](https://www.mongodb.com/docs/manual/core/aggregation-pipeline/). + + **df_schema** + : Schema describing the resulting DataFrame. + + **options** + : Additional pipeline options, see [`MongoDB.PipelineOptions`](#onetl.connection.db_connection.mongodb.options.MongoDBPipelineOptions). + +### Examples + +Get document with a specific `field` value: + +```python +df = connection.pipeline( + collection="collection_name", + pipeline={"$match": {"field": {"$eq": 1}}}, +) +``` + +Calculate aggregation and get result: + +```python +df = connection.pipeline( + collection="collection_name", + pipeline={ + "$group": { + "_id": 1, + "min": {"$min": "$column_int"}, + "max": {"$max": "$column_int"}, + } + }, +) +``` + +Explicitly pass DataFrame schema: + +```python +from pyspark.sql.types import ( + DoubleType, + IntegerType, + StringType, + StructField, + StructType, + TimestampType, +) + +df_schema = StructType( + [ + StructField("_id", StringType()), + StructField("some_string", StringType()), + StructField("some_int", IntegerType()), + StructField("some_datetime", TimestampType()), + StructField("some_float", DoubleType()), + ], +) + +df = connection.pipeline( + collection="collection_name", + df_schema=df_schema, + pipeline={"$match": {"some_int": {"$gt": 999}}}, +) +``` + +Pass additional options to pipeline execution: + +```python +df = connection.pipeline( + collection="collection_name", + pipeline={"$match": {"field": {"$eq": 1}}}, + options=MongoDB.PipelineOptions(hint={"field": 1}), +) +``` + + + +### *pydantic model* onetl.connection.db_connection.mongodb.options.MongoDBPipelineOptions + +Aggregation pipeline options for MongoDB connector. + +The only difference from [`MongoDB.ReadOptions`](read.md#onetl.connection.db_connection.mongodb.options.MongoDBReadOptions) that latter does not allow to pass the `hint` parameter. + +#### WARNING +Options `uri`, `database`, `collection`, `pipeline` are populated from connection attributes, +and cannot be overridden by the user in `PipelineOptions` to avoid issues. + +#### Versionadded +Added in version 0.7.0. + +### Examples + +#### NOTE +You can pass any value +[supported by connector](https://www.mongodb.com/docs/spark-connector/current/batch-mode/batch-read-config/), +even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! + +The set of supported options depends on connector version. + +```python +from onetl.connection import MongoDB + +options = MongoDB.PipelineOptions( + hint={"some_field": 1}, +) +``` + + diff --git a/mddocs/connection/db_connection/mongodb/prerequisites.md b/mddocs/connection/db_connection/mongodb/prerequisites.md new file mode 100644 index 000000000..4572fb4f6 --- /dev/null +++ b/mddocs/connection/db_connection/mongodb/prerequisites.md @@ -0,0 +1,72 @@ + + +# Prerequisites + +## Version Compatibility + +* MongoDB server versions: + : * Officially declared: 4.0 or higher + * Actually tested: 4.0.0, 8.0.4 +* Spark versions: 3.2.x - 3.5.x +* Java versions: 8 - 20 + +See [official documentation](https://www.mongodb.com/docs/spark-connector/). + +## Installing PySpark + +To use MongoDB connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Connecting to MongoDB + +### Connection host + +It is possible to connect to MongoDB host by using either DNS name of host or it’s IP address. + +It is also possible to connect to MongoDB shared cluster: + +```python +mongo = MongoDB( + host="master.host.or.ip", + user="user", + password="*****", + database="target_database", + spark=spark, + extra={ + # read data from secondary cluster node, switch to primary if not available + "readPreference": "secondaryPreferred", + }, +) +``` + +Supported `readPreference` values are described in [official documentation](https://www.mongodb.com/docs/manual/core/read-preference/). + +### Connection port + +Connection is usually performed to port `27017`. Port may differ for different MongoDB instances. +Please ask your MongoDB administrator to provide required information. + +### Required grants + +Ask your MongoDB cluster administrator to set following grants for a user, +used for creating a connection: + +Read + Write + +```js +// allow writing data to specific database +db.grantRolesToUser("username", [{db: "somedb", role: "readWrite"}]) +``` + +Read only + +```js +// allow reading data from specific database +db.grantRolesToUser("username", [{db: "somedb", role: "read"}]) +``` + +See: +: * [db.grantRolesToUser documentation](https://www.mongodb.com/docs/manual/reference/method/db.grantRolesToUser) + * [MongoDB builtin roles](https://www.mongodb.com/docs/manual/reference/built-in-roles) diff --git a/mddocs/connection/db_connection/mongodb/read.md b/mddocs/connection/db_connection/mongodb/read.md new file mode 100644 index 000000000..2f2ffb73c --- /dev/null +++ b/mddocs/connection/db_connection/mongodb/read.md @@ -0,0 +1,157 @@ + + +# Reading from MongoDB using `DBReader` + +[`DBReader`](../../../db/db_reader.md#onetl.db.db_reader.db_reader.DBReader) supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, +but does not support custom pipelines, e.g. aggregation. + +#### WARNING +Please take into account [MongoDB <-> Spark type mapping](types.md#mongodb-types) + +## Supported DBReader features + +* ❌ `columns` (for now, all document fields are read) +* ✅︎ `where` (passed to `{"$match": ...}` aggregation pipeline) +* ✅︎ `hwm`, supported strategies: +* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) +* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) +* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) +* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) +* * Note that `expression` field of HWM can only be a field name, not a custom expression +* ✅︎ `hint` (see [official documentation](https://www.mongodb.com/docs/v5.0/reference/operator/meta/hint/)) +* ✅︎ `df_schema` (mandatory) +* ✅︎ `options` (see [`MongoDB.ReadOptions`](#onetl.connection.db_connection.mongodb.options.MongoDBReadOptions)) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import MongoDB +from onetl.db import DBReader + +from pyspark.sql.types import ( + StructType, + StructField, + IntegerType, + StringType, + TimestampType, +) + +mongodb = MongoDB(...) + +# mandatory +df_schema = StructType( + [ + StructField("_id", StringType()), + StructField("some", StringType()), + StructField( + "field", + StructType( + [ + StructField("nested", IntegerType()), + ], + ), + ), + StructField("updated_dt", TimestampType()), + ] +) + +reader = DBReader( + connection=mongodb, + source="some_collection", + df_schema=df_schema, + where={"field": {"$eq": 123}}, + hint={"field": 1}, + options=MongoDBReadOptions(batchSize=10000), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import MongoDB +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +from pyspark.sql.types import ( + StructType, + StructField, + IntegerType, + StringType, + TimestampType, +) + +mongodb = MongoDB(...) + +# mandatory +df_schema = StructType( + [ + StructField("_id", StringType()), + StructField("some", StringType()), + StructField( + "field", + StructType( + [ + StructField("nested", IntegerType()), + ], + ), + ), + StructField("updated_dt", TimestampType()), + ] +) + +reader = DBReader( + connection=mongodb, + source="some_collection", + df_schema=df_schema, + where={"field": {"$eq": 123}}, + hint={"field": 1}, + hwm=DBReader.AutoDetectHWM(name="mongodb_hwm", expression="updated_dt"), + options=MongoDBReadOptions(batchSize=10000), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where={"column": {"$eq": "value"}})` clause. +This both reduces the amount of data send from MongoDB to Spark, and may also improve performance of the query. +Especially if there are indexes for columns used in `where` clause. + +## Read options + +### *pydantic model* onetl.connection.db_connection.mongodb.options.MongoDBReadOptions + +Reading options for MongoDB connector. + +#### WARNING +Options `uri`, `database`, `collection`, `pipeline`, `hint` are populated from connection +attributes, and cannot be overridden by the user in `ReadOptions` to avoid issues. + +#### Versionadded +Added in version 0.7.0. + +### Examples + +#### NOTE +You can pass any value +[supported by connector](https://www.mongodb.com/docs/spark-connector/current/batch-mode/batch-read-config/), +even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! + +The set of supported options depends on connector version. + +```python +from onetl.connection import MongoDB + +options = MongoDB.ReadOptions( + sampleSize=100, +) +``` + + diff --git a/mddocs/connection/db_connection/mongodb/types.md b/mddocs/connection/db_connection/mongodb/types.md new file mode 100644 index 000000000..2d75b5e52 --- /dev/null +++ b/mddocs/connection/db_connection/mongodb/types.md @@ -0,0 +1,214 @@ + + +# MongoDB <-> Spark type mapping + +#### NOTE +The results below are valid for Spark 3.5.5, and may differ on other Spark versions. + +## Type detection & casting + +Spark’s DataFrames always have a `schema` which is a list of fields with corresponding Spark types. All operations on a field are performed using field type. + +MongoDB is, by design, \_\_schemaless_\_. So there are 2 ways how this can be handled: + +* User provides DataFrame schema explicitly: + + ### See example + + ```python + from onetl.connection import MongoDB + from onetl.db import DBReader + + from pyspark.sql.types import ( + StructType, + StructField, + IntegerType, + StringType, + TimestampType, + ) + + mongodb = MongoDB(...) + + df_schema = StructType( + [ + StructField("_id", StringType()), + StructField("some", StringType()), + StructField( + "field", + StructType( + [ + StructField("nested", IntegerType()), + ] + ), + ), + ] + ) + + reader = DBReader( + connection=mongodb, + source="some_collection", + df_schema=df_schema, + ) + df = reader.run() + + # or + + df = mongodb.pipeline( + collection="some_collection", + df_schema=df_schema, + ) + ``` +* Rely on MongoDB connector schema infer: + ```python + df = mongodb.pipeline(collection="some_collection") + ``` + + In this case MongoDB connector read a sample of collection documents, and build DataFrame schema based on document fields and values. + +It is highly recommended to pass `df_schema` explicitly, to avoid type conversion issues. + +### References + +Here you can find source code with type conversions: + +* [MongoDB -> Spark](https://github.com/mongodb/mongo-spark/blob/r10.4.1/src/main/java/com/mongodb/spark/sql/connector/schema/InferSchema.java#L214-L260) +* [Spark -> MongoDB](https://github.com/mongodb/mongo-spark/blob/r10.4.1/src/main/java/com/mongodb/spark/sql/connector/schema/RowToBsonDocumentConverter.java#L157-L260) + +## Supported types + +See [official documentation](https://www.mongodb.com/docs/manual/reference/bson-types/) + +### Numeric types + +| MongoDB type (read) | Spark type | MongoDB type (write) | +|-----------------------|---------------------------|------------------------| +| `Decimal128` | `DecimalType(P=34, S=32)` | `Decimal128` | +| `-` | `FloatType()` | `Double` | +| `Double` | `DoubleType()` | | +| `-` | `ByteType()` | `Int32` | +| `-` | `ShortType()` | | +| `Int32` | `IntegerType()` | | +| `Int64` | `LongType()` | `Int64` | + +### Temporal types + +| MongoDB type (read) | Spark type | MongoDB type (write) | +|-----------------------|---------------------------------|-------------------------------------------------------------------| +| `-` | `DateType()`, days | `Date`, milliseconds | +| `Date`, milliseconds | `TimestampType()`, microseconds | `Date`, milliseconds,
**precision loss** [2](#id2) | +| `Timestamp`, seconds | `TimestampType()`, microseconds | `Date`, milliseconds | +| `-` | `TimestampNTZType()` | unsupported | +| `-` | `DayTimeIntervalType()` | | + +#### WARNING +Note that types in MongoDB and Spark have different value ranges: + +| MongoDB type | Min value | Max value | Spark type | Min value | Max value | +|----------------|-----------------------|-----------------------|-------------------|------------------------------|------------------------------| +| `Date` | -290 million years | 290 million years | `TimestampType()` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | +| `Timestamp` | `1970-01-01 00:00:00` | `2106-02-07 09:28:16` | | | | + +So not all values can be read from MongoDB to Spark, and can written from Spark DataFrame to MongoDB. + +References: +: * [MongoDB Date type documentation](https://www.mongodb.com/docs/manual/reference/bson-types/#date) + * [MongoDB Timestamp documentation](https://www.mongodb.com/docs/manual/reference/bson-types/#timestamps) + * [Spark DateType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/DateType.html) + * [Spark TimestampType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/TimestampType.html) + +* **[2]** MongoDB `Date` type has precision up to milliseconds (`23:59:59.999`). Inserting data with microsecond precision (`23:59:59.999999`) will lead to **throwing away microseconds**. + +### String types + +Note: fields of deprecated MongoDB type `Symbol` are excluded during read. + +| MongoDB type (read) | Spark type | MongoDB type (write) | +|-----------------------|----------------|------------------------| +| `String` | `StringType()` | `String` | +| `Code` | | | +| `RegExp` | | | + +### Binary types + +| MongoDB type (read) | Spark type | MongoDB type (write) | +|-----------------------|-----------------|------------------------| +| `Boolean` | `BooleanType()` | `Boolean` | +| `Binary` | `BinaryType()` | `Binary` | + +### Struct types + +| MongoDB type (read) | Spark type | MongoDB type (write) | +|-----------------------|---------------------|------------------------| +| `Array[T]` | `ArrayType(T)` | `Array[T]` | +| `Object[...]` | `StructType([...])` | `Object[...]` | +| `-` | `MapType(...)` | | + +### Special types + +| MongoDB type (read) | Spark type | MongoDB type (write) | +|-----------------------|-------------------------------------------------------|-------------------------------------| +| `ObjectId` | `StringType()` | `String` | +| `MaxKey` | | | +| `MinKey` | | | +| `Null` | `NullType()` | `Null` | +| `Undefined` | | | +| `DBRef` | `StructType([$ref: StringType(), $id: StringType()])` | `Object[$ref: String, $id: String]` | + +## Explicit type cast + +### `DBReader` + +Currently it is not possible to cast field types using `DBReader`. But this can be done using `MongoDB.pipeline`. + +### `MongoDB.pipeline` + +You can use `$project` aggregation to cast field types: + +```python +from pyspark.sql.types import IntegerType, StructField, StructType + +from onetl.connection import MongoDB +from onetl.db import DBReader + +mongodb = MongoDB(...) + +df = mongodb.pipeline( + collection="my_collection", + pipeline=[ + { + "$project": { + # convert unsupported_field to string + "unsupported_field_str": { + "$convert": { + "input": "$unsupported_field", + "to": "string", + }, + }, + # skip unsupported_field from result + "unsupported_field": 0, + } + } + ], +) + +# cast field content to proper Spark type +df = df.select( + df.id, + df.supported_field, + # explicit cast + df.unsupported_field_str.cast("integer").alias("parsed_integer"), +) +``` + +### `DBWriter` + +Convert dataframe field to string on Spark side, and then write it to MongoDB: + +```python +df = df.select( + df.id, + df.unsupported_field.cast("string").alias("array_field_json"), +) + +writer.run(df) +``` diff --git a/mddocs/connection/db_connection/mongodb/write.md b/mddocs/connection/db_connection/mongodb/write.md new file mode 100644 index 000000000..a0e3778c1 --- /dev/null +++ b/mddocs/connection/db_connection/mongodb/write.md @@ -0,0 +1,118 @@ + + +# Writing to MongoDB using `DBWriter` + +For writing data to MongoDB, use [`DBWriter`](../../../db/db_writer.md#onetl.db.db_writer.db_writer.DBWriter). + +#### WARNING +Please take into account [MongoDB <-> Spark type mapping](types.md#mongodb-types) + +## Examples + +```python +from onetl.connection import MongoDB +from onetl.db import DBWriter + +mongodb = MongoDB(...) + +df = ... # data is here + +writer = DBWriter( + connection=mongodb, + target="schema.table", + options=MongoDB.WriteOptions( + if_exists="append", + ), +) + +writer.run(df) +``` + +## Write options + +Method above accepts [`MongoDB.WriteOptions`](#onetl.connection.db_connection.mongodb.options.MongoDBWriteOptions) + +### *pydantic model* onetl.connection.db_connection.mongodb.options.MongoDBWriteOptions + +Writing options for MongoDB connector. + +#### WARNING +Options `uri`, `database`, `collection` are populated from connection attributes, +and cannot be overridden by the user in `WriteOptions` to avoid issues. + +#### Versionadded +Added in version 0.7.0. + +### Examples + +#### NOTE +You can pass any value +[supported by connector](https://www.mongodb.com/docs/spark-connector/current/batch-mode/batch-write-config/), +even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! + +The set of supported options depends on connector version. + +```python +from onetl.connection import MongoDB + +options = MongoDB.WriteOptions( + if_exists="append", + sampleSize=500, + localThreshold=20, +) +``` + + + +#### *field* if_exists *: MongoDBCollectionExistBehavior* *= MongoDBCollectionExistBehavior.APPEND* *(alias 'mode')* + +Behavior of writing data into existing collection. + +Possible values: +: * `append` (default) + : Adds new objects into existing collection. +
+ ### Behavior in details +
+ * Collection does not exist + : Collection is created using options provided by user + (`shardkey` and others). + * Collection exists + : Data is appended to a collection. +
+ #### WARNING + This mode does not check whether collection already contains + objects from dataframe, so duplicated objects can be created. + * `replace_entire_collection` + : **Collection is deleted and then created**. +
+ ### Behavior in details +
+ * Collection does not exist + : Collection is created using options provided by user + (`shardkey` and others). + * Collection exists + : Collection content is replaced with dataframe content. + * `ignore` + : Ignores the write operation if the collection already exists. +
+ ### Behavior in details +
+ * Collection does not exist + : Collection is created using options provided by user + * Collection exists + : The write operation is ignored, and no data is written to the collection. + * `error` + : Raises an error if the collection already exists. +
+ ### Behavior in details +
+ * Collection does not exist + : Collection is created using options provided by user + * Collection exists + : An error is raised, and no data is written to the collection. + +#### Versionchanged +Changed in version 0.9.0: Renamed `mode` → `if_exists` + + diff --git a/mddocs/connection/db_connection/mssql/connection.md b/mddocs/connection/db_connection/mssql/connection.md new file mode 100644 index 000000000..3e35a34fb --- /dev/null +++ b/mddocs/connection/db_connection/mssql/connection.md @@ -0,0 +1,186 @@ + + +# MSSQL connection + +### *class* onetl.connection.db_connection.mssql.connection.MSSQL(\*, spark: SparkSession, user: str, password: SecretStr, database: str, host: Host, port: int | None = None, extra: MSSQLExtra = MSSQLExtra()) + +MSSQL JDBC connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on Maven package [com.microsoft.sqlserver:mssql-jdbc:12.8.1.jre8](https://mvnrepository.com/artifact/com.microsoft.sqlserver/mssql-jdbc/12.8.1.jre8) +([official MSSQL JDBC driver](https://docs.microsoft.com/en-us/sql/connect/jdbc/download-microsoft-jdbc-driver-for-sql-server)). + +#### SEE ALSO +Before using this connector please take into account [Prerequisites](prerequisites.md#mssql-prerequisites) + +* **Parameters:** + **host** + : Host of MSSQL database. For example: `test.mssql.domain.com` or `192.168.1.14` + + **port** + : Port of MSSQL database +
+ #### Versionchanged + Changed in version 0.11.1: Default value was changed from `1433` to `None`, + to allow automatic port discovery with `instanceName`. + + **user** + : User, which have proper access to the database. For example: `some_user` + + **password** + : Password for database connection + + **database** + : Database in RDBMS, NOT schema. +
+ See [this page](https://www.educba.com/postgresql-database-vs-schema/) for more details + + **spark** + : Spark session. + + **extra** + : Specifies one or more extra parameters by which clients can connect to the instance. +
+ For example: `{"connectRetryCount": 3, "connectRetryInterval": 10}` +
+ See [MSSQL JDBC driver properties documentation](https://learn.microsoft.com/en-us/sql/connect/jdbc/setting-the-connection-properties#properties) + for more details + +### Examples + +Create MSSQL connection with plain auth + +```py +from onetl.connection import MSSQL +from pyspark.sql import SparkSession + +# Create Spark session with MSSQL driver loaded +maven_packages = MSSQL.get_packages() +spark = ( + SparkSession.builder.appName("spark-app-name") + .config("spark.jars.packages", ",".join(maven_packages)) + .getOrCreate() +) + +# Create connection +mssql = MSSQL( + host="database.host.or.ip", + port=1433, + user="user", + password="*****", + extra={ + "trustServerCertificate": "true", # add this to avoid SSL certificate issues + }, + spark=spark, +) +``` + +Create MSSQL connection with domain auth + +```py +# Create Spark session with MSSQL driver loaded +... + +# Create connection +mssql = MSSQL( + host="database.host.or.ip", + port=1433, + user="user", + password="*****", + extra={ + "domain": "some.domain.com", # add here your domain + "integratedSecurity": "true", + "authenticationScheme": "NTLM", + "trustServerCertificate": "true", # add this to avoid SSL certificate issues + }, + spark=spark, +) +``` + +Create MSSQL connection with instance name + +```py +# Create Spark session with MSSQL driver loaded +... + +# Create connection +mssql = MSSQL( + host="database.host.or.ip", + # !!! no port !!! + user="user", + password="*****", + extra={ + "instanceName": "myinstance", # add here your instance name + "trustServerCertificate": "true", # add this to avoid SSL certificate issues + }, + spark=spark, +) +``` + +Create MSSQL read-only connection + +```py +# Create Spark session with MSSQL driver loaded +... + +# Create connection +mssql = MSSQL( + host="database.host.or.ip", + port=1433, + user="user", + password="*****", + extra={ + "applicationIntent": "ReadOnly", # driver will open read-only connection, to avoid writing to the database + "trustServerCertificate": "true", # add this to avoid SSL certificate issues + }, + spark=spark, +).check() +``` + + + +#### check() + +Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If not, an exception will be raised. + +* **Returns:** + Connection itself +* **Raises:** + RuntimeError + : If the connection is not available + +### Examples + +```python +connection.check() +``` + + + +#### *classmethod* get_packages(java_version: str | None = None, package_version: str | None = None) → list[str] + +Get package names to be downloaded by Spark. Allows specifying custom JDBC driver versions for MSSQL. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **java_version** + : Java major version, defaults to `8`. Must be `8` or `11`. + + **package_version** + : Specifies the version of the MSSQL JDBC driver to use. Defaults to `12.8.1.`. + +### Examples + +```python +from onetl.connection import MSSQL + +MSSQL.get_packages() + +# specify Java and package versions +MSSQL.get_packages(java_version="8", package_version="12.8.1.jre11") +``` + + diff --git a/mddocs/connection/db_connection/mssql/execute.md b/mddocs/connection/db_connection/mssql/execute.md new file mode 100644 index 000000000..7655175c8 --- /dev/null +++ b/mddocs/connection/db_connection/mssql/execute.md @@ -0,0 +1,184 @@ + + +# Executing statements in MSSQL + +#### WARNING +Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + +Do **NOT** use them to read large amounts of data. Use [DBReader](read.md#mssql-read) or [MSSQL.sql](sql.md#mssql-sql) instead. + +## How to + +There are 2 ways to execute some statement in MSSQL + +### Use `MSSQL.fetch` + +Use this method to perform some `SELECT` query which returns **small number or rows**, like reading +MSSQL config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts [`MSSQL.FetchOptions`](#onetl.connection.db_connection.mssql.options.MSSQLFetchOptions). + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### WARNING +Please take into account [MSSQL <-> Spark type mapping](types.md#mssql-types). + +#### Syntax support + +This method supports **any** query syntax supported by MSSQL, like: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ✅︎ `SELECT func(arg1, arg2) FROM DUAL` - call function +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import MSSQL + +mssql = MSSQL(...) + +df = mssql.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=MSSQL.FetchOptions(queryTimeout=10), +) +mssql.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `MSSQL.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts [`MSSQL.ExecuteOptions`](#onetl.connection.db_connection.mssql.options.MSSQLExecuteOptions). + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by MSSQL, like: + +* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...` +* ✅︎ `ALTER ...` +* ✅︎ `INSERT INTO ... AS SELECT ...` +* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +* ✅︎ `EXEC procedure(arg1, arg2) ...` or `{call procedure(arg1, arg2)}` - special syntax for calling procedure +* ✅︎ `DECLARE ... BEGIN ... END` - execute PL/SQL statement +* ✅︎ other statements not mentioned here +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import MSSQL + +mssql = MSSQL(...) + +mssql.execute("DROP TABLE schema.table") +mssql.execute( + """ + CREATE TABLE schema.table ( + id bigint GENERATED ALWAYS AS IDENTITY, + key VARCHAR2(4000), + value NUMBER + ) + """, + options=MSSQL.ExecuteOptions(queryTimeout=10), +) +``` + +## Options + +### *pydantic model* onetl.connection.db_connection.mssql.options.MSSQLFetchOptions + +Options related to fetching data from databases via JDBC. + +#### Versionadded +Added in version 0.11.0: Replace `MSSQL.JDBCOptions` → `MSSQL.FetchOptions` + +### Examples + +#### NOTE +You can pass any value supported by underlying JDBC driver class, +even if it is not mentioned in this documentation. + +```python +from onetl.connection import MSSQL + +options = MSSQL.FetchOptions( + queryTimeout=60_000, + fetchsize=100_000, + customSparkOption="value", +) +``` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int | None* *= None* + +How many rows to fetch per round trip. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value depends on driver. For example, Oracle has +default `fetchsize=10`. + + + +### *pydantic model* onetl.connection.db_connection.mssql.options.MSSQLExecuteOptions + +Options related to executing statements in databases via JDBC. + +#### Versionadded +Added in version 0.11.0: Replace `MSSQL.JDBCOptions` → `MSSQL.ExecuteOptions` + +### Examples + +#### NOTE +You can pass any value supported by underlying JDBC driver class, +even if it is not mentioned in this documentation. + +```python +from onetl.connection import MSSQL + +options = MSSQL.ExecuteOptions( + queryTimeout=60_000, + customSparkOption="value", +) +``` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int | None* *= None* + +How many rows to fetch per round trip. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value depends on driver. For example, Oracle has +default `fetchsize=10`. + + diff --git a/mddocs/connection/db_connection/mssql/index.md b/mddocs/connection/db_connection/mssql/index.md new file mode 100644 index 000000000..a35b4acbb --- /dev/null +++ b/mddocs/connection/db_connection/mssql/index.md @@ -0,0 +1,19 @@ + + +# MSSQL + +# Connection + +* [Prerequisites](prerequisites.md) +* [MSSQL connection](connection.md) + +# Operations + +* [Reading from MSSQL using `DBReader`](read.md) +* [Reading from MSSQL using `MSSQL.sql`](sql.md) +* [Writing to MSSQL using `DBWriter`](write.md) +* [Executing statements in MSSQL](execute.md) + +# Troubleshooting + +* [MSSQL <-> Spark type mapping](types.md) diff --git a/mddocs/connection/db_connection/mssql/prerequisites.md b/mddocs/connection/db_connection/mssql/prerequisites.md new file mode 100644 index 000000000..e103ced3e --- /dev/null +++ b/mddocs/connection/db_connection/mssql/prerequisites.md @@ -0,0 +1,78 @@ + + +# Prerequisites + +## Version Compatibility + +* SQL Server versions: + : * Officially declared: 2016 - 2022 + * Actually tested: 2017, 2022 +* Spark versions: 2.3.x - 3.5.x +* Java versions: 8 - 20 + +See [official documentation](https://learn.microsoft.com/en-us/sql/connect/jdbc/system-requirements-for-the-jdbc-driver) +and [official compatibility matrix](https://learn.microsoft.com/en-us/sql/connect/jdbc/microsoft-jdbc-driver-for-sql-server-support-matrix). + +## Installing PySpark + +To use MSSQL connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Connecting to MSSQL + +### Connection port + +Connection is usually performed to port 1433. Port may differ for different MSSQL instances. +Please ask your MSSQL administrator to provide required information. + +For named MSSQL instances (`instanceName` option), [port number is optional](https://learn.microsoft.com/en-us/sql/connect/jdbc/building-the-connection-url?view=sql-server-ver16#named-and-multiple-sql-server-instances), and could be omitted. + +### Connection host + +It is possible to connect to MSSQL by using either DNS name of host or it’s IP address. + +If you’re using MSSQL cluster, it is currently possible to connect only to **one specific node**. +Connecting to multiple nodes to perform load balancing, as well as automatic failover to new master/replica are not supported. + +### Required grants + +Ask your MSSQL cluster administrator to set following grants for a user, +used for creating a connection: + +Read + Write (schema is owned by user) + +```sql +-- allow creating tables for user +GRANT CREATE TABLE TO username; + +-- allow read & write access to specific table +GRANT SELECT, INSERT ON username.mytable TO username; + +-- only if if_exists="replace_entire_table" is used: +-- allow dropping/truncating tables in any schema +GRANT ALTER ON username.mytable TO username; +``` + +Read + Write (schema is not owned by user) + +```sql +-- allow creating tables for user +GRANT CREATE TABLE TO username; + +-- allow managing tables in specific schema, and inserting data to tables +GRANT ALTER, SELECT, INSERT ON SCHEMA::someschema TO username; +``` + +Read only + +```sql +-- allow read access to specific table +GRANT SELECT ON someschema.mytable TO username; +``` + +More details can be found in official documentation: +: * [GRANT ON DATABASE](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-database-permissions-transact-sql) + * [GRANT ON OBJECT](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-object-permissions-transact-sql) + * [GRANT ON SCHEMA](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-schema-permissions-transact-sql) diff --git a/mddocs/connection/db_connection/mssql/read.md b/mddocs/connection/db_connection/mssql/read.md new file mode 100644 index 000000000..9dc3dc88d --- /dev/null +++ b/mddocs/connection/db_connection/mssql/read.md @@ -0,0 +1,339 @@ + + +# Reading from MSSQL using `DBReader` + +[`DBReader`](../../../db/db_reader.md#onetl.db.db_reader.db_reader.DBReader) supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, +but does not support custom queries, like `JOIN`. + +#### WARNING +Please take into account [MSSQL <-> Spark type mapping](types.md#mssql-types) + +## Supported DBReader features + +* ✅︎ `columns` +* ✅︎ `where` +* ✅︎ `hwm`, supported strategies: +* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) +* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) +* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) +* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) +* ❌ `hint` (MSSQL does support hints, but DBReader not, at least for now) +* ❌ `df_schema` +* ✅︎ `options` (see [`MSSQL.ReadOptions`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions)) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import MSSQL +from onetl.db import DBReader + +mssql = MSSQL(...) + +reader = DBReader( + connection=mssql, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + options=MSSQL.ReadOptions(partitionColumn="id", numPartitions=10), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import MSSQL +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +mssql = MSSQL(...) + +reader = DBReader( + connection=mssql, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="mssql_hwm", expression="updated_dt"), + options=MSSQL.ReadOptions(partitionColumn="id", numPartitions=10), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from MSSQL to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from MSSQL to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +### *pydantic model* onetl.connection.db_connection.mssql.options.MSSQLReadOptions + +Spark JDBC reading options. + +#### Versionadded +Added in version 0.5.0: Replace `MSSQL.Options` → `MSSQL.ReadOptions` + +### Examples + +#### NOTE +You can pass any value +[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), +even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! + +The set of supported options depends on Spark version. + +```python +from onetl.connection import MSSQL + +options = MSSQL.ReadOptions( + partitioning_mode="range", + partitionColumn="reg_id", + numPartitions=10, + customSparkOption="value", +) +``` + + + +#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* + +Column used to parallelize reading from a table. + +#### WARNING +It is highly recommended to use primary key, or column with an index +to avoid performance issues. + +#### NOTE +Column type depends on [`partitioning_mode`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions.partitioning_mode). + +* `partitioning_mode="range"` requires column to be an integer, date or timestamp (can be NULL, but not recommended). +* `partitioning_mode="hash"` accepts any column type (NOT NULL). +* `partitioning_mode="mod"` requires column to be an integer (NOT NULL). + +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions.partitioning_mode) for more details + + + +#### *field* num_partitions *: PositiveInt* *= 1* *(alias 'numPartitions')* + +Number of jobs created by Spark to read the table content in parallel. +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions.partitioning_mode) for more details + + + +#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* + +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions.partitioning_mode) for more details + + + +#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* + +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions.partitioning_mode) for more details + + + +#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* + +After each database session is opened to the remote DB and before starting to read data, +this option executes a custom SQL statement (or a PL/SQL block). + +Use this to implement session initialization code. + +Example: + +```python +sessionInitStatement = """ + BEGIN + execute immediate + 'alter session set "_serial_direct_read"=true'; + END; +""" +``` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int* *= 100000* + +Fetch N rows from an opened cursor per one read round. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value is different from Spark. + +Spark uses driver’s own value, and it may be different in different drivers, +and even versions of the same driver. For example, Oracle has +default `fetchsize=10`, which is absolutely not usable. + +Thus we’ve overridden default value with `100_000`, which should increase reading performance. + +#### Versionchanged +Changed in version 0.2.0: Set explicit default value to `100_000` + + + +#### *field* partitioning_mode *: JDBCPartitioningMode* *= JDBCPartitioningMode.RANGE* + +Defines how Spark will parallelize reading from table. + +Possible values: + +* `range` (default) + : Allocate each executor a range of values from column passed into [`partition_column`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions.partition_column). +
+ ### Spark generates for each executor an SQL query +
+ Executor 1: + ```sql + SELECT ... FROM table + WHERE (partition_column >= lowerBound + OR partition_column IS NULL) + AND partition_column < (lower_bound + stride) + ``` +
+ Executor 2: + ```sql + SELECT ... FROM table + WHERE partition_column >= (lower_bound + stride) + AND partition_column < (lower_bound + 2 * stride) + ``` +
+ … +
+ Executor N: + ```sql + SELECT ... FROM table + WHERE partition_column >= (lower_bound + (N-1) * stride) + AND partition_column <= upper_bound + ``` +
+ Where `stride=(upper_bound - lower_bound) / num_partitions`. +
+ #### NOTE + Can be used only with columns of integer, date or timestamp types. +
+ #### NOTE + [`lower_bound`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions.lower_bound), [`upper_bound`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions.upper_bound) and [`num_partitions`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions.num_partitions) are used just to + calculate the partition stride, **NOT** for filtering the rows in table. + So all rows in the table will be returned (unlike *Incremental* [Read Strategies](../../../strategy/index.md#strategy)). +
+ #### NOTE + All queries are executed in parallel. To execute them sequentially, use *Batch* [Read Strategies](../../../strategy/index.md#strategy). +* `hash` + : Allocate each executor a set of values based on hash of the [`partition_column`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions.partition_column) column. +
+ ### Spark generates for each executor an SQL query +
+ Executor 1: + ```sql + SELECT ... FROM table + WHERE (some_hash(partition_column) mod num_partitions) = 0 -- lower_bound + ``` +
+ Executor 2: + ```sql + SELECT ... FROM table + WHERE (some_hash(partition_column) mod num_partitions) = 1 -- lower_bound + 1 + ``` +
+ … +
+ Executor N: + ```sql + SELECT ... FROM table + WHERE (some_hash(partition_column) mod num_partitions) = num_partitions-1 -- upper_bound + ``` +
+ #### NOTE + The hash function implementation depends on RDBMS. It can be `MD5` or any other fast hash function, + or expression based on this function call. Usually such functions accepts any column type as an input. +* `mod` + : Allocate each executor a set of values based on modulus of the [`partition_column`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions.partition_column) column. +
+ ### Spark generates for each executor an SQL query +
+ Executor 1: + ```sql + SELECT ... FROM table + WHERE (partition_column mod num_partitions) = 0 -- lower_bound + ``` +
+ Executor 2: + ```sql + SELECT ... FROM table + WHERE (partition_column mod num_partitions) = 1 -- lower_bound + 1 + ``` +
+ Executor N: + ```sql + SELECT ... FROM table + WHERE (partition_column mod num_partitions) = num_partitions-1 -- upper_bound + ``` +
+ #### NOTE + Can be used only with columns of integer type. + +#### Versionadded +Added in version 0.5.0. + +### Examples + +Read data in 10 parallel jobs by range of values in `id_column` column: + +```python +ReadOptions( + partitioning_mode="range", # default mode, can be omitted + partitionColumn="id_column", + numPartitions=10, + # Options below can be discarded because they are + # calculated automatically as MIN and MAX values of `partitionColumn` + lowerBound=0, + upperBound=100_000, +) +``` + +Read data in 10 parallel jobs by hash of values in `some_column` column: + +```python +ReadOptions( + partitioning_mode="hash", + partitionColumn="some_column", + numPartitions=10, + # lowerBound and upperBound are automatically set to `0` and `9` +) +``` + +Read data in 10 parallel jobs by modulus of values in `id_column` column: + +```python +ReadOptions( + partitioning_mode="mod", + partitionColumn="id_column", + numPartitions=10, + # lowerBound and upperBound are automatically set to `0` and `9` +) +``` + + diff --git a/mddocs/connection/db_connection/mssql/sql.md b/mddocs/connection/db_connection/mssql/sql.md new file mode 100644 index 000000000..93d2fb3ef --- /dev/null +++ b/mddocs/connection/db_connection/mssql/sql.md @@ -0,0 +1,191 @@ + + +# Reading from MSSQL using `MSSQL.sql` + +`MSSQL.sql` allows passing custom SQL query, but does not support incremental strategies. + +#### WARNING +Please take into account [MSSQL <-> Spark type mapping](types.md#mssql-types) + +#### WARNING +Statement is executed in **read-write** connection, so if you’re calling some functions/procedures with DDL/DML statements inside, +they can change data in your database. + +## Syntax support + +Only queries with the following syntax are supported: + +* ✅︎ `SELECT ... FROM ...` +* ❌ `WITH alias AS (...) SELECT ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import MSSQL + +mssql = MSSQL(...) +df = mssql.sql( + """ + SELECT + id, + key, + CAST(value AS text) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """, + options=MSSQL.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from MSSQL to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from MSSQL to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +### *pydantic model* onetl.connection.db_connection.mssql.options.MSSQLSQLOptions + +Options specifically for SQL queries + +These options allow you to specify configurations for executing SQL queries +without relying on Spark’s partitioning mechanisms. + +#### Versionadded +Added in version 0.11.0: Split up `MSSQL.ReadOptions` to `MSSQL.SQLOptions` + +### Examples + +#### NOTE +You can pass any JDBC configuration +[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), +tailored to optimize SQL query execution. **Option names should be in** `camelCase`! + +```python +from onetl.connection import MSSQL + +options = MSSQL.SQLOptions( + partitionColumn="reg_id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + customSparkOption="value", +) +``` + + + +#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* + +Column used to partition data across multiple executors for parallel query processing. + +#### WARNING +It is highly recommended to use primary key, or column with an index +to avoid performance issues. + +### Example of using `partitionColumn="id"` with `partitioning_mode="range"` + +```sql +-- If partition_column is 'id', with numPartitions=4, lowerBound=1, and upperBound=100: +-- Executor 1 processes IDs from 1 to 25 +SELECT ... FROM table WHERE id >= 1 AND id < 26 +-- Executor 2 processes IDs from 26 to 50 +SELECT ... FROM table WHERE id >= 26 AND id < 51 +-- Executor 3 processes IDs from 51 to 75 +SELECT ... FROM table WHERE id >= 51 AND id < 76 +-- Executor 4 processes IDs from 76 to 100 +SELECT ... FROM table WHERE id >= 76 AND id <= 100 + +-- General case for Executor N +SELECT ... FROM table +WHERE partition_column >= (lowerBound + (N-1) * stride) +AND partition_column <= upperBound +-- Where ``stride`` is calculated as ``(upperBound - lowerBound) / numPartitions``. +``` + + + +#### *field* num_partitions *: int | None* *= None* *(alias 'numPartitions')* + +Number of jobs created by Spark to read the table content in parallel. + + + +#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* + +Defines the starting boundary for partitioning the query’s data. Mandatory if [`partition_column`](#onetl.connection.db_connection.mssql.options.MSSQLSQLOptions.partition_column) is set + + + +#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* + +Sets the ending boundary for data partitioning. Mandatory if [`partition_column`](#onetl.connection.db_connection.mssql.options.MSSQLSQLOptions.partition_column) is set + + + +#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* + +After each database session is opened to the remote DB and before starting to read data, +this option executes a custom SQL statement (or a PL/SQL block). + +Use this to implement session initialization code. + +Example: + +```python +sessionInitStatement = """ + BEGIN + execute immediate + 'alter session set "_serial_direct_read"=true'; + END; +""" +``` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int* *= 100000* + +Fetch N rows from an opened cursor per one read round. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value is different from Spark. + +Spark uses driver’s own value, and it may be different in different drivers, +and even versions of the same driver. For example, Oracle has +default `fetchsize=10`, which is absolutely not usable. + +Thus we’ve overridden default value with `100_000`, which should increase reading performance. + +#### Versionchanged +Changed in version 0.2.0: Set explicit default value to `100_000` + + diff --git a/mddocs/connection/db_connection/mssql/types.md b/mddocs/connection/db_connection/mssql/types.md new file mode 100644 index 000000000..5981107ad --- /dev/null +++ b/mddocs/connection/db_connection/mssql/types.md @@ -0,0 +1,281 @@ + + +# MSSQL <-> Spark type mapping + +#### NOTE +The results below are valid for Spark 3.5.5, and may differ on other Spark versions. + +## Type detection & casting + +Spark’s DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from MSSQL + +This is how MSSQL connector performs this: + +* For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and MSSQL type. +* Find corresponding `MSSQL type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* Create DataFrame from query with specific column names and Spark types. + +### Writing to some existing MSSQL table + +This is how MSSQL connector performs this: + +* Get names of columns in DataFrame. [1](#id3) +* Perform `SELECT * FROM table LIMIT 0` query. +* Take only columns present in DataFrame (by name, case insensitive). For each found column get MSSQL type. +* Find corresponding `Spark type` → `MSSQL type (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* If `MSSQL type (write)` match `MSSQL type (read)`, no additional casts will be performed, DataFrame column will be written to MSSQL as is. +* If `MSSQL type (write)` does not match `MSSQL type (read)`, DataFrame column will be casted to target column type **on MSSQL side**. + For example, you can write column with text data to `int` column, if column contains valid integer values within supported value range and precision [2](#id4). + +* **[1]** This allows to write data to tables with `DEFAULT` and `GENERATED` columns - if DataFrame has no such column, it will be populated by MSSQL. +* **[2]** This is true only if DataFrame column is a `StringType()`, because text value is parsed automatically to target column type. But other types cannot be silently converted, like `int -> text`. This requires explicit casting, see [DBWriter](). + +### Create new table using Spark + +#### WARNING +ABSOLUTELY NOT RECOMMENDED! + +This is how MSSQL connector performs this: + +* Find corresponding `Spark type` → `MSSQL type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* Generate DDL for creating table in MSSQL, like `CREATE TABLE (col1 ...)`, and run it. +* Write DataFrame to created table as is. + +But some cases this may lead to using wrong column type. For example, Spark creates column of type `timestamp` +which corresponds to MSSQL’s type `timestamp(0)` (precision up to seconds) +instead of more precise `timestamp(6)` (precision up to nanoseconds). +This may lead to incidental precision loss, or sometimes data cannot be written to created table at all. + +So instead of relying on Spark to create tables: + +### See example + +```python +writer = DBWriter( + connection=mssql, + target="myschema.target_tbl", + options=MSSQL.WriteOptions( + if_exists="append", + ), +) +writer.run(df) +``` + +Always prefer creating tables with specific types **BEFORE WRITING DATA**: + +### See example + +```python +mssql.execute( + """ + CREATE TABLE schema.table ( + id bigint, + key text, + value datetime2(6) -- specific type and precision + ) + """, +) + +writer = DBWriter( + connection=mssql, + target="myschema.target_tbl", + options=MSSQL.WriteOptions(if_exists="append"), +) +writer.run(df) +``` + +### References + +Here you can find source code with type conversions: + +* [MSSQL -> JDBC](https://github.com/microsoft/mssql-jdbc/blob/v12.2.0/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSetMetaData.java#L117-L170) +* [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MsSqlServerDialect.scala#L135-L152) +* [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MsSqlServerDialect.scala#L154-L163) +* [JDBC -> MSSQL](https://github.com/microsoft/mssql-jdbc/blob/v12.2.0/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java#L625-L676) + +## Supported types + +See [official documentation](https://learn.microsoft.com/en-us/sql/t-sql/data-types/data-types-transact-sql) + +### Numeric types + +| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | +|-----------------------------|---------------------------------|-----------------------------|-----------------------------| +| `decimal` | `DecimalType(P=18, S=0)` | `decimal(P=18, S=0)` | `decimal(P=18, S=0)` | +| `decimal(P=0..38)` | `DecimalType(P=0..38, S=0)` | `decimal(P=0..38, S=0)` | `decimal(P=0..38, S=0)` | +| `decimal(P=0..38, S=0..38)` | `DecimalType(P=0..38, S=0..38)` | `decimal(P=0..38, S=0..38)` | `decimal(P=0..38, S=0..38)` | +| `real` | `FloatType()` | `real` | `real` | +| `float` | `DoubleType()` | `float` | `float` | +| `smallint` | `ShortType()` | `smallint` | `smallint` | +| `tinyint` | `IntegerType()` | `int` | `int` | +| `int` | | | | +| `bigint` | `LongType()` | `bigint` | `bigint` | + +### Temporal types + +#### NOTE +MSSQL `timestamp` type is alias for `rowversion` (see [Special types]()). It is not a temporal type! + +| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | +|-------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------|------------------------------------------------------------------------| +| `date` | `DateType()` | `date` | `date` | +| `smalldatetime`, minutes | `TimestampType()`, microseconds | `datetime2(6)`, microseconds | `datetime`, milliseconds | +| `datetime`, milliseconds | | | | +| `datetime2(0)`, seconds | | | | +| `datetime2(3)`, milliseconds | | | | +| `datetime2(6)`, microseconds | `TimestampType()`, microseconds | `datetime2(6)`, microseconds | `datetime`, milliseconds,
**precision loss** [3](#id14) | +| `datetime2(7)`, 100s of nanoseconds | `TimestampType()`, microseconds,
**precision loss** [4](#id15) | `datetime2(6)`, microseconds,
**precision loss** [4](#id15) | | +| `time(0)`, seconds | `TimestampType()`, microseconds,
with time format quirks [5](#id16) | `datetime2(6)`, microseconds | `datetime`, milliseconds | +| `time(3)`, milliseconds | | | | +| `time(6)`, microseconds | `TimestampType()`, microseconds,
with time format quirks [5](#id16) | `datetime2(6)`, microseconds | `datetime`, milliseconds,
**precision loss** [3](#id14) | +| | | | | +| `time`, 100s of nanoseconds | `TimestampType()`, microseconds,
**precision loss** [4](#id15),
with time format quirks [5](#id16) | `datetime2(6)`, microseconds
**precision loss** [3](#id14) | | +| `time(7)`, 100s of nanoseconds | | | | +| `datetimeoffset` | `StringType()` | `nvarchar` | `nvarchar` | + +#### WARNING +Note that types in MSSQL and Spark have different value ranges: + +| MySQL type | Min value | Max value | Spark type | Min value | Max value | +|-----------------|------------------------------|------------------------------|-------------------|------------------------------|------------------------------| +| `smalldatetime` | `1900-01-01 00:00:00` | `2079-06-06 23:59:00` | `TimestampType()` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | +| `datetime` | `1753-01-01 00:00:00.000` | `9999-12-31 23:59:59.997` | | | | +| `datetime2` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | | | | +| `time` | `00:00:00.0000000` | `23:59:59.9999999` | | | | + +So not all of values in Spark DataFrame can be written to MSSQL. + +References: +: * [MSSQL date & time types documentation](https://learn.microsoft.com/en-us/sql/t-sql/data-types/date-and-time-types) + * [Spark DateType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/DateType.html) + * [Spark TimestampType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/TimestampType.html) + +* **[3]** MSSQL dialect for Spark generates DDL with type `datetime` which has precision up to milliseconds (`23:59:59.999`, 10-3 seconds). Inserting data with microsecond and higher precision (`23:59:59.999999` .. `23.59:59.9999999`, 10-6 .. 10-7 seconds) will lead to **throwing away microseconds**. +* **[4]** MSSQL support timestamp up to 100s of nanoseconds precision (`23:59:59.9999999999`, 10-7 seconds), but Spark `TimestampType()` supports datetime up to microseconds precision (`23:59:59.999999`, 10-6 seconds). Last digit will be lost during read or write operations. +* **[5]** `time` type is the same as `datetime2` with date `1970-01-01`. So instead of reading data from MSSQL like `23:59:59.999999` it is actually read `1970-01-01 23:59:59.999999`, and vice versa. + +### String types + +| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | +|---------------------|----------------|----------------------|-----------------------| +| `char` | `StringType()` | `nvarchar` | `nvarchar` | +| `char(N)` | | | | +| `nchar` | | | | +| `nchar(N)` | | | | +| `varchar` | | | | +| `varchar(N)` | | | | +| `nvarchar` | | | | +| `nvarchar(N)` | | | | +| `mediumtext` | | | | +| `text` | | | | +| `ntext` | | | | +| `xml` | | | | + +### Binary types + +| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | +|---------------------|-----------------|----------------------|-----------------------| +| `bit` | `BooleanType()` | `bit` | `bit` | +| `binary` | `BinaryType()` | `varbinary` | `varbinary` | +| `binary(N)` | | | | +| `varbinary` | | | | +| `varbinary(N)` | | | | +| `image` | | | | + +### Special types + +| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | +|---------------------|----------------|----------------------|-----------------------| +| `geography` | `BinaryType()` | `varbinary` | `varbinary` | +| `geometry` | | | | +| `hierarchyid` | | | | +| `rowversion` | | | | +| `sql_variant` | unsupported | | | +| `sysname` | `StringType()` | `nvarchar` | `nvarchar` | +| `uniqueidentifier` | | | | + +## Explicit type cast + +### `DBReader` + +It is possible to explicitly cast column type using `DBReader(columns=...)` syntax. + +For example, you can use `CAST(column AS text)` to convert data to string representation on MSSQL side, and so it will be read as Spark’s `StringType()`: + +```python +from onetl.connection import MSSQL +from onetl.db import DBReader + +mssql = MSSQL(...) + +DBReader( + connection=mssql, + columns=[ + "id", + "supported_column", + "CAST(unsupported_column AS text) unsupported_column_str", + ], +) +df = reader.run() + +# cast column content to proper Spark type +df = df.select( + df.id, + df.supported_column, + # explicit cast + df.unsupported_column_str.cast("integer").alias("parsed_integer"), +) +``` + +### `DBWriter` + +Convert dataframe column to JSON using [to_json](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.functions.to_json.html), +and write it as `text` column in MSSQL: + +```python +mssql.execute( + """ + CREATE TABLE schema.target_tbl ( + id bigint, + struct_column_json text -- any string type, actually + ) + """, +) + +from pyspark.sql.functions import to_json + +df = df.select( + df.id, + to_json(df.struct_column).alias("struct_column_json"), +) + +writer.run(df) +``` + +Then you can parse this column on MSSQL side - for example, by creating a view: + +```sql +SELECT + id, + JSON_VALUE(struct_column_json, "$.nested.field") AS nested_field +FROM target_tbl +``` + +Or by using [computed column](https://learn.microsoft.com/en-us/sql/relational-databases/tables/specify-computed-columns-in-a-table): + +```sql +CREATE TABLE schema.target_table ( + id bigint, + supported_column datetime2(6), + struct_column_json text, -- any string type, actually + -- computed column + nested_field AS (JSON_VALUE(struct_column_json, "$.nested.field")) + -- or persisted column + -- nested_field AS (JSON_VALUE(struct_column_json, "$.nested.field")) PERSISTED +) +``` + +By default, column value is calculated on every table read. +Column marked as `PERSISTED` is calculated during insert, but this require additional space. diff --git a/mddocs/connection/db_connection/mssql/write.md b/mddocs/connection/db_connection/mssql/write.md new file mode 100644 index 000000000..d29aca762 --- /dev/null +++ b/mddocs/connection/db_connection/mssql/write.md @@ -0,0 +1,183 @@ + + +# Writing to MSSQL using `DBWriter` + +For writing data to MSSQL, use [`DBWriter`](../../../db/db_writer.md#onetl.db.db_writer.db_writer.DBWriter). + +#### WARNING +Please take into account [MSSQL <-> Spark type mapping](types.md#mssql-types) + +#### WARNING +It is always recommended to create table explicitly using [MSSQL.execute](execute.md#mssql-execute) +instead of relying on Spark’s table DDL generation. + +This is because Spark’s DDL generator can create columns with different precision and types than it is expected, +causing precision loss or other issues. + +## Examples + +```python +from onetl.connection import MSSQL +from onetl.db import DBWriter + +mssql = MSSQL(...) + +df = ... # data is here + +writer = DBWriter( + connection=mssql, + target="schema.table", + options=MSSQL.WriteOptions(if_exists="append"), +) + +writer.run(df) +``` + +## Options + +Method above accepts [`MSSQL.WriteOptions`](#onetl.connection.db_connection.mssql.options.MSSQLWriteOptions) + +### *pydantic model* onetl.connection.db_connection.mssql.options.MSSQLWriteOptions + +Spark JDBC writing options. + +#### Versionadded +Added in version 0.5.0: Replace `MSSQL.Options` → `MSSQL.WriteOptions` + +### Examples + +#### NOTE +You can pass any value +[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), +even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! + +The set of supported options depends on Spark version. + +```python +from onetl.connection import MSSQL + +options = MSSQL.WriteOptions( + if_exists="append", + batchsize=20_000, + customSparkOption="value", +) +``` + + + +#### *field* if_exists *: JDBCTableExistBehavior* *= JDBCTableExistBehavior.APPEND* *(alias 'mode')* + +Behavior of writing data into existing table. + +Possible values: +: * `append` (default) + : Adds new rows into existing table. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : Data is appended to a table. Table has the same DDL as before writing data +
+ #### WARNING + This mode does not check whether table already contains + rows from dataframe, so duplicated rows can be created. +
+ Also Spark does not support passing custom options to + insert statement, like `ON CONFLICT`, so don’t try to + implement deduplication using unique indexes or constraints. +
+ Instead, write to staging table and perform deduplication + using `execute` method. + * `replace_entire_table` + : **Table is dropped and then created, or truncated**. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : Table content is replaced with dataframe content. +
+ After writing completed, target table could either have the same DDL as + before writing data (`truncate=True`), or can be recreated (`truncate=False` + or source does not support truncation). + * `ignore` + : Ignores the write operation if the table already exists. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : The write operation is ignored, and no data is written to the table. + * `error` + : Raises an error if the table already exists. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : An error is raised, and no data is written to the table. + +#### Versionchanged +Changed in version 0.9.0: Renamed `mode` → `if_exists` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* batchsize *: int* *= 20000* + +How many rows can be inserted per round trip. + +Tuning this option can influence performance of writing. + +#### WARNING +Default value is different from Spark. + +Spark uses quite small value `1000`, which is absolutely not usable +in BigData world. + +Thus we’ve overridden default value with `20_000`, +which should increase writing performance. + +You can increase it even more, up to `50_000`, +but it depends on your database load and number of columns in the row. +Higher values does not increase performance. + +#### Versionchanged +Changed in version 0.4.0: Changed default value from 1000 to 20_000 + + + +#### *field* isolation_level *: str* *= 'READ_UNCOMMITTED'* *(alias 'isolationLevel')* + +The transaction isolation level, which applies to current connection. + +Possible values: +: * `NONE` (as string, not Python’s `None`) + * `READ_COMMITTED` + * `READ_UNCOMMITTED` + * `REPEATABLE_READ` + * `SERIALIZABLE` + +Values correspond to transaction isolation levels defined by JDBC standard. +Please refer the documentation for +[java.sql.Connection](https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html). + + diff --git a/mddocs/connection/db_connection/mysql/connection.md b/mddocs/connection/db_connection/mysql/connection.md new file mode 100644 index 000000000..93bb348e7 --- /dev/null +++ b/mddocs/connection/db_connection/mysql/connection.md @@ -0,0 +1,120 @@ + + +# MySQL connection + +### *class* onetl.connection.db_connection.mysql.connection.MySQL(\*, spark: SparkSession, user: str, password: SecretStr, host: Host, port: int = 3306, database: str | None = None, extra: MySQLExtra = MySQLExtra(useUnicode='yes', characterEncoding='UTF-8')) + +MySQL JDBC connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on Maven package [com.mysql:mysql-connector-j:9.2.0](https://mvnrepository.com/artifact/com.mysql/mysql-connector-j/9.2.0) +([official MySQL JDBC driver](https://dev.mysql.com/doc/connector-j/en/)). + +#### SEE ALSO +Before using this connector please take into account [Prerequisites](prerequisites.md#mysql-prerequisites) + +#### Versionadded +Added in version 0.1.0. + +* **Parameters:** + **host** + : Host of MySQL database. For example: `mysql0012.domain.com` or `192.168.1.11` + + **port** + : Port of MySQL database + + **user** + : User, which have proper access to the database. For example: `some_user` + + **password** + : Password for database connection + + **database** + : Database in RDBMS, NOT schema. +
+ See [this page](https://www.educba.com/postgresql-database-vs-schema/) for more details + + **spark** + : Spark session. + + **extra** + : Specifies one or more extra parameters by which clients can connect to the instance. +
+ For example: `{"useSSL": "false", "allowPublicKeyRetrieval": "true"}` +
+ See [MySQL JDBC driver properties documentation](https://dev.mysql.com/doc/connector-j/en/connector-j-reference-configuration-properties.html) + for more details + +### Examples + +Create and check MySQL connection: + +```python +from onetl.connection import MySQL +from pyspark.sql import SparkSession + +# Create Spark session with MySQL driver loaded +maven_packages = MySQL.get_packages() +spark = ( + SparkSession.builder.appName("spark-app-name") + .config("spark.jars.packages", ",".join(maven_packages)) + .getOrCreate() +) + +# Create connection +mysql = MySQL( + host="database.host.or.ip", + user="user", + password="*****", + extra={"useSSL": "false", "allowPublicKeyRetrieval": "true"}, + spark=spark, +).check() +``` + + + +#### check() + +Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If not, an exception will be raised. + +* **Returns:** + Connection itself +* **Raises:** + RuntimeError + : If the connection is not available + +### Examples + +```python +connection.check() +``` + + + +#### *classmethod* get_packages(package_version: str | None = None) → list[str] + +Get package names to be downloaded by Spark. Allows specifying a custom JDBC driver version for MySQL. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **package_version** + : Specifies the version of the MySQL JDBC driver to use. Defaults to `9.2.0`. +
+ #### Versionadded + Added in version 0.11.0. + +### Examples + +```python +from onetl.connection import MySQL + +MySQL.get_packages() + +# specify a custom package version +MySQL.get_packages(package_version="8.2.0") +``` + + diff --git a/mddocs/connection/db_connection/mysql/execute.md b/mddocs/connection/db_connection/mysql/execute.md new file mode 100644 index 000000000..fb345665f --- /dev/null +++ b/mddocs/connection/db_connection/mysql/execute.md @@ -0,0 +1,191 @@ + + +# Executing statements in MySQL + +#### WARNING +Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + +Do **NOT** use them to read large amounts of data. Use [DBReader](read.md#mysql-read) or [MySQL.sql](sql.md#mysql-sql) instead. + +## How to + +There are 2 ways to execute some statement in MySQL + +### Use `MySQL.fetch` + +Use this method to perform some `SELECT` query which returns **small number or rows**, like reading +MySQL config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts [`MySQL.FetchOptions`](#onetl.connection.db_connection.mysql.options.MySQLFetchOptions). + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### WARNING +Please take into account [MySQL <-> Spark type mapping](types.md#mysql-types). + +#### Syntax support + +This method supports **any** query syntax supported by MySQL, like: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ✅︎ `SELECT func(arg1, arg2)` or `{?= call func(arg1, arg2)}` - special syntax for calling function +* ✅︎ `SHOW ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import MySQL + +mysql = MySQL(...) + +df = mysql.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=MySQL.FetchOptions(queryTimeout=10), +) +mysql.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `MySQL.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts [`MySQL.ExecuteOptions`](#onetl.connection.db_connection.mysql.options.MySQLExecuteOptions). + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by MySQL, like: + +* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +* ✅︎ `ALTER ...` +* ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, and so on +* ✅︎ `CALL procedure(arg1, arg2) ...` or `{call procedure(arg1, arg2)}` - special syntax for calling procedure +* ✅︎ other statements not mentioned here +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import MySQL + +mysql = MySQL(...) + +mysql.execute("DROP TABLE schema.table") +mysql.execute( + """ + CREATE TABLE schema.table ( + id bigint, + key text, + value float + ) + ENGINE = InnoDB + """, + options=MySQL.ExecuteOptions(queryTimeout=10), +) +``` + +## Options + +### *pydantic model* onetl.connection.db_connection.mysql.options.MySQLFetchOptions + +Options related to fetching data from databases via JDBC. + +#### Versionadded +Added in version 0.11.0: Replace `MySQL.JDBCOptions` → `MySQL.FetchOptions` + +### Examples + +#### NOTE +You can pass any value supported by underlying JDBC driver class, +even if it is not mentioned in this documentation. + +```python +from onetl.connection import MySQL + +options = MySQL.FetchOptions( + queryTimeout=60_000, + fetchsize=100_000, + customSparkOption="value", +) +``` + + +* **Fields:** + - [`fetchsize (int | None)`](#onetl.connection.db_connection.mysql.options.MySQLFetchOptions.fetchsize) + - [`query_timeout (int | None)`](#onetl.connection.db_connection.mysql.options.MySQLFetchOptions.query_timeout) + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int | None* *= None* + +How many rows to fetch per round trip. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value depends on driver. For example, Oracle has +default `fetchsize=10`. + + + +### *pydantic model* onetl.connection.db_connection.mysql.options.MySQLExecuteOptions + +Options related to executing statements in databases via JDBC. + +#### Versionadded +Added in version 0.11.0: Replace `MySQL.JDBCOptions` → `MySQL.ExecuteOptions` + +### Examples + +#### NOTE +You can pass any value supported by underlying JDBC driver class, +even if it is not mentioned in this documentation. + +```python +from onetl.connection import MySQL + +options = MySQL.ExecuteOptions( + queryTimeout=60_000, + customSparkOption="value", +) +``` + + +* **Fields:** + - [`fetchsize (int | None)`](#onetl.connection.db_connection.mysql.options.MySQLExecuteOptions.fetchsize) + - [`query_timeout (int | None)`](#onetl.connection.db_connection.mysql.options.MySQLExecuteOptions.query_timeout) + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int | None* *= None* + +How many rows to fetch per round trip. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value depends on driver. For example, Oracle has +default `fetchsize=10`. + + diff --git a/mddocs/connection/db_connection/mysql/index.md b/mddocs/connection/db_connection/mysql/index.md new file mode 100644 index 000000000..6722ca77d --- /dev/null +++ b/mddocs/connection/db_connection/mysql/index.md @@ -0,0 +1,19 @@ + + +# MySQL + +# Connection + +* [Prerequisites](prerequisites.md) +* [MySQL connection](connection.md) + +# Operations + +* [Reading from MySQL using `DBReader`](read.md) +* [Reading from MySQL using `MySQL.sql`](sql.md) +* [Writing to MySQL using `DBWriter`](write.md) +* [Executing statements in MySQL](execute.md) + +# Troubleshooting + +* [MySQL <-> Spark type mapping](types.md) diff --git a/mddocs/connection/db_connection/mysql/prerequisites.md b/mddocs/connection/db_connection/mysql/prerequisites.md new file mode 100644 index 000000000..d0e551443 --- /dev/null +++ b/mddocs/connection/db_connection/mysql/prerequisites.md @@ -0,0 +1,61 @@ + + +# Prerequisites + +## Version Compatibility + +* MySQL server versions: + : * Officially declared: 8.0 - 9.2 + * Actually tested: 5.7.13, 9.2.0 +* Spark versions: 2.3.x - 3.5.x +* Java versions: 8 - 20 + +See [official documentation](https://dev.mysql.com/doc/connector-j/en/connector-j-versions.html). + +## Installing PySpark + +To use MySQL connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Connecting to MySQL + +### Connection host + +It is possible to connect to MySQL by using either DNS name of host or it’s IP address. + +If you’re using MySQL cluster, it is currently possible to connect only to **one specific node**. +Connecting to multiple nodes to perform load balancing, as well as automatic failover to new master/replica are not supported. + +### Connection port + +Connection is usually performed to port 3306. Port may differ for different MySQL instances. +Please ask your MySQL administrator to provide required information. + +### Required grants + +Ask your MySQL cluster administrator to set following grants for a user, +used for creating a connection: + +Read + Write + +```sql +-- allow creating tables in the target schema +GRANT CREATE ON myschema.* TO username@'192.168.1.%'; + +-- allow read & write access to specific table +GRANT SELECT, INSERT ON myschema.mytable TO username@'192.168.1.%'; +``` + +Read only + +```sql +-- allow read access to specific table +GRANT SELECT ON myschema.mytable TO username@'192.168.1.%'; +``` + +In example above `'192.168.1.%''` is a network subnet `192.168.1.0 - 192.168.1.255` +where Spark driver and executors are running. To allow connecting user from any IP, use `'%'` (not secure!). + +More details can be found in [official documentation](https://dev.mysql.com/doc/refman/en/grant.html). diff --git a/mddocs/connection/db_connection/mysql/read.md b/mddocs/connection/db_connection/mysql/read.md new file mode 100644 index 000000000..aa66a429d --- /dev/null +++ b/mddocs/connection/db_connection/mysql/read.md @@ -0,0 +1,352 @@ + + +# Reading from MySQL using `DBReader` + +[`DBReader`](../../../db/db_reader.md#onetl.db.db_reader.db_reader.DBReader) supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, +but does not support custom queries, like `JOIN`. + +#### WARNING +Please take into account [MySQL <-> Spark type mapping](types.md#mysql-types) + +## Supported DBReader features + +* ✅︎ `columns` +* ✅︎ `where` +* ✅︎ `hwm`, supported strategies: +* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) +* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) +* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) +* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) +* ✅︎ `hint` (see [official documentation](https://dev.mysql.com/doc/refman/en/optimizer-hints.html)) +* ❌ `df_schema` +* ✅︎ `options` (see [`MySQL.ReadOptions`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions)) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import MySQL +from onetl.db import DBReader + +mysql = MySQL(...) + +reader = DBReader( + connection=mysql, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + hint="SKIP_SCAN(schema.table key_index)", + options=MySQL.ReadOptions(partitionColumn="id", numPartitions=10), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import MySQL +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +mysql = MySQL(...) + +reader = DBReader( + connection=mysql, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + hint="SKIP_SCAN(schema.table key_index)", + hwm=DBReader.AutoDetectHWM(name="mysql_hwm", expression="updated_dt"), + options=MySQL.ReadOptions(partitionColumn="id", numPartitions=10), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Oracle to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Oracle to Spark, and may also improve performance of the query. +Especially if there are indexes for columns used in `where` clause. + +## Options + +### *pydantic model* onetl.connection.db_connection.mysql.options.MySQLReadOptions + +Spark JDBC reading options. + +#### Versionadded +Added in version 0.5.0: Replace `MySQL.Options` → `MySQL.ReadOptions` + +### Examples + +#### NOTE +You can pass any value +[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), +even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! + +The set of supported options depends on Spark version. + +```python +from onetl.connection import MySQL + +options = MySQL.ReadOptions( + partitioning_mode="range", + partitionColumn="reg_id", + numPartitions=10, + customSparkOption="value", +) +``` + + +* **Fields:** + - [`fetchsize (int)`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.fetchsize) + - [`lower_bound (int | None)`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.lower_bound) + - [`num_partitions (pydantic.types.PositiveInt)`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.num_partitions) + - [`partition_column (str | None)`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.partition_column) + - [`partitioning_mode (onetl.connection.db_connection.jdbc_connection.options.JDBCPartitioningMode)`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.partitioning_mode) + - [`query_timeout (int | None)`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.query_timeout) + - [`session_init_statement (str | None)`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.session_init_statement) + - [`upper_bound (int | None)`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.upper_bound) + +#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* + +Column used to parallelize reading from a table. + +#### WARNING +It is highly recommended to use primary key, or column with an index +to avoid performance issues. + +#### NOTE +Column type depends on [`partitioning_mode`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.partitioning_mode). + +* `partitioning_mode="range"` requires column to be an integer, date or timestamp (can be NULL, but not recommended). +* `partitioning_mode="hash"` accepts any column type (NOT NULL). +* `partitioning_mode="mod"` requires column to be an integer (NOT NULL). + +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.partitioning_mode) for more details + + + +#### *field* num_partitions *: PositiveInt* *= 1* *(alias 'numPartitions')* + +Number of jobs created by Spark to read the table content in parallel. +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.partitioning_mode) for more details + + +* **Constraints:** + - **exclusiveMinimum** = 0 + +#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* + +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.partitioning_mode) for more details + + + +#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* + +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.partitioning_mode) for more details + + + +#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* + +After each database session is opened to the remote DB and before starting to read data, +this option executes a custom SQL statement (or a PL/SQL block). + +Use this to implement session initialization code. + +Example: + +```python +sessionInitStatement = """ + BEGIN + execute immediate + 'alter session set "_serial_direct_read"=true'; + END; +""" +``` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int* *= 100000* + +Fetch N rows from an opened cursor per one read round. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value is different from Spark. + +Spark uses driver’s own value, and it may be different in different drivers, +and even versions of the same driver. For example, Oracle has +default `fetchsize=10`, which is absolutely not usable. + +Thus we’ve overridden default value with `100_000`, which should increase reading performance. + +#### Versionchanged +Changed in version 0.2.0: Set explicit default value to `100_000` + + + +#### *field* partitioning_mode *: JDBCPartitioningMode* *= JDBCPartitioningMode.RANGE* + +Defines how Spark will parallelize reading from table. + +Possible values: + +* `range` (default) + : Allocate each executor a range of values from column passed into [`partition_column`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.partition_column). +
+ ### Spark generates for each executor an SQL query +
+ Executor 1: + ```sql + SELECT ... FROM table + WHERE (partition_column >= lowerBound + OR partition_column IS NULL) + AND partition_column < (lower_bound + stride) + ``` +
+ Executor 2: + ```sql + SELECT ... FROM table + WHERE partition_column >= (lower_bound + stride) + AND partition_column < (lower_bound + 2 * stride) + ``` +
+ … +
+ Executor N: + ```sql + SELECT ... FROM table + WHERE partition_column >= (lower_bound + (N-1) * stride) + AND partition_column <= upper_bound + ``` +
+ Where `stride=(upper_bound - lower_bound) / num_partitions`. +
+ #### NOTE + Can be used only with columns of integer, date or timestamp types. +
+ #### NOTE + [`lower_bound`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.lower_bound), [`upper_bound`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.upper_bound) and [`num_partitions`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.num_partitions) are used just to + calculate the partition stride, **NOT** for filtering the rows in table. + So all rows in the table will be returned (unlike *Incremental* [Read Strategies](../../../strategy/index.md#strategy)). +
+ #### NOTE + All queries are executed in parallel. To execute them sequentially, use *Batch* [Read Strategies](../../../strategy/index.md#strategy). +* `hash` + : Allocate each executor a set of values based on hash of the [`partition_column`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.partition_column) column. +
+ ### Spark generates for each executor an SQL query +
+ Executor 1: + ```sql + SELECT ... FROM table + WHERE (some_hash(partition_column) mod num_partitions) = 0 -- lower_bound + ``` +
+ Executor 2: + ```sql + SELECT ... FROM table + WHERE (some_hash(partition_column) mod num_partitions) = 1 -- lower_bound + 1 + ``` +
+ … +
+ Executor N: + ```sql + SELECT ... FROM table + WHERE (some_hash(partition_column) mod num_partitions) = num_partitions-1 -- upper_bound + ``` +
+ #### NOTE + The hash function implementation depends on RDBMS. It can be `MD5` or any other fast hash function, + or expression based on this function call. Usually such functions accepts any column type as an input. +* `mod` + : Allocate each executor a set of values based on modulus of the [`partition_column`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.partition_column) column. +
+ ### Spark generates for each executor an SQL query +
+ Executor 1: + ```sql + SELECT ... FROM table + WHERE (partition_column mod num_partitions) = 0 -- lower_bound + ``` +
+ Executor 2: + ```sql + SELECT ... FROM table + WHERE (partition_column mod num_partitions) = 1 -- lower_bound + 1 + ``` +
+ Executor N: + ```sql + SELECT ... FROM table + WHERE (partition_column mod num_partitions) = num_partitions-1 -- upper_bound + ``` +
+ #### NOTE + Can be used only with columns of integer type. + +#### Versionadded +Added in version 0.5.0. + +### Examples + +Read data in 10 parallel jobs by range of values in `id_column` column: + +```python +ReadOptions( + partitioning_mode="range", # default mode, can be omitted + partitionColumn="id_column", + numPartitions=10, + # Options below can be discarded because they are + # calculated automatically as MIN and MAX values of `partitionColumn` + lowerBound=0, + upperBound=100_000, +) +``` + +Read data in 10 parallel jobs by hash of values in `some_column` column: + +```python +ReadOptions( + partitioning_mode="hash", + partitionColumn="some_column", + numPartitions=10, + # lowerBound and upperBound are automatically set to `0` and `9` +) +``` + +Read data in 10 parallel jobs by modulus of values in `id_column` column: + +```python +ReadOptions( + partitioning_mode="mod", + partitionColumn="id_column", + numPartitions=10, + # lowerBound and upperBound are automatically set to `0` and `9` +) +``` + + diff --git a/mddocs/connection/db_connection/mysql/sql.md b/mddocs/connection/db_connection/mysql/sql.md new file mode 100644 index 000000000..2999cedd1 --- /dev/null +++ b/mddocs/connection/db_connection/mysql/sql.md @@ -0,0 +1,192 @@ + + +# Reading from MySQL using `MySQL.sql` + +`MySQL.sql` allows passing custom SQL query, but does not support incremental strategies. + +#### WARNING +Please take into account [MySQL <-> Spark type mapping](types.md#mysql-types) + +#### WARNING +Statement is executed in **read-write** connection, so if you’re calling some functions/procedures with DDL/DML statements inside, +they can change data in your database. + +## Syntax support + +Only queries with the following syntax are supported: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ❌ `SHOW ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import MySQL + +mysql = MySQL(...) +df = mysql.sql( + """ + SELECT + id, + key, + CAST(value AS text) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """, + options=MySQL.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from MySQL to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from MySQL to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +### *pydantic model* onetl.connection.db_connection.mysql.options.MySQLSQLOptions + +Options specifically for SQL queries + +These options allow you to specify configurations for executing SQL queries +without relying on Spark’s partitioning mechanisms. + +#### Versionadded +Added in version 0.11.0: Split up `MySQL.ReadOptions` to `MySQL.SQLOptions` + +### Examples + +#### NOTE +You can pass any JDBC configuration +[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), +tailored to optimize SQL query execution. **Option names should be in** `camelCase`! + +```python +from onetl.connection import MySQL + +options = MySQL.SQLOptions( + partitionColumn="reg_id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + customSparkOption="value", +) +``` + + + +#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* + +Column used to partition data across multiple executors for parallel query processing. + +#### WARNING +It is highly recommended to use primary key, or column with an index +to avoid performance issues. + +### Example of using `partitionColumn="id"` with `partitioning_mode="range"` + +```sql +-- If partition_column is 'id', with numPartitions=4, lowerBound=1, and upperBound=100: +-- Executor 1 processes IDs from 1 to 25 +SELECT ... FROM table WHERE id >= 1 AND id < 26 +-- Executor 2 processes IDs from 26 to 50 +SELECT ... FROM table WHERE id >= 26 AND id < 51 +-- Executor 3 processes IDs from 51 to 75 +SELECT ... FROM table WHERE id >= 51 AND id < 76 +-- Executor 4 processes IDs from 76 to 100 +SELECT ... FROM table WHERE id >= 76 AND id <= 100 + +-- General case for Executor N +SELECT ... FROM table +WHERE partition_column >= (lowerBound + (N-1) * stride) +AND partition_column <= upperBound +-- Where ``stride`` is calculated as ``(upperBound - lowerBound) / numPartitions``. +``` + + + +#### *field* num_partitions *: int | None* *= None* *(alias 'numPartitions')* + +Number of jobs created by Spark to read the table content in parallel. + + + +#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* + +Defines the starting boundary for partitioning the query’s data. Mandatory if [`partition_column`](#onetl.connection.db_connection.mysql.options.MySQLSQLOptions.partition_column) is set + + + +#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* + +Sets the ending boundary for data partitioning. Mandatory if [`partition_column`](#onetl.connection.db_connection.mysql.options.MySQLSQLOptions.partition_column) is set + + + +#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* + +After each database session is opened to the remote DB and before starting to read data, +this option executes a custom SQL statement (or a PL/SQL block). + +Use this to implement session initialization code. + +Example: + +```python +sessionInitStatement = """ + BEGIN + execute immediate + 'alter session set "_serial_direct_read"=true'; + END; +""" +``` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int* *= 100000* + +Fetch N rows from an opened cursor per one read round. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value is different from Spark. + +Spark uses driver’s own value, and it may be different in different drivers, +and even versions of the same driver. For example, Oracle has +default `fetchsize=10`, which is absolutely not usable. + +Thus we’ve overridden default value with `100_000`, which should increase reading performance. + +#### Versionchanged +Changed in version 0.2.0: Set explicit default value to `100_000` + + diff --git a/mddocs/connection/db_connection/mysql/types.md b/mddocs/connection/db_connection/mysql/types.md new file mode 100644 index 000000000..cd1e207a9 --- /dev/null +++ b/mddocs/connection/db_connection/mysql/types.md @@ -0,0 +1,292 @@ + + +# MySQL <-> Spark type mapping + +#### NOTE +The results below are valid for Spark 3.5.5, and may differ on other Spark versions. + +## Type detection & casting + +Spark’s DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from MySQL + +This is how MySQL connector performs this: + +* For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and MySQL type. +* Find corresponding `MySQL type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* Create DataFrame from query with specific column names and Spark types. + +### Writing to some existing MySQL table + +This is how MySQL connector performs this: + +* Get names of columns in DataFrame. [1](#id2) +* Perform `SELECT * FROM table LIMIT 0` query. +* Take only columns present in DataFrame (by name, case insensitive). For each found column get MySQL type. +* Find corresponding `Spark type` → `MySQL type (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* If `MySQL type (write)` match `MySQL type (read)`, no additional casts will be performed, DataFrame column will be written to MySQL as is. +* If `MySQL type (write)` does not match `MySQL type (read)`, DataFrame column will be casted to target column type **on MySQL side**. For example, you can write column with text data to `int` column, if column contains valid integer values within supported value range and precision. + +* **[1]** This allows to write data to tables with `DEFAULT` and `GENERATED` columns - if DataFrame has no such column, it will be populated by MySQL. + +### Create new table using Spark + +#### WARNING +ABSOLUTELY NOT RECOMMENDED! + +This is how MySQL connector performs this: + +* Find corresponding `Spark type` → `MySQL type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* Generate DDL for creating table in MySQL, like `CREATE TABLE (col1 ...)`, and run it. +* Write DataFrame to created table as is. + +But some cases this may lead to using wrong column type. For example, Spark creates column of type `timestamp` +which corresponds to MySQL type `timestamp(0)` (precision up to seconds) +instead of more precise `timestamp(6)` (precision up to nanoseconds). +This may lead to incidental precision loss, or sometimes data cannot be written to created table at all. + +So instead of relying on Spark to create tables: + +### See example + +```python +writer = DBWriter( + connection=mysql, + target="myschema.target_tbl", + options=MySQL.WriteOptions( + if_exists="append", + createTableOptions="ENGINE = InnoDB", + ), +) +writer.run(df) +``` + +Always prefer creating tables with specific types **BEFORE WRITING DATA**: + +### See example + +```python +mysql.execute( + """ + CREATE TABLE schema.table ( + id bigint, + key text, + value timestamp(6) -- specific type and precision + ) + ENGINE = InnoDB + """, +) + +writer = DBWriter( + connection=mysql, + target="myschema.target_tbl", + options=MySQL.WriteOptions(if_exists="append"), +) +writer.run(df) +``` + +### References + +Here you can find source code with type conversions: + +* [MySQL -> JDBC](https://github.com/mysql/mysql-connector-j/blob/8.0.33/src/main/core-api/java/com/mysql/cj/MysqlType.java#L44-L623) +* [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MySQLDialect.scala#L104-L132) +* [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MySQLDialect.scala#L204-L211) +* [JDBC -> MySQL](https://github.com/mysql/mysql-connector-j/blob/8.0.33/src/main/core-api/java/com/mysql/cj/MysqlType.java#L625-L867) + +## Supported types + +See [official documentation](https://dev.mysql.com/doc/refman/en/data-types.html) + +### Numeric types + +| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | +|-----------------------------|----------------------------------|-----------------------------|-----------------------------| +| `decimal` | `DecimalType(P=10, S=0)` | `decimal(P=10, S=0)` | `decimal(P=10, S=0)` | +| `decimal(P=0..38)` | `DecimalType(P=0..38, S=0)` | `decimal(P=0..38, S=0)` | `decimal(P=0..38, S=0)` | +| `decimal(P=0..38, S=0..30)` | `DecimalType(P=0..38, S=0..30)` | `decimal(P=0..38, S=0..30)` | `decimal(P=0..38, S=0..30)` | +| `decimal(P=39..65, S=...)` | unsupported [2](#id4) | | | +| `float` | `DoubleType()` | `double` | `double` | +| `double` | | | | +| `tinyint` | `IntegerType()` | `int` | `int` | +| `smallint` | | | | +| `mediumint` | | | | +| `int` | | | | +| `bigint` | `LongType()` | `bigint` | `bigint` | +* **[2]** MySQL support decimal types with precision `P` up to 65. But Spark’s `DecimalType(P, S)` supports maximum `P=38`. It is impossible to read, write or operate with values of larger precision, this leads to an exception. + +### Temporal types + +| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | +|------------------------------|------------------------------------------------------------------------------------|------------------------------|-----------------------------------------------------------------------| +| `year` | `DateType()` | `date` | `date` | +| `date` | | | | +| `datetime`, seconds | `TimestampType()`, microseconds | `timestamp(6)`, microseconds | `timestamp(0)`, seconds | +| `timestamp`, seconds | | | | +| `datetime(0)`, seconds | | | | +| `timestamp(0)`, seconds | | | | +| `datetime(3)`, milliseconds | `TimestampType()`, microseconds | `timestamp(6)`, microseconds | `timestamp(0)`, seconds,
**precision loss** [4](#id9), | +| `timestamp(3)`, milliseconds | | | | +| `datetime(6)`, microseconds | | | | +| `timestamp(6)`, microseconds | | | | +| `time`, seconds | `TimestampType()`, microseconds,
with time format quirks [5](#id10) | `timestamp(6)`, microseconds | `timestamp(0)`, seconds | +| `time(0)`, seconds | | | | +| `time(3)`, milliseconds | `TimestampType()`, microseconds
with time format quirks [5](#id10) | `timestamp(6)`, microseconds | `timestamp(0)`, seconds,
**precision loss** [4](#id9), | +| `time(6)`, microseconds | | | | + +#### WARNING +Note that types in MySQL and Spark have different value ranges: + +| MySQL type | Min value | Max value | Spark type | Min value | Max value | +|--------------|------------------------------|------------------------------|-------------------|------------------------------|------------------------------| +| `year` | `1901` | `2155` | `DateType()` | `0001-01-01` | `9999-12-31` | +| `date` | `1000-01-01` | `9999-12-31` | | | | +| `datetime` | `1000-01-01 00:00:00.000000` | `9999-12-31 23:59:59.499999` | `TimestampType()` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | +| `timestamp` | `1970-01-01 00:00:01.000000` | `9999-12-31 23:59:59.499999` | | | | +| `time` | `-838:59:59.000000` | `838:59:59.000000` | | | | + +So Spark can read all the values from MySQL, but not all of values in Spark DataFrame can be written to MySQL. + +References: +: * [MySQL year documentation](https://dev.mysql.com/doc/refman/en/year.html) + * [MySQL date, datetime & timestamp documentation](https://dev.mysql.com/doc/refman/en/datetime.html) + * [MySQL time documentation](https://dev.mysql.com/doc/refman/en/time.html) + * [Spark DateType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/DateType.html) + * [Spark TimestampType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/TimestampType.html) + +* **[4]** MySQL dialect generates DDL with MySQL type `timestamp` which is alias for `timestamp(0)` with precision up to seconds (`23:59:59`). Inserting data with microseconds precision (`23:59:59.999999`) will lead to **throwing away microseconds**. +* **[5]** `time` type is the same as `timestamp` with date `1970-01-01`. So instead of reading data from MySQL like `23:59:59` it is actually read `1970-01-01 23:59:59`, and vice versa. + +### String types + +| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | +|-----------------------------|----------------|----------------------|-----------------------| +| `char` | `StringType()` | `longtext` | `longtext` | +| `char(N)` | | | | +| `varchar(N)` | | | | +| `mediumtext` | | | | +| `text` | | | | +| `longtext` | | | | +| `json` | | | | +| `enum("val1", "val2", ...)` | | | | +| `set("val1", "val2", ...)` | | | | + +### Binary types + +| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | +|---------------------|----------------|----------------------|-----------------------| +| `binary` | `BinaryType()` | `blob` | `blob` | +| `binary(N)` | | | | +| `varbinary(N)` | | | | +| `mediumblob` | | | | +| `blob` | | | | +| `longblob` | | | | + +### Geometry types + +| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | +|----------------------|----------------|----------------------|-----------------------| +| `point` | `BinaryType()` | `blob` | `blob` | +| `linestring` | | | | +| `polygon` | | | | +| `geometry` | | | | +| `multipoint` | | | | +| `multilinestring` | | | | +| `multipolygon` | | | | +| `geometrycollection` | | | | + +## Explicit type cast + +### `DBReader` + +It is possible to explicitly cast column type using `DBReader(columns=...)` syntax. + +For example, you can use `CAST(column AS text)` to convert data to string representation on MySQL side, and so it will be read as Spark’s `StringType()`. + +It is also possible to use [JSON_OBJECT](https://dev.mysql.com/doc/refman/en/json.html) MySQL function and parse JSON columns in MySQL with the [`JSON.parse_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.parse_column) method. + +```python +from pyspark.sql.types import IntegerType, StructType, StructField + +from onetl.connection import MySQL +from onetl.db import DBReader +from onetl.file.format import JSON + +mysql = MySQL(...) + +DBReader( + connection=mysql, + columns=[ + "id", + "supported_column", + "CAST(unsupported_column AS text) unsupported_column_str", + # or + "JSON_OBJECT('key', value_column) json_column", + ], +) +df = reader.run() + +json_scheme = StructType([StructField("key", IntegerType())]) + +df = df.select( + df.id, + df.supported_column, + # explicit cast + df.unsupported_column_str.cast("integer").alias("parsed_integer"), + JSON().parse_column("json_column", json_scheme).alias("struct_column"), +) +``` + +### `DBWriter` + +To write JSON data to a `json` or `text` column in a MySQL table, use the [`JSON.serialize_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.serialize_column) method. + +```python +from onetl.connection import MySQL +from onetl.db import DBWriter +from onetl.file.format import JSON + +mysql.execute( + """ + CREATE TABLE schema.target_tbl ( + id bigint, + array_column_json json -- any string type, actually + ) + ENGINE = InnoDB + """, +) + +df = df.select( + df.id, + JSON().serialize_column(df.array_column).alias("array_column_json"), +) + +writer.run(df) +``` + +Then you can parse this column on MySQL side - for example, by creating a view: + +```sql +SELECT + id, + array_column_json->"$[0]" AS array_item +FROM target_tbl +``` + +Or by using [GENERATED column](https://dev.mysql.com/doc/refman/en/create-table-generated-columns.html): + +```sql +CREATE TABLE schema.target_table ( + id bigint, + supported_column timestamp, + array_column_json json, -- any string type, actually + -- virtual column + array_item_0 GENERATED ALWAYS AS (array_column_json->"$[0]")) VIRTUAL + -- or stired column + -- array_item_0 GENERATED ALWAYS AS (array_column_json->"$[0]")) STORED +) +``` + +`VIRTUAL` column value is calculated on every table read. +`STORED` column value is calculated during insert, but this require additional space. diff --git a/mddocs/connection/db_connection/mysql/write.md b/mddocs/connection/db_connection/mysql/write.md new file mode 100644 index 000000000..239aacfdb --- /dev/null +++ b/mddocs/connection/db_connection/mysql/write.md @@ -0,0 +1,187 @@ + + +# Writing to MySQL using `DBWriter` + +For writing data to MySQL, use [`DBWriter`](../../../db/db_writer.md#onetl.db.db_writer.db_writer.DBWriter). + +#### WARNING +Please take into account [MySQL <-> Spark type mapping](types.md#mysql-types) + +#### WARNING +It is always recommended to create table explicitly using [MySQL.execute](execute.md#mysql-execute) +instead of relying on Spark’s table DDL generation. + +This is because Spark’s DDL generator can create columns with different precision and types than it is expected, +causing precision loss or other issues. + +## Examples + +```python +from onetl.connection import MySQL +from onetl.db import DBWriter + +mysql = MySQL(...) + +df = ... # data is here + +writer = DBWriter( + connection=mysql, + target="schema.table", + options=MySQL.WriteOptions( + if_exists="append", + # ENGINE is required by MySQL + createTableOptions="ENGINE = MergeTree() ORDER BY id", + ), +) + +writer.run(df) +``` + +## Options + +Method above accepts [`MySQL.WriteOptions`](#onetl.connection.db_connection.mysql.options.MySQLWriteOptions) + +### *pydantic model* onetl.connection.db_connection.mysql.options.MySQLWriteOptions + +Spark JDBC writing options. + +#### Versionadded +Added in version 0.5.0: Replace `MySQL.Options` → `MySQL.WriteOptions` + +### Examples + +#### NOTE +You can pass any value +[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), +even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! + +The set of supported options depends on Spark version. + +```python +from onetl.connection import MySQL + +options = MySQL.WriteOptions( + if_exists="append", + batchsize=20_000, + customSparkOption="value", +) +``` + + + +#### *field* if_exists *: JDBCTableExistBehavior* *= JDBCTableExistBehavior.APPEND* *(alias 'mode')* + +Behavior of writing data into existing table. + +Possible values: +: * `append` (default) + : Adds new rows into existing table. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : Data is appended to a table. Table has the same DDL as before writing data +
+ #### WARNING + This mode does not check whether table already contains + rows from dataframe, so duplicated rows can be created. +
+ Also Spark does not support passing custom options to + insert statement, like `ON CONFLICT`, so don’t try to + implement deduplication using unique indexes or constraints. +
+ Instead, write to staging table and perform deduplication + using `execute` method. + * `replace_entire_table` + : **Table is dropped and then created, or truncated**. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : Table content is replaced with dataframe content. +
+ After writing completed, target table could either have the same DDL as + before writing data (`truncate=True`), or can be recreated (`truncate=False` + or source does not support truncation). + * `ignore` + : Ignores the write operation if the table already exists. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : The write operation is ignored, and no data is written to the table. + * `error` + : Raises an error if the table already exists. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : An error is raised, and no data is written to the table. + +#### Versionchanged +Changed in version 0.9.0: Renamed `mode` → `if_exists` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* batchsize *: int* *= 20000* + +How many rows can be inserted per round trip. + +Tuning this option can influence performance of writing. + +#### WARNING +Default value is different from Spark. + +Spark uses quite small value `1000`, which is absolutely not usable +in BigData world. + +Thus we’ve overridden default value with `20_000`, +which should increase writing performance. + +You can increase it even more, up to `50_000`, +but it depends on your database load and number of columns in the row. +Higher values does not increase performance. + +#### Versionchanged +Changed in version 0.4.0: Changed default value from 1000 to 20_000 + + + +#### *field* isolation_level *: str* *= 'READ_UNCOMMITTED'* *(alias 'isolationLevel')* + +The transaction isolation level, which applies to current connection. + +Possible values: +: * `NONE` (as string, not Python’s `None`) + * `READ_COMMITTED` + * `READ_UNCOMMITTED` + * `REPEATABLE_READ` + * `SERIALIZABLE` + +Values correspond to transaction isolation levels defined by JDBC standard. +Please refer the documentation for +[java.sql.Connection](https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html). + + diff --git a/mddocs/connection/db_connection/oracle/connection.md b/mddocs/connection/db_connection/oracle/connection.md new file mode 100644 index 000000000..6171f3a50 --- /dev/null +++ b/mddocs/connection/db_connection/oracle/connection.md @@ -0,0 +1,143 @@ + + +# Oracle connection + +### *class* onetl.connection.db_connection.oracle.connection.Oracle(\*, spark: SparkSession, user: str, password: SecretStr, host: Host, port: int = 1521, sid: str | None = None, service_name: str | None = None, extra: OracleExtra = OracleExtra()) + +Oracle JDBC connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on Maven package [com.oracle.database.jdbc:ojdbc8:23.7.0.25.01](https://mvnrepository.com/artifact/com.oracle.database.jdbc/ojdbc8/23.7.0.25.01) +([official Oracle JDBC driver](https://www.oracle.com/cis/database/technologies/appdev/jdbc-downloads.html)). + +#### SEE ALSO +Before using this connector please take into account [Prerequisites](prerequisites.md#oracle-prerequisites) + +#### Versionadded +Added in version 0.1.0. + +* **Parameters:** + **host** + : Host of Oracle database. For example: `test.oracle.domain.com` or `193.168.1.10` + + **port** + : Port of Oracle database + + **user** + : User, which have proper access to the database. For example: `SOME_USER` + + **password** + : Password for database connection + + **sid** + : Sid of oracle database. For example: `XE` +
+ #### WARNING + You should provide either `sid` or `service_name`, not both of them + + **service_name** + : Specifies one or more names by which clients can connect to the instance. +
+ For example: `PDB1`. +
+ #### WARNING + You should provide either `sid` or `service_name`, not both of them + + **spark** + : Spark session. + + **extra** + : Specifies one or more extra parameters by which clients can connect to the instance. +
+ For example: `{"remarksReporting": "false"}` +
+ See official documentation: + : * [Connection parameters](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleDriver.html) + * [Connection properties](https://docs.oracle.com/cd/A97335_02/apps.102/a83724/basic1.htm#1024018) + +### Examples + +Create and check Oracle connection with `sid`: + +```python +from onetl.connection import Oracle +from pyspark.sql import SparkSession + +# Create Spark session with Oracle driver loaded +maven_packages = Oracle.get_packages() +spark = ( + SparkSession.builder.appName("spark-app-name") + .config("spark.jars.packages", ",".join(maven_packages)) + .getOrCreate() +) + +# Create connection +oracle = Oracle( + host="database.host.or.ip", + user="user", + password="*****", + sid="XE", + extra={"remarksReporting": "false"}, + spark=spark, +).check() +``` + +or with `service_name`: + +```python +... + +oracle = Oracle( + host="database.host.or.ip", + user="user", + password="*****", + service_name="PDB1", # <--- instead of SID + extra={"remarksReporting": "false"}, + spark=spark, +).check() +``` + + + +#### check() + +Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If not, an exception will be raised. + +* **Returns:** + Connection itself +* **Raises:** + RuntimeError + : If the connection is not available + +### Examples + +```python +connection.check() +``` + + + +#### *classmethod* get_packages(java_version: str | None = None, package_version: str | None = None) → list[str] + +Get package names to be downloaded by Spark. Allows specifying custom JDBC driver versions for Oracle. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +* **Parameters:** + **java_version** + : Java major version, defaults to “8”. Must be “8” or “11”. + + **package_version** + : Specifies the version of the Oracle JDBC driver to use. Defaults to “23.7.0.25.01”. + +### Examples + +```python +from onetl.connection import Oracle + +Oracle.get_packages() + +# specify Java and package versions +Oracle.get_packages(java_version="8", package_version="23.7.0.25.01") +``` + + diff --git a/mddocs/connection/db_connection/oracle/execute.md b/mddocs/connection/db_connection/oracle/execute.md new file mode 100644 index 000000000..29293882b --- /dev/null +++ b/mddocs/connection/db_connection/oracle/execute.md @@ -0,0 +1,191 @@ + + +# Executing statements in Oracle + +#### WARNING +Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + +Do **NOT** use them to read large amounts of data. Use [DBReader](read.md#oracle-read) or [Oracle.sql](sql.md#oracle-sql) instead. + +## How to + +There are 2 ways to execute some statement in Oracle + +### Use `Oracle.fetch` + +Use this method to execute some `SELECT` query which returns **small number or rows**, like reading +Oracle config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts [`Oracle.FetchOptions`](#onetl.connection.db_connection.oracle.options.OracleFetchOptions). + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### WARNING +Please take into account [Oracle <-> Spark type mapping](types.md#oracle-types). + +#### Syntax support + +This method supports **any** query syntax supported by Oracle, like: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ✅︎ `SELECT func(arg1, arg2) FROM DUAL` - call function +* ✅︎ `SHOW ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Oracle + +oracle = Oracle(...) + +df = oracle.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=Oracle.FetchOptions(queryTimeout=10), +) +oracle.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `Oracle.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts [`Oracle.ExecuteOptions`](#onetl.connection.db_connection.oracle.options.OracleExecuteOptions). + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Oracle, like: + +* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...` +* ✅︎ `ALTER ...` +* ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +* ✅︎ `CALL procedure(arg1, arg2) ...` or `{call procedure(arg1, arg2)}` - special syntax for calling procedure +* ✅︎ `DECLARE ... BEGIN ... END` - execute PL/SQL statement +* ✅︎ other statements not mentioned here +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Oracle + +oracle = Oracle(...) + +oracle.execute("DROP TABLE schema.table") +oracle.execute( + """ + CREATE TABLE schema.table ( + id bigint GENERATED ALWAYS AS IDENTITY, + key VARCHAR2(4000), + value NUMBER + ) + """, + options=Oracle.ExecuteOptions(queryTimeout=10), +) +``` + +## Options + +### *pydantic model* onetl.connection.db_connection.oracle.options.OracleFetchOptions + +Options related to fetching data from databases via JDBC. + +#### Versionadded +Added in version 0.11.0: Replace `Oracle.JDBCOptions` → `Oracle.FetchOptions` + +### Examples + +#### NOTE +You can pass any value supported by underlying JDBC driver class, +even if it is not mentioned in this documentation. + +```python +from onetl.connection import Oracle + +options = Oracle.FetchOptions( + queryTimeout=60_000, + fetchsize=100_000, + customSparkOption="value", +) +``` + + +* **Fields:** + - [`fetchsize (int | None)`](#onetl.connection.db_connection.oracle.options.OracleFetchOptions.fetchsize) + - [`query_timeout (int | None)`](#onetl.connection.db_connection.oracle.options.OracleFetchOptions.query_timeout) + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int | None* *= None* + +How many rows to fetch per round trip. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value depends on driver. For example, Oracle has +default `fetchsize=10`. + + + +### *pydantic model* onetl.connection.db_connection.oracle.options.OracleExecuteOptions + +Options related to executing statements in databases via JDBC. + +#### Versionadded +Added in version 0.11.0: Replace `Oracle.JDBCOptions` → `Oracle.ExecuteOptions` + +### Examples + +#### NOTE +You can pass any value supported by underlying JDBC driver class, +even if it is not mentioned in this documentation. + +```python +from onetl.connection import Oracle + +options = Oracle.ExecuteOptions( + queryTimeout=60_000, + customSparkOption="value", +) +``` + + +* **Fields:** + - [`fetchsize (int | None)`](#onetl.connection.db_connection.oracle.options.OracleExecuteOptions.fetchsize) + - [`query_timeout (int | None)`](#onetl.connection.db_connection.oracle.options.OracleExecuteOptions.query_timeout) + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int | None* *= None* + +How many rows to fetch per round trip. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value depends on driver. For example, Oracle has +default `fetchsize=10`. + + diff --git a/mddocs/connection/db_connection/oracle/index.md b/mddocs/connection/db_connection/oracle/index.md new file mode 100644 index 000000000..39987fb24 --- /dev/null +++ b/mddocs/connection/db_connection/oracle/index.md @@ -0,0 +1,19 @@ + + +# Oracle + +# Connection + +* [Prerequisites](prerequisites.md) +* [Oracle connection](connection.md) + +# Operations + +* [Reading from Oracle using `DBReader`](read.md) +* [Reading from Oracle using `Oracle.sql`](sql.md) +* [Writing to Oracle using `DBWriter`](write.md) +* [Executing statements in Oracle](execute.md) + +# Troubleshooting + +* [Oracle <-> Spark type mapping](types.md) diff --git a/mddocs/connection/db_connection/oracle/prerequisites.md b/mddocs/connection/db_connection/oracle/prerequisites.md new file mode 100644 index 000000000..6dff632ce --- /dev/null +++ b/mddocs/connection/db_connection/oracle/prerequisites.md @@ -0,0 +1,110 @@ + + +# Prerequisites + +## Version Compatibility + +* Oracle Server versions: + : * Officially declared: 19c, 21c, 23ai + * Actually tested: 11.2, 23.5 +* Spark versions: 2.3.x - 3.5.x +* Java versions: 8 - 20 + +See [official documentation](https://www.oracle.com/cis/database/technologies/appdev/jdbc-downloads.html). + +## Installing PySpark + +To use Oracle connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Connecting to Oracle + +### Connection port + +Connection is usually performed to port 1521. Port may differ for different Oracle instances. +Please ask your Oracle administrator to provide required information. + +### Connection host + +It is possible to connect to Oracle by using either DNS name of host or it’s IP address. + +If you’re using Oracle cluster, it is currently possible to connect only to **one specific node**. +Connecting to multiple nodes to perform load balancing, as well as automatic failover to new master/replica are not supported. + +### Connect as proxy user + +It is possible to connect to database as another user without knowing this user password. + +This can be enabled by granting user a special `CONNECT THROUGH` permission: + +```sql +ALTER USER schema_owner GRANT CONNECT THROUGH proxy_user; +``` + +Then you can connect to Oracle using credentials of `proxy_user` but specify that you need permissions of `schema_owner`: + +```python +oracle = Oracle( + ..., + user="proxy_user[schema_owner]", + password="proxy_user password", +) +``` + +See [official documentation](https://oracle-base.com/articles/misc/proxy-users-and-connect-through). + +### Required grants + +Ask your Oracle cluster administrator to set following grants for a user, +used for creating a connection: + +Read + Write (schema is owned by user) + +```sql +-- allow user to log in +GRANT CREATE SESSION TO username; + +-- allow creating tables in user schema +GRANT CREATE TABLE TO username; + +-- allow read & write access to specific table +GRANT SELECT, INSERT ON username.mytable TO username; +``` + +Read + Write (schema is not owned by user) + +```sql +-- allow user to log in +GRANT CREATE SESSION TO username; + +-- allow creating tables in any schema, +-- as Oracle does not support specifying exact schema name +GRANT CREATE ANY TABLE TO username; + +-- allow read & write access to specific table +GRANT SELECT, INSERT ON someschema.mytable TO username; + +-- only if if_exists="replace_entire_table" is used: +-- allow dropping/truncating tables in any schema, +-- as Oracle does not support specifying exact schema name +GRANT DROP ANY TABLE TO username; +``` + +Read only + +```sql +-- allow user to log in +GRANT CREATE SESSION TO username; + +-- allow read access to specific table +GRANT SELECT ON someschema.mytable TO username; +``` + +More details can be found in official documentation: +: * [GRANT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/GRANT.html) + * [SELECT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/SELECT.html) + * [CREATE TABLE](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/SELECT.html) + * [INSERT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/INSERT.html) + * [TRUNCATE TABLE](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/TRUNCATE-TABLE.html) diff --git a/mddocs/connection/db_connection/oracle/read.md b/mddocs/connection/db_connection/oracle/read.md new file mode 100644 index 000000000..fbb28c207 --- /dev/null +++ b/mddocs/connection/db_connection/oracle/read.md @@ -0,0 +1,352 @@ + + +# Reading from Oracle using `DBReader` + +[`DBReader`](../../../db/db_reader.md#onetl.db.db_reader.db_reader.DBReader) supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, +but does not support custom queries, like `JOIN`. + +#### WARNING +Please take into account [Oracle <-> Spark type mapping](types.md#oracle-types) + +## Supported DBReader features + +* ✅︎ `columns` +* ✅︎ `where` +* ✅︎ `hwm`, supported strategies: +* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) +* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) +* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) +* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) +* ✅︎ `hint` (see [official documentation](https://docs.oracle.com/cd/B10500_01/server.920/a96533/hintsref.htm)) +* ❌ `df_schema` +* ✅︎ `options` (see [`Oracle.ReadOptions`](#onetl.connection.db_connection.oracle.options.OracleReadOptions)) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Oracle +from onetl.db import DBReader + +oracle = Oracle(...) + +reader = DBReader( + connection=oracle, + source="schema.table", + columns=["id", "key", "CAST(value AS VARCHAR2(4000)) value", "updated_dt"], + where="key = 'something'", + hint="INDEX(schema.table key_index)", + options=Oracle.ReadOptions(partitionColumn="id", numPartitions=10), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Oracle +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +oracle = Oracle(...) + +reader = DBReader( + connection=oracle, + source="schema.table", + columns=["id", "key", "CAST(value AS VARCHAR2(4000)) value", "updated_dt"], + where="key = 'something'", + hint="INDEX(schema.table key_index)", + hwm=DBReader.AutoDetectHWM(name="oracle_hwm", expression="updated_dt"), + options=Oracle.ReadOptions(partitionColumn="id", numPartitions=10), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Oracle to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Oracle to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +### *pydantic model* onetl.connection.db_connection.oracle.options.OracleReadOptions + +Spark JDBC reading options. + +#### Versionadded +Added in version 0.5.0: Replace `Oracle.Options` → `Oracle.ReadOptions` + +### Examples + +#### NOTE +You can pass any value +[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), +even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! + +The set of supported options depends on Spark version. + +```python +from onetl.connection import Oracle + +options = Oracle.ReadOptions( + partitioning_mode="range", + partitionColumn="reg_id", + numPartitions=10, + customSparkOption="value", +) +``` + + +* **Fields:** + - [`fetchsize (int)`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.fetchsize) + - [`lower_bound (int | None)`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.lower_bound) + - [`num_partitions (pydantic.types.PositiveInt)`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.num_partitions) + - [`partition_column (str | None)`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.partition_column) + - [`partitioning_mode (onetl.connection.db_connection.jdbc_connection.options.JDBCPartitioningMode)`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.partitioning_mode) + - [`query_timeout (int | None)`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.query_timeout) + - [`session_init_statement (str | None)`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.session_init_statement) + - [`upper_bound (int | None)`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.upper_bound) + +#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* + +Column used to parallelize reading from a table. + +#### WARNING +It is highly recommended to use primary key, or column with an index +to avoid performance issues. + +#### NOTE +Column type depends on [`partitioning_mode`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.partitioning_mode). + +* `partitioning_mode="range"` requires column to be an integer, date or timestamp (can be NULL, but not recommended). +* `partitioning_mode="hash"` accepts any column type (NOT NULL). +* `partitioning_mode="mod"` requires column to be an integer (NOT NULL). + +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.partitioning_mode) for more details + + + +#### *field* num_partitions *: PositiveInt* *= 1* *(alias 'numPartitions')* + +Number of jobs created by Spark to read the table content in parallel. +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.partitioning_mode) for more details + + +* **Constraints:** + - **exclusiveMinimum** = 0 + +#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* + +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.partitioning_mode) for more details + + + +#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* + +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.partitioning_mode) for more details + + + +#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* + +After each database session is opened to the remote DB and before starting to read data, +this option executes a custom SQL statement (or a PL/SQL block). + +Use this to implement session initialization code. + +Example: + +```python +sessionInitStatement = """ + BEGIN + execute immediate + 'alter session set "_serial_direct_read"=true'; + END; +""" +``` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int* *= 100000* + +Fetch N rows from an opened cursor per one read round. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value is different from Spark. + +Spark uses driver’s own value, and it may be different in different drivers, +and even versions of the same driver. For example, Oracle has +default `fetchsize=10`, which is absolutely not usable. + +Thus we’ve overridden default value with `100_000`, which should increase reading performance. + +#### Versionchanged +Changed in version 0.2.0: Set explicit default value to `100_000` + + + +#### *field* partitioning_mode *: JDBCPartitioningMode* *= JDBCPartitioningMode.RANGE* + +Defines how Spark will parallelize reading from table. + +Possible values: + +* `range` (default) + : Allocate each executor a range of values from column passed into [`partition_column`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.partition_column). +
+ ### Spark generates for each executor an SQL query +
+ Executor 1: + ```sql + SELECT ... FROM table + WHERE (partition_column >= lowerBound + OR partition_column IS NULL) + AND partition_column < (lower_bound + stride) + ``` +
+ Executor 2: + ```sql + SELECT ... FROM table + WHERE partition_column >= (lower_bound + stride) + AND partition_column < (lower_bound + 2 * stride) + ``` +
+ … +
+ Executor N: + ```sql + SELECT ... FROM table + WHERE partition_column >= (lower_bound + (N-1) * stride) + AND partition_column <= upper_bound + ``` +
+ Where `stride=(upper_bound - lower_bound) / num_partitions`. +
+ #### NOTE + Can be used only with columns of integer, date or timestamp types. +
+ #### NOTE + [`lower_bound`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.lower_bound), [`upper_bound`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.upper_bound) and [`num_partitions`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.num_partitions) are used just to + calculate the partition stride, **NOT** for filtering the rows in table. + So all rows in the table will be returned (unlike *Incremental* [Read Strategies](../../../strategy/index.md#strategy)). +
+ #### NOTE + All queries are executed in parallel. To execute them sequentially, use *Batch* [Read Strategies](../../../strategy/index.md#strategy). +* `hash` + : Allocate each executor a set of values based on hash of the [`partition_column`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.partition_column) column. +
+ ### Spark generates for each executor an SQL query +
+ Executor 1: + ```sql + SELECT ... FROM table + WHERE (some_hash(partition_column) mod num_partitions) = 0 -- lower_bound + ``` +
+ Executor 2: + ```sql + SELECT ... FROM table + WHERE (some_hash(partition_column) mod num_partitions) = 1 -- lower_bound + 1 + ``` +
+ … +
+ Executor N: + ```sql + SELECT ... FROM table + WHERE (some_hash(partition_column) mod num_partitions) = num_partitions-1 -- upper_bound + ``` +
+ #### NOTE + The hash function implementation depends on RDBMS. It can be `MD5` or any other fast hash function, + or expression based on this function call. Usually such functions accepts any column type as an input. +* `mod` + : Allocate each executor a set of values based on modulus of the [`partition_column`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.partition_column) column. +
+ ### Spark generates for each executor an SQL query +
+ Executor 1: + ```sql + SELECT ... FROM table + WHERE (partition_column mod num_partitions) = 0 -- lower_bound + ``` +
+ Executor 2: + ```sql + SELECT ... FROM table + WHERE (partition_column mod num_partitions) = 1 -- lower_bound + 1 + ``` +
+ Executor N: + ```sql + SELECT ... FROM table + WHERE (partition_column mod num_partitions) = num_partitions-1 -- upper_bound + ``` +
+ #### NOTE + Can be used only with columns of integer type. + +#### Versionadded +Added in version 0.5.0. + +### Examples + +Read data in 10 parallel jobs by range of values in `id_column` column: + +```python +ReadOptions( + partitioning_mode="range", # default mode, can be omitted + partitionColumn="id_column", + numPartitions=10, + # Options below can be discarded because they are + # calculated automatically as MIN and MAX values of `partitionColumn` + lowerBound=0, + upperBound=100_000, +) +``` + +Read data in 10 parallel jobs by hash of values in `some_column` column: + +```python +ReadOptions( + partitioning_mode="hash", + partitionColumn="some_column", + numPartitions=10, + # lowerBound and upperBound are automatically set to `0` and `9` +) +``` + +Read data in 10 parallel jobs by modulus of values in `id_column` column: + +```python +ReadOptions( + partitioning_mode="mod", + partitionColumn="id_column", + numPartitions=10, + # lowerBound and upperBound are automatically set to `0` and `9` +) +``` + + diff --git a/mddocs/connection/db_connection/oracle/sql.md b/mddocs/connection/db_connection/oracle/sql.md new file mode 100644 index 000000000..09059e7aa --- /dev/null +++ b/mddocs/connection/db_connection/oracle/sql.md @@ -0,0 +1,192 @@ + + +# Reading from Oracle using `Oracle.sql` + +`Oracle.sql` allows passing custom SQL query, but does not support incremental strategies. + +#### WARNING +Please take into account [Oracle <-> Spark type mapping](types.md#oracle-types) + +#### WARNING +Statement is executed in **read-write** connection, so if you’re calling some functions/procedures with DDL/DML statements inside, +they can change data in your database. + +## Syntax support + +Only queries with the following syntax are supported: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ❌ `SHOW ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import Oracle + +oracle = Oracle(...) +df = oracle.sql( + """ + SELECT + id, + key, + CAST(value AS VARCHAR2(4000)) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """, + options=Oracle.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from Oracle to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from Oracle to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +### *pydantic model* onetl.connection.db_connection.oracle.options.OracleSQLOptions + +Options specifically for SQL queries + +These options allow you to specify configurations for executing SQL queries +without relying on Spark’s partitioning mechanisms. + +#### Versionadded +Added in version 0.11.0: Split up `Oracle.ReadOptions` to `Oracle.SQLOptions` + +### Examples + +#### NOTE +You can pass any JDBC configuration +[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), +tailored to optimize SQL query execution. **Option names should be in** `camelCase`! + +```python +from onetl.connection import Oracle + +options = Oracle.SQLOptions( + partitionColumn="reg_id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + customSparkOption="value", +) +``` + + + +#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* + +Column used to partition data across multiple executors for parallel query processing. + +#### WARNING +It is highly recommended to use primary key, or column with an index +to avoid performance issues. + +### Example of using `partitionColumn="id"` with `partitioning_mode="range"` + +```sql +-- If partition_column is 'id', with numPartitions=4, lowerBound=1, and upperBound=100: +-- Executor 1 processes IDs from 1 to 25 +SELECT ... FROM table WHERE id >= 1 AND id < 26 +-- Executor 2 processes IDs from 26 to 50 +SELECT ... FROM table WHERE id >= 26 AND id < 51 +-- Executor 3 processes IDs from 51 to 75 +SELECT ... FROM table WHERE id >= 51 AND id < 76 +-- Executor 4 processes IDs from 76 to 100 +SELECT ... FROM table WHERE id >= 76 AND id <= 100 + +-- General case for Executor N +SELECT ... FROM table +WHERE partition_column >= (lowerBound + (N-1) * stride) +AND partition_column <= upperBound +-- Where ``stride`` is calculated as ``(upperBound - lowerBound) / numPartitions``. +``` + + + +#### *field* num_partitions *: int | None* *= None* *(alias 'numPartitions')* + +Number of jobs created by Spark to read the table content in parallel. + + + +#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* + +Defines the starting boundary for partitioning the query’s data. Mandatory if [`partition_column`](#onetl.connection.db_connection.oracle.options.OracleSQLOptions.partition_column) is set + + + +#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* + +Sets the ending boundary for data partitioning. Mandatory if [`partition_column`](#onetl.connection.db_connection.oracle.options.OracleSQLOptions.partition_column) is set + + + +#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* + +After each database session is opened to the remote DB and before starting to read data, +this option executes a custom SQL statement (or a PL/SQL block). + +Use this to implement session initialization code. + +Example: + +```python +sessionInitStatement = """ + BEGIN + execute immediate + 'alter session set "_serial_direct_read"=true'; + END; +""" +``` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int* *= 100000* + +Fetch N rows from an opened cursor per one read round. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value is different from Spark. + +Spark uses driver’s own value, and it may be different in different drivers, +and even versions of the same driver. For example, Oracle has +default `fetchsize=10`, which is absolutely not usable. + +Thus we’ve overridden default value with `100_000`, which should increase reading performance. + +#### Versionchanged +Changed in version 0.2.0: Set explicit default value to `100_000` + + diff --git a/mddocs/connection/db_connection/oracle/types.md b/mddocs/connection/db_connection/oracle/types.md new file mode 100644 index 000000000..130c57855 --- /dev/null +++ b/mddocs/connection/db_connection/oracle/types.md @@ -0,0 +1,303 @@ + + +# Oracle <-> Spark type mapping + +#### NOTE +The results below are valid for Spark 3.5.5, and may differ on other Spark versions. + +## Type detection & casting + +Spark’s DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from Oracle + +This is how Oracle connector performs this: + +* For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and Oracle type. +* Find corresponding `Oracle type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* Create DataFrame from query with specific column names and Spark types. + +### Writing to some existing Oracle table + +This is how Oracle connector performs this: + +* Get names of columns in DataFrame. [1](#id3) +* Perform `SELECT * FROM table LIMIT 0` query. +* Take only columns present in DataFrame (by name, case insensitive). For each found column get Clickhouse type. +* **Find corresponding** `Oracle type (read)` → `Spark type` **combination** (see below) for each DataFrame column. If no combination is found, raise exception. [2](#id4) +* Find corresponding `Spark type` → `Oracle type (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* If `Oracle type (write)` match `Oracle type (read)`, no additional casts will be performed, DataFrame column will be written to Oracle as is. +* If `Oracle type (write)` does not match `Oracle type (read)`, DataFrame column will be casted to target column type **on Oracle side**. + For example, you can write column with text data to `int` column, if column contains valid integer values within supported value range and precision. + +* **[1]** This allows to write data to tables with `DEFAULT` and `GENERATED` columns - if DataFrame has no such column, it will be populated by Oracle. +* **[2]** Yes, this is weird. + +### Create new table using Spark + +#### WARNING +ABSOLUTELY NOT RECOMMENDED! + +This is how Oracle connector performs this: + +* Find corresponding `Spark type` → `Oracle type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* Generate DDL for creating table in Oracle, like `CREATE TABLE (col1 ...)`, and run it. +* Write DataFrame to created table as is. + +But Oracle connector support only limited number of types and almost no custom clauses (like `PARTITION BY`, `INDEX`, etc). +So instead of relying on Spark to create tables: + +### See example + +```python +writer = DBWriter( + connection=oracle, + target="public.table", + options=Oracle.WriteOptions(if_exists="append"), +) +writer.run(df) +``` + +Always prefer creating table with desired DDL **BEFORE WRITING DATA**: + +### See example + +```python +oracle.execute( + """ + CREATE TABLE username.table ( + id NUMBER, + business_dt TIMESTAMP(6), + value VARCHAR2(2000) + ) + """, +) + +writer = DBWriter( + connection=oracle, + target="public.table", + options=Oracle.WriteOptions(if_exists="append"), +) +writer.run(df) +``` + +See Oracle [CREATE TABLE](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/SELECT.html) documentation. + +## Supported types + +### References + +See [List of Oracle types](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/Data-Types.html). + +Here you can find source code with type conversions: + +* [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/OracleDialect.scala#L83-L109) +* [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/OracleDialect.scala#L111-L123) + +### Numeric types + +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | +|-----------------------------|----------------------------------|----------------------------|-------------------------| +| `NUMBER` | `DecimalType(P=38, S=10)` | `NUMBER(P=38, S=10)` | `NUMBER(P=38, S=10)` | +| `NUMBER(P=0..38)` | `DecimalType(P=0..38, S=0)` | `NUMBER(P=0..38, S=0)` | `NUMBER(P=38, S=0)` | +| `NUMBER(P=0..38, S=0..38)` | `DecimalType(P=0..38, S=0..38)` | `NUMBER(P=0..38, S=0..38)` | `NUMBER(P=38, S=0..38)` | +| `NUMBER(P=..., S=-127..-1)` | unsupported [3](#id6) | | | +| `FLOAT` | `DecimalType(P=38, S=10)` | `NUMBER(P=38, S=10)` | `NUMBER(P=38, S=10)` | +| `FLOAT(N)` | | | | +| `REAL` | | | | +| `DOUBLE PRECISION` | | | | +| `BINARY_FLOAT` | `FloatType()` | `NUMBER(P=19, S=4)` | `NUMBER(P=19, S=4)` | +| `BINARY_DOUBLE` | `DoubleType()` | | | +| `SMALLINT` | `DecimalType(P=38, S=0)` | `NUMBER(P=38, S=0)` | `NUMBER(P=38, S=0)` | +| `INTEGER` | | | | +| `LONG` | `StringType()` | `CLOB` | `CLOB` | +* **[3]** Oracle support decimal types with negative scale, like `NUMBER(38, -10)`. Spark doesn’t. + +### Temporal types + +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | +|-------------------------------------|------------------------------------------------------------------------------|------------------------------------------------------|------------------------------------------------------| +| `DATE`, days | `TimestampType()`, microseconds | `TIMESTAMP(6)`, microseconds | `TIMESTAMP(6)`, microseconds | +| `TIMESTAMP`, microseconds | `TimestampType()`, microseconds | `TIMESTAMP(6)`, microseconds | `TIMESTAMP(6)`, microseconds | +| `TIMESTAMP(0)`, seconds | | | | +| `TIMESTAMP(3)`, milliseconds | | | | +| `TIMESTAMP(6)`, microseconds | | | | +| `TIMESTAMP(9)`, nanoseconds | `TimestampType()`, microseconds,
**precision loss** [4](#id8) | `TIMESTAMP(6)`, microseconds,
**precision loss** | `TIMESTAMP(6)`, microseconds,
**precision loss** | +| `TIMESTAMP WITH TIME ZONE` | unsupported | | | +| `TIMESTAMP(N) WITH TIME ZONE` | | | | +| `TIMESTAMP WITH LOCAL TIME ZONE` | | | | +| `TIMESTAMP(N) WITH LOCAL TIME ZONE` | | | | +| `INTERVAL YEAR TO MONTH` | | | | +| `INTERVAL DAY TO SECOND` | | | | + +#### WARNING +Note that types in Oracle and Spark have different value ranges: + +| Oracle type | Min value | Max value | Spark type | Min value | Max value | +|---------------|----------------------------------|---------------------------------|-------------------|------------------------------|------------------------------| +| `date` | `-4712-01-01` | `9999-01-01` | `DateType()` | `0001-01-01` | `9999-12-31` | +| `timestamp` | `-4712-01-01 00:00:00.000000000` | `9999-12-31 23:59:59.999999999` | `TimestampType()` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | + +So not all of values can be read from Oracle to Spark. + +References: +: * [Oracle date, timestamp and intervals documentation](https://oracle-base.com/articles/misc/oracle-dates-timestamps-and-intervals#DATE) + * [Spark DateType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/DateType.html) + * [Spark TimestampType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/TimestampType.html) + +* **[4]** Oracle support timestamp up to nanoseconds precision (`23:59:59.999999999`), but Spark `TimestampType()` supports datetime up to microseconds precision (`23:59:59.999999`). Nanoseconds will be lost during read or write operations. + +### String types + +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | +|----------------------|----------------|-----------------------|------------------------| +| `CHAR` | `StringType()` | `CLOB` | `CLOB` | +| `CHAR(N CHAR)` | | | | +| `CHAR(N BYTE)` | | | | +| `NCHAR` | | | | +| `NCHAR(N)` | | | | +| `VARCHAR(N)` | | | | +| `LONG VARCHAR` | | | | +| `VARCHAR2(N CHAR)` | | | | +| `VARCHAR2(N BYTE)` | | | | +| `NVARCHAR2(N)` | | | | +| `CLOB` | | | | +| `NCLOB` | | | | + +### Binary types + +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | +|----------------------|----------------|-----------------------|------------------------| +| `RAW(N)` | `BinaryType()` | `BLOB` | `BLOB` | +| `LONG RAW` | | | | +| `BLOB` | | | | +| `BFILE` | unsupported | | | + +### Struct types + +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | +|-----------------------------------|----------------|-----------------------|------------------------| +| `XMLType` | `StringType()` | `CLOB` | `CLOB` | +| `URIType` | | | | +| `DBURIType` | | | | +| `XDBURIType` | | | | +| `HTTPURIType` | | | | +| `CREATE TYPE ... AS OBJECT (...)` | | | | +| `JSON` | unsupported | | | +| `CREATE TYPE ... AS VARRAY ...` | | | | +| `CREATE TYPE ... AS TABLE OF ...` | | | | + +### Special types + +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | +|----------------------|-----------------|-----------------------|------------------------| +| `BOOLEAN` | `BooleanType()` | `BOOLEAN` | `NUMBER(P=1, S=0)` | +| `ROWID` | `StringType()` | `CLOB` | `CLOB` | +| `UROWID` | | | | +| `UROWID(N)` | | | | +| `ANYTYPE` | unsupported | | | +| `ANYDATA` | | | | +| `ANYDATASET` | | | | + +## Explicit type cast + +### `DBReader` + +It is possible to explicitly cast column of unsupported type using `DBReader(columns=...)` syntax. + +For example, you can use `CAST(column AS CLOB)` to convert data to string representation on Oracle side, and so it will be read as Spark’s `StringType()`. + +It is also possible to use [JSON_ARRAY](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/JSON_ARRAY.html) +or [JSON_OBJECT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/JSON_OBJECT.html) Oracle functions +to convert column of any type to string representation. Then this JSON string can then be effectively parsed using the [`JSON.parse_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.parse_column) method. + +```python +from onetl.file.format import JSON +from pyspark.sql.types import IntegerType, StructType, StructField + +from onetl.connection import Oracle +from onetl.db import DBReader + +oracle = Oracle(...) + +DBReader( + connection=oracle, + columns=[ + "id", + "supported_column", + "CAST(unsupported_column AS VARCHAR2(4000)) unsupported_column_str", + # or + "JSON_ARRAY(array_column) array_column_json", + ], +) +df = reader.run() + +json_scheme = StructType([StructField("key", IntegerType())]) + +df = df.select( + df.id, + df.supported_column, + df.unsupported_column_str.cast("integer").alias("parsed_integer"), + JSON().parse_column("array_column_json", json_scheme).alias("array_column"), +) +``` + +### `DBWriter` + +It is always possible to convert data on Spark side to string, and then write it to text column in Oracle table. + +To serialize and write JSON data to a `text` or `json` column in an Oracle table use the [`JSON.serialize_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.serialize_column) method. + +```python +from onetl.connection import Oracle +from onetl.db import DBWriter +from onetl.file.format import JSON + +oracle = Oracle(...) + +oracle.execute( + """ + CREATE TABLE schema.target_table ( + id INTEGER, + supported_column TIMESTAMP, + array_column_json VARCHAR2(4000) -- any string type, actually + ) + """, +) + +write_df = df.select( + df.id, + df.supported_column, + JSON().serialize_column(df.unsupported_column).alias("array_column_json"), +) + +writer = DBWriter( + connection=oracle, + target="schema.target_table", +) +writer.run(write_df) +``` + +Then you can parse this column on Oracle side - for example, by creating a view: + +```sql +SELECT + id, + supported_column, + JSON_VALUE(array_column_json, '$[0]' RETURNING NUMBER) AS array_item_0 +FROM + schema.target_table +``` + +Or by using [VIRTUAL column](https://oracle-base.com/articles/11g/virtual-columns-11gr1): + +```sql +CREATE TABLE schema.target_table ( + id INTEGER, + supported_column TIMESTAMP, + array_column_json VARCHAR2(4000), -- any string type, actually + array_item_0 GENERATED ALWAYS AS (JSON_VALUE(array_column_json, '$[0]' RETURNING NUMBER)) VIRTUAL +) +``` + +But data will be parsed on each table read in any case, as Oracle does no support `GENERATED ALWAYS AS (...) STORED` columns. diff --git a/mddocs/connection/db_connection/oracle/write.md b/mddocs/connection/db_connection/oracle/write.md new file mode 100644 index 000000000..f1be20d24 --- /dev/null +++ b/mddocs/connection/db_connection/oracle/write.md @@ -0,0 +1,183 @@ + + +# Writing to Oracle using `DBWriter` + +For writing data to Oracle, use [`DBWriter`](../../../db/db_writer.md#onetl.db.db_writer.db_writer.DBWriter). + +#### WARNING +Please take into account [Oracle <-> Spark type mapping](types.md#oracle-types) + +#### WARNING +It is always recommended to create table explicitly using [Oracle.execute](execute.md#oracle-execute) +instead of relying on Spark’s table DDL generation. + +This is because Spark’s DDL generator can create columns with different precision and types than it is expected, +causing precision loss or other issues. + +## Examples + +```python +from onetl.connection import Oracle +from onetl.db import DBWriter + +oracle = Oracle(...) + +df = ... # data is here + +writer = DBWriter( + connection=oracle, + target="schema.table", + options=Oracle.WriteOptions(if_exists="append"), +) + +writer.run(df) +``` + +## Options + +Method above accepts [`OracleWriteOptions`](#onetl.connection.db_connection.oracle.options.OracleWriteOptions) + +### *pydantic model* onetl.connection.db_connection.oracle.options.OracleWriteOptions + +Spark JDBC writing options. + +#### Versionadded +Added in version 0.5.0: Replace `Oracle.Options` → `Oracle.WriteOptions` + +### Examples + +#### NOTE +You can pass any value +[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), +even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! + +The set of supported options depends on Spark version. + +```python +from onetl.connection import Oracle + +options = Oracle.WriteOptions( + if_exists="append", + batchsize=20_000, + customSparkOption="value", +) +``` + + + +#### *field* if_exists *: JDBCTableExistBehavior* *= JDBCTableExistBehavior.APPEND* *(alias 'mode')* + +Behavior of writing data into existing table. + +Possible values: +: * `append` (default) + : Adds new rows into existing table. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : Data is appended to a table. Table has the same DDL as before writing data +
+ #### WARNING + This mode does not check whether table already contains + rows from dataframe, so duplicated rows can be created. +
+ Also Spark does not support passing custom options to + insert statement, like `ON CONFLICT`, so don’t try to + implement deduplication using unique indexes or constraints. +
+ Instead, write to staging table and perform deduplication + using `execute` method. + * `replace_entire_table` + : **Table is dropped and then created, or truncated**. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : Table content is replaced with dataframe content. +
+ After writing completed, target table could either have the same DDL as + before writing data (`truncate=True`), or can be recreated (`truncate=False` + or source does not support truncation). + * `ignore` + : Ignores the write operation if the table already exists. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : The write operation is ignored, and no data is written to the table. + * `error` + : Raises an error if the table already exists. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : An error is raised, and no data is written to the table. + +#### Versionchanged +Changed in version 0.9.0: Renamed `mode` → `if_exists` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* batchsize *: int* *= 20000* + +How many rows can be inserted per round trip. + +Tuning this option can influence performance of writing. + +#### WARNING +Default value is different from Spark. + +Spark uses quite small value `1000`, which is absolutely not usable +in BigData world. + +Thus we’ve overridden default value with `20_000`, +which should increase writing performance. + +You can increase it even more, up to `50_000`, +but it depends on your database load and number of columns in the row. +Higher values does not increase performance. + +#### Versionchanged +Changed in version 0.4.0: Changed default value from 1000 to 20_000 + + + +#### *field* isolation_level *: str* *= 'READ_UNCOMMITTED'* *(alias 'isolationLevel')* + +The transaction isolation level, which applies to current connection. + +Possible values: +: * `NONE` (as string, not Python’s `None`) + * `READ_COMMITTED` + * `READ_UNCOMMITTED` + * `REPEATABLE_READ` + * `SERIALIZABLE` + +Values correspond to transaction isolation levels defined by JDBC standard. +Please refer the documentation for +[java.sql.Connection](https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html). + + diff --git a/mddocs/connection/db_connection/postgres/connection.md b/mddocs/connection/db_connection/postgres/connection.md new file mode 100644 index 000000000..0ca99169c --- /dev/null +++ b/mddocs/connection/db_connection/postgres/connection.md @@ -0,0 +1,133 @@ + + +# Postgres connection + +### *class* onetl.connection.db_connection.postgres.connection.Postgres(\*, spark: SparkSession, user: str, password: SecretStr, host: Host, database: str, port: int = 5432, extra: PostgresExtra = PostgresExtra(stringtype='unspecified', tcpKeepAlive='true')) + +PostgreSQL JDBC connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on Maven package [org.postgresql:postgresql:42.7.5](https://mvnrepository.com/artifact/org.postgresql/postgresql/42.7.5) +([official Postgres JDBC driver](https://jdbc.postgresql.org/)). + +#### SEE ALSO +Before using this connector please take into account [Prerequisites](prerequisites.md#postgres-prerequisites) + +#### Versionadded +Added in version 0.1.0. + +* **Parameters:** + **host** + : Host of Postgres database. For example: `test.postgres.domain.com` or `193.168.1.11` + + **port** + : Port of Postgres database + + **user** + : User, which have proper access to the database. For example: `some_user` + + **password** + : Password for database connection + + **database** + : Database in RDBMS, NOT schema. +
+ See [this page](https://www.educba.com/postgresql-database-vs-schema/) for more details + + **spark** + : Spark session. + + **extra** + : Specifies one or more extra parameters by which clients can connect to the instance. +
+ For example: `{"ssl": "false"}` +
+ See [Postgres JDBC driver properties documentation](https://jdbc.postgresql.org/documentation/use/) + for more details + +### Examples + +Create and check Postgres connection: + +```python +from onetl.connection import Postgres +from pyspark.sql import SparkSession + +# Create Spark session with Postgres driver loaded +maven_packages = Postgres.get_packages() +spark = ( + SparkSession.builder.appName("spark-app-name") + .config("spark.jars.packages", ",".join(maven_packages)) + .getOrCreate() +) + +# Create connection +postgres = Postgres( + host="database.host.or.ip", + user="user", + password="*****", + database="target_database", + spark=spark, +) +``` + +Create read-only connection: + +```python +... + +# Create connection +postgres = Postgres( + host="database.host.or.ip", + user="user", + password="*****", + database="target_database", + extra={"readOnly": True, "readOnlyMode": "always"}, # <-- + spark=spark, +).check() +``` + + + +#### check() + +Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If not, an exception will be raised. + +* **Returns:** + Connection itself +* **Raises:** + RuntimeError + : If the connection is not available + +### Examples + +```python +connection.check() +``` + + + +#### *classmethod* get_packages(package_version: str | None = None) → list[str] + +Get package names to be downloaded by Spark. Allows specifying a custom JDBC driver version. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **package_version** + : Specifies the version of the PostgreSQL JDBC driver to use. Defaults to `42.7.5`. + +### Examples + +```python +from onetl.connection import Postgres + +Postgres.get_packages() + +# custom package version +Postgres.get_packages(package_version="42.6.0") +``` + + diff --git a/mddocs/connection/db_connection/postgres/execute.md b/mddocs/connection/db_connection/postgres/execute.md new file mode 100644 index 000000000..e28ebc5da --- /dev/null +++ b/mddocs/connection/db_connection/postgres/execute.md @@ -0,0 +1,189 @@ + + +# Executing statements in Postgres + +#### WARNING +Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + +Do **NOT** use them to read large amounts of data. Use [DBReader](read.md#postgres-read) or [Postgres.sql](sql.md#postgres-sql) instead. + +## How to + +There are 2 ways to execute some statement in Postgres + +### Use `Postgres.fetch` + +Use this method to execute some `SELECT` query which returns **small number or rows**, like reading +Postgres config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts [`Postgres.FetchOptions`](#onetl.connection.db_connection.postgres.options.PostgresFetchOptions). + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### WARNING +Please take into account [Postgres <-> Spark type mapping](types.md#postgres-types). + +#### Syntax support + +This method supports **any** query syntax supported by Postgres, like: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Postgres + +postgres = Postgres(...) + +df = postgres.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=Postgres.FetchOptions(queryTimeout=10), +) +postgres.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `Postgres.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts [`Postgres.ExecuteOptions`](#onetl.connection.db_connection.postgres.options.PostgresExecuteOptions). + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Postgres, like: + +* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +* ✅︎ `ALTER ...` +* ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +* ✅︎ `CALL procedure(arg1, arg2) ...` +* ✅︎ `SELECT func(arg1, arg2)` or `{call func(arg1, arg2)}` - special syntax for calling functions +* ✅︎ other statements not mentioned here +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Postgres + +postgres = Postgres(...) + +postgres.execute("DROP TABLE schema.table") +postgres.execute( + """ + CREATE TABLE schema.table ( + id bigint GENERATED ALWAYS AS IDENTITY, + key text, + value real + ) + """, + options=Postgres.ExecuteOptions(queryTimeout=10), +) +``` + +## Options + +### *pydantic model* onetl.connection.db_connection.postgres.options.PostgresFetchOptions + +Options related to fetching data from databases via JDBC. + +#### Versionadded +Added in version 0.11.0: Replace `Postgres.JDBCOptions` → `Postgres.FetchOptions` + +### Examples + +#### NOTE +You can pass any value supported by underlying JDBC driver class, +even if it is not mentioned in this documentation. + +```python +from onetl.connection import Postgres + +options = Postgres.FetchOptions( + queryTimeout=60_000, + fetchsize=100_000, + customSparkOption="value", +) +``` + + +* **Fields:** + - [`fetchsize (int | None)`](#onetl.connection.db_connection.postgres.options.PostgresFetchOptions.fetchsize) + - [`query_timeout (int | None)`](#onetl.connection.db_connection.postgres.options.PostgresFetchOptions.query_timeout) + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int | None* *= None* + +How many rows to fetch per round trip. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value depends on driver. For example, Oracle has +default `fetchsize=10`. + + + +### *pydantic model* onetl.connection.db_connection.postgres.options.PostgresExecuteOptions + +Options related to executing statements in databases via JDBC. + +#### Versionadded +Added in version 0.11.0: Replace `Postgres.JDBCOptions` → `Postgres.ExecuteOptions` + +### Examples + +#### NOTE +You can pass any value supported by underlying JDBC driver class, +even if it is not mentioned in this documentation. + +```python +from onetl.connection import Postgres + +options = Postgres.ExecuteOptions( + queryTimeout=60_000, + customSparkOption="value", +) +``` + + +* **Fields:** + - [`fetchsize (int | None)`](#onetl.connection.db_connection.postgres.options.PostgresExecuteOptions.fetchsize) + - [`query_timeout (int | None)`](#onetl.connection.db_connection.postgres.options.PostgresExecuteOptions.query_timeout) + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int | None* *= None* + +How many rows to fetch per round trip. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value depends on driver. For example, Oracle has +default `fetchsize=10`. + + diff --git a/mddocs/connection/db_connection/postgres/index.md b/mddocs/connection/db_connection/postgres/index.md new file mode 100644 index 000000000..e1a8f76eb --- /dev/null +++ b/mddocs/connection/db_connection/postgres/index.md @@ -0,0 +1,19 @@ + + +# Postgres + +# Connection + +* [Prerequisites](prerequisites.md) +* [Postgres connection](connection.md) + +# Operations + +* [Reading from Postgres using `DBReader`](read.md) +* [Reading from Postgres using `Postgres.sql`](sql.md) +* [Writing to Postgres using `DBWriter`](write.md) +* [Executing statements in Postgres](execute.md) + +# Troubleshooting + +* [Postgres <-> Spark type mapping](types.md) diff --git a/mddocs/connection/db_connection/postgres/prerequisites.md b/mddocs/connection/db_connection/postgres/prerequisites.md new file mode 100644 index 000000000..2bfb04e0f --- /dev/null +++ b/mddocs/connection/db_connection/postgres/prerequisites.md @@ -0,0 +1,71 @@ + + +# Prerequisites + +## Version Compatibility + +* PostgreSQL server versions: + : * Officially declared: 8.2 - 17 + * Actually tested: 9.4.26, 17.3 +* Spark versions: 2.3.x - 3.5.x +* Java versions: 8 - 20 + +See [official documentation](https://jdbc.postgresql.org/). + +## Installing PySpark + +To use Postgres connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Connecting to Postgres + +### Allowing connection to Postgres instance + +Ask your Postgres administrator to allow your user (and probably IP) to connect to instance, +e.g. by updating `pg_hba.conf` file. + +See [official documentation](https://www.postgresql.org/docs/current/auth-pg-hba-conf.html). + +### Connection port + +Connection is usually performed to port 5432. Port may differ for different Postgres instances. +Please ask your Postgres administrator to provide required information. + +### Connection host + +It is possible to connect to Postgres by using either DNS name of host or it’s IP address. + +If you’re using Postgres cluster, it is currently possible to connect only to **one specific node**. +Connecting to multiple nodes to perform load balancing, as well as automatic failover to new master/replica are not supported. + +### Required grants + +Ask your Postgres cluster administrator to set following grants for a user, +used for creating a connection: + +Read + Write + +```sql +-- allow creating tables in specific schema +GRANT USAGE, CREATE ON SCHEMA myschema TO username; + +-- allow read & write access to specific table +GRANT SELECT, INSERT ON myschema.mytable TO username; + +-- only if if_exists="replace_entire_table" is used: +GRANT TRUNCATE ON myschema.mytable TO username; +``` + +Read only + +```sql +-- allow creating tables in specific schema +GRANT USAGE ON SCHEMA myschema TO username; + +-- allow read access to specific table +GRANT SELECT ON myschema.mytable TO username; +``` + +More details can be found in [official documentation](https://www.postgresql.org/docs/current/sql-grant.html). diff --git a/mddocs/connection/db_connection/postgres/read.md b/mddocs/connection/db_connection/postgres/read.md new file mode 100644 index 000000000..b0047f58a --- /dev/null +++ b/mddocs/connection/db_connection/postgres/read.md @@ -0,0 +1,350 @@ + + +# Reading from Postgres using `DBReader` + +[`DBReader`](../../../db/db_reader.md#onetl.db.db_reader.db_reader.DBReader) supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, +but does not support custom queries, like `JOIN`. + +#### WARNING +Please take into account [Postgres <-> Spark type mapping](types.md#postgres-types) + +## Supported DBReader features + +* ✅︎ `columns` +* ✅︎ `where` +* ✅︎ `hwm`, supported strategies: +* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) +* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) +* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) +* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) +* ❌ `hint` (is not supported by Postgres) +* ❌ `df_schema` +* ✅︎ `options` (see [`Postgres.ReadOptions`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions)) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Postgres +from onetl.db import DBReader + +postgres = Postgres(...) + +reader = DBReader( + connection=postgres, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + options=Postgres.ReadOptions(partitionColumn="id", numPartitions=10), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Postgres +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +postgres = Postgres(...) + +reader = DBReader( + connection=postgres, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="postgres_hwm", expression="updated_dt"), + options=Postgres.ReadOptions(partitionColumn="id", numPartitions=10), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Postgres to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Postgres to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +### *pydantic model* onetl.connection.db_connection.postgres.options.PostgresReadOptions + +Spark JDBC reading options. + +#### Versionadded +Added in version 0.5.0: Replace `Postgres.Options` → `Postgres.ReadOptions` + +### Examples + +#### NOTE +You can pass any value +[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), +even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! + +The set of supported options depends on Spark version. + +```python +from onetl.connection import Postgres + +options = Postgres.ReadOptions( + partitioning_mode="range", + partitionColumn="reg_id", + numPartitions=10, + customSparkOption="value", +) +``` + + +* **Fields:** + - [`fetchsize (int)`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.fetchsize) + - [`lower_bound (int | None)`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.lower_bound) + - [`num_partitions (pydantic.types.PositiveInt)`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.num_partitions) + - [`partition_column (str | None)`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.partition_column) + - [`partitioning_mode (onetl.connection.db_connection.jdbc_connection.options.JDBCPartitioningMode)`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.partitioning_mode) + - [`query_timeout (int | None)`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.query_timeout) + - [`session_init_statement (str | None)`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.session_init_statement) + - [`upper_bound (int | None)`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.upper_bound) + +#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* + +Column used to parallelize reading from a table. + +#### WARNING +It is highly recommended to use primary key, or column with an index +to avoid performance issues. + +#### NOTE +Column type depends on [`partitioning_mode`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.partitioning_mode). + +* `partitioning_mode="range"` requires column to be an integer, date or timestamp (can be NULL, but not recommended). +* `partitioning_mode="hash"` accepts any column type (NOT NULL). +* `partitioning_mode="mod"` requires column to be an integer (NOT NULL). + +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.partitioning_mode) for more details + + + +#### *field* num_partitions *: PositiveInt* *= 1* *(alias 'numPartitions')* + +Number of jobs created by Spark to read the table content in parallel. +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.partitioning_mode) for more details + + +* **Constraints:** + - **exclusiveMinimum** = 0 + +#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* + +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.partitioning_mode) for more details + + + +#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* + +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.partitioning_mode) for more details + + + +#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* + +After each database session is opened to the remote DB and before starting to read data, +this option executes a custom SQL statement (or a PL/SQL block). + +Use this to implement session initialization code. + +Example: + +```python +sessionInitStatement = """ + BEGIN + execute immediate + 'alter session set "_serial_direct_read"=true'; + END; +""" +``` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int* *= 100000* + +Fetch N rows from an opened cursor per one read round. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value is different from Spark. + +Spark uses driver’s own value, and it may be different in different drivers, +and even versions of the same driver. For example, Oracle has +default `fetchsize=10`, which is absolutely not usable. + +Thus we’ve overridden default value with `100_000`, which should increase reading performance. + +#### Versionchanged +Changed in version 0.2.0: Set explicit default value to `100_000` + + + +#### *field* partitioning_mode *: JDBCPartitioningMode* *= JDBCPartitioningMode.RANGE* + +Defines how Spark will parallelize reading from table. + +Possible values: + +* `range` (default) + : Allocate each executor a range of values from column passed into [`partition_column`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.partition_column). +
+ ### Spark generates for each executor an SQL query +
+ Executor 1: + ```sql + SELECT ... FROM table + WHERE (partition_column >= lowerBound + OR partition_column IS NULL) + AND partition_column < (lower_bound + stride) + ``` +
+ Executor 2: + ```sql + SELECT ... FROM table + WHERE partition_column >= (lower_bound + stride) + AND partition_column < (lower_bound + 2 * stride) + ``` +
+ … +
+ Executor N: + ```sql + SELECT ... FROM table + WHERE partition_column >= (lower_bound + (N-1) * stride) + AND partition_column <= upper_bound + ``` +
+ Where `stride=(upper_bound - lower_bound) / num_partitions`. +
+ #### NOTE + Can be used only with columns of integer, date or timestamp types. +
+ #### NOTE + [`lower_bound`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.lower_bound), [`upper_bound`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.upper_bound) and [`num_partitions`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.num_partitions) are used just to + calculate the partition stride, **NOT** for filtering the rows in table. + So all rows in the table will be returned (unlike *Incremental* [Read Strategies](../../../strategy/index.md#strategy)). +
+ #### NOTE + All queries are executed in parallel. To execute them sequentially, use *Batch* [Read Strategies](../../../strategy/index.md#strategy). +* `hash` + : Allocate each executor a set of values based on hash of the [`partition_column`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.partition_column) column. +
+ ### Spark generates for each executor an SQL query +
+ Executor 1: + ```sql + SELECT ... FROM table + WHERE (some_hash(partition_column) mod num_partitions) = 0 -- lower_bound + ``` +
+ Executor 2: + ```sql + SELECT ... FROM table + WHERE (some_hash(partition_column) mod num_partitions) = 1 -- lower_bound + 1 + ``` +
+ … +
+ Executor N: + ```sql + SELECT ... FROM table + WHERE (some_hash(partition_column) mod num_partitions) = num_partitions-1 -- upper_bound + ``` +
+ #### NOTE + The hash function implementation depends on RDBMS. It can be `MD5` or any other fast hash function, + or expression based on this function call. Usually such functions accepts any column type as an input. +* `mod` + : Allocate each executor a set of values based on modulus of the [`partition_column`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.partition_column) column. +
+ ### Spark generates for each executor an SQL query +
+ Executor 1: + ```sql + SELECT ... FROM table + WHERE (partition_column mod num_partitions) = 0 -- lower_bound + ``` +
+ Executor 2: + ```sql + SELECT ... FROM table + WHERE (partition_column mod num_partitions) = 1 -- lower_bound + 1 + ``` +
+ Executor N: + ```sql + SELECT ... FROM table + WHERE (partition_column mod num_partitions) = num_partitions-1 -- upper_bound + ``` +
+ #### NOTE + Can be used only with columns of integer type. + +#### Versionadded +Added in version 0.5.0. + +### Examples + +Read data in 10 parallel jobs by range of values in `id_column` column: + +```python +ReadOptions( + partitioning_mode="range", # default mode, can be omitted + partitionColumn="id_column", + numPartitions=10, + # Options below can be discarded because they are + # calculated automatically as MIN and MAX values of `partitionColumn` + lowerBound=0, + upperBound=100_000, +) +``` + +Read data in 10 parallel jobs by hash of values in `some_column` column: + +```python +ReadOptions( + partitioning_mode="hash", + partitionColumn="some_column", + numPartitions=10, + # lowerBound and upperBound are automatically set to `0` and `9` +) +``` + +Read data in 10 parallel jobs by modulus of values in `id_column` column: + +```python +ReadOptions( + partitioning_mode="mod", + partitionColumn="id_column", + numPartitions=10, + # lowerBound and upperBound are automatically set to `0` and `9` +) +``` + + diff --git a/mddocs/connection/db_connection/postgres/sql.md b/mddocs/connection/db_connection/postgres/sql.md new file mode 100644 index 000000000..0a5638d62 --- /dev/null +++ b/mddocs/connection/db_connection/postgres/sql.md @@ -0,0 +1,191 @@ + + +# Reading from Postgres using `Postgres.sql` + +`Postgres.sql` allows passing custom SQL query, but does not support incremental strategies. + +#### WARNING +Please take into account [Postgres <-> Spark type mapping](types.md#postgres-types) + +#### WARNING +Statement is executed in **read-write** connection, so if you’re calling some functions/procedures with DDL/DML statements inside, +they can change data in your database. + +## Syntax support + +Only queries with the following syntax are supported: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import Postgres + +postgres = Postgres(...) +df = postgres.sql( + """ + SELECT + id, + key, + CAST(value AS text) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """, + options=Postgres.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from Postgres to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from Postgres to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +### *pydantic model* onetl.connection.db_connection.postgres.options.PostgresSQLOptions + +Options specifically for SQL queries + +These options allow you to specify configurations for executing SQL queries +without relying on Spark’s partitioning mechanisms. + +#### Versionadded +Added in version 0.11.0: Split up `Postgres.ReadOptions` to `Postgres.SQLOptions` + +### Examples + +#### NOTE +You can pass any JDBC configuration +[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), +tailored to optimize SQL query execution. **Option names should be in** `camelCase`! + +```python +from onetl.connection import Postgres + +options = Postgres.SQLOptions( + partitionColumn="reg_id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + customSparkOption="value", +) +``` + + + +#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* + +Column used to partition data across multiple executors for parallel query processing. + +#### WARNING +It is highly recommended to use primary key, or column with an index +to avoid performance issues. + +### Example of using `partitionColumn="id"` with `partitioning_mode="range"` + +```sql +-- If partition_column is 'id', with numPartitions=4, lowerBound=1, and upperBound=100: +-- Executor 1 processes IDs from 1 to 25 +SELECT ... FROM table WHERE id >= 1 AND id < 26 +-- Executor 2 processes IDs from 26 to 50 +SELECT ... FROM table WHERE id >= 26 AND id < 51 +-- Executor 3 processes IDs from 51 to 75 +SELECT ... FROM table WHERE id >= 51 AND id < 76 +-- Executor 4 processes IDs from 76 to 100 +SELECT ... FROM table WHERE id >= 76 AND id <= 100 + +-- General case for Executor N +SELECT ... FROM table +WHERE partition_column >= (lowerBound + (N-1) * stride) +AND partition_column <= upperBound +-- Where ``stride`` is calculated as ``(upperBound - lowerBound) / numPartitions``. +``` + + + +#### *field* num_partitions *: int | None* *= None* *(alias 'numPartitions')* + +Number of jobs created by Spark to read the table content in parallel. + + + +#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* + +Defines the starting boundary for partitioning the query’s data. Mandatory if [`partition_column`](#onetl.connection.db_connection.postgres.options.PostgresSQLOptions.partition_column) is set + + + +#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* + +Sets the ending boundary for data partitioning. Mandatory if [`partition_column`](#onetl.connection.db_connection.postgres.options.PostgresSQLOptions.partition_column) is set + + + +#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* + +After each database session is opened to the remote DB and before starting to read data, +this option executes a custom SQL statement (or a PL/SQL block). + +Use this to implement session initialization code. + +Example: + +```python +sessionInitStatement = """ + BEGIN + execute immediate + 'alter session set "_serial_direct_read"=true'; + END; +""" +``` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int* *= 100000* + +Fetch N rows from an opened cursor per one read round. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value is different from Spark. + +Spark uses driver’s own value, and it may be different in different drivers, +and even versions of the same driver. For example, Oracle has +default `fetchsize=10`, which is absolutely not usable. + +Thus we’ve overridden default value with `100_000`, which should increase reading performance. + +#### Versionchanged +Changed in version 0.2.0: Set explicit default value to `100_000` + + diff --git a/mddocs/connection/db_connection/postgres/types.md b/mddocs/connection/db_connection/postgres/types.md new file mode 100644 index 000000000..3baca234d --- /dev/null +++ b/mddocs/connection/db_connection/postgres/types.md @@ -0,0 +1,372 @@ + + +# Postgres <-> Spark type mapping + +#### NOTE +The results below are valid for Spark 3.5.5, and may differ on other Spark versions. + +## Type detection & casting + +Spark’s DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from Postgres + +This is how Postgres connector performs this: + +* For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and Postgres type. +* Find corresponding `Postgres type (read)` → `Spark type` combination (see below) for each DataFrame column [1](#id2). If no combination is found, raise exception. +* Create DataFrame from query with specific column names and Spark types. + +* **[1]** All Postgres types that doesn’t have corresponding Java type are converted to `String`. + +### Writing to some existing Postgres table + +This is how Postgres connector performs this: + +* Get names of columns in DataFrame. [1](#id2) +* Perform `SELECT * FROM table LIMIT 0` query. +* Take only columns present in DataFrame (by name, case insensitive) [2](#id6). For each found column get Postgres type. +* Find corresponding `Spark type` → `Postgres type (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* If `Postgres type (write)` match `Postgres type (read)`, no additional casts will be performed, DataFrame column will be written to Postgres as is. +* If `Postgres type (write)` does not match `Postgres type (read)`, DataFrame column will be casted to target column type **on Postgres side**. + For example, you can write column with text data to `int` column, if column contains valid integer values within supported value range and precision [3](#id7). + +* **[2]** This allows to write data to tables with `DEFAULT` and `GENERATED` columns - if DataFrame has no such column, it will be populated by Postgres. +* **[3]** This is true only if either DataFrame column is a `StringType()`, or target column is `text` type. But other types cannot be silently converted, like `bytea -> bit(N)`. This requires explicit casting, see [Manual conversion to string](). + +### Create new table using Spark + +#### WARNING +ABSOLUTELY NOT RECOMMENDED! + +This is how Postgres connector performs this: + +* Find corresponding `Spark type` → `Postgres type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* Generate DDL for creating table in Postgres, like `CREATE TABLE (col1 ...)`, and run it. +* Write DataFrame to created table as is. + +But Postgres connector support only limited number of types and almost no custom clauses (like `PARTITION BY`, `INDEX`, etc). +So instead of relying on Spark to create tables: + +### See example + +```python +writer = DBWriter( + connection=postgres, + target="public.table", + options=Postgres.WriteOptions( + if_exists="append", + createTableOptions="PARTITION BY RANGE (id)", + ), +) +writer.run(df) +``` + +Always prefer creating table with desired DDL **BEFORE WRITING DATA**: + +### See example + +```python +postgres.execute( + """ + CREATE TABLE public.table ( + id bigint, + business_dt timestamp(6), + value json + ) + PARTITION BY RANGE (Id) + """, +) + +writer = DBWriter( + connection=postgres, + target="public.table", + options=Postgres.WriteOptions(if_exists="append"), +) +writer.run(df) +``` + +See Postgres [CREATE TABLE](https://www.postgresql.org/docs/current/sql-createtable.html) documentation. + +## Supported types + +### References + +See [List of Postgres types](https://www.postgresql.org/docs/current/datatype.html). + +Here you can find source code with type conversions: + +* [Postgres <-> JDBC](https://github.com/pgjdbc/pgjdbc/blob/REL42.6.0/pgjdbc/src/main/java/org/postgresql/jdbc/TypeInfoCache.java#L78-L112) +* [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/PostgresDialect.scala#L52-L108) +* [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/PostgresDialect.scala#L118-L132) + +### Numeric types + +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | +|-----------------------------|-------------------------------------|-----------------------------|--------------------------| +| `decimal` | `DecimalType(P=38, S=18)` | `decimal(P=38, S=18)` | `decimal` (unbounded) | +| `decimal(P=0..38)` | `DecimalType(P=0..38, S=0)` | `decimal(P=0..38, S=0)` | | +| `decimal(P=0..38, S=0..38)` | `DecimalType(P=0..38, S=0..38)` | `decimal(P=0..38, S=0..38)` | | +| `decimal(P=39.., S=0..)` | unsupported [4](#id11) | | | +| `decimal(P=.., S=..-1)` | unsupported [5](#id12) | | | +| `real` | `FloatType()` | `real` | `real` | +| `double precision` | `DoubleType()` | `double precision` | `double precision` | +| `smallint` | `ShortType()` | `smallint` | `smallint` | +| `-` | `ByteType()` | | | +| `integer` | `IntegerType()` | `integer` | `integer` | +| `bigint` | `LongType()` | `bigint` | `bigint` | +| `money` | `StringType()` [1](#id2) | `text` | `text` | +| `int4range` | | | | +| `int8range` | | | | +| `numrange` | | | | +| `int2vector` | | | | +* **[4]** Postgres support decimal types with unlimited precision. But Spark’s `DecimalType(P, S)` supports maximum `P=38` (128 bit). It is impossible to read, write or operate with values of larger precision, this leads to an exception. +* **[5]** Postgres support decimal types with negative scale, like `decimal(38, -10)`. Spark doesn’t. + +### Temporal types + +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | +|----------------------------------|----------------------------------------------------------------------|-------------------------|--------------------------| +| `date` | `DateType()` | `date` | `date` | +| `time` | `TimestampType()`,
with time format quirks [6](#id16) | `timestamp(6)` | `timestamp(6)` | +| `time(0..6)` | | | | +| `time with time zone` | | | | +| `time(0..6) with time zone` | | | | +| `timestamp` | `TimestampType()` | `timestamp(6)` | `timestamp(6)` | +| `timestamp(0..6)` | | | | +| `timestamp with time zone` | | | | +| `timestamp(0..6) with time zone` | | | | +| `-` | `TimestampNTZType()` | `timestamp(6)` | `timestamp(6)` | +| `interval` of any precision | `StringType()` [1](#id2) | `text` | `text` | +| `-` | `DayTimeIntervalType()` | unsupported | unsupported | +| `-` | `YearMonthIntervalType()` | unsupported | unsupported | +| `daterange` | `StringType()` [1](#id2) | `text` | `text` | +| `tsrange` | | | | +| `tstzrange` | | | | + +#### WARNING +Note that types in Postgres and Spark have different value ranges: + +| Postgres type | Min value | Max value | Spark type | Min value | Max value | +|-----------------|-------------------------------|--------------------------------|-------------------|------------------------------|------------------------------| +| `date` | `-4713-01-01` | `5874897-01-01` | `DateType()` | `0001-01-01` | `9999-12-31` | +| `timestamp` | `-4713-01-01 00:00:00.000000` | `294276-12-31 23:59:59.999999` | `TimestampType()` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | +| `time` | `00:00:00.000000` | `24:00:00.000000` | | | | + +So not all of values can be read from Postgres to Spark. + +References: +: * [Postgres date/time types documentation](https://www.postgresql.org/docs/current/datatype-datetime.html) + * [Spark DateType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/DateType.html) + * [Spark TimestampType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/TimestampType.html) + +* **[6]** `time` type is the same as `timestamp` with date `1970-01-01`. So instead of reading data from Postgres like `23:59:59` it is actually read `1970-01-01 23:59:59`, and vice versa. + +### String types + +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | +|---------------------------|-------------------------------------|-------------------------|--------------------------| +| `character` | `StringType()` | `text` | `text` | +| `character(N)` | | | | +| `character varying` | | | | +| `character varying(N)` | | | | +| `text` | | | | +| `json` | | | | +| `jsonb` | | | | +| `xml` | | | | +| `CREATE TYPE ... AS ENUM` | `StringType()` [1](#id2) | | | +| `tsvector` | | | | +| `tsquery` | | | | +| `-` | `CharType()` | `unsupported` | `unsupported` | +| `-` | `VarcharType()` | `unsupported` | `unsupported` | + +### Binary types + +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | +|------------------------|-------------------------------------|----------------------------------------------------------|--------------------------| +| `boolean` | `BooleanType()` | `boolean` | `boolean` | +| `bit` | `BooleanType()` | `bool`,
**cannot insert data** [3](#id7) | `bool` | +| `bit(N=1)` | | | | +| `bit(N=2..)` | `ByteType()` | `bytea`,
**cannot insert data** [3](#id7) | `bytea` | +| `bit varying` | `StringType()` [1](#id2) | `text` | `text` | +| `bit varying(N)` | | | | +| `bytea` | `BinaryType()` | `bytea` | `bytea` | + +### Struct types + +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | +|------------------------------|-------------------------------------|-------------------------|--------------------------| +| `T[]` | `ArrayType(T)` | `T[]` | `T[]` | +| `T[][]` | unsupported | | | +| `CREATE TYPE sometype (...)` | `StringType()` [1](#id2) | `text` | `text` | +| `-` | `StructType()` | unsupported | | +| `-` | `MapType()` | | | + +### Network types + +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | +|------------------------|-------------------------------------|-------------------------|--------------------------| +| `cidr` | `StringType()` [1](#id2) | `text` | `text` | +| `inet` | | | | +| `macaddr` | | | | +| `macaddr8` | | | | + +### Geo types + +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | +|------------------------|-------------------------------------|-------------------------|--------------------------| +| `circle` | `StringType()` [1](#id2) | `text` | `text` | +| `box` | | | | +| `line` | | | | +| `lseg` | | | | +| `path` | | | | +| `point` | | | | +| `polygon` | | | | +| `polygon` | | | | + +## Explicit type cast + +### `DBReader` + +It is possible to explicitly cast column of unsupported type using `DBReader(columns=...)` syntax. + +For example, you can use `CAST(column AS text)` to convert data to string representation on Postgres side, and so it will be read as Spark’s `StringType()`. + +It is also possible to use [to_json](https://www.postgresql.org/docs/current/functions-json.html) Postgres function to convert column of any type to string representation, and then parse this column on Spark side you can use the [`JSON.parse_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.parse_column) method: + +```python +from pyspark.sql.types import IntegerType + +from onetl.connection import Postgres +from onetl.db import DBReader +from onetl.file.format import JSON + +postgres = Postgres(...) + +DBReader( + connection=postgres, + columns=[ + "id", + "supported_column", + "CAST(unsupported_column AS text) unsupported_column_str", + # or + "to_json(unsupported_column) array_column_json", + ], +) +df = reader.run() + +json_schema = StructType( + [ + StructField("id", IntegerType(), nullable=True), + StructField("name", StringType(), nullable=True), + ..., + ] +) +df = df.select( + df.id, + df.supported_column, + # explicit cast + df.unsupported_column_str.cast("integer").alias("parsed_integer"), + JSON().parse_column("array_column_json", json_schema).alias("json_string"), +) +``` + +### `DBWriter` + +It is always possible to convert data on the Spark side to a string, and then write it to a text column in a Postgres table. + +#### Using JSON.serialize_column + +You can use the [`JSON.serialize_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.serialize_column) method for data serialization: + +```python +from onetl.file.format import JSON +from pyspark.sql.functions import col + +from onetl.connection import Postgres +from onetl.db import DBWriter + +postgres = Postgres(...) + +postgres.execute( + """ + CREATE TABLE schema.target_table ( + id int, + supported_column timestamp, + array_column_json jsonb -- any column type, actually + ) + """, +) + +write_df = df.select( + df.id, + df.supported_column, + JSON().serialize_column(df.unsupported_column).alias("array_column_json"), +) + +writer = DBWriter( + connection=postgres, + target="schema.target_table", +) +writer.run(write_df) +``` + +Then you can parse this column on the Postgres side (for example, by creating a view): + +```sql +SELECT + id, + supported_column, + array_column_json->'0' AS array_item_0 +FROM + schema.target_table +``` + +To avoid casting the value on every table read you can use [GENERATED ALWAYS STORED](https://www.postgresql.org/docs/current/ddl-generated-columns.html) column, but this requires 2x space (for original and parsed value). + +#### Manual conversion to string + +Postgres connector also supports conversion text value directly to target column type, if this value has a proper format. + +For example, you can write data like `[123, 345)` to `int8range` type because Postgres allows cast `'[123, 345)'::int8range'`: + +```python +from pyspark.sql.ftypes import StringType +from pyspark.sql.functions import udf + +from onetl.connection import Postgres +from onetl.db import DBReader + +postgres = Postgres(...) + +postgres.execute( + """ + CREATE TABLE schema.target_table ( + id int, + range_column int8range -- any column type, actually + ) + """, +) + + +@udf(returnType=StringType()) +def array_to_range(value: tuple): + """This UDF allows to convert tuple[start, end] to Postgres' range format""" + start, end = value + return f"[{start},{end})" + + +write_df = df.select( + df.id, + array_to_range(df.range_column).alias("range_column"), +) + +writer = DBWriter( + connection=postgres, + target="schema.target_table", +) +writer.run(write_df) +``` + +This can be tricky to implement and may lead to longer write process. +But this does not require extra space on Postgres side, and allows to avoid explicit value cast on every table read. diff --git a/mddocs/connection/db_connection/postgres/write.md b/mddocs/connection/db_connection/postgres/write.md new file mode 100644 index 000000000..3b5822b1f --- /dev/null +++ b/mddocs/connection/db_connection/postgres/write.md @@ -0,0 +1,183 @@ + + +# Writing to Postgres using `DBWriter` + +For writing data to Postgres, use [`DBWriter`](../../../db/db_writer.md#onetl.db.db_writer.db_writer.DBWriter). + +#### WARNING +Please take into account [Postgres <-> Spark type mapping](types.md#postgres-types) + +#### WARNING +It is always recommended to create table explicitly using [Postgres.execute](execute.md#postgres-execute) +instead of relying on Spark’s table DDL generation. + +This is because Spark’s DDL generator can create columns with different precision and types than it is expected, +causing precision loss or other issues. + +## Examples + +```python +from onetl.connection import Postgres +from onetl.db import DBWriter + +postgres = Postgres(...) + +df = ... # data is here + +writer = DBWriter( + connection=postgres, + target="schema.table", + options=Postgres.WriteOptions(if_exists="append"), +) + +writer.run(df) +``` + +## Options + +Method above accepts [`Postgres.WriteOptions`](#onetl.connection.db_connection.postgres.options.PostgresWriteOptions) + +### *pydantic model* onetl.connection.db_connection.postgres.options.PostgresWriteOptions + +Spark JDBC writing options. + +#### Versionadded +Added in version 0.5.0: Replace `Postgres.Options` → `Postgres.WriteOptions` + +### Examples + +#### NOTE +You can pass any value +[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), +even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! + +The set of supported options depends on Spark version. + +```python +from onetl.connection import Postgres + +options = Postgres.WriteOptions( + if_exists="append", + batchsize=20_000, + customSparkOption="value", +) +``` + + + +#### *field* if_exists *: JDBCTableExistBehavior* *= JDBCTableExistBehavior.APPEND* *(alias 'mode')* + +Behavior of writing data into existing table. + +Possible values: +: * `append` (default) + : Adds new rows into existing table. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : Data is appended to a table. Table has the same DDL as before writing data +
+ #### WARNING + This mode does not check whether table already contains + rows from dataframe, so duplicated rows can be created. +
+ Also Spark does not support passing custom options to + insert statement, like `ON CONFLICT`, so don’t try to + implement deduplication using unique indexes or constraints. +
+ Instead, write to staging table and perform deduplication + using `execute` method. + * `replace_entire_table` + : **Table is dropped and then created, or truncated**. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : Table content is replaced with dataframe content. +
+ After writing completed, target table could either have the same DDL as + before writing data (`truncate=True`), or can be recreated (`truncate=False` + or source does not support truncation). + * `ignore` + : Ignores the write operation if the table already exists. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : The write operation is ignored, and no data is written to the table. + * `error` + : Raises an error if the table already exists. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : An error is raised, and no data is written to the table. + +#### Versionchanged +Changed in version 0.9.0: Renamed `mode` → `if_exists` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* batchsize *: int* *= 20000* + +How many rows can be inserted per round trip. + +Tuning this option can influence performance of writing. + +#### WARNING +Default value is different from Spark. + +Spark uses quite small value `1000`, which is absolutely not usable +in BigData world. + +Thus we’ve overridden default value with `20_000`, +which should increase writing performance. + +You can increase it even more, up to `50_000`, +but it depends on your database load and number of columns in the row. +Higher values does not increase performance. + +#### Versionchanged +Changed in version 0.4.0: Changed default value from 1000 to 20_000 + + + +#### *field* isolation_level *: str* *= 'READ_UNCOMMITTED'* *(alias 'isolationLevel')* + +The transaction isolation level, which applies to current connection. + +Possible values: +: * `NONE` (as string, not Python’s `None`) + * `READ_COMMITTED` + * `READ_UNCOMMITTED` + * `REPEATABLE_READ` + * `SERIALIZABLE` + +Values correspond to transaction isolation levels defined by JDBC standard. +Please refer the documentation for +[java.sql.Connection](https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html). + + diff --git a/mddocs/connection/db_connection/teradata/connection.md b/mddocs/connection/db_connection/teradata/connection.md new file mode 100644 index 000000000..de2745c4e --- /dev/null +++ b/mddocs/connection/db_connection/teradata/connection.md @@ -0,0 +1,134 @@ + + +# Teradata connection + +### *class* onetl.connection.db_connection.teradata.connection.Teradata(\*, spark: SparkSession, user: str, password: SecretStr, host: Host, port: int = 1025, database: str | None = None, extra: TeradataExtra = TeradataExtra(CHARSET='UTF8', COLUMN_NAME='ON', FLATTEN='ON', MAYBENULL='ON', STRICT_NAMES='OFF')) + +Teradata JDBC connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on package [com.teradata.jdbc:terajdbc:17.20.00.15](https://central.sonatype.com/artifact/com.teradata.jdbc/terajdbc/17.20.00.15) +([official Teradata JDBC driver](https://downloads.teradata.com/download/connectivity/jdbc-driver)). + +#### SEE ALSO +Before using this connector please take into account [Prerequisites](prerequisites.md#teradata-prerequisites) + +#### Versionadded +Added in version 0.1.0. + +* **Parameters:** + **host** + : Host of Teradata database. For example: `test.teradata.domain.com` or `193.168.1.12` + + **port** + : Port of Teradata database + + **user** + : User, which have proper access to the database. For example: `some_user` + + **password** + : Password for database connection + + **database** + : Database in RDBMS, NOT schema. +
+ See [this page](https://www.educba.com/postgresql-database-vs-schema/) for more details + + **spark** + : Spark session. + + **extra** + : Specifies one or more extra parameters which should be appended to a connection string. +
+ For example: `{"TMODE": "TERA", "MAYBENULL": "ON", "CHARSET": "UTF8", "LOGMECH":"LDAP"}` +
+ See [Teradata JDBC driver documentation](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/jdbcug_chapter_2.html#BABJIHBJ) + for more details +
+ #### NOTE + By default, these options are added to extra: + > * `CHARSET = "UTF8"` + > * `COLUMN_NAME = "ON"` - allow reading column title from a table + > * `FLATTEN = "ON"` - improves error messages + > * `MAYBENULL = "ON"` + > * `STRICT_NAMES = "OFF"` - ignore Spark options passed to JDBC URL +
+ It is possible to override default values, for example set `extra={"FLATTEN": "OFF"}` + +### Examples + +Create Teradata connection with LDAP auth: + +```python +from onetl.connection import Teradata +from pyspark.sql import SparkSession + +# Create Spark session with Teradata driver loaded +maven_packages = Teradata.get_packages() +spark = ( + SparkSession.builder.appName("spark-app-name") + .config("spark.jars.packages", ",".join(maven_packages)) + .getOrCreate() +) + +# Create connection +teradata = Teradata( + host="database.host.or.ip", + user="user", + password="*****", + extra={ + "TMODE": "TERA", # "TERA" or "ANSI" + "LOGMECH": "LDAP", + "LOG": "TIMING", # increase log level + }, + spark=spark, +).check() +``` + + + +#### check() + +Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If not, an exception will be raised. + +* **Returns:** + Connection itself +* **Raises:** + RuntimeError + : If the connection is not available + +### Examples + +```python +connection.check() +``` + + + +#### *classmethod* get_packages(package_version: str | None = None) → list[str] + +Get package names to be downloaded by Spark. Allows specifying custom JDBC driver versions for Teradata. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **package_version** + : Specifies the version of the Teradata JDBC driver to use. Defaults to `17.20.00.15`. +
+ #### Versionadded + Added in version 0.11.0. + +### Examples + +```python +from onetl.connection import Teradata + +Teradata.get_packages() + +# specify custom driver version +Teradata.get_packages(package_version="20.00.00.18") +``` + + diff --git a/mddocs/connection/db_connection/teradata/execute.md b/mddocs/connection/db_connection/teradata/execute.md new file mode 100644 index 000000000..e836b8a6d --- /dev/null +++ b/mddocs/connection/db_connection/teradata/execute.md @@ -0,0 +1,189 @@ + + +# Executing statements in Teradata + +#### WARNING +Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + +Do **NOT** use them to read large amounts of data. Use [DBReader](read.md#teradata-read) or [Teradata.sql](sql.md#teradata-sql) instead. + +## How to + +There are 2 ways to execute some statement in Teradata + +### Use `Teradata.fetch` + +Use this method to execute some `SELECT` query which returns **small number or rows**, like reading +Teradata config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts [`Teradata.FetchOptions`](#onetl.connection.db_connection.teradata.options.TeradataFetchOptions). + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Teradata, like: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ✅︎ `SHOW ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Teradata + +teradata = Teradata(...) + +df = teradata.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=Teradata.FetchOptions(queryTimeout=10), +) +teradata.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `Teradata.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts [`Teradata.ExecuteOptions`](#onetl.connection.db_connection.teradata.options.TeradataExecuteOptions). + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Teradata, like: + +* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +* ✅︎ `ALTER ...` +* ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +* ✅︎ `CALL procedure(arg1, arg2) ...` or `{call procedure(arg1, arg2)}` - special syntax for calling procedure +* ✅︎ `EXECUTE macro(arg1, arg2)` +* ✅︎ `EXECUTE FUNCTION ...` +* ✅︎ other statements not mentioned here +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Teradata + +teradata = Teradata(...) + +teradata.execute("DROP TABLE database.table") +teradata.execute( + """ + CREATE MULTISET TABLE database.table AS ( + id BIGINT, + key VARCHAR, + value REAL + ) + NO PRIMARY INDEX + """, + options=Teradata.ExecuteOptions(queryTimeout=10), +) +``` + +## Options + +### *pydantic model* onetl.connection.db_connection.teradata.options.TeradataFetchOptions + +Options related to fetching data from databases via JDBC. + +#### Versionadded +Added in version 0.11.0: Replace `Teradata.JDBCOptions` → `Teradata.FetchOptions` + +### Examples + +#### NOTE +You can pass any value supported by underlying JDBC driver class, +even if it is not mentioned in this documentation. + +```python +from onetl.connection import Teradata + +options = Teradata.FetchOptions( + queryTimeout=60_000, + fetchsize=100_000, + customSparkOption="value", +) +``` + + +* **Fields:** + - [`fetchsize (int | None)`](#onetl.connection.db_connection.teradata.options.TeradataFetchOptions.fetchsize) + - [`query_timeout (int | None)`](#onetl.connection.db_connection.teradata.options.TeradataFetchOptions.query_timeout) + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int | None* *= None* + +How many rows to fetch per round trip. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value depends on driver. For example, Oracle has +default `fetchsize=10`. + + + +### *pydantic model* onetl.connection.db_connection.teradata.options.TeradataExecuteOptions + +Options related to executing statements in databases via JDBC. + +#### Versionadded +Added in version 0.11.0: Replace `Teradata.JDBCOptions` → `Teradata.ExecuteOptions` + +### Examples + +#### NOTE +You can pass any value supported by underlying JDBC driver class, +even if it is not mentioned in this documentation. + +```python +from onetl.connection import Teradata + +options = Teradata.ExecuteOptions( + queryTimeout=60_000, + customSparkOption="value", +) +``` + + +* **Fields:** + - [`fetchsize (int | None)`](#onetl.connection.db_connection.teradata.options.TeradataExecuteOptions.fetchsize) + - [`query_timeout (int | None)`](#onetl.connection.db_connection.teradata.options.TeradataExecuteOptions.query_timeout) + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int | None* *= None* + +How many rows to fetch per round trip. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value depends on driver. For example, Oracle has +default `fetchsize=10`. + + diff --git a/mddocs/connection/db_connection/teradata/index.md b/mddocs/connection/db_connection/teradata/index.md new file mode 100644 index 000000000..f35e86981 --- /dev/null +++ b/mddocs/connection/db_connection/teradata/index.md @@ -0,0 +1,15 @@ + + +# Teradata + +# Connection + +* [Prerequisites](prerequisites.md) +* [Teradata connection](connection.md) + +# Operations + +* [Reading from Teradata using `DBReader`](read.md) +* [Reading from Teradata using `Teradata.sql`](sql.md) +* [Writing to Teradata using `DBWriter`](write.md) +* [Executing statements in Teradata](execute.md) diff --git a/mddocs/connection/db_connection/teradata/prerequisites.md b/mddocs/connection/db_connection/teradata/prerequisites.md new file mode 100644 index 000000000..3217390e2 --- /dev/null +++ b/mddocs/connection/db_connection/teradata/prerequisites.md @@ -0,0 +1,57 @@ + + +# Prerequisites + +## Version Compatibility + +* Teradata server versions: + : * Officially declared: 16.10 - 20.0 + * Actually tested: 16.10 +* Spark versions: 2.3.x - 3.5.x +* Java versions: 8 - 20 + +See [official documentation](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/platformMatrix.html). + +## Installing PySpark + +To use Teradata connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Connecting to Teradata + +### Connection host + +It is possible to connect to Teradata by using either DNS name Parsing Engine (PE) host, or it’s IP address. + +### Connection port + +Connection is usually performed to port `1025`. Port may differ for different Teradata instances. +Please ask your Teradata administrator to provide required information. + +### Required grants + +Ask your Teradata cluster administrator to set following grants for a user, +used for creating a connection: + +Read + Write + +```sql +-- allow creating tables in the target schema +GRANT CREATE TABLE ON database TO username; + +-- allow read & write access to specific table +GRANT SELECT, INSERT ON database.mytable TO username; +``` + +Read only + +```sql +-- allow read access to specific table +GRANT SELECT ON database.mytable TO username; +``` + +See: +: * [Teradata access rights](https://www.dwhpro.com/teradata-access-rights/) + * [GRANT documentation](https://teradata.github.io/presto/docs/0.167-t/sql/grant.html) diff --git a/mddocs/connection/db_connection/teradata/read.md b/mddocs/connection/db_connection/teradata/read.md new file mode 100644 index 000000000..49c91e222 --- /dev/null +++ b/mddocs/connection/db_connection/teradata/read.md @@ -0,0 +1,379 @@ + + +# Reading from Teradata using `DBReader` + +[`DBReader`](../../../db/db_reader.md#onetl.db.db_reader.db_reader.DBReader) supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, +but does not support custom queries, like `JOIN`. + +## Supported DBReader features + +* ✅︎ `columns` +* ✅︎ `where` +* ✅︎ `hwm`, supported strategies: +* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) +* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) +* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) +* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) +* ❌ `hint` (is not supported by Teradata) +* ❌ `df_schema` +* ✅︎ `options` (see [`Teradata.ReadOptions`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions)) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Teradata +from onetl.db import DBReader + +teradata = Teradata(...) + +reader = DBReader( + connection=teradata, + source="database.table", + columns=["id", "key", "CAST(value AS VARCHAR) value", "updated_dt"], + where="key = 'something'", + options=Teradata.ReadOptions( + partitioning_mode="hash", + partitionColumn="id", + numPartitions=10, + ), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Teradata +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +teradata = Teradata(...) + +reader = DBReader( + connection=teradata, + source="database.table", + columns=["id", "key", "CAST(value AS VARCHAR) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="teradata_hwm", expression="updated_dt"), + options=Teradata.ReadOptions( + partitioning_mode="hash", + partitionColumn="id", + numPartitions=10, + ), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Teradata to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Teradata to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +### Read data in parallel + +`DBReader` can read data in multiple parallel connections by passing `Teradata.ReadOptions(numPartitions=..., partitionColumn=...)`. + +In the example above, Spark opens 10 parallel connections, and data is evenly distributed between all these connections using expression +`HASHAMP(HASHBUCKET(HASHROW({partition_column}))) MOD {num_partitions}`. +This allows sending each Spark worker only some piece of data, reducing resource consumption. +`partition_column` here can be table column of any type. + +It is also possible to use `partitioning_mode="mod"` or `partitioning_mode="range"`, but in this case +`partition_column` have to be an integer, should not contain `NULL`, and values to be uniformly distributed. +It is also less performant than `partitioning_mode="hash"` due to Teradata `HASHAMP` implementation. + +### Do **NOT** use `TYPE=FASTEXPORT` + +Teradata supports several [different connection types](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/jdbcug_chapter_2.html#BABFGFAF): +: * `TYPE=DEFAULT` - perform plain `SELECT` queries + * `TYPE=FASTEXPORT` - uses special FastExport protocol for select queries + +But `TYPE=FASTEXPORT` uses exclusive lock on the source table, so it is impossible to use multiple Spark workers parallel data read. +This leads to sending all the data to just one Spark worker, which is slow and takes a lot of RAM. + +Prefer using `partitioning_mode="hash"` from example above. + +## Options + +### *pydantic model* onetl.connection.db_connection.teradata.options.TeradataReadOptions + +Spark JDBC reading options. + +#### Versionadded +Added in version 0.5.0: Replace `Teradata.Options` → `Teradata.ReadOptions` + +### Examples + +#### NOTE +You can pass any value +[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), +even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! + +The set of supported options depends on Spark version. + +```python +from onetl.connection import Teradata + +options = Teradata.ReadOptions( + partitioning_mode="range", + partitionColumn="reg_id", + numPartitions=10, + customSparkOption="value", +) +``` + + +* **Fields:** + - [`fetchsize (int)`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.fetchsize) + - [`lower_bound (int | None)`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.lower_bound) + - [`num_partitions (pydantic.types.PositiveInt)`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.num_partitions) + - [`partition_column (str | None)`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.partition_column) + - [`partitioning_mode (onetl.connection.db_connection.jdbc_connection.options.JDBCPartitioningMode)`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.partitioning_mode) + - [`query_timeout (int | None)`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.query_timeout) + - [`session_init_statement (str | None)`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.session_init_statement) + - [`upper_bound (int | None)`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.upper_bound) + +#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* + +Column used to parallelize reading from a table. + +#### WARNING +It is highly recommended to use primary key, or column with an index +to avoid performance issues. + +#### NOTE +Column type depends on [`partitioning_mode`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.partitioning_mode). + +* `partitioning_mode="range"` requires column to be an integer, date or timestamp (can be NULL, but not recommended). +* `partitioning_mode="hash"` accepts any column type (NOT NULL). +* `partitioning_mode="mod"` requires column to be an integer (NOT NULL). + +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.partitioning_mode) for more details + + + +#### *field* num_partitions *: PositiveInt* *= 1* *(alias 'numPartitions')* + +Number of jobs created by Spark to read the table content in parallel. +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.partitioning_mode) for more details + + +* **Constraints:** + - **exclusiveMinimum** = 0 + +#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* + +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.partitioning_mode) for more details + + + +#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* + +See documentation for [`partitioning_mode`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.partitioning_mode) for more details + + + +#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* + +After each database session is opened to the remote DB and before starting to read data, +this option executes a custom SQL statement (or a PL/SQL block). + +Use this to implement session initialization code. + +Example: + +```python +sessionInitStatement = """ + BEGIN + execute immediate + 'alter session set "_serial_direct_read"=true'; + END; +""" +``` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int* *= 100000* + +Fetch N rows from an opened cursor per one read round. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value is different from Spark. + +Spark uses driver’s own value, and it may be different in different drivers, +and even versions of the same driver. For example, Oracle has +default `fetchsize=10`, which is absolutely not usable. + +Thus we’ve overridden default value with `100_000`, which should increase reading performance. + +#### Versionchanged +Changed in version 0.2.0: Set explicit default value to `100_000` + + + +#### *field* partitioning_mode *: JDBCPartitioningMode* *= JDBCPartitioningMode.RANGE* + +Defines how Spark will parallelize reading from table. + +Possible values: + +* `range` (default) + : Allocate each executor a range of values from column passed into [`partition_column`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.partition_column). +
+ ### Spark generates for each executor an SQL query +
+ Executor 1: + ```sql + SELECT ... FROM table + WHERE (partition_column >= lowerBound + OR partition_column IS NULL) + AND partition_column < (lower_bound + stride) + ``` +
+ Executor 2: + ```sql + SELECT ... FROM table + WHERE partition_column >= (lower_bound + stride) + AND partition_column < (lower_bound + 2 * stride) + ``` +
+ … +
+ Executor N: + ```sql + SELECT ... FROM table + WHERE partition_column >= (lower_bound + (N-1) * stride) + AND partition_column <= upper_bound + ``` +
+ Where `stride=(upper_bound - lower_bound) / num_partitions`. +
+ #### NOTE + Can be used only with columns of integer, date or timestamp types. +
+ #### NOTE + [`lower_bound`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.lower_bound), [`upper_bound`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.upper_bound) and [`num_partitions`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.num_partitions) are used just to + calculate the partition stride, **NOT** for filtering the rows in table. + So all rows in the table will be returned (unlike *Incremental* [Read Strategies](../../../strategy/index.md#strategy)). +
+ #### NOTE + All queries are executed in parallel. To execute them sequentially, use *Batch* [Read Strategies](../../../strategy/index.md#strategy). +* `hash` + : Allocate each executor a set of values based on hash of the [`partition_column`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.partition_column) column. +
+ ### Spark generates for each executor an SQL query +
+ Executor 1: + ```sql + SELECT ... FROM table + WHERE (some_hash(partition_column) mod num_partitions) = 0 -- lower_bound + ``` +
+ Executor 2: + ```sql + SELECT ... FROM table + WHERE (some_hash(partition_column) mod num_partitions) = 1 -- lower_bound + 1 + ``` +
+ … +
+ Executor N: + ```sql + SELECT ... FROM table + WHERE (some_hash(partition_column) mod num_partitions) = num_partitions-1 -- upper_bound + ``` +
+ #### NOTE + The hash function implementation depends on RDBMS. It can be `MD5` or any other fast hash function, + or expression based on this function call. Usually such functions accepts any column type as an input. +* `mod` + : Allocate each executor a set of values based on modulus of the [`partition_column`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.partition_column) column. +
+ ### Spark generates for each executor an SQL query +
+ Executor 1: + ```sql + SELECT ... FROM table + WHERE (partition_column mod num_partitions) = 0 -- lower_bound + ``` +
+ Executor 2: + ```sql + SELECT ... FROM table + WHERE (partition_column mod num_partitions) = 1 -- lower_bound + 1 + ``` +
+ Executor N: + ```sql + SELECT ... FROM table + WHERE (partition_column mod num_partitions) = num_partitions-1 -- upper_bound + ``` +
+ #### NOTE + Can be used only with columns of integer type. + +#### Versionadded +Added in version 0.5.0. + +### Examples + +Read data in 10 parallel jobs by range of values in `id_column` column: + +```python +ReadOptions( + partitioning_mode="range", # default mode, can be omitted + partitionColumn="id_column", + numPartitions=10, + # Options below can be discarded because they are + # calculated automatically as MIN and MAX values of `partitionColumn` + lowerBound=0, + upperBound=100_000, +) +``` + +Read data in 10 parallel jobs by hash of values in `some_column` column: + +```python +ReadOptions( + partitioning_mode="hash", + partitionColumn="some_column", + numPartitions=10, + # lowerBound and upperBound are automatically set to `0` and `9` +) +``` + +Read data in 10 parallel jobs by modulus of values in `id_column` column: + +```python +ReadOptions( + partitioning_mode="mod", + partitionColumn="id_column", + numPartitions=10, + # lowerBound and upperBound are automatically set to `0` and `9` +) +``` + + diff --git a/mddocs/connection/db_connection/teradata/sql.md b/mddocs/connection/db_connection/teradata/sql.md new file mode 100644 index 000000000..651dd6e5f --- /dev/null +++ b/mddocs/connection/db_connection/teradata/sql.md @@ -0,0 +1,190 @@ + + +# Reading from Teradata using `Teradata.sql` + +`Teradata.sql` allows passing custom SQL query, but does not support incremental strategies. + +#### WARNING +Statement is executed in **read-write** connection, so if you’re calling some functions/procedures with DDL/DML statements inside, +they can change data in your database. + +## Syntax support + +Only queries with the following syntax are supported: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ❌ `SHOW ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import Teradata + +teradata = Teradata(...) +df = teradata.sql( + """ + SELECT + id, + key, + CAST(value AS VARCHAR) AS value, + updated_at, + HASHAMP(HASHBUCKET(HASHROW(id))) MOD 10 AS part_column + FROM + database.mytable + WHERE + key = 'something' + """, + options=Teradata.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from Teradata to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from Teradata to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +### *pydantic model* onetl.connection.db_connection.teradata.options.TeradataSQLOptions + +Options specifically for SQL queries + +These options allow you to specify configurations for executing SQL queries +without relying on Spark’s partitioning mechanisms. + +#### Versionadded +Added in version 0.11.0: Split up `Teradata.ReadOptions` to `Teradata.SQLOptions` + +### Examples + +#### NOTE +You can pass any JDBC configuration +[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), +tailored to optimize SQL query execution. **Option names should be in** `camelCase`! + +```python +from onetl.connection import Teradata + +options = Teradata.SQLOptions( + partitionColumn="reg_id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + customSparkOption="value", +) +``` + + + +#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* + +Column used to partition data across multiple executors for parallel query processing. + +#### WARNING +It is highly recommended to use primary key, or column with an index +to avoid performance issues. + +### Example of using `partitionColumn="id"` with `partitioning_mode="range"` + +```sql +-- If partition_column is 'id', with numPartitions=4, lowerBound=1, and upperBound=100: +-- Executor 1 processes IDs from 1 to 25 +SELECT ... FROM table WHERE id >= 1 AND id < 26 +-- Executor 2 processes IDs from 26 to 50 +SELECT ... FROM table WHERE id >= 26 AND id < 51 +-- Executor 3 processes IDs from 51 to 75 +SELECT ... FROM table WHERE id >= 51 AND id < 76 +-- Executor 4 processes IDs from 76 to 100 +SELECT ... FROM table WHERE id >= 76 AND id <= 100 + +-- General case for Executor N +SELECT ... FROM table +WHERE partition_column >= (lowerBound + (N-1) * stride) +AND partition_column <= upperBound +-- Where ``stride`` is calculated as ``(upperBound - lowerBound) / numPartitions``. +``` + + + +#### *field* num_partitions *: int | None* *= None* *(alias 'numPartitions')* + +Number of jobs created by Spark to read the table content in parallel. + + + +#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* + +Defines the starting boundary for partitioning the query’s data. Mandatory if [`partition_column`](#onetl.connection.db_connection.teradata.options.TeradataSQLOptions.partition_column) is set + + + +#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* + +Sets the ending boundary for data partitioning. Mandatory if [`partition_column`](#onetl.connection.db_connection.teradata.options.TeradataSQLOptions.partition_column) is set + + + +#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* + +After each database session is opened to the remote DB and before starting to read data, +this option executes a custom SQL statement (or a PL/SQL block). + +Use this to implement session initialization code. + +Example: + +```python +sessionInitStatement = """ + BEGIN + execute immediate + 'alter session set "_serial_direct_read"=true'; + END; +""" +``` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* fetchsize *: int* *= 100000* + +Fetch N rows from an opened cursor per one read round. + +Tuning this option can influence performance of reading. + +#### WARNING +Default value is different from Spark. + +Spark uses driver’s own value, and it may be different in different drivers, +and even versions of the same driver. For example, Oracle has +default `fetchsize=10`, which is absolutely not usable. + +Thus we’ve overridden default value with `100_000`, which should increase reading performance. + +#### Versionchanged +Changed in version 0.2.0: Set explicit default value to `100_000` + + diff --git a/mddocs/connection/db_connection/teradata/write.md b/mddocs/connection/db_connection/teradata/write.md new file mode 100644 index 000000000..ba160f90c --- /dev/null +++ b/mddocs/connection/db_connection/teradata/write.md @@ -0,0 +1,250 @@ + + +# Writing to Teradata using `DBWriter` + +For writing data to Teradata, use [`DBWriter`](../../../db/db_writer.md#onetl.db.db_writer.db_writer.DBWriter). + +#### WARNING +It is always recommended to create table explicitly using [Teradata.execute](execute.md#teradata-execute) +instead of relying on Spark’s table DDL generation. + +This is because Spark’s DDL generator can create columns with different precision and types than it is expected, +causing precision loss or other issues. + +## Examples + +```python +from onetl.connection import Teradata +from onetl.db import DBWriter + +teradata = Teradata( + ..., + extra={"TYPE": "FASTLOAD", "TMODE": "TERA"}, +) + +df = ... # data is here + +writer = DBWriter( + connection=teradata, + target="database.table", + options=Teradata.WriteOptions( + if_exists="append", + # avoid creating SET table, use MULTISET + createTableOptions="NO PRIMARY INDEX", + ), +) + +writer.run(df.repartition(1)) +``` + +## Recommendations + +### Number of connections + +Teradata is not MVCC based, so write operations take exclusive lock on the entire table. +So **it is impossible to write data to Teradata table in multiple parallel connections**, no exceptions. + +The only way to write to Teradata without making deadlocks is write dataframe with exactly 1 partition. + +It can be implemented using `df.repartition(1)`: + +```python +# do NOT use df.coalesce(1) as it can freeze +writer.run(df.repartition(1)) +``` + +This moves all the data to just one Spark worker, so it may consume a lot of RAM. It is usually require to increase `spark.executor.memory` to handle this. + +Another way is to write all dataframe partitions one-by-one: + +```python +from pyspark.sql.functions import spark_partition_id + +# get list of all partitions in the dataframe +partitions = sorted(df.select(spark_partition_id()).distinct().collect()) + +for partition in partitions: + # get only part of data within this exact partition + part_df = df.where(**partition.asDict()).coalesce(1) + + writer.run(part_df) +``` + +This require even data distribution for all partitions to avoid data skew and spikes of RAM consuming. + +### Choosing connection type + +Teradata supports several [different connection types](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/jdbcug_chapter_2.html#BABFGFAF): +: * `TYPE=DEFAULT` - perform plain `INSERT` queries + * `TYPE=FASTLOAD` - uses special FastLoad protocol for insert queries + +It is always recommended to use `TYPE=FASTLOAD` because: +: * It provides higher performance + * It properly handles inserting `NULL` values (`TYPE=DEFAULT` raises an exception) + +But it can be used only during write, not read. + +### Choosing transaction mode + +Teradata supports [2 different transaction modes](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/jdbcug_chapter_2.html#TMODESEC): +: * `TMODE=ANSI` + * `TMODE=TERA` + +Choosing one of the modes can alter connector behavior. For example: +: * Inserting data which exceeds table column length, like insert `CHAR(25)` to column with type `CHAR(24)`: + * * `TMODE=ANSI` - raises exception + * * `TMODE=TERA` - truncates input string to 24 symbols + * Creating table using Spark: + * * `TMODE=ANSI` - creates `MULTISET` table + * * `TMODE=TERA` - creates `SET` table with `PRIMARY KEY` is a first column in dataframe. + This can lead to slower insert time, because each row will be checked against a unique index. + Fortunately, this can be disabled by passing custom `createTableOptions`. + +## Options + +Method above accepts [`Teradata.WriteOptions`](#onetl.connection.db_connection.teradata.options.TeradataWriteOptions) + +### *pydantic model* onetl.connection.db_connection.teradata.options.TeradataWriteOptions + +Spark JDBC writing options. + +#### Versionadded +Added in version 0.5.0: Replace `Teradata.Options` → `Teradata.WriteOptions` + +### Examples + +#### NOTE +You can pass any value +[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), +even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! + +The set of supported options depends on Spark version. + +```python +from onetl.connection import Teradata + +options = Teradata.WriteOptions( + if_exists="append", + batchsize=20_000, + customSparkOption="value", +) +``` + + + +#### *field* if_exists *: JDBCTableExistBehavior* *= JDBCTableExistBehavior.APPEND* *(alias 'mode')* + +Behavior of writing data into existing table. + +Possible values: +: * `append` (default) + : Adds new rows into existing table. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : Data is appended to a table. Table has the same DDL as before writing data +
+ #### WARNING + This mode does not check whether table already contains + rows from dataframe, so duplicated rows can be created. +
+ Also Spark does not support passing custom options to + insert statement, like `ON CONFLICT`, so don’t try to + implement deduplication using unique indexes or constraints. +
+ Instead, write to staging table and perform deduplication + using `execute` method. + * `replace_entire_table` + : **Table is dropped and then created, or truncated**. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : Table content is replaced with dataframe content. +
+ After writing completed, target table could either have the same DDL as + before writing data (`truncate=True`), or can be recreated (`truncate=False` + or source does not support truncation). + * `ignore` + : Ignores the write operation if the table already exists. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : The write operation is ignored, and no data is written to the table. + * `error` + : Raises an error if the table already exists. +
+ ### Behavior in details +
+ * Table does not exist + : Table is created using options provided by user + (`createTableOptions`, `createTableColumnTypes`, etc). + * Table exists + : An error is raised, and no data is written to the table. + +#### Versionchanged +Changed in version 0.9.0: Renamed `mode` → `if_exists` + + + +#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* + +The number of seconds the driver will wait for a statement to execute. +Zero means there is no limit. + +This option depends on driver implementation, +some drivers can check the timeout of each query instead of an entire JDBC batch. + + + +#### *field* batchsize *: int* *= 20000* + +How many rows can be inserted per round trip. + +Tuning this option can influence performance of writing. + +#### WARNING +Default value is different from Spark. + +Spark uses quite small value `1000`, which is absolutely not usable +in BigData world. + +Thus we’ve overridden default value with `20_000`, +which should increase writing performance. + +You can increase it even more, up to `50_000`, +but it depends on your database load and number of columns in the row. +Higher values does not increase performance. + +#### Versionchanged +Changed in version 0.4.0: Changed default value from 1000 to 20_000 + + + +#### *field* isolation_level *: str* *= 'READ_UNCOMMITTED'* *(alias 'isolationLevel')* + +The transaction isolation level, which applies to current connection. + +Possible values: +: * `NONE` (as string, not Python’s `None`) + * `READ_COMMITTED` + * `READ_UNCOMMITTED` + * `REPEATABLE_READ` + * `SERIALIZABLE` + +Values correspond to transaction isolation levels defined by JDBC standard. +Please refer the documentation for +[java.sql.Connection](https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html). + + diff --git a/mddocs/connection/file_connection/ftp.md b/mddocs/connection/file_connection/ftp.md new file mode 100644 index 000000000..6ecce3655 --- /dev/null +++ b/mddocs/connection/file_connection/ftp.md @@ -0,0 +1,627 @@ + + +# FTP connection + +### *class* onetl.connection.file_connection.ftp.FTP(\*, host: Host, port: int = 21, user: str | None = None, password: SecretStr | None = None) + +FTP file connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on [FTPUtil library](https://pypi.org/project/ftputil/). + +#### WARNING +Since onETL v0.7.0 to use FTP connector you should install package as follows: + +```bash +pip install onetl[ftp] + +# or +pip install onetl[files] +``` + +See [File connections](../../install/files.md#install-files) installation instruction for more details. + +#### Versionadded +Added in version 0.1.0. + +* **Parameters:** + **host** + : Host of FTP source. For example: `ftp.domain.com` + + **port** + : Port of FTP source + + **user** + : User, which have access to the file source. For example: `someuser`. +
+ `None` means that the user is anonymous. + + **password** + : Password for file source connection. +
+ `None` means that the user is anonymous. + +### Examples + +Create and check FTP connection: + +```python +from onetl.connection import FTP + +ftp = FTP( + host="ftp.domain.com", + user="someuser", + password="*****", +).check() +``` + + + +#### \_\_init_\_(\*\*kwargs) + + + +#### check() + +Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If not, an exception will be raised. + +* **Returns:** + Connection itself +* **Raises:** + RuntimeError + : If the connection is not available + +### Examples + +```python +connection.check() +``` + + + +#### create_dir(path: PathLike | str) → RemoteDirectory + +Creates directory tree on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Directory path +* **Returns:** + Created directory with stats +* **Raises:** + `onetl.exception.NotAFileError` + : Path is not a file + +### Examples + +```pycon +>>> dir_path = connection.create_dir("/path/to/dir") +>>> os.fspath(dir_path) +'/path/to/dir' +``` + + + +#### download_file(remote_file_path: PathLike | str, local_file_path: PathLike | str, replace: bool = True) → LocalPath + +Downloads file from the remote filesystem to a local path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +Supports only one file download per call. Directory download is **NOT** supported, use [File Downloader](../../file/file_downloader/file_downloader.md#file-downloader) instead. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **remote_file_path** + : Remote file path to read from + + **local_file_path** + : Local file path to create + + **replace** + : If `True`, existing file will be replaced +* **Returns:** + Local file with stats. +* **Raises:** + `onetl.exception.NotAFileError` + : Remote or local path is not a file + + FileNotFoundError + : Remote file does not exist + + FileExistsError + : Local file already exists, and `replace=False` + + `onetl.exception.FileSizeMismatchError` + : Target file size after download is different from source file size. + +### Examples + +```pycon +>>> local_file = connection.download_file( +... remote_file_path="/path/to/source.csv", +... local_file_path="/path/to/target.csv", +... ) +>>> os.fspath(local_file) +'/path/to/target.csv' +>>> local_file.exists() +True +>>> local_file.stat().st_size # in bytes +1024 +>>> connection.get_stat("/path/to/source.csv").st_size # same size +1024 +``` + + + +#### get_stat(path: PathLike | str) → PathStatProtocol + +Returns stats for a specific path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to get stats for +* **Returns:** + Stats object +* **Raises:** + Any underlying client exception + +### Examples + +```pycon +>>> stat = connection.get_stat("/path/to/file.csv") +>>> stat.st_size # in bytes +1024 +>>> stat.st_uid # owner id or name +12345 +``` + + + +#### is_dir(path: PathLike | str) → bool + +Check if specified path is a directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if path is a directory, `False` otherwise. +* **Raises:** + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + +### Examples + +```pycon +>>> connection.is_dir("/path/to/dir") +True +>>> connection.is_dir("/path/to/dir/file.csv") +False +``` + + + +#### is_file(path: PathLike | str) → bool + +Check if specified path is a file. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if path is a file, `False` otherwise. +* **Raises:** + FileNotFoundError + : Path does not exist + +### Examples + +```pycon +>>> connection.is_file("/path/to/dir/file.csv") +True +>>> connection.is_file("/path/to/dir") +False +``` + + + +#### list_dir(path: PathLike | str, filters: Iterable[[BaseFileFilter](../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → list[RemoteDirectory | RemoteFile] + +Return list of child files/directories in a specific directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Directory path to list contents. + + **filters** + : Return only files/directories matching these filters. See [File Filters](../../file/file_filters/index.md#file-filters) + + **limits** + : Apply limits to the list of files/directories, and stop if one of the limits is reached. + See [File Limits](../../file/file_limits/index.md#file-limits) +* **Returns:** + List of `onetl.base.PathWithStatsProtocol` +* **Raises:** + NotADirectoryError + : Path is not a directory + + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + +### Examples + +```pycon +>>> dir_content = connection.list_dir("/path/to/dir") +>>> os.fspath(dir_content[0]) +'file.csv' +>>> connection.path_exists("/path/to/dir/file.csv") +True +``` + + + +#### path_exists(path: PathLike | str) → bool + +Check if specified path exists on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html). + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if path exists, `False` otherwise + +### Examples + +```pycon +>>> connection.path_exists("/path/to/file.csv") +True +>>> connection.path_exists("/path/to/dir") +True +>>> connection.path_exists("/path/to/missing") +False +``` + + + +#### remove_dir(path: PathLike | str, recursive: bool = False) → bool + +Remove directory or directory tree. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If directory does not exist, no exception is raised. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Directory path to remote + + **recursive** + : If `True`, remove directory tree recursively. +* **Returns:** + `True` if directory was removed, `False` if directory does not exist in the first place. +* **Raises:** + NotADirectoryError + : Path is not a directory + +### Examples + +```pycon +>>> connection.remove_dir("/path/to/dir") +True +>>> connection.path_exists("/path/to/dir") +False +>>> connection.path_exists("/path/to/dir/file.csv") +False +>>> connection.remove_dir("/path/to/dir") # already deleted, no error +False +``` + + + +#### remove_file(path: PathLike | str) → bool + +Removes file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If file does not exist, no exception is raised. + +#### WARNING +Supports only one file removal per call. Directory removal is **NOT** supported, use [`remove_dir`](#onetl.connection.file_connection.ftp.FTP.remove_dir) instead. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : File path +* **Returns:** + `True` if file was removed, `False` if file does not exist in the first place. +* **Raises:** + `onetl.exception.NotAFileError` + : Path is not a file + +### Examples + +```pycon +>>> connection.remove_file("/path/to/file.csv") +True +>>> connection.path_exists("/path/to/dir/file.csv") +False +>>> connection.remove_file("/path/to/file.csv") # already deleted, no error +False +``` + + + +#### rename_dir(source_dir_path: PathLike | str, target_dir_path: PathLike | str, replace: bool = False) → RemoteDirectory + +Rename or move dir on remote filesystem. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **source_dir_path** + : Old directory path + + **target_dir_path** + : New directory path + + **replace** + : If `True`, existing directory will be replaced. +* **Returns:** + New directory path with stats. +* **Raises:** + NotADirectoryError + : Path is not a directory + + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + + `onetl.exception.DirectoryExistsError` + : Directory already exists, and `replace=False` + +### Examples + +```pycon +>>> new_dir = connection.rename_dir("/path/to/dir1", "/path/to/dir2") +>>> os.fspath(new_dir) +'/path/to/dir2' +>>> connection.path_exists("/path/to/dir1") +False +>>> connection.path_exists("/path/to/dir2") +True +``` + + + +#### rename_file(source_file_path: PathLike | str, target_file_path: PathLike | str, replace: bool = False) → RemoteFile + +Rename or move file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +Supports only one file move per call. Directory move/rename is **NOT** supported. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **source_file_path** + : Old file path + + **target_file_path** + : New file path + + **replace** + : If `True`, existing file will be replaced. +* **Returns:** + New file path with stats. +* **Raises:** + `onetl.exception.NotAFileError` + : Source or target path is not a file + + FileNotFoundError + : File does not exist + + FileExistsError + : File already exists, and `replace=False` + +### Examples + +```pycon +>>> new_file = connection.rename_file("/path/to/file1.csv", "/path/to/file2.csv") +>>> os.fspath(new_file) +'/path/to/file2.csv' +>>> connection.path_exists("/path/to/file2.csv") +True +>>> connection.path_exists("/path/to/file1.csv") +False +``` + + + +#### resolve_dir(path: PathLike | str) → RemoteDirectory + +Returns directory at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to resolve +* **Returns:** + Directory path with stats +* **Raises:** + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + + NotADirectoryError + : Path is not a directory + +### Examples + +```pycon +>>> dir_path = connection.resolve_dir("/path/to/dir") +>>> os.fspath(dir_path) +'/path/to/dir' +>>> dir_path.stat().st_uid # owner id +12345 +``` + + + +#### resolve_file(path: PathLike | str) → RemoteFile + +Returns file at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to resolve +* **Returns:** + File path with stats +* **Raises:** + FileNotFoundError + : Path does not exist + + `onetl.exception.NotAFileError` + : Path is not a file + +### Examples + +```pycon +>>> file_path = connection.resolve_file("/path/to/dir/file.csv") +>>> os.fspath(file_path) +'/path/to/dir/file.csv' +>>> file_path.stat().st_uid # owner id +12345 +``` + + + +#### upload_file(local_file_path: PathLike | str, remote_file_path: PathLike | str, replace: bool = False) → RemoteFile + +Uploads local file to a remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +Supports only one file upload per call. Directory upload is **NOT** supported, use [File Uploader](../../file/file_uploader/file_uploader.md#file-uploader) instead. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **local_file_path** + : Local file path to read from + + **remote_file_path** + : Remote file path to create + + **replace** + : If `True`, existing file will be replaced +* **Returns:** + Remote file with stats. +* **Raises:** + `onetl.exception.NotAFileError` + : Remote or local path is not a file + + FileNotFoundError + : Local file does not exist + + FileExistsError + : Remote file already exists, and `replace=False` + + `onetl.exception.FileSizeMismatchError` + : Target file size after upload is different from source file size. + +### Examples + +```pycon +>>> remote_file = connection.upload( +... local_file_path="/path/to/source.csv", +... remote_file_path="/path/to/target.csv", +... ) +>>> os.fspath(remote_file) +'/path/to/target.csv' +>>> connection.path_exists("/path/to/target.csv") +True +>>> remote_file.stat().st_size # in bytes +1024 +>>> os.stat("/path/to/source.csv").st_size # same as source +1024 +``` + + + +#### walk(root: PathLike | str, topdown: bool = True, filters: Iterable[[BaseFileFilter](../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → Iterator[tuple[RemoteDirectory, list[RemoteDirectory], list[RemoteFile]]] + +Walk into directory tree, and iterate over its content in all nesting levels. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Just like `os.walk`, but with additional filter/limit logic. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **root** + : Directory path to walk into. + + **topdown** + : If `True`, walk in top-down order, otherwise walk in bottom-up order. + + **filters** + : Return only files/directories matching these filters. See [File Filters](../../file/file_filters/index.md#file-filters). + + **limits** + : Apply limits to the list of files/directories, and immediately stop if any of these limits is reached. + See [File Limits](../../file/file_limits/index.md#file-limits). +* **Returns:** + `Iterator[tuple[root, dirs, files]]`, like `os.walk`. + + But all the paths are not strings, instead path classes with embedded stats are returned. +* **Raises:** + NotADirectoryError + : Path is not a directory + + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + +### Examples + +```pycon +>>> for root, dirs, files in connection.walk("/path/to/dir"): +... break +>>> os.fspath(root) +'/path/to/dir' +>>> dirs +[] +>>> os.fspath(files[0]) +'file.csv' +>>> connection.path_exists("/path/to/dir/file.csv") +True +``` + + diff --git a/mddocs/connection/file_connection/ftps.md b/mddocs/connection/file_connection/ftps.md new file mode 100644 index 000000000..e115be98b --- /dev/null +++ b/mddocs/connection/file_connection/ftps.md @@ -0,0 +1,627 @@ + + +# FTPS connection + +### *class* onetl.connection.file_connection.ftps.FTPS(\*, host: Host, port: int = 21, user: str | None = None, password: SecretStr | None = None) + +FTPS file connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on [FTPUtil library](https://pypi.org/project/ftputil/). + +#### WARNING +Since onETL v0.7.0 to use FTPS connector you should install package as follows: + +```bash +pip install onetl[ftps] + +# or +pip install onetl[files] +``` + +See [File connections](../../install/files.md#install-files) installation instruction for more details. + +#### Versionadded +Added in version 0.1.0. + +* **Parameters:** + **host** + : Host of FTPS source. For example: `ftps.domain.com` + + **port** + : Port of FTPS source + + **user** + : User, which have access to the file source. For example: `someuser`. +
+ `None` means that the user is anonymous. + + **password** + : Password for file source connection. +
+ `None` means that the user is anonymous. + +### Examples + +Create and check FTPS connection: + +```python +from onetl.connection import FTPS + +ftps = FTPS( + host="ftps.domain.com", + user="someuser", + password="*****", +).check() +``` + + + +#### \_\_init_\_(\*\*kwargs) + + + +#### check() + +Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If not, an exception will be raised. + +* **Returns:** + Connection itself +* **Raises:** + RuntimeError + : If the connection is not available + +### Examples + +```python +connection.check() +``` + + + +#### create_dir(path: PathLike | str) → RemoteDirectory + +Creates directory tree on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Directory path +* **Returns:** + Created directory with stats +* **Raises:** + `onetl.exception.NotAFileError` + : Path is not a file + +### Examples + +```pycon +>>> dir_path = connection.create_dir("/path/to/dir") +>>> os.fspath(dir_path) +'/path/to/dir' +``` + + + +#### download_file(remote_file_path: PathLike | str, local_file_path: PathLike | str, replace: bool = True) → LocalPath + +Downloads file from the remote filesystem to a local path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +Supports only one file download per call. Directory download is **NOT** supported, use [File Downloader](../../file/file_downloader/file_downloader.md#file-downloader) instead. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **remote_file_path** + : Remote file path to read from + + **local_file_path** + : Local file path to create + + **replace** + : If `True`, existing file will be replaced +* **Returns:** + Local file with stats. +* **Raises:** + `onetl.exception.NotAFileError` + : Remote or local path is not a file + + FileNotFoundError + : Remote file does not exist + + FileExistsError + : Local file already exists, and `replace=False` + + `onetl.exception.FileSizeMismatchError` + : Target file size after download is different from source file size. + +### Examples + +```pycon +>>> local_file = connection.download_file( +... remote_file_path="/path/to/source.csv", +... local_file_path="/path/to/target.csv", +... ) +>>> os.fspath(local_file) +'/path/to/target.csv' +>>> local_file.exists() +True +>>> local_file.stat().st_size # in bytes +1024 +>>> connection.get_stat("/path/to/source.csv").st_size # same size +1024 +``` + + + +#### get_stat(path: PathLike | str) → PathStatProtocol + +Returns stats for a specific path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to get stats for +* **Returns:** + Stats object +* **Raises:** + Any underlying client exception + +### Examples + +```pycon +>>> stat = connection.get_stat("/path/to/file.csv") +>>> stat.st_size # in bytes +1024 +>>> stat.st_uid # owner id or name +12345 +``` + + + +#### is_dir(path: PathLike | str) → bool + +Check if specified path is a directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if path is a directory, `False` otherwise. +* **Raises:** + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + +### Examples + +```pycon +>>> connection.is_dir("/path/to/dir") +True +>>> connection.is_dir("/path/to/dir/file.csv") +False +``` + + + +#### is_file(path: PathLike | str) → bool + +Check if specified path is a file. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if path is a file, `False` otherwise. +* **Raises:** + FileNotFoundError + : Path does not exist + +### Examples + +```pycon +>>> connection.is_file("/path/to/dir/file.csv") +True +>>> connection.is_file("/path/to/dir") +False +``` + + + +#### list_dir(path: PathLike | str, filters: Iterable[[BaseFileFilter](../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → list[RemoteDirectory | RemoteFile] + +Return list of child files/directories in a specific directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Directory path to list contents. + + **filters** + : Return only files/directories matching these filters. See [File Filters](../../file/file_filters/index.md#file-filters) + + **limits** + : Apply limits to the list of files/directories, and stop if one of the limits is reached. + See [File Limits](../../file/file_limits/index.md#file-limits) +* **Returns:** + List of `onetl.base.PathWithStatsProtocol` +* **Raises:** + NotADirectoryError + : Path is not a directory + + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + +### Examples + +```pycon +>>> dir_content = connection.list_dir("/path/to/dir") +>>> os.fspath(dir_content[0]) +'file.csv' +>>> connection.path_exists("/path/to/dir/file.csv") +True +``` + + + +#### path_exists(path: PathLike | str) → bool + +Check if specified path exists on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html). + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if path exists, `False` otherwise + +### Examples + +```pycon +>>> connection.path_exists("/path/to/file.csv") +True +>>> connection.path_exists("/path/to/dir") +True +>>> connection.path_exists("/path/to/missing") +False +``` + + + +#### remove_dir(path: PathLike | str, recursive: bool = False) → bool + +Remove directory or directory tree. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If directory does not exist, no exception is raised. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Directory path to remote + + **recursive** + : If `True`, remove directory tree recursively. +* **Returns:** + `True` if directory was removed, `False` if directory does not exist in the first place. +* **Raises:** + NotADirectoryError + : Path is not a directory + +### Examples + +```pycon +>>> connection.remove_dir("/path/to/dir") +True +>>> connection.path_exists("/path/to/dir") +False +>>> connection.path_exists("/path/to/dir/file.csv") +False +>>> connection.remove_dir("/path/to/dir") # already deleted, no error +False +``` + + + +#### remove_file(path: PathLike | str) → bool + +Removes file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If file does not exist, no exception is raised. + +#### WARNING +Supports only one file removal per call. Directory removal is **NOT** supported, use [`remove_dir`](#onetl.connection.file_connection.ftps.FTPS.remove_dir) instead. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : File path +* **Returns:** + `True` if file was removed, `False` if file does not exist in the first place. +* **Raises:** + `onetl.exception.NotAFileError` + : Path is not a file + +### Examples + +```pycon +>>> connection.remove_file("/path/to/file.csv") +True +>>> connection.path_exists("/path/to/dir/file.csv") +False +>>> connection.remove_file("/path/to/file.csv") # already deleted, no error +False +``` + + + +#### rename_dir(source_dir_path: PathLike | str, target_dir_path: PathLike | str, replace: bool = False) → RemoteDirectory + +Rename or move dir on remote filesystem. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **source_dir_path** + : Old directory path + + **target_dir_path** + : New directory path + + **replace** + : If `True`, existing directory will be replaced. +* **Returns:** + New directory path with stats. +* **Raises:** + NotADirectoryError + : Path is not a directory + + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + + `onetl.exception.DirectoryExistsError` + : Directory already exists, and `replace=False` + +### Examples + +```pycon +>>> new_dir = connection.rename_dir("/path/to/dir1", "/path/to/dir2") +>>> os.fspath(new_dir) +'/path/to/dir2' +>>> connection.path_exists("/path/to/dir1") +False +>>> connection.path_exists("/path/to/dir2") +True +``` + + + +#### rename_file(source_file_path: PathLike | str, target_file_path: PathLike | str, replace: bool = False) → RemoteFile + +Rename or move file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +Supports only one file move per call. Directory move/rename is **NOT** supported. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **source_file_path** + : Old file path + + **target_file_path** + : New file path + + **replace** + : If `True`, existing file will be replaced. +* **Returns:** + New file path with stats. +* **Raises:** + `onetl.exception.NotAFileError` + : Source or target path is not a file + + FileNotFoundError + : File does not exist + + FileExistsError + : File already exists, and `replace=False` + +### Examples + +```pycon +>>> new_file = connection.rename_file("/path/to/file1.csv", "/path/to/file2.csv") +>>> os.fspath(new_file) +'/path/to/file2.csv' +>>> connection.path_exists("/path/to/file2.csv") +True +>>> connection.path_exists("/path/to/file1.csv") +False +``` + + + +#### resolve_dir(path: PathLike | str) → RemoteDirectory + +Returns directory at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to resolve +* **Returns:** + Directory path with stats +* **Raises:** + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + + NotADirectoryError + : Path is not a directory + +### Examples + +```pycon +>>> dir_path = connection.resolve_dir("/path/to/dir") +>>> os.fspath(dir_path) +'/path/to/dir' +>>> dir_path.stat().st_uid # owner id +12345 +``` + + + +#### resolve_file(path: PathLike | str) → RemoteFile + +Returns file at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to resolve +* **Returns:** + File path with stats +* **Raises:** + FileNotFoundError + : Path does not exist + + `onetl.exception.NotAFileError` + : Path is not a file + +### Examples + +```pycon +>>> file_path = connection.resolve_file("/path/to/dir/file.csv") +>>> os.fspath(file_path) +'/path/to/dir/file.csv' +>>> file_path.stat().st_uid # owner id +12345 +``` + + + +#### upload_file(local_file_path: PathLike | str, remote_file_path: PathLike | str, replace: bool = False) → RemoteFile + +Uploads local file to a remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +Supports only one file upload per call. Directory upload is **NOT** supported, use [File Uploader](../../file/file_uploader/file_uploader.md#file-uploader) instead. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **local_file_path** + : Local file path to read from + + **remote_file_path** + : Remote file path to create + + **replace** + : If `True`, existing file will be replaced +* **Returns:** + Remote file with stats. +* **Raises:** + `onetl.exception.NotAFileError` + : Remote or local path is not a file + + FileNotFoundError + : Local file does not exist + + FileExistsError + : Remote file already exists, and `replace=False` + + `onetl.exception.FileSizeMismatchError` + : Target file size after upload is different from source file size. + +### Examples + +```pycon +>>> remote_file = connection.upload( +... local_file_path="/path/to/source.csv", +... remote_file_path="/path/to/target.csv", +... ) +>>> os.fspath(remote_file) +'/path/to/target.csv' +>>> connection.path_exists("/path/to/target.csv") +True +>>> remote_file.stat().st_size # in bytes +1024 +>>> os.stat("/path/to/source.csv").st_size # same as source +1024 +``` + + + +#### walk(root: PathLike | str, topdown: bool = True, filters: Iterable[[BaseFileFilter](../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → Iterator[tuple[RemoteDirectory, list[RemoteDirectory], list[RemoteFile]]] + +Walk into directory tree, and iterate over its content in all nesting levels. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Just like `os.walk`, but with additional filter/limit logic. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **root** + : Directory path to walk into. + + **topdown** + : If `True`, walk in top-down order, otherwise walk in bottom-up order. + + **filters** + : Return only files/directories matching these filters. See [File Filters](../../file/file_filters/index.md#file-filters). + + **limits** + : Apply limits to the list of files/directories, and immediately stop if any of these limits is reached. + See [File Limits](../../file/file_limits/index.md#file-limits). +* **Returns:** + `Iterator[tuple[root, dirs, files]]`, like `os.walk`. + + But all the paths are not strings, instead path classes with embedded stats are returned. +* **Raises:** + NotADirectoryError + : Path is not a directory + + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + +### Examples + +```pycon +>>> for root, dirs, files in connection.walk("/path/to/dir"): +... break +>>> os.fspath(root) +'/path/to/dir' +>>> dirs +[] +>>> os.fspath(files[0]) +'file.csv' +>>> connection.path_exists("/path/to/dir/file.csv") +True +``` + + diff --git a/mddocs/connection/file_connection/hdfs/connection.md b/mddocs/connection/file_connection/hdfs/connection.md new file mode 100644 index 000000000..d8f53ee60 --- /dev/null +++ b/mddocs/connection/file_connection/hdfs/connection.md @@ -0,0 +1,740 @@ + + +# HDFS connection + +### *class* onetl.connection.file_connection.hdfs.connection.HDFS(\*, cluster: Cluster | None = None, host: Host | None = None, port: int = 50070, user: str | None = None, password: SecretStr | None = None, keytab: FilePath | None = None, timeout: int = 10) + +HDFS file connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Powered by [HDFS Python client](https://pypi.org/project/hdfs/). + +#### WARNING +Since onETL v0.7.0 to use HDFS connector you should install package as follows: + +```bash +pip install onetl[hdfs] + +# or +pip install onetl[files] +``` + +See [File connections](../../../install/files.md#install-files) installation instruction for more details. + +#### NOTE +To access Hadoop cluster with Kerberos installed, you should have `kinit` executable +in some path in `PATH` environment variable. + +See [Kerberos support](../../../install/kerberos.md#install-kerberos) instruction for more details. + +* **Parameters:** + **cluster** + : Hadoop cluster name. For example: `rnd-dwh`. +
+ Used for: + : * HWM and lineage (as instance name for file paths), if set. + * Validation of `host` value, + : if latter is passed and if some hooks are bound to + [`Slots.get_cluster_namenodes`](slots.md#onetl.connection.file_connection.hdfs.slots.HDFSSlots.get_cluster_namenodes) +
+ +
+ #### Versionadded + Added in version 0.7.0. + + **host** + : Hadoop namenode host. For example: `namenode1.domain.com`. +
+ Should be an active namenode (NOT standby). +
+ If value is not set, but there are some hooks bound to + [`Slots.get_cluster_namenodes`](slots.md#onetl.connection.file_connection.hdfs.slots.HDFSSlots.get_cluster_namenodes) + and [`Slots.is_namenode_active`](slots.md#onetl.connection.file_connection.hdfs.slots.HDFSSlots.is_namenode_active), + onETL will iterate over cluster namenodes to detect which one is active. + + + **webhdfs_port** + : Port of Hadoop namenode (WebHDFS protocol). +
+ If omitted, but there are some hooks bound to + [`Slots.get_webhdfs_port`](slots.md#onetl.connection.file_connection.hdfs.slots.HDFSSlots.get_webhdfs_port) slot, + onETL will try to detect port number for a specific `cluster`. + + **user** + : User, which have access to the file source. For example: `someuser`. +
+ If set, Kerberos auth will be used. Otherwise an anonymous connection is created. + + **password** + : User password. +
+ Used for generating Kerberos ticket. +
+ #### WARNING + You can provide only one of the parameters: `password` or `kinit`. + If you provide both, an exception will be raised. + + **keytab** + : LocalPath to keytab file. +
+ Used for generating Kerberos ticket. +
+ #### WARNING + You can provide only one of the parameters: `password` or `kinit`. + If you provide both, an exception will be raised. + + **timeout** + : Connection timeout. + +### Examples + +Create HDFS connection with user+password + +```py +from onetl.connection import HDFS + +hdfs = HDFS( + host="namenode1.domain.com", + user="someuser", + password="*****", +).check() +``` + +Create HDFS connection with user+keytab + +```py +from onetl.connection import HDFS + +hdfs = HDFS( + host="namenode1.domain.com", + user="someuser", + keytab="/path/to/keytab", +).check() +``` + +Create HDFS connection without auth + +```py +from onetl.connection import HDFS + +hdfs = HDFS(host="namenode1.domain.com").check() +``` + +Use cluster name to detect active namenode + +Can be used only if some third-party plugin provides [HDFS Slots](slots.md#hdfs-slots) implementation + +```python +from onetl.connection import HDFS + +hdfs = HDFS( + cluster="rnd-dwh", + user="someuser", + password="*****", +).check() +``` + + + +#### check() + +Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If not, an exception will be raised. + +* **Returns:** + Connection itself +* **Raises:** + RuntimeError + : If the connection is not available + +### Examples + +```python +connection.check() +``` + + + +#### create_dir(path: PathLike | str) → RemoteDirectory + +Creates directory tree on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Directory path +* **Returns:** + Created directory with stats +* **Raises:** + `onetl.exception.NotAFileError` + : Path is not a file + +### Examples + +```pycon +>>> dir_path = connection.create_dir("/path/to/dir") +>>> os.fspath(dir_path) +'/path/to/dir' +``` + + + +#### download_file(remote_file_path: PathLike | str, local_file_path: PathLike | str, replace: bool = True) → LocalPath + +Downloads file from the remote filesystem to a local path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +Supports only one file download per call. Directory download is **NOT** supported, use [File Downloader](../../../file/file_downloader/file_downloader.md#file-downloader) instead. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **remote_file_path** + : Remote file path to read from + + **local_file_path** + : Local file path to create + + **replace** + : If `True`, existing file will be replaced +* **Returns:** + Local file with stats. +* **Raises:** + `onetl.exception.NotAFileError` + : Remote or local path is not a file + + FileNotFoundError + : Remote file does not exist + + FileExistsError + : Local file already exists, and `replace=False` + + `onetl.exception.FileSizeMismatchError` + : Target file size after download is different from source file size. + +### Examples + +```pycon +>>> local_file = connection.download_file( +... remote_file_path="/path/to/source.csv", +... local_file_path="/path/to/target.csv", +... ) +>>> os.fspath(local_file) +'/path/to/target.csv' +>>> local_file.exists() +True +>>> local_file.stat().st_size # in bytes +1024 +>>> connection.get_stat("/path/to/source.csv").st_size # same size +1024 +``` + + + +#### *classmethod* get_current(\*\*kwargs) + +Create connection for current cluster. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Automatically sets up current cluster name as `cluster`. + +#### NOTE +Can be used only if there are a some hooks bound to slot +[`Slots.get_current_cluster`](slots.md#onetl.connection.file_connection.hdfs.slots.HDFSSlots.get_current_cluster) + +#### Versionadded +Added in version 0.7.0. + +* **Parameters:** + **user** + + **password** + + **keytab** + + **timeout** + : See [`HDFS`](#onetl.connection.file_connection.hdfs.connection.HDFS) constructor documentation. + +### Examples + +```python +from onetl.connection import HDFS + +# injecting current cluster name via hooks mechanism +hdfs = HDFS.get_current(user="me", password="pass") +``` + + + +#### get_stat(path: PathLike | str) → PathStatProtocol + +Returns stats for a specific path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to get stats for +* **Returns:** + Stats object +* **Raises:** + Any underlying client exception + +### Examples + +```pycon +>>> stat = connection.get_stat("/path/to/file.csv") +>>> stat.st_size # in bytes +1024 +>>> stat.st_uid # owner id or name +12345 +``` + + + +#### is_dir(path: PathLike | str) → bool + +Check if specified path is a directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if path is a directory, `False` otherwise. +* **Raises:** + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + +### Examples + +```pycon +>>> connection.is_dir("/path/to/dir") +True +>>> connection.is_dir("/path/to/dir/file.csv") +False +``` + + + +#### is_file(path: PathLike | str) → bool + +Check if specified path is a file. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if path is a file, `False` otherwise. +* **Raises:** + FileNotFoundError + : Path does not exist + +### Examples + +```pycon +>>> connection.is_file("/path/to/dir/file.csv") +True +>>> connection.is_file("/path/to/dir") +False +``` + + + +#### list_dir(path: PathLike | str, filters: Iterable[[BaseFileFilter](../../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → list[RemoteDirectory | RemoteFile] + +Return list of child files/directories in a specific directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Directory path to list contents. + + **filters** + : Return only files/directories matching these filters. See [File Filters](../../../file/file_filters/index.md#file-filters) + + **limits** + : Apply limits to the list of files/directories, and stop if one of the limits is reached. + See [File Limits](../../../file/file_limits/index.md#file-limits) +* **Returns:** + List of `onetl.base.PathWithStatsProtocol` +* **Raises:** + NotADirectoryError + : Path is not a directory + + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + +### Examples + +```pycon +>>> dir_content = connection.list_dir("/path/to/dir") +>>> os.fspath(dir_content[0]) +'file.csv' +>>> connection.path_exists("/path/to/dir/file.csv") +True +``` + + + +#### path_exists(path: PathLike | str) → bool + +Check if specified path exists on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html). + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if path exists, `False` otherwise + +### Examples + +```pycon +>>> connection.path_exists("/path/to/file.csv") +True +>>> connection.path_exists("/path/to/dir") +True +>>> connection.path_exists("/path/to/missing") +False +``` + + + +#### remove_dir(path: PathLike | str, recursive: bool = False) → bool + +Remove directory or directory tree. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If directory does not exist, no exception is raised. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Directory path to remote + + **recursive** + : If `True`, remove directory tree recursively. +* **Returns:** + `True` if directory was removed, `False` if directory does not exist in the first place. +* **Raises:** + NotADirectoryError + : Path is not a directory + +### Examples + +```pycon +>>> connection.remove_dir("/path/to/dir") +True +>>> connection.path_exists("/path/to/dir") +False +>>> connection.path_exists("/path/to/dir/file.csv") +False +>>> connection.remove_dir("/path/to/dir") # already deleted, no error +False +``` + + + +#### remove_file(path: PathLike | str) → bool + +Removes file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If file does not exist, no exception is raised. + +#### WARNING +Supports only one file removal per call. Directory removal is **NOT** supported, use [`remove_dir`](#onetl.connection.file_connection.hdfs.connection.HDFS.remove_dir) instead. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : File path +* **Returns:** + `True` if file was removed, `False` if file does not exist in the first place. +* **Raises:** + `onetl.exception.NotAFileError` + : Path is not a file + +### Examples + +```pycon +>>> connection.remove_file("/path/to/file.csv") +True +>>> connection.path_exists("/path/to/dir/file.csv") +False +>>> connection.remove_file("/path/to/file.csv") # already deleted, no error +False +``` + + + +#### rename_dir(source_dir_path: PathLike | str, target_dir_path: PathLike | str, replace: bool = False) → RemoteDirectory + +Rename or move dir on remote filesystem. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **source_dir_path** + : Old directory path + + **target_dir_path** + : New directory path + + **replace** + : If `True`, existing directory will be replaced. +* **Returns:** + New directory path with stats. +* **Raises:** + NotADirectoryError + : Path is not a directory + + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + + `onetl.exception.DirectoryExistsError` + : Directory already exists, and `replace=False` + +### Examples + +```pycon +>>> new_dir = connection.rename_dir("/path/to/dir1", "/path/to/dir2") +>>> os.fspath(new_dir) +'/path/to/dir2' +>>> connection.path_exists("/path/to/dir1") +False +>>> connection.path_exists("/path/to/dir2") +True +``` + + + +#### rename_file(source_file_path: PathLike | str, target_file_path: PathLike | str, replace: bool = False) → RemoteFile + +Rename or move file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +Supports only one file move per call. Directory move/rename is **NOT** supported. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **source_file_path** + : Old file path + + **target_file_path** + : New file path + + **replace** + : If `True`, existing file will be replaced. +* **Returns:** + New file path with stats. +* **Raises:** + `onetl.exception.NotAFileError` + : Source or target path is not a file + + FileNotFoundError + : File does not exist + + FileExistsError + : File already exists, and `replace=False` + +### Examples + +```pycon +>>> new_file = connection.rename_file("/path/to/file1.csv", "/path/to/file2.csv") +>>> os.fspath(new_file) +'/path/to/file2.csv' +>>> connection.path_exists("/path/to/file2.csv") +True +>>> connection.path_exists("/path/to/file1.csv") +False +``` + + + +#### resolve_dir(path: PathLike | str) → RemoteDirectory + +Returns directory at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to resolve +* **Returns:** + Directory path with stats +* **Raises:** + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + + NotADirectoryError + : Path is not a directory + +### Examples + +```pycon +>>> dir_path = connection.resolve_dir("/path/to/dir") +>>> os.fspath(dir_path) +'/path/to/dir' +>>> dir_path.stat().st_uid # owner id +12345 +``` + + + +#### resolve_file(path: PathLike | str) → RemoteFile + +Returns file at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to resolve +* **Returns:** + File path with stats +* **Raises:** + FileNotFoundError + : Path does not exist + + `onetl.exception.NotAFileError` + : Path is not a file + +### Examples + +```pycon +>>> file_path = connection.resolve_file("/path/to/dir/file.csv") +>>> os.fspath(file_path) +'/path/to/dir/file.csv' +>>> file_path.stat().st_uid # owner id +12345 +``` + + + +#### upload_file(local_file_path: PathLike | str, remote_file_path: PathLike | str, replace: bool = False) → RemoteFile + +Uploads local file to a remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +Supports only one file upload per call. Directory upload is **NOT** supported, use [File Uploader](../../../file/file_uploader/file_uploader.md#file-uploader) instead. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **local_file_path** + : Local file path to read from + + **remote_file_path** + : Remote file path to create + + **replace** + : If `True`, existing file will be replaced +* **Returns:** + Remote file with stats. +* **Raises:** + `onetl.exception.NotAFileError` + : Remote or local path is not a file + + FileNotFoundError + : Local file does not exist + + FileExistsError + : Remote file already exists, and `replace=False` + + `onetl.exception.FileSizeMismatchError` + : Target file size after upload is different from source file size. + +### Examples + +```pycon +>>> remote_file = connection.upload( +... local_file_path="/path/to/source.csv", +... remote_file_path="/path/to/target.csv", +... ) +>>> os.fspath(remote_file) +'/path/to/target.csv' +>>> connection.path_exists("/path/to/target.csv") +True +>>> remote_file.stat().st_size # in bytes +1024 +>>> os.stat("/path/to/source.csv").st_size # same as source +1024 +``` + + + +#### walk(root: PathLike | str, topdown: bool = True, filters: Iterable[[BaseFileFilter](../../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → Iterator[tuple[RemoteDirectory, list[RemoteDirectory], list[RemoteFile]]] + +Walk into directory tree, and iterate over its content in all nesting levels. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Just like `os.walk`, but with additional filter/limit logic. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **root** + : Directory path to walk into. + + **topdown** + : If `True`, walk in top-down order, otherwise walk in bottom-up order. + + **filters** + : Return only files/directories matching these filters. See [File Filters](../../../file/file_filters/index.md#file-filters). + + **limits** + : Apply limits to the list of files/directories, and immediately stop if any of these limits is reached. + See [File Limits](../../../file/file_limits/index.md#file-limits). +* **Returns:** + `Iterator[tuple[root, dirs, files]]`, like `os.walk`. + + But all the paths are not strings, instead path classes with embedded stats are returned. +* **Raises:** + NotADirectoryError + : Path is not a directory + + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + +### Examples + +```pycon +>>> for root, dirs, files in connection.walk("/path/to/dir"): +... break +>>> os.fspath(root) +'/path/to/dir' +>>> dirs +[] +>>> os.fspath(files[0]) +'file.csv' +>>> connection.path_exists("/path/to/dir/file.csv") +True +``` + + diff --git a/mddocs/connection/file_connection/hdfs/index.md b/mddocs/connection/file_connection/hdfs/index.md new file mode 100644 index 000000000..a65c43ea3 --- /dev/null +++ b/mddocs/connection/file_connection/hdfs/index.md @@ -0,0 +1,11 @@ + + +# HDFS + +# Connection + +* [HDFS connection](connection.md) + +# For developers + +* [HDFS Slots](slots.md) diff --git a/mddocs/connection/file_connection/hdfs/slots.md b/mddocs/connection/file_connection/hdfs/slots.md new file mode 100644 index 000000000..6f681805b --- /dev/null +++ b/mddocs/connection/file_connection/hdfs/slots.md @@ -0,0 +1,256 @@ + + +# HDFS Slots + +### *class* onetl.connection.file_connection.hdfs.slots.HDFSSlots + +Slots that could be implemented by third-party plugins. + +#### Versionadded +Added in version 0.7.0. + + + +#### *static* normalize_cluster_name(cluster: str) → str | None + +Normalize cluster name passed into HDFS constructor. + +If hooks didn’t return anything, cluster name is left intact. + +#### Versionadded +Added in version 0.7.0. + +* **Parameters:** + **cluster** + : Cluster name +* **Returns:** + str | None + : Normalized cluster name. +
+ If hook cannot be applied to a specific cluster, it should return `None`. + +### Examples + +```python +from onetl.connection import HDFS +from onetl.hooks import hook + +@HDFS.Slots.normalize_cluster_name.bind +@hook +def normalize_cluster_name(cluster: str) -> str: + return cluster.lower() +``` + + + +#### *static* normalize_namenode_host(host: str, cluster: str | None) → str | None + +Normalize namenode host passed into HDFS constructor. + +If hooks didn’t return anything, host is left intact. + +#### Versionadded +Added in version 0.7.0. + +* **Parameters:** + **host** + : Namenode host (raw) + + **cluster** + : Cluster name (normalized), if set +* **Returns:** + str | None + : Normalized namenode host name. +
+ If hook cannot be applied to a specific host name, it should return `None`. + +### Examples + +```python +from onetl.connection import HDFS +from onetl.hooks import hook + +@HDFS.Slots.normalize_namenode_host.bind +@hook +def normalize_namenode_host(host: str, cluster: str) -> str | None: + if cluster == "rnd-dwh": + if not host.endswith(".domain.com"): + # fix missing domain name + host += ".domain.com" + return host + + return None +``` + + + +#### *static* get_known_clusters() → set[str] | None + +Return collection of known clusters. + +Cluster passed into HDFS constructor should be present in this list. +If hooks didn’t return anything, no validation will be performed. + +#### Versionadded +Added in version 0.7.0. + +* **Returns:** + set[str] | None + : Collection of cluster names (in normalized form). +
+ If hook cannot be applied, it should return `None`. + +### Examples + +```python +from onetl.connection import HDFS +from onetl.hooks import hook + +@HDFS.Slots.get_known_clusters.bind +@hook +def get_known_clusters() -> str[str]: + return {"rnd-dwh", "rnd-prod"} +``` + + + +#### *static* get_cluster_namenodes(cluster: str) → set[str] | None + +Return collection of known namenodes for the cluster. + +Namenode host passed into HDFS constructor should be present in this list. +If hooks didn’t return anything, no validation will be performed. + +#### Versionadded +Added in version 0.7.0. + +* **Parameters:** + **cluster** + : Cluster name (normalized) +* **Returns:** + set[str] | None + : Collection of host names (in normalized form). +
+ If hook cannot be applied, it should return `None`. + +### Examples + +```python +from onetl.connection import HDFS +from onetl.hooks import hook + +@HDFS.Slots.get_cluster_namenodes.bind +@hook +def get_cluster_namenodes(cluster: str) -> str[str] | None: + if cluster == "rnd-dwh": + return {"namenode1.domain.com", "namenode2.domain.com"} + return None +``` + + + +#### *static* get_current_cluster() → str | None + +Get current cluster name. + +Used in [`get_current_cluster`](#onetl.connection.file_connection.hdfs.slots.HDFSSlots.get_current_cluster) to automatically fill up `cluster` attribute of a connection. +If hooks didn’t return anything, calling the method above will raise an exception. + +#### Versionadded +Added in version 0.7.0. + +* **Returns:** + str | None + : Current cluster name (in normalized form). +
+ If hook cannot be applied, it should return `None`. + +### Examples + +```python +from onetl.connection import HDFS +from onetl.hooks import hook + +@HDFS.Slots.get_current_cluster.bind +@hook +def get_current_cluster() -> str: + # some magic here + return "rnd-dwh" +``` + + + +#### *static* get_webhdfs_port(cluster: str) → int | None + +Get WebHDFS port number for a specific cluster. + +Used by constructor to automatically set port number if omitted. + +#### Versionadded +Added in version 0.7.0. + +* **Parameters:** + **cluster** + : Cluster name (normalized) +* **Returns:** + int | None + : WebHDFS port number. +
+ If hook cannot be applied, it should return `None`. + +### Examples + +```python +from onetl.connection import HDFS +from onetl.hooks import hook + +@HDFS.Slots.get_webhdfs_port.bind +@hook +def get_webhdfs_port(cluster: str) -> int | None: + if cluster == "rnd-dwh": + return 50007 # Cloudera + return None +``` + + + +#### *static* is_namenode_active(host: str, cluster: str | None) → bool | None + +Check whether a namenode of a specified cluster is active (=not standby) or not. + +Used for: +: * If HDFS connection is created without `host` + > Connector will iterate over [`get_cluster_namenodes`](#onetl.connection.file_connection.hdfs.slots.HDFSSlots.get_cluster_namenodes) of a cluster to get active namenode, + > and then use it instead of `host` attribute. + * If HDFS connection is created with `host` + > `check` will determine whether this host is active. + +#### Versionadded +Added in version 0.7.0. + +* **Parameters:** + **host** + : Namenode host (normalized) + + **cluster** + : Cluster name (normalized), if set +* **Returns:** + bool | None + : `True` if namenode is active, `False` if not. +
+ If hook cannot be applied, it should return `None`. + +### Examples + +```python +from onetl.connection import HDFS +from onetl.hooks import hook + +@HDFS.Slots.is_namenode_active.bind +@hook +def is_namenode_active(host: str, cluster: str | None) -> bool: + # some magic here + return True +``` + + diff --git a/mddocs/connection/file_connection/index.md b/mddocs/connection/file_connection/index.md new file mode 100644 index 000000000..874845c78 --- /dev/null +++ b/mddocs/connection/file_connection/index.md @@ -0,0 +1,13 @@ + + +# File Connections + +# File Connections + +* [FTP](ftp.md) +* [FTPS](ftps.md) +* [HDFS](hdfs/index.md) +* [Samba](samba.md) +* [SFTP](sftp.md) +* [S3](s3.md) +* [Webdav](webdav.md) diff --git a/mddocs/connection/file_connection/s3.md b/mddocs/connection/file_connection/s3.md new file mode 100644 index 000000000..4fa940aaf --- /dev/null +++ b/mddocs/connection/file_connection/s3.md @@ -0,0 +1,598 @@ + + +# S3 connection + +### *class* onetl.connection.file_connection.s3.S3(\*, host: Host, port: int | None = None, bucket: str, access_key: str, secret_key: SecretStr, protocol: Literal['http', 'https'] = 'https', session_token: SecretStr | None = None, region: str | None = None) + +S3 file connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on [minio-py client](https://pypi.org/project/minio/). + +#### WARNING +Since onETL v0.7.0 to use S3 connector you should install package as follows: + +```bash +pip install onetl[s3] + +# or +pip install onetl[files] +``` + +See [File connections](../../install/files.md#install-files) installation instruction for more details. + +#### Versionadded +Added in version 0.5.1. + +* **Parameters:** + **host** + : Host of S3 source. For example: `s3.domain.com` + + **port** + : Port of S3 source + + **bucket** + : Bucket name in the S3 file source + + **access_key** + : Access key (aka user ID) of an account in the S3 service + + **secret_key** + : Secret key (aka password) of an account in the S3 service + + **protocol** + : Connection protocol. Allowed values: `https` or `http` +
+ #### Versionchanged + Changed in version 0.6.0: Renamed `secure: bool` to `protocol: Literal["https", "http"]` + + **session_token** + : Session token of your account in S3 service + + **region** + : Region name of bucket in S3 service + +### Examples + +Create and check S3 connection: + +```python +from onetl.connection import S3 + +s3 = S3( + host="s3.domain.com", + protocol="http", + bucket="my-bucket", + access_key="ACCESS_KEY", + secret_key="SECRET_KEY", +).check() +``` + + + +#### \_\_init_\_(\*\*kwargs) + + + +#### check() + +Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If not, an exception will be raised. + +* **Returns:** + Connection itself +* **Raises:** + RuntimeError + : If the connection is not available + +### Examples + +```python +connection.check() +``` + + + +#### create_dir(path: PathLike | str) → RemoteDirectory + +Creates directory tree on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Directory path +* **Returns:** + Created directory with stats +* **Raises:** + `onetl.exception.NotAFileError` + : Path is not a file + +### Examples + +```pycon +>>> dir_path = connection.create_dir("/path/to/dir") +>>> os.fspath(dir_path) +'/path/to/dir' +``` + + + +#### download_file(remote_file_path: PathLike | str, local_file_path: PathLike | str, replace: bool = True) → LocalPath + +Downloads file from the remote filesystem to a local path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +Supports only one file download per call. Directory download is **NOT** supported, use [File Downloader](../../file/file_downloader/file_downloader.md#file-downloader) instead. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **remote_file_path** + : Remote file path to read from + + **local_file_path** + : Local file path to create + + **replace** + : If `True`, existing file will be replaced +* **Returns:** + Local file with stats. +* **Raises:** + `onetl.exception.NotAFileError` + : Remote or local path is not a file + + FileNotFoundError + : Remote file does not exist + + FileExistsError + : Local file already exists, and `replace=False` + + `onetl.exception.FileSizeMismatchError` + : Target file size after download is different from source file size. + +### Examples + +```pycon +>>> local_file = connection.download_file( +... remote_file_path="/path/to/source.csv", +... local_file_path="/path/to/target.csv", +... ) +>>> os.fspath(local_file) +'/path/to/target.csv' +>>> local_file.exists() +True +>>> local_file.stat().st_size # in bytes +1024 +>>> connection.get_stat("/path/to/source.csv").st_size # same size +1024 +``` + + + +#### get_stat(path: PathLike | str) → PathStatProtocol + +Returns stats for a specific path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to get stats for +* **Returns:** + Stats object +* **Raises:** + Any underlying client exception + +### Examples + +```pycon +>>> stat = connection.get_stat("/path/to/file.csv") +>>> stat.st_size # in bytes +1024 +>>> stat.st_uid # owner id or name +12345 +``` + + + +#### is_dir(path: PathLike | str) → bool + +Check if specified path is a directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if path is a directory, `False` otherwise. +* **Raises:** + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + +### Examples + +```pycon +>>> connection.is_dir("/path/to/dir") +True +>>> connection.is_dir("/path/to/dir/file.csv") +False +``` + + + +#### is_file(path: PathLike | str) → bool + +Check if specified path is a file. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if path is a file, `False` otherwise. +* **Raises:** + FileNotFoundError + : Path does not exist + +### Examples + +```pycon +>>> connection.is_file("/path/to/dir/file.csv") +True +>>> connection.is_file("/path/to/dir") +False +``` + + + +#### list_dir(path: PathLike | str, filters: Iterable[[BaseFileFilter](../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → list[RemoteDirectory | RemoteFile] + +Return list of child files/directories in a specific directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Directory path to list contents. + + **filters** + : Return only files/directories matching these filters. See [File Filters](../../file/file_filters/index.md#file-filters) + + **limits** + : Apply limits to the list of files/directories, and stop if one of the limits is reached. + See [File Limits](../../file/file_limits/index.md#file-limits) +* **Returns:** + List of `onetl.base.PathWithStatsProtocol` +* **Raises:** + NotADirectoryError + : Path is not a directory + + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + +### Examples + +```pycon +>>> dir_content = connection.list_dir("/path/to/dir") +>>> os.fspath(dir_content[0]) +'file.csv' +>>> connection.path_exists("/path/to/dir/file.csv") +True +``` + + + +#### path_exists(path: PathLike | str) → bool + +Check if specified path exists on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html). + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if path exists, `False` otherwise + +### Examples + +```pycon +>>> connection.path_exists("/path/to/file.csv") +True +>>> connection.path_exists("/path/to/dir") +True +>>> connection.path_exists("/path/to/missing") +False +``` + + + +#### remove_dir(path: PathLike | str, recursive: bool = False) → bool + +Remove directory or directory tree. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If directory does not exist, no exception is raised. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Directory path to remote + + **recursive** + : If `True`, remove directory tree recursively. +* **Returns:** + `True` if directory was removed, `False` if directory does not exist in the first place. +* **Raises:** + NotADirectoryError + : Path is not a directory + +### Examples + +```pycon +>>> connection.remove_dir("/path/to/dir") +True +>>> connection.path_exists("/path/to/dir") +False +>>> connection.path_exists("/path/to/dir/file.csv") +False +>>> connection.remove_dir("/path/to/dir") # already deleted, no error +False +``` + + + +#### remove_file(path: PathLike | str) → bool + +Removes file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If file does not exist, no exception is raised. + +#### WARNING +Supports only one file removal per call. Directory removal is **NOT** supported, use [`remove_dir`](#onetl.connection.file_connection.s3.S3.remove_dir) instead. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : File path +* **Returns:** + `True` if file was removed, `False` if file does not exist in the first place. +* **Raises:** + `onetl.exception.NotAFileError` + : Path is not a file + +### Examples + +```pycon +>>> connection.remove_file("/path/to/file.csv") +True +>>> connection.path_exists("/path/to/dir/file.csv") +False +>>> connection.remove_file("/path/to/file.csv") # already deleted, no error +False +``` + + + +#### rename_file(source_file_path: PathLike | str, target_file_path: PathLike | str, replace: bool = False) → RemoteFile + +Rename or move file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +Supports only one file move per call. Directory move/rename is **NOT** supported. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **source_file_path** + : Old file path + + **target_file_path** + : New file path + + **replace** + : If `True`, existing file will be replaced. +* **Returns:** + New file path with stats. +* **Raises:** + `onetl.exception.NotAFileError` + : Source or target path is not a file + + FileNotFoundError + : File does not exist + + FileExistsError + : File already exists, and `replace=False` + +### Examples + +```pycon +>>> new_file = connection.rename_file("/path/to/file1.csv", "/path/to/file2.csv") +>>> os.fspath(new_file) +'/path/to/file2.csv' +>>> connection.path_exists("/path/to/file2.csv") +True +>>> connection.path_exists("/path/to/file1.csv") +False +``` + + + +#### resolve_dir(path: PathLike | str) → RemoteDirectory + +Returns directory at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to resolve +* **Returns:** + Directory path with stats +* **Raises:** + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + + NotADirectoryError + : Path is not a directory + +### Examples + +```pycon +>>> dir_path = connection.resolve_dir("/path/to/dir") +>>> os.fspath(dir_path) +'/path/to/dir' +>>> dir_path.stat().st_uid # owner id +12345 +``` + + + +#### resolve_file(path: PathLike | str) → RemoteFile + +Returns file at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to resolve +* **Returns:** + File path with stats +* **Raises:** + FileNotFoundError + : Path does not exist + + `onetl.exception.NotAFileError` + : Path is not a file + +### Examples + +```pycon +>>> file_path = connection.resolve_file("/path/to/dir/file.csv") +>>> os.fspath(file_path) +'/path/to/dir/file.csv' +>>> file_path.stat().st_uid # owner id +12345 +``` + + + +#### upload_file(local_file_path: PathLike | str, remote_file_path: PathLike | str, replace: bool = False) → RemoteFile + +Uploads local file to a remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +Supports only one file upload per call. Directory upload is **NOT** supported, use [File Uploader](../../file/file_uploader/file_uploader.md#file-uploader) instead. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **local_file_path** + : Local file path to read from + + **remote_file_path** + : Remote file path to create + + **replace** + : If `True`, existing file will be replaced +* **Returns:** + Remote file with stats. +* **Raises:** + `onetl.exception.NotAFileError` + : Remote or local path is not a file + + FileNotFoundError + : Local file does not exist + + FileExistsError + : Remote file already exists, and `replace=False` + + `onetl.exception.FileSizeMismatchError` + : Target file size after upload is different from source file size. + +### Examples + +```pycon +>>> remote_file = connection.upload( +... local_file_path="/path/to/source.csv", +... remote_file_path="/path/to/target.csv", +... ) +>>> os.fspath(remote_file) +'/path/to/target.csv' +>>> connection.path_exists("/path/to/target.csv") +True +>>> remote_file.stat().st_size # in bytes +1024 +>>> os.stat("/path/to/source.csv").st_size # same as source +1024 +``` + + + +#### walk(root: PathLike | str, topdown: bool = True, filters: Iterable[[BaseFileFilter](../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → Iterator[tuple[RemoteDirectory, list[RemoteDirectory], list[RemoteFile]]] + +Walk into directory tree, and iterate over its content in all nesting levels. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Just like `os.walk`, but with additional filter/limit logic. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **root** + : Directory path to walk into. + + **topdown** + : If `True`, walk in top-down order, otherwise walk in bottom-up order. + + **filters** + : Return only files/directories matching these filters. See [File Filters](../../file/file_filters/index.md#file-filters). + + **limits** + : Apply limits to the list of files/directories, and immediately stop if any of these limits is reached. + See [File Limits](../../file/file_limits/index.md#file-limits). +* **Returns:** + `Iterator[tuple[root, dirs, files]]`, like `os.walk`. + + But all the paths are not strings, instead path classes with embedded stats are returned. +* **Raises:** + NotADirectoryError + : Path is not a directory + + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + +### Examples + +```pycon +>>> for root, dirs, files in connection.walk("/path/to/dir"): +... break +>>> os.fspath(root) +'/path/to/dir' +>>> dirs +[] +>>> os.fspath(files[0]) +'file.csv' +>>> connection.path_exists("/path/to/dir/file.csv") +True +``` + + diff --git a/mddocs/connection/file_connection/samba.md b/mddocs/connection/file_connection/samba.md new file mode 100644 index 000000000..81b661ec1 --- /dev/null +++ b/mddocs/connection/file_connection/samba.md @@ -0,0 +1,548 @@ + + +# Samba connection + +### *class* onetl.connection.file_connection.samba.Samba(\*, host: Host, share: str, protocol: Literal['SMB', 'NetBIOS'] = 'SMB', port: int | None = None, domain: str | None = '', auth_type: Literal['NTLMv1', 'NTLMv2'] = 'NTLMv2', user: str | None = None, password: SecretStr | None = None) + +Samba file connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on [pysmb library](https://pypi.org/project/pysmb/). + +#### Versionadded +Added in version 0.9.4. + +#### WARNING +To use Samba connector you should install package as follows: + +```bash +pip install onetl[samba] + +# or +pip install onetl[files] +``` + +See [File connections](../../install/files.md#install-files) installation instruction for more details. + +* **Parameters:** + **host** + : Host of Samba source. For example: `mydomain.com`. + + **share** + : The name of the share on the Samba server. + + **protocol** + : The protocol to use for the connection. Either `SMB` or `NetBIOS`. + Affects the default port and the is_direct_tcp flag in SMBConnection. + + **port** + : Port of Samba source. + + **domain** + : Domain name for the Samba connection. Empty strings means use `host` as domain name. + + **auth_type** + : The authentication type to use. Either `NTLMv2` or `NTLMv1`. + Affects the use_ntlm_v2 flag in SMBConnection. + + **user** + : User, which have access to the file source. Can be None for anonymous connection. + + **password** + : Password for file source connection. Can be None for anonymous connection. + +### Examples + +Create and check Samba connection: + +```python +from onetl.connection import Samba + +samba = Samba( + host="mydomain.com", + share="share_name", + protocol="SMB", + port=445, + user="user", + password="password", +).check() +``` + + + +#### \_\_init_\_(\*\*kwargs) + + + +#### check() + +Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If not, an exception will be raised. + +* **Returns:** + Connection itself +* **Raises:** + RuntimeError + : If the connection is not available + +### Examples + +```python +connection.check() +``` + + + +#### create_dir(path: PathLike | str) → RemoteDirectory + +Creates directory tree on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Directory path +* **Returns:** + Created directory with stats +* **Raises:** + `onetl.exception.NotAFileError` + : Path is not a file + +### Examples + +```pycon +>>> dir_path = connection.create_dir("/path/to/dir") +>>> os.fspath(dir_path) +'/path/to/dir' +``` + + + +#### download_file(remote_file_path: PathLike | str, local_file_path: PathLike | str, replace: bool = True) → LocalPath + +Downloads file from the remote filesystem to a local path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +Supports only one file download per call. Directory download is **NOT** supported, use [File Downloader](../../file/file_downloader/file_downloader.md#file-downloader) instead. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **remote_file_path** + : Remote file path to read from + + **local_file_path** + : Local file path to create + + **replace** + : If `True`, existing file will be replaced +* **Returns:** + Local file with stats. +* **Raises:** + `onetl.exception.NotAFileError` + : Remote or local path is not a file + + FileNotFoundError + : Remote file does not exist + + FileExistsError + : Local file already exists, and `replace=False` + + `onetl.exception.FileSizeMismatchError` + : Target file size after download is different from source file size. + +### Examples + +```pycon +>>> local_file = connection.download_file( +... remote_file_path="/path/to/source.csv", +... local_file_path="/path/to/target.csv", +... ) +>>> os.fspath(local_file) +'/path/to/target.csv' +>>> local_file.exists() +True +>>> local_file.stat().st_size # in bytes +1024 +>>> connection.get_stat("/path/to/source.csv").st_size # same size +1024 +``` + + + +#### get_stat(path: PathLike | str) → PathStatProtocol + +Returns stats for a specific path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to get stats for +* **Returns:** + Stats object +* **Raises:** + Any underlying client exception + +### Examples + +```pycon +>>> stat = connection.get_stat("/path/to/file.csv") +>>> stat.st_size # in bytes +1024 +>>> stat.st_uid # owner id or name +12345 +``` + + + +#### is_dir(path: PathLike | str) → bool + +Check if specified path is a directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if path is a directory, `False` otherwise. +* **Raises:** + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + +### Examples + +```pycon +>>> connection.is_dir("/path/to/dir") +True +>>> connection.is_dir("/path/to/dir/file.csv") +False +``` + + + +#### is_file(path: PathLike | str) → bool + +Check if specified path is a file. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if path is a file, `False` otherwise. +* **Raises:** + FileNotFoundError + : Path does not exist + +### Examples + +```pycon +>>> connection.is_file("/path/to/dir/file.csv") +True +>>> connection.is_file("/path/to/dir") +False +``` + + + +#### list_dir(path: PathLike | str, filters: Iterable[[BaseFileFilter](../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → list[RemoteDirectory | RemoteFile] + +Return list of child files/directories in a specific directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Directory path to list contents. + + **filters** + : Return only files/directories matching these filters. See [File Filters](../../file/file_filters/index.md#file-filters) + + **limits** + : Apply limits to the list of files/directories, and stop if one of the limits is reached. + See [File Limits](../../file/file_limits/index.md#file-limits) +* **Returns:** + List of `onetl.base.PathWithStatsProtocol` +* **Raises:** + NotADirectoryError + : Path is not a directory + + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + +### Examples + +```pycon +>>> dir_content = connection.list_dir("/path/to/dir") +>>> os.fspath(dir_content[0]) +'file.csv' +>>> connection.path_exists("/path/to/dir/file.csv") +True +``` + + + +#### path_exists(path: PathLike | str) → bool + +Check if specified path exists on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html). + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if path exists, `False` otherwise + +### Examples + +```pycon +>>> connection.path_exists("/path/to/file.csv") +True +>>> connection.path_exists("/path/to/dir") +True +>>> connection.path_exists("/path/to/missing") +False +``` + + + +#### remove_dir(path: PathLike | str, recursive: bool = False) → bool + +Remove directory or directory tree. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If directory does not exist, no exception is raised. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Directory path to remote + + **recursive** + : If `True`, remove directory tree recursively. +* **Returns:** + `True` if directory was removed, `False` if directory does not exist in the first place. +* **Raises:** + NotADirectoryError + : Path is not a directory + +### Examples + +```pycon +>>> connection.remove_dir("/path/to/dir") +True +>>> connection.path_exists("/path/to/dir") +False +>>> connection.path_exists("/path/to/dir/file.csv") +False +>>> connection.remove_dir("/path/to/dir") # already deleted, no error +False +``` + + + +#### remove_file(path: PathLike | str) → bool + +Removes file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If file does not exist, no exception is raised. + +#### WARNING +Supports only one file removal per call. Directory removal is **NOT** supported, use [`remove_dir`](#onetl.connection.file_connection.samba.Samba.remove_dir) instead. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : File path +* **Returns:** + `True` if file was removed, `False` if file does not exist in the first place. +* **Raises:** + `onetl.exception.NotAFileError` + : Path is not a file + +### Examples + +```pycon +>>> connection.remove_file("/path/to/file.csv") +True +>>> connection.path_exists("/path/to/dir/file.csv") +False +>>> connection.remove_file("/path/to/file.csv") # already deleted, no error +False +``` + + + +#### rename_file(source_file_path: PathLike | str, target_file_path: PathLike | str, replace: bool = False) → RemoteFile + +Rename or move file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +Supports only one file move per call. Directory move/rename is **NOT** supported. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **source_file_path** + : Old file path + + **target_file_path** + : New file path + + **replace** + : If `True`, existing file will be replaced. +* **Returns:** + New file path with stats. +* **Raises:** + `onetl.exception.NotAFileError` + : Source or target path is not a file + + FileNotFoundError + : File does not exist + + FileExistsError + : File already exists, and `replace=False` + +### Examples + +```pycon +>>> new_file = connection.rename_file("/path/to/file1.csv", "/path/to/file2.csv") +>>> os.fspath(new_file) +'/path/to/file2.csv' +>>> connection.path_exists("/path/to/file2.csv") +True +>>> connection.path_exists("/path/to/file1.csv") +False +``` + + + +#### resolve_dir(path: PathLike | str) → RemoteDirectory + +Returns directory at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to resolve +* **Returns:** + Directory path with stats +* **Raises:** + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + + NotADirectoryError + : Path is not a directory + +### Examples + +```pycon +>>> dir_path = connection.resolve_dir("/path/to/dir") +>>> os.fspath(dir_path) +'/path/to/dir' +>>> dir_path.stat().st_uid # owner id +12345 +``` + + + +#### resolve_file(path: PathLike | str) → RemoteFile + +Returns file at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to resolve +* **Returns:** + File path with stats +* **Raises:** + FileNotFoundError + : Path does not exist + + `onetl.exception.NotAFileError` + : Path is not a file + +### Examples + +```pycon +>>> file_path = connection.resolve_file("/path/to/dir/file.csv") +>>> os.fspath(file_path) +'/path/to/dir/file.csv' +>>> file_path.stat().st_uid # owner id +12345 +``` + + + +#### upload_file(local_file_path: PathLike | str, remote_file_path: PathLike | str, replace: bool = False) → RemoteFile + +Uploads local file to a remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +Supports only one file upload per call. Directory upload is **NOT** supported, use [File Uploader](../../file/file_uploader/file_uploader.md#file-uploader) instead. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **local_file_path** + : Local file path to read from + + **remote_file_path** + : Remote file path to create + + **replace** + : If `True`, existing file will be replaced +* **Returns:** + Remote file with stats. +* **Raises:** + `onetl.exception.NotAFileError` + : Remote or local path is not a file + + FileNotFoundError + : Local file does not exist + + FileExistsError + : Remote file already exists, and `replace=False` + + `onetl.exception.FileSizeMismatchError` + : Target file size after upload is different from source file size. + +### Examples + +```pycon +>>> remote_file = connection.upload( +... local_file_path="/path/to/source.csv", +... remote_file_path="/path/to/target.csv", +... ) +>>> os.fspath(remote_file) +'/path/to/target.csv' +>>> connection.path_exists("/path/to/target.csv") +True +>>> remote_file.stat().st_size # in bytes +1024 +>>> os.stat("/path/to/source.csv").st_size # same as source +1024 +``` + + diff --git a/mddocs/connection/file_connection/sftp.md b/mddocs/connection/file_connection/sftp.md new file mode 100644 index 000000000..1ee71d53c --- /dev/null +++ b/mddocs/connection/file_connection/sftp.md @@ -0,0 +1,635 @@ + + +# SFTP connection + +### *class* onetl.connection.file_connection.sftp.SFTP(\*, host: Host, port: int = 22, user: str | None = None, password: SecretStr | None = None, key_file: FilePath | None = None, timeout: int = 10, host_key_check: bool = False, compress: bool = True) + +SFTP file connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on [Paramiko library](https://pypi.org/project/paramiko/). + +#### WARNING +Since onETL v0.7.0 to use SFTP connector you should install package as follows: + +```bash +pip install onetl[s3] + +# or +pip install onetl[files] +``` + +See [File connections](../../install/files.md#install-files) installation instruction for more details. + +#### Versionadded +Added in version 0.1.0. + +* **Parameters:** + **host** + : Host of SFTP source. For example: `192.168.1.19` + + **port** + : Port of SFTP source + + **user** + : User, which have access to the file source. For example: `someuser` + + **password** + : Password for file source connection + + **key_file** + : the filename of optional private key(s) and/or certs to try for authentication + + **timeout** + : How long to wait for the server to send data before giving up + + **host_key_check** + : set to True to enable searching for discoverable private key files in `~/.ssh/` + + **compress** + : Set to True to turn on compression + +### Examples + +Create and check SFTP connection: + +```python +from onetl.connection import SFTP + +sftp = SFTP( + host="192.168.1.19", + user="someuser", + password="*****", +).check() +``` + + + +#### \_\_init_\_(\*\*kwargs) + + + +#### check() + +Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If not, an exception will be raised. + +* **Returns:** + Connection itself +* **Raises:** + RuntimeError + : If the connection is not available + +### Examples + +```python +connection.check() +``` + + + +#### create_dir(path: PathLike | str) → RemoteDirectory + +Creates directory tree on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Directory path +* **Returns:** + Created directory with stats +* **Raises:** + `onetl.exception.NotAFileError` + : Path is not a file + +### Examples + +```pycon +>>> dir_path = connection.create_dir("/path/to/dir") +>>> os.fspath(dir_path) +'/path/to/dir' +``` + + + +#### download_file(remote_file_path: PathLike | str, local_file_path: PathLike | str, replace: bool = True) → LocalPath + +Downloads file from the remote filesystem to a local path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +Supports only one file download per call. Directory download is **NOT** supported, use [File Downloader](../../file/file_downloader/file_downloader.md#file-downloader) instead. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **remote_file_path** + : Remote file path to read from + + **local_file_path** + : Local file path to create + + **replace** + : If `True`, existing file will be replaced +* **Returns:** + Local file with stats. +* **Raises:** + `onetl.exception.NotAFileError` + : Remote or local path is not a file + + FileNotFoundError + : Remote file does not exist + + FileExistsError + : Local file already exists, and `replace=False` + + `onetl.exception.FileSizeMismatchError` + : Target file size after download is different from source file size. + +### Examples + +```pycon +>>> local_file = connection.download_file( +... remote_file_path="/path/to/source.csv", +... local_file_path="/path/to/target.csv", +... ) +>>> os.fspath(local_file) +'/path/to/target.csv' +>>> local_file.exists() +True +>>> local_file.stat().st_size # in bytes +1024 +>>> connection.get_stat("/path/to/source.csv").st_size # same size +1024 +``` + + + +#### get_stat(path: PathLike | str) → PathStatProtocol + +Returns stats for a specific path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to get stats for +* **Returns:** + Stats object +* **Raises:** + Any underlying client exception + +### Examples + +```pycon +>>> stat = connection.get_stat("/path/to/file.csv") +>>> stat.st_size # in bytes +1024 +>>> stat.st_uid # owner id or name +12345 +``` + + + +#### is_dir(path: PathLike | str) → bool + +Check if specified path is a directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if path is a directory, `False` otherwise. +* **Raises:** + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + +### Examples + +```pycon +>>> connection.is_dir("/path/to/dir") +True +>>> connection.is_dir("/path/to/dir/file.csv") +False +``` + + + +#### is_file(path: PathLike | str) → bool + +Check if specified path is a file. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if path is a file, `False` otherwise. +* **Raises:** + FileNotFoundError + : Path does not exist + +### Examples + +```pycon +>>> connection.is_file("/path/to/dir/file.csv") +True +>>> connection.is_file("/path/to/dir") +False +``` + + + +#### list_dir(path: PathLike | str, filters: Iterable[[BaseFileFilter](../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → list[RemoteDirectory | RemoteFile] + +Return list of child files/directories in a specific directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Directory path to list contents. + + **filters** + : Return only files/directories matching these filters. See [File Filters](../../file/file_filters/index.md#file-filters) + + **limits** + : Apply limits to the list of files/directories, and stop if one of the limits is reached. + See [File Limits](../../file/file_limits/index.md#file-limits) +* **Returns:** + List of `onetl.base.PathWithStatsProtocol` +* **Raises:** + NotADirectoryError + : Path is not a directory + + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + +### Examples + +```pycon +>>> dir_content = connection.list_dir("/path/to/dir") +>>> os.fspath(dir_content[0]) +'file.csv' +>>> connection.path_exists("/path/to/dir/file.csv") +True +``` + + + +#### path_exists(path: PathLike | str) → bool + +Check if specified path exists on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html). + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if path exists, `False` otherwise + +### Examples + +```pycon +>>> connection.path_exists("/path/to/file.csv") +True +>>> connection.path_exists("/path/to/dir") +True +>>> connection.path_exists("/path/to/missing") +False +``` + + + +#### remove_dir(path: PathLike | str, recursive: bool = False) → bool + +Remove directory or directory tree. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If directory does not exist, no exception is raised. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Directory path to remote + + **recursive** + : If `True`, remove directory tree recursively. +* **Returns:** + `True` if directory was removed, `False` if directory does not exist in the first place. +* **Raises:** + NotADirectoryError + : Path is not a directory + +### Examples + +```pycon +>>> connection.remove_dir("/path/to/dir") +True +>>> connection.path_exists("/path/to/dir") +False +>>> connection.path_exists("/path/to/dir/file.csv") +False +>>> connection.remove_dir("/path/to/dir") # already deleted, no error +False +``` + + + +#### remove_file(path: PathLike | str) → bool + +Removes file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If file does not exist, no exception is raised. + +#### WARNING +Supports only one file removal per call. Directory removal is **NOT** supported, use [`remove_dir`](#onetl.connection.file_connection.sftp.SFTP.remove_dir) instead. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : File path +* **Returns:** + `True` if file was removed, `False` if file does not exist in the first place. +* **Raises:** + `onetl.exception.NotAFileError` + : Path is not a file + +### Examples + +```pycon +>>> connection.remove_file("/path/to/file.csv") +True +>>> connection.path_exists("/path/to/dir/file.csv") +False +>>> connection.remove_file("/path/to/file.csv") # already deleted, no error +False +``` + + + +#### rename_dir(source_dir_path: PathLike | str, target_dir_path: PathLike | str, replace: bool = False) → RemoteDirectory + +Rename or move dir on remote filesystem. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **source_dir_path** + : Old directory path + + **target_dir_path** + : New directory path + + **replace** + : If `True`, existing directory will be replaced. +* **Returns:** + New directory path with stats. +* **Raises:** + NotADirectoryError + : Path is not a directory + + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + + `onetl.exception.DirectoryExistsError` + : Directory already exists, and `replace=False` + +### Examples + +```pycon +>>> new_dir = connection.rename_dir("/path/to/dir1", "/path/to/dir2") +>>> os.fspath(new_dir) +'/path/to/dir2' +>>> connection.path_exists("/path/to/dir1") +False +>>> connection.path_exists("/path/to/dir2") +True +``` + + + +#### rename_file(source_file_path: PathLike | str, target_file_path: PathLike | str, replace: bool = False) → RemoteFile + +Rename or move file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +Supports only one file move per call. Directory move/rename is **NOT** supported. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **source_file_path** + : Old file path + + **target_file_path** + : New file path + + **replace** + : If `True`, existing file will be replaced. +* **Returns:** + New file path with stats. +* **Raises:** + `onetl.exception.NotAFileError` + : Source or target path is not a file + + FileNotFoundError + : File does not exist + + FileExistsError + : File already exists, and `replace=False` + +### Examples + +```pycon +>>> new_file = connection.rename_file("/path/to/file1.csv", "/path/to/file2.csv") +>>> os.fspath(new_file) +'/path/to/file2.csv' +>>> connection.path_exists("/path/to/file2.csv") +True +>>> connection.path_exists("/path/to/file1.csv") +False +``` + + + +#### resolve_dir(path: PathLike | str) → RemoteDirectory + +Returns directory at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to resolve +* **Returns:** + Directory path with stats +* **Raises:** + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + + NotADirectoryError + : Path is not a directory + +### Examples + +```pycon +>>> dir_path = connection.resolve_dir("/path/to/dir") +>>> os.fspath(dir_path) +'/path/to/dir' +>>> dir_path.stat().st_uid # owner id +12345 +``` + + + +#### resolve_file(path: PathLike | str) → RemoteFile + +Returns file at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to resolve +* **Returns:** + File path with stats +* **Raises:** + FileNotFoundError + : Path does not exist + + `onetl.exception.NotAFileError` + : Path is not a file + +### Examples + +```pycon +>>> file_path = connection.resolve_file("/path/to/dir/file.csv") +>>> os.fspath(file_path) +'/path/to/dir/file.csv' +>>> file_path.stat().st_uid # owner id +12345 +``` + + + +#### upload_file(local_file_path: PathLike | str, remote_file_path: PathLike | str, replace: bool = False) → RemoteFile + +Uploads local file to a remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +Supports only one file upload per call. Directory upload is **NOT** supported, use [File Uploader](../../file/file_uploader/file_uploader.md#file-uploader) instead. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **local_file_path** + : Local file path to read from + + **remote_file_path** + : Remote file path to create + + **replace** + : If `True`, existing file will be replaced +* **Returns:** + Remote file with stats. +* **Raises:** + `onetl.exception.NotAFileError` + : Remote or local path is not a file + + FileNotFoundError + : Local file does not exist + + FileExistsError + : Remote file already exists, and `replace=False` + + `onetl.exception.FileSizeMismatchError` + : Target file size after upload is different from source file size. + +### Examples + +```pycon +>>> remote_file = connection.upload( +... local_file_path="/path/to/source.csv", +... remote_file_path="/path/to/target.csv", +... ) +>>> os.fspath(remote_file) +'/path/to/target.csv' +>>> connection.path_exists("/path/to/target.csv") +True +>>> remote_file.stat().st_size # in bytes +1024 +>>> os.stat("/path/to/source.csv").st_size # same as source +1024 +``` + + + +#### walk(root: PathLike | str, topdown: bool = True, filters: Iterable[[BaseFileFilter](../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → Iterator[tuple[RemoteDirectory, list[RemoteDirectory], list[RemoteFile]]] + +Walk into directory tree, and iterate over its content in all nesting levels. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Just like `os.walk`, but with additional filter/limit logic. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **root** + : Directory path to walk into. + + **topdown** + : If `True`, walk in top-down order, otherwise walk in bottom-up order. + + **filters** + : Return only files/directories matching these filters. See [File Filters](../../file/file_filters/index.md#file-filters). + + **limits** + : Apply limits to the list of files/directories, and immediately stop if any of these limits is reached. + See [File Limits](../../file/file_limits/index.md#file-limits). +* **Returns:** + `Iterator[tuple[root, dirs, files]]`, like `os.walk`. + + But all the paths are not strings, instead path classes with embedded stats are returned. +* **Raises:** + NotADirectoryError + : Path is not a directory + + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + +### Examples + +```pycon +>>> for root, dirs, files in connection.walk("/path/to/dir"): +... break +>>> os.fspath(root) +'/path/to/dir' +>>> dirs +[] +>>> os.fspath(files[0]) +'file.csv' +>>> connection.path_exists("/path/to/dir/file.csv") +True +``` + + diff --git a/mddocs/connection/file_connection/webdav.md b/mddocs/connection/file_connection/webdav.md new file mode 100644 index 000000000..ef79f4023 --- /dev/null +++ b/mddocs/connection/file_connection/webdav.md @@ -0,0 +1,592 @@ + + +# WebDAV connection + +### *class* onetl.connection.file_connection.webdav.WebDAV(\*, host: Host, user: str, password: SecretStr, port: int | None = None, ssl_verify: bool | FilePath | DirectoryPath | SSLContext = True, protocol: Literal['http', 'https'] = 'https') + +WebDAV file connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on [WebdavClient3 library](https://pypi.org/project/webdavclient3/). + +#### WARNING +Since onETL v0.7.0 to use WebDAV connector you should install package as follows: + +```bash +pip install onetl[webdav] + +# or +pip install onetl[files] +``` + +See [File connections](../../install/files.md#install-files) installation instruction for more details. + +#### Versionadded +Added in version 0.6.0. + +* **Parameters:** + **host** + : Host of WebDAV source. For example: `webdav.domain.com` + + **user** + : User, which have access to the file source. For example: `someuser` + + **password** + : Password for file source connection + + **ssl_verify** + : SSL certificates used to verify the identity of requested hosts. Can be any of + : - `True` (uses default CA bundle), + - a path to an SSL certificate file, + - `False` (disable verification), or + - a `ssl.SSLContext` + + **protocol** + : Connection protocol. Allowed values: `https` or `http` + + **port** + : Connection port + +### Examples + +Create and check WebDAV connection: + +```python +from onetl.connection import WebDAV + +wd = WebDAV( + host="webdav.domain.com", + user="someuser", + password="*****", + protocol="https", +).check() +``` + + + +#### \_\_init_\_(\*\*kwargs) + + + +#### check() + +Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If not, an exception will be raised. + +* **Returns:** + Connection itself +* **Raises:** + RuntimeError + : If the connection is not available + +### Examples + +```python +connection.check() +``` + + + +#### create_dir(path: PathLike | str) → RemoteDirectory + +Creates directory tree on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Directory path +* **Returns:** + Created directory with stats +* **Raises:** + `onetl.exception.NotAFileError` + : Path is not a file + +### Examples + +```pycon +>>> dir_path = connection.create_dir("/path/to/dir") +>>> os.fspath(dir_path) +'/path/to/dir' +``` + + + +#### download_file(remote_file_path: PathLike | str, local_file_path: PathLike | str, replace: bool = True) → LocalPath + +Downloads file from the remote filesystem to a local path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +Supports only one file download per call. Directory download is **NOT** supported, use [File Downloader](../../file/file_downloader/file_downloader.md#file-downloader) instead. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **remote_file_path** + : Remote file path to read from + + **local_file_path** + : Local file path to create + + **replace** + : If `True`, existing file will be replaced +* **Returns:** + Local file with stats. +* **Raises:** + `onetl.exception.NotAFileError` + : Remote or local path is not a file + + FileNotFoundError + : Remote file does not exist + + FileExistsError + : Local file already exists, and `replace=False` + + `onetl.exception.FileSizeMismatchError` + : Target file size after download is different from source file size. + +### Examples + +```pycon +>>> local_file = connection.download_file( +... remote_file_path="/path/to/source.csv", +... local_file_path="/path/to/target.csv", +... ) +>>> os.fspath(local_file) +'/path/to/target.csv' +>>> local_file.exists() +True +>>> local_file.stat().st_size # in bytes +1024 +>>> connection.get_stat("/path/to/source.csv").st_size # same size +1024 +``` + + + +#### get_stat(path: PathLike | str) → PathStatProtocol + +Returns stats for a specific path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to get stats for +* **Returns:** + Stats object +* **Raises:** + Any underlying client exception + +### Examples + +```pycon +>>> stat = connection.get_stat("/path/to/file.csv") +>>> stat.st_size # in bytes +1024 +>>> stat.st_uid # owner id or name +12345 +``` + + + +#### is_dir(path: PathLike | str) → bool + +Check if specified path is a directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if path is a directory, `False` otherwise. +* **Raises:** + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + +### Examples + +```pycon +>>> connection.is_dir("/path/to/dir") +True +>>> connection.is_dir("/path/to/dir/file.csv") +False +``` + + + +#### is_file(path: PathLike | str) → bool + +Check if specified path is a file. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if path is a file, `False` otherwise. +* **Raises:** + FileNotFoundError + : Path does not exist + +### Examples + +```pycon +>>> connection.is_file("/path/to/dir/file.csv") +True +>>> connection.is_file("/path/to/dir") +False +``` + + + +#### list_dir(path: PathLike | str, filters: Iterable[[BaseFileFilter](../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → list[RemoteDirectory | RemoteFile] + +Return list of child files/directories in a specific directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Directory path to list contents. + + **filters** + : Return only files/directories matching these filters. See [File Filters](../../file/file_filters/index.md#file-filters) + + **limits** + : Apply limits to the list of files/directories, and stop if one of the limits is reached. + See [File Limits](../../file/file_limits/index.md#file-limits) +* **Returns:** + List of `onetl.base.PathWithStatsProtocol` +* **Raises:** + NotADirectoryError + : Path is not a directory + + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + +### Examples + +```pycon +>>> dir_content = connection.list_dir("/path/to/dir") +>>> os.fspath(dir_content[0]) +'file.csv' +>>> connection.path_exists("/path/to/dir/file.csv") +True +``` + + + +#### path_exists(path: PathLike | str) → bool + +Check if specified path exists on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html). + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if path exists, `False` otherwise + +### Examples + +```pycon +>>> connection.path_exists("/path/to/file.csv") +True +>>> connection.path_exists("/path/to/dir") +True +>>> connection.path_exists("/path/to/missing") +False +``` + + + +#### remove_dir(path: PathLike | str, recursive: bool = False) → bool + +Remove directory or directory tree. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If directory does not exist, no exception is raised. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Directory path to remote + + **recursive** + : If `True`, remove directory tree recursively. +* **Returns:** + `True` if directory was removed, `False` if directory does not exist in the first place. +* **Raises:** + NotADirectoryError + : Path is not a directory + +### Examples + +```pycon +>>> connection.remove_dir("/path/to/dir") +True +>>> connection.path_exists("/path/to/dir") +False +>>> connection.path_exists("/path/to/dir/file.csv") +False +>>> connection.remove_dir("/path/to/dir") # already deleted, no error +False +``` + + + +#### remove_file(path: PathLike | str) → bool + +Removes file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If file does not exist, no exception is raised. + +#### WARNING +Supports only one file removal per call. Directory removal is **NOT** supported, use [`remove_dir`](#onetl.connection.file_connection.webdav.WebDAV.remove_dir) instead. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : File path +* **Returns:** + `True` if file was removed, `False` if file does not exist in the first place. +* **Raises:** + `onetl.exception.NotAFileError` + : Path is not a file + +### Examples + +```pycon +>>> connection.remove_file("/path/to/file.csv") +True +>>> connection.path_exists("/path/to/dir/file.csv") +False +>>> connection.remove_file("/path/to/file.csv") # already deleted, no error +False +``` + + + +#### rename_file(source_file_path: PathLike | str, target_file_path: PathLike | str, replace: bool = False) → RemoteFile + +Rename or move file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +Supports only one file move per call. Directory move/rename is **NOT** supported. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **source_file_path** + : Old file path + + **target_file_path** + : New file path + + **replace** + : If `True`, existing file will be replaced. +* **Returns:** + New file path with stats. +* **Raises:** + `onetl.exception.NotAFileError` + : Source or target path is not a file + + FileNotFoundError + : File does not exist + + FileExistsError + : File already exists, and `replace=False` + +### Examples + +```pycon +>>> new_file = connection.rename_file("/path/to/file1.csv", "/path/to/file2.csv") +>>> os.fspath(new_file) +'/path/to/file2.csv' +>>> connection.path_exists("/path/to/file2.csv") +True +>>> connection.path_exists("/path/to/file1.csv") +False +``` + + + +#### resolve_dir(path: PathLike | str) → RemoteDirectory + +Returns directory at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to resolve +* **Returns:** + Directory path with stats +* **Raises:** + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + + NotADirectoryError + : Path is not a directory + +### Examples + +```pycon +>>> dir_path = connection.resolve_dir("/path/to/dir") +>>> os.fspath(dir_path) +'/path/to/dir' +>>> dir_path.stat().st_uid # owner id +12345 +``` + + + +#### resolve_file(path: PathLike | str) → RemoteFile + +Returns file at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to resolve +* **Returns:** + File path with stats +* **Raises:** + FileNotFoundError + : Path does not exist + + `onetl.exception.NotAFileError` + : Path is not a file + +### Examples + +```pycon +>>> file_path = connection.resolve_file("/path/to/dir/file.csv") +>>> os.fspath(file_path) +'/path/to/dir/file.csv' +>>> file_path.stat().st_uid # owner id +12345 +``` + + + +#### upload_file(local_file_path: PathLike | str, remote_file_path: PathLike | str, replace: bool = False) → RemoteFile + +Uploads local file to a remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +Supports only one file upload per call. Directory upload is **NOT** supported, use [File Uploader](../../file/file_uploader/file_uploader.md#file-uploader) instead. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **local_file_path** + : Local file path to read from + + **remote_file_path** + : Remote file path to create + + **replace** + : If `True`, existing file will be replaced +* **Returns:** + Remote file with stats. +* **Raises:** + `onetl.exception.NotAFileError` + : Remote or local path is not a file + + FileNotFoundError + : Local file does not exist + + FileExistsError + : Remote file already exists, and `replace=False` + + `onetl.exception.FileSizeMismatchError` + : Target file size after upload is different from source file size. + +### Examples + +```pycon +>>> remote_file = connection.upload( +... local_file_path="/path/to/source.csv", +... remote_file_path="/path/to/target.csv", +... ) +>>> os.fspath(remote_file) +'/path/to/target.csv' +>>> connection.path_exists("/path/to/target.csv") +True +>>> remote_file.stat().st_size # in bytes +1024 +>>> os.stat("/path/to/source.csv").st_size # same as source +1024 +``` + + + +#### walk(root: PathLike | str, topdown: bool = True, filters: Iterable[[BaseFileFilter](../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → Iterator[tuple[RemoteDirectory, list[RemoteDirectory], list[RemoteFile]]] + +Walk into directory tree, and iterate over its content in all nesting levels. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Just like `os.walk`, but with additional filter/limit logic. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **root** + : Directory path to walk into. + + **topdown** + : If `True`, walk in top-down order, otherwise walk in bottom-up order. + + **filters** + : Return only files/directories matching these filters. See [File Filters](../../file/file_filters/index.md#file-filters). + + **limits** + : Apply limits to the list of files/directories, and immediately stop if any of these limits is reached. + See [File Limits](../../file/file_limits/index.md#file-limits). +* **Returns:** + `Iterator[tuple[root, dirs, files]]`, like `os.walk`. + + But all the paths are not strings, instead path classes with embedded stats are returned. +* **Raises:** + NotADirectoryError + : Path is not a directory + + `onetl.exception.DirectoryNotFoundError` + : Path does not exist + +### Examples + +```pycon +>>> for root, dirs, files in connection.walk("/path/to/dir"): +... break +>>> os.fspath(root) +'/path/to/dir' +>>> dirs +[] +>>> os.fspath(files[0]) +'file.csv' +>>> connection.path_exists("/path/to/dir/file.csv") +True +``` + + diff --git a/mddocs/connection/file_df_connection/base.md b/mddocs/connection/file_df_connection/base.md new file mode 100644 index 000000000..8f58267d8 --- /dev/null +++ b/mddocs/connection/file_df_connection/base.md @@ -0,0 +1,63 @@ + + +# Base interface + +### *class* onetl.base.base_file_df_connection.BaseFileDFConnection + +Implements generic methods for reading and writing dataframe as files. + +#### Versionadded +Added in version 0.9.0. + + + +#### *abstract* check() → T + +Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If not, an exception will be raised. + +* **Returns:** + Connection itself +* **Raises:** + RuntimeError + : If the connection is not available + +### Examples + +```python +connection.check() +``` + + + +#### *abstract* check_if_format_supported(format: [BaseReadableFileFormat](../../file_df/file_formats/base.md#onetl.base.base_file_format.BaseReadableFileFormat) | [BaseWritableFileFormat](../../file_df/file_formats/base.md#onetl.base.base_file_format.BaseWritableFileFormat)) → None + +Validate if specific file format is supported. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.9.0. + +* **Raises:** + RuntimeError + : If file format is not supported. + + + +#### *abstract* read_files_as_df(paths: list[PurePathProtocol], format: [BaseReadableFileFormat](../../file_df/file_formats/base.md#onetl.base.base_file_format.BaseReadableFileFormat), root: PurePathProtocol | None = None, df_schema: StructType | None = None, options: FileDFReadOptions | None = None) → DataFrame + +Read files in some paths list as dataframe. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.9.0. + + + +#### *abstract* write_df_as_files(df: DataFrame, path: PurePathProtocol, format: [BaseWritableFileFormat](../../file_df/file_formats/base.md#onetl.base.base_file_format.BaseWritableFileFormat), options: FileDFWriteOptions | None = None) → None + +Write dataframe as files in some path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.9.0. + + diff --git a/mddocs/connection/file_df_connection/index.md b/mddocs/connection/file_df_connection/index.md new file mode 100644 index 000000000..ef8d51549 --- /dev/null +++ b/mddocs/connection/file_df_connection/index.md @@ -0,0 +1,20 @@ + + +# File DataFrame Connections + +# File DataFrame Connections + +* [Spark LocalFS](spark_local_fs.md) + * [`SparkLocalFS`](spark_local_fs.md#onetl.connection.file_df_connection.spark_local_fs.SparkLocalFS) +* [Spark HDFS](spark_hdfs/index.md) + * [Prerequisites](spark_hdfs/prerequisites.md) + * [Connection](spark_hdfs/connection.md) + * [Slots](spark_hdfs/slots.md) +* [Spark S3](spark_s3/index.md) + * [Prerequisites](spark_s3/prerequisites.md) + * [Connection](spark_s3/connection.md) + * [Troubleshooting](spark_s3/troubleshooting.md) + +# For developers + +* [Base interface](base.md) diff --git a/mddocs/connection/file_df_connection/spark_hdfs/connection.md b/mddocs/connection/file_df_connection/spark_hdfs/connection.md new file mode 100644 index 000000000..4f2364e0e --- /dev/null +++ b/mddocs/connection/file_df_connection/spark_hdfs/connection.md @@ -0,0 +1,169 @@ + + +# Spark HDFS Connection + +### *class* onetl.connection.file_df_connection.spark_hdfs.connection.SparkHDFS(\*, spark: SparkSession, cluster: Cluster, host: Host | None = None, port: int = 8020) + +Spark connection to HDFS. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on [Spark Generic File Data Source](https://spark.apache.org/docs/latest/sql-data-sources-generic-options.html). + +#### SEE ALSO +Before using this connector please take into account [Prerequisites](prerequisites.md#spark-hdfs-prerequisites) + +#### NOTE +Supports only reading files as Spark DataFrame and writing DataFrame to files. + +Does NOT support file operations, like create, delete, rename, etc. For these operations, +use `HDFS` connection. + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **cluster** + : Cluster name. +
+ Used for: + : * HWM and lineage (as instance name for file paths) + * Validation of `host` value, + : if latter is passed and if some hooks are bound to + [`Slots.get_cluster_namenodes`](slots.md#onetl.connection.file_df_connection.spark_hdfs.slots.SparkHDFSSlots.get_cluster_namenodes). + + **host** + : Hadoop namenode host. For example: `namenode1.domain.com`. +
+ Should be an active namenode (NOT standby). +
+ If value is not set, but there are some hooks bound to + [`Slots.get_cluster_namenodes`](slots.md#onetl.connection.file_df_connection.spark_hdfs.slots.SparkHDFSSlots.get_cluster_namenodes) + and + [`Slots.is_namenode_active`](slots.md#onetl.connection.file_df_connection.spark_hdfs.slots.SparkHDFSSlots.is_namenode_active), + onETL will iterate over cluster namenodes to detect which one is active. + + **ipc_port** + : Port of Hadoop namenode (IPC protocol). +
+ If omitted, but there are some hooks bound to + [`Slots.get_ipc_port`](slots.md#onetl.connection.file_df_connection.spark_hdfs.slots.SparkHDFSSlots.get_ipc_port), + onETL will try to detect port number for a specific `cluster`. + + **spark** + : Spark session + +### Examples + +Create SparkHDFS connection with Kerberos auth + +Execute `kinit` consome command before creating Spark Session + +```bash +$ kinit -kt /path/to/keytab user +``` + +```python +from onetl.connection import SparkHDFS +from pyspark.sql import SparkSession + +# Create Spark session. +# Use names "spark.yarn.access.hadoopFileSystems", "spark.yarn.principal" +# and "spark.yarn.keytab" for Spark 2 + +spark = ( + SparkSession.builder.appName("spark-app-name") + .option( + "spark.kerberos.access.hadoopFileSystems", + "hdfs://namenode1.domain.com:8020", + ) + .option("spark.kerberos.principal", "user") + .option("spark.kerberos.keytab", "/path/to/keytab") + .enableHiveSupport() + .getOrCreate() +) + +# Create connection +hdfs = SparkHDFS( + host="namenode1.domain.com", + cluster="rnd-dwh", + spark=spark, +).check() +``` + +Create SparkHDFS connection with anonymous auth + +```py +from onetl.connection import SparkHDFS +from pyspark.sql import SparkSession + +# Create Spark session +spark = SparkSession.builder.master("local").appName("spark-app-name").getOrCreate() + +# Create connection +hdfs = SparkHDFS( + host="namenode1.domain.com", + cluster="rnd-dwh", + spark=spark, +).check() +``` + +Use cluster name to detect active namenode + +Can be used only if some third-party plugin provides [Spark HDFS Slots](slots.md#spark-hdfs-slots) implementation + +```python +# Create Spark session +... + +# Create connection +hdfs = SparkHDFS(cluster="rnd-dwh", spark=spark).check() +``` + + + +#### check() + +Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If not, an exception will be raised. + +* **Returns:** + Connection itself +* **Raises:** + RuntimeError + : If the connection is not available + +### Examples + +```python +connection.check() +``` + + + +#### *classmethod* get_current(spark: SparkSession) + +Create connection for current cluster. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Automatically sets up current cluster name as `cluster`. + +#### NOTE +Can be used only if there are a some hooks bound to +[`Slots.get_current_cluster`](slots.md#onetl.connection.file_df_connection.spark_hdfs.slots.SparkHDFSSlots.get_current_cluster). + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **spark** + : See [`SparkHDFS`](#onetl.connection.file_df_connection.spark_hdfs.connection.SparkHDFS) constructor documentation. + +### Examples + +```python +from onetl.connection import SparkHDFS + +# injecting current cluster name via hooks mechanism +hdfs = SparkHDFS.get_current(spark=spark) +``` + + diff --git a/mddocs/connection/file_df_connection/spark_hdfs/index.md b/mddocs/connection/file_df_connection/spark_hdfs/index.md new file mode 100644 index 000000000..d5eae4c27 --- /dev/null +++ b/mddocs/connection/file_df_connection/spark_hdfs/index.md @@ -0,0 +1,12 @@ + + +# Spark HDFS + +# Connection + +* [Prerequisites](prerequisites.md) +* [Connection](connection.md) + +# For developers + +* [Slots](slots.md) diff --git a/mddocs/connection/file_df_connection/spark_hdfs/prerequisites.md b/mddocs/connection/file_df_connection/spark_hdfs/prerequisites.md new file mode 100644 index 000000000..2011410da --- /dev/null +++ b/mddocs/connection/file_df_connection/spark_hdfs/prerequisites.md @@ -0,0 +1,46 @@ + + +# Prerequisites + +## Version Compatibility + +* Hadoop versions: 2.x, 3.x (only with Hadoop 3.x libraries) +* Spark versions: 2.3.x - 3.5.x +* Java versions: 8 - 20 + +## Installing PySpark + +To use SparkHDFS connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Using Kerberos + +Some of Hadoop managed clusters use Kerberos authentication. In this case, you should call [kinit](https://web.mit.edu/kerberos/krb5-1.12/doc/user/user_commands/kinit.html) command +**BEFORE** starting Spark session to generate Kerberos ticket. See [Kerberos support](../../../install/kerberos.md#install-kerberos). + +Sometimes it is also required to pass keytab file to Spark config, allowing Spark executors to generate own Kerberos tickets: + +Spark 3 + +```python +SparkSession.builder + .option("spark.kerberos.access.hadoopFileSystems", "hdfs://namenode1.domain.com:9820,hdfs://namenode2.domain.com:9820") + .option("spark.kerberos.principal", "user") + .option("spark.kerberos.keytab", "/path/to/keytab") + .gerOrCreate() +``` + +Spark 2 + +```python +SparkSession.builder + .option("spark.yarn.access.hadoopFileSystems", "hdfs://namenode1.domain.com:9820,hdfs://namenode2.domain.com:9820") + .option("spark.yarn.principal", "user") + .option("spark.yarn.keytab", "/path/to/keytab") + .gerOrCreate() +``` + +See [Spark security documentation](https://spark.apache.org/docs/latest/security.html#kerberos) +for more details. diff --git a/mddocs/connection/file_df_connection/spark_hdfs/slots.md b/mddocs/connection/file_df_connection/spark_hdfs/slots.md new file mode 100644 index 000000000..a94ddcde0 --- /dev/null +++ b/mddocs/connection/file_df_connection/spark_hdfs/slots.md @@ -0,0 +1,256 @@ + + +# Spark HDFS Slots + +### *class* onetl.connection.file_df_connection.spark_hdfs.slots.SparkHDFSSlots + +Spark HDFS slots that could be implemented by third-party plugins. + +#### Versionadded +Added in version 0.9.0. + + + +#### *static* normalize_cluster_name(cluster: str) → str | None + +Normalize cluster name passed into SparkHDFS constructor. + +If hooks didn’t return anything, cluster name is left intact. + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **cluster** + : Cluster name +* **Returns:** + str | None + : Normalized cluster name. +
+ If hook cannot be applied to a specific cluster, it should return `None`. + +### Examples + +```python +from onetl.connection import SparkHDFS +from onetl.hooks import hook + +@SparkHDFS.Slots.normalize_cluster_name.bind +@hook +def normalize_cluster_name(cluster: str) -> str: + return cluster.lower() +``` + + + +#### *static* normalize_namenode_host(host: str, cluster: str) → str | None + +Normalize namenode host passed into SparkHDFS constructor. + +If hooks didn’t return anything, host is left intact. + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **host** + : Namenode host (raw) + + **cluster** + : Cluster name (normalized) +* **Returns:** + str | None + : Normalized namenode host name. +
+ If hook cannot be applied to a specific host name, it should return `None`. + +### Examples + +```python +from onetl.connection import SparkHDFS +from onetl.hooks import hook + +@SparkHDFS.Slots.normalize_namenode_host.bind +@hook +def normalize_namenode_host(host: str, cluster: str) -> str | None: + if cluster == "rnd-dwh": + if not host.endswith(".domain.com"): + # fix missing domain name + host += ".domain.com" + return host + + return None +``` + + + +#### *static* get_known_clusters() → set[str] | None + +Return collection of known clusters. + +Cluster passed into SparkHDFS constructor should be present in this list. +If hooks didn’t return anything, no validation will be performed. + +#### Versionadded +Added in version 0.9.0. + +* **Returns:** + set[str] | None + : Collection of cluster names (in normalized form). +
+ If hook cannot be applied, it should return `None`. + +### Examples + +```python +from onetl.connection import SparkHDFS +from onetl.hooks import hook + +@SparkHDFS.Slots.get_known_clusters.bind +@hook +def get_known_clusters() -> str[str]: + return {"rnd-dwh", "rnd-prod"} +``` + + + +#### *static* get_cluster_namenodes(cluster: str) → set[str] | None + +Return collection of known namenodes for the cluster. + +Namenode host passed into SparkHDFS constructor should be present in this list. +If hooks didn’t return anything, no validation will be performed. + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **cluster** + : Cluster name (normalized) +* **Returns:** + set[str] | None + : Collection of host names (in normalized form). +
+ If hook cannot be applied, it should return `None`. + +### Examples + +```python +from onetl.connection import SparkHDFS +from onetl.hooks import hook + +@SparkHDFS.Slots.get_cluster_namenodes.bind +@hook +def get_cluster_namenodes(cluster: str) -> str[str] | None: + if cluster == "rnd-dwh": + return {"namenode1.domain.com", "namenode2.domain.com"} + return None +``` + + + +#### *static* get_current_cluster() → str | None + +Get current cluster name. + +Used in [`get_current_cluster`](#onetl.connection.file_df_connection.spark_hdfs.slots.SparkHDFSSlots.get_current_cluster) to automatically fill up `cluster` attribute of a connection. +If hooks didn’t return anything, calling the method above will raise an exception. + +#### Versionadded +Added in version 0.9.0. + +* **Returns:** + str | None + : Current cluster name (in normalized form). +
+ If hook cannot be applied, it should return `None`. + +### Examples + +```python +from onetl.connection import SparkHDFS +from onetl.hooks import hook + +@SparkHDFS.Slots.get_current_cluster.bind +@hook +def get_current_cluster() -> str: + # some magic here + return "rnd-dwh" +``` + + + +#### *static* get_ipc_port(cluster: str) → int | None + +Get IPC port number for a specific cluster. + +Used by constructor to automatically set port number if omitted. + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **cluster** + : Cluster name (normalized) +* **Returns:** + int | None + : IPC port number. +
+ If hook cannot be applied, it should return `None`. + +### Examples + +```python +from onetl.connection import SparkHDFS +from onetl.hooks import hook + +@SparkHDFS.Slots.get_ipc_port.bind +@hook +def get_ipc_port(cluster: str) -> int | None: + if cluster == "rnd-dwh": + return 8020 # Cloudera + return None +``` + + + +#### *static* is_namenode_active(host: str, cluster: str) → bool | None + +Check whether a namenode of a specified cluster is active (=not standby) or not. + +Used for: +: * If SparkHDFS connection is created without `host` + > Connector will iterate over [`get_cluster_namenodes`](#onetl.connection.file_df_connection.spark_hdfs.slots.SparkHDFSSlots.get_cluster_namenodes) of a cluster to get active namenode, + > and then use it instead of `host` attribute. + * If SparkHDFS connection is created with `host` + > `check` will determine whether this host is active. + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **host** + : Namenode host (normalized) + + **cluster** + : Cluster name (normalized) +* **Returns:** + bool | None + : `True` if namenode is active, `False` if not. +
+ If hook cannot be applied, it should return `None`. + +### Examples + +```python +from onetl.connection import SparkHDFS +from onetl.hooks import hook + +@SparkHDFS.Slots.is_namenode_active.bind +@hook +def is_namenode_active(host: str, cluster: str) -> bool: + # some magic here + return True +``` + + diff --git a/mddocs/connection/file_df_connection/spark_local_fs.md b/mddocs/connection/file_df_connection/spark_local_fs.md new file mode 100644 index 000000000..1620f08bc --- /dev/null +++ b/mddocs/connection/file_df_connection/spark_local_fs.md @@ -0,0 +1,65 @@ + + +# Spark LocalFS + +### *class* onetl.connection.file_df_connection.spark_local_fs.SparkLocalFS(\*, spark: SparkSession) + +Spark connection to local filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on [Spark Generic File Data Source](https://spark.apache.org/docs/latest/sql-data-sources-generic-options.html). + +#### WARNING +To use SparkHDFS connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../install/spark.md#install-spark) installation instruction for more details. + +#### WARNING +Currently supports only Spark sessions created with option `spark.master: local`. + +#### NOTE +Supports only reading files as Spark DataFrame and writing DataFrame to files. + +Does NOT support file operations, like create, delete, rename, etc. + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **spark** + : Spark session + +### Examples + +```python +from onetl.connection import SparkLocalFS +from pyspark.sql import SparkSession + +# create Spark session +spark = SparkSession.builder.master("local").appName("spark-app-name").getOrCreate() + +# create connection +local_fs = SparkLocalFS(spark=spark).check() +``` + + + +#### check() + +Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If not, an exception will be raised. + +* **Returns:** + Connection itself +* **Raises:** + RuntimeError + : If the connection is not available + +### Examples + +```python +connection.check() +``` + + diff --git a/mddocs/connection/file_df_connection/spark_s3/connection.md b/mddocs/connection/file_df_connection/spark_s3/connection.md new file mode 100644 index 000000000..5b2c1db31 --- /dev/null +++ b/mddocs/connection/file_df_connection/spark_s3/connection.md @@ -0,0 +1,232 @@ + + +# Spark S3 Connection + +### *class* onetl.connection.file_df_connection.spark_s3.connection.SparkS3(\*, spark: SparkSession, host: Host, port: int | None = None, bucket: str, protocol: Literal['http', 'https'] = 'https', access_key: str | None = None, secret_key: SecretStr | None = None, session_token: SecretStr | None = None, region: str | None = None, extra: SparkS3Extra = SparkS3Extra()) + +Spark connection to S3 filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on [Hadoop-AWS module](https://hadoop.apache.org/docs/current3/hadoop-aws/tools/hadoop-aws/index.html) +and [Spark integration with Cloud Infrastructures](https://spark.apache.org/docs/latest/cloud-integration.html). + +#### SEE ALSO +Before using this connector please take into account [Prerequisites](prerequisites.md#spark-s3-prerequisites) + +#### NOTE +Supports only reading files as Spark DataFrame and writing DataFrame to files. + +Does NOT support file operations, like create, delete, rename, etc. For these operations, +use `S3` connection. + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **host** + : Host of S3 source. For example: `domain.com` + + **port** + : Port of S3 source + + **bucket** + : Bucket name in the S3 file source + + **protocol** + : Connection protocol. Allowed values: `https` or `http` + + **access_key** + : Access key (aka user ID) of an account in the S3 service + + **secret_key** + : Secret key (aka password) of an account in the S3 service + + **session_token** + : Session token of your account in S3 service + + **region** + : Region name of bucket in S3 service + + **extra** + : A dictionary of additional properties to be used when connecting to S3. +
+ These are Hadoop AWS specific properties, see links below: + * [Hadoop AWS](https://hadoop.apache.org/docs/current/hadoop-aws/tools/hadoop-aws/index.html#General_S3A_Client_configuration) + * [Hadoop AWS committers options](https://hadoop.apache.org/docs/current/hadoop-aws/tools/hadoop-aws/committers.html) +
+ Options are passed without prefixes `spark.hadoop.`, `fs.s3a.` and `fs.s3a.bucket.$BUCKET.`, for example: + ```python + extra = { + "path.style.access": True, + "committer.magic.enabled": True, + "committer.name": "magic", + "connection.timeout": 300000, + } + ``` +
+ #### WARNING + Options that populated from connection + attributes (like `endpoint`, `access.key`) are not allowed to override. +
+ But you may override `aws.credentials.provider` and pass custom credential options. + + **spark** + : Spark session + +### Examples + +Create S3 connection with bucket as subdomain (`my-bucket.domain.com`): + +```py +from onetl.connection import SparkS3 +from pyspark.sql import SparkSession + +# Create Spark session with Hadoop AWS libraries loaded +maven_packages = SparkS3.get_packages(spark_version="3.5.5") +# Some packages are not used, but downloading takes a lot of time. Skipping them. +excluded_packages = SparkS3.get_exclude_packages() +spark = ( + SparkSession.builder.appName("spark-app-name") + .config("spark.jars.packages", ",".join(maven_packages)) + .config("spark.jars.excludes", ",".join(excluded_packages)) + .config("spark.hadoop.fs.s3a.committer.magic.enabled", "true") + .config("spark.hadoop.fs.s3a.committer.name", "magic") + .config( + "spark.hadoop.mapreduce.outputcommitter.factory.scheme.s3a", + "org.apache.hadoop.fs.s3a.commit.S3ACommitterFactory", + ) + .config( + "spark.sql.parquet.output.committer.class", + "org.apache.spark.internal.io.cloud.BindingParquetOutputCommitter", + ) + .config( + "spark.sql.sources.commitProtocolClass", + "org.apache.spark.internal.io.cloud.PathOutputCommitProtocol", + ) + .getOrCreate() +) + +# Create connection +s3 = SparkS3( + host="domain.com", + protocol="http", + bucket="my-bucket", + access_key="ACCESS_KEY", + secret_key="SECRET_KEY", + spark=spark, +).check() +``` + +Create S3 connection with bucket as subpath (`domain.com/my-bucket`) + +```py +# Create Spark session with Hadoop AWS libraries loaded +... + +# Create connection +s3 = SparkS3( + host="domain.com", + protocol="http", + bucket="my-bucket", + access_key="ACCESS_KEY", + secret_key="SECRET_KEY", + extra={ + "path.style.access": True, # <--- + }, + spark=spark, +).check() +``` + + + +#### check() + +Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +If not, an exception will be raised. + +* **Returns:** + Connection itself +* **Raises:** + RuntimeError + : If the connection is not available + +### Examples + +```python +connection.check() +``` + + + +#### close() + +Close all connections created to S3. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Also resets all `fs.s3a.bucket.$BUCKET.*` properties of Hadoop configuration. + +#### NOTE +Connection can be used again after it was closed. + +* **Returns:** + Connection itself + +### Examples + +Close connection automatically: + +```python +with connection: + ... +``` + +Close connection manually: + +```python +connection.close() +``` + + + +#### *classmethod* get_exclude_packages() → list[str] + +Get package names to be excluded by Spark. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.13.0. + +### Examples + +```python +from onetl.connection import SparkS3 + +SparkS3.get_exclude_packages() +``` + + + +#### *classmethod* get_packages(spark_version: str, scala_version: str | None = None) → list[str] + +Get package names to be downloaded by Spark. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **spark_version** + : Spark version in format `major.minor.patch`. + + **scala_version** + : Scala version in format `major.minor`. +
+ If `None`, `spark_version` is used to determine Scala version. + +### Examples + +```python +from onetl.connection import SparkS3 + +SparkS3.get_packages(spark_version="3.5.5") +SparkS3.get_packages(spark_version="3.5.5", scala_version="2.12") +``` + + diff --git a/mddocs/connection/file_df_connection/spark_s3/index.md b/mddocs/connection/file_df_connection/spark_s3/index.md new file mode 100644 index 000000000..be66a0adc --- /dev/null +++ b/mddocs/connection/file_df_connection/spark_s3/index.md @@ -0,0 +1,9 @@ + + +# Spark S3 + +# Connection + +* [Prerequisites](prerequisites.md) +* [Connection](connection.md) +* [Troubleshooting](troubleshooting.md) diff --git a/mddocs/connection/file_df_connection/spark_s3/prerequisites.md b/mddocs/connection/file_df_connection/spark_s3/prerequisites.md new file mode 100644 index 000000000..1bfd0846a --- /dev/null +++ b/mddocs/connection/file_df_connection/spark_s3/prerequisites.md @@ -0,0 +1,61 @@ + + +# Prerequisites + +## Version Compatibility + +* Spark versions: 3.2.x - 3.5.x (only with Hadoop 3.x libraries) +* Java versions: 8 - 20 + +## Installing PySpark + +To use SparkS3 connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Connecting to S3 + +### Bucket access style + +AWS and some other S3 cloud providers allows bucket access using domain style only, e.g. `https://mybucket.s3provider.com`. + +Other implementations, like Minio, by default allows path style access only, e.g. `https://s3provider.com/mybucket` +(see [MINIO_DOMAIN](https://min.io/docs/minio/linux/reference/minio-server/minio-server.html#envvar.MINIO_DOMAIN)). + +You should set `path.style.access` to `True` or `False`, to choose the preferred style. + +### Authentication + +Different S3 instances can use different authentication methods, like: +: * `access_key + secret_key` (or username + password) + * `access_key + secret_key + session_token` + +Usually these are just passed to SparkS3 constructor: + +```python +SparkS3( + access_key=..., + secret_key=..., + session_token=..., +) +``` + +But some S3 cloud providers, like AWS, may require custom credential providers. You can pass them like: + +```python +SparkS3( + extra={ + # provider class + "aws.credentials.provider": "org.apache.hadoop.fs.s3a.auth.AssumedRoleCredentialProvider", + # other options, if needed + "assumed.role.arn": "arn:aws:iam::90066806600238:role/s3-restricted", + }, +) +``` + +See [Hadoop-AWS](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/index.html#Changing_Authentication_Providers) documentation. + +## Troubleshooting + +See [Spark S3 Troubleshooting](troubleshooting.md#spark-s3-troubleshooting). diff --git a/mddocs/connection/file_df_connection/spark_s3/troubleshooting.md b/mddocs/connection/file_df_connection/spark_s3/troubleshooting.md new file mode 100644 index 000000000..32ea15667 --- /dev/null +++ b/mddocs/connection/file_df_connection/spark_s3/troubleshooting.md @@ -0,0 +1,366 @@ + + +# Spark S3 Troubleshooting + +#### NOTE +General guide: [Troubleshooting](../../../troubleshooting/index.md#troubleshooting). + +More details: + +* [Hadoop AWS Troubleshooting Guide](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/troubleshooting_s3a.html) +* [Hadoop AWS Performance Guide](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/performance.html) +* [Spark integration with Cloud Infrastructures](https://spark.apache.org/docs/latest/cloud-integration.html) + +## `SparkS3.check()` and other methods hang + +### Details + +S3 may not respond for connection attempts for a long time if it’s under heavy load. +To handle this, Hadoop AWS library has retry mechanism. By default it retries 7 times with 500ms interval. + +Hadoop AWS is based on AWS SDK library, which also has retry mechanism. This mechanism is not disabled because it handles different +errors than Hadoop AWS, so they complement each other. Default number of attempts in AWS SDK is 20 with minimal 5s interval, +which is exponentially increasing with each failed attempt. + +It is not a problem if S3 source is not accessible at all, like hostname cannot be resolved, or port is not opened. +These errors are not recoverable, and retry mechanism is not activated. + +But errors like SSL issues, are considered recoverable, and this causing retry of retry over increasing interval. +So user is waiting for [almost 15 minutes](https://issues.apache.org/jira/browse/HADOOP-18839) just to get exception message. + +### How to determine reason + +#### Make logging more verbose + +Change Spark session log level to [DEBUG](../../../troubleshooting/spark.md#troubleshooting-spark) to print result of each attempt. +Resulting logs will look like this + +### See log + +```text +23/08/03 11:25:10 DEBUG S3AFileSystem: Using S3ABlockOutputStream with buffer = disk; block=67108864; queue limit=4 +23/08/03 11:25:10 DEBUG S3Guard: Metastore option source [core-default.xml] +23/08/03 11:25:10 DEBUG S3Guard: Using NullMetadataStore metadata store for s3a filesystem +23/08/03 11:25:10 DEBUG S3AFileSystem: S3Guard is disabled on this bucket: test-bucket +23/08/03 11:25:10 DEBUG DirectoryPolicyImpl: Directory markers will be deleted +23/08/03 11:25:10 DEBUG S3AFileSystem: Directory marker retention policy is DirectoryMarkerRetention{policy='delete'} +23/08/03 11:25:10 DEBUG S3AUtils: Value of fs.s3a.multipart.purge.age is 86400 +23/08/03 11:25:10 DEBUG S3AUtils: Value of fs.s3a.bulk.delete.page.size is 250 +23/08/03 11:25:10 DEBUG FileSystem: Creating FS s3a://test-bucket/fake: duration 0:01.029s +23/08/03 11:25:10 DEBUG IOStatisticsStoreImpl: Incrementing counter op_is_directory by 1 with final value 1 +23/08/03 11:25:10 DEBUG S3AFileSystem: Getting path status for s3a://test-bucket/fake (fake); needEmptyDirectory=false +23/08/03 11:25:10 DEBUG S3AFileSystem: S3GetFileStatus s3a://test-bucket/fake +23/08/03 11:25:10 DEBUG S3AFileSystem: LIST List test-bucket:/fake/ delimiter=/ keys=2 requester pays=false +23/08/03 11:25:10 DEBUG S3AFileSystem: Starting: LIST +23/08/03 11:25:10 DEBUG IOStatisticsStoreImpl: Incrementing counter object_list_request by 1 with final value 1 +23/08/03 11:25:10 DEBUG AWSCredentialProviderList: Using credentials from SimpleAWSCredentialsProvider +23/08/03 11:25:10 DEBUG request: Sending Request: GET https://test-bucket.localhost:9000 / Parameters: ({"list-type":["2"],"delimiter":["/"],"max-keys":["2"],"prefix":["fake/"],"fetch-owner":["false"]}Headers: (amz-sdk-invocation-id: e6d62603-96e4-a80f-10a1-816e0822bc71, Content-Type: application/octet-stream, User-Agent: Hadoop 3.3.4, aws-sdk-java/1.12.262 Linux/6.4.7-1-MANJARO OpenJDK_64-Bit_Server_VM/25.292-b10 java/1.8.0_292 scala/2.12.17 vendor/AdoptOpenJDK cfg/retry-mode/legacy, ) +23/08/03 11:25:10 DEBUG AWS4Signer: AWS4 Canonical Request: '"GET +/ +delimiter=%2F&fetch-owner=false&list-type=2&max-keys=2&prefix=fake%2F +amz-sdk-invocation-id:e6d62603-96e4-a80f-10a1-816e0822bc71 +amz-sdk-request:attempt=1;max=21 +amz-sdk-retry:0/0/500 +content-type:application/octet-stream +host:test-bucket.localhost:9000 +user-agent:Hadoop 3.3.4, aws-sdk-java/1.12.262 Linux/6.4.7-1-MANJARO OpenJDK_64-Bit_Server_VM/25.292-b10 java/1.8.0_292 scala/2.12.17 vendor/AdoptOpenJDK cfg/retry-mode/legacy +x-amz-content-sha256:UNSIGNED-PAYLOAD +x-amz-date:20230803T112510Z + +amz-sdk-invocation-id;amz-sdk-request;amz-sdk-retry;content-type;host;user-agent;x-amz-content-sha256;x-amz-date +UNSIGNED-PAYLOAD" +23/08/03 11:25:10 DEBUG AWS4Signer: AWS4 String to Sign: '"AWS4-HMAC-SHA256 +20230803T112510Z +20230803/us-east-1/s3/aws4_request +31a317bb7f6d97248dd0cf03429d701cbb3e29ce889cfbb98ba7a34c57a3bfba" +23/08/03 11:25:10 DEBUG AWS4Signer: Generating a new signing key as the signing key not available in the cache for the date 1691020800000 +23/08/03 11:25:10 DEBUG RequestAddCookies: CookieSpec selected: default +23/08/03 11:25:10 DEBUG RequestAuthCache: Auth cache not set in the context +23/08/03 11:25:10 DEBUG PoolingHttpClientConnectionManager: Connection request: [route: {s}->https://test-bucket.localhost:9000][total available: 0; route allocated: 0 of 96; total allocated: 0 of 96] +23/08/03 11:25:10 DEBUG PoolingHttpClientConnectionManager: Connection leased: [id: 0][route: {s}->https://test-bucket.localhost:9000][total available: 0; route allocated: 1 of 96; total allocated: 1 of 96] +23/08/03 11:25:10 DEBUG MainClientExec: Opening connection {s}->https://test-bucket.localhost:9000 +23/08/03 11:25:10 DEBUG DefaultHttpClientConnectionOperator: Connecting to test-bucket.localhost/127.0.0.1:9000 +23/08/03 11:25:10 DEBUG SSLConnectionSocketFactory: Connecting socket to test-bucket.localhost/127.0.0.1:9000 with timeout 5000 +23/08/03 11:25:10 DEBUG SSLConnectionSocketFactory: Enabled protocols: [TLSv1.2] +23/08/03 11:25:10 DEBUG SSLConnectionSocketFactory: Enabled cipher suites:[TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV] +23/08/03 11:25:10 DEBUG SSLConnectionSocketFactory: Starting handshake +23/08/03 11:25:10 DEBUG ClientConnectionManagerFactory: +java.lang.reflect.InvocationTargetException + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:498) + at com.amazonaws.http.conn.ClientConnectionManagerFactory$Handler.invoke(ClientConnectionManagerFactory.java:76) + at com.amazonaws.http.conn.$Proxy32.connect(Unknown Source) + at com.amazonaws.thirdparty.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:393) + at com.amazonaws.thirdparty.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236) + at com.amazonaws.thirdparty.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186) + at com.amazonaws.thirdparty.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185) + at com.amazonaws.thirdparty.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83) + at com.amazonaws.thirdparty.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56) + at com.amazonaws.http.apache.client.impl.SdkHttpClient.execute(SdkHttpClient.java:72) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeOneRequest(AmazonHttpClient.java:1346) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeHelper(AmazonHttpClient.java:1157) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.doExecute(AmazonHttpClient.java:814) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeWithTimer(AmazonHttpClient.java:781) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.execute(AmazonHttpClient.java:755) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.access$500(AmazonHttpClient.java:715) + at com.amazonaws.http.AmazonHttpClient$RequestExecutionBuilderImpl.execute(AmazonHttpClient.java:697) + at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:561) + at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:541) + at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5456) + at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5403) + at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5397) + at com.amazonaws.services.s3.AmazonS3Client.listObjectsV2(AmazonS3Client.java:971) + at org.apache.hadoop.fs.s3a.S3AFileSystem.lambda$listObjects$11(S3AFileSystem.java:2595) + at org.apache.hadoop.fs.statistics.impl.IOStatisticsBinding.lambda$trackDurationOfOperation$5(IOStatisticsBinding.java:499) + at org.apache.hadoop.fs.s3a.Invoker.retryUntranslated(Invoker.java:414) + at org.apache.hadoop.fs.s3a.Invoker.retryUntranslated(Invoker.java:377) + at org.apache.hadoop.fs.s3a.S3AFileSystem.listObjects(S3AFileSystem.java:2586) + at org.apache.hadoop.fs.s3a.S3AFileSystem.s3GetFileStatus(S3AFileSystem.java:3832) + at org.apache.hadoop.fs.s3a.S3AFileSystem.innerGetFileStatus(S3AFileSystem.java:3688) + at org.apache.hadoop.fs.s3a.S3AFileSystem.lambda$isDirectory$35(S3AFileSystem.java:4724) + at org.apache.hadoop.fs.statistics.impl.IOStatisticsBinding.lambda$trackDurationOfOperation$5(IOStatisticsBinding.java:499) + at org.apache.hadoop.fs.statistics.impl.IOStatisticsBinding.trackDuration(IOStatisticsBinding.java:444) + at org.apache.hadoop.fs.s3a.S3AFileSystem.trackDurationAndSpan(S3AFileSystem.java:2337) + at org.apache.hadoop.fs.s3a.S3AFileSystem.trackDurationAndSpan(S3AFileSystem.java:2356) + at org.apache.hadoop.fs.s3a.S3AFileSystem.isDirectory(S3AFileSystem.java:4722) + at org.apache.spark.sql.execution.streaming.FileStreamSink$.hasMetadata(FileStreamSink.scala:54) + at org.apache.spark.sql.execution.datasources.DataSource.resolveRelation(DataSource.scala:366) + at org.apache.spark.sql.DataFrameReader.loadV1Source(DataFrameReader.scala:229) + at org.apache.spark.sql.DataFrameReader.$anonfun$load$2(DataFrameReader.scala:211) + at scala.Option.getOrElse(Option.scala:189) + at org.apache.spark.sql.DataFrameReader.load(DataFrameReader.scala:211) + at org.apache.spark.sql.DataFrameReader.load(DataFrameReader.scala:186) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:498) + at py4j.reflection.MethodInvoker.invoke(MethodInvoker.java:244) + at py4j.reflection.ReflectionEngine.invoke(ReflectionEngine.java:374) + at py4j.Gateway.invoke(Gateway.java:282) + at py4j.commands.AbstractCommand.invokeMethod(AbstractCommand.java:132) + at py4j.commands.CallCommand.execute(CallCommand.java:79) + at py4j.ClientServerConnection.waitForCommands(ClientServerConnection.java:182) + at py4j.ClientServerConnection.run(ClientServerConnection.java:106) + at java.lang.Thread.run(Thread.java:748) +Caused by: javax.net.ssl.SSLException: Unsupported or unrecognized SSL message + at sun.security.ssl.SSLSocketInputRecord.handleUnknownRecord(SSLSocketInputRecord.java:448) + at sun.security.ssl.SSLSocketInputRecord.decode(SSLSocketInputRecord.java:184) + at sun.security.ssl.SSLTransport.decode(SSLTransport.java:109) + at sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1383) + at sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1291) + at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:435) + at com.amazonaws.thirdparty.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:436) + at com.amazonaws.thirdparty.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:384) + at com.amazonaws.thirdparty.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142) + at com.amazonaws.thirdparty.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:376) + ... 58 more +23/08/03 11:25:10 DEBUG DefaultManagedHttpClientConnection: http-outgoing-0: Shutdown connection +23/08/03 11:25:10 DEBUG MainClientExec: Connection discarded +23/08/03 11:25:10 DEBUG PoolingHttpClientConnectionManager: Connection released: [id: 0][route: {s}->https://test-bucket.localhost:9000][total available: 0; route allocated: 0 of 96; total allocated: 0 of 96] +23/08/03 11:25:10 DEBUG AmazonHttpClient: Unable to execute HTTP request: Unsupported or unrecognized SSL message Request will be retried. +23/08/03 11:25:10 DEBUG request: Retrying Request: GET https://test-bucket.localhost:9000 / Parameters: ({"list-type":["2"],"delimiter":["/"],"max-keys":["2"],"prefix":["fake/"],"fetch-owner":["false"]}Headers: (amz-sdk-invocation-id: e6d62603-96e4-a80f-10a1-816e0822bc71, Content-Type: application/octet-stream, User-Agent: Hadoop 3.3.4, aws-sdk-java/1.12.262 Linux/6.4.7-1-MANJARO OpenJDK_64-Bit_Server_VM/25.292-b10 java/1.8.0_292 scala/2.12.17 vendor/AdoptOpenJDK cfg/retry-mode/legacy, ) +23/08/03 11:25:10 DEBUG AmazonHttpClient: Retriable error detected, will retry in 49ms, attempt number: 0 +``` + +#### Change number of retries + +You can also change number of retries performed by both libraries using `extra` parameter: + +```python +spark_s3 = SparkS3( + ..., + extra={ + "attempts.maximum": 1, + "retry.limit": 1, + }, +) +``` + +So accessing S3 will fail almost immediately if there is any error. + +### Most common mistakes + +#### No network access + +```text +Caused by: java.net.ConnectException: Connection refused +``` + +Mostly caused by: + +* Trying to access port number which S3 server does not listen +* You’re trying to access host which is unreachable from your network (e.g. running behind some proxy or VPN) +* There are some firewall restrictions for accessing specific host or port + +#### Using HTTPS protocol for HTTP port + +```text +Caused by: javax.net.ssl.SSLException: Unsupported or unrecognized SSL message +``` + +By default, SparkS3 uses HTTPS protocol for connection. +If you change port number, this does not lead to changing protocol: + +```python +spark_s3 = SparkS3(host="s3provider.com", port=8080, ...) +``` + +You should pass protocol explicitly: + +```python +spark_s3 = SparkS3(host="s3provider.com", port=8080, protocol="http", ...) +``` + +#### SSL certificate is self-signed + +```text +sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target +``` + +To connect to HTTPS port with self-signed certificate, you should +[add certificate chain to Java TrustedStore](https://stackoverflow.com/questions/373295/digital-certificate-how-to-import-cer-file-in-to-truststore-file-using). + +Another option is to disable SSL check: + +```python +spark_s3 = SparkS3( + ..., + extra={ + "connection.ssl.enabled": False, + }, +) +``` + +But is is **NOT** recommended. + +#### Accessing S3 without domain-style access style support + +```text +Caused by: java.net.UnknownHostException: my-bucket.s3provider.com +``` + +To use path-style access, use option below: + +```python +spark_s3 = SparkS3( + host="s3provider.com", + bucket="my-bucket", + ..., + extra={ + "path.style.access": True, + }, +) +``` + +## Slow or unstable writing to S3 + +Hadoop AWS allows to use different writing strategies for different S3 implementations, depending +on list of supported features by server. + +These strategies are called [committers](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/committers.html). +There are [different types of committers](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/committers.html#Switching_to_an_S3A_Committer): + +* `file` (default) +* `directory` +* `partitioned` +* `magic` + +### `file` committer + +This committer is quite slow and unstable, so it is not recommended to use: + +```text +WARN AbstractS3ACommitterFactory: Using standard FileOutputCommitter to commit work. This is slow and potentially unsafe. +``` + +This is caused by the fact it creates files in the temp directory on remote filesystem, and after all of them are written successfully, +they are moved to target directory on same remote filesystem. + +This is not an issue for HDFS which does support file move operations and also support renaming directory +as atomic operation with `O(1)` time complexity. + +But S3 does support only file copying, so moving is performed via copy + delete. +Also it does not support atomic directory rename operation. Instead, renaming files with the same prefix has time complexity `O(n)`. + +### `directory` and `partitioned` committers + +These are [staging committers](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/committer_architecture.html), +meaning that they create temp directories on local filesystem, and after all files are written successfully, +they will be uploaded to S3. Local filesystems do support file moving and directory renaming, +so these committers does not have issues that `file` committer has. + +But they both require free space on local filesystem, and this may be an issue if user need to write large amount of data. +Also this can be an issue for container environment, like Kubernetes, there resources should be allocated before starting a container. + +### `magic` committer + +This committer uses multipart upload feature of S3 API, allowing to create multiple files +and after all of them were written successfully finish the transaction. Before transaction is finished, +files will not be accessible by other clients. + +Because it does not require neither file moving operations, nor directory atomic rename, +upload process is done in most efficient way S3 support. +This [drastically increases writing performance](https://spot.io/blog/improve-apache-spark-performance-with-the-s3-magic-committer/). + +To use this committer, set [following properties](https://github.com/apache/spark/pull/32518) while creating Spark session. + +S3 your main distributed filesystem (Spark on Kubernetes) + +```py +# https://issues.apache.org/jira/browse/SPARK-23977 +# https://spark.apache.org/docs/latest/cloud-integration.html#committing-work-into-cloud-storage-safely-and-fast +spark = ( + SparkSession.builder.appName("spark-app-name") + .config("spark.hadoop.fs.s3a.committer.magic.enabled", "true") + .config("spark.hadoop.fs.s3a.committer.name", "magic") + .config("spark.hadoop.mapreduce.outputcommitter.factory.scheme.s3a", "org.apache.hadoop.fs.s3a.commit.S3ACommitterFactory") + .config("spark.sql.parquet.output.committer.class", "org.apache.spark.internal.io.cloud.BindingParquetOutputCommitter") + .config("spark.sql.sources.commitProtocolClass", "org.apache.spark.internal.io.cloud.PathOutputCommitProtocol") + .getOrCreate() +) +``` + +HDFS is your main distributed filesystem (Spark on Hadoop) + +```py +# https://community.cloudera.com/t5/Support-Questions/spark-sql-sources-partitionOverwriteMode-dynamic-quot-not/m-p/343483/highlight/true +spark = ( + SparkSession.builder.appName("spark-app-name") + .config("spark.hadoop.fs.s3a.committer.magic.enabled", "true") + .config("spark.hadoop.fs.s3a.committer.name", "magic") + .getOrCreate() +) +``` + +#### WARNING +`magic` committer requires S3 implementation to have strong consistency - file upload API return response only +if it was written on enough number of cluster nodes, and any cluster node error does not lead to missing or corrupting files. + +Some S3 implementations does have strong consistency +(like [AWS S3](https://aws.amazon.com/ru/blogs/aws/amazon-s3-update-strong-read-after-write-consistency/) and +[MinIO](https://blog.min.io/migrating-hdfs-to-object-storage/)), some not. Please contact your S3 provider +to get information about S3 implementation consistency. + +#### WARNING +`magic` committer does not support `if_exists="replace_overlapping_partitions"`. +Either use another `if_exists` value, or use `partitioned` committer. + +### See also + +* [directory.marker.retention=”keep”](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/directory_markers.html) + +## Slow reading from S3 + +Please read following documentation: + +* [prefetch.enabled](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/prefetching.html) +* [experimental.input.fadvise](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/performance.html#Improving_data_input_performance_through_fadvise) +* [Parquet and ORC I/O settings](https://spark.apache.org/docs/latest/cloud-integration.html#parquet-io-settings) + +If you’re reading data from row-based formats, like [CSV](../../../file_df/file_formats/csv.md#csv-file-format), prefer +[experimental.input.fadvise=”sequential” with increased readahead.range](https://issues.apache.org/jira/browse/HADOOP-17789?focusedCommentId=17383559#comment-17383559). + +But for other file formats, especially using compression, prefer +[experimental.input.fadvise=”normal”](https://issues.apache.org/jira/browse/HADOOP-17789?focusedCommentId=17383743#comment-17383743) diff --git a/mddocs/connection/index.md b/mddocs/connection/index.md new file mode 100644 index 000000000..5ad28edef --- /dev/null +++ b/mddocs/connection/index.md @@ -0,0 +1,34 @@ + + + DB Connection + +* [DB Connections](db_connection/index.md) + * [Clickhouse](db_connection/clickhouse/index.md) + * [Greenplum](db_connection/greenplum/index.md) + * [Kafka](db_connection/kafka/index.md) + * [Hive](db_connection/hive/index.md) + * [MongoDB](db_connection/mongodb/index.md) + * [MSSQL](db_connection/mssql/index.md) + * [MySQL](db_connection/mysql/index.md) + * [Oracle](db_connection/oracle/index.md) + * [Postgres](db_connection/postgres/index.md) + * [Teradata](db_connection/teradata/index.md) + + File Connection + +* [File Connections](file_connection/index.md) + * [FTP](file_connection/ftp.md) + * [FTPS](file_connection/ftps.md) + * [HDFS](file_connection/hdfs/index.md) + * [Samba](file_connection/samba.md) + * [SFTP](file_connection/sftp.md) + * [S3](file_connection/s3.md) + * [Webdav](file_connection/webdav.md) + + File DataFrame Connection + +* [File DataFrame Connections](file_df_connection/index.md) + * [Spark LocalFS](file_df_connection/spark_local_fs.md) + * [Spark HDFS](file_df_connection/spark_hdfs/index.md) + * [Spark S3](file_df_connection/spark_s3/index.md) + * [Base interface](file_df_connection/base.md) diff --git a/mddocs/contributing.md b/mddocs/contributing.md new file mode 100644 index 000000000..739798897 --- /dev/null +++ b/mddocs/contributing.md @@ -0,0 +1,391 @@ +# Contributing Guide + +Welcome! There are many ways to contribute, including submitting bug +reports, improving documentation, submitting feature requests, reviewing +new submissions, or contributing code that can be incorporated into the +project. + +## Limitations + +We should keep close to these items during development: + +* Some companies still use old Spark versions, like 2.3.1. So it is required to keep compatibility if possible, e.g. adding branches for different Spark versions. +* Different users uses onETL in different ways - some uses only DB connectors, some only files. Connector-specific dependencies should be optional. +* Instead of creating classes with a lot of different options, prefer splitting them into smaller classes, e.g. options class, context manager, etc, and using composition. + +## Initial setup for local development + +### Install Git + +Please follow [instruction](https://docs.github.com/en/get-started/quickstart/set-up-git). + +### Create a fork + +If you are not a member of a development team building onETL, you should create a fork before making any changes. + +Please follow [instruction](https://docs.github.com/en/get-started/quickstart/fork-a-repo). + +### Clone the repo + +Open terminal and run these commands: + +```bash +git clone git@github.com:myuser/onetl.git -b develop + +cd onetl +``` + +### Setup environment + +Create virtualenv and install dependencies: + +```bash +python -m venv venv +source venv/bin/activate +pip install -U wheel +pip install -U pip setuptools +pip install -U \ + -r requirements/core.txt \ + -r requirements/ftp.txt \ + -r requirements/hdfs.txt \ + -r requirements/kerberos.txt \ + -r requirements/s3.txt \ + -r requirements/sftp.txt \ + -r requirements/webdav.txt \ + -r requirements/dev.txt \ + -r requirements/docs.txt \ + -r requirements/tests/base.txt \ + -r requirements/tests/clickhouse.txt \ + -r requirements/tests/kafka.txt \ + -r requirements/tests/mongodb.txt \ + -r requirements/tests/mssql.txt \ + -r requirements/tests/mysql.txt \ + -r requirements/tests/postgres.txt \ + -r requirements/tests/oracle.txt \ + -r requirements/tests/pydantic-2.txt \ + -r requirements/tests/spark-3.5.5.txt + +# TODO: remove after https://github.com/zqmillet/sphinx-plantuml/pull/4 +pip install sphinx-plantuml --no-deps +``` + +### Enable pre-commit hooks + +Install pre-commit hooks: + +```bash +pre-commit install --install-hooks +``` + +Test pre-commit hooks run: + +```bash +pre-commit run +``` + +## How to + +### Run tests locally + +#### Using docker-compose + +Build image for running tests: + +```bash +docker-compose build +``` + +Start all containers with dependencies: + +```bash +docker-compose --profile all up -d +``` + +You can run limited set of dependencies: + +```bash +docker-compose --profile mongodb up -d +``` + +Run tests: + +```bash +docker-compose run --rm onetl ./run_tests.sh +``` + +You can pass additional arguments, they will be passed to pytest: + +```bash +docker-compose run --rm onetl ./run_tests.sh -m mongodb -lsx -vvvv --log-cli-level=INFO +``` + +You can run interactive bash session and use it: + +```bash +docker-compose run --rm onetl bash + +./run_tests.sh -m mongodb -lsx -vvvv --log-cli-level=INFO +``` + +See logs of test container: + +```bash +docker-compose logs -f onetl +``` + +Stop all containers and remove created volumes: + +```bash +docker-compose --profile all down -v +``` + +#### Without docker-compose + +#### WARNING +To run HDFS tests locally you should add the following line to your `/etc/hosts` (file path depends on OS): + +```default +# HDFS server returns container hostname as connection address, causing error in DNS resolution +127.0.0.1 hdfs +``` + +#### NOTE +To run Oracle tests you need to install [Oracle instantclient](https://www.oracle.com/database/technologies/instant-client.html), +and pass its path to `ONETL_ORA_CLIENT_PATH` and `LD_LIBRARY_PATH` environment variables, +e.g. `ONETL_ORA_CLIENT_PATH=/path/to/client64/lib`. + +It may also require to add the same path into `LD_LIBRARY_PATH` environment variable + +#### NOTE +To run Greenplum tests, you should: + +* Download [VMware Greenplum connector for Spark](https://onetl.readthedocs.io/en/latest/connection/db_connection/greenplum/prerequisites.html) +* Either move it to `~/.ivy2/jars/`, or pass file path to `CLASSPATH` +* Set environment variable `ONETL_GP_PACKAGE_VERSION=local`. + +Start all containers with dependencies: + +```bash +docker-compose --profile all up -d +``` + +You can run limited set of dependencies: + +```bash +docker-compose --profile mongodb up -d +``` + +Load environment variables with connection properties: + +```bash +source .env.local +``` + +Run tests: + +```bash +./run_tests.sh +``` + +You can pass additional arguments, they will be passed to pytest: + +```bash +./run_tests.sh -m mongodb -lsx -vvvv --log-cli-level=INFO +``` + +Stop all containers and remove created volumes: + +```bash +docker-compose --profile all down -v +``` + +### Build documentation + +Build documentation using Sphinx: + +```bash +cd docs +make html +``` + +Then open in browser `docs/_build/index.html`. + +## Review process + +Please create a new GitHub issue for any significant changes and +enhancements that you wish to make. Provide the feature you would like +to see, why you need it, and how it will work. Discuss your ideas +transparently and get community feedback before proceeding. + +Significant Changes that you wish to contribute to the project should be +discussed first in a GitHub issue that clearly outlines the changes and +benefits of the feature. + +Small Changes can directly be crafted and submitted to the GitHub +Repository as a Pull Request. + +### Create pull request + +Commit your changes: + +```bash +git commit -m "Commit message" +git push +``` + +Then open Github interface and [create pull request](https://docs.github.com/en/get-started/quickstart/contributing-to-projects#making-a-pull-request). +Please follow guide from PR body template. + +After pull request is created, it get a corresponding number, e.g. 123 (`pr_number`). + +### Write release notes + +`onETL` uses [towncrier](https://pypi.org/project/towncrier/) +for changelog management. + +To submit a change note about your PR, add a text file into the +[docs/changelog/next_release](./next_release) folder. It should contain an +explanation of what applying this PR will change in the way +end-users interact with the project. One sentence is usually +enough but feel free to add as many details as you feel necessary +for the users to understand what it means. + +**Use the past tense** for the text in your fragment because, +combined with others, it will be a part of the “news digest” +telling the readers **what changed** in a specific version of +the library *since the previous version*. + +You should also use +reStructuredText syntax for highlighting code (inline or block), +linking parts of the docs or external sites. +If you wish to sign your change, feel free to add `-- by +:user:`github-username`` at the end (replace `github-username` +with your own!). + +Finally, name your file following the convention that Towncrier +understands: it should start with the number of an issue or a +PR followed by a dot, then add a patch type, like `feature`, +`doc`, `misc` etc., and add `.rst` as a suffix. If you +need to add more than one fragment, you may add an optional +sequence number (delimited with another period) between the type +and the suffix. + +In general the name will follow `..rst` pattern, +where the categories are: + +- `feature`: Any new feature +- `bugfix`: A bug fix +- `improvement`: An improvement +- `doc`: A change to the documentation +- `dependency`: Dependency-related changes +- `misc`: Changes internal to the repo like CI, test and build changes + +A pull request may have more than one of these components, for example +a code change may introduce a new feature that deprecates an old +feature, in which case two fragments should be added. It is not +necessary to make a separate documentation fragment for documentation +changes accompanying the relevant code changes. + +#### Examples for adding changelog entries to your Pull Requests + +```rst +Added a ``:github:user:`` role to Sphinx config -- by :github:user:`someuser` +``` + +```rst +Fixed behavior of ``WebDAV`` connector -- by :github:user:`someuser` +``` + +```rst +Added support of ``timeout`` in ``S3`` connector +-- by :github:user:`someuser`, :github:user:`anotheruser` and :github:user:`otheruser` +``` + +#### How to skip change notes check? + +Just add `ci:skip-changelog` label to pull request. + +#### Release Process + +Before making a release from the `develop` branch, follow these steps: + +1. Checkout to `develop` branch and update it to the actual state + +```bash +git checkout develop +git pull -p +``` + +1. Backup `NEXT_RELEASE.rst` + +```bash +cp "docs/changelog/NEXT_RELEASE.rst" "docs/changelog/temp_NEXT_RELEASE.rst" +``` + +1. Build the Release notes with Towncrier + +```bash +VERSION=$(cat onetl/VERSION) +towncrier build "--version=${VERSION}" --yes +``` + +1. Change file with changelog to release version number + +```bash +mv docs/changelog/NEXT_RELEASE.rst "docs/changelog/${VERSION}.rst" +``` + +1. Remove content above the version number heading in the `${VERSION}.rst` file + +```bash +awk '!/^.*towncrier release notes start/' "docs/changelog/${VERSION}.rst" > temp && mv temp "docs/changelog/${VERSION}.rst" +``` + +1. Update Changelog Index + +```bash +awk -v version=${VERSION} '/DRAFT/{print;print " " version;next}1' docs/changelog/index.rst > temp && mv temp docs/changelog/index.rst +``` + +1. Restore `NEXT_RELEASE.rst` file from backup + +```bash +mv "docs/changelog/temp_NEXT_RELEASE.rst" "docs/changelog/NEXT_RELEASE.rst" +``` + +1. Commit and push changes to `develop` branch + +```bash +git add . +git commit -m "Prepare for release ${VERSION}" +git push +``` + +1. Merge `develop` branch to `master`, **WITHOUT** squashing + +```bash +git checkout master +git pull +git merge develop +git push +``` + +1. Add git tag to the latest commit in `master` branch + +```bash +git tag "$VERSION" +git push origin "$VERSION" +``` + +1. Update version in `develop` branch **after release**: + +```bash +git checkout develop + +NEXT_VERSION=$(echo "$VERSION" | awk -F. '/[0-9]+\./{$NF++;print}' OFS=.) +echo "$NEXT_VERSION" > onetl/VERSION + +git add . +git commit -m "Bump version" +git push +``` diff --git a/mddocs/db/db_reader.md b/mddocs/db/db_reader.md new file mode 100644 index 000000000..29209279a --- /dev/null +++ b/mddocs/db/db_reader.md @@ -0,0 +1,346 @@ + + +# DB Reader + +| [`DBReader`](#onetl.db.db_reader.db_reader.DBReader) | Allows you to read data from a table with specified database connection and parameters, and return its content as Spark dataframe. | +|------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------| +| [`DBReader.run`](#onetl.db.db_reader.db_reader.DBReader.run)() | Reads data from source table and saves as Spark dataframe. | +| [`DBReader.has_data`](#onetl.db.db_reader.db_reader.DBReader.has_data)() | Returns `True` if there is some data in the source, `False` otherwise. | +| [`DBReader.raise_if_no_data`](#onetl.db.db_reader.db_reader.DBReader.raise_if_no_data)() | Raises exception `NoDataError` if source does not contain any data. | + +### *class* onetl.db.db_reader.db_reader.DBReader(\*, connection: BaseDBConnection, table: str, columns: ConstrainedListValue[str] | None = None, where: Any | None = None, hint: Any | None = None, df_schema: StructType | None = None, hwm_column: str | tuple | None = None, hwm_expression: str | None = None, hwm: AutoDetectHWM | ColumnHWM | KeyValueHWM | None = None, options: GenericOptions | None = None) + +Allows you to read data from a table with specified database connection +and parameters, and return its content as Spark dataframe. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### NOTE +DBReader can return different results depending on [Read Strategies](../strategy/index.md#strategy) + +#### NOTE +This class operates with only one source at a time. It does NOT support executing queries +to multiple source, like `SELECT ... JOIN`. + +#### Versionadded +Added in version 0.1.0. + +#### Versionchanged +Changed in version 0.8.0: Moved `onetl.core.DBReader` → `onetl.db.DBReader` + +* **Parameters:** + **connection** + : Class which contains DB connection properties. See [DB Connections](../connection/db_connection/index.md#db-connections) section + + **source** + : Table/collection/etc name to read data from. +
+ If connection has schema support, you need to specify the full name of the source + including the schema, e.g. `schema.name`. +
+ #### Versionchanged + Changed in version 0.7.0: Renamed `table` → `source` + + **columns** + : The list of columns to be read. +
+ If RDBMS supports any kind of expressions, you can pass them too. + ```python + columns = [ + "mycolumn", + "another_column as alias", + "count(*) over ()", + "some(function) as alias2", + ] + ``` +
+ #### NOTE + Some sources does not have columns. +
+ #### NOTE + It is recommended to pass column names explicitly to avoid selecting too many columns, + and to avoid adding unexpected columns to dataframe if source DDL is changed. +
+ #### Deprecated + Deprecated since version 0.10.0: Syntax `DBReader(columns="col1, col2")` (string instead of list) is not supported, + and will be removed in v1.0.0 + + **where** + : Custom `where` for SQL query or MongoDB pipeline. +
+ `where` syntax depends on the source. For example, SQL sources + accept `where` as a string, but MongoDB sources accept `where` as a dictionary. + ```python + # SQL database connection + where = "column_1 > 2" +
+ # MongoDB connection + where = { + "col_1": {"$gt": 1, "$lt": 100}, + "col_2": {"$gt": 2}, + "col_3": {"$eq": "hello"}, + } + ``` +
+ #### NOTE + Some sources does not support data filtering. + + **hwm** + : HWM class to be used as [HWM](https://etl-entities.readthedocs.io/en/stable/hwm/index.html) value. + ```python + hwm = DBReader.AutoDetectHWM( + name="some_unique_hwm_name", + expression="hwm_column", + ) + ``` +
+ HWM value will be fetched using `hwm_column` SQL query. +
+ If you want to use some SQL expression as HWM value, you can use it as well: + ```python + hwm = DBReader.AutoDetectHWM( + name="some_unique_hwm_name", + expression="cast(hwm_column_orig as date)", + ) + ``` +
+ #### NOTE + Some sources does not support passing expressions and can be used only with column/field + names which present in the source. +
+ #### Versionchanged + Changed in version 0.10.0: Replaces deprecated `hwm_column` and `hwm_expression` attributes + + **hint** + : Hint expression used for querying the data. +
+ `hint` syntax depends on the source. For example, SQL sources + accept `hint` as a string, but MongoDB sources accept `hint` as a dictionary. + ```python + # SQL database connection + hint = "index(myschema.mytable mycolumn)" +
+ # MongoDB connection + hint = { + "mycolumn": 1, + } + ``` +
+ #### NOTE + Some sources does not support hints. + + **df_schema** + : Spark DataFrame schema, used for proper type casting of the rows. + ```python + from pyspark.sql.types import ( + DoubleType, + IntegerType, + StringType, + StructField, + StructType, + TimestampType, + ) +
+ df_schema = StructType( + [ + StructField("_id", IntegerType()), + StructField("text_string", StringType()), + StructField("hwm_int", IntegerType()), + StructField("hwm_datetime", TimestampType()), + StructField("float_value", DoubleType()), + ], + ) +
+ reader = DBReader( + connection=connection, + source="fiddle.dummy", + df_schema=df_schema, + ) + ``` +
+ #### NOTE + Some sources does not support passing dataframe schema. + + **options** + : Spark read options, like partitioning mode. + ```python + Postgres.ReadOptions( + partitioningMode="hash", + partitionColumn="some_column", + numPartitions=20, + fetchsize=1000, + ) + ``` +
+ #### NOTE + Some sources does not support reading options. + +### Examples + +Minimal example + +```py +from onetl.db import DBReader +from onetl.connection import Postgres + +postgres = Postgres(...) + +# create reader +reader = DBReader(connection=postgres, source="fiddle.dummy") + +# read data from table "fiddle.dummy" +df = reader.run() +``` + +With custom reading options + +```py +from onetl.connection import Postgres +from onetl.db import DBReader + +postgres = Postgres(...) +options = Postgres.ReadOptions(sessionInitStatement="select 300", fetchsize="100") + +# create reader and pass some options to the underlying connection object +reader = DBReader(connection=postgres, source="fiddle.dummy", options=options) + +# read data from table "fiddle.dummy" +df = reader.run() +``` + +Full example + +```py +from onetl.db import DBReader +from onetl.connection import Postgres + +postgres = Postgres(...) +options = Postgres.ReadOptions(sessionInitStatement="select 300", fetchsize="100") + +# create reader with specific columns, rows filter +reader = DBReader( + connection=postgres, + source="default.test", + where="d_id > 100", + hint="NOWAIT", + columns=["d_id", "d_name", "d_age"], + options=options, +) + +# read data from table "fiddle.dummy" +df = reader.run() +``` + +Incremental reading + +See [Read Strategies](../strategy/index.md#strategy) for more examples + +```python +from onetl.strategy import IncrementalStrategy + +... + +reader = DBReader( + connection=postgres, + source="fiddle.dummy", + hwm=DBReader.AutoDetectHWM( # mandatory for IncrementalStrategy + name="some_unique_hwm_name", + expression="d_age", + ), +) + +# read data from table "fiddle.dummy" +# but only with new rows (`WHERE d_age > previous_hwm_value`) +with IncrementalStrategy(): + df = reader.run() +``` + + + +#### has_data() → bool + +Returns `True` if there is some data in the source, `False` otherwise. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### NOTE +This method can return different results depending on [Read Strategies](../strategy/index.md#strategy) + +#### WARNING +If [hwm](https://etl-entities.readthedocs.io/en/stable/hwm/index.html) is used, then method should be called inside [Read Strategies](../strategy/index.md#strategy) context. And vise-versa, if HWM is not used, this method should not be called within strategy. + +#### Versionadded +Added in version 0.10.0. + +* **Raises:** + RuntimeError + : Current strategy is not compatible with HWM parameter. + +### Examples + +```python +reader = DBReader(...) + +# handle situation when there is no data in the source +if reader.has_data(): + df = reader.run() +else: + # implement your handling logic here + ... +``` + + + +#### raise_if_no_data() → None + +Raises exception `NoDataError` if source does not contain any data. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### NOTE +This method can return different results depending on [Read Strategies](../strategy/index.md#strategy) + +#### WARNING +If [hwm](https://etl-entities.readthedocs.io/en/stable/hwm/index.html) is used, then method should be called inside [Read Strategies](../strategy/index.md#strategy) context. And vise-versa, if HWM is not used, this method should not be called within strategy. + +#### Versionadded +Added in version 0.10.0. + +* **Raises:** + RuntimeError + : Current strategy is not compatible with HWM parameter. + + `onetl.exception.NoDataError` + : There is no data in source. + +### Examples + +```python +reader = DBReader(...) + +# ensure that there is some data in the source before reading it using Spark +reader.raise_if_no_data() +``` + + + +#### run() → DataFrame + +Reads data from source table and saves as Spark dataframe. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### NOTE +This method can return different results depending on [Read Strategies](../strategy/index.md#strategy) + +#### WARNING +If [hwm](https://etl-entities.readthedocs.io/en/stable/hwm/index.html) is used, then method should be called inside [Read Strategies](../strategy/index.md#strategy) context. And vise-versa, if HWM is not used, this method should not be called within strategy. + +#### Versionadded +Added in version 0.1.0. + +* **Returns:** + **df** + : Spark dataframe + +### Examples + +Read data to Spark dataframe: + +```python +df = reader.run() +``` + + diff --git a/mddocs/db/db_writer.md b/mddocs/db/db_writer.md new file mode 100644 index 000000000..91b8a16fb --- /dev/null +++ b/mddocs/db/db_writer.md @@ -0,0 +1,100 @@ + + +# DB Writer + +| [`DBWriter`](#onetl.db.db_writer.db_writer.DBWriter) | Class specifies schema and table where you can write your dataframe. | +|------------------------------------------------------------------|------------------------------------------------------------------------| +| [`DBWriter.run`](#onetl.db.db_writer.db_writer.DBWriter.run)(df) | Method for writing your df to specified target. | + +### *class* onetl.db.db_writer.db_writer.DBWriter(\*, connection: BaseDBConnection, table: str, options: GenericOptions | None = None) + +Class specifies schema and table where you can write your dataframe. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.1.0. + +#### Versionchanged +Changed in version 0.8.0: Moved `onetl.core.DBReader` → `onetl.db.DBReader` + +* **Parameters:** + **connection** + : Class which contains DB connection properties. See [DB Connections](../connection/db_connection/index.md#db-connections) section. + + **target** + : Table/collection/etc name to write data to. +
+ If connection has schema support, you need to specify the full name of the source + including the schema, e.g. `schema.name`. +
+ #### Versionchanged + Changed in version 0.7.0: Renamed `table` → `target` + + **options** + : Spark write options. Can be in form of special `WriteOptions` object or a dict. +
+ For example: + `{"if_exists": "replace_entire_table", "compression": "snappy"}` + or + `Hive.WriteOptions(if_exists="replace_entire_table", compression="snappy")` +
+ #### NOTE + Some sources does not support writing options. + +### Examples + +Minimal example + +```py +from onetl.connection import Postgres +from onetl.db import DBWriter + +postgres = Postgres(...) + +writer = DBWriter( + connection=postgres, + target="fiddle.dummy", +) +``` + +With custom write options + +```py +from onetl.connection import Postgres +from onetl.db import DBWriter + +postgres = Postgres(...) + +options = Postgres.WriteOptions(if_exists="replace_entire_table", batchsize=1000) + +writer = DBWriter( + connection=postgres, + target="fiddle.dummy", + options=options, +) +``` + + + +#### run(df: DataFrame) → None + +Method for writing your df to specified target. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### NOTE +Method does support only **batching** DataFrames. + +#### Versionadded +Added in version 0.1.0. + +* **Parameters:** + **df** + : Spark dataframe + +### Examples + +Write dataframe to target: + +```python +writer.run(df) +``` + + diff --git a/mddocs/db/index.md b/mddocs/db/index.md new file mode 100644 index 000000000..861a539ff --- /dev/null +++ b/mddocs/db/index.md @@ -0,0 +1,6 @@ + + + DB classes + +* [DB Reader](db_reader.md) +* [DB Writer](db_writer.md) diff --git a/mddocs/file/file_downloader/file_downloader.md b/mddocs/file/file_downloader/file_downloader.md new file mode 100644 index 000000000..a1c2f361c --- /dev/null +++ b/mddocs/file/file_downloader/file_downloader.md @@ -0,0 +1,352 @@ + + +# File Downloader + +| [`FileDownloader`](#onetl.file.file_downloader.file_downloader.FileDownloader) | Allows you to download files from a remote source with specified file connection and parameters, and return an object with download result summary. | +|--------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------| +| [`FileDownloader.run`](#onetl.file.file_downloader.file_downloader.FileDownloader.run)([files]) | Method for downloading files from source to local directory. | +| [`FileDownloader.view_files`](#onetl.file.file_downloader.file_downloader.FileDownloader.view_files)() | Get file list in the `source_path`, after `filter`, `limit` and `hwm` applied (if any). | + +### *class* onetl.file.file_downloader.file_downloader.FileDownloader(\*, connection: ~onetl.base.base_file_connection.BaseFileConnection, local_path: ~onetl.impl.local_path.LocalPath, source_path: ~onetl.impl.remote_path.RemotePath | None = None, temp_path: ~onetl.impl.local_path.LocalPath | None = None, filter: ~typing.List[~onetl.base.base_file_filter.BaseFileFilter] = None, limit: ~typing.List[~onetl.base.base_file_limit.BaseFileLimit] = None, hwm: ~etl_entities.hwm.file.file_hwm.FileHWM | None = None, hwm_type: ~typing.Type[~etl_entities.old_hwm.file_list_hwm.FileListHWM] | str | None = None, options: ~onetl.file.file_downloader.options.FileDownloaderOptions = FileDownloaderOptions(if_exists=, delete_source=False, workers=1)) + +Allows you to download files from a remote source with specified file connection +and parameters, and return an object with download result summary. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### NOTE +FileDownloader can return different results depending on [Read Strategies](../../strategy/index.md#strategy) + +#### NOTE +This class is used to download files **only** from remote directory to the local one. + +It does NOT support direct file transfer between filesystems, like `FTP -> SFTP`. +You should use FileDownloader + [File Uploader](../file_uploader/file_uploader.md#file-uploader) to implement `FTP -> local dir -> SFTP`. + +#### Versionadded +Added in version 0.1.0. + +#### Versionchanged +Changed in version 0.8.0: Moved `onetl.core.FileDownloader` → `onetl.file.FileDownloader` + +* **Parameters:** + **connection** + : Class which contains File system connection properties. See [File Connections](../../connection/file_connection/index.md#file-connections) section. + + **local_path** + : Local path where you download files + + **source_path** + : Remote path to download files from. +
+ Could be `None`, but only if you pass absolute file paths directly to + [`run`](#onetl.file.file_downloader.file_downloader.FileDownloader.run) method + + **temp_path** + : If set, this path will be used for downloading a file, and then renaming it to the target file path. + If `None` is passed, files are downloaded directly to `target_path`. +
+ #### WARNING + In case of production ETL pipelines, please set a value for `temp_path` (NOT `None`). + This allows to properly handle download interruption, + without creating half-downloaded files in the target, + because unlike file download, `rename` call is atomic. +
+ #### WARNING + In case of connections like SFTP or FTP, which can have multiple underlying filesystems, + please pass to `temp_path` path on the SAME filesystem as `target_path`. + Otherwise instead of `rename`, remote OS will move file between filesystems, + which is NOT atomic operation. +
+ #### Versionadded + Added in version 0.5.0. + + **filters** + : Return only files/directories matching these filters. See [File Filters](../file_filters/index.md#file-filters) +
+ #### Versionchanged + Changed in version 0.3.0: Replaces old `source_path_pattern: str` and `exclude_dirs: str` options. +
+ #### Versionchanged + Changed in version 0.8.0: Renamed `filter` → `filters` + + **limits** + : Apply limits to the list of files/directories, and stop if one of the limits is reached. + See [File Limits](../file_limits/index.md#file-limits) +
+ #### Versionadded + Added in version 0.4.0. +
+ #### Versionchanged + Changed in version 0.8.0: Renamed `limit` → `limits` + + **options** + : File downloading options. See [`FileDownloader.Options`](options.md#onetl.file.file_downloader.options.FileDownloaderOptions) +
+ #### Versionadded + Added in version 0.3.0. + + **hwm** + : HWM class to detect changes in incremental run. See [File HWM](https://etl-entities.readthedocs.io/en/stable/hwm/file/index.html) +
+ #### WARNING + Used only in [`IncrementalStrategy`](../../strategy/incremental_strategy.md#onetl.strategy.incremental_strategy.IncrementalStrategy). +
+ #### Versionadded + Added in version 0.5.0. +
+ #### Versionchanged + Changed in version 0.10.0: Replaces deprecated `hwm_type` attribute + +### Examples + +Minimal example + +```py +from onetl.connection import SFTP +from onetl.file import FileDownloader + +sftp = SFTP(...) + +# create downloader +downloader = FileDownloader( + connection=sftp, + source_path="/path/to/remote/source", + local_path="/path/to/local", +) + +# download files to "/path/to/local" +downloader.run() +``` + +Full example + +```py +from onetl.connection import SFTP +from onetl.file import FileDownloader +from onetl.file.filter import Glob, ExcludeDir +from onetl.file.limit import MaxFilesCount, TotalFileSize + +sftp = SFTP(...) + +# create downloader with a bunch of options +downloader = FileDownloader( + connection=sftp, + source_path="/path/to/remote/source", + local_path="/path/to/local", + temp_path="/tmp", + filters=[ + Glob("*.txt"), + ExcludeDir("/path/to/remote/source/exclude_dir"), + ], + limits=[MaxFilesCount(100), TotalFileSize("10GiB")], + options=FileDownloader.Options(delete_source=True, if_exists="replace_file"), +) + +# download files to "/path/to/local", +# but only *.txt, +# excluding files from "/path/to/remote/source/exclude_dir" directory +# and stop before downloading 101 file +downloader.run() +``` + +Incremental download (by tracking list of file paths) + +```py +from onetl.connection import SFTP +from onetl.file import FileDownloader +from onetl.strategy import IncrementalStrategy +from etl_entities.hwm import FileListHWM + +sftp = SFTP(...) + +# create downloader +downloader = FileDownloader( + connection=sftp, + source_path="/path/to/remote/source", + local_path="/path/to/local", + hwm=FileListHWM( # mandatory for IncrementalStrategy + name="my_unique_hwm_name", + ), +) + +# download files to "/path/to/local", but only added since previous run +with IncrementalStrategy(): + downloader.run() +``` + +Incremental download (by tracking file modification time) + +```py +from onetl.connection import SFTP +from onetl.file import FileDownloader +from onetl.strategy import IncrementalStrategy +from etl_entities.hwm import FileModifiedTimeHWM + +sftp = SFTP(...) + +# create downloader +downloader = FileDownloader( + connection=sftp, + source_path="/path/to/remote/source", + local_path="/path/to/local", + hwm=FileModifiedTimeHWM( # mandatory for IncrementalStrategy + name="my_unique_hwm_name", + ), +) + +# download files to "/path/to/local", but only modified/created since previous run +with IncrementalStrategy(): + downloader.run() +``` + + + +#### run(files: Iterable[str | PathLike] | None = None) → [DownloadResult](result.md#onetl.file.file_downloader.result.DownloadResult) + +Method for downloading files from source to local directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### NOTE +This method can return different results depending on [Read Strategies](../../strategy/index.md#strategy) + +#### Versionadded +Added in version 0.1.0. + +* **Parameters:** + **files** + : File list to download. +
+ If empty, download files from `source_path` to `local_path`, + applying `filter`, `limit` and `hwm` to each one (if set). +
+ If not, download to `local_path` **all** input files, **ignoring** + filters, limits and HWM. +
+ #### Versionadded + Added in version 0.3.0. +* **Returns:** + `DownloadResult` + : Download result object +* **Raises:** + `onetl.exception.DirectoryNotFoundError` + : `source_path` does not found + + NotADirectoryError + : `source_path` or `local_path` is not a directory + +### Examples + +Download files from `source_path` to `local_path`: + +```pycon +>>> from onetl.file import FileDownloader +>>> downloader = FileDownloader(source_path="/remote", local_path="/local", ...) +>>> download_result = downloader.run() +>>> download_result +DownloadResult( + successful=FileSet([ + LocalPath("/local/file1.txt"), + LocalPath("/local/file2.txt"), + # directory structure is preserved + LocalPath("/local/nested/path/file3.txt"), + ]), + failed=FileSet([ + FailedRemoteFile("/remote/failed.file"), + ]), + skipped=FileSet([ + RemoteFile("/remote/already.exists"), + ]), + missing=FileSet([ + RemotePath("/remote/missing.file"), + ]), +) +``` + +Download only certain files from `source_path`: + +```pycon +>>> from onetl.file import FileDownloader +>>> downloader = FileDownloader(source_path="/remote", local_path="/local", ...) +>>> # paths could be relative or absolute, but all should be in "/remote" +>>> download_result = downloader.run( +... [ +... "/remote/file1.txt", +... "/remote/nested/path/file3.txt", +... # excluding "/remote/file2.txt" +... ] +... ) +>>> download_result +DownloadResult( + successful=FileSet([ + LocalPath("/local/file1.txt"), + # directory structure is preserved + LocalPath("/local/nested/path/file3.txt"), + ]), + failed=FileSet([]), + skipped=FileSet([]), + missing=FileSet([]), +) +``` + +Download certain files from any folder: + +```pycon +>>> from onetl.file import FileDownloader +>>> downloader = FileDownloader(local_path="/local", ...) # no source_path set +>>> # only absolute paths +>>> download_result = downloader.run( +... [ +... "/remote/file1.txt", +... "/any/nested/path/file2.txt", +... ] +... ) +>>> download_result +DownloadResult( + successful=FileSet([ + LocalPath("/local/file1.txt"), + # directory structure is NOT preserved without source_path + LocalPath("/local/file2.txt"), + ]), + failed=FileSet([]), + skipped=FileSet([]), + missing=FileSet([]), +) +``` + + + +#### view_files() → FileSet[RemoteFile] + +Get file list in the `source_path`, +after `filter`, `limit` and `hwm` applied (if any). [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### NOTE +This method can return different results depending on [Read Strategies](../../strategy/index.md#strategy) + +#### Versionadded +Added in version 0.3.0. + +* **Returns:** + FileSet[RemoteFile] + : Set of files in `source_path`, which will be downloaded by [`run`](#onetl.file.file_downloader.file_downloader.FileDownloader.run) method +* **Raises:** + `onetl.exception.DirectoryNotFoundError` + : `source_path` does not found + + NotADirectoryError + : `source_path` is not a directory + +### Examples + +View files: + +```pycon +>>> from onetl.file import FileDownloader +>>> downloader = FileDownloader(source_path="/remote", ...) +>>> downloader.view_files() +FileSet([ + RemoteFile("/remote/file1.txt"), + RemoteFile("/remote/file3.txt"), + RemoteFile("/remote/nested/file3.txt"), +]) +``` + + diff --git a/mddocs/file/file_downloader/index.md b/mddocs/file/file_downloader/index.md new file mode 100644 index 000000000..3a7b4164b --- /dev/null +++ b/mddocs/file/file_downloader/index.md @@ -0,0 +1,9 @@ + + +# File Downloader + +# File Downloader + +* [File Downloader](file_downloader.md) +* [File Downloader Options](options.md) +* [File Downloader Result](result.md) diff --git a/mddocs/file/file_downloader/options.md b/mddocs/file/file_downloader/options.md new file mode 100644 index 000000000..c40baf9b8 --- /dev/null +++ b/mddocs/file/file_downloader/options.md @@ -0,0 +1,67 @@ + + +# File Downloader Options + +### *pydantic model* onetl.file.file_downloader.options.FileDownloaderOptions + +File downloading options. + +#### Versionadded +Added in version 0.3.0. + +### Examples + +```python +from onetl.file import FileDownloader + +options = FileDownloader.Options( + if_exists="replace_entire_directory", + delete_source=True, + workers=4, +) +``` + + + +#### *field* if_exists *: FileExistBehavior* *= FileExistBehavior.ERROR* *(alias 'mode')* + +How to handle existing files in the local directory. + +Possible values: +: * `error` (default) - mark file as failed + * `ignore` - mark file as skipped + * `replace_file` - replace existing file with a new one + * `replace_entire_directory` - delete local directory content before downloading files + +#### Versionchanged +Changed in version 0.9.0: Renamed `mode` → `if_exists` + + + +#### *field* delete_source *: bool* *= False* + +If `True`, remove source file after successful download. + +If download failed, file will left intact. + +#### Versionadded +Added in version 0.2.0. + +#### Versionchanged +Changed in version 0.3.0: Move `FileUploader.delete_local` to `FileUploaderOptions` + + + +#### *field* workers *: int* *= 1* + +Number of workers to create for parallel file download. + +1 (default) means files will me downloaded sequentially. +2 or more means files will be downloaded in parallel workers. + +Recommended value is `min(32, os.cpu_count() + 4)`, e.g. `5`. + +#### Versionadded +Added in version 0.8.1. + + diff --git a/mddocs/file/file_downloader/result.md b/mddocs/file/file_downloader/result.md new file mode 100644 index 000000000..45b94233a --- /dev/null +++ b/mddocs/file/file_downloader/result.md @@ -0,0 +1,550 @@ + + +# File Downloader Result + +### *class* onetl.file.file_downloader.result.DownloadResult(\*, successful: FileSet[LocalPath] = None, failed: FileSet[FailedRemoteFile] = None, skipped: FileSet[RemoteFile] = None, missing: FileSet[RemotePath] = None) + +Representation of file download result. + +Container for file paths, divided into certain categories: + +* [`successful`](#onetl.file.file_downloader.result.DownloadResult.successful) +* [`failed`](#onetl.file.file_downloader.result.DownloadResult.failed) +* [`skipped`](#onetl.file.file_downloader.result.DownloadResult.skipped) +* [`missing`](#onetl.file.file_downloader.result.DownloadResult.missing) + +#### Versionadded +Added in version 0.3.0. + +### Examples + +```pycon +>>> from onetl.file import FileDownloader +>>> downloader = FileDownloader(local_path="/local", ...) +>>> download_result = downloader.run( +... [ +... "/remote/file1", +... "/remote/file2", +... "/failed/file", +... "/existing/file", +... "/missing/file", +... ] +... ) +>>> download_result +DownloadResult( + successful=FileSet([ + LocalPath("/local/file1"), + LocalPath("/local/file2"), + ]), + failed=FileSet([ + FailedLocalFile("/failed/file") + ]), + skipped=FileSet([ + RemoteFile("/existing/file") + ]), + missing=FileSet([ + RemotePath("/missing/file") + ]), +) +``` + + + +#### *property* details *: str* + +Return detailed information about files in the result object + +### Examples + +```pycon +>>> from onetl.impl import FailedRemoteFile, LocalPath, RemoteFile, RemotePathStat +>>> from onetl.exception import NotAFileError +>>> from onetl.file.file_result import FileResult +>>> file_result1 = FileResult( +... successful={ +... RemoteFile("/local/file", stats=RemotePathStat(st_size=1024)), +... RemoteFile("/local/another.file", stats=RemotePathStat(st_size=1024)), +... }, +... failed={ +... FailedRemoteFile( +... path="/remote/file1", +... stats=RemotePathStat(st_size=0), +... exception=NotAFileError("'/remote/file1' is not a file"), +... ), +... FailedRemoteFile( +... path="/remote/file2", +... stats=RemotePathStat(st_size=0), +... exception=PermissionError("'/remote/file2': [Errno 13] Permission denied"), +... ), +... }, +... skipped={LocalPath("/skipped/file1"), LocalPath("/skipped/file2")}, +... missing={LocalPath("/missing/file1"), LocalPath("/missing/file2")}, +... ) +>>> print(file_result1.details) +Total: 8 files (size='2.0 kB') + +Successful 2 files (size='2.0 kB'): + '/local/another.file' (size='1.0 kB') + '/local/file' (size='1.0 kB') + +Failed 2 files (size='0 Bytes'): + '/remote/file2' (size='0 Bytes') + PermissionError("'/remote/file2': [Errno 13] Permission denied") + '/remote/file1' (size='0 Bytes') + NotAFileError("'/remote/file1' is not a file") + +Skipped 2 files (size='0 Bytes'): + '/skipped/file1' + '/skipped/file2' + +Missing 2 files: + '/missing/file2' + '/missing/file1' +``` + +```pycon +>>> file_result2 = FileResult() +>>> print(file_result2.details) +No successful files + +No failed files + +No skipped files + +No missing files +``` + + + +#### dict(\*, include: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, exclude: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, by_alias: bool = False, skip_defaults: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False) → DictStrAny + +Generate a dictionary representation of the model, optionally specifying which fields to include or exclude. + + + +#### *field* failed *: FileSet[FailedRemoteFile]* *[Optional]* + +File paths (remote) which were not downloaded because of some failure + + + +#### *property* failed_count *: int* + +Get number of failed files + +### Examples + +```pycon +>>> from onetl.impl import RemoteFile +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... failed={RemoteFile("/some/file"), RemoteFile("/some/another.file")}, +... ) +>>> file_result.failed_count +2 +``` + + + +#### *property* failed_size *: int* + +Get size (in bytes) of failed files + +### Examples + +```pycon +>>> from onetl.impl import RemoteFile, RemotePathStat +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... failed={ +... RemoteFile("/some/file", stats=RemotePathStat(st_size=1024)), +... RemoteFile("/some/another.file"), stats=RemotePathStat(st_size=1024)), +... }, +... ) +>>> file_result.failed_size # in bytes +2048 +``` + + + +#### *property* is_empty *: bool* + +Returns `True` if there are no files in `successful`, `failed` and `skipped` attributes + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result1 = FileResult() +>>> file_result1.is_empty +True +>>> file_result2 = FileResult( +... successful={LocalPath("/local/file"), LocalPath("/local/another.file")}, +... ) +>>> file_result2.is_empty +False +``` + + + +#### json(\*, include: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, exclude: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, by_alias: bool = False, skip_defaults: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, encoder: Callable[[Any], Any] | None = None, models_as_dict: bool = True, \*\*dumps_kwargs: Any) → str + +Generate a JSON representation of the model, include and exclude arguments as per dict(). + +encoder is an optional function to supply as default to json.dumps(), other arguments as per json.dumps(). + + + +#### *field* missing *: FileSet[RemotePath]* *[Optional]* + +File paths (remote) which are not present in the remote file system + + + +#### *property* missing_count *: int* + +Get number of missing files + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... missing={LocalPath("/some/file"), LocalPath("/some/another.file")}, +... ) +>>> file_result.missing_count +2 +``` + + + +#### raise_if_contains_zero_size() → None + +Raise exception if `successful` attribute contains a file with zero size + +* **Raises:** + ZeroFileSizeError + : `successful` file set contains a file with zero size + +### Examples + +```pycon +>>> from onetl.exception import ZeroFileSizeError +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... successful={ +... LocalPath("/local/empty1.file"), +... LocalPath("/local/empty2.file"), +... LocalPath("/local/normal.file"), +... }, +... ) +>>> file_result.raise_if_contains_zero_size() +Traceback (most recent call last): + ... +onetl.exception.ZeroFileSizeError: 2 files out of 3 have zero size: + '/local/empty1.file' + '/local/empty2.file' +``` + + + +#### raise_if_empty() → None + +Raise exception if there are no files in `successful`, `failed` and `skipped` attributes + +* **Raises:** + EmptyFilesError + : `successful`, `failed` and `skipped` file sets are empty + +### Examples + +```pycon +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult() +>>> file_result.raise_if_empty() +Traceback (most recent call last): + ... +onetl.exception.EmptyFilesError: There are no files in the result +``` + + + +#### raise_if_failed() → None + +Raise exception if there are some files in `failed` attribute + +* **Raises:** + FailedFilesError + : `failed` file set is not empty + +### Examples + +```pycon +>>> from onetl.impl import FailedRemoteFile, RemotePathStat +>>> from onetl.exception import NotAFileError, FileMissingError +>>> from onetl.file.file_result import FileResult +>>> files_with_exception = [ +... FailedRemoteFile( +... path="/remote/file1", +... stats=RemotePathStat(st_size=0), +... exception=NotAFileError("'/remote/file1' is not a file"), +... ), +... FailedRemoteFile( +... path="/remote/file2", +... stats=RemotePathStat(st_size=0), +... exception=PermissionError("'/remote/file2': [Errno 13] Permission denied"), +... ), +... ] +>>> file_result = FileResult(failed=files_with_exception) +>>> file_result.raise_if_failed() +Traceback (most recent call last) +... +onetl.exception.FailedFilesError: Failed 2 files (size='0 bytes'): + '/remote/file1' (size='0 bytes') + NotAFileError("'/remote/file1' is not a file") + + '/remote/file2' (size='0 Bytes') + PermissionError("'/remote/file2': [Errno 13] Permission denied") +``` + + + +#### raise_if_missing() → None + +Raise exception if there are some files in `missing` attribute + +* **Raises:** + MissingFilesError + : `missing` file set is not empty + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... missing={ +... LocalPath("/missing/file1"), +... LocalPath("/missing/file2"), +... }, +... ) +>>> file_result.raise_if_missing() +Traceback (most recent call last): + ... +onetl.exception.MissingFilesError: Missing 2 files: + '/missing/file1' + '/missing/file2' +``` + + + +#### raise_if_skipped() → None + +Raise exception if there are some files in `skipped` attribute + +* **Raises:** + SkippedFilesError + : `skipped` file set is not empty + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... skipped={ +... LocalPath("/skipped/file1"), +... LocalPath("/skipped/file2"), +... }, +... ) +>>> file_result.raise_if_skipped() +Traceback (most recent call last): + ... +onetl.exception.SkippedFilesError: Skipped 2 files (15 kB): + '/skipped/file1' (10kB) + '/skipped/file2' (5 kB) +``` + + + +#### *field* skipped *: FileSet[RemoteFile]* *[Optional]* + +File paths (remote) which were skipped because of some reason + + + +#### *property* skipped_count *: int* + +Get number of skipped files + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... skipped={LocalPath("/some/file"), LocalPath("/some/another.file")}, +... ) +>>> file_result.skipped_count +2 +``` + + + +#### *property* skipped_size *: int* + +Get size (in bytes) of skipped files + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... skipped={LocalPath("/some/file"), LocalPath("/some/another.file")}, +... ) +>>> file_result.skipped_size # in bytes +1024 +``` + + + +#### *field* successful *: FileSet[LocalPath]* *[Optional]* + +File paths (local) which were downloaded successfully + + + +#### *property* successful_count *: int* + +Get number of successful files + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... successful={LocalPath("/some/file"), LocalPath("/some/another.file")}, +... ) +>>> file_result.successful_count +2 +``` + + + +#### *property* successful_size *: int* + +Get size (in bytes) of successful files + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... successful={LocalPath("/some/file"), LocalPath("/some/another.file")}, +... ) +>>> file_result.successful_size # in bytes +1024 +``` + + + +#### *property* summary *: str* + +Return short summary about files in the result object + +### Examples + +```pycon +>>> from onetl.impl import FailedRemoteFile, LocalPath, RemoteFile, RemotePathStat +>>> from onetl.exception import NotAFileError +>>> from onetl.file.file_result import FileResult +>>> file_result1 = FileResult( +... successful={ +... RemoteFile("/local/file", stats=RemotePathStat(st_size=1024)), +... RemoteFile("/local/another.file", stats=RemotePathStat(st_size=1024)), +... }, +... failed={ +... FailedRemoteFile( +... path="/remote/file1", +... stats=RemotePathStat(st_size=0), +... exception=NotAFileError("'/remote/file1' is not a file"), +... ), +... FailedRemoteFile( +... path="/remote/file2", +... stats=RemotePathStat(st_size=0), +... exception=PermissionError("'/remote/file2': [Errno 13] Permission denied"), +... ), +... }, +... skipped={LocalPath("/skipped/file1"), LocalPath("/skipped/file2")}, +... missing={LocalPath("/missing/file1"), LocalPath("/missing/file2")}, +... ) +>>> print(file_result1.summary) +Total: 8 files (size='2.0 kB') + +Successful: 2 files (size='2.0 kB') + +Failed: 2 files (size='0 Bytes') + +Skipped: 2 files (size='0 Bytes') + +Missing: 2 files +``` + +```pycon +>>> file_result2 = FileResult() +>>> print(file_result2.summary) +No files +``` + + + +#### *property* total_count *: int* + +Get total number of all files + +### Examples + +```pycon +>>> from onetl.impl import RemoteFile +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... successful={LocalPath("/local/file"), LocalPath("/local/another.file")}, +... failed={RemoteFile("/remote/file"), RemoteFile("/remote/another.file")}, +... skipped={LocalPath("/skipped/file")}, +... missing={LocalPath("/missing/file")}, +... ) +>>> file_result.total_count +6 +``` + + + +#### *property* total_size *: int* + +Get total size (in bytes) of all files + +### Examples + +```pycon +>>> from onetl.impl import RemoteFile, RemotePathStat, LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... successful={LocalPath("/local/file"), LocalPath("/local/another.file")}, +... failed={ +... RemoteFile("/remote/file", stats=RemotePathStat(st_size=1024)), +... RemoteFile("/remote/another.file", stats=RemotePathStat(st_size=1024)) +... }, +... skipped={LocalPath("/skipped/file")}, +... missing={LocalPath("/missing/file")}, +... ) +>>> file_result.total_size # in bytes +4096 +``` + + diff --git a/mddocs/file/file_filters/base.md b/mddocs/file/file_filters/base.md new file mode 100644 index 000000000..c4f8a9adc --- /dev/null +++ b/mddocs/file/file_filters/base.md @@ -0,0 +1,42 @@ + + +# Base interface + +| [`BaseFileFilter`](#onetl.base.base_file_filter.BaseFileFilter)() | Base file filter interface. | +|-----------------------------------------------------------------------------------|------------------------------------------------------------------| +| [`BaseFileFilter.match`](#onetl.base.base_file_filter.BaseFileFilter.match)(path) | Returns `True` if path is matching the filter, `False` otherwise | + +### *class* onetl.base.base_file_filter.BaseFileFilter + +Base file filter interface. + +Filters used by several onETL components, including [File Downloader](../file_downloader/file_downloader.md#file-downloader) and [File Mover](../file_mover/file_mover.md#file-mover), +to determine if a file should be handled or not. + +All filters are stateless. + +#### Versionadded +Added in version 0.8.0. + + + +#### *abstract* match(path: PathProtocol) → bool + +Returns `True` if path is matching the filter, `False` otherwise + +#### Versionadded +Added in version 0.8.0. + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> filter.match(LocalPath("/path/to/file.csv")) +True +>>> filter.match(LocalPath("/path/to/excluded.csv")) +False +>>> filter.match(LocalPath("/path/to/file.csv")) +True +``` + + diff --git a/mddocs/file/file_filters/exclude_dir.md b/mddocs/file/file_filters/exclude_dir.md new file mode 100644 index 000000000..9ad015e8a --- /dev/null +++ b/mddocs/file/file_filters/exclude_dir.md @@ -0,0 +1,47 @@ + + +# ExcludeDir + +### *class* onetl.file.filter.exclude_dir.ExcludeDir(path: str | os.PathLike) + +Filter files or directories which are included in a specific directory. + +#### Versionadded +Added in version 0.8.0: Replaces deprecated `onetl.core.FileFilter` + +* **Parameters:** + **path** + : Path to directory which should be excluded. + +### Examples + +Create exclude dir filter: + +```python +from onetl.file.filter import ExcludeDir + +exclude_dir = ExcludeDir("/export/news_parse/exclude_dir") +``` + + + +#### match(path: PathProtocol) → bool + +Returns `True` if path is matching the filter, `False` otherwise + +#### Versionadded +Added in version 0.8.0. + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> filter.match(LocalPath("/path/to/file.csv")) +True +>>> filter.match(LocalPath("/path/to/excluded.csv")) +False +>>> filter.match(LocalPath("/path/to/file.csv")) +True +``` + + diff --git a/mddocs/file/file_filters/file_filter.md b/mddocs/file/file_filters/file_filter.md new file mode 100644 index 000000000..31946cb36 --- /dev/null +++ b/mddocs/file/file_filters/file_filter.md @@ -0,0 +1,77 @@ + + +# File Filter (legacy) + +### *class* onetl.core.file_filter.file_filter.FileFilter(\*, glob: str | None = None, regexp: Pattern | None = None, exclude_dirs: List[RemotePath] = None) + +Filter files or directories by their path. + +#### Deprecated +Deprecated since version 0.8.0: Use [`Glob`](glob.md#onetl.file.filter.glob.Glob), [`Regexp`](regexp.md#onetl.file.filter.regexp.Regexp) +or [`ExcludeDir`](exclude_dir.md#onetl.file.filter.exclude_dir.ExcludeDir) instead. + +* **Parameters:** + **glob** + : Pattern (e.g. `*.csv`) for which any **file** (only file) path should match +
+ #### WARNING + Mutually exclusive with `regexp` + + **regexp** + : Regular expression (e.g. `\d+\.csv`) for which any **file** (only file) path should match. +
+ If input is a string, regular expression will be compiles using `re.IGNORECASE` and `re.DOTALL` flags +
+ #### WARNING + Mutually exclusive with `glob` + + **exclude_dirs** + : List of directories which should not be a part of a file or directory path + +### Examples + +Create exclude_dir filter: + +```python +from onetl.core import FileFilter + +file_filter = FileFilter(exclude_dirs=["/export/news_parse/exclude_dir"]) +``` + +Create glob filter: + +```python +from onetl.core import FileFilter + +file_filter = FileFilter(glob="*.csv") +``` + +Create regexp filter: + +```python +from onetl.core import FileFilter + +file_filter = FileFilter(regexp=r"\d+\.csv") + +# or + +import re + +file_filter = FileFilter(regexp=re.compile("\d+\.csv")) +``` + +Not allowed: + +```python +from onetl.core import FileFilter + +FileFilter() # will raise ValueError, at least one argument should be passed +``` + + + +#### match(path: PathProtocol) → bool + +False means it does not match the template by which you want to receive files + + diff --git a/mddocs/file/file_filters/file_mtime_filter.md b/mddocs/file/file_filters/file_mtime_filter.md new file mode 100644 index 000000000..9ec51d939 --- /dev/null +++ b/mddocs/file/file_filters/file_mtime_filter.md @@ -0,0 +1,70 @@ + + +# FileModifiedTime + +### *class* onetl.file.filter.file_mtime.FileModifiedTime(\*, since: datetime | None = None, until: datetime | None = None) + +Filter files matching a specified modification time. + +If file modification time (`.stat().st_mtime`) doesn’t match range, it will be excluded. +Doesn’t affect directories or paths without `.stat()` method. + +#### NOTE +Some filesystems return timestamps truncated to whole seconds (without millisecond part). +obj:~since and :obj\`~until\`\` values should be adjusted accordingly. + +#### Versionadded +Added in version 0.13.0. + +* **Parameters:** + **since** + : Minimal allowed file modification time. `None` means no limit. + + **until** + : Maximum allowed file modification time. `None` means no limit. + +### Examples + +Select files modified between start of the day (`00:00:00`) and hour ago: + +```python +from datetime import datetime, timedelta +from onetl.file.filter import FileModifiedTime + +hour_ago = datetime.now() - timedelta(hours=1) +day_start = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) +file_mtime = FileModifiedTime(since=day_start, until=hour_ago) +``` + +Select only files modified since hour ago: + +```python +from datetime import datetime, timedelta +from onetl.file.filter import FileModifiedTime + +hour_ago = datetime.now() - timedelta(hours=1) +file_mtime = FileModifiedTime(since=hour_ago) +``` + + + +#### match(path: PathProtocol) → bool + +Returns `True` if path is matching the filter, `False` otherwise + +#### Versionadded +Added in version 0.8.0. + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> filter.match(LocalPath("/path/to/file.csv")) +True +>>> filter.match(LocalPath("/path/to/excluded.csv")) +False +>>> filter.match(LocalPath("/path/to/file.csv")) +True +``` + + diff --git a/mddocs/file/file_filters/file_size_filter.md b/mddocs/file/file_filters/file_size_filter.md new file mode 100644 index 000000000..82255b7c8 --- /dev/null +++ b/mddocs/file/file_filters/file_size_filter.md @@ -0,0 +1,74 @@ + + +# FileSizeRange + +### *class* onetl.file.filter.file_size.FileSizeRange(\*, min: ByteSize | None = None, max: ByteSize | None = None) + +Filter files matching a specified size. + +If file size (`.stat().st_size`) doesn’t match the range, it will be excluded. +Doesn’t affect directories or paths without `.stat()` method. + +#### Versionadded +Added in version 0.13.0. + +#### NOTE +[SI unit prefixes](https://en.wikipedia.org/wiki/Byte#Multiple-byte_units) +means that `1KB` == `1 kilobyte` == `1000 bytes`. +If you need `1024 bytes`, use `1 KiB` == `1 kibibyte`. + +* **Parameters:** + **min** + : Minimal allowed file size. `None` means no limit. + + **max** + : Maximum allowed file size. `None` means no limit. + +### Examples + +Specify min and max file sizes: + +```python +from onetl.file.filter import FileSizeRange + +file_size = FileSizeRange(min="1KiB", max="100MiB") +``` + +Specify only min file size: + +```python +from onetl.file.filter import FileSizeRange + +file_size = FileSizeRange(min="1KiB") +``` + +Specify only max file size: + +```python +from onetl.file.filter import FileSizeRange + +file_size = FileSizeRange(max="100MiB") +``` + + + +#### match(path: PathProtocol) → bool + +Returns `True` if path is matching the filter, `False` otherwise + +#### Versionadded +Added in version 0.8.0. + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> filter.match(LocalPath("/path/to/file.csv")) +True +>>> filter.match(LocalPath("/path/to/excluded.csv")) +False +>>> filter.match(LocalPath("/path/to/file.csv")) +True +``` + + diff --git a/mddocs/file/file_filters/glob.md b/mddocs/file/file_filters/glob.md new file mode 100644 index 000000000..f6b727b77 --- /dev/null +++ b/mddocs/file/file_filters/glob.md @@ -0,0 +1,47 @@ + + +# Glob + +### *class* onetl.file.filter.glob.Glob(pattern: str) + +Filter files or directories with path matching a glob expression. + +#### Versionadded +Added in version 0.8.0: Replaces deprecated `onetl.core.FileFilter` + +* **Parameters:** + **pattern** + : Pattern (e.g. `*.csv`) for which any **file** (only file) path should match + +### Examples + +Create glob filter: + +```python +from onetl.file.filter import Glob + +glob = Glob("*.csv") +``` + + + +#### match(path: PathProtocol) → bool + +Returns `True` if path is matching the filter, `False` otherwise + +#### Versionadded +Added in version 0.8.0. + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> filter.match(LocalPath("/path/to/file.csv")) +True +>>> filter.match(LocalPath("/path/to/excluded.csv")) +False +>>> filter.match(LocalPath("/path/to/file.csv")) +True +``` + + diff --git a/mddocs/file/file_filters/index.md b/mddocs/file/file_filters/index.md new file mode 100644 index 000000000..40155ae9a --- /dev/null +++ b/mddocs/file/file_filters/index.md @@ -0,0 +1,20 @@ + + +# File Filters + +# File filters + +* [Glob](glob.md) +* [Regexp](regexp.md) +* [ExcludeDir](exclude_dir.md) +* [FileSizeRange](file_size_filter.md) +* [FileModifiedTime](file_mtime_filter.md) + +# Legacy + +* [File Filter (legacy)](file_filter.md) + +# For developers + +* [Base interface](base.md) +* [match_all_filters](match_all_filters.md) diff --git a/mddocs/file/file_filters/match_all_filters.md b/mddocs/file/file_filters/match_all_filters.md new file mode 100644 index 000000000..9d09eb421 --- /dev/null +++ b/mddocs/file/file_filters/match_all_filters.md @@ -0,0 +1,37 @@ + + +# match_all_filters + +### onetl.file.filter.match_all_filters.match_all_filters(path: PathProtocol, filters: Iterable[[BaseFileFilter](base.md#onetl.base.base_file_filter.BaseFileFilter)]) → bool + +Check if input path satisfies all the filters. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check. + + **filters** + : Filters to test path against. +* **Returns:** + `True` if path matches all the filters, `False` otherwise. + + If filters are empty, returns `True`. + +### Examples + +```pycon +>>> from onetl.file.filter import Glob, ExcludeDir, match_all_filters +>>> from onetl.impl import RemoteFile, RemotePathStat +>>> filters = [Glob("*.csv"), ExcludeDir("/excluded")] +>>> match_all_filters(RemoteFile("/path/to/file.csv", stats=RemotePathStat()), filters) +True +>>> match_all_filters(RemoteFile("/path/to/file.txt", stats=RemotePathStat()), filters) +False +>>> match_all_filters(RemoteFile("/excluded/path/file.csv", stats=RemotePathStat()), filters) +False +``` + + diff --git a/mddocs/file/file_filters/regexp.md b/mddocs/file/file_filters/regexp.md new file mode 100644 index 000000000..3e252ff74 --- /dev/null +++ b/mddocs/file/file_filters/regexp.md @@ -0,0 +1,59 @@ + + +# Regexp + +### *class* onetl.file.filter.regexp.Regexp(pattern: str) + +Filter files or directories with path matching a regular expression. + +#### Versionadded +Added in version 0.8.0: Replaces deprecated `onetl.core.FileFilter` + +* **Parameters:** + **pattern** + : Regular expression (e.g. `\d+\.csv`) for which any **file** (only file) path should match. +
+ If input is a string, regular expression will be compiles using `re.IGNORECASE` and `re.DOTALL` flags. + +### Examples + +Create regexp filter from string: + +```python +from onetl.file.filter import Regexp + +regexp = Regexp(r"\d+\.csv") +``` + +Create regexp filter from `re.Pattern`: + +```python +import re + +from onetl.file.filter import Regexp + +regexp = Regexp(re.compile(r"\d+\.csv", re.IGNORECASE | re.DOTALL)) +``` + + + +#### match(path: PathProtocol) → bool + +Returns `True` if path is matching the filter, `False` otherwise + +#### Versionadded +Added in version 0.8.0. + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> filter.match(LocalPath("/path/to/file.csv")) +True +>>> filter.match(LocalPath("/path/to/excluded.csv")) +False +>>> filter.match(LocalPath("/path/to/file.csv")) +True +``` + + diff --git a/mddocs/file/file_limits/base.md b/mddocs/file/file_limits/base.md new file mode 100644 index 000000000..4573bb8f4 --- /dev/null +++ b/mddocs/file/file_limits/base.md @@ -0,0 +1,108 @@ + + +# Base interface + +| [`BaseFileLimit`](#onetl.base.base_file_limit.BaseFileLimit)() | Base file limit interface. | +|--------------------------------------------------------------------------------------|-------------------------------------------------| +| [`BaseFileLimit.reset`](#onetl.base.base_file_limit.BaseFileLimit.reset)() | Resets the internal limit state. | +| [`BaseFileLimit.stops_at`](#onetl.base.base_file_limit.BaseFileLimit.stops_at)(path) | Update internal state and return current state. | +| [`BaseFileLimit.is_reached`](#onetl.base.base_file_limit.BaseFileLimit.is_reached) | Check if limit is reached. | + +### *class* onetl.base.base_file_limit.BaseFileLimit + +Base file limit interface. + +Limits used by several onETL components, including [File Downloader](../file_downloader/file_downloader.md#file-downloader) and [File Mover](../file_mover/file_mover.md#file-mover), +to determine if internal loop should be stopped. + +Unlike file filters, limits have internal state which can be updated or reset. + +#### Versionadded +Added in version 0.8.0. + + + +#### *abstract property* is_reached *: bool* + +Check if limit is reached. + +#### Versionadded +Added in version 0.8.0. + +* **Returns:** + `True` if limit is reached, `False` otherwise. + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> limit.is_reached +False +>>> limit.stops_at(LocalPath("/path/to/file.csv")) +False +>>> limit.is_reached +False +>>> # after limit is reached +>>> limit.stops_at(LocalPath("/path/to/file.csv")) +True +>>> limit.is_reached +True +``` + + + +#### *abstract* reset() → Self + +Resets the internal limit state. + +#### Versionadded +Added in version 0.8.0. + +* **Returns:** + Returns a filter of the same type, but with non-reached state. + + It could be the same filter or a new one, this is an implementation detail. + +### Examples + +```pycon +>>> limit.is_reached +True +>>> new_limit = limit.reset() +>>> new_limit.is_reached +False +``` + + + +#### *abstract* stops_at(path: PathProtocol) → bool + +Update internal state and return current state. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if limit is reached, `False` otherwise. + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> # limit is not reached yet +>>> limit.stops_at(LocalPath("/path/to/file.csv")) +False +>>> # after limit is reached +>>> limit.stops_at(LocalPath("/path/to/another.csv")) +True +>>> # at this point, .stops_at() and .is_reached will always return True, +>>> # even on inputs that returned False before. +>>> # it will be in the same state until .reset() is called +>>> limit.stops_at(LocalPath("/path/to/file.csv")) +True +``` + + diff --git a/mddocs/file/file_limits/file_limit.md b/mddocs/file/file_limits/file_limit.md new file mode 100644 index 000000000..f60fca2e8 --- /dev/null +++ b/mddocs/file/file_limits/file_limit.md @@ -0,0 +1,114 @@ + + +# File Limit (legacy) + +### *class* onetl.core.file_limit.file_limit.FileLimit(\*, count_limit: int = 100) + +Limits the number of downloaded files. + +#### Deprecated +Deprecated since version 0.8.0: Use [`MaxFilesCount`](max_files_count.md#onetl.file.limit.max_files_count.MaxFilesCount) instead. + +* **Parameters:** + **count_limit** + : Number of downloaded files at a time. + +### Examples + +Create a FileLimit object and set the amount in it: + +```python +from onetl.core import FileLimit + +limit = FileLimit(count_limit=1500) +``` + +If you create a [`onetl.file.file_downloader.file_downloader.FileDownloader`](../file_downloader/file_downloader.md#onetl.file.file_downloader.file_downloader.FileDownloader) object without +specifying the limit option, it will download with a limit of 100 files. + + + +#### *property* is_reached *: bool* + +Check if limit is reached. + +#### Versionadded +Added in version 0.8.0. + +* **Returns:** + `True` if limit is reached, `False` otherwise. + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> limit.is_reached +False +>>> limit.stops_at(LocalPath("/path/to/file.csv")) +False +>>> limit.is_reached +False +>>> # after limit is reached +>>> limit.stops_at(LocalPath("/path/to/file.csv")) +True +>>> limit.is_reached +True +``` + + + +#### reset() + +Resets the internal limit state. + +#### Versionadded +Added in version 0.8.0. + +* **Returns:** + Returns a filter of the same type, but with non-reached state. + + It could be the same filter or a new one, this is an implementation detail. + +### Examples + +```pycon +>>> limit.is_reached +True +>>> new_limit = limit.reset() +>>> new_limit.is_reached +False +``` + + + +#### stops_at(path: PathProtocol) → bool + +Update internal state and return current state. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if limit is reached, `False` otherwise. + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> # limit is not reached yet +>>> limit.stops_at(LocalPath("/path/to/file.csv")) +False +>>> # after limit is reached +>>> limit.stops_at(LocalPath("/path/to/another.csv")) +True +>>> # at this point, .stops_at() and .is_reached will always return True, +>>> # even on inputs that returned False before. +>>> # it will be in the same state until .reset() is called +>>> limit.stops_at(LocalPath("/path/to/file.csv")) +True +``` + + diff --git a/mddocs/file/file_limits/index.md b/mddocs/file/file_limits/index.md new file mode 100644 index 000000000..be23f82da --- /dev/null +++ b/mddocs/file/file_limits/index.md @@ -0,0 +1,19 @@ + + +# File Limits + +# File limits + +* [MaxFilesCount](max_files_count.md) +* [TotalFilesSize](total_files_size.md) + +# Legacy + +* [File Limit (legacy)](file_limit.md) + +# For developers + +* [Base interface](base.md) +* [limits_stop_at](limits_stop_at.md) +* [limits_reached](limits_reached.md) +* [reset_limits](reset_limits.md) diff --git a/mddocs/file/file_limits/limits_reached.md b/mddocs/file/file_limits/limits_reached.md new file mode 100644 index 000000000..bf5aeabbe --- /dev/null +++ b/mddocs/file/file_limits/limits_reached.md @@ -0,0 +1,38 @@ + + +# limits_reached + +### onetl.file.limit.limits_reached.limits_reached(limits: Iterable[[BaseFileLimit](base.md#onetl.base.base_file_limit.BaseFileLimit)]) → bool + +Check if any of limits reached. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **limits** + : Limits to test. +* **Returns:** + `True` if any of limits is reached, `False` otherwise. + + If no limits are passed, returns `False`. + +### Examples + +```pycon +>>> from onetl.file.limit import MaxFilesCount, limits_reached, limits_stop_at +>>> from onetl.impl import LocalPath +>>> limits = [MaxFilesCount(2)] +>>> limits_reached(limits) +False +>>> limits_stop_at(LocalPath("/path/to/file1.csv"), limits) +False +>>> limits_stop_at(LocalPath("/path/to/file2.csv"), limits) +False +>>> limits_stop_at(LocalPath("/path/to/file3.csv"), limits) +True +>>> limits_reached(limits) +True +``` + + diff --git a/mddocs/file/file_limits/limits_stop_at.md b/mddocs/file/file_limits/limits_stop_at.md new file mode 100644 index 000000000..f41606838 --- /dev/null +++ b/mddocs/file/file_limits/limits_stop_at.md @@ -0,0 +1,37 @@ + + +# limits_stop_at + +### onetl.file.limit.limits_stop_at.limits_stop_at(path: PathProtocol, limits: Iterable[[BaseFileLimit](base.md#onetl.base.base_file_limit.BaseFileLimit)]) → bool + +Check if some of limits stops at given path. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check. + + **limits** + : Limits to test path against. +* **Returns:** + `True` if any of limit is reached while handling the path, `False` otherwise. + + If no limits are passed, returns `False`. + +### Examples + +```pycon +>>> from onetl.file.limit import MaxFilesCount, limits_stop_at +>>> from onetl.impl import LocalPath +>>> limits = [MaxFilesCount(2)] +>>> limits_stop_at(LocalPath("/path/to/file1.csv"), limits) +False +>>> limits_stop_at(LocalPath("/path/to/file2.csv"), limits) +False +>>> limits_stop_at(LocalPath("/path/to/file3.csv"), limits) +True +``` + + diff --git a/mddocs/file/file_limits/max_files_count.md b/mddocs/file/file_limits/max_files_count.md new file mode 100644 index 000000000..1b10149b1 --- /dev/null +++ b/mddocs/file/file_limits/max_files_count.md @@ -0,0 +1,114 @@ + + +# MaxFilesCount + +### *class* onetl.file.limit.max_files_count.MaxFilesCount(limit: int) + +Limits the total number of files handled by [File Downloader](../file_downloader/file_downloader.md#file-downloader) or [File Mover](../file_mover/file_mover.md#file-mover). + +All files until specified limit (including) will be downloaded/moved, but `limit+1` will not. + +This doesn’t apply to directories. + +#### Versionadded +Added in version 0.8.0: Replaces deprecated `onetl.core.FileLimit` + +* **Parameters:** + **limit** + +### Examples + +Create filter which allows to download/move up to 100 files, but stops on 101: + +```python +from onetl.file.limit import MaxFilesCount + +limit = MaxFilesCount(100) +``` + + + +#### *property* is_reached *: bool* + +Check if limit is reached. + +#### Versionadded +Added in version 0.8.0. + +* **Returns:** + `True` if limit is reached, `False` otherwise. + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> limit.is_reached +False +>>> limit.stops_at(LocalPath("/path/to/file.csv")) +False +>>> limit.is_reached +False +>>> # after limit is reached +>>> limit.stops_at(LocalPath("/path/to/file.csv")) +True +>>> limit.is_reached +True +``` + + + +#### reset() + +Resets the internal limit state. + +#### Versionadded +Added in version 0.8.0. + +* **Returns:** + Returns a filter of the same type, but with non-reached state. + + It could be the same filter or a new one, this is an implementation detail. + +### Examples + +```pycon +>>> limit.is_reached +True +>>> new_limit = limit.reset() +>>> new_limit.is_reached +False +``` + + + +#### stops_at(path: PathProtocol) → bool + +Update internal state and return current state. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if limit is reached, `False` otherwise. + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> # limit is not reached yet +>>> limit.stops_at(LocalPath("/path/to/file.csv")) +False +>>> # after limit is reached +>>> limit.stops_at(LocalPath("/path/to/another.csv")) +True +>>> # at this point, .stops_at() and .is_reached will always return True, +>>> # even on inputs that returned False before. +>>> # it will be in the same state until .reset() is called +>>> limit.stops_at(LocalPath("/path/to/file.csv")) +True +``` + + diff --git a/mddocs/file/file_limits/reset_limits.md b/mddocs/file/file_limits/reset_limits.md new file mode 100644 index 000000000..23a368345 --- /dev/null +++ b/mddocs/file/file_limits/reset_limits.md @@ -0,0 +1,39 @@ + + +# reset_limits + +### onetl.file.limit.reset_limits.reset_limits(limits: Iterable[[BaseFileLimit](base.md#onetl.base.base_file_limit.BaseFileLimit)]) → list[[BaseFileLimit](base.md#onetl.base.base_file_limit.BaseFileLimit)] + +Reset limits state. + +* **Parameters:** + **limits** + : Limits to reset. +* **Returns:** + List with limits, but with reset state. + + List may contain original filters with reset state, or new copies. + + This is an implementation detail of [`reset`](base.md#onetl.base.base_file_limit.BaseFileLimit.reset) method. + +### Examples + +```pycon +>>> from onetl.file.limit import MaxFilesCount, limits_reached, limits_stop_at, reset_limits +>>> from onetl.impl import LocalPath +>>> limits = [MaxFilesCount(1)] +>>> limits_reached(limits) +False +>>> # do something +>>> limits_stop_at(LocalPath("/path/to/file1.csv"), limits) +False +>>> limits_stop_at(LocalPath("/path/to/file2.csv"), limits) +True +>>> limits_reached(limits) +True +>>> new_limits = reset_limits(limits) +>>> limits_reached(new_limits) +False +``` + + diff --git a/mddocs/file/file_limits/total_files_size.md b/mddocs/file/file_limits/total_files_size.md new file mode 100644 index 000000000..48c56bc5b --- /dev/null +++ b/mddocs/file/file_limits/total_files_size.md @@ -0,0 +1,122 @@ + + +# TotalFilesSize + +### *class* onetl.file.limit.total_files_size.TotalFilesSize(limit: int | str) + +Limits the total size of files handled by [File Downloader](../file_downloader/file_downloader.md#file-downloader) or [File Mover](../file_mover/file_mover.md#file-mover). + +Calculates the sum of downloaded/moved files size (`.stat().st_size`), +and checks that this sum is less or equal to specified limit. + +After limit is reached, no more files will be downloaded/moved. + +Doesn’t affect directories, paths without `.stat()` method or files with zero size. + +#### Versionadded +Added in version 0.13.0. + +#### NOTE +[SI unit prefixes](https://en.wikipedia.org/wiki/Byte#Multiple-byte_units) +means that `1KB` == `1 kilobyte` == `1000 bytes`. +If you need `1024 bytes`, use `1 KiB` == `1 kibibyte`. + +* **Parameters:** + **limit** + +### Examples + +Create filter which allows to download/move files with total size up to 1GiB, but not higher: + +```python +from onetl.file.limit import MaxFilesCount + +limit = TotalFilesSize("1GiB") +``` + + + +#### *property* is_reached *: bool* + +Check if limit is reached. + +#### Versionadded +Added in version 0.8.0. + +* **Returns:** + `True` if limit is reached, `False` otherwise. + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> limit.is_reached +False +>>> limit.stops_at(LocalPath("/path/to/file.csv")) +False +>>> limit.is_reached +False +>>> # after limit is reached +>>> limit.stops_at(LocalPath("/path/to/file.csv")) +True +>>> limit.is_reached +True +``` + + + +#### reset() + +Resets the internal limit state. + +#### Versionadded +Added in version 0.8.0. + +* **Returns:** + Returns a filter of the same type, but with non-reached state. + + It could be the same filter or a new one, this is an implementation detail. + +### Examples + +```pycon +>>> limit.is_reached +True +>>> new_limit = limit.reset() +>>> new_limit.is_reached +False +``` + + + +#### stops_at(path: PathProtocol) → bool + +Update internal state and return current state. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **path** + : Path to check +* **Returns:** + `True` if limit is reached, `False` otherwise. + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> # limit is not reached yet +>>> limit.stops_at(LocalPath("/path/to/file.csv")) +False +>>> # after limit is reached +>>> limit.stops_at(LocalPath("/path/to/another.csv")) +True +>>> # at this point, .stops_at() and .is_reached will always return True, +>>> # even on inputs that returned False before. +>>> # it will be in the same state until .reset() is called +>>> limit.stops_at(LocalPath("/path/to/file.csv")) +True +``` + + diff --git a/mddocs/file/file_mover/file_mover.md b/mddocs/file/file_mover/file_mover.md new file mode 100644 index 000000000..1f7b7ce0b --- /dev/null +++ b/mddocs/file/file_mover/file_mover.md @@ -0,0 +1,243 @@ + + +# File Mover + +| [`FileMover`](#onetl.file.file_mover.file_mover.FileMover) | Allows you to move files between different directories in a filesystem, and return an object with move result summary. | +|------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------| +| [`FileMover.run`](#onetl.file.file_mover.file_mover.FileMover.run)([files]) | Method for moving files from source to target directory. | +| [`FileMover.view_files`](#onetl.file.file_mover.file_mover.FileMover.view_files)() | Get file list in the `source_path`, after `filter` and `limit` applied (if any). | + +### *class* onetl.file.file_mover.file_mover.FileMover(\*, connection: ~onetl.base.base_file_connection.BaseFileConnection, target_path: ~onetl.impl.remote_path.RemotePath, source_path: ~onetl.impl.remote_path.RemotePath | None = None, filters: ~typing.List[~onetl.base.base_file_filter.BaseFileFilter] = None, limits: ~typing.List[~onetl.base.base_file_limit.BaseFileLimit] = None, options: ~onetl.file.file_mover.options.FileMoverOptions = FileMoverOptions(if_exists=, workers=1)) + +Allows you to move files between different directories in a filesystem, +and return an object with move result summary. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### NOTE +This class is used to move files **only** within the same connection, + +It does NOT support direct file transfer between filesystems, like `FTP -> SFTP`. +You should use [File Downloader](../file_downloader/file_downloader.md#file-downloader) + [File Uploader](../file_uploader/file_uploader.md#file-uploader) to implement `FTP -> local dir -> SFTP`. + +#### WARNING +This class does **not** support read strategies. + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **connection** + : Class which contains File system connection properties. See [File Connections](../../connection/file_connection/index.md#file-connections) section. + + **target_path** + : Remote path to move files to + + **source_path** + : Remote path to move files from. +
+ Could be `None`, but only if you pass absolute file paths directly to + [`run`](#onetl.file.file_mover.file_mover.FileMover.run) method + + **filters** + : Return only files/directories matching these filters. See [File Filters](../file_filters/index.md#file-filters) + + **limits** + : Apply limits to the list of files/directories, and stop if one of the limits is reached. + See [File Limits](../file_limits/index.md#file-limits) + + **options** + : File moving options. See [`FileMover.Options`](options.md#onetl.file.file_mover.options.FileMoverOptions) + +### Examples + +Minimal example + +```py +from onetl.connection import SFTP +from onetl.file import FileMover + +sftp = SFTP(...) + +# create mover +mover = FileMover( + connection=sftp, + source_path="/path/to/source/dir", + target_path="/path/to/target/dir", +) + +# move files from "/path/to/source/dir" to "/path/to/target/dir" +mover.run() +``` + +Full example + +```py +from onetl.connection import SFTP +from onetl.file import FileMover +from onetl.file.filter import Glob, ExcludeDir +from onetl.file.limit import MaxFilesCount, TotalFilesSize + +sftp = SFTP(...) + +# create mover with a bunch of options +mover = FileMover( + connection=sftp, + source_path="/path/to/source/dir", + target_path="/path/to/target/dir", + filters=[ + Glob("*.txt"), + ExcludeDir("/path/to/source/dir/exclude"), + ], + limits=[MaxFilesCount(100), TotalFileSize("10GiB")], + options=FileMover.Options(if_exists="replace_file"), +) + +# move files from "/path/to/source/dir" to "/path/to/target/dir", +# but only *.txt files +# excluding files from "/path/to/source/dir/exclude" directory +# and stop before downloading 101 file +mover.run() +``` + + + +#### run(files: Iterable[str | PathLike] | None = None) → [MoveResult](result.md#onetl.file.file_mover.result.MoveResult) + +Method for moving files from source to target directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Parameters:** + **files** + : File list to move. +
+ If empty, move files from `source_path` to `target_path`, + applying `filter` and `limit` to each one (if set). +
+ If not, move to `target_path` **all** input files, **without** + any filtering and limiting. +* **Returns:** + `MoveResult` + : Move result object +* **Raises:** + `onetl.exception.DirectoryNotFoundError` + : `source_path` does not found + + NotADirectoryError + : `source_path` or `target_path` is not a directory + +### Examples + +Move files from `source_path`: + +```pycon +>>> from onetl.file import FileMover +>>> mover = FileMover(source_path="/source", target_path="/target", ...) +>>> move_result = mover.run() +>>> move_result +MoveResult( + successful=FileSet([ + RemoteFile("/target/file1.txt"), + RemoteFile("/target/file2.txt"), + # directory structure is preserved + RemoteFile("/target/nested/path/file3.txt"), + ]), + failed=FileSet([ + FailedRemoteFile("/source/failed.file"), + ]), + skipped=FileSet([ + RemoteFile("/source/already.exists"), + ]), + missing=FileSet([ + RemotePath("/source/missing.file"), + ]), +) +``` + +Move only certain files from `source_path`: + +```pycon +>>> from onetl.file import FileMover +>>> mover = FileMover(source_path="/source", target_path="/target", ...) +>>> # paths could be relative or absolute, but all should be in "/source" +>>> move_result = mover.run( +... [ +... "/source/file1.txt", +... "/source/nested/path/file3.txt", +... # excluding "/source/file2.txt" +... ] +... ) +>>> move_result +MoveResult( + successful=FileSet([ + RemoteFile("/target/file1.txt"), + # directory structure is preserved + RemoteFile("/target/nested/path/file3.txt"), + ]), + failed=FileSet([]), + skipped=FileSet([]), + missing=FileSet([]), +) +``` + +Move certain files from any folder: + +```pycon +>>> from onetl.file import FileMover +>>> mover = FileMover(target_path="/target", ...) # no source_path set +>>> # only absolute paths +>>> move_result = mover.run( +... [ +... "/remote/file1.txt", +... "/any/nested/path/file3.txt", +... ] +... ) +>>> move_result +MoveResult( + successful=FileSet([ + RemoteFile("/target/file1.txt"), + # directory structure is NOT preserved without source_path + RemoteFile("/target/file3.txt"), + ]), + failed=FileSet([]), + skipped=FileSet([]), + missing=FileSet([]), +) +``` + + + +#### view_files() → FileSet[RemoteFile] + +Get file list in the `source_path`, +after `filter` and `limit` applied (if any). [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.8.0. + +* **Returns:** + FileSet[RemoteFile] + : Set of files in `source_path`, which will be moved by [`run`](#onetl.file.file_mover.file_mover.FileMover.run) method +* **Raises:** + `onetl.exception.DirectoryNotFoundError` + : `source_path` does not found + + NotADirectoryError + : `source_path` is not a directory + +### Examples + +View files: + +```pycon +>>> from onetl.file import FileMover +>>> mover = FileMover(source_path="/remote", ...) +>>> mover.view_files() +FileSet([ + RemoteFile("/remote/file1.txt"), + RemoteFile("/remote/file2.txt"), + RemoteFile("/remote/nested/path/file3.txt"), +]) +``` + + diff --git a/mddocs/file/file_mover/index.md b/mddocs/file/file_mover/index.md new file mode 100644 index 000000000..c8b55c7d0 --- /dev/null +++ b/mddocs/file/file_mover/index.md @@ -0,0 +1,9 @@ + + +# File Mover + +# File Mover + +* [File Mover](file_mover.md) +* [File Mover Options](options.md) +* [File Mover Result](result.md) diff --git a/mddocs/file/file_mover/options.md b/mddocs/file/file_mover/options.md new file mode 100644 index 000000000..66e34176a --- /dev/null +++ b/mddocs/file/file_mover/options.md @@ -0,0 +1,55 @@ + + +# File Mover Options + +### *pydantic model* onetl.file.file_mover.options.FileMoverOptions + +File moving options. + +#### Versionadded +Added in version 0.8.0. + +### Examples + +```python +from onetl.file import FileMover + +options = FileMover.Options( + if_exists="replace_entire_directory", + workers=4, +) +``` + + + +#### *field* if_exists *: FileExistBehavior* *= FileExistBehavior.ERROR* *(alias 'mode')* + +How to handle existing files in the local directory. + +Possible values: +: * `error` (default) - mark file as failed + * `ignore` - mark file as skipped + * `replace_file` - replace existing file with a new one + * `replace_entire_directory` - delete directory content before moving files + +#### Versionadded +Added in version 0.8.0. + +#### Versionchanged +Changed in version 0.9.0: Renamed `mode` → `if_exists` + + + +#### *field* workers *: int* *= 1* + +Number of workers to create for parallel file moving. + +1 (default) means files will me moved sequentially. +2 or more means files will be moved in parallel workers. + +Recommended value is `min(32, os.cpu_count() + 4)`, e.g. `5`. + +#### Versionadded +Added in version 0.8.1. + + diff --git a/mddocs/file/file_mover/result.md b/mddocs/file/file_mover/result.md new file mode 100644 index 000000000..815388b8f --- /dev/null +++ b/mddocs/file/file_mover/result.md @@ -0,0 +1,550 @@ + + +# File Mover Result + +### *class* onetl.file.file_mover.result.MoveResult(\*, successful: FileSet[RemoteFile] = None, failed: FileSet[FailedRemoteFile] = None, skipped: FileSet[RemoteFile] = None, missing: FileSet[RemotePath] = None) + +Representation of file move result. + +Container for file paths, divided into certain categories: + +* [`successful`](#onetl.file.file_mover.result.MoveResult.successful) +* [`failed`](#onetl.file.file_mover.result.MoveResult.failed) +* [`skipped`](#onetl.file.file_mover.result.MoveResult.skipped) +* [`missing`](#onetl.file.file_mover.result.MoveResult.missing) + +#### Versionadded +Added in version 0.8.0. + +### Examples + +```pycon +>>> from onetl.file import FileMover +>>> mover = FileMover(local_path="/local", ...) +>>> move_result = mover.run( +... [ +... "/source/file1", +... "/source/file2", +... "/failed/file", +... "/existing/file", +... "/missing/file", +... ] +... ) +>>> move_result +MoveResult( + successful=FileSet([ + RemoteFile("/target/file1"), + RemoteFile("/target/file2"), + ]), + failed=FileSet([ + FailedLocalFile("/failed/file") + ]), + skipped=FileSet([ + RemoteFile("/existing/file") + ]), + missing=FileSet([ + RemotePath("/missing/file") + ]), +) +``` + + + +#### *property* details *: str* + +Return detailed information about files in the result object + +### Examples + +```pycon +>>> from onetl.impl import FailedRemoteFile, LocalPath, RemoteFile, RemotePathStat +>>> from onetl.exception import NotAFileError +>>> from onetl.file.file_result import FileResult +>>> file_result1 = FileResult( +... successful={ +... RemoteFile("/local/file", stats=RemotePathStat(st_size=1024)), +... RemoteFile("/local/another.file", stats=RemotePathStat(st_size=1024)), +... }, +... failed={ +... FailedRemoteFile( +... path="/remote/file1", +... stats=RemotePathStat(st_size=0), +... exception=NotAFileError("'/remote/file1' is not a file"), +... ), +... FailedRemoteFile( +... path="/remote/file2", +... stats=RemotePathStat(st_size=0), +... exception=PermissionError("'/remote/file2': [Errno 13] Permission denied"), +... ), +... }, +... skipped={LocalPath("/skipped/file1"), LocalPath("/skipped/file2")}, +... missing={LocalPath("/missing/file1"), LocalPath("/missing/file2")}, +... ) +>>> print(file_result1.details) +Total: 8 files (size='2.0 kB') + +Successful 2 files (size='2.0 kB'): + '/local/another.file' (size='1.0 kB') + '/local/file' (size='1.0 kB') + +Failed 2 files (size='0 Bytes'): + '/remote/file2' (size='0 Bytes') + PermissionError("'/remote/file2': [Errno 13] Permission denied") + '/remote/file1' (size='0 Bytes') + NotAFileError("'/remote/file1' is not a file") + +Skipped 2 files (size='0 Bytes'): + '/skipped/file1' + '/skipped/file2' + +Missing 2 files: + '/missing/file2' + '/missing/file1' +``` + +```pycon +>>> file_result2 = FileResult() +>>> print(file_result2.details) +No successful files + +No failed files + +No skipped files + +No missing files +``` + + + +#### dict(\*, include: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, exclude: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, by_alias: bool = False, skip_defaults: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False) → DictStrAny + +Generate a dictionary representation of the model, optionally specifying which fields to include or exclude. + + + +#### *field* failed *: FileSet[FailedRemoteFile]* *[Optional]* + +File paths (remote) which were not moved because of some failure + + + +#### *property* failed_count *: int* + +Get number of failed files + +### Examples + +```pycon +>>> from onetl.impl import RemoteFile +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... failed={RemoteFile("/some/file"), RemoteFile("/some/another.file")}, +... ) +>>> file_result.failed_count +2 +``` + + + +#### *property* failed_size *: int* + +Get size (in bytes) of failed files + +### Examples + +```pycon +>>> from onetl.impl import RemoteFile, RemotePathStat +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... failed={ +... RemoteFile("/some/file", stats=RemotePathStat(st_size=1024)), +... RemoteFile("/some/another.file"), stats=RemotePathStat(st_size=1024)), +... }, +... ) +>>> file_result.failed_size # in bytes +2048 +``` + + + +#### *property* is_empty *: bool* + +Returns `True` if there are no files in `successful`, `failed` and `skipped` attributes + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result1 = FileResult() +>>> file_result1.is_empty +True +>>> file_result2 = FileResult( +... successful={LocalPath("/local/file"), LocalPath("/local/another.file")}, +... ) +>>> file_result2.is_empty +False +``` + + + +#### json(\*, include: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, exclude: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, by_alias: bool = False, skip_defaults: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, encoder: Callable[[Any], Any] | None = None, models_as_dict: bool = True, \*\*dumps_kwargs: Any) → str + +Generate a JSON representation of the model, include and exclude arguments as per dict(). + +encoder is an optional function to supply as default to json.dumps(), other arguments as per json.dumps(). + + + +#### *field* missing *: FileSet[RemotePath]* *[Optional]* + +File paths (remote) which are not present in the remote file system + + + +#### *property* missing_count *: int* + +Get number of missing files + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... missing={LocalPath("/some/file"), LocalPath("/some/another.file")}, +... ) +>>> file_result.missing_count +2 +``` + + + +#### raise_if_contains_zero_size() → None + +Raise exception if `successful` attribute contains a file with zero size + +* **Raises:** + ZeroFileSizeError + : `successful` file set contains a file with zero size + +### Examples + +```pycon +>>> from onetl.exception import ZeroFileSizeError +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... successful={ +... LocalPath("/local/empty1.file"), +... LocalPath("/local/empty2.file"), +... LocalPath("/local/normal.file"), +... }, +... ) +>>> file_result.raise_if_contains_zero_size() +Traceback (most recent call last): + ... +onetl.exception.ZeroFileSizeError: 2 files out of 3 have zero size: + '/local/empty1.file' + '/local/empty2.file' +``` + + + +#### raise_if_empty() → None + +Raise exception if there are no files in `successful`, `failed` and `skipped` attributes + +* **Raises:** + EmptyFilesError + : `successful`, `failed` and `skipped` file sets are empty + +### Examples + +```pycon +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult() +>>> file_result.raise_if_empty() +Traceback (most recent call last): + ... +onetl.exception.EmptyFilesError: There are no files in the result +``` + + + +#### raise_if_failed() → None + +Raise exception if there are some files in `failed` attribute + +* **Raises:** + FailedFilesError + : `failed` file set is not empty + +### Examples + +```pycon +>>> from onetl.impl import FailedRemoteFile, RemotePathStat +>>> from onetl.exception import NotAFileError, FileMissingError +>>> from onetl.file.file_result import FileResult +>>> files_with_exception = [ +... FailedRemoteFile( +... path="/remote/file1", +... stats=RemotePathStat(st_size=0), +... exception=NotAFileError("'/remote/file1' is not a file"), +... ), +... FailedRemoteFile( +... path="/remote/file2", +... stats=RemotePathStat(st_size=0), +... exception=PermissionError("'/remote/file2': [Errno 13] Permission denied"), +... ), +... ] +>>> file_result = FileResult(failed=files_with_exception) +>>> file_result.raise_if_failed() +Traceback (most recent call last) +... +onetl.exception.FailedFilesError: Failed 2 files (size='0 bytes'): + '/remote/file1' (size='0 bytes') + NotAFileError("'/remote/file1' is not a file") + + '/remote/file2' (size='0 Bytes') + PermissionError("'/remote/file2': [Errno 13] Permission denied") +``` + + + +#### raise_if_missing() → None + +Raise exception if there are some files in `missing` attribute + +* **Raises:** + MissingFilesError + : `missing` file set is not empty + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... missing={ +... LocalPath("/missing/file1"), +... LocalPath("/missing/file2"), +... }, +... ) +>>> file_result.raise_if_missing() +Traceback (most recent call last): + ... +onetl.exception.MissingFilesError: Missing 2 files: + '/missing/file1' + '/missing/file2' +``` + + + +#### raise_if_skipped() → None + +Raise exception if there are some files in `skipped` attribute + +* **Raises:** + SkippedFilesError + : `skipped` file set is not empty + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... skipped={ +... LocalPath("/skipped/file1"), +... LocalPath("/skipped/file2"), +... }, +... ) +>>> file_result.raise_if_skipped() +Traceback (most recent call last): + ... +onetl.exception.SkippedFilesError: Skipped 2 files (15 kB): + '/skipped/file1' (10kB) + '/skipped/file2' (5 kB) +``` + + + +#### *field* skipped *: FileSet[RemoteFile]* *[Optional]* + +File paths (remote) which were skipped because of some reason + + + +#### *property* skipped_count *: int* + +Get number of skipped files + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... skipped={LocalPath("/some/file"), LocalPath("/some/another.file")}, +... ) +>>> file_result.skipped_count +2 +``` + + + +#### *property* skipped_size *: int* + +Get size (in bytes) of skipped files + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... skipped={LocalPath("/some/file"), LocalPath("/some/another.file")}, +... ) +>>> file_result.skipped_size # in bytes +1024 +``` + + + +#### *field* successful *: FileSet[RemoteFile]* *[Optional]* + +File paths (local) which were moved successfully + + + +#### *property* successful_count *: int* + +Get number of successful files + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... successful={LocalPath("/some/file"), LocalPath("/some/another.file")}, +... ) +>>> file_result.successful_count +2 +``` + + + +#### *property* successful_size *: int* + +Get size (in bytes) of successful files + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... successful={LocalPath("/some/file"), LocalPath("/some/another.file")}, +... ) +>>> file_result.successful_size # in bytes +1024 +``` + + + +#### *property* summary *: str* + +Return short summary about files in the result object + +### Examples + +```pycon +>>> from onetl.impl import FailedRemoteFile, LocalPath, RemoteFile, RemotePathStat +>>> from onetl.exception import NotAFileError +>>> from onetl.file.file_result import FileResult +>>> file_result1 = FileResult( +... successful={ +... RemoteFile("/local/file", stats=RemotePathStat(st_size=1024)), +... RemoteFile("/local/another.file", stats=RemotePathStat(st_size=1024)), +... }, +... failed={ +... FailedRemoteFile( +... path="/remote/file1", +... stats=RemotePathStat(st_size=0), +... exception=NotAFileError("'/remote/file1' is not a file"), +... ), +... FailedRemoteFile( +... path="/remote/file2", +... stats=RemotePathStat(st_size=0), +... exception=PermissionError("'/remote/file2': [Errno 13] Permission denied"), +... ), +... }, +... skipped={LocalPath("/skipped/file1"), LocalPath("/skipped/file2")}, +... missing={LocalPath("/missing/file1"), LocalPath("/missing/file2")}, +... ) +>>> print(file_result1.summary) +Total: 8 files (size='2.0 kB') + +Successful: 2 files (size='2.0 kB') + +Failed: 2 files (size='0 Bytes') + +Skipped: 2 files (size='0 Bytes') + +Missing: 2 files +``` + +```pycon +>>> file_result2 = FileResult() +>>> print(file_result2.summary) +No files +``` + + + +#### *property* total_count *: int* + +Get total number of all files + +### Examples + +```pycon +>>> from onetl.impl import RemoteFile +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... successful={LocalPath("/local/file"), LocalPath("/local/another.file")}, +... failed={RemoteFile("/remote/file"), RemoteFile("/remote/another.file")}, +... skipped={LocalPath("/skipped/file")}, +... missing={LocalPath("/missing/file")}, +... ) +>>> file_result.total_count +6 +``` + + + +#### *property* total_size *: int* + +Get total size (in bytes) of all files + +### Examples + +```pycon +>>> from onetl.impl import RemoteFile, RemotePathStat, LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... successful={LocalPath("/local/file"), LocalPath("/local/another.file")}, +... failed={ +... RemoteFile("/remote/file", stats=RemotePathStat(st_size=1024)), +... RemoteFile("/remote/another.file", stats=RemotePathStat(st_size=1024)) +... }, +... skipped={LocalPath("/skipped/file")}, +... missing={LocalPath("/missing/file")}, +... ) +>>> file_result.total_size # in bytes +4096 +``` + + diff --git a/mddocs/file/file_uploader/file_uploader.md b/mddocs/file/file_uploader/file_uploader.md new file mode 100644 index 000000000..98ebe3165 --- /dev/null +++ b/mddocs/file/file_uploader/file_uploader.md @@ -0,0 +1,241 @@ + + +# File Uploader + +| [`FileUploader`](#onetl.file.file_uploader.file_uploader.FileUploader) | Allows you to upload files to a remote source with specified file connection and parameters, and return an object with upload result summary. | +|------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| +| [`FileUploader.run`](#onetl.file.file_uploader.file_uploader.FileUploader.run)([files]) | Method for uploading files to remote host. | +| [`FileUploader.view_files`](#onetl.file.file_uploader.file_uploader.FileUploader.view_files)() | Get file list in the `local_path`. | + +### *class* onetl.file.file_uploader.file_uploader.FileUploader(\*, connection: ~onetl.base.base_file_connection.BaseFileConnection, target_path: ~onetl.impl.remote_path.RemotePath, local_path: ~onetl.impl.local_path.LocalPath | None = None, temp_path: ~onetl.impl.remote_path.RemotePath | None = None, options: ~onetl.file.file_uploader.options.FileUploaderOptions = FileUploaderOptions(if_exists=, delete_local=False, workers=1)) + +Allows you to upload files to a remote source with specified file connection +and parameters, and return an object with upload result summary. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### NOTE +This class is used to upload files **only** from local directory to the remote one. + +It does NOT support direct file transfer between filesystems, like `FTP -> SFTP`. +You should use [File Downloader](../file_downloader/file_downloader.md#file-downloader) + FileUploader to implement `FTP -> local dir -> SFTP`. + +#### WARNING +This class does **not** support read strategies. + +#### Versionadded +Added in version 0.1.0. + +#### Versionchanged +Changed in version 0.8.0: Moved `onetl.core.FileDownloader` → `onetl.file.FileDownloader` + +* **Parameters:** + **connection** + : Class which contains File system connection properties. See [File Connections](../../connection/file_connection/index.md#file-connections) section. + + **target_path** + : Remote path where want you upload files to + + **local_path** + : The local directory from which the data is loaded. +
+ Could be `None`, but only if you pass absolute file paths directly to + [`run`](#onetl.file.file_uploader.file_uploader.FileUploader.run) method +
+ #### Versionadded + Added in version 0.3.0. + + **temp_path** + : If set, this path will be used for uploading a file, and then renaming it to the target file path. + If `None` (default since v0.5.0) is passed, files are uploaded directly to `target_path`. +
+ #### WARNING + In case of production ETL pipelines, please set a value for `temp_path` (NOT `None`). + This allows to properly handle upload interruption, + without creating half-uploaded files in the target, + because unlike file upload, `rename` call is atomic. +
+ #### WARNING + In case of connections like SFTP or FTP, which can have multiple underlying filesystems, + please pass `temp_path` path on the SAME filesystem as `target_path`. + Otherwise instead of `rename`, remote OS will move file between filesystems, + which is NOT atomic operation. +
+ #### Versionchanged + Changed in version 0.5.0: Default changed from `/tmp` to `None` + + **options** + : File upload options. See [`FileUploader.Options`](options.md#onetl.file.file_uploader.options.FileUploaderOptions) + +### Examples + +Minimal example + +```py +from onetl.connection import HDFS +from onetl.file import FileUploader + +hdfs = HDFS(...) + +uploader = FileUploader( + connection=hdfs, + target_path="/path/to/remote/source", +) +``` + +Full example + +```py +from onetl.connection import HDFS +from onetl.file import FileUploader + +hdfs = HDFS(...) + +uploader = FileUploader( + connection=hdfs, + target_path="/path/to/remote/source", + temp_path="/user/onetl", + local_path="/some/local/directory", + options=FileUploader.Options(delete_local=True, if_exists="overwrite"), +) +``` + + + +#### run(files: Iterable[str | PathLike] | None = None) → [UploadResult](result.md#onetl.file.file_uploader.result.UploadResult) + +Method for uploading files to remote host. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.1.0. + +* **Parameters:** + **files** + : File list to upload. +
+ If empty, upload files from `local_path`. +* **Returns:** + `UploadResult` + : Upload result object +* **Raises:** + `onetl.exception.DirectoryNotFoundError` + : `local_path` does not found + + NotADirectoryError + : `local_path` is not a directory + + ValueError + : File in `files` argument does not match `local_path` + +### Examples + +Upload files from `local_path` to `target_path`: + +```pycon +>>> from onetl.file import FileUploader +>>> uploader = FileUploader(local_path="/local", target_path="/remote", ...) +>>> upload_result = uploader.run() +>>> upload_result +UploadResult( + successful=FileSet([ + RemoteFile("/remote/file1"), + RemoteFile("/remote/file2"), + # directory structure is preserved + RemoteFile("/remote/nested/path/file3") + ]), + failed=FileSet([ + FailedLocalFile("/local/failed.file"), + ]), + skipped=FileSet([ + LocalPath("/local/already.exists"), + ]), + missing=FileSet([ + LocalPath("/local/missing.file"), + ]), +) +``` + +Upload only certain files from `local_path`: + +```pycon +>>> from onetl.file import FileUploader +>>> uploader = FileUploader(local_path="/local", target_path="/remote", ...) +>>> # paths could be relative or absolute, but all should be in "/local" +>>> upload_result = uploader.run( +... [ +... "/local/file1", +... "/local/nested/path/file3", +... # excluding "/local/file2", +... ] +... ) +>>> upload_result +UploadResult( + successful=FileSet([ + RemoteFile("/remote/file1"), + # directory structure is preserved + RemoteFile("/remote/nested/path/file3"), + ]), + failed=FileSet([]), + skipped=FileSet([]), + missing=FileSet([]), +) +``` + +Upload only certain files from any folder: + +```pycon +>>> from onetl.file import FileUploader +>>> uploader = FileUploader(target_path="/remote", ...) # no local_path set +>>> # only absolute paths +>>> upload_result = uploader.run( +... [ +... "/local/file1.txt", +... "/any/nested/path/file3.txt", +... ] +... ) +>>> upload_result +UploadResult( + successful=FileSet([ + RemoteFile("/remote/file1.txt"), + # directory structure is NOT preserved without local_path + RemoteFile("/remote/file3.txt"), + ]), + failed=FileSet([]), + skipped=FileSet([]), + missing=FileSet([]), +) +``` + + + +#### view_files() → FileSet[LocalPath] + +Get file list in the `local_path`. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.3.0. + +* **Returns:** + FileSet[LocalPath] + : Set of files in `local_path` +* **Raises:** + `onetl.exception.DirectoryNotFoundError` + : `local_path` does not found + + NotADirectoryError + : `local_path` is not a directory + +### Examples + +View files: + +```pycon +>>> from onetl.file import FileUploader +>>> uploader = FileUploader(local_path="/local", ...) +>>> uploader.view_files() +FileSet([ + LocalPath("/local/file1.txt"), + LocalPath("/local/file3.txt"), + LocalPath("/local/nested/path/file3.txt"), +]) +``` + + diff --git a/mddocs/file/file_uploader/index.md b/mddocs/file/file_uploader/index.md new file mode 100644 index 000000000..e11581a9c --- /dev/null +++ b/mddocs/file/file_uploader/index.md @@ -0,0 +1,9 @@ + + +# File Uploader + +# File Uploader + +* [File Uploader](file_uploader.md) +* [File Uploader Options](options.md) +* [File Uploader Result](result.md) diff --git a/mddocs/file/file_uploader/options.md b/mddocs/file/file_uploader/options.md new file mode 100644 index 000000000..a32387501 --- /dev/null +++ b/mddocs/file/file_uploader/options.md @@ -0,0 +1,67 @@ + + +# File Uploader Options + +### *pydantic model* onetl.file.file_uploader.options.FileUploaderOptions + +File uploading options. + +#### Versionadded +Added in version 0.3.0. + +### Examples + +```python +from onetl.file import FileUploader + +options = FileUploader.Options( + if_exists="replace_entire_directory", + delete_local=True, + workers=4, +) +``` + + + +#### *field* if_exists *: FileExistBehavior* *= FileExistBehavior.ERROR* *(alias 'mode')* + +How to handle existing files in the target directory. + +Possible values: +: * `error` (default) - mark file as failed + * `ignore` - mark file as skipped + * `replace_file` - replace existing file with a new one + * `replace_entire_directory` - delete local directory content before downloading files + +#### Versionchanged +Changed in version 0.9.0: Renamed `mode` → `if_exists` + + + +#### *field* delete_local *: bool* *= False* + +If `True`, remove local file after successful download. + +If download failed, file will left intact. + +#### Versionadded +Added in version 0.2.0. + +#### Versionchanged +Changed in version 0.3.0: Move `FileUploader.delete_local` to `FileUploaderOptions` + + + +#### *field* workers *: int* *= 1* + +Number of workers to create for parallel file upload. + +1 (default) means files will me uploaded sequentially. +2 or more means files will be uploaded in parallel workers. + +Recommended value is `min(32, os.cpu_count() + 4)`, e.g. `5`. + +#### Versionadded +Added in version 0.8.1. + + diff --git a/mddocs/file/file_uploader/result.md b/mddocs/file/file_uploader/result.md new file mode 100644 index 000000000..59c289bea --- /dev/null +++ b/mddocs/file/file_uploader/result.md @@ -0,0 +1,550 @@ + + +# File Uploader Result + +### *class* onetl.file.file_uploader.result.UploadResult(\*, successful: FileSet[RemoteFile] = None, failed: FileSet[FailedLocalFile] = None, skipped: FileSet[LocalPath] = None, missing: FileSet[LocalPath] = None) + +Representation of file upload result. + +Container for file paths, divided into certain categories: + +* [`successful`](#onetl.file.file_uploader.result.UploadResult.successful) +* [`failed`](#onetl.file.file_uploader.result.UploadResult.failed) +* [`skipped`](#onetl.file.file_uploader.result.UploadResult.skipped) +* [`missing`](#onetl.file.file_uploader.result.UploadResult.missing) + +#### Versionadded +Added in version 0.3.0. + +### Examples + +```pycon +>>> from onetl.file import FileUploader +>>> uploader = FileUploader(target_path="/remote", ...) +>>> upload_result = uploader.run( +... [ +... "/local/file1", +... "/local/file2", +... "/failed/file", +... "/existing/file", +... "/missing/file", +... ] +... ) +>>> upload_result +UploadResult( + successful=FileSet([ + RemoteFile("/remote/file1"), + RemoteFile("/remote/file2"), + ]), + failed=FileSet([ + FailedLocalFile("/failed/file") + ]), + skipped=FileSet([ + LocalPath("/existing/file") + ]), + missing=FileSet([ + LocalPath("/missing/file") + ]), +) +``` + + + +#### *property* details *: str* + +Return detailed information about files in the result object + +### Examples + +```pycon +>>> from onetl.impl import FailedRemoteFile, LocalPath, RemoteFile, RemotePathStat +>>> from onetl.exception import NotAFileError +>>> from onetl.file.file_result import FileResult +>>> file_result1 = FileResult( +... successful={ +... RemoteFile("/local/file", stats=RemotePathStat(st_size=1024)), +... RemoteFile("/local/another.file", stats=RemotePathStat(st_size=1024)), +... }, +... failed={ +... FailedRemoteFile( +... path="/remote/file1", +... stats=RemotePathStat(st_size=0), +... exception=NotAFileError("'/remote/file1' is not a file"), +... ), +... FailedRemoteFile( +... path="/remote/file2", +... stats=RemotePathStat(st_size=0), +... exception=PermissionError("'/remote/file2': [Errno 13] Permission denied"), +... ), +... }, +... skipped={LocalPath("/skipped/file1"), LocalPath("/skipped/file2")}, +... missing={LocalPath("/missing/file1"), LocalPath("/missing/file2")}, +... ) +>>> print(file_result1.details) +Total: 8 files (size='2.0 kB') + +Successful 2 files (size='2.0 kB'): + '/local/another.file' (size='1.0 kB') + '/local/file' (size='1.0 kB') + +Failed 2 files (size='0 Bytes'): + '/remote/file2' (size='0 Bytes') + PermissionError("'/remote/file2': [Errno 13] Permission denied") + '/remote/file1' (size='0 Bytes') + NotAFileError("'/remote/file1' is not a file") + +Skipped 2 files (size='0 Bytes'): + '/skipped/file1' + '/skipped/file2' + +Missing 2 files: + '/missing/file2' + '/missing/file1' +``` + +```pycon +>>> file_result2 = FileResult() +>>> print(file_result2.details) +No successful files + +No failed files + +No skipped files + +No missing files +``` + + + +#### dict(\*, include: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, exclude: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, by_alias: bool = False, skip_defaults: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False) → DictStrAny + +Generate a dictionary representation of the model, optionally specifying which fields to include or exclude. + + + +#### *field* failed *: FileSet[FailedLocalFile]* *[Optional]* + +File paths (local) which were not uploaded because of some failure + + + +#### *property* failed_count *: int* + +Get number of failed files + +### Examples + +```pycon +>>> from onetl.impl import RemoteFile +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... failed={RemoteFile("/some/file"), RemoteFile("/some/another.file")}, +... ) +>>> file_result.failed_count +2 +``` + + + +#### *property* failed_size *: int* + +Get size (in bytes) of failed files + +### Examples + +```pycon +>>> from onetl.impl import RemoteFile, RemotePathStat +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... failed={ +... RemoteFile("/some/file", stats=RemotePathStat(st_size=1024)), +... RemoteFile("/some/another.file"), stats=RemotePathStat(st_size=1024)), +... }, +... ) +>>> file_result.failed_size # in bytes +2048 +``` + + + +#### *property* is_empty *: bool* + +Returns `True` if there are no files in `successful`, `failed` and `skipped` attributes + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result1 = FileResult() +>>> file_result1.is_empty +True +>>> file_result2 = FileResult( +... successful={LocalPath("/local/file"), LocalPath("/local/another.file")}, +... ) +>>> file_result2.is_empty +False +``` + + + +#### json(\*, include: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, exclude: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, by_alias: bool = False, skip_defaults: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, encoder: Callable[[Any], Any] | None = None, models_as_dict: bool = True, \*\*dumps_kwargs: Any) → str + +Generate a JSON representation of the model, include and exclude arguments as per dict(). + +encoder is an optional function to supply as default to json.dumps(), other arguments as per json.dumps(). + + + +#### *field* missing *: FileSet[LocalPath]* *[Optional]* + +File paths (local) which are not present in the local file system + + + +#### *property* missing_count *: int* + +Get number of missing files + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... missing={LocalPath("/some/file"), LocalPath("/some/another.file")}, +... ) +>>> file_result.missing_count +2 +``` + + + +#### raise_if_contains_zero_size() → None + +Raise exception if `successful` attribute contains a file with zero size + +* **Raises:** + ZeroFileSizeError + : `successful` file set contains a file with zero size + +### Examples + +```pycon +>>> from onetl.exception import ZeroFileSizeError +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... successful={ +... LocalPath("/local/empty1.file"), +... LocalPath("/local/empty2.file"), +... LocalPath("/local/normal.file"), +... }, +... ) +>>> file_result.raise_if_contains_zero_size() +Traceback (most recent call last): + ... +onetl.exception.ZeroFileSizeError: 2 files out of 3 have zero size: + '/local/empty1.file' + '/local/empty2.file' +``` + + + +#### raise_if_empty() → None + +Raise exception if there are no files in `successful`, `failed` and `skipped` attributes + +* **Raises:** + EmptyFilesError + : `successful`, `failed` and `skipped` file sets are empty + +### Examples + +```pycon +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult() +>>> file_result.raise_if_empty() +Traceback (most recent call last): + ... +onetl.exception.EmptyFilesError: There are no files in the result +``` + + + +#### raise_if_failed() → None + +Raise exception if there are some files in `failed` attribute + +* **Raises:** + FailedFilesError + : `failed` file set is not empty + +### Examples + +```pycon +>>> from onetl.impl import FailedRemoteFile, RemotePathStat +>>> from onetl.exception import NotAFileError, FileMissingError +>>> from onetl.file.file_result import FileResult +>>> files_with_exception = [ +... FailedRemoteFile( +... path="/remote/file1", +... stats=RemotePathStat(st_size=0), +... exception=NotAFileError("'/remote/file1' is not a file"), +... ), +... FailedRemoteFile( +... path="/remote/file2", +... stats=RemotePathStat(st_size=0), +... exception=PermissionError("'/remote/file2': [Errno 13] Permission denied"), +... ), +... ] +>>> file_result = FileResult(failed=files_with_exception) +>>> file_result.raise_if_failed() +Traceback (most recent call last) +... +onetl.exception.FailedFilesError: Failed 2 files (size='0 bytes'): + '/remote/file1' (size='0 bytes') + NotAFileError("'/remote/file1' is not a file") + + '/remote/file2' (size='0 Bytes') + PermissionError("'/remote/file2': [Errno 13] Permission denied") +``` + + + +#### raise_if_missing() → None + +Raise exception if there are some files in `missing` attribute + +* **Raises:** + MissingFilesError + : `missing` file set is not empty + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... missing={ +... LocalPath("/missing/file1"), +... LocalPath("/missing/file2"), +... }, +... ) +>>> file_result.raise_if_missing() +Traceback (most recent call last): + ... +onetl.exception.MissingFilesError: Missing 2 files: + '/missing/file1' + '/missing/file2' +``` + + + +#### raise_if_skipped() → None + +Raise exception if there are some files in `skipped` attribute + +* **Raises:** + SkippedFilesError + : `skipped` file set is not empty + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... skipped={ +... LocalPath("/skipped/file1"), +... LocalPath("/skipped/file2"), +... }, +... ) +>>> file_result.raise_if_skipped() +Traceback (most recent call last): + ... +onetl.exception.SkippedFilesError: Skipped 2 files (15 kB): + '/skipped/file1' (10kB) + '/skipped/file2' (5 kB) +``` + + + +#### *field* skipped *: FileSet[LocalPath]* *[Optional]* + +File paths (local) which were skipped because of some reason + + + +#### *property* skipped_count *: int* + +Get number of skipped files + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... skipped={LocalPath("/some/file"), LocalPath("/some/another.file")}, +... ) +>>> file_result.skipped_count +2 +``` + + + +#### *property* skipped_size *: int* + +Get size (in bytes) of skipped files + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... skipped={LocalPath("/some/file"), LocalPath("/some/another.file")}, +... ) +>>> file_result.skipped_size # in bytes +1024 +``` + + + +#### *field* successful *: FileSet[RemoteFile]* *[Optional]* + +File paths (remote) which were uploaded successfully + + + +#### *property* successful_count *: int* + +Get number of successful files + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... successful={LocalPath("/some/file"), LocalPath("/some/another.file")}, +... ) +>>> file_result.successful_count +2 +``` + + + +#### *property* successful_size *: int* + +Get size (in bytes) of successful files + +### Examples + +```pycon +>>> from onetl.impl import LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... successful={LocalPath("/some/file"), LocalPath("/some/another.file")}, +... ) +>>> file_result.successful_size # in bytes +1024 +``` + + + +#### *property* summary *: str* + +Return short summary about files in the result object + +### Examples + +```pycon +>>> from onetl.impl import FailedRemoteFile, LocalPath, RemoteFile, RemotePathStat +>>> from onetl.exception import NotAFileError +>>> from onetl.file.file_result import FileResult +>>> file_result1 = FileResult( +... successful={ +... RemoteFile("/local/file", stats=RemotePathStat(st_size=1024)), +... RemoteFile("/local/another.file", stats=RemotePathStat(st_size=1024)), +... }, +... failed={ +... FailedRemoteFile( +... path="/remote/file1", +... stats=RemotePathStat(st_size=0), +... exception=NotAFileError("'/remote/file1' is not a file"), +... ), +... FailedRemoteFile( +... path="/remote/file2", +... stats=RemotePathStat(st_size=0), +... exception=PermissionError("'/remote/file2': [Errno 13] Permission denied"), +... ), +... }, +... skipped={LocalPath("/skipped/file1"), LocalPath("/skipped/file2")}, +... missing={LocalPath("/missing/file1"), LocalPath("/missing/file2")}, +... ) +>>> print(file_result1.summary) +Total: 8 files (size='2.0 kB') + +Successful: 2 files (size='2.0 kB') + +Failed: 2 files (size='0 Bytes') + +Skipped: 2 files (size='0 Bytes') + +Missing: 2 files +``` + +```pycon +>>> file_result2 = FileResult() +>>> print(file_result2.summary) +No files +``` + + + +#### *property* total_count *: int* + +Get total number of all files + +### Examples + +```pycon +>>> from onetl.impl import RemoteFile +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... successful={LocalPath("/local/file"), LocalPath("/local/another.file")}, +... failed={RemoteFile("/remote/file"), RemoteFile("/remote/another.file")}, +... skipped={LocalPath("/skipped/file")}, +... missing={LocalPath("/missing/file")}, +... ) +>>> file_result.total_count +6 +``` + + + +#### *property* total_size *: int* + +Get total size (in bytes) of all files + +### Examples + +```pycon +>>> from onetl.impl import RemoteFile, RemotePathStat, LocalPath +>>> from onetl.file.file_result import FileResult +>>> file_result = FileResult( +... successful={LocalPath("/local/file"), LocalPath("/local/another.file")}, +... failed={ +... RemoteFile("/remote/file", stats=RemotePathStat(st_size=1024)), +... RemoteFile("/remote/another.file", stats=RemotePathStat(st_size=1024)) +... }, +... skipped={LocalPath("/skipped/file")}, +... missing={LocalPath("/missing/file")}, +... ) +>>> file_result.total_size # in bytes +4096 +``` + + diff --git a/mddocs/file/index.md b/mddocs/file/index.md new file mode 100644 index 000000000..2da812bab --- /dev/null +++ b/mddocs/file/index.md @@ -0,0 +1,9 @@ + + + File classes + +* [File Downloader](file_downloader/index.md) +* [File Uploader](file_uploader/index.md) +* [File Mover](file_mover/index.md) +* [File Filters](file_filters/index.md) +* [File Limits](file_limits/index.md) diff --git a/mddocs/file_df/file_df_reader/file_df_reader.md b/mddocs/file_df/file_df_reader/file_df_reader.md new file mode 100644 index 000000000..256ab7b59 --- /dev/null +++ b/mddocs/file_df/file_df_reader/file_df_reader.md @@ -0,0 +1,158 @@ + + +# FileDF Reader + +### *class* onetl.file.file_df_reader.file_df_reader.FileDFReader(\*, connection: [BaseFileDFConnection](../../connection/file_df_connection/base.md#onetl.base.base_file_df_connection.BaseFileDFConnection), format: [BaseReadableFileFormat](../file_formats/base.md#onetl.base.base_file_format.BaseReadableFileFormat), source_path: PurePathProtocol | None = None, df_schema: StructType | None = None, options: [FileDFReaderOptions](options.md#onetl.file.file_df_reader.options.FileDFReaderOptions) = FileDFReaderOptions(recursive=None)) + +Allows you to read files from a source path with specified file connection +and parameters, and return a Spark DataFrame. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +This class does **not** support read strategies. + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **connection** + : File DataFrame connection. See [File DataFrame Connections](../../connection/file_df_connection/index.md#file-df-connections) section. + + **format** + : File format to read. + + **source_path** + : Directory path to read data from. +
+ Could be `None`, but only if you pass file paths directly to + [`run`](#onetl.file.file_df_reader.file_df_reader.FileDFReader.run) method + + **df_schema** + : Spark DataFrame schema. + + **options** + : Common reading options. + +### Examples + +Read CSV files from local filesystem + +```py +from onetl.connection import SparkLocalFS +from onetl.file import FileDFReader +from onetl.file.format import CSV + +csv = CSV(delimiter=",") +local_fs = SparkLocalFS(spark=spark) + +reader = FileDFReader( + connection=local_fs, + format=csv, + source_path="/path/to/directory", +) +``` + +All supported options + +```py +from onetl.connection import SparkLocalFS +from onetl.file import FileDFReader +from onetl.file.format import CSV + +csv = CSV(delimiter=",") +local_fs = SparkLocalFS(spark=spark) + +reader = FileDFReader( + connection=local_fs, + format=csv, + source_path="/path/to/directory", + options=FileDFReader.Options(recursive=False), +) +``` + + + +#### run(files: Iterable[str | os.PathLike] | None = None) → DataFrame + +Method for reading files as DataFrame. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **files** + : File list to read. +
+ If empty, read files from `source_path`. +* **Returns:** + **df** + : Spark DataFrame + +### Examples + +Read CSV files from directory `/path`: + +```python +from onetl.connection import SparkLocalFS +from onetl.file import FileDFReader +from onetl.file.format import CSV + +csv = CSV(delimiter=",") +local_fs = SparkLocalFS(spark=spark) + +reader = FileDFReader( + connection=local_fs, + format=csv, + source_path="/path", +) +df = reader.run() +``` + +Read some CSV files using file paths: + +```python +from onetl.connection import SparkLocalFS +from onetl.file import FileDFReader +from onetl.file.format import CSV + +csv = CSV(delimiter=",") +local_fs = SparkLocalFS(spark=spark) + +reader = FileDFReader( + connection=local_fs, + format=csv, +) + +df = reader.run( + [ + "/path/file1.csv", + "/path/nested/file2.csv", + ] +) +``` + +Read only specific CSV files in directory: + +```python +from onetl.connection import SparkLocalFS +from onetl.file import FileDFReader +from onetl.file.format import CSV + +csv = CSV(delimiter=",") +local_fs = SparkLocalFS(spark=spark) + +reader = FileDFReader( + connection=local_fs, + format=csv, + source_path="/path", +) + +df = reader.run( + [ + # file paths could be relative + "/path/file1.csv", + "/path/nested/file2.csv", + ] +) +``` + + diff --git a/mddocs/file_df/file_df_reader/index.md b/mddocs/file_df/file_df_reader/index.md new file mode 100644 index 000000000..0087e74ac --- /dev/null +++ b/mddocs/file_df/file_df_reader/index.md @@ -0,0 +1,8 @@ + + +# FileDF Reader + +# FileDF Reader + +* [FileDF Reader](file_df_reader.md) +* [Options](options.md) diff --git a/mddocs/file_df/file_df_reader/options.md b/mddocs/file_df/file_df_reader/options.md new file mode 100644 index 000000000..eceb33e5d --- /dev/null +++ b/mddocs/file_df/file_df_reader/options.md @@ -0,0 +1,38 @@ + + +# Options + +### *class* onetl.file.file_df_reader.options.FileDFReaderOptions(\*, recursiveFileLookup: bool | None = None, \*\*kwargs) + +Options for [`FileDFReader`](file_df_reader.md#onetl.file.file_df_reader.file_df_reader.FileDFReader). + +#### Versionadded +Added in version 0.9.0. + +### Examples + +#### NOTE +You can pass any value [supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-load-save-functions.html), +even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! + +The set of supported options depends on Spark version. + +```python +from onetl.file import FileDFReader + +options = FileDFReader.Options(recursive=True) +``` + + + +#### *field* recursive *: bool | None* *= None* *(alias 'recursiveFileLookup')* + +If `True`, perform recursive file lookup. + +#### WARNING +This disables partition inferring using file paths. + +#### WARNING +Can be used only in Spark 3+. See [SPARK-27990](https://issues.apache.org/jira/browse/SPARK-27990). + + diff --git a/mddocs/file_df/file_df_writer/file_df_writer.md b/mddocs/file_df/file_df_writer/file_df_writer.md new file mode 100644 index 000000000..8f845c598 --- /dev/null +++ b/mddocs/file_df/file_df_writer/file_df_writer.md @@ -0,0 +1,80 @@ + + +# FileDF Writer + +### *class* onetl.file.file_df_writer.file_df_writer.FileDFWriter(\*, connection: ~onetl.base.base_file_df_connection.BaseFileDFConnection, format: ~onetl.base.base_file_format.BaseWritableFileFormat, target_path: ~onetl.base.pure_path_protocol.PurePathProtocol, options: ~onetl.file.file_df_writer.options.FileDFWriterOptions = FileDFWriterOptions(if_exists=, partition_by=None)) + +Allows you to write Spark DataFrame as files in a target path of specified file connection +with parameters. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +* **Parameters:** + **connection** + : File DataFrame connection. See [File DataFrame Connections](../../connection/file_df_connection/index.md#file-df-connections) section. + + **format** + : File format to write. + + **target_path** + : Directory path to write data to. + + **options** + : Common writing options. + +### Examples + +Write CSV files to local filesystem + +```py +from onetl.connection import SparkLocalFS +from onetl.file import FileDFWriter +from onetl.file.format import CSV + +local_fs = SparkLocalFS(spark=spark) + +writer = FileDFWriter( + connection=local_fs, + format=CSV(delimiter=","), + target_path="/path/to/directory", +) +``` + +All supported options + +```py +from onetl.connection import SparkLocalFS +from onetl.file import FileDFWriter +from onetl.file.format import CSV + +csv = CSV(delimiter=",") +local_fs = SparkLocalFS(spark=spark) + +writer = FileDFWriter( + connection=local_fs, + format=csv, + target_path="/path/to/directory", + options=FileDFWriter.Options(if_exists="replace_entire_directory"), +) +``` + + + +#### run(df: DataFrame) → None + +Method for writing DataFrame as files. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### NOTE +Method does support only **batching** DataFrames. + +* **Parameters:** + **df** + : Spark dataframe + +### Examples + +Write df to target: + +```python +writer.run(df) +``` + + diff --git a/mddocs/file_df/file_df_writer/index.md b/mddocs/file_df/file_df_writer/index.md new file mode 100644 index 000000000..9813ed1d4 --- /dev/null +++ b/mddocs/file_df/file_df_writer/index.md @@ -0,0 +1,8 @@ + + +# FileDF Writer + +# FileDF Writer + +* [FileDF Writer](file_df_writer.md) +* [Options](options.md) diff --git a/mddocs/file_df/file_df_writer/options.md b/mddocs/file_df/file_df_writer/options.md new file mode 100644 index 000000000..7a76fecbc --- /dev/null +++ b/mddocs/file_df/file_df_writer/options.md @@ -0,0 +1,140 @@ + + +# Options + +### *class* onetl.file.file_df_writer.options.FileDFWriterOptions(\*, if_exists: FileDFExistBehavior = FileDFExistBehavior.APPEND, partitionBy: List[str] | str | None = None, \*\*kwargs) + +Options for [`FileDFWriter`](file_df_writer.md#onetl.file.file_df_writer.file_df_writer.FileDFWriter). + +#### Versionadded +Added in version 0.9.0. + +### Examples + +#### NOTE +You can pass any value [supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-load-save-functions.html), +even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! + +The set of supported options depends on Spark version. + +```python +from onetl.file import FileDFWriter + +options = FileDFWriter.Options( + if_exists="replace_overlapping_partitions", + partitionBy="month", +) +``` + + + +#### *field* if_exists *: FileDFExistBehavior* *= FileDFExistBehavior.APPEND* + +Behavior for existing target directory. + +If target directory does not exist, it will be created. +But if it does exist, then behavior is different for each value. + +#### Versionchanged +Changed in version 0.13.0: Default value was changed from `error` to `append` + +Possible values: +: * `error` + : If folder already exists, raise an exception. +
+ Same as Spark’s `df.write.mode("error").save()`. + * `skip_entire_directory` + : If folder already exists, left existing files intact and stop immediately without any errors. +
+ Same as Spark’s `df.write.mode("ignore").save()`. + * `append` (default) + : Appends data into existing directory. +
+ ### Behavior in details +
+ * Directory does not exist + : Directory is created using all the provided options (`format`, `partition_by`, etc). + * Directory exists, does not contain partitions, but [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by) is set + : Data is appended to a directory, but to partitioned directory structure. +
+ #### WARNING + Existing files still present in the root of directory, but Spark will ignore those files while reading, + unless using `recursive=True`. + * Directory exists and contains partitions, but [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by) is not set + : Data is appended to a directory, but to the root of directory instead of nested partition directories. +
+ #### WARNING + Spark will ignore such files while reading, unless using `recursive=True`. + * Directory exists and contains partitions, but with different partitioning schema than [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by) + : Data is appended to a directory with new partitioning schema. +
+ #### WARNING + Spark cannot read directory with multiple partitioning schemas, + unless using `recursive=True` to disable partition scanning. + * Directory exists and partitioned according [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by), but partition is present only in dataframe + : New partition directory is created. + * Directory exists and partitioned according [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by), partition is present in both dataframe and directory + : New files are added to existing partition directory, existing files are sill present. + * Directory exists and partitioned according [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by), but partition is present only in directory, not dataframe + : Existing partition is left intact. + * `replace_overlapping_partitions` + : If partitions from dataframe already exist in directory structure, they will be overwritten. +
+ Same as Spark’s `df.write.mode("overwrite").save()` + + `spark.sql.sources.partitionOverwriteMode=dynamic`. +
+ ### Behavior in details +
+ * Directory does not exist + : Directory is created using all the provided options (`format`, `partition_by`, etc). + * Directory exists, does not contain partitions, but [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by) is set + : Directory **will be deleted**, and will be created with partitions. + * Directory exists and contains partitions, but [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by) is not set + : Directory **will be deleted**, and will be created with partitions. + * Directory exists and contains partitions, but with different partitioning schema than [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by) + : Data is appended to a directory with new partitioning schema. +
+ #### WARNING + Spark cannot read directory with multiple partitioning schemas, + unless using `recursive=True` to disable partition scanning. + * Directory exists and partitioned according [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by), but partition is present only in dataframe + : New partition directory is created. + * Directory exists and partitioned according [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by), partition is present in both dataframe and directory + : Partition directory **will be deleted**, and new one is created with files containing data from dataframe. + * Directory exists and partitioned according [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by), but partition is present only in directory, not dataframe + : Existing partition is left intact. + * `replace_entire_directory` + : Remove existing directory and create new one, **overwriting all existing data**. + **All existing partitions are dropped.** +
+ Same as Spark’s `df.write.mode("overwrite").save()` + + `spark.sql.sources.partitionOverwriteMode=static`. + +#### NOTE +Unlike using pure Spark, config option `spark.sql.sources.partitionOverwriteMode` +does not affect behavior of any `mode` + + + +#### *field* partition_by *: List[str] | str | None* *= None* *(alias 'partitionBy')* + +List of columns should be used for data partitioning. `None` means partitioning is disabled. + +Each partition is a folder which contains only files with the specific column value, +like `some.csv/col1=value1`, `some.csv/col1=value2`, and so on. + +Multiple partitions columns means nested folder structure, like `some.csv/col1=val1/col2=val2`. + +If `WHERE` clause in the query contains expression like `partition = value`, +Spark will scan only files in a specific partition. + +Examples: `reg_id` or `["reg_id", "business_dt"]` + +#### NOTE +Values should be scalars (integers, strings), +and either static (`countryId`) or incrementing (dates, years), with low +number of distinct values. + +Columns like `userId` or `datetime`/`timestamp` should **NOT** be used for partitioning. + + diff --git a/mddocs/file_df/file_formats/avro.md b/mddocs/file_df/file_formats/avro.md new file mode 100644 index 000000000..de41f7796 --- /dev/null +++ b/mddocs/file_df/file_formats/avro.md @@ -0,0 +1,390 @@ + + +# Avro + +### *class* onetl.file.format.avro.Avro(\*, avroSchema: dict | None = None, avroSchemaUrl: str | None = None, recordName: str | None = None, recordNamespace: str | None = None, compression: str | Literal['uncompressed', 'snappy', 'deflate', 'bzip2', 'xz', 'zstandard'] | None = None, mode: Literal['PERMISSIVE', 'FAILFAST'] | None = None, datetimeRebaseMode: Literal['CORRECTED', 'LEGACY', 'EXCEPTION'] | None = None, positionalFieldMatching: bool | None = None, enableStableIdentifiersForUnionType: bool | None = None, \*\*kwargs) + +Avro file format. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on [Spark Avro](https://spark.apache.org/docs/latest/sql-data-sources-avro.html) file format. + +Supports reading/writing files with `.avro` extension. + +### Version compatibility + +* Spark versions: 2.4.x - 3.5.x +* Java versions: 8 - 20 + +See documentation from link above. + +#### Versionadded +Added in version 0.9.0. + +### Examples + +#### NOTE +You can pass any option mentioned in +[official documentation](https://spark.apache.org/docs/latest/sql-data-sources-avro.html). +**Option names should be in** `camelCase`! + +The set of supported options depends on Spark version. + +Reading files + +```py +from pyspark.sql import SparkSession +from onetl.file.format import Avro + +# Create Spark session with Avro package loaded +maven_packages = Avro.get_packages(spark_version="3.5.5") +spark = ( + SparkSession.builder.appName("spark-app-name") + .config("spark.jars.packages", ",".join(maven_packages)) + .getOrCreate() +) + +schema = { + "type": "record", + "name": "Person", + "fields": [ + {"name": "name", "type": "string"}, + {"name": "age", "type": "int"}, + ], +} +avro = Avro(avroSchema=schema) # or avroSchemaUrl=... +``` + +Writing files + +```py +# Create Spark session with Avro package loaded +spark = ... + +from onetl.file.format import Avro + +schema = { + "type": "record", + "name": "Person", + "fields": [ + {"name": "name", "type": "string"}, + {"name": "age", "type": "int"}, + ], +} +avro = Avro( + avroSchema=schema, # or avroSchemaUrl=... + compression="snappy", +) +``` + + + +#### *field* schema_dict *: dict | None* *= None* *(alias 'avroSchema')* + +Avro schema in JSON format representation. + +```python +avro = Avro( + avroSchema={ + "type": "record", + "name": "Person", + "fields": [ + {"name": "name", "type": "string"}, + {"name": "age", "type": "int"}, + ], + }, +) +``` + +If set, all records should match this schema. + +#### WARNING +Mutually exclusive with [`schema_url`](#onetl.file.format.avro.Avro.schema_url). + + + +#### *field* schema_url *: str | None* *= None* *(alias 'avroSchemaUrl')* + +URL to Avro schema in JSON format. Usually points to Schema Registry, like: + +```python +schema_registry = "http://some.schema.registry.domain" +name = "MyAwesomeSchema" +version = "latest" + +schema_url = f"{schema_registry}/subjects/{name}/versions/{version}/schema" +avro = Avro(avroSchemaUrl=schema_url) +``` + +If set, schema is fetched before any records are parsed, so all records should match this schema. + +#### WARNING +Mutually exclusive with [`schema_dict`](#onetl.file.format.avro.Avro.schema_dict). + + + +#### *field* recordName *: str | None* *= None* + +Record name in written Avro schema. +Default is `topLevelRecord`. + +#### NOTE +Used only for writing files and by [`serialize_column`](#onetl.file.format.avro.Avro.serialize_column). + + + +#### *field* recordNamespace *: str | None* *= None* + +Record namespace in written Avro schema. Default is not set. + +#### NOTE +Used only for writing files and by [`serialize_column`](#onetl.file.format.avro.Avro.serialize_column). + + + +#### *field* compression *: str | Literal['uncompressed', 'snappy', 'deflate', 'bzip2', 'xz', 'zstandard'] | None* *= None* + +Compression codec. +By default, Spark config value `spark.sql.avro.compression.codec `` (``snappy`) is used. + +#### NOTE +Used only for writing files. Ignored by [`serialize_column`](#onetl.file.format.avro.Avro.serialize_column). + + + +#### *field* mode *: Literal['PERMISSIVE', 'FAILFAST'] | None* *= None* + +How to handle parsing errors: +: * `PERMISSIVE` - set field value as `null`. + * `FAILFAST` - throw an error immediately. + +Default is `FAILFAST`. + +#### NOTE +Used only by [`parse_column`](#onetl.file.format.avro.Avro.parse_column) method. + + + +#### *field* datetimeRebaseMode *: Literal['CORRECTED', 'LEGACY', 'EXCEPTION'] | None* *= None* + +While converting dates/timestamps from Julian to Proleptic Gregorian calendar, handle value ambiguity: +: * `EXCEPTION` - fail if ancient dates/timestamps are ambiguous between the two calendars. + * `CORRECTED` - load dates/timestamps without as-is. + * `LEGACY` - rebase ancient dates/timestamps from the Julian to Proleptic Gregorian calendar. + +By default, Spark config value `spark.sql.avro.datetimeRebaseModeInRead` (`CORRECTED`) is used. + +#### NOTE +Used only for reading files and by [`parse_column`](#onetl.file.format.avro.Avro.parse_column). + + + +#### *field* positionalFieldMatching *: bool | None* *= None* + +If `True`, match Avro schema field and DataFrame column by position. +If `False`, match by name. + +Default is `False`. + + + +#### *field* enableStableIdentifiersForUnionType *: bool | None* *= None* + +Avro schema may contain union types, which are not supported by Spark. +Different variants of union are split to separated DataFrame columns with respective type. + +If option value is `True`, DataFrame column names are based on Avro variant names, e.g. `member_int`, `member_string`. +If `False`, DataFrame column names are generated using field position, e.g. `member0`, `member1`. + +Default is `False`. + +#### NOTE +Used only for reading files and by [`parse_column`](#onetl.file.format.avro.Avro.parse_column). + + + +#### *classmethod* get_packages(spark_version: str, scala_version: str | None = None) → list[str] + +Get package names to be downloaded by Spark. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +See [Maven package index](https://mvnrepository.com/artifact/org.apache.spark/spark-avro) +for all available packages. + +#### Versionadded +Added in version 0.9.0. + +* **Parameters:** + **spark_version** + : Spark version in format `major.minor.patch`. + + **scala_version** + : Scala version in format `major.minor`. +
+ If `None`, `spark_version` is used to determine Scala version. + +### Examples + +```python +from onetl.file.format import Avro + +Avro.get_packages(spark_version="3.5.5") +Avro.get_packages(spark_version="3.5.5", scala_version="2.12") +``` + + + +#### parse_column(column: str | Column) → Column + +Parses an Avro binary column into a structured Spark SQL column using Spark’s +[from_avro](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.avro.functions.from_avro.html) function, +based on the schema provided within the class. + +#### NOTE +Can be used only with Spark 3.x+ + +#### WARNING +If `schema_url` is provided, `requests` library is used to fetch the schema from the URL. +It should be installed manually, like this: + +```bash +pip install requests +``` + +#### Versionadded +Added in version 0.11.0. + +* **Parameters:** + **column** + : The name of the column or the column object containing Avro bytes to deserialize. + Schema should match the provided Avro schema. +* **Returns:** + Column with deserialized data. Schema is matching the provided Avro schema. Column name is the same as input column. +* **Raises:** + ValueError + : If the Spark version is less than 3.x or if neither `avroSchema` nor `avroSchemaUrl` are defined. + + ImportError + : If `schema_url` is used and the `requests` library is not installed. + +### Examples + +```pycon +>>> from pyspark.sql.functions import decode +>>> from onetl.file.format import Avro +>>> df.show() ++----+----------------------+----------+---------+------+-----------------------+-------------+ +|key |value |topic |partition|offset|timestamp |timestampType| ++----+----------------------+----------+---------+------+-----------------------+-------------+ +|[31]|[0A 41 6C 69 63 65 28]|topicAvro |0 |0 |2024-04-24 13:02:25.911|0 | +|[32]|[06 42 6F 62 32] |topicAvro |0 |1 |2024-04-24 13:02:25.922|0 | ++----+----------------------+----------+---------+------+-----------------------+-------------+ +>>> df.printSchema() +root +|-- key: binary (nullable = true) +|-- value: binary (nullable = true) +|-- topic: string (nullable = true) +|-- partition: integer (nullable = true) +|-- offset: integer (nullable = true) +|-- timestamp: timestamp (nullable = true) +|-- timestampType: integer (nullable = true) +>>> avro = Avro( +... avroSchema={ # or avroSchemaUrl=... +... "type": "record", +... "name": "Person", +... "fields": [ +... {"name": "name", "type": "string"}, +... {"name": "age", "type": "int"}, +... ], +... } +... ) +>>> parsed_df = df.select(decode("key", "UTF-8").alias("key"), avro.parse_column("value")) +>>> parsed_df.show(truncate=False) ++---+-----------+ +|key|value | ++---+-----------+ +|1 |{Alice, 20}| +|2 |{Bob, 25} | ++---+-----------+ +>>> parsed_df.printSchema() +root +|-- key: string (nullable = true) +|-- value: struct (nullable = true) +| |-- name: string (nullable = true) +| |-- age: integer (nullable = true) +``` + + + +#### serialize_column(column: str | Column) → Column + +Serializes a structured Spark SQL column into an Avro binary column using Spark’s +[to_avro](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.avro.functions.to_avro.html#pyspark.sql.avro.functions.to_avro) function. + +#### NOTE +Can be used only with Spark 3.x+ + +#### WARNING +If `schema_url` is provided, `requests` library is used to fetch the schema from the URL. It should be installed manually, like this: + +```bash +pip install requests +``` + +#### Versionadded +Added in version 0.11.0. + +* **Parameters:** + **column** + : The name of the column or the column object containing the data to serialize to Avro format. +* **Returns:** + Column with binary Avro data. Column name is the same as input column. +* **Raises:** + ValueError + : If the Spark version is less than 3.x. + + ImportError + : If `schema_url` is used and the `requests` library is not installed. + +### Examples + +```pycon +>>> from pyspark.sql.functions import decode +>>> from onetl.file.format import Avro +>>> df.show() ++---+-----------+ +|key|value | ++---+-----------+ +|1 |{Alice, 20}| +|2 | {Bob, 25}| ++---+-----------+ +>>> df.printSchema() +root +|-- key: string (nullable = true) +|-- value: struct (nullable = true) +| |-- name: string (nullable = true) +| |-- age: integer (nullable = true) +>>> # serializing data into Avro format +>>> avro = Avro( +... avroSchema={ # or avroSchemaUrl=... +... "type": "record", +... "name": "Person", +... "fields": [ +... {"name": "name", "type": "string"}, +... {"name": "age", "type": "int"}, +... ], +... } +... ) +>>> serialized_df = df.select("key", avro.serialize_column("value")) +>>> serialized_df.show(truncate=False) ++---+----------------------+ +|key|value | ++---+----------------------+ +| 1|[0A 41 6C 69 63 65 28]| +| 2|[06 42 6F 62 32] | ++---+----------------------+ +>>> serialized_df.printSchema() +root +|-- key: string (nullable = true) +|-- value: binary (nullable = true) +``` + + diff --git a/mddocs/file_df/file_formats/base.md b/mddocs/file_df/file_formats/base.md new file mode 100644 index 000000000..68ca0e042 --- /dev/null +++ b/mddocs/file_df/file_formats/base.md @@ -0,0 +1,73 @@ + + +# Base interface + +### *class* onetl.base.base_file_format.BaseReadableFileFormat + +Representation of readable file format. + +#### Versionadded +Added in version 0.9.0. + + + +#### *abstract* check_if_supported(spark: SparkSession) → None + +Check if Spark session does support this file format. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.9.0. + +* **Raises:** + RuntimeError + : If file format is not supported. + + + +#### *abstract* apply_to_reader(reader: DataFrameReader) → DataFrameReader + +Apply provided format to `pyspark.sql.DataFrameReader`. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.9.0. + +* **Returns:** + `pyspark.sql.DataFrameReader` + : DataFrameReader with options applied. + + + +### *class* onetl.base.base_file_format.BaseWritableFileFormat + +Representation of writable file format. + +#### Versionadded +Added in version 0.9.0. + + + +#### *abstract* check_if_supported(spark: SparkSession) → None + +Check if Spark session does support this file format. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.9.0. + +* **Raises:** + RuntimeError + : If file format is not supported. + + + +#### *abstract* apply_to_writer(writer: DataFrameWriter) → DataFrameWriter + +Apply provided format to `pyspark.sql.DataFrameWriter`. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.9.0. + +* **Returns:** + `pyspark.sql.DataFrameWriter` + : DataFrameWriter with options applied. + + diff --git a/mddocs/file_df/file_formats/csv.md b/mddocs/file_df/file_formats/csv.md new file mode 100644 index 000000000..a02edd58e --- /dev/null +++ b/mddocs/file_df/file_formats/csv.md @@ -0,0 +1,539 @@ + + +# CSV + +### *class* onetl.file.format.csv.CSV(\*, sep: str = ',', header: bool | None = None, quote: ConstrainedStrValue = '"', quoteAll: bool | None = None, escape: ConstrainedStrValue = '\\\\', lineSep: ConstrainedStrValue | None = None, encoding: ConstrainedStrValue | None = None, compression: str | Literal['none', 'bzip2', 'gzip', 'lz4', 'snappy', 'deflate'] | None = None, inferSchema: bool | None = None, samplingRatio: ConstrainedFloatValue | None = None, comment: ConstrainedStrValue | None = None, enforceSchema: bool | None = None, escapeQuotes: bool | None = None, unescapedQuoteHandling: None | Literal['STOP_AT_CLOSING_QUOTE', 'BACK_TO_DELIMITER', 'STOP_AT_DELIMITER', 'SKIP_VALUE', 'RAISE_ERROR'] = None, ignoreLeadingWhiteSpace: bool | None = None, ignoreTrailingWhiteSpace: bool | None = None, emptyValue: str | None = None, nullValue: str | None = None, nanValue: str | None = None, positiveInf: ConstrainedStrValue | None = None, negativeInf: ConstrainedStrValue | None = None, preferDate: bool | None = None, dateFormat: ConstrainedStrValue | None = None, timestampFormat: ConstrainedStrValue | None = None, timestampNTZFormat: ConstrainedStrValue | None = None, locale: ConstrainedStrValue | None = None, maxCharsPerColumn: int | None = None, mode: Literal['PERMISSIVE', 'DROPMALFORMED', 'FAILFAST'] | None = None, columnNameOfCorruptRecord: ConstrainedStrValue | None = None, multiLine: bool | None = None, charToEscapeQuoteEscaping: ConstrainedStrValue | None = None, \*\*kwargs) + +CSV file format. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on [Spark CSV](https://spark.apache.org/docs/latest/sql-data-sources-csv.html) file format. + +Supports reading/writing files with `.csv` extension with content like: + +```csv +"some","value" +"another","value" +``` + +#### Versionadded +Added in version 0.9.0. + +### Examples + +#### NOTE +You can pass any option mentioned in +[official documentation](https://spark.apache.org/docs/latest/sql-data-sources-csv.html). +**Option names should be in** `camelCase`! + +The set of supported options depends on Spark version. + +Reading files + +```py +from onetl.file.format import CSV + +csv = CSV(header=True, inferSchema=True, mode="PERMISSIVE") +``` + +Writing files + +```py +from onetl.file.format import CSV + +csv = CSV(header=True, compression="gzip") +``` + + + +#### *field* delimiter *: str* *= ','* *(alias 'sep')* + +Character used to separate fields in CSV row. + + + +#### *field* header *: bool | None* *= None* + +If `True`, the first row of the file is considered a header. +Default `False`. + + + +#### *field* quote *: str* *= '"'* + +Character used to quote field values within CSV field. + +Empty string is considered as `\u0000` (`NUL`) character. + + +* **Constraints:** + - **maxLength** = 1 + +#### *field* quoteAll *: bool | None* *= None* + +If `True`, all fields are quoted: + +```csv +"some","field with \"quote","123","" +``` + +If `False`, only quote fields containing [`quote`](#onetl.file.format.csv.CSV.quote) symbols. + +```csv +any,"field with \"quote",123, +``` + +Default `False`. + +#### NOTE +Used only for writing files. + + + +#### *field* compression *: str | Literal['none', 'bzip2', 'gzip', 'lz4', 'snappy', 'deflate'] | None* *= None* + +Compression codec of the CSV file. +Default `none`. + +#### NOTE +Used only for writing files. Ignored by [`parse_column`](#onetl.file.format.csv.CSV.parse_column) method. + + + +#### *field* inferSchema *: bool | None* *= None* + +If `True`, try to infer the input schema by reading a sample of the file (see [`samplingRatio`](#onetl.file.format.csv.CSV.samplingRatio)). +Default `False` which means that all parsed columns will be `StringType()`. + +#### NOTE +Used only for reading files, and only if user haven’t provider explicit DataFrame schema. +Ignored by [`parse_column`](#onetl.file.format.csv.CSV.parse_column) function. + + + +#### *field* samplingRatio *: float | None* *= None* + +For `inferSchema=True`, read the specified fraction of rows to infer the schema. +Default `1`. + +#### NOTE +Used only for reading files. Ignored by [`parse_column`](#onetl.file.format.csv.CSV.parse_column) function. + + +* **Constraints:** + - **minimum** = 0 + - **maximum** = 1 + +#### *field* comment *: str | None* *= None* + +If set, all lines starting with specified character (e.g. `#`) are considered a comment, and skipped. +Default is not set, meaning that CSV lines should not contain comments. + +#### NOTE +Used only for reading files and [`parse_column`](#onetl.file.format.csv.CSV.parse_column) method. + + +* **Constraints:** + - **maxLength** = 1 + +#### *field* enforceSchema *: bool | None* *= None* + +If `True`, inferred or user-provided schema has higher priority than CSV file headers. +This means that all input files should have the same structure. + +If `False`, CSV headers are used as a primary source of information about column names and their position. + +Default `True`. + +#### NOTE +Used only for reading files. Ignored by [`parse_column`](#onetl.file.format.csv.CSV.parse_column) function. + + + +#### *field* escapeQuotes *: bool | None* *= None* + +If `True`, escape quotes within CSV field. + +```csv +any,field with \"quote,123, +``` + +If `False`, wrap fields containing [`quote`](#onetl.file.format.csv.CSV.quote) symbols with quotes. + +```csv +any,"field with ""quote ",123, +``` + +Default `True`. + +#### NOTE +Used only for writing files. + + + +#### *field* unescapedQuoteHandling *: None | Literal['STOP_AT_CLOSING_QUOTE', 'BACK_TO_DELIMITER', 'STOP_AT_DELIMITER', 'SKIP_VALUE', 'RAISE_ERROR']* *= None* + +Define how to handle unescaped quotes within CSV field. + +* `STOP_AT_CLOSING_QUOTE` - collect all characters until closing quote. +* `BACK_TO_DELIMITER` - collect all characters until delimiter or line end. +* `STOP_AT_DELIMITER` - collect all characters until delimiter or line end. + : If quotes are not closed, this may produce incorrect results (e.g. including delimiter inside field value). +* `SKIP_VALUE` - skip field and consider it as [`nullValue`](#onetl.file.format.csv.CSV.nullValue). +* `RAISE_ERROR` - raise error immediately. + +Default `STOP_AT_DELIMITER`. + +#### NOTE +Used only for reading files and [`parse_column`](#onetl.file.format.csv.CSV.parse_column) method. + + + +#### *field* ignoreLeadingWhiteSpace *: bool | None* *= None* + +If `True`, trim leading whitespaces in field value. + +Defaults: +: * `True` for reading. + * `False` for writing. + + + +#### *field* ignoreTrailingWhiteSpace *: bool | None* *= None* + +If `True`, trim trailing whitespaces in field value. + +Defaults: +: * `True` for reading. + * `False` for writing. + + + +#### *field* emptyValue *: str | None* *= None* + +Value used for empty string fields. + +Defaults: +: * empty string for reading. + * `""` for writing. + + + +#### *field* nullValue *: str | None* *= None* + +If set, this value will be converted to `null`. +Default is empty string. + + + +#### *field* nanValue *: str | None* *= None* + +If set, this string will be considered as Not-A-Number (NaN) value for `FloatType()` and `DoubleType()`. +Default is `NaN`. + +#### NOTE +Used only for reading files and [`parse_column`](#onetl.file.format.csv.CSV.parse_column) method. + + + +#### *field* positiveInf *: str | None* *= None* + +If set, this string will be considered as positive infinity value for `FloatType()` and `DoubleType()`. +Default is `Inf`. + +#### NOTE +Used only for reading files and [`parse_column`](#onetl.file.format.csv.CSV.parse_column) method. + + +* **Constraints:** + - **minLength** = 1 + +#### \_\_init_\_(\*\*kwargs) + + + +#### *field* negativeInf *: str | None* *= None* + +If set, this string will be considered as negative infinity value for `FloatType()` and `DoubleType()`. +Default is `-Inf`. + +#### NOTE +Used only for reading files and [`parse_column`](#onetl.file.format.csv.CSV.parse_column) method. + + +* **Constraints:** + - **minLength** = 1 + +#### *field* preferDate *: bool | None* *= None* + +If `True` and `inferSchema=True` and column does match [`dateFormat`](#onetl.file.format.csv.CSV.dateFormat), consider it as `DateType()`. +For columns matching both [`timestampFormat`](#onetl.file.format.csv.CSV.timestampFormat) and [`dateFormat`](#onetl.file.format.csv.CSV.dateFormat), consider it as `TimestampType()`. + +If `False`, date and timestamp columns will be considered as `StringType()`. + +Default `True`. + +#### NOTE +Used only for reading files. Ignored by [`parse_column`](#onetl.file.format.csv.CSV.parse_column) function. + + + +#### *field* dateFormat *: str | None* *= None* + +String format for `DateType()` representation. +Default is `yyyy-MM-dd`. + + +* **Constraints:** + - **minLength** = 1 + +#### *field* timestampFormat *: str | None* *= None* + +String format for TimestampType()\` representation. +Default is `yyyy-MM-dd'T'HH:mm:ss[.SSS][XXX]`. + + +* **Constraints:** + - **minLength** = 1 + +#### *field* timestampNTZFormat *: str | None* *= None* + +String format for TimestampNTZType()\` representation. +Default is `yyyy-MM-dd'T'HH:mm:ss[.SSS]`. + +#### NOTE +Added in Spark 3.2.0 + + +* **Constraints:** + - **minLength** = 1 + +#### *field* locale *: str | None* *= None* + +Locale name used to parse dates and timestamps. +Default is `en-US` + +#### NOTE +Used only for reading files and [`parse_column`](#onetl.file.format.csv.CSV.parse_column) method. + + +* **Constraints:** + - **minLength** = 1 + +#### *field* maxCharsPerColumn *: int | None* *= None* + +Maximum number of characters to read per column. +Default is `-1`, which means no limit. + +#### NOTE +Used only for reading files and [`parse_column`](#onetl.file.format.csv.CSV.parse_column) method. + + + +#### *field* mode *: Literal['PERMISSIVE', 'DROPMALFORMED', 'FAILFAST'] | None* *= None* + +How to handle parsing errors: +: * `PERMISSIVE` - set field value as `null`, move raw data to [`columnNameOfCorruptRecord`](#onetl.file.format.csv.CSV.columnNameOfCorruptRecord) column. + * `DROPMALFORMED` - skip the malformed row. + * `FAILFAST` - throw an error immediately. + +Default is `PERMISSIVE`. + +#### NOTE +Used only for reading files and [`parse_column`](#onetl.file.format.csv.CSV.parse_column) method. + + + +#### *field* columnNameOfCorruptRecord *: str | None* *= None* + +Name of column to put corrupt records in. +Default is `_corrupt_record`. + +#### WARNING +If DataFrame schema is provided, this column should be added to schema explicitly: + +```python +from onetl.connection import SparkLocalFS +from onetl.file import FileDFReader +from onetl.file.format import CSV + +from pyspark.sql.types import StructType, StructField, TimestampType, StringType + +spark = ... + +schema = StructType( + [ + StructField("my_field", TimestampType()), + StructField("_corrupt_record", StringType()), # <-- important + ] +) + +csv = CSV(mode="PERMISSIVE", columnNameOfCorruptRecord="_corrupt_record") + +reader = FileDFReader( + connection=connection, + format=csv, + schema=schema, # < --- +) +df = reader.run(["/some/file.csv"]) +``` + +#### NOTE +Used only for reading files and [`parse_column`](#onetl.file.format.csv.CSV.parse_column) method. + + +* **Constraints:** + - **minLength** = 1 + +#### *field* multiLine *: bool | None* *= None* + +If `True`, fields may contain line separators. +If `False`, the input is expected to have one record per file. + +Default is `True`. + +#### NOTE +Used only for reading files. +Ignored by [`parse_column`](#onetl.file.format.csv.CSV.parse_column) method, as it expects that each DataFrame row will contain exactly one CSV line. + + + +#### *field* charToEscapeQuoteEscaping *: str | None* *= None* + +If CSV field value contains `escape` character, it should be escaped as well. +For example, if `escape="\"`, when line: + +```csv +"some \" quoted value",other +"some \\ backslashed value",another +``` + +will be parsed as: + +```python +[ + ('some " quoted value', "other"), + ("some \ backslashed value", "another"), +] +``` + +And vice-versa, for writing CSV rows to file. + +Default is same as `escape`. + + +* **Constraints:** + - **maxLength** = 1 + +#### parse_column(column: str | Column, schema: StructType) → Column + +Parses a CSV string column to a structured Spark SQL column using Spark’s +[from_csv](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.functions.from_csv.html) function, based on the provided schema. + +#### NOTE +Can be used only with Spark 3.x+ + +#### Versionadded +Added in version 0.11.0. + +* **Parameters:** + **column** + : The name of the column or the column object containing CSV strings/bytes to parse. + + **schema** + : The schema to apply when parsing the CSV data. This defines the structure of the output DataFrame column. +* **Returns:** + Column with deserialized data, with the same structure as the provided schema. Column name is the same as input column. + +### Examples + +```pycon +>>> from pyspark.sql.types import StructType, StructField, IntegerType, StringType +>>> from onetl.file.format import CSV +>>> df.show() ++--+--------+ +|id|value | ++--+--------+ +|1 |Alice;20| +|2 |Bob;25 | ++--+--------+ +>>> df.printSchema() +root +|-- id: integer (nullable = true) +|-- value: string (nullable = true) +>>> csv = CSV(delimiter=";") +>>> csv_schema = StructType( +... [ +... StructField("name", StringType(), nullable=True), +... StructField("age", IntegerType(), nullable=True), +... ], +... ) +>>> parsed_df = df.select("id", csv.parse_column("value", csv_schema)) +>>> parsed_df.show() ++--+-----------+ +|id|value | ++--+-----------+ +|1 |{Alice, 20}| +|2 | {Bob, 25}| ++--+-----------+ +>>> parsed_df.printSchema() +root +|-- id: integer (nullable = true) +|-- value: struct (nullable = true) +| |-- name: string (nullable = true) +| |-- age: integer (nullable = true) +``` + + + +#### serialize_column(column: str | Column) → Column + +Serializes a structured Spark SQL column into a CSV string column using Spark’s +[to_csv](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.functions.to_csv.html) function. + +#### NOTE +Can be used only with Spark 3.x+ + +#### Versionadded +Added in version 0.11.0. + +* **Parameters:** + **column** + : The name of the column or the Column object containing the data to serialize to CSV. +* **Returns:** + Column with string CSV data. Column name is the same as input column. + +### Examples + +```pycon +>>> from pyspark.sql.functions import decode +>>> from onetl.file.format import CSV +>>> df.show() ++--+-----------+ +|id|value | ++--+-----------+ +|1 |{Alice, 20}| +|2 | {Bob, 25}| ++--+-----------+ +>>> df.printSchema() +root +|-- id: integer (nullable = true) +|-- value: struct (nullable = true) +| |-- name: string (nullable = true) +| |-- age: integer (nullable = true) +>>> # serializing data into CSV format +>>> csv = CSV(delimiter=";") +>>> serialized_df = df.select("id", csv.serialize_column("value")) +>>> serialized_df.show(truncate=False) ++--+--------+ +|id|value | ++--+--------+ +|1 |Alice;20| +|2 |Bob;25 | ++--+--------+ +>>> serialized_df.printSchema() +root +|-- id: integer (nullable = true) +|-- value: string (nullable = true) +``` + + diff --git a/mddocs/file_df/file_formats/excel.md b/mddocs/file_df/file_formats/excel.md new file mode 100644 index 000000000..6925c91ca --- /dev/null +++ b/mddocs/file_df/file_formats/excel.md @@ -0,0 +1,232 @@ + + +# Excel + +### *class* onetl.file.format.excel.Excel(\*, header: bool = False, dataAddress: str | None = None, timestampFormat: str | None = None, dateFormat: str | None = None, treatEmptyValuesAsNulls: bool | None = None, setErrorCellsToFallbackValues: bool | None = None, usePlainNumberFormat: bool | None = None, inferSchema: bool | None = None, workbookPassword: SecretStr | None = None, maxRowsInMemory: int | None = None, maxByteArraySize: ByteSize | None = None, tempFileThreshold: ByteSize | None = None, excerptSize: int | None = None, \*\*kwargs) + +Excel file format. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on [Spark Excel](https://github.com/crealytics/spark-excel) file format. + +Supports reading/writing files with `.xlsx` (read/write) and `.xls` (read only) extensions. + +### Version compatibility + +* Spark versions: 3.2.x - 3.5.x + > #### WARNING + > Not all combinations of Spark version and package version are supported. + > See [Maven index](https://mvnrepository.com/artifact/com.crealytics/spark-excel) + > and [official documentation](https://github.com/crealytics/spark-excel). +* Java versions: 8 - 20 + +See documentation from link above. + +#### Versionadded +Added in version 0.9.4. + +### Examples + +#### NOTE +You can pass any option mentioned in +[official documentation](https://github.com/crealytics/spark-excel). +**Option names should be in** `camelCase`! + +The set of supported options depends on `spark-excel` package version. + +Reading files + +```py +from pyspark.sql import SparkSession +from onetl.file.format import Excel + +# Create Spark session with Excel package loaded +maven_packages = Excel.get_packages(spark_version="3.5.1") +spark = ( + SparkSession.builder.appName("spark-app-name") + .config("spark.jars.packages", ",".join(maven_packages)) + .getOrCreate() +) + +excel = Excel(header=True, inferSchema=True) +``` + +Writing files + +```py +# Create Spark session with Excel package loaded +spark = ... + +from onetl.file.format import XML + +excel = Excel(header=True, dataAddress="'Sheet1'!A1") +``` + + + +#### *field* header *: bool* *= False* + +If `True`, the first row in file is conditioned as a header. +Default `False`. + + + +#### *field* dataAddress *: str | None* *= None* + +Cell address used as starting point. +For example: `'A1'` or `'Sheet1'!A1` + + + +#### *field* timestampFormat *: str | None* *= None* + +Format string used for parsing or serializing timestamp values. +Default `yyyy-mm-dd hh:mm:ss[.fffffffff]`. + + + +#### *field* treatEmptyValuesAsNulls *: bool | None* *= None* + +If `True`, empty cells are parsed as `null` values. +If `False`, empty cells are parsed as empty strings. +Default `True`. + +#### NOTE +Used only for reading files. + + + +#### *field* setErrorCellsToFallbackValues *: bool | None* *= None* + +If `True`, cells containing `#N/A` value are replaced with default value for column type, +e.g. 0 for `IntegerType()`. If `False`, `#N/A` values are replaced with `null`. +Default `False`. + +#### NOTE +Used only for reading files. + + + +#### *field* usePlainNumberFormat *: bool | None* *= None* + +If `True`, read or write numeric values with plain format, without using scientific notation or rounding. +Default `False`. + + + +#### *field* inferSchema *: bool | None* *= None* + +If `True`, infer DataFrame schema based on cell content. +If `False` and no explicit DataFrame schema is passed, all columns are `StringType()`. + +#### NOTE +Used only for reading files. + + + +#### *field* workbookPassword *: SecretStr | None* *= None* + +If Excel file is encrypted, provide password to open it. + +#### NOTE +Used only for reading files. Cannot be used to write files. + + +* **Constraints:** + - **type** = string + - **writeOnly** = True + - **format** = password + +#### *field* maxRowsInMemory *: int | None* *= None* + +If set, use streaming reader and fetch only specified number of rows per iteration. +This reduces memory usage for large files. +Default `None`, which means reading the entire file content to memory. + +#### WARNING +Can be used only with `.xlsx` files, but fails on `.xls`. + +#### NOTE +Used only for reading files. + + + +#### *field* maxByteArraySize *: ByteSize | None* *= None* + +If set, overrides memory limit (in bytes) of byte array size used for reading rows from input file. +Default `0`, which means using default limit. + +See [IOUtils.setByteArrayMaxOverride](https://poi.apache.org/apidocs/5.0/org/apache/poi/util/IOUtils.html#setByteArrayMaxOverride-int-) +documentation. + +#### NOTE +Used only for reading files. + + + +#### *field* tempFileThreshold *: ByteSize | None* *= None* + +If value is greater than 0, large zip entries will be written to temporary files after reaching this threshold. +If value is 0, all zip entries will be written to temporary files. +If value is -1, no temp files will be created, which may cause errors if zip entry is larger than 2GiB. + +#### NOTE +Used only for reading files. + + + +#### *field* excerptSize *: int | None* *= None* + +If `inferSchema=True`, set number of rows to infer schema from. +Default `10`. + +#### NOTE +Used only for reading files. + + + +#### *classmethod* get_packages(spark_version: str, scala_version: str | None = None, package_version: str | None = None) → list[str] + +Get package names to be downloaded by Spark. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### WARNING +Not all combinations of Spark version and package version are supported. +See [Maven index](https://mvnrepository.com/artifact/com.crealytics/spark-excel) +and [official documentation](https://github.com/crealytics/spark-excel). + +#### Versionadded +Added in version 0.9.4. + +* **Parameters:** + **spark_version** + : Spark version in format `major.minor.patch`. + + **scala_version** + : Scala version in format `major.minor`. +
+ If `None`, `spark_version` is used to determine Scala version. + + **package_version** + : Package version in format `major.minor.patch`. Default is `0.20.4`. +
+ #### WARNING + Version `0.14` and below are not supported. +
+ #### NOTE + It is not guaranteed that custom package versions are supported. + Tests are performed only for default version. + +### Examples + +```python +from onetl.file.format import Excel + +Excel.get_packages(spark_version="3.5.1") +Excel.get_packages(spark_version="3.5.1", scala_version="2.12") +Excel.get_packages( + spark_version="3.5.1", + scala_version="2.12", + package_version="0.20.4", +) +``` + + diff --git a/mddocs/file_df/file_formats/index.md b/mddocs/file_df/file_formats/index.md new file mode 100644 index 000000000..a6500bdf3 --- /dev/null +++ b/mddocs/file_df/file_formats/index.md @@ -0,0 +1,18 @@ + + +# File Formats + +# File formats + +* [Avro](avro.md) +* [CSV](csv.md) +* [Excel](excel.md) +* [JSON](json.md) +* [JSONLine](jsonline.md) +* [ORC](orc.md) +* [Parquet](parquet.md) +* [XML](xml.md) + +# For developers + +* [Base interface](base.md) diff --git a/mddocs/file_df/file_formats/json.md b/mddocs/file_df/file_formats/json.md new file mode 100644 index 000000000..fd6ee8da7 --- /dev/null +++ b/mddocs/file_df/file_formats/json.md @@ -0,0 +1,414 @@ + + +# JSON + +### *class* onetl.file.format.json.JSON(\*, multiLine: Literal[True] = True, encoding: str | None = None, lineSep: str | None = None, allowComments: bool | None = None, allowUnquotedFieldNames: bool | None = None, allowSingleQuotes: bool | None = None, allowNumericLeadingZeros: bool | None = None, allowNonNumericNumbers: bool | None = None, allowBackslashEscapingAnyCharacter: bool | None = None, allowUnquotedControlChars: bool | None = None, mode: Literal['PERMISSIVE', 'DROPMALFORMED', 'FAILFAST'] | None = None, columnNameOfCorruptRecord: ConstrainedStrValue | None = None, samplingRatio: ConstrainedFloatValue | None = None, primitivesAsString: bool | None = None, prefersDecimal: bool | None = None, dropFieldIfAllNull: bool | None = None, dateFormat: ConstrainedStrValue | None = None, timestampFormat: ConstrainedStrValue | None = None, timestampNTZFormat: ConstrainedStrValue | None = None, timeZone: ConstrainedStrValue | None = None, locale: ConstrainedStrValue | None = None, \*\*kwargs) + +JSON file format. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on [Spark JSON](https://spark.apache.org/docs/latest/sql-data-sources-json.html) file format. + +Supports reading (but **NOT** writing) files with `.json` extension with content like: + +```json +[ + {"key": "value1"}, + {"key": "value2"} +] +``` + +#### Versionadded +Added in version 0.9.0. + +### Examples + +#### NOTE +You can pass any option mentioned in +[official documentation](https://spark.apache.org/docs/latest/sql-data-sources-json.html). +**Option names should be in** `camelCase`! + +The set of supported options depends on Spark version. + +Reading files: + +```python +from onetl.file.format import JSON + +json = JSON(encoding="UTF-8") +``` + +Writing files: + +#### WARNING +Not supported. Use [`JSONLine`](jsonline.md#onetl.file.format.jsonline.JSONLine). + + + +#### *field* encoding *: str | None* *= None* + +Encoding of the JSON file. +Default `UTF-8`. + +#### NOTE +Used only for reading and writing files. + +Ignored by [`parse_column`](#onetl.file.format.json.JSON.parse_column) and [`serialize_column`](#onetl.file.format.json.JSON.serialize_column) methods. + + + +#### *field* lineSep *: str | None* *= None* + +Character used to separate lines in the JSON file. + +Defaults: +: * Try to detect for reading (`\r\n`, `\r`, `\n`) + * `\n` for writing + +#### NOTE +Used only for reading and writing files. + +Ignored by [`parse_column`](#onetl.file.format.json.JSON.parse_column) and [`serialize_column`](#onetl.file.format.json.JSON.serialize_column) methods, +as they handle each DataFrame row separately. + + + +#### *field* allowComments *: bool | None* *= None* + +If `True`, add support for C/C++/Java style comments (`//`, `/* */`). +Default `False`, meaning that JSON files should not contain comments. + +#### NOTE +Used only for reading files and [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. + + + +#### *field* allowUnquotedFieldNames *: bool | None* *= None* + +If `True`, allow JSON object field names without quotes (JavaScript-style). +Default `False`. + +#### NOTE +Used only for reading files and [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. + + + +#### *field* allowSingleQuotes *: bool | None* *= None* + +If `True`, allow JSON object field names to be wrapped with single quotes (`'`). +Default `True`. + +#### NOTE +Used only for reading files and [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. + + + +#### *field* allowNumericLeadingZeros *: bool | None* *= None* + +If `True`, allow leading zeros in numbers (e.g. `00012`). +Default `False`. + +#### NOTE +Used only for reading files and [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. + + + +#### *field* allowNonNumericNumbers *: bool | None* *= None* + +If `True`, allow numbers to contain non-numeric characters, like: +: * scientific notation (e.g. `12e10`). + * positive infinity floating point value (`Infinity`, `+Infinity`, `+INF`). + * negative infinity floating point value (`-Infinity`, `-INF`). + * Not-a-Number floating point value (`NaN`). + +Default `True`. + +#### NOTE +Used only for reading files and [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. + + + +#### *field* allowBackslashEscapingAnyCharacter *: bool | None* *= None* + +If `True`, prefix `\` can escape any character. +Default `False`. + +#### NOTE +Used only for reading files and [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. + + + +#### *field* allowUnquotedControlChars *: bool | None* *= None* + +If `True`, allow unquoted control characters (ASCII values 0-31) in strings without escaping them with `\`. +Default `False`. + +#### NOTE +Used only for reading files and [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. + + + +#### *field* mode *: Literal['PERMISSIVE', 'DROPMALFORMED', 'FAILFAST'] | None* *= None* + +How to handle parsing errors: +: * `PERMISSIVE` - set field value as `null`, move raw data to [`columnNameOfCorruptRecord`](#onetl.file.format.json.JSON.columnNameOfCorruptRecord) column. + * `DROPMALFORMED` - skip the malformed row. + * `FAILFAST` - throw an error immediately. + +Default is `PERMISSIVE`. + +#### NOTE +Used only for reading files and [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. + + + +#### *field* columnNameOfCorruptRecord *: str | None* *= None* + +Name of column to put corrupt records in. +Default is `_corrupt_record`. + +#### WARNING +If DataFrame schema is provided, this column should be added to schema explicitly: + +```python +from onetl.connection import SparkLocalFS +from onetl.file import FileDFReader +from onetl.file.format import JSON + +from pyspark.sql.types import StructType, StructField, TimestampType, StringType + +spark = ... + +schema = StructType( + [ + StructField("my_field", TimestampType()), + StructField("_corrupt_record", StringType()), # <-- important + ] +) + +json = JSON(mode="PERMISSIVE", columnNameOfCorruptRecord="_corrupt_record") + +reader = FileDFReader( + connection=connection, + format=json, + schema=schema, # < --- +) +df = reader.run(["/some/file.json"]) +``` + +#### NOTE +Used only for reading files and [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. + + +* **Constraints:** + - **minLength** = 1 + +#### *field* samplingRatio *: float | None* *= None* + +While inferring schema, read the specified fraction of file rows. +Default `1`. + +#### NOTE +Used only for reading files. Ignored by [`parse_column`](#onetl.file.format.json.JSON.parse_column) function. + + +* **Constraints:** + - **minimum** = 0 + - **maximum** = 1 + +#### *field* primitivesAsString *: bool | None* *= None* + +If `True`, infer all primitive types (string, integer, float, boolean) as strings. +Default `False`. + +#### NOTE +Used only for reading files. Ignored by [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. + + + +#### *field* prefersDecimal *: bool | None* *= None* + +If `True`, infer all floating-point values as `Decimal`. +Default `False`. + +#### NOTE +Used only for reading files. Ignored by [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. + + + +#### \_\_init_\_(\*\*kwargs) + + + +#### *field* dropFieldIfAllNull *: bool | None* *= None* + +If `True` and inferred column is always null or empty array, exclude if from DataFrame schema. +Default `False`. + +#### NOTE +Used only for reading files. Ignored by [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. + + + +#### *field* dateFormat *: str | None* *= None* + +String format for `DateType()` representation. +Default is `yyyy-MM-dd`. + + +* **Constraints:** + - **minLength** = 1 + +#### *field* timestampFormat *: str | None* *= None* + +String format for TimestampType()\` representation. +Default is `yyyy-MM-dd'T'HH:mm:ss[.SSS][XXX]`. + + +* **Constraints:** + - **minLength** = 1 + +#### *field* timestampNTZFormat *: str | None* *= None* + +String format for TimestampNTZType()\` representation. +Default is `yyyy-MM-dd'T'HH:mm:ss[.SSS]`. + +#### NOTE +Added in Spark 3.2.0 + + +* **Constraints:** + - **minLength** = 1 + +#### *field* timezone *: str | None* *= None* *(alias 'timeZone')* + +Allows to override timezone used for parsing or serializing date and timestamp values. +By default, `spark.sql.session.timeZone` is used. + + +* **Constraints:** + - **minLength** = 1 + +#### *field* locale *: str | None* *= None* + +Locale name used to parse dates and timestamps. +Default is `en-US`. + +#### NOTE +Used only for reading files and [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. + + +* **Constraints:** + - **minLength** = 1 + +#### parse_column(column: str | Column, schema: StructType | ArrayType | MapType) → Column + +Parses a JSON string column to a structured Spark SQL column using Spark’s [from_json](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.functions.from_json.html) function, based on the provided schema. + +#### Versionadded +Added in version 0.11.0. + +* **Parameters:** + **column** + : The name of the column or the column object containing JSON strings/bytes to parse. + + **schema** + : The schema to apply when parsing the JSON data. This defines the structure of the output DataFrame column. +* **Returns:** + Column with deserialized data, with the same structure as the provided schema. Column name is the same as input column. + +### Examples + +```pycon +>>> from pyspark.sql.types import StructType, StructField, IntegerType, StringType +>>> from pyspark.sql.functions import decode +>>> from onetl.file.format import JSON +>>> df.show() ++----+--------------------+----------+---------+------+-----------------------+-------------+ +|key |value |topic |partition|offset|timestamp |timestampType| ++----+--------------------+----------+---------+------+-----------------------+-------------+ +|[31]|[7B 22 6E 61 6D 6...|topicJSON |0 |0 |2024-04-24 16:51:11.739|0 | +|[32]|[7B 22 6E 61 6D 6...|topicJSON |0 |1 |2024-04-24 16:51:11.749|0 | ++----+--------------------+----------+---------+------+-----------------------+-------------+ +>>> df.printSchema() +root +|-- key: binary (nullable = true) +|-- value: binary (nullable = true) +|-- topic: string (nullable = true) +|-- partition: integer (nullable = true) +|-- offset: integer (nullable = true) +|-- timestamp: timestamp (nullable = true) +|-- timestampType: integer (nullable = true) +>>> json = JSON() +>>> json_schema = StructType( +... [ +... StructField("name", StringType(), nullable=True), +... StructField("age", IntegerType(), nullable=True), +... ], +... ) +>>> parsed_df = df.select(decode("key", "UTF-8").alias("key"), json.parse_column("value", json_schema)) +>>> parsed_df.show() ++---+-----------+ +|key|value | ++---+-----------+ +|1 |{Alice, 20}| +|2 | {Bob, 25}| ++---+-----------+ +>>> parsed_df.printSchema() +root +|-- key: string (nullable = true) +|-- value: struct (nullable = true) +| |-- name: string (nullable = true) +| |-- age: integer (nullable = true) +``` + + + +#### serialize_column(column: str | Column) → Column + +Serializes a structured Spark SQL column into a JSON string column using Spark’s +[to_json](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.functions.to_json.html) function. + +#### Versionadded +Added in version 0.11.0. + +* **Parameters:** + **column** + : The name of the column or the column object containing the data to serialize to JSON format. +* **Returns:** + Column with string JSON data. Column name is the same as input column. + +### Examples + +```pycon +>>> from pyspark.sql.functions import decode +>>> from onetl.file.format import JSON +>>> df.show() ++---+-----------+ +|key|value | ++---+-----------+ +|1 |{Alice, 20}| +|2 | {Bob, 25}| ++---+-----------+ +>>> df.printSchema() +root +|-- key: string (nullable = true) +|-- value: struct (nullable = true) +| |-- name: string (nullable = true) +| |-- age: integer (nullable = true) +>>> # serializing data into JSON format +>>> json = JSON() +>>> serialized_df = df.select("key", json.serialize_column("value")) +>>> serialized_df.show(truncate=False) ++---+-------------------------+ +|key|value | ++---+-------------------------+ +| 1|{"name":"Alice","age":20}| +| 2|{"name":"Bob","age":25} | ++---+-------------------------+ +>>> serialized_df.printSchema() +root +|-- key: string (nullable = true) +|-- value: string (nullable = true) +``` + + diff --git a/mddocs/file_df/file_formats/jsonline.md b/mddocs/file_df/file_formats/jsonline.md new file mode 100644 index 000000000..644b66bcb --- /dev/null +++ b/mddocs/file_df/file_formats/jsonline.md @@ -0,0 +1,314 @@ + + +# JSONLine + +### *class* onetl.file.format.jsonline.JSONLine(\*, multiLine: Literal[False] = False, encoding: str | None = None, lineSep: str | None = None, compression: str | Literal['none', 'bzip2', 'gzip', 'lz4', 'snappy', 'deflate'] | None = None, ignoreNullFields: bool | None = None, allowComments: bool | None = None, allowUnquotedFieldNames: bool | None = None, allowSingleQuotes: bool | None = None, allowNumericLeadingZeros: bool | None = None, allowNonNumericNumbers: bool | None = None, allowBackslashEscapingAnyCharacter: bool | None = None, allowUnquotedControlChars: bool | None = None, mode: Literal['PERMISSIVE', 'DROPMALFORMED', 'FAILFAST'] | None = None, columnNameOfCorruptRecord: ConstrainedStrValue | None = None, samplingRatio: ConstrainedFloatValue | None = None, primitivesAsString: bool | None = None, prefersDecimal: bool | None = None, dropFieldIfAllNull: bool | None = None, dateFormat: ConstrainedStrValue | None = None, timestampFormat: ConstrainedStrValue | None = None, timestampNTZFormat: ConstrainedStrValue | None = None, timeZone: ConstrainedStrValue | None = None, locale: ConstrainedStrValue | None = None, \*\*kwargs) + +JSONLine file format (each line of file contains a JSON object). [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on [Spark JSON](https://spark.apache.org/docs/latest/sql-data-sources-json.html) file format. + +Supports reading/writing files with `.json` extension with content like: + +```json +{"key": "value1"} +{"key": "value2"} +``` + +#### Versionadded +Added in version 0.9.0. + +### Examples + +#### NOTE +You can pass any option mentioned in +[official documentation](https://spark.apache.org/docs/latest/sql-data-sources-json.html). +**Option names should be in** `camelCase`! + +The set of supported options depends on Spark version. + +Reading files + +```py +from onetl.file.format import JSONLine + +jsonline = JSONLine(encoding="UTF-8", mode="PERMISSIVE") +``` + +Writing files + +#### WARNING +Written files have extension `.json`, not `.jsonl` or `.jsonline`. + +```python +from onetl.file.format import JSONLine + +jsonline = JSONLine(encoding="UTF-8", compression="gzip") +``` + + + +#### *field* encoding *: str | None* *= None* + +Encoding of the JSONLine files. +Default `UTF-8`. + + + +#### *field* lineSep *: str | None* *= None* + +Character used to separate lines in the JSONLine files. + +Defaults: +: * Try to detect for reading (`\r\n`, `\r`, `\n`) + * `\n` for writing. + + + +#### *field* compression *: str | Literal['none', 'bzip2', 'gzip', 'lz4', 'snappy', 'deflate'] | None* *= None* + +Compression codec of the JSONLine file. +Default `none`. + +#### NOTE +Used only for writing files. + + + +#### *field* ignoreNullFields *: bool | None* *= None* + +If `True` and field value is `null`, don’t add field into resulting object +Default is value of `spark.sql.jsonGenerator.ignoreNullFields` (`True`). + +#### NOTE +Used only for writing files. + + + +#### *field* allowComments *: bool | None* *= None* + +If `True`, add support for C/C++/Java style comments (`//`, `/* */`). +Default `False`, meaning that JSONLine files should not contain comments. + +#### NOTE +Used only for reading files. + + + +#### *field* allowUnquotedFieldNames *: bool | None* *= None* + +If `True`, allow JSON object field names without quotes (JavaScript-style). +Default `False`. + +#### NOTE +Used only for reading files. + + + +#### *field* allowSingleQuotes *: bool | None* *= None* + +If `True`, allow JSON object field names to be wrapped with single quotes (`'`). +Default `True`. + +#### NOTE +Used only for reading files. + + + +#### *field* allowNumericLeadingZeros *: bool | None* *= None* + +If `True`, allow leading zeros in numbers (e.g. `00012`). +Default `False`. + +#### NOTE +Used only for reading files. + + + +#### *field* allowNonNumericNumbers *: bool | None* *= None* + +If `True`, allow numbers to contain non-numeric characters, like: +: * scientific notation (e.g. `12e10`). + * positive infinity floating point value (`Infinity`, `+Infinity`, `+INF`). + * negative infinity floating point value (`-Infinity`, `-INF`). + * Not-a-Number floating point value (`NaN`). + +Default `True`. + +#### NOTE +Used only for reading files. + + + +#### *field* allowBackslashEscapingAnyCharacter *: bool | None* *= None* + +If `True`, prefix `\` can escape any character. +Default `False`. + +#### NOTE +Used only for reading files. + + + +#### *field* allowUnquotedControlChars *: bool | None* *= None* + +If `True`, allow unquoted control characters (ASCII values 0-31) in strings without escaping them with `\`. +Default `False`. + +#### NOTE +Used only for reading files. + + + +#### *field* mode *: Literal['PERMISSIVE', 'DROPMALFORMED', 'FAILFAST'] | None* *= None* + +How to handle parsing errors: +: * `PERMISSIVE` - set field value as `null`, move raw data to [`columnNameOfCorruptRecord`](#onetl.file.format.jsonline.JSONLine.columnNameOfCorruptRecord) column. + * `DROPMALFORMED` - skip the malformed row. + * `FAILFAST` - throw an error immediately. + +Default is `PERMISSIVE`. + +#### NOTE +Used only for reading files. + + + +#### *field* columnNameOfCorruptRecord *: str | None* *= None* + +Name of column to put corrupt records in. +Default is `_corrupt_record`. + +#### WARNING +If DataFrame schema is provided, this column should be added to schema explicitly: + +```python +from onetl.connection import SparkLocalFS +from onetl.file import FileDFReader +from onetl.file.format import JSONLine + +from pyspark.sql.types import StructType, StructField, TimestampType, StringType + +spark = ... + +schema = StructType( + [ + StructField("my_field", TimestampType()), + StructField("_corrupt_record", StringType()), # <-- important + ] +) + +jsonline = JSONLine(mode="PERMISSIVE", columnNameOfCorruptRecord="_corrupt_record") + +reader = FileDFReader( + connection=connection, + format=jsonline, + schema=schema, # < --- +) +df = reader.run(["/some/file.jsonl"]) +``` + +#### NOTE +Used only for reading files. + + +* **Constraints:** + - **minLength** = 1 + +#### \_\_init_\_(\*\*kwargs) + + + +#### *field* samplingRatio *: float | None* *= None* + +While inferring schema, read the specified fraction of file rows. +Default `1`. + +#### NOTE +Used only for reading files. + + +* **Constraints:** + - **minimum** = 0 + - **maximum** = 1 + +#### *field* primitivesAsString *: bool | None* *= None* + +If `True`, infer all primitive types (string, integer, float, boolean) as strings. +Default `False`. + +#### NOTE +Used only for reading files. + + + +#### *field* prefersDecimal *: bool | None* *= None* + +If `True`, infer all floating-point values as `Decimal`. +Default `False`. + +#### NOTE +Used only for reading files. + + + +#### *field* dropFieldIfAllNull *: bool | None* *= None* + +If `True` and inferred column is always null or empty array, exclude if from DataFrame schema. +Default `False`. + +#### NOTE +Used only for reading files. + + + +#### *field* dateFormat *: str | None* *= None* + +String format for `DateType()` representation. +Default is `yyyy-MM-dd`. + + +* **Constraints:** + - **minLength** = 1 + +#### *field* timestampFormat *: str | None* *= None* + +String format for TimestampType()\` representation. +Default is `yyyy-MM-dd'T'HH:mm:ss[.SSS][XXX]`. + + +* **Constraints:** + - **minLength** = 1 + +#### *field* timestampNTZFormat *: str | None* *= None* + +String format for TimestampNTZType()\` representation. +Default is `yyyy-MM-dd'T'HH:mm:ss[.SSS]`. + +#### NOTE +Added in Spark 3.2.0 + + +* **Constraints:** + - **minLength** = 1 + +#### *field* timezone *: str | None* *= None* *(alias 'timeZone')* + +Allows to override timezone used for parsing or serializing date and timestamp values. +By default, `spark.sql.session.timeZone` is used. + + +* **Constraints:** + - **minLength** = 1 + +#### *field* locale *: str | None* *= None* + +Locale name used to parse dates and timestamps. +Default is `en-US`. + +#### NOTE +Used only for reading files. + + +* **Constraints:** + - **minLength** = 1 diff --git a/mddocs/file_df/file_formats/orc.md b/mddocs/file_df/file_formats/orc.md new file mode 100644 index 000000000..d922a7e47 --- /dev/null +++ b/mddocs/file_df/file_formats/orc.md @@ -0,0 +1,80 @@ + + +# ORC + +### *class* onetl.file.format.orc.ORC(\*, mergeSchema: bool | None = None, compression: str | Literal['uncompressed', 'snappy', 'zlib', 'lzo', 'zstd', 'lz4'] | None = None, \*\*kwargs) + +ORC file format (columnar). [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on [Spark ORC Files](https://spark.apache.org/docs/latest/sql-data-sources-orc.html) file format. + +Supports reading/writing files with `.orc` extension. + +#### Versionadded +Added in version 0.9.0. + +### Examples + +#### NOTE +You can pass any option mentioned in +[official documentation](https://spark.apache.org/docs/latest/sql-data-sources-orc.html). +**Option names should be in** `camelCase`! + +The set of supported options depends on Spark version. + +You may also set options mentioned [orc-java documentation](https://orc.apache.org/docs/core-java-config.html). +They are prefixed with `orc.` with dots in names, so instead of calling constructor `ORC(orc.option=True)` (invalid in Python) +you should call method `ORC.parse({"orc.option": True})`. + +Reading files + +```py +from onetl.file.format import ORC + +orc = ORC(mergeSchema=True) +``` + +Writing files + +```python +from onetl.file.format import ORC + +orc = ORC.parse( + { + "compression": "snappy", + # Enable Bloom filter for columns 'id' and 'name' + "orc.bloom.filter.columns": "id,name", + # Set Bloom filter false positive probability + "orc.bloom.filter.fpp": 0.01, + # Do not use dictionary for 'highly_selective_column' + "orc.column.encoding.direct": "highly_selective_column", + # other options + } +) +``` + + + +#### *field* mergeSchema *: bool | None* *= None* + +Merge schemas of all ORC files being read into a single schema. +By default, Spark config option `spark.sql.orc.mergeSchema` value is used (`False`). + +#### NOTE +Used only for reading files. + + + +#### *field* compression *: str | Literal['uncompressed', 'snappy', 'zlib', 'lzo', 'zstd', 'lz4'] | None* *= None* + +Compression codec of the ORC files. +By default, Spark config option `spark.sql.orc.compression.codec` value is used (`snappy`). + +#### NOTE +Used only for writing files. + + + +#### \_\_init_\_(\*\*kwargs) + + diff --git a/mddocs/file_df/file_formats/parquet.md b/mddocs/file_df/file_formats/parquet.md new file mode 100644 index 000000000..6ff8aa6e0 --- /dev/null +++ b/mddocs/file_df/file_formats/parquet.md @@ -0,0 +1,79 @@ + + +# Parquet + +### *class* onetl.file.format.parquet.Parquet(\*, mergeSchema: bool | None = None, compression: str | Literal['uncompressed', 'snappy', 'gzip', 'lzo', 'brotli', 'lz4', 'lz4raw', 'zstd'] | None = None, \*\*kwargs) + +Parquet file format (columnar). [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on [Spark Parquet Files](https://spark.apache.org/docs/latest/sql-data-sources-parquet.html) file format. + +Supports reading/writing files with `.parquet` extension. + +#### Versionadded +Added in version 0.9.0. + +### Examples + +#### NOTE +You can pass any option mentioned in +[official documentation](https://spark.apache.org/docs/latest/sql-data-sources-parquet.html). +**Option names should be in** `camelCase`! + +The set of supported options depends on Spark version. + +You may also set options mentioned [parquet-hadoop documentation](https://github.com/apache/parquet-java/blob/master/parquet-hadoop/README.md). +They are prefixed with `parquet.` with dots in names, so instead of calling constructor `Parquet(parquet.option=True)` (invalid in Python) +you should call method `Parquet.parse({"parquet.option": True})`. + +Reading files + +```py +from onetl.file.format import Parquet + +parquet = Parquet(mergeSchema=True) +``` + +Writing files + +```py +from onetl.file.format import Parquet + +parquet = Parquet.parse( + { + "compression": "snappy", + # Enable Bloom filter for columns 'id' and 'name' + "parquet.bloom.filter.enabled#id": True, + "parquet.bloom.filter.enabled#name": True, + # Set expected number of distinct values for column 'id' + "parquet.bloom.filter.expected.ndv#id": 10_000_000, + # other options + } +) +``` + + + +#### *field* mergeSchema *: bool | None* *= None* + +Merge schemas of all Parquet files being read into a single schema. +By default, Spark config option `spark.sql.parquet.mergeSchema` value is used (`false`). + +#### NOTE +Used only for reading files. + + + +#### *field* compression *: str | Literal['uncompressed', 'snappy', 'gzip', 'lzo', 'brotli', 'lz4', 'lz4raw', 'zstd'] | None* *= None* + +Compression codec of the Parquet files. +By default, Spark config option `spark.sql.parquet.compression.codec` value is used (`snappy`). + +#### NOTE +Used only for writing files. + + + +#### \_\_init_\_(\*\*kwargs) + + diff --git a/mddocs/file_df/file_formats/xml.md b/mddocs/file_df/file_formats/xml.md new file mode 100644 index 000000000..1da916bc1 --- /dev/null +++ b/mddocs/file_df/file_formats/xml.md @@ -0,0 +1,440 @@ + + +# XML + +### *class* onetl.file.format.xml.XML(\*, rowTag: str, rootTag: str | None = None, compression: str | Literal['bzip2', 'gzip', 'lz4', 'snappy'] | None = None, timestampFormat: str | None = None, dateFormat: str | None = None, timezone: str | None = None, nullValue: str | None = None, ignoreSurroundingSpaces: bool | None = None, mode: Literal['PERMISSIVE', 'DROPMALFORMED', 'FAILFAST'] | None = None, columnNameOfCorruptRecord: str | None = None, inferSchema: bool | None = None, samplingRatio: ConstrainedFloatValue | None = None, charset: str | None = None, valueTag: str | None = None, attributePrefix: str | None = None, excludeAttribute: bool | None = None, wildcardColName: str | None = None, ignoreNamespace: bool | None = None, rowValidationXSDPath: str | None = None, declaration: str | None = None, arrayElementName: str | None = None, \*\*kwargs) + +XML file format. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +Based on [Databricks Spark XML](https://github.com/databricks/spark-xml) file format. + +Supports reading/writing files with `.xml` extension. + +#### Versionadded +Added in version 0.9.5. + +### Version compatibility + +* Spark versions: 3.2.x - 3.5.x +* Java versions: 8 - 20 + +See [official documentation](https://github.com/databricks/spark-xml). + +### Examples + +#### NOTE +You can pass any option mentioned in +[official documentation](https://github.com/databricks/spark-xml). +**Option names should be in** `camelCase`! + +The set of supported options depends on `spark-xml` version. + +Reading files + +```py +from onetl.file.format import XML +from pyspark.sql import SparkSession + +# Create Spark session with XML package loaded +maven_packages = XML.get_packages(spark_version="3.5.5") +spark = ( + SparkSession.builder.appName("spark-app-name") + .config("spark.jars.packages", ",".join(maven_packages)) + .getOrCreate() +) + +xml = XML(rowTag="item", mode="PERMISSIVE") +``` + +Writing files + +#### WARNING +Due to [bug](https://github.com/databricks/spark-xml/issues/664) written files currently does not have `.xml` extension. + +```python +# Create Spark session with XML package loaded +spark = ... + +from onetl.file.format import XML + +xml = XML(rowTag="item", rootTag="data", compression="gzip") +``` + + + +#### *field* row_tag *: str* *[Required]* *(alias 'rowTag')* + +XML tag that encloses each row in XML. Required. + + + +#### *field* rootTag *: str | None* *= None* + +XML tag that encloses content of all DataFrame. Default is `ROWS`. + +#### NOTE +Used only for writing files. + + + +#### *field* compression *: str | Literal['bzip2', 'gzip', 'lz4', 'snappy'] | None* *= None* + +Compression codec. By default no compression is used. + +#### NOTE +Used only for writing files. + + + +#### *field* timestampFormat *: str | None* *= None* + +Format string used for parsing or serializing timestamp values. +By default, ISO 8601 format is used (`yyyy-MM-ddTHH:mm:ss.SSSZ`). + + + +#### *field* dateFormat *: str | None* *= None* + +Format string used for parsing or serializing date values. +By default, ISO 8601 format is used (`yyyy-MM-dd`). + + + +#### *field* nullValue *: str | None* *= None* + +String value used to represent null. Default is `null` string. + + + +#### *field* ignoreSurroundingSpaces *: bool | None* *= None* + +If `True`, trim surrounding spaces while parsing values. Default `false`. + +#### NOTE +Used only for reading files or by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. + + + +#### *field* mode *: Literal['PERMISSIVE', 'DROPMALFORMED', 'FAILFAST'] | None* *= None* + +How to handle parsing errors: +: * `PERMISSIVE` - set field value as `null`, move raw data to [`columnNameOfCorruptRecord`](#onetl.file.format.xml.XML.columnNameOfCorruptRecord) column. + * `DROPMALFORMED` - skip the malformed row. + * `FAILFAST` - throw an error immediately. + +Default is `PERMISSIVE`. + +#### NOTE +Used only for reading files or by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. + + + +#### *field* columnNameOfCorruptRecord *: str | None* *= None* + +Name of DataFrame column there corrupted row is stored with `mode=PERMISSIVE`. + +#### WARNING +If DataFrame schema is provided, this column should be added to schema explicitly: + +```python +from onetl.connection import SparkLocalFS +from onetl.file import FileDFReader +from onetl.file.format import XML + +from pyspark.sql.types import StructType, StructField, TImestampType, StringType + +spark = ... +schema = StructType( + [ + StructField("my_field", TimestampType()), + StructField("_corrupt_record", StringType()), # <-- important + ] +) +xml = XML(rowTag="item", columnNameOfCorruptRecord="_corrupt_record") + +reader = FileDFReader( + connection=connection, + format=xml, + schema=schema, # < --- +) +df = reader.run(["/some/file.xml"]) +``` + +#### NOTE +Used only for reading files or by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. + + + +#### *field* inferSchema *: bool | None* *= None* + +If `True`, try to infer the input schema by reading a sample of the file (see [`samplingRatio`](#onetl.file.format.xml.XML.samplingRatio)). +Default `False` which means that all parsed columns will be `StringType()`. + +#### NOTE +Used only for reading files. Ignored by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. + + + +#### *field* samplingRatio *: float | None* *= None* + +For `inferSchema=True`, read the specified fraction of rows to infer the schema. +Default `1`. + +#### NOTE +Used only for reading files. Ignored by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. + + +* **Constraints:** + - **minimum** = 0 + - **maximum** = 1 + +#### *field* charset *: str | None* *= None* + +File encoding. Default is `UTF-8` + +#### NOTE +Used only for reading files or by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. + + + +#### *field* valueTag *: str | None* *= None* + +Value used to replace missing values while parsing attributes like ``. +Default `_VALUE`. + +#### NOTE +Used only for reading files or by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. + + + +#### *field* attributePrefix *: str | None* *= None* + +While parsing tags containing attributes like ``, attributes are stored as +DataFrame schema columns with specified prefix, e.g. `_attr`. +Default `_`. + +#### NOTE +Used only for reading files or by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. + + + +#### *field* excludeAttribute *: bool | None* *= None* + +If `True`, exclude attributes while parsing tags like ``. +Default `false`. + +#### NOTE +Used only for reading files or by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. + + + +#### *field* wildcardColName *: str | None* *= None* + +Name of column or columns which should be preserved as raw XML string, and not parsed. + +#### WARNING +If DataFrame schema is provided, this column should be added to schema explicitly. +See [`columnNameOfCorruptRecord`](#onetl.file.format.xml.XML.columnNameOfCorruptRecord) example. + +#### NOTE +Used only for reading files or by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. + + + +#### *field* ignoreNamespace *: bool | None* *= None* + +If `True`, all namespaces like `` will be ignored and treated as just ``. +Default `False`. + +#### NOTE +Used only for reading files or by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. + + + +#### *field* rowValidationXSDPath *: str | None* *= None* + +Path to XSD file which should be used to validate each row. +If row does not match XSD, it will be treated as error, behavior depends on [`mode`](#onetl.file.format.xml.XML.mode) value. + +Default is no validation. + +#### NOTE +If Spark session is created with `master=yarn` or `master=k8s`, XSD +file should be accessible from all Spark nodes. This can be achieved by calling: + +```python +spark.addFile("/path/to/file.xsd") +``` + +And then by passing `rowValidationXSDPath=file.xsd` (relative path). + +#### NOTE +Used only for reading files or by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. + + + +#### *field* declaration *: str | None* *= None* + +Content of declaration. +Default is `version="1.0" encoding="UTF-8" standalone="yes"`. + +#### NOTE +Used only for writing files. + + + +#### *field* arrayElementName *: str | None* *= None* + +If DataFrame column is `ArrayType`, its content will be written to XML +inside `...` tag. +Default is `item`. + +#### NOTE +Used only for writing files. + + + +#### *classmethod* get_packages(spark_version: str, scala_version: str | None = None, package_version: str | None = None) → list[str] + +Get package names to be downloaded by Spark. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +#### Versionadded +Added in version 0.9.5. + +* **Parameters:** + **spark_version** + : Spark version in format `major.minor.patch`. + + **scala_version** + : Scala version in format `major.minor`. +
+ If `None`, `spark_version` is used to determine Scala version. + + **package_version** + : Package version in format `major.minor.patch`. Default is `0.18.0`. +
+ See [Maven index](https://mvnrepository.com/artifact/com.databricks/spark-xml) + for list of available versions. +
+ #### WARNING + Version `0.13` and below are not supported. +
+ #### NOTE + It is not guaranteed that custom package versions are supported. + Tests are performed only for default version. + +### Examples + +```python +from onetl.file.format import XML + +XML.get_packages(spark_version="3.5.5") +XML.get_packages(spark_version="3.5.5", scala_version="2.12") +XML.get_packages( + spark_version="3.5.5", + scala_version="2.12", + package_version="0.18.0", +) +``` + + + +#### parse_column(column: str | Column, schema: StructType) → Column + +Parses an XML string column into a structured Spark SQL column using the `from_xml` function +provided by the [Databricks Spark XML library](https://github.com/databricks/spark-xml#pyspark-notes) +based on the provided schema. + +#### NOTE +This method assumes that the `spark-xml` package is installed: [`get_packages`](#onetl.file.format.xml.XML.get_packages). + +#### NOTE +This method parses each DataFrame row individually. Therefore, for a specific column, each row must contain exactly one occurrence of the `rowTag` specified. +If your XML data includes a root tag that encapsulates multiple row tags, you can adjust the schema to use an `ArrayType` to keep all child elements under the single root. + +```xml + + Book OneAuthor A + Book TwoAuthor B + +``` + +And the corresponding schema in Spark using an `ArrayType`: + +```python +from pyspark.sql.types import StructType, StructField, ArrayType, StringType +from onetl.file.format import XML + +# each DataFrame row has exactly one tag +xml = XML(rowTag="books") +# each tag have multiple tags, so using ArrayType for such field +schema = StructType( + [ + StructField( + "book", + ArrayType( + StructType( + [ + StructField("title", StringType(), nullable=True), + StructField("author", StringType(), nullable=True), + ], + ), + ), + nullable=True, + ), + ], +) +``` + +#### Versionadded +Added in version 0.11.0. + +* **Parameters:** + **column** + : The name of the column or the column object containing XML strings/bytes to parse. + + **schema** + : The schema to apply when parsing the XML data. This defines the structure of the output DataFrame column. +* **Returns:** + Column with deserialized data, with the same structure as the provided schema. Column name is the same as input column. + +### Examples + +```pycon +>>> from pyspark.sql.types import StructType, StructField, IntegerType, StringType +>>> from onetl.file.format import XML +>>> df.show() ++--+------------------------------------------------+ +|id|value | ++--+------------------------------------------------+ +|1 |Alice20| +|2 |Bob25 | ++--+------------------------------------------------+ +>>> df.printSchema() +root +|-- id: integer (nullable = true) +|-- value: string (nullable = true) +>>> xml = XML(rowTag="person") +>>> xml_schema = StructType( +... [ +... StructField("name", StringType(), nullable=True), +... StructField("age", IntegerType(), nullable=True), +... ], +... ) +>>> parsed_df = df.select("key", xml.parse_column("value", xml_schema)) +>>> parsed_df.show() ++--+-----------+ +|id|value | ++--+-----------+ +|1 |{Alice, 20}| +|2 | {Bob, 25}| ++--+-----------+ +>>> parsed_df.printSchema() +root +|-- id: integer (nullable = true) +|-- value: struct (nullable = true) +| |-- name: string (nullable = true) +| |-- age: integer (nullable = true) +``` + + diff --git a/mddocs/file_df/index.md b/mddocs/file_df/index.md new file mode 100644 index 000000000..c225567d6 --- /dev/null +++ b/mddocs/file_df/index.md @@ -0,0 +1,7 @@ + + + File DataFrame classes + +* [FileDF Reader](file_df_reader/index.md) +* [FileDF Writer](file_df_writer/index.md) +* [File Formats](file_formats/index.md) diff --git a/mddocs/hooks/design.md b/mddocs/hooks/design.md new file mode 100644 index 000000000..041f206ee --- /dev/null +++ b/mddocs/hooks/design.md @@ -0,0 +1,642 @@ + + +# High level design + +## What are hooks? + +Hook mechanism is a part of onETL which allows to inject some additional behavior into +existing methods of (almost) any class. + +### Features + +Hooks mechanism allows to: + +* Inspect and validate input arguments and output results of method call +* Access, modify or replace method call result (but NOT input arguments) +* Wrap method calls with a context manager and catch raised exceptions + +Hooks can be placed into [Plugins](../plugins.md#plugins), allowing to modify onETL behavior by installing some additional package. + +### Limitations + +* Hooks can be bound to methods of a class only (not functions). +* Only methods decorated with [@slot decorator](slot.md#slot-decorator) implement hooks mechanism. These class and methods are marked as [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html). +* Hooks can be bound to public methods only. + +## Terms + +* [@slot decorator](slot.md#slot-decorator) - method of a class with a special decorator +* `Callback` - function which implements some additional logic which modifies slot behavior +* [@hook decorator](hook.md#hook-decorator) - wrapper around callback which stores hook state, priority and some useful methods +* `Hooks mechanism` - calling `Slot()` will call all enabled hooks which are bound to the slot. Implemented by [@support_hooks decorator](support_hooks.md#support-hooks-decorator). + +## How to implement hooks? + +### TL;DR + +```python +from onetl.hooks import support_hooks, slot, hook + + +@support_hooks # enabling hook mechanism for the class +class MyClass: + def __init__(self, data): + self.data = data + + # this is slot + @slot + def method(self, arg): + pass + + +@MyClass.method.bind # bound hook to the slot +@hook # this is hook +def callback(obj, arg): # this is callback + print(obj.data, arg) + + +obj = MyClass(1) +obj.method(2) # will call callback(obj, 1) + +# prints "1 2" +``` + +#### Define a slot + +* Create a class with a method: + +```python +class MyClass: + def __init__(self, data): + self.data = data + + def method(self, arg): + return self.data, arg +``` + +* Add [@slot decorator](slot.md#slot-decorator) to the method: + +```python +from onetl.hooks import support_hooks, slot, hook + + +class MyClass: + @slot + def method(self, arg): + return self.data, arg +``` + +If method has other decorators like `@classmethod` or `@staticmethod`, `@slot` should be placed on the top: + +```python +from onetl.hooks import support_hooks, slot, hook + + +class MyClass: + @slot + @classmethod + def class_method(cls, arg): + return cls, arg + + @slot + @staticmethod + def static_method(arg): + return arg +``` + +* Add [@support_hooks decorator](support_hooks.md#support-hooks-decorator) to the class: + +```python +from onetl.hooks import support_hooks, slot, hook + + +@support_hooks +class MyClass: + @slot + def method(self, arg): + return self.data, arg +``` + +Slot is created. + +#### Define a callback + +Define some function (a.k.a callback): + +```python +def callback(self, arg): + print(self.data, arg) +``` + +It should have signature *compatible* with `MyClass.method`. *Compatible* does not mean *exactly the same* - +for example, you can rename positional arguments: + +```python +def callback(obj, arg): + print(obj.data, arg) +``` + +Use `*args` and `**kwargs` to omit arguments you don’t care about: + +```python +def callback(obj, *args, **kwargs): + print(obj.data, args, kwargs) +``` + +There is also an argument `method_name` which has a special meaning - +the method name which the callback is bound to is passed into this argument: + +```python +def callback(obj, *args, method_name: str, **kwargs): + print(obj.data, args, method_name, kwargs) +``` + +#### NOTE +`method_name` should always be a keyword argument, **NOT** positional. + +#### WARNING +If callback signature is not compatible with slot signature, an exception will be raised, +but **ONLY** while slot is called. + +#### Define a hook + +Add [@hook decorator](hook.md#hook-decorator) to create a hook from your callback: + +```python +@hook +def callback(obj, arg): + print(obj.data, arg) +``` + +You can pass more options to the `@hook` decorator, like state or priority. +See decorator documentation for more details. + +#### Bind hook to the slot + +Use `Slot.bind` method to bind hook to the slot: + +```python +@MyClass.method.bind +@hook +def callback(obj, arg): + print(obj, arg) +``` + +You can bind more than one hook to the same slot, and bind same hook to multiple slots: + +```python +@MyClass.method1.bind +@MyClass.method2.bind +@hook +def callback1(obj, arg): + "Will be called by both MyClass.method1 and MyClass.method2" + + +@MyClass.method1.bind +@hook +def callback2(obj, arg): + "Will be called by MyClass.method1 too" +``` + +## How hooks are called? + +### General + +Just call the method decorated by `@slot` to trigger the hook: + +```python +obj = MyClass(1) +obj.method(2) # will call callback(obj, 2) + +# prints "1 2" +``` + +There are some special callback types that has a slightly different behavior. + +### Context managers + +`@hook` decorator can be placed on a context manager class: + +```python +@hook +class ContextManager: + def __init__(self, obj, arg): + self.obj = obj + self.arg = arg + + def __enter__(self): + # do something on enter + print(obj.data, arg) + return self + + def __exit__(self, exc_type, exc_value, traceback): + # do something on exit + return False +``` + +Context manager is entered while calling the `Slot()`, and exited then the call is finished. + +If present, method `process_result` has a special meaning - +it can receive `MyClass.method` call result, and also modify/replace it: + +```python +@hook +class ContextManager: + def __init__(self, obj, arg): + self.obj = obj + self.arg = arg + + def __enter__(self): + # do something on enter + print(obj.data, arg) + return self + + def __exit__(self, exc_type, exc_value, traceback): + # do something on exit + return False + + def process_result(self, result): + # do something with method call result + return modified(result) +``` + +See examples below for more information. + +### Generator function + +`@hook` decorator can be placed on a generator function: + +```python +@hook +def callback(obj, arg): + print(obj.data, arg) + # this is called before original method body + + yield # method is called here + + # this is called after original method body +``` + +It is converted to a context manager, in the same manner as +[contextlib.contextmanager](https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager). + +Generator body can be wrapped with `try..except..finally` to catch exceptions: + +```python +@hook +def callback(obj, arg): + print(obj.data, arg) + + try: + # this is called before original method body + + yield # method is called here + except Exception as e: + process_exception(a) + finally: + # this is called after original method body + finalizer() +``` + +There is also a special syntax which allows generator to access and modify/replace method call result: + +```default +@hook +def callback(obj, arg): + original_result = yield # method is called here + + new_result = do_something(original_result) + + yield new_result # modify/replace the result +``` + +### Calling hooks in details + +* The callback will be called with the same arguments as the original method. + * If slot is a regular method: + ```python + callback_result = callback(self, *args, **kwargs) + ``` + + Here `self` is a class instance (`obj`). + * If slot is a class method: + ```python + callback_result = callback(cls, *args, **kwargs) + ``` + + Here `cls` is the class itself (`MyClass`). + * If slot is a static method: + ```python + callback_result = callback(*args, **kwargs) + ``` + + Neither object not class are passed to the callback in this case. +* If `callback_result` is a context manager, enter the context. Context manager can catch all the exceptions raised. + > If there are multiple hooks bound the the slot, every context manager will be entered. +* Then call the original method wrapped by `@slot`: + ```python + original_result = method(*args, **kwargs) + ``` +* Process `original_result`: + * If `callback_result` object has method `process_result`, or is a generator wrapped with `@hook`, call it: + ```python + new_result = callback_result.process_result(original_result) + ``` + * Otherwise set `new_result = callback_result`. + * If there are multiple hooks bound the the method, pass `new_result` through the chain: + ```python + new_result = callback1_result.process_result(original_result) + new_result = callback2_result.process_result(new_result or original_result) + new_result = callback3_result.process_result(new_result or original_result) + ``` +* Finally return: + ```python + return new_result or original_result + ``` + + All `None` values are ignored on every step above. +* Exit all the context managers entered during the slot call. + +### Hooks priority + +Hooks are executed in the following order: + +1. Parent class slot + [`FIRST`](hook.md#onetl.hooks.hook.HookPriority.FIRST) +2. Inherited class slot + [`FIRST`](hook.md#onetl.hooks.hook.HookPriority.FIRST) +3. Parent class slot + [`NORMAL`](hook.md#onetl.hooks.hook.HookPriority.NORMAL) +4. Inherited class slot + [`NORMAL`](hook.md#onetl.hooks.hook.HookPriority.NORMAL) +5. Parent class slot + [`LAST`](hook.md#onetl.hooks.hook.HookPriority.LAST) +6. Inherited class slot + [`LAST`](hook.md#onetl.hooks.hook.HookPriority.LAST) + +Hooks with the same priority and inheritance will be executed in the same order they were registered (`Slot.bind` call). + +#### NOTE +Calls of `super()` inside inherited class methods does not trigger hooks call. +Hooks are triggered only if method is called explicitly. + +This allow to wrap with a hook the entire slot call without influencing its internal logic. + +### Hook types + +Here are several examples of using hooks. These types are not exceptional, they can be mixed - for example, +hook can both modify method result and catch exceptions. + +#### Before hook + +Can be used for inspecting or validating input args of the original function: + +```python +@hook +def before1(obj, arg): + print(obj, arg) + # original method is called after exiting this function + + +@hook +def before2(obj, arg): + if arg == 1: + raise ValueError("arg=1 is not allowed") + return None # return None is the same as no return statement +``` + +Executed before calling the original method wrapped by `@slot`. +If hook raises an exception, method will not be called at all. + +#### After hook + +Can be used for performing some actions after original method was successfully executed: + +```python +@hook +def after1(obj, arg): + yield # original method is called here + print(obj, arg) + + +@hook +def after2(obj, arg): + yield None # yielding None is the same as empty yield + if arg == 1: + raise ValueError("arg=1 is not allowed") +``` + +If original method raises an exception, the block of code after `yield` will not be called. + +#### Context hook + +Can be used for catching and handling some exceptions, or to determine that there was no exception during slot call: + +Generator syntax + +```py +# This is just the same as using @contextlib.contextmanager + +@hook +def context_generator(obj, arg): + try: + yield # original method is called here + print(obj, arg) # <-- this line will not be called if method raised an exception + except SomeException as e: + magic(e) + finally: + finalizer() +``` + +Context manager syntax + +```py +@hook +class ContextManager: + def __init__(self, obj, args): + self.obj = obj + self.args = args + + def __enter__(self): + return self + + # original method is called between __enter__ and __exit__ + + def __exit__(self, exc_type, exc_value, traceback): + result = False + if exc_type is not None and isinstance(exc_value, SomeException): + magic(exc_value) + result = True # suppress exception + else: + print(self.obj, self.arg) + finalizer() + return result +``` + +#### NOTE +Contexts are exited in the reverse order of the hook calls. +So if some hook raised an exception, it will be passed into the previous hook, not the next one. + +It is recommended to specify the proper priority for the hook, e.g. [`FIRST`](hook.md#onetl.hooks.hook.HookPriority.FIRST) + +#### Replacing result hook + +Replaces the output result of the original method. + +Can be used for delegating some implementation details for third-party extensions. +See [Hive](../connection/db_connection/hive/index.md#hive) and [HDFS](../connection/file_connection/hdfs/index.md#hdfs) as an example. + +```python +@hook +def replace1(obj, arg): + result = arg + 10 # any non-None return result + + # original method call result is ignored, output will always be arg + 10 + return result + + +@hook +def replace2(obj, arg): + yield arg + 10 # same as above +``` + +#### NOTE +If there are multiple hooks bound to the same slot, the result of last hook will be used. +It is recommended to specify the proper priority for the hook, e.g. [`LAST`](hook.md#onetl.hooks.hook.HookPriority.LAST) + +#### Accessing result hook + +Can access output result of the original method and inspect or validate it: + +Generator syntax + +```py +@hook +def access_result(obj, arg): + result = yield # original method is called here, and result can be used in the hook + print(result) + yield # does not modify result +``` + +Context manager syntax + +```py +@hook +class ModifiesResult: + def __init__(self, obj, args): + self.obj = obj + self.args = args + + def __enter__(self): + return self + + # original method is called between __enter__ and __exit__ + # result is passed into process_result method of context manager, if present + + def process_result(self, result): + print(result) # result can be used in the hook + return None # does not modify result. same as no return statement in the method + + def __exit__(self, exc_type, exc_value, traceback): + return False +``` + +#### Modifying result hook + +Can access output result of the original method, and return the modified one: + +Generator syntax + +```py +@hook +def modifies_result(obj, arg): + result = yield # original method is called here, and result can be used in the hook + yield result + 10 # modify output result. None values are ignored +``` + +Context manager syntax + +```py +@hook +class ModifiesResult: + def __init__(self, obj, args): + self.obj = obj + self.args = args + + def __enter__(self): + return self + + # original method is called between __enter__ and __exit__ + # result is passed into process_result method of context manager, if present + + def process_result(self, result): + print(result) # result can be used in the hook + return result + 10 # modify output result. None values are ignored + + def __exit__(self, exc_type, exc_value, traceback): + return False +``` + +#### NOTE +If there are multiple hooks bound to the same slot, the result of last hook will be used. +It is recommended to specify the proper priority for the hook, e.g. [`LAST`](hook.md#onetl.hooks.hook.HookPriority.LAST) + +## How to enable/disable hooks? + +You can enable/disable/temporary disable hooks on 4 different levels: + +* Manage global hooks state (level 1): + > * [`onetl.hooks.hooks_state.stop_all_hooks`](global_state.md#onetl.hooks.hooks_state.stop_all_hooks) + > * [`onetl.hooks.hooks_state.resume_all_hooks`](global_state.md#onetl.hooks.hooks_state.resume_all_hooks) + > * [`onetl.hooks.hooks_state.skip_all_hooks`](global_state.md#onetl.hooks.hooks_state.skip_all_hooks) +* Manage all hooks bound to a specific class (level 2): + > * [`onetl.hooks.support_hooks.suspend_hooks`](support_hooks.md#onetl.hooks.support_hooks.suspend_hooks) + > * [`onetl.hooks.support_hooks.resume_hooks`](support_hooks.md#onetl.hooks.support_hooks.resume_hooks) + > * [`onetl.hooks.support_hooks.skip_hooks`](support_hooks.md#onetl.hooks.support_hooks.skip_hooks) +* Manage all hooks bound to a specific slot (level 3): + > * [`onetl.hooks.slot.Slot.suspend_hooks`](slot.md#onetl.hooks.slot.Slot.suspend_hooks) + > * [`onetl.hooks.slot.Slot.resume_hooks`](slot.md#onetl.hooks.slot.Slot.resume_hooks) + > * [`onetl.hooks.slot.Slot.skip_hooks`](slot.md#onetl.hooks.slot.Slot.skip_hooks) +* Manage state of a specific hook (level 4): + > * [`onetl.hooks.hook.Hook.enable`](hook.md#onetl.hooks.hook.Hook.enable) + > * [`onetl.hooks.hook.Hook.disable`](hook.md#onetl.hooks.hook.Hook.disable) + +More details in the documentation above. + +#### NOTE +All of these levels are independent. + +Calling `stop` on the level 1 has higher priority than level 2, and so on. +But calling `resume` on the level 1 does not automatically resume hooks stopped in the level 2, +they should be resumed explicitly. + +## How to see logs of the hook mechanism? + +Hooks registration emits logs with `DEBUG` level: + +```python +from onetl.logs import setup_logging + +setup_logging() +``` + +```text +DEBUG |onETL| Registered hook 'mymodule.callback1' for 'MyClass.method' (enabled=True, priority=HookPriority.NORMAL) +DEBUG |onETL| Registered hook 'mymodule.callback2' for 'MyClass.method' (enabled=True, priority=HookPriority.NORMAL) +DEBUG |onETL| Registered hook 'mymodule.callback3' for 'MyClass.method' (enabled=False, priority=HookPriority.NORMAL) +``` + +But most of logs are emitted with even lower level `NOTICE`, to make output less verbose: + +```python +from onetl.logs import NOTICE, setup_logging + +setup_logging(level=NOTICE) +``` + +```default +NOTICE |Hooks| 2 hooks registered for 'MyClass.method' +NOTICE |Hooks| Calling hook 'mymodule.callback1' (1/2) +NOTICE |Hooks| Hook is finished with returning non-None result +NOTICE |Hooks| Calling hook 'mymodule.callback2' (2/2) +NOTICE |Hooks| This is a context manager, entering ... +NOTICE |Hooks| Calling original method 'MyClass.method' +NOTICE |Hooks| Method call is finished +NOTICE |Hooks| Method call result (*NOT* None) will be replaced with result of hook 'mymodule.callback1' +NOTICE |Hooks| Passing result to 'process_result' method of context manager 'mymodule.callback2' +NOTICE |Hooks| Method call result (*NOT* None) is modified by hook! +``` diff --git a/mddocs/hooks/global_state.md b/mddocs/hooks/global_state.md new file mode 100644 index 000000000..537389dd1 --- /dev/null +++ b/mddocs/hooks/global_state.md @@ -0,0 +1,101 @@ + + +# Hooks global state + +| [`skip_all_hooks`](#onetl.hooks.hooks_state.skip_all_hooks)() | Temporary stop all onETL hooks. | +|-------------------------------------------------------------------|-----------------------------------| +| [`stop_all_hooks`](#onetl.hooks.hooks_state.stop_all_hooks)() | Stop all hooks for all classes. | +| [`resume_all_hooks`](#onetl.hooks.hooks_state.resume_all_hooks)() | Resume all onETL hooks. | + +### onetl.hooks.hooks_state.skip_all_hooks() + +Temporary stop all onETL hooks. Designed to be used as context manager or decorator. + +#### NOTE +If hooks were stopped by [`stop_all_hooks`](#onetl.hooks.hooks_state.stop_all_hooks), they will not be resumed +after exiting the context/decorated function. +You should call [`resume_all_hooks`](#onetl.hooks.hooks_state.resume_all_hooks) explicitly. + +#### Versionadded +Added in version 0.7.0. + +### Examples + +Context manager syntax + +```py +from onetl.hooks import skip_all_hooks + +# hooks are enabled + +with skip_all_hooks(): + # hooks are stopped here + ... + +# hook state is restored +``` + +Decorator syntax + +```py +from onetl.hooks import skip_all_hooks + +# hooks are enabled + +@skip_all_hooks() +def main(): + # hooks are stopped here + ... + +main() +``` + + + +### onetl.hooks.hooks_state.stop_all_hooks() → None + +Stop all hooks for all classes. + +#### Versionadded +Added in version 0.7.0. + +### Examples + +```python +from onetl.hooks import stop_all_hooks + +# hooks are executed + +stop_all_hooks() + +# all hooks are stopped now +``` + + + +### onetl.hooks.hooks_state.resume_all_hooks() → None + +Resume all onETL hooks. + +#### NOTE +This function does not enable hooks which were disabled by [`onetl.hooks.hook.Hook.disable`](hook.md#onetl.hooks.hook.Hook.disable), +or stopped by [`onetl.hooks.support_hooks.suspend_hooks`](support_hooks.md#onetl.hooks.support_hooks.suspend_hooks). + +#### Versionadded +Added in version 0.7.0. + +### Examples + +```python +from onetl.hooks import resume_all_hooks, stop_all_hooks + +stop_all_hooks() + +# hooks are stopped + +resume_all_hooks() + +# all hooks are executed now +``` + + diff --git a/mddocs/hooks/hook.md b/mddocs/hooks/hook.md new file mode 100644 index 000000000..402f79688 --- /dev/null +++ b/mddocs/hooks/hook.md @@ -0,0 +1,223 @@ + + +# `@hook` decorator + +| [`hook`](#onetl.hooks.hook.hook)([inp, enabled, priority]) | Initialize hook from callable/context manager. | +|-------------------------------------------------------------------------------|--------------------------------------------------| +| [`HookPriority`](#onetl.hooks.hook.HookPriority)(value[, names, module, ...]) | Hook priority enum. | +| [`Hook`](#onetl.hooks.hook.Hook)(callback[, enabled, priority]) | Hook representation. | +| [`Hook.enable`](#onetl.hooks.hook.Hook.enable)() | Enable the hook. | +| [`Hook.disable`](#onetl.hooks.hook.Hook.disable)() | Disable the hook. | +| [`Hook.skip`](#onetl.hooks.hook.Hook.skip)() | Temporary disable the hook. | + +### @onetl.hooks.hook.hook(inp: Callable[[...], T] | None = None, enabled: bool = True, priority: [HookPriority](#onetl.hooks.hook.HookPriority) = HookPriority.NORMAL) + +Initialize hook from callable/context manager. + +#### Versionadded +Added in version 0.7.0. + +### Examples + +Decorate a function or generator + +```py +from onetl.hooks import hook, HookPriority + +@hook +def some_func(*args, **kwargs): + ... + +@hook(enabled=True, priority=HookPriority.FIRST) +def another_func(*args, **kwargs): + ... +``` + +Decorate a context manager + +```py +from onetl.hooks import hook, HookPriority + +@hook +class SimpleContextManager: + def __init__(self, *args, **kwargs): + ... + + def __enter__(self): + ... + return self + + def __exit__(self, exc_type, exc_value, traceback): + ... + return False + +@hook(enabled=True, priority=HookPriority.FIRST) +class ContextManagerWithProcessResult: + def __init__(self, *args, **kwargs): + ... + + def __enter__(self): + ... + return self + + def __exit__(self, exc_type, exc_value, traceback): + ... + return False + + def process_result(self, result): + # special method to handle method result call + return modify(result) + + ... +``` + + + +### *class* onetl.hooks.hook.HookPriority(value, names=None, \*, module=None, qualname=None, type=None, start=1, boundary=None) + +Hook priority enum. + +All hooks within the same priority are executed in the same order they were registered. + +#### Versionadded +Added in version 0.7.0. + + + +#### FIRST *= -1* + +Hooks with this priority will run first. + + + +#### NORMAL *= 0* + +Hooks with this priority will run after [`FIRST`](#onetl.hooks.hook.HookPriority.FIRST) but before [`LAST`](#onetl.hooks.hook.HookPriority.LAST). + + + +#### LAST *= 1* + +Hooks with this priority will run last. + + + +### *class* onetl.hooks.hook.Hook(callback: Callable[[...], T], enabled: bool = True, priority: [HookPriority](#onetl.hooks.hook.HookPriority) = HookPriority.NORMAL) + +Hook representation. + +#### Versionadded +Added in version 0.7.0. + +* **Parameters:** + **callback** + : Some callable object which will be wrapped into a Hook, like function or ContextManager class. + + **enabled** + : Will hook be executed or not. Useful for debugging. + + **priority** + : Changes hooks priority, see `HookPriority` documentation. + +### Examples + +```python +from onetl.hooks.hook import Hook, HookPriority + +def some_func(*args, **kwargs): ... + +hook = Hook(callback=some_func, enabled=True, priority=HookPriority.FIRST) +``` + + + +#### enable() + +Enable the hook. + +#### Versionadded +Added in version 0.7.0. + +### Examples + +```pycon +>>> def func1(): ... +>>> hook = Hook(callback=func1, enabled=False) +>>> hook.enabled +False +>>> hook.enable() +>>> hook.enabled +True +``` + + + +#### disable() + +Disable the hook. + +#### Versionadded +Added in version 0.7.0. + +### Examples + +```pycon +>>> def func1(): ... +>>> hook = Hook(callback=func1, enabled=True) +>>> hook.enabled +True +>>> hook.disable() +>>> hook.enabled +False +``` + + + +#### skip() + +Temporary disable the hook. + +#### NOTE +If hook was created with `enabled=False`, or was disabled by [`disable`](#onetl.hooks.hook.Hook.disable), +its state will left intact after exiting the context. + +You should call [`enable`](#onetl.hooks.hook.Hook.enable) explicitly to change its state. + +#### Versionadded +Added in version 0.7.0. + +### Examples + +Context manager syntax + +```pycon +>>> def func1(): ... +>>> hook = Hook(callback=func1, enabled=True) +>>> hook.enabled +True +>>> with hook.skip(): +... print(hook.enabled) +False +>>> # hook state is restored as it was before entering the context manager +>>> hook.enabled +True +``` + +Decorator syntax + +```pycon +>>> def func1(): ... +>>> hook = Hook(callback=func1, enabled=True) +>>> hook.enabled +True +>>> @hook.skip() +... def hook_disabled(): +... print(hook.enabled) +>>> hook_disabled() +False +>>> # hook state is restored as it was before entering the context manager +>>> hook.enabled +True +``` + + diff --git a/mddocs/hooks/index.md b/mddocs/hooks/index.md new file mode 100644 index 000000000..8e1ba7fb9 --- /dev/null +++ b/mddocs/hooks/index.md @@ -0,0 +1,14 @@ + + +# Hooks + +#### Versionadded +Added in version 0.6.0. + +# Hooks + +* [High level design](design.md) +* [@hook decorator](hook.md) +* [@slot decorator](slot.md) +* [@support_hooks decorator](support_hooks.md) +* [Hooks global state](global_state.md) diff --git a/mddocs/hooks/slot.md b/mddocs/hooks/slot.md new file mode 100644 index 000000000..936ee3859 --- /dev/null +++ b/mddocs/hooks/slot.md @@ -0,0 +1,261 @@ + + +# `@slot` decorator + +| [`slot`](#onetl.hooks.slot.slot)(method) | Decorator which enables hooks functionality on a specific class method. | +|----------------------------------------------------------------|------------------------------------------------------------------------------------------------------| +| [`Slot`](#onetl.hooks.slot.Slot) | Protocol which is implemented by a method after applying [`slot`](#onetl.hooks.slot.slot) decorator. | +| [`Slot.bind`](#onetl.hooks.slot.Slot.bind)([inp]) | Bind a hook to the slot. | +| [`Slot.skip_hooks`](#onetl.hooks.slot.Slot.skip_hooks)() | Context manager which temporary stops all the hooks bound to the slot. | +| [`Slot.suspend_hooks`](#onetl.hooks.slot.Slot.suspend_hooks)() | Stop all the hooks bound to the slot. | +| [`Slot.resume_hooks`](#onetl.hooks.slot.Slot.resume_hooks)() | Resume all hooks bound to the slot. | + +### @onetl.hooks.slot.slot + +Decorator which enables hooks functionality on a specific class method. + +Decorated methods get additional nested methods: + +> * [`onetl.hooks.slot.Slot.bind`](#onetl.hooks.slot.Slot.bind) +> * [`onetl.hooks.slot.Slot.suspend_hooks`](#onetl.hooks.slot.Slot.suspend_hooks) +> * [`onetl.hooks.slot.Slot.resume_hooks`](#onetl.hooks.slot.Slot.resume_hooks) +> * [`onetl.hooks.slot.Slot.skip_hooks`](#onetl.hooks.slot.Slot.skip_hooks) + +#### NOTE +Supported method types are: + +> * Regular methods +> * `@classmethod` +> * `@staticmethod` + +It is not allowed to use this decorator over `_private` and `__protected` methods and `@property`. +But is allowed to use on `__dunder__` methods, like `__init__`. + +#### Versionadded +Added in version 0.7.0. + +### Examples + +```python +from onetl.hooks import support_hooks, slot, hook + +@support_hooks +class MyClass: + @slot + def my_method(self, arg): ... + + @slot # decorator should be on top of all other decorators + @classmethod + def class_method(cls): ... + + @slot # decorator should be on top of all other decorators + @staticmethod + def static_method(arg): ... + +@MyClass.my_method.bind +@hook +def callback1(self, arg): ... + +@MyClass.class_method.bind +@hook +def callback2(cls): ... + +@MyClass.static_method.bind +@hook +def callback3(arg): ... + +obj = MyClass() +obj.my_method(1) # will execute callback1(obj, 1) +MyClass.class_method(2) # will execute callback2(MyClass, 2) +MyClass.static_method(3) # will execute callback3(3) +``` + + + +### *protocol* onetl.hooks.slot.Slot + +Protocol which is implemented by a method after applying [`slot`](#onetl.hooks.slot.slot) decorator. + +#### Versionadded +Added in version 0.7.0. + + + +Classes that implement this protocol must have the following methods / attributes: + +#### \_\_call_\_(\*args, \*\*kwargs) + +Call self as a function. + + + +#### *property* \_\_hooks_\_ *: HookCollection* + +Collection of hooks bound to the slot + + + +#### bind(inp=None) + +Bind a hook to the slot. + +See [High level design](design.md#hooks-design) for more details. + +#### Versionadded +Added in version 0.7.0. + +### Examples + +```python +from onetl.hooks import support_hooks, slot, hook, HookPriority + +@support_hooks +class MyClass: + @slot + def method(self, arg): + pass + +@MyClass.method.bind +@hook +def callable(self, arg): + if arg == "some": + do_something() + +@MyClass.method.bind +@hook(priority=HookPriority.FIRST, enabled=True) +def another_callable(self, arg): + if arg == "another": + raise NotAllowed() + +obj = MyClass() +obj.method(1) # will call both callable(obj, 1) and another_callable(obj, 1) +``` + + + +#### resume_hooks() + +Resume all hooks bound to the slot. + +#### NOTE +If hook is disabled by [`onetl.hooks.hook.Hook.disable`](hook.md#onetl.hooks.hook.Hook.disable), it will stay disabled. +You should call [`onetl.hooks.hook.Hook.enable`](hook.md#onetl.hooks.hook.Hook.enable) explicitly. + +### Examples + +```python +from onetl.hooks.hook import hook, support_hooks, slot + +@support_hooks +class MyClass: + @slot + def my_method(self, arg): ... + +@MyClass.my_method.bind +@hook +def callback1(self, arg): ... + +obj = MyClass() +obj.my_method(1) # will call callback1(obj, 1) + +MyClass.my_method.suspend_hooks() +obj.my_method(1) # will NOT call callback1 + +MyClass.my_method.resume_hooks() +obj.my_method(2) # will call callback1(obj, 2) +``` + + + +#### skip_hooks() → ContextManager[None] + +Context manager which temporary stops all the hooks bound to the slot. + +#### NOTE +If hooks were stopped by [`suspend_hooks`](#onetl.hooks.slot.Slot.suspend_hooks), they will not be resumed +after exiting the context/decorated function. +You should call [`resume_hooks`](#onetl.hooks.slot.Slot.resume_hooks) explicitly. + +### Examples + +Context manager syntax + +```py +from onetl.hooks.hook import hook, support_hooks, slot + +@support_hooks +class MyClass: + @slot + def my_method(self, arg): + ... + +@MyClass.my_method.bind +@hook +def callback1(self, arg): + ... + +obj = MyClass() +obj.my_method(1) # will call callback1(obj, 1) + +with MyClass.my_method.skip_hooks(): + obj.my_method(1) # will NOT call callback1 + +obj.my_method(2) # will call callback1(obj, 2) +``` + +Decorator syntax + +```py +from onetl.hooks.hook import hook, support_hooks, slot + +@support_hooks +class MyClass: + @slot + def my_method(self, arg): + ... + +@MyClass.my_method.bind +@hook +def callback1(self, arg): + ... + +@MyClass.my_method.skip_hooks() +def method_without_hooks(obj, arg): + obj.my_method(arg) + +obj = MyClass() +obj.my_method(1) # will call callback1(obj, 1) + +method_without_hooks(obj, 1) # will NOT call callback1 + +obj.my_method(2) # will call callback1(obj, 2) +``` + + + +#### suspend_hooks() + +Stop all the hooks bound to the slot. + +### Examples + +```python +from onetl.hooks.hook import hook, support_hooks, slot + +@support_hooks +class MyClass: + @slot + def my_method(self, arg): ... + +@MyClass.my_method.bind +@hook +def callback1(self, arg): ... + +obj = MyClass() +obj.my_method(1) # will call callback1(obj, 1) + +MyClass.my_method.suspend_hooks() +obj.my_method(1) # will NOT call callback1 +``` + + diff --git a/mddocs/hooks/support_hooks.md b/mddocs/hooks/support_hooks.md new file mode 100644 index 000000000..b876a3e52 --- /dev/null +++ b/mddocs/hooks/support_hooks.md @@ -0,0 +1,161 @@ + + +# `@support_hooks` decorator + +| [`support_hooks`](#onetl.hooks.support_hooks.support_hooks)(cls) | Decorator which adds hooks functionality to a specific class. | +|--------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------| +| [`skip_hooks`](#onetl.hooks.support_hooks.skip_hooks)(cls) | Context manager (and decorator) which temporary disables hooks for all the methods of a specific class. | +| [`suspend_hooks`](#onetl.hooks.support_hooks.suspend_hooks)(cls) | Disables all hooks for all the methods of a specific class. | +| [`resume_hooks`](#onetl.hooks.support_hooks.resume_hooks)(cls) | Enables all hooks for all the methods of a specific class. | + +### @onetl.hooks.support_hooks.support_hooks + +Decorator which adds hooks functionality to a specific class. + +Only methods decorated with `slot` can be used for connecting hooks. + +Adds [`skip_hooks`](#onetl.hooks.support_hooks.skip_hooks), [`suspend_hooks`](#onetl.hooks.support_hooks.suspend_hooks) and [`resume_hooks`](#onetl.hooks.support_hooks.resume_hooks) to the class. + +#### Versionadded +Added in version 0.7.0. + +### Examples + +```python +from onetl.hooks.hook import support_hooks, slot + +@support_hooks +class MyClass: + @slot + def my_method(self, arg): ... + +@MyClass.my_method.hook +def callback(self, arg): ... + +MyClass().my_method() # will execute callback function +``` + + + +### onetl.hooks.support_hooks.skip_hooks(cls: type) + +Context manager (and decorator) which temporary disables hooks for all the methods of a specific class. + +### Examples + +Context manager syntax + +```py +@support_hooks +class MyClass: + @slot + def my_method(self, arg): + ... + +@MyClass.my_method.hook +def callback(self, arg): + ... + +obj = MyClass() +obj.my_method(1) # will execute callback(obj, 1) + +with MyClass.skip_hooks(): + obj.my_method() # will NOT execute callback + +# running outside the context restores previous behavior +obj.my_method(2) # will execute callback(obj, 2) +``` + +Decorator syntax + +```py +@support_hooks +class MyClass: + @slot + def my_method(self, arg): + ... + +@MyClass.my_method.hook +def callback(self, arg): + ... + +def with_hook_enabled(): + obj = MyClass() + obj.my_method(1) + +with_hook_enabled() # will execute callback(obj, 1) + +@MyClass.skip_hooks() +def with_all_hooks_disabled(): + obj = MyClass() + obj.my_method(1) + +with_all_hooks_disabled() # will NOT execute callback function + +# running outside a decorated function restores previous behavior +obj = MyClass() +obj.my_method(2) # will execute callback(obj, 2) +``` + +#### Versionadded +Added in version 0.7.0. + + + +### onetl.hooks.support_hooks.suspend_hooks(cls: type) → None + +Disables all hooks for all the methods of a specific class. + +### Examples + +```python +@support_hooks +class MyClass: + @slot + def my_method(self, arg): ... + +@MyClass.my_method.hook +def callback(self, arg): ... + +obj = MyClass() +obj.my_method(1) # will execute callback(obj, 1) + +MyClass.suspend_hooks() + +obj.my_method(2) # will NOT execute callback +``` + +#### Versionadded +Added in version 0.7.0. + + + +### onetl.hooks.support_hooks.resume_hooks(cls: type) → None + +Enables all hooks for all the methods of a specific class. + +### Examples + +```python +@support_hooks +class MyClass: + @slot + def my_method(self, arg): ... + +@MyClass.my_method.hook +def callback(self, arg): ... + +obj = MyClass() + +MyClass.suspend_hooks() +obj.my_method(1) # will NOT execute callback + +MyClass.resume_hooks() + +obj.my_method(2) # will execute callback(obj, 2) +``` + +#### Versionadded +Added in version 0.7.0. + + diff --git a/mddocs/hwm_store/index.md b/mddocs/hwm_store/index.md new file mode 100644 index 000000000..c59cc0096 --- /dev/null +++ b/mddocs/hwm_store/index.md @@ -0,0 +1,9 @@ + + +# HWM + +Since onETL v0.10.0, the `HWMStore` and `HWM` classes have been moved to a separate library [etl-entities](https://etl-entities.readthedocs.io/en/stable/). + +The only class was left intact is [YAMLHWMStore](yaml_hwm_store.md#yaml-hwm-store), **which is default** in onETL. + +Other known implementation is [HorizonHWMStore](https://horizon-hwm-store.readthedocs.io/). diff --git a/mddocs/hwm_store/yaml_hwm_store.md b/mddocs/hwm_store/yaml_hwm_store.md new file mode 100644 index 000000000..425a7e030 --- /dev/null +++ b/mddocs/hwm_store/yaml_hwm_store.md @@ -0,0 +1,161 @@ + + +# YAMLHWMStore + +### *class* onetl.hwm.store.yaml_hwm_store.YAMLHWMStore(\*, path: LocalPath = LocalPosixPath('/home/sattar/.local/share/onETL/yml_hwm_store'), encoding: str = 'utf-8') + +YAML **local store** for HWM values. Used as default HWM store. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) + +* **Parameters:** + **path** + : Folder name there HWM value files will be stored. +
+ Default: + * `~/.local/share/onETL/yml_hwm_store` on Linux + * `C:\Documents and Settings\\Application Data\oneTools\onETL\yml_hwm_store` on Windows + * `~/Library/Application Support/onETL/yml_hwm_store` on MacOS + + **encoding** + : Encoding of files with HWM value + +### Examples + +Default parameters + +```python +from onetl.connection import Hive, Postgres +from onetl.db import DBReader, DBWriter +from onetl.strategy import IncrementalStrategy +from onetl.hwm.store import YAMLHWMStore + +postgres = Postgres(...) +hive = Hive(...) + +reader = DBReader( + connection=postgres, + source="public.mydata", + columns=["id", "data"], + hwm=DBReader.AutoDetectHWM(name="some_unique_name", expression="id"), +) + +writer = DBWriter(connection=hive, target="db.newtable") + +with YAMLHWMStore(): + with IncrementalStrategy(): + df = reader.run() + writer.run(df) + +# will create file +# "~/.local/share/onETL/id__public.mydata__postgres_postgres.domain.com_5432__myprocess__myhostname.yml" +# with encoding="utf-8" and save a serialized HWM values to this file +``` + +With all options + +```python +with YAMLHWMStore(path="/my/store", encoding="utf-8"): + with IncrementalStrategy(): + df = reader.run() + writer.run(df) + +# will create file +# "/my/store/id__public.mydata__postgres_postgres.domain.com_5432__myprocess__myhostname.yml" +# with encoding="utf-8" and save a serialized HWM values to this file +``` + +File content example: + +```yaml +- column: + name: id + partition: {} + modified_time: '2023-02-11T17:10:49.659019' + process: + dag: '' + host: myhostname + name: myprocess + task: '' + source: + db: public + instance: postgres://postgres.domain.com:5432/target_database + name: mydata + type: int + value: '1500' +- column: + name: id + partition: {} + modified_time: '2023-02-11T16:00:31.962150' + process: + dag: '' + host: myhostname + name: myprocess + task: '' + source: + db: public + instance: postgres://postgres.domain.com:5432/target_database + name: mydata + type: int + value: '1000' +``` + + + +#### \_\_enter_\_() + +HWM store context manager. + +Enter this context to use this HWM store instance as current one (instead default). + +### Examples + +```pycon +>>> from etl_entities.hwm_store import HWMStoreStackManager +>>> with SomeHWMStore(...) as hwm_store: +... print(HWMStoreStackManager.get_current()) +SomeHWMStore(...) +>>> HWMStoreStackManager.get_current() +DefaultHWMStore() +``` + + + +#### get_hwm(name: str) → HWM | None + +Get HWM by name from HWM store. + +* **Parameters:** + **name** + : HWM unique name +* **Returns:** + `HWM` + : HWM object, if it exists in HWM store, or None + +### Examples + +```python +hwm = hwm_store.get_hwm(hwm_unique_name) +``` + + + +#### set_hwm(hwm: HWM) → LocalPath + +Save HWM object to HWM Store. + +* **Parameters:** + **hwm** + : HWM object +* **Returns:** + Any + : HWM location, like URL of file path. Result type is implementation-specific. + +### Examples + +```python +from etl_entities.hwm import ColumnIntHWM + +hwm = ColumnIntHWM(name=..., value=...) +hwm_location = hwm_store.set_hwm(hwm) +``` + + diff --git a/mddocs/index.md b/mddocs/index.md new file mode 100644 index 000000000..ebfe9116e --- /dev/null +++ b/mddocs/index.md @@ -0,0 +1,9 @@ +```{include} README.md +:end-before: +``` + +![onETL logo](_static/logo_wide.svg) + +```{include} README.md +:start-after: +``` \ No newline at end of file diff --git a/mddocs/install/files.md b/mddocs/install/files.md new file mode 100644 index 000000000..60c747462 --- /dev/null +++ b/mddocs/install/files.md @@ -0,0 +1,20 @@ + + +# File connections + +All File (but not *FileDF*) connection classes (`FTP`, `SFTP`, `HDFS` and so on) requires specific Python clients to be installed. + +Each client can be installed explicitly by passing connector name (in lowercase) to `extras`: + +```bash +pip install onetl[ftp] # specific connector +pip install onetl[ftp,ftps,sftp,hdfs,s3,webdav,samba] # multiple connectors +``` + +To install all file connectors at once you can pass `files` to `extras`: + +```bash +pip install onetl[files] +``` + +**Otherwise class import will fail.** diff --git a/mddocs/install/full.md b/mddocs/install/full.md new file mode 100644 index 000000000..889f6c401 --- /dev/null +++ b/mddocs/install/full.md @@ -0,0 +1,15 @@ + + +# Full bundle + +To install all connectors and dependencies, you can pass `all` into `extras`: + +```bash +pip install onetl[all] + +# this is just the same as +pip install onetl[spark,files,kerberos] +``` + +#### WARNING +This method consumes a lot of disk space, and requires for Java & Kerberos libraries to be installed into your OS. diff --git a/mddocs/install/index.md b/mddocs/install/index.md new file mode 100644 index 000000000..1a5fb63cc --- /dev/null +++ b/mddocs/install/index.md @@ -0,0 +1,33 @@ + + +# How to install + +Base `onetl` package contains: + +* `DBReader`, `DBWriter` and related classes +* `FileDownloader`, `FileUploader`, `FileMover` and related classes, like file filters & limits +* `FileDFReader`, `FileDFWriter` and related classes, like file formats +* Read Strategies & HWM classes +* Plugins support + +It can be installed via: + +```bash +pip install onetl +``` + +#### WARNING +This method does NOT include any connections. + +This method is recommended for use in third-party libraries which require for `onetl` to be installed, +but do not use its connection classes. + +## Installation in details + +## How to install + +* [How to install]() +* [Spark](spark.md) +* [File connections](files.md) +* [Kerberos support](kerberos.md) +* [Full bundle](full.md) diff --git a/mddocs/install/kerberos.md b/mddocs/install/kerberos.md new file mode 100644 index 000000000..1826124c2 --- /dev/null +++ b/mddocs/install/kerberos.md @@ -0,0 +1,32 @@ + + +# Kerberos support + +Most of Hadoop instances set up with Kerberos support, +so some connections require additional setup to work properly. + +* `HDFS` + Uses [requests-kerberos](https://pypi.org/project/requests-kerberos/) and + [GSSApi](https://pypi.org/project/gssapi/) for authentication. + It also uses `kinit` executable to generate Kerberos ticket. +* `Hive` and `SparkHDFS` + require Kerberos ticket to exist before creating Spark session. + +So you need to install OS packages with: + +* `krb5` libs +* Headers for `krb5` +* `gcc` or other compiler for C sources + +The exact installation instruction depends on your OS, here are some examples: + +```bash +apt install libkrb5-dev krb5-user gcc # Debian-based +dnf install krb5-devel krb5-libs krb5-workstation gcc # CentOS, OracleLinux +``` + +Also you should pass `kerberos` to `extras` to install required Python packages: + +```bash +pip install onetl[kerberos] +``` diff --git a/mddocs/install/spark.md b/mddocs/install/spark.md new file mode 100644 index 000000000..914f71611 --- /dev/null +++ b/mddocs/install/spark.md @@ -0,0 +1,342 @@ + + +# Spark + +All DB connection classes (`Clickhouse`, `Greenplum`, `Hive` and others) +and all FileDF connection classes (`SparkHDFS`, `SparkLocalFS`, `SparkS3`) +require Spark to be installed. + +## Installing Java + +Firstly, you should install JDK. The exact installation instruction depends on your OS, here are some examples: + +```bash +yum install java-1.8.0-openjdk-devel # CentOS 7 + Spark 2 +dnf install java-11-openjdk-devel # CentOS 8 + Spark 3 +apt-get install openjdk-11-jdk # Debian-based + Spark 3 +``` + + + +### Compatibility matrix + +| Spark | Python | Java | Scala | +|-----------------------------------------------------------|------------|------------|---------| +| [2.3.x](https://spark.apache.org/docs/2.3.1/#downloading) | 3.7 only | 8 only | 2.11 | +| [2.4.x](https://spark.apache.org/docs/2.4.8/#downloading) | 3.7 only | 8 only | 2.11 | +| [3.2.x](https://spark.apache.org/docs/3.2.4/#downloading) | 3.7 - 3.10 | 8u201 - 11 | 2.12 | +| [3.3.x](https://spark.apache.org/docs/3.3.4/#downloading) | 3.7 - 3.12 | 8u201 - 17 | 2.12 | +| [3.4.x](https://spark.apache.org/docs/3.4.4/#downloading) | 3.7 - 3.12 | 8u362 - 20 | 2.12 | +| [3.5.x](https://spark.apache.org/docs/3.5.5/#downloading) | 3.8 - 3.13 | 8u371 - 20 | 2.12 | + +## Installing PySpark + +Then you should install PySpark via passing `spark` to `extras`: + +```bash +pip install onetl[spark] # install latest PySpark +``` + +or install PySpark explicitly: + +```bash +pip install onetl pyspark==3.5.5 # install a specific PySpark version +``` + +or inject PySpark to `sys.path` in some other way BEFORE creating a class instance. +**Otherwise connection object cannot be created.** + + + +## Injecting Java packages + +Some DB and FileDF connection classes require specific packages to be inserted to `CLASSPATH` of Spark session, +like JDBC drivers. + +This is usually done by setting up `spark.jars.packages` option while creating Spark session: + +```python +# here is a list of packages to be downloaded: +maven_packages = ( + Greenplum.get_packages(spark_version="3.2") + + MySQL.get_packages() + + Teradata.get_packages() +) + +spark = ( + SparkSession.builder.config("spark.app.name", "onetl") + .config("spark.jars.packages", ",".join(maven_packages)) + .getOrCreate() +) +``` + +Spark automatically resolves package and all its dependencies, download them and inject to Spark session +(both driver and all executors). + +This requires internet access, because package metadata and `.jar` files are fetched from [Maven Repository](https://mvnrepository.com/). + +But sometimes it is required to: + +* Install package without direct internet access (isolated network) +* Install package which is not available in Maven + +There are several ways to do that. + +### Using `spark.jars` + +The most simple solution, but this requires to store raw `.jar` files somewhere on filesystem or web server. + +* Download `package.jar` files (it’s usually something like `some-package_1.0.0.jar`). Local file name does not matter, but it should be unique. +* (For `spark.submit.deployMode=cluster`) place downloaded files to HDFS or deploy to any HTTP web server serving static files. See [official documentation](https://spark.apache.org/docs/latest/submitting-applications.html#advanced-dependency-management) for more details. +* Create Spark session with passing `.jar` absolute file path to `spark.jars` Spark config option: + +for spark.submit.deployMode=client (default) + +```py +jar_files = ["/path/to/package.jar"] + +# do not pass spark.jars.packages +spark = ( + SparkSession.builder.config("spark.app.name", "onetl") + .config("spark.jars", ",".join(jar_files)) + .getOrCreate() +) +``` + +for spark.submit.deployMode=cluster + +```py +# you can also pass URLs like http://domain.com/path/to/downloadable/package.jar +jar_files = ["hdfs:///path/to/package.jar"] + +# do not pass spark.jars.packages +spark = ( + SparkSession.builder.config("spark.app.name", "onetl") + .config("spark.jars", ",".join(jar_files)) + .getOrCreate() +) +``` + +### Using `spark.jars.repositories` + +#### NOTE +In this case Spark still will try to fetch packages from the internet, so if you don’t have internet access, +Spark session will be created with significant delay because of all attempts to fetch packages. + +Can be used if you have access both to public repos (like Maven) and a private Artifactory/Nexus repo. + +* Setup private Maven repository in [JFrog Artifactory](https://jfrog.com/artifactory/) or [Sonatype Nexus](https://www.sonatype.com/products/sonatype-nexus-repository). +* Download `package.jar` file (it’s usually something like `some-package_1.0.0.jar`). Local file name does not matter. +* Upload `package.jar` file to private repository (with same `groupId` and `artifactoryId` as in source package in Maven). +* Pass repo URL to `spark.jars.repositories` Spark config option. +* Create Spark session with passing Package name to `spark.jars.packages` Spark config option: + +```python +maven_packages = ( + Greenplum.get_packages(spark_version="3.2") + + MySQL.get_packages() + + Teradata.get_packages() +) + +spark = ( + SparkSession.builder.config("spark.app.name", "onetl") + .config("spark.jars.repositories", "http://nexus.mydomain.com/private-repo/") + .config("spark.jars.packages", ",".join(maven_packages)) + .getOrCreate() +) +``` + +### Using `spark.jars.ivySettings` + +Same as above, but can be used even if there is no network access to public repos like Maven. + +* Setup private Maven repository in [JFrog Artifactory](https://jfrog.com/artifactory/) or [Sonatype Nexus](https://www.sonatype.com/products/sonatype-nexus-repository). +* Download `package.jar` file (it’s usually something like `some-package_1.0.0.jar`). Local file name does not matter. +* Upload `package.jar` file to [private repository](https://help.sonatype.com/repomanager3/nexus-repository-administration/repository-management#RepositoryManagement-HostedRepository) (with same `groupId` and `artifactoryId` as in source package in Maven). +* Create `ivysettings.xml` file (see below). +* Add here a resolver with repository URL (and credentials, if required). +* Pass `ivysettings.xml` absolute path to `spark.jars.ivySettings` Spark config option. +* Create Spark session with passing package name to `spark.jars.packages` Spark config option: + +ivysettings-all-packages-uploaded-to-nexus.xml + +```xml + + + + + + + + + + + + + +``` + +ivysettings-private-packages-in-nexus-public-in-maven.xml + +```xml + + + + + + + + + + + + + + + + + +``` + +ivysettings-private-packages-in-nexus-public-fetched-using-proxy-repo.xml + +```xml + + + + + + + + + + + + + + + +``` + +ivysettings-nexus-with-auth-required.xml + +```xml + + + + + + + + + + + + + + + + + + + +``` + +```python +maven_packages = ( + Greenplum.get_packages(spark_version="3.2") + + MySQL.get_packages() + + Teradata.get_packages() +) + +spark = ( + SparkSession.builder.config("spark.app.name", "onetl") + .config("spark.jars.ivySettings", "/path/to/ivysettings.xml") + .config("spark.jars.packages", ",".join(maven_packages)) + .getOrCreate() +) +``` + +### Place `.jar` file to `-/.ivy2/jars/` + +Can be used to pass already downloaded file to Ivy, and skip resolving package from Maven. + +* Download `package.jar` file (it’s usually something like `some-package_1.0.0.jar`). Local file name does not matter, but it should be unique. +* Move it to `-/.ivy2/jars/` folder. +* Create Spark session with passing package name to `spark.jars.packages` Spark config option: + +```python +maven_packages = ( + Greenplum.get_packages(spark_version="3.2") + + MySQL.get_packages() + + Teradata.get_packages() +) + +spark = ( + SparkSession.builder.config("spark.app.name", "onetl") + .config("spark.jars.packages", ",".join(maven_packages)) + .getOrCreate() +) +``` + +### Place `.jar` file to Spark jars folder + +#### NOTE +Package file should be placed on all hosts/containers Spark is running, +both driver and all executors. + +Usually this is used only with either: +: * `spark.master=local` (driver and executors are running on the same host), + * `spark.master=k8s://...` (`.jar` files are added to image or to volume mounted to all pods). + +Can be used to embed `.jar` files to a default Spark classpath. + +* Download `package.jar` file (it’s usually something like `some-package_1.0.0.jar`). Local file name does not matter, but it should be unique. +* Move it to `$SPARK_HOME/jars/` folder, e.g. `^/.local/lib/python3.7/site-packages/pyspark/jars/` or `/opt/spark/3.2.3/jars/`. +* Create Spark session **WITHOUT** passing Package name to `spark.jars.packages` + +```python +# no need to set spark.jars.packages or any other spark.jars.* option +# all jars already present in CLASSPATH, and loaded automatically + +spark = SparkSession.builder.config("spark.app.name", "onetl").getOrCreate() +``` + +### Manually adding `.jar` files to `CLASSPATH` + +#### NOTE +Package file should be placed on all hosts/containers Spark is running, +both driver and all executors. + +Usually this is used only with either: +: * `spark.master=local` (driver and executors are running on the same host), + * `spark.master=k8s://...` (`.jar` files are added to image or to volume mounted to all pods). + +Can be used to embed `.jar` files to a default Java classpath. + +* Download `package.jar` file (it’s usually something like `some-package_1.0.0.jar`). Local file name does not matter. +* Set environment variable `CLASSPATH` to `/path/to/package.jar`. You can set multiple file paths +* Create Spark session **WITHOUT** passing Package name to `spark.jars.packages` + +```python +# no need to set spark.jars.packages or any other spark.jars.* option +# all jars already present in CLASSPATH, and loaded automatically + +import os + +jar_files = ["/path/to/package.jar"] +# different delimiters for Windows and Linux +delimiter = ";" if os.name == "nt" else ":" +spark = ( + SparkSession.builder.config("spark.app.name", "onetl") + .config("spark.driver.extraClassPath", delimiter.join(jar_files)) + .config("spark.executor.extraClassPath", delimiter.join(jar_files)) + .getOrCreate() +) +``` diff --git a/mddocs/logging.md b/mddocs/logging.md new file mode 100644 index 000000000..f9900f789 --- /dev/null +++ b/mddocs/logging.md @@ -0,0 +1,236 @@ + + +# Logging + +Logging is quite important to understand what’s going on under the hood of onETL. + +Default logging level for Python interpreters is `WARNING`, +but most of onETL logs are in `INFO` level, so users usually don’t see much. + +To change logging level, there is a function [`setup_logging`](#onetl.log.setup_logging) +which should be called at the top of the script: + +```python +from onetl.log import setup_logging +from other.lib import some, more, imports + +setup_logging() + +# rest of code +... +``` + +This changes both log level and log formatting to something like this: + +### See logs + +```text +2024-04-12 10:12:10,834 [INFO ] MainThread: |onETL| Using IncrementalStrategy as a strategy +2024-04-12 10:12:10,835 [INFO ] MainThread: =================================== DBReader.run() starts =================================== +2024-04-12 10:12:10,835 [INFO ] MainThread: |DBReader| Getting Spark type for HWM expression: 'updated_at' +2024-04-12 10:12:10,836 [INFO ] MainThread: |MSSQL| Fetching schema of table 'source_schema.table' ... +2024-04-12 10:12:11,636 [INFO ] MainThread: |MSSQL| Schema fetched. +2024-04-12 10:12:11,642 [INFO ] MainThread: |DBReader| Got Spark field: StructField('updated_at', TimestampType(), True) +2024-04-12 10:12:11,642 [INFO ] MainThread: |DBReader| Detected HWM type: 'ColumnDateTimeHWM' +2024-04-12 10:12:11,643 [INFO ] MainThread: |IncrementalStrategy| Fetching HWM from HorizonHWMStore: +2024-04-12 10:12:11,643 [INFO ] MainThread: name = 'updated_at#source_schema.table@mssql:/mssql.host:1433/somedb' +2024-04-12 10:12:12,181 [INFO ] MainThread: |IncrementalStrategy| Fetched HWM: +2024-04-12 10:12:12,182 [INFO ] MainThread: hwm = ColumnDateTimeHWM( +2024-04-12 10:12:12,182 [INFO ] MainThread: name = 'updated_at#source_schema.table@mssql:/mssql.host:1433/somedb', +2024-04-12 10:12:12,182 [INFO ] MainThread: entity = 'source_schema.table', +2024-04-12 10:12:12,182 [INFO ] MainThread: expression = 'updated_at', +2024-04-12 10:12:12,184 [INFO ] MainThread: value = datetime.datetime(2024, 4, 11, 18, 10, 2, 120000), +2024-04-12 10:12:12,184 [INFO ] MainThread: ) +2024-04-12 10:12:12,184 [INFO ] MainThread: |MSSQL| -> |Spark| Reading DataFrame from source using parameters: +2024-04-12 10:12:12,185 [INFO ] MainThread: source = 'source_schema.table' +2024-04-12 10:12:12,185 [INFO ] MainThread: columns = [ +2024-04-12 10:12:12,185 [INFO ] MainThread: 'id', +2024-04-12 10:12:12,186 [INFO ] MainThread: 'new_value', +2024-04-12 10:12:12,186 [INFO ] MainThread: 'old_value', +2024-04-12 10:12:12,186 [INFO ] MainThread: 'updated_at', +2024-04-12 10:12:12,186 [INFO ] MainThread: ] +2024-04-12 10:12:12,187 [INFO ] MainThread: where = "field = 'some'" +2024-04-12 10:12:12,187 [INFO ] MainThread: hwm = AutoDetectHWM( +2024-04-12 10:12:12,187 [INFO ] MainThread: name = 'updated_at#source_schema.table@mssql:/mssql.host:1433/somedb', +2024-04-12 10:12:12,187 [INFO ] MainThread: entity = 'source_schema.table', +2024-04-12 10:12:12,187 [INFO ] MainThread: expression = 'updated_at', +2024-04-12 10:12:12,188 [INFO ] MainThread: ) +2024-04-12 10:12:12,188 [INFO ] MainThread: options = { +2024-04-12 10:12:12,188 [INFO ] MainThread: 'fetchsize': 100000, +2024-04-12 10:12:12,188 [INFO ] MainThread: 'numPartitions': 1, +2024-04-12 10:12:12,189 [INFO ] MainThread: 'partitioningMode': 'range', +2024-04-12 10:12:12,189 [INFO ] MainThread: } +2024-04-12 10:12:12,189 [INFO ] MainThread: |MSSQL| Checking connection availability... +2024-04-12 10:12:12,189 [INFO ] MainThread: |MSSQL| Using connection parameters: +2024-04-12 10:12:12,190 [INFO ] MainThread: user = 'db_user' +2024-04-12 10:12:12,190 [INFO ] MainThread: password = SecretStr('**********') +2024-04-12 10:12:12,190 [INFO ] MainThread: host = 'mssql.host' +2024-04-12 10:12:12,190 [INFO ] MainThread: port = 1433 +2024-04-12 10:12:12,191 [INFO ] MainThread: database = 'somedb' +2024-04-12 10:12:12,191 [INFO ] MainThread: extra = {'applicationIntent': 'ReadOnly', 'trustServerCertificate': 'true'} +2024-04-12 10:12:12,191 [INFO ] MainThread: jdbc_url = 'jdbc:sqlserver:/mssql.host:1433' +2024-04-12 10:12:12,579 [INFO ] MainThread: |MSSQL| Connection is available. +2024-04-12 10:12:12,581 [INFO ] MainThread: |MSSQL| Executing SQL query (on driver): +2024-04-12 10:12:12,581 [INFO ] MainThread: SELECT +2024-04-12 10:12:12,581 [INFO ] MainThread: MIN(updated_at) AS "min", +2024-04-12 10:12:12,582 [INFO ] MainThread: MAX(updated_at) AS "max" +2024-04-12 10:12:12,582 [INFO ] MainThread: FROM +2024-04-12 10:12:12,582 [INFO ] MainThread: source_schema.table +2024-04-12 10:12:12,582 [INFO ] MainThread: WHERE +2024-04-12 10:12:12,582 [INFO ] MainThread: (field = 'some') +2024-04-12 10:12:12,583 [INFO ] MainThread: AND +2024-04-12 10:12:12,583 [INFO ] MainThread: (updated_at >= CAST('2024-04-11T18:10:02.120000' AS datetime2)) +2024-04-12 10:16:22,537 [INFO ] MainThread: |MSSQL| Received values: +2024-04-12 10:16:22,538 [INFO ] MainThread: MIN(updated_at) = datetime.datetime(2024, 4, 11, 21, 10, 7, 397000) +2024-04-12 10:16:22,538 [INFO ] MainThread: MAX(updated_at) = datetime.datetime(2024, 4, 12, 13, 12, 2, 123000) +2024-04-12 10:16:22,540 [INFO ] MainThread: |MSSQL| Executing SQL query (on executor): +2024-04-12 10:16:22,540 [INFO ] MainThread: SELECT +2024-04-12 10:16:22,540 [INFO ] MainThread: id, +2024-04-12 10:16:22,541 [INFO ] MainThread: new_value, +2024-04-12 10:16:22,541 [INFO ] MainThread: old_value, +2024-04-12 10:16:22,541 [INFO ] MainThread: updated_at +2024-04-12 10:16:22,541 [INFO ] MainThread: FROM +2024-04-12 10:16:22,541 [INFO ] MainThread: source_schema.table +2024-04-12 10:16:22,542 [INFO ] MainThread: WHERE +2024-04-12 10:16:22,542 [INFO ] MainThread: (field = 'some') +2024-04-12 10:16:22,542 [INFO ] MainThread: AND +2024-04-12 10:16:22,542 [INFO ] MainThread: (updated_at > CAST('2024-04-11T18:10:02.120000' AS datetime2)) +2024-04-12 10:16:22,542 [INFO ] MainThread: AND +2024-04-12 10:16:22,542 [INFO ] MainThread: (updated_at <= CAST('2024-04-12T13:12:02.123000' AS datetime2)) +2024-04-12 10:16:22,892 [INFO ] MainThread: |Spark| DataFrame successfully created from SQL statement +2024-04-12 10:16:22,892 [INFO ] MainThread: ------------------------------------ DBReader.run() ends ------------------------------------ +2024-04-12 10:40:42,409 [INFO ] MainThread: =================================== DBWriter.run() starts =================================== +2024-04-12 10:40:42,409 [INFO ] MainThread: |Spark| -> |Hive| Writing DataFrame to target using parameters: +2024-04-12 10:40:42,410 [INFO ] MainThread: target = 'target_source_schema.table' +2024-04-12 10:40:42,410 [INFO ] MainThread: options = { +2024-04-12 10:40:42,410 [INFO ] MainThread: 'mode': 'append', +2024-04-12 10:40:42,410 [INFO ] MainThread: 'format': 'orc', +2024-04-12 10:40:42,410 [INFO ] MainThread: 'partitionBy': 'part_dt', +2024-04-12 10:40:42,410 [INFO ] MainThread: } +2024-04-12 10:40:42,411 [INFO ] MainThread: df_schema: +2024-04-12 10:40:42,412 [INFO ] MainThread: root +2024-04-12 10:40:42,412 [INFO ] MainThread: |-- id: integer (nullable = true) +2024-04-12 10:40:42,413 [INFO ] MainThread: |-- new_value: string (nullable = true) +2024-04-12 10:40:42,413 [INFO ] MainThread: |-- old_value: string (nullable = true) +2024-04-12 10:40:42,413 [INFO ] MainThread: |-- updated_at: timestamp (nullable = true) +2024-04-12 10:40:42,413 [INFO ] MainThread: |-- part_dt: date (nullable = true) +2024-04-12 10:40:42,414 [INFO ] MainThread: +2024-04-12 10:40:42,421 [INFO ] MainThread: |Hive| Checking connection availability... +2024-04-12 10:40:42,421 [INFO ] MainThread: |Hive| Using connection parameters: +2024-04-12 10:40:42,421 [INFO ] MainThread: cluster = 'dwh' +2024-04-12 10:40:42,475 [INFO ] MainThread: |Hive| Connection is available. +2024-04-12 10:40:42,476 [INFO ] MainThread: |Hive| Fetching schema of table 'target_source_schema.table' ... +2024-04-12 10:40:43,518 [INFO ] MainThread: |Hive| Schema fetched. +2024-04-12 10:40:43,521 [INFO ] MainThread: |Hive| Table 'target_source_schema.table' already exists +2024-04-12 10:40:43,521 [WARNING ] MainThread: |Hive| User-specified options {'partitionBy': 'part_dt'} are ignored while inserting into existing table. Using only table parameters from Hive metastore +2024-04-12 10:40:43,782 [INFO ] MainThread: |Hive| Inserting data into existing table 'target_source_schema.table' ... +2024-04-12 11:06:07,396 [INFO ] MainThread: |Hive| Data is successfully inserted into table 'target_source_schema.table'. +2024-04-12 11:06:07,397 [INFO ] MainThread: ------------------------------------ DBWriter.run() ends ------------------------------------ +2024-04-12 11:06:07,397 [INFO ] MainThread: |onETL| Exiting IncrementalStrategy +2024-04-12 11:06:07,397 [INFO ] MainThread: |IncrementalStrategy| Saving HWM to 'HorizonHWMStore': +2024-04-12 11:06:07,397 [INFO ] MainThread: hwm = ColumnDateTimeHWM( +2024-04-12 11:06:07,397 [INFO ] MainThread: name = 'updated_at#source_schema.table@mssql:/mssql.host:1433/somedb', +2024-04-12 11:06:07,397 [INFO ] MainThread: entity = 'source_source_schema.table', +2024-04-12 11:06:07,397 [INFO ] MainThread: expression = 'updated_at', +2024-04-12 11:06:07,397 [INFO ] MainThread: value = datetime.datetime(2024, 4, 12, 13, 12, 2, 123000), +2024-04-12 11:06:07,397 [INFO ] MainThread: ) +2024-04-12 11:06:07,495 [INFO ] MainThread: |IncrementalStrategy| HWM has been saved +``` + +Each step performed by onETL is extensively logged, which should help with debugging. + +You can make logs even more verbose by changing level to `DEBUG`: + +```python +from onetl.log import setup_logging + +setup_logging(level="DEBUG", enable_clients=True) + +# rest of code +... +``` + +This also changes log level for all underlying Python libraries, e.g. showing each HTTP request being made, and so on. + +### onetl.log.setup_logging(level: int | str = 20, enable_clients: bool = False) → None + +Set up onETL logging. + +What this function does: +: * Adds stderr logging handler + * Changes root logger format to `2023-05-31 11:22:33.456 [INFO] MainThread: message` + * Changes root logger level to `level` + * Changes onETL logger level to `level` + * Sets up logging level of underlying client modules + +#### NOTE +Should be used only in IDEs (like Jupyter notebooks or PyCharm), +or scripts (ETL pipelines). + +#### Versionchanged +Changed in version 0.5.0: Renamed `setup_notebook_logging` → `setup_logging` + +* **Parameters:** + **level** + : Log level for onETL module + + **enable_clients** + : If `True`, enable logging of underlying client modules. + Otherwise, set client modules log level to `DISABLED`. +
+ #### NOTE + For `level="DEBUG"` it is recommended to use `enable_clients=True` +
+ #### Versionadded + Added in version 0.9.0. + + + +### onetl.log.setup_clients_logging(level: int | str = 9999) → None + +Set logging of underlying client modules used by onETL. + +Affected modules: +: * `ftputil` + * `hdfs` + * `minio` + * `paramiko` + * `py4j` + * `pyspark` + * `webdav3` + +#### NOTE +Can be used in applications, but it is recommended to set up these loggers +according to your framework documentation. + +#### Versionchanged +Changed in version 0.9.0: Renamed `disable_clients_logging` → `setup_clients_logging` + +* **Parameters:** + **level** + : Log level for client modules +
+ #### NOTE + For `py4j`, logging level with maximum verbosity is `INFO` because `DEBUG` logs are + totally unreadable. +
+ #### Versionadded + Added in version 0.9.0. + + + +### onetl.log.set_default_logging_format() → None + +Sets default logging format to preferred by onETL. + +Example log message: `2023-05-31 11:22:33.456 [INFO] MainThread: message` + +#### NOTE +Should be used only in IDEs (like Jupyter notebooks or PyCharm), +or scripts (ETL pipelines). + +#### WARNING +Should **NOT** be used in applications, you should set up logging settings manually, +according to your framework documentation. + + diff --git a/mddocs/plugins.md b/mddocs/plugins.md new file mode 100644 index 000000000..d42ded034 --- /dev/null +++ b/mddocs/plugins.md @@ -0,0 +1,147 @@ + + +# Plugins + +#### Versionadded +Added in version 0.6.0. + +## What are plugins? + +### Terms + +* `Plugin` - some Python package which implements some extra functionality for onETL, like [Hooks](hooks/index.md#hooks) +* `Plugin autoimport` - onETL behavior which allows to automatically import this package if it contains proper metadata (`entry_points`) + +### Features + +Plugins mechanism allows to: + +* Automatically register [Hooks](hooks/index.md#hooks) which can alter onETL behavior +* Automatically register new classes, like HWM type, HWM stores and so on + +### Limitations + +Unlike other projects (like *Airflow 1.x*), plugins does not inject imported classes or functions to `onetl.*` namespace. +Users should import classes from the plugin package **explicitly** to avoid name collisions. + +## How to implement plugin? + +Create a Python package `some-plugin` with a file `some_plugin/setup.py`: + +```python +# some_plugin/setup.py +from setuptools import setup + +setup( + # if you want to import something from onETL, add it to requirements list + install_requires=["onetl"], + entry_points={ + # this key enables plugins autoimport functionality + "onetl.plugins": [ + "some-plugin-name=some_plugin.module", # automatically import all module content + "some-plugin-class=some_plugin.module.internals:MyClass", # import a specific class + "some-plugin-function=some_plugin.module.internals:my_function", # import a specific function + ], + }, +) +``` + +See [setuptools documentation for entry_points](https://setuptools.pypa.io/en/latest/userguide/entry_point.html) + +## How plugins are imported? + +* User should install a package implementing the plugin: + +```bash +pip install some-package +``` + +* Then user should import something from `onetl` module or its submodules: + +```python +import onetl +from onetl.connection import SomeConnection + +# and so on +``` + +* This import automatically executes something like: + +```python +import some_plugin.module +from some_plugin.module.internals import MyClass +from some_plugin.module.internals import my_function +``` + +If specific module/class/function uses some registration capabilities of onETL, +like [@hook decorator](hooks/hook.md#hook-decorator), it will be executed during this import. + +## How to enable/disable plugins? + +#### Versionadded +Added in version 0.7.0. + +### Disable/enable all plugins + +By default plugins are enabled. + +To disabled them, you can set environment variable `ONETL_PLUGINS_ENABLED` to `false` BEFORE +importing onETL. This will disable all plugins autoimport. + +But user is still be able to explicitly import `some_plugin.module`, executing +all decorators and registration capabilities of onETL. + +### Disable a specific plugin (blacklist) + +If some plugin is failing during import, you can disable it by setting up environment variable +`ONETL_PLUGINS_BLACKLIST=some-failing-plugin`. Multiple plugin names could be passed with `,` as delimiter. + +Again, this environment variable should be set BEFORE importing onETL. + +### Disable all plugins except a specific one (whitelist) + +You can also disable all plugins except a specific one by setting up environment variable +`ONETL_PLUGINS_WHITELIST=some-not-failing-plugin`. Multiple plugin names could be passed with `,` as delimiter. + +Again, this environment variable should be set BEFORE importing onETL. + +If both whitelist and blacklist environment variables are set, blacklist has a higher priority. + +## How to see logs of the plugins mechanism? + +Plugins registration emits logs with `INFO` level: + +```python +import logging + +logging.basicConfig(level=logging.INFO) +``` + +```text +INFO |onETL| Found 2 plugins +INFO |onETL| Loading plugin 'my-plugin' +INFO |onETL| Skipping plugin 'failing' because it is in a blacklist +``` + +More detailed logs are emitted with `DEBUG` level, to make output less verbose: + +```python +import logging + +logging.basicConfig(level=logging.DEBUG) +``` + +```text +DEBUG |onETL| Searching for plugins with group 'onetl.plugins' +DEBUG |Plugins| Plugins whitelist: [] +DEBUG |Plugins| Plugins blacklist: ['failing-plugin'] +INFO |Plugins| Found 2 plugins +INFO |onETL| Loading plugin (1/2): +DEBUG name: 'my-plugin' +DEBUG package: 'my-package' +DEBUG version: '0.1.0' +DEBUG importing: 'my_package.my_module:MyClass' +DEBUG |onETL| Successfully loaded plugin 'my-plugin' +DEBUG source: '/usr/lib/python3.11/site-packages/my_package/my_module/my_class.py' +INFO |onETL| Skipping plugin 'failing' because it is in a blacklist +``` diff --git a/mddocs/quickstart.md b/mddocs/quickstart.md new file mode 100644 index 000000000..7a9bd0652 --- /dev/null +++ b/mddocs/quickstart.md @@ -0,0 +1,365 @@ +: + +# Quick start + +## MSSQL → Hive + +Read data from MSSQL, transform & write to Hive. + +```bash +# install onETL and PySpark +pip install onetl[spark] +``` + +```python +# Import pyspark to initialize the SparkSession +from pyspark.sql import SparkSession + +# import function to setup onETL logging +from onetl.log import setup_logging + +# Import required connections +from onetl.connection import MSSQL, Hive + +# Import onETL classes to read & write data +from onetl.db import DBReader, DBWriter + +# change logging level to INFO, and set up default logging format and handler +setup_logging() + +# Initialize new SparkSession with MSSQL driver loaded +maven_packages = MSSQL.get_packages() +spark = ( + SparkSession.builder.appName("spark_app_onetl_demo") + .config("spark.jars.packages", ",".join(maven_packages)) + .enableHiveSupport() # for Hive + .getOrCreate() +) + +# Initialize MSSQL connection and check if database is accessible +mssql = MSSQL( + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, + # These options are passed to MSSQL JDBC Driver: + extra={"applicationIntent": "ReadOnly"}, +).check() + +# >>> INFO:|MSSQL| Connection is available + +# Initialize DBReader +reader = DBReader( + connection=mssql, + source="dbo.demo_table", + columns=["on", "etl"], + # Set some MSSQL read options: + options=MSSQL.ReadOptions(fetchsize=10000), +) + +# checks that there is data in the table, otherwise raises exception +reader.raise_if_no_data() + +# Read data to DataFrame +df = reader.run() +df.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) + +# Apply any PySpark transformations +from pyspark.sql.functions import lit + +df_to_write = df.withColumn("engine", lit("onetl")) +df_to_write.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) +# |-- engine: string (nullable = false) + +# Initialize Hive connection +hive = Hive(cluster="rnd-dwh", spark=spark) + +# Initialize DBWriter +db_writer = DBWriter( + connection=hive, + target="dl_sb.demo_table", + # Set some Hive write options: + options=Hive.WriteOptions(if_exists="replace_entire_table"), +) + +# Write data from DataFrame to Hive +db_writer.run(df_to_write) + +# Success! +``` + +## SFTP → HDFS + +Download files from SFTP & upload them to HDFS. + +```bash +# install onETL with SFTP and HDFS clients, and Kerberos support +pip install onetl[hdfs,sftp,kerberos] +``` + +```python +# import function to setup onETL logging +from onetl.log import setup_logging + +# Import required connections +from onetl.connection import SFTP, HDFS + +# Import onETL classes to download & upload files +from onetl.file import FileDownloader, FileUploader + +# import filter & limit classes +from onetl.file.filter import Glob, ExcludeDir +from onetl.file.limit import MaxFilesCount + +# change logging level to INFO, and set up default logging format and handler +setup_logging() + +# Initialize SFTP connection and check it +sftp = SFTP( + host="sftp.test.com", + user="someuser", + password="somepassword", +).check() + +# >>> INFO:|SFTP| Connection is available + +# Initialize downloader +file_downloader = FileDownloader( + connection=sftp, + source_path="/remote/tests/Report", # path on SFTP + local_path="/local/onetl/Report", # local fs path + filters=[ + # download only files matching the glob + Glob("*.csv"), + # exclude files from this directory + ExcludeDir("/remote/tests/Report/exclude_dir/"), + ], + limits=[ + # download max 1000 files per run + MaxFilesCount(1000), + ], + options=FileDownloader.Options( + # delete files from SFTP after successful download + delete_source=True, + # mark file as failed if it already exist in local_path + if_exists="error", + ), +) + +# Download files to local filesystem +download_result = downloader.run() + +# Method run returns a DownloadResult object, +# which contains collection of downloaded files, divided to 4 categories +download_result + +# DownloadResult( +# successful=[ +# LocalPath('/local/onetl/Report/file_1.json'), +# LocalPath('/local/onetl/Report/file_2.json'), +# ], +# failed=[FailedRemoteFile('/remote/onetl/Report/file_3.json')], +# ignored=[RemoteFile('/remote/onetl/Report/file_4.json')], +# missing=[], +# ) + +# Raise exception if there are failed files, or there were no files in the remote filesystem +download_result.raise_if_failed() or download_result.raise_if_empty() + +# Do any kind of magic with files: rename files, remove header for csv files, ... +renamed_files = my_rename_function(download_result.success) + +# function removed "_" from file names +# [ +# LocalPath('/home/onetl/Report/file1.json'), +# LocalPath('/home/onetl/Report/file2.json'), +# ] + +# Initialize HDFS connection +hdfs = HDFS( + host="my.name.node", + user="someuser", + password="somepassword", # or keytab +) + +# Initialize uploader +file_uploader = FileUploader( + connection=hdfs, + target_path="/user/onetl/Report/", # hdfs path +) + +# Upload files from local fs to HDFS +upload_result = file_uploader.run(renamed_files) + +# Method run returns a UploadResult object, +# which contains collection of uploaded files, divided to 4 categories +upload_result + +# UploadResult( +# successful=[RemoteFile('/user/onetl/Report/file1.json')], +# failed=[FailedLocalFile('/local/onetl/Report/file2.json')], +# ignored=[], +# missing=[], +# ) + +# Raise exception if there are failed files, or there were no files in the local filesystem, or some input file is missing +upload_result.raise_if_failed() or upload_result.raise_if_empty() or upload_result.raise_if_missing() + +# Success! +``` + +## S3 → Postgres + +Read files directly from S3 path, convert them to dataframe, transform it and then write to a database. + +```bash +# install onETL and PySpark +pip install onetl[spark] +``` + +```python +# Import pyspark to initialize the SparkSession +from pyspark.sql import SparkSession + +# import function to setup onETL logging +from onetl.log import setup_logging + +# Import required connections +from onetl.connection import Postgres, SparkS3 + +# Import onETL classes to read files +from onetl.file import FileDFReader +from onetl.file.format import CSV + +# Import onETL classes to write data +from onetl.db import DBWriter + +# change logging level to INFO, and set up default logging format and handler +setup_logging() + +# Initialize new SparkSession with Hadoop AWS libraries and Postgres driver loaded +maven_packages = SparkS3.get_packages(spark_version="3.5.5") + Postgres.get_packages() +exclude_packages = SparkS3.get_exclude_packages() +spark = ( + SparkSession.builder.appName("spark_app_onetl_demo") + .config("spark.jars.packages", ",".join(maven_packages)) + .config("spark.jars.excludes", ",".join(exclude_packages)) + .getOrCreate() +) + +# Initialize S3 connection and check it +spark_s3 = SparkS3( + host="s3.test.com", + protocol="https", + bucket="my-bucket", + access_key="somekey", + secret_key="somesecret", + # Access bucket as s3.test.com/my-bucket + extra={"path.style.access": True}, + spark=spark, +).check() + +# >>> INFO:|SparkS3| Connection is available + +# Describe file format and parsing options +csv = CSV( + delimiter=";", + header=True, + encoding="utf-8", +) + +# Describe DataFrame schema of files +from pyspark.sql.types import ( + DateType, + DoubleType, + IntegerType, + StringType, + StructField, + StructType, + TimestampType, +) + +df_schema = StructType( + [ + StructField("id", IntegerType()), + StructField("phone_number", StringType()), + StructField("region", StringType()), + StructField("birth_date", DateType()), + StructField("registered_at", TimestampType()), + StructField("account_balance", DoubleType()), + ], +) + +# Initialize file df reader +reader = FileDFReader( + connection=spark_s3, + source_path="/remote/tests/Report", # path on S3 there *.csv files are located + format=csv, # file format with specific parsing options + df_schema=df_schema, # columns & types +) + +# Read files directly from S3 as Spark DataFrame +df = reader.run() + +# Check that DataFrame schema is same as expected +df.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) + +# Apply any PySpark transformations +from pyspark.sql.functions import lit + +df_to_write = df.withColumn("engine", lit("onetl")) +df_to_write.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) +# |-- engine: string (nullable = false) + +# Initialize Postgres connection +postgres = Postgres( + host="192.169.11.23", + user="onetl", + password="somepassword", + database="mydb", + spark=spark, +) + +# Initialize DBWriter +db_writer = DBWriter( + connection=postgres, + # write to specific table + target="public.my_table", + # with some writing options + options=Postgres.WriteOptions(if_exists="append"), +) + +# Write DataFrame to Postgres table +db_writer.run(df_to_write) + +# Success! +``` diff --git a/mddocs/security.md b/mddocs/security.md new file mode 100644 index 000000000..3048c040b --- /dev/null +++ b/mddocs/security.md @@ -0,0 +1,25 @@ +# Security + +## Supported Python versions + +3.7 or above + +## Product development security recommendations + +1. Update dependencies to last stable version +2. Build SBOM for the project +3. Perform SAST (Static Application Security Testing) where possible + +## Product development security requirements + +1. No binaries in repository +2. No passwords, keys, access tokens in source code +3. No “Critical” and/or “High” vulnerabilities in contributed source code + +## Vulnerability reports + +Please, use email [mailto:onetools@mts.ru](mailto:onetools@mts.ru) for reporting security issues or anything that can cause any consequences for security. + +Please avoid any public disclosure (including registering issues) at least until it is fixed. + +Thank you in advance for understanding. diff --git a/mddocs/strategy/incremental_batch_strategy.md b/mddocs/strategy/incremental_batch_strategy.md new file mode 100644 index 000000000..2d30a6767 --- /dev/null +++ b/mddocs/strategy/incremental_batch_strategy.md @@ -0,0 +1,301 @@ + + +# Incremental Batch Strategy + +### *class* onetl.strategy.incremental_strategy.IncrementalBatchStrategy(\*, hwm: HWM | None = None, step: Any = None, start: Any = None, stop: Any = None, offset: Any = None) + +Incremental batch strategy for [DB Reader](../db/db_reader.md#db-reader). + +#### NOTE +Cannot be used with [File Downloader](../file/file_downloader/file_downloader.md#file-downloader) + +Same as [`IncrementalStrategy`](incremental_strategy.md#onetl.strategy.incremental_strategy.IncrementalStrategy), +but reads data from the source in sequential batches (1..N) like: + +```sql +1: SELECT id, data + FROM public.mydata + WHERE id > 1000 AND id <= 1100; -- previous HWM value is 1000, step is 100 + +2: WHERE id > 1100 AND id <= 1200; -- + step +3: WHERE id > 1200 AND id <= 1200; -- + step +N: WHERE id > 1300 AND id <= 1400; -- until stop +``` + +This allows to use less CPU and RAM than reading all the data in the one batch, +but takes proportionally more time. + +#### WARNING +Unlike [`SnapshotBatchStrategy`](snapshot_batch_strategy.md#onetl.strategy.snapshot_strategy.SnapshotBatchStrategy), +it **saves** current HWM value after **each batch** into [HWM Store](../hwm_store/index.md#hwm). + +So if code inside the context manager raised an exception, like: + +```python +with IncrementalBatchStrategy() as batches: + for _ in batches: + df = reader.run() # something went wrong here + writer.run(df) # or here + # or here... +``` + +DBReader will **NOT** update HWM in HWM Store for the failed batch. + +All of that allows to resume reading process from the *last successful batch*. + +#### WARNING +Not every [DB connection](../connection/db_connection/index.md#db-connections) +supports batch strategy. For example, Kafka connection doesn’t support it. +Make sure the connection you use is compatible with the IncrementalBatchStrategy. + +#### Versionadded +Added in version 0.1.0. + +* **Parameters:** + **step** + : Step size used for generating batch SQL queries like: + ```sql + SELECT id, data + FROM public.mydata + WHERE id > 1000 AND id <= 1100; -- 1000 is previous HWM value, step is 100 + ``` +
+ #### NOTE + Step defines a range of values will be fetched by each batch. This is **not** + a number of rows, it depends on a table content and value distribution across the rows. +
+ #### NOTE + `step` value will be added to the HWM, so it should have a proper type. +
+ For example, for `TIMESTAMP` column `step` type should be `datetime.timedelta`, not `int` + + **stop** + : If passed, the value will be used for generating WHERE clauses with `hwm.expression` filter, + as a stop value for the last batch. +
+ If not set, the value is determined by a separated query: + ```sql + SELECT MAX(id) as stop + FROM public.mydata + WHERE id > 1000; -- 1000 is previous HWM value (if any) + ``` +
+ #### NOTE + `stop` should be the same type as `hwm.expression` value, + e.g. `datetime.datetime` for `TIMESTAMP` column, `datetime.date` for `DATE`, and so on + + **offset** + : If passed, the offset value will be used to read rows which appeared in the source after the previous read. +
+ For example, previous incremental run returned rows: + ```default + 898 + 899 + 900 + 1000 + ``` +
+ Current HWM value is 1000. +
+ But since then few more rows appeared in the source: + ```default + 898 + 899 + 900 + 901 # new + 902 # new + ... + 999 # new + 1000 + ``` +
+ and you need to read them too. +
+ So you can set `offset=100`, so the first batch of a next incremental run will look like: + ```sql + SELECT id, data + FROM public.mydata + WHERE id > 900 AND id <= 1000; -- 900 = 1000 - 100 = HWM - offset + ``` +
+ and return rows from 901 (**not** 900) to **1000** (duplicate). +
+ #### WARNING + This can lead to reading duplicated values from the table. + You probably need additional deduplication step to handle them +
+ #### NOTE + `offset` value will be subtracted from the HWM, so it should have a proper type. +
+ For example, for `TIMESTAMP` column `offset` type should be `datetime.timedelta`, not `int` + +### Examples + +IncrementalBatch run + +```python +from onetl.db import DBReader, DBWriter +from onetl.strategy import IncrementalBatchStrategy + +reader = DBReader( + connection=postgres, + source="public.mydata", + columns=["id", "data"], + hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="id"), +) + +writer = DBWriter(connection=hive, target="db.newtable") + +with IncrementalBatchStrategy(step=100) as batches: + for _ in batches: + df = reader.run() + writer.run(df) +``` + +```sql +-- previous HWM value was 1000 +-- each batch (1..N) will perform a query which return some part of input data + +1: SELECT id, data + FROM public.mydata + WHERE id > 1100 AND id <= 1200; --- from HWM to HWM+step (EXCLUDING first row) + +2: WHERE id > 1200 AND id <= 1300; -- + step +N: WHERE id > 1300 AND id <= 1400; -- until max value of HWM column +``` + +IncrementalBatch run with `stop` value + +```python +... + +with IncrementalBatchStrategy(step=100, stop=2000) as batches: + for _ in batches: + df = reader.run() + writer.run(df) +``` + +```sql +-- previous HWM value was 1000 +-- each batch (1..N) will perform a query which return some part of input data + +1: SELECT id, data + FROM public.mydata + WHERE id > 1000 AND id <= 1100; --- from HWM to HWM+step (EXCLUDING first row) + +2: WHERE id > 1100 AND id <= 1200; -- + step +... +N: WHERE id > 1900 AND id <= 2000; -- until stop +``` + +IncrementalBatch run with `offset` value + +```python +... + +with IncrementalBatchStrategy(step=100, offset=100) as batches: + for _ in batches: + df = reader.run() + writer.run(df) +``` + +```sql +-- previous HWM value was 1000 +-- each batch (1..N) will perform a query which return some part of input data + +1: SELECT id, data + FROM public.mydata + WHERE id > 900 AND id <= 1000; --- from HWM-offset to HWM-offset+step (EXCLUDING first row) + +2: WHERE id > 1000 AND id <= 1100; -- + step +3: WHERE id > 1100 AND id <= 1200; -- + step +... +N: WHERE id > 1300 AND id <= 1400; -- until max value of HWM column +``` + +IncrementalBatch run with all possible options + +```python +... + +with IncrementalBatchStrategy( + step=100, + stop=2000, + offset=100, +) as batches: + for _ in batches: + df = reader.run() + writer.run(df) +``` + +```sql +-- previous HWM value was 1000 +-- each batch (1..N) will perform a query which return some part of input data + +1: SELECT id, data + FROM public.mydata + WHERE id > 900 AND id <= 1000; --- from HWM-offset to HWM-offset+step (EXCLUDING first row) + +2: WHERE id > 1000 AND id <= 1100; -- + step +3: WHERE id > 1100 AND id <= 1200; -- + step +... +N: WHERE id > 1900 AND id <= 2000; -- until stop +``` + +IncrementalBatch run over non-integer column + +`hwm.expression`, `offset` and `stop` can be a date or datetime, not only integer: + +```python +from onetl.db import DBReader, DBWriter +from datetime import date, timedelta + +reader = DBReader( + connection=postgres, + source="public.mydata", + columns=["business_dt", "data"], + hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="business_dt"), +) + +writer = DBWriter(connection=hive, target="db.newtable") + +with IncrementalBatchStrategy( + step=timedelta(days=5), + stop=date("2021-01-31"), + offset=timedelta(days=1), +) as batches: + for _ in batches: + df = reader.run() + writer.run(df) +``` + +```sql +-- previous HWM value was '2021-01-10' +-- each batch (1..N) will perform a query which return some part of input data + +1: SELECT business_dt, data + FROM public.mydata + WHERE business_dt > CAST('2021-01-09' AS DATE) -- from HWM-offset (EXCLUDING first row) + AND business_dt <= CAST('2021-01-14' AS DATE); -- to HWM-offset+step + +2: WHERE business_dt > CAST('2021-01-14' AS DATE) -- + step + AND business_dt <= CAST('2021-01-19' AS DATE); + +3: WHERE business_dt > CAST('2021-01-19' AS DATE) -- + step + AND business_dt <= CAST('2021-01-24' AS DATE); + +... + +N: WHERE business_dt > CAST('2021-01-29' AS DATE) + AND business_dt <= CAST('2021-01-31' AS DATE); -- until stop +``` + + + +#### \_\_init_\_(\*\*kwargs) + +Create a new model by parsing and validating input data from keyword arguments. + +Raises ValidationError if the input data cannot be parsed to form a valid model. + + diff --git a/mddocs/strategy/incremental_strategy.md b/mddocs/strategy/incremental_strategy.md new file mode 100644 index 000000000..a40dc5b94 --- /dev/null +++ b/mddocs/strategy/incremental_strategy.md @@ -0,0 +1,396 @@ + + +# Incremental Strategy + +### *class* onetl.strategy.incremental_strategy.IncrementalStrategy(\*, hwm: HWM | None = None, offset: Any = None) + +Incremental strategy for [DB Reader](../db/db_reader.md#db-reader)/[File Downloader](../file/file_downloader/file_downloader.md#file-downloader). + +Used for fetching only new rows/files from a source +by filtering items not covered by the previous [HWM](../hwm_store/index.md#hwm) value. + +For [DB Reader](../db/db_reader.md#db-reader): +: First incremental run is just the same as [`SnapshotStrategy`](snapshot_strategy.md#onetl.strategy.snapshot_strategy.SnapshotStrategy): +
+ ```sql + SELECT id, data FROM mydata; + ``` +
+ Then the max value of `id` column (e.g. `1000`) will be saved as `HWM` to [HWM Store](../hwm_store/index.md#hwm). +
+ Next incremental run will read only new data from the source: +
+ ```sql + SELECT id, data FROM mydata WHERE id > 1000; -- hwm value + ``` +
+ Pay attention to resulting dataframe **does not include** row with `id=1000` because it has been read before. +
+ #### WARNING + If code inside the context manager raised an exception, like: +
+ ```python + with IncrementalStrategy(): + df = reader.run() # something went wrong here + writer.run(df) # or here + # or here... + ``` +
+ When DBReader will **NOT** update HWM in HWM Store. + This allows to resume reading process from the *last successful run*. + +For [File Downloader](../file/file_downloader/file_downloader.md#file-downloader): +: Behavior depends on `hwm` type. +
+ FileListHWM +
+ First incremental run is just the same as [`SnapshotStrategy`](snapshot_strategy.md#onetl.strategy.snapshot_strategy.SnapshotStrategy) - + all files are downloaded: +
+ ```bash + $ hdfs dfs -ls /path +
+ /path/my/file1 + /path/my/file2 + ``` +
+ ```python + DownloadResult( + ..., + successful={ + LocalFile("/downloaded/file1"), + LocalFile("/downloaded/file2"), + }, + ) + ``` +
+ Then the list of original file paths is saved as `FileListHWM` object into [HWM Store](../hwm_store/index.md#hwm): +
+ ```python + FileListHWM( + ..., + entity="/path", + value=[ + "/path/my/file1", + "/path/my/file2", + ], + ) + ``` +
+ Next incremental run will download only new files which were added to the source since previous run: +
+ ```bash + $ hdfs dfs -ls /path +
+ /path/my/file1 + /path/my/file2 + /path/my/file3 + ``` +
+ ```python + # only files which are not covered by FileListHWM + DownloadResult( + ..., + successful={ + LocalFile("/downloaded/file3"), + }, + ) + ``` +
+ Value of `FileListHWM` will be updated and saved to [HWM Store](../hwm_store/index.md#hwm): +
+ ```python + FileListHWM( + ..., + directory="/path", + value=[ + "/path/my/file1", + "/path/my/file2", + "/path/my/file3", + ], + ) + ``` +
+ FileModifiedTimeHWM +
+ First incremental run is just the same as [`SnapshotStrategy`](snapshot_strategy.md#onetl.strategy.snapshot_strategy.SnapshotStrategy) - + all files are downloaded: +
+ ```bash + $ hdfs dfs -ls /path +
+ /path/my/file1 + /path/my/file2 + ``` +
+ ```python + DownloadResult( + ..., + successful={ + LocalFile("/downloaded/file1"), + LocalFile("/downloaded/file2"), + }, + ) + ``` +
+ Then the maximum modified time of original files is saved as `FileModifiedTimeHWM` object into [HWM Store](../hwm_store/index.md#hwm): +
+ ```python + FileModifiedTimeHWM( + ..., + directory="/path", + value=datetime.datetime(2025, 1, 1, 11, 22, 33, 456789, tzinfo=timezone.utc), + ) + ``` +
+ Next incremental run will download only files from the source which were modified or created since previous run: +
+ ```bash + $ hdfs dfs -ls /path +
+ /path/my/file1 + /path/my/file2 + /path/my/file3 + ``` +
+ ```python + # only files which are not covered by FileModifiedTimeHWM + DownloadResult( + ..., + successful={ + LocalFile("/downloaded/file3"), + }, + ) + ``` +
+ Value of `FileModifiedTimeHWM` will be updated and and saved to [HWM Store](../hwm_store/index.md#hwm): +
+ ```python + FileModifiedTimeHWM( + ..., + directory="/path", + value=datetime.datetime(2025, 1, 1, 22, 33, 44, 567890, tzinfo=timezone.utc), + ) + ``` +
+ #### WARNING + FileDownload updates HWM in HWM Store at the end of `.run()` call, + **NOT** while exiting strategy context. This is because: +
+ * FileDownloader does not raise exceptions if some file cannot be downloaded. + * FileDownloader creates files on local filesystem, and file content may differ for different `modes`. + * It can remove files from the source if `delete_source` is set to `True`. + +#### Versionadded +Added in version 0.1.0. + +* **Parameters:** + **offset** + : If passed, the offset value will be used to read rows which appeared in the source after the previous read. +
+ For example, previous incremental run returned rows: + ```default + 898 + 899 + 900 + 1000 + ``` +
+ Current HWM value is 1000. +
+ But since then few more rows appeared in the source: + ```default + 898 + 899 + 900 + 901 # new + 902 # new + ... + 999 # new + 1000 + ``` +
+ and you need to read them too. +
+ So you can set `offset=100`, so a next incremental run will generate SQL query like: + ```sql + SELECT id, data FROM public.mydata WHERE id > 900; + -- 900 = 1000 - 100 = hwm - offset + ``` +
+ and return rows since 901 (**not** 900), **including** 1000 which was already captured by HWM. +
+ #### WARNING + This can lead to reading duplicated values from the table. + You probably need additional deduplication step to handle them +
+ #### WARNING + Cannot be used with [File Downloader](../file/file_downloader/file_downloader.md#file-downloader) +
+ #### NOTE + `offset` value will be subtracted from the HWM, so it should have a proper type. +
+ For example, for `TIMESTAMP` column `offset` type should be `datetime.timedelta`, not `int` + +### Examples + +Incremental run with [DB Reader](../db/db_reader.md#db-reader) + +```python +from onetl.db import DBReader, DBWriter +from onetl.strategy import IncrementalStrategy + +reader = DBReader( + connection=postgres, + source="public.mydata", + columns=["id", "data"], + hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="id"), +) + +writer = DBWriter(connection=hive, target="db.newtable") + +with IncrementalStrategy(): + df = reader.run() + writer.run(df) +``` + +```sql +-- previous HWM value was 1000 +-- DBReader will generate query like: + +SELECT id, data +FROM public.mydata +WHERE id > 1000; --- from HWM (EXCLUDING first row) +``` + +Incremental run with [DB Reader](../db/db_reader.md#db-reader) and `IncrementalStrategy(offset=...)` + +```python +from onetl.db import DBReader, DBWriter +from onetl.strategy import IncrementalStrategy + +reader = DBReader( + connection=postgres, + source="public.mydata", + columns=["id", "data"], + hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="id"), +) + +writer = DBWriter(connection=hive, target="db.newtable") + +with IncrementalStrategy(offset=100): + df = reader.run() + writer.run(df) +``` + +```sql +-- previous HWM value was 1000 +-- DBReader will generate query like: + +SELECT id, data +FROM public.mydata +WHERE id > 900; -- from HWM-offset (EXCLUDING first row) +``` + +`offset` and `hwm.expression` can be a date or datetime, not only integer: + +```python +from onetl.db import DBReader, DBWriter +from datetime import timedelta + +reader = DBReader( + connection=postgres, + source="public.mydata", + columns=["business_dt", "data"], + hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="business_dt"), +) + +writer = DBWriter(connection=hive, target="db.newtable") + +with IncrementalStrategy(offset=timedelta(days=1)): + df = reader.run() + writer.run(df) +``` + +```sql +-- previous HWM value was '2021-01-10' +-- DBReader will generate query like: + +SELECT business_dt, data +FROM public.mydata +WHERE business_dt > CAST('2021-01-09' AS DATE); -- from HWM-offset (EXCLUDING first row) +``` + +Incremental run with [DB Reader](../db/db_reader.md#db-reader) and [Kafka](../connection/db_connection/kafka/index.md#kafka) + +```py +from onetl.db import DBReader, DBWriter +from onetl.strategy import IncrementalStrategy + +reader = DBReader( + connection=kafka, + source="topic_name", + hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="offset"), +) + +writer = DBWriter(connection=hive, target="db.newtable") + +with IncrementalStrategy(): + df = reader.run() + +# current run will fetch only messages which were added since previous run +``` + +Incremental run with [File Downloader](../file/file_downloader/file_downloader.md#file-downloader) and `hwm=FileListHWM(...)` + +```py +from onetl.file import FileDownloader +from onetl.strategy import SnapshotStrategy +from etl_entities.hwm import FileListHWM + +downloader = FileDownloader( + connection=sftp, + source_path="/remote", + local_path="/local", + hwm=FileListHWM( # mandatory for IncrementalStrategy + name="my_unique_hwm_name", + ), +) + +with IncrementalStrategy(): + df = downloader.run() + +# current run will download only files which were added since previous run +``` + +Incremental run with [File Downloader](../file/file_downloader/file_downloader.md#file-downloader) and `hwm=FileModifiedTimeHWM(...)` + +```py +from onetl.file import FileDownloader +from onetl.strategy import SnapshotStrategy +from etl_entities.hwm import FileModifiedTimeHWM + +downloader = FileDownloader( + connection=sftp, + source_path="/remote", + local_path="/local", + hwm=FileModifiedTimeHWM( # mandatory for IncrementalStrategy + name="my_unique_hwm_name", + ), +) + +with IncrementalStrategy(): + df = downloader.run() + +# current run will download only files which were modified/created since previous run +``` + + + +#### \_\_init_\_(\*\*kwargs) + +Create a new model by parsing and validating input data from keyword arguments. + +Raises ValidationError if the input data cannot be parsed to form a valid model. + + diff --git a/mddocs/strategy/index.md b/mddocs/strategy/index.md new file mode 100644 index 000000000..7791fed74 --- /dev/null +++ b/mddocs/strategy/index.md @@ -0,0 +1,10 @@ + + +# Read Strategies + +onETL have several builtin strategies for reading data: + +1. [Snapshot Strategy](snapshot_strategy.md) +2. [Incremental Strategy](incremental_strategy.md) +3. [Snapshot Batch Strategy](snapshot_batch_strategy.md) +4. [Incremental Batch Strategy](incremental_batch_strategy.md) diff --git a/mddocs/strategy/snapshot_batch_strategy.md b/mddocs/strategy/snapshot_batch_strategy.md new file mode 100644 index 000000000..da95f8133 --- /dev/null +++ b/mddocs/strategy/snapshot_batch_strategy.md @@ -0,0 +1,281 @@ + + +# Snapshot Batch Strategy + +### *class* onetl.strategy.snapshot_strategy.SnapshotBatchStrategy(\*, hwm: HWM | None = None, step: Any = None, start: Any = None, stop: Any = None) + +Snapshot batch strategy for [DB Reader](../db/db_reader.md#db-reader). + +#### NOTE +Cannot be used with [File Downloader](../file/file_downloader/file_downloader.md#file-downloader) + +Same as [`SnapshotStrategy`](snapshot_strategy.md#onetl.strategy.snapshot_strategy.SnapshotStrategy), +but reads data from the source in sequential batches (1..N) like: + +```sql +1: SELECT id, data + FROM public.mydata + WHERE id >= 1000 AND id <= 1100; -- from start to start+step (INCLUDING first row) + +2: WHERE id > 1100 AND id <= 1200; -- + step +3: WHERE id > 1200 AND id <= 1200; -- + step +N: WHERE id > 1300 AND id <= 1400; -- until stop +``` + +This allows to use less CPU and RAM on Spark cluster than reading all the data in parallel, +but takes proportionally more time. + +#### NOTE +This strategy uses HWM column value to filter data for each batch, +but does **NOT** save it into [HWM Store](../hwm_store/index.md#hwm). +So every run starts from the beginning, not from the previous HWM value. + +#### NOTE +If you only need to reduce number of rows read by Spark from opened cursor, +use `onetl.connection.db_connection.postgres.Postgres.ReadOptions.fetchsize` instead + +#### WARNING +Not every [DB connection](../connection/db_connection/index.md#db-connections) +supports batch strategy. For example, Kafka connection doesn’t support it. +Make sure the connection you use is compatible with the SnapshotBatchStrategy. + +#### Versionadded +Added in version 0.1.0. + +* **Parameters:** + **step** + : Step size used for generating batch SQL queries like: + ```sql + SELECT id, data + FROM public.mydata + WHERE id >= 1000 AND id <= 1100; -- 1000 is start value, step is 100 + ``` +
+ #### NOTE + Step defines a range of values will be fetched by each batch. This is **not** + a number of rows, it depends on a table content and value distribution across the rows. +
+ #### NOTE + `step` value will be added to the HWM, so it should have a proper type. +
+ For example, for `TIMESTAMP` column `step` type should be `datetime.timedelta`, not `int` + + **start** + : If passed, the value will be used for generating WHERE clauses with `hwm.expression` filter, + as a start value for the first batch. +
+ If not set, the value is determined by a separated query: + ```sql + SELECT MIN(id) as start + FROM public.mydata + WHERE id <= 1400; -- 1400 here is stop value (if set) + ``` +
+ #### NOTE + `start` should be the same type as `hwm.expression` value, + e.g. `datetime.datetime` for `TIMESTAMP` column, `datetime.date` for `DATE`, and so on + + **stop** + : If passed, the value will be used for generating WHERE clauses with `hwm.expression` filter, + as a stop value for the last batch. +
+ If not set, the value is determined by a separated query: + ```sql + SELECT MAX(id) as stop + FROM public.mydata + WHERE id >= 1000; -- 1000 here is start value (if set) + ``` +
+ #### NOTE + `stop` should be the same type as `hwm.expression` value, + e.g. `datetime.datetime` for `TIMESTAMP` column, `datetime.date` for `DATE`, and so on + +### Examples + +SnapshotBatch run + +```python +from onetl.db import DBReader, DBWriter +from onetl.strategy import SnapshotBatchStrategy + +reader = DBReader( + connection=postgres, + source="public.mydata", + columns=["id", "data"], + hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="id"), +) + +writer = DBWriter(connection=hive, target="db.newtable") + +with SnapshotBatchStrategy(step=100) as batches: + for _ in batches: + df = reader.run() + writer.run(df) +``` + +```sql +-- get start and stop values + + SELECT MIN(id) as start, MAX(id) as stop + FROM public.mydata; + +-- for example, start=1000 and stop=2345 + +-- when each batch (1..N) will perform a query which return some part of input data + +1: SELECT id, data + FROM public.mydata + WHERE id >= 1000 AND id <= 1100; -- from start to start+step (INCLUDING first row) + +2: WHERE id > 1100 AND id <= 1200; -- + step +3: WHERE id > 1200 AND id <= 1300; -- + step +N: WHERE id > 2300 AND id <= 2345; -- until stop +``` + +SnapshotBatch run with `stop` value + +```python +... + +with SnapshotBatchStrategy(step=100, stop=1234) as batches: + for _ in batches: + df = reader.run() + writer.run(df) +``` + +```sql +-- stop value is set, so there is no need to fetch it from DB +-- get start value + + SELECT MIN(id) as start + FROM public.mydata + WHERE id <= 1234; -- until stop + +-- for example, start=1000. +-- when each batch (1..N) will perform a query which return some part of input data + +1: SELECT id, data + FROM public.mydata + WHERE id >= 1000 AND id <= 1100; -- from start to start+step (INCLUDING first row) + +2: WHERE id > 1100 AND id <= 1200; -- + step +3: WHERE id > 1200 AND id <= 1300; -- + step +N: WHERE id > 1300 AND id <= 1234; -- until stop +``` + +SnapshotBatch run with `start` value + +```python +... + +with SnapshotBatchStrategy(step=100, start=500) as batches: + for _ in batches: + df = reader.run() + writer.run(df) +``` + +```sql +-- start value is set, so there is no need to fetch it from DB +-- get only stop value + + SELECT MAX(id) as stop + FROM public.mydata + WHERE id >= 500; -- from start + +-- for example, stop=2345. +-- when each batch (1..N) will perform a query which return some part of input data + +1: SELECT id, data + FROM public.mydata + WHERE id >= 500 AND id <= 600; -- from start to start+step (INCLUDING first row) + +2: WHERE id > 600 AND id <= 700; -- + step +3: WHERE id > 700 AND id <= 800; -- + step +... +N: WHERE id > 2300 AND id <= 2345; -- until stop +``` + +SnapshotBatch run with all options + +```python +... + +with SnapshotBatchStrategy( + start=1000, + step=100, + stop=2000, +) as batches: + for _ in batches: + df = reader.run() + writer.run(df) +``` + +```sql +-- start and stop values are set, so no need to fetch boundaries from DB +-- each batch (1..N) will perform a query which return some part of input data + +1: SELECT id, data + FROM public.mydata + WHERE id >= 1000 AND id <= 1100; -- from start to start+step (INCLUDING first row) + +2: WHERE id > 1100 AND id <= 1200; -- + step +3: WHERE id > 1200 AND id <= 1300; -- + step +... +N: WHERE id > 1900 AND id <= 2000; -- until stop +``` + +SnapshotBatch run over non-integer column + +`hwm.expression`, `start` and `stop` can be a date or datetime, not only integer: + +```python +from datetime import date, timedelta + +reader = DBReader( + connection=postgres, + source="public.mydata", + columns=["business_dt", "data"], + hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="business_dt"), +) + +with SnapshotBatchStrategy( + start=date("2021-01-01"), + step=timedelta(days=5), + stop=date("2021-01-31"), +) as batches: + for _ in batches: + df = reader.run() + writer.run(df) +``` + +```sql +-- start and stop values are set, so no need to fetch boundaries from DB +-- each batch will perform a query which return some part of input data +-- HWM value will casted to match column type + +1: SELECT business_dt, data + FROM public.mydata + WHERE business_dt >= CAST('2020-01-01' AS DATE) -- from start to start+step (INCLUDING first row) + AND business_dt <= CAST('2021-01-05' AS DATE); + +2: WHERE business_dt > CAST('2021-01-05' AS DATE) -- + step + AND business_dt <= CAST('2021-01-10' AS DATE); + +3: WHERE business_dt > CAST('2021-01-10' AS DATE) -- + step + AND business_dt <= CAST('2021-01-15' AS DATE); + +... + +N: WHERE business_dt > CAST('2021-01-30' AS DATE) + AND business_dt <= CAST('2021-01-31' AS DATE); -- until stop +``` + + + +#### \_\_init_\_(\*\*kwargs) + +Create a new model by parsing and validating input data from keyword arguments. + +Raises ValidationError if the input data cannot be parsed to form a valid model. + + diff --git a/mddocs/strategy/snapshot_strategy.md b/mddocs/strategy/snapshot_strategy.md new file mode 100644 index 000000000..38ef7768c --- /dev/null +++ b/mddocs/strategy/snapshot_strategy.md @@ -0,0 +1,96 @@ + + +# Snapshot Strategy + +### *class* onetl.strategy.snapshot_strategy.SnapshotStrategy + +Snapshot strategy for [DB Reader](../db/db_reader.md#db-reader)/[File Downloader](../file/file_downloader/file_downloader.md#file-downloader). + +Used for fetching all the rows/files from a source. Does not support HWM. + +#### NOTE +This is a default strategy. + +For [DB Reader](../db/db_reader.md#db-reader): +: Every snapshot run is executing the simple query which fetches all the table data: +
+ ```sql + SELECT id, data FROM public.mydata; + ``` + +For [File Downloader](../file/file_downloader/file_downloader.md#file-downloader): +: Every snapshot run is downloading all the files (from the source, or user-defined list): +
+ ```bash + $ hdfs dfs -ls /path +
+ /path/my/file1 + /path/my/file2 + ``` +
+ ```python + DownloadResult( + ..., + successful={ + LocalFile("/downloaded/file1"), + LocalFile("/downloaded/file2"), + }, + ) + ``` + +#### Versionadded +Added in version 0.1.0. + +### Examples + +Snapshot run with [DB Reader](../db/db_reader.md#db-reader) + +```py +from onetl.db import DBReader, DBWriter +from onetl.strategy import SnapshotStrategy + +reader = DBReader( + connection=postgres, + source="public.mydata", + columns=["id", "data"], + hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="id"), +) + +writer = DBWriter(connection=hive, target="db.newtable") + +with SnapshotStrategy(): + df = reader.run() + writer.run(df) + +# current run will execute following query: + +# SELECT id, data FROM public.mydata; +``` + +Snapshot run with [File Downloader](../file/file_downloader/file_downloader.md#file-downloader) + +```py +from onetl.file import FileDownloader +from onetl.strategy import SnapshotStrategy + +downloader = FileDownloader( + connection=sftp, + source_path="/remote", + local_path="/local", +) + +with SnapshotStrategy(): + df = downloader.run() + +# current run will download all files from 'source_path' +``` + + + +#### \_\_init_\_(\*\*kwargs) + +Create a new model by parsing and validating input data from keyword arguments. + +Raises ValidationError if the input data cannot be parsed to form a valid model. + + diff --git a/mddocs/troubleshooting/index.md b/mddocs/troubleshooting/index.md new file mode 100644 index 000000000..847f426e9 --- /dev/null +++ b/mddocs/troubleshooting/index.md @@ -0,0 +1,17 @@ + + +# Troubleshooting + +In case of error please follow instructions below: + +* Read the logs or exception messages you’ve faced with. + : * If Python logs are note verbose enough, [increase the log level](../logging.md#logging). + * If Spark logs are note verbose enough, [increase the log level](spark.md#troubleshooting-spark). +* Read documentation related to a class or method you are using. +* [Google](https://google.com) the error message, and carefully read the search result: + : * [StackOverflow](https://stackoverflow.com/) answers. + * [Spark](https://spark.apache.org/docs/latest/) documentation. + * Documentation of database or filesystem you are connecting to. + * Documentation of underlying connector. +* Search for known [issues](https://github.com/MobileTeleSystems/onetl/issues), or create a new one. +* Always use the most resent versions of onETL, PySpark and connector packages, [compatible with your environment](../install/spark.md#install-spark). diff --git a/mddocs/troubleshooting/spark.md b/mddocs/troubleshooting/spark.md new file mode 100644 index 000000000..812b38acc --- /dev/null +++ b/mddocs/troubleshooting/spark.md @@ -0,0 +1,72 @@ + + +# Spark Troubleshooting + +## Restarting Spark session + +Sometimes it is required to stop current Spark session and start a new one, e.g. to add some .jar packages, or change session config. +But PySpark not only starts Spark session, but also starts Java virtual machine (JVM) process in the background, +which. So calling `sparkSession.stop()` [does not shutdown JVM](https://issues.apache.org/jira/browse/SPARK-47740), +and this can cause some issue. + +Also apart from JVM properties, stopping Spark session does not clear Spark context, which is a global object. So new +Spark sessions are created using the same context object, and thus using the same Spark config options. + +To properly stop Spark session, it is **required** to: +\* Stop Spark session by calling `sparkSession.stop()`. +\* **STOP PYTHON INTERPRETER**, e.g. by calling `sys.exit()`. +\* Start new Python interpreter. +\* Start new Spark session with config options you need. + +Skipping some of these steps can lead to issues with creating new Spark session. + +## Driver log level + +Default logging level for Spark session is `WARN`. To show more verbose logs, use: + +```python +spark.sparkContext.setLogLevel("INFO") +``` + +or increase verbosity even more: + +```python +spark.sparkContext.setLogLevel("DEBUG") +``` + +After getting all information you need, you can return back the previous log level: + +```python +spark.sparkContext.setLogLevel("WARN") +``` + +## Executors log level + +`sparkContext.setLogLevel` changes only log level of Spark session on Spark **driver**. +To make Spark executor logs more verbose, perform following steps: + +* Create `log4j.properties` file with content like this: + ```jproperties + log4j.rootCategory=DEBUG, console + + log4j.appender.console=org.apache.log4j.ConsoleAppender + log4j.appender.console.target=System.err + log4j.appender.console.layout=org.apache.log4j.PatternLayout + log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{1}: %m%n + ``` +* Stop existing Spark session and create a new one with following options: + ```python + from pyspark.sql import SparkSession + + spark = ( + SparkSesion.builder.config("spark.files", "file:log4j.properties").config( + "spark.executor.extraJavaOptions", "-Dlog4j.configuration=file:log4j.properties" + ) + # you can apply the same logging settings to Spark driver, by uncommenting the line below + # .config("spark.driver.extraJavaOptions", "-Dlog4j.configuration=file:log4j.properties") + .getOrCreate() + ) + ``` + +Each Spark executor will receive a copy of `log4j.properties` file during start, and load it to change own log level. +Same approach can be used for Spark driver as well, to investigate issue when Spark session cannot properly start. From edb8b002a160d40e2bd6005bd4a50284cb39606e Mon Sep 17 00:00:00 2001 From: Sattar Gyulmamedov <64004097+wm-wm-wm@users.noreply.github.com> Date: Tue, 15 Apr 2025 16:55:24 +0300 Subject: [PATCH 02/22] check markup in root before docstrings test --- mddocs/changelog.md | 3 +- mddocs/changelog/DRAFT.md | 1 + mddocs/changelog/NEXT_RELEASE.md | 1 + mddocs/changelog/index.md | 2 +- mddocs/concepts.md | 325 +++++++++++++++-------- mddocs/conf.py | 9 +- mddocs/connection/db_connection/index.md | 2 - mddocs/connection/index.md | 2 +- mddocs/contributing.md | 69 ++--- mddocs/db/index.md | 2 +- mddocs/file/index.md | 2 +- mddocs/file_df/index.md | 2 +- mddocs/index.md | 84 +++++- mddocs/install/index.md | 9 +- mddocs/logging.md | 105 ++++---- mddocs/plugins.md | 10 +- 16 files changed, 400 insertions(+), 228 deletions(-) diff --git a/mddocs/changelog.md b/mddocs/changelog.md index 4ba142635..278395a7f 100644 --- a/mddocs/changelog.md +++ b/mddocs/changelog.md @@ -1,5 +1,4 @@ -# Changelog - + # Changelog * [0.13.4 (2025-03-20)](changelog/0.13.4.md) diff --git a/mddocs/changelog/DRAFT.md b/mddocs/changelog/DRAFT.md index e69de29bb..5b77149b5 100644 --- a/mddocs/changelog/DRAFT.md +++ b/mddocs/changelog/DRAFT.md @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/mddocs/changelog/NEXT_RELEASE.md b/mddocs/changelog/NEXT_RELEASE.md index 8cf66aa33..10ebbf98b 100644 --- a/mddocs/changelog/NEXT_RELEASE.md +++ b/mddocs/changelog/NEXT_RELEASE.md @@ -1 +1,2 @@ + \ No newline at end of file diff --git a/mddocs/changelog/index.md b/mddocs/changelog/index.md index 37d5d85ae..4a2663aee 100644 --- a/mddocs/changelog/index.md +++ b/mddocs/changelog/index.md @@ -1,4 +1,4 @@ -Changelog +# Changelog * [0.13.4 (2025-03-20)](0.13.4.md) * [0.13.3 (2025-03-11)](0.13.3.md) diff --git a/mddocs/concepts.md b/mddocs/concepts.md index cf74e9eaa..352c45886 100644 --- a/mddocs/concepts.md +++ b/mddocs/concepts.md @@ -16,9 +16,9 @@ To create a connection to a specific storage type, you must use a class that mat from onetl.connection import SFTP sftp = SFTP( - host="sftp.test.com", - user="onetl", - password="onetl", + host="sftp.test.com", + user="onetl", + password="onetl", ) ``` @@ -26,6 +26,100 @@ All connection types are inherited from the parent class `BaseConnection`. ### Class diagram + + + + ### DBConnection Classes inherited from `DBConnection` could be used for accessing databases. @@ -36,15 +130,15 @@ A `DBConnection` could be instantiated as follows: from onetl.connection import MSSQL mssql = MSSQL( - host="mssqldb.demo.com", - user="onetl", - password="onetl", - database="Telecom", - spark=spark, + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, ) ``` -where **spark** is the current SparkSession. +where **spark** is the current SparkSession. `onETL` uses `Spark` and specific Java connectors under the hood to work with databases. For a description of other parameters, see the documentation for the [available DBConnections](connection/db_connection/index.md#db-connections). @@ -59,9 +153,9 @@ A `FileConnection` could be instantiated as follows: from onetl.connection import SFTP sftp = SFTP( - host="sftp.test.com", - user="onetl", - password="onetl", + host="sftp.test.com", + user="onetl", + password="onetl", ) ``` @@ -77,13 +171,13 @@ A `FileDFConnection` could be instantiated as follows: from onetl.connection import SparkHDFS spark_hdfs = SparkHDFS( - host="namenode1.domain.com", - cluster="mycluster", - spark=spark, + host="namenode1.domain.com", + cluster="mycluster", + spark=spark, ) ``` -where **spark** is the current SparkSession. +where **spark** is the current SparkSession. `onETL` uses `Spark` and specific Java connectors under the hood to work with DataFrames. For a description of other parameters, see the documentation for the [available FileDFConnections](connection/file_df_connection/index.md#file-df-connections). @@ -104,12 +198,12 @@ This method returns connection itself, so you can create connection and immediat ```Python mssql = MSSQL( - host="mssqldb.demo.com", - user="onetl", - password="onetl", - database="Telecom", - spark=spark, -).check() # <-- + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, +).check() # <-- ``` ## Extract/Load data @@ -134,17 +228,17 @@ All of these classes have a method `run()` that starts extracting/loading the da from onetl.db import DBReader, DBWriter reader = DBReader( - connection=mssql, - source="dbo.demo_table", - columns=["column_1", "column_2"], + connection=mssql, + source="dbo.demo_table", + columns=["column_1", "column_2"], ) # Read data as Spark DataFrame df = reader.run() db_writer = DBWriter( - connection=hive, - target="dl_sb.demo_table", + connection=hive, + target="dl_sb.demo_table", ) # Save Spark DataFrame to Hive table @@ -155,28 +249,28 @@ writer.run(df) To extract data you can use classes: -| | Use case | Connection | `run()` gets | `run()` returns | -|-------------------------------------------------------------------------|-------------------------------------------|------------------------------------------------------------------------------------|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------| -| [DBReader](db/db_reader.md#db-reader) | Reading data from a database | Any [DBConnection](connection/db_connection/index.md#db-connections) | - | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | -| [FileDFReader](file_df/file_df_reader/file_df_reader.md#file-df-reader) | Read data from a file or set of files | Any [FileDFConnection](connection/file_df_connection/index.md#file-df-connections) | No input, or List[File path on FileSystem] | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | -| [FileDownloader](db/db_reader.md#db-reader) | Download files from remote FS to local FS | Any [FileConnection](connection/file_connection/index.md#file-connections) | No input, or List[File path on remote FileSystem] | [DownloadResult](file/file_downloader/result.md#file-downloader-result) | +| | Use case | Connection | `run()` gets | `run()` returns | +|--|--|--|--|--| +| [DBReader](db/db_reader.md#db-reader) | Reading data from a database | Any [DBConnection](connection/db_connection/index.md#db-connections) | - | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | +| [FileDFReader](file_df/file_df_reader/file_df_reader.md#file-df-reader) | Read data from a file or set of files | Any [FileDFConnection](connection/file_df_connection/index.md#file-df-connections) | No input, or List[File path on FileSystem] | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | +| [FileDownloader](db/db_reader.md#db-reader) | Download files from remote FS to local FS | Any [FileConnection](connection/file_connection/index.md#file-connections) | No input, or List[File path on remote FileSystem] | [DownloadResult](file/file_downloader/result.md#file-downloader-result) | ### Load data To load data you can use classes: -| | Use case | Connection | `run()` gets | `run()` returns | -|-------------------------------------------------------------------|----------------------------------------------|------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------| -| [DBWriter](db/db_writer.md#db-writer) | Writing data from a DataFrame to a database | Any [DBConnection](connection/db_connection/index.md#db-connections) | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | None | -| [FileDFWriter](db/db_writer.md#db-writer) | Writing data from a DataFrame to a folder | Any [FileDFConnection](connection/file_df_connection/index.md#file-df-connections) | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | None | -| [FileUploader](file/file_uploader/file_uploader.md#file-uploader) | Uploading files from a local FS to remote FS | Any [FileConnection](connection/file_connection/index.md#file-connections) | List[File path on local FileSystem] | [UploadResult](file/file_uploader/result.md#file-uploader-result) | +| | Use case | Connection | `run()` gets | `run()` returns | +|--|--|--|--|--| +| [DBWriter](db/db_writer.md#db-writer) | Writing data from a DataFrame to a database | Any [DBConnection](connection/db_connection/index.md#db-connections) | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | None | +| [FileDFWriter](db/db_writer.md#db-writer) | Writing data from a DataFrame to a folder | Any [FileDFConnection](connection/file_df_connection/index.md#file-df-connections) | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | None | +| [FileUploader](file/file_uploader/file_uploader.md#file-uploader) | Uploading files from a local FS to remote FS | Any [FileConnection](connection/file_connection/index.md#file-connections) | List[File path on local FileSystem] | [UploadResult](file/file_uploader/result.md#file-uploader-result) | ### Manipulate data To manipulate data you can use classes: -| | Use case | Connection | `run()` gets | `run()` returns | -|-------------------------------------------------------|---------------------------------------------|----------------------------------------------------------------------------|--------------------------------------|-----------------------------------------------------------| +| | Use case | Connection | `run()` gets | `run()` returns | +|--|--|--|--|--| | [FileMover](file/file_mover/file_mover.md#file-mover) | Move files between directories in remote FS | Any [FileConnection](connection/file_connection/index.md#file-connections) | List[File path on remote FileSystem] | [MoveResult](file/file_mover/result.md#file-mover-result) | ### Options @@ -185,88 +279,89 @@ Extract and load classes have a `options` parameter, which has a special meaning > * all other parameters - *WHAT* we extract / *WHERE* we load to > * `options` parameter - *HOW* we extract/load data + ```python db_reader = DBReader( - # WHAT do we read: - connection=mssql, - source="dbo.demo_table", # some table from MSSQL - columns=["column_1", "column_2"], # but only specific set of columns - where="column_2 > 1000", # only rows matching the clause - # HOW do we read: - options=MSSQL.ReadOptions( - numPartitions=10, # read in 10 parallel jobs - partitionColumn="id", # balance data read by assigning each job a part of data using `hash(id) mod N` expression - partitioningMode="hash", - fetchsize=1000, # each job will fetch block of 1000 rows each on every read attempt - ), + # WHAT do we read: + connection=mssql, + source="dbo.demo_table", # some table from MSSQL + columns=["column_1", "column_2"], # but only specific set of columns + where="column_2 > 1000", # only rows matching the clause + # HOW do we read: + options=MSSQL.ReadOptions( + numPartitions=10, # read in 10 parallel jobs + partitionColumn="id", # balance data read by assigning each job a part of data using `hash(id) mod N` expression + partitioningMode="hash", + fetchsize=1000, # each job will fetch block of 1000 rows each on every read attempt + ), ) db_writer = DBWriter( - # WHERE do we write to - to some table in Hive - connection=hive, - target="dl_sb.demo_table", - # HOW do we write - overwrite all the data in the existing table - options=Hive.WriteOptions(if_exists="replace_entire_table"), + # WHERE do we write to - to some table in Hive + connection=hive, + target="dl_sb.demo_table", + # HOW do we write - overwrite all the data in the existing table + options=Hive.WriteOptions(if_exists="replace_entire_table"), ) file_downloader = FileDownloader( - # WHAT do we download - files from some dir in SFTP - connection=sftp, - source_path="/source", - filters=[Glob("*.csv")], # only CSV files - limits=[MaxFilesCount(1000)], # 1000 files max - # WHERE do we download to - a specific dir on local FS - local_path="/some", - # HOW do we download: - options=FileDownloader.Options( - delete_source=True, # after downloading each file remove it from source_path - if_exists="replace_file", # replace existing files in the local_path - ), + # WHAT do we download - files from some dir in SFTP + connection=sftp, + source_path="/source", + filters=[Glob("*.csv")], # only CSV files + limits=[MaxFilesCount(1000)], # 1000 files max + # WHERE do we download to - a specific dir on local FS + local_path="/some", + # HOW do we download: + options=FileDownloader.Options( + delete_source=True, # after downloading each file remove it from source_path + if_exists="replace_file", # replace existing files in the local_path + ), ) file_uploader = FileUploader( - # WHAT do we upload - files from some local dir - local_path="/source", - # WHERE do we upload to- specific remote dir in HDFS - connection=hdfs, - target_path="/some", - # HOW do we upload: - options=FileUploader.Options( - delete_local=True, # after uploading each file remove it from local_path - if_exists="replace_file", # replace existing files in the target_path - ), + # WHAT do we upload - files from some local dir + local_path="/source", + # WHERE do we upload to- specific remote dir in HDFS + connection=hdfs, + target_path="/some", + # HOW do we upload: + options=FileUploader.Options( + delete_local=True, # after uploading each file remove it from local_path + if_exists="replace_file", # replace existing files in the target_path + ), ) file_mover = FileMover( - # WHAT do we move - files in some remote dir in HDFS - source_path="/source", - connection=hdfs, - # WHERE do we move files to - target_path="/some", # a specific remote dir within the same HDFS connection - # HOW do we load - replace existing files in the target_path - options=FileMover.Options(if_exists="replace_file"), + # WHAT do we move - files in some remote dir in HDFS + source_path="/source", + connection=hdfs, + # WHERE do we move files to + target_path="/some", # a specific remote dir within the same HDFS connection + # HOW do we load - replace existing files in the target_path + options=FileMover.Options(if_exists="replace_file"), ) file_df_reader = FileDFReader( - # WHAT do we read - *.csv files from some dir in S3 - connection=s3, - source_path="/source", - file_format=CSV(), - # HOW do we read - load files from /source/*.csv, not from /source/nested/*.csv - options=FileDFReader.Options(recursive=False), + # WHAT do we read - *.csv files from some dir in S3 + connection=s3, + source_path="/source", + file_format=CSV(), + # HOW do we read - load files from /source/*.csv, not from /source/nested/*.csv + options=FileDFReader.Options(recursive=False), ) file_df_writer = FileDFWriter( - # WHERE do we write to - as .csv files in some dir in S3 - connection=s3, - target_path="/target", - file_format=CSV(), - # HOW do we write - replace all existing files in /target, if exists - options=FileDFWriter.Options(if_exists="replace_entire_directory"), + # WHERE do we write to - as .csv files in some dir in S3 + connection=s3, + target_path="/target", + file_format=CSV(), + # HOW do we write - replace all existing files in /target, if exists + options=FileDFWriter.Options(if_exists="replace_entire_directory"), ) ``` -More information about `options` could be found on [DB connection](connection/db_connection/index.md#db-connections). and +More information about `options` could be found on [DB connection](connection/db_connection/index.md#db-connections) and [File Downloader](file/file_downloader/file_downloader.md#file-downloader) / [File Uploader](file/file_uploader/file_uploader.md#file-uploader) / [File Mover](file/file_mover/file_mover.md#file-mover) / [FileDF Reader](file_df/file_df_reader/file_df_reader.md#file-df-reader) / [FileDF Writer](file_df/file_df_writer/file_df_writer.md#file-df-writer) documentation ### Read Strategies @@ -284,21 +379,21 @@ For example, an incremental strategy allows you to get only new data from the ta from onetl.strategy import IncrementalStrategy reader = DBReader( - connection=mssql, - source="dbo.demo_table", - hwm_column="id", # detect new data based on value of "id" column + connection=mssql, + source="dbo.demo_table", + hwm_column="id", # detect new data based on value of "id" column ) # first run with IncrementalStrategy(): - df = reader.run() + df = reader.run() sleep(3600) # second run with IncrementalStrategy(): - # only rows, that appeared in the source since previous run - df = reader.run() + # only rows, that appeared in the source since previous run + df = reader.run() ``` or get only files which were not downloaded before: @@ -307,22 +402,22 @@ or get only files which were not downloaded before: from onetl.strategy import IncrementalStrategy file_downloader = FileDownloader( - connection=sftp, - source_path="/remote", - local_path="/local", - hwm_type="file_list", # save all downloaded files to a list, and exclude files already present in this list + connection=sftp, + source_path="/remote", + local_path="/local", + hwm_type="file_list", # save all downloaded files to a list, and exclude files already present in this list ) # first run with IncrementalStrategy(): - files = file_downloader.run() + files = file_downloader.run() sleep(3600) # second run with IncrementalStrategy(): - # only files, that appeared in the source since previous run - files = file_downloader.run() + # only files, that appeared in the source since previous run + files = file_downloader.run() ``` Most of strategies are based on [HWM](hwm_store/index.md#hwm), Please check each strategy documentation for more details @@ -332,9 +427,9 @@ Most of strategies are based on [HWM](hwm_store/index.md#hwm), Please check each Connections are very simple, they have only a set of some basic operations, like `mkdir`, `remove_file`, `get_table_schema`, and so on. -High-level operations, like -: * [Read Strategies](strategy/index.md#strategy) support - * Handling metadata push/pull - * Handling different options, like `if_exists="replace_file"` in case of file download/upload +High-level operations, like: + * [Read Strategies](strategy/index.md#strategy) support + * Handling metadata push/pull + * Handling different options, like `if_exists="replace_file"` in case of file download/upload is moved to a separate class which calls the connection object methods to perform some complex logic. diff --git a/mddocs/conf.py b/mddocs/conf.py index c69e54521..bb6508a95 100644 --- a/mddocs/conf.py +++ b/mddocs/conf.py @@ -60,9 +60,16 @@ "sphinx_favicon", "sphinxcontrib.autodoc_pydantic", "sphinx_last_updated_by_git", - "sphinx_markdown_builder", + # "sphinx_markdown_builder", "myst_parser", ] + +myst_enable_extensions = [ + 'colon_fence', + 'attrs_block', + # ... other extensions +] + numpydoc_show_class_members = False autodoc_pydantic_model_show_config = False autodoc_pydantic_model_show_config_summary = False diff --git a/mddocs/connection/db_connection/index.md b/mddocs/connection/db_connection/index.md index cd8f76477..6bb1ad53b 100644 --- a/mddocs/connection/db_connection/index.md +++ b/mddocs/connection/db_connection/index.md @@ -2,8 +2,6 @@ # DB Connections -# DB Connections - * [Clickhouse](clickhouse/index.md) * [Greenplum](greenplum/index.md) * [Kafka](kafka/index.md) diff --git a/mddocs/connection/index.md b/mddocs/connection/index.md index 5ad28edef..9775b0ccf 100644 --- a/mddocs/connection/index.md +++ b/mddocs/connection/index.md @@ -1,6 +1,6 @@ - DB Connection +# DB Connection * [DB Connections](db_connection/index.md) * [Clickhouse](db_connection/clickhouse/index.md) diff --git a/mddocs/contributing.md b/mddocs/contributing.md index 739798897..8cb8a1744 100644 --- a/mddocs/contributing.md +++ b/mddocs/contributing.md @@ -122,9 +122,7 @@ docker-compose run --rm onetl ./run_tests.sh -m mongodb -lsx -vvvv --log-cli-lev You can run interactive bash session and use it: ```bash -docker-compose run --rm onetl bash - -./run_tests.sh -m mongodb -lsx -vvvv --log-cli-level=INFO +docker-compose run --rm onetl bash ./run_tests.sh -m mongodb -lsx -vvvv --log-cli-level=INFO ``` See logs of test container: @@ -141,27 +139,30 @@ docker-compose --profile all down -v #### Without docker-compose -#### WARNING +```{admonition} warning To run HDFS tests locally you should add the following line to your `/etc/hosts` (file path depends on OS): +``` ```default # HDFS server returns container hostname as connection address, causing error in DNS resolution 127.0.0.1 hdfs ``` -#### NOTE +```{admonition} note To run Oracle tests you need to install [Oracle instantclient](https://www.oracle.com/database/technologies/instant-client.html), and pass its path to `ONETL_ORA_CLIENT_PATH` and `LD_LIBRARY_PATH` environment variables, e.g. `ONETL_ORA_CLIENT_PATH=/path/to/client64/lib`. It may also require to add the same path into `LD_LIBRARY_PATH` environment variable +``` -#### NOTE +```{admonition} note To run Greenplum tests, you should: * Download [VMware Greenplum connector for Spark](https://onetl.readthedocs.io/en/latest/connection/db_connection/greenplum/prerequisites.html) * Either move it to `~/.ivy2/jars/`, or pass file path to `CLASSPATH` * Set environment variable `ONETL_GP_PACKAGE_VERSION=local`. +``` Start all containers with dependencies: @@ -212,17 +213,11 @@ Then open in browser `docs/_build/index.html`. ## Review process -Please create a new GitHub issue for any significant changes and -enhancements that you wish to make. Provide the feature you would like -to see, why you need it, and how it will work. Discuss your ideas -transparently and get community feedback before proceeding. +Please create a new GitHub issue for any significant changes and enhancements that you wish to make. Provide the feature you would like to see, why you need it, and how it will work. Discuss your ideas transparently and get community feedback before proceeding. -Significant Changes that you wish to contribute to the project should be -discussed first in a GitHub issue that clearly outlines the changes and -benefits of the feature. +Significant Changes that you wish to contribute to the project should be discussed first in a GitHub issue that clearly outlines the changes and benefits of the feature. -Small Changes can directly be crafted and submitted to the GitHub -Repository as a Pull Request. +Small Changes can directly be crafted and submitted to the GitHub Repository as a Pull Request. ### Create pull request @@ -243,35 +238,15 @@ After pull request is created, it get a corresponding number, e.g. 123 (`pr_numb `onETL` uses [towncrier](https://pypi.org/project/towncrier/) for changelog management. -To submit a change note about your PR, add a text file into the -[docs/changelog/next_release](./next_release) folder. It should contain an -explanation of what applying this PR will change in the way -end-users interact with the project. One sentence is usually -enough but feel free to add as many details as you feel necessary -for the users to understand what it means. - -**Use the past tense** for the text in your fragment because, -combined with others, it will be a part of the “news digest” -telling the readers **what changed** in a specific version of -the library *since the previous version*. - -You should also use -reStructuredText syntax for highlighting code (inline or block), -linking parts of the docs or external sites. -If you wish to sign your change, feel free to add `-- by -:user:`github-username`` at the end (replace `github-username` -with your own!). - -Finally, name your file following the convention that Towncrier -understands: it should start with the number of an issue or a -PR followed by a dot, then add a patch type, like `feature`, -`doc`, `misc` etc., and add `.rst` as a suffix. If you -need to add more than one fragment, you may add an optional -sequence number (delimited with another period) between the type -and the suffix. - -In general the name will follow `..rst` pattern, -where the categories are: +To submit a change note about your PR, add a text file into the [docs/changelog/next_release](./next_release) folder. It should contain an explanation of what applying this PR will change in the way end-users interact with the project. One sentence is usually enough but feel free to add as many details as you feel necessary for the users to understand what it means. + +**Use the past tense** for the text in your fragment because, combined with others, it will be a part of the “news digest” telling the readers **what changed** in a specific version of the library *since the previous version*. + +You should also use reStructuredText syntax for highlighting code (inline or block), linking parts of the docs or external sites. If you wish to sign your change, feel free to add `-- by :user:`github-username`` at the end (replace `github-username` with your own!). + +Finally, name your file following the convention that Towncrier understands: it should start with the number of an issue or a PR followed by a dot, then add a patch type, like `feature`, `doc`, `misc` etc., and add `.rst` as a suffix. If you need to add more than one fragment, you may add an optional sequence number (delimited with another period) between the type and the suffix. + +In general the name will follow `..rst` pattern, where the categories are: - `feature`: Any new feature - `bugfix`: A bug fix @@ -280,11 +255,7 @@ where the categories are: - `dependency`: Dependency-related changes - `misc`: Changes internal to the repo like CI, test and build changes -A pull request may have more than one of these components, for example -a code change may introduce a new feature that deprecates an old -feature, in which case two fragments should be added. It is not -necessary to make a separate documentation fragment for documentation -changes accompanying the relevant code changes. +A pull request may have more than one of these components, for example a code change may introduce a new feature that deprecates an old feature, in which case two fragments should be added. It is not necessary to make a separate documentation fragment for documentation changes accompanying the relevant code changes. #### Examples for adding changelog entries to your Pull Requests diff --git a/mddocs/db/index.md b/mddocs/db/index.md index 861a539ff..ef8f4ab30 100644 --- a/mddocs/db/index.md +++ b/mddocs/db/index.md @@ -1,6 +1,6 @@ - DB classes +# DB classes * [DB Reader](db_reader.md) * [DB Writer](db_writer.md) diff --git a/mddocs/file/index.md b/mddocs/file/index.md index 2da812bab..675648e5c 100644 --- a/mddocs/file/index.md +++ b/mddocs/file/index.md @@ -1,6 +1,6 @@ - File classes +# File classes * [File Downloader](file_downloader/index.md) * [File Uploader](file_uploader/index.md) diff --git a/mddocs/file_df/index.md b/mddocs/file_df/index.md index c225567d6..a0868372e 100644 --- a/mddocs/file_df/index.md +++ b/mddocs/file_df/index.md @@ -1,6 +1,6 @@ - File DataFrame classes +# File DataFrame classes * [FileDF Reader](file_df_reader/index.md) * [FileDF Writer](file_df_writer/index.md) diff --git a/mddocs/index.md b/mddocs/index.md index ebfe9116e..7595bd92c 100644 --- a/mddocs/index.md +++ b/mddocs/index.md @@ -6,4 +6,86 @@ ```{include} README.md :start-after: -``` \ No newline at end of file +:end-before: +``` + diff --git a/mddocs/install/index.md b/mddocs/install/index.md index 1a5fb63cc..15f1e36e2 100644 --- a/mddocs/install/index.md +++ b/mddocs/install/index.md @@ -16,11 +16,14 @@ It can be installed via: pip install onetl ``` -#### WARNING +```{admonition} warning + +:class: warning + This method does NOT include any connections. -This method is recommended for use in third-party libraries which require for `onetl` to be installed, -but do not use its connection classes. +This method is recommended for use in third-party libraries which require for `onetl` to be installed, but do not use its connection classes. +``` ## Installation in details diff --git a/mddocs/logging.md b/mddocs/logging.md index f9900f789..c67da0558 100644 --- a/mddocs/logging.md +++ b/mddocs/logging.md @@ -1,14 +1,12 @@ - - # Logging + + Logging is quite important to understand what’s going on under the hood of onETL. -Default logging level for Python interpreters is `WARNING`, -but most of onETL logs are in `INFO` level, so users usually don’t see much. +Default logging level for Python interpreters is `WARNING`, but most of onETL logs are in `INFO` level, so users usually don’t see much. -To change logging level, there is a function [`setup_logging`](#onetl.log.setup_logging) -which should be called at the top of the script: +To change logging level, there is a function [`setup_logging`](#setup_logging) which should be called at the top of the script: ```python from onetl.log import setup_logging @@ -22,7 +20,7 @@ setup_logging() This changes both log level and log formatting to something like this: -### See logs +## See logs ```text 2024-04-12 10:12:10,834 [INFO ] MainThread: |onETL| Using IncrementalStrategy as a strategy @@ -152,85 +150,100 @@ setup_logging(level="DEBUG", enable_clients=True) This also changes log level for all underlying Python libraries, e.g. showing each HTTP request being made, and so on. -### onetl.log.setup_logging(level: int | str = 20, enable_clients: bool = False) → None + +**onetl.log.setup_logging**(level: int | str = 20, enable_clients: bool = False) → None Set up onETL logging. What this function does: -: * Adds stderr logging handler - * Changes root logger format to `2023-05-31 11:22:33.456 [INFO] MainThread: message` - * Changes root logger level to `level` - * Changes onETL logger level to `level` - * Sets up logging level of underlying client modules -#### NOTE +* Adds stderr logging handler +* Changes root logger format to `2023-05-31 11:22:33.456 [INFO] MainThread: message` +* Changes root logger level to `level` +* Changes onETL logger level to `level` +* Sets up logging level of underlying client modules + + + +```{admonition} note Should be used only in IDEs (like Jupyter notebooks or PyCharm), or scripts (ETL pipelines). +``` + + + -#### Versionchanged +````{versionchanged} Changed in version 0.5.0: Renamed `setup_notebook_logging` → `setup_logging` * **Parameters:** **level** - : Log level for onETL module + Log level for onETL module **enable_clients** - : If `True`, enable logging of underlying client modules. + If `True`, enable logging of underlying client modules. Otherwise, set client modules log level to `DISABLED`. -
- #### NOTE - For `level="DEBUG"` it is recommended to use `enable_clients=True` -
- #### Versionadded - Added in version 0.9.0. +```{admonition} note +For `level="DEBUG"` it is recommended to use `enable_clients=True` +``` + +```{versionadded} +Added in version 0.9.0. +``` +```` -### onetl.log.setup_clients_logging(level: int | str = 9999) → None +**onetl.log.setup_clients_logging**(level: int | str = 9999) → None Set logging of underlying client modules used by onETL. Affected modules: -: * `ftputil` - * `hdfs` - * `minio` - * `paramiko` - * `py4j` - * `pyspark` - * `webdav3` - -#### NOTE + +* `ftputil` +* `hdfs` +* `minio` +* `paramiko` +* `py4j` +* `pyspark` +* `webdav3` + +```{admonition} note Can be used in applications, but it is recommended to set up these loggers according to your framework documentation. +``` -#### Versionchanged +````{versionchanged} Changed in version 0.9.0: Renamed `disable_clients_logging` → `setup_clients_logging` * **Parameters:** **level** - : Log level for client modules -
- #### NOTE - For `py4j`, logging level with maximum verbosity is `INFO` because `DEBUG` logs are - totally unreadable. + Log level for client modules
- #### Versionadded - Added in version 0.9.0. - +```{admonition} note +For `py4j`, logging level with maximum verbosity is `INFO` because `DEBUG` logs are +totally unreadable. +``` +
+```{versionadded} +Added in version 0.9.0. +``` +```` -### onetl.log.set_default_logging_format() → None +**onetl.log.set_default_logging_format()** → None Sets default logging format to preferred by onETL. Example log message: `2023-05-31 11:22:33.456 [INFO] MainThread: message` -#### NOTE +```{admonition} note Should be used only in IDEs (like Jupyter notebooks or PyCharm), or scripts (ETL pipelines). +``` -#### WARNING +```{admonition} warning Should **NOT** be used in applications, you should set up logging settings manually, according to your framework documentation. - +``` diff --git a/mddocs/plugins.md b/mddocs/plugins.md index d42ded034..ca50f4a84 100644 --- a/mddocs/plugins.md +++ b/mddocs/plugins.md @@ -1,9 +1,10 @@ - - # Plugins -#### Versionadded + + +```{versionadded} Added in version 0.6.0. +``` ## What are plugins? @@ -78,8 +79,9 @@ like [@hook decorator](hooks/hook.md#hook-decorator), it will be executed during ## How to enable/disable plugins? -#### Versionadded +```{versionadded} Added in version 0.7.0. +``` ### Disable/enable all plugins From c2bff695cb02c21d24adb3cfc36be83f443c142a Mon Sep 17 00:00:00 2001 From: Sattar Gyulmamedov Date: Mon, 21 Apr 2025 23:15:51 +0300 Subject: [PATCH 03/22] experiments with uml --- mddocs/concepts.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/mddocs/concepts.md b/mddocs/concepts.md index cf74e9eaa..2e42e2e1c 100644 --- a/mddocs/concepts.md +++ b/mddocs/concepts.md @@ -26,6 +26,14 @@ All connection types are inherited from the parent class `BaseConnection`. ### Class diagram +```{uml} +...uml:: + +@startuml +Bob -> Cat +@enduml +``` + ### DBConnection Classes inherited from `DBConnection` could be used for accessing databases. From 6b786ccc22e49b50840df33bd37f8932346adad6 Mon Sep 17 00:00:00 2001 From: Sattar Gyulmamedov Date: Sat, 7 Jun 2025 15:50:34 +0300 Subject: [PATCH 04/22] experiment --- mddocs/README.md | 657 ------------------ mddocs/_static/icon.svg | 11 - mddocs/_static/logo.svg | 214 ------ mddocs/_static/logo_wide.svg | 329 --------- mddocs/changelog/DRAFT.md | 1 - mddocs/concepts.md | 349 ---------- mddocs/conf.py | 157 ----- mddocs/doctrees/changelog.doctree | Bin 0 -> 3515 bytes mddocs/doctrees/changelog/0.10.0.doctree | Bin 0 -> 89742 bytes mddocs/doctrees/changelog/0.10.1.doctree | Bin 0 -> 14304 bytes mddocs/doctrees/changelog/0.10.2.doctree | Bin 0 -> 17387 bytes mddocs/doctrees/changelog/0.11.0.doctree | Bin 0 -> 61627 bytes mddocs/doctrees/changelog/0.11.1.doctree | Bin 0 -> 5900 bytes mddocs/doctrees/changelog/0.11.2.doctree | Bin 0 -> 4295 bytes mddocs/doctrees/changelog/0.12.0.doctree | Bin 0 -> 22539 bytes mddocs/doctrees/changelog/0.12.1.doctree | Bin 0 -> 9253 bytes mddocs/doctrees/changelog/0.12.2.doctree | Bin 0 -> 8634 bytes mddocs/doctrees/changelog/0.12.3.doctree | Bin 0 -> 4340 bytes mddocs/doctrees/changelog/0.12.4.doctree | Bin 0 -> 4559 bytes mddocs/doctrees/changelog/0.12.5.doctree | Bin 0 -> 8481 bytes mddocs/doctrees/changelog/0.13.0.doctree | Bin 0 -> 53222 bytes mddocs/doctrees/changelog/0.13.1.doctree | Bin 0 -> 5184 bytes mddocs/doctrees/changelog/0.13.3.doctree | Bin 0 -> 4189 bytes mddocs/doctrees/changelog/0.13.4.doctree | Bin 0 -> 8204 bytes mddocs/doctrees/changelog/0.7.0.doctree | Bin 0 -> 56131 bytes mddocs/doctrees/changelog/0.7.1.doctree | Bin 0 -> 8224 bytes mddocs/doctrees/changelog/0.7.2.doctree | Bin 0 -> 9606 bytes mddocs/doctrees/changelog/0.8.0.doctree | Bin 0 -> 43955 bytes mddocs/doctrees/changelog/0.8.1.doctree | Bin 0 -> 15137 bytes mddocs/doctrees/changelog/0.9.0.doctree | Bin 0 -> 52924 bytes mddocs/doctrees/changelog/0.9.1.doctree | Bin 0 -> 4996 bytes mddocs/doctrees/changelog/0.9.2.doctree | Bin 0 -> 11096 bytes mddocs/doctrees/changelog/0.9.3.doctree | Bin 0 -> 3725 bytes mddocs/doctrees/changelog/0.9.4.doctree | Bin 0 -> 14841 bytes mddocs/doctrees/changelog/0.9.5.doctree | Bin 0 -> 7079 bytes mddocs/doctrees/changelog/DRAFT.doctree | Bin 0 -> 2914 bytes .../doctrees/changelog/NEXT_RELEASE.doctree | Bin 0 -> 3107 bytes mddocs/doctrees/changelog/index.doctree | Bin 0 -> 3923 bytes mddocs/doctrees/concepts.doctree | Bin 0 -> 70091 bytes .../clickhouse/connection.doctree | Bin 0 -> 47193 bytes .../db_connection/clickhouse/execute.doctree | Bin 0 -> 47822 bytes .../db_connection/clickhouse/index.doctree | Bin 0 -> 5083 bytes .../clickhouse/prerequisites.doctree | Bin 0 -> 16167 bytes .../db_connection/clickhouse/read.doctree | Bin 0 -> 82617 bytes .../db_connection/clickhouse/sql.doctree | Bin 0 -> 49343 bytes .../db_connection/clickhouse/types.doctree | Bin 0 -> 121722 bytes .../db_connection/clickhouse/write.doctree | Bin 0 -> 58118 bytes .../greenplum/connection.doctree | Bin 0 -> 48986 bytes .../db_connection/greenplum/execute.doctree | Bin 0 -> 50314 bytes .../db_connection/greenplum/index.doctree | Bin 0 -> 5016 bytes .../greenplum/prerequisites.doctree | Bin 0 -> 75612 bytes .../db_connection/greenplum/read.doctree | Bin 0 -> 83259 bytes .../db_connection/greenplum/types.doctree | Bin 0 -> 91709 bytes .../db_connection/greenplum/write.doctree | Bin 0 -> 50996 bytes .../db_connection/hive/connection.doctree | Bin 0 -> 26332 bytes .../db_connection/hive/execute.doctree | Bin 0 -> 15215 bytes .../db_connection/hive/index.doctree | Bin 0 -> 4974 bytes .../db_connection/hive/prerequisites.doctree | Bin 0 -> 21586 bytes .../db_connection/hive/read.doctree | Bin 0 -> 23064 bytes .../db_connection/hive/slots.doctree | Bin 0 -> 26608 bytes .../connection/db_connection/hive/sql.doctree | Bin 0 -> 23281 bytes .../db_connection/hive/write.doctree | Bin 0 -> 145975 bytes .../connection/db_connection/index.doctree | Bin 0 -> 4613 bytes .../db_connection/kafka/auth.doctree | Bin 0 -> 16696 bytes .../db_connection/kafka/basic_auth.doctree | Bin 0 -> 23699 bytes .../db_connection/kafka/connection.doctree | Bin 0 -> 56977 bytes .../db_connection/kafka/index.doctree | Bin 0 -> 5696 bytes .../db_connection/kafka/kerberos_auth.doctree | Bin 0 -> 43862 bytes .../kafka/plaintext_protocol.doctree | Bin 0 -> 18618 bytes .../db_connection/kafka/prerequisites.doctree | Bin 0 -> 25211 bytes .../db_connection/kafka/protocol.doctree | Bin 0 -> 16936 bytes .../db_connection/kafka/read.doctree | Bin 0 -> 34544 bytes .../db_connection/kafka/scram_auth.doctree | Bin 0 -> 27927 bytes .../db_connection/kafka/slots.doctree | Bin 0 -> 36192 bytes .../db_connection/kafka/ssl_protocol.doctree | Bin 0 -> 59151 bytes .../kafka/troubleshooting.doctree | Bin 0 -> 5030 bytes .../db_connection/kafka/write.doctree | Bin 0 -> 27780 bytes .../db_connection/mongodb/connection.doctree | Bin 0 -> 43670 bytes .../db_connection/mongodb/index.doctree | Bin 0 -> 4983 bytes .../db_connection/mongodb/pipeline.doctree | Bin 0 -> 36769 bytes .../mongodb/prerequisites.doctree | Bin 0 -> 15809 bytes .../db_connection/mongodb/read.doctree | Bin 0 -> 26313 bytes .../db_connection/mongodb/types.doctree | Bin 0 -> 56505 bytes .../db_connection/mongodb/write.doctree | Bin 0 -> 34686 bytes .../db_connection/mssql/connection.doctree | Bin 0 -> 47337 bytes .../db_connection/mssql/execute.doctree | Bin 0 -> 45339 bytes .../db_connection/mssql/index.doctree | Bin 0 -> 4993 bytes .../db_connection/mssql/prerequisites.doctree | Bin 0 -> 18921 bytes .../db_connection/mssql/read.doctree | Bin 0 -> 81749 bytes .../db_connection/mssql/sql.doctree | Bin 0 -> 48558 bytes .../db_connection/mssql/types.doctree | Bin 0 -> 95410 bytes .../db_connection/mssql/write.doctree | Bin 0 -> 57255 bytes .../db_connection/mysql/connection.doctree | Bin 0 -> 39396 bytes .../db_connection/mysql/execute.doctree | Bin 0 -> 49720 bytes .../db_connection/mysql/index.doctree | Bin 0 -> 4993 bytes .../db_connection/mysql/prerequisites.doctree | Bin 0 -> 14364 bytes .../db_connection/mysql/read.doctree | Bin 0 -> 90065 bytes .../db_connection/mysql/sql.doctree | Bin 0 -> 48866 bytes .../db_connection/mysql/types.doctree | Bin 0 -> 90824 bytes .../db_connection/mysql/write.doctree | Bin 0 -> 57493 bytes .../db_connection/oracle/connection.doctree | Bin 0 -> 47667 bytes .../db_connection/oracle/execute.doctree | Bin 0 -> 50314 bytes .../db_connection/oracle/index.doctree | Bin 0 -> 5011 bytes .../oracle/prerequisites.doctree | Bin 0 -> 21687 bytes .../db_connection/oracle/read.doctree | Bin 0 -> 90393 bytes .../db_connection/oracle/sql.doctree | Bin 0 -> 49041 bytes .../db_connection/oracle/types.doctree | Bin 0 -> 90841 bytes .../db_connection/oracle/write.doctree | Bin 0 -> 57375 bytes .../db_connection/postgres/connection.doctree | Bin 0 -> 38526 bytes .../db_connection/postgres/execute.doctree | Bin 0 -> 49948 bytes .../db_connection/postgres/index.doctree | Bin 0 -> 5047 bytes .../postgres/prerequisites.doctree | Bin 0 -> 15639 bytes .../db_connection/postgres/read.doctree | Bin 0 -> 90036 bytes .../db_connection/postgres/sql.doctree | Bin 0 -> 49029 bytes .../db_connection/postgres/types.doctree | Bin 0 -> 113678 bytes .../db_connection/postgres/write.doctree | Bin 0 -> 57630 bytes .../db_connection/teradata/connection.doctree | Bin 0 -> 44686 bytes .../db_connection/teradata/execute.doctree | Bin 0 -> 50051 bytes .../db_connection/teradata/index.doctree | Bin 0 -> 4805 bytes .../teradata/prerequisites.doctree | Bin 0 -> 13774 bytes .../db_connection/teradata/read.doctree | Bin 0 -> 97368 bytes .../db_connection/teradata/sql.doctree | Bin 0 -> 49004 bytes .../db_connection/teradata/write.doctree | Bin 0 -> 74219 bytes .../connection/file_connection/ftp.doctree | Bin 0 -> 201643 bytes .../connection/file_connection/ftps.doctree | Bin 0 -> 201889 bytes .../file_connection/hdfs/connection.doctree | Bin 0 -> 233577 bytes .../file_connection/hdfs/index.doctree | Bin 0 -> 4566 bytes .../file_connection/hdfs/slots.doctree | Bin 0 -> 65920 bytes .../connection/file_connection/index.doctree | Bin 0 -> 4404 bytes .../connection/file_connection/s3.doctree | Bin 0 -> 195366 bytes .../connection/file_connection/samba.doctree | Bin 0 -> 175360 bytes .../connection/file_connection/sftp.doctree | Bin 0 -> 210343 bytes .../connection/file_connection/webdav.doctree | Bin 0 -> 195091 bytes .../file_df_connection/base.doctree | Bin 0 -> 32869 bytes .../file_df_connection/index.doctree | Bin 0 -> 4576 bytes .../spark_hdfs/connection.doctree | Bin 0 -> 43085 bytes .../spark_hdfs/index.doctree | Bin 0 -> 4435 bytes .../spark_hdfs/prerequisites.doctree | Bin 0 -> 11394 bytes .../spark_hdfs/slots.doctree | Bin 0 -> 65105 bytes .../file_df_connection/spark_local_fs.doctree | Bin 0 -> 17796 bytes .../spark_s3/connection.doctree | Bin 0 -> 66398 bytes .../file_df_connection/spark_s3/index.doctree | Bin 0 -> 4243 bytes .../spark_s3/prerequisites.doctree | Bin 0 -> 13870 bytes .../spark_s3/troubleshooting.doctree | Bin 0 -> 73472 bytes mddocs/doctrees/connection/index.doctree | Bin 0 -> 4261 bytes mddocs/doctrees/contributing.doctree | Bin 0 -> 57687 bytes mddocs/doctrees/db/db_reader.doctree | Bin 0 -> 85522 bytes mddocs/doctrees/db/db_writer.doctree | Bin 0 -> 29431 bytes mddocs/doctrees/db/index.doctree | Bin 0 -> 3763 bytes .../file_downloader/file_downloader.doctree | Bin 0 -> 82948 bytes .../file/file_downloader/index.doctree | Bin 0 -> 4189 bytes .../file/file_downloader/options.doctree | Bin 0 -> 20877 bytes .../file/file_downloader/result.doctree | Bin 0 -> 124226 bytes .../doctrees/file/file_filters/base.doctree | Bin 0 -> 15123 bytes .../file/file_filters/exclude_dir.doctree | Bin 0 -> 14340 bytes .../file/file_filters/file_filter.doctree | Bin 0 -> 24382 bytes .../file_filters/file_mtime_filter.doctree | Bin 0 -> 20127 bytes .../file_filters/file_size_filter.doctree | Bin 0 -> 20673 bytes .../doctrees/file/file_filters/glob.doctree | Bin 0 -> 13363 bytes .../doctrees/file/file_filters/index.doctree | Bin 0 -> 4687 bytes .../file_filters/match_all_filters.doctree | Bin 0 -> 14653 bytes .../doctrees/file/file_filters/regexp.doctree | Bin 0 -> 15688 bytes mddocs/doctrees/file/file_limits/base.doctree | Bin 0 -> 29393 bytes .../file/file_limits/file_limit.doctree | Bin 0 -> 26560 bytes .../doctrees/file/file_limits/index.doctree | Bin 0 -> 4643 bytes .../file/file_limits/limits_reached.doctree | Bin 0 -> 12366 bytes .../file/file_limits/limits_stop_at.doctree | Bin 0 -> 14340 bytes .../file/file_limits/max_files_count.doctree | Bin 0 -> 25993 bytes .../file/file_limits/reset_limits.doctree | Bin 0 -> 13211 bytes .../file/file_limits/total_files_size.doctree | Bin 0 -> 28858 bytes .../file/file_mover/file_mover.doctree | Bin 0 -> 58487 bytes mddocs/doctrees/file/file_mover/index.doctree | Bin 0 -> 4114 bytes .../doctrees/file/file_mover/options.doctree | Bin 0 -> 16664 bytes .../doctrees/file/file_mover/result.doctree | Bin 0 -> 122686 bytes .../file/file_uploader/file_uploader.doctree | Bin 0 -> 58592 bytes .../doctrees/file/file_uploader/index.doctree | Bin 0 -> 4159 bytes .../file/file_uploader/options.doctree | Bin 0 -> 20716 bytes .../file/file_uploader/result.doctree | Bin 0 -> 123529 bytes mddocs/doctrees/file/index.doctree | Bin 0 -> 3893 bytes .../file_df_reader/file_df_reader.doctree | Bin 0 -> 37256 bytes .../file_df/file_df_reader/index.doctree | Bin 0 -> 4147 bytes .../file_df/file_df_reader/options.doctree | Bin 0 -> 16058 bytes .../file_df_writer/file_df_writer.doctree | Bin 0 -> 22661 bytes .../file_df/file_df_writer/index.doctree | Bin 0 -> 4147 bytes .../file_df/file_df_writer/options.doctree | Bin 0 -> 72939 bytes .../file_df/file_formats/avro.doctree | Bin 0 -> 115063 bytes .../file_df/file_formats/base.doctree | Bin 0 -> 28172 bytes .../doctrees/file_df/file_formats/csv.doctree | Bin 0 -> 224244 bytes .../file_df/file_formats/excel.doctree | Bin 0 -> 98599 bytes .../file_df/file_formats/index.doctree | Bin 0 -> 4513 bytes .../file_df/file_formats/json.doctree | Bin 0 -> 166599 bytes .../file_df/file_formats/jsonline.doctree | Bin 0 -> 154252 bytes .../doctrees/file_df/file_formats/orc.doctree | Bin 0 -> 30977 bytes .../file_df/file_formats/parquet.doctree | Bin 0 -> 32899 bytes .../doctrees/file_df/file_formats/xml.doctree | Bin 0 -> 169345 bytes mddocs/doctrees/file_df/index.doctree | Bin 0 -> 3871 bytes mddocs/doctrees/hooks/design.doctree | Bin 0 -> 87065 bytes mddocs/doctrees/hooks/global_state.doctree | Bin 0 -> 20165 bytes mddocs/doctrees/hooks/hook.doctree | Bin 0 -> 53860 bytes mddocs/doctrees/hooks/index.doctree | Bin 0 -> 4859 bytes mddocs/doctrees/hooks/slot.doctree | Bin 0 -> 44170 bytes mddocs/doctrees/hooks/support_hooks.doctree | Bin 0 -> 27957 bytes mddocs/doctrees/hwm_store/index.doctree | Bin 0 -> 6473 bytes .../doctrees/hwm_store/yaml_hwm_store.doctree | Bin 0 -> 32404 bytes mddocs/doctrees/index.doctree | Bin 0 -> 42083 bytes mddocs/doctrees/install/files.doctree | Bin 0 -> 6302 bytes mddocs/doctrees/install/full.doctree | Bin 0 -> 4935 bytes mddocs/doctrees/install/index.doctree | Bin 0 -> 8296 bytes mddocs/doctrees/install/kerberos.doctree | Bin 0 -> 9162 bytes mddocs/doctrees/install/spark.doctree | Bin 0 -> 68759 bytes mddocs/doctrees/logging.doctree | Bin 0 -> 54923 bytes mddocs/doctrees/plugins.doctree | Bin 0 -> 24191 bytes mddocs/doctrees/quickstart.doctree | Bin 0 -> 24493 bytes mddocs/doctrees/security.doctree | Bin 0 -> 7508 bytes .../incremental_batch_strategy.doctree | Bin 0 -> 52892 bytes .../strategy/incremental_strategy.doctree | Bin 0 -> 60978 bytes mddocs/doctrees/strategy/index.doctree | Bin 0 -> 6430 bytes .../strategy/snapshot_batch_strategy.doctree | Bin 0 -> 47889 bytes .../strategy/snapshot_strategy.doctree | Bin 0 -> 16570 bytes mddocs/doctrees/troubleshooting/index.doctree | Bin 0 -> 12252 bytes mddocs/doctrees/troubleshooting/spark.doctree | Bin 0 -> 14319 bytes mddocs/index.md | 91 --- .../_sphinx_design_static/design-tabs.js | 0 .../sphinx-design.min.css | 0 .../_static/autodoc_pydantic.css | 0 mddocs/{ => markdown}/changelog.md | 3 +- mddocs/{ => markdown}/changelog/0.10.0.md | 0 mddocs/{ => markdown}/changelog/0.10.1.md | 0 mddocs/{ => markdown}/changelog/0.10.2.md | 0 mddocs/{ => markdown}/changelog/0.11.0.md | 0 mddocs/{ => markdown}/changelog/0.11.1.md | 0 mddocs/{ => markdown}/changelog/0.11.2.md | 0 mddocs/{ => markdown}/changelog/0.12.0.md | 0 mddocs/{ => markdown}/changelog/0.12.1.md | 0 mddocs/{ => markdown}/changelog/0.12.2.md | 0 mddocs/{ => markdown}/changelog/0.12.3.md | 0 mddocs/{ => markdown}/changelog/0.12.4.md | 0 mddocs/{ => markdown}/changelog/0.12.5.md | 0 mddocs/{ => markdown}/changelog/0.13.0.md | 0 mddocs/{ => markdown}/changelog/0.13.1.md | 0 mddocs/{ => markdown}/changelog/0.13.3.md | 0 mddocs/{ => markdown}/changelog/0.13.4.md | 0 mddocs/{ => markdown}/changelog/0.7.0.md | 0 mddocs/{ => markdown}/changelog/0.7.1.md | 0 mddocs/{ => markdown}/changelog/0.7.2.md | 0 mddocs/{ => markdown}/changelog/0.8.0.md | 0 mddocs/{ => markdown}/changelog/0.8.1.md | 0 mddocs/{ => markdown}/changelog/0.9.0.md | 0 mddocs/{ => markdown}/changelog/0.9.1.md | 0 mddocs/{ => markdown}/changelog/0.9.2.md | 0 mddocs/{ => markdown}/changelog/0.9.3.md | 0 mddocs/{ => markdown}/changelog/0.9.4.md | 0 mddocs/{ => markdown}/changelog/0.9.5.md | 0 mddocs/markdown/changelog/DRAFT.md | 0 .../{ => markdown}/changelog/NEXT_RELEASE.md | 1 - mddocs/{ => markdown}/changelog/index.md | 2 +- mddocs/markdown/concepts.md | 340 +++++++++ .../db_connection/clickhouse/connection.md | 0 .../db_connection/clickhouse/execute.md | 0 .../db_connection/clickhouse/index.md | 0 .../db_connection/clickhouse/prerequisites.md | 0 .../db_connection/clickhouse/read.md | 0 .../db_connection/clickhouse/sql.md | 0 .../db_connection/clickhouse/types.md | 0 .../db_connection/clickhouse/write.md | 0 .../db_connection/greenplum/connection.md | 0 .../db_connection/greenplum/execute.md | 0 .../db_connection/greenplum/index.md | 0 .../db_connection/greenplum/prerequisites.md | 0 .../db_connection/greenplum/read.md | 0 .../db_connection/greenplum/types.md | 0 .../db_connection/greenplum/write.md | 0 .../db_connection/hive/connection.md | 0 .../connection/db_connection/hive/execute.md | 0 .../connection/db_connection/hive/index.md | 0 .../db_connection/hive/prerequisites.md | 0 .../connection/db_connection/hive/read.md | 0 .../connection/db_connection/hive/slots.md | 0 .../connection/db_connection/hive/sql.md | 0 .../connection/db_connection/hive/write.md | 0 .../connection/db_connection/index.md | 2 + .../connection/db_connection/kafka/auth.md | 0 .../db_connection/kafka/basic_auth.md | 0 .../db_connection/kafka/connection.md | 0 .../connection/db_connection/kafka/index.md | 0 .../db_connection/kafka/kerberos_auth.md | 0 .../db_connection/kafka/plaintext_protocol.md | 0 .../db_connection/kafka/prerequisites.md | 0 .../db_connection/kafka/protocol.md | 0 .../connection/db_connection/kafka/read.md | 0 .../db_connection/kafka/scram_auth.md | 0 .../connection/db_connection/kafka/slots.md | 0 .../db_connection/kafka/ssl_protocol.md | 0 .../db_connection/kafka/troubleshooting.md | 0 .../connection/db_connection/kafka/write.md | 0 .../db_connection/mongodb/connection.md | 0 .../connection/db_connection/mongodb/index.md | 0 .../db_connection/mongodb/pipeline.md | 0 .../db_connection/mongodb/prerequisites.md | 0 .../connection/db_connection/mongodb/read.md | 0 .../connection/db_connection/mongodb/types.md | 0 .../connection/db_connection/mongodb/write.md | 0 .../db_connection/mssql/connection.md | 0 .../connection/db_connection/mssql/execute.md | 0 .../connection/db_connection/mssql/index.md | 0 .../db_connection/mssql/prerequisites.md | 0 .../connection/db_connection/mssql/read.md | 0 .../connection/db_connection/mssql/sql.md | 0 .../connection/db_connection/mssql/types.md | 0 .../connection/db_connection/mssql/write.md | 0 .../db_connection/mysql/connection.md | 0 .../connection/db_connection/mysql/execute.md | 0 .../connection/db_connection/mysql/index.md | 0 .../db_connection/mysql/prerequisites.md | 0 .../connection/db_connection/mysql/read.md | 0 .../connection/db_connection/mysql/sql.md | 0 .../connection/db_connection/mysql/types.md | 0 .../connection/db_connection/mysql/write.md | 0 .../db_connection/oracle/connection.md | 0 .../db_connection/oracle/execute.md | 0 .../connection/db_connection/oracle/index.md | 0 .../db_connection/oracle/prerequisites.md | 0 .../connection/db_connection/oracle/read.md | 0 .../connection/db_connection/oracle/sql.md | 0 .../connection/db_connection/oracle/types.md | 0 .../connection/db_connection/oracle/write.md | 0 .../db_connection/postgres/connection.md | 0 .../db_connection/postgres/execute.md | 0 .../db_connection/postgres/index.md | 0 .../db_connection/postgres/prerequisites.md | 0 .../connection/db_connection/postgres/read.md | 0 .../connection/db_connection/postgres/sql.md | 0 .../db_connection/postgres/types.md | 0 .../db_connection/postgres/write.md | 0 .../db_connection/teradata/connection.md | 0 .../db_connection/teradata/execute.md | 0 .../db_connection/teradata/index.md | 0 .../db_connection/teradata/prerequisites.md | 0 .../connection/db_connection/teradata/read.md | 0 .../connection/db_connection/teradata/sql.md | 0 .../db_connection/teradata/write.md | 0 .../connection/file_connection/ftp.md | 0 .../connection/file_connection/ftps.md | 0 .../file_connection/hdfs/connection.md | 0 .../connection/file_connection/hdfs/index.md | 0 .../connection/file_connection/hdfs/slots.md | 0 .../connection/file_connection/index.md | 0 .../connection/file_connection/s3.md | 0 .../connection/file_connection/samba.md | 0 .../connection/file_connection/sftp.md | 0 .../connection/file_connection/webdav.md | 0 .../connection/file_df_connection/base.md | 0 .../connection/file_df_connection/index.md | 0 .../spark_hdfs/connection.md | 2 +- .../file_df_connection/spark_hdfs/index.md | 0 .../spark_hdfs/prerequisites.md | 0 .../file_df_connection/spark_hdfs/slots.md | 0 .../file_df_connection/spark_local_fs.md | 0 .../file_df_connection/spark_s3/connection.md | 2 +- .../file_df_connection/spark_s3/index.md | 0 .../spark_s3/prerequisites.md | 0 .../spark_s3/troubleshooting.md | 0 mddocs/{ => markdown}/connection/index.md | 2 +- mddocs/{ => markdown}/contributing.md | 69 +- mddocs/{ => markdown}/db/db_reader.md | 0 mddocs/{ => markdown}/db/db_writer.md | 0 mddocs/{ => markdown}/db/index.md | 2 +- .../file/file_downloader/file_downloader.md | 0 .../file/file_downloader/index.md | 0 .../file/file_downloader/options.md | 0 .../file/file_downloader/result.md | 0 .../{ => markdown}/file/file_filters/base.md | 0 .../file/file_filters/exclude_dir.md | 0 .../file/file_filters/file_filter.md | 0 .../file/file_filters/file_mtime_filter.md | 0 .../file/file_filters/file_size_filter.md | 0 .../{ => markdown}/file/file_filters/glob.md | 0 .../{ => markdown}/file/file_filters/index.md | 0 .../file/file_filters/match_all_filters.md | 0 .../file/file_filters/regexp.md | 0 .../{ => markdown}/file/file_limits/base.md | 0 .../file/file_limits/file_limit.md | 0 .../{ => markdown}/file/file_limits/index.md | 0 .../file/file_limits/limits_reached.md | 0 .../file/file_limits/limits_stop_at.md | 0 .../file/file_limits/max_files_count.md | 0 .../file/file_limits/reset_limits.md | 0 .../file/file_limits/total_files_size.md | 0 .../file/file_mover/file_mover.md | 0 .../{ => markdown}/file/file_mover/index.md | 0 .../{ => markdown}/file/file_mover/options.md | 0 .../{ => markdown}/file/file_mover/result.md | 0 .../file/file_uploader/file_uploader.md | 0 .../file/file_uploader/index.md | 0 .../file/file_uploader/options.md | 0 .../file/file_uploader/result.md | 0 mddocs/{ => markdown}/file/index.md | 2 +- .../file_df/file_df_reader/file_df_reader.md | 0 .../file_df/file_df_reader/index.md | 0 .../file_df/file_df_reader/options.md | 0 .../file_df/file_df_writer/file_df_writer.md | 0 .../file_df/file_df_writer/index.md | 0 .../file_df/file_df_writer/options.md | 0 .../file_df/file_formats/avro.md | 0 .../file_df/file_formats/base.md | 0 .../file_df/file_formats/csv.md | 0 .../file_df/file_formats/excel.md | 0 .../file_df/file_formats/index.md | 0 .../file_df/file_formats/json.md | 0 .../file_df/file_formats/jsonline.md | 0 .../file_df/file_formats/orc.md | 0 .../file_df/file_formats/parquet.md | 0 .../file_df/file_formats/xml.md | 0 mddocs/{ => markdown}/file_df/index.md | 2 +- mddocs/{ => markdown}/hooks/design.md | 0 mddocs/{ => markdown}/hooks/global_state.md | 0 mddocs/{ => markdown}/hooks/hook.md | 0 mddocs/{ => markdown}/hooks/index.md | 0 mddocs/{ => markdown}/hooks/slot.md | 0 mddocs/{ => markdown}/hooks/support_hooks.md | 0 mddocs/{ => markdown}/hwm_store/index.md | 0 .../hwm_store/yaml_hwm_store.md | 0 mddocs/markdown/index.md | 59 ++ mddocs/{ => markdown}/install/files.md | 0 mddocs/{ => markdown}/install/full.md | 0 mddocs/{ => markdown}/install/index.md | 9 +- mddocs/{ => markdown}/install/kerberos.md | 0 mddocs/{ => markdown}/install/spark.md | 0 mddocs/{ => markdown}/logging.md | 105 ++- mddocs/{ => markdown}/plugins.md | 10 +- mddocs/{ => markdown}/quickstart.md | 0 mddocs/{ => markdown}/security.md | 0 .../strategy/incremental_batch_strategy.md | 0 .../strategy/incremental_strategy.md | 0 mddocs/{ => markdown}/strategy/index.md | 0 .../strategy/snapshot_batch_strategy.md | 0 .../strategy/snapshot_strategy.md | 0 .../{ => markdown}/troubleshooting/index.md | 0 .../{ => markdown}/troubleshooting/spark.md | 0 439 files changed, 512 insertions(+), 1909 deletions(-) delete mode 100644 mddocs/README.md delete mode 100644 mddocs/_static/icon.svg delete mode 100644 mddocs/_static/logo.svg delete mode 100644 mddocs/_static/logo_wide.svg delete mode 100644 mddocs/changelog/DRAFT.md delete mode 100644 mddocs/concepts.md delete mode 100644 mddocs/conf.py create mode 100644 mddocs/doctrees/changelog.doctree create mode 100644 mddocs/doctrees/changelog/0.10.0.doctree create mode 100644 mddocs/doctrees/changelog/0.10.1.doctree create mode 100644 mddocs/doctrees/changelog/0.10.2.doctree create mode 100644 mddocs/doctrees/changelog/0.11.0.doctree create mode 100644 mddocs/doctrees/changelog/0.11.1.doctree create mode 100644 mddocs/doctrees/changelog/0.11.2.doctree create mode 100644 mddocs/doctrees/changelog/0.12.0.doctree create mode 100644 mddocs/doctrees/changelog/0.12.1.doctree create mode 100644 mddocs/doctrees/changelog/0.12.2.doctree create mode 100644 mddocs/doctrees/changelog/0.12.3.doctree create mode 100644 mddocs/doctrees/changelog/0.12.4.doctree create mode 100644 mddocs/doctrees/changelog/0.12.5.doctree create mode 100644 mddocs/doctrees/changelog/0.13.0.doctree create mode 100644 mddocs/doctrees/changelog/0.13.1.doctree create mode 100644 mddocs/doctrees/changelog/0.13.3.doctree create mode 100644 mddocs/doctrees/changelog/0.13.4.doctree create mode 100644 mddocs/doctrees/changelog/0.7.0.doctree create mode 100644 mddocs/doctrees/changelog/0.7.1.doctree create mode 100644 mddocs/doctrees/changelog/0.7.2.doctree create mode 100644 mddocs/doctrees/changelog/0.8.0.doctree create mode 100644 mddocs/doctrees/changelog/0.8.1.doctree create mode 100644 mddocs/doctrees/changelog/0.9.0.doctree create mode 100644 mddocs/doctrees/changelog/0.9.1.doctree create mode 100644 mddocs/doctrees/changelog/0.9.2.doctree create mode 100644 mddocs/doctrees/changelog/0.9.3.doctree create mode 100644 mddocs/doctrees/changelog/0.9.4.doctree create mode 100644 mddocs/doctrees/changelog/0.9.5.doctree create mode 100644 mddocs/doctrees/changelog/DRAFT.doctree create mode 100644 mddocs/doctrees/changelog/NEXT_RELEASE.doctree create mode 100644 mddocs/doctrees/changelog/index.doctree create mode 100644 mddocs/doctrees/concepts.doctree create mode 100644 mddocs/doctrees/connection/db_connection/clickhouse/connection.doctree create mode 100644 mddocs/doctrees/connection/db_connection/clickhouse/execute.doctree create mode 100644 mddocs/doctrees/connection/db_connection/clickhouse/index.doctree create mode 100644 mddocs/doctrees/connection/db_connection/clickhouse/prerequisites.doctree create mode 100644 mddocs/doctrees/connection/db_connection/clickhouse/read.doctree create mode 100644 mddocs/doctrees/connection/db_connection/clickhouse/sql.doctree create mode 100644 mddocs/doctrees/connection/db_connection/clickhouse/types.doctree create mode 100644 mddocs/doctrees/connection/db_connection/clickhouse/write.doctree create mode 100644 mddocs/doctrees/connection/db_connection/greenplum/connection.doctree create mode 100644 mddocs/doctrees/connection/db_connection/greenplum/execute.doctree create mode 100644 mddocs/doctrees/connection/db_connection/greenplum/index.doctree create mode 100644 mddocs/doctrees/connection/db_connection/greenplum/prerequisites.doctree create mode 100644 mddocs/doctrees/connection/db_connection/greenplum/read.doctree create mode 100644 mddocs/doctrees/connection/db_connection/greenplum/types.doctree create mode 100644 mddocs/doctrees/connection/db_connection/greenplum/write.doctree create mode 100644 mddocs/doctrees/connection/db_connection/hive/connection.doctree create mode 100644 mddocs/doctrees/connection/db_connection/hive/execute.doctree create mode 100644 mddocs/doctrees/connection/db_connection/hive/index.doctree create mode 100644 mddocs/doctrees/connection/db_connection/hive/prerequisites.doctree create mode 100644 mddocs/doctrees/connection/db_connection/hive/read.doctree create mode 100644 mddocs/doctrees/connection/db_connection/hive/slots.doctree create mode 100644 mddocs/doctrees/connection/db_connection/hive/sql.doctree create mode 100644 mddocs/doctrees/connection/db_connection/hive/write.doctree create mode 100644 mddocs/doctrees/connection/db_connection/index.doctree create mode 100644 mddocs/doctrees/connection/db_connection/kafka/auth.doctree create mode 100644 mddocs/doctrees/connection/db_connection/kafka/basic_auth.doctree create mode 100644 mddocs/doctrees/connection/db_connection/kafka/connection.doctree create mode 100644 mddocs/doctrees/connection/db_connection/kafka/index.doctree create mode 100644 mddocs/doctrees/connection/db_connection/kafka/kerberos_auth.doctree create mode 100644 mddocs/doctrees/connection/db_connection/kafka/plaintext_protocol.doctree create mode 100644 mddocs/doctrees/connection/db_connection/kafka/prerequisites.doctree create mode 100644 mddocs/doctrees/connection/db_connection/kafka/protocol.doctree create mode 100644 mddocs/doctrees/connection/db_connection/kafka/read.doctree create mode 100644 mddocs/doctrees/connection/db_connection/kafka/scram_auth.doctree create mode 100644 mddocs/doctrees/connection/db_connection/kafka/slots.doctree create mode 100644 mddocs/doctrees/connection/db_connection/kafka/ssl_protocol.doctree create mode 100644 mddocs/doctrees/connection/db_connection/kafka/troubleshooting.doctree create mode 100644 mddocs/doctrees/connection/db_connection/kafka/write.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mongodb/connection.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mongodb/index.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mongodb/pipeline.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mongodb/prerequisites.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mongodb/read.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mongodb/types.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mongodb/write.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mssql/connection.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mssql/execute.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mssql/index.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mssql/prerequisites.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mssql/read.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mssql/sql.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mssql/types.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mssql/write.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mysql/connection.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mysql/execute.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mysql/index.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mysql/prerequisites.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mysql/read.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mysql/sql.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mysql/types.doctree create mode 100644 mddocs/doctrees/connection/db_connection/mysql/write.doctree create mode 100644 mddocs/doctrees/connection/db_connection/oracle/connection.doctree create mode 100644 mddocs/doctrees/connection/db_connection/oracle/execute.doctree create mode 100644 mddocs/doctrees/connection/db_connection/oracle/index.doctree create mode 100644 mddocs/doctrees/connection/db_connection/oracle/prerequisites.doctree create mode 100644 mddocs/doctrees/connection/db_connection/oracle/read.doctree create mode 100644 mddocs/doctrees/connection/db_connection/oracle/sql.doctree create mode 100644 mddocs/doctrees/connection/db_connection/oracle/types.doctree create mode 100644 mddocs/doctrees/connection/db_connection/oracle/write.doctree create mode 100644 mddocs/doctrees/connection/db_connection/postgres/connection.doctree create mode 100644 mddocs/doctrees/connection/db_connection/postgres/execute.doctree create mode 100644 mddocs/doctrees/connection/db_connection/postgres/index.doctree create mode 100644 mddocs/doctrees/connection/db_connection/postgres/prerequisites.doctree create mode 100644 mddocs/doctrees/connection/db_connection/postgres/read.doctree create mode 100644 mddocs/doctrees/connection/db_connection/postgres/sql.doctree create mode 100644 mddocs/doctrees/connection/db_connection/postgres/types.doctree create mode 100644 mddocs/doctrees/connection/db_connection/postgres/write.doctree create mode 100644 mddocs/doctrees/connection/db_connection/teradata/connection.doctree create mode 100644 mddocs/doctrees/connection/db_connection/teradata/execute.doctree create mode 100644 mddocs/doctrees/connection/db_connection/teradata/index.doctree create mode 100644 mddocs/doctrees/connection/db_connection/teradata/prerequisites.doctree create mode 100644 mddocs/doctrees/connection/db_connection/teradata/read.doctree create mode 100644 mddocs/doctrees/connection/db_connection/teradata/sql.doctree create mode 100644 mddocs/doctrees/connection/db_connection/teradata/write.doctree create mode 100644 mddocs/doctrees/connection/file_connection/ftp.doctree create mode 100644 mddocs/doctrees/connection/file_connection/ftps.doctree create mode 100644 mddocs/doctrees/connection/file_connection/hdfs/connection.doctree create mode 100644 mddocs/doctrees/connection/file_connection/hdfs/index.doctree create mode 100644 mddocs/doctrees/connection/file_connection/hdfs/slots.doctree create mode 100644 mddocs/doctrees/connection/file_connection/index.doctree create mode 100644 mddocs/doctrees/connection/file_connection/s3.doctree create mode 100644 mddocs/doctrees/connection/file_connection/samba.doctree create mode 100644 mddocs/doctrees/connection/file_connection/sftp.doctree create mode 100644 mddocs/doctrees/connection/file_connection/webdav.doctree create mode 100644 mddocs/doctrees/connection/file_df_connection/base.doctree create mode 100644 mddocs/doctrees/connection/file_df_connection/index.doctree create mode 100644 mddocs/doctrees/connection/file_df_connection/spark_hdfs/connection.doctree create mode 100644 mddocs/doctrees/connection/file_df_connection/spark_hdfs/index.doctree create mode 100644 mddocs/doctrees/connection/file_df_connection/spark_hdfs/prerequisites.doctree create mode 100644 mddocs/doctrees/connection/file_df_connection/spark_hdfs/slots.doctree create mode 100644 mddocs/doctrees/connection/file_df_connection/spark_local_fs.doctree create mode 100644 mddocs/doctrees/connection/file_df_connection/spark_s3/connection.doctree create mode 100644 mddocs/doctrees/connection/file_df_connection/spark_s3/index.doctree create mode 100644 mddocs/doctrees/connection/file_df_connection/spark_s3/prerequisites.doctree create mode 100644 mddocs/doctrees/connection/file_df_connection/spark_s3/troubleshooting.doctree create mode 100644 mddocs/doctrees/connection/index.doctree create mode 100644 mddocs/doctrees/contributing.doctree create mode 100644 mddocs/doctrees/db/db_reader.doctree create mode 100644 mddocs/doctrees/db/db_writer.doctree create mode 100644 mddocs/doctrees/db/index.doctree create mode 100644 mddocs/doctrees/file/file_downloader/file_downloader.doctree create mode 100644 mddocs/doctrees/file/file_downloader/index.doctree create mode 100644 mddocs/doctrees/file/file_downloader/options.doctree create mode 100644 mddocs/doctrees/file/file_downloader/result.doctree create mode 100644 mddocs/doctrees/file/file_filters/base.doctree create mode 100644 mddocs/doctrees/file/file_filters/exclude_dir.doctree create mode 100644 mddocs/doctrees/file/file_filters/file_filter.doctree create mode 100644 mddocs/doctrees/file/file_filters/file_mtime_filter.doctree create mode 100644 mddocs/doctrees/file/file_filters/file_size_filter.doctree create mode 100644 mddocs/doctrees/file/file_filters/glob.doctree create mode 100644 mddocs/doctrees/file/file_filters/index.doctree create mode 100644 mddocs/doctrees/file/file_filters/match_all_filters.doctree create mode 100644 mddocs/doctrees/file/file_filters/regexp.doctree create mode 100644 mddocs/doctrees/file/file_limits/base.doctree create mode 100644 mddocs/doctrees/file/file_limits/file_limit.doctree create mode 100644 mddocs/doctrees/file/file_limits/index.doctree create mode 100644 mddocs/doctrees/file/file_limits/limits_reached.doctree create mode 100644 mddocs/doctrees/file/file_limits/limits_stop_at.doctree create mode 100644 mddocs/doctrees/file/file_limits/max_files_count.doctree create mode 100644 mddocs/doctrees/file/file_limits/reset_limits.doctree create mode 100644 mddocs/doctrees/file/file_limits/total_files_size.doctree create mode 100644 mddocs/doctrees/file/file_mover/file_mover.doctree create mode 100644 mddocs/doctrees/file/file_mover/index.doctree create mode 100644 mddocs/doctrees/file/file_mover/options.doctree create mode 100644 mddocs/doctrees/file/file_mover/result.doctree create mode 100644 mddocs/doctrees/file/file_uploader/file_uploader.doctree create mode 100644 mddocs/doctrees/file/file_uploader/index.doctree create mode 100644 mddocs/doctrees/file/file_uploader/options.doctree create mode 100644 mddocs/doctrees/file/file_uploader/result.doctree create mode 100644 mddocs/doctrees/file/index.doctree create mode 100644 mddocs/doctrees/file_df/file_df_reader/file_df_reader.doctree create mode 100644 mddocs/doctrees/file_df/file_df_reader/index.doctree create mode 100644 mddocs/doctrees/file_df/file_df_reader/options.doctree create mode 100644 mddocs/doctrees/file_df/file_df_writer/file_df_writer.doctree create mode 100644 mddocs/doctrees/file_df/file_df_writer/index.doctree create mode 100644 mddocs/doctrees/file_df/file_df_writer/options.doctree create mode 100644 mddocs/doctrees/file_df/file_formats/avro.doctree create mode 100644 mddocs/doctrees/file_df/file_formats/base.doctree create mode 100644 mddocs/doctrees/file_df/file_formats/csv.doctree create mode 100644 mddocs/doctrees/file_df/file_formats/excel.doctree create mode 100644 mddocs/doctrees/file_df/file_formats/index.doctree create mode 100644 mddocs/doctrees/file_df/file_formats/json.doctree create mode 100644 mddocs/doctrees/file_df/file_formats/jsonline.doctree create mode 100644 mddocs/doctrees/file_df/file_formats/orc.doctree create mode 100644 mddocs/doctrees/file_df/file_formats/parquet.doctree create mode 100644 mddocs/doctrees/file_df/file_formats/xml.doctree create mode 100644 mddocs/doctrees/file_df/index.doctree create mode 100644 mddocs/doctrees/hooks/design.doctree create mode 100644 mddocs/doctrees/hooks/global_state.doctree create mode 100644 mddocs/doctrees/hooks/hook.doctree create mode 100644 mddocs/doctrees/hooks/index.doctree create mode 100644 mddocs/doctrees/hooks/slot.doctree create mode 100644 mddocs/doctrees/hooks/support_hooks.doctree create mode 100644 mddocs/doctrees/hwm_store/index.doctree create mode 100644 mddocs/doctrees/hwm_store/yaml_hwm_store.doctree create mode 100644 mddocs/doctrees/index.doctree create mode 100644 mddocs/doctrees/install/files.doctree create mode 100644 mddocs/doctrees/install/full.doctree create mode 100644 mddocs/doctrees/install/index.doctree create mode 100644 mddocs/doctrees/install/kerberos.doctree create mode 100644 mddocs/doctrees/install/spark.doctree create mode 100644 mddocs/doctrees/logging.doctree create mode 100644 mddocs/doctrees/plugins.doctree create mode 100644 mddocs/doctrees/quickstart.doctree create mode 100644 mddocs/doctrees/security.doctree create mode 100644 mddocs/doctrees/strategy/incremental_batch_strategy.doctree create mode 100644 mddocs/doctrees/strategy/incremental_strategy.doctree create mode 100644 mddocs/doctrees/strategy/index.doctree create mode 100644 mddocs/doctrees/strategy/snapshot_batch_strategy.doctree create mode 100644 mddocs/doctrees/strategy/snapshot_strategy.doctree create mode 100644 mddocs/doctrees/troubleshooting/index.doctree create mode 100644 mddocs/doctrees/troubleshooting/spark.doctree delete mode 100644 mddocs/index.md rename mddocs/{ => markdown}/_sphinx_design_static/design-tabs.js (100%) rename mddocs/{ => markdown}/_sphinx_design_static/sphinx-design.min.css (100%) rename mddocs/{ => markdown}/_static/autodoc_pydantic.css (100%) rename mddocs/{ => markdown}/changelog.md (97%) rename mddocs/{ => markdown}/changelog/0.10.0.md (100%) rename mddocs/{ => markdown}/changelog/0.10.1.md (100%) rename mddocs/{ => markdown}/changelog/0.10.2.md (100%) rename mddocs/{ => markdown}/changelog/0.11.0.md (100%) rename mddocs/{ => markdown}/changelog/0.11.1.md (100%) rename mddocs/{ => markdown}/changelog/0.11.2.md (100%) rename mddocs/{ => markdown}/changelog/0.12.0.md (100%) rename mddocs/{ => markdown}/changelog/0.12.1.md (100%) rename mddocs/{ => markdown}/changelog/0.12.2.md (100%) rename mddocs/{ => markdown}/changelog/0.12.3.md (100%) rename mddocs/{ => markdown}/changelog/0.12.4.md (100%) rename mddocs/{ => markdown}/changelog/0.12.5.md (100%) rename mddocs/{ => markdown}/changelog/0.13.0.md (100%) rename mddocs/{ => markdown}/changelog/0.13.1.md (100%) rename mddocs/{ => markdown}/changelog/0.13.3.md (100%) rename mddocs/{ => markdown}/changelog/0.13.4.md (100%) rename mddocs/{ => markdown}/changelog/0.7.0.md (100%) rename mddocs/{ => markdown}/changelog/0.7.1.md (100%) rename mddocs/{ => markdown}/changelog/0.7.2.md (100%) rename mddocs/{ => markdown}/changelog/0.8.0.md (100%) rename mddocs/{ => markdown}/changelog/0.8.1.md (100%) rename mddocs/{ => markdown}/changelog/0.9.0.md (100%) rename mddocs/{ => markdown}/changelog/0.9.1.md (100%) rename mddocs/{ => markdown}/changelog/0.9.2.md (100%) rename mddocs/{ => markdown}/changelog/0.9.3.md (100%) rename mddocs/{ => markdown}/changelog/0.9.4.md (100%) rename mddocs/{ => markdown}/changelog/0.9.5.md (100%) create mode 100644 mddocs/markdown/changelog/DRAFT.md rename mddocs/{ => markdown}/changelog/NEXT_RELEASE.md (53%) rename mddocs/{ => markdown}/changelog/index.md (98%) create mode 100644 mddocs/markdown/concepts.md rename mddocs/{ => markdown}/connection/db_connection/clickhouse/connection.md (100%) rename mddocs/{ => markdown}/connection/db_connection/clickhouse/execute.md (100%) rename mddocs/{ => markdown}/connection/db_connection/clickhouse/index.md (100%) rename mddocs/{ => markdown}/connection/db_connection/clickhouse/prerequisites.md (100%) rename mddocs/{ => markdown}/connection/db_connection/clickhouse/read.md (100%) rename mddocs/{ => markdown}/connection/db_connection/clickhouse/sql.md (100%) rename mddocs/{ => markdown}/connection/db_connection/clickhouse/types.md (100%) rename mddocs/{ => markdown}/connection/db_connection/clickhouse/write.md (100%) rename mddocs/{ => markdown}/connection/db_connection/greenplum/connection.md (100%) rename mddocs/{ => markdown}/connection/db_connection/greenplum/execute.md (100%) rename mddocs/{ => markdown}/connection/db_connection/greenplum/index.md (100%) rename mddocs/{ => markdown}/connection/db_connection/greenplum/prerequisites.md (100%) rename mddocs/{ => markdown}/connection/db_connection/greenplum/read.md (100%) rename mddocs/{ => markdown}/connection/db_connection/greenplum/types.md (100%) rename mddocs/{ => markdown}/connection/db_connection/greenplum/write.md (100%) rename mddocs/{ => markdown}/connection/db_connection/hive/connection.md (100%) rename mddocs/{ => markdown}/connection/db_connection/hive/execute.md (100%) rename mddocs/{ => markdown}/connection/db_connection/hive/index.md (100%) rename mddocs/{ => markdown}/connection/db_connection/hive/prerequisites.md (100%) rename mddocs/{ => markdown}/connection/db_connection/hive/read.md (100%) rename mddocs/{ => markdown}/connection/db_connection/hive/slots.md (100%) rename mddocs/{ => markdown}/connection/db_connection/hive/sql.md (100%) rename mddocs/{ => markdown}/connection/db_connection/hive/write.md (100%) rename mddocs/{ => markdown}/connection/db_connection/index.md (94%) rename mddocs/{ => markdown}/connection/db_connection/kafka/auth.md (100%) rename mddocs/{ => markdown}/connection/db_connection/kafka/basic_auth.md (100%) rename mddocs/{ => markdown}/connection/db_connection/kafka/connection.md (100%) rename mddocs/{ => markdown}/connection/db_connection/kafka/index.md (100%) rename mddocs/{ => markdown}/connection/db_connection/kafka/kerberos_auth.md (100%) rename mddocs/{ => markdown}/connection/db_connection/kafka/plaintext_protocol.md (100%) rename mddocs/{ => markdown}/connection/db_connection/kafka/prerequisites.md (100%) rename mddocs/{ => markdown}/connection/db_connection/kafka/protocol.md (100%) rename mddocs/{ => markdown}/connection/db_connection/kafka/read.md (100%) rename mddocs/{ => markdown}/connection/db_connection/kafka/scram_auth.md (100%) rename mddocs/{ => markdown}/connection/db_connection/kafka/slots.md (100%) rename mddocs/{ => markdown}/connection/db_connection/kafka/ssl_protocol.md (100%) rename mddocs/{ => markdown}/connection/db_connection/kafka/troubleshooting.md (100%) rename mddocs/{ => markdown}/connection/db_connection/kafka/write.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mongodb/connection.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mongodb/index.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mongodb/pipeline.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mongodb/prerequisites.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mongodb/read.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mongodb/types.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mongodb/write.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mssql/connection.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mssql/execute.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mssql/index.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mssql/prerequisites.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mssql/read.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mssql/sql.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mssql/types.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mssql/write.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mysql/connection.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mysql/execute.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mysql/index.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mysql/prerequisites.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mysql/read.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mysql/sql.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mysql/types.md (100%) rename mddocs/{ => markdown}/connection/db_connection/mysql/write.md (100%) rename mddocs/{ => markdown}/connection/db_connection/oracle/connection.md (100%) rename mddocs/{ => markdown}/connection/db_connection/oracle/execute.md (100%) rename mddocs/{ => markdown}/connection/db_connection/oracle/index.md (100%) rename mddocs/{ => markdown}/connection/db_connection/oracle/prerequisites.md (100%) rename mddocs/{ => markdown}/connection/db_connection/oracle/read.md (100%) rename mddocs/{ => markdown}/connection/db_connection/oracle/sql.md (100%) rename mddocs/{ => markdown}/connection/db_connection/oracle/types.md (100%) rename mddocs/{ => markdown}/connection/db_connection/oracle/write.md (100%) rename mddocs/{ => markdown}/connection/db_connection/postgres/connection.md (100%) rename mddocs/{ => markdown}/connection/db_connection/postgres/execute.md (100%) rename mddocs/{ => markdown}/connection/db_connection/postgres/index.md (100%) rename mddocs/{ => markdown}/connection/db_connection/postgres/prerequisites.md (100%) rename mddocs/{ => markdown}/connection/db_connection/postgres/read.md (100%) rename mddocs/{ => markdown}/connection/db_connection/postgres/sql.md (100%) rename mddocs/{ => markdown}/connection/db_connection/postgres/types.md (100%) rename mddocs/{ => markdown}/connection/db_connection/postgres/write.md (100%) rename mddocs/{ => markdown}/connection/db_connection/teradata/connection.md (100%) rename mddocs/{ => markdown}/connection/db_connection/teradata/execute.md (100%) rename mddocs/{ => markdown}/connection/db_connection/teradata/index.md (100%) rename mddocs/{ => markdown}/connection/db_connection/teradata/prerequisites.md (100%) rename mddocs/{ => markdown}/connection/db_connection/teradata/read.md (100%) rename mddocs/{ => markdown}/connection/db_connection/teradata/sql.md (100%) rename mddocs/{ => markdown}/connection/db_connection/teradata/write.md (100%) rename mddocs/{ => markdown}/connection/file_connection/ftp.md (100%) rename mddocs/{ => markdown}/connection/file_connection/ftps.md (100%) rename mddocs/{ => markdown}/connection/file_connection/hdfs/connection.md (100%) rename mddocs/{ => markdown}/connection/file_connection/hdfs/index.md (100%) rename mddocs/{ => markdown}/connection/file_connection/hdfs/slots.md (100%) rename mddocs/{ => markdown}/connection/file_connection/index.md (100%) rename mddocs/{ => markdown}/connection/file_connection/s3.md (100%) rename mddocs/{ => markdown}/connection/file_connection/samba.md (100%) rename mddocs/{ => markdown}/connection/file_connection/sftp.md (100%) rename mddocs/{ => markdown}/connection/file_connection/webdav.md (100%) rename mddocs/{ => markdown}/connection/file_df_connection/base.md (100%) rename mddocs/{ => markdown}/connection/file_df_connection/index.md (100%) rename mddocs/{ => markdown}/connection/file_df_connection/spark_hdfs/connection.md (97%) rename mddocs/{ => markdown}/connection/file_df_connection/spark_hdfs/index.md (100%) rename mddocs/{ => markdown}/connection/file_df_connection/spark_hdfs/prerequisites.md (100%) rename mddocs/{ => markdown}/connection/file_df_connection/spark_hdfs/slots.md (100%) rename mddocs/{ => markdown}/connection/file_df_connection/spark_local_fs.md (100%) rename mddocs/{ => markdown}/connection/file_df_connection/spark_s3/connection.md (98%) rename mddocs/{ => markdown}/connection/file_df_connection/spark_s3/index.md (100%) rename mddocs/{ => markdown}/connection/file_df_connection/spark_s3/prerequisites.md (100%) rename mddocs/{ => markdown}/connection/file_df_connection/spark_s3/troubleshooting.md (100%) rename mddocs/{ => markdown}/connection/index.md (98%) rename mddocs/{ => markdown}/contributing.md (80%) rename mddocs/{ => markdown}/db/db_reader.md (100%) rename mddocs/{ => markdown}/db/db_writer.md (100%) rename mddocs/{ => markdown}/db/index.md (85%) rename mddocs/{ => markdown}/file/file_downloader/file_downloader.md (100%) rename mddocs/{ => markdown}/file/file_downloader/index.md (100%) rename mddocs/{ => markdown}/file/file_downloader/options.md (100%) rename mddocs/{ => markdown}/file/file_downloader/result.md (100%) rename mddocs/{ => markdown}/file/file_filters/base.md (100%) rename mddocs/{ => markdown}/file/file_filters/exclude_dir.md (100%) rename mddocs/{ => markdown}/file/file_filters/file_filter.md (100%) rename mddocs/{ => markdown}/file/file_filters/file_mtime_filter.md (100%) rename mddocs/{ => markdown}/file/file_filters/file_size_filter.md (100%) rename mddocs/{ => markdown}/file/file_filters/glob.md (100%) rename mddocs/{ => markdown}/file/file_filters/index.md (100%) rename mddocs/{ => markdown}/file/file_filters/match_all_filters.md (100%) rename mddocs/{ => markdown}/file/file_filters/regexp.md (100%) rename mddocs/{ => markdown}/file/file_limits/base.md (100%) rename mddocs/{ => markdown}/file/file_limits/file_limit.md (100%) rename mddocs/{ => markdown}/file/file_limits/index.md (100%) rename mddocs/{ => markdown}/file/file_limits/limits_reached.md (100%) rename mddocs/{ => markdown}/file/file_limits/limits_stop_at.md (100%) rename mddocs/{ => markdown}/file/file_limits/max_files_count.md (100%) rename mddocs/{ => markdown}/file/file_limits/reset_limits.md (100%) rename mddocs/{ => markdown}/file/file_limits/total_files_size.md (100%) rename mddocs/{ => markdown}/file/file_mover/file_mover.md (100%) rename mddocs/{ => markdown}/file/file_mover/index.md (100%) rename mddocs/{ => markdown}/file/file_mover/options.md (100%) rename mddocs/{ => markdown}/file/file_mover/result.md (100%) rename mddocs/{ => markdown}/file/file_uploader/file_uploader.md (100%) rename mddocs/{ => markdown}/file/file_uploader/index.md (100%) rename mddocs/{ => markdown}/file/file_uploader/options.md (100%) rename mddocs/{ => markdown}/file/file_uploader/result.md (100%) rename mddocs/{ => markdown}/file/index.md (93%) rename mddocs/{ => markdown}/file_df/file_df_reader/file_df_reader.md (100%) rename mddocs/{ => markdown}/file_df/file_df_reader/index.md (100%) rename mddocs/{ => markdown}/file_df/file_df_reader/options.md (100%) rename mddocs/{ => markdown}/file_df/file_df_writer/file_df_writer.md (100%) rename mddocs/{ => markdown}/file_df/file_df_writer/index.md (100%) rename mddocs/{ => markdown}/file_df/file_df_writer/options.md (100%) rename mddocs/{ => markdown}/file_df/file_formats/avro.md (100%) rename mddocs/{ => markdown}/file_df/file_formats/base.md (100%) rename mddocs/{ => markdown}/file_df/file_formats/csv.md (100%) rename mddocs/{ => markdown}/file_df/file_formats/excel.md (100%) rename mddocs/{ => markdown}/file_df/file_formats/index.md (100%) rename mddocs/{ => markdown}/file_df/file_formats/json.md (100%) rename mddocs/{ => markdown}/file_df/file_formats/jsonline.md (100%) rename mddocs/{ => markdown}/file_df/file_formats/orc.md (100%) rename mddocs/{ => markdown}/file_df/file_formats/parquet.md (100%) rename mddocs/{ => markdown}/file_df/file_formats/xml.md (100%) rename mddocs/{ => markdown}/file_df/index.md (86%) rename mddocs/{ => markdown}/hooks/design.md (100%) rename mddocs/{ => markdown}/hooks/global_state.md (100%) rename mddocs/{ => markdown}/hooks/hook.md (100%) rename mddocs/{ => markdown}/hooks/index.md (100%) rename mddocs/{ => markdown}/hooks/slot.md (100%) rename mddocs/{ => markdown}/hooks/support_hooks.md (100%) rename mddocs/{ => markdown}/hwm_store/index.md (100%) rename mddocs/{ => markdown}/hwm_store/yaml_hwm_store.md (100%) create mode 100644 mddocs/markdown/index.md rename mddocs/{ => markdown}/install/files.md (100%) rename mddocs/{ => markdown}/install/full.md (100%) rename mddocs/{ => markdown}/install/index.md (84%) rename mddocs/{ => markdown}/install/kerberos.md (100%) rename mddocs/{ => markdown}/install/spark.md (100%) rename mddocs/{ => markdown}/logging.md (88%) rename mddocs/{ => markdown}/plugins.md (99%) rename mddocs/{ => markdown}/quickstart.md (100%) rename mddocs/{ => markdown}/security.md (100%) rename mddocs/{ => markdown}/strategy/incremental_batch_strategy.md (100%) rename mddocs/{ => markdown}/strategy/incremental_strategy.md (100%) rename mddocs/{ => markdown}/strategy/index.md (100%) rename mddocs/{ => markdown}/strategy/snapshot_batch_strategy.md (100%) rename mddocs/{ => markdown}/strategy/snapshot_strategy.md (100%) rename mddocs/{ => markdown}/troubleshooting/index.md (100%) rename mddocs/{ => markdown}/troubleshooting/spark.md (100%) diff --git a/mddocs/README.md b/mddocs/README.md deleted file mode 100644 index ffb6fc0c5..000000000 --- a/mddocs/README.md +++ /dev/null @@ -1,657 +0,0 @@ -# onETL - -[![Repo status - Active](https://www.repostatus.org/badges/latest/active.svg)](https://github.com/MobileTeleSystems/onetl) [![PyPI - Latest Release](https://img.shields.io/pypi/v/onetl)](https://pypi.org/project/onetl/) [![PyPI - License](https://img.shields.io/pypi/l/onetl.svg)](https://github.com/MobileTeleSystems/onetl/blob/develop/LICENSE.txt) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/onetl.svg)](https://pypi.org/project/onetl/) [![PyPI - Downloads](https://img.shields.io/pypi/dm/onetl)](https://pypi.org/project/onetl/) -[![Documentation - ReadTheDocs](https://readthedocs.org/projects/onetl/badge/?version=stable)](https://onetl.readthedocs.io/) [![Github Actions - latest CI build status](https://github.com/MobileTeleSystems/onetl/workflows/Tests/badge.svg)](https://github.com/MobileTeleSystems/onetl/actions) [![Test coverage - percent](https://codecov.io/gh/MobileTeleSystems/onetl/branch/develop/graph/badge.svg?token=RIO8URKNZJ)](https://codecov.io/gh/MobileTeleSystems/onetl) [![pre-commit.ci - status](https://results.pre-commit.ci/badge/github/MobileTeleSystems/onetl/develop.svg)](https://results.pre-commit.ci/latest/github/MobileTeleSystems/onetl/develop) - - - -![onETL logo](_static/logo_wide.svg) - - - -## What is onETL? - -Python ETL/ELT library powered by [Apache Spark](https://spark.apache.org/) & other open-source tools. - -## Goals - -* Provide unified classes to extract data from (**E**) & load data to (**L**) various stores. -* Provides [Spark DataFrame API](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.DataFrame.html) for performing transformations (**T**) in terms of *ETL*. -* Provide direct assess to database, allowing to execute SQL queries, as well as DDL, DML, and call functions/procedures. This can be used for building up *ELT* pipelines. -* Support different [read strategies](https://onetl.readthedocs.io/en/stable/strategy/index.html) for incremental and batch data fetching. -* Provide [hooks](https://onetl.readthedocs.io/en/stable/hooks/index.html) & [plugins](https://onetl.readthedocs.io/en/stable/plugins.html) mechanism for altering behavior of internal classes. - -## Non-goals - -* onETL is not a Spark replacement. It just provides additional functionality that Spark does not have, and improves UX for end users. -* onETL is not a framework, as it does not have requirements to project structure, naming, the way of running ETL/ELT processes, configuration, etc. All of that should be implemented in some other tool. -* onETL is deliberately developed without any integration with scheduling software like Apache Airflow. All integrations should be implemented as separated tools. -* Only batch operations, no streaming. For streaming prefer [Apache Flink](https://flink.apache.org/). - -## Requirements - -* **Python 3.7 - 3.13** -* PySpark 2.3.x - 3.5.x (depends on used connector) -* Java 8+ (required by Spark, see below) -* Kerberos libs & GCC (required by `Hive`, `HDFS` and `SparkHDFS` connectors) - -## Supported storages - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeStoragePowered by
DatabaseClickhouseApache Spark JDBC Data Source
MSSQL
MySQL
Postgres
Oracle
Teradata
HiveApache Spark Hive integration
KafkaApache Spark Kafka integration
GreenplumVMware Greenplum Spark connector
MongoDBMongoDB Spark connector
FileHDFSHDFS Python client
S3minio-py client
SFTPParamiko library
FTPFTPUtil library
FTPS
WebDAVWebdavClient3 library
Sambapysmb library
Files as DataFrameSparkLocalFSApache Spark File Data Source
SparkHDFS
SparkS3Hadoop AWS library
- - - - -## Documentation - -See [https://onetl.readthedocs.io/](https://onetl.readthedocs.io/) - -## How to install - - - -### Minimal installation - - - -Base `onetl` package contains: - -* `DBReader`, `DBWriter` and related classes -* `FileDownloader`, `FileUploader`, `FileMover` and related classes, like file filters & limits -* `FileDFReader`, `FileDFWriter` and related classes, like file formats -* Read Strategies & HWM classes -* Plugins support - -It can be installed via: - -```bash -pip install onetl -``` - -```{admonition} WARNING -:class: warning - -This method does NOT include any connections. - -This method is recommended for use in third-party libraries which require for `onetl` to be installed, but do not use its connection classes. -``` - -### With DB and FileDF connections - - - -All DB connection classes (`Clickhouse`, `Greenplum`, `Hive` and others) -and all FileDF connection classes (`SparkHDFS`, `SparkLocalFS`, `SparkS3`) -require Spark to be installed. - - - -Firstly, you should install JDK. The exact installation instruction depends on your OS, here are some examples: - -```bash -yum install java-1.8.0-openjdk-devel # CentOS 7 + Spark 2 -dnf install java-11-openjdk-devel # CentOS 8 + Spark 3 -apt-get install openjdk-11-jdk # Debian-based + Spark 3 -``` - - - -#### Compatibility matrix - -| Spark | Python | Java | Scala | -|-----------------------------------------------------------|------------|------------|---------| -| [2.3.x](https://spark.apache.org/docs/2.3.1/#downloading) | 3.7 only | 8 only | 2.11 | -| [2.4.x](https://spark.apache.org/docs/2.4.8/#downloading) | 3.7 only | 8 only | 2.11 | -| [3.2.x](https://spark.apache.org/docs/3.2.4/#downloading) | 3.7 - 3.10 | 8u201 - 11 | 2.12 | -| [3.3.x](https://spark.apache.org/docs/3.3.4/#downloading) | 3.7 - 3.12 | 8u201 - 17 | 2.12 | -| [3.4.x](https://spark.apache.org/docs/3.4.4/#downloading) | 3.7 - 3.12 | 8u362 - 20 | 2.12 | -| [3.5.x](https://spark.apache.org/docs/3.5.5/#downloading) | 3.8 - 3.13 | 8u371 - 20 | 2.12 | - - - -Then you should install PySpark via passing `spark` to `extras`: - -```bash -pip install onetl[spark] # install latest PySpark -``` - -or install PySpark explicitly: - -```bash -pip install onetl pyspark==3.5.5 # install a specific PySpark version -``` - -or inject PySpark to `sys.path` in some other way BEFORE creating a class instance. -**Otherwise connection object cannot be created.** - -### With File connections - - - -All File (but not *FileDF*) connection classes (`FTP`, `SFTP`, `HDFS` and so on) requires specific Python clients to be installed. - -Each client can be installed explicitly by passing connector name (in lowercase) to `extras`: - -```bash -pip install onetl[ftp] # specific connector -pip install onetl[ftp,ftps,sftp,hdfs,s3,webdav,samba] # multiple connectors -``` - -To install all file connectors at once you can pass `files` to `extras`: - -```bash -pip install onetl[files] -``` - -**Otherwise class import will fail.** - -### With Kerberos support - - - -Most of Hadoop instances set up with Kerberos support, -so some connections require additional setup to work properly. - -* `HDFS` - Uses [requests-kerberos](https://pypi.org/project/requests-kerberos/) and - [GSSApi](https://pypi.org/project/gssapi/) for authentication. - It also uses `kinit` executable to generate Kerberos ticket. -* `Hive` and `SparkHDFS` - require Kerberos ticket to exist before creating Spark session. - -So you need to install OS packages with: - -* `krb5` libs -* Headers for `krb5` -* `gcc` or other compiler for C sources - -The exact installation instruction depends on your OS, here are some examples: - -```bash -apt install libkrb5-dev krb5-user gcc # Debian-based -dnf install krb5-devel krb5-libs krb5-workstation gcc # CentOS, OracleLinux -``` - -Also you should pass `kerberos` to `extras` to install required Python packages: - -```bash -pip install onetl[kerberos] -``` - -### Full bundle - - - -To install all connectors and dependencies, you can pass `all` into `extras`: - -```bash -pip install onetl[all] - -# this is just the same as -pip install onetl[spark,files,kerberos] -``` - - -```{admonition} WARNING -:class: warning - -This method consumes a lot of disk space, and requires for Java & Kerberos libraries to be installed into your OS. -``` - - - -## Quick start - -### MSSQL → Hive - -Read data from MSSQL, transform & write to Hive. - -```bash -# install onETL and PySpark -pip install onetl[spark] -``` - -```python -# Import pyspark to initialize the SparkSession -from pyspark.sql import SparkSession - -# import function to setup onETL logging -from onetl.log import setup_logging - -# Import required connections -from onetl.connection import MSSQL, Hive - -# Import onETL classes to read & write data -from onetl.db import DBReader, DBWriter - -# change logging level to INFO, and set up default logging format and handler -setup_logging() - -# Initialize new SparkSession with MSSQL driver loaded -maven_packages = MSSQL.get_packages() -spark = ( - SparkSession.builder.appName("spark_app_onetl_demo") - .config("spark.jars.packages", ",".join(maven_packages)) - .enableHiveSupport() # for Hive - .getOrCreate() -) - -# Initialize MSSQL connection and check if database is accessible -mssql = MSSQL( - host="mssqldb.demo.com", - user="onetl", - password="onetl", - database="Telecom", - spark=spark, - # These options are passed to MSSQL JDBC Driver: - extra={"applicationIntent": "ReadOnly"}, -).check() - -# >>> INFO:|MSSQL| Connection is available - -# Initialize DBReader -reader = DBReader( - connection=mssql, - source="dbo.demo_table", - columns=["on", "etl"], - # Set some MSSQL read options: - options=MSSQL.ReadOptions(fetchsize=10000), -) - -# checks that there is data in the table, otherwise raises exception -reader.raise_if_no_data() - -# Read data to DataFrame -df = reader.run() -df.printSchema() -# root -# |-- id: integer (nullable = true) -# |-- phone_number: string (nullable = true) -# |-- region: string (nullable = true) -# |-- birth_date: date (nullable = true) -# |-- registered_at: timestamp (nullable = true) -# |-- account_balance: double (nullable = true) - -# Apply any PySpark transformations -from pyspark.sql.functions import lit - -df_to_write = df.withColumn("engine", lit("onetl")) -df_to_write.printSchema() -# root -# |-- id: integer (nullable = true) -# |-- phone_number: string (nullable = true) -# |-- region: string (nullable = true) -# |-- birth_date: date (nullable = true) -# |-- registered_at: timestamp (nullable = true) -# |-- account_balance: double (nullable = true) -# |-- engine: string (nullable = false) - -# Initialize Hive connection -hive = Hive(cluster="rnd-dwh", spark=spark) - -# Initialize DBWriter -db_writer = DBWriter( - connection=hive, - target="dl_sb.demo_table", - # Set some Hive write options: - options=Hive.WriteOptions(if_exists="replace_entire_table"), -) - -# Write data from DataFrame to Hive -db_writer.run(df_to_write) - -# Success! -``` - -### SFTP → HDFS - -Download files from SFTP & upload them to HDFS. - -```bash -# install onETL with SFTP and HDFS clients, and Kerberos support -pip install onetl[hdfs,sftp,kerberos] -``` - -```python -# import function to setup onETL logging -from onetl.log import setup_logging - -# Import required connections -from onetl.connection import SFTP, HDFS - -# Import onETL classes to download & upload files -from onetl.file import FileDownloader, FileUploader - -# import filter & limit classes -from onetl.file.filter import Glob, ExcludeDir -from onetl.file.limit import MaxFilesCount - -# change logging level to INFO, and set up default logging format and handler -setup_logging() - -# Initialize SFTP connection and check it -sftp = SFTP( - host="sftp.test.com", - user="someuser", - password="somepassword", -).check() - -# >>> INFO:|SFTP| Connection is available - -# Initialize downloader -file_downloader = FileDownloader( - connection=sftp, - source_path="/remote/tests/Report", # path on SFTP - local_path="/local/onetl/Report", # local fs path - filters=[ - # download only files matching the glob - Glob("*.csv"), - # exclude files from this directory - ExcludeDir("/remote/tests/Report/exclude_dir/"), - ], - limits=[ - # download max 1000 files per run - MaxFilesCount(1000), - ], - options=FileDownloader.Options( - # delete files from SFTP after successful download - delete_source=True, - # mark file as failed if it already exist in local_path - if_exists="error", - ), -) - -# Download files to local filesystem -download_result = downloader.run() - -# Method run returns a DownloadResult object, -# which contains collection of downloaded files, divided to 4 categories -download_result - -# DownloadResult( -# successful=[ -# LocalPath('/local/onetl/Report/file_1.json'), -# LocalPath('/local/onetl/Report/file_2.json'), -# ], -# failed=[FailedRemoteFile('/remote/onetl/Report/file_3.json')], -# ignored=[RemoteFile('/remote/onetl/Report/file_4.json')], -# missing=[], -# ) - -# Raise exception if there are failed files, or there were no files in the remote filesystem -download_result.raise_if_failed() or download_result.raise_if_empty() - -# Do any kind of magic with files: rename files, remove header for csv files, ... -renamed_files = my_rename_function(download_result.success) - -# function removed "_" from file names -# [ -# LocalPath('/home/onetl/Report/file1.json'), -# LocalPath('/home/onetl/Report/file2.json'), -# ] - -# Initialize HDFS connection -hdfs = HDFS( - host="my.name.node", - user="someuser", - password="somepassword", # or keytab -) - -# Initialize uploader -file_uploader = FileUploader( - connection=hdfs, - target_path="/user/onetl/Report/", # hdfs path -) - -# Upload files from local fs to HDFS -upload_result = file_uploader.run(renamed_files) - -# Method run returns a UploadResult object, -# which contains collection of uploaded files, divided to 4 categories -upload_result - -# UploadResult( -# successful=[RemoteFile('/user/onetl/Report/file1.json')], -# failed=[FailedLocalFile('/local/onetl/Report/file2.json')], -# ignored=[], -# missing=[], -# ) - -# Raise exception if there are failed files, or there were no files in the local filesystem, or some input file is missing -upload_result.raise_if_failed() or upload_result.raise_if_empty() or upload_result.raise_if_missing() - -# Success! -``` - -### S3 → Postgres - -Read files directly from S3 path, convert them to dataframe, transform it and then write to a database. - -```bash -# install onETL and PySpark -pip install onetl[spark] -``` - -```python -# Import pyspark to initialize the SparkSession -from pyspark.sql import SparkSession - -# import function to setup onETL logging -from onetl.log import setup_logging - -# Import required connections -from onetl.connection import Postgres, SparkS3 - -# Import onETL classes to read files -from onetl.file import FileDFReader -from onetl.file.format import CSV - -# Import onETL classes to write data -from onetl.db import DBWriter - -# change logging level to INFO, and set up default logging format and handler -setup_logging() - -# Initialize new SparkSession with Hadoop AWS libraries and Postgres driver loaded -maven_packages = SparkS3.get_packages(spark_version="3.5.5") + Postgres.get_packages() -exclude_packages = SparkS3.get_exclude_packages() -spark = ( - SparkSession.builder.appName("spark_app_onetl_demo") - .config("spark.jars.packages", ",".join(maven_packages)) - .config("spark.jars.excludes", ",".join(exclude_packages)) - .getOrCreate() -) - -# Initialize S3 connection and check it -spark_s3 = SparkS3( - host="s3.test.com", - protocol="https", - bucket="my-bucket", - access_key="somekey", - secret_key="somesecret", - # Access bucket as s3.test.com/my-bucket - extra={"path.style.access": True}, - spark=spark, -).check() - -# >>> INFO:|SparkS3| Connection is available - -# Describe file format and parsing options -csv = CSV( - delimiter=";", - header=True, - encoding="utf-8", -) - -# Describe DataFrame schema of files -from pyspark.sql.types import ( - DateType, - DoubleType, - IntegerType, - StringType, - StructField, - StructType, - TimestampType, -) - -df_schema = StructType( - [ - StructField("id", IntegerType()), - StructField("phone_number", StringType()), - StructField("region", StringType()), - StructField("birth_date", DateType()), - StructField("registered_at", TimestampType()), - StructField("account_balance", DoubleType()), - ], -) - -# Initialize file df reader -reader = FileDFReader( - connection=spark_s3, - source_path="/remote/tests/Report", # path on S3 there *.csv files are located - format=csv, # file format with specific parsing options - df_schema=df_schema, # columns & types -) - -# Read files directly from S3 as Spark DataFrame -df = reader.run() - -# Check that DataFrame schema is same as expected -df.printSchema() -# root -# |-- id: integer (nullable = true) -# |-- phone_number: string (nullable = true) -# |-- region: string (nullable = true) -# |-- birth_date: date (nullable = true) -# |-- registered_at: timestamp (nullable = true) -# |-- account_balance: double (nullable = true) - -# Apply any PySpark transformations -from pyspark.sql.functions import lit - -df_to_write = df.withColumn("engine", lit("onetl")) -df_to_write.printSchema() -# root -# |-- id: integer (nullable = true) -# |-- phone_number: string (nullable = true) -# |-- region: string (nullable = true) -# |-- birth_date: date (nullable = true) -# |-- registered_at: timestamp (nullable = true) -# |-- account_balance: double (nullable = true) -# |-- engine: string (nullable = false) - -# Initialize Postgres connection -postgres = Postgres( - host="192.169.11.23", - user="onetl", - password="somepassword", - database="mydb", - spark=spark, -) - -# Initialize DBWriter -db_writer = DBWriter( - connection=postgres, - # write to specific table - target="public.my_table", - # with some writing options - options=Postgres.WriteOptions(if_exists="append"), -) - -# Write DataFrame to Postgres table -db_writer.run(df_to_write) - -# Success! -``` diff --git a/mddocs/_static/icon.svg b/mddocs/_static/icon.svg deleted file mode 100644 index a4d737f81..000000000 --- a/mddocs/_static/icon.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/mddocs/_static/logo.svg b/mddocs/_static/logo.svg deleted file mode 100644 index 76527ebf1..000000000 --- a/mddocs/_static/logo.svg +++ /dev/null @@ -1,214 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/mddocs/_static/logo_wide.svg b/mddocs/_static/logo_wide.svg deleted file mode 100644 index 981bf0148..000000000 --- a/mddocs/_static/logo_wide.svg +++ /dev/null @@ -1,329 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/mddocs/changelog/DRAFT.md b/mddocs/changelog/DRAFT.md deleted file mode 100644 index 5b77149b5..000000000 --- a/mddocs/changelog/DRAFT.md +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/mddocs/concepts.md b/mddocs/concepts.md deleted file mode 100644 index ef737cdc2..000000000 --- a/mddocs/concepts.md +++ /dev/null @@ -1,349 +0,0 @@ -# Concepts - -Here you can find detailed documentation about each one of the onETL concepts and how to use them. - -## Connection - -### Basics - -onETL is used to pull and push data into other systems, and so it has a first-class `Connection` concept for storing credentials that are used to communicate with external systems. - -A `Connection` is essentially a set of parameters, such as username, password, hostname. - -To create a connection to a specific storage type, you must use a class that matches the storage type. The class name is the same as the storage type name (`Oracle`, `MSSQL`, `SFTP`, etc): - -```python -from onetl.connection import SFTP - -sftp = SFTP( - host="sftp.test.com", - user="onetl", - password="onetl", -) -``` - -All connection types are inherited from the parent class `BaseConnection`. - -### Class diagram - -```{uml} -...uml:: - -@startuml -Bob -> Cat -@enduml -``` - -### DBConnection - -Classes inherited from `DBConnection` could be used for accessing databases. - -A `DBConnection` could be instantiated as follows: - -```python -from onetl.connection import MSSQL - -mssql = MSSQL( - host="mssqldb.demo.com", - user="onetl", - password="onetl", - database="Telecom", - spark=spark, -) -``` - -where **spark** is the current SparkSession. -`onETL` uses `Spark` and specific Java connectors under the hood to work with databases. - -For a description of other parameters, see the documentation for the [available DBConnections](connection/db_connection/index.md#db-connections). - -### FileConnection - -Classes inherited from `FileConnection` could be used to access files stored on the different file systems/file servers - -A `FileConnection` could be instantiated as follows: - -```python -from onetl.connection import SFTP - -sftp = SFTP( - host="sftp.test.com", - user="onetl", - password="onetl", -) -``` - -For a description of other parameters, see the documentation for the [available FileConnections](connection/file_connection/index.md#file-connections). - -### FileDFConnection - -Classes inherited from `FileDFConnection` could be used for accessing files as Spark DataFrames. - -A `FileDFConnection` could be instantiated as follows: - -```python -from onetl.connection import SparkHDFS - -spark_hdfs = SparkHDFS( - host="namenode1.domain.com", - cluster="mycluster", - spark=spark, -) -``` - -where **spark** is the current SparkSession. -`onETL` uses `Spark` and specific Java connectors under the hood to work with DataFrames. - -For a description of other parameters, see the documentation for the [available FileDFConnections](connection/file_df_connection/index.md#file-df-connections). - -### Checking connection availability - -Once you have created a connection, you can check the database/filesystem availability using the method `check()`: - -```python -mssql.check() -sftp.check() -spark_hdfs.check() -``` - -It will raise an exception if database/filesystem cannot be accessed. - -This method returns connection itself, so you can create connection and immediately check its availability: - -```Python -mssql = MSSQL( - host="mssqldb.demo.com", - user="onetl", - password="onetl", - database="Telecom", - spark=spark, -).check() # <-- -``` - -## Extract/Load data - -### Basics - -As we said above, onETL is used to extract data from and load data into remote systems. - -onETL provides several classes for this: - -> * [DBReader](db/db_reader.md#db-reader) -> * [DBWriter](db/db_writer.md#db-writer) -> * [FileDFReader](file_df/file_df_reader/file_df_reader.md#file-df-reader) -> * [FileDFWriter](file_df/file_df_writer/file_df_writer.md#file-df-writer) -> * [FileDownloader](file/file_downloader/file_downloader.md#file-downloader) -> * [FileUploader](file/file_uploader/file_uploader.md#file-uploader) -> * [FileMover](file/file_mover/file_mover.md#file-mover) - -All of these classes have a method `run()` that starts extracting/loading the data: - -```python -from onetl.db import DBReader, DBWriter - -reader = DBReader( - connection=mssql, - source="dbo.demo_table", - columns=["column_1", "column_2"], -) - -# Read data as Spark DataFrame -df = reader.run() - -db_writer = DBWriter( - connection=hive, - target="dl_sb.demo_table", -) - -# Save Spark DataFrame to Hive table -writer.run(df) -``` - -### Extract data - -To extract data you can use classes: - -| | Use case | Connection | `run()` gets | `run()` returns | -|--|--|--|--|--| -| [DBReader](db/db_reader.md#db-reader) | Reading data from a database | Any [DBConnection](connection/db_connection/index.md#db-connections) | - | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | -| [FileDFReader](file_df/file_df_reader/file_df_reader.md#file-df-reader) | Read data from a file or set of files | Any [FileDFConnection](connection/file_df_connection/index.md#file-df-connections) | No input, or List[File path on FileSystem] | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | -| [FileDownloader](db/db_reader.md#db-reader) | Download files from remote FS to local FS | Any [FileConnection](connection/file_connection/index.md#file-connections) | No input, or List[File path on remote FileSystem] | [DownloadResult](file/file_downloader/result.md#file-downloader-result) | - -### Load data - -To load data you can use classes: - -| | Use case | Connection | `run()` gets | `run()` returns | -|--|--|--|--|--| -| [DBWriter](db/db_writer.md#db-writer) | Writing data from a DataFrame to a database | Any [DBConnection](connection/db_connection/index.md#db-connections) | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | None | -| [FileDFWriter](db/db_writer.md#db-writer) | Writing data from a DataFrame to a folder | Any [FileDFConnection](connection/file_df_connection/index.md#file-df-connections) | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | None | -| [FileUploader](file/file_uploader/file_uploader.md#file-uploader) | Uploading files from a local FS to remote FS | Any [FileConnection](connection/file_connection/index.md#file-connections) | List[File path on local FileSystem] | [UploadResult](file/file_uploader/result.md#file-uploader-result) | - -### Manipulate data - -To manipulate data you can use classes: - -| | Use case | Connection | `run()` gets | `run()` returns | -|--|--|--|--|--| -| [FileMover](file/file_mover/file_mover.md#file-mover) | Move files between directories in remote FS | Any [FileConnection](connection/file_connection/index.md#file-connections) | List[File path on remote FileSystem] | [MoveResult](file/file_mover/result.md#file-mover-result) | - -### Options - -Extract and load classes have a `options` parameter, which has a special meaning: - -> * all other parameters - *WHAT* we extract / *WHERE* we load to -> * `options` parameter - *HOW* we extract/load data - -```python -db_reader = DBReader( - # WHAT do we read: - connection=mssql, - source="dbo.demo_table", # some table from MSSQL - columns=["column_1", "column_2"], # but only specific set of columns - where="column_2 > 1000", # only rows matching the clause - # HOW do we read: - options=MSSQL.ReadOptions( - numPartitions=10, # read in 10 parallel jobs - partitionColumn="id", # balance data read by assigning each job a part of data using `hash(id) mod N` expression - partitioningMode="hash", - fetchsize=1000, # each job will fetch block of 1000 rows each on every read attempt - ), -) - -db_writer = DBWriter( - # WHERE do we write to - to some table in Hive - connection=hive, - target="dl_sb.demo_table", - # HOW do we write - overwrite all the data in the existing table - options=Hive.WriteOptions(if_exists="replace_entire_table"), -) - -file_downloader = FileDownloader( - # WHAT do we download - files from some dir in SFTP - connection=sftp, - source_path="/source", - filters=[Glob("*.csv")], # only CSV files - limits=[MaxFilesCount(1000)], # 1000 files max - # WHERE do we download to - a specific dir on local FS - local_path="/some", - # HOW do we download: - options=FileDownloader.Options( - delete_source=True, # after downloading each file remove it from source_path - if_exists="replace_file", # replace existing files in the local_path - ), -) - -file_uploader = FileUploader( - # WHAT do we upload - files from some local dir - local_path="/source", - # WHERE do we upload to- specific remote dir in HDFS - connection=hdfs, - target_path="/some", - # HOW do we upload: - options=FileUploader.Options( - delete_local=True, # after uploading each file remove it from local_path - if_exists="replace_file", # replace existing files in the target_path - ), -) - -file_mover = FileMover( - # WHAT do we move - files in some remote dir in HDFS - source_path="/source", - connection=hdfs, - # WHERE do we move files to - target_path="/some", # a specific remote dir within the same HDFS connection - # HOW do we load - replace existing files in the target_path - options=FileMover.Options(if_exists="replace_file"), -) - -file_df_reader = FileDFReader( - # WHAT do we read - *.csv files from some dir in S3 - connection=s3, - source_path="/source", - file_format=CSV(), - # HOW do we read - load files from /source/*.csv, not from /source/nested/*.csv - options=FileDFReader.Options(recursive=False), -) - -file_df_writer = FileDFWriter( - # WHERE do we write to - as .csv files in some dir in S3 - connection=s3, - target_path="/target", - file_format=CSV(), - # HOW do we write - replace all existing files in /target, if exists - options=FileDFWriter.Options(if_exists="replace_entire_directory"), -) -``` - -More information about `options` could be found on [DB connection](connection/db_connection/index.md#db-connections) and -[File Downloader](file/file_downloader/file_downloader.md#file-downloader) / [File Uploader](file/file_uploader/file_uploader.md#file-uploader) / [File Mover](file/file_mover/file_mover.md#file-mover) / [FileDF Reader](file_df/file_df_reader/file_df_reader.md#file-df-reader) / [FileDF Writer](file_df/file_df_writer/file_df_writer.md#file-df-writer) documentation - -### Read Strategies - -onETL have several builtin strategies for reading data: - -1. [Snapshot strategy](strategy/snapshot_strategy.html) (default strategy) -2. [Incremental strategy](strategy/incremental_strategy.html) -3. [Snapshot batch strategy](strategy/snapshot_batch_strategy.html) -4. [Incremental batch strategy](strategy/incremental_batch_strategy.html) - -For example, an incremental strategy allows you to get only new data from the table: - -```python -from onetl.strategy import IncrementalStrategy - -reader = DBReader( - connection=mssql, - source="dbo.demo_table", - hwm_column="id", # detect new data based on value of "id" column -) - -# first run -with IncrementalStrategy(): - df = reader.run() - -sleep(3600) - -# second run -with IncrementalStrategy(): - # only rows, that appeared in the source since previous run - df = reader.run() -``` - -or get only files which were not downloaded before: - -```python -from onetl.strategy import IncrementalStrategy - -file_downloader = FileDownloader( - connection=sftp, - source_path="/remote", - local_path="/local", - hwm_type="file_list", # save all downloaded files to a list, and exclude files already present in this list -) - -# first run -with IncrementalStrategy(): - files = file_downloader.run() - -sleep(3600) - -# second run -with IncrementalStrategy(): - # only files, that appeared in the source since previous run - files = file_downloader.run() -``` - -Most of strategies are based on [HWM](hwm_store/index.md#hwm), Please check each strategy documentation for more details - -### Why just not use Connection class for extract/load? - -Connections are very simple, they have only a set of some basic operations, -like `mkdir`, `remove_file`, `get_table_schema`, and so on. - -High-level operations, like: - * [Read Strategies](strategy/index.md#strategy) support - * Handling metadata push/pull - * Handling different options, like `if_exists="replace_file"` in case of file download/upload - -is moved to a separate class which calls the connection object methods to perform some complex logic. diff --git a/mddocs/conf.py b/mddocs/conf.py deleted file mode 100644 index bb6508a95..000000000 --- a/mddocs/conf.py +++ /dev/null @@ -1,157 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. - - -import os -import subprocess -import sys -from pathlib import Path - -from packaging.version import Version - -PROJECT_ROOT_DIR = Path(__file__).parent.parent.resolve() - -sys.path.insert(0, os.fspath(PROJECT_ROOT_DIR)) - -# -- Project information ----------------------------------------------------- - -project = "onETL" -copyright = "2021-2024 MTS PJSC" -author = "DataOps.ETL" - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. - -ver = Version(subprocess.check_output("python ../setup.py --version", shell=True, text=True).strip()) -version = ver.base_version -# The full version, including alpha/beta/rc tags. -release = ver.public - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - "numpydoc", - "sphinx_design", - "sphinx_substitution_extensions", - "sphinx_tabs.tabs", - "sphinx_toolbox.more_autodoc.autoprotocol", - "sphinx_toolbox.github", - "sphinx_copybutton", - "sphinx.ext.autodoc", - "sphinx.ext.autosummary", - "sphinxcontrib.towncrier", # provides `towncrier-draft-entries` directive - "sphinxcontrib.plantuml", - "sphinx.ext.extlinks", - "sphinx_favicon", - "sphinxcontrib.autodoc_pydantic", - "sphinx_last_updated_by_git", - # "sphinx_markdown_builder", - "myst_parser", -] - -myst_enable_extensions = [ - 'colon_fence', - 'attrs_block', - # ... other extensions -] - -numpydoc_show_class_members = False -autodoc_pydantic_model_show_config = False -autodoc_pydantic_model_show_config_summary = False -autodoc_pydantic_model_show_config_member = False -autodoc_pydantic_model_show_json = False -autodoc_pydantic_model_show_validator_summary = False -autodoc_pydantic_model_show_validator_members = False -autodoc_pydantic_field_list_validators = False -sphinx_tabs_disable_tab_closing = True - -# prevent >>>, ... and doctest outputs from copying -copybutton_prompt_text = r">>> |\.\.\. |\$ |In \[\d*\]: | {2,5}\.\.\.: | {5,8}: " -copybutton_prompt_is_regexp = True -copybutton_copy_empty_lines = False -copybutton_only_copy_prompt_lines = True - -towncrier_draft_autoversion_mode = "draft" -towncrier_draft_include_empty = False -towncrier_draft_working_directory = PROJECT_ROOT_DIR - -github_username = "MobileTeleSystems" -github_repository = "onetl" - -rst_prolog = f""" -.. |support_hooks| image:: https://img.shields.io/badge/%20-support%20hooks-blue - :target: https://onetl.readthedocs.io/en/{ver}/hooks/index.html -""" - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. - -html_theme = "furo" - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -html_theme_options = {} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] -html_extra_path = ["robots.txt"] -html_logo = "./_static/logo.svg" -favicons = [ - {"rel": "icon", "href": "icon.svg", "type": "image/svg+xml"}, -] - -# The master toctree document. -master_doc = "index" - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = "en" - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = "sphinx" - -# Create an alias for etl-entities lib in onetl documentation -extlinks = { - "etl-entities": ("https://etl-entities.readthedocs.io/en/stable/%s", None), -} - - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - -# -- Options for HTMLHelp output ------------------------------------------ - -# Output file base name for HTML help builder. -htmlhelp_basename = "onetl-doc" diff --git a/mddocs/doctrees/changelog.doctree b/mddocs/doctrees/changelog.doctree new file mode 100644 index 0000000000000000000000000000000000000000..9a5a8b8843ee25c232d790ef7b66528c9e68a0a1 GIT binary patch literal 3515 zcmc&%>x*196mNGsvoo_h`*N#-tt{fB6?Uer7Nv;d14I`0gNUFgm(0DHN!Z-vn&h^# z(1Hq;F63LIe}`ZE|NJHQF%R^EesN*w&B@8hdHjASr$2Rm`(t<|{<%F|>fAg&<~f;!wS{>w9`k%QGK^D^nGsjx z3CU)3{KmaIC-pge#*-%#QPRON{vKGOXVflCC`qjtX-YE7DEy3us2txJ-F;_te;kjG zxy9xy@2@@9zJn0(mz`iOhILDsSA? zk34(Zly1wCS-94-7hShO+0F;r*rd=(sM&|!T@zeVf4bqi1wmQh%wahp>;{5jucBhd z_`QbT>-fEipt|rp`ubku>%T;TFWHFgvwLjkZ|^xG%~e5fz{czjyL-9;{Y}?TS^}}n zaso7M8JeyUfOP}XZegpAa?C*IuCSoOIuHU!buvKZp8fmAbnP_~&$NfDxj{HZR68N8><~aoV^rLLEF}t0L*8Dlb$ivn9A! zp|C3`q)M%(%dymZ|5PV-&-L4z083O>^9aT>x7Th|1ogjq;&!-9MHyO(h-xe+u9uT} zMhnaQ=~W<`6fxDysdCq5LQVXo>oT5YXj_+VSQ}~uJz3@xP(JggSKJ`h#3(5)pgTbK zTX+_i%`MD?tR|@cx5!Zk+5LXK1a7ZR%gG~6ih^oSmZ2KXf48Vgu!Abn7TU`RA`Q-0 zg-iAQ1eP|d`bLjXwS}qXbOYU%ZR}fjWHpgSkl3<8yP2k@>KPrYED=>!YMq#*GI)N~ z?WUy;j7)0gp1GYy5;=S3dWKrdpj=m#wkWMXW1pY7 z{)|ehiB;NT5qpOvd4a{YRw{G|U}5gv)vU(k!H%j84iKuG@V?v_ux6f#)oTq>qxN zG=voV;}SVb@M5V9TIdv|ou0gZVmQ8UtZpV$Yo!w~JQIK;V1U?C>)l?J`6VlzXvMpm zYddQu*q?5vph9N%!0eLqRXn!SU0i-*_dIOAgFKC)7onf3P z02^Er`ox**rbz^}a)Oca#}bq_NYo2)T`d!#Qc^t}?k1iW_=Cb%WJ8u?3UT_p zEO12_YvVIe_W-;ZkZ?H#2I{787`g<8-8Hp1{oyWBF)_?8=N^#3`;vja7w~{lYH`Q) z6k6vA%?TF?Ch*X7qt^%Z!~m>{Y1Pm!-6oX}Jst(*pea#U!xlwKvX&xrkg{bf%%CyT!1Qot zdNkcb0Ji1ZiIlYC93vlzvp(XDH;J8VT}d`^oV7PeRW`28RyMWSO6?{UC(a(O%~mB& zWshuXzyE*lb-#W+({tb;r6r00XS(0x@u zvE~&WzZLE)dWB}ysRgYcZB4wt^;~N_9BJ6AzSnFNoL0C4B?=|CQfxT2)+M|=NaX{! z;^$-ouk@Qse&7ap=+*K?XW6Z}^f79$G!%X;m$TmOH|uq;5#&prciDfp=X9I+dcIbG_*$ySU;^zi#&UFk{+9Io3S3xGM?T(V1dVo;^KKrsuyWB7e7e(%Ph zh5xO1qm&;HJI4 zQ!oUeG)$Z}j+aA_luCllZU%QA&mFhMXOGXGI5snTZ06L2VzxaOoi!57ux+6aw^&{Q z#v`TErQN0Jh0@GI+W|;K5%dqE_*qdrJPwhlI@3P*#%@dp)%rB!ZJI=9x=^xfD-H-k z;sXG4AW@5kjX|CfnxZtM2KE;9AUyN{Foe# zh;e?yQnONVf_%jVkg*Ia@F3#!ChrDLReSyPcZPTDc&fhAu#1khxCrHO4BQ6o<@k$> z)^fwET32SQz_YH*TH|voZcu72&DGJ<+~Ul=vx^hC(b0<~*M};rS5U_aN_K#D-txBd zs4j)h0tZ_)Z`CTsHfuis%>ZKj18T z4QFn26dJSujScPR6)r<<*4KlQR~v;QWH0)S-MzV%q8 zD896lOoQ zt$R&t)vg6mIf<^ZB#tC5owYi!<8vE4Y}L9s>dDDSz=ejGo1C;BS?2DTZHd(s?=#P^ zU`5Oc$*6k8X2qG}Dt^gpR*F``S#3Z#=~09EoTRR5Uv?}v!1rq14qTAbT7?W+HD}ej z;xq_YNC*U@*zoE_Z?y)oI1g#DicVm|LLp8(mXb#Xa%mmR=v;BVrf+$*2QNOV8uYDI zSONg?3M8;S_<$@qBqeVc8f+mkx}0^91et`tL=8fUqj9(uH(1AzZQrZ;Qx+y{E?2ME zwHnL4wdCNnfWfV$HPe+7cwIq#C&xov076W)!|Gu8-=4g-u#3~L;eS%pry4CPiM<+8Z=Y}2l|%dS&o21V&5)?Uz)M2L&)b;m*h zfb9znw+h43v3#fD;_*9a&OnEtp8iCQ%UddAT7`}!>=)fafaV(xF)h>{xWlUn$_b_< z?di>-B-AVle)D^O1fRjz&}eynM5hL;mfZ}zD&Q#CmC8DJr3Mzqc=62wN?{cX zc0>~zMS%^H229(e>m_Yk4BxnsIg#i4@zO%L zPqosDC;Bygq9_ir)Jnb0$_6#D0B()*AhQ-e_ktk*uHh!TMQ)#Q<|epfjG| zwFVPVhIfiJQyv^uS@PC$!Vbu}zTcz;)W{?BVF7bGtq5-3dN1GnCKA>7Yp^R;Gm z$!WCCeK8yn51NfiD?FiWR@pAJ-Sl~H36gOUGJatla$5DJVMeycG)#{cmM43T@CRDK zEXXf0o(%9nM-l-?gfw^!;J#cR7UjcL`sqF3wAVqaz!doY>@H(~>7P$YowjSeFzI_#Au zr5b7UJ?5T2?0vL~-Z#j8M#2M9r1Na4Mlw+vdU_d7h_n@{6laF9ny!>(s-{az`%+o9 z()=79ZEL2=!kc5HZlZ}QX0Of$s%EIk_2a^IjPn8`Y4G;GDCi60LV6$tf8l4jDn{0- zl{vp|lW%PwTQ7|(_&5an5RYC6_rVBUX~HuGuZCFSwZelXccoOpZ;*$>i+nqzX}>8o z?SYhp4~vzF5^(i3mhb=7zkJ^=d-?N7WByiJ&N{AcgrJ>a`%A2N#M&lvWz*ZbCSz68 zVNKIUP{NnTU=$CQ_d?TVv0>@2OMg@P&I_f#jhL+TcS)|gGsRUOy~b94a>5^h7cppf zwH0lq32$i!tCEMS~_E0H5E~wYUb8IlkVEm2t^*SeIb^gEf2&=5jl4-Y`tdhFj3y zh5PlDe}uBc!$<*^7y3b2`B5r8TYiN86V@$}yP`Cqaxy-v9)y3Q7Olz;^78UI2<%3d zec2|&uz$N(d%8@fpw9o@dA5KWEM)gZR&~Ev)%cTI)BE3u{H+;n^)Z$`(=&6Nv1karw86n%&u-`eIR@ zjzqaPZ)FC&y;0;CF#K!Wdp4AcB2+#_k)@6l*))HtNx5$kjyq89KO6Y(opOI=)Xc^S zTxVtKiz#ki;EZ(-rDz>xV~oQq3dY^| zrq#?34&o38akREFpg5R}^(bIQ;{cG>&a;EVv{Q4EGi`N~vNw6123H(suE^#dC9hOo z$ZO~J!(ovP(F%7SV^`5-&A^~lOmiu#vg_xI?v>W_;URcn+?86M;;)D>fs)7;lc83) zr@Xp@a3y?JdjdrETIEk;7~u#e8_jEjv8G2{QW2{YzePU3&oMSNUO@y2M&+GUch5?LV@oSY(i@@v`!ZeX){qozJ9P zYxgRjcq*8PU|&WkQH^5Y`jG?a*D#WeIgn1I$nxyA$2l1v6pDOe)4SFb$iFtIu1z2V z84`bBw3n?IXj&l%cv?v*$qd-K+F*Y%I2;MZXu{B0_vit{Q-Xi83oETi(Z(t%Utl3x zIz4?4ltjuA=p$ccbac`>&mn!vT6h2n35fPxK%#?_Lq6&F#3J$>kceQs@{z#B?>IvQ zd5JJtDbE1qP>aM;tLev2a_EhdK`0B1vI>yh!LAFhhCqLm(b6SvCWrPH7pJVrNy-4w z3ya_)@;pR-ty7$wM0SIol7pNwB!kuB?3yVHc@7x1)vQy3CMDU}g@V_h?19R9lphee zc*0xT;*sU6d?(PXTwmpVyM=8Ap}WaQ6!PaZp&6xey(TRxN9!;ubz4j;tvUy}*u~|u zJaw11kqv})j&NEYlc?fc2?d9SqSqQI()7wJ4AzuftMvcjG)rskzTRlJyM7@^q<$f} z(Avs?@s$RQN_-TMHz$A;Z?%@^2Z!<|kqM+psMc-<#0L_Btl%7pgj;LnvB9AmV%;L! z0+SLz{*p#qfT3xR2#n~N28KO27zq_^A|+evDiM1lptk@)H<46Yn##YL%g0JOe0JMa zqrhk+hhcrYAz%PV>fbO(&l>qZXpmoHqFW4iG0z@~mkuRd#dxK)`2$9q-L(Tj!P;Th zl&%^~;1csh_`06vF$M5zy3Tpp2!5iU5vZvp8bm@a%rW#P7xUor_cBtlk*DN>$_u&Z z+)V)r zdtbR@$JT!51PlPSkf2^)&@hKk7J&&t(5V%6Qc$Us3}C-1hHpQjE^QnK<9>tQFbk{(9R z9L5kvMS;gS&)b^Gu?dL12TM)25+GH&hTPlAIvPo(WvH|#k$5p>J%oWjiro;@Xq)1> zjNGaz>q(>~2hKx`ing^oT58MULG?MyDiA@^IK^^-u@HdAHaHK!a&C2Z; z9nq=A)E1filJ@-^N%nbvn|`wJ$)u5jHya7w3cO#)8;9;lW^Ru|m7IOxNvIg7F-?d* zi~RVCM_*|eN308uBee`RS0TY07Par#4Wxh*(U8nSDcltodmoY<0`WMCjPj#I;MAMV z;T(pKbWm=+N@?fdA0iTI$7GKLgG|`;e0*l5Lq#j@l6vttbm8Nk@2)+L=c){=JX*`< zd|Gd-vbvrY49vSGXb+3rAyUT{M%o-@`B-*izr+e}S_S&#UIOI*LtPZHl?qz{kPN>J z#F9E(x2qN6`n>3G-qO25_C0S_%JV4ajl$#%$TJhs>+vK8sAe6c3dX5%Du7J>wo2}Z z^{9uW{snd3#CBhJgULRlocv?gU~U0cyC=^$u%P*pNEjquN39{EC$gPK1Lvk zj)g7A8WWP!82)EMFmoCaRk4n5FFSdXFYFj=R_jy-mGd;U5M^k~pAXTAibeF|jl%f= zJ1oa1Vrr)9YG@P6TdLF})-w#QWxY%OZ?XQytS~lby~`RS{}4X0WJ1l2VXFo0E%f;H zbJlaT!|Dj{iyOn9A+c{Ja7CSXFX+XY<8#)D`{t}S+?zXb@4YAPJ;fWkWJNhciVm@1 zj{D;+b7Q<$4&BRX0RSj~qA7!*q8)csi}u_lU=p*XXj$!}qk@X}<}70?oz{cSt;tp? zR6wmjaP)C-P9{Ca_PXe~#L%T!t`dCP2K||MoSBI&n$rG89vN@EYpE$trE`^Mw-o;={ zD$91CFvyN;tSNH1U5$jD^3z!;N*HF8FIO>{32Efbb`dFf zEA4)Mu+i`u<700-eeum>5wE6XK4;ev8ZM4Yp+Z5lCTyitSpv4r!m#b+s+1#HL>pWr zDJ2NyZ)%gVQPN|0yHlDo~RPOPsL z#)!kRj1h&eh!Z!o4$`2@L|Nx)md__xr&->Z-n6C_S|XTrtSYQLV>B>LrK7nn**L$; zp877($qbClC>W;E-n%~HkW%`bR{QR z%zjvHQX5Ca7WQpfj3t+AyaIz2pIUn{RHWF1_(W20hG+Rl_`nPDYzSc^L6|>EGP*If zaWrOBeRI-L?YZFb`J=i8ti5IqR%b{E;9^B0K-5YnYD*3$j^`@hGl!Akn%On)zcR9FU9&ogBzPuR<0xeLMFx zz`kh!mKnHFF(Voc2fhxcg9KXJ=Ag4<8Y;MY8ZnT6&mENCq9s^}%|_+(+N1E08G%qJ zNxz6AGyk`*5z8%5|C=gBTcA24DICs7A{2>&o0vXW{sLlS17r@KiBMdMwzWRJ|JEHl zvZ6FyVl+prZV?)uk@Wzqa%72FD;b|j749QClddcM9IC^|)qzbZ1Dw_x(lEvB3rirL zQ8)P%Ct)aW${9`q;Pw%jiP4mClN_TNK-jEUj6f|Lm*j7yPI+z>XfrPx`uNpmL|-D2+F%W6ND&pWHd0>O9^)ypQo=eXcSF*nlZvLuYMUPgkPS!9 zAOwXr97&OFDnAL_2=I`|hsr-9qbT$gUZW!xTze_5G!UJMvy@9m_P6aA>pyB@pequm zG}5vnCORg8%FT$Cq+CUiBt%Mji<4|Yl%!jXWUvTHT&vgkh}01o(UA_}36ccmjMD-- zslWi0gFi(4MDPjG=^Pj7H7XJ{nCdqf6N$=gsAD4@5;fYk^JFw6DmyR$u`FexJua4o z^<)%elVcz~ML=ZyBN_e3j(wzLxJ~3Ep8X`o)$G@O@>Bl%jXN%5ZI`L%kIxy=4yiq3 zKN!uX1Ww#wG`X3P4ny;b)tQWQ$aiYK;wXo%W4p~(3NsOJZNyC(k0ld1ohHGIsD^=p z2->(ug@M5%8amoAHD$yyIJnU@lF==W(MJ@cgByio7??zRieU7#4H5QY+}ShG-x{D#nTSa3h|P8Q^YD{>G+Ij4yM4rFJ2_%X0SK~M=4Tb z|BxP`5Pb>EM5442!H?k2L{3)VfMOFmvcRod8<|KELc}EoB14;~1XBSc(jpSlmweqX zs@!*SBD?MvjSku$BuCv4_3M7oQr}tW*ZrdEr1{a&>wZzrKW}4QTr;`nhQwd@i^eO% z>weMee$m7=IH|0BVc?BLi*3V`eBCc9Tu{=*0$1v$WshI?i#DBPTDx=@Qc@Wu3wLnG zvb!_WF(?h`>weMee$jZR)&y3cMqVf7f|LG>f`ZEX2L{?;LTBuVOSmx7F~jSAQNGqm z#2b>O!z z?hA6H+noF2BT4y!gX1G`xvIK=feu+%Zr1p4fOc0ra+(~D{o~X8^yJ*dMRDsNB{Cl4 zYenbCsGTeE`3}b#-l~sXTJ&Vq#jVr$a^g7tk#RhUI}gS7*u_OYLq;Aeh}FK+V4F{W zkEqz1EPz!E3WyfCoQ0h@*y+;t_E(X|kiF4Io$;Z{;(korr5K+vKpPwMAN^!D^Bvsa znCKeL8M;&YNoNIkX{?)t8vNX8#9fHQ3A*uH!pD_uky@O{QUF5$%9 zw1PO^1hTVA4J2{qwL5Qqpp&WSR95Pbw}+Uwd}IL9_vJr}gM+nGKabOdDO&-{O)BSi z?8G=95aXn~&FR`@?NT1GSq0a84&&}+uUSvMqAN)EVj1Vx9sM#NS+C=L@B2`N)ottC zU341s2M7r`bBzHhe-QsEChXEu`6DRXrVW5KgdJCQF$FQ+?1s`$J?FdK5l(Ncr(S6y z@v4sVLB!v~qKL+MKSiU{j!I4Pt(;jRzUA*)?VmGhcjsH`kNI|#H>UD*r8Q`R(;KG@ z6Q}$nx10$<`LhJ?Z26yX(emf;pFzu5!Q38W)0{ms(E0n+L3?zL(nKwqQurJe547?B zkx@T$IIIY$M-8Doy=x7c0QH8@y@b$jaLbtxBoUg~cKK5@wAu0}dua9JqNor#78g3D zJ;nRwpFp`J0ms`BHQIj_{&ZHxkC>v{n=fgjMHvolOqFZ{MO93gC8jZ=F4=r3K^(1F z_781v&2oN}_-_QwbUFQ1@!v2P&8Gb?FztJ>(ylQUcZP{jZ z|HQr>JI<7FNv^}E0j}COxOExl+xR{mo2V{c#65kZh2VV5Y5K%m2M5oClX0jDFZzQ*u%P*L`sA^@Ln*4$^Cd`N1Z zgG?q0sc@VO-v(J!fDe$M5A?&>gD0qGN5xiRPZ7Bty3&Lf1E50di+E604Tam*mN?1z7f(P^_#6iJ0=lD!vo!TWXFX_Ej)u;O@B+RoeV zHK`QZg*4Ac5Asnm38v{=t6V$dnkTGk=jJ3OVF1Nr zeVLq$)Ey2lSNAMxcOr_TV&tj1aOu(132z|OTE#8k;x0IIX_G2#6UM|UPdTQ!6^S0n zla8A;nvMXvRr0(dj*23kT*Zkrg}7f;AII}Hd4{Iqa4Dcfq{`#z2UGGi-Xz(UJ+;J3 zi;JWFI*zYfgcxVPt7epS{w^QAXfy@PJmcY1GU%&)x^CjQVQpsLTLje@wr{MFq(| zMN$o%?;9MP#7!x<8=L{vEl&+8xM?fNH8E5NlP@&bJ~=pSBTDLYC#s3BY(rN$QIC*i z>@p3?>nV3h`8}ye;~R5vepz(2AvdXP;}a>^dUg23qBf9E^bZWmX`BFGem9@!9~-s1 ziwO0nx$sP%s0mJQgr6Y7|30^z34wBQO_e=@T^p>J6cjgwX%lFG4fhRzA_+?V(la6P2>D3pGs7XW!n?$2yqC?MydweHTxw z(LyEiS#El}kw57ku)>CJjy!*isCWX+^ypbU&PCgaMeJ4n>c;TcbrI2ZvDw2Cv%|s? zR$oN6m-;XwE_rOjTyhU_$)o*pNl&tVOfXd6!up<#FTTHQxP{o;m+`rSm-pYENgKgC6}ErhRaJyqH}j(Rj5vYkY_ zE_fM?B;u9d-Y~B$6R&)|UtZ}$0c01I(Mho;=v=g2bn+M5EIGqfN7qY?|@*srzd$(^nrwvQ+c08BFCrJ(BB&kWiPbvz_H3;7wVlCr#tlv zO--1zMRM1>)DP}WjwcfgP8y0}!2NOo?u3JT(~-w~Z-MVt!KS z9N`apk(;t0zlf=myyeB7F%S+noaJW2ZIzda)gaa{qJG47J0~&*)0=u|#_A!vg3A6N zr+Ay}kI&z>W5?r813N^a>ic{twyspK7nA?>ro^EY?PnX!?)OV9p~L5GX;Tkz8091 zt1WS_2wliowJ%dt4*3d5Y*<{p@Zh5lp1FukCl5XO*m+A7e%o6feDXnSX8t%p8Atd| zgHTAmj~9fQ#l^`*gu$q*2iNK}Kz>5oVX#`p)&Wiq;XDc4deiW#qJt^RE*8b*FpG<^ zyK!-G3e~E>EN*0^s3M5qTjS`*_i%UTlV=||f59qxct*EXfD+y+-(&-f69i|_CFVLL z8raJXy6lrX6yxM;QKP|9m$BE(I(@;4ftaG(Ys9q~0vU3|c$!9$(a@W4Ta|N#Hm~G( zv-{$rfQ<4@8dbN(+wepHNJcedUDQQ4Zt=6>7NhlKbLLKt#~n zr#p~b?Emoi4R7%g4L#`u%?21fA_t1l#Uy++&_&uVvWhmBZQM6ZjNul+Fp_Q0Ms={F zr3*a41lUJHo2KYK?Q#>>oD?{h3$f;!<4q6kv>Fu%uy+C4sRfIBQM;Rw!SVPVBG*Fr$7i2(BPa z3r+%gCV>)}Q839#21$g*x`@=pY6Ex1#Yi_%LT?yJ7&-6A6FC=hSKQp3Rp(2!#HKLu zu7j6PyrjSFoGkHMGnf*;5h(ZPpP@_-m3Me_@~F^*ghe3E!-zZpec_2mrG}1C4JBv( zjMZpDt4I%UB2h*aJ(}PHzFJAWQvV+{xT0;LHtBzSC8%nI6LXGPPswyjWe|e#BmuOk z_JWvS*YUF8HIdXoqo=_W@`+|F>2`&_u8WC!iRaLWYhn#SN(56SG`|#mA!XdDn8%Q; z35io;LCRUv{b`l0Qq+ifd3MSwZk=8@OOuCQ=8f}Kq1k9aZ6#*{Nk1?=rc?wjn31Cg=zjWp>J zw&xK(gi?$O;2tT+e8qm}I($NcsG4NM9D$XAJ)2IgKoY*>1gm5f#lVp+j2a^$A-x^Z zi}igKIk$?^;)W@qJ!6s1DFcW{!YY#5oU+6%cg46rG5bJ$lA^)f03DrfMvj9wA##h% zSFD{_!zDNj_@~+1^_so~Fc|c7lLMJ%v^9ulOAc47Nsg0HElN-#=n0cCU(&2CEi;3= z+M8*nO;GT_fPUSpu{a4*b5FW?ar_d^>!8~z&0!I1!9tXi9l^w@&dLgW5)6hJJds0- zHo%xPr%5}E?twSvSH1@x_9n4p%;ef2&!2vp>=I3kCfZtCS^?`#^Bv5sr*soWt3j4^ z-)2mfRTD8TN=+#?bP|2U8xi zPP0iJ+f9(HLbF0fwW6n%;Ya2c(UW$}V-{u7N>eL`gJ3DuuEfe67Qp1>GFDGc1L_PO zhzg~ET6WcIqGRHHl5uN%a-jKoa~9M;U5H6wQFB@mN|i<`JuHN=$+1hs zta3&atd+2BWKlBL&=tY@q=QwfTL{uhn3}OG*ziCZ?XrePLU zGAyAttzgwHkl8jl`2ch&uOWpEJ~=t3;f?Sl>^{N)R?5^6zPpfu@OBvEW5ZY|cYzFD~*lAJqVeVu~d+moOpH0SIFD z2bW^V;<+$38_sIO1%JB%1OOu2`*97k$F13`a9jgTW1!IH@8yV?ubr8Rm*SdYW$mf| zDbrv!Wp^eFLn;c@uCf%y?EiPktVw}Pr&>+IG>`_y(RU}QPh_SQ^l%x#S z!kSu%4DLgJMkYvLivojhML@~R9IkM|9d2RzSFPnSGPAyk1<4G&q@aFi&6|!U&rp%XQsa^=d~0%XcU&fb}~&GvWP{ z@xwST+ugRPMHISxYD>D*LP5_P0j5$SuJy|U#kEe&%D5JNh~irK634ZE$mq6n05y(l z{fHLVBCt7}x@#h*HEWJ0J5HtI-fI-t`VhC+=AP#9UP`i3kN^czUZsHKaJb136P$AAG8ZSMo9)!!}Zb?KXwj{WUnl_ja zd@SWd?DW#w%7FY9jX|Z_kCP(~MJy^`X>I=9M&P3etkzZrd_x?TRoL4(snr_6xd=LQ z_JN^9eBbQ7Hzt1%G5H@e%CkFG@qMoFM}kJN5Ipzb)r>xyvN1s@m9nu{LZ)H-^#;Z@ z3CN(+tWhf)UBt=M4|DSCr6Z}Aczkbg9!!Sq6=U&5&4S6bcFqnC`Kdt@0<`W;boa)X z?!IkYdaiv)sp5Eh)id*dkF#Df6xnYq(uDNUO0jQbgha#x`YJC_ePI zxn~U{RRbg0%tK;IIAk&1sausMW_#n>_Yv1Faf8_%E3V}VDXx8NlM_}CwuQtL-ns3sMbh3E=b!wnMo1&wM5`<#kIbF)95=J+eBu9 zHce)8484*014QO8bA#C(D>8G16q(N^BB0Xq-f#5Nr0bs&W+YwrPzM^;{>;FdMzNdQ zB$FxP+-RhkX)XR2qs44=5FH2xro%CEXmMsh6zIyxRnz)3N6{P2K1ekCPuzJnkcwtp zAw{#>JLk(8ln7tdv8Y09Mv2%JFggN)_I)1PhiJINsM=+hb#xvZR}jPPjL`CnyLRj- ze+++U9d=nb8_u(_FPFg4(lfk`gUfA_2i5317l;S{+JOfDy)^jlYw{1Dy%rJ}O}6>a zwE&f((X|0g%n$bs#1GFCKb-7C7`VbLSBQN$Pk#q|e^oL~w>8W3jL1-?OjYDEY)^*i zc4wD%$}G){8g|YoZJ$jl-AJ33N!lfgv`YqQ*X&Uvb2JJmwK+cP*=?5srH`&c&$04( zVj$)7{iJ-(3tci2-mp~h3YYWGHoZVUO8;pGx@8;1eEWrbdqupxfMNW;0kvb>~pGh#9~}uyT%|K;_WkvFLoo5No(Y@MkATrQ2sQP zo-I?)%n)zivQ0S}h~_wZqva2_rDbJ1b#z3>n^L`M!}}Ko1==Z^1dM z5V~_rtM#qH!Ax98WTLD$zWpJMZ{s7M{gml z`@HPODNJqX(EkfA%Q|H+99s4o1*i2wc(CNIlq&cQ^4PB6x1MX2{<`!xrN0e_#hyXB z(V_HB>8SdVn0+$&t(+$==S-UOJnaedJ+n^aNMn0HK#BjE< zQ;UL6lSaLR(Ntc9(p09|)AvK)ZK*Qad{wk*ifBvJ(55J%tE7T9O96f9CB;#ZIy*He z*qV4gWoQLW0UqhP{XMqjzY9AEw5_(5xvyU9@siPFcXdfH3UxU(O5&+CmI3~`uHg5+ z;Z~{Ghr0~3*A}%Ac!Q%O;e0VM(VL$AEa}-N1b*WQ`OjYVDl3wqyL%g|MzHTQ2$no! za3ngpLGjEGpX8u5@KK|IOaUm%vjwSFmVsZ2_wUk%*rIkK#I-s%|(Ot3XveZk-pO*Id+BjV?c_A>d7?Now*neL=Gg{99EIks>tUmJWZ_l^!` zHe`&ZH!l8XF&Af6(7gn#pM@QqSts%mu;DDc1f46iPq*RZgjBlCTeZCn>Ufqx_V^fw zph$7p;3l8{(Kj1>Te0-nfj0S=qlkmJ)UcjT)fxMG@Z)ERC%R`8El?sQ4tLn3(_+Nb z6wUYL{Wk!1ncMeWUAof5;2>4gGRQOqmDAI|b;Gdz`88^;oQyNKyH(QQxYdB8yJ92A zS+ONdD04i$k?>1I!clH8yJM9A$QAYq2;w~~?~ik3uhQu~CRdy!v4dd3f@3qz+AACHcWcp#7G0t4l8Z^P}jemcR`1b^3DjTDUf4M@6f8TvI&wVmk zw@#>)3hniM0Hz_cVNgd?ZjVRGZIjNeQY`bRRDYY&tTvW)V=UQpCdr}Erxm1$linEY z=ZV2;++cRciov)-ioxEJ$ZFT7hmB^of0D4%A+!C*4a{j=0C%j+Ur)lKh|}Eqi38!2#o_MWj>FSBGfB~# z9Q-25!N25Qv!PRRz!g$*kg+*~SSmhVq5N&a2|ZfK+Vc@FLH>JRI8>pQU!r0C*FJ{D z6~wSQBd+}tlTBZ--05639CrPD(Y?}oK0H)(e0QaWElgD$W{I=kTIEk802B^;bq9yQ zAx^)CmPGRD2(qy9IET67pz0nMX&SACaBrdHT*0Bb_;?eZ&$>yN5KkWfu-A`t)Q)sCwfGcaUuyBUZyBaEy^}M@opc@JXhn5Qred67@jz?h zq|rns$0+A9^~92NA6odZ8Da#CLcN0FK`wn980W$g5ZN1@TfHJD2?r+UtftQ&1) zlZcEC>Xjwwl^xD=?F~X)R-;aZNgeyDB+dp})P>(E4cZ$j`ZBm@p) zE*rzjaN-InAt-Iv8Mg)xhb!CEv7-F8^%ol=pYMy4lp*q~MA*;tL0GOJ2-}%;+MAB7 z7*qDvXRrC=h?%J4$(jDXa_%+S^k779CNs-9V=c(6fqyyHD705sIg&Czlz7h|myuK$+^AqRXmEV(k zG+zE->RIW|=Wq_)U05eu_#MOVYw>$GmUbUO*>kth!tDe21m3yv08QVU(#9uJaFlzz z`8%fBtGwES7azr0p?rN8pAm|K%_}(JTWAeP?XW1K@!&Cxp@YPMgJnyKu0#A?t9{a_ z-Cf?OKQ?(rIfx>_iwSRn(;E+eod`e4EoVYd{;VL}Q7+o2 z&bJx}Nh+}8Ink8D=dgI7A@qBU`kBMwtP1K;L#O~SMyLr;ZwUQYgwPAzawY^xgl4u~ z{*=J|YkO$*;|clNh1+IT#;!cFvRC1Vci;T=X6(L+P6jv6OxI6JmwQ;{A;`FcVO~4` z9B0#~Pq6Os9Qu9_BUfDZ|Krew0Dy^q80?m{M~C;}eCGviD^BU}o=a@U`5lslAMAl^ zrE>WJF4|7HMBMoFNjTHCjUnmi?OqP-*VvS*48q6QA|tn|Mc15Aifwf0e(I?G0eSj5 z0@YmweIqcEjGx)CWc(3H#uxgPjGktR=Ywz`#u+GsDbOhv0>r)HVMz~_AWfX zBP8N;sf9u*=Kc?CC-)Oe$J2_&qVdjl&0fM~S?aD>OWyiH|0l1C*cO4>wmIrLHKLnc zI;*#>r$#j4eQU!q`wt|u-|Sar`ui|)ii2mlDTL+7I59%wVab#52ADxMQdsr_w$ zErru2wEunAg)U38BzU5#?9|aE2k*ZrH7MjY8jWvF447xT#GJMq)PH^KHNxCtl*0K(NW&6!Y%Q|vA8G?m&GRl zRQJzPLaMs%SAqXno$l-N$5*|^Wn7RmzkqB#b?uQL7uF~J3Y8!(Ot&Xk;ots_E@g{AQWn> zQnOaXt-QFKkni}$rLg$GmsK*3#DTJOi7jE*#_kwgxrs|6C3u1&I7-5bX(0w%$MI~q z!4Sw^x6YpRF2n@PS>tmnxJa_OG*<_i<`!p8-M0t}^CE`Bl(ca_Y{6l4;$B0dPD5Vb zh>IUVHuTLSC5$A{1ce=qtKRVFZeYH45%+lE*70T?S10Ez5^5C0EsJ!(gZ7HfI^aVw zVDD})3rxhfw{UmVs%EyRgbeB2);Yvw$_pX8r z1qJufK?bO+J-1hovm;LQ4csc-K}o=Bcc-gCU8eiss6HMPo8TtF3pHoO4qUpYzKYw= z12qnUg1QtSC%dHkD49qMa6p&M;UAa~mXf%yeaXS)p{_^Q*-OnBJ0d@FO-Zl>FWzZ$ zTPN_8`{|aXcnkfbsmKTV*2iyB`nY*jl$ZuPL85!lv$eH~Q@n!I`guhIbMobnZZJxRHjqGA>}jF4Qe624>tN!NKtBeZAWS@Y}Ph3pGC z2ezA@6cumpTuAHZiqTItJt=@mkg;*{p=)6x)P`cBtT)-XgIM!2k12cX%Jkt1C4q!E z#p$26VO=!|TszaT!JXmfUkXq&&~!fL4_|Z8YTWk;Ly(f!0{+He*8<+Bt_8#gkx9Mh z2n;K&+s_!?cIM&uTENc{^Wyxk7s2@iHcNBjIxKTAy@}GDBuXuAurnCcu@WV&AVjHi z4mpZ^oeT~HyT6#j=|8{#CL;cv@3boE6sSV zg@0qTkj;xUrBR`f=M;Km9D-1=cYYV8@qW#Oh*hN{i zuZa<#O(~`8$Ocy)lg>5TCJ&z5i>mJ+v%iUTn@Sf74=usDa2YH3WAXytmaS5V;&F2I>_8T3AxJDw)-sWv2jgnDEW$36pOmdOqkrib{uhUx_g zXlYtdID3pVYn)?T0J+Ee9fj5{HKH%*lZ^H212NVq#aQ$qVk~@#8Eet#w)1FX##*BB zO2&%Do?t9I5F14jLgnp=#0e`+A`Mf!9u9nb-&~V57w&1d+`xS@ZTR|BN_B-VyRnUH zrrj&r6Hc-3;WSk>W`4eBW+pBo(ftr}8$@@A$)dGIZxZqtE0H_1I7mTyn|6w6g2}W# ze$412o9h$szF)E7RpV4`94TBSCdID|rn^q5B`-e2y6cqI?H7ASU#z=+jtDMwS48}T z?lK3{o9>z*-Ss|dd?}lXYEtA1ZFSdY&mi#SKt=e?N?>`*$g75?sli_l?UBQ)!sT&9 zpAg)z=y3026@)EtCvt_-aA;@UWd}9XrYnk4nSOGq2NA+RdzL)L?K0K%R`7@7Dp#Dw zlIJ^Vg>>I>1akyIB4aB2%(V_N15BQyLnfpeJN;XOoswFq9SIa;9!|g1+WikkyV?9f zQcjStwRUF~k+m)|K<{}I;eQ((%w1FZS*97l;Jm-2>n@I(A56Z`2KV=a118Q(>W$I0 z9#bPo=(Dd5q|fd=5cGeHO7zLZ6v~=}n)#p7hx_ zm=8OHq1$3yA=YP|duh9}JCR9 zR(lfD<;^|Qg%Cmu+BZfuctM-%vY`DiV1Sms->-N>I(0(XRrnC;Av}%w{k=USCDudlCjv>k zDk4@w51E7MtRbi!oAtWa2q1ZOkwgqjI=8@!J;+$0;gGm-5)86S>M+2l%;Mro z!)w-aZV}RpKIHdDijOy(Wf%Loa-*jUL6fRYp*N9}L8&G|qX{8hayUQ968?^8u}S$V z=u>z|Xc>vz7%`gFl8Zzs+(zo#D=^~tN>Yr_$H0*Hgzc1tW`i<@kg5Z33e*U>R=gU% za!wM~s>r|b;C;)FZ8ppEcUZ7Fkm4)GI4?^LoGxaFBY*ea8}|U^w6hSc4-=&6Q)&qA=NFbC4dw%_bgZ z*n?z;#h`?{-QtYa%FVnd_P8}Zdwll9v6dV6?F z(6DQMg=d*OvieqNQw{Fo8V`p84--i1+>UTmmBD#n=XNw{65JUcl)5`F+gb?st0xs4 z0EWZB_W4c_VA}Cp;Y~D~1#ZR9E#sgtBxn{};qY7S3Q{_wD;=>ByBXA*L2IG(@IpAU zg5$^7xI{}M@L?kP)2`O><%Wjm1pt+1tX8;lx)t6aK**!t%P24^zcw2cd^-?$h5V9T zxJ+;T7Vpjt4=x~P`TY%tX6Zb8LXdtQj+X37^mQn_$ysxQJcbrDeN|{E94b2C8&p5I zYBy>?AxdCItm4TCXi;!zJOrOx1H$r7t>BRj&$lP`?Q2>)RWU_2lB$Mp)v z)Cvz^F|g8vsQ`=@@qL$5YlSyAXq8dS7abf(S420+#04IPVMi=un81izRNPP+4iDPR zz{|&v!d)(S?Le_vt>+_t1Y-FCC*js?Yz$ZkZ{#P;=|uhbQQdc%MK8ZrC2l%|^i+7C z@_J3?l0uNb;`%NuH);!q>-qLlg$5#_+^ggSGPfUt@gW&3bSQp67RytliB8N5UqPSH z35+6gt${YwS`j3_p<%B^U(f9fZz5p>=hRlfh!wZ$2Ceglj)n*8PCXA1p@eLtBcqf3 z^km5f>?9ZAZKWWn`*YLNcx_rl}a)ID47os34nS4IricCvDY1c1MqpU zv0N~krzdgqcdO)gKZ)!@l$J`&Do z0C^vI#dK-lA%7ia?OHy%JFpe*sd&Wt`~50XipBNZJ`i;?f9Kn}Az5C*mZ*I-BGG^c zF%Kbcz%ru{wJ$g8bT1*G6Gs&aHShws$19M18|j6^q$vF6l2Od967SNM0v5(StdGDl z)g`A030?;TCC3TS$E^jFx}0~awp+>DMI^$LhZIoXL@g7=eW$W46xs{nZl`vog@|sr zPiPYyUI9*YktGh<2=W20Pjmw2o0|X>I4gjzMH@wDnXgs6rL^A<4<}yL5YwS|&{$#} zk*DP+g6FsR)XQ@8ewg%Zk&Eu7hvM^i`L8hLmcLDZhTw`R@1j5V(bs$E&tKA?KgXYD zI6}4P2uYq*xZVp~=LIhR0+)TES-J~z4mVgj%dM5(A(|r33pd5Ci)MI0=MFK1f(ss_ z0RvX?gp`0PClI4Q5#=Ce;HK)5SBV&oi{4^9j7pCrS{zZma;3wG_My)kK?~A(tJ#)( zgEz(y_ZEw~g5GQI3gte8KS+5htTHzoQix4eY?vh+CKlS{iT>yAblJ%0*OVJnx zro;MP;L&3V&u#Fpx7)_&k}QhCI5pic+)O@acq41T*47L+O`~M3)A2@wcH(->capXj zc6ydGV4V-GuC$7AcoZXBJwIb}2L7$3Ry<&71tJVs>bGNNIjPHlrvSu)&Pw~z<@S>u zF}&ji4%=zF>ByhU-Q%z2ZW9EvJ_CUuSTO-epBzJ2^=$~e^?q;OdrAaye9lVK*z08} z`ib%^qIE&GPOTuzwcaugmUy1tu|KodkMg#hr8% zbp+o!WI7$&wSobIK*)UXzYP)PL^gW0Cb z@Jr-?SZrK1u7B(W@whCS$z@eAt_rZr?RNXZG@)=3lh<7FLe!#`hxh~ zqtU+u=t|n`K{Z0s4DDkqp!B5wr<&`$?=-NyUz=z(&mak*;zR7B)4CO&h5}F=r z6TPWVG}#}LnZE%u-+`%Y@P<%h4azvWUnniNS*p6is38QX4b5){fq6~q*EC>bc(94y zov7J@%O&GK4yt0|08SP>HiZG|oc_lN=68VUO*FeZWOA^XTbbG(GgC77|3Z#57s&6g zlY^G>=FZ4pOCpP6&slO`EZ)Ln1FjfNH~FlORVhQ{h*e+u+?+4C-oW+olbQ$!DfS_q z`Cg$j%|d6kOGW?k{pG+mczQ1o(k$@@x>@sca*tr9Vd(e5owoG6Z7)eOtm?e^96fAc z@hh#4pKz^2^iRDALYfzS%^(}~Se&o_kT+#R7W+BBtYWkRF4lm~^I^~P*-Ol4FYYE- zvy&naA#dM-v(FKZANzoKpoo2=c&S|QPjw0=d&V+SbpMr9>%nInun?h!DoqpDDHSRa#-XUE(CIq22D|c#ADBVW!tNgRp2dMhuaLlVDlJcNGs% zctSz`J}c$rVx@FT191OKMx$UO&8boTd!3DLwKUA@cqz{`t5DSd{ErS`vK-V3^?Skm zqmLg3ho$|vp>R1}XHAnZ915{n)l>l(8n=m1vy^cKtcJ}` z3U&I~P4}PIf8_ql?O5AoWo>wlte44j3{ z{q9!V9c6RWd5_J#a%uX=kw1$ZY%-0PpWmiG&&G@ek)MrJ(1I<1h-SOpRqTPDfT0P) zTWw!%uXMX@L%42l6#3XP$f)_E2IQ&sj@oR9i29<@UC-bYk*Hva`eJ-UxkQn(*cuTg zZSsrAT}ZrIC0?y(I@VCts>w-C#*aq7cLA()0w0kwWgUIQ%M^|dUuH!*>b@-a3w8`fxKU(g(RQA>N)gD5nKdVi}*=qICLJ`S=az8Nq zCe12Cy$J)#o!0PM?}$q(R(lVK`Gz3oVDYMOjr#wUB)%f%YxiI5ROjU17Mxsm&nTe` zRU3?Jac}Uasmk9Unzj6xI6~PMUKw)p&)&PRQXQxa|4s_A$!FwYou<9~^}7`O>>v0_ z(Wx^fzPMS&mi0ebSlSZ#0?I9@3h`Lz)BWc{U_pNi_Je|?eS|NaNFi>os5!w*73R1$u1F9S4B@fh{g zsyj5d`0aN?Zb^B@N2{F(S!g{ErcvCgQ9N0i+T3>T@ZwR+YNcJY>~#-z4XLq4HJYK@lofK2mZ9KJwuMa*~>^?6Hoq1CXEH z-T}xJbpV2g>Hy@5HtnYlucE3pZTP2>U^;XhmSpN@Afi<_^}{*ghXL=rP^igheqkzuhH_rKAcq~oT~9$gZSMV#Dnc) z4D)C1fZ#Mt_Rw~B+|>6E&44m#H2en=o~(_i1UcrW>ckvq)@4HD92Da#1p9#MAKgu% zzcSFt6AUvzz)efd7T{1oTY*yPB zP-LH!$vQq0W@(hA`G))K25$~nKqrJ@oa04Qc&FAV!pn&`45^MAmjHIKc64%nLZW~J z!eA2}=89LU1lr&y)6h1l@|?Z`l?HH*cXPhj2bdsCnfCDp>hV$Sikc#G2M6Dy{03j3 zJzHc_5h(^H0Qj(MK^>83G3mtDRMqd8uNY92!-Fj`6IT*^w#C5CqkPUq?YmjLHuxE~ zn3wc)T{Ho$ZXIH(W6Mf5iXj z#KZHs@sB1BjKkPO&9&pBW(uY8pv_KmeoAs)gVGRCa{fr|OAW;EVH~aU1qrvpL5)5) z{-HLQpC%d-FvOtL3I@u^POO*s;MS}-zj04LjfTu)@Jcx(%tEMPnnG>2Q8$_?u zD0vR4U`iV)w8T!$Eidt;%APW@jkkI}10hrrfq}=Bsle>!K@2Xm1j6p{?PSr-?4w3a zT#$H>o-hfD6X}>7vLR>=M1BHSXUf<0DSn3R4Vn`Spol(pJ!$^@=@0XTh(%_Z9wuVu zsikKDcTptwklL%DIcdv6$95PGU{PnOr$7KbO%%&PJgKObWKruwD^Gx*3vu7Bfv1)V z{9_6{Rhp5?ZnC)Tv9vGdfJ5le{TNKvOsyWQ2={67^;!N1baG^t##s_z%7V?C{B*L5 zD*7F>EXbt093iRxd@@4)xA~b2R-J%R4~Td9YNCeTDp>mnt{ng$2wvJ7xGY$R(VH37 z7SjDAafhOC12>^Pq3s2I=s@8Y7RgcIR#L5-xTb@-ENP5%$z(H!%AiQmfd%gZ1DCNB zbDXu&Ytv*S%kxdk!QCfuedY!~Q(WzUuKIoH({A!<7Hs9Xcfn_+H-Y&=6LHPRgKeay zJogZKbEbY(ioChI9#lWcGQCCi!-XH{*{B!# zr7DW9OSQ(bcXJG4Q_U&{Esnt#mlSYV!_U@X6<@I6!g<+YMh*t6n;qgBin#5A{?pR_ z&Yih4cV>5=o}N|>6&02|bLZZ3&pG#;bI-Z=ocHSR7ys_c9`T1C4t>jZ*XxF9x}M3x zgpZnDGww1sO1_Ysc`bP*nc`!Cu@-u9&}0eUgC0%GcFcge$;gN{f7iPuqS=%+)dfke;PA#zxyqs8LAegln2n4~B2|)T#31QiHSm3Spd;Momi9n9m zj3^51MjT;XX&r}bGsK$l15L*WLp0)f*fqN8)2JEy#Y@9zu7C@o&ye9niFJYx#etph z3j-}s!fQ>#HEq*KvEh4vF|p2uF+UI)ZJIW*-p5Czl#TyF5JtKmc#hZpM8fwww#$<1 z`}l}&U|E37vAAJar$ChT7* z-2C~OxrLeeb7!Qm?Z3^|Niq4ho!&@sX?M$Oe&DQcGbCOTvChHt^Bsm=VPR zWK1a6lG0G%`||za`{}g1^(jn0EO>md5jzfxbjJo#h0dnvkVex7-HupS`O4>?F-kW389?-ZML~gY9S%2*N7#lqBW=AO-hQ3t#EnC4_qo z;3KK{C4BFUFdZw(Xd$d|qri=iwxmiJ(sY&D6z#lRXlE|%J(O+yS{FuhmC^p4D_ps; zSch-*w+_-sd^7DX&^U;NaHNV$oxdv^?)=BzJ$o`tG#GrNVT4R;daj!q!cdbY(jdFI zh|T+~M)E&-d&6hpq?Q>>4Gu{Y8<_33wXWg&^#2Bn));fOD~{bN|0Q@2&#gAB-1RA!yeD(4z zxEd^2UKThlFW1An&eR#a&f;s`w|zz%*;Kt=M`MmBTQ|DC!$Pg)1=O#_qNXK=5oKs# z>kKXy6HQgCbne{pv{tcH^Ov@1dunUj!ooIf=eKFQuua>OTiedVLdu=$aN+pvXp~P> z`c&=nouAyN9POqQWw3TzhR$lcC_yQXkDmOHW3)eFL20x8_1*8An8GstP5+kh zuQN-32!{Fx!WF3%Qk`$Q(lP2`H2Jk%vw$h}YtkHB|CV`PH)w`;Ui}?mW#4)AcOT5o zDx2`X_s=i62~U7u|I%wurEN!U!qoZ4U~ay{@Y{k}yR4iQzIAFaKN;)pWUMRAxC0CC zYf~w>*cqqk`u5u;BM)2iol%VJmzU^Y;pH#yo<*v1@FN)q$;18N&eJGN_-KU*6hArG zzXQWjw267hY~%{KDteudqpMUjUMseI ziPK2x11KOq@jw_2!Iw~g^~UGeRnwS9d-vINWnF za_y%$ysZ6yN!s}6ZF^R3vO5kff;b{r;zzeotE`q=S*@}_LQM*HQZD_dY*N6)6)-(l zMyd(Ty}g?B#%iMM(@MN*g`=xr_W1|GY@arzz^bBUy`HJ=8=Kaf*S~Llet>}Y+_@~^ zrH4G=#Y^r^biUL#3HixY=eLQsY1rGz`Mhsh)&1Vo{QW@G%wNi=p@*CrycE=YbpUF< z)|(m_1R^GElo#Y51|#7qNdg`U5}r~>`0fBC{NX?(ygLvH3k&iP1rIq1Xf0OZ2Lq7s zR|ApoqrpfxFG;{dLBe^3gm(uZ;k|)KcyBNgE=UsaP>^szA>qFUAmP6UBH^C~BjHI& z0v-wyo>WLUy02favoKD_F{X!c+CiOvIuHrx<$)qS8{zCvDFMIN0-@xLY|}Ga6nTY3Y%+wuUo# z?b=NZ7Yd#ut{8=j-iUD}N#}#h%Wc19;;?^tS$pZ$3tLX{aWLa0RbqMzz6?o@L2FIHp`OglLlX4gMx5V##&Ntgt!X$o<|R?Q;kj+^>g95% z1;G?T?BfoWx^?0(!kII!uf#2(Mwg+}q6>*VTlaC&ZR3Et-40k=9GGkJBzc=ls*792 z`^&`WOuQ$`C@NC$mCACIMf2x&Bt{DA$!&>MCV0CtK~GN05QDH1V*A2wGEKp%N_JWJ z|9PM-YvH2Yb@7mIS!gY`tZ(&Y zg4?Fso5@9OGIr@M#%|fnoO}hHJ)drg>b2XJ9cs`mZ%u3$YXOdhv$Tc*7?_6AKqa@+691V5@w&{KV+MI)?)MH{D8ms+4e z?Tz+F?x`;l{nI`5N}2FKABc?Le_uZp+^fv$&*Ghc#rt8p4!IY||M<~R`dl+E4oHu} z-4jFQ*s!cYW4XDLA~!6|p90`i1wdaKLbD1DnaIuPJFk8zRFA%TBX*=}awUG_swGy? ztUnN%zxI$Ux`JSp-m;hS(9X#|**9MbFXb|=Ugo9bD_QbVs>JlCv+vJ!ws(ISaYDzX zm$LH!>uu|+gsjZhp)ghCxu_8{Ql!+Znj&YX+TJ5?hZ1Ibef2Ig!O6e_Anfvi{=C@74si|wi zJ6nU_V`!^}V@WT-2>J|!jg%^o~D6po>~_! zBRN!f4noak^bOA@`q#B*ggFuWxL!x{by@K1m^i<~>cT)21SDdNn-She9Ik%yH| zDdH}Z`2pr4pAra&0bAXtg{{W~cmAI{c`Bsj4tZc7*hyj(`}JlnhG|M_c_VqOxXj&7 zwxsp`puFDQ+DVz}H!4&0wdD|gV$1pN|9L@HSW)GAdDr;yV6m;dAX-o_i11Qaso(2+ z1&Uqc4`>0hT_azN(ymb@W@n>LaOAF^K@&4UP1SNf<(S zNzcCU?3LR{zI^W1uiQvON$rJeH?G~*2=}`&3pP^2X?i%K#X+ssbUb8S+7S+@EW2rG zu;-?T#!WmpUUNl8&uP>eks;3VDS?Zaw!rjaeSKO(<|Ac?(rFm}DKk?ZemNV9wHiuB znn+|uxd)~}oru4I_}Hx>jZ#B)=qhvdYY)02Y7lhYcd zWM-ao5fQxfP!>F!W{IEa8@!@ie^gPfvL%H}o=KiGn#1B<0r00406q0W2CX!!u#fvm z))Z)cpd(~oK?m;&fFC{(Iz!MY1y~iEvcdbwU+vL-Q=SV zO37llO(@k%xABBeSa#cT@Hf&?#YqK=Fpr%}#O1VX=ZTzky+uwQ0FWgtT)CacRL=m& z1(acP=@?+F5w~YrB1kXJ0H{%ujXe$k5P26UP$lNMqyVyQHD9U5km`Vj(rg3~$fM}4 z09Q@6%-xOMI-& zTqN~IUXb8LRE0(;gTc$e!1Ji~6NSU;D5#oE_`wtf)RDL==updEsX}duABw!DP9Z*h z1uDb9IoL?}u@=BkZh`XgE!6PhmKC*b`Z{E>o4my*=(e;LNLk#iyUDM4oS@i$5E8X z_o0fMAIEu&7nrCON1S3}8eGDG4gpBCce*xxE(U%uCM1J z3C4;Bac|}t4gowYT7K9FZ$YRseVYSff2%-)IIl4MhNJ%r&SE-gvi0okE7qp>KC#k1E z06ldSabkHQVXT=y^Z49(@N*()H7nq$r2zji37$ew64@0N)NK~E#2hdPvs6=$$;NO; z2`xemesX=3pN33!^;|h~0`zph*yKmjgF`*v52*ed2Yd-154#3_FW_!W8AD*yaq*tM z>WB)BjJ2mxstkN^VVde2xGY$R(JL|4ywYzLh`Q^{3~WMryr%88AOo3QI7Ws7zhqG9 zCQAD;mys$X6{fEw5E&HDn$X}4U|=y8VUCjsm5mjhbq(9m4HLhYAWH7v;zzUUM#!qu zN^ROEA7bumf?91pnpzX6FC-B^O<+SeB0c?K10gp@t3R(m*4#Q_jfebL=~G(E{|Ypt z^1B5mLYk$Of2V4B`egugJwT1=^Hj%z?sph%z~8XG-dRCV(Xr^CWomyJ|HOQZ`qS07 z*iyuZP3s$*qVuNcxEWiIp-ex8bJhB~^=Sdb`lf&&HrkjUQpG_aatJ9e>7E7T(iYNs z*)QdU+Cri#S|L8&hUerGCK^vv8uQ*`C5U5kRx#*U2|f&X5G7E@lT}#R7wn_3Nlh=N zn|f?oI3>?gdax@UxRSMh0c`Gk8O*!BH+&8LkIiT(gRx~p>X{ufQe*jo%J&phX literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/changelog/0.11.0.doctree b/mddocs/doctrees/changelog/0.11.0.doctree new file mode 100644 index 0000000000000000000000000000000000000000..2b6db47b6f74c5b8b46bd7417dcba0d118d7bd30 GIT binary patch literal 61627 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 W!~+G0p=ot4dWJ4@X2}Yk_J09(E0#I{ literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/changelog/0.11.2.doctree b/mddocs/doctrees/changelog/0.11.2.doctree new file mode 100644 index 0000000000000000000000000000000000000000..2fecd5f96f7e901a64dcbec24c9fa9cb5946e4ff GIT binary patch literal 4295 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 XzaE8guG3Agxb_c0HH<&JC|>;^nYQ8e literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/changelog/0.12.1.doctree b/mddocs/doctrees/changelog/0.12.1.doctree new file mode 100644 index 0000000000000000000000000000000000000000..63be1641886da6f2056c5eb194396b124a5329e9 GIT binary patch literal 9253 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 F{vScGLj(W- literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/changelog/0.12.2.doctree b/mddocs/doctrees/changelog/0.12.2.doctree new file mode 100644 index 0000000000000000000000000000000000000000..4d376ba2508b438cc5451fc414dae26e9504ea3a GIT binary patch literal 8634 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/changelog/0.12.3.doctree b/mddocs/doctrees/changelog/0.12.3.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ebe32e25d20cfa1720190c29c038df1e8af32c79 GIT binary patch literal 4340 zcmc&%-Hsc#72Y3fccuOD?s}aXs2vDKlCBGAw7W^r!srj;1Wk)YbrBRT62KT!GnB|I zXE-G}wzf#nq(Fcyz&o>jgFa4x+yuxg^ey_4qmf4H+HHJO0Ro1HhlhvfeBa^W7p=el zYkwvD>`h%To=+l@rnyY1c6KY3Ntw~yxaaP{m+o_SY}<-F)3Q_vb#?_B3F9JFGnG$+HeD*%;S`fc+UOj%pPmme?(FDGhG&iR3>IpUg*!dJR@WJ@L`u3 zQ|O2NKF`LHW}J#tM_l%YBpuWKn`bAz={kIdmA#=TX{U?dLqpVAZ4?{EMA zlLY5%hZv*yur$c4&*M5zHF9P*6G60w5Z|qwWYyPJS{C!ckR%s~LeNzh*Gt8n zefO0ThE3$Czw^DPjR;w}8&G=ej?_EW{p|bL^%FQX`WfH?p@ zO#Vq>Uv*rSr|D!==d=w>ld&QNYdFSC1(1P#y}U2GXNX_`Qi}7`Jy{cA`2> zcV2Yp#%5nOH+wN5J2J%%WT8F|D0|Z{^zT>w;DApEVxErSPy;ppQ1>4vTPy^>c7>5qM@95FJHF6ZdA;3 zcC{Dk=0#(5;AuIW`)1q2a@-q*dO+&J9faLl*eFhq=EbR|dczd%vr{)Wa&1jqsMOxv zdKL9x4y9{uu6Cv_?X%m6eQb}6BDoeMv^2Pu#)|6dPHh=!K(6x@+o^SEiec%4zH0Z9 zQu!Mzp5AN&yHiWToi?yrni_-JgVyU{)=G_(%ni*`TxEYGXm9k4y>>&ErYMaYun$p6 z$214IjZ_YUaJL$g6&P$QDg6zK_G^O7^qt+VTtI=$FQCww4of94+BGtX{hjY;;YtfW zt4n7OM=+C1LmQJ{qZ7i7mX}#P@fFg2ZTB=9jUrBD<~v6XgFmh$RL~%aea(#Pz61MB z7fc%wghkQN3<~;`BAjcs!_ala(_zaV&^vR##T+US)UgL%sU$;>6NoyYJ5c>}iG&37pBrH7q_<* zO{7ZEaTh=iw&8%yz-}ZmAK{pY2v2K3unoIMN+aXB$gXj~Z8t5mBA!7Kfeiug8rl70_-6 zhrv@K54?lX-KknU;sS?5r}vuw!X}!kz)hNaYbjE`jYkVoRSnNq?SbbTkdu!AM8Pv| z-0$~)V)qJK#IttaA7;l(h6KKQYS=q<3kk7~ic7;}9!WI@qi%c6-~cpZbQVaDK{~b| z@4b2Q4&u3|M#++SFPWQvt2Phv5)au6sv=I!DC~iP(DyGBY}Q8a0T$sU(3=vtr4hOVO$MkArmqU?L9Pn z@CS*n_=YHl5MuR3SzrtQ@(bPTv0>2pE{wg+pH`@E%-J3lAjhMJh7-_Wr^F zGTD%(VDKe8V3Zo{aXW$5MU3W)ix`hoUw4Do2h*k+SQVqHrd`=}nm={ucE=VmhZ3Fw^t>5-G6_F4*VPTNojYR84b2}6AoPXmo)5G5>MP( zuR2UqQt5#jKtMK>Vg{0+dbA{*X}1?Fw(DKPqQeFIsKLrOFzWWQt?3M|LX?$6mcD4< z-bo}H^Y8+~#j^SSRQ?=M`1BSEI>ntW>nov^lt=MctQXG literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/changelog/0.12.4.doctree b/mddocs/doctrees/changelog/0.12.4.doctree new file mode 100644 index 0000000000000000000000000000000000000000..3e55261d7607a05829cb480b7977c739890d4810 GIT binary patch literal 4559 zcmc&%TW{RP71nJft!|cNIV})7Ak-$6i;64B4$=xq(>g)ZK=3}KL4gD?s9AEBGZD#Q zIYVoO#%NL?Km^R2r+w^W(U<;<{)GT}36P)CZ-z@QcVV}6^3VkqAP>)+IhXI;KI{DD zUpsT|r*Fud@oeOiD9S`crO~UA2+Nda$~-l9KQ~Xzf$kRMQi`$&snK)b2pLbJf@bCs zK5kil#S`g|1^hsk1F5(IQDi|xhdkr7oM zDwn6d9#3OmGES37`dst|B#LS8)sv&c@i{!YlZS((q-!32r-~FYRTB{+qbl(WN+QK5 z^mKw~);sc#@AdIB0BP!N|QCiRvsDFg~4=4VIT~yodBgbrxdPvJ5KL5-=F{g zmN@0~8d0j?gHj=`Hjaxtl8BjJ2@@hEhk|@LW^kP|X zqhJ3{0Wx|mBw55GQfZ^--!|;0veYYz#P(XlUee1o$~N9CqzdvvBqIK$(Mt)>s5x8E z%Q-<-vI7FeUTZHalS(&%9@k)K^)) zHP&NC?D%W}{ENC9Gzr8O%K^}|B56320BqSJ?EoJ;$dqIXW;cP(6aT(59S3PgRqWUVk0XO7y=vh6O{-L0!wOB7o5v6%>YX1SkgLRQ>j6#yp=Re2(=R;KP+O`~NW={;4DOeVxu zH`{UJg34-K=qRC3GtTDpTFpZvyq4DK^Li^R3)_r?antVWjhf=xbziSasuU_F5WCY% zi9-}XMY9N9`u7Bro18#+S(GX-mFcth&?RD;p=K#j7`(W#L6J1a%X%S%9S$&Aju4ri z(d(58sQuXm7}mzG7V3SysYDpqamIdyDjn!tTp7JPgqTby+WPnMe;GDi4q$b|{t*_;N)-qBu%$iLxEkBNgr&9~jSAa7#hmoSV?c6z*KmYYYQbFn;am z9XjIhq9Ur46KW@-fD^9Ux+I0`JCI9B3Q8B@2&py6>c=zqEWofTBKR7`p;W`epC3w& z=iAdC16mZKK;Oxt1XTerfNh~N`bOpXmXwZ@V)UJvFEb)O*_@x7(_59?i4OJY+tVLr z4(n}e1F$CCM#wM|F4 zkThtYbSMc`@R&=Q1L z6tf3%lV(<0gpjXeA8TIE=Jk%vHy|gA0mOu-T$zX4Khaw`&4Xr>vBqpz%78$3*9Cr9 zcL9*4pI<2^GG7!iGU}G)bP9koKsP{m6vD9z`r)fb_u)#O;1B#R zC77y=-jFEYiz9?Q@&xRc4C3-jH2@*LfBuWrXAwvEA0!B3lT@HKUqd{C@*G*3aGm literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/changelog/0.12.5.doctree b/mddocs/doctrees/changelog/0.12.5.doctree new file mode 100644 index 0000000000000000000000000000000000000000..2942c17a6384bd7a20ce69538f5d5fc2359901a1 GIT binary patch literal 8481 zcmd5>TW=gm6;9&#GLA2~H~~Sj>dlf}2jUsWNdQG?A#s*XFfq$Ii}oIUqK&APkkatV6F&nXgwXQD58#R4z!O3|@KyD7+V!4&6wqigf7UjIAV#sb|42Oa@w|hhdEX%jXeDQgnpC9a#C1bwQWMr$cY?fGvAB2 zFvBQtgYN5zJnAx^Bx^_HL}2bP3XLj6Y!OFK%bknp&m+9rTBcLCX4YLR!Iu^Hqrb$lhS^&@qg|tPy97m+M zLoho9bgne78H+2+D_55;uPiNJxtN3P=shw}fhl(+`ZUq;3Nh}`8+44e*6HQ-9Tmt7 zrKEooa9+~h30ZE^bTtDBn!^SMCcFoF(Vc+NGwWURW(?$aO!c_)Yrm0FnJHGpc% zc*Fz+?a$8xr0^PA*bDnB4%A4Rk9|f%?4GQDY&`NF0$Op$8vT^x7-VjLf;CzkE-l;k1++E4*;D3 z%npB6%~fGGEZc_#C?+$L=8}xTz?ILQ9z=!|zVJ`N$k-n~M_4|V; z^tBOC*~R^2*PN*QwS`2bLS+(ui}uP3VTIUJ;=|Do3kUup4HyM#ft0jsyu1}~(TxM2;6MOieqUUa+y?}`L=RwptTZxG6a42J9NRR!+#1vxU&-;xDMZ7&GWEvAU@2uZ= z`@Xq)`;FDLjd$+WL*w?1joUZx-+N;tJy7m$Z2a`c&mlT$v5}RsMR3Sp{T~UZLv0bq zM(rr;EgU{{sEUh9te)E3+-BH3Wan#dN<{b0);R7Nyjq4)@jFUL9J3Yo=HfMqwTfWP zg9~P;pdN*r$~Ge``uu~s_k)8gwXef&o9*`(+YP$XI}XwI8{VML!+ZA)1*t|tgIBd zvG?$qe1zLU&o92vV&F5+StTmCEu)=JF8YqBDOXspqYsqTdmxzaE8);x4dXsJf~L zf*hg!GEp!!w zu4T!tetr~PnEa&VvQq5{J zYigrQmaph`_PU(nLY(7 z(c?s(?0}6Q2vT|Z8HONeW}|E~2k7W{$!BtoW5y7gcHpHQ-gWu)bcMKS&(~Bnotcju zuwV8;HiLj!BBJL&F3LBU^N5_L7&w^O{TEUlC z9#$G~hfS19N!SRPk80DGLmL@snne2hsMEbid> z2*z;oR7C6`axiQQfgEhZa0ytK6L#Qtpe8KBoE#8zLe5#S2+VRx9$|pntP^{oSwIqo z)eEW~iG0gd4CnQV2D|cpxfF5|JApay6rhfi0V;E1R=?CJv4y$CP_k>t&YKS7s)~Xl znUs@xs37cSpbVynzK%OEoz2M14s+9a)fx2*n{a||J0^|9SKJ)9{21ny#pBvxc|ye- zkmGj&M3;F?BzKR$Cg(yDn#H6~DKn#1+7@J2p&`GTPmi$7o8e|f2wvQnIBch}CKtgD#p(HkIpxQ(f zVThTmP04e>q-PevStwxgP$A@rJkB5c!Wx*Ab*YO#;PA6Wd+3P2OjMpX(V*D{Apo9qQaoPq|-z1*JTodzYm3Ot&C$f1Ou1dI4!uf;q3Rp#sCL{mS}lutC(lbF7Q z0jFj_Z|Se;yZTdoy2f(0iUGa*tj&NV6%3FlT~VQ))1~THS5i7mOo17%9k>OoH0eTZ zv7l}Yactzf>W@wi@kiE8EQ zthRQTf113F;6iVvr5 F{4Wf=8v+0T literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/changelog/0.13.0.doctree b/mddocs/doctrees/changelog/0.13.0.doctree new file mode 100644 index 0000000000000000000000000000000000000000..81f90529319e6e6d8102350196fc229fe8d72c98 GIT binary patch literal 53222 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/changelog/0.13.3.doctree b/mddocs/doctrees/changelog/0.13.3.doctree new file mode 100644 index 0000000000000000000000000000000000000000..b39b5f90aa2e45cb57e0b82a0f6cd3eb7340d3bd GIT binary patch literal 4189 zcmc&%>uMy&6<$d@l4f*SNvm}N*)>Cm9XT-FBW>0Ri*azgunEHY;h2C!mUc~d%~YZ8 zu1;0;?g(s10!~&?V6ZEB0fYY-{6cwxyhXmMzD&=c1RNYPFwk}C)Twj(&N=nR-rwFo zxaEK5jw%?>=K)EQTqIOkvz>^z%xJFd7xs-m*-z|==}YoLiBiVYnp@zA8BY^QbNdXN z`;K4pR0Xww@2PU4G}j=CJWA-4=iGg^@>tLOJqU&$tFkDB)DaWnQhhw+8JW??kB3a_ zLOl*cp3MTqI8753a1l;OGNa*ZXX8p|JPyECN%fD{;bjUTV{eSS-=k18QZ<_o@3Hn<@g3H zWMg)Ez7GBk(~nvJv5j(qY}!&ZUI75xbeVR7%^r}FE~VLBpPdhfCuifcN2Bq>(dp?M zO|sqofX*E-&5cZ-T8~$#@oV-D+hE}ZJH5EkfZ9<``=?-kQn8ycAd%5fA#aEbbykGl zZ=r)u7&DU3C<0Q!=V5>lS!Xtke7C9;tk)R!tA=29pU{E=VtLGwXC7b|E5wD~&%T}B zzpL2Uhft&EeceZbkeMU}>mriBlcwUuuv9#s4L?Fn7$J#Jl&Bh>1#br9;ag3f%(!Od zB#1>8z9%L;r616g{^m+)nyIjRFqGIJ08xni4ZW1NKZ*ix;0c3;-7D?F!mMi{J8a$b z`~Rg{joXj97F4EpR4cox{Red2_HZ>f30olV$#>z=7KWJ*?q*fryODkR9fhv)P*YHr zZ5y49-W-iBY0Av!mI@y*DqxS{kMh93A{wL zarwRlNk0F}$>M<7O}c1XsJu$=6o6kY3P1y-jzIK`(~jvk)}7XNr=JtI(ZMCk!$Mr+ z!lrz-6`y@*jq_-2>oj zD86r8m~BP12KF)D9Jb@iREk`qGNE1kmSBWXZC;s8QR<@9_JTdVF#Q?LsU%uRi-qrB znq&nQJ5mVOr7+^mtyv4L*{M{BaPK7;2KBO(DOP)0#F6W(ZWpTbptHQP=4c8rxzMz; z`4ZCxx<+1R(R?aoW?!0pg=StvC{1oUQxN!rih@GqDbWiQuc+zmxUW7?H+#56Qa^B| z^f`q)*Uf;Tvq$x^XAbF{!;6~eQZ1;xgigvCuG@b>WDYNa10z)iN+02fsWZvx$8-2B zLZKH4xUA`ydOG^~NO64MU*3$UltM=6C`n2sBH&bwwKaDt&v&HsycBC5tn93aVE-`t zmC%I(&C&bI8!IQxfinP!rx^T`!XRt*QuMkK^C;Y%V7-oE*W8sf7Bay|+i7SQW-}J~ z6gI*gJZZ9lZJK>jY7s3+W}PF~_L4FyqBbAlRZrkZMVXV-h4qf7_)K@9Uyuq#%S1%; z%w^L7ofksUsC&|?5egl_c5WqnO1fDoPv_I%qsKtg6wi_F%5NEI$auFZkE$~Bos zjL%q#Ka%%$DqP<0s*K=S^M?|UR&dl^zpj>1Dq>Q*_5mg;=!3vlV1t*v3vv3gEO3Q; zTKVy?t^wD`9uf0=iX5nm!jY>IcyO(-`4Kl{8nTzv() zQqGRqt};hUTh)`}6Ch*dd3CJZEz@@24fXB;1J*4^tiDg!H|%Tn75kEX&OR$m-|gtD z0w%b}!8h0Jt84c8%aXn7mS1Di{SsaCGj?5?y>4*5*u9t|Mck_uS5*{;;fAlWt4pc) z@Zdl)WD}9L`QllRSBNcle@SA$xz!&!T9S`iEH$PEb8l5vy@QsB3KNUc)erQaSfnW; z0UOX@w%)DjUpU|Y^L$NxYTpNBFe3wec=jn&xa!un+ANWGTCxxH4$tGXOaO8*b8p8% O;tFD2J6y}h)zT3!9e75(00R0{@mKJWM;-u)7bN%_5aM^v+&Al7MwKen(pqP}`Q|&{ z@4L;J@B6Uv%W-W?{fUz-ptd(?8J6Yw7GbfNv3xUfiO1v5;)@@}AH*wSHZ&eGKMG9} zi!pGR)OM_pc=0{lT$KFWc33MF@B)jvjN2SU-_tG9vpribb9uBOe${Ge@3SZf{E+L^ z_Yc_nn(Z2Wva_R6&I7j7Zrg6Z#i&gji?wXO-8HN}X}_|uwwgYNrFwF;>qMlf;ctf< zVW03qgzpjVv_fK7oD%4%1QD;j)>^;Z+G?xE+qP$sL5p(NX~uN=UQA~JnDht$0&r#o zD7{coIOA<7y~}(bfAvU|a-wN)9@^cA!>`hhleWd+Gcj#C24f&%-S7-ISnC3=bZX45cVk(QW`PW0n z^&s>ezyC%oPB^wl;@t@`6&P>}lsO%B4SF7+=u7aJhTnJadl|o10cr}GD_yT-x;|$l z#1U=LCAvZ9NA9z1WLrtTCT-I-y1qLB{z);b=Ltk7qb{Opnh|p-0qB&Bv=!WJAXA)5 zn4MA4xv8yetZi(qu3cW;Si6`7+ljkmAQMxJWcnml`3f~|(jU@E+V0Tx&PV{Mp``Y2 zf&E6pF4m9|E@?BwjS;rF8?=?(+A?+8CN;c1fkTq?*=)f@v4jnf*ON*?8yT>-GlE#U z;hUQ8IfvSHC4ePZIwNir`rms#{ohO2>AO&;p=>_ajU0z?-Lau$k;9pKNO5^bw>fc3 zx5eLYtIXH-_U@o){3MWWV=D+ccnQnvz7x6L_gk&jCC!W6+ZmlDr27#G5AWJ8@gpuN z7x(s>w#PU`X@2iGp}JX>s^{C4#p=8w_fnJlnrm>AYVUymK+A&8kb1VXm7TuLY1G{b zV27Q(%Uc_J7mML4sa!zml*=9d-` zS@^&Cr9@Z>#>ZtC#}`ML^B*GT2gs9-m@fxtNm$h;ALZHn<74blD~K~wygkwsHAI#G z#C5Fk?Wd+yX@`o5#QEePrE}kRy8fV*q+iQsERv_H*_(3F!4WHuDltb5ruexX#7AOY z@#?56T@uE(_Qu6*LWx2r7MHU(St{7{-S#cNYdhpFamYJ|s4^}~%eBn;Hc~#uq2`H|Z zxcBpbe3)0QpM^-jReyH8N@utS2exKkFi|Rk3 zmTu;?^kJ^YgRe%9C)Kq_W$)4=hrhMAS24h_t+KRy@d;cdzyGzo&C~17qonSX9a0i@ zoX+1I(z)ccOx<%8b#sQ2@c-1&^o3)Bq9nJf^zYJ`_`?zXvz4U`7DdWzmG)g3+V}Wkm1oWVYj`G29HqCaUK5wJ zlGrE1v1-EE&fs8Cz}YVC`+5yHZ#)?e-B2J|x9Sp76T_7Z0~Q5_Dlp?t3JmKd48N{{;WxD~?AOGQeN}8@QDE38Vfdm3hCh$OP(GqO_R@H^ zQ_2D=+IcDa3GG5z-m{Mk@Mi~=<2MjfNp~q0sYFLNq4NcS8C4v271 z%)7DlU9rqV!()zWF3GlRKO`*aDL1&!(JI*;6U~f=Sh$R&Rma7m8HKWAtEZh^bR05@ z>gjgG3?rP|ULPOHr(_dLjS_mH?{VT;XyyLYKzpARU(Hi!t_2Z~JM^uNnC%k}ts3r! zF)mbZnHz3^i@DJEWd|AKhXMT5j>TM}0$PIJ0T`O;rTn_?hz0JOy6kz&yHKS8ox{Ue zobEx4=W|lJ`6(s==;-1@T_4D!Y5b{JWN5Sp=vcUNa>5|+#e{-yb4Ae~q8+DaeRu4a zK9DxzVV01nVTeN>5SVj9G%337dV1Lqr|>xoD{_NJte`e5(vAAqZt8)4HCQ>O3r023Cmtt=qulvp$(?&}3fOxOsw1uJp`Jr7A3 zRuODOW9S)<6nIimRIp3$7o>oZ$ny1pD+6kY3<94Kz5Jw#5|ir>Y-T4Rds4S-W^^41 zB-L^{OBJNuJiwp};ia_un8k*QDfXjJSokB%jYn(%P)7w#VMI@ zh#aqvKy+-^=JBni?}^2L1iET^%W;Jol6UeL;9CC{OvLE)5QD-Hm zQUIJf8c=W#hdX9KUwvh56ZTvTd*+aMNh!p?nu$m98Zu-LNZ7Io@2Ndd5ZZDwhs|cW z(M1+vk{9pJhzp2GSI?6(2cWY+M#zy^Vuv0#20FF-)WHw&d&2Q$=+843eS7_Wgsf&T z)ERZ1T8Ffg?nZZ`xiDF_6}U)3Q)t z9y-LP287tlNRm;Gw+>>23_5=nGWZZ0P(nENIBSCIKqszYJ35B6vhFIaPo+a8#H!Ov zYTA*QB;JD<6CW{?R1>5xA`t^l8@a*tWIU?k=G5?zR#tPmOy`+6UAdJ^1V2D#3{5|m zncSt9-W_XI{NZmh*=u46! zAD}lBH+?S>3uRBMJr+zG@ghSW`z92b&>3F>vP?<%FdRf=xLx1LgGaG04-s?j*^0#3 zY*+Qr>56>R=8(t8w6nvq(mQxXqTHB%B#Q~&N0|FKa0@Wtg)@CUrGEyC?7xhHxjW82 ziSHnVFs(r{VoJ+U@sf@6>Qf=lU{`!}|2pbk|H(Pj^?l zt6DQdtX%_vW05)}n=}Lnv3Ij!!(Rv?2|3y9CY-Z`=F*k zE@XlI?)#~F^}4F2x_e~V1}BVWs$Si9|K5G~-Fx4APj7nfeP3P2{tI?`P1|WK7tKng z;Z`iK6Kt)x<#yd__?@5c9C%yja%UtMYMD!(+isPuPOuIw%C=Lhw5&$w3B24v?R}@_ z6$OBodF?sRcYHi_8>Nah?=&3xHH2qx3f@;N8c%ucX47r?CEIluy{8PPZZ23yj~ce` zH@&0d<4%2{=-G}{t9V7n9iKBR3)c86Cl8N_@9-=8a%`^Fwh9LRJL;RQ1xDCs% z6i{BUt%s(56aeLn6qD%Nt*_UpAm$KLWp#~uQ~*1QD*L9k>3klr3c zShn57@K)Qs{n}H)kP8Z?@3)+}why_I95*->4>Ak3lxwEv;URwC)G+J(x2=_SGx}rB zEH8o!tjz|q=6CG9!G?Cr=>$jm5>O{7l+8xPshAv_VEt=5_Cc>5Z1K$n>b7Iw7;KhW zw(WtI=a-r-x8^RqrW0(eISs3G=DJ{W(}Y-n%%1j~Y2OB->^mSa1OMBH|GfhLdliV< z49pp>_exxQ)PzpkF50{7NqbvjKYLBt6}2na>kuzabbZg#*am(4K=fZSkzK zHUR9+q-i5~xd|hs7E`l5hl}?X4;v$shbO1T4j&ntIC4Ph+QtjkGL6e1sm(h(Y_tN2 z)Aob*277$ko|sNVU$BXAe;CbA^5(%f21MN&_ns;Hhp8s^o*L@STI_ySs67jfQ%)}fE_by zwG{(8Y&ibQCEmS!65(IZkN+ani}2n8Q87B&b5^MO!A(p?V~#fl=8h3_$HX8q<**1@ z3uBlng_uSkkF+9WS%#E%9fyfsbil9^OiMh;o9-782i}#R1Ei$yP$XDj5*(gu*J_qu zsyR@_$V^KmgcYYZCC9hw%Ik{{46R$o(S#Tqo)4LtG&+sC<=bwhSSTDkc;V6ePd;Y6 ziFCoU-FB^FoHqCXFnrf&S)T8ngKN(`C@3~mi*dXvvyb4+hDDjYi2{a?~Ey%qJ@befVU=%3Uj;n`c} zotov1E?TWQtL1u680b?c0DmVv)0-VC6drRHY#MPZ#+YF>;CQq!K^SGX(SXr(Tb>la zoFy@{D&Q>qhcf&X%>8b85l+LL>DdM51a#5x7P~U z@JRS4E#HJYL&7}W;B0sRLNMS$7){KI@KPiyTybjTzZf-V(Mm&%1a!1)Hc8Kbt2Gbx zNx`;II1Y2;+pv~Sqg-plCo`a;M#aL=uD~U6@BrVlhY_1b4eJu@sxxo2J*#C@T(oMq zKK(_A*NcS<1hQ=6K?UZw4jMeK4LLGk*0RaemVIoS6;g9CIE*tw(4cUxWwxk)yoEf> zIVif)kePW65dieSLzvB(DPv^G!4Ly^AxPm-9l)rY0lAh??AW-37f`l5k6C3t2vZdx z&S!Jpw^}J|HlP7@IHq+ICQ-2hqa0a2W0grv1oJN9Ax1o#!k{^R3o`@>A+9}O3S%&} zUNPXQfc<0@UvFMArRsnV+bP@Zt>_p6aT-Re-SC}ynp(Vt^cVm0MwvEy^rts9x@65& z%u7!YhEiNzisCh`vNP|X7aF)RZ715g#|sPx66b~7qzZCrNJ;TndIYJ%68KG=? zkmLoXG^7X6B+MF37Y;D=K{|-se9#2NwaGG}7+kwBARerFF0_$cr?PF8VPbqXA<@`v zSx>f|mPN}nmp%>OI<4Z26E&y2h|vwX2(yG@+$HZQ`#M>Zxdw6g7_CmDib+LEm8kbtykgS?b1(pz zQFLjbv+3ygL#NI?_K;DAYvYs9&cY#dp>Yfom@1&HPeS>bb?UksnS9{zHS&mE3w`|Cvb-tj31&Bnyu#=5dxg?3-UjdR7vT{`7~MaB2geZ}!cx?4 zLE?(%X#YBk4onC4_URsWA~Jt7KbgM)@_uc#Wt`;2jxJsdD-Nj?M(kOH)aUafCHtc# zGVz|)vE&Y@>|#q?yxHt8wA^;H>lLkjx0Kx)%}-q~!v&69#K;9VqEBvZ$w5G;bLM(l z!F~-Wd=)7CwNyyH0LTU4E)@XLU?Wu2jmuE5q2(@hJ!i{zSm4%5*DFOCw{w#w9C;}M z?P(~4>W1~}*7Yuj>+Q+vI()gBp8NpJ`X?~!hZ?h@v9p7tE7uqjU;S}}@GX5K{IdKA zM+;tEA4r1V1y$c!jr4B|Js~mAxs?Q&eJ{?Gkqj>F?(&)>qY~akyn3iFUflrU9?!?C z>T6lM49s8Ng}J_-la%CW8BzAn`ljsho+w*Y5vmWY4zGHYuw`Z=9bT}JY!iZxq+wq{ z>px#j!~P77;uF!4zE-zV0DXQP3FS&(Lb*whF%)dC!8dK0wGvxpD@HCj)~!W7tge-la3|wCH%SxOE^5b5^Sv@xY@?S9-(H6By@ru zHsTUBicOS|Q=srU)S&tc#84iMyQNFm!%FCYQ}}6VP^zDz4=J}W9c&Ttg~%JIe!3?) z!6B%A5?`#Qj&hE^qYKlCp5q!)VtsrWspB8^M5aXF2-R^2>k5Qs8dghD#VFjjMFd!s zm%(NpYmw;7KO>y}q8FU1|CL&E`R{`izRaXheLwz@k`5B=zYu)Fn^1%Q4j{5LxcaMT zQ2hw~_$Yo{seTMUg|1dVf#19IGr})2ag+d>?6A0SaDy1zJc2g1gh+T3j5T9<6wLrU zLu25-ptmlK`rkj2K`W_NKcdt}qiFqg(23kRCpohBcd5}>PeDM*{vN-I(B9b>+6Ot> z)jz{Pki%YBq3TzZXTc^GfRtE@zoOoaUV2we&nQgK?fAGo7c)H zyYRd^0nUVOD0vYJDs}}-lj4GfLN}-!fz9rZct}bO*sc=POaU=iK(ia@D#1*l#Xr`u z?yz?^FQX8%3|szK$B;~X%W66nxSMeX zKE@nA)_jcMk5i+X9Q$|WWc_zh}vvo8!-qY{cIE}IK(+OkaeaT}($>6Q^*x;Evx5ct{-9v}_A(BBa zRP;if{m`20Y$U0;&K~JgXCo*{X^0Pq&=4gg-$%<~Bw9WwSBMwCzH!~U2P+knVKl5I zV|Es`JszT~v$M$GFR+4ZN=VPnKH^-m=ppj>_~YcM8T=6?)IFq~!!}R0QF_2CJE(xx z7(woxm6AO2rn3j=8w!@8=7)D=P@AGbff^*cS(b;&nowz~HZZ98$2MCPd{4MuGErO& zL|To?SY^ouF1&n&N+c<#f9?#cgJHRIbl(#A>1Z1osv&Z=>-olriTc<}7Q>K=$PIVi zc*Lx@Zd2lRfLCEUsCM8Op_W)y1WeR1o`L_SF%OTL3bmLMm}pO=wtn5F?f}jWdH7icwG`dIeedXk73){um-S z2`SZ3y^DfJ5;`j)rRp9Cn~Dpe^i(-dvZYam5sa!31G*ILwZJPjG(>e^+jr~uAkx2z z9C@z^#rAv*8go_*41(8v|jp zJvO5$V0EmrCtSIFy#W6IJI?>8kl|U$P<`%sVN8=;qkGhWts8oXvrEm~h>w_NR-Zpk zC?ciq)!aawWDLpBwUD~DMLbb}+@b-=H6%mAU%sl-qI9L>n@TdDYfrkzVMh3tpy27RY~&+{bG{Pzw(mxF}c!UihEfKiOMLN))*Qe z^{Y;HZ`3}|HQuRDX6U*myVbGDRD63NZ1yOM@9~91Zbs$wSl&7if)5zsKGEJ6Gr37w zRp!A!MBIoyc?qkjp3uuwf@cRpuSn?SU(RKqozFDAImtA=y6-ijoSFxK5ETnM(Y%fv~xmTd#z8KyH2w zg^>fPQk&Q^QUUPIfpFXrk4PwRb=Kt@NTo>a-YXYtvgRxd?gh7`M?E;5;ku-{9dSUa z2#e`a_!P72jZwTqjh`r#>>&_&%WdQ`U?)DxTQX>Z=;0jgWyF)(Z)NSHfM;;y>}*o7 zN9V^F5!1yF`4s%v(+x%8#Uy>+D=)@#&?Xm`bD-@|j`orl)V_-sFXO!Eif`%6IGe}} z`4r5!UWq5kFqy)Q$NJ&Mxg69iCVHx8+zWtEto&Y`;C&`50j1h-~@ zslvZjKm2nUKOmefA|R;%q<5-%#<3=!DAQj5l=gY9*(1bf{ecWj@|nL1 zxY$p#Yj&UExj8+wBOP`&*ZNZZHP}yv^pmFL^}^reTC7N*>xyd zYTqT={j;;V@*tXYgIDGIj(w&Lb9fL*T}mC;)BRG>f}1wQg_cRW&& z(1L^7=e^+*(*hlnd|I#osqSL*Wp*8E0k!8^kPaUkEZ89|onZYzc4ksX zKR}9%>j%c?P7R16^79&te4R#l>+vn1~!v@Qj-W5rb!!+^izPXjw8}O z@$_JXqxMPxTOA0;^+g4gj$yj=X=j{t44ou_fAYnZ)|8BXv7sG>R^Hx=8icZ2Op@JA zX(?XGrSt|7K0n(RK8+BcY~7PiC-Q#@1vei@M(_(uae_ z!!65dG;8fTQuC9=iQ*K}^#A=`KPUdWdv>;nvVyZSGe7Yt9!(rBQk!bax-V=tF^Ok~ z#j~PuR-7s$&NiVM8gxY`ZlDr{!(qqBEE$uBC+=p)Nl-loPJ5eRJ+1Jb?gPlF!(k`v zPoPGpT^uydi(`nW3I_!fsEVMHjQWcAWbG*XIVLKG+}2nXhtGsyZ~{m%r5O=Quun4H zh-!~`2jSPnYGtlmG7}iJWFQN~k z7mASHMlvnLhHDlDqEX{wG~07HgA7McQn}4pln^zj2FbFFqYEgTY0n*PV)i^bJ2e5q z*|1X2)wpvN<(1=b)ppTv{77HJv5DdMEBF%ASw;wK-|7R2V;qITevi;0#~;&rA^KdI zi~fU#aC}BbFj0(H$%{?wZw0{TGyu8gZt7KpU&#_E;Bw&u#fjl_w*uhL2f`;dPwTj3 z+e$f4w;;^lW%OmTBJ={O{bx8oqHyQJ^JEWoh4~I~Pbkc0j)TJ6|Euvft}Ht>>n%kz ztk`q~*bg;encR|2A7PNJIIH8P&LR6f zxL*U2sa$fvQoqRqHXlat$l@4HDHxp{2&0>hk}s6t=qWv9BY8)*ODGY)CB-3x->7s| z8i<%p%##w}FrEoZ_Q|>ApGP&2;WYgXsRo$bdxOt1gb-9<36#Z z=7z-gN(b*4h>kprzfDC__uVC)H$`~Hn<%KhcOX<_&W+C2d{c`L)fxv9tK~W6!jal{ zO)dL#E>$>ZB5(G$iF)CjY!NUN=KPMvoVfDuh%byJ*9*~VNdfiy8mL^Rj`~kFb=jD0 z17_j+OyadwK2osy(}A#xNngh(pY)d?&EI5HW-=snEU0~#^zX{C{M04?O`=>#eklB2 zVa>NR*2HCx=}j(lC4V>b2MUPqY9KO6l+zpan=JD@7_qgsiV+?t7!7af!wU^Dipg8Y zCZD{QA;160D9mI=C~s=tCGX!3m+rX&3CUA3H=u)s5e^z5CaVV#Gz|=XEBy9FLzuBS#NmkUV(g#B6kh-<7Jp5`E@(rLqY|Cz z)ZSIct{@M8|Sy0Q0R4h zYNCUSeKy=h|6&4%En>e%5z`N96r>xD@Y0#;b0i?_40}I)J6Rqd6XiK{-w_)+{G-$_lU#s%R!u``$6DOb%L z2S9Pv47*f>Zt=*tRAlQZ9K78|9zaSadJu`{k8_KMxQKg1s{c9sf+6uVkVp?|U;G_Z zP4FFu^7wC&SV2Aqq5LUZ}!lW_7&aA&QaoBi3>gUqZQn5}4 zidW`}q@?VM9d^=1l(`uf)q>-%nJpEKbL875#7ok7F4Vpp7bae)=x zPU0^i#T6c4P8)prG3S)?z;Tz@$;mGF;`?_`<2D3JNFiH=_<_I?i6vx1#LiZ12jzDm zMDKC-fJJvS@!ZU`D|ZAdAm%N3Es zGaj>T$&yBFxL#?}r4+b(hPJj-{!CIvBQ2@Unlk* zRi4n%^7~$4J&A`CheyMPm-(-kIUvF}5za)|?4WU6Tn{1DB)p}A(c|Hi|2h(SU_5ZB z@y)uppJNU?eYpx@{{Zc|XItAXM&o=7xj$aOm{GPB*=0A5fZZVnEAu7Z z09>Tx9?)*#JpQtUi*F3$bbJd1(_Vor$Abfes*_%GB@((r6X6NIeH)j3$o^={1EGwa z@$7H{wp(11Pnuva)p1V&9mz^5Qd}NVTqpuxJT8F=?Rw+b$iDgt|8?J}@ss;rfvXjK zl8+SBKFHeIclk1k46zA5x_|>JN*5zET!WoVPNd2qFe=XaXWYSs9Hh=Njfz%u?FY|) zT5?Ofq-TV|I#w(e(VfT+hVM8h#7nS)*#4##9if~2S@@1Ft&^V>&S8f>Wpw#QaLG7K zn4{6PBE(W0DTW&zKs(L_(9(&AXK^5an3SW*`{qnWszD;!bUr#fbyiTu5OvH>!mjbY>8bdb^Uf6y8PLG^N@-o2S&N`p8qM39I z)@qE;u9$@tk3}}ckQet&F|M+v25+igDOfFR<=)*e{tXJ@MUj=xnf1_dLEv<7=PFaS zA~0RE(mgfR75u5B5p`;9`~azSYXvT{9kTWw7141{rPL8N1tZxlQO&A5kgAd^{P43OGSagIgh3`hEMSaogjh!;{@ z6o?AD+8Vp!+Oj*6f^e3l2)0WRu;&WEr!;_>OpqEwSTK$0+9#cj-}MoGq1!Go+$k{> zZxkHgGZ2n39nta2rz2M&^Cub2nd}L5gxYuM$Qu+VdhHe`nhpgQu0}7c6Ror!x+eWr zvDT83+#Y+b$nm%Ok>jUQb&cMrGeLYT>{0L&t3HX~ju1iiLc#F!8pup>%h3>GINHmy zQt4GEB%e~g8%p*G#+*znL#3qlTq)DxCkJxKXHQ>ij2J>R7#>+G zOcV_~q-kJ$a!0hRfR0SWOfP~7_b+`Y;z34u0A=~oY9_~HdFo*# z7RJ?vZFQayq(xp_qjA1NB5cXE>l5B8`ncE&+0tO!OusOIV>$B?@Pgi?Pc1hoxF*m# z9qbK}RbDIDUe>^8s(2jRbnRA6{dNA+3!(?^-aVNoW5f5S2fwHELzTL0-%2~!Exuaf+tG6^Dq z`JdMvPm0wIKgGlv;LsStmj1slNnkXiNZ!(288Fpe%;x%|tGh5}y^v!)3>G z7&A+@WAo?&9kgC)x9Aza<{UZs^DXO=gA37-4|V3%raa>~Z#oQy_zY9E_>?93a6&u2 zTQYHSw<~W>=U4MCu|vF(7{m*X2{N6vnjz3A@H3mO#w7qoOhI2pR=&Sb=;OX+56lyZlf=qfAkFxpPrs6Jv9B`@kbvzc?_qJ zdz6(QOG3vmaT2v)iJ@}DXUhz^ca=lt5M>9vm|%7`-s|y4A3Sm9(bF>*j&Xv=2*jv~ zlqiY=#`rUa3?0Re-Iq8fdtUNJ9B(L&zMejJffCakIiyhuQ+8%HxVb|4Y`yC@J#zZO zLu=CYx#>qA&+5CtuHRW=$kFlK6(q^a7SHA*HSFQ4QZ6c_fZaNTj3hX1F}6v(idx`= zc&=oeFA3EUnRnL9uB>IV-mF=?)ZrmIHeQ?$&Ur^#&pR;o*L{{f+i%Rb+&T`VFE0|V z(c??l(2M`$pk{pJuSrE|TQmQULo@`KBCFNnjZfHCd67nv=n54upuGvgq1v1qWnO^5 zZ#M|{U9*G!MURiWZFUT!95^*s+!$a3oB%wxLdW>C!`72VQG{!nVoGX91^0zk?&ow! z6fkNaDQg~OGloh0^ie`1)RnUh!_juiwmdhIaD^rSJ!S~f@QAc@KL*8E9EXX~?q`z% zvGceX0L4JQxd^>$K#j~YE(}0j3o2`Hw*a$ptTX{v7OV&(5f?M`qK;}3h=T?tRg@S8 zl>|yPKmifov`tpDPGuH2l^z=Ebyq9g)1TFq^HaZ{M@y0K=bqBkiRa&7BKa<#$)cFr zYrg)tLIKD`i8i)1idh`XujZiOm?4=2gszJ6yu$*KMUMD*O;7d;<|=*d{#direj zJWS7=H=&|eM~S}f)iEl}y0{(;sK2!6%XpALIbxgB8Vj02u7?kcH<5_0c;R=h|@8>)Yz2{(A7yR?( zyy=QrCU$l!<~{x4Cxti}*VjD6DI&J}5wXQtJRKZ|8V~5Rh#qa$j2dblOdE( zN2q<5j=WWuLFB)1m!p3Ne>E>#(~@B2nxVol(IQ4&%9)QbDax7WV?`m}qbWqDfTjI; zw^Ys(d6aV%=_tq$+}^eG6VSGI^`M>9o@-~i_9w@P$o6$PeQ&x-OE@Xts|G)ES3a%r zJyQw7X#6KbZmfRAR1UnZO`Fov*R-T4Bx^vJIfAljBm$dkHVtfSbm~toN|^CcErZ0_%@@-`;tyAythxZ<$T}w z_mO07=t(ki!TWhgXg9uuRKkEpu=qUkl5v0va_-3PRB?>&(kP7=CE!dMRn(Hhh3KII z>W~I1Q=O&5PyMr?N>?R$HfFm7W}(JO+_tHTs9<+&Ana~RQ)s1!E`*aasJHc*L7lvh zXHe;1nL)+xkxm5Nwzm!WXIFWW7AeLh+LCNxLs_lnE)luxzGZCEVd=XyNyRrcE3%5_3zQr_k36G;t=IcSrVK@&HFo!K z%|SkB#cl7$Ex~;2aYgJ$_V}({HY>T z9kYLgG3b&>P-n0|c##h($;1~u9g%e3)|Yg@m?Yg%%4+4cIFgmUeH7vSGkxLxCn4U? zTR7-n^@6Ax4*C*s{UtVn;^Ck-tY5e8YT4Sgu(PTSTLT;0mwoNawhEQJ_+tipt4?73 zF_;fu==CR|*J~SCh(=4~BT``D-hKiLeBz1#$)&w|f{=7JX`)MXHh&G{^rTyE(*m9s zM8PGZ`YE=ehhu{?N^nZd0-QLUxT7@lP<%t40S;3cgR@88+U$J022OH~_OD(@rVrbBr zKjiF8!$aYCiPL_lM6Uu57#@s;nnO~GirHGEx*Jw-(|tnM#9^${K{-tomPzGA-20nT3^qG5qReL6Uh8)ao- z{cK`x<=sd&LjnH}8u;|MOCU~b;Sj1r*y9$wTplQvYM#QQSBQ@GTy+~h z3lFY&MWS8O>>I~CJYI!&ABA4!-(3f#e4jc*bb<~_sosJg?0^)G2BKVh3kmA3FeZcV zuN%~~N?{BuCR*uw=eE3|T`7|oATFBm1kq$!{7gYjCJHJG+-o$k4J(b66L7T0T|&GQ ztjG{zEYhS)EPFDSao4ZG;QkHMO(pL7(3%)6#sZ3{PW1s!(T96AeMs3|QRp)^h$D&E zA(Zs5j1A)VXeN8D)_;bL*o*^-unnP8eNYHa970JXut;r8Vme{@B=$F<#IoB3`R{t$ncc}+fQ+v+pbiGc_Rg#8tr!oCYRjA}-MhlqqZ<2eX zSi8(6#xkSZ)YXn+S^Jcd21`EBJDG)R2FR z&0)0b;Q)o2ks4rIzvLbik%Ohw1Iq-2n|ZLk!og{p&N4m5rihkRM;IQzvsgOeT{LE# zM%gmhE|w@|K?lILtY!^4iV6=~FPW_dzLexs$^yw1k8&N@Z;vcSY0+9K;gHet;;~aG z)}bw1bhaqkAw43_>_uoFY(qmFJlai!NS%av6VOlSH?T;>CxjBVj9{A*KTWhZylH2T zVB;mT)<*Q6ZU7XBvbAtZ9(v-{NqOj7()B|csjMcEE`K)E%wEpB{Cnv1zh|~5Lyv>+ zT~jsfmM=dE^?6ZJZN?71`yk_Ska;;9qF~ zGaV9{@+T~qBl+d4WRQJop8@$_FbMy|86U1^gIhDkPpDl( zVIzf}35}kV*v0kz#x7nhVi))~j9t81>HpPQ|LImSs%yF*-;f%L*aeZm)G)?fov?h| z{Z`1`?Dlf`P14}zIlX=IDvb;~_W;wsRM&*$G5)SF0-!dU3_hcIvQyDI2iYZ*JkdG+ zCPpZZp1|z~Q#3C(`0W8=|2R#k$cSMffaF(&9XdfP3w9(vl1oO*MbQ*aNgyEQMHDrS zQO*g~rUc!=K@uL@Vo#-8)Lg)KTj`~taLI*QMml^`8XZz|(i=qHxRYC*)Jgf<;H|~A zD8lDMF9PmO2k(C2V5yAiB~1(|R^|3St;(dZD)g_kD)>E8gMXp*pU0}ajT9iVDnvrJ zRnZB{9=kGz{hd(1vfE3mLi!ku_4IF&Eelx+883;&e}-TK#^zD2t=`H*4=KRF9%e*- z5ORqSp)~%45<%L-qYopKhbO1T4j&ntIC21QHE0|79z7PoxlVP39uM_O1bU#8BUJ=KVz!EkponZ4DDa_!7R}ci7QN!45`<-d~Vcbc#U^Q?y zj_?I zMcC{I!Jc^lK%J_kJbotF?!ji`TC37B>}-!nRuFf>?Oknue(b(6&%y7v#b1_K?Q4lm zKe)CF3IN*5eka(;d9Fa&#GrJ7+qE|ui0aoceuEtxZiIvU`nLEB?PFY31sb4ro-JZ% zonU(ng&}PmRs)XX{;}XXtI-Lrr$At~`&>3{&Fvf1?Aw=ScQ<17*Zw_|Q5kjTt zQE;6DMcZC!*PEqK8NpcgfYlH;5Qqbqo$LuybCQ1asOeelid$N)lPc}PO*yV-mDDep zmX!SxP10Ohcb3E^KoCOFPkgvV4iXHwZ6L$M2lowLjR=w3?;xW(=l@^n|XODzSUo zZj!lCJVxa&+s{&TTMHOVH&9E409uxCIuJsfKE$yV&&OVQ_-^oXM{B;Ufu|=C_*Y5r zbPW`#>>^5z9m}6*eZUkTH(RfvvmunZU=*PWr*md&a65FeUJ3_iNC4Fzmo3~qw~L2; zOSZFM*YH2+??x&b?*!YtI*N`i?l1H&W5T_4MPz z@Tsf6Oh2~JkB#)>0R4Cs{rDI``c?dB2Sd~YyGo3vAJ)ee`{^qT{1pcJ3IqEL19^pk zdxn8}hJm@#w(p=18D1yt%l4ZYQuaGIMzoiWW3-*nVi-{thhy{uSqZQt6~kdtIEtdUUqn891S4; zglv{<$K|L#;qB;lKX0Wz=hnjU!J6*X9)_)lVi1R9uk52eG5D-pAD8C@H|Vg$8_fT( zsO%&vnmUG+%h5W)mJ@VB6`ub*gjW3q*mh=%_s-5^uoAc{5B5fEx8NOz1~&G1IDE`> cdl>1KTDwAHX@SOUp+a+jFqh=wwC9TdA5+P}=Kufz literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/changelog/0.7.1.doctree b/mddocs/doctrees/changelog/0.7.1.doctree new file mode 100644 index 0000000000000000000000000000000000000000..0c5545a127066cacb3c18e4414048944c7ebdb14 GIT binary patch literal 8224 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/changelog/0.8.0.doctree b/mddocs/doctrees/changelog/0.8.0.doctree new file mode 100644 index 0000000000000000000000000000000000000000..43e1a0abf7a45eb11f6991e689966d159fbec9d8 GIT binary patch literal 43955 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/changelog/0.8.1.doctree b/mddocs/doctrees/changelog/0.8.1.doctree new file mode 100644 index 0000000000000000000000000000000000000000..cbc63ea52da01874c5133c91818698b2ff65e519 GIT binary patch literal 15137 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 JRfuYp{{V8w&(;6{ literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/changelog/0.9.3.doctree b/mddocs/doctrees/changelog/0.9.3.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ad5f9d18934483a520d2b242b38fda106771f278 GIT binary patch literal 3725 zcmc&%ZEGb(7M>(?liZu+P7*UJI3pL)*>T}^XJ*!29mHi-d?A<*B7(xu)a|a@RiwMC zw_av$Kw(!94AeJk#4mz>Xn(=}8K0`|zHf}X>wYnCq3hJCQ|IM*PM!Rt_w<|LweV+e zsG9L=7LhEgL`IdfyO~H$Nh|HXcd!4`eeLerz9dhSFfygiUV}!;c#%n3xkuO>dVS3c z6}1LlP-dbu*AR*-$>@|<+<(sPv7Y@Qibh{4Q`bW3gb8t>z8dk85964Z zd88PpMW!My;t9!e8oze`bljf9XE-^Y6ow8)_qwq5rqE&nv z-Fpzd5r^Y3uQED|m@bQfV>^!=>my(~r3erKcS(TL2P+PDy1n4{vfekpey6hnL^F%PFfJ9AjEgCB4zWno0)pCn2_`WSqM5?q|lC?*exTuvv2%g1#)(f zk}BgFX}qx;e|7AS%Ge!Ea(}I3&)e+|W&3YSrIT8ULgeo_d%fTlb!VG)yCx_LoH;fV z!fqib_A)9q!tWLQUd8VYg4%}X!PmcZzWzoeIKv`#$nLZKXWnx{x~sh2fW_>T-8jGQh#K-J!Y>UO`?B4k^-EY(6eg6>Z?>Fi;1|dosE1-tRSeJDivKD*d z#3>_HPEih!2ku3vkMl7j!uOgs!FpX(A9VuTf7j%r5BLlS3)DJamMk28^xGW%vQcMW zz=2*!@!rG~1=UHx;nsq(jYFQ$ek0+UmP@<#1$Z{MiYb9)}^}-w2@qrnk}7T zx{c&lPcPz{{qJ!-28et-J1B_!Z&j<8luLide>z~do8&mVF%G!CSUMe2X(n{eCEK5v zd^`;$BOS4VaMTHx;PdVRK3z4IOrhW0a|R+5%h)Yn?A0^XAQhePK_u zB$X;c34PbdB~?w!>uI4$;m)qvL8n7AER6^Mh8?Cx`i7FUjpy9%caqR@&h4(ETJtK$ z%Xs9W5?;Eg6qTk`hEDT2!Rw>u_^{i;=-Oy^&OQX#b6QbJw2%&q(E2nfYb^Gp5Wd0T zb)2Cvv9o)P3xIHS0fj-kG_t_zK#Mf-E!pqFl^%SSm*@jin5l%OOPk-}p+du{OqtB4 zLYD43J5+cF>LeAVf7lfazSKx)L4zcD!Zk~}X94%M2ikW6X^}L%)dhV_kbjFcIO|(%9X)mKd6RsORA+kaip@G-9grtvhq|}Gx{o@sKmf*n^8MK`38$BJr zIaVCsw^lb3Dy5JK+EG?ei3)gEQ|;`HCi6>Hdclgbx7T*oOt631VPkaRKzH=k>c-kh zd*}lI@DxLS3IycrL4ihTFv=j^8P=Palk9Vnrb1?zhHAhD*KlCw+-|3$nj%J|gJ&HY z*tQ)KqeZe1*-Z|t9b~4glQ|xd)ga)2qO3^a4c-WnfOKE_g{W54WFncB9!-aIRtrUw z<&yy-sZJhq#T&fdNHVU-r0{{XUhZ@}0q6D+492`FkPb!VJ~{k@9oDo?=A)PoX5ULD1ipJ-*cZF! z9by&Lmzs$x5;6y!ZhFn&05lUcew0U}9J`Q@UpxI1@;Q{#bj`e%EX?2O%!9eaN_E23`n#>Z$b5`I7@Ln&3 z$Nj!42`1vd8&FyyQO|{SvrGz+k~X!s@f^b+6uu!FvK&%~(-)@36@DrYlSSJDu7Dnq z@@fhUv`yjIcL~huYieP5#a*Ofq946393WFpXa@RT!UIOB#vM0PXk8?J z!Rw=T-~m>PY17cI>=vyaJIo_?w`nEdE|7@fn}ZuVX-4;eo1^uAAbcxlCv0EY}CJ` z6Xv6*>`P-0mMa=;9i$w%z}VnX)JQyWJG~~DWl5t4Y5)P{L=XUAaj5PduhPO~V7AD8Ll|Vf3$_iT8C#-X0OeAhKmY&$ literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/changelog/0.9.4.doctree b/mddocs/doctrees/changelog/0.9.4.doctree new file mode 100644 index 0000000000000000000000000000000000000000..d0b203bac57214ae692e7f6dc6aa4a011810ba65 GIT binary patch literal 14841 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/changelog/DRAFT.doctree b/mddocs/doctrees/changelog/DRAFT.doctree new file mode 100644 index 0000000000000000000000000000000000000000..61303865a885c9ac9794f699c0216cf8d87c297f GIT binary patch literal 2914 zcmc&$TWcIQ6prnz?Oop;CoMDyrZnjVJmaJdEupl8mO?T0Ln-Z35ZW2-N?0RJB~7x1 z1lmF&0lh`-@9ihe%-UY^(!O+HKsq`)*YBL8-v)pEvT-eae#h2as98qKQt6V};MYrC zG!;`Wya^xw9$toz{Ls+nRyU?#!CwPM!G$ahQ{idw+mYP~X|wMBJGL2HCme`ck&;b> z5^*^f#|QrJEIWK*o4VG<5w7)_eQ_u%I%SU^A9Ckv`#8@+=%JO>; zkB_>0Sf-mtW7)9LA->0snkjP^BD7*oW`@zyaRxh+Ag1!;?7^qmqdZ;Dg(}%BQQDZYwL(&B zLBx8XXw|OP%cj2AjA?O(C?uX0DqY|Qex)%Y_(%UKLBWp-s!CDP)*8R`b>N@a#;-X# zjcNnG^Eb|Z^J2m7a2Rm|33Z?@ZX11 ztFS!T`gw2bzeGZ4c*b}6L%#V|JqNT`75PRy=g0iP$ujs?{E*D+hp)W#_T0+e+Z;%& zSZenrEPN*q5!QQ;ICq^sBREg03Oi^xjj(5MR z8&@|jocaNZUNS6(Q>K`qPMf$%8F#d*vDq+M$GoB`%z|IagWqTkA<%vXhEca|jKprs z>4HQg##0zHfaT^q`27jQDDBv#!&j(JRyfsEWH!;J3a|XO1^jDL=qk1|3xV&o6ed}s zkmtg2(pLlYbr-twKyVC8ElRRy3_)G?BaT`i-FD#j*i0anj=IKPPzNO&H&dwFeol=- zEWv>$RDm)?QVJGbiuMVG$Px4zU4kq4VdEx8UmRJ1_3f*Ngc+j^0T@cj4Dt|7EdYg` zHvX5S43d=K?<|}wh~Ua^w?Z!r=&#S_A_xG| zC@Q1CokU4^kEy>P)s{7-CbKG{X_w7vZ5g?IGeM+q#K!- z?>FHLrkhHo2ccUX8<_~%_R(8$1tpf5RGv&lX(jT#fia3awcC>=zZXjmuu)T>P>M>p z@Wbwhe!FHhnNOhT%x0vFDQu5*;ot4M8pt-Q&mGq)(`Jftx*0i>0pKK#RY;FRI@Uoy zx_A5;;<;@m#e(=qxe$N97mv!N5wB;=WP-U#(t)0k$6*O&Lr2FbAq>}WvhMEykQJF% z=G*~d>xhuoe%GEWM`whKDVO*G-s@6F+;7^7;vw*BgOaug)Qz-nw?yiKcA>q4W(a$b z__o*(wxfg}NT_8tBo5P!nLFYYO|ZOW=vJz?NnuoDR?E;B- za|qOiBkhYNP1(K0cT;TW{D5y-zklVZ9nvpR84C}R0~@tW=I3z?#*q)!wMgvN!?5PmO@L}CIpjX?VFDN20s58enc7IDS?8*7Q_<|ahvB`D)fIo>tKl=xV4}aqT literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/changelog/NEXT_RELEASE.doctree b/mddocs/doctrees/changelog/NEXT_RELEASE.doctree new file mode 100644 index 0000000000000000000000000000000000000000..d9b7ad2db3a70fc65f3f4e05cbec5504473d2ca6 GIT binary patch literal 3107 zcmc&$-)kH<5RUDf?K}T=oHWoRn9`&_z_(7?w8fN`w9S)SKcpe_B?#TF?j)?0wvyI4 zLjr9ekbvHz_V4W{?cUiwN=p0EgM-y*G&7p{zR~>F`Q!TPrS$U~w&FsK`?M&OE|?8| zxzKrCGUdYC@X7DtMfliv4Si;HZE_a;C1~Va$igrco?>%7>N_EAzcuiltq0Z#2ccG^ zU_+rq{G8h39sg&)zx&+QRi%w1Tlo#10O2xpS30$TCrm`j!hO3)>eu8_;6J zvfKCe_u6y#OegmSvSz(q{0=L}5`g zzm!XAErj^)C|WjO%SBzCEe13{Mi!FJ0+lZC9luZ;5&ZrCRG{E{IaP%yXcLW}`!?`T zZS9vF9Ywc+U-OF-l&yYktRt1tQjfj~{<0K`g~NHjSW%P(!EDz9%5UIO{1z& z9@vUT9xqphS!PaHy6Sf^bp8PU%zpu1lq|CV4>g%9t1Klii~PxQPSuE^D%sJ)UmlZ3 z4}X04;OB>ZR4X6@bM=6|qbhB6`4hk63{_Ur)FM#k5rSzj+Hu-Z84fS`-b9BLSk^Jq zbACOqO+)}`Z5{d5i6mj<$S+&woKPdQvaM-r8XBz}Qw5;$grZ&8DPR4fu3c5TaO68^ zT9Va>ZAgjh$*3X2t^b;Ko_+Bo>REclfs5CZSVQ0TSG+DNR{oX$yPQrtyA z9Yk(UgWn#)Osx00&DZEJEO)9d$#|$u8D9H!3;I^1g^z{7_ZtZi69Hw(GvPRyuo?c^ z11-Ig9K%wJmF$Eesq?SzQ?Nljg1hx+)F|W<8fc;=BwZ9G zXYoogKB15~fb7c>>4i+(p`Wb^LXC;6~l6E3Or&J zzymh|$T{+hxmH8m5$O=c1Py%AuhZITawhWg0*G5H>arqJR3fWMAOJ|CsEh_Tk|g0h zmi|msSymUCfVa@vWaCO(M$Vs15Xl`m5mq#K-5`aqbRgqJ+9;PMYytCDaT&bMDp3zc z*IG3)6tZ~-uEj4nL3c>>WGY%KQ12DY0jN{6Je>1ek#c~I8Uck;l){CdHb3&~6|2bf z1&x0*gSj-~^_ZEyU~ZUvpeJPUv4^v+qXSR~!$CM)Mvpb3BvWQiFF=}(`NnVB zQ|0KGa53T%e}MP0)DicqwnWz^zt*vZBT-k*vbbK0i%CfXduUCs@=U{u{-ga4sBns-#qMK&DB|3 z?G9c%w2oZYu`%Fb$+eVCLGW@cec#I8;?A$aBhUb&5D10g&7x~H4yPUDf_P;rPQ_ah ZDwlNu;_6YH0DDDja&13|GeAA){{?_I$n*dJ literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/changelog/index.doctree b/mddocs/doctrees/changelog/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..4b35b3059f3639bae80e04f1ce34d964c673b4e6 GIT binary patch literal 3923 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

op=imzjvcdU1c(05FzGep6TT`5iTd?&t+ywLfQe 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/clickhouse/execute.doctree b/mddocs/doctrees/connection/db_connection/clickhouse/execute.doctree new file mode 100644 index 0000000000000000000000000000000000000000..c150482563c321d8f45da611ef88bd90ae8ee211 GIT binary patch literal 47822 zcmeHwYmgk*bsk>3i(LYX2LXZ*A8A4oxqz^<04$00@)Z!f6hM#*3IJbHR3JsZHC`SV<+cX<0HYKO$SSQb}AD%hB^!QgWpdSyEz4lqk!JQ%=ei zONkvR-?@*z_x8-(o!JLLCSq|1Gu?gfIrp4%&pqedb5DO@;FmuC2OHRb>5jPW2enHD zw_L78WiM`}Tgy?YS@mj3>m#j$Kht_&Yb+gZxEJH7*(iCfbOW9!`9Y=J@M^7D+#IFn zlb{k8L;ZUK5h=mF?f~^l`3u=<08Hf)Z8lnwzb@> zs~_`j=>n+0p4seHl9sBhTT{-L;;4khj)fo=KM zq=Qn*w%yT)lVZIQRieeaTIrTbQ1e=+H>HDh7i@Ed1m6n@CSX z#H-#!3^{Nc6G^o`!RRnSQ(>YM)oNaeX68hBzNr0Dolgk73gAYIjb$k2O|-%IgEVW# zFwgMFw{r+P-pz3 zGaXqaM1uJAFdje0ANL;wrMB=9U342YAgGP#(Ty87Y4<1V*=NyFef>Uk;Pk0Td>Q$r0(xF3#9Y{a5 ztvMhy?j-c1>fre)H*xQ6U~fTI)_|jk1*gB(r%7hjhaNnXu-i;atqEw zcy1DcF*!GN&siV|pm!)1j#JaS=A5V@9#WJq#E(~uNAVj^2DLarn*w7Dv2_^ZaTgnI z-PdT8CRc($0;kz@)P&wr0#Vlmf@WWwG$Mn&>E4dqmR`;;*))tGGpe6JveABXOgO3N3D5$X|SXo2<=a{WDn{Jg_N5kMt-N!qYZeeJo zo8ts4*$_UVq83`7zJQaUd;tRTM>dttR(Y&st@JJ0;yo5It1X! zQXQ7XU-ZNS|GyY?{Xy?^^}n5tYShFU z)PYvOC{W&wP5;kyP+#@JMY6kabOHP$DE7qQcQM)FxH$HHlH%LI$2w@>B>7$ zV>*;!*o*Zg75@%xY<8kvOP0;toF?20UQ+Vs<`|jBu&{2SCzQB{x92dz@;_7w60hM_ zbiKEyYp3XQXC9u`|8%Z@t11sq(9_2lQp<@OG5ofybH;FZINIX)c^YZr2QdsvvP+5I zb+54yHL9H7b93jW@1H(#es0cryy-QToQr-?@`d3M!>Ej_WW3gz)p;0*P)po|J?vD1 z3$SNl@L`1*QDY}7bs<&RH%V2UlSXs9j+nC8} z8;nNs$1=M~M zow%a)9ruGeS)5^pcjZ-OtwMfVYOvjSrw{21El z_5#7^YKL8hJGwMkjmk|#8fMb1_`#xCtr-{+C)bMoh6%C{vPq0)>R@9*BV3;Sy~#TD zCzZV+>$MYENXCg^v?H+4j45`a>&h4rqe>ZJ2?`D%m{5sgJa;ije5{q4;IkWJ2eBSN z+0V_f2h{D!IZeRtUL{J|x8at?U{pDQOrj+JPC@GVK z593?T6c6o)!a`^#m_&1D{+5R1*H(q3AzXP3dlRnxrf>z>QhD^jiszCM;bOfHYOo)m zQYn-q;zCL_j*~wW3;9qsNch(@HWuoeP4{>0O(Vwt%n?J=j{CUB(*~5&ICF8r9|@Rb znB^bVAZ^%WGp|X5_26!Xe+OVIQD7s0t}C#1NAj}fNJ3Aw4~0Bl=|A28782>&^-Z3j zy@>?%_dk{y8o`%7&vm8v=+iM@XqjQQ*~_4@>x(Df5FF zX&*Ut{v@^~0ylP!pL51Q*@KQ6Yde)cL_Kz=^3Mr?{zNBt>%iPxZ}4W*8|@Ejv~T4= z+6g3_YQnQu+P@iuyv&}rcdgN$nslT6v5fYD&I`?2X$*TaM-et}EFL41YuqW3w@r80 z);F`ovru>Vdo<+kOn50r5mVH!%S62$uv9(!x}c_!{a@#R*l8okPM9%`w6kr9O6%m& z?@jC643d9}b#KQ+;}SLL#-*zjmrkB}q_f8P8j+wo8@`dFkjaK^U_-8F1jFSejnFUW zy0H&W5Sn23D&Mz&3}0o>+q>2%M@_m>?)b~UoHCt#o~U-arcG{Vl3i_9lAvDELX5Ff zixO!Bcx91Bk%@cy_gC8^6KfXmqngrmWDV;_kr^%j9x3%uK=DN4j zSFnz7Vsqq8m?O&)OM{jlOmFjM46OXS{NeB7D-%2+gB9AznB~ezZleEor zK}|=w!6mbFTCSd*A@53I!CmBQkn#CW;3S=50m4Z~vJhjr^(<&=F-5mdUvjIE%2@JN zT~L~7vLAjj^YhlM1v5Y2!t9~#BBorNuSBH_n#uF%B+P7<3y=>)AiIfjoixjHWJLKh z&Yju~wuX<4loxPSJyKw??=hC=bUl8F`_XRNgiv2h!V}JH1R9qbu zxx8aO-3Q@~ZFy3|m#US?xb9M7-`0A=i@nAZ_&R`X1zftEZmqbr#U|47Snv<~4z*S~ z>IaK{1%F8q)($0>k!Cna^Mq@LaX^;S#?TB~Jvl{9ouFUut_|K2z8x^21(d!@_cW0^ zrRK;unCoMlI^@$G|d~@wxz_zn`L9Fk*SeR>lDIb+5|Lv zgHO7<{ie2RJeljdr!|W3(J86xXsMCO`;M~C8D(yV`cupL&vw|8l6#O-Q}?s!J@vr- z6v}M4EIwawrd@1Gi7hN(0LmN3M)oMU_WC;(_ zgGR3jT+~AITt+v}Z!1%>oR~W`iW`)EUv3AYZEIng=MS#QHi+3*j~gM+=LW2W`l_!g zwrmYCEcNh3!i}vg>3(f0nd8)E)F*ReF`3u%cE~5t%}Sz)FQJ_tO|)-^gfBsP&4j;C zpNK5pmp456*#OH?gv>G-le`Ct8YiSie7QiN0O!8J5z|JGv z8~^Xs$d1VAd*HRo$f-zHH>0VyXR)FaXQz*!pLWh4zvupG9y28tt@TqALNaD6g z962zEnaE^DQTTQuQumnBZ8;j6x&c$J9Rumm?#$oPmaMyT-C9{A^#b9-xU=c79#gTS zcy)%NB8S9qM~+Y$g6~`vf>-GTSCcW1X>ZbT64LQjHgr3cv~U?UIVwhw55@1vgy;DE z=cmthjx;__jk`-loTH8@72D_L+Pa=ihqG%MnV-ycWFMgjSP}H)oB_FRp8_Q!_Po7o zjd;|g8}S~@h<9q{-1OP=&Z(L6XNZJa)NyW(=HNpQPSMO{J4f_;3fXSzH=V8MI2}Jq z1nExrf0Cn@N%$M*=4@z5v42DNhcwo_kOO6>pI{B)*I|m<(H=FaCD9yFB-GIQ^ctZB zCKFciyh%dfqH`+4MP1Zz`TVMI>7pd^wCYVs+=ubB81e0V(&U_)bd&SbnVe6ZJ@a6G zePP#>IwkQwfA*o76WLNkQ=qGef19Y*UBq9>5!K|+Zty3Ek>t^+c|+sVU*@{BlSqg% z;n6{q?da_i=qchz?6w<3(~$eo8X*U65^i~c2Ep>YktmItH#D4f4uD14Hbs8~>mt~B z%JnALr!lT?F;>`-&;*;BbQA1DLa-wwqcv30Io}bY(|$z~*)j64zrF4Xd0=b1l z*-tNO`9IbVRkI+={TNLFEp4tjMKm=6P?K&1cs=cwoyrl+R0XTvEqiCKd%J)MwLmyA zwb1WwS$;6RiT?v&<#d1CrzVQ~-NA&1;rE8)@ zH}=G2kVFL6r(_g-)q7#@?}uA#FYLWp3+Bl_2ut`B(3P&$qwiU}%`X+xFL*q(Id(u> zj^YxI&bkG*%XPrmk&;jIeX;`w*uRG3q_=hWei}*#I6>KY=u|Dajqi)m71}pDhx7Bb zMRiT=xK$!I`Ke>9?}O}fdgdLcW~Q;t_JD^oy5}1nQdilHGiRsZ6WsF{`e(ln9O%CD zW`^H7?Y)VW<|mY1e@gRZ1~d2PAoaFQwD3`y_K(rWyYO*2d^bKRSDtN`g|qn8wncUp z{mR*t*9gaQ8o@&cHjVJ0)Cfv5rPt*4#l);oJ+mkDoiX)Sk6{=dxBP_H4l&txr_;#vgw%nV5r9Hm2A zG~bh=}wMcArz#K8?&-T?hSNOoxVNTe%L@iwT>-8>sj>+5Za9R`Te0b z2~b2`*@^Y=Rz=yaht|WNUK9GhZH4qb(Jy^%r`HYgb~?b$h_4G%Z@Py1Rk}vCK{+S3 zcB%zAyI`*C#(GQ(qGdfE5@`i;wr9W9njBhLmfM$P&ctHYg00Clps*nwV70ikggU-m z)3u;TI)n&ABVhHb$mzO7ogNES-(u<9kZwV3vr=2fV}LnIcZzXU+`cH3v_Z@FcP|;}8xuw2TM#^1Ka&T(VtK4$eVdoABv&fRK zCY|hA=a#$fLP$h}^G404n!fvRM!sx_*+6)wIx-s_$vb>gTzfv(iK(a4jTd3YX=SyD z!vB<0n853nV18XxMlm(|1>Pvlz@;DSQkaTmFZ5KIN*>?E`A@25#r!m?iL{#h2b0N1 zPj6&Gxs7uf1@cg$NvG9FmzKeND`C#j-b&Dx!}tppj5V#ecISeb zVnroA;E9TMge}r@knd_9icSZ`0p;{)-!g*qOMal)A56C{;NTUb0V8udQcR*!5y^2$ z+zS5;V`rD2Mok-bLp@2e?t?^|iHwENi_Zw^YhsOr;Die5wV&!z9@0)bbW&xtHMZxuZ#+4;V}ULH*{wN*}0QMmVYqGeDv zF#L0!GdoZnE=JrOG88NXURh&KdhU3+45tBUlHwr;wJeVoj!Zgdy?O8mk4aFR#TQ(G_9vs~|*@$)wrS-cwUTb2JdwSjUqS za+%1H#|!8WIK*(RoW1M z1|I%xCmwcJ59to1A@LKr?tD(M#C8%OE?`nVwut!(F+pLpMsvOq=$2)w z4xWf*QC(1Sd#W!=y@Q@sS^`>K$m(8(#1=C8buE!R7Bz7KvF6lK=M_80OXB>6I3f)N zfLs`01zRQyLr$)tRjUhbjplhq3o3! zxq?W{rdI~I;_qxf{H4W8a`am8`7t!Mg$8JKe}n*Jt*+6eBeyM+KHu|!Bw z@a+=gP|(|t6ptJ^!golRQ4j}DdN?YiT`o1_B&xD%P<%DI^8njN!A{6QsqA0dNOHb{ z#*c&Qw#@)_bf*27jIy=OEdt^uKRzR6fC?~Z76FQwm7*$5KYfm0chgM{3M7|2RB~qX z8Ku48E=DEirkf;HtAJVR+Z&G?fHz@OfD4G%eqTcYBijKg-t(5x-iVUz8nrZV}B$1j882&RIRF~Mt zOSwj-xe)#ay66`CAb5QTKVHn>y>92r7j?33H$-15oZMBl9)39+e#ekS_!aaW{xLob zBi+Yskxw_)+v8+kqbA3~|A>FE9`_=U-?sv>3M7Z3j$8?4hU&!_zBY~@T0!W}g)E9-eZ#Cz6A5&KF z;p-rP)cEPf_e!b7Hzldp*`a(FU9>5N{<(e74#lQ^x^jEV3|0zlB)3{&bYpioh-Uf* z6FYyV7eD53R>$z}PM$gEMd-MPInS|Tk@KGB+rw7sSm)bWG{vkorRmO0n(owiP_{#{ zNt1xmi|%OHq1dE}LqWtKFFkZaUsczhWBZ&jUbyHM-J`fA^9j1%SCjRFIV4S$po@6# zVxS{Bf}=K%&2kC#8NAC#AHwU>Uh8Spne%oYF?{7%#{` z=C6z$Tnte3=%S050V)VMuF412%2O9c-sLqSL=$kbo7y%|jgSHiC`61y-C4-^)d8j? z>BXKUiB>L(E|=D2-zjf9v)9Um9~amm+@k_4?`O5@xYO>GJ*x7Lqx3b&3{)Jcuotc{ zGw1LKtJ%!oN)of+5RxcM7a045V?_IB9v#qU^ASO2_976vick_of50miDw=mub2nqv z6~{wy@n7`I#qcxKg1t3IBXanUbVI@FMT;1p^%;&IrrPHL%oaW{m6Y(bCGQdK5y%U%@=qX0~+blxCyI>KHfdhBIFiwLRp1bCLU4hB2NFink_&U&k=gA=m{hjvD@F z`pLHI^xw5;obTqN_GK-SUq|KTamZ6+35Mb}aZ~=@g(t+WRGdAuqZY|)@;T~>6)N?T z1nxMC28U14z{wR90SZ~gZl$h%_yPnWeNPtHS|=C(?3%gw8Ky0N_3phE2GrLWUW$m~ ze9YuoPuKp(G&}os?T3cVMy63>Brb!6*oDH{rSi8+P z^|dQX&t7jO@mN3ZyrN!s9rfCyJD;<@J3H z`kJ{G98-OSe*>v1`=QoL~}qtMDbP&E5NzcdT~ zhFaiFpkTc=EcMcavs2!Ji+Z;rf1=IFMm=)`Iz(N=!_Jw8TS8%6-;GD`-ecBqZZ5N^ zuq!U#x}kaT>ji>N7adh?yLeXGo1(r$X%`k zSspW`t@0T}Vy;NO4}tFktV=6&dNYD-f)~PRqOx9rf(anicc4H@utIjf&6m*v1yb$V z0tMkW$egf72*PjTk?>yN9)63yy@S5JoPGN?zFiK#!#-bOpWkJlf6hL?$3FjpJ_)lf zp1hT{GVbw=i~(rGf@hl)9gWz<3|{*2*QA4=XCwEa ze;gm{d`(%B@vBD0g0_7$qFot zWkJqm&F8y#f<_E8FgsGfkD!wgX}&7na;{*t-6%o$U8^8;GPhqZnt+ZM{Ep(w|JIel zLtT^bW%j#TD)fYPmRzpg%IhEVJi$6bS%(t^U$RQV7&}oAj$&KD&{vob>^Q;avex0h zp|7^%1nSv!J5KN?89JSmZ%(YGk)6j2=62h0g8Vb4GjM}(oWPV0b)cZ9V$9!PixDU_ zoOcMi9Vz(346MdS)6UST7oMPfBJ)5&I8E2OoFx##L5?R)PAJTicsg6H)sX?Qn5hd& zXANCt+D_fkBW*3t5J*)3CqdwVGX&wII`Au#JI@BHHn*=J5?B88!2W(X%T5mnUef7- zy=dIy=>gG-P7g@AfH3GqoziDkcY45_`^%gk=;ffmYX%=a4?($c(A(y#ukuWQ;1Qh( z(0H^?2?}>FGZxb<7YPayE#$;}`gPzQwP)DiocD?k1Mm!ivXKqFx-0>6${0rh9$ihI zfZ1=J3i)gS#Z+OOEPEK>W@QlCL9O#~fKpC*_7qaMkZWYFrPh!F!RtGuP|V=Hj^~V# zIgrac6n0DQsxn?akP)DhW!hH;fud#je(H5CGYCFxMbnx=@En>#vGlSH_^Wm(wrxNT zW%&$(Pgz0ew(Qzw5K#0$&;1v{1D5;W2ZcUoMc+C<1(aSg2)<&6V%zlXV=RAahq5AW z=Q9ZY(h8$jcCR3VV0nIbcg9TXLS$FE2{)TS_XPW`{u4#g%+ z0!lBc<7PV)n=o-G9Wn^$h8~n(dyaKY?H9{N*ZXP#xhsdHxnk=Q-n&?-frjtG1X{;1 zE03?T)c;=XCLWDFi(#kTL)+cb|IeYB7PFP>B%F$WAT$M}`v2qf|9AB)O%&;)^nY*7 z(k<-PGEcgAMBry6U0m_Rdof$DI37wX1pV?c^r;0u0HBDxUiFs6q~Wkp)Bub z|Fso_ZkE1!es&)e8rp779T&D26NJ03moa3U`>L&BGF$(HXrj5Ua_NEV`Z1wvmvvoxO}`fl zX|Io~RrVKioHW&@KKG>i=&qOhXjl!D{PUc^&2Sx8o^CH_wDJxV)qb^Ks)a953!bm9 z4eKZQ`q^;wk)M zX@$=6^@XTVzP=Kb_44cg1W5Jm*GshE*9&BO&DWQTZ%zFAzXI;uumA7#jrsNL+gkYb zR@TaVeVI0D9S$oy)kD7i|H+2)qii_r+e(MCh>Xd0%uxTJPA@l%wU-}hmn?5vz^e`v zRmRc-9(%zV@?OVP-YZHcjF&1w>4G2OWhgEGj3;M?-XNDKt2Ib>s~_VWfQQ2kIjlBw zW`1@Mn&QVZdDo*!xd@3ln-Ulr4fIc{h zvxJMniBYpr!gwA`_sCMdpkWbpEEgLnWQk73(t!l8Q>e5~Z%9YvGbrACdP9?x!Nzp7 z)NBB~q$v8DNw>)>l>qgxX40)OF9{L1(p^|(n@LcK3rJ(4O6O%%_kGyKk+e9grI`*! z&7|H;S~LE`GwJZ6SMwTf5;f4%FmjJQaHU$ukEuJ3BHg`!Zm3|dcJtYEJF4j2Nby7{6_t6~@2byjk=bji^!d@e13D*8m3yRn*K+cW}yUy$rBkTImhBJGm~x z;}FMml=rJ}kgcybZ{!-MJ829t2<3xBR5Tq`lPYd)vFR>)pm-U-H+i*IdUXROjvD16 zKlTSO^rn%SbP%OT7Eu2m6bZ^wAW*G;)NLkFQC&(mVOrqz3#iLl%tR7|WfxGH&ui4& z3U#=HU17pb+*gLTgXLE>+>6=I(;L%WG~XaO zwM7UbtKQUlVCOA3BIebV5~0Ctqbu}k05&z!YyBjt$CDEixEvSwi}DIlV-YiIFFnZ+ zz$7Xr9!eNt2DXqu;MK?9eB`a5=V)W0l0)qjCFySZF z3V;oxj5uadIq_Skx288hCacA4a%NgkyiMhDx-(v?CGMr7A1wM6{6T)VR3Z}nZE+Q^ zWGFt~WbfX2Hocnt=0A&-sN@RL-ca&g-~-0<`35R85~B5mW*xO~MuA-tB%nW61D7E^ zQ3+>gAp=4$>?TD4Et`8RsFLjBy_9@1Zec13B2%4*8jq7DVBmXR0vLNsc zMU*}#y$jAio9?2XiQ}QVDa=O{8J<*zJ&Zk?sHo^3TB%+!RYC%En5*M$##LFlk zH@kooBrAcsMISi+O4{7t<8O=8-Nr3iQRp5tR(4&lNj(`R1w~FJskj(Hm z>ERdYqfEz;T>2QIuD0N#nGO>;R%D1)CI;|X2JbS1b(z7r96pVQFNe>tPwMe9>+Le@ z35OYPL|d$l=NOXshhexMFH2<@hPTnTBZTnbP55x>+pYA$@S^v>h8GD#dWCHG0)3-b zM{->09Z(Ec_Rgd5tAszjW`|X-3tvQ5H#@H%QjwML*O7`7(lI5rxgAns1&a}c6#8kV z+jZIJ3@Qcu#YQyW_!Uxd@;T0S{R)2$!hpF_osTNnbYhS0$z9Bz9yWR$mQY!v-A4ad ztr|%Sx@T{`FL4J(0~BxxC-5=LutJxjN`;Fxmf)ci;`&Fx&hQNQb$V0$RO>7izH%E- zPY}!)^N0YCp}0R}2Nlq&@^WS|_yeNCs!CTsI@PShUW2l7sP0;TF*tm3I3z=p&H+;n z4`c`5-?m)Kmp|~l+4kiR{Bl9_^L`uS(4$Cp*NEPi@JY0=Wr#C*bJGD~Ic$_KOxSX2 z=H4^3d}mf1=^3wedWzyRFA^T=O~>+u#R$&)2JXnC4Q-Tr%W~s~feZr-3QW;#V!1Y; znQWD#)|dOH))$te7R||aYK8w6?K;rvL5)`O9k7B{c%!nH?#}E0DR(4}RvKpYes4Ru zgaQ<#`=#(5X!ipS$QL?fOI%rs6WI41U~Thcxkrw7$S*^cHtFd`j{!*!NMw3dHh8ws dTou$xm1db1wnbWF5axhECTtgy`R07#{{oNx{jvZ6 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/clickhouse/index.doctree b/mddocs/doctrees/connection/db_connection/clickhouse/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..7ad5ac4ae3f4dac68746892a4b02fee6f93c7c44 GIT binary patch literal 5083 zcmc&&TW=gm6}Dr~*fa4hj*|tEtVR*Ln+16$4y-^H0>MUzmBn~rR}n8!tERhVs>Xdw zS9LNL8<8v|#G)h~Qt$*4LI{aR{s0osD@FW<{Uv;-`ZD9m1+x!eY2@kCr%s*gcg~rI zjoDty8t%>&a-;RYLMGP+ghy1}qry-di_s?3U;7)t%fOggI|_@VBp7HsX3JB8j;U zyBTMJ794syK|Jo==Hpsnr<`dstur~uHR4KfT#|u8%*=`(GNnMo_eRX3;@fJFr_+Z4^N-*K$Fsyj zZN-jR$}?%rdtWF(*0g;V2QpxVHfHgAR(wt6W<|3RU2DZ<)2y;=?K_#$UYaFgGWxDH z%b|?9Jve8YDMMJG%yvFt;tGrsZz5tHe6Hd1ReautQB7#>bp2MP>wlREHWyv7C2oqf zm)^6(s;h{vEqdaH*gH4}{w34$>H-o=`2g9}EYfl|MK+QDo*zp8NF=%9`$aOn(w+wF zkpa1|KH@a|0d8&3t>XLm^vnlH>4^6f;?A<3j?$j9yGN5b7 zx(q0J`8L=;D%iz`uzT5QxaucSn&fftf{J1Rsg?LTc28`#7*<=jAvf&ClnHL zF_W%*uJ_H}Sx%-IfaPv3m83d&V*93hmSa;MoZ)6o#(tO+wuZ&+;^*Q4H>(kw1U%Kk z9xS5DS?X{XzOO@%S{jAPz@C_jVE`CAP_9yXY z@fYz|E*>2$icd}Z5WZs+TxG?tPQ>FA@#IAO`d~pkJrO4W!QY6#%?4u~u6Q;d4Ds9l zh4cGoD=2-Ox^iG-ulNTm4c+`WOY#A79IaZ*c=Rf3|2b>zWmx<7FJvvRf#JG1q;|Q7 z0F=$;6!<#)pQE7hkQP6|?Ij);IE5mqris8lHDBhF6sYP=iQeGYa~<%370<;jW!`ux z3mO0z4#AKQrixbTRSl``q2fccqcaw(&?O2r(^1A%F$pwuqFGp!cgf?bJtqqm&4!<6 z)Mvf2ANS2#MRA?DZ&nr86c7xI7wQ3Um?g31z+%(9%`lXy8k9{l$#t4*+ZVU{rZwU* ze()3H@!eA23Vpgs?c(!DixI7 z8jQgL6uY{E(DjndtGczhOy6*c;u(QBVnfmh9nPb_){ki77pUHlIq?XI7|KX$`{S*5 z%tp%7DT0tOYeX3^=uV|!zElmqAggYAtVI%clWc^Fx zgg-a3p+6^{C{yuoSK?8;j8gW9XI;tl(A@z*=+Qof%UYTZP(|3k?7^zJikytRx;SeH z^i)b2a$>gBaje|BV`M|p}XXkT?BO$h^-e?XD(5IIod!ZrZ~PZt#!DI6~#zP$&p3MU{(rH z!N=65IM=x2k`JyUk4H>~9#(6@U8nVha-c@8hQk8WPRtUI?^z5wX0<>QlrJ(7YpFzS zXs_5F+#+^iezhhv7dv82ne9`riaqWDDr0VE-BEPQuzpXQ5^aUlS?*YIiz%~0FH72U zai~m-2t|Vi+w-SNyh)4KA;$5~#7DVVpA9I9GV8uXT2N*~d_keQt@sLCW+??9Ha8?d zG)TfaM;zTqz>ASpM)X24;#=| zR^Bh^&tTnn6S#P=s2^m$;%)xdPJ6`Py`c2R9wOozEQOzh9|npYg+P*Y$#$>AE}qXEdv=x7hNsW(GmmU#?cIcB|r>7IO^0 zV!742ZnRkS!%J7n$vAu!Bg=IsWF-y%mIK{uu^^km?XbY9cuY3}lL1dL5$ja1R2HvR zo~?@ERjXsL-HI8sol;~@Y(?fI2xd(N0zt530+61`AuM6X1l}s#Z~gyMB9P-HJqSFj z9tMyr$#KLoe8`MXG#uUc@etp~I(j?)cG3vD*^j#3*Z~&=%!uv;k$IMngq{`gXCFyG z5id3Lj$s*kj152jLS!!ZA)g5J7A+f@kMnVw$&vnk~aw&o< znN{B&{8jlUq3&%~^`R5GR}I?Ts^CeL%%a+GI~~>_E3F#! zn(`}KqMF080{KLOx(PoXsE}ullO4^2^fCAWCvQY|RZ^u9jc&4CayWb@|N4uO@IWBr z^_Yw`b8)SwND|SLJYEI(MhtG=1s6tv=X&hQ%rS}EYw`g<_bT&zP_C`G?XDhJb<44W zy%@8tes43xc8-RasUS8X7&uiA9ft)q#{x?;cOwynH=;K+XhvIk%^NGJ`e;7$?lDip z9}(g&!y7JWcbZMBVd;*urx^?u?=fRpTUx4I(_Bw8t(M6=0e7PjgaQhx7YLZe%Hm~h z>DkJn6wh4d#e1U(QJ*cq96-1>2;t$-UC<6gmXvNZU|x<@PYzgF%6TA6Chac`FYVuj zTOjqDggX9?kaQ_yks`@h(B`M5LQ27ZRzBb#972>*RFm+Kddt(hrnE3T2HuD!0;0K2ROVQEHZxTmy z;=8Z`_1noDk+xL1TG`dg8p5s0Zmgr30DqPU@Q_iN2>L|2m^M(@@JW7@Wx>@u2+da#3HFMUo1UReN9X}Ik;+CT(ubpi%2MX-L=27fhTH zm-m}93u509FJ&Za6W<`em6TgAyh9Q1` zaZ-|`%=fv%eEqE+Qm{Wu&`UNU0p@S*TZ%gUMZk^tFp-2?9CWO{@J%;Qe(5e0-IMmB!f2F`4^BAA*G86+kt=cZu zRC#{37ZNe=kx5$}Q3=ShB>PWkZmQaB@tGXvig>sBYS{}L&>I^BMo?eIfr#dDW7Yk1>mx9HCeQ)yo$Y0uJ(;M zx9HW#g$!`L=cHGQU2nq)QFZjB-21%E2yY*Wa0+r}gZ*QjBGt7=EGs~wH zy;Jp#e|yt}u8FfA4+=u!QgW@hjvwnyNc*j3SWR&f8EB^K2Y#%33k!52z&b+GKF-cG za@)c>7Zxg|4Hh8t(xFNY)={iqD}apuhQUV6Axb{Q^_%+_rRKAbrwPyss}0l!d-jSSTO)7X$oc`y%Whv@Jd! z^HQ1b0OGkV{(K*5sBIx{|PwuvzWn@;=#&Hs-xx(AvE4Hpyh~5jFn|JpD6P+5A zF8&Q^W1KEt&Wk!L0Y7|qOTgHenpO)3_s9WBIm)T~Dw9ylsZh(<5z9>!9muy0tQLQ- zU{gx9`19{xwfK<{m)h5_2%H(|4-16!_hkH#!m@o6yF7d*Eg)Sn*>hv#E9r=@E2!7OIQ1#0E6?}8_V8uY&t!c>E{H}H~b&j|^I+H+CGhI5}E z%h^;mhMxnCq89^35V&O-A7=r%0zfW51IU*{ieWj$y#vDXxpnG4L63ljW0_9iOU*p+ zJX6W(kt5{LqV_zQju4^vaxOG#-|%;+#_XvPQ9_-j8*Q|aA<@?&?0n`6fL!Tm1ic)r@=ZPkC{a!16h$jpQP@hvpEd3(Bt@ zq)=H^G-)BQFJTRt?e~Ay157y*m|mw8$Un@8`jw*+1=?>P2TfNoa8OZ| zcBFZle968tJZM>f_M%Vvf*ui$Tqha)9k>C>L;m;WGR5rfedQE`;%1-nDmu@I>#v&M zFj-qKy}W*7ZBtuWU%j!ps%_qQ@s(BW&YITVi(gk3w9Pw0Tj?b5*Gi>ya*;GXo#c#q zqrrTU6C_z&1FK-oWcH2KS5{XxFKf5fHdfa+bMv?N1Ym9zDZyU=N@BHxMtlll9uyVX z{|Fh{|AY@N<0Jc@>DP$=h7c zZpxF)>^_YZhp$8jQG(K#a!mBcK znCJvo#3MH!p+jiJRFw7pCzR7babQqq*nyFBk#~I5Xd5O<$8m8xb`@vD=z~NHuwNB> zelGaGFc1MH)BXxabpgISzL@WeF;Jd5Ce z{m=-0@XS5p@}+!SK5W0p_X)LJtezlSh}DaN&zps62Q-_k=B>fvq~7NaE$5O#o>5MM z<}6B2U&(m-u(COV;S3w}b{Db$*(#p#M}>(K0R<+X3*=f+aO#*XG|y*pech3Hhsmw& zz`Fq*=S*#gr{%_h2ijF%(~Gw?GcaUbWijnJd} z1-0ZJ!Wy5JPek9r8b67=gPK=xQRX!DIf;ueO&mX>9E;|G@mFAnLR`*+bETE#Jci~`!NJ39a|6N$RQ_d+d7kUl zOjLv%LY2 z98qhVkp(tz+(4D>1a408$2`_>Jp=cns1pXu;JgTY7>_F)#1YGo3So}(SyA=O9`O@& z17yYs+ud5Kl3=WOK+Tn2M|Wt#Iq^g&J4ru#)b&|txHZ%S0d)$N=lZOsjuct~o4rT3 zQiRIR(Rl);9&|$ll{yTZkfs8&n+7qs_|*}1hj%jxSgmOxL!!$@kJC3Sf&`s9KOq}& z=m3#V;fh)M^Y(Fmn(Pgl(`i8w9jlFAmrtDf2%qh;ZstUY7&H=fq3yt(OoKlmw>msV z=b0#HR$Q+Ii#kV;0s%nQ5Jf;d$Y@atW!>_JuRH^O&U(#8VS0L!P5-oz-UQ|gO~iFPl%b$^aVjnJ=5+B&A$fE2 ztU2xTGr3o^qlj8}@vh!kpH^w6@9BmZ-G-x`1GyUByUI zA5x4I^)SRp(J{IGQ~LNZeGqyg=&}EmzWqR8JEN*_%x83=#rrH68b2XVCE;S}sZYqf z#8aqzyY4!vRfvaY3lCH1$=r;Sa#bKWv7#AAGg<9B)7Pn%XY?RnVp8Km8Mo zI1EO&AVv(T&k4=Z`B)kdBx&TtYd6-{Zmqpc8*_Yv7#l@$opThE+kZf)9B9(G7S`=Q z#2Z<7OYTTzCr>{|%>3&kGxM(wV;l<9UWAa4eB`;`ZsL${lPutnERc{TyZ^b5 zy0@yUZddm_B(w4Nsom36_nvdl>z;G&x%a+p;5}z9T6Gcq=Wli!LuH4 z`$MIAu{Ceky!MZ@cfGy+=61#(ZWhkC^;WZJxBZLoMzLI}mYR00J&lK(=zXtJb+e*_ z9d2vJ^(r2Itk-fSd$v-m(AS{9@__%|Y}R^{+iEoGO)poj*H61|vMTe1IeY(ptL%9V zcmKphWqvN}mMeC(ey`A+vpv;Cy=HsW zY|}23ys`~ESx0tlVo!GO4cQwfSo4WWtz@6gmc9AvNV_~Z-7XJf!1k<-0b;@^9PI7g6rZ(d>^P4=J~gwQmdi< zm?;!bg9_}OHHE6zE^qVKw3?N+e`8+^YWpL_LakIO6*xBjs@vM--EPYt^a^tXwq4%t zuai=?;g+WBX;wLW)i+h1F))a>?MtNnG20@wm%E^f^f%2#4g<*UFk3;#y(?^^u3 z9)ns3%o(mXOI&-Dgm$Z(EpI9BD{qK}v#lUO(R(B1iSnNE-n&-g{Wbn@E>NKIn$`@Y zX&o0!T|~A){&U4@rFgnrZ@Kn3iRgYVQHg^Zz@RFnw!b;jj^TO`@COKf`2hY+_;*6M z=j{m>5>RMPc=L@3Mu7=Zf{9|iR>|@F8!L%A3jKL*v)*N4Vrpcd(u8jHpjop+_?Q{ zn^`3c_7orNRC(_d$Ff@S>}H{=Vd-z^09Ky%ha`Nhvy$FfvTPBFKcfBUmYi=QL&DNV%rB-a(WXTIv3u;%es3*{WYy>93D%EY*s?|N&hgEF39=P>T z%WhU|cg(6*PTOGBYmObA%IC9!PZ#+c8aC9pHkUgKkoq8Q?sE`a?2CM^DG!w2!HjS` zlfEyRNg01F!^mIVSbzm+ER2JuZCkesl6P;5RchUMKuy|yM$-(Vp0wG7QL4`uDz$d` zUH%{_<1I8WTdpA-{PpuO}&(}*WsLzx?gg=;N#&?>5 zgxT<~OC=4=47>%Vh=gq&gzpgu+_XO;`RiJmus@>HvHVs^dw+98jyP{%!YVapg4JwT zenzrM3Z2&d$Mo*Qx|AtD;jeK$*s(!;!tl*@^5~<${%2G1sQhCg(*KH1dg?k%`g4l( zkC%VK9||xh!RRF2(IwL&jWl7(2U713NloJWbdFHh`A8^c$1Drkbcg9~l~wC4h7-xN~2CHkfY}x1?Sj{8 z!r>HrFuoJa^v*RPO)^)a@{g(4v4R%NAZ}s;ynd!tt=e9$T7hGu*|NZu8Kx&WcoFm3 zWB=mkfBexGe)$PZhQ)fdHD7au+oGh{*-zk@KQ!$Rir1A$IkyDZ2xmnp{`!ty(Sv}a z^>8TCLs%}O6o7}qV5Vh1TwUjtfY3z(oSO&>KE+Uz8w66V8Ruqv4LH9gAlR8Q;;2q? z<)~pYxeB7+@JoyCmr_EoZm&ep60ojvFH=sC15sDFRcdC`#;|Wcl58z6E*o*mnQx? zKD)I2RlCUPKqiUswGk24BE4Rvcr+x%$sK8ee9>y7Ag`v!@q&B> z7i0qzCClpt6v*An=AK=e#2 z-SqyQT}BX*HfnODOOr(Drp5=lG>(CnB;C6s(ybe_IHBz7cw`byc3gLwjJGi;gN(nI z9>>f0)t!br7M2q8rvxmB`Ad>VtV#NZBGCJj^!KBeekA=FwChRIO@q>feLIAS2%A>e z>HT+i8AL?*sL8v!G)WY8YW#y;8pps(687=u-|r(%#+ss+&s&*FD8-DJW@bTK4%o$m z5Ea(^2@sJ2x|Vv<K% zaZDHA>;MbC#NZk6(*03tlEi|ooCT%XoLem0^M%gn`=T|;WL{0r;>o-*pYP~&+8+*n z(+EBs>BTrO7Df`>v^sJbM*nZU5saE75$x^4p62uQ1}%yqy<=ru+qU?EhFZSL(#q5^ z{*GLuQELRWLZkhq5{y~VZC3T9b#!ZfmsocW^uT}H`NoJTQU`;ulRiCL%p_mkxr-)Nvi>jMiBPIx23STOkJWp$7KIIYi@wFA4f>S3K9HN5lV! zkr)({aw?AE|H0inkXZY3?^|hrJSftJnP2Hzd|h3+NJT*3}4V44^rU;S-+sY zX35u7#ynJWaGy-Uiv$DZw+*TIp(AGt^Np&?yQ?46?DY<#Bt~b+2Qb#R1}#{U{YoIA zjM&8|LhP%K zjO^~X;Rw1?17e7KWuxbCcE>&doon_H#w?oU=HuX zXrWdH66eCmOzQRvMg>Kl*3ucMkuw#qY{jr4vui&Cm1x2KT|Sm>!uhU}kHEryri+Dj zp2G{y$LQnd@Nv%hI6kRtjoDJ?7w}hy{oEI_Sjs>3hivKIDPL0mO_a8gyA*XC$!9Y- zj+>9$#riyo2;oIgXe^_;j^C9v{_j9t&mfrHH*zEYB51+f$UDp^U3tQ;!hTrwT6F>L z#Y3%16Nx2RF(KkE^GgcZ7Xr{}d`&|3D?tl}>}x_k@B5F|Tn|YCt3GQrP-6|h3fZvH z-J?h;RBHGt4Iyd^StZHj>)At-w)?k-ufJvh1vNvW$Eh2HPs4bQ2Zr zR$-o1S^*fV?<9yI1)Flz1ZANn>jd1k)3Szh+P@?QO)cR1PB6rd@(Yv?u+d04ScAW` zmIG^h;XRcP5G;@o^=?EUtt$f(R~YCh5!;bKX$``T{*f)M2)mS+Mv0+xU&fAlLAnfy z?$*RL6pmi$G8Gyp6+#InSD|sKLdu4S_Yzbs8jwcym{h}HTC95u3l{3rQJ@UVLQ)!( z_VDn9v67NdTRYy=xVS&UMJ*kbqE_f&q_x>tk-!o>Ca~)SmWoSJRLoo=V{&r zxFE2YFJQwBSxz!-REEBg9XaAQ>|&)*g{iNw2TfQ#{9~JTU)rtJf7$42r-M zGMg*v#pEyovC8&FTutU)8?ddzKwd*h@-94(HtJhAi8(Y4JQuWJ)4&7J+l%}GX5+NHB`-ou+;rp*VkV`nJJ{AoiI-1P zr+VMQR$b(;#YWF!N5>h!+(b`uZUsB?s7zaX$G=p0l-lZ*Fs9f@%32m`*ipnH1-2$E;f<$Q(;I?YDw=t(~{tibWJrl(Q+^RFmF6 z3#b>2Fc=5#R7Yl&C0U1WifMlx=_G7m{Z(fmx7rp%BJf{v0uy*$6r&!Qiu9<_pYTS1 z6oB(Tn6OokC8tyE)e{+!EcOr4{yvrhrg=1)-tKdSvW*QztC&b`;7l%+*oIA2K=1GC z9hBrkKTQ?e{xzT@=Y5!M8Gk)wc^sh?L)hO)__Hlm?C;R#urQncCCVn}pXnD#h+#&E z2o;r6H`D$t7F5W9AiGih(A! z_7bvKJVaO?-p%E&6T92|y**0^vdw%qTzQ>8G>eTJj0B9x{z%TN7jxwKxoziq44wV_ z7&hXu2kK2yxi1o1CIWhZN?)K~P~@NqHB!73iD;HWZg5p;>MOXz{hGrE53zL(^2Md@ zNpUv5qu9UK$o`0>p*5}-hnZLn;%3Fr7;!g0Y&R*93o)q7R_u~SU;my1r4ski#Y@(n z?2Xwy`>o@4ql%RWZ9Oe3W?k#~$KI{~9G!`@<;P{{J)=tP{mYV&mx7S|f|7#Q7^NWN z+>Yn|0M2Fr;X=j=VqL~AN$1r;2hMHub?;r5fb7^iD@mx$aO&NTpk;eLuV!~iYgsph z<=W1NTEEi)4sj+pD#{c>cVL1|JdCqiuO06M+zQWFI=TF{d=v2op&ncg{6X<2>?6|`=IoKA@T9Bk zdaY1J$OHE8Hd`~zith9IsscbvS?UKRpic(frr@pf1ih^c0Bo)h%1ap%YvCK}F6F5~ zBsjje-m-8o1dmY*wFU0{kK}`b2`U-ncJ~%}m;)R|WHSp`oh;kgdUKASY5;QEt~cR6 zR2`=T$v9t1cgLO5OpzV$ace#|Hex?)*C>@x@hnlCNIT?Vp=_2dz*4x2t?~Q>6z0fg zt=+r1_2Fq)3wwoHRa7!k5(2fo*uIBVtm+|bg3afz9zkGgTds}t^sKg(OUx2h$)?@& zNL$F{jo_tB&8oTP_rJ>s!^|Qj^L8W*Ow;|Jj7VL1t6Z4%A6zEEkXHNRPPx+%dp3fY zNq4#L7T2FTp5(SgN;E5=qM{?B7@=ttC5V)EQ~W1)Ixdnl=M|T z)sIx5^H`iu5}vSz2YuAWh$D7N?YmZ~Ax!%>_NAm6mpa4;_76c(J-l)KrAD=u@^E>g z>T3Z1Ftu2aJNgIocEGY}KNN;`;I5`Ul;3KWMj|*I{H7uF1(}nzW5MG?qH3#Y&*i9$jFmY= ztG7hG89NV#O}$(RCPzh8*@&#pf+r7EaDK0YNEzo3wcq^fRotgVSuvm}MeF>j-gKWz z`6BGEmU_wb4zL#5G<1|CQ`?i`JgL`X>p7!)B!KrK)|(BO zz@*;zTcRo;txx9zHOI~dWQdCxGtLe`a4y3~D4!9wL?V}|DC*eMD@(qH#cU&=nF`g{ z!NHSj1sD0-Sb7#o$y}pVD|)St6^T_raVfqj;#uW2az!->_{!PDnE;a zLg)xHmWkDlF=I(t@(kb78-<=VqMw37&H{P}DxDq z)Gw=zMxYTf++d&DwuH{%XO|h0qMY07P0Ek>S&tpQ!dT*$>d?YoOX>OslF?P1| zQZIYQ__^$iU+18}56O+cm8A0vM_EFdZGoY(WKlJZ2o7XJEgC{P91GoCu(0oHjLOz{ zz+$m22*`i4VviBLTX?u$L3ycG5m#~DmhB4CstoZ;L7ZNRu_^AK>XrM>32MQPKXy*y ztLgX$5*VCfD4Wn4iQ=a7l%@-(=r}wf{j?0Dr|$W-;{aLIJwtuj!|_dsvw(|SHZ#D9=2^-8CoF0TT}PnefO9}hxgLCy_$^e z$|k`r7u+%xpW{$YyWzP2v=Bh1QDvr%on-wccY$mY_0SD*y(>0DX0*lFy**P>bbAvFl|T$Ylp|OU(NeUjxmwqL9L!jc8wt5Y0#d#?vpbF+5RP)*7iflw0TT!Vl# z2&IgRTY@>pcQ}54a2$g$9di)Q5{LybiIzgvGA#y>HjE)~paF@nTCT8!sgdFN)!|_I z2PY=Tn)V!D@(AU`{dFQQ>r@!SQD4jI!JO0dCo2lp|E|@?`Gx_wfVH~(x=H>#j#b9y z0`j#R#6$V_R=gpKU)|uP&9%DxO}uR4gBI%ftj>0Dq7LF;$jg^wjX_vjdP(W6A(ui7g(o@SxqTSfvI?Z7{A zj;C3485jabUv~JY+>S=%2a1&zX+7Z*e!pOK z*OivlXoS$+vxGqpk>F{UUaC4-BJBReGP4MXb|cIf%pbm2J;kuE6jSKDxoWw| z_>W@5iD7tXij^_=kD-MYgUgu_G5Al4si12NzLKkzPe)j)X0#rn-e;+k6jASU5ny5Y zNf-5ggy`Ex)ceb5*HhF>tWOp7=9eYv4Y_nY9wlCN4|$^;RSK3A@_vp+%0WCuUzauH z{SqMb9P&!E5b_FSx0yVCZ7ozPopfNr)ZlBOsYBkc0CyhpewDtlke7YC5JFyDa{VF+ zc{NOzG2{(FS>}*;-FmJzQp!?=yqhA8!q(8}4iP+)0>ntsIJS5RyXP`-~uTFLC8U z-X17q8__qarGOHSQsP5i*9^rJrSveCx0|8##qDU+@-8EcwCrBukeAaaJlX50bTN>L z+TCl&`%xotjKcJ@XqqzQ{m*77CSej#(uBOfV}@c9CJrSg>*YnQ3~cMpK|9P&!E5b_FS(}uiK>0~exrbdq; z?;XIMhrD;vHx}};Zx=ww>$v=Ou3V*7@p9Os=-G6L(ef<#zG0~reB+T`ia>ZQe(g62 z;4;U=1yV81@}-K2E0IRw>DK5k5xkyb;^_dM|DP`SW#A-6#)QY)liuZ1R?=hT_q-(_ z%%X#4MPuj9)T=RQR_EtL7JK3=dY?3+X$+cuG));af4~gInV|H$PTT{9e$9x!QEvs5G(qzh%uq~mP7h=GZ)PZcaXT6`|Gg1LT6QmS(9CHR zb{BM1VuR)s+TCl=JhU-9bM!C?tVPq5LGyMq6q7IsC~1P`Yt2wh!o;D(1kLn7pIMil zV=oKViN!o}6du+I>0qf94w>>J~$4wKm0Gtjt9~)r@mGiS8t6&w` zp{>@5`|hTOfnVOlqkHe3pr6@o=(~c0dM;oLl?_{^7p6Di4(c8GwG{db`Ra?1(~?^q15bO8qodG@I*{X3i|N;OFSX z4n@2L-m(FW5ool>!?+E%}{s))1G=kB*MA1B?$Ix#Q>*`$iW;3v~ds=j~Dj zN)SBRS)eNv0|lgG`bTrP+8i9nl`1$;$IHDxFB%h z^ajP=338iK9O}mQzRoQcB0*=kXuMgbbj^FZj3w*_iQU=~Eu?D%vR9jeQLREj8u2Ab z*E|c{dAjDK^o^x!*tZKHT~lCpY3ArWsXES%$}CYrC3KGQ{Y%toHiYX^BxkOPPtHW( zEOSogHz5?d=a?!d^My#Gu;*rs?FgPp4Z^A<_EP_P0MCE!g3rlB_!pCk*%*0+XJ8_~ zs)_5bgV8TK@uCRnJWstE6E8g$=zqhArZMsIbu>+xcv-V4EU!I?9)}W_cnKHiZ#9C@ zMlR%ur0Rat~uUV1{Cfl@qxgO}rd4!br>RB~H9> z>{T=yUA5|BCKI*0*Tl=bkvK+Ss-S7g#LFAaP)x!kprlE>oHIi)2@{7BlX#&A`t-T< z9NQ5jUf8U0lKV<3;ga}VO&}kNAQ`rqx`d|}D>YE(ld#?^lW0lhG4cG$FJa7y`IX32 zE7K~UKpQQsB4vSfM4-8R+tXze_E6_C*nSxhf<6 z9KcLzgRq4Xp|(T|p|(JFhbhL;LT#zoOA=~-9k}yQ`yc5W3$@v|3n0{VgDdiczsEvQFy*KI$Z?MB>G|X5#jY<0MGx( z1s`5(yo(8~hqcE%tk!-}MlchMc+nxXBA&C8dW{LG+1BjzXOitVB4-SwZ$i_Qf%Khb zC?;>1L+Kt!KVSr*`<*KnNORnk_nIoOO2OlGd-u?PJsd>l8L4B`@&;#fI8Kk>`}__w z6jNv=pri?{o-{)-2@{8c&`MHJX(>HgL~s?$HN|;nHM(D!xTWhf#ABA{fKX!0qS-MS zuY3&eYw?OSKZsY>3d(hjS8jSWxdqmg)vTX!Eq-B2O)mZxTTK@+3N{hqT4;W)3f{v8 z@xZU`x4Gi+Cj_R_0SnJ{x{Z)A#T%9-rqJdM9#8y2KtgTJ^pz)DHMZidg%uPhRBE%; zmR&32G8OH56hsBIXom&?!TRq&k?c2mC7JVCYO(h&x>Q6PR&YhksVUqja@d|Nw5lGz z<($IJQe~FigN#76S)b=Oo#Pr2e&!qQjlml%qTPuA>r9Qe8)NkWh;8Q!3v@4t>H|l? zsXur_cWk79!Q-E{Ua8SZcvwRhJl0vy`K{kGa(z1b%C+3IuPdqelYXUw zj!6F_G!6yMpZ#uNGoOqxwY;ZeG|#{7edk?lwUY+_Cc-@Jevd6t>6Rk0K^J zGFw!-#e<-O`_ul_T{TC86UAB0tWlpzSZ-;CM&bl6zd{;0?Y~?=f(Ufger>n|Tf)wW z#-jC_k&xUO6lO!~VPm9+?u;Rrj4@-9F{x~iG73?oQ}bw3z$0@Ly0S{lF6+=l%`VOX z?aC_uwnJsQ^SeYRf(lb_?aA)T?%8jhK&gv;wo$DVF>QnmFcPAYifm(*s$E)={xap~ zf|LDUl8m6_sOc3&@f(ax&N#P&EVx{olcOCvR-e@5qafl$(1j$e#f0E0GpRI5Q$6tT zws%>5b$~-$fE;zp$cRK?Cd-Y#!NU#kZWYynxR{R}*N@7$ea_1RnA*6LmBjf0BIPC_ zPLiUIPrnW@;cAm}Oa$jagzR;T5L7tF%TzB^#7KHhVoKdn8Z~Ol}<=zeALsi|i` zo>#pY6i&>m#u!tXUwtLok((thPmq=l?&A*$N_Nk$BJESDAy-zepW!JqUH}3^AAenS z__FGo`fRRvew$Icbd9F)gRZQm3j&t@$o|-wZ8b=Fi$E}xe%gZDeB2761D;lNgN+@X z57bbndL62B54GTVQH4g=%V1Bv5+OxCj~Hgo#`VJ)5wz~Yri=sCYQ2bk9lS<_SM#tc zANDdOH(6>?WZK0=+l_+jvd|duZuB~g9Dj%IZ?>##7Aetsql9$pegw`S>1k^Y+q9a1 zh%PBhO53><*c#hz#Y$DEJ^got?a0Ebd-uzq&@JumL6`1BRrw>md*aC5M-H6?SfpT& zAG?QN?jic2=MUa_cRf~w`TEA{e$s3TtwnUq}~ z!YT_E6tM1qYO5O3`Rsant{rUE0M*q=x0JKX0i_0oyA}GTi4;~3#E%-*xTYK{72@{@ z!d3w_Ukon?TO6?&QiyP|P%XBQ%?cR+W&Uf!-dOKU8?O%Ll2@49e z7Kcn3p9@5HC+Zev4D}3s^2>HuK_N)fw7pi7_H1TawQA+GZS6ivD-?A8Nuj#i+K;PW z_7g{d(cD5Fx2mIKXm*$$Fklwhiwf=8#JIRE;fXs8fz4T2>ts-=NMoDFwl%at@f#j5 zj1euY-3O`utpjRyglE%+{mrnNbVy63UdvT0uGjvNwoJx}0}+P(ZIO3!6>J;TUOaVC zf+5Bzmdy+=-UtUKN}AvV18g?G{Tbg>rZ=7O3K`yDOw#NwVoZUm$ho1r!A*y8hq(=m zZGgTswabfsl97!cMvRGFtbxVwbiH2+;~MF5*$nHFn$=}6su4P@j6uzmhyAsfL3Hcr zUlQY^@)%oN4;T(!;+A>ZA7H+!wwmDeo#KXqj|h_=nN1r{f4Il2^wOb`FbT*taNQ zp`H6gN+ye|vf8Ml1XgK`q{V9yD^izF@Ggh_VY=wBfcU=IDSpno;G9Ti_}Si~HK~ie zHdDdyZxf}ov(}QCLpt^ZEt2hXeuEkXszzi}ORnpKIdX3hjfZ|yu>ZIh*zE3gLEQBM z2c%V=TBA|#iFjA2Hy6n0`ozz?32~m}&V_~+=KyHJz+nMvE>ggv zPJg_hg`9m!)AIp35UPF4FH}}Qa!sgiU0jgEXqTb7a^8<78iymIp}E61_nEYkoVuZp zl$VCt-4V=;vv!$WZ5m6bdlMaDYWL>)fD_40{B;!AAQMdhd{1CDBIm9Ll4ZH1Bfvi7_(p?<+;-U2Caer+EE>wGpDx&O%o9ye>z^Td= ztCSjd+tY1-&@GM6&y3&DF8CW+WBuWVnR;nK`+;1V&xCv#Iwy#`ZO&7=Uv_Zup)Y5B z;3zq7Bp!0VdntNX7VXVF2?v&PWX!Z1gI{2BVw5N61LOq;tuhHDdtq5Z`y~tOmpr78 z@YnzUa{L)B0=*mG#LW6dbbzhIXoEITb-NJM$bwCju+J;PZVJDuf%)uJc_>21xrtV(HIHb<8JI8s6FyE8`p za>Ftt*9SEuvqmJzs;qIn5_xD|X&j4;@kQ;s(Mvx!mb?<}bh|$5I>$N zG;8h0%Rk|(A(-TRHO`q;b5WGJ$-Xc3sd-jp?Vs$T)g-N9&uT2ev&t{`Vgcv8a#de1 zI8R~Hol6B=qw5Vkigrt*9{i{ z9F3);q->WpUoS-}g?8bX7t#{=1Mvz0X+FprZP|vL``s4) zzhJp7)I!Q~|viMF3>MC`VuPQqg z?Wa?DU2T_FX;D_NNR zdaf*nT@^D@Z?bYqxl$@gnwW9wn%?Et^jaYY-&p^HeGx>DM2d@ zHV~$FF269{6OcN2vNP5j@S1`ptTb>u}S}>j1-8hu^bO{(FiD#MJ7j@2XN+A;Vd$ReV^$PbaZBssnw@b zvD=DyE=@;D_%aek!C7Oin-4|HGH6^d2@g$d?Kz5cQ%bF6CWLXZd zS&EEmN^H8ya3qb|kNifGl$hR`Ddy9=%29rc=5?M4TZ0$08UJ5;p7FntVqOfVC?CZ9 z@paa!ONuhL%o@hXLax?riv*+*aPy_%R8v2R%FC&b?up7PBA~*)NxDSk4)kGKc}`mN zzYgt|Gf{a8GD4(GOjJhcY;=}%Uygd-z_gVr~m%|FXULlAPH=e$DwUl34DK^xBh3zWh$VV((&5zk$XkH-%Cg z;R4r(0#YRNm0E>M_{cfnuBo|IIQc>j?blMc!Qm1s^@AeJ%Z!B4>kUW-!~qME@ycU-G_UeD%-yKci)hqhxQj4j^BjeN(*c(HTC9#GB zY_wi(jPw!?mm<_+$!vlf3PAMD-BF~HRT0G3-Q~vw%S92ce#*qDEcBF!b*w?fjBbXN z9tJZw4rj|DT(>7j84E_oEbE%!z5;qMCQBRZxTDpxOX22+SoQ7XXh57%UfPJM|6X}C zoQvCQ_^;Pk0tuXXhqLKq&!vi6EHueP%-F>OowJ6r$pzZ7!+>3b_r#fVC9BZ#>hq|> zq@qZ?;#&6{onm{Z?m2Kuo0ySZb@&4pBcGKzt;P z$sryHgAvi!$x&_SU2KXvl$99*rP6HEU)W&KP6M1ro8QBazRZ75K2Uf6#e{O8XK!5* zJ$rA6)0&>$)m0}jhsgCeaY%62x&5a8I@L7fha(!I=~@r7`}-ojbk{S2o2X||q*CkI zQz7by_3XoG^b9;mQnR!Z_fjd@iw567!#=(u8upIg)gGT;l^$eS>3nfr2g4!AlwYC1`*fwPsy&Sek+fBNm zw3M4!$klMYX8X-;=U+k5@?qEC5&Kr9gm=ov@Pa-u=p5%~^+^t&!<5k*(|n{d}G(DQPluPJ|n{H2QX6>&n) z5IhnOv~b)0R@_j~@+ws~3lihl?;3WWuX~_?8(7s=d>lU1^023?Jym{S${!ZTLu(eY3XIxdNh<#y5G75o>w$`0yw zb9AhEaFP+=i3Z{zB#@-%;DP9 zdK3FKYo#j08DfC36>$R8W=?snmjTvG+ka`~Nu&#BYzLVH-mk(zw!Sj_Akx_1LSw+K za&8$Us$xuSe`6JQG_(qH6fofOA%C@9Yx|cp?P9%I%JJQa0E4s6L5FoXc4`*G1Vu2_ z3xO!F^EZ)SoKrvftDzSD#!_p(kqbl;gk?X_A@t2!p-LTYX1_3DC+@4C8m`?c)pKX( zNuahs^Xjgh(_1nrfzCc$aVs7Z+08g_i(Ordfe0)IrK&*fHefI=l!I-%M5i0YYdM@N z&yUaAPTv5;wQ=tPbknZU-Fqdle0{TUCiwHNRsL4eH%Lxx4uV*%AeYg;XUk3)YP+F~ z2n}Wf{X#o2bEF#nWo6H6xcet2@UxrcfoQhgoP$PPN^dd*@MaEYxPv_&*fE6P$FJM- z3ea;?bG8`io_vYbZFI1{IBtXgTGOV{!&%|-puFi6s zcFtd8*B)+9wPySwZcU)Rki<%@RDo`IIj_*1vtgYu18xOUkSq`87JcBjWwPewZRHKF ze{tv$%_#H?77KADS*UWo4hvb{<`R_I;Q2Z-wI$ZgG0drl3$8y%5BYByJ|!J=+b+36O%*NHMRVWy6kfX;i6S$C`jMq0&xpfP-_%^-Z#MHTEgT`*TR`Ar~MH>@s>QWcv&vA@4T9}q#`~l&StP+Pk z`fHC)z4{n8T6X)cD+4!?t>{qf^DaVyA3BW{m?!a9c(1*dJ^8L@$j|l6kdOAt5a%Ok z!R8RK<7gr~_^wCWzc}z*q{fgU8QCQCXxhK=PE_?aX|}>0H8hu4qmi&>)T+321qE)c zk#UJUat0#td%)C`6Q`Jggz|I!(Lf7WXi5b+2BbVf#QpWb$l1KOzEUezTP2!@=V(gA Pr33IEg{#Hak+S~}P;(m4 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/clickhouse/sql.doctree b/mddocs/doctrees/connection/db_connection/clickhouse/sql.doctree new file mode 100644 index 0000000000000000000000000000000000000000..a8dabd56b8c6dfa2b10ea99a3ae3d3309218a82d GIT binary patch literal 49343 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 z zlav>0Fqo6!Wf%~bCI(_{LLe?M(m|mf!JXd%wr-~nd!YiJVj2XZ{sH6 zWCF7T6B56cCvDU{N5 zi;{n_%$Km8M6kNhC1MKx<+cmUoc~?6#Na)MxQm)%QpHnkwz;xEXXD)DBuoA zeLRT*32_%QLMHMrks?t)`A=pMJR<+hOrJ#LsMcM!REZnVx-0N)B2kw>iMSyFwr;DL zex^iCMT)vEv8Y{U);+D-VW#(r`jnV_&{C?yexy8ye%_K8shiA_E2JI-r8sD&mq^_} zq|qk70>%!d8tf$rv-#sFk+AtCoRS&+6p|#egqm3hkI-c^eG;Li(Fe8fYqh+>=soRw ze_~PZHM8y!^;Ks2nG*Fwk)l4DSky<%tb0U#pPAk(>Z}?)tP#c2@EXTQKh!X_AO+L> z#}i}peY4C8qldxVziXzK7=4>aqs<-G|AGj3&tsUq&snDIeW{0(i9HW#5*hq|n8omj z{eKPg<WD9QI*U>U-wl5^{G9bQ}y?eYe=@WwIA)*&8mlyC|{D7eI-)-90w-xd!^# zk$Fdi%)1iHe6>LaXev)TZ!^$G%RD`Q5N#B@Hl=$J2oW|kn9^(UT_X4KYO^Ftt=1fC_%0DjA2o}jQ2I+i=}|MiMCpS>8m$a)zW-jhZ6d5= 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/clickhouse/write.doctree b/mddocs/doctrees/connection/db_connection/clickhouse/write.doctree new file mode 100644 index 0000000000000000000000000000000000000000..e746c5298fbb4bbf6e31f7a1366cc0eaa3433dde GIT binary patch literal 58118 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űkaaIm>MXu(I!jaV>#GuhgFIITr12wQVj6K3Z^ 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 E2ce0N#sB~S literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/greenplum/index.doctree b/mddocs/doctrees/connection/db_connection/greenplum/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..9a10a11691a99806f7ffd5d6f293a40c2bdb95de GIT binary patch literal 5016 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 kB^s3_K+RmPHFxFUa=B}v{EXYWQvd(} literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/greenplum/prerequisites.doctree b/mddocs/doctrees/connection/db_connection/greenplum/prerequisites.doctree new file mode 100644 index 0000000000000000000000000000000000000000..aff2a9cf055f9602d9f5d358518bf49c9e4cd935 GIT binary patch literal 75612 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/greenplum/read.doctree b/mddocs/doctrees/connection/db_connection/greenplum/read.doctree new file mode 100644 index 0000000000000000000000000000000000000000..31dd9ba4d46959ba8246a9134cba302b16b645b6 GIT binary patch literal 83259 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 R8QJ4uiz2m6ep|C#`2Qr5twR6+ literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/hive/connection.doctree b/mddocs/doctrees/connection/db_connection/hive/connection.doctree new file mode 100644 index 0000000000000000000000000000000000000000..9cc9a702b384839f224637c9bf2c627257dc8e46 GIT binary patch literal 26332 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 X>ouVX{T2G@l2VhbhE%X3Tv7R720RvH literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/hive/execute.doctree b/mddocs/doctrees/connection/db_connection/hive/execute.doctree new file mode 100644 index 0000000000000000000000000000000000000000..dda6f55d6030edd890bfe9026dbb5ad8d4e30177 GIT binary patch literal 15215 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/hive/index.doctree b/mddocs/doctrees/connection/db_connection/hive/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..daa534dc26eca60280018329a6f2c8f9e4b94db3 GIT binary patch literal 4974 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 G=KlkWS+4Q` literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/hive/read.doctree b/mddocs/doctrees/connection/db_connection/hive/read.doctree new file mode 100644 index 0000000000000000000000000000000000000000..77b64d5704038aca9f18d823456f836674d38bf2 GIT binary patch literal 23064 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/hive/slots.doctree b/mddocs/doctrees/connection/db_connection/hive/slots.doctree new file mode 100644 index 0000000000000000000000000000000000000000..0419934b02558079dc91d34b2876d3fe9be7eb0b GIT binary patch literal 26608 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 AhX4Qo literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/hive/sql.doctree b/mddocs/doctrees/connection/db_connection/hive/sql.doctree new file mode 100644 index 0000000000000000000000000000000000000000..a5a78bb759bb1fa3b7fa981682c8c88434aa8db2 GIT binary patch literal 23281 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/kafka/basic_auth.doctree b/mddocs/doctrees/connection/db_connection/kafka/basic_auth.doctree new file mode 100644 index 0000000000000000000000000000000000000000..30fb1798537fb5575e831963b7b4eabc96a92ff5 GIT binary patch literal 23699 zcmdU1TaX;rS=LHBd(&!lw_;m1ZN-jOF7K?gK`~iNO35}(gp!r894EnST0Jwp+uhr9 z$#jpj8_Oa#m{@S0P#YXDMI}%PI0Xq5xdAT(Fcsi|1QDPrqzZ}(;qt%(rU*}b|GD%z zJ=@beyI$Frsx;eu`dt3=-_L)}`9D#5@r9q7lzKd;1ov0S#UM4eWv*^X_m*<6aQSbp7EbZ2KRFOFN$?DVu>Uo1zS?}oLg z>^G+?PHoYhzHjE}lx&Ab-g2rEcHA)w|7K&Sz39fei)O=(!*bhoYO&`6PTrB*m_AxQ z_RjM0Y2JL=Z`9o7vKQCGvBVpmPrOk~*j;cjK}=X70YLA_0IcekINWBv=idC8aNyXO z6US}8(uqM=qT@Ed7J+7LxEeZ9ghD)*8ctn2jnq0V{ZetNOTYrJv&{+P#M{TVb=rQy zj&F!S2^*_AjhbI`1T<{mJ&AXJ)M3N1vq-}x-fe71aoPBZb`;yKb~9`)o=VtY=r`Qt z>{d3^azGXUv!_#Wyn~pOcQ+_z;r}80zZd`Cfk_Pkat`Z*3f6UULelZd-Y##(8_ykQ zpQDDNdSl+Schoy}b}Q;{W21H|K;E`a1>7_w*wSQ?{osFl$yr!(rYcV4SErm#?9B?6 z>N%?tX64rswyV1#hxTDWP$CGt)A&El&Vaq^?sNnmaN5&xy*15oFim1GU2QfRZk42R zx>m7`H{La54KUdRQyFwf_$-Da(ZHC7H$?I>i7A#K?_ow*cAll#Sr1cBr_qrTm%%68 zWl}9{RfXphu5B=d4~r?xdB^5*(J#qNxPKDWFNo^id0=1=ux?=`F4LDG-8J$2&k zF_1KC9d51EoJQP= z&k%8cLlemRbqJuC{wSY**WA6U+`NK0yRfE#`)UzzBLtj)Y=rvn4b+!c1Esl-J-RUx zbOG&<)qza~P_S(TTC);BMJ5?Eu_1_V2da@B-_WQq(3}8?H^fF3I$_8s?OkPKcHFGm zFo$uJc;ED{{%iuvLq)Oz(dOeYOq?b_$7RB-!J1(7*mUx|lGf&<=+;?mJ1x)T+?hv& z(XisC#c^SEB9fhp7b7PM%XPQvISoImpFDK_{in~&9lCh2oT{S(J=z*t<+~>7VN!-e zKX%(rXo7G@@3EAC+?EfFG0^wTfwJn$WN<>@_kg(X0%=fyWuu1-sG705-4N>GJFvvJ zhTbCY&vPid@&aDCqyFTq$r9W}Q&J))HC%vIG0% zx#ebikz5YGqTuzW-wsCt&RM)-6RGT0==hCO7j4Zf`*X+C_zwmKwrnwp=_&a@4%M!Uf3&0B0xsIlA*wfFka>%ge;U^%kD!jGgVI`#S}C6J+dn!X> zxB${4m!GZf&)trCa0hTl^71enOV?htcFAo= zaD$G4R#qa`(o6NJG-&0c= zyr+=cCWCk4J1b#bk4a@r63`r+q{cJByYZ*U!)D`g+i66h6FU{yuxmo2%>>8Ou@zSL z6?QWiue4wS$0Z&GP;6Mf!p3pL63cS1g>0*?hlpU{U?8&HskD9531=fey6n_jAv|~K zTj&CfDklZEk0nK5bKD4B=uImqZUY3G{)?Qp_H|wihUW8yMAR{I!f%q zr)!u=9%`YjKaI5k4S5n*!3U;SR^E?wTTJwK9 zonW4+n)JTz{Yl1{E979sYal21Tk1wE#x-+nL$HIZ9Ew2g<12J5TkTe-QH?vR!cYUL z`NES1@ZSpoWCN{L@t@%P)MO_37yN+{U6YA^P&k5@&qT8GEo{8yBFfxYw3p#W8YWMw zlX)!IfdxIE(XCk~?_yaqs!Y2AwPqd5m4t0=tsst$0`A#(-{M;aGR`Z8VB7IEYPnVT zl~tITQ3RZ1EWrCFqFgJpa$$d!%*rr

WjOmsRA>%0fQ5w_8s=?tZM}x80hE3xeG; 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/kafka/connection.doctree b/mddocs/doctrees/connection/db_connection/kafka/connection.doctree new file mode 100644 index 0000000000000000000000000000000000000000..cc9b9a6a38f5baec0c896587676ef0d275f3e239 GIT binary patch literal 56977 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 YZc<}c%2tDA;@DV)1~XyzxeHtTf3H0ng#Z8m literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/kafka/index.doctree b/mddocs/doctrees/connection/db_connection/kafka/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..370672058d1332f95efcf65da69e9c7371bec452 GIT binary patch literal 5696 zcmd5=U2j}R8Mb5Z+PkqgiR+{wl4=#wHl_7$5{Li`fl8wSvRDU#B5tCi*>h%hW_`YT zW=^sewMc=K7={bfQ?6PhN~KQ#7!ms076LI@w{_BcD;$a&Fq{*zecOp|Gy$bPC}SF+=h^b~AnR$QMcg-(3eLKfs-t6myU z8hz&O0R=0w#C)BJO|z6HGBejdl7TYQbXn+0kLB8!#jj-IGb%MJnhj~&Oq?_Il4WaO zN|biuB=V!-mou~M%aCU~CrmwNFbkB~Nc&8j$Dzbiuvir|vT251>3I02KY_P|+ za~>Uglp2mAJ=n=gTfM%Dy=t_VcoDy@xd9>vysKbsmUMLxckOArMEI^7g&}vz|GHk^ znQGbf3i1Q&%&NMga@i?eMV*}DGR{$hQ~O6eqP9avC_Q2MJA6!0!xY^QG8;ai>34HY zd*W*Ev3M4CBQS46{8lb5ZsMTJR>V!{gmMeWMLBut4X$p#C zfq|=lZHcrFa=B+-m@_F(5H(&&rIJMQ*R!5^ZqAI!uodr2+K)AtPNKB$bA`yIWjMqf zZq{Vz`YFX(3{XfG-xCMo``oMsZ0zw^i)?2RKF?wsg-!FNiW2awp&#|LLsJ*h^AOPw z&3dt1)v+3;K_BPxvYiFfv>Z@T=r^fwbxj^sl;veFlC4Q}!qonQeyWEceM$2}52bsLNuPs6BqK3Dt8>QTO%CTG?YOzMffCTvMjQ#pFzN(HJBV z!ZV5iQ@_nn{Hv-eF!dE=_z?F5K#!#9j0aH?z#1)u z8X`vV70|s(VZl%3J>2KE$GIo9aLTG2`xO(4G99oa++LM@q9zr>O@W%+DVjBNhL0ss z)J&&p(p&R*KOJJb);*SlKoJrMG68xTFqzBgNTi<(ftiE*PUKNlVH&9(Y<*!%NqnC_ zI_U5ui4sSkmiLjdK?9u4)tNb!BR{oD&90J}3o|1#ON_n?;1R(Nprp&DSEF7k|OagHa?GvbL%o7QBqr}5~GzN394Hb1gQ%7VO;Fv%}dL&mD3N^NI3;g>Kb} z_mPXkPLvGcQD;fgDga4G^8n^S+A^!?-+KDW=YVG`8MrgcQ_p1iXUgT_yo@M|GNL27 z9@sq)5V~}4$7T)9`tTy$^Rt~*a{-(ToXR3u_NSA`1pap%YH=E zUsD0Yo5U+AyjlUMCu}=!I)3D`yvQ~eT*06Z44?ZOP___a_Ffue3wjr^#c~k_P+6l# zh68Y*hzlDOC=g|5#O(6{_F}m+(&cwA17%Q!d+^|Ws{c9H*yF4VsXd1WO!^L(iDcNwe-s@PjCheexc1;8HF>)O1fSho=HCA?QbbMHVZ%In!B5H;$~> zD0j7!Hjb>1Se$6Oc%7YHs~Evu3gN}Yr4}4Q{=qH7hZ%4NB$$<#ivBkUhp*w>JB#W; z_HDQfUO?b{c(YN6aI`UQ<}J0a;9zBMl@6;*5}vd$j}39p5>un*@{Kh1r3=5r^Ba9h z-OhQ?CQEF$FWP5Pg$gJfyVm~ v{V+5h!C~}U%yY8w&CDpar;O38PfA$)1$OmWEe7F+VA`uzowU( literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/kafka/kerberos_auth.doctree b/mddocs/doctrees/connection/db_connection/kafka/kerberos_auth.doctree new file mode 100644 index 0000000000000000000000000000000000000000..d90a1dd00a609a9f132ccacd07366ebfe52256a9 GIT binary patch literal 43862 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 Ji`SFF{{fu-)N=p; literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/kafka/plaintext_protocol.doctree b/mddocs/doctrees/connection/db_connection/kafka/plaintext_protocol.doctree new file mode 100644 index 0000000000000000000000000000000000000000..b2d454b532f7cb8e9165d131c12be6c72b1a619a GIT binary patch literal 18618 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/kafka/protocol.doctree b/mddocs/doctrees/connection/db_connection/kafka/protocol.doctree new file mode 100644 index 0000000000000000000000000000000000000000..021a6cc89858aaed0bd8d05de98db22af26a14fa GIT binary patch literal 16936 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 F{6E|bxvc;I literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/kafka/scram_auth.doctree b/mddocs/doctrees/connection/db_connection/kafka/scram_auth.doctree new file mode 100644 index 0000000000000000000000000000000000000000..e8c6cabaccb23a429cd928cbe7b3155da15899a2 GIT binary patch literal 27927 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/kafka/ssl_protocol.doctree b/mddocs/doctrees/connection/db_connection/kafka/ssl_protocol.doctree new file mode 100644 index 0000000000000000000000000000000000000000..8f2b33b9cd0ec4119b951266806bc771b9e965d5 GIT binary patch literal 59151 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 OY7{EtsKO^hrT+&^-oh~e literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/kafka/troubleshooting.doctree b/mddocs/doctrees/connection/db_connection/kafka/troubleshooting.doctree new file mode 100644 index 0000000000000000000000000000000000000000..156f1ce98d8099a3158fbca7649d4ed1ac59c379 GIT binary patch literal 5030 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/mongodb/connection.doctree b/mddocs/doctrees/connection/db_connection/mongodb/connection.doctree new file mode 100644 index 0000000000000000000000000000000000000000..4b771c6a1769c29abe9ef99b23174f72e44e0922 GIT binary patch literal 43670 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 AbpQYW literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/mongodb/index.doctree b/mddocs/doctrees/connection/db_connection/mongodb/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..c8db90f0d3a424ff21d29d6898c9df821f238efc GIT binary patch literal 4983 zcmc&&TW=f36_zZK5~-_YIcZTlAdJ{`Qi!zO1}%i5MH3@vA%ser!03yx814=^Bke6a zvlfNMh?B;Ng$~dMPy5sYMNy!S`2z*|9KgRKf294+?1dD4OY_hQAjCa;=FGW#=f?Yu zKZY0P+@IM{sgUuw%Yq#72B~ zw!y^+ZGxa~*aw z&H^ns^mKxF+`G}e`NQrTJ$Jk(RoIShosl@p-!+8Ko_-iFhA zn(x#9za>sN(`H&{a*%7pmEyP{1BIBGB|l_Jfr#&om_^06SK49V!j1sRPVjX<0;PWaz*I-l=nmb*8TBEjaOD>lU~vHH|| zwpn!*@wG)y+z>bS&Vhfyw7fcj#6ms*nwkYHry;Tm{CiOnkCI@pTOjF~#x!7z3=p-F z-|6-y?rYF};urYz%-aBS#Cr-6XIW22Y0p{QBhdH!B#yaHIoJyZ-qcemu}lHD+&YDq zRKY!^v1n3iT!%Fcq;_~FFP7dRLvFuWWyInKd`#8D)Gr=bS4g1mZwh_;;%5J;o-HFt zZoLEc_X~FMF3eqYx-I)jlqPu`JfWPJ1C|nBXM9?_Lh)xpMcgvirX2Dd_rM$=FOx1= zuJ=v%g~!qiHRQuwDoK$4-1f~6UU+80fCJpD%GeKcs-aYuzT~! zW|q48YT9qrfkw`Z!en4iO;gAqK&?JCYo)N7upH;n06GSCZ_cz`TvZZ>C*W&>dZbYMuSv>RntPFvq zu{Z%b`{wGzf6stAH3Jum*Ivk)%9OjpL!o;yN{%zcnW82>rcC*(_?!4U7oY6Si%(5^ zA5LHtC1u5Dr{ck>`219Su{S3ko{Cdct>24(OnY?=Ha(j4s`$hI!i(R;RDF^`2a{oW7RSqJe=@vhm{8H-iu@Ig&)lyOyb?1oM>3ybtDd0e*UWWl^y_w$VUp;z|JzFDm(4!C`@ zthlC7V6Z!14{F0Ki8V(FHO)H=!;`9Osc9y;PIGPh;!fYRMm#3iCK(c-4!K z?mB7+wSi>PG}h3nBA4nMC@@7(VkbFD3!O z-n4Q(-1*6llK6h<>`RYlS(14Ivrve-2pGUNUt6nTqs<%5h_CCY$7cghR% zooYG-S#{GBEt0sKWFw^0MdEY{fYZ}FLU_jZKFpEQ3XZzqo{L8>Ong>MG3K%_81zBli)4eBoeOdLC{J+(EtPH_ zDQf_R7f@t81P02Yutik@C3c3*?OwQx6`4qz!;uAKFz5tG-xKOioNL^1(FfO&$0H^~ z5Bo0_U8nW=a{L8W!(ma-PR#<3?^;YLX1ORO$Xy^2yQ4&IXs;OY-L`aocK@YPF1E$0 zGFxX}70cE=WX8;5xvl7yVf~)g8Cu<_tsGc!n<=wIFH72Ev9C;v2t{88+vA5yyh7X2 zK1S%T#QV8fn+_<6GHbpBEC|^UUr;D-E55>(X-dI|^$Gxp21!_B#IbJA5Nqz%8Hugx zR(ELYjC|DJOw&kvaaLCO22(1kijVOh&_U1flQ4w$VFQ}V(px3{5v)60o1dH8n^zC) zuaPO(8j*9@a}^33U5q_uU7aZ8RYk2^U_E}~X8@dIBTTSj>aBVGcAkdPM>^ss3;HH? zrwD(CVzAq}Qm$23y2VCxV7f0l^7Z{cmxsQ-|Cg?<_oYJ#GrsZ4bT}gJTZaMuJuy*D z?Z@IPW*TMhn>UMu8l>>A`fu-3KPrCcL5VE4?nTTRhXW^hb{}JAt1i}+BgwsoKG@Gr z?}y?Ek~lCszr@`cT~dDiP*E9vyGun~SRI@|ap?)EGh2RJOn582oV%R+LniR-dEw>x(;qurhL z&a9;aD=`m)M0HGIFRtQ|3Xc$y3M9M?RTM>lP~=4j7$<=$Do`Y(QdB~v@*tHWMe_aq znC_X|?cF`e#w4gpdpon;{r~;Hy8G|H|9)uX9gn|l9skF+#Z5nGoG!Z6Y9p$8afgjp zqe{E(HImL_oxQK`oas!niI#gRj@qq?*J0~WqT&a&YRhYM7VzUFl~00NT$Bx561NxQ zBuMZwYLu$pQqTzKYucVa!X7Ucod@G~vl+FLk{?AU;|HCf?k;=t^Nyb+&3JxpE~qaT zV?Xd})wmc$bBk_u*_*riz_qio9=`ILvx~L1S8(ueK5<*iUZR_b8eUQ>wmi3*_#W`& z4S9{ZYm57@D_%dxtIq|Es&~5RC-qvP#`tjdmXyv=fN z`04kABgYDElC*-wb^^WL4Yw}7jaS=E{jlg(PJ#-&%tp7C zbo^axW4je}*!62-P=^&NZlfAhU4ac-|BD@eU)*M6iMvc-JO0INR7u&E8(MKvYPOzlW z&c^rG=7lu%?9d206jVEGvr&iRcr)OR5M2Kz{GVfYK&b29Tnx^;t+}M$oZ~&8BjKN` zM2&`5AzhrSE|!cZ+3H*~XnI8IBBQ8_o%=BsKB!p zBwov{8F;c?Jt6uFY48(q=IZXHwfX?{3>R+SCrf<8U^y5J5+YuS}i&iJ56Xlaih|X6R=3J zVx|By;Z!3pb{bLQNK5AgjY`X-!N9FKangeBE(cy*lx$nawlqEHbYr=68Uku|POsWTlDWbt56cHsjel-K+(bAnBatKc8}2jm|kX z(Oj9YN7Xje?kF3_12^UPPD7ToGwkN`7Z`3_eiep|^X^h(t{``k>ge>QM-{i^PtvzSt`yfkvl4DvEwJ4^n=vHq39|$Kuc=npuHK+9G6W4ivgt{N|2ZkpTQkTRfWBw~${MF>{}Hw^ zPB2c4;d2&!TrJO^2QNQ6kUabE&PeH3Oev+Nvr>9aOX*qv11yI=X_Atr&A{M^ekM~f znF@c*hAy@I2T~#?P*_RCam4{OT-~Eyux-b^N>qnc!(0SoVJvLQ!s_>0JpZ{j(enm_ zjQtNpajra*5Kjv-R~~a$9E{emvB1TFjF!vFD3{C511=`F(%dev9jgi(g7Fg*M?W=@ zY>U~T>g%|Ip&JNC*RNY=wviRCd_QUA`w2G+qY_I^t_&)6Z6VRXEX-vlP@Y%G2-#*0~PobpnIvNhc1fUOLODE)_+&X?&|hwRXMX96ab;!%2C~ zUJ#YWKnIh{rq=?srJ|*?lIdcxIFk;H2VXaJRozW0{?xqlx+&2d|9X{oKmJbfR;SLK zIYSNehhpJwKZu>hDDj<^S8Z1?regWwxOHxN&<&1!4Ef7WlfZl4of!ucWv5PZQHvw2 zi-76_o&#)`qE_9-s)T4kP2S)3S}VoEt#Q+<1Q_5~oPht(0yO;RD;q#ZHAi3`gGyc% zKXj2YHL5SL;&3E4MCgi~2pM8{Wx3p!GfFz2->0Mj@dY`EeT6n89J$9*Rc_99F5tcm?!jH$FVVt&@C)} zxYKKt)=-#(vpW@Kr}sM3Yo2yHbuaOw>fX);nY#O*aN96MTJ@ zRlT1EVJ7L9QXIf3leAlz)%}O=6bpJiRAN-A2?xPEtq=9EIqy`1O5!}|)Pgv3VHGZo1?_9Y>YeYdzr68Y}!w!J>Dku-LnMxNx|1_4ncEMQ9PrL-PATY%ycPlwr;C znKHbC)!=%rKNFm^tLUoKA*E~jLeuqAX57@7z_jt?T?C#k&y zp(bJY33{V2-N`W}XA88jqVSGqr3|McEYvZ@-o$)c4)e4Q7e!LxQl38rT}5%o ze5HBx6j6}76ua?=t#5SP+=3u{Qn2Bx_`@Y+7pFU6(QG#=Nt-XWO#IhNujc~8Hx2sx zeLy!?gZ{f5=+uUlgw7e7zur+D$2Sf1M}2^vS`GByO`zd7@RY7Fu?TMoFsUOz4lfI| zKyMjxJ}86kge zuMu7lTl>IL(ceTh4B7t`D?+_wKfDChv@GgQ-HBZes~2{=_1O%jQ0?-L5SHx z(cT6!v=LK6$qL4Usabl^pwL5hC^o?uqA!owq4e)dH(v8`Rx8qbtT5KjYYyf-c%9|K zeJL02Gq~_kEAC-`E_9<$P8U9Bg|T+}a4;8)KGw)_6-*zbd`_fIKXbEI+rLfUvp$3#FxgnJ?uN=E)f zVvn!3_SR~T#o&ge?~ZiFO0HoyiCUTI>cLd_4;o~?-U^tlk5IL(|5vW0$Yn+dR*{r5Q_f3wFg#3 zlu)wL(R~p5tR0F?I)>=WpV^_p!frhIG5Q0E#$ky z?8A%zp5?D8W&;m1FItQ1x!briCL26j)amKbFPw}daL8V=P-0}_u9sgRI#FKO3qM2QZK-38J>sW~gC5j*|r5l8J#Qr9&y2CGHPoUSns0I)| z%6AuVgaQXduv>?N8npk4IAm<>XX8iNs0=+`%hr+b(~rpk89PZ!?OILj@~)1>XN6J{ zRj5}<+zD?7Uzkx-ZTocIfchI1J4inq}Y z^r~}Da_+Lg4qu4IjhW3oIcfHH$dv_oa4#{wkND4nkLKc@p)dZlzsuk2@AF@HrsL1j zp&jh4Z{UYs4umBMg?0c(P>znx^g71kzto>c-J|~8QSdztoWUeYnbN>4MMY9Vx~g&YQ^ zug_tc4Z|@FD%&LNfVE+LIPB2*QJf!AaaKNTl?LUD}Q1vqEs- zUY5t-GR5-pg<|G`6*?UkK1zr~!)|5;1oVpokJZkR^}0`xpNv%LC2L-O^njw!QShV= zho^~;IQ*1Vl?k?09^EP})}qQu!~U`xtBVxCE$t}}W+^I4c+>Y8k=Y2@J~M;(?5mbr zQM-w_YPjJ=iiX%I8grxdJ)^s9*!W|8ev}N*rXpU`!)PeN;sBECouJZ&&F$R zW4Vn3uN}4_jQJ?_dqkf(MW(SVne-TRGab}Y1Du0Eb5D#(2#{Cd@ZwF>if1U&+eL^R zVovxu(5iFFT@iuaAj^elN=wA&3ICoUL#CeQBm!qVOyh~efH;Dd3D>)yN7RcT$<>F{ z#0eX8k&dUWlxGTvad*V|v%@#jaYBUo%QJKkD4d<61HoR)_@;{Cm|P%E8wdr|RR1Yq zKoJzon{TXLno?(rEd+xt6mgc)(*v;kVIBK2XRM z3vQX|BwhC8QTPMY)A1f`W8cF^x(g&}ICrF!Y8xbts03@hz($3j7~QlG@WpGCCqd}+ zFiyB(R1;cBoq@q7`njhL5uy8@Rt(pnjkS<@67@_n+fO3;@8i>Q|NVMmj$>a}i;%KD zYQ{&-UThgpRZyFjJYxnPY>44s>mzoMk?^Z1qs3%v)*!88+aB;*F^*?p`4cP!Uez3= zF1e{%6-R1hA?MoSi;LGvBdaZlyOy<9 zFYYHoTVQM(aSe-_MMS~#J>Ju(cU=ltY1~CtN!%n3y>5||6Bw~E`H18MoRoZYB%Fn2 zsHS&HE8m!ih=08V+a-5w@*)l_U&_ctUb?oaZ5Toa;O8vLSf%N zH8irB!^hR~)1afqxZp9r4xf}3a}w_tdas>^p}ju7OUhf?#TB{=u4 z=qU+@NmUF|WY4$f+Z^=BzQn88eVgJ?85A-8iNR!oZPQ6rx=TD|R6zK(%Dlo0m5?Bz zZU`28L073-B~ctGPtkzj`Ah?o*#^>rqnJV9poj^pTTu+b-DA%m8A_v=(5SQcL&Lg1 zs#TNTnWL$7*a+WDrBj`}7+U%U(I!-rhgXy7&qM_A2x~cQ;@>ss59L5-XH0pk@Ifjq z;7JUGMbIoXh$)Sl9$B{Mc_!opIWAWHpb`AcFbI+lMw#79fas?=VeEZ+f&gS{33A$* z@KB}}G2sk{6}}#S(gpUXhOlxSwCN%ndK_Hj1VM0Uie=0`z%yYqKr80qD3C8if!8b=b_2`e zL$lkg&8J$z7FYp=S({H5q?^q@wr}46Q^O&qh25<2)(ntt=D14<=x5hR(%T0XYNDMq z$cKfnW#%#TZmkCSa**>z-e%Sy@9H+QR+hN?kJ<#6atPU_LYiR0Vn7qTZ*Vp$l!vLJ zJJKAcBc?`N0kYOPS?zt#Y6R61%os)^h=tuW;!8uWYo5Jpn(6GEXtU9=ZaTQ?cW7Xj zsJcgoVXo4@M>tcn>j-+cR{e9pytnc;v-;<#5xv(kR0($ff?Aw^H;1TQHlcc82>(#Bah)AUwUTN^Ma|`9sDK2Nci0^QNas2!7%Xuat{A2dk6Cl ze-Uk1eR7mLvx^h{YpAyd?{N5hns|SJ-8$e|W4l8n?>OEANOfd6T<)n07j%(?c)vhiC_yY*%br-Ia8Q~E;Nu$|-C zrtI*yQ)#_@s2vX1LMF6}WcKAAWw#WGjh#~gQgazGJLNKW532-TI(Xu8Yc)eNogC6e z-m8hUb=7O>jMiPA&egVdnNp611dj{7Ogd!dPsrn9l`T{B_Zx&nNco4s({%k_uPz~R zi5`DM*zlB|fL>?rB+;f9X~B46kL1?)Cjtrt1;lFnddvrYGb#ny?XIdwV$Uo37BH$1ZO(5HOKE#B{U!+2eRSj0qP|n=(5sf|?@M3$(##O|bJ0mz zM3#BAu>D+gR*Oz*q91xjO!SeA4at2YT9@| zx>n=8l<7joc(V_luT@Z89b{@tLN2Lvu*ZUb8W*x8XiLqF_n(0^PN&2_BiN=Q`rc``+GYnilDpX}2q9q3C7 z@sE?#c3Y#~-)~)hoR-!4vyv?^HQziQvUv zLP0v>m{Lcc#X(1TrdHX>@J)EH)}L~dRt3c8L(I+OtVHo1c~8T4tZH&Pe3$W#^*Xjo z{t8NtipU!zM|B$rP+pbJ@MZViaAks9Oy-?yAXP5%xmO$n40^R|4$U|>q^V_iM#6a~ zE$du!=nxL%Z~zNzGF#PfG5nCE$Z)X?lxOHixk_2jg7p|6!OM%9>HW#d_&*V#1K~fW z@Xq(hj&cO9F^8$D-aW8NrZElQYw}*)lw;na(*2qh)wD7hL)X*e+2)BXPqXZq4nGY1 z!jIr16Q!A@yi6dgDIXT7Ri6Dce8T8p_yb~&a5ndnbB)^WKB#?xQ2U}CwNK*bj8GV; zX&ZK-e8c=4%yf7&Y-V^1KC(1>Ir(CW!+D`Q{LG?18oq+sB=Uu?q;IpJ4S#%dCp@Np zBD*6G8Y%{$@86dS0e{*Fl+4<*X=5&RUDv}R z(MNI^KI?Uxj^oYAUS<5iZ@e2pqEQ1b1q5=-6Hnq2hV1?iof^4I#)sY@32(f^Ig!M7 zajK8wvkbR@$UiK&Mh81~_e91p`#0s7W9(VY4T-Zb zSbFp62sJJ2!9{8=YIM>-;?5irIXgAbv4lI&hHcBD*XfUYP;aP>C(=&;{leOPfT=UV zX2(d3>G;8oR8pKsBQkS``t+cjHORAyM=6cjxDo(0T5KYDFzVI`2V+>zk~Xh=QDv;y$CWCS%Q!b!F3&)$RfxEF(msFjtd>L&e7cCF z88x{(TV8mgw-M-;0(nE>Oo47a!5x8g|JWk56sG`M0nR8iXsS-SgkxPTadpYDY!-Mx zp}UN~Ik7P|oW>-Vl*I8{8?|`6Jy)~8z!5C2 z8yjZKl-U*(aNUNM4~56LdS!Iu0~Z1r=mg;sFB(LcESF80R5)}GIDg{l!eZcSNB(&2 z7@T+-G|b?Th07O}%c~So5qewh7o){*t-Y^1R_nPI>^!Ce+r4oJ(8EB=Had~1#5H5e zRN_WFV}cPLz%F=dE$s)m^q#c6+yy-l_CHpVPxAv|;ce(dX3k7~{yaMR<36-uJx+uD zX9`u3ZMTiY;XOcrWzT|zuc3F{kAcb2n@$hYsD&B!4otRFHVhPOa9Lr9(KnRA25OAd z;&3T||D`d#zjq2NpVvff8qUP#mD^YmckdL+GE1s(LCK*I7j)V4Kk#iO^0Ckbu2E~Q z(r(d3xVRM=SCh?>n?ZhUVdhHZmV#x<2{BXf9>55SYr%qq4vW*^O80rfA1e+mCUjSs zij3esjiR$}pBQoYnX=f4{it1o!;CHz!wD)_R?K8;x8QtOxxA-<+s-_kh)U>!5#6;& zPM)hC-4Ghn+4K~7N1pFfGhwX~|M35VX52{9$bLzW{u zVgR~#V4*=@RkUu}&&*{md7O=T;ns}xQV33@7R^8?90nmCm8G%SW3-dUX5e`VMT_qf z;;BvK{CS#NSe^4}LQl8eFcf(xibxLFQ)#!Xlu&2#Wv$EPFfW1NM%iV&#rvZ zH^Z^bs%g4{-OD4mQ8>By4UC)EZrpB;i7YblsI*h1#T9W|8PX&@3F`WXV|Gc`Qb84| zl>EC;!0e~OTES(banzHjDBGgm@O!&)1-snJj61kJ0`bL3P>YKoH02V+J=~+Oru&=YGzglF zM(w28PC7^ZSL4q1Wv?OcnZN@DJ@HU3+~&Ty6-5b9#S#9~9kzZBmu(9aaFoAs5(NtK zvE8cSX)7g5Ty!fZ=~tjKA~syv&LuR1DZgj@c$RIAL3-?cmrm1t!nj|4k|MgACAnoD zqrpqX8?WZkVj(V3#B_-tf=6cnPgbbG&COqIX?YapR;ppO0dj6&1>eOj5%O__?eImZ zQX`9%a6LCVheNIx(>DNtW0zK>;0?7_1PfI*n^lX?xnCXi4CT2xK^&XI9; zIb^b4N|iIUg7Uh;b8NfF?N>@u$aUDJ8s*erTlnQFC3(BbS!Bc4m#vhLK+lz=y}aUM z$EgDL34H^VISEnvWV=b}>404lB%r@A1Lq(;Q6*?BK?bB<*hz*0RyJ2Gs3UJ3VDV9f z5F?WWk*P0wRcP=EFz`JONod(~Dkycbggah?S_yGtWOu>&vup>oOdOBB+LEwo=h#NC z@jwUREo@v^6R0mF5m6`sbR#Jx$fD;V9W3N#2atkfB`~*W16eZ2nj`t>mYD6#{6tA{ z=r>rbc9UBuKZ;->{arCZSxSGWQ%^yaH**)FF%G*iB4AKiyqgw7(p}-#Y25!ReMGeX zU89d>`l!;!pVALsrjPf~uJAkQ;}|{OiH|m$payyP3ynRz$+Nu0bG)^4yrpxzm2

zv%L1%wtof6W5hebLE{g>^?$&D`ac#BDRhRX^5yrS3fPYk0$1WNQ+PRjOv6jYBQ!WZ z;mbKvTTLkxq^blWxP>HNydh(w$_WOTZ$vq;1yWmIjB2T1^P;m!3$zL#h!xUDcHkm7omD=@>!Eo#r`TjAGJ=_6feq??8aOXJG8=JB) z6cCXZ5>0>+IW`%u#4rZxZ8~%#yX`2S>FTxEp~LNFEvUe1Aj*-x!N(?~lzYzRohkG9 zhMHqJLbs>V?faYiHS@iP-eYO#dk_7FdC31HIym@-B4zFJQN!w(B@uGjh#bw=Gnuw) z(;Y`|yNgCxG11})Vz@Q%I!9VG(_JD|(#!#DXFBRA;u6WjWt380+dPTo^8(v{JHp0r zcW>jQqVSY4-JDXc&D4@s6-fa9901ssPc+vfI=s0L9ad2wd_8KU{l|z$rVyPUQJn`> g+2*v@+@5U?8kJhRO7?S^Oj)5ym$+6QG>~imR$lc}M z?qwe$8Ar8T#D$m)kOtlcNYJ2h+aO5YqzGX2bCBdyfdXmKqD713gTDso*Psue{rzWl z_NBYy(RH0K6~NW)%mM6-VGJ z@$ralhTs_=Z@Weqq7lExJfkCj9W&!@{?anqTcCm%bHs3?#5%=~#DSgg$KMfy5?*T? zo@twggoclNJh9G(F&~eNO-^Qj7=MIM=-CFcj>IiU(@`mwMG<)j@~?M%Z__th^=<%J`+RJNHb}E9<*F}gV?fO| z6F%4TYXSE~z%oX#SkK_E$yXuh9o7sX4Mxz6I^CwAMw2!~v+a8xYt#O0nk~KXmd@WS zLDv9V5`olsD8DLTWgVsMvjB|8u>IZg-nm{xQth457uXKz89r6+ek~D-2RM9A;;?F+ zU+u|>l;gyP7cu;%9By3)2}S|u4XMGbF$LR8s)5hF$bt}PYnS{^*NE(v?b^|&+O>@hyKNh;yQ`TDyF6g#vi4YGQS$@MvNtUjh+)sPqgV`rz(whh#^O2ciN<2% zF~yvjTy^&b5{y1o8FLS~Ykk2T3fQyS{-Bb;jYcdek?Dbc>naBKACU9-Ys2&StIz=? zbQ2K5uL%Cua?%v|C)sO#L5ZPa_baM_&+pGe#i0pkLt`^Ax>j+8?}HS%2{iPy{_2U$)eZI;O`su+DIu3+>G%P|i7znWe zr;yy?2_HEt^zv?j6mRUe%%2nnL!kOEL#l5t9Zb^q2m8(ZrNZDl`wdp?qnTeOik4Y; z6B4+U>mcpX#!_QPtK!YzbC^;ueR?z8oY|2>hYpV3l(pncl>v}^F zYQb9Lqcm@l6XslnFxE+>1NfD9ZF$9}`+DP30Ozp^I0LNCQDMgNesktlY5hRdQnsi= zlmSaFW%O7)DBUI6%C^iQt(Ul&7)66RA64c#MiotM=UIXdQgo5mGVjSu?m}QwxYzhd zqd?dypZm)v&8p6vnOrvW5BOYa1wqD&wp@lsHbAQmDA)~$LfHiEbub-)1eD=9(H=fnn@Sh{X5Xc8EHK=H*~2G`z$;E1Q}`6-d$lrO{~Znb zJFn2_C5M0j+1L1);pvjIyoy3Dy2YJF`>81FrwXhuvHTLL%w_Dd<@J=8b)jO{6>FtZ zroJE+zMZxKj&=Ni!7Ydk3f2e;_0qrJsF24x#3#E9p1ZfH?_kX$%}@0LLed^?uk8u9 zJ@;*#@6g;7Vp-!;CCCfx`CeuHGJ6*Ir~rZ=2_tyH<9OByC|30UiErPfX_lNn!+(Pg zOQDpZ{SuDj>L8wQB7#Jw-!W`2ao)l*d>nv8yIlmyju1%vIG${$>%t?8l0DJAZ3JF& zSA3cV1*(J~*xw-7r9W{$a3GEd*?Z(5{LQ7W%F*GZk5*QSr)Yoj*_3k-h_K{%&{+ji8pNP4>gLh0zYH_#Pn@XKu1fRJZ%&eN2L9j4Lx`DJZB zn^iW0ohg8~&(+SP+w=cP#r7walw644LlE3>P`G_!r{gYz^OPSt)`i-j&?Hg|Fgnn* z$B(&&w;3b5gUEb$H?oim;?oq4bnzGINZL?-f=<2%XphK~??Yw5&mGaP+?07E?9=u2 zGXK}twOaLXVz?!ra#p+X8D|@lE9H@dV)2wWH z=MJ`L=Dy9|3%)u&rptI0ro2#@vTPnzt)SF;B{-lG*Z6~d=~Gyu3U$E)|Li;S!1p1e z!n&X5oa@jlLu1eP@y4CYUG0& z22Zn_6ys~O=I zIqyL>#4tNZqQjdrBA966VDxX=Xya1h$0!VsOI#n9vGC#;&zfcgNlEYNk40R#Az4uAlRd$uC z`-FsY&_21L1OjOxFA5cxW~%KuvTq%PUt*v9F#AI6laH15iStZVcvE8<9z{inLl$96 zxURoVK8XZa>=A-@c3GN;>*Gb$X!Nm+rADEfhJ$nrvo zA|m6lpkT_USwZLTu!b@h`nUBMR)`Ny-XN>{5$&Y_F*79(<;EPTUmO%E;C^)kIQ=U= z9293EyOF|-$c>V)jEA=NtR#}8r-MMLg4n;K%a{~f#+6uFUcND8Iyp!c7 zXgz;>P5B$|{koE+p!Ud-!N$9@0Qxv*=y4X0J?9ZT`C%taoH~BpW0qgUoRYEwwW4Ce z-(!xr)q{fzJ=%IS6$E)g2(s_FS&>GeB^O~02oE3eft#&F_%uEoQiLBGe(3#TXfMo$ z5aIOWk_fw*2$Q5`A}l@XcT$J9?Fl)ipe^n zAt6ljV%gcKjQ$=+SDm>`6j2rwpVIq6){JthU}m36;}u z!#Nwd*Vv!;sRx z*$FOR@;z*W@5Aq5ReSSkaF>bgW_??&lTvm-y^a4K(^wwCSvbRag>V&hb6KK0YlFxB z!5*9{OF+T){?gENQ||E)(@RhJlf!t*!-!etIKL`cl{?N?4=QB?WLZ*KsaT?7T^!~@ z9wVRZ%YI4koaBYU6_RJ}Y?jn_UMF-iCtMgs62r3oC@KsZuHwAULwIisiVSp^ELD(0 zDyfX9YphBZ>uAIES9ksrVQYrIBA3sTs3Xn7=T9f;#ztatxqU zm4bX^zQh8bLO@zTDT>Dv>l#zg-_w`LSGnLEsq@NmY2CPR?nn89NMWNuM9=n*ske4@ zQR;L%agL8-CtnWv{XIvaQ2jPw!hj;w3PQDRR`zdQ=cf?k@2@5@)c`%cLa@pwRg0)XSmnnMZy=WscU(?Vy_2}J0<+nR zP~Cvc#ET+xka^+!s2@k&I7(Kn7gzbjCWDJk;kN`Ya2ioKZFIVLIUe{vT~$C?{tik# zo5=f15KuJjZDBwyeT@SbZ&Q)q*6DHu^#YVJz#Qx*e0BrVAj!oFop*8N1f=)kj=n?T zJyZ@%BM4}BQK#P_>oUZIzad*vdo$FLJhOCl!2|21AEY-vK$d_}q)B(#Ee3KP<~0k~ zJJjnKKgo7%P%$#1ILuqeOxB7wvD`G3Fo22}0so_emI<(<%?Kre&-Oqw9am?3@=Z`B z5l0*KC+eY%-w&5NbruAEpj)^W;v)P3F#vAuDB&}b@`YK(#4HIvUFoSz;XDO#w?FuMXqV6MtWT#ARUOY7^DPU>O@1Xjb`A zZ1)YI35wXJ5(w)kpElyi*YhTRnC@>(nQ^D9XCev0iUu3f7^JEYVpUGb1mj;pPLZ8r&IaA(UC`wo*% zrNi_M8}>Wh6S|LnVG$TkRPhPk&}=C1!v-+6v)8L5{3Pu+NRGD&L3Hg7ieH{T@lihA zW!+qf5Hct$>O$H9J8cdAfU+>rj3P9VCq$xp6FX{-MhXBhQpb%5um`~`BpX?`{^7;P zK+ox5qg|PvTJq^XqNb;a4~gs+lHoRsHpCiG2u->IhQ%h}zF`;P%2;ys7(We}?C9C% z%(S2`^PPMAM7WCp-j1H-GwHe#?r0=@GDPw^(mx+#SBD_f`^39^)seh6QqrDoqw2sw z4PGGk8>lQuh_AQeF7l^z#ZKI)N!7p}q{nYl$UoByv!p1XWh=wPO*E_}0tNCO)m8$L z=^!hL9lQ$&EXJrhX|9b?TRKBMldGr29|G!=G&6BLWbTI4X?y$#^KK<@VfitsO|X3- ziMUCJWCY|+RtN~WIa$38NZQ;wWle^Bw$w!@3iaJ{&F)l4t88Tbbh(QzveC(cQnJfN z$j7Nuwk^mb>6%l6DsaZ>(MD3tS*LVgzXao|^G`J9ALwzIKKXB07EX-Vl@rk82lRN8 z9{)idzoN&t=@+ArMFX`7h0sY$<_>|(3 z;FB#R!A}(U&QApnQ$=By96L+QG-jhG zN;6KVRmDfMrTK9@Aqfa&GgqA}?SYj5jobEP(x*6*+sHFWImVegekpB#9YW-M3M{!g z626w)fEGceI`k1FF+{lnEsPNTG>S-}b5v_)A*fqrxnsniyc|=#AC!}(D*rUO8APEK z!o$4_&gW&)4!+aMelxs%=Z|~O@%Ei>73-_N1GI+ovB-dW6Xx-s;)_86P$5hPAImI< zG~PA-(la+!udIH8PH=fk7$-`4WpfmhJKrWy^5U$tz~fP@#b>kQO_7&~?!197Dj}RK zVv>e**7(OhiH#PJ9i?bIpPJBR3wNIC>L*xgP>fMg=STZcC!@;< zZlj4R(9tWa&t2<9lrcpVqQLQt0z!!%w>>1xOdO#8CY`&uMh0UeJ(r1}ajWq^kg702 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/mongodb/read.doctree b/mddocs/doctrees/connection/db_connection/mongodb/read.doctree new file mode 100644 index 0000000000000000000000000000000000000000..43ba5a56375efc23c28bcb663d067362a8378814 GIT binary patch literal 26313 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 Sa=qk`GLv$jT)^FW<$nRb0R;>I literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/mongodb/types.doctree b/mddocs/doctrees/connection/db_connection/mongodb/types.doctree new file mode 100644 index 0000000000000000000000000000000000000000..d2e7864672d42e1480add0c3cb498f4358ef41fa GIT binary patch literal 56505 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�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 P1uQOMoRD|YTqyiMwRWXV literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/mssql/index.doctree b/mddocs/doctrees/connection/db_connection/mssql/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..44b8310207ffde8f5623578dcce88a9f205c6754 GIT binary patch literal 4993 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/mssql/read.doctree b/mddocs/doctrees/connection/db_connection/mssql/read.doctree new file mode 100644 index 0000000000000000000000000000000000000000..75075f911a951d92e0672311ed8c661853990e0c GIT binary patch literal 81749 zcmeHw3z!_&Ri^bANi%xevSr7PQyxDwQf7K2Ikto1u`S7pt=Jyfl57hnLw8Si&2*P~ zy2oABlE!(GU}HhyIH8jzgv5yh1Y*Y_3*-ZKLs$}CAp`y5JO zx5IViMyWOD)`Rwsws(Gi`;G09aIjfC?KfJ@lG_e1z#FAXwN`Gr_4X7VZld>tYR%7y z4z~NPX+Nk2__0yXm))6ay-HtIf0cpo-MO6e2ER2w-)IK;N~3Yoe}hw{_4*2j0}_FSL4VVpG|uhz@%nOr59s|~d)Yp2?kK@8ZPaWOy)SP}t9 zZ;T);-yL9hoA15#g&zq+E*vTbL9;sD3cy$5Ta}*1C@>Adk5>vWv($bsr@hVxpVziJ>|Swq?}f&mRc zyK52Exb`SoSL?IROtUfP+;i;MeRn%8pMEG5Za+W|-DaV{MoAs4KJK0&`x0K3+OGX* zn;9RB?gSs*WM$7J$E{Wk+-9+6;1+J^fK!`axd7inxQxdIW5jMi}Mu|$*1=bcmD$>4 zCjCG zd3(4PLN{WyDky4b9@&xV8Z3;X68B77_fa((sXP&`@&lNwwfKa6o9SfFM}hs%W@1m}y%Ey?vPpXCI!gMp zn)HuXJ`}D~n3GI&lJ4-5DUd;$DCL#FhwG%0agtAkt4ZgKhQFUq(JH zzxk9g6<(IwiGluCOad4(dm)$EPC(BoK;IOyy54xn==opFp8FHmf9^wEy_ImYdy?1) z{-)5bhsJ!R+hin}N-zJN4PWZ{e+dZ~#LH3wjw*tR$^Jq4LwNZy?ykFKN6t*nj9Uy^ zO}LMO`qp>UgzjAps-!a{F6)?KJx8@*#%~i7*7eh^TFnjewJN+8!)_@*Wssia;VjG< zkHZU}|A|Mx_DfG-&MP%)t+~1{91|_J-aZ1yoSms~t$1BalXtViM))LJ$=7%EiXH?U zqlbgB9-<-{rvMxg1~V)B;kbIQ1cWXU;N3`A@acq_TrZGnO?x-ttK$3?MX=KqL`9wC z%2UI1aur3tmGKU84HgQKKBvL~_P0T*O00EvQW3nYq}qycPqFu{{hFkrCTXO)R*|Z5 zdah6yl~Xg6ModNUxrDc`tfcn?G{kiJJr<)tMT^_oLV*F23b+b?8MOP6SWD{|$=~$| z7V|2$r{S*EIF47g3FksUc`Jir#m1yKHA$oRRl6KPbK%(3i}QY^5sb;HpU+9&UVxMm z%_EtpkiRTTFNTTu1tM)a&pttqSx_cqUVC7dS`n?NInDc&00AFws~;bh!R7xJgB7J~ zMt98nBzmzK3>H+A!yNUpQ*`GS>JL0(CZlLh&5 zF31)rT9(%fD3IlCNev8f-WY?}pEwVqmwv=~JKFUm&dI|uC3+jgg~*syqUrsA?@hd@ z$-i}J;(ZM-c>h3eMa7vKf2&L5m^f3DG;!V$6K7?*ri($T(yK(v88v1!lln4*`ZXj& z>DKvfdYmlOS7rgE<@$aB2y(qWWuS&=KNy4CpJ*RLFa3yi5$$>s?bH#O(tR;Rf#{i4 zy6OEpyNn4jlZPle==l?%DaCg+!7-%g&*+&cyE@`Q7LB7LL!Q<2 zr_uH=V{N07mlRqPG#2yRy4WG=*eE`NY_ZXHu6E+qK9i2{vgX%Yf9fPr6tOH{qD8bFDUzcL^|K1zHs7V^Zn!*+r z3XOSMC_`$<8S!1$;cFgD{&Tcsb%Z~bD>!-uf!1$~zgRXhCpycq_D1tt3OmJ;vr>!j z;d71xQ=}1C_JmB=ju`ih)B`eIibzry*NR^Z{*T9c=+1wF$<%QTr@hfx2Rb^cKCxUZ z;$u?=$U8^_f@=waZS4>U1J^^=fm2l8bp^}Y&q$F@vb31|2-=zRpM8z0^1;c<)00>( znc&M%hWz#-&5`A)%b;b7kt=o@bcypYn`NZ_uI@sO*u+Lnw4$ejNu5k7Cn^)qra=lX z7qg#R9xG4tb=xtO3NOlP1miVFzNRzgkqUzQWC~s+7%0DONyV=_bf!2rU(LbEyoB9tT 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/mssql/sql.doctree b/mddocs/doctrees/connection/db_connection/mssql/sql.doctree new file mode 100644 index 0000000000000000000000000000000000000000..07de9e856d064be8785dde6d7443fc3148091789 GIT binary patch literal 48558 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/mssql/write.doctree b/mddocs/doctrees/connection/db_connection/mssql/write.doctree new file mode 100644 index 0000000000000000000000000000000000000000..5bd78b5ff0e061be730d0816599413caeed369e3 GIT binary patch literal 57255 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 Qa%GyOghD5au360gf5D@fzyJUM literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/mysql/connection.doctree b/mddocs/doctrees/connection/db_connection/mysql/connection.doctree new file mode 100644 index 0000000000000000000000000000000000000000..601eade80b6c08428e9cb317b988e60b0d6a6a66 GIT binary patch literal 39396 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/mysql/execute.doctree b/mddocs/doctrees/connection/db_connection/mysql/execute.doctree new file mode 100644 index 0000000000000000000000000000000000000000..70f97ab1d1f36ef3ccf4ed3b496289746f63f8e4 GIT binary patch literal 49720 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/mysql/read.doctree b/mddocs/doctrees/connection/db_connection/mysql/read.doctree new file mode 100644 index 0000000000000000000000000000000000000000..98b5b445c41c1de03ef8ad9ebf9fbb8342b77a3f GIT binary patch literal 90065 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/mysql/sql.doctree b/mddocs/doctrees/connection/db_connection/mysql/sql.doctree new file mode 100644 index 0000000000000000000000000000000000000000..a3a08368e01d9d82f0a2dd88f133a7a9eea63809 GIT binary patch literal 48866 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/mysql/write.doctree b/mddocs/doctrees/connection/db_connection/mysql/write.doctree new file mode 100644 index 0000000000000000000000000000000000000000..66116785e0593a3f07750e1c59e3e967b006fe04 GIT binary patch literal 57493 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 W63xzYG*RN#0eF?d0plx5x&IFef@%2x literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/oracle/sql.doctree b/mddocs/doctrees/connection/db_connection/oracle/sql.doctree new file mode 100644 index 0000000000000000000000000000000000000000..944b5037504dae01317e573d1a4f33c160dce7d2 GIT binary patch literal 49041 zcmeHwdypK*c^_W)#NhytAowN+AjLaCcY6RxN~$9P884C$$O8yK1SRUUH@iD`vxD88 z<<2Z{$50F%M>5Yf<+7udB|DT=HkBw;>{7YPmTkHGD9aD2T;+-rMv9e6DxsuUf4Gv0 zB$xH5NcnyJnC_X~?VG&^1i8d29A{^``}_KPb$?%f-TiXkM?Un&o7jKR&YI%;nsnlZhfFN9t}0@Yk}Wv6rEPI2_=edxl(F4wbm>?j#Bxs zTnTcbfm?%SAqdMMUi!6s$(b+L%Jdkw=k`Uvl*?H!2F-fiZ-jZ*_pbymTIH&};7m_j zZWz{s>8Yu5bs-nHWv5aKa%F$2V3!u0skakYr+FC1${+bZRn(>l8)>aW{i3y`^~S+L!^&?)yGaLmHL{rGo3{yl(B z^#gN;>)R!+JxW5W>E_&B?h$vSBb+_91V!bB-6{8wd-&`Yl;0W+ArqrhYD# zx`^zA{OA3KU932h!Sj`AE=*nf(}(_)OCX`D!Z17msC@+0J&u1<(MgDO)tL&wce^na zR_jx&-%}*|Q$@d4bBd&iQ>8*)`^6ib0v{uT&+v*2`>FgV`;*I3R@-L39OnQTEYrh zWM5b^+uK#(979qW}SPW*#3f!j+p_A&_W`7 zzhnfLlWTUP2BPZZpEzG}?7*=?`-%f9hQ4JNi+-~fTGP<8={c?sli^a`3An(zOOl%I zuW%)pjc)JSiY8^R>iD~xqLI1-8(3S&F9MQ|)6HE$!+w-&%>JpN$o&)>sAi)P9ajzR zzt`a=rF%Tu#(+m#gAgWV0DmV58ArFEB+;*fkpHHm`P0oTOiL@ePqQZ7Y)gxcswKZ_ zmuoHev(W&65{X;URstVw$6sf$UMUyLVQZOvCf>DPVfBOwO?8;id`<9u80fybl9oy# zp~4?C^^IEoCDHPrCQ%=if)KraF@(yI{#oU~auyxTf}9fM$vSwDW*n2(8Zc1@4_b;g z6IS3`<#}t#Z;mw_s|dX(eGg!>t@&n++2&vhqh8S|H5*X(Yvs_jPMvyk>eN$DLdONX z06>Cr$(b0&tY!(#)U2Xw*A^TLgR4y#06>HNC_v7+MNlIA^h{#u%E&P|A`Tge0zkEG8t}JMRDA_FN zC_3|{tjaN>DaQq=9CE5~S-yxyFD_xAT9kvABz*dHjJMafdDEus^vCGyZHa5JaR4(! zP39jySF0?s;S^&e*DENGHSg2V%aoAghv}un(J6^(*=)4R+Nw7<$GGtsj9HqbmD;2A zij8L53(ZQ!3GcF9?s@ao5B{Iu`*}>oE}nk!^vO$B4%5%a zE}VOc9>knX(aJkW$g-*4Y&0OMDz)$?QhjmNRO417gxTok4)C>>A567urV^%Q4414n ze=o|91HuZD?L9zP@VNw4xlbU~EO=vhj3xR&Ow`L~EI7dtLVFbZY~?J+2|f~L5=Fqrr@nib;2+!)o%p}pJL^W?dnvgD&45Q z>&=fxB*#yjyWClLL+Xtn?lvl{_4I10r<&xBw6{4M4N5Rt8y`ruVeA#T4jw_xO0C;a zzQM{H+tsN>Rk~5@xS-Z)rt*(yQu)Z-oHc1xo0YI!ufXJ~xrOFpI3*DgiKoBOPp!2@ z;hxM!Y3BUv6k#>y+(u@lQy1hgvZvE$jcKo?+BOoGF^#ZcX4wCpHyaAdq zKP@K%qJ0{J4U>Yw(~EW$;ulChsuybLe4{7c(L~^H;u>rsa2FdocZh&NzEJUtSG3Xb z*S$(6`emqNQx;UUq>Pn^DRI{plxz;=vEg$VmDjIl4kmbdQLDLND!J;&lVIRqfi z@i)9z)CI&d5d1-@lYXUHtsUK8E{V$Ywbrbjmj!~OhYu;FG0SoqC*Wk4j%sfa0G4kL zqp!U9fr;S*LLh#^n;VfgTsNU;UxVfZra0`-Vzn|I)NNWg7_7sibsAUk*f;OfTEcQP zSb^KxMA#o8+xk-IVv!&kb;}EG1^>c4qWKiKCZqKx=`=T5_h>4fFj|9-G$+bj>F*4N zifn|zQ0=_n6#XiEeP|wOh_qL2@0Howwgm{YsBD%QKbft~aSdj+-eE@Ru8U3uAzI5P zzoy|l-z+x}eJC=mjPWxUQpnyCgU*WVO>qr|>?1%{O%h%^Qwu_bKrDYg4r<1p!9mUQ zupkpyXo(Aff#?$NgvHh(l+S{RVH-t~?$G?6Ncoj zfa%sG=@w$6T(>4c1B`fqHWw1*y69kfW|UTN(iwxzvCi8|78Y_4V1&uBAh>gL*IcYh zV&=|f1dK-%9NXPiQsd8Dia*-4dMIvCvb&D~&}zG>+AGT`!DDjTFR)ZxN~2=t5}Cx= z7(BBw$8{_7>`4p>n(oe*D_9YtP!7|5kc=t1MUm2YDXKJ|onjer<0XnC;{(D~HvX|SY#2eGY^j)`1&D4nMhjB>pZQY$f$ouBzR;0Oa3iwl`9PyFn|irXlEleD^*}Rd@{^_SD0!J2rp2I zdi-0N_AmFu95U^1#5I^{e*(I>De7Z(OmoipLPFPt_hwHWk|%9tX@(`jQXINJ0s*=%$s4c5z2o`N6_gZi;rYN_GvwGUgHJwH|ry(fdLq zH%wb&^`(-H;k#&6ky}+6J2qiGDxu|>TQqIOWv#Je$B@1vLYJe-s+!>bFeY5wxws3w zQ5~7hmgE|q6u*YHn!J-}^EJ$Jw1gU>?EqCd;t_aV3T0qOhJn0qVp#URg+Em3|8a(V zH^w>oaF-Mno~WXNFP(E#M1&$&Uz(xK9kxXb0iVvbxys#9DrmBlmJPDvo&f z&>KmBAx3~y@4fF6{~(!|Y_{u!!ui1!a2_O_Il_YkXC+(@8*tU^;IZC#)5d*fHjy0% z?2uwbIQj(-zI8GsBI<_{H<9)f!7re+LM5w4X+yY!F{b7AM}zZNzhk{-B#nmipT)u3+W`La{eWH~x>yj0?T zj3{IwmG^M&&@`Mq1o*9HT?FTIlvOC@9DLzQuZ6U^d@H7|bIarIr{k64+q)tymCR5= zQTQbzg~z=R-=jVxa)V@v0RYB>lDWJW;szw`_d>2`qoJ5~+6+MzZu9B(#_5Y1k>xV? zY;=>Lwf0eK_ZMR%c)KfmZf%%g?T}PWYqYJ1S~5qFu8sE!*WGR3v{qkRGpcHkN>7ub z5oB!Ef(jD0uvF7pCLS$cq*UeI1??Ik7Meg(VU9c~qu;W1@|CuZ>j**TGD1foDU5@=g#^ z((@$6iza#Ic`)gf3PrXV;MiOq33hh5;!|K9@<#T)T7JQa&G(YX`h%^?A?c4>8xgh+ zVALzXhC(yQ0i2sFBE`LO66ttzbNkY^=bvmJDLQ$776xis_g_-tE#XLvoymu zwTqF4*#Af&X3|PN$?eph+MDpsH3}%{H0@FFh+MN8XxQxS2^&g95|RRJ_G#^; zvFSc)@lh%^*>|GJH<<8s*9BDZ{v&BlT#FJ{)V$jJDnbP@ul_JwA_mV&h%+&?X&uj0 zh-c0xMV3OtS;)tk3Z1hQ+A|b7W+(8?85e0x;SzInKa24p3qocsL_g-e;6ppO^biEh zxV|4RhC9iN-KY0TNuyV1$V_z4oQnq+N$NOK15J=_f zA(lFh+<7>i6zq5|-Ly5{Q}`ptXV@x+O3`n=De*unI=8mAI`q4LCT!WnIS~t%mPTgN zdf#dz$+-6&?On7ljd2Gk6s7fkpw~L0qP}oiE2Un7y?v~PW|6BY%kB-rf=WKpP$H(| zi9w~As+6?J-X1j3EvQTIdJgKAV|YJaI&nm&<*u}NOVYM~P#(|E#Xavd4H);10)lr8 ze-dtZ(y)lwrXr%ZOs%pwY*Mmz@NvejJcoFnMn{pSHiY#C5&dOhS|qbAP4lQOi`c^6U>{mf z6#5TF^fOS%`#4&VM2a@QCLyrJjaTx{CFmPyS)z@+BMtIC{4GZ zDzrK&O-EyCI;!zt%nZdOO#(_5+wrg&ibUfvt$x`T;pPPl$kMvx^NMNnr8j7`|}hH~2CR3S+&@sqmC6>6Bo$EcV&XMjO?CrQH)Se|L~0&WMOGRpi1Sh!OBU^jpaeGV z!SFqri2JgXkYRAizUr4tiQKqy5H#h92ip4F^@2DXT?a^t=`Z%mbnhC~U?+rli+EgN z<0I{NmsMy{PU9v{#G!>{?A&)6+H#!GEbsH0wna~hO+A#BAx{8_6^tt$+cknDO18ay zZZ7@`CCXE#%+dW=8(DC$+mQ`JEXTF2Rc&1QNQyFAjw`l~#my&x%K>grr_Wm3A5XR2 z-5isuQIL(WfdD4N-w_@SuUs2<+ zoY2fc58yrfI(*{EN$U(H{)^RX5t5)&62{70z{c znjNzMBXL+7UL?JqIR(p1=gu6aWtt-o539|$e6ImpW3zaICQQh&qbPZN<`nj0S&v#r z-j-vu9QbiC zjz=cm6b;0IL_xk(zKWxHb|DCb%vqRtY?ZB1;u+2l#O8^Te+^st`mu|mhHR8&k*47N zSF{`rxpt7}Cy2cgZ7XA9(V9Wb-*v9yv=%(>j;9)V7GmM2(+vcr$!cNp!ImBEV3qZc zBL%;-q`jc=>K76N70&R{CN$pTa22F~U|u&6kyhjSL(GwHWabEADApF_JWGwLZ#7Y$ zYF@yEi#jA69vRzC4&dWF)-@Ez+p)J&W)TUQb9e=iFnSLrBPc6^x5UG(A%o@*JvWT*9!;P)uP+ z4yDr)270PFYQ7PK9_rJVFj6t?4oesWWwjCQ9w_uPM)Zv;DWD{S&YhPqe$fnNow)r8 zGn7?vI~|1nO(Tq~>>grsC|bZ`haKZ^T((lOHhw2(4mv6;7{{*K-Rla*Um1yG6sEsK z)r>0`e`|(f5+(s9%L>NdnW313i9_kIfw zy9L!&T)B8`rdcguXF6i5o?k%p0|!@9=p)X0Mwkk(kgtqA<2>vo=GAm8C?YIu=|ToR zqV;M{@&8WrQ5Vzh{0Al2%EWuub7dkuIDQ8R z!^3Cyn5L;zQ#gK7UQCXkw+TXJ_V~O$6!DG&w%1QjC+KM)ej3EnvNy#34zs`8+20ZN zcL)1BN`DEnjq%K$VFA2+fewyZSGVk4Yjn#ryw<}fyQ|G7O90qlhwM3$J}rA7lS5Wa zRZ42Qalj^cCQ0u&=eQihi#~B9I%Elkb@0ZvCkk*^Eb&em(Z#r{Yxctw)x2xe>U#QN zKW9YK=!bm>RWtfwA2UNSO{O@MmHn`PYXqTBTCSfT)&qq;XGGtqi2_O%KkN%;D5j}% z4}JNP8Oo}-o%X}NW`vQI-Rta!aT=+qZh8rJ1)JDayLgzt4pFmy%Vt9pvAvJxi^;=p92 zH(*~ro0?Mk60MKNOW5>@;{iNm}a^hiW>RVeyikS<<=oSK1lgbin2bhOpLu> zqy!7oeyg=CR`x)lTaD-&HBmsx;v~Pt48`Qv^w5_F%}`dw?X;78+z2BpyVu!C<}~U) zt?B5|WYF$jo#eccI7VT54plQc$yGBHlQ0P=S)Am5W`<%CCJv>8lT07h;3Q|@m~w;J z=+Mp|Iv8%drri z*KdjOWko0XeHWdOpT*BFx=i}V5_N7n>J^O!!9k^?miW_N&ppW0a20sey& zsHDhcbAW%B7_ma##WeNhP}FomdCkdFO5Se-k=2b_-&0C@piskzzEM>L zlq_EJ%VsDhN2iCr{Ja^;s<@r@nqM`-$ja_@_L@13l2ZX4m5we>2JP}hv+M>woquwQPO@hgsvd}Qif>>HmDCP{6S}H( zms+t>8Wq-aUANmTj<3WH2;|`Ms`KfOyc>*WJObNrQCP*nb(EoEJro)Df|Xxj$H3tX zm68*4WHnV4sc!F;RNie=gC9H6u`BWr@pyNadZECMtrjeuX|BK~PN#@|rjyyjF|zW$ z-V>)EKQj~Gmdnrm!3o~D`Lk3;o_+jf%%;2MV&Y>J-rAqX<)+{OHb&!&n=nsV`1_BB zjixj92&;E`=F}s@03;6{xgl`j^ajNq2Dwdf3Uz1es*ZXUA~BTY%?2goKfXd=lJgyr zj4wq7F&BaCK9i$KW?UrWOWVCJF_-rPcOG-e(-Vuiu%{a!=JG7dX2RC}szW!Htmiqf zPp&c4lE7>|A}sf`MOacGHaM`d2=40xRHnen%c)AqQO!8PCU_=!PwI@=BOS)@qF=ia z11l+(b%>|zNEP8>l+;@_D19XE@S3A1igsR$S~W&butwg?2I)^3(KJR+UPIN4(UadX zLovBP97?C?Niyf)^F|Q5b8-E04tk)_myPHf^-(~{5u~HrNd?@M0M@v?k z8}T5?0=n8M^B^_2%GijD;#zD(j)`y>o)?y9#n?y*_uYLsy$ z7ET_lRIF<@E{daUeH+Km!K-6_G@NST?&XHx>ok1aw}jO+lj-Od_WW?e<>lv{~&69Ybq9Slg1S)Vlq6#FH9Gn0K9^f{1!Gbt+!2%T%!+~FQ z#5X$3uV&#Ou;LXKy^@XwHoLQU7sCPj*n^OExZ^EKBL)J0o8Jjmu*0GoGw4T-hvMB! zz4Ffc0M+0j6h-8X+PoeCn927~nj+yZOSIrG3uJFK`45`EEcJO^{N>kxJNK7ANl(mQ zW=}VOzkF%lfj|ayW!!rD%1^A(SB|k-4?p>iHa}TweuI7FKO`mTb=&%%rz#~!VB<83 z;F)ARsVBlm{{0wU^zj?fN7mTY!9O0-K6Bq#d!r28SK>~t*)LY~^8S`u?cf)0B`1;J zjga(@;(&7E!jr!@B4_l5zk#Y5z2Wbfp_rz$9Lmbx@ZgT5yeCCBt9#U*OR$yxhXVgdQc!6r zebVjadT>p(;r%u=VB?(+(}2Srk2!R+ljEUTF6ngGD5^Of(%``9_^eRp6`hW^Jw}7P zHDfjWYvLGkOiRTwY_Zu0Xj?Fy*i@tCSj<}+be2s#OI>Ys#Z@=O)$Ue7ItK4vPsczT zAh=WD2z(Mw!8gl)Ba{ zrM#=sCJ6>)1hyWA8`CLK!8u8PbJ53~Q%l_My@ zyisfm$0^CY9zXCNereTSqDzBS8#uO0{ooaYiD5b(-?{3bHoq$!P0cf(DZn+uqWzrp z7-Y{`v}v(;Lfn9E;qc|*l0bQGF6Q3R!-wYPa5TSLhA^WOb|Jt;f*~%YhwF=b)dRYw z8ojvWHUsOLGj#XPe9@o~r zAIL^uxvHAAxRQ$H)k*~&Dg8lIP6+y+{Zn8wA9XQ?e59>6>`;LpvDj++H$IwTo_6!c zuC#PZ5gE~iUTf;>C#O&B|5!Pn0o6_?nv&?w1q8DFz)qAxrkvS zX@C*TlvHFJlT;Sby0ka$emX|*j_wFbj+%-nihte6gVisr9?Nl6Zeaq#PCEBq{3s3BB!O!bLaBGlFw(LAFYF&6A*FPP{>xJGYFkA`&Te zM`_f4Q7K`={0W`OVYF4lXj^nU3^GstV;(@!Ddh`G`5NZrtq-)kr$9*eR1n?Tu~fN)GVVDP z&^sovm2hz}x8H0OotAq(+9S;Y?SsSR=nD<(i9;jfQD5kXcBOT8Q#35g;2M^*o0>$( z&Cw|Q(gs#e@}jMoXhgoL;DjsO!7_-HMmpXqXhpl>EjGh)CCGuYbZtio7rQ)T!)*)V ztKe|cOEauD!`6)Z%uF=2!0wI0L04#L2-(FB*4e6c{Mg>`{Sc_)Ag#q#w0R1r)N>T_ zfO`c6upC6a%QqVp{Mr%v#eBgoUZJl*r4KmcPLb%Qc{BsJ#L+&!679f(Uq}Z4FOnmP z%V|dGo~SzAO+!n30UErMzj1i-Y>+2UCB8NW@I-}*ja%la{7A#8`;9PPfv*K}Zi$9n zWPZw@ebH`bQS3f!2I_5J$tg4!P<`~8-5|FY`x7CZRlFGjJBkiBah*)RKXp55CinBh-3bDa>{G1^s!c2(cP&psV2Oa zAdb-}Z&%?UYu}apkg6Q*qCQ|K3S5w=f<() zJJ}m1?8JTbsvbDalAm9!l0fZ(=J|nxO{1zNlM*ECUM&Y@F0wn3!^`|#bVOh|AXNow zH-gRtP!4|KHl#)r!<{-6bPfh=FFgSWGJ-?WO{Yc!b_p!s-mtI5KhJKCc9XtAa%u|@ zM7F@(dTQ4gPHlGT$RHL*gh?X3p=5ui8qsZT7}kU7sVTe;a`mOq^=mo5u>g&_g-S96 zphS5rZ-YG{*fEIrlW#fn5a>DDm@lT9r#Gtk2W0cKHGo9+iqpuIop7GPfI*mY!)gV< zhACgXVBk79Z*4icrHE2j@=nz*SMqkL zM0S^9y_;Gljt5R{~IYrfu)1U8S z2I+kVf11${0b#c{(RjqbyuzR?GYHG9^<~!bGHZ32wYbb$TV^f2(sb`ZU5tnq8EW_+ z!)uGei=G%>n`69git(DICx#czJiJqc+;{1TF!a7jf9Nh`uS|dFUICBptYPO{dUR?s zJH*4Iqe$2$B#*XNu>>2BQa4$&+oOeyX0$_>c+M91WXwE}mhm@a*kn1*Z2b){2h#x! zsTTZ7tVFEnp47{@^iZP3AqkaLx+&2de>F%0af?A?f%aFZS=fG_kP{++D5qID;l#m1`36MMl+8d>Ao7~Nj&jDcH2TD- zzggT~7i~K;^Vm5WdgB=w*$Ag~wvQ&^U*+vZ+hZ~?Mc=w%)UJ(c(q;=d7}4Guh&)gqcnkOKHolIc@1us#>s$ zmD(TIHs4vhz1Hotm(7!Qsaj51HD?h}q%zrjx@;9|6L51S(pR#1JHaYA*RD?4m23qr zmWs)=HJvSH@vEpW)9So7kuYwvtFyDEawVB5m5$rD8QFq4W9`^sWGa-;|l2vHC9AwzZd)gRi7y?^M2Obr|rsqhgk4tct9nRJ1DjMA^ zl8UTi-_}I`_QVx^qTDY%Oz+vy^LD=e1?K zlAJA<@}-&UYEDZ&TeNBi&T^K{n$Rr3%-ZUdnb`nBWiEmiGvNO&`2Xqf|D_<*QsA8M zdKKq2rY6*?nM7uFW?QDy_c`lK{wSpD$n<5lX8I4D1@udt_M|YN%#!L9Xwy=vma2-Z z0sT*w%4RBW^;YI)Eqe!5r$C8XL85HB=5(rs2$#Fy$*uV0%mDn~=L~{c7py)T+TJYp zRSL6xMD{*Z|GreIShP}TjeY5cuI;_f7=xBNZeV>z!JM7V7H7D@w2WJ)(78Ao@|V_#_W{w4QqhJp{lmV^!I~Z0 z;xLepQS!`9fJ_UKqor7?xHugj&sav;va9)uZA@AD(n$jp%Q#`?vuR_xRA%ghlSo|A zV}Ra*5OYR4I}L8ZC>4zo_!CZa!YbPUsGZ;fwu9*AOxc{xsJP{cZ<%lw=Ft+Y+2~*u zL2(hJHUKTBLpH;RmvV#w*-_d-WLmja9N`z7^Tx61(^iFQka4MzGND)CYA_K(AZ2-3 zbXl*3q{*FnXP2tM;TOSwgld+IHM@KoiGOSd80IIl}$7;8#Tpb zWr=>l9Z;@=cl1uJ9|kvV`mK*`p9SU^`8h zO9g{EH;$k0J;~IEql1Z>`;el!t6iG=jPVR;BK(&DAI1DD^V{I2m_pe`1*ljbGU3E3 zBYFO8be=hoYdP=0bGx&As+!MRl_Xk?qJG>tKwO1)lAxaj<+fAa3zAqS*c`KmY|(gL z)hf?14{dZ$PL2%@4elOi1pPhi%eEdP0X^y7(UF5j#hl7p_;Ye{vjI^cuMA@g;=vhs z*d;MnE&M~~d$d!Va5idrQ7q;MDqxv632)OElV7e=&xj$V!p1+TZ0HU52gGo@B7h`S z!zF1n8(Rme@m*b~@@>|sAn`X*4B+iM0>^vzLTnE0sJh$9Nu?c)Zj9|WgU|la%kBmj zE`tuZlqwabvPI&Fjc(hrXg|N1bnislL|?#EC1LEJHi{)zK`^}OQni@wG0JAvwhZf3 z%9=&z?p2}OG8j>DEjrtWaoJ$Gb|fXy8VzX_)j7vsKCU5a>xEb9UG1q?s|$n^q7JPD zL8Yz`9#^`7=Dc-C^p$$L8s}-@n@}itU6Vx9@%5Q)xmFM`s+t46kDA}G8??A$$=HPU zH`E1^h{K+>QrYQjiYcl=hH1p9NzCm88@>v!=Im=y#k_@^R-eRuPb~G|-Of3H`?F%; z4)h%N;D&XU((bF~EZ#y!R^L#Z6X~FosYvLTMgY6yr~av4#A z3?j}4_ND5@3PIS&C{`KC`JCV*$^en8&;^PF!1<%R)8WC2?1e7d2f+ljIAtUNF^cFv z62`Iqi6n(yK#+5gFEUn!m_9-siqm|RMpXSI zP66{l{ZeIlN+UCjH?~8X2vP4Fawvh*2E(LM{)CmT9ayZ| z4mT41L^U+O5DLEn3P*LRNdIysBDAAyw!}?zu!Ug(4Ia0sf#T5qgZsygtq>;=KU>$G ze+cVjt=Gn@QegcEduFDut`EOh^F(f=7liPoJn2)OC&>o%!x4sNIphtnm~fVH;iR>A z`Ia8duQqNi7eTELky^u_&f__x_idHpxOv=yadCc*^<}m(3t9p>Jwvr9qkC$O*c?Rq zCWKwtB9PeN|4vw&A>QZX1rnO^P7!1N-T7?lc&1c^B7Sjx?uUpa=6ENZCz=!41pg4L zL3tBg43K_5MiU^tZxg&@%a;1^bjud76W)zSDenZ*=lxeY{RE1X;BD`O7mb>0? zQ$$A~_^S)=LwJl8-fwE0CxIDb)Sk-y4v^y=v9rpfX1pDjE6S&!enlKk>KgFSf+(@V zo)_@kYgJiWq`HGLZBxqa3ls}`>1{#X_vN+R+u;GZ2jN5MmXV9xJK!UFhipLe{0eIc znQ*}loV$Lc_w_q(s<%Jyke!a=U2f=ZZx-Ut`hU%Ee#X;8%5XlG zEfqcW=7+XsI|Az#=ymkZyBvXk`!5G~Hx2+ErxpoW9b zl^yAQmHjo4N(#Ukrjja<>-5}ZiL4$>K*3}@nK3u>WYPqu9bjHz%Hru>3(`?y@WI0$ zPJ_)^v5GiMWDRLz{8FT^da zHazOC_@@wt=jERsZ=Bt$Lwb%-y9*J^9k#Enw@@BgtaO{MQf2#+7(*K#yo)xtvN0E+ zJU^tRmN8Gkg;)X(K`D7b9L{m#T&m7Yv8>F(#zf= z_!+t9f)~ssaYO;52A*TbG#*bVgI%>dZ9Lzpj_~g$li_9_*6YBOcZvCU?$XH&Fk7QB zIk{(W@4%sCKOQ8=iUYuET_9b!^N7&-2HgVoqxC%oj z;(n72bMR(WbEYR?$JRAjE(Vu zT|zJS}R`-b#Etihx5Q&8R>mnx%&vtizs6k=##0%K;n!v zGtK(etqHpT_a1gY*)bVEFyVn`l0~w|Dh?@7qg7l3i5nJ<)J%K)w-t=bh>H4a;?U^RkmbTeDK zX<&4G|M>oqVPn_LXke3*`ypyLieIftYICAvtc0r_DzLZ=Yc5;)IW%3!oUo;Syq#$? zvqj&SEt^J6I%g5WRJ*{K zbJI#NSJj#ZMpa0KQz|FMr(Igu-z%iseCDQTxI1IBw=4Rb{czj z5g%3VCYw6t(zBg(n9)Nn)2V@-U9;6GSo4!0KCg@3Dv<@+_D^jddzI!T5( z_RcQYm1Y&wU7LG4Hh0ixC!nY6PET*Sa!uDI(IypO6$-rh^P_*2qWa~YP zHD=f{K4)pxPNuUbYNwsmu$4GFQ-mFc1=uHrJ2qioak(^`E}bmGGhmBU5mp7Baau6e ztL1(G4|3We{g$MA3Qs#NS!nm#Fl<%LSSMhi4}4uCv^%NnUtMOWdka&&+iRw?f)rM7 zc21SjbIJu2>RnQ(>)Zw;hHTE>0lGH~5W)?Ao&btWCZT|3=mapx+OlQW;NFqZLF33M zxc1?F#-4%kfh}9;ejIeoiFoxXj_GH}trDBNbM5B_Jee!-**<(JmH*OHd8qT{S$E`! z_}|4ll&ZXZM)P3%T@8?XH&o4YK+J9B{#>~h$6c*%bpsbJ8*^7Hu#j+9xrdc!AMJ*u z7Ii=g>y!ms-Yx2%y1KfELvn?o(B+qL=`8h22Bm}@>IQ5t9X@n$*Wf61Rd{I%Qyi8J zrc*J-_a7V_8y`4$Q}>nNm+&tB(J98m1Eafd7#Q8wy>08(t((bB`UxDM4`WX3Dda%^ zfEn2a8nM$69{wWG!bUxO?h(-0+&A#!QTPaQ=Lf;?P}v)H=hS64(UIpK19ltm;RjqU zM42FYK87|l1Tfqq+-v!v0OUjnX4BA9^})#yIF~d9D~|i#hsx;qNi4;fP`u@Ck8{eb;cm|{7o1n#IdrNXOJVMShq++S z>9o-80q&aQ=?f!G2Ab^3fm0cBh#&Mpy9H{aM$;RW!o&|`a60Mex-KcsuG7(&%*_iB za?e3tVWxAJ(;;?l;D!h(#)PxVt3NXA_Nq#>j&;&y6|#)8fr$YLb}C9QNwD`qOjtgy zwZ`949RoD?8tIuJkHE~TFAfE)Zn zW)fD7(bHuhM8o=RuJv`-CDf-QD?ajr^93&zCCGe1Dx9T`QGTk7;6}Y98F_=KHcI{< z8F{TJK^U0^Mr1TwMo&07#Evpx-iUzG9IL?z^xkmVVxf?3d8U9Fs%26{x#-~K}F73Gd!XPZ{ZaYTbEZnyfE$S@r>(mQ-5!1T1*=f zbCdQ>?qe>CcB#x9g>6eu`3dA7J!LKj--w4~BEg#YgwiHnC6ewYEW@5r?8Idsm)`(q zJ&ARL)41oHoPULGTbVs`UPLc@v_d8FwKh%%!JNebl?`$(ngM_a#B7Hw|6Ay?3a8>opK3QPmNyDfiDPJ()ug zen4D<@M(pMcSQsKEi!66M)6cd&GA-lj!!%o!fJmCqPVQ~c4@UwJX_a!jJpF{wJthX zfmczdJmG+}<=>%HZd;~;MzhsC==Wm;m~eaG^CU?BW2O@TrkoACi=7Dpyb@h{GSt7n zzaR8}+jh|Zy6Ko#J+=5Ft=}z&Be{!~QUgU}93w}1F(egZG>}HgQ3W@dFh+M()EJ-t zBp4%a8exqaKo*xZzTio*M$T8AITC~GMe|dF^%dkQ+9SKcqH}4EeJGaO9I2$y%&`FG zbsj+`xNWg|ZjMMV%`x~H{&RC^%silVjV0s&%2uRzUv&>myb(GE#vy}-6~l1yB}|dH zhYX&pvhThnvf?8+7^fc*B}kkOlNT^2(n{A&$(^h;_X&5FR%Xhj>Z~$NB4=}!!8|>l z3sQG8@B}P4xU-Z$nT4}3YX`c3qg)xe*_JDjmd)Al49KpwUp%I}ucoj((r5M_+V<31aj z>p{|7p@x`y8`i!p_f|rhdprE&X;~41IqI=JjC1vpI zHz+dJeeX#bzSXb{k2Y5Z|Fup`2>+W>ik~+u#n0kO5$wCs#O_B%U{hIyTJNQ#)YlMe z#JRaT)9TK>1qj{B+O1LG$w@k;GC4_uS8)(q?!sljA6EzZx}K1R3ks{D_Q2kkBM^zd)XN5UIGvqK@q8oxiHAZkfI7K&&D#bRP3N$OcyLS>gnUa5=%!) z5|Jo6^1Rq!6!gNmz{u(8pRA=~VA1B0lO7(R(HruLG)Byj>~xJx@8A*}yZou8_G&zEhJP zsd=UUnOw+)*asV`FfR1IP9Tww_U7nte9qE9!RQ4g_?lZdR4ATAXf%?C)%|;iMh0M` zzjqA$w06<2v=S1f2{+)qSpCO&P$FuGayw}EdObaFpS6TET)X28{&%2okq7_RB6Tb% zuGWhtR28APLPPJg6=0GXlCXB|z!F9EqTmuS6L3sI5}pZ|h>gb08tFhF7aE5(^iKd9 zw?#$c1+me%RU@4jjguPsa5U!US;8%@lR1{|VV~agZ4$vY;#+`9aW4Q7?xuv^xQ! z&+F+qM4yd7IzNck!LVrL5N@!&2@VU`KL$Gh>*4w}VnX3s?WIy(`2=(? z2IzjFr{~c9JOZf>I!%Tjb`|d0KRhscb26Z(-`F8=>r^Qf%r+rOEX*#35>Y$+mjY%R z^zJKm#Zc)s^K6p(65bq=+T*^?DJ6nxM$n>gnT%tY)G5 zufg%mso3Ht_2Rh1e~yM;?YkB$aZQxwkA}sIB4Y89*jT(!Bb(YE zp(@<2p%2Gme*L({eXJ4ggZk8Zzxyr;;|k_KD5;`O?}-iJJ2jA~5WXBBe7l~WThjxC zG~9uJ*W6Qr-LY1X2l)a@L_J8SN~s9_B_xUELO!n-Lf8-r{aHP|Tj&K<6^Qx94h{?r z?H~3TPySNk)yAn5tR9bz)i3n23#`rnRzKI%bF6+wNW&H6Y2HjNPvzGe$ol0%RnBpz zXn7e0h;xxRmeQOJC8Bn<2*hd)eUP(>f{N;Rxp4J>TqyyOoe(Eiu(&!l7ME*eQ@sXa z(XXLjcr1=Y#Ny`ISRB&G=EY)ELm!UC{5(szt-%?F;8O>}5H6uVqosQ zp^;tnE&I<>*cPNvg#2l2As^E-?Gp0Gditga+0iLl)GK5s-ip8roB5LaLYl+4v3ZG7M$1vuMc-` zf$(0I*u5xLqu?HhJ46Yx2jU(u)@}6nfC7u)Pj{Of;g=nx5XYnl>(E zO>e5hF8%^ZzugNgAo05;72>m^dE;zscs(e_r%Bs{S~c&aK1G|cIU5_k*D%N5X)edU zn->`9zV{^Rzie2B|JPg@{P!*}A^dMjDLPlg3558j7}TA1D5(jE+`1!uw%@`DHir4D z2-vWV4a~8ufpEvWdm;9Y1~|PJP7oW!Ga=LF96c3XCf(x@xKL0jdyqu}>kkI11ui9{N=5dHzbl^%{Hj!4oNjz>u4p-gb)j)LPw z0E%+X7^+z)h%i`n({ltn)yoQeFZM_p$~l4W4n2HG(inWV{SSlhL64+*_#V*1ha`=` zcYj>?LLCHO?$twTjUSi5Iw+xaTFr%bbtuGKZQI2)MQ0!N;G+lqYea%jlSh*Hprb$j zGO0Y232yRVAf!(f*ZE%_kwZlww*NTcKNLqKX$;5zAe9F*p=N~DrDsts8hiY>nlpq7%;BE*>8pHBwr1D@Up3+$E^N1ZR2FG%@9!n%?49jQD3(NU=*lyFjnFRHkRcV`jgS9*U??RG{y02m ztEcl3NZEKHuu}_%+~r8N6UYnj#@B2l+>xI5Yzpq%)#Zo}cyQ9={$?UHcghMc5_}|y z5B{40{5M5Fl!5=o2&7E}|4Iga$dJON^hoam{*@~DU-RIk2meb%Xb!%@iv%A@;)DMd z0RR6+K$L<1xd@~>@HK05U=NG*71t2-i)Jx!Jm&%*$$h}TMg@P-DmPAg@PEs}UjeW3 zRalYOBSn1d9{}wCGXkIt`(F{#aKEtwzzZJ`_jGL8B3?*q)TzJ(n+{%;Eq<<0zC^FXhKSXLxlOj=*oAmvjD#LOAZ*UEuf0WtiP5OEwTzJ*4 zCJn86zDXC@yexWY9rSE_i->j$ST6$>& zQe9jg4rrGGC`EHOnAsmegs9nHBrx?YtH63Q7K#PyA-(JZ);|MSNA>g^){zLL z^Mmyg1y-SMT4Y~{AW>vD3S@(dE8xBe%f$ls1$qPoxbFsVpRcFq;GT*=IzMn>IXkYN z9X$+sdh_7Gs4+fb92^-QzoEX1dIusy5k1Ghi~{4IA#p4i->#Qc!1!K(@c}(Ohw=Uh zq|JkI&%n)y;uyT$cz91ej9)`!yfC`TC@?;P#Ia!fie6R$Vh%6 z_hO42=^B7wQoluxua2<9fGu)#FPy?fj$eis!18hD(X(Lb!Egy)3R=mS6}GfdyzAA# zms8SHmop};a!hBZ;mu}nyeWIRR2g0XXW2VCI-b$%{`3BJ-(|m|5c$*iUsWEWC+N71 zzvc&*#=+~dZZi&o-1Mbc{k>b ze;WU*%0u)7fb!%dyavgdoTND5@7~L=>fhG8wIBZDm)%$+(KYa=qk&gih;3@JdU($n zR_$NAs2Vuf?)P{=S>izAxO@Brq@~82?syswbKv1smoE~86LD|^4bqDz`0oc$# zMnq61P#D{`@3`U`_%DIi$RDGrOBRyqp&Ev=w{~sSNbo-qWqiqQu~dP#_2%)#B*azN zM#?N2Qx?2(7hb(PU50n}vPIDFYGvX%6CKLydUlW(@NF?B84;RBk0F|l_}gr;VwItC z+x>bcd7?Zlv`yu$UVPygeGx~wl7$O)Uml z;nfw>^Q%ccXMTP)t#6ui#zNNAj8)vrxeJ5Hi0iTInpDYDLn`@UP{~Q6A(3*G6I7W( z*KN0p=#6u%T#@|V+AzN-o6Ro|BqhJ%isbh-4fFe|=JKna?$1VV>LtnXgAH^1r_JS9 zX@ADBaz*m{iH7<8ShM*R?JxKhS0ulWHq7tW4n8>MS7tHDU@Ww61){FnecnPa3Y!TQE+O%)zkB2!haId@PsOy=HnVHQCA@+ zO*Tqdmn(#d$eUt|d@+=Wnt?`Tkn5ZgCIe1n2enntOrwIJk*aH8FUI3TC_v-200!Qi@enmZ8?2SyY{$7k* z>-FRF60UYZrl9qO*l2xDFTFtPqd+^Oqc}=O~Y=O&#p+yx~I^lrT;@i0b=Rt-GIdCdcsNB^cx+N1e5aV zx1~7dVP}k-7a`1N({D8MmXVpoD;vuqQ&%_PG+JB^An%LSraX)INpNILmUd@V+JYBe zXDiuKF^TW-u6rRHysB-ThbEb=SOtafC@z1M@3!C3@f6C6KZzM}@n~0``bo)#w>vE` z6QQ=5vs`H~u92>5Qq4XbN6m77iPU}MXpe_M(>_4F0%%c()FMT9*o#I(USNLusWQVt zQ4;2#`zi_wp_}@;WPS}+rEZHqF{HTbMKZl^XQA0Z{jarZq{^^c$@OqY2)wa zYZRQZ1(^Gy?ZTHgI{i!v%oxd8GxwxfFHCpAaXQ5c9Qjl#%60e7Hd+{tU5GrJCr|j1 zuUq~x$_-mGcja>MQt|_f!44=C!JQM5a}WL3K>uX|U1(BWylxrkzd)guZ$3ZuWK9Kn z?PT$~dOP9Qz)m)V%~&cHu@tL`6y0-+2(Ovy{$Z~B&7Bby7O(`khTSgG0&bQT(A-CQ zUlqQARNsr5j8nFODo^Q%#*=j}Uel<1m-u=V$*tT}%4o{{X;9YR$53vhuT$<9G39=; zyH*(u=K6iqoL>Vq-$>OwgkeS3uZ*qh5vr#Of2?T=?-GSfzYDG3?$!F|Hc{*ApX*iq zja2n3d>u`w{!?PPawD1%4AvU~6*`&`dAfeyQ{Lq+LWd#YPx%t8ab%2r5T3#pL3)pP z{22?k_z{ny@r~-N0V{Zvt(=D3BiKDT>9<;Hb5F-qwPH|{g*Dq;k;cK1Vrt)DPl1I! z=w9JAdy0CFPUvGMCpRZLhUNBB<0NeO#??o#N3&w2t9UjnU5QjG8z;*!;xbCJR++>r zcEF*Nm-9H@wdwHA$i4*IbP&;o)BLFci(uFJ>Hj8p366LS=9P^}t$6agJm@Q@c)^>i z{B(~u;@bL~O1-c1)En+ba0euELiXLRaA~(vX9%ysnaT+#BrW-l#<}c|ZDWxo@c_${ zYaof|j#A@C8<*tT`B=Kbg131>-KH2zd`?)iq|Y6N-DS!trI*VwOl}Ns1)fs+S;YhU z>^*i?LS~2@A6j=zskjsIoKiRzH0+$xPI@S|`;=1mMVY|(ajeF}GfICXN{~dx7Z-fR z-G%6U*^0Yg!NcK!4X(Ib+q9b0b#n9L>706=nz6dg?;uh(ndWy(D#VucQOAoU4~4#{&&>d|h)n_C1G`vF>|M%5bz{84fj92LEG7 znGpUrr4*%xrO3yXLV9eUV-5JKt&I_CWvjNB5jShq*6teyeBNg9M#P8CXtyAR(u3U_ z3pxy1UauEUnCI7k{vJI&H_y8XX}Ed9!4&Gt7kH!~XJa7U=R=1GiqIcJl1O*+a2 zvk^$=2NJx!jvxSM1JvVmKVm^~YU5N2RBy%Gv7q`By*vV{Zv#~K>ghRDZ-_uTKd4}F z^5KC|YFj>FeIC)EV0B131+Fh3K`gjFrbw2{= zo~5Vf(Cv&sIzQ;ZP=}==`FOS>;=v46rBt+S7D-}3meTB(OLDIXS6E-=v!Qqz{XSNEW19W9q6A5^|8&9A z>`%k=2TQZBKCfA6_W9MMrrGCLlbU8{H9e_m_C0`bbO*p!zx^91#sF!Xa2sJ+tm|kq zhHfcWbLz}nsh6Y*A8VN7qs`^mH%uqvNSt0y{4quJ0>Sb2afm-=s$=E(XMwXrBPzIhho8pS?5333n=>De+T;a z>gjpk`z}JNO(W87g|D%p>mX#R{SlOi1+FTkBJ`t35-Z^NnqCN(&|lWjt7+J?lvH~Z z1RxIG{L7C_egF9ti3)1^DK#38>dn={VksrCKw3IfVQ8B|wzc({qT-2&6iQH1FPkQQfY+`-gmv z@V*<7V52%hq9{@g@5Mst)p}6`N{<6duhi3XlwLtd!+RFj+1yMj*EJb*yf2n)JYYqh z-`xzJ`5qR(FoZ?B!2(Av9K%C}ld>@|4R?Y`{l>s6G`$LTooo#39wU0jz*A&+T0aJ+ z*CURBVb~cu28Ja7)f78Xm9Ngk*?xIZYS3?DX%LQq9~ULa82G;yd<=XEg3HFh%b$jQ z<2|34T15VuvF40c*=SAbV0eBtse@rw(~~+Fz8;{C?l-w_`5kD;wWN_E4Ti5Fq~ZQh z9r-eMs9ut~e7Iqb$C}Hr?|_%F?t4$lP-s|&TytgcAMY|D{BKGrUed4>cgB?>*pH*z zU=hca5o%?FVP<+w84NFEDNHLC`v0=MLgMz9lXQ4~DQeS|(gzXVXne%NBDL9L3 z&#pkM(ufB2ChdYuL2DZC$5OH>y+p#5v>;kZeF2Wvb0Uz=4=q?9bX4F_-`l(r;h=DJ zNIC_tyO1CjUVo&QN#L~<@j9(9!0~!%1k(B81?OAdFg|`$png4scu>HWD})MSA3};) zh<#8ml|ZZw5qrPB07vXS5lH8U*d|z6Jvs=7w;SU(-!y0>5(#5q%oy2q$17P5b@mtG*-fyjPaUw|X~%?PAAWHpH`2pn!48yT)Q+qMk? zzZRrWK&|C-^0lyL39_d3`C58C zV!jqqq+$8m6=SnjDr@H5iY+jWR9OW8h3l%mo6 z>e)@JNzK&Gk0&)#%W8U3GqoKT#&H6?w>%e(dl?kf#01>9TL@{m<5Tmp%$cc|q!Mpz zoaKv}%d&4imhtX;PYQ8$<3e1~Tp|4Pu}lj8n^KIC#>E(lD@L#vM>}VI!{P|FvTQ9g zzh-4?ca4k;4G#DO;-A!|<-SK>AdcLSehX~lk4TAq+W2F!W&Mzz zb+@d4p)U|eR@l%peCXh=!O`xUcJ}ueV>`F{jL^R=gl{Fzl#pj ze^J>(R~l=lZ8k}9gH8;%B(~HW<4S#zra;hGJPHJX(>8mg=-xSuy|G2!rI7&oKX*f4 zuPLw~B3r>C&%_ovrIDacGd*r2^qgMkX# zh3F7op)bJw=F20HvKTDTZ-(OplQt9s-xc5=LNsVh(Jsgov_69OW4X_V^b!fHx){;= zfW81n>wOVOb!cgN83;!PhHu7vdVQkphX@7>IT?`x)j#0fSWtamFOGm}BSQ6EeE|;D zw+LytYQ#wY{yvs)d_YT>{;h`TAQuuWLEQ9jvuj#Nw?_33-pD&puYXrw?9x9ZQuOa? zyclagNOh(fuDvk73Py+^j17+0gOx-$sIhiPIt4Ei31Zm`WSE&2i+uOksiU3i5nAxqTK!FVvKU!wNw5mLr1QR_})x)Zf4Y??!@%hd84 z(NeV}H!1~iRW3AnK9blWR+|xJO#XV@YHNRo66EfyYOOfc(DmnC%;=V zck{MbYK6(*w~7)Z8N3;i!C}d1*ut@A*C@PU+$z^={fDtGs`YK0O40d`iboA+`k}o- zSA>$2#QqXW=;%<04~m+J5Irb_Jo`7jzhY!9 zrLIgsl7=*@HXdax#qOrG_D(v*ZP;`08irE|(mr_R?(QZ$O#$soLNp4Rasco7flP5_ zS9Q>Cx6HvoM>7 zm($Ocs^lH_RojC0*gY^d&cwon2e@}1@)_8;sZ#NTRfbcj&5DV?VRD9^Mh)j?+x(>& zMyY7<(~+}js2$!)Z`dURY>ppgT?REbOq(8~eVGlwooNt+eEM zLE`X+T<0gPQunVsb?e&^xgqWN+y#T|M=yMXZ758J01%LNm~et}3~ff-{!<9NI1$2A zG>zRFq_Iao3dh$|2Vmwwn^d1e)D~B5B3m3Vl|w4^?-{ zw5>jRnf-$=!=RV9ZSS+6moHEE$@<6}46d6@c7Vx{d7p%wB6ar2q37E|~d z8oNWWJ?T)*B0YW~&=M2Ic*ZIkl??c36o=bu%EXIs2Tql@rl&1<$O#J^_N-a9tu*t? zKw#`BP33kFkrU)zl0E#|Qqij96VqA1b-Gk8n3Y7%E)^5_5EtcjlSaX+WJ+mi(HDd? zqM|;AM-4M;#H)f*v=J7xt+{*XkZPbX*X?00*eUQg&Zza!*KLd~fRc*WY#@5zgXGI) z4$IeUG}9<3Onl(xbf6aWn&q-Nr@T6eTut(;b#INi8hM4BobpN}#{PuUf;OiBAEFi3 z6JnZNFP0O%pk}M^cVleDa8*P#W4vjap}3V`)i}~b)leSjef7$6q8s2)z}+ayK`tl7 zFiuDG*QjUbWPMUtc}oSafY>sEfg~3ATK`yr&G&4xOmYb zryVdzmkMUKSj*iGMV)1Ep#nR1YR(dThO-=g^HD_YG`W4!EEj8Moc7td9feZ5nzw4h zP8ywxJsq1n($jze4q~iwqFgO@ zZ|nMMPt0AxK67<7`ivRLbvWN9|#OY zB1y#*RjPDd@CupDtdsnMa-(A7UzfM>6n*UagL41RQf}HGKwoh$^c{9c$CrBqJbmsP z@Zq$=C@E#t&g34&U(lVxn5bBDR%Ws@nLPYof$(_7uH8=hq)!12>yxf@4~J!k%jlh`T1ca|Igm>|DE0}6v61sMfE+MQ1j58-`z zXimXP?UPryG#clMqJGbVosu?$l`2#~-p25j>{At`>`y#pgEIg;Ova~?v|tN@rc@Qq z2E|sefj^b9ziyn3O%d8ld6JjQTmc>}{KWxfAmeQ-BWBA|S>=GHbE9xK>&bw|`v8T@ zl}H=td%APC0n-tNG#yY)m_rH~(wqlHqYr6vuL6dKb9a%?S0i&hbprtO|jj}Pt}9F>C-Z`fvx?>{&= zHa>9hCP|}4zht1`8Il;iz2Fb$c zIAeoDus5B!F`lvTx=xvANFE*-IyC4?w-BGxV_f$1V_PRKGe!r;4~-7*hnI@bb@H2W z7Mi+Q7?2c_TRZlSjvORU^KmsC!>1NgLPJLz($FQ4^Q-x*=WXtmQC4Rh8z~#0WNewm z;r*lIhX#gt)F8@4bCFkKKI9TV?b>6z@#LNypF%us%G5vzLxMt>d z%ITN*)p{pcR=OKZ$qn#3to`9m-Bio1$eMd1j&VETEZ(8u>22F+LF7x_it2VD{~a+> z^G0c2H{*@abkSLlK0H%BkFjrXcyJVs=GbEl3>_J`c??}sHwIntz1n7jDXd7CI{(X2 z5Ki)L(8T!Nc!-mHxm))XCwa%NDqGLQ7bme*5Db)EqPR)*T6hW-vyVz;Yx$HV zS-;v#7M98vsf)XG6ori!JrkG~Sr5RXLh$-=I zr?pb5nEBd)MNS8&vC{Cfih8ryS(&PqfxSwS)ivyN@+X-!*G)fuR7CCG7E5}9oiygp*+Z6wg`YFK}kxu(xK+GS1Z%KSNGak_`bn?GijB}rE)R@>q7HZ z8Q@1&VNr;)io&kc(n@Nnfm)LW5qCUQME= zWLsn=mzN}30tR~`YiFsxu1cn}HqKUnAdJgp+)lveIzbp4EQ1&wRJwwZB{2tQ*CuMt zIrt480vRP@m{t*MOGA?{FPkT!LFmr|i=8!SY@j*C8BoN0wvesV4z9k;SvhOXN)Lj< zz;c*b(01S*?ZP>aN7ZoIPSB|_ShP6<=CmFu2?Icxgs~qqPX(Hz4X*cIvh{Mnb7grt z<*6R8$m%cU)#DORRN3P&X`i(!)8rZ84f-;bLLMI04jDnPqI4xwJJ9A_2s&9v3gZ+m zFbQ52XY!oYba{GGOySj>mV60Sztb+jAS(I1Dp<7*K%Gr~)1S$FDP=NfFHB`j;0GF> z+8dxuAcWG#tF!QkmB3vkTLJv38#n{nQ%Yrv)1U*)FRVpJ0WQ`cE(v^6;IS) zgMrgVy$P5vXkr#tH-T+bAg79JaERWl0j5B+D&T9OjjD}3C69~L|%H-#S`Sppw_i64K2 zA8*5Je~#Y{!^3m8BJoN5n8uG3e!Tf&_;?e3j4y?cXXD4;;*Gz-k2d_-iXYeD$CdbT zTPJ+X;YS5Oeg>lCz7H>h%Y7F={_Sk|_$hu|j6XNv#|VB5;m0@e<0uH6`_FUX<5&3c zA8>Mf?%(m_6R_(i_c8om43Y2*k?stU>@<<;G?C~G zk>+$Ya}n?l0m>L@diV$1J@aDH=$VITi)R*t-1p-LYF+Nx*y5;jR0{@#7Q70-5iR&Q zej{2ifsIYH;HCJDXu)go8_|MK;y0oNzsGMx3$~&v5G^=@-yTCPK&__wa2;v}(T6+m z+ZeVu>N(YnpP+gW%{WJB#$KTrx8paW8Tj5nsu}q91gaT$E*I4d-1tQ`1J?^s&A^pw zR5Pv-nsE%j5zW9!A)*<%=M(iDCc-Q?mr&qYDquGZL{BTY(LlfaF*Sx!6W zg;cfH^;C$L%9R@Qe(SwYV0R#9g%BWBs$vHZ0U~75aD^8*MF>Wh?Pk~i7#v{k96;be zoBh(-D0FLdy*;=_8$3CEu!snf%H>Qu#D;S==0(|65yoa(>B0^<+GW2doNYJ2G*lUT z1sLC=&o;W$fk7AU>VlvY*0}M=pKtAe3C0%ypJAhOjLM5Z_k5rWokeYBvFKu_1w9IM zp!6$PW~{)27-fq2q&@gupuXLV#flUU?)yUcsTg@ z7?Iz1A655OcUMc zI=C)ql!DL+@zARkE7porbLeB-pFJ9Vv`{eK6*TH~&ku{X=UoWiWjJf*s1om$1ZT(HBn>QvJnUuxQun6R~CVS<>jBm$7$t|2Vj zjxxN}cJKYs&x9ctO_^cnJEcYlz7ij|I+XxCi^j`UGYIeypGRxv8vis=Y1ET1C9`}1 zRA6nkn$@sr?~S%Le5V=R(-(u9(Nx*2Rh){+v57Xnv1uO+8qs)YuF|kg``TzsO4;;% zeh?PxzE|~Dk2Rx_s#CL?r?*67brWm>GP@fk)4matvTp{*4E(nb|J{cF?!ctRfH}kU zbrRPeC861{3-(U?h&|nQoV}(TirP)t^Y&r;&eL1aerq&Yj1|b<+9*Mq#<*CjBC-qe zU-W#lT(#yd`A%ppaB=E5qfyMrsWhW$6^LPZ93zj?$oBpCZ$3H&v0k(01MuGT=fk!7 zJe&JGiT`}rtJSPBY2$pQR8+q4Zs!>;1<=09Fdq+iV+D14jC5oc(;9`2S2YE>?L4O9 z)K-nqGtRKd8I6FxFE5`wPQO|H@-mw!!H=A?E|ZaouI~iae1DS}Ak6M@KD$Nx&P9${ zm3lL)3TDxC%P{s*G$BV;I;*IibrvJyZHdNil&p@jMvWdrqepq4_F1rFgrPVISv6OE zvu>+MKK_8`8$@QJ3zWf3h;<0mvWx|<r!NN z8$qGOJ5@^9o1*Eu1s$!e7B55IRJ32$Ct6&r%X}fRN9_+V3)`M8S5#zlw#-H&45MgE zeI4puU!Mb&nwF~cl3VXk3{1jOSoRgI=Gb( zh_>Oob-7-3%1+q4!hXJF`nBfOXtKV(u;x`7(2&Jw0$-TT#b;Vugh7aI&&6D(=I@1( zA=w&(ti2QRwG?eGbYf(-v|d(vVp7W$itq;KRi+<0TeVDxWN2Qn45tRIHp^wNQ45U) zOk!bK*diiG!1dZ*k2(FD+)gY-H+1Yp!T+-=@9m$Fy+nIDjs&~v)Y!U^EYbGw3h|O= zlA)#y^JR6Il!)0Mj*M&4pps|#OPfPMYUf8^8(}SVm1cq6?WUdE> zZlDOD{)DnfN#n0?MWz7iCy%NG1Z3kHoZd*b`CTsQ$y3v{GTo`=z!BG6Ma`^*;7h-P<4}o#iv_vq)>bi?bt}chXu< zE7tmvSZ{=DeqKI^CQe;8*XmWochc>OA0+6$p(k|V>$>;G5X>dHhB*-1g-2d2RlV{B z)s1*z#rM`=(kT{Cye`E16#a`pVM=LJDaqfYdpkA7)yOz1n$H#rg#%Mll@&a}N5i<4 z`_fntV2iD(DefubSJ}lZ%Mc{PALTtBVvo5$c647*wyia@z?QRp{3owY3r=uUm?8>Q ztTX)Ctg}+IE;~UO9NlNu$#dR!;LsG%5^lQRsLfVZ4oKSnxRZfP9K`1|#3#rK-zP7x zRTqM~N%^J;tha&XU&P1J6^~L-SEGrlSzB!&Tf{kn-6v1XI2Png#bCwm^=GVV1m$7J2JdJq(Z-V=*NcVO4@&j_DY~}(*vTU0uE(g0I6m?7pWJX1?KlW^e}?d&Oy5$pyLCzm!tYNZtaE1; z5kuE_)O`>7=*QLg2Y^#ujqG^S{^X+l+#(!~H>K@tuN>7}l-EGuh-<3CNF`BIU~&2wimXRa{{#D{Y(x*KyBxh4Jf^2oUrQ=zQIYYk` z0|#lvu;FVNs&?!qNg%Kt49u+6ypRPMO5f2Np1AMU5b3G~V}E_UV%9>ZY#_R~s{4-} zGVYV(6`9RiFygkx{$s}=W^90^XhsKB1(y5^4)l5YKb~^@g$^l4CW>8ij?%awM?yJD zaGwIel@^DgXF*1`WHS@OY0g<}aZsK)Nf$(g-jqd>y;rb=M}HO*t)01_jkZB*=dj{2 zM5FD5H_IEb?^?P`=gHU8JaNB9Pb2{*839tWcfUYQ@|eND>VowIVa-vVppm*^``rv| z6aRMzri?A<*!KW_*nc1_VMP(o>zSQsOeE@}xt`jC-w^52NUgiAV^7mKDt->d~y>?HS8@cT`6$UsmFB+-(l7x)-T+qU129 zLJWCsVgx>&y6`_f4*U9cufg)eb?k*B`MYjhG^vnaS?uz|29(x$TG_ZyZX?6N^Dh)k zgr>Gt@cdQ2s|s>kK{y{gRh^?9y*a*%9n86vQkfMN99u3PnzAlh$SSXh#4<24sLBFX zh+CVG654QpSnUPRavKK^a&b3ky$FoJ_8L{7MVm1P4`NrfjK|dz*rHipzJ*famJwJX z`A*3kg!wlr79}Wo74zH=~r)*}LLg+W8F<3~8r# zCp?pKry$l$A(o}P%(;l#^_LVzym3H@W>F#vBwV5x6#Ya@r{jZb3nz)Jg-~WPS6ZKA zDu_@<)O;wvlD^7c)%{+o1aUt}c)}VUkEXEy#*2F@&C{EcDJ(_z^rfTuPs$txST z+#8;t5N)RVhr_fr#4>8}zUXq!gLM)n1o&s@yP0@fJGLW+{-^fP6la3yX z`!r^I$`Z6mnj3Ll=7DDPmO*<{XFIkSHceuc$eT(dAoHf~Z?q6%*8QCFEV{8ZA+8QU zsZ95aD!`E>HHuKWTInStI?5mvvwtT^n34!}e@`8%L*n!GDInc9a{nW`=#=@S@p{gD z&c)-sY~=rYDj6qZQI^I+d6KMIi}5tuMPRe;57DQ)306_d2t~a$|ZqMWe$=lfqaytW=2>iY^QY~NW!3ZXO5~SKGa$?zGW2`GP*m! zL9HmU)4;4`EkHRGQf6hn!jWY-l`2P;>?FtRy)hj-yDz%Lh~0%85_3~~0u}B*d-l}g zNiI0Kxc6)(9W%N2OI$`w{9o)~Z9Xd13ZD{geJXpDHT>LSrGzAkQS#Wr&t;F2WefQz zaCf^9HklGZsMdP;Jl2tvi-_y?A{QOnGC}$UmAJkJJB!o-s}W|M@W?M%UOq(0%jEe9R-1c{)sf>jD=Z&Ci7y{L zL6Kn=#ESR`a1steGT}Yt7+lKW(fvyoCS87m|AikF@loDB*5GTV?zaXHf zhhJFGf1zJl6(Sj=glfnKmoFHXY?R-i7MQ63N~Y=L*}6eHcQ`+wV@~}rF0yc((#9)3 zFg#=go`Hi{I8lJ4a)mdcDrO{o1^E@qP$%^7{wknYY@J{v(uoG8w@EE|0B%UGg`P%8 z>IEoTD98&?{$NpR`jAm|Q23ACjz#0dnZ*YldhoayM+#>{6V?)Erw(FJ; zhOX)3wcy3a=Y>XpPn4f8BMDCxQy7@^&gXl4MM}*3R*~^>d71Te@L;S5q+4|`hgFQ* z$m$Eu9HQBTDEni)J9tpc=Ep`GIf2ARHL0Z_XGIj$Hl1WF;`3-EI9AKOiYK8*=%raH9 zh7Xuk+Gf!@2e8); z*a;Nq&_W(8e2jV!3{t=vtdUp+s3~eB$VS8z6x6qT-}43WA2A&qeg&|>p8ka3Wc>NR z`(v6wd5KJUi7euDoa%%Qt3FP&GwtBW=&1FF#QB>V(IbmY=g*y>*7NS)l7EKNmvCxF z@PutYPbDsx;&{a#+O`tPcC4_y2S9~ zm-!6mY;f01BG=-WZH!x%<&+J_B^#1MHVk)cIL;W(SPab-8h2rIc)yQq|0fMdYj$g2cgrECoTq8x$}Fa^&n5?L{R=+^ zC;i>i40ZBDL=wUBTilpg_mYygi}tih_ejj!{+6I>_JXRcx%+7Xijk%rlbi7*MQHa{ zAtiadx9yJ!lb`O1$+~NeHww7x zfCrOXwMjWh?7nyeV$-JTzB_v!Q!z1rKSh+}uvm1hmZ^+0n3CMPx!=tO7e6Zfel*o@ zw(?Us6k&qMWhvTnNUhGwK=4%>f?6t7B6+QXq|!h^^w;`FbX+8CmEO|WW?F2@-AkLd z4HWnwqd+F5-0KNl4T_6p=0NTi4T^;MVvezXKw)J(7D4$rp!T(lQ{!9=9HfTVnZX;l`az1JTmYyY2-QOfI@r#xxAF2sGFo3Pfke^y!i-!eRb zl=B&8mkBk#*9$e&RcZ%F@oh#5J9$-7NP0|0U#Q<8oaWH9CEUomnhg(836 zi_8Tnq%Zooi6oDW_Mq3QB=3PFZvn7YE&oqmK2pVKPu~B-L&aHfW%s5?t3(DnQaj^P z5Vk4%wHhnJ%;{J9NmPMRv|_td=iD zZyIy}W$JgQIFeLhMmthv0-U#TCd@rXp#1FoxF{u1I(}2CW1ZFc-WzfsW4;clR zMcG>bm;K%lF84k$fLBq$yx zcE&Io<9n9PX!AjK0QV+xV`9=!d`c;~tg3drIWs6$oQrs4=}weYV>=!1{K4C}YN*Me z7l4b>F}z&~RS7trieg`WAlPk^a8075s>omddnFoikeS45O~+9IbrC1s@Nrk1t1K!n znjQ}-b8Dr!yP9USodK&qPM5sOy7GVs`lL25yq{8JLpzg(p!CcDhf75l6}8h-uJ238;CtvzB3SJ$M%;VfzEPu6lunq*}MIqO%-29+_1R-BgOV$ad( zOTLCJT^lA_JxNw78*zOnla=FhznUqS!ECj{HGIOBRZYBxy?i^$3F$+P> z!y(vwxKIk^Rp=iC~N7imUBds<=eIMMM`harFzR0RkniC_-qBe zzSAKkrTYwIcPq$>vrH1(nHXF4gVNt`^^fi>UfG}Pbd)k&8vV@mPZjn9v9M=?q!wCI z5w82$POi-WA0i`pUpxR}C z*Wepy#|8&EG?+6R_p;6V+Cw-fn9pS^>TKzsDLGjpGm(0H9G^qw4rA|W_yK}=Hnv17 z_Vh)9bZCr7d4xE6z>j^(_v|Knv3HEa|KoeHW4w*Brp(fhJ!KDX14Pz^4P;|Z~pXNs9?auUqpFD-|5CQ!pZ%401=y>#V z0F`I*m(s}gsX3X{=XOaqT8dg;=!wa?x*dTWu5vkSjoFlF=Vf;a;aw@BD6z&sdAs+e z`pxudSkyt7w8`6LAUKq~-ADUJv`yYFjcuj_sD=}FLBQ4-1u`j>3MUAPi)H3Osk|Kt zb0x=EdftwpmUBSqo+d#pF(@r>7d^_AHD1ck4Pg<_pkiGT2oUI?J7&Fe7x=h^IgQ5%BdH0woeh|<58 zV=P^52N$to#c|}CwhcOxM1a>AyYZOkdBlnQAq4X%6{OX%> zuuEq$LEVr8N@p@bwTr^fv2ZVWeN3+}kI5P3y<7wD!Cn~Cb${dmjrzUOJ}2d6AgjBo zFV*;ZFnXuf#mVJmA=o^=OhW1h@M1!-PL8rBitGMn`mLvk`XnHFi>O}@ZG01h>Jm{O z>P04G6x|z8vNHa3Pcv5{=LJmX2|k^TjGVn*@RN~qm4JQ{IlpWo=MVP8WIb{|208p$ zE{B=^>B}y1{^b-=h7~z~BGqrEW6L6E!lX^)oPpp_BInQak7%37IgM?m)24<1Z-jvT zBBMYirBabIL25=o_(c)LUh_%o6=jDr* zPidnKG*-$J=qW<}HVBd10Z2v2$3e*d%?X)lf$kk>c8H4m?;u(4w@mghQB@62V3t43 zA!Pbt6@ucl=|v5QqW?3;Sh^Yz)HicL>1sewTn##7m=y_=8j-*8iim{o!(_Nq*O?ft zkubS}+_TMw(j#H=hmYo9m(FB@dVLNkoyi2nBjL^%W-*ygG`%XvIVfHD35vh9v-7A# zO`R_EuFoIh{Vd(03!N?m^_TQk7ep~A{ym`9<==h6HfyVRxwCcw?CbdjuynItg&**z zYhP3Ms3e!Y_TPzmwd<=oqaq`jtR{&aThFv(;(Avc;A0bSf5B@b?n0iyd>iv}aKS4l ziV`3uZN>GfaaiJg)19P}qMr(7KYfGC1BiwBl zvoS$Sxj`Wz*VWUEKOqJf=eT&c2oFLzbf%aZjt{s2kE#+}R2-PT$!oUN(11 zs07qu8#F_n$r$2QW1q)NvhGjQZ{rYGjY%y{0@U+aYX=#Ky_hQ|Bd@iP*GK52pXImU zOvV>+HKQ5apl0H=@8u%DluEnRC>pDHYj{I_)BOsj5sl*-adtzl`yc2lyYyWBUi0SI z`|WXXcD&}vk2CV|9M0ML>-ce6{3yTgLJLu08pMru)I9l2wxgbCJlysgB5-B*{JNh; zu8g^5;_knBu0eg@edgW)L5SXgm-YMWIPSY@yAKht;P@uFXOY2E>tVj914 zOuGkdd#VX9Axb?>%>Ul0tZ1O8`_K~VxqGPBHus@z=fY=JpTNBsOKiQLb@h9u+Fy8f zM<$wX);&}E0Ez`5kG$QTJxbO+Q+$-`T#B%v5m&j%N;`x&rF;X6}c`YGqt+ z#zyJ)F0&pe^jIePnHniZ(YgID>GN%VmqMgU_9#Q*wx2ypU)&xc1XUE4GjN!WTpyJZSICfNTjumaO%;wU!k7ZIPQ=0w}K%v#TG_}8@?w@9lk|j-I zlsvZM^Vy?hNfRFh>7BIn&<|=(YWN(xF20J6EgR>!L$p7>lCIlxZ$+o|qF15nSE9(9N0OY`nE9&IJ2w(pH3TFdSoP9-I zh3aZ>8FC3%1L2aoz=Gq98`DBOt@sY2B3wjNt>S7gCp2(jlwl^9UD2g6;zdq{sdreu zhgTe#H3o~D!cbq0duFQ6niIx1+r2o0CDQYMa!U_|#@Ky~o*3?hcqw;@UyAp?1Xjof zB>9hbvsdu(LR4JJg_j6{3%CJHFd=?(8N0;w&{W{9Ne1DPITQD3vD?&$Z8GivH!ZVl z8w>}!sVv0RYQ$r-MpT5$x(vDw)L@s7$1M7hCA%FE{urOGx_`p{ z{#W+*Pubt++260Rzh9@ngxSV~&yO*I44ijbLk^t>zcxI^Y#3p4y9-N|0X8^fJ_^38 ztA8$+>Qo9yUyUa|Un%%VB&M(Yl4`{1~miCkuIYy&iBaQx2fQL-FdK1%oC_)6vwsw?^81;-rs z#34=J&eBCII`Vk*$YJ~wDUdt?#&em($<*wRb1v($)IM1HboMA&!IBsy&s=^tdz36$ z;-er~5(G?ilzvg|GkL$Dj+xCsjXBSR;_FiVdw&1lRm?#r;9uR z5EH2X4iZ{@zgJqh|BoQ}eUi$wk~hA802?kR`SslV@@@(fE6&P_fnJ;OJ`QOSXd`2i=&Z0C!@CR&||S%H0S|ToFEn zJ?Z1lDto{FCC{(Yt;Of;MqpgB_TwI04*{<4R4TXuRoogb`oH9aHU>FzxX6c@;==?s zyGqxo(d{rnaviq93=HsgufbxnxEW$+>mtTj!*#ZYj7FUz=jG*l;?7IhCPEt}>|6I5 zKD(r~M8i})L~iWn);Lfvv-`j4MspUc<8}CjsW&^Tbi*&d6+O9NH%3FnlCKSl#NCmP zFEXW?w|OE>lhe8d?W%cFn&>^YqX5?8DAp-1tx#dmsqyglRc!>P^cU`K$9dzht_BJ8Zm~bl#uo{V!TO0 z`S)i^4X+V%--l$GRu@C|^h==Qdtzuw-P@Qz9mr3u1eK&So0Jy%UhsZM15j;w4I#XO>FN(MSIpwLOrtEO`2WxoL2 zd0zIn=@ZM#vQIC8yzG+>{A;%TP^`k6tqwUmdv@T$rWAg|$kFaQQNP0q~c(0Ez0-!>=p)p)$!f;JI_BPVN^h&y)Oo zQzoS{75_fY@;r&$#q3eC61ifOJac(6dz37};-er;Zb!{TuBh1nS=}5OdqZ3U6Wf-D zX!T60U@}@Hh=wUdb$i~Q*d?Ot{yW&4jZZ)4SxtEo`UI zGk$#b)cxn~KmO3EB7upgkDhwu!TTS2@E!CVkA!vAZdllvMpTA|tv@6Tdaf4+$y1)s z5)eLJLOYUlXP|J-UEk!drxH7}PCb*v7 zP4ZXWr^Qb8iaAlz=-b^vQj{R5c9hsFD52?knihLzT4)hJT8efuS|}YT)VQ7?d!oiQ zfar3TQl-W?=C!LkNQxQ+)s7l_h#DyqmnOztt;C2QEk!$0@Cq&N?U@$$bf1_?3)u}t&ZCk~CQcv=C^}30u^eNG6UYiw;%O*c{r$&G zY>A@^)`Ke+;7U%E3V2y=9CnEeO=$ejx;ZV_R5Vl z>`k-87gaz{ExYDLvk*L0Eu4s*KI1++4C}$d{5*MF1&?Ko3b-M3o*D<%{Jrdld9&`Y zC-VTGPrwUyxK=&3T;#qZUC!*i$dghe{O3{D4a0~9h&ts75hVL}pj}x*${J8t;pu_3S3I3@Y>(auIspqO0+lDR^ zb=|03|K~7|o^$;YE#&%zZMius-$@2c;DRUV!3|yX&AZwOg_o! zd`2FB7U!OIAhC6^gA^q>=}j*msOm2~;3ehH$J6;*JRSC_-|5(dT?=M7NM5JdJW!-w zc_bvAWc#i55&Kc@%h_k)ksnZ*JR8Z1skzXb8AS~kkCFEMAx-# z@#RWmyfedxP5vSFutC=wvn;4ZYB;HcxMgrequ3 zUVVB~gGjhJnh^<(qUdWenwC$hSVD1i^@KQ4A2g#~So|8HQw<8(+pUE-!UB`S7qaF@ zn%pqrg6u|EZ-mW7`?1Anaut_l`)26*=xMTIh8C9MwK~3R^F1#t)F8kc~#8(TY`StO9)IlIc^yA6lTYYzcYKs2iXALZ-Rq_zES@n1sv_9IRqe-EpDxCM%$}+Z%V^lwLo#a%Ou)j)v$K?ID6(- zio6OA!yv^5I*ef@TfsCz5vL*r!XAre%tq)HlSk1Os718B(pak(W03@5*#jJtw)~n| zr4DznCrsFh`^lqvU^OaU@$wo8)J|xg7g$9Vl1T}4_M#IwTx56PPIy*lh=~X+$EB)3 z?WQrA0A-oXc|@n%MKioFql(F4MZT6kVGyczg;fJ@sA{nSmT&XTOYzs!o1=?WqQ|Isl>e zuEeW6ju5Hr9obVAUdn}Ms49@LH47xhdW z53K46w`o_Styb+~bFop1Cb%_$`a%-%G87!vgWQFtziPocL2h;dDM(fbbBjI@&yY2@ z_uA7zv|Ia$RuuXT7OPQb7RvTKSV((sK%=b0zmsq1R9H7>;4Ypt1H6a@!2H=PUy{za z-=@X=TlB}m`s|kI&;OvW-=shP49hIIN>9te>l_kE;y+ zDucb^ehMI08urcP@Sd?BWkccr*f{snU~C-n4csvr=kxT5jrbgWokarLoufZ=wAZD# zMzGp?mk!3TZ4Q?(|9GfN5yL>;!JY#Mmz)&9$1RN6E*(!uyN1#e`t$w+-df)kcNXeU&tCszS?@u;}E{HA;p553i%! A$p8QV literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/postgres/connection.doctree b/mddocs/doctrees/connection/db_connection/postgres/connection.doctree new file mode 100644 index 0000000000000000000000000000000000000000..9f594faefe39a5857ff7a24d3471841bce0e261d GIT binary patch literal 38526 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/postgres/prerequisites.doctree b/mddocs/doctrees/connection/db_connection/postgres/prerequisites.doctree new file mode 100644 index 0000000000000000000000000000000000000000..4b2707dad0f5ae146750d74371b9961f0640c97a GIT binary patch literal 15639 zcmeHO>u(&_b=QLraru&DN^Wb(87O|xvUXRZBtIC-jZD#wDvM-DMRr@W!`Yp?J2RY} zSElhRJN*ub6JN zrs+*qeRA$%IhlvAVrIE!hpeRF-+Z8XO%`NJxDE^KipMlPFc{zz3o)m9v2y8hpnA@+=|APjH+?b zxOD3X#vkQlYN|lS(Xa+-8i~a+FCvdY{?)eY2ThOp<+cZ5yBC_i3EIrZg0(Mh!@$kd zBYwL3bOHG*fM%GWF|OmU%9kPPEmrj*51LmETJ5T!N0n5fT6Z0X)k#CEdQHv!k{eVD z@KwN>NZ>W<$J26%86%`Yvp{+n7Qim5&q^LmsXjw@*fyCOK3VMkS|n@_ka#mjV#&C) z)Rh?t(}@#zF?=l^ZmfU?Lx6KNHe$xGgzXL4z)#&}o)5g01-I4K0<&h?X0Q`Ow!U9) z2H57o05fucjS2#e*FxK7fohu|X=Z37ps+*Kqe3cLxo*C(5)01S=U-QR=G|wWfe3blqe6yz)Y2PEpFrrRT+v>-8WM10Zn$b-!4-Qki@10{SbL zC2yuPIH3ISkEi{AtiAe3Yb?Q(qlcgl^pIlCgbxr2Iuqd!xWI< zjX@B zF-A&yg&7>>0w*FZ?+a(6iu}T;yq9b(5-^Z}S9mE6eDvsD2wMW?IzL>=k*$+y`^y%s zD$k4QOs=vC{8Zw)lUz*ROj{2jjnH`dtLc&7Bq;;YN6i%K1_Z$M}w{>`l}RzY-| z>E-L6Bq%vi*-G(`{dc%(qj0jBr`b7&g-_(6m%KP85z4LiPRDxtYb-cmeT7EPSp)f$uYmc^aZkj^U$;Tjf9zHpN`QD$=leZQlA%!uuN%y z)h*X!@a6-JGA)8a-OTCx9po_%@rgD=*zGjcZS0w+`Ajz;V%j5SH60OiXHkjur?fWZ zN7ngd0rDJszT2^Xkv+3~NC3f)`T-)?QGAvOD3%Z2iEkg!GIQ2<@L#W^P8eO-zla|+ zh>b*wM|WGA=|t8Kun!*vB*9J_DXyag6CcMX+iu&4q=INybZ=>%6TK^@CLw(%B?$O$ z6Yy~u@F|`nz<7u(-d{8^GD?w(V|_h0GEkc}QcP zQ6exA{lO<>Nc?LG(x6EEb2>vHakgMVzIxr?qyxfB>8WUX3t3iJ3@w1I0Htxdxm)od zoG_j;i`<)nd|FMbAuDimlBC{068V=ht4+CRYX$Ikxb;^E407wOHFT0Yp8(e3&UZ3-Fj+lUkkxDspB1); z&X>%jmIJpe!@{z(H5mrk9BGp%s)SjRb5i?+ImOec)83P*>~SDXFQnOSEbieZA@*qY zv}ZzgcVHnSVw2Oqmd+5?`^kbTe~^Q~^x^-{ldVT zaZFF?KgfA2Xa7GLr?Txvp58`iUpW!hqswAj{uPJVkqk?Ar%Imc;Xm z=?o#B&lbeCwtxCv>bN)Ccg-T^ZaT6)Tjr8R{zB#PTm2tb9R)8J_z zt_GYFTo(ID)B=fm>yzB#;|K2-H_0gT^%T#8h(S3=p#o)odb2?e)zgT>V$@S&)a?+L zDBcBNOth=V$1VJ&#c#8^iQ5{5F3-=Ouau;9h#lruXa{B+Y!G+r z!Xn0YLa|*qD7bPhp?Z+wqQpH&Z@^KtkD!) z3}N9wN$$0oo&}aGF|wfUCxmX!*LuH$FUSduz;$(JKHm4|K=cU69?^Ir89&jyIj!M&ApLHqNsbrC7%U2S!E~~LoAM3 zjS_U;g%Un>(U4};w4AGw+}W@5pXr_%<$j#f;UJdoD#S!m5R}7pGY&c=m>lNi6fwD) zw0?>`WOTKETbE(Egy+O6N^z8Dl{Y-30Hs6}8#AQ-?Vv~j_gja7)4yP1ue1><{l~QU zob^wG5B*0mv>SfXE$^a|a-VoINIaP&Kh>J;);Duxe)C!Q=;d+}xTjJUAYNxa9JB1C z@aqO^X{FazuP@zEzOZ)vwZ#j{!s_DnJB!NllCrk&xy9SpmDWx?Ou4fxB$&?T&y-5( z22>NJJf&S4PxiXA`h}&11eKnf~B7lk+0M zI&iiTfrB(T&tc+#Cg<|tLobS<-7p)#EAp?o|p_$|Zy`beha zt0sry>yL4Cm6^>%4RG$Pl&*r1dX(bj)T@0vcRQt^&Qqx|cuOI2&N#XL*NH+{m$LB5 z_9iUP_S`ua6MP8-&8v1UlezlI11Lqee+u)f9dPX*s|=RHd~VPbW|63A?vm&P-e$SW z7YEN>7MJXjw4BRQew4-=v_9cIf~kr`2TM=QgxT%62jDBlBXh z@R7PCg~ohZcFE|bU)~3yNd^r+Ei+0;lIAj_xean7MAV;@3S&euDl3#d;I1p%WR_1B z>UJYznMwHf^zB9XQQ0cBz)XVE?ZATTQGH*xLUyy9y@cE-% zN1=%MZ2*M7 zm`^1s88xXdU*Z$8Mci^P@neVp>9z|GAWl$4jd;+|Ku#jS?Kuj{?`n8t;HUmNA92H= z9R|^oad(N2H5p#}b-s)D2q3)^w$yFP+Tvn*3Rx7hjS}t_*=`>T zJ{Gqm*4$UOkgZc?^*{DYCM3@RKo&UZ^d!S(_Zi4}gqIA|{>z`k`~;l&Kt=IP5c*l` zu+C~>6WdME4LPWY5eP6^Xc+@L>Wok#_)G^hLjX{BsUiWF@L|v>zfkr~d_Pm{RGH_w zo@(IU(ndTgGDK!Qi1>6&`P?eQVwH%W?da)P!uofJBcF=b&0vtT&lEd4CiCNj17y!P zk~jtx>(T)R#dUbWNAMzsKjN{v>*;v+LUk9|#)Q}`@eycz189OGrY;4-7~xZttW&cl zeuQfKC-tz^R#TA#VMT)pY4jY;rUj=(ix76=e%93XS*W`zoOg^m4$X6Yrsih~DS^)3 zr}qXzWT#c#^eND!>i-zZQK>3Wy9pqJ2R)9^J1jbx3{&YQSUeCpP2aEyLIkP@1aD|H zl#oFIw5{~#t;75T=^G@+X+jWfvxVA<+sB{gQ*GAHj0hovvXwTZ9k7#X@L!V08k$k% zv=bPvBdQA{eu_p405DQTtvtd^WET*#uy*;$i&sF;DX&rQSe{z4<)4zvLwPk4*-hqE zOcpf69xw=1dLIP!9fNZOEy8=E=+-fQ7W&pwQ{_yppc;owyZpGngN(|ynnp(SmW2|X ze8O*OP_%m?wAu%u9udFepDJ<#T0+{hbpsXNDDv`ReFK#R2{C&!Y$FV#_gCVrMPdea zAw4c~!VSnkVi!)5p@5a`7$%-6VJ|IKMtWqk89`)PHKs#@cL0IG7!|*qs$jJ8?jB>I+H4Gb@Dskh{2aM##+x09AM@M%LVz zF(!O|vd~2*3ia)IgEiyRE{(LGUjEQ4CpuYBs&qs1`6zY9ZL{%_v}}C@Q(*mwKI%xj zSR44**BM!|{+7o4m_GiTru>jTRuG(8x9MYxJ_7pqh(3NqAK#}TAL1kAW3)Ya*5afR z0K6;q-xYiBTF(#Ra#rlIE7so?YwwD6cg32!V!hqac$^+QEgNeBbo@tf8;12df%a+& zB-Q=IKvFGC3?x+*Sf3%#uF{79m#!&eaOp-!fGZp!>kkO}NI*ZC7w(t?sE3MAV30a) zAsZ&g#eB`Tkm89}C_}5}+Nr*X#+i=Bbo5wZ#WA_7m~^tRK8_Rc=7fmZ+v zTSq(sHNp{GN2V8&2up=Cc61?We-A=q9Rf3M9r3>wt-@-cED!bvlIJ7KK^xOclPOds z!pO+x)FqG)jB;Voi#I}X(Sj74tRz!n=@)lp^UAsI70Tz*%hzlDR`~eAA9i2k;|G6~ zFWLP~;FS-5_75{+*Lco)6Ek|HG=wy1d^mL#lJIf#jq9sRHz&C`& zt-AzDR_BuBhCUtEtT)gja}~)8nXCZ){}3^^4ou9TA2CVg_7c;&hk1SIC_|T+jxf87 zpG@5XDWxR5J;|Gpd!4`ZIhZPsjshvF=iotfd8a5+w{&|5^-g?$DRAI!1i*XcKBSa= zqYq(H`8tdzUZOg4=;qR^%iV-)SPB=UmXD_-5U0<$>EP~1hv(ro>D4PH`(1lc!Ur(`SK-@WJ7pv!shYi@$G-^ zt^26zs$11PJ!8Sf-{)3Ob=`Z;J+FJtx#!;d>eX*wwru$_`p?_!Hp-RSrEH;8s?|%j z+wsc#e)U2ApT*4h2Xpjvst%Xv8yFFTd9vv7fQ4C_^tc*j`{8Im9-q3uD0!A3;!Kx6`HelOLbAN z*{y1}X%|YZvJE_0M|N#|Z+71u**nKs^YKcpWM9gbTXWUnPI>KAr#yrK+cP!>hye>C z0O^eZgvGn78Q%JPk3Ib(VaR#Ig;uLsnQphhSK{N!O34M!ytTz@!FBNv-&fZPbNt)7 zQoEu4m@X90g9_}Om4#}nQ{L{aY&R<%@6Ns$)bWOkg<7dnDsXJP<@a>Td)&6SwpExV zu$}TX-Wnlg8xA(zR<6;kSL?HfI^L>krDk_dtnk(}3SbM6+18#ely_oK8_e-7yl-16-bBhqM|)s+2n3#y~5E=MlhOO|Z*{@&CB@ zd+-iX?8|FN zO)?nSYN&ZNpd*>nXkDqzS~Jc1ob`Y|LaXi4vwZ&EBlNr7%;(u~sgG4>?Mq~9yq%_| zo!51kEy6I*@nKGu_f2vPtCg1BEL1fNy$xNU%2VDt0bT2?q<0oCQN-qHg593BW5teM zdI&F_;eD1L0AE%y6o(+Th1q7IQPz?CxdZiS=YXV%b&v~bX*vte5EtyG_DvvvC~x_8 z$h8hYC=TS^Ruf7!JD;~^>P@RsD>iMi--W6LWh+?J6R1Br3`1a*>b7gu>MhZSRcyO0 zaO!1kyIHZ_QL9=xZ-Y_yojN|5&u4uuE%P=sY$$JSHg^di^+DX-=ODPa7x@fRUR{2S znciq7y*8dn8E+NC$Xn5vhu$~l$3WALty=@ZyH`i5vTibz2cxyqI)_emq+lD& z#`sRNj&CZwTT{sbQ~y`O{E(=vf#|&);x^?C3+}p>Ch9NOsaSrMpuD#^Bt@JvFk6)x zGrMXAEPqfiNr;@*{fG7LgPN2nzu8;qwqU*1;uGd>rkg{*4D3IUibLgh2T1>)b<$JU zLDFAVq<^CPK5w1GoW!D=bjJs#LmFvR{$4yyqa>Vje|c8x zApqKJx-MyQ#7r2st|YZkd|l_W4^-`fYg?@XY(=Hks#}F(vEHt=e1jyWk}*;Umz(!Cg0O`4kogX!gOnNX&l|F3=Eccjj9ZR-ghHw)T-1XD#YSnJ#suj30nhlfA%MksN zgYPh>{p@Y~#?QR&`yYQZrodvo+McVqzN@0-*EvAomq zx>b^@d~q(H9~ILyl*XTy;DY&qfHarRYiWq_^m{x+fwB^}wfQ^)BouHZ{?cgorcg`c z7|H#02p01&cEsVX)HsY+y2)o7pghK)7_reQPEFz{e$yVyr@8Os)Cvu^TyKpDH-Jx0 z&VGOt63xS#ppf5~rWegb{3?+)o@XDUpINZx%e?Z&t~DZBmNT34X&(gqy{&F|SQ?i< z8-f+2Yf9J5`3>}9G8io6CjU9;ojLy#?RuK!BF3UmbXS4dM7^{cOz(d*0eAGT%M&&E za6%Jr4WC^)-ts-A z&(VT>9T#K+6eY{+eJGIS?NJRhao!k$*q=BLp_hKdc?a6{B+k*p(It92#D&P1R-)KoDkQgZ#E4+wI-BW9qQXg?f++n;EkK`;G?b^+~r67AR#>C$~Q zM1km;R=Vl^7bc7#ByH5>SVEIn>88eq6BA?enQS(H#FIv$x=lO5HaCgU9p z${^$Kpr4~<{HAWh9SKW``I9~@i1{<)N32QuQz7X6N&3&CmwqJugJ{>2q?-n%3;Q(? zCL(NFVW;=snlOlv@KKYuBs7T?c53{_gvJr@;)MP7Z~Ux>I2r4Qt$g0fR04@*#5B|M zasgmB4?$E=^EIF$#B&w(qe;it={H?ARYZe~83!jqnpO0t*7i?BZG(~(6*?0%7Sr6A zGz1mfgfUDP+w1`OzsjH)vC@4}Y7$5Ot(^R&nVefJ+jE8P$a~q!cp|T$-=c}UF`w`1 zbjll&&opumg?cd#jD?T{H?4kLi_!mUZv>+zaRmDb-}Fn#DnB(@{<%pkj2z_)PMT=fsAoVE zC2N=O)~FNaYC7vl{oR~ElhEWvO_VW+2NOGCQr=S}mvy7%UFR>s+NH75G+(M6lj-h) zs7KIVv&3sED;}sJc#%l6`$+`KVH?uz>yBM2%r&Yix2_(jS?V#P6o%)*hcMPx$rdb; zz7t3&leO)PpJfus%6UIkIpszDr11DD*(=X>#mh7~Q(~3k1`QwJG30IZ*ZFhP)q3%~ zma{)cNnloQ5SC%o6j6ndbVG)NX9StdsK)SPVO1V;NWQzB~65$in7)b4MQSi}%D>S>y2jUy%i6}#rK{WyjH!uSp zWlJ0r9Fbak?g8HAnlB~Ixh_E=-V0T7O_fhW zm*%Pm+(v;)_0~18_+~dR;OlBshTzdMA(M5NTERfzdj8HQi$%P{N>VBi60$RDiwk;gcDv(wi6&b}olE zVVY3m0x5H0OeS=E&OlwePX!v5KRkk8XpV@tY0gAO#|0WR&ol1UeBtlxz-%7Be z&WG@V^I`hoW|QXiB_a`hP#>Z9z3|6Yumg#*j54wG6K%!>6uVctURI7*0NRY*o<84eFf z7$Yfxw6*I^jfV$9Jk(N2De8KTgj$;o6^SdsW8%8T$5L@AjEb2{WJvCo;F%#gr5loS zFG9Gfsp(9miu!Z1CQQ*mGN#j(TPfMH@VzvXQG_ZdtLzgk8%PMMp83sLI1evw< zE4#!Xq)=shBdsQJk4UC<8ONI`rJcY5VV`bHcvFKo9fFuFrGz7s(v5635e(P*G>qc5 zbaEjEG9nFatPp8%Tm6salY)sqijt_2z(D#GChPJ33Aj!W(148DXo;x>E~GSG!KtND zm>-i?eXLaGWz)&hYz0J*#gQ~zp{A6&16THr~r}%1( z5PbQ!ngTx36LV+^_>gSDrhtc_wadKK%))88M_w!#yCuk5i&>O5)?lk0B~?C6o$7u2 zmTH-|3Y#yBT^(lta})iNb1T>-N9EMoJKj~olhjt5d_#(@ovdAmwe{E#)~JJZT*ymd3!Ou+`vi0cjKTJ>U%d_1?~+=fB3 z$B$!E9Q#4NNecEQV#m0o1nBbxdV=BuO^|^WMI4%45Zg>uy7@ZpSl@T=k)v!)f~gfZ z{XBw3Ok#XTF@B$s@gciHYf@LbQCI=8X8l1ivTnX$Hz_R(_E%;qc1feGcl>av#Qkvp zC2Mc?&g|X;)@i#@#oB>f{mS|?tM!e?->M#lXB%yiaKJrhRKdG1rjeV@%5DcNlbzB+HFx0S-fy86O+HA7+*d_LV# zJhv7pix=117LH)xac7}6&t3ZAyews)f<10w4~k48ei@=F70S2`?`%`yO3u6D5y|qWv0n-y zX3|~mpGEbj4g$GnffCJ1o2VR!CG?6Hf?9yCekFYs zzv{;r(8(vxZxEiahDSWquK0)9lsYGtt07EzclM>E8kf4n2lfx)PCd9?{CcBW3wd}( ztmnDs& z{FKM1IBD>y;Pt)4YrRzKt_7Gz_%i)ASF_na_e2)B*JA$KDTiaEfb$*#AX{C7R?7Je zl!SPn&}unU>?5iZe9Uk%32Ut!ZOSAdBTzv^pP)LQ*W4}r@B__JkALg|vMQR=QoQaOR(QamX+ zuTv88Lsf%H65x+vdp#|Y>w%?r-Ay-9;vFlsv>E{`3f3T(RWDjM-6XW#_>LUZEeV5C zh!d(Kcw56y$?xsVYa2$GRxL#zSb&kNWB5G7%z>!o>(aOdts{$b2IfczX%CI|8}HntwRm6zMt z0a8QRrGF5UvK9%MlBlBptnBnqeprK5tzWd8xoOk}lo&MY71ktN;sF8GdJlqygtK%F zEBkbnK0dJF2LT3=k#-V0Dd>z1)cv=bgC#^5L<|DLj$4_8q$!0q z`o0f;w#ZU0>H$$Ti{4IRQWy{~!KON8+)+#7pEh%4`!CwR$p!I$z ze2ka#1|mjK)Z`;2YVy&Pcdgc409j4u-b$UudL-#&?kx#)j@vo)GibK}nL9NInbSxV zC3RlH&?2PniR30JrS1(<>VlK|)8I{n&E6+5$dD|`M@kmuqbcw99)_Ta=jRB{0>$&Q z2@Hu7&o7|e0>tz5AjC6BwI~VwRKn;YB=i%>O;SqeE2V@==V>Wj%&=5`4+9HHrF^8M zQa+mUt~a8s$>HBo&kK~pzfB-}oE$D&yC69{I|w-p(V~2ub`-a+8#j_}Okr z6mI;FNURYSW(#Z&3A22pgjqhC@>IOJI3w58Aw#$=P#tbXAN_c$`xmAT4-Qlv1f`?& zVI*OI5&CdLa+8$$a7pSzAg&C!frH7r1p4pxT2Et0Au*PZlo-oLIMF81VE|!G0w1An z7bt-bCr~@iAeYc?0gl0o1|fj~B1K8x*@VGGNZ$j=O;Spq@?4`c3QL%}2%FW{Vvr$u zl#i4=%12Y)%_)YU38qK=FHkTqCom#TFyDc83lPjl1|gWy)QXbO#}h^wA)${YH%Td> z7o>gEGEMS2LG z|H%a2#tHp@N4o_G{Xnt^8i}GL{o@Hki;(nRO>Pn^>G3PM?BEq|`4}PTAEBzJzD}iZ z$4}xNq~mB&%2e`A0Pdz&14+DR(G$pt$?v0<`#BJ?uqckIc0Pl3BvA;`!%lzX=C*yosd*k9 z!fcox%s6D6KhzuUSGyP3nrJVZ7W`FIwFY|4x}dFazM+GP*_&`fs8Mj?)cGE|h})b% z@Jx~#Tr&1ICv21OyboTvn-e0K#|rhG4XD--dg5(~WHHK4x_?kyx;MGk zT6_eGc7wF^i}MvNo&D4+M=>B^8+j>4q53EmCdG!AW!`qS+W?jJxkkHIY_+?#)hw51 zpWvH9{sl(lz2%MWoh-+w363VkKR9qdjYvLZ1Y#6P4n-{)`KD}&E^TRQ<0u(H1PW^D zE*t-H9DdfBydTHe1ug~dx?xb*tu1U@=?to81GnU`wH@uI9Ja^<%Y*A%dZN(05&aYt za-NSKq#Q4Q1$SIf$@L12NUt|TIgOvYOhfP7e!Cfp$pFQ2dleNTXd`)_5k?%lohF)f zIY9c|MjXz<7;f+>8m~rEkA!q>fRVK82>x>Yy}DNNJkTc<@Ct z6q7XhP}10rKQTiwNfUm8OJ`a$=>2hXu*TA4z%QgE?{QB+{?!n<9va?bRMvd@1P zLNz#>>w+GfP)x$5hU;uZ_k6RKW6?BqQ`aM^`2Y^11All2MSUd|zY4gijp0OxRw zfL*2Ht0upwjvrD`!0l4l(joVC_1;kNcN_55MKPYgG^@~Nufm}BZAQ9Av}bFxj~mL> z{z{O|D;#HgCfK=bbdHXH%nyncK{6bnfa*ouG1{EBaQxpWownfDQL$69u*HMzQXa;; zg$wmcDR7WO#dX`Z>yuUSWvL)eEydUr=Wp$m^NvL=*nOJLP57D+8PYXbq6uvSQrvW& z)b!vS-M8dRJ{8>QyHj`x;wz;I6DjyR&g{%LTVgrzPRGH@net z0cc)Arcq@kPn~7`#&?0DY3d;falI=Z0cuIAeLYjs?*g|l#5C1r+=-CLD7V;qMl7%R z+^zCeFQ88N4&<#}4Ag)f4*?5WrgY^l#}dReE>U#`?YjK5z_pdy-HDgWPq8^el~_fL zi~HnKA#in^L)3(6gO9A;O30q-23bv(;SGMiK^8hjT|S3(nOua|ep+;gOM*14P7ByF zr&2rr=z1e&ya(Kl*d;hZ^ECmQr|x4jox1Bnl%(yT7X(MnC2C=x=0ih|fa&O^W;E_7 zbeas)ph|>BByx_v)uvW;)+$&p_q<4g_h8P#Tzw~vi$VpBt2<3V1XHh#U@F5yBiegH zL^D!=@$}tn%+Ru1(%5DV6gBLNKN~>lf6t&Au?z8sT1D~ag^{G|cdk%Vh4U#IUx-gQ zqb`C89GGC!j+i$z(*0J5bVfQbE`5g43tSpY)V#p_r`#Fpp)YoA1mQo)z#GvFv5Q)9 zcE#&$)Y<96T9Z0*YaQPnW=U-3@|AcUS}8wDG1GhA8Qj$n*fGqG7K@#f*{^eToJ&@+ zTZKxMACO50*Ig6clg8cehA7>a7x4GgNeVCEA49-`JWA&U{NDub#(4oNhZork*bH_O zHJKN%wVNkOy2CU#3Jry6Le2<;k{IV^tk%d-%D9iqXO8h5jvpZ02uhjG-VWvl{JmZR zErqOQO8%2Jj3RKL0fmwhL26`pR&_`&^xzn8v8N};H;M%^;ocfQ8|&P~7><2U?8pP> z+4Ltn+eZJpRv+UhUBLyc)y3CM;_(=| z=aJoNA>d&LH#onGRTF02{4IR`g|L0EvN`KPLhq%)n$^LtuPsJl?EqHSMxs{Ng0x_> z-D0k-JqqE{=c`nS)nceo&>9*Os|3%aUO^T0({~R`c-{xD;I*|d>mpXxHih5eb5HoO zvbdMz&=hCv#(jy$UOePF>a9yQ)87%;<-_;d9mf&2YW0aslvAkct&*ylW<%rK2 z31qYYpWz%2%2o8z%9qVhOmgKzNi&u|HA6AU6^BB(lYN?JN>6p0I(S}PE7w(EPaY6U zE3R5qiGM)}`9Flv3|jI2L`n_SIgRd@VofE@EXr3@HmrxU5xt@kHo#&%Wh0u9$LFu7 z2)haA@2pR~#PyWdPqyc#abh&iKyvESNCaaM1s4UWqnwZurgar|dNW&5LB^Jyv`42f zBg$o~FWKSffT)kqYHCpt()3_Eh^Om;vcEm#X>XuTQbcH02v|^>(?w`IiQA^Td)Ba; ziLNWu_n=)*d0LXhGu@-0^Q z_X0rZIs6l7A^h`^y#^|MhZdl6XTf=P*Q1Oxp*{1~AH@wHIP4Dl{N+U-foIC}KI+p# zlqm* z4H3EiV1D;nV{?^4a$FiEeV3|#AuId3+B*sDsg+-%OA=-Z z&gfOYL{~%bLA2tEZ62CmqN@~+M>Iwfid2i3ZH2-F8}U?&b2p}BbB@J1h*z9n2g1$- zefv0lJ0!o|gKw9e=dsVj?DGiwJjy=rWuM3BlL)amxsXS-?d5|{gbbu~UBj?5fGo(3 zQCW}x3}y`i)t>_`FaDA*ze2WBr9i$OY7`WGV*o_(Oj;HhzNO(sq3Ve-ef#yqq4K}z+{dyF-1-UW2?>HJM*U|d z2&u$vlNpLBM&(eH?=5p5D=|`SqTFc&q5C8~2u|)gDhZ}BR=azx9KK{Ej!~FeXbSH+ zC`{4mt;fw!Ov2;d9Tpz?A)OoCFUPKy87?+s9HGxvfOsh$pr_gVDw$wgQu@$ZCBS?~@DbXSKwfh!Ev}_>*p8 zR% zIC4D0Lwrh3B89oB)G98C^aC0t2k~F&>!K!+{s|CzP9h1k5V`xvZZ~=Oq^&qX=xTqHip)W#6uZz*gJwe1!(K8itD&*al!Ma$q|OA<$>(RFTHBLXCp< z&*=IPJd@Z*`T`jV?}`p=1AL1LYy&U&fvx^b`6kEZh!-5#D&jdWq+SOZ*gk4R)3~Vr zFq*=HNaLMW%ur0;DTkt*fw;i-q7j7d3ocz?+XIE(Xhh$rkUo@PED{~qzS9iF6pQpQ zmR~SK>5JRp*yPuYFw(Mnz=18NQE+nCQAse3vD)2hVEYv#ag4(BduW<6u>CVL6q7Ld zP|}!=@0+2Rgo#6m2yE#Geaaj>$5iObW{23oHt?&OJl1XsA{d;W`x7ZONatpZ|7i}m2+gp9cUQvPV%^_y0xvYo4c8oen5!jB0fCVKd zU0{1V(YKGl_Bm+RQ(#Msf0_bYU9SA7llYVz*oL{O)G98pJwc=7fvrVf7d5bb2oQP> zYz0~fY<*6<3`iQ zFEm}eCEeHi+9ZT8RVeb7P@|xMH2Ovaujk13H4>ipkt=vfH;#o7LGU$k@ABy>?q}uQ z{JbQ|fZlc$GKW0SJ7#e>RO;d)(pEE--`L`U3awk-196T=S z%SI5oH`!ZgOi=n=^X-8`e`!SDsIfkjG@nX+q=s%ur0GgF}f3 zjp>I4gvKd2cB5SL_2-e}xV`F(xV%cO{wBVsCY0$A(m}1#B|g2FDN%J^3Dd9?;c_p& z6_1=RVBj&4bKEp6g6NBAss+(v_Cyf{P7Tfv@w%xpX{i{RtmKM91<#6>tB8;5K6vcvqQgRQcs|lK;@^3XX+)IXj#an_Wuk z46%NiDtf)CS9&=fwcuw-MGi&W126qR^+17rcfVlQLZ=ki;H9{)iL!yHMjsg&VRzLY zIrf6%lkA&?|9n*dwdd?o1qvX4*;S~LDhlG$ZR{gC*KUFXxl#p(O0{yNULzB&X4~HN zT-NT`au|S}Oq>PX3O{U;et2Ph@TsWC(Dx(xtkJScW2JSp5^A-BWk}~Yu6_Avf3fy)v z_*~2)+{iJ26wFOgDVP8y7CrOw9f*SNC8o-}{4mrg=%pE5Z-QqM{-6T+=L!6kgy;R@ z6`XkqaV{d^vN7}u&$EP{s)=f7OOQE1I`?!$w<4jlj(RnwS9&a|zs`uJF}<<_O;e^< zcAKG?Vk8bFBH#{{)Zc9cp+_IRrB|qMzu)vq4-`6KMBk{EK9n@+l^2_#m?GjH#xiS$ z(igYG>6Nw-Mp||cIK9GY6rAXFR1yqitakUBUU{35I7VT53!0`(uRLXjViG1FN}BY_ zhs{t-!o;COq*v$%ec~KE$9Bl{3Y#^~@_WfuS|#>(HG%wg2+80C(w|tVfjZyByu1|Y zloWmv&!YSh#v7AG2~DyhdGg0-p(Rhm+=yV}FMOvXF?muti<}6y>HCL9xHwz7R&v?# z5Tzz|UpK9bm1@!vP0;xGg&R15fd`U27pAx9+?5$Wd#$&?=TC zm!5V!6a)XJR|Y!&hg$F)iz4!3odL0It2sFkobLR9TA+o1+DCSWDW=c@YN6Bv38;4f zcOFn*OW#;P&Awd$0riV#YzU-FmjGl2aOq#*`m2)z=rAks8${{;^-+Pe1_5!_Vjjx3 z5A4@{fmA{Cb3%=RbF``y${Ue0hRZKO)0E+I%?!ol^KvN3;qohtAaw6? z>B41>yYf-PrF;ph5zFH>`}Wd*em*Wu(D5!Kb&UG`4$kJFFh%c4{zWqsQvl^dNi&v@ znW313i9gGf{Cc)t9VHC+LzKZCLWkqjQruTIybVrqHgotP2H|eSE32(4?Xcrd!FV=XwQMS&H=yk3zPxsuYJ}QW^WQTqr zH9AaH0rmwOt?j0ZNDN)gu%OB<$apJjJs$#SU&8%4ReRJr)hraNwuO5ehD$!m`Ml)b z#J;`x{HS%YT!BPm5QS;CUPUEAb)Ma{({|atR>Q+*%Wc=XXz#iJE&+D4QYrxp(IKY+ zyU;10hdIx|o~v%%a=l$Gf#-DIBi6%x9_y^<{LW~Sb6Yz4ignqvuPdqemwu&!j{N^e zG!BHvpZjrOGoOMnp}f6oG~amjJD+y3)lTSHzb!bwX*Zi}2}`#Skqx@gYkgf6>dS0R z=~fzo4(?BRHzjJ01}BWOnoq-?ly9|#85)ifxcm}n24+$cWsQub-Zx4}GW6^re zNC@t%_05LX!yTa>k{LrV8DmBTV^Y~3Wfa0lr{>Wn$s=a>jWc$l|Rna&mM6*F=z-{4$96pzK1B)?z|%gPByCq^TZwP}{q# zRwWKmVR6_cBO?-q880{91{|eYbE{N6h-=vC$VF7R?RQ=zVQS-!R}$w9M9NLRI0=e6 z9z9*ngp1v5PXyiYYzCp+w|5 zqere>g4I`v87PVm`dr#m@-rH2zr{m*bitWJM=e3iV#VT_QDTujeR%Q($9&;PlU2Og zNJ69exDibu9BDG7`^->G8B!lgnz7tthGG&y4h4aliK|i=+aVT zO;gT$JbOBYF~?+2Bh0ACqP`TZ$f5F&O%yf`j^eNQ1|vC(iUd!khOAh*e$nFj23E`g z6CZuadhm+%yZUUdxNV2H9h_oyUFHt=xHmoUENbL@Mz>H*`^W{7Ok3d4xD~_zJbCEK zMX>H4)G%AU4%2a|14*5JHm025?W@r4+bT_t99Ij%=5#8<0 z>OFW>4%?8#UZxZ#OCb8WbpLwzM!|Ji0F0rCzeBf}!LQ?er!efy1 zyfuqWRZT!dmy`>o%~uL+jctx%Whd00{yWC@PvO;l2gDB>Y$LKwRa1=<@ojwYJ6R&Vt6q)@Zr&CI^6eLd&2mG z*j+=JT*9GbCdc;e*3e2C(uAd*ogy%QxQyiiE1&1QnaF6bSYuo^a?`X_wA&h|s<)h^ zv748oUc>&t19Id?v6g~m1FSBfa;k=;JF*z8J6p+UiJeSOnJ z3Tp)ZLk;UZTfOj~hY+?37%Rie!G=U^UGzn`Sg01;NM+ErIIR8%f%h3cin~=NPo2ee zDx|kF722@H*N?DlK&x%Yhw*v9@9s?9!fc^_L!az-UKj|{H0@TqNjoev?OL^R-nRA} zr*()qtkV>#d#nSv#^?ZX1Q^ZE=XXcb0|v|@dr_esmKYbeB|JTcA+Y%>Yn_!9hcvc1 zY&1g~6tdyx`B9>UwdV-cvUNet4)dJZkhd9D6F15*6Hu+VtmnFu^ef*ld3LGrp-zZ#v@@FuZb1!t5?!Oj1?E+>mT= z(_!3YZl$r6=nGT3xah|j+2~_toNWR1^^OU!m`KsEQ!Ml3$t109E4zyqk6T`|o);gr0(?y>Y zd3EURap9}eTGCJP?uR^D=l3TOZCc>-hrY!A$l^^1K0&9v!wZI?DD?LC=9aPloL>)- zN+p@RYkd_9o69_BWKm3~&S$9)Ug)?b{IY`S{8k85FiK2!R^X=@XM!#D_%iOg`_H3Y zPiF;EFt5%E>>d^cvaeK9L!0i2luRg>XSGp>39Pahack9n&`4eWD(`a08=~7gaS5Q^ z?3P34E$~tVGrVkXVVh9OUYn_?_B#XITg>U+?39+{%+HHR`=N+9&GG7c%-l(Nl0tz$dtKrJ=<+02&T& zH!nB#a%{p%WCz%~>3hHt#j1^xRgp5XKSD=$nP{6|8f3%knS5>!{>!*snguRS1_=34<@hK~5* zZkzL@?w1`|cnr*0FC8W4DdHjby9d#`vS{z?NjNmOY7YkgB$E@PJUQo8(;DwFQdpNk zn%=+2GZs?S&L%IY^*%Fb*oB9N%FtwkD@_dZ?!%AEdW6 z>#{thaoHSN1LR1_Qg`Px^~(**IU?s<|l4+<4!Y`qVrtvi2WKu$UyR zLCv^u0g+djG?Z%@p^KGwoWD z9h((ABuhU<>-4=xPM*nH$6M^IER-$f^C$1U6J?ZCCRv-e>L@5eJyZdeRDmu>{W2&j za=g@N`p3#k!DlCrOYrbit6A$ow~c@2Ew_!LNbwuWnu@2nkn^CVkoq7X)#RH$B;HIoh~k=CT5(sYkKFc6H_Gj7*)3EZ^#GZF4`(G zADR#OL;5K;ZVyG3ETjH|6dp^kIOQ(P?B8LnoL|xK?Jh`p$zmD{Milbo4=x0XM!Ml^ zf~3=Y=F~+f+o=!EneJ*06@Xn60u^+G(`8q-p%0TE7w1A-Xs5f-^=T(Q!n|C5%IUEP zx3I-ptI3|)uT`9c`fVN`Ri`EdzuSS?g3({6;BkP9Hd5a1%gN>gH=zh z{U#Qmoa_a*PSNLrqJ%@Z03{ba5%oO*g=a+(0xvoou5uXVqZp-WNBRItL?$FLBe|GZ zA~M%67mrB5!4>@CfVmjuMhH$W2`tIYpICfu-Ybc!ann%v@uq&ccRUJ^QvSrkk9YSA z_=E%;6r0+B#U{bG6B4x4V5Km9VDW|Nq$G9xWM?ch;H3luEHH51g*0w|wFS??VEkz~ zP+Rc3OXHL;jYH0*Qq9IceuE>|d165<(|o!whm4R`VzkyzeF!~YiA0Fj@_&U|23_H( zfQ4W&=kkf@2kE3v6UY3WSzcPD@=h0tzLM$&uy(oVisn zc}!v7w^9U)!Q?Tu`im5;g2`iQ6_-5z*a}J>BSG3%?ilUb)wVom@kcm)98P&Z{W;^5 z7)AM!B2nCxhgx`cI3-pIZc1cIIBhkZMXA@c>DG5>^4)7{qi5;9!V<12c@>T)x%5cc z8AO&fB_{E*EU_6xMl~fii835YqxK`ep(G`ycV>$D^saK0*YA*-u$6d0oAGa?UwTSW zUWXQ@R2MB-22+#|WB%B|S|y|?bBnBDj4b48-LpVI8UgPN5wM>`54>|FpyA0*9gRIE>3CLPw$BnceGesg*L}nctHI-3jka#s97( zCv&aIDdMM=I}f4;Kk~rxThQ7hI&lYnTN0``5#TkZZuo)U8~g*m3q-_@JnkC=3?KLH zAK}mjJBbzWodRbxtUJc96mf;&RSy(lj!6`g&Tb_c@Xu8cj8`7x>jF#L$4}GEkR_ad zPUnB^+e^oW6{=OcYB}|3*P5QU!UqZBwS2x(@{dYF9UU)eyn0qYU1~Aanb0`3 z1op-NWI?PUiH+9FqoH2ngC07|A06e&cz)y{OQe>j|9%V!`XDA=TgNj7Mf%trtM;Z&RIjXe3EUMI(hh*#{)q zwKt=~yM31b8|A2=x;M&6H_L^b4=UJM)0l6Sv3Hs*PVkhDCjxS>`Ofzxng>>ft7^@e z!C{q_+|optoICpHQ?nJX3)u?I;%vsQe5wr>poPs@uRYazdQ+&UWUE84W35gYvD8+l z6QF)DfcTGTOb+qDHy9y(ofy%Eo?uhdp{&dhD3xY={elLAHW=VM+WcOA^kx1zd8zK+ zj}ppK&;G-b=-E30oYwU0M50b$4iW2b{vpAMbNgNWb*gE|uZJ{5)3qLE_fLm?qwq*CkIlL6`n_3RhY=oxqrr)Fs$BT4N&$klB61?=l zzo)0$yZ@l+dJ>wYNBQn$ahAL|q%A1%J>Ju^>>1*op5^q7-P6Oq4dR}jAZGe%g*Kco ztO^E=!AAaYc46q=rpA1!fRlrZ%(t!vw+uT6hk^elIh@UMIP6=$!}$pHR^6_ z7TcA^+Abo9vC=gCiem!Hr)+V+plZVJ(-ci`kTRVJEt_~7#V^=hT_}N8A*5DnNUido zfqhbTv6aJ~Xu7PkXqR%+^SK(X&+PnU$GHi#EZ^&TJ0jnzl<-dZ6kgEBCJ+{Iy)|yV z-7MlRmj}G<;^rNtS9b7Gk{EZ#wQeqvdhXt~_mgnMM^F43Id z*QB>WJgMS9MH~sV4!%eWlyE!VR@_U_ZdIyo7R1GY-!*JEU-J;%ORDzXm+s!Ke z+SsZWbJK<5dHNNotOm}_`Hr`32HntMy4sIVcpF`ip3=IP=+0N1Z?uWdgKE(ENOT~# ziw>{jPwXl?qT9{Uf#&k)BEa(-_$MEMEKR#nZ?wYSy2Q~?!R zg;v`YFRk`gm+a~GEWmA|jqw!3qF=i(hbKc|N71Gs5q@mrTMlwkr)z0D-cY+WGxqE; zoa)r^b_RdU;ab#s6B{&Zr7A=jVt}y~apcoxPI;}D)vT9}cYWxWP#4Z=h@-cO_p5La zt#<~02sQS$&=_!QoLdHosu)wp+gQcz4DG@!g$lTA$Xj98I^I=HyI60QawWR~$pILg zat=DI!8ubi7$zu!N!}NT@)~awdBi#O$Xfxm@HUp(bB&x7Nf4GjpyTJ8wL+CT+{~UZ zVJGgZM-A6*m+HAob0kn(pm}xI&gm_glt5=MRNP97iR@+^u*I&X#Xx*4*9ujE+HJsK zTqp+ueRgKnM*p=OPL${8WnDwx0K~O%;{tTkuF>6kC9r&bvv5)Vd1AS@mGljgQ=5e# zRx8M2bWU#B1ru#Ilo6r9Y@jE!^)g4Q;ayv9wHoe$@o_wMvpfXN)|<1?sH^Esh5+8o z;q-Q}rv-Mb!}GCQ_dW~s+|-;YhPtOGs{7kS_mmbRkv(rWvlY8F!(hN5jF($;RR9~p zdHB$xQmfoKvCg|5GC7x%$|64z;B0Kz$*Jm0GC+-Du@nh32dc>x8*)E0BU@ zwP0@12M$^$YhK=7-r#!M0zc7=LchUcA)+J;Rj$`zA#e09`Lhh4l1@1m&GOgN$1AYC)|sb|KctVZ(#Hoec{{&IAJ3wXef04@`gkvWTuaTa zrjOr-cj0^vA8l`lfUr9tXg*?Ko?uWeGYFSi@0VH6msziuS&x@lZdNNg7l3)ArmLw<80TT@b@4L(3QFAdR3|udv$y0p?rHN(BqJR${KA8^pDx9+3LXd zTpjMq{{>D13%uH7UNJ)>@l)kujVXBTQRE}(`P;zQ*L6@d>vjl;3p+5wWPDbD4 z&bzQZ;<0v0kAKyKT3N9ltZ|13IaXL_eg_fLO?DeNsW;VDGuzHCugDIc#dJ#UiG^Eg zk!HmN19RM*w_5h4mdw*uTDT0|ty{RIx7m_Mq|PoHGS z3)A9@x7znemiwnWdaI64KK~RqT6X8ePU$AHwHyi)evOder%WRW|1ta(+-0xjO@8bd z@&`RL2;1vz?rrTq*G-sa#!RJN8xOuIg;3;-5PEoa9cnFZWZ4 zeV3H$lKcH$|4jD`b{n(clBQi&6`|Rg?tZV|d;R*o*RQ*OV&MHRd};&xFWC__ylQ>9 zWIIkhblj+&Y<0p)tLE0@_WRoV-`W1L_Czw=v@b?st66c|$p)0Dc-6pZy7l%PK8{iO zxEe$y(ZEenYaxoOFs5M8+w%sJ50pyQJEB&j5jNwp7l!AfcUaY$z2wf! zSY8}AqM7OGYHg_$c~v)XqEa=SUa*}dclx!54^D}Ccw{xF7J`;LV&T7;*lsSlv1%f$ zyKzuzy0#O0F7RXxx%KITr9-bT-80RqPgm=XyIk_(S}@Y~hUVJdFgolmy67M}EQtW5 zw`UNRWd|7EdbxLf?|Z_KOGfNCZdMmsG5AV+++1}c@GKds1a=hRC7uWBc8x!6by^Me zW5KSR2NhVE&2|vCz1_*?R;2 zx`^z8{FfVH6fZU1XexSpFvG>EYmWxdqpH(RM)gVz%j1ANK#;v-_&1%Lf>_tw=?J{H zo6~WvG0pluP2xXY3F~#YLZfloStx72WV6%2r3Biy8RkO~FRY>N4bm8yK(_`k#)HfN znS2T(ty*8Q7Mo$sIwQJgwIX^qKQCXD0Aqfhb(q@Oa?V{QWs~eNRd2t$&6E*(`YiA1 ztaoUZV;NLqw`m6&mdVbJ;JmqHRL4SV)X^L5E?UIz1;U}V;C7WiD)cl8o#kzMXTYK@ z4Ao)Cuf5c?8=j8r+w#a-b`XRYBddX7O0=uAq8JoC`{d)++gomPWzxc+ww%z7ta=z* zLN8g>dZp=->b3(biklc|OI0^22})PEHishXnK&=@Mw}PW*STc2&%ICr@PdwxnGMSr zn?(LW$qp_t7wu*pgw>f)Iup2dNKJH5g`F1K>0&! zjan%#RQMN6{iBvYAX?t4$<{xX0+GCYHpT#?(P&j8%UyP1CAkhH%Q|p?ERv~Y}w0w#J*TRe=_u(H}vdTZ^qa)7#NBX2pu@v@mg47pt*nojm#Y^vNe4 z$A}m3A^?f1jypL5t7iqx)UAqV*Oy!ildnw}06>%dSb)TH{h&no`wjC6=$c!)5?d4e zujztM^D=Z~m{kH}S>EtTc`g}I4Aa{Crrut5Amzu)s}PZ|F|GJho%#LhPkiuda$M=s z3{i^d(lK=AD@6y#s5UsBl7mAU5zojM$=KNyOk2xxDwBlIzfSS?2R3flP@Mjljr~n& z4K^ddg4AUGp40VUg-x!QG5NT{;IS4%nuysTTQz2B zl3HrF)+;uzZChvsfg6{DDl9l{Qb=vKVfs>r-l=JyliOeY(7V6=2mc5r+SyZ&pE_~Q zD#1K`=&93B(1Wnl6s`ONge)@x=aL~&RSgS&GS?SR86GzoABfZnvQfE9iB&CeGDHkaIPE~ruc*&MvJsZJOcrTR@E;S;R9v0a_& zRHYl$kG=ZK36o*d-5MZi~YFo=dh+ zOB!J>IUeou_{w#_! z7oA5%lmUtu97W_(*L`F}dJ)#q^d(bZBhDgT$*<(mPp}Z&zJ2@H|G9o+j})1!I+N-1 zd3QzqIC1RkxrqxlB6y@5V?+Y>TkNMQ(rQr5$SK<~Yl7ybv@J4?OSP5QUI=&y@R>(W zJ#{Mm3{b#woJz$Ab1sduFTjzv9yTZv5*(&PsY6J zk{94#Tt@twLbar{{)omjS6a7eLp-Cjwz^WCD0QX3Gbk$35e7xI<0-ci*5JKj?9mvJ z`fAmCrMCVuRYFBb)OlfJ!jnj;YceaOTFp7y;W_(pRTY`nc`%ki z3c4t=f1TD~$j$&+Wh8v;(Rviaskg$#G-#LFx(75{+Ja1AX(25H2BJ&66BdiUD=Qpt z4^7gy!;{<)FO8h@5KBO}TNQXIc_{|6sUk% zFVgHGQLf84%v(qKK~Cewpm(e@_KJn23xv&}b}WeQ{QN}^YjLpNnO(rRL?NGDt4eAd zxA%!rao zDJ_R$ST}4TV93?bLWtm_K^H}sJi2J2NefGXR*hI4P{^|e&=*__&@oTeY^=MIA|?IC zmODx#r=o^isUjY_Lh(_2Kp4ixKel!YRTz>+6?R&Ls7+JO3!niHx~5#0ok#_#)EhZ9 zIe9*1)G9rB=luMdRG0F1#~LLyh?N|~wI!2qWRkf(!(^>ZLoc8$yUBt?{>IJ)wDw!; z$N3K>v%ViCP9uSV#0AvRd)80Dt&)HSWXuXlP8-KH#gv^k8urRmsiuDi&JD=`(`1^H&O_u~mza|w%#cmQs*%G%-omG;QN3-Ukv1e-kkwOJ)$jyhj?tHL zR7LtljO8S)OmdxciIqnz^yP+R2die+5jJDKfnM+a=a1Y;PW+5D-dJ&LOyU)*hPEH26UrNCxAMBEv!4ou87^Yip zhKN^`8Y?rD%>n*n-p53l595?F>#U_TN+aucV(y{GHZnmR<=k}~mXo8_5R!wvLXpJp zy`tjP|4xb5$!C~Iwn2KQ;8HPUlkJ2%%WY!6Gpp8F-uy$JH~!z#8%cy=MuZ#%{I5_E z$S91@PUAyiJ+|58=}aVB32Tn>R)Vw^wxiVEVP8p-hOMR*kN3)<4syU0DKf=?Au_fI z&v3zq*PTrPNCq*eTS)#%cybYpN|{%*hU_5}!Wh){29vFeSgm4xXQWL=%5hjJlN}ee z{c-e}z5Z#W2CxsRB#o{=BW6segaCQIKyTn*YGMqu$dk~Nf?Ro2uKQg=$6L(AT&&Wn z`wBB5Wx5S%S;6;Wg_R^6HcCQH&5(j{_~Yk1#JVh@VR+^br5TV1 z#|eu>gs{F?U1}jOi6Tmj#5^s6)R;!>5lU+ak>J#HgB7OYgjX|88BxN@r}s6N@*1j9 zP2@OXV7~Ke;DN<@115_5$9mAx=MOWSLEf8Gn7!2 ze$Ghg3I8;{Cj&@w2I(@>0#pelGx={#8<0G>5%N8k45u{IEC^M2mEVVjyOgLJvE?G~ zT(Y~vif#pUSx?vRV=KCDA5s_9u0#W@nIY57M@!Qn@c=f z7?gLTm{NkLD7rMov$LZq-&v@zg!u>N%SbY_t3gPic*q>-|6270H&y2=BJ&NptAxZi zu6snFJA#rAfDMIlkOwzEUqKppZ~`f5^YeT2dguqXj}*(is|W+NQTNYt;^gg073wor zh|f|TU$9}>-!p}(}Q}z?p zlc|N3DK?_CsJZ$|!%u$Ivt?<+g8y#`PvCrd7G?^wo7Uxlrjv$khcg~V<+RUaBQ|Gg({XgU>=GM8-J ztF@EIru(eLXQ@}>y&2q~nz`s0GB8>EZL&Q_Tl znwiJ*8&Yffgvf1Zx=ZCWzoBz(!|J?-jyVl{%feZjSGd$1-Nzz=Sl*Yp4#^KdPxurM zZtd8zMrJ^o0QTAX=~#012Ysk=Ue&qx0AD%-(H9Q56;GIoA@YV$x{Ew zJX8Pkt4K59e^Gmv+$ntpJk$^ldF`~)&959M7N+Y!Rt9Fd@_ai{WwOpW3G~8B-y!oB=H#O>JQ2va?}2I@`on; zn*qVU1%EQmdRE(N#coB(w$q2+KksONmQOP2q#^z{OS5xc92aF&x zE;Mb37Mrt@O^p?74Z{oOR{Im+Qf3ntgTgj!$%rcQ-iEPeA!5cXw2S1pl^GrhW>H?~ z9qiQVi9)|=M85!q{AIKtiIi-7hcqPdq?r9L%us#`pI2#y-nsoJW+*28)0Nv>W-(1@ zJ^8W`MmKi*A4N6A?qoC81Nde=x#^C>xjcqa~>UaYAV81oz}wxn2Sy#1Y#I!0;w z4ys~QXQk$Xf;w2px*wA7>>RfWAnpcu?vaqDk z{6x!h^L<`;?_eLZgCzW80yPA!)ShDdQMQ?w<%?4ev`EqBu~%fefr>bLq`6|z9t%o} zWqO9hN{Hhk|T(Q7Y zqsCJ?Asa#u^ga7Byw0g9>rvWLAXdF)L;<@1#;eZwB&SL?Q1^yOON-HA2La{UW@B_S zBWU*&JCXoO;*eCnNThxAB(yV~-g1bRa}M7#qBg4XT?}k3&f-a*P$3T-Mag5cC$YoJ zI$|BZH`DOpP7NQvms-B(V1brVG;R8xLz$*u->K<)7}EC@K^jHVrtiyQ{f16W-^Z}V zmaPJ4qpWNj)Ik-({9za49p|!C=!_9cP)^A|+quaRPUBUq2gN58f34S96SGr?_FD(& z#1|xe?;j!9t7;uNs)+#M^9;+Oo}cHnpgpBh z#<%%-Icfh;Xn!cQr&f#KwJNmSBEUrRVW?u$#y;gK_+LPX{t^$=Z^qJi+y67X(D&@g zi64i^_@v@Z(Lo&H6P2Cn1sr6v6X7W2;zGq^6K@ zRn|X_E`-jC_JZcC^O=bX=iO)qjlU1>g7gv0>k1-4!ni(>a^$mxIYJnUwFhZusaf@P zChAkoDHvSTA>s7MXm@r3pWsoi;WP@5U7IpXxy&QbnMh?aD57Ql*N9C+2%7L{+1KeO zOZwD**TX5E|BaK4>Y;obldq>J*Lwx59~B?v?_DTC9LDb`EwrN^%6GCHwM6hDH;!TK zuv8lVb1*NMM=zEd#c>krUIFosf`rK&j1jd067_xUd7OKUlnPhc8O##XSs9JDwy35yK%uu8(KilE0VNxb?!34Wn4$EE+b@`* ztc%4_|4(6fthuJ~&}^%= zfL-v2xccD&Vj?*Dkpd!V&NPBobQ&bvoz8<{!rG>TH4%4ViyAT>~% z`wSIdZ5tzjp!T4qkvMxL;Hvsipi+%p)bE< zhO#bh=RLGf8DSJHxihSWo2GX^`jQ@LkdzN*P%*VZh8+1Xi_?&2b)I_XcL z|5w8|Tgx)yj@fI`+0KqxW@?pQ+1)5k#;fqkuSk*0$C7t9HKA|5Szhz%*_0YX%&%H!4|=>Mv`XA zoLMhec0xc0z*>NEDkm{&!c1=Nh96I~NjJjz_fZ?%s4->RzbOYQE1VPF@A1_w97sy1 zeXQOfPnkIP5ED%&pVFO6a5UTAXEM|E3DjGaKD-TOJKg4y%@b#0mdg|O{&k0xMhF*O z^N~E?Z$~#{JaR9W*v2xFhA3l#gheTVJWJI8&d)1^G13YBXNWVr;RE!@jxV`V?(+g5 z6kFq_(JI_$iIzgPsQWAjcVFD+72wX@=NIUSxzFtBT5z9Pn#?uqK5O`0Dfc-8;tIRZ z?-L_M3Ra=~{$I*f%IZL)g+=gscAtMHg?BBx&l$FLa-TB=g!`<&Q_AnR(+*#@`>bf^ zze26{(|!Jo5ly4}{7FqFclgHCTU&hQ(*2V3-`@F{pqbR%k>^^fEbvJW5 z`Z@(hcdzdAjFC7-VY(Mp3%bvbn4y@2NkA#$K0j@SViG0}rGxuSA1=dvF2FJ6AhX$_ ztNWb!swR(m4#Dg=)up?*NGY-O-wS2)y>g%R*^;}@??Pw0xX*fF=|2A?ifit(oDt!Z zB|?X;=|2DHSvThQ1@LPovz{~gsTucqo!;`%%Yvf$QSZ%3gPIJByT)y5V{O;?136Gx z!7Jt(|2#2h9j@`OqMn+H#m?HUuCad(-1bR!{I}8*JB{?Jdc(SK3a6Dg)y*5ub5V`Q zZr<=G(3)uYJ@j}*z2QFsgr2=&i59$JfoxH4SdLX`iDaq4^NR~FCVvUIb8q;|^u)Yj z_H+$+!$>m5rNy}UovwW^r`N3YeO>vvKCCNuMztS4^8OA>%-x_}VR!jXrwSIAxd zZmv>RFB&Z>g4eUV{9jXe$!}k~tIgdw*uitYv0GU_Cw2R*OrmWgS(`1(fu0hE6>&|%Ad}jhVmvO2;Btj&7USH>rEK$ zfkICh(KilQ0i}pP{k$29$pz}6FI6*?b#XiIPhT{`D9Y|W`_r68S<^sArK9sxV08ED zPru(t9HTJ(9I6)dr$1tbViG0+rHDWMJ7y>*Vd79a_|x>^GW_WR9J?c3ju!LCqp+sV z$~$Y-!t|Qu)P(YHa!6+@|On8B}xkKKc1#og^cN54C}+ubH%>3RPJYHFUh zoIBxp|CKOT*7UqTcx(~(b6GA9R%Q#)3$*=Pu#^@M`L0gd5l>6jO}B=lovo9{*`ECc z7wg*WreeO_Ei;eFU&j%!bQK^!<5QfmjOB6L;^%I%^OtqoyNOR{9-pRD_Sto?`_l~N zYZyZ%yWc7-yJBTEp|xn*+m2*_zTA*f>SoXJ)!SDEf^de_+4+ax3T87tfpxg#EO2q5 zW~|tcA&ooDsxPpE=Wtew2TUIT0 z*D!)h5-gpuuE1ta!AO3xlLEw{w(_RoQ)tl$7PVB;*ExJ-Hd`;NxVmOFkwt9NSlbCUD{O2E}%Q+@=VI zx^;G4N6re77|!zM3Z?e%Tca;o%SYrUNRdG_Mj*S_MaHD7!!rM+DSdx>2RyC()kiLl7o|VLxIo>9r=>F|;`#S2*Ny z7IL85hlN5e&*v&-wWrbOCU_>v&yF6k7yEPyFZqpYH{_CIT!%=@_FNGjV9C8zlT;(^ z_GRZOC<^)^wQ7v3U|qeJjo9xpqG^n)yojm=<0?OEhGKG#IFwFtm295Ehm9a~x1+bX z%H`xK^gy9c7|}P5N&%%vT;k-F=R$a2jPNc^#EC z^kdi2-D_OsAB@B?3e$H{wP0Lj>u7fN$xS3CVG>Y^#8qxILoo>xhteUgLLc;bv;Q2s zDUGYJS>qhHi&Q+N>vuJQl#Cr;pIE7m`fmXZu0|YXZ7Yd~PmZIzox>-&DON^JPN0Go zHIXwT+=#~od)JJbIJm`+PM8xLu_GLGT6cH?gVU_y;x3$f7zEZu8`s-W?!S%0@8Ih( z?;5VQZ~{vsKjJn++!BaO#3;N%_k~ARJ+uPcQ5C1I_SFHVPzD-*)Ny^bo(qYZJ@ZjayXo;3r4b#0C(ds#Td?Dbmon*lj)&skbG`D*lgXdZJZU+m`{GHz2;8|R{p0k+JZbiH4S3SeF1irNh%UG5w-^0* zAH3)kyMB1kdpcwiNkWcV(RB6Zh5_8C`iih*OdApI~zlZ)4;3tWM}* zc>f>H=BzMvzMf-;8H&ks5>Sft-H{_Gthpmn?Z6%RIYF^C-I04AqN(4SwVL5YaXdPVRk3tiX*DC- zeoSXZ)oBSA)@+kb(n)RC^;TEBJzT{lRV;kkah4BAw|-Gv7QP8oOMjptf*zW-Wz z4dez?>x)6ltygeEh1-P3Rm$I=jT)I9X)h#eje8bcvT2AI$^0Ag-K%E-yowt7gT$&eb}dorV=~!DF5EoZl1LTKV0@=qp!ZixyW>;jC9G z=(y{7RL%(czxqL7GoODko4kKjZ`c6_-(#`Wc7!x@%+qcq*_oGaDI%k~&}(hIkZUVz zO5~L(oqa&i!Tq^p@0ue=gOkTuO{RG>N+@lqhUnb*RRTAEl}6-T@;U(tBCzJ@*Sh=j zIkIXj+BQF*DhbJ*A@N3Q;TLl)tj!pL$rv*!8B<92D5a1`x-gH%QXZMB&^5ib}sHupe_@|9bp72kDEOdhbBgg8aNL~gJ|1fPrlGb8EaJ!jQnxv@~ zxJTQ&tbQ@Yp_5PCNr~iPc9)xEG{&7x5iVk($(SA1kHbO^`+tza)cW0hkoX@YQjQ66 zk`(oSiQWz{;cAmzOa$kCgKTxD5LDR3S18-(JLpNzF-kdh)QH+=irP@$M7EDK^!pN{ z92%1`%B}RoVwCKuA2G`ALZNl%3-aOyabrWal1N@+*RrZRD(EV(AUmA+xuiFw-MKOC z4trW}cU~dvR$+!KF!*)!xEmK4Ry08LS9jsn-E$<5-jm*8?<^Nc?+lFchjk`TBoo4# zoFb!psu4!>y1itCgYR?2l)=j|VB`x79kcQQ(D>R77kpp17K1@taToF!abEFQL+6}Ro3 zNp?%cK>O=(#XEI9LL(E&Kpe()(0*(~G9t_1vY5vr%wT~|)+p#bh(_zHRB>7YGfz&AJ^Br*^P6OxTJ0>Qy6hTTWPBu8~0P z#K;RH7n@O4O(rEs_q|Y!s$67uAOo0rzUYX+a!3vp47*WuCc@z07lcDs@@-A~V*2y3jma(=Z;+h&5(JSgM7N*VIgS&g-3IcHg%V+s zNN;Gn02>;~&0ZWgqM7MwypBqZmDmgGrLeh#5p@HVWC&mom9Y#C_QYVvR=l5j?ZG#I zo@33$O0Ic&qnf{4Hcy)iNMz5u%~I8k7a0sFglR9X1pqb-qXeVKiM{q?Ta()$leKa> zIMWW4qXtRHrDSKcg1au4%k0Y1>XHZf-4cW(`lAtUDvir;Z(&qNAk_8jH~(1eB}{Bj8Cqxp}a7JhV*tv1Z6S(PRH<79oEcgOsXgC2vGJiM0r=-*VI8F9<(Vv$Q+wy;% z{(P1Ge1-n}7UpoDuAlVpp+AS|&qwLcuhE}7sM;R-^A*^R{ulA5l?)RQcE=RWM-0qM z49X=2;Sy{85^MPqYxNRq@e*t85^L$DmUlbqVn#g6P{aQiUYitN^u+M`Iz2JG{+gZ` zUT-Ai7+z$9_;(O;U!o_%(El9$p}U*?lk|sfJMify9d@>+PbW6BV?um7?8K+dOg?SX zV(B<5xK9gMtz^3{@tiI2&X{_jQO4hpVw2@Kv-LN;98?EJNNphu(m}+E?#{hT zOAlvS9F|a7rRy^7V+L-XgET%*;dyx6qS}6U0PX8EL literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/postgres/types.doctree b/mddocs/doctrees/connection/db_connection/postgres/types.doctree new file mode 100644 index 0000000000000000000000000000000000000000..aca6b7a332f8fb1628bc2938b88b671d1d63069c GIT binary patch literal 113678 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 X1DO%%UtP#$Ljdl@syI@u4yFGeaL%bY literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/postgres/write.doctree b/mddocs/doctrees/connection/db_connection/postgres/write.doctree new file mode 100644 index 0000000000000000000000000000000000000000..6466a1cafafd18d01fbcb5820de48f0f18d50f95 GIT binary patch literal 57630 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 T=_@3(@iO&L!kP2AYX1KN0g^|A literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/teradata/execute.doctree b/mddocs/doctrees/connection/db_connection/teradata/execute.doctree new file mode 100644 index 0000000000000000000000000000000000000000..d0ef0fba86ef501b4bc64e2464f0ed0a70d1fc8e GIT binary patch literal 50051 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 Do!yE< literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/teradata/index.doctree b/mddocs/doctrees/connection/db_connection/teradata/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..f043366129fca97bd4e578d816cb0c5d96d7aecf GIT binary patch literal 4805 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 E4~gkyWB>pF literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/db_connection/teradata/sql.doctree b/mddocs/doctrees/connection/db_connection/teradata/sql.doctree new file mode 100644 index 0000000000000000000000000000000000000000..b73e74ee29dca5c4fa3f487a54bb555e5e9fdf8e GIT binary patch literal 49004 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/file_connection/ftps.doctree b/mddocs/doctrees/connection/file_connection/ftps.doctree new file mode 100644 index 0000000000000000000000000000000000000000..a01a5b209fad850cdcb5a9406ba467b910bb227c GIT binary patch literal 201889 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%0DOC_YmXnU+y|A78N=MXpGymVs2&M4NUJWSTLJ z@dGTeOdn`R|6&KPHB|^K%(x5NU{k2?aj>|WeJY+?9(2mtS;l#y-!{y=sh$HhFQiRRa&?mXyS(2Gake09>|e zAk-5Aphz`P#Nt!~59HRW!FPxPylSPT?06Re-n)U$aOX?`UX52x+6!PO%>Zxv*%;kz zf=jUDy6NtL4s2pDSHxju1QucVIH`JLx;j>XKq?$O0|~nD8TJn*AsE`XeaAWHo~Lx; zVW>9E|Oofz#0e!%j}01)Mf@tcL-^w|giw=eSP5u#>j_Jc*}(q1q)qQ3S}Pm-?!m%Rj)NWZM)z_V=9>w*9IjD)y&3{cuM9ew{MMx4V)ZJ@_TSf!PR& z{4HC@jU5~9ky!ErR>!j_GNL`yc+Vn#{X7xfhJ<<&`P&GzK`r9TRJK%mfxNU1c2eMz z*0YQFKKLVlvT;Wi{E@%Mv+c985ZZ;CSQHtuKwSul{EhSNcRS2;iu_6PZJ(E?yY|~2 zrz|3WKNotQJ%jFy{AqKW{0xnYC-N72O6tq!hvjoSCAAGiaXa*CYD0lJFI)5&B;NpD z%~N1*h2fm(P{+Rn;#F8qd0Q2flW{yX`ACm(RD%ig&cDNPm3v3IA-M}KtCU9%7OLYa z4p$ya%lS(B620s*8~Z~WFwEGy9f6sj;M|7@OMGxn(U&*5rQqC=I&Hzw+=D3-9wDK* ze{f{Owrv5Sx$i&&oZ>T~xo@VplW*Y19*5?rO$^Q9u)zFmenWFh9>vYjB6;BMqOK%O>q_ zm{1%^5r*PU5pBkY;)Wf(-Gt&Ei*2x>xIGRQSChj-al1u7ZFp0BnNZxd02&*LL+=|D ziZg}+S(@iAC=`dIMj8g%l^2RT#*%fl$>luND};mCcx>p6`hlUhis(l+tBuf`0VM^I z7hej!89X~N02gh^8c^#3plC}LZCT!+Q0R>iVCd~AfTHPBFG6pxq2|=q7tdRYSKNa@ z*;e>cbs`Q<+@>%G0!}F~rfY+6G6J25P^iP9+>OIkNc}dV7($M6ub{uEJ2ctByz{zh zP!QX1SEc5j3G~h=wo?u2UjdLf6X>K&#v0RDeB0DDo5FyDoId3=z>9`U;|gPF8LD`# zq(BdeKSl%pLh!(6m&{G*xESd7M4No~D9}TcU<_1J!qabC6{P=918~_^1)=T_fFi4c zx*2CxFa{c9RaDS3GX}cqcw6##&XP@8fk4?dgx z%36<@h~b4H^ZusiPS*nl)1LEMMQ0IEzc4U;Z9u(1(=Fk;p`C%XB*Kt+HliR zY`85NZjeq(e{)_PGJ@XnJV!F_^NvT#H;m1zfK$=o=uS>;1WuVVfgXmV|G-0`xz1C2 zwp72`N!zA8iKTF~+96ePXA5h7IQm^cs0>HH8$XF~wD|P!g`+P90Yw?>_BQDqaq(zG zHHUz78rK#i;|j)iYqYrf*&C5wO1)KC6@eH>f#eP`N#-FQsX@fN}s?J(QUM{Cqe18KwC&q zdJEqNe^6RB?#O~aD19N@J{yz{?ZV?&6dAHWT`*S-X~m;cj(Q{Cez(Isr=YYX-}Zdj zL1`)Y_S+uUEP~SC6MCLKd+rQMYkQk~7d5{JLFs4D56kBkl)e>+;?`|yfn>2$XG(ALEbj;&iP?A?XXEF=R)hx5c(4cAH`Y z(f391nS#83D1-?@7VGRlwB)2={P9IoudHxY$YnzBavI=O%q1@)2JKSCYaqpXNYpVJ z`}?B%J{x52JjcbL|30HS3DJWHJc zPxLnZR>ac`V`hXtJAoifYK1%>0!(Sp^8j!wI_TNSt$7T3PJ1Xc7ZGYtkb<6_w1oye z)%Kf`JzHS&gPv_5R0cg?f}cdtQ+#^(f}T5Ta9z|~s&LPVh1__>V^3_bfHXdVi)$x`F7>KX<6hl#WxlA)ep2+yAPx@dt$-|e*Z9qJ;ILM(;ass zzaPqqoSi8>J_(EY9xO zl>irLly2rxa7IaD$=9VSiy}i1sc~(T*5|NU^4AIlq}dhA6s)lkb}LU+p!h!JXva#e zb|zn7x4?#}GmB^MY%iah8J?(*O+z|d^2~(*y$l|V)=JIhY~=&ED=qS$cO-{>V|cR? zmIXH(U3nSX71o##7O%dqvgO%8_<9zhsX|c}en3vHaf({Rf0%h{Z`mp*j(B*+UZa<9 zV|y9E)_m3YAd4bHHN2AHcQc=5+wVyizR9BCyWkbOMY!-VhT(@if^HR6qM4g^+O8wd z9_Ne0!c(|eK?X0$Qhq(14KKJjwGtN_d_0bC{2xoE-hv^jd=0j%sPWu`X4dnK`{zrt z@#G^g(@Tk$!$RAUt|)9}u_T{|hOAz+*EXl8rs@s2Dr=(DXgz(p4%cbT-icQB!|-m} zKYJWaKG0^0e+sZwEsm9Yxmj6E-@;cqsEYPzOC-1&EcI2p|2=$nF`#XA7YFSZ(5c-3 z7wGisN=456mRHM36Vc`TV5?)_UlQQaDqo_YT%-w^t}{~c^sSQ}4R zyk__qOO_2gayN_5RGuj3-|uz)LPhNTq^@1Rm#2Q$|5mn-0X)rDi+8aoGStG`1;6Y6 zINN?ty6`0y1>Xg)%(?dd?|B5>YU$iuzZ?VGWn{blP%HTxzN>x*N{#2b{=QS$d4=Eg zhf;nb-#ABkgzG<>#nO%Im;JNH(X?KjuAhI`zA#B|3@7ivy(CYD`jh8%7u@d)7a>l= zm|xDa)nrGMF_&n%om}q%qB`a;e&_cTmVVaC5f-l@TJ3}%42~UOF?P+dEzgQI;OoU~ixR0@$(K+Lb5DtI8nO+H4i-mwKj(F+;VeB z8G&lKi5C_d*Pp3hX0i)gE}d(`hNS)Kc)OS3Rf zz_ORGr)+qHl)WrDjhkWi=UA5ok#DABi-}UT*pZY!w9^l!ym0>ZA({nB#3}!Z5*s6)m{rq*?6i8yKM2 z)xZGUxTegOJV8fo8-SyP@L1s@@3LNkr|Ww8?#_LKh5A&hTAwV{L>ebquli(d#$yd9 zN-Z#yaQO`#ueB}Md3>y;$@%dqB_&P|~ykCB>B&a;ifxIQtlZ%QhQtdkCz3Z!Nvg zLhYabUJ%;Xn}%_PfmR8RC(+|Cqhy4v+@FfUvaK9GGlh*80IDG%0ayXTcE1#W2hBc+ z1rYis*+B@C>nB>wKjC1u?cDCUPYwijt6VAhTf`~tVs^5^7ahWEO^Pv%pBK!r;R8+M zw*Ztkjd$dm#+dvFvQNaDQGK0Vbk)_Cq1^3p>l7DXo9#6F>?s-8;ZaQDp` z{r?Si#W2;j46pUfDy2}G5$t5i>8VhzG-sd&o@MxofyWjp5=lse8=J+8amTiT&zvor zl7^#85$r|K7Q1lDKH6~&fSN*yXvCha&tKn>4B^p`c9%M(Y$X^GWMiIxrs5!yO@#V) z0cEqo?X&IY5K0;>UQlg6hi1;_=l*t{*9Y^qHsIFkO1N@B9&lL5zU=|=nW2Y9|}vj@2fw=CO|49NbBBtMQk{>7a>0k$7ZX*8pDCo?uC?U%YV*d z8?@LJ{!_kcV7c4DV#{Ql`+Oy|)usDJx9pXM$m_6#0Q>^Mn9>#f3gnD64KMWPNlWDo0-RgDX@fk#}m)HI1`{y_+X*mi}NnN z4jeUQ*_JZX@*!_&ANDHX8 z`K?2bJ-9J$@z>B&mj-0ndfR|>y2XI~vix3mnwjH4TX013bj+R|A=FOe4g zcw>u)M6{|HM{&uMwkok=7;8G=#cMEs^8KXe{o_&mwsq=s82tT0{(U-ST~u&FMrRa} znZIjF4@XgIFVWWnr7c0JZ7W?zXiqW_O7<^CQ;{npk}Iz;7Ch}E`T~%9kh$QA>@7<5 z6r?Eh5)3_FuVcq6#!J0AXXKd0+kF6PPIJ5RQlWkC^i^koEZmhtJwn-fg;1{$P`P^L zJ@ba96yD$T_4$PKAaJe2MEPz!GkppZNtdXPBJ zcPb#zo{uN(AWrvnVvIVIq>J-#ei1cjbRLdM$k8rx0G1inc{umrc{pR$#t0=!6fQQe z1F6BpUmQGQ9ghPi-5^xGS%eZ%@KyZ={LS0;ZtmNJ>&4<|^Ux;oqH$>mzCKdE1)6R0 zNA4*9aKzFl^$7-dL7|JYcRELA2u8-7jrQ=uk<}gwO$X?lz0*nCjyw&a3rAG?pptW2 zIr7imIU5L-XYXvnPvYzy@#*0^duNv^v6~CDoo7fiiaILKBpd+)NkGN%)>Kp6`%#!E zw<`6q&9m+E#McipF*8?0_kC4+`$~+3L za#CW+&tY$}C^B-`sc~I7ImgpnZs&>UHp9Iqr@1^AXbZV<`FT+`1ac#-z-YGp9sC> zww%z9+x1-9xF**it335w@~RK91oU!T{;qBPkLQQwbGvcmhXI_yPA4p=NsBA%;PioG zE2lma;^U`7Jy8Mv8djrnVP$x_vi%yI(C$bSeIg#apvX|JWyx(3rI3m_j*RIJ7H+If zS@H-e+<2@bDfZ@Ge}3V{rO*IpN}2mgE=qAHkHL>UF5I9tv2X*21@w4+3pWm=-SJI2 zO=UpQrGViW0u8DDBavp|M*G5`oPDg?xE8m%q2vJS1x61tMFDnl?|DJ(d)s!xh4WVe z@Ox2wrPQG3h!M9_gYcPY6?xl28;T@FSORdHXwyy&g8aNjq2AEBvFQWt=wIvLwS%8) zC{)rEK}$Tpa-VPn_CE4kuno$JfbAk25q#CKoPXqCaW(l=*g=`yAWQSMQYc3UM~yTLv@5R!;22BR)oR^& zs)zP7*tn7KlFc;ju9UK4a{s49KX@qG0jAy~loUi>d@1&6@N6ak7wrHWP}2cWv;&N` zEN@UK_DKjZZKDKG$U+#LUZidO2Q{a5&UoIE75)_hWn1BkJ1+i^^E&SxB_b{=(<<=F zf$Z`u+Y(SYgONH6tJXwI&j7*Z!~XXR-L z#gNnnhLUw#LGoirZvjGO4Cy}nBw|S7)58}-x)gU&4wdI}gTr|dAta4$4iTisi|r=e zRAvBa5cY^hryI@cq4ID6Z?T66vhZx#k)=M}tn$<5Bkd7C2qTznwD`EvdsEol zh~bkS+3)ixxHy!=k{^5cIg27A_CSsIEDp8kOiqGL0~&QwSx@3nM*(diai|mdKKSEM zvT;Wi{BfwW+4k8uRA?8@Wl?0v0(HR~6>;KFyZQEw9p$KDo>Lr3l5e}uJl(b5HpSCC zSMYN}&kdV%LWT<<X$D+s( zI%-_AEH*wj%VX_U6z3M7d$|}Gd(DwIjQI@ybH1y7!$b_uJ$>|jeB=JpM?*#9(|qF` z<@V{Lw244_tQ}hlUt{rfbN;C8VT_&N-?ih_deWAB7TtFb;?@7gvcNQPQKorr`tS=N ziVHuf)fC06y=>8ZO&$bZ%~QNuo*HP(S!KBTgpE2}ZO+|QwE8%A`LKBwwvsE)g6&E* zu(0fX7fsQ?WLZIzeVf5DM)b2%ExZ5Y}8XGH* z;jx&9;Nn9_87*upKDxL~(rx#jf7Pr%=351BWHzNcd=uN@fS~#f&;V!En4tQ#Dei<= zxRh%tIRG4OuOtSRkfF0l6`w5@%tT`&k^rOWPE z2sET10T<^9;(pCwVXQn}ny$49X^27u zgFyFGGzE7ba0km!Mzptj=chIU=PQPu=(jVdBbl9$M%}K0o4+vry-_3ix5$Y6{O8oP3GEWrviX8fv7vG*C{=c~c-dGCS58 zLC09IYLL8J8b_ZMGb&_9ek<5x+Yx+b4lmDg+m2x9@z;{&4=ysMB&%#cAiD~a2KTuZ zvXh+dL;w-~IZq(wX|xRmLP`1KaoL6frB)gW9*W}lhIzSD$U09U#qn|S zl_y%11w;sz0a?>1WxdM+nu!|!=|zBiM{)>(vZsv))FcjbSM+-Ma0$<8#!20nnGa;W zV=uUH#e(NNk5?OT;Qv&uG+LG?JR66xg<(qPu z84_`sOaDu>$@9Rrc#ldc;pw+69@78f09>}kL#P)9K#|2myAfyc)&PPSiK@>Y&AnD*}oVyL$3T+fWp{n zhEV?z07Yts;u@!BCj$blW_*VZ$~gdTYMF}EMS|d!QG-Saf*P-y7($wtogny6PJ*DE zba)Syz+Bzn10z(LUogOsk%TA`lw8Pc$_j-u3#A%dYcVzhn`O0f3nxvu{VYzDl6kzO z9{;~|S86**>XSikqEi<;+sn7UZ@P-yNbsbJenmw*!KeS7U=w&m$Br7o&) zR=25(w*jFtb@7?_Nu(}{PY++};x1AO)Sdo^;P?vz-5in^-QS<_7g78}X}RxbhkKQ@#)W z)EU{hBMbg)#Q$X5XR{GQyRc#tXD>2jfw~Zqjd&8@ez(Isr))$?zCD@E(_Q;*kINRP zV!TBNj%{<@ckL`AZF7@1<7oFF%jmNCVfoy$j4lD9xG1`s_)wOSmo0h>l9vFl<|)fa zo{C|5T$yGx3Oz_q#V~(Xc}DV74C5E$R17t?FsFMNDK+?m+(e@t*NJV>ra1H>>r8J`>dIE!aM%9!PcAgD}e{iBxuy-hPQ;eeF z^qldf&d_JuFC)9?B?u(5i;S*(+en>jPhCGfHC1oGMU3JCO1z&@^h~HosaBYR+bu^+ zwOY~05>dawHrBwvHBY<&QbU@u6c$NYl4KppjCMpb<0yZR}%x{iS{Zp*l29d9ElPh7;#9@pUqbN^KRYT8=};v-VPX z?`2bsN%~!n$oa$WDS zi81T9=CAq5E+2~;O-dvA5CDnk_wkrU(m7tnBphEg_17jZAkXEiP6NESxdd)W3Cx!m zhHBmt*bWR6OWrRSfiQB|QDMB}4k(6M)OMWC-r3p;}J8h zx)568)-}wuY7c5K8tc#%LigiUGK6qlHid0Z?SLQ2FDG)+vBM8!f&=lrW?= zG}QE}i-e(90iEH4MhQb2ui6_p8zzj_iDDn}Ck&xsF|LfkuXw{u)Cpmb{v4VGl!-1P z7wEr1Po$25eEk88c66Fg=V%YYXq%Ih9;W&Hg@;0OnX95*l;+b(8{ELd>kg&)s9=)P z9UCbBgtCtVp)$?q6Zq+jrSOmV^zfzmT-qp4mL?v;6Ur2}IVAbKk&YNpP4zeH(~VKl zhY{Q`^B<$tDm4z2#hq5nOO3K9Z}6OF_AEKis7_4PiWm)P34HQEg5q`JL0ZEnS4x%l zs--aQ?JHuHUo;_5aJvL&!{fkR)9mY2|#Dytabp@Kz2OWqxo;?h{Po@{Q}^? zT!thtEgs+mhD}Dua3^% z*r&a!tTX8>)mUGcJ1-51E?)nRo4pR_n! z$oIjYz$6=YWWk@n^f58ztFzkWu<7+dgZQLc8#fEQ$}2IAI6k&y zk_A4t@(Wmkt_8S2({mP&f-^l5OTNaO!=lI#H)>p)p4ihJFXyp#D}-~)GuyLR5SUYaI9 z!8MU5?|LwLfo)}9q!Y^3?pj=%pZi&^nT99oK*%jII|JRWG2RTcb1?_C$3+?4UWV%t zOTG%DFi#oX>fA`R7eLn>eiAyM61vU350%fY&W%)HMmo0=8Sn&ON9mEd2D$0n{c`&O zPm5GXnE8{smB$JP&@%P6!7_ylMoO1{By7)M*7WD7bKrX)8Oe`7U`He1Aj9uN1Du*M z$&cSnaVOuwk3GH}klMuaC2?3_Nai>Bam64vS2K+z3O0qWmm$!Q>OT@`B|rXTV8Gc# z6K4_9m2?y#)|bV}NcIzJW5(${=v4~Rg znYkaXU6?QFo8s4`DwtAU5N*b1dS2q-wdNWsx8Jze(02vmmDmQG>3Nld#hysVq$E5} z^wWknb(Bd-xDh~OQxedbTgm;_mPNWXnpy%J3S?O)8G?M8z-4FYfZ>QUgB}x@VK}1N z%`hAtZKzRM?fhbFk-su(NXT%!RPe?=ED@iXW6Sf~HXL{s=8Gi1AGDHJ1MW`!V+U_H zhU2fW4c2fx;9#+3Fvf7aP4v@-4>TMf1km|595@un(m=z3qZVN}I`u(`=o-&Y+HiaW z(R67zFk||wqDR?G?=&0;B}I}KWZQ5c_k4y!9JDItDeSxlY9mz^@t{0!7;2BaDq&%WYsqQ--)#;?R6*j59dnFGr++qN25&G4_HO?$fG9;J$!jrEc4 zb1hzLx7t)#xsUCDoaR%U26#!MoMyxtsAkyBa5dW=Ek{xmfJe?Dt2>;2*^J3OWOdj9 z4`K`A6uIbUhv|Uf%v^GLX`G9*y*RNC_kgpxAIyCrO&g+i6uWG5hza|52m$s@;LS|HxH5&;h&FkVvy}qN z6eK0Qc-u;W^xqzU%T@}6dTsy|DFupFoKj$!LX1-6mMMG!NHAR<&skE5k0MaEO8lMY zxJ)c*#bgHWk(vJDfHa>Dn%h&Ko*Z-Q8|<2!zP_AhF2AMPY&DHWQUvK4f9iJc(&QM- zeQ4Hlu{h^%-#)&AqTgPeZSn9RtN%3hxyM{=_>KgnMS<@pt+d~b8cWtXOqpqYby{}0MI!vI79p1xrtQ(F<6~LJwn+kg-|O5RIW;`^+K?LT>m}sgxu0mg!EVHQH=D3)3wTSUq}nGcvtH z&(u@UvyGX0X7tC={1o?PM4(**fhEO*Os9YTz ziWDAcpkXXe=K+mTyd5Ypb9@jCgP%3_mHQg{8e2LQ_@9}6kk5uGMEp0on5xDp~ z0mUP*?Epea-pAMh95HDJcqn^zjgI)s0ZyN0*EmbfV}ZTo#CkeCyXHxwdO8&&S2aOh z)YE+|YJ91lPUBVKR;;kvu|glf(EdEu(@mBSO=6> z_6>st>%k7Fvcpy&)CHOG@M1r;x4CbV3AkgEd!{zNJR+ylM)97&vANHC3U?+15`ia# zD@FNl*DvYELH51~k`P^M*C`3-fFzhJ>>k$Iea}OoIlWMMNK|XrN!$5(8cDTwD$_^l zm932VwRXP(LS?PpzvCxSYbQQEe6@CPq_$8yQDOR!9I2h6n?tqTtIcXVK|1Ad;Af_k zj)HtSZ;{UnE4ah9sMc-|d?eqj%j}?j$LKLd(9CCg!n|h4cG9-X;|uLprLafb$=ycx z-IKdZ^FC}e9=gV^(24R|a0^V4-LWo&viY4q=&{_Kjz?UX-B|z!mNKNw?gAbKmsT#Z z+$qLnI5g`t>B`d!M?47sDmHM?Rh*(7Id{PX$bs-Cj2Z93?e;8Zer`@#L0C{Q7FX6m z9s04AQ=bXW_w>E9Zskek!piV;W&1U_Q@JBi^yO`H%Gk@1y@7-^fJddANUZBdo|JLD zhbXo5-8F(g49U$n%mM1SZ=@&|IQD%an-IB}^z3b4GDn1l3~hE~DC$CyZ4R*QicJfL zfl5j?MvNkhbcMc9q{vXD4T>vmM*MywQnLLj%#%Dzw9k`5ms=1N77`j``^c#8{2i9} z{rQ($T$jTqroQu<6n8=_T+X?T*vl=bO{|o{VF4RFzxACnX?J{^rmh%M-rpev8dCj7 zBCYz)@1@m(=nh0|AfPZ4%eRF4zyP38+ZodaX1-c&1QZ;I%fbD*>7uDQlyqJa%?O(b z!W25mD%)b)q(-cI&UL;<)XoyO$9{K|xRBL*r4VGh!~&n0*%7>@qg*Od4y=NCsc6$K zu~-^VnDu84UTabtSkd`TY=go|M)9X%$KUN>u`O~;2G0YcpEkUyl}rZDM*vh$rrQhQ z3a?o)J1*ZcRjZCxTX41=Y!!?jE;S}+?@UhXgZL=UA%iM9jiEr6=1p^`q7z4r3k5!F(6Xk6#d9%H9}F+3-unMq#*L*ODW|B&t5EW z*{Q-{E!()%H0Y2{+ZQ7Fxw?0#?L*3_E zvYzWuVJChD)NS7BG+^5US2QWn6R`&B6?QXRuLfg8NV*T$)p1HT-{^G7=1FeJ=KT(M z5JwQFSZtqRlx+H^^oGxJDdlyQQfJAtV^!Ac1y-IwdlrFEQu26Qwp~DZm3D!LqSRSq z?(st540#GEbru(Tc|t|0vxER|Wo)9|?p?L!_o4=$mZ;xGpzI~;M$0YCsZg_!-Bfms z=1G}A%5gDoXjAj+DAw5XqLW~xekp|2HxW1UM&l|omz{$$!;6=#04JxE@Sp zK|hbnRse+hX#f-{0E$tZ0-OK{ViX{^GV?s(r|H!A-jew5K%i{#f4{%t1#1=ez?O|s z8+pCBQvnaplkQeiMZZ|8HQ+Ys8F|1&bFeV? z(#tD@IvLVI>DBCGwKj&dEXLvk&S3@isM512j$&zWnO??BXagtkBx$D`T+V0bIpUxcD0jhZD`9*W=-YAaQV~%x6 zv$7X?+f8dd-B9#-f72BHaTJC2n4Sd`-X|#JExSFY2qpU!Gp5K5-IK=BA{8AK?_@faDzags8~L)r5-v__W&P)PGOod4vpz+Skl z2LyWF#*;=he=2UOYLmLC`8)C47-v8=e;Tg}!EJ-xbgSm?N2=y;94BGEqCfn>sK~>w z+r@o)X6YY3-9fkPb=LpkuVw*Y`gULyVETU49exNUDWw8JWmo>c>a(<&AhTzK1Vz^h zc1q9%AVKDuuZOjQ=XxkKH?dS^71auM(l(r@$y6(-5|@-d+De>XD|i(UDr*I=#!sSF zP<(n=YXzJ2+MzjJ%y+&_nltKtQH4g#@2#n3k?Q+WbaSW`e5%`4KgRTZ_wj8mY~5M# z{*wv3G6)lAue_0B0PSBuNQ6F$vq)CfbVngTH(CFjNy-C8*C2XCmk%VHlK>8 zU>-d+GuvJ=HM12{dCRU`((dm^ML%tL^HnC8_XPlr4d$VP4$2rdh5}id=Qk*07)On? z3^Xh+gtOX_b|sV@&-JqP$A};{Ynb|ix4-<0=u0-c(PGfHxdA1Gkr!b~cQE+&ngCq1 zZEir_9RNk!=4jLM28OoH2?16eJrAIeg)l%*@32|@$syxednrwc)X1dY^@yB$e$0rF zH9iS}vaRvP$Epn_nZTe=W)~RLjh}?Ld z|Vt&I|wIRxt-HZvnxo91W+;J+hf;Y1w;F-*aP(K#& zMxg%9@ZZREb*xZ0vrs=gS#A`nV}09qoOAAZYDRbjQ6zN+#(N`hKRVl>ll%R^eRIay z!)${c9tzFnuaj-iN!yk@Eud@zwWF+L*;au3Y=bL-P?>G;c>E-?4aBF1FWX?ZikoAC zK!2A<6JO9px&h*wL%zY2tb7AO{1j|^#Uvac5O=2VN|QB8L+yv!v5dliW=|maM1zqO zw*Bg%N`0a{)GW0i_kO4~F*PJuOaJQpjUgo7$mbXu$9#^dnPIp;1xn)aw#NE6oXS&f z)ruOb=s^d+0!XfY^mU_eP>eEBu3bg{=ooTwy+~qe*hE-JBm!4uw&XljIigX z4NL+F;l_tV{U8Emi+T-(&dLqY;eIzYz@K4&kw|RBI8nroM2^FcQF#KM;|D~G4n0CO zoRFsHkO4C4o?o=~yfBKhw)S8U?)gGEavhF0C{6Tvf72A+8$}@nGD*KN^n90~Fh&;< zO7<&87m*v!k=(EX))xl}4=Xs%Pv=%g()J3#FXE4jCoB|kghaX_fd4!8n8iR=LJ>EX)`*fl*> ztCz+`gidss91wFBfq`xgxdHcz&3vP+{#t#sR1>{mt}SkqC+e+o{##SE(rCFy*A^Q@ zDtETRe5UUc*+5$LOkcVV4ynsi+Wim)C`2qM@7DYz%ni}s}Cc}L^5#5Hkdy>iU zhhn($8TlT*5B^LB*|;MM{!E5XvhA~(453~43X38`7N`p$nGE0O+wXRm=ak7H$+tax zp6=Rjd!`^FlVJ*aYhPL1>6db>(y=atGE1F5XuGuRF`R$N?%N`O3$#ne^C&pGB(dbH z(?%9WhAdL!J+n*a^F(wjusyL$JH&A1GxG6#AN+PnHXdY`ZerVK?NVqL##s~@vOrx3 zu}dw!{ceYOPIgI>Z(Eu?-L>Biwo4gT7UNc0|EA(l z(fAGDI7fNZO~ri|u|E9saJO#B)bI9MbwO)$qSR!P* z{$2aDC%rCB>Nv@Jh;lGDvuv^$gf|h7)h9~T$=T##ys7vspqNXxQhQjGgX!g~9+>1N zVD>!aV9J|{&2_y@!MqZBpi(fc9S)U&DQ_w^0F4ApB^)4-Po>2G*!}zj%p1k-RkI*g z>GGCHI2NS@$FAT`k9g3WX|~D}#mw`ZwnVgHBw$|Wh}irjU_ON@KpOj1M7gVpg=bebHR#G34tON51S6dX~}^%WB=X@R}#yU;sbc0k$2rY5&j@ zP*NRY6jv)9tM4(E{4iSXAj_Obx7YbNCup?1T=FVn7{p$WvDj>2x{K`t#adx1JvQ5h z6Mx3G!kBd)pF>XW6{2g|TwtQDZvap;+KOm0FWkH%8NwrdoIW9ssZhR&os!o&r|e#? zMN2Q%T|R5u?#pJRpr+A!E2qau6I~ijP?$|?DA^<9(&$eiKQpuQ0+O6Rmcu6|$@xPm z?u1y_(@UeNO-xP2VF7nIze&#jJME5d+uF4mlxy@u1R7HPMWOaQ4pwj!wH8 zLpjoyPo?LamEa!fMtuUWkaqXJ0-C%|PX}a@A50QP;cB85q$y#8BH$z1*rA~8@K*4@%;J#8r8!-PVXU(BheD^ui$76TCa zp~@0%4)K|(=e#+jbS=^#yjgmIXfr;!yWrsMCb@e6+hCKs2OTW7ql!r@>KFaA;Y~I( zj_!N_H62~Z&C)pM4@&Mfh5}idHz}p$ZX7kTE?`;mlDkjkG(!n1|B@ECCn4%g$6~S+ zJ*n+~F%9$@(aY?CKqyKoBHkdB6jNTfDec?f=x?Qau(I#2j<%DGjr3U+xUxRZ5c|vu zTv^}Qh#D#`meGtF9x^O-F($TQ!Dr?;^SrkW3+59a6m7;EmKQmAyD==Y*amA@?sl-) z@)%=SUMc!%!<$;b7?w8z=zJR%913JH_{i&z5{4!aqya9biINV*l+ zNN_HvUc1vC7@H-zms6kOfCn)Iaf%(3Lf+-n#y*EjC@(?wOapJ3K2{**>9c1V2qoo? z$7LG@lvZgJcqmF=Hs&Y47V{IHLP}r8^<_vgu4N+rH zE8!<0Q1(iAgXu%`LAAx`lWI@TYACzR?1T&u<-9U~*9K-HiY2z0!`qpwLP&j6`0k6% zZWV3vVr2_HX5dRocoDV*AL$sD%G<>!=)iEFIQOHd6&*kU2@ zK2(1v_CO2QSD#USLKVWRt_Api*A1^n`lkg5~ zY!#aFPHsUHzF&+gpu56PpHrz9z+mZ4dvh!HM^H9kePX%ZL_LkEyZR(6nlbB`k3maZ zoRwL}d;vh`yp9R4yCMgQccgwOhiIJl+cp=W-Y=khHrKipTX`l%?UbVkc~$$29|Qhy zdty&)@OJBe3Fx(L-w{goI>x>uuihhhWo@AhqITPbve|aVr#GGgR9dcke$jkeaY+o{ z>~Y7tpqB!Cz;@Y5nVnI?&Ag$hJvEA2dzA6M>yrevysxoG8KGn!V@4S{;~VAOILfAj zFegi4&cQeK9rt0=$ag%~oE(ngq&@C<|MK}^R(;LeP zbaQL=^-hDl$j}lMsmPmF*)rJ=oV-Fn@d#|0L@3Gs7@5RT^T}k(@6AzCJ+qBo&NQ5T zn86rAgqT?Sn8^%|S;q`OJq3w6OCDfazGbvL)vDGfi{3sOV)ZOm3Swpgx8@8oGZVN2 zK<7LY2<$rgb~|}s0Ho${4)L8c0)%P`sN5OBt~pDT@*q8EN!!Oa0rlO^C$>QE-x$C% zTdNUD@+?NHk<(JEc_@3mh_jFf171o+MAf@? zQRn^Ts6nGTZ;e-F7MueU8uMiLn?_+_iKX5RUD9cv?=BZw{e8t^5tn>&NvTUeFV<(i z%axa?{vJ$UH`)~irN;YkZZoXxjQ2P9Atm)@alDBM3;hPHSv9WVWX-s7YBPe3ly9jv zTTP=dT?4?G^fxr7%L0$lx>0N{mY8tre08$0e+F`V`?hb}ajv3EoK5fTusE<@!8S`1 zuuN^zQdG1+iu^z9#wpK3z;0>nKvVw#^d`FMzEf{50lhJI*m`&-|Ia)Wnh`;jvHa2{ z!eDjMwmZ*$(V6@zAy`=<+jQku-7mXTRoySgPonBxe0un*?uQSTY6rVLi(eiU&mQ`Y zN5zX0dBoFwo&8OBf|X6%>2K8QE#L9`gK!n))EL}%k(SJ(u^Pm^FWB;G0!V9S3J$C< zUI|}I`)lQ!pCzY#`%BGo5&w~PS-xM~4G(Y{2!G-Iy@lHf*TCyw-SNM{LajPcZG|@~ zUQgS+8iAcAHy5_C&(R~%7!;j+V`h{U+2 z;H&>l1(OE+i|K%`lO4?|4qk+BsTsKZqQ&CEbZC89p4lZ_=K&n}l90m*9>=5LD)A(i z{6euCSri$Sc+|MA#9QTKq_u#h(+~Hym#J5crRV{kh;9q>dQt>DDuyeck+<@F@Yk)$ z#vNJkA5L&5+df+a9NL9@SQHtuKwU747t*m7<+)m8-om%v?J&=&2w0MDPlWPx*M8eG zQxk_1Y=_=jhZC&x`F65QT9ua4TgvRD-!&gU#qu$m`i}{U9BCU?h!zYR8u?Wo1!s9A zmVAZzM;1kfv{B=lU9mO5|HfnORtD!*1N;jyF!nBK-Z17f__$r%yoG0QZVm8yzHxsI zaHwc(rz+60JmqNE5XVYtxn(umUhS;-WXW7}sytd9ua3^%*@TD`8o1v(c5i*{-m24Z`A`4y>}}*+BSa7NO~3 zqi*^c2wxocmaTGy2~n8^dyQVci|u6qTk}=p<1C5{)$mG&e-`>Bw*8)T;qO@#d>6c8 zw+MGTjA8gcJc4c&RiY6EtzB!U?K-lj0HKz$Z+90{0BSsU3UDIdxPJ-|O8MD*-}%#iR#v<^vwGo_6?!m z`@f9s3TxvDi`NVvW682%k6+K?GnFUG`S*LBzfci-6T55I@8zlA^}mhnV*pR{)#8IJ ziVU^zcERuZKg+h?lP-LdMZtH$D|4>B{~?c{TP>ZN>z89-yNqntA8I8RUe<-{r^a(# z|8acde%BvL`Fg%_zw4KcN4Wm2ES7Fuza+sPN7H(Bx_Zo@fT%?^{vUd}o2t7^p9?Y#X8ps!gz;?QyOJ9SoU%jW7jO(BBvlh0qovN;GkLrEEqJp z`DV780gTO`g}j|bk>SEQ@m_?f07f7F7mpy&g?C7|MDq{`_m^1u1IEB_I~n2rdlus( zP`Le5>|e6o3}LJtlJ~37qQ`R6ssJqcDzt`0(E}AK@Cc506*`xtKVS^}D%2>Y%r^D`jGGsqOMl>Au zmL}~LT8%O!5tb&$3RTQTghOKMhoCMOQVI)@kln(1T*#p=)oO*wa(qlKe3Tao=EJ|8%P$&T%M>ja9L9NuF5!9G^Ky_4}7TXUowLaaH zF~aY3M%c3P?Nv5XF*~;E=#md1yi5RHO0*fRf5d6s&ee!1mii#H)MeT9X8|;}SPJcf zb*^06ximC9lst&N+kO?ETZ(KH2d``n9~d2Aa0fvfMdqwk1G^zTkBn1Oe}*+k*9}9ll;$*(coSOQ>6t^0{rJP_T2Y`(BN-)t))UmO#^4M%r z#b=AR6(3#PHj7jty|O>5O}us-hXvfk{GOV+?sA3NmrrZ*1PP@>Gmb-`RDh94^VHOv zZaVs?qu``1$l?t++BDr5NDnqe10}p393e3IXx~0@1dVm#619M7{aZ1w zzffMtDmSISS>Z`GKAcJU6fTn}wT~kv)tV9E>McPEg0P+VYDO+8jDayn|d8`VP z6aZ|(XcVgBFm;(h2-!1g4Gu|x(`-j8`1=%y8K7F8_&=n3|dnT|Zoe8K!^nIc+dp2+#wjpN&Ru;L&a)X1# zHsLWdf@?)TZFtk%G3C=w1JF6o2&@iv&I)iSkfnK3(+ALX$WbGU4A%JLhRjO!u&0Hm za)PfUlz&MN$qYLrCQGrGbXWli+zKH5P|iRg6qPj_P*O~J;eI*tCUsND(X$0E`vB{S zARp5!{~}e$IiDzm0s^$G$_fKe4Vx!({k2geM7HG~!7BT(NPO0Yx zTc@{?%=)?m9>grfDONKV4W<*V_^Ft&4s<)gtG#VvR)O4oP7uV?Zkrf{lG4iKvP}%i zxX;AAePDp5YRvR(&)g=JJ)Thd?9~4G??#4|OSzX?IeKCU$>Ke>ar%GVU0sMIM;*Vt zyWQLG+pkLu**ljdu6GH$d#s6(YnYH*}aY6!4*5zRsZ_> z{XgpKU&X^6OSu7L%34qqcT_1y{)+!C(572Z!^uP<<{>(CVzX;se7<9P&FG+haTJRHxrLa1 z4yWg>G8$eQBq7~K+fzCPhS@PJmLIYCMv&=UXKdbIUQZ#pessD@2Rxs_iVosg<%Ok~ zhvS*R&v5i1nbXy85fO>XaU^5Qd4Au{l+zKDJ-)B>q8L-1f@--Ipt6RnDk^(k8Fiz8*}zIc=N6+&*MvlMvr9ws#x~`W60Nu_T@w$`@5Z=PU$>z+ zl~6HK^fd=^=KY$Wc@&NLgG|Y~uz5qiFGA`rFUKVmH(|oNOkMZ2Urh9 zU%XG9rREgc<4GH-%p^IfwrIf#AO3?uu1HMO9{1?zVqJj8NWK|*G ziz?Pyln3z~Kump@*TmVLEyk{{iHYiaUD}z~#FR@)%HpzgO-yVkHF0sS*qfiS61>>M ztU31PH!YGD7tqpH!xijJg{ebzHku5{O#@A*xc!`$KHM$;c4u((}@h)F=@e7p1)vvY@z7}*c9=w z%>z>L>#TAUOxz>JHjvn#XN?l0Z9}9rama&mdSde^*(^~WPN;X?82RL}8ib-^YeU-> zxpo|)F~Q%ci-JqN5foWr+AMY-BSl)>>a zEM#=slBjNoMt9m0QT++#VudDbe1avk+38L6r^>8{vjdHyj;Bi)k2g*LPw(`NCvt`> zYtj#%sQwa9#kq7_fKYh7hZ@GV0BO{2TP3ro%0P zSLiL0bl+4wlp+pt(0=wp`p66H0_j|FbTr=aZnD5xY5NCHDQFKf$TKX0RQCNBdPYSs zNO#@-ScfVeR=rs3YHEBYpFd&EPM_L5^&fQIH+JVviGRI!yv|%pK*7HRb~*`b?O5Z3 zhqNlG?V;b>Ri3)h4@7kHo!s6}Ab-&`C9eV~muz1#^XV#pdgULoS2PvyOn>kyhE_(> zFZV@Tf>XUDSHDV~3xh`AX-HyWur0}xQ{C7?v9X>g9It+?ptUX|S6~G_g{;T6f_iOx z-blIvEUa{?eO_r%u17_#u6#Ay0VWAgb^O-mrX5sgnLwAieI=R)NJ;+o6ec`^e7x0N| zdd$96pY+HfozdeDfArRNCOIuv`M0iaBMbdWKJR<maiM!$`|uk?7BU;@wvb= zo4#Yx1sbE}xiyc$10Yrr_+a1$rW0+P;-g6#9R1%o6;iba_+&Zs=+Ts4A=4yAdB)#SM5_1q0>oAi-$-GxQw%nh4yoQ8LRr z^c7VZM4i38h|g3(rs)Pu`MA#~d~|&?Y#6(c{*7p#PtpQs%P6}z2;K~Q5PVq1Fo%g4 zgVs2Wc)^p(i!+cXMYImx;<1+N1%|lA7Hyp7qn7EUKL`0Vo*h9iVr?|^lSG4ju)<1V z4cRAYMGC!$6i_D(#Ef7#%8Z&s^|PDkGXpqrD@Yabkuaz(e`49U@%Oy`(O{nEdWMC& zcMgN^iXp0MIe@cB+4BmOK|v+r7c*ZnB1;0x0%iU?dzWFD@L5yA5!Mu440za7=`Sy*w6-39?_3Y_QS#RN5{ zKnZyEW!twUWX~HF+oz`%(2%&6=Mq){+l`|!bh%OR9f(fE$;3MhezYC&2S|CN5TAL1 zH(8UYRWRgZo_PmDC4X)V@M+>VAg5Ua5XIJ3^x3J8^2ru!wS|a4BE^*GwFhDi{vj)X zI9gv_MYO-t+6yc^|K)l$aMT4#DJp=JM!@j;0yr>&k5JZH{>atG(VvrEwVWYOF>Ufo z33)KDNs!%P-ipnFswe{jVbuy64hkCy%o4b$5?Im3Fuw>)HjI=xQz>ZRX&)fufKT~* z&A{9>EW2ho_z(P^bzFk}INpE-qkTC9SNrIwS@B!`G{lRxh(_05EL$e(gMpX)hF%t3 zh}=723q?$#c7YwB|D_l>0D9cA-K+uwv0RuTK>;b7NoF?)yN+v0W`uhPh$KLUwm`ta zd$8s##sZXap^Q{J25XqMW8hj9$z4(FX`)Qh*k?{v%CrN1hBaS~Hp3DhmeK_53nbc2 zSb0coNUyJ=anq<2kQG4Of<}c^Nt#=8*0|4Sv`?f_=o=(f*b)-Ta$QJBYtE-qs_FM3 z615dUW($hzy6N+C^ijrLmMUqh{w`_R-%@bz41!-F2>A#lgezK6l1#cUQXYs@`=ND- zR;;(In<6XJ>>cZ?B5(Z{MCHvP1V5zU3DW876hy-azC}TO41r6*W77z(Qt*da1b?F7 zmk%JgN5OX>FQ3`;yqRau^tP8aiiex^sm5q&rhOc|F$f-WC{)Ia2TMakg>tJk z*&G@itc)KVXqGFb+E{a-QXkx396MMVJa_xnEd&Q21>~0fwW(510saiNij9M%mTsax zQEJr&8l~b`t6Ty+MMI^D!L0+^&L6mNP=F6sCdNuf2Fk7RTF*>*@xGbzQW$XQKnVs2 z16D);q))IAR_`tpc$@D%|LBiIAlL3GwpxwK{;3wsE6(Hm%2*TTti5=&R%|xmA$(pq zQ5=__mW)kJ>M#3?qqhSU#5?ngwbo2|ZF~MyqcYRJu%ii@Y4?m4C&nscMTt#&-la3; zzUEYWajSR`yPYYY++L)(Z22XPW@~t|QLohx?wn~as8uFPGuIy9UNl*RX#r$bPwg+3 z&wxRd&w?2%z~6Q7cLV%A4+gaeFc-L9q;QSogqf-GK>5V-_VRLXKWmGsD}1k~JXqdZ z-gfQr@c#Vv(&0pa%JZl812-*_Y-zH{THycT^4NjqmeKmeL}?V|V@PsT=AahBpekcC z?UfFk!1Z$IeQozP&P1z zTjdz`l(+5iQlDlSN&jSc{bu=k`9>hY0>JgS_Cg`jDNM8ZWijwnqawth7nrazaS%q+ zzD$gvR|rd?GEtB`q{lSi5N`m9H=uvt1hNtfGk|^0lo#!3%@lEfWj#Pi4;bD8(9@%- z(e=VFEAW@$W(5SQHPtXD1@sP~6}rY&gf!mV2`+S*^3 zY9dC>RwMN;=&Hl@#u#)pIXzYcY)1!L)02Sk&85*ssdaN2)B5yLO&X1Iy*q!O8~Bi($yOMN6fkcL$Jqe%_fqKfUc`Ahlb7 z!~)m$35dGj5b?caPF?w}<+tJVy+cny`R$!E(=7aXUdRl*c4JQ0coP}1L`UQCLe}YJUQSK*T zDScLAg1ojR8LW^4G-$&vN5dLc7%?T8$#>1y4|C(0NRQ`dfabpn&9OEi-{jQ9Xlp8^ z`RCDM4JbIIDOgDgTqH)m1*#bT;qY#SWJM%XgBfbmt2ci^X^c*4WAOOuI^#mudafhT|OWt3m(GL*)IHiriRdK z3?X5j1-NcB#!%%u$l#pnMF3`S&iwlR>WsxVvLA*Wsynd>Zi4OAP!-QfTymC`qR8ci zyZCW>c|oY4uWK&Yk#NBdgMu&TTmF6uXH?dx3UYCN6W?++&aly~Oo)xTw(o5@BjQ*E z79I{8yoc=y7eNP#Q?=Icp(32{*s8O=Mjgg9$BT{DGp6cztZ*;N>$|a4F<5&peQT(+ zWja;vYSNn)Qk7bjF2Fa{J2MrQ#d8^F!UB8WhWfZ ztrRjSD;sy1!*3-{XWM73MCcGUv0d;(@Xx=a9xmcL2%P_Ecm8K(BqVWc#gZq@`Eqs> zKT@yGB~?+*H5J2r<6cvt8qc*B<9y>B=G6yaW)eHc8j5GJy;MI44Os>+*E-Pn?0F0` z5UWyWS82VuR$5WRG`W_(agihb6Wtqwwo`;YuU{X#{J=c3$mAdweWbhiww0ujcc{gqrCuVg2%O&Tt_CIkw$jK}|Di(kR(_;@ zZlK0<)&G0>#{KF)6!VYpjdPgW>YrT=eva*>6Xh?*XU}8UYVe%N#5-vwtVWH0%K;vu$+3~fM-YeTWp zAu4AlmZ*0PGx1M67J(`wWhPE>%tX?2hr5bzimA&d>d*M$_@zrW9%LovE##~_Yb8R5 zu#)W}gD+?ZK~~~SzJtIi@2-`Q_}khg+e(Cr*hTzE{oFu}=URy?`NsWLA{6tR`NlcS zBdkP`?WGedA;)LWW0;j#l?q5q8HUC5in0vJcWoM;$&V=0G#moBK+|v!-vwtHWH0$z z?3HX687hDp*QO!vr1>3upMhE-We8?-()<=NDWNCLp~U?tKTyADQRBHv;UDrWEj~YCd+0Zp+<{`iFSV25^Hw5vBr}PdYe<$Qrh@e&&#IrD@4z|U|wPi#vL=?Bo z{G{?ior$m%DRHn-oGcqbZ0&PJB>w@3{x(t)QW;33WB0XB_P=iM#+it?$h4rS{8i-0 z#0c9{jL!?pkAcygoEc(W?k%q^Zz}hdH$Qu(yaf|tL?Q_$lx(k_OTA!>x zjEJeaak^EmPe5Kn1^A+HiAwsJoSv)<)Efr}A(04Kdr)Mz>>L>$NTzz3(Uvj4mr~bA zP0T_mtNC5ner#u5Djl;rjj+-06PcQm8biDvlRMc|}6w zb8@LD2c+1sy;@`?O^S)wA}0bOwkR=6{#uZ~(lo)ZZn{?WRzw5X{PK1WM0Cmwys?>U z=K(Vih7fQ&V9?8*CquzGIboXHt2>d%kSk=d7-TUmK3r@}Kn$#@IevI^1u|kF6XD8R zt}7hcI&jgz)gj>ZuTjXfgha>v#b&ujBrZ-?CKDhLuX-C&bD#WieWBjScndWezIJn|R2Tw^ z4UJIOTf{FTs!PZnsa3?}O(_w!pxw~r!7CZ)db`?a9}X6v zG(#)_hG0jk6slj&&F2@Kcz7SAsa@mgy zVR9jJ_1^Z1a^+yT27g<_kQj?Opcs+gUQ#Pg9Gn8igv3hZLDT}(yO3~$N;Y~DQrH}E zMb|hD^H8ucLzw^AnLEqt+g|Hqa6wdSJy(4TwjvW`vPqv!vZ7&c}@US*& zLB8c;_;(8km%T7Tn#oAh>iy6fmj)r!F(fw4wEM!7p=4k)#mraV16`Pr&xnpVJt`fU ztW`!Uu;ddzqXwI~#|n3Jh=6!+gm@`E*Q;Qzk@TSd_q8|2s0=N3?$pKTVeDUu9S`*k zoV0{P7Xg%BAb|E>02z8#me+@GsTYc)qoroEa8+@vUY}HXkA=e(NVvJS)YxBY)SGI+ zrLlffhnI_oN`;Y;+c9~4WTa3!Qi3e^;{IBx2QopMkXsAd9a@1eBO^ENy5%Z-XW~$$ zQJ=uv`NE-Mqk?Y__>}YQ)V0*lQ7ZYaN5!C|u>f|teD>%VV);#+VRKatHks70seqRz z)1q|hLNQH7OW$`|vIX2+N1;4?7vJ7q#qNa=oWV`E(Tj_3^?T4lvV>Zq7(-#X0QtlK z|K+TZJlW50M*A*$ym+0C*d*e0642wTf*!U3xHxS9qPW?k_E#>pXWdr&r`T#g>CB_~ zPs4_!=08zse)YaVmeZa7Y~JXtJ^;fGRsE+xTS2P7`nTAskp@*g7eK>Rdi7%npB`3P zou3Lbha?r8d{)k)Pit^@7GjtMyOhkrLwy;>pjm7UKv^2R=KvP_kS7>Ag@lrbpvZvu z*-!*}*H4gcj$qG?D($qXWYB!12Y)lm60W+q3E$pc-Gl!n0)zBk+9pKy*T<#}L~nin z63BAxqm0GAzJkbe?=RFRF{8X#6RUg2M&Vcn3cF#MUOG}7pR7S9`N&9PVr5cB);L3zQR>cxxN5}HVSwygqx3E*nJ3Tziy<<;LUmwzv=K-809K5KokYWP$>$U;8I)sW7;|WLHEO(&$ z0nrIq{j@5uictWC-aR+m0u>g3JN8d+B1Eq~AZHb)4^~jf2>k^p8H2-xNyCXV=FytN zM||#ZmXNj0jw{WCSB7c49db;%rJW;^*X8d2a0RyXVpG@xet_9c){~0BO*|Ta4K*A! z6%Lmxqh-N5Kv;>GW{o*yzP`T0rTut%FpPNzg3lnxz8}yMxpc_2BD8vBL@@r1@DegN zX&*I`Lhn;b`^5nV9G$^|ZM|mjOMSco0uRgO&e+7nZ#L3xwzG4jfC?ZrU9HBnF(*io z!o<`#towx$EEZJKuqn$hO?`bBot+`1pmZDkG{0h(K${Ghdytue(5$kKQA%`ClCb4O zTtWJTz+J00BgHA$R0vK35r$XC1`0QzBp$Ay?g5dWs839f*Qc7sK{kpm6heW*N@D_d z6BwkFSkHu5 zjUtdp5g_Nf0r4|D7m7PGYYb*tV7U;EJRKRe|0I5*UBv+G#14 z!cn!{{EIX!1qG!Ote1vCV~(3^q3^)q1Gg56T?Z+Sm203`uYdXqVQU{)-*md}G0oQ_ zv*yvVN|M^OwNBPLNAL{t!va_un)BaYW=wwa)*?u!KfeP)L?2Y|EoZQjO?Q5^ghcouxZ*xXvpO`E& zb~uVai)qheH1aN|5uP-3xPv{5EwSZ4a`E9O-992Y6A1oh(Q35}|EY5yN!{wW@#xky zp!2kznw5?U(N7)frbk>aNhYc`4N|q2CUjC9U_8;{@esh6(#rFIEv;w6#C;mzA;#%` zqwD0LVcC9&SAe~!Q>#iJH!Q_pT*KBL>U`%dhHA-U^#?dWd6+euhBd{06h$$c)eAte z?+c3Ybp9^(k!VGP`ey-E{T}?M1f>0mJPX?q{~jIg%@h6hNBO~aFw~HefNGWHI>gmcUjh z=Rn)@McW>QVu^M8&BwrmC$o2c2gn*E?Q`bHKG>gpr1@AXS<^*Ry#O(kO7?W-*w7f$ zW1TVCd`Hd$QQ>USU`#3;nUp8qFO0JG!6Sx?TsI8LnPy+HMeF)m6-Keq^bI`I}M!}F?3OvFE-%!cR zY8&3u6o4|qn{6z&1##-E3>B?^xVDng>{ZQ`X+ zJtfmWSx8N(XFT5mIj-u;DXPoA452udp|R#C$=tS-sE1-Z?%~ zaewwvn^tK5?x0{RG;{TZW{?imu;1iR&9lJowN>;bvk_D|Lof^L16naA>G-8KZSCFE zsUstP#h@(aTHnt`^F2=UJl$x4i#VlE++0j~F0hXw`hg4nOLBpg5U@EyyQv{Z!DtRz z7exsh{Q*Dvw~oN+;O|wg-~HY#m756GMIBPwkcO=e4?OzzwL^$u)V}r-XH2$)$Lwn# z2Q9J7G`yS#qoLFL+T8#ezOSv`1gb!6XsdfsML#lISF?>9zS1VgRW~sf6VW4!`4W(kWYsqZTV!IbIcQmu0!DH- za6`oATMnEjm%W|C1%_N^@=*464i~4s?c4<-(!mT<^2TAPaIC0MEU;n`VOXZ#!H5GB zr%ttw+fEXKWr#p$;aAdD+k2S4@VGVtw{|%vy5Qm^SZ6OcNnvQecd**e)Ly6)XxH-= zq3G;g4?Z*1D9>2-uIFN4tT&1_?Oo3bJEBhx7zX1$*E-}w4i=VWXhT8;F`*($s^ID; zod&YCInsl60N88ZkuPnsaF`LBtj!U^qQ(>jqe=IzsFB+%{vnvaZweyVd&F=4Hx}Q7 z8wcVBX$ap>9Fp4$>;sRCHWTBp|oflPwn0kEamcPV5KAJf_F54IxHJ(7sX(o~W`@fo`VO z&L+O0?vjyMAx17U^9I1yA4ngmKX@p66@ywwnhhSxUd5oX@vLIn@68N`hDe>R1VV$J z=yDLW)Is(N0uDN-0O3A)M_O;yS|8Xy;wdP;YjD*Qq8NhK1D631vbGs!h|f&U;-|=7 z4_pRQbctxwUJpc`g5Kt!z%w@)XzVw_K=1Ajb0o&(;*Q%hCb8xW+|usts)<+NnCzEv)MPyNQ4gQb=9;An`&%iwy4l+XJudrELVeLNF_M{8$#qsp%(VCM)*Hwf$&ut;RZw_CMR)6x+KTZGAxK-6YuEa(hIWEA43hp zBrjNI%d;&>VA+*w9+4;G(3MZWEO(j-k|^w3SoQ^&!xoHGKoyI#W`)lA13mFfp_$z! zDQ_cf>AVh@{Ww7uo`QBsmunIc;rp_!IBEi^j(I5CilY+uS@DPa`tq{}nasfu^&@J4 zQS{rR*PO2ppoGvN&Q=PNXB7pNo=sORzcGrl$&g=1f@T_WeAcNUzX5pQD$(X_4f#O_ z3tRo<8uGdW&I=7~D6r2!Lym)tFyyCY^0%5$<_)bt?v5Ijtw3&ssrmoJ0Bs8uX0hpv zF6?EE@XRa_-m4M*y?J+YvZb1sg?WFQGc&e)1)KMGIn4yg8TRcp?>tfg=KaI7LT8rd z-Nl|ap0;#88JPWkK^C5Zwt2rL5fQ#G+q|PDkV=||vdufHcAt5_qOUJEB|az}-bsgA zm*1|>Rzi+*f{xOyob5ydEEEq_XUYg$Wu)~U68DvYZELi~!zv1I?{C`H^hOQD7Qd&#Oq?sm#1G#V zB!rUl7b8eG>r#+-C_DKB1KdXgQbvmK4!nw$wDKnCI(#)rEqx3?U5!s5K#2=BL+{KJ zWhbiCSW?m%?yGN1?Qc{@&0LHnSITNzNx?AvKu_M&qbBcZ__i)}rUy|0v$C|k6l#J( z30|nSV&I`t^lt}PbySf3teSq!X4Fkri1IL2$zrVSB?@6q!HMK%wLezu8v&u|awLi_ zlBpr6T-0}-0@%EntV&8Y!e6b6S3UH}tfP(`L;Vo&-R5!4T?ZQVaja+4szKeYi3uq6 zgoLb&i`qh1F{!5qt1lIH2x9k&QaKn=zhj**#(JHsScFrsW1Vs}&_gvPJHWWWA9%WH zV6rk5_5X+O&lZsu zw)7kV>5)@%6r^W8BRzE}HHbyel;i}xtEJ|5Fy2n3<~INvXmWlP{_4ZxRgXpqks1QF zq~_*GsbQ>5%7DyC+X)Asb3zw6__@Hrk~~L&gZtoI*DCjDR26ipY$`^M0nz+?8IUZN zpH&L$-9Vuo03XQHL*cJpBwqFKw3GYxq${8_9~CZ?mH#SK2DVkMG{&~zwLjB^$=cLG zNOq>0eoeZ$LfjvMMI4(@799)TV#2s6K`SVV-k}RSYToe3>~pAV2Ntf9rKSx;M9=p1 z0tc}P83QO5dRqj_MjsWY58Eoom6t9Fy4x)TM*cMc)XbdVZw!!CAj|>b0?bZzcpOn5t#s!|v#?8KUR>?1?LE-P(?lOtO9wtHQU{7}X)gwS+^CeAGjMHOd8)me z_M(N}Ty$_e#%_5)6c!#Y0SAt4nrT12BX~84MSEH^MNn4dGBB<>#me z?d6DtwW01Ue<1mFp%|Uw6#Axh+&6$JvdwgF`IF^OS=Zp@b^O ztD^^X?U%aXOI_uq#`#%moYf!VkOjMG@vnX!9u~zVgwQ9um-B9Pr+?Wh2kN&W~;cr0{59rXWu+{Is;>@F^*qhvtM#O9WG3| zcKu)v^@8ikxa$WMetCzw()b?(Vu05X{+FJ6Yo0eilnqqQ=gU$nO%nF9Rx}%&na*Ad z;)0D9Q|l2(UT|k1eHEO8y9|51=h}0_nY0Gp{*5^+;5g@2dL&amZg@f@|>Eh?NifD42Zhc0^IT6txCPDCd~7T9{NpD zL#JSV8uKz>sp|pWTZPnG%t-B$X_%b_$gpUW=M(Y_5hlwMOtf}(6+3blxmXSSuLL5JYw0i($xP2I;qb_4Dm6Mw>Z|3a(%sJ0!!SZ=nYWWs)^ zN%ht!QrY5ww*kEg;QiuYDcp7W15%iVysGj4t0??!WxE#ef3LuwXAoQT5K4|ZM)Z(Y zuMg{XkFJ4*}2Y zrMS7HL>)%XSlzEOL1zHtSw{}5wJch@1UQHC{Ui|r3j6}_nUf7VDL5=Sr_cwS=V;fv zWFia5q16ToX9B(Est(<^6 zapW`g9=3%2K`JBOnj-IDpB9`+8&h?cbf-J+Yvm;`!()|3@1|LAyDy8~SBY)=fW~2_ z-0|M~sd7^$f)d1V&`UQ|xU=3IzzbQftK42H+yOULH8vHt?1abSyaNw~2O6buxaJdK z!1G>jOb_f851ydD3&n44D<?MWh<|2%nX0luY7u>7Lw{IrBtzIuYytCmqcjQoX<@-9?%2VwIH>I}^iqDA?xK)%Vdi!mV!J56HZ_Eo>c%ypR`>{M zIVEWA59uoERb!m@@JN`4spid-7r{`qYe1ZqST_bQGsIBdGOVu20`<5Q4kMw6?G)5% zO&60!3oD!k+p8}S)0I!i*YRWU6H+$r@WLI*MBQP~=$&l)RgM=zhw#^I7a6=jL+CZw zIhkv?ce-H=;S+rOoldjpMO-4?@$}lpI!|`(x6PHAIyU?Q2+3X;E$6|+cdLtG@o+C{ z20Uog+P8WWl1iRy!)YEa0frlNfMHz4(;sb7{4%-HH9lGe{m0zQ+2$Bi*y-H>*^4^HuvYeE1e|;9rmt15p*3L_L@rpb>h3q0fPf>WGg)ORlEd4yo6>G z>`(5o?AQLfe%yBLLawFiM*)qtUD>_*clZp7 zy2G%AW7sI_PHirrX#u(I==9sXEm{SA7|Cq6)6PYt`m3pfg?UN z-w4=-D<=|#>k%kh6n2rl5+}0BCENph>aATEWF{P%bhA7xMG_~b*z?4AFn0v?gJ)uy zeYi`MkQSxwH4kN~w=R z=L|V*wNC-wuH+z%P`08$sFHxnwcM-Ite{k)?{XqZ}DJ>NKIHh$GqLo;1en8~&Rr_RRp*!&TeiHBupEQ+#o2(4f zma8BVa8H2P+ExiJ^xa>pkKS%1;kIwvxpODl{9)XpKt`wO5l*}*y*kGXUR}4T2Z3OV zcA$w+y$XC&&&DJd4W4aO+&QOxNH&Kl`YI62bxOAv)4=VxYvtMfGSXO|YG|9V9_+qp z-8GQ z_AQO6Qp(#bTf&|OlA1_Da7_<0=nsSNO0J!!+TfhBn$umppLtCJ+kWe#)z9ouLpo!$ zs~P+;!h=6ob4b&pUDkB!Zt}y8FrhlefFQJ2_Cyy&HK*wx^t28nQ{y`U%yy0S0URjQ z5V!Uvd>5Q6Q}&YYjlG8LBEywQjcWqV(yje;9*Iuoeh zcX+|?*1nT%pLJ`84x!CDdSnn%Htz6(Un#9%+h>(h=n&3eyWocqqLljh z_B);CSt%t+8(Yof$*%o&uu@vQlJ8pc>|yDcjr}zM7q}dHI^PAS zd1Np70#jwX$lx|=Tx*^MxWy2(vV4DuU6j9T+TFqT+9?mpZaQ7~91#;^AI|4ZV?Kdj z$q&`POdHU6A$oWtBIDA8VkJ5Pnb{GwUm z!)!0r54w@8?KRD*Ni0P++#H8m8_$@6VCkj^MOQH!a(prBfq&Ny#nT(q>bYnz#qO{A zzu~|mnJg=uJ9C5Wmz1S?7p`gaPb`g0)hD7{RnkCS6zthXsGS$hP9^D^fG>A=uMW8= zyv|EfJtx%_KToc8XCz_)ern7FdAXIJ;|} zK*ugk;an<+3L%(7mx{8(T0KR1DkK7AvZL-EKZ`aE#j|9sz0R7O7`zjy7v!b5^lXJ+ z-eD=l;2k?hO&Er^Fd<9Ts>FThW~mvQvM>=yx8xgjUR9Wci+SMSur^S5g32Z-BtKS~ zg}%N$H{1fzSfpV~WBmoF+ep=m@HlIWdb`ws5=Ijc;8`5=MEOiF(G2eK`U`aXS7Bsi zcd^zijmS_K$X6d3!Dar)NPnSpq|(F$QQ7F>O09+!^QfsYCV02EI&PN8xFMQEJb8r1 zbz)|=#`ZytShu^a^);fb5a&|)P!wa4N&0|bc{}tMpP9nW>qLq{Au+<@81EBp#)tlX z#p&1D)C7k9{xi10hW>uX>BY8JF=0jD663VtP5EW)-A@42w09w)zqoS<3jH;v0!f;; zY^Bg&oHbH0pkDGqf6rv4L$PblTiOgSKFMaeY@(t;waqXF0xke}&L|)hg%uHX5K1yC z&)yUmY|!*C1Wj%2a!stV%vO{>#WEP#yic#J@wB1liSdbSikcbHD(8ynu(b+4>r|^? zFzGp>&3LVHnbU74TICvSgVie6Ilb5n9HUjP663Vt1GUPN0d%gl3Qh%*G*GMHtc7b8 zc~O>c{h&CNc|&WHXJDt9t5uVjX>Ecr(i38Q*;A0#CI}@NG)9{sEq&Sqw#^}Hr4H6f z&VmK*pbhBAr%m80o&-|OjmNL?@~D|0?eQ`}ep`FsGjmFLGTUpC)j;MKi#BcTvBF-2 z%3;EG*?q1V>zxi3R*-WW?y#a|tfI(5@!i;N|GCou&tr5U5B3@;ZaB?HZ_jP zKL8X=GsaVvSpGHy%4GRlF9ok=NV1NQ0ot}fa&!&Deuaa_O$pi!hx3r3w7WhvF{Vo{ znj5~cdZ~#;T;=IZPY*&|i3c@J{k|a%#Tp(fP0P2NSow7fPIzejutOdF^!NvXZQopZ zX6gD0_-I?BWg}zsU??-O?ahHJkSEKgz_ukxW+064wM>DbWA$DsJbwohZhh+ zR5;Bpk?NcwUFFBn$>s_mu{5UDrg=cMA*68uXzgIC)5smEge&zlCgJK%mN{egFwcUP zxa~M|GI9@q&Uz0Mo^VCh?HmyF!#PO9s{a+oW4$f-qcX1y@TLIIJvdpH&mPKTguJR% z%bTJ9aMfb3Z`Q%^-YaN2Ac>UeWk|Aqro6qR$02|-^D*QhzvV@S@+>^M zyXbHZWcp)J5p!rS1_p7~Qi}og6jZcZi-Mg1I_pJ2 z;L!1Kk7}rpb?*bd*^+}iLfIMup*9Jq+%>@!UEnDt&|PUu>&U0V2s&L=Yz7KEIe>1q zu|X(Fw;0(*YD?MXq3jJLu0?7A+J`ofxDm@!Z}zFl+0cRBK=Pyw%1_rquQf3=9}!1<{`LkOuet07YV7x>u@m?Z1{}V-Slw{Y9^3tu#FPtuY!k_p0Slveop^`fT+yr{}6r>IsM|((aY)IixN0G)xh(f&NKQ)YsF@>kleSn ztFyd*1C?21^*=_0f*Wo1_VwKk_23RR<)!Am<-3%2Za?m}OV+naM_NOLjbaFL1RHnu z7cLQ%eVdho!%$=cllYSt3mbRtggdZc#fatjmKkuy67Xv*yHjg?LImhz_R?Yy7^f6>WJAcPZXx&-$b3pyMPc}KxRT7kH zaxsiYrJ7irrxHzMmWsiDMw*FY91yn86Dxwz%S|(RtgO0c)cz?P0A~X!WpyWXc1j7= z4$Or&IPh|ZHRdLLfocPU|Ps@ z0d96_7N#cMxi3uM+PByQ4-nlrJTfBFKF|aW#I<3t!I;Aa_6gWBqc^3<7E}L5BQ>Xy zY-NBmMyFobvj|6+H()g7;-l39lUXo|{mZKe4Jv=dxI44`R^P4OkjT{lqT*k;K@;d( zuGepGLa`oj;bB-e-nvcxwVUzIaMMf;6B!DZgspVM4c_8*o6M$mjtCmt>~Rbsj+}UL z$85k<)7((Fe1w(h*-H8!&Y=^NP4vN} zZ$-l893!&XL@+G3l0Ho)-d2j!0!yZJHc@`z=HNp5KS)O7+a;I_U1S}KC;1)%B^-n} zTFFFj@9Tp^&4$YMNkXTv^n9~C+x_$j=w3}QJ(r^aDB537p$1VwAe!nm3D+B!iStDWIM1eY*Q zj^_ooK62Fbqm`3m{|HRJ0N*t<>y`~$q>@q3#`V{!3jn*>RCEXZ9zgT}~f zIcT0#6mMpb>erG~RuICO*-W0nH_J^3AMd1J0RCB`M9JWxtE1+Ec<4$&Fq?<)S*JXN z(Wkpan|7>p4_@6M#a#>%#$Z0u95v!}&d()r6!thr95o>Z!((A{6o&0e62x#663)j_ zr^8u`S1D%j6XZ#%_kG;t{tWZ5NWO1y(o3V}fH>(zf>1Um;j>OT2}>Q{E!ymmliuod z&d(un681Ppob+BX7#<6olTJ=Z5W`7GI3FiLj+tvdXJtAa0|*%%N%KC=OZ^V4x0oOd z-uq(ItP$^hPLS5-J$%+F?_tgG&xkf{-kT3=4yC@tXRWt>>~zP^GjSXC!E&2*5%=bZ z>5?-5Bwm0(27Q0y;A!tDmzxVI(S~&lQeF5MKv?ehl;7N?uf$1h?&QdD!E5i!FkBqTC(de4XO@`^xp_gR*d|(JV1K_7{qf9XD-DE_ zyvz4xFKCeCrPAP`DE`4%20bj6K|F>2 zn^k_v4&onfVp-W;)l&R}?pJLLdtr5J-Esb6Q8dXtIw-(R%TPkBfSZ?|9*hyF!ez@? zvcNg@mM5QwDn&Y-8!wHPixUvm-h-Yb2&Hh(@q2NXv18qMXdU>?j&=BHopk?! zet1;jB((|5`7q3Y>t{FV-d+e^fzc9Nkh`K>Iasd2-_|g?HD{hZBit;nhEtCAqRHu2 zxjrHG*mqgS9-E*KHDNGI=7yt-zW8V4jKcB7D}=}*z&Y-=JZT|cyz;Opp0{N<-xq%m zAS!+F_u?nvix;1co-h7tbSudKnpxkyzS9-CfddemIc$X9WV&JzgIW0BpO%QE7z$QN z@zuvSE8*`iHcJEeq8R<~WTW1ykJfAAruLfwn;Yo?S$ORnUZNwv zA%DY6}org`h)+a0=T+1~1SM%mQ>t6<+5c@m#+BPN#WRg`g$+ zwk^$*UHffw$)Ysr7eJG~M=(zIa#k#1tV`Fm?yWw6+~leBI!M>7M;Q=n`!=;Tzw5p#@t8foM0^@C_JYQBT?Zw z+SIGq9C<;zGKWTBj$$8R!`G5t0+W*)n6rIvsWsJ@XcllG12tg5w*sL8lcmwhfy!u3 zo!9jNu*RPGh{IuX6Oi*<>IIo0uAZyHFK=;90XfISS_$KFrV@%ABR8tw?=Xt3Py?z; zz8)IjlmX)h{^O+Y>TB?0mv2<3HnG?zP76qA+7H~@MMv<*li~PQtfn0^9Kjz&pg}c$ zMAC8u?^2hI7luG$u~rs_VIRdow^F>P9A9U|7)jT zYXK2hWO4o(fICGm!_|#AGsp{u30R14bmcI49<;=Y_#&tk3{e6z)xX6fqov~8c(F1u zQ(Xz5v4_cMl&wf1YolSDDH~IPB+auW#{M8NceO?qesx z)d_TVP2>71l-Xzi4 zHFbcYRUg9n>R!d8r$)`lP4d+J$q1CKrBA_iK2&MnkEhN_C{A`w6I=JLv7o^xt+ckI zQJ0(VDGszBYQ}3s4b#?b7{oFm7|$mj=0R^>Tr%q%Ja3aA)nM*MFN=*wni zggO`iMa+yW5y#Bvh>2llRl11Oy!xu=_|E`^Yz^;R-x(3dS52=##gC=v~1u{hDdcnd8W{D81D~@a5n$IM?K7nn&{?Y0( zRO3II4#RRnC*Sc)w$ot@;6Sg0I1CT*U2qOV*-O5k>ji8V8BQWPfQp z@try!?i16MPsn%jWAHl+W#bMn_#K9S&9=`v3`2+T3AT$2UZ5d_I1InUx8LbB&&pva z(YK9ap6uFhd#tudEgyl=a;I2nhoRQI)nO!+$6*+IQR?sJhUBxmDD^)BD1!%0&d(5 zigIc6_Cn==a^IE4k}|$UD0tg|bD(--Iy=X{H?#rcKzAby&s;>zjZbfodWrYxDg5#l z!sOFC1{O2u(c6&F;~4Si-Q=)|eW(!N(YqcR;1ng}(Yre7yLuIV?6OCX+C+~YP76rt z+A{cZxho4;~ugAP=4~ z6-d%N%RwGIoHdd#kghxr-U$}3tFtlZsotu89UISdo0>#J`jW?v#(^HYuZVGElNzDO z$zecAM&#L-Jaz`n?hfdSPI3*X=LbO1NiOQLyh$OC9d&>ny9t0o62jzMpVC&n)xE~E z_F{@_QuC60*E6!@?3fuLExZtcvbFGK>QWqB!{LDsUQ}SL-1?z34qb{+sAFZ9!xacA zH=G{=-&|#UNoU<;`v5QOs6l~jua$<@&b=1s+$ge>qB#eEL~hq2Tbt*C8@wc@K z;{TX{zHF_6P$vgKkyb(3jMFOUI*rjP%2%4{I=ur>FgcH>Ea{YK1j^Pa*HUEGM5$GS zqPC-@Ns&>+MFX=VAnHxi`5a{{LL{^2E3vg(1xB%~}0ziE3M_L-Ww7`YNOrtZyu# zPM$*A+<7uwIge6?WWJ7_wV3?a*Fa0`9w$4b>1_Zy>jhEh*m1Q$8MLHf&=>oP9PAOw z)=LQWM*_;Hm#m1VH6Aum*4g`;w$vYi9>S%{UOS;b`@w*5*;)>v1ICQ4(@M4FvZ!agOZ82@3LHBKfX${ADm-+3JFKWnYrOw%_n5c4Z?MsKU_?CAGaD z$>i6kB!u!EJZAgDZU#8eE+M|`65j>q%a*<5yI`Aa7a9IbYP@T{?7Mg*I(2e(#h3j& zzzufGFH3lAUO}z2;FFfQ*YIQT`?6)@4lnq9*>7jtXMNeSm;538CEG;?FVGOoZAFq_ z=_J2?jBmfwX`YoYTcU59#yr`z-}cyU;mf`OM$2tq(!Ol1daHYoR9*08|L?gW`Rshz zKMJ4>ZjZ>9?InwzgX%@l+}!)Jmn&bkusqc@aIRlH;T-MEPRy|KX0I>-jKowmwXj&d zm}UpeL9RP{JL`=W3-7`m&8bPOu`?{nT1!2GHJaB4z#6LTWEhsYe3%<2_F2?Ryc1jD zm$wckC-$+hj6pBO^gS z7%w()5qhzYh&JQB*spT>?Zk`yCTxTCV!zqx#g*i6FZLV6IBj^7eHkzIp8;s>p)FeA zATPEt6-d%N(?MQroHdd#kghI!u|I{4$9l189O%XVxEMz^sZmqVNvr`S8IfmS@;4bY zn+oWQPGSwHX9PgeNi6EJyh$N1Hg$kr?0o=*B!tQ7h8O#n)SNmo<7rD;_~!_et%V=& z_XgvFtxFE#2=)6y0 z$WOd2QV1o-6(dqejb{h+Ws4L-O$R`cNFi&+i4?j=wMg*;BKN4;_9~;>fqV3mfM>Ye zk$Y74t9DQYSVmax(PG*?iq@vr@{gKM&*XMw+fkYZ0;j6wAbkXSN?fF025Lno`lhKh zhc43ZdN4FM5sr&Aja#UTR2{%6-m@7t-$nYLfT(nl{sun@7peGk^jxG@*D7!kcXv{K zJ1i)yFzDP#dh6zS#~mlmPgPr)8NdAnkgis#(F8AQYkCsuVGLXiUyJ)|rQ6I(kyvq2 z_>S=(cwyjjc(5C!8UDi8eROLJlmo!Kn)(a1%6O#}-l%vzY4bV+cA6B$k8wB(EvScH z8=y7iNR&-Nlc}em>TjRCsIkm~ViBC*m zAH7AAlOan_N?H4fKa%BkKW4Z|?z_tRPU5w4NVtl_B4fZeP>YV<(vI2%_?F@c`9<@? z=44KNhmhG`v5f!+rZdE&eKFq!=h2qENB4Qscb!_^*H1(VQ zQ`s>F&@^8x&SSgC5DV`R{HFf`w*9UQ;c~VMeh6NkbM^g=dFi9uoCDirWSjm_ zEm`G<>NlX&c&_O`!Z+?W{h^rO%{R_r9%1@t*j_p@{c?QvJo2WWf46E~=A_SvtLt!k z=80#2%A6jE`)%O@?9))@?`6qqk|Sy-C%fr%as6&UDz$#`8^2Go__JD$dhsfvm5wLE zpxBq$9y_MkW@-LRM@Ob#q+<&xhn#Ple#DQ_FVfyw^V_Chvh4@VTK*6gp2x0I*dcgX zqDQ-&3~pG%cN1u#QYRtjq`ARyd(DyuoU=jyDrh4+xPWQm6((-OD9y#IEj4s?8K}^? zPjitq7!%K#%yZcVL`be+OMX*{F7gUC_1MUCQA!`SV<*t@9H_M1OqUyExgqetHd}@I zfx>9HQX4}rZ>c$`^g*T6gbSn##?@@<7p9=&WwUMAVEStuzL=XFnZ49Ye2$F5FE4zD za%3L0m$xal!dzcWl6hi6kz*uDrsXh-9U2ypBr^#ObeJSlOZu+fjvu?6Btvatk_=7@ z$oAYO$-F8Vj?X`BE;~@IQh4PI0u8G1Ba&v4%&p&m1Eip8j0O0kqcYHqj<>7{9R3t>8fSj%HL25E7cmw~hA zfFgzQuE|NjRd$1f1Jn}oAU2k2?=KW5#vp#7K8c~;uns^oAR#a)2kXE!N=2x-IVQ`! zIRlfuz_@zEk(j4MQ?Ff*b9WF2Z8D|;Nt$QzDYXe_jr1bu z#Jtp|T-B#e@bh2PXZycJ6k-#gXtbCB^=mQO>alw?<)$tiuopzNQDzHGJe zo<~62cURN_uM(;xrbCNvP`&& zy}qlWo^a$)=gk`9ui{V)Rc-U|8qcayGNtL)uC}ld$&|~@6{ykAA*UakrHCaVJZ@~J z4VU(gZGqKnx}Y}swHweDyK%~{ANgDWHJK8-$+Xw^R6n)|Ublyo=Cb9WM39Vm_L*{u zh&K`HV9r3&b`GJW!r~d#wsWZFe0J{hbG$#8v%LYoFK|-Y{$OrLDFw1>*+66CC4L42 zl8%7*A29o-=-DD+YFXq1vViXpEMr?hd}cPpvxaQ}R{(3gMYP$81^k%Pv#mWYwJl)u zPE%@{?|}@e@So~K*rC)knLhl_IY`-bjj?{85rnbf&3R!q244qIQ_-Y11_9PDJZvDX zm2hHldKla1&K}PmtR=2GuN|LmmJQQW*8hgvP2JB-gBNtCI^GI;r;zab|#23qC{1HGQTfj!Y z8)Y({$a0xWTRMk*0s>{8!?uU6BX5sc(p;R4Vn)+bDy_1(S^dUFy;UEr*JNpSqmG5= zkk8(%KQ^`lqj6`YFc{fXqHUHaKtp6l9AetGabqAkJT|b34NDt{N2pNmKAW5dJ~am) zTX15`Vi{WMP=Oo*P*Z`V&0@fLS^lWgYUX5+7Mzhh8M9YM2=#OUr+{WVKb-TIe7~b>-IQ_yC}gZ5}fe*Iy%0 zw&Hpg)aKZgxCX9lH0ljqpaUEg6ZICjsT1#XPmd|ldG{)Gcmsvw-q`Fx5w$A%QC#$- zuBuu-jMZs)@fxh3d_U=vuP;T>+m@-bVDkS#u#8Wpta2ZxWDG_jnfbfM^!rhm+FSIq z0n_gYOl@81@{@fOjZkuYF_MZ@c}P-a9L&7h@U$P*w~Q>@BrTDBM5&$v7lq!mq35dy zo6KA223@lv8Qd-Ilv3^bFfD!Tdol5zry6ody@z)DZIbw`59#A z$CV6Qp4I_|{Q={$mC;dw zF{+eF2W51MQIkexbacNe#-b0l8CDscS78|)uzG#jQTh=Son*2C011(D3F6t z_2$5VCI(yd8t}Jm-M6V{Bkq6)Mw^E=iWiLr@%7=-5oor_SL#vr;Zcj9lqcYYI?POT z$)5Dg48qKqtI;mrYVvFkhNhT1C417iZO@Ysy46HQ5-L8og(JUY&kP_cOZL11KZ%k( z;?vP9*>j!Av6~8XoM&(}vN{US1YB8NsZR`)#||_HjPF<}r!Zb>mFr`hX4<>tZqN8V z1b)CkqVvK%?d_s!kI`0U;T};BXMeq3lS|IBE?%?D09P_EM4W7$&v$$wdCHZNP1FL-N_(aq<*EirZGI^$;b0c*&yYp!!yz z)!Zk4oah?w8JYj_EEtT+|F8_PNz0vcH$YMQ~~z8z;=`XO?&K(5TXvqY}!;cypX zfXAVjfee72ZnjF}1MUqd1GKi-*S@f$=8SXk7dTutHwhnY>IGS6VR4 zg8aF|LAKfq$OL&8G{ET;=DL)3CVf}`6hC%36NK8tOc0zFSo+OvCdikQ;rQ02rjQ^@ zlLzt(2sEh1k4Ww}k5DfTN&>(%kQ?#r9C8>S0b%rZCdQ<2T4F^yJ4W$r0F6C;MnfKyQ(#O5k~HsZhJt=@)=0uY zy6!imOD^83mfPk8i`Ugg-+8KuKEx8;m3cZ+rg31lz(qNvMomFS$_A8VM4o-gnQGAN z2ZDWVws0||BV_~Xy8%#iq>Q>OZ&JvaN*!Pv$mei=x)%phiJB48!Uqs2TMO?xJn!PxQ>a5)U*mA4X^z)LikCjycwp?- zonq)d{bL?}$6b~3fM}B+ovl6asuYO{kH4)w5dUWd^kr)gggP7mMcM;pFiv~mRVi9~ z@B<=;lvp9tyXchqtp`KX468$!_7dPT zy`$9T>D)XiA*Yl&*igJ~3rN0GYE8d#O0C6D!YL&_9X+SiwRpO6sMMvKBrfaBC#7+i zg-hxhaX6)$%JfL}!->-9RHIorR2nLvX9^tE;+B?dS5=R0R#|WJkzT!SfqBhVW&B+W zn-bXe{YTAF<@ykJgHQ4b?UFR<;CigKp{j)IIl!5ZOMZPlK_`%9)20Xfr<+8W$op=;CxxKmwXq@2-`)5|AHFtnlG!tBVn9TMpaC+cH5m6PMZ+Z zl~2gK`7!v5W68!HUhw;}X4v*wUsmW4?q|Em-~}2&h%f5_zWq+8c~-tGiN0-L^JLe4 z+p_==zN|}Nv~YN?Z#Ot4!y1rs($cgvc~RK_zscY`d>5Po zlD*{1&A+i-WH22yu1OZ_<2!x}XFkm-jnX)?^YQ&oOpLuL$(zP}0-wPT)vuU{z`0dW z`}oHFRZv4&<8gfB9OiZv)P>gFc=SyR(r&h&PU@h_5yt2V{$1PWt=DhW$D{Og!RK9K zNpO_pp$JHEhuLboMLusYSu|a%8-P}G@AH<`3ytNgba~$eV^J<|b49N_-mBbA#ikiJ zKrY`2dq*|Lu%kSWlqIYma~SJVEgl_8>Cp59WAs1YIz?ki^Iuy9X+#GRc+YcY%(`ZT5 zfD`41zz?Pv9&iNlQ6p*VtHT z3@ddWf*TqkbhWT`;Bf<6CEoV<`B%+6=r;Q*P$TnqJ>XBU0}k-Te-s+vbRFZ1e=zA= zk#I5BQuU)i(e`p8U=ayAQ~f)9wonig^-lG5c!DMJVN{#w@4;yS)ibxg_#Y+1@$IW! z-XSm0zaY?{!WEIUeDQAz@|F8bGNLMPm9VH`vMd*M4e>yyzc5xhP@Jl@3L_(8jWHrK z>_GOE69&&D@Zif3MpV7}Xs9*~bsj@23+@aW$P+4nYOCIuF5pQCh8EBuq<*)v@#fC^ zzi2=)QOHb^_y2eR!k8no))qc954*Hc_eC6$1BcW}5Af~pB;Vd=IFPa2PpOsC&Y zy#E`q4c7a=$?3&bmN6O9=R!-I=re^w&Iiy~??0|-g1rC6R3J(7j4*lsan?wtL7vI` z|BB=AiXQlbL2L_sVF0IQalhJ3XTPBJ`5tyoTqQ)o0W?f3dvtBiOc=od29)Gep1aAt zXOQ$alDn<^&8LNGs}41l)7Oa{i?}^7`)U1#~sfnqu6$(D&NdQQ1<%q zpjypg?2Fz*A1-3yXk6Wmnejx{JNC{Cw=P(C`aq=tmk~_XilZf2a@wd4lUsUbbWX7` zFmbf})912ZG7V!W~}rjwlp;;`Im zW0}uS4C=h>+Y4u`mhxMnoz&XP)cj_IMO@_5fFM6l16$*rlwiW+Z)-fn|JMQ9)z)|j z^>6?bX*|>$aT;$mbP%KQa*NR17R6np?`}n)Y<+h@xckSgN=ec57$P?Z2E5;J7iI5B$+bfCqI9}oo?szXN=bk#wC;rjqj(^T-} zrvO9UuR6biFflv8@L!w&L%HbiR=J1`EfmLA9hJQBLS|!DD6B6OYjDrT*fgA))k-Z~ zG~qe4C~;+Z=>A_Bu9SA*)Q82b1{4~FIjQ~Ba3^f8Uw);#&8 z&`0IsQc`G(Zhq+Fj{#9xkK=#gClUH6J{`T#$LmNaFyyWGsMX^zkeEgAqpZhaw3QkC zm>uM}%m7#CJ=QaA1s81P6e=I)cw7S8e)FR|%n>;QZzP)s`;vzeZ`UJPnd9_?SiZCR z>?o8|01mWHNHpUnz6&mxNcNH+>v9p>MMf|YHLin+W?7lzY95JB!`!+O&3L7lu6#o7 z$QxsOtk zl{rj1EW?yO4}(!*N|s4haZ0i>hXH6*=1|iN3)=q!qo6b0+(4!6d&LoIQxx6^tHY7F z4%P?28s}l(a2RNA!j!&4y`a3r72Qzy<*k(|OzBuz)?l2{R}-SxaXRQV!sSoFKG@6+ zQHqAsi^|_RT)^+r>1gFQ2qdGEjG;7)TSD5CH&0DY)*Eo4q_{{E@0k=M6Vg(w6(&oK z!f3Hp8!#@lQ@=s!yRE7-n3NM3TIcuGO++xDml>C-xn(npeoT1AMlq)B;WObG7XoN(cn0cnD?Gy- zA6g{3wTw6$U2zH3mueoN zhQZjn>U-cHtCA!Ro-P`@Er(7_EZ|d;z7+|VajUl2SU{Q*^xH{9-iNUbHX`q%PA|5~j#2p! zigDWTCfhP9|FZx(Yn5*u8l-hTP6d)QZ@ozoc{pn%Ss+z;^>~&he6AJ-#uIH-et?Y{ zYm01RA^x^i!8#S+6ZrE`G?7i;rOa4zczXS;W1Y_4e+AuBDgt0FrQ-> zta(jf8!(JV*(?a;(}AV~E-o@=pJh1L+0Me&PU9$ z>g~`Hx36KAReu4XvtCx^3^~2f!q}QO=OB$xwxU6(HwY-7qOl^5R;O7(t-5?a>E+kw zp#5+mv6o+XZ_Q@|MrA84gp#9*QCdig*9G)tD=mb2bpRA8EfoGZrFA-Vpp_OsAc`|m z2O28Ep@TT1dFRJW8pRpue%0B)d9aHA$|@tj@+1B@BUCJ1C}cK>?cfzVsydk)c0^~K zncYQ%5dAk8iIh>`uaz*{(a}Na+3ttgHWw#dj1D@}gQ2<2b)tjPxWP?AyzEePkn$@j z*|CB0qk}F0L}hf)h4@KC2Z>KdFFFVcqfHdYk9uJ=g~KdjgWgCr3{+FS&H7YhRE%sG zkJ0=kyIRG@!IHSKi+QP061ftdGHQ3q)kkG~vNnMFqb2&2>8}HO#e*avQ0||Mn}Og& zTp_EhrTIu($vwa=*;bN|L%Jq`ZNKxV#UYJXj)2z)D5S;`@BvWhdVIn7JsplT zJjc^Xv!e?pot^3s7dg7z8v*FRos|xNn$y&CJ+Y-^a(pMu*|8HvfCGIR68F^PyWrxU zWH0&QI?rRf$cS~J#x?n78Ta&Z9*Ir^Vyq*~POG@ROiWikA>YoA!5{Y|8+UlYANTZ^ zZ2N57Q|J&r#&(gx3p50C<40!~``XTpM4#u|?{u1H75603w-*C>vTMKXS*?h;r+qM5 zI0!S8Qp#TCDJ2IWQA+M}t&|>SNt`{fe;3F)jOefIHva<7ps|c7d&!rjRcsd-j7W`l zO)0JCk?53LyP}lN2Hb$RUX}#>&Xadhz?lks((G^%KL)>2l8rmO;8#jlvhB0H5ITgL z*)B48frb#Gl!|=&olf(tl#)c>Rx^3BYrh?=luq)lk5lWT#q^p|LCJT`Yj?4P%trrt z02jC(dO6<(w;qzcRjOkFx}ov0FcuXDZk~v!FmP3_Qh^y!;EH?T;PQ#Ijs?Sz8wK7gPa?2Lr7D-^ zj|Er8D;z+x*QLSirDIKwwL=>Ww!VbM34}j3VkV!=PE0`k#J$h}CwxrIh<`s z%Xb-4n^@Korv;{DZeu2=li~PIv`iI>JWgKkLkKje#*au^F_S;->vN9QL44MaIF%cBu`ks@fV)8%lFB$Dyx`Vjc<*y-%>U9UzL&%mZ_6*nF|# z%YD<{A@$S~p-jHy79=DFu6Kon<<)Cj1Mya3VFX?ajA>ibEX@o!-_ zu_2>0BD}isn_@)SL`OPM%z^3yF`mypQvQ=9>G zqBzdMHdw{6-s#2W!5GDHHnhYpc(QML-2$LcQnt3WdgA1H}x787^kqqWMU&0?^1Q>H0=zTsC2HOS-

;{Pm&)&9BApSoS`15_)LV-~K8URH?fvgoL6qwx*BNVyW4Hp7EO_RrumPBGZ0%eQD zKYEIDVkRtxICu+m`g01BfIDbzcYSJN%uS21Q+0ZJa+X8)>Z`wCC3+|3Xm#KlvJ^9N4PwP-yF6T zv+uC1Z|1i|H7nWC3~s1C2kDwhwNX!DS#_S9YLf_pg|e}fc>z0BF&ob3mq_O2r(Ww^Vn+t z#FRyQm%1votWY9;g0YrQv-J)9@xrhq1tSVMk%KJ~06aEpXkTk)L7YKjWjZ4~b;JsbI z3DGHfX-+sFIKkXucQHlp1`mejfuf2|q5|G&+#Z`Jk(8pRf_{`-*}|BgqIUogl_`1$ z@smi=6Q7P=iXIg37J*J8G4&`G@J^7JMUviY%p^TAAd2k3&rAj$x&CtAB1;&Sr-yA( zir#McNEZ78NjN+dAT>rzel_!%UPeC`Sj9BmwkGED^X^DsAI){b4NACm!i#f0gETcd z3#O3n@# zQ+ayHg-G;L>IbctHnLRC9@~0=3)D*&@?CIxN%oR2PFJv9Wbh(2-Zj1SBp!*Qs+VpO z)0I!i3O@$FUXqOm>7~PL`>b9H9l~8~7a6=jLkQ7JFX!9ubed@61if{h1U=s-+lp;XR+2q3El+I4^-||5mQ1Dsj9O5hlg`LR}-E+~?t`3t99wUtY*khQX*rJnK9}W#Xv|SrR}a6;JUA98gZv zz5`4^ZYtizPAVQwj6WBza0gvhFWA#Lqz0g`YtrV9|q+i9YzeLE;Sht`z!m>l2>5o z6kU7iEITyV=~WIpMcrERdI#7x(5Bu)w{g=vSz+#Va*pnu2GJ|G+P!#Q+fvOzzX^j#$qF6E3#>!CZ@RI zw17IE+icJuB*XEYpt@{KQG(w?pg}c$MAFIz{dSUDh?YV43Ok zV4SSgMnHOkxVYRO$u63jMFQxh(S)#xAdl7U@WHLIZBipv64zQ^Bgw&5>LgyT_7*%I zWIUY2o{MdeyU9ou zHT3vKrx#lz$He=rhn6_~!o>S*1JKxbA6!lb<$oGefh5ga@lgIJ&KfBWP#SsppC?$- zpxD!Ss(JG|mguf3vyJBoz*1M|kQ(hcg~-t85lS*5&%P9ZZqV%4l6|eHQ_vRfk#dgO zwAEdPwuoypLtCU9QPXjv`;#FC6Hzll+M+6^!PXY|tW#})sWS&en|AtENYdwBPQRUK ziI7F-BV;&3xM8Nk#@`LB><`7sg5lic_@~WH_kIhE5hX zz4nS@&a*tf7Bv;5CcY%-Z>tG>X3i;3XM0hx8tD8FqD@;(xY?X)AL>5Wob|5`7Iv6u zK$hn(od$R&qp(oyHBhc_nBj8OAHzb@e82&Zli<1H5_@KBn&c*Up6q}JQ3U%Go9%Oq z1W*5x-mqCNraTYXdI58|mWf_@1npG>LP^f!`?6ON$gff_@K6+JYpgvU7Hbb4LkhIT zjb0v8QJ^h#fLA|$8&`SV%k8`&YVv7|dL05~Z&5c`W?@!oorUbCvNJkQ%LGz}i+Mw< znsO9rY=w6k%+#1*R^LM0j53YO<2)qVfX5`5@T_bL0ODT^=*t!WgxVJXMFN0q z6ej?uKnF1bkekQ(W{Zc* zkk$pDwP~n6h%*D*0m}j=Pr-8L+#RKb|6?p;#2hkw7+UJ^kl`T!o%OaMXUMpern{)g z%3zM#2Xl}{C|lzp)L#oIpT@J2VAlt*h0@eP@9Js$Az%${X^!%bJwrEET#q2r9ET-cDKH#`%jb|7NKksiE z!&9O#wCD6(z;La=kk{c^09qAYzfXXt8;)7if2d zfZ{u_c@Uu_#bbC7=SlJ)4`r|0P#e7`K=aYM4c9+;B(OIwXF~^i-NvIv8G_1hsxp~6 z$Pm0cYJMq0Q1`1`a9csWSs8-=OJxWiz(trZMG${5^7ruTHgWf!nJ9=)x6_S$>1;v# z)l3&m-VV$eOx~w7z_3h1gp!z2x}nl5|4)rs>P+C-8Q`Gk%)&GWT?8Ct?)kcyS$Mw( zL-SBt#amHkVH&rgJV~a^LKVZL0 z$*4LbVHl-p*!W%^JJ1|3zEd_}g2XH`3!mn;)r+2f-=%+>3R`v-Jh6(9w;XtJAB4gg zD3kZae5ThNUxc~NUXSysfuBoYAI-sB1|{6oz*DoPKq^(8II~v1cq5<0w*cQ#`dEI^ zg#K|tEMKTec1ZKT032wikYv4I@m+9{>9Uvn!0AOhId;j2OsB?mWcnCZ-{N5O^OK}!c}Y+8N5J42uX^0GT(lu(>$xB z7>T}ZTJvPre%oXFMY7&^gq&w@KyaO4hrDa`Tm3dlz9;1U5xA4)xpOm1kI0=gcL46( z$)8%AQ7pWd;Cgnee-E?p$i>2IyC!4d-w1;-V&V1AZY+HAixCU27zmcspCu~-MkhBG z{?fgr)>LDnY1|l5Y!#Z5rP0cP%BVOk?>F5`nGx|@V;eG#9sbnez`2Qwe-HJ7{1O)z zukg!T$IC;BV`eFZaq(|Uh;oeFr1M3GS8RP75Ig-jXn<20OziY$lD?~dj~}~ylMc0s zA?-LVAii@OJN=7fIKBm~Y39%uL7usvA<&>2KO$+xPCve{57*E+fqzNO)m9xYrV;84 zN|=g;x2J%qUBC3ApdN6cUr4v^9Pw@lX-lBhYbRGMQGi*Sg*BJ5%WPxmgS4fXpp^jR z1{&itb9<+)l`o}&j+BX$FbaQ}Xw!}f3JD$TbNaQsfPsO=7hoG~=-^|WUR-H@U=f(a z<0q$Q+KVQqx77DnXG$(#(xLHoF-{xae3c0uybM5NeSm1Ig943>sX&tEnGgyz##tjN z0}0CuG+ya&yJE`JbG@0p4LgVpAEdG16*ISrv1F4QxtdPf4JgTsJPT8lgh96@0e#VF zy8*Qz0E$lAQK#ii44t-92biq%Z=^n9g045h_+_+7Z*{Nnti704M`~u0?|MdF6g4BH zjbDgB+1hx+6V!>5jC;`K-$mep5#N)mb_>}}8O=EQ^|VYNMF{2%O`11Hk;c}67=QW( z!3sVxGLMYo!i3%<+KdqZjGvX5#0UW5|GI#_Yym*1R|i0m03aL12>_-J#0Wrcn9#QX zg>Z#L?Eg&!%4Yv(`D0uXrnF*ylRCAqdoZpj0TRJQLVU!#kOMkVZxxDq10{;F zXD()(YV42JQr*8s%BI46)Kl;$hnB0>Qv5r1hGXJ=egiGBOZe=&p_W|M1zR6Fb~J(% z_(OIBqw9W|gFQmoIt-zHCZK#e%nJPRmgA;1nD;k5KkI=s;Sxv=cpBHKfMEgPxmigs z2@pz-D@G$CHU3Rf!wT%#>Jig?5p^t z1zM;>7UkIOAkg9kQIkf27P?=xUtbT4RV&cqUJQ#Et29KGvm0ok&l#=S%c;`K3f>H+ zfM+s{Lj5>^*ADe=g8zo6Dr1Gh`a=EiM5$4zjP-2We*Og)sukg*2qP&oFyHq9^`nCn z($wz->YFRhE(R&Q(SxD6{Z$9y6r_;GZF8O!P>_N;Tvohn3qXF5!utSG8Km&n_(=pQ zh)+i^Na1?rSjS+3-r3hbzI=933W&ul!W3?@!W6`iCgCtFCQbo?xWdKDO@b-8wI6D8 z^Ld!&>}3TXpYTuu+kVSnxjtSRY!+J($UoQ`pBxlarhj$B#~>naM1%}tM9852ePD8W zXskX8S9{}~k@W*m_@~sWnIR)i0|V~&-O#}mX32V~Ch@fy>B1{Nl+<{BBm)$Fm{7}i zo}2CD{2su8W(o;V_yyku7oZ?}$@i--*v0WnhEtjv?^=Mu$vhIq@oiL9u&x9stOndd z0u=iAG57-%WaADm_yZIk$F|P~D1;7SH`_%9FVGM|0u*lH+wXLmXBD6z(YMWNp6uFh zd+fdlQ21vd=h@5Rbbx}^ztw+2$@c^(%tcMUJLhIv=CCH;GXZ06@2i$y6o=rYtDeN_ zr-0aVABV6iwNjXofe5dM(WpQK+pMcdgf%HpBNRcg3M`!ep6msfm)uZu0sHvtImre2Uy;sOv9etBzj3P3n!)=C(G@Ro!s$4CUi=N)FT6>2~P z!b8vir!1HVgij}ZS3iXxyBvW)ZDIrhP76rw+(sb$JQB5oAe;a|ZUh28GdFPBPB9S(#7P)|uvD}eAA!*8^xH`U z!gg$fjX=1->BW`i@CbyhVw^U-$-+zo!cG8C!jr{VvdL|@8w@DPj64fd1cE`gMFD-$F|+}7d;k<3L!(a1n;42f zpbjts;isfNVS>65fpAaMjF2|I3xTq=@!5DRkulGHc8oTVn8YwU;{V41ec8;8P_GPtB4$UHiDPz* zHi%*N+-QS;1Qf!R53%~!5h$D0Yshp~NP&(La3c!*feGlq#72zzCAI_e0Q?vgUf?Nq zAX;?r5u)LELOmr7$dY<~(bDs96lHDcLEqiK3+C`iPq+Nsi>`>7d-8vhe51#DKEQL! z_q5?bC^@beSww35R8qt8bMFXZ9+q*OsqVBE%y~%3RFH^z8dD2qGdm|Su_3)Vv%{>y zE(Fk7ufoD>!4ONKn|nhJ(#TS_s3259K;??c>NG1TQ|S9ii}H=oez=mf1qEIH*9VO1 zO%Nribt*#1QN;)f(n1Of4`mxI6t5o(@#4)Y87)-)aYhRR1+>xPQKLWsbrzzWs~rRi zJU?pEC{RH6tG3mtK!G=4pa9w*CQ<-@Hfl0}=l}nrT?iI{g|45cZZM z#7Vv%Np|=V0%Q3f$tV8>n<&TL-tL@sSG%{%&a9;iU`Q$iiy9~wY=8vH5Nwi=s#FM7 zm=OLAAtodN5*$}5Kw*lXa#d1ARZ>X`iX`9b?wRSH?Rhh^whCu}nTPW0Lw85&j z3M%NQEo)FguuL4ty*XIYg9<(dLS<0FWAv8@DiDAD?1BnTblZ(qsZtbUV)Zcv17<$Z zh*(8P!F$ANzgE{|qg5_7MBA7vl{-$e!mOcv|`5HDowX9FOzHsMAVMS?7-CS(bKxQnlU&~9E;0E8spbOF-Fu36gRqlf^A zZ;NrRDJ2{}jhmDsF!}5N5SbOi6vbxy@N%OAd9N< zq3NYZc_IcC*r5bweL!?qIwSvrZ-ZSg$;vb7r7yAdvwA6O6TZQsNRS29ge-dLyL|nF zcJr$Ak|f{MG-+ekES*^|4evVTv70z&BANX4fXk?QcJnAW)g!T_kC>}i6ba&{Dvwl8 z-s_aF<*^PbgsZzw`Jm_+bIB^L8`Bv)$2Zlkk`#keuT%bYzH<9@%2`FD&R5P+p6fd0 zb1a@gu2YsR%%dUrvSx0nXo_0@7R^~MQ0V5nSvKhkFxR|c4MR8I35vPxoM4elg>G8; z8g)$lOJMfehi=O2l=UgTjNJS<8WTir8tWcG;HJDzSp(GKHUl}q+T;^znhO(<9=G|c zVx`M1hzYyAzY>{OO3JwwFWeQ^bDA@W*`uz`QHK_{`MH?1txeqK6ADZIxXpmRw2cE5 zxB1A$dd5YQ7dqlXVVj>0nPOh3+)lXPSXNQ6U8$@-*=|#wDs)|^aHQib9a*52)M;7P z=`0j#%Mhtj#dhEF_lP#^OVUlCtYNx@$|S~XsV!PK z*p;4-{6dv!^jUwhoG~SccLa{X@>RD{H8nz1}DV1D~8~&IybFs`U zx94RM%O0V0V_`S5IWed@jGM4`S?UOk5VKQqHkVAP;BXf}@!)WJr&|SHFRB<8HG=3K z?3SoAe{lHQV|WJ%4u22T!3Kx_ehiCgvGRl^y-T#ygx7~IWAPpZsBZBn3tpndJ72$( zG%L*mG6jcgT|q2Ob5vDuIQ5#+F3>D#!Qp$jkr4rlAx=&t&XR2 zb&pak-x7Q>@4}^bdO!2LHx&!TBmcAH{X2{-d^q(_R{_cv!`TfJVMg-x-!Z&{D3bIA-NZMDI!(tUerI;D#t?rW3D#na3iCp>40mbQV-@g*OXb2PtQiL!H8cBwyE=yi zg-SMdmG*DOT4b^$^(yW2G4MrLc>T4OGl;D&2>(&9peis2Hiv4{r!$NGPb40YhqQn%Qvsg6Bafh}x^NSrh6PIr5SGH=1XbHNI z@%C1GBW^|wch`QyxxMB#OJ2FEov6E~)H&^Va>>a>kwR(7LmI+p6QWXET5>whGRk0K ztKiBT#05=MekRueyj$(`#ZLNVV6Zf&!@0xz5Xna0xvk(W=0z!om?HJ_LRK{M%ww)W zO#{wjUIEZm&tp=WOdhas~-nV4Z07>X=KqfR>o{MpT@ z>DbU+-1lW@wW;3;O13(WekWc{N?sWUAg5?_n+I)^^^9%YSPrnNbwJCPh;J*o_-6Jy z-CW(3ZAn`$dv;=Dg6?KYMAW`D7qwX2;EuA zW*lsN-*
iNF+5hwpR7bnerr`w_*&DQUSayL-65BfT=(85UBc%w~gJ`rn;IsY2q zj_=RMD&$2*O;L%8G`Gr>$w_eXF#*LRFf|WBN&e@NN$NG5OnP=}PLgWzHiqeG#MWV^ z@(?1##8}6y|B(8u&jSeFLZY58)1o@(%1+y>wU%eBZPdi-Us);0;|V;I(#gzL$$tWL z)t*4cruTaU2Ou@yPa(eFBOs`!1yrg>aQ&(!DzG3!s43EqJ8m1?pST*Wp8!1P`Vr5} zQB6>iXL&|7aaxXQ9?G0Ak{7Z!L$9j&BF($f6q~tFawQN%^F^LCbxu482MNy04scHV zhFqPd&WT5OgUEtwuy~*+l)tZxi6tEs??fkS%(U`yqBS`_Gc!X|J~^d~4vD@jN;NNM zUgGrlR9IZAR~$Xn$|Er}W_DWn{o_PQ%bi(rDPUnzgVj%+M{x4Yxt=T1y!?@8`*oJr&K+!^>zJ(P8G^t}ar|E9C0#+e~p0dOQWt30Kp*R#%x zBld)S@N@e2ormrX>zVAdTAnTI{}k@UY*%pkMtJ0&RM-@)h+!ej1&FuOMsoa_+wilr z*lw2pFU+AhbUyuWs?ex4YhLzBMe(rC69gQq za(`j^bOSqTZPHQS9DrH+w=#3`f1in`F?#^5)R=6XTfGJ(t|!+;t3l?n6n z^J;ST$?z(G4YGG-z>)kB`A`pG1SGG?@Yx9b5Y~U+#Tr2_+b#~FXtyIg^$RMas_&!h z2fJlMQw|qzpi+U0E`LN4@<(AW+rmJTkAOV_I2fBO$qhcnqu@^RNi69H&Az~*NI1!- zDvwU`P1qP|%y3nD#d&{IP*j_%KFJd?=)u0B93uX@=&p1|KEt=ce(p_H9+L%oa)bY3 z>t_!UXKljhD>mRh6S9OS3l5cuSX=B$cZE@v9a)b9v z!O?V%&CDy!6b+?~s#KtM!ru`eU&Qh;nfhx0mr?WF#G~LekHnHbVD4s7BuJa8JYrYg zGsF&$bx;{x-5KJN=ooVuHLV-d8T>}RsdkN|7@T^B_-%aU_A|s;MdQ7E=;}Wz;My z=mK#PTdryK{B5zxpFC>dww-cqsaD3}y3M#it=Xz{aobsa#~7eD;YGPoa@}6_GvT5* zF5$HG;i*7aMowu>*>Ie51Xy1~VX?Xc6Q(?C2zRmwb&1S%O8r3gdGc`CDkq!Rwi%h% zTDgyHWd^pU4~=VC6bVDaDj9Y!^cY+JP?~TDi-K>0RqQsf@9>q|U4X2V_xQ>=%405o=zSGb5Eq3HvRDRj0c88k ze$@4sTI{vY5T=90xxl=f3N)r|;jPaB7?a?EN)LWU=Kqs?a}&+~#{rko{C}B8!I^)F zC0)lo#iB@15UTRX{9nQIIIs18h9_!Jr7|?@|9x8*+O7Xj*`}~MUSYAS;jM-&Yx;Q0 z3C`Ql2Tv~JKWR1oVu+aQ*#nz?D^KmF|4O!v8F-pLET&l$3B$tL1iR^fDO>+gns6J7 zf^ULV=HmMPULL`qqjYtqU-p4%GLlVyRxSBjzNvNtswz)4{cq$ex10W~l)sIyoTEI4 z>3=VaWf0RZ+h_KpuD$wAKVR0&9~NyY)gPv%U`zh6M~scai+LB0n^l8u%{cre+f6!q zIpe_3stMzue*0sfug^8I+tt5ko5Ctkg~h7lM#WaE#PKt? z24>W@yF>_7xZ$j^?0;B{1GDUE>4Ie1%O0)-P6d;Iktwa2J8$PatqhD!_d*IRiUbqR ziT4JI3eegx!z0LO!uzCKqPYOVeT=0)Lm$|6CnMZ38SJ-A|VXV0&Zy!RP z)nZ8>LT_SG3}Fbpokwud51|jT^k?a5t05hi_Tgqk4r%)4BP_CkN6#82P_?=w_A&EC zwsBcFX^t5?mVdY)SiZAbEW9^PJ1HvVtko5dpzCjef4=r9XE%qDg*};T_QzmAkZV?k zvjs_Jw}eHt?6Scu0^57%Rcr|y+0^W^^Yw_I6}*mv(8AJkrBI_7Mb9a; z?#CHk1R53)yzSB1UBp|L8jZrT<9di?EHs?^oras7fOc-UXtV)+$(-o~txb;FEeZ>r zqZqC3#y;xy>Yg_s1L_qRBL)1^v~2Yy)g#^Np*f1%#dVe;WF%5?2ogql8tdYZFr?(P z>%unGjax&ne`VM85chcoIc1SX?Z^Vo22f<;!c<|g>lH-$hf<|dqZ3au)gi=dR;=OS zoWki^S!SE1yt3m_Ra$31Ki1iX^W;yMNQKXA9KWm261M6l*Q=D{-!6aL~H^d#Va3d#J^~X0sd@hByc4>@NDo&+W|02B` zJvj5?nS(tN2%=ZEC#aKdJEv}e#aP>9tG^XC#};L)^MnXhDQ4bCK%oE=k$SS#`|ex6 zZXFVIA+|R|M$~Y*KxWy1*&_ud3?MR395|rD^{hEjDf(z(L1eBm5;dtBDDHon0{@W% zui|v33N5edbk5e0VLK?X*z&wq6Ir%p5rI*K^$3hKIWDx8l=cjESm44!PZo|^cR(a~ zxCJ81z8o62B@ULB8iL-t9Ix8}Vatsbk($Gu4Xl05aSI}c2bpmY3~CicGljbyBRRNw zzaYq#o0AOHC+KiN0@0Rjn?91SZ?{3LL<80mZQ@7)@duPRMiZ2`(rh}F8vGRiy6BBU zZ3$k?3PH%0soIbV9Z7J@Ri`5J5XIUlOHnh&r46ZWT(>SbXCR|tW65lfWs*Nu_S6og ztD*AuW~o*I8fj}c1%Tos%xOH@{r$Pdp~?gIUZEk(JaF_**Nwa}P>1)GDWVis?-q5M zdEmmdkz}gL8303u$?qYeD(K1j=co?p4H&WI5theeSWFe3 z$0PWRXr~FUt2@Rc_*;Ok+9NO;*zXlkR}f3nj;5&uwbW}$BSRafW!%`q34S1r<3k$$0fgK9ZSb9_eB zX==Gm!9*e)r_q}T^IKz>O&{u*^r2QW2@%*06;SUHPU&+c$B(}&MwmI2^0-~^5X>>* zbsvFoyFLI=-tBr&d=@w1sCxnW7Gm;*toM6m)Vf5eX6)LwI5p>l7DgjUr`zB9^@DHEY{wIeyLi*|2aH@L7C@ zW7r?AK6-~aQq2*hYh@A(Widwp6rUH+JAKIWl48yaCHIKF2i<$=1ONRlf>!to%bTU}W>@nW`N{l*|x`(lq@!sR* zM%hXdmh86jfuF5L2yN?`3rL4%FYb(qqB%ZxYQ|Wq3!5!IMRllEIbn-sQ4Py{42xCZ zGn(R83z0Ev&{Yv*icbSn7x^rvc#Gbmej(lsAtoO7TjDrpdD%5BG1WSk($0QMOi)r& z^0-V(Ov6xGVjfB*0n}E4AIh*Wr;-5DhEUq_$O&ULZWG9QJ)p>JV8wD6wzpWMu5LBTZIA?uFX~HeWYot&*zd8wE*~OF^oN6aOgMV8Ulqtxp3~SFQctgmw#U z(%d_2T)&=uF4RHDVq?Y=XD+YBOy81f{e)<=ZA(N?N2pTjBt{T@sQQOQv`QiP(-cPb z>+U@vB|QD6?k4&llk^)2;TP6Up=in^Hj8=nnjsB_@fu=?hSRg}GzV+)aG+i^<>?Ca z$7O)GZ?O$7PcIO4@|2kC)09J3Qj$lWh<+(gJe27RkQ9D2gTyOefGlc0Ux03?$>R&8 zULbQ%u0B?tz?}q?>?8)sFYF<27cYwBlh6 z#(fSlhDB5rzcz+$Ul9W&-L_wCAu)+OWpxcRzOx@b7@D?E(U@xH;V4!jCaG7=pL(o%YFqskG6cxmJ&`{guc0`9Mm~G4skl1?Z}spR8@~ z_l@>L7T%XaeZQ?DsP_n{)N!@TA|}C@vVM!k(-Xj;yT1I$Fvk<6r~j)A?J_MNLCJRI z8BfFwIi7eZ)AG?MT+BeBvV3Gg@>xE*3nsFBJZUNweh{o1B%vN475nSJ@-t?-M}3qhPJpOf_rXZ`1#O)i%aFrOcMXu} zyN&xon@N8gC5P7(1hVqco9Repp!8lG>e)9>@$%I>tB0$X$VOK0pd%Fivz>Fu z*yk0omYqs*aizG7dn9^q=+$pVQ>r(*{^izEwF=5qPoe-P9{!lTo}*X2!{6<7 zO3QAeM8{@|?M~}-$3ZqgoIdhLy_Q#MAi0)59+W}A|M7KQB74N2D0e$_(_&H8HRoR% zylK>sL~YLBzBLD;yEb)X753Nf|fPg#c~Vb z-^-qh3ZEkvGtCx_s6THM)-5o-+?@N%$pJMg1(q(Vvj@ zMl=M_d(00pV1GNc0kY>-!J-D*)bqDCO3SCarPB^LUcvA6&T`M+g=+wC?4ek3+K?Qe z!MOtPVGLtx3C#pYYL!4Bs$>2{sq3|h(JOyFhK0Yi(rvbjp-6(U;ss78IvpGsqYAf) zH$vE{_oG*B*XdST#dA#(sO=beEnK9aLkcOun7zN|)}+X8D^_YQ-N1l`C@MDwLj}X` zQZ$B6H;QqGO1G+F;(7+RH{bK0MYbDAT&E+(rn5}7RnX;II;FGd5c%`?h`)oz8zg7> zGz3v>X!UO2{sMoZ?X)8$A|#RCEYc+qG&KBQs(N1AJu*9s*X~Sv#jE13uvX_ZM$~0g zQV@WWMbE*d3>bkur#Dgc`-18*cuOR*XPnMV&GD854k(1# zs@H4)*=Db_h*4DWs=ecz{O3U?o5gT&hFY+Qdw?Jz=l$*O%Cc8FSFF}fR~z^T`Q6ZH zk?3EF3u7_RUfac}cEPA!;P*pTmTHY64pWid6;$t_nn@eGPGd>xwDbNY&hq`ex$dIBNoo@eUr1tY8BQKb7i9)m z(YPHT3XqsJl+Lez_Rbp6d#5NT-zCRy}BQS!Vf^G7fh9!Kf1<(Ki;PLB`m$Ky}vv3ClO{q*?BEFS9);&Ib8c)XGxXJ3rR>*?_~FT>-j z^myVGc>EnbzH|c~U!ez`->%ak@28?^aI;lL6lb~BCAA34T5dHOQXFAA zH{PJ$zY}6pe>1xJ_=x-F-d#`z?NSG71p?(l{w3m2kLs_hn_*E`TxgWwZD{{-_IXS! zx#$aJ;r#%=aw~+WL&K>cg}rc9%&)pCSh@^VMkNS0!<67@P#eGdNp~5^=M;1L!}V{Y zC>yCVs!aKazkx&>`scQ}n@^HG757b(TSWuk=^Y=XYQHJ#^S6W?5u1S*h)Ku|You~-*^BEDExv-i&KjD6p@ zGdEd_Mx=$5SQg;aAWp#ep_R=f~lkpF&Sq^2) z?cpWUOc}xgWp?r*6IWoAcoPxp;&%Un zy@FkQ2s@XZR;zvzrAZzKIAkUoj8Gd`R;$2h~F@?8nH>hQ!VV_B4Ef;R~SwE-8xS2-#AQ$_S`gu3<4C{ zbF)!OstK!c9t}~T0(-b%+Ac0CiNiDSwo$96TDxU+jrf>+93flDw@vu^WIiBfvvlX) z!;GaV&n%k-7M&LtV}zIrS@a3fC_Nv~tzVYikTelz$9C}QfT%Fhirgw50povX;lfwn^>ACm=xX;BuW({A*wSSs7T>SAX7|QFM zx*?9Jn%=g&sy!t_U16xdQH$v2pX0_7u0>iSELDRE>{Ih~K1l&QZ%XurXPzsHkF0nm z4wQN0g)FEHU^oIpKAb97sZ`adx~z&1&92T^tU^Y4NA-1_aaA;jhE6mKi-an9Tz=(b z!J_H-c}AtzD@*jitXC9QoCjuAajj)M#tgAt&jh0^i8TjwP4hlOpH?-7O*6@Lnrk}{ z_Xnmm<}uHhPBLUe%QXqjq7;j@EJ>&p07nyR7W>w$6)JeR_Y@56a+zl#RvSoQkD3?l zLX`%A;mn$y5yZra<};g5(bC~}fXSPTk}N_fItqYKQ70oBffNLOqoDARJBs&EYT;E4 z6!(<}%8m-RWZX?PA!Q}?thPY=^vY$!Z1ahP7d6wlno=79ALe6R*LldY7+wSi=9UPQ z7DDE8GKut)F?{A>G)V$*S<}k(Xz%-bO5*#<#Z8ZAS(14IZ8rpxkp{5M*Vb$no}ZD@ za8j(fI=3??Lj22g3ZYX4s-ssfZp@uDTVwzdPYL)ZghAG9ge)HC82aGu0P9OAcILH= z`$-mHZcLFjxQ5E_z%)@@N3ap@kU^CVqG>uT*NHbJnM>3yH-bD$y*eM^RY#CW#Vlqa z3EXrPhXxA$l$0u-2Z=X{D4VtbyF_ts_M}S@p!I=NvdGs>FOZ52i~d?@xl-W-m|KT2 z7>HvBItbmMHVihe>I~*}*MwxOc;CEF+`3qLVdAr*>6)v)V9*DF zFTjRSIv3*fX`bQ=dcU|1TGjxxTTo;?LJpKgVTY;&O6(k)8}o4&E09P->ZwJ_pfv}8 z?-?`@oNL^1*$3CD$0H^~4^J&Bx=!oua^OO)hNGgOotq^dKd|VlX0<3Kz%DWo&lCx4 zXs?(i-C({w|J0&VE_TJbGCLQxin;y~kTLg$*;RDQsNSc^nO+IhR*tPWV9Kn}W=VS{ zj+AK;L7%0t0Bp~mD)A-_;ztSCr!jp=-7R9@r2y=Auh9s9tyc`-$L8Cw;{W3DU+KgbkN?)Q^>}`AxVW(J zl_&I*4~YBD$zA;O+z2)E0u!Gz( CKIG;A literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/file_connection/hdfs/slots.doctree b/mddocs/doctrees/connection/file_connection/hdfs/slots.doctree new file mode 100644 index 0000000000000000000000000000000000000000..1f91369cfaafd37a105e3d0b34c67ea9139b64c2 GIT binary patch literal 65920 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%0AFf}97v74_w#hsZ^pQ!xzDm+AqC*O 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/file_connection/index.doctree b/mddocs/doctrees/connection/file_connection/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..0a8d38cc477bec7e0cd0298a2e29e414c8c2c333 GIT binary patch literal 4404 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/file_connection/samba.doctree b/mddocs/doctrees/connection/file_connection/samba.doctree new file mode 100644 index 0000000000000000000000000000000000000000..8f26f2bcb8dac0244397467553f1ef116c60a364 GIT binary patch literal 175360 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 z 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 z

~a)~MH8Kx%2Ca(kz}U>I_3Bncyc_ZE1epT15vs_<<^t3Ez5Rv5npe+5z&0nH7# zPIB!8G*hd$O2&`(v{y8N^|k5Ak=rNgjmgeE?Ul_|qcR0|I806gU=tcVPChABOXA50 z#w13BB0^^X9|53diU)AS@>IRi8mX3UD*-zXZTFW8)%fe8_8M?sz(u$lWxAB2s|HKSPSv=1%SI_<+7rSW>BI8rQ4f#v|3ieT(w z7+Vw2P2fnSs1%~SxV^G4-Kvl1N9{vlShQCZrzfXIVwD8OiU$?Y#zw7BMbWJiPlU3g z^!1~uW@)-uAGv)J6>2q%yn3@VVnPZnfiZhqrCE_GyK1CZX=3IpUjHGfT%v{w47=sf znI;U5LJP(nG`fOb6Z5QQXK$x{Bsxw&(kwOb9gqOFRs@kRYZUg!U$0uwUW4NeG^e%? z6tP;Fgv{!zS8r^uoGMLeBZ8e-j!(u4KsyeN_R-~5YpS_@co-fx^HVdea=n(XH}=7Z zIs)Gm1i+gkPzC~do-Gi^Qh2`Q)YH!dey(gxj62Qa6W#o2s(Bc^1ytEvN{xJ_)S3`D zz#t5lTa#5l)(08FFp7$B(Ct-A+sA`WPL9NbGq!>w%_-1&$QfVVoT;@6w~v%7`^r`L zAM|%&wT`O49AXJD&~BZEQQZVa9WK7huOlKd0*`^U$B&l_Ko1CBjyK@4U?D)?GCc(- zRswacN(=Ze&A`2&J@xTQZ31+F?7~_!6kuhY*D8~!yLd*77>o;rNdlFb90N1nY|T_l z&2p)PsqaUO!&|qElqL(6>PVqjM7t}fUV|;8#G9q+gtTe*wht@SZtLux9&0a^)&zzx zXkw)Xr!m3XBXYtkI=2Rh0?lfHxrH{0r3tj=<#pxd&GuUBC-f-z8(6IADPf_+HSX|k z6R}Lhzb{5pTNKS)1CHyGLbJUDf0WM#q)WOcdK)_0x8R?D#oY04!=LGPAHLX{nG%@f ztM`aE?-g&|GhIFbd<}T4JpvRw?f&xJ0ycUKTPdJ3pTIxg$3H*9KUc1RKiA-&+-mr9 zD*pNA5%A|b_~&Iu!=G2-pSzBOKQG2Vdrp8qH{hQioC1G-jDM;d;m@u3=i<%q=W_gW ze;)q40{?vD4EXbH{DX(|M|hIIs1J*XzU&(Glp@H86DAzer4HQt4inXLWqa*qF(1UZTvn-# XSEq|;2lk=8=`W)HB2zEZE%N^tDk>ao literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/file_connection/sftp.doctree b/mddocs/doctrees/connection/file_connection/sftp.doctree new file mode 100644 index 0000000000000000000000000000000000000000..e538215b491ece9388e7f8658d234bd71fc60222 GIT binary patch literal 210343 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/file_connection/webdav.doctree b/mddocs/doctrees/connection/file_connection/webdav.doctree new file mode 100644 index 0000000000000000000000000000000000000000..68a6353502f8c893cc3c10030c60c235855cc746 GIT binary patch literal 195091 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/file_df_connection/base.doctree b/mddocs/doctrees/connection/file_df_connection/base.doctree new file mode 100644 index 0000000000000000000000000000000000000000..3eaa7feac776911574429576816a643a6db20a7f GIT binary patch literal 32869 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/file_df_connection/index.doctree b/mddocs/doctrees/connection/file_df_connection/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..8c17a0c531bf7283ad07089f0b2841e8b4d3194f GIT binary patch literal 4576 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 YEi~0ug`{(Z1ZAQ^Rz)gcv5qYMALRXAKmY&$ literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/file_df_connection/spark_hdfs/index.doctree b/mddocs/doctrees/connection/file_df_connection/spark_hdfs/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..534982d22196cac3cb704f91cf5738f1e448de31 GIT binary patch literal 4435 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 BCPV-L literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/file_df_connection/spark_hdfs/slots.doctree b/mddocs/doctrees/connection/file_df_connection/spark_hdfs/slots.doctree new file mode 100644 index 0000000000000000000000000000000000000000..1514925d5790081b084225e17bc1ffe8500d530f GIT binary patch literal 65105 zcmeHw3zQsJb)Y59U!&0<+1PSQ@~<_PW+eFop7lqzY=hB3HW)i*jZRNZcgL=rf@>s5JIxaZaxHj0`@LocKMjYZU|&|bKt~$91~c~VHb9jEQ^zv1Y_8H z->2St)m5*mTHTgJ;&VLeu702UfA`&c->dtE-*e+n&mjMW>;0zfc*keVYSn90Ex!|v zRU4Id-SUFYBb{Au?YyN^3ddXKF~8AnRjf{U2DGTyPOaLqyv`B$aSGc9PR*ZT18no# z3x41P@UY>PtJb39IruRe&mIonJ2PY4}pK%(q3ubl6n!RBE-aV`zd?Y>hEY#Z8gaQA}1!il>3d9f%&kAZYEz7J1 zwgqsKfh=!!@65hSW-gm0-De%IY8{`kgL-YEV~-x`*yDh(wP*o?fG{Tl0KHBDn717! zaLeu9cJeVHz=acL5VV|yb^v@uKCW@9KJY9Yt<+54hllWa*fZ<&(^$3L6kit1$}&)a zv{_@;f{wip3p;Ru(q$tzoa zP;RywwZ_ubop7k;cvk1o>TslK0$Tu>P3;BKJ_nGp&jpSd@c&Nu|9trWLO^N+ASbZC zg2P%+5;|>r#@=A>wDl3 zW`lZjme61p)nK;L@I0%6S~% ztaPauKVGlR5j-=S4REhZ^5VZ}$YD=l!B^(2>^3qAH0oO?8#bER-CNbDXUK7l!0hbR!)A>S>T zsrDb*A3(-_P%y{-Bd`e+`Ei1L9P_ETa-kn^B#b{6uzp$x>lngH0Ug8X(qa5$H;j@X z9y4)egLNCmgY^?fZ7jH8fjbJq;YhtvZKI{Sv=;_~4?wlh9tp=5+qD`2*M2gbCAzAIN^BJuKFWaTwNIYMD)27QpKW z8|ZpLjHMCSW?)nr?ON4Xu)sxZ)-3c*E%>?uKRT`I9t3d3Xx7?Gj_1#CY3#>Pg5BRq zXSXbRNRe;D+Iu?K0`ykC8F6g=QLE*HI{=d8EIL+I=E}Bft5qO4taq%Dv3KT*nZ1nE z&(ci-_{aKo;0HM#@#7?mqwjJgeMrHY7t>i&vVRy3!_EfKQ4|K*^a7y&Bb{3jP6jR0 z^J`{cE`V-5NO`;8{yRFK{r|wEY}O7i6s$BsDJL1#C?Me|`wD7D$Ve8FA4-D`A!Dew zRcX|fofr`L>>WD{@QW)JIPgRXymr000*i&QV+WUgZ5v8Cd%yb&tIjyX*Lcy)j=I~S z9hVi5?I%w0bq=dU^M-41KX!2%yYa;B~QuzCH^41R$U`gMhEAZ5Pc3% zYV9)iT#f%4_ZWu(AtIk8`|Z(?mk8I95}=6hI6}J!*UvqOl1g?z44Yq#W=eJz-9xb+ zs=sLeR+6_!rqMZ!+yMda(7h&_;B;4y!3#WU}4)h$u;WyOR9{`-_Ub z<^%u{CVwKs9^!Y=#Wqwo*$tF{m%asFo8s|4+?z1S)iLx4TeoTM1< z%9y&@J@q@4sWTPnx3x2*C=$iXy&L*Q!nFp>1?SIX(}IYRw^Tk3kq?PG%yb_C#~2z5 z3O}v~LXHq&gsKot{Sa zrYtO?w>wAJfctU#OZJ!5_30};?!4`<*-zSE4=>2&S#|8E-K&7iT~UcJZN+^@$9^Wf zDA(W+>%$EWZ092b05rK0NkcdFf5oMk@8QM1?~23R<2KkTXkdiEXkwB9_l-frh=732 zya%?UT?rbkXtRC-BM1?^F}DcPt=wt7m4V&6dypXvHcT+%s+$%KlGidI*Wz6i7}R(h zrnds&5^rg@$|J`&YM^b%B(oAk47o-b z?42NmKDh9coa5yQLb%IXBq0vrj)5vEMgxTqf)MT!bd9?S@-m(hhj1&=l$>Crx?$Y2 z0DxF5!aWqe<<6b_M{3Ee@AG*>V@-xX?f{|X1$?>Fd*I!U7ASVTH z$EjKhHIdlDHW9*}Ps%#MrB;0?ByM62%#&-Y0l;C6Tb>xOt6(472g8a%n8Uw0OAd8LJ zB(n($0S+p-r>4Po_q^+F!wC%ECYdMZLe0kfEV&<(!HMtPA<-#65=?u2A*QKTdn3ruVdxJ`8-agf@*_E{XQokvYj}DC0coWO zq_Vbe0x;(Zm=Z{b`p61`MF~iJ-H8t>#SGpYL)iVM%;7OEs+@-L?fOcrd-|$`)`2Zx zI43Ub;i?%8BwG6?lYUD?dTp1Op+$yKxzhJi4J}IJAp*W0Q`OL7uUF;h#SAT)t73+B zJxm}BtsbEVfWgq5Z~CH)4fuDh_CgEdl9FpZcEfQKbLEj^EglG^`2@h5dx>;Ej;-bS z`GRSXHGCcOtI7+tMrB!s<-aVp8uhLU7R=>C@uvt3Oi0b>29~wJFB207q6oqaslrN0 zKg}#a&DX>M(g(Z>!{ea|B^j8Qq?#kk1he&QKQL2k9J5-i5DFy!=K>ch0l>>I084cl zVA#_&u;}-}aLRU;>>B(Zlp!#O)ubf4JXSNkr8bo9Ldj(l;tNpZi}vF4C@PU_v_p~_ zSPjmFJttoc`k2jrCIc59Jg3=gNsYJ|@6J$N`x}4<4~TK?Q&qOaHUcz@t8N6s5$>S> z5FialVW&XS4eoRJm6Qp|-@V2jTD%C(y4TT_0#6dxb-dB-6AYeO+XGFs_Um54dd%IOLiY1Qm(B?Ho1Q(n^8IZg0fmt!q{mR;JH(Z_WzgAK3h;nhE+#Hi*|f%HAP+&c50{4qmZkbVy}LJd|KsR3=sn*f`0MabZ5U8>=a90Wx=2Q>1!eq@1FZZ+NMmg8NzE(M~aqg@}o!o?7uw8jjiqP{kT4ri3ca6lqi*QNW6w018nrT)e@jKXQmLrcqDhW1wdKx7&sVL$w zi|SCrRDufvc1=u3MFsjY3ZoS5f~(Mv)>I^<(MoC_Z8{mABv{yXRLOg7P3+EEep2F2 zIL2G1ZC;-&L~T|W@kt^FYGDLElVk8=jnouIFxkTF@e^cB_bD0rxr*xMtZX{J z2x(oWzy@@}1gKougwJ|a;B`RobI6!|P~dA3kVFMO7z0riqaq4?4FOi|pHqQf3tb1L zz(~h`l>wThz({hTz;MHdUV+a_l~=CDnifKtZJf1GK@wB2DZtLkft{+%*o(?m5oJb7 zQf1cnVtI1O1U#0*+^{@3dL6nP0J4e|B(VvwAnCWD7t#;dmFYD_1xP&b#|xAMJAR`G zJGj5pQ6#6!UwTNY^TGRwqJ3O7wvodeLBX;lvPHmt{VYt9E2y%7&0D_G3!pKB9}kD6 z7AQSlh>5H~iOCd}pZqaVs|58gNelS7ddhGkkkw3x-sg%yO)6#>$XfyACkevQr3FpN1S}j^V`s~&?x`NLchVnm6@ms>@QQYM2F}y^$DF_}?VO?IC{Su% z-?>XP#t!Ehi_USQ?)Z2CmO9HlpRX>;XB>M_K(Z ztuE&wHE6UTNaZGuy7ikyvqex^U(z1e>l|(u)4X}U3;}DsSypCB>f~D zkheyJND8j&E3O0)-TQEugo>-xyudi^>Q8RM*li8TLn;*o>l39nt*pQbBv1X{XpvRI zJ#vILeq4W4{xV`T* zUB|IDV;@6Ssm*vN56)*^Ho^~c!og2+hp&g1Uf>m?a03Qhhcq+60$%A=9$v#Z3Kt8) z4d!rBrg6-v)$kN)(`vyfnYvYtF3)(^fM80l9m#}Bh|9sE;^JP1J!lPTJ}P00ziRyRzct2h`3MwtSDE7V;_(wBtDJrNQ~KCZkTV1$(wkgQ;* zS&lV2YDxjgLoqLugoh3)marn-uHhRcP)Uy;*{d3;Si7MBqhsCvk4}F1V^D4 z%}nb-V+j$ykn%`k4+)WfWHa(1^z*WWh?tk#>+PW6azZ5h^kA|f-C>t_BBZ+oi@^mg z5`$CDX5SqpId;>02i%8U-3dpMX(;FPMuJ*7kgAdK0R`uoXGE?V#th=X z;`49pSA4z;;$B1aHzaJyy9Q6V#6fl5*b+Uk>&9;aSt%B=e_gCc%x)(~>~R{M637wz z8=?uqkDm?^`!1$-2ko~e>H;_RDZdI|aWSGu(zZq)x);=REgU8xbl-gjwf|>S@6n>5 zJ)?RYx`J_6qvy#dr61DIqC2BXXUPr^zfU_wN^F&(Z~|vkAJtErFXGy_lhPj`EKxN- z2M^UHP2P_4C6t|%4id(XH}5*5nk?!4$|!whAPnd=qx5A$pqNf1XOzCL1zekNV6!Bq z1Y#Mb=e4uQyi<-uWF*{r%_t?3Cpn`uF`+JiF`?F(3_44;$v9IxMT*HtL?Jn&bfI?q z930L@;Zp4sdK41HC!SHdPCr3D`_|Xf!ju8oCW~d160MKu?H&LH8Bj*){5+l}CY2U| z$lXb$6!(MVmEs4H6a5$|wZvTv*q2?$EP_6@beSf##On!tjG;o7!{W)NC`-{}Z{b-j z=N3Djd^&wyQp`Wu3|}Sx^zOsTLC8}m3-!oAAv@t*)LliT$6di5G#4eY;*`!s{hAg@ zZ7wQ=uDT>r+L`7DqA5AS#&p9J%0)2_U@i(d02_<$TvRDL8I}7?^G73GlJ;)0V-3z9C-v%Nl9IidMpvkmJdqVsKld*35S$Vr(=&_E<{mw?D4-7UaQ9*@tGX^5UX`U z&VXO&&g2KrnSPOs*@t6~e~W-59((*j3`EsCEpqJfuL-bfe_8r8=a&Box)we5h^~hA z+_FT+?`437iown;BgutgOn_o^C-N~Qt{L!Jnlb_=Mr8u-|4m%stBKcl^4|n{^_!H0Xzo`%-S$?0% z6`ta{gQ!-536PII&J?2!56TwpCaguh2v#H&%~0A5D7geB$%LJx%>LamBnoEse>v7& zYccszN1U@evmb{=gBo+5arSDG#A13Crw@dHLeG16FrAvwsJ^?0Im>{jM}l zQhvXyQ9=TbrTf{Do16z6+0nwgck-aeKKV!@)ClnB`{n0vk*|A~k0t&KsUX>$K`Lso zK|}r==i&*kY4hj)2qXq+KpYn2aU}KYCh}Bd8qgE=Jg#AoASnP5e!`wtFI4Bn7kd zg$ROWJR?IPSDx`5`Uy@#o{>VI?wj*%&I zG+WDbe0VLQXIRH8RufOTQ9k2+P-7ec=?KGTV|-PPmxpK5m#-$GO4dvzF5>A%IMRo#{%f&DMQNV@p4Cb0ii=vp+ekJ_yS_7f4sI52y~>XV2mO?e`%Nr2~U zjhN_oqr4z+tt3#~e~I3fAt?RVIDg(d3wbCig0z`iRFkvG#f4Qs9v4kv&baC2Fti@@fNBG|}w36Cz` zMo>BV368}W63RlJeV3yb>#nZfarI+&IpUn%363}@u4GQ{^S-MYGF#&{%?lFN{2b*+YWk zQ8ptVLO(A{aEN)i{p~us__+aQH`EPPVs1li5{3R~2ID-#Z`8rzHkX`=VOuRrPCZ12 z8M$HkIlzS4JCLvc2;3e>dVt9=9%AIeF}U==f6c&xhZ$IUfCD7Fh3O2VkitJc9kK$P zjos;hNm<0X{Z=0e*Y8X%UG)h7K{9t64wH}ocqvXI&S+84o?@GZu3(|_oJ71*Ka1{I z5uGJl#$ThIA|+PNP&k2;h;PzQn=gXix08r>5SFOwor8z!x+J^nd>v#b5id*_Ki<6S zBx16pt9hxo^;F`H;%%AGObUqIKM3066g}PB_%=eDY(ag$9_nOcOb3tYT5?|MgWB=6 zd8xc8JfWQ;Mb{V#x$;tfqMsn2R`vD#8-+ewQCMCoQRYQ&_c>TF2b7mOTjRE1Lh8Ff z+wO#vy2O!8)OO84QVNQ%Uf{R?J#z!}DX5Dv1$EOc2MzQh4X<5auv+9yn`!t>tKuxe zmWt&@qNG%zK4O9X+uU+QqoK2>pZkLOssF>!1iFj-)B%#-Sy;hz5(Ej`l>mwU;3NL^LHQ zSpVL8$vA-Ty+jUx%hw&m@8=n@%OYF~#PauJr&*3X0;$CE|9PJgdwn5_vNK|bp%aE@ z;y81dx_=Om{h$bBvWDU@oxdSqNzUd;I+yig42gni%i#?L ztQ0SX!8yCrmT^e5c1R{CzTl0LQ`?27EtzZ}6R2$Hd#MHwCF<)0e7yls4IcJ-Dn~D7 z@X%}&GkBPT6$Vd_5KCL~tpGP@J)|wKEyR45wiNSnSJMFXl+R^_-ydAsl83F6@|FBI zU1p-#>8n@n-M<%K9e1$SXjiS4DW^E2JO>nc{PRJWT2GMHn?ZI4k_2Vyy#wapq6YFc zpfgft1{OT{%aWiRAmJ0tWav3bg7TmcS5;NNvy-650ZN(#UBOQz2}(Ylq$DW3ULv}{ zwQN=b=cqNPRA^sbv(^o6uL;qcVgbSKk+rY6Txc|EFHws0*FjR`?MIHNe5^-oflQ9d z<1{)IkW-`&M-!a-Z*1viY`i0(&ln1rP1HoWd( z&VY#LZXQJ@-)L4|y~=ewYRi6A5`jbbD|e;o7lFm+wOD9#Zl8m$VAS*EO`p=wqRX4o zS+WKHS?v@lxg3UqLM{@nz!*VF?hZI2#>%Erpk8r$~`P zhJr%0Vp4d6e%hB<3a=$BDa^xyQmAj||Gg4jwc5$qD>-Ysc3n@bjYZyV@yI)QzE#D}n!E{RH_GtFH-s3cX(V zG5E<6_=#dKdbQ9#l%{bKeSfq3>kt zv_7SO%R$?!EE~sc3yvYS;0PnXpM@T1WNCe?$~`gzUeQOlPDbe}e3iW)m|k^ZWtRat zYWN=I{rf+#SUkU$TSh@HFl2YSI__8^@DxK9mi!N`KI4pCZ|%ILQ?iF?X_a!{5vUU)09_FbnG zc#+VYqXSEllIa+m72i7Ct~I1&GDWQ1OK~_Fr%Ac$UKpUuV^gaVR-~3tTj1(sszH9R>L>l?j@liRxKgO4ftd&+l4NStpO{nJ~Sxbpb7iRDj;W!_mp5@jAiA$}Cb|QDR(hF_S&*)ON27cg6 zRp{lky2?_1q6@iXSN0a2KT`ct-kuVx?nRnN6*Y=h;|X2;VsU@0d%7x0;)8P*SS+x@ z>n_s8y#W4+7&Ui-M3-oOMbAS~;;Y7UoibX2vHSz18a0-~#P^dJ@&Wu!Vu(u7BF6Fe z2+h>~vO;T&yn-=9I6ZX1KW zs1Rx=irE+xU20>}dxay^XYPcnraL(Ci|A@c#tqHXkBON|=Op%Gm1~lNMg8>jiLnL)gq5g49YE+tPYp9>kkeRgiY&;iD$=9p(x>D>y@7Z7+ zz}qX31F$O_Yz_7H&8pI=Z zth_R(B;NPF3%1@Y`C$Adk*ouj0LE5d!rU}->NOM$W_- z_Lt>Uvj|q8>j^J{@oH2;HRjY# z$g%$0BBXVhI{!97Q$0L^&tx9yNyln|nHb{j{qLisdrCG&$?48;f;zTnp9yBlX2Jao z0+OiCpN)a2s$&s#{uBXL?JtX=MxFl*x(-U6k&d6p08LV7B)L%MEm`W^r^6H9#aVk% z=9d9>&*s2RRc7o(WlJxSstbygq{^)CrMjSK0xr%0A*&@uk0fIQ%CA6Tih}QW*v9Aw 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/file_df_connection/spark_s3/prerequisites.doctree b/mddocs/doctrees/connection/file_df_connection/spark_s3/prerequisites.doctree new file mode 100644 index 0000000000000000000000000000000000000000..6580854bd07d8fd62db499db739f0357aeb11e69 GIT binary patch literal 13870 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 dyeG*ba8ZU$t?>Vpw8%Q<{{hgEk*WXy literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/connection/file_df_connection/spark_s3/troubleshooting.doctree b/mddocs/doctrees/connection/file_df_connection/spark_s3/troubleshooting.doctree new file mode 100644 index 0000000000000000000000000000000000000000..10109b746bbebba14e708eb742d37b66dca6e59d GIT binary patch literal 73472 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/contributing.doctree b/mddocs/doctrees/contributing.doctree new file mode 100644 index 0000000000000000000000000000000000000000..d0484099aae2270a8e71dfba4e22c78d7e7bd14e GIT binary patch literal 57687 zcmeHw3zQsJd8TA(Mw*s1vL3d<&+^EYrLnqO4?pl=3+rWBku}oMj9=KMyQaHls#^VW zSM`i0##syt*6h}h7BEc?f%t?c;J!)qVW;f8GE7_rL#r%fP!I`fqFaAAhS`x1H+6j9DyJ zYema#`5TM1LbGC3z1F*0d!K7P+uGv~HOx7;)@&54mcIri3bs=&HmqvvDSRBJ@}5(6 zGeHB_xXmfob3DAPRdYpa+NnD9Q?+Lg`2RAKG2ZMp>-Ad0%h|Qs1^3N{Q!!_(u`$E; zyt+G<%{rBtjB7hqx#(t`T6W4T&RE%75AII~_3)Exrl-nHD`nuHG0$wwSYFsft!jDY zOv5sZo^1h6Zpf-;_h$|q%G{ad>RG2+v@T|BuToC6>>&);nzk@N3|JxoKyQfv zEXxis+G3T4xE@e;oWs%Ay}+E{GX!#}3X z!UbS~%d9iYUd!I;uWL4(mVf7x2-NaZ1+!Xoil%_ZU;BoZJ?b|74W2ne-L~wj{XxlP zo9}J7UasD#m1{GHTmJg8Q?**BukZ)!CddL{wl}9t`+5w@z7Z5N@ZSjjy9NL4#-IiP zIm3FlgtbpjXf^GOeWiWS-rTXDou=%H%BAeAz281?`U;d^=MUwS0NLxBQ{bizu2qN$ zfIUc@wg(>vASvYtn{7WbtuITCB1;ZzIdW|4*j{ZMf{kA6-yXHpG|w1A5e z6%c%uUP}y`gtmQpg=x$5n&_rsJ>9fis@1S&9cvD?QdMhC6m?xx!>3v!gn*hYK*JfH zZF&Y+(U`Ibrdk8NgC~pl0}w+`qyR+;6w3&#kNf|>4>M$(Cwpg{H-hOO2Y;-W3#QhhG>EwSNh!z$8r_={H z;RVIj?>w$>3VJqNqN;1uswXBNf(p|x&Pq6KxWoxTnex1G{3z+fD)d{e0Tsz#YgK%h zcFLBUF);avk7>7V6`X0O0NJQpu#u_-<|H9dP@c6qX=k(CM$3-{w0s#t8WQj0{S$91 z=<(5BiAROoiTAUos;&of)Tm7x1=yuL2?o}fG3R);VH)L{N1sE{5r_t{>TH%8ZXFWH zH8ERN#JuHJOy~*Bcfzs@L^7x;-tObc5C#Oz(J-|eDC8CP8L-O0A7Xy2K&IJP&jhxx zl2HF+uNcs!{(c2P9P;HC`sYisQ-7sbzNEqdUs?$Uw3v2U{@PJCg%>oH#1n3FDy;2q zDr=LwDXsDNYt8CB3LpZjQ_b56#>1{tw1PCthHH7vI!`R+S^9y6F@y@E=*AJKe0OQTA{? zC)E5NjAA(2OG^!;v~lg4HTqcWZED&&v!cK#glZ$y;J7jdN5WEZJ+D3eO?pG&`f*uE zlZ!}33tMx8!a<>1YHwI3VFV`RN;oA@MqKG`$-1WV}BaaDs=!*vmhd@8DSma9&P5z=OwoDV%Jw?b?@nsOUoS!g8rB1$Pl?1dO0 zeQLLof%^V^{Z9uP%9Ls&q%@~WyYN4y>hE8QO4AxY9MfrVTOJ6t0%?^}2K|{4S6}HF zf+SF3r33hbRF?n`b5=xk#VOgF>bfbynPDA&ix5k26AdDSRDO_ZC*m|D6G+!!q%smT z*zmD3T$tpZ!os{>lN=I%9Wu!WfB}6f?#?7qhrOt3unrrENZ+kzu?B>GYE&F7`FPFH zXyOGH`s`=GlA`D;&q)?$AH}=7xn;n3EK|~ z{;=)L*k%0Z<*>U!j(mUUVx>Ih)=k=v*@#6U;Gd^akD#Hsa zuU0F&@vl$v&RV)5{eeU`Hw(A9mGR(8==^!wt0#Q5i_>nx=L=Q?#!)S)ha*ce)O}=`}0mY#qM#>>Uvah!ru?>1r(v zhp61dcvU1Vp%Gpc$*6`DGXAPaUQUrz*3}O%X)e{eD*YxZm%dDo-@>D_DRm$+No%Im zHl9h-x{(w5GsH(D`Z2+)L6%E`L3PcEs)_A4+7?K|k%#?NgCkm`vD)=}Xrk=AqTf}* zHdTX-t8)?K>b0i=pJyB-WlANK2%#ELee`2Eod-=MbTPxpAJG6(_E0+bm5yMom!gW% z_5;#YWwgo2ptQ~HYbRl8w2|JxqDrAA;1AIgfbfxX~Q%YNePk*vCz8N#Sw63IW3d}R(5ILzkng3q7Jl0 zmn@#R-O<*)zMrzl*2Lh0*c|Yb6G8qN@2kfJ|lVc48x^ zJTIA~!o$nJ8uvSY41s%(>Z(`Z zTB-&EXGK)$jYr81EP^6=#|x5FB|k5tP09FT#X|T#qI&BEKIx8~0TLygaizL5iYVc@ zmW1s5QGkOsKMiPBRdG5Iz%|8r+XQWN8MLiH;b|r!3lT)Yd+&OngTipn`$^DMcq;b}t&~S^NX?Bl0^97g@SYHqw2k#veE}HTJ*D}bV5BdfXhOn8?75EH-zG#%!$&EgkN0_FXR| z#oL!eig!gu5GBPeF;YbB!(wd?6Y8wgpS=}UqPYNbC&Q9zdgNDvP-xP5uFU7lnyA1A_x0`805GQe}Zm5yqeRMqvr!!1` zOL63g5GPXyJ4CQ<3Z{rNwi*qb4M4E^@$m^X<3h5iAop?L;N6-1`1gQ8+l0)rdo*dS zTviMO-H-dnSE5@jcmJ*%_a_J z#-cnvsBoP~o6@fk1Bk$o_iH+cbs=I(1@vGI#FCj-Q%BDq zIsdqPuWi8?vu4AgeH3?JiqA21Y3;r&b(i&OSpn2tcVx@XR6d_crB2b2ih0-&F4Qv) zCgRu>XT~dh*D<3iJ6`r{6d+ar@5}_**l&0R(E1POcZTye|Jp7x zm(NQaUDZ*-wGM9X8A+rsgV|4wV1PA+)?6~YJ37d)gmi06+h~m*S{95q=o`mvT1)FD z8TM=!Tz3;KIj-9~QlN7!4VV+l8gnFUH$JY!`OTJ&$B$xiTO&9%Jk0jUs;ca9Kht0P z8or*2;VUXAOJqDN)@XMPq6r5|xl#0a;i z6Ddr)O8wAN*>IKod_OFyg!$*e0B;j~`A_8R zADb7+ zYa(gO^wxv0ZJw02&1y)hvU`X+@iQ;EaDlTLeaDZ)+%j7s#g$f?1;t%GEUx8(EM#37*_Qtz~e{&MM%_QoQ=0Fr;1N0x{L#UfO`jYaNV z_RJT$8B2o$Az!?@gy3?(UPXq`-~B%vC(X%S7Bs1|m3_fg+Z5 z9QxkK_Gyq|J-3(GwIUmp7i8<+zWvAcpO7|?2?t0UhzY-Y9nw#lk@yKzksfsnRZY8o z&yhTVtH0;+p;dEFKQvJ)^0N_$qb|~3!g&ICdnu}1ktYG^O1UCCo;Le;kwIx>=sl1p z7+3EVLP9*gBtm>X0&|oQy@v7xK=)c)5oBhy3G&O4!D$4!>mgbTby_q8adF{N24S$Z zy4CRDqgDIQORXLfYko<@`j3(EM2WR6I1!h@s4~2~k@~g67e#{SSDOUC76Dl!fgWiU zFp&N#MAg4q5~{uw={Jh1;b<6!#8dpWLefW98%e(r0aioO3WPlcxLtvkLXv!LNhJAl zWRy{o+!H0q5+Y-k(SjoMmsXq5za2q=M(DmnD+03iT`wf!A1;ZAzZV%nl!(1WSp<;x zR#=hdTdPf)-;WF|G`;+*DNq{G5#56_WQlnE#A0C?hm^9lwQ~vw1LT`6t`L6z(ykYH+y6A4QDR zn>#=fq-UGpRLyGM?V)4{<+ZBMexm>XM1wXS{l5*l5r8i@h5R!Xj@edJl7tRl6zA>% z9II+}{?`b|no`+6X(NLLtQU%_M3&0h9O78u6{Rs`bDVTXO9dTzL&yEAI@Z)yVq-Dn zMtuXmrfMV0_Z?9&An_1FPGlL~af2OwJpwGgGBpoQR2# zNuFrOGG$ef@u?t=U?b)WnT;tTW6nXWF!lyWGBd_GN~+9>kI`O`KAB5#W?ahsnnGFE zLLS#Sd!8HMkPgQaxgiPH*puL>^f@HB$r#5Sx6nikJBO2@Y12)4$ofi7L_}*Kmnel@ zAg3IhjFn0gx%-e^6hR`CQd}mjVys(~=1S~3*yFDvqyQW-LPDN|aMS}*Y4H^WlwB|t&0RP{cRCo;jatPxcixz)73Z;< zsUQuFX9;1XEUPyyG8)Y|{DT;qXxqi}Ke9n9aqEC}4fa2c!QS1ll^eo4IyETvfAezx zceRMA_J9fH@jc+eWnd|UdT>M39}ry(V%t>%MasyKdZ#rs9BZh%WDqHX zh(_uiI2`w{z%WC~#DVIKGV7w02`l*lqeT>5$cLPw^M~LuPSHu*dB}DdUk&|0BUa>|hEPjbls z_ped)r~IKL z>=UrA^tBjuG*9jB-Ut^{xf&dyLn89BEr_N_C~s4@iV)W(Fh#MUk8o z)_XINzaao{XOxRpBV(LWOG&b&kb(pDS<1RdiTK#{N~^&+@@AOzdU>97x@SZ2!&qW3 zL;xzP(GmyuoWKuKOj8 zY@{#2@Q@!9h8YgsV&%)jc1cd>aWoEKma%kVuZJjy_$e}*8}m#*>Pmoss!iONf@LxJ zL3kXPsVWvcB;nL^|0H}ad=b_Xajyx}n`6SH$!)ez5=o)Gjx4BbXUb|l|KnKCi;Hn$ z!$PpJ^{6wFl3^NV+~(S5#Mvw+uH0W*4J1u1!!+mlB%HDQM6fzoP_2$4Eg*z1YaN8^ zRc_ZUkT{R_roJ|HQ(ykqM@Jt<>S-2m6AmaywQG%zvd5HOvbKVTU?nz<%j;;Og}a2# zpj!J=9FUCt zs~-ZsK8Z=>pswxCNLrU)>uZRAK&3UpM?wbU(Dx?K+q=p0W5dvxk7Ms5xaoq=QbJO3 z7s&))VS`H_i*);?;m*TixX3)HGNY3)^5u?uoGPDnhoQ|W;P>uJ;cvblgTz_6nQ3;T z09rI?H7KFIIFOMFZt9R5D75V&G>DMveeDK9KA%n-Q}d}Y0NWVL3kMS0B6JcyPiIh} zoFLyYQH(vv-CrhmRKC4niZv0dD=Zpr)>f&Xc_rdSEwR0<8b}IyN==cFTM| z*jd6ssuXX?(}}8lzJh%$$S4wGWW;VK8U5J&hw)CLOM4}5b9&mjh(!zE;)7de#qJ6f ztkC^C7#ZRUu(ps^hBzL@C&FprL_r54(^twRmoQEE!NB$uEt&d`$82KtMd!c}V(HY1 zdtt74b2vK2`hy0;$KMID0ZhzS~9S*2rkAk)Fhd zdS;QoVqZ#YD;b~-0ryB?)?qHuD&Mzk*laCswRDNGj91FU^{$s+V2RM)-A zmwY&LZvk6MGuZV#94IvCMKXByrEn7nt1jqhQH1VxMfir;nk1ECEY_9A5Eo+B9FUbID-Mbm8ryv-HpE1T zrK|mmJUGbGCLO8}Z}h!?yp0mxewCX}ghJtsD|E!$P5Hc2fq!(CBghzeu(QH9aSVQo z0B*tzVjhpnQ!eRf<*sv2vA#wR_9x!luC2?W|$gpK9)IUORCxre5x1R`#LMT`0 zh|t}EaX|DIOGX(&1uH=dfQ(7@=An*wUBuMbMzypvhl#*TJPxB0tHPTWgE2%a)zLfZ zO}_ULT2tIzB4i4!T%jXcM}bzE6MCN5^tk$qe7TsxiZZG!&awvx_I8#dJaRDX^@-qySf&d7V>wM&&T?OgU@0tfg^pN0DGkao6>G*BXvsek z1On3CgJMB->=YJU@P=|^4Tk_r3bfrE{=mw#%QTA3eT&+_PsWg%h$Bh5$0X?vFAozM z3sIB(6E{P|o$Q0STp=WGcR{uP*k7CGot5CEt(aXq?iAjK_5Kk=g@vJXobJpvk(M~R z0>(leR;joV644k~`opPCyWZAC6Y))0zX~^9pc-k33=V75icPGEP2KjPRW(v@TJVaK zfePCc%jj}36_`eF)mO2z=3tRupbM|PxtdyjyV99}*hnnWts?I?V!scIYpmXbJv-uP zKDmGez6IYPI*A6UtM+&R5%rxC@B^p<_7UpX9}IT`kgl48w2?Mb3WRXvV^`=^L^h&0 z?i-PzwtOxoP>F&qPgrV)tb(#KPBz7`$%rr%BfVUC*fq>>9tLix| zqE>~dEUS%htUuY;SScj+1yzGXS?;D#mLY#A*lo8O+Wvw}pMwmf1(O|B+DCtsJmp>X zfwPFMx+8-EnFGASXQO7kuPWgfKdK<^C6?tQRGO1lp1Fre`eFPX4|P3rgoGbz1qOrg zy9Pe`ywRw^RSC+86IJAVv%QGC8As5Rs{kug3n?ENnx|$wPe1$ z8dls-whpe!mfwC$ENxPSm%r+03TFi$@q>rNF1UYHkbgGK8zFJOB11ZUeL3nGp*! zo!$DX6Y|?v3F@AWXN}(u6Tf{0wd4GD`~l}828@$SCN^^UcK3Y#QETe>kw@T429E{# zc6ws7m1#_I*Vh4WZ`@V-H56UYSgZ8wc=0Tm3OYM!SctOI9fF-o{~5KGz&oYiLA@Ti zg>C@ps++(pcVz-=L1&l#Duw*=_XO8c_~jV!%ip4QoL`<7`pIq9>opN+kk5Ng#X{&6 z_yzV&K0k3t=bTg`=j@AVu3#^Ll@}Z7QN_j^4H3=uvVli&?WWWUbFD^;DA(S(Qe2B! zcAe+c^(pjCbcaJ8L5d}%7p?sRvG%(gd@^D*-c}G@ z7V80u&At*MO7KHOcVV$3=O`c1Wzo{{JZxbXI0%9h&|ySq_=O3MFKuC=N2o`r)e&^b z!O)yrO~3&J-y{srt{kD^CXV~4gZP>*yd!bGqXTDYjZgH9cp6Nwo32I?jYE$E8cJbO z5G&*5R;EvPs#E8?2vlJfo+7m?&EPm)CvmYsPHjdTI9Fe;xwX1IKgM?u$iU)I*xGY` zVy7B8!?U}>x&oc*XvR)eJFS%o86^C!^&hf{VAi{q2#RDYgt1TRh#=%d&Q`xZSed(N zN*gFvIa;)2u*#uRPPE4#iT8IkZdc9pwph}A3DivNG`L)#1*GKFOTtR+CUKpm;a{q^~ z+h9WM#v$)b3os)~G)I{DMv)*Ot)Ugyw8Luz9D@NgKD|jK4(QG*i}51UHGvnmo)3`k4Aaz!J`&r8Ll~#>*0p`a4Y!cMO)Ok=XVrvo!Anam8x&ajJU~yq0t)oTC91qLs(U5G_kvN#aBum8 za5+yo1^j@En>VWm5$DaJW-T9)w*COe!nIDbiTGV=J>sxH zu7X3|FeKA;DRYY#6k>u-ZwKL$dB-CUfUcJapp7#0e4J(k2?$B@|X%zac23G0yQAuSOYuXQ#d;Vy}#Yr8WCd ztl93IqFK@ei$2>W8Ol?H$@{rNV%v&!xx#$`SV^o~x)p^=yXkQo6(20^q2GJ)AXq!| z=TsYio-Wi4N>i%4ZQ=+UX)8g8<8y7rB*s^?O~v(PjBHi8zSs}f-zKtx(Oe)63}NAN z*r-~`v-pNx=K_qk=oQ;EjYu<_aR7T?A~Tc+@W(2;0_JCFjFNBM#nej5h>X=@k`d-|Hs zM{$KE7M+=HWAO2nZJ#MZHvHX7Y*#Z={zr`cqt?&%_7O^U7>*8IYw~-sCKIg_=|dzq z*!Uk{6l%@IA?(etZxa2##f>KRuVx*t@QncUWeMo|?gH9u6VPYOxeLZ^H)r3J84afx zsW-*6&ufG1ZHvjNaTxormAbKew*jH&pKpDe3k(oPYLX*!>$-&(K)*p=uJR>hlj**c$l|Z31N_e(xGg?~IYbJY zoI3D)Y_?NHB}HCF5GmIGvDIe%XZvCO%z1=*l2ftM(pQ*z>|LZ3pUrxkM5OzwuI93h zvYMv)3o+Sazp35sCh32Iv#3KAghZL)ff$dls^5@Au36<2sN7{;=s zd9b@QKi4MBr!ld_=akOt^|oT&SM)2{rjn=^fSXlaCcUjMi3&0=Qi>Q#xEO3)aX?|a zTew}K`l_xZ>bit#8q5Anz(#Tb6cw@_9loE8HIm52QlzM77O4$^!#4q{-UR7+qRPMK zW)mS$g2WX*6ac?0LE6w=ke+W7q$A|Ir5(Zy`)d(cEOT_8L)1Nv=JD35Gy_AktvzSw z3bdqz=BvQNstV0QKSFc(jMbRww0|WwQz}RVgHTNz>>eF`^wi{oXCIy%9W~sio2F}H ziPk1W_bsbAB~*i=8I4o_D&U@F$b(4reiVOHQJ*H?PwKG1Ac z^~yW@(JOZ$6q9$vJd>T2Kcze665>m zEZvhVzbV#4BG*b;rhdE4(Q%l1lbiPvvCea|i4Z8c;RWH=%urxaU1Q=0x-;q2&s#0PJN6ZK}J_zSJhPpN&j* za!O0_>NDKmvkG=ilU?J@2D_;q$iOM}#QyX>Pu>1xCi~1S;}6_^yYNdrdz*3gfrY1uE6d%&6wj(1&HJNGcxVHu3e}0E|0@)FrNsUa^j=jZ_MxZ}3whVy z8Z4G5K|f#d69(VPPm~-s_>>%jN-=g?(>vxUFSX1gDuNBQFhlWKDnZVv+ab&~dOZ;5 zo7TD*rhJgrTnFHTFI{1e+=7HlhqOkw({@(FL8@DRq9zG8^SNELBt1j{GEK65bQ6fR?UGx zxU}4LThF%k_ybt9Ix%{=+mcL1WfN;R-1r$gHe>NLs{I$ia6>{V%rLO>GfWoZjTmJTG zG(#Ia+Q&=&78jRGV6!oI5n-K`)+K)!=|G)&jx1ADbkX2-;tfJi`Av?t-E)Pq>ADyU z`U@(k7n|uEf5Xe+u(AboUg4)~v#kCc@V8kP9bggRhD}!%8t?~-7LMPcyDgH zKS$ExzeXN;z-u>CpT~8RRnDp3@~@(j3;;^z$jeL$pyhAG`}D2*?*K}N8`FhY^YkWc zez$BM@@o>;UO+-}$MUAR59ov}U1bOf9>M_+2vX6rTcewFQ42u2*j1WUs$bC=^IA zm=XJHd_7eT+8(#SG2d(hP~a>N8W(L8t!Yx|_D*}V>u-;IqKszr4N6NKjLg}!8kD5H z)1_Xf)%QWtwnZ+wfnJK=drBL@$fW^1>_4~vtW>)Kf0gNRgicu<#>3wP_T`+i0Hrlv zdJlbQ(c|~&@w@aGrW%{*F+-0c|H6e9r6N5B>9LL;Kf4Z(kJ95Ga3yQ$@96Q%>+$#{ zdgQ3clk`|aK>a;TA$fB~;@l*78A3ffNsMyk5=y94J z57Ohu=`lx-e@~Ck(c`<+z+d6f^oIy96mHD3Cu3&8{__P!$^s)~q4eW`>=NT(fyckV zV_)EL;|aJHc)SZd)&(Bt0*~<$k8gp;worP8+o2&WF!+}k^h*r(0)u>s!M((wE-;u2 zP5VYn>w;Rx?LV_iOe*`EOegyXrBR~AE%ade9H(DzrN^7;!IXNDelexKM!%R++n8pA z(gyuvO1+JKF{N7ciz)R9A{jXCP;Ktk?3hfB09vvB;sQPrQe{!AEw9up$DBUFWo>z_|&k!Mb|n)Gzt!M z7YPz%K)j*(MV1o+r@s;9puVA{DpR$xQe<3oXY8daJrrqiNcPH=wny3zV%69BSH;^3 zzQAI`>{qBYVQFES-&DYH6QNLG|7QOA35 z!gko5hoiqDUn_IX9te(GsGM(p=6JJS#=V(1PC@reI2Z#QIHZ<1cjA~aqA7zBnW@D; zxL7qLE7vL_S-GC;{w0i{pZJY~rT3#o;)Vc2_ulICoSve#g)R1^^sYpB+K_Z$7k@(krBk?Klnq zTm~d^pxVkI@oo)?cP|x*cSn$jWz0bd*F}(sW0gGa(XxzHcP{++{!_Z^b7f@!mcx<&gurwS6Eo zx9{(x5t!QtqV_g56e99J^^M3cT?Rzb_Ne7=R}N*lhNh);;%X*XS&#c;55l3;AP=Hk zNRNc4RKiR*BXq+eEpn8rv?!OzSdDxOLrvNhr2U*XqTDxD2qnmZLtDnQ8ipb1Z&KrD xk0l&}g>th<9?}_da$g0vDvrm+H>UQC;~%f}j+W^oPc+>Bft#Eb*3GHR{{x^C7Uuu} literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/db/db_reader.doctree b/mddocs/doctrees/db/db_reader.doctree new file mode 100644 index 0000000000000000000000000000000000000000..996eb03473054796f3c2e29a6053c44cb622f7b1 GIT binary patch literal 85522 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 E|E}dJj{pDw literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/db/db_writer.doctree b/mddocs/doctrees/db/db_writer.doctree new file mode 100644 index 0000000000000000000000000000000000000000..61d1b9291d599a82f6cfd916c32b73d160e5d06f GIT binary patch literal 29431 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 UlT!ahGNAqjXvor!w^cm(FA_by!2kdN literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file/file_downloader/file_downloader.doctree b/mddocs/doctrees/file/file_downloader/file_downloader.doctree new file mode 100644 index 0000000000000000000000000000000000000000..630c3354a49f3d93fd87744975845653e9fd2de3 GIT binary patch literal 82948 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file/file_downloader/index.doctree b/mddocs/doctrees/file/file_downloader/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..b8836d4a108d7497637b8251207b4dfc0fe28684 GIT binary patch literal 4189 zcmc&%ZI2tb5xzI+B;EVvoHUIc2u6}#3!G#-O&c7F7Hy57#i6=1DU5zG7St{!(#KtH zNv=z zp3Md%NivyG?QAEJagox@xUbv~|K?t}W7}2aQp-Zc)Y%ni#EgqX(ab%?;;z>>T1m%Clk3yrkf+mu zW}J#d54aqTNiwCw_a8ktslLNw`0`{d3fk-A@6-@ArDkqIX4Hs*q9iel!p~rcX2S=A zhaV0;8ivn@JWJ?oz)UK7j%_}5tcwHF3B>_%;5rC!dgq42wQei;y{Py1|NlvXbGAo} zQG8q&{AEny3q&F4tP^1zJF@GA;?92bKPAZ7 zUQDutC#3YouKvQYAL+tw8Z!0UI`*b**C^ZmnbIc8l@xONb7waM&!{_Fv+bN9EpX9=(dl8 z*S~@%E$<2YC4NJD3Ye#KsDTBdh9=F2Ar?bVhoQF;{(;lA9~B{x02kcf)HGh=FKc_E z9OG;)5MA70$=)Zh*?-)at9i2V2RidL#ePTOr3+;SKL4@wdBh%$ZjF1DUmx@*Q2)GC zXP@H88^PC3EYn;TS+Wr9(pk0wq{-MQy6A1S_PTJhNA~-#4-tq2EGi!8b3C%|-Ky$^ z3(<97x{8Rc2Su6@)T}8P+`!jpN{z7dhC9{O)hPks< zFr5UWw?y4;Oobe~Yujc#Nl?tM?M`L05p=R59itE??rg>OLaMdQ#1(XV=n3=Fhu3;9 z_R=RP1uJ}F+DO#1XP3dQP$W8bpJ+P-zG3K$B7b0{o&UHeyW z+LSx?sXa1^WLgl90AKf}NOf7XTSgip$^=w8As>Yg(yHB!3*}2%EsrXg6((>TBa`tcnGnq7WVeu7OE#eOF zMYD-ise5I2HHel+ptm1QGz`8|N~oYg5?yk`qPnqRzv_dk(;+O1hG|34=M>>wvpt5k z7FDk;dq8I#Q8dI9dM<4xbX-ia-R>n(8KMXc%o8ajU8F3g{z%?Go*`xtMh2Nc%h_&W zCMO@CXpZN*Hy0zSlvEKzI}|`ZV1Tp51{^ITzhI>mtT=mbX=TX-`X+9LU)nL9%IqVGQ9;@VI7Ns2U&8ayJZLBIh;l@Z|$?gdF;gHQcjlxtcfGMc3xP5Ypo z)HGWBGC%}ueadf$eE7p`d&!uOXj^~ZvKPXJeW&7UN5K` zaB3!D4^)GppL?-c*N`zNggM!rb?iL=GL0H$HuW#kxkt#g-Pc!{A+w0_DHHesycW%PTzD7dEq->~SZC)w{|M0anGNENItuoo3G+`jG9Er3Bms5^;Ut;D(9HDKFsWVEGE!#~u@HH~nHMJ4Q1)XWy`|3)}TlbCu&5emeg475nD8 zVDI|pa|}#hv(F2=vzS@0XLn)_eE8mjeM*TZaIIG!YEe?^Vblf^(yB)U*U8w-DjW* zZVn(F?m8Nkt=zDwGK*piwY&OCgKxF8X^pH^&5O7p!W@}mc46OLeQHmCg9Ip#yMBp^ z{{5wSB@Fxb`&{?Y`}z&Rk^#K+(=dDV?Bmsy75yJ>Iq3h-egYk+eu)2;N36wp%C@RD zw`w;$Ho(^3jy^f}t*5-!qZ(Fstd=+Xu(W5lng|7~Z&mXlxHa{6$6n%lLRCKfEMANU F{|2XCVWt28 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file/file_downloader/options.doctree b/mddocs/doctrees/file/file_downloader/options.doctree new file mode 100644 index 0000000000000000000000000000000000000000..e167bfd87af29c8f8ea13072f851dadca87db45c GIT binary patch literal 20877 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 H^Tz)JUYkKE literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file/file_downloader/result.doctree b/mddocs/doctrees/file/file_downloader/result.doctree new file mode 100644 index 0000000000000000000000000000000000000000..143ab6540ad48695d947a05e9b6c4f4785e81b71 GIT binary patch literal 124226 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file/file_filters/base.doctree b/mddocs/doctrees/file/file_filters/base.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ffceb7adfaf7da12ced57aec5587f786b9237bdf GIT binary patch literal 15123 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file/file_filters/exclude_dir.doctree b/mddocs/doctrees/file/file_filters/exclude_dir.doctree new file mode 100644 index 0000000000000000000000000000000000000000..9f236fc3ba7f1a3761ccf0f09ff9a58c39e10027 GIT binary patch literal 14340 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 HtIq!b%x^zL literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file/file_filters/file_filter.doctree b/mddocs/doctrees/file/file_filters/file_filter.doctree new file mode 100644 index 0000000000000000000000000000000000000000..4ae0db2bfd2b3e59a011e4f2706f3a7485102ec0 GIT binary patch literal 24382 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 C4arvk literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file/file_filters/index.doctree b/mddocs/doctrees/file/file_filters/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..3f23765c3e564d2cec689a927ad5bd064032f737 GIT binary patch literal 4687 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 b9M(6>ULUa)+FuKrfvpK;`m|$NKIr}%3`!rs literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file/file_filters/match_all_filters.doctree b/mddocs/doctrees/file/file_filters/match_all_filters.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ffb5b8f9f4a917803515addb25d93c088b10989e GIT binary patch literal 14653 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 J#rag@{{YUo+1daA literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file/file_limits/base.doctree b/mddocs/doctrees/file/file_limits/base.doctree new file mode 100644 index 0000000000000000000000000000000000000000..dba8aa4d0b65dd0b8dae1dda7dc4638a1b72cc91 GIT binary patch literal 29393 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 K(z^;^v;6YeG`?pDwA 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 I$ZMAW4{Kfz0{{R3 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file/file_limits/index.doctree b/mddocs/doctrees/file/file_limits/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..8c142e0cb61564ba956275188df4085913f05ec9 GIT binary patch literal 4643 zcmc&&|8E>e6}MyG*>|?jU*fbyP0A6`Vvu|n2MWkSAfSpsZqJ3Hh+nML-0s}X*tOYRK=Z~ zl1MQMJ)Iz$cJFnLzTSDW>pt)DG@_#pQ%T%3Z0*dj1`MVH3Ikzq)1_A{N8%8oe6x zl$w(px}Fh)1$MF;YuF+Y`WH<8Vur* z(HnE$I30h0du#Sy_I>=i`kP4Vgmxt&Px7uxvaYkdYm>igKX!iw{?;<};cDX)U6V9i zs8m+BC2;^NYAEUWQax-ufV}qG&VZ^KR)0oE7CH2#!>7gp3H1EA*R#itdN1^B8A0pT zhhTrtv$J<$=Bm?iJrqeMiZq&Yr&p3KA(up`ayqYNd3K#=*?s-ei`{}UL8j&06~{qO zx391zWaMHfgAfWS^O5Q4FJ0kMLXo)Sll+$yY*cUZG>i+2jj@-{e#M?py$+EP%@i{y z%cxS4xoDKW^f5y*3}ewZ6J2LKiU7hBy;Vw^64uiq=>xzbbF!qHE^tz$@ff^KVEN20 z_gdYu~@1VvO{5>EXl2Imt4b8!}&b_Iq|YYzQ;szK^h48H{w{{rcdr z5gVVut*>5jt0nj}hC>PaH7frvgne+b%s$lhx9O0CW5a$oVNWOQqY3-{$r5`uVH0ru zf&F#f%q^(`?CKL~Q>3TAc;(0^Kp>UK0^-x{S|;?o5G~J1x^qQR zP9;?(2-tkI?mJ+AXa8XTWdEY27h4ZR-j3)a8VdwkUgyn^=e>Co-u&C=@+O_4`2jn% zo$3f`mAHQ3aj#ufz$Fx<^HBT~++RYW#|-@iI?8}*!L*}CL4O>$hV#U*&)8d1zx+ZL zz%3X~!BF&P%2g^=mrh$2dspwPoTM@)wrFkd8p6%K@zxv&_+l)1Tx}NCcgwmJ7P)O} zLD~L#db6Uq#@Ey9h?nB&5cApYblw`|B2^SfsOt|2W*|9*L|qq!$_izA>|swghBT!) zQ6fiLHCzK$B+2lykqcqlEO2OK^l}%l?NvZ-q!(almajz~<82GYAK1abo0Jeq7=xy)$IVm+K#ppW= zCkrCZmzI~7bj!<~=}>)r`|`oU$9l)wfZUS|E{b83(OWS|hXrP8cs#=U4fG0qGpC`* zBaG!4G6&b#{?^m=P^1Hx36JooN(ffhEm9~E%t-o%okF&vBFTbjO2V;@z>$r4N@6Q; z+fiIW*yztlnWRM|f>B}T_tVs&6kL%&kZnIOwfHdaie9gz=FQMW9o>sH_l6ipBwg>bBce)#&mZ@{0e zd=M^(x0IRqH!ATcUSd=Bg617g)xg~WK7$CU?wFHxeFr(21k>W2LSP^> zn;{duBgd)26vOzC#rPqAS7TvQe^Vv|tJyCKRJDYoZn}5>8pI+bes0lsLPnqu0`Kb$ zUUn|T*B3>GE9`pc29Od4Fs*>X(*bgz#DzTz6hPSpHn&CLF5-*P?f@4CDTB!-LIsbZ z0i#smj$0wPE&`em9tYTDE$%w4cgukmxf%~VrcLw;O&=MIC3@YX3Cb6lh%Jz#HdNq; zb+`BIE^e|G&DlQNlzQ*dkzbphpfVO##C>UR8BEXZVq%vt+gZ*Gdy7cDW)D5>0R8Bc zeab#5bi-2BID;8>$@%OV`*c#U*X;8trs7Z7dxhSbkF4d>TOmh&*!F{aJjE7p#pjKh zr+7Y;2r?n*3(Tn{k~`M@1>%&uafz)_-ReHtyCff2oGUxc-ddDZo?t!&yto0{;^;;w z;uv9o4QMcHKeU52=+9x@c?$q}vMisPpQ2>22%`9~x=J9Nzj%r^^JHu;EjgB`yNWvP zlGAw<3qQn>Ax2tu_4G_1{Rn}`ZT_`=cC_!>f-ZNryKOGp=zi^KeMKGPHy(EXJciQp zKkTZQ0M5_B1J)t_xiUM=ueG(Z(Jkv$n;IzAANL-d+UDc`a|U2oZpt+)mU?-5W9k@} Z>Ko;@fYKUTbZkbpC6wrC_pPGe`8N)u4a@)l literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file/file_limits/limits_reached.doctree b/mddocs/doctrees/file/file_limits/limits_reached.doctree new file mode 100644 index 0000000000000000000000000000000000000000..3c8f7959830147829833580d6362bfd6f8618816 GIT binary patch literal 12366 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file/file_limits/limits_stop_at.doctree b/mddocs/doctrees/file/file_limits/limits_stop_at.doctree new file mode 100644 index 0000000000000000000000000000000000000000..1f33546afb012db22535ccb80ac59e6d5a0f447a GIT binary patch literal 14340 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file/file_limits/reset_limits.doctree b/mddocs/doctrees/file/file_limits/reset_limits.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ace8825cf3f72fcf9aefd51ad2524676e3722393 GIT binary patch literal 13211 zcmeHOTZ|;vS>D;5o=flS?8UQoOl(uT*h$aE)4j7MiW$X=VuzKZv1c8}vMiZVxw`9g zSJhNk6?NI25wS6lix(XM!bJc<1P?rM5kyGbUO*5a1fGz1fbb9y7YTw1LcH*R`2KTF zRh{adsqJx=L_&JQ=ld_`KmWej`pF-E;)M9&XOqZrgZ-{$+d*ivB7`f5+GuAfnvz1!$h^5sOVZc(q8#BvJ9R@zdL@elC?QTBN z{a{ZF@411^_Pb6x_S?C$xSKo6STGwhED#GWgaD?`)EF-MwglgLzaRS6uS77%+g6&! zZa+)0ue6V+Tsy&@@x_5}B?%hwy%ktv`EAM0qUxb<4fY@f(dU%qr@6DnPi3*2^AEnO z4a#|YUITGVGME_HK*(?>vIhFN+rUeO09U|v}e=}wYOE-LX?54?L-IW6e6UsDXpl$xc`i}{z>KG_h(a`E=lxv(i<_!Y_Uj~&I?%YJh(i;TS9X+u}U6~hQ%VF6T1TK9bnB3Mpy@bQkOwz-RQW1F%Ip_XNE)# z39T#S1yh18Q%hOT`IW{Fwcfe2u#@H%t;VTVBNvag%)|B$rk2vdn^k&aCN7jD&ElHe zKc%zNQ9v@@NDm?`t^2W*#a7>Euf8F<>{|)z(l3)1Bp1|RI zEbou~tt7I@uPtFn!s1&@DAlL1MQ0Rb$goDUnyF@E#pIkMi1Og_A3#C^AASmJ{+00d&*?*@}*)1jjyjC&6h54 z5w6+c*m8rMpNtL=;D89MAk_L)*!ObLK{;W5cEUX7{4{ze5gWKecaY!V%TQfu|8xFu zqv;~6aj+$nXp)}+^xYpcnxGpjdg60Fw(b2CT}@f=0u(2ZSfWmlS4cBJ%J zJv8{@l$4d)PW00xRoKHXU^{-bDQcmP{K0YNr!G8gz|7Kg;Spx?4#DM@F^lptnpKs4 zG-mMjL87S&@4Y+EJPjFy(_o?0fA8bX^3>L^tD24!?@`)Mf>EJnl177W9R&gJc}z8n z4zXsg4@E&S`Ls-qRHLMs%)1iK|EdlZYt)R;;cE2R=9M)G@U9x-ZPVma@YcrNw&>V~ zLn(YC4u=$qz*ghjk=KK?g&A$JfS|L$mxh_|3kUCfp0~|39FXp%N$&k3vGn=Rz#WKI z8ThQ$V(4`d{k?$$orrR5a%YDN5@z3niqDb(aI#g(Lvwkzc+EOZnDm7Rdt;!&1`zWoe_v`3f93c3) zNrf(SX!=F9p*M)CNHornw^JUeP=Y0F;xl4pbr|SpIoj`!5RFoGXp~f)W_kQWB?wq> zQnOX{!DA*i!Z8U zfYQP8x!RHJSUaNJU0f3}u;zV5C;Okz;ecS=c2h@%Esqn7lA$x4wCnhD>DfbC^kRziF&U z>PV21=ST8`BFFKU=fGS`B}x3r+q`w+T|XwiET-*&w3qT$`+`7M!{(89zu zpqloCPAPn;@M)T$CO52EW$G$VlYdoAeuxkF22}q3Q_Uiuj{Yu|oHX-akN${%sq)RE z>d_xLj2{-Ou7heaK2^~l#ZbqK{#HPsivF5e`+bBA=w2jMoSwQW#z8pF`Y~=Qsq#LUN`8#zt$n(6PM!&~Abnf~nJ9>`2B_01pOZ9WEIVzwwmuYK>fHbA4BPUr<%|=j-homY*=u;X4dFG!j%(q^OY^>0=aU zoswYZaxVhiUK)xU;mz(KxpjHHScyKVT z>yj(G9x_WcNb#!TkpJIK$|d|C*iLB&pwX{Dv6G47DZZ%GxqjDM1Z*!KWecw2ke~;E z{Jobh8K~MCFqD_sM*qMFvT<~P$&E{w6r!5@lx&U;VN*B61#jw5QQ67DPCaoq+x$zy z!pa1HVP5Mtxg!G!+$iAYo+=n8ZYGKx(i`BFFCup(?wq|Jphri(L-H2BxR?wL0GgE{b(klhj1x{wURfC2g2Y}@s#o()dD-t zO)3c7#G+LCvNKUdGEr>C^qRMi<7z}zoXLtT{vZ-&xV?cnRi%@***}n#7O&^t+XSJH zCj5azd!e%J_1w9M4o#a@mIpd7@U=9yg2Wez0jd)m!O+Pf$5tAq$YE}u;O(Lhv+$d$FN@Md)Hg9^Nbmo3zrRu{8;Av72fjCHVw4sG?A#Xg?S z(8U!MVbWW$(vthkFAOnF5T;Cf`4&o3A$sVVxjzh%h<}T(Qsq%(s`!1x2+)S2Ktu34Nvk~T7 zrGzOJd|+N?jnT4q z%*9QBi+e(TR!Ux*r6p#ex(%@><(r8qyf2c9uF82U9*WTq)n7~u=I3Y)z+U1&M82pV zJL6lyC`0`+ByOYZauhE2OMT-DC?S9o1B`mW;6IE{Lbq<;rqWDiADNq8P zz2zpZgzQ<QbLQyMxKlw`#K`kBi!4A-D zHn#jUs>sX7?Kys4)J6C#7y*bPsL7u{_kO+_v8Xa4v>4P5ML;`vC)MEZDQbGroOGiD zB=dr97>}S)7pbRU06k6IgkgJ9QQXI(jmNHjC*-*r4+j&|Q%g1d`-`!Z;C0zus;p5>#7@YaF*3KxW)$HZ=GE95@W;@H_nC0KN81 zhH5L{M6oB?UBUHvRWl5%`a@~c?(kDAxP{WFK3|g71nLVU!uh$-jntIIB4Tmp!Bp*k z4Ujc=)|{1uU#PvJdtrJvRgS-w5S3y1o{GS!;GH}R3LrjeC2+hLEZaKLCEfIX0;a(G zG5pH-GQAc)6oX`kx5Rr?>pViW<~N;Bh_3Lbx1BdLen#JK$rNskq*DTQ*+Obv^pi`~ zTL_Dy75EtUL%)QOXk43UEPF54W?U|)ia{4@^TR^NX%2n6I60Sk14jv?GoX_gS=ZBp z(D$)9P+qv5MR7ty``ZwS_ZJY;_FVGm{6#1diXEVm*o*{rPZBO|klu`(@oI9AKpTrz z?YMZz#guG9R^!^U2xm}L8tfSoD8^-RoKYq7m+6)Oo)Bl8BI4+({0G9^?)-F^tH+O& zY7hkz*Srs)Yhxm)rJUXz?zu%(;rz#TZrmhCB>z(k3?pd_{)a+~rd^lw@zbR^1nf_{ Y!NAXKR9=T8Qs=f!?m<~nc_GyOA2Oxf=>Px# literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file/file_limits/total_files_size.doctree b/mddocs/doctrees/file/file_limits/total_files_size.doctree new file mode 100644 index 0000000000000000000000000000000000000000..230eab6c4abdf8b40baa11e55f99b10558b5a645 GIT binary patch literal 28858 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 QLoi*Vg+DgT&ga+;Eg%*j`KD<)IhSjL2XZp_E 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 S67Wos42ICHG_7hb=KdeoT>b0- literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file/file_mover/index.doctree b/mddocs/doctrees/file/file_mover/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..b76f651d41679a438eb50b8d9ee8f69feea87ca9 GIT binary patch literal 4114 zcmc&%TW=f371oWENZl1V?!^uWqe;~QB5kK`1EFZqHV9e>q0*)>`eH1GyF<=cyF1&N zS(`#*^a41rfO*Tb0g}JeAJy;7?vfO%6lh;6fcEa0Gw1r9%l@VH_sxTq@aOhTA!I)7 zvn0t?!i{%ZiHggN=hlDWfAClT%AdH7W-pB@b<8+Si;VR&dxE1o3?Eu>a`& z{*yttK9G6Br+s0wwCly@GcP)Dn2$Legu_h`VD#<{gB#vfuzQj3Z~uQ!f^n|PtkrT< zTI7}TxGoceoVm?7WyXMr=T^?L>S-$}i}}ll#g~Xe;8|y>_2SsAms)!F7e6$m+q7&#YQ1>dwHuV}{8Sqo6IAY(oJ9^m&q{N968ZCD;`{Yhi%e~AQNioQ4ykHyZddX8D6iuk%>ARdZG=WF0! zcb#ZfKw`Zd0Zr`+mWvQM2L7Y5O!;Z1UT}S?m9l3QqQ23o7Mzlacl%5Cf~~)V{ub#M zzrb(c2;3PT7$AV@fz66Rh`@k~U_b)GKT*(LR{$dJgT0%E$4dn(+Ef`QaHfUIPH&V& z_alhweN28dHL>w~KBfNQ7SCS$&`n_EA1Wh<;?eNd@YZRh*gggOrxm;S1U_yA3%6pG z6{^gWg_u?*iWLA##a8o08|$-g3axnT?%Wy^lnT(MWhju7q5IA<7rj?O^q>2oySsd@ z3JRnNhi@+*YHmst*5{tP9ht{zNxd?ybcsKSSK`myZDnkl@WKjzzKXhKLEg={x{XPy zM*iBhg-jB3F%3^a4O{!8~6qz~4ec z4g3lu?%RA?0L0O(k0;USDhh4pl^0)&4~@HfD+>w?3>RQnG_$d)Q95f>;uCjlHOo!P zDAClhCP-`5Zd+DamR1q3c|rn8ipr|ni%U(-EUKH{(Csvo(C&tA%W!LDKEYHzoK4xW zR=MSXzH2{an0#hd1Fo$~Ta?xh#nYkdOnA;Uvq}S)ozQkI%L=?~Q@4SsK-<*2)q!{0 zl?f5_UV@=pzm_`1+b%L3(Kw(}n9?G&TzPjihL~JgzHs;kLlSWZ+M?-L>CC@zdj$S+7~1u5R$UpiS5A^z$1Dxq@&nydG29xUB-2jl>NX9VJt z!XfW=Q!Dx8fs@l*N#;_hK~q`Od2$Zw%cQ+RneT} z)@bPLCS_JcGdv=yfgk}zowJk#?gvV!21@;$R2W_+Dw<}5rUR6nGCW$m86bjceIbpk z@VXx*(y$Sl>&(i{rcO|EJ8%XoW*&+TLU(JnFjB+H5}GZP7_LKM@|>h4$oDq-9r9GY zqNyHIu>m&u1Sm{pCawSY;QMZ`;KjUskTW|(88O&Sb>Y6%tQH`fesN`m%KJ)BP)>J< zllmHQMwVwtk3~ARKtFx=;g1l{Jw1+>#1myM{=G&#Dwh#nFS+hZZpT3fy1{@pRw(OO zHbMzu!}aG|?mhsSMYGDB^}omzgpg}@V6Jk@rjd{nk>UsNZlsEEzhg3nZTycVO4=Y$ zZ-w{jHA+>?s?p@`$AZBgB)%#(L^*^Iw=c^AD(H(KjF`FyU><=Y^D!_`H-#hW5@@kY zY+-SRE>=|{t$mjskiqOqP`+2NKyYrM<4z2&%ZO)8rV&0~sObi)59{RxSWU-OL%VkC zJb&Rac-&UiN>IB%A{Kv%+OSczp9S0;E`PjGD;LLN$GD>#N7bhM3YD?+Rd8%b%XoH9 zyD075G*+H@@gXyAlMXA|35L;y_)2_Px(-q8RRK3>ul@Rs`0Bb8@6huFcAziCr={Cn z%q;P_-BSkoW$r0HSo z^=7q|^kFFNw&L;g4^~%J%zyYa!RUYfWAK3Wef+n)PA%?Eu2oOCRkuN~K%xF-_|XMT so$6a0jj;YOYLVDSl{~jS3r>LfcD)OttS0oq*G*^~)SZ(y>vGip7x#ujwg3PC literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file/file_mover/options.doctree b/mddocs/doctrees/file/file_mover/options.doctree new file mode 100644 index 0000000000000000000000000000000000000000..4f7c7c0dbacc5e76ce72bcc1d1a3bf307c154a61 GIT binary patch literal 16664 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 PC`yCNkk(aPk~jYsPK|Ro literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file/file_mover/result.doctree b/mddocs/doctrees/file/file_mover/result.doctree new file mode 100644 index 0000000000000000000000000000000000000000..83f38943970126e6a777b3bc37f484367868fed2 GIT binary patch literal 122686 zcmeHw378y5b+C1;j@4nyk}Yi6PFp^9*Oqpr^~u_jFKi65Wk)i^iI7LLJH47|&CU!n zJ(AWoHwK(^Fu?;PAtW3D41@&o1d#_$y&mMZ&=qXE1!T+LDTa9w1c6_{8D%I*GztxFWmFhF? zIlmTm9`1}ir}K=?XtcUnJl3kWn=^hVIt5D1lq=Oz)30?7!pHSkKCDz*;{w3uR(rY? zRzi4LuN6xEY^7Gg#~5CDD0=z$xOac6-DuRCVWC{FA8Fn1RpyF^{i!Lh9EOe7)Wk$( z?(lf4T=A==)_A2pFBok6fH zJH+VL%RP7TIbp;_!^JRcR;Jq_$Q9+dtWs)$%%YVu)ncm!FX4HpR-EHct4i&L`Y~Oc zIRdS~$}B5Z!%lfqw5;8%bfPPI5>O`^o+;K!l~R#Y6D_%}Q@*6tj#h@n!-%$1J~LV& zwQTLR%~n`wH0#y+;oY5Rd9_mWJA0N!D;h-*3lMX9d%9RY2be0K2NLt(|E=)(mNO+|?c%MF(&$~($C_bi3-%c9kVxC51!wWmRw zR&ce{Rpd<2|H5pg>TjQ`AN8Btn|`ZZ4X3zHWwJE{Y*k8~XhW(dqxVLjVF+m`-vIwl zL|1}>&-oKA(1K!fBAjbXuvVDBjxd2B*q;KUV1iRH4$aed#x=lB#X`cR4Bfm z7cbulEH4LoPl<+DH`dyiQ=6?LD?yK%6{h;5pm>$qVc;m5L>4#$-YDxPUZv)7?ZGA+ z$3MKuz^+wsWRe_EYwwrpx z-+d4axxz4d$u_Umo|*Amt=V>U%6n##vX&o?qp0@#;Q{fnGS@&GIad#Trjq0P*fW+o z=qHcJBsD#X8f$Xo%J5 zsJ`$5y)Wp}=YTW~iI#Z0n;pa_1>q$+1k+ANOOADly0JuUd@*R_3)<6uc5hjIe6Bjh z_yGUk0L}NCM}1~>o)2}O&GAJ?GAs_$!Yq^1n4dz61f8*V*sITyoz3V>JNGPMU4JbX z3`8*KMl^?gZc_W_&_0QNJ1$$UODWZ=Z_aMj(P$0y&h5}JjD~0(lEmc7e*1cZmtS9g z12+02s^Q9S1Utr=SdUj|cj(*6wC{s=Q6eL&p!TEj<6F5yaXO0rAwyp0*ZW0_FHT<)x*De-;XU8oKD`_&5SW=!hm@jK@r~euR#%km?Z% zQrsi7SE@(+8^v8M&E1cI=pQhwo_Jri3r%BJ*QNA1}mT+P)Hjoq``8m z!tp*7H_^89KuaaeMCTYFj^^UYb=k^ zH0iN9j;%`dxniZ(iIz6zVKf3S!ubXSU&0fz;g^V+qb*4ru0=4NXbseDHT;>%Y-OhN zV6+;1$y~kE#z^j*WJTThqZ6B-;*NJO7~^1s2_U!t{%KDKL-0@BSW6B_1%szpuY&7I zmV*uOIVlMtW{G7~!xA^{u<8vOvb`iS-Mg^H6YU#Z1=Uph29L*|*#5F{&*yDKM1WKi z2fGnoj)vqhPZ_$)L5;U*k~fEjG{cqvQya;aFUwxtwRasQsUdpT-ztUK6)VgxjnC)P zkpC$~Gum0~w_Ry|DGfQ>BEZEfdi4~P8T+P9j&^_W-y~P0853Bn?RQ#InKg^Db}1ET=$$8*R}krplvhYmLb%`D zjcWG|9?79Yqnh@lTZir5l`baDVMNPRFXa+{;izCF`CD$_#ki0oGq-i>`U4U!H`2k4~-Eq3Qfd8U_+g*Swl|=0sVq86s;#+FiiQN!}b9(li~}F2J?pyqP43?l@gtz`JPRb{F7E z`NwGBb{D{b=Wqd^C$aS60yqh#9Cf8mxB$9fbk-c?s44}_d==su75EABS(uM1e-e^W zVop;|&1n#i6>-4{ET@5a30T&GlWO(p0G3O1jCsCjgdUywLuvuX zYUKKGv)CwW3I6DUeLkDot?>zNoQ$1?X@3th=N@0}5YL)Al~oRNXgL{rXcivxfy=P; z#0j5Hh6OyqDS}Y&(#zp79`#P$1kDMP7O=i$whsSeyPzd(ZSzW%qm`0hf`uYs-J9{7 zSj?M&bxw!txWFV%`}G4twdAHAS~A#(m0+oi!5}Q;Su+hf=Z9SNRSsBF6zmj*O6bq& zigNlCDE!o+XQ~OeL(JnFO%_~=NMVtK5eCF>0sG1-0B#nm8ff+TAoQ$wC5z_ht|0jz zjh3@%D?YWfqA`zM)9jb1V3#3~$zTWkLrr9|g=DZep%a~n0#C1(f-b41vx4gZoSJTl zmLY1EBlC|ps+E}vSQqyBSg~0%XRBBj*QQdy0_gKJdP^YrY_@7bN_7Qrvb_r@s#oc? z#vE#mK$)_E>q30PY)vo0 z*l2Jae0NiU3Y2tGf#5cTsf+t4zEHE5UBn&igW^oi!G3rue;~LE>r39!VqFPhorNM4 zb!o9;t!al^rdY97mRL`fot>}MvdQ)3$WThI@moW#@-4{Kl3V+Igv6FF_AA&|c4>sT zZ>I(O2vqG`urYIK$*zLd{_QU0r6r5CIu5l=$zrW6$(|-8E5^5n`1)tVh(}WTvJAKL z`oJXkBoM`U4=ln>Je7VwP5u=#Imr+!Y^sQI@)|giN`&j zDCF%Jzj}NJj}c8ZqKg{IC9)`JQ|5OSBdr;twQC7{kA`5+1Mp+S?ARcDGCEiZWhm1d ztqq&STB}+Ni_@^)gw12KxOFmE7NeKW_AC<8a=!d!t!PC~SF}?6gmp2jS(&tua^7zw z=rtgClo&+*FrD9QPd8yHj0)wVRW~0m!U|uQYR5hb?WbO-2HPHkwV7RgpoJ}TDVA2D zveKR{jEHp*F~QV+SeUNXXO3vJ++Bz4=I-6Qz1e1cPFv>*TR(6kh=(~kT9-;M&fVig8OVR_fq3HD>_Hg9}Z?2nVFk37O*HB;aG;}9{!@+tdQVJwm#4>Ig2KD0HSqilzZiime%v%o6u z(F5!Qa-?RrMz>DzB|d_=v29%oO>Py14vK#{j}|n18u&Jp1=0Yx^yV02m?RJ}nP!1v z5No!X2|~13CJ4-f0S!jODhPb}cKP5*)iYa2sryOQ)4Hot9PCj2svftmU@3Qj!E7Zq zEJZ7YnKV~Pi9}X#O_q(llG)gmGxa%RlO8Ot-E#i<9&DMy%^#4Bn4b4)?YYK0VDZjB zUmAb1Oib@y3%$A%kVM{=Br)WGz(hKCg6dqK3HxB_lnKXs9hHGQ$h| zO@dmCU{MwBy>YXzj8&1xSn+QaVm%boYzwhQ%O5ZQr4foseqk$y#&7Uq1jF@RNqrgY zo?$(FG_iC!&OI6n)L%oALe8hW3aWwu>blzKPe>FQD!_po>B4wjG-FY<=KE>*T0YrG z*>qbF&9G3*+AtMwL&wwQ15#LS8qyYvS{ zsn0(N!ll_wSSU?>P`+Y1VzGQ1PD}iwYUyq8C|g<^%n_rzpXB0JjP4{xkob?W{?0uc zLCn``$nU;&MQS*Qi$>$&Tw>ejHjfP&Fks)N4OZkczTXcsPi!!!jS&6gGkya%TuQim z>){PJ^0>L#tn;CwR(cjba7QeLg4)=U`%VrYiiyGEPBF&hU)69?8*ANX3=WN<3mb-t z;hfh3@>{$3cgfBu41`;id;MKIcT8SB27@a(98C?`IH)4*FD=6^MPvkqx4A$*LqQkA zHvul$}#kPaqofQu6P-b?^V}Ie1vhm21|};3|RWmc_+|hz|ub|SUgkFfGxY>C@QM# zN-{1T8@>y6Ny7&8tA$Y?WnExoa(st(j7!DI6(!-` z)IqSRVcSVy%h#36Cg*cGUHLZDY($W!znmN~;NH%6ze>2W`3xnJVDy(eN5la2q)&nV zd(x+fP$2f5Cw+?aktco1NuQ$mhwkd|i|kY4gyvEhsU#Dy!L2ZsF*YN~w5G_trbj@# z6C8zx1wfPLSRx}#np9!Z-nw>P4-b9}hYH_$ByayFT(SM5aIpNNOaT86qBxe` zX@im8tsuSIwejl!6XxexC?pc*Y0cV0r9LqS@!ybN(K0SFaiLO1d`&7lQ9)&Kjg(Qe zLY@n71&|x9gwzU~cn+?_pX@{!{dcXtou8=%i`Hs&`M6%b-j3&I&BHRbwc?}vy#Y#y zwWqDP(5bb$d?(8xB*X^?I^cXMUwJ#vGR- z{x-*%fN{iN9_y7SrGNJx*bk{^^^smULZ?!jskTdSWYirj@lmWEcuEZtAO}WnfRh!Q z#hDN$exFne_lM1qZQjW3#YO`fkiWR0Ha~Ju?rz+M1MdMrnw~EdtCeDF%A2m&t7zwn z)fSu#h9(G4dBko1@G+(sOQq;2v}+*lBnUyI2E{}nzt=!2X5BGw`)= zmsMVVIhPMl%JUy83HYm^_WHzznFORW@=uBDMu!G5&%Z6Q(6Ea+>G(89IP{$u98a$^#9WQdn7x~I?QZv; zK=W$f(F6?%?B? z+~N#+a_|D6l;2AETUn(S(oK-LU;TMd4@S+V)vzxrc^Qp@a_}5WcSV04i6X;l2;luX z_`q-vR$nb<;H}@3DBeLbK{|-HGih{hr;m^*GTMm)Pl(#?AU;FFCmn<<3tuBq&@32| zvUzJEDBC9`r+*JzyFNuO?((ICUIjvsRvmAu{xTF)Zw@sO|PN;A2Gq0DaC9P;o88TpjowdGy%M;iTDx?+-@RV zDgP!7oTA(`5koBREc=r9rIg_(B%WT31n00vBXNErJe3uw=Uk*Piq+&8l4;1n{md z;x#mIyNht8{7-1$6y>IiAWx`$KZ&Im2f_I><>(wmZjqClmYGp*!nCQJbw=UXHJERe zYxoFW}GKG zyjpglFPKP~jnk6lrR7L}P{yN3^PFis4njRg<58zkP{xB}>25rpL88bo9s+pp#^WIx zt7Et^;1TI>Fu}NvNL?HFO*E_aE+~L^H6D-B!0pDvmGY0$z$wae8IRAAczQ7&oWmZC z$Jx>tWG<6YdcRgLD~*QvQW=cz(R^kajBi6dM}zS*8U;LB-N$p-FfFm9xQ+YN>*<#*D+DavyhjC)8ty%-G6VIG4) zmkoXZtC8hNxq(^>863u?De@{yehh7sZToKNtDuGxD>m&(8oiXVGS4C#r{}m_N7ie~ z_dXyk*%>_ zoL}AM)x}ci>b%Eq{F{fhb>6XZWu^=}9PpNS502jzYaKl}qF4B?_{8E9i&SasBhDJD zoi%Vm78T>c09C*x&2f-y7K98`~(*{rwbIhAOL)zDUwej61d*O5g3 zny6t8gEGc}h=$TVxsDqTbooY%AZiX`LqJ~Hai^fhd+fxsk@I{>DM&Mfi|E0`jQICi$n9Npb$6t z>CD?LkiAo`@eat%Suvf)J7pW5K~kKH07ZN7H87EbJ=02$OoiX!A?ZMQu-8DwB(uRQ<{|8~lU830QZ-I@!{1OOnl<&zN69MTlzv;7sZ06v|J~)ffpDDIm zK3=HNs?YgsOK-awU$WajASJrraTc8xZC(S1MDoolNMe51gMsAB1>%JZBp(3`IRZ&Z z5D55Bv;3wRwr67x9=5rq+)El<-z3#fdn30eh8mfaLJw)FSxvpcbURW zyGw0 zHRk-A7(sOW#K)Xcn#ss>ZAvn5=(zb(nX8}1EZFu}(ov{lKlncgCK-h;gVU+FW+c|R z*yGLB>r4Tg;v(Jj5L9)EH#d_gGBkn%w}()SMb(-Y(eQhXH5ry(10_zr3InJR#wt-& zHTVpOd9V>grbXd##Ldv4W*lKEvWzBfK?M-egw#dwiJueN?GjC#{X{>+6Pu^uhAF&w zi^ay~*&S37r3AM-D}fdPEUNv$@ZrgVYG|tb2V6ca7t7*+ONkNUg9BJUeZXb(t;A&o z`stSY*tvxm0wxcv#8c<;9YJZH8WW#+$aP@XeF_yG?gs%RJN6Gx>Pvso566p-{^RG+ zAYJ01zH!|~4^PUeysfW(pZsEZ^qFwJjQ(pNaZo^OYn)sxa?{1V%*(A(ee1noHRy0uDF zRSjMZ{0%m`&9WvuxZSiq8QdCbEMwasdR&}v+fI8i3C2WwU6k4>qn8pNy-LnMIJq5d$=HgfcyAmS=yO&`7bg+B()8SAl zr+8O}o$`e91&@E@fEdqqPKUf0*XBJb-44kNo_jldJu}v3`tMzU_ZzStFg&^A@~edd zIobHWajZ9vZOB9BlZWYxGtP51emo3#JoJg&!+><3f>3**R#80BLLo5>NYmy~L6AhGl@0NAJv02H`|kN%H? zsB}NteWmQP6EzZ^*S;JQ(NK!0NB?1rAPP@p^skyd8TzkMpZJlI>V=9(UKW#`Kj>F1 z2OR{H4Eoi&Vz--~-bm6yM*XjYsxDFgJ4h56Q9lQ6kNP=}sy#nKBhVteTuTA^+lj>) zP{BJD_gjUkss?pnaIkT|)rjz*|LcIv%xEhemJ$Cqpp1<8r5Zxi`g9=(mx#Z>Z+mKa zhYsDIuW9DIB`Z%kci>$pCRecR3iH zoz1C+gC=pk*(Q?EVbxF?sG(C$%bkzhrQ19W7ERW$5{)EDE~`-8b#q_j3^AEIQ?y3Y z4oOmc@w%dEtS_3@=z6DsCfiE&=%g{wMxKl2$Z**CZPkJE&#~N?s}04lQ(}Z+=M7lj zGVHvR9(Kl;9`!%UOkDJp8Y}D^m+pNSEkg31u*BK^@t{<7%3a2_O4=yn@u!sI5hTeyGcfY|r^q3TkS%4ct_$R*TbB|G)vb2H@Z}5C5v$2iO*(R-xD`;BKQB6I%Ig-X)im z;GXPO;mEOK^Dx|C09Dzw0Aas8)8ker5`eGEO;$ca@e}a4mAFK zR(6qZEcgJ)W%ijU?_tC{tq1%hje^>2&are)p?#S|k&!zQz}05+-fk=y*n!61Bbgu% z%KbKzM)!958HpmJojCA>sO<;kt~!mf++>=}m4!1&6f_HiInek*l7fNAX_7=iGih!_ zvdigu68=Esw3kFdv*2_T-P33UT6T#_XAbx%IwZXWeO9!2+R5=$=!J#hX^Ia-E}8Gc;sn4j`a z+{Fa%rkf!1(3p2XJ;+0wM#H|Y|Kl_Y%8zp_-4*={B#I29A%H(-{P?#>CP@GAbtaAO z?euRXii~#Rz!Rdj`-f#4diLY4ENmcA&@2qbkN*-$!9e7+okT%1X?97woUS3^lYZP) zPER6H&@4Fl@n4}4ERG-Ntz+8%Y(MU5-kwggYWIc$cvnCEEE>4okGoR-n>28W@*IBr zl_Zv4{5a>&l%umBALzvde@63`>7d^R^&B1ahiMd)gXUPeJD&eVqR6lY0=RO}YW0QN z%7uTVu{xTF0UrbTRVElSH<@iBTpRd5X;$qWO#ts|B8E?=+8MTX@Nz?J3b^??nBZZW=gheBA*knQ zJYGYipo|B{(%pFc35g=ZcnILV8;=jsSQp25yq^ii)p)oz@Kwp38U)Z6dw!TVYXdmmwnkzQLr?=~{kGF$|-UhFRvaq(2 zbJ(NtSg?x;&ZGIvG#FON-^oc&1T?~*)_xu+ah{-bX!)+E$2SmLb7W91oC9H1u*s1-@6 z5hcSiP*KFk7B4xdk-vo`KffrpNt3W-n}%hqUo72w>Qy9)jGijA$wkgL)9`!kcPC2D zzeN8iAu04LD=Xdx058WTz2b zUFTb9R_$?>0Nynsd^Zi;9uc}y{&5;OMY$Ofk{RnSkXU+&2RVPH9CZgIkB9gy3^r5y z5VjE8e0YqaU8TF;EG%t)^0m_Y@6*g?MuXphdXCZHf6^$ZXpm#+ZsCT{Bz?VIF$8cG z4d%^RpF?AHv>B-gqrW2@9tT7v_q=4=4A%y}l4g}`;I1~~2{drK&2XiB9}S$MJYh4~ zc?#mqrzCQA(=eSmcR%W*>VdhB@z` zarSD2`U-#F4m7#jAl?9ZG@UMMUgM~?pa!qP{Z?#~XmBrFh!XFXSSI(xW;8f{R;v27 z1!{1_((AbLE0zYg1Q>z1qtV(Xs!@X%Ktm1o=pOx!Mz&d(AKUYDpxZpQhwnZXNA?&u zuwgarjz%d=2$^0a_R&E+()EtUf_6vallR}Tm%g3xkhpkJoY^CGlEAO#;r5(g3tL{X zy`yy`1xCSaoi|S>>aXJfnO{Ux{k4NVW2}YskEQbT1Vi7hD zi99kKa0NPEi?1p5X4^G(u_@k4it}c0Zz*(vLnVK<*sg|$@HHYBXU=~VPKc@F@mOhb z9nwq5Zj;g@ejvgjDJe@fdw(yYx&taP&A!-;Jx!iiRX+_ONwRE+m}K!n1K}q?ICaLD z}m1Nthr{c(rV## z#y#EhI^lXesld|v(KXTO+UcY*w8Y`01HI|>&ryYQ-MaOci5dx`VIRgK8cO|S-U)Ad zeP4_qYMsbqN#piTo=Unh{=(0tiGQn5{O@C$ZHI!;N7E>NgTF&C$umhe;vG2q{qO)k znHx_9=Myz}b#xhH z>UF>)*f3`&>~osGM6%!@343D=E4|Qs<8~66?l^KyI_k&<+GO^*#~Vm49GWcKkK0X- zy$c%GxQfJ^@lNUKQ%H*ScJE4nqCI#Nde~sc!8T9Bhr$)$z#Iz422UPf8$c0=7}({m z(q@6>ez&j#F2moO(5l%lcjX7zhF+uTw_s?CXV^mM4&&oFSP;j~we?!_t#V zDG@&1S%erhEyMF^C!%Y6y5d!OmX|I4+9~Wmu>Lq4stFRHB^M~1d^TXn5l%{iAe?*_ z(&m2It5gaqoDBXPF7k7^<@FVSo5z;FjZZALWKWA1TV5|@*vB=mSL6&Z<;|cg_J*Az z_QNu$%qggcdL(zR97LB_pvf97KTE!d9+wU?CCo&yxd8WIy&d&OC$wvP@$N*8gzvY9 zoQTFXy?wQ;(}!6P7f)p5>-`$M}WVvG{>Q)y5HM+e@SRYLh zv~XZ?%?O4N=L@o-welZEepxP_#bM+UBg6=kSU-IjdGt-<0;~a^M4oZqtIz`T7;)pi z*O&%*5vb+30=D71{qWEwS1o9lJ2 z(pph*Z$w`AIt!%oVD~USv0#@yEncv@w^^*T{6b|GLY7)stkhbCd;Mm;fOoI;bSC&s zuAwi1xQ~464lNKSFaT@YcYAw*Ru+WmCsuDEs>z6rETbIFvq8$)j)qPm-XGVEp0f}U zEDXhAR&5*y?fdv6$u|!l;{&jxr7&O4@+Rjz0|p}JL9)k#|25YsBbQHt5mCGZFGjW) zFms`VS~h8q<&ukPUllixEi~2%p|W8EUR3)#KvZ&ZicX4X6S-Ex+ALZ?cMQM{HlVxM zt#@>Ic=vw5Z|03iy_YL1M%|ET3ofKmfU3+3xw?dePu_>N%YzjFej}{ZYj}^4c@L1f zhsKlU%`5VR)3Hie_R!R^YgUC7I2diTz|@kk9~0zBA7EaTl4i%cxD7X53$dwFQckv ztvF|_c3HtB&+o~CMK4IZf*Lo9hc@}RoIRZ&fmdkRHWRyDv@URu(IB`E~Z0j{i)En7c%6uNVX!5 zSR`1Bi-g9t)~JPoTS|5Bd)yxz0U84Em*&Rg%~Y&iL9PwTA2NtxHoEUu#Zy4o^JrQhhji9a&|rQT5;t z7LG^h$wFxD#_>;q$E&genn@nECNCY!$Q0;zj>Q*|p?Qr_LxP}MpD9)+*tNGi8CJMe z*`@HLW8qr8IajQv3TNG9Hcmw3qhoO8T5-moF3ueBM(66S5T?^+aB>Y;sCw06D;%3* z{$qT6e3*~PVi}F6gfrz$@>H5vZ-;o%{AFIbc+^(`5{EUCZlV!}#Q4CQV?-G3liE@? zRqLd-Sha;aaxi>tuv)oOPHC$(0#<8*rdpq8q0lzYyA6z{VacUOyri4=5G(iOXh`}1 z(-VsO=7^PhzYB!&gwreWi6xxa)8ZwZZitNEO^ojPwvc%MzJ)RnufS-BrD*FLF}5 zeQ1gt@2bAIFPbv$s!md-`W%reTade|T?{W~$9}4M&$nR&DbqG0ZSgOuSEIp?w0Cj7 zODu*=r@wB_)yyLObt6`q2zKK?Ed3QM1u`XV(cL%CHNtru%(b!-UgY)mI5Na!7;GeJ zLLwM}X^F33m4-{S!#eF%bXO9+na=40^=PnBM|5fekqiY=?^P^tPp;+`*(}k{m<0JX zs1Jtp0*wq?25WbJEcczsYUN^6*ta_44U)8`eZ$v!_+*-0C;Rq50tB6~N|KYZZ!|@Y z_U#pY(X=T}_Gz6&^HoU_T@0P<&zPdCGFi~=Bocc}fjvD-O6sVo$5J z-=UUB?4h~pHZ&87J%Iodd)Gm&9Em+?sN?7$e`4=*0;2>$H@`^iDZEmC$;4jt)x>HZ zw6Fc7-%_{CtCSWq*8OF%5=M2(y9@S%`pgy7>LQ&LFEz*6ZfSP^e`hmJrThcvN$yli zT2Ekw%tED7eqfC?OC0!wb-DtK^yVLOay}1|=!{bL8)i7E=GaiZ> zBGfDZ7S$cLOIMQ$g~Q4}W2op(F9xHcS+N#xRvMQWjB3L80|#7UFsjvhr9&+-7{&h5 zEw~kfQGoylqc4P7IR>NBQp7el|6uf|0;2>$H@_H+D!fvD$-(H|KU|htWumVc;T@1~ zl}Dqo%~RXv_lLzWxs)Vc{UFzn?5&44U`3XB+~(oq?fg^`o{Q{#EZi#K>e7P3xXU{b zuOms)JsN+(zk(7Vy^EW%4@Of-6Q~)!?oji3KY$3A+q# zN4Jg`C`M$5x)xg<1D-l-Kiylqhjc_w_y<8R`1IipOzBRjn5dALMo zL@e$}iZNM=EzUu`$|wHeYj3>mhP``kd(z&UcZ>Bsnnq5Jcrl(EIW;jJ)&9K+OSsXK zo!>v%`Ju<}>Q|qX?G%RWWasZ@`xmkE$BE0Kb9+c!relg73zxXuXrWLaUnUZlY1%I9 z7Tbx$r5wS_@XCx+yC*I$0Ydp2uuJiYB`(?1;w3I`ic@)Fn{7{-%d2x|E(NweQkNIm z@-K>stlZ^kNrvOmV(Nn$Ej|Inr4NS8xSGyhULV(uj-KG`W!%Vpr7sl&!9L_=0O`wO z+$N8q^yNVyDo6U#+APW<1lIt#!KN>*i@2nW`98tt08%r5nbvoiz&w}iKpn`+u`{To z%wa&gONh-SgL!?9#N(kuf{Q_>Fn?VTD~+|8!h{L`7^k!V@ljl=gmjQA6hbRiFv4g*5JhHMd5mO-50;X#88YEHYte`BJ-eS{-}bJdWNn0RyKjdB~g~W ze=TLQrabXV=F8-MHG?I&S51F88Shg&DIj(ZjA)$4l7Qm0z!>z+-0PXree3 zMIpAqrv(JsL8b-%SP(0Xb!xN{bs={x^R|}7Km#}ZY&*b*)LHZ zHlw5coDuGn7Qmvq2Y2aoQnPS?|DOyM-Tg&cK(s8@;w?+#5@`WV9^Z1nCDH;~t*<-O z5@`YKINd^CkrogLFfH)cP%B4TK$?u${^m~$T$f8JL|Q=MmGVoa1>UmQX#vqAh_rwx z(LF7o7tEO!5LNpdb?#3Z*(p!Fl4${NbmtY`ksIUHGdjgPVOh)09q>`Cc+tP%k{4D{ zn2oznij5@;L>oQ!#TKU!ZijxHJB5($#|xN3IAEbrcRGm_LYg+$6oL$)rByV2L-!Oy z6A0xggcd%r6asr%ycEJULLnFUUO0iJkBotSTTcuvaaG4a?}dv%M0(KC{SvTFqDCU% zuwT-FXef6sl(&rc!o4U)aANO;i`$#8P-D6u-eE z2!^A7AV#0K7Y@e?dG3XKFG(7CMaQF16-J3&?``=j5=F)p9UQp*Sm*e*Hr2vkq;Y6R zOj9R2KOVv=_C16PmHn_2#h7}%*PaxdI>NfsO=VQ(yPRU7JswrAo$NMyR>$kkpt zvVk_4{a(0ro@t&;W8=_drt{L-wwoM#7c}mG6IQ@@r}XrElHyzhDB6SPfiwr3sk3<+ zo}jw|I4~1*vB490x!_7DBG=?dn+2Bp0ikm)$+umoul9T4j^TEF>9ME%nPMA`^M=DG z*;d1*UxeG9L#g`0f34KO5QTZZs5Mwal2g^}!O@xsXKh3NP_UhctUqgikG&2WCK6FFZ2XQ1jM zqSP;IO2ACSl?!kM+%r&rw3CLrhY}AYYM2H;Ba}ciuF>UujNrsx0heg|WQ@5k@tsfj zCEhA?^}8__`QuI10>SSfm}I<}^9r~>B1s`#^dnH!CBA$Qi6TQOIB-`$5cnHx^pI^;cntAvz-Y!0300Pn#6Lqp8A(WO1gCgZ zNW>+QNL=qGIue`Mh_7uR%cySkuL7-fUp$kMfD`Ss09U{n5_qg!0e9lq<(&n(=(_XMQDxUF;L>`tfLFk6u~10F zy=mH9uYi-*N}3I2deLf&j462q+$507gWb#Ui3PjtY4L*H>!hON4&$C;-OF8fV(FOr z2z57Wn@CKM3m@s;24c$>>E4y7k?=M4JSw7bjdX8~5k$uqVzbv={ixp@7w6DsHhnVC zU7L~&Opco`l|Qb;EaVS!75hOM!SosC-ba!`hPlU}s!N#rAc-O)%;mtH!rWKT@U`eB zS4yz2Fqe~W8cd6gW)lNeiK?o>O~Bt^!`w`Z!lT?r0ihY?nu;ug+;>4a8RSY`1P^+% zkcUfN2CzGU+0r4$R z)g>Uln?#Wj5Od)6fHTsSnD>Ckkx^~8L)E@-SFpw|_=jifji0gCqEbbvb8AybCe z;RB|>0CeVnNjH0Pxb#sdC5KDWazVVmNwk2=aA}9Wx`5r^Ip!TJS7yo{TU>xPyW}Y& zT%DI@IDbXeH9jc_ltE9zm1(9#8%AAMs?W6Gq~zLR9M1I;An-YWzf!HUK-U|71u!JT zTZ`Vw;879;gQYJcZSKnmq@ZOxDt%SKqw_L?9|3NjS^Y6Su{e@FEnXaXlTy4sRuVXG zXiZ}4Bc#+%{YisO1eOc1o?z_-dBe+1i5iJMX%8w)PDyZrbU_OaNkr7~W;P#j) zv9RD*X!x2($Q3O0m2u_dn>N*A##zSIDp6H6SO)wJHhds03=blX0a7!FNGr4qC5}T` z8A?cag$avpTQK8# zziPJp8P^x*Vq0WfB}ND$eiQ3kGOpiS+>C3i4hxZVje&C;uT~rUVlmD84k+O7fes`? z(5@NQv<@v`hV{)B3N6-E2FYpKTr;fFgh?x+tg;G{BXZ5bhk#Tb8Gjg`SY*te7B4cs zQ43jmUUuMobyWgcA91mMnNJGp!iU9Q0r}<2x_&)TBjJPWVKJg{4U4}RBZ%%>q_7wj zC><4_YLgEH#i{qoVgGx~Oa7QxwL|bz1k-0sJba-kX;W@wOuPcBy2QkfBT;0;#2mO& zOnfm7UkiUuTujW#H%+L;YO|QwDp6H6_$-KdurV=ZU3f@*3$&>j5~tKzM#N7?!(Tropq=klt{?R8cXEk<5!LxUmm9zi&?WBw5NU@opjh%7Ne zi2O9HZwZlq_6rdrlS>nRk((e6pO1ikJ}myNeDlX+n)8Rlir?U4 z2qqN{!&Deog}|Koo{|JDnbJ17|1C)h84iB|s=9>3-y%_Dr~n6^5e_4Zsx|+OhTn5I zj97Y|Nay6Mm=-e%7AwrcVXH({)!;S2-(bUGoAuyf@EI2+!{AtbW%zq06p`Vt)IbQ^ zUMHmB68>JX&kx(pn%64Vo3JKVnB3xYy&dwn7Z*o{W#8jVYxBazxG--y_pEM4{io`g!`YB@Q+1C9tB88hy|+aISqrqo%2I1t@6VlB_*o+`V;lNV;{H*D_b4dqz+ zc7DT5Tv`N7CoSDcOBecg!u`Sf;fCPM8-cM8pR{z{ZVEnW>0nDjY+>Yezl(0^;sndtTatKwg9XjQd_z1pRSpro#i%B!?%5W)@%BAeBn%IKwS9w@E3y2^dWNhW2YWU)JP1f?epP?hSErVC&2S!1kte*ITkls zKAG%WV|)dBoU*B>j*@)taBB;cGdZ6E_FW$yb>>D(PpHsHz$~6Zji!(l5)J@WkJb0G~PkZK$zK z{{0w=$>g8ZL`eQUTS&tt`FAePeY0+(tqCnwtEPMJQc5Yp&jf1esS^`6-&s3p2}ABpLH{XzxdijJ?W-jpQxqT z5(x_a#mJiJ84sBUQ?$cSw&~B+L!W2u0G#bxCcv)gn0yJBxHOM{wPeb;=2(WWl$nT= zB8tWLm=jSvjMxnQEB6p8-9KR-exZgD7g;DIh7oDnJSu3@h7r;)NaL&=oa++XYk^cg zjM#-wY#70w7H=4_i+9bQSJyh9VW9BzF^JH2lj;B$ehl$c5Lmu3#65``3D0I9Lm(PT z9rZE9z8JxYJ;^|C_~Z~`SpUdJ5c(?>>9k_v^A8{t<3SU_^f`cdfFy++Ks*Pkx(py5 zCQ)PzAUJTR0mN%)_*&d};sy|$eA7r;>@r(5V3nw<8r%;24R!#LVM+M-;X{DV96#tv zEQb#thEj6)AoUQ!;X8yZT!s%9tKkE;!15d=c9>Uc9N}7Ab+f+`#7Li&FhZJQXakC) zCq5&A+~eYW98Y{qHDLbn#Fuh0EXETOBaA1$i1jVwiA$C`jwfUcE~i+uImrKcLydSa zgEJkru|+Ra`&qDzAlPXILQI}EFMOg0HLTz2C!k%vqGgrRPCzaeb&f_G(5QRt95b<6I=-Fl%LRwHd{+oN>HYJ2Na-` zBwEp`x0^E%H{2O*3Y*1Rt6B^z^;!W|u^$E^TL5G<8e$`o&K?NpWEsB%Kif$5l4$)* zy9wll1p#Ysv{t^UR%(7{Z?vlAhasHD(}Grj!+F|arP>;wZPsg{Un_N@6;CZz+kUI_ zaEDJ1M=N0AXQLf<_LiTzH(GtzuldblSZ@NP)um$S!xF8z2K-pltk**z)t{{#??g)` zV0A7hp#XS~K!IWLv)!!1uXSO4rZ8QcIf7q-lp&zGIp2v+p9L_m&d%39z9?GPf~JQ< z3JS+(>&>~&i=y>-rceWB3+LvbVhaFn;cxt^&)yV{Rl;(CDIzcf_(TQNO!i`J({I26 z*Fx1l>Q_5^mPW(nVm1Cb6m9U2SD+PPQAxWc3k^j>C4aho7~rixR&3Uw6`=%N*)#`l zR)aWZd}ImftJI*)3ZN(T5|rvhtJ~r1_N%wID)4-c`LW3{6x8o;uOGYO!{> zT|DeVi?nQps?qY>rF!A`94gdl(DUjoxSmz3 z$+QIe?9ocA!d3RvLaEX!PQ$Xen97y1t3bC~3(T~jbFh8~K-Y<41vDY8&cRM}CQAcX zYAv|%2>YgALue%s`I=_&Sp0L(l4t|=H_)8gVNk?sWv&u-Za-}#THo*+%7`E{Yw^u= z5opJ*5k0OPhK<(L#00!_cstFYeJ}fq}>1%>w&tk5`yr!8F&!1r#@4u&4LaHyKp)h3b3+? zVwE}6-Dk*@ycQAB@hJfl@~b{#>zAEfh;7w7ZPz4G0+} z-tw!n+@?JkE%R$fJA2#H(JF3Dp!_OqhkS&x;U9aM&(Y7PI~iO%~e_~&bD z;LkVk&u7-cpTEIBuU-d#UXOq3>)}s`e_n7Z{P_+1^XO*y^FI9Z$m8J8Tk+4av*6GD z_~$KW!=Ja|pUvmMpL6ifHRr;g>+w$kE<6uP_y=zYWLKxM+kOJP5{Mm$9^k3z?3}*< zPa|YU_5^sY58J35;BIENMrrIu{MLCR4VYq~zP&f^Y8r#$3{}1mIJgxu$ literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file/file_uploader/file_uploader.doctree b/mddocs/doctrees/file/file_uploader/file_uploader.doctree new file mode 100644 index 0000000000000000000000000000000000000000..3b56abafe5b25539b9fcaa040988d556f9ef2228 GIT binary patch literal 58592 zcmeHw3y@q_d8S61XGLnA z>3dtF2{r+O!Af0{THGw;VF&@1M*-PQD0X4DHiSyp6dSTj!Y+?xE0sr)K!x2(c8h`~ zyV>tQk9*F&eedbV%-Eu0RjH@@-t+j+fBx6`&;OtE){*!Am%rM={)M~zhFhs!D%j<6 ztzLHgb~s+Hms%@MEoeW}o_bsR$#y=RXxgiOz11u^?Qjc9l-x?S+;nQ~dAyvY@ zGX}6MJHp`B%f0>i9|;34%-KQEtSq(y%qz{~wo2K@oP}ehs_pxDi0>mcdxd`+FSi=% z$D&=j04%UF+w5x4cK3zbTFpv3yltQfYKOU!T`O10Hisr0y1VU8`>k*+u$QUZwtICr zDyeM8T}?kIHk$QneffAh9IjSsPW$B6aI|4#S^&)6)}rlRk3qRF#f(|_zaRf!j{gTS zs8K-9V7*@?>%J!KAQ1VgHn+u_bwEe7Yk=zE0vcJIOe+3-~$>=kF$ z$L!n9*v^kXP~mKS#)!Jvl$=az7HU@x|+ zcC%QjSBrkbDQSr20A~}*Fe@MKe*D~y>5qlv|mLGF^27P2Zpw6$>ASiRpLCCyQf;Mulm+ny=4V; zi%+mcGw#!G+iE&1^}w+>mszWoz_kbgm8FVPX639>y;kGXZh>1Zup3x2aGL%h_Pgl> zt!52ht@@&ea=Z=EQPc5T)xZ)1EX08;G+VX&c?Qe;R4(_h6S#HsS#Jt3E45_>sese1 z`$4jqixp>;agLuyGp}|64tKTU2UewKLC#y%ddaR98+PCpBpHq*5hw_Qm~**VZ#6n- zhKPI&6xAm|jibtH1?r}KGDjr)F3`|BK&DS~RqBp(qe!iRpVb>6mRqcs*HQu9)~v6l zz8Hb9YH;@SDRTN<@$g5)sMR>5cS%X1`O3Wl^pJE*geHGr) zTLXm{P6-#k4K1`O67%E`u>2YD)4V&XChs%)mB96`rDDupO|$&+?(cUOx86RqVdCFN;<{hc1**3n^?H}7?l;_Ty5BnGemlH< z0C~uUz3DR1+s#nn&W96`k)iNuWen#7QwM@i#7&^HFn6EuLlano5}S<{1w9aUdQpH88(xcNONMNpmaO`e?j z@S2ziQs8RL@g^gs^d^Z`(1wXWvLw-)QEw1QbbJs=q>X;_1yY4!1T(NTKcyj?9``4S z-c2>QBw7-cO_-LFsKoovmP9X4T{|u;sMX`b0$)GoF^a2FOKSuBP-0+iq!Gi(c%xFT zP=S;XN`L2D-ok6EISS={(yj>cPyLR7ShUG=vkOhSSM!} z3WsSi4j&**@Y_-P=acY-S7{y-I00(Qe9>{GVQ^Vtc{{>f$og`V`N=XDBHr?H))@$zQsi18GtKRo$;-lelvkapf#BGrSAJTkFbJknA-_0y-$UE@sJw0FM4#y;_Fi8*@;@HtTf2OPkt@# z@;Sr4KLYR1AKjbqYXP$Jifq=BzY&?kZWi-XPDZAN9J_={XTa zVvFa~yc%H8aVMVUD@#S^68v2LvAgK?1N8k~$F(n3>dnFfXU?2HGiTl8G@JG2O~(&e zWv2?qU6Bp-*a^GpJBO^*dh-H&dB=`SNj<$K+{Gr*uGQ*+opAStN6+7NAb@8Q?J{bl zcJGpt4qq!6WX_6;ST`L%4yDUH#Pi_@_b4cfY;0>>^y>t~+tN5F9I~XG#5dK*o72J?h>hV7$Ku$$Ec-58{OpUTG-mE__>t2bd8ubKk^$IPqTvxLC;l z4z`D*v%b_LQ^%+S9^;ckOyKO`bA~-T+Bng+sBY>_RtUf7eAsh&H3L%-N>UeY;daP_ z8OTfqTDX%SWt6~v$Gm27jNg>RxOb4A$RSU#At%QhcjuXXeBLX&Kt4{8bFjy$w{8&M zX@+<≧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 EZ6gZ*ovLl#zy6pkz?Zoi`kx;p6zb+ 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file/index.doctree b/mddocs/doctrees/file/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..af38298181836de2e90856cebd9d992ec43b784e GIT binary patch literal 3893 zcmc&%ZI2vB5x%$G+uhsSd&xOYz_wOa5`v}Mbqo>cgg`(PflizQiSo@F_005cw{K>8 z=4bZ?1&u_=EY-a5&#*WkeJe9FY7E>p zRcx%Z5UL_d`9v0y9vAjR%l&gS8h&G{vQ*keLaA%>%~0lS$}cX4!rIbYjK?ycj*O5z zOU+2Cam>;wAAk7d@p-ckk74I|oK?Ix#NUNwddlt6gethrMw+wK3JyPmAzq9hk3RXy z=+kl7K9)twXCqb zNM*`uZ`}5;ym(|Pw{O{$j`iYQ*Pf%S{|jwwRBDx}=`X$8%Vfd*<&JBY3~7Ngr&Y|v zJzPpWK*omnJ;d(={64~^+VDL1`m?#O|0NQ9B}U>{JQ4l3o^!_LN0DAnjKyQ|$>k38 zcU?DH5J>D+G0@bmVYv#C0q`G9WX8|6Qud<8(sy2`h3m-FyY6B~@b5P`vqfi$U*k7+ zj{xYLj}5ZU^w{R*INW+nz#r4r2I>f{4~}&bm6xh2(iPW4J8)nNc~Obge3b#s+IRUW zp19uf7{Cmf(lUVS<16=rutfhLKsQCESju(nk^j5Da_=qItMWfq?<`ky^buLuOy<%q*SPD;BFQQ$P+9GyNX0}5@myrwb#gXKd1-~e+y-G-8A_Pze-7p0 zoIh@FnyJ{oacv>f6b12(J8XLLzPMRp6_9_s2cM&WgSkkEvz zuYs&DkmX%u`O7sVK4k=Ychn^5{6@2~o4PEutgmxQu#kFuie0aGGzo}QVTXP_`_PlGae+-jkF?~ z0}$sm8=8ua;AkGYYQ|r11a-&t1j={Rytdp4pGm~hGFzFYw3YI>n&P;@4buf;2@Ukk z9Fi^)mvFig`6mlRE<$TmDYO8!%1+LIer_b5@2xLJTx+ExfwG?gnyBu$TVjFQI{qtG zTEU8U_cvBHOo)HEL2Y#Dza5N$VLl~vJF*F#Q z$cjKc7(Hy%LIVm7K2%8@Vz>_U8JV$yg($RtZ{KLmTMLY+3 zl5ChK$BfMU7J(Ao`!WpOuW18=9Sl6-`6v8~{FFWo&0GUS%W-h8=WJ*HF z8+UAO3(IDakW-Q22k`D?ig4dIIm7Jw=L(cI2-G{_y?%`{m9V;}xciA<@CS*n$%ZJ0 z5MuXrRpJPmpF_z4{ufl3EW<6uHl1l`Y^ARL?eq z3+?Wj#qRvnu<3NoK1#2)G*#T$wAHL&Oh7?NFqi=~Xg7(8^`-S~G% zfF{TdYd{Pi5{JV)`*0}D5WQ}=AXGYnuk4bLDwS07 z`?`B(x@Tv4cTci$RirBI?e_HJdw*Ydf8BF_Olvh#Q+A564LXFa#wDw~~P18S5lyH;tLjm{z-PEmc|u6YH~!EUd; z?D@8jLbp+@m{q%B)7Pj!Yb5w^pE-V<9?>D`L`FXp( zQt&L>tW~^%?anWkDl6vvjR*G4iFWwPTFx!k+GftczXiY4S~2~&3%6nVwL;4*ReZ|? zoU9|WF~6^{|FwnN=2`Q3yHPP$3zlE61y*NM zspfaAox!Gd%kBiXt&2gOAXhFmDt4vBp$Udw-?3)Ab};6bR%qCcb!9LbO4;O{Ezd7D zTW-x=In)V;Yj(rz9N8F*HcMa&fZ5SrE?IkklyxmQX5jx`{C^Gp-vp#a0Xc*9_7K(q zC85){3f4C3fHj#q&dyRe6xGXF^VU9V|B;QTzbTj~MhawYYA-{YM!8tZB60=fzgV?v z=3J#ZNAhX57PvrV;xq!B>`Et?QkyYI@5bmOG`e*c{?7;ZLA2}Uya)c5TJwIrInVH) zCl#2d_Qgsy{9EM1Cw>+{{|2-9PU$Q;USAsK@p zwQMH)c}Sk!SOF%%8#sU*d?U{^#jqO&7a*ctfj&S$LAb=vX(&j)bqn%X9c%Qc-zgD- zR*WF02hbaaaV~dZm&cPSdQ$XkXp!G;$;>!eAKI`%9wR?9Yn0tagL8Djc!EQ^T=L8U zeK3(E)Z$8oyHU>jxF>2kYgFAfUNpf1MojT#S+GYQtwWhfk z)pOZHFB*@Sm|$H&)KS>NJN3|yOh{3hZ<*zG%d^jz2MMU@X(sQ)l&y>@rAEW`O9|}> z4xGC41|RycVBE~a>*hnV#+@M(i(a!-HW!SjigELyLojPh;TD4t)}j;Z&3k0Mk)?rm zBuPv#WN>v{q;GK#ACP-cjcPD-wo{TVD@o`3Af4}NFPqt#HL+T+EigO~EHqoDXSU9m z%uu}>ZO`-Zf~PW!6q8A2juJL0@w3oeTOfl5C2p)3ZnbX}RruS10U2TjWRkNCfe?8k z$?s2dElHCYDw9k>NZ$ck#C&)*n-BS5GxU27q-3xKTL@Gp3GBBiv&i}b>yyN$PsOCR zo{5m0U`Qr0oRnodL_#f$;ryZw&T)d1136AZWyANUsqpP!@WIQ22fo>=*>FQKD^3<+ z&@S7s(CVg1$Rr1|dT0K~t8Oh>&FI$3w zA-CJ&`~yVg1zl7S%KW!c@#|3L@9>EWVs^#drMa>QU8YmyMvF=9$i?0;)D5{P){LL) z@M?79^&bHD-%8IiHsZj}9Ym(Lgy;UOrw5L40_g+Ay8ZFUXn3{w+6?)xJ$rMm`a zm$rNBcT;@>Vf3@8=bf2EhqIb&nwSzaR*I_#ZY9AHq1$4R-a4YVGpz?uo)gfVuHx29 zcB2z)Y_4GzgaRKi0_H&6sj_LGh}DDJxzVSnXrR!j6KqDmUehewRlD4|5KO>L)!j;) zg3Elro`n47+5!jbD6F}2Gvk_b1O6lx!P$VqE=~y9%J$OPI&*Cp+!5=#<2Ru(F^fle z{ZKc;pD~w?Yq^vOzw>5>tuul@kvwH6!Xx@2w&QVq^f{6w%19&bBG2=m+MFIsBgIz zL|u<)-=a)C^mB_YIo~Wb4uxGQi#hUf;FE2avnBpxj1GNC{HOIp=p>$x(p%!6(+-g# z@uKrs;&*3|nbS92=C>u=NIvhIdZ^Yb?FC8No4x0u>y`B98Han*{-5+%rn+8-cb*WR zbD^vbos+2eQ*Hl_PJH@BVZ%n95@d*vKq1u<4$~xC!eL53K!1`DY;u>Kj$-)uI2uQ; zGi4kB)j0K1_}=0$tw%Lo9E2)ZO+djIH9mcWV zwET=7gLIYSohQU0-FJLR+rQR#^ozpRv_oWwgFvA#-|?b;fIhi=4SKnBu7ks`+LvDX z+globuE#W88iZ!`bRC;EX?=XU>*&`eY}a?LgWP!tSVw+O>oTs>j^Zq#BgFys_7faG zm5=@TiMz$}-9p-KLw|b^U2bGd^cgyl=y&WwUZ*EP)&hfbE86wxLJsSP(76ykN^e1b zNIOJ^%@Cc(F60V%EnMvLFw&*_kksliWvhCa`ZP0LQ*+H0qhpgj#%6VEDBCXe z%dYc!(xhv*=)9+0dY``Ybi33qxgb}>ZZyH^dmZN+MB3-xgE=EB6?F*cCy)uk z_SyjaW3>JwZ7gCF!I!hDtdTg4VuC2J(keAAX%Rel`mS2dJ?j~3Zrku(qlKMxgSLD; zquO%oMoH|W8?=vZoVD>GmYq#rPuly)A@v6J1ev{AgMPP6&Y3bayehDjToZ)+XVU3{@gCE#|xP-$d#CN5(MP|Ke+M^*agJ|4{Y$uoaTMWL6>H z5ha&IJ0ZN=&V-3d&_ZkLh-+mNj{2 z2G4^Lq^1HN#jX<>#hT1M#;I(k29prQhF61eLlzQW&S9E&z?qGP56PO-aUmGyyM>d2 ziX5(BO#H+)A|os1xiHAN7`s`62lS;Ha^3ji z<$|%aL@VJ-oCf#W$U;Nn#TpL;!bky$wj~xQU?574zQ=9XD#o%IHJ&w)8H}W<+L~ck zS&-30Rxpb&SWli&VLjuy@ACX=#>XdG?Z%RU^c>5r5cQ%}N{N8YHKc#FP8Ym))C#0E zULjs;Tk4HcA|Nq%h99GtnBGWZrNq!7r-<_dB>5eZKoc~p*WCtlX)MQ#=MVx8=^xj( z3Cj2jGu_h_oAPCm=b=<8+P+zr^e1@sWm29?pg0V1LTVU^S_%@?mj?+BfU_CaDGV)w zDRt<`7{|?x7SXCBSkq1}u zsCK5=PKKMZC=tB%C9$-ehzJL3kk(eICdsSw3BrW}@7}pOpGpu(_1XDgm?Z)6+?>(o z8cpcBz5XYulLOAj8Nt*Z^Y!FgfkGa~c z*=5`BJk1_cc&ujlu(_d>TK`MRYML}ogYwNTu`qdkw^pt z(x^X7jH*gm-uYuZ$HIe;vlwIe#PN-4x15UT^!uo*jVbA*3dyPSCF+%AB^1IRQ$d`g zwuy#e`$mxvUWkS8Y3Hj{Jr%QV*_na{5pBtg1rt#yB+(#AdA&j7GubfGrQW~AdRHkY z>nFkV4b+K6l3H;ys2^)hX+_5K3>(XN6n`Q&(0-|dIwr))fR4+!b6$p|_!2`R##Ef3 z6w(lwDOtq|>H_~9$3OpFQ zyh|0abL;Vp1dnq&;mEB=7HDxaa7Th5DrJQMVJWD++}v%9G-`WUjUqSKZ9eWnQ`vlw zH&xAt?8BU|09)p~oWI03>p5;u){_C)ZElFk`+Hy_xhj%}*e-0v{?a!kR;0P7ooU(_ zN`Csom|Ks~aFUbBrZ&N-n!4@4>{K6^siuxvaiQ0%ifZbJifN|q3o|npJB#dN26>UQ zN3NvG%uyIF#aP~T(YT0S6?3>X(Q7Cf8M&M12NDQj7|$^&P-g{HCQFu{+p1x_0^D9@ zeX54><~~;lI2~a4`)&|x#BV3yc4Si+jE9R4AsLfddB2xvu2(1#(9@W`7R*H`OJouD zA=NhI)!6m=p3)s6(nrmhNS_}FOVw=b1am*j5LQ8b;F4=uBnA^v*HV=Cf;) zhNgPAEk*qoGTb&ptlJM?39*aFKPFc2BmyMGL2s{Ak*2=6Nb6gvo{$uQTRD9z)f19! zO!I{HC0~ooT8HFnr3U)PFZJkR5XF^kQ3hc(XbKlC!AjiRGpc%@Y;hz>5pL{NXq1fa zscgM&>?*KoC+kynW4GxRh*Q^xS?V__ZeET5Z{X^#>u)K=4BSCTaWk+-*H$9vFDK}r z5_Ayza66-j+P=@Q{ysFlX#1eYxZ7|L7hlU@hOq5QWsgKBswvBhVt;#w52q5%v&lq& zitvF$r#B)Zd?0a*rx&kE4IxywygS1TgzA_1JdPGx10kiF)~i8%7wvWHNJI+Nv{Eas zd3vg-rj^_8&52V7+q^J7e=i zkNcM;VMs}UVnh1sehg`bYKQ8wJL`E2>Guq*9`Lx=mZ0a;o(#zC0s1?u4=rw8n6=i_UX!S6au-rsF86M(;gc)I!+(vgZ zVdH4D)n0DlaC@A(5{%!sTBIw0 z%1)_BlyR;@+i)`=yDX)e>iJ(ome9E8#KUslWz8*@YP?|0qwRZPnfj|7Q)6}@YH(2o z3yutdDAQ=$OGvKe^t-A5syDEzDOAbKd?Y0k+AB$DG+KB~tV%)q(nJWj`=X5UDV!kkO^dir)Lt^+3hJDSl2ONrn7@7Z; z)@Kjyou_o&dEcGq*)_=9**lx#7e`r(mr3MXnIz(>1L=PNa_VXB|D&rdkVNOwg_ReA z@mi^|(#GAA;@*!b%U-c+`0p2ST_oL%NK$-^v?Vgc^B9E-wBCFR**CHjC!eBBX+_Fz z`z~ZzhiRwC+Z2I!Xl62=#&*RcOa|Q02<=LfA)>d-0_S@FRh=yI^kY{<6+1K*PCm|b1|;JKD1-nIuY%@&G266kI9ojD*k!BjdK)#vOY6thCkNmXJ_vKN_{f1(hAJ>xJ!J{sUrIIB(Dv77f7mIz$AX*wA^AWFpfM=B=+K}$V<0W;IM);?GwM^Xym66A-S-qu|@VN(x0FmmK6HA`)k9vj?dcb2>2+y zj}}j9hsZcuEIN;m7VqsX^jyA`Ee>HV7K8DyxwP1B=mA^r`Qmse-BlHI5K>8YB}*)B zs@ns=y7+f0H2y=UBphzl;-KZ97)4%^jL3x)&6YIE-P%!(hiZKK|1zsmRyF!=JWb-)~gW8Xcx!sgiS;4JYU%! zT9nY}XwkSN9Qn9*2t7wsvz#tepVsz&NlunDb*64ynOYvaa(L_~Y>M1Sx`b{*_`d^_p{j_TQ6MXM>680sv-_(qj;* zQXJ#$*OEpCQ#s+#%e6{lt=Hnt0W#e>xgfD~3t=Ef{SwV$207mbA^FV{&R>Jp&fnle znL;>Ur;w=cnOcWmHu0SEO=`9+Ialg9-=a|-0C0hN8+*W}I=|j=zC#UnBpc?Z*=_r{ zjv~SX*u6kvz&3uIzeTff5)tl?C~J_v^cHr$liu?X9e-qjzESex*;w{X5jXeQUUGSF zPe{>AbFag)7=@%_uzMXs_&tbt9%#_|Lu_RQ(Ft_6Sw^rH=cmftACjSt%sTw0Pg)Rva$I5ziKZ3#L~LiJSGyDl2wG++k@G?BRUhKmJ@ zVV_qILQh1UhVzTArI{}eBoiVP=`YYInITGL@5|L^{1QLn}CG0*9l{oUi z;sI5sLLo?d6qDh<2Es#R*9b<^1tP*PpSO#k{3vGMBcHd4Sao>Rq6W!lz+nFuM6 zaq4GVqO-nONDz=R1VaT4TzYj-sYnSroQnEZB1cK0Jy#-qE0LolT9NAg3FDX^S&^e8 zwfbBitwfFz`H)^xE^?H_0HXlw_t8p(sy%(+*n{jIvncZ@`W?&l{R1&qc)!EcDBJtp zL#g=EH6(Htc{G#aH?$=3hvUmbm`pmo+}7*+8%aM(!sp!BSD#!VRmU>QTZZbiu~L;p zzd8`1g-cl$S|s17L1pW8DRjQ_5!R>bQg)@Xi1R>R4GQ*jpDBnr%InKV;+$V2VEH0m z#u%|EK9xXBRlPwHRG(xhs_kW0qd8^tel*o5sCJ(+qFJbNI5-2hM$-vo#_5p=@@Q$830(2FctTl*vetsWy5C@Oh>Wd{i4vt+-SUVx!p! z<37exZ8WudTOX~2jV4Psn2p|w0b(1iM@Zz6hO^Hw`P12o<%nnw8mEg2MT!ezrhOl9 z_Rq8_TJDHvy|GAL3z1S5JMw91o>6)prYkIGZ)u5Nj-Q9WzgFKuvWP!Uz^n!-6H(ux z%#N_Jqz-M1EUX5*9t*RH464A|>djq+{*5oQ@^Zu_%=@SE(Xh zl;@37`Ue){xJ;kCOs6oxDuYg3)gfuQ&H9w7IJ>huo0Habc_J0A*T5xp4LQG4#F*Es z(yUm68<}oTxN98F+tf)zUfkEouLD-^K@3L2y9_-5 z8;s!!GIoQu}9wcE%cR_;o5Qo=cJ+ZrpP2ZB`%yU;zCLj zFQhM!at9YY13^x>9&w@rt)tmDDX@cMRKZePTi zf|V8y6`+%RFygyDE~7oNA;^Vwa8LY^4Q(RMP%u?)w*a1B6nz~HCc_f;M!}=OxW^^I z>)E_Zx&fDl~2=CfHK16HP}%_ zH+YqSDLs5T*h2S~<1|Hal@1x=9@;5nx!TR5IE3M$!&mbXaeBim(wjkw>|S9YgYiUz z8ZP^01(Pk44qp~)<{1;@+!*An65fy~evSm&%~cyz^hT{p#H7=Gco{_NWhwW~=44iWXi@Qp2%c_Ob^X z?{o>aa>`4+jIdrh!8M5|i7uS4LL7rB-fs*;*m{rpAkjG3MmXTr0iFdC)qqn6hg-#A zLQuRyCkjoxPW%d7!0xsx#fnMqGsiG^?*-^E3JX-l`)NQCyAlcn-Yhd!YWr?6E($h6 zE%0heoRKL;A_>B>0vpoUYT&3Ay4lJ~n6MM~ zgL&9- z-}Dyd=TYnx_*}l=wpO4~yQn4u05yv^gbnuiV8=Mh=Wg70E9g1Zs+JSoQ%T(YO=0&? z-V%xIX|q+ZO~1;=B?pi z`f4#cX4(mcYxK^NV3NI9v?$&z`ZQi77r$t<$c_+0Za~^=$`)SdPzK|ZM$hREWxK+T9>TYd+cw=3C~cmkIR^Mff685j7cvRkWxO<)Kd&={@? zWaS?Oah~ct2vx#~cqk><;X!)%fRPoFi*fpY%43%zgr!n~smREYr{KH@>*=??5if~o zkt`WpRE)i9P_oy+TZiBc5wwI+N4VvV7CkKa$w=eC9X-o1#iCl)%nl)&IjK`6rseOvYuG7~%R)f;O z+bSO`%{7D~4I(<>DR+;xJ=QS)9t?V4sXWVs)BzLXLVeZa37OK<(;m}0Q>VirPo@LK zIE^DU;9@u?(UcDFJ-pv9_u)ceQ(|0do!(;-?* zKF&4r>hieBBZZuq?Jy=vL5Sx@N|NHK73JCdWlX{gM8WH9l2}`IY&LVrt@+@8N{}_3 zkfafhNa2lH|E*=eP`TOGWa^H!>@Cx*P`3NAR659{h{g0b)@;Q*rS@#YG&6#eOR$Gy+}ttysmeMx7oqL{tq<*%(TG4S}$g~ucIVD!eAm$`KTe**O{ z3U&4=F1+RaY=t7pM4m{TGsC8I_hSlGE*H%LzonX-(!6v*+$y^TM)oS`oZn>lIMFh6r?#ouA>X zk_`n|%unbn1Ei6WvdBll_%iS{=%r=fu}8|h{YDiq4+`f{Sai5@t~#`?V(e3MtR+cR zOk7@FIh#tVidx*zLX)_NeW~M9k-PY;n{Jp(SLuSXB96>%CGoXzWLk=9&C@Ab_F>(E zCsL%Ef&!-bgrF^|nhT~Wa-HScj@XltX-{cNCDB3xj%{C)HAynO?6@)nR{^}4HS0rb zb_y2)oO=OMo1TZJnPhnq`ljwUdZ3QZRlU!j8{?!a9zn+TcMSEj4L zpez7$-Nd0_@TY}@#?(uK7hJQT>Q^|g+)y?!ghf(6M8xztML0K1hoM^qW4eXWxdYYKTgwY8JI#T+0Dz|$ z;uGUSs1Y$qr#WUUgge6f2I_*jA!#UNgfT4xY;cULXCu=LMLNM95e^=3uOB|XGKs%vmu(;Dh1Z;iI z6)*6*A4FV{F?v_+<#xpraBdfu!Tgze(!uCnspk8wpJ(3k^_QoLN>5ChI%^T~y@Se! zJQc5~u!oLpfK56D3S*vdZ9h5qndxRUo0kuFnO!Ft6Zr0UVcx0c4v5ttyVOji10koN z(@m%89e`$_X@c}4$99}P|9B`^9{tQ%up&Ms$ zSX+}ZD1`CXp0&(v05S<`X4dsDP?l{rwCsV1|4@hOY(19-P$;c&mJ5`t0vFF7c! z5U3mey?6~`5t5=yncE>F@CS)6$c9w<5MuX5p5X}hx!{{kSp(3SAmQl*7$}Rvk*g9Y zu`6nRs>WHQV4|A|FDxK~E*62lm+*j5s&U565Ly=jO$d(zeA&37>%BfKM;Tx>o)iV` z%52i~xkU>yt)i5GyFeno3OKl-gJO8|xH(+?vT>!H9kX3!j+T~+G5RH#vGU1qtlTM+ zdfg4GZYXzc<*8+lh%(!5v5=jh8J)9l+3Vc2om5|DxP}{gzk9{Ly~^1g_k507=r#Ky zN8hY!F048|vlnvU!}Xq8;s{*n6_;9+6ndDTfrMl%;u{^CYqysywyRV9rlTeM zD8y2`+2!V{t#Spk2+B){ISv>?^9e;9BMrC%y36(-O8Gaq-PiVWPz4_ZARRto6e?SB zU|nSv#h6vQ>QaGkm9%b+tWl1F`0#@{GR4@!zQ6j^{Q8ebfONR)6{zUlbwpm7;qUgi z>Y?>vMyckD0i1PnE_?Xw!}YZ_^#k87X#daNhYnO9;NR+SwKz$cM%m@o&6a}($oi+z v<8#+}ijQ>^!}80i;mi&TduFE&kcawCIT?alQ};#KiCjx4tEZc?^YP%nWP(gR literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file_df/file_df_reader/options.doctree b/mddocs/doctrees/file_df/file_df_reader/options.doctree new file mode 100644 index 0000000000000000000000000000000000000000..fa40620e70d8a13a2c0639e007ec20eefb2354cf GIT binary patch literal 16058 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file_df/file_df_writer/index.doctree b/mddocs/doctrees/file_df/file_df_writer/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..aaa1736708c7efc64bd95ff444e591cdeec5e972 GIT binary patch literal 4147 zcmc&%`)?e#5!UNYcPHueu$%{K8waCK)Bw7(okv?CXpq)0S_nDi1*g9l3*Ij8NNabw zA-VPmje#~r3Jc(Gp^M~S>R;85+{Zm)1!#YXfP;32v%}%c_stCd-gtSmx8ncIuF4os zr+pGdsfehurWJ`WPiU&`_x3mcu+Qz0X-o1na~9%{7D~4I(<`DR+;xJ=QS)?)Q7&s65Mr)BzLXTz%8y37OE7lOEGLQzwG~PbPiE zIE^FK=VCA-(S#1}+`oHVeuqc@X`|VQXTgtC6Q(netJVR9o+5T zd$<4oz<)mAX+)=erjxj1+2&)*+Bh&BQydTnu7UukcNQG3bQ|99dA)Cc`IC6(OowPK z`6$=OtIOj$j}&rdHp7@G1tFdrDM^Z_R+MM6mk|li5e2WaPGW7@p;^x*x90u-D?!$D zLXt*2B84|*^&`vPQMuXFWa759>^0M@P`3S{R659{h{fa&)@;N)rS^2qG&6#(Y%+ppZ`kW^bay$&0&yX@ZS z8uZsqJE#f7*7Fh2)GS~*50N*3|6t5xdK`_9pGmH%JeES}lLA#&9;|_b@yMEMulUyc z{73lFaDK3l@f(;A0rG?n6p%pVKquM2M`GZ}FmOhKXk7gd3P(ypAOY69zo}@r#9!8Q zL^;CQ8X$VS$dZS!`ru*jD}SnrB^zJTsjDbvXL0#E>q`tg{%hg!klh=;GUjD&UBDkf z{fk1KeToBbct2aANHUS9(OjSlN7)KcCPFRgyr<>bRk3FG&5fT65NHG>O76*XG&Fav zP%gz~aP7bB(A>I0Rb(#C3Odwm^E8ZeS3Tom2lhAioSIfbrV-6Fv!|=zG4WsgB&ud( z5{r?&G)=~%2!;C6?3DIuK`YIZ5sF-7PghLGr%{PCzJP8A^?r6X|61+D{^=5s0P!yH zO(f#Ua<{cn0L5xuG+M&V(e3Ms3l2N zOk7@FIh#nTidx*zLX)_NeW~M9k-PY;nr@g&SLuSXA`Z=VCGoXzXj+PD&C>~5_I}-h z$5N!4f&!-bh@dU1nhT~Wa-HSc4%wrjX-{ZMCDB3xj%{C)HAynOY`HQ7R{^}KHLC+_ zwh9*l?0XJ{PWhV47;ihsb>Mo1TZJnPhnov)4#qH(3Qgx0U!a2`?!a9z9SfP*7pAMg zpez7$-Nd0_@K=R|#?(uKXI!(O>Q~sW{Ge=L2#chCh=}P^ig2!(4nwyJ%GZY3r&Eq7 zYNB&BlQtqc$|u;a`;5pGQG^D@ganc{QWjEoB5avOr7z8%I{<*E z7~&J-K&TNhNhdjGEQCA4`x@$kxh82SWP~v-18lI3t7k*g3`IJ|84(U1RcK&M(>lGqvC^%Bnpm-?9~Q#6l6Fijkq_CPzKXfXe!hX~mE zlq+7~bvKB(A|v#!+RM#~C*a&R4uknK^`wK*ol?#BTR+dd=Ibv{6_uWtGvIcwyeC<_?HeKfBOOqcaV0pk-E;|K6=#KPfzTO|ae`qw!q ztq`bd{=Ik&ViA&}OPQM?Bk%`_FUW>e`Vivl^E|^A?z-TcO<4obnIPfm7#Jvv!hx$2 zD6vaweyYY^q+p_(3C}GcgDw_V|UHRvugSfGD%+77N)Cn$a2ifxXO4+e!6BhGV#)_s19Phs&JZa?fX&gnWQ}qZ#KjNh$OK~x`|0Xq^ZP#|0n*{FSD>PI+YxzbhQHn8 zs)yFsL(GdyWYmYVZq8-*pM0>ovZ8+G?t=FJ^&b8$4_EV(lxdV*Zq;l!Xn?GL x89qF7ou|00qZpPqr-m~-DD0W7IzS%kTjgX3YE9gYuoJnKP*zVjW#^;*e*xt0PKf{j literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file_df/file_df_writer/options.doctree b/mddocs/doctrees/file_df/file_df_writer/options.doctree new file mode 100644 index 0000000000000000000000000000000000000000..52f8599ec6c761d4f9b82607ea1a076d8f4178f1 GIT binary patch literal 72939 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 YZj?wZm&vkDl*pX~mX|`W#$xXO0|%%yNdN!< literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file_df/file_formats/avro.doctree b/mddocs/doctrees/file_df/file_formats/avro.doctree new file mode 100644 index 0000000000000000000000000000000000000000..5cc900b46911bdbcf2dd3015753f7b5a115dbb81 GIT binary patch literal 115063 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file_df/file_formats/base.doctree b/mddocs/doctrees/file_df/file_formats/base.doctree new file mode 100644 index 0000000000000000000000000000000000000000..63cab204b0d019e02caa6dcbc8794231db4494e2 GIT binary patch literal 28172 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file_df/file_formats/csv.doctree b/mddocs/doctrees/file_df/file_formats/csv.doctree new file mode 100644 index 0000000000000000000000000000000000000000..899308f9cb8a3dacc6ced1cee9d38db98360102a GIT binary patch literal 224244 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file_df/file_formats/index.doctree b/mddocs/doctrees/file_df/file_formats/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..46819e43f20e19aa3ffddfa805fe628cb3f940d4 GIT binary patch literal 4513 zcmc&&TW=gm6}Dr~*yGFi5@#=vtVR*L7$namh84&{AYc`NEXE6gXoY08YPxIYRNS}i zs&>X=BUn%%BZ_NkcOQ7*4#s?qn zEVw_tB~uddxXXedP695CUJVjIk9e%i3-hJ-%~P|lTN!&IlRWde(F@@4NrXYhV{?kb zw&hnMl-*Lm%Q7EGB@~EB>;-%%VqqU^dDPG!cDtSTWS*u;raVfLGx=UeL~O*5jyj}N zDvx?S5skW%1P=q*6-jTvf)Ve%et7So{0@)q%Y#9f^R*8CjugvATwRJtVy?n&##x|< zLr*7&$Gv;q`(Nq4*>j)wL>%yOmsAw48Cp3t)Pli$$YCH1uABg+H?JsM@iv^^(|o`D z|6Age(`!trObl{`xY{@_i9jM|dc_Z!lpx}{5woaxS`G5_@@2sMGkC%AEU{1-+S5yU zCX9abe+rP%Yd(tu5wJoVz4$FdUzWLEQEX(dHT05hRw&!}MkbY)W=WWgzG?JwC}M7o zZ|G*q5Edx2n-3V>hEenyBG$q0E`DFY?+qB$gyv4yuT{GKmq;)TFM7NQ?r2OG(>iQe{U#4elSe3h$(qg!0B3x8n8$NMsG}i zD&1A^buek@t#ENS=Lig+H^#1r|yd6{!G47 z(_@c=SA4+=A?q*(qjAKL<=ubw_LuAo5McM5I$biY5>!(}9`dmn-Q zVZlys!`Nk~<*J`VX_Cjmlv@imX#to@e3kKOJ`f^B z*I8rfFaLpYye_BvbYfe@eZ#B5x?j|tP93_dWr$B#!yiI}vKAHTs$b>f6eY#0n`+<< zE<9KBpBnm<-je#|xhyDnU^oFoKDbn`QmVSN?i%#A-cuQiWymbvZ2K5xTo%2rp%TTy z0!Af|Lsf3Qx2W5Gp4ragl^v+BH!6xttG-^9TqzNcFe2^LBhN5PV#QH}bn_9z7$WOx zq?<{u(p;H7J?iV$h{rr*D#?I>mg|9vMJZm^vm~*d4(T*Dda-BpdZB`cd(XhIR=(z0 zh__9^*0a6Yo?j2o8*85hyK$%;(l5tDlJBGY^wj5`fF-R<4EzUq6rn&$q80dOXXL%p*({A@Ub6 zfNj1qdaLmKl$3^(V)UJvlNk~858W<=UMf(1di(0Z%$IuG8UWx)f`397Wb|gp;!%#7 z7VZx4egh>=-^{q5WC6zF6tKZHwx{%U(@)|dY=k>RP@#dEy3KNxc$Xx7!wvzPK^~=E zjYoLZ5d@%^#VoV}w;aV~gAM&9DV01A63-U%zTQUKC6as7Upj~&ThE0Q1zxwjKu9*g z0F4f&+-&k*!9fy&9Vld$$}!{Auv!DgtEP^u2%v#4#~U^erD;XC9B32t90)w&*&ocgj%`SPh3oL7V6$9-kX@ zQ@vW0667wBh%HPYH*a`x#2VTCdo8dHp{$*D%gG1be*xMx;spQz literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file_df/file_formats/json.doctree b/mddocs/doctrees/file_df/file_formats/json.doctree new file mode 100644 index 0000000000000000000000000000000000000000..b0e34b63210396f15718850c1d97178b07e697fd GIT binary patch literal 166599 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 Km$9XY^#2P=sk$5h literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file_df/file_formats/jsonline.doctree b/mddocs/doctrees/file_df/file_formats/jsonline.doctree new file mode 100644 index 0000000000000000000000000000000000000000..c23ff2b2390dbaeb3b3d2710b2ae68fcd07090e1 GIT binary patch literal 154252 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 Vf&yI96nclg0)|o%b$Bh3{lA;}N}K=y literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file_df/file_formats/orc.doctree b/mddocs/doctrees/file_df/file_formats/orc.doctree new file mode 100644 index 0000000000000000000000000000000000000000..4f5d06fba9c043aebd259afad99ba35f23cd9a4a GIT binary patch literal 30977 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file_df/file_formats/parquet.doctree b/mddocs/doctrees/file_df/file_formats/parquet.doctree new file mode 100644 index 0000000000000000000000000000000000000000..393242cb0b2e970d3c304c851ae38b7ee4003f83 GIT binary patch literal 32899 zcmeHQdypJQdDlt0r%q=*ZDh-4Bw_9(bi0zkF+OYs{Gb5gld!=E#IZNGJ9jgq-JSK$ ztfcdU7?)+FN+(cZA%7%1LKTlt6oKRqDugnxRGN5g{ri-JJbEW`s=U1?*97gCx+j3;fW3WAG8P zgoi=BRB@KQx<_AQcIssur4#U5Q)=baFsW zy?CT>^o51nioAN!t5=*01vjcyC))1lLfah&!_Kk;2Enir0*Kz4B3P9j=5*`jp8V-^ z!ii%Ob`&+erB;M?rFPupRYJ5G8!cDuFvLTAAFkUq@olWqY9v3F?DAPiftT52SEIJO zi*0H(y*9gTT@BP`6J@(z@hY~Uh7J8<+dUMv*l1+05ZbnTJsVM4HhE_=j7p7WPz_d& zx7p^ZS9jVcHnNe1jb;HcJ6cP&yBAEkx1hx={NIoN&%^)cgQ*cv&S||}(b}gbv|Db$ z-R2&1Cv)-avK1;SH{llDBks`?8&Q4}8!yEj$lcUhf;No^wbWJQIne(SczL+xM6=6Y z)tOxmnl(F`6I#`gv0*UgRoZN4PF+s(-JoNb=x|?#|3!8ul)UB?Lui8CEJn3PkxQaT zouNpGOO<8uR}c^uCFBAcpv}o24V`kP|GFd8QD(r~FgkiQ)k{WC3AR-K*!)`O0@)H4 zzqr`uW(8E97F5o=N9S|pEZdWW{1A#iD2ltMz~pAowt)@v&Z|jQP}W&9Mxi}Tk1PBP z)Xl4}fO>XV(rnG}o?&@)ODGIUs30U$kb*hbX8St6EX2%@%tvjT2zL|01)m^$Gc0X+ z4b?MvGevJop@%LJwVElx{+@(AbLg;Db71HmEW1w4p0k#MplUs4&4ViIF8=4RRSs&6 zrW1y+yK`0;HSzu}(P{(y7GCW|PSdWQnQqlpDW@`h*qRROcB8RM-&P*+8uYjNNI-v< znn6_c#Df>fKNu3|+aqCAnO?|ZKXmBqd6?qxAXft7>pIStU9Sg`olz!s?9819A}Gn6 zHO<98ef+R>r;u%rZ+!AakSlivc4w3(bX|zxp^JggdH7NU^3ojN9+;h8_ z&6r!RkYt5RS=HL2fl&P+^(^TC-#0NB;rYZvO+haN98%BS(5xn~wxt5VQ`d$oqi#Arp(w=NMa184!r3>(G zG>wsC`2yQawKP5tnhDW8LGt7)LCyB+ZMLzo3NHi?Fc4zUmqQ14_N9j=F|jQu6gHf) zx9pYMm)STBZ7ry@$W_?W(LR!GU!4=<(L79+Kh0(9AHW|7p{sabYUV0eyb`!SjJmOq zhaOX!iEpt!`twHk{&u|1wxCpxo>_R45kCZk7Lt}Hr$*z!HzVlK^8W3prnUU0 zV99T%+nwVvw7Y+tK$sdhJ^uR}P(CY8S9S5Ox^U6VXRa=Aro$pPum7y@`kpYn&Wn~d;{Ls+jY95kXZ zvyj`mqf)7~7QWq#qsJCr&9?AVdV=4+qQ=g&#~EczhR$e=oG>!dOM7f!H^k0vRV+-D z#DrFJ1ld%2twPa~eVo{~Ge*MLP?#PR$ojv5svPrkwehgRICNO!-ZvuYh5N2!>IEYT zvo^jFbz^T}Os(!HPLcmIB_Y;7|Z)1c66C;osDMIT}t5ldl8XzFJo zvQHQ>4N7Fsn6V5(WM4I+=tE?~eAQ4%O#Vs)_6;MRK?&@i%vc5?upbyv^dYb@wZ5uk zmOMzrwt16jWF3^)c9^jYLTr{1MIT}#&3e(al9~LK2&`blGbn-GX~r@LfxXIzq7Q+M zk-%u#Udb$ZkcjQH5!;}|wrs{S2(d**6n%(oMC@@Wp{bvV$euJ}>R)7HbHl~j-h$?A zyu*xT5F&f85rtV~y(~a|%m{xV{?ez7D9kM6dhgklhcBBEtk-oHXs*a%Pp?^Yo4$9S zSpS`w)7<48xi;W-U4{CN8F=n0lmgz>Y5s{BxQTNA6PQ%RPP1_hYI3u2ME(o_rJUui z3r~{2bUXI8_B5t8T)CB+(d57JkldYFVDvSyTDKa>ck^3dA1bBR^GC!!Z)_ap&jig2 zjc7V+sle2;EX)Pjo*GsIy%Mc4Qq+ammcvi%%hAf8`&BIa$!%D*xu`)rZ&QE?!e_7{ zgfJG`9K-@Lf+E_fok}*+!yW-&mvKkf*mA2{<(o_HGi;(11!W>E3funcNn+1DjrD2% zRF)ws--0+3zV!)eAEkXBG{pucc5=%>J=gkU=TLXvEzYB+qR+H_Pm2RKVJKd! zEqVkJ`EQPS@|W;6wn-1)ZHG<;Tm9DJgJ{>YacjIB%@|sDs(>ei8#K=rYy?lZP9bQn zh=`vmMPEh5@Zst#0B4RCq$$4 zAHxo5f@)zNY(}eJGGtD^wxj~^hNsurNIL($L@avN-E0Dzqarx6(mpW+Rh4`!u&rsm zNk)OJl4!;?6S|jlHvcJVI|OSpI6d|OcMKPY(LN1%14a*5aZH z-CA6<5N71mDJm*a>*w_XMANBHWpoNraHDeRQn#;@I$ou&V~P|F-sdy$I_oG2=d)D$ zm`_ANcNW+doux$LSt9$9_()3P6ufoLX_Cw^zF|k)spxFp@Ult;_6QMkvXFJ8a7W>Y z5O3OanTw`8FJn<~-SK@Qfo~ecI^#c!_iPw(&)_FDj+2zdR-pg4T*{prgiS_GyB@-- z+Dixy;Jb)?s5s{Tb&Osq^7W>m6}DLfB}_^w3cA>+{E27^M~oN^K7p+Jwx7K27$ zGqu@TY9eky+e{t1?}A-xphv`d7(Yk^HarZ?;!&eL#AEuKsB|(+xkW|Gb2t&35$LDK zm7P9`<+3*fEek=wFg9Um+4WT`XizL5q8aP-m#fjlVz7*;3(u}vDvp+izYN4QE|v-t zhYp<-SjpapR_F$;YQxOxu;*~P_u6BDm>9V>JqimsFU#x(eH0HIZJ zC~_sF!B`AKvb72m6U1uGUbXCM7zhBePQvS=w7IjcT?v9lCw5n0!&*xP?BN#0Xs*Kl zW_|mdU0kXLOU0TUBF-#+ApjO1xbJ26-hW@AR>6o~AWlF|)3FeCx9nX28`3(eu^-lv zKdA&!Nd1}1=dcC(y*j|#;CmU(SA`e7g7{KAYqrXW@q)}w3HM<}ff53`>$T06a}Z!> zygGK`J#HmVtwwH8KRB@p0ZG{tOy~ffR}xrcC_`uF(fw(d`{yiz`Z&N!3!b(ypZuBA zG2SI-VAF2xiF9q?*pun7n(OuEtJ{l|8M!{cuWB^z%g|`BrP2ZG1k-;H8SFv&UnIY) z?|;So%%<#_=7Pl~(k39!3p+|AlNaWH4AS7sITY)!_7u( zsG50CM~kT;yszh$rZMauDt*k4iPs~r`BQx)@;?V=V=sAY%0??1$78##(e(PhXp*i~ zLeoACR92q-I8--*)7{i^OKD^E3+o$Q6wB+ao(%axU-A)?U^UJ^H=n7~6)b%HeDn+I z*Jkv?xN)EdX={r7c&5#@QKNQET@t0nd*uk9>j!CsU(68ReJC+Q76Zx&HK8AXpB2yS zNye#>Wj=9V2Y~0e5M4aQ&+yrCpvjc$KHR*E_(%^olC=f4r=v@$wOLl#(v}=kWaZs0 zE-G~xQ;#$-AMUy7X>5^pJf`&ab^5g$0cJiDueE~*ta#gd%HSw#=zjl-_&wZ@!>wU8 zGx>tp&F}7-D}JXXzujtRM119nSRX2AptAh*Gi2|JM>lh=okVUt_uYe9$-*t>;^VX~ zkHZI!eiBBm%=_O$UFFquewI@#?L};TK&*YqwL2Lyoq3v!`9JJSz+%kDf&lk5zlXY} zRSfEV|4i_Y`QPom=(l;%96mcP;%j(BWr%vK3LnIbxO`T3a$mLF3~Fl+$Kk*^9Y0qK z=o1st@Zb!k>ZaOZA2FOO#U3O`(KHa*-jNL(Lc_6UI@rX9u~UO%>6h79)vm9!aAaN{ zY?*Ss6}O82QHhRT;=nga=|;dz^m7_mguu*NE!*&4IyKBpPGjXgO6^>1eZ9Umt0b9u zn&|0qqP*wjsI;EG1a*7V(--$%^fq2JhtFR0R93zTFjSvEO(!PStDDlK^1KI+@=wZu zuwM)H$5P3Dain+u;kdv7>%KazRa$pf19S>3lVjnCcK@)VLpy(=HSJZVu{c+;rfC6a zI<6Tn#Gro#wSuTwzzI%3s~qqua%Cn#Wv)QL*Hk%RJ&3h7=K_viMOdJ!w`xnE4$Dpz zY}I-=3TN>X9ic`rr?(rC5@4ZTIVUJRdZcvZ2>+kZd%y~iB?9QgN*#y!_=GsZ>@NJg zvBb)oF0cQGjTIYH8y&5iG4&6j&}IJx_~eHJ{aJj=vzxQ9o1z2vVK>u7apak2TA9UG zK_>cKt^IGot68V&@Z>An5T`tfc#9PqPUz>nn6}7&)4qLJ+YQP%^^d-@v`XElv5G|} zYu`R)ZH;AO**)2V?1;W6P!5tw=wtP(e}=uF4a#kK5ZqHHRURBvGb6Rj8EmJshntySl*s9?!~x} zmO}wMbk#~bnFje zS*Ol>OLsf3*{`@~!JDQEAOF-TX7*XN>TT8Y8fDXTdX~=%6tYI1)f) z)Pr8=1Jo6?NmA8+53Gs^_4HqZe(pbjkCb)xAENGW$N(XtJD>4SQ@Px6+kXQ=ejDgd zveK~B;PJM$e}>9jLK#>Wiu#OI2v+8O()6Ke7f?(MN^1QmHRFE&{gfuk3VEI=gefc| z9HFWfH)M#^l0)116h4J*{wT0)OC-gTwF(n=uOiNYAE!Soqa3iNwd^6`kO-q#Toj>( zh$*BwDGwvmJQZyJ^EsR4g;t9k)0xG^Wkf|Fyy9RIgH^c}lxiN%+cOH3UYFk2l_%v- z-)!VvL-^|%gc+l+88jME$U>d?tB7uh@k}E7dF5-q(p>+3G4^GLTV%LiKY()cijr zI24<_(4}g^@*njjKDmebd8%~u#3(xS7x1;)T9sU7mbH-IPiOe8um~~3#tEL}%vdI# z1U?J8x=o;|5#=OMwE(P@Bol#P=H`DB?3ZTI)buu8X-1^&8IPx4Z8n8jt&NDWa!E{F zQZ|~6$aFvccfbG}MWjBDtn>e!e)4o)`tN!$D^dtvW~22$eVtN|XDMCaDpp%3^sCOY5CA~YsF*L}k6F@kF{hfRrEOdhp4Z0fDBcpdV@ z9E-{Be-4|l>>njC**UC>kfdvpno=y7^fW=f*T}EY8-6LO_A)_!z=)!QH!OhXPmnVy zWERcvHE)Bbh*1_ZohEA?DP&Bur(R_fnQ71OhEg`YOpl*5!XHTUzQc%O9nG7QL*~6^ z96dJini+CGIb^!GcBa4OCz1JGBPTsHh@Z-Il1&ODou2ID#DV*q8NVs(Nv_%*R-Se$ zS0)4C*!Ic~%=D1p7&jAal4d!rFW9|B8q z!^B-vX6M#s;jR(epu~30jAamFd&-ES53zMg7V~RHJcAP0`^{JeA+V1dQS>3OBsWZ~ zXDWM4V$VKf#5O3g{jnL#AjI}{BZ@x6rn1AtLjcc@uXW9TFk$Hp2ZTjBV zF4n8e(#W6Ky&RQ#+{HR=gm2Usfu*~~EE`dD&=?7P?JgF*=zkY0w@s4f^e5plUyWTX zeZffVVm$@=^<6BzgxbZrh%#yyORWH5&38(=^}Xz3{qjA@T_%W9tT<)b$wH`JdM9gr zJ5?IZIAROfdHYo&iZZof^@>3erHu_NqYvp#sp-5;sT}gvfN)^#*3_EP8uE4qd1e-* z*(z;=iYV^7L1kuZ0&MYnXOa^jBjSNZ+=s4+xbzlPmYk&P3~-A|lQhMsQ9_r?l7}u^ zRH+0Od0SMUCpfuVRGF%YpZ3MR#3#3?o}o%&i|VuZ+T#|LtVLT?8pBst<=`@HFlZ`vI<=@&hvg1aSVDQ|D)oW!q>o{u<&*I#w|SmcEuJxQOrDNMUXFY0~%?US0AMUCj{w_#-u+M zlg_`bhjd)nD5Dz^x@Xv^j1p_RCmqFw_B8))OGkz9)7bYKBtVoGT#CCO*&cCbsFb88 zD=n>x%ex+L`%i-v_ui1*kXs7(X(FfKNfa<`^Cy z;eNwv`@{w|p~@h=)QJr(B72BUm0L}^zqBOpIh|*d>IuL9W}c17KoMNTyPfid@QZbE zMvSsM;@;1Zhv-I|Fh1SLMuJw5NJUfH#RJ@^DL7qC`U_epMEj0X|tlUP}wHG#!35aMd~I zK%5)dgo`_9OY-M1+wNR|yAt9q$yS&=9j-V_trftV!tIlFgxkhmE(Q<=Gd{^8B8A(=SU-vl%o?E>6T%af+8GQSp%alU*kyuR$5+ zP}=O~%$p1d|5H#$HYMODG^pBp(=RfW**4+<8OK5wBC3L?Hcr0Elc11zg^q*bW@fgt z>6C+J1@|vgjw+y`OHb$7hzQ;!iFg&I5XeV5Ww)ZBlssY^>6+QC6&xWd#VQHG@&^bM zb((bqaRSVB{0UcfYW?I_esrD? zj8KRv9*wH5f^IhnX6P88xDF3mBa0ynj8dE7-rwu#8<60FO-d++w&AA}(d1i@_&ol3 zVu)?0{szsduRsy2Ud@Zz_ivkq`|dOnBSOsJa=Hez9kf%|V9!zMBJemYG1DPpT*W!`3#IssFx{z4xq#RtzXq!$ z@4>o+e2~etSS6gKwxwuqF1yGgi({Eh(cd6tE<%((+iD;}j_v{HmjO#Na2eVYl)d^g zbU@mL9b_nAWi!S2b=knhGdPONp3kDRWI9JMyazU+fb^N;Jz_Ux7AWX+LC zb~0o;Qm=SUG`w4L7x=D_uq?;#=>}@LZJS0GtkK_Vhlnr4kyr6-Mhr!0Nx4KHf(kzlo0)8>h-VkdVe1Ui}iUeVNz1#A{vRH7>Q>Tj*l!lkS5Y zG)Cd`?h^v``v?JteN4gj-%Z~r_MM+N;_HV#Ei!V~m;1*pwpG_H;)-o8d*UWv(r596 zOo%Ec#Hv3L<f&-wEz6wjZD{Q_4VBLXyA`r|bp7*v{#Xk$hk(Tt$k! zntB?K&ngXbDr18@q`1KDcm*OuaXoDPtRgXK-^!vuD(fXBAPQsF&p e3l3^mTNPN>V11Mc literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file_df/file_formats/xml.doctree b/mddocs/doctrees/file_df/file_formats/xml.doctree new file mode 100644 index 0000000000000000000000000000000000000000..f93c3a6413224bca156be39349f9ab94a6bd2a56 GIT binary patch literal 169345 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 OV-M{EgVH$g=pz7nF-F4x literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/file_df/index.doctree b/mddocs/doctrees/file_df/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..7970f744dc203eb5379a7103fc97c4fbfd36eac0 GIT binary patch literal 3871 zcmc&%ZI2tb5xzI+B%Mz8;6fN2sMvFtvX;b8zvEVK#k-6RF zhUEHmz!_)@w6Fnw3)`R3-`I!TUA@5x(0=Iw!tD-+!{N;H%*_6!_0O}Tt?*}$biqYF z9}`M*Ntt%GLuFECEH~~u_p`scSMDR*Rpds?QYFmUEodZMq*Sroy};tY>l=~kac$ro zUB=o71EI_#%BCV0{&8WCx9q>h~+?YaNOeP|mjWri6 zrFtynBqnsmCLf$X8P)sn7%#Zmw@4xk&GtwO8^?H25pYV?_cc8y( zyU~I`e7B5&rgjC(RfwDb|It*WY(%FcC8fEj;PkyWYT-tLI@@2}6ukT`PHy>=`ET%> z*b!izv57|RiJF+Kn1s7eJp3nqcSNT(<`H8TJnSSgD`c6|6(4y!kYNi6l8I4ll?wIR z_j$|D?cv%L;%`HSN37ETK|tcMVYGHa{q8|(YR5r^JwF3UC}Y9I<<|RL1U&ZF4~> ziryQ0Sesr5I(eDJC>YdTZrL7cAC8oHdJEkiYUlFq;k7xE=N>&@13h0Nhr7t(S8Hf{ zPCT&fQ62x$jUq+Clp`MN8%S6K( zRRQFn@Ei(d%zl7xHE;Qs_RJ`f>y(7z4+75=(^d6p8EHsbMX1&Z0V{nJw(TG(mFIR; z+q$y-MiSV6WjmS~Bk~!V)XAdhOqI+H19ENqYl23g7ZA2>S(>6WKvOHE1P|RA%b6lZ zD*qB{ks(=umjlm2R2<-C?(Fu&*@McF2&Z2|p;y0_D#hC&k`nnE=~v-U%jfvk+0!Y^ z?zSC+L{Bb2=DGQS5Mi?G77{qz?hPrSgIEsbl znX;D*LEW)Ejw&71uPuAR<^r)a#FTm|ZBZ7NGaNU#Au2~Kp#kn?kaUr_g!wCZ|3r?+ zMd*N%Ld)51X{MuJjI_Y>!}Z08DJ50JQQcFZ6Ga?%OALTp#ec<0D_C*%(Zu9*t-WVyFSk?#XkDdeeoMOi%Ylml$?8BmytOc?jY@lWlbV8ybC_{;RYBqs3P z^TNK{^jnDaxVSZ3=3}X5pwoS?861FSWLSpu7^I^E`RIcupCXOv4$d2@^lY;jpeDF(`zw&s}!xBLFgs7R+2!!AKV#A#d!lzReApM_kN! ziXXtcmr9TOzRn0n)Ne~rS|d>R!h7`^r7|H^bFq&SPT&s`Uy%)|3?ans>$1QReoPKk zsICF%B9MrD3Jlam;nY_N6xt27FiqktQZdobLe~zELC2w>?=3vwj2WD9KY`YD#4;k% z2ot?8y}|1fZy6&vu$oS*g7(JlvizmHDr4KJN(s0NBw`vC;D(8+p(WtvWOJhT1)ZPq zzP6`pOV#-J3e4CTmCv+4Wx81R1DYR3eRFx?_*0_ozF(|mcl^1wT`w@V1#SSfyVsh( z@74KN8q-BnWnsrJ+rvZv5&nt`iKlF%S6yROQt6?OBKKJ=(*-<&>e+^Hq1|1x*lkV? zn@-p4qx34{M~wTMw(1p33MeQE#x9@+?Iw|FTBQm7WdC=y{5#z4@9s|^2S#7e48yuc zEvpV()Rk2+CeDGr)nHg7U9?2rswX#m;=ml4VWQw~TiyFJoBIzyd;WVQKzYawD?kh% zdk!~a>*Jx&L$tdgqF#M4(J|chpN;(doA1h1lybB$u-p9YS;b(KCZ`GY` z+wOTxfS`Z4diLCRo9e?91+e}~XnC{uDtq=|5uyO^gL-lUoo4>)r$>Dos4J!)pv!pt EAM=stnE(I) literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/hooks/design.doctree b/mddocs/doctrees/hooks/design.doctree new file mode 100644 index 0000000000000000000000000000000000000000..6ac734b15e65fb78ecc7b6a5c80989ce8ee45892 GIT binary patch literal 87065 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 T#C=DKq^d}%PHKH~CinjV^C_+; literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/hooks/global_state.doctree b/mddocs/doctrees/hooks/global_state.doctree new file mode 100644 index 0000000000000000000000000000000000000000..769919e9e11634f103dfb61908e25bef42d77ac0 GIT binary patch literal 20165 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/hooks/hook.doctree b/mddocs/doctrees/hooks/hook.doctree new file mode 100644 index 0000000000000000000000000000000000000000..9cdd61ac942b408cf063ed361644e72039694592 GIT binary patch literal 53860 zcmeHw3zS?(d8S92x1`a-l8yYlez&xv9!U_`GnTYAeqgU`*=q|wkmR18?mKh)s$cZI zZE1pSjES)o-z?eCBnyEoC%aj8F(f1=OUNd?41~2q0Pib=vzRQ0knA~xkUeaYlZ5Q| z*W*^z?b|iCTRq0!MdzsJKC0^f>wnc>fBjYW(Xn^m|H3)!U$i-BdX@U=e6d`vH_C3% zi6+a9QoH8X!_K=pyWZA$xRZ;fTE$aAqunaGo#-5tD0!7?x#iY7C-8EH%7>L|kQWV{ zAG8;PuoB{7qh2VxOO<+sF5~vRvFPXXdFP>^-E1~mVZm!OP6iJ-m0EGxou7BSFl+|% zb90s2az5}XZnYfbD~-9uVtLt}yK4X5J)#~iSQ|Pd}UB>=zGGDi>8ZV*s3hHL-3XOxV};jz_VzgR4oPp9^!heUaaw}$#T1?ZWfEB zlb`}Cv%XjjJKpwaeY;iZL_aYQgF4Z4saP*p%0-S%H1bb6-tM3sO@zf|0^9MY5V^xpoCw@fLPx!EQidGxAA>Si4NW+AVc|MzU}3?D@n`5$ z@ri0BbX&!$-ruRXf>fK)qwZ-kG0_-nsq=V;*&1}>0p5ut-o7KL#1~8^${$DZ`_O?> zG_4_`nz~PK%IzsH)YLs_>TX`#yA$LbMYHEbTf*XEyIO1&N{wnEXu2h>Q=C9O8;wR; z`Dn}W``)te;kWF6SpH=kJFUP)*JkI|{Jz)DA1JoNM$oR+imjDH9Y&?-JYmK`)4cY& z6K&P*3h0c%$NR1wIp>^d{3BlGj#cW-S;wtYq4KQLY*iYqO1N@j*YxzQWWkHo%1^ou z6?K+cjha&`R;yHYuGFZ90PNI?by)CLetP;A6uMK?&plX#eLgnpFxxWg)Eeb>)tz~wLt zRcv}%#pss#=E{6y(Vt&nQgnbDWx5XX^viw@EgW}_=Z=#!9*;SXD=Cl1PaNMRY|S~* z#-@ugrM_G^4eO|bx;h&u&cPEg`M$s$fKoI{=x8?Si@G5(2cBFs%791fnkyK2nk#$I z!;Y(uN77V%&SuZppalbsfs#>#+35~*Maqp@u~P4N|2djKH^Y@C{IK-|CE9>{_jI#b zDOJMGlkD}WVyoUc0}iarb5-j^Tatu^4~N&x3+NnSAjA+A?MN17JYgp5vlv*;(o`KD zfxv!}8SD#s+Z&DUpVaAYH{id6QTTtsa1)46P&FH|i6b9J!H)r#--@}X4G`yiE_0lxuy{u=1{jHI9RAbJz{c&w$jK;Ptu z;dC1Y{WUibY*aaZ42FS?EBMQ$9Tyl`G$Zk>L??RfV9*ja!oLcGrZ5v5b-a|=`qxkq z=8C0xF*)yr)s2OJ1=_HQaBMHU7j+}y??b)pcEbC%_b1+Wj(C3>UEHPlM(0RlhGYT! zOIg?8wOwp=4m5&N4V|ENNrZ0rH&6{`4g8BlWg9hxLGX8S)Ow5TxkQzCFyao2RQ%L; zv3C3SCOYwwD7b&L@7~*qJAj(sZYW+4_2z0Voj#`7!tF~R{FPp-v7YfJ-Fh5aNb2z; zL>QXyW4Nb^nz%h`CLM_hgH+x0mr8d(n&`$$qq`sJyZ=Mi-FZWI#muUQ z3X8YGgO^^qQql6>Zq0u9x**he$~KBgJ$;_T@2#g>l5=*cquno*eon13 z(SK?5b6h~p-tVF7=K)ivWQ-zx5X_6$JvG#Hk!A5->K?b2)X}{}7p|ksMV1X|^0wc~ zuxv`2M;kWM_fpaWs5gj`rn|^-=Z2}JyII$|w3HtAr=>@z2G`PSL}eSvq?TUE;rG_k ztv4p)W>QU4-Y7j?OmyI-(bGcTJs-NBCNztpi@nr}-u2Yby{5je-cC|C&k!xRZcefB zlbX;J^C>HbvMJ^hXu~FDy%h7^s5gjWUXVU|vYri7E8oug)uom6xIeA@Al2Yn`KYLD zqnFgmhdKP-T6y8B(dtrUEu%E`KPEc0s-}96iyr+}-#yBtZT3PRLG%fs@ZN=@QHr-? z5s`@bb141JB*mj0+G2@QZPb@}T)&u%z5NVopGnr%3q@0>Yt{LnS)>*7Nwz}KdeCK2 z)F)6^t}}sPSmMGm(->QW*5cQz#qlR7@?K%Dk6b4QHC!px*u37k`qQD1f% zOI>{DVSG~1_M$%n0C7}wYZvKcpHto_8aOeMsUc4P0=)Mx#7{r{^)IDX8IlmIDaMv0 zPx#V|4BU`-X`I<>$10j6&TzI&QtPX6Bv0+>95FH6#xSI{u9}NA?`j3>>6-Yw`(-Kj zjJ-;2W9)UzuVR=dH8rW1aZ60Z$p93R+**%@q&%@}B|%55yoyHZouuLQZ{zH^1%H^` z+|KAvaGULVDQvSvV7(tBBCDbl;YxuntOB~(4f;d^bZqmG7C>fRWBZ6BdyFduw7Ck< zQ{6!8I#B<1+{J3UvnO)|SK_dX7)wggq^zPL^>!;#=9q@HMzIpfmoJiwk(4zh*AX`- zj6Gkfq~-3)Ql(_BPkMi%FGtew{8{MSTY(vD)dPB`M~=mmJ*IWyJ=RY6K3;c;lHg|_ zP(CNsKs9YbGsc(PjTQH8UE}Z3At_5b+t-cSoR=)h zl7%5#>`+!`NJ8-+iiPVTjVqVfp=40}EPa`^Ls`8qDZ({M?yc67c`J;J^5wsa#7#HD z91Ken>EHI6b1DD+G$k@dWIqcvk6L@`+o&0j`&;92ztKdhA+jfp@%LaXEC)3bm#u)K zkw*9UyGT{``y6hj-UU`5S$c=0u}YogyJ{{4=c9Cb6SDJmD@>YyEEU_;u)tC#%uYt< z%ADo^>4f2%+YQRjJwua`x9NR1ML`!wa|UxWnaTbB>+uI;_Z#phw%psrYOXPdV~om~ zo(<-N_nN2?nY-q9f# zlvx%2(W5(v4(Ycd*pp^x5SZgB&a(6Bfg(W8P5|4WcqOW4G&QfaL$R3}0VRtP+-Qel z6C44>Fg0fO8ylRuO^#Ve(e)lHE(4pM^d&kO2<+dnzKij&51Z5nJDOG!SZ$JklEo%H zVuxb0NqzJsvO~!rT82$xnRJqCx(Z|MCH0*WtoPb68Ngz3G7My~vQ&z8*5I903gq(@ z;%FRh5lm#B*mc~gQej~&2&n-0=b%`TwnAZ}7eZplBd1^|(!{Z~$IM5M#(QUmv(ele zk0zJe)hdhT`TJ>npDu)r5}_9c9sg6HF?;+DB&f3&Jf=EM;aH@VkpDPASkJ|%!Ydr5 zCoC}=!CfA6XIn?Kt%h<3L+(eIh3!9Ql_1?TQLKs*v4E zBNa`ETWnHe1Z8qfX!FHh+4QL375Ie7TdTd;LWEPByiMM8x>##gF|iU8IQ2kLYiwLL zo3@^k5ITQ7l~xi0JrmvB&XAZQy~36eSDzP)bbNn1>Pknp_p_8@noc@5@@_HWu3-M-U+a?vp_V@ z?Z$5BVAk2a`y}@KEC;*h*(PX&l|}KKm`EQqS4$5X?(xmH-gWoUU99C|y@7pltyLPA zLi|capUWGh=4S9Wulxf@zrb#LH%U~ez7 zEwEe@Nm~mAI{lzf$OUe73A&o9vUGLAn~T-J9l(@uvX}(9w-S1Hwi+cj2<~zN zxV!%^QHYp9w8wD;)WHHaB%_CdIAH zuNt$N_qUQ0jdHfDb;KXbF;gR=7yT0o@?$y9sOdzrAF&+AgY#1`&)EiN|0s%{@!yDF zHYWS`;wo(%z5?SgH~xK$!)8%rcCq7f_F%dZBvw+y6k-Ej?QyFvJ3&VqcI0G^d99T7 zhl4Uojhb=L4$hdl@UqLK9l?xlamB%b2+b9wb~%?_CdX`RnV5nf&P+kV@<2i^fG>iT^BdaRM<5HXrjpN4G3dOTVu- z_V9ekGtor7Aung-<30RTf-})Z@lxJzMG58x)x#hzw7K4p&ty4jiQolp7$TjH2VvHU z-~~&Jq?reJ=>H}B2I)lfld|dm5EY}G?ua`%K5k+XuT2S9+8%N?R|#v{b2t6Zvmm=feyw<#T+( zq^L=pTt6wFs^9(?waSs4(`C|JI1u3k^Pyt)_Ek{37A^Y+@M8p;OqPgT&vewT*HOD$ zIMNjK420aDt%fk;o->!s$M}jp_1#<|*Itn5^J0n`yRFo)E~#FPstmU z;y$g2Bf4g+9gVTqazqn%st2zhya$IkGFg^J?zBU(EsY2$M$FWjr15|q)&R>A90hZn z)#uikSv9ho@xeDq#vekw@LDnEl)W1^k_C)0rqLJ(-wk~Af+=2_Y{}IPVKd~d>cl(k zov^LxKE*nbAXR!Y!29h`Y@`xUvP;*~c31;Q7e`?T(zVI(71H=MDN9ImpdKimea_xP z8_zzSoo6rDq1bpPpk(LSH|?+n;2B3@2t1pL9lA7r@tKa$E`Fo<^f&fi+4%H>?0lM- zw7DX_jZXqfc0TQ}!y1539EBn9X+n70X z;z`Qkd?E$I|1{M)4@S}d3|)Ny6l1sV==jgdS2ULtnHF*)g=ytC#k3My`UR9s%>A_$ zqSO8xL6qX2Uq2Ir75`)UK90M7NI=h(Z9Ba$TC1a#{J?Q1c=bj^UdWE&9#4WM;J+>w?oV|Jsb(@ZX zY!+IQ^Nq09m>>qrxLUnwNhKmLu^gUciw2(ElRr zi~kNZ;r}PPdV;Q=i?5!?)fxZy+3%Ow?;o(=FSFmTu-~uJFX5xs^0=KJ&T`&feym78 zW7C!Pv57a>Nv`ee72xC3cL5_gK1tD+1Js|Rj5a_?TrohshtSS4Kpi1lZy%tF zOPF6e{2K|6Fi&y8!S{QdMHd;Uu6%LZ^>-g#;KLX#ePQ&-j}p{FOvpPWVwO zpf!2*5a@n6+8BiHd(V#Uyeoq2{>9W0?)}7c9LZ}vO^U7QLZk$p^d|q3IL;MyjU1}m zfPdc(l|%~;mFRy~he}!=sl+BVGC5QwV9Xt=GF>r;ie0@79I8Btc|Y#d<#u<9Y0VIv zsjY_3&RgwvrMP7G(AbAb(6!{Ggr)3fn!ge^6}@R~+$i3|G$*Q`N&e5qc=qi?MW~w5 ziTWiw6q^$zpk#5PK5U0#bD}sDa-#G(K0Vdt!PI2kuBFcLl??b#QA!Z%8JqwMj)7|ArRp|6}{)nESrwaOiu zIUPy<9U$qmvA!r&4zGerA4@aos2DlMQAvFM^5vj0B35Rc=XH@nP@XF)Pjo&i((=uu z`-i8qJWacOMKW4#pWdnC`QWkwq9gb zu^a1AukW;?qmPHS!@jzX7(DxvUY|;1T>43`|JvFI>*1AsK>79ILb06m`l;bU>T%NR zmxc@_{iN5gpEZ;$O7&-UC^n_y5|E-T#!0V#Wd)E`O}fs+8_F@SsWQxsEzKzP(|tE= zG+mH{H(@hz9E#~KXRy3x1)Wf)`yn=s+z&d#TpMG`FwcnGWha0wW%4Ri%{Y?0&JM*! zW&tIO(BEW-ViS4+#TZHYIwSI4D{P(3wqc`7A2<$upAmUCV@*PsGw?DCmEUabsnr1& zRL+tT{Z=~^Tas-b67w^5C>bQiOy#wm5&4UDSOcW;@{SI{pu99^M1IUp0GoJz6jd{t zn$Oyy*i4Opl0^x=VuxZA90A2JHD=Zin^|Q)Bl3S*aT(b3tg@N9Z=3WD#=|~rQXlO2 zJ1YsSHc3FqVw0vfS)B!&P3ogBud+kQAX@#% z!St7kg|}H*sM`h^s2|=TjWWnZ>;1P1JYd9M0U7osE7dY&*cZ@%HBF#LhW)V^u>U5y z+;@1))=a+{_M%PN@<0q>$E!@^n9ORKpQEZyV-z8hDV*s>ueAgD#94?qm_^iNXPT;- z(e$12Lr?KZj`|}dn^D}^w4h}|HvONb();}<=+B&_j?sn1MtMa;^uBxW3EkbhW%O+K zZf9O(24UN#{)M##c6`*`I53bMCPk;}JN!IY=ag3|dDs(!#MnjGIn^pQn{4+B4x=iv zwSrlvS~=+&hdY`_dO7Tf4xBZ(d$%}ekRd`xzcaA+QrjNF!2}@QUxPCuaq>srd1K(> zfLRAwO*nFBvAw)ZcvD;QOm3}L)>VDp#~W~h*0LLjtv<4iSsae$ItvS=uktYAg#`z> zJ#9L7hn+tHpxQqknw*MoO34x(+=gQ<(*-C8F~QfTj6#J<=+-noMUQ7=(i$^}2XBGz zD00gT{I1G`UtE72pO?c&5%nH!)LXE{b)V@=C4QZFRla;dy=rCan&Us_RPURvhy z(^NWEs!6EX2YH)NawflYmacwCYSb0ZG=0|SJf5SK)HL3Al_vKB0p{6ST`j84{Fg*$ zOdm4aw^qNxI%L+=O-!$Zxo@CeUkP*M0w7_I27V;Wb@hsdS)|TM14gv|{Z&vS|KT}m zm9s@vp5O*t>6a#R!howiY7eZf{z$1_YcNVG{+3A6GdW1-t#EB5&8I-fzs^AO|3d&` zPEjqc6?R?v6{8mM_m>$g|KH+|%nyy;kTM($G8AyD<9}FhdxO@tc>f#L_Xvund_Csk znLg<23tO}km6EoIp6QIT)6cX;8$iDutWE!Uy$@P!t`sG|*YcVKz}{M~sTM&I+=i3- zR~oP`ReJRRoRa!6P+cYTH}%ssO1zOmzY&CdJtL%@8m7=wElz$r(@dc!rlkq}LxcD& zL-kp5rl!1l0EE&s3gf$Urgp~%0;PQ>Ww{^rNu4S&wwdEQJsVV~X26~TYtz)JRRTVV z5&lX(!rQ%0F7;}>|0zP7kN2xUQzHM4L|0Au%{KZ!#=2|P)JKS3qyNvM-dT+PtMoD9 z3lLu(Sp`weU=a(Ies5aMXkB|2io|6e!^B0lMzjR8^Z;NLL-Rz!T$kWwwKb5=mUX-G*TxFNOI z`T#~NB?{zoybn)okpq&W_fjoBqqb#5QPIZ@0JKB@F?zzXQSD@Slmn6RpEyr%6uS&# zZ%BRVK^O=1K*_=P`jG?q=?4W@4uqFLQS?*IqYosHG-s!Y%C8L2q&WGN0XqHH@_hk* zNI9F+^N(!BX?+LdT5-x=hOZ7t1$p@+%9#b}_aE`;PttnRDkziuK$6Yb&CNMr|+n{H*#x&JUOZk2&}H5jE2 zI;LUjYt!vq+|=5#&&Xw5C;Hv<^9-h>iSJ!0RQ>A->*S_}-FP_o5(kT8uvLy|gT|o_ zU+%aIZAUyCG?M%f8yTVmLrFKp2}}}AsL8CLGql%!0@Ie8*w;?kxkO}Hcv#eaK=R<;IKO8lI1OE!uCIs5?IpMJT(?^dTI&_GAYJkpz z-GRw;WmDs!ZS0I1V zyND(P8O`qvm?rc6#5bjgiW3*Eo%`^sDah)k$HY*$58bi?x79ue5T}Poiz;oNSxIa! z8t30sNjo~^^FWvHE&2jov8_4mYQU{I+(8@8?wnk?642^g27e`e=A9P}be|Q{zr!-a zhQk=6G>>H>2FG2N_+AZBFOo!R6J>LV>dR;Odfe2I56M?l<9k7d;It@Q{qGXmNvGw% z;o(r678X>9JHWk`AEI&TwZtruZcCD_n%^QZg$4OTLF-lhmaR8(k?nR|Qr;-BnAlmu4D*M%4| zkn3`7jA9?Ii)23rOo0Pj7yp-_1aS!G>>LDgHF|g;%7KY(t$M`gfWoJdagvt#89{?-56$d zQ=|bM)J>@aUGAnd=!&^1?CNFUrd$=AtTZ$DC>Ny1RH*ha927oZ=eVSKC!-t?cTM<> z8i1YzRfpu4{8Zdjbk}f0VY~xFb4nDV{?8N6NvGtkcsLZNWQw_t414a8d=M>5kA!nV zx+4Y?HD5#`h@s$Zg1)QzA{QQY>C3tJ*pwqoX}4EmEu+NaqXMQ>U(;>X?S}ME)`<1w zoO~oE0A+6>Z_d^7V4~08y2Pi;r;347YPm%^znV_DM#h^MJ`S=e@xsC^eVdtz3G%Qn z%ZbCL#G{4T=}H(l_-H_gd_cv%iaaeJy5_SyHKN@l=Uano`(4Nw4=Erq#fhvacIZ zKPRMMvZ_j5gZQ_J@X|cco}cvKV5&GUGVx50SHu_oA^c@lw<~3t5qi0SQr|96BNvQ$lA|o2SN`B!x4} z1VQ97VXfkSiexz!X_{fFQRS5|<-OG|EB@DT^tAPx05`2BC_Q#8Tpt6LYPi!TZ~aE= zg^i(%GGfR;Q{JT*#XhFIlKo03X!5mP%UTc{Au>KoDW+Me^TehBr^dgG?gT6FC{D{U zu-9v)AdmLbN0!baD0>Mg`|v8fC%^MtzKHRY*^8cHMN|511`fV|R*M{~1rC^_#}*9v zL}-2*-vOsDIMcD`^)Sa?2@hkJwls1D_ZhjLheOiwDe(|T%a$E@hn0{yiQ=$X?2vHL zl;Q44Hg*Rp^fi_L4|~o979#h42*-e7k@xm1f_BR<1Uu{{t+_M_?zRJ(GH`cnQC zJuAq08kR<_dMy__wF)n-uK$=wd|c~c5M#6OKOk}gnuR~=SFrmq3uaOO-(V~(>MDsn zWB9ap$N%?up$(SGhv1IKT4kvHFokM5da1Eng`9NUuFbf_ECWt5_+wIM^(E#V+hpwa zJi21BTXr>!*sY0-P6c9&SR|KwudO}keSGB=aR;u9JHW06?0`DHQemI-?ugb`%KNZJ z?_iApYX!MzGqZ%q2ob|eCz>HMxJNy3<$?lDy#ATD-&@be6z@(<)JLP8h%&jQuz)!q zzRy-Kx#hy*N`YaQ znhe~KVr)St+DiLW!%8*CBaaupSiRhd#_uUs+innt0i*FoJ8ZVY&JpjPBhl2dTX)&` zjFzU#h~&Y|t2J@6q19-FKouKSPj{k`xlXi!qk!G5^^+(tEpFSbD(*IgjZ$H;SUO2> zvDx|K(Y7TtKoLmo@srV}07S31YlYL;$Xn|?8O>meXr);wHEOiMHb7Gs@+WT9Wlsv^ zw-(seOLPN}L=ItrUbTko(#WWTCNWjBO%D zdoy&tKj?=Nh=|tkWD1NZxzriLuTlrK3XqUS8KpYWR6AVS^N;rg6N+-HF@g~uP{{RFrn&Is#9Axc_%omBu(H80hq%QD4 zqAL2-i8fWS?YUiCc0utn?$^2XPIPXI)Tmr2yG_Upz*HDv#}TnzLN`H?N?D46Hy+It z+hL=i9!2Y*5YeV`yVfkk!U)2$2bfv7t$MLatlP|H`(=`&MEzr3} z;1=|nOiG}w4_1N-oJnd+Ixy&nz;Z$=2^4N4Iuk%Gn8u;_CQ+=gRH>qKCpyu2bOj)R z+hUux#momR-_RvdxATqGGIVJNm1GE@WC5GN!5+lwqe(p9bJgD0fSxn0rBb4K zdZL=YS~d^0Et1HdbX)m~8!j;zFa>j7SgQiqR9IYu7L`Mv4q~iVzryh zLeU5X+qgX-C`<8siaS=yteHD7zTH?1q6vD*pXK;qbcer(2H4&BX-8A^n*1y_hOh!p zviJB{)g>5JFgD%IisH{XIIh;8q96LatWV#dWnXaf=`(LWeJsbPFY5So;=51B!n2cL zd^#@1r-W&rvZ{T`Lh>n9$)}}1pVkN4(I#DrcvrD(!v{I^i9aD@AQ6|**;7be zZLv{}1(QA8o_H9So-$gTl2BQtZASaB`>l|)ct^4=@dg7gOj)VXCPRf$uGFYjxqx6q zowz|fe-{|(S3&&4Bf;aHyP%%UVhf5676*_>4mh@K`eQ7_%>*m>3T#b2jmKhdEV-Xn z+?S%)+yd^lNV**^d(P#ie{;EmPz;7wgkz)wBR_kBS@0KuX>#(AZ~zhc!eRx9lkM9% zV`5Yt5q^C%dh3y!@1UVA_H9T9-40G?fm8V`Z!g*qlY%MIhDyCuZI?-Lmq~}Fg_{Nq Lf*-=RPUZhEC%Awn literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/hooks/index.doctree b/mddocs/doctrees/hooks/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..6a796fe99c0b0a6700914b3cb1c5fc92211a3770 GIT binary patch literal 4859 zcmc&&ZEsvh6}DsVdUw5kjnfp7RI7*+9-g^WjC76fq;aAi#+Nc=qFv9=%Bul&kBw!5a8u}3P&GoM?t01aQr zFvxgpPcXSo`dWsnTW)Yu<$a~4hA@e}fDdFW>FanuQ8OQQyPbDbo~B8rJ&`15>Ya{^ z*pMF_bcEKaI_ULeH0&xNc^Ig!OnQA540-R>d;5FkI(&62_xfSZTOIrzXqFARo@|lC zT!-C^vp@?DJKaV+?(KK)e!crf&n@rCIN+nM&{5d3V&%k&CLHDi4hP|I6$BW)He+z5 zTXS|#>%IH`_rw`zT1@Lq_H&JUrF&eGfx?}c6+dK3L5T0Qm_^06Mv$kIL7(|&h=S8u zVxhKT$1LTUwC0Wf>4L0j`791(zzS>3;`glhs>;oZWGiKV;`G}Q}OPD7*x{1>=CC~$P8D>b+x1Kg7X ztF!C-IIBiyiMR0Snb!g2i1!q3n`J#6r9J0uj{rY|am3U*i&hl(=TcJC3H!S+w}#ve zXMkUOKp9#Ae);EoMC4#f@q5;>1PuB`VbGDddvs~o%Il$+J%swt3U%=|{8@%w3ufb- zX9`piC4n4B9!!Vq=37C)gN}?lrBY|V`_1ltw=$~ynvp%lq87+#j=yWQQD=*w&Szn}J z@pc)*!Vt4k4soY0s19agYSTJhmxE5jPogx*%~?x^R#690i{G|7$$udc9aIIL0BjkLaz|MZ@Fp9*bj56gkj;B z__O${_`CS0_=uZE#6|&6wXlbaU}l!O#H^CbEYoiGnW+m2(8Tz*R_-+t_>=gH_?!5L_!oCm)oJ2mLelTSW0Z>xT=wtgQDipCyWM+) z97}m-**ttdB#=x%0}kM0iry8+RDSLP^d(tWjOM6xG5~#(EkD6tCBGH?0DG&N9N4Gk z3w)GtA@Tjy$&Fxwx;)%}Z6lrBFDA!nApnQ5CI^E0K8H>{K~N?F>rNFypGI zr!}2u78X%2^U_^HP97|pbwAIjT6twvJ2LG`;yCBXG!)lb#zVASTjMrrkR`F^NH9}> z$k5uTF`t=wlIt|r_DDQDGR+~6d4YHgTwT>HO6`$Z&60$wCjd9HX0d0@YGHzhbI+jA zDu;O%Vzh>v^{C#{EKI2pVlDvZ0k(+~&8H@xq6&3nk8>-c}W+&zrn@#coe6IvT6v8KK z)%^Og%r$B; z)`C1ry)h&aSSOG`V-~ZJHr#L$mk@OO6H%&o9wgo6H#8NZ50!B#|`s z@&c)-j=_=A%9Scrkalf2gJ(p{93KKTrJ7fxq2I9G%4g9(7Nix~~wQ74LFC)mF@vJMk9=J0=2t9h0;IJm@J+cU| z7<S?#9K? z3lpCe1IHl7PA|r}q4*60gwn?POiKzqyng_IsGo#mj5yVuIpJ8lIkRK4I@K-Op4lH1oN4M)Zp`mh&Y=GQtN7>- z0UeYbKM6xbA3mV2to)#qe*o`Jx50~ti|Rf5Q)CKycjO!%q6)-D2jd;Gt}Ybvs*;YY zps1BSWm5b+ftUI2{X7k&j|9Z82=q@f@adC3xFw%H z`D53P8~f7`Mql0A&o@M9%}*s(%=c2$dg2piYGtKcG-Z3f{G+2E9#i!xeslpFmM`FC zbQY&WGGeCHqURGc+hbm?Vn&fX6;Q7tH6OWhd}3aI0AyyAi&fytkaeW$pa*=Ut70)P(< K6Q0U>zxy91>{L$x literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/hooks/slot.doctree b/mddocs/doctrees/hooks/slot.doctree new file mode 100644 index 0000000000000000000000000000000000000000..19450e33136179cb0f38891d3df11fe15c052593 GIT binary patch literal 44170 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/hooks/support_hooks.doctree b/mddocs/doctrees/hooks/support_hooks.doctree new file mode 100644 index 0000000000000000000000000000000000000000..e0766fecb46181ae9e176cc7c163357c31fb60b3 GIT binary patch literal 27957 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/hwm_store/index.doctree b/mddocs/doctrees/hwm_store/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..f61a0ca2c81293589c0ea385aaf2332e283e3ba9 GIT binary patch literal 6473 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 I)slAOzYW2R5C8xG literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/hwm_store/yaml_hwm_store.doctree b/mddocs/doctrees/hwm_store/yaml_hwm_store.doctree new file mode 100644 index 0000000000000000000000000000000000000000..e1a4df108b70e1bf89a78d3a78db018c7888cf2b GIT binary patch literal 32404 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/index.doctree b/mddocs/doctrees/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..318503c88efa4179f282cd9a3212f2727c531b46 GIT binary patch literal 42083 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/install/index.doctree b/mddocs/doctrees/install/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..a7362f14f7ab6b55d2d11f42e7683027177d0971 GIT binary patch literal 8296 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/install/kerberos.doctree b/mddocs/doctrees/install/kerberos.doctree new file mode 100644 index 0000000000000000000000000000000000000000..21f494dcb3c1f4e625ac17efc1e84f71f73d6adc GIT binary patch literal 9162 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/install/spark.doctree b/mddocs/doctrees/install/spark.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ffd4e082d5d250a204e0563607e0c143bf7e0a71 GIT binary patch literal 68759 zcmeHw3y>UVou4J`uC%MSEXl?%+ACwPgl0zCwJaedgDhD{mi0hugTO4ay)(T#-8(Zq zp6-!$!24gM(CsO0IGjj>jEIxGF9rRafA;B&06BOSo{kqV94W zuUyFQ|Nr{y{<>$Tr)PF08)pq3@AQ0+|M!1=|L_0!`q{yc58krs7W&8A?9@tTbs?)4 zi&eX5I8AR|(JnM9M%8V8thxJr&8y8ZZ@8{saqLFDU^Km3(4tT>%f-4;ZBF6GP1N2s z%TCrG;5Mf*u$be+w;ydnpx53jKhbu zlIzx-!?~PUnaesQ(z*XIm39Kx;| zZaG^w^rBlb04EP*RCD{X`zNvoa@;*_k-r7Q0cN{##--A`XBK}%eY}CRp zGkRejSl~8m^s?J5ZTHqR>SohB&=-N4-bg{O7R{nApz&6{p;_AFG`zL0K1b6wOFO+G z$z>ak)EzfptJ`IJ?%}34P&TVZ^UP{*sHTG~0A^cbMlaokMV0OW#Wegoihu9JzxQKN zLx7xNJt$%AkrSGYQns|EG+x@!cAo9JoQm3wlyaqgrTu4Cqx~9hI3Ea5X-#7W+%zQE zQe}~C;Qzc?bzHq%&Nx`q{9(aQ9hWqSC7H#hw=vR-;d}&h57OMFWB51cWx&`KBjYB^qgjsyVQ&XeYN(k!stWEj^vMjAYBq5oh$7>*A|gJ!3c|QLGww`ymSF88eq7FF^@i1*J~veeP20NE^8+q z(F%68D&W?9me$6mr;nG-!hFeYIL7q!UTu2%O?AVl*2;~F_Gqh>=b)ScZU z^fxG>oixkFiIW|MVTc)h{#e0SuBuZ z%3HlxQ-+AbFFrv%Go!lj}-uZcbNfL?*l-BBIjPqb5Bkv z(|)U;X|=AB*FmU^j(Z8oKnY59{pPMm#|H72yv8mX-y8W$z=OO5$f`M}$JTBuvI zI;TCRU)JSAEv-1bQm>b<aE7|-^*)Ghhs`m4Xjf%vWW`U}i{nBRxfz(YL0>u7{{0zDNt2y0 zW=y@Bnb94i7?kWJsYy4|QL(j#>wteApbc4|hU<;RY?7$;A= ze8t3Ju6gERBHIZ9Xxuu6|1@T-NAVv?Egyo8=Cm@>#+TtpYg=GqGrWY8Y%98ky)||F zO3QP$+@XQ$i!DDXyM$`VPqi5E*42RIX_y2`RdU8w(B)lBmqh#0TA&HWtp+!>7V)1L z3BwI-=)$5~f**ko=~JHk8gg`##!Ai6FG<5n%js8!KaXa<&@8N1jj2xFFuhEa|iOXkAYPmgCO zvJ2V~=~`f|p4G9@l#HxhpA+Y7YP3JMt7u=TmTkQV1MuPLJhSdtX{(&j`T>DSJ|~DC z4;xy*)?d+Yskqr2UlH@n_ptsFlS8+MyWd0~3l@?pb+ET;p{Yw(jf6FD zqy#`)t5#!bSly3!I7a(!xMK6nDo|cPmB=JytVzsF6JslRR_dl1HO zc~NSi@Ifduuf__}QI6Ib1NbgleuB+IJAIVQ5>-8Ba_da$eJ_)EfzuQ_;FMRWWhHO>OfA%;k$Ia=WhG{;{szI)Let%J4E z9OJ6y5RkUf9RHUlk7^D9Pa;uO&2bw9@y~e)sWiub=cdV;V}xvuj0SshpWGYON1;I8 zxu#F;K|tS*&MEDVR6rkUjPKhIu)_^~M0pefJ-!s6bIS`fX_0Y>WAP;f^z>2yJ=rUu zi432QkqDNP>LA_AU7qN-ZtP6*_m@e)#L zk^ja`mr0ADCJSO#b=iS^lO(F=tM2@=u!ZE zq*p)_8E!eW$bmE`nght#>*i0LBfJD!1j8y@gqa#=iWZ?EQraSM_yR3rFur@!BJsi6 z*dhm_T7)2NV~Z@%vl9prJAj(upRjMdYIp=r1e<=+7-L(1}{)>q`OjYrO)R$Z%0DlHBw~cwIqW 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 D0Mw_X literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/logging.doctree b/mddocs/doctrees/logging.doctree new file mode 100644 index 0000000000000000000000000000000000000000..06b8f98fee380b1a556015f6a6d89dffcdd75147 GIT binary patch literal 54923 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 DGDgbD literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/quickstart.doctree b/mddocs/doctrees/quickstart.doctree new file mode 100644 index 0000000000000000000000000000000000000000..9c8f40a31d202e0d73429222e993786d20266f2b GIT binary patch literal 24493 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/strategy/incremental_batch_strategy.doctree b/mddocs/doctrees/strategy/incremental_batch_strategy.doctree new file mode 100644 index 0000000000000000000000000000000000000000..d1f993805f8523380e2fff534f718b3ccecf3d27 GIT binary patch literal 52892 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/strategy/snapshot_batch_strategy.doctree b/mddocs/doctrees/strategy/snapshot_batch_strategy.doctree new file mode 100644 index 0000000000000000000000000000000000000000..00e99d34010bfc50642fd69f4564d9a38878a99e GIT binary patch literal 47889 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 literal 0 HcmV?d00001 diff --git a/mddocs/doctrees/strategy/snapshot_strategy.doctree b/mddocs/doctrees/strategy/snapshot_strategy.doctree new file mode 100644 index 0000000000000000000000000000000000000000..7f4b0136f610d71587eca6e37886d5526c58dee2 GIT binary patch literal 16570 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 D;bKjA literal 0 HcmV?d00001 diff --git a/mddocs/index.md b/mddocs/index.md deleted file mode 100644 index 7595bd92c..000000000 --- a/mddocs/index.md +++ /dev/null @@ -1,91 +0,0 @@ -```{include} README.md -:end-before: -``` - -![onETL logo](_static/logo_wide.svg) - -```{include} README.md -:start-after: -:end-before: -``` - diff --git a/mddocs/_sphinx_design_static/design-tabs.js b/mddocs/markdown/_sphinx_design_static/design-tabs.js similarity index 100% rename from mddocs/_sphinx_design_static/design-tabs.js rename to mddocs/markdown/_sphinx_design_static/design-tabs.js diff --git a/mddocs/_sphinx_design_static/sphinx-design.min.css b/mddocs/markdown/_sphinx_design_static/sphinx-design.min.css similarity index 100% rename from mddocs/_sphinx_design_static/sphinx-design.min.css rename to mddocs/markdown/_sphinx_design_static/sphinx-design.min.css diff --git a/mddocs/_static/autodoc_pydantic.css b/mddocs/markdown/_static/autodoc_pydantic.css similarity index 100% rename from mddocs/_static/autodoc_pydantic.css rename to mddocs/markdown/_static/autodoc_pydantic.css diff --git a/mddocs/changelog.md b/mddocs/markdown/changelog.md similarity index 97% rename from mddocs/changelog.md rename to mddocs/markdown/changelog.md index 278395a7f..4ba142635 100644 --- a/mddocs/changelog.md +++ b/mddocs/markdown/changelog.md @@ -1,4 +1,5 @@ - +# Changelog + # Changelog * [0.13.4 (2025-03-20)](changelog/0.13.4.md) diff --git a/mddocs/changelog/0.10.0.md b/mddocs/markdown/changelog/0.10.0.md similarity index 100% rename from mddocs/changelog/0.10.0.md rename to mddocs/markdown/changelog/0.10.0.md diff --git a/mddocs/changelog/0.10.1.md b/mddocs/markdown/changelog/0.10.1.md similarity index 100% rename from mddocs/changelog/0.10.1.md rename to mddocs/markdown/changelog/0.10.1.md diff --git a/mddocs/changelog/0.10.2.md b/mddocs/markdown/changelog/0.10.2.md similarity index 100% rename from mddocs/changelog/0.10.2.md rename to mddocs/markdown/changelog/0.10.2.md diff --git a/mddocs/changelog/0.11.0.md b/mddocs/markdown/changelog/0.11.0.md similarity index 100% rename from mddocs/changelog/0.11.0.md rename to mddocs/markdown/changelog/0.11.0.md diff --git a/mddocs/changelog/0.11.1.md b/mddocs/markdown/changelog/0.11.1.md similarity index 100% rename from mddocs/changelog/0.11.1.md rename to mddocs/markdown/changelog/0.11.1.md diff --git a/mddocs/changelog/0.11.2.md b/mddocs/markdown/changelog/0.11.2.md similarity index 100% rename from mddocs/changelog/0.11.2.md rename to mddocs/markdown/changelog/0.11.2.md diff --git a/mddocs/changelog/0.12.0.md b/mddocs/markdown/changelog/0.12.0.md similarity index 100% rename from mddocs/changelog/0.12.0.md rename to mddocs/markdown/changelog/0.12.0.md diff --git a/mddocs/changelog/0.12.1.md b/mddocs/markdown/changelog/0.12.1.md similarity index 100% rename from mddocs/changelog/0.12.1.md rename to mddocs/markdown/changelog/0.12.1.md diff --git a/mddocs/changelog/0.12.2.md b/mddocs/markdown/changelog/0.12.2.md similarity index 100% rename from mddocs/changelog/0.12.2.md rename to mddocs/markdown/changelog/0.12.2.md diff --git a/mddocs/changelog/0.12.3.md b/mddocs/markdown/changelog/0.12.3.md similarity index 100% rename from mddocs/changelog/0.12.3.md rename to mddocs/markdown/changelog/0.12.3.md diff --git a/mddocs/changelog/0.12.4.md b/mddocs/markdown/changelog/0.12.4.md similarity index 100% rename from mddocs/changelog/0.12.4.md rename to mddocs/markdown/changelog/0.12.4.md diff --git a/mddocs/changelog/0.12.5.md b/mddocs/markdown/changelog/0.12.5.md similarity index 100% rename from mddocs/changelog/0.12.5.md rename to mddocs/markdown/changelog/0.12.5.md diff --git a/mddocs/changelog/0.13.0.md b/mddocs/markdown/changelog/0.13.0.md similarity index 100% rename from mddocs/changelog/0.13.0.md rename to mddocs/markdown/changelog/0.13.0.md diff --git a/mddocs/changelog/0.13.1.md b/mddocs/markdown/changelog/0.13.1.md similarity index 100% rename from mddocs/changelog/0.13.1.md rename to mddocs/markdown/changelog/0.13.1.md diff --git a/mddocs/changelog/0.13.3.md b/mddocs/markdown/changelog/0.13.3.md similarity index 100% rename from mddocs/changelog/0.13.3.md rename to mddocs/markdown/changelog/0.13.3.md diff --git a/mddocs/changelog/0.13.4.md b/mddocs/markdown/changelog/0.13.4.md similarity index 100% rename from mddocs/changelog/0.13.4.md rename to mddocs/markdown/changelog/0.13.4.md diff --git a/mddocs/changelog/0.7.0.md b/mddocs/markdown/changelog/0.7.0.md similarity index 100% rename from mddocs/changelog/0.7.0.md rename to mddocs/markdown/changelog/0.7.0.md diff --git a/mddocs/changelog/0.7.1.md b/mddocs/markdown/changelog/0.7.1.md similarity index 100% rename from mddocs/changelog/0.7.1.md rename to mddocs/markdown/changelog/0.7.1.md diff --git a/mddocs/changelog/0.7.2.md b/mddocs/markdown/changelog/0.7.2.md similarity index 100% rename from mddocs/changelog/0.7.2.md rename to mddocs/markdown/changelog/0.7.2.md diff --git a/mddocs/changelog/0.8.0.md b/mddocs/markdown/changelog/0.8.0.md similarity index 100% rename from mddocs/changelog/0.8.0.md rename to mddocs/markdown/changelog/0.8.0.md diff --git a/mddocs/changelog/0.8.1.md b/mddocs/markdown/changelog/0.8.1.md similarity index 100% rename from mddocs/changelog/0.8.1.md rename to mddocs/markdown/changelog/0.8.1.md diff --git a/mddocs/changelog/0.9.0.md b/mddocs/markdown/changelog/0.9.0.md similarity index 100% rename from mddocs/changelog/0.9.0.md rename to mddocs/markdown/changelog/0.9.0.md diff --git a/mddocs/changelog/0.9.1.md b/mddocs/markdown/changelog/0.9.1.md similarity index 100% rename from mddocs/changelog/0.9.1.md rename to mddocs/markdown/changelog/0.9.1.md diff --git a/mddocs/changelog/0.9.2.md b/mddocs/markdown/changelog/0.9.2.md similarity index 100% rename from mddocs/changelog/0.9.2.md rename to mddocs/markdown/changelog/0.9.2.md diff --git a/mddocs/changelog/0.9.3.md b/mddocs/markdown/changelog/0.9.3.md similarity index 100% rename from mddocs/changelog/0.9.3.md rename to mddocs/markdown/changelog/0.9.3.md diff --git a/mddocs/changelog/0.9.4.md b/mddocs/markdown/changelog/0.9.4.md similarity index 100% rename from mddocs/changelog/0.9.4.md rename to mddocs/markdown/changelog/0.9.4.md diff --git a/mddocs/changelog/0.9.5.md b/mddocs/markdown/changelog/0.9.5.md similarity index 100% rename from mddocs/changelog/0.9.5.md rename to mddocs/markdown/changelog/0.9.5.md diff --git a/mddocs/markdown/changelog/DRAFT.md b/mddocs/markdown/changelog/DRAFT.md new file mode 100644 index 000000000..e69de29bb diff --git a/mddocs/changelog/NEXT_RELEASE.md b/mddocs/markdown/changelog/NEXT_RELEASE.md similarity index 53% rename from mddocs/changelog/NEXT_RELEASE.md rename to mddocs/markdown/changelog/NEXT_RELEASE.md index 10ebbf98b..8cf66aa33 100644 --- a/mddocs/changelog/NEXT_RELEASE.md +++ b/mddocs/markdown/changelog/NEXT_RELEASE.md @@ -1,2 +1 @@ - \ No newline at end of file diff --git a/mddocs/changelog/index.md b/mddocs/markdown/changelog/index.md similarity index 98% rename from mddocs/changelog/index.md rename to mddocs/markdown/changelog/index.md index 4a2663aee..37d5d85ae 100644 --- a/mddocs/changelog/index.md +++ b/mddocs/markdown/changelog/index.md @@ -1,4 +1,4 @@ -# Changelog +Changelog * [0.13.4 (2025-03-20)](0.13.4.md) * [0.13.3 (2025-03-11)](0.13.3.md) diff --git a/mddocs/markdown/concepts.md b/mddocs/markdown/concepts.md new file mode 100644 index 000000000..cf74e9eaa --- /dev/null +++ b/mddocs/markdown/concepts.md @@ -0,0 +1,340 @@ +# Concepts + +Here you can find detailed documentation about each one of the onETL concepts and how to use them. + +## Connection + +### Basics + +onETL is used to pull and push data into other systems, and so it has a first-class `Connection` concept for storing credentials that are used to communicate with external systems. + +A `Connection` is essentially a set of parameters, such as username, password, hostname. + +To create a connection to a specific storage type, you must use a class that matches the storage type. The class name is the same as the storage type name (`Oracle`, `MSSQL`, `SFTP`, etc): + +```python +from onetl.connection import SFTP + +sftp = SFTP( + host="sftp.test.com", + user="onetl", + password="onetl", +) +``` + +All connection types are inherited from the parent class `BaseConnection`. + +### Class diagram + +### DBConnection + +Classes inherited from `DBConnection` could be used for accessing databases. + +A `DBConnection` could be instantiated as follows: + +```python +from onetl.connection import MSSQL + +mssql = MSSQL( + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, +) +``` + +where **spark** is the current SparkSession. +`onETL` uses `Spark` and specific Java connectors under the hood to work with databases. + +For a description of other parameters, see the documentation for the [available DBConnections](connection/db_connection/index.md#db-connections). + +### FileConnection + +Classes inherited from `FileConnection` could be used to access files stored on the different file systems/file servers + +A `FileConnection` could be instantiated as follows: + +```python +from onetl.connection import SFTP + +sftp = SFTP( + host="sftp.test.com", + user="onetl", + password="onetl", +) +``` + +For a description of other parameters, see the documentation for the [available FileConnections](connection/file_connection/index.md#file-connections). + +### FileDFConnection + +Classes inherited from `FileDFConnection` could be used for accessing files as Spark DataFrames. + +A `FileDFConnection` could be instantiated as follows: + +```python +from onetl.connection import SparkHDFS + +spark_hdfs = SparkHDFS( + host="namenode1.domain.com", + cluster="mycluster", + spark=spark, +) +``` + +where **spark** is the current SparkSession. +`onETL` uses `Spark` and specific Java connectors under the hood to work with DataFrames. + +For a description of other parameters, see the documentation for the [available FileDFConnections](connection/file_df_connection/index.md#file-df-connections). + +### Checking connection availability + +Once you have created a connection, you can check the database/filesystem availability using the method `check()`: + +```python +mssql.check() +sftp.check() +spark_hdfs.check() +``` + +It will raise an exception if database/filesystem cannot be accessed. + +This method returns connection itself, so you can create connection and immediately check its availability: + +```Python +mssql = MSSQL( + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, +).check() # <-- +``` + +## Extract/Load data + +### Basics + +As we said above, onETL is used to extract data from and load data into remote systems. + +onETL provides several classes for this: + +> * [DBReader](db/db_reader.md#db-reader) +> * [DBWriter](db/db_writer.md#db-writer) +> * [FileDFReader](file_df/file_df_reader/file_df_reader.md#file-df-reader) +> * [FileDFWriter](file_df/file_df_writer/file_df_writer.md#file-df-writer) +> * [FileDownloader](file/file_downloader/file_downloader.md#file-downloader) +> * [FileUploader](file/file_uploader/file_uploader.md#file-uploader) +> * [FileMover](file/file_mover/file_mover.md#file-mover) + +All of these classes have a method `run()` that starts extracting/loading the data: + +```python +from onetl.db import DBReader, DBWriter + +reader = DBReader( + connection=mssql, + source="dbo.demo_table", + columns=["column_1", "column_2"], +) + +# Read data as Spark DataFrame +df = reader.run() + +db_writer = DBWriter( + connection=hive, + target="dl_sb.demo_table", +) + +# Save Spark DataFrame to Hive table +writer.run(df) +``` + +### Extract data + +To extract data you can use classes: + +| | Use case | Connection | `run()` gets | `run()` returns | +|-------------------------------------------------------------------------|-------------------------------------------|------------------------------------------------------------------------------------|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------| +| [DBReader](db/db_reader.md#db-reader) | Reading data from a database | Any [DBConnection](connection/db_connection/index.md#db-connections) | - | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | +| [FileDFReader](file_df/file_df_reader/file_df_reader.md#file-df-reader) | Read data from a file or set of files | Any [FileDFConnection](connection/file_df_connection/index.md#file-df-connections) | No input, or List[File path on FileSystem] | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | +| [FileDownloader](db/db_reader.md#db-reader) | Download files from remote FS to local FS | Any [FileConnection](connection/file_connection/index.md#file-connections) | No input, or List[File path on remote FileSystem] | [DownloadResult](file/file_downloader/result.md#file-downloader-result) | + +### Load data + +To load data you can use classes: + +| | Use case | Connection | `run()` gets | `run()` returns | +|-------------------------------------------------------------------|----------------------------------------------|------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------| +| [DBWriter](db/db_writer.md#db-writer) | Writing data from a DataFrame to a database | Any [DBConnection](connection/db_connection/index.md#db-connections) | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | None | +| [FileDFWriter](db/db_writer.md#db-writer) | Writing data from a DataFrame to a folder | Any [FileDFConnection](connection/file_df_connection/index.md#file-df-connections) | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | None | +| [FileUploader](file/file_uploader/file_uploader.md#file-uploader) | Uploading files from a local FS to remote FS | Any [FileConnection](connection/file_connection/index.md#file-connections) | List[File path on local FileSystem] | [UploadResult](file/file_uploader/result.md#file-uploader-result) | + +### Manipulate data + +To manipulate data you can use classes: + +| | Use case | Connection | `run()` gets | `run()` returns | +|-------------------------------------------------------|---------------------------------------------|----------------------------------------------------------------------------|--------------------------------------|-----------------------------------------------------------| +| [FileMover](file/file_mover/file_mover.md#file-mover) | Move files between directories in remote FS | Any [FileConnection](connection/file_connection/index.md#file-connections) | List[File path on remote FileSystem] | [MoveResult](file/file_mover/result.md#file-mover-result) | + +### Options + +Extract and load classes have a `options` parameter, which has a special meaning: + +> * all other parameters - *WHAT* we extract / *WHERE* we load to +> * `options` parameter - *HOW* we extract/load data +```python +db_reader = DBReader( + # WHAT do we read: + connection=mssql, + source="dbo.demo_table", # some table from MSSQL + columns=["column_1", "column_2"], # but only specific set of columns + where="column_2 > 1000", # only rows matching the clause + # HOW do we read: + options=MSSQL.ReadOptions( + numPartitions=10, # read in 10 parallel jobs + partitionColumn="id", # balance data read by assigning each job a part of data using `hash(id) mod N` expression + partitioningMode="hash", + fetchsize=1000, # each job will fetch block of 1000 rows each on every read attempt + ), +) + +db_writer = DBWriter( + # WHERE do we write to - to some table in Hive + connection=hive, + target="dl_sb.demo_table", + # HOW do we write - overwrite all the data in the existing table + options=Hive.WriteOptions(if_exists="replace_entire_table"), +) + +file_downloader = FileDownloader( + # WHAT do we download - files from some dir in SFTP + connection=sftp, + source_path="/source", + filters=[Glob("*.csv")], # only CSV files + limits=[MaxFilesCount(1000)], # 1000 files max + # WHERE do we download to - a specific dir on local FS + local_path="/some", + # HOW do we download: + options=FileDownloader.Options( + delete_source=True, # after downloading each file remove it from source_path + if_exists="replace_file", # replace existing files in the local_path + ), +) + +file_uploader = FileUploader( + # WHAT do we upload - files from some local dir + local_path="/source", + # WHERE do we upload to- specific remote dir in HDFS + connection=hdfs, + target_path="/some", + # HOW do we upload: + options=FileUploader.Options( + delete_local=True, # after uploading each file remove it from local_path + if_exists="replace_file", # replace existing files in the target_path + ), +) + +file_mover = FileMover( + # WHAT do we move - files in some remote dir in HDFS + source_path="/source", + connection=hdfs, + # WHERE do we move files to + target_path="/some", # a specific remote dir within the same HDFS connection + # HOW do we load - replace existing files in the target_path + options=FileMover.Options(if_exists="replace_file"), +) + +file_df_reader = FileDFReader( + # WHAT do we read - *.csv files from some dir in S3 + connection=s3, + source_path="/source", + file_format=CSV(), + # HOW do we read - load files from /source/*.csv, not from /source/nested/*.csv + options=FileDFReader.Options(recursive=False), +) + +file_df_writer = FileDFWriter( + # WHERE do we write to - as .csv files in some dir in S3 + connection=s3, + target_path="/target", + file_format=CSV(), + # HOW do we write - replace all existing files in /target, if exists + options=FileDFWriter.Options(if_exists="replace_entire_directory"), +) +``` + +More information about `options` could be found on [DB connection](connection/db_connection/index.md#db-connections). and +[File Downloader](file/file_downloader/file_downloader.md#file-downloader) / [File Uploader](file/file_uploader/file_uploader.md#file-uploader) / [File Mover](file/file_mover/file_mover.md#file-mover) / [FileDF Reader](file_df/file_df_reader/file_df_reader.md#file-df-reader) / [FileDF Writer](file_df/file_df_writer/file_df_writer.md#file-df-writer) documentation + +### Read Strategies + +onETL have several builtin strategies for reading data: + +1. [Snapshot strategy](strategy/snapshot_strategy.html) (default strategy) +2. [Incremental strategy](strategy/incremental_strategy.html) +3. [Snapshot batch strategy](strategy/snapshot_batch_strategy.html) +4. [Incremental batch strategy](strategy/incremental_batch_strategy.html) + +For example, an incremental strategy allows you to get only new data from the table: + +```python +from onetl.strategy import IncrementalStrategy + +reader = DBReader( + connection=mssql, + source="dbo.demo_table", + hwm_column="id", # detect new data based on value of "id" column +) + +# first run +with IncrementalStrategy(): + df = reader.run() + +sleep(3600) + +# second run +with IncrementalStrategy(): + # only rows, that appeared in the source since previous run + df = reader.run() +``` + +or get only files which were not downloaded before: + +```python +from onetl.strategy import IncrementalStrategy + +file_downloader = FileDownloader( + connection=sftp, + source_path="/remote", + local_path="/local", + hwm_type="file_list", # save all downloaded files to a list, and exclude files already present in this list +) + +# first run +with IncrementalStrategy(): + files = file_downloader.run() + +sleep(3600) + +# second run +with IncrementalStrategy(): + # only files, that appeared in the source since previous run + files = file_downloader.run() +``` + +Most of strategies are based on [HWM](hwm_store/index.md#hwm), Please check each strategy documentation for more details + +### Why just not use Connection class for extract/load? + +Connections are very simple, they have only a set of some basic operations, +like `mkdir`, `remove_file`, `get_table_schema`, and so on. + +High-level operations, like +: * [Read Strategies](strategy/index.md#strategy) support + * Handling metadata push/pull + * Handling different options, like `if_exists="replace_file"` in case of file download/upload + +is moved to a separate class which calls the connection object methods to perform some complex logic. diff --git a/mddocs/connection/db_connection/clickhouse/connection.md b/mddocs/markdown/connection/db_connection/clickhouse/connection.md similarity index 100% rename from mddocs/connection/db_connection/clickhouse/connection.md rename to mddocs/markdown/connection/db_connection/clickhouse/connection.md diff --git a/mddocs/connection/db_connection/clickhouse/execute.md b/mddocs/markdown/connection/db_connection/clickhouse/execute.md similarity index 100% rename from mddocs/connection/db_connection/clickhouse/execute.md rename to mddocs/markdown/connection/db_connection/clickhouse/execute.md diff --git a/mddocs/connection/db_connection/clickhouse/index.md b/mddocs/markdown/connection/db_connection/clickhouse/index.md similarity index 100% rename from mddocs/connection/db_connection/clickhouse/index.md rename to mddocs/markdown/connection/db_connection/clickhouse/index.md diff --git a/mddocs/connection/db_connection/clickhouse/prerequisites.md b/mddocs/markdown/connection/db_connection/clickhouse/prerequisites.md similarity index 100% rename from mddocs/connection/db_connection/clickhouse/prerequisites.md rename to mddocs/markdown/connection/db_connection/clickhouse/prerequisites.md diff --git a/mddocs/connection/db_connection/clickhouse/read.md b/mddocs/markdown/connection/db_connection/clickhouse/read.md similarity index 100% rename from mddocs/connection/db_connection/clickhouse/read.md rename to mddocs/markdown/connection/db_connection/clickhouse/read.md diff --git a/mddocs/connection/db_connection/clickhouse/sql.md b/mddocs/markdown/connection/db_connection/clickhouse/sql.md similarity index 100% rename from mddocs/connection/db_connection/clickhouse/sql.md rename to mddocs/markdown/connection/db_connection/clickhouse/sql.md diff --git a/mddocs/connection/db_connection/clickhouse/types.md b/mddocs/markdown/connection/db_connection/clickhouse/types.md similarity index 100% rename from mddocs/connection/db_connection/clickhouse/types.md rename to mddocs/markdown/connection/db_connection/clickhouse/types.md diff --git a/mddocs/connection/db_connection/clickhouse/write.md b/mddocs/markdown/connection/db_connection/clickhouse/write.md similarity index 100% rename from mddocs/connection/db_connection/clickhouse/write.md rename to mddocs/markdown/connection/db_connection/clickhouse/write.md diff --git a/mddocs/connection/db_connection/greenplum/connection.md b/mddocs/markdown/connection/db_connection/greenplum/connection.md similarity index 100% rename from mddocs/connection/db_connection/greenplum/connection.md rename to mddocs/markdown/connection/db_connection/greenplum/connection.md diff --git a/mddocs/connection/db_connection/greenplum/execute.md b/mddocs/markdown/connection/db_connection/greenplum/execute.md similarity index 100% rename from mddocs/connection/db_connection/greenplum/execute.md rename to mddocs/markdown/connection/db_connection/greenplum/execute.md diff --git a/mddocs/connection/db_connection/greenplum/index.md b/mddocs/markdown/connection/db_connection/greenplum/index.md similarity index 100% rename from mddocs/connection/db_connection/greenplum/index.md rename to mddocs/markdown/connection/db_connection/greenplum/index.md diff --git a/mddocs/connection/db_connection/greenplum/prerequisites.md b/mddocs/markdown/connection/db_connection/greenplum/prerequisites.md similarity index 100% rename from mddocs/connection/db_connection/greenplum/prerequisites.md rename to mddocs/markdown/connection/db_connection/greenplum/prerequisites.md diff --git a/mddocs/connection/db_connection/greenplum/read.md b/mddocs/markdown/connection/db_connection/greenplum/read.md similarity index 100% rename from mddocs/connection/db_connection/greenplum/read.md rename to mddocs/markdown/connection/db_connection/greenplum/read.md diff --git a/mddocs/connection/db_connection/greenplum/types.md b/mddocs/markdown/connection/db_connection/greenplum/types.md similarity index 100% rename from mddocs/connection/db_connection/greenplum/types.md rename to mddocs/markdown/connection/db_connection/greenplum/types.md diff --git a/mddocs/connection/db_connection/greenplum/write.md b/mddocs/markdown/connection/db_connection/greenplum/write.md similarity index 100% rename from mddocs/connection/db_connection/greenplum/write.md rename to mddocs/markdown/connection/db_connection/greenplum/write.md diff --git a/mddocs/connection/db_connection/hive/connection.md b/mddocs/markdown/connection/db_connection/hive/connection.md similarity index 100% rename from mddocs/connection/db_connection/hive/connection.md rename to mddocs/markdown/connection/db_connection/hive/connection.md diff --git a/mddocs/connection/db_connection/hive/execute.md b/mddocs/markdown/connection/db_connection/hive/execute.md similarity index 100% rename from mddocs/connection/db_connection/hive/execute.md rename to mddocs/markdown/connection/db_connection/hive/execute.md diff --git a/mddocs/connection/db_connection/hive/index.md b/mddocs/markdown/connection/db_connection/hive/index.md similarity index 100% rename from mddocs/connection/db_connection/hive/index.md rename to mddocs/markdown/connection/db_connection/hive/index.md diff --git a/mddocs/connection/db_connection/hive/prerequisites.md b/mddocs/markdown/connection/db_connection/hive/prerequisites.md similarity index 100% rename from mddocs/connection/db_connection/hive/prerequisites.md rename to mddocs/markdown/connection/db_connection/hive/prerequisites.md diff --git a/mddocs/connection/db_connection/hive/read.md b/mddocs/markdown/connection/db_connection/hive/read.md similarity index 100% rename from mddocs/connection/db_connection/hive/read.md rename to mddocs/markdown/connection/db_connection/hive/read.md diff --git a/mddocs/connection/db_connection/hive/slots.md b/mddocs/markdown/connection/db_connection/hive/slots.md similarity index 100% rename from mddocs/connection/db_connection/hive/slots.md rename to mddocs/markdown/connection/db_connection/hive/slots.md diff --git a/mddocs/connection/db_connection/hive/sql.md b/mddocs/markdown/connection/db_connection/hive/sql.md similarity index 100% rename from mddocs/connection/db_connection/hive/sql.md rename to mddocs/markdown/connection/db_connection/hive/sql.md diff --git a/mddocs/connection/db_connection/hive/write.md b/mddocs/markdown/connection/db_connection/hive/write.md similarity index 100% rename from mddocs/connection/db_connection/hive/write.md rename to mddocs/markdown/connection/db_connection/hive/write.md diff --git a/mddocs/connection/db_connection/index.md b/mddocs/markdown/connection/db_connection/index.md similarity index 94% rename from mddocs/connection/db_connection/index.md rename to mddocs/markdown/connection/db_connection/index.md index 6bb1ad53b..cd8f76477 100644 --- a/mddocs/connection/db_connection/index.md +++ b/mddocs/markdown/connection/db_connection/index.md @@ -2,6 +2,8 @@ # DB Connections +# DB Connections + * [Clickhouse](clickhouse/index.md) * [Greenplum](greenplum/index.md) * [Kafka](kafka/index.md) diff --git a/mddocs/connection/db_connection/kafka/auth.md b/mddocs/markdown/connection/db_connection/kafka/auth.md similarity index 100% rename from mddocs/connection/db_connection/kafka/auth.md rename to mddocs/markdown/connection/db_connection/kafka/auth.md diff --git a/mddocs/connection/db_connection/kafka/basic_auth.md b/mddocs/markdown/connection/db_connection/kafka/basic_auth.md similarity index 100% rename from mddocs/connection/db_connection/kafka/basic_auth.md rename to mddocs/markdown/connection/db_connection/kafka/basic_auth.md diff --git a/mddocs/connection/db_connection/kafka/connection.md b/mddocs/markdown/connection/db_connection/kafka/connection.md similarity index 100% rename from mddocs/connection/db_connection/kafka/connection.md rename to mddocs/markdown/connection/db_connection/kafka/connection.md diff --git a/mddocs/connection/db_connection/kafka/index.md b/mddocs/markdown/connection/db_connection/kafka/index.md similarity index 100% rename from mddocs/connection/db_connection/kafka/index.md rename to mddocs/markdown/connection/db_connection/kafka/index.md diff --git a/mddocs/connection/db_connection/kafka/kerberos_auth.md b/mddocs/markdown/connection/db_connection/kafka/kerberos_auth.md similarity index 100% rename from mddocs/connection/db_connection/kafka/kerberos_auth.md rename to mddocs/markdown/connection/db_connection/kafka/kerberos_auth.md diff --git a/mddocs/connection/db_connection/kafka/plaintext_protocol.md b/mddocs/markdown/connection/db_connection/kafka/plaintext_protocol.md similarity index 100% rename from mddocs/connection/db_connection/kafka/plaintext_protocol.md rename to mddocs/markdown/connection/db_connection/kafka/plaintext_protocol.md diff --git a/mddocs/connection/db_connection/kafka/prerequisites.md b/mddocs/markdown/connection/db_connection/kafka/prerequisites.md similarity index 100% rename from mddocs/connection/db_connection/kafka/prerequisites.md rename to mddocs/markdown/connection/db_connection/kafka/prerequisites.md diff --git a/mddocs/connection/db_connection/kafka/protocol.md b/mddocs/markdown/connection/db_connection/kafka/protocol.md similarity index 100% rename from mddocs/connection/db_connection/kafka/protocol.md rename to mddocs/markdown/connection/db_connection/kafka/protocol.md diff --git a/mddocs/connection/db_connection/kafka/read.md b/mddocs/markdown/connection/db_connection/kafka/read.md similarity index 100% rename from mddocs/connection/db_connection/kafka/read.md rename to mddocs/markdown/connection/db_connection/kafka/read.md diff --git a/mddocs/connection/db_connection/kafka/scram_auth.md b/mddocs/markdown/connection/db_connection/kafka/scram_auth.md similarity index 100% rename from mddocs/connection/db_connection/kafka/scram_auth.md rename to mddocs/markdown/connection/db_connection/kafka/scram_auth.md diff --git a/mddocs/connection/db_connection/kafka/slots.md b/mddocs/markdown/connection/db_connection/kafka/slots.md similarity index 100% rename from mddocs/connection/db_connection/kafka/slots.md rename to mddocs/markdown/connection/db_connection/kafka/slots.md diff --git a/mddocs/connection/db_connection/kafka/ssl_protocol.md b/mddocs/markdown/connection/db_connection/kafka/ssl_protocol.md similarity index 100% rename from mddocs/connection/db_connection/kafka/ssl_protocol.md rename to mddocs/markdown/connection/db_connection/kafka/ssl_protocol.md diff --git a/mddocs/connection/db_connection/kafka/troubleshooting.md b/mddocs/markdown/connection/db_connection/kafka/troubleshooting.md similarity index 100% rename from mddocs/connection/db_connection/kafka/troubleshooting.md rename to mddocs/markdown/connection/db_connection/kafka/troubleshooting.md diff --git a/mddocs/connection/db_connection/kafka/write.md b/mddocs/markdown/connection/db_connection/kafka/write.md similarity index 100% rename from mddocs/connection/db_connection/kafka/write.md rename to mddocs/markdown/connection/db_connection/kafka/write.md diff --git a/mddocs/connection/db_connection/mongodb/connection.md b/mddocs/markdown/connection/db_connection/mongodb/connection.md similarity index 100% rename from mddocs/connection/db_connection/mongodb/connection.md rename to mddocs/markdown/connection/db_connection/mongodb/connection.md diff --git a/mddocs/connection/db_connection/mongodb/index.md b/mddocs/markdown/connection/db_connection/mongodb/index.md similarity index 100% rename from mddocs/connection/db_connection/mongodb/index.md rename to mddocs/markdown/connection/db_connection/mongodb/index.md diff --git a/mddocs/connection/db_connection/mongodb/pipeline.md b/mddocs/markdown/connection/db_connection/mongodb/pipeline.md similarity index 100% rename from mddocs/connection/db_connection/mongodb/pipeline.md rename to mddocs/markdown/connection/db_connection/mongodb/pipeline.md diff --git a/mddocs/connection/db_connection/mongodb/prerequisites.md b/mddocs/markdown/connection/db_connection/mongodb/prerequisites.md similarity index 100% rename from mddocs/connection/db_connection/mongodb/prerequisites.md rename to mddocs/markdown/connection/db_connection/mongodb/prerequisites.md diff --git a/mddocs/connection/db_connection/mongodb/read.md b/mddocs/markdown/connection/db_connection/mongodb/read.md similarity index 100% rename from mddocs/connection/db_connection/mongodb/read.md rename to mddocs/markdown/connection/db_connection/mongodb/read.md diff --git a/mddocs/connection/db_connection/mongodb/types.md b/mddocs/markdown/connection/db_connection/mongodb/types.md similarity index 100% rename from mddocs/connection/db_connection/mongodb/types.md rename to mddocs/markdown/connection/db_connection/mongodb/types.md diff --git a/mddocs/connection/db_connection/mongodb/write.md b/mddocs/markdown/connection/db_connection/mongodb/write.md similarity index 100% rename from mddocs/connection/db_connection/mongodb/write.md rename to mddocs/markdown/connection/db_connection/mongodb/write.md diff --git a/mddocs/connection/db_connection/mssql/connection.md b/mddocs/markdown/connection/db_connection/mssql/connection.md similarity index 100% rename from mddocs/connection/db_connection/mssql/connection.md rename to mddocs/markdown/connection/db_connection/mssql/connection.md diff --git a/mddocs/connection/db_connection/mssql/execute.md b/mddocs/markdown/connection/db_connection/mssql/execute.md similarity index 100% rename from mddocs/connection/db_connection/mssql/execute.md rename to mddocs/markdown/connection/db_connection/mssql/execute.md diff --git a/mddocs/connection/db_connection/mssql/index.md b/mddocs/markdown/connection/db_connection/mssql/index.md similarity index 100% rename from mddocs/connection/db_connection/mssql/index.md rename to mddocs/markdown/connection/db_connection/mssql/index.md diff --git a/mddocs/connection/db_connection/mssql/prerequisites.md b/mddocs/markdown/connection/db_connection/mssql/prerequisites.md similarity index 100% rename from mddocs/connection/db_connection/mssql/prerequisites.md rename to mddocs/markdown/connection/db_connection/mssql/prerequisites.md diff --git a/mddocs/connection/db_connection/mssql/read.md b/mddocs/markdown/connection/db_connection/mssql/read.md similarity index 100% rename from mddocs/connection/db_connection/mssql/read.md rename to mddocs/markdown/connection/db_connection/mssql/read.md diff --git a/mddocs/connection/db_connection/mssql/sql.md b/mddocs/markdown/connection/db_connection/mssql/sql.md similarity index 100% rename from mddocs/connection/db_connection/mssql/sql.md rename to mddocs/markdown/connection/db_connection/mssql/sql.md diff --git a/mddocs/connection/db_connection/mssql/types.md b/mddocs/markdown/connection/db_connection/mssql/types.md similarity index 100% rename from mddocs/connection/db_connection/mssql/types.md rename to mddocs/markdown/connection/db_connection/mssql/types.md diff --git a/mddocs/connection/db_connection/mssql/write.md b/mddocs/markdown/connection/db_connection/mssql/write.md similarity index 100% rename from mddocs/connection/db_connection/mssql/write.md rename to mddocs/markdown/connection/db_connection/mssql/write.md diff --git a/mddocs/connection/db_connection/mysql/connection.md b/mddocs/markdown/connection/db_connection/mysql/connection.md similarity index 100% rename from mddocs/connection/db_connection/mysql/connection.md rename to mddocs/markdown/connection/db_connection/mysql/connection.md diff --git a/mddocs/connection/db_connection/mysql/execute.md b/mddocs/markdown/connection/db_connection/mysql/execute.md similarity index 100% rename from mddocs/connection/db_connection/mysql/execute.md rename to mddocs/markdown/connection/db_connection/mysql/execute.md diff --git a/mddocs/connection/db_connection/mysql/index.md b/mddocs/markdown/connection/db_connection/mysql/index.md similarity index 100% rename from mddocs/connection/db_connection/mysql/index.md rename to mddocs/markdown/connection/db_connection/mysql/index.md diff --git a/mddocs/connection/db_connection/mysql/prerequisites.md b/mddocs/markdown/connection/db_connection/mysql/prerequisites.md similarity index 100% rename from mddocs/connection/db_connection/mysql/prerequisites.md rename to mddocs/markdown/connection/db_connection/mysql/prerequisites.md diff --git a/mddocs/connection/db_connection/mysql/read.md b/mddocs/markdown/connection/db_connection/mysql/read.md similarity index 100% rename from mddocs/connection/db_connection/mysql/read.md rename to mddocs/markdown/connection/db_connection/mysql/read.md diff --git a/mddocs/connection/db_connection/mysql/sql.md b/mddocs/markdown/connection/db_connection/mysql/sql.md similarity index 100% rename from mddocs/connection/db_connection/mysql/sql.md rename to mddocs/markdown/connection/db_connection/mysql/sql.md diff --git a/mddocs/connection/db_connection/mysql/types.md b/mddocs/markdown/connection/db_connection/mysql/types.md similarity index 100% rename from mddocs/connection/db_connection/mysql/types.md rename to mddocs/markdown/connection/db_connection/mysql/types.md diff --git a/mddocs/connection/db_connection/mysql/write.md b/mddocs/markdown/connection/db_connection/mysql/write.md similarity index 100% rename from mddocs/connection/db_connection/mysql/write.md rename to mddocs/markdown/connection/db_connection/mysql/write.md diff --git a/mddocs/connection/db_connection/oracle/connection.md b/mddocs/markdown/connection/db_connection/oracle/connection.md similarity index 100% rename from mddocs/connection/db_connection/oracle/connection.md rename to mddocs/markdown/connection/db_connection/oracle/connection.md diff --git a/mddocs/connection/db_connection/oracle/execute.md b/mddocs/markdown/connection/db_connection/oracle/execute.md similarity index 100% rename from mddocs/connection/db_connection/oracle/execute.md rename to mddocs/markdown/connection/db_connection/oracle/execute.md diff --git a/mddocs/connection/db_connection/oracle/index.md b/mddocs/markdown/connection/db_connection/oracle/index.md similarity index 100% rename from mddocs/connection/db_connection/oracle/index.md rename to mddocs/markdown/connection/db_connection/oracle/index.md diff --git a/mddocs/connection/db_connection/oracle/prerequisites.md b/mddocs/markdown/connection/db_connection/oracle/prerequisites.md similarity index 100% rename from mddocs/connection/db_connection/oracle/prerequisites.md rename to mddocs/markdown/connection/db_connection/oracle/prerequisites.md diff --git a/mddocs/connection/db_connection/oracle/read.md b/mddocs/markdown/connection/db_connection/oracle/read.md similarity index 100% rename from mddocs/connection/db_connection/oracle/read.md rename to mddocs/markdown/connection/db_connection/oracle/read.md diff --git a/mddocs/connection/db_connection/oracle/sql.md b/mddocs/markdown/connection/db_connection/oracle/sql.md similarity index 100% rename from mddocs/connection/db_connection/oracle/sql.md rename to mddocs/markdown/connection/db_connection/oracle/sql.md diff --git a/mddocs/connection/db_connection/oracle/types.md b/mddocs/markdown/connection/db_connection/oracle/types.md similarity index 100% rename from mddocs/connection/db_connection/oracle/types.md rename to mddocs/markdown/connection/db_connection/oracle/types.md diff --git a/mddocs/connection/db_connection/oracle/write.md b/mddocs/markdown/connection/db_connection/oracle/write.md similarity index 100% rename from mddocs/connection/db_connection/oracle/write.md rename to mddocs/markdown/connection/db_connection/oracle/write.md diff --git a/mddocs/connection/db_connection/postgres/connection.md b/mddocs/markdown/connection/db_connection/postgres/connection.md similarity index 100% rename from mddocs/connection/db_connection/postgres/connection.md rename to mddocs/markdown/connection/db_connection/postgres/connection.md diff --git a/mddocs/connection/db_connection/postgres/execute.md b/mddocs/markdown/connection/db_connection/postgres/execute.md similarity index 100% rename from mddocs/connection/db_connection/postgres/execute.md rename to mddocs/markdown/connection/db_connection/postgres/execute.md diff --git a/mddocs/connection/db_connection/postgres/index.md b/mddocs/markdown/connection/db_connection/postgres/index.md similarity index 100% rename from mddocs/connection/db_connection/postgres/index.md rename to mddocs/markdown/connection/db_connection/postgres/index.md diff --git a/mddocs/connection/db_connection/postgres/prerequisites.md b/mddocs/markdown/connection/db_connection/postgres/prerequisites.md similarity index 100% rename from mddocs/connection/db_connection/postgres/prerequisites.md rename to mddocs/markdown/connection/db_connection/postgres/prerequisites.md diff --git a/mddocs/connection/db_connection/postgres/read.md b/mddocs/markdown/connection/db_connection/postgres/read.md similarity index 100% rename from mddocs/connection/db_connection/postgres/read.md rename to mddocs/markdown/connection/db_connection/postgres/read.md diff --git a/mddocs/connection/db_connection/postgres/sql.md b/mddocs/markdown/connection/db_connection/postgres/sql.md similarity index 100% rename from mddocs/connection/db_connection/postgres/sql.md rename to mddocs/markdown/connection/db_connection/postgres/sql.md diff --git a/mddocs/connection/db_connection/postgres/types.md b/mddocs/markdown/connection/db_connection/postgres/types.md similarity index 100% rename from mddocs/connection/db_connection/postgres/types.md rename to mddocs/markdown/connection/db_connection/postgres/types.md diff --git a/mddocs/connection/db_connection/postgres/write.md b/mddocs/markdown/connection/db_connection/postgres/write.md similarity index 100% rename from mddocs/connection/db_connection/postgres/write.md rename to mddocs/markdown/connection/db_connection/postgres/write.md diff --git a/mddocs/connection/db_connection/teradata/connection.md b/mddocs/markdown/connection/db_connection/teradata/connection.md similarity index 100% rename from mddocs/connection/db_connection/teradata/connection.md rename to mddocs/markdown/connection/db_connection/teradata/connection.md diff --git a/mddocs/connection/db_connection/teradata/execute.md b/mddocs/markdown/connection/db_connection/teradata/execute.md similarity index 100% rename from mddocs/connection/db_connection/teradata/execute.md rename to mddocs/markdown/connection/db_connection/teradata/execute.md diff --git a/mddocs/connection/db_connection/teradata/index.md b/mddocs/markdown/connection/db_connection/teradata/index.md similarity index 100% rename from mddocs/connection/db_connection/teradata/index.md rename to mddocs/markdown/connection/db_connection/teradata/index.md diff --git a/mddocs/connection/db_connection/teradata/prerequisites.md b/mddocs/markdown/connection/db_connection/teradata/prerequisites.md similarity index 100% rename from mddocs/connection/db_connection/teradata/prerequisites.md rename to mddocs/markdown/connection/db_connection/teradata/prerequisites.md diff --git a/mddocs/connection/db_connection/teradata/read.md b/mddocs/markdown/connection/db_connection/teradata/read.md similarity index 100% rename from mddocs/connection/db_connection/teradata/read.md rename to mddocs/markdown/connection/db_connection/teradata/read.md diff --git a/mddocs/connection/db_connection/teradata/sql.md b/mddocs/markdown/connection/db_connection/teradata/sql.md similarity index 100% rename from mddocs/connection/db_connection/teradata/sql.md rename to mddocs/markdown/connection/db_connection/teradata/sql.md diff --git a/mddocs/connection/db_connection/teradata/write.md b/mddocs/markdown/connection/db_connection/teradata/write.md similarity index 100% rename from mddocs/connection/db_connection/teradata/write.md rename to mddocs/markdown/connection/db_connection/teradata/write.md diff --git a/mddocs/connection/file_connection/ftp.md b/mddocs/markdown/connection/file_connection/ftp.md similarity index 100% rename from mddocs/connection/file_connection/ftp.md rename to mddocs/markdown/connection/file_connection/ftp.md diff --git a/mddocs/connection/file_connection/ftps.md b/mddocs/markdown/connection/file_connection/ftps.md similarity index 100% rename from mddocs/connection/file_connection/ftps.md rename to mddocs/markdown/connection/file_connection/ftps.md diff --git a/mddocs/connection/file_connection/hdfs/connection.md b/mddocs/markdown/connection/file_connection/hdfs/connection.md similarity index 100% rename from mddocs/connection/file_connection/hdfs/connection.md rename to mddocs/markdown/connection/file_connection/hdfs/connection.md diff --git a/mddocs/connection/file_connection/hdfs/index.md b/mddocs/markdown/connection/file_connection/hdfs/index.md similarity index 100% rename from mddocs/connection/file_connection/hdfs/index.md rename to mddocs/markdown/connection/file_connection/hdfs/index.md diff --git a/mddocs/connection/file_connection/hdfs/slots.md b/mddocs/markdown/connection/file_connection/hdfs/slots.md similarity index 100% rename from mddocs/connection/file_connection/hdfs/slots.md rename to mddocs/markdown/connection/file_connection/hdfs/slots.md diff --git a/mddocs/connection/file_connection/index.md b/mddocs/markdown/connection/file_connection/index.md similarity index 100% rename from mddocs/connection/file_connection/index.md rename to mddocs/markdown/connection/file_connection/index.md diff --git a/mddocs/connection/file_connection/s3.md b/mddocs/markdown/connection/file_connection/s3.md similarity index 100% rename from mddocs/connection/file_connection/s3.md rename to mddocs/markdown/connection/file_connection/s3.md diff --git a/mddocs/connection/file_connection/samba.md b/mddocs/markdown/connection/file_connection/samba.md similarity index 100% rename from mddocs/connection/file_connection/samba.md rename to mddocs/markdown/connection/file_connection/samba.md diff --git a/mddocs/connection/file_connection/sftp.md b/mddocs/markdown/connection/file_connection/sftp.md similarity index 100% rename from mddocs/connection/file_connection/sftp.md rename to mddocs/markdown/connection/file_connection/sftp.md diff --git a/mddocs/connection/file_connection/webdav.md b/mddocs/markdown/connection/file_connection/webdav.md similarity index 100% rename from mddocs/connection/file_connection/webdav.md rename to mddocs/markdown/connection/file_connection/webdav.md diff --git a/mddocs/connection/file_df_connection/base.md b/mddocs/markdown/connection/file_df_connection/base.md similarity index 100% rename from mddocs/connection/file_df_connection/base.md rename to mddocs/markdown/connection/file_df_connection/base.md diff --git a/mddocs/connection/file_df_connection/index.md b/mddocs/markdown/connection/file_df_connection/index.md similarity index 100% rename from mddocs/connection/file_df_connection/index.md rename to mddocs/markdown/connection/file_df_connection/index.md diff --git a/mddocs/connection/file_df_connection/spark_hdfs/connection.md b/mddocs/markdown/connection/file_df_connection/spark_hdfs/connection.md similarity index 97% rename from mddocs/connection/file_df_connection/spark_hdfs/connection.md rename to mddocs/markdown/connection/file_df_connection/spark_hdfs/connection.md index 4f2364e0e..fe8e621a9 100644 --- a/mddocs/connection/file_df_connection/spark_hdfs/connection.md +++ b/mddocs/markdown/connection/file_df_connection/spark_hdfs/connection.md @@ -15,7 +15,7 @@ Before using this connector please take into account [Prerequisites](prerequisit Supports only reading files as Spark DataFrame and writing DataFrame to files. Does NOT support file operations, like create, delete, rename, etc. For these operations, -use `HDFS` connection. +use [`HDFS`](../../file_connection/hdfs/connection.md#onetl.connection.file_connection.hdfs.connection.HDFS) connection. #### Versionadded Added in version 0.9.0. diff --git a/mddocs/connection/file_df_connection/spark_hdfs/index.md b/mddocs/markdown/connection/file_df_connection/spark_hdfs/index.md similarity index 100% rename from mddocs/connection/file_df_connection/spark_hdfs/index.md rename to mddocs/markdown/connection/file_df_connection/spark_hdfs/index.md diff --git a/mddocs/connection/file_df_connection/spark_hdfs/prerequisites.md b/mddocs/markdown/connection/file_df_connection/spark_hdfs/prerequisites.md similarity index 100% rename from mddocs/connection/file_df_connection/spark_hdfs/prerequisites.md rename to mddocs/markdown/connection/file_df_connection/spark_hdfs/prerequisites.md diff --git a/mddocs/connection/file_df_connection/spark_hdfs/slots.md b/mddocs/markdown/connection/file_df_connection/spark_hdfs/slots.md similarity index 100% rename from mddocs/connection/file_df_connection/spark_hdfs/slots.md rename to mddocs/markdown/connection/file_df_connection/spark_hdfs/slots.md diff --git a/mddocs/connection/file_df_connection/spark_local_fs.md b/mddocs/markdown/connection/file_df_connection/spark_local_fs.md similarity index 100% rename from mddocs/connection/file_df_connection/spark_local_fs.md rename to mddocs/markdown/connection/file_df_connection/spark_local_fs.md diff --git a/mddocs/connection/file_df_connection/spark_s3/connection.md b/mddocs/markdown/connection/file_df_connection/spark_s3/connection.md similarity index 98% rename from mddocs/connection/file_df_connection/spark_s3/connection.md rename to mddocs/markdown/connection/file_df_connection/spark_s3/connection.md index 5b2c1db31..e51ec8421 100644 --- a/mddocs/connection/file_df_connection/spark_s3/connection.md +++ b/mddocs/markdown/connection/file_df_connection/spark_s3/connection.md @@ -16,7 +16,7 @@ Before using this connector please take into account [Prerequisites](prerequisit Supports only reading files as Spark DataFrame and writing DataFrame to files. Does NOT support file operations, like create, delete, rename, etc. For these operations, -use `S3` connection. +use [`S3`](../../file_connection/s3.md#onetl.connection.file_connection.s3.S3) connection. #### Versionadded Added in version 0.9.0. diff --git a/mddocs/connection/file_df_connection/spark_s3/index.md b/mddocs/markdown/connection/file_df_connection/spark_s3/index.md similarity index 100% rename from mddocs/connection/file_df_connection/spark_s3/index.md rename to mddocs/markdown/connection/file_df_connection/spark_s3/index.md diff --git a/mddocs/connection/file_df_connection/spark_s3/prerequisites.md b/mddocs/markdown/connection/file_df_connection/spark_s3/prerequisites.md similarity index 100% rename from mddocs/connection/file_df_connection/spark_s3/prerequisites.md rename to mddocs/markdown/connection/file_df_connection/spark_s3/prerequisites.md diff --git a/mddocs/connection/file_df_connection/spark_s3/troubleshooting.md b/mddocs/markdown/connection/file_df_connection/spark_s3/troubleshooting.md similarity index 100% rename from mddocs/connection/file_df_connection/spark_s3/troubleshooting.md rename to mddocs/markdown/connection/file_df_connection/spark_s3/troubleshooting.md diff --git a/mddocs/connection/index.md b/mddocs/markdown/connection/index.md similarity index 98% rename from mddocs/connection/index.md rename to mddocs/markdown/connection/index.md index 9775b0ccf..5ad28edef 100644 --- a/mddocs/connection/index.md +++ b/mddocs/markdown/connection/index.md @@ -1,6 +1,6 @@ -# DB Connection + DB Connection * [DB Connections](db_connection/index.md) * [Clickhouse](db_connection/clickhouse/index.md) diff --git a/mddocs/contributing.md b/mddocs/markdown/contributing.md similarity index 80% rename from mddocs/contributing.md rename to mddocs/markdown/contributing.md index 8cb8a1744..739798897 100644 --- a/mddocs/contributing.md +++ b/mddocs/markdown/contributing.md @@ -122,7 +122,9 @@ docker-compose run --rm onetl ./run_tests.sh -m mongodb -lsx -vvvv --log-cli-lev You can run interactive bash session and use it: ```bash -docker-compose run --rm onetl bash ./run_tests.sh -m mongodb -lsx -vvvv --log-cli-level=INFO +docker-compose run --rm onetl bash + +./run_tests.sh -m mongodb -lsx -vvvv --log-cli-level=INFO ``` See logs of test container: @@ -139,30 +141,27 @@ docker-compose --profile all down -v #### Without docker-compose -```{admonition} warning +#### WARNING To run HDFS tests locally you should add the following line to your `/etc/hosts` (file path depends on OS): -``` ```default # HDFS server returns container hostname as connection address, causing error in DNS resolution 127.0.0.1 hdfs ``` -```{admonition} note +#### NOTE To run Oracle tests you need to install [Oracle instantclient](https://www.oracle.com/database/technologies/instant-client.html), and pass its path to `ONETL_ORA_CLIENT_PATH` and `LD_LIBRARY_PATH` environment variables, e.g. `ONETL_ORA_CLIENT_PATH=/path/to/client64/lib`. It may also require to add the same path into `LD_LIBRARY_PATH` environment variable -``` -```{admonition} note +#### NOTE To run Greenplum tests, you should: * Download [VMware Greenplum connector for Spark](https://onetl.readthedocs.io/en/latest/connection/db_connection/greenplum/prerequisites.html) * Either move it to `~/.ivy2/jars/`, or pass file path to `CLASSPATH` * Set environment variable `ONETL_GP_PACKAGE_VERSION=local`. -``` Start all containers with dependencies: @@ -213,11 +212,17 @@ Then open in browser `docs/_build/index.html`. ## Review process -Please create a new GitHub issue for any significant changes and enhancements that you wish to make. Provide the feature you would like to see, why you need it, and how it will work. Discuss your ideas transparently and get community feedback before proceeding. +Please create a new GitHub issue for any significant changes and +enhancements that you wish to make. Provide the feature you would like +to see, why you need it, and how it will work. Discuss your ideas +transparently and get community feedback before proceeding. -Significant Changes that you wish to contribute to the project should be discussed first in a GitHub issue that clearly outlines the changes and benefits of the feature. +Significant Changes that you wish to contribute to the project should be +discussed first in a GitHub issue that clearly outlines the changes and +benefits of the feature. -Small Changes can directly be crafted and submitted to the GitHub Repository as a Pull Request. +Small Changes can directly be crafted and submitted to the GitHub +Repository as a Pull Request. ### Create pull request @@ -238,15 +243,35 @@ After pull request is created, it get a corresponding number, e.g. 123 (`pr_numb `onETL` uses [towncrier](https://pypi.org/project/towncrier/) for changelog management. -To submit a change note about your PR, add a text file into the [docs/changelog/next_release](./next_release) folder. It should contain an explanation of what applying this PR will change in the way end-users interact with the project. One sentence is usually enough but feel free to add as many details as you feel necessary for the users to understand what it means. - -**Use the past tense** for the text in your fragment because, combined with others, it will be a part of the “news digest” telling the readers **what changed** in a specific version of the library *since the previous version*. - -You should also use reStructuredText syntax for highlighting code (inline or block), linking parts of the docs or external sites. If you wish to sign your change, feel free to add `-- by :user:`github-username`` at the end (replace `github-username` with your own!). - -Finally, name your file following the convention that Towncrier understands: it should start with the number of an issue or a PR followed by a dot, then add a patch type, like `feature`, `doc`, `misc` etc., and add `.rst` as a suffix. If you need to add more than one fragment, you may add an optional sequence number (delimited with another period) between the type and the suffix. - -In general the name will follow `..rst` pattern, where the categories are: +To submit a change note about your PR, add a text file into the +[docs/changelog/next_release](./next_release) folder. It should contain an +explanation of what applying this PR will change in the way +end-users interact with the project. One sentence is usually +enough but feel free to add as many details as you feel necessary +for the users to understand what it means. + +**Use the past tense** for the text in your fragment because, +combined with others, it will be a part of the “news digest” +telling the readers **what changed** in a specific version of +the library *since the previous version*. + +You should also use +reStructuredText syntax for highlighting code (inline or block), +linking parts of the docs or external sites. +If you wish to sign your change, feel free to add `-- by +:user:`github-username`` at the end (replace `github-username` +with your own!). + +Finally, name your file following the convention that Towncrier +understands: it should start with the number of an issue or a +PR followed by a dot, then add a patch type, like `feature`, +`doc`, `misc` etc., and add `.rst` as a suffix. If you +need to add more than one fragment, you may add an optional +sequence number (delimited with another period) between the type +and the suffix. + +In general the name will follow `..rst` pattern, +where the categories are: - `feature`: Any new feature - `bugfix`: A bug fix @@ -255,7 +280,11 @@ In general the name will follow `..rst` pattern, where the - `dependency`: Dependency-related changes - `misc`: Changes internal to the repo like CI, test and build changes -A pull request may have more than one of these components, for example a code change may introduce a new feature that deprecates an old feature, in which case two fragments should be added. It is not necessary to make a separate documentation fragment for documentation changes accompanying the relevant code changes. +A pull request may have more than one of these components, for example +a code change may introduce a new feature that deprecates an old +feature, in which case two fragments should be added. It is not +necessary to make a separate documentation fragment for documentation +changes accompanying the relevant code changes. #### Examples for adding changelog entries to your Pull Requests diff --git a/mddocs/db/db_reader.md b/mddocs/markdown/db/db_reader.md similarity index 100% rename from mddocs/db/db_reader.md rename to mddocs/markdown/db/db_reader.md diff --git a/mddocs/db/db_writer.md b/mddocs/markdown/db/db_writer.md similarity index 100% rename from mddocs/db/db_writer.md rename to mddocs/markdown/db/db_writer.md diff --git a/mddocs/db/index.md b/mddocs/markdown/db/index.md similarity index 85% rename from mddocs/db/index.md rename to mddocs/markdown/db/index.md index ef8f4ab30..861a539ff 100644 --- a/mddocs/db/index.md +++ b/mddocs/markdown/db/index.md @@ -1,6 +1,6 @@ -# DB classes + DB classes * [DB Reader](db_reader.md) * [DB Writer](db_writer.md) diff --git a/mddocs/file/file_downloader/file_downloader.md b/mddocs/markdown/file/file_downloader/file_downloader.md similarity index 100% rename from mddocs/file/file_downloader/file_downloader.md rename to mddocs/markdown/file/file_downloader/file_downloader.md diff --git a/mddocs/file/file_downloader/index.md b/mddocs/markdown/file/file_downloader/index.md similarity index 100% rename from mddocs/file/file_downloader/index.md rename to mddocs/markdown/file/file_downloader/index.md diff --git a/mddocs/file/file_downloader/options.md b/mddocs/markdown/file/file_downloader/options.md similarity index 100% rename from mddocs/file/file_downloader/options.md rename to mddocs/markdown/file/file_downloader/options.md diff --git a/mddocs/file/file_downloader/result.md b/mddocs/markdown/file/file_downloader/result.md similarity index 100% rename from mddocs/file/file_downloader/result.md rename to mddocs/markdown/file/file_downloader/result.md diff --git a/mddocs/file/file_filters/base.md b/mddocs/markdown/file/file_filters/base.md similarity index 100% rename from mddocs/file/file_filters/base.md rename to mddocs/markdown/file/file_filters/base.md diff --git a/mddocs/file/file_filters/exclude_dir.md b/mddocs/markdown/file/file_filters/exclude_dir.md similarity index 100% rename from mddocs/file/file_filters/exclude_dir.md rename to mddocs/markdown/file/file_filters/exclude_dir.md diff --git a/mddocs/file/file_filters/file_filter.md b/mddocs/markdown/file/file_filters/file_filter.md similarity index 100% rename from mddocs/file/file_filters/file_filter.md rename to mddocs/markdown/file/file_filters/file_filter.md diff --git a/mddocs/file/file_filters/file_mtime_filter.md b/mddocs/markdown/file/file_filters/file_mtime_filter.md similarity index 100% rename from mddocs/file/file_filters/file_mtime_filter.md rename to mddocs/markdown/file/file_filters/file_mtime_filter.md diff --git a/mddocs/file/file_filters/file_size_filter.md b/mddocs/markdown/file/file_filters/file_size_filter.md similarity index 100% rename from mddocs/file/file_filters/file_size_filter.md rename to mddocs/markdown/file/file_filters/file_size_filter.md diff --git a/mddocs/file/file_filters/glob.md b/mddocs/markdown/file/file_filters/glob.md similarity index 100% rename from mddocs/file/file_filters/glob.md rename to mddocs/markdown/file/file_filters/glob.md diff --git a/mddocs/file/file_filters/index.md b/mddocs/markdown/file/file_filters/index.md similarity index 100% rename from mddocs/file/file_filters/index.md rename to mddocs/markdown/file/file_filters/index.md diff --git a/mddocs/file/file_filters/match_all_filters.md b/mddocs/markdown/file/file_filters/match_all_filters.md similarity index 100% rename from mddocs/file/file_filters/match_all_filters.md rename to mddocs/markdown/file/file_filters/match_all_filters.md diff --git a/mddocs/file/file_filters/regexp.md b/mddocs/markdown/file/file_filters/regexp.md similarity index 100% rename from mddocs/file/file_filters/regexp.md rename to mddocs/markdown/file/file_filters/regexp.md diff --git a/mddocs/file/file_limits/base.md b/mddocs/markdown/file/file_limits/base.md similarity index 100% rename from mddocs/file/file_limits/base.md rename to mddocs/markdown/file/file_limits/base.md diff --git a/mddocs/file/file_limits/file_limit.md b/mddocs/markdown/file/file_limits/file_limit.md similarity index 100% rename from mddocs/file/file_limits/file_limit.md rename to mddocs/markdown/file/file_limits/file_limit.md diff --git a/mddocs/file/file_limits/index.md b/mddocs/markdown/file/file_limits/index.md similarity index 100% rename from mddocs/file/file_limits/index.md rename to mddocs/markdown/file/file_limits/index.md diff --git a/mddocs/file/file_limits/limits_reached.md b/mddocs/markdown/file/file_limits/limits_reached.md similarity index 100% rename from mddocs/file/file_limits/limits_reached.md rename to mddocs/markdown/file/file_limits/limits_reached.md diff --git a/mddocs/file/file_limits/limits_stop_at.md b/mddocs/markdown/file/file_limits/limits_stop_at.md similarity index 100% rename from mddocs/file/file_limits/limits_stop_at.md rename to mddocs/markdown/file/file_limits/limits_stop_at.md diff --git a/mddocs/file/file_limits/max_files_count.md b/mddocs/markdown/file/file_limits/max_files_count.md similarity index 100% rename from mddocs/file/file_limits/max_files_count.md rename to mddocs/markdown/file/file_limits/max_files_count.md diff --git a/mddocs/file/file_limits/reset_limits.md b/mddocs/markdown/file/file_limits/reset_limits.md similarity index 100% rename from mddocs/file/file_limits/reset_limits.md rename to mddocs/markdown/file/file_limits/reset_limits.md diff --git a/mddocs/file/file_limits/total_files_size.md b/mddocs/markdown/file/file_limits/total_files_size.md similarity index 100% rename from mddocs/file/file_limits/total_files_size.md rename to mddocs/markdown/file/file_limits/total_files_size.md diff --git a/mddocs/file/file_mover/file_mover.md b/mddocs/markdown/file/file_mover/file_mover.md similarity index 100% rename from mddocs/file/file_mover/file_mover.md rename to mddocs/markdown/file/file_mover/file_mover.md diff --git a/mddocs/file/file_mover/index.md b/mddocs/markdown/file/file_mover/index.md similarity index 100% rename from mddocs/file/file_mover/index.md rename to mddocs/markdown/file/file_mover/index.md diff --git a/mddocs/file/file_mover/options.md b/mddocs/markdown/file/file_mover/options.md similarity index 100% rename from mddocs/file/file_mover/options.md rename to mddocs/markdown/file/file_mover/options.md diff --git a/mddocs/file/file_mover/result.md b/mddocs/markdown/file/file_mover/result.md similarity index 100% rename from mddocs/file/file_mover/result.md rename to mddocs/markdown/file/file_mover/result.md diff --git a/mddocs/file/file_uploader/file_uploader.md b/mddocs/markdown/file/file_uploader/file_uploader.md similarity index 100% rename from mddocs/file/file_uploader/file_uploader.md rename to mddocs/markdown/file/file_uploader/file_uploader.md diff --git a/mddocs/file/file_uploader/index.md b/mddocs/markdown/file/file_uploader/index.md similarity index 100% rename from mddocs/file/file_uploader/index.md rename to mddocs/markdown/file/file_uploader/index.md diff --git a/mddocs/file/file_uploader/options.md b/mddocs/markdown/file/file_uploader/options.md similarity index 100% rename from mddocs/file/file_uploader/options.md rename to mddocs/markdown/file/file_uploader/options.md diff --git a/mddocs/file/file_uploader/result.md b/mddocs/markdown/file/file_uploader/result.md similarity index 100% rename from mddocs/file/file_uploader/result.md rename to mddocs/markdown/file/file_uploader/result.md diff --git a/mddocs/file/index.md b/mddocs/markdown/file/index.md similarity index 93% rename from mddocs/file/index.md rename to mddocs/markdown/file/index.md index 675648e5c..2da812bab 100644 --- a/mddocs/file/index.md +++ b/mddocs/markdown/file/index.md @@ -1,6 +1,6 @@ -# File classes + File classes * [File Downloader](file_downloader/index.md) * [File Uploader](file_uploader/index.md) diff --git a/mddocs/file_df/file_df_reader/file_df_reader.md b/mddocs/markdown/file_df/file_df_reader/file_df_reader.md similarity index 100% rename from mddocs/file_df/file_df_reader/file_df_reader.md rename to mddocs/markdown/file_df/file_df_reader/file_df_reader.md diff --git a/mddocs/file_df/file_df_reader/index.md b/mddocs/markdown/file_df/file_df_reader/index.md similarity index 100% rename from mddocs/file_df/file_df_reader/index.md rename to mddocs/markdown/file_df/file_df_reader/index.md diff --git a/mddocs/file_df/file_df_reader/options.md b/mddocs/markdown/file_df/file_df_reader/options.md similarity index 100% rename from mddocs/file_df/file_df_reader/options.md rename to mddocs/markdown/file_df/file_df_reader/options.md diff --git a/mddocs/file_df/file_df_writer/file_df_writer.md b/mddocs/markdown/file_df/file_df_writer/file_df_writer.md similarity index 100% rename from mddocs/file_df/file_df_writer/file_df_writer.md rename to mddocs/markdown/file_df/file_df_writer/file_df_writer.md diff --git a/mddocs/file_df/file_df_writer/index.md b/mddocs/markdown/file_df/file_df_writer/index.md similarity index 100% rename from mddocs/file_df/file_df_writer/index.md rename to mddocs/markdown/file_df/file_df_writer/index.md diff --git a/mddocs/file_df/file_df_writer/options.md b/mddocs/markdown/file_df/file_df_writer/options.md similarity index 100% rename from mddocs/file_df/file_df_writer/options.md rename to mddocs/markdown/file_df/file_df_writer/options.md diff --git a/mddocs/file_df/file_formats/avro.md b/mddocs/markdown/file_df/file_formats/avro.md similarity index 100% rename from mddocs/file_df/file_formats/avro.md rename to mddocs/markdown/file_df/file_formats/avro.md diff --git a/mddocs/file_df/file_formats/base.md b/mddocs/markdown/file_df/file_formats/base.md similarity index 100% rename from mddocs/file_df/file_formats/base.md rename to mddocs/markdown/file_df/file_formats/base.md diff --git a/mddocs/file_df/file_formats/csv.md b/mddocs/markdown/file_df/file_formats/csv.md similarity index 100% rename from mddocs/file_df/file_formats/csv.md rename to mddocs/markdown/file_df/file_formats/csv.md diff --git a/mddocs/file_df/file_formats/excel.md b/mddocs/markdown/file_df/file_formats/excel.md similarity index 100% rename from mddocs/file_df/file_formats/excel.md rename to mddocs/markdown/file_df/file_formats/excel.md diff --git a/mddocs/file_df/file_formats/index.md b/mddocs/markdown/file_df/file_formats/index.md similarity index 100% rename from mddocs/file_df/file_formats/index.md rename to mddocs/markdown/file_df/file_formats/index.md diff --git a/mddocs/file_df/file_formats/json.md b/mddocs/markdown/file_df/file_formats/json.md similarity index 100% rename from mddocs/file_df/file_formats/json.md rename to mddocs/markdown/file_df/file_formats/json.md diff --git a/mddocs/file_df/file_formats/jsonline.md b/mddocs/markdown/file_df/file_formats/jsonline.md similarity index 100% rename from mddocs/file_df/file_formats/jsonline.md rename to mddocs/markdown/file_df/file_formats/jsonline.md diff --git a/mddocs/file_df/file_formats/orc.md b/mddocs/markdown/file_df/file_formats/orc.md similarity index 100% rename from mddocs/file_df/file_formats/orc.md rename to mddocs/markdown/file_df/file_formats/orc.md diff --git a/mddocs/file_df/file_formats/parquet.md b/mddocs/markdown/file_df/file_formats/parquet.md similarity index 100% rename from mddocs/file_df/file_formats/parquet.md rename to mddocs/markdown/file_df/file_formats/parquet.md diff --git a/mddocs/file_df/file_formats/xml.md b/mddocs/markdown/file_df/file_formats/xml.md similarity index 100% rename from mddocs/file_df/file_formats/xml.md rename to mddocs/markdown/file_df/file_formats/xml.md diff --git a/mddocs/file_df/index.md b/mddocs/markdown/file_df/index.md similarity index 86% rename from mddocs/file_df/index.md rename to mddocs/markdown/file_df/index.md index a0868372e..c225567d6 100644 --- a/mddocs/file_df/index.md +++ b/mddocs/markdown/file_df/index.md @@ -1,6 +1,6 @@ -# File DataFrame classes + File DataFrame classes * [FileDF Reader](file_df_reader/index.md) * [FileDF Writer](file_df_writer/index.md) diff --git a/mddocs/hooks/design.md b/mddocs/markdown/hooks/design.md similarity index 100% rename from mddocs/hooks/design.md rename to mddocs/markdown/hooks/design.md diff --git a/mddocs/hooks/global_state.md b/mddocs/markdown/hooks/global_state.md similarity index 100% rename from mddocs/hooks/global_state.md rename to mddocs/markdown/hooks/global_state.md diff --git a/mddocs/hooks/hook.md b/mddocs/markdown/hooks/hook.md similarity index 100% rename from mddocs/hooks/hook.md rename to mddocs/markdown/hooks/hook.md diff --git a/mddocs/hooks/index.md b/mddocs/markdown/hooks/index.md similarity index 100% rename from mddocs/hooks/index.md rename to mddocs/markdown/hooks/index.md diff --git a/mddocs/hooks/slot.md b/mddocs/markdown/hooks/slot.md similarity index 100% rename from mddocs/hooks/slot.md rename to mddocs/markdown/hooks/slot.md diff --git a/mddocs/hooks/support_hooks.md b/mddocs/markdown/hooks/support_hooks.md similarity index 100% rename from mddocs/hooks/support_hooks.md rename to mddocs/markdown/hooks/support_hooks.md diff --git a/mddocs/hwm_store/index.md b/mddocs/markdown/hwm_store/index.md similarity index 100% rename from mddocs/hwm_store/index.md rename to mddocs/markdown/hwm_store/index.md diff --git a/mddocs/hwm_store/yaml_hwm_store.md b/mddocs/markdown/hwm_store/yaml_hwm_store.md similarity index 100% rename from mddocs/hwm_store/yaml_hwm_store.md rename to mddocs/markdown/hwm_store/yaml_hwm_store.md diff --git a/mddocs/markdown/index.md b/mddocs/markdown/index.md new file mode 100644 index 000000000..436e5eb85 --- /dev/null +++ b/mddocs/markdown/index.md @@ -0,0 +1,59 @@ + + +# onETL + +[![Repo status - Active](https://www.repostatus.org/badges/latest/active.svg)](https://github.com/MobileTeleSystems/onetl) [![PyPI - Latest Release](https://img.shields.io/pypi/v/onetl)](https://pypi.org/project/onetl/) [![PyPI - License](https://img.shields.io/pypi/l/onetl.svg)](https://github.com/MobileTeleSystems/onetl/blob/develop/LICENSE.txt) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/onetl.svg)](https://pypi.org/project/onetl/) [![PyPI - Downloads](https://img.shields.io/pypi/dm/onetl)](https://pypi.org/project/onetl/) +[![Documentation - ReadTheDocs](https://readthedocs.org/projects/onetl/badge/?version=stable)](https://onetl.readthedocs.io/) [![Github Actions - latest CI build status](https://github.com/MobileTeleSystems/onetl/workflows/Tests/badge.svg)](https://github.com/MobileTeleSystems/onetl/actions) [![Test coverage - percent](https://codecov.io/gh/MobileTeleSystems/onetl/branch/develop/graph/badge.svg?token=RIO8URKNZJ)](https://codecov.io/gh/MobileTeleSystems/onetl) [![pre-commit.ci - status](https://results.pre-commit.ci/badge/github/MobileTeleSystems/onetl/develop.svg)](https://results.pre-commit.ci/latest/github/MobileTeleSystems/onetl/develop) + +![onETL logo](_static/logo_wide.svg) + +## What is onETL? + +Python ETL/ELT library powered by [Apache Spark](https://spark.apache.org/) & other open-source tools. + +## Goals + +* Provide unified classes to extract data from (**E**) & load data to (**L**) various stores. +* Provides [Spark DataFrame API](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.DataFrame.html) for performing transformations (**T**) in terms of *ETL*. +* Provide direct assess to database, allowing to execute SQL queries, as well as DDL, DML, and call functions/procedures. This can be used for building up *ELT* pipelines. +* Support different [read strategies](https://onetl.readthedocs.io/en/stable/strategy/index.html) for incremental and batch data fetching. +* Provide [hooks](https://onetl.readthedocs.io/en/stable/hooks/index.html) & [plugins](https://onetl.readthedocs.io/en/stable/plugins.html) mechanism for altering behavior of internal classes. + +## Non-goals + +* onETL is not a Spark replacement. It just provides additional functionality that Spark does not have, and improves UX for end users. +* onETL is not a framework, as it does not have requirements to project structure, naming, the way of running ETL/ELT processes, configuration, etc. All of that should be implemented in some other tool. +* onETL is deliberately developed without any integration with scheduling software like Apache Airflow. All integrations should be implemented as separated tools. +* Only batch operations, no streaming. For streaming prefer [Apache Flink](https://flink.apache.org/). + +## Requirements + +* **Python 3.7 - 3.13** +* PySpark 2.3.x - 3.5.x (depends on used connector) +* Java 8+ (required by Spark, see below) +* Kerberos libs & GCC (required by `Hive`, `HDFS` and `SparkHDFS` connectors) + +## Supported storages + +| Type | Storage | Powered by | +|--------------------|-----------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------| +| Database | Clickhouse | Apache Spark [JDBC Data Source](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html) | +| MSSQL | | | +| MySQL | | | +| Postgres | | | +| Oracle | | | +| Teradata | | | +| Hive | Apache Spark [Hive integration](https://spark.apache.org/docs/latest/sql-data-sources-hive-tables.html) | | +| Kafka | Apache Spark [Kafka integration](https://spark.apache.org/docs/latest/structured-streaming-kafka-integration.html) | | +| Greenplum | VMware [Greenplum Spark connector](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/index.html) | | +| MongoDB | [MongoDB Spark connector](https://www.mongodb.com/docs/spark-connector/current) | | +| File | HDFS | [HDFS Python client](https://pypi.org/project/hdfs/) | +| S3 | [minio-py client](https://pypi.org/project/minio/) | | +| SFTP | [Paramiko library](https://pypi.org/project/paramiko/) | | +| FTP | [FTPUtil library](https://pypi.org/project/ftputil/) | | +| FTPS | | | +| WebDAV | [WebdavClient3 library](https://pypi.org/project/webdavclient3/) | | +| Samba | [pysmb library](https://pypi.org/project/pysmb/) | | +| Files as DataFrame | SparkLocalFS | Apache Spark [File Data Source](https://spark.apache.org/docs/latest/sql-data-sources-generic-options.html) | +| SparkHDFS | | | +| SparkS3 | [Hadoop AWS](https://hadoop.apache.org/docs/current3/hadoop-aws/tools/hadoop-aws/index.html) library | | diff --git a/mddocs/install/files.md b/mddocs/markdown/install/files.md similarity index 100% rename from mddocs/install/files.md rename to mddocs/markdown/install/files.md diff --git a/mddocs/install/full.md b/mddocs/markdown/install/full.md similarity index 100% rename from mddocs/install/full.md rename to mddocs/markdown/install/full.md diff --git a/mddocs/install/index.md b/mddocs/markdown/install/index.md similarity index 84% rename from mddocs/install/index.md rename to mddocs/markdown/install/index.md index 15f1e36e2..1a5fb63cc 100644 --- a/mddocs/install/index.md +++ b/mddocs/markdown/install/index.md @@ -16,14 +16,11 @@ It can be installed via: pip install onetl ``` -```{admonition} warning - -:class: warning - +#### WARNING This method does NOT include any connections. -This method is recommended for use in third-party libraries which require for `onetl` to be installed, but do not use its connection classes. -``` +This method is recommended for use in third-party libraries which require for `onetl` to be installed, +but do not use its connection classes. ## Installation in details diff --git a/mddocs/install/kerberos.md b/mddocs/markdown/install/kerberos.md similarity index 100% rename from mddocs/install/kerberos.md rename to mddocs/markdown/install/kerberos.md diff --git a/mddocs/install/spark.md b/mddocs/markdown/install/spark.md similarity index 100% rename from mddocs/install/spark.md rename to mddocs/markdown/install/spark.md diff --git a/mddocs/logging.md b/mddocs/markdown/logging.md similarity index 88% rename from mddocs/logging.md rename to mddocs/markdown/logging.md index c67da0558..f9900f789 100644 --- a/mddocs/logging.md +++ b/mddocs/markdown/logging.md @@ -1,12 +1,14 @@ -# Logging - +# Logging + Logging is quite important to understand what’s going on under the hood of onETL. -Default logging level for Python interpreters is `WARNING`, but most of onETL logs are in `INFO` level, so users usually don’t see much. +Default logging level for Python interpreters is `WARNING`, +but most of onETL logs are in `INFO` level, so users usually don’t see much. -To change logging level, there is a function [`setup_logging`](#setup_logging) which should be called at the top of the script: +To change logging level, there is a function [`setup_logging`](#onetl.log.setup_logging) +which should be called at the top of the script: ```python from onetl.log import setup_logging @@ -20,7 +22,7 @@ setup_logging() This changes both log level and log formatting to something like this: -## See logs +### See logs ```text 2024-04-12 10:12:10,834 [INFO ] MainThread: |onETL| Using IncrementalStrategy as a strategy @@ -150,100 +152,85 @@ setup_logging(level="DEBUG", enable_clients=True) This also changes log level for all underlying Python libraries, e.g. showing each HTTP request being made, and so on. - -**onetl.log.setup_logging**(level: int | str = 20, enable_clients: bool = False) → None +### onetl.log.setup_logging(level: int | str = 20, enable_clients: bool = False) → None Set up onETL logging. What this function does: +: * Adds stderr logging handler + * Changes root logger format to `2023-05-31 11:22:33.456 [INFO] MainThread: message` + * Changes root logger level to `level` + * Changes onETL logger level to `level` + * Sets up logging level of underlying client modules -* Adds stderr logging handler -* Changes root logger format to `2023-05-31 11:22:33.456 [INFO] MainThread: message` -* Changes root logger level to `level` -* Changes onETL logger level to `level` -* Sets up logging level of underlying client modules - - - -```{admonition} note +#### NOTE Should be used only in IDEs (like Jupyter notebooks or PyCharm), or scripts (ETL pipelines). -``` - - - -````{versionchanged} +#### Versionchanged Changed in version 0.5.0: Renamed `setup_notebook_logging` → `setup_logging` * **Parameters:** **level** - Log level for onETL module + : Log level for onETL module **enable_clients** - If `True`, enable logging of underlying client modules. + : If `True`, enable logging of underlying client modules. Otherwise, set client modules log level to `DISABLED`. +
+ #### NOTE + For `level="DEBUG"` it is recommended to use `enable_clients=True` +
+ #### Versionadded + Added in version 0.9.0. -```{admonition} note -For `level="DEBUG"` it is recommended to use `enable_clients=True` -``` - -```{versionadded} -Added in version 0.9.0. -``` -```` -**onetl.log.setup_clients_logging**(level: int | str = 9999) → None +### onetl.log.setup_clients_logging(level: int | str = 9999) → None Set logging of underlying client modules used by onETL. Affected modules: - -* `ftputil` -* `hdfs` -* `minio` -* `paramiko` -* `py4j` -* `pyspark` -* `webdav3` - -```{admonition} note +: * `ftputil` + * `hdfs` + * `minio` + * `paramiko` + * `py4j` + * `pyspark` + * `webdav3` + +#### NOTE Can be used in applications, but it is recommended to set up these loggers according to your framework documentation. -``` -````{versionchanged} +#### Versionchanged Changed in version 0.9.0: Renamed `disable_clients_logging` → `setup_clients_logging` * **Parameters:** **level** - Log level for client modules + : Log level for client modules
-```{admonition} note -For `py4j`, logging level with maximum verbosity is `INFO` because `DEBUG` logs are -totally unreadable. -``` -
-```{versionadded} -Added in version 0.9.0. -``` -```` + #### NOTE + For `py4j`, logging level with maximum verbosity is `INFO` because `DEBUG` logs are + totally unreadable. +
+ #### Versionadded + Added in version 0.9.0. + -**onetl.log.set_default_logging_format()** → None +### onetl.log.set_default_logging_format() → None Sets default logging format to preferred by onETL. Example log message: `2023-05-31 11:22:33.456 [INFO] MainThread: message` -```{admonition} note +#### NOTE Should be used only in IDEs (like Jupyter notebooks or PyCharm), or scripts (ETL pipelines). -``` -```{admonition} warning +#### WARNING Should **NOT** be used in applications, you should set up logging settings manually, according to your framework documentation. -``` + diff --git a/mddocs/plugins.md b/mddocs/markdown/plugins.md similarity index 99% rename from mddocs/plugins.md rename to mddocs/markdown/plugins.md index ca50f4a84..d42ded034 100644 --- a/mddocs/plugins.md +++ b/mddocs/markdown/plugins.md @@ -1,10 +1,9 @@ -# Plugins - -```{versionadded} +# Plugins + +#### Versionadded Added in version 0.6.0. -``` ## What are plugins? @@ -79,9 +78,8 @@ like [@hook decorator](hooks/hook.md#hook-decorator), it will be executed during ## How to enable/disable plugins? -```{versionadded} +#### Versionadded Added in version 0.7.0. -``` ### Disable/enable all plugins diff --git a/mddocs/quickstart.md b/mddocs/markdown/quickstart.md similarity index 100% rename from mddocs/quickstart.md rename to mddocs/markdown/quickstart.md diff --git a/mddocs/security.md b/mddocs/markdown/security.md similarity index 100% rename from mddocs/security.md rename to mddocs/markdown/security.md diff --git a/mddocs/strategy/incremental_batch_strategy.md b/mddocs/markdown/strategy/incremental_batch_strategy.md similarity index 100% rename from mddocs/strategy/incremental_batch_strategy.md rename to mddocs/markdown/strategy/incremental_batch_strategy.md diff --git a/mddocs/strategy/incremental_strategy.md b/mddocs/markdown/strategy/incremental_strategy.md similarity index 100% rename from mddocs/strategy/incremental_strategy.md rename to mddocs/markdown/strategy/incremental_strategy.md diff --git a/mddocs/strategy/index.md b/mddocs/markdown/strategy/index.md similarity index 100% rename from mddocs/strategy/index.md rename to mddocs/markdown/strategy/index.md diff --git a/mddocs/strategy/snapshot_batch_strategy.md b/mddocs/markdown/strategy/snapshot_batch_strategy.md similarity index 100% rename from mddocs/strategy/snapshot_batch_strategy.md rename to mddocs/markdown/strategy/snapshot_batch_strategy.md diff --git a/mddocs/strategy/snapshot_strategy.md b/mddocs/markdown/strategy/snapshot_strategy.md similarity index 100% rename from mddocs/strategy/snapshot_strategy.md rename to mddocs/markdown/strategy/snapshot_strategy.md diff --git a/mddocs/troubleshooting/index.md b/mddocs/markdown/troubleshooting/index.md similarity index 100% rename from mddocs/troubleshooting/index.md rename to mddocs/markdown/troubleshooting/index.md diff --git a/mddocs/troubleshooting/spark.md b/mddocs/markdown/troubleshooting/spark.md similarity index 100% rename from mddocs/troubleshooting/spark.md rename to mddocs/markdown/troubleshooting/spark.md From 08188ef8733169c7dd7e02a705db67b72ae0cc33 Mon Sep 17 00:00:00 2001 From: Sattar Gyulmamedov Date: Sat, 7 Jun 2025 15:52:01 +0300 Subject: [PATCH 05/22] clear before conversion --- mddocs/doctrees/changelog.doctree | Bin 3515 -> 0 bytes mddocs/doctrees/changelog/0.10.0.doctree | Bin 89742 -> 0 bytes mddocs/doctrees/changelog/0.10.1.doctree | Bin 14304 -> 0 bytes mddocs/doctrees/changelog/0.10.2.doctree | Bin 17387 -> 0 bytes mddocs/doctrees/changelog/0.11.0.doctree | Bin 61627 -> 0 bytes mddocs/doctrees/changelog/0.11.1.doctree | Bin 5900 -> 0 bytes mddocs/doctrees/changelog/0.11.2.doctree | Bin 4295 -> 0 bytes mddocs/doctrees/changelog/0.12.0.doctree | Bin 22539 -> 0 bytes mddocs/doctrees/changelog/0.12.1.doctree | Bin 9253 -> 0 bytes mddocs/doctrees/changelog/0.12.2.doctree | Bin 8634 -> 0 bytes mddocs/doctrees/changelog/0.12.3.doctree | Bin 4340 -> 0 bytes mddocs/doctrees/changelog/0.12.4.doctree | Bin 4559 -> 0 bytes mddocs/doctrees/changelog/0.12.5.doctree | Bin 8481 -> 0 bytes mddocs/doctrees/changelog/0.13.0.doctree | Bin 53222 -> 0 bytes mddocs/doctrees/changelog/0.13.1.doctree | Bin 5184 -> 0 bytes mddocs/doctrees/changelog/0.13.3.doctree | Bin 4189 -> 0 bytes mddocs/doctrees/changelog/0.13.4.doctree | Bin 8204 -> 0 bytes mddocs/doctrees/changelog/0.7.0.doctree | Bin 56131 -> 0 bytes mddocs/doctrees/changelog/0.7.1.doctree | Bin 8224 -> 0 bytes mddocs/doctrees/changelog/0.7.2.doctree | Bin 9606 -> 0 bytes mddocs/doctrees/changelog/0.8.0.doctree | Bin 43955 -> 0 bytes mddocs/doctrees/changelog/0.8.1.doctree | Bin 15137 -> 0 bytes mddocs/doctrees/changelog/0.9.0.doctree | Bin 52924 -> 0 bytes mddocs/doctrees/changelog/0.9.1.doctree | Bin 4996 -> 0 bytes mddocs/doctrees/changelog/0.9.2.doctree | Bin 11096 -> 0 bytes mddocs/doctrees/changelog/0.9.3.doctree | Bin 3725 -> 0 bytes mddocs/doctrees/changelog/0.9.4.doctree | Bin 14841 -> 0 bytes mddocs/doctrees/changelog/0.9.5.doctree | Bin 7079 -> 0 bytes mddocs/doctrees/changelog/DRAFT.doctree | Bin 2914 -> 0 bytes .../doctrees/changelog/NEXT_RELEASE.doctree | Bin 3107 -> 0 bytes mddocs/doctrees/changelog/index.doctree | Bin 3923 -> 0 bytes mddocs/doctrees/concepts.doctree | Bin 70091 -> 0 bytes .../clickhouse/connection.doctree | Bin 47193 -> 0 bytes .../db_connection/clickhouse/execute.doctree | Bin 47822 -> 0 bytes .../db_connection/clickhouse/index.doctree | Bin 5083 -> 0 bytes .../clickhouse/prerequisites.doctree | Bin 16167 -> 0 bytes .../db_connection/clickhouse/read.doctree | Bin 82617 -> 0 bytes .../db_connection/clickhouse/sql.doctree | Bin 49343 -> 0 bytes .../db_connection/clickhouse/types.doctree | Bin 121722 -> 0 bytes .../db_connection/clickhouse/write.doctree | Bin 58118 -> 0 bytes .../greenplum/connection.doctree | Bin 48986 -> 0 bytes .../db_connection/greenplum/execute.doctree | Bin 50314 -> 0 bytes .../db_connection/greenplum/index.doctree | Bin 5016 -> 0 bytes .../greenplum/prerequisites.doctree | Bin 75612 -> 0 bytes .../db_connection/greenplum/read.doctree | Bin 83259 -> 0 bytes .../db_connection/greenplum/types.doctree | Bin 91709 -> 0 bytes .../db_connection/greenplum/write.doctree | Bin 50996 -> 0 bytes .../db_connection/hive/connection.doctree | Bin 26332 -> 0 bytes .../db_connection/hive/execute.doctree | Bin 15215 -> 0 bytes .../db_connection/hive/index.doctree | Bin 4974 -> 0 bytes .../db_connection/hive/prerequisites.doctree | Bin 21586 -> 0 bytes .../db_connection/hive/read.doctree | Bin 23064 -> 0 bytes .../db_connection/hive/slots.doctree | Bin 26608 -> 0 bytes .../connection/db_connection/hive/sql.doctree | Bin 23281 -> 0 bytes .../db_connection/hive/write.doctree | Bin 145975 -> 0 bytes .../connection/db_connection/index.doctree | Bin 4613 -> 0 bytes .../db_connection/kafka/auth.doctree | Bin 16696 -> 0 bytes .../db_connection/kafka/basic_auth.doctree | Bin 23699 -> 0 bytes .../db_connection/kafka/connection.doctree | Bin 56977 -> 0 bytes .../db_connection/kafka/index.doctree | Bin 5696 -> 0 bytes .../db_connection/kafka/kerberos_auth.doctree | Bin 43862 -> 0 bytes .../kafka/plaintext_protocol.doctree | Bin 18618 -> 0 bytes .../db_connection/kafka/prerequisites.doctree | Bin 25211 -> 0 bytes .../db_connection/kafka/protocol.doctree | Bin 16936 -> 0 bytes .../db_connection/kafka/read.doctree | Bin 34544 -> 0 bytes .../db_connection/kafka/scram_auth.doctree | Bin 27927 -> 0 bytes .../db_connection/kafka/slots.doctree | Bin 36192 -> 0 bytes .../db_connection/kafka/ssl_protocol.doctree | Bin 59151 -> 0 bytes .../kafka/troubleshooting.doctree | Bin 5030 -> 0 bytes .../db_connection/kafka/write.doctree | Bin 27780 -> 0 bytes .../db_connection/mongodb/connection.doctree | Bin 43670 -> 0 bytes .../db_connection/mongodb/index.doctree | Bin 4983 -> 0 bytes .../db_connection/mongodb/pipeline.doctree | Bin 36769 -> 0 bytes .../mongodb/prerequisites.doctree | Bin 15809 -> 0 bytes .../db_connection/mongodb/read.doctree | Bin 26313 -> 0 bytes .../db_connection/mongodb/types.doctree | Bin 56505 -> 0 bytes .../db_connection/mongodb/write.doctree | Bin 34686 -> 0 bytes .../db_connection/mssql/connection.doctree | Bin 47337 -> 0 bytes .../db_connection/mssql/execute.doctree | Bin 45339 -> 0 bytes .../db_connection/mssql/index.doctree | Bin 4993 -> 0 bytes .../db_connection/mssql/prerequisites.doctree | Bin 18921 -> 0 bytes .../db_connection/mssql/read.doctree | Bin 81749 -> 0 bytes .../db_connection/mssql/sql.doctree | Bin 48558 -> 0 bytes .../db_connection/mssql/types.doctree | Bin 95410 -> 0 bytes .../db_connection/mssql/write.doctree | Bin 57255 -> 0 bytes .../db_connection/mysql/connection.doctree | Bin 39396 -> 0 bytes .../db_connection/mysql/execute.doctree | Bin 49720 -> 0 bytes .../db_connection/mysql/index.doctree | Bin 4993 -> 0 bytes .../db_connection/mysql/prerequisites.doctree | Bin 14364 -> 0 bytes .../db_connection/mysql/read.doctree | Bin 90065 -> 0 bytes .../db_connection/mysql/sql.doctree | Bin 48866 -> 0 bytes .../db_connection/mysql/types.doctree | Bin 90824 -> 0 bytes .../db_connection/mysql/write.doctree | Bin 57493 -> 0 bytes .../db_connection/oracle/connection.doctree | Bin 47667 -> 0 bytes .../db_connection/oracle/execute.doctree | Bin 50314 -> 0 bytes .../db_connection/oracle/index.doctree | Bin 5011 -> 0 bytes .../oracle/prerequisites.doctree | Bin 21687 -> 0 bytes .../db_connection/oracle/read.doctree | Bin 90393 -> 0 bytes .../db_connection/oracle/sql.doctree | Bin 49041 -> 0 bytes .../db_connection/oracle/types.doctree | Bin 90841 -> 0 bytes .../db_connection/oracle/write.doctree | Bin 57375 -> 0 bytes .../db_connection/postgres/connection.doctree | Bin 38526 -> 0 bytes .../db_connection/postgres/execute.doctree | Bin 49948 -> 0 bytes .../db_connection/postgres/index.doctree | Bin 5047 -> 0 bytes .../postgres/prerequisites.doctree | Bin 15639 -> 0 bytes .../db_connection/postgres/read.doctree | Bin 90036 -> 0 bytes .../db_connection/postgres/sql.doctree | Bin 49029 -> 0 bytes .../db_connection/postgres/types.doctree | Bin 113678 -> 0 bytes .../db_connection/postgres/write.doctree | Bin 57630 -> 0 bytes .../db_connection/teradata/connection.doctree | Bin 44686 -> 0 bytes .../db_connection/teradata/execute.doctree | Bin 50051 -> 0 bytes .../db_connection/teradata/index.doctree | Bin 4805 -> 0 bytes .../teradata/prerequisites.doctree | Bin 13774 -> 0 bytes .../db_connection/teradata/read.doctree | Bin 97368 -> 0 bytes .../db_connection/teradata/sql.doctree | Bin 49004 -> 0 bytes .../db_connection/teradata/write.doctree | Bin 74219 -> 0 bytes .../connection/file_connection/ftp.doctree | Bin 201643 -> 0 bytes .../connection/file_connection/ftps.doctree | Bin 201889 -> 0 bytes .../file_connection/hdfs/connection.doctree | Bin 233577 -> 0 bytes .../file_connection/hdfs/index.doctree | Bin 4566 -> 0 bytes .../file_connection/hdfs/slots.doctree | Bin 65920 -> 0 bytes .../connection/file_connection/index.doctree | Bin 4404 -> 0 bytes .../connection/file_connection/s3.doctree | Bin 195366 -> 0 bytes .../connection/file_connection/samba.doctree | Bin 175360 -> 0 bytes .../connection/file_connection/sftp.doctree | Bin 210343 -> 0 bytes .../connection/file_connection/webdav.doctree | Bin 195091 -> 0 bytes .../file_df_connection/base.doctree | Bin 32869 -> 0 bytes .../file_df_connection/index.doctree | Bin 4576 -> 0 bytes .../spark_hdfs/connection.doctree | Bin 43085 -> 0 bytes .../spark_hdfs/index.doctree | Bin 4435 -> 0 bytes .../spark_hdfs/prerequisites.doctree | Bin 11394 -> 0 bytes .../spark_hdfs/slots.doctree | Bin 65105 -> 0 bytes .../file_df_connection/spark_local_fs.doctree | Bin 17796 -> 0 bytes .../spark_s3/connection.doctree | Bin 66398 -> 0 bytes .../file_df_connection/spark_s3/index.doctree | Bin 4243 -> 0 bytes .../spark_s3/prerequisites.doctree | Bin 13870 -> 0 bytes .../spark_s3/troubleshooting.doctree | Bin 73472 -> 0 bytes mddocs/doctrees/connection/index.doctree | Bin 4261 -> 0 bytes mddocs/doctrees/contributing.doctree | Bin 57687 -> 0 bytes mddocs/doctrees/db/db_reader.doctree | Bin 85522 -> 0 bytes mddocs/doctrees/db/db_writer.doctree | Bin 29431 -> 0 bytes mddocs/doctrees/db/index.doctree | Bin 3763 -> 0 bytes .../file_downloader/file_downloader.doctree | Bin 82948 -> 0 bytes .../file/file_downloader/index.doctree | Bin 4189 -> 0 bytes .../file/file_downloader/options.doctree | Bin 20877 -> 0 bytes .../file/file_downloader/result.doctree | Bin 124226 -> 0 bytes .../doctrees/file/file_filters/base.doctree | Bin 15123 -> 0 bytes .../file/file_filters/exclude_dir.doctree | Bin 14340 -> 0 bytes .../file/file_filters/file_filter.doctree | Bin 24382 -> 0 bytes .../file_filters/file_mtime_filter.doctree | Bin 20127 -> 0 bytes .../file_filters/file_size_filter.doctree | Bin 20673 -> 0 bytes .../doctrees/file/file_filters/glob.doctree | Bin 13363 -> 0 bytes .../doctrees/file/file_filters/index.doctree | Bin 4687 -> 0 bytes .../file_filters/match_all_filters.doctree | Bin 14653 -> 0 bytes .../doctrees/file/file_filters/regexp.doctree | Bin 15688 -> 0 bytes mddocs/doctrees/file/file_limits/base.doctree | Bin 29393 -> 0 bytes .../file/file_limits/file_limit.doctree | Bin 26560 -> 0 bytes .../doctrees/file/file_limits/index.doctree | Bin 4643 -> 0 bytes .../file/file_limits/limits_reached.doctree | Bin 12366 -> 0 bytes .../file/file_limits/limits_stop_at.doctree | Bin 14340 -> 0 bytes .../file/file_limits/max_files_count.doctree | Bin 25993 -> 0 bytes .../file/file_limits/reset_limits.doctree | Bin 13211 -> 0 bytes .../file/file_limits/total_files_size.doctree | Bin 28858 -> 0 bytes .../file/file_mover/file_mover.doctree | Bin 58487 -> 0 bytes mddocs/doctrees/file/file_mover/index.doctree | Bin 4114 -> 0 bytes .../doctrees/file/file_mover/options.doctree | Bin 16664 -> 0 bytes .../doctrees/file/file_mover/result.doctree | Bin 122686 -> 0 bytes .../file/file_uploader/file_uploader.doctree | Bin 58592 -> 0 bytes .../doctrees/file/file_uploader/index.doctree | Bin 4159 -> 0 bytes .../file/file_uploader/options.doctree | Bin 20716 -> 0 bytes .../file/file_uploader/result.doctree | Bin 123529 -> 0 bytes mddocs/doctrees/file/index.doctree | Bin 3893 -> 0 bytes .../file_df_reader/file_df_reader.doctree | Bin 37256 -> 0 bytes .../file_df/file_df_reader/index.doctree | Bin 4147 -> 0 bytes .../file_df/file_df_reader/options.doctree | Bin 16058 -> 0 bytes .../file_df_writer/file_df_writer.doctree | Bin 22661 -> 0 bytes .../file_df/file_df_writer/index.doctree | Bin 4147 -> 0 bytes .../file_df/file_df_writer/options.doctree | Bin 72939 -> 0 bytes .../file_df/file_formats/avro.doctree | Bin 115063 -> 0 bytes .../file_df/file_formats/base.doctree | Bin 28172 -> 0 bytes .../doctrees/file_df/file_formats/csv.doctree | Bin 224244 -> 0 bytes .../file_df/file_formats/excel.doctree | Bin 98599 -> 0 bytes .../file_df/file_formats/index.doctree | Bin 4513 -> 0 bytes .../file_df/file_formats/json.doctree | Bin 166599 -> 0 bytes .../file_df/file_formats/jsonline.doctree | Bin 154252 -> 0 bytes .../doctrees/file_df/file_formats/orc.doctree | Bin 30977 -> 0 bytes .../file_df/file_formats/parquet.doctree | Bin 32899 -> 0 bytes .../doctrees/file_df/file_formats/xml.doctree | Bin 169345 -> 0 bytes mddocs/doctrees/file_df/index.doctree | Bin 3871 -> 0 bytes mddocs/doctrees/hooks/design.doctree | Bin 87065 -> 0 bytes mddocs/doctrees/hooks/global_state.doctree | Bin 20165 -> 0 bytes mddocs/doctrees/hooks/hook.doctree | Bin 53860 -> 0 bytes mddocs/doctrees/hooks/index.doctree | Bin 4859 -> 0 bytes mddocs/doctrees/hooks/slot.doctree | Bin 44170 -> 0 bytes mddocs/doctrees/hooks/support_hooks.doctree | Bin 27957 -> 0 bytes mddocs/doctrees/hwm_store/index.doctree | Bin 6473 -> 0 bytes .../doctrees/hwm_store/yaml_hwm_store.doctree | Bin 32404 -> 0 bytes mddocs/doctrees/index.doctree | Bin 42083 -> 0 bytes mddocs/doctrees/install/files.doctree | Bin 6302 -> 0 bytes mddocs/doctrees/install/full.doctree | Bin 4935 -> 0 bytes mddocs/doctrees/install/index.doctree | Bin 8296 -> 0 bytes mddocs/doctrees/install/kerberos.doctree | Bin 9162 -> 0 bytes mddocs/doctrees/install/spark.doctree | Bin 68759 -> 0 bytes mddocs/doctrees/logging.doctree | Bin 54923 -> 0 bytes mddocs/doctrees/plugins.doctree | Bin 24191 -> 0 bytes mddocs/doctrees/quickstart.doctree | Bin 24493 -> 0 bytes mddocs/doctrees/security.doctree | Bin 7508 -> 0 bytes .../incremental_batch_strategy.doctree | Bin 52892 -> 0 bytes .../strategy/incremental_strategy.doctree | Bin 60978 -> 0 bytes mddocs/doctrees/strategy/index.doctree | Bin 6430 -> 0 bytes .../strategy/snapshot_batch_strategy.doctree | Bin 47889 -> 0 bytes .../strategy/snapshot_strategy.doctree | Bin 16570 -> 0 bytes mddocs/doctrees/troubleshooting/index.doctree | Bin 12252 -> 0 bytes mddocs/doctrees/troubleshooting/spark.doctree | Bin 14319 -> 0 bytes .../_sphinx_design_static/design-tabs.js | 101 --- .../sphinx-design.min.css | 1 - mddocs/markdown/_static/autodoc_pydantic.css | 11 - mddocs/markdown/changelog.md | 31 - mddocs/markdown/changelog/0.10.0.md | 359 --------- mddocs/markdown/changelog/0.10.1.md | 25 - mddocs/markdown/changelog/0.10.2.md | 34 - mddocs/markdown/changelog/0.11.0.md | 202 ----- mddocs/markdown/changelog/0.11.1.md | 9 - mddocs/markdown/changelog/0.11.2.md | 5 - mddocs/markdown/changelog/0.12.0.md | 48 -- mddocs/markdown/changelog/0.12.1.md | 17 - mddocs/markdown/changelog/0.12.2.md | 18 - mddocs/markdown/changelog/0.12.3.md | 5 - mddocs/markdown/changelog/0.12.4.md | 5 - mddocs/markdown/changelog/0.12.5.md | 13 - mddocs/markdown/changelog/0.13.0.md | 197 ----- mddocs/markdown/changelog/0.13.1.md | 9 - mddocs/markdown/changelog/0.13.3.md | 5 - mddocs/markdown/changelog/0.13.4.md | 10 - mddocs/markdown/changelog/0.7.0.md | 211 ----- mddocs/markdown/changelog/0.7.1.md | 31 - mddocs/markdown/changelog/0.7.2.md | 33 - mddocs/markdown/changelog/0.8.0.md | 137 ---- mddocs/markdown/changelog/0.8.1.md | 34 - mddocs/markdown/changelog/0.9.0.md | 107 --- mddocs/markdown/changelog/0.9.1.md | 7 - mddocs/markdown/changelog/0.9.2.md | 21 - mddocs/markdown/changelog/0.9.3.md | 5 - mddocs/markdown/changelog/0.9.4.md | 24 - mddocs/markdown/changelog/0.9.5.md | 14 - mddocs/markdown/changelog/DRAFT.md | 0 mddocs/markdown/changelog/NEXT_RELEASE.md | 1 - mddocs/markdown/changelog/index.md | 29 - mddocs/markdown/concepts.md | 340 -------- .../db_connection/clickhouse/connection.md | 128 --- .../db_connection/clickhouse/execute.md | 191 ----- .../db_connection/clickhouse/index.md | 19 - .../db_connection/clickhouse/prerequisites.md | 73 -- .../db_connection/clickhouse/read.md | 339 -------- .../db_connection/clickhouse/sql.md | 191 ----- .../db_connection/clickhouse/types.md | 347 -------- .../db_connection/clickhouse/write.md | 187 ----- .../db_connection/greenplum/connection.md | 144 ---- .../db_connection/greenplum/execute.md | 195 ----- .../db_connection/greenplum/index.md | 18 - .../db_connection/greenplum/prerequisites.md | 358 --------- .../db_connection/greenplum/read.md | 405 ---------- .../db_connection/greenplum/types.md | 318 -------- .../db_connection/greenplum/write.md | 141 ---- .../db_connection/hive/connection.md | 118 --- .../connection/db_connection/hive/execute.md | 57 -- .../connection/db_connection/hive/index.md | 19 - .../db_connection/hive/prerequisites.md | 128 --- .../connection/db_connection/hive/read.md | 92 --- .../connection/db_connection/hive/slots.md | 105 --- .../connection/db_connection/hive/sql.md | 86 -- .../connection/db_connection/hive/write.md | 394 ---------- .../connection/db_connection/index.md | 16 - .../connection/db_connection/kafka/auth.md | 37 - .../db_connection/kafka/basic_auth.md | 60 -- .../db_connection/kafka/connection.md | 243 ------ .../connection/db_connection/kafka/index.md | 31 - .../db_connection/kafka/kerberos_auth.md | 118 --- .../db_connection/kafka/plaintext_protocol.md | 48 -- .../db_connection/kafka/prerequisites.md | 65 -- .../db_connection/kafka/protocol.md | 37 - .../connection/db_connection/kafka/read.md | 172 ---- .../db_connection/kafka/scram_auth.md | 79 -- .../connection/db_connection/kafka/slots.md | 136 ---- .../db_connection/kafka/ssl_protocol.md | 149 ---- .../db_connection/kafka/troubleshooting.md | 10 - .../connection/db_connection/kafka/write.md | 118 --- .../db_connection/mongodb/connection.md | 124 --- .../connection/db_connection/mongodb/index.md | 18 - .../db_connection/mongodb/pipeline.md | 152 ---- .../db_connection/mongodb/prerequisites.md | 72 -- .../connection/db_connection/mongodb/read.md | 157 ---- .../connection/db_connection/mongodb/types.md | 214 ----- .../connection/db_connection/mongodb/write.md | 118 --- .../db_connection/mssql/connection.md | 186 ----- .../connection/db_connection/mssql/execute.md | 184 ----- .../connection/db_connection/mssql/index.md | 19 - .../db_connection/mssql/prerequisites.md | 78 -- .../connection/db_connection/mssql/read.md | 339 -------- .../connection/db_connection/mssql/sql.md | 191 ----- .../connection/db_connection/mssql/types.md | 281 ------- .../connection/db_connection/mssql/write.md | 183 ----- .../db_connection/mysql/connection.md | 120 --- .../connection/db_connection/mysql/execute.md | 191 ----- .../connection/db_connection/mysql/index.md | 19 - .../db_connection/mysql/prerequisites.md | 61 -- .../connection/db_connection/mysql/read.md | 352 --------- .../connection/db_connection/mysql/sql.md | 192 ----- .../connection/db_connection/mysql/types.md | 292 ------- .../connection/db_connection/mysql/write.md | 187 ----- .../db_connection/oracle/connection.md | 143 ---- .../db_connection/oracle/execute.md | 191 ----- .../connection/db_connection/oracle/index.md | 19 - .../db_connection/oracle/prerequisites.md | 110 --- .../connection/db_connection/oracle/read.md | 352 --------- .../connection/db_connection/oracle/sql.md | 192 ----- .../connection/db_connection/oracle/types.md | 303 ------- .../connection/db_connection/oracle/write.md | 183 ----- .../db_connection/postgres/connection.md | 133 ---- .../db_connection/postgres/execute.md | 189 ----- .../db_connection/postgres/index.md | 19 - .../db_connection/postgres/prerequisites.md | 71 -- .../connection/db_connection/postgres/read.md | 350 --------- .../connection/db_connection/postgres/sql.md | 191 ----- .../db_connection/postgres/types.md | 372 --------- .../db_connection/postgres/write.md | 183 ----- .../db_connection/teradata/connection.md | 134 ---- .../db_connection/teradata/execute.md | 189 ----- .../db_connection/teradata/index.md | 15 - .../db_connection/teradata/prerequisites.md | 57 -- .../connection/db_connection/teradata/read.md | 379 --------- .../connection/db_connection/teradata/sql.md | 190 ----- .../db_connection/teradata/write.md | 250 ------ .../connection/file_connection/ftp.md | 627 --------------- .../connection/file_connection/ftps.md | 627 --------------- .../file_connection/hdfs/connection.md | 740 ------------------ .../connection/file_connection/hdfs/index.md | 11 - .../connection/file_connection/hdfs/slots.md | 256 ------ .../connection/file_connection/index.md | 13 - .../markdown/connection/file_connection/s3.md | 598 -------------- .../connection/file_connection/samba.md | 548 ------------- .../connection/file_connection/sftp.md | 635 --------------- .../connection/file_connection/webdav.md | 592 -------------- .../connection/file_df_connection/base.md | 63 -- .../connection/file_df_connection/index.md | 20 - .../spark_hdfs/connection.md | 169 ---- .../file_df_connection/spark_hdfs/index.md | 12 - .../spark_hdfs/prerequisites.md | 46 -- .../file_df_connection/spark_hdfs/slots.md | 256 ------ .../file_df_connection/spark_local_fs.md | 65 -- .../file_df_connection/spark_s3/connection.md | 232 ------ .../file_df_connection/spark_s3/index.md | 9 - .../spark_s3/prerequisites.md | 61 -- .../spark_s3/troubleshooting.md | 366 --------- mddocs/markdown/connection/index.md | 34 - mddocs/markdown/contributing.md | 391 --------- mddocs/markdown/db/db_reader.md | 346 -------- mddocs/markdown/db/db_writer.md | 100 --- mddocs/markdown/db/index.md | 6 - .../file/file_downloader/file_downloader.md | 352 --------- mddocs/markdown/file/file_downloader/index.md | 9 - .../markdown/file/file_downloader/options.md | 67 -- .../markdown/file/file_downloader/result.md | 550 ------------- mddocs/markdown/file/file_filters/base.md | 42 - .../markdown/file/file_filters/exclude_dir.md | 47 -- .../markdown/file/file_filters/file_filter.md | 77 -- .../file/file_filters/file_mtime_filter.md | 70 -- .../file/file_filters/file_size_filter.md | 74 -- mddocs/markdown/file/file_filters/glob.md | 47 -- mddocs/markdown/file/file_filters/index.md | 20 - .../file/file_filters/match_all_filters.md | 37 - mddocs/markdown/file/file_filters/regexp.md | 59 -- mddocs/markdown/file/file_limits/base.md | 108 --- .../markdown/file/file_limits/file_limit.md | 114 --- mddocs/markdown/file/file_limits/index.md | 19 - .../file/file_limits/limits_reached.md | 38 - .../file/file_limits/limits_stop_at.md | 37 - .../file/file_limits/max_files_count.md | 114 --- .../markdown/file/file_limits/reset_limits.md | 39 - .../file/file_limits/total_files_size.md | 122 --- mddocs/markdown/file/file_mover/file_mover.md | 243 ------ mddocs/markdown/file/file_mover/index.md | 9 - mddocs/markdown/file/file_mover/options.md | 55 -- mddocs/markdown/file/file_mover/result.md | 550 ------------- .../file/file_uploader/file_uploader.md | 241 ------ mddocs/markdown/file/file_uploader/index.md | 9 - mddocs/markdown/file/file_uploader/options.md | 67 -- mddocs/markdown/file/file_uploader/result.md | 550 ------------- mddocs/markdown/file/index.md | 9 - .../file_df/file_df_reader/file_df_reader.md | 158 ---- .../markdown/file_df/file_df_reader/index.md | 8 - .../file_df/file_df_reader/options.md | 38 - .../file_df/file_df_writer/file_df_writer.md | 80 -- .../markdown/file_df/file_df_writer/index.md | 8 - .../file_df/file_df_writer/options.md | 140 ---- mddocs/markdown/file_df/file_formats/avro.md | 390 --------- mddocs/markdown/file_df/file_formats/base.md | 73 -- mddocs/markdown/file_df/file_formats/csv.md | 539 ------------- mddocs/markdown/file_df/file_formats/excel.md | 232 ------ mddocs/markdown/file_df/file_formats/index.md | 18 - mddocs/markdown/file_df/file_formats/json.md | 414 ---------- .../markdown/file_df/file_formats/jsonline.md | 314 -------- mddocs/markdown/file_df/file_formats/orc.md | 80 -- .../markdown/file_df/file_formats/parquet.md | 79 -- mddocs/markdown/file_df/file_formats/xml.md | 440 ----------- mddocs/markdown/file_df/index.md | 7 - mddocs/markdown/hooks/design.md | 642 --------------- mddocs/markdown/hooks/global_state.md | 101 --- mddocs/markdown/hooks/hook.md | 223 ------ mddocs/markdown/hooks/index.md | 14 - mddocs/markdown/hooks/slot.md | 261 ------ mddocs/markdown/hooks/support_hooks.md | 161 ---- mddocs/markdown/hwm_store/index.md | 9 - mddocs/markdown/hwm_store/yaml_hwm_store.md | 161 ---- mddocs/markdown/index.md | 59 -- mddocs/markdown/install/files.md | 20 - mddocs/markdown/install/full.md | 15 - mddocs/markdown/install/index.md | 33 - mddocs/markdown/install/kerberos.md | 32 - mddocs/markdown/install/spark.md | 342 -------- mddocs/markdown/logging.md | 236 ------ mddocs/markdown/plugins.md | 147 ---- mddocs/markdown/quickstart.md | 365 --------- mddocs/markdown/security.md | 25 - .../strategy/incremental_batch_strategy.md | 301 ------- .../markdown/strategy/incremental_strategy.md | 396 ---------- mddocs/markdown/strategy/index.md | 10 - .../strategy/snapshot_batch_strategy.md | 281 ------- mddocs/markdown/strategy/snapshot_strategy.md | 96 --- mddocs/markdown/troubleshooting/index.md | 17 - mddocs/markdown/troubleshooting/spark.md | 72 -- 431 files changed, 33051 deletions(-) delete mode 100644 mddocs/doctrees/changelog.doctree delete mode 100644 mddocs/doctrees/changelog/0.10.0.doctree delete mode 100644 mddocs/doctrees/changelog/0.10.1.doctree delete mode 100644 mddocs/doctrees/changelog/0.10.2.doctree delete mode 100644 mddocs/doctrees/changelog/0.11.0.doctree delete mode 100644 mddocs/doctrees/changelog/0.11.1.doctree delete mode 100644 mddocs/doctrees/changelog/0.11.2.doctree delete mode 100644 mddocs/doctrees/changelog/0.12.0.doctree delete mode 100644 mddocs/doctrees/changelog/0.12.1.doctree delete mode 100644 mddocs/doctrees/changelog/0.12.2.doctree delete mode 100644 mddocs/doctrees/changelog/0.12.3.doctree delete mode 100644 mddocs/doctrees/changelog/0.12.4.doctree delete mode 100644 mddocs/doctrees/changelog/0.12.5.doctree delete mode 100644 mddocs/doctrees/changelog/0.13.0.doctree delete mode 100644 mddocs/doctrees/changelog/0.13.1.doctree delete mode 100644 mddocs/doctrees/changelog/0.13.3.doctree delete mode 100644 mddocs/doctrees/changelog/0.13.4.doctree delete mode 100644 mddocs/doctrees/changelog/0.7.0.doctree delete mode 100644 mddocs/doctrees/changelog/0.7.1.doctree delete mode 100644 mddocs/doctrees/changelog/0.7.2.doctree delete mode 100644 mddocs/doctrees/changelog/0.8.0.doctree delete mode 100644 mddocs/doctrees/changelog/0.8.1.doctree delete mode 100644 mddocs/doctrees/changelog/0.9.0.doctree delete mode 100644 mddocs/doctrees/changelog/0.9.1.doctree delete mode 100644 mddocs/doctrees/changelog/0.9.2.doctree delete mode 100644 mddocs/doctrees/changelog/0.9.3.doctree delete mode 100644 mddocs/doctrees/changelog/0.9.4.doctree delete mode 100644 mddocs/doctrees/changelog/0.9.5.doctree delete mode 100644 mddocs/doctrees/changelog/DRAFT.doctree delete mode 100644 mddocs/doctrees/changelog/NEXT_RELEASE.doctree delete mode 100644 mddocs/doctrees/changelog/index.doctree delete mode 100644 mddocs/doctrees/concepts.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/clickhouse/connection.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/clickhouse/execute.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/clickhouse/index.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/clickhouse/prerequisites.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/clickhouse/read.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/clickhouse/sql.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/clickhouse/types.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/clickhouse/write.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/greenplum/connection.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/greenplum/execute.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/greenplum/index.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/greenplum/prerequisites.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/greenplum/read.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/greenplum/types.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/greenplum/write.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/hive/connection.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/hive/execute.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/hive/index.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/hive/prerequisites.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/hive/read.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/hive/slots.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/hive/sql.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/hive/write.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/index.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/kafka/auth.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/kafka/basic_auth.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/kafka/connection.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/kafka/index.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/kafka/kerberos_auth.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/kafka/plaintext_protocol.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/kafka/prerequisites.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/kafka/protocol.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/kafka/read.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/kafka/scram_auth.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/kafka/slots.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/kafka/ssl_protocol.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/kafka/troubleshooting.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/kafka/write.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mongodb/connection.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mongodb/index.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mongodb/pipeline.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mongodb/prerequisites.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mongodb/read.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mongodb/types.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mongodb/write.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mssql/connection.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mssql/execute.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mssql/index.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mssql/prerequisites.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mssql/read.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mssql/sql.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mssql/types.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mssql/write.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mysql/connection.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mysql/execute.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mysql/index.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mysql/prerequisites.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mysql/read.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mysql/sql.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mysql/types.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/mysql/write.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/oracle/connection.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/oracle/execute.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/oracle/index.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/oracle/prerequisites.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/oracle/read.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/oracle/sql.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/oracle/types.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/oracle/write.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/postgres/connection.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/postgres/execute.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/postgres/index.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/postgres/prerequisites.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/postgres/read.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/postgres/sql.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/postgres/types.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/postgres/write.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/teradata/connection.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/teradata/execute.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/teradata/index.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/teradata/prerequisites.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/teradata/read.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/teradata/sql.doctree delete mode 100644 mddocs/doctrees/connection/db_connection/teradata/write.doctree delete mode 100644 mddocs/doctrees/connection/file_connection/ftp.doctree delete mode 100644 mddocs/doctrees/connection/file_connection/ftps.doctree delete mode 100644 mddocs/doctrees/connection/file_connection/hdfs/connection.doctree delete mode 100644 mddocs/doctrees/connection/file_connection/hdfs/index.doctree delete mode 100644 mddocs/doctrees/connection/file_connection/hdfs/slots.doctree delete mode 100644 mddocs/doctrees/connection/file_connection/index.doctree delete mode 100644 mddocs/doctrees/connection/file_connection/s3.doctree delete mode 100644 mddocs/doctrees/connection/file_connection/samba.doctree delete mode 100644 mddocs/doctrees/connection/file_connection/sftp.doctree delete mode 100644 mddocs/doctrees/connection/file_connection/webdav.doctree delete mode 100644 mddocs/doctrees/connection/file_df_connection/base.doctree delete mode 100644 mddocs/doctrees/connection/file_df_connection/index.doctree delete mode 100644 mddocs/doctrees/connection/file_df_connection/spark_hdfs/connection.doctree delete mode 100644 mddocs/doctrees/connection/file_df_connection/spark_hdfs/index.doctree delete mode 100644 mddocs/doctrees/connection/file_df_connection/spark_hdfs/prerequisites.doctree delete mode 100644 mddocs/doctrees/connection/file_df_connection/spark_hdfs/slots.doctree delete mode 100644 mddocs/doctrees/connection/file_df_connection/spark_local_fs.doctree delete mode 100644 mddocs/doctrees/connection/file_df_connection/spark_s3/connection.doctree delete mode 100644 mddocs/doctrees/connection/file_df_connection/spark_s3/index.doctree delete mode 100644 mddocs/doctrees/connection/file_df_connection/spark_s3/prerequisites.doctree delete mode 100644 mddocs/doctrees/connection/file_df_connection/spark_s3/troubleshooting.doctree delete mode 100644 mddocs/doctrees/connection/index.doctree delete mode 100644 mddocs/doctrees/contributing.doctree delete mode 100644 mddocs/doctrees/db/db_reader.doctree delete mode 100644 mddocs/doctrees/db/db_writer.doctree delete mode 100644 mddocs/doctrees/db/index.doctree delete mode 100644 mddocs/doctrees/file/file_downloader/file_downloader.doctree delete mode 100644 mddocs/doctrees/file/file_downloader/index.doctree delete mode 100644 mddocs/doctrees/file/file_downloader/options.doctree delete mode 100644 mddocs/doctrees/file/file_downloader/result.doctree delete mode 100644 mddocs/doctrees/file/file_filters/base.doctree delete mode 100644 mddocs/doctrees/file/file_filters/exclude_dir.doctree delete mode 100644 mddocs/doctrees/file/file_filters/file_filter.doctree delete mode 100644 mddocs/doctrees/file/file_filters/file_mtime_filter.doctree delete mode 100644 mddocs/doctrees/file/file_filters/file_size_filter.doctree delete mode 100644 mddocs/doctrees/file/file_filters/glob.doctree delete mode 100644 mddocs/doctrees/file/file_filters/index.doctree delete mode 100644 mddocs/doctrees/file/file_filters/match_all_filters.doctree delete mode 100644 mddocs/doctrees/file/file_filters/regexp.doctree delete mode 100644 mddocs/doctrees/file/file_limits/base.doctree delete mode 100644 mddocs/doctrees/file/file_limits/file_limit.doctree delete mode 100644 mddocs/doctrees/file/file_limits/index.doctree delete mode 100644 mddocs/doctrees/file/file_limits/limits_reached.doctree delete mode 100644 mddocs/doctrees/file/file_limits/limits_stop_at.doctree delete mode 100644 mddocs/doctrees/file/file_limits/max_files_count.doctree delete mode 100644 mddocs/doctrees/file/file_limits/reset_limits.doctree delete mode 100644 mddocs/doctrees/file/file_limits/total_files_size.doctree delete mode 100644 mddocs/doctrees/file/file_mover/file_mover.doctree delete mode 100644 mddocs/doctrees/file/file_mover/index.doctree delete mode 100644 mddocs/doctrees/file/file_mover/options.doctree delete mode 100644 mddocs/doctrees/file/file_mover/result.doctree delete mode 100644 mddocs/doctrees/file/file_uploader/file_uploader.doctree delete mode 100644 mddocs/doctrees/file/file_uploader/index.doctree delete mode 100644 mddocs/doctrees/file/file_uploader/options.doctree delete mode 100644 mddocs/doctrees/file/file_uploader/result.doctree delete mode 100644 mddocs/doctrees/file/index.doctree delete mode 100644 mddocs/doctrees/file_df/file_df_reader/file_df_reader.doctree delete mode 100644 mddocs/doctrees/file_df/file_df_reader/index.doctree delete mode 100644 mddocs/doctrees/file_df/file_df_reader/options.doctree delete mode 100644 mddocs/doctrees/file_df/file_df_writer/file_df_writer.doctree delete mode 100644 mddocs/doctrees/file_df/file_df_writer/index.doctree delete mode 100644 mddocs/doctrees/file_df/file_df_writer/options.doctree delete mode 100644 mddocs/doctrees/file_df/file_formats/avro.doctree delete mode 100644 mddocs/doctrees/file_df/file_formats/base.doctree delete mode 100644 mddocs/doctrees/file_df/file_formats/csv.doctree delete mode 100644 mddocs/doctrees/file_df/file_formats/excel.doctree delete mode 100644 mddocs/doctrees/file_df/file_formats/index.doctree delete mode 100644 mddocs/doctrees/file_df/file_formats/json.doctree delete mode 100644 mddocs/doctrees/file_df/file_formats/jsonline.doctree delete mode 100644 mddocs/doctrees/file_df/file_formats/orc.doctree delete mode 100644 mddocs/doctrees/file_df/file_formats/parquet.doctree delete mode 100644 mddocs/doctrees/file_df/file_formats/xml.doctree delete mode 100644 mddocs/doctrees/file_df/index.doctree delete mode 100644 mddocs/doctrees/hooks/design.doctree delete mode 100644 mddocs/doctrees/hooks/global_state.doctree delete mode 100644 mddocs/doctrees/hooks/hook.doctree delete mode 100644 mddocs/doctrees/hooks/index.doctree delete mode 100644 mddocs/doctrees/hooks/slot.doctree delete mode 100644 mddocs/doctrees/hooks/support_hooks.doctree delete mode 100644 mddocs/doctrees/hwm_store/index.doctree delete mode 100644 mddocs/doctrees/hwm_store/yaml_hwm_store.doctree delete mode 100644 mddocs/doctrees/index.doctree delete mode 100644 mddocs/doctrees/install/files.doctree delete mode 100644 mddocs/doctrees/install/full.doctree delete mode 100644 mddocs/doctrees/install/index.doctree delete mode 100644 mddocs/doctrees/install/kerberos.doctree delete mode 100644 mddocs/doctrees/install/spark.doctree delete mode 100644 mddocs/doctrees/logging.doctree delete mode 100644 mddocs/doctrees/plugins.doctree delete mode 100644 mddocs/doctrees/quickstart.doctree delete mode 100644 mddocs/doctrees/security.doctree delete mode 100644 mddocs/doctrees/strategy/incremental_batch_strategy.doctree delete mode 100644 mddocs/doctrees/strategy/incremental_strategy.doctree delete mode 100644 mddocs/doctrees/strategy/index.doctree delete mode 100644 mddocs/doctrees/strategy/snapshot_batch_strategy.doctree delete mode 100644 mddocs/doctrees/strategy/snapshot_strategy.doctree delete mode 100644 mddocs/doctrees/troubleshooting/index.doctree delete mode 100644 mddocs/doctrees/troubleshooting/spark.doctree delete mode 100644 mddocs/markdown/_sphinx_design_static/design-tabs.js delete mode 100644 mddocs/markdown/_sphinx_design_static/sphinx-design.min.css delete mode 100644 mddocs/markdown/_static/autodoc_pydantic.css delete mode 100644 mddocs/markdown/changelog.md delete mode 100644 mddocs/markdown/changelog/0.10.0.md delete mode 100644 mddocs/markdown/changelog/0.10.1.md delete mode 100644 mddocs/markdown/changelog/0.10.2.md delete mode 100644 mddocs/markdown/changelog/0.11.0.md delete mode 100644 mddocs/markdown/changelog/0.11.1.md delete mode 100644 mddocs/markdown/changelog/0.11.2.md delete mode 100644 mddocs/markdown/changelog/0.12.0.md delete mode 100644 mddocs/markdown/changelog/0.12.1.md delete mode 100644 mddocs/markdown/changelog/0.12.2.md delete mode 100644 mddocs/markdown/changelog/0.12.3.md delete mode 100644 mddocs/markdown/changelog/0.12.4.md delete mode 100644 mddocs/markdown/changelog/0.12.5.md delete mode 100644 mddocs/markdown/changelog/0.13.0.md delete mode 100644 mddocs/markdown/changelog/0.13.1.md delete mode 100644 mddocs/markdown/changelog/0.13.3.md delete mode 100644 mddocs/markdown/changelog/0.13.4.md delete mode 100644 mddocs/markdown/changelog/0.7.0.md delete mode 100644 mddocs/markdown/changelog/0.7.1.md delete mode 100644 mddocs/markdown/changelog/0.7.2.md delete mode 100644 mddocs/markdown/changelog/0.8.0.md delete mode 100644 mddocs/markdown/changelog/0.8.1.md delete mode 100644 mddocs/markdown/changelog/0.9.0.md delete mode 100644 mddocs/markdown/changelog/0.9.1.md delete mode 100644 mddocs/markdown/changelog/0.9.2.md delete mode 100644 mddocs/markdown/changelog/0.9.3.md delete mode 100644 mddocs/markdown/changelog/0.9.4.md delete mode 100644 mddocs/markdown/changelog/0.9.5.md delete mode 100644 mddocs/markdown/changelog/DRAFT.md delete mode 100644 mddocs/markdown/changelog/NEXT_RELEASE.md delete mode 100644 mddocs/markdown/changelog/index.md delete mode 100644 mddocs/markdown/concepts.md delete mode 100644 mddocs/markdown/connection/db_connection/clickhouse/connection.md delete mode 100644 mddocs/markdown/connection/db_connection/clickhouse/execute.md delete mode 100644 mddocs/markdown/connection/db_connection/clickhouse/index.md delete mode 100644 mddocs/markdown/connection/db_connection/clickhouse/prerequisites.md delete mode 100644 mddocs/markdown/connection/db_connection/clickhouse/read.md delete mode 100644 mddocs/markdown/connection/db_connection/clickhouse/sql.md delete mode 100644 mddocs/markdown/connection/db_connection/clickhouse/types.md delete mode 100644 mddocs/markdown/connection/db_connection/clickhouse/write.md delete mode 100644 mddocs/markdown/connection/db_connection/greenplum/connection.md delete mode 100644 mddocs/markdown/connection/db_connection/greenplum/execute.md delete mode 100644 mddocs/markdown/connection/db_connection/greenplum/index.md delete mode 100644 mddocs/markdown/connection/db_connection/greenplum/prerequisites.md delete mode 100644 mddocs/markdown/connection/db_connection/greenplum/read.md delete mode 100644 mddocs/markdown/connection/db_connection/greenplum/types.md delete mode 100644 mddocs/markdown/connection/db_connection/greenplum/write.md delete mode 100644 mddocs/markdown/connection/db_connection/hive/connection.md delete mode 100644 mddocs/markdown/connection/db_connection/hive/execute.md delete mode 100644 mddocs/markdown/connection/db_connection/hive/index.md delete mode 100644 mddocs/markdown/connection/db_connection/hive/prerequisites.md delete mode 100644 mddocs/markdown/connection/db_connection/hive/read.md delete mode 100644 mddocs/markdown/connection/db_connection/hive/slots.md delete mode 100644 mddocs/markdown/connection/db_connection/hive/sql.md delete mode 100644 mddocs/markdown/connection/db_connection/hive/write.md delete mode 100644 mddocs/markdown/connection/db_connection/index.md delete mode 100644 mddocs/markdown/connection/db_connection/kafka/auth.md delete mode 100644 mddocs/markdown/connection/db_connection/kafka/basic_auth.md delete mode 100644 mddocs/markdown/connection/db_connection/kafka/connection.md delete mode 100644 mddocs/markdown/connection/db_connection/kafka/index.md delete mode 100644 mddocs/markdown/connection/db_connection/kafka/kerberos_auth.md delete mode 100644 mddocs/markdown/connection/db_connection/kafka/plaintext_protocol.md delete mode 100644 mddocs/markdown/connection/db_connection/kafka/prerequisites.md delete mode 100644 mddocs/markdown/connection/db_connection/kafka/protocol.md delete mode 100644 mddocs/markdown/connection/db_connection/kafka/read.md delete mode 100644 mddocs/markdown/connection/db_connection/kafka/scram_auth.md delete mode 100644 mddocs/markdown/connection/db_connection/kafka/slots.md delete mode 100644 mddocs/markdown/connection/db_connection/kafka/ssl_protocol.md delete mode 100644 mddocs/markdown/connection/db_connection/kafka/troubleshooting.md delete mode 100644 mddocs/markdown/connection/db_connection/kafka/write.md delete mode 100644 mddocs/markdown/connection/db_connection/mongodb/connection.md delete mode 100644 mddocs/markdown/connection/db_connection/mongodb/index.md delete mode 100644 mddocs/markdown/connection/db_connection/mongodb/pipeline.md delete mode 100644 mddocs/markdown/connection/db_connection/mongodb/prerequisites.md delete mode 100644 mddocs/markdown/connection/db_connection/mongodb/read.md delete mode 100644 mddocs/markdown/connection/db_connection/mongodb/types.md delete mode 100644 mddocs/markdown/connection/db_connection/mongodb/write.md delete mode 100644 mddocs/markdown/connection/db_connection/mssql/connection.md delete mode 100644 mddocs/markdown/connection/db_connection/mssql/execute.md delete mode 100644 mddocs/markdown/connection/db_connection/mssql/index.md delete mode 100644 mddocs/markdown/connection/db_connection/mssql/prerequisites.md delete mode 100644 mddocs/markdown/connection/db_connection/mssql/read.md delete mode 100644 mddocs/markdown/connection/db_connection/mssql/sql.md delete mode 100644 mddocs/markdown/connection/db_connection/mssql/types.md delete mode 100644 mddocs/markdown/connection/db_connection/mssql/write.md delete mode 100644 mddocs/markdown/connection/db_connection/mysql/connection.md delete mode 100644 mddocs/markdown/connection/db_connection/mysql/execute.md delete mode 100644 mddocs/markdown/connection/db_connection/mysql/index.md delete mode 100644 mddocs/markdown/connection/db_connection/mysql/prerequisites.md delete mode 100644 mddocs/markdown/connection/db_connection/mysql/read.md delete mode 100644 mddocs/markdown/connection/db_connection/mysql/sql.md delete mode 100644 mddocs/markdown/connection/db_connection/mysql/types.md delete mode 100644 mddocs/markdown/connection/db_connection/mysql/write.md delete mode 100644 mddocs/markdown/connection/db_connection/oracle/connection.md delete mode 100644 mddocs/markdown/connection/db_connection/oracle/execute.md delete mode 100644 mddocs/markdown/connection/db_connection/oracle/index.md delete mode 100644 mddocs/markdown/connection/db_connection/oracle/prerequisites.md delete mode 100644 mddocs/markdown/connection/db_connection/oracle/read.md delete mode 100644 mddocs/markdown/connection/db_connection/oracle/sql.md delete mode 100644 mddocs/markdown/connection/db_connection/oracle/types.md delete mode 100644 mddocs/markdown/connection/db_connection/oracle/write.md delete mode 100644 mddocs/markdown/connection/db_connection/postgres/connection.md delete mode 100644 mddocs/markdown/connection/db_connection/postgres/execute.md delete mode 100644 mddocs/markdown/connection/db_connection/postgres/index.md delete mode 100644 mddocs/markdown/connection/db_connection/postgres/prerequisites.md delete mode 100644 mddocs/markdown/connection/db_connection/postgres/read.md delete mode 100644 mddocs/markdown/connection/db_connection/postgres/sql.md delete mode 100644 mddocs/markdown/connection/db_connection/postgres/types.md delete mode 100644 mddocs/markdown/connection/db_connection/postgres/write.md delete mode 100644 mddocs/markdown/connection/db_connection/teradata/connection.md delete mode 100644 mddocs/markdown/connection/db_connection/teradata/execute.md delete mode 100644 mddocs/markdown/connection/db_connection/teradata/index.md delete mode 100644 mddocs/markdown/connection/db_connection/teradata/prerequisites.md delete mode 100644 mddocs/markdown/connection/db_connection/teradata/read.md delete mode 100644 mddocs/markdown/connection/db_connection/teradata/sql.md delete mode 100644 mddocs/markdown/connection/db_connection/teradata/write.md delete mode 100644 mddocs/markdown/connection/file_connection/ftp.md delete mode 100644 mddocs/markdown/connection/file_connection/ftps.md delete mode 100644 mddocs/markdown/connection/file_connection/hdfs/connection.md delete mode 100644 mddocs/markdown/connection/file_connection/hdfs/index.md delete mode 100644 mddocs/markdown/connection/file_connection/hdfs/slots.md delete mode 100644 mddocs/markdown/connection/file_connection/index.md delete mode 100644 mddocs/markdown/connection/file_connection/s3.md delete mode 100644 mddocs/markdown/connection/file_connection/samba.md delete mode 100644 mddocs/markdown/connection/file_connection/sftp.md delete mode 100644 mddocs/markdown/connection/file_connection/webdav.md delete mode 100644 mddocs/markdown/connection/file_df_connection/base.md delete mode 100644 mddocs/markdown/connection/file_df_connection/index.md delete mode 100644 mddocs/markdown/connection/file_df_connection/spark_hdfs/connection.md delete mode 100644 mddocs/markdown/connection/file_df_connection/spark_hdfs/index.md delete mode 100644 mddocs/markdown/connection/file_df_connection/spark_hdfs/prerequisites.md delete mode 100644 mddocs/markdown/connection/file_df_connection/spark_hdfs/slots.md delete mode 100644 mddocs/markdown/connection/file_df_connection/spark_local_fs.md delete mode 100644 mddocs/markdown/connection/file_df_connection/spark_s3/connection.md delete mode 100644 mddocs/markdown/connection/file_df_connection/spark_s3/index.md delete mode 100644 mddocs/markdown/connection/file_df_connection/spark_s3/prerequisites.md delete mode 100644 mddocs/markdown/connection/file_df_connection/spark_s3/troubleshooting.md delete mode 100644 mddocs/markdown/connection/index.md delete mode 100644 mddocs/markdown/contributing.md delete mode 100644 mddocs/markdown/db/db_reader.md delete mode 100644 mddocs/markdown/db/db_writer.md delete mode 100644 mddocs/markdown/db/index.md delete mode 100644 mddocs/markdown/file/file_downloader/file_downloader.md delete mode 100644 mddocs/markdown/file/file_downloader/index.md delete mode 100644 mddocs/markdown/file/file_downloader/options.md delete mode 100644 mddocs/markdown/file/file_downloader/result.md delete mode 100644 mddocs/markdown/file/file_filters/base.md delete mode 100644 mddocs/markdown/file/file_filters/exclude_dir.md delete mode 100644 mddocs/markdown/file/file_filters/file_filter.md delete mode 100644 mddocs/markdown/file/file_filters/file_mtime_filter.md delete mode 100644 mddocs/markdown/file/file_filters/file_size_filter.md delete mode 100644 mddocs/markdown/file/file_filters/glob.md delete mode 100644 mddocs/markdown/file/file_filters/index.md delete mode 100644 mddocs/markdown/file/file_filters/match_all_filters.md delete mode 100644 mddocs/markdown/file/file_filters/regexp.md delete mode 100644 mddocs/markdown/file/file_limits/base.md delete mode 100644 mddocs/markdown/file/file_limits/file_limit.md delete mode 100644 mddocs/markdown/file/file_limits/index.md delete mode 100644 mddocs/markdown/file/file_limits/limits_reached.md delete mode 100644 mddocs/markdown/file/file_limits/limits_stop_at.md delete mode 100644 mddocs/markdown/file/file_limits/max_files_count.md delete mode 100644 mddocs/markdown/file/file_limits/reset_limits.md delete mode 100644 mddocs/markdown/file/file_limits/total_files_size.md delete mode 100644 mddocs/markdown/file/file_mover/file_mover.md delete mode 100644 mddocs/markdown/file/file_mover/index.md delete mode 100644 mddocs/markdown/file/file_mover/options.md delete mode 100644 mddocs/markdown/file/file_mover/result.md delete mode 100644 mddocs/markdown/file/file_uploader/file_uploader.md delete mode 100644 mddocs/markdown/file/file_uploader/index.md delete mode 100644 mddocs/markdown/file/file_uploader/options.md delete mode 100644 mddocs/markdown/file/file_uploader/result.md delete mode 100644 mddocs/markdown/file/index.md delete mode 100644 mddocs/markdown/file_df/file_df_reader/file_df_reader.md delete mode 100644 mddocs/markdown/file_df/file_df_reader/index.md delete mode 100644 mddocs/markdown/file_df/file_df_reader/options.md delete mode 100644 mddocs/markdown/file_df/file_df_writer/file_df_writer.md delete mode 100644 mddocs/markdown/file_df/file_df_writer/index.md delete mode 100644 mddocs/markdown/file_df/file_df_writer/options.md delete mode 100644 mddocs/markdown/file_df/file_formats/avro.md delete mode 100644 mddocs/markdown/file_df/file_formats/base.md delete mode 100644 mddocs/markdown/file_df/file_formats/csv.md delete mode 100644 mddocs/markdown/file_df/file_formats/excel.md delete mode 100644 mddocs/markdown/file_df/file_formats/index.md delete mode 100644 mddocs/markdown/file_df/file_formats/json.md delete mode 100644 mddocs/markdown/file_df/file_formats/jsonline.md delete mode 100644 mddocs/markdown/file_df/file_formats/orc.md delete mode 100644 mddocs/markdown/file_df/file_formats/parquet.md delete mode 100644 mddocs/markdown/file_df/file_formats/xml.md delete mode 100644 mddocs/markdown/file_df/index.md delete mode 100644 mddocs/markdown/hooks/design.md delete mode 100644 mddocs/markdown/hooks/global_state.md delete mode 100644 mddocs/markdown/hooks/hook.md delete mode 100644 mddocs/markdown/hooks/index.md delete mode 100644 mddocs/markdown/hooks/slot.md delete mode 100644 mddocs/markdown/hooks/support_hooks.md delete mode 100644 mddocs/markdown/hwm_store/index.md delete mode 100644 mddocs/markdown/hwm_store/yaml_hwm_store.md delete mode 100644 mddocs/markdown/index.md delete mode 100644 mddocs/markdown/install/files.md delete mode 100644 mddocs/markdown/install/full.md delete mode 100644 mddocs/markdown/install/index.md delete mode 100644 mddocs/markdown/install/kerberos.md delete mode 100644 mddocs/markdown/install/spark.md delete mode 100644 mddocs/markdown/logging.md delete mode 100644 mddocs/markdown/plugins.md delete mode 100644 mddocs/markdown/quickstart.md delete mode 100644 mddocs/markdown/security.md delete mode 100644 mddocs/markdown/strategy/incremental_batch_strategy.md delete mode 100644 mddocs/markdown/strategy/incremental_strategy.md delete mode 100644 mddocs/markdown/strategy/index.md delete mode 100644 mddocs/markdown/strategy/snapshot_batch_strategy.md delete mode 100644 mddocs/markdown/strategy/snapshot_strategy.md delete mode 100644 mddocs/markdown/troubleshooting/index.md delete mode 100644 mddocs/markdown/troubleshooting/spark.md diff --git a/mddocs/doctrees/changelog.doctree b/mddocs/doctrees/changelog.doctree deleted file mode 100644 index 9a5a8b8843ee25c232d790ef7b66528c9e68a0a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3515 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 diff --git a/mddocs/doctrees/changelog/0.11.0.doctree b/mddocs/doctrees/changelog/0.11.0.doctree deleted file mode 100644 index 2b6db47b6f74c5b8b46bd7417dcba0d118d7bd30..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61627 zcmeHw36xw{d8QV3OWkU1wrm_*HjnnAmXN9!snv3WAli(Kt+ugbknwS%@B#q}4q;&8Ojyn^9QK3|k^@XK$!rWmUS-~ZqL{&#zF;GG};yAABWV5iq8yS1x1yHu*xOODqL zwwCI}R@JHb?RT{Izoz~3?Xh5}XbTy%U@MZMzXT`_eTFJSZEBn>TaJ#(a@pgF#3_A-B7zD#Y2q1cUgkX_( zfYGhz`-Nwo6GmJxZ2NxGop1T*SL(;jZplNR1zU<0+w+i!&jU5P%0F!_wHoTnyj{Em zDX=`7?TX(n?+G@ynr=Io&eTEeV7O@4N^Z&K)C3z}+%6yRTEQ0IUZiH*<(q;*fwGYo zHa$PzXx1zB#TT`M>nd)|Y2Uvo7;M<+77%lNYu+yJ1ykkQ(PI|=jpE;(_;)v$8U*Ex z))_%-pGau8%DM8c@>F@GYdd>v(G=wyE{~Td%9HnRLjKLcP+lrfd2?$XXxidAMMD9~ zgM_p({5SwjsYGCQ1JF5{o3O^FCZ>)YnwUN`HM3uU?YaxjRZ`5LYo^z?nZAOI`^qQE zo6F;K<;l6O41|Ir>R*EFr+M~Z0xD5;#y#N1ZjSra#yFGPIH}Hfv252C9drn(4+zYm zi`v9Cm^{TTMR`Clc2*<=yG}y?F1fWu>lCZN(*tb z>A{X!tp+NFt;|{H>(&*=s@ly<77UE1YO_7-z=8RY@dF1eC{VfHS}YG&>z;4b7p#_t z%B(AHrDE0UzU4WNW!ILilDiNZ9xun_G*+GqF0CWgZPf==y$^uYLCxx%g!ea|~tfCL+g6V8xi{ZeDH5b(CCFn4rXA4?qXksYZHs7jL96w)i z0d+MJ2#{l-pkMN??^Ly)FaEoY8#X-BAWLVRs<_2V=z3t|rKeAxvPw-CFd{S&tbhdp zsGkK*oBf@@BeY z0(tfrc{(G6=LI!76MH{6-p6t6i%@lq}H-*7o z?tka~dc87VznbGlAm@5si=6SH`{=_QJacJZf^B8nWBGlz(Y_Xpu)O(Nt2*yA+xLGz z7~%=7W~Ch*7XD9IFW^6Zt`77&7o3W7ei=Zodf^~XhG!ghrwv#4x;xkh@`mTaFUqWb zQs*Gvm`g9L{28*aABScA5VPx>_Rq%2BiTrQAT-j$sOs04fgV=MCIuv%kn%4JA4J&e zcZ!6dv>ry44~P}fX!)Dm=bH=mbZcD`$+vX@!=!A1u_6nnu^5b{9RkF#d}o>wLnGop z>qNx&GIj0K=#Ou+t{q?lj#f*QmG1~y1f#tNB|-S@(Q&~XzltTccW!o;P|_!|DF+Ca{aoC}49}HyhZIUu*p_qxIf6 zCwjZ%Jb@D!^Qe)FbAAOQcc#g%@lnmrM?^179@6Q>cId@}OfJdt6IK%WNiX6n4+iFQG+c;nkgL2_{yRqb@}W7G_>_T9Ba8^7q+k*<_7r6?G8uiLlc? zeXaZ(_y4f`n;D`7(?`N+0ez6s0(?=}+OjD+(TJC&3>Yj8p-dPvP?FuWUz@41WPSdU zJYa~?<$840H&|E2lR*uO0fzGbN8Yt+%64|O?2rlS{Uj<0YG&*-K|bFQWE9k5@DbfG z$P={kQ%3n@2@t5M#qOX^parwXaO4xD46yGSC8x7f5`}aJ84*bTARD9)3rORGg7jgH z{+FkwUqSjJ$&!b`lw3C*w6tmyxjA>+#!T!Sn-1Bb50V{xQ5w3Gft^ZMb|M;TvXf3j zZvgK0QRNBiySj!_{tgX&c=d;OE<7~=1thdkSGR=CX)=9_AyZ9RM|9s#^vPD??rC+r z)2O4j!cwc<6*hq$1X-f4rJ!jf1q7M1B8HCcIBi3H8MXiFc$byni+| z{Yrg5N0JTIS4ugmzKPtN+nZ5+#Q=LuA7JrCvGb)2?0mT^I}!CY*-59qd!UZLNR_8l zU&`O1zW1!mk=CZ&$FkPMutV{wAz6ba^6wcU*R&h{rI=s|{0;I1t^D_m^1U^iTI{ab zJ!xUHJDnCJXtZqGoYp@h{F7TCX^Coc2O$w{{@>ZOc~WRIJ}7OT)abu0HT_DPpNld= zn-w<^_?z6Mga3^X&)@U`f6C9{A7B5{+u(pMy`8*JbbyF-k!SixDLh#b?4ta9vKHSG z)i2^*G`aiT6!nXc7v=wKiu}T}M*hywP4JFz-g^!kP3osliY+-<((l+YF-x%;>_o~< z%7_cT9_}7;neGxBIZWXVb1wnkIe^W99jpIx=nv}aOr*3$2m5JQQ<1!(B2jZCgeVkj zkSA#67mf10%@wuS-CXsgh4r9L3lcP1mb23mHCG*kM9kIy&1SBIvpual+xVhJ>o=#S zUzw|4B58)shm>s8`AFpE+^rd%4>1)wrcZ_NMX~d%8QA%?uIxmtugOlb%@VWw=fDO% zMwRQ4_vaar_f8ELU`vrxG5*=qj8o4E3V%vcn9Px|lHe%8N)`$i&piO^*9dorb+wT& zkuzGj7he$>=oaq9hBLz+E;v`L`PL%eS9Jy3(=6K4=DXPAgY zvXYO6#%lG6l=g5-*o211uNd8;nYt|@C8E1GLwH)rZyF_g8#!u=8M(blJ)kv_Kpfmg z(NTiFL7t$M|EugInj15gdeXvBqSJx|jg}u|rzL8KI|$M3@VU*|EZ4Ny#Y7*J-*}J4 zz|QN^`n}2!??xG6h-L3b{Ji&KLeHrxx(kL{aPd$T8R1pb(7dCC6!4*wRXy z*Q<5fIF6RE;TItSmrw}lgFr2 z@l$ERIxc@_5JV#VH##~RemZ~VfitHrSO>zd_dfjKIR>Cyb=kM)EBqvrWxe9;nTO8+ zfy=0HA2HFx1} zgO+s%HoYIhhIn79dSzK_OjBue-9(QPqD#pH)#_Ks{3O~{I#y#Bi%!EwP7Q^y62h3$ zsj@3}vjiQC?87G`=u}3{LMfoof9uOe+rV z6bhH=JQBRxE`I0}>pq6C@t0zUw}cEITP%tuQ8$Aft5N+Z|o}p+6FXn}>xld6$6l@Obb*zQ@v1YdBC~X#;z!M(45?uD z9bWq}_YtX#2Hydq5-~qRCT*k)%1PhULE!bK? zh^&R%7jUNxj}*3p9c6d1T){s-kL!l$W*v+|OIJv1_#G1al`5!d*B}p!3B43wmD;@} zliIyNwzYH>6^L#47irJPvsKSjXhQ|o`U|UN)409^YTA&}Gn}TY^G`a5EmHJl4%fbO zq}6zD538~CMr2re6aBe{Ki8Ii5r3t9SPJm1!zLV;daCVC{K&H@vdSzM+Yu?L)?A#A zfNA|WhUR;%YMk)XMmxQy{6ra>8jP@epTYojI@0@`68u4S;__=8B2ULJmhMC(T@F+{ zK`Z}_>|9395}S}o=M}$9DpGQOv9^pDXqUC+V^~{0kF`*r?hh&yXo*>^SMr5qw}wTF zLg8Kq*Bq(eXf?G^ICIs(orjJ}HqTDZu!EPwb6BMg@7iOF!npHDtpzVo%`r>VyctBj z9Ntn)HolTg*Ft#>y1DGKQ(30f5-d33f4Xa|vMhlWyO5o7N|Cg7+Q;f?;`*t%g34ti zSFlEfZ*O(@^>GWP+UjM8L$7ta@-%3mVa-?DueC9~&z1ZEbZ>0>GbSmn4FVXhZop)G| zc(h)xYYN+j!K6sPVAq%(kqLMd%hh7#e0_Ufr*%v$oAUe4!gVmZ$xQYb_2+6}LWnAA zKbWw%qLZ{dAG;~&0#+{~v-QMLls;~JG|2ClE6v?S1`Uut0*mM~*~Qqfl%_7SxU4B| z0Xw^;iHliY9u~{XmfdxI*`oF3dIhV}bT4Y(E6nzqk0i?Xl1*s?^8YdhWX<=20~3*z z7}d9Fsaj2&Ff8g`TyO7++7{jw6CN8jwJH;c!@)CM4^>odL{HZ0+LN8UNQ7_-Pvi2?uYCkEV6Qaea4k*ag1z6KSqWxD*iw&QM$fYE7!9qBUY{@ z5~3^DCOgSqn}{Y}Ko9)`hn5PVy#LwYPDxe0IgVOQ<_{Y(*EAPiCfvUS!zomGZ&Eg` ztydat_10~oj_G!X@+8oWi`{y;t%CB~lQU|xSF+Q7CS2%UnUk#1V)C92&@b->=+wda zaI;D3{7S1|*O%mk`aLSCjYbuhS3Ro80nQwq?9P2U$DTFS#i=(P<X`LK7xLD|6zH_2nCWFP5<(17-!OhwC*%>G(XEY9 ziPF^9Oir#o{sa-_K8KATTSQ3f_whBbesyV)H0$@RAKyrVoGJ`S9yLGn3x-u+%ele7 zGTQ1rHz4Y|&kYjjUX!`Ox3be7w<46lLZ1PZzs^ovX+7dv>F}0nS?Gt^S&b|UnZi#u zzk02-9m)B{vJe6cU6zF|;Rxn=r|H@i#Qre@KgA9~oemE@pMAwSew0N$>amY5)TG}p zU_IdIk+Gw6zHp3Au(;teZQxcOT z;*A|_Dzv6>fd@YgMDKav6CFDv*|6t2lyD&+b^_fFS4lc^96f?O=20tpz{hW~V?@}= zh%-hFAFu3SJz}AW06*5s>Xv=E?m`Y&G^5Oo3N{0&^J|e4f?{{KSS-RObv{|Ntn!uW zaq)BRM9Z(Ac6_YJ0Wu;pts&5&!tv4Savs^Wg(-_pYhewKAai^azlA#^Mn#-GT!>&x zIGB}^Af8bYPn|e_VNB6!oj7j^ARj%lUy^OnNnLs7lnO-|2?szG;rnOL8s(E?H;w@d zd!ZRqYfChkstw-Q$J<3SMNDjW$8TD#V@9pLhiRgN4bwM}qAM+Y`pIAM^s5IG=-sV! zFf?$2*6pg#VfZvUXS37!{64Iw)?xzvbWkHS&?=6lSZw*?Euli`p*l8PxhGDI=KJ|@<<1mH+2Kdr=N{} zg#^$GS3Yuni?^UZZg{OA)Z zf%BJZ51jYN?o8Ho5n+Ir()W*H?hw0*8;&Tffax?Y z;PG9DPJfr&qOV5|9(niyZhF~DA-i*=GfHqw-RKrxuG2u%Y?_THE^*t9);?<)b5^{2 zS#dA1;D>xybc_*q{O3lCtXQ5N70icAXQQ*$DE~NloJAKV5Av^5{Oi={&m$(nrmWa6 ziF=!P%)xFnur(EL6ylL>x~K=U6G;JgQ4R_^!rJdTe45vP*VL)71(7t>_2Q4}y2@FO z?02PVRvStNx5myDF+%Dn5C)1OEg>|tdUhD~^d82kWj2i8oep1GV;w|CCNd)#Co;#x zL*u9cceHIW%5Hp7XGWH~yKG1*C0abe-#50kKo%TP^Z{)}y>{;}6LiT9(~ z_|^J!*635shn+*PWEIVjK-EiEuTqVVlLjBX6_7Y*3OJkHKxzae2y$Pckjjn<3Ehz* zx7PB7?5szE|0W0N^zegH50mqYp&5EBhUWMJ?Aq;M_&m5O8SD}-o*xQd zlW*H)3s2{cKHgTpiM#0V+nxCB?#EfhCsE;PzU@-JUVloQ;uP=Jo)rnf)_ZZ)UJIdO z(N8KtxvQ`1t215#;k`zdU_^Wvm{bOB@xCg?o68P+v!24a2zX-D#)IT^BkcY5!X+Df z>^u*rWZ}8sd<^Dq_PmW3&)I!;w4WOJaI%&!tI*wF6yry^Ivbb_uogp(6-z{JhH;_+8FemGOyqTT;& z{|IKtCv%H%~sJ-HnErko^hfLGX^@GQL105EuvDKq-f$)9sj6JFvtqf4B(25!@pSRTcBG7 zF|i0&E}M9d95y3oJru5JVtP=o;ZLE!Objs3Pl~*x={U6pEC^E6y{WW(ha)=7ILb=^ z<6kZOJ#2DYXN4rZDYv7i+_S4A12%s@A==6sC?jiYg{rtVib%Dtff`b^tDuBb%_^uM z8#48GBWvpm>ahK}U^-K~ngjV!V+7Xb05``Cg2G|g(`}5@>i$im?%sYmQN#T5=k*+D zwRTJ%l!qw1LEZBcuV{W0Uc`radwsm3xv)OQgo{}Ef=gc~QDSC3ruEN5t4)gN*9TV9 zTx$LF?Y{Bw4S|Q~Q7uZ=k4%;IAjtjS&-8@I5pi=peULE_d{N%&-x$rt<5|T({wr}1 z#y}J|kvBI?Zj#|EoZycEzF%YFi6?^^7IQDk{}qs_dd*(Ox!Y(q9#0C^W5huSYsF0j z)+RUUVEu83XFC&5JQ+HyDSrp7Ppo*JV#4(-$;hz3&+nrZ0mFSM=T92E&ZX`ik_*A> zrA&UfEH(-+N{)xunmyW@H=2#_R|PMdI0)gTxQW2av-vKYO zQ}k$k)XQJl1l9!HYe;Ga;Eu1TT>-&+GSjYs@(rmwRe3r^ zM}q?8dx(P&l!}`OC{1qCf%208*{hj&;>plKN%=cKd0{Q4T_!4jo5W_I@yoPtH9;&Fu`}=l9J+YCBs+xA6 zHrkB`iNfl=#6oBj6+02En(U;*>Tdvh2bg@~$+k;1FiqJ1eToUbcxagN z^LSQ#OGKG?Xqe((A4*ZbcxagN|2hvK$hcJSD?hSp5uwt}1q9^_Hewfn!{SD=Y*%hjHO@f zm=jCC;T4m!I9){x3nwl&>(P=}aqqUEdQ@9brDgbzLRhWEig48yFDIJUTu!rdh`iNP z=U?7`dA385m!~ZN5q3dzulA2>Z`RuU`9P|I9YMt6X81N3y%C7T!UfmC6>nHEk8D|> z)k@E&vxnpJZq05kYex{-JA!Nz1MSGPv0RChn zzNvewniT%JZ#n;kkaILvNy+iP=1?|Y^Qd?wo<2xl6JM0C`9q`Kc%Ud>bHg^}Ybtgk zzNX1eGCIT%_uD|ncbR9h!az@_7Z5Pdphq znkat(y+Xg3}J3Wpyd7D5~}A*pW`sT{_Y_e z8eso4#eghAGL)aYckwL|Wg;X)?%;2ws9%I+DF3JVfG?f9;MLJ{A?rRX3l(WD*qcO% z2BxPCFliG3giF}jGj&LLfPL3W|D{nn83Sa~sXgW)ymIZPrW%1Jd&y7`zTf9%*Cyu| z;SGX9*YHNA+Z0$H3g{!mA?Lvzg_ENkax$E@To~LCv*6UlI!|4C_GJo5@VG%3M3CVF zT;0^frvIkBj4PJt#gPzcD*{FDm_+dB9TVyf4*4Uz2@}uZjGd9OFS2z-Xb4m&;nXT{ zF0G3hPPk#j7z)25Ll}2P!U-|b7DJt-g%rAG)l~adqs`v7hp6l)tYC7LYz#zL;fpW^ zJ9$fegcWcV1e@S@aGj*zTx@dmS$YA9#PI0x5LHseLh`r7XVPT;(|ybQxR7}?#6ZdM zzSG);7#=p-jR%eLoh}dyq3@*FiTF+?JIP29!_2<`Qtn~$i6=w%ohbiNmS08C0|19j z>eB*;8F&*oK8d9hI7t;t;KXOrfU`^u^c70D%o#QJ(NVHq(Rzgv-fXlR-(w0mzeFsA zz)|c(fMc?g4miIDAT*hL;>pl~L-~)g{88Z0^gT6h*a&X`CpgUG1$|Y*>d+^9w|h?_~MoTO!KDzQh8E|hy#hD9^>65#D2+=g<2jDIOaOJY4nD`BBkCslx2k9BL$DFSht<1+yU?CX_ zV$}Tu*|o{}#pI5lpeA?mWjL^}(k2Z6@2%mp$h7h;4w|>-Rq0@Bcy80vj?L?73CHE> zqy{G=?wj?MpT5Fg))PpA<=GWg^SWCx56i3x@{}5Kran)~> z5q6>D@)tL#-9R|rMRRO{i*!S;N`>A0(ChMmghU8H3A z>SvfBX=X-rLNvX5h0!Bg%m|le3jqq`4e|u7eBLPE+jvlm%y>MqCf%aZ*1?;8y6xQV zY(d?b!{I(V`XGb4_@Yq$L~5C+ao;1UgyX)HQ1pr)lO1J%x}DAx%JU^C&v_>D)~ch63g%h>;MOlBp;#O?J`&^UILZ+nAJ+r73_(`MUx0;w}Vc#SJffXH>*) z^G#(0l~LTx&~%OX2Had?v)70b*j$h|%;9$g zH(CAs!VuBq=)>Vf^YrG>D7)if^c78hMTB1uTalZ#BBbGp1dzqX73Zp5Z9tt^`4(M8 zW7X>H`gz)1#;7;o8P?pPZ|rIdj_LU8BZhm)ENpYtBzC}BFL`B^mVOJtUO{X(JPCV| zoJncjw^C~z>6r*Ueg$ye`|gw-e~?`~RSwKhe&Wrq}PU4Reb)olw&mu`?j zva&ByBZCio$uB6Vm_2>z=OX^!0ao; zYFJn)+c8Z%V@A927*}KW0qT@+>?XRILlPakO?J|a z-Csiw?qwCmlR*uO9;f`g$K&ho9!w%7{qqz`ErLmupPSA2mWVPDOd>P;)fDxMU=rp3 za*F(dbIRY`ofzP_QoG#)Stg6#O)o1frIthz&S@2*`y^6R1uH-_X0I%kd+ae6v zD1XF{d@S`gJk9>7e&r!`wwCLhPH3U)c=zUT58w z>VD9zq6a(aofU3SVjg9`o=9!KbT8=ZP@gKdTzz;!53G_G#Cq2psozM9a3|=YRdj-Q zTS{Kn`jVFzivJk$dOed@I>Su)e}T(a`9Mpe*VFnyo#;r5W>dKTLP|2H7c_x2%v%HJXWM+M_+WHyz@baAa(lAhIB(E>U#$y!NQ3vO&@|6v%=JfY7R zT@&@dZp!F^i9N$J`krBYQIPn&(QdqkDG%(61cuN9Bf7hIU?w}sev44_Zvr`=Vim@d zp?hGIpCc{4{_Y+anTS`XXi0crl%JdI_?C1Jj7kL`v#d-~qtjj98x%SjPv2 z^(n1g+h{i)WXe7k367zMLv(k+y2(yDtp6#n`v|Kro(vu9lwV;zzK!mCJ80Qc?1qT! z?HF;jJrCOc4l_Mrj|Xk5NZ8{sbh5RG&C*xZn+RHpGDL4r-8m!`+THP1S%U5E_zuW0 z_D*QayXQaw-g)Mi`xgq0Wv@DqMcwdbcFSJC$bpaz;y0BF1#*zZZHNS1bnf_-#};t! zgZDT}g?mHfOk?Q-L<&Gh(bf^YB@KNalH~HuA=6?^@}P8l{s#Lrb*O z9z*Lb@my*RB#^f^Nf?6So8#oAwef}QOl*SC6?N>;aXGv zr$UmGg8$uBj!FxNMpSNSzO{I0fhmM~hJ+Ha+l;g%@%<5?TrnGLc1x3*(%vwUo1Dx| zSYuNYQ%4R>Odpz>+22{+X1)Z%lxCA&ow?5<81(9`X3=Sv9}V{4^|P8+Vd2j_o*P-D z=djy9-yRDF{5q7TegB4FSmbd^_}U`TZwz)6TTNPY&WCkDK|~4^&1ugCTRq38Jr-U& z*iBJl->rB#tTxwt#9Q0J;LGWL5U>4?Hb1Bl48jX+wEXs5`6Y9~5Zkh0`}HPj8bWv$ zJD#xAj4#`o*t!8y9Z0nuY#eU~+c*h%w0j8|hQqITO;+ceM`%S?Px}&6lV^S0)_B~X1D^J0*2`nW~d$RK3 zO)%76SfE0Cjwt^K-Zrl{{d~o_>_D8Gg5k1VkzWUb-Og1PQuOhJf+sQ!1Op`p$U*TP zS8Vo*I&$Fo_bO6`&>cmGSR(q|8f2Dduem|i?O>?oFC02{$aC>|Z}i8!(`?q8`7&1H zDoztUK%^G^cCeF6UaMt*)zS`bGk!6uSh{`Jh7G|EUaz7-l-?Wt!6=L;dcXs)=anHM zdj7o~Y_Hh0#TK>^K;k8Q-{jQV!3`9pZiX?-yY>Y;8crh*EupO;MUF&D zp$CHUq#D7^^qinKJ3fwNFV|ScGa>Tv>A4ev>?O?MT*qHvZNMOmm;Gu5jSb%aeTCKV-dM7<#EZATghWCLUxx?y_;&L9`~He0=H?` zg3V6tavKjM1zWi_f%*c8Zmr}(H~hShnT-SM1m<>wC?Ly+xkVi%XMwDFc~5!73$Bm+ zMAIJn4Hm1_U=|8{;$R`mdpv4oLH<5SrnbbgZlOf}d2H!>7($o+mj3)B>^NTfIr@{s z8spOQ=}$mET%$jKL4W=Xe_Fv1mG_q$tYKcmHCFl>D|wBTy2eUeYn5-uFvJ=tpJug} zU&*Vc&CI-Na(!9VY(#Da+jZP<=87)RN1^~1JcZ1I$j3#dr|^6bByd)puU8}#S=K#9 zqRc%Msc}fO$_iZ{sUMcU>9?V`H=1>YzhJ`#8fri9OTvu1}N^+Opj0oqB=opPGH>+sk5l$Nndo^%hGyE7UDI;~%9ei^OXWnd@Wt>) zsh;(DNYjH$dM+Z^7C5t!^a;C;MX{H#V=er?h2MAZdlieCgXh-Q*9u?1CKA$wwb>fm zW6NjGvq_4njNT&aupPF0a0&V|y6Kb&#AcE{&@?ZpJCOiv&LC|I9~;ONe+*_S*-zLE>vY*}_sj+4fim8|1@+f6 zb-ja>2xvzFHze+;AnMq-bxi7XTt>nn#fF&lfrD*qQEHZrMc&Vtf;EbT{j?D1g*Pct zNeqnHg+vC->@-W>L%~A!QqMqwXgcO_wAZm zz21X|4}badwxOe5uLaH`iLY9NSOoaCd;8WcNR_~I7Xj(@zTYCgFCO!7*h(aAi2=Uw zP$~k(=mN``wnr5?A9BT#{(c01_j`MHU+djmtau0TCc~IStnTHL z{|hhe_HD7Qp3mJ$tL+=rx6dHnN2Fuo*H^JR9^?WIi&f-n20yJEe0HK6{C*7lj(~x# zULD`Knz?cRKB=%-Y;NJrtl8_@!1N-1%o?cFjGQxj2B&g%^I9AD$BWFq@iaJVCZFZY z+9ZGrpO)*mt{M9CMSS~)iOiX4QL{YeqQ0J8P#h(M-xs5HmOgDRWdcgk&9}`%7gv`O zYkG+hY4sJ4(o?-`b)75&(+4k4>t-fNVn5ZliW4F??24fCK=ip!-=#i%cp^a=GN(b4 zvmK;tip#+4x9x+uFLd*lDj)v#e8DNtDfTae4iZ-`lL9V$7Hqh=U#RGlze{9anih( z3|nvVQFWfwB;^hq&P-2DJ>MqeXW-#fIl{WJ1WNG>qR5Yt4~lpF#yd0+#}u?bpvq;E z|Hg2BeqQ|S^?Uc*zm7qHzlhAaFMGW+i2rUcKh+->Ru8Wk7wo+NESt|z0pak)(!=ar zXBCmHj5$)|ZnJc*X65AxD__a3c3w-cbJ+@O#?Eo+YOi-jAPU#kpZZp_x_faKXAtR{ zrKkM+YaZ;spR#{EXic+!UW5Sd76gEYiU97`=Dq*iD=0bO@C}T~X`l*uoe7=-K$UPs zlGU%J1sSZcy~9mo`nVXDSS%0wi^rnhQD1Bm!SQH{d z++edRs-c9!)#hMIFBUq~!)s!KF|AkKBsMjQlh-m`ykvpJEdC{bybz17vtAq{c0QX)?AVyh`df(S25u@I(aLcu)(O?Ohg zl(~RQad-rU#r)NrdtJSvgzK0p&wPa|4fq_Nq2!Sk8#m;ib@mnfalW~J78RORjMy%k*`^4SSzW%G4W->#Ni*1KzcQ^Tlr9ozCQk8 zVz6E_YXJ5nLx_B=Db*`JimwC>5=0z#9eoMi7kwqBu82MKSRxWY2OUL9Y!BoFNkl=$!m8=vJHqHIHm(MO57N}FDv z?N{V9a0>JYmxlvjAm@b*Llo!$PN>C?W)iX3aPSNAg3mKareSt)@^f+=uIax!{;%0raEos=C zZL(#lH^x3?J+P0E858}!O=)Hsl;2I4$#m49I&Xy8^VZ=pa{o}86ZA{=#bIdPj_@zh zO{1kwA}j};E30?PzC5)mrwMz>2<^Dn*%$16JIJ1>iC(GiyPa{xn4LQ@2(i7urC|78=GTr?(LD#aECOFVS_ W!~+G0p=ot4dWJ4@X2}Yk_J09(E0#I{ diff --git a/mddocs/doctrees/changelog/0.11.2.doctree b/mddocs/doctrees/changelog/0.11.2.doctree deleted file mode 100644 index 2fecd5f96f7e901a64dcbec24c9fa9cb5946e4ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4295 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 XzaE8guG3Agxb_c0HH<&JC|>;^nYQ8e diff --git a/mddocs/doctrees/changelog/0.12.1.doctree b/mddocs/doctrees/changelog/0.12.1.doctree deleted file mode 100644 index 63be1641886da6f2056c5eb194396b124a5329e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9253 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 F{vScGLj(W- diff --git a/mddocs/doctrees/changelog/0.12.2.doctree b/mddocs/doctrees/changelog/0.12.2.doctree deleted file mode 100644 index 4d376ba2508b438cc5451fc414dae26e9504ea3a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8634 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 diff --git a/mddocs/doctrees/changelog/0.12.3.doctree b/mddocs/doctrees/changelog/0.12.3.doctree deleted file mode 100644 index ebe32e25d20cfa1720190c29c038df1e8af32c79..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4340 zcmc&%-Hsc#72Y3fccuOD?s}aXs2vDKlCBGAw7W^r!srj;1Wk)YbrBRT62KT!GnB|I zXE-G}wzf#nq(Fcyz&o>jgFa4x+yuxg^ey_4qmf4H+HHJO0Ro1HhlhvfeBa^W7p=el zYkwvD>`h%To=+l@rnyY1c6KY3Ntw~yxaaP{m+o_SY}<-F)3Q_vb#?_B3F9JFGnG$+HeD*%;S`fc+UOj%pPmme?(FDGhG&iR3>IpUg*!dJR@WJ@L`u3 zQ|O2NKF`LHW}J#tM_l%YBpuWKn`bAz={kIdmA#=TX{U?dLqpVAZ4?{EMA zlLY5%hZv*yur$c4&*M5zHF9P*6G60w5Z|qwWYyPJS{C!ckR%s~LeNzh*Gt8n zefO0ThE3$Czw^DPjR;w}8&G=ej?_EW{p|bL^%FQX`WfH?p@ zO#Vq>Uv*rSr|D!==d=w>ld&QNYdFSC1(1P#y}U2GXNX_`Qi}7`Jy{cA`2> zcV2Yp#%5nOH+wN5J2J%%WT8F|D0|Z{^zT>w;DApEVxErSPy;ppQ1>4vTPy^>c7>5qM@95FJHF6ZdA;3 zcC{Dk=0#(5;AuIW`)1q2a@-q*dO+&J9faLl*eFhq=EbR|dczd%vr{)Wa&1jqsMOxv zdKL9x4y9{uu6Cv_?X%m6eQb}6BDoeMv^2Pu#)|6dPHh=!K(6x@+o^SEiec%4zH0Z9 zQu!Mzp5AN&yHiWToi?yrni_-JgVyU{)=G_(%ni*`TxEYGXm9k4y>>&ErYMaYun$p6 z$214IjZ_YUaJL$g6&P$QDg6zK_G^O7^qt+VTtI=$FQCww4of94+BGtX{hjY;;YtfW zt4n7OM=+C1LmQJ{qZ7i7mX}#P@fFg2ZTB=9jUrBD<~v6XgFmh$RL~%aea(#Pz61MB z7fc%wghkQN3<~;`BAjcs!_ala(_zaV&^vR##T+US)UgL%sU$;>6NoyYJ5c>}iG&37pBrH7q_<* zO{7ZEaTh=iw&8%yz-}ZmAK{pY2v2K3unoIMN+aXB$gXj~Z8t5mBA!7Kfeiug8rl70_-6 zhrv@K54?lX-KknU;sS?5r}vuw!X}!kz)hNaYbjE`jYkVoRSnNq?SbbTkdu!AM8Pv| z-0$~)V)qJK#IttaA7;l(h6KKQYS=q<3kk7~ic7;}9!WI@qi%c6-~cpZbQVaDK{~b| z@4b2Q4&u3|M#++SFPWQvt2Phv5)au6sv=I!DC~iP(DyGBY}Q8a0T$sU(3=vtr4hOVO$MkArmqU?L9Pn z@CS*n_=YHl5MuR3SzrtQ@(bPTv0>2pE{wg+pH`@E%-J3lAjhMJh7-_Wr^F zGTD%(VDKe8V3Zo{aXW$5MU3W)ix`hoUw4Do2h*k+SQVqHrd`=}nm={ucE=VmhZ3Fw^t>5-G6_F4*VPTNojYR84b2}6AoPXmo)5G5>MP( zuR2UqQt5#jKtMK>Vg{0+dbA{*X}1?Fw(DKPqQeFIsKLrOFzWWQt?3M|LX?$6mcD4< z-bo}H^Y8+~#j^SSRQ?=M`1BSEI>ntW>nov^lt=MctQXG diff --git a/mddocs/doctrees/changelog/0.12.4.doctree b/mddocs/doctrees/changelog/0.12.4.doctree deleted file mode 100644 index 3e55261d7607a05829cb480b7977c739890d4810..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4559 zcmc&%TW{RP71nJft!|cNIV})7Ak-$6i;64B4$=xq(>g)ZK=3}KL4gD?s9AEBGZD#Q zIYVoO#%NL?Km^R2r+w^W(U<;<{)GT}36P)CZ-z@QcVV}6^3VkqAP>)+IhXI;KI{DD zUpsT|r*Fud@oeOiD9S`crO~UA2+Nda$~-l9KQ~Xzf$kRMQi`$&snK)b2pLbJf@bCs zK5kil#S`g|1^hsk1F5(IQDi|xhdkr7oM zDwn6d9#3OmGES37`dst|B#LS8)sv&c@i{!YlZS((q-!32r-~FYRTB{+qbl(WN+QK5 z^mKw~);sc#@AdIB0BP!N|QCiRvsDFg~4=4VIT~yodBgbrxdPvJ5KL5-=F{g zmN@0~8d0j?gHj=`Hjaxtl8BjJ2@@hEhk|@LW^kP|X zqhJ3{0Wx|mBw55GQfZ^--!|;0veYYz#P(XlUee1o$~N9CqzdvvBqIK$(Mt)>s5x8E z%Q-<-vI7FeUTZHalS(&%9@k)K^)) zHP&NC?D%W}{ENC9Gzr8O%K^}|B56320BqSJ?EoJ;$dqIXW;cP(6aT(59S3PgRqWUVk0XO7y=vh6O{-L0!wOB7o5v6%>YX1SkgLRQ>j6#yp=Re2(=R;KP+O`~NW={;4DOeVxu zH`{UJg34-K=qRC3GtTDpTFpZvyq4DK^Li^R3)_r?antVWjhf=xbziSasuU_F5WCY% zi9-}XMY9N9`u7Bro18#+S(GX-mFcth&?RD;p=K#j7`(W#L6J1a%X%S%9S$&Aju4ri z(d(58sQuXm7}mzG7V3SysYDpqamIdyDjn!tTp7JPgqTby+WPnMe;GDi4q$b|{t*_;N)-qBu%$iLxEkBNgr&9~jSAa7#hmoSV?c6z*KmYYYQbFn;am z9XjIhq9Ur46KW@-fD^9Ux+I0`JCI9B3Q8B@2&py6>c=zqEWofTBKR7`p;W`epC3w& z=iAdC16mZKK;Oxt1XTerfNh~N`bOpXmXwZ@V)UJvFEb)O*_@x7(_59?i4OJY+tVLr z4(n}e1F$CCM#wM|F4 zkThtYbSMc`@R&=Q1L z6tf3%lV(<0gpjXeA8TIE=Jk%vHy|gA0mOu-T$zX4Khaw`&4Xr>vBqpz%78$3*9Cr9 zcL9*4pI<2^GG7!iGU}G)bP9koKsP{m6vD9z`r)fb_u)#O;1B#R zC77y=-jFEYiz9?Q@&xRc4C3-jH2@*LfBuWrXAwvEA0!B3lT@HKUqd{C@*G*3aGm diff --git a/mddocs/doctrees/changelog/0.12.5.doctree b/mddocs/doctrees/changelog/0.12.5.doctree deleted file mode 100644 index 2942c17a6384bd7a20ce69538f5d5fc2359901a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8481 zcmd5>TW=gm6;9&#GLA2~H~~Sj>dlf}2jUsWNdQG?A#s*XFfq$Ii}oIUqK&APkkatV6F&nXgwXQD58#R4z!O3|@KyD7+V!4&6wqigf7UjIAV#sb|42Oa@w|hhdEX%jXeDQgnpC9a#C1bwQWMr$cY?fGvAB2 zFvBQtgYN5zJnAx^Bx^_HL}2bP3XLj6Y!OFK%bknp&m+9rTBcLCX4YLR!Iu^Hqrb$lhS^&@qg|tPy97m+M zLoho9bgne78H+2+D_55;uPiNJxtN3P=shw}fhl(+`ZUq;3Nh}`8+44e*6HQ-9Tmt7 zrKEooa9+~h30ZE^bTtDBn!^SMCcFoF(Vc+NGwWURW(?$aO!c_)Yrm0FnJHGpc% zc*Fz+?a$8xr0^PA*bDnB4%A4Rk9|f%?4GQDY&`NF0$Op$8vT^x7-VjLf;CzkE-l;k1++E4*;D3 z%npB6%~fGGEZc_#C?+$L=8}xTz?ILQ9z=!|zVJ`N$k-n~M_4|V; z^tBOC*~R^2*PN*QwS`2bLS+(ui}uP3VTIUJ;=|Do3kUup4HyM#ft0jsyu1}~(TxM2;6MOieqUUa+y?}`L=RwptTZxG6a42J9NRR!+#1vxU&-;xDMZ7&GWEvAU@2uZ= z`@Xq)`;FDLjd$+WL*w?1joUZx-+N;tJy7m$Z2a`c&mlT$v5}RsMR3Sp{T~UZLv0bq zM(rr;EgU{{sEUh9te)E3+-BH3Wan#dN<{b0);R7Nyjq4)@jFUL9J3Yo=HfMqwTfWP zg9~P;pdN*r$~Ge``uu~s_k)8gwXef&o9*`(+YP$XI}XwI8{VML!+ZA)1*t|tgIBd zvG?$qe1zLU&o92vV&F5+StTmCEu)=JF8YqBDOXspqYsqTdmxzaE8);x4dXsJf~L zf*hg!GEp!!w zu4T!tetr~PnEa&VvQq5{J zYigrQmaph`_PU(nLY(7 z(c?s(?0}6Q2vT|Z8HONeW}|E~2k7W{$!BtoW5y7gcHpHQ-gWu)bcMKS&(~Bnotcju zuwV8;HiLj!BBJL&F3LBU^N5_L7&w^O{TEUlC z9#$G~hfS19N!SRPk80DGLmL@snne2hsMEbid> z2*z;oR7C6`axiQQfgEhZa0ytK6L#Qtpe8KBoE#8zLe5#S2+VRx9$|pntP^{oSwIqo z)eEW~iG0gd4CnQV2D|cpxfF5|JApay6rhfi0V;E1R=?CJv4y$CP_k>t&YKS7s)~Xl znUs@xs37cSpbVynzK%OEoz2M14s+9a)fx2*n{a||J0^|9SKJ)9{21ny#pBvxc|ye- zkmGj&M3;F?BzKR$Cg(yDn#H6~DKn#1+7@J2p&`GTPmi$7o8e|f2wvQnIBch}CKtgD#p(HkIpxQ(f zVThTmP04e>q-PevStwxgP$A@rJkB5c!Wx*Ab*YO#;PA6Wd+3P2OjMpX(V*D{Apo9qQaoPq|-z1*JTodzYm3Ot&C$f1Ou1dI4!uf;q3Rp#sCL{mS}lutC(lbF7Q z0jFj_Z|Se;yZTdoy2f(0iUGa*tj&NV6%3FlT~VQ))1~THS5i7mOo17%9k>OoH0eTZ zv7l}Yactzf>W@wi@kiE8EQ zthRQTf113F;6iVvr5 F{4Wf=8v+0T diff --git a/mddocs/doctrees/changelog/0.13.0.doctree b/mddocs/doctrees/changelog/0.13.0.doctree deleted file mode 100644 index 81f90529319e6e6d8102350196fc229fe8d72c98..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53222 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 diff --git a/mddocs/doctrees/changelog/0.13.3.doctree b/mddocs/doctrees/changelog/0.13.3.doctree deleted file mode 100644 index b39b5f90aa2e45cb57e0b82a0f6cd3eb7340d3bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4189 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 diff --git a/mddocs/doctrees/changelog/0.7.1.doctree b/mddocs/doctrees/changelog/0.7.1.doctree deleted file mode 100644 index 0c5545a127066cacb3c18e4414048944c7ebdb14..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8224 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 diff --git a/mddocs/doctrees/changelog/0.8.0.doctree b/mddocs/doctrees/changelog/0.8.0.doctree deleted file mode 100644 index 43e1a0abf7a45eb11f6991e689966d159fbec9d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43955 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 diff --git a/mddocs/doctrees/changelog/0.8.1.doctree b/mddocs/doctrees/changelog/0.8.1.doctree deleted file mode 100644 index cbc63ea52da01874c5133c91818698b2ff65e519..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15137 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 JRfuYp{{V8w&(;6{ diff --git a/mddocs/doctrees/changelog/0.9.3.doctree b/mddocs/doctrees/changelog/0.9.3.doctree deleted file mode 100644 index ad5f9d18934483a520d2b242b38fda106771f278..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3725 zcmc&%ZEGb(7M>(?liZu+P7*UJI3pL)*>T}^XJ*!29mHi-d?A<*B7(xu)a|a@RiwMC zw_av$Kw(!94AeJk#4mz>Xn(=}8K0`|zHf}X>wYnCq3hJCQ|IM*PM!Rt_w<|LweV+e zsG9L=7LhEgL`IdfyO~H$Nh|HXcd!4`eeLerz9dhSFfygiUV}!;c#%n3xkuO>dVS3c z6}1LlP-dbu*AR*-$>@|<+<(sPv7Y@Qibh{4Q`bW3gb8t>z8dk85964Z zd88PpMW!My;t9!e8oze`bljf9XE-^Y6ow8)_qwq5rqE&nv z-Fpzd5r^Y3uQED|m@bQfV>^!=>my(~r3erKcS(TL2P+PDy1n4{vfekpey6hnL^F%PFfJ9AjEgCB4zWno0)pCn2_`WSqM5?q|lC?*exTuvv2%g1#)(f zk}BgFX}qx;e|7AS%Ge!Ea(}I3&)e+|W&3YSrIT8ULgeo_d%fTlb!VG)yCx_LoH;fV z!fqib_A)9q!tWLQUd8VYg4%}X!PmcZzWzoeIKv`#$nLZKXWnx{x~sh2fW_>T-8jGQh#K-J!Y>UO`?B4k^-EY(6eg6>Z?>Fi;1|dosE1-tRSeJDivKD*d z#3>_HPEih!2ku3vkMl7j!uOgs!FpX(A9VuTf7j%r5BLlS3)DJamMk28^xGW%vQcMW zz=2*!@!rG~1=UHx;nsq(jYFQ$ek0+UmP@<#1$Z{MiYb9)}^}-w2@qrnk}7T zx{c&lPcPz{{qJ!-28et-J1B_!Z&j<8luLide>z~do8&mVF%G!CSUMe2X(n{eCEK5v zd^`;$BOS4VaMTHx;PdVRK3z4IOrhW0a|R+5%h)Yn?A0^XAQhePK_u zB$X;c34PbdB~?w!>uI4$;m)qvL8n7AER6^Mh8?Cx`i7FUjpy9%caqR@&h4(ETJtK$ z%Xs9W5?;Eg6qTk`hEDT2!Rw>u_^{i;=-Oy^&OQX#b6QbJw2%&q(E2nfYb^Gp5Wd0T zb)2Cvv9o)P3xIHS0fj-kG_t_zK#Mf-E!pqFl^%SSm*@jin5l%OOPk-}p+du{OqtB4 zLYD43J5+cF>LeAVf7lfazSKx)L4zcD!Zk~}X94%M2ikW6X^}L%)dhV_kbjFcIO|(%9X)mKd6RsORA+kaip@G-9grtvhq|}Gx{o@sKmf*n^8MK`38$BJr zIaVCsw^lb3Dy5JK+EG?ei3)gEQ|;`HCi6>Hdclgbx7T*oOt631VPkaRKzH=k>c-kh zd*}lI@DxLS3IycrL4ihTFv=j^8P=Palk9Vnrb1?zhHAhD*KlCw+-|3$nj%J|gJ&HY z*tQ)KqeZe1*-Z|t9b~4glQ|xd)ga)2qO3^a4c-WnfOKE_g{W54WFncB9!-aIRtrUw z<&yy-sZJhq#T&fdNHVU-r0{{XUhZ@}0q6D+492`FkPb!VJ~{k@9oDo?=A)PoX5ULD1ipJ-*cZF! z9by&Lmzs$x5;6y!ZhFn&05lUcew0U}9J`Q@UpxI1@;Q{#bj`e%EX?2O%!9eaN_E23`n#>Z$b5`I7@Ln&3 z$Nj!42`1vd8&FyyQO|{SvrGz+k~X!s@f^b+6uu!FvK&%~(-)@36@DrYlSSJDu7Dnq z@@fhUv`yjIcL~huYieP5#a*Ofq946393WFpXa@RT!UIOB#vM0PXk8?J z!Rw=T-~m>PY17cI>=vyaJIo_?w`nEdE|7@fn}ZuVX-4;eo1^uAAbcxlCv0EY}CJ` z6Xv6*>`P-0mMa=;9i$w%z}VnX)JQyWJG~~DWl5t4Y5)P{L=XUAaj5PduhPO~V7AD8Ll|Vf3$_iT8C#-X0OeAhKmY&$ diff --git a/mddocs/doctrees/changelog/0.9.4.doctree b/mddocs/doctrees/changelog/0.9.4.doctree deleted file mode 100644 index d0b203bac57214ae692e7f6dc6aa4a011810ba65..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14841 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 diff --git a/mddocs/doctrees/changelog/DRAFT.doctree b/mddocs/doctrees/changelog/DRAFT.doctree deleted file mode 100644 index 61303865a885c9ac9794f699c0216cf8d87c297f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2914 zcmc&$TWcIQ6prnz?Oop;CoMDyrZnjVJmaJdEupl8mO?T0Ln-Z35ZW2-N?0RJB~7x1 z1lmF&0lh`-@9ihe%-UY^(!O+HKsq`)*YBL8-v)pEvT-eae#h2as98qKQt6V};MYrC zG!;`Wya^xw9$toz{Ls+nRyU?#!CwPM!G$ahQ{idw+mYP~X|wMBJGL2HCme`ck&;b> z5^*^f#|QrJEIWK*o4VG<5w7)_eQ_u%I%SU^A9Ckv`#8@+=%JO>; zkB_>0Sf-mtW7)9LA->0snkjP^BD7*oW`@zyaRxh+Ag1!;?7^qmqdZ;Dg(}%BQQDZYwL(&B zLBx8XXw|OP%cj2AjA?O(C?uX0DqY|Qex)%Y_(%UKLBWp-s!CDP)*8R`b>N@a#;-X# zjcNnG^Eb|Z^J2m7a2Rm|33Z?@ZX11 ztFS!T`gw2bzeGZ4c*b}6L%#V|JqNT`75PRy=g0iP$ujs?{E*D+hp)W#_T0+e+Z;%& zSZenrEPN*q5!QQ;ICq^sBREg03Oi^xjj(5MR z8&@|jocaNZUNS6(Q>K`qPMf$%8F#d*vDq+M$GoB`%z|IagWqTkA<%vXhEca|jKprs z>4HQg##0zHfaT^q`27jQDDBv#!&j(JRyfsEWH!;J3a|XO1^jDL=qk1|3xV&o6ed}s zkmtg2(pLlYbr-twKyVC8ElRRy3_)G?BaT`i-FD#j*i0anj=IKPPzNO&H&dwFeol=- zEWv>$RDm)?QVJGbiuMVG$Px4zU4kq4VdEx8UmRJ1_3f*Ngc+j^0T@cj4Dt|7EdYg` zHvX5S43d=K?<|}wh~Ua^w?Z!r=&#S_A_xG| zC@Q1CokU4^kEy>P)s{7-CbKG{X_w7vZ5g?IGeM+q#K!- z?>FHLrkhHo2ccUX8<_~%_R(8$1tpf5RGv&lX(jT#fia3awcC>=zZXjmuu)T>P>M>p z@Wbwhe!FHhnNOhT%x0vFDQu5*;ot4M8pt-Q&mGq)(`Jftx*0i>0pKK#RY;FRI@Uoy zx_A5;;<;@m#e(=qxe$N97mv!N5wB;=WP-U#(t)0k$6*O&Lr2FbAq>}WvhMEykQJF% z=G*~d>xhuoe%GEWM`whKDVO*G-s@6F+;7^7;vw*BgOaug)Qz-nw?yiKcA>q4W(a$b z__o*(wxfg}NT_8tBo5P!nLFYYO|ZOW=vJz?NnuoDR?E;B- za|qOiBkhYNP1(K0cT;TW{D5y-zklVZ9nvpR84C}R0~@tW=I3z?#*q)!wMgvN!?5PmO@L}CIpjX?VFDN20s58enc7IDS?8*7Q_<|ahvB`D)fIo>tKl=xV4}aqT diff --git a/mddocs/doctrees/changelog/NEXT_RELEASE.doctree b/mddocs/doctrees/changelog/NEXT_RELEASE.doctree deleted file mode 100644 index d9b7ad2db3a70fc65f3f4e05cbec5504473d2ca6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3107 zcmc&$-)kH<5RUDf?K}T=oHWoRn9`&_z_(7?w8fN`w9S)SKcpe_B?#TF?j)?0wvyI4 zLjr9ekbvHz_V4W{?cUiwN=p0EgM-y*G&7p{zR~>F`Q!TPrS$U~w&FsK`?M&OE|?8| zxzKrCGUdYC@X7DtMfliv4Si;HZE_a;C1~Va$igrco?>%7>N_EAzcuiltq0Z#2ccG^ zU_+rq{G8h39sg&)zx&+QRi%w1Tlo#10O2xpS30$TCrm`j!hO3)>eu8_;6J zvfKCe_u6y#OegmSvSz(q{0=L}5`g zzm!XAErj^)C|WjO%SBzCEe13{Mi!FJ0+lZC9luZ;5&ZrCRG{E{IaP%yXcLW}`!?`T zZS9vF9Ywc+U-OF-l&yYktRt1tQjfj~{<0K`g~NHjSW%P(!EDz9%5UIO{1z& z9@vUT9xqphS!PaHy6Sf^bp8PU%zpu1lq|CV4>g%9t1Klii~PxQPSuE^D%sJ)UmlZ3 z4}X04;OB>ZR4X6@bM=6|qbhB6`4hk63{_Ur)FM#k5rSzj+Hu-Z84fS`-b9BLSk^Jq zbACOqO+)}`Z5{d5i6mj<$S+&woKPdQvaM-r8XBz}Qw5;$grZ&8DPR4fu3c5TaO68^ zT9Va>ZAgjh$*3X2t^b;Ko_+Bo>REclfs5CZSVQ0TSG+DNR{oX$yPQrtyA z9Yk(UgWn#)Osx00&DZEJEO)9d$#|$u8D9H!3;I^1g^z{7_ZtZi69Hw(GvPRyuo?c^ z11-Ig9K%wJmF$Eesq?SzQ?Nljg1hx+)F|W<8fc;=BwZ9G zXYoogKB15~fb7c>>4i+(p`Wb^LXC;6~l6E3Or&J zzymh|$T{+hxmH8m5$O=c1Py%AuhZITawhWg0*G5H>arqJR3fWMAOJ|CsEh_Tk|g0h zmi|msSymUCfVa@vWaCO(M$Vs15Xl`m5mq#K-5`aqbRgqJ+9;PMYytCDaT&bMDp3zc z*IG3)6tZ~-uEj4nL3c>>WGY%KQ12DY0jN{6Je>1ek#c~I8Uck;l){CdHb3&~6|2bf z1&x0*gSj-~^_ZEyU~ZUvpeJPUv4^v+qXSR~!$CM)Mvpb3BvWQiFF=}(`NnVB zQ|0KGa53T%e}MP0)DicqwnWz^zt*vZBT-k*vbbK0i%CfXduUCs@=U{u{-ga4sBns-#qMK&DB|3 z?G9c%w2oZYu`%Fb$+eVCLGW@cec#I8;?A$aBhUb&5D10g&7x~H4yPUDf_P;rPQ_ah ZDwlNu;_6YH0DDDja&13|GeAA){{?_I$n*dJ diff --git a/mddocs/doctrees/changelog/index.doctree b/mddocs/doctrees/changelog/index.doctree deleted file mode 100644 index 4b35b3059f3639bae80e04f1ce34d964c673b4e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3923 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

op=imzjvcdU1c(05FzGep6TT`5iTd?&t+ywLfQe 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 diff --git a/mddocs/doctrees/connection/db_connection/clickhouse/execute.doctree b/mddocs/doctrees/connection/db_connection/clickhouse/execute.doctree deleted file mode 100644 index c150482563c321d8f45da611ef88bd90ae8ee211..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47822 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 diff --git a/mddocs/doctrees/connection/db_connection/clickhouse/index.doctree b/mddocs/doctrees/connection/db_connection/clickhouse/index.doctree deleted file mode 100644 index 7ad5ac4ae3f4dac68746892a4b02fee6f93c7c44..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5083 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 Pr33IEg{#Hak+S~}P;(m4 diff --git a/mddocs/doctrees/connection/db_connection/clickhouse/sql.doctree b/mddocs/doctrees/connection/db_connection/clickhouse/sql.doctree deleted file mode 100644 index a8dabd56b8c6dfa2b10ea99a3ae3d3309218a82d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49343 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 z zlav>0Fqo6!Wf%~bCI(_{LLe?M(m|mf!JXd%wr-~nd!YiJVj2XZ{sH6 zWCF7T6B56cCvDU{N5 zi;{n_%$Km8M6kNhC1MKx<+cmUoc~?6#Na)MxQm)%QpHnkwz;xEXXD)DBuoA zeLRT*32_%QLMHMrks?t)`A=pMJR<+hOrJ#LsMcM!REZnVx-0N)B2kw>iMSyFwr;DL zex^iCMT)vEv8Y{U);+D-VW#(r`jnV_&{C?yexy8ye%_K8shiA_E2JI-r8sD&mq^_} zq|qk70>%!d8tf$rv-#sFk+AtCoRS&+6p|#egqm3hkI-c^eG;Li(Fe8fYqh+>=soRw ze_~PZHM8y!^;Ks2nG*Fwk)l4DSky<%tb0U#pPAk(>Z}?)tP#c2@EXTQKh!X_AO+L> z#}i}peY4C8qldxVziXzK7=4>aqs<-G|AGj3&tsUq&snDIeW{0(i9HW#5*hq|n8omj z{eKPg<WD9QI*U>U-wl5^{G9bQ}y?eYe=@WwIA)*&8mlyC|{D7eI-)-90w-xd!^# zk$Fdi%)1iHe6>LaXev)TZ!^$G%RD`Q5N#B@Hl=$J2oW|kn9^(UT_X4KYO^Ftt=1fC_%0DjA2o}jQ2I+i=}|MiMCpS>8m$a)zW-jhZ6d5= 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 diff --git a/mddocs/doctrees/connection/db_connection/clickhouse/write.doctree b/mddocs/doctrees/connection/db_connection/clickhouse/write.doctree deleted file mode 100644 index e746c5298fbb4bbf6e31f7a1366cc0eaa3433dde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58118 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űkaaIm>MXu(I!jaV>#GuhgFIITr12wQVj6K3Z^ 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 E2ce0N#sB~S diff --git a/mddocs/doctrees/connection/db_connection/greenplum/index.doctree b/mddocs/doctrees/connection/db_connection/greenplum/index.doctree deleted file mode 100644 index 9a10a11691a99806f7ffd5d6f293a40c2bdb95de..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5016 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 kB^s3_K+RmPHFxFUa=B}v{EXYWQvd(} diff --git a/mddocs/doctrees/connection/db_connection/greenplum/prerequisites.doctree b/mddocs/doctrees/connection/db_connection/greenplum/prerequisites.doctree deleted file mode 100644 index aff2a9cf055f9602d9f5d358518bf49c9e4cd935..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 75612 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 diff --git a/mddocs/doctrees/connection/db_connection/greenplum/read.doctree b/mddocs/doctrees/connection/db_connection/greenplum/read.doctree deleted file mode 100644 index 31dd9ba4d46959ba8246a9134cba302b16b645b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 83259 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 R8QJ4uiz2m6ep|C#`2Qr5twR6+ diff --git a/mddocs/doctrees/connection/db_connection/hive/connection.doctree b/mddocs/doctrees/connection/db_connection/hive/connection.doctree deleted file mode 100644 index 9cc9a702b384839f224637c9bf2c627257dc8e46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26332 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 X>ouVX{T2G@l2VhbhE%X3Tv7R720RvH diff --git a/mddocs/doctrees/connection/db_connection/hive/execute.doctree b/mddocs/doctrees/connection/db_connection/hive/execute.doctree deleted file mode 100644 index dda6f55d6030edd890bfe9026dbb5ad8d4e30177..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15215 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 diff --git a/mddocs/doctrees/connection/db_connection/hive/index.doctree b/mddocs/doctrees/connection/db_connection/hive/index.doctree deleted file mode 100644 index daa534dc26eca60280018329a6f2c8f9e4b94db3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4974 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 G=KlkWS+4Q` diff --git a/mddocs/doctrees/connection/db_connection/hive/read.doctree b/mddocs/doctrees/connection/db_connection/hive/read.doctree deleted file mode 100644 index 77b64d5704038aca9f18d823456f836674d38bf2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23064 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 diff --git a/mddocs/doctrees/connection/db_connection/hive/slots.doctree b/mddocs/doctrees/connection/db_connection/hive/slots.doctree deleted file mode 100644 index 0419934b02558079dc91d34b2876d3fe9be7eb0b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26608 zcmdU2Ymgk(b=FGS*X(LnFF;_Vw1h#sLOUxhP{5Wf9wIi5l7*OrvJ19Gy)(Vr-RkKX zx_cxo1_CaNpycLJ#bAd}F5=2N@sGs$LFJ{y<5m<)0nLRr$_+ zbl>jj>DgIUAhv3Er@QatyzjZ^+&=T<;B((u*~EX?WZ1MF|6JLu)%-@y3fpX?)~L4X zmLIjBZBIYde!M-!Mg#L~*k}b+tIal{Mb&n^T44F@MLe9K_L1X-WjVlAVQVRjoCr4? zex+tDJHA67f&RTai}|EVCBb7U1LqS^n&i z^3j{hx6JbHvyNZ0&Xw(`?v>j1@M7B@1;W;{1q6YxA_9QkmI7F{9prH9?LP7DW5R)B zB{PZwXQ>r|uf)eKPAvq_*l^V|!w@&|dC)iO;?qd2)l4py%<5@Sfw$RWdQscn&9<}x zr_F9z7lYcYR5ksYQ!@oLY~YTzeJE_P;mBN}Y1{UdY)DDj_^}|2D$StbHCB$d*;dc- zt@g>yY^Z61EdXX`Yss{)1yc4taLmB(0sOunzc&D>AwbSyy;Z^5rzEsncG=!;&)MU- z^XxX&RMf6y&)P@qqbE0`{T4P_i51A+(prKv4GFQ-MPw54U$LDt)=cO%qHtaaQ&)Tj zfsa!I36m}y!n-l+AkAvuh2L3r0wP_vW<&7Z3}&NxbC%;hOQJtpZTP-bB~6^IEmgED z8g!P!Q3lo99OmIrG}VB%he$i7fY2bc+e@iN>0XRsJV3Z8z1aFw);Y2vOn-L1%`FN* z{-^+X!9Kc>OJd1NqVI!f{(xw1-w%wp0>Vvfkn3#+eBFg&7=jZxT-K)`X^y`F9N2M! zoC9u5akVfU-w@K0kf@v@SeXb`<^-E=vzyl3xExQ_wucs?wn@m@337rKKy51wakT@p zDLk2=Cza5F#zd_k#e%VT^r>mi18P6X+nc`Mh|H`Fo_pxnbrEB2&GQuzGBcyek4V-P8 zx`Gqoei4Lwy0v6=XZYy3x;M}9z?#(ztk4S1SX^&DiM~&Y`Pe7B=#0>uL=dg z6b!=UuNaNxwM&mK5JyiAaIqd29K&)Yk}%@;x-(*mjY0NiKuZoC z+eW~0QQ+5>&LjIb?cX9k{Z2w_`?sOJ0?kp5W){cMScVk}+MNW#KP!T8gdh~)jnE|B zA^Vd~$TVp>YtpiUMHAz}h|#ExB^Oq)okEih)f=@IS(52BGYG4$&>wqp)388w3Ar;NxpBcs}Ac{O9nf```hEOHGkT(07w6Vzr1 z+Et_JwN@NIEK3WqiH$cctoHs&*A&2KifLbL$Y5 zM4?~PIjHGi#;+qry<*S0+XgmmlK$4qu?9?@v4RlJ4b;(DcC4B%b64F}t3hV5vdc!s zk@BtOBSKp3SH(659)5AV|5{_a#qQ(8D7yy;2ky1yxj<9y9z17**p>m4JZW|Jp+TX* zjz@v%hn^XkOE6Ct1o7tFYvPF&9d{KDvsK9LxTKtbhz-k2m@-aEl1&b?z)20g6$0`y z1Y{I@jz>s|2~NrPp=8GX{b~UyY$M2N&$;&isjy&2AQvO| zZbBX)2`JeMm&4kReREJZJ?BxYQuSJ4WChaPi?z~GQYTD#cXvnkZ|xR)IDgowX=7^5 z3svHv7$G^3Rikc2cB2Nj_s7;TNLNf#-mqkj^oAczqd#T;jJm;&#oj`ff#J||dd>s- zVA_~DPXCR^j0M0h7pn8|Xg4M9u)wt+e|SiANfss7j@3*e@=Nhd`3|t#%=Ae6!1fDy z9x$J#$O%?n7(8CL7Ekb=5d1%l33g?^C@T}u?Qvpk&&QbO8e98s>@TNmtfCq`M2%|h zCutbv!S3W9EIC!s2`lg%Y@HKtg^la7Cm>bZx42Jnd)mKo2b=bL!X#+pBZk9eTM;3? zPO$!LA=Yfb@7PqiFH)B|_XYgpZsl%{B+cJ!`PHb^v56dzp$ly0<~8a6e2=M{om2lo zYU-Za@`b_~x@e2Q%l$R#00I z`|Bwmk59q;q?S2k7vsNO_ z((b;MS|Oh&9OvBUa>mc@?%37!mKQk{a@1+lA`}6a&;)Wf+-JpDiQP$Skl5-579tsb zsOv(h!%X*N&A?`W-PN7aA=f=~_fg0CnBIp5xk!QDT^97x zn5Tc86M*+tp&x*<-4>3dw_D*1pcsSw75neg>(4**6HTzcZeO&&!Hiz+P}}~ddkQpV zT2O^`TReZZZGVd$=rtrljbxD!lZaItn43fZ5t;ZWC8a75k!-f^Sx|njA=fg4!W>4E zauc*!iy9my#28ELb3NYE)Ibd~mb(7pyJU)j|W zx2Npg_OyM-zVY$4JwrJTzE`Cj!JPYuoP})nAlgxup4oF@Zn{HXXOlJiu{9-Ayx;&^j8sW@>dS0Ma2mX58)D$6|)-2 z?ffFUBQ?rvPd?x8IEfAV`HWO#oN~_cQ37^+r-_ZkJfZ^YN^TY{NyvxW@qiOGhHyJLz725JIwI zNV0;*Axel1c{SmrB<{4bPRMQX=bAT?Jd#~`EH>%NO7|<*N9Mj3Q@0XX`nwq0yj_S+ zFVNNfGPRy_zeN9L6?w=jA7v7drABR41C-4jIwTG0p+m;JF^`3B{uC_dDV`iZA@&Qx z#!exlKV2%t32)?yRGL1YG?*28fmEA{6IpeA{&Ls(yU$JO#I4vqr3Tv5Z6Imw5grCw z4Sz+SeN5}5{Gpsp(f{?xg;6?gai7DQX-ncLzy3wat*b4_yS`@KS?m%8T0H@qHb#Su#f7mhoR>HLNeK9Rh*QfU8l((jH*U)aS>8(LzRkgMWeX+ulW z_yh;P7}K<&rCyJ&p_eqYWUl%%w3C=1F|@@9U4#mwx!Tv!u9ePq!ND5}T1x=}o0`)+ z@}uWWI!zF}%*l;JY2F09YhEJV*QvEWzj6i)4#YO;C{>ocM)kA~%O5WXje5ta9XjoR z)2TcJQPNt7?Wuf77p2mj6ecP2V+ma{QRO9^Rx91fo5Qa#d=>ggrTlX9Rr7ptkQQG0 z_hqkf)(Ye~FUb3Q7z4p=@a=!WzM>Q^c4iGafqa2Y*v^XW;Ww(_ye}PXrQkIi@l1cE zg)_uB|Jg*i04I*g#rqzKMeGLc*CYg%{{$RJW%)NS9ldR9F)k|DF4EDO$|#XN+(S+6 zZvzh%W>cD$R?jKv%8QI`+LdQR@@R_ten85G;p_7(zk5Ah@?%r_wcnT#hb`fn`wjJR zLfxLBgO#gb%(#50u6Lk?JdzZ~jVAqux~JOFNHkw)MG=BL;;h0d*L=#=D?_s0O!!A} zCcJXmZ=Cg&wuKv&1^?iBvf!QIWX^-GIRkz)iO+Tp3LGipZAA~kyIXvR(%$z%fOM1H zI}LJwrblM|%TI&QG({?+61bH1z9*i5efqLE@6pMsw#+IgDes+mjl`Bd?@_X5S~0eH zL8hs~RChutw?@P8RJYsKZRBRP=yVNQi({WE_m@B9|LH?c=8I@&erO9CYJS~Qg_!Ca z=u`lrN6HUV^q(1e9H3w|NoVCAQoilLLmJf26+&H@l@qhrd4x%&seiq2iuDo(Q*01n z5SS8@Jg}${=A}ZY*Fu<{8P_irPSGV@nakV!jO$km$KMbXUM-xW5QQAU$?m$|Djr?S zb`+?TyMT=VfBtq8u7dPww?-FV-XUcgiIh2@1PrVf}`221i!$G9OM{8)qHQqj+W03b043TeUb7`^?6J zlb_D*2~gr8&1YmZ<8W$5PD+A%JM|DL-Gu0ueM{RCSjs<~uD{SC$1=9P5&3{H#v{*<)>k14GRS0YlRC{R#K|Shx+FdGa!X) z0>bqS`h$JNnStpwlSO&Fee_6h`Ak2!@Bp?%grc{<$`R34h+EdhAdUcK2@dzuUGofn zKZ8V{`G7ylbT2g8DossucIE@JkR%>rPU2oCCq>%@ISad}e1OITAx*`-WIjNX!Sfva zVoYT|KEo{&ns$_d6nf;xJUk5^>Y$tKEtfLbwC7|(qJ0ogd_nuPu3 zo-nzQD8O5CMm0n+Nn{=%nOE84y-?`y>yv+83+pE@^{q4T-|4q!gZ`Z@WQZ;bc!(|) z>HqkFz^~2J&e{hK8~h_)l&T#-s9S8E-;1CNhC+gV2!6huP`^8dy-!|{UrOmd%%Rv1 zo?oIM|Kw0H7HW4YgZv7h#6Otsp?f^YueNzg_R~hBD9G*MO|DB`1u6hyK+QU z+2P!0$;!1DBb>L3D2~P_#Bv{|VKyKdpFdbg1s-2dN&kgnkn<&744#Sd=SPBHC>*~q61-Lvez$OnE|Fk4cxMVB8wq}?cyujD zQoz%Viv$a}(`%nDSZb}6k>Ff8PkOtz0=tcj1n(_0{1giQ24+l!f^!WAj|Klo;b2-Y zSXl*lJGV=A6$gW-KV+erhgf@6^p{5(x<`Po>E1=#+`W>KmP_;Q%d@>Amm#` zeLSk@((t`igYSq?EYAq(RV;PfXoCX66h9bIfNJ8ic{t;##P*486DQs!K77?pMGo9? z>MQm{gB)pX+=eU|>kO!w}FhSW=* zrKdSNW9L~&)@;uyb}q=bF?#!oe@bRL0J=UiCHflCg6#KRw(@ z6+4#`Q0)9;=+#H;T&;jA__vPO`M2eaYKUTz$k=%@uUc3-V&^YybnIO2OH@oSFL<75 z5(m^o;QTrxMDbe#RivG&VWWJresDgHrYke@o`pY-qQ3#oUXm!flvj$PD?zw)QS=}5 zz(VcpWfWZjl&t)RdT1e!qO1L}(t@-(=^aJiIijNIyXX^-qVrF0FN&T9Bje~0;G)83 zA=I*zXzIOu5&Ab`p!df>^H1vn`hiX;rtJHurub33Tp%koPKgrL1{JPEEqT3OwWXKL z{z;%Fi&l%0&g^QG!^8o#3KhoWmDg*PrBzY&>hZRFBM`9f3E5S-txy^piLv|8phyf-kOY`3b7h~rCCW_$(Jfkln@WTo;26#A0M z3|_BvfsM$w7=Zo`lrj{h@nI~fGAl}u4n1h1TzyjRhz&JbQL`1b7wiWY*yxJo<85wK zI%(sA*GiA@o)j*|f<}WXqT?O>b8R*-i^I4A1S-FD8VySFvK4r^+D27lsfIf}1t^2! zT?@8z8N=Z9QcHXI3@Y1$^eDbqIY;NWP=#~?4yw~ck#JtKF~orH6L&0805+_gbs`(K zTGWxpe6mA)V?3~UaevP`V}YETAq%J?Uy+xCY=?Eui7He!uN5YD2W!?+YX#FyP&rg8 zAB%AZ(wxZ>k+h%yJ-fPnjD^?IRf{Kmm^B!KY z=d}ZzDvo(i3d(Dv4DwOhY;WdCW(fCTh$EX2<0deu-q)rdWID6$gahhwhBip#0jD#3Bj8@*60aVgJ7 z_KXucLS!c^H7BHcqf}pBKsj8Issgne2Qv6-2ox`e-eJ&fvRP%>p;rM>x&KP~gh^1V zo7#XkG+GTTA4AEC`10fc+ri6@d6vHdLG+wD3hDmD_JeGqX*ClgLdc*JaTC%G*hw|m z9`%&~xE+?oi(=(QumX*`f?9F_(6WN+7GO^Vc8uWu%=JfZ20bT&g zn)F^BpyQSOr5U&Y>1k9Qe;G0$?ZQqn6tJ?HX8a2;FqbKmk-o0sbQ>a5U$Sb@;8j3i zTX?hL0=uG$R;Mc#3L|?J)RHB;%c0&OziMaYKc7biN{4;I5L&`>kHG%p<67d3> z1Ko%!;(R%mn;qFN9pSG9?zYE6wlnpJs<+Uyj&JkW9nvhz@pJkP3HrVaT3PVM!aZh) zm*3D?+?x_h(tYkBT3Qd{Pm7JxWBwi+I*A6)@aIUO@53FeP!I5?_%?&H_LIB-7%wl$ zPp0$tlz3*srSwIMZPTSefQ)LI5t0{~3 zIAG%yUh!od2Ph~`6&PYA#Chh}>;7v%S>$z-oA3%e=P!~-6jUT;gK{YxK>ZtD8GP%9 z7e06&El=W`c3>{$*Paa0w)_iXJT?}Sfh)t9<0D94v!Hw{WIs!?dONqTVy9XDe`t6C AhX4Qo diff --git a/mddocs/doctrees/connection/db_connection/hive/sql.doctree b/mddocs/doctrees/connection/db_connection/hive/sql.doctree deleted file mode 100644 index a5a78bb759bb1fa3b7fa981682c8c88434aa8db2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23281 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 diff --git a/mddocs/doctrees/connection/db_connection/kafka/basic_auth.doctree b/mddocs/doctrees/connection/db_connection/kafka/basic_auth.doctree deleted file mode 100644 index 30fb1798537fb5575e831963b7b4eabc96a92ff5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23699 zcmdU1TaX;rS=LHBd(&!lw_;m1ZN-jOF7K?gK`~iNO35}(gp!r894EnST0Jwp+uhr9 z$#jpj8_Oa#m{@S0P#YXDMI}%PI0Xq5xdAT(Fcsi|1QDPrqzZ}(;qt%(rU*}b|GD%z zJ=@beyI$Frsx;eu`dt3=-_L)}`9D#5@r9q7lzKd;1ov0S#UM4eWv*^X_m*<6aQSbp7EbZ2KRFOFN$?DVu>Uo1zS?}oLg z>^G+?PHoYhzHjE}lx&Ab-g2rEcHA)w|7K&Sz39fei)O=(!*bhoYO&`6PTrB*m_AxQ z_RjM0Y2JL=Z`9o7vKQCGvBVpmPrOk~*j;cjK}=X70YLA_0IcekINWBv=idC8aNyXO z6US}8(uqM=qT@Ed7J+7LxEeZ9ghD)*8ctn2jnq0V{ZetNOTYrJv&{+P#M{TVb=rQy zj&F!S2^*_AjhbI`1T<{mJ&AXJ)M3N1vq-}x-fe71aoPBZb`;yKb~9`)o=VtY=r`Qt z>{d3^azGXUv!_#Wyn~pOcQ+_z;r}80zZd`Cfk_Pkat`Z*3f6UULelZd-Y##(8_ykQ zpQDDNdSl+Schoy}b}Q;{W21H|K;E`a1>7_w*wSQ?{osFl$yr!(rYcV4SErm#?9B?6 z>N%?tX64rswyV1#hxTDWP$CGt)A&El&Vaq^?sNnmaN5&xy*15oFim1GU2QfRZk42R zx>m7`H{La54KUdRQyFwf_$-Da(ZHC7H$?I>i7A#K?_ow*cAll#Sr1cBr_qrTm%%68 zWl}9{RfXphu5B=d4~r?xdB^5*(J#qNxPKDWFNo^id0=1=ux?=`F4LDG-8J$2&k zF_1KC9d51EoJQP= z&k%8cLlemRbqJuC{wSY**WA6U+`NK0yRfE#`)UzzBLtj)Y=rvn4b+!c1Esl-J-RUx zbOG&<)qza~P_S(TTC);BMJ5?Eu_1_V2da@B-_WQq(3}8?H^fF3I$_8s?OkPKcHFGm zFo$uJc;ED{{%iuvLq)Oz(dOeYOq?b_$7RB-!J1(7*mUx|lGf&<=+;?mJ1x)T+?hv& z(XisC#c^SEB9fhp7b7PM%XPQvISoImpFDK_{in~&9lCh2oT{S(J=z*t<+~>7VN!-e zKX%(rXo7G@@3EAC+?EfFG0^wTfwJn$WN<>@_kg(X0%=fyWuu1-sG705-4N>GJFvvJ zhTbCY&vPid@&aDCqyFTq$r9W}Q&J))HC%vIG0% zx#ebikz5YGqTuzW-wsCt&RM)-6RGT0==hCO7j4Zf`*X+C_zwmKwrnwp=_&a@4%M!Uf3&0B0xsIlA*wfFka>%ge;U^%kD!jGgVI`#S}C6J+dn!X> zxB${4m!GZf&)trCa0hTl^71enOV?htcFAo= zaD$G4R#qa`(o6NJG-&0c= zyr+=cCWCk4J1b#bk4a@r63`r+q{cJByYZ*U!)D`g+i66h6FU{yuxmo2%>>8Ou@zSL z6?QWiue4wS$0Z&GP;6Mf!p3pL63cS1g>0*?hlpU{U?8&HskD9531=fey6n_jAv|~K zTj&CfDklZEk0nK5bKD4B=uImqZUY3G{)?Qp_H|wihUW8yMAR{I!f%q zr)!u=9%`YjKaI5k4S5n*!3U;SR^E?wTTJwK9 zonW4+n)JTz{Yl1{E979sYal21Tk1wE#x-+nL$HIZ9Ew2g<12J5TkTe-QH?vR!cYUL z`NES1@ZSpoWCN{L@t@%P)MO_37yN+{U6YA^P&k5@&qT8GEo{8yBFfxYw3p#W8YWMw zlX)!IfdxIE(XCk~?_yaqs!Y2AwPqd5m4t0=tsst$0`A#(-{M;aGR`Z8VB7IEYPnVT zl~tITQ3RZ1EWrCFqFgJpa$$d!%*rr

WjOmsRA>%0fQ5w_8s=?tZM}x80hE3xeG; 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 diff --git a/mddocs/doctrees/connection/db_connection/kafka/connection.doctree b/mddocs/doctrees/connection/db_connection/kafka/connection.doctree deleted file mode 100644 index cc9b9a6a38f5baec0c896587676ef0d275f3e239..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56977 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 YZc<}c%2tDA;@DV)1~XyzxeHtTf3H0ng#Z8m diff --git a/mddocs/doctrees/connection/db_connection/kafka/index.doctree b/mddocs/doctrees/connection/db_connection/kafka/index.doctree deleted file mode 100644 index 370672058d1332f95efcf65da69e9c7371bec452..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5696 zcmd5=U2j}R8Mb5Z+PkqgiR+{wl4=#wHl_7$5{Li`fl8wSvRDU#B5tCi*>h%hW_`YT zW=^sewMc=K7={bfQ?6PhN~KQ#7!ms076LI@w{_BcD;$a&Fq{*zecOp|Gy$bPC}SF+=h^b~AnR$QMcg-(3eLKfs-t6myU z8hz&O0R=0w#C)BJO|z6HGBejdl7TYQbXn+0kLB8!#jj-IGb%MJnhj~&Oq?_Il4WaO zN|biuB=V!-mou~M%aCU~CrmwNFbkB~Nc&8j$Dzbiuvir|vT251>3I02KY_P|+ za~>Uglp2mAJ=n=gTfM%Dy=t_VcoDy@xd9>vysKbsmUMLxckOArMEI^7g&}vz|GHk^ znQGbf3i1Q&%&NMga@i?eMV*}DGR{$hQ~O6eqP9avC_Q2MJA6!0!xY^QG8;ai>34HY zd*W*Ev3M4CBQS46{8lb5ZsMTJR>V!{gmMeWMLBut4X$p#C zfq|=lZHcrFa=B+-m@_F(5H(&&rIJMQ*R!5^ZqAI!uodr2+K)AtPNKB$bA`yIWjMqf zZq{Vz`YFX(3{XfG-xCMo``oMsZ0zw^i)?2RKF?wsg-!FNiW2awp&#|LLsJ*h^AOPw z&3dt1)v+3;K_BPxvYiFfv>Z@T=r^fwbxj^sl;veFlC4Q}!qonQeyWEceM$2}52bsLNuPs6BqK3Dt8>QTO%CTG?YOzMffCTvMjQ#pFzN(HJBV z!ZV5iQ@_nn{Hv-eF!dE=_z?F5K#!#9j0aH?z#1)u z8X`vV70|s(VZl%3J>2KE$GIo9aLTG2`xO(4G99oa++LM@q9zr>O@W%+DVjBNhL0ss z)J&&p(p&R*KOJJb);*SlKoJrMG68xTFqzBgNTi<(ftiE*PUKNlVH&9(Y<*!%NqnC_ zI_U5ui4sSkmiLjdK?9u4)tNb!BR{oD&90J}3o|1#ON_n?;1R(Nprp&DSEF7k|OagHa?GvbL%o7QBqr}5~GzN394Hb1gQ%7VO;Fv%}dL&mD3N^NI3;g>Kb} z_mPXkPLvGcQD;fgDga4G^8n^S+A^!?-+KDW=YVG`8MrgcQ_p1iXUgT_yo@M|GNL27 z9@sq)5V~}4$7T)9`tTy$^Rt~*a{-(ToXR3u_NSA`1pap%YH=E zUsD0Yo5U+AyjlUMCu}=!I)3D`yvQ~eT*06Z44?ZOP___a_Ffue3wjr^#c~k_P+6l# zh68Y*hzlDOC=g|5#O(6{_F}m+(&cwA17%Q!d+^|Ws{c9H*yF4VsXd1WO!^L(iDcNwe-s@PjCheexc1;8HF>)O1fSho=HCA?QbbMHVZ%In!B5H;$~> zD0j7!Hjb>1Se$6Oc%7YHs~Evu3gN}Yr4}4Q{=qH7hZ%4NB$$<#ivBkUhp*w>JB#W; z_HDQfUO?b{c(YN6aI`UQ<}J0a;9zBMl@6;*5}vd$j}39p5>un*@{Kh1r3=5r^Ba9h z-OhQ?CQEF$FWP5Pg$gJfyVm~ v{V+5h!C~}U%yY8w&CDpar;O38PfA$)1$OmWEe7F+VA`uzowU( diff --git a/mddocs/doctrees/connection/db_connection/kafka/kerberos_auth.doctree b/mddocs/doctrees/connection/db_connection/kafka/kerberos_auth.doctree deleted file mode 100644 index d90a1dd00a609a9f132ccacd07366ebfe52256a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43862 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 Ji`SFF{{fu-)N=p; diff --git a/mddocs/doctrees/connection/db_connection/kafka/plaintext_protocol.doctree b/mddocs/doctrees/connection/db_connection/kafka/plaintext_protocol.doctree deleted file mode 100644 index b2d454b532f7cb8e9165d131c12be6c72b1a619a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18618 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 diff --git a/mddocs/doctrees/connection/db_connection/kafka/protocol.doctree b/mddocs/doctrees/connection/db_connection/kafka/protocol.doctree deleted file mode 100644 index 021a6cc89858aaed0bd8d05de98db22af26a14fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16936 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 F{6E|bxvc;I diff --git a/mddocs/doctrees/connection/db_connection/kafka/scram_auth.doctree b/mddocs/doctrees/connection/db_connection/kafka/scram_auth.doctree deleted file mode 100644 index e8c6cabaccb23a429cd928cbe7b3155da15899a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27927 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 diff --git a/mddocs/doctrees/connection/db_connection/kafka/ssl_protocol.doctree b/mddocs/doctrees/connection/db_connection/kafka/ssl_protocol.doctree deleted file mode 100644 index 8f2b33b9cd0ec4119b951266806bc771b9e965d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 59151 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 OY7{EtsKO^hrT+&^-oh~e diff --git a/mddocs/doctrees/connection/db_connection/kafka/troubleshooting.doctree b/mddocs/doctrees/connection/db_connection/kafka/troubleshooting.doctree deleted file mode 100644 index 156f1ce98d8099a3158fbca7649d4ed1ac59c379..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5030 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 diff --git a/mddocs/doctrees/connection/db_connection/mongodb/connection.doctree b/mddocs/doctrees/connection/db_connection/mongodb/connection.doctree deleted file mode 100644 index 4b771c6a1769c29abe9ef99b23174f72e44e0922..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43670 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 AbpQYW diff --git a/mddocs/doctrees/connection/db_connection/mongodb/index.doctree b/mddocs/doctrees/connection/db_connection/mongodb/index.doctree deleted file mode 100644 index c8db90f0d3a424ff21d29d6898c9df821f238efc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4983 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 diff --git a/mddocs/doctrees/connection/db_connection/mongodb/read.doctree b/mddocs/doctrees/connection/db_connection/mongodb/read.doctree deleted file mode 100644 index 43ba5a56375efc23c28bcb663d067362a8378814..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26313 zcmeHQ4~!hwS@)m&yFQv_!KU(F`Q!A7Wm1sdK6{;#!DpVm#iz*?3N(BOmidLvXf~qZ*DhNUdDue{T z@BNwgW^Q-(?ozi@*wU?c-n>8G``+(;-}k=vy|?clef=v-+xS1WC+>KDd!=GKPCInm zxW^`(u+eR~?WFfw@5BdsH+y9^8QC}Dup2ep9@~Z-4bN{nk=yPq;N>*sPyA+FkrnKZ zyY)En6C{T1n&U3{ZJ)lT^?9T0*DDq4y>YkG38SRuh2gdMy_Vmym)-e!%S)0@JYTK) zt>sGW`EJvRD}Gq5+s?9EJ@&xqIav;0dC9qYv+I^D{F_hgXxUA46=B;=nw7}4oy2oN zC$GqDS5H^YyuI>Zl^3u2ZO2`ycuA{S>UrY}J#P{nc9&dq5FJ)b0MWZM1gpHGoNhDU z2Y>QC;l#0$og|T8?m8poZ!6JyNSc%x~@F%t28v~9P+n2cI!aXn1H1Tks08>)(YFpp;MnD ze9j9v4eZe`*4qoA@uP@(Z@^PnM;Dcrk}Wx-8AT9 zQ|nst7TAPp+^EbkD;o?Z8sR#%-L1O=5l5MxN2aTyPVX5E$_OWM5{R{zBfI07M1E#I ztOxTcORR?lKAn06!$M==M#eWq{JBL-ilEq<2O#Db<0Qg-T3%hWmcq#L+l|O2(QP*^ z%ro1fn!x;1CCC8F3EkLghl#4gYINfSy9J$LjfutfwY8#vC zxR|`{<=P6`HGA^V7JDM_-Vhqb8}&ZOMQ{I+`jx>WRc0fcBDSNmih18zodaWgt|<-F z0KUKPR5nEhrbv$+GiJr$Inv`g!Ul=2|?ya%O&gy(Cbh?;2m)HazxG=_dQaVynuwxs-1E2rD4CzB~8w32_1IR6~k{aRI zGT{CmlZoE@6yw>R9FoMqKx+AIE_Jm6^nO>3k%FC3{pZctFRg%_dUH2^H&E_rk53M_Ptu#-UiEtQbSf zC5sSx1SCvFa~94$({%0FwG#W93o(#{mfdKC-F717gwh%W(OAe|Z&g9S`#T}h7ubP; z)fglFEpwy|Ang-C>etlorA>WDLTGm;n^F7s%-Xkxqi=5oNB_Dh9OcM2!L$oV{?;&E zp+=T6f3t{Os`+mv3?`9T!Qe{DQK^q#PzmhdRbf}Vj-?cbwdC4KH-eof*eO%xPNwuq zqaxKvY?slr;@h|+#;D;;u7)gT^E`qj!Q145y1d~Y=^@DzyfFy^Q#QCCgbsmPa36I> z=o2b(RQ9b~502q$3bDt({mBo%@x_l~aBg^5C(+P!Xhq>zkUWcvugu=pvXh24J8xAg zm792N*c7R~v2L86rPky)jms7cMqic0c-3`7HJK8k25OWOW|V$+uJrI>qW1VOO_{wj zsw^Pr&OS^~r0vPE$EdNnbt3N`z+;V^Jh>Y~&J191nRB9$V5FM#+=;;^?rqe@p!{DNLObd=@kB-ba7Yn0CbW8w zZ9hp1k>CglNZmo@zc8eHpC$)E_`V#3jdkv3Q9PSL@!*OKAW!Tl1agU9_XqMp0px;K zG?wp_tpLmW`V}zXd{?f;t-*N)wQL2>M^J84aPHq76Vdws7h>Z^5l#8WhLE3w7Zuq# zq)70efNbzX!fh7LRQP{32WKiW2+qfIa85;6os6c!i z(Zb%B6}FqJR^7F1`HFVEPH~UCt$gvi0={4wIyYXpm8~07>3d+Ano1?zrh(`0Qx0=B z={4ANmN9B6Rx&!P*GzBZ>dDTFe(n@%nXkwmd9y6nROEZJv6b6?fa^P)nnJ2gOPh-P zRTw1~|25l)xha2A-vL04jt2b`k zs7P;ar4hEoM&tDdE2puUD0k7TVF%lENf=dIZemxd`s%sG+By%=!JQgreLO&i>2-gM zO(>!jx<;rG1E2Th$`+0gU+Gh899Lt45k_U}2$@kUXZF%9xn zH{aQyI`v-qBX4rR)t-7F8T*cuXOF#kFbUSE?Q`9mxeRD~wh9%{IkK=u4s%D4f(U{q z2?+4RKgLQaqMhD_?U+g_Mz)_YxSxl|#-ztvP~QQLxk7B3(u0Z&n$61Vv$*Ii)#8Td zwrrZxEd*iEOk7kRV24eRgOuT9+ns4cp>#5B*I>=dxmpUl%BLZ2^q4Kx=PuNpAaE@)XOk``^fFra)WL_2b(M@!xV7FWr_itPvmMS z95W6ZYIA_}UPq30J^s?I9WK$s)G`V_Pu;?l1KaNG0}+D(zhB4za$yb)fCxUvSu&V) zgB+M&Mmck_P#oaked?0;+n2D{{-}syFc7_2d}rbt?!oL!2Q?#Z^W(WO0YLSAYzRkk z#uF9M0$^}60}VFXQ&T&n<_d`&lyN<-Gw0Mr{LfsvXRDQ#&St4>dTTe;T%@a>ReQzK zBTE~x1$)L5kFVIRPE&6X>jW)m|D_OvdDHxP^z{d63BDVA7({4EwfCxY*{hwORk`o8 z!1}$?bo_T(tJpdijA-J7+G;;y&;vRq*=`v;R;xF|#x-N(`Ng-P0UpAZM=)YkDPmQH zwv~)bPF=lJo9-p?y0aQ11ngQRez{rU;TI}jna8YNO`>iic@A+Ir;O((XiHlpJl#fs zmm6hrAo@c;Kj$NWGr{nQq!Xo*)Ew4XMQ9n@ut>kxN)>1+RnEwzX%IBM#3`}xPrTvO zlxJ(cGkc1XWb@?{Cj^P>a>j^2+*sDMC8}eVFPUd`rMhD3n;?$T)IUwAN}>NVL?f;( zafxn&j~T*M$%SmQM%Rq5nX0=T3TAa`PJ-$)qmVYRPy*Y;+7!%}TbffF|I*NEGB(1TDrrlod;`js)flWLdwqVnq zzw9;fm(*}7f(`(XHB&-U(j+*VRft+K-}ST+M;yxt<5V{fc&pX(QEI~Q(ripUU&v4s z>_R2VKk(w8|7i5Fp{-S~lrA=UjJeB0s&A`raAKIIPbD5{(c9{qxbS0F--u{Bt1OGk zqunGt?C*xy%b zwyYN0q6}8qYPCJfi_YG>NriE;s~fwO(sQ04TlEl{GIE`61IO@i9NDs4{D2N<*J1*_mBHX~| zFK{w~PDk*Q&S;&EQOJV~UMZER>CHkyb?MY*kYv#E`qumRxt}UH8htL{|K#v`9N)8|!z5*?zkrf8?qQhRxFn=6~BmBHU^YM4kF{B>G_D}#?<5^rOpT*Mi^ zd~uRt;JIx!4t*No$O*1OAos{WP^D(wQgCf!Be)!+v997W+MK30wb;ik26V>G$iw!o zcd1ITx{dAO#q2gtWOF0KEcf#-J$!G1qw(|B@y@DaL*+LtoEdhT$IqRz9#-wuxJPK- zO7mLB&z-|&qTIuu&K5O5{9h^S((a|55S5z5Y`2uu!Z$r)BcnwI58HkNp%liQ48+|X zROEqxnl}-dSNGaTq(VOwL-Ow_&Ftd?ZaCn(HN&qskch0{=rw{v;0Yol0SYv~*|X{G z_#RGSZ|5+a5@YK)Jhn!kcx2)(2ACS^8ipZ_D9&%XoF1n`h2vCayMWv|_!LDL&~Bof zpGfD=nS=D%ak)nfrXH8z0Hr7vOmY_F`XBrUjiT7_?l(6y)s-PsPY~6D+ zswH8g1~WH_d%=_FF;D&o?keCfbWWOA|3*VmO$QD0@(nq~3KFSW-lh?>KJTs;~0Z1eOMLrwGWP5th`U1-f0}dgx=>5XW6NmLsbH@zdQA zieZ{iLt*GUH-|zahzW;G`G_fL&F@Ll_-*A%4quc|TfoKw+i}WNxdkLlnOISDOepvm z-FV-Rf*3%UXo>3v6Ag)?2=M z3ud>$lLCD0$q50)+FgcdFlzeh*84cKUcTt5l&4Uu?z|BR6-TNdSR_jL?KpvruSJJt zs0i-bP(?f?$t2YYApd5S`@7X9W{zFePb{_Eo_wfWj7{V_$g{{(d3`JwtahuVaVEF^VKcx*T zR_-riI~z&(#Yt0=bAlo^E+1j=a8~m58PP1ftg32Uio(??#3k-5in{g4Hw`U#Xb zKYB#hg#pt7y9qfeGZFI@>*PtH3@tJrV+&UYbej$t41OC&{UG#_+U6Ex zO=vx(yB12r#&PKuu^1~YqpNb5#Y`z{hc5fx{|}yVNlHe?{sH|p#OkpePCb3@r2XTC zl9K;->rplVY|uQHqj~V$N{^xps55v1aUkqU;W3mgO${DrOtHDdU(8{BGUc7OWF`vc z>BA~%f9L9}dRm)@Rni<8G12d^%310H1o&gYJHah}NJaK)fjzh-0bq>fI{ZQ8!hwRr z4W=$UTqrUWat;g>D@KFQZWui1b8wk*r=z&QGXAP{w5MVC4Chx2DW(XVOw$a48k-@A zA4EvGzx}9DPoA0$8#0H`!J{eHucBZP0;>>mE1M8JMgKmA z9v21&rcr?FBmT6ZtTryZ&^m5*wo_F!)%@V{{bJosnjCzrbz^0GADV>ZRDLWz4r z$?)`azb3z@l)2g#n=QJ+GP{>*1O;<6+&o0V^Z zKx8mGrpQ(8rq@$@&#--J=>yxD?y1I=xv{updo;m@PP2Dm8!M?iSYTb))&(BD?QB{a z>os}R^(8i?QuuYNxD!^6C+=dOAzPf^tq#r-AaIB3*z*+a5vK~5jfLH$(@lDpyytO= z?6TVyN3H2bS@?g%(GWcBio%et)W$v1E4Z_%ii=VO3AmQAeGM5(^06B=@wA&<$#e-g zy#*9QtgJ;Ji`GD6gf*4(HzFst6{=N!EyZH4Uotq3nxXVRi@g0n6^#1B;qGD15gH z00+k%M1k+X<^1eU+USjNK7}v4&x4A~uOrtTn0aCB*36O|C783<{n*Eu zWU8y?_%XQ?=p-zbtwmT2vl*IiKu&uZKy3OgT-1E=j^oe(Zbu6d>J09_?f~teou&qRi&}sn zImRv`g|1b?Xc;r=F3QOXK+YPjGsW=07l4?3w=;L_^v{5w)6r5RS3RZZ>hD+8lk=7! zdkuGi`);zt+kil*(oYqju}QdHFpKch2E7Xt>@YCds-=@Nm4b3Vca7ZE0?E^`*5^tEmWii~~= zf?o_Ax$uSfkdccA9EwAiR-pG zZf5eoT0zp>+viQiY;WcjcZ1;Fx}SsC7gH-s>3jMS0{X!P(k$4OKX1n{1K=zXX=S03 zE(hO$W(vMeAK%A8T>xa?i!q~SkK7oA-d zbUj7Hak}V?r=W`h8UgN=u#RXjm78WV4DjiA6~T-xm73~% z+?CJoP1c?=`Mkg$cna2FL<$=hfzvOcSSt3P|Hj0u^7*C0!Kuh4DQx5jDye9lLFUT} z@`cD(=*}ILDtfze?2bHr>50pvHpNfKK#a@Z^B5%)y(Q{nyHZB*NxBQSXf?YI1a!De Sa=qk`GLv$jT)^FW<$nRb0R;>I diff --git a/mddocs/doctrees/connection/db_connection/mongodb/types.doctree b/mddocs/doctrees/connection/db_connection/mongodb/types.doctree deleted file mode 100644 index d2e7864672d42e1480add0c3cb498f4358ef41fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56505 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�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 P1uQOMoRD|YTqyiMwRWXV diff --git a/mddocs/doctrees/connection/db_connection/mssql/index.doctree b/mddocs/doctrees/connection/db_connection/mssql/index.doctree deleted file mode 100644 index 44b8310207ffde8f5623578dcce88a9f205c6754..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4993 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 diff --git a/mddocs/doctrees/connection/db_connection/mssql/read.doctree b/mddocs/doctrees/connection/db_connection/mssql/read.doctree deleted file mode 100644 index 75075f911a951d92e0672311ed8c661853990e0c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 81749 zcmeHw3z!_&Ri^bANi%xevSr7PQyxDwQf7K2Ikto1u`S7pt=Jyfl57hnLw8Si&2*P~ zy2oABlE!(GU}HhyIH8jzgv5yh1Y*Y_3*-ZKLs$}CAp`y5JO zx5IViMyWOD)`Rwsws(Gi`;G09aIjfC?KfJ@lG_e1z#FAXwN`Gr_4X7VZld>tYR%7y z4z~NPX+Nk2__0yXm))6ay-HtIf0cpo-MO6e2ER2w-)IK;N~3Yoe}hw{_4*2j0}_FSL4VVpG|uhz@%nOr59s|~d)Yp2?kK@8ZPaWOy)SP}t9 zZ;T);-yL9hoA15#g&zq+E*vTbL9;sD3cy$5Ta}*1C@>Adk5>vWv($bsr@hVxpVziJ>|Swq?}f&mRc zyK52Exb`SoSL?IROtUfP+;i;MeRn%8pMEG5Za+W|-DaV{MoAs4KJK0&`x0K3+OGX* zn;9RB?gSs*WM$7J$E{Wk+-9+6;1+J^fK!`axd7inxQxdIW5jMi}Mu|$*1=bcmD$>4 zCjCG zd3(4PLN{WyDky4b9@&xV8Z3;X68B77_fa((sXP&`@&lNwwfKa6o9SfFM}hs%W@1m}y%Ey?vPpXCI!gMp zn)HuXJ`}D~n3GI&lJ4-5DUd;$DCL#FhwG%0agtAkt4ZgKhQFUq(JH zzxk9g6<(IwiGluCOad4(dm)$EPC(BoK;IOyy54xn==opFp8FHmf9^wEy_ImYdy?1) z{-)5bhsJ!R+hin}N-zJN4PWZ{e+dZ~#LH3wjw*tR$^Jq4LwNZy?ykFKN6t*nj9Uy^ zO}LMO`qp>UgzjAps-!a{F6)?KJx8@*#%~i7*7eh^TFnjewJN+8!)_@*Wssia;VjG< zkHZU}|A|Mx_DfG-&MP%)t+~1{91|_J-aZ1yoSms~t$1BalXtViM))LJ$=7%EiXH?U zqlbgB9-<-{rvMxg1~V)B;kbIQ1cWXU;N3`A@acq_TrZGnO?x-ttK$3?MX=KqL`9wC z%2UI1aur3tmGKU84HgQKKBvL~_P0T*O00EvQW3nYq}qycPqFu{{hFkrCTXO)R*|Z5 zdah6yl~Xg6ModNUxrDc`tfcn?G{kiJJr<)tMT^_oLV*F23b+b?8MOP6SWD{|$=~$| z7V|2$r{S*EIF47g3FksUc`Jir#m1yKHA$oRRl6KPbK%(3i}QY^5sb;HpU+9&UVxMm z%_EtpkiRTTFNTTu1tM)a&pttqSx_cqUVC7dS`n?NInDc&00AFws~;bh!R7xJgB7J~ zMt98nBzmzK3>H+A!yNUpQ*`GS>JL0(CZlLh&5 zF31)rT9(%fD3IlCNev8f-WY?}pEwVqmwv=~JKFUm&dI|uC3+jgg~*syqUrsA?@hd@ z$-i}J;(ZM-c>h3eMa7vKf2&L5m^f3DG;!V$6K7?*ri($T(yK(v88v1!lln4*`ZXj& z>DKvfdYmlOS7rgE<@$aB2y(qWWuS&=KNy4CpJ*RLFa3yi5$$>s?bH#O(tR;Rf#{i4 zy6OEpyNn4jlZPle==l?%DaCg+!7-%g&*+&cyE@`Q7LB7LL!Q<2 zr_uH=V{N07mlRqPG#2yRy4WG=*eE`NY_ZXHu6E+qK9i2{vgX%Yf9fPr6tOH{qD8bFDUzcL^|K1zHs7V^Zn!*+r z3XOSMC_`$<8S!1$;cFgD{&Tcsb%Z~bD>!-uf!1$~zgRXhCpycq_D1tt3OmJ;vr>!j z;d71xQ=}1C_JmB=ju`ih)B`eIibzry*NR^Z{*T9c=+1wF$<%QTr@hfx2Rb^cKCxUZ z;$u?=$U8^_f@=waZS4>U1J^^=fm2l8bp^}Y&q$F@vb31|2-=zRpM8z0^1;c<)00>( znc&M%hWz#-&5`A)%b;b7kt=o@bcypYn`NZ_uI@sO*u+Lnw4$ejNu5k7Cn^)qra=lX z7qg#R9xG4tb=xtO3NOlP1miVFzNRzgkqUzQWC~s+7%0DONyV=_bf!2rU(LbEyoB9tT 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 diff --git a/mddocs/doctrees/connection/db_connection/mssql/sql.doctree b/mddocs/doctrees/connection/db_connection/mssql/sql.doctree deleted file mode 100644 index 07de9e856d064be8785dde6d7443fc3148091789..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48558 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 diff --git a/mddocs/doctrees/connection/db_connection/mssql/write.doctree b/mddocs/doctrees/connection/db_connection/mssql/write.doctree deleted file mode 100644 index 5bd78b5ff0e061be730d0816599413caeed369e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57255 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 Qa%GyOghD5au360gf5D@fzyJUM diff --git a/mddocs/doctrees/connection/db_connection/mysql/connection.doctree b/mddocs/doctrees/connection/db_connection/mysql/connection.doctree deleted file mode 100644 index 601eade80b6c08428e9cb317b988e60b0d6a6a66..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39396 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 diff --git a/mddocs/doctrees/connection/db_connection/mysql/execute.doctree b/mddocs/doctrees/connection/db_connection/mysql/execute.doctree deleted file mode 100644 index 70f97ab1d1f36ef3ccf4ed3b496289746f63f8e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49720 zcmeHwdypK*c^_T};0||(H{Ya&5J?;$+#Uc$5_Kd%fw+SJ0z8laL{ehTdvm*UH#6AX zS?W632f?DMyZ2mK;juawhJryzwVa@J~#eb>-c|cdtCQ}+SP(v zF4v;67q{5fa#U(oy;{=xZ0o?sTQ9Z7*kHrG5=YHO$!oE7C{gl*O1a_HTC;dLO68NF z5*K6xx5mx+I0+IwjB3TQw-D3<`kJ=q53tV{3eJmhvtExHNzsp@%khg&P<0o*$w|jg zl6pKjF%eW33$Y(~m2zANqKSF8yy#8bfAsLUtcS0>=J@SI4RZ}Q6*YD-eQ|7LCtHO-M}{2U62L9>}t-t{#_W9e-9|;;BO!P?!(^$7}RD! z&SCw4g0)XhXf^$Uzr#Q354Y`SkE^<(azp-vf7m~Ab_2?9WP`<2fc%Zkd2rKa!InCU z>;nH6t4r~7m2vN?2l??P1wVBh(g21Olv`}XsKw!Y6nzg+-~I{wO|Vm7?5Z~rgZge` zBB|CV_~<7{047RNt>%?T9w*B4MeP@FcS1r_0Qy@T_${$0twHZ^CNUYq$Oa(h6+=eu zp60^|YKu;sfYhW&Vkf9Mk1t((@|=>HO_#i@q+eK<;;*#00>apy5o4S2kIb~Ch@?D` z<0%xsD2n?}05zM$IIg&j8o<)V!G83(m-taRcILfGbj3M%kTiwkRw@qEh0}QNL+BIhC)@v>8H1kZa7r|MOs#?rxqha7M?)vY-|EZ!vaqw1ji zl$*Gx8esFn(9l$bHfGLWLgUTYON;Q%E2P03w;DAGZ_$DSd2tKQM^J7O956XI_2>lv ziB2CDj6k)H&pA;;zUO!@fU#5oe8u~q7AGiK;1nRz4PpT9V#BTbI!2Nkfe{JQEE_d@ zZ)N1K>*ObqijziU5_4}`@(GTcWjmLVR%`vI_0~J96=JTdV6HkCYL@L<&KX*hCcsN# zBE=kXM`!L)px)A+y|l*JO5nY}C+^gGF^1rERJMeb8Z;n@+E!pE1a_iyyf5Aa%s$ID z0t#(pzoO&M&xo@{ilw5&MhK}^?Vsw^y0giza5fq5OUAX{Od!{>;kt+Ev$j~g3O%69 z>|Hj+Acg{g32J-}&CD79x45x0%eE?&qILdnI}de?ZQ>BHjd6ncXbV2a2_jFQfr%-; z24?uzHf7DHX-rBj_JB4IS!SgcH_XaW)eUMb|J!T}`c0PVuu3-)1Z*2Vy{q*~PzsXP zYy357eS9T@PR_z?C{vt#`uvJy-XXv%Xts&esfG=c6kko5TVlSw$`zRZZB^L+fkgLU zT3qYl&n)=%ztt0}zuyz8{qGt${L9{P=zot53d~_f>43PwY@W$5HBTO zaD!0TqMV6H<#A8dN!BMbH+c-3Ae>6piV5o2k4^qh*%0`?@ zmq3Bmgqv%kCxTJGK>jS-+ZL5t^LYrhS^c(77+1jVX-5#P?YJM%$wCZMxIwyihN_wH z=P_YdqDuJpXl!q=dnozIEtR}FT$0IXKAe=Ie~Lu^e4U)h*m+pE*x5{8FlO=spF0YX zC|%&}t=B(3r+kxHwmsLUMvG79XyIqG@yD(oXq;0FrVl?yfKapuo`6tM`e-zW|85Rq zlkvweyKks3A!Jcs8V72ZnNdo2eHP8EV1^I>A?oRqJoVAhP`U=UaA~p{m7DPDXV_N! z;Gv?{3}*l1t0S;sGTaJwBT{nVUSH7Al_zj->`Uz_t!=Px?PMD7a3WaH@Z*~?MJII4 z7yD7OQbrVjd~x{hl_*BJD?#F8meUxTzZg3J#SX1KH^&QPuP5g;7Jg)v{c0HbYL0;n zK4iDFhfwLZT^W1@3BK0!*Kg)PVfXs(>_@ zD-U6B%yqlTTtPbV9eSeTxnvEvn7D%)3;}3KirmOpj4+qULy0A8WOhXO=QJ!HN1J80 zcWg+*!(Zg^psBuH!Ufd(6PQiikMPH`J2I)Xa$5WAHrVW6QdB*7eBu8=KP%BwL+D-C zQ_CH?Ynnp`b!3c!{7vP@-3@XPu9oYTJO+CsKehKix{QkYSN_PICc9W%lF^GxwZy&Z z$aMuJ4Ee{|+&*C?QBGbd5E>pzYj9K6F_h!UOPU!gfgl6bts$5xEw0!A5o&RMNyKu6 zWh27YWcTlF+nB~*$5K#)5viGI2)yt<6$UoqmsWE+SJSGdxB*6ear*D?br>0v^KkaY zCHpjFqvt`kZN6ElcuBDmAe63!UDDwBAiXKV6RK*j**$N+@yZW><8NYm5hk=#z*51f z3+ErFFVfA@w8#RXMrx5QvZ{tTKZiMU?zq+3$t>I51_Z6~DA%}^ENb>3h`RY;$|$`L zI4$$?_NM0v5D|f-Dn$ubb3QELYpSjfq^N!R%%#(a^9OG1oVe(W0ka33Y^=+%{Ap^j zGnPLiJM<^oxkvlv=6dUImfd47sNueq>(Y)O>HiX(z2g3UAo3b7Z*N+|Jyq$1`!L15 z#PdS4RvN=%#}S038;eI%qw0l6jyWarkMjjyg4s3h#C5L}xD`jLC3xMuG+A_1fv+=j zvpfw!hhHRAbtc}IbC@(m4e)QQ3h)Pdq*$hW^F!-R`91*7`!$X&J7P6vr7E46b$75!N2kl@#+1QKBp$uPnDkpFaAI=ghXOpxtOm#+Oqe88#9X>5r-`SzwDrUi3?DZha`$@p0 zynoq+!-mqxXu0(oaB8u`w@zPmtKiC5(KdUKX{K}i(39KVXVMzn_I?jH3rA$^yEtEo zN|!au?-^Qu;Cl@6aTe^1qJ2`r>>d)!SI#l3J7B zq=XSkF=K3qk}=~Vj~P2ppPs%j?OgVj(8zu&LHk0meWRA_-*(dEO@DP%&Dn!Tu!GRKS$x!hRfFi3eL2P6)(9y#;y5A6H5eX30m-Uo!Fy2 zNC{#JNyDwhF1Ox|T`JXTK)c5hu`~em+!U~6bHXpBcoS5%5+&Uwb4iUAm)xBXhhrdC<@JWE}5prUVNfVdf%)xC^rsq|c ze}QIJ63zS?>gmx;`>IR$WvHT=@Ym@Rk=9pLdH9~)ba;8t})nebxSS(E!VWkkh##U3YHN(oNoCD zfE~~kFSY#dRRL)*S02LNnCpbeT$cGbA0(H0A^$kj)q6Lo>An|TO|#;zv<9CQrwnd? zXJhB@PC*gC_Z~ZcPp+nw?|AH-`r=Htg4lT;&fchgN<%h+wm$_=Q3Y+~UUM^$`%oI6 zIeB6F#HDHH(uqgUO^Z0qA%q{~)2Gf%%ixVLD&ok20m~UBwQj=GgrClFoX2wLGc^JP zZ8-$WiQJKYrA<#y=9;xqMQH$Dp8+O5&db}I){sY4Iw9|g6nST6E>2&#SC zMf&FENCrRh#1u&;U$mm%Q`i%i{iZyLw&M6t2qB#j|EoECnTSW^+Xj|m`#U-p(n#~A zTvvAdNzxE}ZA8=#_Na+11?EtOLJh2cvqoTn$OKh9Z=w*e=#)xf$sTI3{Qjz7>7XR? zxav(wJcjXnfg|5eCymdkN+&-5K+5M+7tTMCpI`X1Or7e=E;qaU1!1T&JO5=4fhH|> zffl*m6+K4H7aC1|oNLmK4#~CzM;o@agNGnS4iO5v5#zZA+>QaDLkqe02V4h+$wS#2 z!<+^6|C|ro4ur-qRHYNcBq_t3JaO)vgUrQ}S8g^ueTh>jL*dZ8z@s8+W{8o@u9>(Y*E$&Caj=f=F(V;=w}89P=L_;=Zg zX~3Ud74ZA*Yo8erGy5n%zTPrHr6DJ>(oE%{lt`(4A#52iQvp8+Xf?vMIZC8K`lZo=b;hlGz%5XM}zH12XsrEGVG6J zix9Np5S~JHW2I6B&*XB+lv>wV^$O*0_CqbcLisP#8a%@BBz&c30j_espBY=llB8IT z^hDTtY@cSZ#3dv}y9K`XxKAw>s)AzSa^F7wui-)K)f}%L5AJX(DIMKFL8HOZ^m+7N|k85ESpOiGg7bU|8zm_dTUPQZ!!}C&lG$*B3(11-! zpHxzsX$!V9TYZ$=n>ET`%Js+WrF{hw%WhihoBim@IPw+KwNge;D1$M-l4+cFKuL&} zu;~`LfTV1p^mjH1|0q=oy#`8unAYHGWDy{&V*~tL6>Woq2<3ImBWGJ+TQmZG773}_ zb(>*K91XL!W#n@m+k|s!O3NA^L!YDcrWgk}Py~lI7zb1AT zRn5e_g9fQNMEV#+quMyxdPk8B-!g*TmutkFrP=x`;27<66hiJkfn4fdXS)2lPbPI# z={8YmPud-Oy~9Z(d;zQ5Q6nl93`s_GG6`spO`JIKqW{$|ld4C~uICImEXaz3jp%fl zYzHVWPK@hz#hNEfD{w;b$LI+W{J0MQo&q9KI2TM2Sw05sAX| zZ61kAvGaW3I*&~~dNSQmv5FQlW@$+1)&6%=0p)ZLxotPA;=l<`VWg8{I}Y)QF(rUs zf`z~ct;#$C*Y25sb&xZM4ENBxtO`vs+=v4!~KACo_(W|qZvrXb$mSHIx zf)m&l`4ig-g{<(sgTySt2vTitl5z-^z|l30=6oa24L+9b&y{mo_8{ZYZ>P=nLfyBh zbSC{!&yp|qav(OrI5doiZ4OS8pl$G3cnw;GXDF+)$L%Xb}T z#;_R+Aj*l|$co;SkgLt=IDe)TRY}zJ+5FCX?-i>yI2e;Bx#D~lxNT9%x%Xa$OKX{Q zP$OIHgUSFWP`9D924BW+`?=m3g4Izew@)I4ZXDpRwQ(pLagfQH-15w9NVfSDKT%Z; zN&U>kYCi#_xul(yJb{0|0Dm9-bJLo26pl_2G9Q(fG=#A~9(N|;g2GRjoJ-}*8ACwP zdC{4He{f*6N1GJVW{=MeX}xiAFD4>&Vr_dgnntf8t*ZOZs>|;nDg+CrlUaCzc!&() z^hlTVkzqfjPhol8lA_kYpJs}kv9fc|8-Y)u8QXl3o@HkquohlKy*@Vr3-^&W0}F5c zd+c;SUh49rbu#|r*;3dT9KMgRf=B^0&&e($gOu~hQ>*ew_bQ=SZPYt@nAb9Q@-&wWBF#6(Y0au=ek$v$nu@NExy3W){k>HNF+$=67ygWoxUGs zAjLER+akF%S36YHko~*q3An|k*{J57q7!9AsW?x#9`>7BEbJq^KZf&r;S<5XWX~AH za>iCidM7>b{6LGXEgSipXOfmn$dC&Y%aAd;3>jnLZtWR+*Rmyuf-!oo62`*Y^*Tqh z^#^Y1^|S0Cjl;~e`#nkSlz#8lyXvsI@Sk#(OnWe#Kogyo7pOlQQ<|2rTwZu9?T`J^ z&9}Uerx=g6hmY%c99d3OsPEjv{fh&#Ze}6u=Vxj zOHUi9%F*!0oHxQ=IB>VM6DtRbUNVbPDk+=@jp1wKxYyc+9tqTs&lM;4rKb>wKB#0I zHOzbAr|t1`OBN8tGLG2erk-}p9yj=C_W4SG7<#`I{2nkAZbS=8le6_ND)$iIGESYc z_p%+YmuZ6jseQrTi%s8jr1mEAZO~+DhI;PR!GxlC=+$4MHMR)vtdoMO_61_lm z=edVo=r;D+Q|wk}Oq@=&pU)-obcOwCP_N_rOjv(C2c)T)brv_L5*u4(0}X!%>RT+f zhAvG|^UEfv1yY9nt*>$g%5`8EQ0=Y;Qy5a;U&l~co7L?6ocpO(SfTQ7qC82ET!vLs zB~ZR9jmptM+94DRgC{ifHSPFxQOl38C7n! zkybkfyfE}JuMr{YkL-7Z*nZ;C(FI)Wu(ZoBO`%OGSm;5(H~@wCLe9L4laEsZ-EcTE+WqH#X%~K%YVZq$ zG$e<^kOam4%o_KWD{$5_0zQ!OPtlnz;BV?885>u$hwmzZz18MbkZ4ySy_!5L^E6Xn zwC*`3?6<+xX~f|sYNu9F1lA3kn{Ad?PlOu>$q+!un6Gk%1N4(`gVBH2qH(bW4hNUk zBK37tJst-?HI`rx4$GJ7_fC|Mn?K^T(DquSo~d%w5g~e62Pdp@CEUM=p7& z6A4%9sfTZZ5!fXy%({ji`r5@c*T6f!bGdh(j{3TL=40R>-K6NrPCJ~dWKQu`JB|9Y z$&e|B(k8es?T@{26ME(vwc2>)gW79aWI#f0_9WAv)9Dyj?Tu&X3a6;m9=-9L@!a0; ze%cCVXfvKKqAKj0UW{kY-iyt67QJ+Lyklz@x|ymw-Z@WfjUDekF!U)a_*M~=y_l|2 zd&m28_FmSA+W*Mj%ZjL-cb&du?W0$6N3bG{ILqq%Zb1ptHqb%oq6|AqcfWr3TUO*) znduv-+N0n7U3)J!W|F=1qB?$H@5RPUqL(&)H@(m&$+e(Z)<*a)G=JS)TC0^I+}Xh) zaCbX*R-3StD?5a;nk%d38CZY!N@R6)Wgnn>v8uFzjqV{L@08Drbf*!XUtg?J~ED$ zM|47-%Xr8+-*8JfoY!~bA>7-{E6!z`VzBv4U2&p0;n%ARI`QavLM7x6@4x&gH0rqf+zSZg5w)ERD zzFiAP_~$79+|ECD@Xww6a~FLQWE~uFD`{2OV!LwUrT1=D5@8Er^Jei1o|Ik+;gY0%52_f?wEm9ep?V)XnVDb{_h5&fRMPo%+1ilS}nRu97LY zRvVA{v&lnSGxcw${jt`~;j`!ICfwqOv~c@7ouK!n6i4lS{tpAT@H9S*XrPI1nT(gA zJNzQGx;zjOzE00Y1fne?XXe0HyTH}Q&VaUXWol&&jvT!{mb1T^uLN=lKn@mFU9(zkJ-D;c3DE}XeZRR9s&SyDJRaS5w8rZ|jQt0w!Oo(x^ zO*=a&iXB9%Rbra9^l-3+-zn4%=PAfV{cz5A@D&OuiT&Xr>l!AP_e`WA4#@D9bnf zOIlLK+;LRywoh1Q%*k4mF{i11{bV8b(n>PsOpV_qXRepzxElVST_hhAlp8_mMD2q<+RliV~+Jdaw$ zFg6uOHD!`pW{OBxd39UoK4wV8N)p^mDKk>s)|!u&ZKGR;jIP1?d)ox(bKFWw;CiOH z-8XD%h@JxThjNunr`#Htr~YhgX0X_EnZp3~L`hCXQp-zt!@mtMBk{T+KRw%t&DjO8ELdsz{+^IPlwnYE8z z$-RQDb=~Q0F%H_LwDlm}{cf%Mh7~zhX8Jm+La0q-iwWy zL@zM4^UOmpbfbFhDb_J%O->sf-)Dj8|C%lw{>zunsphep*5a;JzwHI&?YLFc(7edv<2q*Y%i8!$F|Ho-_KP>A%dF9> zjk~OE-Uca#6Q zwF}*&I+L|^xmVub(`)Pf8O8y5e&;1bhUd5PG%|^ql{b*#%Iz0d;T+Wvd1%_O zev*flrqOAMV{s0<#(H@Fd1x=>nzJ&t+JQ?jfbVj7XnDvo)s&x*q>L!KQyyBxo3!@M z^Ux%#P##*wD%Z;`|9Jq_w_C2jf?F!yd^H^o|F+WM=y_<_aBwV7opw~j+QoU=Bl8A6NvK^$0i5(jqboKW z75o~Zo4MxQ(q(!JPzJ;SN^I8xnxSi5w3n~35uD|g;O>XwRh-*a#SXSnx|yzClvhT? zXz)()#1n^$#lZg};mkrXSq@CE4Hz=nLclKvcEJH4v` zu!zeCo3Sc1zy`|Rd~*@)jne)(upvqy>AZ?3gCIxAqahJ|K@B)7f=_Tq6^?;ogUw`N z{K0X41=?Na%c9q4M2({F*2)#H0SXYPxE+mc7ns*t8Q`t7*xk7|xhBF5+hHk=igq&? zRPDRW7rDx82aO>HqI{r;uFPt&k&0VeY`TjcFkZ&*4PLFqb~kXqUZY$T`RnM0u8Evs zo3Vjv0XMw?BSBd)1TJSAb(=|4%pS1~5DVO_hZ{YLDN6#e`~ePv^%^y|LJe-`PdKv^ z^|MFy*lU)f;?*iK)DB2q6njOzCg&2!?DIh!2$tPmEC(@NqJ)7+D7Pq4f!GaWFfq>U z5-06~Yh*DTJXXQrW?Q)10N>C_>@`TCfi~1y86@ATR^b@u9+5~0Bi(-XQ0j*AAn%}`6)yMBY{MUfb(Z)h4*E~JRntwnwPdRhMvX{L^A@Gs~-Uk%Igr8I^=xh*|O+kvv ziQhWAmE8@VtQJ$@OtqlA5Azz^5iive_iE7(7X1qTz`vU+5wZSoT*VE1#pjxk>KKUH z&3}uZ#Y$9iC2H?3`7YoA;f1^b%N&KMeYsi3nQWtgE(sFgU#Nj=;GU?2!yv!|QZMWx zMFB0FD;89VcX83M5XMbhC4ptC^HAe)vIGcx&r8t9-V#b(F5)WWpi;yIS)_M4)H|tV zqIm387KBc_#x{Dj=UX$)dA3z(6NoQ35vR)GOhWKo;x-mN+&u`s*$JS)SqaoF+Q2yu zq|N<3{&3888LwzYp?A<&%{tdmeiT7N`g>yPWg&gPnN)3=H*+3)n5WzrC)%O1cs3@c zr1RmoXkPza`uI~^2pfK%K7I!G>V_}i1A3HG&5?Zp`mOTZ32Elgm;J5M7 zWP{WRKjMz&Bi_wxyq9adhikm`YvG5Xg06+9`6sn=jkkd;{CiLfbKyk}6#n55-A)&4 z5JcgF^zBjlc4z}WV)}N5J~$k7O?UWZf{1SV4*w&4quYczZ2VfXhC z2a-b~s`F7L6&hZ2Pwrt_deCTbQ1!|y?K0ZOoYY8K&@g-RZOJz<4WMRAI3@0DW889!wJHlK1XbJ>8j57w*8^ERECIdz^U>C{#uZQ`}gPEoLBk>JR#p;x!b zBhupIjXJw?8Rp*8ZT$O`RAxAv=vD_fQ?+u~daZA4z0wU^B#Fzh6@CKs+TiL*4Oi+i zaEU8icki)X>1?m~j@Z$P!*s*ldrvRnlqAydiuv{#hWEFxeG#;F-|8`3@B898QHdU+ z+8-WXp0j^uL&ZXdqc-!rF!H#{%(54`X~09By~y5vl0C|{O(S7PDoPWVXJ!r@u8_W$ z@Gv{T%XQ+{(pT+5z-ub)DJ?aKQRsNQFGERRD|w=3{@8A}K2~WQM~QYs6dkIMTQXn+ zzPsBJTE}X)(~-fTt%T&hr`j^=^q4o`owx4X+9}TAt3A2X^Hbhz;cr*7WWe=AL=ydy)8kJ>^9elrtG2bpNVVBY625C)e{fYNIx6fSvd zR_|%PPyYXwSmjKUX`RSksu5R;Se^yu z>r8B!g*1_wdG|{NC^Jo$g`V_Su8o=hK_vJYmx2E zBYRnF>#1qJR|OY2GVr5bc5LcGdLC-?u~{pGRfOd*4SLYf%l76>(?(N8p+5$16D59P zs(mc45x=Im1OPe3qmK9tXM<=qiV*A^B`l74lCkN0r1RorT!8~2^Y$WYMYO`1=d*GG zl15?-Om@v16aSqGZz}yZ4mZwWL?&9k^I&n5AheeA<~b7ih@&qOFBiX}1p15ktN5Gv zI~Py(=EWzbxeq@v3Z%-!Z;r*&WAW@*{C00nJUv-|||AnKw z=Vpr@#}@F6OcnoPg^e2@B~jV~l+kIm3%-oKZ!!kQCUs>KEouXsuFAJQL5uqXI*i>YZ?O{5@4eQ zWvyY`xMo3&hm{1;g{r7pqs+{AGP9Da;NadvFf@zDH1YAYhFam!ctLNWN{#ByI5V4l zhzTRjr(QnC&;`GvZaJfVlmrOHxt$Dnc4shN25lcdN5gaHl0VoZG%;jVf z=_f<@%)#&&c?5aWNOgbbyE{ta`?ZrV9iAjn;s^{zK8hw_0NY%hnT_1@Q&MVHN@lLl zyqOU(oS&bYGwZqBi4NuIYbRgK95xrp24GJKxX6c1nOXB$I7l&>!{Z*FFQB!U%L#X* z#KUkO139>cI%L<>Q4;$w6CROX2?$X)>nzogGa;D^G|{YiX%IUVB;i;~kU(P+GM@x) zSc**uivENYE1r6hGYSY$7m<09;?DF*ixMR4L+ZsAvKx*k6+4xk}$~iQ)-WZN!G8QFT~B^Q<1xp)XOs0Xg9SK=frGb@u+nubcIl z$CC;|#;g*h$Dli|T5o8Tqt0@WiMvdhC3;!VUW$EX z8brw3GT2@|SK@Wrmi94Re9k&jL8d2AFWg)LXNC zH;sMiA|3G)27Qydm50AYG1zV0u&dUMcD^San0L-Q@cH9E*(0An{&PF4wy5DDO!$u% zsi7q9JHtNyy)sNq?aJaaW@<(2n>UMu7^Lq{yFcEic9j3^8nWrnjmXE`&R zomtOCBojMUg1Qiq1GI%FL4&3!jJ61ZByWA|1wm011&S6ZP#{2y6op?4^r=8mv=9CM zGjnFnESJlrtt4m#aJ4(<_TSHcn};LcIDYkj_{XQhj%9nBHN!ML-(+FJkC=WlZZj`R zK1iPZYVvM!o{t5_M(D>ulO_BBW;89^H3Q})D;S)l`H}60wQPeY!nhGec7%t%r<<&0 zdp3Q|_p?U$H)}QRoiOfn{28Kkl{v&HN%JEz)tv;kHw&bSDJ=r z+NP00!w#x0>@L|{XSaSOiKio0E763CH zHw^13AZ0xbj%oNigTG(E-!nk!Fd!GOz9?bcrz9k?RkMy+bJlpzb7l;A6wRwxb?cHf zd;1XP5AiWQS0HOBZa|t2r(&s!$fqFxdV4E;+pTs2NZQ-69oitwd@5DDA~piVY%}2} zdaM;NUk4l`1c!AEe|7#EguKn_Atb>F>QTE>7nG=zGSr*C=dmW~X5DP)$}h2dy#!ee z3`qn!qhY#6!N)pGigO;gjzIC2&KZ2rvvdmr~nhjY!z;%Ln}An=A-{w06U9cZ|qx*sdLIrI4-cHd+9- zzCXY$1+Y;;z@v@Wby=jlHb`377YQiL4~^)Mhqf}zfBp5XdvCm^g)Df$0u6tJLpTr1 zcR{<;YS~TOaNRA3y;PeMQ?50mSWJMJMKpY+_9D#SMVe5%B>6H` z*z4Z#fyXmlb9O-b)-I6l3FifE04Pb!h9ee~h;ypyq4HsPfox!>KThMnBvoVO> z*9Au_1w|B&r3|&cBn3}0_&xc6Ki!{ik||@bdd6B{bS!oE?~2Q6WzS6t9Dgu4j^E30 ztN_RF?4^HSQ6~Qp2%;c306(qoOL|y;DN)*ES%K1D?H{G1KeT0r2Zn;vRp2vs zDf0kxZ1~BZ)1+D1Wi-dlnLc>n!2Z#ju;!detQ-_W%$FGME0XLWhN0%Xize@KUdT za=J=~)gc;!qc?KOEj$*sr38|GX@OIZCgh1TrM^IIt@yUf$B@+O-q%k1N?Fj2jEBKYpbi_R#*f5tku*xijnA-1ZPkuIrnKBKU`#O@npZReq3^Q##rt07(N ztCF#)k*BdB7Ez(r0>9dRzz|eK2Bmleg?gFpZ*`H!I>5&}4Dr6Vrf=d*mF6?OfJkYN zVA=LWuwTSQ&L7j>l$crJ6D7zM_I$VN{4#sa^I-u59||Kx&!hOP5>QOd8W7*UL)*+b ze~AD3UB1F}!}2Bkq7pUl8AB%`v1Iyf!}b#APjCny1sKs*2Z^;I0*D{QC)@0}h~}bX zTMTa)ftNfIOS42mHyH@>4+-)#E_nhN5I=#Y{udgoy2QoOS((T$NR2`&OxB3UyIDEWvSOE2R29tJ?n z3m1@K)4~pG+Q`XhloY>kp;nQ6PermFyOG@i^+nE{mUUPFYunab-!Qd?;o^z}11@St zsBJLUrT>jMg6W8`X$!{Krz{W_dTc{$8w6)PU}QK94e1|j9BEm;nwf>&em%t$q5_ zc)Lk8m~H(`NqCDjd@564L}*oEQVQo)8A`29DQ^w8wcC1;spw-^RVziM8_BpG&kJN< z+z;D?&-#46LRhb}B~ATM>JjWGR5A|JoMxsysW(cHX_qfwdZDJ>rLS!RSy8)%ijHt- z(yvhD%<&ah6CBd8R=Mjk0}infu9GAA5QZ0wlRry6zRZs|q>kEt1(#`J(R@N#cJ$kzAtU2{8c) zQZEW~k?ek6Ap6CAAxmM@ujMO*NIq8*N$Xl!T$<^vfs3(AY(f@60A1JLpsO_nS4gBm zVOVgnf=^RC$U`SX-px?%tTwd5e|iTNG;?zb>_FOs9n&O?+A${m++U*&Av2On|3NFZxwU{gSd78{(!(2?Z_*b&1)?0N7vYTB|d zt$&dnA-_Xw1%4X~w-C)=B)d-Foh6vgiy#u41?)4=7BS@U9r!3=+KHjEIyw&#s^`Ky zy%Te(+>>RJC|f?+wPk;m%~G!f2V4&HbMz#wvVDJuKfMPNB=}Dj%-O*z<*@1}!sQD; zF_tSJ_NT{aCL9v)A&82y_?1tD-&D8A6=uF0sXJJMFJ4X>ARi zXIfR5oY(RW^HV^>T`|^ropFa<)iAQ%j$7 z7Z7%pCw)4X%9GAL3{qJ#gQTl0+0D1XB=mYoLW?zgN@f?TSzxQg&~iw|ynCf}5U?sc zpO6V7B;zB$Dhl}p8Mrb`{<<74N9m<{Cvx7!_UHLTsc|r|?l9>v?ilOc$jH?0N-C~q z-Hamm9_BxX0trfwI+aR9ldh>ZwscW!e>ZWyP2zepKbLcMLX?C*G{M7hTl2jlBq!Gjy@OX`r(?%tJVS*pH)593_j^n~QvGjUkMtlvIKr zqOCT9kAWXY1K+3SS+t67qAjV8s!|F9I{LhIOsHf(Qa+PjM*J@kG9Dr3bEmn>66S!3iS;X5p>ydjPtSu zSr0PEg1j~LfxGMh133@ziiL(_`E!IHXPb7UqiP(*VexdtWQ}+Y$4yd!3{=Dngq&?W z83Q|-j8G!@Y!5Wk(ah~rH4-(rIBHd|Rzn-#pDGXPEC~ESx8Oy&NKuFkw%v>pK9y2l z*=0oRlJL`ABVAiKe+O~olj*(%4082T<$)m(9m4Y66|hoR^#pb3iDrW6S4FrTDKf?ho0hp4A`!i?J;Jr_w3Ry?pF zje%#lwBeL^B7~i|Up(rBEH-@|(I;jdgXZ}m)76ziN}#h3sLxr5?38ZWA*BGQ@i>KY zRH_QpZXC$qEdo#I9X6fFrs*vk84K!MKSkeg2wYg|P6BUeH#Ckw0gR3O=k0_1IO!WC z$6JFSx^^3-)z^=mdn-8Z_!8%@hD& zrjAZsum=ez1l_DtJ$vbS&~q|qHM_Q_C&l*9$nBxL28rxC3u-otTH*}qs-l+?aM&1P z4rmcxVn}Wu;isW*Z9P}c+zRSx+`r9_gv0Ep1bXR1bb4oIU>nloqhQs73}kj;nhXW3 zY}YjLZUoLUQe~vh{`CYR({3;m8oUJvEXJtzeX@yJ>pD_VwyS4VMgjHltN|Ue>b6pw zw#|o__aH&1I6snF6R0mF5$|>&Hw?K;Q(HoAj(5MGLe|`xvBpC_T^gbrg~oQg_%RdG zDXn~*UN@juE9hoHo5D>a#U1OO02TicoR;OhMxeZ!14U*26eucyr$A9{H3f>Q0s<)EeLD{b z#*Tn-LKWPUx@dJsZpeM0t0&P#cwJ|U$NPa){-hVSOOARf+iJ zrTuZ$ptc3Wak6`_Yy>wxtY;J90#pRoYZF<0NExgU?$6O1+4Bz|5Y9gX>)Qvz_md@9 z1hm`0wm_;vq~PdapEtl}zgPH6zXCl9 z$T~?9pAnvL`F));lw6=J2}CmUMJ1I0(lQR}m*Pqz)j8*lJxG%is5Y0lvw-#T$ p(~BY_k|Kov(Hwta4UgI$YEvd|LVt}eP`tGOuOYi&iJ5Vu_P>Z56W9O% diff --git a/mddocs/doctrees/connection/db_connection/mysql/read.doctree b/mddocs/doctrees/connection/db_connection/mysql/read.doctree deleted file mode 100644 index 98b5b445c41c1de03ef8ad9ebf9fbb8342b77a3f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90065 zcmeHw37A}0b*8nnTHV@Zd0%*L18$k_YDw4zgj=8ukR`Q*C1ZipuBxtj-BnMuwChz# zYGMN+EY_D7NJ2pZ3=@`wkgx^95VA0tU^2-}GLxAE0_0=zfgvFwlYuxa;UmlZ=kD*- zd$(RySIdIL-{(^Gt9S1?_iXo^bI-l^RYPwcy<*iB^k1;WUo4et7c%)`u~sj-emhuS ztQT5Ux7KXGslD^Z+K;rygONu5ykBoM3T`{N0&f&b^CPh1ZWi2P?DlK=^!GC+3`No3V)LqnTZnKhU zxcOqUL-6B47p%5-)uI@bFC)$N_<>XF8bhEFkGnQeIF0;eW;eN@^9;ltwsIE zT)uD?RABF{$yb`~(vDzFt5I$TclO1gb}(AV*NWw0o?{cNdS1J9yWa|ioB0I-+b&%l ztdml<@$QD-%q}+SmHNWIcCfZmuDR`_tAllmd9Ve@Y;VowOE+RrrJKPq2mg=Z|EJ>r zr(;m-fH}kUSrXSCC86CaWlCF1drBK);q1suQ1sqtX{xlVwEO64yuT(G$tndZt!d3c zn$~f#G(}_!9l63Cn!jw3kA&jS71POH$hT#D~9U`cu{rG<>I0%uhx>G** zo^MPwtBX@?+*2g_Q-yl1<`zg3r;2k~<1g0f6ycEp)!Pi`VV}QhAYEEV+A)p+4MDpr zA=P-=y=YyoEjaUydeu3;bn3;&oR&`ya=AkX=tsAa%dt^X2WwBe7s$Q@*Cn=VzqZYc z4@P%}k8Y;4dxqmyDL36lzGC1OZ0vwjnhn-V&_-uPv$JFoBEHTM?ADx{C};H2OYqVu z-e>6~*s+$OI08A%FEsLtB@@Ya@2$^ydlf;PySY>r=Q3aj(cf(t-z4(;a*i-OzOxsC zus7#78_=SKrJOTgZ#d;zq2ZFj&Q~00SKgtXK>CSMXueadyS`JaH)S7Aq2)Khqla2< zqwM+;-@{2l1>v2f;HPcE-hj{Ttnc44fxx=uvjS<%FXtp?C10OMy-7@7+GA}Tdfyc(3hEDJ^o;}7vE`) z5r!dnS}H+cD*p-?8wA(mA8yA1Bi^PP{qyS7nSILZIz- z>w+Oi%zkmxN>U50Hx>RFCo68=cb#S)hN4_+)}4HzP;b?m!W!{8iL}Az7QVD3rLFY0 ze9D*&u1oC1K>zPd0vIy8oy%+|pbsfP-x0F9!Fb8&`5(=m`xDo1^&zg_YPi`wNo)vz zQ)o9pWB#t&WF(nNFaM1VU+Vd5LIOtcvXp>(6+y*hf4_VX+;ED!>u%AJGm|s#=9{es z+($uu>pN;f_ihDMk{J?}b4cg*Qy|rv^PYvTit}3)!OoWu6?Kv; zOAV9Bl^6Y%raH(qlFNnqoDGK9-v+5lk=ETwMex#+Y8%Eq%ig#4Ym$nZB$4WAid3cZ z)m&~uPR&pnF%`k*65hVDlHO}*h{^POU4#N9EpEfP90Md3a1H)4X!nzmmew(nzv~e! z=2cvigu7PbC|>C%ob7<}Y6iuMjY)B8l0@-aZg&LDg=168FZ!i=b5c(Id`|NA0Hl;? z9?67-{3&UAF-*kAh_uN(`#Al~f-)iV+5@}RifBpAY2NP$5b*J~`O#q+T>jk%tT0_u zx?|oa(TmMsu%Mb8=CDWR{UO@*G{wb?#hmD_1ha{HX*HPM|7aK7(Z4BA)a1imngr|k z?9vWa-A<0Tw;K(Fv849z@6tX7WReI!H6p@BK-a4jkAzI@0;{iSe&Wxce^x>h%+@w66f0@;;c;9T)tT-^(xVFMvWNFxV{XbzK3Ke z**YJipW}u4<}`q`T;C@EL9VY!7^orI_ebFNC)%gbOFyEWN4uUxJ8?v&bYBHgAbO^i zZhHSkT}BX*HfnOXOOr(DrpEibG>(CnB;B`1q}w!RaYEVE@fc4u*>T+>z3ZEHeHU z`Z-?4Z|OALv9Pq5|Ga<&F~2@}#D=85D+0YgNq+}==||Gvk9Ivtx@}OVuwM;fBEqH> zc6$G3x(p&BeAMKPU792cJ2n1^E{$X0B?-GHWD`*a5tF-ul#fAB5zBYxWxB6nUTrI!cZA?;^K3jL&m&@+t5sZ$B zK8I0Qr27l>vnky=CPJQ#V=IHAwe+VUn_r5w4a;U+@J-NIRJ4v$!04<4k!>={2u`mA zq2!>hj%R{_*%PUU8LVXsa6wX^dOhvg{jyjtsCj~v2Nji`y9~ME#E{Eo8vn_VV7Bve zfl$8@7G1nyE(_4op|@rYc#bsLfnXHU%U6So?W3!pVM-iU?KJ2em4tNR@HOb~vrN3K zVrYgh)Feq`w>|!j0GX}geAX|N+-klvS@TqkODfh{dfOPn)9JT(t=bgR>1;5f02#eZ zMtZRhjAb1NZd#LiEk-}a-naH^5{#N85v-w199gD1Wdpg6^JOl!l$oJb$`kx&xoD>s zjcCcv_=_7p(OHJIH=6Iu?G#l8%G3o9U2+td-i%{1k4bJGjd0J%HX*sGh$QXMuy|td z|5T(0E1wwu31)(Q?LbE-QqHUti}={ojN&cOfDlc9VB0zb!WekjI&ezFbX~OcTt0L9Wch6vjY?`k;8uHtVG>29suY;B;Muse7l!xgoBlY*fF4Tzl zGSoyXdNP&O931{rAyF~vS+<)ry__;g^)|~yPoF=MZb~@}&`mwc0#Zkp< zu{k!&dtqxK)6^Ik-R7gVrwLzn;Whz;{MzZUQ%8@S%$_>9e`ego!$G4gD{b2;-WG;> zy2FBvqgVwMJ(&CsvIVhej&rS5Jt=?ir+4q#IWf9(v^Wn;IFT}qR&8AP?X&;R63(&9 zf+buNp-!KMBKo#MxseOi%3gmlPnDhP7qOD-HqPPe5bBEX=wh(GlCLeaP?yO{ZJH%i zaR!@9<%LoO|2MNJx1`EKvcjJto#$(bPFRt(LAmOPQK}@@XOvAz_*I*gFZ5+rc89R> z9z!QG_Bb;J$J;OxTFa%eV7k)9ju;)tG0En}cu$F%t$%Z72P=}((wJuUx0>}sZWFbI zFS`HuI7OY)W6Ypt@fvI(YN#NDK1|WHX%1m}P~Phd#Da~UFE>k03}?o7?qyICP29gr z%gRquKLpaEtnb&lSYPi?@PhZJ^zj*dT=YJRPbz6-cGmkG{_3!;&kR{J?coONUFjfd zZ`6Dy@*8okCwYAq*K^CgF4A}?FokzQ0kVwzI-YAwy?Sjpk6?EI$r1fm43=jem?L_` zj?#^%+zM=nQ?FH);Eg=gDmPGJAJ1c8@XM-zaP&C5WTh#D6>UB_W z!bT!xU=98H~LIcv*vR6` z+n9ZUae$GjFT%(eIwYb@*+jM&qb8_UZ~1O!l-ZrRIy@(f@$oT7#G0N72U~xfDlxs{ z*U39>)X%vN@Iat}g`t8=){@K_ZA)0nj2`wEU0Pv+c`vgc8nATup9QqAe6;UD$SlSW zgU2kZC?%9gDa;9LSh5tBFEm!2P-Y$MONm#2*C7Glpy-bqbU8EBzB}y(E|g8M{%EdAL)C zaSBfzi}|HVWwj@p^+_fKlgz*vi)=UIy|8s>?+KgPZR&s_)OZG48WHDH&rmTxS)AN1 z9_8=}zPuv^UHz?1{AYV&4w?8rQ7xE>$6m716~Pd*VMaESSED9wG7E+=OVU;??AD}2 z%U7sVvu|OUt_ap*|8Ak9<8i>;OuuCPGB#RLy}t2Iur<0uC9Ed42D3)_8a9tH$KP!A z+z0Nym9m$6o!b_dig`@q1qZvF+{$hHCY-w^v@FXk?RC_<&TaelVHrgfW^E2PH3avc z72(w2)DU>5CNirW$u)e_{2CsTAqDRwY)OMv=OMDjrct8p5H;E35qMJyqmGsi+^Es_ zIYYjya1MStVH+mPR(IQnDUuu+Y!$;QHZn>y?PGR~rn-Atc5GwE*eWJ|8#z~tMYiOx zcTEKF2x+sl&VgA0NQ(E^N^|EaR_=@k8z8Nd2#9!P!Y0C+?VMwOhqn8Lxp5O`q2`8n zBR!GBA7R6fjNSVl@s2O>b?Sr0d3P6_*Avbh;q?Tk8?I9pTn!tz%r!b@F|qj@$YBRD zp@_9XM9<+}dBHldOEH+}MG%lH<{KJI>w@+3*pI`;%m^BcW}EdwmKGQMws#N4${xQC zdnwru`c2ZJZxaiq6tkd(PY8uF#1_IyF@vG}a#ONSGT*>m<9iMrILH<%nEG(z&Ldz% z71nn&zwfj1J7PR&9qTgH{MDf5y<#kknp@7f4N9_t<>mRZTQmq79Nb?la)(;HUpXIuRvdSAH5PJ#vm>? zO`+#TZ$G9W$IWk1>7-;!2YhU|Vhl8T?RZC(K?GMx91TuuJgt=xZ(264+8#z{<6t9} zR%(6)Ti>z%+P+Afxq#8mE0_jn!cKrmq+l)I(7jQJJ?D8aEdGR{V|u`RFp>(Mt(0A_ z6)FbaV=r~1HP%}~yA829xnCdnKN4<~I+gh<aY&I}`` z@X~tA!7&^>P|VksxKBQsQ`HU5+>*#GsA^6)aY!EKu}D~QGxf#-KTQJUcKzlQonS#Z ziAlZ|?oWEfxq>X}oEUY_xiyO5%S{J)OFD*t4l01<;T8s~*?zbZFXP;PJ68do@pQ14 zt5qpFa}@id_8QwOv1n8|hz-fP+%2Ps4qeB0kqMqRw%Q37$|<^Zh=C8+NsLMGQf5N6 zf%@YgGQuzuNcppC5(Z{y{2xT5uB0ni?_VLokQRD-r`#Ec{jUgOHr?f3S6qMIYFbr! zo-ADLQ<_<&6IBNhwV+?Re)WK^U+uH+eViPsj8%_fa&Bpob!tS{`d89b`D;h{>X!&l zSi%F|HjJN@tqRmYbLfZs|j3gk;ZL2oNo z%?6>cumg8J!J)?0ur3n85%tVKXx%zF1Eet3tj1AMf7LBUKT&Lwr#U{YDI-rct)J$y zsLj}2%P@y97X3EYGuX{Nkp&*JoU?9J!?9Ap`xLQOwYnOuv|}5p?Fc?*)Me_@?18d!pDlqCSCeq=2cs-mHn->{LnW2I~&3*V? zIB0>^sm=H>xZ##tC?k&LQd%Z}#ROB3cd8ehTW*mWZhc1$>IGc}r4c7o50NHl{ItwI zgL!A82)io9_@gT@k|Fl95thj~cO(!k*@S1LZ`Qqlos=hsB>&W^nd?P0y$3n^570lC zYgkM3u%gFYy|`r1>&y_c2b0cTWYLiA*qfVovG>%+z9VNGMUy+1S;e1G-R;Yb8c-_r z^KK(MhZXrEgX28x%)r-OHaMf-FhB@1ORz8#YzVJL23r&T1pk92C_;J%@qf~v(w`S%CmS7VKi`@(26LF(Juh< z-8M11aL2>ve-ti(so=(0xoh6b$+}Y7S?y`jkCa!cAI%0^l-=QG%y1-MZ8h)d_MW^F zee|>4HbA>x_MWh0aOvGV?f#zNgYY$&_Kh2dd>pzx$$xuX!IJGK9rTJ<{T(9?b^V8r_A^H011K)TyyW zFdUSple3R>p>Wb3r~iz0%aF4p%ab#MIB}Bpp)P}pk+fgw-Xx`@y+%n|c$&VX9zoQ+ z{RM^>kw5iF%b$8Q8$6>2hxB6)0iA{nMvoK$|P%eZ2s z@{8S@q?F1FN-C8bbZH#Ks3dM3?vOh8Cj(&+pzOr4BTd4us;t zkaM@(Tu7+rPA{~H!9+w?J<_799^rtYP=_H@4XJyWx?QH!Ep?%DlGMEp?Uo^RN0%pc zA@amY+_^5pijlZR_a-SNPJ5#9Ie<%&W{8^4_hNVv8B~w745~-7!L2EfH-zy+)c-Pt z@%>#`kR*(sK)YoKt>qmJP|}x^mh>C?>h)G?tRC27TnT3C?lmG_x{OjwMW^iET+te!qB zSDSb>L%#9=7XRcfk1K*5tl}PJ=h?+pt?K*HP5{9-jeOaPe6VVF%3`F^<<+L4+z$=}I7R#}Gd$OkFeN9R-&{R|YXC z?D8A7RT2kshA$gpYb?fvLu|1H)&{rW^hBYzThUKJA+LlUv_*JW3ht_h4I=%r9ZC~F zcUXkpx&7;QC^oy3$nCW>FBl{FJu8ePc6)6!>u_<*E221@17X|W7j+5`ARwOW8OvYx2fVY+bh~WUuzw{Dd^z|#U@;OxZV^f!FMishiIBQ z+%ZiZA(PnKW+i4unI-g&QR@^Aly{tx0_f)O$nC z);m(Ol_nF~^Yp;rwkgK?LbQ8nx4;S&UwcNFjD&(+Jt&}jr^-*36TAgJcZrGhwnDaNLG{=!~) z?j4{O?Cw$TAij19`RJ-K*@QOAXl{Cs85(eg?yM3rPxWi&{taHm_X=gRWX3(086Blv zAss)+I$fM$Ax=5*k zhc42j%`1kC?aU;>E#>_Z9rlA;^4!Iy4?s%_GL33{=EP~%Z*mtXG^QT9A#QfXCc<$e zdke5-UZin^&rMd$cn`Q(vO{pB<{JX^cIrMcd1?A8L`m8z z`E!yZXS!-(pXS3rkARuzX*Vdk*D=Cq-1F$Q8Kz;Ch>S=k6NS~LRzJ;Jh0E2RA4~8a z%vqGHzeVGs1eC$m8*M-&Q?HF-D#ODd+JA`<%}N2r(@(N7L(86;#x@(Es9|6H*$7Ji z27_wFF2Wyb6~~_!#geYyxk67B-j`^65kBFNwHPLFV3JMOB)nme?k^*xv(kZa>2XFc zaA~ru=7r`z?aojSeX(m32>%xhycNv|yQmdsSF+y59XKwnHK`-F*2(Rm_j2o#P~!Dy zrTr+)%;5Q_a79b#{tI?mR^p`0ey!GVxK`0^=F1g+z9a1&y*j=pgS%Ts!<6pJ3%HIt zN#O z;Ph&_MJCI)ScOvG!8(zC_0BMcW9tw*6u^5K{mIUBG5@aBC;4GcZ~<#|`E|2=Jc+Gn zjU}XIH;NzS-`ntpILgFVFKwySrtU4v(3?W_#6{`tS#%?)QFe8T&9WVy_I^6SuWdPY|axB z7tU?RFN=M^ilk@DZNCpqQ?85srX7lHrG!J#o`6~x3k_IP{SU05%t(f}$HQ_Jzh?3+I~1E-2`FjCvU)?< z^7UZEI26i}>^3}5dTN@|!Sm``wO|5!a#F6D_-BYSkiUc`+VvoMF4gn0xYb&=>o8Q#BJM+H0xLmM8_@j$Iz~)0F1;iO&T_M=>V6nq8`WY zqqKNMOzIxjMOmrUJ1GhHAPtd&cqV;iwRbBO*OdXG=eSOyg}6>2do`5#J|ptv-hubB zjz{BO)_4|dIEb4kaLyZc$g2w|LeI3VeMHbg78ZuZ)U>r^b{+&2@aW~#XljLpfxNFp zb5z$);fQ-rl_gqmgaL%LZUEMqy1pP&|ei}#yYuXZlq zg_T7Jq0#E6y0qdMyDf-~NlJEiv=6$pr&gkzmSoEoe$lIsa6|BHY8C5Ae?!3w-f{V5HA3u)Noi~dy}(_z&@*jPw{8rZ)G%Rts{Pe8^R`g0 zG06;F`-=%$)c!_F_2SbRV^%b+>5QAvG-WztmmP{Nox!1Kzgnd;)?lQ@gt*TN!t^?N zN@Fl6{iZK^pwPWm^sRa*poEhQ@nya_I}}@zp@*^5>`?mRcC=9QVJnQZ?EVa;-UnCZ z%V`vz#7$JXSj0r_?zPJIZC2t~h3PG53Z)JUQ+&qieRe1|VG>Z%WUM}Jhhh^Z4h4>M zRCwrzbnb4y9J@*-t=NolfKH^-8LNa}_2ltCBM62kkN!kTi>3E8v0HI zSXf}vrG}m$`u33;x?)q0sUhNes?^X+2ACQOd2T#uLmcX!7mD&x>rql(XgdfZ@I}NTP+1Tp+u{=E;-RiS>S|YBIWa_X9lYo*YV%=tkViP6~B_?8}AIzC@ z@Ep@IDVrS}i0hX;Vl^d7 zgp?evf*!#=QE3rpQuD z{3`9|t!P?f-p``-iuD~F;T1|9Q;4`ce46@=*~_7?LJlzvxfd!W$zEqrp6 z+TDYq3Mgq}-mC3UY+*_dV|l6_N?+WL#=Lv1Fw(Mnz%ehUQL%56g&oQ*i7lYL!;ZBS&x}()$Z;WY((EgvEj&x4F_!u>m7kA<0 zn!I!J*c3gUtJDi;cdDbHE@KR}(7rRZ(1Ks=%&GhUS7wuZI3AC_L*Rz6R~=Up?70to zW-J2N%GJdRzd=oL9%@|1DQhohXI)~iNl~B8)lXARsSo!`Deptnf}hkBI}~{jxpMsU z0}b|*{em5aHfgZo3tE3X&gh{qd~9rt-JW{j@C%R3ux}3j6G{MTSKVS6qOX3rEngue zl*Fgoz{j$_+W-f$#WId;YG!Hk&)9Uc(QuD3QwR!p6Utd8EUx8 z&YNh;dAC!q*1SoN)$P+(G_84)=b&lIyvb2J6kG7ap~U4)!qx3BwSq9iir(@j1f}1+ zNe>jNTG6*^qkxhoZ}OvdD7H|yhp}9=L+Oj#(Y(peT4AJR_ki;zoJQdZ-9)8}T};&O zUh^jZ$x0lnFnt6~Q|3(`vqP~7lYo*YZ}PwGP;A1)p~U1(=!a$GO;Xt7YgFEZ%^Iiq z_1QXKlK8uxK>j9zWO#n=Ppq_PdPC@b#q%WH93!3{`DdUKmmZ1Ctuia}FL>R^ipVJu zvBIz*S=X#c@idYh*a|O>Wbkn?bFJvJ6iBgwea;P>z+9;~=kw(zJLoKLvr_YI`2wq!ILMN$SYMzKL%j7 zs6RZviAY(Zg-BT-dyOr2Fd}8C&I5^*p99=^q`a5Du}GPHy9^@bQ}Zqa(x+4N$Cq{4 zUFbTffn8cZfC%~ejtE%|dBvmSQ=}rj*3dr?X%sf1Ru_uk*(5xyB_cXLq2L8?zx<+O zgIO_=@rdy=kBW^a+PE#MAuc;2*3|Oq)N4#c%(f--^F+dtkxCHQ~bW)|89q3i;M)6G-LU^9g0nuI21%il7d=G=_k`#4uWgC5APmyzv5wsZMNYd z$9I56T*zSbH;xHXbqzP}dI8Pv&WzKjpZCRuI5oNST4*(VERQbI z>2NiEZ4TbUZfND8_S;-D^E5aEomd`%jsRj4>>A z1<;upoXd2`ozJ%_P3K%5BcT|vSe~DE8wg3bFr2~xe`j$`4EvYv1;QIF(76o@^yh25 z-2_{cM?hN5FVVd(x(}QXsQ(ZV(!?mO;JfEsw00XlLM(JO&!QziuL7xz^8$#Tdja>! zRNM*YL?d6QxDM_y7%d8vb2-Jm>D{|>xe4cdsSH`hAo6p5y@FN$$`ZSIrscC6o<>Jc zms-Aa-o5P{xCGdZa1FpOEOZ8Tz2%ggciCDoFJT_U+`EA}K z^YnD|l`E-fU)NIclzydxj=;YGjYDzor+*mO%;#LpAaCv%&Ev0n>ys|F#<4e(5#|{; zeQb?Nw-k|$rqCOG-5Kf2ZXW2C7J?4$&jz=2)f@v(6lXnwMqMXiuca9pjT5-sCDO>* z;28oEM4+qo8^b*wA*;cn4Th1B+!+>T!|36@NDti^LogX*CM080*&b~aqDZIa(PqUX zdlR~nOw2Cp&_vHJo@*YE_`HLqd~E?WT$l!gsdsi|_GEVLbxxts#J#XsDHkwpgbgsV zn39TYFOset8c2WRr4Ol_8?H}AP;%7vil+FFTA4iV6+xC@xWLKL4qcl-YVtu4@x1Cn zlGb5DaI>9MhNS5pcof^atiD0v5ElnWoiQ>ZQJBec6Kuo@q&2@nm43KyoX#smHQFA} zS1^rnCo75fAtL2wAx@H_-mlWrAtqex3U?wnj}>IETZN#)C0?Owo%drTJ?9{$?r4n~ zvt?*NHF=tc^zo(V9438_r+It|xbqz3WAu&XAlbLeAP4zC$4yCt%tfA9R$5bEx+yPJ z9K^{UV;wok4ia!LCwz(cKrg5NqS8xl3sP0?d^OT2tjE?Q2EnsQerzV@^9o+@p35&c z8N;-g9A(TchiUfUeoqZO&Q|wyMx~~r_m9+TOwMu#txlbk2lzwQkE1SE$Y40{6raT0 zaHX`zaoAdun8RqAGKqPe9f~dQ#G%9_G2=(Br~cclFwHr$r>tf?*nZ1>_~^njgo#=g z*K^CbMO9{7akVEktSVu*h)gH)!5I4BP@dPsn1h(D7Mt6fRbh`c{>!F2y!S0 z$ZS;Af=90}BOMy6^jbo^KZPY&@pNdKiH@g5Uy0GirA1?`r%aE&3Qfpg5@!xd(*{5A zv%+?EPmdyhQ?4QDRjQwNcshZVU%;}*-zFZuOZ=`rn`&yN`tMj9ZIZFgxb@8 zhuJ13yt;d@e1vXkYYe*dAF9ZQ^zNy{#||Gn4Y0_d-h1LWzujK+L%-kuqQmzdcE%NJ z1+vb*X-6M-YP?Tv=)V*9(q%7AY|g3IWSb+W_+8w_>-%R8b-s7EGj1H==sW{8OUS*t zJ0;#~e0v5pz7A9|yaXKh@aQug?t7fuqxgf^9YdM9ghLshncTh8K&#u3rXAya5{dc! zCFK2a8$9RD^tkbgGs$HmJ4dTOJDn-2P|GSB`#B}*xab`+DZ4y~MG~wBVA%k*PBmoX zS#q6g2TS^+ggNOFd3JZB)Sz&;Lf;ILLh@fcG_b}k@>pmP&m#ysd5o3e<+c1K_74gX zF61kP7IGA{Lk-J1Qs4!{Cvf8kE}+LHBc!+UW!gK%7lyDNKnrL{bnzKLba$%mV4l#w zp-=WZFX97f8g8@Iply!hty-mg)^%<_LJJI4EWqR|w>x`rq0e682ryb$%Hh&`bPUZ7 z(*p*~A$w7#ZH^chwOdM>6??wW2Bl~Cd1-=Z;oN?Js?R#0W=453 zY$VtMtBHG7mIz7bX?N}AvV z18g_H{Tbg>rZ=7O3K?ECCTVt;F(#!da&G8uaMNMjVQ!VNRp?7oyRzsf8QJ(@#F*IS z8dwa^)cZjg*GQKuW>^PmR#(8NM(A*94C;7kBv^|X#I%mVl`%dl52cNC{)l>sTjtqd zi216Kc@>fDdze(y*(v;by_6)DDQqGpapy}i5SxY zpFhmC^OvpMgdhky8|+^;3`U_h|62A6_TMAzaSZhf5mM=_QgE$Mv8cJsaYh#8bn1PD z`rsvtTca-Es_?YMbJoH{u%FbBp9K4EO9BG+vt=-@7Lg^NM;0?-oiGil)X07 zQSsItVP#8Z4k_D5X|ZdMw}~1log=d8rPB>+vfQo0^3XF4`{rI?v-{)(aW@DYkX3nX zjX}LP#QQ|O`9fA7D1Hh~2>2v-E;qC|2SCFKFu=Bs*vkn9Ymgma3!FcdlMaDYWL>piWA9Af^}sdNBf*>KN@T;x_)_qvbohoR6)>j0_$b2x*e?J2XNt_ zoVApTYkMC9vcU*SaI?q)qlD+tU~L&LRC|Uho7{5^_H~gYb6NX=BB0jt5#`F7k7TCi%=)Uo&OK*mJxS`MidD-GtinZGPE%D zte$WT<4%uF{f!~1x%Lnx>AI_wUD;r~X8>4FZzLLsbR=DiK5TX(iKN@nZaE}1M`R=E z5L=Ql25q70)k07sD>qTX-mVF|IsB>tW?}%e9q*I2cP)>$(LveByQgzmtD9vuh!o{T z__q6DvGm31KO3yULYUF`n3ck2I*I10CwRs}soLr83u?X33rd0X7D07p2U+UBFtjOAbzRM&;(i--xCK5cW+)A$+aL#L2^%n%^DNK6Npnw~6eQ!i` zyeuB5oanGvEtX0$#1@Z@<{ykSw>rF@P!EF110qvBqL>(SZ#VV$UBV(+Jsz_nnN~f1 zhhd#Oz@%ySf1ur9)Z-2arKX*!9Z}k))(-0O-CYLOsU3Tic31&Vw3TIw}+-fd|$)uFa zp*|yVO^Hs|)zxHl-ZhoQ(5BXs+yN_mo@mB1hhi@(JCpSvGD^RkD$3{-q%4i4qqc0P zQ>_;xB||%LSn)1rGE`V3%ZAiig{(_pH80C5ck3Ow-4WzawYFO#s7erw(mYv&@MPc` z_IoE8>V``mTQo%Hj4{cZZ`BIypsVn)R_3`^_Z~WM{8Yv{(qyMwp=>FaJAUX+lu=Td zWNpc*qo4@&P&ogX$_kdLsY(?F zQFhe362xn$LQ3i<#u=xb@hNv%oG8}+dPgc*GnHN{45e0HS`54DW2D~X(&^%*)WmV` z*A2Z3*2^i9dyG2UwBu^!qOB$KVfc_&(@%*xkw{d@GV0sW!WOBJKM*cXxeGJxTVS8 zBp3Ps+L!llJp|jP2HKR}Jc_-`H+lU}EJY|hP z$pt8RAeLJ3WC6;9)JXaCL^2hu272wcYXM5V7uY66p9_i-4$%UXZ2Uyj_XHH36=ev# z;&9Ze?jsoG3YUmXD`LiSv1^IQ8~f$r0R=d`f?pUg7vtOr$;nFsOL6lZE6>eCil_!R zErlN+?w5N<;s9yo?^^ir$$kMJRe-}{QyZ|@bn)$^1YK&dN|^p|<%Q|EB6ad)XDu_} zr33>kFz|jGGiZOc1<%1?#55eLE%>8LYHq2 zRtiLxR_;~0Xay8W9+M-@12}uDaPpYKz6~ib3@4AN)o_Yd;p8#3N=hDod^IJHks$3W zcZ_ze3Jx_C2Ppmkr;np4?5y?64N^~ z#e90#Im+iNUuMQqC!KuRMxCVgERE!gk3nrh<8Dr7xUTgfMT%s;(&~A*a*vz??wZ)zM!CDA`p-CQE_BNIHT!XkMC6R~ zJ2Sh)mRcnff&D#I(B0*|+2r4~)Yc z&ah{%6D%iV7;%};WT2-+tP_h=!|0E*ibFMn%R{-R#~2I7CLHHhb=?5sc-$;g;Ahm; zaW87qEry#PWJR~rV~RLqyrL0P|4Df?oR7s8z;O0|%9#dnS$nPFR|j_*GsPoYGrH>U>3F{G+v z$2h)mT42O#Ko8KFeL#}kXEQOnQ?UHsDMtm>y-`lNS#O4%4=UJMx46_SVed3qobV|f zkA~#l@SX4JY93e_uC6s_28UHza!V6qa-P{opN6gYrHHLCEY23}%BR|J30m5m4aQTW zrw>PZ>TY!icB0jZB9_|fv_sSn2N1uK#^ewWgu#gD>-3m0^e#3tS~Pa-^5;dPZ;)^(=~1YCZe;5cR`)_DgB>3_M6uv$PZUpp@(f24AIN-@7Cl z_O39GSLgzs4cFOcY^OZ6KliihHxWspU_bZJ=nasRLtY#;6j#M=t_9Yu=n67@ZY6|vs(>^ed~8P ze?r(z@u*E>77W+9-`{3e6;J?YK6>hGyY%!~@~lcPF6}9u;@OnaNgN-4#$@t%a7zO* z1fqq>`9{4uSzW@hX_GCV9u(*3C!7&jI^oLG1@-&qvpw;`qDDQ@vgG3Qr z))=^~4X(rXD7VneVkdVpra0%jhF#|CUPAYb>Mi#; zWT@4|-mUgb=_ND4h&UI&-atF4s6U5- zZSI9KsMyRmTfTg0C>ScbbFBq{+f4i6K}Ea)>8aw$2-s0@X-I@0+wYczoYYBX+rdby zIY0UANgU?X4sHzpn8h`x^#=B3)`}GfF~k64E8wK3Eu8X3FGH-Cc5qYVmq-_06XFRS_GSlt?FV{i6jWi9?+@tjat4! z9d2Pyn6MM~^`k}KZ58X;3sn-Rt?`F-GOiG}$=gNM$$wYPw&edX9&0-(|%VDW1 zP`iy7j1T2Ne9w-`+9Y1f;vji`OxD%(4M2Ps7cD?H-5TAMR|Ly9H1g-wpGQ{(+eqIa zIkg1{Vx^4aMf>>H+hCd9MQucAFdOL!?YPX6Y6RDon$1Oj@6;3?`xzd2X6lUvXw+5o zCPM&kW^rgc*wX|%*5mo)({|kfdTwsa7b4x$6W#sOW%rZ~BauDpHZoh z=vR@6$v)JAR{J2-mF#c+XO<3>7o@!jR|NteFrMohsLV)+)@NIb0I?a^@uSJO8Mp}P zsTXj?Gh{&6h3#Z0U}Yn(m8&GXl{%Y12r)8A5Si*6%(&lN0tO}5Z34zs1-x}O>sIsS zN){PUvbzlHZPYVy+;=PU+@@U&*0{BE?U~kGu%25Js4pb3Tq~BL8_jGp-&k;AoiOEX z15%KzCd@7R!1>B#%}YB<8~tE==qH*{=r>p_1d?Q-O7%J{WNC*_Q0CR|>&Vm=SvMyz zi5|-P!7%;EpN;b=>4bNHCimyj$2+mB)_VtiY@&}*`uHdM_#S=yF=lV?kLcqJecVSM zzfT{ZrjNbU!L#V&>u@i;ui>K=j1V|>2?Whd4B(>--bDuMB7^fNgK?4df06Zlk@b6# z^?8x?_h_qhGo8zPqI8O(ihmfM4yF4Uo@*7J@1buD&wr(F49~3!&j;um!;`!b?*JkD zUHV2CdtauHsrC4{gFc$%1})LY*Eitf8}xB_6F%;tk9Tgy$1l>y&{lkm(g$5Y@139z zx_il^3z}NNCQ~ao>u_Z%Ga;mj{0SL9`5qSt^9g?sVgfCy&ebbQ(b%gyA`jKuBcUEg zBvjUDd#HcRV~u7TR_Ur}U*Z?I6D$&Ik>SPMjZ9F53p^(0;e$v}((^Zg(XVaa3p0as z2@DoQ)rV#gKsqXYv%loSD#^#ju|4AM3BAz{A4?RQvv1f*SyH|#IPoM>xUBHebiR|DTAl+cQ@@b^TkRn;xB=l%D*!3b5 z_%>)B!*w+@%Q#~?mKk%(K3znCyK7{yBF~$FG+cGPJqD*hg0B7Dj`we8;_3l7mBRnxt4W#v4_Zv~8~^|S diff --git a/mddocs/doctrees/connection/db_connection/mysql/sql.doctree b/mddocs/doctrees/connection/db_connection/mysql/sql.doctree deleted file mode 100644 index a3a08368e01d9d82f0a2dd88f133a7a9eea63809..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48866 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 diff --git a/mddocs/doctrees/connection/db_connection/mysql/write.doctree b/mddocs/doctrees/connection/db_connection/mysql/write.doctree deleted file mode 100644 index 66116785e0593a3f07750e1c59e3e967b006fe04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57493 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 W63xzYG*RN#0eF?d0plx5x&IFef@%2x diff --git a/mddocs/doctrees/connection/db_connection/oracle/sql.doctree b/mddocs/doctrees/connection/db_connection/oracle/sql.doctree deleted file mode 100644 index 944b5037504dae01317e573d1a4f33c160dce7d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49041 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 A$p8QV diff --git a/mddocs/doctrees/connection/db_connection/postgres/connection.doctree b/mddocs/doctrees/connection/db_connection/postgres/connection.doctree deleted file mode 100644 index 9f594faefe39a5857ff7a24d3471841bce0e261d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 38526 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 diff --git a/mddocs/doctrees/connection/db_connection/postgres/prerequisites.doctree b/mddocs/doctrees/connection/db_connection/postgres/prerequisites.doctree deleted file mode 100644 index 4b2707dad0f5ae146750d74371b9961f0640c97a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15639 zcmeHO>u(&_b=QLraru&DN^Wb(87O|xvUXRZBtIC-jZD#wDvM-DMRr@W!`Yp?J2RY} zSElhRJN*ub6JN zrs+*qeRA$%IhlvAVrIE!hpeRF-+Z8XO%`NJxDE^KipMlPFc{zz3o)m9v2y8hpnA@+=|APjH+?b zxOD3X#vkQlYN|lS(Xa+-8i~a+FCvdY{?)eY2ThOp<+cZ5yBC_i3EIrZg0(Mh!@$kd zBYwL3bOHG*fM%GWF|OmU%9kPPEmrj*51LmETJ5T!N0n5fT6Z0X)k#CEdQHv!k{eVD z@KwN>NZ>W<$J26%86%`Yvp{+n7Qim5&q^LmsXjw@*fyCOK3VMkS|n@_ka#mjV#&C) z)Rh?t(}@#zF?=l^ZmfU?Lx6KNHe$xGgzXL4z)#&}o)5g01-I4K0<&h?X0Q`Ow!U9) z2H57o05fucjS2#e*FxK7fohu|X=Z37ps+*Kqe3cLxo*C(5)01S=U-QR=G|wWfe3blqe6yz)Y2PEpFrrRT+v>-8WM10Zn$b-!4-Qki@10{SbL zC2yuPIH3ISkEi{AtiAe3Yb?Q(qlcgl^pIlCgbxr2Iuqd!xWI< zjX@B zF-A&yg&7>>0w*FZ?+a(6iu}T;yq9b(5-^Z}S9mE6eDvsD2wMW?IzL>=k*$+y`^y%s zD$k4QOs=vC{8Zw)lUz*ROj{2jjnH`dtLc&7Bq;;YN6i%K1_Z$M}w{>`l}RzY-| z>E-L6Bq%vi*-G(`{dc%(qj0jBr`b7&g-_(6m%KP85z4LiPRDxtYb-cmeT7EPSp)f$uYmc^aZkj^U$;Tjf9zHpN`QD$=leZQlA%!uuN%y z)h*X!@a6-JGA)8a-OTCx9po_%@rgD=*zGjcZS0w+`Ajz;V%j5SH60OiXHkjur?fWZ zN7ngd0rDJszT2^Xkv+3~NC3f)`T-)?QGAvOD3%Z2iEkg!GIQ2<@L#W^P8eO-zla|+ zh>b*wM|WGA=|t8Kun!*vB*9J_DXyag6CcMX+iu&4q=INybZ=>%6TK^@CLw(%B?$O$ z6Yy~u@F|`nz<7u(-d{8^GD?w(V|_h0GEkc}QcP zQ6exA{lO<>Nc?LG(x6EEb2>vHakgMVzIxr?qyxfB>8WUX3t3iJ3@w1I0Htxdxm)od zoG_j;i`<)nd|FMbAuDimlBC{068V=ht4+CRYX$Ikxb;^E407wOHFT0Yp8(e3&UZ3-Fj+lUkkxDspB1); z&X>%jmIJpe!@{z(H5mrk9BGp%s)SjRb5i?+ImOec)83P*>~SDXFQnOSEbieZA@*qY zv}ZzgcVHnSVw2Oqmd+5?`^kbTe~^Q~^x^-{ldVT zaZFF?KgfA2Xa7GLr?Txvp58`iUpW!hqswAj{uPJVkqk?Ar%Imc;Xm z=?o#B&lbeCwtxCv>bN)Ccg-T^ZaT6)Tjr8R{zB#PTm2tb9R)8J_z zt_GYFTo(ID)B=fm>yzB#;|K2-H_0gT^%T#8h(S3=p#o)odb2?e)zgT>V$@S&)a?+L zDBcBNOth=V$1VJ&#c#8^iQ5{5F3-=Ouau;9h#lruXa{B+Y!G+r z!Xn0YLa|*qD7bPhp?Z+wqQpH&Z@^KtkD!) z3}N9wN$$0oo&}aGF|wfUCxmX!*LuH$FUSduz;$(JKHm4|K=cU69?^Ir89&jyIj!M&ApLHqNsbrC7%U2S!E~~LoAM3 zjS_U;g%Un>(U4};w4AGw+}W@5pXr_%<$j#f;UJdoD#S!m5R}7pGY&c=m>lNi6fwD) zw0?>`WOTKETbE(Egy+O6N^z8Dl{Y-30Hs6}8#AQ-?Vv~j_gja7)4yP1ue1><{l~QU zob^wG5B*0mv>SfXE$^a|a-VoINIaP&Kh>J;);Duxe)C!Q=;d+}xTjJUAYNxa9JB1C z@aqO^X{FazuP@zEzOZ)vwZ#j{!s_DnJB!NllCrk&xy9SpmDWx?Ou4fxB$&?T&y-5( z22>NJJf&S4PxiXA`h}&11eKnf~B7lk+0M zI&iiTfrB(T&tc+#Cg<|tLobS<-7p)#EAp?o|p_$|Zy`beha zt0sry>yL4Cm6^>%4RG$Pl&*r1dX(bj)T@0vcRQt^&Qqx|cuOI2&N#XL*NH+{m$LB5 z_9iUP_S`ua6MP8-&8v1UlezlI11Lqee+u)f9dPX*s|=RHd~VPbW|63A?vm&P-e$SW z7YEN>7MJXjw4BRQew4-=v_9cIf~kr`2TM=QgxT%62jDBlBXh z@R7PCg~ohZcFE|bU)~3yNd^r+Ei+0;lIAj_xean7MAV;@3S&euDl3#d;I1p%WR_1B z>UJYznMwHf^zB9XQQ0cBz)XVE?ZATTQGH*xLUyy9y@cE-% zN1=%MZ2*M7 zm`^1s88xXdU*Z$8Mci^P@neVp>9z|GAWl$4jd;+|Ku#jS?Kuj{?`n8t;HUmNA92H= z9R|^oad(N2H5p#}b-s)D2q3)^w$yFP+Tvn*3Rx7hjS}t_*=`>T zJ{Gqm*4$UOkgZc?^*{DYCM3@RKo&UZ^d!S(_Zi4}gqIA|{>z`k`~;l&Kt=IP5c*l` zu+C~>6WdME4LPWY5eP6^Xc+@L>Wok#_)G^hLjX{BsUiWF@L|v>zfkr~d_Pm{RGH_w zo@(IU(ndTgGDK!Qi1>6&`P?eQVwH%W?da)P!uofJBcF=b&0vtT&lEd4CiCNj17y!P zk~jtx>(T)R#dUbWNAMzsKjN{v>*;v+LUk9|#)Q}`@eycz189OGrY;4-7~xZttW&cl zeuQfKC-tz^R#TA#VMT)pY4jY;rUj=(ix76=e%93XS*W`zoOg^m4$X6Yrsih~DS^)3 zr}qXzWT#c#^eND!>i-zZQK>3Wy9pqJ2R)9^J1jbx3{&YQSUeCpP2aEyLIkP@1aD|H zl#oFIw5{~#t;75T=^G@+X+jWfvxVA<+sB{gQ*GAHj0hovvXwTZ9k7#X@L!V08k$k% zv=bPvBdQA{eu_p405DQTtvtd^WET*#uy*;$i&sF;DX&rQSe{z4<)4zvLwPk4*-hqE zOcpf69xw=1dLIP!9fNZOEy8=E=+-fQ7W&pwQ{_yppc;owyZpGngN(|ynnp(SmW2|X ze8O*OP_%m?wAu%u9udFepDJ<#T0+{hbpsXNDDv`ReFK#R2{C&!Y$FV#_gCVrMPdea zAw4c~!VSnkVi!)5p@5a`7$%-6VJ|IKMtWqk89`)PHKs#@cL0IG7!|*qs$jJ8?jB>I+H4Gb@Dskh{2aM##+x09AM@M%LVz zF(!O|vd~2*3ia)IgEiyRE{(LGUjEQ4CpuYBs&qs1`6zY9ZL{%_v}}C@Q(*mwKI%xj zSR44**BM!|{+7o4m_GiTru>jTRuG(8x9MYxJ_7pqh(3NqAK#}TAL1kAW3)Ya*5afR z0K6;q-xYiBTF(#Ra#rlIE7so?YwwD6cg32!V!hqac$^+QEgNeBbo@tf8;12df%a+& zB-Q=IKvFGC3?x+*Sf3%#uF{79m#!&eaOp-!fGZp!>kkO}NI*ZC7w(t?sE3MAV30a) zAsZ&g#eB`Tkm89}C_}5}+Nr*X#+i=Bbo5wZ#WA_7m~^tRK8_Rc=7fmZ+v zTSq(sHNp{GN2V8&2up=Cc61?We-A=q9Rf3M9r3>wt-@-cED!bvlIJ7KK^xOclPOds z!pO+x)FqG)jB;Voi#I}X(Sj74tRz!n=@)lp^UAsI70Tz*%hzlDR`~eAA9i2k;|G6~ zFWLP~;FS-5_75{+*Lco)6Ek|HG=wy1d^mL#lJIf#jq9sRHz&C`& zt-AzDR_BuBhCUtEtT)gja}~)8nXCZ){}3^^4ou9TA2CVg_7c;&hk1SIC_|T+jxf87 zpG@5XDWxR5J;|Gpd!4`ZIhZPsjshvF=iotfd8a5+w{&|5^-g?$DRAI!1i*XcKBSa= zqYq(H`8tdzUZOg4=;qR^%iV-)SPB=UmXD_-5U0<$>EP~1hv(ro>D4PH`(1lc!Ur(`SK-@WJ7pv!shYi@$G-^ zt^26zs$11PJ!8Sf-{)3Ob=`Z;J+FJtx#!;d>eX*wwru$_`p?_!Hp-RSrEH;8s?|%j z+wsc#e)U2ApT*4h2Xpjvst%Xv8yFFTd9vv7fQ4C_^tc*j`{8Im9-q3uD0!A3;!Kx6`HelOLbAN z*{y1}X%|YZvJE_0M|N#|Z+71u**nKs^YKcpWM9gbTXWUnPI>KAr#yrK+cP!>hye>C z0O^eZgvGn78Q%JPk3Ib(VaR#Ig;uLsnQphhSK{N!O34M!ytTz@!FBNv-&fZPbNt)7 zQoEu4m@X90g9_}Om4#}nQ{L{aY&R<%@6Ns$)bWOkg<7dnDsXJP<@a>Td)&6SwpExV zu$}TX-Wnlg8xA(zR<6;kSL?HfI^L>krDk_dtnk(}3SbM6+18#ely_oK8_e-7yl-16-bBhqM|)s+2n3#y~5E=MlhOO|Z*{@&CB@ zd+-iX?8|FN zO)?nSYN&ZNpd*>nXkDqzS~Jc1ob`Y|LaXi4vwZ&EBlNr7%;(u~sgG4>?Mq~9yq%_| zo!51kEy6I*@nKGu_f2vPtCg1BEL1fNy$xNU%2VDt0bT2?q<0oCQN-qHg593BW5teM zdI&F_;eD1L0AE%y6o(+Th1q7IQPz?CxdZiS=YXV%b&v~bX*vte5EtyG_DvvvC~x_8 z$h8hYC=TS^Ruf7!JD;~^>P@RsD>iMi--W6LWh+?J6R1Br3`1a*>b7gu>MhZSRcyO0 zaO!1kyIHZ_QL9=xZ-Y_yojN|5&u4uuE%P=sY$$JSHg^di^+DX-=ODPa7x@fRUR{2S znciq7y*8dn8E+NC$Xn5vhu$~l$3WALty=@ZyH`i5vTibz2cxyqI)_emq+lD& z#`sRNj&CZwTT{sbQ~y`O{E(=vf#|&);x^?C3+}p>Ch9NOsaSrMpuD#^Bt@JvFk6)x zGrMXAEPqfiNr;@*{fG7LgPN2nzu8;qwqU*1;uGd>rkg{*4D3IUibLgh2T1>)b<$JU zLDFAVq<^CPK5w1GoW!D=bjJs#LmFvR{$4yyqa>Vje|c8x zApqKJx-MyQ#7r2st|YZkd|l_W4^-`fYg?@XY(=Hks#}F(vEHt=e1jyWk}*;Umz(!Cg0O`4kogX!gOnNX&l|F3=Eccjj9ZR-ghHw)T-1XD#YSnJ#suj30nhlfA%MksN zgYPh>{p@Y~#?QR&`yYQZrodvo+McVqzN@0-*EvAomq zx>b^@d~q(H9~ILyl*XTy;DY&qfHarRYiWq_^m{x+fwB^}wfQ^)BouHZ{?cgorcg`c z7|H#02p01&cEsVX)HsY+y2)o7pghK)7_reQPEFz{e$yVyr@8Os)Cvu^TyKpDH-Jx0 z&VGOt63xS#ppf5~rWegb{3?+)o@XDUpINZx%e?Z&t~DZBmNT34X&(gqy{&F|SQ?i< z8-f+2Yf9J5`3>}9G8io6CjU9;ojLy#?RuK!BF3UmbXS4dM7^{cOz(d*0eAGT%M&&E za6%Jr4WC^)-ts-A z&(VT>9T#K+6eY{+eJGIS?NJRhao!k$*q=BLp_hKdc?a6{B+k*p(It92#D&P1R-)KoDkQgZ#E4+wI-BW9qQXg?f++n;EkK`;G?b^+~r67AR#>C$~Q zM1km;R=Vl^7bc7#ByH5>SVEIn>88eq6BA?enQS(H#FIv$x=lO5HaCgU9p z${^$Kpr4~<{HAWh9SKW``I9~@i1{<)N32QuQz7X6N&3&CmwqJugJ{>2q?-n%3;Q(? zCL(NFVW;=snlOlv@KKYuBs7T?c53{_gvJr@;)MP7Z~Ux>I2r4Qt$g0fR04@*#5B|M zasgmB4?$E=^EIF$#B&w(qe;it={H?ARYZe~83!jqnpO0t*7i?BZG(~(6*?0%7Sr6A zGz1mfgfUDP+w1`OzsjH)vC@4}Y7$5Ot(^R&nVefJ+jE8P$a~q!cp|T$-=c}UF`w`1 zbjll&&opumg?cd#jD?T{H?4kLi_!mUZv>+zaRmDb-}Fn#DnB(@{<%pkj2z_)PMT=fsAoVE zC2N=O)~FNaYC7vl{oR~ElhEWvO_VW+2NOGCQr=S}mvy7%UFR>s+NH75G+(M6lj-h) zs7KIVv&3sED;}sJc#%l6`$+`KVH?uz>yBM2%r&Yix2_(jS?V#P6o%)*hcMPx$rdb; zz7t3&leO)PpJfus%6UIkIpszDr11DD*(=X>#mh7~Q(~3k1`QwJG30IZ*ZFhP)q3%~ zma{)cNnloQ5SC%o6j6ndbVG)NX9StdsK)SPVO1V;NWQzB~65$in7)b4MQSi}%D>S>y2jUy%i6}#rK{WyjH!uSp zWlJ0r9Fbak?g8HAnlB~Ixh_E=-V0T7O_fhW zm*%Pm+(v;)_0~18_+~dR;OlBshTzdMA(M5NTERfzdj8HQi$%P{N>VBi60$RDiwk;gcDv(wi6&b}olE zVVY3m0x5H0OeS=E&OlwePX!v5KRkk8XpV@tY0gAO#|0WR&ol1UeBtlxz-%7Be z&WG@V^I`hoW|QXiB_a`hP#>Z9z3|6Yumg#*j54wG6K%!>6uVctURI7*0NRY*o<84eFf z7$Yfxw6*I^jfV$9Jk(N2De8KTgj$;o6^SdsW8%8T$5L@AjEb2{WJvCo;F%#gr5loS zFG9Gfsp(9miu!Z1CQQ*mGN#j(TPfMH@VzvXQG_ZdtLzgk8%PMMp83sLI1evw< zE4#!Xq)=shBdsQJk4UC<8ONI`rJcY5VV`bHcvFKo9fFuFrGz7s(v5635e(P*G>qc5 zbaEjEG9nFatPp8%Tm6salY)sqijt_2z(D#GChPJ33Aj!W(148DXo;x>E~GSG!KtND zm>-i?eXLaGWz)&hYz0J*#gQ~zp{A6&16THr~r}%1( z5PbQ!ngTx36LV+^_>gSDrhtc_wadKK%))88M_w!#yCuk5i&>O5)?lk0B~?C6o$7u2 zmTH-|3Y#yBT^(lta})iNb1T>-N9EMoJKj~olhjt5d_#(@ovdAmwe{E#)~JJZT*ymd3!Ou+`vi0cjKTJ>U%d_1?~+=fB3 z$B$!E9Q#4NNecEQV#m0o1nBbxdV=BuO^|^WMI4%45Zg>uy7@ZpSl@T=k)v!)f~gfZ z{XBw3Ok#XTF@B$s@gciHYf@LbQCI=8X8l1ivTnX$Hz_R(_E%;qc1feGcl>av#Qkvp zC2Mc?&g|X;)@i#@#oB>f{mS|?tM!e?->M#lXB%yiaKJrhRKdG1rjeV@%5DcNlbzB+HFx0S-fy86O+HA7+*d_LV# zJhv7pix=117LH)xac7}6&t3ZAyews)f<10w4~k48ei@=F70S2`?`%`yO3u6D5y|qWv0n-y zX3|~mpGEbj4g$GnffCJ1o2VR!CG?6Hf?9yCekFYs zzv{;r(8(vxZxEiahDSWquK0)9lsYGtt07EzclM>E8kf4n2lfx)PCd9?{CcBW3wd}( ztmnDs& z{FKM1IBD>y;Pt)4YrRzKt_7Gz_%i)ASF_na_e2)B*JA$KDTiaEfb$*#AX{C7R?7Je zl!SPn&}unU>?5iZe9Uk%32Ut!ZOSAdBTzv^pP)LQ*W4}r@B__JkALg|vMQR=QoQaOR(QamX+ zuTv88Lsf%H65x+vdp#|Y>w%?r-Ay-9;vFlsv>E{`3f3T(RWDjM-6XW#_>LUZEeV5C zh!d(Kcw56y$?xsVYa2$GRxL#zSb&kNWB5G7%z>!o>(aOdts{$b2IfczX%CI|8}HntwRm6zMt z0a8QRrGF5UvK9%MlBlBptnBnqeprK5tzWd8xoOk}lo&MY71ktN;sF8GdJlqygtK%F zEBkbnK0dJF2LT3=k#-V0Dd>z1)cv=bgC#^5L<|DLj$4_8q$!0q z`o0f;w#ZU0>H$$Ti{4IRQWy{~!KON8+)+#7pEh%4`!CwR$p!I$z ze2ka#1|mjK)Z`;2YVy&Pcdgc409j4u-b$UudL-#&?kx#)j@vo)GibK}nL9NInbSxV zC3RlH&?2PniR30JrS1(<>VlK|)8I{n&E6+5$dD|`M@kmuqbcw99)_Ta=jRB{0>$&Q z2@Hu7&o7|e0>tz5AjC6BwI~VwRKn;YB=i%>O;SqeE2V@==V>Wj%&=5`4+9HHrF^8M zQa+mUt~a8s$>HBo&kK~pzfB-}oE$D&yC69{I|w-p(V~2ub`-a+8#j_}Okr z6mI;FNURYSW(#Z&3A22pgjqhC@>IOJI3w58Aw#$=P#tbXAN_c$`xmAT4-Qlv1f`?& zVI*OI5&CdLa+8$$a7pSzAg&C!frH7r1p4pxT2Et0Au*PZlo-oLIMF81VE|!G0w1An z7bt-bCr~@iAeYc?0gl0o1|fj~B1K8x*@VGGNZ$j=O;Spq@?4`c3QL%}2%FW{Vvr$u zl#i4=%12Y)%_)YU38qK=FHkTqCom#TFyDc83lPjl1|gWy)QXbO#}h^wA)${YH%Td> z7o>gEGEMS2LG z|H%a2#tHp@N4o_G{Xnt^8i}GL{o@Hki;(nRO>Pn^>G3PM?BEq|`4}PTAEBzJzD}iZ z$4}xNq~mB&%2e`A0Pdz&14+DR(G$pt$?v0<`#BJ?uqckIc0Pl3BvA;`!%lzX=C*yosd*k9 z!fcox%s6D6KhzuUSGyP3nrJVZ7W`FIwFY|4x}dFazM+GP*_&`fs8Mj?)cGE|h})b% z@Jx~#Tr&1ICv21OyboTvn-e0K#|rhG4XD--dg5(~WHHK4x_?kyx;MGk zT6_eGc7wF^i}MvNo&D4+M=>B^8+j>4q53EmCdG!AW!`qS+W?jJxkkHIY_+?#)hw51 zpWvH9{sl(lz2%MWoh-+w363VkKR9qdjYvLZ1Y#6P4n-{)`KD}&E^TRQ<0u(H1PW^D zE*t-H9DdfBydTHe1ug~dx?xb*tu1U@=?to81GnU`wH@uI9Ja^<%Y*A%dZN(05&aYt za-NSKq#Q4Q1$SIf$@L12NUt|TIgOvYOhfP7e!Cfp$pFQ2dleNTXd`)_5k?%lohF)f zIY9c|MjXz<7;f+>8m~rEkA!q>fRVK82>x>Yy}DNNJkTc<@Ct z6q7XhP}10rKQTiwNfUm8OJ`a$=>2hXu*TA4z%QgE?{QB+{?!n<9va?bRMvd@1P zLNz#>>w+GfP)x$5hU;uZ_k6RKW6?BqQ`aM^`2Y^11All2MSUd|zY4gijp0OxRw zfL*2Ht0upwjvrD`!0l4l(joVC_1;kNcN_55MKPYgG^@~Nufm}BZAQ9Av}bFxj~mL> z{z{O|D;#HgCfK=bbdHXH%nyncK{6bnfa*ouG1{EBaQxpWownfDQL$69u*HMzQXa;; zg$wmcDR7WO#dX`Z>yuUSWvL)eEydUr=Wp$m^NvL=*nOJLP57D+8PYXbq6uvSQrvW& z)b!vS-M8dRJ{8>QyHj`x;wz;I6DjyR&g{%LTVgrzPRGH@net z0cc)Arcq@kPn~7`#&?0DY3d;falI=Z0cuIAeLYjs?*g|l#5C1r+=-CLD7V;qMl7%R z+^zCeFQ88N4&<#}4Ag)f4*?5WrgY^l#}dReE>U#`?YjK5z_pdy-HDgWPq8^el~_fL zi~HnKA#in^L)3(6gO9A;O30q-23bv(;SGMiK^8hjT|S3(nOua|ep+;gOM*14P7ByF zr&2rr=z1e&ya(Kl*d;hZ^ECmQr|x4jox1Bnl%(yT7X(MnC2C=x=0ih|fa&O^W;E_7 zbeas)ph|>BByx_v)uvW;)+$&p_q<4g_h8P#Tzw~vi$VpBt2<3V1XHh#U@F5yBiegH zL^D!=@$}tn%+Ru1(%5DV6gBLNKN~>lf6t&Au?z8sT1D~ag^{G|cdk%Vh4U#IUx-gQ zqb`C89GGC!j+i$z(*0J5bVfQbE`5g43tSpY)V#p_r`#Fpp)YoA1mQo)z#GvFv5Q)9 zcE#&$)Y<96T9Z0*YaQPnW=U-3@|AcUS}8wDG1GhA8Qj$n*fGqG7K@#f*{^eToJ&@+ zTZKxMACO50*Ig6clg8cehA7>a7x4GgNeVCEA49-`JWA&U{NDub#(4oNhZork*bH_O zHJKN%wVNkOy2CU#3Jry6Le2<;k{IV^tk%d-%D9iqXO8h5jvpZ02uhjG-VWvl{JmZR zErqOQO8%2Jj3RKL0fmwhL26`pR&_`&^xzn8v8N};H;M%^;ocfQ8|&P~7><2U?8pP> z+4Ltn+eZJpRv+UhUBLyc)y3CM;_(=| z=aJoNA>d&LH#onGRTF02{4IR`g|L0EvN`KPLhq%)n$^LtuPsJl?EqHSMxs{Ng0x_> z-D0k-JqqE{=c`nS)nceo&>9*Os|3%aUO^T0({~R`c-{xD;I*|d>mpXxHih5eb5HoO zvbdMz&=hCv#(jy$UOePF>a9yQ)87%;<-_;d9mf&2YW0aslvAkct&*ylW<%rK2 z31qYYpWz%2%2o8z%9qVhOmgKzNi&u|HA6AU6^BB(lYN?JN>6p0I(S}PE7w(EPaY6U zE3R5qiGM)}`9Flv3|jI2L`n_SIgRd@VofE@EXr3@HmrxU5xt@kHo#&%Wh0u9$LFu7 z2)haA@2pR~#PyWdPqyc#abh&iKyvESNCaaM1s4UWqnwZurgar|dNW&5LB^Jyv`42f zBg$o~FWKSffT)kqYHCpt()3_Eh^Om;vcEm#X>XuTQbcH02v|^>(?w`IiQA^Td)Ba; ziLNWu_n=)*d0LXhGu@-0^Q z_X0rZIs6l7A^h`^y#^|MhZdl6XTf=P*Q1Oxp*{1~AH@wHIP4Dl{N+U-foIC}KI+p# zlqm* z4H3EiV1D;nV{?^4a$FiEeV3|#AuId3+B*sDsg+-%OA=-Z z&gfOYL{~%bLA2tEZ62CmqN@~+M>Iwfid2i3ZH2-F8}U?&b2p}BbB@J1h*z9n2g1$- zefv0lJ0!o|gKw9e=dsVj?DGiwJjy=rWuM3BlL)amxsXS-?d5|{gbbu~UBj?5fGo(3 zQCW}x3}y`i)t>_`FaDA*ze2WBr9i$OY7`WGV*o_(Oj;HhzNO(sq3Ve-ef#yqq4K}z+{dyF-1-UW2?>HJM*U|d z2&u$vlNpLBM&(eH?=5p5D=|`SqTFc&q5C8~2u|)gDhZ}BR=azx9KK{Ej!~FeXbSH+ zC`{4mt;fw!Ov2;d9Tpz?A)OoCFUPKy87?+s9HGxvfOsh$pr_gVDw$wgQu@$ZCBS?~@DbXSKwfh!Ev}_>*p8 zR% zIC4D0Lwrh3B89oB)G98C^aC0t2k~F&>!K!+{s|CzP9h1k5V`xvZZ~=Oq^&qX=xTqHip)W#6uZz*gJwe1!(K8itD&*al!Ma$q|OA<$>(RFTHBLXCp< z&*=IPJd@Z*`T`jV?}`p=1AL1LYy&U&fvx^b`6kEZh!-5#D&jdWq+SOZ*gk4R)3~Vr zFq*=HNaLMW%ur0;DTkt*fw;i-q7j7d3ocz?+XIE(Xhh$rkUo@PED{~qzS9iF6pQpQ zmR~SK>5JRp*yPuYFw(Mnz=18NQE+nCQAse3vD)2hVEYv#ag4(BduW<6u>CVL6q7Ld zP|}!=@0+2Rgo#6m2yE#Geaaj>$5iObW{23oHt?&OJl1XsA{d;W`x7ZONatpZ|7i}m2+gp9cUQvPV%^_y0xvYo4c8oen5!jB0fCVKd zU0{1V(YKGl_Bm+RQ(#Msf0_bYU9SA7llYVz*oL{O)G98pJwc=7fvrVf7d5bb2oQP> zYz0~fY<*6<3`iQ zFEm}eCEeHi+9ZT8RVeb7P@|xMH2Ovaujk13H4>ipkt=vfH;#o7LGU$k@ABy>?q}uQ z{JbQ|fZlc$GKW0SJ7#e>RO;d)(pEE--`L`U3awk-196T=S z%SI5oH`!ZgOi=n=^X-8`e`!SDsIfkjG@nX+q=s%ur0GgF}f3 zjp>I4gvKd2cB5SL_2-e}xV`F(xV%cO{wBVsCY0$A(m}1#B|g2FDN%J^3Dd9?;c_p& z6_1=RVBj&4bKEp6g6NBAss+(v_Cyf{P7Tfv@w%xpX{i{RtmKM91<#6>tB8;5K6vcvqQgRQcs|lK;@^3XX+)IXj#an_Wuk z46%NiDtf)CS9&=fwcuw-MGi&W126qR^+17rcfVlQLZ=ki;H9{)iL!yHMjsg&VRzLY zIrf6%lkA&?|9n*dwdd?o1qvX4*;S~LDhlG$ZR{gC*KUFXxl#p(O0{yNULzB&X4~HN zT-NT`au|S}Oq>PX3O{U;et2Ph@TsWC(Dx(xtkJScW2JSp5^A-BWk}~Yu6_Avf3fy)v z_*~2)+{iJ26wFOgDVP8y7CrOw9f*SNC8o-}{4mrg=%pE5Z-QqM{-6T+=L!6kgy;R@ z6`XkqaV{d^vN7}u&$EP{s)=f7OOQE1I`?!$w<4jlj(RnwS9&a|zs`uJF}<<_O;e^< zcAKG?Vk8bFBH#{{)Zc9cp+_IRrB|qMzu)vq4-`6KMBk{EK9n@+l^2_#m?GjH#xiS$ z(igYG>6Nw-Mp||cIK9GY6rAXFR1yqitakUBUU{35I7VT53!0`(uRLXjViG1FN}BY_ zhs{t-!o;COq*v$%ec~KE$9Bl{3Y#^~@_WfuS|#>(HG%wg2+80C(w|tVfjZyByu1|Y zloWmv&!YSh#v7AG2~DyhdGg0-p(Rhm+=yV}FMOvXF?muti<}6y>HCL9xHwz7R&v?# z5Tzz|UpK9bm1@!vP0;xGg&R15fd`U27pAx9+?5$Wd#$&?=TC zm!5V!6a)XJR|Y!&hg$F)iz4!3odL0It2sFkobLR9TA+o1+DCSWDW=c@YN6Bv38;4f zcOFn*OW#;P&Awd$0riV#YzU-FmjGl2aOq#*`m2)z=rAks8${{;^-+Pe1_5!_Vjjx3 z5A4@{fmA{Cb3%=RbF``y${Ue0hRZKO)0E+I%?!ol^KvN3;qohtAaw6? z>B41>yYf-PrF;ph5zFH>`}Wd*em*Wu(D5!Kb&UG`4$kJFFh%c4{zWqsQvl^dNi&v@ znW313i9gGf{Cc)t9VHC+LzKZCLWkqjQruTIybVrqHgotP2H|eSE32(4?Xcrd!FV=XwQMS&H=yk3zPxsuYJ}QW^WQTqr zH9AaH0rmwOt?j0ZNDN)gu%OB<$apJjJs$#SU&8%4ReRJr)hraNwuO5ehD$!m`Ml)b z#J;`x{HS%YT!BPm5QS;CUPUEAb)Ma{({|atR>Q+*%Wc=XXz#iJE&+D4QYrxp(IKY+ zyU;10hdIx|o~v%%a=l$Gf#-DIBi6%x9_y^<{LW~Sb6Yz4ignqvuPdqemwu&!j{N^e zG!BHvpZjrOGoOMnp}f6oG~amjJD+y3)lTSHzb!bwX*Zi}2}`#Skqx@gYkgf6>dS0R z=~fzo4(?BRHzjJ01}BWOnoq-?ly9|#85)ifxcm}n24+$cWsQub-Zx4}GW6^re zNC@t%_05LX!yTa>k{LrV8DmBTV^Y~3Wfa0lr{>Wn$s=a>jWc$l|Rna&mM6*F=z-{4$96pzK1B)?z|%gPByCq^TZwP}{q# zRwWKmVR6_cBO?-q880{91{|eYbE{N6h-=vC$VF7R?RQ=zVQS-!R}$w9M9NLRI0=e6 z9z9*ngp1v5PXyiYYzCp+w|5 zqere>g4I`v87PVm`dr#m@-rH2zr{m*bitWJM=e3iV#VT_QDTujeR%Q($9&;PlU2Og zNJ69exDibu9BDG7`^->G8B!lgnz7tthGG&y4h4aliK|i=+aVT zO;gT$JbOBYF~?+2Bh0ACqP`TZ$f5F&O%yf`j^eNQ1|vC(iUd!khOAh*e$nFj23E`g z6CZuadhm+%yZUUdxNV2H9h_oyUFHt=xHmoUENbL@Mz>H*`^W{7Ok3d4xD~_zJbCEK zMX>H4)G%AU4%2a|14*5JHm025?W@r4+bT_t99Ij%=5#8<0 z>OFW>4%?8#UZxZ#OCb8WbpLwzM!|Ji0F0rCzeBf}!LQ?er!efy1 zyfuqWRZT!dmy`>o%~uL+jctx%Whd00{yWC@PvO;l2gDB>Y$LKwRa1=<@ojwYJ6R&Vt6q)@Zr&CI^6eLd&2mG z*j+=JT*9GbCdc;e*3e2C(uAd*ogy%QxQyiiE1&1QnaF6bSYuo^a?`X_wA&h|s<)h^ zv748oUc>&t19Id?v6g~m1FSBfa;k=;JF*z8J6p+UiJeSOnJ z3Tp)ZLk;UZTfOj~hY+?37%Rie!G=U^UGzn`Sg01;NM+ErIIR8%f%h3cin~=NPo2ee zDx|kF722@H*N?DlK&x%Yhw*v9@9s?9!fc^_L!az-UKj|{H0@TqNjoev?OL^R-nRA} zr*()qtkV>#d#nSv#^?ZX1Q^ZE=XXcb0|v|@dr_esmKYbeB|JTcA+Y%>Yn_!9hcvc1 zY&1g~6tdyx`B9>UwdV-cvUNet4)dJZkhd9D6F15*6Hu+VtmnFu^ef*ld3LGrp-zZ#v@@FuZb1!t5?!Oj1?E+>mT= z(_!3YZl$r6=nGT3xah|j+2~_toNWR1^^OU!m`KsEQ!Ml3$t109E4zyqk6T`|o);gr0(?y>Y zd3EURap9}eTGCJP?uR^D=l3TOZCc>-hrY!A$l^^1K0&9v!wZI?DD?LC=9aPloL>)- zN+p@RYkd_9o69_BWKm3~&S$9)Ug)?b{IY`S{8k85FiK2!R^X=@XM!#D_%iOg`_H3Y zPiF;EFt5%E>>d^cvaeK9L!0i2luRg>XSGp>39Pahack9n&`4eWD(`a08=~7gaS5Q^ z?3P34E$~tVGrVkXVVh9OUYn_?_B#XITg>U+?39+{%+HHR`=N+9&GG7c%-l(Nl0tz$dtKrJ=<+02&T& zH!nB#a%{p%WCz%~>3hHt#j1^xRgp5XKSD=$nP{6|8f3%knS5>!{>!*snguRS1_=34<@hK~5* zZkzL@?w1`|cnr*0FC8W4DdHjby9d#`vS{z?NjNmOY7YkgB$E@PJUQo8(;DwFQdpNk zn%=+2GZs?S&L%IY^*%Fb*oB9N%FtwkD@_dZ?!%AEdW6 z>#{thaoHSN1LR1_Qg`Px^~(**IU?s<|l4+<4!Y`qVrtvi2WKu$UyR zLCv^u0g+djG?Z%@p^KGwoWD z9h((ABuhU<>-4=xPM*nH$6M^IER-$f^C$1U6J?ZCCRv-e>L@5eJyZdeRDmu>{W2&j za=g@N`p3#k!DlCrOYrbit6A$ow~c@2Ew_!LNbwuWnu@2nkn^CVkoq7X)#RH$B;HIoh~k=CT5(sYkKFc6H_Gj7*)3EZ^#GZF4`(G zADR#OL;5K;ZVyG3ETjH|6dp^kIOQ(P?B8LnoL|xK?Jh`p$zmD{Milbo4=x0XM!Ml^ zf~3=Y=F~+f+o=!EneJ*06@Xn60u^+G(`8q-p%0TE7w1A-Xs5f-^=T(Q!n|C5%IUEP zx3I-ptI3|)uT`9c`fVN`Ri`EdzuSS?g3({6;BkP9Hd5a1%gN>gH=zh z{U#Qmoa_a*PSNLrqJ%@Z03{ba5%oO*g=a+(0xvoou5uXVqZp-WNBRItL?$FLBe|GZ zA~M%67mrB5!4>@CfVmjuMhH$W2`tIYpICfu-Ybc!ann%v@uq&ccRUJ^QvSrkk9YSA z_=E%;6r0+B#U{bG6B4x4V5Km9VDW|Nq$G9xWM?ch;H3luEHH51g*0w|wFS??VEkz~ zP+Rc3OXHL;jYH0*Qq9IceuE>|d165<(|o!whm4R`VzkyzeF!~YiA0Fj@_&U|23_H( zfQ4W&=kkf@2kE3v6UY3WSzcPD@=h0tzLM$&uy(oVisn zc}!v7w^9U)!Q?Tu`im5;g2`iQ6_-5z*a}J>BSG3%?ilUb)wVom@kcm)98P&Z{W;^5 z7)AM!B2nCxhgx`cI3-pIZc1cIIBhkZMXA@c>DG5>^4)7{qi5;9!V<12c@>T)x%5cc z8AO&fB_{E*EU_6xMl~fii835YqxK`ep(G`ycV>$D^saK0*YA*-u$6d0oAGa?UwTSW zUWXQ@R2MB-22+#|WB%B|S|y|?bBnBDj4b48-LpVI8UgPN5wM>`54>|FpyA0*9gRIE>3CLPw$BnceGesg*L}nctHI-3jka#s97( zCv&aIDdMM=I}f4;Kk~rxThQ7hI&lYnTN0``5#TkZZuo)U8~g*m3q-_@JnkC=3?KLH zAK}mjJBbzWodRbxtUJc96mf;&RSy(lj!6`g&Tb_c@Xu8cj8`7x>jF#L$4}GEkR_ad zPUnB^+e^oW6{=OcYB}|3*P5QU!UqZBwS2x(@{dYF9UU)eyn0qYU1~Aanb0`3 z1op-NWI?PUiH+9FqoH2ngC07|A06e&cz)y{OQe>j|9%V!`XDA=TgNj7Mf%trtM;Z&RIjXe3EUMI(hh*#{)q zwKt=~yM31b8|A2=x;M&6H_L^b4=UJM)0l6Sv3Hs*PVkhDCjxS>`Ofzxng>>ft7^@e z!C{q_+|optoICpHQ?nJX3)u?I;%vsQe5wr>poPs@uRYazdQ+&UWUE84W35gYvD8+l z6QF)DfcTGTOb+qDHy9y(ofy%Eo?uhdp{&dhD3xY={elLAHW=VM+WcOA^kx1zd8zK+ zj}ppK&;G-b=-E30oYwU0M50b$4iW2b{vpAMbNgNWb*gE|uZJ{5)3qLE_fLm?qwq*CkIlL6`n_3RhY=oxqrr)Fs$BT4N&$klB61?=l zzo)0$yZ@l+dJ>wYNBQn$ahAL|q%A1%J>Ju^>>1*op5^q7-P6Oq4dR}jAZGe%g*Kco ztO^E=!AAaYc46q=rpA1!fRlrZ%(t!vw+uT6hk^elIh@UMIP6=$!}$pHR^6_ z7TcA^+Abo9vC=gCiem!Hr)+V+plZVJ(-ci`kTRVJEt_~7#V^=hT_}N8A*5DnNUido zfqhbTv6aJ~Xu7PkXqR%+^SK(X&+PnU$GHi#EZ^&TJ0jnzl<-dZ6kgEBCJ+{Iy)|yV z-7MlRmj}G<;^rNtS9b7Gk{EZ#wQeqvdhXt~_mgnMM^F43Id z*QB>WJgMS9MH~sV4!%eWlyE!VR@_U_ZdIyo7R1GY-!*JEU-J;%ORDzXm+s!Ke z+SsZWbJK<5dHNNotOm}_`Hr`32HntMy4sIVcpF`ip3=IP=+0N1Z?uWdgKE(ENOT~# ziw>{jPwXl?qT9{Uf#&k)BEa(-_$MEMEKR#nZ?wYSy2Q~?!R zg;v`YFRk`gm+a~GEWmA|jqw!3qF=i(hbKc|N71Gs5q@mrTMlwkr)z0D-cY+WGxqE; zoa)r^b_RdU;ab#s6B{&Zr7A=jVt}y~apcoxPI;}D)vT9}cYWxWP#4Z=h@-cO_p5La zt#<~02sQS$&=_!QoLdHosu)wp+gQcz4DG@!g$lTA$Xj98I^I=HyI60QawWR~$pILg zat=DI!8ubi7$zu!N!}NT@)~awdBi#O$Xfxm@HUp(bB&x7Nf4GjpyTJ8wL+CT+{~UZ zVJGgZM-A6*m+HAob0kn(pm}xI&gm_glt5=MRNP97iR@+^u*I&X#Xx*4*9ujE+HJsK zTqp+ueRgKnM*p=OPL${8WnDwx0K~O%;{tTkuF>6kC9r&bvv5)Vd1AS@mGljgQ=5e# zRx8M2bWU#B1ru#Ilo6r9Y@jE!^)g4Q;ayv9wHoe$@o_wMvpfXN)|<1?sH^Esh5+8o z;q-Q}rv-Mb!}GCQ_dW~s+|-;YhPtOGs{7kS_mmbRkv(rWvlY8F!(hN5jF($;RR9~p zdHB$xQmfoKvCg|5GC7x%$|64z;B0Kz$*Jm0GC+-Du@nh32dc>x8*)E0BU@ zwP0@12M$^$YhK=7-r#!M0zc7=LchUcA)+J;Rj$`zA#e09`Lhh4l1@1m&GOgN$1AYC)|sb|KctVZ(#Hoec{{&IAJ3wXef04@`gkvWTuaTa zrjOr-cj0^vA8l`lfUr9tXg*?Ko?uWeGYFSi@0VH6msziuS&x@lZdNNg7l3)ArmLw<80TT@b@4L(3QFAdR3|udv$y0p?rHN(BqJR${KA8^pDx9+3LXd zTpjMq{{>D13%uH7UNJ)>@l)kujVXBTQRE}(`P;zQ*L6@d>vjl;3p+5wWPDbD4 z&bzQZ;<0v0kAKyKT3N9ltZ|13IaXL_eg_fLO?DeNsW;VDGuzHCugDIc#dJ#UiG^Eg zk!HmN19RM*w_5h4mdw*uTDT0|ty{RIx7m_Mq|PoHGS z3)A9@x7znemiwnWdaI64KK~RqT6X8ePU$AHwHyi)evOder%WRW|1ta(+-0xjO@8bd z@&`RL2;1vz?rrTq*G-sa#!RJN8xOuIg;3;-5PEoa9cnFZWZ4 zeV3H$lKcH$|4jD`b{n(clBQi&6`|Rg?tZV|d;R*o*RQ*OV&MHRd};&xFWC__ylQ>9 zWIIkhblj+&Y<0p)tLE0@_WRoV-`W1L_Czw=v@b?st66c|$p)0Dc-6pZy7l%PK8{iO zxEe$y(ZEenYaxoOFs5M8+w%sJ50pyQJEB&j5jNwp7l!AfcUaY$z2wf! zSY8}AqM7OGYHg_$c~v)XqEa=SUa*}dclx!54^D}Ccw{xF7J`;LV&T7;*lsSlv1%f$ zyKzuzy0#O0F7RXxx%KITr9-bT-80RqPgm=XyIk_(S}@Y~hUVJdFgolmy67M}EQtW5 zw`UNRWd|7EdbxLf?|Z_KOGfNCZdMmsG5AV+++1}c@GKds1a=hRC7uWBc8x!6by^Me zW5KSR2NhVE&2|vCz1_*?R;2 zx`^z8{FfVH6fZU1XexSpFvG>EYmWxdqpH(RM)gVz%j1ANK#;v-_&1%Lf>_tw=?J{H zo6~WvG0pluP2xXY3F~#YLZfloStx72WV6%2r3Biy8RkO~FRY>N4bm8yK(_`k#)HfN znS2T(ty*8Q7Mo$sIwQJgwIX^qKQCXD0Aqfhb(q@Oa?V{QWs~eNRd2t$&6E*(`YiA1 ztaoUZV;NLqw`m6&mdVbJ;JmqHRL4SV)X^L5E?UIz1;U}V;C7WiD)cl8o#kzMXTYK@ z4Ao)Cuf5c?8=j8r+w#a-b`XRYBddX7O0=uAq8JoC`{d)++gomPWzxc+ww%z7ta=z* zLN8g>dZp=->b3(biklc|OI0^22})PEHishXnK&=@Mw}PW*STc2&%ICr@PdwxnGMSr zn?(LW$qp_t7wu*pgw>f)Iup2dNKJH5g`F1K>0&! zjan%#RQMN6{iBvYAX?t4$<{xX0+GCYHpT#?(P&j8%UyP1CAkhH%Q|p?ERv~Y}w0w#J*TRe=_u(H}vdTZ^qa)7#NBX2pu@v@mg47pt*nojm#Y^vNe4 z$A}m3A^?f1jypL5t7iqx)UAqV*Oy!ildnw}06>%dSb)TH{h&no`wjC6=$c!)5?d4e zujztM^D=Z~m{kH}S>EtTc`g}I4Aa{Crrut5Amzu)s}PZ|F|GJho%#LhPkiuda$M=s z3{i^d(lK=AD@6y#s5UsBl7mAU5zojM$=KNyOk2xxDwBlIzfSS?2R3flP@Mjljr~n& z4K^ddg4AUGp40VUg-x!QG5NT{;IS4%nuysTTQz2B zl3HrF)+;uzZChvsfg6{DDl9l{Qb=vKVfs>r-l=JyliOeY(7V6=2mc5r+SyZ&pE_~Q zD#1K`=&93B(1Wnl6s`ONge)@x=aL~&RSgS&GS?SR86GzoABfZnvQfE9iB&CeGDHkaIPE~ruc*&MvJsZJOcrTR@E;S;R9v0a_& zRHYl$kG=ZK36o*d-5MZi~YFo=dh+ zOB!J>IUeou_{w#_! z7oA5%lmUtu97W_(*L`F}dJ)#q^d(bZBhDgT$*<(mPp}Z&zJ2@H|G9o+j})1!I+N-1 zd3QzqIC1RkxrqxlB6y@5V?+Y>TkNMQ(rQr5$SK<~Yl7ybv@J4?OSP5QUI=&y@R>(W zJ#{Mm3{b#woJz$Ab1sduFTjzv9yTZv5*(&PsY6J zk{94#Tt@twLbar{{)omjS6a7eLp-Cjwz^WCD0QX3Gbk$35e7xI<0-ci*5JKj?9mvJ z`fAmCrMCVuRYFBb)OlfJ!jnj;YceaOTFp7y;W_(pRTY`nc`%ki z3c4t=f1TD~$j$&+Wh8v;(Rviaskg$#G-#LFx(75{+Ja1AX(25H2BJ&66BdiUD=Qpt z4^7gy!;{<)FO8h@5KBO}TNQXIc_{|6sUk% zFVgHGQLf84%v(qKK~Cewpm(e@_KJn23xv&}b}WeQ{QN}^YjLpNnO(rRL?NGDt4eAd zxA%!rao zDJ_R$ST}4TV93?bLWtm_K^H}sJi2J2NefGXR*hI4P{^|e&=*__&@oTeY^=MIA|?IC zmODx#r=o^isUjY_Lh(_2Kp4ixKel!YRTz>+6?R&Ls7+JO3!niHx~5#0ok#_#)EhZ9 zIe9*1)G9rB=luMdRG0F1#~LLyh?N|~wI!2qWRkf(!(^>ZLoc8$yUBt?{>IJ)wDw!; z$N3K>v%ViCP9uSV#0AvRd)80Dt&)HSWXuXlP8-KH#gv^k8urRmsiuDi&JD=`(`1^H&O_u~mza|w%#cmQs*%G%-omG;QN3-Ukv1e-kkwOJ)$jyhj?tHL zR7LtljO8S)OmdxciIqnz^yP+R2die+5jJDKfnM+a=a1Y;PW+5D-dJ&LOyU)*hPEH26UrNCxAMBEv!4ou87^Yip zhKN^`8Y?rD%>n*n-p53l595?F>#U_TN+aucV(y{GHZnmR<=k}~mXo8_5R!wvLXpJp zy`tjP|4xb5$!C~Iwn2KQ;8HPUlkJ2%%WY!6Gpp8F-uy$JH~!z#8%cy=MuZ#%{I5_E z$S91@PUAyiJ+|58=}aVB32Tn>R)Vw^wxiVEVP8p-hOMR*kN3)<4syU0DKf=?Au_fI z&v3zq*PTrPNCq*eTS)#%cybYpN|{%*hU_5}!Wh){29vFeSgm4xXQWL=%5hjJlN}ee z{c-e}z5Z#W2CxsRB#o{=BW6segaCQIKyTn*YGMqu$dk~Nf?Ro2uKQg=$6L(AT&&Wn z`wBB5Wx5S%S;6;Wg_R^6HcCQH&5(j{_~Yk1#JVh@VR+^br5TV1 z#|eu>gs{F?U1}jOi6Tmj#5^s6)R;!>5lU+ak>J#HgB7OYgjX|88BxN@r}s6N@*1j9 zP2@OXV7~Ke;DN<@115_5$9mAx=MOWSLEf8Gn7!2 ze$Ghg3I8;{Cj&@w2I(@>0#pelGx={#8<0G>5%N8k45u{IEC^M2mEVVjyOgLJvE?G~ zT(Y~vif#pUSx?vRV=KCDA5s_9u0#W@nIY57M@!Qn@c=f z7?gLTm{NkLD7rMov$LZq-&v@zg!u>N%SbY_t3gPic*q>-|6270H&y2=BJ&NptAxZi zu6snFJA#rAfDMIlkOwzEUqKppZ~`f5^YeT2dguqXj}*(is|W+NQTNYt;^gg073wor zh|f|TU$9}>-!p}(}Q}z?p zlc|N3DK?_CsJZ$|!%u$Ivt?<+g8y#`PvCrd7G?^wo7Uxlrjv$khcg~V<+RUaBQ|Gg({XgU>=GM8-J ztF@EIru(eLXQ@}>y&2q~nz`s0GB8>EZL&Q_Tl znwiJ*8&Yffgvf1Zx=ZCWzoBz(!|J?-jyVl{%feZjSGd$1-Nzz=Sl*Yp4#^KdPxurM zZtd8zMrJ^o0QTAX=~#012Ysk=Ue&qx0AD%-(H9Q56;GIoA@YV$x{Ew zJX8Pkt4K59e^Gmv+$ntpJk$^ldF`~)&959M7N+Y!Rt9Fd@_ai{WwOpW3G~8B-y!oB=H#O>JQ2va?}2I@`on; zn*qVU1%EQmdRE(N#coB(w$q2+KksONmQOP2q#^z{OS5xc92aF&x zE;Mb37Mrt@O^p?74Z{oOR{Im+Qf3ntgTgj!$%rcQ-iEPeA!5cXw2S1pl^GrhW>H?~ z9qiQVi9)|=M85!q{AIKtiIi-7hcqPdq?r9L%us#`pI2#y-nsoJW+*28)0Nv>W-(1@ zJ^8W`MmKi*A4N6A?qoC81Nde=x#^C>xjcqa~>UaYAV81oz}wxn2Sy#1Y#I!0;w z4ys~QXQk$Xf;w2px*wA7>>RfWAnpcu?vaqDk z{6x!h^L<`;?_eLZgCzW80yPA!)ShDdQMQ?w<%?4ev`EqBu~%fefr>bLq`6|z9t%o} zWqO9hN{Hhk|T(Q7Y zqsCJ?Asa#u^ga7Byw0g9>rvWLAXdF)L;<@1#;eZwB&SL?Q1^yOON-HA2La{UW@B_S zBWU*&JCXoO;*eCnNThxAB(yV~-g1bRa}M7#qBg4XT?}k3&f-a*P$3T-Mag5cC$YoJ zI$|BZH`DOpP7NQvms-B(V1brVG;R8xLz$*u->K<)7}EC@K^jHVrtiyQ{f16W-^Z}V zmaPJ4qpWNj)Ik-({9za49p|!C=!_9cP)^A|+quaRPUBUq2gN58f34S96SGr?_FD(& z#1|xe?;j!9t7;uNs)+#M^9;+Oo}cHnpgpBh z#<%%-Icfh;Xn!cQr&f#KwJNmSBEUrRVW?u$#y;gK_+LPX{t^$=Z^qJi+y67X(D&@g zi64i^_@v@Z(Lo&H6P2Cn1sr6v6X7W2;zGq^6K@ zRn|X_E`-jC_JZcC^O=bX=iO)qjlU1>g7gv0>k1-4!ni(>a^$mxIYJnUwFhZusaf@P zChAkoDHvSTA>s7MXm@r3pWsoi;WP@5U7IpXxy&QbnMh?aD57Ql*N9C+2%7L{+1KeO zOZwD**TX5E|BaK4>Y;obldq>J*Lwx59~B?v?_DTC9LDb`EwrN^%6GCHwM6hDH;!TK zuv8lVb1*NMM=zEd#c>krUIFosf`rK&j1jd067_xUd7OKUlnPhc8O##XSs9JDwy35yK%uu8(KilE0VNxb?!34Wn4$EE+b@`* ztc%4_|4(6fthuJ~&}^%= zfL-v2xccD&Vj?*Dkpd!V&NPBobQ&bvoz8<{!rG>TH4%4ViyAT>~% z`wSIdZ5tzjp!T4qkvMxL;Hvsipi+%p)bE< zhO#bh=RLGf8DSJHxihSWo2GX^`jQ@LkdzN*P%*VZh8+1Xi_?&2b)I_XcL z|5w8|Tgx)yj@fI`+0KqxW@?pQ+1)5k#;fqkuSk*0$C7t9HKA|5Szhz%*_0YX%&%H!4|=>Mv`XA zoLMhec0xc0z*>NEDkm{&!c1=Nh96I~NjJjz_fZ?%s4->RzbOYQE1VPF@A1_w97sy1 zeXQOfPnkIP5ED%&pVFO6a5UTAXEM|E3DjGaKD-TOJKg4y%@b#0mdg|O{&k0xMhF*O z^N~E?Z$~#{JaR9W*v2xFhA3l#gheTVJWJI8&d)1^G13YBXNWVr;RE!@jxV`V?(+g5 z6kFq_(JI_$iIzgPsQWAjcVFD+72wX@=NIUSxzFtBT5z9Pn#?uqK5O`0Dfc-8;tIRZ z?-L_M3Ra=~{$I*f%IZL)g+=gscAtMHg?BBx&l$FLa-TB=g!`<&Q_AnR(+*#@`>bf^ zze26{(|!Jo5ly4}{7FqFclgHCTU&hQ(*2V3-`@F{pqbR%k>^^fEbvJW5 z`Z@(hcdzdAjFC7-VY(Mp3%bvbn4y@2NkA#$K0j@SViG0}rGxuSA1=dvF2FJ6AhX$_ ztNWb!swR(m4#Dg=)up?*NGY-O-wS2)y>g%R*^;}@??Pw0xX*fF=|2A?ifit(oDt!Z zB|?X;=|2DHSvThQ1@LPovz{~gsTucqo!;`%%Yvf$QSZ%3gPIJByT)y5V{O;?136Gx z!7Jt(|2#2h9j@`OqMn+H#m?HUuCad(-1bR!{I}8*JB{?Jdc(SK3a6Dg)y*5ub5V`Q zZr<=G(3)uYJ@j}*z2QFsgr2=&i59$JfoxH4SdLX`iDaq4^NR~FCVvUIb8q;|^u)Yj z_H+$+!$>m5rNy}UovwW^r`N3YeO>vvKCCNuMztS4^8OA>%-x_}VR!jXrwSIAxd zZmv>RFB&Z>g4eUV{9jXe$!}k~tIgdw*uitYv0GU_Cw2R*OrmWgS(`1(fu0hE6>&|%Ad}jhVmvO2;Btj&7USH>rEK$ zfkICh(KilQ0i}pP{k$29$pz}6FI6*?b#XiIPhT{`D9Y|W`_r68S<^sArK9sxV08ED zPru(t9HTJ(9I6)dr$1tbViG0+rHDWMJ7y>*Vd79a_|x>^GW_WR9J?c3ju!LCqp+sV z$~$Y-!t|Qu)P(YHa!6+@|On8B}xkKKc1#og^cN54C}+ubH%>3RPJYHFUh zoIBxp|CKOT*7UqTcx(~(b6GA9R%Q#)3$*=Pu#^@M`L0gd5l>6jO}B=lovo9{*`ECc z7wg*WreeO_Ei;eFU&j%!bQK^!<5QfmjOB6L;^%I%^OtqoyNOR{9-pRD_Sto?`_l~N zYZyZ%yWc7-yJBTEp|xn*+m2*_zTA*f>SoXJ)!SDEf^de_+4+ax3T87tfpxg#EO2q5 zW~|tcA&ooDsxPpE=Wtew2TUIT0 z*D!)h5-gpuuE1ta!AO3xlLEw{w(_RoQ)tl$7PVB;*ExJ-Hd`;NxVmOFkwt9NSlbCUD{O2E}%Q+@=VI zx^;G4N6re77|!zM3Z?e%Tca;o%SYrUNRdG_Mj*S_MaHD7!!rM+DSdx>2RyC()kiLl7o|VLxIo>9r=>F|;`#S2*Ny z7IL85hlN5e&*v&-wWrbOCU_>v&yF6k7yEPyFZqpYH{_CIT!%=@_FNGjV9C8zlT;(^ z_GRZOC<^)^wQ7v3U|qeJjo9xpqG^n)yojm=<0?OEhGKG#IFwFtm295Ehm9a~x1+bX z%H`xK^gy9c7|}P5N&%%vT;k-F=R$a2jPNc^#EC z^kdi2-D_OsAB@B?3e$H{wP0Lj>u7fN$xS3CVG>Y^#8qxILoo>xhteUgLLc;bv;Q2s zDUGYJS>qhHi&Q+N>vuJQl#Cr;pIE7m`fmXZu0|YXZ7Yd~PmZIzox>-&DON^JPN0Go zHIXwT+=#~od)JJbIJm`+PM8xLu_GLGT6cH?gVU_y;x3$f7zEZu8`s-W?!S%0@8Ih( z?;5VQZ~{vsKjJn++!BaO#3;N%_k~ARJ+uPcQ5C1I_SFHVPzD-*)Ny^bo(qYZJ@ZjayXo;3r4b#0C(ds#Td?Dbmon*lj)&skbG`D*lgXdZJZU+m`{GHz2;8|R{p0k+JZbiH4S3SeF1irNh%UG5w-^0* zAH3)kyMB1kdpcwiNkWcV(RB6Zh5_8C`iih*OdApI~zlZ)4;3tWM}* zc>f>H=BzMvzMf-;8H&ks5>Sft-H{_Gthpmn?Z6%RIYF^C-I04AqN(4SwVL5YaXdPVRk3tiX*DC- zeoSXZ)oBSA)@+kb(n)RC^;TEBJzT{lRV;kkah4BAw|-Gv7QP8oOMjptf*zW-Wz z4dez?>x)6ltygeEh1-P3Rm$I=jT)I9X)h#eje8bcvT2AI$^0Ag-K%E-yowt7gT$&eb}dorV=~!DF5EoZl1LTKV0@=qp!ZixyW>;jC9G z=(y{7RL%(czxqL7GoODko4kKjZ`c6_-(#`Wc7!x@%+qcq*_oGaDI%k~&}(hIkZUVz zO5~L(oqa&i!Tq^p@0ue=gOkTuO{RG>N+@lqhUnb*RRTAEl}6-T@;U(tBCzJ@*Sh=j zIkIXj+BQF*DhbJ*A@N3Q;TLl)tj!pL$rv*!8B<92D5a1`x-gH%QXZMB&^5ib}sHupe_@|9bp72kDEOdhbBgg8aNL~gJ|1fPrlGb8EaJ!jQnxv@~ zxJTQ&tbQ@Yp_5PCNr~iPc9)xEG{&7x5iVk($(SA1kHbO^`+tza)cW0hkoX@YQjQ66 zk`(oSiQWz{;cAmzOa$kCgKTxD5LDR3S18-(JLpNzF-kdh)QH+=irP@$M7EDK^!pN{ z92%1`%B}RoVwCKuA2G`ALZNl%3-aOyabrWal1N@+*RrZRD(EV(AUmA+xuiFw-MKOC z4trW}cU~dvR$+!KF!*)!xEmK4Ry08LS9jsn-E$<5-jm*8?<^Nc?+lFchjk`TBoo4# zoFb!psu4!>y1itCgYR?2l)=j|VB`x79kcQQ(D>R77kpp17K1@taToF!abEFQL+6}Ro3 zNp?%cK>O=(#XEI9LL(E&Kpe()(0*(~G9t_1vY5vr%wT~|)+p#bh(_zHRB>7YGfz&AJ^Br*^P6OxTJ0>Qy6hTTWPBu8~0P z#K;RH7n@O4O(rEs_q|Y!s$67uAOo0rzUYX+a!3vp47*WuCc@z07lcDs@@-A~V*2y3jma(=Z;+h&5(JSgM7N*VIgS&g-3IcHg%V+s zNN;Gn02>;~&0ZWgqM7MwypBqZmDmgGrLeh#5p@HVWC&mom9Y#C_QYVvR=l5j?ZG#I zo@33$O0Ic&qnf{4Hcy)iNMz5u%~I8k7a0sFglR9X1pqb-qXeVKiM{q?Ta()$leKa> zIMWW4qXtRHrDSKcg1au4%k0Y1>XHZf-4cW(`lAtUDvir;Z(&qNAk_8jH~(1eB}{Bj8Cqxp}a7JhV*tv1Z6S(PRH<79oEcgOsXgC2vGJiM0r=-*VI8F9<(Vv$Q+wy;% z{(P1Ge1-n}7UpoDuAlVpp+AS|&qwLcuhE}7sM;R-^A*^R{ulA5l?)RQcE=RWM-0qM z49X=2;Sy{85^MPqYxNRq@e*t85^L$DmUlbqVn#g6P{aQiUYitN^u+M`Iz2JG{+gZ` zUT-Ai7+z$9_;(O;U!o_%(El9$p}U*?lk|sfJMify9d@>+PbW6BV?um7?8K+dOg?SX zV(B<5xK9gMtz^3{@tiI2&X{_jQO4hpVw2@Kv-LN;98?EJNNphu(m}+E?#{hT zOAlvS9F|a7rRy^7V+L-XgET%*;dyx6qS}6U0PX8EL diff --git a/mddocs/doctrees/connection/db_connection/postgres/types.doctree b/mddocs/doctrees/connection/db_connection/postgres/types.doctree deleted file mode 100644 index aca6b7a332f8fb1628bc2938b88b671d1d63069c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 113678 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 X1DO%%UtP#$Ljdl@syI@u4yFGeaL%bY diff --git a/mddocs/doctrees/connection/db_connection/postgres/write.doctree b/mddocs/doctrees/connection/db_connection/postgres/write.doctree deleted file mode 100644 index 6466a1cafafd18d01fbcb5820de48f0f18d50f95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57630 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 T=_@3(@iO&L!kP2AYX1KN0g^|A diff --git a/mddocs/doctrees/connection/db_connection/teradata/execute.doctree b/mddocs/doctrees/connection/db_connection/teradata/execute.doctree deleted file mode 100644 index d0ef0fba86ef501b4bc64e2464f0ed0a70d1fc8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50051 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 Do!yE< diff --git a/mddocs/doctrees/connection/db_connection/teradata/index.doctree b/mddocs/doctrees/connection/db_connection/teradata/index.doctree deleted file mode 100644 index f043366129fca97bd4e578d816cb0c5d96d7aecf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4805 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 E4~gkyWB>pF diff --git a/mddocs/doctrees/connection/db_connection/teradata/sql.doctree b/mddocs/doctrees/connection/db_connection/teradata/sql.doctree deleted file mode 100644 index b73e74ee29dca5c4fa3f487a54bb555e5e9fdf8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49004 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 diff --git a/mddocs/doctrees/connection/file_connection/ftps.doctree b/mddocs/doctrees/connection/file_connection/ftps.doctree deleted file mode 100644 index a01a5b209fad850cdcb5a9406ba467b910bb227c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 201889 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%0DOC_YmXnU+y|A78N=MXpGymVs2&M4NUJWSTLJ z@dGTeOdn`R|6&KPHB|^K%(x5NU{k2?aj>|WeJY+?9(2mtS;l#y-!{y=sh$HhFQiRRa&?mXyS(2Gake09>|e zAk-5Aphz`P#Nt!~59HRW!FPxPylSPT?06Re-n)U$aOX?`UX52x+6!PO%>Zxv*%;kz zf=jUDy6NtL4s2pDSHxju1QucVIH`JLx;j>XKq?$O0|~nD8TJn*AsE`XeaAWHo~Lx; zVW>9E|Oofz#0e!%j}01)Mf@tcL-^w|giw=eSP5u#>j_Jc*}(q1q)qQ3S}Pm-?!m%Rj)NWZM)z_V=9>w*9IjD)y&3{cuM9ew{MMx4V)ZJ@_TSf!PR& z{4HC@jU5~9ky!ErR>!j_GNL`yc+Vn#{X7xfhJ<<&`P&GzK`r9TRJK%mfxNU1c2eMz z*0YQFKKLVlvT;Wi{E@%Mv+c985ZZ;CSQHtuKwSul{EhSNcRS2;iu_6PZJ(E?yY|~2 zrz|3WKNotQJ%jFy{AqKW{0xnYC-N72O6tq!hvjoSCAAGiaXa*CYD0lJFI)5&B;NpD z%~N1*h2fm(P{+Rn;#F8qd0Q2flW{yX`ACm(RD%ig&cDNPm3v3IA-M}KtCU9%7OLYa z4p$ya%lS(B620s*8~Z~WFwEGy9f6sj;M|7@OMGxn(U&*5rQqC=I&Hzw+=D3-9wDK* ze{f{Owrv5Sx$i&&oZ>T~xo@VplW*Y19*5?rO$^Q9u)zFmenWFh9>vYjB6;BMqOK%O>q_ zm{1%^5r*PU5pBkY;)Wf(-Gt&Ei*2x>xIGRQSChj-al1u7ZFp0BnNZxd02&*LL+=|D ziZg}+S(@iAC=`dIMj8g%l^2RT#*%fl$>luND};mCcx>p6`hlUhis(l+tBuf`0VM^I z7hej!89X~N02gh^8c^#3plC}LZCT!+Q0R>iVCd~AfTHPBFG6pxq2|=q7tdRYSKNa@ z*;e>cbs`Q<+@>%G0!}F~rfY+6G6J25P^iP9+>OIkNc}dV7($M6ub{uEJ2ctByz{zh zP!QX1SEc5j3G~h=wo?u2UjdLf6X>K&#v0RDeB0DDo5FyDoId3=z>9`U;|gPF8LD`# zq(BdeKSl%pLh!(6m&{G*xESd7M4No~D9}TcU<_1J!qabC6{P=918~_^1)=T_fFi4c zx*2CxFa{c9RaDS3GX}cqcw6##&XP@8fk4?dgx z%36<@h~b4H^ZusiPS*nl)1LEMMQ0IEzc4U;Z9u(1(=Fk;p`C%XB*Kt+HliR zY`85NZjeq(e{)_PGJ@XnJV!F_^NvT#H;m1zfK$=o=uS>;1WuVVfgXmV|G-0`xz1C2 zwp72`N!zA8iKTF~+96ePXA5h7IQm^cs0>HH8$XF~wD|P!g`+P90Yw?>_BQDqaq(zG zHHUz78rK#i;|j)iYqYrf*&C5wO1)KC6@eH>f#eP`N#-FQsX@fN}s?J(QUM{Cqe18KwC&q zdJEqNe^6RB?#O~aD19N@J{yz{?ZV?&6dAHWT`*S-X~m;cj(Q{Cez(Isr=YYX-}Zdj zL1`)Y_S+uUEP~SC6MCLKd+rQMYkQk~7d5{JLFs4D56kBkl)e>+;?`|yfn>2$XG(ALEbj;&iP?A?XXEF=R)hx5c(4cAH`Y z(f391nS#83D1-?@7VGRlwB)2={P9IoudHxY$YnzBavI=O%q1@)2JKSCYaqpXNYpVJ z`}?B%J{x52JjcbL|30HS3DJWHJc zPxLnZR>ac`V`hXtJAoifYK1%>0!(Sp^8j!wI_TNSt$7T3PJ1Xc7ZGYtkb<6_w1oye z)%Kf`JzHS&gPv_5R0cg?f}cdtQ+#^(f}T5Ta9z|~s&LPVh1__>V^3_bfHXdVi)$x`F7>KX<6hl#WxlA)ep2+yAPx@dt$-|e*Z9qJ;ILM(;ass zzaPqqoSi8>J_(EY9xO zl>irLly2rxa7IaD$=9VSiy}i1sc~(T*5|NU^4AIlq}dhA6s)lkb}LU+p!h!JXva#e zb|zn7x4?#}GmB^MY%iah8J?(*O+z|d^2~(*y$l|V)=JIhY~=&ED=qS$cO-{>V|cR? zmIXH(U3nSX71o##7O%dqvgO%8_<9zhsX|c}en3vHaf({Rf0%h{Z`mp*j(B*+UZa<9 zV|y9E)_m3YAd4bHHN2AHcQc=5+wVyizR9BCyWkbOMY!-VhT(@if^HR6qM4g^+O8wd z9_Ne0!c(|eK?X0$Qhq(14KKJjwGtN_d_0bC{2xoE-hv^jd=0j%sPWu`X4dnK`{zrt z@#G^g(@Tk$!$RAUt|)9}u_T{|hOAz+*EXl8rs@s2Dr=(DXgz(p4%cbT-icQB!|-m} zKYJWaKG0^0e+sZwEsm9Yxmj6E-@;cqsEYPzOC-1&EcI2p|2=$nF`#XA7YFSZ(5c-3 z7wGisN=456mRHM36Vc`TV5?)_UlQQaDqo_YT%-w^t}{~c^sSQ}4R zyk__qOO_2gayN_5RGuj3-|uz)LPhNTq^@1Rm#2Q$|5mn-0X)rDi+8aoGStG`1;6Y6 zINN?ty6`0y1>Xg)%(?dd?|B5>YU$iuzZ?VGWn{blP%HTxzN>x*N{#2b{=QS$d4=Eg zhf;nb-#ABkgzG<>#nO%Im;JNH(X?KjuAhI`zA#B|3@7ivy(CYD`jh8%7u@d)7a>l= zm|xDa)nrGMF_&n%om}q%qB`a;e&_cTmVVaC5f-l@TJ3}%42~UOF?P+dEzgQI;OoU~ixR0@$(K+Lb5DtI8nO+H4i-mwKj(F+;VeB z8G&lKi5C_d*Pp3hX0i)gE}d(`hNS)Kc)OS3Rf zz_ORGr)+qHl)WrDjhkWi=UA5ok#DABi-}UT*pZY!w9^l!ym0>ZA({nB#3}!Z5*s6)m{rq*?6i8yKM2 z)xZGUxTegOJV8fo8-SyP@L1s@@3LNkr|Ww8?#_LKh5A&hTAwV{L>ebquli(d#$yd9 zN-Z#yaQO`#ueB}Md3>y;$@%dqB_&P|~ykCB>B&a;ifxIQtlZ%QhQtdkCz3Z!Nvg zLhYabUJ%;Xn}%_PfmR8RC(+|Cqhy4v+@FfUvaK9GGlh*80IDG%0ayXTcE1#W2hBc+ z1rYis*+B@C>nB>wKjC1u?cDCUPYwijt6VAhTf`~tVs^5^7ahWEO^Pv%pBK!r;R8+M zw*Ztkjd$dm#+dvFvQNaDQGK0Vbk)_Cq1^3p>l7DXo9#6F>?s-8;ZaQDp` z{r?Si#W2;j46pUfDy2}G5$t5i>8VhzG-sd&o@MxofyWjp5=lse8=J+8amTiT&zvor zl7^#85$r|K7Q1lDKH6~&fSN*yXvCha&tKn>4B^p`c9%M(Y$X^GWMiIxrs5!yO@#V) z0cEqo?X&IY5K0;>UQlg6hi1;_=l*t{*9Y^qHsIFkO1N@B9&lL5zU=|=nW2Y9|}vj@2fw=CO|49NbBBtMQk{>7a>0k$7ZX*8pDCo?uC?U%YV*d z8?@LJ{!_kcV7c4DV#{Ql`+Oy|)usDJx9pXM$m_6#0Q>^Mn9>#f3gnD64KMWPNlWDo0-RgDX@fk#}m)HI1`{y_+X*mi}NnN z4jeUQ*_JZX@*!_&ANDHX8 z`K?2bJ-9J$@z>B&mj-0ndfR|>y2XI~vix3mnwjH4TX013bj+R|A=FOe4g zcw>u)M6{|HM{&uMwkok=7;8G=#cMEs^8KXe{o_&mwsq=s82tT0{(U-ST~u&FMrRa} znZIjF4@XgIFVWWnr7c0JZ7W?zXiqW_O7<^CQ;{npk}Iz;7Ch}E`T~%9kh$QA>@7<5 z6r?Eh5)3_FuVcq6#!J0AXXKd0+kF6PPIJ5RQlWkC^i^koEZmhtJwn-fg;1{$P`P^L zJ@ba96yD$T_4$PKAaJe2MEPz!GkppZNtdXPBJ zcPb#zo{uN(AWrvnVvIVIq>J-#ei1cjbRLdM$k8rx0G1inc{umrc{pR$#t0=!6fQQe z1F6BpUmQGQ9ghPi-5^xGS%eZ%@KyZ={LS0;ZtmNJ>&4<|^Ux;oqH$>mzCKdE1)6R0 zNA4*9aKzFl^$7-dL7|JYcRELA2u8-7jrQ=uk<}gwO$X?lz0*nCjyw&a3rAG?pptW2 zIr7imIU5L-XYXvnPvYzy@#*0^duNv^v6~CDoo7fiiaILKBpd+)NkGN%)>Kp6`%#!E zw<`6q&9m+E#McipF*8?0_kC4+`$~+3L za#CW+&tY$}C^B-`sc~I7ImgpnZs&>UHp9Iqr@1^AXbZV<`FT+`1ac#-z-YGp9sC> zww%z9+x1-9xF**it335w@~RK91oU!T{;qBPkLQQwbGvcmhXI_yPA4p=NsBA%;PioG zE2lma;^U`7Jy8Mv8djrnVP$x_vi%yI(C$bSeIg#apvX|JWyx(3rI3m_j*RIJ7H+If zS@H-e+<2@bDfZ@Ge}3V{rO*IpN}2mgE=qAHkHL>UF5I9tv2X*21@w4+3pWm=-SJI2 zO=UpQrGViW0u8DDBavp|M*G5`oPDg?xE8m%q2vJS1x61tMFDnl?|DJ(d)s!xh4WVe z@Ox2wrPQG3h!M9_gYcPY6?xl28;T@FSORdHXwyy&g8aNjq2AEBvFQWt=wIvLwS%8) zC{)rEK}$Tpa-VPn_CE4kuno$JfbAk25q#CKoPXqCaW(l=*g=`yAWQSMQYc3UM~yTLv@5R!;22BR)oR^& zs)zP7*tn7KlFc;ju9UK4a{s49KX@qG0jAy~loUi>d@1&6@N6ak7wrHWP}2cWv;&N` zEN@UK_DKjZZKDKG$U+#LUZidO2Q{a5&UoIE75)_hWn1BkJ1+i^^E&SxB_b{=(<<=F zf$Z`u+Y(SYgONH6tJXwI&j7*Z!~XXR-L z#gNnnhLUw#LGoirZvjGO4Cy}nBw|S7)58}-x)gU&4wdI}gTr|dAta4$4iTisi|r=e zRAvBa5cY^hryI@cq4ID6Z?T66vhZx#k)=M}tn$<5Bkd7C2qTznwD`EvdsEol zh~bkS+3)ixxHy!=k{^5cIg27A_CSsIEDp8kOiqGL0~&QwSx@3nM*(diai|mdKKSEM zvT;Wi{BfwW+4k8uRA?8@Wl?0v0(HR~6>;KFyZQEw9p$KDo>Lr3l5e}uJl(b5HpSCC zSMYN}&kdV%LWT<<X$D+s( zI%-_AEH*wj%VX_U6z3M7d$|}Gd(DwIjQI@ybH1y7!$b_uJ$>|jeB=JpM?*#9(|qF` z<@V{Lw244_tQ}hlUt{rfbN;C8VT_&N-?ih_deWAB7TtFb;?@7gvcNQPQKorr`tS=N ziVHuf)fC06y=>8ZO&$bZ%~QNuo*HP(S!KBTgpE2}ZO+|QwE8%A`LKBwwvsE)g6&E* zu(0fX7fsQ?WLZIzeVf5DM)b2%ExZ5Y}8XGH* z;jx&9;Nn9_87*upKDxL~(rx#jf7Pr%=351BWHzNcd=uN@fS~#f&;V!En4tQ#Dei<= zxRh%tIRG4OuOtSRkfF0l6`w5@%tT`&k^rOWPE z2sET10T<^9;(pCwVXQn}ny$49X^27u zgFyFGGzE7ba0km!Mzptj=chIU=PQPu=(jVdBbl9$M%}K0o4+vry-_3ix5$Y6{O8oP3GEWrviX8fv7vG*C{=c~c-dGCS58 zLC09IYLL8J8b_ZMGb&_9ek<5x+Yx+b4lmDg+m2x9@z;{&4=ysMB&%#cAiD~a2KTuZ zvXh+dL;w-~IZq(wX|xRmLP`1KaoL6frB)gW9*W}lhIzSD$U09U#qn|S zl_y%11w;sz0a?>1WxdM+nu!|!=|zBiM{)>(vZsv))FcjbSM+-Ma0$<8#!20nnGa;W zV=uUH#e(NNk5?OT;Qv&uG+LG?JR66xg<(qPu z84_`sOaDu>$@9Rrc#ldc;pw+69@78f09>}kL#P)9K#|2myAfyc)&PPSiK@>Y&AnD*}oVyL$3T+fWp{n zhEV?z07Yts;u@!BCj$blW_*VZ$~gdTYMF}EMS|d!QG-Saf*P-y7($wtogny6PJ*DE zba)Syz+Bzn10z(LUogOsk%TA`lw8Pc$_j-u3#A%dYcVzhn`O0f3nxvu{VYzDl6kzO z9{;~|S86**>XSikqEi<;+sn7UZ@P-yNbsbJenmw*!KeS7U=w&m$Br7o&) zR=25(w*jFtb@7?_Nu(}{PY++};x1AO)Sdo^;P?vz-5in^-QS<_7g78}X}RxbhkKQ@#)W z)EU{hBMbg)#Q$X5XR{GQyRc#tXD>2jfw~Zqjd&8@ez(Isr))$?zCD@E(_Q;*kINRP zV!TBNj%{<@ckL`AZF7@1<7oFF%jmNCVfoy$j4lD9xG1`s_)wOSmo0h>l9vFl<|)fa zo{C|5T$yGx3Oz_q#V~(Xc}DV74C5E$R17t?FsFMNDK+?m+(e@t*NJV>ra1H>>r8J`>dIE!aM%9!PcAgD}e{iBxuy-hPQ;eeF z^qldf&d_JuFC)9?B?u(5i;S*(+en>jPhCGfHC1oGMU3JCO1z&@^h~HosaBYR+bu^+ zwOY~05>dawHrBwvHBY<&QbU@u6c$NYl4KppjCMpb<0yZR}%x{iS{Zp*l29d9ElPh7;#9@pUqbN^KRYT8=};v-VPX z?`2bsN%~!n$oa$WDS zi81T9=CAq5E+2~;O-dvA5CDnk_wkrU(m7tnBphEg_17jZAkXEiP6NESxdd)W3Cx!m zhHBmt*bWR6OWrRSfiQB|QDMB}4k(6M)OMWC-r3p;}J8h zx)568)-}wuY7c5K8tc#%LigiUGK6qlHid0Z?SLQ2FDG)+vBM8!f&=lrW?= zG}QE}i-e(90iEH4MhQb2ui6_p8zzj_iDDn}Ck&xsF|LfkuXw{u)Cpmb{v4VGl!-1P z7wEr1Po$25eEk88c66Fg=V%YYXq%Ih9;W&Hg@;0OnX95*l;+b(8{ELd>kg&)s9=)P z9UCbBgtCtVp)$?q6Zq+jrSOmV^zfzmT-qp4mL?v;6Ur2}IVAbKk&YNpP4zeH(~VKl zhY{Q`^B<$tDm4z2#hq5nOO3K9Z}6OF_AEKis7_4PiWm)P34HQEg5q`JL0ZEnS4x%l zs--aQ?JHuHUo;_5aJvL&!{fkR)9mY2|#Dytabp@Kz2OWqxo;?h{Po@{Q}^? zT!thtEgs+mhD}Dua3^% z*r&a!tTX8>)mUGcJ1-51E?)nRo4pR_n! z$oIjYz$6=YWWk@n^f58ztFzkWu<7+dgZQLc8#fEQ$}2IAI6k&y zk_A4t@(Wmkt_8S2({mP&f-^l5OTNaO!=lI#H)>p)p4ihJFXyp#D}-~)GuyLR5SUYaI9 z!8MU5?|LwLfo)}9q!Y^3?pj=%pZi&^nT99oK*%jII|JRWG2RTcb1?_C$3+?4UWV%t zOTG%DFi#oX>fA`R7eLn>eiAyM61vU350%fY&W%)HMmo0=8Sn&ON9mEd2D$0n{c`&O zPm5GXnE8{smB$JP&@%P6!7_ylMoO1{By7)M*7WD7bKrX)8Oe`7U`He1Aj9uN1Du*M z$&cSnaVOuwk3GH}klMuaC2?3_Nai>Bam64vS2K+z3O0qWmm$!Q>OT@`B|rXTV8Gc# z6K4_9m2?y#)|bV}NcIzJW5(${=v4~Rg znYkaXU6?QFo8s4`DwtAU5N*b1dS2q-wdNWsx8Jze(02vmmDmQG>3Nld#hysVq$E5} z^wWknb(Bd-xDh~OQxedbTgm;_mPNWXnpy%J3S?O)8G?M8z-4FYfZ>QUgB}x@VK}1N z%`hAtZKzRM?fhbFk-su(NXT%!RPe?=ED@iXW6Sf~HXL{s=8Gi1AGDHJ1MW`!V+U_H zhU2fW4c2fx;9#+3Fvf7aP4v@-4>TMf1km|595@un(m=z3qZVN}I`u(`=o-&Y+HiaW z(R67zFk||wqDR?G?=&0;B}I}KWZQ5c_k4y!9JDItDeSxlY9mz^@t{0!7;2BaDq&%WYsqQ--)#;?R6*j59dnFGr++qN25&G4_HO?$fG9;J$!jrEc4 zb1hzLx7t)#xsUCDoaR%U26#!MoMyxtsAkyBa5dW=Ek{xmfJe?Dt2>;2*^J3OWOdj9 z4`K`A6uIbUhv|Uf%v^GLX`G9*y*RNC_kgpxAIyCrO&g+i6uWG5hza|52m$s@;LS|HxH5&;h&FkVvy}qN z6eK0Qc-u;W^xqzU%T@}6dTsy|DFupFoKj$!LX1-6mMMG!NHAR<&skE5k0MaEO8lMY zxJ)c*#bgHWk(vJDfHa>Dn%h&Ko*Z-Q8|<2!zP_AhF2AMPY&DHWQUvK4f9iJc(&QM- zeQ4Hlu{h^%-#)&AqTgPeZSn9RtN%3hxyM{=_>KgnMS<@pt+d~b8cWtXOqpqYby{}0MI!vI79p1xrtQ(F<6~LJwn+kg-|O5RIW;`^+K?LT>m}sgxu0mg!EVHQH=D3)3wTSUq}nGcvtH z&(u@UvyGX0X7tC={1o?PM4(**fhEO*Os9YTz ziWDAcpkXXe=K+mTyd5Ypb9@jCgP%3_mHQg{8e2LQ_@9}6kk5uGMEp0on5xDp~ z0mUP*?Epea-pAMh95HDJcqn^zjgI)s0ZyN0*EmbfV}ZTo#CkeCyXHxwdO8&&S2aOh z)YE+|YJ91lPUBVKR;;kvu|glf(EdEu(@mBSO=6> z_6>st>%k7Fvcpy&)CHOG@M1r;x4CbV3AkgEd!{zNJR+ylM)97&vANHC3U?+15`ia# zD@FNl*DvYELH51~k`P^M*C`3-fFzhJ>>k$Iea}OoIlWMMNK|XrN!$5(8cDTwD$_^l zm932VwRXP(LS?PpzvCxSYbQQEe6@CPq_$8yQDOR!9I2h6n?tqTtIcXVK|1Ad;Af_k zj)HtSZ;{UnE4ah9sMc-|d?eqj%j}?j$LKLd(9CCg!n|h4cG9-X;|uLprLafb$=ycx z-IKdZ^FC}e9=gV^(24R|a0^V4-LWo&viY4q=&{_Kjz?UX-B|z!mNKNw?gAbKmsT#Z z+$qLnI5g`t>B`d!M?47sDmHM?Rh*(7Id{PX$bs-Cj2Z93?e;8Zer`@#L0C{Q7FX6m z9s04AQ=bXW_w>E9Zskek!piV;W&1U_Q@JBi^yO`H%Gk@1y@7-^fJddANUZBdo|JLD zhbXo5-8F(g49U$n%mM1SZ=@&|IQD%an-IB}^z3b4GDn1l3~hE~DC$CyZ4R*QicJfL zfl5j?MvNkhbcMc9q{vXD4T>vmM*MywQnLLj%#%Dzw9k`5ms=1N77`j``^c#8{2i9} z{rQ($T$jTqroQu<6n8=_T+X?T*vl=bO{|o{VF4RFzxACnX?J{^rmh%M-rpev8dCj7 zBCYz)@1@m(=nh0|AfPZ4%eRF4zyP38+ZodaX1-c&1QZ;I%fbD*>7uDQlyqJa%?O(b z!W25mD%)b)q(-cI&UL;<)XoyO$9{K|xRBL*r4VGh!~&n0*%7>@qg*Od4y=NCsc6$K zu~-^VnDu84UTabtSkd`TY=go|M)9X%$KUN>u`O~;2G0YcpEkUyl}rZDM*vh$rrQhQ z3a?o)J1*ZcRjZCxTX41=Y!!?jE;S}+?@UhXgZL=UA%iM9jiEr6=1p^`q7z4r3k5!F(6Xk6#d9%H9}F+3-unMq#*L*ODW|B&t5EW z*{Q-{E!()%H0Y2{+ZQ7Fxw?0#?L*3_E zvYzWuVJChD)NS7BG+^5US2QWn6R`&B6?QXRuLfg8NV*T$)p1HT-{^G7=1FeJ=KT(M z5JwQFSZtqRlx+H^^oGxJDdlyQQfJAtV^!Ac1y-IwdlrFEQu26Qwp~DZm3D!LqSRSq z?(st540#GEbru(Tc|t|0vxER|Wo)9|?p?L!_o4=$mZ;xGpzI~;M$0YCsZg_!-Bfms z=1G}A%5gDoXjAj+DAw5XqLW~xekp|2HxW1UM&l|omz{$$!;6=#04JxE@Sp zK|hbnRse+hX#f-{0E$tZ0-OK{ViX{^GV?s(r|H!A-jew5K%i{#f4{%t1#1=ez?O|s z8+pCBQvnaplkQeiMZZ|8HQ+Ys8F|1&bFeV? z(#tD@IvLVI>DBCGwKj&dEXLvk&S3@isM512j$&zWnO??BXagtkBx$D`T+V0bIpUxcD0jhZD`9*W=-YAaQV~%x6 zv$7X?+f8dd-B9#-f72BHaTJC2n4Sd`-X|#JExSFY2qpU!Gp5K5-IK=BA{8AK?_@faDzags8~L)r5-v__W&P)PGOod4vpz+Skl z2LyWF#*;=he=2UOYLmLC`8)C47-v8=e;Tg}!EJ-xbgSm?N2=y;94BGEqCfn>sK~>w z+r@o)X6YY3-9fkPb=LpkuVw*Y`gULyVETU49exNUDWw8JWmo>c>a(<&AhTzK1Vz^h zc1q9%AVKDuuZOjQ=XxkKH?dS^71auM(l(r@$y6(-5|@-d+De>XD|i(UDr*I=#!sSF zP<(n=YXzJ2+MzjJ%y+&_nltKtQH4g#@2#n3k?Q+WbaSW`e5%`4KgRTZ_wj8mY~5M# z{*wv3G6)lAue_0B0PSBuNQ6F$vq)CfbVngTH(CFjNy-C8*C2XCmk%VHlK>8 zU>-d+GuvJ=HM12{dCRU`((dm^ML%tL^HnC8_XPlr4d$VP4$2rdh5}id=Qk*07)On? z3^Xh+gtOX_b|sV@&-JqP$A};{Ynb|ix4-<0=u0-c(PGfHxdA1Gkr!b~cQE+&ngCq1 zZEir_9RNk!=4jLM28OoH2?16eJrAIeg)l%*@32|@$syxednrwc)X1dY^@yB$e$0rF zH9iS}vaRvP$Epn_nZTe=W)~RLjh}?Ld z|Vt&I|wIRxt-HZvnxo91W+;J+hf;Y1w;F-*aP(K#& zMxg%9@ZZREb*xZ0vrs=gS#A`nV}09qoOAAZYDRbjQ6zN+#(N`hKRVl>ll%R^eRIay z!)${c9tzFnuaj-iN!yk@Eud@zwWF+L*;au3Y=bL-P?>G;c>E-?4aBF1FWX?ZikoAC zK!2A<6JO9px&h*wL%zY2tb7AO{1j|^#Uvac5O=2VN|QB8L+yv!v5dliW=|maM1zqO zw*Bg%N`0a{)GW0i_kO4~F*PJuOaJQpjUgo7$mbXu$9#^dnPIp;1xn)aw#NE6oXS&f z)ruOb=s^d+0!XfY^mU_eP>eEBu3bg{=ooTwy+~qe*hE-JBm!4uw&XljIigX z4NL+F;l_tV{U8Emi+T-(&dLqY;eIzYz@K4&kw|RBI8nroM2^FcQF#KM;|D~G4n0CO zoRFsHkO4C4o?o=~yfBKhw)S8U?)gGEavhF0C{6Tvf72A+8$}@nGD*KN^n90~Fh&;< zO7<&87m*v!k=(EX))xl}4=Xs%Pv=%g()J3#FXE4jCoB|kghaX_fd4!8n8iR=LJ>EX)`*fl*> ztCz+`gidss91wFBfq`xgxdHcz&3vP+{#t#sR1>{mt}SkqC+e+o{##SE(rCFy*A^Q@ zDtETRe5UUc*+5$LOkcVV4ynsi+Wim)C`2qM@7DYz%ni}s}Cc}L^5#5Hkdy>iU zhhn($8TlT*5B^LB*|;MM{!E5XvhA~(453~43X38`7N`p$nGE0O+wXRm=ak7H$+tax zp6=Rjd!`^FlVJ*aYhPL1>6db>(y=atGE1F5XuGuRF`R$N?%N`O3$#ne^C&pGB(dbH z(?%9WhAdL!J+n*a^F(wjusyL$JH&A1GxG6#AN+PnHXdY`ZerVK?NVqL##s~@vOrx3 zu}dw!{ceYOPIgI>Z(Eu?-L>Biwo4gT7UNc0|EA(l z(fAGDI7fNZO~ri|u|E9saJO#B)bI9MbwO)$qSR!P* z{$2aDC%rCB>Nv@Jh;lGDvuv^$gf|h7)h9~T$=T##ys7vspqNXxQhQjGgX!g~9+>1N zVD>!aV9J|{&2_y@!MqZBpi(fc9S)U&DQ_w^0F4ApB^)4-Po>2G*!}zj%p1k-RkI*g z>GGCHI2NS@$FAT`k9g3WX|~D}#mw`ZwnVgHBw$|Wh}irjU_ON@KpOj1M7gVpg=bebHR#G34tON51S6dX~}^%WB=X@R}#yU;sbc0k$2rY5&j@ zP*NRY6jv)9tM4(E{4iSXAj_Obx7YbNCup?1T=FVn7{p$WvDj>2x{K`t#adx1JvQ5h z6Mx3G!kBd)pF>XW6{2g|TwtQDZvap;+KOm0FWkH%8NwrdoIW9ssZhR&os!o&r|e#? zMN2Q%T|R5u?#pJRpr+A!E2qau6I~ijP?$|?DA^<9(&$eiKQpuQ0+O6Rmcu6|$@xPm z?u1y_(@UeNO-xP2VF7nIze&#jJME5d+uF4mlxy@u1R7HPMWOaQ4pwj!wH8 zLpjoyPo?LamEa!fMtuUWkaqXJ0-C%|PX}a@A50QP;cB85q$y#8BH$z1*rA~8@K*4@%;J#8r8!-PVXU(BheD^ui$76TCa zp~@0%4)K|(=e#+jbS=^#yjgmIXfr;!yWrsMCb@e6+hCKs2OTW7ql!r@>KFaA;Y~I( zj_!N_H62~Z&C)pM4@&Mfh5}idHz}p$ZX7kTE?`;mlDkjkG(!n1|B@ECCn4%g$6~S+ zJ*n+~F%9$@(aY?CKqyKoBHkdB6jNTfDec?f=x?Qau(I#2j<%DGjr3U+xUxRZ5c|vu zTv^}Qh#D#`meGtF9x^O-F($TQ!Dr?;^SrkW3+59a6m7;EmKQmAyD==Y*amA@?sl-) z@)%=SUMc!%!<$;b7?w8z=zJR%913JH_{i&z5{4!aqya9biINV*l+ zNN_HvUc1vC7@H-zms6kOfCn)Iaf%(3Lf+-n#y*EjC@(?wOapJ3K2{**>9c1V2qoo? z$7LG@lvZgJcqmF=Hs&Y47V{IHLP}r8^<_vgu4N+rH zE8!<0Q1(iAgXu%`LAAx`lWI@TYACzR?1T&u<-9U~*9K-HiY2z0!`qpwLP&j6`0k6% zZWV3vVr2_HX5dRocoDV*AL$sD%G<>!=)iEFIQOHd6&*kU2@ zK2(1v_CO2QSD#USLKVWRt_Api*A1^n`lkg5~ zY!#aFPHsUHzF&+gpu56PpHrz9z+mZ4dvh!HM^H9kePX%ZL_LkEyZR(6nlbB`k3maZ zoRwL}d;vh`yp9R4yCMgQccgwOhiIJl+cp=W-Y=khHrKipTX`l%?UbVkc~$$29|Qhy zdty&)@OJBe3Fx(L-w{goI>x>uuihhhWo@AhqITPbve|aVr#GGgR9dcke$jkeaY+o{ z>~Y7tpqB!Cz;@Y5nVnI?&Ag$hJvEA2dzA6M>yrevysxoG8KGn!V@4S{;~VAOILfAj zFegi4&cQeK9rt0=$ag%~oE(ngq&@C<|MK}^R(;LeP zbaQL=^-hDl$j}lMsmPmF*)rJ=oV-Fn@d#|0L@3Gs7@5RT^T}k(@6AzCJ+qBo&NQ5T zn86rAgqT?Sn8^%|S;q`OJq3w6OCDfazGbvL)vDGfi{3sOV)ZOm3Swpgx8@8oGZVN2 zK<7LY2<$rgb~|}s0Ho${4)L8c0)%P`sN5OBt~pDT@*q8EN!!Oa0rlO^C$>QE-x$C% zTdNUD@+?NHk<(JEc_@3mh_jFf171o+MAf@? zQRn^Ts6nGTZ;e-F7MueU8uMiLn?_+_iKX5RUD9cv?=BZw{e8t^5tn>&NvTUeFV<(i z%axa?{vJ$UH`)~irN;YkZZoXxjQ2P9Atm)@alDBM3;hPHSv9WVWX-s7YBPe3ly9jv zTTP=dT?4?G^fxr7%L0$lx>0N{mY8tre08$0e+F`V`?hb}ajv3EoK5fTusE<@!8S`1 zuuN^zQdG1+iu^z9#wpK3z;0>nKvVw#^d`FMzEf{50lhJI*m`&-|Ia)Wnh`;jvHa2{ z!eDjMwmZ*$(V6@zAy`=<+jQku-7mXTRoySgPonBxe0un*?uQSTY6rVLi(eiU&mQ`Y zN5zX0dBoFwo&8OBf|X6%>2K8QE#L9`gK!n))EL}%k(SJ(u^Pm^FWB;G0!V9S3J$C< zUI|}I`)lQ!pCzY#`%BGo5&w~PS-xM~4G(Y{2!G-Iy@lHf*TCyw-SNM{LajPcZG|@~ zUQgS+8iAcAHy5_C&(R~%7!;j+V`h{U+2 z;H&>l1(OE+i|K%`lO4?|4qk+BsTsKZqQ&CEbZC89p4lZ_=K&n}l90m*9>=5LD)A(i z{6euCSri$Sc+|MA#9QTKq_u#h(+~Hym#J5crRV{kh;9q>dQt>DDuyeck+<@F@Yk)$ z#vNJkA5L&5+df+a9NL9@SQHtuKwU747t*m7<+)m8-om%v?J&=&2w0MDPlWPx*M8eG zQxk_1Y=_=jhZC&x`F65QT9ua4TgvRD-!&gU#qu$m`i}{U9BCU?h!zYR8u?Wo1!s9A zmVAZzM;1kfv{B=lU9mO5|HfnORtD!*1N;jyF!nBK-Z17f__$r%yoG0QZVm8yzHxsI zaHwc(rz+60JmqNE5XVYtxn(umUhS;-WXW7}sytd9ua3^%*@TD`8o1v(c5i*{-m24Z`A`4y>}}*+BSa7NO~3 zqi*^c2wxocmaTGy2~n8^dyQVci|u6qTk}=p<1C5{)$mG&e-`>Bw*8)T;qO@#d>6c8 zw+MGTjA8gcJc4c&RiY6EtzB!U?K-lj0HKz$Z+90{0BSsU3UDIdxPJ-|O8MD*-}%#iR#v<^vwGo_6?!m z`@f9s3TxvDi`NVvW682%k6+K?GnFUG`S*LBzfci-6T55I@8zlA^}mhnV*pR{)#8IJ ziVU^zcERuZKg+h?lP-LdMZtH$D|4>B{~?c{TP>ZN>z89-yNqntA8I8RUe<-{r^a(# z|8acde%BvL`Fg%_zw4KcN4Wm2ES7Fuza+sPN7H(Bx_Zo@fT%?^{vUd}o2t7^p9?Y#X8ps!gz;?QyOJ9SoU%jW7jO(BBvlh0qovN;GkLrEEqJp z`DV780gTO`g}j|bk>SEQ@m_?f07f7F7mpy&g?C7|MDq{`_m^1u1IEB_I~n2rdlus( zP`Le5>|e6o3}LJtlJ~37qQ`R6ssJqcDzt`0(E}AK@Cc506*`xtKVS^}D%2>Y%r^D`jGGsqOMl>Au zmL}~LT8%O!5tb&$3RTQTghOKMhoCMOQVI)@kln(1T*#p=)oO*wa(qlKe3Tao=EJ|8%P$&T%M>ja9L9NuF5!9G^Ky_4}7TXUowLaaH zF~aY3M%c3P?Nv5XF*~;E=#md1yi5RHO0*fRf5d6s&ee!1mii#H)MeT9X8|;}SPJcf zb*^06ximC9lst&N+kO?ETZ(KH2d``n9~d2Aa0fvfMdqwk1G^zTkBn1Oe}*+k*9}9ll;$*(coSOQ>6t^0{rJP_T2Y`(BN-)t))UmO#^4M%r z#b=AR6(3#PHj7jty|O>5O}us-hXvfk{GOV+?sA3NmrrZ*1PP@>Gmb-`RDh94^VHOv zZaVs?qu``1$l?t++BDr5NDnqe10}p393e3IXx~0@1dVm#619M7{aZ1w zzffMtDmSISS>Z`GKAcJU6fTn}wT~kv)tV9E>McPEg0P+VYDO+8jDayn|d8`VP z6aZ|(XcVgBFm;(h2-!1g4Gu|x(`-j8`1=%y8K7F8_&=n3|dnT|Zoe8K!^nIc+dp2+#wjpN&Ru;L&a)X1# zHsLWdf@?)TZFtk%G3C=w1JF6o2&@iv&I)iSkfnK3(+ALX$WbGU4A%JLhRjO!u&0Hm za)PfUlz&MN$qYLrCQGrGbXWli+zKH5P|iRg6qPj_P*O~J;eI*tCUsND(X$0E`vB{S zARp5!{~}e$IiDzm0s^$G$_fKe4Vx!({k2geM7HG~!7BT(NPO0Yx zTc@{?%=)?m9>grfDONKV4W<*V_^Ft&4s<)gtG#VvR)O4oP7uV?Zkrf{lG4iKvP}%i zxX;AAePDp5YRvR(&)g=JJ)Thd?9~4G??#4|OSzX?IeKCU$>Ke>ar%GVU0sMIM;*Vt zyWQLG+pkLu**ljdu6GH$d#s6(YnYH*}aY6!4*5zRsZ_> z{XgpKU&X^6OSu7L%34qqcT_1y{)+!C(572Z!^uP<<{>(CVzX;se7<9P&FG+haTJRHxrLa1 z4yWg>G8$eQBq7~K+fzCPhS@PJmLIYCMv&=UXKdbIUQZ#pessD@2Rxs_iVosg<%Ok~ zhvS*R&v5i1nbXy85fO>XaU^5Qd4Au{l+zKDJ-)B>q8L-1f@--Ipt6RnDk^(k8Fiz8*}zIc=N6+&*MvlMvr9ws#x~`W60Nu_T@w$`@5Z=PU$>z+ zl~6HK^fd=^=KY$Wc@&NLgG|Y~uz5qiFGA`rFUKVmH(|oNOkMZ2Urh9 zU%XG9rREgc<4GH-%p^IfwrIf#AO3?uu1HMO9{1?zVqJj8NWK|*G ziz?Pyln3z~Kump@*TmVLEyk{{iHYiaUD}z~#FR@)%HpzgO-yVkHF0sS*qfiS61>>M ztU31PH!YGD7tqpH!xijJg{ebzHku5{O#@A*xc!`$KHM$;c4u((}@h)F=@e7p1)vvY@z7}*c9=w z%>z>L>#TAUOxz>JHjvn#XN?l0Z9}9rama&mdSde^*(^~WPN;X?82RL}8ib-^YeU-> zxpo|)F~Q%ci-JqN5foWr+AMY-BSl)>>a zEM#=slBjNoMt9m0QT++#VudDbe1avk+38L6r^>8{vjdHyj;Bi)k2g*LPw(`NCvt`> zYtj#%sQwa9#kq7_fKYh7hZ@GV0BO{2TP3ro%0P zSLiL0bl+4wlp+pt(0=wp`p66H0_j|FbTr=aZnD5xY5NCHDQFKf$TKX0RQCNBdPYSs zNO#@-ScfVeR=rs3YHEBYpFd&EPM_L5^&fQIH+JVviGRI!yv|%pK*7HRb~*`b?O5Z3 zhqNlG?V;b>Ri3)h4@7kHo!s6}Ab-&`C9eV~muz1#^XV#pdgULoS2PvyOn>kyhE_(> zFZV@Tf>XUDSHDV~3xh`AX-HyWur0}xQ{C7?v9X>g9It+?ptUX|S6~G_g{;T6f_iOx z-blIvEUa{?eO_r%u17_#u6#Ay0VWAgb^O-mrX5sgnLwAieI=R)NJ;+o6ec`^e7x0N| zdd$96pY+HfozdeDfArRNCOIuv`M0iaBMbdWKJR<maiM!$`|uk?7BU;@wvb= zo4#Yx1sbE}xiyc$10Yrr_+a1$rW0+P;-g6#9R1%o6;iba_+&Zs=+Ts4A=4yAdB)#SM5_1q0>oAi-$-GxQw%nh4yoQ8LRr z^c7VZM4i38h|g3(rs)Pu`MA#~d~|&?Y#6(c{*7p#PtpQs%P6}z2;K~Q5PVq1Fo%g4 zgVs2Wc)^p(i!+cXMYImx;<1+N1%|lA7Hyp7qn7EUKL`0Vo*h9iVr?|^lSG4ju)<1V z4cRAYMGC!$6i_D(#Ef7#%8Z&s^|PDkGXpqrD@Yabkuaz(e`49U@%Oy`(O{nEdWMC& zcMgN^iXp0MIe@cB+4BmOK|v+r7c*ZnB1;0x0%iU?dzWFD@L5yA5!Mu440za7=`Sy*w6-39?_3Y_QS#RN5{ zKnZyEW!twUWX~HF+oz`%(2%&6=Mq){+l`|!bh%OR9f(fE$;3MhezYC&2S|CN5TAL1 zH(8UYRWRgZo_PmDC4X)V@M+>VAg5Ua5XIJ3^x3J8^2ru!wS|a4BE^*GwFhDi{vj)X zI9gv_MYO-t+6yc^|K)l$aMT4#DJp=JM!@j;0yr>&k5JZH{>atG(VvrEwVWYOF>Ufo z33)KDNs!%P-ipnFswe{jVbuy64hkCy%o4b$5?Im3Fuw>)HjI=xQz>ZRX&)fufKT~* z&A{9>EW2ho_z(P^bzFk}INpE-qkTC9SNrIwS@B!`G{lRxh(_05EL$e(gMpX)hF%t3 zh}=723q?$#c7YwB|D_l>0D9cA-K+uwv0RuTK>;b7NoF?)yN+v0W`uhPh$KLUwm`ta zd$8s##sZXap^Q{J25XqMW8hj9$z4(FX`)Qh*k?{v%CrN1hBaS~Hp3DhmeK_53nbc2 zSb0coNUyJ=anq<2kQG4Of<}c^Nt#=8*0|4Sv`?f_=o=(f*b)-Ta$QJBYtE-qs_FM3 z615dUW($hzy6N+C^ijrLmMUqh{w`_R-%@bz41!-F2>A#lgezK6l1#cUQXYs@`=ND- zR;;(In<6XJ>>cZ?B5(Z{MCHvP1V5zU3DW876hy-azC}TO41r6*W77z(Qt*da1b?F7 zmk%JgN5OX>FQ3`;yqRau^tP8aiiex^sm5q&rhOc|F$f-WC{)Ia2TMakg>tJk z*&G@itc)KVXqGFb+E{a-QXkx396MMVJa_xnEd&Q21>~0fwW(510saiNij9M%mTsax zQEJr&8l~b`t6Ty+MMI^D!L0+^&L6mNP=F6sCdNuf2Fk7RTF*>*@xGbzQW$XQKnVs2 z16D);q))IAR_`tpc$@D%|LBiIAlL3GwpxwK{;3wsE6(Hm%2*TTti5=&R%|xmA$(pq zQ5=__mW)kJ>M#3?qqhSU#5?ngwbo2|ZF~MyqcYRJu%ii@Y4?m4C&nscMTt#&-la3; zzUEYWajSR`yPYYY++L)(Z22XPW@~t|QLohx?wn~as8uFPGuIy9UNl*RX#r$bPwg+3 z&wxRd&w?2%z~6Q7cLV%A4+gaeFc-L9q;QSogqf-GK>5V-_VRLXKWmGsD}1k~JXqdZ z-gfQr@c#Vv(&0pa%JZl812-*_Y-zH{THycT^4NjqmeKmeL}?V|V@PsT=AahBpekcC z?UfFk!1Z$IeQozP&P1z zTjdz`l(+5iQlDlSN&jSc{bu=k`9>hY0>JgS_Cg`jDNM8ZWijwnqawth7nrazaS%q+ zzD$gvR|rd?GEtB`q{lSi5N`m9H=uvt1hNtfGk|^0lo#!3%@lEfWj#Pi4;bD8(9@%- z(e=VFEAW@$W(5SQHPtXD1@sP~6}rY&gf!mV2`+S*^3 zY9dC>RwMN;=&Hl@#u#)pIXzYcY)1!L)02Sk&85*ssdaN2)B5yLO&X1Iy*q!O8~Bi($yOMN6fkcL$Jqe%_fqKfUc`Ahlb7 z!~)m$35dGj5b?caPF?w}<+tJVy+cny`R$!E(=7aXUdRl*c4JQ0coP}1L`UQCLe}YJUQSK*T zDScLAg1ojR8LW^4G-$&vN5dLc7%?T8$#>1y4|C(0NRQ`dfabpn&9OEi-{jQ9Xlp8^ z`RCDM4JbIIDOgDgTqH)m1*#bT;qY#SWJM%XgBfbmt2ci^X^c*4WAOOuI^#mudafhT|OWt3m(GL*)IHiriRdK z3?X5j1-NcB#!%%u$l#pnMF3`S&iwlR>WsxVvLA*Wsynd>Zi4OAP!-QfTymC`qR8ci zyZCW>c|oY4uWK&Yk#NBdgMu&TTmF6uXH?dx3UYCN6W?++&aly~Oo)xTw(o5@BjQ*E z79I{8yoc=y7eNP#Q?=Icp(32{*s8O=Mjgg9$BT{DGp6cztZ*;N>$|a4F<5&peQT(+ zWja;vYSNn)Qk7bjF2Fa{J2MrQ#d8^F!UB8WhWfZ ztrRjSD;sy1!*3-{XWM73MCcGUv0d;(@Xx=a9xmcL2%P_Ecm8K(BqVWc#gZq@`Eqs> zKT@yGB~?+*H5J2r<6cvt8qc*B<9y>B=G6yaW)eHc8j5GJy;MI44Os>+*E-Pn?0F0` z5UWyWS82VuR$5WRG`W_(agihb6Wtqwwo`;YuU{X#{J=c3$mAdweWbhiww0ujcc{gqrCuVg2%O&Tt_CIkw$jK}|Di(kR(_;@ zZlK0<)&G0>#{KF)6!VYpjdPgW>YrT=eva*>6Xh?*XU}8UYVe%N#5-vwtVWH0%K;vu$+3~fM-YeTWp zAu4AlmZ*0PGx1M67J(`wWhPE>%tX?2hr5bzimA&d>d*M$_@zrW9%LovE##~_Yb8R5 zu#)W}gD+?ZK~~~SzJtIi@2-`Q_}khg+e(Cr*hTzE{oFu}=URy?`NsWLA{6tR`NlcS zBdkP`?WGedA;)LWW0;j#l?q5q8HUC5in0vJcWoM;$&V=0G#moBK+|v!-vwtHWH0$z z?3HX687hDp*QO!vr1>3upMhE-We8?-()<=NDWNCLp~U?tKTyADQRBHv;UDrWEj~YCd+0Zp+<{`iFSV25^Hw5vBr}PdYe<$Qrh@e&&#IrD@4z|U|wPi#vL=?Bo z{G{?ior$m%DRHn-oGcqbZ0&PJB>w@3{x(t)QW;33WB0XB_P=iM#+it?$h4rS{8i-0 z#0c9{jL!?pkAcygoEc(W?k%q^Zz}hdH$Qu(yaf|tL?Q_$lx(k_OTA!>x zjEJeaak^EmPe5Kn1^A+HiAwsJoSv)<)Efr}A(04Kdr)Mz>>L>$NTzz3(Uvj4mr~bA zP0T_mtNC5ner#u5Djl;rjj+-06PcQm8biDvlRMc|}6w zb8@LD2c+1sy;@`?O^S)wA}0bOwkR=6{#uZ~(lo)ZZn{?WRzw5X{PK1WM0Cmwys?>U z=K(Vih7fQ&V9?8*CquzGIboXHt2>d%kSk=d7-TUmK3r@}Kn$#@IevI^1u|kF6XD8R zt}7hcI&jgz)gj>ZuTjXfgha>v#b&ujBrZ-?CKDhLuX-C&bD#WieWBjScndWezIJn|R2Tw^ z4UJIOTf{FTs!PZnsa3?}O(_w!pxw~r!7CZ)db`?a9}X6v zG(#)_hG0jk6slj&&F2@Kcz7SAsa@mgy zVR9jJ_1^Z1a^+yT27g<_kQj?Opcs+gUQ#Pg9Gn8igv3hZLDT}(yO3~$N;Y~DQrH}E zMb|hD^H8ucLzw^AnLEqt+g|Hqa6wdSJy(4TwjvW`vPqv!vZ7&c}@US*& zLB8c;_;(8km%T7Tn#oAh>iy6fmj)r!F(fw4wEM!7p=4k)#mraV16`Pr&xnpVJt`fU ztW`!Uu;ddzqXwI~#|n3Jh=6!+gm@`E*Q;Qzk@TSd_q8|2s0=N3?$pKTVeDUu9S`*k zoV0{P7Xg%BAb|E>02z8#me+@GsTYc)qoroEa8+@vUY}HXkA=e(NVvJS)YxBY)SGI+ zrLlffhnI_oN`;Y;+c9~4WTa3!Qi3e^;{IBx2QopMkXsAd9a@1eBO^ENy5%Z-XW~$$ zQJ=uv`NE-Mqk?Y__>}YQ)V0*lQ7ZYaN5!C|u>f|teD>%VV);#+VRKatHks70seqRz z)1q|hLNQH7OW$`|vIX2+N1;4?7vJ7q#qNa=oWV`E(Tj_3^?T4lvV>Zq7(-#X0QtlK z|K+TZJlW50M*A*$ym+0C*d*e0642wTf*!U3xHxS9qPW?k_E#>pXWdr&r`T#g>CB_~ zPs4_!=08zse)YaVmeZa7Y~JXtJ^;fGRsE+xTS2P7`nTAskp@*g7eK>Rdi7%npB`3P zou3Lbha?r8d{)k)Pit^@7GjtMyOhkrLwy;>pjm7UKv^2R=KvP_kS7>Ag@lrbpvZvu z*-!*}*H4gcj$qG?D($qXWYB!12Y)lm60W+q3E$pc-Gl!n0)zBk+9pKy*T<#}L~nin z63BAxqm0GAzJkbe?=RFRF{8X#6RUg2M&Vcn3cF#MUOG}7pR7S9`N&9PVr5cB);L3zQR>cxxN5}HVSwygqx3E*nJ3Tziy<<;LUmwzv=K-809K5KokYWP$>$U;8I)sW7;|WLHEO(&$ z0nrIq{j@5uictWC-aR+m0u>g3JN8d+B1Eq~AZHb)4^~jf2>k^p8H2-xNyCXV=FytN zM||#ZmXNj0jw{WCSB7c49db;%rJW;^*X8d2a0RyXVpG@xet_9c){~0BO*|Ta4K*A! z6%Lmxqh-N5Kv;>GW{o*yzP`T0rTut%FpPNzg3lnxz8}yMxpc_2BD8vBL@@r1@DegN zX&*I`Lhn;b`^5nV9G$^|ZM|mjOMSco0uRgO&e+7nZ#L3xwzG4jfC?ZrU9HBnF(*io z!o<`#towx$EEZJKuqn$hO?`bBot+`1pmZDkG{0h(K${Ghdytue(5$kKQA%`ClCb4O zTtWJTz+J00BgHA$R0vK35r$XC1`0QzBp$Ay?g5dWs839f*Qc7sK{kpm6heW*N@D_d z6BwkFSkHu5 zjUtdp5g_Nf0r4|D7m7PGYYb*tV7U;EJRKRe|0I5*UBv+G#14 z!cn!{{EIX!1qG!Ote1vCV~(3^q3^)q1Gg56T?Z+Sm203`uYdXqVQU{)-*md}G0oQ_ zv*yvVN|M^OwNBPLNAL{t!va_un)BaYW=wwa)*?u!KfeP)L?2Y|EoZQjO?Q5^ghcouxZ*xXvpO`E& zb~uVai)qheH1aN|5uP-3xPv{5EwSZ4a`E9O-992Y6A1oh(Q35}|EY5yN!{wW@#xky zp!2kznw5?U(N7)frbk>aNhYc`4N|q2CUjC9U_8;{@esh6(#rFIEv;w6#C;mzA;#%` zqwD0LVcC9&SAe~!Q>#iJH!Q_pT*KBL>U`%dhHA-U^#?dWd6+euhBd{06h$$c)eAte z?+c3Ybp9^(k!VGP`ey-E{T}?M1f>0mJPX?q{~jIg%@h6hNBO~aFw~HefNGWHI>gmcUjh z=Rn)@McW>QVu^M8&BwrmC$o2c2gn*E?Q`bHKG>gpr1@AXS<^*Ry#O(kO7?W-*w7f$ zW1TVCd`Hd$QQ>USU`#3;nUp8qFO0JG!6Sx?TsI8LnPy+HMeF)m6-Keq^bI`I}M!}F?3OvFE-%!cR zY8&3u6o4|qn{6z&1##-E3>B?^xVDng>{ZQ`X+ zJtfmWSx8N(XFT5mIj-u;DXPoA452udp|R#C$=tS-sE1-Z?%~ zaewwvn^tK5?x0{RG;{TZW{?imu;1iR&9lJowN>;bvk_D|Lof^L16naA>G-8KZSCFE zsUstP#h@(aTHnt`^F2=UJl$x4i#VlE++0j~F0hXw`hg4nOLBpg5U@EyyQv{Z!DtRz z7exsh{Q*Dvw~oN+;O|wg-~HY#m756GMIBPwkcO=e4?OzzwL^$u)V}r-XH2$)$Lwn# z2Q9J7G`yS#qoLFL+T8#ezOSv`1gb!6XsdfsML#lISF?>9zS1VgRW~sf6VW4!`4W(kWYsqZTV!IbIcQmu0!DH- za6`oATMnEjm%W|C1%_N^@=*464i~4s?c4<-(!mT<^2TAPaIC0MEU;n`VOXZ#!H5GB zr%ttw+fEXKWr#p$;aAdD+k2S4@VGVtw{|%vy5Qm^SZ6OcNnvQecd**e)Ly6)XxH-= zq3G;g4?Z*1D9>2-uIFN4tT&1_?Oo3bJEBhx7zX1$*E-}w4i=VWXhT8;F`*($s^ID; zod&YCInsl60N88ZkuPnsaF`LBtj!U^qQ(>jqe=IzsFB+%{vnvaZweyVd&F=4Hx}Q7 z8wcVBX$ap>9Fp4$>;sRCHWTBp|oflPwn0kEamcPV5KAJf_F54IxHJ(7sX(o~W`@fo`VO z&L+O0?vjyMAx17U^9I1yA4ngmKX@p66@ywwnhhSxUd5oX@vLIn@68N`hDe>R1VV$J z=yDLW)Is(N0uDN-0O3A)M_O;yS|8Xy;wdP;YjD*Qq8NhK1D631vbGs!h|f&U;-|=7 z4_pRQbctxwUJpc`g5Kt!z%w@)XzVw_K=1Ajb0o&(;*Q%hCb8xW+|usts)<+NnCzEv)MPyNQ4gQb=9;An`&%iwy4l+XJudrELVeLNF_M{8$#qsp%(VCM)*Hwf$&ut;RZw_CMR)6x+KTZGAxK-6YuEa(hIWEA43hp zBrjNI%d;&>VA+*w9+4;G(3MZWEO(j-k|^w3SoQ^&!xoHGKoyI#W`)lA13mFfp_$z! zDQ_cf>AVh@{Ww7uo`QBsmunIc;rp_!IBEi^j(I5CilY+uS@DPa`tq{}nasfu^&@J4 zQS{rR*PO2ppoGvN&Q=PNXB7pNo=sORzcGrl$&g=1f@T_WeAcNUzX5pQD$(X_4f#O_ z3tRo<8uGdW&I=7~D6r2!Lym)tFyyCY^0%5$<_)bt?v5Ijtw3&ssrmoJ0Bs8uX0hpv zF6?EE@XRa_-m4M*y?J+YvZb1sg?WFQGc&e)1)KMGIn4yg8TRcp?>tfg=KaI7LT8rd z-Nl|ap0;#88JPWkK^C5Zwt2rL5fQ#G+q|PDkV=||vdufHcAt5_qOUJEB|az}-bsgA zm*1|>Rzi+*f{xOyob5ydEEEq_XUYg$Wu)~U68DvYZELi~!zv1I?{C`H^hOQD7Qd&#Oq?sm#1G#V zB!rUl7b8eG>r#+-C_DKB1KdXgQbvmK4!nw$wDKnCI(#)rEqx3?U5!s5K#2=BL+{KJ zWhbiCSW?m%?yGN1?Qc{@&0LHnSITNzNx?AvKu_M&qbBcZ__i)}rUy|0v$C|k6l#J( z30|nSV&I`t^lt}PbySf3teSq!X4Fkri1IL2$zrVSB?@6q!HMK%wLezu8v&u|awLi_ zlBpr6T-0}-0@%EntV&8Y!e6b6S3UH}tfP(`L;Vo&-R5!4T?ZQVaja+4szKeYi3uq6 zgoLb&i`qh1F{!5qt1lIH2x9k&QaKn=zhj**#(JHsScFrsW1Vs}&_gvPJHWWWA9%WH zV6rk5_5X+O&lZsu zw)7kV>5)@%6r^W8BRzE}HHbyel;i}xtEJ|5Fy2n3<~INvXmWlP{_4ZxRgXpqks1QF zq~_*GsbQ>5%7DyC+X)Asb3zw6__@Hrk~~L&gZtoI*DCjDR26ipY$`^M0nz+?8IUZN zpH&L$-9Vuo03XQHL*cJpBwqFKw3GYxq${8_9~CZ?mH#SK2DVkMG{&~zwLjB^$=cLG zNOq>0eoeZ$LfjvMMI4(@799)TV#2s6K`SVV-k}RSYToe3>~pAV2Ntf9rKSx;M9=p1 z0tc}P83QO5dRqj_MjsWY58Eoom6t9Fy4x)TM*cMc)XbdVZw!!CAj|>b0?bZzcpOn5t#s!|v#?8KUR>?1?LE-P(?lOtO9wtHQU{7}X)gwS+^CeAGjMHOd8)me z_M(N}Ty$_e#%_5)6c!#Y0SAt4nrT12BX~84MSEH^MNn4dGBB<>#me z?d6DtwW01Ue<1mFp%|Uw6#Axh+&6$JvdwgF`IF^OS=Zp@b^O ztD^^X?U%aXOI_uq#`#%moYf!VkOjMG@vnX!9u~zVgwQ9um-B9Pr+?Wh2kN&W~;cr0{59rXWu+{Is;>@F^*qhvtM#O9WG3| zcKu)v^@8ikxa$WMetCzw()b?(Vu05X{+FJ6Yo0eilnqqQ=gU$nO%nF9Rx}%&na*Ad z;)0D9Q|l2(UT|k1eHEO8y9|51=h}0_nY0Gp{*5^+;5g@2dL&amZg@f@|>Eh?NifD42Zhc0^IT6txCPDCd~7T9{NpD zL#JSV8uKz>sp|pWTZPnG%t-B$X_%b_$gpUW=M(Y_5hlwMOtf}(6+3blxmXSSuLL5JYw0i($xP2I;qb_4Dm6Mw>Z|3a(%sJ0!!SZ=nYWWs)^ zN%ht!QrY5ww*kEg;QiuYDcp7W15%iVysGj4t0??!WxE#ef3LuwXAoQT5K4|ZM)Z(Y zuMg{XkFJ4*}2Y zrMS7HL>)%XSlzEOL1zHtSw{}5wJch@1UQHC{Ui|r3j6}_nUf7VDL5=Sr_cwS=V;fv zWFia5q16ToX9B(Est(<^6 zapW`g9=3%2K`JBOnj-IDpB9`+8&h?cbf-J+Yvm;`!()|3@1|LAyDy8~SBY)=fW~2_ z-0|M~sd7^$f)d1V&`UQ|xU=3IzzbQftK42H+yOULH8vHt?1abSyaNw~2O6buxaJdK z!1G>jOb_f851ydD3&n44D<?MWh<|2%nX0luY7u>7Lw{IrBtzIuYytCmqcjQoX<@-9?%2VwIH>I}^iqDA?xK)%Vdi!mV!J56HZ_Eo>c%ypR`>{M zIVEWA59uoERb!m@@JN`4spid-7r{`qYe1ZqST_bQGsIBdGOVu20`<5Q4kMw6?G)5% zO&60!3oD!k+p8}S)0I!i*YRWU6H+$r@WLI*MBQP~=$&l)RgM=zhw#^I7a6=jL+CZw zIhkv?ce-H=;S+rOoldjpMO-4?@$}lpI!|`(x6PHAIyU?Q2+3X;E$6|+cdLtG@o+C{ z20Uog+P8WWl1iRy!)YEa0frlNfMHz4(;sb7{4%-HH9lGe{m0zQ+2$Bi*y-H>*^4^HuvYeE1e|;9rmt15p*3L_L@rpb>h3q0fPf>WGg)ORlEd4yo6>G z>`(5o?AQLfe%yBLLawFiM*)qtUD>_*clZp7 zy2G%AW7sI_PHirrX#u(I==9sXEm{SA7|Cq6)6PYt`m3pfg?UN z-w4=-D<=|#>k%kh6n2rl5+}0BCENph>aATEWF{P%bhA7xMG_~b*z?4AFn0v?gJ)uy zeYi`MkQSxwH4kN~w=R z=L|V*wNC-wuH+z%P`08$sFHxnwcM-Ite{k)?{XqZ}DJ>NKIHh$GqLo;1en8~&Rr_RRp*!&TeiHBupEQ+#o2(4f zma8BVa8H2P+ExiJ^xa>pkKS%1;kIwvxpODl{9)XpKt`wO5l*}*y*kGXUR}4T2Z3OV zcA$w+y$XC&&&DJd4W4aO+&QOxNH&Kl`YI62bxOAv)4=VxYvtMfGSXO|YG|9V9_+qp z-8GQ z_AQO6Qp(#bTf&|OlA1_Da7_<0=nsSNO0J!!+TfhBn$umppLtCJ+kWe#)z9ouLpo!$ zs~P+;!h=6ob4b&pUDkB!Zt}y8FrhlefFQJ2_Cyy&HK*wx^t28nQ{y`U%yy0S0URjQ z5V!Uvd>5Q6Q}&YYjlG8LBEywQjcWqV(yje;9*Iuoeh zcX+|?*1nT%pLJ`84x!CDdSnn%Htz6(Un#9%+h>(h=n&3eyWocqqLljh z_B);CSt%t+8(Yof$*%o&uu@vQlJ8pc>|yDcjr}zM7q}dHI^PAS zd1Np70#jwX$lx|=Tx*^MxWy2(vV4DuU6j9T+TFqT+9?mpZaQ7~91#;^AI|4ZV?Kdj z$q&`POdHU6A$oWtBIDA8VkJ5Pnb{GwUm z!)!0r54w@8?KRD*Ni0P++#H8m8_$@6VCkj^MOQH!a(prBfq&Ny#nT(q>bYnz#qO{A zzu~|mnJg=uJ9C5Wmz1S?7p`gaPb`g0)hD7{RnkCS6zthXsGS$hP9^D^fG>A=uMW8= zyv|EfJtx%_KToc8XCz_)ern7FdAXIJ;|} zK*ugk;an<+3L%(7mx{8(T0KR1DkK7AvZL-EKZ`aE#j|9sz0R7O7`zjy7v!b5^lXJ+ z-eD=l;2k?hO&Er^Fd<9Ts>FThW~mvQvM>=yx8xgjUR9Wci+SMSur^S5g32Z-BtKS~ zg}%N$H{1fzSfpV~WBmoF+ep=m@HlIWdb`ws5=Ijc;8`5=MEOiF(G2eK`U`aXS7Bsi zcd^zijmS_K$X6d3!Dar)NPnSpq|(F$QQ7F>O09+!^QfsYCV02EI&PN8xFMQEJb8r1 zbz)|=#`ZytShu^a^);fb5a&|)P!wa4N&0|bc{}tMpP9nW>qLq{Au+<@81EBp#)tlX z#p&1D)C7k9{xi10hW>uX>BY8JF=0jD663VtP5EW)-A@42w09w)zqoS<3jH;v0!f;; zY^Bg&oHbH0pkDGqf6rv4L$PblTiOgSKFMaeY@(t;waqXF0xke}&L|)hg%uHX5K1yC z&)yUmY|!*C1Wj%2a!stV%vO{>#WEP#yic#J@wB1liSdbSikcbHD(8ynu(b+4>r|^? zFzGp>&3LVHnbU74TICvSgVie6Ilb5n9HUjP663Vt1GUPN0d%gl3Qh%*G*GMHtc7b8 zc~O>c{h&CNc|&WHXJDt9t5uVjX>Ecr(i38Q*;A0#CI}@NG)9{sEq&Sqw#^}Hr4H6f z&VmK*pbhBAr%m80o&-|OjmNL?@~D|0?eQ`}ep`FsGjmFLGTUpC)j;MKi#BcTvBF-2 z%3;EG*?q1V>zxi3R*-WW?y#a|tfI(5@!i;N|GCou&tr5U5B3@;ZaB?HZ_jP zKL8X=GsaVvSpGHy%4GRlF9ok=NV1NQ0ot}fa&!&Deuaa_O$pi!hx3r3w7WhvF{Vo{ znj5~cdZ~#;T;=IZPY*&|i3c@J{k|a%#Tp(fP0P2NSow7fPIzejutOdF^!NvXZQopZ zX6gD0_-I?BWg}zsU??-O?ahHJkSEKgz_ukxW+064wM>DbWA$DsJbwohZhh+ zR5;Bpk?NcwUFFBn$>s_mu{5UDrg=cMA*68uXzgIC)5smEge&zlCgJK%mN{egFwcUP zxa~M|GI9@q&Uz0Mo^VCh?HmyF!#PO9s{a+oW4$f-qcX1y@TLIIJvdpH&mPKTguJR% z%bTJ9aMfb3Z`Q%^-YaN2Ac>UeWk|Aqro6qR$02|-^D*QhzvV@S@+>^M zyXbHZWcp)J5p!rS1_p7~Qi}og6jZcZi-Mg1I_pJ2 z;L!1Kk7}rpb?*bd*^+}iLfIMup*9Jq+%>@!UEnDt&|PUu>&U0V2s&L=Yz7KEIe>1q zu|X(Fw;0(*YD?MXq3jJLu0?7A+J`ofxDm@!Z}zFl+0cRBK=Pyw%1_rquQf3=9}!1<{`LkOuet07YV7x>u@m?Z1{}V-Slw{Y9^3tu#FPtuY!k_p0Slveop^`fT+yr{}6r>IsM|((aY)IixN0G)xh(f&NKQ)YsF@>kleSn ztFyd*1C?21^*=_0f*Wo1_VwKk_23RR<)!Am<-3%2Za?m}OV+naM_NOLjbaFL1RHnu z7cLQ%eVdho!%$=cllYSt3mbRtggdZc#fatjmKkuy67Xv*yHjg?LImhz_R?Yy7^f6>WJAcPZXx&-$b3pyMPc}KxRT7kH zaxsiYrJ7irrxHzMmWsiDMw*FY91yn86Dxwz%S|(RtgO0c)cz?P0A~X!WpyWXc1j7= z4$Or&IPh|ZHRdLLfocPU|Ps@ z0d96_7N#cMxi3uM+PByQ4-nlrJTfBFKF|aW#I<3t!I;Aa_6gWBqc^3<7E}L5BQ>Xy zY-NBmMyFobvj|6+H()g7;-l39lUXo|{mZKe4Jv=dxI44`R^P4OkjT{lqT*k;K@;d( zuGepGLa`oj;bB-e-nvcxwVUzIaMMf;6B!DZgspVM4c_8*o6M$mjtCmt>~Rbsj+}UL z$85k<)7((Fe1w(h*-H8!&Y=^NP4vN} zZ$-l893!&XL@+G3l0Ho)-d2j!0!yZJHc@`z=HNp5KS)O7+a;I_U1S}KC;1)%B^-n} zTFFFj@9Tp^&4$YMNkXTv^n9~C+x_$j=w3}QJ(r^aDB537p$1VwAe!nm3D+B!iStDWIM1eY*Q zj^_ooK62Fbqm`3m{|HRJ0N*t<>y`~$q>@q3#`V{!3jn*>RCEXZ9zgT}~f zIcT0#6mMpb>erG~RuICO*-W0nH_J^3AMd1J0RCB`M9JWxtE1+Ec<4$&Fq?<)S*JXN z(Wkpan|7>p4_@6M#a#>%#$Z0u95v!}&d()r6!thr95o>Z!((A{6o&0e62x#663)j_ zr^8u`S1D%j6XZ#%_kG;t{tWZ5NWO1y(o3V}fH>(zf>1Um;j>OT2}>Q{E!ymmliuod z&d(un681Ppob+BX7#<6olTJ=Z5W`7GI3FiLj+tvdXJtAa0|*%%N%KC=OZ^V4x0oOd z-uq(ItP$^hPLS5-J$%+F?_tgG&xkf{-kT3=4yC@tXRWt>>~zP^GjSXC!E&2*5%=bZ z>5?-5Bwm0(27Q0y;A!tDmzxVI(S~&lQeF5MKv?ehl;7N?uf$1h?&QdD!E5i!FkBqTC(de4XO@`^xp_gR*d|(JV1K_7{qf9XD-DE_ zyvz4xFKCeCrPAP`DE`4%20bj6K|F>2 zn^k_v4&onfVp-W;)l&R}?pJLLdtr5J-Esb6Q8dXtIw-(R%TPkBfSZ?|9*hyF!ez@? zvcNg@mM5QwDn&Y-8!wHPixUvm-h-Yb2&Hh(@q2NXv18qMXdU>?j&=BHopk?! zet1;jB((|5`7q3Y>t{FV-d+e^fzc9Nkh`K>Iasd2-_|g?HD{hZBit;nhEtCAqRHu2 zxjrHG*mqgS9-E*KHDNGI=7yt-zW8V4jKcB7D}=}*z&Y-=JZT|cyz;Opp0{N<-xq%m zAS!+F_u?nvix;1co-h7tbSudKnpxkyzS9-CfddemIc$X9WV&JzgIW0BpO%QE7z$QN z@zuvSE8*`iHcJEeq8R<~WTW1ykJfAAruLfwn;Yo?S$ORnUZNwv zA%DY6}org`h)+a0=T+1~1SM%mQ>t6<+5c@m#+BPN#WRg`g$+ zwk^$*UHffw$)Ysr7eJG~M=(zIa#k#1tV`Fm?yWw6+~leBI!M>7M;Q=n`!=;Tzw5p#@t8foM0^@C_JYQBT?Zw z+SIGq9C<;zGKWTBj$$8R!`G5t0+W*)n6rIvsWsJ@XcllG12tg5w*sL8lcmwhfy!u3 zo!9jNu*RPGh{IuX6Oi*<>IIo0uAZyHFK=;90XfISS_$KFrV@%ABR8tw?=Xt3Py?z; zz8)IjlmX)h{^O+Y>TB?0mv2<3HnG?zP76qA+7H~@MMv<*li~PQtfn0^9Kjz&pg}c$ zMAC8u?^2hI7luG$u~rs_VIRdow^F>P9A9U|7)jT zYXK2hWO4o(fICGm!_|#AGsp{u30R14bmcI49<;=Y_#&tk3{e6z)xX6fqov~8c(F1u zQ(Xz5v4_cMl&wf1YolSDDH~IPB+auW#{M8NceO?qesx z)d_TVP2>71l-Xzi4 zHFbcYRUg9n>R!d8r$)`lP4d+J$q1CKrBA_iK2&MnkEhN_C{A`w6I=JLv7o^xt+ckI zQJ0(VDGszBYQ}3s4b#?b7{oFm7|$mj=0R^>Tr%q%Ja3aA)nM*MFN=*wni zggO`iMa+yW5y#Bvh>2llRl11Oy!xu=_|E`^Yz^;R-x(3dS52=##gC=v~1u{hDdcnd8W{D81D~@a5n$IM?K7nn&{?Y0( zRO3II4#RRnC*Sc)w$ot@;6Sg0I1CT*U2qOV*-O5k>ji8V8BQWPfQp z@try!?i16MPsn%jWAHl+W#bMn_#K9S&9=`v3`2+T3AT$2UZ5d_I1InUx8LbB&&pva z(YK9ap6uFhd#tudEgyl=a;I2nhoRQI)nO!+$6*+IQR?sJhUBxmDD^)BD1!%0&d(5 zigIc6_Cn==a^IE4k}|$UD0tg|bD(--Iy=X{H?#rcKzAby&s;>zjZbfodWrYxDg5#l z!sOFC1{O2u(c6&F;~4Si-Q=)|eW(!N(YqcR;1ng}(Yre7yLuIV?6OCX+C+~YP76rt z+A{cZxho4;~ugAP=4~ z6-d%N%RwGIoHdd#kghxr-U$}3tFtlZsotu89UISdo0>#J`jW?v#(^HYuZVGElNzDO z$zecAM&#L-Jaz`n?hfdSPI3*X=LbO1NiOQLyh$OC9d&>ny9t0o62jzMpVC&n)xE~E z_F{@_QuC60*E6!@?3fuLExZtcvbFGK>QWqB!{LDsUQ}SL-1?z34qb{+sAFZ9!xacA zH=G{=-&|#UNoU<;`v5QOs6l~jua$<@&b=1s+$ge>qB#eEL~hq2Tbt*C8@wc@K z;{TX{zHF_6P$vgKkyb(3jMFOUI*rjP%2%4{I=ur>FgcH>Ea{YK1j^Pa*HUEGM5$GS zqPC-@Ns&>+MFX=VAnHxi`5a{{LL{^2E3vg(1xB%~}0ziE3M_L-Ww7`YNOrtZyu# zPM$*A+<7uwIge6?WWJ7_wV3?a*Fa0`9w$4b>1_Zy>jhEh*m1Q$8MLHf&=>oP9PAOw z)=LQWM*_;Hm#m1VH6Aum*4g`;w$vYi9>S%{UOS;b`@w*5*;)>v1ICQ4(@M4FvZ!agOZ82@3LHBKfX${ADm-+3JFKWnYrOw%_n5c4Z?MsKU_?CAGaD z$>i6kB!u!EJZAgDZU#8eE+M|`65j>q%a*<5yI`Aa7a9IbYP@T{?7Mg*I(2e(#h3j& zzzufGFH3lAUO}z2;FFfQ*YIQT`?6)@4lnq9*>7jtXMNeSm;538CEG;?FVGOoZAFq_ z=_J2?jBmfwX`YoYTcU59#yr`z-}cyU;mf`OM$2tq(!Ol1daHYoR9*08|L?gW`Rshz zKMJ4>ZjZ>9?InwzgX%@l+}!)Jmn&bkusqc@aIRlH;T-MEPRy|KX0I>-jKowmwXj&d zm}UpeL9RP{JL`=W3-7`m&8bPOu`?{nT1!2GHJaB4z#6LTWEhsYe3%<2_F2?Ryc1jD zm$wckC-$+hj6pBO^gS z7%w()5qhzYh&JQB*spT>?Zk`yCTxTCV!zqx#g*i6FZLV6IBj^7eHkzIp8;s>p)FeA zATPEt6-d%N(?MQroHdd#kghI!u|I{4$9l189O%XVxEMz^sZmqVNvr`S8IfmS@;4bY zn+oWQPGSwHX9PgeNi6EJyh$N1Hg$kr?0o=*B!tQ7h8O#n)SNmo<7rD;_~!_et%V=& z_XgvFtxFE#2=)6y0 z$WOd2QV1o-6(dqejb{h+Ws4L-O$R`cNFi&+i4?j=wMg*;BKN4;_9~;>fqV3mfM>Ye zk$Y74t9DQYSVmax(PG*?iq@vr@{gKM&*XMw+fkYZ0;j6wAbkXSN?fF025Lno`lhKh zhc43ZdN4FM5sr&Aja#UTR2{%6-m@7t-$nYLfT(nl{sun@7peGk^jxG@*D7!kcXv{K zJ1i)yFzDP#dh6zS#~mlmPgPr)8NdAnkgis#(F8AQYkCsuVGLXiUyJ)|rQ6I(kyvq2 z_>S=(cwyjjc(5C!8UDi8eROLJlmo!Kn)(a1%6O#}-l%vzY4bV+cA6B$k8wB(EvScH z8=y7iNR&-Nlc}em>TjRCsIkm~ViBC*m zAH7AAlOan_N?H4fKa%BkKW4Z|?z_tRPU5w4NVtl_B4fZeP>YV<(vI2%_?F@c`9<@? z=44KNhmhG`v5f!+rZdE&eKFq!=h2qENB4Qscb!_^*H1(VQ zQ`s>F&@^8x&SSgC5DV`R{HFf`w*9UQ;c~VMeh6NkbM^g=dFi9uoCDirWSjm_ zEm`G<>NlX&c&_O`!Z+?W{h^rO%{R_r9%1@t*j_p@{c?QvJo2WWf46E~=A_SvtLt!k z=80#2%A6jE`)%O@?9))@?`6qqk|Sy-C%fr%as6&UDz$#`8^2Go__JD$dhsfvm5wLE zpxBq$9y_MkW@-LRM@Ob#q+<&xhn#Ple#DQ_FVfyw^V_Chvh4@VTK*6gp2x0I*dcgX zqDQ-&3~pG%cN1u#QYRtjq`ARyd(DyuoU=jyDrh4+xPWQm6((-OD9y#IEj4s?8K}^? zPjitq7!%K#%yZcVL`be+OMX*{F7gUC_1MUCQA!`SV<*t@9H_M1OqUyExgqetHd}@I zfx>9HQX4}rZ>c$`^g*T6gbSn##?@@<7p9=&WwUMAVEStuzL=XFnZ49Ye2$F5FE4zD za%3L0m$xal!dzcWl6hi6kz*uDrsXh-9U2ypBr^#ObeJSlOZu+fjvu?6Btvatk_=7@ z$oAYO$-F8Vj?X`BE;~@IQh4PI0u8G1Ba&v4%&p&m1Eip8j0O0kqcYHqj<>7{9R3t>8fSj%HL25E7cmw~hA zfFgzQuE|NjRd$1f1Jn}oAU2k2?=KW5#vp#7K8c~;uns^oAR#a)2kXE!N=2x-IVQ`! zIRlfuz_@zEk(j4MQ?Ff*b9WF2Z8D|;Nt$QzDYXe_jr1bu z#Jtp|T-B#e@bh2PXZycJ6k-#gXtbCB^=mQO>alw?<)$tiuopzNQDzHGJe zo<~62cURN_uM(;xrbCNvP`&& zy}qlWo^a$)=gk`9ui{V)Rc-U|8qcayGNtL)uC}ld$&|~@6{ykAA*UakrHCaVJZ@~J z4VU(gZGqKnx}Y}swHweDyK%~{ANgDWHJK8-$+Xw^R6n)|Ublyo=Cb9WM39Vm_L*{u zh&K`HV9r3&b`GJW!r~d#wsWZFe0J{hbG$#8v%LYoFK|-Y{$OrLDFw1>*+66CC4L42 zl8%7*A29o-=-DD+YFXq1vViXpEMr?hd}cPpvxaQ}R{(3gMYP$81^k%Pv#mWYwJl)u zPE%@{?|}@e@So~K*rC)knLhl_IY`-bjj?{85rnbf&3R!q244qIQ_-Y11_9PDJZvDX zm2hHldKla1&K}PmtR=2GuN|LmmJQQW*8hgvP2JB-gBNtCI^GI;r;zab|#23qC{1HGQTfj!Y z8)Y({$a0xWTRMk*0s>{8!?uU6BX5sc(p;R4Vn)+bDy_1(S^dUFy;UEr*JNpSqmG5= zkk8(%KQ^`lqj6`YFc{fXqHUHaKtp6l9AetGabqAkJT|b34NDt{N2pNmKAW5dJ~am) zTX15`Vi{WMP=Oo*P*Z`V&0@fLS^lWgYUX5+7Mzhh8M9YM2=#OUr+{WVKb-TIe7~b>-IQ_yC}gZ5}fe*Iy%0 zw&Hpg)aKZgxCX9lH0ljqpaUEg6ZICjsT1#XPmd|ldG{)Gcmsvw-q`Fx5w$A%QC#$- zuBuu-jMZs)@fxh3d_U=vuP;T>+m@-bVDkS#u#8Wpta2ZxWDG_jnfbfM^!rhm+FSIq z0n_gYOl@81@{@fOjZkuYF_MZ@c}P-a9L&7h@U$P*w~Q>@BrTDBM5&$v7lq!mq35dy zo6KA223@lv8Qd-Ilv3^bFfD!Tdol5zry6ody@z)DZIbw`59#A z$CV6Qp4I_|{Q={$mC;dw zF{+eF2W51MQIkexbacNe#-b0l8CDscS78|)uzG#jQTh=Son*2C011(D3F6t z_2$5VCI(yd8t}Jm-M6V{Bkq6)Mw^E=iWiLr@%7=-5oor_SL#vr;Zcj9lqcYYI?POT z$)5Dg48qKqtI;mrYVvFkhNhT1C417iZO@Ysy46HQ5-L8og(JUY&kP_cOZL11KZ%k( z;?vP9*>j!Av6~8XoM&(}vN{US1YB8NsZR`)#||_HjPF<}r!Zb>mFr`hX4<>tZqN8V z1b)CkqVvK%?d_s!kI`0U;T};BXMeq3lS|IBE?%?D09P_EM4W7$&v$$wdCHZNP1FL-N_(aq<*EirZGI^$;b0c*&yYp!!yz z)!Zk4oah?w8JYj_EEtT+|F8_PNz0vcH$YMQ~~z8z;=`XO?&K(5TXvqY}!;cypX zfXAVjfee72ZnjF}1MUqd1GKi-*S@f$=8SXk7dTutHwhnY>IGS6VR4 zg8aF|LAKfq$OL&8G{ET;=DL)3CVf}`6hC%36NK8tOc0zFSo+OvCdikQ;rQ02rjQ^@ zlLzt(2sEh1k4Ww}k5DfTN&>(%kQ?#r9C8>S0b%rZCdQ<2T4F^yJ4W$r0F6C;MnfKyQ(#O5k~HsZhJt=@)=0uY zy6!imOD^83mfPk8i`Ugg-+8KuKEx8;m3cZ+rg31lz(qNvMomFS$_A8VM4o-gnQGAN z2ZDWVws0||BV_~Xy8%#iq>Q>OZ&JvaN*!Pv$mei=x)%phiJB48!Uqs2TMO?xJn!PxQ>a5)U*mA4X^z)LikCjycwp?- zonq)d{bL?}$6b~3fM}B+ovl6asuYO{kH4)w5dUWd^kr)gggP7mMcM;pFiv~mRVi9~ z@B<=;lvp9tyXchqtp`KX468$!_7dPT zy`$9T>D)XiA*Yl&*igJ~3rN0GYE8d#O0C6D!YL&_9X+SiwRpO6sMMvKBrfaBC#7+i zg-hxhaX6)$%JfL}!->-9RHIorR2nLvX9^tE;+B?dS5=R0R#|WJkzT!SfqBhVW&B+W zn-bXe{YTAF<@ykJgHQ4b?UFR<;CigKp{j)IIl!5ZOMZPlK_`%9)20Xfr<+8W$op=;CxxKmwXq@2-`)5|AHFtnlG!tBVn9TMpaC+cH5m6PMZ+Z zl~2gK`7!v5W68!HUhw;}X4v*wUsmW4?q|Em-~}2&h%f5_zWq+8c~-tGiN0-L^JLe4 z+p_==zN|}Nv~YN?Z#Ot4!y1rs($cgvc~RK_zscY`d>5Po zlD*{1&A+i-WH22yu1OZ_<2!x}XFkm-jnX)?^YQ&oOpLuL$(zP}0-wPT)vuU{z`0dW z`}oHFRZv4&<8gfB9OiZv)P>gFc=SyR(r&h&PU@h_5yt2V{$1PWt=DhW$D{Og!RK9K zNpO_pp$JHEhuLboMLusYSu|a%8-P}G@AH<`3ytNgba~$eV^J<|b49N_-mBbA#ikiJ zKrY`2dq*|Lu%kSWlqIYma~SJVEgl_8>Cp59WAs1YIz?ki^Iuy9X+#GRc+YcY%(`ZT5 zfD`41zz?Pv9&iNlQ6p*VtHT z3@ddWf*TqkbhWT`;Bf<6CEoV<`B%+6=r;Q*P$TnqJ>XBU0}k-Te-s+vbRFZ1e=zA= zk#I5BQuU)i(e`p8U=ayAQ~f)9wonig^-lG5c!DMJVN{#w@4;yS)ibxg_#Y+1@$IW! z-XSm0zaY?{!WEIUeDQAz@|F8bGNLMPm9VH`vMd*M4e>yyzc5xhP@Jl@3L_(8jWHrK z>_GOE69&&D@Zif3MpV7}Xs9*~bsj@23+@aW$P+4nYOCIuF5pQCh8EBuq<*)v@#fC^ zzi2=)QOHb^_y2eR!k8no))qc954*Hc_eC6$1BcW}5Af~pB;Vd=IFPa2PpOsC&Y zy#E`q4c7a=$?3&bmN6O9=R!-I=re^w&Iiy~??0|-g1rC6R3J(7j4*lsan?wtL7vI` z|BB=AiXQlbL2L_sVF0IQalhJ3XTPBJ`5tyoTqQ)o0W?f3dvtBiOc=od29)Gep1aAt zXOQ$alDn<^&8LNGs}41l)7Oa{i?}^7`)U1#~sfnqu6$(D&NdQQ1<%q zpjypg?2Fz*A1-3yXk6Wmnejx{JNC{Cw=P(C`aq=tmk~_XilZf2a@wd4lUsUbbWX7` zFmbf})912ZG7V!W~}rjwlp;;`Im zW0}uS4C=h>+Y4u`mhxMnoz&XP)cj_IMO@_5fFM6l16$*rlwiW+Z)-fn|JMQ9)z)|j z^>6?bX*|>$aT;$mbP%KQa*NR17R6np?`}n)Y<+h@xckSgN=ec57$P?Z2E5;J7iI5B$+bfCqI9}oo?szXN=bk#wC;rjqj(^T-} zrvO9UuR6biFflv8@L!w&L%HbiR=J1`EfmLA9hJQBLS|!DD6B6OYjDrT*fgA))k-Z~ zG~qe4C~;+Z=>A_Bu9SA*)Q82b1{4~FIjQ~Ba3^f8Uw);#&8 z&`0IsQc`G(Zhq+Fj{#9xkK=#gClUH6J{`T#$LmNaFyyWGsMX^zkeEgAqpZhaw3QkC zm>uM}%m7#CJ=QaA1s81P6e=I)cw7S8e)FR|%n>;QZzP)s`;vzeZ`UJPnd9_?SiZCR z>?o8|01mWHNHpUnz6&mxNcNH+>v9p>MMf|YHLin+W?7lzY95JB!`!+O&3L7lu6#o7 z$QxsOtk zl{rj1EW?yO4}(!*N|s4haZ0i>hXH6*=1|iN3)=q!qo6b0+(4!6d&LoIQxx6^tHY7F z4%P?28s}l(a2RNA!j!&4y`a3r72Qzy<*k(|OzBuz)?l2{R}-SxaXRQV!sSoFKG@6+ zQHqAsi^|_RT)^+r>1gFQ2qdGEjG;7)TSD5CH&0DY)*Eo4q_{{E@0k=M6Vg(w6(&oK z!f3Hp8!#@lQ@=s!yRE7-n3NM3TIcuGO++xDml>C-xn(npeoT1AMlq)B;WObG7XoN(cn0cnD?Gy- zA6g{3wTw6$U2zH3mueoN zhQZjn>U-cHtCA!Ro-P`@Er(7_EZ|d;z7+|VajUl2SU{Q*^xH{9-iNUbHX`q%PA|5~j#2p! zigDWTCfhP9|FZx(Yn5*u8l-hTP6d)QZ@ozoc{pn%Ss+z;^>~&he6AJ-#uIH-et?Y{ zYm01RA^x^i!8#S+6ZrE`G?7i;rOa4zczXS;W1Y_4e+AuBDgt0FrQ-> zta(jf8!(JV*(?a;(}AV~E-o@=pJh1L+0Me&PU9$ z>g~`Hx36KAReu4XvtCx^3^~2f!q}QO=OB$xwxU6(HwY-7qOl^5R;O7(t-5?a>E+kw zp#5+mv6o+XZ_Q@|MrA84gp#9*QCdig*9G)tD=mb2bpRA8EfoGZrFA-Vpp_OsAc`|m z2O28Ep@TT1dFRJW8pRpue%0B)d9aHA$|@tj@+1B@BUCJ1C}cK>?cfzVsydk)c0^~K zncYQ%5dAk8iIh>`uaz*{(a}Na+3ttgHWw#dj1D@}gQ2<2b)tjPxWP?AyzEePkn$@j z*|CB0qk}F0L}hf)h4@KC2Z>KdFFFVcqfHdYk9uJ=g~KdjgWgCr3{+FS&H7YhRE%sG zkJ0=kyIRG@!IHSKi+QP061ftdGHQ3q)kkG~vNnMFqb2&2>8}HO#e*avQ0||Mn}Og& zTp_EhrTIu($vwa=*;bN|L%Jq`ZNKxV#UYJXj)2z)D5S;`@BvWhdVIn7JsplT zJjc^Xv!e?pot^3s7dg7z8v*FRos|xNn$y&CJ+Y-^a(pMu*|8HvfCGIR68F^PyWrxU zWH0&QI?rRf$cS~J#x?n78Ta&Z9*Ir^Vyq*~POG@ROiWikA>YoA!5{Y|8+UlYANTZ^ zZ2N57Q|J&r#&(gx3p50C<40!~``XTpM4#u|?{u1H75603w-*C>vTMKXS*?h;r+qM5 zI0!S8Qp#TCDJ2IWQA+M}t&|>SNt`{fe;3F)jOefIHva<7ps|c7d&!rjRcsd-j7W`l zO)0JCk?53LyP}lN2Hb$RUX}#>&Xadhz?lks((G^%KL)>2l8rmO;8#jlvhB0H5ITgL z*)B48frb#Gl!|=&olf(tl#)c>Rx^3BYrh?=luq)lk5lWT#q^p|LCJT`Yj?4P%trrt z02jC(dO6<(w;qzcRjOkFx}ov0FcuXDZk~v!FmP3_Qh^y!;EH?T;PQ#Ijs?Sz8wK7gPa?2Lr7D-^ zj|Er8D;z+x*QLSirDIKwwL=>Ww!VbM34}j3VkV!=PE0`k#J$h}CwxrIh<`s z%Xb-4n^@Korv;{DZeu2=li~PIv`iI>JWgKkLkKje#*au^F_S;->vN9QL44MaIF%cBu`ks@fV)8%lFB$Dyx`Vjc<*y-%>U9UzL&%mZ_6*nF|# z%YD<{A@$S~p-jHy79=DFu6Kon<<)Cj1Mya3VFX?ajA>ibEX@o!-_ zu_2>0BD}isn_@)SL`OPM%z^3yF`mypQvQ=9>G zqBzdMHdw{6-s#2W!5GDHHnhYpc(QML-2$LcQnt3WdgA1H}x787^kqqWMU&0?^1Q>H0=zTsC2HOS-

;{Pm&)&9BApSoS`15_)LV-~K8URH?fvgoL6qwx*BNVyW4Hp7EO_RrumPBGZ0%eQD zKYEIDVkRtxICu+m`g01BfIDbzcYSJN%uS21Q+0ZJa+X8)>Z`wCC3+|3Xm#KlvJ^9N4PwP-yF6T zv+uC1Z|1i|H7nWC3~s1C2kDwhwNX!DS#_S9YLf_pg|e}fc>z0BF&ob3mq_O2r(Ww^Vn+t z#FRyQm%1votWY9;g0YrQv-J)9@xrhq1tSVMk%KJ~06aEpXkTk)L7YKjWjZ4~b;JsbI z3DGHfX-+sFIKkXucQHlp1`mejfuf2|q5|G&+#Z`Jk(8pRf_{`-*}|BgqIUogl_`1$ z@smi=6Q7P=iXIg37J*J8G4&`G@J^7JMUviY%p^TAAd2k3&rAj$x&CtAB1;&Sr-yA( zir#McNEZ78NjN+dAT>rzel_!%UPeC`Sj9BmwkGED^X^DsAI){b4NACm!i#f0gETcd z3#O3n@# zQ+ayHg-G;L>IbctHnLRC9@~0=3)D*&@?CIxN%oR2PFJv9Wbh(2-Zj1SBp!*Qs+VpO z)0I!i3O@$FUXqOm>7~PL`>b9H9l~8~7a6=jLkQ7JFX!9ubed@61if{h1U=s-+lp;XR+2q3El+I4^-||5mQ1Dsj9O5hlg`LR}-E+~?t`3t99wUtY*khQX*rJnK9}W#Xv|SrR}a6;JUA98gZv zz5`4^ZYtizPAVQwj6WBza0gvhFWA#Lqz0g`YtrV9|q+i9YzeLE;Sht`z!m>l2>5o z6kU7iEITyV=~WIpMcrERdI#7x(5Bu)w{g=vSz+#Va*pnu2GJ|G+P!#Q+fvOzzX^j#$qF6E3#>!CZ@RI zw17IE+icJuB*XEYpt@{KQG(w?pg}c$MAFIz{dSUDh?YV43Ok zV4SSgMnHOkxVYRO$u63jMFQxh(S)#xAdl7U@WHLIZBipv64zQ^Bgw&5>LgyT_7*%I zWIUY2o{MdeyU9ou zHT3vKrx#lz$He=rhn6_~!o>S*1JKxbA6!lb<$oGefh5ga@lgIJ&KfBWP#SsppC?$- zpxD!Ss(JG|mguf3vyJBoz*1M|kQ(hcg~-t85lS*5&%P9ZZqV%4l6|eHQ_vRfk#dgO zwAEdPwuoypLtCU9QPXjv`;#FC6Hzll+M+6^!PXY|tW#})sWS&en|AtENYdwBPQRUK ziI7F-BV;&3xM8Nk#@`LB><`7sg5lic_@~WH_kIhE5hX zz4nS@&a*tf7Bv;5CcY%-Z>tG>X3i;3XM0hx8tD8FqD@;(xY?X)AL>5Wob|5`7Iv6u zK$hn(od$R&qp(oyHBhc_nBj8OAHzb@e82&Zli<1H5_@KBn&c*Up6q}JQ3U%Go9%Oq z1W*5x-mqCNraTYXdI58|mWf_@1npG>LP^f!`?6ON$gff_@K6+JYpgvU7Hbb4LkhIT zjb0v8QJ^h#fLA|$8&`SV%k8`&YVv7|dL05~Z&5c`W?@!oorUbCvNJkQ%LGz}i+Mw< znsO9rY=w6k%+#1*R^LM0j53YO<2)qVfX5`5@T_bL0ODT^=*t!WgxVJXMFN0q z6ej?uKnF1bkekQ(W{Zc* zkk$pDwP~n6h%*D*0m}j=Pr-8L+#RKb|6?p;#2hkw7+UJ^kl`T!o%OaMXUMpern{)g z%3zM#2Xl}{C|lzp)L#oIpT@J2VAlt*h0@eP@9Js$Az%${X^!%bJwrEET#q2r9ET-cDKH#`%jb|7NKksiE z!&9O#wCD6(z;La=kk{c^09qAYzfXXt8;)7if2d zfZ{u_c@Uu_#bbC7=SlJ)4`r|0P#e7`K=aYM4c9+;B(OIwXF~^i-NvIv8G_1hsxp~6 z$Pm0cYJMq0Q1`1`a9csWSs8-=OJxWiz(trZMG${5^7ruTHgWf!nJ9=)x6_S$>1;v# z)l3&m-VV$eOx~w7z_3h1gp!z2x}nl5|4)rs>P+C-8Q`Gk%)&GWT?8Ct?)kcyS$Mw( zL-SBt#amHkVH&rgJV~a^LKVZL0 z$*4LbVHl-p*!W%^JJ1|3zEd_}g2XH`3!mn;)r+2f-=%+>3R`v-Jh6(9w;XtJAB4gg zD3kZae5ThNUxc~NUXSysfuBoYAI-sB1|{6oz*DoPKq^(8II~v1cq5<0w*cQ#`dEI^ zg#K|tEMKTec1ZKT032wikYv4I@m+9{>9Uvn!0AOhId;j2OsB?mWcnCZ-{N5O^OK}!c}Y+8N5J42uX^0GT(lu(>$xB z7>T}ZTJvPre%oXFMY7&^gq&w@KyaO4hrDa`Tm3dlz9;1U5xA4)xpOm1kI0=gcL46( z$)8%AQ7pWd;Cgnee-E?p$i>2IyC!4d-w1;-V&V1AZY+HAixCU27zmcspCu~-MkhBG z{?fgr)>LDnY1|l5Y!#Z5rP0cP%BVOk?>F5`nGx|@V;eG#9sbnez`2Qwe-HJ7{1O)z zukg!T$IC;BV`eFZaq(|Uh;oeFr1M3GS8RP75Ig-jXn<20OziY$lD?~dj~}~ylMc0s zA?-LVAii@OJN=7fIKBm~Y39%uL7usvA<&>2KO$+xPCve{57*E+fqzNO)m9xYrV;84 zN|=g;x2J%qUBC3ApdN6cUr4v^9Pw@lX-lBhYbRGMQGi*Sg*BJ5%WPxmgS4fXpp^jR z1{&itb9<+)l`o}&j+BX$FbaQ}Xw!}f3JD$TbNaQsfPsO=7hoG~=-^|WUR-H@U=f(a z<0q$Q+KVQqx77DnXG$(#(xLHoF-{xae3c0uybM5NeSm1Ig943>sX&tEnGgyz##tjN z0}0CuG+ya&yJE`JbG@0p4LgVpAEdG16*ISrv1F4QxtdPf4JgTsJPT8lgh96@0e#VF zy8*Qz0E$lAQK#ii44t-92biq%Z=^n9g045h_+_+7Z*{Nnti704M`~u0?|MdF6g4BH zjbDgB+1hx+6V!>5jC;`K-$mep5#N)mb_>}}8O=EQ^|VYNMF{2%O`11Hk;c}67=QW( z!3sVxGLMYo!i3%<+KdqZjGvX5#0UW5|GI#_Yym*1R|i0m03aL12>_-J#0Wrcn9#QX zg>Z#L?Eg&!%4Yv(`D0uXrnF*ylRCAqdoZpj0TRJQLVU!#kOMkVZxxDq10{;F zXD()(YV42JQr*8s%BI46)Kl;$hnB0>Qv5r1hGXJ=egiGBOZe=&p_W|M1zR6Fb~J(% z_(OIBqw9W|gFQmoIt-zHCZK#e%nJPRmgA;1nD;k5KkI=s;Sxv=cpBHKfMEgPxmigs z2@pz-D@G$CHU3Rf!wT%#>Jig?5p^t z1zM;>7UkIOAkg9kQIkf27P?=xUtbT4RV&cqUJQ#Et29KGvm0ok&l#=S%c;`K3f>H+ zfM+s{Lj5>^*ADe=g8zo6Dr1Gh`a=EiM5$4zjP-2We*Og)sukg*2qP&oFyHq9^`nCn z($wz->YFRhE(R&Q(SxD6{Z$9y6r_;GZF8O!P>_N;Tvohn3qXF5!utSG8Km&n_(=pQ zh)+i^Na1?rSjS+3-r3hbzI=933W&ul!W3?@!W6`iCgCtFCQbo?xWdKDO@b-8wI6D8 z^Ld!&>}3TXpYTuu+kVSnxjtSRY!+J($UoQ`pBxlarhj$B#~>naM1%}tM9852ePD8W zXskX8S9{}~k@W*m_@~sWnIR)i0|V~&-O#}mX32V~Ch@fy>B1{Nl+<{BBm)$Fm{7}i zo}2CD{2su8W(o;V_yyku7oZ?}$@i--*v0WnhEtjv?^=Mu$vhIq@oiL9u&x9stOndd z0u=iAG57-%WaADm_yZIk$F|P~D1;7SH`_%9FVGM|0u*lH+wXLmXBD6z(YMWNp6uFh zd+fdlQ21vd=h@5Rbbx}^ztw+2$@c^(%tcMUJLhIv=CCH;GXZ06@2i$y6o=rYtDeN_ zr-0aVABV6iwNjXofe5dM(WpQK+pMcdgf%HpBNRcg3M`!ep6msfm)uZu0sHvtImre2Uy;sOv9etBzj3P3n!)=C(G@Ro!s$4CUi=N)FT6>2~P z!b8vir!1HVgij}ZS3iXxyBvW)ZDIrhP76rw+(sb$JQB5oAe;a|ZUh28GdFPBPB9S(#7P)|uvD}eAA!*8^xH`U z!gg$fjX=1->BW`i@CbyhVw^U-$-+zo!cG8C!jr{VvdL|@8w@DPj64fd1cE`gMFD-$F|+}7d;k<3L!(a1n;42f zpbjts;isfNVS>65fpAaMjF2|I3xTq=@!5DRkulGHc8oTVn8YwU;{V41ec8;8P_GPtB4$UHiDPz* zHi%*N+-QS;1Qf!R53%~!5h$D0Yshp~NP&(La3c!*feGlq#72zzCAI_e0Q?vgUf?Nq zAX;?r5u)LELOmr7$dY<~(bDs96lHDcLEqiK3+C`iPq+Nsi>`>7d-8vhe51#DKEQL! z_q5?bC^@beSww35R8qt8bMFXZ9+q*OsqVBE%y~%3RFH^z8dD2qGdm|Su_3)Vv%{>y zE(Fk7ufoD>!4ONKn|nhJ(#TS_s3259K;??c>NG1TQ|S9ii}H=oez=mf1qEIH*9VO1 zO%Nribt*#1QN;)f(n1Of4`mxI6t5o(@#4)Y87)-)aYhRR1+>xPQKLWsbrzzWs~rRi zJU?pEC{RH6tG3mtK!G=4pa9w*CQ<-@Hfl0}=l}nrT?iI{g|45cZZM z#7Vv%Np|=V0%Q3f$tV8>n<&TL-tL@sSG%{%&a9;iU`Q$iiy9~wY=8vH5Nwi=s#FM7 zm=OLAAtodN5*$}5Kw*lXa#d1ARZ>X`iX`9b?wRSH?Rhh^whCu}nTPW0Lw85&j z3M%NQEo)FguuL4ty*XIYg9<(dLS<0FWAv8@DiDAD?1BnTblZ(qsZtbUV)Zcv17<$Z zh*(8P!F$ANzgE{|qg5_7MBA7vl{-$e!mOcv|`5HDowX9FOzHsMAVMS?7-CS(bKxQnlU&~9E;0E8spbOF-Fu36gRqlf^A zZ;NrRDJ2{}jhmDsF!}5N5SbOi6vbxy@N%OAd9N< zq3NYZc_IcC*r5bweL!?qIwSvrZ-ZSg$;vb7r7yAdvwA6O6TZQsNRS29ge-dLyL|nF zcJr$Ak|f{MG-+ekES*^|4evVTv70z&BANX4fXk?QcJnAW)g!T_kC>}i6ba&{Dvwl8 z-s_aF<*^PbgsZzw`Jm_+bIB^L8`Bv)$2Zlkk`#keuT%bYzH<9@%2`FD&R5P+p6fd0 zb1a@gu2YsR%%dUrvSx0nXo_0@7R^~MQ0V5nSvKhkFxR|c4MR8I35vPxoM4elg>G8; z8g)$lOJMfehi=O2l=UgTjNJS<8WTir8tWcG;HJDzSp(GKHUl}q+T;^znhO(<9=G|c zVx`M1hzYyAzY>{OO3JwwFWeQ^bDA@W*`uz`QHK_{`MH?1txeqK6ADZIxXpmRw2cE5 zxB1A$dd5YQ7dqlXVVj>0nPOh3+)lXPSXNQ6U8$@-*=|#wDs)|^aHQib9a*52)M;7P z=`0j#%Mhtj#dhEF_lP#^OVUlCtYNx@$|S~XsV!PK z*p;4-{6dv!^jUwhoG~SccLa{X@>RD{H8nz1}DV1D~8~&IybFs`U zx94RM%O0V0V_`S5IWed@jGM4`S?UOk5VKQqHkVAP;BXf}@!)WJr&|SHFRB<8HG=3K z?3SoAe{lHQV|WJ%4u22T!3Kx_ehiCgvGRl^y-T#ygx7~IWAPpZsBZBn3tpndJ72$( zG%L*mG6jcgT|q2Ob5vDuIQ5#+F3>D#!Qp$jkr4rlAx=&t&XR2 zb&pak-x7Q>@4}^bdO!2LHx&!TBmcAH{X2{-d^q(_R{_cv!`TfJVMg-x-!Z&{D3bIA-NZMDI!(tUerI;D#t?rW3D#na3iCp>40mbQV-@g*OXb2PtQiL!H8cBwyE=yi zg-SMdmG*DOT4b^$^(yW2G4MrLc>T4OGl;D&2>(&9peis2Hiv4{r!$NGPb40YhqQn%Qvsg6Bafh}x^NSrh6PIr5SGH=1XbHNI z@%C1GBW^|wch`QyxxMB#OJ2FEov6E~)H&^Va>>a>kwR(7LmI+p6QWXET5>whGRk0K ztKiBT#05=MekRueyj$(`#ZLNVV6Zf&!@0xz5Xna0xvk(W=0z!om?HJ_LRK{M%ww)W zO#{wjUIEZm&tp=WOdhas~-nV4Z07>X=KqfR>o{MpT@ z>DbU+-1lW@wW;3;O13(WekWc{N?sWUAg5?_n+I)^^^9%YSPrnNbwJCPh;J*o_-6Jy z-CW(3ZAn`$dv;=Dg6?KYMAW`D7qwX2;EuA zW*lsN-*
iNF+5hwpR7bnerr`w_*&DQUSayL-65BfT=(85UBc%w~gJ`rn;IsY2q zj_=RMD&$2*O;L%8G`Gr>$w_eXF#*LRFf|WBN&e@NN$NG5OnP=}PLgWzHiqeG#MWV^ z@(?1##8}6y|B(8u&jSeFLZY58)1o@(%1+y>wU%eBZPdi-Us);0;|V;I(#gzL$$tWL z)t*4cruTaU2Ou@yPa(eFBOs`!1yrg>aQ&(!DzG3!s43EqJ8m1?pST*Wp8!1P`Vr5} zQB6>iXL&|7aaxXQ9?G0Ak{7Z!L$9j&BF($f6q~tFawQN%^F^LCbxu482MNy04scHV zhFqPd&WT5OgUEtwuy~*+l)tZxi6tEs??fkS%(U`yqBS`_Gc!X|J~^d~4vD@jN;NNM zUgGrlR9IZAR~$Xn$|Er}W_DWn{o_PQ%bi(rDPUnzgVj%+M{x4Yxt=T1y!?@8`*oJr&K+!^>zJ(P8G^t}ar|E9C0#+e~p0dOQWt30Kp*R#%x zBld)S@N@e2ormrX>zVAdTAnTI{}k@UY*%pkMtJ0&RM-@)h+!ej1&FuOMsoa_+wilr z*lw2pFU+AhbUyuWs?ex4YhLzBMe(rC69gQq za(`j^bOSqTZPHQS9DrH+w=#3`f1in`F?#^5)R=6XTfGJ(t|!+;t3l?n6n z^J;ST$?z(G4YGG-z>)kB`A`pG1SGG?@Yx9b5Y~U+#Tr2_+b#~FXtyIg^$RMas_&!h z2fJlMQw|qzpi+U0E`LN4@<(AW+rmJTkAOV_I2fBO$qhcnqu@^RNi69H&Az~*NI1!- zDvwU`P1qP|%y3nD#d&{IP*j_%KFJd?=)u0B93uX@=&p1|KEt=ce(p_H9+L%oa)bY3 z>t_!UXKljhD>mRh6S9OS3l5cuSX=B$cZE@v9a)b9v z!O?V%&CDy!6b+?~s#KtM!ru`eU&Qh;nfhx0mr?WF#G~LekHnHbVD4s7BuJa8JYrYg zGsF&$bx;{x-5KJN=ooVuHLV-d8T>}RsdkN|7@T^B_-%aU_A|s;MdQ7E=;}Wz;My z=mK#PTdryK{B5zxpFC>dww-cqsaD3}y3M#it=Xz{aobsa#~7eD;YGPoa@}6_GvT5* zF5$HG;i*7aMowu>*>Ie51Xy1~VX?Xc6Q(?C2zRmwb&1S%O8r3gdGc`CDkq!Rwi%h% zTDgyHWd^pU4~=VC6bVDaDj9Y!^cY+JP?~TDi-K>0RqQsf@9>q|U4X2V_xQ>=%405o=zSGb5Eq3HvRDRj0c88k ze$@4sTI{vY5T=90xxl=f3N)r|;jPaB7?a?EN)LWU=Kqs?a}&+~#{rko{C}B8!I^)F zC0)lo#iB@15UTRX{9nQIIIs18h9_!Jr7|?@|9x8*+O7Xj*`}~MUSYAS;jM-&Yx;Q0 z3C`Ql2Tv~JKWR1oVu+aQ*#nz?D^KmF|4O!v8F-pLET&l$3B$tL1iR^fDO>+gns6J7 zf^ULV=HmMPULL`qqjYtqU-p4%GLlVyRxSBjzNvNtswz)4{cq$ex10W~l)sIyoTEI4 z>3=VaWf0RZ+h_KpuD$wAKVR0&9~NyY)gPv%U`zh6M~scai+LB0n^l8u%{cre+f6!q zIpe_3stMzue*0sfug^8I+tt5ko5Ctkg~h7lM#WaE#PKt? z24>W@yF>_7xZ$j^?0;B{1GDUE>4Ie1%O0)-P6d;Iktwa2J8$PatqhD!_d*IRiUbqR ziT4JI3eegx!z0LO!uzCKqPYOVeT=0)Lm$|6CnMZ38SJ-A|VXV0&Zy!RP z)nZ8>LT_SG3}Fbpokwud51|jT^k?a5t05hi_Tgqk4r%)4BP_CkN6#82P_?=w_A&EC zwsBcFX^t5?mVdY)SiZAbEW9^PJ1HvVtko5dpzCjef4=r9XE%qDg*};T_QzmAkZV?k zvjs_Jw}eHt?6Scu0^57%Rcr|y+0^W^^Yw_I6}*mv(8AJkrBI_7Mb9a; z?#CHk1R53)yzSB1UBp|L8jZrT<9di?EHs?^oras7fOc-UXtV)+$(-o~txb;FEeZ>r zqZqC3#y;xy>Yg_s1L_qRBL)1^v~2Yy)g#^Np*f1%#dVe;WF%5?2ogql8tdYZFr?(P z>%unGjax&ne`VM85chcoIc1SX?Z^Vo22f<;!c<|g>lH-$hf<|dqZ3au)gi=dR;=OS zoWki^S!SE1yt3m_Ra$31Ki1iX^W;yMNQKXA9KWm261M6l*Q=D{-!6aL~H^d#Va3d#J^~X0sd@hByc4>@NDo&+W|02B` zJvj5?nS(tN2%=ZEC#aKdJEv}e#aP>9tG^XC#};L)^MnXhDQ4bCK%oE=k$SS#`|ex6 zZXFVIA+|R|M$~Y*KxWy1*&_ud3?MR395|rD^{hEjDf(z(L1eBm5;dtBDDHon0{@W% zui|v33N5edbk5e0VLK?X*z&wq6Ir%p5rI*K^$3hKIWDx8l=cjESm44!PZo|^cR(a~ zxCJ81z8o62B@ULB8iL-t9Ix8}Vatsbk($Gu4Xl05aSI}c2bpmY3~CicGljbyBRRNw zzaYq#o0AOHC+KiN0@0Rjn?91SZ?{3LL<80mZQ@7)@duPRMiZ2`(rh}F8vGRiy6BBU zZ3$k?3PH%0soIbV9Z7J@Ri`5J5XIUlOHnh&r46ZWT(>SbXCR|tW65lfWs*Nu_S6og ztD*AuW~o*I8fj}c1%Tos%xOH@{r$Pdp~?gIUZEk(JaF_**Nwa}P>1)GDWVis?-q5M zdEmmdkz}gL8303u$?qYeD(K1j=co?p4H&WI5theeSWFe3 z$0PWRXr~FUt2@Rc_*;Ok+9NO;*zXlkR}f3nj;5&uwbW}$BSRafW!%`q34S1r<3k$$0fgK9ZSb9_eB zX==Gm!9*e)r_q}T^IKz>O&{u*^r2QW2@%*06;SUHPU&+c$B(}&MwmI2^0-~^5X>>* zbsvFoyFLI=-tBr&d=@w1sCxnW7Gm;*toM6m)Vf5eX6)LwI5p>l7DgjUr`zB9^@DHEY{wIeyLi*|2aH@L7C@ zW7r?AK6-~aQq2*hYh@A(Widwp6rUH+JAKIWl48yaCHIKF2i<$=1ONRlf>!to%bTU}W>@nW`N{l*|x`(lq@!sR* zM%hXdmh86jfuF5L2yN?`3rL4%FYb(qqB%ZxYQ|Wq3!5!IMRllEIbn-sQ4Py{42xCZ zGn(R83z0Ev&{Yv*icbSn7x^rvc#Gbmej(lsAtoO7TjDrpdD%5BG1WSk($0QMOi)r& z^0-V(Ov6xGVjfB*0n}E4AIh*Wr;-5DhEUq_$O&ULZWG9QJ)p>JV8wD6wzpWMu5LBTZIA?uFX~HeWYot&*zd8wE*~OF^oN6aOgMV8Ulqtxp3~SFQctgmw#U z(%d_2T)&=uF4RHDVq?Y=XD+YBOy81f{e)<=ZA(N?N2pTjBt{T@sQQOQv`QiP(-cPb z>+U@vB|QD6?k4&llk^)2;TP6Up=in^Hj8=nnjsB_@fu=?hSRg}GzV+)aG+i^<>?Ca z$7O)GZ?O$7PcIO4@|2kC)09J3Qj$lWh<+(gJe27RkQ9D2gTyOefGlc0Ux03?$>R&8 zULbQ%u0B?tz?}q?>?8)sFYF<27cYwBlh6 z#(fSlhDB5rzcz+$Ul9W&-L_wCAu)+OWpxcRzOx@b7@D?E(U@xH;V4!jCaG7=pL(o%YFqskG6cxmJ&`{guc0`9Mm~G4skl1?Z}spR8@~ z_l@>L7T%XaeZQ?DsP_n{)N!@TA|}C@vVM!k(-Xj;yT1I$Fvk<6r~j)A?J_MNLCJRI z8BfFwIi7eZ)AG?MT+BeBvV3Gg@>xE*3nsFBJZUNweh{o1B%vN475nSJ@-t?-M}3qhPJpOf_rXZ`1#O)i%aFrOcMXu} zyN&xon@N8gC5P7(1hVqco9Repp!8lG>e)9>@$%I>tB0$X$VOK0pd%Fivz>Fu z*yk0omYqs*aizG7dn9^q=+$pVQ>r(*{^izEwF=5qPoe-P9{!lTo}*X2!{6<7 zO3QAeM8{@|?M~}-$3ZqgoIdhLy_Q#MAi0)59+W}A|M7KQB74N2D0e$_(_&H8HRoR% zylK>sL~YLBzBLD;yEb)X753Nf|fPg#c~Vb z-^-qh3ZEkvGtCx_s6THM)-5o-+?@N%$pJMg1(q(Vvj@ zMl=M_d(00pV1GNc0kY>-!J-D*)bqDCO3SCarPB^LUcvA6&T`M+g=+wC?4ek3+K?Qe z!MOtPVGLtx3C#pYYL!4Bs$>2{sq3|h(JOyFhK0Yi(rvbjp-6(U;ss78IvpGsqYAf) zH$vE{_oG*B*XdST#dA#(sO=beEnK9aLkcOun7zN|)}+X8D^_YQ-N1l`C@MDwLj}X` zQZ$B6H;QqGO1G+F;(7+RH{bK0MYbDAT&E+(rn5}7RnX;II;FGd5c%`?h`)oz8zg7> zGz3v>X!UO2{sMoZ?X)8$A|#RCEYc+qG&KBQs(N1AJu*9s*X~Sv#jE13uvX_ZM$~0g zQV@WWMbE*d3>bkur#Dgc`-18*cuOR*XPnMV&GD854k(1# zs@H4)*=Db_h*4DWs=ecz{O3U?o5gT&hFY+Qdw?Jz=l$*O%Cc8FSFF}fR~z^T`Q6ZH zk?3EF3u7_RUfac}cEPA!;P*pTmTHY64pWid6;$t_nn@eGPGd>xwDbNY&hq`ex$dIBNoo@eUr1tY8BQKb7i9)m z(YPHT3XqsJl+Lez_Rbp6d#5NT-zCRy}BQS!Vf^G7fh9!Kf1<(Ki;PLB`m$Ky}vv3ClO{q*?BEFS9);&Ib8c)XGxXJ3rR>*?_~FT>-j z^myVGc>EnbzH|c~U!ez`->%ak@28?^aI;lL6lb~BCAA34T5dHOQXFAA zH{PJ$zY}6pe>1xJ_=x-F-d#`z?NSG71p?(l{w3m2kLs_hn_*E`TxgWwZD{{-_IXS! zx#$aJ;r#%=aw~+WL&K>cg}rc9%&)pCSh@^VMkNS0!<67@P#eGdNp~5^=M;1L!}V{Y zC>yCVs!aKazkx&>`scQ}n@^HG757b(TSWuk=^Y=XYQHJ#^S6W?5u1S*h)Ku|You~-*^BEDExv-i&KjD6p@ zGdEd_Mx=$5SQg;aAWp#ep_R=f~lkpF&Sq^2) z?cpWUOc}xgWp?r*6IWoAcoPxp;&%Un zy@FkQ2s@XZR;zvzrAZzKIAkUoj8Gd`R;$2h~F@?8nH>hQ!VV_B4Ef;R~SwE-8xS2-#AQ$_S`gu3<4C{ zbF)!OstK!c9t}~T0(-b%+Ac0CiNiDSwo$96TDxU+jrf>+93flDw@vu^WIiBfvvlX) z!;GaV&n%k-7M&LtV}zIrS@a3fC_Nv~tzVYikTelz$9C}QfT%Fhirgw50povX;lfwn^>ACm=xX;BuW({A*wSSs7T>SAX7|QFM zx*?9Jn%=g&sy!t_U16xdQH$v2pX0_7u0>iSELDRE>{Ih~K1l&QZ%XurXPzsHkF0nm z4wQN0g)FEHU^oIpKAb97sZ`adx~z&1&92T^tU^Y4NA-1_aaA;jhE6mKi-an9Tz=(b z!J_H-c}AtzD@*jitXC9QoCjuAajj)M#tgAt&jh0^i8TjwP4hlOpH?-7O*6@Lnrk}{ z_Xnmm<}uHhPBLUe%QXqjq7;j@EJ>&p07nyR7W>w$6)JeR_Y@56a+zl#RvSoQkD3?l zLX`%A;mn$y5yZra<};g5(bC~}fXSPTk}N_fItqYKQ70oBffNLOqoDARJBs&EYT;E4 z6!(<}%8m-RWZX?PA!Q}?thPY=^vY$!Z1ahP7d6wlno=79ALe6R*LldY7+wSi=9UPQ z7DDE8GKut)F?{A>G)V$*S<}k(Xz%-bO5*#<#Z8ZAS(14IZ8rpxkp{5M*Vb$no}ZD@ za8j(fI=3??Lj22g3ZYX4s-ssfZp@uDTVwzdPYL)ZghAG9ge)HC82aGu0P9OAcILH= z`$-mHZcLFjxQ5E_z%)@@N3ap@kU^CVqG>uT*NHbJnM>3yH-bD$y*eM^RY#CW#Vlqa z3EXrPhXxA$l$0u-2Z=X{D4VtbyF_ts_M}S@p!I=NvdGs>FOZ52i~d?@xl-W-m|KT2 z7>HvBItbmMHVihe>I~*}*MwxOc;CEF+`3qLVdAr*>6)v)V9*DF zFTjRSIv3*fX`bQ=dcU|1TGjxxTTo;?LJpKgVTY;&O6(k)8}o4&E09P->ZwJ_pfv}8 z?-?`@oNL^1*$3CD$0H^~4^J&Bx=!oua^OO)hNGgOotq^dKd|VlX0<3Kz%DWo&lCx4 zXs?(i-C({w|J0&VE_TJbGCLQxin;y~kTLg$*;RDQsNSc^nO+IhR*tPWV9Kn}W=VS{ zj+AK;L7%0t0Bp~mD)A-_;ztSCr!jp=-7R9@r2y=Auh9s9tyc`-$L8Cw;{W3DU+KgbkN?)Q^>}`AxVW(J zl_&I*4~YBD$zA;O+z2)E0u!Gz( CKIG;A diff --git a/mddocs/doctrees/connection/file_connection/hdfs/slots.doctree b/mddocs/doctrees/connection/file_connection/hdfs/slots.doctree deleted file mode 100644 index 1f91369cfaafd37a105e3d0b34c67ea9139b64c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65920 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%0AFf}97v74_w#hsZ^pQ!xzDm+AqC*O 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 diff --git a/mddocs/doctrees/connection/file_connection/index.doctree b/mddocs/doctrees/connection/file_connection/index.doctree deleted file mode 100644 index 0a8d38cc477bec7e0cd0298a2e29e414c8c2c333..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4404 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 diff --git a/mddocs/doctrees/connection/file_connection/samba.doctree b/mddocs/doctrees/connection/file_connection/samba.doctree deleted file mode 100644 index 8f26f2bcb8dac0244397467553f1ef116c60a364..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 175360 zcmeHw37n)=aX1Tm&FsA__X5pw?Ck7p54(WK!Y(&*EDN%zC@eEQ(>>e$(bGM2_ppp2 z5Tgc26Fl1cug0hmqe;{lJVP!MH4#OPXN+i!nrMv1BM`6rtKO^ky`$e(-}`!I*yaBZ zzaO*RUw`$gURAxS_g+=KclV-K9ev1xL-4=$s^(O=QoB80C>Cq=VyW3_FD=%`rzcCb zR_B$SO~283VP~M-*C_07)~6ferB3@0cw@X=sTLchT4yi(xDwxQRjSQAHE>jOdaT*1 zwBTXAHc~82RB9Fc7`Inm)P8k7pSzPtR{XBY)Pg03WW@ily80<aVnI^VUT}V=JlLFWFKHF_ zA=*y)$o68TWy{ZNG+QH6je51dZ)c~yuv)2=I#(UqUOZI*u>dh^r^gEA_0XyE2_Uf? z{J#PIKL!3j4LY?LC>OMzt!T}t37zS3zP!4;t-RceXI(*|!uR^i!{yVc0bVrc6q$%Gs=qxYB z0Wko5SOf#QYK?}~dyxE`Zchz}h1ZvEN8{1%e&Zz_VNihf8zk+!%UgGQIZTp7_C6b4 z-y>fyUkhDd2-F_ZUL=NX7p=)Uu>|y{Q4y*>0E$+r?SuZb&z8Ly5F;X2spX`i=w9U= zuH^;SHg(!VVQ=OIf=+qy?pCLO-6`wt$lgO|7J}s+@9N6{{IU{%8EIBvh_t2~rT}gj z1`{qC9~d0UmFrEgdv}7l8-ad;y)A*Qt2J8%uwi-pQU19cJ`LrXWf(=Z1)#|N+>zlUS zc;3k^P~q*ljY2Cn?i|XUr^GPQoGOe%C*oIf8+Yymt0RWV-u5B^(b0PTQd7@OdCG} z2m3?^-QF!Cqk*##P%8ONQ}$7gvZY9wBx@-)o=nk)y%en#6oH=wj)NhnR=_`K#@{di zR_A~1O_;rj&5g9;F;5MCwigUa6+NWAMlc?vH&UMh1Kg@NeD8sE7&NTISCdtty`bK0 z9li`Q{|C&v*ur6lO)O2mUHQi;s7tn+X__ST~DOwC_%ORcWLK-bZ5Q+DJmJz;O|$-5mi390 zM!Q76qDAmetON<#dozz>4gBmfgVdL>lv6dh%A)XVuxebh>M(Hlf>9Cr3DvDwYsJr+ zb9eGM6O=YO8^E;EM(5yHRBENM0t=&%(N2W$E8Fwor(k8H{`aD9oAtaqb-vxDkT7YJ zlXj((`hjNd??F$JEnc!(zDaN(NG;#Pqu|t1Vo6uahglQ}YDtZ2wLB(~$C9^xZCyf8 zh8ua5Cnro}+lST1qK>7qEA6!xXHGfo{gh93H!3YzaHG+aZ?ip#IN3=o;H8d=opDvJ z8As%N-*8y%MPkXQNT6?8OMk|a(O%H(_RBv(kZqB-uR{vaf+3@?3lC+7LO@?r&F5Mc zMS`AE7k&yIQl_%q7q7QfSBxi`Z#foUbO`+1$t5gIEEeoF7?i|L}9wx8o8|iyXW>GYagyQx|@@QM(ft;I&RtCi&L^HwyHX93)i>i?OsG2 zm9TXr1TtVwhw()vriJ2*mef6L=aSXz#Q--w7UbJr&7ixT5yNKnlH2M zvz{@u3*TW;BuJjR;GI}6#O+O^4?pD*1d8`Da7@-TMWUINd~KtVHdNEKVdY_*Z%<$) z4Wd+!u%2(+?-4?W{~+JEUyo(u(Im(>c4ty5B6JBG1CAyR2XB|Lj7yg(DoF}`uXPYb2|bA01|pC%i(eVWz=S_J3^ zT0OqOV(Z1fNm6WihWWRn67rO~yx2l~QP~RlMfd2x`5qgmZb){ewp~9<;Ezf^DC5e4$-< z4vQi|^3;W(g-wY^5GdZcUD!zaZKILw1VRVVU3^dd+CYt`I)VH7#{Etpl=3(5jdPUS zPJrFndMAse7vnGcXUj3n_@AV6Q4?}cyWGH11)^MmexTjKU-5lRbO#RuT%bF6lt;n2 z1BoTwu6>F1>c1rf05@A1j>AF{e@(XZD*4GMd&EIobRdM>QdvW z{^Hqu<9>e;N_mlQoTEI0zi6;ndhr*sf3_SMf5E@lK4uc)@s$yb^N+--!tfQ+XfgI) z{uCsmQgXgZAU9gvU%)CTsPTeiT&T6eByh+wf{LDwm5ttBoHZ*`1bNGg+e;^=t5uQZ zTYk9RKLSM~NM);gw1KkhYGtfZ zXw2l!Qzd#+P=1uJH}(xfb|Wh8uy}8H=je#6;uwJJq`tTnLx_JvD3N7YP8JJsQF~i< zWNJFL!Xnc^_lSeeuE{aSe|w=A3vH(q5fNL6 z?U3<9MUyUdacN^rX7qK$4qAIryC2eQWnpZwbJYS6HxUMTV|jx`u0#ww<&n>$Xy(3H6I$xFET_R5VS(Q5P1ApjA+}M=#}0&!3Y&Q!0$Dfc&dgK%IRR zv@r2^J#k;KgqxZTM2|yf+l!#;8v0`yXk-XwS^Y`~ol&pltwy2NtQJ~@G03wQ30lGs zY>WOIc`2$x6)_bZl6$6R-f*YGykGfa>dUCGbNO%q1M-yJ#Q!3@QgF0^?CEQ!Y zFQckeQCT9sG^R!UED+SlP1YNwT(Q(DV8J^rKrB1AV^)1t#%GqTXg%D>>Y=Fv-6OFd z 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 z

~a)~MH8Kx%2Ca(kz}U>I_3Bncyc_ZE1epT15vs_<<^t3Ez5Rv5npe+5z&0nH7# zPIB!8G*hd$O2&`(v{y8N^|k5Ak=rNgjmgeE?Ul_|qcR0|I806gU=tcVPChABOXA50 z#w13BB0^^X9|53diU)AS@>IRi8mX3UD*-zXZTFW8)%fe8_8M?sz(u$lWxAB2s|HKSPSv=1%SI_<+7rSW>BI8rQ4f#v|3ieT(w z7+Vw2P2fnSs1%~SxV^G4-Kvl1N9{vlShQCZrzfXIVwD8OiU$?Y#zw7BMbWJiPlU3g z^!1~uW@)-uAGv)J6>2q%yn3@VVnPZnfiZhqrCE_GyK1CZX=3IpUjHGfT%v{w47=sf znI;U5LJP(nG`fOb6Z5QQXK$x{Bsxw&(kwOb9gqOFRs@kRYZUg!U$0uwUW4NeG^e%? z6tP;Fgv{!zS8r^uoGMLeBZ8e-j!(u4KsyeN_R-~5YpS_@co-fx^HVdea=n(XH}=7Z zIs)Gm1i+gkPzC~do-Gi^Qh2`Q)YH!dey(gxj62Qa6W#o2s(Bc^1ytEvN{xJ_)S3`D zz#t5lTa#5l)(08FFp7$B(Ct-A+sA`WPL9NbGq!>w%_-1&$QfVVoT;@6w~v%7`^r`L zAM|%&wT`O49AXJD&~BZEQQZVa9WK7huOlKd0*`^U$B&l_Ko1CBjyK@4U?D)?GCc(- zRswacN(=Ze&A`2&J@xTQZ31+F?7~_!6kuhY*D8~!yLd*77>o;rNdlFb90N1nY|T_l z&2p)PsqaUO!&|qElqL(6>PVqjM7t}fUV|;8#G9q+gtTe*wht@SZtLux9&0a^)&zzx zXkw)Xr!m3XBXYtkI=2Rh0?lfHxrH{0r3tj=<#pxd&GuUBC-f-z8(6IADPf_+HSX|k z6R}Lhzb{5pTNKS)1CHyGLbJUDf0WM#q)WOcdK)_0x8R?D#oY04!=LGPAHLX{nG%@f ztM`aE?-g&|GhIFbd<}T4JpvRw?f&xJ0ycUKTPdJ3pTIxg$3H*9KUc1RKiA-&+-mr9 zD*pNA5%A|b_~&Iu!=G2-pSzBOKQG2Vdrp8qH{hQioC1G-jDM;d;m@u3=i<%q=W_gW ze;)q40{?vD4EXbH{DX(|M|hIIs1J*XzU&(Glp@H86DAzer4HQt4inXLWqa*qF(1UZTvn-# XSEq|;2lk=8=`W)HB2zEZE%N^tDk>ao diff --git a/mddocs/doctrees/connection/file_connection/sftp.doctree b/mddocs/doctrees/connection/file_connection/sftp.doctree deleted file mode 100644 index e538215b491ece9388e7f8658d234bd71fc60222..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 210343 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 diff --git a/mddocs/doctrees/connection/file_connection/webdav.doctree b/mddocs/doctrees/connection/file_connection/webdav.doctree deleted file mode 100644 index 68a6353502f8c893cc3c10030c60c235855cc746..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 195091 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 diff --git a/mddocs/doctrees/connection/file_df_connection/base.doctree b/mddocs/doctrees/connection/file_df_connection/base.doctree deleted file mode 100644 index 3eaa7feac776911574429576816a643a6db20a7f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32869 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 diff --git a/mddocs/doctrees/connection/file_df_connection/index.doctree b/mddocs/doctrees/connection/file_df_connection/index.doctree deleted file mode 100644 index 8c17a0c531bf7283ad07089f0b2841e8b4d3194f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4576 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 YEi~0ug`{(Z1ZAQ^Rz)gcv5qYMALRXAKmY&$ diff --git a/mddocs/doctrees/connection/file_df_connection/spark_hdfs/index.doctree b/mddocs/doctrees/connection/file_df_connection/spark_hdfs/index.doctree deleted file mode 100644 index 534982d22196cac3cb704f91cf5738f1e448de31..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4435 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 BCPV-L diff --git a/mddocs/doctrees/connection/file_df_connection/spark_hdfs/slots.doctree b/mddocs/doctrees/connection/file_df_connection/spark_hdfs/slots.doctree deleted file mode 100644 index 1514925d5790081b084225e17bc1ffe8500d530f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65105 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%0A*&@uk0fIQ%CA6Tih}QW*v9Aw z?0Q&I7MklQxsOQC!W?(YZQfFV9o(1NfSuBPsr#jrIq{(8=ikt3TW}YsvnY~hkYS-& zvfU8FxY4Znya8m$XkG#W33j8gY*b7S(`XBD*Jra?bFAua%!c@ol&Mt>_BMsIH`in( zgu;7eV0!?xA3(+oeOJi=_+iR3u{7>3#6H#Ro&(ZUg8qq$FW)w|uP|BIiWCn5QYqle z(GY%>_R`lD!i9zWuObKlfzC`5E@=jAEZZJsq6S0&AxHUFF`VxS4HV)z# z0TC0xf`M_6wDv~!ZMFmV<|y*QiyYDM*36pn)Ijo{OqD zXZJ~!I3&5r`3n&_wOwKX4rBsVA$>1(bwS0ufPk;ZR9#)L*Nz;$*y@5dy4dRSa+pA@ zE_#I6NfjP&;pOi=oK$%X;FNVzPZD-gMa;{i85e^U`_na2T=p*pb5f)lb#g?9(Uz3!tQ@Priqr$mtXE>BLT-s9?w&Bix^V!`^S-Eo;2moIyQY zshPgtvE<(F^Pu4F-$y|ILj*MW)DzIRcGIq8-yE)Ss{3Ga<46YqD1{sRrrBCH?CPR# z__aphci5vn_-GG(WZCCK`wNd?{M>#Wpt**kY2Si1E8$tzakv2{D7UOdxKq=sSk>~v z3MmYNawYd?fQ)^;A8t!*_}WIWSA|pT1qx+0*5PoHKY_dWtYi-MqiYUSnsGe%JgU(_5_QT=$lI3BkRHFqWjaQ*&3d-Z^ zP5835)o26&sL3orR8*Q}!!kaKl7VVkw+>u|WyI_?Bzz%FR}ywPwtT(uV3OEBKl zF|*|X6`=(bn$_XSIIyE)Axa3J;{naeASVqtjK0(f$J@c;o>%Vi9r%2X`eWH@wHmFm z4Y~T7)dCJ6s1>;CYdxjBG|Dg;r4w$C{Sq6(-Mbay6jO9u0R!)Sj`~BabGQL6M z4WM}q-zv*JiIjlO9(8<&itPGw)$#F?c|e3gIm%T9)NT@x@j*G@l^6{V23^ORmEqMG zfZUNz_%i$illWE(+W>EHv?{QCZOc3seLb`)+=%)HlH)CbAl4kXxv2Bn4Htz|O{*!4 z2qH6yPZmso9aSSd(++~BKQ}uIkNuhE3Y0H;GmX{~Xw+70NdSPBWvG7x_CUQ;I0n!6 zT(I|2pyyO;u@V~|pNQcvIa1EZ3YNjNf`mK>26e>?%;RO-S+Z;JKgjP;t%0IH>DNtAG*X-81EDsP@APXK z%C<~K+U*tF1bBe)RNsKggoMz0x!r^*rU1IY34s383_J+Z)2KM!BFF%<3!BhTfR&9k zbKo{);6=lv$~Z(!5{L}k?^Oj2UI7Sf%L-tQtrcjsT(;__Q!AU*D%xG(`~%@e92q(8 zTeU@M(;f`hSl-dj;r2o}My&~`FG!-}RUOccpd6SjC=Vravk`y-$qK;S!WdO+5v{qs z#h&!TP0CMLM2f$G#cDT+g~BL`4{d#%Wik4F1Wj#~40AIqT-Td^IEp{gXC=BM-R%Ap zm)RHKPdgmPpM#Yq8J{+Ifcy?GTsRkNZf~}4B~9&H?PKJ$szc&5&zsd z3V-JD&w+9Ha|8atHyOG3x+8LToQoICks@>#E7jZKI$3HcY83}$4+#M{&?jgdcsoik z`3Y?Y>IYI*Uue`KK_pGL#2!Yi$CVMs`K+YVCS`oE^R1u*TDvtq7W)O3TQFUfMjH(i ztZkJB)Tk1HfW>R{Abb7@FwVUijPIdU{@XjR2XzF;0~8xr>VpK*i6cuUKZY8{DSrhj zgzNliv;y-|Xi;kt-8y;stAU#>lxBmJ?MLxPwjio`#Wc!)SB5fBcLfCT)30Eqx01Sv8JG5FTPlzIT1~)mQUE<(Jpb@8bX1zPRVO!CKvFw}Y^4#~GVw zhpn`02TAsFcH(EU=d&7{jI3*Mm_{u-W4q9!<+y%3vV&|1FK4KK;`(u20yq+<%{Xxr zG=_oMwmWX%($^fGQ(>>v>&A0&+Utc;Vme{C8b4>aU2DajpEsN&>BaMnhTC1K$Bt|J z?YQoSji%LJu^acCIXx%G;VU0G*Ys1nYT(~|Vnr)V#;PqVXuEAoV8cehy}HLJJf_Mb?VN3=ZrJ8={&QR znu_{WorZJTdEnx1^xwlK&D?;TJ!uo#G$zziSCLzw|7HwgR_FY%W%+ZRcwQ*fK$|V%b!8m-6uI04YEVEo zSOF#MagLxw->7keG2FlqdQ|YMzLACXG7C>+?0q{QRoCawoUw%@vxsP?AX@MUB#**x zw>D5*!r1fbP~OsGU4zaIEJI-n?kXG4$gEmm2}v#{pSE5GGc?FF*6l z!*?Z+>%4IvXVZO;oH8C(6O)p+?0F;aV%+!0Bd}7gI7_U;M`VTkU)1HVQy>6oRI(96 zsMQjEDDzmp5n@0 z@=;5y+*siG+AusP2v33J1Wh#FbJEi6N%+CE6`SSI7vHmQ;b9nc0A$WJ;zkUVw zcA;?tW2LGLUOD=7eRxN9TQ~I+*Q8L_%uN}Oa0S=NvwaB~A{QIw*))jPEQ*2=-{*$t++bBR30s755@+5Asl$yIGlap^D*F)n zKZPinJ7kdYlVl&-iiFiM(RZ9qrHCyji5Dx8)pInZWDj!JZ4}F@k%R`1S`CWG;`KPO zyBqeH(w!`^d$%`SvucM3hXbW-1>&aP20W0W7n~*^}0_P!T9%C1r#sWqv{Dws`q~#r&q47N)tzakRwQ^Q? zEBKl_ueC?5*lr^xGM0r9f7lM}$ZZ)HAa~=Ol~~3i&s7)?tIZ!@A?g+)QOB-_(Tdmy z`Lq*F8u7FK95$uaoY;HDb1MSgT-c*XH|CjvN0!Zct~`@D2AY2SyhN9zL30~i>@$O&1L#Q_B5mdaYtm3Y8f)Ek!KBZ_IRHlQeftfvMN?Lk#lNi7e`gE+B#A7)uYFE^ld2M=(`%gb=Z^`4bD%gZOKkDkA9 z>8bNZ3mLhD_BP1MY0xY{BM^I*vQvIITHg|H0Rd?p@?@rNkosSKloiMwHB z8*MwWC>trvwd^}S{N1N5_aaZ;R9rL-`9hyHlbn4Qy9&$K*VdT~gn@hFNh!^aGkqNVnbj`bKu zX>rcy@iJGM#j|Ndxo&^mkmn9Df*w2xmm7-|T`Zg_ob5F?aSRc&kk_TXbiHdK436eS zV7$C6d}7Hgyt{S_U8_Oae&`)csj22Y1X2r?y+7CKIx+w`<$^>Spp3c({8S(Cvfk@D zt9c{Bh-*e^DecE|xa36Otn_UkBzKHM9gaX*-qR&{!!eH{p;}7By}T$6#iXq1=!zCf zzNay)SPGw_+zXT|GH|3~T#InNhE7E*7G6FAE9tq=j*W#&Pb(!7QHjU^QFwdG@ZD9c z!n7pYr?7hZNI;!3M9S@yVJEFkSJH)>i>DI)%?4W-_vIbwOo*tb)^W&{ImRXp89@`V3u6r;^AD zV(dSyCX!c_+!5RIGhUKUuN=*-Lm`cdv(R@v-WS-0L)u>8|G&D%Kj^FB$v&v>7p z|8m_U=bn?$4BP7(qU?#2C;4i4@}x0uh=kw#a&KL%kl6i;vl5wQT;_!Yr6sni|5H&L z4#ZSl!$6)Pfn4fiA_(v5m_4>L%(lRa4q`{K+?S@9+tC!9I)XEX!>6#kFPzLFDRK7a z+P%NrktDw?B>AWKkJe^qtWXJR(<6xCe&}`To$JEw(2hS6FrgAWzf10?bl7q0An|k3 z%E9}wEmcMg(t{lD4MIso@Pp?n=>29Nz2%sq=KVLkoJg1>XFVr!)*HN!+>|KZI0 zRWXU$808sNxTPprv${QG{FMZYhC+Bx?nrnSsduq4D|mZ9SFvfCOfj1{ zZ&=lFd8-qJ-3_PpC~TuLHxata!{#dQsd=GNC%G0?t9Qw`yu9bD@}>&+BJ zd3Gc>PPOCwFy#0Nq`i9!*eTSG^HFnn8xDNR$kdXBfkZmE0|N9(sAJ7%Dmu7#WdqVDFp z5v1MTIu-%r*fC`{L;K`JMOzRhk379zzYVd^elOI=(5|>#@Ft?a3ur%g;cQ+RCv-ZK z^&_rI%E)k79Sx|&zbXb~dM%lgvdhvkkQL{JsD)u|T{c##;4x4Y;)HFKlAV8tkQ8Bh zL0O)H%02B*lnJgmKdl>_2Bq#r1me$Dw@-mAhXSE?nS3V-|xX@`~}Dz67M=XK|c zrSL`_q@#Go`&d5q`&p&FoU>}T@|=UTN)eCqe~ozWRpJA$Zz{qA?-$T03IV(;h#9>f z#Yc&g-ZKCs1$YD^qLr`SSGig<#6ugS(-zhv>Db_oB>%)7fbc};3|OIhm6rujyEJ4xp}Cg z-J5#!|0g-IJ3abW_-z%D$)o@8Q%6iS0iVlxR2Kf-ALtm!q@F@L)UelDHb1#XM%?#} zrfo!)i)BybmJpw8uXEw7zS1ZDva`~+R$Ao$k2FY}xN1oM8Y0_%E&k0-+CHVVvGJ#D z6z>LkfSW%TD89KP6lDtKTQsQQNldXh@qSOA_Gqyh8g3HXeut+<>c}Zq#YGA4^ICto z#rlHQ#BSXPLK|N5zNmLTu%WYBJ~nw%kUl*+Y$@rNj!YFun(5GH65~PoAuyvX8SmK* zO@m8uMDeMLdT!@1hwF*l>q$(ea!d+m-XC3%cFDb-hX2?$=8}6oZPe~C!(ujg&4;A2mbf#&+H9t#;i25bk13;i;d>V2LD4OZ`mo=Stn?b_#~ zD2%vf!-r_~2uNHTZcJW9EhfG#5`kPTx77UyOYnCDyU#GDbV$l3%8do9tR1ncYz}nH zvBVW)Gjk{vVIAL^Acg66x)%6HJHlTc(@4FW1^yw&eT0Zn*W3rmt`+FeE#!+$152U<*phtcaLVpgz zIdMA+bsj;ezRsxTIl76pj(o_Je5sxfpa)ceN`&LQ)4m{7ZK@wZqWNMwp#yPIHoeX@ zQ>OD|ab}sq{sly^MHDFy<%z6WFK@8E0@A&2)5ky2$D8!=&-u51!ME$)SNZ3^^3SjF z&wt~eU+16yPM@?LlBnaB+XlRAxQ$91adCYR*EF3=wh9>3J5lz5cN^{u+A3o~v-}7O zvgO4=agUDOhHO77_)N^Zp0uIp<<_WYb=_qKNd>C3G03efdpK z=Q5jBr=eJK>B{E{Zdi%?s+9!S&HU`*E>=~2aO`?P@*q3sr1L>r$QhG^T`47k}ud2 z+5DolZ(p^+&fTo);6|@0A1myDz2+t+s7ungXsxvEX1W4+GqOq)J&-K!qGb}|XxT&( z;pYb6nF&3?K|idHO{Pg_?g!`iMTDc}m!?hOmg%5u#mA8xKWB7tM}h4VoY$aKIFyXt z(f6hggx9FR)yxRE1rBQT(ejJF!E8U#fRp&x0gHUlgsZ74>k5vy@w^-N+}SOW-3p^N zZnM)Z8B9YLMi7JT z3E)27!j+w*Uo`b%J8g$1ZX%-Bepp@@+onE}YYA-jsvEm>wF|J!wi{E00bTDESdJ@G zf!R%g7+llG%~04Kx=+h)Ca)Z#tKo-vwiE@3c0_$38-mt`$oJw00rJO-BkTa#8#E_a zfg<{D7o~hp?!S-C^z2^YM2Hw%GU`FwfjgN7yHyp3qZw7gJjcxo^fPvddU6EN)5O6! z#Dh{PoG{wGxqD8(hi*bgomL-sYAL|qtH4tcHmU3?&PQB3>8MKp4Z8IY^PNN`2CN9b z5|CY-V0XaYx@K;ixfhgIP_MK7@p_P0Yi6FdryJ^YT+XKAF7CmY&!({I7>v4wKZ{3k zsT$2-?Hw%#)e$I?j)c8Il$?bay_)vuNC57NU#gaF;5xJ?Y`H-PIw1YRL2?xEvVGn7 zj}KrjUN|PM&aY-rnQjwqJWkeeROZ+=)mk2Eq1UQuqdL>Qnmd0$N#67JJT$5kO$DXsd}O>5}&rRwnN|^zqx2iu?>dQZ`8)c~pSm zqU%e%=XKuerPR5Ten#Muv&e^HvAyQJz{h&85)?|Q^1N%xrgfbVh@)zi8xZNCXd%;A z{RBJo7SRtH03GT!LqAtD-gUOGG4DNDf;g#W<%15E;KPeY2`&(@L;bMi8!V=9Osx>x zU-H0vTOkVfc?~_5rt!0~{bexgBiU0hGDI0LR|qP`NtBo{_fL8=szzqSwJX({udC`h zS1D;H&r+7ydmcl`hAXeZsv(rfkVlpL@Daa^a9nM~b4x@rFAw%V4*x$a*r&7!-zT2& zUP0GkXYi~dm&6ei((qe}qaRqfaEX>F@#`mWoARe9Dg-tY@Yvp*A>1hTx$ZkKJ|3pTXpKxsq?5hRp-ADy-0*7c6L`6q$~P<3 zupj_j61L{UW~GUT^;)s)EmUe1`WnOY2cx$X3eJOJYk9feXcql?{bcx{Q(1Bsz1dmE zZ#I|1*_oNj(qbX>D_*r67Ap0bdAGdi%{*uC?rBjEUs=uR`D)7>b?|St={6RpQ+T!-kE~mT&j+?{oxaBe*_)&7Cdwi9hO7@ z(whu~W!XW7w_fgFJo!Cg$VH=Wv)QQ3x0)DN8pm~&a)>dDhD%j94Dk@(2W##U|F)sr zT2?>i-O@==ft6Y3R-0{qd$g|AsI;RS`Wm2iG+J_NW2{<<~_c#aMv+b**`D z(-3D%okcDM{})4a=Hzs^Z@N^k)w~kP$Smio%#IDBW0i6{x-g|K!}(U=F-Um$2l0O< zdI6Yx$(sqm32tMixwJgPC^19AFr(U?S*TRKVtJvcJz`B1!+lz@0!F3H`ZOGJgtboj zLnJ*D=w)u{Xpl*I2gccKGz?a2RG4&3fHy0(Mf58A71kGYWrB&3Q>i(e zyA;4bIHVE(&lT!{ueAeMlyf6ftPC0%(gr0pE>a_?WLBD&@t+lY}LT47}CI7e! z-=-YW^0S~*(|OQ2f(|?T*`FzAzEwI&`Z{bjP^wXH)=Tv&z&2NwLF~fwD^0KAR*zpx zQhe={!~TZXo8&s@$riw-4X?>xKfJtBc7bfE0QkW7D0_Fb+32jq zdRc7NPkJ?=leb>OTO=8#?j%8i#Tza_^)%e9k{s#V2?Ba9Muz>I*tLsE5e?`THlS{; zR&TmVbG!HWEjyclHtSr=c=p-@Q_d}t0Z0#(yjdqM;#_;+02C<`<`dB%tI<|+_!?af zbxz`dG;WIq94_Q1=$nG@1|5Q_#-oALZCBUj#M!R{XTPR3?`2Es$eE?;Eb9lRC(8{l z^ctr;rVw9+y075vMeoi~hHOwYHJ4W#F0agz9)*smEjsmue${{+;WjkY0Mk@sd@M01 zBg3doL2l}af0U*v$Z_Z&fEvEv%^tpqXd_h0G)9179&MtIFa~13J$+>T-|^o^qx%6h zLjL<>5{xhsB>O)sxL9@bkX2EEn z-l^Zd1z!86ZB8Ra& zTL)^L9r!8m`VrGy+hckyw`!$ktHTrxh$${eHmgV$3_)^ijV^aI8my-(yxrHw^hmcj z!9`Z^{yS1l4)y3~G2}6>diVrLvd7r6hc!)Yv3Ld(bB#0jT-ZMmnYCgH2fOSD>5Ca! zjF#(5Zl%_a)-JDL`GyBHgTdmB+h(?26^o2Si_u0D4wt=BWua1PKN5|=W-ZmrEt<&Q z*rzce6T33Yn?3>!9$d>tBG`#PNiGh^(a=F%!7tecJ)7Z2`OK3hmV(dK5Ur3dY>Yekcn67O>6Mf7#smZr00O-1!lE6E^PTt#s$kPg|SF;7$Qtap$O7DRLsF zGwC`M(=5o>>}~hUqpX!aS@6qjaJrN2`*ze*ty92(Cr183$zKBxYNYvz6@FkF`Mh0N zv)0DH86z*ckgkHrQK0o;9_(>}@=I2!ls)&yc^U9wPKa|dVp|v$y zdoH-GYO{FC#Rh>nf5g05Y(9ibZln1?t4?0&M`$v0l%UEkn?swndhMK&Z`8b6Tz_e^ z$RYkx4vqY&I#suJDx0}ZqF!r0p$^jL+7H>Au&D#yN_Q51t+j~^bs&H%L$D666-KEH z13<48<=$m)GoOa&oTM-$`qU3^XMsi zrEYeoKE+6oi!Z-wZ^Fixyp`^J`QNNfWbmZ`uK4oOEFyE#rgQ7CsHAc1kL=jwulEFI>F$rz>R!K>^Z1%w||j4R<%`iG{koT zBo6YY;?fJPh(rUSX$DB&Z>1`GgVf$m$(9DDwBC+e8_Cn#uFJgL)+RFeRCHk*>c+Ex zVX;vnI}-m^G&^8zE4UJ`lam4~(fHN@d>;cRG@?D<-il4QdB04(*iGSdR|Rucgfd7h zR6*BSR?QAPeU>GFcV&(T?ZDG#SsZw93_{%tbNnl7TfrQ_8O+grV#N_LM>F5VYKL{N zk8${fHq{zc#U=OakCXuXw!M#89<1Qspkt4z!Q_2^tJa-t?;eJ zB5xN4*4wOa#>k5<is42fTmgIOa14JN2_28D}{irMq@JYi%M!H3;C!C|r=FuFW)z#D%rl-@movkyC?o7>G-=PUiHx z=6MSnz3z1Vu)RO&(#C=3vKpVX!nazDyj}RBwTTSY5M9V+HNItUAm^B$TdTqGH?>W+ z)yOMlKe6{Tof`!3u2y5spw+skTaCP!Z?*%sVcvt)xWd{>KURbH&m6~mR^u9+d!{3d z@^D|Qt;`d99fo6*QBT^A#8YKHZm@Sb(|qhfy`1Lbki7|;`QWW|S9340Hj$wt1n_fd zK1y~xa;lQ^Z$64_^t#jaw7oy+V#k5!G9RzD!nc}_yj^&cwTTSY5M9V+KHg(*Am^B$ zTl2y3H`PwI`N%6}pRo5dof`!3uIA%0JMeV#kr(s7umiVY-h=u0fwh%>%m?qEIga_v z$7PvkygMugf>7u=DeV!flbkX6!bD4l6R`}MD&)bDxw>FV6@}dj5 zOh907Am{L&TNA+XH&sWr3CJr%ud??vof`!3t|s7(cHrqIATQ>R+JW0J@4*Co*xE`z zCV=Uf`{C zS8D&m+C+v%5Wtl!5ND=2Wf{fNHzCY&ik3gOW06yBbXbMnLiN~i%g7$aBmMfMe$0kA z?;(=BL$zhtHZ!mfRacF(!wx)M-mjl+SP8fUWzCSs#A!6WIgZJm6q80%(h z%l&8}j#h3hWG@(9wuIwul_DLYE5>mx_=!XDbhOR?GQuT9ES!wA(jcC8DUKRZ;s_W) zv^GUb;j|>eeA`CYRy4Hg_yKmv${&h0EVQas7MkLJIvRyXrbK8r!*=jeqV%UEa0d>Ij32t zC}oP@4OhaZx73lRKxp<8(as!oH5Yi0U><*~*oUW-1D9yu2mNQEpBHBaJNgs;c7K;Y z>0kFy+n=TwdIZg_WijY`gNA5@PQDMK97VBbMU(qC`?IKf#Gg5W127UlTbw1Gm+`5E znWsZ6CF1b|FQ!LO!Pofc!}o4=L$8d8O=s>dw_L9;Pak~oQHOK3bBoLx;Imf+7Xh-q zSEx4@McjO=L0Jg)&G7fr$c{17tkjcaWL|p5Q@l|(AEy}Ygo1uSeiyqpRPsgnU083z^QByN6ziI$|Mm15fdal z6m!=L)aacf7UJZRk{oSSoHs4hG1J~e68)v%XqvTa=B(aY3f_wbqF6ug!O7@0o|8-Qb`NL=E%)v&J50`9xjTc|fYr$s3~oTgt1<&2=-aF(kc zOoh{QPkPkZI%S-ww~)(X7Gg6y$Ek-TE{J(=cn`EHAx^J`bBxG_$qBws7>T3`H}ut_ zMw1^BFqYzl>u+O)6z#}?n|=~7_>sU3=HlY?9%*g=IMKGLl_#S0EEpE^$R;w?4C8Ye zlrkrgG*BYY;%@;h8K-Axc1q@hGz;x$kD8|C%+SC?eMInV@Q-Log(F7m2rG85-#fEh zt&}R5C9~J3-A1i_);Lm~MHK1>i+-Jv3^xLXqru5HjnZp9-h~j<)^}0HkJ}nHnR^6A z5OFLzXaCQmJj^A;OsBfyP$n#5aW=hm+>j>|ac*;)?j4lUz@cf%=?2odppbeBre`$@ zqocRgF*P}I&oM{D&#)H`kB!HKCY*Ar6$lEFu3&yL<&?cD@+3^5VKUECj@K*|oI4;@ z$j1et(&~-2@Ou^uj@dcPzi&woO;!w02=^6^?mI9i1r=?J8IK`!(UEgY0BLs4HCVOA z9N%CHwkce?V00?GP2h(sQ6CN=W3v0sr~arx@x1i>VyP*x-3c0-%5!h;T*%pCBpMc> z3z`T9v(y-^@lHf*kM7g3IFP`?9O;SRA4pxXfd8T86mpG=>yawGc~FcOD5QSU4qf7wipcar4sTU1;Ul^*q7Fn4luZD;gF*k@ln*|Nx2&+8x_nb zPp8N&u6M>=!d!ONnVaLnk3@7!b91AklarK-fPujLb_zzBToZ1U0lOEk3}95A$FLfW z-pVq$IYcXBewcUZMiWS|c0`<^tV#0&7NQI6=jPa22?$Xp&qHyRz&I^j!8us1)=xvY zSUhj2RdFz(;PrWbRN#kd$>`NVmNd>m(l}?rJ?UcN?=_seZ`(yZb!+hqUv-4?iB(D- zbwZsM&H&C&lqulL$hCR8-YDmUuANTHY=D)4s}kwy2;~*zX4R3Vkyi-wV+a|r?qg>Z z__zTQA6Xhf69(JOU?9qsQj$jwI`oiurm?~(ST3`KuWpqF4{4JqJ0Lk$=V^(B0X*#k zZ#kEv6&LwjF}b9XKgK48$nxoM$B;vQIhnvlgNM9?o`=b1?PLhJV2oK_e^NU1SjC*4 zJl$_+apdj1)2mTJ!s5|=2aAWQ75D%tVdg?*vBmv)b=f^2>kAJZzWo?pPdQjEJ5rzUMv>37g#5us-@Ckmwrcz-(SorK@)o|FQHGw- zzwu?a=@-K0O4TcHaUGv^jx}1|lvyn9WC7WMk+~7;2$+^~x5A`{P-dJ}8t;H9)2$Yo zl_d{^LD7A?>7Oe3P~0zC4q`ntD;SKhmn3fbE{IxRZjm>(>?}0uOEF)A22_;(sWiOK z1X~`>%^_{navkcHQ=U_x0DUw!x67e{!$Qt+g}#G$)M5A? zyv88H7hS;Wt4vur;pZqo0#Bi0LyRghgi~sTO~N!TCixQJq2S2h%w(PCc$%GCUSZD) z;R98%qsh2{cs#v*f=QK$$%%93X8q5}zA*Q1?d z$WTM;A&Cay4H9r%WC(uvdTmi}-7JJTS2DlUV?9~>&vR@4IsBpNu0N!v4fIT(soCeM zpP|z6eBoluAU?%mV%oq`bkP*mNLt5EH;EMq`>vXPJkfOaRF63$s5Ea+V8CZ9Y9KWN z@@bVh11|J7gkpG3L{7JQ+A8bki7o~E@gR28Z#E}GoMBHyqiTkvf%>NeC_8n;+Kk%o z%@=eDX!;XY%ih5MB6Q-Br&@gE?fmiyqZaYYpRl%qPvTEZm+LYog`E>ZJ}kj!_4<$& zKB*KhzmWZsM2X63gnrJ5DFJpm2vNEUm6D=F&-xXm%RrhP3})~>9s5njBw2Dy;;9m; zTM{VP`|@41B-)KdD)_P1e3}?-?HCh@RmvNM*{}A58Q=DmE39V$$zNf_vG>R1QL4pB z+6#};K&0{LzK(@R_eDq=uWAdLG;U442EV3c?dge@f@4oI0-DO7p7qPISAbB945rDk z7KQ(sM057h6x>N|QaFv7+e|pw0|34d~{wRZK z^8XlSbFr&I_QHIq`08s31ne9lB!XI15@DE&LFU$At(sGh5tH&zE25~vm5_aIS_`w%fSk!j-y&!gzRmZvq%!WG#z*Uwo}Ix7^=kws272u zVNDa5mvnJ2_wQQHkQm*WogpzDMMz@QLrt^2x+m%jiFqX>xj6}-XZ=ddb3odM7|c~k z%sUe(*m*)o47HnIVs7qxG^NO_0)mp6-|wlXrp#OeV*f59ww*qv%up>pM7_ui4Q!gs z93h#pZ<-wC?{jUKqzTH_jwaG%C8HF?B{NIFS9_wpkeDwqf}0XU&-#^^2@v-44CbmN z=7$Ls>>ME^hT6?9G1v7lkWxl^s-vXhfA`cIQz~|Wpg&^-wbQ_q3aZ72p%5O37IjJ?od_uLIGxGMKC4 z_}vKs0Y*T3pG^*@TAb{?a5xQC3WwkS zAtctDoXp&qK4+WKtJ#n?_ko{;&1e&Wcr*3WV3lxMyl!LW2KQ_mO#R;TZKKLmTmTA!rXGb^Bn0#*_(3) zPm$rZJ(0mQOfwjZvwSR!6VS8$YjN4r)qqf)d;2Onw7Sp}60U zLlh5-;Jzp6bQB||!I8m7h$1X3Mh3(V#?Q#rppJoQu7*F#O-30QsKepGSv47)wTd%X zD#)|z*|0dC>tl>RwP?WyR6N%cgGw?4Y?L=kajaT^mw4*%ryb#2|B<>u@q;RSi}%bv zxijT;A6WXsj2?Dn_j@$n@mU9|^+8rE_v~AZcNk@Qku9pl zWh=WaIsie&r%l(M(n+xQ6VDjpnP!CRa#s5UEeeD1mJJEdU~#juUu;bQSdn%z5Og%lOpR$)M zzd;_n*T6iS0F%viqymI?rnXbGvmGTIH`b5(OQggB1Umn(q4kDUp>?G_jzDWCiE=L* z@DK#3!6;#J)(g)g4}*CwN(Ha==>rMcnVfNjElffV%B5M66k0PMNi>s-a|k!aIaf0? z+9e`GYc1KSUIyxpc%baHmZ?XvemofKM|&;P&rePm?hv!rG7k}v8!g4pAKlekL`s)v zlnL|D&~#70x$+8(Gez)Hihvo(ID_oBHg1pi#3jN@`5nO}dSnPa)0bv;@|uw$cY(Zr z$zYmZN?f7A@a+ihVcjt!SZkPU1y`)G=k9u<#kzmcQ{ z@l2iZ%0fr~6YH-D!C1h`)FPd6@f(4L78gIr8_LWU@<{L!9MWeY@xiar8GaEApWS0J zC1&Oj8lhiw!I)|Z$1e@0lGHnM9Nyey$cALs#8WjA*H}kFr(@^L_-^!NC+gc`ys4I{ zIH(rqQ9Eawii7CJ6^FgnAJe$Rs*NIFK87dVF#HJB;$hj{!g$_cG_W1D-bPPYj>2pv zBr?2(HzKnP#7Wt3Cvu}T->8)IIq8Pmc_eoneXbs;!TYtI2Je0)v2Ff6*mYG-bpyS945(@ODJyUx#*+Ty_6iB)n&_ zwaL^XPGyNg$9Ni}qx(i#Al2|3jxBKCrnc9%C0Ps4E4$#9ZRn-+-$;g6nkJ3d-gyIIW;|u00m7a@XrFl zdW@PpeB7)WM|E=w$q@P_7s2)ARtafBTg~NGQ{l7VmYVg(N+CoJQ4AwO?6rXOaU>O$ zxjoacL6WP3hop1)4h~6Fx1&X|+F|nD?+w#+cuxMFlMG7!o<ZAnC_bZ9}O%+*Y6i?ASKixOH7P4@&(TVi~f zdZ!%;=vWPpU zr*q5n^fa56Kg9Dlv6)lm{OK72vg~&^{l)Af>b)m2i@S(LvKh7xT%J8qf7Qn9F==L zRpR$>Xh^>m8(vAieP_zE_h`?e;rl#XjZ};Gz2e^r#rO7v;-h@q?LAa!CJ>lnRS|qt zZ}y5qvH5c<&CshZa91*>(xaoy_6Pr7D=!vzpVS^jmv(}nnYgH)IFqooqp+M{q`XmG z|5Q(17l#8xpDD%p$5B6h6ZtB5x!H$`?EAQW6 z!0!omLNLa3NKz)uhl*5Nw{W`WBb6x*7h>Etp?P%~Mq2Ki(7mrn04@ z2hV2s+Zn_ZJ*vff-HYfEtwwXSWr`lv8p=^ih#s+UZ=$yq4JgsGcSxMRkmgBpOz;vc zIHlf;)FBITm`OcQgLbT^K@+DgRJ+myT>-|^eLm4PC5wsOpww3!Ay8l*_}E;dxc$I- zikh$9Q_fEd1xG0nCHlZwHx!X|H7saoEfe>m*`*f=qazVgI&Zui@02iQMpxv-T5^EN zHWT797QU`D51mCr+{f7tUJGQSVcgcjF0cq9`njR(g(dxWt&aQF%HA2eaH@v=a{hI* zd^}A{;uX9c6EEfOttbIjpi54}xX_kbT|SfL2okjydQG~@iQoRZ!j_Q2qad$+?0=bF zt%u&kuBX&8olt7~=U|!6`^FTvxlHFR$YnbFrKJB4FxLGp(?1l~ed;enI1#v;r_W`& z>Td8U>UnaR{ysc>5|-(#XMCCd6<}a2)7iVuWjZ~EgF-ITWpAKOJ|Kn^im~f5{rW=$ zhcf!!D#E=zaDJ798Jx_>jKin(;Z=OAvS#6@|~**N(HM#zeqo5WQPa7D{|Rp7W4 zht^;P)Kw(nHaslzFGvE`7~vNQMlg5wF1Mf{XSJ84NA68(jSO61(~C*Jf!N<8%-H_k z)ww#Wv~aRZrV&rJCMM2*Pjuj33T=FL=xg{rhm;$j1ow)(%*I{B$ymo-zQI~_RPm$$LM zMo=LBt@`y#J@rd0aQ9KA7*Sfa8UCAbZQwfmH@&Q6uj~ZGy*L_>#F90|ViCqSTFGs`wt{iQN1*KN& zwj8y@f|6LKn{g4o9MJ&Xt+^YuxG%>}2C<-&nnYUJJ{FY!TC^xZ*xMBgO4Y6uURqEZ zZi{F083Qba*w}O27On)srSv73o)vzE9=4-vImNT@=m}L*CDA%kVhvP{weO{Ld6hOR znibyH*o~cCfFlRk9|UgaWgRS-gA!nC_#32tRtKq<+*-?^vaN_B@F!M5;Hymp4FVg^ z1+rXnzwLzSToA8fqCR$>iArf=HKOX8>l`{4{;Eqa5?W9qr0mUg-SmiXF39D86lGH< z+MEl)8!$TwegZ^;e?#AXO5ffXe|rU{$2e7f4eKi21NthlU<2WTYmT!o|HI=P>p(f{cKxR@|(9>>uNY=vUFH{riJ zb|l_({Fa?foNtr%kiR zwsPwv8O(ou+~$BIr|bBpG|Kiwjf8c1hjEaGxaV~cNovN%@cx;m{oaS(t)js#ux)O#4f9L`ibR| z)7aSv3l`HzT$Djfud&yi*8b`%G2=?=8*#uFXlLl$hiH#h{OFvS=|BT77u$Kr8Z!~eJy|6nUXBqbzRkB4A<(aMC@ zN=&B2oqH8)D}7UmwQ^30#1#-Gg##xvB0Xe9KL?RaJE-?pn%;xdgsx095m2>*btv|P z`3$eHnSeHTA-)v|X)eTc^&mZpSK0t-4n`^cAjqH~J=nRcgK_pw4>y(aNIiDjue^{~ z2HVk@MutN6xuENMfT3~7qXNy?E}fTiLLZ2U(W|pj;%75zq(FqT@#$*F$oKfJm`K5r z?%TRF<<|N=`_iwi?^4#dx87bBswBIBGMisa@Q*o5nF#3q_u!TIFwXJOh>2_K{|o?Z z&MwQf^`DK~4n9L)_Z;44gA}|GXhi0v^+B7S4ec%vLD|V;?ZSEibiEiGNOJF+&VL=_ zm^Q7@6)Mq0_y{B?U>zfH*q&T?-jEhWRIZtcP z;cfO_NCPO2f_IJ?{5r}2rX7^86vt~*yI9$?R$2c0A627;UE&o1Z5 zS(n}fmeIGxyRS>1m#ATCd_R3%O3m2BL0^|%A2$$v=xMMnmBZe-9v#!AKrUj;hsuz? zFz$7lp|Gz%6~%&M1SYxuygsCub%Xn+oNgQ`d?Vk87h>Bwp@=d+i_2^>%E&$^o~llt zw01gJld_2Oe+uoth_tn|(9)yEm82@gg(XMV}vgpD(KE8RKs+twyBI8y*uq#K0=6V{3gd@c_C z8++SN3WxqR8=dZS`)51u={Al7PYgXj2Sk3E2Q}{6bfIZ=6Eb5P`n+AZ+}cFO(2Fjl zddAk%G)3#fE_(wx$9KnCtiMP@#STQ)OuxP{-T_mobQ9L@>~P55)pT|cV)X6UHV*8U zS_j{h4Saze_z#j^O#fR@htgQrd7-ORs(S`D?T)5;m znh&(LhSxR@RKWfF_>Ms_pq^gd@+~vd8R{EyWs^%`KL`rw6f7XhA4kFoEm89Sxd5tpo zRMieAI1Pa2JeNxsbbODnzhK+0&huNc9N#i! zA5RIu8qap0Pxk~xQ{O*GaKrgIt^2p474mNm`rEPFdqH-X-_8DP%inW^{7HKQiJxut zO)MuI=ufv5H#yE9kBMVC&M!%EoZp=8H@6oxETOe1CVH9gmC4_--8TKp;WXa}05(^* z#c4hqw;c@8*FA>^ZIH5^=7YNnAZF5ME?|<+oQ4Gg+|OauQ6w@%o=cqCjpHr#M~aer z|R2JT8u;&QPTp*?q#%IFkA!ku+TTOC~2B=CET&+n5@0L;_IbK9&5rIYbcymcGK2u%?_ki~e@294eLR29?31gn4A=)jBM^1r66;e4yJp0;t!GC{V*!g>2^K4J3Z6=q{<4XdG^JjUm*;1 zru$9?)6DM9CM;q~otPpmB$_i9h)KTwE@&y*5PS`u{;@`25%z8zedOn_x-nhM%6vd* zS1@b5HKTYsfxa2Ja8atTum%8sYlk3dfPW(a*iIJ_tw7lDY-9xuR1!z~h>9bzVniu+ zyvd;25fCxFS}o28_O`^4YmIwuV{Mu8_>S(X#Sr=|WyRFW`A{jz)Ak+sX34mzFeP6k z9C*qGe5fa$5HkLL!4tZS)3bhMoRT#BHXo($X@UpM)NCkauliyg+o%b<2JEjA!0coYVon%zig^+* zyXcu>PWdjr#-N%9^lx3gxWu$w2UuFqf12pINyZJ*?n_2WdSd<|Q9T#os7*$=wZ>g` zc2wj=dvcbf#x&CyZ@S2ouilWmI?u$$LdjkResetV_FATXCwAdFz+TJL@1!}nez(^W zAvKy+cpe)!I~7Gp4NZBwtpG(xjc9;E%BBQ2*%k;iuNEOSavdfQGW2m==I)+YL>!k< z?aDd*4$OayFp4+zeq2WG>zdp7>dpW*4a#-DKw__T|M=d*p1tEd^((aNQxZ3BlE4Em z#)zDk!#sk|u0$Jh05NLk=`c^FOD__Kc_cz=m3U1z=@Ex{q~}V?fGN}65A(bNi1Ncc zucUA6Fc15->cc!HCdnlPN8f*W_WVcSE-qU#j`Q&tnAgRqzdlBted`Bx|7a%#C;WS& zb(QiSWcP63n#B&&1a=Z=-RR(w02MCR7Eh)#6} z$ol9q@60mtXcn;-C}3lgXoz31 z(Dv_*wl^DYEvzD@uZ~+w8udlQmeci^6VYI^-gK+&!)u~ZS;i~lXN%Ath&Gp64WQR7 z3Rp*?G5Ms5g?{@;v_T{m3ER%GtKlCs0{M-C(*VAfI+@0PkVV5(`1kySzC)|$e+?a8*RqCxw4Gp zrYzZO2!NOKCn8I1Sft3qB0Cp`&Y(R}fiA0H55^iET|ZN-dZ#>)b8Unx)2s2%!Dy>@ zrUEK9-DWG4g$AR+vNzva1ia1S!cmk!o`)qo8NqOrJn9nRSE=D1ks|n{P8V;qqmfo~ zVfvZY zg@deph4~^;Il6%Q0Nx9Ikf@42wQ)bLxR4bTFXQ)GuLdb^<92;dg>gfTmfzIwDA zdaZK3cxH(h>HMiBM<8=*heb+(|T|OXX$jc#I2r*!8ys+%yA;x`;|L1W>Zr^p-F@O$^5d zJfD8f?(0F%&5eap0z5rY;IEP3A-pcJ>`7c4RPmY%tPLoH8NazyMPnmKx(q2QH~seE z4bcwp9rLpk))qD!54l zW5F+IVo^v?Nnn|!d8qNQxdIG)5AjKlL>D3dgL|^*Eg{)z(Jhxr?_%W7Mq3FoX=6$v z$#vQz(K@eos(qw2A8p{;1mX)$q|0_78_gm=w7~dgE06+bHKA?+qwFoP%Z|7EV_~$- zctsY4-a%uvmYIfP31iy+_K;dxh~E#9WR=7An-8s9!~T(pf!({!4Uw1%Q0OHr2DeT3 z1aBj&`)2z118jB&zegWmr;o4F$5(I*eDEdu_-Fd~M|`xR5dy<5Ry!RjPoUiMAjMe?t03mg8ffKjGyd(O}%A`Fb_xZB}%9;$d8R z#DF*=TV<8D8Sr5`8qGEo!9__};teb~v|*`^6pp0!Hsi@0`nBg(=e;i++-xgUP)p1UL4QS6z{3Keo^m!J^}8Apnz?lBg3NZ-g< z_bUUh>0+dfG!SNK5b?20B|SFwM9co8-|u~>i#*Si2}G7%>JNJ|VH19K))U6$`fMFoG~t6c?%zAE_Tka*JRZdb@AmL_W|*3AGc%E;+{AswS!4unvs4%!!y2n)+Y22Az4`Ze^gm`YHEGeHhq9UKYj97SyEO?z&78@rH?P{T4)nWuRwM$seLu4EH4>W?gJl3aYC6vB4niisw5qMW`@ZS9jC${KB z@k{&$_IH4D!Ur06&eXsp`M}3KAlMJWEKPYxEF6qw%!6ng%!$0+K%Z7rKrG>i)>+^|SBUi?}7MSRTdM#82M&y8^B%P1l&_htQN0Be&t8@X%S7BY&^BChRr zWv&ryq(w495+ZlLWV;?lI!og#=yuU*=CUfDi;uV$)`y9|5g-yUj)2exFrF-;$L>~l zfBa0bJm<=>`QkwP#Z5o~fV{!Qk+`5jdE$6oR*ov>_4MgH3Hf{t_ya6fOe`U4zssjN z=o2)pBnn1XfvI) z)eQq}J1b0H7&jD;hPE@|DOb#7%HhRVe#4R+FPjQLg{#2W)Y;{Mvzw(00nWXILbrM? zRE)Q6+-N`@iB{oCi!gHK?7B=%2XiGOJhJA zFhFdfK@BJ~KWC-otvGvUVP(OD_@~_|jm{jXx8A??|gnTjwB=70^3p{hBwZ4{|7VnjM*RHH$(?G7tU7R*F;h1%bCR3v%O z;1OB%0tqOpl*MFl*GoJb=;~*pT=OEzf@wl%+5_z}&4c-!9wK1tQ>x1zue(7cHA7LQ zPFZ@nUh@Q;+d?pya#K$_7~QVag1TzjSA7eSspiTPlc!`Y!hLU|%Hf{MSCrCyA{$_n zPJqH#Cepay@BP5;_BBqIjji5K=e^>hQV?&nuVWNANB6VT}vX?h2s85o}6 zdJL{(1M=fH?!AS4?x=CNV4fs1^KaJX!CXdoz2vGdxf%O2Pz?q&G~=+2VIxooL$*8L zuy+8+Bxsn~)IXYcfRJmur>|1Orh$+X5#tB&uEiO3tu37}d=Pw4fYKU?dd0t&uR)xJ ztZYd3PAC}s!Nr$k!&UkeV)tc{;|TgF@r|Xb0q8=I$aD-0R7K%{ssu{xf|{S*aTY6? zNR#KK17y&#BGC7Wnk?rAXWR;*bs6x4$vD9G6BS+W^?o%r0ju%2ENItum8VY~8jam3 zO9{9OB;wmcf*U3%M?^nu?=OBoQ7IRPVoTeD8%yP|{v6C$_%J!tbjr9{r*W7@e`+g_ zop_gNyH1Ox>1LVO`UFKmaT<|;=RG+KZ8LVR&uh}Y@)0u$Kh;$dO8=Wmwu>~<)D z59&QQr<4$ZYrQh4c}b~qa z!Q6rJ5@LD;hR}S%ERJyvhymSY{hdnw8D;EW=gayN_ebyr(=C{XuO*H0R-W3Fnt3sX z;~jmaLAqMn^heRErciui!W^4mLJ?nGeQf)`!7V5PU$2Bl?`}Q)-|b1=L(}Uu%!>N1 zoU6tCCuhq`OZpqWjnMd?ybT?wKEOYV!_)kvWLs5#TefQi3efbo!(U%e&nf>i0A;ZH lGHa39)6$;ZY_j4(zFEzGAk~DvIJ*%wgQ{-QR9}qx{|2$Dc7Ff> diff --git a/mddocs/doctrees/connection/file_df_connection/spark_s3/prerequisites.doctree b/mddocs/doctrees/connection/file_df_connection/spark_s3/prerequisites.doctree deleted file mode 100644 index 6580854bd07d8fd62db499db739f0357aeb11e69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13870 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 dyeG*ba8ZU$t?>Vpw8%Q<{{hgEk*WXy diff --git a/mddocs/doctrees/connection/file_df_connection/spark_s3/troubleshooting.doctree b/mddocs/doctrees/connection/file_df_connection/spark_s3/troubleshooting.doctree deleted file mode 100644 index 10109b746bbebba14e708eb742d37b66dca6e59d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73472 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 diff --git a/mddocs/doctrees/contributing.doctree b/mddocs/doctrees/contributing.doctree deleted file mode 100644 index d0484099aae2270a8e71dfba4e22c78d7e7bd14e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57687 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 diff --git a/mddocs/doctrees/db/db_reader.doctree b/mddocs/doctrees/db/db_reader.doctree deleted file mode 100644 index 996eb03473054796f3c2e29a6053c44cb622f7b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 85522 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 E|E}dJj{pDw diff --git a/mddocs/doctrees/db/db_writer.doctree b/mddocs/doctrees/db/db_writer.doctree deleted file mode 100644 index 61d1b9291d599a82f6cfd916c32b73d160e5d06f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29431 zcmeHQYmgjQb=FGz9IaLlB)_n6dyVmEy*s;-4JLyn2T3*-!s{1hl*F6a*_qz$Zp};& z-93^vmN0R}wiGVm;jy78NGNdPq$+^|QsI#RMab(9A;}NGfuwjO5Ga%U36)AJsmgcm zqwnpWncf+BjY(Bp*6hsf`#AT!?z!ild;0AoPdy)O;s3FnVbgINXA9+Owc%Iou+7G+ zex+5n8&Uho_WoaOpKs^cL{L5x`mLa1x7ilds5ow|8rY5Y5*|)b{m89_1=+#nVQVFf z+z5q!qg1t5-G)nFuZJ3 zaqU_)EV%ysO1Zjb&);xxVNSNgSKe}NrPi`@7XB+n|PR2AsSj zyD`5|IB;{})$_dhyxXYSXA4eLujSg#*ize>z=Z8p8xzEY6%qjSjs(D}?g)q5toP_k zKNAid%ax-ja93Ip=t^|l=2k<{jEz-lD zXD{2<3fwk(^(7Ig&2p7;qv}@60va}Sd)t`_TWl;UuhFn==Q1{`xNP#aAdE`Qz_0mh zhudtp<~Hp1iLGq3Sq51E%%0Xt*|{2%a$W(7S@<`Nf3L*9S7B14fSkj6tAcevPH4BB zg0ssx=uCDUXKz^zMfGydytCjOII$J=x3P&*EI`h-)(W_3RIsJVB2(c1QgvnSOyEX# zP!zn>b2=lKj$3WBiMR!a?+C^mp)sA;sp8j4VbiV{NECqfW`bc}pY3|+ zxbKXa_lmxNP&)m@wm-)8^21J zP7U+805Z^MZQTf1_Z=)NB%go*w|Q+2r~Nu>V`Y{znzI)neP59B=Fp ze$wiFsu!vX6=OYwRp6ohQqz1&4bG0d@V*71KdZX*E*sjiMe3DFB`*Yp)-B!@RE1kC z-Ry$A^Jg1t7H=>5$YS+axj27o>Jx7o?fO?K&exr9IL|FQf6w;!r5N}`H&r=!yZA(% zB{ob8xq(G`U}72Xp)(UHO!w%d^Nj@yRLHxc))V0o8ix|>6#Q!KIzJ=}N zu$LPRKPsoTaO~hO-FAJ1ok-ETkt6%Y!?V_HYOdHvf%Zi!u43JI_%JrkeAlRcExUw-5n=mVq0u%kYOLvt{Hrcosz$r$*I~dBpt(LW>U}wJ~eV|7%rr1vRGbj zyO!7nwKP`05Kpt~Qp&c2wdWw>91OODpyJf!zY`{V5{rM5#-jJfRHCLg!Kas=o8CwC z-dK2lvjN281hD{joJQFQ(!b7zbPtD=_H1<<8=#sC$A>|Vcm7PtwiH37mSU()h=*o` zcx_?3`P6_e=nZV`B0uO>Z;7FWW|)7?L_ZtyJH_yMf#H9_pR8pi$w1#1jlRnpc|X7( zWg)VwwX!T)X>A#!fb%M8=2Du?a=N|JG-i>W>%>;21G)(Z8)|gUb&Q%E^tRy-m*c%L zj+?DUC2Dmj+9Bz>1DcBlny=bm=w|28SC~V4zincyhf3X_If(ZObkR$viV1q-=pm-d zuCBCj;@yJuz$~%OiW=vy%Y-RuhUjg+F!=ROUoJt3t$AUvWmh-~q$918^bR=RFdYD7 za(xm+zZIaT8Qj~8!VV-RxTLpH*SAFExGBdi1i}AF!bm5aRI2iqMtM0C<(V^B;#2mn z#GjZAdT_L#J<1Ju+<_;_(sGo3So(bSC|N8mM@g{sHZ0&mM|5&Af2OhcuFR2pF|KzU z&GdfKJ3&9mf2ABsd=&1p8iWOekpsDEKZf9^uwuN%^oF-Q{^ZLPTkiP-2%^DRPa=?y0P+nCljtq=40vq z=wrJwxZ?c>LEC3BEQniywby(ZvJs;Pd&z8}^oW==JeHKaiPIAL=gido~3fzZ3M zNBPfgHkUM@2T(2#W{;A^8e$YWbaos3(A?PV2&zdtBF!qRnPbx4akbp4MWxeaL|Y`y zSN1X$7!l$`GmTfuLnH=Rg&-~_hpz${WM}%)&ZJZq6wGcZ4-WCx5vTw zHs}H{v{%5`&*}lZcXIL}m;j*?*m@vjfp()fc7pFGdBVj;`#Wjk7J)O$##dXl8sEuy ze}_R>t`zwd0yPTT9s`ni@#8rB;tzC9LSmbspN~PtMBYOs{Imzo7~yQdt|&5tcGQt+ z-qA-pZ$7#S$II!s9l1y?u{$=UcSG2oPO+r3TUx0Uq;t9PeZ;v6M*L+xU6M}T+3W0g zW}KVOx1BkPI)N)=E05YZ=>4V~g$%9{)T6kMUXd8i&pAbMj5_m+#7=Qb%|q!>6xv7R_!YJV`U`^;ZcPJ>5bCdlUdKR zF>*LeaY0KaFy+A+Gp=4G-k~h;g$6>${J?SNkeesaVN0Avd|_ ze3JH8^@dw5saaJt2mC0eLIwwnRRorL^1%1wUKGN|;o4bZqXLPBy3sYocust($u3`B z?t<;*Wi_Ffb$M#^{68@a-DYBPByk`jC%g_=;FssbHG1u+qv74B;6?a};sOorZUL?F z%;4CIF0bfhMrVM^>ND%$K<`r%^WJe$QebNv0pm~ioMrR?StOU^k-h- zhuo7fAgTDKllKD=Gc(#vIx}MxEfJoi_%uGQGLNd3c@V8w#m_iy#i4Vr2yr-}b?k_7 z24*2H6E5cnXM6?g0o%5U@OTuL5uB*U43{k#T8MbGnHk{^;efy=!^e3u>D+mod)CCs z+PocAEW#;8q=~>bifO|y^gz+Q$5%}loN^o;C#}y}9v0WIp&H|uwsi}EzZMx^qH7%G zP=N?D<-NL%O07j!dk&vUKZhZ1%hgMS%QJc%_Qd`WO<=*Yhi(RM}XeX z&s(Z!&)1dZWiGk&i-PNxmkA-#Tf#9?bt}TY4`_V zR-Td=l*1IPqlgwr&s^k5HVL}8bg~L&Vl#8DnHwciG)DNwyj|fZDup|S; zp^*w}6q_0UCuDkp&RtbRdw1nQA&@vYNK`5z{H|^&6L#U!1a$L-lTV949>)1t0>oEe z5{PksqY!k2DVURym*(>`|#G+BWo_lX2plH_(8^L4%jcAn##U_@JOfKf7%5PqrF zLZZ>x4FDz6`!pd$kW)9#iG@^)tHH0*7SM+CeVZDqy?HgqK zx~|TA;Dq7seS-QBOg5EV%Z-6Pl^Q6?8^l#O^1xMvF?`O!Ea+*Me;gfUZmQ@G2ZaZx zBKSU!b|!1l7|EbugS9SznbLDfanB@Jg0ROo1rxE+VoW)W+ursqbxo0Wa??m%KeNF! zi7`KdI>}Z-v+h?G)ur|tMhjEgl0slANFuLXntq5z>7|Lv%#N}to?M!yK=FU(eR^l` zC#E<*a+Gq+ziYUMN0P&3!!7kCjUl-cE{cyDL#Z8+R**Lhz+4>rK<_|p^j!80G8a=S zcLHyF&=|DPfS-R!8C@_Ua8duMf??>m>r>b?d>=bg+aN`^?Z)QF=;@|ZPnkT%eH}D5 z*Szhb0_&&~o6Iby_uC4nPDSp&V5oYtWiMyf(RfS)=e?z517{alqKC6{*f}bPdyd)u`@f+hbtiA5R>N(v6Az?M4gK*+f1fwH_?ru)W4j9E~$t5a}s=!<0aYB zw1Im$3BH1M=1#8jdZ)3IyV&6*7-YhCyRdH=P zmg0MvST%7&fYEu6fF@hzN_(8xuwQ%pzD?JWF{jTD*tLP$|4O09FgACkz0nHAlctl} z=xgg}QmA{gp+Yr{&I!M#)6;EqoCY$JHm3b~1<3gyVhn9Le)wm7Wr^v!BLS&_kgHOd zHmG*wg)fzg&WB&vnSufDbPC>$IfG`%Ib%47h1=*VW9oNr>iVv*aD3CwLX275a)}7( zoAa;IoR8xKI(jjjsjM)Vts+nnOOW>_f?h0jgZ88sVri3bC$$oX&{=o! zsOo{l0263cpb{RfmW{kwF^CFWQV|F^n;H#TE4U$PveWn-;w&;gYu5#u-cJldZ|^ax ztplet9T%V#kop5>E_526IQ@}6Fz;!!RW7*RWnHQnPNu-vIy_PXt)v4pAz`^Q2$jwz^itfWv_@j;utxOXbeQ}kjY9)D>c7z$|U-; ztAUThYo66hpMW8A808VO?)5`trLn`DQLDP5o~rx2xm;7$! zmGk@{Sr2tcwVmY;gq!HlG_o^QYj&ZEltAm#vpI1|7u$jVGw=$I0$=Um>*3yx> zY-H7^jBpp(cnyJSEhKMii;G-Sj=Sd6@GmMM;(=0+5taXfxG7eRH)AY8@N9J@dE%nzL&*Pkm_!M;CJh3C`+?44Fs{`SRNUAqK{TU(tJa z#|-k`Q$`uPURedyA=2>_tz!sKypwVZ?6>9)^ZXo~)Oqi#X1i^a|45z4CUYU(Pt@zV zisZd-q95;D_(+`K=tY<~K&C)gjJtY?IN&?xoOdM9!)KpN1JI>HK6&qXYjss&jlSxo z*Ntb|%_E$7q0B~8Fl+J_VD~%xXR=4hx&F3(oW7(r*UGsC0d8bT^owrO_R5?<_t*pRCUZF;4?qQ5uc%9Tqdy>DrDTFBi z=ESvhG0dgX>vxK}n9B({7ig0AqE&L) zGcVlTK45CJo_FgO2JOfY?&o973w8P8T%-H<7$<{Xqf_v0;8>zP)Jw3k(N(%1#&h$I z8+#;nKvIPVlB*zczfoJaW@h--1lI}>;U8<qnj&J-6R#PsrpESWds|I&Hdw(i7DxYISrf^?;h78y&VJxl05^- zQ@ro!1Aub`7#EfnJ=0FE^6iNnwMX?#h9z6fLYZliNyd?f>O@92BH4aZ6aag2(dIT)#s3-b{; zHfdHrG7wg!PIwrNNGF&VE~w1ZQJHE$UAT}elmiQ$;e93pEw0^wDoWXHSWk1&((BAp=dSx359K!JXK0wA)E6!bwt=fQ#AtqDIN(#^y5(=ZPdB>AsN zLOh3MNUcAc=$jjx$exRam$Mn!joN(FtIzaCtoW@8Ah;zVr2A54dPq((y_!qRs0=8Xn2~t|6ULNx zJrLl%hCW_NAGgxS4e_^E;oC*;M*f-SpZode4F9}|f6me;(N|9ZX_Gsk&pY?f8+Q(X zF!KF0c#{&&BZc@R-sWPPuamhCDU9VSUBsJE*p+zU0WL|GaMww^m8e=;Sr>^h&$qqT z103gA$S&_%3-66V(vy2oA&ckYH}5$2vb`!P2JY+9D|qNgRlGlFBtov?TKfd@*Qh$k zzHwqp3&?PW*i@wzAlGbEl5g=@WRt3dry^QpH!+aLPmsgiB+i4^na= z?JZ%Wek*FWqV}TmAYRn4MtPBWqEq~U?PZ4%t|Q|W4%-9Yr?&;+&h6PY8=A+3FaZK( z1UiKZIr*~{)bMKu<=&z98_`q54?N5EtfB)7STTw(;C)X(I^6iBvpDIj<7E?5ln}Uy zY|q^LAEK+5ixPQk5SGs1Y_Y_ziDDWUNj9K+qr6}ecckf+S2g>z4Mc8bIS21nQa?x7 zZrr8@{*ckR73$KFs=d-$Lw{5Bstd>uYT%_-b(Bni5EYvyL-4r`U<>!JD@gMKDPt3@ zXm#$jb3B*P)yan?I|%%sgzOnOxekDP0u^bF*iM0Sqn8ohOB>fRIW^)ZsV=;?f)Ux2 z=vTv_T3?-fkZR0!(HxM9C3NI9@;2fCA^7(d$w$3}qT$ zWTVhHt4P@djJQ?B3P`*(MQPJZx`=IsKp>kH4rfX+GXk-^zy%)$jWRN^FXEavRpD$- z)YnDL&~8=z5|XH+)-K4Lj~w7;OU@;b*3)k2QdVB{RjRrnr8}TR;R4DrB_t5INlXSW zOhXQGNF1u~kkv}7ZVi*e`>ZabZx{rr+NlmGK)qE#^6hvvTm19H5ZldjVAX8ohj+mJ zb-X0y?p-&ssixi3>VqbOx6L%c?SP$xf?c6rl!fB3AXeFeAFM%^_EAj^0BV+Sj~%!C z5!wZht=*ivVc{0wb1GP^q`IdP-TkXn_q1gqmOW($1=o&N`4~_I^Ys2T3^svNI7ktH z6VN!j20U3W#ljiuKbdy?BHI3-O#NAu(6=#MI3aQ4fLyeyOkERbq*% zdsBsR?TJLS2{kJ3e@~)+XzUPF{x?D<*VjaUY|kLusart1j@zJxP&7zt6bXdLX;b9Q7;uK%A)}e$ zl;l_|BL><6Eo^{qVfq{T&>z{4oSD7B3DCS$z(T{rGl%E$osVb!+WGF^qpk2~Z|RbY zVm=|17LqdUY>&#M%2{FDx9;bEb1&UP+gIdT%St88*)3=!Tx3+S!ac+0$m<)C=}BYY zEnUUh2m_%kBFbi>5dLvtk9X|9CX?|ux++VlOvI(U(%+0lPEvM$KIX=h`g}SSc{sYB+3X9o!> zsGy|w#%_P)_%C#2_YFz?v5w!h-4zkuUlXLM%uWo63B01BN@4LtNyRxm}3RDKai!jQbg;=uO^r~u%YJG(t~_Mmno!s%B~7&OaD zWmp}e9+4+6zYB*tzPvZip3GpTkcO>nz5=Bq`+!F@pGlRwS9YWU-7-RZ_$yGu;5)U1 zWh_XdYhifQ!Vl*)FEosb{C4h^h2a+Mz72QXDtB zCaOR#p@EjkA?c%V3G*R&|3rbzMYx(Ig_g7Z%FNDwbEXBJ@2zh}Oev`%4%*ECOt33r zOAIhu=YPdYCs=Xz{>ILR3H~2Ds*NrkXkWdzzOnJ9J@Nqnewrgc83JFm9FnZXiMJ?UgyZ#A>Xr@AYvcg-VsP_Q~3U#WNV8LVGa)3>d0)?5# zg>j!B{mhO^RxU}z2Q&1Nn80`67xwM;B7|5^${WLFF_9`oJKgb`!2xJShUF-aK{&;Ro>UWzyq*sB?nZ@h=rxS|d@n!@6EZnM_E1li2$S zC-4V_ubT~74k^UjS5=84{QMeNsNn!yAdrY+1`IT$aN=14M7yCD#yp%w>Q3}S%9R6T zaM@6_?+rZQj2WD9CxO;g#Bw6C2&1;A-r)7Ix9rDc&9YfdXs_)qE1tW{Dz?3vO3=GN zBF0pK-Y`);O@zVhcyrYDgw9X-P}`HWt$MC|iO$%VVNbO`WwzM&larqk{pIq^@h3#v zeZSerUht>d_PxN|l!yRoFJ5W>u2<(@YJ0d^OTTRo69Gi{5MjBN_?B(;>M&L%wH~fe z)IN`8wtz=aJ>3v4wEJro`|YXWrIR)Lpk8JCL~&=+RWhx-$n12ZYw3{$iQm(>R@cx6?LQE;ShG&-!6F0Mr0X(lXud%zq?F--6` z?e6`V&Ha~|J^K^|P#$vQ8W7_L&3yP^Ec6&R-FVSXbOKNPryPI$_1oK9TlxoPXWaQ; z--QlTZ{y$E$g??ScbZFW+wOTdpf^9e{KHd!(bOM{;J#*Z?|8GvwLN>V$WB1^K{KzR UlT!ahGNAqjXvor!w^cm(FA_by!2kdN diff --git a/mddocs/doctrees/file/file_downloader/file_downloader.doctree b/mddocs/doctrees/file/file_downloader/file_downloader.doctree deleted file mode 100644 index 630c3354a49f3d93fd87744975845653e9fd2de3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 82948 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 diff --git a/mddocs/doctrees/file/file_downloader/index.doctree b/mddocs/doctrees/file/file_downloader/index.doctree deleted file mode 100644 index b8836d4a108d7497637b8251207b4dfc0fe28684..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4189 zcmc&%ZI2tb5xzI+B;EVvoHUIc2u6}#3!G#-O&c7F7Hy57#i6=1DU5zG7St{!(#KtH zNv=z zp3Md%NivyG?QAEJagox@xUbv~|K?t}W7}2aQp-Zc)Y%ni#EgqX(ab%?;;z>>T1m%Clk3yrkf+mu zW}J#d54aqTNiwCw_a8ktslLNw`0`{d3fk-A@6-@ArDkqIX4Hs*q9iel!p~rcX2S=A zhaV0;8ivn@JWJ?oz)UK7j%_}5tcwHF3B>_%;5rC!dgq42wQei;y{Py1|NlvXbGAo} zQG8q&{AEny3q&F4tP^1zJF@GA;?92bKPAZ7 zUQDutC#3YouKvQYAL+tw8Z!0UI`*b**C^ZmnbIc8l@xONb7waM&!{_Fv+bN9EpX9=(dl8 z*S~@%E$<2YC4NJD3Ye#KsDTBdh9=F2Ar?bVhoQF;{(;lA9~B{x02kcf)HGh=FKc_E z9OG;)5MA70$=)Zh*?-)at9i2V2RidL#ePTOr3+;SKL4@wdBh%$ZjF1DUmx@*Q2)GC zXP@H88^PC3EYn;TS+Wr9(pk0wq{-MQy6A1S_PTJhNA~-#4-tq2EGi!8b3C%|-Ky$^ z3(<97x{8Rc2Su6@)T}8P+`!jpN{z7dhC9{O)hPks< zFr5UWw?y4;Oobe~Yujc#Nl?tM?M`L05p=R59itE??rg>OLaMdQ#1(XV=n3=Fhu3;9 z_R=RP1uJ}F+DO#1XP3dQP$W8bpJ+P-zG3K$B7b0{o&UHeyW z+LSx?sXa1^WLgl90AKf}NOf7XTSgip$^=w8As>Yg(yHB!3*}2%EsrXg6((>TBa`tcnGnq7WVeu7OE#eOF zMYD-ise5I2HHel+ptm1QGz`8|N~oYg5?yk`qPnqRzv_dk(;+O1hG|34=M>>wvpt5k z7FDk;dq8I#Q8dI9dM<4xbX-ia-R>n(8KMXc%o8ajU8F3g{z%?Go*`xtMh2Nc%h_&W zCMO@CXpZN*Hy0zSlvEKzI}|`ZV1Tp51{^ITzhI>mtT=mbX=TX-`X+9LU)nL9%IqVGQ9;@VI7Ns2U&8ayJZLBIh;l@Z|$?gdF;gHQcjlxtcfGMc3xP5Ypo z)HGWBGC%}ueadf$eE7p`d&!uOXj^~ZvKPXJeW&7UN5K` zaB3!D4^)GppL?-c*N`zNggM!rb?iL=GL0H$HuW#kxkt#g-Pc!{A+w0_DHHesycW%PTzD7dEq->~SZC)w{|M0anGNENItuoo3G+`jG9Er3Bms5^;Ut;D(9HDKFsWVEGE!#~u@HH~nHMJ4Q1)XWy`|3)}TlbCu&5emeg475nD8 zVDI|pa|}#hv(F2=vzS@0XLn)_eE8mjeM*TZaIIG!YEe?^Vblf^(yB)U*U8w-DjW* zZVn(F?m8Nkt=zDwGK*piwY&OCgKxF8X^pH^&5O7p!W@}mc46OLeQHmCg9Ip#yMBp^ z{{5wSB@Fxb`&{?Y`}z&Rk^#K+(=dDV?Bmsy75yJ>Iq3h-egYk+eu)2;N36wp%C@RD zw`w;$Ho(^3jy^f}t*5-!qZ(Fstd=+Xu(W5lng|7~Z&mXlxHa{6$6n%lLRCKfEMANU F{|2XCVWt28 diff --git a/mddocs/doctrees/file/file_downloader/options.doctree b/mddocs/doctrees/file/file_downloader/options.doctree deleted file mode 100644 index e167bfd87af29c8f8ea13072f851dadca87db45c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20877 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 H^Tz)JUYkKE diff --git a/mddocs/doctrees/file/file_downloader/result.doctree b/mddocs/doctrees/file/file_downloader/result.doctree deleted file mode 100644 index 143ab6540ad48695d947a05e9b6c4f4785e81b71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124226 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 diff --git a/mddocs/doctrees/file/file_filters/base.doctree b/mddocs/doctrees/file/file_filters/base.doctree deleted file mode 100644 index ffceb7adfaf7da12ced57aec5587f786b9237bdf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15123 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 diff --git a/mddocs/doctrees/file/file_filters/exclude_dir.doctree b/mddocs/doctrees/file/file_filters/exclude_dir.doctree deleted file mode 100644 index 9f236fc3ba7f1a3761ccf0f09ff9a58c39e10027..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14340 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 HtIq!b%x^zL diff --git a/mddocs/doctrees/file/file_filters/file_filter.doctree b/mddocs/doctrees/file/file_filters/file_filter.doctree deleted file mode 100644 index 4ae0db2bfd2b3e59a011e4f2706f3a7485102ec0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24382 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 C4arvk diff --git a/mddocs/doctrees/file/file_filters/index.doctree b/mddocs/doctrees/file/file_filters/index.doctree deleted file mode 100644 index 3f23765c3e564d2cec689a927ad5bd064032f737..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4687 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 b9M(6>ULUa)+FuKrfvpK;`m|$NKIr}%3`!rs diff --git a/mddocs/doctrees/file/file_filters/match_all_filters.doctree b/mddocs/doctrees/file/file_filters/match_all_filters.doctree deleted file mode 100644 index ffb5b8f9f4a917803515addb25d93c088b10989e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14653 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 J#rag@{{YUo+1daA diff --git a/mddocs/doctrees/file/file_limits/base.doctree b/mddocs/doctrees/file/file_limits/base.doctree deleted file mode 100644 index dba8aa4d0b65dd0b8dae1dda7dc4638a1b72cc91..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29393 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 K(z^;^v;6YeG`?pDwA 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 I$ZMAW4{Kfz0{{R3 diff --git a/mddocs/doctrees/file/file_limits/index.doctree b/mddocs/doctrees/file/file_limits/index.doctree deleted file mode 100644 index 8c142e0cb61564ba956275188df4085913f05ec9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4643 zcmc&&|8E>e6}MyG*>|?jU*fbyP0A6`Vvu|n2MWkSAfSpsZqJ3Hh+nML-0s}X*tOYRK=Z~ zl1MQMJ)Iz$cJFnLzTSDW>pt)DG@_#pQ%T%3Z0*dj1`MVH3Ikzq)1_A{N8%8oe6x zl$w(px}Fh)1$MF;YuF+Y`WH<8Vur* z(HnE$I30h0du#Sy_I>=i`kP4Vgmxt&Px7uxvaYkdYm>igKX!iw{?;<};cDX)U6V9i zs8m+BC2;^NYAEUWQax-ufV}qG&VZ^KR)0oE7CH2#!>7gp3H1EA*R#itdN1^B8A0pT zhhTrtv$J<$=Bm?iJrqeMiZq&Yr&p3KA(up`ayqYNd3K#=*?s-ei`{}UL8j&06~{qO zx391zWaMHfgAfWS^O5Q4FJ0kMLXo)Sll+$yY*cUZG>i+2jj@-{e#M?py$+EP%@i{y z%cxS4xoDKW^f5y*3}ewZ6J2LKiU7hBy;Vw^64uiq=>xzbbF!qHE^tz$@ff^KVEN20 z_gdYu~@1VvO{5>EXl2Imt4b8!}&b_Iq|YYzQ;szK^h48H{w{{rcdr z5gVVut*>5jt0nj}hC>PaH7frvgne+b%s$lhx9O0CW5a$oVNWOQqY3-{$r5`uVH0ru zf&F#f%q^(`?CKL~Q>3TAc;(0^Kp>UK0^-x{S|;?o5G~J1x^qQR zP9;?(2-tkI?mJ+AXa8XTWdEY27h4ZR-j3)a8VdwkUgyn^=e>Co-u&C=@+O_4`2jn% zo$3f`mAHQ3aj#ufz$Fx<^HBT~++RYW#|-@iI?8}*!L*}CL4O>$hV#U*&)8d1zx+ZL zz%3X~!BF&P%2g^=mrh$2dspwPoTM@)wrFkd8p6%K@zxv&_+l)1Tx}NCcgwmJ7P)O} zLD~L#db6Uq#@Ey9h?nB&5cApYblw`|B2^SfsOt|2W*|9*L|qq!$_izA>|swghBT!) zQ6fiLHCzK$B+2lykqcqlEO2OK^l}%l?NvZ-q!(almajz~<82GYAK1abo0Jeq7=xy)$IVm+K#ppW= zCkrCZmzI~7bj!<~=}>)r`|`oU$9l)wfZUS|E{b83(OWS|hXrP8cs#=U4fG0qGpC`* zBaG!4G6&b#{?^m=P^1Hx36JooN(ffhEm9~E%t-o%okF&vBFTbjO2V;@z>$r4N@6Q; z+fiIW*yztlnWRM|f>B}T_tVs&6kL%&kZnIOwfHdaie9gz=FQMW9o>sH_l6ipBwg>bBce)#&mZ@{0e zd=M^(x0IRqH!ATcUSd=Bg617g)xg~WK7$CU?wFHxeFr(21k>W2LSP^> zn;{duBgd)26vOzC#rPqAS7TvQe^Vv|tJyCKRJDYoZn}5>8pI+bes0lsLPnqu0`Kb$ zUUn|T*B3>GE9`pc29Od4Fs*>X(*bgz#DzTz6hPSpHn&CLF5-*P?f@4CDTB!-LIsbZ z0i#smj$0wPE&`em9tYTDE$%w4cgukmxf%~VrcLw;O&=MIC3@YX3Cb6lh%Jz#HdNq; zb+`BIE^e|G&DlQNlzQ*dkzbphpfVO##C>UR8BEXZVq%vt+gZ*Gdy7cDW)D5>0R8Bc zeab#5bi-2BID;8>$@%OV`*c#U*X;8trs7Z7dxhSbkF4d>TOmh&*!F{aJjE7p#pjKh zr+7Y;2r?n*3(Tn{k~`M@1>%&uafz)_-ReHtyCff2oGUxc-ddDZo?t!&yto0{;^;;w z;uv9o4QMcHKeU52=+9x@c?$q}vMisPpQ2>22%`9~x=J9Nzj%r^^JHu;EjgB`yNWvP zlGAw<3qQn>Ax2tu_4G_1{Rn}`ZT_`=cC_!>f-ZNryKOGp=zi^KeMKGPHy(EXJciQp zKkTZQ0M5_B1J)t_xiUM=ueG(Z(Jkv$n;IzAANL-d+UDc`a|U2oZpt+)mU?-5W9k@} Z>Ko;@fYKUTbZkbpC6wrC_pPGe`8N)u4a@)l diff --git a/mddocs/doctrees/file/file_limits/limits_reached.doctree b/mddocs/doctrees/file/file_limits/limits_reached.doctree deleted file mode 100644 index 3c8f7959830147829833580d6362bfd6f8618816..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12366 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 diff --git a/mddocs/doctrees/file/file_limits/limits_stop_at.doctree b/mddocs/doctrees/file/file_limits/limits_stop_at.doctree deleted file mode 100644 index 1f33546afb012db22535ccb80ac59e6d5a0f447a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14340 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 diff --git a/mddocs/doctrees/file/file_limits/reset_limits.doctree b/mddocs/doctrees/file/file_limits/reset_limits.doctree deleted file mode 100644 index ace8825cf3f72fcf9aefd51ad2524676e3722393..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13211 zcmeHOTZ|;vS>D;5o=flS?8UQoOl(uT*h$aE)4j7MiW$X=VuzKZv1c8}vMiZVxw`9g zSJhNk6?NI25wS6lix(XM!bJc<1P?rM5kyGbUO*5a1fGz1fbb9y7YTw1LcH*R`2KTF zRh{adsqJx=L_&JQ=ld_`KmWej`pF-E;)M9&XOqZrgZ-{$+d*ivB7`f5+GuAfnvz1!$h^5sOVZc(q8#BvJ9R@zdL@elC?QTBN z{a{ZF@411^_Pb6x_S?C$xSKo6STGwhED#GWgaD?`)EF-MwglgLzaRS6uS77%+g6&! zZa+)0ue6V+Tsy&@@x_5}B?%hwy%ktv`EAM0qUxb<4fY@f(dU%qr@6DnPi3*2^AEnO z4a#|YUITGVGME_HK*(?>vIhFN+rUeO09U|v}e=}wYOE-LX?54?L-IW6e6UsDXpl$xc`i}{z>KG_h(a`E=lxv(i<_!Y_Uj~&I?%YJh(i;TS9X+u}U6~hQ%VF6T1TK9bnB3Mpy@bQkOwz-RQW1F%Ip_XNE)# z39T#S1yh18Q%hOT`IW{Fwcfe2u#@H%t;VTVBNvag%)|B$rk2vdn^k&aCN7jD&ElHe zKc%zNQ9v@@NDm?`t^2W*#a7>Euf8F<>{|)z(l3)1Bp1|RI zEbou~tt7I@uPtFn!s1&@DAlL1MQ0Rb$goDUnyF@E#pIkMi1Og_A3#C^AASmJ{+00d&*?*@}*)1jjyjC&6h54 z5w6+c*m8rMpNtL=;D89MAk_L)*!ObLK{;W5cEUX7{4{ze5gWKecaY!V%TQfu|8xFu zqv;~6aj+$nXp)}+^xYpcnxGpjdg60Fw(b2CT}@f=0u(2ZSfWmlS4cBJ%J zJv8{@l$4d)PW00xRoKHXU^{-bDQcmP{K0YNr!G8gz|7Kg;Spx?4#DM@F^lptnpKs4 zG-mMjL87S&@4Y+EJPjFy(_o?0fA8bX^3>L^tD24!?@`)Mf>EJnl177W9R&gJc}z8n z4zXsg4@E&S`Ls-qRHLMs%)1iK|EdlZYt)R;;cE2R=9M)G@U9x-ZPVma@YcrNw&>V~ zLn(YC4u=$qz*ghjk=KK?g&A$JfS|L$mxh_|3kUCfp0~|39FXp%N$&k3vGn=Rz#WKI z8ThQ$V(4`d{k?$$orrR5a%YDN5@z3niqDb(aI#g(Lvwkzc+EOZnDm7Rdt;!&1`zWoe_v`3f93c3) zNrf(SX!=F9p*M)CNHornw^JUeP=Y0F;xl4pbr|SpIoj`!5RFoGXp~f)W_kQWB?wq> zQnOX{!DA*i!Z8U zfYQP8x!RHJSUaNJU0f3}u;zV5C;Okz;ecS=c2h@%Esqn7lA$x4wCnhD>DfbC^kRziF&U z>PV21=ST8`BFFKU=fGS`B}x3r+q`w+T|XwiET-*&w3qT$`+`7M!{(89zu zpqloCPAPn;@M)T$CO52EW$G$VlYdoAeuxkF22}q3Q_Uiuj{Yu|oHX-akN${%sq)RE z>d_xLj2{-Ou7heaK2^~l#ZbqK{#HPsivF5e`+bBA=w2jMoSwQW#z8pF`Y~=Qsq#LUN`8#zt$n(6PM!&~Abnf~nJ9>`2B_01pOZ9WEIVzwwmuYK>fHbA4BPUr<%|=j-homY*=u;X4dFG!j%(q^OY^>0=aU zoswYZaxVhiUK)xU;mz(KxpjHHScyKVT z>yj(G9x_WcNb#!TkpJIK$|d|C*iLB&pwX{Dv6G47DZZ%GxqjDM1Z*!KWecw2ke~;E z{Jobh8K~MCFqD_sM*qMFvT<~P$&E{w6r!5@lx&U;VN*B61#jw5QQ67DPCaoq+x$zy z!pa1HVP5Mtxg!G!+$iAYo+=n8ZYGKx(i`BFFCup(?wq|Jphri(L-H2BxR?wL0GgE{b(klhj1x{wURfC2g2Y}@s#o()dD-t zO)3c7#G+LCvNKUdGEr>C^qRMi<7z}zoXLtT{vZ-&xV?cnRi%@***}n#7O&^t+XSJH zCj5azd!e%J_1w9M4o#a@mIpd7@U=9yg2Wez0jd)m!O+Pf$5tAq$YE}u;O(Lhv+$d$FN@Md)Hg9^Nbmo3zrRu{8;Av72fjCHVw4sG?A#Xg?S z(8U!MVbWW$(vthkFAOnF5T;Cf`4&o3A$sVVxjzh%h<}T(Qsq%(s`!1x2+)S2Ktu34Nvk~T7 zrGzOJd|+N?jnT4q z%*9QBi+e(TR!Ux*r6p#ex(%@><(r8qyf2c9uF82U9*WTq)n7~u=I3Y)z+U1&M82pV zJL6lyC`0`+ByOYZauhE2OMT-DC?S9o1B`mW;6IE{Lbq<;rqWDiADNq8P zz2zpZgzQ<QbLQyMxKlw`#K`kBi!4A-D zHn#jUs>sX7?Kys4)J6C#7y*bPsL7u{_kO+_v8Xa4v>4P5ML;`vC)MEZDQbGroOGiD zB=dr97>}S)7pbRU06k6IgkgJ9QQXI(jmNHjC*-*r4+j&|Q%g1d`-`!Z;C0zus;p5>#7@YaF*3KxW)$HZ=GE95@W;@H_nC0KN81 zhH5L{M6oB?UBUHvRWl5%`a@~c?(kDAxP{WFK3|g71nLVU!uh$-jntIIB4Tmp!Bp*k z4Ujc=)|{1uU#PvJdtrJvRgS-w5S3y1o{GS!;GH}R3LrjeC2+hLEZaKLCEfIX0;a(G zG5pH-GQAc)6oX`kx5Rr?>pViW<~N;Bh_3Lbx1BdLen#JK$rNskq*DTQ*+Obv^pi`~ zTL_Dy75EtUL%)QOXk43UEPF54W?U|)ia{4@^TR^NX%2n6I60Sk14jv?GoX_gS=ZBp z(D$)9P+qv5MR7ty``ZwS_ZJY;_FVGm{6#1diXEVm*o*{rPZBO|klu`(@oI9AKpTrz z?YMZz#guG9R^!^U2xm}L8tfSoD8^-RoKYq7m+6)Oo)Bl8BI4+({0G9^?)-F^tH+O& zY7hkz*Srs)Yhxm)rJUXz?zu%(;rz#TZrmhCB>z(k3?pd_{)a+~rd^lw@zbR^1nf_{ Y!NAXKR9=T8Qs=f!?m<~nc_GyOA2Oxf=>Px# diff --git a/mddocs/doctrees/file/file_limits/total_files_size.doctree b/mddocs/doctrees/file/file_limits/total_files_size.doctree deleted file mode 100644 index 230eab6c4abdf8b40baa11e55f99b10558b5a645..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28858 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 QLoi*Vg+DgT&ga+;Eg%*j`KD<)IhSjL2XZp_E zzS}SQ-rkw9v;;^1$+@l=+{O>U#NdiBG1!S6FeV0y0)kyOiTMKpML9{?m82Y}sNlqu zP1(usJCA$MIeq)|V`jYA(N=9w_r2%wec$;W=X~e;&Uwf1&y@dU8~YdS3F=;{x|S;x zi`81u4O-Dyv9{E#xYe-r)2$=#Y+Y?-qwz*zHK;WkOKvOLh7wC&sa$Nh)z&;-PEz@> zR1R{Yfg6J6Vi1->JgimoMR&PWEz#GwJ#RSrKrZLJ5H#!cS|iMRwc6$21*cRgthjS? zju(dYU~YD{R9VRdUdb&NgIuXLyI3f$xU+X0KRP4o;VY{-vsiAr83+I7!a`%k4U;Bn zRW~f>8g8K&dM@B(4Y}3Xqq$@Ez?em&OXWfk;32*bR|^&XZLHX= zCqEVoOP4_fR%S<`9Jagz(T--L)QavMXnA8= z!DvKs*~9~lAk5brwQ_CcWGmWPE>+#u>Fv=-y?|i>F#DT}1@9Ih<=u)AbMXH#{=WwQ z?*vjKfSkd4pMl`>MGNZWl-I3jpYkgYp=FnI= zG_--U=!UDVTXl-1Wv0DCd~0AlYmE}#ma0yH7hDfQw=(TO8l8q4HXC?Rb!v+~iaM*M z(Bo}3+@M(w9f3wp0U_6DR{m7za`NV!xgg-)SfFPGe+ zlPxXd>H3kJ*;GKyy*s1tP;>b|;?UxV^e`*>)heaJx_EtfF-g|gDP z_i1Lq=cDl$rdFbSe!H{s$woUFoYD6BI_yV%eFju$xvEv6(fBP4=Wp5+7c)Ijx2@=A zqLrd#m9H%}!6?=$g;KTUeI*(Ng~IhZW>-6?*Ju}hyK7+OrBc{>k-c6mG^(waFly^_ zm0Gb0B@sE@#sxDq6lb=+b(VE03{f=It!geIAEEuPVOus?C!QVxDL)0tnD<7K5&D!m zLT*P*PB8r{tnPPV7O0(Hp=!2ap$adf;4h=Yj~k-+N%=dva&1&A=jZur)_p?$@2TYP zJiij;-x8Mivp$B>&LAJfcz*=r{V_SdawgN60p8NCn#kDoqXMZs)S&Zh_E~NspPhg9 zh){~ZAt8w#->i-}?|fD^>>tE@L8!ppu?!PTi5m28q%v&6m^KZ|dSC9Y1pNbO!zOUC z9`ycDRd)Vi)azZ}dEfH>qxbEz-gly0?;49?yM2ZHr=O?KK*E-9hvW>J->n!p9J8PY1Qde&a3w$NW z)^7_7as{`rfLSso@e2!V6}Pa!)^YSJbtRVt-q)wLuaM%G3{rTpb&U2HU`R!`O94)4 z=~s-F?79nU1E2b)Ic+p1F;tqf0G3GzAz>Qflu_z`Zg6mh8F`hqEU=8nqaZ;UW}~9i z{)53G*`G4BiM24nvTI>l0yG5=|Dph?cOJI4Ei}Bn(1$U_#Ol6tp=c+$5a78a@p%_Y z|05H==z7KfPlA{ z2nN?17U;vw@vSbNnWwMgqAMCDX98SF%hP^yZOPUu#fr zGlMhPpF~ilRdM_`26D$)0pPia^mTJ{1 z_kYegZ|5j176La%AIv)ir=u*9djt=u_M`EmY2o8yIxcRZRH@SzM5PwG>_zSZ`+lDJ zu@t@2O9kfz=PclIPOv}I!T}bbD%=MckKB`Rm|aQ-ovV`=Y=92EJ0Ca2M7+0_Skbtl zC&}}cv{S2-|Cky|=K=9PaWI_Y6#z&)7UB`SczJgX7zHODpm&ea_eWf>aHUjhK67wA93ZIXLxmP7oTz3;+Dr+j0^LSf|phZKDRIT`yG;*)e zh;DD9q1BeD75sacJowbTO%M6IP{5y{QV^meywV-?hi@D-Iyk)97&6b5;=WS8=HOtW z%?0QM_3hF!3PODkhBe?Y`7gtXCZL1bV5op_E0h*^O+d$)b($) z3)*4^`jdeZ?I!5haI@c0r^eg-SKh$}AC1o`2Wfw@Tb{}qJ^z4!M{VRCaB54Cq*y$t%5$Rr0Ukk93Sh4<@ENwoUyQcZR$F!lpNg zwZeu$O9=e|B7kCsbPM9Yh}Ufpe7;Z^WP zqOs*>xy&ZlegS<&nS5AVB8bDF<-Zw3Vvpa0Koa&MDM?7&PDsqgkOLowo?u$2EVnTo z4%HxE8JU#zl6Z*sY*IW#roT`#GcuU*BDwxouMs@TO6DIJyzT81D-7BMyFvLr?A;8P z@4=4VR4?lt@Q!#>-rZMQ-V8;;!cnlDMUo!(=S43h)x#)9v8bJbL%sXGIr8axvu82V z5%ltz`UIt#RSuPS>&K#n(=4c zW2Z;a+dA(pA^Ye4?(@)m^ynrap-}iEL@&k9pzpD2@*-Jbm-19;>1CrOD@RcLrB8h` zcG%Y@ib}g}5GKJ_2h$~%0}8$_Rav@0;)wP?M?ms0hdqX%D_wrUiV8(I>UotWL?@$ZJ{m(KCJ5!=ln8WX)o*L#redxiYFD1dGFCwa{4H=RL1ba$(7+1-? z11e3y)-%>Qal~%lUb8U;`MV4v`tJd-lEb4>(XAYo9RNb9oHHux%x#Q-G>6n9 z(|0V5n0P=D#czD6OxWiB@}zcIw)EHNxYASkuV8lI%)6=T?iLV zu!(26z=J(#?Frf+_s_APj7-NM+N=Z)1W4 zW*Vo|GGtovrEp^T;xw6I_F5S{8B875G`E5>=jHl3kle36O6dKr80WO@SW^hzL8UP# zq(3p&kvznJ7+EqG|1Sh`YAV_OffAT=c#;d>Urt`J-RVc;fQ|*0Rr)9;OFFAwY00Ct zMTn%0fW;~}e4U^yI2>dw*4;)(3dMQK)z$_{FTsK%((I4}H8rJdm#ClxISLVZ5-X4e z2dPyYU??mO6b~YCC>zRH7lk!yRM|=BsnKbNI|K{mX$L901-MD(usoq?lZ9?QQlb|y zaq#a^wklerMH8<&?I8UWQ32(32RT`kvsNc+78ZkA8F{IE5k^=9Vp)cQz<}D~7{48{ z>kAHcp^;-oh+&>U?t6w5OAkZP0WT>7a!#=lGN9JjMWmyjNvV^lY|mZQM15gucOo!)*5RAO0)IT5n6 z;2=b)SVAHpk5uZ!OjJ>33yO9HCH3Sp*{-A!p{Hh^$+|Hh0uE40{l^v1IE4erZ1i+Z zM_GRdj6!*2nN<|c7yZnj+GI;L3B!@#-wFB9;Jt4^czKKZgl1B2E7t6Zc2lM|FZZR{ z#cKa!s6^8nB`wUSXX+xrI*IXrNwWv$2I{GcpZ4@bWkvat_buj| z_P))$(^y}2#nM3#I~q59LzQ! z*hnvmAbpSURTjZs!iqO2Ywbh`g9QeIHaGVFl3L;Hx0e*@j8b0d?1x5YI@k2E;{E{| zvd&)Vmc(it#^9^#!7M~W(ksz;Jo{3RHYse?FzPain*Osch!V^G zDN!|^uu?6ql&l^3AJl4*c>Eme%l{Dm#H-Bn156^V6G+a${zuhLj`!b*XhvHR?=hNT zWiTqclxeUsF86{LRL<9#QB`3?WMM*%p^|Ar=$YD(o$k5`xdC*aVQum(rp_g{KYRbt?2`0?so8^qMODI+=jyP;Ub! zK*PuoOQP$O3@k9fPNJZKGrdp;OrdFp)07rNoP81#dxdIoTEf}dP}*BF+S4h~8x2DS ztX{Jh8omqlHb6s$Nk`h*Ia!!WV&6pl9BeN7|3l&AHuHFkO+84FwHHbzFdw#3UZo%5 zsJNhR1b_TO7m}0M>$;U{acQ+vM%S&xi)p&`_cwbj-I=mt9lcp=Xf7n$`k8AdU9X?p zWCTSYrqb6O5|V>Ds-l`u8(@tWZz+!=`~eJ>|_o8JL+vfli92R5&WABtQ2cN zFVvA#Xlk(r+eTtq8rEPty-{Y=Iwg9e;ol5M!wK}g0UD-R1C4gqr}Cp>3fqVo+s|lB zI_{*1?qqzNDD%Ts!l^7o%AEFV`Iy>8Pgp6Ex5P@_lxn5a0HxcyC{u|<*K!Gd_xFXL zVys>TR9}b6n0O}3%(rfxG8ei9>zTL@Wr*Koppi^ByASa>qpmI+X+uoCv9;BG1NwVy zkSA;1U5ZKLLmM-$zeGTbiJyMliEBltH&HJz1UnO=le(;--bO^7Ffk^6uLhsR^d1$S zaSQ#df~U^5sA0~))+)wqi`brNBY^ zxoN=>gw;=;BntV{M$d}ne}wCUjcojI+9hh_i_{xeBa?c&3RS;kfTZh%-ZaYR8A0@# z>Ueb0D8G+-c8#JQ7}w^J6(_&-vMXjP9vVg(j4q+2(m6{bE^K+K9apU6nN1bWn49JX zZ&n%X|3-KyGew2}u2X!kLiW=9uK$Z#;5;?K{I2bC`sYR`I$sb+Vb<46=-Btr&>%k7 zzeYWkt-AVLbxaH!bqK6NbnC(@s`a5Nf6@i`_O(9M;;O~2kaX)qoSbHTzOp4-wp+Gc z+hMDg^S^GJp56+~L`RxkQg*$$YMqZp_#Uz%Qb$I0kf5NmS~tMri3v!WFq)U6WRxtIIbhTdhAG57d6-4c`ZX6lNokKNKFu|^e& zK52lXQ?xgW^f)7iUQ->5P8R7I)KgVQ`yQdyB4zpBYj?_+_J{_MhM|j+k@kqjx<`9d z`&$-SE=ZXF%x`JO3ctLKh>>p8_A5q>OKzG?d!8ELOu38MG+tEc;;PYw&RBhzv~@Hz zh)H`d>ZyF$)ud^N7#8g{U^XIJ7iLo}8da%wfxX?LQ7x{P?8;2HXe0}17VWc0$l&Me zY&dM^)HMj%$?8_@iA}>M&2(W&71K)Eu2Jzs;p^YPkgB4i^R=ol731}5j1TlB6+KfM zvX8fJyzaty|4Y`UZoKXg@lTz@uu|R9HwniIjj=8H7!ogiml@ZW~TH1zYYp zgwW*u>!?%C`zhR#cr30rfYV2=H!0YMHU=?zx1pY03apc{9G{H!N+VN5vfx4*nm()~ zRT=4mY`c}DT3r9x*}Icm~S zw>?h~T<-@#n!tgibgO`W1r@uE$7N2Bp!cG6Rx}OEP8r@rchL{83_|Y~cVFZek>Pb& ziFCX;h2&{=^@4J>>BDguQT*alFy)ld% zf^anLOYrg+sw+)g_SWJT4NQ8a6|ao{VIKFl(N$%{@gE||iUTI+&=+TZyXqElQt2dQ z*%ZM`d8BBZiBmX=!&Eo=!$jA7k$_g#)mCMy>YCqS09%Vo9q`+|7yV2pGITsmLeVv% z@`ure)Y=z+h=W8F)se`$R3M}-x+@o@2|3lk#+7X2P379+wDZ{7Qn^`l-&ksx?d6!S zL6$yKSR<_!U>Cd^PCHNIq}MWg@Kou?-R^3P-*{C(noqyaa0IDzk?b)cJvr)G|gJPA_j@%Z;6#B z?tVoKFhx?PEk8nH5S#x$hH>c4{QJL(qA&TM#3!@*{;%O%nx#JtOV3SDAC_J(iW8jK zt+q4Bt`790Wsx1nE?Psn!Qza|kKtj{4KZ;@Sy0PsF0O)o)uBU_>$~LQ8r7n+ zxb9S&mHIkzS)4F+3l{n#f{owv1bjnkQ^YQt$n>n(t z7uXG)jGeOnb1EP^C@xI6qS*Ij2AU-f={N(;`aat6FXKZyx*(aR6PJm@!Cw-8)=b@SL?1 z{~lCw}Nlw#ME(k zAs&YplyUg9y_fFe(34bs+1`sysydGLnABC4i;#o9>^*lEWRM0^;^}zFy^)@#u}9x)UO-- zw>5`n|CZ>5IHteDvn708w(n`*=31_Z=>Y9d#z^{S5E39+q4W8MOwT5Xi5<^Dq;k5R zwid-itmFGc@qEV^czvIXAF1dUfQKf4Bm_cZe-jPZG(e~G6u%L7?EfKsJ$8E7){Nf( z43U1Cq94+;^n(;dn>)pTU?sPz42Fzxcp>H4JC$dVdM=`{hw7t*j(;7aljclN6*$pO z9F_Q2RB2Y?Ps`2sDV<+@5b**mnat&f>d7^&aAT!-vEoDB;#2*v z*r&Zpus`1y26Q@r7(6=a`V2!xnWiV^*K{UUp1r}=(>3ouF*rnR*SuQ>uZL^iKQuTb2h&Zm zg-lje$nt80DD6rUrDwO&8pRT`*X%{X=Qj;v>2lH!Oa5D_34z-t{o`k;KUOz3D~H3qBQcKHw4@*xR0vibWZ8T z1hr=lrK9t?vbR=|Cut}qg4=g{EfLeM%nJ6f;kUAhFJju2TDy9vC1ToX3DM0Ai3xIqtdy8s)kAain_uJF&etP3UtcJHBG^bC4>}3Z)>)#*sgh!T3pl z(DPtO|ISZPrOqnum$}e$)BraxRvfDAlXr-q80 z>8l4*AAX#MLf41!qxtC0-o`-Dm7lcoLvOnB6KG=)UHKW*v+GJDpH|nEy&?;h$8H=G z`hgB1`xjjxZr74j>!YleT}$3-=LpUai7|`%T;P5aAoMK0%)jvs1~eA9d-ye#v5+ZD zKxNcct%_6C(6@pr|DJVVRqDnxlxlHWT04?QlPlx)5!RP3v1e0sg*gcQ>Ip++_qTV! z<#IksdzEP0zcC1Fr;=`$9Z>17IKI|{lf9uJbGi=_9OB^73>8KCJ5uOzZAh{NY`cY*R$X=&AH z$;x9)3aM}I$5F$0O^B?6q_X!E##Ryy%_3_U843u73EQ)V7Z9d<^5jWQw`7?klkCV} zPBd#|s8EHzX_>zY0g$-Me;uB76!n0|{1+axbYj_%SXHAr)+`Tzh`SMQm8B%lG7g$c zlzf3}(pfs%Q*M||(&hN6zyve$;&`vK#1_ZQ1iDTIkDCQ=PbRE%%R>`E*hm+{Y_^VN zzuUOt7TFA#(w5TNtb*olk>@=VAor;4&x?${CDPQ3= zCOT3IHjCG|t$_l3A9Sjjq85z`MW{y(CLw&Z#Q0~Vz7qwSEU_Qbtj-LxCG1?`C0zZ8 z!!Il$4v7hRwY8dJMgF;AMFzJ=A2(X+ZjY$%c6($()U!Q$L?I_Uu|I{@<+S1Ak6#l5 zbUV92Gr$#|%`)=%$cu|RSxa!|ViqhfspMyxV|&C25E8=6VU(r9J8T*&&6H(e?8q!Q zm*Q{qOIB9w(sX)(blag9RuiGMmbr3?8=hz1j+g*%k#)TmMpLShHuEQ6wCNXm5u2u0 zxKL-Tn3-PTLL6Ds^VCf;TfbZw8?7%wHJV1t{}3K(DPBF=tBabwx}6!G6jQ}EMQi1v z+laFXdn%i(>b?#DRbPSKz$no%Z?zbPJ2z$+EK3l_xgFkUM_Id2+#pkqxAQBDr3K(t z!c?2R>G_6RK>~tB&x7u>-NfrhL~)-z_jI!2VJD{`L{cT7brrPb;=>&d7hmBROfX*I z80Hg@nz)16j}`8BH&fx(FuC1tlZFgPpFI&1gO zBfkVnS!SF_SD#)f%QK}%`0ea1EqFf~Db^}j47L2BN!m!lRy#X+!{1In$0*57{avli zl&VE{4Y|{!)tdY|DId?c)v&RSmlNWp{Jjq)=*&U<4&p+4sx|pcmZO%aKPdPRt_+?I zb}LK^vsdCo4RM&_1`oA(=t{|5W#=CS*&~CUZE&tQ1Qik3AsoA;7J zy-Xq^3IU7Oo>YzB7Xc4(0$Rj$bIF*Vt-oePt_L#eW=N%mE*&BjG*vp1HvC%v&z7Wu z9!S%sEuInjesrp3gsO^9CWanNYXBO8{F5R-RLVQFz^#I=;AVS%=<=Ifp6bN&wcyMO zJr}_`%2J4OuL{d_Dr3i_D++!u;rJ^`xWXdlNUme{==G`nDeV41gChoaa(>Kc$-V}q zW7DZ`=Hwic821KPPr>~EW^i!Yg83~(>LHl_-wh7Qbx!+G+i0{;7#%X`Hz?nSy_*s3 zbFky#qh8iK;2rU%yt}WqycusQXu^!Z3?Dp0@w%M&}-$?O`(DqkRrCEtNO<~l$-e_4)&6D}X zJaf-rx=!cZE@S2%AWfr9?K`Vp38}QJC2V+LN2G$q06XH4M@DM*iT51k&MuIkb%F}a z764*|hFzCJ0~s5Z`Ysug`}!IZ5gaBsW&#{@59(t`tifS#-aHD3h%tn=m~ASG`f&rN z!L96hqowXvmilH^_NM7h3}%Bg&N+x|mjD+DEWV61jzwIy0^D09?IJ^=Gpe1k4(h1a zS0pV>4U?<`h6&`Jg~;x)HU=-2e&hnz|&z_yyz1pNT5@ zq-ab!(K>UGqTN41l{#zp_w^O0&h)-9y*C1mdyeY=dmh67HKMy7!XG~xB!vGDtb^a1 z;`u$aVVl@=Qas;6y$vXyena?${LpA700^D^jK)li z#cY0`0c{WAr!s1*RxPQjV@hHA9_zraG<0=LwKy%U9U1DFM&JvqFFl0+4ejyuSo&(N zf|T%Vme#{_AdN{`T2Q{wooTNUJ$mb2or-pn=Z6yG_XscgJj_!TI;}g9c-< zSq3;*tVTE`}I8FOuGa3obX7*aT)hFisTn~aEtUg)K{;WP- z!j)KkJ7^*fUar!mT^}G0zGjS=o&4hRt)ySM_;a)fk6q7T1B$Qj;z$ndSt3$b`{`d* zOq2s5;ebr3p?uN**|b3N28*3ABgXZusf<|TiTLkifr*G@USEm55W&%%3Td&pX@%0` zG|=k23?cjf6?@K8;EI0=mKrd0NitM2@0|i~ZP8Bp&!@nt+IM{?LF}ZrLpak-&4`_J z>6@3zLf0_1opgT$%R&Fw0j@tv-~JVS8;ie<4*P ze+Rzj!}G0dlp#kK&n;>3A2a0AYM{((Q0C5gmZ<+@-f?dSx1HX3I1+b8H@Ry#&pku} z%Cg&Vt4nS%zqp>S7Ah_-DD!_8ICyUiq8n06m5M0iokIZ|cSIw664mm~M+c;bvsA0* zakIz@@=E9?v}`mS)^MX$>-4rLBg?o&{A^-qyrF1vso4N{VP3R#Hky!6%D9KIbv7Cc zc+5l4iuNJwy&0CuK@JxsQP4!O6^%SYN9ZQU&qgDN>B3QXt+U=UXQS~I7neD*Q;6}S zh}-{y5O+$V*4*bHQ$;b^$%E;d)t-sEbb zQANxMN+3z5f+yn`h$WYhA^6zAfq8I9tw@n2(Redlo_YODP{Q|H^p|;;lI-&y4iGGZ z1Hk~mZ7IZs0-WYbE5odnR`hD)jnRbvmte$blDC_{AZy>Ezc4CCdkF{l)dNpl+ttFw zlHzhcP`pSt^tshmbREtLs5OfDqFV>gpqmmS>8`VOYhHejg!q}WfKY3IS+-9+sU#k!+?S;(M0ynSLWLyGi zy;2HFbYwQ#$`?xk?H2$M0p+L^5(wM`kO>e9#P8Ao*N9>~aHlojiVl+EKqtuW3P~#6 zDz#R`knd^~R^y+ihoXHXY2cjd3K)?c4B0xf_pWHN?$#6aLC8$dlf?pHC!vUL^un+n z%+1c?ageL8%pduI1xv8(Y!Na*bGjf}OaX7e{Wth?BUJ6S(uZsI)*grGT`rUh)cn2LsRL4OC_% zMCr@TI=Yyo{G3t<`g1k#5=&hyRhPj7LNDwmMFIV46vO4PD;SGffeYhKri{Qcl|?A< zAY2Cop6iC_0&OVJLudZl%?xfqRc zZ36KHCn8XdouiTGVa5!(eE@6G60JdquW{-a%tE>r6v=wHoQh z0|E829KRnSC0k_8oP)`FV`q_@h~nuuC+KWdu5gOYwbgKH^LSq@p`i@@dm~d>Ihjrq-K$(zx3K5_QcIWkSPCSBJGRY@}Bws3MX=7P`h_M%-e8JHmKDplESoe;bg S67Wos42ICHG_7hb=KdeoT>b0- diff --git a/mddocs/doctrees/file/file_mover/index.doctree b/mddocs/doctrees/file/file_mover/index.doctree deleted file mode 100644 index b76f651d41679a438eb50b8d9ee8f69feea87ca9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4114 zcmc&%TW=f371oWENZl1V?!^uWqe;~QB5kK`1EFZqHV9e>q0*)>`eH1GyF<=cyF1&N zS(`#*^a41rfO*Tb0g}JeAJy;7?vfO%6lh;6fcEa0Gw1r9%l@VH_sxTq@aOhTA!I)7 zvn0t?!i{%ZiHggN=hlDWfAClT%AdH7W-pB@b<8+Si;VR&dxE1o3?Eu>a`& z{*yttK9G6Br+s0wwCly@GcP)Dn2$Legu_h`VD#<{gB#vfuzQj3Z~uQ!f^n|PtkrT< zTI7}TxGoceoVm?7WyXMr=T^?L>S-$}i}}ll#g~Xe;8|y>_2SsAms)!F7e6$m+q7&#YQ1>dwHuV}{8Sqo6IAY(oJ9^m&q{N968ZCD;`{Yhi%e~AQNioQ4ykHyZddX8D6iuk%>ARdZG=WF0! zcb#ZfKw`Zd0Zr`+mWvQM2L7Y5O!;Z1UT}S?m9l3QqQ23o7Mzlacl%5Cf~~)V{ub#M zzrb(c2;3PT7$AV@fz66Rh`@k~U_b)GKT*(LR{$dJgT0%E$4dn(+Ef`QaHfUIPH&V& z_alhweN28dHL>w~KBfNQ7SCS$&`n_EA1Wh<;?eNd@YZRh*gggOrxm;S1U_yA3%6pG z6{^gWg_u?*iWLA##a8o08|$-g3axnT?%Wy^lnT(MWhju7q5IA<7rj?O^q>2oySsd@ z3JRnNhi@+*YHmst*5{tP9ht{zNxd?ybcsKSSK`myZDnkl@WKjzzKXhKLEg={x{XPy zM*iBhg-jB3F%3^a4O{!8~6qz~4ec z4g3lu?%RA?0L0O(k0;USDhh4pl^0)&4~@HfD+>w?3>RQnG_$d)Q95f>;uCjlHOo!P zDAClhCP-`5Zd+DamR1q3c|rn8ipr|ni%U(-EUKH{(Csvo(C&tA%W!LDKEYHzoK4xW zR=MSXzH2{an0#hd1Fo$~Ta?xh#nYkdOnA;Uvq}S)ozQkI%L=?~Q@4SsK-<*2)q!{0 zl?f5_UV@=pzm_`1+b%L3(Kw(}n9?G&TzPjihL~JgzHs;kLlSWZ+M?-L>CC@zdj$S+7~1u5R$UpiS5A^z$1Dxq@&nydG29xUB-2jl>NX9VJt z!XfW=Q!Dx8fs@l*N#;_hK~q`Od2$Zw%cQ+RneT} z)@bPLCS_JcGdv=yfgk}zowJk#?gvV!21@;$R2W_+Dw<}5rUR6nGCW$m86bjceIbpk z@VXx*(y$Sl>&(i{rcO|EJ8%XoW*&+TLU(JnFjB+H5}GZP7_LKM@|>h4$oDq-9r9GY zqNyHIu>m&u1Sm{pCawSY;QMZ`;KjUskTW|(88O&Sb>Y6%tQH`fesN`m%KJ)BP)>J< zllmHQMwVwtk3~ARKtFx=;g1l{Jw1+>#1myM{=G&#Dwh#nFS+hZZpT3fy1{@pRw(OO zHbMzu!}aG|?mhsSMYGDB^}omzgpg}@V6Jk@rjd{nk>UsNZlsEEzhg3nZTycVO4=Y$ zZ-w{jHA+>?s?p@`$AZBgB)%#(L^*^Iw=c^AD(H(KjF`FyU><=Y^D!_`H-#hW5@@kY zY+-SRE>=|{t$mjskiqOqP`+2NKyYrM<4z2&%ZO)8rV&0~sObi)59{RxSWU-OL%VkC zJb&Rac-&UiN>IB%A{Kv%+OSczp9S0;E`PjGD;LLN$GD>#N7bhM3YD?+Rd8%b%XoH9 zyD075G*+H@@gXyAlMXA|35L;y_)2_Px(-q8RRK3>ul@Rs`0Bb8@6huFcAziCr={Cn z%q;P_-BSkoW$r0HSo z^=7q|^kFFNw&L;g4^~%J%zyYa!RUYfWAK3Wef+n)PA%?Eu2oOCRkuN~K%xF-_|XMT so$6a0jj;YOYLVDSl{~jS3r>LfcD)OttS0oq*G*^~)SZ(y>vGip7x#ujwg3PC diff --git a/mddocs/doctrees/file/file_mover/options.doctree b/mddocs/doctrees/file/file_mover/options.doctree deleted file mode 100644 index 4f7c7c0dbacc5e76ce72bcc1d1a3bf307c154a61..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16664 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 PC`yCNkk(aPk~jYsPK|Ro diff --git a/mddocs/doctrees/file/file_mover/result.doctree b/mddocs/doctrees/file/file_mover/result.doctree deleted file mode 100644 index 83f38943970126e6a777b3bc37f484367868fed2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 122686 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 diff --git a/mddocs/doctrees/file/file_uploader/file_uploader.doctree b/mddocs/doctrees/file/file_uploader/file_uploader.doctree deleted file mode 100644 index 3b56abafe5b25539b9fcaa040988d556f9ef2228..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58592 zcmeHw3y@q_d8S61XGLnA z>3dtF2{r+O!Af0{THGw;VF&@1M*-PQD0X4DHiSyp6dSTj!Y+?xE0sr)K!x2(c8h`~ zyV>tQk9*F&eedbV%-Eu0RjH@@-t+j+fBx6`&;OtE){*!Am%rM={)M~zhFhs!D%j<6 ztzLHgb~s+Hms%@MEoeW}o_bsR$#y=RXxgiOz11u^?Qjc9l-x?S+;nQ~dAyvY@ zGX}6MJHp`B%f0>i9|;34%-KQEtSq(y%qz{~wo2K@oP}ehs_pxDi0>mcdxd`+FSi=% z$D&=j04%UF+w5x4cK3zbTFpv3yltQfYKOU!T`O10Hisr0y1VU8`>k*+u$QUZwtICr zDyeM8T}?kIHk$QneffAh9IjSsPW$B6aI|4#S^&)6)}rlRk3qRF#f(|_zaRf!j{gTS zs8K-9V7*@?>%J!KAQ1VgHn+u_bwEe7Yk=zE0vcJIOe+3-~$>=kF$ z$L!n9*v^kXP~mKS#)!Jvl$=az7HU@x|+ zcC%QjSBrkbDQSr20A~}*Fe@MKe*D~y>5qlv|mLGF^27P2Zpw6$>ASiRpLCCyQf;Mulm+ny=4V; zi%+mcGw#!G+iE&1^}w+>mszWoz_kbgm8FVPX639>y;kGXZh>1Zup3x2aGL%h_Pgl> zt!52ht@@&ea=Z=EQPc5T)xZ)1EX08;G+VX&c?Qe;R4(_h6S#HsS#Jt3E45_>sese1 z`$4jqixp>;agLuyGp}|64tKTU2UewKLC#y%ddaR98+PCpBpHq*5hw_Qm~**VZ#6n- zhKPI&6xAm|jibtH1?r}KGDjr)F3`|BK&DS~RqBp(qe!iRpVb>6mRqcs*HQu9)~v6l zz8Hb9YH;@SDRTN<@$g5)sMR>5cS%X1`O3Wl^pJE*geHGr) zTLXm{P6-#k4K1`O67%E`u>2YD)4V&XChs%)mB96`rDDupO|$&+?(cUOx86RqVdCFN;<{hc1**3n^?H}7?l;_Ty5BnGemlH< z0C~uUz3DR1+s#nn&W96`k)iNuWen#7QwM@i#7&^HFn6EuLlano5}S<{1w9aUdQpH88(xcNONMNpmaO`e?j z@S2ziQs8RL@g^gs^d^Z`(1wXWvLw-)QEw1QbbJs=q>X;_1yY4!1T(NTKcyj?9``4S z-c2>QBw7-cO_-LFsKoovmP9X4T{|u;sMX`b0$)GoF^a2FOKSuBP-0+iq!Gi(c%xFT zP=S;XN`L2D-ok6EISS={(yj>cPyLR7ShUG=vkOhSSM!} z3WsSi4j&**@Y_-P=acY-S7{y-I00(Qe9>{GVQ^Vtc{{>f$og`V`N=XDBHr?H))@$zQsi18GtKRo$;-lelvkapf#BGrSAJTkFbJknA-_0y-$UE@sJw0FM4#y;_Fi8*@;@HtTf2OPkt@# z@;Sr4KLYR1AKjbqYXP$Jifq=BzY&?kZWi-XPDZAN9J_={XTa zVvFa~yc%H8aVMVUD@#S^68v2LvAgK?1N8k~$F(n3>dnFfXU?2HGiTl8G@JG2O~(&e zWv2?qU6Bp-*a^GpJBO^*dh-H&dB=`SNj<$K+{Gr*uGQ*+opAStN6+7NAb@8Q?J{bl zcJGpt4qq!6WX_6;ST`L%4yDUH#Pi_@_b4cfY;0>>^y>t~+tN5F9I~XG#5dK*o72J?h>hV7$Ku$$Ec-58{OpUTG-mE__>t2bd8ubKk^$IPqTvxLC;l z4z`D*v%b_LQ^%+S9^;ckOyKO`bA~-T+Bng+sBY>_RtUf7eAsh&H3L%-N>UeY;daP_ z8OTfqTDX%SWt6~v$Gm27jNg>RxOb4A$RSU#At%QhcjuXXeBLX&Kt4{8bFjy$w{8&M zX@+<≧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 EZ6gZ*ovLl#zy6pkz?Zoi`kx;p6zb+ zOi%iv-A!y)Y_PG+p$Md7!eh9ks8l%*fdCJApfE2~C`cR^5Gqhbo@0^}DQ-`E|G9LZ z?(ON>T}h>=YDY7t&-u@P{_FYwKIi{j?Ti2Z^bY=y?G5|3(_5>X?RL*^TVcW`+I}nU zTD>UwLUQ7j>~9#?3HtA~gEF zM%(H*J%_%g@a!6Ut6n!=4C8*^52A+c`9Yy_caem(EuGB-@vAlLz zcl`OL*#5uv0z06AiXDtu=#e-HpEw;O`zRY7CfjTtBRE-J&EUv0bh|Y3HReen<_Qe{XednP zB_?&SAmIxb53N=~gY7X=m07H=237ZRdUEJ-z%-tf3o|Z=g(-a*{e-nfwuKGo_FBS? z4)A|o;J;*_UMl5+qWAzOjN)q$Ye8B~D; zV2;8_wl;vA#giF&(g+<$a})?6PO;Lk^(*P?N} zzuq=`k<&7|u+!f0b0>`l)r5_(Z?>#OBkgM(KX(qQz&Uh@&E$tB8HHcfDXcF+K-8AW zMhwBgOY|*+z;px(4!U~_N9@H>Vu3^brlAE_49l@^q;?)3Q)E@9JxcW|C$!8BiS zY$#ks&Q0{p$o-YUg z{xk3zoaR{RDiKW+sFvT0^c|R8%k1qWz?v(8*|&9hJ4_2UgbC-8EsSnmN!h^X8R!0O zFz0mHA>6+YuX58hklgL}Qo`-MVg(@_D46GT9IKrp&Li!%)i#`-Av+nT>i5-8N#gvg z*yUn_cet`29J$lu8xc*mZ+gkLN^IV0w+F z=eI8F67g0i@VlCRBlw_D#L&&g)Rc4q#zVRGx^&>PJmzpZ4QtH_qwt}-1FP@B4>zE> zPGB|K_-I9buzvSR-V!WjN%Y-K5W7>dn+*lK5W+e>yeG{5c27sAXW+%9#Smj ztQ>Fo-CR%#b-nZ0F#{n&%R(&EHk#{3FYaQicI@h5^DOf$w0`eGAE$-b!N) z`2@`~O4D(PmCz((#y6_x)>AQ*N^ARF)9EE_XMY{ZCp7SVh5ativ&rO0*@SF2`i6b0 zg&kcBzGM=?o7|k+-$8J=z9_ffFO%>1Q=NWUuux16DZ$9=N4a3|U2{$_>W^K#c;TWH zkD)wzubNKf3jS>jikt2i@n5PWBM&L%z_*MKKd6N7ei@I8HbjY;m}R(_{kU?90b(YB zWBa>7vm^ntHBevOY+fJE?KNgi&oM*eIHgODr#q_+?HM8yJM)MXtg$B4Lq#ZCgZl#K z0R^7+x9q>mo2YMZBE+=cMdbb6p^oi6_XyB056+opP=6z_|Is~)uH;$~f~Im0%k!&} z6R?WB-f{b$fT=vWIuh{4?_om znsLR%`;2gL>Rw}c*$M(bSY9?}G2D#3=!7w6bj{#0r?vs{?wBBZoA*WH(GM(iE_4Ym zJN>@wqe2v77Vjw=@@zC#oyay!qi3yBjKra4r>W7)=Y&fMvA( z)gC3gQ)vk*UTdM{^Y| z0-^0r2U|Dtr6%=;MGyqJ#EfAQsLe!!^qDeaSaiiwLlq0~&cgi^pRpRb=V^kJaWv{# zKFa+h{!@mF9a~-=q7g+Z{~Ez1D!WY`&{PIUQFly0))xH+jom7FU&A2C8{Fgooz*6w zzWM~4rs^2z^9rA6D^#UXWmNs}^0Kl7Te2ZVTK@?xXCtlm6~QZtPwTU4WO=(lmg+T1 zvHmJ22k3qrbl;2x)Fz~{+v3AGDEbvXaW#&4>84R-eE8*zP}kog+o|PY&)t}LI9m&P zcBE=5B?q@DIVi4LW6^gDEUKorl!<>(GO-Am*rq?za3FBo5|arq{99WV6B;GNWUyUe zW5R7EY~)^^YbQpxfUz0*dPQ;9Jxr~5)dG3eU$rQZQ_v;S3Zx7^ik_(?R%X8CyO+v7f_!9Hu#Bo? zzYK?Ec07%ZGShOTDql&Pz<yt zvvIuXzB#Y57q7-j5zK$_dj>dwafW zQ~d}M%@mD zUpd8qG^zmZ{+VXh)BLwRw*oEmf^y3YvmLJghc|!HcF5y z6AgN@xmaWzo& zVjnDUGrx<)?pjQy4CW~P)P_G=7+&x$nqpI&C3n{}C*SVZx44E(YKmxQtRIlZV@ubb zXwQ2Pk!ycI5&-MTqP2TLW0g;qN$`^*DZc7>p5fsgBi=*mOtXCvq>ZNWpLf&Gj*K z8>i6R%juMgVB=L_L~N8liIzr2SWPZ6k{qBMCUF3QlTrUdX@HK?fQ!acr0S1D$<;~i zx-O3N&P#tNccwyZGr_ze@P}s@_yUv(DbFy7E*aCmlyaba2ll>u7c6Ee)01?lV%}}s z+SE7+HRF|a6m#Tl-_hk|ouWB%XA%AF1N&$beKp0XY%%)IL$i`a>|nb`iOm|^Vwd~^ z4(AsjMIuAJ`mD73&Fg{mV7ojhE%EtuT8(>y-O7-(1jMRL`6wePl7Vrp>I+8EylO2?iK#I z%0Jim=Q{uV4FCKreG*#NHE-~QJb!u}QU(uhD`g<(-)^?xPTe}FY(Wb1MkWfTYWZ#T z@O}M(!ia*-m4_xYO_iYPd-kz(0`}`SHBpc&%r6~J7g1))t1|-` z0o;2COffUC3wuHlNETwf^2ERcm8ew4dS@{dK5=Nc`U{m)42bmv@bbg}Ut&hhXRG0B z;lt+h0(^>Xr3D1yc^j%b=W<~#=jF(0!O7t?4y$QUu6I6XU_ngEQv$D4!mkt)G0X5J z{C4FO1H?oE$I;Z&+Lne|x1J6t)vSz>?g*e>hjc)-Ie*W68`BKP1f-^fCjq{Oj^r#U z37|{?-1#oKc&KJY65#Ao+-)Mv$NfNQuV`D?!bS4PGH{m3pgUMN0_2I1Q(U`B1I=xY z&UE%05=ph^DEJ9YXK96W#_h>#54}^vETZnOXN1avo8Yu|4ybG4c9A^x$Aj*!x`1Z2 zjnMjZOt0ucrSRXyMu;pxDJdVh zXo)K=bTf?~`7@jlnWwJPo4s%0q~VA4RzGgE{J0m*o-jUSoWb$2lZI7asiW7~<>f5j zbf^@Rrt$9-Q6-+*M8;y<0=`MSgd3(WEmWm%j1>!+o>XDV0SuR05q!7I-K@#mS4 zPmkptL!NV$S)d6FtxDev3PnfLDHXwxNXXBtZx1T7Kv+#K5S1D@DLHWC-jAQaBDN5S zDsg~gk|=H}5LKQ_Ifc9kZZQyj7$}QC^bz{T15y6%+5*u$PCDmgI4y2ES1kdzLehE# z4LWGz~|3ZpC|F$jspV^4etbKvXrJTm(vbe~sD7hi;SkvY}*0mT^io?=t zg!~e2vB`QIqCf?^-CD!VmZ*U%E~3;-yU|>4@C)KECGID&Ec?-r9WL#K0w6CX_62mP ziVV|oD)!Utpt=i8HF_7)#P+G&%9=?H6J|I1=i#FDH`l{Vcw z4I6YOzM%@|U_LoOR?-1ka59mq8F|(f$Si8rOxY-#qkh)de%zD*6;b0O4zt!;+iJ!u zm~Lj(40?1S3vey7in&E133-{Y`fP_uPQD^S{IbO@)Nd94ipZ#u# z3mO49K&Yb30oyAmuR*DCCMkLR-U=a_J!+sf4A01?~hX% zD4#@sI>Z&2sPX9-S{;H=xA5QMr?_5^W>kSXiX(Ku2aFf`1}bwB(gm8hPj}OS9j{&_ z&A?SikKb~79ms&R3kS$hz{(c7@d{s`ZD{a1u0q+C6=9A;E%dtF zuy6y?YoLMx*svWTjVNEPDi~DdMjn@)rh-+1Rar=Q( zuF4v}T7S@<4%va+Bl0NpY(x37gCWh*NuN{EEGiI2o&~wNN6ipt;V@XV%?g)v!F`?_ z?z8xa*(5#Y$J#JRba;b5N1FOBw7^3>!@Ht3)8E=J^1E>SGA=(n;L917r2@vg9xODby(e=JC)KQc(19F$*zX`p zppA!$u;dfCQJ5qiwt;zUcgshffLG6gQyTv`g>z{C0tn`Q2}C?U626vPq{ED60EGps zLKH<16Q>qRZ^rGSnQ%RXuBz75v#KgF*=zFjw9rSMz~RGyT%)?0cQ>j1hU#N?QxFPo ziGvSnSE9loY@>e)gsV2XA{(*b{)D8XVjItD(n*fs;ukM5GWz7w;}^(Ni7JDzoU-Iw n4KUpw33zOGN)fIXyPaOki`!IEV}gn!!N4;0?s_LqThF-Snq(vv;f5EzTvr?@e9WR#4^+wrkcY_t>MyWIF z*8T2d-LdC%pVb`=Rq+Ko=DOt<~2 z4>ue2LfM_E)~k3K!mA7gFCQOw9%y%(%|^>FR2q%L?FXFdZ1IpgHRV)%zuBIen5fPk z8gEysZmrxNuQn#8i{(S^#D$aFwu*Le$y#omu65jD2mYJ#i>*VhuRt{Fu3sB(xy7UYONuqWTiFmS>9nfd;PRdX)D4D9#d^6~E^=yuMb~vJ7q>gXa=&;8(RM4R2g{_E zt-iL^_6yBcqt-aIvl}d}RqJkd*WzGVvj}1VVovQ$7b|B2QjyTJxqV@CCjK*;C zp9ro7HJ^1S+Mo-?)`UOXoM3%1fx}?}p|C#%Nyh}IV;uUw%cx)8c1w}rTUmzVWE5B% zf^l9m2FZq70i^RZ!IiUzaV5vf(!1TGXjcOJTMu`cy#eC)apHGZw(qtIKnNOp|LO4h zUjBOJF5rDBP<>J`#74E&<(%eZm01pI)vB_QFbaBDtseruf-5;AoC{}^jT@(0cepxX z?~UW1u=l{$pcKct!IqR)#&yWu%Cg;lw}|Xi6g!-IU}h;8=2DE8QFyWzpA_0v7(srg zr3+%;L9p;Tj?s&^IPFfU?nb4*B?jW?REYA;$d~RiRN>*;k!&*$M>*n zeCvR-!`TfWwm36I_*u>YkT)pQ8l_^5gT0l3xE*hpu-k{L&1Np(9BQ-ZSH>B>UFvMDP)xdaxJ}`6z;D9tG2^Kkg*d4$t1>q$+1k;8Fi;i@Qx*=cdq9!QiYP zavC#fLoYI}w(nZR?7?a-Ac%6%)o4yV6`t(G^cPhhzd5;&M}t)`T(?3uF-n3pNEnkc z`|ax8UwM7y4Jd-gRX0}N2quoRw34wD<~UYr!1!bAjeE;O2M ztLQgcw)en*T&x+8FDENau&5C?AYTIQ_&nvsrJ8>hUi>tS*3a>@10wE=bcZE51EPC5O!v>Q!n|Qm?Kyuf;;HXDb=6e zG8hOH(q;^)w-lRjypM*RWw-ay;ISNAgIh0wd;t#@jT>TZWD_gBcHtA<>?%$bp?Y!0sifAqLpr zDuvk*D$EXz&*#&S|1m~0npzyXsWiWohMa5^Ae*h$SvIa*d9V}{0m%XXp|t+BBr1%5 zW{Qw9749uU8OJnr!FpM7Y0nm0{xds}hqSwo;OKibLS@XFveXqj*{_VqoJAV8gakww zIb{i#@rIID`BQQGK9bu+P4nIb?P6xc?k_$;qoB+P$C9odUm{W1Ra&s1xs5ZDdY5;r zXSHS)Z}DB4ngnl=YRYb71(R3L}5IveweZ-A4aI24yLp9Y++(iSo zx(R8@UIY3aIyllp+(TmV-U@&WPnYTz1PP`bb^T8E44cD>lQdqjDpJfTEEjF0R|tPA zw{V1JF)0A?+R!eeTX>j8LAeEvC0&spBT*zMvH-4p=&A9Z&FK(cPt$QcIfU2P1ZZ~% z?q6bZ?_5Uv#xZV0^rF_*AGG4G-e-7NsZvD?BvGij7ISHm5b(JQpe^`u*+bA*L z5x4%LO=$h)Z)N=_X%-W$|2VYEX#KCJQBc;OV@bFEyGaxYiY$N+()u5y={TOO|1)d? zv|E3lu>EvyY+uL$ytV8G_6Lm_0KwJ-b@li51Lf#|1KK1 z-TG&x{9`n5yY=V5b6Ee+lURDO{+t9;j=D-mtUvu?aON!3rm6+3coo9Q6!-~qSy+dv zd=iRKVun*r&1n#i6me+^EWCj!30MGw6KaiV57tw3jd{LcS>`GubI$ zLu!%7N@V;{tJth)<^ABoJuaK;t@9agoSL11g#Zq(n>&0V#JKc|8Q)eub7(0Ud}vl4 z%YjSq?TH1OV}_MK-bsRBaM(-XGE9A^Zi23b=?k~z%rxMC>?2&B(B9&dtM^vRZW$Jo z_zkDzw(vEl1Pi1NHE=0PSpMq=gzC`^J@lxz9vi`;8iPSt__Jyn^w0JANKmCEt1Hc^Q=tD-2cNBG;|?+nF}lpV1d+lD2%|z6-UaN-YXG=atZAUt;{!0z!gVfM z&T~1*|7ft3O6tfSIi+ z%%}mTw;IdPY6RY?7`QILJIwTWqi`9HlC?rHcmdI-6lH7~Q;P8JWg0Y3-i3*yc!|Cy zNT^KMmY0os*TM5_T2O^IGiiZ$JHph(eJ|cnQ<*8^_V&Q*OwQh3xT<`>yBpg}-r{0i z4q}~wS19V@V#QX|8CscQ#a2mTT_?voU&m#W>&ua$m|VlVhFs-ckgKK0_WKBlEqd%% zu&+$%gm8Gr1^YNO?OU)Rb8*S8fZqP?6!PMd#a7)6txU;ct0c*uEF>$k-GhAXv|+~s zDSi14H}!g+;(Zc`V$wI{EbpS6{lOXxhJ=C3A5(uY|t;L<)Iv zIb*AjWoTu}8CxkiYw>GrT{ZXmU0e`^$TtHj)4r=H2v$cjk@*kpGSaIQ&HFq6s zyrIlPA4k08Y#YCHd>aoKO*NulH7ZTytDsGZ&sCVTYKYdZMesZrg6}1OA0vv#4&l?% z-g0<{GQGiSzg4WaYel~}4U13Mj5Z5lC%q*hdg*M>Bq1&3YhhN4UgUHI%f(MvB*VIu zNee0G<3@sB1%gM3K_UPX`>oD&3)aP`Xf9ZB%h4h%0ftF;9HY>G>W1pDtsz*O+0|1o z?4e8WZ51sm?b*bLSOY;5O!E7MX{a9`)@a%HAe1_H?%e6jv>LP8T1@zC1V18iuwciR zKDw=8d{3_5fx4akY?QBLfo#dI?AQ#e-EndA<}J?n`1qLCuE6sbXe{B3w>tIFVR#Kb zG;y5$;T8VcJi)5nEX>=!c}wE6$=EX<@34lkXYNr5phdl?lb@4zmCh2Nw5*#qQBttuLo-6Z%M+miayfdk~r?yE9m70WLZKh)B{C#rOU z>jWHUG-aKs@%;jJIV{Zf=iq~)%knf-Wa}Rmg*7Uf`d02U*%_@oC~pKCbbq1XK^!nz zc{qIQa`kbGq&3g?E%3PrXHcutv{3L`c%MEqbzh`~`Bh6oksZ-cpS8$3F?_?~)k6fw zg%Z9u?EdAUdh+-z{H?;XM?>0e;n`^A- zEX4Bkc#RKZX|V*Ilb@WnNr{@8tVvWg^{T)hpS19P0uu?|PvMWD9^Svfo%Gf6e4gS1 z-p}!EyD9Ph0}=c^APmg!lfZikqx-_H_h0zZSK%dK7-uj-vOzr$3t>RX{TJ|>92KGn zZ6=Y|?s4G@$-2Wqv!Xkc<7e4N8cuP#(eYVnzKvgZ!pGub{=yN{H6s1#4~WvDe-wmE zI~*}sniip$#pK39yFZ-d_`2%xEpRD&T+0iIMDeG&xE6_`#0ZlA3AW$9YdwhjS`GQW z+m^*LLtKFrW`-=oQ0dGTC(IVMXjzWD#t%mK!kiXM2DN0`_-&_zySw4vfN9e$ ztyY7loZ8zn@PL0BED}jAP30*VhYzobRCBvXRAquDGfgc?yWfbr6-SWtsQeRh**1{h z-oc+sc19tmZddPfcWmD_dD$3bWfFcY*W;jy@F{D>Z80A5f_#>OF7iH;a-Q>P^c%z= zK}L2i(oO{w-@yM!KDR}9#0H@ZcNoGO-8>=+0UXtcQ*ArIzf)v30drZsw<14#;U-!L z=#!hf+!o8h8g-_O5q>5|rkwropx$t{UwXh%FM~0xLs^dCPm`ofzV8o552&5E85RcA z(26{Sf`A4~rr`!GJ&W89G#Rk;hYA)ib2Q=8V1E=xoE%CrTpb&}8$QT}kKL~l#(k6x zfsx7aZO-8vN5+P)z47)NcJI3VDZ6jkIlMQVG;}8IuTQaR#E2Md`Un|u1mx`!GD=jw ziKNBf&~bAeisIeo+&Bltu#qu|xW%efthXmafZ^?(@BpChX_7RcGp71Fre2VYJeCG+D0>yG+eopul zyOS^s(Fva-LyTW$pAyzS7el5Jt<`$BK`vu_-y|!bqD&jlfObYQpx{kxVoU`76_n>D>m>yF<%g2UuG_ z*FqstTaRnjE~+AmwTS{N9pD}oej`=3^aV#xlH8(_1?R zqu6XhXYw02)aOPHs6DtK2_F!o>A6C&RxP%toasiRhBmKQYr~FYXpV5>N!(We9)=X- zTQNEcojQm<3PKR6K`~Lt=XDT{*>=p?y3@HCdJVb)IOpZ@iYnPJMQxB~kpEDH!C!?T zpwE4ng+aPl|Fp1eP#Vxo8O-n%>wdK~j{C2S-&kzBcfcm0wQ`Ca)Qa+BTyU?wZf{o#>RASA10n9b}Ic=HOdJAwV7wa=6 zqj!vGnrgBj?YQqb2*R*d2TrmRp=bPYzxHl=53H ze=Do>Lb?kQzl(n!w1Xttv>Ns$CoiK>P!67BNmumOkthZctq582k{vaKItH`vhXz$1|^D(GqBTh0FbIPV?P{wMqK ztmf@HnpL|u6u?vc_#HHGyC2U=`Fg;>S6ErW`Z-@zd=^G3JH!{CkCb zPx@zdLs^^W(!3=)=x0N_j1KxGGz!W=b1dnO=l4kz3D!UWR}Ok9764gi*%ylP=c@1C zMq|xrB4Qixax}e$`hUa(V^$TDO+?lX{shgceV_^8sV3q}G;q6#$V&OQXy6p(rimD0 zrDr*o#4n``KPB<>Vk9_+JsOGgA{BC0+otPvYm6q6(a2rJ@S2sRlTWl9E1_LR%dv?@ zL0JxtCEaq2kth-@hXAfD$11IRS;viLvmn>d*cZlvT*(BK-gS4<#M?&}2c9X~KS07K zqy4Nb941jDNMEp!DTWTpI1 zXy6p(ri&o=zeXpz?9q6fC5=JiG8v`! ztMzxK(J*f+gYkWu&qRaq9cY))VEmj$K^Y8=CEZ}GSVj7MyH*I`s^juT8)wm27sg-HnMi`Wi+c~2d5g08)@KngOQc;yJ+AP<+%*TJtUr93gkHMhd^?nG8ljW|w zo>~s+9m1t4^4Lti-))P14c^vQK@CS1ZQ6@8`diA%JVwKJLgEHTB^X84|s^Z1DpJcMUM__1T36bxI^->MXFTy@yx2L?L}~`78v86 z1FshZg5>7uvxz4FL-ey8+Yk|35=nwU!QP3qZQr#a<7X363CYhUmhqMU=H`Ol6zH$_ zX+Y;)gI9lzSJ#GDJK*XOZzubE9s7Gd`+EcXdn5aM6aEDmRtEZU!*$}jpnUrp!C~~R z>U76<;roV5bA8{y(e?g?L&RW6aFK0|v@^Te;LWqA(GY8)tuXx#DoL&ljs7Ll!c316 z@7iWTjoZL{Gei*FH=u6?dXge{%If0?xudaI3nPCjYc;gm>`Sv}=no_m)80}9 z6Wz@y?Yu&Baf^Mu*+!B+a#~_NG|jRg`a%*#f^u-+_K?PMkc7Rni1QVnMq<<*O0ME;4C6pM&wh?dm1HGD=c$MhKM2%$x0@mRh+O6D zmmhD1bQ<@RVR#lvaV`QB?cUeGP7ZuKEHh*k9*D=gzXe=G<6UV4s@pw)IViAAb#dJ-Vf0r|shT9__}g%f18d zw8G}uhn&zt$^DU=$Wx}Wm{Te}Me>whuJvFjaMf_kR&gG)!FryIpY&i7b?{N2Dv)n1VLN zM!NDLXqqJ=-AJNH&KPQwtOT=;3lU*P8jGl&Lu<)cXwkM;xB?$G3 z(uF%R>w*>`92e#UBaEjC>Y>Z>@1OaMT&#=zGbKg{A@*bY`2Lx}wZb7w#;`x4F^w0UXy&_VA>fD*UD4 zmya4yI}aou4Lei!9g1ttS@RbdAGJ_uN!mP7f{oL54i#mfDG)o?NTWpt#tA#u@G$sM zAeD!~FTpDo2D7V$3xjVGijKQm_8tXa)k_qNDEkP4&#(o-9A=~>I}b7Nn?Qc~V&Jz& zTA0C9LZpLeDDBiMBCih-1P?A?49q1E4S`o_Y9@`TaZkDFkA-~X4}eu4cppVDeFnf^ zB1s_w;Lk(TECKMhNE8Wb!GUKAfPYHEUyuNpldqy!Dgd@hR5kTp4g3u}08Tb7JpSFV zHXi>P+APE0)8IuJ{z`=er}|nUlq})z`569k>kRwLPvfq>V@YvvrgzmyI~%CejabAa zX}X~W#K(he!Fc#gL3~f~@Flr87xA#f2=Va6*ghT)?|UZfW0ff+mXWF4CwAULN_U zVNB%C6yjqFqVf4!tnp+Eg+!(hr_G^aF)dS&_PzH^;T|BBX9@+pVwnQFTDVN%`f9u2 z&NltI-qVE3bEOFqUmrQbX-1Bq0M37ca1_L*`_}Y@x(`KKM7*(mH725=lu}O+{18F# zz(OPls{5na!3y<=A4a8asA%P7A^G|90>!%5MKIC4KrL_1b{y3kNm|In;C0Y6OJeX2 z5=BB{z=7Kn1J0xB&yUauwCFF_nuGp62;&T>_?}7)tU^^yy#_Ei@YEo|qVT-n8-Oud z7gwq*lY(!;TQVt-+6V#dGle{|Bn1V2Bvsovc<}C4$31w^sm|cpQ!oQxWV7(RK8|tH z!Gr1;po0guz;B+4Lz`l~jE}_u>+oAHt>cIGj^e4k5|Oqj?R$by>D@g20PbLgBuS?E zOOZmY`A;IvBX&;HJM^fR;yKZi9^!;ib09b?nNtl1P2yU!Pb8tks-rYeL+eaKo{!wc zTO17*P1djyjU-7f;83G>V_)P9F_~wkXqBcNlBDo}c}3G$Uo@@K^-cke_LUmgQJbJ8 zrwi`aFf;v*8p!!)YHrNcmm)Kj7$Gyg0oz+L(@W^gG(4H9|3ox$8d)rt+deM6{V-aF z=wW7&0|>&rRSnJ^#@SE&%rTa~Mh<@BPt24kDZ!jnMCseH>^sqISQwB8aK&U9>oz`q zVG!H}dYC&KjO+0{W`p-wC?vAMIBi+8LFw3~zcek`Xh5BKz~_xRKq}7$@5L*Y4YI3+ z%LcFY+Ku|LI+*}*^_~UpdpvqF0i*rIe)HM)n}@yV>m~SFsihr>a8t2XD^Azk{rlmp zf&*I|{Hu;gVBa>i3&nN;KZ6P}p~c_gTzqjE&H!&04j(DD4#6=C(3G8h0824hm@%p! zdccLx^TMU+{1 zn=GPbW#M!Z1c3r-Jq+lR&nj}%sOq!oV+2wRS34b7R+D)RMS;(|s-ZN+fTG5Fr zb`E&IJg&uNI*?{W(}(T7G^_S`1YzD%_oH||4cxvTg#i9&co@kdM^=;IN=0)orGZnF zoBL6a`{liw#L~-d6r4X(j+RU^!HBr8XcjX0@n4}4EQ}xLy<^({WIvwO zygid<)$R=i@KitkAPwB^$FoxYn>28W@*IBrl_Zv4{5a>&lw)Q;KG4$>{*2}=(Luir z+GTXmAEr@I4w_?0cRc@_M3G<(1aRe`)!K|~>l(gJW6fwH2E140SD9eU@?^4!$lAgG zNwaDnXaabui5NbWa_b2tt*n%vK?A2KH%&y!Mz- z+GVsHchD#(%fYdvTaNuCiUi9cfGf+<>*)!zH1>tDAXO%y^sajjO}u?%ap0Mv{YOdo zWE7c|g;$X%5~MF!$P`7sg+`E3zyq!ZaRyAolI$X~j--#$tlHH<08e!hpQC}>qsXk3 z{{s!2qTF;5WHsoAB$i$r1n19`W9BGwK2J|rv7U11$*Q&#+GTVNr_(4X*TAu)+qVly z6bZ&b09URdZ%}y!jWwgi7;sQI$pn)%sLa~IchRic$DIJ4YBBDif!i%cR?2HMaEfx% zVx$C>_mOydu^F7h9&N@kIX&Sqn&-s0@=<7)(RjRuMnM@5jwRiA{0WI7!FUMZy&I1Y z(O4J8c)XtpCadwt+QDC;S+x&A0X)@s{1Xk_ZalJ5{xcdlMR_jcF|>j7!f%B|K}9`W z2-ded&1R$J7uvJMmjBF71HJ(6;%>a&tKnT(YRNh5(Rj?;=?Ul3d?p%zCCEad(lthtWHw18HH#QMn%-1T~OX4eJJ8f~r7io%mwJ8c!uiN2$&L%kf#?0R% zc_3>~Ik5bD-&m|gs3owi? zmVt^Q+&+2H0ge1kB>DM8u|=AMMO!p1WBp=DAE{T8C=y1h(55VMzJ-S0>$p2sa{g74 z{0wqVm%t$s1ucOr>mE8J{DH9WJQ4-Xf>nul%~E;A-=Yy@)S>}btobM~^p=_ z#FsuiM$yjuU2DE9ZGQB&()%CK%qB*I--UJ=qrv~AQBct!$C7U0hEFGbyr`n90Y2bF7k(Kg2G;oUYh|OTzEQr%@ zqI)hJB(d~jGdO=e+Kly<4*-R&P|{d@@x{_U;H9z&b0jkf3sXAK2sV&VqiMDN38F+~ z-c%@GNTN*EUye00uWny3{W2N_WrhTAsq5=sN1{lGQeqv)B2Uyad+r%YbKXJY?9~YM z75=^*Xv%Jbcn9RsboyQMtV#71s`o1V-im!B>fHw?vV>ntERmnZCUiJHR;v27dFpV) z(raG%6-$R(0t`B>Q5QZ|d_4AU?XO5C0BXZ0Hj?7Y>_h z2T4k)LhSS+@j)E~CaDKW7PNyTpSt(X-SiQX2gT`-Vox9ONeTRF9qP=wb-(Qt+fEZU z&_9S@V~>s<7N9x-I}z1Ou6{=9%8*8BY?lk7 zHJGPHV4BrtU1f0o<79k$(nCVn-axV4ZK5Q4r@*V>xLSEo_ESiaxS>&WL0=S|8kc|u zn~;EKM8TQshLiqfKn8?oKae4phhznjNhb%q45FnE^2o=~9_n+HWr z8TP;eHLr)qsX8;AIy-K&go9F8mdf_tUZiy=G-AqqQ8xB8kz!MQbA%|#vLiy0#SIOF8v&7NJccBX zW$t1PB~A5OQy7x;)w+l@U`P6*-xwfKy0Q!qZ#UxKzH6zezw>k!?W2G!y5){;GDR9M z4?>Uw3O0=rWS&OtB#8!B9E2eK7`dShaTe_xfKa}j%^UHGZD+%-7H&J6>u0O&HXgg& z`(d<~<=Wg-;Ol)?n_z>nw`mA3vbE_zkE8u_ROehLfBj{oMZ|#E(_2JCsiLe$;c>L@ z3lRkOFT|#%VZTTBG+iD(;ak^)zg4LI_aW`JO+)D2YZTw!-yxXjj;5#H3LAIrb^ZO} z&Tf1jC)@s}KO`w5OXlB$rr@wrlhOYoQ6waz9JoCX3Qvet9lYucQ(q(6*K6__V(K*? z5^R{07xpR7Cy^{}v2*zI=5 zKK_ieW<^!bxTm!AX(YvZx2F=IX!o9g@ip)y+3s^*=6(Gn_8(7}tH4h*VGgaI+yvKy zmms8IN6|_H2Il=(Deh0xw%8$z<3ah>*D+jnPW z_&5MaN}SKkEKUrmmf-%j6TuC=A4D7aneDqUPpS*_(1Y_7XFdcNa>SXEAc!*`MB1{S zL@SkniZi`Ghoc0u96|dEz|Dis-^MEzbh4|33p#HRqU-kz+NoY5PkC7BvfX~Sh$FEA z%5xHGqaNVhC-dyeGBjeNl?UaG;4EAGDWOK9(0Mq4>+PsZI;p7<$GamfBGG_77DY5! zW6`&U2!iJ%kNgcUi6_CIO(y@sNF9Yo8?S207h(x-@(xBMnt1!#BmP1DkBc5lwk7SECiD!;@D&!MJvV=-)movC-NONx-I}}bn_K4Mw&io zQNrZ-x0z{~Meq97Zd&!Q7qP zIE*%=L7q~ZZEx~%!)~j?Xn$GQ<%0&4?go&1H1JG4i#Dz|^LQ5RofZl$37jm!#%arX z7Ok{eRA?NKXVK08sXQJ&gjXycW>*Ur5ASUit8KSXoq-6Z?!$8DcHus^)hOVpZ@q6d zep{~Sm>{8#TJ59}D-$??&8<6~-9R$)C;CR%n}}wi^P%2R&gUVJc(U`M3y%+kZG-2} zQ;-W;J511xV|u@bUs7y|;4<7-J6H^}@2ui-_Ol>iITy-A4*aiqR2kQN5^RcM$9pky z#v)a7{e@bMX^-@xYjs~0_K+=b)(N4?XaioW`#V5Xbj^!Sis%!$1jE`bTDW%vzzsay z8_haOx;*%MKM)ZuG?Al3F4(v@DcgEFt4l_n=L4U}1sV^s9|J9$#c0 zKctSoairOEio8m7r0Q23G=b~@SYZ(k1nVs@wKDF<1bNaonCCO)I{fI<5m0oLD$Z&` z{A|j4EIVt;HbtpdN*5i*T3~$KSR#y#KN6}3EQuMOsk*gtp;m4C+G-eHO@YTl>~ZYL zsA^d+&Km1zmNDV;6S`pej{K=Q@F%%s_9J|I()$7aGvR#;|Jl9^#TSx3-6+p#1O|J4 z4HS~iE`eLVrPXS*hVdlWh*HmBZMF?Z7t=aD%>Lu%8rBcN3WRFCisK})$2LUXDO9nL z`&c)uOYM%+*Eek_kEOobG7y~-LnPg><%yEdhO?lLwZauWrcTATL&2;CQLTkLTt2EN zH2%42nQLUO=&b{|!Rf<%T#}1bVLnDUe+Pn5QL0Z>BF*+;W4&{;SP!Z7>!5QlV905a zY(^ZRNU#+b35{#D(Fz5*G6SyVu~thmv|5C%_@Jh0muNu0Zw&m^(CVM?4(KNpEnv%U z<9o{5h2AEAJ>U?Mn|VZtdVsXDN)j8vA74A9mWJ)yyf}6mx8{duaZ?2+Hio11f$D6t z=D;6(IZWWAFQK&?$3L+45o1@`0L>&1VWY>KB@_|#Q_jMp&(OSvsG)XHYm|z$33e9m zc7}EH*p@6WPiB0%-e}DhYq6J;&Q2RAqVv%)IOMHZa;J->!_MezqwT{&))LOi0Sh&! zR&4uYQ_O#ikB<-YOf8hrcuY7`&Lpq4IgO6*G-jMjol5auSH(yi)=0dIMi>&~0q>3x zVYE+ZOT|>J6WU_c7VgNw@O8jy<*r7>t=0%wt$CWLeV&CvTh4bo7)`^HOOJSQn)eV3 z0%bxZeSql+#R+%Bg23MeLU}Ffm3YN!QS55rYEid_Mvd9o-pfzd^j3aiSo^3rU1(2n zB{NAyrnM&9GN6s#R?+OAfxwb|H(g44Ti7-@a_mbWM%->qtAq$ zavW8UJ_STk@nEESWbGDZ6uh?pxPe!XHW3;x3PwKyywQSDRJCP|=o|u^Ru+sZgR$HY0mq|02gnf%!BTXFr^R~AmV2QB=huanygHx zXK(0>oY?n8G({Ot&%UTHni5XWj#8!uA5leHNF$7ev!AH}^c_epWkp9+H2yhuYt;L( zb}uZ2iPe_z%GfQrdRkP*Zp20t-cJ07RmQx#4Z8q6&Od7FH2A?jqpiGRww3?W=Lc=BcVU2bxxI2p8tn~DOdemF5BU%?h zBqM{^eH9wqldHQ$p-c2L)<=E~+Jh}UUn9d-+}hb6%Y7HQ+PT;iCa%GFgZiy$;_$W} zO_`QAlZksc0)nn(MahYoIGUo2Chiq|(X=6~18Rdrb5~Ikv)DR0p0RpYW3r(8N!0v= zc48~8oir{{^V4+tS2ExdH9xJ@-VCio%@560TGl3NegXm3{H}vmIck2=P=^6R{+i$C z1V#yh?tW48Q+TEPqBXzZtC2N8XkYuO%*BqpSJf_Ntoti)MU3i{b2of}>M~bQZ-`2l zSiH^)i_+}=|ITJuW%&otliXF7xSqhen)#}-{Ln(79@xsFZPY%lsJV5Hb%yMmSWfqRMk2=dm_B1hMC#le~F2cDS z*s=R6o^n8HdT?NxOuE!^cGxyJdhAOrvX}d+wI~+6apYzIwU!-WuN+6UmaBoN9JLl} zw_X}u`6900`8VoOZBWwqs83ZPbQk&=g)T{qy2H^bDTtRH{|Lzk?xEB!gT+&&^|-DuNPCSY}pd}Oquh0b1@(? z=O-A|kjS5I7oj%V@KxP^XBll$p)lk8Glq&D_aaXf-HNSvx6-&oo~jAs4>I5qd8*dx zl^I%zJQc@FTHGu0RDl5V)E7dl9C@m=6rtJ8pQrvzV3Z)}?iYEg!Ykz$%~SXNXi02s zioVi>55PU{ES&a%Mq`V^ukR}QmA!t^k1jw7*G$NjC%YT|4cL$sCATP#*X+9yY*oc-_s ziUHd%J>aO9ow8f6x@8M52k4bXGt3X|z`li!JFvy~-#4;cIWa{f6DUZhy+|TXu?7L7GNRjyNHn z8#y&09@YQ72}`)q6NBG9G5BG`@90;blMHNB>JF%ZgEj$MLRtftAX7OtjwTUgl>-|hBZ z)ZEcqQB&aUqpCS#FUKf`lFFJVN0|>Z$JhgvIo=GS(-R{z#-E;^y@^YLW*J3Qlk-%Al&7zb=TCM%=7!!Ww}Pr?dv) zc3{c~yfgyDGy=Nw(dwIsnW%3@2~)~L)HfH_j9`KDg-ZDIQs8`huC5jZ&OgRR6W*Kg zA4`FA#~r-7pprb6HEFW9*T?cKcDg!_BMNo9z><{D~#|=l}>zBcj#HhozyI>S$>J3qAh@^bc&wER=j6v zT%yvc$>Sp#aEVH%*6M>9T8T<04xF@TSX4R%0<3ht1zP2(bV`#En&AAE&Q0fiVWirqS31KF+oB~+EK&8dawn`j3h%Sy{_qagj6%Hd)suKb6eYBx zAob#;UVKacHcsHkQbSArATDOIrW|ogmeS-0a0W~A<2U{0WccCR2?4P^XGG^5PXY=n z24gTbbFYGmkIf6`uVQeeg+e`Oi&PBaw2h|ONKrA6k+HONroB(E80-c@dBxyPykZpt zc6B^d48*$dV_q>3cn4oGPzFKhUqGyk`Gi6RsqrJ0fyUAXXajSQZoFlsVaQ*+~AbJfe2H%DD*(wGpoF!BY z!d8+S%UE0TB+B6?jJ01d!ZTG2@KxQRXBl@=voQDnPlk%N0HR_bdKO#po~3b#ih(AN zZ)dI>p;zG0gUD z@KCIi(VyW$8P-skjeRG?#_9;7kB)z_h1G>SU>xVJF2u+2JXRO>TPW0>PNcdJr!8xB zK}ONiDw@6_y}Hl>LV0zejaRI?z^)dqx^TTv$YXZ2oxs;eF~K}$D1;X|RAit>+XWyl zJpf5Rz^of-5eYu*$B7^s%C!qcuHeyjFA5PX%+YpXzw^Zn?+y1n2;)Ps(?a-L#k_9{ zX}47q=mL%6+j|_raP$v{==(d`?!6>s0I0Gj(z+Y$JhxIVBAyPejZ72E&>$o-t$11125y*eGaeQT@GwS zt9PN%6Gc1k3V2Db0-+* z!a3G5d+!g3lHHEXvcvLP|L53g?gmR*Qv&_`Okj>R-@^*{JF7iknbEj18E<@rg_Lz z1ZpJYoQHGlo`t%kZGM~{UOXIWVY-2Y@B-0f4Ls*U1PgPHT|@@a@N-S%IiD_!+*Nk$ zcSCOShoY({yx&1E(NHw!Id*?Ul0rK0$DwJK*z-LkiUj50!0q8w}|ILFSA!0~d9-LYer4;mP%YtD-Yo~h^9#r0+$&#~KNp^ylTig&lRzquhcCq|77w$lg^P!8mWqyFHTND4U!CjgFCAwe(eRnZ z$0{b!`45I~2NC8AhVPEFi1;6S6&BHC4Tf(E5d;s-(+6gqX01^yyRC7)$KN<1f@mzf zIwm2QZZ~f#UtJAZ$sY-;ZtyAyrq4+Dev%Y25V~*z83l9 zDk=6A33KvIBWkhRYA`=%Vb^mmy`K0X#P9U ziD%p1*cO#>N;mj}9DVH3zLU!;6~q(#IZ*&}5{ zlQmM_7$OLsy&#dY*7eaid01(ew5R$l<+ZO0naCd_s}At4L@?19c|A_=?REYAe19|i z#V2}4J#)T%O47J!O4~p1sU&G+%zP^}%@Q;3BT*#8%pABqW)9aAs2-l7acFT-uDChG z)ayjNV8fKe{!kM8HA&n@vXDU%_RbnsdZ8hE5s6H9FuD3mM>fzdv#(wFJ(7zIU1mBt zoo&6mpEd-Ibqe&_g}0Fu=ORGS?wtxy2cA_Wx*(oY{RQBR=2W`%lNr@V;XRpANdpF{ zzy{F~Su(22^c4(zSG_UkNTph;IBW@nD0P$_Wshrfa*XxXSXq`&3PNRE6!CDHWzjOO zo6C(-8@73_AHvM9=kS3~C;XM_s(Cuo@hgBK8U|Z*Q3jckAV{jdjI?E6v>>G}J5%Yf z3NkY4%F&CQD9fFl9uJgi<=eSY3B zbVH;?WRTj!P(+h83|$c-2%f*NVJO$Du80HG`q`7Ml7hl*{DCrVVlNw6hk|&Wg8I ziK?dF65wy(VMdC%;laicz#I)W;(9GZj-&9l3^}A~LdY>B#FHiDI2Vh!_0H_H+hVob zFbg!xw`s4bk@lcKEuhp@xH)!TIrD!DdCDI)tN!qQ zhG6;(n};tjC2q=(44aoh(=1{086=8?u$cqT6gFQ(!`DKiV;44a@=dd9vE(dlwn|hr z^*#$?9(dSHn;IT9-wGWYjhbUxEraGKqc(^=gQa4E)BU_q^(;a2NYcuOFp@E&=gjY` zk+(auu4rNO@nUx{irys{fd0%MMIXq;y@;YEMu?)Hf$c3(^v{1OqG)m<#4mFfgc0^( z(2v{^c3i*aF~Xj+P-wBsGQy72mNmkbcCYsc`z1gskFb9WuULf5t`;uBzOLPwoh`QJ za;}BYzNWh@=UfIu*GEJh`GA&_&!%=pE9&F@d8k3Z8Dy3(lK#_33o{Z+2$K*^)=2sd zA%X?kxNbj?Y75 zT&3w(T!$~(cN){4Tk_?&>an%bn{$8z-Ka%Ol%_XY^nCxqWbMLp@G0=K1mQhJ&0BJD zEuvl);BZ-;R9zrX!ZcrmBiWcC1qz!6x^$MyXh18`f>!;*jTT;;Q4Z$S(L6P(?VWxB_3T%s|)V1u6E+ z$NlyB*^&{;qY_cfViWu?{VY*@i{vwUo+vix|4dPY66w5NxQU)GieUTTW^hLhM)=bt zXoD&bW*fn4`Fvi4VKuA-<+(`&o9^x$1fPL^oI%-Kh(4d>wElBRMY}D z*W1U1MIRf!yWtmWQ_fYws*A%vC&#zJrideB#(9Bz!|KSC+C33-)g2?&_qp6-?{0JC z!J_&}qrAPt?8fg(pwC%I%h=$Tm{gZ^}g$RNN z7a~75BoM6!t}>p2d|)=+xTh)-e;D$Szb>fyz~_N`-{_;6p+vS?Yu5;0!t&J=)^aVy$L+{*V3z9J;9mspQ(P!KU2Q)BGY8koQs;D#0WLN71-WV^Lxc2vgW4>TKyO1%=j=oW>T-q z>BmiKmAFU^gFi6}YkE;cmdX_EknOhI*@o}(!XALLJxc_bTCvGjlL_DEA+lC)8Q1*G z@D;LHaY96)_>Mafg_*`i7+<-wtoZnZ+5Gv+G%mDINMssu+8ipD(=rX|AEc31{x0iU z-fMwWo@wmBE0$@ntA)!nZsbF=zh%7n_FYgI`^YoQk6v|<^Pgrs9fX%J&A2DhBI4!j zX$GR9R8vng_Jjx)X7>eM0MRUCSbxZq4E>ghg4!Vy`Ev}_0bUEi^qFHkOp-$87|(&G zS#pfWNE8V<1_z!g$9N45UyCV^U5>%YHw~x7RXIH(DUfCP(!_mr} z!IEluJ0KS;MMr}TXw)6Coli?Fg6#6!TjlMoe6bC*y8vbuE;`V~i0FS+CM!!gyOq1Z zi!Ke$bdNS+?IbAw3>UtLdQ?G9g2B*NbLLI+aYmd*g8g<{Tm%G8Trx$A-x7~fL z%cq)yWw4mE+3~x(D^K4YtUTn_-B!_Wv;fk|a?y8Txz}tHeynOW8a|Nf&QytXwZ z!qJ&VYqtBMU@h*3)P$MG*;#1V27sIR9k=GPJB1@vzfxf82+ROJ(E#<6-B{gno3LoM zP;>8fYu#OogW*cC7XBOxPH~S`p%?w4-)YO2hJvB8JKZ@1@YWtFw(8J}@CKZ0H4ArE zf;dVpvV`(FNf(33_P-s%P`JO0eptG2eQaDBG)yy4ayLM(hbguJc)qto(Ad|tmSYO4YKvw=7UJ%;AG?hbgx~3E~)`f-C#|vSU=P$ z9&(|_%kX=#Tki(zTW+b*Di_Lb6Ep|VR9VL^D>dpfz$WxawJa5)vMg9z?D&m>x)m&j zVG*n;cV?S~P$i*b*$tS7a$EIc4Mn$(-C@d((pR^dZMRcy6pqfKLY)jFuhE9XZMBw6 zOJL02TWwdl%C0MvtL@@6EU^ozTrP(S47=69OdAFV8+QP7jd-nqhNRs+&<##!_2O#1 z4aY&@*mUa%tqdYx)hZqdf9_fooPy&GG^c(D6tPyFt@_linyA1ks zZEL0!0grbS_=_ZX7`#PP*~73pw(9yb3(E{AYnp zCh)F3r&K8dJs@~K-awSB3!(MlP7_e91?v2&55nVS;1STCMyXn#0UZ!_;Z!sfU}Yn( zRcBFmaRYlc7?(1W1S$jjv6Nv1&w+3&uImGi(@OBx;etC`tkw#}avAL|qxuwtj1q6V zwHa>H9toDX^?SR!JJZ1mZcSkLf+kk$70uISc;04gYLB8~&V)f37(P{#=iL3UFM!SH?ehoFY4HmK}cN;R#7>^LGz-h-VuX zdbq73+f&HHjfdE0(jIW0Y8@a>fb#FY{&YjmmsAN)|u?}Al1EecCKfp7p! zYpKye^9R1S)Tq_C!h&;Mev`QW7$}xk0%h2>sQqwv515N)u?2<)RIUvpgd+|ut$w?* z5`6Al8*Gle8%79~p~^DcrP5mpA27)EY5W!})R?*r6t0B>&f&ws*^Cw}8h&kds%;1S zJ_Knpz8V)R5uhUVK_EGrMd&dm2C3|DN7xe{Red_SN)!wUkH08biemxH)KhlfdMCQw yF#1CK;C6QnA=vkDc(C9EBQi8IY>=<2)=RZc8I8&z9N)ubbcVujVBpx99{+#hbdBQx diff --git a/mddocs/doctrees/file/index.doctree b/mddocs/doctrees/file/index.doctree deleted file mode 100644 index af38298181836de2e90856cebd9d992ec43b784e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3893 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 diff --git a/mddocs/doctrees/file_df/file_df_reader/options.doctree b/mddocs/doctrees/file_df/file_df_reader/options.doctree deleted file mode 100644 index fa40620e70d8a13a2c0639e007ec20eefb2354cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16058 zcmds8TZ|l6TAs0|=jQR)EVD6254sqQ}A zRW;RBy>+o?AlVI<1dliZq!jS5@PN=RXc1b%a$Qhf04>tOA}$XsNbtlXS|LF2!0r3b zIaPJ4r@LnC7^Z);Ov-Zyhm+uN~E$ zpwYD2UDo*Er3=ey9=?j1%S|s~vj+aH#8%j4aj`_;v)HSL%xcFr1Ds+Z<~J_XFFsL! zsv)K~9KX%B>UP}oW>b4=J+)_mFzYZN2!t~t0O-RdfU~hR0k=Nxhrj)p2;lgv7002| zOk(hr_;}E1N8lNsYI#-^;UT`)e5)tFO}CSN@uO+AHbDh3=Ah-psePOuOhPB+Pu&xP zQa;LT(m>LN;1LrXWS3BLsgDV(0i>K+ivV9kh7<`b&Z# z3VvfzXp7$&@K>^^ZAVrLRuV?eEk^G@0o$AOSBz#5c*g6-8lX3>h(G6zrKQc= zFsRWv!9v>Yh}g~Y{UEjmM1fy=?eb$WSiEAK6$Cqb^}KO8gU*clR*S6|`4Hpm)vHie zAwBE7CT66CK)kIBgpQ_MNFxOwH>8BE)3;)UpVC(-n+iUDJGFFsL1h04kp1nX$wrIJ z%vR4^5qQ7__Cpr2@D>y5_M@2lrreKzc7&=)g(I;UrD*-_71BqDrQbDz&fd~CT#zoV zjtiBTle~goC*!KQu`_#1XhzZf3@u3hrO_l`Zl z8p!`umF3QO`WG1Z4T$Go%E%Hy)!BWTqa14?I%M(8T|zKqsayz#y(@&^U#k%IcO(4I zz~fs&&5q~v?I(UKPIlSFapllK;+2BL?&SE`Sk?rcJE zM4X{v2nNL9-O8;fBIsC&7n`>%9Oui%k3W!|yrZ5K#-B+7Iz#S|zJ8fj708x(IV_;q z?R=)Q5Lel^f)fXplm}hs^tI5)uUDdB59NjXHB2hm)=3#zXWY(y7TbKYa+{Hos&?8p zEvg6dhkD?MV-lauJkG#RL@`8t`%gf675HW$nzrI?iw@>UJHT1v_)nn}lo*G|v=s00 z$-A)E?lVM~Nj}|4JWm7w_IrHRjDr>d7)Po5pETgTw_z*9Q#FR5{VdMuB9H{sKS;h8 zEE(f6PHqK$tmnM!T5-0WA=JB})weZ=$6w9Phn*laR)E;bh8~0(mq$AvItAdwogE(a zX;(MuN*34ooW6r5f-?tzp*Yyf1EQ;&VZM4FVUhAV!>2q4(W~WY_`44f6qGIlpgIN` zpeF|aEg!=c`Gg1}q_3asZ`iqLYd|Kn45hI4NV0#&^O=5*aO* zqz#IK@U^b7c2EX2wI4{v5k^zQ!-o4|`XlmW?gV|!RPe|xhJ-}m!ww*dV{J{$_{nG6 zZPupjiyCBHs6SJ`AhpDu#xlA0tRDJnunYSe3S37nRaodxDh%w#u(Z{pdkyb-4S99o zQ?hqN5b3tu7ciz$sORF)@*~fRttJ9m;qe45FS#Gfx6jC0of4gpf%KfBqJ)c2sh^0j z1zCms5#-XoWJ^y#4v1rr133OLX@-ckHOD{wu`R3D_u!P3MJOH=1Pk(IhY?J7*hHZo zjh4HXVL2{7g+7($hyUrJ9OHke66S)T-`9w z&$3(0H=K^)#D)_Y0MMW^3dTtJjcq*5a>L@7SVxLY+IWN9RAfYUka&QSLEe{^jE#*J z9(&iUh;3|~o}GQ!W=6zfN)8n$snny#5W zP|<5S6+d;~l}!>XtB`1TE;qNAg+k-}pxlu5mb5>6J6I`le~!RLkoX**CGRhaf9-U2 zyufIkKXp$!uJLe}tihTh;T!l}-I|`Nv4=VGtC941Vbmq5Dzm9-^Zf#5#@<*2G?T*E;Q1^*pBZ#!Mv z!~fW%@)t_yl)mQEp5=EF^eCXd+~1CEbTp9Ce5*vtM+Qkb)e3s0ZVKeQA3lBBKyACl zP=psTiJ$cP+XxMe)2B1lsT`9dHF>ZaDV2A3CooQf3?<<^-`+YEnle-sGF7D=C$lAY zQ@XVizM8LOERH^MD~TLdGBffn36yU?a!*U>*yx4aT&uD}g8c(!EEnz1We@l%U5Ava z=nIQl>09}5t=@_=lh?khdaIbB8PX+^4=tszcjoZYz>I-#WN7BlrERKetXY`|1y%R) ze1q>iS^d473pn!Uax9Y}+LOYje{YV=u(Qvi{WbfKOJ~H45U46@gmACZGO`j{2*SLS z70Y{#N-wIYK2Tj4UW58{B~+Ewh3~^uM0tBAn7fr*jHoWi#Y<_eqPjpxYLc5&Z?M1W zg2*bC(l@eKQooe8RE0Lnke#%EbTe<#yVel6!x40Ju57Z65 zQoX@G8N5y0((`k&Jn{Y*JUYh8^tWuthU;%pHCfzSpZ>g>8p9>#PcaE$Zdqc6mzw^* za{Wq)k-O}T!ar7SF+yV0;=9XA^g{Ps_lGBs8jF_aSdlTTJ>hH~vFzF&%U&}eCg?Dp1DvY%bX}mSfr<>T_>~}%`mtfv4%DU$>(7NT_+I5p<$h$*&7H= z%uSl9p9YGQ=yUHM)=aq$X((u$cLp{etz%i9v$T-=dDsztF-valn}~V-l8`UuFQ2>@ zmPjP?6y+h2ca$8~a+3p(cO@ln5iFFHbiV|wL^4u=H&Bin1W(xXYe zTjZz9B42|c&qcm6ccFB=a*Whv?2rPks-%r_mRWuy)4@VBAFeuX5>^L5SZtIzqU@kv zCg7s1QP1R=f|SoAVPm1X(?T0RPT%DcAD_(X%fA8y_!J7;qWtFmD*Y6lr220^SeE_k zJABFyvaj>m<7Mhq-$r6;PQA>2FJOdfT!`{PNBkgrmW`v8Xg}1BVlv*McVk;6j!&9)usLy zTq~J;h5E0e+`;q7Ac^})oUYlgtnryH^Hmoyeu%p~F)F|58V*uHKsR=9Ib|#5xynd)ba zFR(2KRKz`pBr2ZP+N_y$vEIB2gfIg4WqNor19r3+p+xXGK4@m*1{qo!pmBUAi95?5 zUKX8DkCb1U2$F)(v~fGiV<9*|prT2NACZ*TR;h_qQhsXS&A<}wSPf=oUanifkj;Li z{9<4-KT0^D=OD5{A`dvFs0XOjHz?l5?*q8|#~(nHAB1h(@T0;rwxR1ZYkU&cr-Ozs zP{e6x0)dXLc`Jznvv|Z0&@HCJZKS@qhvA3SFmY8& zT!j<2uCNI%f6y54hN9j!SbhkXB=euE<9tDM&3Vl4LJ&QthgPCb9z6@c!TN;}A!N`} z-G{UTc2W)gU0LXY$5CAt8bo74%1_Wp(LIEbX3WrXfj$N5VP^g14_6rK#Op5GF_eKry!F(Ggr>s3aa}!JN#(0 z?Z?)Znb%I})*cl$`J8ClG*#E;9W-+mO`Im`OBVHw;L_fymW@8U78oz}4OA8+#AsYm zqPh&smAGc6%)kz$Curf60U1zs;TRbTSlNML;zB0&vZON7b;8XQBGYTKHZ*t}5ZH{N zsga*(VbrF{(1q@qXp$kj3-Ipn1$Fxkvg&oDP21rInSU$Al{G#stqIf@k_ZRxKsRDj z_JRrI76vcHin7vidoH2^wRT@tG=jig?7D4wJfdAX`FpzKNcTC(vmojFycHpMK)Z;1 zwkTcF4fh-HOzzj|a3sN3JuBG0<&BA20f`kkbd|E5;Jg^q3 z52rPCO+9}H*u9m$L{Xs?LXpwS6QNKjH_R=U{uq5!^U-z$9nPNS9ye<}EoDFQ{P*RL zBap*IB7f76;$@s6i?I71>6xqvDc99@*1(5&*M*0u3alyKQ=MjA46Qm2^O|y+<6JeF zO}wyn{RTNQd1oAkRbAk%(Xvdg#}DO%5o&YD@mpTfW>C{EnS1bp*KoN!MCzv0qz=w*xHHs7maH{NM-)lKpuJ?wSRr66hCS0g+uhsK zJ?icucV$Ud97`5~isita{BfM*j}Q0=q9jNFCs2+bu>sqGBRhy}AWr_|XKW_{5+jM7 ze6OmyySnGt*`-MHhX`zQy1I^6uipFW)vJ2-;@EFKdufFKW4pt)WqWHC-7q}gFvFNl z8h)eGGQBANWIX$;@$+$+O$GYZ(C-8dGiD>m(XedS2uv?t#>W}TAK7kLkrmt#cIshd zM@aO&nqf9=&!(SgebyNJtx83EF6^}1eh}3x-(L-%)9jYMVlFOfRur|v#cI`VtyDtG zHeDmE*nYLH8!Kk@p@n1fvK)T$lJj-9W0o}hTa5Hz#f-8le9w&BN?__nWSO9oS7dtC zW0gmKzVePLFJ84h!(6LaQOhmG*2HpbO`*eP(?kc+;e-hwdRLC%B<~ofTg~^8pM6g_ zajc|AQDE0Q5yq9qal37V7&A7}aP=@mB7To~dQ1G8G&=31!-#44JB+^v@%Jz~H4e%-t&be5tocq^(6kP>QA55euZ}`QIZ|MY_1WYuwD7jFU1^T zK>8&?`jYj?Qcsr4p(TDljqDdicIyIqz5{fQuraR33X=tu{e@%#f)v!jnh+r3U4b7^Wp@I0}PMk$G|qJ}=5?Cp48MGPP_sYv!6AM&XI$;`14LJ7rq>726Lg zPhPliX6f{zc0_NtP0u)T@~8$Vg7niIS6@G|1WwM*a&f0I+0Dm9_dGw+3(CV5K6w0r z2AiIE^28O?a0ZKKsBy!%WRC7h%>c$SydZV7(_*iz_3wGXyY>d z%4+z8T06qbU2LXpgYJg z8;39SABQsA3H6=_D>-d!7m>;(fxnyT5LutGK1;*&`Ha=p=h9A2@lFBRsar&TH7z50SE>ut%ic6P)XK!Pe7v2;M6Z1+s7RorV z(K#U{BSI8u@~IVtD#-)5nN#5Ad;P_Sje6a1^CslvJA*8OMBv{*#=nIoe_JeCV6UIf zX0dd#*z{BUoVlYQMLdvVD6W?5#vK4o3GxRtJ)-&|Mv+uSq>vb$OxH?nKwE5Ra4noxL! z4Rwu7!f$k&TuO~sb#kM?NrcH9&Kff0{JMjcVw|%t*?7x0IHJGvtqAfijP!Zr8Sm0 zC#HxAD}mm&RNLi4Pr9yuHPqJqju!bE_kL&>k#$u&tw;J<;+v+28Z9eBTKVg8y@~~2 zWYIz+Y?}?cX`2Smqb#UPS8e1-Ss!-!IJvZyt!3ukOd>Mt%lT+QO0B)pupz(a9m?-9 z>uzxV_P&cet8DGJX018v=bn$Pd2-}ojj@e8!WJARTcKHT4Ee~F*3YkHJz*`P?2=Vo zvKCVM2CXk463$1ch3YM|hJ^3W`Ec58c*aT)5Z4gemn|lxPYRAR{#lXBOl)PzqCH)@ zkf?MhKx>()Nf{FTVPTB(YkGC9NSyow%Dv!xnKRV+4E^`WIoLB<7wW#Tt`K@@a@)wr z++1c`&&_Fz+9I%G@p3O;k#=0*#HKCplb5+upVp>|W@hbHw2P*xEy69jc$t)yT&daD z@UkWymz7d!Zcf;%1yZ|xQ+9&g_dE9B66uf1w&exSjD!TWjI z4gj`(ur6>Wg(f)6fj~a5N}4%JH`JAT;^2}DC!TD=FLv^B;#$*PPS8_+tL1wfAi0M{ zxX(a`@=r+O6#2W7ZJypDPtM}~g-kWLy<3W8u`k>tv3d(cN+S6b+W|L4t`p6h(}|RT zaR`v_t*CsQ$&--6MW!9flUGF4B^h-QSNF2 zIFo>&Z;Cnro!nZW88%RRwZ9j-h`x=mY4XZKOlxbv1GUr#ZU{1oW~bPMOItU(tF-9+ zH}#C>(`DA1%_v~4>3{8PD;xU06n@UXxg$KnE7z}g5P)%TtYCpnj z&vQ=IQ=;i1>pY1L8m5N*`iB+L3djC&fmc6DdutXz&2$=fs^DK!?}q{F~Z? z+i4!`HeGpd@^J9}Z0_sMzco`=wDJ>$9_Ll+qkxZoq-8VSZxk4k*8_45g&V@-PyUh zA=Y0TuDv+F%6atKU8q{-$)QcpJeXN_Dnu!`^X8_wEpg{NoW*%toYK@`8_dc)?#zId z-{N)Vac5um!Zi`2UY{}<`eA{X{yM8jI#D1ceF!PnHX6Z%(v34_bdPM$GjTB8dD-6$ zCjNvoad4@;>{BVh`oRO1mwg(rUb6q{+}urb7!2(!C@7J0^Jwg)pF5T26A_t^OjGpo z@TMd|>g9bXR9qud+E(>)FNXPUUT0n}4-&$kK1(ya8Au;c=E{-1&Mgz9UC^UkViYtt z>pSAjI61rE-PBjPtmx5{!rmtfT*<4H9sS)G1Kfe|%^H$OGv< zFk*!>VsNQEkWwk3E1LmQV!8m*!KMBN=5NMCT4?<>QU)z%sb7CdrG%$_qwS3^<{B7B zsn1gi-})NNhR=#dlC22ZRiOj5@V<@*oqAxaM@ZS^nKixD#x_?nmRUkJc)zo$!TW8> zoo$6=Tsp*wn1Ty3fIwdwEtRhF39oPzu3Vx(kc`X|NnJc}E79kbVB2;j{j@AA5$u z5Lx*9s3XDdOSPW`Xf(nrz=CegzVT>+mWPE80w*ajCsb6dNJ~%&LpU6mZ5OcsH5_jN z0M#-S0$8Om^fs%lK1lu6u{s5|2cfzf0h{x$(1dHw5Afu8=KKi1dSTcCFia@VCNM0Y z6=4B9dUGBTHevqgH1?(WMjaeY^?B1Zc|fN!_4hCkCmPuB&!44Y_Z&K;Arz-!B3#hW z>g$@m}i>Mtsl-N)M^Q62xGX(F5zNZKtRRfY;yCnieDnZ9ff<^2E>4fFl7bck+ zVrF&NY1C1k)rLMems=}ph={O!1My`~414e{S6*~LjWJ6PEG1Ks^=!UlW$VL90y~m} zE9!{RT+Ghp%xd!|H41{_Qc<@4EG@X5Gfwyc`0s2gvGK9mE)37nJhw~$Jerz#Tslgnu(Ip&x~;|rk(G(Kh^W7zw;alVG%Y$=Ws;8 zV6ENp8d0Y!LTyyWq9Sx-QEBxP16yUI?K}jLHh-LoEF?|8nzE&LESmGX16!~@)fK{k zV6>$2jBQitIZn>K+@6@!SnTa)nKHh3 zhAgk()Z~~TFP+wI$RX!FvK7+G%^h-1=-bE(DAs%XKo`NA9{dH&@pqTpr@B-~?+b1Yt$Mv??}Eg_?lTK!@e;>RX{Q=*J3!?&S7i zr`4vDIdw9jMyclULN$Ze+H$9d+NPZzHl6ELhG|9OG9)NoT|pQk#@(4EQgFKg7=DyD zFqoD(%3f7VWy9oru1igwFLfoAo!6AK-05=Z+~|Ga{Dai;&}RhXf%8i_a=||5uhhoh zGdE}8BrQ&sx7WGDuM$4vRt%hlZc>0oC6ylT*5h76E&Zw-P%eo(U7W8fl?$B8pNcv& zbbA}NRoLMi7d3OCE9a$B{RHXB+jwi6;;NLy2!%)z)iX$xrVh_w;ge?pr0~(+Mw{1} z-*dcwgHcITY{Pt+j=WkRbFj3_?JS~A3jGjUS-T5kmr&W2o>BFCo1!YOxpZ#t)0}yO z`y#ilB+IFkVEN#I`Z^>!({~!E)(~yUtt(}C?IB9Ztt-;mjV7%x=E$P0s}G>mCbq5; z)JkS==hKR7akwq3H)M-R6@%F?x2&>u6AZl*BBf4B>^8BpWHc$jL)ew+N~!0!Opg9} zQzYf(=q@lZp$;Ueu2wc9c0p)yj|F++7VJtEgrJ@ufQ~^fECeP=zkIhJo-YQtV+&`| z;fwH8&%BhVd-AlNhNYO8hi{=XX=HqGFXR#@aV90q%hW~NNQ|E+G0rUAM3V9ABq$T;NCTcGPy6#lzagZLz=y6~A>wf&Y=4kwRh(8bW=L7utAb&nYPg;>k(s2(|7u{vH ztBK9W^kS{pI&UUsR{BlEeFojvX_@NHUcZEhQ+cPBxM_;rg?I;qY1Jt5fnN}3;PxL8 ztonTHEP_1ibja@LnG3gfAz1P}G7OTw^b$1d0^1)2x)-|KK|@y}t>7{pah2Oxgqwxj z_}mC9C3#E(uN`8}D4S_?0=g)zCNET5V$(?qKmEVNCZ#0c#=pJr=ZYKruzyQpgX8<- zPwTixfp1@dCTc3?rN*z{}3S_vzv`goCZcY_y7V z`GN!rOI}5Wl6>t1F5Y%gs4rbLM_)n8n2=YtuZe1CVWE6{1sCvv_3%K~)@UCW5hpWr z`%fFURdL64hzjo!DbnK|)+i#k#sg2#8MG%0$oucW7VNUn9fYoV#RNOIv66+$?vmFr zwilN|fs44ssS{?YV}@Dptf0Obig%%~Bh+i^Eu>6gI2tB(iRiOE@C-XR4WELO5K_~L zn)C0R=ONbj=0DcVAn=2lg-h~W9FKq)pl^u2X1fLFm0HGlEipS-_)@6CS%o;V8BuRW zLsI(Q{D(qewukxv*$XYX5rxa!lC4^B+@SqhTmi{$L)fAp7`W(>La@*bU8%Oj#(_Xh zM3RFewvh+~0>EeVPUP3JB({w%wcBN2U!j(YBpAyRY)E6^=`K~co2PJLr_s-n+M(Go z{2DHuLasf~JRcXCsU^9TKxePmp)EvqH+IuPx|NJBIule*B&q_nn?`4FgC*`$voXm* zYGgKkJsQ6e@peKOG=i(GC=bSlYBMn8J8@TL`g(4Z?InGKX&p&kR zQMx2BXf_JfQ%Y9-!%6kDXd;naHG_(6Mor!Z0HI13VxqArY#>34aDirgZjv2@OtxyN za;7LKZyUSD_Jr#=;ImdsH}UE6MhZ`4(_ssN{ifn?m>c(7q1 z(7Azu7y1S+a~7iXYNw4UkS-kM*A7Y;xCZI*8@AVk3`ktqM?e9TEo9^Oa-uC=C?nk} zxf(-cT6NQa2Cw5Jlx3Q@ErA_qAlGWmMDUwi!`-NayPWF1GN>4`>NW+YU1Qr#?@EkQ zR%}wB3Dg&oh)J9u9H|LQk#}xy@orQ?b418bhiqT&6Gea1x2{V^_lMLMYZwC`=0?TMskcZGeF+HUzq~J+D(P=e>q>`sXQd)K2O@)ak?k^;!xuY&f2*V6_oq4OtO%sPnyD1D$SoeoD zJJc{6YT%u;Ix5m<1snD6F{*=1`qZVg;JiTsFyLek8Fd`Vh`Qb_yvU7yX^K*Kj)qV9#G6l`a`HMMyLiOTo(j1BhaT+W-In diff --git a/mddocs/doctrees/file_df/file_df_writer/index.doctree b/mddocs/doctrees/file_df/file_df_writer/index.doctree deleted file mode 100644 index aaa1736708c7efc64bd95ff444e591cdeec5e972..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4147 zcmc&%`)?e#5!UNYcPHueu$%{K8waCK)Bw7(okv?CXpq)0S_nDi1*g9l3*Ij8NNabw zA-VPmje#~r3Jc(Gp^M~S>R;85+{Zm)1!#YXfP;32v%}%c_stCd-gtSmx8ncIuF4os zr+pGdsfehurWJ`WPiU&`_x3mcu+Qz0X-o1na~9%{7D~4I(<`DR+;xJ=QS)?)Q7&s65Mr)BzLXTz%8y37OE7lOEGLQzwG~PbPiE zIE^FK=VCA-(S#1}+`oHVeuqc@X`|VQXTgtC6Q(netJVR9o+5T zd$<4oz<)mAX+)=erjxj1+2&)*+Bh&BQydTnu7UukcNQG3bQ|99dA)Cc`IC6(OowPK z`6$=OtIOj$j}&rdHp7@G1tFdrDM^Z_R+MM6mk|li5e2WaPGW7@p;^x*x90u-D?!$D zLXt*2B84|*^&`vPQMuXFWa759>^0M@P`3S{R659{h{fa&)@;N)rS^2qG&6#(Y%+ppZ`kW^bay$&0&yX@ZS z8uZsqJE#f7*7Fh2)GS~*50N*3|6t5xdK`_9pGmH%JeES}lLA#&9;|_b@yMEMulUyc z{73lFaDK3l@f(;A0rG?n6p%pVKquM2M`GZ}FmOhKXk7gd3P(ypAOY69zo}@r#9!8Q zL^;CQ8X$VS$dZS!`ru*jD}SnrB^zJTsjDbvXL0#E>q`tg{%hg!klh=;GUjD&UBDkf z{fk1KeToBbct2aANHUS9(OjSlN7)KcCPFRgyr<>bRk3FG&5fT65NHG>O76*XG&Fav zP%gz~aP7bB(A>I0Rb(#C3Odwm^E8ZeS3Tom2lhAioSIfbrV-6Fv!|=zG4WsgB&ud( z5{r?&G)=~%2!;C6?3DIuK`YIZ5sF-7PghLGr%{PCzJP8A^?r6X|61+D{^=5s0P!yH zO(f#Ua<{cn0L5xuG+M&V(e3Ms3l2N zOk7@FIh#nTidx*zLX)_NeW~M9k-PY;nr@g&SLuSXA`Z=VCGoXzXj+PD&C>~5_I}-h z$5N!4f&!-bh@dU1nhT~Wa-HSc4%wrjX-{ZMCDB3xj%{C)HAynOY`HQ7R{^}KHLC+_ zwh9*l?0XJ{PWhV47;ihsb>Mo1TZJnPhnov)4#qH(3Qgx0U!a2`?!a9z9SfP*7pAMg zpez7$-Nd0_@K=R|#?(uKXI!(O>Q~sW{Ge=L2#chCh=}P^ig2!(4nwyJ%GZY3r&Eq7 zYNB&BlQtqc$|u;a`;5pGQG^D@ganc{QWjEoB5avOr7z8%I{<*E z7~&J-K&TNhNhdjGEQCA4`x@$kxh82SWP~v-18lI3t7k*g3`IJ|84(U1RcK&M(>lGqvC^%Bnpm-?9~Q#6l6Fijkq_CPzKXfXe!hX~mE zlq+7~bvKB(A|v#!+RM#~C*a&R4uknK^`wK*ol?#BTR+dd=Ibv{6_uWtGvIcwyeC<_?HeKfBOOqcaV0pk-E;|K6=#KPfzTO|ae`qw!q ztq`bd{=Ik&ViA&}OPQM?Bk%`_FUW>e`Vivl^E|^A?z-TcO<4obnIPfm7#Jvv!hx$2 zD6vaweyYY^q+p_(3C}GcgDw_V|UHRvugSfGD%+77N)Cn$a2ifxXO4+e!6BhGV#)_s19Phs&JZa?fX&gnWQ}qZ#KjNh$OK~x`|0Xq^ZP#|0n*{FSD>PI+YxzbhQHn8 zs)yFsL(GdyWYmYVZq8-*pM0>ovZ8+G?t=FJ^&b8$4_EV(lxdV*Zq;l!Xn?GL x89qF7ou|00qZpPqr-m~-DD0W7IzS%kTjgX3YE9gYuoJnKP*zVjW#^;*e*xt0PKf{j diff --git a/mddocs/doctrees/file_df/file_df_writer/options.doctree b/mddocs/doctrees/file_df/file_df_writer/options.doctree deleted file mode 100644 index 52f8599ec6c761d4f9b82607ea1a076d8f4178f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 72939 zcmeHwdypK*c^@A4#^Fg2e1YTuq=>`mZjXQjN<0A|K?0N*kO%N3f`Xvkx!t*&8SL&X zcV+>cOi`3WNklji9dji|v}~muTVbRqWhb)BkrY<`Nc6B2IhK^fNkyqjTyiB&T((M) zY{{aP-`72l?%CPi*~c9y6(|kL{#LhUTGdNAy;Q0? zCBtp{VP4gJuS1KXX_ZTLquM-&ms_a4XO-Pt zFu*l#W6||24-cJczGN&}Rf~R&#xsZgkL7aOQ*NVHbLw8+be!| zx}ldm(*T@oAfq~Wd+xxSa);(v_c^OtGA`vzuTq|9nxp5M<~R^GmJA>WghL_#=*<$q zVcTH_x7h9%uRJCUIDbO-yt=j6@W5B%<3_9Gf@l6{v8=l;9^&`ms$Sv0jg=a;_>V=s zcpg+>Z8qv>gmfgZPVQ4kAzY- zdARO+`C8p6JIhC#{!rPf8qJd%{E?atwg8wNjYZwu4W!JQz%dQ~@5TSO;{V%#)CeGF zu)ZaPwM$88Hq4y4%{*vMrp~id52vDb6Xu+GyLsT`2DIPkkLM!=GB-9BAx$G(EJYEy z0rH<;vdYG6X=(Oi-SUk3tWzT`ap$>UwZ&~1xLKv9zbm~vgZv&qGfdE!$MFB0e-xr# zG3H#zgI=HWDz!OAk2z9_IT|NlS_=Qob8zMYaB^Tolc6!{8pVVjn+=H_XjIRx0&`NK?! z1qyS>+DphNq^NE&!JmTIS=D7g@85kNXmR|$#~DKc__ZmfEt*x;xDXM&askl-V&wv2 z?T2=BY?Moxq-l;Ucuk!UG-CugJ%HX2EOW61yD7ZcLT~b}1r_odb%_C=OPObCdPcLB z^2R00_1t;w>8!w>pbqyKrhdV4>e>;F4=p{-9Xoyc#KQ3z4Jry@fGFRyf*HJ(8i<>F z0R7LpUj3Z*l!kxsa{+T>B>HDYo1Q*@5w_di&!mMA+sY8rt5wI-6H@0NJbU;S57IQR z?PI*zcXUQO91Y`~P4u1q3{!nAlXfK`Pmn!9Xh6lWDt!}tR{eru9A z>GmX_@;5;pW3{021PTRdJysQ5O) z$fnpzr3H;B!6cV#dp;?H6X^MS~f{SCDhL`8T2Ux^?j@K9_K5=4&K zqY4=>e-lP^YevypvWm^i{x}SO#VIu?*4fk1Hk0pJndkm;0cO(P$5>_Gf`5`~Vbg-L zh5VsFb!6;)YRa`|?0<#pB2#fLnRuvieA>Q*>@7gs}@^ciT>S*(AmoT80br?UB}h|Nz4C~V%K z3Oxpb!VStP)F@<;q5bOdvnkwa$WUkQ9#Mmy>J05JxO-AL{y@0FK{eo_)XqnyQl9|n@5h>n3-fS=4q(do30&Bnsq8xIWx`fr#H*4yWN z`ZbQT{;d*xGo1tf2A!mp*a%Pgg~mMmGoI#~%4yn5DW54bEz}MD>nezrtvto+`6w%a zKM^WXWNdyNVR4X=3v)-ph!=I*oWRx#mhspEp&Sq5Atch9QX0(*NEWA15k1aLI zWtJK+U-2jMo>L?MJ-2E9kSO=chY)tLr$HNn_G#=@unZTV{smg8gC*;Hp1>K@%c$$e|d#D)vGY?58|8Pc_WF7E;{U)w4<3peP8Ma{_CVGAO8h zVndQJT1Pybvk%ZeY_HIMHL!0?1-D)>>Mk}-U8zK8*MNFlq~ck7VzNAUvgD--9+v zp`P^WdetrKp1z2@70X62S{}5IM$?C6Rc{Lq@dVpsKt&FhKN|dr%o`&slRtu-V-^lQ z1UVp%fm=x6)EkR+q|}52&e(}ddZkuI!X&T=@q?IPW;ARX!9-mq3N2Asw&AR#pV|Iyh!?b73swa*X@-q|01*3`` zM$Pgx%hdp&Mx`WZ!{ygA@ig2YXG!H8_Wrny*T_w|nrk|ZGN3dd@6*#-p-{x*^1Zri z6bgGLCLT2n%{4sQwTe*+wqLc9K^a^Z`&j{s0c!()BCE=O|M@0WcR1s=A@8muW&X-;)nw5B&ItwJa**R{1A!i1mBEHq?uU#8X(2jTIBoqonw(cjry z(K=%CX318CFpfv|OMvN%UBMKDe}Pm1rai(?NqTk@gWLswW})F8^!_>#g>=-EvB_>mhr!rr+OfiEmG#f zA1mwCp#I70Cx>%tN>!xhZZ1^Xmk+iAy zOZ`9-(rSpbs6rK`b9ePLgouq;L>FnZITzH?y;sI^%6xzLz`s#ky$32P4$O+jJ{UC@ z3ed1tE0?+|Kw~`w1Afa8*G>2)LH|i1dF1Xt(0?_u=&gBr z^=mOd9m^PFvkRuN~)=XF*2tqV8(NpfOue8TC4Z(sMMdevrju!MH5G10H-z-m$$w0eZAD zP_>Dv`+X&KgS_}?fPLG{_+Lb$1eC|&7%1h<9q1^8mhlKY^pW!w@VpGQd18Ipva3*Yq+Som}BI(Vfxi zy5$CN7)@ki{NScJ3Vv|x6`Y&z-9~Ag3twAT-z{j!%H${1m z3l{AphPkP!LIFOiqI-qH4A>E+m-34%^u+Lr`vZ7s1`!bE{2&sM-=yLJRI|sYzwi<# zPlKv}r=_83YY2NdGWd*U*s@|Y1dQYe;*f+7hG)@0Xc7^Xj+8ZUOrdTF;1b9nGwoV~ z2=ntXMd=OwcOndcxC|i}0-@M(t13KwmC>y3ASl3QQ#2Qcgh@IN5x9_`u>ZB#9*wI{m~s;5(hWhZk9yRk%P-Xzj5 zh*TA*5u3)rf|&{;pj&iEmO>Dj?nZ{B1z;}cIN+|a$^;@aGqXgUc!H=u?6YjYsHZEK zu)(58#yo~1jT51lnqdp62x|$?$mB{xF^|q&IDn?eK)oFjhX~OyuErJ-(X4Xt!Xzbx zFZLEQWa3Fj_)3rVUMOlo6M;Mj1zLOVc=FO95#3I1Iv=Ly0&qy2LgHaxm`wN&@=25A6`2yzWB_a^xi3 z8zH*+ZhYwJ(9#J8u&t6(+=cx{noH7C?t}vkB+pA8CE;6s9vo|%RrkY21uiX)EMiwe zmW@-2#jKzmUE2}*KcW;ZGoz4%$TmIAJG4WMw3N;E@XdPUGa|&I~8u?t8?iFD! zmu0``5}P3uc#HWGe_JwH5+=%8U&M*2d%8+Y-A%3M!qn;}qp56)j_w7@GwBr%vNSDz`RrO3wWzm>~XcSp5`yu=z z({f5ql+SiW!ceHY>=jG=2fCs_P^}dtIX45Xr#V{EF3rL2*&68aIe&-L_kTX~aaYVe zzmR}d;3-?RK@#@%`RjOhEwn9LBTB1#ohEQ2 zi$wh4VyEskFZCi}Eieq!$R29x_3jM4-qq6Uan{Q|i+@7J@P9{@Uv94;1IjK-BAAvE zPsuH3vy`80kJl<=)iX{BQ+r)o3^)bEs#^Qo#B6@X>gv?}0zuRM6SeKu6n~S+j#^G- zQ@kD0{SDTuvnf8esyqp8(JIdpoBIFT6@_I}zXN3Z-<)icsee-sa>Wpz!p@@F*8eyG zu59b~w%Ga>r3?vtQ^MFM8VWMFVWbBcBsg2GeG+=L1(u1uGa|OSmu&5cMO?Dgz2=Fo zk&hsK_!`4Ntwpl6r(R#q(Cb|-bER1OGjpTs*2UY#(#BloZMn?JE-`Nt?SQeRPn2O> z{S7%02sZcNw&RY|ninhML2kx@xNW!MqO34qeXr3*T!$1*w90QK^q@B3veJ2QTW~5a ztj6|Rx3n8j;;;i@vv0k1rq`gh-8x8P#)M%TdPgaj)!K6FV#93!AV%o)mMSD*S8cOx z0O$>3t1U9A60M|-HXdwuNX(r1DdL$_$C}ZtW^}3|+1j|&}7JAZKxdC&DC+4nH}Z;z>84n#a*tVvm5|w{kzJ@WJ*+Ob(D!5*3LAa$g>i1k|9$u z^RZr%21n3|B@HIx<*ty3px0tDOueF)GhDl{Houb%-#NqehD?D#m`KUkI?gz~JAsBe z#ULj^=zRMVoJ!aZ3pRBP@viXjn9Q(0cm4wfS+eSntUQbi54Rl7LteL9pMjzA$~J4gj$n> zjZ&JI8MzHJ;wGLNoNdWk_7JGa$jGC_T>$O(`T}h9ZN;l2fzNF_09U#2RC3GHQfe|i1OE-t*uom zH-i|rpg#zWJ6D4pBcc4we{QX;LMMyBRYuy$er>@_6w*8k1QLLxTQ@xK<==CE!5QSSLdm%BAY?rzT$DYSXs`6xZg=%^@_HlGw^>FMqSQ{cMv*?>B91Bi3E6A?4 zoN`D;2m^>?{wOh|tghnfhn#c}#}FK?aZD0AwXDl=%p1X?FR@Y8 z0LXF7{)n;ax#T#8#DVLTx|bZskTyP*0YW*BpXjD9yuE7|+~;m=2dmO~ zAGX!?hOtY(d#@c|WdG1DvTq1P2S$Ztma@6|k#=Bsq*b2oZdU&8Y@Y4&!c*1Xn$m9LABj zb1hf*lEXN%4VyARD2H*>YcxYIIgF!TDPf%d%c-s?+TIv+tyh*RuT0^P|4VBLcxJE@ z!NvJC1Tu;)-q^*Yg0B5KWJ)ymy@d=f(^w|I02`qc{KX-ykcQ7i+ zLYeehvj3r8oVIF?$o_{o@@fW8BKsfe^`i{EWdB3G()`aS`?gg5oIr?*Gol3uZDomP z53)IwU%P@>WW*uiA>MoCAnw;q*a`LO%}sbTirP(FNK7PaWH)Z2-a(@k3x{XWb=BR( z+n}99R^}$&mSy1(9tBO@H7odvB%9QhN%j>4&k}>Dqp#STQT~zg%F;Bp;-AF)y;||Z zfd3N=epzPJR-AfqtktxVtvJ!Dm|-s2ic_zAhF-E2r(WEOw};Rl;rHS-{q@uA!wQc{ z_lMkoA?Un~Dy{90ST4RtZ~@;v-&QhQRQcrbCBYi5Jgj<6e=NMXAKxN$@TC%b+xjxD z4qT}jP5VCrC4b!1-8{b2h_6*Y=MPypcGO((@wE@*0={yJU$;jGk@CedKGTd(MxSf? zqi$)ovN-#urtWWMoyC`vi%w}pctDAF8(743W!C{Oe$=TwX{eX%xaok@ogJt_y=0ct z>99L8REWter(Op$^paUlz0&S`p&HXn*F@v~rO3Ok#wF1mFE0wO$ZWYHy5|d`vDuis z9(r(>Lub4i8Zd-Y8?X&98o56Sjn=B@yKcYSB6R%y=~Hhw#i9{ORg?(_VdPkDj!}j}FR< zX7ZsKJ*X&XZTx3!-3NPmnCrI)0QX7*`s`B-cArkE6~e*z%_|1rKzwFj)it4MSADXn z-wKY<)6lK_y7N!$$E=?g?W zGb>$HJUS8O^lnmj7xGl&)T!G$i-*xkweQnfn0FNG==H2uN=hqn$n@Q->E*+1kky?e z&ZFJj6@BI1_`5)~-JEFR=>os9rh!V7uvXu7JexpNE_{1K%U#E5d5MrYdlJ_jx3(1| z_)ubEb9$z*$P=P$AebE!eSB`aG%nmxkT-SGK~l<&I<)4soV z6uS}K`#>0-5k^hJJb@D(mp&c|ynP-k)R1xEomhjqw(ApJ(IB|C>*Ern;P~s;c6Ik< zVWxD6$gBOWT)}Q>`v$Sk|NXAmDewKAgv5Og{lU)G@9fAX?`PoJuCFF2AqNGxo3Qh> zU4N7qLan}md#8Ds36!txQiId=wOxPG7icNhb|vBK#=t!Q!TAzHTTLN(2abAiaZ}GF z@4%6e{!WIu+-+9a=8dnNWXN)grO7hU_!k42p|y?9&$u}Rf+kd(IR(MfzOF{msoZJY!_(xa0|BB5r7>?9rrJHEoERzoQ} z3F^g}sGdu962$r!GR!4A3F`HHhF-Fhpk66X;>_#!tLW}bP}tGuW@x4*(3#ioSLu7# znFqS^XQY2V?^oF}nJ~va`SWXO6tzFUJ~5GO{JQbyyD(CZ3%*;?b=Cd(qcDU-R#hQ- z>ozX+nqa+BymVq4x6hkV0#5ZgN}QCv3t)>bU1n|4`^w(%F`#{zqy75*Dq6{M^~JIg z*R^Xd-7{rXmvhnDd)4k&c__g~IiuXug}zTDhEz*NkYOe`SX|b{{VGY=)j^Uhf=@sM zA7pc?!ISS-c{~za^;~ioN8--4T-{3!30#WcXBKB3V7V z2^oH)gBmDO0Kbi{s~$4E8%jrHWoEC}`&E*BQCk=}M?e64i~*1meaL&GneI#pIGd?$ z97y~jAP^->X1F?#I0p>A&M;8-lI=0|;&`j6CEH`7){7bDlI=0|dLctE*&b7`G<*Cf zamrE1y>?@dqqg1EoSt606B*eI{QtNzlvryzBr?N4n>Vqn+bnB%Z@y!KMp65Y$;3pm zQRv2ZjA5i6mz%rLb=7@G9tMKQ%6v!vH?br+q_&H)7a>r7!Yo`zFJiBq`b6>0&a^p& zt-nJFnHk9b<&ngovLGgXTCjgfkT8DU%QwBM_^vq)sgt2D)(zc5w9QVE@K2NlyH;2w zvLH`esymf9+{-C2R5MpzCKbTkgA6V;3bKQxUR)&9bIA^t1nEZ^T&3(_sn-uO^pYJc z^-6QFuU{a8FDVxKxUys71f6sysjNjIL)s-c3CH#`e1XiRgs%7I-F}fqQG2&%6BEfA z*^PI5rh`T+Rz@E|*H!m!i_lIYE3<|Jx#PEhLqFtCIH0+hh0*F$26e#{Lcf?d{5#`+*EU-PE2KL;{(~ccB+Q zNT%*3GntN`{VkDnQ7uq1ld0E_GxU;~Ouf>sjMMMl; zc3&ShDmdz-v86H#l)|^XWQhe;S`cQ<;KGmaTokp{%VnV>(rDAuISe^HeN222IXT?z zQ8(BfDm9~&o1UfqldUJCSYRM9WUHUJ2Gaxe{T>Zk0gs z#DsK2bo-XbYhN245lu>>5Q$$!&AUAux)q%|>6z@V3~073`Q2v`nlL zM{ddB#r7`a*RYW%DhF)@vM7?;7)E+`rAUuiw4X z(1G>nuyt8hnZFfP=7v3cv|8OM8n~~tq%E#!)kdYZf=|F|d-jCpUfG7nF___dfXh`Z z1I^zuKSf61ReqR0)ZrM+XH=_i>iIVtfECuZf%cno3+91^WTz>ALv5w$kL#XSw{X#n z(e$?fAsx%YVt&ao%B7|uraN%mf>Y70YSSLuN*OI@(dKu)PSBrYxF@zK{#|uut!l}* zgu7u!t4{dqmhkZ`g8upnUQPxt!@swq1!d~-w;MIuT6Mx_VLKX$=5q~?lBdnfK-^qm zYX#h@P6PN%(8Sx(i>-G_I_?|Ym*JjI`B5q0-B{d;;EA}4~;G)ES^ zrcSt;;4s5eSRR@0m2sUi-yc5kwG{9YV{0aVXIvp~=6h zi_9a6d;}DsX_AsIwhNTYd=_o@M-%wZ&zVlem~+7{y*}qvYIBTN!B1lP9Q}xyJ{K~* zwlWVR;6iYa0)eL+1dk7#To1$ig@{0tvIwY?-hU^;ifg@0wkh+==2s-W4k_|^i6Zv> zGz?7KB+|QqCeOC!<1EDzRAxN+d46;Pd)(0{H)234R75br#ew zDyL8js;p~IMSOlrK;aLR;|~OdKUPkmMj=(#5<>K+>hXJ3wE+lGXZHSB4ZG3~Wnu5w zHrchx7Jne@-KCtO2lfsvz)=b!daHVZjQss-iz#K(^k*g7uRB85DF+kju9G9o!=sL# z>mD^V(k137I$@zNOU#kTARG~>eOfubQeyZlorV5A$|>4NOfWb+Z9+Wgg%~{!h9|qU zDZOmzuC^}nJKQM)s|+7dov@rtH6jqqo0@A5zF> z|A74eo{phrlf@S1EB0Se`_2mh`>$!bzXzoJt5Q}AO-$RiVh0jH&+^AGt%{84y8S8AyQDMD}Tbg*$R>UZT z+c~6^NKOg`tzzitB)+5~u;83fDLxf+M{n~m)%oaWcCTaq(fSd`j1#<L zgkCgb1`_vlaJ^hI>e@xq!n>e+&&3K0CpBm_=JM)3&os0!P7dTpyP%gF#>@muav}+b zwOy|0UYLV)9XP_?9Vnn?EA`DmdPAR`a2-q)i_W`N4?@8*|I36}$WnD}D#XAgl(L;^ zs0zb>Au)U=sUw(})X8_5^#Ur0s{-|)0P4T*3)D%8hp{pwfzF}~_P-!d4xAiP$gloj zqwHBV@lYa)Py=Jety9%-sUkHHYOIkOMGu+x8Lgd^9w2!=Nb(A@#e#Tuzf%cB{%(TE zvbd!why+x~%iS$vCo~hL`}4$fnZ%D^Wa1}nY9%4>hLNKRMm~$Xp9_V@-*MvM6Ywio zU&H^K*fXBcKuJH@saAgJgD%M{tLH8D@A018hzVkcj~_D`P;eJ9-H7ssX>cP>67kDHkHK zz2%)S@XdW8bf`D(#O5T4S2t2Pu8_h*Cobs~SQB?1!dN$!^HvGch#;s?IJ=ksy>~|2 zyGR>&5W#%O+j}lBt+bPcn&Pm#K&!VTXeCPz?!pWw3MoIr9|%L-l^7zE{Ls8ieufAG zVKg;BH+IGqs9x8W9>PQq^$vD79Hs4DA;6KgT)+QL3Q5Gk7RI2g*IjMO!fprdv36M! z6>+V*e`bOL5W|AesUx#W^=jHwp-^-hv}PrdjWqyvW&!gQ=}-~^$w5<(}d+h&QKp~j6z zEEWcOGBHplrK8!I(w!KHs0iRB@#;oHwn0M-2ALfGzh^B35e(ie%_AUnIYDEYS+I?v zYJ{8`;SYo%{y}1hOl+cgQ*EOfphXu>~~gwzSc}P=!EkgxL~0f4M7gf)d4uZ`Ui#UmPbS0qjO+c1Aj* z?iJKB1dEj|vM*YtT88g}c3spmgw7F(q5#Y{s5WaU9}(0tgvq3ES6?OtuVt{uk#SNN zG1yzs#Qq7Owztx6uhMVZqTjaTx6Aep_VX(Cb0_LQo1Xx*gytLOaR8X zn7`0IlNl6c4D)?GIqAnhpV$$CzKMQgptIk40o{D0m7G(k?BKNvP4koyW{8BR{w~VB z%$AmBdHKU^*xU#UDTp|@=FZ&6@ib4<+S$Jj%O#YB<*}=2)QxJ$YXJH0wGf@MTbPBo9Zon>07VUQ_**mFF+ z+&sC?p9tIF3&tncH3;nW{+42+4)DBuFxG-U89pf^*2d?H$AXd*x9M+3maO4fWjBYz zq*Sv~!Z(q54AFeC$upk#NOHrgHN576`OXD@d>J21uIo5(fsw{p$VNdG{@7G^91oxx zOV%ZPyLqnZZ{i^2G4FXamZf zy?oiYV1S$({0UPpM}H3c+i{2lR7CLFaKlE!{&2}yY%F8CEf@7V-PnW{I8{=?lX0-4 zXb?&SpH&6T@{kj!gjT3iYIsYtZ=FTuNz>mgzswu;x>L`ah?&dy91O$&xD~OHzLis6 z7-g7^(nS3_<4N=;F@${|#L?fv$BkhKd+(NCBs%-s2nWcXYX&DAn*Qc8K2_7umkm%H z^>F?MquTVZ!pR$_UdopYd_}v2X{h#S!5`r@m_!k)6bghn;&0I#o`Z#8_{iS?weUBW zP=}U}L=uE$4`@wMuj*wQa4UPlgq^q_KdQM#qvYf-RY;(=LGv8f$csIhlt5=MSgyrI zb}J4HvCFrB2vv%r<7lWVP`gPWLu(ACcX+osXoi9^EN1C5-^_Yo609_sZW>j(%BuvH zZ>sAT!4UrE$@Ttr(lbs2(Kwknp_d~n-7*mt8A8xcZglAbK;fSpvszt;4;nma!? zhsSQNw&Iyc<2vxTh~di%Oo^JUQmy2$`(pBjt>&Ag`lBeaAL;rRwRI z@}{+HmhnFof|MN+{Ykfilfn5X8_;SOgu06T&Hv1o9V{a`X>Tl=I^Y50xxRtQjD+ZY zzEQ&zTL2yZv^qBfmmxh)(W)*%1_HaVgA4_%Y@(S}A=xcEI#SXq{!!7G5k zG*G*8*}uAoR_F5uN(IY#y;LH*%b?y)Ba=0Djq(z=X_x(tM)g7yrHTF+wBn*1MWrG-^Ji1sC7rT&lAqm9KR$_qO#2h~(eTHqAuB&5pTe3x$68)y zt)6R`H_rk zr;&#>hxwkOw4{uRwhq4oRibqgSdEL5wl_V1!N;mx4W3hrbQ2qgJcLIq{o9ZLRisPt z9osv@Cq~dzbuPdk4t(Z%e~6XL`a}0G+;@r`R}{aHh8fM1!!+z?_;~)Nh%ih|HX)T$ YZj?wZm&vkDl*pX~mX|`W#$xXO0|%%yNdN!< diff --git a/mddocs/doctrees/file_df/file_formats/avro.doctree b/mddocs/doctrees/file_df/file_formats/avro.doctree deleted file mode 100644 index 5cc900b46911bdbcf2dd3015753f7b5a115dbb81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 115063 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 diff --git a/mddocs/doctrees/file_df/file_formats/base.doctree b/mddocs/doctrees/file_df/file_formats/base.doctree deleted file mode 100644 index 63cab204b0d019e02caa6dcbc8794231db4494e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28172 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 diff --git a/mddocs/doctrees/file_df/file_formats/csv.doctree b/mddocs/doctrees/file_df/file_formats/csv.doctree deleted file mode 100644 index 899308f9cb8a3dacc6ced1cee9d38db98360102a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 224244 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 diff --git a/mddocs/doctrees/file_df/file_formats/index.doctree b/mddocs/doctrees/file_df/file_formats/index.doctree deleted file mode 100644 index 46819e43f20e19aa3ffddfa805fe628cb3f940d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4513 zcmc&&TW=gm6}Dr~*yGFi5@#=vtVR*L7$namh84&{AYc`NEXE6gXoY08YPxIYRNS}i zs&>X=BUn%%BZ_NkcOQ7*4#s?qn zEVw_tB~uddxXXedP695CUJVjIk9e%i3-hJ-%~P|lTN!&IlRWde(F@@4NrXYhV{?kb zw&hnMl-*Lm%Q7EGB@~EB>;-%%VqqU^dDPG!cDtSTWS*u;raVfLGx=UeL~O*5jyj}N zDvx?S5skW%1P=q*6-jTvf)Ve%et7So{0@)q%Y#9f^R*8CjugvATwRJtVy?n&##x|< zLr*7&$Gv;q`(Nq4*>j)wL>%yOmsAw48Cp3t)Pli$$YCH1uABg+H?JsM@iv^^(|o`D z|6Age(`!trObl{`xY{@_i9jM|dc_Z!lpx}{5woaxS`G5_@@2sMGkC%AEU{1-+S5yU zCX9abe+rP%Yd(tu5wJoVz4$FdUzWLEQEX(dHT05hRw&!}MkbY)W=WWgzG?JwC}M7o zZ|G*q5Edx2n-3V>hEenyBG$q0E`DFY?+qB$gyv4yuT{GKmq;)TFM7NQ?r2OG(>iQe{U#4elSe3h$(qg!0B3x8n8$NMsG}i zD&1A^buek@t#ENS=Lig+H^#1r|yd6{!G47 z(_@c=SA4+=A?q*(qjAKL<=ubw_LuAo5McM5I$biY5>!(}9`dmn-Q zVZlys!`Nk~<*J`VX_Cjmlv@imX#to@e3kKOJ`f^B z*I8rfFaLpYye_BvbYfe@eZ#B5x?j|tP93_dWr$B#!yiI}vKAHTs$b>f6eY#0n`+<< zE<9KBpBnm<-je#|xhyDnU^oFoKDbn`QmVSN?i%#A-cuQiWymbvZ2K5xTo%2rp%TTy z0!Af|Lsf3Qx2W5Gp4ragl^v+BH!6xttG-^9TqzNcFe2^LBhN5PV#QH}bn_9z7$WOx zq?<{u(p;H7J?iV$h{rr*D#?I>mg|9vMJZm^vm~*d4(T*Dda-BpdZB`cd(XhIR=(z0 zh__9^*0a6Yo?j2o8*85hyK$%;(l5tDlJBGY^wj5`fF-R<4EzUq6rn&$q80dOXXL%p*({A@Ub6 zfNj1qdaLmKl$3^(V)UJvlNk~858W<=UMf(1di(0Z%$IuG8UWx)f`397Wb|gp;!%#7 z7VZx4egh>=-^{q5WC6zF6tKZHwx{%U(@)|dY=k>RP@#dEy3KNxc$Xx7!wvzPK^~=E zjYoLZ5d@%^#VoV}w;aV~gAM&9DV01A63-U%zTQUKC6as7Upj~&ThE0Q1zxwjKu9*g z0F4f&+-&k*!9fy&9Vld$$}!{Auv!DgtEP^u2%v#4#~U^erD;XC9B32t90)w&*&ocgj%`SPh3oL7V6$9-kX@ zQ@vW0667wBh%HPYH*a`x#2VTCdo8dHp{$*D%gG1be*xMx;spQz diff --git a/mddocs/doctrees/file_df/file_formats/json.doctree b/mddocs/doctrees/file_df/file_formats/json.doctree deleted file mode 100644 index b0e34b63210396f15718850c1d97178b07e697fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 166599 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 Km$9XY^#2P=sk$5h diff --git a/mddocs/doctrees/file_df/file_formats/jsonline.doctree b/mddocs/doctrees/file_df/file_formats/jsonline.doctree deleted file mode 100644 index c23ff2b2390dbaeb3b3d2710b2ae68fcd07090e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 154252 zcmeHw33wb=nV@A`vL)Y+b2$fX=dkQZE!#;55wXE1aqP$@c0wkOkXqd(b(dS+ovv!z z3eE)q5~hYL4Gb_G!*K6#?7%<>B)hP{awOb01GB@Gfn{JegdJcq%l`j6s$NxB{q?H5 zB!9af`M${2RrUV+{nvX{b$9n`y5@Dw!~dKWR;^g7oXqA5g-W$xSWRbfp_*?@8x_0x zKy%|g%{!X?PH#PT!m2jvd86sfgFEuYQn^q!D$P;2xE$}dOJyr70Gw?#CM>&T!^vu8 ztYA!*DkVI0;T5}`*JiVsQ&yu^tJdwYVzqkAI+ZC+=cbI|;Y`uCYu514P-%K9YZXgI zxnN~W)uD-8Vagb~Wb5WZF%Ayd$iazn!|2Pvf5UdJK4sV*h-$^K%h|e-E7(N?=wyJ5 z%FyQQmdmqO4zb}wrAom#nJwDW<-TTd(P*>S3k(~R1~3Q=OCbQ!%X|dOd%GFk>U}Rf z{hTo3oW7iG*Gm%(8}y2LTu>@lpfhJtzMQixI0?tymE1HxT3l$wEIM9u~K zA2Y3L1sNNhES2F;wLYD*hq+lra@Gx;l?qK~P1s;Y^-iFq8!0L7fd7Y_ZD8ip#*hWJ zkgE^b)3qTciy@p2Lx_2-Fv6_s8!~FK*A>eZ%P&d!% zW;0k3FQ+ZqJQjfo)l1Ck`@!Z)l_{X#$x5m-{cL_@N|g*Z82@E zDDL6Ro%T!(*vTF!*+xBAKDswowv5rtwhTKM$bd_$7T|{AjAhp|r!phJ0$jy^1~O=i zhmBh3wS0BDRyQmQ{J6Xx#I>OUP&N!?HcZ@Js%^!CDLBA?%eP;F|5%k=tu}*45ERPb z2R4ibNdR`Mbz`JaF7IWIe>gKyt(Jp#<;vyiiCxucj8YbH%Z-)W8dcjUFjyn#=ZKpR zGc$ND4xnxuf!J6ta&K%%b*9?kJbhRwM2M3*XC~dZ*&BOJKV;$RureBS&sY5#%kPbZuotslHsz=gOUf z*|h1{3CISmjVuPCM_s`@YOYeL+Gth2e01y4Yc8@OMh#~+FyFsn`#|Oz>4C?rS}qS1 zxwm9CY~K!<28(~APB$CT^a9#T)PSbCat=sw_fA)ahsaSp@*uoYh2YO~oURkioSN)l zXn8q=mX|apjOdWpdvdxw%=myHjGo-6A2(ROa4(F#o8xngh+Kh9W|kxj@i6G+2(_7E z%p};H%2X$bh^fTHE&IDzjDEby7eVn+(h$5P< zheGLE&FF%p8RQF5q+EiG&J**^8OS95lt4u17&5f}DS_Y_;-3g@!2CJ{ZjbDOD`*q&PgC5p8rC1Cv5?y{-G{g#YEEv~%T_7?RD`&jO`&){wN-JBx z0cwBE@`5$46l;x2-fpzyYh9vG0)ia&1Znm_SCc|oYe8x*#33B-D_kjsr)B1uB>3VR zL-R7^=t&pya=*}BVK1#2&>X8wjh%!FL@^oB${2MPiGd2Y8)h8=a_O zqB%3nlj9Lcoy-kP)aFI-Ph-ODhJReKbRCtJ1V^p~na?IUG*`jppv;9r1s2G@SGu-z z%$PX=#i=4)r4e?l?F~xZoU~bBWJn8wIcdBK}#@6 zEarOwkT1H$1_HsKUypjRU~Tj=PD?0QB<__{)cx%Rf)~-OMI{^NT`*2F5WI>;K?MSi zCEkGFLZXQ91p>GiIok^a|Cz?E=_dYn&`op_1>VP$6EC*U(4>dY9u8b93VfY}PeuV< z7XFDu5utg(f>spxHH|=1z3D~)PQTxKL`MPLNz}W5irEpO6u@Jnz}YnL@F<{5`6V>) za69I}6GVZ_Ni6L|0ZxKnk9tvHG-SbEwjMfPsBZ}*OB8f6nE1|l0p$QKrO1GCEsWC) zD7VlksDQ$;#Cygfi6UYy2;g2o*~cr36cocXqE(pOPLr^fAvl z+wK3A`KG8bzF4ice0sP`l%7le^qkf|kc2p0mcP-19i8vtkgaAYw*wMW*ML$_{!bE# z4!5!8(Cv`APRdOF^hCCW#FUoEo<(CxLuA*JC=!UQn{6JH5);2XfgL6Bq$RKtjU^3%ogh&p z5ZGe5%~HzDJK>4##U!@0#P$G&yH zu;_L32vG{)v6cM=H1P1sUYGLAXyD-$0SBI-vcH^0>E>E5c;4;?n(fF^eFVm7mg?g)3d+@SEb&ESg+viy+yb~)sxMIiL_MP98H^xM5ma3;w9IlX%C-V9Jp4w{WcOl86L4woocP`OELU$g0hvqcg8U*mzAn`LAczBS|rTj@6I7NAaAThs(3Zm@< z3C^Eik9t93UF;)LEfHdwZ-@*JfwNw0IFIHwGB%tI<1}N#Mj8bb8#tDD4|f%bBEmfg z;GM^YeKdAW-;r)?*u(S_FS${g_V78yfosKvVa$ z=IHevZE1-TXGaW{@gnlN7dd`H%OWyz{0|tX89APyQBaYCV~O{F^A=IjKEinj;GIW~ z)iicZ&ysHBSPnGlY%RQ$ragSdao}2!;|dZ!898)W*h!*@P`+S6D{>s75oqc+-N?b| z_j{b^$e}xrOq$bhYY@O=BgaV^czEQ{rTir{aEkH-k)ugsX(w`U{``8>iyW6Q=O6c? zgqE1Gs%4mr79m%>IPpH3^T;^y9vG(?Cq7Q2pyC9_5+5f%Poju$6#{tYapLDg;soc(kQ6l!Lh`Z1CvoC4+o;@X)3F92z)9d4k|^DT$?>;KBLx>rpRwY~t=;=f!C)q2sLZu`+N( zUG~DpvuHs?hK*;!IL)weJ&l428yri#^E*nSi0~W&c;{hbn#Qi_R?-a{C8nQv$=yTK z9zNYTaILWM3KBjUHgs8d6Nw^1`GN(lu<@U01e*FyH*9eF{q80@Z0OFT&(NHPTY~@| z8#ca90}l@yx|IJD4V#fow@$R@6c=sA?xGe$Z(=_s~1`p z_fcs=w6QLPahjp!EE)wBS~!+?2YE4xBEq`};GKt-r_K9yyUKqXg%+DepsC+wXYrHkQoaQv#8pJ$`4K05~ z0}l@^x*p)YG;oUY1fk_Y5=%Rwh4bgvqh4qkWFbK=n8&?lxh0~U8#!DC65v`5AOA?BpyCI|5^wkaLZXOpAp&^k@#Bv)c1{11Zv6Onrk{Apow0<g6tw7cp7AY`@HL!7{1iG)qM%vO${6pZ5wz>EJ9>PJTdzOUh|U;wL+P7o zPQ&vq0X#Ni{977$c*dwp`9m~tit+>*rpQ(Z4G>uPjMbmpWbT;M$3FKWB>}B^@7fOY4#(7&O2e8X3%+%MnMG~ zjwL?me3nEJ;V%SmFX)_4$(ss0XNC?`0?&77`ZV25OW@h87kE6|p*}T2y;ATSOxy8# z_$AF}_>|?qwL;GyNcd#v(Pd%raxzCCSeg{G=7e4H0*^&A z9~pS6Fitb@+(VFC`@Z({n1m)zge zw1-bI4qPi#{5KLl87g#H_$rAaLivIPtx)lgGy+ZirW-0a{eBk{9V&F^(eG(a!>vI8 zj|~-zR*)e*JXGjXekKi^qC7#UxQN8kPN?Ag`Sqw5D%Qt;#JeR-EccI*AtHF%3lG=O z3`d5CXTUhk@NgZCf(j2DOT3f2nM4ucAq4Qw!-Gj<*K{B0hKEU}pLogLNz)!atvGP4 z@ZgZ}$?%}d!W&2w5y}@VXoZJ&(+D*6n{IgE^!wdPba>F6N1viO4YvjXJT^Rhg$5oT z9&{=HAq||OJVAK)C5fe-@WA==>rpQ}Tw17CYwUyG`zCjk%Oj0)c{tw72czbuM5U$hKe(26jZ3-SmGVtIub>Mw-CTP4;7cw*fm{Ax}jo-=_g)t z&!K4#pJE)iR;ajMpUPQ$H1 z%%j*)@p>9~c&O0z0PmuKQ0{``8>3l)QfoNer_)~9p!a7O8&AGYf^ z;n#h|M`1JDGVIHs_;E<}o!foha2YAoQ(lDl63ugDG4TZ$rx_vsfkr_^2#zJ*vVTFM zh;R}DxECR2XJ!2fonyY3rYB7q|s$zfJ8yF zpcQF`X#|>m!tr~k=t!eGACJ(ShWl#)JT}ry(7?kZjV|Ri8aPFHf=F{GiKU%L z!};^;Q7_VLwM)~6W#^`A?T4Ec0T?pow4C<>&+BLrL zvtTtDoHZ>W-N3`?_dBfUz@s}KFQ7RMw*~<`Ht<|V0}l^8x|Cl{1E(lY5O|(LVreJv zaQ^&y)C)XUx)28WnPFDna8PjvT2Hr+<89sA4aIFaRK@vV0fpl4TghUZxM1lpa2=pk8 zK+_V^jX<1!zvqgMK)UnsXEdkb)*yh#MxZBY;NcNSm-6{%kXZsnd4dSElEl(Z1mgVp z^{5wtF2m4q3+xnar^U8d4wlizJK@EcjWoBBG3F8&rx|0eqES#WhGU8MaC=A;5$-_% zpY0g)JeoF5&vLqA%weYIcnut*xeTA19Jp4DnIYkmF-Dh#my#$Vj7YGc6=PmQBha*j zbYl#s-|w5EV~p;6`~b~qxHSmiu`%Xh8hCh&(WU&0G;oUY1Tp5jB$jq!4Cl|UN4*%c zv0TmP%56oM9zImY7x~kRE>F;WMn;!k!8pz6GH(r)6p)UAV~O`|%SjXwjzIvQ?dX!B zY18y6rz^Ug57g^C7I_8DW%#V*z_p^wP7*$8M7k^-B2h#bkzhe9x;&poplJ!|Mi)-M z-z!B&7v1@IlIAqr8U*mz=<*U8czATtrMyW4rzlSlUEW4wX(zgH{``8>i!O^dZ8~-$ zSD&(2kmxUZ@}IwHlMEbq=mm|BljIk}HSkdw1fIlmw>=8l;;18kdT2gRqKURrj!9b< z^o`EB(~YuS8Y`D7##k*^&rKV)QHP(F-Mqh1%oZOlZY|!;KR;i5S8>b!uEvA{@kFke zfqfOu0;@FDbo$`$V0FSY@^;fu)#LQQ+prpCqv@=Te(JDkViYL$IEyD?+g}AJX%-)G z`o`>P9x1b}rui=@vPWLhbb8rYaSxJtXK{#=2jsp0$pu}^bCwniD?gU6R&1kUHy>zj zyr+2w>;ma7_VDc>y-qLkJXO!tippi@&~=9ojvN4yWF|{x_``OYWcrO z3WCm+Dpn?!!66yW{KjnNl+~!!s&#v;SgjtjP6^{1b#`eZ$`mRk+Pbn*@J+_rR`W?AJV^6?2Zz!$9AjtBG711G zGkzG1;}}=$HAUO5S;Ip^7M{!IYG4XRwpyPWLi|Hz*o$P@L)L9&+?JFZw5pAI-mnId z&}`A3E^i+nQ*9~h8bq&)PEqW6LgXmMEhAvcQnB66!IY_zF0jvIRw3k~03o?Z%{vR( zY;;sOPEXyKY}89l=UE+!uZeSfC72XS2IR5JRoBi;$Ex8XkaGjP7^;}5uF2dR&J=Mr zg9?SS7sLFy6wXQp%`1>DpmC?u2Rs{fqmnlY&HY^-$)nCPzXf=aAgFG|!A8y&b1kZ7 z%l?IcAo3u(``Rsyl zo8a({8N;rI+hZ)jy92$1d2a-ho*tbArNS0IwVg8s--C?r!4r@P)IeD|H97``b04CqA{uY% zLHJsE&6H6AyS5>E*f5v2L!?39JCDP^x6aO_|Nv ztFxPVfbd5q`qno?^5l9&@>Z~?$m^bIy$>0LL`udWzn=Qdzk%yccMhrdCwnNJH(@5X z)qD@!gK0yQhMlE$Jy)?Hdgdk|JH&*OCj(o}cev=Ky`E1(T4>MIz%-T$DRR1;MdD}E z>1JYO7J`&>&LrsNLX)7aUhwISiFzroDrxZ^J|}q*0rK-scmn&f3zi4%Xtt5f$M9~| zek3gyG9ngMsKm_4PL}eeTsZ@2 zNdqtC@M7mNa(pb?w`tQsjuIoim9dJ|M!ArgFu-^>ZGt$GhtuU7yv6%Flt7nU}2WU^A4dWkr(;x{{8=NvF?V-OSI@#BHWJy7Nn$oM`uy9 z(tDp3ZafNs|1&^fw28}fFbbx5IeqQhm>k`oYzp24W2qzqEkfjdhxKng#BL4YGcqkw@QA@LR^;o9 zPu8o`t*wqsX&O6DtY3k>tG+%=lUciJn|rb!3RB2JD^uIn-*{y2;4{__Wbj4x+tz#U zy3CiEp&N(l!E$0 zlooc%*FReJJhjG_q70n%?DdU+JUY!*%aB%oGz5J9B_lpM5G%Iy8vR>e9Vrs5-=Uvg z0TWUK(6z`lv=2mQp_n^vCV|XtWzX697PqSi~H}3V8^$`#GoD1oa6KflQ>y^so*v z%JezeT)Iv+U!8`x*jU&F*tUM^_EOFBvU7mRb+e1krroU9ZfR|<6rml%oo`QV#x6qF z3Vn;e-EyTXO#4J?Hjy#ljr znab`4x;4-dyG`lNoVRXWrdF>)D<9VcCT22~#&m543V_VIb#hLS_qdYZ93y#@F3K|t z_kj>S`kcy~EQ@4iVu0%-u*g>|$wZy!bCJo4vj{|6FBw)7AFysX&yavJ{Tiz=8CdM8 zmnIsvv9ak4Xbe#3>>06}IgnYg=o6S&GP&JPe}C~VdBRyG*MbyaEWW(hy=CjsYc9ej z-f(6EbnME74ciAY*Ra(@SmPXn6@ol$SaNU4Y}mdX`m`cOb+ZwQS380_Rbo;6j-Oit zBLW^Re4QTAP)lvX#ESQVxDL4pPM3Xs+?DlWX)$s}c_+L)={A?(u(Gtj_}`14^DQ|` zW*_n~Yt|49H*inceNa$~*9CAT2KMam)eng3UBil2Uqn(w?%BBuhQbtkg)0Imn?(!V zS3tjtL=mxPhXW6P$Nd7}RAJ#QGOi#u*{F1Zv*cc z>&oUsH0|M2M941YFz3-C*DwVKS1T%g3QNZo}2#+e|)al{3TBR_?F7|aJ}Fg^!gjPO?qM3xbl_rTla{D2`*&Vu%;pL z@k%$_x7(P^!4@hgV_8E*mazR0F8X!HDn_&|py8IfRbY`8*v)<`0QLj`_^nW#Xy|lB z-HKRQ-Ri5xg0y%_j_#E@SL245BpY&+Bn&qlZ8e7l2cVuI(3DhFOon_Yfipcfg{muQ zp2mB11netf)*@Y_``b&1KI&4Nyo&HRH9vx2(y1aoPf|oC4WEIbI#tBCNfZ%D0|%~E zMf{Y8ubE}0TSe%~=Jz!1;qybtMyHBcbgqBK`sZIN>OGT0(S~}1Y2rmR0!{s1*hzKR$nQAAjb0Nz?f-~}}(b)XY( zsQD_Gm}yoKt&y4+5ibDh)gr>dz~L#KwgYF81SGRVWG*GvQZ z$aWQWM&W}8w!t1JuyFfdNNphlS=-~i!1tnnlKg&1oj91iiVgYCNSw;M5twK4cMGf# zrFxwe1!J;Qf&ItT3V*)D>$8g%bFZ@|aL*X*6rNVs84XcxS;KvsGDqq&D9ZvovXZy) zDGj;LInzHrerx?!*wj1#b=j?T@h|+|2*cnEZgyA?VD9zt_bt zm7|>T#Ql~;;(im}J!HNL|KU5jUk`^&XnmeQ6fwB+S~3BT${2RIJduH3e3GX{idVEF zImWPmW%lDpvFLiAN5E9d(}!`0lEV7y+r?6EQo0LKjmq#(2ZqyjEUH_?$*SA=1L^`O zO-c_p97)LhTh+$tw^AYl90^ikP58xtxWPm{+^Puvxg*?N8x?5=CE)qr=mKAwX*baR zF(z1A2OmXpUE{`4A7-P>2jL&LwqLI~DSh_moDnjL39UbYJeRNaTO`enFsVKm#7qhv z>QMZMB3ryeHS{F1#peim7zUaczie?7mu)mIr-yrtrqi{Ft%v?yi+Vo5)I%eFU&|=& zv7!-USb3dq6h67q)WG%XmQh|EqaW!4alXrGJuFVG!Ot7=_T)sYhCp0^u z&zU{IA74AVp#n|D4Fj1C6R@9WD<0s+9{jg_`xW?)Re`68X7C8IIDABO!)Wql=68~e zk@eu)U?>Es*oCnVlPDtUK@ME2LG&pazGed<-3F1aY`#U)9zNxSY;+n#KOy0#qTVM+ z6m6(?k(dEWwlnWMzb^}TCQTK$^oW-GryThSVrtDfR3S%Ye%*Cc;baZTiH1VV?=Mgi z+aB&EBr5sSb6o3be2)V#-_wYgJbVvMLiQBO)qq6RHK1^{nZ`oB15bCRY$s9BX66&w zBH>UaBkpfcXa`7KW=cZ4g~pPG(269A1VZcK!AHtW{`5q4oWzut$nK@Fq#?3blPD61 zteeGYDKYWO6WCixJZTB+{WO*|1ojCMMFN2>mKlwdnRmhy+ZRY|X^HJSG?p~P_A?Si z07SIFa>f(flO(?6g5w)fao6Ig5(ws>PX%g?@D`s=t|U>=g40QvE+pZn!omQF zf@VSMWog4Sf_C#IzLM4M)=M7gqkA~vm120!lxR)uW?CBIN%j#Kq`9UxNx~;BhGU60 zdYeQMVKD-D>zW!~P+J2X=)@apR-h0_b4@M0XTsOjUJV?m>uSETU9PP?05@UF3HRs8 z${X_9nkZOw*4D1xNyiVb zb*LE1@V5nq`^%=+%*Pj$!hgZ_Injhh{@@cI{FVep z_}y4U@Iy3|ajMzIAHj@xJamrFO<%fIIO|8i8gy-O^#^XxdGub)_>w z6Ca-a3hC(7OEnUHDhj@nMA43dNBoJBLnF{sa7%x=vwDf^AUeMa_h8|ZH&MOxURoOA z7V}ORq**ULNWv#AhGU6$37;iVL|BXf-dZo=h2-^8s6+MCY5oDCPMY-+5zu+j^b=r1 zEt*;*iL9J{3U|uNNd^N*NS_jMQm1lySW|+`^yA@FxSq19^(#MKCiK zRTko8Ro0p?DJjrniR1EgiNl}D@SY3(Hk^z&cs?D}^rO39-=3lrI%qZqlBiU&+OKN` z3K{2gB#!x+egziI5>)B5*=*{J6fkEm0SuDjxf`N7-;uaF4^A)n*6W{(8PI8oz6V8; ztVDlKA}NXzeJ2j$CHgKn)GX1(C@j$xhG(fnzXj+@P@+qMphOpB>Xzuzxu;U1-v(6k z61|Q`tVCx=r>{icmMfR5C!iUJZ((VpKHs8KpZjohP@JFT7Uuzovssz{IjBlabK=vi z2LdAkK_xs>L^QgU`Ab~{jx{%xc|hL5vV2+KI(Jfmvz|YGw@ZKWePbT)=DQF~N(K2t zBuQlU{Sg?dv*YV?B#Maan*-M?$iGD+P%dBz)3hIF|TGF+!qt(x!c3pPa&~>@vxr}Ac!1st? z=}n|a7IEYu)UxY)=%36%V%35J~ zmMZHX0bL0yYe^7P)`Cpk%3AvBR4VJ=1J%5;{sSJd%9+}41viWJ!T z_XzujN8(p>K2y8KZpGF?we7xjD;vQHb4&e2@12gRzQFU0j6a)+&!)}`j0l9d@PrW2 z=+@mUTm;S?b69tWX*F1ZpH1lqdis&qJ&(58F8ADca!i_QSi%26zwQDP?i4`jX+bu zDOcrO2hq8hT9wC5-b7XTL$ox)E#@OINV6*c90{Ma7>*@Aj(m$m5n(X`cxzRT7m`=y zp*r!@Y4!osG^_IXV9)FGCjqZopGU?OS*iaP2FOZXh6YGvmkO!qRO;jYN*xz07`b8* zTr80D52WE&G^-6e!?G?|&%g@)aK2uu*@LW(R|>K5aZz4HmfTMXB4vu2u;gC2&ae4E zKB71VrQ}AOtmNJgC3jk^DkUS$6I8-+HRo9!apl`J9i+vxZ=!U(w!P9ZJc+_rpZ?x6 zMQR6ipc2G#(Z7Rw3koJ#5qWtcS&AYui-UL(IRuBAMWh&oMWn*;EESP^fvyBaq$CK6 zNI|A<5h>k>oS}XT;_rIZf0YMNEnek;NBmVDcy#)T$Q{fD9ELt(*?2A%l5H%}Ua4I^ zs(3mm9M}4nY5h=Pg3MaUcmkBAhT8a0e^+2cz(t2=D~Lw7WVBoa&WSlJ8T~R37K^J| ztV(tmL#}uQV$QSqKuHtk zut3z6&bMgd!zYlCj!uF26B2$Z3Vwn_(T;)x1>(H*ezz2GGMWlbxj^JP@W)H_ku9}A zjGMfP3d9R(X@pzMS{S5RAP$i5NsHlF;-khei6X*c1n|}Z5ih7ws{@^QL(K|Ub(#et z71ViYcr);!mWCm*L>7gA2Di$hP=*6YOlx8$>J){O35r74ISpUT=HHnwWUxfcSS+P- z%f#{V4S{uBzB@ItG#n2oEn#U`3=EHU2})^*I9X}9Gp;m@XIQBSX;!+KXk>oSwLuwp zdq+$}tclXp#BLjQI-y7cQnHLpv*fk&(^ zWk;v4F1?mj5F?Fgqh87%;2(>=#i&=?C{M4^E>A8-a^7p zMZrZ9MLP-(RItZs1eyv?xq{_7h|bE?3N~)?CaPeYv^2sk=9Mr=vx0pa37@nWjwL>n ze2_#DVKD-DYXyrJ)KJ!ePQ0P!PaqnlS;1-qd|t?Y3D{8!+33h3>)9{E2wBg{-~b8j z&qO@csb>#`)U#!tSY>W!nH7Amq|%t4FzS$G8QG~UJm|Z5^X4sE1BGkcqUN^(%1T(d z{xC4yA41g*l2W-MPFA_Xdzo1r&&05%RGBoruwW^KAoGB3_FDn)uN_ewQKrUH6}OB! z|6>1~NhU{Mp&F#cn?ppQy05)LH6mBTdiCo<&Yjk)7ocR4)vHfSBur7SuEjyTUOf*E zHS1L|3hPyc;aRFzw*p-W>QzY))T@F_-Fj7es#NOLeLyv@SFgh(RKL5=Df? z2;i+XDqc{7SO+@shMKp4(WP0V#s+#`q3#A|)Cx5!s>u3u58N;7QyCZ_fxT5snTY!I z0r&`*{8$|S`l0!k%Bh>Tbj_O=Qnkt=6&4}o!>yHSWe`r|VnHS^zsqS2z=t@G8Te62 zD_?@wkCZ0iGCUKT!_QT^HSU(J#+J>j#@&>uRW10I<#7YP-N`;{3GcQoSK)Se%(ey} zTernau=>Zx_lX<$gSwXme~#=G5G%$MjMsrU=!Y@5&xJ8kwFh&9Jf(~zXe2knQ1VpG ze6u1*XIBbR6e(cK+q(`2>^@<+e>5;W;71g%s+9YPla>3=kmY_-n^4O4AOqg)3#)G5 z#6Dc-Jg3d-HY~rx+V&vh$b7$|H8CST)N-od|CPl{w2Ygn&%dAe6pEg$K#e+Uqk$-* zx-+0GzpkNGBIc4zM!VT>1;C2~0JOA?Sedrx`(C;gB!eof52>x9-KJp8#OI3d+mA6>>BL&R+17K+BiFfTG zZmvJyms)9(mW_`qPF2#>>z-4H`mZSw1vY^+1;5)0TS6{lzE~2m5PvH8zt$OkpHT;e zlEkR-+nayGWKFBZZ`3#{%&2#C9JxZw_z4u1G-F)^im0Cqh}!Rfz-E*nD%I8No>Kt* zdjJqEF=5DvJ;ab{qQHE3D3|ms#GeW&%P#d#&4i@*%qK`n67#{16xk02Wbe19MdRZvtCE^q0Kb&MR|N*sViPum7(;?t zkdP#b!%qZX#=z1P_!WnvxZfm2a_-HRbeF<+8X8}zACJY;<-*VR>bu> zmS&k^6lO>VG;oQp3M-@b!6=M-P=WkzjaO|rtHdYYVT~)e5p%J;q^Np2plZLJz!az7 zHQERar}ZQ(1#vP<@vX%KDWS#a&w3Vv7!`qZwCV2^$WpRJlvgE^f}ixIK>A-WfzYxO zpYsqF(C24(mKIUo4s<10M3EYRMHE5i1yolWXTuX6Itn1Wl_|Kzxtv2Z0fR*%#gzMl_U3t4+zTx(J;6=5V1UMhU@1m-X?MDU$@9TfN1Y zCtUWEFUEMHGyfNYNog@=;Re5v_*G7JUgyD32wAb;rd~~=i0Hg>;ChQO85)7oRhq*` z-gKpN1x0=W zmPWY6oPksqxO0UvxyRuRsmb;?1A1q8L4K9E~&258X6t?3OhG(hbT?=$2sCXqoQ1J>f zb+-*l-;_$ly9KD`74KF&VihktI(-%IK2}%AR|oB?R_uDUjKyvn&$(^WE_B22bx_}~ z_t&@K&}Xx(-4E(ir&)ZqaX2s{FfqduCPbrK);`BY;M_HbWo@|ZgVpT0kya-|8F7bK zyB1v@lh>}Ee9S2XlTz(^J4q6ms2+!*I<@P4B#MYcl>^tSU0+Kh&@63I-bkn`o%hnj zhfgda9i7_sK@xr{3jQpKq8$YXYS(Yj2s9O(a_!1>5S^^4wQJnuO;o!+K}#dtVtxgK zG;7y+8~wq-@9Ig5;aK9M$#N1!gvAKpt+gv&P@`D~I`M{@I{|8%wX06h=Oya^NJlMM ziP$15)j=2{D^(dDAhF#oB%@QQzR*{xvVfOqhj#t0%5Uty5MU+S%r(+0qvqO&ENFGYQQ8_?iB%ZNm%7x6BzC{=!hy8 zak47+`EtkH>+BZ2RIrywdcCE|JMgQ;$LO2M8uu zn(nw0zGDa}K=we%HtM7|oo@uv&m^fi(T~&akY#yM~px^{*r&WcK0)bGxGSPNArHCw>C2jX3{7dP4i!14~?@JPUK;4 ziPbDVf*Wf#z!N4A7z8(dg&cJia#Z0YLvo^_5cB&BlznJRRpXHe{U<%E&eFIa2f(>t z{`WmN8O7m%MA$W;aCi-kWfh351V zNn=Svh&G8Lfe`yV4<+TNp7KQZLK0(g(Tz%UU89Qqy^_X~hUnf*qM$|RcM_*u7EUQF z{4EJz!%4(Xp@&EmGz=z>Y@QpLsbL|v+%(b5P{ zt9}H7G`mz!lJH53;aK7=X8vVl45nBJF%QJ5W~LqJ#2acp2)Rg_U8+E?&Kpu05QN&0 z;>n}zJ*|VQviBtOKM0@?i7Y9i_Z0IetMij<;hQq>z-lp9DU^*&4OaNDi)7U6)w+dG zsE&_&StRVH7|d9PEj~PRJXdbOrV0!0sWi%E_U+dyJW*QDoyZh&b`IeVS0~Kj@jLU? za$~xJp8-5LxvN^QH){3`M!s4v!2S$&Da%m#QdExZSgD3Rt;}?;JXx(z8-+~0dV=3A z^H+AGU4-+n!-JEaDZxi;3#FWGlxMPi%oq0iA3eQ*WL$dBn7h0XG(;P*;*~JH6Ax=% znYec%OAc(4T!scQ(bl7x#ktZdlcVnm2Xik<3Tsn;dvUp+B&mQt&-#=&wm*@y#d8bS zGI@c^I4j(W75I3o>O}mc!ebTO-X)O>@uxC;G%!56sz751l6MAtoi;d)6yPnisZrBe z7dzp&N^UyS3$^BLNH1@cof|n*6%s6xzkX-oNho)k&ivX;)9IfUuw4aM zRL9Ke0a!3kn$882rLr+rm>gpNqBF?Z7Cfs^t&~d@qv_i)dnZ737Qsj;#Ng5bq=3bC z<7BN|%9m^?!tnBmT)h&g$G8_(>M;h`&ef+3yXg?}aqG4ra~leXKh5h}-D2tK6hQ`c z=|!CAj;jiMcZ~3mB=K~WLedU4%KxbFW#i-8&NpOIzY`g5oz$yZpvoz;%!f@8$$vsf z+FO%^5xtvn1XFu0PVMlJq2R#VRUApgj*q3jouQ*=wO{NwDnjhZqL|N@I-B6DWul1_ z7X+E$13niXul9yZ%`BFu6oGv;ATXMBp_Ip@QZ59g5aC(DgG1U?3^vj~pI9+CiYq4C z2tuDWROZum4Xom6LuC{X-_clc?uw@k#V8E={TjH$(}v2ZON3k@UGQz2T`M0cEMXGo zh1&6PZ!7E)>S#~q zMr4Uc)bnq&AZdjl1@COB)-!>x>3JFCWr?KVCw&Q!zJUpZmYsMCQ83)=XLy#Dp0)#B z36`Fu24Lw)ka<3}0)VqbJcTGLp;VThZUn0N($h_N#Fn1e(dk=y+83FcC0~!y+LIZH zuY*M@Z+iv-9TSP!`nq}*)TxG;`2NI+z=*&U3-3=L8p>|fhR`t=!CdXhBxE04&LR@t zBqf96@|b+#%af1!=Ljaa@YM&KVUn+4y_uwl>}J>+3%Z^NjrQJ@bu57+U(;hynglu$L-Ty$sPer}IAW^iT-e9Zy zcQgV`{ieJjlj|Tlu?qKW;WIZ;tNW}$zvtHS!>eJCW~=*R5`G^$2PSEdoI2Gd#qX-6L*a9JJgct%Cz zF;{?#Xar)2Y#5ejhWq-mSr||-hKDn?8M|1m^z~)nzsY)a8VWqaE@$DrpA{p|UgKGs zu2t)H<}fU&96M0W=gNBz2k*v4CahGj`}V@|?!7m_hcAqJ*nL^v@RYa9y zWv$!FS!{cP%?c9idLwTifl~vS!|Zr($tZ&aj+CYi%g#;L+-tD*gU1{y1DDyB$!4?S z1efAA0R`{uXSAXej=D$qOTp8>etKqX5;3k1pzPncaUgnBz>;Aw02Kt{$V}#<%r%39 zg3DaRc0rA@qm7(s*&qf+q|o|t8D-mguipW%BW-Ql`XC_!Fjn$L9l)xp^W@=~!Zz>F zW0$LQo21UW!0m1u|A#Y~85|rGETL=*lYo?C&(<52{-f)Mtm?GE`r=szaQ)~;(eLxQ zx*K5cdCGG_`kmPT?e#|PNaVWYwp<(}w&gM}X1-Wi^tk!C;3M>Bu5xGOtPR`~Ib+#< zsm@m4Ocjo+b(VTFH2LiGjR;N%y_q@M2NOryd#baMn2FxhYng?iGcaoQZS2#Zb;Z#? z8JEpMjHc`VlX2-87k7w|_xw5dGpeWRbc1o{4OrP+UMx)&%kaNF1}m6W^NuFgXXb0b z6XMb=CNWJ+_wI!gxZ9umOzaHLFrP_hb`L_*<&DC%6eT0;sDb_tBqUh_{arXjd*#p{ zBrXBc|AsM4XT_p{F2r<^pW#_*pg#n3QOXttx0yW~`1ds6>E$X&;Cr1umV%9yOP1Yy zpc&X^*9#Vd8CuJges5$25^3eSvt(DbV%hZ^ynY_GEpuJ@&Ux|s8;Ml&HN1PP`DOfP zDDAQVs~J}q6V<|u%F-EwnN6FfOO*pgWy&sY+5}N*3uDb}ecQQz{d`r#I5qz=e}x=j zbK6|5R4dLh-&iTiUY8D~hWkw-K`z7T2`|ruL9B*b$oTQpf)~^oLr-=wV}68#palzn zf5`yTGe)d~E2DlMGm0&@I$eXzFNt@%D#L#oGaS9ykC8@hK~xhSZ+#4J9WoUJU%)f$ z)vPpUpW{fR#|vL}3hsIlYgMvA5k9D&ZJjZXp94;Md2OZuwM{9{ODO+78uJBki+Oog zu2o%uM{KQ%9i6_ls;kDvN|lm5HrBsUZ*gkcfKXI`?~D!Tye&jR?_eS7JXs88C9wWY zn~uSLpeak#PQ{N)C+RGf_sGfVo$zMU^&ot8HuJh?cWinOFG2NR&Q9bkV+_mP za&{*iUeAs^ZunBlV;-YY2At8aM_lMHe%HM+f72!@6+HChQE+(*Tkt^tf?xA z2bT6CAIvYBzfoRUEj|Y_OUch00cnEd=Pw0~kT<0E5TM>XI)mE8h=E3Uekn!D^E*A< z9fmpk4*`dKW5S2=h&g+9bo!kAHVayaum^MM2K<1Lp1)T=ExAR<<@<1S;P=mRtvvv7 zHa8tS=9(pUh4Cvz-wcch_@40PQA9)KJJO}y3$yw2E&|7zo5y7X^5&t(i?PcB*Lgt^ zIP1CnU%2##MHf2Sdc2!IM=+ce@7>l**~hh#Zz*_+q={TX{2dI1xEK3|;=Um&$%t4% z1zZPSh7?>#4EJ5(^Ec790-KgbxW&|9kmk067n1Nvi{V(}ox&?g6cH99 zfVXZdzzb>}(}7OBq2{N-!KIn-M#XWS^}Zi?P_y1Z7?IltJ^;7LOjkw%h>f2Weq3k! zz@g_A*(z>j5}ujEN9W|i9WF|+C)dPt|FEh&Sk$<1 zJ2~4EKP(qWD7Ng!Edw7CY=CY%YEXX{;qV+qcZc`)6G>3C+rEW^_$ubN;V`bs5DjWE z3On)&!?RSQ{R-$xP@_qLpz9{cJQtMldFZCeqF8y-8la2EC>``#7^7eJ?pM`kC1t&*(g5zftR(IoQ?G-5@xE+jAe6l^zt;Y*0G##(0 z3T_(%+@9c~aq)+v&c^Zue6?uTD}rC8fmO8Yak^3EDdIiB#I7#)}^`7_Pbz?MV zh)y(ZWI{kSZM56Ylj7^@tDWxys|8#wsD@W z)5d8<)Il%D>*0ri#c4ZRPm#O{G^ftR_#FKmff0du7+z)|8fr$UMbYbB1kTC1Y3PIr zJ=oP*9d;8>o5C)6{hfzgc9QpZJW-e*LomVq&XS2Lyks@;JI@{^Ng^w`&%scg_RjZ6 z6cLpi2Oi$uS%|DEA?crK1WFd4sJp|_wAjN3|!$z*M5#N`@@9}kh_U6i#p>}^307AvkUJ}rozikuyq2HGWhWkC6 zx|EGsuV~&NPFBL*fb68pu{LWOufTq5N6h&;Q$aC%HcoV=b|vmixmM$*+f7JPus!u6 z!3)@ANA0PPBOIP*>9(gHP9#Rro_Y`m@%Gfm;83$YB}QR;N?~}G+EZTvx)QXfBtg)g z5@hPW`&#;wRI0?sfofhQ{xcr2Dv=$XzAAA$eumDdTf5;sw(vT(h`&0?YD1p=K_ zgSyfgr-=`rH$po8+&=texfK$ADhfVDqG(6Kfokw&Gy+Wpr(6wk9Ym*AYBd-)c@tHG z@1~^@ZZU6%L7LUzM@aaj#c(X~F5%N8iU^Alz+0<9ypX&a4AqIJPV;vVZ_=y=BLg|F z1b+lfsFh$aipVPP$8evl0%ah81oRIgI_gw`2P3LLR^vh0H(0Ae-6ucR#NLc6k|ikZ z`s9n$;H`Tx)!r`xdP!LAJrNil$S9OquT*=8lT~|{sMTI1XG#G_Q@aG-0m&0aZ)3qC zaYg~M@N&N?CZjq0<4QDP8kOZ)9TDZLEQ7Ro^M$A^pVdZX8O*$}s(f4+M5k5d7L-S_ zs`BzgA{13+76e6dbhJl^UwZ{{nIW=g< zCk=N6Mg)9oc;16(bSq5DMKD)uk6}U&R++2AZW3#cA(y;rv+1&veC^Q_h4}!2NvYa= zCrJ{STD}d2>a0C}m_!kgT5{le)#j&Y1WH;mhii|z()kumeE1v^($T3le?r1fMZr&y zDB4kQpxT^wh2Jv;{EVi8Q?53-4x-a7wc3oEyosvK3u$SDTg+M*q*-kaknl;1;aK7W z$1sT^!eRvQ)@lTy)?(rNYh8ibRqdVF0XF^cN()i{V(j}O41X7wmWVfCmm zJWJK%dx5S5)uSW`sz*VlZuKbrOe)pmCxL2SJ$?$0SoO$`PG9vn3?G^@_OfrmB(EB? zI_r@hf)1)h?-_d)+-z2f-vp7N@}-??NbuLeDq&3{8M!D_I*SC{u)>DR4aQ)K4X14Cnr!Pk^;naBPEUw96Q zDq|}-)ZVD%=313J{Biv%{gh84$;VVh_q{VP7@nPc4B#H=& z5x`q(LcE}csSb4F4K=?8zBbL8Ff@GgTJRIVfLaTxF+|pY55qmO29#j{=H53%V9}`o z_lu`;_)AOKmxN$l4RRm$66THY^{=`Cn`$hhV&h9#f-ftdf&T`tzkaY#Q1_UwZUw5; zK%Wk%#$W2e7oJq~a(8r}cTxfMXaJNyU`1>{Mf|MnJD=iB!4gZbDg~Ed@1z3ihaC~@ zE8l`td1gwKZ_iA;@TX=oylnfV-~r5Qma3S40}KhO7)cORG5;HB+p_;Gply~P{9W>N zzk{JoPdxa$7zYhGooWHSw(qImS$@CdMMyRl6>Yy?vOkfu#1pHRz#ukJgHNpD1wH4E z-5ycBpNoSaPX-6Mjsd1MCLZ-wMqL;)D)fm}W&GNh@kpz9VioO#s($cR@x-cvpc_V^ zSmGKJ;sul5v`O&9s*EkE)csRHFt7WIc*N>{c69pc{%6_p+Xd|w|Cj3&{{bu=bO6qA zcO$pJoXs}CJ;K(c9mMB%_XkD2FF{ifUu;5vv-%fo$F_{>ez z4Ctq&5pFRT!yrg2{T37caO%@Z_@u>fEb+l&H;E#`Vg&HkW&mE$%NJvH;;GX-309qE zGax#W^Nzp-aG`btT7rmd3*_NO*%pv-0G#aYVitmyf3qg|Ql!9RsYYuBKYm1JMoC)( z8;)$a_S)g;>0!${nmv5@@aWN-Z@zg{zhPho)Ccd`2j1CTBGTSb2jVMEM#3ykO??8N> z0j6Dy=s+l=J{vPCv;(1x|5VI)q*Zhv&{(L#3vU%22nE6WU{rz*gg18*bRc9Hla;66 z|0Mjx#foRh4#Y}4VjT!}box3FJ6+E=a^#kFn-E(2)>{#E(1-B0w}pY7%~r%l(40CK z~+~m-jnb|VeUaN z>GULSCMhCIkRvcurzbH0=giXWOtXb3TN$ASvKAQIM2_$6WuQ0Nn+{1lNH-WU9-g;Z7`k<|gV%e1(=qxWzmQgEV^*KP2Ik z7Q?Z`M~z>SC?YII0B`L{-~}~mb)XY(sF?w%Y4#+@sLtCGeOLS25@Df4b|#jpbvJ7;=r2!$+Jyq|GIc2m5I zO1-`|06LJND9!^DanX!GdJ;7wS{S(LOof4`k+u*K zu}%v+I(?m%XP|?>9ky#Ef5JoSYoZc-2VE8K^NSv^+3cnKOqi8ad3;v{ZWBDaQzf;a*)|`)E z(&?6*Nm4`>N2_3{PPgPD5=BID#DQydOSaJPm4rCaGyWYt&Y>%tJv8m%Q$)x{r(1HE zgrACfb0ms3)En%URA~g7`c1i8!gUaxafG|6@R^&aTk;pQG{PpNw*)WfWr0a_az>^Ru8rj>%Tdx8M%hs*#}x3d4UC(O9Qd^D=nt z5`2g*V^=fPSb>9voX&KBQDiP# z4x5nZj#>^wiDV^O4g)YKwB>*o^nwk}B++uq;2_>|=x2aweTkNXGU}3;QK2mdW&FCB z@kp!K+<{g?m0x(P*xaEYSPG*Ov>d!ilc41w!;7px{T?Ub=8g&w%v%msJYp>ec69n$ z4o@#v^SN>x4TnuSPnB@s9kd%(xDS&^P_x-=xK9|ARCat)`O3hEz#I$DixCZF?P|t& zuZzGb&rP#I3Od+o=#dvd1CY1KpI&f&hf8YmO&K1W=GzfWI&FrJk`$3u#fM<1PMhH~ zB#MZtf&ZB}zbW+w+YGB{ z1e*Fyxy`^C@Q1pn3NmiyCTcSb(b5RFn9E?0W}9If37@nWjwL=K>?ct~I0ON_watJR z^fJHjprLqEPo3snFmckX>RX~Tujxxbyjs)CkRdC26RtyM$2MO`uMgGMJz`qxRP=ie z@bsmUn>Kg_54AlO@CK{b;dO!V7D2aQS3X1rb@=$WQ5n4Pu)4kCnH_*rD&N}zTJsm# z@KQa2Sk}|s?6(47CIBGdb`-CGnXxFH5i2Vnd|MNOw0LGfl+0HpE}3O=z_%k*1pS~w z-*yE03td=p`94NxHW3cbH|U{W7V_Feq7${!t8ftCp73foj9bGL+Y`hn+@7E?JWCyd z_W)f9Is}p+=nx1p=U{un!--@sIs^~lpdqIdlSB7Em;HfnBFR{YwEg;t?_Xobt0MkxJ^aqqtUx^tN+8S0X%+nev=ORe!dpdu zKtb?P7?q$u;7yqX{Q()QWXb4vuL=7D=Wdhzfwg$V`UC8!3aGspD%ML!o4{)Exy7x;1)R^~Au#`i&bh|Pnvu7?M>h*&6EkC&P&Dso znp=UJ;%>`1JM^wn0q!Xtgc~U9bb736qYlr677saV?0T+ZmD#JS#%lHI6uipH0wDcP zw_UY!<>vl*PM^HTD8SDKlHKJj&o}DuWT`zSV2wCSEk8wNhw0JvZgb4a0IDOm%vy4ZGH`n15OC8frRAI0<8b_ZZyJCw?~SW%#wsuI9%ka`|I;6-a^H#;(saoz;^7 zrc$*H<>GzLG7CgsX-touL}fMabCz51S=riHzB)Y(11$h}9zSE04R&ViM9D6WF*^ii z0G}9uhRIGWh4+J1>-JdLIBtMA=R1AHT-p8E?W{6RmOzT|_OON}Z|Zis3&uoa3g9h2 zk*ilgif~8W$W6nUUQkEgK$eicQUzo-29Nkw3vgG{>227PgU=YWN^pF>|Kb=dF;(kh zMaV(RMjbSOq~`6Wvw}-rfzr*OG@T0qR{|i+JeZ@ioWu2KkfYD{UkD6#Rw55zdsY!d zR0f`!&a!f@GS$dU86fcj{GM-An$8(@BVVl-#tKFaECDriUk|0=i0>2hqufSKK0L%(@h8a6*-#b;aj7Fh4c5)gG zY9-9Ps%4C+Bbk-JoIPH$O59{uj1@{&ZUP>`cBxz>rwUBFrNE2@lY`AW0J==vHinME zYK}IYv+xLzz>*)nRt|K7&yIH3ts?>R1ft z2QS%t1;}%GeKH>ak7qpamrC$3d2?v8$BcTmWZ07o2RMYGqCH&(WW9E70%lRcE;jcs zb}j&$oE~#0r~3f=7_9Muh1~C~v}WK{dnd<=rKw^W{s;SASgxY!FSVv~Fwt&nz^t}F zP-n2;{O1@i2$*Ou$QN@!4=A3`H;^(DA&frOr~!)QKplU6p1Xnj!FsBB_!2YNfba{e z(NTbx4crEwX9e3WS95$aE@UnVOlEq*D8LM!0p%7A!v-8@=HaemW5#r@R36I}3g~wk z)vFLPYTPo)lia7>?<_DX$D1RK31>0)CNO=$5=)gr3Fd}9X6Nct26!j1n^iy*Se6a$ z7GS_KFnaUin&MK+S?#-o5e2V-$7zbD z1Mue<{@I;{Kl|{{r}59{@eh8F-F!X%!Ef=I_`yE*odgpava zAjQOI0NLIK6E{Gxeu#;UEc1=X>TU3+;Ve_<1XoE7MzgV-yvvO%kT@F9xT?I!r7GlQ_i6-Xw)G3Todbh{9_;X4Xt zZtf787wr<~9{>|EZv#`>-(|hHc>_2b)<%8jC;VbfWoUR!@nKJ9clDGA^3jnePcrm7Uu?B3N;Ke>v-H!puR85Sz zEvt66nqPohsj}Yb7WqP#vk+$uIJtczdk~l2V0z Vf&yI96nclg0)|o%b$Bh3{lA;}N}K=y diff --git a/mddocs/doctrees/file_df/file_formats/orc.doctree b/mddocs/doctrees/file_df/file_formats/orc.doctree deleted file mode 100644 index 4f5d06fba9c043aebd259afad99ba35f23cd9a4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30977 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 diff --git a/mddocs/doctrees/file_df/file_formats/parquet.doctree b/mddocs/doctrees/file_df/file_formats/parquet.doctree deleted file mode 100644 index 393242cb0b2e970d3c304c851ae38b7ee4003f83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32899 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 e3l3^mTNPN>V11Mc diff --git a/mddocs/doctrees/file_df/file_formats/xml.doctree b/mddocs/doctrees/file_df/file_formats/xml.doctree deleted file mode 100644 index f93c3a6413224bca156be39349f9ab94a6bd2a56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 169345 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 OV-M{EgVH$g=pz7nF-F4x diff --git a/mddocs/doctrees/file_df/index.doctree b/mddocs/doctrees/file_df/index.doctree deleted file mode 100644 index 7970f744dc203eb5379a7103fc97c4fbfd36eac0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3871 zcmc&%ZI2tb5xzI+B%Mz8;6fN2sMvFtvX;b8zvEVK#k-6RF zhUEHmz!_)@w6Fnw3)`R3-`I!TUA@5x(0=Iw!tD-+!{N;H%*_6!_0O}Tt?*}$biqYF z9}`M*Ntt%GLuFECEH~~u_p`scSMDR*Rpds?QYFmUEodZMq*Sroy};tY>l=~kac$ro zUB=o71EI_#%BCV0{&8WCx9q>h~+?YaNOeP|mjWri6 zrFtynBqnsmCLf$X8P)sn7%#Zmw@4xk&GtwO8^?H25pYV?_cc8y( zyU~I`e7B5&rgjC(RfwDb|It*WY(%FcC8fEj;PkyWYT-tLI@@2}6ukT`PHy>=`ET%> z*b!izv57|RiJF+Kn1s7eJp3nqcSNT(<`H8TJnSSgD`c6|6(4y!kYNi6l8I4ll?wIR z_j$|D?cv%L;%`HSN37ETK|tcMVYGHa{q8|(YR5r^JwF3UC}Y9I<<|RL1U&ZF4~> ziryQ0Sesr5I(eDJC>YdTZrL7cAC8oHdJEkiYUlFq;k7xE=N>&@13h0Nhr7t(S8Hf{ zPCT&fQ62x$jUq+Clp`MN8%S6K( zRRQFn@Ei(d%zl7xHE;Qs_RJ`f>y(7z4+75=(^d6p8EHsbMX1&Z0V{nJw(TG(mFIR; z+q$y-MiSV6WjmS~Bk~!V)XAdhOqI+H19ENqYl23g7ZA2>S(>6WKvOHE1P|RA%b6lZ zD*qB{ks(=umjlm2R2<-C?(Fu&*@McF2&Z2|p;y0_D#hC&k`nnE=~v-U%jfvk+0!Y^ z?zSC+L{Bb2=DGQS5Mi?G77{qz?hPrSgIEsbl znX;D*LEW)Ejw&71uPuAR<^r)a#FTm|ZBZ7NGaNU#Au2~Kp#kn?kaUr_g!wCZ|3r?+ zMd*N%Ld)51X{MuJjI_Y>!}Z08DJ50JQQcFZ6Ga?%OALTp#ec<0D_C*%(Zu9*t-WVyFSk?#XkDdeeoMOi%Ylml$?8BmytOc?jY@lWlbV8ybC_{;RYBqs3P z^TNK{^jnDaxVSZ3=3}X5pwoS?861FSWLSpu7^I^E`RIcupCXOv4$d2@^lY;jpeDF(`zw&s}!xBLFgs7R+2!!AKV#A#d!lzReApM_kN! ziXXtcmr9TOzRn0n)Ne~rS|d>R!h7`^r7|H^bFq&SPT&s`Uy%)|3?ans>$1QReoPKk zsICF%B9MrD3Jlam;nY_N6xt27FiqktQZdobLe~zELC2w>?=3vwj2WD9KY`YD#4;k% z2ot?8y}|1fZy6&vu$oS*g7(JlvizmHDr4KJN(s0NBw`vC;D(8+p(WtvWOJhT1)ZPq zzP6`pOV#-J3e4CTmCv+4Wx81R1DYR3eRFx?_*0_ozF(|mcl^1wT`w@V1#SSfyVsh( z@74KN8q-BnWnsrJ+rvZv5&nt`iKlF%S6yROQt6?OBKKJ=(*-<&>e+^Hq1|1x*lkV? zn@-p4qx34{M~wTMw(1p33MeQE#x9@+?Iw|FTBQm7WdC=y{5#z4@9s|^2S#7e48yuc zEvpV()Rk2+CeDGr)nHg7U9?2rswX#m;=ml4VWQw~TiyFJoBIzyd;WVQKzYawD?kh% zdk!~a>*Jx&L$tdgqF#M4(J|chpN;(doA1h1lybB$u-p9YS;b(KCZ`GY` z+wOTxfS`Z4diLCRo9e?91+e}~XnC{uDtq=|5uyO^gL-lUoo4>)r$>Dos4J!)pv!pt EAM=stnE(I) diff --git a/mddocs/doctrees/hooks/design.doctree b/mddocs/doctrees/hooks/design.doctree deleted file mode 100644 index 6ac734b15e65fb78ecc7b6a5c80989ce8ee45892..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 87065 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 T#C=DKq^d}%PHKH~CinjV^C_+; diff --git a/mddocs/doctrees/hooks/global_state.doctree b/mddocs/doctrees/hooks/global_state.doctree deleted file mode 100644 index 769919e9e11634f103dfb61908e25bef42d77ac0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20165 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 diff --git a/mddocs/doctrees/hooks/hook.doctree b/mddocs/doctrees/hooks/hook.doctree deleted file mode 100644 index 9cdd61ac942b408cf063ed361644e72039694592..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53860 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 Lf*-=RPUZhEC%Awn diff --git a/mddocs/doctrees/hooks/index.doctree b/mddocs/doctrees/hooks/index.doctree deleted file mode 100644 index 6a796fe99c0b0a6700914b3cb1c5fc92211a3770..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4859 zcmc&&ZEsvh6}DsVdUw5kjnfp7RI7*+9-g^WjC76fq;aAi#+Nc=qFv9=%Bul&kBw!5a8u}3P&GoM?t01aQr zFvxgpPcXSo`dWsnTW)Yu<$a~4hA@e}fDdFW>FanuQ8OQQyPbDbo~B8rJ&`15>Ya{^ z*pMF_bcEKaI_ULeH0&xNc^Ig!OnQA540-R>d;5FkI(&62_xfSZTOIrzXqFARo@|lC zT!-C^vp@?DJKaV+?(KK)e!crf&n@rCIN+nM&{5d3V&%k&CLHDi4hP|I6$BW)He+z5 zTXS|#>%IH`_rw`zT1@Lq_H&JUrF&eGfx?}c6+dK3L5T0Qm_^06Mv$kIL7(|&h=S8u zVxhKT$1LTUwC0Wf>4L0j`791(zzS>3;`glhs>;oZWGiKV;`G}Q}OPD7*x{1>=CC~$P8D>b+x1Kg7X ztF!C-IIBiyiMR0Snb!g2i1!q3n`J#6r9J0uj{rY|am3U*i&hl(=TcJC3H!S+w}#ve zXMkUOKp9#Ae);EoMC4#f@q5;>1PuB`VbGDddvs~o%Il$+J%swt3U%=|{8@%w3ufb- zX9`piC4n4B9!!Vq=37C)gN}?lrBY|V`_1ltw=$~ynvp%lq87+#j=yWQQD=*w&Szn}J z@pc)*!Vt4k4soY0s19agYSTJhmxE5jPogx*%~?x^R#690i{G|7$$udc9aIIL0BjkLaz|MZ@Fp9*bj56gkj;B z__O${_`CS0_=uZE#6|&6wXlbaU}l!O#H^CbEYoiGnW+m2(8Tz*R_-+t_>=gH_?!5L_!oCm)oJ2mLelTSW0Z>xT=wtgQDipCyWM+) z97}m-**ttdB#=x%0}kM0iry8+RDSLP^d(tWjOM6xG5~#(EkD6tCBGH?0DG&N9N4Gk z3w)GtA@Tjy$&Fxwx;)%}Z6lrBFDA!nApnQ5CI^E0K8H>{K~N?F>rNFypGI zr!}2u78X%2^U_^HP97|pbwAIjT6twvJ2LG`;yCBXG!)lb#zVASTjMrrkR`F^NH9}> z$k5uTF`t=wlIt|r_DDQDGR+~6d4YHgTwT>HO6`$Z&60$wCjd9HX0d0@YGHzhbI+jA zDu;O%Vzh>v^{C#{EKI2pVlDvZ0k(+~&8H@xq6&3nk8>-c}W+&zrn@#coe6IvT6v8KK z)%^Og%r$B; z)`C1ry)h&aSSOG`V-~ZJHr#L$mk@OO6H%&o9wgo6H#8NZ50!B#|`s z@&c)-j=_=A%9Scrkalf2gJ(p{93KKTrJ7fxq2I9G%4g9(7Nix~~wQ74LFC)mF@vJMk9=J0=2t9h0;IJm@J+cU| z7<S?#9K? z3lpCe1IHl7PA|r}q4*60gwn?POiKzqyng_IsGo#mj5yVuIpJ8lIkRK4I@K-Op4lH1oN4M)Zp`mh&Y=GQtN7>- z0UeYbKM6xbA3mV2to)#qe*o`Jx50~ti|Rf5Q)CKycjO!%q6)-D2jd;Gt}Ybvs*;YY zps1BSWm5b+ftUI2{X7k&j|9Z82=q@f@adC3xFw%H z`D53P8~f7`Mql0A&o@M9%}*s(%=c2$dg2piYGtKcG-Z3f{G+2E9#i!xeslpFmM`FC zbQY&WGGeCHqURGc+hbm?Vn&fX6;Q7tH6OWhd}3aI0AyyAi&fytkaeW$pa*=Ut70)P(< K6Q0U>zxy91>{L$x diff --git a/mddocs/doctrees/hooks/slot.doctree b/mddocs/doctrees/hooks/slot.doctree deleted file mode 100644 index 19450e33136179cb0f38891d3df11fe15c052593..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44170 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 diff --git a/mddocs/doctrees/hooks/support_hooks.doctree b/mddocs/doctrees/hooks/support_hooks.doctree deleted file mode 100644 index e0766fecb46181ae9e176cc7c163357c31fb60b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27957 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 diff --git a/mddocs/doctrees/hwm_store/index.doctree b/mddocs/doctrees/hwm_store/index.doctree deleted file mode 100644 index f61a0ca2c81293589c0ea385aaf2332e283e3ba9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6473 zcmc&&%a0sK8Q<6J?2PyAwS$RmHCAHQEAPzO!6I5&MkEe0TCxtF%cf7!(#{oT2(Z6XltdC?8MPaOL5r+Hn zBg^ZvF5lj^gi?{*ZZ^Gsw;_eceOETTu-RsAmp9+MxwV;}!=pL5+4d7&xA1RUvAE0C zkVF`8Z6{G?| zJU7+T*lT!t2lrIy9`RlLnz{)P_jyyo(kyPOe$+IQHVN!a9I~YfW1eFiI!z5nRmq(( zmlU&v{dKTgf$#bwz@NVN;rj*LXCCq+3JuyUew>;J0fv5@F|;dg?4GEy+%%-GN7()# z+b%wUJTr!;8cfT&F^fc5QBNLt@IosL-h221>i~Yc)!4dj!JDmC3tT-Q=~^ud$jTP* zoP*mE?0{Qs&I79-9&p!Ep~Wo8=^Di?-)qM#KDM?IAvOUigeWezTje+F_MKM4$h|K1 z^o3J7NrzhKm9+p}R(((LnE7Rq=F6OCYTeUwg)k*zs!Z&3w3A9>sI&()?&*rrMTyEN zOEL@Th-Ov8MypkDFlNtBzRKmzS5*F~Y_1I0oG)9X;B|CysQ%xc&a_?wZV^WIH%ADv zk+?DE1u$0tdp#S*5Tu4IA(aoFkW{2%ZJ##>p(C@$WaBg;`Kj@XEGiIG$T?;UXMuIS zfmS|z2oDGY|2R{)b;zXU^NzA00CVu4w6+mCwp+)n?{6XwY??UGx^8W391717v;(P` zCBE9&z^Tl`hG@U?;-V zdwOwHkkZ|MDc?QnCF^>|FrcTULJ+Ou?3nS{jq7ba`#;@=@fMjihHxd8MBtHVkpsURqT}bTh)k zJns%d()CwScqU*`_Q0t!6T!DkoJv_y(w+2ayvpl+9l9Woc}LgD+f3o^raP13X2Is3 zF2~{LEcbG-te0R}Ll+eGqc92FlEY<16arvXg^r4Oxr)la6*3nB7KJ2e|4ml`=oobf z3iDn4wPB>8;78?RR?VfKq)gAFF7gxVu6(n<_a2nM^jx1Exja&Ws^zM~B2(t=>Y1(| zwkfL#&vntcJ=F_2fqdbao(qzG8_I*`CUo5tfs#@TPSw!>4q1b$dv6o`vJ5p3HQyo& zj8)`%DObYgA*#BF$FORtNGB?95tWf*5XIxr80+Fb^(;3Ku=5H(D}+t;T|A=RC)@uJ zbn>D&D=K^IApTTeK=4JeYY%~8w~uY(5s%Z}Plzx+Q*j>~%3Kw^6qXxw zE#d>creYRI-vlLUU%D}uS?^U*p+a3@RGaTHnofxMNxkSKF?BX}-U03E`C`SS#9cin zxl+ifXwg@w!!Rv(2N7Q(*P`Wl#Lx!I0T}gcn5ZaG>8^ORt7}~zpbkZ~3}Dqv52jc@ z!poUB45`xskd9J4*+j<5Sg@&B!iIYOn#4Zd7GP7GdPDjORw@LG<5aJ9Kqd$kFMR~Y zsLueb?4wQ?_W`;^FQ=6=>YW`xHE1pN+W_#x5o^RHfk>3OKuZ z)(L|S#DqpXw*Z8g)r%}qp*>vEQ`C?yxJf^<2atqf%|cN>6Tv03VjP03KU|6=Pu$Qx z>Jy-r;d!CtcKM`134HdzlU@edCEN8RmDdm?=dxP33fyiU!eHbS7{3G2g?yV`L`Qv| zo^XleF-1h!hN!tP`5DBv!RyYXULk)2a)K^^=zD#XZSOCCQ!hq58U_&(21RZJw1anY z4TS5wO~A5jM8`^mK_iU2@ThaN(=dRYw&JK=(1K!+hV{)ix4s2^F2)^aj6AIj$zLtV z!+9A&cAv)$kE@Qk0|B8)6D(X-LmL7w!o(}xnbVixZ+&~HQ?a z6*M6<=_C&W&w!rL@q!L8kjI5p3KWR4W82Ii4R^838EFi(p8_(-Q!YIC7#s-Bsjfcf zVAsCQQHJ|AhM5$14c9CAr~_E_J6TM7rl)yukRsLSxh$H%eSt&_0zLSKva_n(jJ{UJ zhnW=3#hRFxdUfPeHaU9?&lsB$u1UJ3Gx$!kDVoDkX2H1XE|a=SAG5XfEG6CVy@62B z`v(3bx<;R=r!bksY=)G=^EtIWJNA&`FkSsNUhirwM zyV#crTwWnWnpL2r-wyo&kj&!R*y3P!ZG>X2xYZn59ifjn9V=97`rJ6J{0k}(gdPWV z7Vw4Kz_ip-e-_{XHtHg{s``bDGwYIlTC@}M;BulvnY=xoUd3N zq)JiAYqMd0ef4faEgKDrsMDb4&B{<=kukQdE2ze3OGX2y6gGc^+^v;Iwn9V@fSHI+dD)0I3MPU@Mg6`lG1?)UCfhRTNch?x0Ab%v_2 ze-a-fRTz!rm|h*k?m}gx$lkxOy{P)f6_>y7>1+3Z*O>gZ(3{a1HEO65EZ{0H(+h<_ zy7N;L6Y>SpQKdv$IE#HR(!z7{_ZD&D1nDb<^d#+>fwU$XoXN{$M4v9r$?Gm<%{;-7 I)slAOzYW2R5C8xG diff --git a/mddocs/doctrees/hwm_store/yaml_hwm_store.doctree b/mddocs/doctrees/hwm_store/yaml_hwm_store.doctree deleted file mode 100644 index e1a4df108b70e1bf89a78d3a78db018c7888cf2b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32404 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 diff --git a/mddocs/doctrees/index.doctree b/mddocs/doctrees/index.doctree deleted file mode 100644 index 318503c88efa4179f282cd9a3212f2727c531b46..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42083 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 diff --git a/mddocs/doctrees/install/index.doctree b/mddocs/doctrees/install/index.doctree deleted file mode 100644 index a7362f14f7ab6b55d2d11f42e7683027177d0971..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8296 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 diff --git a/mddocs/doctrees/install/kerberos.doctree b/mddocs/doctrees/install/kerberos.doctree deleted file mode 100644 index 21f494dcb3c1f4e625ac17efc1e84f71f73d6adc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9162 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 diff --git a/mddocs/doctrees/install/spark.doctree b/mddocs/doctrees/install/spark.doctree deleted file mode 100644 index ffd4e082d5d250a204e0563607e0c143bf7e0a71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 68759 zcmeHw3y>UVou4J`uC%MSEXl?%+ACwPgl0zCwJaedgDhD{mi0hugTO4ay)(T#-8(Zq zp6-!$!24gM(CsO0IGjj>jEIxGF9rRafA;B&06BOSo{kqV94W zuUyFQ|Nr{y{<>$Tr)PF08)pq3@AQ0+|M!1=|L_0!`q{yc58krs7W&8A?9@tTbs?)4 zi&eX5I8AR|(JnM9M%8V8thxJr&8y8ZZ@8{saqLFDU^Km3(4tT>%f-4;ZBF6GP1N2s z%TCrG;5Mf*u$be+w;ydnpx53jKhbu zlIzx-!?~PUnaesQ(z*XIm39Kx;| zZaG^w^rBlb04EP*RCD{X`zNvoa@;*_k-r7Q0cN{##--A`XBK}%eY}CRp zGkRejSl~8m^s?J5ZTHqR>SohB&=-N4-bg{O7R{nApz&6{p;_AFG`zL0K1b6wOFO+G z$z>ak)EzfptJ`IJ?%}34P&TVZ^UP{*sHTG~0A^cbMlaokMV0OW#Wegoihu9JzxQKN zLx7xNJt$%AkrSGYQns|EG+x@!cAo9JoQm3wlyaqgrTu4Cqx~9hI3Ea5X-#7W+%zQE zQe}~C;Qzc?bzHq%&Nx`q{9(aQ9hWqSC7H#hw=vR-;d}&h57OMFWB51cWx&`KBjYB^qgjsyVQ&XeYN(k!stWEj^vMjAYBq5oh$7>*A|gJ!3c|QLGww`ymSF88eq7FF^@i1*J~veeP20NE^8+q z(F%68D&W?9me$6mr;nG-!hFeYIL7q!UTu2%O?AVl*2;~F_Gqh>=b)ScZU z^fxG>oixkFiIW|MVTc)h{#e0SuBuZ z%3HlxQ-+AbFFrv%Go!lj}-uZcbNfL?*l-BBIjPqb5Bkv z(|)U;X|=AB*FmU^j(Z8oKnY59{pPMm#|H72yv8mX-y8W$z=OO5$f`M}$JTBuvI zI;TCRU)JSAEv-1bQm>b<aE7|-^*)Ghhs`m4Xjf%vWW`U}i{nBRxfz(YL0>u7{{0zDNt2y0 zW=y@Bnb94i7?kWJsYy4|QL(j#>wteApbc4|hU<;RY?7$;A= ze8t3Ju6gERBHIZ9Xxuu6|1@T-NAVv?Egyo8=Cm@>#+TtpYg=GqGrWY8Y%98ky)||F zO3QP$+@XQ$i!DDXyM$`VPqi5E*42RIX_y2`RdU8w(B)lBmqh#0TA&HWtp+!>7V)1L z3BwI-=)$5~f**ko=~JHk8gg`##!Ai6FG<5n%js8!KaXa<&@8N1jj2xFFuhEa|iOXkAYPmgCO zvJ2V~=~`f|p4G9@l#HxhpA+Y7YP3JMt7u=TmTkQV1MuPLJhSdtX{(&j`T>DSJ|~DC z4;xy*)?d+Yskqr2UlH@n_ptsFlS8+MyWd0~3l@?pb+ET;p{Yw(jf6FD zqy#`)t5#!bSly3!I7a(!xMK6nDo|cPmB=JytVzsF6JslRR_dl1HO zc~NSi@Ifduuf__}QI6Ib1NbgleuB+IJAIVQ5>-8Ba_da$eJ_)EfzuQ_;FMRWWhHO>OfA%;k$Ia=WhG{;{szI)Let%J4E z9OJ6y5RkUf9RHUlk7^D9Pa;uO&2bw9@y~e)sWiub=cdV;V}xvuj0SshpWGYON1;I8 zxu#F;K|tS*&MEDVR6rkUjPKhIu)_^~M0pefJ-!s6bIS`fX_0Y>WAP;f^z>2yJ=rUu zi432QkqDNP>LA_AU7qN-ZtP6*_m@e)#L zk^ja`mr0ADCJSO#b=iS^lO(F=tM2@=u!ZE zq*p)_8E!eW$bmE`nght#>*i0LBfJD!1j8y@gqa#=iWZ?EQraSM_yR3rFur@!BJsi6 z*dhm_T7)2NV~Z@%vl9prJAj(upRjMdYIp=r1e<=+7-L(1}{)>q`OjYrO)R$Z%0DlHBw~cwIqW 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 D0Mw_X diff --git a/mddocs/doctrees/logging.doctree b/mddocs/doctrees/logging.doctree deleted file mode 100644 index 06b8f98fee380b1a556015f6a6d89dffcdd75147..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54923 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 DGDgbD diff --git a/mddocs/doctrees/quickstart.doctree b/mddocs/doctrees/quickstart.doctree deleted file mode 100644 index 9c8f40a31d202e0d73429222e993786d20266f2b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24493 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 diff --git a/mddocs/doctrees/strategy/incremental_batch_strategy.doctree b/mddocs/doctrees/strategy/incremental_batch_strategy.doctree deleted file mode 100644 index d1f993805f8523380e2fff534f718b3ccecf3d27..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52892 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 diff --git a/mddocs/doctrees/strategy/snapshot_batch_strategy.doctree b/mddocs/doctrees/strategy/snapshot_batch_strategy.doctree deleted file mode 100644 index 00e99d34010bfc50642fd69f4564d9a38878a99e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47889 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 diff --git a/mddocs/doctrees/strategy/snapshot_strategy.doctree b/mddocs/doctrees/strategy/snapshot_strategy.doctree deleted file mode 100644 index 7f4b0136f610d71587eca6e37886d5526c58dee2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16570 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 D;bKjA diff --git a/mddocs/markdown/_sphinx_design_static/design-tabs.js b/mddocs/markdown/_sphinx_design_static/design-tabs.js deleted file mode 100644 index b25bd6a4f..000000000 --- a/mddocs/markdown/_sphinx_design_static/design-tabs.js +++ /dev/null @@ -1,101 +0,0 @@ -// @ts-check - -// Extra JS capability for selected tabs to be synced -// The selection is stored in local storage so that it persists across page loads. - -/** - * @type {Record} - */ -let sd_id_to_elements = {}; -const storageKeyPrefix = "sphinx-design-tab-id-"; - -/** - * Create a key for a tab element. - * @param {HTMLElement} el - The tab element. - * @returns {[string, string, string] | null} - The key. - * - */ -function create_key(el) { - let syncId = el.getAttribute("data-sync-id"); - let syncGroup = el.getAttribute("data-sync-group"); - if (!syncId || !syncGroup) return null; - return [syncGroup, syncId, syncGroup + "--" + syncId]; -} - -/** - * Initialize the tab selection. - * - */ -function ready() { - // Find all tabs with sync data - - /** @type {string[]} */ - let groups = []; - - document.querySelectorAll(".sd-tab-label").forEach((label) => { - if (label instanceof HTMLElement) { - let data = create_key(label); - if (data) { - let [group, id, key] = data; - - // add click event listener - // @ts-ignore - label.onclick = onSDLabelClick; - - // store map of key to elements - if (!sd_id_to_elements[key]) { - sd_id_to_elements[key] = []; - } - sd_id_to_elements[key].push(label); - - if (groups.indexOf(group) === -1) { - groups.push(group); - // Check if a specific tab has been selected via URL parameter - const tabParam = new URLSearchParams(window.location.search).get( - group - ); - if (tabParam) { - console.log( - "sphinx-design: Selecting tab id for group '" + - group + - "' from URL parameter: " + - tabParam - ); - window.sessionStorage.setItem(storageKeyPrefix + group, tabParam); - } - } - - // Check is a specific tab has been selected previously - let previousId = window.sessionStorage.getItem( - storageKeyPrefix + group - ); - if (previousId === id) { - // console.log( - // "sphinx-design: Selecting tab from session storage: " + id - // ); - // @ts-ignore - label.previousElementSibling.checked = true; - } - } - } - }); -} - -/** - * Activate other tabs with the same sync id. - * - * @this {HTMLElement} - The element that was clicked. - */ -function onSDLabelClick() { - let data = create_key(this); - if (!data) return; - let [group, id, key] = data; - for (const label of sd_id_to_elements[key]) { - if (label === this) continue; - // @ts-ignore - label.previousElementSibling.checked = true; - } - window.sessionStorage.setItem(storageKeyPrefix + group, id); -} - -document.addEventListener("DOMContentLoaded", ready, false); diff --git a/mddocs/markdown/_sphinx_design_static/sphinx-design.min.css b/mddocs/markdown/_sphinx_design_static/sphinx-design.min.css deleted file mode 100644 index 860c36da0..000000000 --- a/mddocs/markdown/_sphinx_design_static/sphinx-design.min.css +++ /dev/null @@ -1 +0,0 @@ -.sd-bg-primary{background-color:var(--sd-color-primary) !important}.sd-bg-text-primary{color:var(--sd-color-primary-text) !important}button.sd-bg-primary:focus,button.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}a.sd-bg-primary:focus,a.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}.sd-bg-secondary{background-color:var(--sd-color-secondary) !important}.sd-bg-text-secondary{color:var(--sd-color-secondary-text) !important}button.sd-bg-secondary:focus,button.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}a.sd-bg-secondary:focus,a.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}.sd-bg-success{background-color:var(--sd-color-success) !important}.sd-bg-text-success{color:var(--sd-color-success-text) !important}button.sd-bg-success:focus,button.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}a.sd-bg-success:focus,a.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}.sd-bg-info{background-color:var(--sd-color-info) !important}.sd-bg-text-info{color:var(--sd-color-info-text) !important}button.sd-bg-info:focus,button.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}a.sd-bg-info:focus,a.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}.sd-bg-warning{background-color:var(--sd-color-warning) !important}.sd-bg-text-warning{color:var(--sd-color-warning-text) !important}button.sd-bg-warning:focus,button.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}a.sd-bg-warning:focus,a.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}.sd-bg-danger{background-color:var(--sd-color-danger) !important}.sd-bg-text-danger{color:var(--sd-color-danger-text) !important}button.sd-bg-danger:focus,button.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}a.sd-bg-danger:focus,a.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}.sd-bg-light{background-color:var(--sd-color-light) !important}.sd-bg-text-light{color:var(--sd-color-light-text) !important}button.sd-bg-light:focus,button.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}a.sd-bg-light:focus,a.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}.sd-bg-muted{background-color:var(--sd-color-muted) !important}.sd-bg-text-muted{color:var(--sd-color-muted-text) !important}button.sd-bg-muted:focus,button.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}a.sd-bg-muted:focus,a.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}.sd-bg-dark{background-color:var(--sd-color-dark) !important}.sd-bg-text-dark{color:var(--sd-color-dark-text) !important}button.sd-bg-dark:focus,button.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}a.sd-bg-dark:focus,a.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}.sd-bg-black{background-color:var(--sd-color-black) !important}.sd-bg-text-black{color:var(--sd-color-black-text) !important}button.sd-bg-black:focus,button.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}a.sd-bg-black:focus,a.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}.sd-bg-white{background-color:var(--sd-color-white) !important}.sd-bg-text-white{color:var(--sd-color-white-text) !important}button.sd-bg-white:focus,button.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}a.sd-bg-white:focus,a.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}.sd-text-primary,.sd-text-primary>p{color:var(--sd-color-primary) !important}a.sd-text-primary:focus,a.sd-text-primary:hover{color:var(--sd-color-primary-highlight) !important}.sd-text-secondary,.sd-text-secondary>p{color:var(--sd-color-secondary) !important}a.sd-text-secondary:focus,a.sd-text-secondary:hover{color:var(--sd-color-secondary-highlight) !important}.sd-text-success,.sd-text-success>p{color:var(--sd-color-success) !important}a.sd-text-success:focus,a.sd-text-success:hover{color:var(--sd-color-success-highlight) !important}.sd-text-info,.sd-text-info>p{color:var(--sd-color-info) !important}a.sd-text-info:focus,a.sd-text-info:hover{color:var(--sd-color-info-highlight) !important}.sd-text-warning,.sd-text-warning>p{color:var(--sd-color-warning) !important}a.sd-text-warning:focus,a.sd-text-warning:hover{color:var(--sd-color-warning-highlight) !important}.sd-text-danger,.sd-text-danger>p{color:var(--sd-color-danger) !important}a.sd-text-danger:focus,a.sd-text-danger:hover{color:var(--sd-color-danger-highlight) !important}.sd-text-light,.sd-text-light>p{color:var(--sd-color-light) !important}a.sd-text-light:focus,a.sd-text-light:hover{color:var(--sd-color-light-highlight) !important}.sd-text-muted,.sd-text-muted>p{color:var(--sd-color-muted) !important}a.sd-text-muted:focus,a.sd-text-muted:hover{color:var(--sd-color-muted-highlight) !important}.sd-text-dark,.sd-text-dark>p{color:var(--sd-color-dark) !important}a.sd-text-dark:focus,a.sd-text-dark:hover{color:var(--sd-color-dark-highlight) !important}.sd-text-black,.sd-text-black>p{color:var(--sd-color-black) !important}a.sd-text-black:focus,a.sd-text-black:hover{color:var(--sd-color-black-highlight) !important}.sd-text-white,.sd-text-white>p{color:var(--sd-color-white) !important}a.sd-text-white:focus,a.sd-text-white:hover{color:var(--sd-color-white-highlight) !important}.sd-outline-primary{border-color:var(--sd-color-primary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-primary:focus,a.sd-outline-primary:hover{border-color:var(--sd-color-primary-highlight) !important}.sd-outline-secondary{border-color:var(--sd-color-secondary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-secondary:focus,a.sd-outline-secondary:hover{border-color:var(--sd-color-secondary-highlight) !important}.sd-outline-success{border-color:var(--sd-color-success) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-success:focus,a.sd-outline-success:hover{border-color:var(--sd-color-success-highlight) !important}.sd-outline-info{border-color:var(--sd-color-info) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-info:focus,a.sd-outline-info:hover{border-color:var(--sd-color-info-highlight) !important}.sd-outline-warning{border-color:var(--sd-color-warning) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-warning:focus,a.sd-outline-warning:hover{border-color:var(--sd-color-warning-highlight) !important}.sd-outline-danger{border-color:var(--sd-color-danger) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-danger:focus,a.sd-outline-danger:hover{border-color:var(--sd-color-danger-highlight) !important}.sd-outline-light{border-color:var(--sd-color-light) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-light:focus,a.sd-outline-light:hover{border-color:var(--sd-color-light-highlight) !important}.sd-outline-muted{border-color:var(--sd-color-muted) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-muted:focus,a.sd-outline-muted:hover{border-color:var(--sd-color-muted-highlight) !important}.sd-outline-dark{border-color:var(--sd-color-dark) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-dark:focus,a.sd-outline-dark:hover{border-color:var(--sd-color-dark-highlight) !important}.sd-outline-black{border-color:var(--sd-color-black) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-black:focus,a.sd-outline-black:hover{border-color:var(--sd-color-black-highlight) !important}.sd-outline-white{border-color:var(--sd-color-white) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-white:focus,a.sd-outline-white:hover{border-color:var(--sd-color-white-highlight) !important}.sd-bg-transparent{background-color:transparent !important}.sd-outline-transparent{border-color:transparent !important}.sd-text-transparent{color:transparent !important}.sd-p-0{padding:0 !important}.sd-pt-0,.sd-py-0{padding-top:0 !important}.sd-pr-0,.sd-px-0{padding-right:0 !important}.sd-pb-0,.sd-py-0{padding-bottom:0 !important}.sd-pl-0,.sd-px-0{padding-left:0 !important}.sd-p-1{padding:.25rem !important}.sd-pt-1,.sd-py-1{padding-top:.25rem !important}.sd-pr-1,.sd-px-1{padding-right:.25rem !important}.sd-pb-1,.sd-py-1{padding-bottom:.25rem !important}.sd-pl-1,.sd-px-1{padding-left:.25rem !important}.sd-p-2{padding:.5rem !important}.sd-pt-2,.sd-py-2{padding-top:.5rem !important}.sd-pr-2,.sd-px-2{padding-right:.5rem !important}.sd-pb-2,.sd-py-2{padding-bottom:.5rem !important}.sd-pl-2,.sd-px-2{padding-left:.5rem !important}.sd-p-3{padding:1rem !important}.sd-pt-3,.sd-py-3{padding-top:1rem !important}.sd-pr-3,.sd-px-3{padding-right:1rem !important}.sd-pb-3,.sd-py-3{padding-bottom:1rem !important}.sd-pl-3,.sd-px-3{padding-left:1rem !important}.sd-p-4{padding:1.5rem !important}.sd-pt-4,.sd-py-4{padding-top:1.5rem !important}.sd-pr-4,.sd-px-4{padding-right:1.5rem !important}.sd-pb-4,.sd-py-4{padding-bottom:1.5rem !important}.sd-pl-4,.sd-px-4{padding-left:1.5rem !important}.sd-p-5{padding:3rem !important}.sd-pt-5,.sd-py-5{padding-top:3rem !important}.sd-pr-5,.sd-px-5{padding-right:3rem !important}.sd-pb-5,.sd-py-5{padding-bottom:3rem !important}.sd-pl-5,.sd-px-5{padding-left:3rem !important}.sd-m-auto{margin:auto !important}.sd-mt-auto,.sd-my-auto{margin-top:auto !important}.sd-mr-auto,.sd-mx-auto{margin-right:auto !important}.sd-mb-auto,.sd-my-auto{margin-bottom:auto !important}.sd-ml-auto,.sd-mx-auto{margin-left:auto !important}.sd-m-0{margin:0 !important}.sd-mt-0,.sd-my-0{margin-top:0 !important}.sd-mr-0,.sd-mx-0{margin-right:0 !important}.sd-mb-0,.sd-my-0{margin-bottom:0 !important}.sd-ml-0,.sd-mx-0{margin-left:0 !important}.sd-m-1{margin:.25rem !important}.sd-mt-1,.sd-my-1{margin-top:.25rem !important}.sd-mr-1,.sd-mx-1{margin-right:.25rem !important}.sd-mb-1,.sd-my-1{margin-bottom:.25rem !important}.sd-ml-1,.sd-mx-1{margin-left:.25rem !important}.sd-m-2{margin:.5rem !important}.sd-mt-2,.sd-my-2{margin-top:.5rem !important}.sd-mr-2,.sd-mx-2{margin-right:.5rem !important}.sd-mb-2,.sd-my-2{margin-bottom:.5rem !important}.sd-ml-2,.sd-mx-2{margin-left:.5rem !important}.sd-m-3{margin:1rem !important}.sd-mt-3,.sd-my-3{margin-top:1rem !important}.sd-mr-3,.sd-mx-3{margin-right:1rem !important}.sd-mb-3,.sd-my-3{margin-bottom:1rem !important}.sd-ml-3,.sd-mx-3{margin-left:1rem !important}.sd-m-4{margin:1.5rem !important}.sd-mt-4,.sd-my-4{margin-top:1.5rem !important}.sd-mr-4,.sd-mx-4{margin-right:1.5rem !important}.sd-mb-4,.sd-my-4{margin-bottom:1.5rem !important}.sd-ml-4,.sd-mx-4{margin-left:1.5rem !important}.sd-m-5{margin:3rem !important}.sd-mt-5,.sd-my-5{margin-top:3rem !important}.sd-mr-5,.sd-mx-5{margin-right:3rem !important}.sd-mb-5,.sd-my-5{margin-bottom:3rem !important}.sd-ml-5,.sd-mx-5{margin-left:3rem !important}.sd-w-25{width:25% !important}.sd-w-50{width:50% !important}.sd-w-75{width:75% !important}.sd-w-100{width:100% !important}.sd-w-auto{width:auto !important}.sd-h-25{height:25% !important}.sd-h-50{height:50% !important}.sd-h-75{height:75% !important}.sd-h-100{height:100% !important}.sd-h-auto{height:auto !important}.sd-d-none{display:none !important}.sd-d-inline{display:inline !important}.sd-d-inline-block{display:inline-block !important}.sd-d-block{display:block !important}.sd-d-grid{display:grid !important}.sd-d-flex-row{display:-ms-flexbox !important;display:flex !important;flex-direction:row !important}.sd-d-flex-column{display:-ms-flexbox !important;display:flex !important;flex-direction:column !important}.sd-d-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}@media(min-width: 576px){.sd-d-sm-none{display:none !important}.sd-d-sm-inline{display:inline !important}.sd-d-sm-inline-block{display:inline-block !important}.sd-d-sm-block{display:block !important}.sd-d-sm-grid{display:grid !important}.sd-d-sm-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-sm-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 768px){.sd-d-md-none{display:none !important}.sd-d-md-inline{display:inline !important}.sd-d-md-inline-block{display:inline-block !important}.sd-d-md-block{display:block !important}.sd-d-md-grid{display:grid !important}.sd-d-md-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-md-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 992px){.sd-d-lg-none{display:none !important}.sd-d-lg-inline{display:inline !important}.sd-d-lg-inline-block{display:inline-block !important}.sd-d-lg-block{display:block !important}.sd-d-lg-grid{display:grid !important}.sd-d-lg-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-lg-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 1200px){.sd-d-xl-none{display:none !important}.sd-d-xl-inline{display:inline !important}.sd-d-xl-inline-block{display:inline-block !important}.sd-d-xl-block{display:block !important}.sd-d-xl-grid{display:grid !important}.sd-d-xl-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-xl-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}.sd-align-major-start{justify-content:flex-start !important}.sd-align-major-end{justify-content:flex-end !important}.sd-align-major-center{justify-content:center !important}.sd-align-major-justify{justify-content:space-between !important}.sd-align-major-spaced{justify-content:space-evenly !important}.sd-align-minor-start{align-items:flex-start !important}.sd-align-minor-end{align-items:flex-end !important}.sd-align-minor-center{align-items:center !important}.sd-align-minor-stretch{align-items:stretch !important}.sd-text-justify{text-align:justify !important}.sd-text-left{text-align:left !important}.sd-text-right{text-align:right !important}.sd-text-center{text-align:center !important}.sd-font-weight-light{font-weight:300 !important}.sd-font-weight-lighter{font-weight:lighter !important}.sd-font-weight-normal{font-weight:400 !important}.sd-font-weight-bold{font-weight:700 !important}.sd-font-weight-bolder{font-weight:bolder !important}.sd-font-italic{font-style:italic !important}.sd-text-decoration-none{text-decoration:none !important}.sd-text-lowercase{text-transform:lowercase !important}.sd-text-uppercase{text-transform:uppercase !important}.sd-text-capitalize{text-transform:capitalize !important}.sd-text-wrap{white-space:normal !important}.sd-text-nowrap{white-space:nowrap !important}.sd-text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sd-fs-1,.sd-fs-1>p{font-size:calc(1.375rem + 1.5vw) !important;line-height:unset !important}.sd-fs-2,.sd-fs-2>p{font-size:calc(1.325rem + 0.9vw) !important;line-height:unset !important}.sd-fs-3,.sd-fs-3>p{font-size:calc(1.3rem + 0.6vw) !important;line-height:unset !important}.sd-fs-4,.sd-fs-4>p{font-size:calc(1.275rem + 0.3vw) !important;line-height:unset !important}.sd-fs-5,.sd-fs-5>p{font-size:1.25rem !important;line-height:unset !important}.sd-fs-6,.sd-fs-6>p{font-size:1rem !important;line-height:unset !important}.sd-border-0{border:0 solid !important}.sd-border-top-0{border-top:0 solid !important}.sd-border-bottom-0{border-bottom:0 solid !important}.sd-border-right-0{border-right:0 solid !important}.sd-border-left-0{border-left:0 solid !important}.sd-border-1{border:1px solid !important}.sd-border-top-1{border-top:1px solid !important}.sd-border-bottom-1{border-bottom:1px solid !important}.sd-border-right-1{border-right:1px solid !important}.sd-border-left-1{border-left:1px solid !important}.sd-border-2{border:2px solid !important}.sd-border-top-2{border-top:2px solid !important}.sd-border-bottom-2{border-bottom:2px solid !important}.sd-border-right-2{border-right:2px solid !important}.sd-border-left-2{border-left:2px solid !important}.sd-border-3{border:3px solid !important}.sd-border-top-3{border-top:3px solid !important}.sd-border-bottom-3{border-bottom:3px solid !important}.sd-border-right-3{border-right:3px solid !important}.sd-border-left-3{border-left:3px solid !important}.sd-border-4{border:4px solid !important}.sd-border-top-4{border-top:4px solid !important}.sd-border-bottom-4{border-bottom:4px solid !important}.sd-border-right-4{border-right:4px solid !important}.sd-border-left-4{border-left:4px solid !important}.sd-border-5{border:5px solid !important}.sd-border-top-5{border-top:5px solid !important}.sd-border-bottom-5{border-bottom:5px solid !important}.sd-border-right-5{border-right:5px solid !important}.sd-border-left-5{border-left:5px solid !important}.sd-rounded-0{border-radius:0 !important}.sd-rounded-1{border-radius:.2rem !important}.sd-rounded-2{border-radius:.3rem !important}.sd-rounded-3{border-radius:.5rem !important}.sd-rounded-pill{border-radius:50rem !important}.sd-rounded-circle{border-radius:50% !important}.shadow-none{box-shadow:none !important}.sd-shadow-sm{box-shadow:0 .125rem .25rem var(--sd-color-shadow) !important}.sd-shadow-md{box-shadow:0 .5rem 1rem var(--sd-color-shadow) !important}.sd-shadow-lg{box-shadow:0 1rem 3rem var(--sd-color-shadow) !important}@keyframes sd-slide-from-left{0%{transform:translateX(-100%)}100%{transform:translateX(0)}}@keyframes sd-slide-from-right{0%{transform:translateX(200%)}100%{transform:translateX(0)}}@keyframes sd-grow100{0%{transform:scale(0);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50{0%{transform:scale(0.5);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50-rot20{0%{transform:scale(0.5) rotateZ(-20deg);opacity:.5}75%{transform:scale(1) rotateZ(5deg);opacity:1}95%{transform:scale(1) rotateZ(-1deg);opacity:1}100%{transform:scale(1) rotateZ(0);opacity:1}}.sd-animate-slide-from-left{animation:1s ease-out 0s 1 normal none running sd-slide-from-left}.sd-animate-slide-from-right{animation:1s ease-out 0s 1 normal none running sd-slide-from-right}.sd-animate-grow100{animation:1s ease-out 0s 1 normal none running sd-grow100}.sd-animate-grow50{animation:1s ease-out 0s 1 normal none running sd-grow50}.sd-animate-grow50-rot20{animation:1s ease-out 0s 1 normal none running sd-grow50-rot20}.sd-badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.sd-badge:empty{display:none}a.sd-badge{text-decoration:none}.sd-btn .sd-badge{position:relative;top:-1px}.sd-btn{background-color:transparent;border:1px solid transparent;border-radius:.25rem;cursor:pointer;display:inline-block;font-weight:400;font-size:1rem;line-height:1.5;padding:.375rem .75rem;text-align:center;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;vertical-align:middle;user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none}.sd-btn:hover{text-decoration:none}@media(prefers-reduced-motion: reduce){.sd-btn{transition:none}}.sd-btn-primary,.sd-btn-outline-primary:hover,.sd-btn-outline-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-primary:hover,.sd-btn-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary-highlight) !important;border-color:var(--sd-color-primary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-primary{color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary,.sd-btn-outline-secondary:hover,.sd-btn-outline-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary:hover,.sd-btn-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary-highlight) !important;border-color:var(--sd-color-secondary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-secondary{color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success,.sd-btn-outline-success:hover,.sd-btn-outline-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success:hover,.sd-btn-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success-highlight) !important;border-color:var(--sd-color-success-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-success{color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info,.sd-btn-outline-info:hover,.sd-btn-outline-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info:hover,.sd-btn-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info-highlight) !important;border-color:var(--sd-color-info-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-info{color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning,.sd-btn-outline-warning:hover,.sd-btn-outline-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning:hover,.sd-btn-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning-highlight) !important;border-color:var(--sd-color-warning-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-warning{color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger,.sd-btn-outline-danger:hover,.sd-btn-outline-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger:hover,.sd-btn-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger-highlight) !important;border-color:var(--sd-color-danger-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-danger{color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light,.sd-btn-outline-light:hover,.sd-btn-outline-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light:hover,.sd-btn-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light-highlight) !important;border-color:var(--sd-color-light-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-light{color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted,.sd-btn-outline-muted:hover,.sd-btn-outline-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted:hover,.sd-btn-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted-highlight) !important;border-color:var(--sd-color-muted-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-muted{color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark,.sd-btn-outline-dark:hover,.sd-btn-outline-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark:hover,.sd-btn-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark-highlight) !important;border-color:var(--sd-color-dark-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-dark{color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black,.sd-btn-outline-black:hover,.sd-btn-outline-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black:hover,.sd-btn-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black-highlight) !important;border-color:var(--sd-color-black-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-black{color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white,.sd-btn-outline-white:hover,.sd-btn-outline-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white:hover,.sd-btn-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white-highlight) !important;border-color:var(--sd-color-white-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-white{color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.sd-hide-link-text{font-size:0}.sd-octicon,.sd-material-icon{display:inline-block;fill:currentColor;vertical-align:middle}.sd-avatar-xs{border-radius:50%;object-fit:cover;object-position:center;width:1rem;height:1rem}.sd-avatar-sm{border-radius:50%;object-fit:cover;object-position:center;width:3rem;height:3rem}.sd-avatar-md{border-radius:50%;object-fit:cover;object-position:center;width:5rem;height:5rem}.sd-avatar-lg{border-radius:50%;object-fit:cover;object-position:center;width:7rem;height:7rem}.sd-avatar-xl{border-radius:50%;object-fit:cover;object-position:center;width:10rem;height:10rem}.sd-avatar-inherit{border-radius:50%;object-fit:cover;object-position:center;width:inherit;height:inherit}.sd-avatar-initial{border-radius:50%;object-fit:cover;object-position:center;width:initial;height:initial}.sd-card{background-clip:border-box;background-color:var(--sd-color-card-background);border:1px solid var(--sd-color-card-border);border-radius:.25rem;color:var(--sd-color-card-text);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;position:relative;word-wrap:break-word}.sd-card>hr{margin-left:0;margin-right:0}.sd-card-hover:hover{border-color:var(--sd-color-card-border-hover);transform:scale(1.01)}.sd-card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem 1rem}.sd-card-title{margin-bottom:.5rem}.sd-card-subtitle{margin-top:-0.25rem;margin-bottom:0}.sd-card-text:last-child{margin-bottom:0}.sd-card-link:hover{text-decoration:none}.sd-card-link+.card-link{margin-left:1rem}.sd-card-header{padding:.5rem 1rem;margin-bottom:0;background-color:var(--sd-color-card-header);border-bottom:1px solid var(--sd-color-card-border)}.sd-card-header:first-child{border-radius:calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0}.sd-card-footer{padding:.5rem 1rem;background-color:var(--sd-color-card-footer);border-top:1px solid var(--sd-color-card-border)}.sd-card-footer:last-child{border-radius:0 0 calc(0.25rem - 1px) calc(0.25rem - 1px)}.sd-card-header-tabs{margin-right:-0.5rem;margin-bottom:-0.5rem;margin-left:-0.5rem;border-bottom:0}.sd-card-header-pills{margin-right:-0.5rem;margin-left:-0.5rem}.sd-card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom,.sd-card-img-top{width:100%}.sd-card-img,.sd-card-img-top{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom{border-bottom-left-radius:calc(0.25rem - 1px);border-bottom-right-radius:calc(0.25rem - 1px)}.sd-cards-carousel{width:100%;display:flex;flex-wrap:nowrap;-ms-flex-direction:row;flex-direction:row;overflow-x:hidden;scroll-snap-type:x mandatory}.sd-cards-carousel.sd-show-scrollbar{overflow-x:auto}.sd-cards-carousel:hover,.sd-cards-carousel:focus{overflow-x:auto}.sd-cards-carousel>.sd-card{flex-shrink:0;scroll-snap-align:start}.sd-cards-carousel>.sd-card:not(:last-child){margin-right:3px}.sd-card-cols-1>.sd-card{width:90%}.sd-card-cols-2>.sd-card{width:45%}.sd-card-cols-3>.sd-card{width:30%}.sd-card-cols-4>.sd-card{width:22.5%}.sd-card-cols-5>.sd-card{width:18%}.sd-card-cols-6>.sd-card{width:15%}.sd-card-cols-7>.sd-card{width:12.8571428571%}.sd-card-cols-8>.sd-card{width:11.25%}.sd-card-cols-9>.sd-card{width:10%}.sd-card-cols-10>.sd-card{width:9%}.sd-card-cols-11>.sd-card{width:8.1818181818%}.sd-card-cols-12>.sd-card{width:7.5%}.sd-container,.sd-container-fluid,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container-xl{margin-left:auto;margin-right:auto;padding-left:var(--sd-gutter-x, 0.75rem);padding-right:var(--sd-gutter-x, 0.75rem);width:100%}@media(min-width: 576px){.sd-container-sm,.sd-container{max-width:540px}}@media(min-width: 768px){.sd-container-md,.sd-container-sm,.sd-container{max-width:720px}}@media(min-width: 992px){.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:960px}}@media(min-width: 1200px){.sd-container-xl,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:1140px}}.sd-row{--sd-gutter-x: 1.5rem;--sd-gutter-y: 0;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-top:calc(var(--sd-gutter-y) * -1);margin-right:calc(var(--sd-gutter-x) * -0.5);margin-left:calc(var(--sd-gutter-x) * -0.5)}.sd-row>*{box-sizing:border-box;flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--sd-gutter-x) * 0.5);padding-left:calc(var(--sd-gutter-x) * 0.5);margin-top:var(--sd-gutter-y)}.sd-col{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-auto>*{flex:0 0 auto;width:auto}.sd-row-cols-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}@media(min-width: 576px){.sd-col-sm{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-sm-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-sm-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-sm-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-sm-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-sm-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-sm-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-sm-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-sm-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-sm-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-sm-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-sm-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-sm-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-sm-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 768px){.sd-col-md{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-md-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-md-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-md-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-md-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-md-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-md-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-md-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-md-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-md-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-md-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-md-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-md-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-md-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 992px){.sd-col-lg{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-lg-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-lg-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-lg-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-lg-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-lg-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-lg-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-lg-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-lg-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-lg-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-lg-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-lg-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-lg-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-lg-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 1200px){.sd-col-xl{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-xl-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-xl-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-xl-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-xl-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-xl-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-xl-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-xl-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-xl-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-xl-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-xl-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-xl-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-xl-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-xl-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}.sd-col-auto{flex:0 0 auto;-ms-flex:0 0 auto;width:auto}.sd-col-1{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}.sd-col-2{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-col-3{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-col-4{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-col-5{flex:0 0 auto;-ms-flex:0 0 auto;width:41.6666666667%}.sd-col-6{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-col-7{flex:0 0 auto;-ms-flex:0 0 auto;width:58.3333333333%}.sd-col-8{flex:0 0 auto;-ms-flex:0 0 auto;width:66.6666666667%}.sd-col-9{flex:0 0 auto;-ms-flex:0 0 auto;width:75%}.sd-col-10{flex:0 0 auto;-ms-flex:0 0 auto;width:83.3333333333%}.sd-col-11{flex:0 0 auto;-ms-flex:0 0 auto;width:91.6666666667%}.sd-col-12{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-g-0,.sd-gy-0{--sd-gutter-y: 0}.sd-g-0,.sd-gx-0{--sd-gutter-x: 0}.sd-g-1,.sd-gy-1{--sd-gutter-y: 0.25rem}.sd-g-1,.sd-gx-1{--sd-gutter-x: 0.25rem}.sd-g-2,.sd-gy-2{--sd-gutter-y: 0.5rem}.sd-g-2,.sd-gx-2{--sd-gutter-x: 0.5rem}.sd-g-3,.sd-gy-3{--sd-gutter-y: 1rem}.sd-g-3,.sd-gx-3{--sd-gutter-x: 1rem}.sd-g-4,.sd-gy-4{--sd-gutter-y: 1.5rem}.sd-g-4,.sd-gx-4{--sd-gutter-x: 1.5rem}.sd-g-5,.sd-gy-5{--sd-gutter-y: 3rem}.sd-g-5,.sd-gx-5{--sd-gutter-x: 3rem}@media(min-width: 576px){.sd-col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-sm-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-sm-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-sm-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-sm-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-sm-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-sm-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-sm-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-sm-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-sm-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-sm-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-sm-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-sm-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-sm-0,.sd-gy-sm-0{--sd-gutter-y: 0}.sd-g-sm-0,.sd-gx-sm-0{--sd-gutter-x: 0}.sd-g-sm-1,.sd-gy-sm-1{--sd-gutter-y: 0.25rem}.sd-g-sm-1,.sd-gx-sm-1{--sd-gutter-x: 0.25rem}.sd-g-sm-2,.sd-gy-sm-2{--sd-gutter-y: 0.5rem}.sd-g-sm-2,.sd-gx-sm-2{--sd-gutter-x: 0.5rem}.sd-g-sm-3,.sd-gy-sm-3{--sd-gutter-y: 1rem}.sd-g-sm-3,.sd-gx-sm-3{--sd-gutter-x: 1rem}.sd-g-sm-4,.sd-gy-sm-4{--sd-gutter-y: 1.5rem}.sd-g-sm-4,.sd-gx-sm-4{--sd-gutter-x: 1.5rem}.sd-g-sm-5,.sd-gy-sm-5{--sd-gutter-y: 3rem}.sd-g-sm-5,.sd-gx-sm-5{--sd-gutter-x: 3rem}}@media(min-width: 768px){.sd-col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-md-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-md-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-md-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-md-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-md-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-md-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-md-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-md-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-md-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-md-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-md-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-md-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-md-0,.sd-gy-md-0{--sd-gutter-y: 0}.sd-g-md-0,.sd-gx-md-0{--sd-gutter-x: 0}.sd-g-md-1,.sd-gy-md-1{--sd-gutter-y: 0.25rem}.sd-g-md-1,.sd-gx-md-1{--sd-gutter-x: 0.25rem}.sd-g-md-2,.sd-gy-md-2{--sd-gutter-y: 0.5rem}.sd-g-md-2,.sd-gx-md-2{--sd-gutter-x: 0.5rem}.sd-g-md-3,.sd-gy-md-3{--sd-gutter-y: 1rem}.sd-g-md-3,.sd-gx-md-3{--sd-gutter-x: 1rem}.sd-g-md-4,.sd-gy-md-4{--sd-gutter-y: 1.5rem}.sd-g-md-4,.sd-gx-md-4{--sd-gutter-x: 1.5rem}.sd-g-md-5,.sd-gy-md-5{--sd-gutter-y: 3rem}.sd-g-md-5,.sd-gx-md-5{--sd-gutter-x: 3rem}}@media(min-width: 992px){.sd-col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-lg-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-lg-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-lg-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-lg-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-lg-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-lg-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-lg-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-lg-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-lg-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-lg-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-lg-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-lg-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-lg-0,.sd-gy-lg-0{--sd-gutter-y: 0}.sd-g-lg-0,.sd-gx-lg-0{--sd-gutter-x: 0}.sd-g-lg-1,.sd-gy-lg-1{--sd-gutter-y: 0.25rem}.sd-g-lg-1,.sd-gx-lg-1{--sd-gutter-x: 0.25rem}.sd-g-lg-2,.sd-gy-lg-2{--sd-gutter-y: 0.5rem}.sd-g-lg-2,.sd-gx-lg-2{--sd-gutter-x: 0.5rem}.sd-g-lg-3,.sd-gy-lg-3{--sd-gutter-y: 1rem}.sd-g-lg-3,.sd-gx-lg-3{--sd-gutter-x: 1rem}.sd-g-lg-4,.sd-gy-lg-4{--sd-gutter-y: 1.5rem}.sd-g-lg-4,.sd-gx-lg-4{--sd-gutter-x: 1.5rem}.sd-g-lg-5,.sd-gy-lg-5{--sd-gutter-y: 3rem}.sd-g-lg-5,.sd-gx-lg-5{--sd-gutter-x: 3rem}}@media(min-width: 1200px){.sd-col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-xl-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-xl-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-xl-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-xl-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-xl-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-xl-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-xl-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-xl-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-xl-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-xl-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-xl-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-xl-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-xl-0,.sd-gy-xl-0{--sd-gutter-y: 0}.sd-g-xl-0,.sd-gx-xl-0{--sd-gutter-x: 0}.sd-g-xl-1,.sd-gy-xl-1{--sd-gutter-y: 0.25rem}.sd-g-xl-1,.sd-gx-xl-1{--sd-gutter-x: 0.25rem}.sd-g-xl-2,.sd-gy-xl-2{--sd-gutter-y: 0.5rem}.sd-g-xl-2,.sd-gx-xl-2{--sd-gutter-x: 0.5rem}.sd-g-xl-3,.sd-gy-xl-3{--sd-gutter-y: 1rem}.sd-g-xl-3,.sd-gx-xl-3{--sd-gutter-x: 1rem}.sd-g-xl-4,.sd-gy-xl-4{--sd-gutter-y: 1.5rem}.sd-g-xl-4,.sd-gx-xl-4{--sd-gutter-x: 1.5rem}.sd-g-xl-5,.sd-gy-xl-5{--sd-gutter-y: 3rem}.sd-g-xl-5,.sd-gx-xl-5{--sd-gutter-x: 3rem}}.sd-flex-row-reverse{flex-direction:row-reverse !important}details.sd-dropdown{position:relative;font-size:var(--sd-fontsize-dropdown)}details.sd-dropdown:hover{cursor:pointer}details.sd-dropdown .sd-summary-content{cursor:default}details.sd-dropdown summary.sd-summary-title{padding:.5em .6em .5em 1em;font-size:var(--sd-fontsize-dropdown-title);font-weight:var(--sd-fontweight-dropdown-title);user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;list-style:none;display:inline-flex;justify-content:space-between}details.sd-dropdown summary.sd-summary-title::-webkit-details-marker{display:none}details.sd-dropdown summary.sd-summary-title:focus{outline:none}details.sd-dropdown summary.sd-summary-title .sd-summary-icon{margin-right:.6em;display:inline-flex;align-items:center}details.sd-dropdown summary.sd-summary-title .sd-summary-icon svg{opacity:.8}details.sd-dropdown summary.sd-summary-title .sd-summary-text{flex-grow:1;line-height:1.5;padding-right:.5rem}details.sd-dropdown summary.sd-summary-title .sd-summary-state-marker{pointer-events:none;display:inline-flex;align-items:center}details.sd-dropdown summary.sd-summary-title .sd-summary-state-marker svg{opacity:.6}details.sd-dropdown summary.sd-summary-title:hover .sd-summary-state-marker svg{opacity:1;transform:scale(1.1)}details.sd-dropdown[open] summary .sd-octicon.no-title{visibility:hidden}details.sd-dropdown .sd-summary-chevron-right{transition:.25s}details.sd-dropdown[open]>.sd-summary-title .sd-summary-chevron-right{transform:rotate(90deg)}details.sd-dropdown[open]>.sd-summary-title .sd-summary-chevron-down{transform:rotate(180deg)}details.sd-dropdown:not([open]).sd-card{border:none}details.sd-dropdown:not([open])>.sd-card-header{border:1px solid var(--sd-color-card-border);border-radius:.25rem}details.sd-dropdown.sd-fade-in[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out;animation:sd-fade-in .5s ease-in-out}details.sd-dropdown.sd-fade-in-slide-down[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out}.sd-col>.sd-dropdown{width:100%}.sd-summary-content>.sd-tab-set:first-child{margin-top:0}@keyframes sd-fade-in{0%{opacity:0}100%{opacity:1}}@keyframes sd-slide-down{0%{transform:translate(0, -10px)}100%{transform:translate(0, 0)}}.sd-tab-set{border-radius:.125rem;display:flex;flex-wrap:wrap;margin:1em 0;position:relative}.sd-tab-set>input{opacity:0;position:absolute}.sd-tab-set>input:checked+label{border-color:var(--sd-color-tabs-underline-active);color:var(--sd-color-tabs-label-active)}.sd-tab-set>input:checked+label+.sd-tab-content{display:block}.sd-tab-set>input:not(:checked)+label:hover{color:var(--sd-color-tabs-label-hover);border-color:var(--sd-color-tabs-underline-hover)}.sd-tab-set>input:focus+label{outline-style:auto}.sd-tab-set>input:not(.focus-visible)+label{outline:none;-webkit-tap-highlight-color:transparent}.sd-tab-set>label{border-bottom:.125rem solid transparent;margin-bottom:0;color:var(--sd-color-tabs-label-inactive);border-color:var(--sd-color-tabs-underline-inactive);cursor:pointer;font-size:var(--sd-fontsize-tabs-label);font-weight:700;padding:1em 1.25em .5em;transition:color 250ms;width:auto;z-index:1}html .sd-tab-set>label:hover{color:var(--sd-color-tabs-label-active)}.sd-col>.sd-tab-set{width:100%}.sd-tab-content{box-shadow:0 -0.0625rem var(--sd-color-tabs-overline),0 .0625rem var(--sd-color-tabs-underline);display:none;order:99;padding-bottom:.75rem;padding-top:.75rem;width:100%}.sd-tab-content>:first-child{margin-top:0 !important}.sd-tab-content>:last-child{margin-bottom:0 !important}.sd-tab-content>.sd-tab-set{margin:0}.sd-sphinx-override,.sd-sphinx-override *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.sd-sphinx-override p{margin-top:0}:root{--sd-color-primary: #0071bc;--sd-color-secondary: #6c757d;--sd-color-success: #28a745;--sd-color-info: #17a2b8;--sd-color-warning: #f0b37e;--sd-color-danger: #dc3545;--sd-color-light: #f8f9fa;--sd-color-muted: #6c757d;--sd-color-dark: #212529;--sd-color-black: black;--sd-color-white: white;--sd-color-primary-highlight: #0060a0;--sd-color-secondary-highlight: #5c636a;--sd-color-success-highlight: #228e3b;--sd-color-info-highlight: #148a9c;--sd-color-warning-highlight: #cc986b;--sd-color-danger-highlight: #bb2d3b;--sd-color-light-highlight: #d3d4d5;--sd-color-muted-highlight: #5c636a;--sd-color-dark-highlight: #1c1f23;--sd-color-black-highlight: black;--sd-color-white-highlight: #d9d9d9;--sd-color-primary-bg: rgba(0, 113, 188, 0.2);--sd-color-secondary-bg: rgba(108, 117, 125, 0.2);--sd-color-success-bg: rgba(40, 167, 69, 0.2);--sd-color-info-bg: rgba(23, 162, 184, 0.2);--sd-color-warning-bg: rgba(240, 179, 126, 0.2);--sd-color-danger-bg: rgba(220, 53, 69, 0.2);--sd-color-light-bg: rgba(248, 249, 250, 0.2);--sd-color-muted-bg: rgba(108, 117, 125, 0.2);--sd-color-dark-bg: rgba(33, 37, 41, 0.2);--sd-color-black-bg: rgba(0, 0, 0, 0.2);--sd-color-white-bg: rgba(255, 255, 255, 0.2);--sd-color-primary-text: #fff;--sd-color-secondary-text: #fff;--sd-color-success-text: #fff;--sd-color-info-text: #fff;--sd-color-warning-text: #212529;--sd-color-danger-text: #fff;--sd-color-light-text: #212529;--sd-color-muted-text: #fff;--sd-color-dark-text: #fff;--sd-color-black-text: #fff;--sd-color-white-text: #212529;--sd-color-shadow: rgba(0, 0, 0, 0.15);--sd-color-card-border: rgba(0, 0, 0, 0.125);--sd-color-card-border-hover: hsla(231, 99%, 66%, 1);--sd-color-card-background: transparent;--sd-color-card-text: inherit;--sd-color-card-header: transparent;--sd-color-card-footer: transparent;--sd-color-tabs-label-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-hover: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-inactive: hsl(0, 0%, 66%);--sd-color-tabs-underline-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-underline-hover: rgba(178, 206, 245, 0.62);--sd-color-tabs-underline-inactive: transparent;--sd-color-tabs-overline: rgb(222, 222, 222);--sd-color-tabs-underline: rgb(222, 222, 222);--sd-fontsize-tabs-label: 1rem;--sd-fontsize-dropdown: inherit;--sd-fontsize-dropdown-title: 1rem;--sd-fontweight-dropdown-title: 700} diff --git a/mddocs/markdown/_static/autodoc_pydantic.css b/mddocs/markdown/_static/autodoc_pydantic.css deleted file mode 100644 index 994a3e548..000000000 --- a/mddocs/markdown/_static/autodoc_pydantic.css +++ /dev/null @@ -1,11 +0,0 @@ -.autodoc_pydantic_validator_arrow { - padding-left: 8px; - } - -.autodoc_pydantic_collapsable_json { - cursor: pointer; - } - -.autodoc_pydantic_collapsable_erd { - cursor: pointer; - } \ No newline at end of file diff --git a/mddocs/markdown/changelog.md b/mddocs/markdown/changelog.md deleted file mode 100644 index 4ba142635..000000000 --- a/mddocs/markdown/changelog.md +++ /dev/null @@ -1,31 +0,0 @@ -# Changelog - -# Changelog - -* [0.13.4 (2025-03-20)](changelog/0.13.4.md) -* [0.13.3 (2025-03-11)](changelog/0.13.3.md) -* [0.13.1 (2025-03-06)](changelog/0.13.1.md) -* [0.13.0 (2025-02-24)](changelog/0.13.0.md) -* [0.12.5 (2024-12-03)](changelog/0.12.5.md) -* [0.12.4 (2024-11-27)](changelog/0.12.4.md) -* [0.12.3 (2024-11-22)](changelog/0.12.3.md) -* [0.12.2 (2024-11-12)](changelog/0.12.2.md) -* [0.12.1 (2024-10-28)](changelog/0.12.1.md) -* [0.12.0 (2024-09-03)](changelog/0.12.0.md) -* [0.11.2 (2024-09-02)](changelog/0.11.2.md) -* [0.11.1 (2024-05-29)](changelog/0.11.1.md) -* [0.11.0 (2024-05-27)](changelog/0.11.0.md) -* [0.10.2 (2024-03-21)](changelog/0.10.2.md) -* [0.10.1 (2024-02-05)](changelog/0.10.1.md) -* [0.10.0 (2023-12-18)](changelog/0.10.0.md) -* [0.9.5 (2023-10-10)](changelog/0.9.5.md) -* [0.9.4 (2023-09-26)](changelog/0.9.4.md) -* [0.9.3 (2023-09-06)](changelog/0.9.3.md) -* [0.9.2 (2023-09-06)](changelog/0.9.2.md) -* [0.9.1 (2023-08-17)](changelog/0.9.1.md) -* [0.9.0 (2023-08-17)](changelog/0.9.0.md) -* [0.8.1 (2023-07-10)](changelog/0.8.1.md) -* [0.8.0 (2023-05-31)](changelog/0.8.0.md) -* [0.7.2 (2023-05-24)](changelog/0.7.2.md) -* [0.7.1 (2023-05-23)](changelog/0.7.1.md) -* [0.7.0 (2023-05-15)](changelog/0.7.0.md) diff --git a/mddocs/markdown/changelog/0.10.0.md b/mddocs/markdown/changelog/0.10.0.md deleted file mode 100644 index ff0fe78bc..000000000 --- a/mddocs/markdown/changelog/0.10.0.md +++ /dev/null @@ -1,359 +0,0 @@ -# 0.10.0 (2023-12-18) - -## Breaking Changes - -- Upgrade `etl-entities` from v1 to v2 ([#172](https://github.com/MobileTeleSystems/onetl/pull/172)). - - This implies that `HWM` classes are now have different internal structure than they used to. - - Before: - ```python - from etl_entities.old_hwm import IntHWM as OldIntHWM - from etl_entities.source import Column, Table - from etl_entities.process import Process - - hwm = OldIntHWM( - process=Process(name="myprocess", task="abc", dag="cde", host="myhost"), - source=Table(name="schema.table", instance="postgres://host:5432/db"), - column=Column(name="col1"), - value=123, - ) - ``` - - After: - ```python - from etl_entities.hwm import ColumnIntHWM - - hwm = ColumnIntHWM( - name="some_unique_name", - description="any value you want", - source="schema.table", - expression="col1", - value=123, - ) - ``` - - **Breaking change:** If you used HWM classes from `etl_entities` module, you should rewrite your code to make it compatible with new version. - - ### More details - - - `HWM` classes used by previous onETL versions were moved from `etl_entities` to `etl_entities.old_hwm` submodule. They are here for compatibility reasons, but are planned to be removed in `etl-entities` v3 release. - - New `HWM` classes have flat structure instead of nested. - - New `HWM` classes have mandatory `name` attribute (it was known as `qualified_name` before). - - Type aliases used while serializing and deserializing `HWM` objects to `dict` representation were changed too: `int` → `column_int`. - - To make migration simpler, you can use new method: - ```python - old_hwm = OldIntHWM(...) - new_hwm = old_hwm.as_new_hwm() - ``` - - Which automatically converts all fields from old structure to new one, including `qualified_name` → `name`. -- **Breaking changes:** - * Methods `BaseHWMStore.get()` and `BaseHWMStore.save()` were renamed to `get_hwm()` and `set_hwm()`. - * They now can be used only with new HWM classes from `etl_entities.hwm`, **old HWM classes are not supported**. - - If you used them in your code, please update it accordingly. -- YAMLHWMStore **CANNOT read files created by older onETL versions** (0.9.x or older). - - ### Update procedure - - ```python - # pip install onetl==0.9.5 - - # Get qualified_name for HWM - - - # Option 1. HWM is built manually - from etl_entities import IntHWM, FileListHWM - from etl_entities.source import Column, Table, RemoteFolder - from etl_entities.process import Process - - # for column HWM - old_column_hwm = IntHWM( - process=Process(name="myprocess", task="abc", dag="cde", host="myhost"), - source=Table(name="schema.table", instance="postgres://host:5432/db"), - column=Column(name="col1"), - ) - qualified_name = old_column_hwm.qualified_name - # "col1#schema.table@postgres://host:5432/db#cde.abc.myprocess@myhost" - - # for file HWM - old_file_hwm = FileListHWM( - process=Process(name="myprocess", task="abc", dag="cde", host="myhost"), - source=RemoteFolder(name="/absolute/path", instance="ftp://ftp.server:21"), - ) - qualified_name = old_file_hwm.qualified_name - # "file_list#/absolute/path@ftp://ftp.server:21#cde.abc.myprocess@myhost" - - - # Option 2. HWM is generated automatically (by DBReader/FileDownloader) - # See onETL logs and search for string like qualified_name = '...' - - qualified_name = "col1#schema.table@postgres://host:5432/db#cde.abc.myprocess@myhost" - - - # Get .yml file path by qualified_name - - import os - from pathlib import PurePosixPath - from onetl.hwm.store import YAMLHWMStore - - # here you should pass the same arguments as used on production, if any - yaml_hwm_store = YAMLHWMStore() - hwm_path = yaml_hwm_store.get_file_path(qualified_name) - print(hwm_path) - - # for column HWM - # LocalPosixPath('/home/maxim/.local/share/onETL/yml_hwm_store/col1__schema.table__postgres_host_5432_db__cde.abc.myprocess__myhost.yml') - - # for file HWM - # LocalPosixPath('/home/maxim/.local/share/onETL/yml_hwm_store/file_list__absolute_path__ftp_ftp.server_21__cde.abc.myprocess__myhost.yml') - - - # Read raw .yml file content - - from yaml import safe_load, dump - - raw_old_hwm_items = safe_load(hwm_path.read_text()) - print(raw_old_hwm_items) - - # for column HWM - # [ - # { - # "column": { "name": "col1", "partition": {} }, - # "modified_time": "2023-12-18T10: 39: 47.377378", - # "process": { "dag": "cde", "host": "myhost", "name": "myprocess", "task": "abc" }, - # "source": { "instance": "postgres: //host:5432/db", "name": "schema.table" }, - # "type": "int", - # "value": "123", - # }, - # ] - - # for file HWM - # [ - # { - # "modified_time": "2023-12-18T11:15:36.478462", - # "process": { "dag": "cde", "host": "myhost", "name": "myprocess", "task": "abc" }, - # "source": { "instance": "ftp://ftp.server:21", "name": "/absolute/path" }, - # "type": "file_list", - # "value": ["file1.txt", "file2.txt"], - # }, - # ] - - - # Convert file content to new structure, compatible with onETL 0.10.x - raw_new_hwm_items = [] - for old_hwm in raw_old_hwm_items: - new_hwm = {"name": qualified_name, "modified_time": old_hwm["modified_time"]} - - if "column" in old_hwm: - new_hwm["expression"] = old_hwm["column"]["name"] - new_hwm["entity"] = old_hwm["source"]["name"] - old_hwm.pop("process", None) - - if old_hwm["type"] == "int": - new_hwm["type"] = "column_int" - new_hwm["value"] = old_hwm["value"] - - elif old_hwm["type"] == "date": - new_hwm["type"] = "column_date" - new_hwm["value"] = old_hwm["value"] - - elif old_hwm["type"] == "datetime": - new_hwm["type"] = "column_datetime" - new_hwm["value"] = old_hwm["value"] - - elif old_hwm["type"] == "file_list": - new_hwm["type"] = "file_list" - new_hwm["value"] = [ - os.fspath(PurePosixPath(old_hwm["source"]["name"]).joinpath(path)) - for path in old_hwm["value"] - ] - - else: - raise ValueError("WAT?") - - raw_new_hwm_items.append(new_hwm) - - - print(raw_new_hwm_items) - # for column HWM - # [ - # { - # "name": "col1#schema.table@postgres://host:5432/db#cde.abc.myprocess@myhost", - # "modified_time": "2023-12-18T10:39:47.377378", - # "expression": "col1", - # "source": "schema.table", - # "type": "column_int", - # "value": 123, - # }, - # ] - - # for file HWM - # [ - # { - # "name": "file_list#/absolute/path@ftp://ftp.server:21#cde.abc.myprocess@myhost", - # "modified_time": "2023-12-18T11:15:36.478462", - # "entity": "/absolute/path", - # "type": "file_list", - # "value": ["/absolute/path/file1.txt", "/absolute/path/file2.txt"], - # }, - # ] - - - # Save file with new content - with open(hwm_path, "w") as file: - dump(raw_new_hwm_items, file) - - - # Stop Python interpreter and update onETL - # pip install onetl==0.10.0 - # Check that new .yml file can be read - - from onetl.hwm.store import YAMLHWMStore - - qualified_name = ... - - # here you should pass the same arguments as used on production, if any - yaml_hwm_store = YAMLHWMStore() - yaml_hwm_store.get_hwm(qualified_name) - - # for column HWM - # ColumnIntHWM( - # name='col1#schema.table@postgres://host:5432/db#cde.abc.myprocess@myhost', - # description='', - # entity='schema.table', - # value=123, - # expression='col1', - # modified_time=datetime.datetime(2023, 12, 18, 10, 39, 47, 377378), - # ) - - # for file HWM - # FileListHWM( - # name='file_list#/absolute/path@ftp://ftp.server:21#cde.abc.myprocess@myhost', - # description='', - # entity=AbsolutePath('/absolute/path'), - # value=frozenset({AbsolutePath('/absolute/path/file1.txt'), AbsolutePath('/absolute/path/file2.txt')}), - # expression=None, - # modified_time=datetime.datetime(2023, 12, 18, 11, 15, 36, 478462) - # ) - - - # That's all! - ``` - - But most of users use other HWM store implementations which do not have such issues. -- Several classes and functions were moved from `onetl` to `etl_entities`: - - | onETL `0.9.x` and older | onETL `0.10.x` and newer | - |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| - | ```python
from onetl.hwm.store import (
detect_hwm_store,
BaseHWMStore,
HWMStoreClassRegistry,
register_hwm_store_class,
HWMStoreManager,
MemoryHWMStore,
)
``` | ```python
from etl_entities.hwm_store import (
detect_hwm_store,
BaseHWMStore,
HWMStoreClassRegistry,
register_hwm_store_class,
HWMStoreManager,
MemoryHWMStore,
)
``` | - - They still can be imported from old module, but this is deprecated and will be removed in v1.0.0 release. -- Change the way of passing `HWM` to `DBReader` and `FileDownloader` classes: - - | onETL `0.9.x` and older | onETL `0.10.x` and newer | - |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| - | ```python
reader = DBReader(
connection=...,
source=...,
hwm_column="col1",
)
``` | ```python
reader = DBReader(
connection=...,
source=...,
hwm=DBReader.AutoDetectHWM(
# name is mandatory now!
name="my_unique_hwm_name",
expression="col1",
),
)
``` | - | ```python
reader = DBReader(
connection=...,
source=...,
hwm_column=(
"col1",
"cast(col1 as date)",
),
)
``` | ```python
reader = DBReader(
connection=...,
source=...,
hwm=DBReader.AutoDetectHWM(
# name is mandatory now!
name="my_unique_hwm_name",
expression="cast(col1 as date)",
),
)
``` | - | ```python
downloader = FileDownloader(
connection=...,
source_path=...,
target_path=...,
hwm_type="file_list",
)
``` | ```python
downloader = FileDownloader(
connection=...,
source_path=...,
target_path=...,
hwm=FileListHWM(
# name is mandatory now!
name="another_unique_hwm_name",
),
)
``` | - - New HWM classes have **mandatory** `name` attribute which should be passed explicitly, - instead of generating if automatically under the hood. - - Automatic `name` generation using the old `DBReader.hwm_column` / `FileDownloader.hwm_type` - syntax is still supported, but will be removed in v1.0.0 release. ([#179](https://github.com/MobileTeleSystems/onetl/pull/179)) -- Performance of read Incremental and Batch strategies has been drastically improved. ([#182](https://github.com/MobileTeleSystems/onetl/pull/182)). - - ### Before and after in details - - `DBReader.run()` + incremental/batch strategy behavior in versions 0.9.x and older: - - Get table schema by making query `SELECT * FROM table WHERE 1=0` (if `DBReader.columns` has `*`) - - Expand `*` to real column names from table, add here `hwm_column`, remove duplicates (as some RDBMS does not allow that). - - Create dataframe from query like `SELECT hwm_expression AS hwm_column, ...other table columns... FROM table WHERE hwm_expression > prev_hwm.value`. - - Determine HWM class using dataframe schema: `df.schema[hwm_column].dataType`. - - Determine x HWM column value using Spark: `df.select(max(hwm_column)).collect()`. - - Use `max(hwm_column)` as next HWM value, and save it to HWM Store. - - Return dataframe to user. - - This was far from ideal: - - Dataframe content (all rows or just changed ones) was loaded from the source to Spark only to get min/max values of specific column. - - Step of fetching table schema and then substituting column names in the next query caused some unexpected errors. - > For example, source contains columns with mixed name case, like `"CamelColumn"` or `"spaced column"`. - - > Column names were *not* escaped during query generation, leading to queries that cannot be executed by database. - - > So users have to *explicitly* pass column names `DBReader`, wrapping columns with mixed naming with `"`: - > ```python - > reader = DBReader( - > connection=..., - > source=..., - > columns=[ # passing '*' here leads to wrong SQL query generation - > "normal_column", - > '"CamelColumn"', - > '"spaced column"', - > ..., - > ], - > ) - > ``` - - Using `DBReader` with `IncrementalStrategy` could lead to reading rows already read before. - > Dataframe was created from query with WHERE clause like `hwm.expression > prev_hwm.value`, - > not `hwm.expression > prev_hwm.value AND hwm.expression <= current_hwm.value`. - - > So if new rows appeared in the source **after** HWM value is determined, - > they can be read by accessing dataframe content (because Spark dataframes are lazy), - > leading to inconsistencies between HWM value and dataframe content. - - > This may lead to issues then `DBReader.run()` read some data, updated HWM value, and next call of `DBReader.run()` - > will read rows that were already read in previous run. - - `DBReader.run()` + incremental/batch strategy behavior in versions 0.10.x and newer: - - Detect type of HWM expression: `SELECT hwm.expression FROM table WHERE 1=0`. - - Determine corresponding Spark type `df.schema[0]` and when determine matching HWM class (if `DReader.AutoDetectHWM` is used). - - Get min/max values by querying the source: `SELECT MAX(hwm.expression) FROM table WHERE hwm.expression >= prev_hwm.value`. - - Use `max(hwm.expression)` as next HWM value, and save it to HWM Store. - - Create dataframe from query `SELECT ... table columns ... FROM table WHERE hwm.expression > prev_hwm.value AND hwm.expression <= current_hwm.value`, baking new HWM value into the query. - - Return dataframe to user. - - Improvements: - - Allow source to calculate min/max instead of loading everything to Spark. This should be **faster** on large amounts of data (**up to x2**), because we do not transfer all the data from the source to Spark. This can be even faster if source have indexes for HWM column. - - Columns list is passed to source as-is, without any resolving on `DBReader` side. So you can pass `DBReader(columns=["*"])` to read tables with mixed columns naming. - - Restrict dataframe content to always match HWM values, which leads to never reading the same row twice. - - **Breaking change**: HWM column is not being implicitly added to dataframe. It was a part of `SELECT` clause, but now it is mentioned only in `WHERE` clause. - - So if you had code like this, you have to rewrite it: - - | onETL `0.9.x` and older | onETL `0.10.x` and newer | - |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| - | ```python
reader = DBReader(
connection=...,
source=...,
columns=[
"col1",
"col2",
],
hwm_column="hwm_col",
)

df = reader.run()
# hwm_column value is in the dataframe
assert df.columns == ["col1", "col2", "hwm_col"]
``` | ```python
reader = DBReader(
connection=...,
source=...,
columns=[
"col1",
"col2",
# add hwm_column explicitly
"hwm_col",
],
hwm_column="hwm_col",
)

df = reader.run()
# if columns list is not updated,
# this fill fail
assert df.columns == ["col1", "col2", "hwm_col"]
``` | - | ```python
reader = DBReader(
connection=...,
source=...,
columns=[
"col1",
"col2",
],
hwm_column=(
"hwm_col",
"cast(hwm_col as int)",
),
)

df = reader.run()
# hwm_expression value is in the dataframe
assert df.columns == ["col1", "col2", "hwm_col"]
``` | ```python
reader = DBReader(
connection=...,
source=...,
columns=[
"col1",
"col2",
# add hwm_expression explicitly
"cast(hwm_col as int) as hwm_col",
],
hwm_column=(
"hwm_col",
"cast(hwm_col as int)",
),
)

df = reader.run()
# if columns list is not updated,
# this fill fail
assert df.columns == ["col1", "col2", "hwm_col"]
``` | - - But most users just use `columns=["*"]` anyway, they won’t see any changes. -- `FileDownloader.run()` now updates HWM in HWM Store not after each file is being successfully downloaded, - but after all files were handled. - - This is because: - * FileDownloader can be used with `DownloadOptions(workers=N)`, which could lead to race condition - one thread can save to HWM store one HWM value when another thread will save different value. - * FileDownloader can download hundreds and thousands of files, and issuing a request to HWM Store for each file could potentially DDoS HWM Store. ([#189](https://github.com/MobileTeleSystems/onetl/pull/189)) - - There is a exception handler which tries to save HWM to HWM store if download process was interrupted. But if it was interrupted by force, like sending `SIGKILL` event, - HWM will not be saved to HWM store, so some already downloaded files may be downloaded again next time. - - But unexpected process kill may produce other negative impact, like some file will be downloaded partially, so this is an expected behavior. - -## Features - -- Add Python 3.12 compatibility. ([#167](https://github.com/MobileTeleSystems/onetl/pull/167)) -- `Excel` file format now can be used with Spark 3.5.0. ([#187](https://github.com/MobileTeleSystems/onetl/pull/187)) -- `SnapshotBatchStagy` and `IncrementalBatchStrategy` does no raise exceptions if source does not contain any data. - Instead they stop at first iteration and return empty dataframe. ([#188](https://github.com/MobileTeleSystems/onetl/pull/188)) -- Cache result of `connection.check()` in high-level classes like `DBReader`, `FileDownloader` and so on. This makes logs less verbose. ([#190](https://github.com/MobileTeleSystems/onetl/pull/190)) - -## Bug Fixes - -- Fix `@slot` and `@hook` decorators returning methods with missing arguments in signature (Pylance, VS Code). ([#183](https://github.com/MobileTeleSystems/onetl/pull/183)) -- Kafka connector documentation said that it does support reading topic data incrementally by passing `group.id` or `groupIdPrefix`. - Actually, this is not true, because Spark does not send information to Kafka which messages were consumed. - So currently users can only read the whole topic, no incremental reads are supported. diff --git a/mddocs/markdown/changelog/0.10.1.md b/mddocs/markdown/changelog/0.10.1.md deleted file mode 100644 index 1bc703cb4..000000000 --- a/mddocs/markdown/changelog/0.10.1.md +++ /dev/null @@ -1,25 +0,0 @@ -# 0.10.1 (2024-02-05) - -## Features - -- Add support of `Incremental Strategies` for `Kafka` connection: - ```python - reader = DBReader( - connection=Kafka(...), - source="topic_name", - hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="offset"), - ) - - with IncrementalStrategy(): - df = reader.run() - ``` - - This lets you resume reading data from a Kafka topic starting at the last committed offset from your previous run. ([#202](https://github.com/MobileTeleSystems/onetl/pull/202)) -- Add `has_data`, `raise_if_no_data` methods to `DBReader` class. ([#203](https://github.com/MobileTeleSystems/onetl/pull/203)) -- Updare VMware Greenplum connector from `2.1.4` to `2.3.0`. This implies: - : * Greenplum 7.x support - * [Kubernetes support](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/configure.html#k8scfg) - * New read option [gpdb.matchDistributionPolicy](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#distpolmotion) - which allows to match each Spark executor with specific Greenplum segment, avoiding redundant data transfer between Greenplum segments - * Allows overriding [Greenplum optimizer parameters](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#greenplum-gucs) in read/write operations ([#208](https://github.com/MobileTeleSystems/onetl/pull/208)) -- `Greenplum.get_packages()` method now accepts optional arg `package_version` which allows to override version of Greenplum connector package. ([#208](https://github.com/MobileTeleSystems/onetl/pull/208)) diff --git a/mddocs/markdown/changelog/0.10.2.md b/mddocs/markdown/changelog/0.10.2.md deleted file mode 100644 index 3ca2693c2..000000000 --- a/mddocs/markdown/changelog/0.10.2.md +++ /dev/null @@ -1,34 +0,0 @@ -# 0.10.2 (2024-03-21) - -## Features - -- Add support of Pydantic v2. ([#230](https://github.com/MobileTeleSystems/onetl/pull/230)) - -## Improvements - -- Improve database connections documentation: - : * Add “Types” section describing mapping between Clickhouse and Spark types - * Add “Prerequisites” section describing different aspects of connecting to Clickhouse - * Separate documentation of `DBReader` and `.sql()` / `.pipeline(...)` - * Add examples for `.fetch()` and `.execute()` ([#211](https://github.com/MobileTeleSystems/onetl/pull/211), [#228](https://github.com/MobileTeleSystems/onetl/pull/228), [#229](https://github.com/MobileTeleSystems/onetl/pull/229), [#233](https://github.com/MobileTeleSystems/onetl/pull/233), [#234](https://github.com/MobileTeleSystems/onetl/pull/234), [#235](https://github.com/MobileTeleSystems/onetl/pull/235), [#236](https://github.com/MobileTeleSystems/onetl/pull/236), [#240](https://github.com/MobileTeleSystems/onetl/pull/240)) -- Add notes to Greenplum documentation about issues with IP resolution and building `gpfdist` URL ([#228](https://github.com/MobileTeleSystems/onetl/pull/228)) -- Allow calling `MongoDB.pipeline(...)` with passing just collection name, without explicit aggregation pipeline. ([#237](https://github.com/MobileTeleSystems/onetl/pull/237)) -- Update default `Postgres(extra={...})` to include `{"stringtype": "unspecified"}` option. - This allows to write text data to non-text column (or vice versa), relying to Postgres cast capabilities. - - For example, now it is possible to read column of type `money` as Spark’s `StringType()`, and write it back to the same column, - without using intermediate columns or tables. ([#229](https://github.com/MobileTeleSystems/onetl/pull/229)) - -## Bug Fixes - -- Return back handling of `DBReader(columns="string")`. This was a valid syntax up to v0.10 release, but it was removed because - most of users neved used it. It looks that we were wrong, returning this behavior back, but with deprecation warning. ([#238](https://github.com/MobileTeleSystems/onetl/pull/238)) -- Downgrade Greenplum package version from `2.3.0` to `2.2.0`. ([#239](https://github.com/MobileTeleSystems/onetl/pull/239)) - - This is because version 2.3.0 introduced issues with writing data to Greenplum 6.x. - Connector can open transaction with `SELECT * FROM table LIMIT 0` query, but does not close it, which leads to deadlocks. - - For using this connector with Greenplum 7.x, please pass package version explicitly: - ```python - maven_packages = Greenplum.get_packages(package_version="2.3.0", ...) - ``` diff --git a/mddocs/markdown/changelog/0.11.0.md b/mddocs/markdown/changelog/0.11.0.md deleted file mode 100644 index 2069d081f..000000000 --- a/mddocs/markdown/changelog/0.11.0.md +++ /dev/null @@ -1,202 +0,0 @@ -# 0.11.0 (2024-05-27) - -## Breaking Changes - -There can be some changes in connection behavior, related to version upgrades. So we mark these changes as **breaking** although -most of users will not see any differences. - -- Update Clickhouse JDBC driver to latest version ([#249](https://github.com/MobileTeleSystems/onetl/pull/249)): - : * Package was renamed `ru.yandex.clickhouse:clickhouse-jdbc` → `com.clickhouse:clickhouse-jdbc`. - * Package version changed `0.3.2` → `0.6.0-patch5`. - * Driver name changed `ru.yandex.clickhouse.ClickHouseDriver` → `com.clickhouse.jdbc.ClickHouseDriver`. - - This brings up several fixes for Spark <-> Clickhouse type compatibility, and also Clickhouse clusters support. -- Update other JDBC drivers to latest versions: - : * MSSQL `12.2.0` → `12.6.2` ([#254](https://github.com/MobileTeleSystems/onetl/pull/254)). - * MySQL `8.0.33` → `8.4.0` ([#253](https://github.com/MobileTeleSystems/onetl/pull/253), [#285](https://github.com/MobileTeleSystems/onetl/pull/285)). - * Oracle `23.2.0.0` → `23.4.0.24.05` ([#252](https://github.com/MobileTeleSystems/onetl/pull/252), [#284](https://github.com/MobileTeleSystems/onetl/pull/284)). - * Postgres `42.6.0` → `42.7.3` ([#251](https://github.com/MobileTeleSystems/onetl/pull/251)). -- Update MongoDB connector to latest version: `10.1.1` → `10.3.0` ([#255](https://github.com/MobileTeleSystems/onetl/pull/255), [#283](https://github.com/MobileTeleSystems/onetl/pull/283)). - - This brings up Spark 3.5 support. -- Update `XML` package to latest version: `0.17.0` → `0.18.0` ([#259](https://github.com/MobileTeleSystems/onetl/pull/259)). - - This brings few bugfixes with datetime format handling. -- For JDBC connections add new `SQLOptions` class for `DB.sql(query, options=...)` method ([#272](https://github.com/MobileTeleSystems/onetl/pull/272)). - - Firsly, to keep naming more consistent. - - Secondly, some of options are not supported by `DB.sql(...)` method, but supported by `DBReader`. - For example, `SQLOptions` do not support `partitioning_mode` and require explicit definition of `lower_bound` and `upper_bound` when `num_partitions` is greater than 1. - `ReadOptions` does support `partitioning_mode` and allows skipping `lower_bound` and `upper_bound` values. - - This require some code changes. Before: - ```python - from onetl.connection import Postgres - - postgres = Postgres(...) - df = postgres.sql( - """ - SELECT * - FROM some.mytable - WHERE key = 'something' - """, - options=Postgres.ReadOptions( - partitioning_mode="range", - partition_column="id", - num_partitions=10, - ), - ) - ``` - - After: - ```python - from onetl.connection import Postgres - - postgres = Postgres(...) - df = postgres.sql( - """ - SELECT * - FROM some.mytable - WHERE key = 'something' - """, - options=Postgres.SQLOptions( - # partitioning_mode is not supported! - partition_column="id", - num_partitions=10, - lower_bound=0, # <-- set explicitly - upper_bound=1000, # <-- set explicitly - ), - ) - ``` - - For now, `DB.sql(query, options=...)` can accept `ReadOptions` to keep backward compatibility, but emits deprecation warning. - The support will be removed in `v1.0.0`. -- Split up `JDBCOptions` class into `FetchOptions` and `ExecuteOptions` ([#274](https://github.com/MobileTeleSystems/onetl/pull/274)). - - New classes are used by `DB.fetch(query, options=...)` and `DB.execute(query, options=...)` methods respectively. - This is mostly to keep naming more consistent. - - This require some code changes. Before: - ```python - from onetl.connection import Postgres - - postgres = Postgres(...) - df = postgres.fetch( - "SELECT * FROM some.mytable WHERE key = 'something'", - options=Postgres.JDBCOptions( - fetchsize=1000, - query_timeout=30, - ), - ) - - postgres.execute( - "UPDATE some.mytable SET value = 'new' WHERE key = 'something'", - options=Postgres.JDBCOptions(query_timeout=30), - ) - ``` - - After: - ```python - from onetl.connection import Postgres - - # Using FetchOptions for fetching data - postgres = Postgres(...) - df = postgres.fetch( - "SELECT * FROM some.mytable WHERE key = 'something'", - options=Postgres.FetchOptions( # <-- change class name - fetchsize=1000, - query_timeout=30, - ), - ) - - # Using ExecuteOptions for executing statements - postgres.execute( - "UPDATE some.mytable SET value = 'new' WHERE key = 'something'", - options=Postgres.ExecuteOptions(query_timeout=30), # <-- change class name - ) - ``` - - For now, `DB.fetch(query, options=...)` and `DB.execute(query, options=...)` can accept `JDBCOptions`, to keep backward compatibility, - but emit a deprecation warning. The old class will be removed in `v1.0.0`. -- Serialize `ColumnDatetimeHWM` to Clickhouse’s `DateTime64(6)` (precision up to microseconds) instead of `DateTime` (precision up to seconds) ([#267](https://github.com/MobileTeleSystems/onetl/pull/267)). - - In previous onETL versions, `ColumnDatetimeHWM` value was rounded to the second, and thus reading some rows that were read in previous runs, - producing duplicates. - - For Clickhouse versions below 21.1 comparing column of type `DateTime` with a value of type `DateTime64` is not supported, returning an empty dataframe. - To avoid this, replace: - ```python - DBReader( - ..., - hwm=DBReader.AutoDetectHWM( - name="my_hwm", - expression="hwm_column", # <-- - ), - ) - ``` - - with: - ```python - DBReader( - ..., - hwm=DBReader.AutoDetectHWM( - name="my_hwm", - expression="CAST(hwm_column AS DateTime64)", # <-- add explicit CAST - ), - ) - ``` -- Pass JDBC connection extra params as `properties` dict instead of URL with query part ([#268](https://github.com/MobileTeleSystems/onetl/pull/268)). - - This allows passing custom connection parameters like `Clickhouse(extra={"custom_http_options": "option1=value1,option2=value2"})` - without need to apply urlencode to parameter value, like `option1%3Dvalue1%2Coption2%3Dvalue2`. - -## Features - -Improve user experience with Kafka messages and Database tables with serialized columns, like JSON/XML. - -- Allow passing custom package version as argument for `DB.get_packages(...)` method of several DB connectors: - : * `Clickhouse.get_packages(package_version=..., apache_http_client_version=...)` ([#249](https://github.com/MobileTeleSystems/onetl/pull/249)). - * `MongoDB.get_packages(scala_version=..., spark_version=..., package_version=...)` ([#255](https://github.com/MobileTeleSystems/onetl/pull/255)). - * `MySQL.get_packages(package_version=...)` ([#253](https://github.com/MobileTeleSystems/onetl/pull/253)). - * `MSSQL.get_packages(java_version=..., package_version=...)` ([#254](https://github.com/MobileTeleSystems/onetl/pull/254)). - * `Oracle.get_packages(java_version=..., package_version=...)` ([#252](https://github.com/MobileTeleSystems/onetl/pull/252)). - * `Postgres.get_packages(package_version=...)` ([#251](https://github.com/MobileTeleSystems/onetl/pull/251)). - * `Teradata.get_packages(package_version=...)` ([#256](https://github.com/MobileTeleSystems/onetl/pull/256)). - - Now users can downgrade or upgrade connection without waiting for next onETL release. Previously only `Kafka` and `Greenplum` supported this feature. -- Add `FileFormat.parse_column(...)` method to several classes: - : * `Avro.parse_column(col)` ([#265](https://github.com/MobileTeleSystems/onetl/pull/265)). - * `JSON.parse_column(col, schema=...)` ([#257](https://github.com/MobileTeleSystems/onetl/pull/257)). - * `CSV.parse_column(col, schema=...)` ([#258](https://github.com/MobileTeleSystems/onetl/pull/258)). - * `XML.parse_column(col, schema=...)` ([#269](https://github.com/MobileTeleSystems/onetl/pull/269)). - - This allows parsing data in `value` field of Kafka message or string/binary column of some table as a nested Spark structure. -- Add `FileFormat.serialize_column(...)` method to several classes: - : * `Avro.serialize_column(col)` ([#265](https://github.com/MobileTeleSystems/onetl/pull/265)). - * `JSON.serialize_column(col)` ([#257](https://github.com/MobileTeleSystems/onetl/pull/257)). - * `CSV.serialize_column(col)` ([#258](https://github.com/MobileTeleSystems/onetl/pull/258)). - - This allows saving Spark nested structures or arrays to `value` field of Kafka message or string/binary column of some table. - -## Improvements - -Few documentation improvements. - -- Replace all `assert` in documentation with doctest syntax. This should make documentation more readable ([#273](https://github.com/MobileTeleSystems/onetl/pull/273)). -- Add generic `Troubleshooting` guide ([#275](https://github.com/MobileTeleSystems/onetl/pull/275)). -- Improve Kafka documentation: - : * Add “Prerequisites” page describing different aspects of connecting to Kafka. - * Improve “Reading from” and “Writing to” page of Kafka documentation, add more examples and usage notes. - * Add “Troubleshooting” page ([#276](https://github.com/MobileTeleSystems/onetl/pull/276)). -- Improve Hive documentation: - : * Add “Prerequisites” page describing different aspects of connecting to Hive. - * Improve “Reading from” and “Writing to” page of Hive documentation, add more examples and recommendations. - * Improve “Executing statements in Hive” page of Hive documentation. ([#278](https://github.com/MobileTeleSystems/onetl/pull/278)). -- Add “Prerequisites” page describing different aspects of using SparkHDFS and SparkS3 connectors. ([#279](https://github.com/MobileTeleSystems/onetl/pull/279)). -- Add note about connecting to Clickhouse cluster. ([#280](https://github.com/MobileTeleSystems/onetl/pull/280)). -- Add notes about versions when specific class/method/attribute/argument was added, renamed or changed behavior ([#282](https://github.com/MobileTeleSystems/onetl/pull/282)). - -## Bug Fixes - -- Fix missing `pysmb` package after installing `pip install onetl[files]` . diff --git a/mddocs/markdown/changelog/0.11.1.md b/mddocs/markdown/changelog/0.11.1.md deleted file mode 100644 index 823afe3be..000000000 --- a/mddocs/markdown/changelog/0.11.1.md +++ /dev/null @@ -1,9 +0,0 @@ -# 0.11.1 (2024-05-29) - -## Features - -- Change `MSSQL.port` default from `1433` to `None`, allowing use of `instanceName` to detect port number. ([#287](https://github.com/MobileTeleSystems/onetl/pull/287)) - -## Bug Fixes - -- Remove `fetchsize` from `JDBC.WriteOptions`. ([#288](https://github.com/MobileTeleSystems/onetl/pull/288)) diff --git a/mddocs/markdown/changelog/0.11.2.md b/mddocs/markdown/changelog/0.11.2.md deleted file mode 100644 index 9278d22f8..000000000 --- a/mddocs/markdown/changelog/0.11.2.md +++ /dev/null @@ -1,5 +0,0 @@ -# 0.11.2 (2024-09-02) - -## Bug Fixes - -- Fix passing `Greenplum(extra={"options": ...})` during read/write operations. ([#308](https://github.com/MobileTeleSystems/onetl/pull/308)) diff --git a/mddocs/markdown/changelog/0.12.0.md b/mddocs/markdown/changelog/0.12.0.md deleted file mode 100644 index d212c0062..000000000 --- a/mddocs/markdown/changelog/0.12.0.md +++ /dev/null @@ -1,48 +0,0 @@ -# 0.12.0 (2024-09-03) - -## Breaking Changes - -- Change connection URL used for generating HWM names of S3 and Samba sources: - : * `smb://host:port` -> `smb://host:port/share` - * `s3://host:port` -> `s3://host:port/bucket` ([#304](https://github.com/MobileTeleSystems/onetl/pull/304)) -- Update DB connectors/drivers to latest versions: - : * Clickhouse `0.6.0-patch5` → `0.6.5` - * MongoDB `10.3.0` → `10.4.0` - * MSSQL `12.6.2` → `12.8.1` - * MySQL `8.4.0` → `9.0.0` - * Oracle `23.4.0.24.05` → `23.5.0.24.07` - * Postgres `42.7.3` → `42.7.4` -- Update `Excel` package from `0.20.3` to `0.20.4`, to include Spark 3.5.1 support. ([#306](https://github.com/MobileTeleSystems/onetl/pull/306)) - -## Features - -- Add support for specifying file formats (`ORC`, `Parquet`, `CSV`, etc.) in `HiveWriteOptions.format` ([#292](https://github.com/MobileTeleSystems/onetl/pull/292)): - ```python - Hive.WriteOptions(format=ORC(compression="snappy")) - ``` -- Collect Spark execution metrics in following methods, and log then in DEBUG mode: - : * `DBWriter.run()` - * `FileDFWriter.run()` - * `Hive.sql()` - * `Hive.execute()` - - This is implemented using custom `SparkListener` which wraps the entire method call, and - then report collected metrics. But these metrics sometimes may be missing due to Spark architecture, - so they are not reliable source of information. That’s why logs are printed only in DEBUG mode, and - are not returned as method call result. ([#303](https://github.com/MobileTeleSystems/onetl/pull/303)) -- Generate default `jobDescription` based on currently executed method. Examples: - : * `DBWriter.run(schema.table) -> Postgres[host:5432/database]` - * `MongoDB[localhost:27017/admin] -> DBReader.has_data(mycollection)` - * `Hive[cluster].execute()` - - If user already set custom `jobDescription`, it will left intact. ([#304](https://github.com/MobileTeleSystems/onetl/pull/304)) -- Add log.info about JDBC dialect usage ([#305](https://github.com/MobileTeleSystems/onetl/pull/305)): - ```text - |MySQL| Detected dialect: 'org.apache.spark.sql.jdbc.MySQLDialect' - ``` -- Log estimated size of in-memory dataframe created by `JDBC.fetch` and `JDBC.execute` methods. ([#303](https://github.com/MobileTeleSystems/onetl/pull/303)) - -## Bug Fixes - -- Fix passing `Greenplum(extra={"options": ...})` during read/write operations. ([#308](https://github.com/MobileTeleSystems/onetl/pull/308)) -- Do not raise exception if yield-based hook whas something past (and only one) `yield`. diff --git a/mddocs/markdown/changelog/0.12.1.md b/mddocs/markdown/changelog/0.12.1.md deleted file mode 100644 index 3575634d5..000000000 --- a/mddocs/markdown/changelog/0.12.1.md +++ /dev/null @@ -1,17 +0,0 @@ -# 0.12.1 (2024-10-28) - -## Features - -- Log detected JDBC dialect while using `DBWriter`. - -## Bug Fixes - -- Fix `SparkMetricsRecorder` failing when receiving `SparkListenerTaskEnd` without `taskMetrics` (e.g. executor was killed by OOM). ([#313](https://github.com/MobileTeleSystems/onetl/pull/313)) -- Call `kinit` before checking for HDFS active namenode. -- Wrap `kinit` with `threading.Lock` to avoid multithreading issues. -- Immediately show `kinit` errors to user, instead of hiding them. -- Use `AttributeError` instead of `ImportError` in module’s `__getattr__` method, to make code compliant with Python spec. - -## Doc only Changes - -- Add note about [spark-dialect-extension](https://github.com/MobileTeleSystems/spark-dialect-extension) package to Clickhouse connector documentation. ([#310](https://github.com/MobileTeleSystems/onetl/pull/310)) diff --git a/mddocs/markdown/changelog/0.12.2.md b/mddocs/markdown/changelog/0.12.2.md deleted file mode 100644 index 23a8d383d..000000000 --- a/mddocs/markdown/changelog/0.12.2.md +++ /dev/null @@ -1,18 +0,0 @@ -# 0.12.2 (2024-11-12) - -## Improvements - -- Change Spark `jobDescription` for DBReader & FileDFReader from `DBReader.run() -> Connection` to `Connection -> DBReader.run()`. - -## Bug Fixes - -- Fix `log_hwm` result for `KeyValueIntHWM` (used by Kafka). ([#316](https://github.com/MobileTeleSystems/onetl/pull/316)) -- Fix `log_collection` hiding values of `Kafka.addresses` in logs with `INFO` level. ([#316](https://github.com/MobileTeleSystems/onetl/pull/316)) - -## Dependencies - -- Allow using [etl-entities==2.4.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.4.0). - -## Doc only Changes - -- Fix links to MSSQL date & time type documentation. diff --git a/mddocs/markdown/changelog/0.12.3.md b/mddocs/markdown/changelog/0.12.3.md deleted file mode 100644 index 741c74e1f..000000000 --- a/mddocs/markdown/changelog/0.12.3.md +++ /dev/null @@ -1,5 +0,0 @@ -# 0.12.3 (2024-11-22) - -## Bug Fixes - -- Allow passing table names in format `schema."table.with.dots"` to `DBReader(source=...)` and `DBWriter(target=...)`. diff --git a/mddocs/markdown/changelog/0.12.4.md b/mddocs/markdown/changelog/0.12.4.md deleted file mode 100644 index 3ebc57a87..000000000 --- a/mddocs/markdown/changelog/0.12.4.md +++ /dev/null @@ -1,5 +0,0 @@ -# 0.12.4 (2024-11-27) - -## Bug Fixes - -- Fix `DBReader(conn=oracle, options={"partitioning_mode": "hash"})` lead to data skew in last partition due to wrong `ora_hash` usage. ([#319](https://github.com/MobileTeleSystems/onetl/pull/319)) diff --git a/mddocs/markdown/changelog/0.12.5.md b/mddocs/markdown/changelog/0.12.5.md deleted file mode 100644 index c542a50fd..000000000 --- a/mddocs/markdown/changelog/0.12.5.md +++ /dev/null @@ -1,13 +0,0 @@ -# 0.12.5 (2024-12-03) - -## Improvements - -- Use `sipHash64` instead of `md5` in Clickhouse for reading data with `{"partitioning_mode": "hash"}`, as it is 5 times faster. -- Use `hashtext` instead of `md5` in Postgres for reading data with `{"partitioning_mode": "hash"}`, as it is 3-5 times faster. -- Use `BINARY_CHECKSUM` instead of `HASHBYTES` in MSSQL for reading data with `{"partitioning_mode": "hash"}`, as it is 5 times faster. - -## Big fixes - -- In JDBC sources wrap `MOD(partitionColumn, numPartitions)` with `ABS(...)` to make al returned values positive. This prevents data skew. -- Fix reading table data from MSSQL using `{"partitioning_mode": "hash"}` with `partitionColumn` of integer type. -- Fix reading table data from Postgres using `{"partitioning_mode": "hash"}` lead to data skew (all the data was read into one Spark partition). diff --git a/mddocs/markdown/changelog/0.13.0.md b/mddocs/markdown/changelog/0.13.0.md deleted file mode 100644 index 0b7256299..000000000 --- a/mddocs/markdown/changelog/0.13.0.md +++ /dev/null @@ -1,197 +0,0 @@ -# 0.13.0 (2025-02-24) - -🎉 3 years since first release 0.1.0 🎉 - -## Breaking Changes - -- Add Python 3.13. support. ([#298](https://github.com/MobileTeleSystems/onetl/pull/298)) -- Change the logic of `FileConnection.walk` and `FileConnection.list_dir`. ([#327](https://github.com/MobileTeleSystems/onetl/pull/327)) - - Previously `limits.stops_at(path) == True` considered as “return current file and stop”, and could lead to exceeding some limit. - Not it means “stop immediately”. -- Change default value for `FileDFWriter.Options(if_exists=...)` from `error` to `append`, - to make it consistent with other `.Options()` classes within onETL. ([#343](https://github.com/MobileTeleSystems/onetl/pull/343)) - -## Features - -- Add support for `FileModifiedTimeHWM` HWM class (see [etl-entities 2.5.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.5.0)): - ```python - from etl_entitites.hwm import FileModifiedTimeHWM - from onetl.file import FileDownloader - from onetl.strategy import IncrementalStrategy - - downloader = FileDownloader( - ..., - hwm=FileModifiedTimeHWM(name="somename"), - ) - - with IncrementalStrategy(): - downloader.run() - ``` -- Introduce `FileSizeRange(min=..., max=...)` filter class. ([#325](https://github.com/MobileTeleSystems/onetl/pull/325)) - - Now users can set `FileDownloader` / `FileMover` to download/move only files with specific file size range: - ```python - from onetl.file import FileDownloader - from onetl.file.filter import FileSizeRange - - downloader = FileDownloader( - ..., - filters=[FileSizeRange(min="10KiB", max="1GiB")], - ) - ``` -- Introduce `TotalFilesSize(...)` limit class. ([#326](https://github.com/MobileTeleSystems/onetl/pull/326)) - - Now users can set `FileDownloader` / `FileMover` to stop downloading/moving files after reaching a certain amount of data: - ```python - from datetime import datetime, timedelta - from onetl.file import FileDownloader - from onetl.file.limit import TotalFilesSize - - downloader = FileDownloader( - ..., - limits=[TotalFilesSize("1GiB")], - ) - ``` -- Implement `FileModifiedTime(since=..., until=...)` file filter. ([#330](https://github.com/MobileTeleSystems/onetl/pull/330)) - - Now users can set `FileDownloader` / `FileMover` to download/move only files with specific file modification time: - ```python - from datetime import datetime, timedelta - from onetl.file import FileDownloader - from onetl.file.filter import FileModifiedTime - - downloader = FileDownloader( - ..., - filters=[FileModifiedTime(before=datetime.now() - timedelta(hours=1))], - ) - ``` -- Add `SparkS3.get_exclude_packages()` and `Kafka.get_exclude_packages()` methods. ([#341](https://github.com/MobileTeleSystems/onetl/pull/341)) - - Using them allows to skip downloading dependencies not required by this specific connector, or which are already a part of Spark/PySpark: - ```python - from onetl.connection import SparkS3, Kafka - - maven_packages = [ - *SparkS3.get_packages(spark_version="3.5.4"), - *Kafka.get_packages(spark_version="3.5.4"), - ] - exclude_packages = SparkS3.get_exclude_packages() + Kafka.get_exclude_packages() - spark = ( - SparkSession.builder.appName("spark_app_onetl_demo") - .config("spark.jars.packages", ",".join(maven_packages)) - .config("spark.jars.excludes", ",".join(exclude_packages)) - .getOrCreate() - ) - ``` - -## Improvements - -- All DB connections opened by `JDBC.fetch(...)`, `JDBC.execute(...)` or `JDBC.check()` - are immediately closed after the statements is executed. ([#334](https://github.com/MobileTeleSystems/onetl/pull/334)) - - Previously Spark session with `master=local[3]` actually opened up to 5 connections to target DB - one for `JDBC.check()`, - another for Spark driver interaction with DB to create tables, and one for each Spark executor. Now only max 4 connections are opened, - as `JDBC.check()` does not hold opened connection. - - This is important for RDBMS like Postgres or Greenplum where number of connections is strictly limited and limit is usually quite low. -- Set up `ApplicationName` (client info) for Clickhouse, MongoDB, MSSQL, MySQL and Oracle. ([#339](https://github.com/MobileTeleSystems/onetl/pull/339), [#248](https://github.com/MobileTeleSystems/onetl/pull/248)) - - Also update `ApplicationName` format for Greenplum, Postgres, Kafka and SparkS3. - Now all connectors have the same `ApplicationName` format: `${spark.applicationId} ${spark.appName} onETL/${onetl.version} Spark/${spark.version}` - - The only connections not sending `ApplicationName` are Teradata and FileConnection implementations. -- Now `DB.check()` will test connection availability not only on Spark driver, but also from some Spark executor. ([#346](https://github.com/MobileTeleSystems/onetl/pull/346)) - - This allows to fail immediately if Spark driver host has network access to target DB, but Spark executors have not. - - #### NOTE - Now `Greenplum.check()` requires the same user grants as `DBReader(connection=greenplum)`: - ```sql - -- yes, "writable" for reading data from GP, it's not a mistake - ALTER USER username CREATEEXTTABLE(type = 'writable', protocol = 'gpfdist'); - - -- for both reading and writing to GP - -- ALTER USER username CREATEEXTTABLE(type = 'readable', protocol = 'gpfdist') CREATEEXTTABLE(type = 'writable', protocol = 'gpfdist'); - ``` - - Please ask your Greenplum administrators to provide these grants. - -## Bug Fixes - -- Avoid suppressing Hive Metastore errors while using `DBWriter`. ([#329](https://github.com/MobileTeleSystems/onetl/pull/329)) - - Previously this was implemented as: - ```python - try: - spark.sql(f"SELECT * FROM {table}") - table_exists = True - except Exception: - table_exists = False - ``` - - If Hive Metastore was overloaded and responded with an exception, it was considered as non-existing table, resulting - to full table override instead of append or override only partitions subset. -- Fix using onETL to write data to PostgreSQL or Greenplum instances behind *pgbouncer* with `pool_mode=transaction`. ([#336](https://github.com/MobileTeleSystems/onetl/pull/336)) - - Previously `Postgres.check()` opened a read-only transaction, pgbouncer changed the entire connection type from read-write to read-only, - and when `DBWriter.run(df)` executed in read-only connection, producing errors like: - ```default - org.postgresql.util.PSQLException: ERROR: cannot execute INSERT in a read-only transaction - org.postgresql.util.PSQLException: ERROR: cannot execute TRUNCATE TABLE in a read-only transaction - ``` - - Added a workaround by passing `readOnly=True` to JDBC params for read-only connections, so pgbouncer may differ read-only and read-write connections properly. - - After upgrading onETL 0.13.x or higher the same error still may appear of pgbouncer still holds read-only connections and returns them for DBWriter. - To this this, user can manually convert read-only connection to read-write: - ```python - postgres.execute("BEGIN READ WRITE;") # <-- add this line - DBWriter(...).run() - ``` - - After all connections in pgbouncer pool were converted from read-only to read-write, and error fixed, this additional line could be removed. - - See [Postgres JDBC driver documentation](https://jdbc.postgresql.org/documentation/use/). -- Fix `MSSQL.fetch(...)` and `MySQL.fetch(...)` opened a read-write connection instead of read-only. ([#337](https://github.com/MobileTeleSystems/onetl/pull/337)) - - Now this is fixed: - : * `MSSQL.fetch(...)` establishes connection with `ApplicationIntent=ReadOnly`. - * `MySQL.fetch(...)` calls `SET SESSION TRANSACTION READ ONLY` statement. -- Fixed passing multiple filters to `FileDownloader` and `FileMover`. ([#338](https://github.com/MobileTeleSystems/onetl/pull/338)) - If was caused by sorting filters list in internal logging method, but `FileFilter` subclasses are not sortable. -- Fix a false warning about a lof of parallel connections to Grenplum. ([#342](https://github.com/MobileTeleSystems/onetl/pull/342)) - - Creating Spark session with `.master("local[5]")` may open up to 6 connections to Greenplum (=number of Spark executors + 1 for driver), - but onETL instead used number of *CPU cores* on the host as a number of parallel connections. - - This lead to showing a false warning that number of Greenplum connections is too high, - which actually should be the case only if number of executors is higher than 30. -- Fix MongoDB trying to use current database name as `authSource`. ([#347](https://github.com/MobileTeleSystems/onetl/pull/347)) - - Use default connector value which is `admin` database. Previous onETL versions could be fixed by: - ```python - from onetl.connection import MongoDB - - mongodb = MongoDB( - ..., - database="mydb", - extra={ - "authSource": "admin", - }, - ) - ``` - -## Dependencies - -- Minimal `etl-entities` version is now [2.5.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.5.0). ([#331](https://github.com/MobileTeleSystems/onetl/pull/331)) -- Update DB connectors/drivers to latest versions: ([#345](https://github.com/MobileTeleSystems/onetl/pull/345)) - : * Clickhouse `0.6.5` → `0.7.2` - * MongoDB `10.4.0` → `10.4.1` - * MySQL `9.0.0` → `9.2.0` - * Oracle `23.5.0.24.07` → `23.7.0.25.01` - * Postgres `42.7.4` → `42.7.5` - -## Doc only Changes - -- Split large code examples to tabs. ([#344](https://github.com/MobileTeleSystems/onetl/pull/344)) diff --git a/mddocs/markdown/changelog/0.13.1.md b/mddocs/markdown/changelog/0.13.1.md deleted file mode 100644 index e8eb120d2..000000000 --- a/mddocs/markdown/changelog/0.13.1.md +++ /dev/null @@ -1,9 +0,0 @@ -# 0.13.1 (2025-03-06) - -## Bug Fixes - -In 0.13.0, using `DBWriter(connection=hive, target="SOMEDB.SOMETABLE")` lead to executing `df.write.saveAsTable()` -instead of `df.write.insertInto()` if target table `somedb.sometable` already exist. - -This is caused by table name normalization (Hive uses lower-case names), which wasn’t properly handled by method used for checking table existence. -([#350](https://github.com/MobileTeleSystems/onetl/pull/350)) diff --git a/mddocs/markdown/changelog/0.13.3.md b/mddocs/markdown/changelog/0.13.3.md deleted file mode 100644 index 1aa289b49..000000000 --- a/mddocs/markdown/changelog/0.13.3.md +++ /dev/null @@ -1,5 +0,0 @@ -# 0.13.3 (2025-03-11) - -## Dependencies - -Allow using [etl-entities 2.6.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.6.0). diff --git a/mddocs/markdown/changelog/0.13.4.md b/mddocs/markdown/changelog/0.13.4.md deleted file mode 100644 index 10f695e0c..000000000 --- a/mddocs/markdown/changelog/0.13.4.md +++ /dev/null @@ -1,10 +0,0 @@ -# 0.13.4 (2025-03-20) - -## Doc only Changes - -- Prefer `ReadOptions(partitionColumn=..., numPartitions=..., queryTimeout=...)` - instead of `ReadOptions(partition_column=..., num_partitions=..., query_timeout=...)`, - to match Spark documentation. ([#352](https://github.com/MobileTeleSystems/onetl/pull/352)) -- Prefer `WriteOptions(if_exists=...)` instead of `WriteOptions(mode=...)` for IDE suggestions. ([#354](https://github.com/MobileTeleSystems/onetl/pull/354)) -- Document all options of supported file formats. - ([#355](https://github.com/MobileTeleSystems/onetl/pull/355), [#356](https://github.com/MobileTeleSystems/onetl/pull/356), [#357](https://github.com/MobileTeleSystems/onetl/pull/357), [#358](https://github.com/MobileTeleSystems/onetl/pull/358), [#359](https://github.com/MobileTeleSystems/onetl/pull/359), [#360](https://github.com/MobileTeleSystems/onetl/pull/360), [#361](https://github.com/MobileTeleSystems/onetl/pull/361), [#362](https://github.com/MobileTeleSystems/onetl/pull/362)) diff --git a/mddocs/markdown/changelog/0.7.0.md b/mddocs/markdown/changelog/0.7.0.md deleted file mode 100644 index 3f896b82b..000000000 --- a/mddocs/markdown/changelog/0.7.0.md +++ /dev/null @@ -1,211 +0,0 @@ -# 0.7.0 (2023-05-15) - -## 🎉 onETL is now open source 🎉 - -That was long road, but we finally did it! - -## Breaking Changes - -* Changed installation method. - - **TL;DR What should I change to restore previous behavior** - - Simple way: - - | onETL < 0.7.0 | onETL >= 0.7.0 | - |-------------------|-----------------------------------| - | pip install onetl | pip install onetl[files,kerberos] | - - Right way - enumerate connectors should be installed: - ```bash - pip install onetl[hdfs,ftp,kerberos] # except DB connections - ``` - - **Details** - - In onetl<0.7 the package installation looks like: - ```bash - pip install onetl - ``` - - But this includes all dependencies for all connectors, even if user does not use them. - This caused some issues, for example user had to install Kerberos libraries to be able to install onETL, even if user uses only S3 (without Kerberos support). - - Since 0.7.0 installation process was changed: - ```bash - pip install onetl # minimal installation, only onETL core - # there is no extras for DB connections because they are using Java packages which are installed in runtime - - pip install onetl[ftp,ftps,hdfs,sftp,s3,webdav] # install dependencies for specified file connections - pip install onetl[files] # install dependencies for all file connections - - pip install onetl[kerberos] # Kerberos auth support - pip install onetl[spark] # install PySpark to use DB connections - - pip install onetl[spark,kerberos,files] # all file connections + Kerberos + PySpark - pip install onetl[all] # alias for previous case - ``` - - There are corresponding documentation items for each extras. - - Also onETL checks that some requirements are missing, and raises exception with recommendation how to install them: - ```text - Cannot import module "pyspark". - - Since onETL v0.7.0 you should install package as follows: - pip install onetl[spark] - - or inject PySpark to sys.path in some other way BEFORE creating MongoDB instance. - ``` - - ```text - Cannot import module "ftputil". - - Since onETL v0.7.0 you should install package as follows: - pip install onetl[ftp] - - or - pip install onetl[files] - ``` -* Added new `cluster` argument to `Hive` and `HDFS` connections. - - `Hive` qualified name (used in HWM) contains cluster name. But in onETL<0.7.0 cluster name had hard coded value `rnd-dwh` which was not OK for some users. - - `HDFS` connection qualified name contains host (active namenode of Hadoop cluster), but its value can change over time, leading to creating of new HWM. - - Since onETL 0.7.0 both `Hive` and `HDFS` connections have `cluster` attribute which can be set to a specific cluster name. - For `Hive` it is mandatory, for `HDFS` it can be omitted (using host as a fallback). - - But passing cluster name every time could lead to errors. - - Now `Hive` and `HDFS` have nested class named `slots` with methods: - * `normalize_cluster_name` - * `get_known_clusters` - * `get_current_cluster` - * `normalize_namenode_host` (only `HDFS`) - * `get_cluster_namenodes` (only `HDFS`) - * `get_webhdfs_port` (only `HDFS`) - * `is_namenode_active` (only `HDFS`) - - And new method `HDFS.get_current` / `Hive.get_current`. - - Developers can implement hooks validating user input or substituting values for automatic cluster detection. - This should improve user experience while using these connectors. - - See slots documentation. -* Update JDBC connection drivers. - * Greenplum `2.1.3` → `2.1.4`. - * MSSQL `10.2.1.jre8` → `12.2.0.jre8`. Minimal supported version of MSSQL is now 2014 instead 2021. - * MySQL `8.0.30` → `8.0.33`: - \* Package was renamed `mysql:mysql-connector-java` → `com.mysql:mysql-connector-j`. - \* Driver class was renamed `com.mysql.jdbc.Driver` → `com.mysql.cj.jdbc.Driver`. - * Oracle `21.6.0.0.1` → `23.2.0.0`. - * Postgres `42.4.0` → `42.6.0`. - * Teradata `17.20.00.08` → `17.20.00.15`: - \* Package was renamed `com.teradata.jdbc:terajdbc4` → `com.teradata.jdbc:terajdbc`. - \* Teradata driver is now published to Maven. - - See [#31](https://github.com/MobileTeleSystems/onetl/pull/31). - -## Features - -* Added MongoDB connection. - - Using official [MongoDB connector for Spark v10](https://www.mongodb.com/docs/spark-connector/current/). Only Spark 3.2+ is supported. - - There are some differences between MongoDB and other database sources: - * Instead of `mongodb.sql` method there is `mongodb.pipeline`. - * No methods `mongodb.fetch` and `mongodb.execute`. - * `DBReader.hint` and `DBReader.where` have different types than in SQL databases: - - ```python - where = { - "col1": { - "$eq": 10, - }, - } - - hint = { - "col1": 1, - } - ``` - - * Because MongoDB does not have schemas of collections, but Spark cannot create dataframe with dynamic schema, new option `DBReader.df_schema` was introduced. - It is mandatory for MongoDB, but optional for other sources. - * Currently DBReader cannot be used with MongoDB and hwm expression, e.g. `hwm_column=("mycolumn", {"$cast": {"col1": "date"}})` - - Because there are no tables in MongoDB, some options were renamed in core classes: - * `DBReader(table=...)` → `DBReader(source=...)` - * `DBWriter(table=...)` → `DBWriter(target=...)` - - Old names can be used too, they are not deprecated ([#30](https://github.com/MobileTeleSystems/onetl/pull/30)). -* Added option for disabling some plugins during import. - - Previously if some plugin were failing during the import, the only way to import onETL would be to disable all plugins - using environment variable. - - Now there are several variables with different behavior: - * `ONETL_PLUGINS_ENABLED=false` - disable all plugins autoimport. Previously it was named `ONETL_ENABLE_PLUGINS`. - * `ONETL_PLUGINS_BLACKLIST=plugin-name,another-plugin` - set list of plugins which should NOT be imported automatically. - * `ONETL_PLUGINS_WHITELIST=plugin-name,another-plugin` - set list of plugins which should ONLY be imported automatically. - - Also we improved exception message with recommendation how to disable a failing plugin: - ```text - Error while importing plugin 'mtspark' from package 'mtspark' v4.0.0. - - Statement: - import mtspark.onetl - - Check if plugin is compatible with current onETL version 0.7.0. - - You can disable loading this plugin by setting environment variable: - ONETL_PLUGINS_BLACKLIST='mtspark,failing-plugin' - - You can also define a whitelist of packages which can be loaded by onETL: - ONETL_PLUGINS_WHITELIST='not-failing-plugin1,not-failing-plugin2' - - Please take into account that plugin name may differ from package or module name. - See package metadata for more details - ``` - -## Improvements - -* Added compatibility with Python 3.11 and PySpark 3.4.0. - - File connections were OK, but `jdbc.fetch` and `jdbc.execute` were failing. Fixed in [#28](https://github.com/MobileTeleSystems/onetl/pull/28). -* Added check for missing Java packages. - - Previously if DB connection tried to use some Java class which were not loaded into Spark version, it raised an exception - with long Java stacktrace. Most users failed to interpret this trace. - - Now onETL shows the following error message: - ```text - |Spark| Cannot import Java class 'com.mongodb.spark.sql.connector.MongoTableProvider'. - - It looks like you've created Spark session without this option: - SparkSession.builder.config("spark.jars.packages", MongoDB.package_spark_3_2) - - Please call `spark.stop()`, restart the interpreter, - and then create new SparkSession with proper options. - ``` -* Documentation improvements. - * Changed documentation site theme - using [furo](https://github.com/pradyunsg/furo) - instead of default [ReadTheDocs](https://github.com/readthedocs/sphinx_rtd_theme). - - New theme supports wide screens and dark mode. - See [#10](https://github.com/MobileTeleSystems/onetl/pull/10). - * Now each connection class have compatibility table for Spark + Java + Python. - * Added global compatibility table for Spark + Java + Python + Scala. - -## Bug Fixes - -* Fixed several SFTP issues. - * If SSH config file `~/.ssh/config` contains some options not recognized by Paramiko (unknown syntax, unknown option name), - previous versions were raising exception until fixing or removing this file. Since 0.7.0 exception is replaced with warning. - * If user passed `host_key_check=False` but server changed SSH keys, previous versions raised exception until new key is accepted. - Since 0.7.0 exception is replaced with warning if option value is `False`. - - Fixed in [#19](https://github.com/MobileTeleSystems/onetl/pull/19). -* Fixed several S3 issues. - - There was a bug in S3 connection which prevented handling files in the root of a bucket - they were invisible for the connector. Fixed in [#29](https://github.com/MobileTeleSystems/onetl/pull/29). diff --git a/mddocs/markdown/changelog/0.7.1.md b/mddocs/markdown/changelog/0.7.1.md deleted file mode 100644 index fc423f756..000000000 --- a/mddocs/markdown/changelog/0.7.1.md +++ /dev/null @@ -1,31 +0,0 @@ -# 0.7.1 (2023-05-23) - -## Bug Fixes - -* Fixed `setup_logging` function. - - In onETL==0.7.0 calling `onetl.log.setup_logging()` broke the logging: - ```text - Traceback (most recent call last): - File "/opt/anaconda/envs/py39/lib/python3.9/logging/__init__.py", line 434, in format - return self._format(record) - File "/opt/anaconda/envs/py39/lib/python3.9/logging/__init__.py", line 430, in _format - return self._fmt % record.dict - KeyError: 'levelname:8s' - ``` -* Fixed installation examples. - - In onETL==0.7.0 there are examples of installing onETL with extras: - ```bash - pip install onetl[files, kerberos, spark] - ``` - - But pip fails to install such package: - ```text - ERROR: Invalid requirement: 'onet[files,' - ``` - - This is because of spaces in extras clause. Fixed: - ```bash - pip install onetl[files,kerberos,spark] - ``` diff --git a/mddocs/markdown/changelog/0.7.2.md b/mddocs/markdown/changelog/0.7.2.md deleted file mode 100644 index 80bb2cfc5..000000000 --- a/mddocs/markdown/changelog/0.7.2.md +++ /dev/null @@ -1,33 +0,0 @@ -# 0.7.2 (2023-05-24) - -## Dependencies - -* Limited `typing-extensions` version. - - `typing-extensions==4.6.0` release contains some breaking changes causing errors like: - ```text - Traceback (most recent call last): - File "/Users/project/lib/python3.9/typing.py", line 852, in __subclasscheck__ - return issubclass(cls, self.__origin__) - TypeError: issubclass() arg 1 must be a class - ``` - - `typing-extensions==4.6.1` was causing another error: - ```text - Traceback (most recent call last): - File "/home/maxim/Repo/typing_extensions/1.py", line 33, in - isinstance(file, ContainsException) - File "/home/maxim/Repo/typing_extensions/src/typing_extensions.py", line 599, in __instancecheck__ - if super().__instancecheck__(instance): - File "/home/maxim/.pyenv/versions/3.7.8/lib/python3.7/abc.py", line 139, in __instancecheck__ - return _abc_instancecheck(cls, instance) - File "/home/maxim/Repo/typing_extensions/src/typing_extensions.py", line 583, in __subclasscheck__ - return super().__subclasscheck__(other) - File "/home/maxim/.pyenv/versions/3.7.8/lib/python3.7/abc.py", line 143, in __subclasscheck__ - return _abc_subclasscheck(cls, subclass) - File "/home/maxim/Repo/typing_extensions/src/typing_extensions.py", line 661, in _proto_hook - and other._is_protocol - AttributeError: type object 'PathWithFailure' has no attribute '_is_protocol' - ``` - - We updated requirements with `typing-extensions<4.6` until fixing compatibility issues. diff --git a/mddocs/markdown/changelog/0.8.0.md b/mddocs/markdown/changelog/0.8.0.md deleted file mode 100644 index 3fe438549..000000000 --- a/mddocs/markdown/changelog/0.8.0.md +++ /dev/null @@ -1,137 +0,0 @@ -# 0.8.0 (2023-05-31) - -## Breaking Changes - -- Rename methods of `FileConnection` classes: - * `get_directory` → `resolve_dir` - * `get_file` → `resolve_file` - * `listdir` → `list_dir` - * `mkdir` → `create_dir` - * `rmdir` → `remove_dir` - - New naming should be more consistent. - - They were undocumented in previous versions, but someone could use these methods, so this is a breaking change. ([#36](https://github.com/MobileTeleSystems/onetl/pull/36)) -- Deprecate `onetl.core.FileFilter` class, replace it with new classes: - * `onetl.file.filter.Glob` - * `onetl.file.filter.Regexp` - * `onetl.file.filter.ExcludeDir` - - Old class will be removed in v1.0.0. ([#43](https://github.com/MobileTeleSystems/onetl/pull/43)) -- Deprecate `onetl.core.FileLimit` class, replace it with new class `onetl.file.limit.MaxFilesCount`. - - Old class will be removed in v1.0.0. ([#44](https://github.com/MobileTeleSystems/onetl/pull/44)) -- Change behavior of `BaseFileLimit.reset` method. - - This method should now return `self` instead of `None`. - Return value could be the same limit object or a copy, this is an implementation detail. ([#44](https://github.com/MobileTeleSystems/onetl/pull/44)) -- Replaced `FileDownloader.filter` and `.limit` with new options `.filters` and `.limits`: - ```python - FileDownloader( - ..., - filter=FileFilter(glob="*.txt", exclude_dir="/path"), - limit=FileLimit(count_limit=10), - ) - ``` - - ```python - FileDownloader( - ..., - filters=[Glob("*.txt"), ExcludeDir("/path")], - limits=[MaxFilesCount(10)], - ) - ``` - - This allows to developers to implement their own filter and limit classes, and combine them with existing ones. - - Old behavior still supported, but it will be removed in v1.0.0. ([#45](https://github.com/MobileTeleSystems/onetl/pull/45)) -- Removed default value for `FileDownloader.limits`, user should pass limits list explicitly. ([#45](https://github.com/MobileTeleSystems/onetl/pull/45)) -- Move classes from module `onetl.core`: - ```python - from onetl.core import DBReader - from onetl.core import DBWriter - from onetl.core import FileDownloader - from onetl.core import FileUploader - ``` - - with new modules `onetl.db` and `onetl.file`: - ```python - from onetl.db import DBReader - from onetl.db import DBWriter - - from onetl.file import FileDownloader - from onetl.file import FileUploader - ``` - - Imports from old module `onetl.core` still can be used, but marked as deprecated. Module will be removed in v1.0.0. ([#46](https://github.com/MobileTeleSystems/onetl/pull/46)) - -## Features - -- Add `rename_dir` method. - - Method was added to following connections: - * `FTP` - * `FTPS` - * `HDFS` - * `SFTP` - * `WebDAV` - - It allows to rename/move directory to new path with all its content. - - `S3` does not have directories, so there is no such method in that class. ([#40](https://github.com/MobileTeleSystems/onetl/pull/40)) -- Add `onetl.file.FileMover` class. - - It allows to move files between directories of remote file system. - Signature is almost the same as in `FileDownloader`, but without HWM support. ([#42](https://github.com/MobileTeleSystems/onetl/pull/42)) - -## Improvements - -- Document all public methods in `FileConnection` classes: - * `download_file` - * `resolve_dir` - * `resolve_file` - * `get_stat` - * `is_dir` - * `is_file` - * `list_dir` - * `create_dir` - * `path_exists` - * `remove_file` - * `rename_file` - * `remove_dir` - * `upload_file` - * `walk` ([#39](https://github.com/MobileTeleSystems/onetl/pull/39)) -- Update documentation of `check` method of all connections - add usage example and document result type. ([#39](https://github.com/MobileTeleSystems/onetl/pull/39)) -- Add new exception type `FileSizeMismatchError`. - - Methods `connection.download_file` and `connection.upload_file` now raise new exception type instead of `RuntimeError`, - if target file after download/upload has different size than source. ([#39](https://github.com/MobileTeleSystems/onetl/pull/39)) -- Add new exception type `DirectoryExistsError` - it is raised if target directory already exists. ([#40](https://github.com/MobileTeleSystems/onetl/pull/40)) -- Improved `FileDownloader` / `FileUploader` exception logging. - - If `DEBUG` logging is enabled, print exception with stacktrace instead of - printing only exception message. ([#42](https://github.com/MobileTeleSystems/onetl/pull/42)) -- Updated documentation of `FileUploader`. - * Class does not support read strategies, added note to documentation. - * Added examples of using `run` method with explicit files list passing, both absolute and relative paths. - * Fix outdated imports and class names in examples. ([#42](https://github.com/MobileTeleSystems/onetl/pull/42)) -- Updated documentation of `DownloadResult` class - fix outdated imports and class names. ([#42](https://github.com/MobileTeleSystems/onetl/pull/42)) -- Improved file filters documentation section. - - Document interface class `onetl.base.BaseFileFilter` and function `match_all_filters`. ([#43](https://github.com/MobileTeleSystems/onetl/pull/43)) -- Improved file limits documentation section. - - Document interface class `onetl.base.BaseFileLimit` and functions `limits_stop_at` / `limits_reached` / `reset_limits`. ([#44](https://github.com/MobileTeleSystems/onetl/pull/44)) -- Added changelog. - - Changelog is generated from separated news files using [towncrier](https://pypi.org/project/towncrier/). ([#47](https://github.com/MobileTeleSystems/onetl/pull/47)) - -## Misc - -- Improved CI workflow for tests. - * If developer haven’t changed source core of a specific connector or its dependencies, - run tests only against maximum supported versions of Spark, Python, Java and db/file server. - * If developed made some changes in a specific connector, or in core classes, or in dependencies, - run tests for both minimal and maximum versions. - * Once a week run all aganst for minimal and latest versions to detect breaking changes in dependencies - * Minimal tested Spark version is 2.3.1 instead on 2.4.8. ([#32](https://github.com/MobileTeleSystems/onetl/pull/32)) diff --git a/mddocs/markdown/changelog/0.8.1.md b/mddocs/markdown/changelog/0.8.1.md deleted file mode 100644 index d3bd32564..000000000 --- a/mddocs/markdown/changelog/0.8.1.md +++ /dev/null @@ -1,34 +0,0 @@ -# 0.8.1 (2023-07-10) - -## Features - -- Add `@slot` decorator to public methods of: - * `DBConnection` - * `FileConnection` - * `DBReader` - * `DBWriter` - * `FileDownloader` - * `FileUploader` - * `FileMover` ([#49](https://github.com/MobileTeleSystems/onetl/pull/49)) -- Add `workers` field to `FileDownloader` / `FileUploader` / `FileMover`. `Options` classes. - - This allows to speed up all file operations using parallel threads. ([#57](https://github.com/MobileTeleSystems/onetl/pull/57)) - -## Improvements - -- Add documentation for HWM store `.get` and `.save` methods. ([#49](https://github.com/MobileTeleSystems/onetl/pull/49)) -- Improve Readme: - * Move `Quick start` section from documentation - * Add `Non-goals` section - * Fix code blocks indentation ([#50](https://github.com/MobileTeleSystems/onetl/pull/50)) -- Improve Contributing guide: - * Move `Develop` section from Readme - * Move `docs/changelog/README.rst` content - * Add `Limitations` section - * Add instruction of creating a fork and building documentation ([#50](https://github.com/MobileTeleSystems/onetl/pull/50)) -- Remove duplicated checks for source file existence in `FileDownloader` / `FileMover`. ([#57](https://github.com/MobileTeleSystems/onetl/pull/57)) -- Update default logging format to include thread name. ([#57](https://github.com/MobileTeleSystems/onetl/pull/57)) - -## Bug Fixes - -- Fix `S3.list_dir('/')` returns empty list on latest Minio version. ([#58](https://github.com/MobileTeleSystems/onetl/pull/58)) diff --git a/mddocs/markdown/changelog/0.9.0.md b/mddocs/markdown/changelog/0.9.0.md deleted file mode 100644 index b2d10f257..000000000 --- a/mddocs/markdown/changelog/0.9.0.md +++ /dev/null @@ -1,107 +0,0 @@ -# 0.9.0 (2023-08-17) - -## Breaking Changes - -- Rename methods: - * `DBConnection.read_df` → `DBConnection.read_source_as_df` - * `DBConnection.write_df` → `DBConnection.write_df_to_target` ([#66](https://github.com/MobileTeleSystems/onetl/pull/66)) -- Rename classes: - * `HDFS.slots` → `HDFS.Slots` - * `Hive.slots` → `Hive.Slots` - - Old names are left intact, but will be removed in v1.0.0 ([#103](https://github.com/MobileTeleSystems/onetl/pull/103)) -- Rename options to make them self-explanatory: - * `Hive.WriteOptions(mode="append")` → `Hive.WriteOptions(if_exists="append")` - * `Hive.WriteOptions(mode="overwrite_table")` → `Hive.WriteOptions(if_exists="replace_entire_table")` - * `Hive.WriteOptions(mode="overwrite_partitions")` → `Hive.WriteOptions(if_exists="replace_overlapping_partitions")` - * `JDBC.WriteOptions(mode="append")` → `JDBC.WriteOptions(if_exists="append")` - * `JDBC.WriteOptions(mode="overwrite")` → `JDBC.WriteOptions(if_exists="replace_entire_table")` - * `Greenplum.WriteOptions(mode="append")` → `Greenplum.WriteOptions(if_exists="append")` - * `Greenplum.WriteOptions(mode="overwrite")` → `Greenplum.WriteOptions(if_exists="replace_entire_table")` - * `MongoDB.WriteOptions(mode="append")` → `Greenplum.WriteOptions(if_exists="append")` - * `MongoDB.WriteOptions(mode="overwrite")` → `Greenplum.WriteOptions(if_exists="replace_entire_collection")` - * `FileDownloader.Options(mode="error")` → `FileDownloader.Options(if_exists="error")` - * `FileDownloader.Options(mode="ignore")` → `FileDownloader.Options(if_exists="ignore")` - * `FileDownloader.Options(mode="overwrite")` → `FileDownloader.Options(if_exists="replace_file")` - * `FileDownloader.Options(mode="delete_all")` → `FileDownloader.Options(if_exists="replace_entire_directory")` - * `FileUploader.Options(mode="error")` → `FileUploader.Options(if_exists="error")` - * `FileUploader.Options(mode="ignore")` → `FileUploader.Options(if_exists="ignore")` - * `FileUploader.Options(mode="overwrite")` → `FileUploader.Options(if_exists="replace_file")` - * `FileUploader.Options(mode="delete_all")` → `FileUploader.Options(if_exists="replace_entire_directory")` - * `FileMover.Options(mode="error")` → `FileMover.Options(if_exists="error")` - * `FileMover.Options(mode="ignore")` → `FileMover.Options(if_exists="ignore")` - * `FileMover.Options(mode="overwrite")` → `FileMover.Options(if_exists="replace_file")` - * `FileMover.Options(mode="delete_all")` → `FileMover.Options(if_exists="replace_entire_directory")` - - Old names are left intact, but will be removed in v1.0.0 ([#108](https://github.com/MobileTeleSystems/onetl/pull/108)) -- Rename `onetl.log.disable_clients_logging()` to `onetl.log.setup_clients_logging()`. ([#120](https://github.com/MobileTeleSystems/onetl/pull/120)) - -## Features - -- Add new methods returning Maven packages for specific connection class: - * `Clickhouse.get_packages()` - * `MySQL.get_packages()` - * `Postgres.get_packages()` - * `Teradata.get_packages()` - * `MSSQL.get_packages(java_version="8")` - * `Oracle.get_packages(java_version="8")` - * `Greenplum.get_packages(scala_version="2.12")` - * `MongoDB.get_packages(scala_version="2.12")` - * `Kafka.get_packages(spark_version="3.4.1", scala_version="2.12")` - - Deprecate old syntax: - * `Clickhouse.package` - * `MySQL.package` - * `Postgres.package` - * `Teradata.package` - * `MSSQL.package` - * `Oracle.package` - * `Greenplum.package_spark_2_3` - * `Greenplum.package_spark_2_4` - * `Greenplum.package_spark_3_2` - * `MongoDB.package_spark_3_2` - * `MongoDB.package_spark_3_3` - * `MongoDB.package_spark_3_4` ([#87](https://github.com/MobileTeleSystems/onetl/pull/87)) -- Allow to set client modules log level in `onetl.log.setup_clients_logging()`. - - Allow to enable underlying client modules logging in `onetl.log.setup_logging()` by providing additional argument `enable_clients=True`. - This is useful for debug. ([#120](https://github.com/MobileTeleSystems/onetl/pull/120)) -- Added support for reading and writing data to Kafka topics. - - For these operations, new classes were added. - * `Kafka` ([#54](https://github.com/MobileTeleSystems/onetl/pull/54), [#60](https://github.com/MobileTeleSystems/onetl/pull/60), [#72](https://github.com/MobileTeleSystems/onetl/pull/72), [#84](https://github.com/MobileTeleSystems/onetl/pull/84), [#87](https://github.com/MobileTeleSystems/onetl/pull/87), [#89](https://github.com/MobileTeleSystems/onetl/pull/89), [#93](https://github.com/MobileTeleSystems/onetl/pull/93), [#96](https://github.com/MobileTeleSystems/onetl/pull/96), [#102](https://github.com/MobileTeleSystems/onetl/pull/102), [#104](https://github.com/MobileTeleSystems/onetl/pull/104)) - * `Kafka.PlaintextProtocol` ([#79](https://github.com/MobileTeleSystems/onetl/pull/79)) - * `Kafka.SSLProtocol` ([#118](https://github.com/MobileTeleSystems/onetl/pull/118)) - * `Kafka.BasicAuth` ([#63](https://github.com/MobileTeleSystems/onetl/pull/63), [#77](https://github.com/MobileTeleSystems/onetl/pull/77)) - * `Kafka.KerberosAuth` ([#63](https://github.com/MobileTeleSystems/onetl/pull/63), [#77](https://github.com/MobileTeleSystems/onetl/pull/77), [#110](https://github.com/MobileTeleSystems/onetl/pull/110)) - * `Kafka.ScramAuth` ([#115](https://github.com/MobileTeleSystems/onetl/pull/115)) - * `Kafka.Slots` ([#109](https://github.com/MobileTeleSystems/onetl/pull/109)) - * `Kafka.ReadOptions` ([#68](https://github.com/MobileTeleSystems/onetl/pull/68)) - * `Kafka.WriteOptions` ([#68](https://github.com/MobileTeleSystems/onetl/pull/68)) - - Currently, Kafka does not support incremental read strategies, this will be implemented in future releases. -- Added support for reading files as Spark DataFrame and saving DataFrame as Files. - - For these operations, new classes were added. - - FileDFConnections: - * `SparkHDFS` ([#98](https://github.com/MobileTeleSystems/onetl/pull/98)) - * `SparkS3` ([#94](https://github.com/MobileTeleSystems/onetl/pull/94), [#100](https://github.com/MobileTeleSystems/onetl/pull/100), [#124](https://github.com/MobileTeleSystems/onetl/pull/124)) - * `SparkLocalFS` ([#67](https://github.com/MobileTeleSystems/onetl/pull/67)) - - High-level classes: - * `FileDFReader` ([#73](https://github.com/MobileTeleSystems/onetl/pull/73)) - * `FileDFWriter` ([#81](https://github.com/MobileTeleSystems/onetl/pull/81)) - - File formats: - * `Avro` ([#69](https://github.com/MobileTeleSystems/onetl/pull/69)) - * `CSV` ([#92](https://github.com/MobileTeleSystems/onetl/pull/92)) - * `JSON` ([#83](https://github.com/MobileTeleSystems/onetl/pull/83)) - * `JSONLine` ([#83](https://github.com/MobileTeleSystems/onetl/pull/83)) - * `ORC` ([#86](https://github.com/MobileTeleSystems/onetl/pull/86)) - * `Parquet` ([#88](https://github.com/MobileTeleSystems/onetl/pull/88)) - -## Improvements - -- Remove redundant checks for driver availability in Greenplum and MongoDB connections. ([#67](https://github.com/MobileTeleSystems/onetl/pull/67)) -- Check of Java class availability moved from `.check()` method to connection constructor. ([#97](https://github.com/MobileTeleSystems/onetl/pull/97)) diff --git a/mddocs/markdown/changelog/0.9.1.md b/mddocs/markdown/changelog/0.9.1.md deleted file mode 100644 index 1779274b1..000000000 --- a/mddocs/markdown/changelog/0.9.1.md +++ /dev/null @@ -1,7 +0,0 @@ -# 0.9.1 (2023-08-17) - -## Bug Fixes - -- Fixed bug then number of threads created by `FileDownloader` / `FileUploader` / `FileMover` was - not `min(workers, len(files))`, but `max(workers, len(files))`. leading to create too much workers - on large files list. diff --git a/mddocs/markdown/changelog/0.9.2.md b/mddocs/markdown/changelog/0.9.2.md deleted file mode 100644 index 4ca4fcff9..000000000 --- a/mddocs/markdown/changelog/0.9.2.md +++ /dev/null @@ -1,21 +0,0 @@ -# 0.9.2 (2023-09-06) - -## Features - -- Add `if_exists="ignore"` and `error` to `Greenplum.WriteOptions` ([#142](https://github.com/MobileTeleSystems/onetl/pull/142)) - -## Improvements - -- Improve validation messages while writing dataframe to Kafka. ([#131](https://github.com/MobileTeleSystems/onetl/pull/131)) -- Improve documentation: - * Add notes about reading and writing to database connections documentation - * Add notes about executing statements in JDBC and Greenplum connections - -## Bug Fixes - -- Fixed validation of `headers` column is written to Kafka with default `Kafka.WriteOptions()` - default value was `False`, - but instead of raising an exception, column value was just ignored. ([#131](https://github.com/MobileTeleSystems/onetl/pull/131)) -- Fix reading data from Oracle with `partitioningMode="range"` without explicitly set `lowerBound` / `upperBound`. ([#133](https://github.com/MobileTeleSystems/onetl/pull/133)) -- Update Kafka documentation with SSLProtocol usage. ([#136](https://github.com/MobileTeleSystems/onetl/pull/136)) -- Raise exception if someone tries to read data from Kafka topic which does not exist. ([#138](https://github.com/MobileTeleSystems/onetl/pull/138)) -- Allow to pass Kafka topics with name like `some.topic.name` to DBReader. Same for MongoDB collections. ([#139](https://github.com/MobileTeleSystems/onetl/pull/139)) diff --git a/mddocs/markdown/changelog/0.9.3.md b/mddocs/markdown/changelog/0.9.3.md deleted file mode 100644 index 1a8c25d4d..000000000 --- a/mddocs/markdown/changelog/0.9.3.md +++ /dev/null @@ -1,5 +0,0 @@ -# 0.9.3 (2023-09-06) - -## Bug Fixes - -- Fix documentation build diff --git a/mddocs/markdown/changelog/0.9.4.md b/mddocs/markdown/changelog/0.9.4.md deleted file mode 100644 index cf9288760..000000000 --- a/mddocs/markdown/changelog/0.9.4.md +++ /dev/null @@ -1,24 +0,0 @@ -# 0.9.4 (2023-09-26) - -## Features - -- Add `Excel` file format support. ([#148](https://github.com/MobileTeleSystems/onetl/pull/148)) -- Add `Samba` file connection. - It is now possible to download and upload files to Samba shared folders using `FileDownloader`/`FileUploader`. ([#150](https://github.com/MobileTeleSystems/onetl/pull/150)) -- Add `if_exists="ignore"` and `error` to `Hive.WriteOptions` ([#143](https://github.com/MobileTeleSystems/onetl/pull/143)) -- Add `if_exists="ignore"` and `error` to `JDBC.WriteOptions` ([#144](https://github.com/MobileTeleSystems/onetl/pull/144)) -- Add `if_exists="ignore"` and `error` to `MongoDB.WriteOptions` ([#145](https://github.com/MobileTeleSystems/onetl/pull/145)) - -## Improvements - -- Add documentation about different ways of passing packages to Spark session. ([#151](https://github.com/MobileTeleSystems/onetl/pull/151)) -- Drastically improve `Greenplum` documentation: - : * Added information about network ports, grants, `pg_hba.conf` and so on. - * Added interaction schemas for reading, writing and executing statements in Greenplum. - * Added recommendations about reading data from views and `JOIN` results from Greenplum. ([#154](https://github.com/MobileTeleSystems/onetl/pull/154)) -- Make `.fetch` and `.execute` methods of DB connections thread-safe. Each thread works with its own connection. ([#156](https://github.com/MobileTeleSystems/onetl/pull/156)) -- Call `.close()` on `FileConnection` then it is removed by garbage collector. ([#156](https://github.com/MobileTeleSystems/onetl/pull/156)) - -## Bug Fixes - -- Fix issue when stopping Python interpreter calls `JDBCMixin.close()`, but it is finished with exceptions. ([#156](https://github.com/MobileTeleSystems/onetl/pull/156)) diff --git a/mddocs/markdown/changelog/0.9.5.md b/mddocs/markdown/changelog/0.9.5.md deleted file mode 100644 index 1d7358c0b..000000000 --- a/mddocs/markdown/changelog/0.9.5.md +++ /dev/null @@ -1,14 +0,0 @@ -# 0.9.5 (2023-10-10) - -## Features - -- Add `XML` file format support. ([#163](https://github.com/MobileTeleSystems/onetl/pull/163)) -- Tested compatibility with Spark 3.5.0. `MongoDB` and `Excel` are not supported yet, but other packages do. ([#159](https://github.com/MobileTeleSystems/onetl/pull/159)) - -## Improvements - -- Add check to all DB and FileDF connections that Spark session is alive. ([#164](https://github.com/MobileTeleSystems/onetl/pull/164)) - -## Bug Fixes - -- Fix `Hive.check()` behavior when Hive Metastore is not available. ([#164](https://github.com/MobileTeleSystems/onetl/pull/164)) diff --git a/mddocs/markdown/changelog/DRAFT.md b/mddocs/markdown/changelog/DRAFT.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/mddocs/markdown/changelog/NEXT_RELEASE.md b/mddocs/markdown/changelog/NEXT_RELEASE.md deleted file mode 100644 index 8cf66aa33..000000000 --- a/mddocs/markdown/changelog/NEXT_RELEASE.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/mddocs/markdown/changelog/index.md b/mddocs/markdown/changelog/index.md deleted file mode 100644 index 37d5d85ae..000000000 --- a/mddocs/markdown/changelog/index.md +++ /dev/null @@ -1,29 +0,0 @@ -Changelog - -* [0.13.4 (2025-03-20)](0.13.4.md) -* [0.13.3 (2025-03-11)](0.13.3.md) -* [0.13.1 (2025-03-06)](0.13.1.md) -* [0.13.0 (2025-02-24)](0.13.0.md) -* [0.12.5 (2024-12-03)](0.12.5.md) -* [0.12.4 (2024-11-27)](0.12.4.md) -* [0.12.3 (2024-11-22)](0.12.3.md) -* [0.12.2 (2024-11-12)](0.12.2.md) -* [0.12.1 (2024-10-28)](0.12.1.md) -* [0.12.0 (2024-09-03)](0.12.0.md) -* [0.11.2 (2024-09-02)](0.11.2.md) -* [0.11.1 (2024-05-29)](0.11.1.md) -* [0.11.0 (2024-05-27)](0.11.0.md) -* [0.10.2 (2024-03-21)](0.10.2.md) -* [0.10.1 (2024-02-05)](0.10.1.md) -* [0.10.0 (2023-12-18)](0.10.0.md) -* [0.9.5 (2023-10-10)](0.9.5.md) -* [0.9.4 (2023-09-26)](0.9.4.md) -* [0.9.3 (2023-09-06)](0.9.3.md) -* [0.9.2 (2023-09-06)](0.9.2.md) -* [0.9.1 (2023-08-17)](0.9.1.md) -* [0.9.0 (2023-08-17)](0.9.0.md) -* [0.8.1 (2023-07-10)](0.8.1.md) -* [0.8.0 (2023-05-31)](0.8.0.md) -* [0.7.2 (2023-05-24)](0.7.2.md) -* [0.7.1 (2023-05-23)](0.7.1.md) -* [0.7.0 (2023-05-15)](0.7.0.md) diff --git a/mddocs/markdown/concepts.md b/mddocs/markdown/concepts.md deleted file mode 100644 index cf74e9eaa..000000000 --- a/mddocs/markdown/concepts.md +++ /dev/null @@ -1,340 +0,0 @@ -# Concepts - -Here you can find detailed documentation about each one of the onETL concepts and how to use them. - -## Connection - -### Basics - -onETL is used to pull and push data into other systems, and so it has a first-class `Connection` concept for storing credentials that are used to communicate with external systems. - -A `Connection` is essentially a set of parameters, such as username, password, hostname. - -To create a connection to a specific storage type, you must use a class that matches the storage type. The class name is the same as the storage type name (`Oracle`, `MSSQL`, `SFTP`, etc): - -```python -from onetl.connection import SFTP - -sftp = SFTP( - host="sftp.test.com", - user="onetl", - password="onetl", -) -``` - -All connection types are inherited from the parent class `BaseConnection`. - -### Class diagram - -### DBConnection - -Classes inherited from `DBConnection` could be used for accessing databases. - -A `DBConnection` could be instantiated as follows: - -```python -from onetl.connection import MSSQL - -mssql = MSSQL( - host="mssqldb.demo.com", - user="onetl", - password="onetl", - database="Telecom", - spark=spark, -) -``` - -where **spark** is the current SparkSession. -`onETL` uses `Spark` and specific Java connectors under the hood to work with databases. - -For a description of other parameters, see the documentation for the [available DBConnections](connection/db_connection/index.md#db-connections). - -### FileConnection - -Classes inherited from `FileConnection` could be used to access files stored on the different file systems/file servers - -A `FileConnection` could be instantiated as follows: - -```python -from onetl.connection import SFTP - -sftp = SFTP( - host="sftp.test.com", - user="onetl", - password="onetl", -) -``` - -For a description of other parameters, see the documentation for the [available FileConnections](connection/file_connection/index.md#file-connections). - -### FileDFConnection - -Classes inherited from `FileDFConnection` could be used for accessing files as Spark DataFrames. - -A `FileDFConnection` could be instantiated as follows: - -```python -from onetl.connection import SparkHDFS - -spark_hdfs = SparkHDFS( - host="namenode1.domain.com", - cluster="mycluster", - spark=spark, -) -``` - -where **spark** is the current SparkSession. -`onETL` uses `Spark` and specific Java connectors under the hood to work with DataFrames. - -For a description of other parameters, see the documentation for the [available FileDFConnections](connection/file_df_connection/index.md#file-df-connections). - -### Checking connection availability - -Once you have created a connection, you can check the database/filesystem availability using the method `check()`: - -```python -mssql.check() -sftp.check() -spark_hdfs.check() -``` - -It will raise an exception if database/filesystem cannot be accessed. - -This method returns connection itself, so you can create connection and immediately check its availability: - -```Python -mssql = MSSQL( - host="mssqldb.demo.com", - user="onetl", - password="onetl", - database="Telecom", - spark=spark, -).check() # <-- -``` - -## Extract/Load data - -### Basics - -As we said above, onETL is used to extract data from and load data into remote systems. - -onETL provides several classes for this: - -> * [DBReader](db/db_reader.md#db-reader) -> * [DBWriter](db/db_writer.md#db-writer) -> * [FileDFReader](file_df/file_df_reader/file_df_reader.md#file-df-reader) -> * [FileDFWriter](file_df/file_df_writer/file_df_writer.md#file-df-writer) -> * [FileDownloader](file/file_downloader/file_downloader.md#file-downloader) -> * [FileUploader](file/file_uploader/file_uploader.md#file-uploader) -> * [FileMover](file/file_mover/file_mover.md#file-mover) - -All of these classes have a method `run()` that starts extracting/loading the data: - -```python -from onetl.db import DBReader, DBWriter - -reader = DBReader( - connection=mssql, - source="dbo.demo_table", - columns=["column_1", "column_2"], -) - -# Read data as Spark DataFrame -df = reader.run() - -db_writer = DBWriter( - connection=hive, - target="dl_sb.demo_table", -) - -# Save Spark DataFrame to Hive table -writer.run(df) -``` - -### Extract data - -To extract data you can use classes: - -| | Use case | Connection | `run()` gets | `run()` returns | -|-------------------------------------------------------------------------|-------------------------------------------|------------------------------------------------------------------------------------|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------| -| [DBReader](db/db_reader.md#db-reader) | Reading data from a database | Any [DBConnection](connection/db_connection/index.md#db-connections) | - | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | -| [FileDFReader](file_df/file_df_reader/file_df_reader.md#file-df-reader) | Read data from a file or set of files | Any [FileDFConnection](connection/file_df_connection/index.md#file-df-connections) | No input, or List[File path on FileSystem] | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | -| [FileDownloader](db/db_reader.md#db-reader) | Download files from remote FS to local FS | Any [FileConnection](connection/file_connection/index.md#file-connections) | No input, or List[File path on remote FileSystem] | [DownloadResult](file/file_downloader/result.md#file-downloader-result) | - -### Load data - -To load data you can use classes: - -| | Use case | Connection | `run()` gets | `run()` returns | -|-------------------------------------------------------------------|----------------------------------------------|------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------| -| [DBWriter](db/db_writer.md#db-writer) | Writing data from a DataFrame to a database | Any [DBConnection](connection/db_connection/index.md#db-connections) | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | None | -| [FileDFWriter](db/db_writer.md#db-writer) | Writing data from a DataFrame to a folder | Any [FileDFConnection](connection/file_df_connection/index.md#file-df-connections) | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | None | -| [FileUploader](file/file_uploader/file_uploader.md#file-uploader) | Uploading files from a local FS to remote FS | Any [FileConnection](connection/file_connection/index.md#file-connections) | List[File path on local FileSystem] | [UploadResult](file/file_uploader/result.md#file-uploader-result) | - -### Manipulate data - -To manipulate data you can use classes: - -| | Use case | Connection | `run()` gets | `run()` returns | -|-------------------------------------------------------|---------------------------------------------|----------------------------------------------------------------------------|--------------------------------------|-----------------------------------------------------------| -| [FileMover](file/file_mover/file_mover.md#file-mover) | Move files between directories in remote FS | Any [FileConnection](connection/file_connection/index.md#file-connections) | List[File path on remote FileSystem] | [MoveResult](file/file_mover/result.md#file-mover-result) | - -### Options - -Extract and load classes have a `options` parameter, which has a special meaning: - -> * all other parameters - *WHAT* we extract / *WHERE* we load to -> * `options` parameter - *HOW* we extract/load data -```python -db_reader = DBReader( - # WHAT do we read: - connection=mssql, - source="dbo.demo_table", # some table from MSSQL - columns=["column_1", "column_2"], # but only specific set of columns - where="column_2 > 1000", # only rows matching the clause - # HOW do we read: - options=MSSQL.ReadOptions( - numPartitions=10, # read in 10 parallel jobs - partitionColumn="id", # balance data read by assigning each job a part of data using `hash(id) mod N` expression - partitioningMode="hash", - fetchsize=1000, # each job will fetch block of 1000 rows each on every read attempt - ), -) - -db_writer = DBWriter( - # WHERE do we write to - to some table in Hive - connection=hive, - target="dl_sb.demo_table", - # HOW do we write - overwrite all the data in the existing table - options=Hive.WriteOptions(if_exists="replace_entire_table"), -) - -file_downloader = FileDownloader( - # WHAT do we download - files from some dir in SFTP - connection=sftp, - source_path="/source", - filters=[Glob("*.csv")], # only CSV files - limits=[MaxFilesCount(1000)], # 1000 files max - # WHERE do we download to - a specific dir on local FS - local_path="/some", - # HOW do we download: - options=FileDownloader.Options( - delete_source=True, # after downloading each file remove it from source_path - if_exists="replace_file", # replace existing files in the local_path - ), -) - -file_uploader = FileUploader( - # WHAT do we upload - files from some local dir - local_path="/source", - # WHERE do we upload to- specific remote dir in HDFS - connection=hdfs, - target_path="/some", - # HOW do we upload: - options=FileUploader.Options( - delete_local=True, # after uploading each file remove it from local_path - if_exists="replace_file", # replace existing files in the target_path - ), -) - -file_mover = FileMover( - # WHAT do we move - files in some remote dir in HDFS - source_path="/source", - connection=hdfs, - # WHERE do we move files to - target_path="/some", # a specific remote dir within the same HDFS connection - # HOW do we load - replace existing files in the target_path - options=FileMover.Options(if_exists="replace_file"), -) - -file_df_reader = FileDFReader( - # WHAT do we read - *.csv files from some dir in S3 - connection=s3, - source_path="/source", - file_format=CSV(), - # HOW do we read - load files from /source/*.csv, not from /source/nested/*.csv - options=FileDFReader.Options(recursive=False), -) - -file_df_writer = FileDFWriter( - # WHERE do we write to - as .csv files in some dir in S3 - connection=s3, - target_path="/target", - file_format=CSV(), - # HOW do we write - replace all existing files in /target, if exists - options=FileDFWriter.Options(if_exists="replace_entire_directory"), -) -``` - -More information about `options` could be found on [DB connection](connection/db_connection/index.md#db-connections). and -[File Downloader](file/file_downloader/file_downloader.md#file-downloader) / [File Uploader](file/file_uploader/file_uploader.md#file-uploader) / [File Mover](file/file_mover/file_mover.md#file-mover) / [FileDF Reader](file_df/file_df_reader/file_df_reader.md#file-df-reader) / [FileDF Writer](file_df/file_df_writer/file_df_writer.md#file-df-writer) documentation - -### Read Strategies - -onETL have several builtin strategies for reading data: - -1. [Snapshot strategy](strategy/snapshot_strategy.html) (default strategy) -2. [Incremental strategy](strategy/incremental_strategy.html) -3. [Snapshot batch strategy](strategy/snapshot_batch_strategy.html) -4. [Incremental batch strategy](strategy/incremental_batch_strategy.html) - -For example, an incremental strategy allows you to get only new data from the table: - -```python -from onetl.strategy import IncrementalStrategy - -reader = DBReader( - connection=mssql, - source="dbo.demo_table", - hwm_column="id", # detect new data based on value of "id" column -) - -# first run -with IncrementalStrategy(): - df = reader.run() - -sleep(3600) - -# second run -with IncrementalStrategy(): - # only rows, that appeared in the source since previous run - df = reader.run() -``` - -or get only files which were not downloaded before: - -```python -from onetl.strategy import IncrementalStrategy - -file_downloader = FileDownloader( - connection=sftp, - source_path="/remote", - local_path="/local", - hwm_type="file_list", # save all downloaded files to a list, and exclude files already present in this list -) - -# first run -with IncrementalStrategy(): - files = file_downloader.run() - -sleep(3600) - -# second run -with IncrementalStrategy(): - # only files, that appeared in the source since previous run - files = file_downloader.run() -``` - -Most of strategies are based on [HWM](hwm_store/index.md#hwm), Please check each strategy documentation for more details - -### Why just not use Connection class for extract/load? - -Connections are very simple, they have only a set of some basic operations, -like `mkdir`, `remove_file`, `get_table_schema`, and so on. - -High-level operations, like -: * [Read Strategies](strategy/index.md#strategy) support - * Handling metadata push/pull - * Handling different options, like `if_exists="replace_file"` in case of file download/upload - -is moved to a separate class which calls the connection object methods to perform some complex logic. diff --git a/mddocs/markdown/connection/db_connection/clickhouse/connection.md b/mddocs/markdown/connection/db_connection/clickhouse/connection.md deleted file mode 100644 index 09365e6a8..000000000 --- a/mddocs/markdown/connection/db_connection/clickhouse/connection.md +++ /dev/null @@ -1,128 +0,0 @@ -
- -# Clickhouse connection - -### *class* onetl.connection.db_connection.clickhouse.connection.Clickhouse(\*, spark: SparkSession, user: str, password: SecretStr, host: Host, port: int = 8123, database: str | None = None, extra: ClickhouseExtra = ClickhouseExtra()) - -Clickhouse JDBC connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on Maven package [com.clickhouse:clickhouse-jdbc:0.7.2](https://mvnrepository.com/artifact/com.clickhouse/clickhouse-jdbc/0.7.2) -([official Clickhouse JDBC driver](https://github.com/ClickHouse/clickhouse-jdbc)). - -#### SEE ALSO -Before using this connector please take into account [Prerequisites](prerequisites.md#clickhouse-prerequisites) - -#### Versionadded -Added in version 0.1.0. - -* **Parameters:** - **host** - : Host of Clickhouse database. For example: `test.clickhouse.domain.com` or `193.168.1.11` - - **port** - : Port of Clickhouse database - - **user** - : User, which have proper access to the database. For example: `some_user` - - **password** - : Password for database connection - - **database** - : Database in RDBMS, NOT schema. -
- See [this page](https://www.educba.com/postgresql-database-vs-schema/) for more details - - **spark** - : Spark session. - - **extra** - : Specifies one or more extra parameters by which clients can connect to the instance. -
- For example: `{"continueBatchOnError": "false"}`. -
- See: - : * [Clickhouse JDBC driver properties documentation](https://clickhouse.com/docs/en/integrations/java#configuration) - * [Clickhouse core settings documentation](https://clickhouse.com/docs/en/operations/settings/settings) - * [Clickhouse query complexity documentation](https://clickhouse.com/docs/en/operations/settings/query-complexity) - * [Clickhouse query level settings](https://clickhouse.com/docs/en/operations/settings/query-level) - -### Examples - -Create and check Clickhouse connection: - -```python -from onetl.connection import Clickhouse -from pyspark.sql import SparkSession - -# Create Spark session with Clickhouse driver loaded -maven_packages = Clickhouse.get_packages() -spark = ( - SparkSession.builder.appName("spark-app-name") - .config("spark.jars.packages", ",".join(maven_packages)) - .getOrCreate() -) - -# Create connection -clickhouse = Clickhouse( - host="database.host.or.ip", - user="user", - password="*****", - extra={"continueBatchOnError": "false"}, - spark=spark, -).check() -``` - - - -#### check() - -Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If not, an exception will be raised. - -* **Returns:** - Connection itself -* **Raises:** - RuntimeError - : If the connection is not available - -### Examples - -```python -connection.check() -``` - - - -#### *classmethod* get_packages(package_version: str | None = None, apache_http_client_version: str | None = None) → list[str] - -Get package names to be downloaded by Spark. Allows specifying custom JDBC and Apache HTTP Client versions. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **package_version** - : ClickHouse JDBC version client packages. Defaults to `0.7.2`. -
- #### Versionadded - Added in version 0.11.0. - - **apache_http_client_version** - : Apache HTTP Client version package. Defaults to `5.4.2`. -
- Used only if `package_version` is in range `0.5.0-0.7.0`. -
- #### Versionadded - Added in version 0.11.0. - -### Examples - -```python -from onetl.connection import Clickhouse - -Clickhouse.get_packages(package_version="0.6.0", apache_http_client_version="5.4.2") -``` - - diff --git a/mddocs/markdown/connection/db_connection/clickhouse/execute.md b/mddocs/markdown/connection/db_connection/clickhouse/execute.md deleted file mode 100644 index c65c26a67..000000000 --- a/mddocs/markdown/connection/db_connection/clickhouse/execute.md +++ /dev/null @@ -1,191 +0,0 @@ - - -# Executing statements in Clickhouse - -#### WARNING -Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. - -Do **NOT** use them to read large amounts of data. Use [DBReader](read.md#clickhouse-read) or [Clickhouse.sql](sql.md#clickhouse-sql) instead. - -## How to - -There are 2 ways to execute some statement in Clickhouse - -### Use `Clickhouse.fetch` - -Use this method to perform some `SELECT` query which returns **small number or rows**, like reading -Clickhouse config, or reading data from some reference table. Method returns Spark DataFrame. - -Method accepts [`Clickhouse.FetchOptions`](#onetl.connection.db_connection.clickhouse.options.ClickhouseFetchOptions). - -Connection opened using this method should be then closed with `connection.close()` or `with connection:`. - -#### WARNING -Please take into account [Clickhouse <-> Spark type mapping](types.md#clickhouse-types). - -#### Syntax support - -This method supports **any** query syntax supported by Clickhouse, like: - -* ✅︎ `SELECT ... FROM ...` -* ✅︎ `WITH alias AS (...) SELECT ...` -* ✅︎ `SELECT func(arg1, arg2)` - call function -* ✅︎ `SHOW ...` -* ❌ `SET ...; SELECT ...;` - multiple statements not supported - -#### Examples - -```python -from onetl.connection import Clickhouse - -clickhouse = Clickhouse(...) - -df = clickhouse.fetch( - "SELECT value FROM some.reference_table WHERE key = 'some_constant'", - options=Clickhouse.FetchOptions(queryTimeout=10), -) -clickhouse.close() -value = df.collect()[0][0] # get value from first row and first column -``` - -### Use `Clickhouse.execute` - -Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. - -Method accepts [`Clickhouse.ExecuteOptions`](#onetl.connection.db_connection.clickhouse.options.ClickhouseExecuteOptions). - -Connection opened using this method should be then closed with `connection.close()` or `with connection:`. - -#### Syntax support - -This method supports **any** query syntax supported by Clickhouse, like: - -* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on -* ✅︎ `ALTER ...` -* ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on -* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on -* ✅︎ other statements not mentioned here -* ❌ `SET ...; SELECT ...;` - multiple statements not supported - -#### Examples - -```python -from onetl.connection import Clickhouse - -clickhouse = Clickhouse(...) - -clickhouse.execute("DROP TABLE schema.table") -clickhouse.execute( - """ - CREATE TABLE schema.table ( - id UInt8, - key String, - value Float32 - ) - ENGINE = MergeTree() - ORDER BY id - """, - options=Clickhouse.ExecuteOptions(queryTimeout=10), -) -``` - -## Notes - -These methods **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. - -So it should **NOT** be used to read large amounts of data. Use [DBReader](read.md#clickhouse-read) or [Clickhouse.sql](sql.md#clickhouse-sql) instead. - -## Options - -### *pydantic model* onetl.connection.db_connection.clickhouse.options.ClickhouseFetchOptions - -Options related to fetching data from databases via JDBC. - -#### Versionadded -Added in version 0.11.0: Replace `Clickhouse.JDBCOptions` → `Clickhouse.FetchOptions` - -### Examples - -#### NOTE -You can pass any value supported by underlying JDBC driver class, -even if it is not mentioned in this documentation. - -```python -from onetl.connection import Clickhouse - -options = Clickhouse.FetchOptions( - queryTimeout=60_000, - fetchsize=100_000, - customSparkOption="value", -) -``` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int | None* *= None* - -How many rows to fetch per round trip. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value depends on driver. For example, Oracle has -default `fetchsize=10`. - - - -### *pydantic model* onetl.connection.db_connection.clickhouse.options.ClickhouseExecuteOptions - -Options related to executing statements in databases via JDBC. - -#### Versionadded -Added in version 0.11.0: Replace `Clickhouse.JDBCOptions` → `Clickhouse.ExecuteOptions` - -### Examples - -#### NOTE -You can pass any value supported by underlying JDBC driver class, -even if it is not mentioned in this documentation. - -```python -from onetl.connection import Clickhouse - -options = Clickhouse.ExecuteOptions( - queryTimeout=60_000, - customSparkOption="value", -) -``` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int | None* *= None* - -How many rows to fetch per round trip. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value depends on driver. For example, Oracle has -default `fetchsize=10`. - - diff --git a/mddocs/markdown/connection/db_connection/clickhouse/index.md b/mddocs/markdown/connection/db_connection/clickhouse/index.md deleted file mode 100644 index 3b226910d..000000000 --- a/mddocs/markdown/connection/db_connection/clickhouse/index.md +++ /dev/null @@ -1,19 +0,0 @@ - - -# Clickhouse - -# Connection - -* [Prerequisites](prerequisites.md) -* [Clickhouse connection](connection.md) - -# Operations - -* [Reading from Clickhouse using `DBReader`](read.md) -* [Reading from Clickhouse using `Clickhouse.sql`](sql.md) -* [Writing to Clickhouse using `DBWriter`](write.md) -* [Executing statements in Clickhouse](execute.md) - -# Troubleshooting - -* [Clickhouse <-> Spark type mapping](types.md) diff --git a/mddocs/markdown/connection/db_connection/clickhouse/prerequisites.md b/mddocs/markdown/connection/db_connection/clickhouse/prerequisites.md deleted file mode 100644 index 86551b41a..000000000 --- a/mddocs/markdown/connection/db_connection/clickhouse/prerequisites.md +++ /dev/null @@ -1,73 +0,0 @@ - - -# Prerequisites - -## Version Compatibility - -* Clickhouse server versions: - : * Officially declared: 22.8 or higher - * Actually tested: 21.1, 25.1 -* Spark versions: 2.3.x - 3.5.x -* Java versions: 8 - 20 - -See [official documentation](https://clickhouse.com/docs/en/integrations/java#jdbc-driver). - -## Installing PySpark - -To use Clickhouse connector you should have PySpark installed (or injected to `sys.path`) -BEFORE creating the connector instance. - -See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. - -## Connecting to Clickhouse - -### Connection port - -Connector can only use **HTTP** (usually `8123` port) or **HTTPS** (usually `8443` port) protocol. - -TCP and GRPC protocols are NOT supported. - -### Connecting to cluster - -It is possible to connect to Clickhouse cluster, and use it’s load balancing capabilities to read or write data in parallel. -Each Spark executor can connect to random Clickhouse nodes, instead of sending all the data to a node specified in connection params. - -This requires all Clickhouse servers to run on different hosts, and **listen the same HTTP port**. -Set `auto_discovery=True` to enable this feature (disabled by default): - -```python -Clickhouse( - host="node1.of.cluster", - port=8123, - extra={ - "auto_discovery": True, - "load_balancing_policy": "roundRobin", - }, -) -``` - -See [official documentation](https://clickhouse.com/docs/en/integrations/java#configuring-node-discovery-load-balancing-and-failover). - -### Required grants - -Ask your Clickhouse cluster administrator to set following grants for a user, -used for creating a connection: - -Read + Write - -```sql --- allow creating tables in the target schema -GRANT CREATE TABLE ON myschema.* TO username; - --- allow read & write access to specific table -GRANT SELECT, INSERT ON myschema.mytable TO username; -``` - -Read only - -```sql --- allow read access to specific table -GRANT SELECT ON myschema.mytable TO username; -``` - -More details can be found in [official documentation](https://clickhouse.com/docs/en/sql-reference/statements/grant). diff --git a/mddocs/markdown/connection/db_connection/clickhouse/read.md b/mddocs/markdown/connection/db_connection/clickhouse/read.md deleted file mode 100644 index 611930634..000000000 --- a/mddocs/markdown/connection/db_connection/clickhouse/read.md +++ /dev/null @@ -1,339 +0,0 @@ - - -# Reading from Clickhouse using `DBReader` - -[`DBReader`](../../../db/db_reader.md#onetl.db.db_reader.db_reader.DBReader) supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, -but does not support custom queries, like `JOIN`. - -#### WARNING -Please take into account [Clickhouse <-> Spark type mapping](types.md#clickhouse-types) - -## Supported DBReader features - -* ✅︎ `columns` -* ✅︎ `where` -* ✅︎ `hwm`, supported strategies: -* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) -* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) -* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) -* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) -* ❌ `hint` (is not supported by Clickhouse) -* ❌ `df_schema` -* ✅︎ `options` (see [`Clickhouse.ReadOptions`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions)) - -## Examples - -Snapshot strategy: - -```python -from onetl.connection import Clickhouse -from onetl.db import DBReader - -clickhouse = Clickhouse(...) - -reader = DBReader( - connection=clickhouse, - source="schema.table", - columns=["id", "key", "CAST(value AS String) value", "updated_dt"], - where="key = 'something'", - options=Clickhouse.ReadOptions(partitionColumn="id", numPartitions=10), -) -df = reader.run() -``` - -Incremental strategy: - -```python -from onetl.connection import Clickhouse -from onetl.db import DBReader -from onetl.strategy import IncrementalStrategy - -clickhouse = Clickhouse(...) - -reader = DBReader( - connection=clickhouse, - source="schema.table", - columns=["id", "key", "CAST(value AS String) value", "updated_dt"], - where="key = 'something'", - hwm=DBReader.AutoDetectHWM(name="clickhouse_hwm", expression="updated_dt"), - options=Clickhouse.ReadOptions(partitionColumn="id", numPartitions=10), -) - -with IncrementalStrategy(): - df = reader.run() -``` - -## Recommendations - -### Select only required columns - -Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Clickhouse to Spark. - -### Pay attention to `where` value - -Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. -This both reduces the amount of data send from Clickhouse to Spark, and may also improve performance of the query. -Especially if there are indexes or partitions for columns used in `where` clause. - -## Options - -### *pydantic model* onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions - -Spark JDBC reading options. - -#### Versionadded -Added in version 0.5.0: Replace `Clickhouse.Options` → `Clickhouse.ReadOptions` - -### Examples - -#### NOTE -You can pass any value -[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), -even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! - -The set of supported options depends on Spark version. - -```python -from onetl.connection import Clickhouse - -options = Clickhouse.ReadOptions( - partitioning_mode="range", - partitionColumn="reg_id", - numPartitions=10, - customSparkOption="value", -) -``` - - - -#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* - -Column used to parallelize reading from a table. - -#### WARNING -It is highly recommended to use primary key, or column with an index -to avoid performance issues. - -#### NOTE -Column type depends on [`partitioning_mode`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions.partitioning_mode). - -* `partitioning_mode="range"` requires column to be an integer, date or timestamp (can be NULL, but not recommended). -* `partitioning_mode="hash"` accepts any column type (NOT NULL). -* `partitioning_mode="mod"` requires column to be an integer (NOT NULL). - -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions.partitioning_mode) for more details - - - -#### *field* num_partitions *: PositiveInt* *= 1* *(alias 'numPartitions')* - -Number of jobs created by Spark to read the table content in parallel. -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions.partitioning_mode) for more details - - - -#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* - -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions.partitioning_mode) for more details - - - -#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* - -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions.partitioning_mode) for more details - - - -#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* - -After each database session is opened to the remote DB and before starting to read data, -this option executes a custom SQL statement (or a PL/SQL block). - -Use this to implement session initialization code. - -Example: - -```python -sessionInitStatement = """ - BEGIN - execute immediate - 'alter session set "_serial_direct_read"=true'; - END; -""" -``` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int* *= 100000* - -Fetch N rows from an opened cursor per one read round. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value is different from Spark. - -Spark uses driver’s own value, and it may be different in different drivers, -and even versions of the same driver. For example, Oracle has -default `fetchsize=10`, which is absolutely not usable. - -Thus we’ve overridden default value with `100_000`, which should increase reading performance. - -#### Versionchanged -Changed in version 0.2.0: Set explicit default value to `100_000` - - - -#### *field* partitioning_mode *: JDBCPartitioningMode* *= JDBCPartitioningMode.RANGE* - -Defines how Spark will parallelize reading from table. - -Possible values: - -* `range` (default) - : Allocate each executor a range of values from column passed into [`partition_column`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions.partition_column). -
- ### Spark generates for each executor an SQL query -
- Executor 1: - ```sql - SELECT ... FROM table - WHERE (partition_column >= lowerBound - OR partition_column IS NULL) - AND partition_column < (lower_bound + stride) - ``` -
- Executor 2: - ```sql - SELECT ... FROM table - WHERE partition_column >= (lower_bound + stride) - AND partition_column < (lower_bound + 2 * stride) - ``` -
- … -
- Executor N: - ```sql - SELECT ... FROM table - WHERE partition_column >= (lower_bound + (N-1) * stride) - AND partition_column <= upper_bound - ``` -
- Where `stride=(upper_bound - lower_bound) / num_partitions`. -
- #### NOTE - Can be used only with columns of integer, date or timestamp types. -
- #### NOTE - [`lower_bound`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions.lower_bound), [`upper_bound`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions.upper_bound) and [`num_partitions`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions.num_partitions) are used just to - calculate the partition stride, **NOT** for filtering the rows in table. - So all rows in the table will be returned (unlike *Incremental* [Read Strategies](../../../strategy/index.md#strategy)). -
- #### NOTE - All queries are executed in parallel. To execute them sequentially, use *Batch* [Read Strategies](../../../strategy/index.md#strategy). -* `hash` - : Allocate each executor a set of values based on hash of the [`partition_column`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions.partition_column) column. -
- ### Spark generates for each executor an SQL query -
- Executor 1: - ```sql - SELECT ... FROM table - WHERE (some_hash(partition_column) mod num_partitions) = 0 -- lower_bound - ``` -
- Executor 2: - ```sql - SELECT ... FROM table - WHERE (some_hash(partition_column) mod num_partitions) = 1 -- lower_bound + 1 - ``` -
- … -
- Executor N: - ```sql - SELECT ... FROM table - WHERE (some_hash(partition_column) mod num_partitions) = num_partitions-1 -- upper_bound - ``` -
- #### NOTE - The hash function implementation depends on RDBMS. It can be `MD5` or any other fast hash function, - or expression based on this function call. Usually such functions accepts any column type as an input. -* `mod` - : Allocate each executor a set of values based on modulus of the [`partition_column`](#onetl.connection.db_connection.clickhouse.options.ClickhouseReadOptions.partition_column) column. -
- ### Spark generates for each executor an SQL query -
- Executor 1: - ```sql - SELECT ... FROM table - WHERE (partition_column mod num_partitions) = 0 -- lower_bound - ``` -
- Executor 2: - ```sql - SELECT ... FROM table - WHERE (partition_column mod num_partitions) = 1 -- lower_bound + 1 - ``` -
- Executor N: - ```sql - SELECT ... FROM table - WHERE (partition_column mod num_partitions) = num_partitions-1 -- upper_bound - ``` -
- #### NOTE - Can be used only with columns of integer type. - -#### Versionadded -Added in version 0.5.0. - -### Examples - -Read data in 10 parallel jobs by range of values in `id_column` column: - -```python -ReadOptions( - partitioning_mode="range", # default mode, can be omitted - partitionColumn="id_column", - numPartitions=10, - # Options below can be discarded because they are - # calculated automatically as MIN and MAX values of `partitionColumn` - lowerBound=0, - upperBound=100_000, -) -``` - -Read data in 10 parallel jobs by hash of values in `some_column` column: - -```python -ReadOptions( - partitioning_mode="hash", - partitionColumn="some_column", - numPartitions=10, - # lowerBound and upperBound are automatically set to `0` and `9` -) -``` - -Read data in 10 parallel jobs by modulus of values in `id_column` column: - -```python -ReadOptions( - partitioning_mode="mod", - partitionColumn="id_column", - numPartitions=10, - # lowerBound and upperBound are automatically set to `0` and `9` -) -``` - - diff --git a/mddocs/markdown/connection/db_connection/clickhouse/sql.md b/mddocs/markdown/connection/db_connection/clickhouse/sql.md deleted file mode 100644 index e2ce4add5..000000000 --- a/mddocs/markdown/connection/db_connection/clickhouse/sql.md +++ /dev/null @@ -1,191 +0,0 @@ - - -# Reading from Clickhouse using `Clickhouse.sql` - -`Clickhouse.sql` allows passing custom SQL query, but does not support incremental strategies. - -#### WARNING -Please take into account [Clickhouse <-> Spark type mapping](types.md#clickhouse-types) - -#### WARNING -Statement is executed in **read-write** connection, so if you’re calling some functions/procedures with DDL/DML statements inside, -they can change data in your database. - -## Syntax support - -Only queries with the following syntax are supported: - -* ✅︎ `SELECT ... FROM ...` -* ✅︎ `WITH alias AS (...) SELECT ...` -* ❌ `SET ...; SELECT ...;` - multiple statements not supported - -## Examples - -```python -from onetl.connection import Clickhouse - -clickhouse = Clickhouse(...) -df = clickhouse.sql( - """ - SELECT - id, - key, - CAST(value AS String) value, - updated_at - FROM - some.mytable - WHERE - key = 'something' - """, - options=Clickhouse.SQLOptions( - partitionColumn="id", - numPartitions=10, - lowerBound=0, - upperBound=1000, - ), -) -``` - -## Recommendations - -### Select only required columns - -Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. -This reduces the amount of data passed from Clickhouse to Spark. - -### Pay attention to `where` value - -Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. -This both reduces the amount of data send from Clickhouse to Spark, and may also improve performance of the query. -Especially if there are indexes or partitions for columns used in `where` clause. - -## Options - -### *pydantic model* onetl.connection.db_connection.clickhouse.options.ClickhouseSQLOptions - -Options specifically for SQL queries - -These options allow you to specify configurations for executing SQL queries -without relying on Spark’s partitioning mechanisms. - -#### Versionadded -Added in version 0.11.0: Split up `Clickhouse.ReadOptions` to `Clickhouse.SQLOptions` - -### Examples - -#### NOTE -You can pass any JDBC configuration -[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), -tailored to optimize SQL query execution. **Option names should be in** `camelCase`! - -```python -from onetl.connection import Clickhouse - -options = Clickhouse.SQLOptions( - partitionColumn="reg_id", - numPartitions=10, - lowerBound=0, - upperBound=1000, - customSparkOption="value", -) -``` - - - -#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* - -Column used to partition data across multiple executors for parallel query processing. - -#### WARNING -It is highly recommended to use primary key, or column with an index -to avoid performance issues. - -### Example of using `partitionColumn="id"` with `partitioning_mode="range"` - -```sql --- If partition_column is 'id', with numPartitions=4, lowerBound=1, and upperBound=100: --- Executor 1 processes IDs from 1 to 25 -SELECT ... FROM table WHERE id >= 1 AND id < 26 --- Executor 2 processes IDs from 26 to 50 -SELECT ... FROM table WHERE id >= 26 AND id < 51 --- Executor 3 processes IDs from 51 to 75 -SELECT ... FROM table WHERE id >= 51 AND id < 76 --- Executor 4 processes IDs from 76 to 100 -SELECT ... FROM table WHERE id >= 76 AND id <= 100 - --- General case for Executor N -SELECT ... FROM table -WHERE partition_column >= (lowerBound + (N-1) * stride) -AND partition_column <= upperBound --- Where ``stride`` is calculated as ``(upperBound - lowerBound) / numPartitions``. -``` - - - -#### *field* num_partitions *: int | None* *= None* *(alias 'numPartitions')* - -Number of jobs created by Spark to read the table content in parallel. - - - -#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* - -Defines the starting boundary for partitioning the query’s data. Mandatory if [`partition_column`](#onetl.connection.db_connection.clickhouse.options.ClickhouseSQLOptions.partition_column) is set - - - -#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* - -Sets the ending boundary for data partitioning. Mandatory if [`partition_column`](#onetl.connection.db_connection.clickhouse.options.ClickhouseSQLOptions.partition_column) is set - - - -#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* - -After each database session is opened to the remote DB and before starting to read data, -this option executes a custom SQL statement (or a PL/SQL block). - -Use this to implement session initialization code. - -Example: - -```python -sessionInitStatement = """ - BEGIN - execute immediate - 'alter session set "_serial_direct_read"=true'; - END; -""" -``` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int* *= 100000* - -Fetch N rows from an opened cursor per one read round. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value is different from Spark. - -Spark uses driver’s own value, and it may be different in different drivers, -and even versions of the same driver. For example, Oracle has -default `fetchsize=10`, which is absolutely not usable. - -Thus we’ve overridden default value with `100_000`, which should increase reading performance. - -#### Versionchanged -Changed in version 0.2.0: Set explicit default value to `100_000` - - diff --git a/mddocs/markdown/connection/db_connection/clickhouse/types.md b/mddocs/markdown/connection/db_connection/clickhouse/types.md deleted file mode 100644 index 447a8ae26..000000000 --- a/mddocs/markdown/connection/db_connection/clickhouse/types.md +++ /dev/null @@ -1,347 +0,0 @@ - - -# Clickhouse <-> Spark type mapping - -#### NOTE -The results below are valid for Spark 3.5.5, and may differ on other Spark versions. - -#### NOTE -It is recommended to use [spark-dialect-extension](https://github.com/MobileTeleSystems/spark-dialect-extension) package, -which implements writing Arrays from Spark to Clickhouse, fixes dropping fractions of seconds in `TimestampType`, -and fixes other type conversion issues. - -## Type detection & casting - -Spark’s DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. - -### Reading from Clickhouse - -This is how Clickhouse connector performs this: - -* For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and Clickhouse type. -* Find corresponding `Clickhouse type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. -* Create DataFrame from query with specific column names and Spark types. - -### Writing to some existing Clickhouse table - -This is how Clickhouse connector performs this: - -* Get names of columns in DataFrame. [1](#id3) -* Perform `SELECT * FROM table LIMIT 0` query. -* Take only columns present in DataFrame (by name, case insensitive). For each found column get Clickhouse type. -* **Find corresponding** `Clickhouse type (read)` → `Spark type` **combination** (see below) for each DataFrame column. If no combination is found, raise exception. [2](#id4) -* Find corresponding `Spark type` → `Clickhousetype (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. -* If `Clickhousetype (write)` match `Clickhouse type (read)`, no additional casts will be performed, DataFrame column will be written to Clickhouse as is. -* If `Clickhousetype (write)` does not match `Clickhouse type (read)`, DataFrame column will be casted to target column type **on Clickhouse side**. For example, you can write column with text data to `Int32` column, if column contains valid integer values within supported value range and precision. - -* **[1]** This allows to write data to tables with `DEFAULT` columns - if DataFrame has no such column, it will be populated by Clickhouse. -* **[2]** Yes, this is weird. - -### Create new table using Spark - -#### WARNING -ABSOLUTELY NOT RECOMMENDED! - -This is how Clickhouse connector performs this: - -* Find corresponding `Spark type` → `Clickhouse type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. -* Generate DDL for creating table in Clickhouse, like `CREATE TABLE (col1 ...)`, and run it. -* Write DataFrame to created table as is. - -But Spark does not have specific dialect for Clickhouse, so Generic JDBC dialect is used. -Generic dialect is using SQL ANSI type names while creating tables in target database, not database-specific types. - -If some cases this may lead to using wrong column type. For example, Spark creates column of type `TIMESTAMP` -which corresponds to Clickhouse type `DateTime32` (precision up to seconds) -instead of more precise `DateTime64` (precision up to nanoseconds). -This may lead to incidental precision loss, or sometimes data cannot be written to created table at all. - -So instead of relying on Spark to create tables: - -### See example - -```python -writer = DBWriter( - connection=clickhouse, - target="default.target_tbl", - options=Clickhouse.WriteOptions( - if_exists="append", - # ENGINE is required by Clickhouse - createTableOptions="ENGINE = MergeTree() ORDER BY id", - ), -) -writer.run(df) -``` - -Always prefer creating tables with specific types **BEFORE WRITING DATA**: - -### See example - -```python -clickhouse.execute( - """ - CREATE TABLE default.target_tbl ( - id UInt8, - value DateTime64(6) -- specific type and precision - ) - ENGINE = MergeTree() - ORDER BY id - """, -) - -writer = DBWriter( - connection=clickhouse, - target="default.target_tbl", - options=Clickhouse.WriteOptions(if_exists="append"), -) -writer.run(df) -``` - -### References - -Here you can find source code with type conversions: - -* [Clickhouse -> JDBC](https://github.com/ClickHouse/clickhouse-java/blob/0.3.2/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/JdbcTypeMapping.java#L39-L176) -* [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/jdbc/JdbcUtils.scala#L307) -* [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/jdbc/JdbcUtils.scala#L141-L164) -* [JDBC -> Clickhouse](https://github.com/ClickHouse/clickhouse-java/blob/0.3.2/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/JdbcTypeMapping.java#L185-L311) - -## Supported types - -See [official documentation](https://clickhouse.com/docs/en/sql-reference/data-types) - -### Generic types - -* `LowCardinality(T)` is same as `T` -* `Nullable(T)` is same as `T`, but Spark column is inferred as `nullable=True` - -### Numeric types - -| Clickhouse type (read) | Spark type | Clickhouse type (write) | Clickhouse type (create) | -|------------------------------|----------------------------------|-----------------------------|-----------------------------| -| `Bool` | `BooleanType()` | `Bool` | `UInt64` | -| `Decimal` | `DecimalType(P=10, S=0)` | `Decimal(P=10, S=0)` | `Decimal(P=10, S=0)` | -| `Decimal(P=0..38)` | `DecimalType(P=0..38, S=0)` | `Decimal(P=0..38, S=0)` | `Decimal(P=0..38, S=0)` | -| `Decimal(P=0..38, S=0..38)` | `DecimalType(P=0..38, S=0..38)` | `Decimal(P=0..38, S=0..38)` | `Decimal(P=0..38, S=0..38)` | -| `Decimal(P=39..76, S=0..76)` | unsupported [3](#id9) | | | -| `Decimal32(P=0..9)` | `DecimalType(P=9, S=0..9)` | `Decimal(P=9, S=0..9)` | `Decimal(P=9, S=0..9)` | -| `Decimal64(S=0..18)` | `DecimalType(P=18, S=0..18)` | `Decimal(P=18, S=0..18)` | `Decimal(P=18, S=0..18)` | -| `Decimal128(S=0..38)` | `DecimalType(P=38, S=0..38)` | `Decimal(P=38, S=0..38)` | `Decimal(P=38, S=0..38)` | -| `Decimal256(S=0..76)` | unsupported [3](#id9) | | | -| `Float32` | `FloatType()` | `Float32` | `Float32` | -| `Float64` | `DoubleType()` | `Float64` | `Float64` | -| `Int8` | `IntegerType()` | `Int32` | `Int32` | -| `Int16` | | | | -| `Int32` | | | | -| `Int64` | `LongType()` | `Int64` | `Int64` | -| `Int128` | unsupported [3](#id9) | | | -| `Int256` | | | | -| `-` | `ByteType()` | `Int8` | `Int8` | -| `-` | `ShortType()` | `Int32` | `Int32` | -| `UInt8` | `IntegerType()` | `Int32` | `Int32` | -| `UInt16` | `LongType()` | `Int64` | `Int64` | -| `UInt32` | `DecimalType(20,0)` | `Decimal(20,0)` | `Decimal(20,0)` | -| `UInt64` | | | | -| `UInt128` | unsupported [3](#id9) | | | -| `UInt256` | | | | -* **[3]** Clickhouse support numeric types up to 256 bit - `Int256`, `UInt256`, `Decimal256(S)`, `Decimal(P=39..76, S=0..76)`. But Spark’s `DecimalType(P, S)` supports maximum `P=38` (128 bit). It is impossible to read, write or operate with values of larger precision, this leads to an exception. - -### Temporal types - -Notes: -: * Datetime with timezone has the same precision as without timezone - * `DateTime` is alias for `DateTime32` - * `TIMESTAMP` is alias for `DateTime32`, but `TIMESTAMP(N)` is alias for `DateTime64(N)` - -| Clickhouse type (read) | Spark type | Clickhouse type (write) | Clickhouse type (create) | -|---------------------------------|-------------------------------------------------------------------------------|-------------------------------|---------------------------------------------------------------------| -| `Date` | `DateType()` | `Date` | `Date` | -| `Date32` | `DateType()` | `Date` | `Date`,
**cannot insert data** [4](#id15) | -| `DateTime32`, seconds | `TimestampType()`, microseconds | `DateTime64(6)`, microseconds | `DateTime32`, seconds | -| `DateTime64(3)`, milliseconds | `TimestampType()`, microseconds | `DateTime64(6)`, microseconds | `DateTime32`, seconds,
**precision loss** [5](#id16) | -| `DateTime64(6)`, microseconds | `TimestampType()`, microseconds | | `DateTime32`, seconds,
**precision loss** [7](#id18) | -| `DateTime64(7..9)`, nanoseconds | `TimestampType()`, microseconds,
**precision loss** [6](#id17) | | | -| `-` | `TimestampNTZType()`, microseconds | | | -| `DateTime32(TZ)` | unsupported [7](#id18) | | | -| `DateTime64(P, TZ)` | | | | -| `IntervalNanosecond` | `LongType()` | `Int64` | `Int64` | -| `IntervalMicrosecond` | | | | -| `IntervalMillisecond` | | | | -| `IntervalSecond` | | | | -| `IntervalMinute` | | | | -| `IntervalHour` | | | | -| `IntervalDay` | | | | -| `IntervalMonth` | | | | -| `IntervalQuarter` | | | | -| `IntervalWeek` | | | | -| `IntervalYear` | | | | - -#### WARNING -Note that types in Clickhouse and Spark have different value ranges: - -| Clickhouse type | Min value | Max value | Spark type | Min value | Max value | -|----------------------|---------------------------------|---------------------------------|-------------------|------------------------------|------------------------------| -| `Date` | `1970-01-01` | `2149-06-06` | `DateType()` | `0001-01-01` | `9999-12-31` | -| `DateTime32` | `1970-01-01 00:00:00` | `2106-02-07 06:28:15` | `TimestampType()` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | -| `DateTime64(P=0..8)` | `1900-01-01 00:00:00.00000000` | `2299-12-31 23:59:59.99999999` | | | | -| `DateTime64(P=9)` | `1900-01-01 00:00:00.000000000` | `2262-04-11 23:47:16.999999999` | | | | - -So not all of values in Spark DataFrame can be written to Clickhouse. - -References: -: * [Clickhouse Date documentation](https://clickhouse.com/docs/en/sql-reference/data-types/date) - * [Clickhouse Datetime32 documentation](https://clickhouse.com/docs/en/sql-reference/data-types/datetime) - * [Clickhouse Datetime64 documentation](https://clickhouse.com/docs/en/sql-reference/data-types/datetime64) - * [Spark DateType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/DateType.html) - * [Spark TimestampType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/TimestampType.html) - -* **[4]** `Date32` has different bytes representation than `Date`, and inserting value of type `Date32` to `Date` column leads to errors on Clickhouse side, e.g. `Date(106617) should be between 0 and 65535 inclusive of both values`. Although Spark does properly read the `Date32` column as `DateType()`, and there should be no difference at all. Probably this is some bug in Clickhouse driver. -* **[5]** Generic JDBC dialect generates DDL with Clickhouse type `TIMESTAMP` which is alias for `DateTime32` with precision up to seconds (`23:59:59`). Inserting data with milliseconds precision (`23:59:59.999`) will lead to **throwing away milliseconds**. Solution: create table manually, with proper column type. -* **[6]** Clickhouse support datetime up to nanoseconds precision (`23:59:59.999999999`), but Spark `TimestampType()` supports datetime up to microseconds precision (`23:59:59.999999`). Nanoseconds will be lost during read or write operations. Solution: create table manually, with proper column type. -* **[7]** Clickhouse will raise an exception that data in format `2001-01-01 23:59:59.999999` has data `.999999` which does not match format `YYYY-MM-DD hh:mm:ss` of `DateTime32` column type (see [5](#id16)). So Spark can create Clickhouse table, but cannot write data to column of this type. Solution: create table manually, with proper column type. - -### String types - -| Clickhouse type (read) | Spark type | Clickhousetype (write) | Clickhouse type (create) | -|--------------------------|----------------|--------------------------|----------------------------| -| `FixedString(N)` | `StringType()` | `String` | `String` | -| `String` | | | | -| `Enum8` | | | | -| `Enum16` | | | | -| `IPv4` | | | | -| `IPv6` | | | | -| `UUID` | | | | -| `-` | `BinaryType()` | | | - -## Unsupported types - -Columns of these Clickhouse types cannot be read by Spark: -: * `AggregateFunction(func, T)` - * `Array(T)` - * `JSON` - * `Map(K, V)` - * `MultiPolygon` - * `Nested(field1 T1, ...)` - * `Nothing` - * `Point` - * `Polygon` - * `Ring` - * `SimpleAggregateFunction(func, T)` - * `Tuple(T1, T2, ...)` - -Dataframe with these Spark types cannot be written to Clickhouse: -: * `ArrayType(T)` - * `BinaryType()` - * `CharType(N)` - * `DayTimeIntervalType(P, S)` - * `MapType(K, V)` - * `NullType()` - * `StructType([...])` - * `TimestampNTZType()` - * `VarcharType(N)` - -This is because Spark does not have dedicated Clickhouse dialect, and uses Generic JDBC dialect instead. -This dialect does not have type conversion between some types, like Clickhouse `Array` -> Spark `ArrayType()`, and vice versa. - -The is a way to avoid this - just cast everything to `String`. - -## Explicit type cast - -### `DBReader` - -Use `CAST` or `toJSONString` to get column data as string in JSON format, - -For parsing JSON columns in ClickHouse, [`JSON.parse_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.parse_column) method. - -```python -from pyspark.sql.types import ArrayType, IntegerType - -from onetl.file.format import JSON -from onetl.connection import ClickHouse -from onetl.db import DBReader - -reader = DBReader( - connection=clickhouse, - target="default.source_tbl", - columns=[ - "id", - "toJSONString(array_column) array_column", - ], -) -df = reader.run() - -# Spark requires all columns to have some specific type, describe it -column_type = ArrayType(IntegerType()) - -json = JSON() -df = df.select( - df.id, - json.parse_column("array_column", column_type), -) -``` - -### `DBWriter` - -For writing JSON data to ClickHouse, use the [`JSON.serialize_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.serialize_column) method to convert a DataFrame column to JSON format efficiently and write it as a `String` column in Clickhouse. - -```python -from onetl.file.format import JSON -from onetl.connection import ClickHouse -from onetl.db import DBWriter - -clickhouse = ClickHouse(...) - -clickhouse.execute( - """ - CREATE TABLE default.target_tbl ( - id Int32, - array_column_json String, - ) - ENGINE = MergeTree() - ORDER BY id - """, -) - -json = JSON() -df = df.select( - df.id, - json.serialize_column(df.array_column).alias("array_column_json"), -) - -writer.run(df) -``` - -Then you can parse this column on Clickhouse side - for example, by creating a view: - -```sql -SELECT - id, - JSONExtract(json_column, 'Array(String)') AS array_column -FROM target_tbl -``` - -You can also use [ALIAS](https://clickhouse.com/docs/en/sql-reference/statements/create/table#alias) -or [MATERIALIZED](https://clickhouse.com/docs/en/sql-reference/statements/create/table#materialized) columns -to avoid writing such expression in every `SELECT` clause all the time: - -```sql -CREATE TABLE default.target_tbl ( - id Int32, - array_column_json String, - -- computed column - array_column Array(String) ALIAS JSONExtract(json_column, 'Array(String)') - -- or materialized column - -- array_column Array(String) MATERIALIZED JSONExtract(json_column, 'Array(String)') -) -ENGINE = MergeTree() -ORDER BY id -``` - -Downsides: - -* Using `SELECT JSONExtract(...)` or `ALIAS` column can be expensive, because value is calculated on every row access. This can be especially harmful if such column is used in `WHERE` clause. -* `ALIAS` and `MATERIALIZED` columns are not included in `SELECT *` clause, they should be added explicitly: `SELECT *, calculated_column FROM table`. - -#### WARNING -[EPHEMERAL](https://clickhouse.com/docs/en/sql-reference/statements/create/table#ephemeral) columns are not supported by Spark -because they cannot be selected to determine target column type. diff --git a/mddocs/markdown/connection/db_connection/clickhouse/write.md b/mddocs/markdown/connection/db_connection/clickhouse/write.md deleted file mode 100644 index dd8ca31cc..000000000 --- a/mddocs/markdown/connection/db_connection/clickhouse/write.md +++ /dev/null @@ -1,187 +0,0 @@ - - -# Writing to Clickhouse using `DBWriter` - -For writing data to Clickhouse, use [`DBWriter`](../../../db/db_writer.md#onetl.db.db_writer.db_writer.DBWriter). - -#### WARNING -Please take into account [Clickhouse <-> Spark type mapping](types.md#clickhouse-types) - -#### WARNING -It is always recommended to create table explicitly using [Clickhouse.execute](execute.md#clickhouse-execute) -instead of relying on Spark’s table DDL generation. - -This is because Spark’s DDL generator can create columns with different precision and types than it is expected, -causing precision loss or other issues. - -## Examples - -```python -from onetl.connection import Clickhouse -from onetl.db import DBWriter - -clickhouse = Clickhouse(...) - -df = ... # data is here - -writer = DBWriter( - connection=clickhouse, - target="schema.table", - options=Clickhouse.WriteOptions( - if_exists="append", - # ENGINE is required by Clickhouse - createTableOptions="ENGINE = MergeTree() ORDER BY id", - ), -) - -writer.run(df) -``` - -## Options - -Method above accepts [`Clickhouse.WriteOptions`](#onetl.connection.db_connection.clickhouse.options.ClickhouseWriteOptions) - -### *pydantic model* onetl.connection.db_connection.clickhouse.options.ClickhouseWriteOptions - -Spark JDBC writing options. - -#### Versionadded -Added in version 0.5.0: Replace `Clickhouse.Options` → `Clickhouse.WriteOptions` - -### Examples - -#### NOTE -You can pass any value -[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), -even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! - -The set of supported options depends on Spark version. - -```python -from onetl.connection import Clickhouse - -options = Clickhouse.WriteOptions( - if_exists="append", - batchsize=20_000, - customSparkOption="value", -) -``` - - - -#### *field* if_exists *: JDBCTableExistBehavior* *= JDBCTableExistBehavior.APPEND* *(alias 'mode')* - -Behavior of writing data into existing table. - -Possible values: -: * `append` (default) - : Adds new rows into existing table. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : Data is appended to a table. Table has the same DDL as before writing data -
- #### WARNING - This mode does not check whether table already contains - rows from dataframe, so duplicated rows can be created. -
- Also Spark does not support passing custom options to - insert statement, like `ON CONFLICT`, so don’t try to - implement deduplication using unique indexes or constraints. -
- Instead, write to staging table and perform deduplication - using `execute` method. - * `replace_entire_table` - : **Table is dropped and then created, or truncated**. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : Table content is replaced with dataframe content. -
- After writing completed, target table could either have the same DDL as - before writing data (`truncate=True`), or can be recreated (`truncate=False` - or source does not support truncation). - * `ignore` - : Ignores the write operation if the table already exists. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : The write operation is ignored, and no data is written to the table. - * `error` - : Raises an error if the table already exists. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : An error is raised, and no data is written to the table. - -#### Versionchanged -Changed in version 0.9.0: Renamed `mode` → `if_exists` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* batchsize *: int* *= 20000* - -How many rows can be inserted per round trip. - -Tuning this option can influence performance of writing. - -#### WARNING -Default value is different from Spark. - -Spark uses quite small value `1000`, which is absolutely not usable -in BigData world. - -Thus we’ve overridden default value with `20_000`, -which should increase writing performance. - -You can increase it even more, up to `50_000`, -but it depends on your database load and number of columns in the row. -Higher values does not increase performance. - -#### Versionchanged -Changed in version 0.4.0: Changed default value from 1000 to 20_000 - - - -#### *field* isolation_level *: str* *= 'READ_UNCOMMITTED'* *(alias 'isolationLevel')* - -The transaction isolation level, which applies to current connection. - -Possible values: -: * `NONE` (as string, not Python’s `None`) - * `READ_COMMITTED` - * `READ_UNCOMMITTED` - * `REPEATABLE_READ` - * `SERIALIZABLE` - -Values correspond to transaction isolation levels defined by JDBC standard. -Please refer the documentation for -[java.sql.Connection](https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html). - - diff --git a/mddocs/markdown/connection/db_connection/greenplum/connection.md b/mddocs/markdown/connection/db_connection/greenplum/connection.md deleted file mode 100644 index b180a5fc7..000000000 --- a/mddocs/markdown/connection/db_connection/greenplum/connection.md +++ /dev/null @@ -1,144 +0,0 @@ - - -# Greenplum connection - -### *class* onetl.connection.db_connection.greenplum.connection.Greenplum(\*, spark: SparkSession, host: Host, user: str, password: SecretStr, database: str, port: int = 5432, extra: GreenplumExtra = GreenplumExtra(tcpKeepAlive='true')) - -Greenplum connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on package `io.pivotal:greenplum-spark:2.2.0` -([VMware Greenplum connector for Spark](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/index.html)). - -#### SEE ALSO -Before using this connector please take into account [Prerequisites](prerequisites.md#greenplum-prerequisites) - -#### Versionadded -Added in version 0.5.0. - -* **Parameters:** - **host** - : Host of Greenplum master. For example: `test.greenplum.domain.com` or `193.168.1.17` - - **port** - : Port of Greenplum master - - **user** - : User, which have proper access to the database. For example: `some_user` - - **password** - : Password for database connection - - **database** - : Database in RDBMS, NOT schema. -
- See [this page](https://www.educba.com/postgresql-database-vs-schema/) for more details - - **spark** - : Spark session. - - **extra** - : Specifies one or more extra parameters by which clients can connect to the instance. -
- For example: `{"tcpKeepAlive": "true", "server.port": "50000-65535"}` -
- Supported options are: - : * All [Postgres JDBC driver properties](https://jdbc.postgresql.org/documentation/use/) - * Properties from [Greenplum connector for Spark documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html) page, but only starting with `server.` or `pool.` - -### Examples - -Create and check Greenplum connection: - -```python -from onetl.connection import Greenplum -from pyspark.sql import SparkSession - -# Create Spark session with Greenplum connector loaded -maven_packages = Greenplum.get_packages(spark_version="3.2") -spark = ( - SparkSession.builder.appName("spark-app-name") - .config("spark.jars.packages", ",".join(maven_packages)) - .config("spark.executor.allowSparkContext", "true") - # IMPORTANT!!! - # Set number of executors according to "Prerequisites" -> "Number of executors" - .config("spark.dynamicAllocation.maxExecutors", 10) - .config("spark.executor.cores", 1) - .getOrCreate() -) - -# IMPORTANT!!! -# Set port range of executors according to "Prerequisites" -> "Network ports" -extra = { - "server.port": "41000-42000", -} - -# Create connection -greenplum = Greenplum( - host="master.host.or.ip", - user="user", - password="*****", - database="target_database", - extra=extra, - spark=spark, -).check() -``` - - - -#### check() - -Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If not, an exception will be raised. - -* **Returns:** - Connection itself -* **Raises:** - RuntimeError - : If the connection is not available - -### Examples - -```python -connection.check() -``` - - - -#### *classmethod* get_packages(\*, scala_version: str | None = None, spark_version: str | None = None, package_version: str | None = None) → list[str] - -Get package names to be downloaded by Spark. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -You should pass either `scala_version` or `spark_version`. - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **scala_version** - : Scala version in format `major.minor`. -
- If `None`, `spark_version` is used to determine Scala version. - - **spark_version** - : Spark version in format `major.minor`. -
- Used only if `scala_version=None`. - - **package_version** - : Package version in format `major.minor.patch` -
- #### Versionadded - Added in version 0.10.1. - -### Examples - -```python -from onetl.connection import Greenplum - -Greenplum.get_packages(scala_version="2.12") -Greenplum.get_packages(spark_version="3.2", package_version="2.3.0") -``` - - diff --git a/mddocs/markdown/connection/db_connection/greenplum/execute.md b/mddocs/markdown/connection/db_connection/greenplum/execute.md deleted file mode 100644 index c923512a3..000000000 --- a/mddocs/markdown/connection/db_connection/greenplum/execute.md +++ /dev/null @@ -1,195 +0,0 @@ - - -# Executing statements in Greenplum - -#### WARNING -Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. - -Do **NOT** use them to read large amounts of data. Use [DBReader](read.md#greenplum-read) instead. - -## How to - -There are 2 ways to execute some statement in Greenplum - -### Use `Greenplum.fetch` - -Use this method to perform some `SELECT` query which returns **small number or rows**, like reading -Greenplum config, or reading data from some reference table. Method returns Spark DataFrame. - -Method accepts [`Greenplum.FetchOptions`](#onetl.connection.db_connection.greenplum.options.GreenplumFetchOptions). - -Connection opened using this method should be then closed with `connection.close()` or `with connection:`. - -#### WARNING -`Greenplum.fetch` is implemented using Postgres JDBC connection, -so types are handled a bit differently than in `DBReader`. See [Postgres <-> Spark type mapping](../postgres/types.md#postgres-types). - -#### Syntax support - -This method supports **any** query syntax supported by Greenplum, like: - -* ✅︎ `SELECT ... FROM ...` -* ✅︎ `WITH alias AS (...) SELECT ...` -* ✅︎ `SELECT func(arg1, arg2)` or `{call func(arg1, arg2)}` - special syntax for calling functions -* ❌ `SET ...; SELECT ...;` - multiple statements not supported - -#### Examples - -```python -from onetl.connection import Greenplum - -greenplum = Greenplum(...) - -df = greenplum.fetch( - "SELECT value FROM some.reference_table WHERE key = 'some_constant'", - options=Greenplum.FetchOptions(queryTimeout=10), -) -greenplum.close() -value = df.collect()[0][0] # get value from first row and first column -``` - -### Use `Greenplum.execute` - -Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. - -Method accepts [`Greenplum.ExecuteOptions`](#onetl.connection.db_connection.greenplum.options.GreenplumExecuteOptions). - -Connection opened using this method should be then closed with `connection.close()` or `with connection:`. - -#### Syntax support - -This method supports **any** query syntax supported by Greenplum, like: - -* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on -* ✅︎ `ALTER ...` -* ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on -* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on -* ✅︎ `CALL procedure(arg1, arg2) ...` -* ✅︎ `SELECT func(arg1, arg2)` or `{call func(arg1, arg2)}` - special syntax for calling functions -* ✅︎ other statements not mentioned here -* ❌ `SET ...; SELECT ...;` - multiple statements not supported - -#### Examples - -```python -from onetl.connection import Greenplum - -greenplum = Greenplum(...) - -greenplum.execute("DROP TABLE schema.table") -greenplum.execute( - """ - CREATE TABLE schema.table ( - id int, - key text, - value real - ) - DISTRIBUTED BY id - """, - options=Greenplum.ExecuteOptions(queryTimeout=10), -) -``` - -## Interaction schema - -Unlike reading & writing, executing statements in Greenplum is done **only** through Greenplum master node, -without any interaction between Greenplum segments and Spark executors. More than that, Spark executors are not used in this case. - -The only port used while interacting with Greenplum in this case is `5432` (Greenplum master port). - -### Spark <-> Greenplum interaction during Greenplum.execute()/Greenplum.fetch() - -## Options - -### *pydantic model* onetl.connection.db_connection.greenplum.options.GreenplumFetchOptions - -Options related to fetching data from databases via JDBC. - -#### Versionadded -Added in version 0.11.0: Replace `Greenplum.JDBCOptions` → `Greenplum.FetchOptions` - -### Examples - -#### NOTE -You can pass any value supported by underlying JDBC driver class, -even if it is not mentioned in this documentation. - -```python -from onetl.connection import Greenplum - -options = Greenplum.FetchOptions( - queryTimeout=60_000, - fetchsize=100_000, - customSparkOption="value", -) -``` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int | None* *= None* - -How many rows to fetch per round trip. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value depends on driver. For example, Oracle has -default `fetchsize=10`. - - - -### *pydantic model* onetl.connection.db_connection.greenplum.options.GreenplumExecuteOptions - -Options related to executing statements in databases via JDBC. - -#### Versionadded -Added in version 0.11.0: Replace `Greenplum.JDBCOptions` → `Greenplum.ExecuteOptions` - -### Examples - -#### NOTE -You can pass any value supported by underlying JDBC driver class, -even if it is not mentioned in this documentation. - -```python -from onetl.connection import Greenplum - -options = Greenplum.ExecuteOptions( - queryTimeout=60_000, - customSparkOption="value", -) -``` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int | None* *= None* - -How many rows to fetch per round trip. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value depends on driver. For example, Oracle has -default `fetchsize=10`. - - diff --git a/mddocs/markdown/connection/db_connection/greenplum/index.md b/mddocs/markdown/connection/db_connection/greenplum/index.md deleted file mode 100644 index 9fcc68f66..000000000 --- a/mddocs/markdown/connection/db_connection/greenplum/index.md +++ /dev/null @@ -1,18 +0,0 @@ - - -# Greenplum - -# Connection - -* [Prerequisites](prerequisites.md) -* [Greenplum connection](connection.md) - -# Operations - -* [Reading from Greenplum using `DBReader`](read.md) -* [Writing to Greenplum using `DBWriter`](write.md) -* [Executing statements in Greenplum](execute.md) - -# Troubleshooting - -* [Greenplum <-> Spark type mapping](types.md) diff --git a/mddocs/markdown/connection/db_connection/greenplum/prerequisites.md b/mddocs/markdown/connection/db_connection/greenplum/prerequisites.md deleted file mode 100644 index 15c06300b..000000000 --- a/mddocs/markdown/connection/db_connection/greenplum/prerequisites.md +++ /dev/null @@ -1,358 +0,0 @@ - - -# Prerequisites - -## Version Compatibility - -* Greenplum server versions: - : * Officially declared: 5.x, 6.x, and 7.x (which requires `Greenplum.get_packages(package_version="2.3.0")` or higher) - * Actually tested: 6.23, 7.0 -* Spark versions: 2.3.x - 3.2.x (Spark 3.3+ is not supported yet) -* Java versions: 8 - 11 - -See [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.2/greenplum-connector-spark/release_notes.html). - -## Installing PySpark - -To use Greenplum connector you should have PySpark installed (or injected to `sys.path`) -BEFORE creating the connector instance. - -See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. - -## Downloading VMware package - -To use Greenplum connector you should download connector `.jar` file from -[VMware website](https://network.tanzu.vmware.com/products/vmware-greenplum#/releases/1413479/file_groups/16966) -and then pass it to Spark session. - -#### WARNING -Please pay attention to [Spark & Scala version compatibility](../../../install/spark.md#spark-compatibility-matrix). - -#### WARNING -There are issues with using package of version 2.3.0/2.3.1 with Greenplum 6.x - connector can -open transaction with `SELECT * FROM table LIMIT 0` query, but does not close it, which leads to deadlocks -during write. - -There are several ways to do that. See [Injecting Java packages](../../../install/spark.md#java-packages) for details. - -#### NOTE -If you’re uploading package to private package repo, use `groupId=io.pivotal` and `artifactoryId=greenplum-spark_2.12` -(`2.12` is Scala version) to give uploaded package a proper name. - -## Connecting to Greenplum - -### Interaction schema - -Spark executors open ports to listen incoming requests. -Greenplum segments are initiating connections to Spark executors using [EXTERNAL TABLE](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-sql_commands-CREATE_EXTERNAL_TABLE.html) -functionality, and send/read data using [gpfdist protocol](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/admin_guide-external-g-using-the-greenplum-parallel-file-server--gpfdist-.html#about-gpfdist-setup-and-performance-1). - -Data is **not** send through Greenplum master. -Greenplum master only receives commands to start reading/writing process, and manages all the metadata (external table location, schema and so on). - -More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/overview.html). - -### Set number of connections - -#### WARNING -This is very important!!! - -If you don’t limit number of connections, you can exceed the [max_connections](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/admin_guide-client_auth.html#limiting-concurrent-connections#limiting-concurrent-connections-2) -limit set on the Greenplum side. It’s usually not so high, e.g. 500-1000 connections max, -depending on your Greenplum instance settings and using connection balancers like `pgbouncer`. - -Consuming all available connections means **nobody** (even admin users) can connect to Greenplum. - -Each job on the Spark executor makes its own connection to Greenplum master node, -so you need to limit number of connections to avoid opening too many of them. - -* Reading about `5-10Gb` of data requires about `3-5` parallel connections. -* Reading about `20-30Gb` of data requires about `5-10` parallel connections. -* Reading about `50Gb` of data requires ~ `10-20` parallel connections. -* Reading about `100+Gb` of data requires `20-30` parallel connections. -* Opening more than `30-50` connections is not recommended. - -Number of connections can be limited by 2 ways: - -* By limiting number of Spark executors and number of cores per-executor. Max number of parallel jobs is `executors * cores`. - -Spark with master=local - -```py -spark = ( - SparkSession.builder - # Spark will run with 5 threads in local mode, allowing up to 5 parallel tasks - .config("spark.master", "local[5]") - .config("spark.executor.cores", 1) -).getOrCreate() -``` - -Spark with master=yarn or master=k8s, dynamic allocation - -```py -spark = ( - SparkSession.builder - .config("spark.master", "yarn") - # Spark will start MAX 10 executors with 1 core each (dynamically), so max number of parallel jobs is 10 - .config("spark.dynamicAllocation.maxExecutors", 10) - .config("spark.executor.cores", 1) -).getOrCreate() -``` - -Spark with master=yarn or master=k8s, static allocation - -```py -spark = ( - SparkSession.builder - .config("spark.master", "yarn") - # Spark will start EXACTLY 10 executors with 1 core each, so max number of parallel jobs is 10 - .config("spark.executor.instances", 10) - .config("spark.executor.cores", 1) -).getOrCreate() -``` - -* By limiting connection pool size user by Spark (**only** for Spark with `master=local`): - -```python -spark = SparkSession.builder.config("spark.master", "local[*]").getOrCreate() - -# No matter how many executors are started and how many cores they have, -# number of connections cannot exceed pool size: -Greenplum( - ..., - extra={ - "pool.maxSize": 10, - }, -) -``` - -See [connection pooling](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/using_the_connector.html#jdbcconnpool) -documentation. - -* By setting [`num_partitions`](read.md#onetl.connection.db_connection.greenplum.options.GreenplumReadOptions.num_partitions) - and [`partition_column`](read.md#onetl.connection.db_connection.greenplum.options.GreenplumReadOptions.partition_column) (not recommended). - -### Allowing connection to Greenplum master - -Ask your Greenplum cluster administrator to allow your user to connect to Greenplum master node, -e.g. by updating `pg_hba.conf` file. - -More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/admin_guide-client_auth.html#limiting-concurrent-connections#allowing-connections-to-greenplum-database-0). - -### Set connection port - -#### Spark with `master=k8s` - -Please follow [the official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/configure.html#k8scfg) - -#### Spark with `master=yarn` or `master=local` - -To read data from Greenplum using Spark, following ports should be opened in firewall between Spark and Greenplum: - -* Spark driver and all Spark executors -> port `5432` on Greenplum master node. - - This port number should be set while connecting to Greenplum: - ```python - greenplum = Greenplum(host="master.host", port=5432, ...) - ``` -* Greenplum segments -> some port range (e.g. `41000-42000`) **listened by Spark executors**. - - This range should be set in `extra` option: - ```python - greenplum = Greenplum( - ..., - extra={ - "server.port": "41000-42000", - }, - ) - ``` - - Number of ports in this range is `number of parallel running Spark sessions` \* `number of parallel connections per session`. - - Number of connections per session (see below) is usually less than `30` (see above). - - Number of session depends on your environment: - : * For `master=local` only few ones-tens sessions can be started on the same host, depends on available RAM and CPU. - * For `master=yarn` hundreds or thousands of sessions can be started simultaneously, - but they are executing on different cluster nodes, so one port can be opened on different nodes at the same time. - -More details can be found in official documentation: -: * [port requirements](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/sys_reqs.html#network-port-requirements) - * [format of server.port value](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#server.port) - * [port troubleshooting](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/troubleshooting.html#port-errors) - -### Set connection host - -#### Spark with `master=k8s` - -Please follow [the official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/configure.html#k8scfg) - -#### Spark with `master=local` - -By default, Greenplum connector tries to resolve IP of current host, and then pass it as `gpfdist` URL to Greenplum segment. -This may fail in some cases. - -For example, IP can be resolved using `/etc/hosts` content like this: - -```text -127.0.0.1 localhost real-host-name -``` - -```bash -$ hostname -f -localhost - -$ hostname -i -127.0.0.1 -``` - -Reading/writing data to Greenplum will fail with following exception: - -```text -org.postgresql.util.PSQLException: ERROR: connection with gpfdist failed for -"gpfdist://127.0.0.1:49152/local-1709739764667/exec/driver", -effective url: "http://127.0.0.1:49152/local-1709739764667/exec/driver": -error code = 111 (Connection refused); (seg3 slice1 12.34.56.78:10003 pid=123456) -``` - -There are 2 ways to fix that: - -* Explicitly pass your host IP address to connector, like this - ```python - import os - - # pass here real host IP (accessible from GP segments) - os.environ["HOST_IP"] = "192.168.1.1" - - greenplum = Greenplum( - ..., - extra={ - # connector will read IP from this environment variable - "server.hostEnv": "env.HOST_IP", - }, - spark=spark, - ) - ``` - - More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#server.hostenv). -* Update `/etc/hosts` file to include real host IP: - ```text - 127.0.0.1 localhost - # this IP should be accessible from GP segments - 192.168.1.1 driver-host-name - ``` - - So Greenplum connector will properly resolve host IP. - -#### Spark with `master=yarn` - -The same issue with resolving IP address can occur on Hadoop cluster node, but it’s tricky to fix, because each node has a different IP. - -There are 3 ways to fix that: - -* Pass node hostname to `gpfdist` URL. So IP will be resolved on segment side: - ```python - greenplum = Greenplum( - ..., - extra={ - "server.useHostname": "true", - }, - ) - ``` - - But this may fail if Hadoop cluster node hostname cannot be resolved from Greenplum segment side. - - More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#server.usehostname). -* Set specific network interface to get IP address from: - ```python - greenplum = Greenplum( - ..., - extra={ - "server.nic": "eth0", - }, - ) - ``` - - You can get list of network interfaces using this command. - - #### NOTE - This command should be executed on Hadoop cluster node, **not** Spark driver host! - - ```bash - $ ip address - 1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 - inet 127.0.0.1/8 scope host lo - valid_lft forever preferred_lft forever - 2: eth0: mtu 1500 qdisc fq_codel state UP group default qlen 1000 - inet 192.168.1.1/24 brd 192.168.1.255 scope global dynamic noprefixroute eth0 - valid_lft 83457sec preferred_lft 83457sec - ``` - - Note that in this case **each** Hadoop cluster node node should have network interface with name `eth0`. - - More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#server.nic). -* Update `/etc/hosts` on each Hadoop cluster node to include real node IP: - ```text - 127.0.0.1 localhost - # this IP should be accessible from GP segments - 192.168.1.1 cluster-node-name - ``` - - So Greenplum connector will properly resolve node IP. - -### Set required grants - -Ask your Greenplum cluster administrator to set following grants for a user, -used for creating a connection: - -Read + Write - -```sql --- get access to get tables metadata & cluster information -GRANT SELECT ON information_schema.tables TO username; -GRANT SELECT ON pg_attribute TO username; -GRANT SELECT ON pg_class TO username; -GRANT SELECT ON pg_namespace TO username; -GRANT SELECT ON pg_settings TO username; -GRANT SELECT ON pg_stats TO username; -GRANT SELECT ON gp_distributed_xacts TO username; -GRANT SELECT ON gp_segment_configuration TO username; --- Greenplum 5.x only -GRANT SELECT ON gp_distribution_policy TO username; - --- allow creating external tables in the same schema as source/target table -GRANT USAGE ON SCHEMA myschema TO username; -GRANT CREATE ON SCHEMA myschema TO username; -ALTER USER username CREATEEXTTABLE(type = 'readable', protocol = 'gpfdist') CREATEEXTTABLE(type = 'writable', protocol = 'gpfdist'); - --- allow read access to specific table (to get column types) --- allow write access to specific table -GRANT SELECT, INSERT ON myschema.mytable TO username; -``` - -Read only - -```sql --- get access to get tables metadata & cluster information -GRANT SELECT ON information_schema.tables TO username; -GRANT SELECT ON pg_attribute TO username; -GRANT SELECT ON pg_class TO username; -GRANT SELECT ON pg_namespace TO username; -GRANT SELECT ON pg_settings TO username; -GRANT SELECT ON pg_stats TO username; -GRANT SELECT ON gp_distributed_xacts TO username; -GRANT SELECT ON gp_segment_configuration TO username; --- Greenplum 5.x only -GRANT SELECT ON gp_distribution_policy TO username; - --- allow creating external tables in the same schema as source table -GRANT USAGE ON SCHEMA schema_to_read TO username; -GRANT CREATE ON SCHEMA schema_to_read TO username; --- yes, ``writable`` for reading from GP, because data is written from Greenplum to Spark executor. -ALTER USER username CREATEEXTTABLE(type = 'writable', protocol = 'gpfdist'); - --- allow read access to specific table -GRANT SELECT ON schema_to_read.table_to_read TO username; -``` - -More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/install_cfg.html#role-privileges). diff --git a/mddocs/markdown/connection/db_connection/greenplum/read.md b/mddocs/markdown/connection/db_connection/greenplum/read.md deleted file mode 100644 index 476f6f0db..000000000 --- a/mddocs/markdown/connection/db_connection/greenplum/read.md +++ /dev/null @@ -1,405 +0,0 @@ - - -# Reading from Greenplum using `DBReader` - -Data can be read from Greenplum to Spark using [`DBReader`](../../../db/db_reader.md#onetl.db.db_reader.db_reader.DBReader). -It also supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading. - -#### WARNING -Please take into account [Greenplum <-> Spark type mapping](types.md#greenplum-types). - -#### NOTE -Unlike JDBC connectors, *Greenplum connector for Spark* does not support -executing **custom** SQL queries using `.sql` method. Connector can be used to only read data from a table or view. - -## Supported DBReader features - -* ✅︎ `columns` (see note below) -* ✅︎ `where` (see note below) -* ✅︎ `hwm` (see note below), supported strategies: -* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) -* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) -* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) -* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) -* ❌ `hint` (is not supported by Greenplum) -* ❌ `df_schema` -* ✅︎ `options` (see [`Greenplum.ReadOptions`](#onetl.connection.db_connection.greenplum.options.GreenplumReadOptions)) - -#### WARNING -In case of Greenplum connector, `DBReader` does not generate raw `SELECT` query. Instead it relies on Spark SQL syntax -which in some cases (using column projection and predicate pushdown) can be converted to Greenplum SQL. - -So `columns`, `where` and `hwm.expression` should be specified in [Spark SQL](https://spark.apache.org/docs/latest/sql-ref-syntax.html) syntax, -not Greenplum SQL. - -This is OK: - -```python -DBReader( - columns=[ - "some_column", - # this cast is executed on Spark side - "CAST(another_column AS STRING)", - ], - # this predicate is parsed by Spark, and can be pushed down to Greenplum - where="some_column LIKE 'val1%'", -) -``` - -This is will fail: - -```python -DBReader( - columns=[ - "some_column", - # Spark does not have `text` type - "CAST(another_column AS text)", - ], - # Spark does not support ~ syntax for regexp matching - where="some_column ~ 'val1.*'", -) -``` - -## Examples - -Snapshot strategy: - -```python -from onetl.connection import Greenplum -from onetl.db import DBReader - -greenplum = Greenplum(...) - -reader = DBReader( - connection=greenplum, - source="schema.table", - columns=["id", "key", "CAST(value AS string) value", "updated_dt"], - where="key = 'something'", -) -df = reader.run() -``` - -Incremental strategy: - -```python -from onetl.connection import Greenplum -from onetl.db import DBReader -from onetl.strategy import IncrementalStrategy - -greenplum = Greenplum(...) - -reader = DBReader( - connection=greenplum, - source="schema.table", - columns=["id", "key", "CAST(value AS string) value", "updated_dt"], - where="key = 'something'", - hwm=DBReader.AutoDetectHWM(name="greenplum_hwm", expression="updated_dt"), -) - -with IncrementalStrategy(): - df = reader.run() -``` - -## Interaction schema - -High-level schema is described in [Prerequisites](prerequisites.md#greenplum-prerequisites). You can find detailed interaction schema below. - -### Spark <-> Greenplum interaction during DBReader.run() - -## Recommendations - -### Select only required columns - -Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Greenplum to Spark. - -### Pay attention to `where` value - -Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. -This both reduces the amount of data send from Greenplum to Spark, and may also improve performance of the query. -Especially if there are indexes or partitions for columns used in `where` clause. - -### Read data in parallel - -`DBReader` in case of Greenplum connector requires view or table to have a column which is used by Spark -for parallel reads. - -Choosing proper column allows each Spark executor to read only part of data stored in the specified segment, -avoiding moving large amounts of data between segments, which improves reading performance. - -#### Using `gp_segment_id` - -By default, `DBReader` will use [gp_segment_id](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/troubleshooting.html#reading-from-a-view) -column for parallel data reading. Each DataFrame partition will contain data of a specific Greenplum segment. - -This allows each Spark executor read only data from specific Greenplum segment, avoiding moving large amounts of data between segments. - -If view is used, it is recommended to include `gp_segment_id` column to this view: - -### Reading from view with gp_segment_id column - -```python -from onetl.connection import Greenplum -from onetl.db import DBReader - -greenplum = Greenplum(...) - -greenplum.execute( - """ - CREATE VIEW schema.view_with_gp_segment_id AS - SELECT - id, - some_column, - another_column, - gp_segment_id -- IMPORTANT - FROM schema.some_table - """, -) - -reader = DBReader( - connection=greenplum, - source="schema.view_with_gp_segment_id", -) -df = reader.run() -``` - -#### Using custom `partition_column` - -Sometimes table or view is lack of `gp_segment_id` column, but there is some column -with value range correlated with Greenplum segment distribution. - -In this case, custom column can be used instead: - -### Reading from view with custom partition_column - -```python -from onetl.connection import Greenplum -from onetl.db import DBReader - -greenplum = Greenplum(...) - -greenplum.execute( - """ - CREATE VIEW schema.view_with_partition_column AS - SELECT - id, - some_column, - part_column -- correlated to greenplum segment ID - FROM schema.some_table - """, -) - -reader = DBReader( - connection=greenplum, - source="schema.view_with_partition_column", - options=Greenplum.ReadOptions( - # parallelize data using specified column - partitionColumn="part_column", - # create 10 Spark tasks, each will read only part of table data - partitions=10, - ), -) -df = reader.run() -``` - -#### Reading `DISTRIBUTED REPLICATED` tables - -Replicated tables do not have `gp_segment_id` column at all, so you need to set `partition_column` to some column name -of type integer/bigint/smallint. - -### Parallel `JOIN` execution - -In case of using views which require some data motion between Greenplum segments, like `JOIN` queries, another approach should be used. - -Each Spark executor N will run the same query, so each of N query will start its own JOIN process, leading to really heavy load on Greenplum segments. -**This should be avoided**. - -Instead is recommended to run `JOIN` query on Greenplum side, save the result to an intermediate table, -and then read this table using `DBReader`: - -### Reading from view using intermediate table - -```python -from onetl.connection import Greenplum -from onetl.db import DBReader - -greenplum = Greenplum(...) - -greenplum.execute( - """ - CREATE UNLOGGED TABLE schema.intermediate_table AS - SELECT - id, - tbl1.col1, - tbl1.data, - tbl2.another_data - FROM - schema.table1 as tbl1 - JOIN - schema.table2 as tbl2 - ON - tbl1.col1 = tbl2.col2 - WHERE ... - """, -) - -reader = DBReader( - connection=greenplum, - source="schema.intermediate_table", -) -df = reader.run() - -# write dataframe somethere - -greenplum.execute( - """ - DROP TABLE schema.intermediate_table - """, -) -``` - -#### WARNING -**NEVER** do that: - -```python -df1 = DBReader(connection=greenplum, target="public.table1", ...).run() -df2 = DBReader(connection=greenplum, target="public.table2", ...).run() - -joined_df = df1.join(df2, on="col") -``` - -This will lead to sending all the data from both `table1` and `table2` to Spark executor memory, and then `JOIN` -will be performed on Spark side, not inside Greenplum. This is **VERY** inefficient. - -#### `TEMPORARY` tables notice - -Someone could think that writing data from view or result of `JOIN` to `TEMPORARY` table, -and then passing it to `DBReader`, is an efficient way to read data from Greenplum. This is because temp tables are not generating WAL files, -and are automatically deleted after finishing the transaction. - -That will **NOT** work. Each Spark executor establishes its own connection to Greenplum. -And each connection starts its own transaction which means that every executor will read empty temporary table. - -You should use [UNLOGGED](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-sql_commands-CREATE_TABLE.html) tables -to write data to intermediate table without generating WAL logs. - -## Options - -### *pydantic model* onetl.connection.db_connection.greenplum.options.GreenplumReadOptions - -VMware’s Greenplum Spark connector reading options. - -#### WARNING -Some options, like `url`, `dbtable`, `server.*`, `pool.*`, -etc are populated from connection attributes, and cannot be overridden by the user in `ReadOptions` to avoid issues. - -### Examples - -#### NOTE -You can pass any value -[supported by connector](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/read_from_gpdb.html), -even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! - -The set of supported options depends on connector version. - -```python -from onetl.connection import Greenplum - -options = Greenplum.ReadOptions( - partitionColumn="reg_id", - partitions=10, -) -``` - - - -#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* - -Column used to parallelize reading from a table. - -#### WARNING -You should not change this option, unless you know what you’re doing. - -It’s preferable to use default values to read data parallel by number of segments in Greenplum cluster. - -Possible values: -: * `None` (default): - : Spark generates N jobs (where N == number of segments in Greenplum cluster), - each job is reading only data from a specific segment - (filtering data by `gp_segment_id` column). -
- This is very effective way to fetch the data from a cluster. - * table column - : Allocate each executor a range of values from a specific column. -
- Spark generates for each executor an SQL query: -
- Executor 1: - ```sql - SELECT ... FROM table - WHERE (partition_column >= lowerBound - OR partition_column IS NULL) - AND partition_column < (lower_bound + stride) - ``` -
- Executor 2: - ```sql - SELECT ... FROM table - WHERE partition_column >= (lower_bound + stride) - AND partition_column < (lower_bound + 2 * stride) - ``` -
- … -
- Executor N: - ```sql - SELECT ... FROM table - WHERE partition_column >= (lower_bound + (N-1) * stride) - AND partition_column <= upper_bound - ``` -
- Where `stride=(upper_bound - lower_bound) / num_partitions`, - `lower_bound=MIN(partition_column)`, `upper_bound=MAX(partition_column)`. -
- #### NOTE - Column type must be numeric. Other types are not supported. -
- #### NOTE - [`num_partitions`](#onetl.connection.db_connection.greenplum.options.GreenplumReadOptions.num_partitions) is used just to - calculate the partition stride, **NOT** for filtering the rows in table. - So all rows in the table will be returned (unlike *Incremental* [Read Strategies](../../../strategy/index.md#strategy)). -
- #### NOTE - All queries are executed in parallel. To execute them sequentially, use *Batch* [Read Strategies](../../../strategy/index.md#strategy). - -#### WARNING -Both options [`partition_column`](#onetl.connection.db_connection.greenplum.options.GreenplumReadOptions.partition_column) and [`num_partitions`](#onetl.connection.db_connection.greenplum.options.GreenplumReadOptions.num_partitions) should have a value, -or both should be `None` - -### Examples - -Read data in 10 parallel jobs by range of values in `id_column` column: - -```python -Greenplum.ReadOptions( - partitionColumn="id_column", - partitions=10, -) -``` - - - -#### *field* num_partitions *: int | None* *= None* *(alias 'partitions')* - -Number of jobs created by Spark to read the table content in parallel. - -See documentation for [`partition_column`](#onetl.connection.db_connection.greenplum.options.GreenplumReadOptions.partition_column) for more details - -#### WARNING -By default connector uses number of segments in the Greenplum cluster. -You should not change this option, unless you know what you’re doing - -#### WARNING -Both options [`partition_column`](#onetl.connection.db_connection.greenplum.options.GreenplumReadOptions.partition_column) and [`num_partitions`](#onetl.connection.db_connection.greenplum.options.GreenplumReadOptions.num_partitions) should have a value, -or both should be `None` - - diff --git a/mddocs/markdown/connection/db_connection/greenplum/types.md b/mddocs/markdown/connection/db_connection/greenplum/types.md deleted file mode 100644 index 6a6c0effb..000000000 --- a/mddocs/markdown/connection/db_connection/greenplum/types.md +++ /dev/null @@ -1,318 +0,0 @@ - - -# Greenplum <-> Spark type mapping - -#### NOTE -The results below are valid for Spark 3.2.4, and may differ on other Spark versions. - -## Type detection & casting - -Spark’s DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. - -### Reading from Greenplum - -This is how Greenplum connector performs this: - -* Execute query `SELECT * FROM table LIMIT 0` [1](#id2). -* For each column in query result get column name and Greenplum type. -* Find corresponding `Greenplum type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. -* Use Spark column projection and predicate pushdown features to build a final query. -* Create DataFrame from generated query with inferred schema. - -* **[1]** Yes, **all columns of a table**, not just selected ones. This means that if source table **contains** columns with unsupported type, the entire table cannot be read. - -### Writing to some existing Greenplum table - -This is how Greenplum connector performs this: - -* Get names of columns in DataFrame. -* Perform `SELECT * FROM table LIMIT 0` query. -* For each column in query result get column name and Greenplum type. -* Match table columns with DataFrame columns (by name, case insensitive). - If some column is present only in target table, but not in DataFrame (like `DEFAULT` or `SERIAL` column), and vice versa, raise an exception. - See [Explicit type cast](). -* Find corresponding `Spark type` → `Greenplumtype (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. -* If `Greenplumtype (write)` match `Greenplum type (read)`, no additional casts will be performed, DataFrame column will be written to Greenplum as is. -* If `Greenplumtype (write)` does not match `Greenplum type (read)`, DataFrame column will be casted to target column type **on Greenplum side**. For example, you can write column with text data to `json` column which Greenplum connector currently does not support. - -### Create new table using Spark - -#### WARNING -ABSOLUTELY NOT RECOMMENDED! - -This is how Greenplum connector performs this: - -* Find corresponding `Spark type` → `Greenplum type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. -* Generate DDL for creating table in Greenplum, like `CREATE TABLE (col1 ...)`, and run it. -* Write DataFrame to created table as is. - -More details [can be found here](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/write_to_gpdb.html). - -But Greenplum connector support only limited number of types and almost no custom clauses (like `PARTITION BY`). -So instead of relying on Spark to create tables: - -### See example - -```python -writer = DBWriter( - connection=greenplum, - target="public.table", - options=Greenplum.WriteOptions( - if_exists="append", - # by default distribution is random - distributedBy="id", - # partitionBy is not supported - ), -) -writer.run(df) -``` - -Always prefer creating table with desired DDL **BEFORE WRITING DATA**: - -### See example - -```python -greenplum.execute( - """ - CREATE TABLE public.table ( - id int32, - business_dt timestamp(6), - value json - ) - PARTITION BY RANGE (business_dt) - DISTRIBUTED BY id - """, -) - -writer = DBWriter( - connection=greenplum, - target="public.table", - options=Greenplum.WriteOptions(if_exists="append"), -) -writer.run(df) -``` - -See Greenplum [CREATE TABLE](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-sql_commands-CREATE_TABLE.html) documentation. - -## Supported types - -See: -: * [official connector documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/reference-datatype_mapping.html) - * [list of Greenplum types](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-data_types.html) - -### Numeric types - -| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | -|-----------------------------|----------------------------------|-----------------------------|---------------------------| -| `decimal` | `DecimalType(P=38, S=18)` | `decimal(P=38, S=18)` | `decimal` (unbounded) | -| `decimal(P=0..38)` | `DecimalType(P=0..38, S=0)` | `decimal(P=0..38, S=0)` | | -| `decimal(P=0..38, S=0..38)` | `DecimalType(P=0..38, S=0..38)` | `decimal(P=0..38, S=0..38)` | | -| `decimal(P=39.., S=0..)` | unsupported [2](#id4) | | | -| `real` | `FloatType()` | `real` | `real` | -| `double precision` | `DoubleType()` | `double precision` | `double precision` | -| `-` | `ByteType()` | unsupported | unsupported | -| `smallint` | `ShortType()` | `smallint` | `smallint` | -| `integer` | `IntegerType()` | `integer` | `integer` | -| `bigint` | `LongType()` | `bigint` | `bigint` | -| `money` | unsupported | | | -| `int4range` | | | | -| `int8range` | | | | -| `numrange` | | | | -| `int2vector` | | | | -* **[2]** Greenplum support decimal types with unlimited precision. But Spark’s `DecimalType(P, S)` supports maximum `P=38` (128 bit). It is impossible to read, write or operate with values of larger precision, this leads to an exception. - -### Temporal types - -| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | -|----------------------------------|----------------------------------------------------------------|-------------------------|---------------------------| -| `date` | `DateType()` | `date` | `date` | -| `time` | `TimestampType()`,
time format quirks [3](#id6) | `timestamp` | `timestamp` | -| `time(0..6)` | | | | -| `time with time zone` | | | | -| `time(0..6) with time zone` | | | | -| `timestamp` | `TimestampType()` | `timestamp` | `timestamp` | -| `timestamp(0..6)` | | | | -| `timestamp with time zone` | | | | -| `timestamp(0..6) with time zone` | | | | -| `interval` or any precision | unsupported | | | -| `daterange` | | | | -| `tsrange` | | | | -| `tstzrange` | | | | - -#### WARNING -Note that types in Greenplum and Spark have different value ranges: - -| Greenplum type | Min value | Max value | Spark type | Min value | Max value | -|------------------|-------------------------------|--------------------------------|-------------------|------------------------------|------------------------------| -| `date` | `-4713-01-01` | `5874897-01-01` | `DateType()` | `0001-01-01` | `9999-12-31` | -| `timestamp` | `-4713-01-01 00:00:00.000000` | `294276-12-31 23:59:59.999999` | `TimestampType()` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | -| `time` | `00:00:00.000000` | `24:00:00.000000` | | | | - -So not all of values can be read from Greenplum to Spark. - -References: -: * [Greenplum types documentation](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-data_types.html) - * [Spark DateType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/DateType.html) - * [Spark TimestampType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/TimestampType.html) - -* **[3]** `time` type is the same as `timestamp` with date `1970-01-01`. So instead of reading data from Postgres like `23:59:59` it is actually read `1970-01-01 23:59:59`, and vice versa. - -### String types - -| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | -|---------------------------|----------------|-------------------------|---------------------------| -| `character` | `StringType()` | `text` | `text` | -| `character(N)` | | | | -| `character varying` | | | | -| `character varying(N)` | | | | -| `text` | | | | -| `xml` | | | | -| `CREATE TYPE ... AS ENUM` | | | | -| `json` | unsupported | | | -| `jsonb` | | | | - -### Binary types - -| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | -|-------------------------|----------------------------------|-------------------------|---------------------------| -| `boolean` | `BooleanType()` | `boolean` | `boolean` | -| `bit` | unsupported | | | -| `bit(N)` | | | | -| `bit varying` | | | | -| `bit varying(N)` | | | | -| `bytea` | unsupported [4](#id8) | | | -| `-` | `BinaryType()` | `bytea` | `bytea` | -* **[4]** Yes, that’s weird. - -### Struct types - -| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | -|------------------------------|----------------|-------------------------|---------------------------| -| `T[]` | unsupported | | | -| `-` | `ArrayType()` | unsupported | | -| `CREATE TYPE sometype (...)` | `StringType()` | `text` | `text` | -| `-` | `StructType()` | unsupported | | -| `-` | `MapType()` | | | - -## Unsupported types - -Columns of these types cannot be read/written by Spark: -: * `cidr` - * `inet` - * `macaddr` - * `macaddr8` - * `circle` - * `box` - * `line` - * `lseg` - * `path` - * `point` - * `polygon` - * `tsvector` - * `tsquery` - * `uuid` - -The is a way to avoid this - just cast unsupported types to `text`. But the way this can be done is not a straightforward. - -## Explicit type cast - -### `DBReader` - -Direct casting of Greenplum types is not supported by DBReader due to the connector’s implementation specifics. - -```python -reader = DBReader( - connection=greenplum, - # will fail - columns=["CAST(unsupported_column AS text)"], -) -``` - -But there is a workaround - create a view with casting unsupported column to text (or any other supported type). -For example, you can use [to_json](https://www.postgresql.org/docs/current/functions-json.html) Postgres function to convert column of any type to string representation and then parse this column on Spark side using [`JSON.parse_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.parse_column) method. - -```python -from pyspark.sql.types import ArrayType, IntegerType - -from onetl.connection import Greenplum -from onetl.db import DBReader -from onetl.file.format import JSON - -greenplum = Greenplum(...) - -greenplum.execute( - """ - CREATE VIEW schema.view_with_json_column AS - SELECT - id, - supported_column, - to_json(array_column) array_column_as_json, - gp_segment_id -- ! important ! - FROM - schema.table_with_unsupported_columns - """, -) - -# create dataframe using this view -reader = DBReader( - connection=greenplum, - source="schema.view_with_json_column", -) -df = reader.run() - -# Define the schema for the JSON data -json_scheme = ArrayType(IntegerType()) - -df = df.select( - df.id, - df.supported_column, - JSON().parse_column(df.array_column_as_json, json_scheme).alias("array_column"), -) -``` - -### `DBWriter` - -To write data to a `text` or `json` column in a Greenplum table, use [`JSON.serialize_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.serialize_column) method. - -```python -from onetl.connection import Greenplum -from onetl.db import DBWriter -from onetl.file.format import JSON - -greenplum = Greenplum(...) - -greenplum.execute( - """ - CREATE TABLE schema.target_table ( - id int, - supported_column timestamp, - array_column_as_json jsonb, -- or text - ) - DISTRIBUTED BY id - """, -) - -write_df = df.select( - df.id, - df.supported_column, - JSON().serialize_column(df.array_column).alias("array_column_json"), -) - -writer = DBWriter( - connection=greenplum, - target="schema.target_table", -) -writer.run(write_df) -``` - -Then you can parse this column on Greenplum side: - -```sql -SELECT - id, - supported_column, - -- access first item of an array - array_column_as_json->0 -FROM - schema.target_table -``` diff --git a/mddocs/markdown/connection/db_connection/greenplum/write.md b/mddocs/markdown/connection/db_connection/greenplum/write.md deleted file mode 100644 index 0815e0760..000000000 --- a/mddocs/markdown/connection/db_connection/greenplum/write.md +++ /dev/null @@ -1,141 +0,0 @@ - - -# Writing to Greenplum using `DBWriter` - -For writing data to Greenplum, use [`DBWriter`](../../../db/db_writer.md#onetl.db.db_writer.db_writer.DBWriter) -with [`GreenplumWriteOptions`](#onetl.connection.db_connection.greenplum.options.GreenplumWriteOptions). - -#### WARNING -Please take into account [Greenplum <-> Spark type mapping](types.md#greenplum-types). - -#### WARNING -It is always recommended to create table explicitly using [Greenplum.execute](execute.md#greenplum-execute) -instead of relying on Spark’s table DDL generation. - -This is because Spark’s DDL generator can create columns with different types than it is expected. - -## Examples - -```python -from onetl.connection import Greenplum -from onetl.db import DBWriter - -greenplum = Greenplum(...) - -df = ... # data is here - -writer = DBWriter( - connection=greenplum, - target="schema.table", - options=Greenplum.WriteOptions( - if_exists="append", - # by default distribution is random - distributedBy="id", - # partitionBy is not supported - ), -) - -writer.run(df) -``` - -## Interaction schema - -High-level schema is described in [Prerequisites](prerequisites.md#greenplum-prerequisites). You can find detailed interaction schema below. - -### Spark <-> Greenplum interaction during DBWriter.run() - -## Options - -### *pydantic model* onetl.connection.db_connection.greenplum.options.GreenplumWriteOptions - -VMware’s Greenplum Spark connector writing options. - -#### WARNING -Some options, like `url`, `dbtable`, `server.*`, `pool.*`, etc -are populated from connection attributes, and cannot be overridden by the user in `WriteOptions` to avoid issues. - -### Examples - -#### NOTE -You can pass any value -[supported by connector](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/write_to_gpdb.html), -even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! - -The set of supported options depends on connector version. - -```python -from onetl.connection import Greenplum - -options = Greenplum.WriteOptions( - if_exists="append", - truncate="false", - distributedBy="mycolumn", -) -``` - - - -#### *field* if_exists *: GreenplumTableExistBehavior* *= GreenplumTableExistBehavior.APPEND* *(alias 'mode')* - -Behavior of writing data into existing table. - -Possible values: -: * `append` (default) - : Adds new rows into existing table. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`distributedBy` and others). - * Table exists - : Data is appended to a table. Table has the same DDL as before writing data. -
- #### WARNING - This mode does not check whether table already contains - rows from dataframe, so duplicated rows can be created. -
- Also Spark does not support passing custom options to - insert statement, like `ON CONFLICT`, so don’t try to - implement deduplication using unique indexes or constraints. -
- Instead, write to staging table and perform deduplication - using `execute` method. - * `replace_entire_table` - : **Table is dropped and then created**. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`distributedBy` and others). - * Table exists - : Table content is replaced with dataframe content. -
- After writing completed, target table could either have the same DDL as - before writing data (`truncate=True`), or can be recreated (`truncate=False`). - * `ignore` - : Ignores the write operation if the table already exists. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`distributedBy` and others). - * Table exists - : The write operation is ignored, and no data is written to the table. - * `error` - : Raises an error if the table already exists. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`distributedBy` and others). - * Table exists - : An error is raised, and no data is written to the table. - -#### Versionchanged -Changed in version 0.9.0: Renamed `mode` → `if_exists` - - diff --git a/mddocs/markdown/connection/db_connection/hive/connection.md b/mddocs/markdown/connection/db_connection/hive/connection.md deleted file mode 100644 index 7a8470de1..000000000 --- a/mddocs/markdown/connection/db_connection/hive/connection.md +++ /dev/null @@ -1,118 +0,0 @@ - - -# Hive Connection - -### *class* onetl.connection.db_connection.hive.connection.Hive(\*, spark: SparkSession, cluster: Cluster) - -Spark connection with Hive MetaStore support. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### SEE ALSO -Before using this connector please take into account [Prerequisites](prerequisites.md#hive-prerequisites) - -#### Versionadded -Added in version 0.1.0. - -* **Parameters:** - **cluster** - : Cluster name. Used for HWM and lineage. -
- #### Versionadded - Added in version 0.7.0. - - **spark** - : Spark session with Hive metastore support enabled - -### Examples - -Create Hive connection with Kerberos auth - -Execute `kinit` consome command before creating Spark Session - -```bash -$ kinit -kt /path/to/keytab user -``` - -```python -from onetl.connection import Hive -from pyspark.sql import SparkSession - -# Create Spark session -# Use names "spark.yarn.access.hadoopFileSystems", "spark.yarn.principal" -# and "spark.yarn.keytab" for Spark 2 - -spark = ( - SparkSession.builder.appName("spark-app-name") - .option("spark.kerberos.access.hadoopFileSystems", "hdfs://cluster.name.node:8020") - .option("spark.kerberos.principal", "user") - .option("spark.kerberos.keytab", "/path/to/keytab") - .enableHiveSupport() - .getOrCreate() -) - -# Create connection -hive = Hive(cluster="rnd-dwh", spark=spark).check() -``` - -Create Hive connection with anonymous auth - -```py -from onetl.connection import Hive -from pyspark.sql import SparkSession - -# Create Spark session -spark = SparkSession.builder.appName("spark-app-name").enableHiveSupport().getOrCreate() - -# Create connection -hive = Hive(cluster="rnd-dwh", spark=spark).check() -``` - - - -#### *classmethod* get_current(spark: SparkSession) - -Create connection for current cluster. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### NOTE -Can be used only if there are some hooks bound to -[`Slots.get_current_cluster`](slots.md#onetl.connection.db_connection.hive.slots.HiveSlots.get_current_cluster) slot. - -#### Versionadded -Added in version 0.7.0. - -* **Parameters:** - **spark** - : Spark session - -### Examples - -```python -from onetl.connection import Hive -from pyspark.sql import SparkSession - -spark = SparkSession.builder.appName("spark-app-name").enableHiveSupport().getOrCreate() - -# injecting current cluster name via hooks mechanism -hive = Hive.get_current(spark=spark) -``` - - - -#### check() - -Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If not, an exception will be raised. - -* **Returns:** - Connection itself -* **Raises:** - RuntimeError - : If the connection is not available - -### Examples - -```python -connection.check() -``` - - diff --git a/mddocs/markdown/connection/db_connection/hive/execute.md b/mddocs/markdown/connection/db_connection/hive/execute.md deleted file mode 100644 index c1d77874c..000000000 --- a/mddocs/markdown/connection/db_connection/hive/execute.md +++ /dev/null @@ -1,57 +0,0 @@ - - -# Executing statements in Hive - -Use `Hive.execute(...)` to execute DDL and DML operations. - -## Syntax support - -This method supports **any** query syntax supported by Hive, like: - -* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on -* ✅︎ `LOAD DATA ...`, and so on -* ✅︎ `ALTER ...` -* ✅︎ `INSERT INTO ... SELECT ...`, and so on -* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, and so on -* ✅︎ `MSCK REPAIR TABLE ...`, and so on -* ✅︎ other statements not mentioned here -* ❌ `SET ...; SELECT ...;` - multiple statements not supported - -#### WARNING -Actually, query should be written using [SparkSQL](https://spark.apache.org/docs/latest/sql-ref-syntax.html#ddl-statements) syntax, not HiveQL. - -## Examples - -```python -from onetl.connection import Hive - -hive = Hive(...) - -hive.execute("DROP TABLE schema.table") -hive.execute( - """ - CREATE TABLE schema.table ( - id NUMBER, - key VARCHAR, - value DOUBLE - ) - PARTITION BY (business_date DATE) - STORED AS orc - """ -) -``` - -### Details - -#### Hive.execute(statement: str) → None - -Execute DDL or DML statement. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.2.0. - -* **Parameters:** - **statement** - : Statement to be executed. - - diff --git a/mddocs/markdown/connection/db_connection/hive/index.md b/mddocs/markdown/connection/db_connection/hive/index.md deleted file mode 100644 index bc551b745..000000000 --- a/mddocs/markdown/connection/db_connection/hive/index.md +++ /dev/null @@ -1,19 +0,0 @@ - - -# Hive - -# Connection - -* [Prerequisites](prerequisites.md) -* [Hive Connection](connection.md) - -# Operations - -* [Reading from Hive using `DBReader`](read.md) -* [Reading from Hive using `Hive.sql`](sql.md) -* [Writing to Hive using `DBWriter`](write.md) -* [Executing statements in Hive](execute.md) - -# For developers - -* [Hive Slots](slots.md) diff --git a/mddocs/markdown/connection/db_connection/hive/prerequisites.md b/mddocs/markdown/connection/db_connection/hive/prerequisites.md deleted file mode 100644 index aa0fb49c7..000000000 --- a/mddocs/markdown/connection/db_connection/hive/prerequisites.md +++ /dev/null @@ -1,128 +0,0 @@ - - -# Prerequisites - -#### NOTE -onETL’s Hive connection is actually SparkSession with access to [Hive Thrift Metastore](https://docs.cloudera.com/cdw-runtime/1.5.0/hive-hms-overview/topics/hive-hms-introduction.html) -and HDFS/S3. -All data motion is made using Spark. Hive Metastore is used only to store tables and partitions metadata. - -This connector does **NOT** require Hive server. It also does **NOT** use Hive JDBC connector. - -## Version Compatibility - -* Hive Metastore version: - : * Officially declared: 0.12 - 3.1.3 (may require to add proper .jar file explicitly) - * Actually tested: 1.2.100, 2.3.10, 3.1.3 -* Spark versions: 2.3.x - 3.5.x -* Java versions: 8 - 20 - -See [official documentation](https://spark.apache.org/docs/latest/sql-data-sources-hive-tables.html). - -## Installing PySpark - -To use Hive connector you should have PySpark installed (or injected to `sys.path`) -BEFORE creating the connector instance. - -See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. - -## Connecting to Hive Metastore - -#### NOTE -If you’re using managed Hadoop cluster, skip this step, as all Spark configs are should already present on the host. - -Create `$SPARK_CONF_DIR/hive-site.xml` with Hive Metastore URL: - -```xml - - - - - hive.metastore.uris - thrift://metastore.host.name:9083 - - -``` - -Create `$SPARK_CONF_DIR/core-site.xml` with warehouse location ,e.g. HDFS IPC port of Hadoop namenode, or S3 bucket address & credentials: - -HDFS - -```xml - - - - - fs.defaultFS - hdfs://myhadoopcluster:9820 - - -``` - -S3 - -```xml - - - - !-- See https://hadoop.apache.org/docs/current/hadoop-aws/tools/hadoop-aws/index.html#General_S3A_Client_configuration - - fs.defaultFS - s3a://mys3bucket/ - - - fs.s3a.bucket.mybucket.endpoint - http://s3.somain - - - fs.s3a.bucket.mybucket.connection.ssl.enabled - false - - - fs.s3a.bucket.mybucket.path.style.access - true - - - fs.s3a.bucket.mybucket.aws.credentials.provider - org.apache.hadoop.fs.s3a.SimpleAWSCredentialsProvider - - - fs.s3a.bucket.mybucket.access.key - some-user - - - fs.s3a.bucket.mybucket.secret.key - mysecrettoken - - -``` - -## Using Kerberos - -Some of Hadoop managed clusters use Kerberos authentication. In this case, you should call [kinit](https://web.mit.edu/kerberos/krb5-1.12/doc/user/user_commands/kinit.html) command -**BEFORE** starting Spark session to generate Kerberos ticket. See [Kerberos support](../../../install/kerberos.md#install-kerberos). - -Sometimes it is also required to pass keytab file to Spark config, allowing Spark executors to generate own Kerberos tickets: - -Spark 3 - -```python -SparkSession.builder - .option("spark.kerberos.access.hadoopFileSystems", "hdfs://namenode1.domain.com:9820,hdfs://namenode2.domain.com:9820") - .option("spark.kerberos.principal", "user") - .option("spark.kerberos.keytab", "/path/to/keytab") - .gerOrCreate() -``` - -Spark 2 - -```python -SparkSession.builder - .option("spark.yarn.access.hadoopFileSystems", "hdfs://namenode1.domain.com:9820,hdfs://namenode2.domain.com:9820") - .option("spark.yarn.principal", "user") - .option("spark.yarn.keytab", "/path/to/keytab") - .gerOrCreate() -``` - -See [Spark security documentation](https://spark.apache.org/docs/latest/security.html#kerberos) -for more details. diff --git a/mddocs/markdown/connection/db_connection/hive/read.md b/mddocs/markdown/connection/db_connection/hive/read.md deleted file mode 100644 index cbe3469d9..000000000 --- a/mddocs/markdown/connection/db_connection/hive/read.md +++ /dev/null @@ -1,92 +0,0 @@ - - -# Reading from Hive using `DBReader` - -[`DBReader`](../../../db/db_reader.md#onetl.db.db_reader.db_reader.DBReader) supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, -but does not support custom queries, like `JOIN`. - -## Supported DBReader features - -* ✅︎ `columns` -* ✅︎ `where` -* ✅︎ `hwm`, supported strategies: -* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) -* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) -* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) -* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) -* ❌ `hint` (is not supported by Hive) -* ❌ `df_schema` -* ❌ `options` (only Spark config params are used) - -#### WARNING -Actually, `columns`, `where` and `hwm.expression` should be written using [SparkSQL](https://spark.apache.org/docs/latest/sql-ref-syntax.html#data-retrieval-statements) syntax, -not HiveQL. - -## Examples - -Snapshot strategy: - -```python -from onetl.connection import Hive -from onetl.db import DBReader - -hive = Hive(...) - -reader = DBReader( - connection=hive, - source="schema.table", - columns=["id", "key", "CAST(value AS text) value", "updated_dt"], - where="key = 'something'", -) -df = reader.run() -``` - -Incremental strategy: - -```python -from onetl.connection import Hive -from onetl.db import DBReader -from onetl.strategy import IncrementalStrategy - -hive = Hive(...) - -reader = DBReader( - connection=hive, - source="schema.table", - columns=["id", "key", "CAST(value AS text) value", "updated_dt"], - where="key = 'something'", - hwm=DBReader.AutoDetectHWM(name="hive_hwm", expression="updated_dt"), -) - -with IncrementalStrategy(): - df = reader.run() -``` - -## Recommendations - -### Use column-based write formats - -Prefer these write formats: -: * [ORC](https://spark.apache.org/docs/latest/sql-data-sources-orc.html) - * [Parquet](https://spark.apache.org/docs/latest/sql-data-sources-parquet.html) - * [Iceberg](https://iceberg.apache.org/spark-quickstart/) - * [Hudi](https://hudi.apache.org/docs/quick-start-guide/) - * [Delta](https://docs.delta.io/latest/quick-start.html#set-up-apache-spark-with-delta-lake) - -For colum-based write formats, each file contains separated sections there column data is stored. The file footer contains -location of each column section/group. Spark can use this information to load only sections required by specific query, e.g. only selected columns, -to drastically speed up the query. - -Another advantage is high compression ratio, e.g. 10x-100x in comparison to JSON or CSV. - -### Select only required columns - -Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. -This drastically reduces the amount of data read by Spark, **if column-based file formats are used**. - -### Use partition columns in `where` clause - -Queries should include `WHERE` clause with filters on Hive partitioning columns. -This allows Spark to read only small set of files (*partition pruning*) instead of scanning the entire table, so this drastically increases performance. - -Supported operators are: `=`, `>`, `<` and `BETWEEN`, and only against some **static** value. diff --git a/mddocs/markdown/connection/db_connection/hive/slots.md b/mddocs/markdown/connection/db_connection/hive/slots.md deleted file mode 100644 index 75bad02ff..000000000 --- a/mddocs/markdown/connection/db_connection/hive/slots.md +++ /dev/null @@ -1,105 +0,0 @@ - - -# Hive Slots - -### *class* onetl.connection.db_connection.hive.slots.HiveSlots - -[Slots](../../../hooks/slot.md#slot-decorator) that could be implemented by third-party plugins. - -#### Versionadded -Added in version 0.7.0. - - - -#### *static* normalize_cluster_name(cluster: str) → str | None - -Normalize cluster name passed into Hive constructor. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If hooks didn’t return anything, cluster name is left intact. - -#### Versionadded -Added in version 0.7.0. - -* **Parameters:** - **cluster** - : Cluster name (raw) -* **Returns:** - str | None - : Normalized cluster name. -
- If hook cannot be applied to a specific cluster, it should return `None`. - -### Examples - -```python -from onetl.connection import Hive -from onetl.hooks import hook - -@Hive.Slots.normalize_cluster_name.bind -@hook -def normalize_cluster_name(cluster: str) -> str: - return cluster.lower() -``` - - - -#### *static* get_known_clusters() → set[str] | None - -Return collection of known clusters. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Cluster passed into Hive constructor should be present in this list. -If hooks didn’t return anything, no validation will be performed. - -#### Versionadded -Added in version 0.7.0. - -* **Returns:** - set[str] | None - : Collection of cluster names (normalized). -
- If hook cannot be applied, it should return `None`. - -### Examples - -```python -from onetl.connection import Hive -from onetl.hooks import hook - -@Hive.Slots.get_known_clusters.bind -@hook -def get_known_clusters() -> str[str]: - return {"rnd-dwh", "rnd-prod"} -``` - - - -#### *static* get_current_cluster() → str | None - -Get current cluster name. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Used in `check` method to verify that connection is created only from the same cluster. -If hooks didn’t return anything, no validation will be performed. - -#### Versionadded -Added in version 0.7.0. - -* **Returns:** - str | None - : Current cluster name (normalized). -
- If hook cannot be applied, it should return `None`. - -### Examples - -```python -from onetl.connection import Hive -from onetl.hooks import hook - -@Hive.Slots.get_current_cluster.bind -@hook -def get_current_cluster() -> str: - # some magic here - return "rnd-dwh" -``` - - diff --git a/mddocs/markdown/connection/db_connection/hive/sql.md b/mddocs/markdown/connection/db_connection/hive/sql.md deleted file mode 100644 index 9e6d15f1c..000000000 --- a/mddocs/markdown/connection/db_connection/hive/sql.md +++ /dev/null @@ -1,86 +0,0 @@ - - -# Reading from Hive using `Hive.sql` - -`Hive.sql` allows passing custom SQL query, but does not support incremental strategies. - -## Syntax support - -Only queries with the following syntax are supported: - -* ✅︎ `SELECT ... FROM ...` -* ✅︎ `WITH alias AS (...) SELECT ...` -* ❌ `SET ...; SELECT ...;` - multiple statements not supported - -#### WARNING -Actually, query should be written using [SparkSQL](https://spark.apache.org/docs/latest/sql-ref-syntax.html#data-retrieval-statements) syntax, not HiveQL. - -## Examples - -```python -from onetl.connection import Hive - -hive = Hive(...) -df = hive.sql( - """ - SELECT - id, - key, - CAST(value AS text) value, - updated_at - FROM - some.mytable - WHERE - key = 'something' - """ -) -``` - -## Recommendations - -### Use column-based write formats - -Prefer these write formats: -: * [ORC](https://spark.apache.org/docs/latest/sql-data-sources-orc.html) - * [Parquet](https://spark.apache.org/docs/latest/sql-data-sources-parquet.html) - * [Iceberg](https://iceberg.apache.org/spark-quickstart/) - * [Hudi](https://hudi.apache.org/docs/quick-start-guide/) - * [Delta](https://docs.delta.io/latest/quick-start.html#set-up-apache-spark-with-delta-lake) - -For colum-based write formats, each file contains separated sections there column data is stored. The file footer contains -location of each column section/group. Spark can use this information to load only sections required by specific query, e.g. only selected columns, -to drastically speed up the query. - -Another advantage is high compression ratio, e.g. 10x-100x in comparison to JSON or CSV. - -### Select only required columns - -Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. -This drastically reduces the amount of data read by Spark, **if column-based file formats are used**. - -### Use partition columns in `where` clause - -Queries should include `WHERE` clause with filters on Hive partitioning columns. -This allows Spark to read only small set of files (*partition pruning*) instead of scanning the entire table, so this drastically increases performance. - -Supported operators are: `=`, `>`, `<` and `BETWEEN`, and only against some **static** value. - -## Details - -#### Hive.sql(query: str) → DataFrame - -Lazily execute SELECT statement and return DataFrame. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Same as `spark.sql(query)`. - -#### Versionadded -Added in version 0.2.0. - -* **Parameters:** - **query** - : SQL query to be executed. -* **Returns:** - **df** - : Spark dataframe - - diff --git a/mddocs/markdown/connection/db_connection/hive/write.md b/mddocs/markdown/connection/db_connection/hive/write.md deleted file mode 100644 index 1c61846df..000000000 --- a/mddocs/markdown/connection/db_connection/hive/write.md +++ /dev/null @@ -1,394 +0,0 @@ - - -# Writing to Hive using `DBWriter` - -For writing data to Hive, use [`DBWriter`](../../../db/db_writer.md#onetl.db.db_writer.db_writer.DBWriter). - -## Examples - -```python -from onetl.connection import Hive -from onetl.db import DBWriter - -hive = Hive(...) - -df = ... # data is here - -# Create dataframe with specific number of Spark partitions. -# Use the Hive partitioning columns to group the data. Create max 20 files per Hive partition. -# Also sort the data by column which most data is correlated with (e.g. user_id), reducing files size. - -num_files_per_partition = 20 -partition_columns = ["country", "business_date"] -sort_columns = ["user_id"] -write_df = df.repartition( - num_files_per_partition, - *partition_columns, - *sort_columns, -).sortWithinPartitions(*partition_columns, *sort_columns) - -writer = DBWriter( - connection=hive, - target="schema.table", - options=Hive.WriteOptions( - if_exists="append", - # Hive partitioning columns. - partitionBy=partition_columns, - ), -) - -writer.run(write_df) -``` - -## Recommendations - -### Use column-based write formats - -Prefer these write formats: -: * [ORC](https://spark.apache.org/docs/latest/sql-data-sources-orc.html) (**default**) - * [Parquet](https://spark.apache.org/docs/latest/sql-data-sources-parquet.html) - * [Iceberg](https://iceberg.apache.org/spark-quickstart/) - * [Hudi](https://hudi.apache.org/docs/quick-start-guide/) - * [Delta](https://docs.delta.io/latest/quick-start.html#set-up-apache-spark-with-delta-lake) - -#### WARNING -When using `DBWriter`, the default spark data format configured in `spark.sql.sources.default` is ignored, as `Hive.WriteOptions(format=...)` default value is explicitly set to `orc`. - -For column-based write formats, each file contains separated sections where column data is stored. The file footer contains -location of each column section/group. Spark can use this information to load only sections required by specific query, e.g. only selected columns, -to drastically speed up the query. - -Another advantage is high compression ratio, e.g. 10x-100x in comparison to JSON or CSV. - -### Use partitioning - -#### How does it work - -Hive support splitting data to partitions, which are different directories in filesystem with names like `some_col=value1/another_col=value2`. - -For example, dataframe with content like this: - -| country: string | business_date: date | user_id: int | bytes: long | -|-------------------|-----------------------|----------------|---------------| -| RU | 2024-01-01 | 1234 | 25325253525 | -| RU | 2024-01-01 | 2345 | 23234535243 | -| RU | 2024-01-02 | 1234 | 62346634564 | -| US | 2024-01-01 | 5678 | 4252345354 | -| US | 2024-01-02 | 5678 | 5474575745 | -| US | 2024-01-03 | 5678 | 3464574567 | - -With `partitionBy=["country", "business_dt"]` data will be stored as files in the following subfolders: -: * `/country=RU/business_date=2024-01-01/` - * `/country=RU/business_date=2024-01-02/` - * `/country=US/business_date=2024-01-01/` - * `/country=US/business_date=2024-01-02/` - * `/country=US/business_date=2024-01-03/` - -A separated subdirectory is created for each distinct combination of column values in the dataframe. - -Please do not confuse Spark dataframe partitions (a.k.a batches of data handled by Spark executors, usually in parallel) -and Hive partitioning (store data in different subdirectories). -Number of Spark dataframe partitions is correlated the number of files created in **each** Hive partition. -For example, Spark dataframe with 10 partitions and 5 distinct values of Hive partition columns will be saved as 5 subfolders with 10 files each = 50 files in total. -Without Hive partitioning, all the files are placed into one flat directory. - -#### But why? - -Queries which has `WHERE` clause with filters on Hive partitioning columns, like `WHERE country = 'RU' AND business_date='2024-01-01'`, will -read only files from this exact partitions, like `/country=RU/business_date=2024-01-01/`, and skip files from other partitions. - -This drastically increases performance and reduces the amount of memory used by Spark. -Consider using Hive partitioning in all tables. - -#### Which columns should I use? - -Usually Hive partitioning columns are based on event date or location, like `country: string`, `business_date: date`, `run_date: date` and so on. - -**Partition columns should contain data with low cardinality.** -Dates, small integers, strings with low number of possible values are OK. -But timestamp, float, decimals, longs (like user id), strings with lots oj unique values (like user name or email) should **NOT** be used as Hive partitioning columns. -Unlike some other databases, range and hash-based partitions are not supported. - -Partition column should be a part of a dataframe. If you want to partition values by date component of `business_dt: timestamp` column, -add a new column to dataframe like this: `df.withColumn("business_date", date(df.business_dt))`. - -### Use compression - -Using compression algorithms like `snappy`, `lz4` or `zstd` can reduce the size of files (up to 10x). - -### Prefer creating large files - -Storing millions of small files is not that HDFS and S3 are designed for. Minimal file size should be at least 10Mb, but usually it is like 128Mb+ or 256Mb+ (HDFS block size). -**NEVER** create files with few Kbytes in size. - -Number of files can be different in different cases. -On one hand, Spark Adaptive Query Execution (AQE) can merge small Spark dataframe partitions into one larger. -On the other hand, dataframes with skewed data can produce a larger number of files than expected. - -To create small amount of large files, you can reduce number of Spark dataframe partitions. -Use [df.repartition(N, columns…)](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.DataFrame.repartition.html) function, -like this: `df.repartition(20, "col1", "col2")`. -This creates new Spark dataframe with partitions using `hash(df.col1 + df.col2) mod 20` expression, avoiding data skew. - -Note: larger dataframe partitions requires more resources (CPU, RAM) on Spark executor. The exact number of partitions -should be determined empirically, as it depends on the amount of data and available resources. - -### Sort data before writing - -Dataframe with sorted content: - -| country: string | business_date: date | user_id: int | business_dt: timestamp | bytes: long | -|-------------------|-----------------------|----------------|--------------------------|---------------| -| RU | 2024-01-01 | 1234 | 2024-01-01T11:22:33.456 | 25325253525 | -| RU | 2024-01-01 | 1234 | 2024-01-01T12:23:44.567 | 25325253525 | -| RU | 2024-01-02 | 1234 | 2024-01-01T13:25:56.789 | 34335645635 | -| US | 2024-01-01 | 2345 | 2024-01-01T10:00:00.000 | 12341 | -| US | 2024-01-02 | 2345 | 2024-01-01T15:11:22.345 | 13435 | -| US | 2024-01-03 | 2345 | 2024-01-01T20:22:33.567 | 14564 | - -Has a much better compression rate than unsorted one, e.g. 2x or even higher: - -| country: string | business_date: date | user_id: int | business_dt: timestamp | bytes: long | -|-------------------|-----------------------|----------------|--------------------------|---------------| -| RU | 2024-01-01 | 1234 | 2024-01-01T11:22:33.456 | 25325253525 | -| RU | 2024-01-01 | 6345 | 2024-12-01T23:03:44.567 | 25365 | -| RU | 2024-01-02 | 5234 | 2024-07-01T06:10:56.789 | 45643456747 | -| US | 2024-01-01 | 4582 | 2024-04-01T17:59:00.000 | 362546475 | -| US | 2024-01-02 | 2345 | 2024-09-01T04:24:22.345 | 3235 | -| US | 2024-01-03 | 3575 | 2024-03-01T21:37:33.567 | 346345764 | - -Choosing columns to sort data by is really depends on the data. If data is correlated with some specific -column, like in example above the amount of traffic is correlated with both `user_id` and `timestamp`, -use `df.sortWithinPartitions("user_id", "timestamp")` before writing the data. - -If `df.repartition(N, repartition_columns...)` is used in combination with `df.sortWithinPartitions(sort_columns...)`, -then `sort_columns` should start with `repartition_columns` or be equal to it. - -## Options - -### *pydantic model* onetl.connection.db_connection.hive.options.HiveWriteOptions - -Hive source writing options. - -You can pass here key-value items which then will be converted to calls -of `pyspark.sql.readwriter.DataFrameWriter` methods. - -For example, `Hive.WriteOptions(if_exists="append", partitionBy="reg_id")` will -be converted to `df.write.mode("append").partitionBy("reg_id")` call, and so on. - -### Examples - -#### NOTE -You can pass any method name and its value -[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-load-save-functions.html), -even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! - -The set of supported options depends on Spark version used. - -```python -from onetl.connection import Hive - -options = Hive.WriteOptions( - if_exists="append", - partitionBy="reg_id", - customSparkOption="value", -) -``` - - - -#### *field* if_exists *: HiveTableExistBehavior* *= HiveTableExistBehavior.APPEND* *(alias 'mode')* - -Behavior of writing data into existing table. - -Possible values: -: * `append` (default) - : Appends data into existing partition/table, or create partition/table if it does not exist. -
- Same as Spark’s `df.write.insertInto(table, overwrite=False)`. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user (`format`, `compression`, etc). - * Table exists, but not partitioned, [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by) is set - : Data is appended to a table. Table is still not partitioned (DDL is unchanged). - * Table exists and partitioned, but has different partitioning schema than [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by) - : Partition is created based on table’s `PARTITIONED BY (...)` options. - Explicit [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by) value is ignored. - * Table exists and partitioned according [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by), but partition is present only in dataframe - : Partition is created. - * Table exists and partitioned according [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by), partition is present in both dataframe and table - : Data is appended to existing partition. -
- #### WARNING - This mode does not check whether table already contains - rows from dataframe, so duplicated rows can be created. -
- To implement deduplication, write data to staging table first, - and then perform some deduplication logic using `sql`. - * Table exists and partitioned according [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by), but partition is present only in table, not dataframe - : Existing partition is left intact. - * `replace_overlapping_partitions` - : Overwrites data in the existing partition, or create partition/table if it does not exist. -
- Same as Spark’s `df.write.insertInto(table, overwrite=True)` + - `spark.sql.sources.partitionOverwriteMode=dynamic`. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user (`format`, `compression`, etc). - * Table exists, but not partitioned, [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by) is set - : Data is **overwritten in all the table**. Table is still not partitioned (DDL is unchanged). - * Table exists and partitioned, but has different partitioning schema than [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by) - : Partition is created based on table’s `PARTITIONED BY (...)` options. - Explicit [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by) value is ignored. - * Table exists and partitioned according [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by), but partition is present only in dataframe - : Partition is created. - * Table exists and partitioned according [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by), partition is present in both dataframe and table - : Existing partition **replaced** with data from dataframe. - * Table exists and partitioned according [`partition_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.partition_by), but partition is present only in table, not dataframe - : Existing partition is left intact. - * `replace_entire_table` - : **Recreates table** (via `DROP + CREATE`), **deleting all existing data**. - **All existing partitions are dropped.** -
- Same as Spark’s `df.write.saveAsTable(table, mode="overwrite")` (NOT `insertInto`)! -
- #### WARNING - Table is recreated using options provided by user (`format`, `compression`, etc) - **instead of using original table options**. Be careful - * `ignore` - : Ignores the write operation if the table/partition already exists. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user (`format`, `compression`, etc). - * Table exists - : If the table exists, **no further action is taken**. This is true whether or not new partition - values are present and whether the partitioning scheme differs or not - * `error` - : Raises an error if the table/partition already exists. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user (`format`, `compression`, etc). - * Table exists - : If the table exists, **raises an error**. This is true whether or not new partition - values are present and whether the partitioning scheme differs or not - -#### NOTE -Unlike using pure Spark, config option `spark.sql.sources.partitionOverwriteMode` -does not affect behavior. - - - -#### *field* format *: str | [BaseWritableFileFormat](../../../file_df/file_formats/base.md#onetl.base.base_file_format.BaseWritableFileFormat)* *= 'orc'* - -Format of files which should be used for storing table data. - -### Examples - -- string format: `"orc"` (default), `"parquet"`, `"csv"` (NOT recommended). -- format class instance: `ORC(compression="snappy")`, `Parquet()`, `CSV(header=True, delimiter=",")`. - -```default -options = Hive.WriteOptions( - if_exists="append", - partitionBy="reg_id", - format="orc", -) - -# or using an ORC format class instance: - -from onetl.file.format import ORC - -options = Hive.WriteOptions( - if_exists="append", - partitionBy="reg_id", - format=ORC(compression="snappy"), -) -``` - -#### NOTE -It’s better to use column-based formats like `orc` or `parquet`, -not row-based (`csv`, `json`) - -#### WARNING -Used **only** while **creating new table**, or in case of `if_exists=replace_entire_table` - - - -#### *field* partition_by *: List[str] | str | None* *= None* *(alias 'partitionBy')* - -List of columns should be used for data partitioning. `None` means partitioning is disabled. - -Examples: `reg_id` or `["reg_id", "business_dt"]` - -#### WARNING -Used **only** while **creating new table**, or in case of `if_exists=replace_entire_table` - - - -#### *field* bucket_by *: Tuple[int, List[str] | str] | None* *= None* *(alias 'bucketBy')* - -Number of buckets plus bucketing columns. `None` means bucketing is disabled. - -Each bucket is created as a set of files with name containing result of calculation `hash(columns) mod num_buckets`. - -This allows to remove shuffle from queries containing `GROUP BY` or `JOIN` or using `=` / `IN` predicates -on specific columns. - -Examples: `(10, "user_id")`, `(10, ["user_id", "user_phone"])` - -#### NOTE -Bucketing should be used on columns containing a lot of unique values, -like `userId`. - -Columns like `date` should **NOT** be used for bucketing -because of too low number of unique values. - -#### WARNING -It is recommended to use this option **ONLY** if you have a large table -(hundreds of Gb or more), which is used mostly for JOINs with other tables, -and you’re inserting data using `if_exists=overwrite_partitions` or `if_exists=replace_entire_table`. - -Otherwise Spark will create a lot of small files -(one file for each bucket and each executor), drastically **decreasing** HDFS performance. - -#### WARNING -Used **only** while **creating new table**, or in case of `if_exists=replace_entire_table` - - - -#### *field* sort_by *: List[str] | str | None* *= None* *(alias 'sortBy')* - -Each file in a bucket will be sorted by these columns value. `None` means sorting is disabled. - -Examples: `user_id` or `["user_id", "user_phone"]` - -#### NOTE -Sorting columns should contain values which are used in `ORDER BY` clauses. - -#### WARNING -Could be used only with [`bucket_by`](#onetl.connection.db_connection.hive.options.HiveWriteOptions.bucket_by) option - -#### WARNING -Used **only** while **creating new table**, or in case of `if_exists=replace_entire_table` - - - -#### *field* compression *: str | None* *= None* - -Compressing algorithm which should be used for compressing created files in HDFS. -`None` means compression is disabled. - -Examples: `snappy`, `zlib` - -#### WARNING -Used **only** while **creating new table**, or in case of `if_exists=replace_entire_table` - - diff --git a/mddocs/markdown/connection/db_connection/index.md b/mddocs/markdown/connection/db_connection/index.md deleted file mode 100644 index cd8f76477..000000000 --- a/mddocs/markdown/connection/db_connection/index.md +++ /dev/null @@ -1,16 +0,0 @@ - - -# DB Connections - -# DB Connections - -* [Clickhouse](clickhouse/index.md) -* [Greenplum](greenplum/index.md) -* [Kafka](kafka/index.md) -* [Hive](hive/index.md) -* [MongoDB](mongodb/index.md) -* [MSSQL](mssql/index.md) -* [MySQL](mysql/index.md) -* [Oracle](oracle/index.md) -* [Postgres](postgres/index.md) -* [Teradata](teradata/index.md) diff --git a/mddocs/markdown/connection/db_connection/kafka/auth.md b/mddocs/markdown/connection/db_connection/kafka/auth.md deleted file mode 100644 index 0bf454521..000000000 --- a/mddocs/markdown/connection/db_connection/kafka/auth.md +++ /dev/null @@ -1,37 +0,0 @@ - - -# Kafka Auth - -### *class* onetl.connection.db_connection.kafka.kafka_auth.KafkaAuth - -Interface for Kafka connection Auth classes. - -#### Versionadded -Added in version 0.9.0. - - - -#### *abstract* get_options(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → dict - -Get options for Kafka connection - -* **Parameters:** - **kafka** - : Connection instance -* **Returns:** - dict: - : Kafka client options - - - -#### *abstract* cleanup(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → None - -This method is called while closing Kafka connection. - -Implement it to cleanup resources like temporary files. - -* **Parameters:** - **kafka** - : Connection instance - - diff --git a/mddocs/markdown/connection/db_connection/kafka/basic_auth.md b/mddocs/markdown/connection/db_connection/kafka/basic_auth.md deleted file mode 100644 index 586b07500..000000000 --- a/mddocs/markdown/connection/db_connection/kafka/basic_auth.md +++ /dev/null @@ -1,60 +0,0 @@ - - -# Kafka BasicAuth - -### *pydantic model* onetl.connection.db_connection.kafka.kafka_basic_auth.KafkaBasicAuth - -Connect to Kafka using `sasl.mechanism="PLAIN"`. - -For more details see [Kafka Documentation](https://kafka.apache.org/documentation/#security_sasl_plain). - -#### Versionadded -Added in version 0.9.0. - -### Examples - -Auth in Kafka with user and password: - -```python -from onetl.connection import Kafka - -auth = Kafka.BasicAuth( - user="some_user", - password="abc", -) -``` - - - -#### *field* user *: str* *[Required]* *(alias 'username')* - -#### *field* password *: SecretStr* *[Required]* - -#### get_jaas_conf() → str - - - -#### get_options(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → dict - -Get options for Kafka connection - -* **Parameters:** - **kafka** - : Connection instance -* **Returns:** - dict: - : Kafka client options - - - -#### cleanup(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → None - -This method is called while closing Kafka connection. - -Implement it to cleanup resources like temporary files. - -* **Parameters:** - **kafka** - : Connection instance - - diff --git a/mddocs/markdown/connection/db_connection/kafka/connection.md b/mddocs/markdown/connection/db_connection/kafka/connection.md deleted file mode 100644 index 23a54a42e..000000000 --- a/mddocs/markdown/connection/db_connection/kafka/connection.md +++ /dev/null @@ -1,243 +0,0 @@ - - -# Kafka Connection - -### *class* onetl.connection.db_connection.kafka.connection.Kafka(\*, spark: SparkSession, cluster: Cluster, addresses: List[str], auth: [KafkaAuth](auth.md#onetl.connection.db_connection.kafka.kafka_auth.KafkaAuth) | None = None, protocol: [KafkaProtocol](protocol.md#onetl.connection.db_connection.kafka.kafka_protocol.KafkaProtocol) = KafkaPlaintextProtocol(), extra: KafkaExtra = KafkaExtra()) - -This connector is designed to read and write from Kafka in batch mode. - -Based on [official Kafka Source For Spark](https://spark.apache.org/docs/latest/structured-streaming-kafka-integration.html). - -#### SEE ALSO -Before using this connector please take into account [Prerequisites](prerequisites.md#kafka-prerequisites) - -#### NOTE -This connector is for **batch** ETL processes, not streaming. - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **addresses** - : A list of broker addresses, for example `["192.168.1.10:9092", "192.168.1.11:9092"]`. - - **cluster** - : Cluster name. Used for HWM and lineage. - - **auth** - : Kafka authentication mechanism. `None` means anonymous auth. - - **protocol** - : Kafka security protocol. - - **extra** - : A dictionary of additional properties to be used when connecting to Kafka. -
- These are Kafka-specific properties that control behavior of the producer or consumer. See: - * [producer options documentation](https://kafka.apache.org/documentation/#producerconfigs) - * [consumer options documentation](https://kafka.apache.org/documentation/#consumerconfigs) - * [Spark Kafka documentation](https://spark.apache.org/docs/latest/structured-streaming-kafka-integration.html#kafka-specific-configurations) -
- Options are passed without `kafka.` prefix, for example: -
- For example: - ```python - extra = { - "group.id": "myGroup", - "request.timeout.ms": 120000, - } - ``` -
- #### WARNING - Options that populated from connection - attributes (like `bootstrap.servers`, `sasl.*`, `ssl.*`) are not allowed to override. - -### Examples - -Create Kafka connection with `PLAINTEXT` protocol and `SCRAM-SHA-256` auth - -```py -from onetl.connection import Kafka -from pyspark.sql import SparkSession - -# Create Spark session with Kafka connector loaded -maven_packages = Kafka.get_packages(spark_version="3.5.5") -exclude_packages = Kafka.get_exclude_packages() -spark = ( - SparkSession.builder.appName("spark-app-name") - .config("spark.jars.packages", ",".join(maven_packages)) - .config("spark.jars.excludes", ",".join(exclude_packages)) - .getOrCreate() -) - -# Create connection -kafka = Kafka( - addresses=["mybroker:9092", "anotherbroker:9092"], - cluster="my-cluster", - auth=Kafka.ScramAuth( - user="me", - password="abc", - digest="SHA-256", - ), - spark=spark, -).check() -``` - -Create Kafka connection with `PLAINTEXT` protocol and Kerberos (`GSSAPI`) auth - -```py -# Create Spark session with Kafka connector loaded -... - -# Create connection -kafka = Kafka( - addresses=["mybroker:9092", "anotherbroker:9092"], - cluster="my-cluster", - auth=Kafka.KerberosAuth( - principal="me@example.com", - keytab="/path/to/keytab", - deploy_keytab=True, - ), - spark=spark, -).check() -``` - -Create Kafka connection with `SASL_SSL` protocol and `SCRAM-SHA-512` auth - -```py -from pathlib import Path - -# Create Spark session with Kafka connector loaded -... - -# Create connection -kafka = Kafka( - addresses=["mybroker:9092", "anotherbroker:9092"], - cluster="my-cluster", - protocol=Kafka.SSLProtocol( - # read client certificate and private key from file - keystore_type="PEM", - keystore_certificate_chain=Path("path/to/user.crt").read_text(), - keystore_key=Path("path/to/user.key").read_text(), - # read server public certificate from file - truststore_type="PEM", - truststore_certificates=Path("/path/to/server.crt").read_text(), - ), - auth=Kafka.ScramAuth( - user="me", - password="abc", - digest="SHA-512", - ), - spark=spark, -).check() -``` - -Create Kafka connection with extra options - -```py -# Create Spark session with Kafka connector loaded -... - -# Create connection -kafka = Kafka( - addresses=["mybroker:9092", "anotherbroker:9092"], - cluster="my-cluster", - protocol=..., - auth=..., - extra={"max.request.size": 1024 * 1024}, # <-- - spark=spark, -).check() -``` - - - -#### check() - -Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If not, an exception will be raised. - -* **Returns:** - Connection itself -* **Raises:** - RuntimeError - : If the connection is not available - -### Examples - -```python -connection.check() -``` - - - -#### close() - -Close all connections created to Kafka. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### NOTE -Connection can be used again after it was closed. - -* **Returns:** - Connection itself - -### Examples - -Close connection automatically: - -```python -with connection: - ... -``` - -Close connection manually: - -```python -connection.close() -``` - - - -#### *classmethod* get_exclude_packages() → list[str] - -Get package names to be excluded by Spark. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.13.0. - -### Examples - -```python -from onetl.connection import Kafka - -Kafka.get_exclude_packages() -``` - - - -#### *classmethod* get_packages(spark_version: str, scala_version: str | None = None) → list[str] - -Get package names to be downloaded by Spark. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -See [Maven package index](https://mvnrepository.com/artifact/org.apache.spark/spark-sql-kafka-0-10) -for all available packages. - -* **Parameters:** - **spark_version** - : Spark version in format `major.minor.patch`. - - **scala_version** - : Scala version in format `major.minor`. -
- If `None`, `spark_version` is used to determine Scala version. - -### Examples - -```python -from onetl.connection import Kafka - -Kafka.get_packages(spark_version="3.5.5") -Kafka.get_packages(spark_version="3.5.5", scala_version="2.12") -``` - - diff --git a/mddocs/markdown/connection/db_connection/kafka/index.md b/mddocs/markdown/connection/db_connection/kafka/index.md deleted file mode 100644 index 5e5f45ba1..000000000 --- a/mddocs/markdown/connection/db_connection/kafka/index.md +++ /dev/null @@ -1,31 +0,0 @@ - - -# Kafka - -# Connection - -* [Prerequisites](prerequisites.md) -* [Kafka Connection](connection.md) -* [Kafka Troubleshooting](troubleshooting.md) - -# Protocols - -* [Kafka PlaintextProtocol](plaintext_protocol.md) -* [Kafka SSLProtocol](ssl_protocol.md) - -# Auth methods - -* [Kafka BasicAuth](basic_auth.md) -* [Kafka KerberosAuth](kerberos_auth.md) -* [Kafka ScramAuth](scram_auth.md) - -# Operations - -* [Reading from Kafka](read.md) -* [Writing to Kafka](write.md) - -# For developers - -* [Kafka Auth](auth.md) -* [Kafka Protocol](protocol.md) -* [Kafka Slots](slots.md) diff --git a/mddocs/markdown/connection/db_connection/kafka/kerberos_auth.md b/mddocs/markdown/connection/db_connection/kafka/kerberos_auth.md deleted file mode 100644 index 1fefd2992..000000000 --- a/mddocs/markdown/connection/db_connection/kafka/kerberos_auth.md +++ /dev/null @@ -1,118 +0,0 @@ - - -# Kafka KerberosAuth - -### *pydantic model* onetl.connection.db_connection.kafka.kafka_kerberos_auth.KafkaKerberosAuth - -Connect to Kafka using `sasl.mechanism="GSSAPI"`. - -For more details see: - -* [Kafka Documentation](https://kafka.apache.org/documentation/#security_sasl_kerberos_clientconfig) -* [Krb5LoginModule documentation](https://docs.oracle.com/javase/8/docs/jre/api/security/jaas/spec/com/sun/security/auth/module/Krb5LoginModule.html) - -#### Versionadded -Added in version 0.9.0. - -### Examples - -Auth in Kafka with keytab, automatically deploy keytab files to all Spark hosts (driver and executors): - -```python -from onetl.connection import Kafka - -auth = Kafka.KerberosAuth( - principal="user", - keytab="/path/to/keytab", - deploy_keytab=True, -) -``` - -Auth in Kafka with keytab, keytab is **already deployed** on all Spark hosts (driver and executors): - -```python -from onetl.connection import Kafka - -auth = Kafka.KerberosAuth( - principal="user", - keytab="/path/to/keytab", - deploy_keytab=False, -) -``` - -Auth in Kafka with existing Kerberos ticket (only Spark session created with `master=local`): - -```python -from onetl.connection import Kafka - -auth = Kafka.KerberosAuth( - principal="user", - use_keytab=False, - use_ticket_cache=True, -) -``` - -Pass custom options for JAAS config and Kafka SASL: - -```python -from onetl.connection import Kafka - -auth = Kafka.KerberosAuth.parse( - { - "principal": "user", - "keytab": "/path/to/keytab", - # options without sasl.kerberos. prefix are passed to JAAS config - # names are in camel case! - "isInitiator": True, - # options with `sasl.kerberos.` prefix are passed to Kafka client config as-is - "sasl.kerberos.kinit.cmd": "/usr/bin/kinit", - } -) -``` - - - -#### *field* principal *: str* *[Required]* - -#### *field* keytab *: LocalPath | None* *= None* *(alias 'keyTab')* - -#### *field* deploy_keytab *: bool* *= True* - -#### *field* service_name *: str* *= 'kafka'* *(alias 'serviceName')* - -#### *field* renew_ticket *: bool* *= True* *(alias 'renewTicket')* - -#### *field* store_key *: bool* *= True* *(alias 'storeKey')* - -#### *field* use_keytab *: bool* *= True* *(alias 'useKeyTab')* - -#### *field* use_ticket_cache *: bool* *= False* *(alias 'useTicketCache')* - -#### get_jaas_conf(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → str - - - -#### get_options(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → dict - -Get options for Kafka connection - -* **Parameters:** - **kafka** - : Connection instance -* **Returns:** - dict: - : Kafka client options - - - -#### cleanup(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → None - -This method is called while closing Kafka connection. - -Implement it to cleanup resources like temporary files. - -* **Parameters:** - **kafka** - : Connection instance - - diff --git a/mddocs/markdown/connection/db_connection/kafka/plaintext_protocol.md b/mddocs/markdown/connection/db_connection/kafka/plaintext_protocol.md deleted file mode 100644 index f7ebd316d..000000000 --- a/mddocs/markdown/connection/db_connection/kafka/plaintext_protocol.md +++ /dev/null @@ -1,48 +0,0 @@ - - -# Kafka PlaintextProtocol - -### *pydantic model* onetl.connection.db_connection.kafka.kafka_plaintext_protocol.KafkaPlaintextProtocol - -Connect to Kafka using `PLAINTEXT` or `SASL_PLAINTEXT` security protocols. - -#### WARNING -Not recommended to use on production environments. -Prefer [`SSLProtocol`](ssl_protocol.md#onetl.connection.db_connection.kafka.kafka_ssl_protocol.KafkaSSLProtocol). - -#### Versionadded -Added in version 0.9.0. - -### Examples - -```python -# No options -protocol = Kafka.PlaintextProtocol() -``` - - - -#### get_options(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → dict - -Get options for Kafka connection - -* **Parameters:** - **kafka** - : Connection instance -* **Returns:** - dict: - : Kafka client options - - - -#### cleanup(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → None - -This method is called while closing Kafka connection. - -Implement it to cleanup resources like temporary files. - -* **Parameters:** - **kafka** - : Connection instance - - diff --git a/mddocs/markdown/connection/db_connection/kafka/prerequisites.md b/mddocs/markdown/connection/db_connection/kafka/prerequisites.md deleted file mode 100644 index fcb90f705..000000000 --- a/mddocs/markdown/connection/db_connection/kafka/prerequisites.md +++ /dev/null @@ -1,65 +0,0 @@ - - -# Prerequisites - -## Version Compatibility - -* Kafka server versions: - : * Officially declared: 0.10 or higher - * Actually tested: 3.2.3, 3.9.0 (only Kafka 3.x supports message headers) -* Spark versions: 2.4.x - 3.5.x -* Java versions: 8 - 17 - -See [official documentation](https://spark.apache.org/docs/latest/structured-streaming-kafka-integration.html). - -## Installing PySpark - -To use Kafka connector you should have PySpark installed (or injected to `sys.path`) -BEFORE creating the connector instance. - -See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. - -## Connecting to Kafka - -### Connection address - -Kafka is a distributed service, and usually has a list of brokers you can connect to (unlike other connectors, there only one host+port can be set). -Please contact your Kafka administrator to get addresses of these brokers, as there are no defaults. - -Also Kafka has a feature called *advertised listeners* - client connects to one broker, and received list of other brokers in the clusters. -So you don’t have to pass all brokers to `addresses`, it can be some subset. Other broker addresses will be fetched directly from the cluster. - -### Connection protocol - -Kafka can support different connection protocols. List of currently supported protocols: -: * [`PLAINTEXT`](plaintext_protocol.md#onetl.connection.db_connection.kafka.kafka_plaintext_protocol.KafkaPlaintextProtocol) (not secure) - * [`SSL`](ssl_protocol.md#onetl.connection.db_connection.kafka.kafka_ssl_protocol.KafkaSSLProtocol) (secure, recommended) - -Note that specific port can listen for only one of these protocols, so it is important to set -proper port number + protocol combination. - -### Authentication mechanism - -Kafka can support different authentication mechanism (also known as [SASL](https://en.wikipedia.org/wiki/Simple_Authentication_and_Security_Layer)). - -List of currently supported mechanisms: -: * [`PLAIN`](basic_auth.md#onetl.connection.db_connection.kafka.kafka_basic_auth.KafkaBasicAuth). To no confuse this with `PLAINTEXT` connection protocol, onETL uses name `BasicAuth`. - * [`GSSAPI`](kerberos_auth.md#onetl.connection.db_connection.kafka.kafka_kerberos_auth.KafkaKerberosAuth). To simplify naming, onETL uses name `KerberosAuth`. - * [`SCRAM-SHA-256 or SCRAM-SHA-512`](scram_auth.md#onetl.connection.db_connection.kafka.kafka_scram_auth.KafkaScramAuth) (recommended). - -Different mechanisms use different types of credentials (login + password, keytab file, and so on). - -Note that connection protocol and auth mechanism are set in pairs: -: * If you see `SASL_PLAINTEXT` this means `PLAINTEXT` connection protocol + some auth mechanism. - * If you see `SASL_SSL` this means `SSL` connection protocol + some auth mechanism. - * If you see just `PLAINTEXT` or `SSL` (**no** `SASL`), this means that authentication is disabled (anonymous access). - -Please contact your Kafka administrator to get details about enabled auth mechanism in a specific Kafka instance. - -### Required grants - -Ask your Kafka administrator to set following grants for a user, *if Kafka instance uses ACL*: -: * `Describe` + `Read` for reading data from Kafka (Consumer). - * `Describe` + `Write` for writing data from Kafka (Producer). - -More details can be found in [documentation](https://kafka.apache.org/documentation/#operations_in_kafka). diff --git a/mddocs/markdown/connection/db_connection/kafka/protocol.md b/mddocs/markdown/connection/db_connection/kafka/protocol.md deleted file mode 100644 index 720e5f0b4..000000000 --- a/mddocs/markdown/connection/db_connection/kafka/protocol.md +++ /dev/null @@ -1,37 +0,0 @@ - - -# Kafka Protocol - -### *class* onetl.connection.db_connection.kafka.kafka_protocol.KafkaProtocol - -Interface for Kafka connection Protocol classes. - -#### Versionadded -Added in version 0.9.0. - - - -#### *abstract* get_options(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → dict - -Get options for Kafka connection - -* **Parameters:** - **kafka** - : Connection instance -* **Returns:** - dict: - : Kafka client options - - - -#### *abstract* cleanup(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → None - -This method is called while closing Kafka connection. - -Implement it to cleanup resources like temporary files. - -* **Parameters:** - **kafka** - : Connection instance - - diff --git a/mddocs/markdown/connection/db_connection/kafka/read.md b/mddocs/markdown/connection/db_connection/kafka/read.md deleted file mode 100644 index 52ec5ff33..000000000 --- a/mddocs/markdown/connection/db_connection/kafka/read.md +++ /dev/null @@ -1,172 +0,0 @@ - - -# Reading from Kafka - -Data can be read from Kafka to Spark using [`DBReader`](../../../db/db_reader.md#onetl.db.db_reader.db_reader.DBReader). -It also supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading. - -## Supported DBReader features - -* ❌ `columns` (is not supported by Kafka) -* ❌ `where` (is not supported by Kafka) -* ✅︎ `hwm`, supported strategies: -* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) -* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) -* * ❌ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) -* * ❌ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) -* ❌ `hint` (is not supported by Kafka) -* ❌ `df_schema` (see note below) -* ✅︎ `options` (see [`Kafka.ReadOptions`](#onetl.connection.db_connection.kafka.options.KafkaReadOptions)) - -## Dataframe schema - -Unlike other DB connections, Kafka does not have concept of columns. -All the topics messages have the same set of fields, see structure below: - -```text -root -|-- key: binary (nullable = true) -|-- value: binary (nullable = true) -|-- topic: string (nullable = false) -|-- partition: integer (nullable = false) -|-- offset: integer (nullable = false) -|-- timestamp: timestamp (nullable = false) -|-- timestampType: integer (nullable = false) -|-- headers: struct (nullable = true) - |-- key: string (nullable = false) - |-- value: binary (nullable = true) -``` - -`headers` field is present in the dataframe only if `Kafka.ReadOptions(include_headers=True)` is passed (compatibility with Kafka 1.x). - -## Value deserialization - -To read `value` or `key` of other type than bytes (e.g. struct or integer), users have to deserialize values manually. - -This could be done using following methods: -: * [`Avro.parse_column`](../../../file_df/file_formats/avro.md#onetl.file.format.avro.Avro.parse_column) - * [`JSON.parse_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.parse_column) - * [`CSV.parse_column`](../../../file_df/file_formats/csv.md#onetl.file.format.csv.CSV.parse_column) - * [`XML.parse_column`](../../../file_df/file_formats/xml.md#onetl.file.format.xml.XML.parse_column) - -## Examples - -Snapshot strategy, `value` is Avro binary data: - -```python -from onetl.connection import Kafka -from onetl.db import DBReader, DBWriter -from onetl.file.format import Avro -from pyspark.sql.functions import decode - -# read all topic data from Kafka -kafka = Kafka(...) -reader = DBReader(connection=kafka, source="avro_topic") -read_df = reader.run() - -# parse Avro format to Spark struct -avro = Avro( - schema_dict={ - "type": "record", - "name": "Person", - "fields": [ - {"name": "name", "type": "string"}, - {"name": "age", "type": "int"}, - ], - } -) -deserialized_df = read_df.select( - # cast binary key to string - decode("key", "UTF-8").alias("key"), - avro.parse_column("value"), -) -``` - -Incremental strategy, `value` is JSON string: - -#### NOTE -Currently Kafka connector does support only HWMs based on `offset` field. Other fields, like `timestamp`, are not yet supported. - -```python -from onetl.connection import Kafka -from onetl.db import DBReader, DBWriter -from onetl.file.format import JSON -from pyspark.sql.functions import decode - -kafka = Kafka(...) - -# read only new data from Kafka topic -reader = DBReader( - connection=kafka, - source="topic_name", - hwm=DBReader.AutoDetectHWM(name="kafka_hwm", expression="offset"), -) - -with IncrementalStrategy(): - read_df = reader.run() - -# parse JSON format to Spark struct -json = JSON() -schema = StructType( - [ - StructField("name", StringType(), nullable=True), - StructField("age", IntegerType(), nullable=True), - ], -) -deserialized_df = read_df.select( - # cast binary key to string - decode("key", "UTF-8").alias("key"), - json.parse_column("value", json), -) -``` - -## Options - -### *pydantic model* onetl.connection.db_connection.kafka.options.KafkaReadOptions - -Reading options for Kafka connector. - -#### WARNING -Options: -: * `assign` - * `endingOffsets` - * `endingOffsetsByTimestamp` - * `kafka.*` - * `startingOffsets` - * `startingOffsetsByTimestamp` - * `startingTimestamp` - * `subscribe` - * `subscribePattern` - -are populated from connection attributes, and cannot be overridden by the user in `ReadOptions` to avoid issues. - -#### Versionadded -Added in version 0.9.0. - -### Examples - -#### NOTE -You can pass any value -[supported by connector](https://spark.apache.org/docs/latest/structured-streaming-kafka-integration.html), -even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! - -The set of supported options depends on connector version. - -```python -from onetl.connection import Kafka - -options = Kafka.ReadOptions( - includeHeaders=False, - minPartitions=50, -) -``` - - - -#### *field* include_headers *: bool* *= False* *(alias 'includeHeaders')* - -If `True`, add `headers` column to output DataFrame. - -If `False`, column will not be added. - - diff --git a/mddocs/markdown/connection/db_connection/kafka/scram_auth.md b/mddocs/markdown/connection/db_connection/kafka/scram_auth.md deleted file mode 100644 index c0c974ad3..000000000 --- a/mddocs/markdown/connection/db_connection/kafka/scram_auth.md +++ /dev/null @@ -1,79 +0,0 @@ - - -# Kafka ScramAuth - -### *pydantic model* onetl.connection.db_connection.kafka.kafka_scram_auth.KafkaScramAuth - -Connect to Kafka using `sasl.mechanism="SCRAM-SHA-256"` or `sasl.mechanism="SCRAM-SHA-512"`. - -For more details see [Kafka Documentation](https://kafka.apache.org/documentation/#security_sasl_scram_clientconfig). - -#### Versionadded -Added in version 0.9.0. - -### Examples - -Auth in Kafka with `SCRAM-SHA-256` mechanism: - -```python -from onetl.connection import Kafka - -auth = Kafka.ScramAuth( - user="me", - password="abc", - digest="SHA-256", -) -``` - -Auth in Kafka with `SCRAM-SHA-512` mechanism and some custom SASL options passed to Kafka client config: - -```python -from onetl.connection import Kafka - -auth = Kafka.ScramAuth.parse( - { - "user": "me", - "password": "abc", - "digest": "SHA-512", - # options with `sasl.login.` prefix are passed to Kafka client config as-is - "sasl.login.class": "com.example.CustomScramLogin", - } -) -``` - - - -#### *field* user *: str* *[Required]* *(alias 'username')* - -#### *field* password *: SecretStr* *[Required]* - -#### *field* digest *: Literal['SHA-256', 'SHA-512']* *[Required]* - -#### get_jaas_conf() → str - - - -#### get_options(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → dict - -Get options for Kafka connection - -* **Parameters:** - **kafka** - : Connection instance -* **Returns:** - dict: - : Kafka client options - - - -#### cleanup(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → None - -This method is called while closing Kafka connection. - -Implement it to cleanup resources like temporary files. - -* **Parameters:** - **kafka** - : Connection instance - - diff --git a/mddocs/markdown/connection/db_connection/kafka/slots.md b/mddocs/markdown/connection/db_connection/kafka/slots.md deleted file mode 100644 index e4de1772d..000000000 --- a/mddocs/markdown/connection/db_connection/kafka/slots.md +++ /dev/null @@ -1,136 +0,0 @@ - - -# Kafka Slots - -### *class* onetl.connection.db_connection.kafka.slots.KafkaSlots - -Kafka slots that could be implemented by third-party plugins - -#### Versionadded -Added in version 0.9.0. - - - -#### *static* normalize_cluster_name(cluster: str) → str | None - -Normalize the given Kafka cluster name. - -This can be used to ensure that the Kafka cluster name conforms to specific naming conventions. - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **cluster** - : The original Kafka cluster name. -* **Returns:** - str | None - : The normalized Kafka cluster name. If the hook cannot be applied, return `None`. - -### Examples - -```python -from onetl.connection import Kafka -from onetl.hooks import hook - -@Kafka.Slots.normalize_cluster_name.bind -@hook -def normalize_cluster_name(cluster: str) -> str | None: - return cluster.lower() -``` - - - -#### *static* get_known_clusters() → set[str] | None - -Retrieve the collection of known Kafka clusters. - -This can be used to validate if the provided Kafka cluster name is recognized in the system. - -#### Versionadded -Added in version 0.9.0. - -* **Returns:** - set[str] | None - : A collection of known Kafka cluster names. If the hook cannot be applied, return `None`. - -### Examples - -```python -from onetl.connection import Kafka -from onetl.hooks import hook - -@Kafka.Slots.get_known_clusters.bind -@hook -def get_known_clusters() -> set[str] | None: - return {"kafka-cluster", "local"} -``` - - - -#### *static* normalize_address(address: str, cluster: str) → str | None - -Normalize the given broker address for a specific Kafka cluster. - -This can be used to format the broker address according to specific rules, such as adding default ports. - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **address** - : The original broker address. - - **cluster** - : The Kafka cluster name for which the address should be normalized. -* **Returns:** - str | None - : The normalized broker address. If the hook cannot be applied to the specific address, return `None`. - -### Examples - -```python -from onetl.connection import Kafka -from onetl.hooks import hook - -@Kafka.Slots.normalize_address.bind -@hook -def normalize_address(address: str, cluster: str) -> str | None: - if cluster == "kafka-cluster" and ":" not in address: - return f"{address}:9092" - return None -``` - - - -#### *static* get_cluster_addresses(cluster: str) → list[str] | None - -Retrieve a collection of known broker addresses for the specified Kafka cluster. - -This can be used to obtain the broker addresses dynamically. - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **cluster** - : The Kafka cluster name. -* **Returns:** - list[str] | None - : A collection of broker addresses for the specified Kafka cluster. If the hook cannot be applied, return `None`. - -### Examples - -```python -from onetl.connection import Kafka -from onetl.hooks import hook - -@Kafka.Slots.get_cluster_addresses.bind -@hook -def get_cluster_addresses(cluster: str) -> list[str] | None: - if cluster == "kafka_cluster": - return ["192.168.1.1:9092", "192.168.1.2:9092", "192.168.1.3:9092"] - return None -``` - - diff --git a/mddocs/markdown/connection/db_connection/kafka/ssl_protocol.md b/mddocs/markdown/connection/db_connection/kafka/ssl_protocol.md deleted file mode 100644 index ca2a59b45..000000000 --- a/mddocs/markdown/connection/db_connection/kafka/ssl_protocol.md +++ /dev/null @@ -1,149 +0,0 @@ - - -# Kafka SSLProtocol - -### *pydantic model* onetl.connection.db_connection.kafka.kafka_ssl_protocol.KafkaSSLProtocol - -Connect to Kafka using `SSL` or `SASL_SSL` security protocols. - -For more details see: - -* [Kafka Documentation](https://kafka.apache.org/documentation/#producerconfigs_ssl.keystore.location) -* [IBM Documentation](https://www.ibm.com/docs/en/cloud-paks/cp-biz-automation/19.0.x?topic=fcee-kafka-using-ssl-kerberos-authentication) -* [How to use PEM Certificates with Kafka](https://codingharbour.com/apache-kafka/using-pem-certificates-with-apache-kafka/) - -#### Versionadded -Added in version 0.9.0. - -### Examples - -Pass PEM key and certificates as files located on Spark driver host: - -```python -from pathlib import Path - -# Just read existing files located on host, and pass key and certificates as strings -protocol = Kafka.SSLProtocol( - keystore_type="PEM", - keystore_certificate_chain=Path("path/to/user.crt").read_text(), - keystore_key=Path("path/to/user.key").read_text(), - truststore_type="PEM", - truststore_certificates=Path("/path/to/server.crt").read_text(), -) -``` - -Pass PEM key and certificates as raw strings: - -```python -protocol = Kafka.SSLProtocol( - keystore_type="PEM", - keystore_certificate_chain="-----BEGIN CERTIFICATE-----\nMIIDZjC...\n-----END CERTIFICATE-----", - keystore_key="-----BEGIN PRIVATE KEY-----\nMIIEvg..\n-----END PRIVATE KEY-----", - truststore_type="PEM", - truststore_certificates="-----BEGIN CERTIFICATE-----\nMICC...\n-----END CERTIFICATE-----", -) -``` - -Pass custom options: - -```python -protocol = Kafka.SSLProtocol.parse( - { - # Just the same options as above, but using Kafka config naming with dots - "ssl.keystore.type": "PEM", - "ssl.keystore.certificate_chain": "-----BEGIN CERTIFICATE-----\nMIIDZjC...\n-----END CERTIFICATE-----", - "ssl.keystore.key": "-----BEGIN PRIVATE KEY-----\nMIIEvg..\n-----END PRIVATE KEY-----", - "ssl.truststore.type": "PEM", - "ssl.truststore.certificates": "-----BEGIN CERTIFICATE-----\nMICC...\n-----END CERTIFICATE-----", - # Any option starting from "ssl." is passed to Kafka client as-is - "ssl.protocol": "TLSv1.3", - } -) -``` - -### Not recommended - -These options are error-prone and have several drawbacks, so it is not recommended to use them. - -Passing PEM certificates as files: - -* ENCRYPT `user.key` file with password `"some password"` [using PKCS#8 scheme](https://www.mkssoftware.com/docs/man1/openssl_pkcs8.1.asp). -* Save encrypted key to file `/path/to/user/encrypted_key_with_certificate_chain.pem`. -* Then append user certificate to the end of this file. -* Deploy this file (and server certificate too) to **EVERY** host Spark could run (both driver and executors). -* Then pass file locations and password for key decryption to options below. - -```python -protocol = Kafka.SSLProtocol( - keystore_type="PEM", - keystore_location="/path/to/user/encrypted_key_with_certificate_chain.pem", - key_password="some password", - truststore_type="PEM", - truststore_location="/path/to/server.crt", -) -``` - -Passing JKS (Java Key Store) location: - -* [Add user key and certificate to JKS keystore](https://stackoverflow.com/a/4326346). -* [Add server certificate to JKS truststore](https://stackoverflow.com/a/373307). -* This should be done on **EVERY** host Spark could run (both driver and executors). -* Pass keystore and truststore paths to options below, as well as passwords for accessing these stores: - -```python -protocol = Kafka.SSLProtocol( - keystore_type="JKS", - keystore_location="/usr/lib/jvm/default/lib/security/keystore.jks", - keystore_password="changeit", - truststore_type="JKS", - truststore_location="/usr/lib/jvm/default/lib/security/truststore.jks", - truststore_password="changeit", -) -``` - - - -#### *field* keystore_type *: str* *[Required]* *(alias 'ssl.keystore.type')* - -#### *field* keystore_location *: LocalPath | None* *= None* *(alias 'ssl.keystore.location')* - -#### *field* keystore_password *: SecretStr | None* *= None* *(alias 'ssl.keystore.password')* - -#### *field* keystore_certificate_chain *: str | None* *= None* *(alias 'ssl.keystore.certificate.chain')* - -#### *field* keystore_key *: SecretStr | None* *= None* *(alias 'ssl.keystore.key')* - -#### *field* key_password *: SecretStr | None* *= None* *(alias 'ssl.key.password')* - -#### *field* truststore_type *: str* *[Required]* *(alias 'ssl.truststore.type')* - -#### *field* truststore_location *: LocalPath | None* *= None* *(alias 'ssl.truststore.location')* - -#### *field* truststore_password *: SecretStr | None* *= None* *(alias 'ssl.truststore.password')* - -#### *field* truststore_certificates *: str | None* *= None* *(alias 'ssl.truststore.certificates')* - -#### get_options(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → dict - -Get options for Kafka connection - -* **Parameters:** - **kafka** - : Connection instance -* **Returns:** - dict: - : Kafka client options - - - -#### cleanup(kafka: [Kafka](connection.md#onetl.connection.db_connection.kafka.connection.Kafka)) → None - -This method is called while closing Kafka connection. - -Implement it to cleanup resources like temporary files. - -* **Parameters:** - **kafka** - : Connection instance - - diff --git a/mddocs/markdown/connection/db_connection/kafka/troubleshooting.md b/mddocs/markdown/connection/db_connection/kafka/troubleshooting.md deleted file mode 100644 index cd15b5ba7..000000000 --- a/mddocs/markdown/connection/db_connection/kafka/troubleshooting.md +++ /dev/null @@ -1,10 +0,0 @@ - - -# Kafka Troubleshooting - -#### NOTE -General guide: [Troubleshooting](../../../troubleshooting/index.md#troubleshooting). - -## Cannot connect using `SSL` protocol - -Please check that certificate files are not Base-64 encoded. diff --git a/mddocs/markdown/connection/db_connection/kafka/write.md b/mddocs/markdown/connection/db_connection/kafka/write.md deleted file mode 100644 index c85f9c3cc..000000000 --- a/mddocs/markdown/connection/db_connection/kafka/write.md +++ /dev/null @@ -1,118 +0,0 @@ - - -# Writing to Kafka - -For writing data to Kafka, use [`DBWriter`](../../../db/db_writer.md#onetl.db.db_writer.db_writer.DBWriter) with specific options (see below). - -## Dataframe schema - -Unlike other DB connections, Kafka does not have concept of columns. -All the topics messages have the same set of fields. Only some of them can be written: - -```text -root -|-- key: binary (nullable = true) -|-- value: binary (nullable = true) -|-- headers: struct (nullable = true) - |-- key: string (nullable = false) - |-- value: binary (nullable = true) -``` - -`headers` can be passed only with `Kafka.WriteOptions(include_headers=True)` (compatibility with Kafka 1.x). - -Field `topic` should not be present in the dataframe, as it is passed to `DBWriter(target=...)`. - -Other fields, like `partition`, `offset`, `timestamp` are set by Kafka, and cannot be passed explicitly. - -## Value serialization - -To write `value` or `key` of other type than bytes (e.g. struct or integer), users have to serialize values manually. - -This could be done using following methods: -: * [`Avro.serialize_column`](../../../file_df/file_formats/avro.md#onetl.file.format.avro.Avro.serialize_column) - * [`JSON.serialize_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.serialize_column) - * [`CSV.serialize_column`](../../../file_df/file_formats/csv.md#onetl.file.format.csv.CSV.serialize_column) - -## Examples - -Convert `value` to JSON string, and write to Kafka: - -```python -from onetl.connection import Kafka -from onetl.db import DBWriter -from onetl.file.format import JSON - -df = ... # original data is here - -# serialize struct data as JSON -json = JSON() -write_df = df.select( - df.key, - json.serialize_column(df.value), -) - -# write data to Kafka -kafka = Kafka(...) - -writer = DBWriter( - connection=kafka, - target="topic_name", -) -writer.run(write_df) -``` - -## Options - -### *pydantic model* onetl.connection.db_connection.kafka.options.KafkaWriteOptions - -Writing options for Kafka connector. - -#### WARNING -Options: -: * `kafka.*` - * `topic` - -are populated from connection attributes, and cannot be overridden by the user in `WriteOptions` to avoid issues. - -#### Versionadded -Added in version 0.9.0. - -### Examples - -#### NOTE -You can pass any value -[supported by connector](https://spark.apache.org/docs/latest/structured-streaming-kafka-integration.html), -even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! - -The set of supported options depends on connector version. - -```python -from onetl.connection import Kafka - -options = Kafka.WriteOptions( - if_exists="append", - includeHeaders=True, -) -``` - - - -#### *field* if_exists *: KafkaTopicExistBehaviorKafka* *= KafkaTopicExistBehaviorKafka.APPEND* - -Behavior of writing data into existing topic. - -Same as `df.write.mode(...)`. - -Possible values: -: * `append` (default) - Adds new objects into existing topic. - * `error` - Raises an error if topic already exists. - - - -#### *field* include_headers *: bool* *= False* *(alias 'includeHeaders')* - -If `True`, `headers` column from dataframe can be written to Kafka (requires Kafka 2.0+). - -If `False` and dataframe contains `headers` column, an exception will be raised. - - diff --git a/mddocs/markdown/connection/db_connection/mongodb/connection.md b/mddocs/markdown/connection/db_connection/mongodb/connection.md deleted file mode 100644 index 8a01a11be..000000000 --- a/mddocs/markdown/connection/db_connection/mongodb/connection.md +++ /dev/null @@ -1,124 +0,0 @@ - - -# MongoDB Connection - -### *class* onetl.connection.db_connection.mongodb.connection.MongoDB(\*, spark: SparkSession, database: str, host: Host, user: str, password: SecretStr, port: int = 27017, extra: MongoDBExtra = MongoDBExtra()) - -MongoDB connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on package [org.mongodb.spark:mongo-spark-connector:10.4.1](https://mvnrepository.com/artifact/org.mongodb.spark/mongo-spark-connector_2.12/10.4.1) -([MongoDB connector for Spark](https://www.mongodb.com/docs/spark-connector/current/)) - -#### SEE ALSO -Before using this connector please take into account [Prerequisites](prerequisites.md#mongodb-prerequisites) - -#### Versionadded -Added in version 0.7.0. - -* **Parameters:** - **host** - : Host of MongoDB. For example: `test.mongodb.com` or `193.168.1.17`. - - **port** - : Port of MongoDB - - **user** - : User, which have proper access to the database. For example: `some_user`. - - **password** - : Password for database connection. - - **database** - : Database in MongoDB. - - **extra** - : Specifies one or more extra parameters by which clients can connect to the instance. -
- For example: `{"tls": "false"}` -
- See [Connection string options documentation](https://www.mongodb.com/docs/manual/reference/connection-string/#std-label-connections-connection-options) - for more details - - **spark** - : Spark session. - -### Examples - -```python -from onetl.connection import MongoDB -from pyspark.sql import SparkSession - -# Create Spark session with MongoDB connector loaded -maven_packages = MongoDB.get_packages(spark_version="3.4") -spark = ( - SparkSession.builder.appName("spark-app-name") - .config("spark.jars.packages", ",".join(maven_packages)) - .getOrCreate() -) - -# Create connection -mongo = MongoDB( - host="master.host.or.ip", - user="user", - password="*****", - database="target_database", - spark=spark, -).check() -``` - - - -#### *classmethod* get_packages(scala_version: str | None = None, spark_version: str | None = None, package_version: str | None = None) → list[str] - -Get package names to be downloaded by Spark. Allows specifying custom MongoDB Spark connector versions. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **scala_version** - : Scala version in format `major.minor`. -
- If `None`, `spark_version` is used to determine Scala version. - - **spark_version** - : Spark version in format `major.minor`. Used only if `scala_version=None`. - - **package_version** - : Specifies the version of the MongoDB Spark connector to use. Defaults to `10.4.1`. -
- #### Versionadded - Added in version 0.11.0. - -### Examples - -```python -from onetl.connection import MongoDB - -MongoDB.get_packages(scala_version="2.12") - -# specify custom connector version -MongoDB.get_packages(scala_version="2.12", package_version="10.4.1") -``` - - - -#### check() - -Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If not, an exception will be raised. - -* **Returns:** - Connection itself -* **Raises:** - RuntimeError - : If the connection is not available - -### Examples - -```python -connection.check() -``` - - diff --git a/mddocs/markdown/connection/db_connection/mongodb/index.md b/mddocs/markdown/connection/db_connection/mongodb/index.md deleted file mode 100644 index 45ab522a3..000000000 --- a/mddocs/markdown/connection/db_connection/mongodb/index.md +++ /dev/null @@ -1,18 +0,0 @@ - - -# MongoDB - -# Connection - -* [Prerequisites](prerequisites.md) -* [MongoDB Connection](connection.md) - -# Operations - -* [Reading from MongoDB using `DBReader`](read.md) -* [Reading from MongoDB using `MongoDB.pipeline`](pipeline.md) -* [Writing to MongoDB using `DBWriter`](write.md) - -# Troubleshooting - -* [MongoDB <-> Spark type mapping](types.md) diff --git a/mddocs/markdown/connection/db_connection/mongodb/pipeline.md b/mddocs/markdown/connection/db_connection/mongodb/pipeline.md deleted file mode 100644 index 2bae88c48..000000000 --- a/mddocs/markdown/connection/db_connection/mongodb/pipeline.md +++ /dev/null @@ -1,152 +0,0 @@ - - -# Reading from MongoDB using `MongoDB.pipeline` - -[`MongoDB.sql`](#onetl.connection.db_connection.mongodb.connection.MongoDB.pipeline) allows passing custom pipeline, -but does not support incremental strategies. - -#### WARNING -Please take into account [MongoDB <-> Spark type mapping](types.md#mongodb-types) - -## Recommendations - -### Pay attention to `pipeline` value - -Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `mongodb.pipeline(..., pipeline={"$match": {"column": {"$eq": "value"}}})` value. -This both reduces the amount of data send from MongoDB to Spark, and may also improve performance of the query. -Especially if there are indexes for columns used in `pipeline` value. - -## References - -#### MongoDB.pipeline(collection: str, pipeline: dict | list[dict] | None = None, df_schema: StructType | None = None, options: [MongoDBPipelineOptions](#onetl.connection.db_connection.mongodb.options.MongoDBPipelineOptions) | dict | None = None) - -Execute a pipeline for a specific collection, and return DataFrame. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Almost like [Aggregation pipeline syntax](https://www.mongodb.com/docs/manual/core/aggregation-pipeline/) -in MongoDB: - -```js -db.collection_name.aggregate([{"$match": ...}, {"$group": ...}]) -``` - -but pipeline is executed on Spark executors, in a distributed way. - -#### NOTE -This method does not support [Read Strategies](../../../strategy/index.md#strategy), -use [`DBReader`](../../../db/db_reader.md#onetl.db.db_reader.db_reader.DBReader) instead - -#### Versionadded -Added in version 0.7.0. - -* **Parameters:** - **collection** - : Collection name. - - **pipeline** - : Pipeline containing a database query. - See [Aggregation pipeline syntax](https://www.mongodb.com/docs/manual/core/aggregation-pipeline/). - - **df_schema** - : Schema describing the resulting DataFrame. - - **options** - : Additional pipeline options, see [`MongoDB.PipelineOptions`](#onetl.connection.db_connection.mongodb.options.MongoDBPipelineOptions). - -### Examples - -Get document with a specific `field` value: - -```python -df = connection.pipeline( - collection="collection_name", - pipeline={"$match": {"field": {"$eq": 1}}}, -) -``` - -Calculate aggregation and get result: - -```python -df = connection.pipeline( - collection="collection_name", - pipeline={ - "$group": { - "_id": 1, - "min": {"$min": "$column_int"}, - "max": {"$max": "$column_int"}, - } - }, -) -``` - -Explicitly pass DataFrame schema: - -```python -from pyspark.sql.types import ( - DoubleType, - IntegerType, - StringType, - StructField, - StructType, - TimestampType, -) - -df_schema = StructType( - [ - StructField("_id", StringType()), - StructField("some_string", StringType()), - StructField("some_int", IntegerType()), - StructField("some_datetime", TimestampType()), - StructField("some_float", DoubleType()), - ], -) - -df = connection.pipeline( - collection="collection_name", - df_schema=df_schema, - pipeline={"$match": {"some_int": {"$gt": 999}}}, -) -``` - -Pass additional options to pipeline execution: - -```python -df = connection.pipeline( - collection="collection_name", - pipeline={"$match": {"field": {"$eq": 1}}}, - options=MongoDB.PipelineOptions(hint={"field": 1}), -) -``` - - - -### *pydantic model* onetl.connection.db_connection.mongodb.options.MongoDBPipelineOptions - -Aggregation pipeline options for MongoDB connector. - -The only difference from [`MongoDB.ReadOptions`](read.md#onetl.connection.db_connection.mongodb.options.MongoDBReadOptions) that latter does not allow to pass the `hint` parameter. - -#### WARNING -Options `uri`, `database`, `collection`, `pipeline` are populated from connection attributes, -and cannot be overridden by the user in `PipelineOptions` to avoid issues. - -#### Versionadded -Added in version 0.7.0. - -### Examples - -#### NOTE -You can pass any value -[supported by connector](https://www.mongodb.com/docs/spark-connector/current/batch-mode/batch-read-config/), -even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! - -The set of supported options depends on connector version. - -```python -from onetl.connection import MongoDB - -options = MongoDB.PipelineOptions( - hint={"some_field": 1}, -) -``` - - diff --git a/mddocs/markdown/connection/db_connection/mongodb/prerequisites.md b/mddocs/markdown/connection/db_connection/mongodb/prerequisites.md deleted file mode 100644 index 4572fb4f6..000000000 --- a/mddocs/markdown/connection/db_connection/mongodb/prerequisites.md +++ /dev/null @@ -1,72 +0,0 @@ - - -# Prerequisites - -## Version Compatibility - -* MongoDB server versions: - : * Officially declared: 4.0 or higher - * Actually tested: 4.0.0, 8.0.4 -* Spark versions: 3.2.x - 3.5.x -* Java versions: 8 - 20 - -See [official documentation](https://www.mongodb.com/docs/spark-connector/). - -## Installing PySpark - -To use MongoDB connector you should have PySpark installed (or injected to `sys.path`) -BEFORE creating the connector instance. - -See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. - -## Connecting to MongoDB - -### Connection host - -It is possible to connect to MongoDB host by using either DNS name of host or it’s IP address. - -It is also possible to connect to MongoDB shared cluster: - -```python -mongo = MongoDB( - host="master.host.or.ip", - user="user", - password="*****", - database="target_database", - spark=spark, - extra={ - # read data from secondary cluster node, switch to primary if not available - "readPreference": "secondaryPreferred", - }, -) -``` - -Supported `readPreference` values are described in [official documentation](https://www.mongodb.com/docs/manual/core/read-preference/). - -### Connection port - -Connection is usually performed to port `27017`. Port may differ for different MongoDB instances. -Please ask your MongoDB administrator to provide required information. - -### Required grants - -Ask your MongoDB cluster administrator to set following grants for a user, -used for creating a connection: - -Read + Write - -```js -// allow writing data to specific database -db.grantRolesToUser("username", [{db: "somedb", role: "readWrite"}]) -``` - -Read only - -```js -// allow reading data from specific database -db.grantRolesToUser("username", [{db: "somedb", role: "read"}]) -``` - -See: -: * [db.grantRolesToUser documentation](https://www.mongodb.com/docs/manual/reference/method/db.grantRolesToUser) - * [MongoDB builtin roles](https://www.mongodb.com/docs/manual/reference/built-in-roles) diff --git a/mddocs/markdown/connection/db_connection/mongodb/read.md b/mddocs/markdown/connection/db_connection/mongodb/read.md deleted file mode 100644 index 2f2ffb73c..000000000 --- a/mddocs/markdown/connection/db_connection/mongodb/read.md +++ /dev/null @@ -1,157 +0,0 @@ - - -# Reading from MongoDB using `DBReader` - -[`DBReader`](../../../db/db_reader.md#onetl.db.db_reader.db_reader.DBReader) supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, -but does not support custom pipelines, e.g. aggregation. - -#### WARNING -Please take into account [MongoDB <-> Spark type mapping](types.md#mongodb-types) - -## Supported DBReader features - -* ❌ `columns` (for now, all document fields are read) -* ✅︎ `where` (passed to `{"$match": ...}` aggregation pipeline) -* ✅︎ `hwm`, supported strategies: -* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) -* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) -* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) -* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) -* * Note that `expression` field of HWM can only be a field name, not a custom expression -* ✅︎ `hint` (see [official documentation](https://www.mongodb.com/docs/v5.0/reference/operator/meta/hint/)) -* ✅︎ `df_schema` (mandatory) -* ✅︎ `options` (see [`MongoDB.ReadOptions`](#onetl.connection.db_connection.mongodb.options.MongoDBReadOptions)) - -## Examples - -Snapshot strategy: - -```python -from onetl.connection import MongoDB -from onetl.db import DBReader - -from pyspark.sql.types import ( - StructType, - StructField, - IntegerType, - StringType, - TimestampType, -) - -mongodb = MongoDB(...) - -# mandatory -df_schema = StructType( - [ - StructField("_id", StringType()), - StructField("some", StringType()), - StructField( - "field", - StructType( - [ - StructField("nested", IntegerType()), - ], - ), - ), - StructField("updated_dt", TimestampType()), - ] -) - -reader = DBReader( - connection=mongodb, - source="some_collection", - df_schema=df_schema, - where={"field": {"$eq": 123}}, - hint={"field": 1}, - options=MongoDBReadOptions(batchSize=10000), -) -df = reader.run() -``` - -Incremental strategy: - -```python -from onetl.connection import MongoDB -from onetl.db import DBReader -from onetl.strategy import IncrementalStrategy - -from pyspark.sql.types import ( - StructType, - StructField, - IntegerType, - StringType, - TimestampType, -) - -mongodb = MongoDB(...) - -# mandatory -df_schema = StructType( - [ - StructField("_id", StringType()), - StructField("some", StringType()), - StructField( - "field", - StructType( - [ - StructField("nested", IntegerType()), - ], - ), - ), - StructField("updated_dt", TimestampType()), - ] -) - -reader = DBReader( - connection=mongodb, - source="some_collection", - df_schema=df_schema, - where={"field": {"$eq": 123}}, - hint={"field": 1}, - hwm=DBReader.AutoDetectHWM(name="mongodb_hwm", expression="updated_dt"), - options=MongoDBReadOptions(batchSize=10000), -) - -with IncrementalStrategy(): - df = reader.run() -``` - -## Recommendations - -### Pay attention to `where` value - -Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where={"column": {"$eq": "value"}})` clause. -This both reduces the amount of data send from MongoDB to Spark, and may also improve performance of the query. -Especially if there are indexes for columns used in `where` clause. - -## Read options - -### *pydantic model* onetl.connection.db_connection.mongodb.options.MongoDBReadOptions - -Reading options for MongoDB connector. - -#### WARNING -Options `uri`, `database`, `collection`, `pipeline`, `hint` are populated from connection -attributes, and cannot be overridden by the user in `ReadOptions` to avoid issues. - -#### Versionadded -Added in version 0.7.0. - -### Examples - -#### NOTE -You can pass any value -[supported by connector](https://www.mongodb.com/docs/spark-connector/current/batch-mode/batch-read-config/), -even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! - -The set of supported options depends on connector version. - -```python -from onetl.connection import MongoDB - -options = MongoDB.ReadOptions( - sampleSize=100, -) -``` - - diff --git a/mddocs/markdown/connection/db_connection/mongodb/types.md b/mddocs/markdown/connection/db_connection/mongodb/types.md deleted file mode 100644 index 2d75b5e52..000000000 --- a/mddocs/markdown/connection/db_connection/mongodb/types.md +++ /dev/null @@ -1,214 +0,0 @@ - - -# MongoDB <-> Spark type mapping - -#### NOTE -The results below are valid for Spark 3.5.5, and may differ on other Spark versions. - -## Type detection & casting - -Spark’s DataFrames always have a `schema` which is a list of fields with corresponding Spark types. All operations on a field are performed using field type. - -MongoDB is, by design, \_\_schemaless_\_. So there are 2 ways how this can be handled: - -* User provides DataFrame schema explicitly: - - ### See example - - ```python - from onetl.connection import MongoDB - from onetl.db import DBReader - - from pyspark.sql.types import ( - StructType, - StructField, - IntegerType, - StringType, - TimestampType, - ) - - mongodb = MongoDB(...) - - df_schema = StructType( - [ - StructField("_id", StringType()), - StructField("some", StringType()), - StructField( - "field", - StructType( - [ - StructField("nested", IntegerType()), - ] - ), - ), - ] - ) - - reader = DBReader( - connection=mongodb, - source="some_collection", - df_schema=df_schema, - ) - df = reader.run() - - # or - - df = mongodb.pipeline( - collection="some_collection", - df_schema=df_schema, - ) - ``` -* Rely on MongoDB connector schema infer: - ```python - df = mongodb.pipeline(collection="some_collection") - ``` - - In this case MongoDB connector read a sample of collection documents, and build DataFrame schema based on document fields and values. - -It is highly recommended to pass `df_schema` explicitly, to avoid type conversion issues. - -### References - -Here you can find source code with type conversions: - -* [MongoDB -> Spark](https://github.com/mongodb/mongo-spark/blob/r10.4.1/src/main/java/com/mongodb/spark/sql/connector/schema/InferSchema.java#L214-L260) -* [Spark -> MongoDB](https://github.com/mongodb/mongo-spark/blob/r10.4.1/src/main/java/com/mongodb/spark/sql/connector/schema/RowToBsonDocumentConverter.java#L157-L260) - -## Supported types - -See [official documentation](https://www.mongodb.com/docs/manual/reference/bson-types/) - -### Numeric types - -| MongoDB type (read) | Spark type | MongoDB type (write) | -|-----------------------|---------------------------|------------------------| -| `Decimal128` | `DecimalType(P=34, S=32)` | `Decimal128` | -| `-` | `FloatType()` | `Double` | -| `Double` | `DoubleType()` | | -| `-` | `ByteType()` | `Int32` | -| `-` | `ShortType()` | | -| `Int32` | `IntegerType()` | | -| `Int64` | `LongType()` | `Int64` | - -### Temporal types - -| MongoDB type (read) | Spark type | MongoDB type (write) | -|-----------------------|---------------------------------|-------------------------------------------------------------------| -| `-` | `DateType()`, days | `Date`, milliseconds | -| `Date`, milliseconds | `TimestampType()`, microseconds | `Date`, milliseconds,
**precision loss** [2](#id2) | -| `Timestamp`, seconds | `TimestampType()`, microseconds | `Date`, milliseconds | -| `-` | `TimestampNTZType()` | unsupported | -| `-` | `DayTimeIntervalType()` | | - -#### WARNING -Note that types in MongoDB and Spark have different value ranges: - -| MongoDB type | Min value | Max value | Spark type | Min value | Max value | -|----------------|-----------------------|-----------------------|-------------------|------------------------------|------------------------------| -| `Date` | -290 million years | 290 million years | `TimestampType()` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | -| `Timestamp` | `1970-01-01 00:00:00` | `2106-02-07 09:28:16` | | | | - -So not all values can be read from MongoDB to Spark, and can written from Spark DataFrame to MongoDB. - -References: -: * [MongoDB Date type documentation](https://www.mongodb.com/docs/manual/reference/bson-types/#date) - * [MongoDB Timestamp documentation](https://www.mongodb.com/docs/manual/reference/bson-types/#timestamps) - * [Spark DateType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/DateType.html) - * [Spark TimestampType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/TimestampType.html) - -* **[2]** MongoDB `Date` type has precision up to milliseconds (`23:59:59.999`). Inserting data with microsecond precision (`23:59:59.999999`) will lead to **throwing away microseconds**. - -### String types - -Note: fields of deprecated MongoDB type `Symbol` are excluded during read. - -| MongoDB type (read) | Spark type | MongoDB type (write) | -|-----------------------|----------------|------------------------| -| `String` | `StringType()` | `String` | -| `Code` | | | -| `RegExp` | | | - -### Binary types - -| MongoDB type (read) | Spark type | MongoDB type (write) | -|-----------------------|-----------------|------------------------| -| `Boolean` | `BooleanType()` | `Boolean` | -| `Binary` | `BinaryType()` | `Binary` | - -### Struct types - -| MongoDB type (read) | Spark type | MongoDB type (write) | -|-----------------------|---------------------|------------------------| -| `Array[T]` | `ArrayType(T)` | `Array[T]` | -| `Object[...]` | `StructType([...])` | `Object[...]` | -| `-` | `MapType(...)` | | - -### Special types - -| MongoDB type (read) | Spark type | MongoDB type (write) | -|-----------------------|-------------------------------------------------------|-------------------------------------| -| `ObjectId` | `StringType()` | `String` | -| `MaxKey` | | | -| `MinKey` | | | -| `Null` | `NullType()` | `Null` | -| `Undefined` | | | -| `DBRef` | `StructType([$ref: StringType(), $id: StringType()])` | `Object[$ref: String, $id: String]` | - -## Explicit type cast - -### `DBReader` - -Currently it is not possible to cast field types using `DBReader`. But this can be done using `MongoDB.pipeline`. - -### `MongoDB.pipeline` - -You can use `$project` aggregation to cast field types: - -```python -from pyspark.sql.types import IntegerType, StructField, StructType - -from onetl.connection import MongoDB -from onetl.db import DBReader - -mongodb = MongoDB(...) - -df = mongodb.pipeline( - collection="my_collection", - pipeline=[ - { - "$project": { - # convert unsupported_field to string - "unsupported_field_str": { - "$convert": { - "input": "$unsupported_field", - "to": "string", - }, - }, - # skip unsupported_field from result - "unsupported_field": 0, - } - } - ], -) - -# cast field content to proper Spark type -df = df.select( - df.id, - df.supported_field, - # explicit cast - df.unsupported_field_str.cast("integer").alias("parsed_integer"), -) -``` - -### `DBWriter` - -Convert dataframe field to string on Spark side, and then write it to MongoDB: - -```python -df = df.select( - df.id, - df.unsupported_field.cast("string").alias("array_field_json"), -) - -writer.run(df) -``` diff --git a/mddocs/markdown/connection/db_connection/mongodb/write.md b/mddocs/markdown/connection/db_connection/mongodb/write.md deleted file mode 100644 index a0e3778c1..000000000 --- a/mddocs/markdown/connection/db_connection/mongodb/write.md +++ /dev/null @@ -1,118 +0,0 @@ - - -# Writing to MongoDB using `DBWriter` - -For writing data to MongoDB, use [`DBWriter`](../../../db/db_writer.md#onetl.db.db_writer.db_writer.DBWriter). - -#### WARNING -Please take into account [MongoDB <-> Spark type mapping](types.md#mongodb-types) - -## Examples - -```python -from onetl.connection import MongoDB -from onetl.db import DBWriter - -mongodb = MongoDB(...) - -df = ... # data is here - -writer = DBWriter( - connection=mongodb, - target="schema.table", - options=MongoDB.WriteOptions( - if_exists="append", - ), -) - -writer.run(df) -``` - -## Write options - -Method above accepts [`MongoDB.WriteOptions`](#onetl.connection.db_connection.mongodb.options.MongoDBWriteOptions) - -### *pydantic model* onetl.connection.db_connection.mongodb.options.MongoDBWriteOptions - -Writing options for MongoDB connector. - -#### WARNING -Options `uri`, `database`, `collection` are populated from connection attributes, -and cannot be overridden by the user in `WriteOptions` to avoid issues. - -#### Versionadded -Added in version 0.7.0. - -### Examples - -#### NOTE -You can pass any value -[supported by connector](https://www.mongodb.com/docs/spark-connector/current/batch-mode/batch-write-config/), -even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! - -The set of supported options depends on connector version. - -```python -from onetl.connection import MongoDB - -options = MongoDB.WriteOptions( - if_exists="append", - sampleSize=500, - localThreshold=20, -) -``` - - - -#### *field* if_exists *: MongoDBCollectionExistBehavior* *= MongoDBCollectionExistBehavior.APPEND* *(alias 'mode')* - -Behavior of writing data into existing collection. - -Possible values: -: * `append` (default) - : Adds new objects into existing collection. -
- ### Behavior in details -
- * Collection does not exist - : Collection is created using options provided by user - (`shardkey` and others). - * Collection exists - : Data is appended to a collection. -
- #### WARNING - This mode does not check whether collection already contains - objects from dataframe, so duplicated objects can be created. - * `replace_entire_collection` - : **Collection is deleted and then created**. -
- ### Behavior in details -
- * Collection does not exist - : Collection is created using options provided by user - (`shardkey` and others). - * Collection exists - : Collection content is replaced with dataframe content. - * `ignore` - : Ignores the write operation if the collection already exists. -
- ### Behavior in details -
- * Collection does not exist - : Collection is created using options provided by user - * Collection exists - : The write operation is ignored, and no data is written to the collection. - * `error` - : Raises an error if the collection already exists. -
- ### Behavior in details -
- * Collection does not exist - : Collection is created using options provided by user - * Collection exists - : An error is raised, and no data is written to the collection. - -#### Versionchanged -Changed in version 0.9.0: Renamed `mode` → `if_exists` - - diff --git a/mddocs/markdown/connection/db_connection/mssql/connection.md b/mddocs/markdown/connection/db_connection/mssql/connection.md deleted file mode 100644 index 3e35a34fb..000000000 --- a/mddocs/markdown/connection/db_connection/mssql/connection.md +++ /dev/null @@ -1,186 +0,0 @@ - - -# MSSQL connection - -### *class* onetl.connection.db_connection.mssql.connection.MSSQL(\*, spark: SparkSession, user: str, password: SecretStr, database: str, host: Host, port: int | None = None, extra: MSSQLExtra = MSSQLExtra()) - -MSSQL JDBC connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on Maven package [com.microsoft.sqlserver:mssql-jdbc:12.8.1.jre8](https://mvnrepository.com/artifact/com.microsoft.sqlserver/mssql-jdbc/12.8.1.jre8) -([official MSSQL JDBC driver](https://docs.microsoft.com/en-us/sql/connect/jdbc/download-microsoft-jdbc-driver-for-sql-server)). - -#### SEE ALSO -Before using this connector please take into account [Prerequisites](prerequisites.md#mssql-prerequisites) - -* **Parameters:** - **host** - : Host of MSSQL database. For example: `test.mssql.domain.com` or `192.168.1.14` - - **port** - : Port of MSSQL database -
- #### Versionchanged - Changed in version 0.11.1: Default value was changed from `1433` to `None`, - to allow automatic port discovery with `instanceName`. - - **user** - : User, which have proper access to the database. For example: `some_user` - - **password** - : Password for database connection - - **database** - : Database in RDBMS, NOT schema. -
- See [this page](https://www.educba.com/postgresql-database-vs-schema/) for more details - - **spark** - : Spark session. - - **extra** - : Specifies one or more extra parameters by which clients can connect to the instance. -
- For example: `{"connectRetryCount": 3, "connectRetryInterval": 10}` -
- See [MSSQL JDBC driver properties documentation](https://learn.microsoft.com/en-us/sql/connect/jdbc/setting-the-connection-properties#properties) - for more details - -### Examples - -Create MSSQL connection with plain auth - -```py -from onetl.connection import MSSQL -from pyspark.sql import SparkSession - -# Create Spark session with MSSQL driver loaded -maven_packages = MSSQL.get_packages() -spark = ( - SparkSession.builder.appName("spark-app-name") - .config("spark.jars.packages", ",".join(maven_packages)) - .getOrCreate() -) - -# Create connection -mssql = MSSQL( - host="database.host.or.ip", - port=1433, - user="user", - password="*****", - extra={ - "trustServerCertificate": "true", # add this to avoid SSL certificate issues - }, - spark=spark, -) -``` - -Create MSSQL connection with domain auth - -```py -# Create Spark session with MSSQL driver loaded -... - -# Create connection -mssql = MSSQL( - host="database.host.or.ip", - port=1433, - user="user", - password="*****", - extra={ - "domain": "some.domain.com", # add here your domain - "integratedSecurity": "true", - "authenticationScheme": "NTLM", - "trustServerCertificate": "true", # add this to avoid SSL certificate issues - }, - spark=spark, -) -``` - -Create MSSQL connection with instance name - -```py -# Create Spark session with MSSQL driver loaded -... - -# Create connection -mssql = MSSQL( - host="database.host.or.ip", - # !!! no port !!! - user="user", - password="*****", - extra={ - "instanceName": "myinstance", # add here your instance name - "trustServerCertificate": "true", # add this to avoid SSL certificate issues - }, - spark=spark, -) -``` - -Create MSSQL read-only connection - -```py -# Create Spark session with MSSQL driver loaded -... - -# Create connection -mssql = MSSQL( - host="database.host.or.ip", - port=1433, - user="user", - password="*****", - extra={ - "applicationIntent": "ReadOnly", # driver will open read-only connection, to avoid writing to the database - "trustServerCertificate": "true", # add this to avoid SSL certificate issues - }, - spark=spark, -).check() -``` - - - -#### check() - -Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If not, an exception will be raised. - -* **Returns:** - Connection itself -* **Raises:** - RuntimeError - : If the connection is not available - -### Examples - -```python -connection.check() -``` - - - -#### *classmethod* get_packages(java_version: str | None = None, package_version: str | None = None) → list[str] - -Get package names to be downloaded by Spark. Allows specifying custom JDBC driver versions for MSSQL. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **java_version** - : Java major version, defaults to `8`. Must be `8` or `11`. - - **package_version** - : Specifies the version of the MSSQL JDBC driver to use. Defaults to `12.8.1.`. - -### Examples - -```python -from onetl.connection import MSSQL - -MSSQL.get_packages() - -# specify Java and package versions -MSSQL.get_packages(java_version="8", package_version="12.8.1.jre11") -``` - - diff --git a/mddocs/markdown/connection/db_connection/mssql/execute.md b/mddocs/markdown/connection/db_connection/mssql/execute.md deleted file mode 100644 index 7655175c8..000000000 --- a/mddocs/markdown/connection/db_connection/mssql/execute.md +++ /dev/null @@ -1,184 +0,0 @@ - - -# Executing statements in MSSQL - -#### WARNING -Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. - -Do **NOT** use them to read large amounts of data. Use [DBReader](read.md#mssql-read) or [MSSQL.sql](sql.md#mssql-sql) instead. - -## How to - -There are 2 ways to execute some statement in MSSQL - -### Use `MSSQL.fetch` - -Use this method to perform some `SELECT` query which returns **small number or rows**, like reading -MSSQL config, or reading data from some reference table. Method returns Spark DataFrame. - -Method accepts [`MSSQL.FetchOptions`](#onetl.connection.db_connection.mssql.options.MSSQLFetchOptions). - -Connection opened using this method should be then closed with `connection.close()` or `with connection:`. - -#### WARNING -Please take into account [MSSQL <-> Spark type mapping](types.md#mssql-types). - -#### Syntax support - -This method supports **any** query syntax supported by MSSQL, like: - -* ✅︎ `SELECT ... FROM ...` -* ✅︎ `WITH alias AS (...) SELECT ...` -* ✅︎ `SELECT func(arg1, arg2) FROM DUAL` - call function -* ❌ `SET ...; SELECT ...;` - multiple statements not supported - -#### Examples - -```python -from onetl.connection import MSSQL - -mssql = MSSQL(...) - -df = mssql.fetch( - "SELECT value FROM some.reference_table WHERE key = 'some_constant'", - options=MSSQL.FetchOptions(queryTimeout=10), -) -mssql.close() -value = df.collect()[0][0] # get value from first row and first column -``` - -### Use `MSSQL.execute` - -Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. - -Method accepts [`MSSQL.ExecuteOptions`](#onetl.connection.db_connection.mssql.options.MSSQLExecuteOptions). - -Connection opened using this method should be then closed with `connection.close()` or `with connection:`. - -#### Syntax support - -This method supports **any** query syntax supported by MSSQL, like: - -* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...` -* ✅︎ `ALTER ...` -* ✅︎ `INSERT INTO ... AS SELECT ...` -* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on -* ✅︎ `EXEC procedure(arg1, arg2) ...` or `{call procedure(arg1, arg2)}` - special syntax for calling procedure -* ✅︎ `DECLARE ... BEGIN ... END` - execute PL/SQL statement -* ✅︎ other statements not mentioned here -* ❌ `SET ...; SELECT ...;` - multiple statements not supported - -#### Examples - -```python -from onetl.connection import MSSQL - -mssql = MSSQL(...) - -mssql.execute("DROP TABLE schema.table") -mssql.execute( - """ - CREATE TABLE schema.table ( - id bigint GENERATED ALWAYS AS IDENTITY, - key VARCHAR2(4000), - value NUMBER - ) - """, - options=MSSQL.ExecuteOptions(queryTimeout=10), -) -``` - -## Options - -### *pydantic model* onetl.connection.db_connection.mssql.options.MSSQLFetchOptions - -Options related to fetching data from databases via JDBC. - -#### Versionadded -Added in version 0.11.0: Replace `MSSQL.JDBCOptions` → `MSSQL.FetchOptions` - -### Examples - -#### NOTE -You can pass any value supported by underlying JDBC driver class, -even if it is not mentioned in this documentation. - -```python -from onetl.connection import MSSQL - -options = MSSQL.FetchOptions( - queryTimeout=60_000, - fetchsize=100_000, - customSparkOption="value", -) -``` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int | None* *= None* - -How many rows to fetch per round trip. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value depends on driver. For example, Oracle has -default `fetchsize=10`. - - - -### *pydantic model* onetl.connection.db_connection.mssql.options.MSSQLExecuteOptions - -Options related to executing statements in databases via JDBC. - -#### Versionadded -Added in version 0.11.0: Replace `MSSQL.JDBCOptions` → `MSSQL.ExecuteOptions` - -### Examples - -#### NOTE -You can pass any value supported by underlying JDBC driver class, -even if it is not mentioned in this documentation. - -```python -from onetl.connection import MSSQL - -options = MSSQL.ExecuteOptions( - queryTimeout=60_000, - customSparkOption="value", -) -``` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int | None* *= None* - -How many rows to fetch per round trip. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value depends on driver. For example, Oracle has -default `fetchsize=10`. - - diff --git a/mddocs/markdown/connection/db_connection/mssql/index.md b/mddocs/markdown/connection/db_connection/mssql/index.md deleted file mode 100644 index a35b4acbb..000000000 --- a/mddocs/markdown/connection/db_connection/mssql/index.md +++ /dev/null @@ -1,19 +0,0 @@ - - -# MSSQL - -# Connection - -* [Prerequisites](prerequisites.md) -* [MSSQL connection](connection.md) - -# Operations - -* [Reading from MSSQL using `DBReader`](read.md) -* [Reading from MSSQL using `MSSQL.sql`](sql.md) -* [Writing to MSSQL using `DBWriter`](write.md) -* [Executing statements in MSSQL](execute.md) - -# Troubleshooting - -* [MSSQL <-> Spark type mapping](types.md) diff --git a/mddocs/markdown/connection/db_connection/mssql/prerequisites.md b/mddocs/markdown/connection/db_connection/mssql/prerequisites.md deleted file mode 100644 index e103ced3e..000000000 --- a/mddocs/markdown/connection/db_connection/mssql/prerequisites.md +++ /dev/null @@ -1,78 +0,0 @@ - - -# Prerequisites - -## Version Compatibility - -* SQL Server versions: - : * Officially declared: 2016 - 2022 - * Actually tested: 2017, 2022 -* Spark versions: 2.3.x - 3.5.x -* Java versions: 8 - 20 - -See [official documentation](https://learn.microsoft.com/en-us/sql/connect/jdbc/system-requirements-for-the-jdbc-driver) -and [official compatibility matrix](https://learn.microsoft.com/en-us/sql/connect/jdbc/microsoft-jdbc-driver-for-sql-server-support-matrix). - -## Installing PySpark - -To use MSSQL connector you should have PySpark installed (or injected to `sys.path`) -BEFORE creating the connector instance. - -See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. - -## Connecting to MSSQL - -### Connection port - -Connection is usually performed to port 1433. Port may differ for different MSSQL instances. -Please ask your MSSQL administrator to provide required information. - -For named MSSQL instances (`instanceName` option), [port number is optional](https://learn.microsoft.com/en-us/sql/connect/jdbc/building-the-connection-url?view=sql-server-ver16#named-and-multiple-sql-server-instances), and could be omitted. - -### Connection host - -It is possible to connect to MSSQL by using either DNS name of host or it’s IP address. - -If you’re using MSSQL cluster, it is currently possible to connect only to **one specific node**. -Connecting to multiple nodes to perform load balancing, as well as automatic failover to new master/replica are not supported. - -### Required grants - -Ask your MSSQL cluster administrator to set following grants for a user, -used for creating a connection: - -Read + Write (schema is owned by user) - -```sql --- allow creating tables for user -GRANT CREATE TABLE TO username; - --- allow read & write access to specific table -GRANT SELECT, INSERT ON username.mytable TO username; - --- only if if_exists="replace_entire_table" is used: --- allow dropping/truncating tables in any schema -GRANT ALTER ON username.mytable TO username; -``` - -Read + Write (schema is not owned by user) - -```sql --- allow creating tables for user -GRANT CREATE TABLE TO username; - --- allow managing tables in specific schema, and inserting data to tables -GRANT ALTER, SELECT, INSERT ON SCHEMA::someschema TO username; -``` - -Read only - -```sql --- allow read access to specific table -GRANT SELECT ON someschema.mytable TO username; -``` - -More details can be found in official documentation: -: * [GRANT ON DATABASE](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-database-permissions-transact-sql) - * [GRANT ON OBJECT](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-object-permissions-transact-sql) - * [GRANT ON SCHEMA](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-schema-permissions-transact-sql) diff --git a/mddocs/markdown/connection/db_connection/mssql/read.md b/mddocs/markdown/connection/db_connection/mssql/read.md deleted file mode 100644 index 9dc3dc88d..000000000 --- a/mddocs/markdown/connection/db_connection/mssql/read.md +++ /dev/null @@ -1,339 +0,0 @@ - - -# Reading from MSSQL using `DBReader` - -[`DBReader`](../../../db/db_reader.md#onetl.db.db_reader.db_reader.DBReader) supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, -but does not support custom queries, like `JOIN`. - -#### WARNING -Please take into account [MSSQL <-> Spark type mapping](types.md#mssql-types) - -## Supported DBReader features - -* ✅︎ `columns` -* ✅︎ `where` -* ✅︎ `hwm`, supported strategies: -* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) -* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) -* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) -* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) -* ❌ `hint` (MSSQL does support hints, but DBReader not, at least for now) -* ❌ `df_schema` -* ✅︎ `options` (see [`MSSQL.ReadOptions`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions)) - -## Examples - -Snapshot strategy: - -```python -from onetl.connection import MSSQL -from onetl.db import DBReader - -mssql = MSSQL(...) - -reader = DBReader( - connection=mssql, - source="schema.table", - columns=["id", "key", "CAST(value AS text) value", "updated_dt"], - where="key = 'something'", - options=MSSQL.ReadOptions(partitionColumn="id", numPartitions=10), -) -df = reader.run() -``` - -Incremental strategy: - -```python -from onetl.connection import MSSQL -from onetl.db import DBReader -from onetl.strategy import IncrementalStrategy - -mssql = MSSQL(...) - -reader = DBReader( - connection=mssql, - source="schema.table", - columns=["id", "key", "CAST(value AS text) value", "updated_dt"], - where="key = 'something'", - hwm=DBReader.AutoDetectHWM(name="mssql_hwm", expression="updated_dt"), - options=MSSQL.ReadOptions(partitionColumn="id", numPartitions=10), -) - -with IncrementalStrategy(): - df = reader.run() -``` - -## Recommendations - -### Select only required columns - -Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from MSSQL to Spark. - -### Pay attention to `where` value - -Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. -This both reduces the amount of data send from MSSQL to Spark, and may also improve performance of the query. -Especially if there are indexes or partitions for columns used in `where` clause. - -## Options - -### *pydantic model* onetl.connection.db_connection.mssql.options.MSSQLReadOptions - -Spark JDBC reading options. - -#### Versionadded -Added in version 0.5.0: Replace `MSSQL.Options` → `MSSQL.ReadOptions` - -### Examples - -#### NOTE -You can pass any value -[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), -even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! - -The set of supported options depends on Spark version. - -```python -from onetl.connection import MSSQL - -options = MSSQL.ReadOptions( - partitioning_mode="range", - partitionColumn="reg_id", - numPartitions=10, - customSparkOption="value", -) -``` - - - -#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* - -Column used to parallelize reading from a table. - -#### WARNING -It is highly recommended to use primary key, or column with an index -to avoid performance issues. - -#### NOTE -Column type depends on [`partitioning_mode`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions.partitioning_mode). - -* `partitioning_mode="range"` requires column to be an integer, date or timestamp (can be NULL, but not recommended). -* `partitioning_mode="hash"` accepts any column type (NOT NULL). -* `partitioning_mode="mod"` requires column to be an integer (NOT NULL). - -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions.partitioning_mode) for more details - - - -#### *field* num_partitions *: PositiveInt* *= 1* *(alias 'numPartitions')* - -Number of jobs created by Spark to read the table content in parallel. -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions.partitioning_mode) for more details - - - -#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* - -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions.partitioning_mode) for more details - - - -#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* - -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions.partitioning_mode) for more details - - - -#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* - -After each database session is opened to the remote DB and before starting to read data, -this option executes a custom SQL statement (or a PL/SQL block). - -Use this to implement session initialization code. - -Example: - -```python -sessionInitStatement = """ - BEGIN - execute immediate - 'alter session set "_serial_direct_read"=true'; - END; -""" -``` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int* *= 100000* - -Fetch N rows from an opened cursor per one read round. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value is different from Spark. - -Spark uses driver’s own value, and it may be different in different drivers, -and even versions of the same driver. For example, Oracle has -default `fetchsize=10`, which is absolutely not usable. - -Thus we’ve overridden default value with `100_000`, which should increase reading performance. - -#### Versionchanged -Changed in version 0.2.0: Set explicit default value to `100_000` - - - -#### *field* partitioning_mode *: JDBCPartitioningMode* *= JDBCPartitioningMode.RANGE* - -Defines how Spark will parallelize reading from table. - -Possible values: - -* `range` (default) - : Allocate each executor a range of values from column passed into [`partition_column`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions.partition_column). -
- ### Spark generates for each executor an SQL query -
- Executor 1: - ```sql - SELECT ... FROM table - WHERE (partition_column >= lowerBound - OR partition_column IS NULL) - AND partition_column < (lower_bound + stride) - ``` -
- Executor 2: - ```sql - SELECT ... FROM table - WHERE partition_column >= (lower_bound + stride) - AND partition_column < (lower_bound + 2 * stride) - ``` -
- … -
- Executor N: - ```sql - SELECT ... FROM table - WHERE partition_column >= (lower_bound + (N-1) * stride) - AND partition_column <= upper_bound - ``` -
- Where `stride=(upper_bound - lower_bound) / num_partitions`. -
- #### NOTE - Can be used only with columns of integer, date or timestamp types. -
- #### NOTE - [`lower_bound`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions.lower_bound), [`upper_bound`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions.upper_bound) and [`num_partitions`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions.num_partitions) are used just to - calculate the partition stride, **NOT** for filtering the rows in table. - So all rows in the table will be returned (unlike *Incremental* [Read Strategies](../../../strategy/index.md#strategy)). -
- #### NOTE - All queries are executed in parallel. To execute them sequentially, use *Batch* [Read Strategies](../../../strategy/index.md#strategy). -* `hash` - : Allocate each executor a set of values based on hash of the [`partition_column`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions.partition_column) column. -
- ### Spark generates for each executor an SQL query -
- Executor 1: - ```sql - SELECT ... FROM table - WHERE (some_hash(partition_column) mod num_partitions) = 0 -- lower_bound - ``` -
- Executor 2: - ```sql - SELECT ... FROM table - WHERE (some_hash(partition_column) mod num_partitions) = 1 -- lower_bound + 1 - ``` -
- … -
- Executor N: - ```sql - SELECT ... FROM table - WHERE (some_hash(partition_column) mod num_partitions) = num_partitions-1 -- upper_bound - ``` -
- #### NOTE - The hash function implementation depends on RDBMS. It can be `MD5` or any other fast hash function, - or expression based on this function call. Usually such functions accepts any column type as an input. -* `mod` - : Allocate each executor a set of values based on modulus of the [`partition_column`](#onetl.connection.db_connection.mssql.options.MSSQLReadOptions.partition_column) column. -
- ### Spark generates for each executor an SQL query -
- Executor 1: - ```sql - SELECT ... FROM table - WHERE (partition_column mod num_partitions) = 0 -- lower_bound - ``` -
- Executor 2: - ```sql - SELECT ... FROM table - WHERE (partition_column mod num_partitions) = 1 -- lower_bound + 1 - ``` -
- Executor N: - ```sql - SELECT ... FROM table - WHERE (partition_column mod num_partitions) = num_partitions-1 -- upper_bound - ``` -
- #### NOTE - Can be used only with columns of integer type. - -#### Versionadded -Added in version 0.5.0. - -### Examples - -Read data in 10 parallel jobs by range of values in `id_column` column: - -```python -ReadOptions( - partitioning_mode="range", # default mode, can be omitted - partitionColumn="id_column", - numPartitions=10, - # Options below can be discarded because they are - # calculated automatically as MIN and MAX values of `partitionColumn` - lowerBound=0, - upperBound=100_000, -) -``` - -Read data in 10 parallel jobs by hash of values in `some_column` column: - -```python -ReadOptions( - partitioning_mode="hash", - partitionColumn="some_column", - numPartitions=10, - # lowerBound and upperBound are automatically set to `0` and `9` -) -``` - -Read data in 10 parallel jobs by modulus of values in `id_column` column: - -```python -ReadOptions( - partitioning_mode="mod", - partitionColumn="id_column", - numPartitions=10, - # lowerBound and upperBound are automatically set to `0` and `9` -) -``` - - diff --git a/mddocs/markdown/connection/db_connection/mssql/sql.md b/mddocs/markdown/connection/db_connection/mssql/sql.md deleted file mode 100644 index 93d2fb3ef..000000000 --- a/mddocs/markdown/connection/db_connection/mssql/sql.md +++ /dev/null @@ -1,191 +0,0 @@ - - -# Reading from MSSQL using `MSSQL.sql` - -`MSSQL.sql` allows passing custom SQL query, but does not support incremental strategies. - -#### WARNING -Please take into account [MSSQL <-> Spark type mapping](types.md#mssql-types) - -#### WARNING -Statement is executed in **read-write** connection, so if you’re calling some functions/procedures with DDL/DML statements inside, -they can change data in your database. - -## Syntax support - -Only queries with the following syntax are supported: - -* ✅︎ `SELECT ... FROM ...` -* ❌ `WITH alias AS (...) SELECT ...` -* ❌ `SET ...; SELECT ...;` - multiple statements not supported - -## Examples - -```python -from onetl.connection import MSSQL - -mssql = MSSQL(...) -df = mssql.sql( - """ - SELECT - id, - key, - CAST(value AS text) value, - updated_at - FROM - some.mytable - WHERE - key = 'something' - """, - options=MSSQL.SQLOptions( - partitionColumn="id", - numPartitions=10, - lowerBound=0, - upperBound=1000, - ), -) -``` - -## Recommendations - -### Select only required columns - -Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. -This reduces the amount of data passed from MSSQL to Spark. - -### Pay attention to `where` value - -Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. -This both reduces the amount of data send from MSSQL to Spark, and may also improve performance of the query. -Especially if there are indexes or partitions for columns used in `where` clause. - -## Options - -### *pydantic model* onetl.connection.db_connection.mssql.options.MSSQLSQLOptions - -Options specifically for SQL queries - -These options allow you to specify configurations for executing SQL queries -without relying on Spark’s partitioning mechanisms. - -#### Versionadded -Added in version 0.11.0: Split up `MSSQL.ReadOptions` to `MSSQL.SQLOptions` - -### Examples - -#### NOTE -You can pass any JDBC configuration -[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), -tailored to optimize SQL query execution. **Option names should be in** `camelCase`! - -```python -from onetl.connection import MSSQL - -options = MSSQL.SQLOptions( - partitionColumn="reg_id", - numPartitions=10, - lowerBound=0, - upperBound=1000, - customSparkOption="value", -) -``` - - - -#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* - -Column used to partition data across multiple executors for parallel query processing. - -#### WARNING -It is highly recommended to use primary key, or column with an index -to avoid performance issues. - -### Example of using `partitionColumn="id"` with `partitioning_mode="range"` - -```sql --- If partition_column is 'id', with numPartitions=4, lowerBound=1, and upperBound=100: --- Executor 1 processes IDs from 1 to 25 -SELECT ... FROM table WHERE id >= 1 AND id < 26 --- Executor 2 processes IDs from 26 to 50 -SELECT ... FROM table WHERE id >= 26 AND id < 51 --- Executor 3 processes IDs from 51 to 75 -SELECT ... FROM table WHERE id >= 51 AND id < 76 --- Executor 4 processes IDs from 76 to 100 -SELECT ... FROM table WHERE id >= 76 AND id <= 100 - --- General case for Executor N -SELECT ... FROM table -WHERE partition_column >= (lowerBound + (N-1) * stride) -AND partition_column <= upperBound --- Where ``stride`` is calculated as ``(upperBound - lowerBound) / numPartitions``. -``` - - - -#### *field* num_partitions *: int | None* *= None* *(alias 'numPartitions')* - -Number of jobs created by Spark to read the table content in parallel. - - - -#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* - -Defines the starting boundary for partitioning the query’s data. Mandatory if [`partition_column`](#onetl.connection.db_connection.mssql.options.MSSQLSQLOptions.partition_column) is set - - - -#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* - -Sets the ending boundary for data partitioning. Mandatory if [`partition_column`](#onetl.connection.db_connection.mssql.options.MSSQLSQLOptions.partition_column) is set - - - -#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* - -After each database session is opened to the remote DB and before starting to read data, -this option executes a custom SQL statement (or a PL/SQL block). - -Use this to implement session initialization code. - -Example: - -```python -sessionInitStatement = """ - BEGIN - execute immediate - 'alter session set "_serial_direct_read"=true'; - END; -""" -``` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int* *= 100000* - -Fetch N rows from an opened cursor per one read round. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value is different from Spark. - -Spark uses driver’s own value, and it may be different in different drivers, -and even versions of the same driver. For example, Oracle has -default `fetchsize=10`, which is absolutely not usable. - -Thus we’ve overridden default value with `100_000`, which should increase reading performance. - -#### Versionchanged -Changed in version 0.2.0: Set explicit default value to `100_000` - - diff --git a/mddocs/markdown/connection/db_connection/mssql/types.md b/mddocs/markdown/connection/db_connection/mssql/types.md deleted file mode 100644 index 5981107ad..000000000 --- a/mddocs/markdown/connection/db_connection/mssql/types.md +++ /dev/null @@ -1,281 +0,0 @@ - - -# MSSQL <-> Spark type mapping - -#### NOTE -The results below are valid for Spark 3.5.5, and may differ on other Spark versions. - -## Type detection & casting - -Spark’s DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. - -### Reading from MSSQL - -This is how MSSQL connector performs this: - -* For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and MSSQL type. -* Find corresponding `MSSQL type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. -* Create DataFrame from query with specific column names and Spark types. - -### Writing to some existing MSSQL table - -This is how MSSQL connector performs this: - -* Get names of columns in DataFrame. [1](#id3) -* Perform `SELECT * FROM table LIMIT 0` query. -* Take only columns present in DataFrame (by name, case insensitive). For each found column get MSSQL type. -* Find corresponding `Spark type` → `MSSQL type (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. -* If `MSSQL type (write)` match `MSSQL type (read)`, no additional casts will be performed, DataFrame column will be written to MSSQL as is. -* If `MSSQL type (write)` does not match `MSSQL type (read)`, DataFrame column will be casted to target column type **on MSSQL side**. - For example, you can write column with text data to `int` column, if column contains valid integer values within supported value range and precision [2](#id4). - -* **[1]** This allows to write data to tables with `DEFAULT` and `GENERATED` columns - if DataFrame has no such column, it will be populated by MSSQL. -* **[2]** This is true only if DataFrame column is a `StringType()`, because text value is parsed automatically to target column type. But other types cannot be silently converted, like `int -> text`. This requires explicit casting, see [DBWriter](). - -### Create new table using Spark - -#### WARNING -ABSOLUTELY NOT RECOMMENDED! - -This is how MSSQL connector performs this: - -* Find corresponding `Spark type` → `MSSQL type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. -* Generate DDL for creating table in MSSQL, like `CREATE TABLE (col1 ...)`, and run it. -* Write DataFrame to created table as is. - -But some cases this may lead to using wrong column type. For example, Spark creates column of type `timestamp` -which corresponds to MSSQL’s type `timestamp(0)` (precision up to seconds) -instead of more precise `timestamp(6)` (precision up to nanoseconds). -This may lead to incidental precision loss, or sometimes data cannot be written to created table at all. - -So instead of relying on Spark to create tables: - -### See example - -```python -writer = DBWriter( - connection=mssql, - target="myschema.target_tbl", - options=MSSQL.WriteOptions( - if_exists="append", - ), -) -writer.run(df) -``` - -Always prefer creating tables with specific types **BEFORE WRITING DATA**: - -### See example - -```python -mssql.execute( - """ - CREATE TABLE schema.table ( - id bigint, - key text, - value datetime2(6) -- specific type and precision - ) - """, -) - -writer = DBWriter( - connection=mssql, - target="myschema.target_tbl", - options=MSSQL.WriteOptions(if_exists="append"), -) -writer.run(df) -``` - -### References - -Here you can find source code with type conversions: - -* [MSSQL -> JDBC](https://github.com/microsoft/mssql-jdbc/blob/v12.2.0/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSetMetaData.java#L117-L170) -* [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MsSqlServerDialect.scala#L135-L152) -* [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MsSqlServerDialect.scala#L154-L163) -* [JDBC -> MSSQL](https://github.com/microsoft/mssql-jdbc/blob/v12.2.0/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java#L625-L676) - -## Supported types - -See [official documentation](https://learn.microsoft.com/en-us/sql/t-sql/data-types/data-types-transact-sql) - -### Numeric types - -| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | -|-----------------------------|---------------------------------|-----------------------------|-----------------------------| -| `decimal` | `DecimalType(P=18, S=0)` | `decimal(P=18, S=0)` | `decimal(P=18, S=0)` | -| `decimal(P=0..38)` | `DecimalType(P=0..38, S=0)` | `decimal(P=0..38, S=0)` | `decimal(P=0..38, S=0)` | -| `decimal(P=0..38, S=0..38)` | `DecimalType(P=0..38, S=0..38)` | `decimal(P=0..38, S=0..38)` | `decimal(P=0..38, S=0..38)` | -| `real` | `FloatType()` | `real` | `real` | -| `float` | `DoubleType()` | `float` | `float` | -| `smallint` | `ShortType()` | `smallint` | `smallint` | -| `tinyint` | `IntegerType()` | `int` | `int` | -| `int` | | | | -| `bigint` | `LongType()` | `bigint` | `bigint` | - -### Temporal types - -#### NOTE -MSSQL `timestamp` type is alias for `rowversion` (see [Special types]()). It is not a temporal type! - -| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | -|-------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------|------------------------------------------------------------------------| -| `date` | `DateType()` | `date` | `date` | -| `smalldatetime`, minutes | `TimestampType()`, microseconds | `datetime2(6)`, microseconds | `datetime`, milliseconds | -| `datetime`, milliseconds | | | | -| `datetime2(0)`, seconds | | | | -| `datetime2(3)`, milliseconds | | | | -| `datetime2(6)`, microseconds | `TimestampType()`, microseconds | `datetime2(6)`, microseconds | `datetime`, milliseconds,
**precision loss** [3](#id14) | -| `datetime2(7)`, 100s of nanoseconds | `TimestampType()`, microseconds,
**precision loss** [4](#id15) | `datetime2(6)`, microseconds,
**precision loss** [4](#id15) | | -| `time(0)`, seconds | `TimestampType()`, microseconds,
with time format quirks [5](#id16) | `datetime2(6)`, microseconds | `datetime`, milliseconds | -| `time(3)`, milliseconds | | | | -| `time(6)`, microseconds | `TimestampType()`, microseconds,
with time format quirks [5](#id16) | `datetime2(6)`, microseconds | `datetime`, milliseconds,
**precision loss** [3](#id14) | -| | | | | -| `time`, 100s of nanoseconds | `TimestampType()`, microseconds,
**precision loss** [4](#id15),
with time format quirks [5](#id16) | `datetime2(6)`, microseconds
**precision loss** [3](#id14) | | -| `time(7)`, 100s of nanoseconds | | | | -| `datetimeoffset` | `StringType()` | `nvarchar` | `nvarchar` | - -#### WARNING -Note that types in MSSQL and Spark have different value ranges: - -| MySQL type | Min value | Max value | Spark type | Min value | Max value | -|-----------------|------------------------------|------------------------------|-------------------|------------------------------|------------------------------| -| `smalldatetime` | `1900-01-01 00:00:00` | `2079-06-06 23:59:00` | `TimestampType()` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | -| `datetime` | `1753-01-01 00:00:00.000` | `9999-12-31 23:59:59.997` | | | | -| `datetime2` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | | | | -| `time` | `00:00:00.0000000` | `23:59:59.9999999` | | | | - -So not all of values in Spark DataFrame can be written to MSSQL. - -References: -: * [MSSQL date & time types documentation](https://learn.microsoft.com/en-us/sql/t-sql/data-types/date-and-time-types) - * [Spark DateType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/DateType.html) - * [Spark TimestampType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/TimestampType.html) - -* **[3]** MSSQL dialect for Spark generates DDL with type `datetime` which has precision up to milliseconds (`23:59:59.999`, 10-3 seconds). Inserting data with microsecond and higher precision (`23:59:59.999999` .. `23.59:59.9999999`, 10-6 .. 10-7 seconds) will lead to **throwing away microseconds**. -* **[4]** MSSQL support timestamp up to 100s of nanoseconds precision (`23:59:59.9999999999`, 10-7 seconds), but Spark `TimestampType()` supports datetime up to microseconds precision (`23:59:59.999999`, 10-6 seconds). Last digit will be lost during read or write operations. -* **[5]** `time` type is the same as `datetime2` with date `1970-01-01`. So instead of reading data from MSSQL like `23:59:59.999999` it is actually read `1970-01-01 23:59:59.999999`, and vice versa. - -### String types - -| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | -|---------------------|----------------|----------------------|-----------------------| -| `char` | `StringType()` | `nvarchar` | `nvarchar` | -| `char(N)` | | | | -| `nchar` | | | | -| `nchar(N)` | | | | -| `varchar` | | | | -| `varchar(N)` | | | | -| `nvarchar` | | | | -| `nvarchar(N)` | | | | -| `mediumtext` | | | | -| `text` | | | | -| `ntext` | | | | -| `xml` | | | | - -### Binary types - -| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | -|---------------------|-----------------|----------------------|-----------------------| -| `bit` | `BooleanType()` | `bit` | `bit` | -| `binary` | `BinaryType()` | `varbinary` | `varbinary` | -| `binary(N)` | | | | -| `varbinary` | | | | -| `varbinary(N)` | | | | -| `image` | | | | - -### Special types - -| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | -|---------------------|----------------|----------------------|-----------------------| -| `geography` | `BinaryType()` | `varbinary` | `varbinary` | -| `geometry` | | | | -| `hierarchyid` | | | | -| `rowversion` | | | | -| `sql_variant` | unsupported | | | -| `sysname` | `StringType()` | `nvarchar` | `nvarchar` | -| `uniqueidentifier` | | | | - -## Explicit type cast - -### `DBReader` - -It is possible to explicitly cast column type using `DBReader(columns=...)` syntax. - -For example, you can use `CAST(column AS text)` to convert data to string representation on MSSQL side, and so it will be read as Spark’s `StringType()`: - -```python -from onetl.connection import MSSQL -from onetl.db import DBReader - -mssql = MSSQL(...) - -DBReader( - connection=mssql, - columns=[ - "id", - "supported_column", - "CAST(unsupported_column AS text) unsupported_column_str", - ], -) -df = reader.run() - -# cast column content to proper Spark type -df = df.select( - df.id, - df.supported_column, - # explicit cast - df.unsupported_column_str.cast("integer").alias("parsed_integer"), -) -``` - -### `DBWriter` - -Convert dataframe column to JSON using [to_json](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.functions.to_json.html), -and write it as `text` column in MSSQL: - -```python -mssql.execute( - """ - CREATE TABLE schema.target_tbl ( - id bigint, - struct_column_json text -- any string type, actually - ) - """, -) - -from pyspark.sql.functions import to_json - -df = df.select( - df.id, - to_json(df.struct_column).alias("struct_column_json"), -) - -writer.run(df) -``` - -Then you can parse this column on MSSQL side - for example, by creating a view: - -```sql -SELECT - id, - JSON_VALUE(struct_column_json, "$.nested.field") AS nested_field -FROM target_tbl -``` - -Or by using [computed column](https://learn.microsoft.com/en-us/sql/relational-databases/tables/specify-computed-columns-in-a-table): - -```sql -CREATE TABLE schema.target_table ( - id bigint, - supported_column datetime2(6), - struct_column_json text, -- any string type, actually - -- computed column - nested_field AS (JSON_VALUE(struct_column_json, "$.nested.field")) - -- or persisted column - -- nested_field AS (JSON_VALUE(struct_column_json, "$.nested.field")) PERSISTED -) -``` - -By default, column value is calculated on every table read. -Column marked as `PERSISTED` is calculated during insert, but this require additional space. diff --git a/mddocs/markdown/connection/db_connection/mssql/write.md b/mddocs/markdown/connection/db_connection/mssql/write.md deleted file mode 100644 index d29aca762..000000000 --- a/mddocs/markdown/connection/db_connection/mssql/write.md +++ /dev/null @@ -1,183 +0,0 @@ - - -# Writing to MSSQL using `DBWriter` - -For writing data to MSSQL, use [`DBWriter`](../../../db/db_writer.md#onetl.db.db_writer.db_writer.DBWriter). - -#### WARNING -Please take into account [MSSQL <-> Spark type mapping](types.md#mssql-types) - -#### WARNING -It is always recommended to create table explicitly using [MSSQL.execute](execute.md#mssql-execute) -instead of relying on Spark’s table DDL generation. - -This is because Spark’s DDL generator can create columns with different precision and types than it is expected, -causing precision loss or other issues. - -## Examples - -```python -from onetl.connection import MSSQL -from onetl.db import DBWriter - -mssql = MSSQL(...) - -df = ... # data is here - -writer = DBWriter( - connection=mssql, - target="schema.table", - options=MSSQL.WriteOptions(if_exists="append"), -) - -writer.run(df) -``` - -## Options - -Method above accepts [`MSSQL.WriteOptions`](#onetl.connection.db_connection.mssql.options.MSSQLWriteOptions) - -### *pydantic model* onetl.connection.db_connection.mssql.options.MSSQLWriteOptions - -Spark JDBC writing options. - -#### Versionadded -Added in version 0.5.0: Replace `MSSQL.Options` → `MSSQL.WriteOptions` - -### Examples - -#### NOTE -You can pass any value -[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), -even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! - -The set of supported options depends on Spark version. - -```python -from onetl.connection import MSSQL - -options = MSSQL.WriteOptions( - if_exists="append", - batchsize=20_000, - customSparkOption="value", -) -``` - - - -#### *field* if_exists *: JDBCTableExistBehavior* *= JDBCTableExistBehavior.APPEND* *(alias 'mode')* - -Behavior of writing data into existing table. - -Possible values: -: * `append` (default) - : Adds new rows into existing table. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : Data is appended to a table. Table has the same DDL as before writing data -
- #### WARNING - This mode does not check whether table already contains - rows from dataframe, so duplicated rows can be created. -
- Also Spark does not support passing custom options to - insert statement, like `ON CONFLICT`, so don’t try to - implement deduplication using unique indexes or constraints. -
- Instead, write to staging table and perform deduplication - using `execute` method. - * `replace_entire_table` - : **Table is dropped and then created, or truncated**. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : Table content is replaced with dataframe content. -
- After writing completed, target table could either have the same DDL as - before writing data (`truncate=True`), or can be recreated (`truncate=False` - or source does not support truncation). - * `ignore` - : Ignores the write operation if the table already exists. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : The write operation is ignored, and no data is written to the table. - * `error` - : Raises an error if the table already exists. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : An error is raised, and no data is written to the table. - -#### Versionchanged -Changed in version 0.9.0: Renamed `mode` → `if_exists` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* batchsize *: int* *= 20000* - -How many rows can be inserted per round trip. - -Tuning this option can influence performance of writing. - -#### WARNING -Default value is different from Spark. - -Spark uses quite small value `1000`, which is absolutely not usable -in BigData world. - -Thus we’ve overridden default value with `20_000`, -which should increase writing performance. - -You can increase it even more, up to `50_000`, -but it depends on your database load and number of columns in the row. -Higher values does not increase performance. - -#### Versionchanged -Changed in version 0.4.0: Changed default value from 1000 to 20_000 - - - -#### *field* isolation_level *: str* *= 'READ_UNCOMMITTED'* *(alias 'isolationLevel')* - -The transaction isolation level, which applies to current connection. - -Possible values: -: * `NONE` (as string, not Python’s `None`) - * `READ_COMMITTED` - * `READ_UNCOMMITTED` - * `REPEATABLE_READ` - * `SERIALIZABLE` - -Values correspond to transaction isolation levels defined by JDBC standard. -Please refer the documentation for -[java.sql.Connection](https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html). - - diff --git a/mddocs/markdown/connection/db_connection/mysql/connection.md b/mddocs/markdown/connection/db_connection/mysql/connection.md deleted file mode 100644 index 93bb348e7..000000000 --- a/mddocs/markdown/connection/db_connection/mysql/connection.md +++ /dev/null @@ -1,120 +0,0 @@ - - -# MySQL connection - -### *class* onetl.connection.db_connection.mysql.connection.MySQL(\*, spark: SparkSession, user: str, password: SecretStr, host: Host, port: int = 3306, database: str | None = None, extra: MySQLExtra = MySQLExtra(useUnicode='yes', characterEncoding='UTF-8')) - -MySQL JDBC connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on Maven package [com.mysql:mysql-connector-j:9.2.0](https://mvnrepository.com/artifact/com.mysql/mysql-connector-j/9.2.0) -([official MySQL JDBC driver](https://dev.mysql.com/doc/connector-j/en/)). - -#### SEE ALSO -Before using this connector please take into account [Prerequisites](prerequisites.md#mysql-prerequisites) - -#### Versionadded -Added in version 0.1.0. - -* **Parameters:** - **host** - : Host of MySQL database. For example: `mysql0012.domain.com` or `192.168.1.11` - - **port** - : Port of MySQL database - - **user** - : User, which have proper access to the database. For example: `some_user` - - **password** - : Password for database connection - - **database** - : Database in RDBMS, NOT schema. -
- See [this page](https://www.educba.com/postgresql-database-vs-schema/) for more details - - **spark** - : Spark session. - - **extra** - : Specifies one or more extra parameters by which clients can connect to the instance. -
- For example: `{"useSSL": "false", "allowPublicKeyRetrieval": "true"}` -
- See [MySQL JDBC driver properties documentation](https://dev.mysql.com/doc/connector-j/en/connector-j-reference-configuration-properties.html) - for more details - -### Examples - -Create and check MySQL connection: - -```python -from onetl.connection import MySQL -from pyspark.sql import SparkSession - -# Create Spark session with MySQL driver loaded -maven_packages = MySQL.get_packages() -spark = ( - SparkSession.builder.appName("spark-app-name") - .config("spark.jars.packages", ",".join(maven_packages)) - .getOrCreate() -) - -# Create connection -mysql = MySQL( - host="database.host.or.ip", - user="user", - password="*****", - extra={"useSSL": "false", "allowPublicKeyRetrieval": "true"}, - spark=spark, -).check() -``` - - - -#### check() - -Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If not, an exception will be raised. - -* **Returns:** - Connection itself -* **Raises:** - RuntimeError - : If the connection is not available - -### Examples - -```python -connection.check() -``` - - - -#### *classmethod* get_packages(package_version: str | None = None) → list[str] - -Get package names to be downloaded by Spark. Allows specifying a custom JDBC driver version for MySQL. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **package_version** - : Specifies the version of the MySQL JDBC driver to use. Defaults to `9.2.0`. -
- #### Versionadded - Added in version 0.11.0. - -### Examples - -```python -from onetl.connection import MySQL - -MySQL.get_packages() - -# specify a custom package version -MySQL.get_packages(package_version="8.2.0") -``` - - diff --git a/mddocs/markdown/connection/db_connection/mysql/execute.md b/mddocs/markdown/connection/db_connection/mysql/execute.md deleted file mode 100644 index fb345665f..000000000 --- a/mddocs/markdown/connection/db_connection/mysql/execute.md +++ /dev/null @@ -1,191 +0,0 @@ - - -# Executing statements in MySQL - -#### WARNING -Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. - -Do **NOT** use them to read large amounts of data. Use [DBReader](read.md#mysql-read) or [MySQL.sql](sql.md#mysql-sql) instead. - -## How to - -There are 2 ways to execute some statement in MySQL - -### Use `MySQL.fetch` - -Use this method to perform some `SELECT` query which returns **small number or rows**, like reading -MySQL config, or reading data from some reference table. Method returns Spark DataFrame. - -Method accepts [`MySQL.FetchOptions`](#onetl.connection.db_connection.mysql.options.MySQLFetchOptions). - -Connection opened using this method should be then closed with `connection.close()` or `with connection:`. - -#### WARNING -Please take into account [MySQL <-> Spark type mapping](types.md#mysql-types). - -#### Syntax support - -This method supports **any** query syntax supported by MySQL, like: - -* ✅︎ `SELECT ... FROM ...` -* ✅︎ `WITH alias AS (...) SELECT ...` -* ✅︎ `SELECT func(arg1, arg2)` or `{?= call func(arg1, arg2)}` - special syntax for calling function -* ✅︎ `SHOW ...` -* ❌ `SET ...; SELECT ...;` - multiple statements not supported - -#### Examples - -```python -from onetl.connection import MySQL - -mysql = MySQL(...) - -df = mysql.fetch( - "SELECT value FROM some.reference_table WHERE key = 'some_constant'", - options=MySQL.FetchOptions(queryTimeout=10), -) -mysql.close() -value = df.collect()[0][0] # get value from first row and first column -``` - -### Use `MySQL.execute` - -Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. - -Method accepts [`MySQL.ExecuteOptions`](#onetl.connection.db_connection.mysql.options.MySQLExecuteOptions). - -Connection opened using this method should be then closed with `connection.close()` or `with connection:`. - -#### Syntax support - -This method supports **any** query syntax supported by MySQL, like: - -* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on -* ✅︎ `ALTER ...` -* ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on -* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, and so on -* ✅︎ `CALL procedure(arg1, arg2) ...` or `{call procedure(arg1, arg2)}` - special syntax for calling procedure -* ✅︎ other statements not mentioned here -* ❌ `SET ...; SELECT ...;` - multiple statements not supported - -#### Examples - -```python -from onetl.connection import MySQL - -mysql = MySQL(...) - -mysql.execute("DROP TABLE schema.table") -mysql.execute( - """ - CREATE TABLE schema.table ( - id bigint, - key text, - value float - ) - ENGINE = InnoDB - """, - options=MySQL.ExecuteOptions(queryTimeout=10), -) -``` - -## Options - -### *pydantic model* onetl.connection.db_connection.mysql.options.MySQLFetchOptions - -Options related to fetching data from databases via JDBC. - -#### Versionadded -Added in version 0.11.0: Replace `MySQL.JDBCOptions` → `MySQL.FetchOptions` - -### Examples - -#### NOTE -You can pass any value supported by underlying JDBC driver class, -even if it is not mentioned in this documentation. - -```python -from onetl.connection import MySQL - -options = MySQL.FetchOptions( - queryTimeout=60_000, - fetchsize=100_000, - customSparkOption="value", -) -``` - - -* **Fields:** - - [`fetchsize (int | None)`](#onetl.connection.db_connection.mysql.options.MySQLFetchOptions.fetchsize) - - [`query_timeout (int | None)`](#onetl.connection.db_connection.mysql.options.MySQLFetchOptions.query_timeout) - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int | None* *= None* - -How many rows to fetch per round trip. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value depends on driver. For example, Oracle has -default `fetchsize=10`. - - - -### *pydantic model* onetl.connection.db_connection.mysql.options.MySQLExecuteOptions - -Options related to executing statements in databases via JDBC. - -#### Versionadded -Added in version 0.11.0: Replace `MySQL.JDBCOptions` → `MySQL.ExecuteOptions` - -### Examples - -#### NOTE -You can pass any value supported by underlying JDBC driver class, -even if it is not mentioned in this documentation. - -```python -from onetl.connection import MySQL - -options = MySQL.ExecuteOptions( - queryTimeout=60_000, - customSparkOption="value", -) -``` - - -* **Fields:** - - [`fetchsize (int | None)`](#onetl.connection.db_connection.mysql.options.MySQLExecuteOptions.fetchsize) - - [`query_timeout (int | None)`](#onetl.connection.db_connection.mysql.options.MySQLExecuteOptions.query_timeout) - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int | None* *= None* - -How many rows to fetch per round trip. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value depends on driver. For example, Oracle has -default `fetchsize=10`. - - diff --git a/mddocs/markdown/connection/db_connection/mysql/index.md b/mddocs/markdown/connection/db_connection/mysql/index.md deleted file mode 100644 index 6722ca77d..000000000 --- a/mddocs/markdown/connection/db_connection/mysql/index.md +++ /dev/null @@ -1,19 +0,0 @@ - - -# MySQL - -# Connection - -* [Prerequisites](prerequisites.md) -* [MySQL connection](connection.md) - -# Operations - -* [Reading from MySQL using `DBReader`](read.md) -* [Reading from MySQL using `MySQL.sql`](sql.md) -* [Writing to MySQL using `DBWriter`](write.md) -* [Executing statements in MySQL](execute.md) - -# Troubleshooting - -* [MySQL <-> Spark type mapping](types.md) diff --git a/mddocs/markdown/connection/db_connection/mysql/prerequisites.md b/mddocs/markdown/connection/db_connection/mysql/prerequisites.md deleted file mode 100644 index d0e551443..000000000 --- a/mddocs/markdown/connection/db_connection/mysql/prerequisites.md +++ /dev/null @@ -1,61 +0,0 @@ - - -# Prerequisites - -## Version Compatibility - -* MySQL server versions: - : * Officially declared: 8.0 - 9.2 - * Actually tested: 5.7.13, 9.2.0 -* Spark versions: 2.3.x - 3.5.x -* Java versions: 8 - 20 - -See [official documentation](https://dev.mysql.com/doc/connector-j/en/connector-j-versions.html). - -## Installing PySpark - -To use MySQL connector you should have PySpark installed (or injected to `sys.path`) -BEFORE creating the connector instance. - -See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. - -## Connecting to MySQL - -### Connection host - -It is possible to connect to MySQL by using either DNS name of host or it’s IP address. - -If you’re using MySQL cluster, it is currently possible to connect only to **one specific node**. -Connecting to multiple nodes to perform load balancing, as well as automatic failover to new master/replica are not supported. - -### Connection port - -Connection is usually performed to port 3306. Port may differ for different MySQL instances. -Please ask your MySQL administrator to provide required information. - -### Required grants - -Ask your MySQL cluster administrator to set following grants for a user, -used for creating a connection: - -Read + Write - -```sql --- allow creating tables in the target schema -GRANT CREATE ON myschema.* TO username@'192.168.1.%'; - --- allow read & write access to specific table -GRANT SELECT, INSERT ON myschema.mytable TO username@'192.168.1.%'; -``` - -Read only - -```sql --- allow read access to specific table -GRANT SELECT ON myschema.mytable TO username@'192.168.1.%'; -``` - -In example above `'192.168.1.%''` is a network subnet `192.168.1.0 - 192.168.1.255` -where Spark driver and executors are running. To allow connecting user from any IP, use `'%'` (not secure!). - -More details can be found in [official documentation](https://dev.mysql.com/doc/refman/en/grant.html). diff --git a/mddocs/markdown/connection/db_connection/mysql/read.md b/mddocs/markdown/connection/db_connection/mysql/read.md deleted file mode 100644 index aa66a429d..000000000 --- a/mddocs/markdown/connection/db_connection/mysql/read.md +++ /dev/null @@ -1,352 +0,0 @@ - - -# Reading from MySQL using `DBReader` - -[`DBReader`](../../../db/db_reader.md#onetl.db.db_reader.db_reader.DBReader) supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, -but does not support custom queries, like `JOIN`. - -#### WARNING -Please take into account [MySQL <-> Spark type mapping](types.md#mysql-types) - -## Supported DBReader features - -* ✅︎ `columns` -* ✅︎ `where` -* ✅︎ `hwm`, supported strategies: -* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) -* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) -* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) -* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) -* ✅︎ `hint` (see [official documentation](https://dev.mysql.com/doc/refman/en/optimizer-hints.html)) -* ❌ `df_schema` -* ✅︎ `options` (see [`MySQL.ReadOptions`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions)) - -## Examples - -Snapshot strategy: - -```python -from onetl.connection import MySQL -from onetl.db import DBReader - -mysql = MySQL(...) - -reader = DBReader( - connection=mysql, - source="schema.table", - columns=["id", "key", "CAST(value AS text) value", "updated_dt"], - where="key = 'something'", - hint="SKIP_SCAN(schema.table key_index)", - options=MySQL.ReadOptions(partitionColumn="id", numPartitions=10), -) -df = reader.run() -``` - -Incremental strategy: - -```python -from onetl.connection import MySQL -from onetl.db import DBReader -from onetl.strategy import IncrementalStrategy - -mysql = MySQL(...) - -reader = DBReader( - connection=mysql, - source="schema.table", - columns=["id", "key", "CAST(value AS text) value", "updated_dt"], - where="key = 'something'", - hint="SKIP_SCAN(schema.table key_index)", - hwm=DBReader.AutoDetectHWM(name="mysql_hwm", expression="updated_dt"), - options=MySQL.ReadOptions(partitionColumn="id", numPartitions=10), -) - -with IncrementalStrategy(): - df = reader.run() -``` - -## Recommendations - -### Select only required columns - -Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Oracle to Spark. - -### Pay attention to `where` value - -Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. -This both reduces the amount of data send from Oracle to Spark, and may also improve performance of the query. -Especially if there are indexes for columns used in `where` clause. - -## Options - -### *pydantic model* onetl.connection.db_connection.mysql.options.MySQLReadOptions - -Spark JDBC reading options. - -#### Versionadded -Added in version 0.5.0: Replace `MySQL.Options` → `MySQL.ReadOptions` - -### Examples - -#### NOTE -You can pass any value -[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), -even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! - -The set of supported options depends on Spark version. - -```python -from onetl.connection import MySQL - -options = MySQL.ReadOptions( - partitioning_mode="range", - partitionColumn="reg_id", - numPartitions=10, - customSparkOption="value", -) -``` - - -* **Fields:** - - [`fetchsize (int)`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.fetchsize) - - [`lower_bound (int | None)`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.lower_bound) - - [`num_partitions (pydantic.types.PositiveInt)`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.num_partitions) - - [`partition_column (str | None)`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.partition_column) - - [`partitioning_mode (onetl.connection.db_connection.jdbc_connection.options.JDBCPartitioningMode)`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.partitioning_mode) - - [`query_timeout (int | None)`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.query_timeout) - - [`session_init_statement (str | None)`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.session_init_statement) - - [`upper_bound (int | None)`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.upper_bound) - -#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* - -Column used to parallelize reading from a table. - -#### WARNING -It is highly recommended to use primary key, or column with an index -to avoid performance issues. - -#### NOTE -Column type depends on [`partitioning_mode`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.partitioning_mode). - -* `partitioning_mode="range"` requires column to be an integer, date or timestamp (can be NULL, but not recommended). -* `partitioning_mode="hash"` accepts any column type (NOT NULL). -* `partitioning_mode="mod"` requires column to be an integer (NOT NULL). - -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.partitioning_mode) for more details - - - -#### *field* num_partitions *: PositiveInt* *= 1* *(alias 'numPartitions')* - -Number of jobs created by Spark to read the table content in parallel. -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.partitioning_mode) for more details - - -* **Constraints:** - - **exclusiveMinimum** = 0 - -#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* - -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.partitioning_mode) for more details - - - -#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* - -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.partitioning_mode) for more details - - - -#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* - -After each database session is opened to the remote DB and before starting to read data, -this option executes a custom SQL statement (or a PL/SQL block). - -Use this to implement session initialization code. - -Example: - -```python -sessionInitStatement = """ - BEGIN - execute immediate - 'alter session set "_serial_direct_read"=true'; - END; -""" -``` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int* *= 100000* - -Fetch N rows from an opened cursor per one read round. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value is different from Spark. - -Spark uses driver’s own value, and it may be different in different drivers, -and even versions of the same driver. For example, Oracle has -default `fetchsize=10`, which is absolutely not usable. - -Thus we’ve overridden default value with `100_000`, which should increase reading performance. - -#### Versionchanged -Changed in version 0.2.0: Set explicit default value to `100_000` - - - -#### *field* partitioning_mode *: JDBCPartitioningMode* *= JDBCPartitioningMode.RANGE* - -Defines how Spark will parallelize reading from table. - -Possible values: - -* `range` (default) - : Allocate each executor a range of values from column passed into [`partition_column`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.partition_column). -
- ### Spark generates for each executor an SQL query -
- Executor 1: - ```sql - SELECT ... FROM table - WHERE (partition_column >= lowerBound - OR partition_column IS NULL) - AND partition_column < (lower_bound + stride) - ``` -
- Executor 2: - ```sql - SELECT ... FROM table - WHERE partition_column >= (lower_bound + stride) - AND partition_column < (lower_bound + 2 * stride) - ``` -
- … -
- Executor N: - ```sql - SELECT ... FROM table - WHERE partition_column >= (lower_bound + (N-1) * stride) - AND partition_column <= upper_bound - ``` -
- Where `stride=(upper_bound - lower_bound) / num_partitions`. -
- #### NOTE - Can be used only with columns of integer, date or timestamp types. -
- #### NOTE - [`lower_bound`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.lower_bound), [`upper_bound`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.upper_bound) and [`num_partitions`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.num_partitions) are used just to - calculate the partition stride, **NOT** for filtering the rows in table. - So all rows in the table will be returned (unlike *Incremental* [Read Strategies](../../../strategy/index.md#strategy)). -
- #### NOTE - All queries are executed in parallel. To execute them sequentially, use *Batch* [Read Strategies](../../../strategy/index.md#strategy). -* `hash` - : Allocate each executor a set of values based on hash of the [`partition_column`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.partition_column) column. -
- ### Spark generates for each executor an SQL query -
- Executor 1: - ```sql - SELECT ... FROM table - WHERE (some_hash(partition_column) mod num_partitions) = 0 -- lower_bound - ``` -
- Executor 2: - ```sql - SELECT ... FROM table - WHERE (some_hash(partition_column) mod num_partitions) = 1 -- lower_bound + 1 - ``` -
- … -
- Executor N: - ```sql - SELECT ... FROM table - WHERE (some_hash(partition_column) mod num_partitions) = num_partitions-1 -- upper_bound - ``` -
- #### NOTE - The hash function implementation depends on RDBMS. It can be `MD5` or any other fast hash function, - or expression based on this function call. Usually such functions accepts any column type as an input. -* `mod` - : Allocate each executor a set of values based on modulus of the [`partition_column`](#onetl.connection.db_connection.mysql.options.MySQLReadOptions.partition_column) column. -
- ### Spark generates for each executor an SQL query -
- Executor 1: - ```sql - SELECT ... FROM table - WHERE (partition_column mod num_partitions) = 0 -- lower_bound - ``` -
- Executor 2: - ```sql - SELECT ... FROM table - WHERE (partition_column mod num_partitions) = 1 -- lower_bound + 1 - ``` -
- Executor N: - ```sql - SELECT ... FROM table - WHERE (partition_column mod num_partitions) = num_partitions-1 -- upper_bound - ``` -
- #### NOTE - Can be used only with columns of integer type. - -#### Versionadded -Added in version 0.5.0. - -### Examples - -Read data in 10 parallel jobs by range of values in `id_column` column: - -```python -ReadOptions( - partitioning_mode="range", # default mode, can be omitted - partitionColumn="id_column", - numPartitions=10, - # Options below can be discarded because they are - # calculated automatically as MIN and MAX values of `partitionColumn` - lowerBound=0, - upperBound=100_000, -) -``` - -Read data in 10 parallel jobs by hash of values in `some_column` column: - -```python -ReadOptions( - partitioning_mode="hash", - partitionColumn="some_column", - numPartitions=10, - # lowerBound and upperBound are automatically set to `0` and `9` -) -``` - -Read data in 10 parallel jobs by modulus of values in `id_column` column: - -```python -ReadOptions( - partitioning_mode="mod", - partitionColumn="id_column", - numPartitions=10, - # lowerBound and upperBound are automatically set to `0` and `9` -) -``` - - diff --git a/mddocs/markdown/connection/db_connection/mysql/sql.md b/mddocs/markdown/connection/db_connection/mysql/sql.md deleted file mode 100644 index 2999cedd1..000000000 --- a/mddocs/markdown/connection/db_connection/mysql/sql.md +++ /dev/null @@ -1,192 +0,0 @@ - - -# Reading from MySQL using `MySQL.sql` - -`MySQL.sql` allows passing custom SQL query, but does not support incremental strategies. - -#### WARNING -Please take into account [MySQL <-> Spark type mapping](types.md#mysql-types) - -#### WARNING -Statement is executed in **read-write** connection, so if you’re calling some functions/procedures with DDL/DML statements inside, -they can change data in your database. - -## Syntax support - -Only queries with the following syntax are supported: - -* ✅︎ `SELECT ... FROM ...` -* ✅︎ `WITH alias AS (...) SELECT ...` -* ❌ `SHOW ...` -* ❌ `SET ...; SELECT ...;` - multiple statements not supported - -## Examples - -```python -from onetl.connection import MySQL - -mysql = MySQL(...) -df = mysql.sql( - """ - SELECT - id, - key, - CAST(value AS text) value, - updated_at - FROM - some.mytable - WHERE - key = 'something' - """, - options=MySQL.SQLOptions( - partitionColumn="id", - numPartitions=10, - lowerBound=0, - upperBound=1000, - ), -) -``` - -## Recommendations - -### Select only required columns - -Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. -This reduces the amount of data passed from MySQL to Spark. - -### Pay attention to `where` value - -Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. -This both reduces the amount of data send from MySQL to Spark, and may also improve performance of the query. -Especially if there are indexes or partitions for columns used in `where` clause. - -## Options - -### *pydantic model* onetl.connection.db_connection.mysql.options.MySQLSQLOptions - -Options specifically for SQL queries - -These options allow you to specify configurations for executing SQL queries -without relying on Spark’s partitioning mechanisms. - -#### Versionadded -Added in version 0.11.0: Split up `MySQL.ReadOptions` to `MySQL.SQLOptions` - -### Examples - -#### NOTE -You can pass any JDBC configuration -[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), -tailored to optimize SQL query execution. **Option names should be in** `camelCase`! - -```python -from onetl.connection import MySQL - -options = MySQL.SQLOptions( - partitionColumn="reg_id", - numPartitions=10, - lowerBound=0, - upperBound=1000, - customSparkOption="value", -) -``` - - - -#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* - -Column used to partition data across multiple executors for parallel query processing. - -#### WARNING -It is highly recommended to use primary key, or column with an index -to avoid performance issues. - -### Example of using `partitionColumn="id"` with `partitioning_mode="range"` - -```sql --- If partition_column is 'id', with numPartitions=4, lowerBound=1, and upperBound=100: --- Executor 1 processes IDs from 1 to 25 -SELECT ... FROM table WHERE id >= 1 AND id < 26 --- Executor 2 processes IDs from 26 to 50 -SELECT ... FROM table WHERE id >= 26 AND id < 51 --- Executor 3 processes IDs from 51 to 75 -SELECT ... FROM table WHERE id >= 51 AND id < 76 --- Executor 4 processes IDs from 76 to 100 -SELECT ... FROM table WHERE id >= 76 AND id <= 100 - --- General case for Executor N -SELECT ... FROM table -WHERE partition_column >= (lowerBound + (N-1) * stride) -AND partition_column <= upperBound --- Where ``stride`` is calculated as ``(upperBound - lowerBound) / numPartitions``. -``` - - - -#### *field* num_partitions *: int | None* *= None* *(alias 'numPartitions')* - -Number of jobs created by Spark to read the table content in parallel. - - - -#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* - -Defines the starting boundary for partitioning the query’s data. Mandatory if [`partition_column`](#onetl.connection.db_connection.mysql.options.MySQLSQLOptions.partition_column) is set - - - -#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* - -Sets the ending boundary for data partitioning. Mandatory if [`partition_column`](#onetl.connection.db_connection.mysql.options.MySQLSQLOptions.partition_column) is set - - - -#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* - -After each database session is opened to the remote DB and before starting to read data, -this option executes a custom SQL statement (or a PL/SQL block). - -Use this to implement session initialization code. - -Example: - -```python -sessionInitStatement = """ - BEGIN - execute immediate - 'alter session set "_serial_direct_read"=true'; - END; -""" -``` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int* *= 100000* - -Fetch N rows from an opened cursor per one read round. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value is different from Spark. - -Spark uses driver’s own value, and it may be different in different drivers, -and even versions of the same driver. For example, Oracle has -default `fetchsize=10`, which is absolutely not usable. - -Thus we’ve overridden default value with `100_000`, which should increase reading performance. - -#### Versionchanged -Changed in version 0.2.0: Set explicit default value to `100_000` - - diff --git a/mddocs/markdown/connection/db_connection/mysql/types.md b/mddocs/markdown/connection/db_connection/mysql/types.md deleted file mode 100644 index cd1e207a9..000000000 --- a/mddocs/markdown/connection/db_connection/mysql/types.md +++ /dev/null @@ -1,292 +0,0 @@ - - -# MySQL <-> Spark type mapping - -#### NOTE -The results below are valid for Spark 3.5.5, and may differ on other Spark versions. - -## Type detection & casting - -Spark’s DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. - -### Reading from MySQL - -This is how MySQL connector performs this: - -* For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and MySQL type. -* Find corresponding `MySQL type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. -* Create DataFrame from query with specific column names and Spark types. - -### Writing to some existing MySQL table - -This is how MySQL connector performs this: - -* Get names of columns in DataFrame. [1](#id2) -* Perform `SELECT * FROM table LIMIT 0` query. -* Take only columns present in DataFrame (by name, case insensitive). For each found column get MySQL type. -* Find corresponding `Spark type` → `MySQL type (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. -* If `MySQL type (write)` match `MySQL type (read)`, no additional casts will be performed, DataFrame column will be written to MySQL as is. -* If `MySQL type (write)` does not match `MySQL type (read)`, DataFrame column will be casted to target column type **on MySQL side**. For example, you can write column with text data to `int` column, if column contains valid integer values within supported value range and precision. - -* **[1]** This allows to write data to tables with `DEFAULT` and `GENERATED` columns - if DataFrame has no such column, it will be populated by MySQL. - -### Create new table using Spark - -#### WARNING -ABSOLUTELY NOT RECOMMENDED! - -This is how MySQL connector performs this: - -* Find corresponding `Spark type` → `MySQL type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. -* Generate DDL for creating table in MySQL, like `CREATE TABLE (col1 ...)`, and run it. -* Write DataFrame to created table as is. - -But some cases this may lead to using wrong column type. For example, Spark creates column of type `timestamp` -which corresponds to MySQL type `timestamp(0)` (precision up to seconds) -instead of more precise `timestamp(6)` (precision up to nanoseconds). -This may lead to incidental precision loss, or sometimes data cannot be written to created table at all. - -So instead of relying on Spark to create tables: - -### See example - -```python -writer = DBWriter( - connection=mysql, - target="myschema.target_tbl", - options=MySQL.WriteOptions( - if_exists="append", - createTableOptions="ENGINE = InnoDB", - ), -) -writer.run(df) -``` - -Always prefer creating tables with specific types **BEFORE WRITING DATA**: - -### See example - -```python -mysql.execute( - """ - CREATE TABLE schema.table ( - id bigint, - key text, - value timestamp(6) -- specific type and precision - ) - ENGINE = InnoDB - """, -) - -writer = DBWriter( - connection=mysql, - target="myschema.target_tbl", - options=MySQL.WriteOptions(if_exists="append"), -) -writer.run(df) -``` - -### References - -Here you can find source code with type conversions: - -* [MySQL -> JDBC](https://github.com/mysql/mysql-connector-j/blob/8.0.33/src/main/core-api/java/com/mysql/cj/MysqlType.java#L44-L623) -* [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MySQLDialect.scala#L104-L132) -* [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MySQLDialect.scala#L204-L211) -* [JDBC -> MySQL](https://github.com/mysql/mysql-connector-j/blob/8.0.33/src/main/core-api/java/com/mysql/cj/MysqlType.java#L625-L867) - -## Supported types - -See [official documentation](https://dev.mysql.com/doc/refman/en/data-types.html) - -### Numeric types - -| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | -|-----------------------------|----------------------------------|-----------------------------|-----------------------------| -| `decimal` | `DecimalType(P=10, S=0)` | `decimal(P=10, S=0)` | `decimal(P=10, S=0)` | -| `decimal(P=0..38)` | `DecimalType(P=0..38, S=0)` | `decimal(P=0..38, S=0)` | `decimal(P=0..38, S=0)` | -| `decimal(P=0..38, S=0..30)` | `DecimalType(P=0..38, S=0..30)` | `decimal(P=0..38, S=0..30)` | `decimal(P=0..38, S=0..30)` | -| `decimal(P=39..65, S=...)` | unsupported [2](#id4) | | | -| `float` | `DoubleType()` | `double` | `double` | -| `double` | | | | -| `tinyint` | `IntegerType()` | `int` | `int` | -| `smallint` | | | | -| `mediumint` | | | | -| `int` | | | | -| `bigint` | `LongType()` | `bigint` | `bigint` | -* **[2]** MySQL support decimal types with precision `P` up to 65. But Spark’s `DecimalType(P, S)` supports maximum `P=38`. It is impossible to read, write or operate with values of larger precision, this leads to an exception. - -### Temporal types - -| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | -|------------------------------|------------------------------------------------------------------------------------|------------------------------|-----------------------------------------------------------------------| -| `year` | `DateType()` | `date` | `date` | -| `date` | | | | -| `datetime`, seconds | `TimestampType()`, microseconds | `timestamp(6)`, microseconds | `timestamp(0)`, seconds | -| `timestamp`, seconds | | | | -| `datetime(0)`, seconds | | | | -| `timestamp(0)`, seconds | | | | -| `datetime(3)`, milliseconds | `TimestampType()`, microseconds | `timestamp(6)`, microseconds | `timestamp(0)`, seconds,
**precision loss** [4](#id9), | -| `timestamp(3)`, milliseconds | | | | -| `datetime(6)`, microseconds | | | | -| `timestamp(6)`, microseconds | | | | -| `time`, seconds | `TimestampType()`, microseconds,
with time format quirks [5](#id10) | `timestamp(6)`, microseconds | `timestamp(0)`, seconds | -| `time(0)`, seconds | | | | -| `time(3)`, milliseconds | `TimestampType()`, microseconds
with time format quirks [5](#id10) | `timestamp(6)`, microseconds | `timestamp(0)`, seconds,
**precision loss** [4](#id9), | -| `time(6)`, microseconds | | | | - -#### WARNING -Note that types in MySQL and Spark have different value ranges: - -| MySQL type | Min value | Max value | Spark type | Min value | Max value | -|--------------|------------------------------|------------------------------|-------------------|------------------------------|------------------------------| -| `year` | `1901` | `2155` | `DateType()` | `0001-01-01` | `9999-12-31` | -| `date` | `1000-01-01` | `9999-12-31` | | | | -| `datetime` | `1000-01-01 00:00:00.000000` | `9999-12-31 23:59:59.499999` | `TimestampType()` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | -| `timestamp` | `1970-01-01 00:00:01.000000` | `9999-12-31 23:59:59.499999` | | | | -| `time` | `-838:59:59.000000` | `838:59:59.000000` | | | | - -So Spark can read all the values from MySQL, but not all of values in Spark DataFrame can be written to MySQL. - -References: -: * [MySQL year documentation](https://dev.mysql.com/doc/refman/en/year.html) - * [MySQL date, datetime & timestamp documentation](https://dev.mysql.com/doc/refman/en/datetime.html) - * [MySQL time documentation](https://dev.mysql.com/doc/refman/en/time.html) - * [Spark DateType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/DateType.html) - * [Spark TimestampType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/TimestampType.html) - -* **[4]** MySQL dialect generates DDL with MySQL type `timestamp` which is alias for `timestamp(0)` with precision up to seconds (`23:59:59`). Inserting data with microseconds precision (`23:59:59.999999`) will lead to **throwing away microseconds**. -* **[5]** `time` type is the same as `timestamp` with date `1970-01-01`. So instead of reading data from MySQL like `23:59:59` it is actually read `1970-01-01 23:59:59`, and vice versa. - -### String types - -| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | -|-----------------------------|----------------|----------------------|-----------------------| -| `char` | `StringType()` | `longtext` | `longtext` | -| `char(N)` | | | | -| `varchar(N)` | | | | -| `mediumtext` | | | | -| `text` | | | | -| `longtext` | | | | -| `json` | | | | -| `enum("val1", "val2", ...)` | | | | -| `set("val1", "val2", ...)` | | | | - -### Binary types - -| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | -|---------------------|----------------|----------------------|-----------------------| -| `binary` | `BinaryType()` | `blob` | `blob` | -| `binary(N)` | | | | -| `varbinary(N)` | | | | -| `mediumblob` | | | | -| `blob` | | | | -| `longblob` | | | | - -### Geometry types - -| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | -|----------------------|----------------|----------------------|-----------------------| -| `point` | `BinaryType()` | `blob` | `blob` | -| `linestring` | | | | -| `polygon` | | | | -| `geometry` | | | | -| `multipoint` | | | | -| `multilinestring` | | | | -| `multipolygon` | | | | -| `geometrycollection` | | | | - -## Explicit type cast - -### `DBReader` - -It is possible to explicitly cast column type using `DBReader(columns=...)` syntax. - -For example, you can use `CAST(column AS text)` to convert data to string representation on MySQL side, and so it will be read as Spark’s `StringType()`. - -It is also possible to use [JSON_OBJECT](https://dev.mysql.com/doc/refman/en/json.html) MySQL function and parse JSON columns in MySQL with the [`JSON.parse_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.parse_column) method. - -```python -from pyspark.sql.types import IntegerType, StructType, StructField - -from onetl.connection import MySQL -from onetl.db import DBReader -from onetl.file.format import JSON - -mysql = MySQL(...) - -DBReader( - connection=mysql, - columns=[ - "id", - "supported_column", - "CAST(unsupported_column AS text) unsupported_column_str", - # or - "JSON_OBJECT('key', value_column) json_column", - ], -) -df = reader.run() - -json_scheme = StructType([StructField("key", IntegerType())]) - -df = df.select( - df.id, - df.supported_column, - # explicit cast - df.unsupported_column_str.cast("integer").alias("parsed_integer"), - JSON().parse_column("json_column", json_scheme).alias("struct_column"), -) -``` - -### `DBWriter` - -To write JSON data to a `json` or `text` column in a MySQL table, use the [`JSON.serialize_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.serialize_column) method. - -```python -from onetl.connection import MySQL -from onetl.db import DBWriter -from onetl.file.format import JSON - -mysql.execute( - """ - CREATE TABLE schema.target_tbl ( - id bigint, - array_column_json json -- any string type, actually - ) - ENGINE = InnoDB - """, -) - -df = df.select( - df.id, - JSON().serialize_column(df.array_column).alias("array_column_json"), -) - -writer.run(df) -``` - -Then you can parse this column on MySQL side - for example, by creating a view: - -```sql -SELECT - id, - array_column_json->"$[0]" AS array_item -FROM target_tbl -``` - -Or by using [GENERATED column](https://dev.mysql.com/doc/refman/en/create-table-generated-columns.html): - -```sql -CREATE TABLE schema.target_table ( - id bigint, - supported_column timestamp, - array_column_json json, -- any string type, actually - -- virtual column - array_item_0 GENERATED ALWAYS AS (array_column_json->"$[0]")) VIRTUAL - -- or stired column - -- array_item_0 GENERATED ALWAYS AS (array_column_json->"$[0]")) STORED -) -``` - -`VIRTUAL` column value is calculated on every table read. -`STORED` column value is calculated during insert, but this require additional space. diff --git a/mddocs/markdown/connection/db_connection/mysql/write.md b/mddocs/markdown/connection/db_connection/mysql/write.md deleted file mode 100644 index 239aacfdb..000000000 --- a/mddocs/markdown/connection/db_connection/mysql/write.md +++ /dev/null @@ -1,187 +0,0 @@ - - -# Writing to MySQL using `DBWriter` - -For writing data to MySQL, use [`DBWriter`](../../../db/db_writer.md#onetl.db.db_writer.db_writer.DBWriter). - -#### WARNING -Please take into account [MySQL <-> Spark type mapping](types.md#mysql-types) - -#### WARNING -It is always recommended to create table explicitly using [MySQL.execute](execute.md#mysql-execute) -instead of relying on Spark’s table DDL generation. - -This is because Spark’s DDL generator can create columns with different precision and types than it is expected, -causing precision loss or other issues. - -## Examples - -```python -from onetl.connection import MySQL -from onetl.db import DBWriter - -mysql = MySQL(...) - -df = ... # data is here - -writer = DBWriter( - connection=mysql, - target="schema.table", - options=MySQL.WriteOptions( - if_exists="append", - # ENGINE is required by MySQL - createTableOptions="ENGINE = MergeTree() ORDER BY id", - ), -) - -writer.run(df) -``` - -## Options - -Method above accepts [`MySQL.WriteOptions`](#onetl.connection.db_connection.mysql.options.MySQLWriteOptions) - -### *pydantic model* onetl.connection.db_connection.mysql.options.MySQLWriteOptions - -Spark JDBC writing options. - -#### Versionadded -Added in version 0.5.0: Replace `MySQL.Options` → `MySQL.WriteOptions` - -### Examples - -#### NOTE -You can pass any value -[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), -even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! - -The set of supported options depends on Spark version. - -```python -from onetl.connection import MySQL - -options = MySQL.WriteOptions( - if_exists="append", - batchsize=20_000, - customSparkOption="value", -) -``` - - - -#### *field* if_exists *: JDBCTableExistBehavior* *= JDBCTableExistBehavior.APPEND* *(alias 'mode')* - -Behavior of writing data into existing table. - -Possible values: -: * `append` (default) - : Adds new rows into existing table. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : Data is appended to a table. Table has the same DDL as before writing data -
- #### WARNING - This mode does not check whether table already contains - rows from dataframe, so duplicated rows can be created. -
- Also Spark does not support passing custom options to - insert statement, like `ON CONFLICT`, so don’t try to - implement deduplication using unique indexes or constraints. -
- Instead, write to staging table and perform deduplication - using `execute` method. - * `replace_entire_table` - : **Table is dropped and then created, or truncated**. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : Table content is replaced with dataframe content. -
- After writing completed, target table could either have the same DDL as - before writing data (`truncate=True`), or can be recreated (`truncate=False` - or source does not support truncation). - * `ignore` - : Ignores the write operation if the table already exists. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : The write operation is ignored, and no data is written to the table. - * `error` - : Raises an error if the table already exists. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : An error is raised, and no data is written to the table. - -#### Versionchanged -Changed in version 0.9.0: Renamed `mode` → `if_exists` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* batchsize *: int* *= 20000* - -How many rows can be inserted per round trip. - -Tuning this option can influence performance of writing. - -#### WARNING -Default value is different from Spark. - -Spark uses quite small value `1000`, which is absolutely not usable -in BigData world. - -Thus we’ve overridden default value with `20_000`, -which should increase writing performance. - -You can increase it even more, up to `50_000`, -but it depends on your database load and number of columns in the row. -Higher values does not increase performance. - -#### Versionchanged -Changed in version 0.4.0: Changed default value from 1000 to 20_000 - - - -#### *field* isolation_level *: str* *= 'READ_UNCOMMITTED'* *(alias 'isolationLevel')* - -The transaction isolation level, which applies to current connection. - -Possible values: -: * `NONE` (as string, not Python’s `None`) - * `READ_COMMITTED` - * `READ_UNCOMMITTED` - * `REPEATABLE_READ` - * `SERIALIZABLE` - -Values correspond to transaction isolation levels defined by JDBC standard. -Please refer the documentation for -[java.sql.Connection](https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html). - - diff --git a/mddocs/markdown/connection/db_connection/oracle/connection.md b/mddocs/markdown/connection/db_connection/oracle/connection.md deleted file mode 100644 index 6171f3a50..000000000 --- a/mddocs/markdown/connection/db_connection/oracle/connection.md +++ /dev/null @@ -1,143 +0,0 @@ - - -# Oracle connection - -### *class* onetl.connection.db_connection.oracle.connection.Oracle(\*, spark: SparkSession, user: str, password: SecretStr, host: Host, port: int = 1521, sid: str | None = None, service_name: str | None = None, extra: OracleExtra = OracleExtra()) - -Oracle JDBC connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on Maven package [com.oracle.database.jdbc:ojdbc8:23.7.0.25.01](https://mvnrepository.com/artifact/com.oracle.database.jdbc/ojdbc8/23.7.0.25.01) -([official Oracle JDBC driver](https://www.oracle.com/cis/database/technologies/appdev/jdbc-downloads.html)). - -#### SEE ALSO -Before using this connector please take into account [Prerequisites](prerequisites.md#oracle-prerequisites) - -#### Versionadded -Added in version 0.1.0. - -* **Parameters:** - **host** - : Host of Oracle database. For example: `test.oracle.domain.com` or `193.168.1.10` - - **port** - : Port of Oracle database - - **user** - : User, which have proper access to the database. For example: `SOME_USER` - - **password** - : Password for database connection - - **sid** - : Sid of oracle database. For example: `XE` -
- #### WARNING - You should provide either `sid` or `service_name`, not both of them - - **service_name** - : Specifies one or more names by which clients can connect to the instance. -
- For example: `PDB1`. -
- #### WARNING - You should provide either `sid` or `service_name`, not both of them - - **spark** - : Spark session. - - **extra** - : Specifies one or more extra parameters by which clients can connect to the instance. -
- For example: `{"remarksReporting": "false"}` -
- See official documentation: - : * [Connection parameters](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleDriver.html) - * [Connection properties](https://docs.oracle.com/cd/A97335_02/apps.102/a83724/basic1.htm#1024018) - -### Examples - -Create and check Oracle connection with `sid`: - -```python -from onetl.connection import Oracle -from pyspark.sql import SparkSession - -# Create Spark session with Oracle driver loaded -maven_packages = Oracle.get_packages() -spark = ( - SparkSession.builder.appName("spark-app-name") - .config("spark.jars.packages", ",".join(maven_packages)) - .getOrCreate() -) - -# Create connection -oracle = Oracle( - host="database.host.or.ip", - user="user", - password="*****", - sid="XE", - extra={"remarksReporting": "false"}, - spark=spark, -).check() -``` - -or with `service_name`: - -```python -... - -oracle = Oracle( - host="database.host.or.ip", - user="user", - password="*****", - service_name="PDB1", # <--- instead of SID - extra={"remarksReporting": "false"}, - spark=spark, -).check() -``` - - - -#### check() - -Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If not, an exception will be raised. - -* **Returns:** - Connection itself -* **Raises:** - RuntimeError - : If the connection is not available - -### Examples - -```python -connection.check() -``` - - - -#### *classmethod* get_packages(java_version: str | None = None, package_version: str | None = None) → list[str] - -Get package names to be downloaded by Spark. Allows specifying custom JDBC driver versions for Oracle. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -* **Parameters:** - **java_version** - : Java major version, defaults to “8”. Must be “8” or “11”. - - **package_version** - : Specifies the version of the Oracle JDBC driver to use. Defaults to “23.7.0.25.01”. - -### Examples - -```python -from onetl.connection import Oracle - -Oracle.get_packages() - -# specify Java and package versions -Oracle.get_packages(java_version="8", package_version="23.7.0.25.01") -``` - - diff --git a/mddocs/markdown/connection/db_connection/oracle/execute.md b/mddocs/markdown/connection/db_connection/oracle/execute.md deleted file mode 100644 index 29293882b..000000000 --- a/mddocs/markdown/connection/db_connection/oracle/execute.md +++ /dev/null @@ -1,191 +0,0 @@ - - -# Executing statements in Oracle - -#### WARNING -Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. - -Do **NOT** use them to read large amounts of data. Use [DBReader](read.md#oracle-read) or [Oracle.sql](sql.md#oracle-sql) instead. - -## How to - -There are 2 ways to execute some statement in Oracle - -### Use `Oracle.fetch` - -Use this method to execute some `SELECT` query which returns **small number or rows**, like reading -Oracle config, or reading data from some reference table. Method returns Spark DataFrame. - -Method accepts [`Oracle.FetchOptions`](#onetl.connection.db_connection.oracle.options.OracleFetchOptions). - -Connection opened using this method should be then closed with `connection.close()` or `with connection:`. - -#### WARNING -Please take into account [Oracle <-> Spark type mapping](types.md#oracle-types). - -#### Syntax support - -This method supports **any** query syntax supported by Oracle, like: - -* ✅︎ `SELECT ... FROM ...` -* ✅︎ `WITH alias AS (...) SELECT ...` -* ✅︎ `SELECT func(arg1, arg2) FROM DUAL` - call function -* ✅︎ `SHOW ...` -* ❌ `SET ...; SELECT ...;` - multiple statements not supported - -#### Examples - -```python -from onetl.connection import Oracle - -oracle = Oracle(...) - -df = oracle.fetch( - "SELECT value FROM some.reference_table WHERE key = 'some_constant'", - options=Oracle.FetchOptions(queryTimeout=10), -) -oracle.close() -value = df.collect()[0][0] # get value from first row and first column -``` - -### Use `Oracle.execute` - -Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. - -Method accepts [`Oracle.ExecuteOptions`](#onetl.connection.db_connection.oracle.options.OracleExecuteOptions). - -Connection opened using this method should be then closed with `connection.close()` or `with connection:`. - -#### Syntax support - -This method supports **any** query syntax supported by Oracle, like: - -* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...` -* ✅︎ `ALTER ...` -* ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on -* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on -* ✅︎ `CALL procedure(arg1, arg2) ...` or `{call procedure(arg1, arg2)}` - special syntax for calling procedure -* ✅︎ `DECLARE ... BEGIN ... END` - execute PL/SQL statement -* ✅︎ other statements not mentioned here -* ❌ `SET ...; SELECT ...;` - multiple statements not supported - -#### Examples - -```python -from onetl.connection import Oracle - -oracle = Oracle(...) - -oracle.execute("DROP TABLE schema.table") -oracle.execute( - """ - CREATE TABLE schema.table ( - id bigint GENERATED ALWAYS AS IDENTITY, - key VARCHAR2(4000), - value NUMBER - ) - """, - options=Oracle.ExecuteOptions(queryTimeout=10), -) -``` - -## Options - -### *pydantic model* onetl.connection.db_connection.oracle.options.OracleFetchOptions - -Options related to fetching data from databases via JDBC. - -#### Versionadded -Added in version 0.11.0: Replace `Oracle.JDBCOptions` → `Oracle.FetchOptions` - -### Examples - -#### NOTE -You can pass any value supported by underlying JDBC driver class, -even if it is not mentioned in this documentation. - -```python -from onetl.connection import Oracle - -options = Oracle.FetchOptions( - queryTimeout=60_000, - fetchsize=100_000, - customSparkOption="value", -) -``` - - -* **Fields:** - - [`fetchsize (int | None)`](#onetl.connection.db_connection.oracle.options.OracleFetchOptions.fetchsize) - - [`query_timeout (int | None)`](#onetl.connection.db_connection.oracle.options.OracleFetchOptions.query_timeout) - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int | None* *= None* - -How many rows to fetch per round trip. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value depends on driver. For example, Oracle has -default `fetchsize=10`. - - - -### *pydantic model* onetl.connection.db_connection.oracle.options.OracleExecuteOptions - -Options related to executing statements in databases via JDBC. - -#### Versionadded -Added in version 0.11.0: Replace `Oracle.JDBCOptions` → `Oracle.ExecuteOptions` - -### Examples - -#### NOTE -You can pass any value supported by underlying JDBC driver class, -even if it is not mentioned in this documentation. - -```python -from onetl.connection import Oracle - -options = Oracle.ExecuteOptions( - queryTimeout=60_000, - customSparkOption="value", -) -``` - - -* **Fields:** - - [`fetchsize (int | None)`](#onetl.connection.db_connection.oracle.options.OracleExecuteOptions.fetchsize) - - [`query_timeout (int | None)`](#onetl.connection.db_connection.oracle.options.OracleExecuteOptions.query_timeout) - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int | None* *= None* - -How many rows to fetch per round trip. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value depends on driver. For example, Oracle has -default `fetchsize=10`. - - diff --git a/mddocs/markdown/connection/db_connection/oracle/index.md b/mddocs/markdown/connection/db_connection/oracle/index.md deleted file mode 100644 index 39987fb24..000000000 --- a/mddocs/markdown/connection/db_connection/oracle/index.md +++ /dev/null @@ -1,19 +0,0 @@ - - -# Oracle - -# Connection - -* [Prerequisites](prerequisites.md) -* [Oracle connection](connection.md) - -# Operations - -* [Reading from Oracle using `DBReader`](read.md) -* [Reading from Oracle using `Oracle.sql`](sql.md) -* [Writing to Oracle using `DBWriter`](write.md) -* [Executing statements in Oracle](execute.md) - -# Troubleshooting - -* [Oracle <-> Spark type mapping](types.md) diff --git a/mddocs/markdown/connection/db_connection/oracle/prerequisites.md b/mddocs/markdown/connection/db_connection/oracle/prerequisites.md deleted file mode 100644 index 6dff632ce..000000000 --- a/mddocs/markdown/connection/db_connection/oracle/prerequisites.md +++ /dev/null @@ -1,110 +0,0 @@ - - -# Prerequisites - -## Version Compatibility - -* Oracle Server versions: - : * Officially declared: 19c, 21c, 23ai - * Actually tested: 11.2, 23.5 -* Spark versions: 2.3.x - 3.5.x -* Java versions: 8 - 20 - -See [official documentation](https://www.oracle.com/cis/database/technologies/appdev/jdbc-downloads.html). - -## Installing PySpark - -To use Oracle connector you should have PySpark installed (or injected to `sys.path`) -BEFORE creating the connector instance. - -See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. - -## Connecting to Oracle - -### Connection port - -Connection is usually performed to port 1521. Port may differ for different Oracle instances. -Please ask your Oracle administrator to provide required information. - -### Connection host - -It is possible to connect to Oracle by using either DNS name of host or it’s IP address. - -If you’re using Oracle cluster, it is currently possible to connect only to **one specific node**. -Connecting to multiple nodes to perform load balancing, as well as automatic failover to new master/replica are not supported. - -### Connect as proxy user - -It is possible to connect to database as another user without knowing this user password. - -This can be enabled by granting user a special `CONNECT THROUGH` permission: - -```sql -ALTER USER schema_owner GRANT CONNECT THROUGH proxy_user; -``` - -Then you can connect to Oracle using credentials of `proxy_user` but specify that you need permissions of `schema_owner`: - -```python -oracle = Oracle( - ..., - user="proxy_user[schema_owner]", - password="proxy_user password", -) -``` - -See [official documentation](https://oracle-base.com/articles/misc/proxy-users-and-connect-through). - -### Required grants - -Ask your Oracle cluster administrator to set following grants for a user, -used for creating a connection: - -Read + Write (schema is owned by user) - -```sql --- allow user to log in -GRANT CREATE SESSION TO username; - --- allow creating tables in user schema -GRANT CREATE TABLE TO username; - --- allow read & write access to specific table -GRANT SELECT, INSERT ON username.mytable TO username; -``` - -Read + Write (schema is not owned by user) - -```sql --- allow user to log in -GRANT CREATE SESSION TO username; - --- allow creating tables in any schema, --- as Oracle does not support specifying exact schema name -GRANT CREATE ANY TABLE TO username; - --- allow read & write access to specific table -GRANT SELECT, INSERT ON someschema.mytable TO username; - --- only if if_exists="replace_entire_table" is used: --- allow dropping/truncating tables in any schema, --- as Oracle does not support specifying exact schema name -GRANT DROP ANY TABLE TO username; -``` - -Read only - -```sql --- allow user to log in -GRANT CREATE SESSION TO username; - --- allow read access to specific table -GRANT SELECT ON someschema.mytable TO username; -``` - -More details can be found in official documentation: -: * [GRANT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/GRANT.html) - * [SELECT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/SELECT.html) - * [CREATE TABLE](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/SELECT.html) - * [INSERT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/INSERT.html) - * [TRUNCATE TABLE](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/TRUNCATE-TABLE.html) diff --git a/mddocs/markdown/connection/db_connection/oracle/read.md b/mddocs/markdown/connection/db_connection/oracle/read.md deleted file mode 100644 index fbb28c207..000000000 --- a/mddocs/markdown/connection/db_connection/oracle/read.md +++ /dev/null @@ -1,352 +0,0 @@ - - -# Reading from Oracle using `DBReader` - -[`DBReader`](../../../db/db_reader.md#onetl.db.db_reader.db_reader.DBReader) supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, -but does not support custom queries, like `JOIN`. - -#### WARNING -Please take into account [Oracle <-> Spark type mapping](types.md#oracle-types) - -## Supported DBReader features - -* ✅︎ `columns` -* ✅︎ `where` -* ✅︎ `hwm`, supported strategies: -* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) -* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) -* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) -* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) -* ✅︎ `hint` (see [official documentation](https://docs.oracle.com/cd/B10500_01/server.920/a96533/hintsref.htm)) -* ❌ `df_schema` -* ✅︎ `options` (see [`Oracle.ReadOptions`](#onetl.connection.db_connection.oracle.options.OracleReadOptions)) - -## Examples - -Snapshot strategy: - -```python -from onetl.connection import Oracle -from onetl.db import DBReader - -oracle = Oracle(...) - -reader = DBReader( - connection=oracle, - source="schema.table", - columns=["id", "key", "CAST(value AS VARCHAR2(4000)) value", "updated_dt"], - where="key = 'something'", - hint="INDEX(schema.table key_index)", - options=Oracle.ReadOptions(partitionColumn="id", numPartitions=10), -) -df = reader.run() -``` - -Incremental strategy: - -```python -from onetl.connection import Oracle -from onetl.db import DBReader -from onetl.strategy import IncrementalStrategy - -oracle = Oracle(...) - -reader = DBReader( - connection=oracle, - source="schema.table", - columns=["id", "key", "CAST(value AS VARCHAR2(4000)) value", "updated_dt"], - where="key = 'something'", - hint="INDEX(schema.table key_index)", - hwm=DBReader.AutoDetectHWM(name="oracle_hwm", expression="updated_dt"), - options=Oracle.ReadOptions(partitionColumn="id", numPartitions=10), -) - -with IncrementalStrategy(): - df = reader.run() -``` - -## Recommendations - -### Select only required columns - -Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Oracle to Spark. - -### Pay attention to `where` value - -Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. -This both reduces the amount of data send from Oracle to Spark, and may also improve performance of the query. -Especially if there are indexes or partitions for columns used in `where` clause. - -## Options - -### *pydantic model* onetl.connection.db_connection.oracle.options.OracleReadOptions - -Spark JDBC reading options. - -#### Versionadded -Added in version 0.5.0: Replace `Oracle.Options` → `Oracle.ReadOptions` - -### Examples - -#### NOTE -You can pass any value -[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), -even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! - -The set of supported options depends on Spark version. - -```python -from onetl.connection import Oracle - -options = Oracle.ReadOptions( - partitioning_mode="range", - partitionColumn="reg_id", - numPartitions=10, - customSparkOption="value", -) -``` - - -* **Fields:** - - [`fetchsize (int)`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.fetchsize) - - [`lower_bound (int | None)`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.lower_bound) - - [`num_partitions (pydantic.types.PositiveInt)`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.num_partitions) - - [`partition_column (str | None)`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.partition_column) - - [`partitioning_mode (onetl.connection.db_connection.jdbc_connection.options.JDBCPartitioningMode)`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.partitioning_mode) - - [`query_timeout (int | None)`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.query_timeout) - - [`session_init_statement (str | None)`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.session_init_statement) - - [`upper_bound (int | None)`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.upper_bound) - -#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* - -Column used to parallelize reading from a table. - -#### WARNING -It is highly recommended to use primary key, or column with an index -to avoid performance issues. - -#### NOTE -Column type depends on [`partitioning_mode`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.partitioning_mode). - -* `partitioning_mode="range"` requires column to be an integer, date or timestamp (can be NULL, but not recommended). -* `partitioning_mode="hash"` accepts any column type (NOT NULL). -* `partitioning_mode="mod"` requires column to be an integer (NOT NULL). - -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.partitioning_mode) for more details - - - -#### *field* num_partitions *: PositiveInt* *= 1* *(alias 'numPartitions')* - -Number of jobs created by Spark to read the table content in parallel. -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.partitioning_mode) for more details - - -* **Constraints:** - - **exclusiveMinimum** = 0 - -#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* - -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.partitioning_mode) for more details - - - -#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* - -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.partitioning_mode) for more details - - - -#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* - -After each database session is opened to the remote DB and before starting to read data, -this option executes a custom SQL statement (or a PL/SQL block). - -Use this to implement session initialization code. - -Example: - -```python -sessionInitStatement = """ - BEGIN - execute immediate - 'alter session set "_serial_direct_read"=true'; - END; -""" -``` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int* *= 100000* - -Fetch N rows from an opened cursor per one read round. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value is different from Spark. - -Spark uses driver’s own value, and it may be different in different drivers, -and even versions of the same driver. For example, Oracle has -default `fetchsize=10`, which is absolutely not usable. - -Thus we’ve overridden default value with `100_000`, which should increase reading performance. - -#### Versionchanged -Changed in version 0.2.0: Set explicit default value to `100_000` - - - -#### *field* partitioning_mode *: JDBCPartitioningMode* *= JDBCPartitioningMode.RANGE* - -Defines how Spark will parallelize reading from table. - -Possible values: - -* `range` (default) - : Allocate each executor a range of values from column passed into [`partition_column`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.partition_column). -
- ### Spark generates for each executor an SQL query -
- Executor 1: - ```sql - SELECT ... FROM table - WHERE (partition_column >= lowerBound - OR partition_column IS NULL) - AND partition_column < (lower_bound + stride) - ``` -
- Executor 2: - ```sql - SELECT ... FROM table - WHERE partition_column >= (lower_bound + stride) - AND partition_column < (lower_bound + 2 * stride) - ``` -
- … -
- Executor N: - ```sql - SELECT ... FROM table - WHERE partition_column >= (lower_bound + (N-1) * stride) - AND partition_column <= upper_bound - ``` -
- Where `stride=(upper_bound - lower_bound) / num_partitions`. -
- #### NOTE - Can be used only with columns of integer, date or timestamp types. -
- #### NOTE - [`lower_bound`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.lower_bound), [`upper_bound`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.upper_bound) and [`num_partitions`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.num_partitions) are used just to - calculate the partition stride, **NOT** for filtering the rows in table. - So all rows in the table will be returned (unlike *Incremental* [Read Strategies](../../../strategy/index.md#strategy)). -
- #### NOTE - All queries are executed in parallel. To execute them sequentially, use *Batch* [Read Strategies](../../../strategy/index.md#strategy). -* `hash` - : Allocate each executor a set of values based on hash of the [`partition_column`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.partition_column) column. -
- ### Spark generates for each executor an SQL query -
- Executor 1: - ```sql - SELECT ... FROM table - WHERE (some_hash(partition_column) mod num_partitions) = 0 -- lower_bound - ``` -
- Executor 2: - ```sql - SELECT ... FROM table - WHERE (some_hash(partition_column) mod num_partitions) = 1 -- lower_bound + 1 - ``` -
- … -
- Executor N: - ```sql - SELECT ... FROM table - WHERE (some_hash(partition_column) mod num_partitions) = num_partitions-1 -- upper_bound - ``` -
- #### NOTE - The hash function implementation depends on RDBMS. It can be `MD5` or any other fast hash function, - or expression based on this function call. Usually such functions accepts any column type as an input. -* `mod` - : Allocate each executor a set of values based on modulus of the [`partition_column`](#onetl.connection.db_connection.oracle.options.OracleReadOptions.partition_column) column. -
- ### Spark generates for each executor an SQL query -
- Executor 1: - ```sql - SELECT ... FROM table - WHERE (partition_column mod num_partitions) = 0 -- lower_bound - ``` -
- Executor 2: - ```sql - SELECT ... FROM table - WHERE (partition_column mod num_partitions) = 1 -- lower_bound + 1 - ``` -
- Executor N: - ```sql - SELECT ... FROM table - WHERE (partition_column mod num_partitions) = num_partitions-1 -- upper_bound - ``` -
- #### NOTE - Can be used only with columns of integer type. - -#### Versionadded -Added in version 0.5.0. - -### Examples - -Read data in 10 parallel jobs by range of values in `id_column` column: - -```python -ReadOptions( - partitioning_mode="range", # default mode, can be omitted - partitionColumn="id_column", - numPartitions=10, - # Options below can be discarded because they are - # calculated automatically as MIN and MAX values of `partitionColumn` - lowerBound=0, - upperBound=100_000, -) -``` - -Read data in 10 parallel jobs by hash of values in `some_column` column: - -```python -ReadOptions( - partitioning_mode="hash", - partitionColumn="some_column", - numPartitions=10, - # lowerBound and upperBound are automatically set to `0` and `9` -) -``` - -Read data in 10 parallel jobs by modulus of values in `id_column` column: - -```python -ReadOptions( - partitioning_mode="mod", - partitionColumn="id_column", - numPartitions=10, - # lowerBound and upperBound are automatically set to `0` and `9` -) -``` - - diff --git a/mddocs/markdown/connection/db_connection/oracle/sql.md b/mddocs/markdown/connection/db_connection/oracle/sql.md deleted file mode 100644 index 09059e7aa..000000000 --- a/mddocs/markdown/connection/db_connection/oracle/sql.md +++ /dev/null @@ -1,192 +0,0 @@ - - -# Reading from Oracle using `Oracle.sql` - -`Oracle.sql` allows passing custom SQL query, but does not support incremental strategies. - -#### WARNING -Please take into account [Oracle <-> Spark type mapping](types.md#oracle-types) - -#### WARNING -Statement is executed in **read-write** connection, so if you’re calling some functions/procedures with DDL/DML statements inside, -they can change data in your database. - -## Syntax support - -Only queries with the following syntax are supported: - -* ✅︎ `SELECT ... FROM ...` -* ✅︎ `WITH alias AS (...) SELECT ...` -* ❌ `SHOW ...` -* ❌ `SET ...; SELECT ...;` - multiple statements not supported - -## Examples - -```python -from onetl.connection import Oracle - -oracle = Oracle(...) -df = oracle.sql( - """ - SELECT - id, - key, - CAST(value AS VARCHAR2(4000)) value, - updated_at - FROM - some.mytable - WHERE - key = 'something' - """, - options=Oracle.SQLOptions( - partitionColumn="id", - numPartitions=10, - lowerBound=0, - upperBound=1000, - ), -) -``` - -## Recommendations - -### Select only required columns - -Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. -This reduces the amount of data passed from Oracle to Spark. - -### Pay attention to `where` value - -Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. -This both reduces the amount of data send from Oracle to Spark, and may also improve performance of the query. -Especially if there are indexes or partitions for columns used in `where` clause. - -## Options - -### *pydantic model* onetl.connection.db_connection.oracle.options.OracleSQLOptions - -Options specifically for SQL queries - -These options allow you to specify configurations for executing SQL queries -without relying on Spark’s partitioning mechanisms. - -#### Versionadded -Added in version 0.11.0: Split up `Oracle.ReadOptions` to `Oracle.SQLOptions` - -### Examples - -#### NOTE -You can pass any JDBC configuration -[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), -tailored to optimize SQL query execution. **Option names should be in** `camelCase`! - -```python -from onetl.connection import Oracle - -options = Oracle.SQLOptions( - partitionColumn="reg_id", - numPartitions=10, - lowerBound=0, - upperBound=1000, - customSparkOption="value", -) -``` - - - -#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* - -Column used to partition data across multiple executors for parallel query processing. - -#### WARNING -It is highly recommended to use primary key, or column with an index -to avoid performance issues. - -### Example of using `partitionColumn="id"` with `partitioning_mode="range"` - -```sql --- If partition_column is 'id', with numPartitions=4, lowerBound=1, and upperBound=100: --- Executor 1 processes IDs from 1 to 25 -SELECT ... FROM table WHERE id >= 1 AND id < 26 --- Executor 2 processes IDs from 26 to 50 -SELECT ... FROM table WHERE id >= 26 AND id < 51 --- Executor 3 processes IDs from 51 to 75 -SELECT ... FROM table WHERE id >= 51 AND id < 76 --- Executor 4 processes IDs from 76 to 100 -SELECT ... FROM table WHERE id >= 76 AND id <= 100 - --- General case for Executor N -SELECT ... FROM table -WHERE partition_column >= (lowerBound + (N-1) * stride) -AND partition_column <= upperBound --- Where ``stride`` is calculated as ``(upperBound - lowerBound) / numPartitions``. -``` - - - -#### *field* num_partitions *: int | None* *= None* *(alias 'numPartitions')* - -Number of jobs created by Spark to read the table content in parallel. - - - -#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* - -Defines the starting boundary for partitioning the query’s data. Mandatory if [`partition_column`](#onetl.connection.db_connection.oracle.options.OracleSQLOptions.partition_column) is set - - - -#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* - -Sets the ending boundary for data partitioning. Mandatory if [`partition_column`](#onetl.connection.db_connection.oracle.options.OracleSQLOptions.partition_column) is set - - - -#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* - -After each database session is opened to the remote DB and before starting to read data, -this option executes a custom SQL statement (or a PL/SQL block). - -Use this to implement session initialization code. - -Example: - -```python -sessionInitStatement = """ - BEGIN - execute immediate - 'alter session set "_serial_direct_read"=true'; - END; -""" -``` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int* *= 100000* - -Fetch N rows from an opened cursor per one read round. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value is different from Spark. - -Spark uses driver’s own value, and it may be different in different drivers, -and even versions of the same driver. For example, Oracle has -default `fetchsize=10`, which is absolutely not usable. - -Thus we’ve overridden default value with `100_000`, which should increase reading performance. - -#### Versionchanged -Changed in version 0.2.0: Set explicit default value to `100_000` - - diff --git a/mddocs/markdown/connection/db_connection/oracle/types.md b/mddocs/markdown/connection/db_connection/oracle/types.md deleted file mode 100644 index 130c57855..000000000 --- a/mddocs/markdown/connection/db_connection/oracle/types.md +++ /dev/null @@ -1,303 +0,0 @@ - - -# Oracle <-> Spark type mapping - -#### NOTE -The results below are valid for Spark 3.5.5, and may differ on other Spark versions. - -## Type detection & casting - -Spark’s DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. - -### Reading from Oracle - -This is how Oracle connector performs this: - -* For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and Oracle type. -* Find corresponding `Oracle type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. -* Create DataFrame from query with specific column names and Spark types. - -### Writing to some existing Oracle table - -This is how Oracle connector performs this: - -* Get names of columns in DataFrame. [1](#id3) -* Perform `SELECT * FROM table LIMIT 0` query. -* Take only columns present in DataFrame (by name, case insensitive). For each found column get Clickhouse type. -* **Find corresponding** `Oracle type (read)` → `Spark type` **combination** (see below) for each DataFrame column. If no combination is found, raise exception. [2](#id4) -* Find corresponding `Spark type` → `Oracle type (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. -* If `Oracle type (write)` match `Oracle type (read)`, no additional casts will be performed, DataFrame column will be written to Oracle as is. -* If `Oracle type (write)` does not match `Oracle type (read)`, DataFrame column will be casted to target column type **on Oracle side**. - For example, you can write column with text data to `int` column, if column contains valid integer values within supported value range and precision. - -* **[1]** This allows to write data to tables with `DEFAULT` and `GENERATED` columns - if DataFrame has no such column, it will be populated by Oracle. -* **[2]** Yes, this is weird. - -### Create new table using Spark - -#### WARNING -ABSOLUTELY NOT RECOMMENDED! - -This is how Oracle connector performs this: - -* Find corresponding `Spark type` → `Oracle type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. -* Generate DDL for creating table in Oracle, like `CREATE TABLE (col1 ...)`, and run it. -* Write DataFrame to created table as is. - -But Oracle connector support only limited number of types and almost no custom clauses (like `PARTITION BY`, `INDEX`, etc). -So instead of relying on Spark to create tables: - -### See example - -```python -writer = DBWriter( - connection=oracle, - target="public.table", - options=Oracle.WriteOptions(if_exists="append"), -) -writer.run(df) -``` - -Always prefer creating table with desired DDL **BEFORE WRITING DATA**: - -### See example - -```python -oracle.execute( - """ - CREATE TABLE username.table ( - id NUMBER, - business_dt TIMESTAMP(6), - value VARCHAR2(2000) - ) - """, -) - -writer = DBWriter( - connection=oracle, - target="public.table", - options=Oracle.WriteOptions(if_exists="append"), -) -writer.run(df) -``` - -See Oracle [CREATE TABLE](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/SELECT.html) documentation. - -## Supported types - -### References - -See [List of Oracle types](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/Data-Types.html). - -Here you can find source code with type conversions: - -* [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/OracleDialect.scala#L83-L109) -* [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/OracleDialect.scala#L111-L123) - -### Numeric types - -| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | -|-----------------------------|----------------------------------|----------------------------|-------------------------| -| `NUMBER` | `DecimalType(P=38, S=10)` | `NUMBER(P=38, S=10)` | `NUMBER(P=38, S=10)` | -| `NUMBER(P=0..38)` | `DecimalType(P=0..38, S=0)` | `NUMBER(P=0..38, S=0)` | `NUMBER(P=38, S=0)` | -| `NUMBER(P=0..38, S=0..38)` | `DecimalType(P=0..38, S=0..38)` | `NUMBER(P=0..38, S=0..38)` | `NUMBER(P=38, S=0..38)` | -| `NUMBER(P=..., S=-127..-1)` | unsupported [3](#id6) | | | -| `FLOAT` | `DecimalType(P=38, S=10)` | `NUMBER(P=38, S=10)` | `NUMBER(P=38, S=10)` | -| `FLOAT(N)` | | | | -| `REAL` | | | | -| `DOUBLE PRECISION` | | | | -| `BINARY_FLOAT` | `FloatType()` | `NUMBER(P=19, S=4)` | `NUMBER(P=19, S=4)` | -| `BINARY_DOUBLE` | `DoubleType()` | | | -| `SMALLINT` | `DecimalType(P=38, S=0)` | `NUMBER(P=38, S=0)` | `NUMBER(P=38, S=0)` | -| `INTEGER` | | | | -| `LONG` | `StringType()` | `CLOB` | `CLOB` | -* **[3]** Oracle support decimal types with negative scale, like `NUMBER(38, -10)`. Spark doesn’t. - -### Temporal types - -| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | -|-------------------------------------|------------------------------------------------------------------------------|------------------------------------------------------|------------------------------------------------------| -| `DATE`, days | `TimestampType()`, microseconds | `TIMESTAMP(6)`, microseconds | `TIMESTAMP(6)`, microseconds | -| `TIMESTAMP`, microseconds | `TimestampType()`, microseconds | `TIMESTAMP(6)`, microseconds | `TIMESTAMP(6)`, microseconds | -| `TIMESTAMP(0)`, seconds | | | | -| `TIMESTAMP(3)`, milliseconds | | | | -| `TIMESTAMP(6)`, microseconds | | | | -| `TIMESTAMP(9)`, nanoseconds | `TimestampType()`, microseconds,
**precision loss** [4](#id8) | `TIMESTAMP(6)`, microseconds,
**precision loss** | `TIMESTAMP(6)`, microseconds,
**precision loss** | -| `TIMESTAMP WITH TIME ZONE` | unsupported | | | -| `TIMESTAMP(N) WITH TIME ZONE` | | | | -| `TIMESTAMP WITH LOCAL TIME ZONE` | | | | -| `TIMESTAMP(N) WITH LOCAL TIME ZONE` | | | | -| `INTERVAL YEAR TO MONTH` | | | | -| `INTERVAL DAY TO SECOND` | | | | - -#### WARNING -Note that types in Oracle and Spark have different value ranges: - -| Oracle type | Min value | Max value | Spark type | Min value | Max value | -|---------------|----------------------------------|---------------------------------|-------------------|------------------------------|------------------------------| -| `date` | `-4712-01-01` | `9999-01-01` | `DateType()` | `0001-01-01` | `9999-12-31` | -| `timestamp` | `-4712-01-01 00:00:00.000000000` | `9999-12-31 23:59:59.999999999` | `TimestampType()` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | - -So not all of values can be read from Oracle to Spark. - -References: -: * [Oracle date, timestamp and intervals documentation](https://oracle-base.com/articles/misc/oracle-dates-timestamps-and-intervals#DATE) - * [Spark DateType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/DateType.html) - * [Spark TimestampType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/TimestampType.html) - -* **[4]** Oracle support timestamp up to nanoseconds precision (`23:59:59.999999999`), but Spark `TimestampType()` supports datetime up to microseconds precision (`23:59:59.999999`). Nanoseconds will be lost during read or write operations. - -### String types - -| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | -|----------------------|----------------|-----------------------|------------------------| -| `CHAR` | `StringType()` | `CLOB` | `CLOB` | -| `CHAR(N CHAR)` | | | | -| `CHAR(N BYTE)` | | | | -| `NCHAR` | | | | -| `NCHAR(N)` | | | | -| `VARCHAR(N)` | | | | -| `LONG VARCHAR` | | | | -| `VARCHAR2(N CHAR)` | | | | -| `VARCHAR2(N BYTE)` | | | | -| `NVARCHAR2(N)` | | | | -| `CLOB` | | | | -| `NCLOB` | | | | - -### Binary types - -| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | -|----------------------|----------------|-----------------------|------------------------| -| `RAW(N)` | `BinaryType()` | `BLOB` | `BLOB` | -| `LONG RAW` | | | | -| `BLOB` | | | | -| `BFILE` | unsupported | | | - -### Struct types - -| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | -|-----------------------------------|----------------|-----------------------|------------------------| -| `XMLType` | `StringType()` | `CLOB` | `CLOB` | -| `URIType` | | | | -| `DBURIType` | | | | -| `XDBURIType` | | | | -| `HTTPURIType` | | | | -| `CREATE TYPE ... AS OBJECT (...)` | | | | -| `JSON` | unsupported | | | -| `CREATE TYPE ... AS VARRAY ...` | | | | -| `CREATE TYPE ... AS TABLE OF ...` | | | | - -### Special types - -| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | -|----------------------|-----------------|-----------------------|------------------------| -| `BOOLEAN` | `BooleanType()` | `BOOLEAN` | `NUMBER(P=1, S=0)` | -| `ROWID` | `StringType()` | `CLOB` | `CLOB` | -| `UROWID` | | | | -| `UROWID(N)` | | | | -| `ANYTYPE` | unsupported | | | -| `ANYDATA` | | | | -| `ANYDATASET` | | | | - -## Explicit type cast - -### `DBReader` - -It is possible to explicitly cast column of unsupported type using `DBReader(columns=...)` syntax. - -For example, you can use `CAST(column AS CLOB)` to convert data to string representation on Oracle side, and so it will be read as Spark’s `StringType()`. - -It is also possible to use [JSON_ARRAY](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/JSON_ARRAY.html) -or [JSON_OBJECT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/JSON_OBJECT.html) Oracle functions -to convert column of any type to string representation. Then this JSON string can then be effectively parsed using the [`JSON.parse_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.parse_column) method. - -```python -from onetl.file.format import JSON -from pyspark.sql.types import IntegerType, StructType, StructField - -from onetl.connection import Oracle -from onetl.db import DBReader - -oracle = Oracle(...) - -DBReader( - connection=oracle, - columns=[ - "id", - "supported_column", - "CAST(unsupported_column AS VARCHAR2(4000)) unsupported_column_str", - # or - "JSON_ARRAY(array_column) array_column_json", - ], -) -df = reader.run() - -json_scheme = StructType([StructField("key", IntegerType())]) - -df = df.select( - df.id, - df.supported_column, - df.unsupported_column_str.cast("integer").alias("parsed_integer"), - JSON().parse_column("array_column_json", json_scheme).alias("array_column"), -) -``` - -### `DBWriter` - -It is always possible to convert data on Spark side to string, and then write it to text column in Oracle table. - -To serialize and write JSON data to a `text` or `json` column in an Oracle table use the [`JSON.serialize_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.serialize_column) method. - -```python -from onetl.connection import Oracle -from onetl.db import DBWriter -from onetl.file.format import JSON - -oracle = Oracle(...) - -oracle.execute( - """ - CREATE TABLE schema.target_table ( - id INTEGER, - supported_column TIMESTAMP, - array_column_json VARCHAR2(4000) -- any string type, actually - ) - """, -) - -write_df = df.select( - df.id, - df.supported_column, - JSON().serialize_column(df.unsupported_column).alias("array_column_json"), -) - -writer = DBWriter( - connection=oracle, - target="schema.target_table", -) -writer.run(write_df) -``` - -Then you can parse this column on Oracle side - for example, by creating a view: - -```sql -SELECT - id, - supported_column, - JSON_VALUE(array_column_json, '$[0]' RETURNING NUMBER) AS array_item_0 -FROM - schema.target_table -``` - -Or by using [VIRTUAL column](https://oracle-base.com/articles/11g/virtual-columns-11gr1): - -```sql -CREATE TABLE schema.target_table ( - id INTEGER, - supported_column TIMESTAMP, - array_column_json VARCHAR2(4000), -- any string type, actually - array_item_0 GENERATED ALWAYS AS (JSON_VALUE(array_column_json, '$[0]' RETURNING NUMBER)) VIRTUAL -) -``` - -But data will be parsed on each table read in any case, as Oracle does no support `GENERATED ALWAYS AS (...) STORED` columns. diff --git a/mddocs/markdown/connection/db_connection/oracle/write.md b/mddocs/markdown/connection/db_connection/oracle/write.md deleted file mode 100644 index f1be20d24..000000000 --- a/mddocs/markdown/connection/db_connection/oracle/write.md +++ /dev/null @@ -1,183 +0,0 @@ - - -# Writing to Oracle using `DBWriter` - -For writing data to Oracle, use [`DBWriter`](../../../db/db_writer.md#onetl.db.db_writer.db_writer.DBWriter). - -#### WARNING -Please take into account [Oracle <-> Spark type mapping](types.md#oracle-types) - -#### WARNING -It is always recommended to create table explicitly using [Oracle.execute](execute.md#oracle-execute) -instead of relying on Spark’s table DDL generation. - -This is because Spark’s DDL generator can create columns with different precision and types than it is expected, -causing precision loss or other issues. - -## Examples - -```python -from onetl.connection import Oracle -from onetl.db import DBWriter - -oracle = Oracle(...) - -df = ... # data is here - -writer = DBWriter( - connection=oracle, - target="schema.table", - options=Oracle.WriteOptions(if_exists="append"), -) - -writer.run(df) -``` - -## Options - -Method above accepts [`OracleWriteOptions`](#onetl.connection.db_connection.oracle.options.OracleWriteOptions) - -### *pydantic model* onetl.connection.db_connection.oracle.options.OracleWriteOptions - -Spark JDBC writing options. - -#### Versionadded -Added in version 0.5.0: Replace `Oracle.Options` → `Oracle.WriteOptions` - -### Examples - -#### NOTE -You can pass any value -[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), -even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! - -The set of supported options depends on Spark version. - -```python -from onetl.connection import Oracle - -options = Oracle.WriteOptions( - if_exists="append", - batchsize=20_000, - customSparkOption="value", -) -``` - - - -#### *field* if_exists *: JDBCTableExistBehavior* *= JDBCTableExistBehavior.APPEND* *(alias 'mode')* - -Behavior of writing data into existing table. - -Possible values: -: * `append` (default) - : Adds new rows into existing table. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : Data is appended to a table. Table has the same DDL as before writing data -
- #### WARNING - This mode does not check whether table already contains - rows from dataframe, so duplicated rows can be created. -
- Also Spark does not support passing custom options to - insert statement, like `ON CONFLICT`, so don’t try to - implement deduplication using unique indexes or constraints. -
- Instead, write to staging table and perform deduplication - using `execute` method. - * `replace_entire_table` - : **Table is dropped and then created, or truncated**. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : Table content is replaced with dataframe content. -
- After writing completed, target table could either have the same DDL as - before writing data (`truncate=True`), or can be recreated (`truncate=False` - or source does not support truncation). - * `ignore` - : Ignores the write operation if the table already exists. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : The write operation is ignored, and no data is written to the table. - * `error` - : Raises an error if the table already exists. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : An error is raised, and no data is written to the table. - -#### Versionchanged -Changed in version 0.9.0: Renamed `mode` → `if_exists` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* batchsize *: int* *= 20000* - -How many rows can be inserted per round trip. - -Tuning this option can influence performance of writing. - -#### WARNING -Default value is different from Spark. - -Spark uses quite small value `1000`, which is absolutely not usable -in BigData world. - -Thus we’ve overridden default value with `20_000`, -which should increase writing performance. - -You can increase it even more, up to `50_000`, -but it depends on your database load and number of columns in the row. -Higher values does not increase performance. - -#### Versionchanged -Changed in version 0.4.0: Changed default value from 1000 to 20_000 - - - -#### *field* isolation_level *: str* *= 'READ_UNCOMMITTED'* *(alias 'isolationLevel')* - -The transaction isolation level, which applies to current connection. - -Possible values: -: * `NONE` (as string, not Python’s `None`) - * `READ_COMMITTED` - * `READ_UNCOMMITTED` - * `REPEATABLE_READ` - * `SERIALIZABLE` - -Values correspond to transaction isolation levels defined by JDBC standard. -Please refer the documentation for -[java.sql.Connection](https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html). - - diff --git a/mddocs/markdown/connection/db_connection/postgres/connection.md b/mddocs/markdown/connection/db_connection/postgres/connection.md deleted file mode 100644 index 0ca99169c..000000000 --- a/mddocs/markdown/connection/db_connection/postgres/connection.md +++ /dev/null @@ -1,133 +0,0 @@ - - -# Postgres connection - -### *class* onetl.connection.db_connection.postgres.connection.Postgres(\*, spark: SparkSession, user: str, password: SecretStr, host: Host, database: str, port: int = 5432, extra: PostgresExtra = PostgresExtra(stringtype='unspecified', tcpKeepAlive='true')) - -PostgreSQL JDBC connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on Maven package [org.postgresql:postgresql:42.7.5](https://mvnrepository.com/artifact/org.postgresql/postgresql/42.7.5) -([official Postgres JDBC driver](https://jdbc.postgresql.org/)). - -#### SEE ALSO -Before using this connector please take into account [Prerequisites](prerequisites.md#postgres-prerequisites) - -#### Versionadded -Added in version 0.1.0. - -* **Parameters:** - **host** - : Host of Postgres database. For example: `test.postgres.domain.com` or `193.168.1.11` - - **port** - : Port of Postgres database - - **user** - : User, which have proper access to the database. For example: `some_user` - - **password** - : Password for database connection - - **database** - : Database in RDBMS, NOT schema. -
- See [this page](https://www.educba.com/postgresql-database-vs-schema/) for more details - - **spark** - : Spark session. - - **extra** - : Specifies one or more extra parameters by which clients can connect to the instance. -
- For example: `{"ssl": "false"}` -
- See [Postgres JDBC driver properties documentation](https://jdbc.postgresql.org/documentation/use/) - for more details - -### Examples - -Create and check Postgres connection: - -```python -from onetl.connection import Postgres -from pyspark.sql import SparkSession - -# Create Spark session with Postgres driver loaded -maven_packages = Postgres.get_packages() -spark = ( - SparkSession.builder.appName("spark-app-name") - .config("spark.jars.packages", ",".join(maven_packages)) - .getOrCreate() -) - -# Create connection -postgres = Postgres( - host="database.host.or.ip", - user="user", - password="*****", - database="target_database", - spark=spark, -) -``` - -Create read-only connection: - -```python -... - -# Create connection -postgres = Postgres( - host="database.host.or.ip", - user="user", - password="*****", - database="target_database", - extra={"readOnly": True, "readOnlyMode": "always"}, # <-- - spark=spark, -).check() -``` - - - -#### check() - -Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If not, an exception will be raised. - -* **Returns:** - Connection itself -* **Raises:** - RuntimeError - : If the connection is not available - -### Examples - -```python -connection.check() -``` - - - -#### *classmethod* get_packages(package_version: str | None = None) → list[str] - -Get package names to be downloaded by Spark. Allows specifying a custom JDBC driver version. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **package_version** - : Specifies the version of the PostgreSQL JDBC driver to use. Defaults to `42.7.5`. - -### Examples - -```python -from onetl.connection import Postgres - -Postgres.get_packages() - -# custom package version -Postgres.get_packages(package_version="42.6.0") -``` - - diff --git a/mddocs/markdown/connection/db_connection/postgres/execute.md b/mddocs/markdown/connection/db_connection/postgres/execute.md deleted file mode 100644 index e28ebc5da..000000000 --- a/mddocs/markdown/connection/db_connection/postgres/execute.md +++ /dev/null @@ -1,189 +0,0 @@ - - -# Executing statements in Postgres - -#### WARNING -Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. - -Do **NOT** use them to read large amounts of data. Use [DBReader](read.md#postgres-read) or [Postgres.sql](sql.md#postgres-sql) instead. - -## How to - -There are 2 ways to execute some statement in Postgres - -### Use `Postgres.fetch` - -Use this method to execute some `SELECT` query which returns **small number or rows**, like reading -Postgres config, or reading data from some reference table. Method returns Spark DataFrame. - -Method accepts [`Postgres.FetchOptions`](#onetl.connection.db_connection.postgres.options.PostgresFetchOptions). - -Connection opened using this method should be then closed with `connection.close()` or `with connection:`. - -#### WARNING -Please take into account [Postgres <-> Spark type mapping](types.md#postgres-types). - -#### Syntax support - -This method supports **any** query syntax supported by Postgres, like: - -* ✅︎ `SELECT ... FROM ...` -* ✅︎ `WITH alias AS (...) SELECT ...` -* ❌ `SET ...; SELECT ...;` - multiple statements not supported - -#### Examples - -```python -from onetl.connection import Postgres - -postgres = Postgres(...) - -df = postgres.fetch( - "SELECT value FROM some.reference_table WHERE key = 'some_constant'", - options=Postgres.FetchOptions(queryTimeout=10), -) -postgres.close() -value = df.collect()[0][0] # get value from first row and first column -``` - -### Use `Postgres.execute` - -Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. - -Method accepts [`Postgres.ExecuteOptions`](#onetl.connection.db_connection.postgres.options.PostgresExecuteOptions). - -Connection opened using this method should be then closed with `connection.close()` or `with connection:`. - -#### Syntax support - -This method supports **any** query syntax supported by Postgres, like: - -* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on -* ✅︎ `ALTER ...` -* ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on -* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on -* ✅︎ `CALL procedure(arg1, arg2) ...` -* ✅︎ `SELECT func(arg1, arg2)` or `{call func(arg1, arg2)}` - special syntax for calling functions -* ✅︎ other statements not mentioned here -* ❌ `SET ...; SELECT ...;` - multiple statements not supported - -#### Examples - -```python -from onetl.connection import Postgres - -postgres = Postgres(...) - -postgres.execute("DROP TABLE schema.table") -postgres.execute( - """ - CREATE TABLE schema.table ( - id bigint GENERATED ALWAYS AS IDENTITY, - key text, - value real - ) - """, - options=Postgres.ExecuteOptions(queryTimeout=10), -) -``` - -## Options - -### *pydantic model* onetl.connection.db_connection.postgres.options.PostgresFetchOptions - -Options related to fetching data from databases via JDBC. - -#### Versionadded -Added in version 0.11.0: Replace `Postgres.JDBCOptions` → `Postgres.FetchOptions` - -### Examples - -#### NOTE -You can pass any value supported by underlying JDBC driver class, -even if it is not mentioned in this documentation. - -```python -from onetl.connection import Postgres - -options = Postgres.FetchOptions( - queryTimeout=60_000, - fetchsize=100_000, - customSparkOption="value", -) -``` - - -* **Fields:** - - [`fetchsize (int | None)`](#onetl.connection.db_connection.postgres.options.PostgresFetchOptions.fetchsize) - - [`query_timeout (int | None)`](#onetl.connection.db_connection.postgres.options.PostgresFetchOptions.query_timeout) - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int | None* *= None* - -How many rows to fetch per round trip. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value depends on driver. For example, Oracle has -default `fetchsize=10`. - - - -### *pydantic model* onetl.connection.db_connection.postgres.options.PostgresExecuteOptions - -Options related to executing statements in databases via JDBC. - -#### Versionadded -Added in version 0.11.0: Replace `Postgres.JDBCOptions` → `Postgres.ExecuteOptions` - -### Examples - -#### NOTE -You can pass any value supported by underlying JDBC driver class, -even if it is not mentioned in this documentation. - -```python -from onetl.connection import Postgres - -options = Postgres.ExecuteOptions( - queryTimeout=60_000, - customSparkOption="value", -) -``` - - -* **Fields:** - - [`fetchsize (int | None)`](#onetl.connection.db_connection.postgres.options.PostgresExecuteOptions.fetchsize) - - [`query_timeout (int | None)`](#onetl.connection.db_connection.postgres.options.PostgresExecuteOptions.query_timeout) - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int | None* *= None* - -How many rows to fetch per round trip. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value depends on driver. For example, Oracle has -default `fetchsize=10`. - - diff --git a/mddocs/markdown/connection/db_connection/postgres/index.md b/mddocs/markdown/connection/db_connection/postgres/index.md deleted file mode 100644 index e1a8f76eb..000000000 --- a/mddocs/markdown/connection/db_connection/postgres/index.md +++ /dev/null @@ -1,19 +0,0 @@ - - -# Postgres - -# Connection - -* [Prerequisites](prerequisites.md) -* [Postgres connection](connection.md) - -# Operations - -* [Reading from Postgres using `DBReader`](read.md) -* [Reading from Postgres using `Postgres.sql`](sql.md) -* [Writing to Postgres using `DBWriter`](write.md) -* [Executing statements in Postgres](execute.md) - -# Troubleshooting - -* [Postgres <-> Spark type mapping](types.md) diff --git a/mddocs/markdown/connection/db_connection/postgres/prerequisites.md b/mddocs/markdown/connection/db_connection/postgres/prerequisites.md deleted file mode 100644 index 2bfb04e0f..000000000 --- a/mddocs/markdown/connection/db_connection/postgres/prerequisites.md +++ /dev/null @@ -1,71 +0,0 @@ - - -# Prerequisites - -## Version Compatibility - -* PostgreSQL server versions: - : * Officially declared: 8.2 - 17 - * Actually tested: 9.4.26, 17.3 -* Spark versions: 2.3.x - 3.5.x -* Java versions: 8 - 20 - -See [official documentation](https://jdbc.postgresql.org/). - -## Installing PySpark - -To use Postgres connector you should have PySpark installed (or injected to `sys.path`) -BEFORE creating the connector instance. - -See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. - -## Connecting to Postgres - -### Allowing connection to Postgres instance - -Ask your Postgres administrator to allow your user (and probably IP) to connect to instance, -e.g. by updating `pg_hba.conf` file. - -See [official documentation](https://www.postgresql.org/docs/current/auth-pg-hba-conf.html). - -### Connection port - -Connection is usually performed to port 5432. Port may differ for different Postgres instances. -Please ask your Postgres administrator to provide required information. - -### Connection host - -It is possible to connect to Postgres by using either DNS name of host or it’s IP address. - -If you’re using Postgres cluster, it is currently possible to connect only to **one specific node**. -Connecting to multiple nodes to perform load balancing, as well as automatic failover to new master/replica are not supported. - -### Required grants - -Ask your Postgres cluster administrator to set following grants for a user, -used for creating a connection: - -Read + Write - -```sql --- allow creating tables in specific schema -GRANT USAGE, CREATE ON SCHEMA myschema TO username; - --- allow read & write access to specific table -GRANT SELECT, INSERT ON myschema.mytable TO username; - --- only if if_exists="replace_entire_table" is used: -GRANT TRUNCATE ON myschema.mytable TO username; -``` - -Read only - -```sql --- allow creating tables in specific schema -GRANT USAGE ON SCHEMA myschema TO username; - --- allow read access to specific table -GRANT SELECT ON myschema.mytable TO username; -``` - -More details can be found in [official documentation](https://www.postgresql.org/docs/current/sql-grant.html). diff --git a/mddocs/markdown/connection/db_connection/postgres/read.md b/mddocs/markdown/connection/db_connection/postgres/read.md deleted file mode 100644 index b0047f58a..000000000 --- a/mddocs/markdown/connection/db_connection/postgres/read.md +++ /dev/null @@ -1,350 +0,0 @@ - - -# Reading from Postgres using `DBReader` - -[`DBReader`](../../../db/db_reader.md#onetl.db.db_reader.db_reader.DBReader) supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, -but does not support custom queries, like `JOIN`. - -#### WARNING -Please take into account [Postgres <-> Spark type mapping](types.md#postgres-types) - -## Supported DBReader features - -* ✅︎ `columns` -* ✅︎ `where` -* ✅︎ `hwm`, supported strategies: -* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) -* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) -* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) -* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) -* ❌ `hint` (is not supported by Postgres) -* ❌ `df_schema` -* ✅︎ `options` (see [`Postgres.ReadOptions`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions)) - -## Examples - -Snapshot strategy: - -```python -from onetl.connection import Postgres -from onetl.db import DBReader - -postgres = Postgres(...) - -reader = DBReader( - connection=postgres, - source="schema.table", - columns=["id", "key", "CAST(value AS text) value", "updated_dt"], - where="key = 'something'", - options=Postgres.ReadOptions(partitionColumn="id", numPartitions=10), -) -df = reader.run() -``` - -Incremental strategy: - -```python -from onetl.connection import Postgres -from onetl.db import DBReader -from onetl.strategy import IncrementalStrategy - -postgres = Postgres(...) - -reader = DBReader( - connection=postgres, - source="schema.table", - columns=["id", "key", "CAST(value AS text) value", "updated_dt"], - where="key = 'something'", - hwm=DBReader.AutoDetectHWM(name="postgres_hwm", expression="updated_dt"), - options=Postgres.ReadOptions(partitionColumn="id", numPartitions=10), -) - -with IncrementalStrategy(): - df = reader.run() -``` - -## Recommendations - -### Select only required columns - -Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Postgres to Spark. - -### Pay attention to `where` value - -Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. -This both reduces the amount of data send from Postgres to Spark, and may also improve performance of the query. -Especially if there are indexes or partitions for columns used in `where` clause. - -## Options - -### *pydantic model* onetl.connection.db_connection.postgres.options.PostgresReadOptions - -Spark JDBC reading options. - -#### Versionadded -Added in version 0.5.0: Replace `Postgres.Options` → `Postgres.ReadOptions` - -### Examples - -#### NOTE -You can pass any value -[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), -even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! - -The set of supported options depends on Spark version. - -```python -from onetl.connection import Postgres - -options = Postgres.ReadOptions( - partitioning_mode="range", - partitionColumn="reg_id", - numPartitions=10, - customSparkOption="value", -) -``` - - -* **Fields:** - - [`fetchsize (int)`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.fetchsize) - - [`lower_bound (int | None)`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.lower_bound) - - [`num_partitions (pydantic.types.PositiveInt)`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.num_partitions) - - [`partition_column (str | None)`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.partition_column) - - [`partitioning_mode (onetl.connection.db_connection.jdbc_connection.options.JDBCPartitioningMode)`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.partitioning_mode) - - [`query_timeout (int | None)`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.query_timeout) - - [`session_init_statement (str | None)`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.session_init_statement) - - [`upper_bound (int | None)`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.upper_bound) - -#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* - -Column used to parallelize reading from a table. - -#### WARNING -It is highly recommended to use primary key, or column with an index -to avoid performance issues. - -#### NOTE -Column type depends on [`partitioning_mode`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.partitioning_mode). - -* `partitioning_mode="range"` requires column to be an integer, date or timestamp (can be NULL, but not recommended). -* `partitioning_mode="hash"` accepts any column type (NOT NULL). -* `partitioning_mode="mod"` requires column to be an integer (NOT NULL). - -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.partitioning_mode) for more details - - - -#### *field* num_partitions *: PositiveInt* *= 1* *(alias 'numPartitions')* - -Number of jobs created by Spark to read the table content in parallel. -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.partitioning_mode) for more details - - -* **Constraints:** - - **exclusiveMinimum** = 0 - -#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* - -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.partitioning_mode) for more details - - - -#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* - -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.partitioning_mode) for more details - - - -#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* - -After each database session is opened to the remote DB and before starting to read data, -this option executes a custom SQL statement (or a PL/SQL block). - -Use this to implement session initialization code. - -Example: - -```python -sessionInitStatement = """ - BEGIN - execute immediate - 'alter session set "_serial_direct_read"=true'; - END; -""" -``` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int* *= 100000* - -Fetch N rows from an opened cursor per one read round. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value is different from Spark. - -Spark uses driver’s own value, and it may be different in different drivers, -and even versions of the same driver. For example, Oracle has -default `fetchsize=10`, which is absolutely not usable. - -Thus we’ve overridden default value with `100_000`, which should increase reading performance. - -#### Versionchanged -Changed in version 0.2.0: Set explicit default value to `100_000` - - - -#### *field* partitioning_mode *: JDBCPartitioningMode* *= JDBCPartitioningMode.RANGE* - -Defines how Spark will parallelize reading from table. - -Possible values: - -* `range` (default) - : Allocate each executor a range of values from column passed into [`partition_column`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.partition_column). -
- ### Spark generates for each executor an SQL query -
- Executor 1: - ```sql - SELECT ... FROM table - WHERE (partition_column >= lowerBound - OR partition_column IS NULL) - AND partition_column < (lower_bound + stride) - ``` -
- Executor 2: - ```sql - SELECT ... FROM table - WHERE partition_column >= (lower_bound + stride) - AND partition_column < (lower_bound + 2 * stride) - ``` -
- … -
- Executor N: - ```sql - SELECT ... FROM table - WHERE partition_column >= (lower_bound + (N-1) * stride) - AND partition_column <= upper_bound - ``` -
- Where `stride=(upper_bound - lower_bound) / num_partitions`. -
- #### NOTE - Can be used only with columns of integer, date or timestamp types. -
- #### NOTE - [`lower_bound`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.lower_bound), [`upper_bound`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.upper_bound) and [`num_partitions`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.num_partitions) are used just to - calculate the partition stride, **NOT** for filtering the rows in table. - So all rows in the table will be returned (unlike *Incremental* [Read Strategies](../../../strategy/index.md#strategy)). -
- #### NOTE - All queries are executed in parallel. To execute them sequentially, use *Batch* [Read Strategies](../../../strategy/index.md#strategy). -* `hash` - : Allocate each executor a set of values based on hash of the [`partition_column`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.partition_column) column. -
- ### Spark generates for each executor an SQL query -
- Executor 1: - ```sql - SELECT ... FROM table - WHERE (some_hash(partition_column) mod num_partitions) = 0 -- lower_bound - ``` -
- Executor 2: - ```sql - SELECT ... FROM table - WHERE (some_hash(partition_column) mod num_partitions) = 1 -- lower_bound + 1 - ``` -
- … -
- Executor N: - ```sql - SELECT ... FROM table - WHERE (some_hash(partition_column) mod num_partitions) = num_partitions-1 -- upper_bound - ``` -
- #### NOTE - The hash function implementation depends on RDBMS. It can be `MD5` or any other fast hash function, - or expression based on this function call. Usually such functions accepts any column type as an input. -* `mod` - : Allocate each executor a set of values based on modulus of the [`partition_column`](#onetl.connection.db_connection.postgres.options.PostgresReadOptions.partition_column) column. -
- ### Spark generates for each executor an SQL query -
- Executor 1: - ```sql - SELECT ... FROM table - WHERE (partition_column mod num_partitions) = 0 -- lower_bound - ``` -
- Executor 2: - ```sql - SELECT ... FROM table - WHERE (partition_column mod num_partitions) = 1 -- lower_bound + 1 - ``` -
- Executor N: - ```sql - SELECT ... FROM table - WHERE (partition_column mod num_partitions) = num_partitions-1 -- upper_bound - ``` -
- #### NOTE - Can be used only with columns of integer type. - -#### Versionadded -Added in version 0.5.0. - -### Examples - -Read data in 10 parallel jobs by range of values in `id_column` column: - -```python -ReadOptions( - partitioning_mode="range", # default mode, can be omitted - partitionColumn="id_column", - numPartitions=10, - # Options below can be discarded because they are - # calculated automatically as MIN and MAX values of `partitionColumn` - lowerBound=0, - upperBound=100_000, -) -``` - -Read data in 10 parallel jobs by hash of values in `some_column` column: - -```python -ReadOptions( - partitioning_mode="hash", - partitionColumn="some_column", - numPartitions=10, - # lowerBound and upperBound are automatically set to `0` and `9` -) -``` - -Read data in 10 parallel jobs by modulus of values in `id_column` column: - -```python -ReadOptions( - partitioning_mode="mod", - partitionColumn="id_column", - numPartitions=10, - # lowerBound and upperBound are automatically set to `0` and `9` -) -``` - - diff --git a/mddocs/markdown/connection/db_connection/postgres/sql.md b/mddocs/markdown/connection/db_connection/postgres/sql.md deleted file mode 100644 index 0a5638d62..000000000 --- a/mddocs/markdown/connection/db_connection/postgres/sql.md +++ /dev/null @@ -1,191 +0,0 @@ - - -# Reading from Postgres using `Postgres.sql` - -`Postgres.sql` allows passing custom SQL query, but does not support incremental strategies. - -#### WARNING -Please take into account [Postgres <-> Spark type mapping](types.md#postgres-types) - -#### WARNING -Statement is executed in **read-write** connection, so if you’re calling some functions/procedures with DDL/DML statements inside, -they can change data in your database. - -## Syntax support - -Only queries with the following syntax are supported: - -* ✅︎ `SELECT ... FROM ...` -* ✅︎ `WITH alias AS (...) SELECT ...` -* ❌ `SET ...; SELECT ...;` - multiple statements not supported - -## Examples - -```python -from onetl.connection import Postgres - -postgres = Postgres(...) -df = postgres.sql( - """ - SELECT - id, - key, - CAST(value AS text) value, - updated_at - FROM - some.mytable - WHERE - key = 'something' - """, - options=Postgres.SQLOptions( - partitionColumn="id", - numPartitions=10, - lowerBound=0, - upperBound=1000, - ), -) -``` - -## Recommendations - -### Select only required columns - -Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. -This reduces the amount of data passed from Postgres to Spark. - -### Pay attention to `where` value - -Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. -This both reduces the amount of data send from Postgres to Spark, and may also improve performance of the query. -Especially if there are indexes or partitions for columns used in `where` clause. - -## Options - -### *pydantic model* onetl.connection.db_connection.postgres.options.PostgresSQLOptions - -Options specifically for SQL queries - -These options allow you to specify configurations for executing SQL queries -without relying on Spark’s partitioning mechanisms. - -#### Versionadded -Added in version 0.11.0: Split up `Postgres.ReadOptions` to `Postgres.SQLOptions` - -### Examples - -#### NOTE -You can pass any JDBC configuration -[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), -tailored to optimize SQL query execution. **Option names should be in** `camelCase`! - -```python -from onetl.connection import Postgres - -options = Postgres.SQLOptions( - partitionColumn="reg_id", - numPartitions=10, - lowerBound=0, - upperBound=1000, - customSparkOption="value", -) -``` - - - -#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* - -Column used to partition data across multiple executors for parallel query processing. - -#### WARNING -It is highly recommended to use primary key, or column with an index -to avoid performance issues. - -### Example of using `partitionColumn="id"` with `partitioning_mode="range"` - -```sql --- If partition_column is 'id', with numPartitions=4, lowerBound=1, and upperBound=100: --- Executor 1 processes IDs from 1 to 25 -SELECT ... FROM table WHERE id >= 1 AND id < 26 --- Executor 2 processes IDs from 26 to 50 -SELECT ... FROM table WHERE id >= 26 AND id < 51 --- Executor 3 processes IDs from 51 to 75 -SELECT ... FROM table WHERE id >= 51 AND id < 76 --- Executor 4 processes IDs from 76 to 100 -SELECT ... FROM table WHERE id >= 76 AND id <= 100 - --- General case for Executor N -SELECT ... FROM table -WHERE partition_column >= (lowerBound + (N-1) * stride) -AND partition_column <= upperBound --- Where ``stride`` is calculated as ``(upperBound - lowerBound) / numPartitions``. -``` - - - -#### *field* num_partitions *: int | None* *= None* *(alias 'numPartitions')* - -Number of jobs created by Spark to read the table content in parallel. - - - -#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* - -Defines the starting boundary for partitioning the query’s data. Mandatory if [`partition_column`](#onetl.connection.db_connection.postgres.options.PostgresSQLOptions.partition_column) is set - - - -#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* - -Sets the ending boundary for data partitioning. Mandatory if [`partition_column`](#onetl.connection.db_connection.postgres.options.PostgresSQLOptions.partition_column) is set - - - -#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* - -After each database session is opened to the remote DB and before starting to read data, -this option executes a custom SQL statement (or a PL/SQL block). - -Use this to implement session initialization code. - -Example: - -```python -sessionInitStatement = """ - BEGIN - execute immediate - 'alter session set "_serial_direct_read"=true'; - END; -""" -``` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int* *= 100000* - -Fetch N rows from an opened cursor per one read round. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value is different from Spark. - -Spark uses driver’s own value, and it may be different in different drivers, -and even versions of the same driver. For example, Oracle has -default `fetchsize=10`, which is absolutely not usable. - -Thus we’ve overridden default value with `100_000`, which should increase reading performance. - -#### Versionchanged -Changed in version 0.2.0: Set explicit default value to `100_000` - - diff --git a/mddocs/markdown/connection/db_connection/postgres/types.md b/mddocs/markdown/connection/db_connection/postgres/types.md deleted file mode 100644 index 3baca234d..000000000 --- a/mddocs/markdown/connection/db_connection/postgres/types.md +++ /dev/null @@ -1,372 +0,0 @@ - - -# Postgres <-> Spark type mapping - -#### NOTE -The results below are valid for Spark 3.5.5, and may differ on other Spark versions. - -## Type detection & casting - -Spark’s DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. - -### Reading from Postgres - -This is how Postgres connector performs this: - -* For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and Postgres type. -* Find corresponding `Postgres type (read)` → `Spark type` combination (see below) for each DataFrame column [1](#id2). If no combination is found, raise exception. -* Create DataFrame from query with specific column names and Spark types. - -* **[1]** All Postgres types that doesn’t have corresponding Java type are converted to `String`. - -### Writing to some existing Postgres table - -This is how Postgres connector performs this: - -* Get names of columns in DataFrame. [1](#id2) -* Perform `SELECT * FROM table LIMIT 0` query. -* Take only columns present in DataFrame (by name, case insensitive) [2](#id6). For each found column get Postgres type. -* Find corresponding `Spark type` → `Postgres type (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. -* If `Postgres type (write)` match `Postgres type (read)`, no additional casts will be performed, DataFrame column will be written to Postgres as is. -* If `Postgres type (write)` does not match `Postgres type (read)`, DataFrame column will be casted to target column type **on Postgres side**. - For example, you can write column with text data to `int` column, if column contains valid integer values within supported value range and precision [3](#id7). - -* **[2]** This allows to write data to tables with `DEFAULT` and `GENERATED` columns - if DataFrame has no such column, it will be populated by Postgres. -* **[3]** This is true only if either DataFrame column is a `StringType()`, or target column is `text` type. But other types cannot be silently converted, like `bytea -> bit(N)`. This requires explicit casting, see [Manual conversion to string](). - -### Create new table using Spark - -#### WARNING -ABSOLUTELY NOT RECOMMENDED! - -This is how Postgres connector performs this: - -* Find corresponding `Spark type` → `Postgres type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. -* Generate DDL for creating table in Postgres, like `CREATE TABLE (col1 ...)`, and run it. -* Write DataFrame to created table as is. - -But Postgres connector support only limited number of types and almost no custom clauses (like `PARTITION BY`, `INDEX`, etc). -So instead of relying on Spark to create tables: - -### See example - -```python -writer = DBWriter( - connection=postgres, - target="public.table", - options=Postgres.WriteOptions( - if_exists="append", - createTableOptions="PARTITION BY RANGE (id)", - ), -) -writer.run(df) -``` - -Always prefer creating table with desired DDL **BEFORE WRITING DATA**: - -### See example - -```python -postgres.execute( - """ - CREATE TABLE public.table ( - id bigint, - business_dt timestamp(6), - value json - ) - PARTITION BY RANGE (Id) - """, -) - -writer = DBWriter( - connection=postgres, - target="public.table", - options=Postgres.WriteOptions(if_exists="append"), -) -writer.run(df) -``` - -See Postgres [CREATE TABLE](https://www.postgresql.org/docs/current/sql-createtable.html) documentation. - -## Supported types - -### References - -See [List of Postgres types](https://www.postgresql.org/docs/current/datatype.html). - -Here you can find source code with type conversions: - -* [Postgres <-> JDBC](https://github.com/pgjdbc/pgjdbc/blob/REL42.6.0/pgjdbc/src/main/java/org/postgresql/jdbc/TypeInfoCache.java#L78-L112) -* [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/PostgresDialect.scala#L52-L108) -* [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/PostgresDialect.scala#L118-L132) - -### Numeric types - -| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | -|-----------------------------|-------------------------------------|-----------------------------|--------------------------| -| `decimal` | `DecimalType(P=38, S=18)` | `decimal(P=38, S=18)` | `decimal` (unbounded) | -| `decimal(P=0..38)` | `DecimalType(P=0..38, S=0)` | `decimal(P=0..38, S=0)` | | -| `decimal(P=0..38, S=0..38)` | `DecimalType(P=0..38, S=0..38)` | `decimal(P=0..38, S=0..38)` | | -| `decimal(P=39.., S=0..)` | unsupported [4](#id11) | | | -| `decimal(P=.., S=..-1)` | unsupported [5](#id12) | | | -| `real` | `FloatType()` | `real` | `real` | -| `double precision` | `DoubleType()` | `double precision` | `double precision` | -| `smallint` | `ShortType()` | `smallint` | `smallint` | -| `-` | `ByteType()` | | | -| `integer` | `IntegerType()` | `integer` | `integer` | -| `bigint` | `LongType()` | `bigint` | `bigint` | -| `money` | `StringType()` [1](#id2) | `text` | `text` | -| `int4range` | | | | -| `int8range` | | | | -| `numrange` | | | | -| `int2vector` | | | | -* **[4]** Postgres support decimal types with unlimited precision. But Spark’s `DecimalType(P, S)` supports maximum `P=38` (128 bit). It is impossible to read, write or operate with values of larger precision, this leads to an exception. -* **[5]** Postgres support decimal types with negative scale, like `decimal(38, -10)`. Spark doesn’t. - -### Temporal types - -| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | -|----------------------------------|----------------------------------------------------------------------|-------------------------|--------------------------| -| `date` | `DateType()` | `date` | `date` | -| `time` | `TimestampType()`,
with time format quirks [6](#id16) | `timestamp(6)` | `timestamp(6)` | -| `time(0..6)` | | | | -| `time with time zone` | | | | -| `time(0..6) with time zone` | | | | -| `timestamp` | `TimestampType()` | `timestamp(6)` | `timestamp(6)` | -| `timestamp(0..6)` | | | | -| `timestamp with time zone` | | | | -| `timestamp(0..6) with time zone` | | | | -| `-` | `TimestampNTZType()` | `timestamp(6)` | `timestamp(6)` | -| `interval` of any precision | `StringType()` [1](#id2) | `text` | `text` | -| `-` | `DayTimeIntervalType()` | unsupported | unsupported | -| `-` | `YearMonthIntervalType()` | unsupported | unsupported | -| `daterange` | `StringType()` [1](#id2) | `text` | `text` | -| `tsrange` | | | | -| `tstzrange` | | | | - -#### WARNING -Note that types in Postgres and Spark have different value ranges: - -| Postgres type | Min value | Max value | Spark type | Min value | Max value | -|-----------------|-------------------------------|--------------------------------|-------------------|------------------------------|------------------------------| -| `date` | `-4713-01-01` | `5874897-01-01` | `DateType()` | `0001-01-01` | `9999-12-31` | -| `timestamp` | `-4713-01-01 00:00:00.000000` | `294276-12-31 23:59:59.999999` | `TimestampType()` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | -| `time` | `00:00:00.000000` | `24:00:00.000000` | | | | - -So not all of values can be read from Postgres to Spark. - -References: -: * [Postgres date/time types documentation](https://www.postgresql.org/docs/current/datatype-datetime.html) - * [Spark DateType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/DateType.html) - * [Spark TimestampType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/TimestampType.html) - -* **[6]** `time` type is the same as `timestamp` with date `1970-01-01`. So instead of reading data from Postgres like `23:59:59` it is actually read `1970-01-01 23:59:59`, and vice versa. - -### String types - -| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | -|---------------------------|-------------------------------------|-------------------------|--------------------------| -| `character` | `StringType()` | `text` | `text` | -| `character(N)` | | | | -| `character varying` | | | | -| `character varying(N)` | | | | -| `text` | | | | -| `json` | | | | -| `jsonb` | | | | -| `xml` | | | | -| `CREATE TYPE ... AS ENUM` | `StringType()` [1](#id2) | | | -| `tsvector` | | | | -| `tsquery` | | | | -| `-` | `CharType()` | `unsupported` | `unsupported` | -| `-` | `VarcharType()` | `unsupported` | `unsupported` | - -### Binary types - -| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | -|------------------------|-------------------------------------|----------------------------------------------------------|--------------------------| -| `boolean` | `BooleanType()` | `boolean` | `boolean` | -| `bit` | `BooleanType()` | `bool`,
**cannot insert data** [3](#id7) | `bool` | -| `bit(N=1)` | | | | -| `bit(N=2..)` | `ByteType()` | `bytea`,
**cannot insert data** [3](#id7) | `bytea` | -| `bit varying` | `StringType()` [1](#id2) | `text` | `text` | -| `bit varying(N)` | | | | -| `bytea` | `BinaryType()` | `bytea` | `bytea` | - -### Struct types - -| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | -|------------------------------|-------------------------------------|-------------------------|--------------------------| -| `T[]` | `ArrayType(T)` | `T[]` | `T[]` | -| `T[][]` | unsupported | | | -| `CREATE TYPE sometype (...)` | `StringType()` [1](#id2) | `text` | `text` | -| `-` | `StructType()` | unsupported | | -| `-` | `MapType()` | | | - -### Network types - -| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | -|------------------------|-------------------------------------|-------------------------|--------------------------| -| `cidr` | `StringType()` [1](#id2) | `text` | `text` | -| `inet` | | | | -| `macaddr` | | | | -| `macaddr8` | | | | - -### Geo types - -| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | -|------------------------|-------------------------------------|-------------------------|--------------------------| -| `circle` | `StringType()` [1](#id2) | `text` | `text` | -| `box` | | | | -| `line` | | | | -| `lseg` | | | | -| `path` | | | | -| `point` | | | | -| `polygon` | | | | -| `polygon` | | | | - -## Explicit type cast - -### `DBReader` - -It is possible to explicitly cast column of unsupported type using `DBReader(columns=...)` syntax. - -For example, you can use `CAST(column AS text)` to convert data to string representation on Postgres side, and so it will be read as Spark’s `StringType()`. - -It is also possible to use [to_json](https://www.postgresql.org/docs/current/functions-json.html) Postgres function to convert column of any type to string representation, and then parse this column on Spark side you can use the [`JSON.parse_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.parse_column) method: - -```python -from pyspark.sql.types import IntegerType - -from onetl.connection import Postgres -from onetl.db import DBReader -from onetl.file.format import JSON - -postgres = Postgres(...) - -DBReader( - connection=postgres, - columns=[ - "id", - "supported_column", - "CAST(unsupported_column AS text) unsupported_column_str", - # or - "to_json(unsupported_column) array_column_json", - ], -) -df = reader.run() - -json_schema = StructType( - [ - StructField("id", IntegerType(), nullable=True), - StructField("name", StringType(), nullable=True), - ..., - ] -) -df = df.select( - df.id, - df.supported_column, - # explicit cast - df.unsupported_column_str.cast("integer").alias("parsed_integer"), - JSON().parse_column("array_column_json", json_schema).alias("json_string"), -) -``` - -### `DBWriter` - -It is always possible to convert data on the Spark side to a string, and then write it to a text column in a Postgres table. - -#### Using JSON.serialize_column - -You can use the [`JSON.serialize_column`](../../../file_df/file_formats/json.md#onetl.file.format.json.JSON.serialize_column) method for data serialization: - -```python -from onetl.file.format import JSON -from pyspark.sql.functions import col - -from onetl.connection import Postgres -from onetl.db import DBWriter - -postgres = Postgres(...) - -postgres.execute( - """ - CREATE TABLE schema.target_table ( - id int, - supported_column timestamp, - array_column_json jsonb -- any column type, actually - ) - """, -) - -write_df = df.select( - df.id, - df.supported_column, - JSON().serialize_column(df.unsupported_column).alias("array_column_json"), -) - -writer = DBWriter( - connection=postgres, - target="schema.target_table", -) -writer.run(write_df) -``` - -Then you can parse this column on the Postgres side (for example, by creating a view): - -```sql -SELECT - id, - supported_column, - array_column_json->'0' AS array_item_0 -FROM - schema.target_table -``` - -To avoid casting the value on every table read you can use [GENERATED ALWAYS STORED](https://www.postgresql.org/docs/current/ddl-generated-columns.html) column, but this requires 2x space (for original and parsed value). - -#### Manual conversion to string - -Postgres connector also supports conversion text value directly to target column type, if this value has a proper format. - -For example, you can write data like `[123, 345)` to `int8range` type because Postgres allows cast `'[123, 345)'::int8range'`: - -```python -from pyspark.sql.ftypes import StringType -from pyspark.sql.functions import udf - -from onetl.connection import Postgres -from onetl.db import DBReader - -postgres = Postgres(...) - -postgres.execute( - """ - CREATE TABLE schema.target_table ( - id int, - range_column int8range -- any column type, actually - ) - """, -) - - -@udf(returnType=StringType()) -def array_to_range(value: tuple): - """This UDF allows to convert tuple[start, end] to Postgres' range format""" - start, end = value - return f"[{start},{end})" - - -write_df = df.select( - df.id, - array_to_range(df.range_column).alias("range_column"), -) - -writer = DBWriter( - connection=postgres, - target="schema.target_table", -) -writer.run(write_df) -``` - -This can be tricky to implement and may lead to longer write process. -But this does not require extra space on Postgres side, and allows to avoid explicit value cast on every table read. diff --git a/mddocs/markdown/connection/db_connection/postgres/write.md b/mddocs/markdown/connection/db_connection/postgres/write.md deleted file mode 100644 index 3b5822b1f..000000000 --- a/mddocs/markdown/connection/db_connection/postgres/write.md +++ /dev/null @@ -1,183 +0,0 @@ - - -# Writing to Postgres using `DBWriter` - -For writing data to Postgres, use [`DBWriter`](../../../db/db_writer.md#onetl.db.db_writer.db_writer.DBWriter). - -#### WARNING -Please take into account [Postgres <-> Spark type mapping](types.md#postgres-types) - -#### WARNING -It is always recommended to create table explicitly using [Postgres.execute](execute.md#postgres-execute) -instead of relying on Spark’s table DDL generation. - -This is because Spark’s DDL generator can create columns with different precision and types than it is expected, -causing precision loss or other issues. - -## Examples - -```python -from onetl.connection import Postgres -from onetl.db import DBWriter - -postgres = Postgres(...) - -df = ... # data is here - -writer = DBWriter( - connection=postgres, - target="schema.table", - options=Postgres.WriteOptions(if_exists="append"), -) - -writer.run(df) -``` - -## Options - -Method above accepts [`Postgres.WriteOptions`](#onetl.connection.db_connection.postgres.options.PostgresWriteOptions) - -### *pydantic model* onetl.connection.db_connection.postgres.options.PostgresWriteOptions - -Spark JDBC writing options. - -#### Versionadded -Added in version 0.5.0: Replace `Postgres.Options` → `Postgres.WriteOptions` - -### Examples - -#### NOTE -You can pass any value -[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), -even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! - -The set of supported options depends on Spark version. - -```python -from onetl.connection import Postgres - -options = Postgres.WriteOptions( - if_exists="append", - batchsize=20_000, - customSparkOption="value", -) -``` - - - -#### *field* if_exists *: JDBCTableExistBehavior* *= JDBCTableExistBehavior.APPEND* *(alias 'mode')* - -Behavior of writing data into existing table. - -Possible values: -: * `append` (default) - : Adds new rows into existing table. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : Data is appended to a table. Table has the same DDL as before writing data -
- #### WARNING - This mode does not check whether table already contains - rows from dataframe, so duplicated rows can be created. -
- Also Spark does not support passing custom options to - insert statement, like `ON CONFLICT`, so don’t try to - implement deduplication using unique indexes or constraints. -
- Instead, write to staging table and perform deduplication - using `execute` method. - * `replace_entire_table` - : **Table is dropped and then created, or truncated**. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : Table content is replaced with dataframe content. -
- After writing completed, target table could either have the same DDL as - before writing data (`truncate=True`), or can be recreated (`truncate=False` - or source does not support truncation). - * `ignore` - : Ignores the write operation if the table already exists. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : The write operation is ignored, and no data is written to the table. - * `error` - : Raises an error if the table already exists. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : An error is raised, and no data is written to the table. - -#### Versionchanged -Changed in version 0.9.0: Renamed `mode` → `if_exists` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* batchsize *: int* *= 20000* - -How many rows can be inserted per round trip. - -Tuning this option can influence performance of writing. - -#### WARNING -Default value is different from Spark. - -Spark uses quite small value `1000`, which is absolutely not usable -in BigData world. - -Thus we’ve overridden default value with `20_000`, -which should increase writing performance. - -You can increase it even more, up to `50_000`, -but it depends on your database load and number of columns in the row. -Higher values does not increase performance. - -#### Versionchanged -Changed in version 0.4.0: Changed default value from 1000 to 20_000 - - - -#### *field* isolation_level *: str* *= 'READ_UNCOMMITTED'* *(alias 'isolationLevel')* - -The transaction isolation level, which applies to current connection. - -Possible values: -: * `NONE` (as string, not Python’s `None`) - * `READ_COMMITTED` - * `READ_UNCOMMITTED` - * `REPEATABLE_READ` - * `SERIALIZABLE` - -Values correspond to transaction isolation levels defined by JDBC standard. -Please refer the documentation for -[java.sql.Connection](https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html). - - diff --git a/mddocs/markdown/connection/db_connection/teradata/connection.md b/mddocs/markdown/connection/db_connection/teradata/connection.md deleted file mode 100644 index de2745c4e..000000000 --- a/mddocs/markdown/connection/db_connection/teradata/connection.md +++ /dev/null @@ -1,134 +0,0 @@ - - -# Teradata connection - -### *class* onetl.connection.db_connection.teradata.connection.Teradata(\*, spark: SparkSession, user: str, password: SecretStr, host: Host, port: int = 1025, database: str | None = None, extra: TeradataExtra = TeradataExtra(CHARSET='UTF8', COLUMN_NAME='ON', FLATTEN='ON', MAYBENULL='ON', STRICT_NAMES='OFF')) - -Teradata JDBC connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on package [com.teradata.jdbc:terajdbc:17.20.00.15](https://central.sonatype.com/artifact/com.teradata.jdbc/terajdbc/17.20.00.15) -([official Teradata JDBC driver](https://downloads.teradata.com/download/connectivity/jdbc-driver)). - -#### SEE ALSO -Before using this connector please take into account [Prerequisites](prerequisites.md#teradata-prerequisites) - -#### Versionadded -Added in version 0.1.0. - -* **Parameters:** - **host** - : Host of Teradata database. For example: `test.teradata.domain.com` or `193.168.1.12` - - **port** - : Port of Teradata database - - **user** - : User, which have proper access to the database. For example: `some_user` - - **password** - : Password for database connection - - **database** - : Database in RDBMS, NOT schema. -
- See [this page](https://www.educba.com/postgresql-database-vs-schema/) for more details - - **spark** - : Spark session. - - **extra** - : Specifies one or more extra parameters which should be appended to a connection string. -
- For example: `{"TMODE": "TERA", "MAYBENULL": "ON", "CHARSET": "UTF8", "LOGMECH":"LDAP"}` -
- See [Teradata JDBC driver documentation](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/jdbcug_chapter_2.html#BABJIHBJ) - for more details -
- #### NOTE - By default, these options are added to extra: - > * `CHARSET = "UTF8"` - > * `COLUMN_NAME = "ON"` - allow reading column title from a table - > * `FLATTEN = "ON"` - improves error messages - > * `MAYBENULL = "ON"` - > * `STRICT_NAMES = "OFF"` - ignore Spark options passed to JDBC URL -
- It is possible to override default values, for example set `extra={"FLATTEN": "OFF"}` - -### Examples - -Create Teradata connection with LDAP auth: - -```python -from onetl.connection import Teradata -from pyspark.sql import SparkSession - -# Create Spark session with Teradata driver loaded -maven_packages = Teradata.get_packages() -spark = ( - SparkSession.builder.appName("spark-app-name") - .config("spark.jars.packages", ",".join(maven_packages)) - .getOrCreate() -) - -# Create connection -teradata = Teradata( - host="database.host.or.ip", - user="user", - password="*****", - extra={ - "TMODE": "TERA", # "TERA" or "ANSI" - "LOGMECH": "LDAP", - "LOG": "TIMING", # increase log level - }, - spark=spark, -).check() -``` - - - -#### check() - -Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If not, an exception will be raised. - -* **Returns:** - Connection itself -* **Raises:** - RuntimeError - : If the connection is not available - -### Examples - -```python -connection.check() -``` - - - -#### *classmethod* get_packages(package_version: str | None = None) → list[str] - -Get package names to be downloaded by Spark. Allows specifying custom JDBC driver versions for Teradata. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **package_version** - : Specifies the version of the Teradata JDBC driver to use. Defaults to `17.20.00.15`. -
- #### Versionadded - Added in version 0.11.0. - -### Examples - -```python -from onetl.connection import Teradata - -Teradata.get_packages() - -# specify custom driver version -Teradata.get_packages(package_version="20.00.00.18") -``` - - diff --git a/mddocs/markdown/connection/db_connection/teradata/execute.md b/mddocs/markdown/connection/db_connection/teradata/execute.md deleted file mode 100644 index e836b8a6d..000000000 --- a/mddocs/markdown/connection/db_connection/teradata/execute.md +++ /dev/null @@ -1,189 +0,0 @@ - - -# Executing statements in Teradata - -#### WARNING -Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. - -Do **NOT** use them to read large amounts of data. Use [DBReader](read.md#teradata-read) or [Teradata.sql](sql.md#teradata-sql) instead. - -## How to - -There are 2 ways to execute some statement in Teradata - -### Use `Teradata.fetch` - -Use this method to execute some `SELECT` query which returns **small number or rows**, like reading -Teradata config, or reading data from some reference table. Method returns Spark DataFrame. - -Method accepts [`Teradata.FetchOptions`](#onetl.connection.db_connection.teradata.options.TeradataFetchOptions). - -Connection opened using this method should be then closed with `connection.close()` or `with connection:`. - -#### Syntax support - -This method supports **any** query syntax supported by Teradata, like: - -* ✅︎ `SELECT ... FROM ...` -* ✅︎ `WITH alias AS (...) SELECT ...` -* ✅︎ `SHOW ...` -* ❌ `SET ...; SELECT ...;` - multiple statements not supported - -#### Examples - -```python -from onetl.connection import Teradata - -teradata = Teradata(...) - -df = teradata.fetch( - "SELECT value FROM some.reference_table WHERE key = 'some_constant'", - options=Teradata.FetchOptions(queryTimeout=10), -) -teradata.close() -value = df.collect()[0][0] # get value from first row and first column -``` - -### Use `Teradata.execute` - -Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. - -Method accepts [`Teradata.ExecuteOptions`](#onetl.connection.db_connection.teradata.options.TeradataExecuteOptions). - -Connection opened using this method should be then closed with `connection.close()` or `with connection:`. - -#### Syntax support - -This method supports **any** query syntax supported by Teradata, like: - -* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on -* ✅︎ `ALTER ...` -* ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on -* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on -* ✅︎ `CALL procedure(arg1, arg2) ...` or `{call procedure(arg1, arg2)}` - special syntax for calling procedure -* ✅︎ `EXECUTE macro(arg1, arg2)` -* ✅︎ `EXECUTE FUNCTION ...` -* ✅︎ other statements not mentioned here -* ❌ `SET ...; SELECT ...;` - multiple statements not supported - -#### Examples - -```python -from onetl.connection import Teradata - -teradata = Teradata(...) - -teradata.execute("DROP TABLE database.table") -teradata.execute( - """ - CREATE MULTISET TABLE database.table AS ( - id BIGINT, - key VARCHAR, - value REAL - ) - NO PRIMARY INDEX - """, - options=Teradata.ExecuteOptions(queryTimeout=10), -) -``` - -## Options - -### *pydantic model* onetl.connection.db_connection.teradata.options.TeradataFetchOptions - -Options related to fetching data from databases via JDBC. - -#### Versionadded -Added in version 0.11.0: Replace `Teradata.JDBCOptions` → `Teradata.FetchOptions` - -### Examples - -#### NOTE -You can pass any value supported by underlying JDBC driver class, -even if it is not mentioned in this documentation. - -```python -from onetl.connection import Teradata - -options = Teradata.FetchOptions( - queryTimeout=60_000, - fetchsize=100_000, - customSparkOption="value", -) -``` - - -* **Fields:** - - [`fetchsize (int | None)`](#onetl.connection.db_connection.teradata.options.TeradataFetchOptions.fetchsize) - - [`query_timeout (int | None)`](#onetl.connection.db_connection.teradata.options.TeradataFetchOptions.query_timeout) - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int | None* *= None* - -How many rows to fetch per round trip. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value depends on driver. For example, Oracle has -default `fetchsize=10`. - - - -### *pydantic model* onetl.connection.db_connection.teradata.options.TeradataExecuteOptions - -Options related to executing statements in databases via JDBC. - -#### Versionadded -Added in version 0.11.0: Replace `Teradata.JDBCOptions` → `Teradata.ExecuteOptions` - -### Examples - -#### NOTE -You can pass any value supported by underlying JDBC driver class, -even if it is not mentioned in this documentation. - -```python -from onetl.connection import Teradata - -options = Teradata.ExecuteOptions( - queryTimeout=60_000, - customSparkOption="value", -) -``` - - -* **Fields:** - - [`fetchsize (int | None)`](#onetl.connection.db_connection.teradata.options.TeradataExecuteOptions.fetchsize) - - [`query_timeout (int | None)`](#onetl.connection.db_connection.teradata.options.TeradataExecuteOptions.query_timeout) - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int | None* *= None* - -How many rows to fetch per round trip. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value depends on driver. For example, Oracle has -default `fetchsize=10`. - - diff --git a/mddocs/markdown/connection/db_connection/teradata/index.md b/mddocs/markdown/connection/db_connection/teradata/index.md deleted file mode 100644 index f35e86981..000000000 --- a/mddocs/markdown/connection/db_connection/teradata/index.md +++ /dev/null @@ -1,15 +0,0 @@ - - -# Teradata - -# Connection - -* [Prerequisites](prerequisites.md) -* [Teradata connection](connection.md) - -# Operations - -* [Reading from Teradata using `DBReader`](read.md) -* [Reading from Teradata using `Teradata.sql`](sql.md) -* [Writing to Teradata using `DBWriter`](write.md) -* [Executing statements in Teradata](execute.md) diff --git a/mddocs/markdown/connection/db_connection/teradata/prerequisites.md b/mddocs/markdown/connection/db_connection/teradata/prerequisites.md deleted file mode 100644 index 3217390e2..000000000 --- a/mddocs/markdown/connection/db_connection/teradata/prerequisites.md +++ /dev/null @@ -1,57 +0,0 @@ - - -# Prerequisites - -## Version Compatibility - -* Teradata server versions: - : * Officially declared: 16.10 - 20.0 - * Actually tested: 16.10 -* Spark versions: 2.3.x - 3.5.x -* Java versions: 8 - 20 - -See [official documentation](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/platformMatrix.html). - -## Installing PySpark - -To use Teradata connector you should have PySpark installed (or injected to `sys.path`) -BEFORE creating the connector instance. - -See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. - -## Connecting to Teradata - -### Connection host - -It is possible to connect to Teradata by using either DNS name Parsing Engine (PE) host, or it’s IP address. - -### Connection port - -Connection is usually performed to port `1025`. Port may differ for different Teradata instances. -Please ask your Teradata administrator to provide required information. - -### Required grants - -Ask your Teradata cluster administrator to set following grants for a user, -used for creating a connection: - -Read + Write - -```sql --- allow creating tables in the target schema -GRANT CREATE TABLE ON database TO username; - --- allow read & write access to specific table -GRANT SELECT, INSERT ON database.mytable TO username; -``` - -Read only - -```sql --- allow read access to specific table -GRANT SELECT ON database.mytable TO username; -``` - -See: -: * [Teradata access rights](https://www.dwhpro.com/teradata-access-rights/) - * [GRANT documentation](https://teradata.github.io/presto/docs/0.167-t/sql/grant.html) diff --git a/mddocs/markdown/connection/db_connection/teradata/read.md b/mddocs/markdown/connection/db_connection/teradata/read.md deleted file mode 100644 index 49c91e222..000000000 --- a/mddocs/markdown/connection/db_connection/teradata/read.md +++ /dev/null @@ -1,379 +0,0 @@ - - -# Reading from Teradata using `DBReader` - -[`DBReader`](../../../db/db_reader.md#onetl.db.db_reader.db_reader.DBReader) supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, -but does not support custom queries, like `JOIN`. - -## Supported DBReader features - -* ✅︎ `columns` -* ✅︎ `where` -* ✅︎ `hwm`, supported strategies: -* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) -* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) -* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) -* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) -* ❌ `hint` (is not supported by Teradata) -* ❌ `df_schema` -* ✅︎ `options` (see [`Teradata.ReadOptions`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions)) - -## Examples - -Snapshot strategy: - -```python -from onetl.connection import Teradata -from onetl.db import DBReader - -teradata = Teradata(...) - -reader = DBReader( - connection=teradata, - source="database.table", - columns=["id", "key", "CAST(value AS VARCHAR) value", "updated_dt"], - where="key = 'something'", - options=Teradata.ReadOptions( - partitioning_mode="hash", - partitionColumn="id", - numPartitions=10, - ), -) -df = reader.run() -``` - -Incremental strategy: - -```python -from onetl.connection import Teradata -from onetl.db import DBReader -from onetl.strategy import IncrementalStrategy - -teradata = Teradata(...) - -reader = DBReader( - connection=teradata, - source="database.table", - columns=["id", "key", "CAST(value AS VARCHAR) value", "updated_dt"], - where="key = 'something'", - hwm=DBReader.AutoDetectHWM(name="teradata_hwm", expression="updated_dt"), - options=Teradata.ReadOptions( - partitioning_mode="hash", - partitionColumn="id", - numPartitions=10, - ), -) - -with IncrementalStrategy(): - df = reader.run() -``` - -## Recommendations - -### Select only required columns - -Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Teradata to Spark. - -### Pay attention to `where` value - -Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. -This both reduces the amount of data send from Teradata to Spark, and may also improve performance of the query. -Especially if there are indexes or partitions for columns used in `where` clause. - -### Read data in parallel - -`DBReader` can read data in multiple parallel connections by passing `Teradata.ReadOptions(numPartitions=..., partitionColumn=...)`. - -In the example above, Spark opens 10 parallel connections, and data is evenly distributed between all these connections using expression -`HASHAMP(HASHBUCKET(HASHROW({partition_column}))) MOD {num_partitions}`. -This allows sending each Spark worker only some piece of data, reducing resource consumption. -`partition_column` here can be table column of any type. - -It is also possible to use `partitioning_mode="mod"` or `partitioning_mode="range"`, but in this case -`partition_column` have to be an integer, should not contain `NULL`, and values to be uniformly distributed. -It is also less performant than `partitioning_mode="hash"` due to Teradata `HASHAMP` implementation. - -### Do **NOT** use `TYPE=FASTEXPORT` - -Teradata supports several [different connection types](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/jdbcug_chapter_2.html#BABFGFAF): -: * `TYPE=DEFAULT` - perform plain `SELECT` queries - * `TYPE=FASTEXPORT` - uses special FastExport protocol for select queries - -But `TYPE=FASTEXPORT` uses exclusive lock on the source table, so it is impossible to use multiple Spark workers parallel data read. -This leads to sending all the data to just one Spark worker, which is slow and takes a lot of RAM. - -Prefer using `partitioning_mode="hash"` from example above. - -## Options - -### *pydantic model* onetl.connection.db_connection.teradata.options.TeradataReadOptions - -Spark JDBC reading options. - -#### Versionadded -Added in version 0.5.0: Replace `Teradata.Options` → `Teradata.ReadOptions` - -### Examples - -#### NOTE -You can pass any value -[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), -even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! - -The set of supported options depends on Spark version. - -```python -from onetl.connection import Teradata - -options = Teradata.ReadOptions( - partitioning_mode="range", - partitionColumn="reg_id", - numPartitions=10, - customSparkOption="value", -) -``` - - -* **Fields:** - - [`fetchsize (int)`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.fetchsize) - - [`lower_bound (int | None)`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.lower_bound) - - [`num_partitions (pydantic.types.PositiveInt)`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.num_partitions) - - [`partition_column (str | None)`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.partition_column) - - [`partitioning_mode (onetl.connection.db_connection.jdbc_connection.options.JDBCPartitioningMode)`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.partitioning_mode) - - [`query_timeout (int | None)`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.query_timeout) - - [`session_init_statement (str | None)`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.session_init_statement) - - [`upper_bound (int | None)`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.upper_bound) - -#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* - -Column used to parallelize reading from a table. - -#### WARNING -It is highly recommended to use primary key, or column with an index -to avoid performance issues. - -#### NOTE -Column type depends on [`partitioning_mode`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.partitioning_mode). - -* `partitioning_mode="range"` requires column to be an integer, date or timestamp (can be NULL, but not recommended). -* `partitioning_mode="hash"` accepts any column type (NOT NULL). -* `partitioning_mode="mod"` requires column to be an integer (NOT NULL). - -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.partitioning_mode) for more details - - - -#### *field* num_partitions *: PositiveInt* *= 1* *(alias 'numPartitions')* - -Number of jobs created by Spark to read the table content in parallel. -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.partitioning_mode) for more details - - -* **Constraints:** - - **exclusiveMinimum** = 0 - -#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* - -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.partitioning_mode) for more details - - - -#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* - -See documentation for [`partitioning_mode`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.partitioning_mode) for more details - - - -#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* - -After each database session is opened to the remote DB and before starting to read data, -this option executes a custom SQL statement (or a PL/SQL block). - -Use this to implement session initialization code. - -Example: - -```python -sessionInitStatement = """ - BEGIN - execute immediate - 'alter session set "_serial_direct_read"=true'; - END; -""" -``` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int* *= 100000* - -Fetch N rows from an opened cursor per one read round. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value is different from Spark. - -Spark uses driver’s own value, and it may be different in different drivers, -and even versions of the same driver. For example, Oracle has -default `fetchsize=10`, which is absolutely not usable. - -Thus we’ve overridden default value with `100_000`, which should increase reading performance. - -#### Versionchanged -Changed in version 0.2.0: Set explicit default value to `100_000` - - - -#### *field* partitioning_mode *: JDBCPartitioningMode* *= JDBCPartitioningMode.RANGE* - -Defines how Spark will parallelize reading from table. - -Possible values: - -* `range` (default) - : Allocate each executor a range of values from column passed into [`partition_column`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.partition_column). -
- ### Spark generates for each executor an SQL query -
- Executor 1: - ```sql - SELECT ... FROM table - WHERE (partition_column >= lowerBound - OR partition_column IS NULL) - AND partition_column < (lower_bound + stride) - ``` -
- Executor 2: - ```sql - SELECT ... FROM table - WHERE partition_column >= (lower_bound + stride) - AND partition_column < (lower_bound + 2 * stride) - ``` -
- … -
- Executor N: - ```sql - SELECT ... FROM table - WHERE partition_column >= (lower_bound + (N-1) * stride) - AND partition_column <= upper_bound - ``` -
- Where `stride=(upper_bound - lower_bound) / num_partitions`. -
- #### NOTE - Can be used only with columns of integer, date or timestamp types. -
- #### NOTE - [`lower_bound`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.lower_bound), [`upper_bound`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.upper_bound) and [`num_partitions`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.num_partitions) are used just to - calculate the partition stride, **NOT** for filtering the rows in table. - So all rows in the table will be returned (unlike *Incremental* [Read Strategies](../../../strategy/index.md#strategy)). -
- #### NOTE - All queries are executed in parallel. To execute them sequentially, use *Batch* [Read Strategies](../../../strategy/index.md#strategy). -* `hash` - : Allocate each executor a set of values based on hash of the [`partition_column`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.partition_column) column. -
- ### Spark generates for each executor an SQL query -
- Executor 1: - ```sql - SELECT ... FROM table - WHERE (some_hash(partition_column) mod num_partitions) = 0 -- lower_bound - ``` -
- Executor 2: - ```sql - SELECT ... FROM table - WHERE (some_hash(partition_column) mod num_partitions) = 1 -- lower_bound + 1 - ``` -
- … -
- Executor N: - ```sql - SELECT ... FROM table - WHERE (some_hash(partition_column) mod num_partitions) = num_partitions-1 -- upper_bound - ``` -
- #### NOTE - The hash function implementation depends on RDBMS. It can be `MD5` or any other fast hash function, - or expression based on this function call. Usually such functions accepts any column type as an input. -* `mod` - : Allocate each executor a set of values based on modulus of the [`partition_column`](#onetl.connection.db_connection.teradata.options.TeradataReadOptions.partition_column) column. -
- ### Spark generates for each executor an SQL query -
- Executor 1: - ```sql - SELECT ... FROM table - WHERE (partition_column mod num_partitions) = 0 -- lower_bound - ``` -
- Executor 2: - ```sql - SELECT ... FROM table - WHERE (partition_column mod num_partitions) = 1 -- lower_bound + 1 - ``` -
- Executor N: - ```sql - SELECT ... FROM table - WHERE (partition_column mod num_partitions) = num_partitions-1 -- upper_bound - ``` -
- #### NOTE - Can be used only with columns of integer type. - -#### Versionadded -Added in version 0.5.0. - -### Examples - -Read data in 10 parallel jobs by range of values in `id_column` column: - -```python -ReadOptions( - partitioning_mode="range", # default mode, can be omitted - partitionColumn="id_column", - numPartitions=10, - # Options below can be discarded because they are - # calculated automatically as MIN and MAX values of `partitionColumn` - lowerBound=0, - upperBound=100_000, -) -``` - -Read data in 10 parallel jobs by hash of values in `some_column` column: - -```python -ReadOptions( - partitioning_mode="hash", - partitionColumn="some_column", - numPartitions=10, - # lowerBound and upperBound are automatically set to `0` and `9` -) -``` - -Read data in 10 parallel jobs by modulus of values in `id_column` column: - -```python -ReadOptions( - partitioning_mode="mod", - partitionColumn="id_column", - numPartitions=10, - # lowerBound and upperBound are automatically set to `0` and `9` -) -``` - - diff --git a/mddocs/markdown/connection/db_connection/teradata/sql.md b/mddocs/markdown/connection/db_connection/teradata/sql.md deleted file mode 100644 index 651dd6e5f..000000000 --- a/mddocs/markdown/connection/db_connection/teradata/sql.md +++ /dev/null @@ -1,190 +0,0 @@ - - -# Reading from Teradata using `Teradata.sql` - -`Teradata.sql` allows passing custom SQL query, but does not support incremental strategies. - -#### WARNING -Statement is executed in **read-write** connection, so if you’re calling some functions/procedures with DDL/DML statements inside, -they can change data in your database. - -## Syntax support - -Only queries with the following syntax are supported: - -* ✅︎ `SELECT ... FROM ...` -* ✅︎ `WITH alias AS (...) SELECT ...` -* ❌ `SHOW ...` -* ❌ `SET ...; SELECT ...;` - multiple statements not supported - -## Examples - -```python -from onetl.connection import Teradata - -teradata = Teradata(...) -df = teradata.sql( - """ - SELECT - id, - key, - CAST(value AS VARCHAR) AS value, - updated_at, - HASHAMP(HASHBUCKET(HASHROW(id))) MOD 10 AS part_column - FROM - database.mytable - WHERE - key = 'something' - """, - options=Teradata.SQLOptions( - partitionColumn="id", - numPartitions=10, - lowerBound=0, - upperBound=1000, - ), -) -``` - -## Recommendations - -### Select only required columns - -Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. -This reduces the amount of data passed from Teradata to Spark. - -### Pay attention to `where` value - -Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. -This both reduces the amount of data send from Teradata to Spark, and may also improve performance of the query. -Especially if there are indexes or partitions for columns used in `where` clause. - -## Options - -### *pydantic model* onetl.connection.db_connection.teradata.options.TeradataSQLOptions - -Options specifically for SQL queries - -These options allow you to specify configurations for executing SQL queries -without relying on Spark’s partitioning mechanisms. - -#### Versionadded -Added in version 0.11.0: Split up `Teradata.ReadOptions` to `Teradata.SQLOptions` - -### Examples - -#### NOTE -You can pass any JDBC configuration -[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), -tailored to optimize SQL query execution. **Option names should be in** `camelCase`! - -```python -from onetl.connection import Teradata - -options = Teradata.SQLOptions( - partitionColumn="reg_id", - numPartitions=10, - lowerBound=0, - upperBound=1000, - customSparkOption="value", -) -``` - - - -#### *field* partition_column *: str | None* *= None* *(alias 'partitionColumn')* - -Column used to partition data across multiple executors for parallel query processing. - -#### WARNING -It is highly recommended to use primary key, or column with an index -to avoid performance issues. - -### Example of using `partitionColumn="id"` with `partitioning_mode="range"` - -```sql --- If partition_column is 'id', with numPartitions=4, lowerBound=1, and upperBound=100: --- Executor 1 processes IDs from 1 to 25 -SELECT ... FROM table WHERE id >= 1 AND id < 26 --- Executor 2 processes IDs from 26 to 50 -SELECT ... FROM table WHERE id >= 26 AND id < 51 --- Executor 3 processes IDs from 51 to 75 -SELECT ... FROM table WHERE id >= 51 AND id < 76 --- Executor 4 processes IDs from 76 to 100 -SELECT ... FROM table WHERE id >= 76 AND id <= 100 - --- General case for Executor N -SELECT ... FROM table -WHERE partition_column >= (lowerBound + (N-1) * stride) -AND partition_column <= upperBound --- Where ``stride`` is calculated as ``(upperBound - lowerBound) / numPartitions``. -``` - - - -#### *field* num_partitions *: int | None* *= None* *(alias 'numPartitions')* - -Number of jobs created by Spark to read the table content in parallel. - - - -#### *field* lower_bound *: int | None* *= None* *(alias 'lowerBound')* - -Defines the starting boundary for partitioning the query’s data. Mandatory if [`partition_column`](#onetl.connection.db_connection.teradata.options.TeradataSQLOptions.partition_column) is set - - - -#### *field* upper_bound *: int | None* *= None* *(alias 'upperBound')* - -Sets the ending boundary for data partitioning. Mandatory if [`partition_column`](#onetl.connection.db_connection.teradata.options.TeradataSQLOptions.partition_column) is set - - - -#### *field* session_init_statement *: str | None* *= None* *(alias 'sessionInitStatement')* - -After each database session is opened to the remote DB and before starting to read data, -this option executes a custom SQL statement (or a PL/SQL block). - -Use this to implement session initialization code. - -Example: - -```python -sessionInitStatement = """ - BEGIN - execute immediate - 'alter session set "_serial_direct_read"=true'; - END; -""" -``` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* fetchsize *: int* *= 100000* - -Fetch N rows from an opened cursor per one read round. - -Tuning this option can influence performance of reading. - -#### WARNING -Default value is different from Spark. - -Spark uses driver’s own value, and it may be different in different drivers, -and even versions of the same driver. For example, Oracle has -default `fetchsize=10`, which is absolutely not usable. - -Thus we’ve overridden default value with `100_000`, which should increase reading performance. - -#### Versionchanged -Changed in version 0.2.0: Set explicit default value to `100_000` - - diff --git a/mddocs/markdown/connection/db_connection/teradata/write.md b/mddocs/markdown/connection/db_connection/teradata/write.md deleted file mode 100644 index ba160f90c..000000000 --- a/mddocs/markdown/connection/db_connection/teradata/write.md +++ /dev/null @@ -1,250 +0,0 @@ - - -# Writing to Teradata using `DBWriter` - -For writing data to Teradata, use [`DBWriter`](../../../db/db_writer.md#onetl.db.db_writer.db_writer.DBWriter). - -#### WARNING -It is always recommended to create table explicitly using [Teradata.execute](execute.md#teradata-execute) -instead of relying on Spark’s table DDL generation. - -This is because Spark’s DDL generator can create columns with different precision and types than it is expected, -causing precision loss or other issues. - -## Examples - -```python -from onetl.connection import Teradata -from onetl.db import DBWriter - -teradata = Teradata( - ..., - extra={"TYPE": "FASTLOAD", "TMODE": "TERA"}, -) - -df = ... # data is here - -writer = DBWriter( - connection=teradata, - target="database.table", - options=Teradata.WriteOptions( - if_exists="append", - # avoid creating SET table, use MULTISET - createTableOptions="NO PRIMARY INDEX", - ), -) - -writer.run(df.repartition(1)) -``` - -## Recommendations - -### Number of connections - -Teradata is not MVCC based, so write operations take exclusive lock on the entire table. -So **it is impossible to write data to Teradata table in multiple parallel connections**, no exceptions. - -The only way to write to Teradata without making deadlocks is write dataframe with exactly 1 partition. - -It can be implemented using `df.repartition(1)`: - -```python -# do NOT use df.coalesce(1) as it can freeze -writer.run(df.repartition(1)) -``` - -This moves all the data to just one Spark worker, so it may consume a lot of RAM. It is usually require to increase `spark.executor.memory` to handle this. - -Another way is to write all dataframe partitions one-by-one: - -```python -from pyspark.sql.functions import spark_partition_id - -# get list of all partitions in the dataframe -partitions = sorted(df.select(spark_partition_id()).distinct().collect()) - -for partition in partitions: - # get only part of data within this exact partition - part_df = df.where(**partition.asDict()).coalesce(1) - - writer.run(part_df) -``` - -This require even data distribution for all partitions to avoid data skew and spikes of RAM consuming. - -### Choosing connection type - -Teradata supports several [different connection types](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/jdbcug_chapter_2.html#BABFGFAF): -: * `TYPE=DEFAULT` - perform plain `INSERT` queries - * `TYPE=FASTLOAD` - uses special FastLoad protocol for insert queries - -It is always recommended to use `TYPE=FASTLOAD` because: -: * It provides higher performance - * It properly handles inserting `NULL` values (`TYPE=DEFAULT` raises an exception) - -But it can be used only during write, not read. - -### Choosing transaction mode - -Teradata supports [2 different transaction modes](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/jdbcug_chapter_2.html#TMODESEC): -: * `TMODE=ANSI` - * `TMODE=TERA` - -Choosing one of the modes can alter connector behavior. For example: -: * Inserting data which exceeds table column length, like insert `CHAR(25)` to column with type `CHAR(24)`: - * * `TMODE=ANSI` - raises exception - * * `TMODE=TERA` - truncates input string to 24 symbols - * Creating table using Spark: - * * `TMODE=ANSI` - creates `MULTISET` table - * * `TMODE=TERA` - creates `SET` table with `PRIMARY KEY` is a first column in dataframe. - This can lead to slower insert time, because each row will be checked against a unique index. - Fortunately, this can be disabled by passing custom `createTableOptions`. - -## Options - -Method above accepts [`Teradata.WriteOptions`](#onetl.connection.db_connection.teradata.options.TeradataWriteOptions) - -### *pydantic model* onetl.connection.db_connection.teradata.options.TeradataWriteOptions - -Spark JDBC writing options. - -#### Versionadded -Added in version 0.5.0: Replace `Teradata.Options` → `Teradata.WriteOptions` - -### Examples - -#### NOTE -You can pass any value -[supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html), -even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! - -The set of supported options depends on Spark version. - -```python -from onetl.connection import Teradata - -options = Teradata.WriteOptions( - if_exists="append", - batchsize=20_000, - customSparkOption="value", -) -``` - - - -#### *field* if_exists *: JDBCTableExistBehavior* *= JDBCTableExistBehavior.APPEND* *(alias 'mode')* - -Behavior of writing data into existing table. - -Possible values: -: * `append` (default) - : Adds new rows into existing table. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : Data is appended to a table. Table has the same DDL as before writing data -
- #### WARNING - This mode does not check whether table already contains - rows from dataframe, so duplicated rows can be created. -
- Also Spark does not support passing custom options to - insert statement, like `ON CONFLICT`, so don’t try to - implement deduplication using unique indexes or constraints. -
- Instead, write to staging table and perform deduplication - using `execute` method. - * `replace_entire_table` - : **Table is dropped and then created, or truncated**. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : Table content is replaced with dataframe content. -
- After writing completed, target table could either have the same DDL as - before writing data (`truncate=True`), or can be recreated (`truncate=False` - or source does not support truncation). - * `ignore` - : Ignores the write operation if the table already exists. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : The write operation is ignored, and no data is written to the table. - * `error` - : Raises an error if the table already exists. -
- ### Behavior in details -
- * Table does not exist - : Table is created using options provided by user - (`createTableOptions`, `createTableColumnTypes`, etc). - * Table exists - : An error is raised, and no data is written to the table. - -#### Versionchanged -Changed in version 0.9.0: Renamed `mode` → `if_exists` - - - -#### *field* query_timeout *: int | None* *= None* *(alias 'queryTimeout')* - -The number of seconds the driver will wait for a statement to execute. -Zero means there is no limit. - -This option depends on driver implementation, -some drivers can check the timeout of each query instead of an entire JDBC batch. - - - -#### *field* batchsize *: int* *= 20000* - -How many rows can be inserted per round trip. - -Tuning this option can influence performance of writing. - -#### WARNING -Default value is different from Spark. - -Spark uses quite small value `1000`, which is absolutely not usable -in BigData world. - -Thus we’ve overridden default value with `20_000`, -which should increase writing performance. - -You can increase it even more, up to `50_000`, -but it depends on your database load and number of columns in the row. -Higher values does not increase performance. - -#### Versionchanged -Changed in version 0.4.0: Changed default value from 1000 to 20_000 - - - -#### *field* isolation_level *: str* *= 'READ_UNCOMMITTED'* *(alias 'isolationLevel')* - -The transaction isolation level, which applies to current connection. - -Possible values: -: * `NONE` (as string, not Python’s `None`) - * `READ_COMMITTED` - * `READ_UNCOMMITTED` - * `REPEATABLE_READ` - * `SERIALIZABLE` - -Values correspond to transaction isolation levels defined by JDBC standard. -Please refer the documentation for -[java.sql.Connection](https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html). - - diff --git a/mddocs/markdown/connection/file_connection/ftp.md b/mddocs/markdown/connection/file_connection/ftp.md deleted file mode 100644 index 6ecce3655..000000000 --- a/mddocs/markdown/connection/file_connection/ftp.md +++ /dev/null @@ -1,627 +0,0 @@ - - -# FTP connection - -### *class* onetl.connection.file_connection.ftp.FTP(\*, host: Host, port: int = 21, user: str | None = None, password: SecretStr | None = None) - -FTP file connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on [FTPUtil library](https://pypi.org/project/ftputil/). - -#### WARNING -Since onETL v0.7.0 to use FTP connector you should install package as follows: - -```bash -pip install onetl[ftp] - -# or -pip install onetl[files] -``` - -See [File connections](../../install/files.md#install-files) installation instruction for more details. - -#### Versionadded -Added in version 0.1.0. - -* **Parameters:** - **host** - : Host of FTP source. For example: `ftp.domain.com` - - **port** - : Port of FTP source - - **user** - : User, which have access to the file source. For example: `someuser`. -
- `None` means that the user is anonymous. - - **password** - : Password for file source connection. -
- `None` means that the user is anonymous. - -### Examples - -Create and check FTP connection: - -```python -from onetl.connection import FTP - -ftp = FTP( - host="ftp.domain.com", - user="someuser", - password="*****", -).check() -``` - - - -#### \_\_init_\_(\*\*kwargs) - - - -#### check() - -Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If not, an exception will be raised. - -* **Returns:** - Connection itself -* **Raises:** - RuntimeError - : If the connection is not available - -### Examples - -```python -connection.check() -``` - - - -#### create_dir(path: PathLike | str) → RemoteDirectory - -Creates directory tree on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Directory path -* **Returns:** - Created directory with stats -* **Raises:** - `onetl.exception.NotAFileError` - : Path is not a file - -### Examples - -```pycon ->>> dir_path = connection.create_dir("/path/to/dir") ->>> os.fspath(dir_path) -'/path/to/dir' -``` - - - -#### download_file(remote_file_path: PathLike | str, local_file_path: PathLike | str, replace: bool = True) → LocalPath - -Downloads file from the remote filesystem to a local path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -Supports only one file download per call. Directory download is **NOT** supported, use [File Downloader](../../file/file_downloader/file_downloader.md#file-downloader) instead. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **remote_file_path** - : Remote file path to read from - - **local_file_path** - : Local file path to create - - **replace** - : If `True`, existing file will be replaced -* **Returns:** - Local file with stats. -* **Raises:** - `onetl.exception.NotAFileError` - : Remote or local path is not a file - - FileNotFoundError - : Remote file does not exist - - FileExistsError - : Local file already exists, and `replace=False` - - `onetl.exception.FileSizeMismatchError` - : Target file size after download is different from source file size. - -### Examples - -```pycon ->>> local_file = connection.download_file( -... remote_file_path="/path/to/source.csv", -... local_file_path="/path/to/target.csv", -... ) ->>> os.fspath(local_file) -'/path/to/target.csv' ->>> local_file.exists() -True ->>> local_file.stat().st_size # in bytes -1024 ->>> connection.get_stat("/path/to/source.csv").st_size # same size -1024 -``` - - - -#### get_stat(path: PathLike | str) → PathStatProtocol - -Returns stats for a specific path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to get stats for -* **Returns:** - Stats object -* **Raises:** - Any underlying client exception - -### Examples - -```pycon ->>> stat = connection.get_stat("/path/to/file.csv") ->>> stat.st_size # in bytes -1024 ->>> stat.st_uid # owner id or name -12345 -``` - - - -#### is_dir(path: PathLike | str) → bool - -Check if specified path is a directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if path is a directory, `False` otherwise. -* **Raises:** - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - -### Examples - -```pycon ->>> connection.is_dir("/path/to/dir") -True ->>> connection.is_dir("/path/to/dir/file.csv") -False -``` - - - -#### is_file(path: PathLike | str) → bool - -Check if specified path is a file. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if path is a file, `False` otherwise. -* **Raises:** - FileNotFoundError - : Path does not exist - -### Examples - -```pycon ->>> connection.is_file("/path/to/dir/file.csv") -True ->>> connection.is_file("/path/to/dir") -False -``` - - - -#### list_dir(path: PathLike | str, filters: Iterable[[BaseFileFilter](../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → list[RemoteDirectory | RemoteFile] - -Return list of child files/directories in a specific directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Directory path to list contents. - - **filters** - : Return only files/directories matching these filters. See [File Filters](../../file/file_filters/index.md#file-filters) - - **limits** - : Apply limits to the list of files/directories, and stop if one of the limits is reached. - See [File Limits](../../file/file_limits/index.md#file-limits) -* **Returns:** - List of `onetl.base.PathWithStatsProtocol` -* **Raises:** - NotADirectoryError - : Path is not a directory - - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - -### Examples - -```pycon ->>> dir_content = connection.list_dir("/path/to/dir") ->>> os.fspath(dir_content[0]) -'file.csv' ->>> connection.path_exists("/path/to/dir/file.csv") -True -``` - - - -#### path_exists(path: PathLike | str) → bool - -Check if specified path exists on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html). - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if path exists, `False` otherwise - -### Examples - -```pycon ->>> connection.path_exists("/path/to/file.csv") -True ->>> connection.path_exists("/path/to/dir") -True ->>> connection.path_exists("/path/to/missing") -False -``` - - - -#### remove_dir(path: PathLike | str, recursive: bool = False) → bool - -Remove directory or directory tree. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If directory does not exist, no exception is raised. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Directory path to remote - - **recursive** - : If `True`, remove directory tree recursively. -* **Returns:** - `True` if directory was removed, `False` if directory does not exist in the first place. -* **Raises:** - NotADirectoryError - : Path is not a directory - -### Examples - -```pycon ->>> connection.remove_dir("/path/to/dir") -True ->>> connection.path_exists("/path/to/dir") -False ->>> connection.path_exists("/path/to/dir/file.csv") -False ->>> connection.remove_dir("/path/to/dir") # already deleted, no error -False -``` - - - -#### remove_file(path: PathLike | str) → bool - -Removes file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If file does not exist, no exception is raised. - -#### WARNING -Supports only one file removal per call. Directory removal is **NOT** supported, use [`remove_dir`](#onetl.connection.file_connection.ftp.FTP.remove_dir) instead. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : File path -* **Returns:** - `True` if file was removed, `False` if file does not exist in the first place. -* **Raises:** - `onetl.exception.NotAFileError` - : Path is not a file - -### Examples - -```pycon ->>> connection.remove_file("/path/to/file.csv") -True ->>> connection.path_exists("/path/to/dir/file.csv") -False ->>> connection.remove_file("/path/to/file.csv") # already deleted, no error -False -``` - - - -#### rename_dir(source_dir_path: PathLike | str, target_dir_path: PathLike | str, replace: bool = False) → RemoteDirectory - -Rename or move dir on remote filesystem. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **source_dir_path** - : Old directory path - - **target_dir_path** - : New directory path - - **replace** - : If `True`, existing directory will be replaced. -* **Returns:** - New directory path with stats. -* **Raises:** - NotADirectoryError - : Path is not a directory - - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - - `onetl.exception.DirectoryExistsError` - : Directory already exists, and `replace=False` - -### Examples - -```pycon ->>> new_dir = connection.rename_dir("/path/to/dir1", "/path/to/dir2") ->>> os.fspath(new_dir) -'/path/to/dir2' ->>> connection.path_exists("/path/to/dir1") -False ->>> connection.path_exists("/path/to/dir2") -True -``` - - - -#### rename_file(source_file_path: PathLike | str, target_file_path: PathLike | str, replace: bool = False) → RemoteFile - -Rename or move file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -Supports only one file move per call. Directory move/rename is **NOT** supported. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **source_file_path** - : Old file path - - **target_file_path** - : New file path - - **replace** - : If `True`, existing file will be replaced. -* **Returns:** - New file path with stats. -* **Raises:** - `onetl.exception.NotAFileError` - : Source or target path is not a file - - FileNotFoundError - : File does not exist - - FileExistsError - : File already exists, and `replace=False` - -### Examples - -```pycon ->>> new_file = connection.rename_file("/path/to/file1.csv", "/path/to/file2.csv") ->>> os.fspath(new_file) -'/path/to/file2.csv' ->>> connection.path_exists("/path/to/file2.csv") -True ->>> connection.path_exists("/path/to/file1.csv") -False -``` - - - -#### resolve_dir(path: PathLike | str) → RemoteDirectory - -Returns directory at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to resolve -* **Returns:** - Directory path with stats -* **Raises:** - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - - NotADirectoryError - : Path is not a directory - -### Examples - -```pycon ->>> dir_path = connection.resolve_dir("/path/to/dir") ->>> os.fspath(dir_path) -'/path/to/dir' ->>> dir_path.stat().st_uid # owner id -12345 -``` - - - -#### resolve_file(path: PathLike | str) → RemoteFile - -Returns file at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to resolve -* **Returns:** - File path with stats -* **Raises:** - FileNotFoundError - : Path does not exist - - `onetl.exception.NotAFileError` - : Path is not a file - -### Examples - -```pycon ->>> file_path = connection.resolve_file("/path/to/dir/file.csv") ->>> os.fspath(file_path) -'/path/to/dir/file.csv' ->>> file_path.stat().st_uid # owner id -12345 -``` - - - -#### upload_file(local_file_path: PathLike | str, remote_file_path: PathLike | str, replace: bool = False) → RemoteFile - -Uploads local file to a remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -Supports only one file upload per call. Directory upload is **NOT** supported, use [File Uploader](../../file/file_uploader/file_uploader.md#file-uploader) instead. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **local_file_path** - : Local file path to read from - - **remote_file_path** - : Remote file path to create - - **replace** - : If `True`, existing file will be replaced -* **Returns:** - Remote file with stats. -* **Raises:** - `onetl.exception.NotAFileError` - : Remote or local path is not a file - - FileNotFoundError - : Local file does not exist - - FileExistsError - : Remote file already exists, and `replace=False` - - `onetl.exception.FileSizeMismatchError` - : Target file size after upload is different from source file size. - -### Examples - -```pycon ->>> remote_file = connection.upload( -... local_file_path="/path/to/source.csv", -... remote_file_path="/path/to/target.csv", -... ) ->>> os.fspath(remote_file) -'/path/to/target.csv' ->>> connection.path_exists("/path/to/target.csv") -True ->>> remote_file.stat().st_size # in bytes -1024 ->>> os.stat("/path/to/source.csv").st_size # same as source -1024 -``` - - - -#### walk(root: PathLike | str, topdown: bool = True, filters: Iterable[[BaseFileFilter](../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → Iterator[tuple[RemoteDirectory, list[RemoteDirectory], list[RemoteFile]]] - -Walk into directory tree, and iterate over its content in all nesting levels. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Just like `os.walk`, but with additional filter/limit logic. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **root** - : Directory path to walk into. - - **topdown** - : If `True`, walk in top-down order, otherwise walk in bottom-up order. - - **filters** - : Return only files/directories matching these filters. See [File Filters](../../file/file_filters/index.md#file-filters). - - **limits** - : Apply limits to the list of files/directories, and immediately stop if any of these limits is reached. - See [File Limits](../../file/file_limits/index.md#file-limits). -* **Returns:** - `Iterator[tuple[root, dirs, files]]`, like `os.walk`. - - But all the paths are not strings, instead path classes with embedded stats are returned. -* **Raises:** - NotADirectoryError - : Path is not a directory - - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - -### Examples - -```pycon ->>> for root, dirs, files in connection.walk("/path/to/dir"): -... break ->>> os.fspath(root) -'/path/to/dir' ->>> dirs -[] ->>> os.fspath(files[0]) -'file.csv' ->>> connection.path_exists("/path/to/dir/file.csv") -True -``` - - diff --git a/mddocs/markdown/connection/file_connection/ftps.md b/mddocs/markdown/connection/file_connection/ftps.md deleted file mode 100644 index e115be98b..000000000 --- a/mddocs/markdown/connection/file_connection/ftps.md +++ /dev/null @@ -1,627 +0,0 @@ - - -# FTPS connection - -### *class* onetl.connection.file_connection.ftps.FTPS(\*, host: Host, port: int = 21, user: str | None = None, password: SecretStr | None = None) - -FTPS file connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on [FTPUtil library](https://pypi.org/project/ftputil/). - -#### WARNING -Since onETL v0.7.0 to use FTPS connector you should install package as follows: - -```bash -pip install onetl[ftps] - -# or -pip install onetl[files] -``` - -See [File connections](../../install/files.md#install-files) installation instruction for more details. - -#### Versionadded -Added in version 0.1.0. - -* **Parameters:** - **host** - : Host of FTPS source. For example: `ftps.domain.com` - - **port** - : Port of FTPS source - - **user** - : User, which have access to the file source. For example: `someuser`. -
- `None` means that the user is anonymous. - - **password** - : Password for file source connection. -
- `None` means that the user is anonymous. - -### Examples - -Create and check FTPS connection: - -```python -from onetl.connection import FTPS - -ftps = FTPS( - host="ftps.domain.com", - user="someuser", - password="*****", -).check() -``` - - - -#### \_\_init_\_(\*\*kwargs) - - - -#### check() - -Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If not, an exception will be raised. - -* **Returns:** - Connection itself -* **Raises:** - RuntimeError - : If the connection is not available - -### Examples - -```python -connection.check() -``` - - - -#### create_dir(path: PathLike | str) → RemoteDirectory - -Creates directory tree on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Directory path -* **Returns:** - Created directory with stats -* **Raises:** - `onetl.exception.NotAFileError` - : Path is not a file - -### Examples - -```pycon ->>> dir_path = connection.create_dir("/path/to/dir") ->>> os.fspath(dir_path) -'/path/to/dir' -``` - - - -#### download_file(remote_file_path: PathLike | str, local_file_path: PathLike | str, replace: bool = True) → LocalPath - -Downloads file from the remote filesystem to a local path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -Supports only one file download per call. Directory download is **NOT** supported, use [File Downloader](../../file/file_downloader/file_downloader.md#file-downloader) instead. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **remote_file_path** - : Remote file path to read from - - **local_file_path** - : Local file path to create - - **replace** - : If `True`, existing file will be replaced -* **Returns:** - Local file with stats. -* **Raises:** - `onetl.exception.NotAFileError` - : Remote or local path is not a file - - FileNotFoundError - : Remote file does not exist - - FileExistsError - : Local file already exists, and `replace=False` - - `onetl.exception.FileSizeMismatchError` - : Target file size after download is different from source file size. - -### Examples - -```pycon ->>> local_file = connection.download_file( -... remote_file_path="/path/to/source.csv", -... local_file_path="/path/to/target.csv", -... ) ->>> os.fspath(local_file) -'/path/to/target.csv' ->>> local_file.exists() -True ->>> local_file.stat().st_size # in bytes -1024 ->>> connection.get_stat("/path/to/source.csv").st_size # same size -1024 -``` - - - -#### get_stat(path: PathLike | str) → PathStatProtocol - -Returns stats for a specific path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to get stats for -* **Returns:** - Stats object -* **Raises:** - Any underlying client exception - -### Examples - -```pycon ->>> stat = connection.get_stat("/path/to/file.csv") ->>> stat.st_size # in bytes -1024 ->>> stat.st_uid # owner id or name -12345 -``` - - - -#### is_dir(path: PathLike | str) → bool - -Check if specified path is a directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if path is a directory, `False` otherwise. -* **Raises:** - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - -### Examples - -```pycon ->>> connection.is_dir("/path/to/dir") -True ->>> connection.is_dir("/path/to/dir/file.csv") -False -``` - - - -#### is_file(path: PathLike | str) → bool - -Check if specified path is a file. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if path is a file, `False` otherwise. -* **Raises:** - FileNotFoundError - : Path does not exist - -### Examples - -```pycon ->>> connection.is_file("/path/to/dir/file.csv") -True ->>> connection.is_file("/path/to/dir") -False -``` - - - -#### list_dir(path: PathLike | str, filters: Iterable[[BaseFileFilter](../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → list[RemoteDirectory | RemoteFile] - -Return list of child files/directories in a specific directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Directory path to list contents. - - **filters** - : Return only files/directories matching these filters. See [File Filters](../../file/file_filters/index.md#file-filters) - - **limits** - : Apply limits to the list of files/directories, and stop if one of the limits is reached. - See [File Limits](../../file/file_limits/index.md#file-limits) -* **Returns:** - List of `onetl.base.PathWithStatsProtocol` -* **Raises:** - NotADirectoryError - : Path is not a directory - - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - -### Examples - -```pycon ->>> dir_content = connection.list_dir("/path/to/dir") ->>> os.fspath(dir_content[0]) -'file.csv' ->>> connection.path_exists("/path/to/dir/file.csv") -True -``` - - - -#### path_exists(path: PathLike | str) → bool - -Check if specified path exists on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html). - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if path exists, `False` otherwise - -### Examples - -```pycon ->>> connection.path_exists("/path/to/file.csv") -True ->>> connection.path_exists("/path/to/dir") -True ->>> connection.path_exists("/path/to/missing") -False -``` - - - -#### remove_dir(path: PathLike | str, recursive: bool = False) → bool - -Remove directory or directory tree. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If directory does not exist, no exception is raised. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Directory path to remote - - **recursive** - : If `True`, remove directory tree recursively. -* **Returns:** - `True` if directory was removed, `False` if directory does not exist in the first place. -* **Raises:** - NotADirectoryError - : Path is not a directory - -### Examples - -```pycon ->>> connection.remove_dir("/path/to/dir") -True ->>> connection.path_exists("/path/to/dir") -False ->>> connection.path_exists("/path/to/dir/file.csv") -False ->>> connection.remove_dir("/path/to/dir") # already deleted, no error -False -``` - - - -#### remove_file(path: PathLike | str) → bool - -Removes file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If file does not exist, no exception is raised. - -#### WARNING -Supports only one file removal per call. Directory removal is **NOT** supported, use [`remove_dir`](#onetl.connection.file_connection.ftps.FTPS.remove_dir) instead. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : File path -* **Returns:** - `True` if file was removed, `False` if file does not exist in the first place. -* **Raises:** - `onetl.exception.NotAFileError` - : Path is not a file - -### Examples - -```pycon ->>> connection.remove_file("/path/to/file.csv") -True ->>> connection.path_exists("/path/to/dir/file.csv") -False ->>> connection.remove_file("/path/to/file.csv") # already deleted, no error -False -``` - - - -#### rename_dir(source_dir_path: PathLike | str, target_dir_path: PathLike | str, replace: bool = False) → RemoteDirectory - -Rename or move dir on remote filesystem. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **source_dir_path** - : Old directory path - - **target_dir_path** - : New directory path - - **replace** - : If `True`, existing directory will be replaced. -* **Returns:** - New directory path with stats. -* **Raises:** - NotADirectoryError - : Path is not a directory - - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - - `onetl.exception.DirectoryExistsError` - : Directory already exists, and `replace=False` - -### Examples - -```pycon ->>> new_dir = connection.rename_dir("/path/to/dir1", "/path/to/dir2") ->>> os.fspath(new_dir) -'/path/to/dir2' ->>> connection.path_exists("/path/to/dir1") -False ->>> connection.path_exists("/path/to/dir2") -True -``` - - - -#### rename_file(source_file_path: PathLike | str, target_file_path: PathLike | str, replace: bool = False) → RemoteFile - -Rename or move file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -Supports only one file move per call. Directory move/rename is **NOT** supported. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **source_file_path** - : Old file path - - **target_file_path** - : New file path - - **replace** - : If `True`, existing file will be replaced. -* **Returns:** - New file path with stats. -* **Raises:** - `onetl.exception.NotAFileError` - : Source or target path is not a file - - FileNotFoundError - : File does not exist - - FileExistsError - : File already exists, and `replace=False` - -### Examples - -```pycon ->>> new_file = connection.rename_file("/path/to/file1.csv", "/path/to/file2.csv") ->>> os.fspath(new_file) -'/path/to/file2.csv' ->>> connection.path_exists("/path/to/file2.csv") -True ->>> connection.path_exists("/path/to/file1.csv") -False -``` - - - -#### resolve_dir(path: PathLike | str) → RemoteDirectory - -Returns directory at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to resolve -* **Returns:** - Directory path with stats -* **Raises:** - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - - NotADirectoryError - : Path is not a directory - -### Examples - -```pycon ->>> dir_path = connection.resolve_dir("/path/to/dir") ->>> os.fspath(dir_path) -'/path/to/dir' ->>> dir_path.stat().st_uid # owner id -12345 -``` - - - -#### resolve_file(path: PathLike | str) → RemoteFile - -Returns file at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to resolve -* **Returns:** - File path with stats -* **Raises:** - FileNotFoundError - : Path does not exist - - `onetl.exception.NotAFileError` - : Path is not a file - -### Examples - -```pycon ->>> file_path = connection.resolve_file("/path/to/dir/file.csv") ->>> os.fspath(file_path) -'/path/to/dir/file.csv' ->>> file_path.stat().st_uid # owner id -12345 -``` - - - -#### upload_file(local_file_path: PathLike | str, remote_file_path: PathLike | str, replace: bool = False) → RemoteFile - -Uploads local file to a remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -Supports only one file upload per call. Directory upload is **NOT** supported, use [File Uploader](../../file/file_uploader/file_uploader.md#file-uploader) instead. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **local_file_path** - : Local file path to read from - - **remote_file_path** - : Remote file path to create - - **replace** - : If `True`, existing file will be replaced -* **Returns:** - Remote file with stats. -* **Raises:** - `onetl.exception.NotAFileError` - : Remote or local path is not a file - - FileNotFoundError - : Local file does not exist - - FileExistsError - : Remote file already exists, and `replace=False` - - `onetl.exception.FileSizeMismatchError` - : Target file size after upload is different from source file size. - -### Examples - -```pycon ->>> remote_file = connection.upload( -... local_file_path="/path/to/source.csv", -... remote_file_path="/path/to/target.csv", -... ) ->>> os.fspath(remote_file) -'/path/to/target.csv' ->>> connection.path_exists("/path/to/target.csv") -True ->>> remote_file.stat().st_size # in bytes -1024 ->>> os.stat("/path/to/source.csv").st_size # same as source -1024 -``` - - - -#### walk(root: PathLike | str, topdown: bool = True, filters: Iterable[[BaseFileFilter](../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → Iterator[tuple[RemoteDirectory, list[RemoteDirectory], list[RemoteFile]]] - -Walk into directory tree, and iterate over its content in all nesting levels. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Just like `os.walk`, but with additional filter/limit logic. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **root** - : Directory path to walk into. - - **topdown** - : If `True`, walk in top-down order, otherwise walk in bottom-up order. - - **filters** - : Return only files/directories matching these filters. See [File Filters](../../file/file_filters/index.md#file-filters). - - **limits** - : Apply limits to the list of files/directories, and immediately stop if any of these limits is reached. - See [File Limits](../../file/file_limits/index.md#file-limits). -* **Returns:** - `Iterator[tuple[root, dirs, files]]`, like `os.walk`. - - But all the paths are not strings, instead path classes with embedded stats are returned. -* **Raises:** - NotADirectoryError - : Path is not a directory - - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - -### Examples - -```pycon ->>> for root, dirs, files in connection.walk("/path/to/dir"): -... break ->>> os.fspath(root) -'/path/to/dir' ->>> dirs -[] ->>> os.fspath(files[0]) -'file.csv' ->>> connection.path_exists("/path/to/dir/file.csv") -True -``` - - diff --git a/mddocs/markdown/connection/file_connection/hdfs/connection.md b/mddocs/markdown/connection/file_connection/hdfs/connection.md deleted file mode 100644 index d8f53ee60..000000000 --- a/mddocs/markdown/connection/file_connection/hdfs/connection.md +++ /dev/null @@ -1,740 +0,0 @@ - - -# HDFS connection - -### *class* onetl.connection.file_connection.hdfs.connection.HDFS(\*, cluster: Cluster | None = None, host: Host | None = None, port: int = 50070, user: str | None = None, password: SecretStr | None = None, keytab: FilePath | None = None, timeout: int = 10) - -HDFS file connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Powered by [HDFS Python client](https://pypi.org/project/hdfs/). - -#### WARNING -Since onETL v0.7.0 to use HDFS connector you should install package as follows: - -```bash -pip install onetl[hdfs] - -# or -pip install onetl[files] -``` - -See [File connections](../../../install/files.md#install-files) installation instruction for more details. - -#### NOTE -To access Hadoop cluster with Kerberos installed, you should have `kinit` executable -in some path in `PATH` environment variable. - -See [Kerberos support](../../../install/kerberos.md#install-kerberos) instruction for more details. - -* **Parameters:** - **cluster** - : Hadoop cluster name. For example: `rnd-dwh`. -
- Used for: - : * HWM and lineage (as instance name for file paths), if set. - * Validation of `host` value, - : if latter is passed and if some hooks are bound to - [`Slots.get_cluster_namenodes`](slots.md#onetl.connection.file_connection.hdfs.slots.HDFSSlots.get_cluster_namenodes) -
- -
- #### Versionadded - Added in version 0.7.0. - - **host** - : Hadoop namenode host. For example: `namenode1.domain.com`. -
- Should be an active namenode (NOT standby). -
- If value is not set, but there are some hooks bound to - [`Slots.get_cluster_namenodes`](slots.md#onetl.connection.file_connection.hdfs.slots.HDFSSlots.get_cluster_namenodes) - and [`Slots.is_namenode_active`](slots.md#onetl.connection.file_connection.hdfs.slots.HDFSSlots.is_namenode_active), - onETL will iterate over cluster namenodes to detect which one is active. - - - **webhdfs_port** - : Port of Hadoop namenode (WebHDFS protocol). -
- If omitted, but there are some hooks bound to - [`Slots.get_webhdfs_port`](slots.md#onetl.connection.file_connection.hdfs.slots.HDFSSlots.get_webhdfs_port) slot, - onETL will try to detect port number for a specific `cluster`. - - **user** - : User, which have access to the file source. For example: `someuser`. -
- If set, Kerberos auth will be used. Otherwise an anonymous connection is created. - - **password** - : User password. -
- Used for generating Kerberos ticket. -
- #### WARNING - You can provide only one of the parameters: `password` or `kinit`. - If you provide both, an exception will be raised. - - **keytab** - : LocalPath to keytab file. -
- Used for generating Kerberos ticket. -
- #### WARNING - You can provide only one of the parameters: `password` or `kinit`. - If you provide both, an exception will be raised. - - **timeout** - : Connection timeout. - -### Examples - -Create HDFS connection with user+password - -```py -from onetl.connection import HDFS - -hdfs = HDFS( - host="namenode1.domain.com", - user="someuser", - password="*****", -).check() -``` - -Create HDFS connection with user+keytab - -```py -from onetl.connection import HDFS - -hdfs = HDFS( - host="namenode1.domain.com", - user="someuser", - keytab="/path/to/keytab", -).check() -``` - -Create HDFS connection without auth - -```py -from onetl.connection import HDFS - -hdfs = HDFS(host="namenode1.domain.com").check() -``` - -Use cluster name to detect active namenode - -Can be used only if some third-party plugin provides [HDFS Slots](slots.md#hdfs-slots) implementation - -```python -from onetl.connection import HDFS - -hdfs = HDFS( - cluster="rnd-dwh", - user="someuser", - password="*****", -).check() -``` - - - -#### check() - -Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If not, an exception will be raised. - -* **Returns:** - Connection itself -* **Raises:** - RuntimeError - : If the connection is not available - -### Examples - -```python -connection.check() -``` - - - -#### create_dir(path: PathLike | str) → RemoteDirectory - -Creates directory tree on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Directory path -* **Returns:** - Created directory with stats -* **Raises:** - `onetl.exception.NotAFileError` - : Path is not a file - -### Examples - -```pycon ->>> dir_path = connection.create_dir("/path/to/dir") ->>> os.fspath(dir_path) -'/path/to/dir' -``` - - - -#### download_file(remote_file_path: PathLike | str, local_file_path: PathLike | str, replace: bool = True) → LocalPath - -Downloads file from the remote filesystem to a local path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -Supports only one file download per call. Directory download is **NOT** supported, use [File Downloader](../../../file/file_downloader/file_downloader.md#file-downloader) instead. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **remote_file_path** - : Remote file path to read from - - **local_file_path** - : Local file path to create - - **replace** - : If `True`, existing file will be replaced -* **Returns:** - Local file with stats. -* **Raises:** - `onetl.exception.NotAFileError` - : Remote or local path is not a file - - FileNotFoundError - : Remote file does not exist - - FileExistsError - : Local file already exists, and `replace=False` - - `onetl.exception.FileSizeMismatchError` - : Target file size after download is different from source file size. - -### Examples - -```pycon ->>> local_file = connection.download_file( -... remote_file_path="/path/to/source.csv", -... local_file_path="/path/to/target.csv", -... ) ->>> os.fspath(local_file) -'/path/to/target.csv' ->>> local_file.exists() -True ->>> local_file.stat().st_size # in bytes -1024 ->>> connection.get_stat("/path/to/source.csv").st_size # same size -1024 -``` - - - -#### *classmethod* get_current(\*\*kwargs) - -Create connection for current cluster. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Automatically sets up current cluster name as `cluster`. - -#### NOTE -Can be used only if there are a some hooks bound to slot -[`Slots.get_current_cluster`](slots.md#onetl.connection.file_connection.hdfs.slots.HDFSSlots.get_current_cluster) - -#### Versionadded -Added in version 0.7.0. - -* **Parameters:** - **user** - - **password** - - **keytab** - - **timeout** - : See [`HDFS`](#onetl.connection.file_connection.hdfs.connection.HDFS) constructor documentation. - -### Examples - -```python -from onetl.connection import HDFS - -# injecting current cluster name via hooks mechanism -hdfs = HDFS.get_current(user="me", password="pass") -``` - - - -#### get_stat(path: PathLike | str) → PathStatProtocol - -Returns stats for a specific path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to get stats for -* **Returns:** - Stats object -* **Raises:** - Any underlying client exception - -### Examples - -```pycon ->>> stat = connection.get_stat("/path/to/file.csv") ->>> stat.st_size # in bytes -1024 ->>> stat.st_uid # owner id or name -12345 -``` - - - -#### is_dir(path: PathLike | str) → bool - -Check if specified path is a directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if path is a directory, `False` otherwise. -* **Raises:** - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - -### Examples - -```pycon ->>> connection.is_dir("/path/to/dir") -True ->>> connection.is_dir("/path/to/dir/file.csv") -False -``` - - - -#### is_file(path: PathLike | str) → bool - -Check if specified path is a file. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if path is a file, `False` otherwise. -* **Raises:** - FileNotFoundError - : Path does not exist - -### Examples - -```pycon ->>> connection.is_file("/path/to/dir/file.csv") -True ->>> connection.is_file("/path/to/dir") -False -``` - - - -#### list_dir(path: PathLike | str, filters: Iterable[[BaseFileFilter](../../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → list[RemoteDirectory | RemoteFile] - -Return list of child files/directories in a specific directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Directory path to list contents. - - **filters** - : Return only files/directories matching these filters. See [File Filters](../../../file/file_filters/index.md#file-filters) - - **limits** - : Apply limits to the list of files/directories, and stop if one of the limits is reached. - See [File Limits](../../../file/file_limits/index.md#file-limits) -* **Returns:** - List of `onetl.base.PathWithStatsProtocol` -* **Raises:** - NotADirectoryError - : Path is not a directory - - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - -### Examples - -```pycon ->>> dir_content = connection.list_dir("/path/to/dir") ->>> os.fspath(dir_content[0]) -'file.csv' ->>> connection.path_exists("/path/to/dir/file.csv") -True -``` - - - -#### path_exists(path: PathLike | str) → bool - -Check if specified path exists on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html). - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if path exists, `False` otherwise - -### Examples - -```pycon ->>> connection.path_exists("/path/to/file.csv") -True ->>> connection.path_exists("/path/to/dir") -True ->>> connection.path_exists("/path/to/missing") -False -``` - - - -#### remove_dir(path: PathLike | str, recursive: bool = False) → bool - -Remove directory or directory tree. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If directory does not exist, no exception is raised. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Directory path to remote - - **recursive** - : If `True`, remove directory tree recursively. -* **Returns:** - `True` if directory was removed, `False` if directory does not exist in the first place. -* **Raises:** - NotADirectoryError - : Path is not a directory - -### Examples - -```pycon ->>> connection.remove_dir("/path/to/dir") -True ->>> connection.path_exists("/path/to/dir") -False ->>> connection.path_exists("/path/to/dir/file.csv") -False ->>> connection.remove_dir("/path/to/dir") # already deleted, no error -False -``` - - - -#### remove_file(path: PathLike | str) → bool - -Removes file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If file does not exist, no exception is raised. - -#### WARNING -Supports only one file removal per call. Directory removal is **NOT** supported, use [`remove_dir`](#onetl.connection.file_connection.hdfs.connection.HDFS.remove_dir) instead. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : File path -* **Returns:** - `True` if file was removed, `False` if file does not exist in the first place. -* **Raises:** - `onetl.exception.NotAFileError` - : Path is not a file - -### Examples - -```pycon ->>> connection.remove_file("/path/to/file.csv") -True ->>> connection.path_exists("/path/to/dir/file.csv") -False ->>> connection.remove_file("/path/to/file.csv") # already deleted, no error -False -``` - - - -#### rename_dir(source_dir_path: PathLike | str, target_dir_path: PathLike | str, replace: bool = False) → RemoteDirectory - -Rename or move dir on remote filesystem. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **source_dir_path** - : Old directory path - - **target_dir_path** - : New directory path - - **replace** - : If `True`, existing directory will be replaced. -* **Returns:** - New directory path with stats. -* **Raises:** - NotADirectoryError - : Path is not a directory - - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - - `onetl.exception.DirectoryExistsError` - : Directory already exists, and `replace=False` - -### Examples - -```pycon ->>> new_dir = connection.rename_dir("/path/to/dir1", "/path/to/dir2") ->>> os.fspath(new_dir) -'/path/to/dir2' ->>> connection.path_exists("/path/to/dir1") -False ->>> connection.path_exists("/path/to/dir2") -True -``` - - - -#### rename_file(source_file_path: PathLike | str, target_file_path: PathLike | str, replace: bool = False) → RemoteFile - -Rename or move file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -Supports only one file move per call. Directory move/rename is **NOT** supported. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **source_file_path** - : Old file path - - **target_file_path** - : New file path - - **replace** - : If `True`, existing file will be replaced. -* **Returns:** - New file path with stats. -* **Raises:** - `onetl.exception.NotAFileError` - : Source or target path is not a file - - FileNotFoundError - : File does not exist - - FileExistsError - : File already exists, and `replace=False` - -### Examples - -```pycon ->>> new_file = connection.rename_file("/path/to/file1.csv", "/path/to/file2.csv") ->>> os.fspath(new_file) -'/path/to/file2.csv' ->>> connection.path_exists("/path/to/file2.csv") -True ->>> connection.path_exists("/path/to/file1.csv") -False -``` - - - -#### resolve_dir(path: PathLike | str) → RemoteDirectory - -Returns directory at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to resolve -* **Returns:** - Directory path with stats -* **Raises:** - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - - NotADirectoryError - : Path is not a directory - -### Examples - -```pycon ->>> dir_path = connection.resolve_dir("/path/to/dir") ->>> os.fspath(dir_path) -'/path/to/dir' ->>> dir_path.stat().st_uid # owner id -12345 -``` - - - -#### resolve_file(path: PathLike | str) → RemoteFile - -Returns file at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to resolve -* **Returns:** - File path with stats -* **Raises:** - FileNotFoundError - : Path does not exist - - `onetl.exception.NotAFileError` - : Path is not a file - -### Examples - -```pycon ->>> file_path = connection.resolve_file("/path/to/dir/file.csv") ->>> os.fspath(file_path) -'/path/to/dir/file.csv' ->>> file_path.stat().st_uid # owner id -12345 -``` - - - -#### upload_file(local_file_path: PathLike | str, remote_file_path: PathLike | str, replace: bool = False) → RemoteFile - -Uploads local file to a remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -Supports only one file upload per call. Directory upload is **NOT** supported, use [File Uploader](../../../file/file_uploader/file_uploader.md#file-uploader) instead. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **local_file_path** - : Local file path to read from - - **remote_file_path** - : Remote file path to create - - **replace** - : If `True`, existing file will be replaced -* **Returns:** - Remote file with stats. -* **Raises:** - `onetl.exception.NotAFileError` - : Remote or local path is not a file - - FileNotFoundError - : Local file does not exist - - FileExistsError - : Remote file already exists, and `replace=False` - - `onetl.exception.FileSizeMismatchError` - : Target file size after upload is different from source file size. - -### Examples - -```pycon ->>> remote_file = connection.upload( -... local_file_path="/path/to/source.csv", -... remote_file_path="/path/to/target.csv", -... ) ->>> os.fspath(remote_file) -'/path/to/target.csv' ->>> connection.path_exists("/path/to/target.csv") -True ->>> remote_file.stat().st_size # in bytes -1024 ->>> os.stat("/path/to/source.csv").st_size # same as source -1024 -``` - - - -#### walk(root: PathLike | str, topdown: bool = True, filters: Iterable[[BaseFileFilter](../../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → Iterator[tuple[RemoteDirectory, list[RemoteDirectory], list[RemoteFile]]] - -Walk into directory tree, and iterate over its content in all nesting levels. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Just like `os.walk`, but with additional filter/limit logic. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **root** - : Directory path to walk into. - - **topdown** - : If `True`, walk in top-down order, otherwise walk in bottom-up order. - - **filters** - : Return only files/directories matching these filters. See [File Filters](../../../file/file_filters/index.md#file-filters). - - **limits** - : Apply limits to the list of files/directories, and immediately stop if any of these limits is reached. - See [File Limits](../../../file/file_limits/index.md#file-limits). -* **Returns:** - `Iterator[tuple[root, dirs, files]]`, like `os.walk`. - - But all the paths are not strings, instead path classes with embedded stats are returned. -* **Raises:** - NotADirectoryError - : Path is not a directory - - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - -### Examples - -```pycon ->>> for root, dirs, files in connection.walk("/path/to/dir"): -... break ->>> os.fspath(root) -'/path/to/dir' ->>> dirs -[] ->>> os.fspath(files[0]) -'file.csv' ->>> connection.path_exists("/path/to/dir/file.csv") -True -``` - - diff --git a/mddocs/markdown/connection/file_connection/hdfs/index.md b/mddocs/markdown/connection/file_connection/hdfs/index.md deleted file mode 100644 index a65c43ea3..000000000 --- a/mddocs/markdown/connection/file_connection/hdfs/index.md +++ /dev/null @@ -1,11 +0,0 @@ - - -# HDFS - -# Connection - -* [HDFS connection](connection.md) - -# For developers - -* [HDFS Slots](slots.md) diff --git a/mddocs/markdown/connection/file_connection/hdfs/slots.md b/mddocs/markdown/connection/file_connection/hdfs/slots.md deleted file mode 100644 index 6f681805b..000000000 --- a/mddocs/markdown/connection/file_connection/hdfs/slots.md +++ /dev/null @@ -1,256 +0,0 @@ - - -# HDFS Slots - -### *class* onetl.connection.file_connection.hdfs.slots.HDFSSlots - -Slots that could be implemented by third-party plugins. - -#### Versionadded -Added in version 0.7.0. - - - -#### *static* normalize_cluster_name(cluster: str) → str | None - -Normalize cluster name passed into HDFS constructor. - -If hooks didn’t return anything, cluster name is left intact. - -#### Versionadded -Added in version 0.7.0. - -* **Parameters:** - **cluster** - : Cluster name -* **Returns:** - str | None - : Normalized cluster name. -
- If hook cannot be applied to a specific cluster, it should return `None`. - -### Examples - -```python -from onetl.connection import HDFS -from onetl.hooks import hook - -@HDFS.Slots.normalize_cluster_name.bind -@hook -def normalize_cluster_name(cluster: str) -> str: - return cluster.lower() -``` - - - -#### *static* normalize_namenode_host(host: str, cluster: str | None) → str | None - -Normalize namenode host passed into HDFS constructor. - -If hooks didn’t return anything, host is left intact. - -#### Versionadded -Added in version 0.7.0. - -* **Parameters:** - **host** - : Namenode host (raw) - - **cluster** - : Cluster name (normalized), if set -* **Returns:** - str | None - : Normalized namenode host name. -
- If hook cannot be applied to a specific host name, it should return `None`. - -### Examples - -```python -from onetl.connection import HDFS -from onetl.hooks import hook - -@HDFS.Slots.normalize_namenode_host.bind -@hook -def normalize_namenode_host(host: str, cluster: str) -> str | None: - if cluster == "rnd-dwh": - if not host.endswith(".domain.com"): - # fix missing domain name - host += ".domain.com" - return host - - return None -``` - - - -#### *static* get_known_clusters() → set[str] | None - -Return collection of known clusters. - -Cluster passed into HDFS constructor should be present in this list. -If hooks didn’t return anything, no validation will be performed. - -#### Versionadded -Added in version 0.7.0. - -* **Returns:** - set[str] | None - : Collection of cluster names (in normalized form). -
- If hook cannot be applied, it should return `None`. - -### Examples - -```python -from onetl.connection import HDFS -from onetl.hooks import hook - -@HDFS.Slots.get_known_clusters.bind -@hook -def get_known_clusters() -> str[str]: - return {"rnd-dwh", "rnd-prod"} -``` - - - -#### *static* get_cluster_namenodes(cluster: str) → set[str] | None - -Return collection of known namenodes for the cluster. - -Namenode host passed into HDFS constructor should be present in this list. -If hooks didn’t return anything, no validation will be performed. - -#### Versionadded -Added in version 0.7.0. - -* **Parameters:** - **cluster** - : Cluster name (normalized) -* **Returns:** - set[str] | None - : Collection of host names (in normalized form). -
- If hook cannot be applied, it should return `None`. - -### Examples - -```python -from onetl.connection import HDFS -from onetl.hooks import hook - -@HDFS.Slots.get_cluster_namenodes.bind -@hook -def get_cluster_namenodes(cluster: str) -> str[str] | None: - if cluster == "rnd-dwh": - return {"namenode1.domain.com", "namenode2.domain.com"} - return None -``` - - - -#### *static* get_current_cluster() → str | None - -Get current cluster name. - -Used in [`get_current_cluster`](#onetl.connection.file_connection.hdfs.slots.HDFSSlots.get_current_cluster) to automatically fill up `cluster` attribute of a connection. -If hooks didn’t return anything, calling the method above will raise an exception. - -#### Versionadded -Added in version 0.7.0. - -* **Returns:** - str | None - : Current cluster name (in normalized form). -
- If hook cannot be applied, it should return `None`. - -### Examples - -```python -from onetl.connection import HDFS -from onetl.hooks import hook - -@HDFS.Slots.get_current_cluster.bind -@hook -def get_current_cluster() -> str: - # some magic here - return "rnd-dwh" -``` - - - -#### *static* get_webhdfs_port(cluster: str) → int | None - -Get WebHDFS port number for a specific cluster. - -Used by constructor to automatically set port number if omitted. - -#### Versionadded -Added in version 0.7.0. - -* **Parameters:** - **cluster** - : Cluster name (normalized) -* **Returns:** - int | None - : WebHDFS port number. -
- If hook cannot be applied, it should return `None`. - -### Examples - -```python -from onetl.connection import HDFS -from onetl.hooks import hook - -@HDFS.Slots.get_webhdfs_port.bind -@hook -def get_webhdfs_port(cluster: str) -> int | None: - if cluster == "rnd-dwh": - return 50007 # Cloudera - return None -``` - - - -#### *static* is_namenode_active(host: str, cluster: str | None) → bool | None - -Check whether a namenode of a specified cluster is active (=not standby) or not. - -Used for: -: * If HDFS connection is created without `host` - > Connector will iterate over [`get_cluster_namenodes`](#onetl.connection.file_connection.hdfs.slots.HDFSSlots.get_cluster_namenodes) of a cluster to get active namenode, - > and then use it instead of `host` attribute. - * If HDFS connection is created with `host` - > `check` will determine whether this host is active. - -#### Versionadded -Added in version 0.7.0. - -* **Parameters:** - **host** - : Namenode host (normalized) - - **cluster** - : Cluster name (normalized), if set -* **Returns:** - bool | None - : `True` if namenode is active, `False` if not. -
- If hook cannot be applied, it should return `None`. - -### Examples - -```python -from onetl.connection import HDFS -from onetl.hooks import hook - -@HDFS.Slots.is_namenode_active.bind -@hook -def is_namenode_active(host: str, cluster: str | None) -> bool: - # some magic here - return True -``` - - diff --git a/mddocs/markdown/connection/file_connection/index.md b/mddocs/markdown/connection/file_connection/index.md deleted file mode 100644 index 874845c78..000000000 --- a/mddocs/markdown/connection/file_connection/index.md +++ /dev/null @@ -1,13 +0,0 @@ - - -# File Connections - -# File Connections - -* [FTP](ftp.md) -* [FTPS](ftps.md) -* [HDFS](hdfs/index.md) -* [Samba](samba.md) -* [SFTP](sftp.md) -* [S3](s3.md) -* [Webdav](webdav.md) diff --git a/mddocs/markdown/connection/file_connection/s3.md b/mddocs/markdown/connection/file_connection/s3.md deleted file mode 100644 index 4fa940aaf..000000000 --- a/mddocs/markdown/connection/file_connection/s3.md +++ /dev/null @@ -1,598 +0,0 @@ - - -# S3 connection - -### *class* onetl.connection.file_connection.s3.S3(\*, host: Host, port: int | None = None, bucket: str, access_key: str, secret_key: SecretStr, protocol: Literal['http', 'https'] = 'https', session_token: SecretStr | None = None, region: str | None = None) - -S3 file connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on [minio-py client](https://pypi.org/project/minio/). - -#### WARNING -Since onETL v0.7.0 to use S3 connector you should install package as follows: - -```bash -pip install onetl[s3] - -# or -pip install onetl[files] -``` - -See [File connections](../../install/files.md#install-files) installation instruction for more details. - -#### Versionadded -Added in version 0.5.1. - -* **Parameters:** - **host** - : Host of S3 source. For example: `s3.domain.com` - - **port** - : Port of S3 source - - **bucket** - : Bucket name in the S3 file source - - **access_key** - : Access key (aka user ID) of an account in the S3 service - - **secret_key** - : Secret key (aka password) of an account in the S3 service - - **protocol** - : Connection protocol. Allowed values: `https` or `http` -
- #### Versionchanged - Changed in version 0.6.0: Renamed `secure: bool` to `protocol: Literal["https", "http"]` - - **session_token** - : Session token of your account in S3 service - - **region** - : Region name of bucket in S3 service - -### Examples - -Create and check S3 connection: - -```python -from onetl.connection import S3 - -s3 = S3( - host="s3.domain.com", - protocol="http", - bucket="my-bucket", - access_key="ACCESS_KEY", - secret_key="SECRET_KEY", -).check() -``` - - - -#### \_\_init_\_(\*\*kwargs) - - - -#### check() - -Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If not, an exception will be raised. - -* **Returns:** - Connection itself -* **Raises:** - RuntimeError - : If the connection is not available - -### Examples - -```python -connection.check() -``` - - - -#### create_dir(path: PathLike | str) → RemoteDirectory - -Creates directory tree on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Directory path -* **Returns:** - Created directory with stats -* **Raises:** - `onetl.exception.NotAFileError` - : Path is not a file - -### Examples - -```pycon ->>> dir_path = connection.create_dir("/path/to/dir") ->>> os.fspath(dir_path) -'/path/to/dir' -``` - - - -#### download_file(remote_file_path: PathLike | str, local_file_path: PathLike | str, replace: bool = True) → LocalPath - -Downloads file from the remote filesystem to a local path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -Supports only one file download per call. Directory download is **NOT** supported, use [File Downloader](../../file/file_downloader/file_downloader.md#file-downloader) instead. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **remote_file_path** - : Remote file path to read from - - **local_file_path** - : Local file path to create - - **replace** - : If `True`, existing file will be replaced -* **Returns:** - Local file with stats. -* **Raises:** - `onetl.exception.NotAFileError` - : Remote or local path is not a file - - FileNotFoundError - : Remote file does not exist - - FileExistsError - : Local file already exists, and `replace=False` - - `onetl.exception.FileSizeMismatchError` - : Target file size after download is different from source file size. - -### Examples - -```pycon ->>> local_file = connection.download_file( -... remote_file_path="/path/to/source.csv", -... local_file_path="/path/to/target.csv", -... ) ->>> os.fspath(local_file) -'/path/to/target.csv' ->>> local_file.exists() -True ->>> local_file.stat().st_size # in bytes -1024 ->>> connection.get_stat("/path/to/source.csv").st_size # same size -1024 -``` - - - -#### get_stat(path: PathLike | str) → PathStatProtocol - -Returns stats for a specific path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to get stats for -* **Returns:** - Stats object -* **Raises:** - Any underlying client exception - -### Examples - -```pycon ->>> stat = connection.get_stat("/path/to/file.csv") ->>> stat.st_size # in bytes -1024 ->>> stat.st_uid # owner id or name -12345 -``` - - - -#### is_dir(path: PathLike | str) → bool - -Check if specified path is a directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if path is a directory, `False` otherwise. -* **Raises:** - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - -### Examples - -```pycon ->>> connection.is_dir("/path/to/dir") -True ->>> connection.is_dir("/path/to/dir/file.csv") -False -``` - - - -#### is_file(path: PathLike | str) → bool - -Check if specified path is a file. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if path is a file, `False` otherwise. -* **Raises:** - FileNotFoundError - : Path does not exist - -### Examples - -```pycon ->>> connection.is_file("/path/to/dir/file.csv") -True ->>> connection.is_file("/path/to/dir") -False -``` - - - -#### list_dir(path: PathLike | str, filters: Iterable[[BaseFileFilter](../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → list[RemoteDirectory | RemoteFile] - -Return list of child files/directories in a specific directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Directory path to list contents. - - **filters** - : Return only files/directories matching these filters. See [File Filters](../../file/file_filters/index.md#file-filters) - - **limits** - : Apply limits to the list of files/directories, and stop if one of the limits is reached. - See [File Limits](../../file/file_limits/index.md#file-limits) -* **Returns:** - List of `onetl.base.PathWithStatsProtocol` -* **Raises:** - NotADirectoryError - : Path is not a directory - - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - -### Examples - -```pycon ->>> dir_content = connection.list_dir("/path/to/dir") ->>> os.fspath(dir_content[0]) -'file.csv' ->>> connection.path_exists("/path/to/dir/file.csv") -True -``` - - - -#### path_exists(path: PathLike | str) → bool - -Check if specified path exists on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html). - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if path exists, `False` otherwise - -### Examples - -```pycon ->>> connection.path_exists("/path/to/file.csv") -True ->>> connection.path_exists("/path/to/dir") -True ->>> connection.path_exists("/path/to/missing") -False -``` - - - -#### remove_dir(path: PathLike | str, recursive: bool = False) → bool - -Remove directory or directory tree. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If directory does not exist, no exception is raised. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Directory path to remote - - **recursive** - : If `True`, remove directory tree recursively. -* **Returns:** - `True` if directory was removed, `False` if directory does not exist in the first place. -* **Raises:** - NotADirectoryError - : Path is not a directory - -### Examples - -```pycon ->>> connection.remove_dir("/path/to/dir") -True ->>> connection.path_exists("/path/to/dir") -False ->>> connection.path_exists("/path/to/dir/file.csv") -False ->>> connection.remove_dir("/path/to/dir") # already deleted, no error -False -``` - - - -#### remove_file(path: PathLike | str) → bool - -Removes file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If file does not exist, no exception is raised. - -#### WARNING -Supports only one file removal per call. Directory removal is **NOT** supported, use [`remove_dir`](#onetl.connection.file_connection.s3.S3.remove_dir) instead. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : File path -* **Returns:** - `True` if file was removed, `False` if file does not exist in the first place. -* **Raises:** - `onetl.exception.NotAFileError` - : Path is not a file - -### Examples - -```pycon ->>> connection.remove_file("/path/to/file.csv") -True ->>> connection.path_exists("/path/to/dir/file.csv") -False ->>> connection.remove_file("/path/to/file.csv") # already deleted, no error -False -``` - - - -#### rename_file(source_file_path: PathLike | str, target_file_path: PathLike | str, replace: bool = False) → RemoteFile - -Rename or move file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -Supports only one file move per call. Directory move/rename is **NOT** supported. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **source_file_path** - : Old file path - - **target_file_path** - : New file path - - **replace** - : If `True`, existing file will be replaced. -* **Returns:** - New file path with stats. -* **Raises:** - `onetl.exception.NotAFileError` - : Source or target path is not a file - - FileNotFoundError - : File does not exist - - FileExistsError - : File already exists, and `replace=False` - -### Examples - -```pycon ->>> new_file = connection.rename_file("/path/to/file1.csv", "/path/to/file2.csv") ->>> os.fspath(new_file) -'/path/to/file2.csv' ->>> connection.path_exists("/path/to/file2.csv") -True ->>> connection.path_exists("/path/to/file1.csv") -False -``` - - - -#### resolve_dir(path: PathLike | str) → RemoteDirectory - -Returns directory at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to resolve -* **Returns:** - Directory path with stats -* **Raises:** - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - - NotADirectoryError - : Path is not a directory - -### Examples - -```pycon ->>> dir_path = connection.resolve_dir("/path/to/dir") ->>> os.fspath(dir_path) -'/path/to/dir' ->>> dir_path.stat().st_uid # owner id -12345 -``` - - - -#### resolve_file(path: PathLike | str) → RemoteFile - -Returns file at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to resolve -* **Returns:** - File path with stats -* **Raises:** - FileNotFoundError - : Path does not exist - - `onetl.exception.NotAFileError` - : Path is not a file - -### Examples - -```pycon ->>> file_path = connection.resolve_file("/path/to/dir/file.csv") ->>> os.fspath(file_path) -'/path/to/dir/file.csv' ->>> file_path.stat().st_uid # owner id -12345 -``` - - - -#### upload_file(local_file_path: PathLike | str, remote_file_path: PathLike | str, replace: bool = False) → RemoteFile - -Uploads local file to a remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -Supports only one file upload per call. Directory upload is **NOT** supported, use [File Uploader](../../file/file_uploader/file_uploader.md#file-uploader) instead. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **local_file_path** - : Local file path to read from - - **remote_file_path** - : Remote file path to create - - **replace** - : If `True`, existing file will be replaced -* **Returns:** - Remote file with stats. -* **Raises:** - `onetl.exception.NotAFileError` - : Remote or local path is not a file - - FileNotFoundError - : Local file does not exist - - FileExistsError - : Remote file already exists, and `replace=False` - - `onetl.exception.FileSizeMismatchError` - : Target file size after upload is different from source file size. - -### Examples - -```pycon ->>> remote_file = connection.upload( -... local_file_path="/path/to/source.csv", -... remote_file_path="/path/to/target.csv", -... ) ->>> os.fspath(remote_file) -'/path/to/target.csv' ->>> connection.path_exists("/path/to/target.csv") -True ->>> remote_file.stat().st_size # in bytes -1024 ->>> os.stat("/path/to/source.csv").st_size # same as source -1024 -``` - - - -#### walk(root: PathLike | str, topdown: bool = True, filters: Iterable[[BaseFileFilter](../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → Iterator[tuple[RemoteDirectory, list[RemoteDirectory], list[RemoteFile]]] - -Walk into directory tree, and iterate over its content in all nesting levels. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Just like `os.walk`, but with additional filter/limit logic. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **root** - : Directory path to walk into. - - **topdown** - : If `True`, walk in top-down order, otherwise walk in bottom-up order. - - **filters** - : Return only files/directories matching these filters. See [File Filters](../../file/file_filters/index.md#file-filters). - - **limits** - : Apply limits to the list of files/directories, and immediately stop if any of these limits is reached. - See [File Limits](../../file/file_limits/index.md#file-limits). -* **Returns:** - `Iterator[tuple[root, dirs, files]]`, like `os.walk`. - - But all the paths are not strings, instead path classes with embedded stats are returned. -* **Raises:** - NotADirectoryError - : Path is not a directory - - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - -### Examples - -```pycon ->>> for root, dirs, files in connection.walk("/path/to/dir"): -... break ->>> os.fspath(root) -'/path/to/dir' ->>> dirs -[] ->>> os.fspath(files[0]) -'file.csv' ->>> connection.path_exists("/path/to/dir/file.csv") -True -``` - - diff --git a/mddocs/markdown/connection/file_connection/samba.md b/mddocs/markdown/connection/file_connection/samba.md deleted file mode 100644 index 81b661ec1..000000000 --- a/mddocs/markdown/connection/file_connection/samba.md +++ /dev/null @@ -1,548 +0,0 @@ - - -# Samba connection - -### *class* onetl.connection.file_connection.samba.Samba(\*, host: Host, share: str, protocol: Literal['SMB', 'NetBIOS'] = 'SMB', port: int | None = None, domain: str | None = '', auth_type: Literal['NTLMv1', 'NTLMv2'] = 'NTLMv2', user: str | None = None, password: SecretStr | None = None) - -Samba file connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on [pysmb library](https://pypi.org/project/pysmb/). - -#### Versionadded -Added in version 0.9.4. - -#### WARNING -To use Samba connector you should install package as follows: - -```bash -pip install onetl[samba] - -# or -pip install onetl[files] -``` - -See [File connections](../../install/files.md#install-files) installation instruction for more details. - -* **Parameters:** - **host** - : Host of Samba source. For example: `mydomain.com`. - - **share** - : The name of the share on the Samba server. - - **protocol** - : The protocol to use for the connection. Either `SMB` or `NetBIOS`. - Affects the default port and the is_direct_tcp flag in SMBConnection. - - **port** - : Port of Samba source. - - **domain** - : Domain name for the Samba connection. Empty strings means use `host` as domain name. - - **auth_type** - : The authentication type to use. Either `NTLMv2` or `NTLMv1`. - Affects the use_ntlm_v2 flag in SMBConnection. - - **user** - : User, which have access to the file source. Can be None for anonymous connection. - - **password** - : Password for file source connection. Can be None for anonymous connection. - -### Examples - -Create and check Samba connection: - -```python -from onetl.connection import Samba - -samba = Samba( - host="mydomain.com", - share="share_name", - protocol="SMB", - port=445, - user="user", - password="password", -).check() -``` - - - -#### \_\_init_\_(\*\*kwargs) - - - -#### check() - -Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If not, an exception will be raised. - -* **Returns:** - Connection itself -* **Raises:** - RuntimeError - : If the connection is not available - -### Examples - -```python -connection.check() -``` - - - -#### create_dir(path: PathLike | str) → RemoteDirectory - -Creates directory tree on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Directory path -* **Returns:** - Created directory with stats -* **Raises:** - `onetl.exception.NotAFileError` - : Path is not a file - -### Examples - -```pycon ->>> dir_path = connection.create_dir("/path/to/dir") ->>> os.fspath(dir_path) -'/path/to/dir' -``` - - - -#### download_file(remote_file_path: PathLike | str, local_file_path: PathLike | str, replace: bool = True) → LocalPath - -Downloads file from the remote filesystem to a local path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -Supports only one file download per call. Directory download is **NOT** supported, use [File Downloader](../../file/file_downloader/file_downloader.md#file-downloader) instead. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **remote_file_path** - : Remote file path to read from - - **local_file_path** - : Local file path to create - - **replace** - : If `True`, existing file will be replaced -* **Returns:** - Local file with stats. -* **Raises:** - `onetl.exception.NotAFileError` - : Remote or local path is not a file - - FileNotFoundError - : Remote file does not exist - - FileExistsError - : Local file already exists, and `replace=False` - - `onetl.exception.FileSizeMismatchError` - : Target file size after download is different from source file size. - -### Examples - -```pycon ->>> local_file = connection.download_file( -... remote_file_path="/path/to/source.csv", -... local_file_path="/path/to/target.csv", -... ) ->>> os.fspath(local_file) -'/path/to/target.csv' ->>> local_file.exists() -True ->>> local_file.stat().st_size # in bytes -1024 ->>> connection.get_stat("/path/to/source.csv").st_size # same size -1024 -``` - - - -#### get_stat(path: PathLike | str) → PathStatProtocol - -Returns stats for a specific path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to get stats for -* **Returns:** - Stats object -* **Raises:** - Any underlying client exception - -### Examples - -```pycon ->>> stat = connection.get_stat("/path/to/file.csv") ->>> stat.st_size # in bytes -1024 ->>> stat.st_uid # owner id or name -12345 -``` - - - -#### is_dir(path: PathLike | str) → bool - -Check if specified path is a directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if path is a directory, `False` otherwise. -* **Raises:** - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - -### Examples - -```pycon ->>> connection.is_dir("/path/to/dir") -True ->>> connection.is_dir("/path/to/dir/file.csv") -False -``` - - - -#### is_file(path: PathLike | str) → bool - -Check if specified path is a file. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if path is a file, `False` otherwise. -* **Raises:** - FileNotFoundError - : Path does not exist - -### Examples - -```pycon ->>> connection.is_file("/path/to/dir/file.csv") -True ->>> connection.is_file("/path/to/dir") -False -``` - - - -#### list_dir(path: PathLike | str, filters: Iterable[[BaseFileFilter](../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → list[RemoteDirectory | RemoteFile] - -Return list of child files/directories in a specific directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Directory path to list contents. - - **filters** - : Return only files/directories matching these filters. See [File Filters](../../file/file_filters/index.md#file-filters) - - **limits** - : Apply limits to the list of files/directories, and stop if one of the limits is reached. - See [File Limits](../../file/file_limits/index.md#file-limits) -* **Returns:** - List of `onetl.base.PathWithStatsProtocol` -* **Raises:** - NotADirectoryError - : Path is not a directory - - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - -### Examples - -```pycon ->>> dir_content = connection.list_dir("/path/to/dir") ->>> os.fspath(dir_content[0]) -'file.csv' ->>> connection.path_exists("/path/to/dir/file.csv") -True -``` - - - -#### path_exists(path: PathLike | str) → bool - -Check if specified path exists on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html). - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if path exists, `False` otherwise - -### Examples - -```pycon ->>> connection.path_exists("/path/to/file.csv") -True ->>> connection.path_exists("/path/to/dir") -True ->>> connection.path_exists("/path/to/missing") -False -``` - - - -#### remove_dir(path: PathLike | str, recursive: bool = False) → bool - -Remove directory or directory tree. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If directory does not exist, no exception is raised. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Directory path to remote - - **recursive** - : If `True`, remove directory tree recursively. -* **Returns:** - `True` if directory was removed, `False` if directory does not exist in the first place. -* **Raises:** - NotADirectoryError - : Path is not a directory - -### Examples - -```pycon ->>> connection.remove_dir("/path/to/dir") -True ->>> connection.path_exists("/path/to/dir") -False ->>> connection.path_exists("/path/to/dir/file.csv") -False ->>> connection.remove_dir("/path/to/dir") # already deleted, no error -False -``` - - - -#### remove_file(path: PathLike | str) → bool - -Removes file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If file does not exist, no exception is raised. - -#### WARNING -Supports only one file removal per call. Directory removal is **NOT** supported, use [`remove_dir`](#onetl.connection.file_connection.samba.Samba.remove_dir) instead. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : File path -* **Returns:** - `True` if file was removed, `False` if file does not exist in the first place. -* **Raises:** - `onetl.exception.NotAFileError` - : Path is not a file - -### Examples - -```pycon ->>> connection.remove_file("/path/to/file.csv") -True ->>> connection.path_exists("/path/to/dir/file.csv") -False ->>> connection.remove_file("/path/to/file.csv") # already deleted, no error -False -``` - - - -#### rename_file(source_file_path: PathLike | str, target_file_path: PathLike | str, replace: bool = False) → RemoteFile - -Rename or move file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -Supports only one file move per call. Directory move/rename is **NOT** supported. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **source_file_path** - : Old file path - - **target_file_path** - : New file path - - **replace** - : If `True`, existing file will be replaced. -* **Returns:** - New file path with stats. -* **Raises:** - `onetl.exception.NotAFileError` - : Source or target path is not a file - - FileNotFoundError - : File does not exist - - FileExistsError - : File already exists, and `replace=False` - -### Examples - -```pycon ->>> new_file = connection.rename_file("/path/to/file1.csv", "/path/to/file2.csv") ->>> os.fspath(new_file) -'/path/to/file2.csv' ->>> connection.path_exists("/path/to/file2.csv") -True ->>> connection.path_exists("/path/to/file1.csv") -False -``` - - - -#### resolve_dir(path: PathLike | str) → RemoteDirectory - -Returns directory at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to resolve -* **Returns:** - Directory path with stats -* **Raises:** - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - - NotADirectoryError - : Path is not a directory - -### Examples - -```pycon ->>> dir_path = connection.resolve_dir("/path/to/dir") ->>> os.fspath(dir_path) -'/path/to/dir' ->>> dir_path.stat().st_uid # owner id -12345 -``` - - - -#### resolve_file(path: PathLike | str) → RemoteFile - -Returns file at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to resolve -* **Returns:** - File path with stats -* **Raises:** - FileNotFoundError - : Path does not exist - - `onetl.exception.NotAFileError` - : Path is not a file - -### Examples - -```pycon ->>> file_path = connection.resolve_file("/path/to/dir/file.csv") ->>> os.fspath(file_path) -'/path/to/dir/file.csv' ->>> file_path.stat().st_uid # owner id -12345 -``` - - - -#### upload_file(local_file_path: PathLike | str, remote_file_path: PathLike | str, replace: bool = False) → RemoteFile - -Uploads local file to a remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -Supports only one file upload per call. Directory upload is **NOT** supported, use [File Uploader](../../file/file_uploader/file_uploader.md#file-uploader) instead. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **local_file_path** - : Local file path to read from - - **remote_file_path** - : Remote file path to create - - **replace** - : If `True`, existing file will be replaced -* **Returns:** - Remote file with stats. -* **Raises:** - `onetl.exception.NotAFileError` - : Remote or local path is not a file - - FileNotFoundError - : Local file does not exist - - FileExistsError - : Remote file already exists, and `replace=False` - - `onetl.exception.FileSizeMismatchError` - : Target file size after upload is different from source file size. - -### Examples - -```pycon ->>> remote_file = connection.upload( -... local_file_path="/path/to/source.csv", -... remote_file_path="/path/to/target.csv", -... ) ->>> os.fspath(remote_file) -'/path/to/target.csv' ->>> connection.path_exists("/path/to/target.csv") -True ->>> remote_file.stat().st_size # in bytes -1024 ->>> os.stat("/path/to/source.csv").st_size # same as source -1024 -``` - - diff --git a/mddocs/markdown/connection/file_connection/sftp.md b/mddocs/markdown/connection/file_connection/sftp.md deleted file mode 100644 index 1ee71d53c..000000000 --- a/mddocs/markdown/connection/file_connection/sftp.md +++ /dev/null @@ -1,635 +0,0 @@ - - -# SFTP connection - -### *class* onetl.connection.file_connection.sftp.SFTP(\*, host: Host, port: int = 22, user: str | None = None, password: SecretStr | None = None, key_file: FilePath | None = None, timeout: int = 10, host_key_check: bool = False, compress: bool = True) - -SFTP file connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on [Paramiko library](https://pypi.org/project/paramiko/). - -#### WARNING -Since onETL v0.7.0 to use SFTP connector you should install package as follows: - -```bash -pip install onetl[s3] - -# or -pip install onetl[files] -``` - -See [File connections](../../install/files.md#install-files) installation instruction for more details. - -#### Versionadded -Added in version 0.1.0. - -* **Parameters:** - **host** - : Host of SFTP source. For example: `192.168.1.19` - - **port** - : Port of SFTP source - - **user** - : User, which have access to the file source. For example: `someuser` - - **password** - : Password for file source connection - - **key_file** - : the filename of optional private key(s) and/or certs to try for authentication - - **timeout** - : How long to wait for the server to send data before giving up - - **host_key_check** - : set to True to enable searching for discoverable private key files in `~/.ssh/` - - **compress** - : Set to True to turn on compression - -### Examples - -Create and check SFTP connection: - -```python -from onetl.connection import SFTP - -sftp = SFTP( - host="192.168.1.19", - user="someuser", - password="*****", -).check() -``` - - - -#### \_\_init_\_(\*\*kwargs) - - - -#### check() - -Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If not, an exception will be raised. - -* **Returns:** - Connection itself -* **Raises:** - RuntimeError - : If the connection is not available - -### Examples - -```python -connection.check() -``` - - - -#### create_dir(path: PathLike | str) → RemoteDirectory - -Creates directory tree on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Directory path -* **Returns:** - Created directory with stats -* **Raises:** - `onetl.exception.NotAFileError` - : Path is not a file - -### Examples - -```pycon ->>> dir_path = connection.create_dir("/path/to/dir") ->>> os.fspath(dir_path) -'/path/to/dir' -``` - - - -#### download_file(remote_file_path: PathLike | str, local_file_path: PathLike | str, replace: bool = True) → LocalPath - -Downloads file from the remote filesystem to a local path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -Supports only one file download per call. Directory download is **NOT** supported, use [File Downloader](../../file/file_downloader/file_downloader.md#file-downloader) instead. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **remote_file_path** - : Remote file path to read from - - **local_file_path** - : Local file path to create - - **replace** - : If `True`, existing file will be replaced -* **Returns:** - Local file with stats. -* **Raises:** - `onetl.exception.NotAFileError` - : Remote or local path is not a file - - FileNotFoundError - : Remote file does not exist - - FileExistsError - : Local file already exists, and `replace=False` - - `onetl.exception.FileSizeMismatchError` - : Target file size after download is different from source file size. - -### Examples - -```pycon ->>> local_file = connection.download_file( -... remote_file_path="/path/to/source.csv", -... local_file_path="/path/to/target.csv", -... ) ->>> os.fspath(local_file) -'/path/to/target.csv' ->>> local_file.exists() -True ->>> local_file.stat().st_size # in bytes -1024 ->>> connection.get_stat("/path/to/source.csv").st_size # same size -1024 -``` - - - -#### get_stat(path: PathLike | str) → PathStatProtocol - -Returns stats for a specific path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to get stats for -* **Returns:** - Stats object -* **Raises:** - Any underlying client exception - -### Examples - -```pycon ->>> stat = connection.get_stat("/path/to/file.csv") ->>> stat.st_size # in bytes -1024 ->>> stat.st_uid # owner id or name -12345 -``` - - - -#### is_dir(path: PathLike | str) → bool - -Check if specified path is a directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if path is a directory, `False` otherwise. -* **Raises:** - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - -### Examples - -```pycon ->>> connection.is_dir("/path/to/dir") -True ->>> connection.is_dir("/path/to/dir/file.csv") -False -``` - - - -#### is_file(path: PathLike | str) → bool - -Check if specified path is a file. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if path is a file, `False` otherwise. -* **Raises:** - FileNotFoundError - : Path does not exist - -### Examples - -```pycon ->>> connection.is_file("/path/to/dir/file.csv") -True ->>> connection.is_file("/path/to/dir") -False -``` - - - -#### list_dir(path: PathLike | str, filters: Iterable[[BaseFileFilter](../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → list[RemoteDirectory | RemoteFile] - -Return list of child files/directories in a specific directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Directory path to list contents. - - **filters** - : Return only files/directories matching these filters. See [File Filters](../../file/file_filters/index.md#file-filters) - - **limits** - : Apply limits to the list of files/directories, and stop if one of the limits is reached. - See [File Limits](../../file/file_limits/index.md#file-limits) -* **Returns:** - List of `onetl.base.PathWithStatsProtocol` -* **Raises:** - NotADirectoryError - : Path is not a directory - - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - -### Examples - -```pycon ->>> dir_content = connection.list_dir("/path/to/dir") ->>> os.fspath(dir_content[0]) -'file.csv' ->>> connection.path_exists("/path/to/dir/file.csv") -True -``` - - - -#### path_exists(path: PathLike | str) → bool - -Check if specified path exists on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html). - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if path exists, `False` otherwise - -### Examples - -```pycon ->>> connection.path_exists("/path/to/file.csv") -True ->>> connection.path_exists("/path/to/dir") -True ->>> connection.path_exists("/path/to/missing") -False -``` - - - -#### remove_dir(path: PathLike | str, recursive: bool = False) → bool - -Remove directory or directory tree. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If directory does not exist, no exception is raised. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Directory path to remote - - **recursive** - : If `True`, remove directory tree recursively. -* **Returns:** - `True` if directory was removed, `False` if directory does not exist in the first place. -* **Raises:** - NotADirectoryError - : Path is not a directory - -### Examples - -```pycon ->>> connection.remove_dir("/path/to/dir") -True ->>> connection.path_exists("/path/to/dir") -False ->>> connection.path_exists("/path/to/dir/file.csv") -False ->>> connection.remove_dir("/path/to/dir") # already deleted, no error -False -``` - - - -#### remove_file(path: PathLike | str) → bool - -Removes file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If file does not exist, no exception is raised. - -#### WARNING -Supports only one file removal per call. Directory removal is **NOT** supported, use [`remove_dir`](#onetl.connection.file_connection.sftp.SFTP.remove_dir) instead. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : File path -* **Returns:** - `True` if file was removed, `False` if file does not exist in the first place. -* **Raises:** - `onetl.exception.NotAFileError` - : Path is not a file - -### Examples - -```pycon ->>> connection.remove_file("/path/to/file.csv") -True ->>> connection.path_exists("/path/to/dir/file.csv") -False ->>> connection.remove_file("/path/to/file.csv") # already deleted, no error -False -``` - - - -#### rename_dir(source_dir_path: PathLike | str, target_dir_path: PathLike | str, replace: bool = False) → RemoteDirectory - -Rename or move dir on remote filesystem. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **source_dir_path** - : Old directory path - - **target_dir_path** - : New directory path - - **replace** - : If `True`, existing directory will be replaced. -* **Returns:** - New directory path with stats. -* **Raises:** - NotADirectoryError - : Path is not a directory - - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - - `onetl.exception.DirectoryExistsError` - : Directory already exists, and `replace=False` - -### Examples - -```pycon ->>> new_dir = connection.rename_dir("/path/to/dir1", "/path/to/dir2") ->>> os.fspath(new_dir) -'/path/to/dir2' ->>> connection.path_exists("/path/to/dir1") -False ->>> connection.path_exists("/path/to/dir2") -True -``` - - - -#### rename_file(source_file_path: PathLike | str, target_file_path: PathLike | str, replace: bool = False) → RemoteFile - -Rename or move file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -Supports only one file move per call. Directory move/rename is **NOT** supported. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **source_file_path** - : Old file path - - **target_file_path** - : New file path - - **replace** - : If `True`, existing file will be replaced. -* **Returns:** - New file path with stats. -* **Raises:** - `onetl.exception.NotAFileError` - : Source or target path is not a file - - FileNotFoundError - : File does not exist - - FileExistsError - : File already exists, and `replace=False` - -### Examples - -```pycon ->>> new_file = connection.rename_file("/path/to/file1.csv", "/path/to/file2.csv") ->>> os.fspath(new_file) -'/path/to/file2.csv' ->>> connection.path_exists("/path/to/file2.csv") -True ->>> connection.path_exists("/path/to/file1.csv") -False -``` - - - -#### resolve_dir(path: PathLike | str) → RemoteDirectory - -Returns directory at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to resolve -* **Returns:** - Directory path with stats -* **Raises:** - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - - NotADirectoryError - : Path is not a directory - -### Examples - -```pycon ->>> dir_path = connection.resolve_dir("/path/to/dir") ->>> os.fspath(dir_path) -'/path/to/dir' ->>> dir_path.stat().st_uid # owner id -12345 -``` - - - -#### resolve_file(path: PathLike | str) → RemoteFile - -Returns file at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to resolve -* **Returns:** - File path with stats -* **Raises:** - FileNotFoundError - : Path does not exist - - `onetl.exception.NotAFileError` - : Path is not a file - -### Examples - -```pycon ->>> file_path = connection.resolve_file("/path/to/dir/file.csv") ->>> os.fspath(file_path) -'/path/to/dir/file.csv' ->>> file_path.stat().st_uid # owner id -12345 -``` - - - -#### upload_file(local_file_path: PathLike | str, remote_file_path: PathLike | str, replace: bool = False) → RemoteFile - -Uploads local file to a remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -Supports only one file upload per call. Directory upload is **NOT** supported, use [File Uploader](../../file/file_uploader/file_uploader.md#file-uploader) instead. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **local_file_path** - : Local file path to read from - - **remote_file_path** - : Remote file path to create - - **replace** - : If `True`, existing file will be replaced -* **Returns:** - Remote file with stats. -* **Raises:** - `onetl.exception.NotAFileError` - : Remote or local path is not a file - - FileNotFoundError - : Local file does not exist - - FileExistsError - : Remote file already exists, and `replace=False` - - `onetl.exception.FileSizeMismatchError` - : Target file size after upload is different from source file size. - -### Examples - -```pycon ->>> remote_file = connection.upload( -... local_file_path="/path/to/source.csv", -... remote_file_path="/path/to/target.csv", -... ) ->>> os.fspath(remote_file) -'/path/to/target.csv' ->>> connection.path_exists("/path/to/target.csv") -True ->>> remote_file.stat().st_size # in bytes -1024 ->>> os.stat("/path/to/source.csv").st_size # same as source -1024 -``` - - - -#### walk(root: PathLike | str, topdown: bool = True, filters: Iterable[[BaseFileFilter](../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → Iterator[tuple[RemoteDirectory, list[RemoteDirectory], list[RemoteFile]]] - -Walk into directory tree, and iterate over its content in all nesting levels. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Just like `os.walk`, but with additional filter/limit logic. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **root** - : Directory path to walk into. - - **topdown** - : If `True`, walk in top-down order, otherwise walk in bottom-up order. - - **filters** - : Return only files/directories matching these filters. See [File Filters](../../file/file_filters/index.md#file-filters). - - **limits** - : Apply limits to the list of files/directories, and immediately stop if any of these limits is reached. - See [File Limits](../../file/file_limits/index.md#file-limits). -* **Returns:** - `Iterator[tuple[root, dirs, files]]`, like `os.walk`. - - But all the paths are not strings, instead path classes with embedded stats are returned. -* **Raises:** - NotADirectoryError - : Path is not a directory - - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - -### Examples - -```pycon ->>> for root, dirs, files in connection.walk("/path/to/dir"): -... break ->>> os.fspath(root) -'/path/to/dir' ->>> dirs -[] ->>> os.fspath(files[0]) -'file.csv' ->>> connection.path_exists("/path/to/dir/file.csv") -True -``` - - diff --git a/mddocs/markdown/connection/file_connection/webdav.md b/mddocs/markdown/connection/file_connection/webdav.md deleted file mode 100644 index ef79f4023..000000000 --- a/mddocs/markdown/connection/file_connection/webdav.md +++ /dev/null @@ -1,592 +0,0 @@ - - -# WebDAV connection - -### *class* onetl.connection.file_connection.webdav.WebDAV(\*, host: Host, user: str, password: SecretStr, port: int | None = None, ssl_verify: bool | FilePath | DirectoryPath | SSLContext = True, protocol: Literal['http', 'https'] = 'https') - -WebDAV file connection. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on [WebdavClient3 library](https://pypi.org/project/webdavclient3/). - -#### WARNING -Since onETL v0.7.0 to use WebDAV connector you should install package as follows: - -```bash -pip install onetl[webdav] - -# or -pip install onetl[files] -``` - -See [File connections](../../install/files.md#install-files) installation instruction for more details. - -#### Versionadded -Added in version 0.6.0. - -* **Parameters:** - **host** - : Host of WebDAV source. For example: `webdav.domain.com` - - **user** - : User, which have access to the file source. For example: `someuser` - - **password** - : Password for file source connection - - **ssl_verify** - : SSL certificates used to verify the identity of requested hosts. Can be any of - : - `True` (uses default CA bundle), - - a path to an SSL certificate file, - - `False` (disable verification), or - - a `ssl.SSLContext` - - **protocol** - : Connection protocol. Allowed values: `https` or `http` - - **port** - : Connection port - -### Examples - -Create and check WebDAV connection: - -```python -from onetl.connection import WebDAV - -wd = WebDAV( - host="webdav.domain.com", - user="someuser", - password="*****", - protocol="https", -).check() -``` - - - -#### \_\_init_\_(\*\*kwargs) - - - -#### check() - -Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If not, an exception will be raised. - -* **Returns:** - Connection itself -* **Raises:** - RuntimeError - : If the connection is not available - -### Examples - -```python -connection.check() -``` - - - -#### create_dir(path: PathLike | str) → RemoteDirectory - -Creates directory tree on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Directory path -* **Returns:** - Created directory with stats -* **Raises:** - `onetl.exception.NotAFileError` - : Path is not a file - -### Examples - -```pycon ->>> dir_path = connection.create_dir("/path/to/dir") ->>> os.fspath(dir_path) -'/path/to/dir' -``` - - - -#### download_file(remote_file_path: PathLike | str, local_file_path: PathLike | str, replace: bool = True) → LocalPath - -Downloads file from the remote filesystem to a local path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -Supports only one file download per call. Directory download is **NOT** supported, use [File Downloader](../../file/file_downloader/file_downloader.md#file-downloader) instead. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **remote_file_path** - : Remote file path to read from - - **local_file_path** - : Local file path to create - - **replace** - : If `True`, existing file will be replaced -* **Returns:** - Local file with stats. -* **Raises:** - `onetl.exception.NotAFileError` - : Remote or local path is not a file - - FileNotFoundError - : Remote file does not exist - - FileExistsError - : Local file already exists, and `replace=False` - - `onetl.exception.FileSizeMismatchError` - : Target file size after download is different from source file size. - -### Examples - -```pycon ->>> local_file = connection.download_file( -... remote_file_path="/path/to/source.csv", -... local_file_path="/path/to/target.csv", -... ) ->>> os.fspath(local_file) -'/path/to/target.csv' ->>> local_file.exists() -True ->>> local_file.stat().st_size # in bytes -1024 ->>> connection.get_stat("/path/to/source.csv").st_size # same size -1024 -``` - - - -#### get_stat(path: PathLike | str) → PathStatProtocol - -Returns stats for a specific path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to get stats for -* **Returns:** - Stats object -* **Raises:** - Any underlying client exception - -### Examples - -```pycon ->>> stat = connection.get_stat("/path/to/file.csv") ->>> stat.st_size # in bytes -1024 ->>> stat.st_uid # owner id or name -12345 -``` - - - -#### is_dir(path: PathLike | str) → bool - -Check if specified path is a directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if path is a directory, `False` otherwise. -* **Raises:** - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - -### Examples - -```pycon ->>> connection.is_dir("/path/to/dir") -True ->>> connection.is_dir("/path/to/dir/file.csv") -False -``` - - - -#### is_file(path: PathLike | str) → bool - -Check if specified path is a file. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if path is a file, `False` otherwise. -* **Raises:** - FileNotFoundError - : Path does not exist - -### Examples - -```pycon ->>> connection.is_file("/path/to/dir/file.csv") -True ->>> connection.is_file("/path/to/dir") -False -``` - - - -#### list_dir(path: PathLike | str, filters: Iterable[[BaseFileFilter](../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → list[RemoteDirectory | RemoteFile] - -Return list of child files/directories in a specific directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Directory path to list contents. - - **filters** - : Return only files/directories matching these filters. See [File Filters](../../file/file_filters/index.md#file-filters) - - **limits** - : Apply limits to the list of files/directories, and stop if one of the limits is reached. - See [File Limits](../../file/file_limits/index.md#file-limits) -* **Returns:** - List of `onetl.base.PathWithStatsProtocol` -* **Raises:** - NotADirectoryError - : Path is not a directory - - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - -### Examples - -```pycon ->>> dir_content = connection.list_dir("/path/to/dir") ->>> os.fspath(dir_content[0]) -'file.csv' ->>> connection.path_exists("/path/to/dir/file.csv") -True -``` - - - -#### path_exists(path: PathLike | str) → bool - -Check if specified path exists on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html). - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if path exists, `False` otherwise - -### Examples - -```pycon ->>> connection.path_exists("/path/to/file.csv") -True ->>> connection.path_exists("/path/to/dir") -True ->>> connection.path_exists("/path/to/missing") -False -``` - - - -#### remove_dir(path: PathLike | str, recursive: bool = False) → bool - -Remove directory or directory tree. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If directory does not exist, no exception is raised. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Directory path to remote - - **recursive** - : If `True`, remove directory tree recursively. -* **Returns:** - `True` if directory was removed, `False` if directory does not exist in the first place. -* **Raises:** - NotADirectoryError - : Path is not a directory - -### Examples - -```pycon ->>> connection.remove_dir("/path/to/dir") -True ->>> connection.path_exists("/path/to/dir") -False ->>> connection.path_exists("/path/to/dir/file.csv") -False ->>> connection.remove_dir("/path/to/dir") # already deleted, no error -False -``` - - - -#### remove_file(path: PathLike | str) → bool - -Removes file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If file does not exist, no exception is raised. - -#### WARNING -Supports only one file removal per call. Directory removal is **NOT** supported, use [`remove_dir`](#onetl.connection.file_connection.webdav.WebDAV.remove_dir) instead. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : File path -* **Returns:** - `True` if file was removed, `False` if file does not exist in the first place. -* **Raises:** - `onetl.exception.NotAFileError` - : Path is not a file - -### Examples - -```pycon ->>> connection.remove_file("/path/to/file.csv") -True ->>> connection.path_exists("/path/to/dir/file.csv") -False ->>> connection.remove_file("/path/to/file.csv") # already deleted, no error -False -``` - - - -#### rename_file(source_file_path: PathLike | str, target_file_path: PathLike | str, replace: bool = False) → RemoteFile - -Rename or move file on remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -Supports only one file move per call. Directory move/rename is **NOT** supported. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **source_file_path** - : Old file path - - **target_file_path** - : New file path - - **replace** - : If `True`, existing file will be replaced. -* **Returns:** - New file path with stats. -* **Raises:** - `onetl.exception.NotAFileError` - : Source or target path is not a file - - FileNotFoundError - : File does not exist - - FileExistsError - : File already exists, and `replace=False` - -### Examples - -```pycon ->>> new_file = connection.rename_file("/path/to/file1.csv", "/path/to/file2.csv") ->>> os.fspath(new_file) -'/path/to/file2.csv' ->>> connection.path_exists("/path/to/file2.csv") -True ->>> connection.path_exists("/path/to/file1.csv") -False -``` - - - -#### resolve_dir(path: PathLike | str) → RemoteDirectory - -Returns directory at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to resolve -* **Returns:** - Directory path with stats -* **Raises:** - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - - NotADirectoryError - : Path is not a directory - -### Examples - -```pycon ->>> dir_path = connection.resolve_dir("/path/to/dir") ->>> os.fspath(dir_path) -'/path/to/dir' ->>> dir_path.stat().st_uid # owner id -12345 -``` - - - -#### resolve_file(path: PathLike | str) → RemoteFile - -Returns file at specific path, with stats. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to resolve -* **Returns:** - File path with stats -* **Raises:** - FileNotFoundError - : Path does not exist - - `onetl.exception.NotAFileError` - : Path is not a file - -### Examples - -```pycon ->>> file_path = connection.resolve_file("/path/to/dir/file.csv") ->>> os.fspath(file_path) -'/path/to/dir/file.csv' ->>> file_path.stat().st_uid # owner id -12345 -``` - - - -#### upload_file(local_file_path: PathLike | str, remote_file_path: PathLike | str, replace: bool = False) → RemoteFile - -Uploads local file to a remote filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -Supports only one file upload per call. Directory upload is **NOT** supported, use [File Uploader](../../file/file_uploader/file_uploader.md#file-uploader) instead. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **local_file_path** - : Local file path to read from - - **remote_file_path** - : Remote file path to create - - **replace** - : If `True`, existing file will be replaced -* **Returns:** - Remote file with stats. -* **Raises:** - `onetl.exception.NotAFileError` - : Remote or local path is not a file - - FileNotFoundError - : Local file does not exist - - FileExistsError - : Remote file already exists, and `replace=False` - - `onetl.exception.FileSizeMismatchError` - : Target file size after upload is different from source file size. - -### Examples - -```pycon ->>> remote_file = connection.upload( -... local_file_path="/path/to/source.csv", -... remote_file_path="/path/to/target.csv", -... ) ->>> os.fspath(remote_file) -'/path/to/target.csv' ->>> connection.path_exists("/path/to/target.csv") -True ->>> remote_file.stat().st_size # in bytes -1024 ->>> os.stat("/path/to/source.csv").st_size # same as source -1024 -``` - - - -#### walk(root: PathLike | str, topdown: bool = True, filters: Iterable[[BaseFileFilter](../../file/file_filters/base.md#onetl.base.base_file_filter.BaseFileFilter)] | None = None, limits: Iterable[[BaseFileLimit](../../file/file_limits/base.md#onetl.base.base_file_limit.BaseFileLimit)] | None = None) → Iterator[tuple[RemoteDirectory, list[RemoteDirectory], list[RemoteFile]]] - -Walk into directory tree, and iterate over its content in all nesting levels. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Just like `os.walk`, but with additional filter/limit logic. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **root** - : Directory path to walk into. - - **topdown** - : If `True`, walk in top-down order, otherwise walk in bottom-up order. - - **filters** - : Return only files/directories matching these filters. See [File Filters](../../file/file_filters/index.md#file-filters). - - **limits** - : Apply limits to the list of files/directories, and immediately stop if any of these limits is reached. - See [File Limits](../../file/file_limits/index.md#file-limits). -* **Returns:** - `Iterator[tuple[root, dirs, files]]`, like `os.walk`. - - But all the paths are not strings, instead path classes with embedded stats are returned. -* **Raises:** - NotADirectoryError - : Path is not a directory - - `onetl.exception.DirectoryNotFoundError` - : Path does not exist - -### Examples - -```pycon ->>> for root, dirs, files in connection.walk("/path/to/dir"): -... break ->>> os.fspath(root) -'/path/to/dir' ->>> dirs -[] ->>> os.fspath(files[0]) -'file.csv' ->>> connection.path_exists("/path/to/dir/file.csv") -True -``` - - diff --git a/mddocs/markdown/connection/file_df_connection/base.md b/mddocs/markdown/connection/file_df_connection/base.md deleted file mode 100644 index 8f58267d8..000000000 --- a/mddocs/markdown/connection/file_df_connection/base.md +++ /dev/null @@ -1,63 +0,0 @@ - - -# Base interface - -### *class* onetl.base.base_file_df_connection.BaseFileDFConnection - -Implements generic methods for reading and writing dataframe as files. - -#### Versionadded -Added in version 0.9.0. - - - -#### *abstract* check() → T - -Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If not, an exception will be raised. - -* **Returns:** - Connection itself -* **Raises:** - RuntimeError - : If the connection is not available - -### Examples - -```python -connection.check() -``` - - - -#### *abstract* check_if_format_supported(format: [BaseReadableFileFormat](../../file_df/file_formats/base.md#onetl.base.base_file_format.BaseReadableFileFormat) | [BaseWritableFileFormat](../../file_df/file_formats/base.md#onetl.base.base_file_format.BaseWritableFileFormat)) → None - -Validate if specific file format is supported. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.9.0. - -* **Raises:** - RuntimeError - : If file format is not supported. - - - -#### *abstract* read_files_as_df(paths: list[PurePathProtocol], format: [BaseReadableFileFormat](../../file_df/file_formats/base.md#onetl.base.base_file_format.BaseReadableFileFormat), root: PurePathProtocol | None = None, df_schema: StructType | None = None, options: FileDFReadOptions | None = None) → DataFrame - -Read files in some paths list as dataframe. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.9.0. - - - -#### *abstract* write_df_as_files(df: DataFrame, path: PurePathProtocol, format: [BaseWritableFileFormat](../../file_df/file_formats/base.md#onetl.base.base_file_format.BaseWritableFileFormat), options: FileDFWriteOptions | None = None) → None - -Write dataframe as files in some path. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.9.0. - - diff --git a/mddocs/markdown/connection/file_df_connection/index.md b/mddocs/markdown/connection/file_df_connection/index.md deleted file mode 100644 index ef8d51549..000000000 --- a/mddocs/markdown/connection/file_df_connection/index.md +++ /dev/null @@ -1,20 +0,0 @@ - - -# File DataFrame Connections - -# File DataFrame Connections - -* [Spark LocalFS](spark_local_fs.md) - * [`SparkLocalFS`](spark_local_fs.md#onetl.connection.file_df_connection.spark_local_fs.SparkLocalFS) -* [Spark HDFS](spark_hdfs/index.md) - * [Prerequisites](spark_hdfs/prerequisites.md) - * [Connection](spark_hdfs/connection.md) - * [Slots](spark_hdfs/slots.md) -* [Spark S3](spark_s3/index.md) - * [Prerequisites](spark_s3/prerequisites.md) - * [Connection](spark_s3/connection.md) - * [Troubleshooting](spark_s3/troubleshooting.md) - -# For developers - -* [Base interface](base.md) diff --git a/mddocs/markdown/connection/file_df_connection/spark_hdfs/connection.md b/mddocs/markdown/connection/file_df_connection/spark_hdfs/connection.md deleted file mode 100644 index fe8e621a9..000000000 --- a/mddocs/markdown/connection/file_df_connection/spark_hdfs/connection.md +++ /dev/null @@ -1,169 +0,0 @@ - - -# Spark HDFS Connection - -### *class* onetl.connection.file_df_connection.spark_hdfs.connection.SparkHDFS(\*, spark: SparkSession, cluster: Cluster, host: Host | None = None, port: int = 8020) - -Spark connection to HDFS. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on [Spark Generic File Data Source](https://spark.apache.org/docs/latest/sql-data-sources-generic-options.html). - -#### SEE ALSO -Before using this connector please take into account [Prerequisites](prerequisites.md#spark-hdfs-prerequisites) - -#### NOTE -Supports only reading files as Spark DataFrame and writing DataFrame to files. - -Does NOT support file operations, like create, delete, rename, etc. For these operations, -use [`HDFS`](../../file_connection/hdfs/connection.md#onetl.connection.file_connection.hdfs.connection.HDFS) connection. - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **cluster** - : Cluster name. -
- Used for: - : * HWM and lineage (as instance name for file paths) - * Validation of `host` value, - : if latter is passed and if some hooks are bound to - [`Slots.get_cluster_namenodes`](slots.md#onetl.connection.file_df_connection.spark_hdfs.slots.SparkHDFSSlots.get_cluster_namenodes). - - **host** - : Hadoop namenode host. For example: `namenode1.domain.com`. -
- Should be an active namenode (NOT standby). -
- If value is not set, but there are some hooks bound to - [`Slots.get_cluster_namenodes`](slots.md#onetl.connection.file_df_connection.spark_hdfs.slots.SparkHDFSSlots.get_cluster_namenodes) - and - [`Slots.is_namenode_active`](slots.md#onetl.connection.file_df_connection.spark_hdfs.slots.SparkHDFSSlots.is_namenode_active), - onETL will iterate over cluster namenodes to detect which one is active. - - **ipc_port** - : Port of Hadoop namenode (IPC protocol). -
- If omitted, but there are some hooks bound to - [`Slots.get_ipc_port`](slots.md#onetl.connection.file_df_connection.spark_hdfs.slots.SparkHDFSSlots.get_ipc_port), - onETL will try to detect port number for a specific `cluster`. - - **spark** - : Spark session - -### Examples - -Create SparkHDFS connection with Kerberos auth - -Execute `kinit` consome command before creating Spark Session - -```bash -$ kinit -kt /path/to/keytab user -``` - -```python -from onetl.connection import SparkHDFS -from pyspark.sql import SparkSession - -# Create Spark session. -# Use names "spark.yarn.access.hadoopFileSystems", "spark.yarn.principal" -# and "spark.yarn.keytab" for Spark 2 - -spark = ( - SparkSession.builder.appName("spark-app-name") - .option( - "spark.kerberos.access.hadoopFileSystems", - "hdfs://namenode1.domain.com:8020", - ) - .option("spark.kerberos.principal", "user") - .option("spark.kerberos.keytab", "/path/to/keytab") - .enableHiveSupport() - .getOrCreate() -) - -# Create connection -hdfs = SparkHDFS( - host="namenode1.domain.com", - cluster="rnd-dwh", - spark=spark, -).check() -``` - -Create SparkHDFS connection with anonymous auth - -```py -from onetl.connection import SparkHDFS -from pyspark.sql import SparkSession - -# Create Spark session -spark = SparkSession.builder.master("local").appName("spark-app-name").getOrCreate() - -# Create connection -hdfs = SparkHDFS( - host="namenode1.domain.com", - cluster="rnd-dwh", - spark=spark, -).check() -``` - -Use cluster name to detect active namenode - -Can be used only if some third-party plugin provides [Spark HDFS Slots](slots.md#spark-hdfs-slots) implementation - -```python -# Create Spark session -... - -# Create connection -hdfs = SparkHDFS(cluster="rnd-dwh", spark=spark).check() -``` - - - -#### check() - -Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If not, an exception will be raised. - -* **Returns:** - Connection itself -* **Raises:** - RuntimeError - : If the connection is not available - -### Examples - -```python -connection.check() -``` - - - -#### *classmethod* get_current(spark: SparkSession) - -Create connection for current cluster. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Automatically sets up current cluster name as `cluster`. - -#### NOTE -Can be used only if there are a some hooks bound to -[`Slots.get_current_cluster`](slots.md#onetl.connection.file_df_connection.spark_hdfs.slots.SparkHDFSSlots.get_current_cluster). - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **spark** - : See [`SparkHDFS`](#onetl.connection.file_df_connection.spark_hdfs.connection.SparkHDFS) constructor documentation. - -### Examples - -```python -from onetl.connection import SparkHDFS - -# injecting current cluster name via hooks mechanism -hdfs = SparkHDFS.get_current(spark=spark) -``` - - diff --git a/mddocs/markdown/connection/file_df_connection/spark_hdfs/index.md b/mddocs/markdown/connection/file_df_connection/spark_hdfs/index.md deleted file mode 100644 index d5eae4c27..000000000 --- a/mddocs/markdown/connection/file_df_connection/spark_hdfs/index.md +++ /dev/null @@ -1,12 +0,0 @@ - - -# Spark HDFS - -# Connection - -* [Prerequisites](prerequisites.md) -* [Connection](connection.md) - -# For developers - -* [Slots](slots.md) diff --git a/mddocs/markdown/connection/file_df_connection/spark_hdfs/prerequisites.md b/mddocs/markdown/connection/file_df_connection/spark_hdfs/prerequisites.md deleted file mode 100644 index 2011410da..000000000 --- a/mddocs/markdown/connection/file_df_connection/spark_hdfs/prerequisites.md +++ /dev/null @@ -1,46 +0,0 @@ - - -# Prerequisites - -## Version Compatibility - -* Hadoop versions: 2.x, 3.x (only with Hadoop 3.x libraries) -* Spark versions: 2.3.x - 3.5.x -* Java versions: 8 - 20 - -## Installing PySpark - -To use SparkHDFS connector you should have PySpark installed (or injected to `sys.path`) -BEFORE creating the connector instance. - -See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. - -## Using Kerberos - -Some of Hadoop managed clusters use Kerberos authentication. In this case, you should call [kinit](https://web.mit.edu/kerberos/krb5-1.12/doc/user/user_commands/kinit.html) command -**BEFORE** starting Spark session to generate Kerberos ticket. See [Kerberos support](../../../install/kerberos.md#install-kerberos). - -Sometimes it is also required to pass keytab file to Spark config, allowing Spark executors to generate own Kerberos tickets: - -Spark 3 - -```python -SparkSession.builder - .option("spark.kerberos.access.hadoopFileSystems", "hdfs://namenode1.domain.com:9820,hdfs://namenode2.domain.com:9820") - .option("spark.kerberos.principal", "user") - .option("spark.kerberos.keytab", "/path/to/keytab") - .gerOrCreate() -``` - -Spark 2 - -```python -SparkSession.builder - .option("spark.yarn.access.hadoopFileSystems", "hdfs://namenode1.domain.com:9820,hdfs://namenode2.domain.com:9820") - .option("spark.yarn.principal", "user") - .option("spark.yarn.keytab", "/path/to/keytab") - .gerOrCreate() -``` - -See [Spark security documentation](https://spark.apache.org/docs/latest/security.html#kerberos) -for more details. diff --git a/mddocs/markdown/connection/file_df_connection/spark_hdfs/slots.md b/mddocs/markdown/connection/file_df_connection/spark_hdfs/slots.md deleted file mode 100644 index a94ddcde0..000000000 --- a/mddocs/markdown/connection/file_df_connection/spark_hdfs/slots.md +++ /dev/null @@ -1,256 +0,0 @@ - - -# Spark HDFS Slots - -### *class* onetl.connection.file_df_connection.spark_hdfs.slots.SparkHDFSSlots - -Spark HDFS slots that could be implemented by third-party plugins. - -#### Versionadded -Added in version 0.9.0. - - - -#### *static* normalize_cluster_name(cluster: str) → str | None - -Normalize cluster name passed into SparkHDFS constructor. - -If hooks didn’t return anything, cluster name is left intact. - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **cluster** - : Cluster name -* **Returns:** - str | None - : Normalized cluster name. -
- If hook cannot be applied to a specific cluster, it should return `None`. - -### Examples - -```python -from onetl.connection import SparkHDFS -from onetl.hooks import hook - -@SparkHDFS.Slots.normalize_cluster_name.bind -@hook -def normalize_cluster_name(cluster: str) -> str: - return cluster.lower() -``` - - - -#### *static* normalize_namenode_host(host: str, cluster: str) → str | None - -Normalize namenode host passed into SparkHDFS constructor. - -If hooks didn’t return anything, host is left intact. - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **host** - : Namenode host (raw) - - **cluster** - : Cluster name (normalized) -* **Returns:** - str | None - : Normalized namenode host name. -
- If hook cannot be applied to a specific host name, it should return `None`. - -### Examples - -```python -from onetl.connection import SparkHDFS -from onetl.hooks import hook - -@SparkHDFS.Slots.normalize_namenode_host.bind -@hook -def normalize_namenode_host(host: str, cluster: str) -> str | None: - if cluster == "rnd-dwh": - if not host.endswith(".domain.com"): - # fix missing domain name - host += ".domain.com" - return host - - return None -``` - - - -#### *static* get_known_clusters() → set[str] | None - -Return collection of known clusters. - -Cluster passed into SparkHDFS constructor should be present in this list. -If hooks didn’t return anything, no validation will be performed. - -#### Versionadded -Added in version 0.9.0. - -* **Returns:** - set[str] | None - : Collection of cluster names (in normalized form). -
- If hook cannot be applied, it should return `None`. - -### Examples - -```python -from onetl.connection import SparkHDFS -from onetl.hooks import hook - -@SparkHDFS.Slots.get_known_clusters.bind -@hook -def get_known_clusters() -> str[str]: - return {"rnd-dwh", "rnd-prod"} -``` - - - -#### *static* get_cluster_namenodes(cluster: str) → set[str] | None - -Return collection of known namenodes for the cluster. - -Namenode host passed into SparkHDFS constructor should be present in this list. -If hooks didn’t return anything, no validation will be performed. - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **cluster** - : Cluster name (normalized) -* **Returns:** - set[str] | None - : Collection of host names (in normalized form). -
- If hook cannot be applied, it should return `None`. - -### Examples - -```python -from onetl.connection import SparkHDFS -from onetl.hooks import hook - -@SparkHDFS.Slots.get_cluster_namenodes.bind -@hook -def get_cluster_namenodes(cluster: str) -> str[str] | None: - if cluster == "rnd-dwh": - return {"namenode1.domain.com", "namenode2.domain.com"} - return None -``` - - - -#### *static* get_current_cluster() → str | None - -Get current cluster name. - -Used in [`get_current_cluster`](#onetl.connection.file_df_connection.spark_hdfs.slots.SparkHDFSSlots.get_current_cluster) to automatically fill up `cluster` attribute of a connection. -If hooks didn’t return anything, calling the method above will raise an exception. - -#### Versionadded -Added in version 0.9.0. - -* **Returns:** - str | None - : Current cluster name (in normalized form). -
- If hook cannot be applied, it should return `None`. - -### Examples - -```python -from onetl.connection import SparkHDFS -from onetl.hooks import hook - -@SparkHDFS.Slots.get_current_cluster.bind -@hook -def get_current_cluster() -> str: - # some magic here - return "rnd-dwh" -``` - - - -#### *static* get_ipc_port(cluster: str) → int | None - -Get IPC port number for a specific cluster. - -Used by constructor to automatically set port number if omitted. - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **cluster** - : Cluster name (normalized) -* **Returns:** - int | None - : IPC port number. -
- If hook cannot be applied, it should return `None`. - -### Examples - -```python -from onetl.connection import SparkHDFS -from onetl.hooks import hook - -@SparkHDFS.Slots.get_ipc_port.bind -@hook -def get_ipc_port(cluster: str) -> int | None: - if cluster == "rnd-dwh": - return 8020 # Cloudera - return None -``` - - - -#### *static* is_namenode_active(host: str, cluster: str) → bool | None - -Check whether a namenode of a specified cluster is active (=not standby) or not. - -Used for: -: * If SparkHDFS connection is created without `host` - > Connector will iterate over [`get_cluster_namenodes`](#onetl.connection.file_df_connection.spark_hdfs.slots.SparkHDFSSlots.get_cluster_namenodes) of a cluster to get active namenode, - > and then use it instead of `host` attribute. - * If SparkHDFS connection is created with `host` - > `check` will determine whether this host is active. - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **host** - : Namenode host (normalized) - - **cluster** - : Cluster name (normalized) -* **Returns:** - bool | None - : `True` if namenode is active, `False` if not. -
- If hook cannot be applied, it should return `None`. - -### Examples - -```python -from onetl.connection import SparkHDFS -from onetl.hooks import hook - -@SparkHDFS.Slots.is_namenode_active.bind -@hook -def is_namenode_active(host: str, cluster: str) -> bool: - # some magic here - return True -``` - - diff --git a/mddocs/markdown/connection/file_df_connection/spark_local_fs.md b/mddocs/markdown/connection/file_df_connection/spark_local_fs.md deleted file mode 100644 index 1620f08bc..000000000 --- a/mddocs/markdown/connection/file_df_connection/spark_local_fs.md +++ /dev/null @@ -1,65 +0,0 @@ - - -# Spark LocalFS - -### *class* onetl.connection.file_df_connection.spark_local_fs.SparkLocalFS(\*, spark: SparkSession) - -Spark connection to local filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on [Spark Generic File Data Source](https://spark.apache.org/docs/latest/sql-data-sources-generic-options.html). - -#### WARNING -To use SparkHDFS connector you should have PySpark installed (or injected to `sys.path`) -BEFORE creating the connector instance. - -See [Spark](../../install/spark.md#install-spark) installation instruction for more details. - -#### WARNING -Currently supports only Spark sessions created with option `spark.master: local`. - -#### NOTE -Supports only reading files as Spark DataFrame and writing DataFrame to files. - -Does NOT support file operations, like create, delete, rename, etc. - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **spark** - : Spark session - -### Examples - -```python -from onetl.connection import SparkLocalFS -from pyspark.sql import SparkSession - -# create Spark session -spark = SparkSession.builder.master("local").appName("spark-app-name").getOrCreate() - -# create connection -local_fs = SparkLocalFS(spark=spark).check() -``` - - - -#### check() - -Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If not, an exception will be raised. - -* **Returns:** - Connection itself -* **Raises:** - RuntimeError - : If the connection is not available - -### Examples - -```python -connection.check() -``` - - diff --git a/mddocs/markdown/connection/file_df_connection/spark_s3/connection.md b/mddocs/markdown/connection/file_df_connection/spark_s3/connection.md deleted file mode 100644 index e51ec8421..000000000 --- a/mddocs/markdown/connection/file_df_connection/spark_s3/connection.md +++ /dev/null @@ -1,232 +0,0 @@ - - -# Spark S3 Connection - -### *class* onetl.connection.file_df_connection.spark_s3.connection.SparkS3(\*, spark: SparkSession, host: Host, port: int | None = None, bucket: str, protocol: Literal['http', 'https'] = 'https', access_key: str | None = None, secret_key: SecretStr | None = None, session_token: SecretStr | None = None, region: str | None = None, extra: SparkS3Extra = SparkS3Extra()) - -Spark connection to S3 filesystem. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on [Hadoop-AWS module](https://hadoop.apache.org/docs/current3/hadoop-aws/tools/hadoop-aws/index.html) -and [Spark integration with Cloud Infrastructures](https://spark.apache.org/docs/latest/cloud-integration.html). - -#### SEE ALSO -Before using this connector please take into account [Prerequisites](prerequisites.md#spark-s3-prerequisites) - -#### NOTE -Supports only reading files as Spark DataFrame and writing DataFrame to files. - -Does NOT support file operations, like create, delete, rename, etc. For these operations, -use [`S3`](../../file_connection/s3.md#onetl.connection.file_connection.s3.S3) connection. - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **host** - : Host of S3 source. For example: `domain.com` - - **port** - : Port of S3 source - - **bucket** - : Bucket name in the S3 file source - - **protocol** - : Connection protocol. Allowed values: `https` or `http` - - **access_key** - : Access key (aka user ID) of an account in the S3 service - - **secret_key** - : Secret key (aka password) of an account in the S3 service - - **session_token** - : Session token of your account in S3 service - - **region** - : Region name of bucket in S3 service - - **extra** - : A dictionary of additional properties to be used when connecting to S3. -
- These are Hadoop AWS specific properties, see links below: - * [Hadoop AWS](https://hadoop.apache.org/docs/current/hadoop-aws/tools/hadoop-aws/index.html#General_S3A_Client_configuration) - * [Hadoop AWS committers options](https://hadoop.apache.org/docs/current/hadoop-aws/tools/hadoop-aws/committers.html) -
- Options are passed without prefixes `spark.hadoop.`, `fs.s3a.` and `fs.s3a.bucket.$BUCKET.`, for example: - ```python - extra = { - "path.style.access": True, - "committer.magic.enabled": True, - "committer.name": "magic", - "connection.timeout": 300000, - } - ``` -
- #### WARNING - Options that populated from connection - attributes (like `endpoint`, `access.key`) are not allowed to override. -
- But you may override `aws.credentials.provider` and pass custom credential options. - - **spark** - : Spark session - -### Examples - -Create S3 connection with bucket as subdomain (`my-bucket.domain.com`): - -```py -from onetl.connection import SparkS3 -from pyspark.sql import SparkSession - -# Create Spark session with Hadoop AWS libraries loaded -maven_packages = SparkS3.get_packages(spark_version="3.5.5") -# Some packages are not used, but downloading takes a lot of time. Skipping them. -excluded_packages = SparkS3.get_exclude_packages() -spark = ( - SparkSession.builder.appName("spark-app-name") - .config("spark.jars.packages", ",".join(maven_packages)) - .config("spark.jars.excludes", ",".join(excluded_packages)) - .config("spark.hadoop.fs.s3a.committer.magic.enabled", "true") - .config("spark.hadoop.fs.s3a.committer.name", "magic") - .config( - "spark.hadoop.mapreduce.outputcommitter.factory.scheme.s3a", - "org.apache.hadoop.fs.s3a.commit.S3ACommitterFactory", - ) - .config( - "spark.sql.parquet.output.committer.class", - "org.apache.spark.internal.io.cloud.BindingParquetOutputCommitter", - ) - .config( - "spark.sql.sources.commitProtocolClass", - "org.apache.spark.internal.io.cloud.PathOutputCommitProtocol", - ) - .getOrCreate() -) - -# Create connection -s3 = SparkS3( - host="domain.com", - protocol="http", - bucket="my-bucket", - access_key="ACCESS_KEY", - secret_key="SECRET_KEY", - spark=spark, -).check() -``` - -Create S3 connection with bucket as subpath (`domain.com/my-bucket`) - -```py -# Create Spark session with Hadoop AWS libraries loaded -... - -# Create connection -s3 = SparkS3( - host="domain.com", - protocol="http", - bucket="my-bucket", - access_key="ACCESS_KEY", - secret_key="SECRET_KEY", - extra={ - "path.style.access": True, # <--- - }, - spark=spark, -).check() -``` - - - -#### check() - -Check source availability. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -If not, an exception will be raised. - -* **Returns:** - Connection itself -* **Raises:** - RuntimeError - : If the connection is not available - -### Examples - -```python -connection.check() -``` - - - -#### close() - -Close all connections created to S3. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Also resets all `fs.s3a.bucket.$BUCKET.*` properties of Hadoop configuration. - -#### NOTE -Connection can be used again after it was closed. - -* **Returns:** - Connection itself - -### Examples - -Close connection automatically: - -```python -with connection: - ... -``` - -Close connection manually: - -```python -connection.close() -``` - - - -#### *classmethod* get_exclude_packages() → list[str] - -Get package names to be excluded by Spark. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.13.0. - -### Examples - -```python -from onetl.connection import SparkS3 - -SparkS3.get_exclude_packages() -``` - - - -#### *classmethod* get_packages(spark_version: str, scala_version: str | None = None) → list[str] - -Get package names to be downloaded by Spark. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **spark_version** - : Spark version in format `major.minor.patch`. - - **scala_version** - : Scala version in format `major.minor`. -
- If `None`, `spark_version` is used to determine Scala version. - -### Examples - -```python -from onetl.connection import SparkS3 - -SparkS3.get_packages(spark_version="3.5.5") -SparkS3.get_packages(spark_version="3.5.5", scala_version="2.12") -``` - - diff --git a/mddocs/markdown/connection/file_df_connection/spark_s3/index.md b/mddocs/markdown/connection/file_df_connection/spark_s3/index.md deleted file mode 100644 index be66a0adc..000000000 --- a/mddocs/markdown/connection/file_df_connection/spark_s3/index.md +++ /dev/null @@ -1,9 +0,0 @@ - - -# Spark S3 - -# Connection - -* [Prerequisites](prerequisites.md) -* [Connection](connection.md) -* [Troubleshooting](troubleshooting.md) diff --git a/mddocs/markdown/connection/file_df_connection/spark_s3/prerequisites.md b/mddocs/markdown/connection/file_df_connection/spark_s3/prerequisites.md deleted file mode 100644 index 1bfd0846a..000000000 --- a/mddocs/markdown/connection/file_df_connection/spark_s3/prerequisites.md +++ /dev/null @@ -1,61 +0,0 @@ - - -# Prerequisites - -## Version Compatibility - -* Spark versions: 3.2.x - 3.5.x (only with Hadoop 3.x libraries) -* Java versions: 8 - 20 - -## Installing PySpark - -To use SparkS3 connector you should have PySpark installed (or injected to `sys.path`) -BEFORE creating the connector instance. - -See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. - -## Connecting to S3 - -### Bucket access style - -AWS and some other S3 cloud providers allows bucket access using domain style only, e.g. `https://mybucket.s3provider.com`. - -Other implementations, like Minio, by default allows path style access only, e.g. `https://s3provider.com/mybucket` -(see [MINIO_DOMAIN](https://min.io/docs/minio/linux/reference/minio-server/minio-server.html#envvar.MINIO_DOMAIN)). - -You should set `path.style.access` to `True` or `False`, to choose the preferred style. - -### Authentication - -Different S3 instances can use different authentication methods, like: -: * `access_key + secret_key` (or username + password) - * `access_key + secret_key + session_token` - -Usually these are just passed to SparkS3 constructor: - -```python -SparkS3( - access_key=..., - secret_key=..., - session_token=..., -) -``` - -But some S3 cloud providers, like AWS, may require custom credential providers. You can pass them like: - -```python -SparkS3( - extra={ - # provider class - "aws.credentials.provider": "org.apache.hadoop.fs.s3a.auth.AssumedRoleCredentialProvider", - # other options, if needed - "assumed.role.arn": "arn:aws:iam::90066806600238:role/s3-restricted", - }, -) -``` - -See [Hadoop-AWS](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/index.html#Changing_Authentication_Providers) documentation. - -## Troubleshooting - -See [Spark S3 Troubleshooting](troubleshooting.md#spark-s3-troubleshooting). diff --git a/mddocs/markdown/connection/file_df_connection/spark_s3/troubleshooting.md b/mddocs/markdown/connection/file_df_connection/spark_s3/troubleshooting.md deleted file mode 100644 index 32ea15667..000000000 --- a/mddocs/markdown/connection/file_df_connection/spark_s3/troubleshooting.md +++ /dev/null @@ -1,366 +0,0 @@ - - -# Spark S3 Troubleshooting - -#### NOTE -General guide: [Troubleshooting](../../../troubleshooting/index.md#troubleshooting). - -More details: - -* [Hadoop AWS Troubleshooting Guide](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/troubleshooting_s3a.html) -* [Hadoop AWS Performance Guide](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/performance.html) -* [Spark integration with Cloud Infrastructures](https://spark.apache.org/docs/latest/cloud-integration.html) - -## `SparkS3.check()` and other methods hang - -### Details - -S3 may not respond for connection attempts for a long time if it’s under heavy load. -To handle this, Hadoop AWS library has retry mechanism. By default it retries 7 times with 500ms interval. - -Hadoop AWS is based on AWS SDK library, which also has retry mechanism. This mechanism is not disabled because it handles different -errors than Hadoop AWS, so they complement each other. Default number of attempts in AWS SDK is 20 with minimal 5s interval, -which is exponentially increasing with each failed attempt. - -It is not a problem if S3 source is not accessible at all, like hostname cannot be resolved, or port is not opened. -These errors are not recoverable, and retry mechanism is not activated. - -But errors like SSL issues, are considered recoverable, and this causing retry of retry over increasing interval. -So user is waiting for [almost 15 minutes](https://issues.apache.org/jira/browse/HADOOP-18839) just to get exception message. - -### How to determine reason - -#### Make logging more verbose - -Change Spark session log level to [DEBUG](../../../troubleshooting/spark.md#troubleshooting-spark) to print result of each attempt. -Resulting logs will look like this - -### See log - -```text -23/08/03 11:25:10 DEBUG S3AFileSystem: Using S3ABlockOutputStream with buffer = disk; block=67108864; queue limit=4 -23/08/03 11:25:10 DEBUG S3Guard: Metastore option source [core-default.xml] -23/08/03 11:25:10 DEBUG S3Guard: Using NullMetadataStore metadata store for s3a filesystem -23/08/03 11:25:10 DEBUG S3AFileSystem: S3Guard is disabled on this bucket: test-bucket -23/08/03 11:25:10 DEBUG DirectoryPolicyImpl: Directory markers will be deleted -23/08/03 11:25:10 DEBUG S3AFileSystem: Directory marker retention policy is DirectoryMarkerRetention{policy='delete'} -23/08/03 11:25:10 DEBUG S3AUtils: Value of fs.s3a.multipart.purge.age is 86400 -23/08/03 11:25:10 DEBUG S3AUtils: Value of fs.s3a.bulk.delete.page.size is 250 -23/08/03 11:25:10 DEBUG FileSystem: Creating FS s3a://test-bucket/fake: duration 0:01.029s -23/08/03 11:25:10 DEBUG IOStatisticsStoreImpl: Incrementing counter op_is_directory by 1 with final value 1 -23/08/03 11:25:10 DEBUG S3AFileSystem: Getting path status for s3a://test-bucket/fake (fake); needEmptyDirectory=false -23/08/03 11:25:10 DEBUG S3AFileSystem: S3GetFileStatus s3a://test-bucket/fake -23/08/03 11:25:10 DEBUG S3AFileSystem: LIST List test-bucket:/fake/ delimiter=/ keys=2 requester pays=false -23/08/03 11:25:10 DEBUG S3AFileSystem: Starting: LIST -23/08/03 11:25:10 DEBUG IOStatisticsStoreImpl: Incrementing counter object_list_request by 1 with final value 1 -23/08/03 11:25:10 DEBUG AWSCredentialProviderList: Using credentials from SimpleAWSCredentialsProvider -23/08/03 11:25:10 DEBUG request: Sending Request: GET https://test-bucket.localhost:9000 / Parameters: ({"list-type":["2"],"delimiter":["/"],"max-keys":["2"],"prefix":["fake/"],"fetch-owner":["false"]}Headers: (amz-sdk-invocation-id: e6d62603-96e4-a80f-10a1-816e0822bc71, Content-Type: application/octet-stream, User-Agent: Hadoop 3.3.4, aws-sdk-java/1.12.262 Linux/6.4.7-1-MANJARO OpenJDK_64-Bit_Server_VM/25.292-b10 java/1.8.0_292 scala/2.12.17 vendor/AdoptOpenJDK cfg/retry-mode/legacy, ) -23/08/03 11:25:10 DEBUG AWS4Signer: AWS4 Canonical Request: '"GET -/ -delimiter=%2F&fetch-owner=false&list-type=2&max-keys=2&prefix=fake%2F -amz-sdk-invocation-id:e6d62603-96e4-a80f-10a1-816e0822bc71 -amz-sdk-request:attempt=1;max=21 -amz-sdk-retry:0/0/500 -content-type:application/octet-stream -host:test-bucket.localhost:9000 -user-agent:Hadoop 3.3.4, aws-sdk-java/1.12.262 Linux/6.4.7-1-MANJARO OpenJDK_64-Bit_Server_VM/25.292-b10 java/1.8.0_292 scala/2.12.17 vendor/AdoptOpenJDK cfg/retry-mode/legacy -x-amz-content-sha256:UNSIGNED-PAYLOAD -x-amz-date:20230803T112510Z - -amz-sdk-invocation-id;amz-sdk-request;amz-sdk-retry;content-type;host;user-agent;x-amz-content-sha256;x-amz-date -UNSIGNED-PAYLOAD" -23/08/03 11:25:10 DEBUG AWS4Signer: AWS4 String to Sign: '"AWS4-HMAC-SHA256 -20230803T112510Z -20230803/us-east-1/s3/aws4_request -31a317bb7f6d97248dd0cf03429d701cbb3e29ce889cfbb98ba7a34c57a3bfba" -23/08/03 11:25:10 DEBUG AWS4Signer: Generating a new signing key as the signing key not available in the cache for the date 1691020800000 -23/08/03 11:25:10 DEBUG RequestAddCookies: CookieSpec selected: default -23/08/03 11:25:10 DEBUG RequestAuthCache: Auth cache not set in the context -23/08/03 11:25:10 DEBUG PoolingHttpClientConnectionManager: Connection request: [route: {s}->https://test-bucket.localhost:9000][total available: 0; route allocated: 0 of 96; total allocated: 0 of 96] -23/08/03 11:25:10 DEBUG PoolingHttpClientConnectionManager: Connection leased: [id: 0][route: {s}->https://test-bucket.localhost:9000][total available: 0; route allocated: 1 of 96; total allocated: 1 of 96] -23/08/03 11:25:10 DEBUG MainClientExec: Opening connection {s}->https://test-bucket.localhost:9000 -23/08/03 11:25:10 DEBUG DefaultHttpClientConnectionOperator: Connecting to test-bucket.localhost/127.0.0.1:9000 -23/08/03 11:25:10 DEBUG SSLConnectionSocketFactory: Connecting socket to test-bucket.localhost/127.0.0.1:9000 with timeout 5000 -23/08/03 11:25:10 DEBUG SSLConnectionSocketFactory: Enabled protocols: [TLSv1.2] -23/08/03 11:25:10 DEBUG SSLConnectionSocketFactory: Enabled cipher suites:[TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV] -23/08/03 11:25:10 DEBUG SSLConnectionSocketFactory: Starting handshake -23/08/03 11:25:10 DEBUG ClientConnectionManagerFactory: -java.lang.reflect.InvocationTargetException - at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) - at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) - at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) - at java.lang.reflect.Method.invoke(Method.java:498) - at com.amazonaws.http.conn.ClientConnectionManagerFactory$Handler.invoke(ClientConnectionManagerFactory.java:76) - at com.amazonaws.http.conn.$Proxy32.connect(Unknown Source) - at com.amazonaws.thirdparty.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:393) - at com.amazonaws.thirdparty.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236) - at com.amazonaws.thirdparty.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186) - at com.amazonaws.thirdparty.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185) - at com.amazonaws.thirdparty.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83) - at com.amazonaws.thirdparty.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56) - at com.amazonaws.http.apache.client.impl.SdkHttpClient.execute(SdkHttpClient.java:72) - at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeOneRequest(AmazonHttpClient.java:1346) - at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeHelper(AmazonHttpClient.java:1157) - at com.amazonaws.http.AmazonHttpClient$RequestExecutor.doExecute(AmazonHttpClient.java:814) - at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeWithTimer(AmazonHttpClient.java:781) - at com.amazonaws.http.AmazonHttpClient$RequestExecutor.execute(AmazonHttpClient.java:755) - at com.amazonaws.http.AmazonHttpClient$RequestExecutor.access$500(AmazonHttpClient.java:715) - at com.amazonaws.http.AmazonHttpClient$RequestExecutionBuilderImpl.execute(AmazonHttpClient.java:697) - at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:561) - at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:541) - at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5456) - at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5403) - at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5397) - at com.amazonaws.services.s3.AmazonS3Client.listObjectsV2(AmazonS3Client.java:971) - at org.apache.hadoop.fs.s3a.S3AFileSystem.lambda$listObjects$11(S3AFileSystem.java:2595) - at org.apache.hadoop.fs.statistics.impl.IOStatisticsBinding.lambda$trackDurationOfOperation$5(IOStatisticsBinding.java:499) - at org.apache.hadoop.fs.s3a.Invoker.retryUntranslated(Invoker.java:414) - at org.apache.hadoop.fs.s3a.Invoker.retryUntranslated(Invoker.java:377) - at org.apache.hadoop.fs.s3a.S3AFileSystem.listObjects(S3AFileSystem.java:2586) - at org.apache.hadoop.fs.s3a.S3AFileSystem.s3GetFileStatus(S3AFileSystem.java:3832) - at org.apache.hadoop.fs.s3a.S3AFileSystem.innerGetFileStatus(S3AFileSystem.java:3688) - at org.apache.hadoop.fs.s3a.S3AFileSystem.lambda$isDirectory$35(S3AFileSystem.java:4724) - at org.apache.hadoop.fs.statistics.impl.IOStatisticsBinding.lambda$trackDurationOfOperation$5(IOStatisticsBinding.java:499) - at org.apache.hadoop.fs.statistics.impl.IOStatisticsBinding.trackDuration(IOStatisticsBinding.java:444) - at org.apache.hadoop.fs.s3a.S3AFileSystem.trackDurationAndSpan(S3AFileSystem.java:2337) - at org.apache.hadoop.fs.s3a.S3AFileSystem.trackDurationAndSpan(S3AFileSystem.java:2356) - at org.apache.hadoop.fs.s3a.S3AFileSystem.isDirectory(S3AFileSystem.java:4722) - at org.apache.spark.sql.execution.streaming.FileStreamSink$.hasMetadata(FileStreamSink.scala:54) - at org.apache.spark.sql.execution.datasources.DataSource.resolveRelation(DataSource.scala:366) - at org.apache.spark.sql.DataFrameReader.loadV1Source(DataFrameReader.scala:229) - at org.apache.spark.sql.DataFrameReader.$anonfun$load$2(DataFrameReader.scala:211) - at scala.Option.getOrElse(Option.scala:189) - at org.apache.spark.sql.DataFrameReader.load(DataFrameReader.scala:211) - at org.apache.spark.sql.DataFrameReader.load(DataFrameReader.scala:186) - at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) - at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) - at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) - at java.lang.reflect.Method.invoke(Method.java:498) - at py4j.reflection.MethodInvoker.invoke(MethodInvoker.java:244) - at py4j.reflection.ReflectionEngine.invoke(ReflectionEngine.java:374) - at py4j.Gateway.invoke(Gateway.java:282) - at py4j.commands.AbstractCommand.invokeMethod(AbstractCommand.java:132) - at py4j.commands.CallCommand.execute(CallCommand.java:79) - at py4j.ClientServerConnection.waitForCommands(ClientServerConnection.java:182) - at py4j.ClientServerConnection.run(ClientServerConnection.java:106) - at java.lang.Thread.run(Thread.java:748) -Caused by: javax.net.ssl.SSLException: Unsupported or unrecognized SSL message - at sun.security.ssl.SSLSocketInputRecord.handleUnknownRecord(SSLSocketInputRecord.java:448) - at sun.security.ssl.SSLSocketInputRecord.decode(SSLSocketInputRecord.java:184) - at sun.security.ssl.SSLTransport.decode(SSLTransport.java:109) - at sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1383) - at sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1291) - at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:435) - at com.amazonaws.thirdparty.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:436) - at com.amazonaws.thirdparty.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:384) - at com.amazonaws.thirdparty.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142) - at com.amazonaws.thirdparty.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:376) - ... 58 more -23/08/03 11:25:10 DEBUG DefaultManagedHttpClientConnection: http-outgoing-0: Shutdown connection -23/08/03 11:25:10 DEBUG MainClientExec: Connection discarded -23/08/03 11:25:10 DEBUG PoolingHttpClientConnectionManager: Connection released: [id: 0][route: {s}->https://test-bucket.localhost:9000][total available: 0; route allocated: 0 of 96; total allocated: 0 of 96] -23/08/03 11:25:10 DEBUG AmazonHttpClient: Unable to execute HTTP request: Unsupported or unrecognized SSL message Request will be retried. -23/08/03 11:25:10 DEBUG request: Retrying Request: GET https://test-bucket.localhost:9000 / Parameters: ({"list-type":["2"],"delimiter":["/"],"max-keys":["2"],"prefix":["fake/"],"fetch-owner":["false"]}Headers: (amz-sdk-invocation-id: e6d62603-96e4-a80f-10a1-816e0822bc71, Content-Type: application/octet-stream, User-Agent: Hadoop 3.3.4, aws-sdk-java/1.12.262 Linux/6.4.7-1-MANJARO OpenJDK_64-Bit_Server_VM/25.292-b10 java/1.8.0_292 scala/2.12.17 vendor/AdoptOpenJDK cfg/retry-mode/legacy, ) -23/08/03 11:25:10 DEBUG AmazonHttpClient: Retriable error detected, will retry in 49ms, attempt number: 0 -``` - -#### Change number of retries - -You can also change number of retries performed by both libraries using `extra` parameter: - -```python -spark_s3 = SparkS3( - ..., - extra={ - "attempts.maximum": 1, - "retry.limit": 1, - }, -) -``` - -So accessing S3 will fail almost immediately if there is any error. - -### Most common mistakes - -#### No network access - -```text -Caused by: java.net.ConnectException: Connection refused -``` - -Mostly caused by: - -* Trying to access port number which S3 server does not listen -* You’re trying to access host which is unreachable from your network (e.g. running behind some proxy or VPN) -* There are some firewall restrictions for accessing specific host or port - -#### Using HTTPS protocol for HTTP port - -```text -Caused by: javax.net.ssl.SSLException: Unsupported or unrecognized SSL message -``` - -By default, SparkS3 uses HTTPS protocol for connection. -If you change port number, this does not lead to changing protocol: - -```python -spark_s3 = SparkS3(host="s3provider.com", port=8080, ...) -``` - -You should pass protocol explicitly: - -```python -spark_s3 = SparkS3(host="s3provider.com", port=8080, protocol="http", ...) -``` - -#### SSL certificate is self-signed - -```text -sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target -``` - -To connect to HTTPS port with self-signed certificate, you should -[add certificate chain to Java TrustedStore](https://stackoverflow.com/questions/373295/digital-certificate-how-to-import-cer-file-in-to-truststore-file-using). - -Another option is to disable SSL check: - -```python -spark_s3 = SparkS3( - ..., - extra={ - "connection.ssl.enabled": False, - }, -) -``` - -But is is **NOT** recommended. - -#### Accessing S3 without domain-style access style support - -```text -Caused by: java.net.UnknownHostException: my-bucket.s3provider.com -``` - -To use path-style access, use option below: - -```python -spark_s3 = SparkS3( - host="s3provider.com", - bucket="my-bucket", - ..., - extra={ - "path.style.access": True, - }, -) -``` - -## Slow or unstable writing to S3 - -Hadoop AWS allows to use different writing strategies for different S3 implementations, depending -on list of supported features by server. - -These strategies are called [committers](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/committers.html). -There are [different types of committers](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/committers.html#Switching_to_an_S3A_Committer): - -* `file` (default) -* `directory` -* `partitioned` -* `magic` - -### `file` committer - -This committer is quite slow and unstable, so it is not recommended to use: - -```text -WARN AbstractS3ACommitterFactory: Using standard FileOutputCommitter to commit work. This is slow and potentially unsafe. -``` - -This is caused by the fact it creates files in the temp directory on remote filesystem, and after all of them are written successfully, -they are moved to target directory on same remote filesystem. - -This is not an issue for HDFS which does support file move operations and also support renaming directory -as atomic operation with `O(1)` time complexity. - -But S3 does support only file copying, so moving is performed via copy + delete. -Also it does not support atomic directory rename operation. Instead, renaming files with the same prefix has time complexity `O(n)`. - -### `directory` and `partitioned` committers - -These are [staging committers](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/committer_architecture.html), -meaning that they create temp directories on local filesystem, and after all files are written successfully, -they will be uploaded to S3. Local filesystems do support file moving and directory renaming, -so these committers does not have issues that `file` committer has. - -But they both require free space on local filesystem, and this may be an issue if user need to write large amount of data. -Also this can be an issue for container environment, like Kubernetes, there resources should be allocated before starting a container. - -### `magic` committer - -This committer uses multipart upload feature of S3 API, allowing to create multiple files -and after all of them were written successfully finish the transaction. Before transaction is finished, -files will not be accessible by other clients. - -Because it does not require neither file moving operations, nor directory atomic rename, -upload process is done in most efficient way S3 support. -This [drastically increases writing performance](https://spot.io/blog/improve-apache-spark-performance-with-the-s3-magic-committer/). - -To use this committer, set [following properties](https://github.com/apache/spark/pull/32518) while creating Spark session. - -S3 your main distributed filesystem (Spark on Kubernetes) - -```py -# https://issues.apache.org/jira/browse/SPARK-23977 -# https://spark.apache.org/docs/latest/cloud-integration.html#committing-work-into-cloud-storage-safely-and-fast -spark = ( - SparkSession.builder.appName("spark-app-name") - .config("spark.hadoop.fs.s3a.committer.magic.enabled", "true") - .config("spark.hadoop.fs.s3a.committer.name", "magic") - .config("spark.hadoop.mapreduce.outputcommitter.factory.scheme.s3a", "org.apache.hadoop.fs.s3a.commit.S3ACommitterFactory") - .config("spark.sql.parquet.output.committer.class", "org.apache.spark.internal.io.cloud.BindingParquetOutputCommitter") - .config("spark.sql.sources.commitProtocolClass", "org.apache.spark.internal.io.cloud.PathOutputCommitProtocol") - .getOrCreate() -) -``` - -HDFS is your main distributed filesystem (Spark on Hadoop) - -```py -# https://community.cloudera.com/t5/Support-Questions/spark-sql-sources-partitionOverwriteMode-dynamic-quot-not/m-p/343483/highlight/true -spark = ( - SparkSession.builder.appName("spark-app-name") - .config("spark.hadoop.fs.s3a.committer.magic.enabled", "true") - .config("spark.hadoop.fs.s3a.committer.name", "magic") - .getOrCreate() -) -``` - -#### WARNING -`magic` committer requires S3 implementation to have strong consistency - file upload API return response only -if it was written on enough number of cluster nodes, and any cluster node error does not lead to missing or corrupting files. - -Some S3 implementations does have strong consistency -(like [AWS S3](https://aws.amazon.com/ru/blogs/aws/amazon-s3-update-strong-read-after-write-consistency/) and -[MinIO](https://blog.min.io/migrating-hdfs-to-object-storage/)), some not. Please contact your S3 provider -to get information about S3 implementation consistency. - -#### WARNING -`magic` committer does not support `if_exists="replace_overlapping_partitions"`. -Either use another `if_exists` value, or use `partitioned` committer. - -### See also - -* [directory.marker.retention=”keep”](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/directory_markers.html) - -## Slow reading from S3 - -Please read following documentation: - -* [prefetch.enabled](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/prefetching.html) -* [experimental.input.fadvise](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/performance.html#Improving_data_input_performance_through_fadvise) -* [Parquet and ORC I/O settings](https://spark.apache.org/docs/latest/cloud-integration.html#parquet-io-settings) - -If you’re reading data from row-based formats, like [CSV](../../../file_df/file_formats/csv.md#csv-file-format), prefer -[experimental.input.fadvise=”sequential” with increased readahead.range](https://issues.apache.org/jira/browse/HADOOP-17789?focusedCommentId=17383559#comment-17383559). - -But for other file formats, especially using compression, prefer -[experimental.input.fadvise=”normal”](https://issues.apache.org/jira/browse/HADOOP-17789?focusedCommentId=17383743#comment-17383743) diff --git a/mddocs/markdown/connection/index.md b/mddocs/markdown/connection/index.md deleted file mode 100644 index 5ad28edef..000000000 --- a/mddocs/markdown/connection/index.md +++ /dev/null @@ -1,34 +0,0 @@ - - - DB Connection - -* [DB Connections](db_connection/index.md) - * [Clickhouse](db_connection/clickhouse/index.md) - * [Greenplum](db_connection/greenplum/index.md) - * [Kafka](db_connection/kafka/index.md) - * [Hive](db_connection/hive/index.md) - * [MongoDB](db_connection/mongodb/index.md) - * [MSSQL](db_connection/mssql/index.md) - * [MySQL](db_connection/mysql/index.md) - * [Oracle](db_connection/oracle/index.md) - * [Postgres](db_connection/postgres/index.md) - * [Teradata](db_connection/teradata/index.md) - - File Connection - -* [File Connections](file_connection/index.md) - * [FTP](file_connection/ftp.md) - * [FTPS](file_connection/ftps.md) - * [HDFS](file_connection/hdfs/index.md) - * [Samba](file_connection/samba.md) - * [SFTP](file_connection/sftp.md) - * [S3](file_connection/s3.md) - * [Webdav](file_connection/webdav.md) - - File DataFrame Connection - -* [File DataFrame Connections](file_df_connection/index.md) - * [Spark LocalFS](file_df_connection/spark_local_fs.md) - * [Spark HDFS](file_df_connection/spark_hdfs/index.md) - * [Spark S3](file_df_connection/spark_s3/index.md) - * [Base interface](file_df_connection/base.md) diff --git a/mddocs/markdown/contributing.md b/mddocs/markdown/contributing.md deleted file mode 100644 index 739798897..000000000 --- a/mddocs/markdown/contributing.md +++ /dev/null @@ -1,391 +0,0 @@ -# Contributing Guide - -Welcome! There are many ways to contribute, including submitting bug -reports, improving documentation, submitting feature requests, reviewing -new submissions, or contributing code that can be incorporated into the -project. - -## Limitations - -We should keep close to these items during development: - -* Some companies still use old Spark versions, like 2.3.1. So it is required to keep compatibility if possible, e.g. adding branches for different Spark versions. -* Different users uses onETL in different ways - some uses only DB connectors, some only files. Connector-specific dependencies should be optional. -* Instead of creating classes with a lot of different options, prefer splitting them into smaller classes, e.g. options class, context manager, etc, and using composition. - -## Initial setup for local development - -### Install Git - -Please follow [instruction](https://docs.github.com/en/get-started/quickstart/set-up-git). - -### Create a fork - -If you are not a member of a development team building onETL, you should create a fork before making any changes. - -Please follow [instruction](https://docs.github.com/en/get-started/quickstart/fork-a-repo). - -### Clone the repo - -Open terminal and run these commands: - -```bash -git clone git@github.com:myuser/onetl.git -b develop - -cd onetl -``` - -### Setup environment - -Create virtualenv and install dependencies: - -```bash -python -m venv venv -source venv/bin/activate -pip install -U wheel -pip install -U pip setuptools -pip install -U \ - -r requirements/core.txt \ - -r requirements/ftp.txt \ - -r requirements/hdfs.txt \ - -r requirements/kerberos.txt \ - -r requirements/s3.txt \ - -r requirements/sftp.txt \ - -r requirements/webdav.txt \ - -r requirements/dev.txt \ - -r requirements/docs.txt \ - -r requirements/tests/base.txt \ - -r requirements/tests/clickhouse.txt \ - -r requirements/tests/kafka.txt \ - -r requirements/tests/mongodb.txt \ - -r requirements/tests/mssql.txt \ - -r requirements/tests/mysql.txt \ - -r requirements/tests/postgres.txt \ - -r requirements/tests/oracle.txt \ - -r requirements/tests/pydantic-2.txt \ - -r requirements/tests/spark-3.5.5.txt - -# TODO: remove after https://github.com/zqmillet/sphinx-plantuml/pull/4 -pip install sphinx-plantuml --no-deps -``` - -### Enable pre-commit hooks - -Install pre-commit hooks: - -```bash -pre-commit install --install-hooks -``` - -Test pre-commit hooks run: - -```bash -pre-commit run -``` - -## How to - -### Run tests locally - -#### Using docker-compose - -Build image for running tests: - -```bash -docker-compose build -``` - -Start all containers with dependencies: - -```bash -docker-compose --profile all up -d -``` - -You can run limited set of dependencies: - -```bash -docker-compose --profile mongodb up -d -``` - -Run tests: - -```bash -docker-compose run --rm onetl ./run_tests.sh -``` - -You can pass additional arguments, they will be passed to pytest: - -```bash -docker-compose run --rm onetl ./run_tests.sh -m mongodb -lsx -vvvv --log-cli-level=INFO -``` - -You can run interactive bash session and use it: - -```bash -docker-compose run --rm onetl bash - -./run_tests.sh -m mongodb -lsx -vvvv --log-cli-level=INFO -``` - -See logs of test container: - -```bash -docker-compose logs -f onetl -``` - -Stop all containers and remove created volumes: - -```bash -docker-compose --profile all down -v -``` - -#### Without docker-compose - -#### WARNING -To run HDFS tests locally you should add the following line to your `/etc/hosts` (file path depends on OS): - -```default -# HDFS server returns container hostname as connection address, causing error in DNS resolution -127.0.0.1 hdfs -``` - -#### NOTE -To run Oracle tests you need to install [Oracle instantclient](https://www.oracle.com/database/technologies/instant-client.html), -and pass its path to `ONETL_ORA_CLIENT_PATH` and `LD_LIBRARY_PATH` environment variables, -e.g. `ONETL_ORA_CLIENT_PATH=/path/to/client64/lib`. - -It may also require to add the same path into `LD_LIBRARY_PATH` environment variable - -#### NOTE -To run Greenplum tests, you should: - -* Download [VMware Greenplum connector for Spark](https://onetl.readthedocs.io/en/latest/connection/db_connection/greenplum/prerequisites.html) -* Either move it to `~/.ivy2/jars/`, or pass file path to `CLASSPATH` -* Set environment variable `ONETL_GP_PACKAGE_VERSION=local`. - -Start all containers with dependencies: - -```bash -docker-compose --profile all up -d -``` - -You can run limited set of dependencies: - -```bash -docker-compose --profile mongodb up -d -``` - -Load environment variables with connection properties: - -```bash -source .env.local -``` - -Run tests: - -```bash -./run_tests.sh -``` - -You can pass additional arguments, they will be passed to pytest: - -```bash -./run_tests.sh -m mongodb -lsx -vvvv --log-cli-level=INFO -``` - -Stop all containers and remove created volumes: - -```bash -docker-compose --profile all down -v -``` - -### Build documentation - -Build documentation using Sphinx: - -```bash -cd docs -make html -``` - -Then open in browser `docs/_build/index.html`. - -## Review process - -Please create a new GitHub issue for any significant changes and -enhancements that you wish to make. Provide the feature you would like -to see, why you need it, and how it will work. Discuss your ideas -transparently and get community feedback before proceeding. - -Significant Changes that you wish to contribute to the project should be -discussed first in a GitHub issue that clearly outlines the changes and -benefits of the feature. - -Small Changes can directly be crafted and submitted to the GitHub -Repository as a Pull Request. - -### Create pull request - -Commit your changes: - -```bash -git commit -m "Commit message" -git push -``` - -Then open Github interface and [create pull request](https://docs.github.com/en/get-started/quickstart/contributing-to-projects#making-a-pull-request). -Please follow guide from PR body template. - -After pull request is created, it get a corresponding number, e.g. 123 (`pr_number`). - -### Write release notes - -`onETL` uses [towncrier](https://pypi.org/project/towncrier/) -for changelog management. - -To submit a change note about your PR, add a text file into the -[docs/changelog/next_release](./next_release) folder. It should contain an -explanation of what applying this PR will change in the way -end-users interact with the project. One sentence is usually -enough but feel free to add as many details as you feel necessary -for the users to understand what it means. - -**Use the past tense** for the text in your fragment because, -combined with others, it will be a part of the “news digest” -telling the readers **what changed** in a specific version of -the library *since the previous version*. - -You should also use -reStructuredText syntax for highlighting code (inline or block), -linking parts of the docs or external sites. -If you wish to sign your change, feel free to add `-- by -:user:`github-username`` at the end (replace `github-username` -with your own!). - -Finally, name your file following the convention that Towncrier -understands: it should start with the number of an issue or a -PR followed by a dot, then add a patch type, like `feature`, -`doc`, `misc` etc., and add `.rst` as a suffix. If you -need to add more than one fragment, you may add an optional -sequence number (delimited with another period) between the type -and the suffix. - -In general the name will follow `..rst` pattern, -where the categories are: - -- `feature`: Any new feature -- `bugfix`: A bug fix -- `improvement`: An improvement -- `doc`: A change to the documentation -- `dependency`: Dependency-related changes -- `misc`: Changes internal to the repo like CI, test and build changes - -A pull request may have more than one of these components, for example -a code change may introduce a new feature that deprecates an old -feature, in which case two fragments should be added. It is not -necessary to make a separate documentation fragment for documentation -changes accompanying the relevant code changes. - -#### Examples for adding changelog entries to your Pull Requests - -```rst -Added a ``:github:user:`` role to Sphinx config -- by :github:user:`someuser` -``` - -```rst -Fixed behavior of ``WebDAV`` connector -- by :github:user:`someuser` -``` - -```rst -Added support of ``timeout`` in ``S3`` connector --- by :github:user:`someuser`, :github:user:`anotheruser` and :github:user:`otheruser` -``` - -#### How to skip change notes check? - -Just add `ci:skip-changelog` label to pull request. - -#### Release Process - -Before making a release from the `develop` branch, follow these steps: - -1. Checkout to `develop` branch and update it to the actual state - -```bash -git checkout develop -git pull -p -``` - -1. Backup `NEXT_RELEASE.rst` - -```bash -cp "docs/changelog/NEXT_RELEASE.rst" "docs/changelog/temp_NEXT_RELEASE.rst" -``` - -1. Build the Release notes with Towncrier - -```bash -VERSION=$(cat onetl/VERSION) -towncrier build "--version=${VERSION}" --yes -``` - -1. Change file with changelog to release version number - -```bash -mv docs/changelog/NEXT_RELEASE.rst "docs/changelog/${VERSION}.rst" -``` - -1. Remove content above the version number heading in the `${VERSION}.rst` file - -```bash -awk '!/^.*towncrier release notes start/' "docs/changelog/${VERSION}.rst" > temp && mv temp "docs/changelog/${VERSION}.rst" -``` - -1. Update Changelog Index - -```bash -awk -v version=${VERSION} '/DRAFT/{print;print " " version;next}1' docs/changelog/index.rst > temp && mv temp docs/changelog/index.rst -``` - -1. Restore `NEXT_RELEASE.rst` file from backup - -```bash -mv "docs/changelog/temp_NEXT_RELEASE.rst" "docs/changelog/NEXT_RELEASE.rst" -``` - -1. Commit and push changes to `develop` branch - -```bash -git add . -git commit -m "Prepare for release ${VERSION}" -git push -``` - -1. Merge `develop` branch to `master`, **WITHOUT** squashing - -```bash -git checkout master -git pull -git merge develop -git push -``` - -1. Add git tag to the latest commit in `master` branch - -```bash -git tag "$VERSION" -git push origin "$VERSION" -``` - -1. Update version in `develop` branch **after release**: - -```bash -git checkout develop - -NEXT_VERSION=$(echo "$VERSION" | awk -F. '/[0-9]+\./{$NF++;print}' OFS=.) -echo "$NEXT_VERSION" > onetl/VERSION - -git add . -git commit -m "Bump version" -git push -``` diff --git a/mddocs/markdown/db/db_reader.md b/mddocs/markdown/db/db_reader.md deleted file mode 100644 index 29209279a..000000000 --- a/mddocs/markdown/db/db_reader.md +++ /dev/null @@ -1,346 +0,0 @@ - - -# DB Reader - -| [`DBReader`](#onetl.db.db_reader.db_reader.DBReader) | Allows you to read data from a table with specified database connection and parameters, and return its content as Spark dataframe. | -|------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------| -| [`DBReader.run`](#onetl.db.db_reader.db_reader.DBReader.run)() | Reads data from source table and saves as Spark dataframe. | -| [`DBReader.has_data`](#onetl.db.db_reader.db_reader.DBReader.has_data)() | Returns `True` if there is some data in the source, `False` otherwise. | -| [`DBReader.raise_if_no_data`](#onetl.db.db_reader.db_reader.DBReader.raise_if_no_data)() | Raises exception `NoDataError` if source does not contain any data. | - -### *class* onetl.db.db_reader.db_reader.DBReader(\*, connection: BaseDBConnection, table: str, columns: ConstrainedListValue[str] | None = None, where: Any | None = None, hint: Any | None = None, df_schema: StructType | None = None, hwm_column: str | tuple | None = None, hwm_expression: str | None = None, hwm: AutoDetectHWM | ColumnHWM | KeyValueHWM | None = None, options: GenericOptions | None = None) - -Allows you to read data from a table with specified database connection -and parameters, and return its content as Spark dataframe. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### NOTE -DBReader can return different results depending on [Read Strategies](../strategy/index.md#strategy) - -#### NOTE -This class operates with only one source at a time. It does NOT support executing queries -to multiple source, like `SELECT ... JOIN`. - -#### Versionadded -Added in version 0.1.0. - -#### Versionchanged -Changed in version 0.8.0: Moved `onetl.core.DBReader` → `onetl.db.DBReader` - -* **Parameters:** - **connection** - : Class which contains DB connection properties. See [DB Connections](../connection/db_connection/index.md#db-connections) section - - **source** - : Table/collection/etc name to read data from. -
- If connection has schema support, you need to specify the full name of the source - including the schema, e.g. `schema.name`. -
- #### Versionchanged - Changed in version 0.7.0: Renamed `table` → `source` - - **columns** - : The list of columns to be read. -
- If RDBMS supports any kind of expressions, you can pass them too. - ```python - columns = [ - "mycolumn", - "another_column as alias", - "count(*) over ()", - "some(function) as alias2", - ] - ``` -
- #### NOTE - Some sources does not have columns. -
- #### NOTE - It is recommended to pass column names explicitly to avoid selecting too many columns, - and to avoid adding unexpected columns to dataframe if source DDL is changed. -
- #### Deprecated - Deprecated since version 0.10.0: Syntax `DBReader(columns="col1, col2")` (string instead of list) is not supported, - and will be removed in v1.0.0 - - **where** - : Custom `where` for SQL query or MongoDB pipeline. -
- `where` syntax depends on the source. For example, SQL sources - accept `where` as a string, but MongoDB sources accept `where` as a dictionary. - ```python - # SQL database connection - where = "column_1 > 2" -
- # MongoDB connection - where = { - "col_1": {"$gt": 1, "$lt": 100}, - "col_2": {"$gt": 2}, - "col_3": {"$eq": "hello"}, - } - ``` -
- #### NOTE - Some sources does not support data filtering. - - **hwm** - : HWM class to be used as [HWM](https://etl-entities.readthedocs.io/en/stable/hwm/index.html) value. - ```python - hwm = DBReader.AutoDetectHWM( - name="some_unique_hwm_name", - expression="hwm_column", - ) - ``` -
- HWM value will be fetched using `hwm_column` SQL query. -
- If you want to use some SQL expression as HWM value, you can use it as well: - ```python - hwm = DBReader.AutoDetectHWM( - name="some_unique_hwm_name", - expression="cast(hwm_column_orig as date)", - ) - ``` -
- #### NOTE - Some sources does not support passing expressions and can be used only with column/field - names which present in the source. -
- #### Versionchanged - Changed in version 0.10.0: Replaces deprecated `hwm_column` and `hwm_expression` attributes - - **hint** - : Hint expression used for querying the data. -
- `hint` syntax depends on the source. For example, SQL sources - accept `hint` as a string, but MongoDB sources accept `hint` as a dictionary. - ```python - # SQL database connection - hint = "index(myschema.mytable mycolumn)" -
- # MongoDB connection - hint = { - "mycolumn": 1, - } - ``` -
- #### NOTE - Some sources does not support hints. - - **df_schema** - : Spark DataFrame schema, used for proper type casting of the rows. - ```python - from pyspark.sql.types import ( - DoubleType, - IntegerType, - StringType, - StructField, - StructType, - TimestampType, - ) -
- df_schema = StructType( - [ - StructField("_id", IntegerType()), - StructField("text_string", StringType()), - StructField("hwm_int", IntegerType()), - StructField("hwm_datetime", TimestampType()), - StructField("float_value", DoubleType()), - ], - ) -
- reader = DBReader( - connection=connection, - source="fiddle.dummy", - df_schema=df_schema, - ) - ``` -
- #### NOTE - Some sources does not support passing dataframe schema. - - **options** - : Spark read options, like partitioning mode. - ```python - Postgres.ReadOptions( - partitioningMode="hash", - partitionColumn="some_column", - numPartitions=20, - fetchsize=1000, - ) - ``` -
- #### NOTE - Some sources does not support reading options. - -### Examples - -Minimal example - -```py -from onetl.db import DBReader -from onetl.connection import Postgres - -postgres = Postgres(...) - -# create reader -reader = DBReader(connection=postgres, source="fiddle.dummy") - -# read data from table "fiddle.dummy" -df = reader.run() -``` - -With custom reading options - -```py -from onetl.connection import Postgres -from onetl.db import DBReader - -postgres = Postgres(...) -options = Postgres.ReadOptions(sessionInitStatement="select 300", fetchsize="100") - -# create reader and pass some options to the underlying connection object -reader = DBReader(connection=postgres, source="fiddle.dummy", options=options) - -# read data from table "fiddle.dummy" -df = reader.run() -``` - -Full example - -```py -from onetl.db import DBReader -from onetl.connection import Postgres - -postgres = Postgres(...) -options = Postgres.ReadOptions(sessionInitStatement="select 300", fetchsize="100") - -# create reader with specific columns, rows filter -reader = DBReader( - connection=postgres, - source="default.test", - where="d_id > 100", - hint="NOWAIT", - columns=["d_id", "d_name", "d_age"], - options=options, -) - -# read data from table "fiddle.dummy" -df = reader.run() -``` - -Incremental reading - -See [Read Strategies](../strategy/index.md#strategy) for more examples - -```python -from onetl.strategy import IncrementalStrategy - -... - -reader = DBReader( - connection=postgres, - source="fiddle.dummy", - hwm=DBReader.AutoDetectHWM( # mandatory for IncrementalStrategy - name="some_unique_hwm_name", - expression="d_age", - ), -) - -# read data from table "fiddle.dummy" -# but only with new rows (`WHERE d_age > previous_hwm_value`) -with IncrementalStrategy(): - df = reader.run() -``` - - - -#### has_data() → bool - -Returns `True` if there is some data in the source, `False` otherwise. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### NOTE -This method can return different results depending on [Read Strategies](../strategy/index.md#strategy) - -#### WARNING -If [hwm](https://etl-entities.readthedocs.io/en/stable/hwm/index.html) is used, then method should be called inside [Read Strategies](../strategy/index.md#strategy) context. And vise-versa, if HWM is not used, this method should not be called within strategy. - -#### Versionadded -Added in version 0.10.0. - -* **Raises:** - RuntimeError - : Current strategy is not compatible with HWM parameter. - -### Examples - -```python -reader = DBReader(...) - -# handle situation when there is no data in the source -if reader.has_data(): - df = reader.run() -else: - # implement your handling logic here - ... -``` - - - -#### raise_if_no_data() → None - -Raises exception `NoDataError` if source does not contain any data. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### NOTE -This method can return different results depending on [Read Strategies](../strategy/index.md#strategy) - -#### WARNING -If [hwm](https://etl-entities.readthedocs.io/en/stable/hwm/index.html) is used, then method should be called inside [Read Strategies](../strategy/index.md#strategy) context. And vise-versa, if HWM is not used, this method should not be called within strategy. - -#### Versionadded -Added in version 0.10.0. - -* **Raises:** - RuntimeError - : Current strategy is not compatible with HWM parameter. - - `onetl.exception.NoDataError` - : There is no data in source. - -### Examples - -```python -reader = DBReader(...) - -# ensure that there is some data in the source before reading it using Spark -reader.raise_if_no_data() -``` - - - -#### run() → DataFrame - -Reads data from source table and saves as Spark dataframe. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### NOTE -This method can return different results depending on [Read Strategies](../strategy/index.md#strategy) - -#### WARNING -If [hwm](https://etl-entities.readthedocs.io/en/stable/hwm/index.html) is used, then method should be called inside [Read Strategies](../strategy/index.md#strategy) context. And vise-versa, if HWM is not used, this method should not be called within strategy. - -#### Versionadded -Added in version 0.1.0. - -* **Returns:** - **df** - : Spark dataframe - -### Examples - -Read data to Spark dataframe: - -```python -df = reader.run() -``` - - diff --git a/mddocs/markdown/db/db_writer.md b/mddocs/markdown/db/db_writer.md deleted file mode 100644 index 91b8a16fb..000000000 --- a/mddocs/markdown/db/db_writer.md +++ /dev/null @@ -1,100 +0,0 @@ - - -# DB Writer - -| [`DBWriter`](#onetl.db.db_writer.db_writer.DBWriter) | Class specifies schema and table where you can write your dataframe. | -|------------------------------------------------------------------|------------------------------------------------------------------------| -| [`DBWriter.run`](#onetl.db.db_writer.db_writer.DBWriter.run)(df) | Method for writing your df to specified target. | - -### *class* onetl.db.db_writer.db_writer.DBWriter(\*, connection: BaseDBConnection, table: str, options: GenericOptions | None = None) - -Class specifies schema and table where you can write your dataframe. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.1.0. - -#### Versionchanged -Changed in version 0.8.0: Moved `onetl.core.DBReader` → `onetl.db.DBReader` - -* **Parameters:** - **connection** - : Class which contains DB connection properties. See [DB Connections](../connection/db_connection/index.md#db-connections) section. - - **target** - : Table/collection/etc name to write data to. -
- If connection has schema support, you need to specify the full name of the source - including the schema, e.g. `schema.name`. -
- #### Versionchanged - Changed in version 0.7.0: Renamed `table` → `target` - - **options** - : Spark write options. Can be in form of special `WriteOptions` object or a dict. -
- For example: - `{"if_exists": "replace_entire_table", "compression": "snappy"}` - or - `Hive.WriteOptions(if_exists="replace_entire_table", compression="snappy")` -
- #### NOTE - Some sources does not support writing options. - -### Examples - -Minimal example - -```py -from onetl.connection import Postgres -from onetl.db import DBWriter - -postgres = Postgres(...) - -writer = DBWriter( - connection=postgres, - target="fiddle.dummy", -) -``` - -With custom write options - -```py -from onetl.connection import Postgres -from onetl.db import DBWriter - -postgres = Postgres(...) - -options = Postgres.WriteOptions(if_exists="replace_entire_table", batchsize=1000) - -writer = DBWriter( - connection=postgres, - target="fiddle.dummy", - options=options, -) -``` - - - -#### run(df: DataFrame) → None - -Method for writing your df to specified target. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### NOTE -Method does support only **batching** DataFrames. - -#### Versionadded -Added in version 0.1.0. - -* **Parameters:** - **df** - : Spark dataframe - -### Examples - -Write dataframe to target: - -```python -writer.run(df) -``` - - diff --git a/mddocs/markdown/db/index.md b/mddocs/markdown/db/index.md deleted file mode 100644 index 861a539ff..000000000 --- a/mddocs/markdown/db/index.md +++ /dev/null @@ -1,6 +0,0 @@ - - - DB classes - -* [DB Reader](db_reader.md) -* [DB Writer](db_writer.md) diff --git a/mddocs/markdown/file/file_downloader/file_downloader.md b/mddocs/markdown/file/file_downloader/file_downloader.md deleted file mode 100644 index a1c2f361c..000000000 --- a/mddocs/markdown/file/file_downloader/file_downloader.md +++ /dev/null @@ -1,352 +0,0 @@ - - -# File Downloader - -| [`FileDownloader`](#onetl.file.file_downloader.file_downloader.FileDownloader) | Allows you to download files from a remote source with specified file connection and parameters, and return an object with download result summary. | -|--------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------| -| [`FileDownloader.run`](#onetl.file.file_downloader.file_downloader.FileDownloader.run)([files]) | Method for downloading files from source to local directory. | -| [`FileDownloader.view_files`](#onetl.file.file_downloader.file_downloader.FileDownloader.view_files)() | Get file list in the `source_path`, after `filter`, `limit` and `hwm` applied (if any). | - -### *class* onetl.file.file_downloader.file_downloader.FileDownloader(\*, connection: ~onetl.base.base_file_connection.BaseFileConnection, local_path: ~onetl.impl.local_path.LocalPath, source_path: ~onetl.impl.remote_path.RemotePath | None = None, temp_path: ~onetl.impl.local_path.LocalPath | None = None, filter: ~typing.List[~onetl.base.base_file_filter.BaseFileFilter] = None, limit: ~typing.List[~onetl.base.base_file_limit.BaseFileLimit] = None, hwm: ~etl_entities.hwm.file.file_hwm.FileHWM | None = None, hwm_type: ~typing.Type[~etl_entities.old_hwm.file_list_hwm.FileListHWM] | str | None = None, options: ~onetl.file.file_downloader.options.FileDownloaderOptions = FileDownloaderOptions(if_exists=, delete_source=False, workers=1)) - -Allows you to download files from a remote source with specified file connection -and parameters, and return an object with download result summary. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### NOTE -FileDownloader can return different results depending on [Read Strategies](../../strategy/index.md#strategy) - -#### NOTE -This class is used to download files **only** from remote directory to the local one. - -It does NOT support direct file transfer between filesystems, like `FTP -> SFTP`. -You should use FileDownloader + [File Uploader](../file_uploader/file_uploader.md#file-uploader) to implement `FTP -> local dir -> SFTP`. - -#### Versionadded -Added in version 0.1.0. - -#### Versionchanged -Changed in version 0.8.0: Moved `onetl.core.FileDownloader` → `onetl.file.FileDownloader` - -* **Parameters:** - **connection** - : Class which contains File system connection properties. See [File Connections](../../connection/file_connection/index.md#file-connections) section. - - **local_path** - : Local path where you download files - - **source_path** - : Remote path to download files from. -
- Could be `None`, but only if you pass absolute file paths directly to - [`run`](#onetl.file.file_downloader.file_downloader.FileDownloader.run) method - - **temp_path** - : If set, this path will be used for downloading a file, and then renaming it to the target file path. - If `None` is passed, files are downloaded directly to `target_path`. -
- #### WARNING - In case of production ETL pipelines, please set a value for `temp_path` (NOT `None`). - This allows to properly handle download interruption, - without creating half-downloaded files in the target, - because unlike file download, `rename` call is atomic. -
- #### WARNING - In case of connections like SFTP or FTP, which can have multiple underlying filesystems, - please pass to `temp_path` path on the SAME filesystem as `target_path`. - Otherwise instead of `rename`, remote OS will move file between filesystems, - which is NOT atomic operation. -
- #### Versionadded - Added in version 0.5.0. - - **filters** - : Return only files/directories matching these filters. See [File Filters](../file_filters/index.md#file-filters) -
- #### Versionchanged - Changed in version 0.3.0: Replaces old `source_path_pattern: str` and `exclude_dirs: str` options. -
- #### Versionchanged - Changed in version 0.8.0: Renamed `filter` → `filters` - - **limits** - : Apply limits to the list of files/directories, and stop if one of the limits is reached. - See [File Limits](../file_limits/index.md#file-limits) -
- #### Versionadded - Added in version 0.4.0. -
- #### Versionchanged - Changed in version 0.8.0: Renamed `limit` → `limits` - - **options** - : File downloading options. See [`FileDownloader.Options`](options.md#onetl.file.file_downloader.options.FileDownloaderOptions) -
- #### Versionadded - Added in version 0.3.0. - - **hwm** - : HWM class to detect changes in incremental run. See [File HWM](https://etl-entities.readthedocs.io/en/stable/hwm/file/index.html) -
- #### WARNING - Used only in [`IncrementalStrategy`](../../strategy/incremental_strategy.md#onetl.strategy.incremental_strategy.IncrementalStrategy). -
- #### Versionadded - Added in version 0.5.0. -
- #### Versionchanged - Changed in version 0.10.0: Replaces deprecated `hwm_type` attribute - -### Examples - -Minimal example - -```py -from onetl.connection import SFTP -from onetl.file import FileDownloader - -sftp = SFTP(...) - -# create downloader -downloader = FileDownloader( - connection=sftp, - source_path="/path/to/remote/source", - local_path="/path/to/local", -) - -# download files to "/path/to/local" -downloader.run() -``` - -Full example - -```py -from onetl.connection import SFTP -from onetl.file import FileDownloader -from onetl.file.filter import Glob, ExcludeDir -from onetl.file.limit import MaxFilesCount, TotalFileSize - -sftp = SFTP(...) - -# create downloader with a bunch of options -downloader = FileDownloader( - connection=sftp, - source_path="/path/to/remote/source", - local_path="/path/to/local", - temp_path="/tmp", - filters=[ - Glob("*.txt"), - ExcludeDir("/path/to/remote/source/exclude_dir"), - ], - limits=[MaxFilesCount(100), TotalFileSize("10GiB")], - options=FileDownloader.Options(delete_source=True, if_exists="replace_file"), -) - -# download files to "/path/to/local", -# but only *.txt, -# excluding files from "/path/to/remote/source/exclude_dir" directory -# and stop before downloading 101 file -downloader.run() -``` - -Incremental download (by tracking list of file paths) - -```py -from onetl.connection import SFTP -from onetl.file import FileDownloader -from onetl.strategy import IncrementalStrategy -from etl_entities.hwm import FileListHWM - -sftp = SFTP(...) - -# create downloader -downloader = FileDownloader( - connection=sftp, - source_path="/path/to/remote/source", - local_path="/path/to/local", - hwm=FileListHWM( # mandatory for IncrementalStrategy - name="my_unique_hwm_name", - ), -) - -# download files to "/path/to/local", but only added since previous run -with IncrementalStrategy(): - downloader.run() -``` - -Incremental download (by tracking file modification time) - -```py -from onetl.connection import SFTP -from onetl.file import FileDownloader -from onetl.strategy import IncrementalStrategy -from etl_entities.hwm import FileModifiedTimeHWM - -sftp = SFTP(...) - -# create downloader -downloader = FileDownloader( - connection=sftp, - source_path="/path/to/remote/source", - local_path="/path/to/local", - hwm=FileModifiedTimeHWM( # mandatory for IncrementalStrategy - name="my_unique_hwm_name", - ), -) - -# download files to "/path/to/local", but only modified/created since previous run -with IncrementalStrategy(): - downloader.run() -``` - - - -#### run(files: Iterable[str | PathLike] | None = None) → [DownloadResult](result.md#onetl.file.file_downloader.result.DownloadResult) - -Method for downloading files from source to local directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### NOTE -This method can return different results depending on [Read Strategies](../../strategy/index.md#strategy) - -#### Versionadded -Added in version 0.1.0. - -* **Parameters:** - **files** - : File list to download. -
- If empty, download files from `source_path` to `local_path`, - applying `filter`, `limit` and `hwm` to each one (if set). -
- If not, download to `local_path` **all** input files, **ignoring** - filters, limits and HWM. -
- #### Versionadded - Added in version 0.3.0. -* **Returns:** - `DownloadResult` - : Download result object -* **Raises:** - `onetl.exception.DirectoryNotFoundError` - : `source_path` does not found - - NotADirectoryError - : `source_path` or `local_path` is not a directory - -### Examples - -Download files from `source_path` to `local_path`: - -```pycon ->>> from onetl.file import FileDownloader ->>> downloader = FileDownloader(source_path="/remote", local_path="/local", ...) ->>> download_result = downloader.run() ->>> download_result -DownloadResult( - successful=FileSet([ - LocalPath("/local/file1.txt"), - LocalPath("/local/file2.txt"), - # directory structure is preserved - LocalPath("/local/nested/path/file3.txt"), - ]), - failed=FileSet([ - FailedRemoteFile("/remote/failed.file"), - ]), - skipped=FileSet([ - RemoteFile("/remote/already.exists"), - ]), - missing=FileSet([ - RemotePath("/remote/missing.file"), - ]), -) -``` - -Download only certain files from `source_path`: - -```pycon ->>> from onetl.file import FileDownloader ->>> downloader = FileDownloader(source_path="/remote", local_path="/local", ...) ->>> # paths could be relative or absolute, but all should be in "/remote" ->>> download_result = downloader.run( -... [ -... "/remote/file1.txt", -... "/remote/nested/path/file3.txt", -... # excluding "/remote/file2.txt" -... ] -... ) ->>> download_result -DownloadResult( - successful=FileSet([ - LocalPath("/local/file1.txt"), - # directory structure is preserved - LocalPath("/local/nested/path/file3.txt"), - ]), - failed=FileSet([]), - skipped=FileSet([]), - missing=FileSet([]), -) -``` - -Download certain files from any folder: - -```pycon ->>> from onetl.file import FileDownloader ->>> downloader = FileDownloader(local_path="/local", ...) # no source_path set ->>> # only absolute paths ->>> download_result = downloader.run( -... [ -... "/remote/file1.txt", -... "/any/nested/path/file2.txt", -... ] -... ) ->>> download_result -DownloadResult( - successful=FileSet([ - LocalPath("/local/file1.txt"), - # directory structure is NOT preserved without source_path - LocalPath("/local/file2.txt"), - ]), - failed=FileSet([]), - skipped=FileSet([]), - missing=FileSet([]), -) -``` - - - -#### view_files() → FileSet[RemoteFile] - -Get file list in the `source_path`, -after `filter`, `limit` and `hwm` applied (if any). [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### NOTE -This method can return different results depending on [Read Strategies](../../strategy/index.md#strategy) - -#### Versionadded -Added in version 0.3.0. - -* **Returns:** - FileSet[RemoteFile] - : Set of files in `source_path`, which will be downloaded by [`run`](#onetl.file.file_downloader.file_downloader.FileDownloader.run) method -* **Raises:** - `onetl.exception.DirectoryNotFoundError` - : `source_path` does not found - - NotADirectoryError - : `source_path` is not a directory - -### Examples - -View files: - -```pycon ->>> from onetl.file import FileDownloader ->>> downloader = FileDownloader(source_path="/remote", ...) ->>> downloader.view_files() -FileSet([ - RemoteFile("/remote/file1.txt"), - RemoteFile("/remote/file3.txt"), - RemoteFile("/remote/nested/file3.txt"), -]) -``` - - diff --git a/mddocs/markdown/file/file_downloader/index.md b/mddocs/markdown/file/file_downloader/index.md deleted file mode 100644 index 3a7b4164b..000000000 --- a/mddocs/markdown/file/file_downloader/index.md +++ /dev/null @@ -1,9 +0,0 @@ - - -# File Downloader - -# File Downloader - -* [File Downloader](file_downloader.md) -* [File Downloader Options](options.md) -* [File Downloader Result](result.md) diff --git a/mddocs/markdown/file/file_downloader/options.md b/mddocs/markdown/file/file_downloader/options.md deleted file mode 100644 index c40baf9b8..000000000 --- a/mddocs/markdown/file/file_downloader/options.md +++ /dev/null @@ -1,67 +0,0 @@ - - -# File Downloader Options - -### *pydantic model* onetl.file.file_downloader.options.FileDownloaderOptions - -File downloading options. - -#### Versionadded -Added in version 0.3.0. - -### Examples - -```python -from onetl.file import FileDownloader - -options = FileDownloader.Options( - if_exists="replace_entire_directory", - delete_source=True, - workers=4, -) -``` - - - -#### *field* if_exists *: FileExistBehavior* *= FileExistBehavior.ERROR* *(alias 'mode')* - -How to handle existing files in the local directory. - -Possible values: -: * `error` (default) - mark file as failed - * `ignore` - mark file as skipped - * `replace_file` - replace existing file with a new one - * `replace_entire_directory` - delete local directory content before downloading files - -#### Versionchanged -Changed in version 0.9.0: Renamed `mode` → `if_exists` - - - -#### *field* delete_source *: bool* *= False* - -If `True`, remove source file after successful download. - -If download failed, file will left intact. - -#### Versionadded -Added in version 0.2.0. - -#### Versionchanged -Changed in version 0.3.0: Move `FileUploader.delete_local` to `FileUploaderOptions` - - - -#### *field* workers *: int* *= 1* - -Number of workers to create for parallel file download. - -1 (default) means files will me downloaded sequentially. -2 or more means files will be downloaded in parallel workers. - -Recommended value is `min(32, os.cpu_count() + 4)`, e.g. `5`. - -#### Versionadded -Added in version 0.8.1. - - diff --git a/mddocs/markdown/file/file_downloader/result.md b/mddocs/markdown/file/file_downloader/result.md deleted file mode 100644 index 45b94233a..000000000 --- a/mddocs/markdown/file/file_downloader/result.md +++ /dev/null @@ -1,550 +0,0 @@ - - -# File Downloader Result - -### *class* onetl.file.file_downloader.result.DownloadResult(\*, successful: FileSet[LocalPath] = None, failed: FileSet[FailedRemoteFile] = None, skipped: FileSet[RemoteFile] = None, missing: FileSet[RemotePath] = None) - -Representation of file download result. - -Container for file paths, divided into certain categories: - -* [`successful`](#onetl.file.file_downloader.result.DownloadResult.successful) -* [`failed`](#onetl.file.file_downloader.result.DownloadResult.failed) -* [`skipped`](#onetl.file.file_downloader.result.DownloadResult.skipped) -* [`missing`](#onetl.file.file_downloader.result.DownloadResult.missing) - -#### Versionadded -Added in version 0.3.0. - -### Examples - -```pycon ->>> from onetl.file import FileDownloader ->>> downloader = FileDownloader(local_path="/local", ...) ->>> download_result = downloader.run( -... [ -... "/remote/file1", -... "/remote/file2", -... "/failed/file", -... "/existing/file", -... "/missing/file", -... ] -... ) ->>> download_result -DownloadResult( - successful=FileSet([ - LocalPath("/local/file1"), - LocalPath("/local/file2"), - ]), - failed=FileSet([ - FailedLocalFile("/failed/file") - ]), - skipped=FileSet([ - RemoteFile("/existing/file") - ]), - missing=FileSet([ - RemotePath("/missing/file") - ]), -) -``` - - - -#### *property* details *: str* - -Return detailed information about files in the result object - -### Examples - -```pycon ->>> from onetl.impl import FailedRemoteFile, LocalPath, RemoteFile, RemotePathStat ->>> from onetl.exception import NotAFileError ->>> from onetl.file.file_result import FileResult ->>> file_result1 = FileResult( -... successful={ -... RemoteFile("/local/file", stats=RemotePathStat(st_size=1024)), -... RemoteFile("/local/another.file", stats=RemotePathStat(st_size=1024)), -... }, -... failed={ -... FailedRemoteFile( -... path="/remote/file1", -... stats=RemotePathStat(st_size=0), -... exception=NotAFileError("'/remote/file1' is not a file"), -... ), -... FailedRemoteFile( -... path="/remote/file2", -... stats=RemotePathStat(st_size=0), -... exception=PermissionError("'/remote/file2': [Errno 13] Permission denied"), -... ), -... }, -... skipped={LocalPath("/skipped/file1"), LocalPath("/skipped/file2")}, -... missing={LocalPath("/missing/file1"), LocalPath("/missing/file2")}, -... ) ->>> print(file_result1.details) -Total: 8 files (size='2.0 kB') - -Successful 2 files (size='2.0 kB'): - '/local/another.file' (size='1.0 kB') - '/local/file' (size='1.0 kB') - -Failed 2 files (size='0 Bytes'): - '/remote/file2' (size='0 Bytes') - PermissionError("'/remote/file2': [Errno 13] Permission denied") - '/remote/file1' (size='0 Bytes') - NotAFileError("'/remote/file1' is not a file") - -Skipped 2 files (size='0 Bytes'): - '/skipped/file1' - '/skipped/file2' - -Missing 2 files: - '/missing/file2' - '/missing/file1' -``` - -```pycon ->>> file_result2 = FileResult() ->>> print(file_result2.details) -No successful files - -No failed files - -No skipped files - -No missing files -``` - - - -#### dict(\*, include: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, exclude: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, by_alias: bool = False, skip_defaults: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False) → DictStrAny - -Generate a dictionary representation of the model, optionally specifying which fields to include or exclude. - - - -#### *field* failed *: FileSet[FailedRemoteFile]* *[Optional]* - -File paths (remote) which were not downloaded because of some failure - - - -#### *property* failed_count *: int* - -Get number of failed files - -### Examples - -```pycon ->>> from onetl.impl import RemoteFile ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... failed={RemoteFile("/some/file"), RemoteFile("/some/another.file")}, -... ) ->>> file_result.failed_count -2 -``` - - - -#### *property* failed_size *: int* - -Get size (in bytes) of failed files - -### Examples - -```pycon ->>> from onetl.impl import RemoteFile, RemotePathStat ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... failed={ -... RemoteFile("/some/file", stats=RemotePathStat(st_size=1024)), -... RemoteFile("/some/another.file"), stats=RemotePathStat(st_size=1024)), -... }, -... ) ->>> file_result.failed_size # in bytes -2048 -``` - - - -#### *property* is_empty *: bool* - -Returns `True` if there are no files in `successful`, `failed` and `skipped` attributes - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result1 = FileResult() ->>> file_result1.is_empty -True ->>> file_result2 = FileResult( -... successful={LocalPath("/local/file"), LocalPath("/local/another.file")}, -... ) ->>> file_result2.is_empty -False -``` - - - -#### json(\*, include: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, exclude: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, by_alias: bool = False, skip_defaults: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, encoder: Callable[[Any], Any] | None = None, models_as_dict: bool = True, \*\*dumps_kwargs: Any) → str - -Generate a JSON representation of the model, include and exclude arguments as per dict(). - -encoder is an optional function to supply as default to json.dumps(), other arguments as per json.dumps(). - - - -#### *field* missing *: FileSet[RemotePath]* *[Optional]* - -File paths (remote) which are not present in the remote file system - - - -#### *property* missing_count *: int* - -Get number of missing files - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... missing={LocalPath("/some/file"), LocalPath("/some/another.file")}, -... ) ->>> file_result.missing_count -2 -``` - - - -#### raise_if_contains_zero_size() → None - -Raise exception if `successful` attribute contains a file with zero size - -* **Raises:** - ZeroFileSizeError - : `successful` file set contains a file with zero size - -### Examples - -```pycon ->>> from onetl.exception import ZeroFileSizeError ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... successful={ -... LocalPath("/local/empty1.file"), -... LocalPath("/local/empty2.file"), -... LocalPath("/local/normal.file"), -... }, -... ) ->>> file_result.raise_if_contains_zero_size() -Traceback (most recent call last): - ... -onetl.exception.ZeroFileSizeError: 2 files out of 3 have zero size: - '/local/empty1.file' - '/local/empty2.file' -``` - - - -#### raise_if_empty() → None - -Raise exception if there are no files in `successful`, `failed` and `skipped` attributes - -* **Raises:** - EmptyFilesError - : `successful`, `failed` and `skipped` file sets are empty - -### Examples - -```pycon ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult() ->>> file_result.raise_if_empty() -Traceback (most recent call last): - ... -onetl.exception.EmptyFilesError: There are no files in the result -``` - - - -#### raise_if_failed() → None - -Raise exception if there are some files in `failed` attribute - -* **Raises:** - FailedFilesError - : `failed` file set is not empty - -### Examples - -```pycon ->>> from onetl.impl import FailedRemoteFile, RemotePathStat ->>> from onetl.exception import NotAFileError, FileMissingError ->>> from onetl.file.file_result import FileResult ->>> files_with_exception = [ -... FailedRemoteFile( -... path="/remote/file1", -... stats=RemotePathStat(st_size=0), -... exception=NotAFileError("'/remote/file1' is not a file"), -... ), -... FailedRemoteFile( -... path="/remote/file2", -... stats=RemotePathStat(st_size=0), -... exception=PermissionError("'/remote/file2': [Errno 13] Permission denied"), -... ), -... ] ->>> file_result = FileResult(failed=files_with_exception) ->>> file_result.raise_if_failed() -Traceback (most recent call last) -... -onetl.exception.FailedFilesError: Failed 2 files (size='0 bytes'): - '/remote/file1' (size='0 bytes') - NotAFileError("'/remote/file1' is not a file") - - '/remote/file2' (size='0 Bytes') - PermissionError("'/remote/file2': [Errno 13] Permission denied") -``` - - - -#### raise_if_missing() → None - -Raise exception if there are some files in `missing` attribute - -* **Raises:** - MissingFilesError - : `missing` file set is not empty - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... missing={ -... LocalPath("/missing/file1"), -... LocalPath("/missing/file2"), -... }, -... ) ->>> file_result.raise_if_missing() -Traceback (most recent call last): - ... -onetl.exception.MissingFilesError: Missing 2 files: - '/missing/file1' - '/missing/file2' -``` - - - -#### raise_if_skipped() → None - -Raise exception if there are some files in `skipped` attribute - -* **Raises:** - SkippedFilesError - : `skipped` file set is not empty - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... skipped={ -... LocalPath("/skipped/file1"), -... LocalPath("/skipped/file2"), -... }, -... ) ->>> file_result.raise_if_skipped() -Traceback (most recent call last): - ... -onetl.exception.SkippedFilesError: Skipped 2 files (15 kB): - '/skipped/file1' (10kB) - '/skipped/file2' (5 kB) -``` - - - -#### *field* skipped *: FileSet[RemoteFile]* *[Optional]* - -File paths (remote) which were skipped because of some reason - - - -#### *property* skipped_count *: int* - -Get number of skipped files - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... skipped={LocalPath("/some/file"), LocalPath("/some/another.file")}, -... ) ->>> file_result.skipped_count -2 -``` - - - -#### *property* skipped_size *: int* - -Get size (in bytes) of skipped files - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... skipped={LocalPath("/some/file"), LocalPath("/some/another.file")}, -... ) ->>> file_result.skipped_size # in bytes -1024 -``` - - - -#### *field* successful *: FileSet[LocalPath]* *[Optional]* - -File paths (local) which were downloaded successfully - - - -#### *property* successful_count *: int* - -Get number of successful files - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... successful={LocalPath("/some/file"), LocalPath("/some/another.file")}, -... ) ->>> file_result.successful_count -2 -``` - - - -#### *property* successful_size *: int* - -Get size (in bytes) of successful files - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... successful={LocalPath("/some/file"), LocalPath("/some/another.file")}, -... ) ->>> file_result.successful_size # in bytes -1024 -``` - - - -#### *property* summary *: str* - -Return short summary about files in the result object - -### Examples - -```pycon ->>> from onetl.impl import FailedRemoteFile, LocalPath, RemoteFile, RemotePathStat ->>> from onetl.exception import NotAFileError ->>> from onetl.file.file_result import FileResult ->>> file_result1 = FileResult( -... successful={ -... RemoteFile("/local/file", stats=RemotePathStat(st_size=1024)), -... RemoteFile("/local/another.file", stats=RemotePathStat(st_size=1024)), -... }, -... failed={ -... FailedRemoteFile( -... path="/remote/file1", -... stats=RemotePathStat(st_size=0), -... exception=NotAFileError("'/remote/file1' is not a file"), -... ), -... FailedRemoteFile( -... path="/remote/file2", -... stats=RemotePathStat(st_size=0), -... exception=PermissionError("'/remote/file2': [Errno 13] Permission denied"), -... ), -... }, -... skipped={LocalPath("/skipped/file1"), LocalPath("/skipped/file2")}, -... missing={LocalPath("/missing/file1"), LocalPath("/missing/file2")}, -... ) ->>> print(file_result1.summary) -Total: 8 files (size='2.0 kB') - -Successful: 2 files (size='2.0 kB') - -Failed: 2 files (size='0 Bytes') - -Skipped: 2 files (size='0 Bytes') - -Missing: 2 files -``` - -```pycon ->>> file_result2 = FileResult() ->>> print(file_result2.summary) -No files -``` - - - -#### *property* total_count *: int* - -Get total number of all files - -### Examples - -```pycon ->>> from onetl.impl import RemoteFile ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... successful={LocalPath("/local/file"), LocalPath("/local/another.file")}, -... failed={RemoteFile("/remote/file"), RemoteFile("/remote/another.file")}, -... skipped={LocalPath("/skipped/file")}, -... missing={LocalPath("/missing/file")}, -... ) ->>> file_result.total_count -6 -``` - - - -#### *property* total_size *: int* - -Get total size (in bytes) of all files - -### Examples - -```pycon ->>> from onetl.impl import RemoteFile, RemotePathStat, LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... successful={LocalPath("/local/file"), LocalPath("/local/another.file")}, -... failed={ -... RemoteFile("/remote/file", stats=RemotePathStat(st_size=1024)), -... RemoteFile("/remote/another.file", stats=RemotePathStat(st_size=1024)) -... }, -... skipped={LocalPath("/skipped/file")}, -... missing={LocalPath("/missing/file")}, -... ) ->>> file_result.total_size # in bytes -4096 -``` - - diff --git a/mddocs/markdown/file/file_filters/base.md b/mddocs/markdown/file/file_filters/base.md deleted file mode 100644 index c4f8a9adc..000000000 --- a/mddocs/markdown/file/file_filters/base.md +++ /dev/null @@ -1,42 +0,0 @@ - - -# Base interface - -| [`BaseFileFilter`](#onetl.base.base_file_filter.BaseFileFilter)() | Base file filter interface. | -|-----------------------------------------------------------------------------------|------------------------------------------------------------------| -| [`BaseFileFilter.match`](#onetl.base.base_file_filter.BaseFileFilter.match)(path) | Returns `True` if path is matching the filter, `False` otherwise | - -### *class* onetl.base.base_file_filter.BaseFileFilter - -Base file filter interface. - -Filters used by several onETL components, including [File Downloader](../file_downloader/file_downloader.md#file-downloader) and [File Mover](../file_mover/file_mover.md#file-mover), -to determine if a file should be handled or not. - -All filters are stateless. - -#### Versionadded -Added in version 0.8.0. - - - -#### *abstract* match(path: PathProtocol) → bool - -Returns `True` if path is matching the filter, `False` otherwise - -#### Versionadded -Added in version 0.8.0. - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> filter.match(LocalPath("/path/to/file.csv")) -True ->>> filter.match(LocalPath("/path/to/excluded.csv")) -False ->>> filter.match(LocalPath("/path/to/file.csv")) -True -``` - - diff --git a/mddocs/markdown/file/file_filters/exclude_dir.md b/mddocs/markdown/file/file_filters/exclude_dir.md deleted file mode 100644 index 9ad015e8a..000000000 --- a/mddocs/markdown/file/file_filters/exclude_dir.md +++ /dev/null @@ -1,47 +0,0 @@ - - -# ExcludeDir - -### *class* onetl.file.filter.exclude_dir.ExcludeDir(path: str | os.PathLike) - -Filter files or directories which are included in a specific directory. - -#### Versionadded -Added in version 0.8.0: Replaces deprecated `onetl.core.FileFilter` - -* **Parameters:** - **path** - : Path to directory which should be excluded. - -### Examples - -Create exclude dir filter: - -```python -from onetl.file.filter import ExcludeDir - -exclude_dir = ExcludeDir("/export/news_parse/exclude_dir") -``` - - - -#### match(path: PathProtocol) → bool - -Returns `True` if path is matching the filter, `False` otherwise - -#### Versionadded -Added in version 0.8.0. - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> filter.match(LocalPath("/path/to/file.csv")) -True ->>> filter.match(LocalPath("/path/to/excluded.csv")) -False ->>> filter.match(LocalPath("/path/to/file.csv")) -True -``` - - diff --git a/mddocs/markdown/file/file_filters/file_filter.md b/mddocs/markdown/file/file_filters/file_filter.md deleted file mode 100644 index 31946cb36..000000000 --- a/mddocs/markdown/file/file_filters/file_filter.md +++ /dev/null @@ -1,77 +0,0 @@ - - -# File Filter (legacy) - -### *class* onetl.core.file_filter.file_filter.FileFilter(\*, glob: str | None = None, regexp: Pattern | None = None, exclude_dirs: List[RemotePath] = None) - -Filter files or directories by their path. - -#### Deprecated -Deprecated since version 0.8.0: Use [`Glob`](glob.md#onetl.file.filter.glob.Glob), [`Regexp`](regexp.md#onetl.file.filter.regexp.Regexp) -or [`ExcludeDir`](exclude_dir.md#onetl.file.filter.exclude_dir.ExcludeDir) instead. - -* **Parameters:** - **glob** - : Pattern (e.g. `*.csv`) for which any **file** (only file) path should match -
- #### WARNING - Mutually exclusive with `regexp` - - **regexp** - : Regular expression (e.g. `\d+\.csv`) for which any **file** (only file) path should match. -
- If input is a string, regular expression will be compiles using `re.IGNORECASE` and `re.DOTALL` flags -
- #### WARNING - Mutually exclusive with `glob` - - **exclude_dirs** - : List of directories which should not be a part of a file or directory path - -### Examples - -Create exclude_dir filter: - -```python -from onetl.core import FileFilter - -file_filter = FileFilter(exclude_dirs=["/export/news_parse/exclude_dir"]) -``` - -Create glob filter: - -```python -from onetl.core import FileFilter - -file_filter = FileFilter(glob="*.csv") -``` - -Create regexp filter: - -```python -from onetl.core import FileFilter - -file_filter = FileFilter(regexp=r"\d+\.csv") - -# or - -import re - -file_filter = FileFilter(regexp=re.compile("\d+\.csv")) -``` - -Not allowed: - -```python -from onetl.core import FileFilter - -FileFilter() # will raise ValueError, at least one argument should be passed -``` - - - -#### match(path: PathProtocol) → bool - -False means it does not match the template by which you want to receive files - - diff --git a/mddocs/markdown/file/file_filters/file_mtime_filter.md b/mddocs/markdown/file/file_filters/file_mtime_filter.md deleted file mode 100644 index 9ec51d939..000000000 --- a/mddocs/markdown/file/file_filters/file_mtime_filter.md +++ /dev/null @@ -1,70 +0,0 @@ - - -# FileModifiedTime - -### *class* onetl.file.filter.file_mtime.FileModifiedTime(\*, since: datetime | None = None, until: datetime | None = None) - -Filter files matching a specified modification time. - -If file modification time (`.stat().st_mtime`) doesn’t match range, it will be excluded. -Doesn’t affect directories or paths without `.stat()` method. - -#### NOTE -Some filesystems return timestamps truncated to whole seconds (without millisecond part). -obj:~since and :obj\`~until\`\` values should be adjusted accordingly. - -#### Versionadded -Added in version 0.13.0. - -* **Parameters:** - **since** - : Minimal allowed file modification time. `None` means no limit. - - **until** - : Maximum allowed file modification time. `None` means no limit. - -### Examples - -Select files modified between start of the day (`00:00:00`) and hour ago: - -```python -from datetime import datetime, timedelta -from onetl.file.filter import FileModifiedTime - -hour_ago = datetime.now() - timedelta(hours=1) -day_start = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) -file_mtime = FileModifiedTime(since=day_start, until=hour_ago) -``` - -Select only files modified since hour ago: - -```python -from datetime import datetime, timedelta -from onetl.file.filter import FileModifiedTime - -hour_ago = datetime.now() - timedelta(hours=1) -file_mtime = FileModifiedTime(since=hour_ago) -``` - - - -#### match(path: PathProtocol) → bool - -Returns `True` if path is matching the filter, `False` otherwise - -#### Versionadded -Added in version 0.8.0. - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> filter.match(LocalPath("/path/to/file.csv")) -True ->>> filter.match(LocalPath("/path/to/excluded.csv")) -False ->>> filter.match(LocalPath("/path/to/file.csv")) -True -``` - - diff --git a/mddocs/markdown/file/file_filters/file_size_filter.md b/mddocs/markdown/file/file_filters/file_size_filter.md deleted file mode 100644 index 82255b7c8..000000000 --- a/mddocs/markdown/file/file_filters/file_size_filter.md +++ /dev/null @@ -1,74 +0,0 @@ - - -# FileSizeRange - -### *class* onetl.file.filter.file_size.FileSizeRange(\*, min: ByteSize | None = None, max: ByteSize | None = None) - -Filter files matching a specified size. - -If file size (`.stat().st_size`) doesn’t match the range, it will be excluded. -Doesn’t affect directories or paths without `.stat()` method. - -#### Versionadded -Added in version 0.13.0. - -#### NOTE -[SI unit prefixes](https://en.wikipedia.org/wiki/Byte#Multiple-byte_units) -means that `1KB` == `1 kilobyte` == `1000 bytes`. -If you need `1024 bytes`, use `1 KiB` == `1 kibibyte`. - -* **Parameters:** - **min** - : Minimal allowed file size. `None` means no limit. - - **max** - : Maximum allowed file size. `None` means no limit. - -### Examples - -Specify min and max file sizes: - -```python -from onetl.file.filter import FileSizeRange - -file_size = FileSizeRange(min="1KiB", max="100MiB") -``` - -Specify only min file size: - -```python -from onetl.file.filter import FileSizeRange - -file_size = FileSizeRange(min="1KiB") -``` - -Specify only max file size: - -```python -from onetl.file.filter import FileSizeRange - -file_size = FileSizeRange(max="100MiB") -``` - - - -#### match(path: PathProtocol) → bool - -Returns `True` if path is matching the filter, `False` otherwise - -#### Versionadded -Added in version 0.8.0. - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> filter.match(LocalPath("/path/to/file.csv")) -True ->>> filter.match(LocalPath("/path/to/excluded.csv")) -False ->>> filter.match(LocalPath("/path/to/file.csv")) -True -``` - - diff --git a/mddocs/markdown/file/file_filters/glob.md b/mddocs/markdown/file/file_filters/glob.md deleted file mode 100644 index f6b727b77..000000000 --- a/mddocs/markdown/file/file_filters/glob.md +++ /dev/null @@ -1,47 +0,0 @@ - - -# Glob - -### *class* onetl.file.filter.glob.Glob(pattern: str) - -Filter files or directories with path matching a glob expression. - -#### Versionadded -Added in version 0.8.0: Replaces deprecated `onetl.core.FileFilter` - -* **Parameters:** - **pattern** - : Pattern (e.g. `*.csv`) for which any **file** (only file) path should match - -### Examples - -Create glob filter: - -```python -from onetl.file.filter import Glob - -glob = Glob("*.csv") -``` - - - -#### match(path: PathProtocol) → bool - -Returns `True` if path is matching the filter, `False` otherwise - -#### Versionadded -Added in version 0.8.0. - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> filter.match(LocalPath("/path/to/file.csv")) -True ->>> filter.match(LocalPath("/path/to/excluded.csv")) -False ->>> filter.match(LocalPath("/path/to/file.csv")) -True -``` - - diff --git a/mddocs/markdown/file/file_filters/index.md b/mddocs/markdown/file/file_filters/index.md deleted file mode 100644 index 40155ae9a..000000000 --- a/mddocs/markdown/file/file_filters/index.md +++ /dev/null @@ -1,20 +0,0 @@ - - -# File Filters - -# File filters - -* [Glob](glob.md) -* [Regexp](regexp.md) -* [ExcludeDir](exclude_dir.md) -* [FileSizeRange](file_size_filter.md) -* [FileModifiedTime](file_mtime_filter.md) - -# Legacy - -* [File Filter (legacy)](file_filter.md) - -# For developers - -* [Base interface](base.md) -* [match_all_filters](match_all_filters.md) diff --git a/mddocs/markdown/file/file_filters/match_all_filters.md b/mddocs/markdown/file/file_filters/match_all_filters.md deleted file mode 100644 index 9d09eb421..000000000 --- a/mddocs/markdown/file/file_filters/match_all_filters.md +++ /dev/null @@ -1,37 +0,0 @@ - - -# match_all_filters - -### onetl.file.filter.match_all_filters.match_all_filters(path: PathProtocol, filters: Iterable[[BaseFileFilter](base.md#onetl.base.base_file_filter.BaseFileFilter)]) → bool - -Check if input path satisfies all the filters. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check. - - **filters** - : Filters to test path against. -* **Returns:** - `True` if path matches all the filters, `False` otherwise. - - If filters are empty, returns `True`. - -### Examples - -```pycon ->>> from onetl.file.filter import Glob, ExcludeDir, match_all_filters ->>> from onetl.impl import RemoteFile, RemotePathStat ->>> filters = [Glob("*.csv"), ExcludeDir("/excluded")] ->>> match_all_filters(RemoteFile("/path/to/file.csv", stats=RemotePathStat()), filters) -True ->>> match_all_filters(RemoteFile("/path/to/file.txt", stats=RemotePathStat()), filters) -False ->>> match_all_filters(RemoteFile("/excluded/path/file.csv", stats=RemotePathStat()), filters) -False -``` - - diff --git a/mddocs/markdown/file/file_filters/regexp.md b/mddocs/markdown/file/file_filters/regexp.md deleted file mode 100644 index 3e252ff74..000000000 --- a/mddocs/markdown/file/file_filters/regexp.md +++ /dev/null @@ -1,59 +0,0 @@ - - -# Regexp - -### *class* onetl.file.filter.regexp.Regexp(pattern: str) - -Filter files or directories with path matching a regular expression. - -#### Versionadded -Added in version 0.8.0: Replaces deprecated `onetl.core.FileFilter` - -* **Parameters:** - **pattern** - : Regular expression (e.g. `\d+\.csv`) for which any **file** (only file) path should match. -
- If input is a string, regular expression will be compiles using `re.IGNORECASE` and `re.DOTALL` flags. - -### Examples - -Create regexp filter from string: - -```python -from onetl.file.filter import Regexp - -regexp = Regexp(r"\d+\.csv") -``` - -Create regexp filter from `re.Pattern`: - -```python -import re - -from onetl.file.filter import Regexp - -regexp = Regexp(re.compile(r"\d+\.csv", re.IGNORECASE | re.DOTALL)) -``` - - - -#### match(path: PathProtocol) → bool - -Returns `True` if path is matching the filter, `False` otherwise - -#### Versionadded -Added in version 0.8.0. - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> filter.match(LocalPath("/path/to/file.csv")) -True ->>> filter.match(LocalPath("/path/to/excluded.csv")) -False ->>> filter.match(LocalPath("/path/to/file.csv")) -True -``` - - diff --git a/mddocs/markdown/file/file_limits/base.md b/mddocs/markdown/file/file_limits/base.md deleted file mode 100644 index 4573bb8f4..000000000 --- a/mddocs/markdown/file/file_limits/base.md +++ /dev/null @@ -1,108 +0,0 @@ - - -# Base interface - -| [`BaseFileLimit`](#onetl.base.base_file_limit.BaseFileLimit)() | Base file limit interface. | -|--------------------------------------------------------------------------------------|-------------------------------------------------| -| [`BaseFileLimit.reset`](#onetl.base.base_file_limit.BaseFileLimit.reset)() | Resets the internal limit state. | -| [`BaseFileLimit.stops_at`](#onetl.base.base_file_limit.BaseFileLimit.stops_at)(path) | Update internal state and return current state. | -| [`BaseFileLimit.is_reached`](#onetl.base.base_file_limit.BaseFileLimit.is_reached) | Check if limit is reached. | - -### *class* onetl.base.base_file_limit.BaseFileLimit - -Base file limit interface. - -Limits used by several onETL components, including [File Downloader](../file_downloader/file_downloader.md#file-downloader) and [File Mover](../file_mover/file_mover.md#file-mover), -to determine if internal loop should be stopped. - -Unlike file filters, limits have internal state which can be updated or reset. - -#### Versionadded -Added in version 0.8.0. - - - -#### *abstract property* is_reached *: bool* - -Check if limit is reached. - -#### Versionadded -Added in version 0.8.0. - -* **Returns:** - `True` if limit is reached, `False` otherwise. - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> limit.is_reached -False ->>> limit.stops_at(LocalPath("/path/to/file.csv")) -False ->>> limit.is_reached -False ->>> # after limit is reached ->>> limit.stops_at(LocalPath("/path/to/file.csv")) -True ->>> limit.is_reached -True -``` - - - -#### *abstract* reset() → Self - -Resets the internal limit state. - -#### Versionadded -Added in version 0.8.0. - -* **Returns:** - Returns a filter of the same type, but with non-reached state. - - It could be the same filter or a new one, this is an implementation detail. - -### Examples - -```pycon ->>> limit.is_reached -True ->>> new_limit = limit.reset() ->>> new_limit.is_reached -False -``` - - - -#### *abstract* stops_at(path: PathProtocol) → bool - -Update internal state and return current state. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if limit is reached, `False` otherwise. - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> # limit is not reached yet ->>> limit.stops_at(LocalPath("/path/to/file.csv")) -False ->>> # after limit is reached ->>> limit.stops_at(LocalPath("/path/to/another.csv")) -True ->>> # at this point, .stops_at() and .is_reached will always return True, ->>> # even on inputs that returned False before. ->>> # it will be in the same state until .reset() is called ->>> limit.stops_at(LocalPath("/path/to/file.csv")) -True -``` - - diff --git a/mddocs/markdown/file/file_limits/file_limit.md b/mddocs/markdown/file/file_limits/file_limit.md deleted file mode 100644 index f60fca2e8..000000000 --- a/mddocs/markdown/file/file_limits/file_limit.md +++ /dev/null @@ -1,114 +0,0 @@ - - -# File Limit (legacy) - -### *class* onetl.core.file_limit.file_limit.FileLimit(\*, count_limit: int = 100) - -Limits the number of downloaded files. - -#### Deprecated -Deprecated since version 0.8.0: Use [`MaxFilesCount`](max_files_count.md#onetl.file.limit.max_files_count.MaxFilesCount) instead. - -* **Parameters:** - **count_limit** - : Number of downloaded files at a time. - -### Examples - -Create a FileLimit object and set the amount in it: - -```python -from onetl.core import FileLimit - -limit = FileLimit(count_limit=1500) -``` - -If you create a [`onetl.file.file_downloader.file_downloader.FileDownloader`](../file_downloader/file_downloader.md#onetl.file.file_downloader.file_downloader.FileDownloader) object without -specifying the limit option, it will download with a limit of 100 files. - - - -#### *property* is_reached *: bool* - -Check if limit is reached. - -#### Versionadded -Added in version 0.8.0. - -* **Returns:** - `True` if limit is reached, `False` otherwise. - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> limit.is_reached -False ->>> limit.stops_at(LocalPath("/path/to/file.csv")) -False ->>> limit.is_reached -False ->>> # after limit is reached ->>> limit.stops_at(LocalPath("/path/to/file.csv")) -True ->>> limit.is_reached -True -``` - - - -#### reset() - -Resets the internal limit state. - -#### Versionadded -Added in version 0.8.0. - -* **Returns:** - Returns a filter of the same type, but with non-reached state. - - It could be the same filter or a new one, this is an implementation detail. - -### Examples - -```pycon ->>> limit.is_reached -True ->>> new_limit = limit.reset() ->>> new_limit.is_reached -False -``` - - - -#### stops_at(path: PathProtocol) → bool - -Update internal state and return current state. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if limit is reached, `False` otherwise. - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> # limit is not reached yet ->>> limit.stops_at(LocalPath("/path/to/file.csv")) -False ->>> # after limit is reached ->>> limit.stops_at(LocalPath("/path/to/another.csv")) -True ->>> # at this point, .stops_at() and .is_reached will always return True, ->>> # even on inputs that returned False before. ->>> # it will be in the same state until .reset() is called ->>> limit.stops_at(LocalPath("/path/to/file.csv")) -True -``` - - diff --git a/mddocs/markdown/file/file_limits/index.md b/mddocs/markdown/file/file_limits/index.md deleted file mode 100644 index be23f82da..000000000 --- a/mddocs/markdown/file/file_limits/index.md +++ /dev/null @@ -1,19 +0,0 @@ - - -# File Limits - -# File limits - -* [MaxFilesCount](max_files_count.md) -* [TotalFilesSize](total_files_size.md) - -# Legacy - -* [File Limit (legacy)](file_limit.md) - -# For developers - -* [Base interface](base.md) -* [limits_stop_at](limits_stop_at.md) -* [limits_reached](limits_reached.md) -* [reset_limits](reset_limits.md) diff --git a/mddocs/markdown/file/file_limits/limits_reached.md b/mddocs/markdown/file/file_limits/limits_reached.md deleted file mode 100644 index bf5aeabbe..000000000 --- a/mddocs/markdown/file/file_limits/limits_reached.md +++ /dev/null @@ -1,38 +0,0 @@ - - -# limits_reached - -### onetl.file.limit.limits_reached.limits_reached(limits: Iterable[[BaseFileLimit](base.md#onetl.base.base_file_limit.BaseFileLimit)]) → bool - -Check if any of limits reached. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **limits** - : Limits to test. -* **Returns:** - `True` if any of limits is reached, `False` otherwise. - - If no limits are passed, returns `False`. - -### Examples - -```pycon ->>> from onetl.file.limit import MaxFilesCount, limits_reached, limits_stop_at ->>> from onetl.impl import LocalPath ->>> limits = [MaxFilesCount(2)] ->>> limits_reached(limits) -False ->>> limits_stop_at(LocalPath("/path/to/file1.csv"), limits) -False ->>> limits_stop_at(LocalPath("/path/to/file2.csv"), limits) -False ->>> limits_stop_at(LocalPath("/path/to/file3.csv"), limits) -True ->>> limits_reached(limits) -True -``` - - diff --git a/mddocs/markdown/file/file_limits/limits_stop_at.md b/mddocs/markdown/file/file_limits/limits_stop_at.md deleted file mode 100644 index f41606838..000000000 --- a/mddocs/markdown/file/file_limits/limits_stop_at.md +++ /dev/null @@ -1,37 +0,0 @@ - - -# limits_stop_at - -### onetl.file.limit.limits_stop_at.limits_stop_at(path: PathProtocol, limits: Iterable[[BaseFileLimit](base.md#onetl.base.base_file_limit.BaseFileLimit)]) → bool - -Check if some of limits stops at given path. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check. - - **limits** - : Limits to test path against. -* **Returns:** - `True` if any of limit is reached while handling the path, `False` otherwise. - - If no limits are passed, returns `False`. - -### Examples - -```pycon ->>> from onetl.file.limit import MaxFilesCount, limits_stop_at ->>> from onetl.impl import LocalPath ->>> limits = [MaxFilesCount(2)] ->>> limits_stop_at(LocalPath("/path/to/file1.csv"), limits) -False ->>> limits_stop_at(LocalPath("/path/to/file2.csv"), limits) -False ->>> limits_stop_at(LocalPath("/path/to/file3.csv"), limits) -True -``` - - diff --git a/mddocs/markdown/file/file_limits/max_files_count.md b/mddocs/markdown/file/file_limits/max_files_count.md deleted file mode 100644 index 1b10149b1..000000000 --- a/mddocs/markdown/file/file_limits/max_files_count.md +++ /dev/null @@ -1,114 +0,0 @@ - - -# MaxFilesCount - -### *class* onetl.file.limit.max_files_count.MaxFilesCount(limit: int) - -Limits the total number of files handled by [File Downloader](../file_downloader/file_downloader.md#file-downloader) or [File Mover](../file_mover/file_mover.md#file-mover). - -All files until specified limit (including) will be downloaded/moved, but `limit+1` will not. - -This doesn’t apply to directories. - -#### Versionadded -Added in version 0.8.0: Replaces deprecated `onetl.core.FileLimit` - -* **Parameters:** - **limit** - -### Examples - -Create filter which allows to download/move up to 100 files, but stops on 101: - -```python -from onetl.file.limit import MaxFilesCount - -limit = MaxFilesCount(100) -``` - - - -#### *property* is_reached *: bool* - -Check if limit is reached. - -#### Versionadded -Added in version 0.8.0. - -* **Returns:** - `True` if limit is reached, `False` otherwise. - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> limit.is_reached -False ->>> limit.stops_at(LocalPath("/path/to/file.csv")) -False ->>> limit.is_reached -False ->>> # after limit is reached ->>> limit.stops_at(LocalPath("/path/to/file.csv")) -True ->>> limit.is_reached -True -``` - - - -#### reset() - -Resets the internal limit state. - -#### Versionadded -Added in version 0.8.0. - -* **Returns:** - Returns a filter of the same type, but with non-reached state. - - It could be the same filter or a new one, this is an implementation detail. - -### Examples - -```pycon ->>> limit.is_reached -True ->>> new_limit = limit.reset() ->>> new_limit.is_reached -False -``` - - - -#### stops_at(path: PathProtocol) → bool - -Update internal state and return current state. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if limit is reached, `False` otherwise. - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> # limit is not reached yet ->>> limit.stops_at(LocalPath("/path/to/file.csv")) -False ->>> # after limit is reached ->>> limit.stops_at(LocalPath("/path/to/another.csv")) -True ->>> # at this point, .stops_at() and .is_reached will always return True, ->>> # even on inputs that returned False before. ->>> # it will be in the same state until .reset() is called ->>> limit.stops_at(LocalPath("/path/to/file.csv")) -True -``` - - diff --git a/mddocs/markdown/file/file_limits/reset_limits.md b/mddocs/markdown/file/file_limits/reset_limits.md deleted file mode 100644 index 23a368345..000000000 --- a/mddocs/markdown/file/file_limits/reset_limits.md +++ /dev/null @@ -1,39 +0,0 @@ - - -# reset_limits - -### onetl.file.limit.reset_limits.reset_limits(limits: Iterable[[BaseFileLimit](base.md#onetl.base.base_file_limit.BaseFileLimit)]) → list[[BaseFileLimit](base.md#onetl.base.base_file_limit.BaseFileLimit)] - -Reset limits state. - -* **Parameters:** - **limits** - : Limits to reset. -* **Returns:** - List with limits, but with reset state. - - List may contain original filters with reset state, or new copies. - - This is an implementation detail of [`reset`](base.md#onetl.base.base_file_limit.BaseFileLimit.reset) method. - -### Examples - -```pycon ->>> from onetl.file.limit import MaxFilesCount, limits_reached, limits_stop_at, reset_limits ->>> from onetl.impl import LocalPath ->>> limits = [MaxFilesCount(1)] ->>> limits_reached(limits) -False ->>> # do something ->>> limits_stop_at(LocalPath("/path/to/file1.csv"), limits) -False ->>> limits_stop_at(LocalPath("/path/to/file2.csv"), limits) -True ->>> limits_reached(limits) -True ->>> new_limits = reset_limits(limits) ->>> limits_reached(new_limits) -False -``` - - diff --git a/mddocs/markdown/file/file_limits/total_files_size.md b/mddocs/markdown/file/file_limits/total_files_size.md deleted file mode 100644 index 48c56bc5b..000000000 --- a/mddocs/markdown/file/file_limits/total_files_size.md +++ /dev/null @@ -1,122 +0,0 @@ - - -# TotalFilesSize - -### *class* onetl.file.limit.total_files_size.TotalFilesSize(limit: int | str) - -Limits the total size of files handled by [File Downloader](../file_downloader/file_downloader.md#file-downloader) or [File Mover](../file_mover/file_mover.md#file-mover). - -Calculates the sum of downloaded/moved files size (`.stat().st_size`), -and checks that this sum is less or equal to specified limit. - -After limit is reached, no more files will be downloaded/moved. - -Doesn’t affect directories, paths without `.stat()` method or files with zero size. - -#### Versionadded -Added in version 0.13.0. - -#### NOTE -[SI unit prefixes](https://en.wikipedia.org/wiki/Byte#Multiple-byte_units) -means that `1KB` == `1 kilobyte` == `1000 bytes`. -If you need `1024 bytes`, use `1 KiB` == `1 kibibyte`. - -* **Parameters:** - **limit** - -### Examples - -Create filter which allows to download/move files with total size up to 1GiB, but not higher: - -```python -from onetl.file.limit import MaxFilesCount - -limit = TotalFilesSize("1GiB") -``` - - - -#### *property* is_reached *: bool* - -Check if limit is reached. - -#### Versionadded -Added in version 0.8.0. - -* **Returns:** - `True` if limit is reached, `False` otherwise. - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> limit.is_reached -False ->>> limit.stops_at(LocalPath("/path/to/file.csv")) -False ->>> limit.is_reached -False ->>> # after limit is reached ->>> limit.stops_at(LocalPath("/path/to/file.csv")) -True ->>> limit.is_reached -True -``` - - - -#### reset() - -Resets the internal limit state. - -#### Versionadded -Added in version 0.8.0. - -* **Returns:** - Returns a filter of the same type, but with non-reached state. - - It could be the same filter or a new one, this is an implementation detail. - -### Examples - -```pycon ->>> limit.is_reached -True ->>> new_limit = limit.reset() ->>> new_limit.is_reached -False -``` - - - -#### stops_at(path: PathProtocol) → bool - -Update internal state and return current state. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **path** - : Path to check -* **Returns:** - `True` if limit is reached, `False` otherwise. - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> # limit is not reached yet ->>> limit.stops_at(LocalPath("/path/to/file.csv")) -False ->>> # after limit is reached ->>> limit.stops_at(LocalPath("/path/to/another.csv")) -True ->>> # at this point, .stops_at() and .is_reached will always return True, ->>> # even on inputs that returned False before. ->>> # it will be in the same state until .reset() is called ->>> limit.stops_at(LocalPath("/path/to/file.csv")) -True -``` - - diff --git a/mddocs/markdown/file/file_mover/file_mover.md b/mddocs/markdown/file/file_mover/file_mover.md deleted file mode 100644 index 1f7b7ce0b..000000000 --- a/mddocs/markdown/file/file_mover/file_mover.md +++ /dev/null @@ -1,243 +0,0 @@ - - -# File Mover - -| [`FileMover`](#onetl.file.file_mover.file_mover.FileMover) | Allows you to move files between different directories in a filesystem, and return an object with move result summary. | -|------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------| -| [`FileMover.run`](#onetl.file.file_mover.file_mover.FileMover.run)([files]) | Method for moving files from source to target directory. | -| [`FileMover.view_files`](#onetl.file.file_mover.file_mover.FileMover.view_files)() | Get file list in the `source_path`, after `filter` and `limit` applied (if any). | - -### *class* onetl.file.file_mover.file_mover.FileMover(\*, connection: ~onetl.base.base_file_connection.BaseFileConnection, target_path: ~onetl.impl.remote_path.RemotePath, source_path: ~onetl.impl.remote_path.RemotePath | None = None, filters: ~typing.List[~onetl.base.base_file_filter.BaseFileFilter] = None, limits: ~typing.List[~onetl.base.base_file_limit.BaseFileLimit] = None, options: ~onetl.file.file_mover.options.FileMoverOptions = FileMoverOptions(if_exists=, workers=1)) - -Allows you to move files between different directories in a filesystem, -and return an object with move result summary. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### NOTE -This class is used to move files **only** within the same connection, - -It does NOT support direct file transfer between filesystems, like `FTP -> SFTP`. -You should use [File Downloader](../file_downloader/file_downloader.md#file-downloader) + [File Uploader](../file_uploader/file_uploader.md#file-uploader) to implement `FTP -> local dir -> SFTP`. - -#### WARNING -This class does **not** support read strategies. - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **connection** - : Class which contains File system connection properties. See [File Connections](../../connection/file_connection/index.md#file-connections) section. - - **target_path** - : Remote path to move files to - - **source_path** - : Remote path to move files from. -
- Could be `None`, but only if you pass absolute file paths directly to - [`run`](#onetl.file.file_mover.file_mover.FileMover.run) method - - **filters** - : Return only files/directories matching these filters. See [File Filters](../file_filters/index.md#file-filters) - - **limits** - : Apply limits to the list of files/directories, and stop if one of the limits is reached. - See [File Limits](../file_limits/index.md#file-limits) - - **options** - : File moving options. See [`FileMover.Options`](options.md#onetl.file.file_mover.options.FileMoverOptions) - -### Examples - -Minimal example - -```py -from onetl.connection import SFTP -from onetl.file import FileMover - -sftp = SFTP(...) - -# create mover -mover = FileMover( - connection=sftp, - source_path="/path/to/source/dir", - target_path="/path/to/target/dir", -) - -# move files from "/path/to/source/dir" to "/path/to/target/dir" -mover.run() -``` - -Full example - -```py -from onetl.connection import SFTP -from onetl.file import FileMover -from onetl.file.filter import Glob, ExcludeDir -from onetl.file.limit import MaxFilesCount, TotalFilesSize - -sftp = SFTP(...) - -# create mover with a bunch of options -mover = FileMover( - connection=sftp, - source_path="/path/to/source/dir", - target_path="/path/to/target/dir", - filters=[ - Glob("*.txt"), - ExcludeDir("/path/to/source/dir/exclude"), - ], - limits=[MaxFilesCount(100), TotalFileSize("10GiB")], - options=FileMover.Options(if_exists="replace_file"), -) - -# move files from "/path/to/source/dir" to "/path/to/target/dir", -# but only *.txt files -# excluding files from "/path/to/source/dir/exclude" directory -# and stop before downloading 101 file -mover.run() -``` - - - -#### run(files: Iterable[str | PathLike] | None = None) → [MoveResult](result.md#onetl.file.file_mover.result.MoveResult) - -Method for moving files from source to target directory. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Parameters:** - **files** - : File list to move. -
- If empty, move files from `source_path` to `target_path`, - applying `filter` and `limit` to each one (if set). -
- If not, move to `target_path` **all** input files, **without** - any filtering and limiting. -* **Returns:** - `MoveResult` - : Move result object -* **Raises:** - `onetl.exception.DirectoryNotFoundError` - : `source_path` does not found - - NotADirectoryError - : `source_path` or `target_path` is not a directory - -### Examples - -Move files from `source_path`: - -```pycon ->>> from onetl.file import FileMover ->>> mover = FileMover(source_path="/source", target_path="/target", ...) ->>> move_result = mover.run() ->>> move_result -MoveResult( - successful=FileSet([ - RemoteFile("/target/file1.txt"), - RemoteFile("/target/file2.txt"), - # directory structure is preserved - RemoteFile("/target/nested/path/file3.txt"), - ]), - failed=FileSet([ - FailedRemoteFile("/source/failed.file"), - ]), - skipped=FileSet([ - RemoteFile("/source/already.exists"), - ]), - missing=FileSet([ - RemotePath("/source/missing.file"), - ]), -) -``` - -Move only certain files from `source_path`: - -```pycon ->>> from onetl.file import FileMover ->>> mover = FileMover(source_path="/source", target_path="/target", ...) ->>> # paths could be relative or absolute, but all should be in "/source" ->>> move_result = mover.run( -... [ -... "/source/file1.txt", -... "/source/nested/path/file3.txt", -... # excluding "/source/file2.txt" -... ] -... ) ->>> move_result -MoveResult( - successful=FileSet([ - RemoteFile("/target/file1.txt"), - # directory structure is preserved - RemoteFile("/target/nested/path/file3.txt"), - ]), - failed=FileSet([]), - skipped=FileSet([]), - missing=FileSet([]), -) -``` - -Move certain files from any folder: - -```pycon ->>> from onetl.file import FileMover ->>> mover = FileMover(target_path="/target", ...) # no source_path set ->>> # only absolute paths ->>> move_result = mover.run( -... [ -... "/remote/file1.txt", -... "/any/nested/path/file3.txt", -... ] -... ) ->>> move_result -MoveResult( - successful=FileSet([ - RemoteFile("/target/file1.txt"), - # directory structure is NOT preserved without source_path - RemoteFile("/target/file3.txt"), - ]), - failed=FileSet([]), - skipped=FileSet([]), - missing=FileSet([]), -) -``` - - - -#### view_files() → FileSet[RemoteFile] - -Get file list in the `source_path`, -after `filter` and `limit` applied (if any). [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.8.0. - -* **Returns:** - FileSet[RemoteFile] - : Set of files in `source_path`, which will be moved by [`run`](#onetl.file.file_mover.file_mover.FileMover.run) method -* **Raises:** - `onetl.exception.DirectoryNotFoundError` - : `source_path` does not found - - NotADirectoryError - : `source_path` is not a directory - -### Examples - -View files: - -```pycon ->>> from onetl.file import FileMover ->>> mover = FileMover(source_path="/remote", ...) ->>> mover.view_files() -FileSet([ - RemoteFile("/remote/file1.txt"), - RemoteFile("/remote/file2.txt"), - RemoteFile("/remote/nested/path/file3.txt"), -]) -``` - - diff --git a/mddocs/markdown/file/file_mover/index.md b/mddocs/markdown/file/file_mover/index.md deleted file mode 100644 index c8b55c7d0..000000000 --- a/mddocs/markdown/file/file_mover/index.md +++ /dev/null @@ -1,9 +0,0 @@ - - -# File Mover - -# File Mover - -* [File Mover](file_mover.md) -* [File Mover Options](options.md) -* [File Mover Result](result.md) diff --git a/mddocs/markdown/file/file_mover/options.md b/mddocs/markdown/file/file_mover/options.md deleted file mode 100644 index 66e34176a..000000000 --- a/mddocs/markdown/file/file_mover/options.md +++ /dev/null @@ -1,55 +0,0 @@ - - -# File Mover Options - -### *pydantic model* onetl.file.file_mover.options.FileMoverOptions - -File moving options. - -#### Versionadded -Added in version 0.8.0. - -### Examples - -```python -from onetl.file import FileMover - -options = FileMover.Options( - if_exists="replace_entire_directory", - workers=4, -) -``` - - - -#### *field* if_exists *: FileExistBehavior* *= FileExistBehavior.ERROR* *(alias 'mode')* - -How to handle existing files in the local directory. - -Possible values: -: * `error` (default) - mark file as failed - * `ignore` - mark file as skipped - * `replace_file` - replace existing file with a new one - * `replace_entire_directory` - delete directory content before moving files - -#### Versionadded -Added in version 0.8.0. - -#### Versionchanged -Changed in version 0.9.0: Renamed `mode` → `if_exists` - - - -#### *field* workers *: int* *= 1* - -Number of workers to create for parallel file moving. - -1 (default) means files will me moved sequentially. -2 or more means files will be moved in parallel workers. - -Recommended value is `min(32, os.cpu_count() + 4)`, e.g. `5`. - -#### Versionadded -Added in version 0.8.1. - - diff --git a/mddocs/markdown/file/file_mover/result.md b/mddocs/markdown/file/file_mover/result.md deleted file mode 100644 index 815388b8f..000000000 --- a/mddocs/markdown/file/file_mover/result.md +++ /dev/null @@ -1,550 +0,0 @@ - - -# File Mover Result - -### *class* onetl.file.file_mover.result.MoveResult(\*, successful: FileSet[RemoteFile] = None, failed: FileSet[FailedRemoteFile] = None, skipped: FileSet[RemoteFile] = None, missing: FileSet[RemotePath] = None) - -Representation of file move result. - -Container for file paths, divided into certain categories: - -* [`successful`](#onetl.file.file_mover.result.MoveResult.successful) -* [`failed`](#onetl.file.file_mover.result.MoveResult.failed) -* [`skipped`](#onetl.file.file_mover.result.MoveResult.skipped) -* [`missing`](#onetl.file.file_mover.result.MoveResult.missing) - -#### Versionadded -Added in version 0.8.0. - -### Examples - -```pycon ->>> from onetl.file import FileMover ->>> mover = FileMover(local_path="/local", ...) ->>> move_result = mover.run( -... [ -... "/source/file1", -... "/source/file2", -... "/failed/file", -... "/existing/file", -... "/missing/file", -... ] -... ) ->>> move_result -MoveResult( - successful=FileSet([ - RemoteFile("/target/file1"), - RemoteFile("/target/file2"), - ]), - failed=FileSet([ - FailedLocalFile("/failed/file") - ]), - skipped=FileSet([ - RemoteFile("/existing/file") - ]), - missing=FileSet([ - RemotePath("/missing/file") - ]), -) -``` - - - -#### *property* details *: str* - -Return detailed information about files in the result object - -### Examples - -```pycon ->>> from onetl.impl import FailedRemoteFile, LocalPath, RemoteFile, RemotePathStat ->>> from onetl.exception import NotAFileError ->>> from onetl.file.file_result import FileResult ->>> file_result1 = FileResult( -... successful={ -... RemoteFile("/local/file", stats=RemotePathStat(st_size=1024)), -... RemoteFile("/local/another.file", stats=RemotePathStat(st_size=1024)), -... }, -... failed={ -... FailedRemoteFile( -... path="/remote/file1", -... stats=RemotePathStat(st_size=0), -... exception=NotAFileError("'/remote/file1' is not a file"), -... ), -... FailedRemoteFile( -... path="/remote/file2", -... stats=RemotePathStat(st_size=0), -... exception=PermissionError("'/remote/file2': [Errno 13] Permission denied"), -... ), -... }, -... skipped={LocalPath("/skipped/file1"), LocalPath("/skipped/file2")}, -... missing={LocalPath("/missing/file1"), LocalPath("/missing/file2")}, -... ) ->>> print(file_result1.details) -Total: 8 files (size='2.0 kB') - -Successful 2 files (size='2.0 kB'): - '/local/another.file' (size='1.0 kB') - '/local/file' (size='1.0 kB') - -Failed 2 files (size='0 Bytes'): - '/remote/file2' (size='0 Bytes') - PermissionError("'/remote/file2': [Errno 13] Permission denied") - '/remote/file1' (size='0 Bytes') - NotAFileError("'/remote/file1' is not a file") - -Skipped 2 files (size='0 Bytes'): - '/skipped/file1' - '/skipped/file2' - -Missing 2 files: - '/missing/file2' - '/missing/file1' -``` - -```pycon ->>> file_result2 = FileResult() ->>> print(file_result2.details) -No successful files - -No failed files - -No skipped files - -No missing files -``` - - - -#### dict(\*, include: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, exclude: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, by_alias: bool = False, skip_defaults: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False) → DictStrAny - -Generate a dictionary representation of the model, optionally specifying which fields to include or exclude. - - - -#### *field* failed *: FileSet[FailedRemoteFile]* *[Optional]* - -File paths (remote) which were not moved because of some failure - - - -#### *property* failed_count *: int* - -Get number of failed files - -### Examples - -```pycon ->>> from onetl.impl import RemoteFile ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... failed={RemoteFile("/some/file"), RemoteFile("/some/another.file")}, -... ) ->>> file_result.failed_count -2 -``` - - - -#### *property* failed_size *: int* - -Get size (in bytes) of failed files - -### Examples - -```pycon ->>> from onetl.impl import RemoteFile, RemotePathStat ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... failed={ -... RemoteFile("/some/file", stats=RemotePathStat(st_size=1024)), -... RemoteFile("/some/another.file"), stats=RemotePathStat(st_size=1024)), -... }, -... ) ->>> file_result.failed_size # in bytes -2048 -``` - - - -#### *property* is_empty *: bool* - -Returns `True` if there are no files in `successful`, `failed` and `skipped` attributes - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result1 = FileResult() ->>> file_result1.is_empty -True ->>> file_result2 = FileResult( -... successful={LocalPath("/local/file"), LocalPath("/local/another.file")}, -... ) ->>> file_result2.is_empty -False -``` - - - -#### json(\*, include: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, exclude: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, by_alias: bool = False, skip_defaults: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, encoder: Callable[[Any], Any] | None = None, models_as_dict: bool = True, \*\*dumps_kwargs: Any) → str - -Generate a JSON representation of the model, include and exclude arguments as per dict(). - -encoder is an optional function to supply as default to json.dumps(), other arguments as per json.dumps(). - - - -#### *field* missing *: FileSet[RemotePath]* *[Optional]* - -File paths (remote) which are not present in the remote file system - - - -#### *property* missing_count *: int* - -Get number of missing files - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... missing={LocalPath("/some/file"), LocalPath("/some/another.file")}, -... ) ->>> file_result.missing_count -2 -``` - - - -#### raise_if_contains_zero_size() → None - -Raise exception if `successful` attribute contains a file with zero size - -* **Raises:** - ZeroFileSizeError - : `successful` file set contains a file with zero size - -### Examples - -```pycon ->>> from onetl.exception import ZeroFileSizeError ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... successful={ -... LocalPath("/local/empty1.file"), -... LocalPath("/local/empty2.file"), -... LocalPath("/local/normal.file"), -... }, -... ) ->>> file_result.raise_if_contains_zero_size() -Traceback (most recent call last): - ... -onetl.exception.ZeroFileSizeError: 2 files out of 3 have zero size: - '/local/empty1.file' - '/local/empty2.file' -``` - - - -#### raise_if_empty() → None - -Raise exception if there are no files in `successful`, `failed` and `skipped` attributes - -* **Raises:** - EmptyFilesError - : `successful`, `failed` and `skipped` file sets are empty - -### Examples - -```pycon ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult() ->>> file_result.raise_if_empty() -Traceback (most recent call last): - ... -onetl.exception.EmptyFilesError: There are no files in the result -``` - - - -#### raise_if_failed() → None - -Raise exception if there are some files in `failed` attribute - -* **Raises:** - FailedFilesError - : `failed` file set is not empty - -### Examples - -```pycon ->>> from onetl.impl import FailedRemoteFile, RemotePathStat ->>> from onetl.exception import NotAFileError, FileMissingError ->>> from onetl.file.file_result import FileResult ->>> files_with_exception = [ -... FailedRemoteFile( -... path="/remote/file1", -... stats=RemotePathStat(st_size=0), -... exception=NotAFileError("'/remote/file1' is not a file"), -... ), -... FailedRemoteFile( -... path="/remote/file2", -... stats=RemotePathStat(st_size=0), -... exception=PermissionError("'/remote/file2': [Errno 13] Permission denied"), -... ), -... ] ->>> file_result = FileResult(failed=files_with_exception) ->>> file_result.raise_if_failed() -Traceback (most recent call last) -... -onetl.exception.FailedFilesError: Failed 2 files (size='0 bytes'): - '/remote/file1' (size='0 bytes') - NotAFileError("'/remote/file1' is not a file") - - '/remote/file2' (size='0 Bytes') - PermissionError("'/remote/file2': [Errno 13] Permission denied") -``` - - - -#### raise_if_missing() → None - -Raise exception if there are some files in `missing` attribute - -* **Raises:** - MissingFilesError - : `missing` file set is not empty - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... missing={ -... LocalPath("/missing/file1"), -... LocalPath("/missing/file2"), -... }, -... ) ->>> file_result.raise_if_missing() -Traceback (most recent call last): - ... -onetl.exception.MissingFilesError: Missing 2 files: - '/missing/file1' - '/missing/file2' -``` - - - -#### raise_if_skipped() → None - -Raise exception if there are some files in `skipped` attribute - -* **Raises:** - SkippedFilesError - : `skipped` file set is not empty - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... skipped={ -... LocalPath("/skipped/file1"), -... LocalPath("/skipped/file2"), -... }, -... ) ->>> file_result.raise_if_skipped() -Traceback (most recent call last): - ... -onetl.exception.SkippedFilesError: Skipped 2 files (15 kB): - '/skipped/file1' (10kB) - '/skipped/file2' (5 kB) -``` - - - -#### *field* skipped *: FileSet[RemoteFile]* *[Optional]* - -File paths (remote) which were skipped because of some reason - - - -#### *property* skipped_count *: int* - -Get number of skipped files - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... skipped={LocalPath("/some/file"), LocalPath("/some/another.file")}, -... ) ->>> file_result.skipped_count -2 -``` - - - -#### *property* skipped_size *: int* - -Get size (in bytes) of skipped files - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... skipped={LocalPath("/some/file"), LocalPath("/some/another.file")}, -... ) ->>> file_result.skipped_size # in bytes -1024 -``` - - - -#### *field* successful *: FileSet[RemoteFile]* *[Optional]* - -File paths (local) which were moved successfully - - - -#### *property* successful_count *: int* - -Get number of successful files - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... successful={LocalPath("/some/file"), LocalPath("/some/another.file")}, -... ) ->>> file_result.successful_count -2 -``` - - - -#### *property* successful_size *: int* - -Get size (in bytes) of successful files - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... successful={LocalPath("/some/file"), LocalPath("/some/another.file")}, -... ) ->>> file_result.successful_size # in bytes -1024 -``` - - - -#### *property* summary *: str* - -Return short summary about files in the result object - -### Examples - -```pycon ->>> from onetl.impl import FailedRemoteFile, LocalPath, RemoteFile, RemotePathStat ->>> from onetl.exception import NotAFileError ->>> from onetl.file.file_result import FileResult ->>> file_result1 = FileResult( -... successful={ -... RemoteFile("/local/file", stats=RemotePathStat(st_size=1024)), -... RemoteFile("/local/another.file", stats=RemotePathStat(st_size=1024)), -... }, -... failed={ -... FailedRemoteFile( -... path="/remote/file1", -... stats=RemotePathStat(st_size=0), -... exception=NotAFileError("'/remote/file1' is not a file"), -... ), -... FailedRemoteFile( -... path="/remote/file2", -... stats=RemotePathStat(st_size=0), -... exception=PermissionError("'/remote/file2': [Errno 13] Permission denied"), -... ), -... }, -... skipped={LocalPath("/skipped/file1"), LocalPath("/skipped/file2")}, -... missing={LocalPath("/missing/file1"), LocalPath("/missing/file2")}, -... ) ->>> print(file_result1.summary) -Total: 8 files (size='2.0 kB') - -Successful: 2 files (size='2.0 kB') - -Failed: 2 files (size='0 Bytes') - -Skipped: 2 files (size='0 Bytes') - -Missing: 2 files -``` - -```pycon ->>> file_result2 = FileResult() ->>> print(file_result2.summary) -No files -``` - - - -#### *property* total_count *: int* - -Get total number of all files - -### Examples - -```pycon ->>> from onetl.impl import RemoteFile ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... successful={LocalPath("/local/file"), LocalPath("/local/another.file")}, -... failed={RemoteFile("/remote/file"), RemoteFile("/remote/another.file")}, -... skipped={LocalPath("/skipped/file")}, -... missing={LocalPath("/missing/file")}, -... ) ->>> file_result.total_count -6 -``` - - - -#### *property* total_size *: int* - -Get total size (in bytes) of all files - -### Examples - -```pycon ->>> from onetl.impl import RemoteFile, RemotePathStat, LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... successful={LocalPath("/local/file"), LocalPath("/local/another.file")}, -... failed={ -... RemoteFile("/remote/file", stats=RemotePathStat(st_size=1024)), -... RemoteFile("/remote/another.file", stats=RemotePathStat(st_size=1024)) -... }, -... skipped={LocalPath("/skipped/file")}, -... missing={LocalPath("/missing/file")}, -... ) ->>> file_result.total_size # in bytes -4096 -``` - - diff --git a/mddocs/markdown/file/file_uploader/file_uploader.md b/mddocs/markdown/file/file_uploader/file_uploader.md deleted file mode 100644 index 98ebe3165..000000000 --- a/mddocs/markdown/file/file_uploader/file_uploader.md +++ /dev/null @@ -1,241 +0,0 @@ - - -# File Uploader - -| [`FileUploader`](#onetl.file.file_uploader.file_uploader.FileUploader) | Allows you to upload files to a remote source with specified file connection and parameters, and return an object with upload result summary. | -|------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| -| [`FileUploader.run`](#onetl.file.file_uploader.file_uploader.FileUploader.run)([files]) | Method for uploading files to remote host. | -| [`FileUploader.view_files`](#onetl.file.file_uploader.file_uploader.FileUploader.view_files)() | Get file list in the `local_path`. | - -### *class* onetl.file.file_uploader.file_uploader.FileUploader(\*, connection: ~onetl.base.base_file_connection.BaseFileConnection, target_path: ~onetl.impl.remote_path.RemotePath, local_path: ~onetl.impl.local_path.LocalPath | None = None, temp_path: ~onetl.impl.remote_path.RemotePath | None = None, options: ~onetl.file.file_uploader.options.FileUploaderOptions = FileUploaderOptions(if_exists=, delete_local=False, workers=1)) - -Allows you to upload files to a remote source with specified file connection -and parameters, and return an object with upload result summary. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### NOTE -This class is used to upload files **only** from local directory to the remote one. - -It does NOT support direct file transfer between filesystems, like `FTP -> SFTP`. -You should use [File Downloader](../file_downloader/file_downloader.md#file-downloader) + FileUploader to implement `FTP -> local dir -> SFTP`. - -#### WARNING -This class does **not** support read strategies. - -#### Versionadded -Added in version 0.1.0. - -#### Versionchanged -Changed in version 0.8.0: Moved `onetl.core.FileDownloader` → `onetl.file.FileDownloader` - -* **Parameters:** - **connection** - : Class which contains File system connection properties. See [File Connections](../../connection/file_connection/index.md#file-connections) section. - - **target_path** - : Remote path where want you upload files to - - **local_path** - : The local directory from which the data is loaded. -
- Could be `None`, but only if you pass absolute file paths directly to - [`run`](#onetl.file.file_uploader.file_uploader.FileUploader.run) method -
- #### Versionadded - Added in version 0.3.0. - - **temp_path** - : If set, this path will be used for uploading a file, and then renaming it to the target file path. - If `None` (default since v0.5.0) is passed, files are uploaded directly to `target_path`. -
- #### WARNING - In case of production ETL pipelines, please set a value for `temp_path` (NOT `None`). - This allows to properly handle upload interruption, - without creating half-uploaded files in the target, - because unlike file upload, `rename` call is atomic. -
- #### WARNING - In case of connections like SFTP or FTP, which can have multiple underlying filesystems, - please pass `temp_path` path on the SAME filesystem as `target_path`. - Otherwise instead of `rename`, remote OS will move file between filesystems, - which is NOT atomic operation. -
- #### Versionchanged - Changed in version 0.5.0: Default changed from `/tmp` to `None` - - **options** - : File upload options. See [`FileUploader.Options`](options.md#onetl.file.file_uploader.options.FileUploaderOptions) - -### Examples - -Minimal example - -```py -from onetl.connection import HDFS -from onetl.file import FileUploader - -hdfs = HDFS(...) - -uploader = FileUploader( - connection=hdfs, - target_path="/path/to/remote/source", -) -``` - -Full example - -```py -from onetl.connection import HDFS -from onetl.file import FileUploader - -hdfs = HDFS(...) - -uploader = FileUploader( - connection=hdfs, - target_path="/path/to/remote/source", - temp_path="/user/onetl", - local_path="/some/local/directory", - options=FileUploader.Options(delete_local=True, if_exists="overwrite"), -) -``` - - - -#### run(files: Iterable[str | PathLike] | None = None) → [UploadResult](result.md#onetl.file.file_uploader.result.UploadResult) - -Method for uploading files to remote host. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.1.0. - -* **Parameters:** - **files** - : File list to upload. -
- If empty, upload files from `local_path`. -* **Returns:** - `UploadResult` - : Upload result object -* **Raises:** - `onetl.exception.DirectoryNotFoundError` - : `local_path` does not found - - NotADirectoryError - : `local_path` is not a directory - - ValueError - : File in `files` argument does not match `local_path` - -### Examples - -Upload files from `local_path` to `target_path`: - -```pycon ->>> from onetl.file import FileUploader ->>> uploader = FileUploader(local_path="/local", target_path="/remote", ...) ->>> upload_result = uploader.run() ->>> upload_result -UploadResult( - successful=FileSet([ - RemoteFile("/remote/file1"), - RemoteFile("/remote/file2"), - # directory structure is preserved - RemoteFile("/remote/nested/path/file3") - ]), - failed=FileSet([ - FailedLocalFile("/local/failed.file"), - ]), - skipped=FileSet([ - LocalPath("/local/already.exists"), - ]), - missing=FileSet([ - LocalPath("/local/missing.file"), - ]), -) -``` - -Upload only certain files from `local_path`: - -```pycon ->>> from onetl.file import FileUploader ->>> uploader = FileUploader(local_path="/local", target_path="/remote", ...) ->>> # paths could be relative or absolute, but all should be in "/local" ->>> upload_result = uploader.run( -... [ -... "/local/file1", -... "/local/nested/path/file3", -... # excluding "/local/file2", -... ] -... ) ->>> upload_result -UploadResult( - successful=FileSet([ - RemoteFile("/remote/file1"), - # directory structure is preserved - RemoteFile("/remote/nested/path/file3"), - ]), - failed=FileSet([]), - skipped=FileSet([]), - missing=FileSet([]), -) -``` - -Upload only certain files from any folder: - -```pycon ->>> from onetl.file import FileUploader ->>> uploader = FileUploader(target_path="/remote", ...) # no local_path set ->>> # only absolute paths ->>> upload_result = uploader.run( -... [ -... "/local/file1.txt", -... "/any/nested/path/file3.txt", -... ] -... ) ->>> upload_result -UploadResult( - successful=FileSet([ - RemoteFile("/remote/file1.txt"), - # directory structure is NOT preserved without local_path - RemoteFile("/remote/file3.txt"), - ]), - failed=FileSet([]), - skipped=FileSet([]), - missing=FileSet([]), -) -``` - - - -#### view_files() → FileSet[LocalPath] - -Get file list in the `local_path`. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.3.0. - -* **Returns:** - FileSet[LocalPath] - : Set of files in `local_path` -* **Raises:** - `onetl.exception.DirectoryNotFoundError` - : `local_path` does not found - - NotADirectoryError - : `local_path` is not a directory - -### Examples - -View files: - -```pycon ->>> from onetl.file import FileUploader ->>> uploader = FileUploader(local_path="/local", ...) ->>> uploader.view_files() -FileSet([ - LocalPath("/local/file1.txt"), - LocalPath("/local/file3.txt"), - LocalPath("/local/nested/path/file3.txt"), -]) -``` - - diff --git a/mddocs/markdown/file/file_uploader/index.md b/mddocs/markdown/file/file_uploader/index.md deleted file mode 100644 index e11581a9c..000000000 --- a/mddocs/markdown/file/file_uploader/index.md +++ /dev/null @@ -1,9 +0,0 @@ - - -# File Uploader - -# File Uploader - -* [File Uploader](file_uploader.md) -* [File Uploader Options](options.md) -* [File Uploader Result](result.md) diff --git a/mddocs/markdown/file/file_uploader/options.md b/mddocs/markdown/file/file_uploader/options.md deleted file mode 100644 index a32387501..000000000 --- a/mddocs/markdown/file/file_uploader/options.md +++ /dev/null @@ -1,67 +0,0 @@ - - -# File Uploader Options - -### *pydantic model* onetl.file.file_uploader.options.FileUploaderOptions - -File uploading options. - -#### Versionadded -Added in version 0.3.0. - -### Examples - -```python -from onetl.file import FileUploader - -options = FileUploader.Options( - if_exists="replace_entire_directory", - delete_local=True, - workers=4, -) -``` - - - -#### *field* if_exists *: FileExistBehavior* *= FileExistBehavior.ERROR* *(alias 'mode')* - -How to handle existing files in the target directory. - -Possible values: -: * `error` (default) - mark file as failed - * `ignore` - mark file as skipped - * `replace_file` - replace existing file with a new one - * `replace_entire_directory` - delete local directory content before downloading files - -#### Versionchanged -Changed in version 0.9.0: Renamed `mode` → `if_exists` - - - -#### *field* delete_local *: bool* *= False* - -If `True`, remove local file after successful download. - -If download failed, file will left intact. - -#### Versionadded -Added in version 0.2.0. - -#### Versionchanged -Changed in version 0.3.0: Move `FileUploader.delete_local` to `FileUploaderOptions` - - - -#### *field* workers *: int* *= 1* - -Number of workers to create for parallel file upload. - -1 (default) means files will me uploaded sequentially. -2 or more means files will be uploaded in parallel workers. - -Recommended value is `min(32, os.cpu_count() + 4)`, e.g. `5`. - -#### Versionadded -Added in version 0.8.1. - - diff --git a/mddocs/markdown/file/file_uploader/result.md b/mddocs/markdown/file/file_uploader/result.md deleted file mode 100644 index 59c289bea..000000000 --- a/mddocs/markdown/file/file_uploader/result.md +++ /dev/null @@ -1,550 +0,0 @@ - - -# File Uploader Result - -### *class* onetl.file.file_uploader.result.UploadResult(\*, successful: FileSet[RemoteFile] = None, failed: FileSet[FailedLocalFile] = None, skipped: FileSet[LocalPath] = None, missing: FileSet[LocalPath] = None) - -Representation of file upload result. - -Container for file paths, divided into certain categories: - -* [`successful`](#onetl.file.file_uploader.result.UploadResult.successful) -* [`failed`](#onetl.file.file_uploader.result.UploadResult.failed) -* [`skipped`](#onetl.file.file_uploader.result.UploadResult.skipped) -* [`missing`](#onetl.file.file_uploader.result.UploadResult.missing) - -#### Versionadded -Added in version 0.3.0. - -### Examples - -```pycon ->>> from onetl.file import FileUploader ->>> uploader = FileUploader(target_path="/remote", ...) ->>> upload_result = uploader.run( -... [ -... "/local/file1", -... "/local/file2", -... "/failed/file", -... "/existing/file", -... "/missing/file", -... ] -... ) ->>> upload_result -UploadResult( - successful=FileSet([ - RemoteFile("/remote/file1"), - RemoteFile("/remote/file2"), - ]), - failed=FileSet([ - FailedLocalFile("/failed/file") - ]), - skipped=FileSet([ - LocalPath("/existing/file") - ]), - missing=FileSet([ - LocalPath("/missing/file") - ]), -) -``` - - - -#### *property* details *: str* - -Return detailed information about files in the result object - -### Examples - -```pycon ->>> from onetl.impl import FailedRemoteFile, LocalPath, RemoteFile, RemotePathStat ->>> from onetl.exception import NotAFileError ->>> from onetl.file.file_result import FileResult ->>> file_result1 = FileResult( -... successful={ -... RemoteFile("/local/file", stats=RemotePathStat(st_size=1024)), -... RemoteFile("/local/another.file", stats=RemotePathStat(st_size=1024)), -... }, -... failed={ -... FailedRemoteFile( -... path="/remote/file1", -... stats=RemotePathStat(st_size=0), -... exception=NotAFileError("'/remote/file1' is not a file"), -... ), -... FailedRemoteFile( -... path="/remote/file2", -... stats=RemotePathStat(st_size=0), -... exception=PermissionError("'/remote/file2': [Errno 13] Permission denied"), -... ), -... }, -... skipped={LocalPath("/skipped/file1"), LocalPath("/skipped/file2")}, -... missing={LocalPath("/missing/file1"), LocalPath("/missing/file2")}, -... ) ->>> print(file_result1.details) -Total: 8 files (size='2.0 kB') - -Successful 2 files (size='2.0 kB'): - '/local/another.file' (size='1.0 kB') - '/local/file' (size='1.0 kB') - -Failed 2 files (size='0 Bytes'): - '/remote/file2' (size='0 Bytes') - PermissionError("'/remote/file2': [Errno 13] Permission denied") - '/remote/file1' (size='0 Bytes') - NotAFileError("'/remote/file1' is not a file") - -Skipped 2 files (size='0 Bytes'): - '/skipped/file1' - '/skipped/file2' - -Missing 2 files: - '/missing/file2' - '/missing/file1' -``` - -```pycon ->>> file_result2 = FileResult() ->>> print(file_result2.details) -No successful files - -No failed files - -No skipped files - -No missing files -``` - - - -#### dict(\*, include: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, exclude: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, by_alias: bool = False, skip_defaults: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False) → DictStrAny - -Generate a dictionary representation of the model, optionally specifying which fields to include or exclude. - - - -#### *field* failed *: FileSet[FailedLocalFile]* *[Optional]* - -File paths (local) which were not uploaded because of some failure - - - -#### *property* failed_count *: int* - -Get number of failed files - -### Examples - -```pycon ->>> from onetl.impl import RemoteFile ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... failed={RemoteFile("/some/file"), RemoteFile("/some/another.file")}, -... ) ->>> file_result.failed_count -2 -``` - - - -#### *property* failed_size *: int* - -Get size (in bytes) of failed files - -### Examples - -```pycon ->>> from onetl.impl import RemoteFile, RemotePathStat ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... failed={ -... RemoteFile("/some/file", stats=RemotePathStat(st_size=1024)), -... RemoteFile("/some/another.file"), stats=RemotePathStat(st_size=1024)), -... }, -... ) ->>> file_result.failed_size # in bytes -2048 -``` - - - -#### *property* is_empty *: bool* - -Returns `True` if there are no files in `successful`, `failed` and `skipped` attributes - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result1 = FileResult() ->>> file_result1.is_empty -True ->>> file_result2 = FileResult( -... successful={LocalPath("/local/file"), LocalPath("/local/another.file")}, -... ) ->>> file_result2.is_empty -False -``` - - - -#### json(\*, include: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, exclude: 'AbstractSetIntStr' | 'MappingIntStrAny' | None = None, by_alias: bool = False, skip_defaults: bool | None = None, exclude_unset: bool = False, exclude_defaults: bool = False, exclude_none: bool = False, encoder: Callable[[Any], Any] | None = None, models_as_dict: bool = True, \*\*dumps_kwargs: Any) → str - -Generate a JSON representation of the model, include and exclude arguments as per dict(). - -encoder is an optional function to supply as default to json.dumps(), other arguments as per json.dumps(). - - - -#### *field* missing *: FileSet[LocalPath]* *[Optional]* - -File paths (local) which are not present in the local file system - - - -#### *property* missing_count *: int* - -Get number of missing files - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... missing={LocalPath("/some/file"), LocalPath("/some/another.file")}, -... ) ->>> file_result.missing_count -2 -``` - - - -#### raise_if_contains_zero_size() → None - -Raise exception if `successful` attribute contains a file with zero size - -* **Raises:** - ZeroFileSizeError - : `successful` file set contains a file with zero size - -### Examples - -```pycon ->>> from onetl.exception import ZeroFileSizeError ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... successful={ -... LocalPath("/local/empty1.file"), -... LocalPath("/local/empty2.file"), -... LocalPath("/local/normal.file"), -... }, -... ) ->>> file_result.raise_if_contains_zero_size() -Traceback (most recent call last): - ... -onetl.exception.ZeroFileSizeError: 2 files out of 3 have zero size: - '/local/empty1.file' - '/local/empty2.file' -``` - - - -#### raise_if_empty() → None - -Raise exception if there are no files in `successful`, `failed` and `skipped` attributes - -* **Raises:** - EmptyFilesError - : `successful`, `failed` and `skipped` file sets are empty - -### Examples - -```pycon ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult() ->>> file_result.raise_if_empty() -Traceback (most recent call last): - ... -onetl.exception.EmptyFilesError: There are no files in the result -``` - - - -#### raise_if_failed() → None - -Raise exception if there are some files in `failed` attribute - -* **Raises:** - FailedFilesError - : `failed` file set is not empty - -### Examples - -```pycon ->>> from onetl.impl import FailedRemoteFile, RemotePathStat ->>> from onetl.exception import NotAFileError, FileMissingError ->>> from onetl.file.file_result import FileResult ->>> files_with_exception = [ -... FailedRemoteFile( -... path="/remote/file1", -... stats=RemotePathStat(st_size=0), -... exception=NotAFileError("'/remote/file1' is not a file"), -... ), -... FailedRemoteFile( -... path="/remote/file2", -... stats=RemotePathStat(st_size=0), -... exception=PermissionError("'/remote/file2': [Errno 13] Permission denied"), -... ), -... ] ->>> file_result = FileResult(failed=files_with_exception) ->>> file_result.raise_if_failed() -Traceback (most recent call last) -... -onetl.exception.FailedFilesError: Failed 2 files (size='0 bytes'): - '/remote/file1' (size='0 bytes') - NotAFileError("'/remote/file1' is not a file") - - '/remote/file2' (size='0 Bytes') - PermissionError("'/remote/file2': [Errno 13] Permission denied") -``` - - - -#### raise_if_missing() → None - -Raise exception if there are some files in `missing` attribute - -* **Raises:** - MissingFilesError - : `missing` file set is not empty - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... missing={ -... LocalPath("/missing/file1"), -... LocalPath("/missing/file2"), -... }, -... ) ->>> file_result.raise_if_missing() -Traceback (most recent call last): - ... -onetl.exception.MissingFilesError: Missing 2 files: - '/missing/file1' - '/missing/file2' -``` - - - -#### raise_if_skipped() → None - -Raise exception if there are some files in `skipped` attribute - -* **Raises:** - SkippedFilesError - : `skipped` file set is not empty - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... skipped={ -... LocalPath("/skipped/file1"), -... LocalPath("/skipped/file2"), -... }, -... ) ->>> file_result.raise_if_skipped() -Traceback (most recent call last): - ... -onetl.exception.SkippedFilesError: Skipped 2 files (15 kB): - '/skipped/file1' (10kB) - '/skipped/file2' (5 kB) -``` - - - -#### *field* skipped *: FileSet[LocalPath]* *[Optional]* - -File paths (local) which were skipped because of some reason - - - -#### *property* skipped_count *: int* - -Get number of skipped files - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... skipped={LocalPath("/some/file"), LocalPath("/some/another.file")}, -... ) ->>> file_result.skipped_count -2 -``` - - - -#### *property* skipped_size *: int* - -Get size (in bytes) of skipped files - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... skipped={LocalPath("/some/file"), LocalPath("/some/another.file")}, -... ) ->>> file_result.skipped_size # in bytes -1024 -``` - - - -#### *field* successful *: FileSet[RemoteFile]* *[Optional]* - -File paths (remote) which were uploaded successfully - - - -#### *property* successful_count *: int* - -Get number of successful files - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... successful={LocalPath("/some/file"), LocalPath("/some/another.file")}, -... ) ->>> file_result.successful_count -2 -``` - - - -#### *property* successful_size *: int* - -Get size (in bytes) of successful files - -### Examples - -```pycon ->>> from onetl.impl import LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... successful={LocalPath("/some/file"), LocalPath("/some/another.file")}, -... ) ->>> file_result.successful_size # in bytes -1024 -``` - - - -#### *property* summary *: str* - -Return short summary about files in the result object - -### Examples - -```pycon ->>> from onetl.impl import FailedRemoteFile, LocalPath, RemoteFile, RemotePathStat ->>> from onetl.exception import NotAFileError ->>> from onetl.file.file_result import FileResult ->>> file_result1 = FileResult( -... successful={ -... RemoteFile("/local/file", stats=RemotePathStat(st_size=1024)), -... RemoteFile("/local/another.file", stats=RemotePathStat(st_size=1024)), -... }, -... failed={ -... FailedRemoteFile( -... path="/remote/file1", -... stats=RemotePathStat(st_size=0), -... exception=NotAFileError("'/remote/file1' is not a file"), -... ), -... FailedRemoteFile( -... path="/remote/file2", -... stats=RemotePathStat(st_size=0), -... exception=PermissionError("'/remote/file2': [Errno 13] Permission denied"), -... ), -... }, -... skipped={LocalPath("/skipped/file1"), LocalPath("/skipped/file2")}, -... missing={LocalPath("/missing/file1"), LocalPath("/missing/file2")}, -... ) ->>> print(file_result1.summary) -Total: 8 files (size='2.0 kB') - -Successful: 2 files (size='2.0 kB') - -Failed: 2 files (size='0 Bytes') - -Skipped: 2 files (size='0 Bytes') - -Missing: 2 files -``` - -```pycon ->>> file_result2 = FileResult() ->>> print(file_result2.summary) -No files -``` - - - -#### *property* total_count *: int* - -Get total number of all files - -### Examples - -```pycon ->>> from onetl.impl import RemoteFile ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... successful={LocalPath("/local/file"), LocalPath("/local/another.file")}, -... failed={RemoteFile("/remote/file"), RemoteFile("/remote/another.file")}, -... skipped={LocalPath("/skipped/file")}, -... missing={LocalPath("/missing/file")}, -... ) ->>> file_result.total_count -6 -``` - - - -#### *property* total_size *: int* - -Get total size (in bytes) of all files - -### Examples - -```pycon ->>> from onetl.impl import RemoteFile, RemotePathStat, LocalPath ->>> from onetl.file.file_result import FileResult ->>> file_result = FileResult( -... successful={LocalPath("/local/file"), LocalPath("/local/another.file")}, -... failed={ -... RemoteFile("/remote/file", stats=RemotePathStat(st_size=1024)), -... RemoteFile("/remote/another.file", stats=RemotePathStat(st_size=1024)) -... }, -... skipped={LocalPath("/skipped/file")}, -... missing={LocalPath("/missing/file")}, -... ) ->>> file_result.total_size # in bytes -4096 -``` - - diff --git a/mddocs/markdown/file/index.md b/mddocs/markdown/file/index.md deleted file mode 100644 index 2da812bab..000000000 --- a/mddocs/markdown/file/index.md +++ /dev/null @@ -1,9 +0,0 @@ - - - File classes - -* [File Downloader](file_downloader/index.md) -* [File Uploader](file_uploader/index.md) -* [File Mover](file_mover/index.md) -* [File Filters](file_filters/index.md) -* [File Limits](file_limits/index.md) diff --git a/mddocs/markdown/file_df/file_df_reader/file_df_reader.md b/mddocs/markdown/file_df/file_df_reader/file_df_reader.md deleted file mode 100644 index 256ab7b59..000000000 --- a/mddocs/markdown/file_df/file_df_reader/file_df_reader.md +++ /dev/null @@ -1,158 +0,0 @@ - - -# FileDF Reader - -### *class* onetl.file.file_df_reader.file_df_reader.FileDFReader(\*, connection: [BaseFileDFConnection](../../connection/file_df_connection/base.md#onetl.base.base_file_df_connection.BaseFileDFConnection), format: [BaseReadableFileFormat](../file_formats/base.md#onetl.base.base_file_format.BaseReadableFileFormat), source_path: PurePathProtocol | None = None, df_schema: StructType | None = None, options: [FileDFReaderOptions](options.md#onetl.file.file_df_reader.options.FileDFReaderOptions) = FileDFReaderOptions(recursive=None)) - -Allows you to read files from a source path with specified file connection -and parameters, and return a Spark DataFrame. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -This class does **not** support read strategies. - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **connection** - : File DataFrame connection. See [File DataFrame Connections](../../connection/file_df_connection/index.md#file-df-connections) section. - - **format** - : File format to read. - - **source_path** - : Directory path to read data from. -
- Could be `None`, but only if you pass file paths directly to - [`run`](#onetl.file.file_df_reader.file_df_reader.FileDFReader.run) method - - **df_schema** - : Spark DataFrame schema. - - **options** - : Common reading options. - -### Examples - -Read CSV files from local filesystem - -```py -from onetl.connection import SparkLocalFS -from onetl.file import FileDFReader -from onetl.file.format import CSV - -csv = CSV(delimiter=",") -local_fs = SparkLocalFS(spark=spark) - -reader = FileDFReader( - connection=local_fs, - format=csv, - source_path="/path/to/directory", -) -``` - -All supported options - -```py -from onetl.connection import SparkLocalFS -from onetl.file import FileDFReader -from onetl.file.format import CSV - -csv = CSV(delimiter=",") -local_fs = SparkLocalFS(spark=spark) - -reader = FileDFReader( - connection=local_fs, - format=csv, - source_path="/path/to/directory", - options=FileDFReader.Options(recursive=False), -) -``` - - - -#### run(files: Iterable[str | os.PathLike] | None = None) → DataFrame - -Method for reading files as DataFrame. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **files** - : File list to read. -
- If empty, read files from `source_path`. -* **Returns:** - **df** - : Spark DataFrame - -### Examples - -Read CSV files from directory `/path`: - -```python -from onetl.connection import SparkLocalFS -from onetl.file import FileDFReader -from onetl.file.format import CSV - -csv = CSV(delimiter=",") -local_fs = SparkLocalFS(spark=spark) - -reader = FileDFReader( - connection=local_fs, - format=csv, - source_path="/path", -) -df = reader.run() -``` - -Read some CSV files using file paths: - -```python -from onetl.connection import SparkLocalFS -from onetl.file import FileDFReader -from onetl.file.format import CSV - -csv = CSV(delimiter=",") -local_fs = SparkLocalFS(spark=spark) - -reader = FileDFReader( - connection=local_fs, - format=csv, -) - -df = reader.run( - [ - "/path/file1.csv", - "/path/nested/file2.csv", - ] -) -``` - -Read only specific CSV files in directory: - -```python -from onetl.connection import SparkLocalFS -from onetl.file import FileDFReader -from onetl.file.format import CSV - -csv = CSV(delimiter=",") -local_fs = SparkLocalFS(spark=spark) - -reader = FileDFReader( - connection=local_fs, - format=csv, - source_path="/path", -) - -df = reader.run( - [ - # file paths could be relative - "/path/file1.csv", - "/path/nested/file2.csv", - ] -) -``` - - diff --git a/mddocs/markdown/file_df/file_df_reader/index.md b/mddocs/markdown/file_df/file_df_reader/index.md deleted file mode 100644 index 0087e74ac..000000000 --- a/mddocs/markdown/file_df/file_df_reader/index.md +++ /dev/null @@ -1,8 +0,0 @@ - - -# FileDF Reader - -# FileDF Reader - -* [FileDF Reader](file_df_reader.md) -* [Options](options.md) diff --git a/mddocs/markdown/file_df/file_df_reader/options.md b/mddocs/markdown/file_df/file_df_reader/options.md deleted file mode 100644 index eceb33e5d..000000000 --- a/mddocs/markdown/file_df/file_df_reader/options.md +++ /dev/null @@ -1,38 +0,0 @@ - - -# Options - -### *class* onetl.file.file_df_reader.options.FileDFReaderOptions(\*, recursiveFileLookup: bool | None = None, \*\*kwargs) - -Options for [`FileDFReader`](file_df_reader.md#onetl.file.file_df_reader.file_df_reader.FileDFReader). - -#### Versionadded -Added in version 0.9.0. - -### Examples - -#### NOTE -You can pass any value [supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-load-save-functions.html), -even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! - -The set of supported options depends on Spark version. - -```python -from onetl.file import FileDFReader - -options = FileDFReader.Options(recursive=True) -``` - - - -#### *field* recursive *: bool | None* *= None* *(alias 'recursiveFileLookup')* - -If `True`, perform recursive file lookup. - -#### WARNING -This disables partition inferring using file paths. - -#### WARNING -Can be used only in Spark 3+. See [SPARK-27990](https://issues.apache.org/jira/browse/SPARK-27990). - - diff --git a/mddocs/markdown/file_df/file_df_writer/file_df_writer.md b/mddocs/markdown/file_df/file_df_writer/file_df_writer.md deleted file mode 100644 index 8f845c598..000000000 --- a/mddocs/markdown/file_df/file_df_writer/file_df_writer.md +++ /dev/null @@ -1,80 +0,0 @@ - - -# FileDF Writer - -### *class* onetl.file.file_df_writer.file_df_writer.FileDFWriter(\*, connection: ~onetl.base.base_file_df_connection.BaseFileDFConnection, format: ~onetl.base.base_file_format.BaseWritableFileFormat, target_path: ~onetl.base.pure_path_protocol.PurePathProtocol, options: ~onetl.file.file_df_writer.options.FileDFWriterOptions = FileDFWriterOptions(if_exists=, partition_by=None)) - -Allows you to write Spark DataFrame as files in a target path of specified file connection -with parameters. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -* **Parameters:** - **connection** - : File DataFrame connection. See [File DataFrame Connections](../../connection/file_df_connection/index.md#file-df-connections) section. - - **format** - : File format to write. - - **target_path** - : Directory path to write data to. - - **options** - : Common writing options. - -### Examples - -Write CSV files to local filesystem - -```py -from onetl.connection import SparkLocalFS -from onetl.file import FileDFWriter -from onetl.file.format import CSV - -local_fs = SparkLocalFS(spark=spark) - -writer = FileDFWriter( - connection=local_fs, - format=CSV(delimiter=","), - target_path="/path/to/directory", -) -``` - -All supported options - -```py -from onetl.connection import SparkLocalFS -from onetl.file import FileDFWriter -from onetl.file.format import CSV - -csv = CSV(delimiter=",") -local_fs = SparkLocalFS(spark=spark) - -writer = FileDFWriter( - connection=local_fs, - format=csv, - target_path="/path/to/directory", - options=FileDFWriter.Options(if_exists="replace_entire_directory"), -) -``` - - - -#### run(df: DataFrame) → None - -Method for writing DataFrame as files. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### NOTE -Method does support only **batching** DataFrames. - -* **Parameters:** - **df** - : Spark dataframe - -### Examples - -Write df to target: - -```python -writer.run(df) -``` - - diff --git a/mddocs/markdown/file_df/file_df_writer/index.md b/mddocs/markdown/file_df/file_df_writer/index.md deleted file mode 100644 index 9813ed1d4..000000000 --- a/mddocs/markdown/file_df/file_df_writer/index.md +++ /dev/null @@ -1,8 +0,0 @@ - - -# FileDF Writer - -# FileDF Writer - -* [FileDF Writer](file_df_writer.md) -* [Options](options.md) diff --git a/mddocs/markdown/file_df/file_df_writer/options.md b/mddocs/markdown/file_df/file_df_writer/options.md deleted file mode 100644 index 7a76fecbc..000000000 --- a/mddocs/markdown/file_df/file_df_writer/options.md +++ /dev/null @@ -1,140 +0,0 @@ - - -# Options - -### *class* onetl.file.file_df_writer.options.FileDFWriterOptions(\*, if_exists: FileDFExistBehavior = FileDFExistBehavior.APPEND, partitionBy: List[str] | str | None = None, \*\*kwargs) - -Options for [`FileDFWriter`](file_df_writer.md#onetl.file.file_df_writer.file_df_writer.FileDFWriter). - -#### Versionadded -Added in version 0.9.0. - -### Examples - -#### NOTE -You can pass any value [supported by Spark](https://spark.apache.org/docs/latest/sql-data-sources-load-save-functions.html), -even if it is not mentioned in this documentation. **Option names should be in** `camelCase`! - -The set of supported options depends on Spark version. - -```python -from onetl.file import FileDFWriter - -options = FileDFWriter.Options( - if_exists="replace_overlapping_partitions", - partitionBy="month", -) -``` - - - -#### *field* if_exists *: FileDFExistBehavior* *= FileDFExistBehavior.APPEND* - -Behavior for existing target directory. - -If target directory does not exist, it will be created. -But if it does exist, then behavior is different for each value. - -#### Versionchanged -Changed in version 0.13.0: Default value was changed from `error` to `append` - -Possible values: -: * `error` - : If folder already exists, raise an exception. -
- Same as Spark’s `df.write.mode("error").save()`. - * `skip_entire_directory` - : If folder already exists, left existing files intact and stop immediately without any errors. -
- Same as Spark’s `df.write.mode("ignore").save()`. - * `append` (default) - : Appends data into existing directory. -
- ### Behavior in details -
- * Directory does not exist - : Directory is created using all the provided options (`format`, `partition_by`, etc). - * Directory exists, does not contain partitions, but [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by) is set - : Data is appended to a directory, but to partitioned directory structure. -
- #### WARNING - Existing files still present in the root of directory, but Spark will ignore those files while reading, - unless using `recursive=True`. - * Directory exists and contains partitions, but [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by) is not set - : Data is appended to a directory, but to the root of directory instead of nested partition directories. -
- #### WARNING - Spark will ignore such files while reading, unless using `recursive=True`. - * Directory exists and contains partitions, but with different partitioning schema than [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by) - : Data is appended to a directory with new partitioning schema. -
- #### WARNING - Spark cannot read directory with multiple partitioning schemas, - unless using `recursive=True` to disable partition scanning. - * Directory exists and partitioned according [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by), but partition is present only in dataframe - : New partition directory is created. - * Directory exists and partitioned according [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by), partition is present in both dataframe and directory - : New files are added to existing partition directory, existing files are sill present. - * Directory exists and partitioned according [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by), but partition is present only in directory, not dataframe - : Existing partition is left intact. - * `replace_overlapping_partitions` - : If partitions from dataframe already exist in directory structure, they will be overwritten. -
- Same as Spark’s `df.write.mode("overwrite").save()` + - `spark.sql.sources.partitionOverwriteMode=dynamic`. -
- ### Behavior in details -
- * Directory does not exist - : Directory is created using all the provided options (`format`, `partition_by`, etc). - * Directory exists, does not contain partitions, but [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by) is set - : Directory **will be deleted**, and will be created with partitions. - * Directory exists and contains partitions, but [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by) is not set - : Directory **will be deleted**, and will be created with partitions. - * Directory exists and contains partitions, but with different partitioning schema than [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by) - : Data is appended to a directory with new partitioning schema. -
- #### WARNING - Spark cannot read directory with multiple partitioning schemas, - unless using `recursive=True` to disable partition scanning. - * Directory exists and partitioned according [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by), but partition is present only in dataframe - : New partition directory is created. - * Directory exists and partitioned according [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by), partition is present in both dataframe and directory - : Partition directory **will be deleted**, and new one is created with files containing data from dataframe. - * Directory exists and partitioned according [`partition_by`](#onetl.file.file_df_writer.options.FileDFWriterOptions.partition_by), but partition is present only in directory, not dataframe - : Existing partition is left intact. - * `replace_entire_directory` - : Remove existing directory and create new one, **overwriting all existing data**. - **All existing partitions are dropped.** -
- Same as Spark’s `df.write.mode("overwrite").save()` + - `spark.sql.sources.partitionOverwriteMode=static`. - -#### NOTE -Unlike using pure Spark, config option `spark.sql.sources.partitionOverwriteMode` -does not affect behavior of any `mode` - - - -#### *field* partition_by *: List[str] | str | None* *= None* *(alias 'partitionBy')* - -List of columns should be used for data partitioning. `None` means partitioning is disabled. - -Each partition is a folder which contains only files with the specific column value, -like `some.csv/col1=value1`, `some.csv/col1=value2`, and so on. - -Multiple partitions columns means nested folder structure, like `some.csv/col1=val1/col2=val2`. - -If `WHERE` clause in the query contains expression like `partition = value`, -Spark will scan only files in a specific partition. - -Examples: `reg_id` or `["reg_id", "business_dt"]` - -#### NOTE -Values should be scalars (integers, strings), -and either static (`countryId`) or incrementing (dates, years), with low -number of distinct values. - -Columns like `userId` or `datetime`/`timestamp` should **NOT** be used for partitioning. - - diff --git a/mddocs/markdown/file_df/file_formats/avro.md b/mddocs/markdown/file_df/file_formats/avro.md deleted file mode 100644 index de41f7796..000000000 --- a/mddocs/markdown/file_df/file_formats/avro.md +++ /dev/null @@ -1,390 +0,0 @@ - - -# Avro - -### *class* onetl.file.format.avro.Avro(\*, avroSchema: dict | None = None, avroSchemaUrl: str | None = None, recordName: str | None = None, recordNamespace: str | None = None, compression: str | Literal['uncompressed', 'snappy', 'deflate', 'bzip2', 'xz', 'zstandard'] | None = None, mode: Literal['PERMISSIVE', 'FAILFAST'] | None = None, datetimeRebaseMode: Literal['CORRECTED', 'LEGACY', 'EXCEPTION'] | None = None, positionalFieldMatching: bool | None = None, enableStableIdentifiersForUnionType: bool | None = None, \*\*kwargs) - -Avro file format. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on [Spark Avro](https://spark.apache.org/docs/latest/sql-data-sources-avro.html) file format. - -Supports reading/writing files with `.avro` extension. - -### Version compatibility - -* Spark versions: 2.4.x - 3.5.x -* Java versions: 8 - 20 - -See documentation from link above. - -#### Versionadded -Added in version 0.9.0. - -### Examples - -#### NOTE -You can pass any option mentioned in -[official documentation](https://spark.apache.org/docs/latest/sql-data-sources-avro.html). -**Option names should be in** `camelCase`! - -The set of supported options depends on Spark version. - -Reading files - -```py -from pyspark.sql import SparkSession -from onetl.file.format import Avro - -# Create Spark session with Avro package loaded -maven_packages = Avro.get_packages(spark_version="3.5.5") -spark = ( - SparkSession.builder.appName("spark-app-name") - .config("spark.jars.packages", ",".join(maven_packages)) - .getOrCreate() -) - -schema = { - "type": "record", - "name": "Person", - "fields": [ - {"name": "name", "type": "string"}, - {"name": "age", "type": "int"}, - ], -} -avro = Avro(avroSchema=schema) # or avroSchemaUrl=... -``` - -Writing files - -```py -# Create Spark session with Avro package loaded -spark = ... - -from onetl.file.format import Avro - -schema = { - "type": "record", - "name": "Person", - "fields": [ - {"name": "name", "type": "string"}, - {"name": "age", "type": "int"}, - ], -} -avro = Avro( - avroSchema=schema, # or avroSchemaUrl=... - compression="snappy", -) -``` - - - -#### *field* schema_dict *: dict | None* *= None* *(alias 'avroSchema')* - -Avro schema in JSON format representation. - -```python -avro = Avro( - avroSchema={ - "type": "record", - "name": "Person", - "fields": [ - {"name": "name", "type": "string"}, - {"name": "age", "type": "int"}, - ], - }, -) -``` - -If set, all records should match this schema. - -#### WARNING -Mutually exclusive with [`schema_url`](#onetl.file.format.avro.Avro.schema_url). - - - -#### *field* schema_url *: str | None* *= None* *(alias 'avroSchemaUrl')* - -URL to Avro schema in JSON format. Usually points to Schema Registry, like: - -```python -schema_registry = "http://some.schema.registry.domain" -name = "MyAwesomeSchema" -version = "latest" - -schema_url = f"{schema_registry}/subjects/{name}/versions/{version}/schema" -avro = Avro(avroSchemaUrl=schema_url) -``` - -If set, schema is fetched before any records are parsed, so all records should match this schema. - -#### WARNING -Mutually exclusive with [`schema_dict`](#onetl.file.format.avro.Avro.schema_dict). - - - -#### *field* recordName *: str | None* *= None* - -Record name in written Avro schema. -Default is `topLevelRecord`. - -#### NOTE -Used only for writing files and by [`serialize_column`](#onetl.file.format.avro.Avro.serialize_column). - - - -#### *field* recordNamespace *: str | None* *= None* - -Record namespace in written Avro schema. Default is not set. - -#### NOTE -Used only for writing files and by [`serialize_column`](#onetl.file.format.avro.Avro.serialize_column). - - - -#### *field* compression *: str | Literal['uncompressed', 'snappy', 'deflate', 'bzip2', 'xz', 'zstandard'] | None* *= None* - -Compression codec. -By default, Spark config value `spark.sql.avro.compression.codec `` (``snappy`) is used. - -#### NOTE -Used only for writing files. Ignored by [`serialize_column`](#onetl.file.format.avro.Avro.serialize_column). - - - -#### *field* mode *: Literal['PERMISSIVE', 'FAILFAST'] | None* *= None* - -How to handle parsing errors: -: * `PERMISSIVE` - set field value as `null`. - * `FAILFAST` - throw an error immediately. - -Default is `FAILFAST`. - -#### NOTE -Used only by [`parse_column`](#onetl.file.format.avro.Avro.parse_column) method. - - - -#### *field* datetimeRebaseMode *: Literal['CORRECTED', 'LEGACY', 'EXCEPTION'] | None* *= None* - -While converting dates/timestamps from Julian to Proleptic Gregorian calendar, handle value ambiguity: -: * `EXCEPTION` - fail if ancient dates/timestamps are ambiguous between the two calendars. - * `CORRECTED` - load dates/timestamps without as-is. - * `LEGACY` - rebase ancient dates/timestamps from the Julian to Proleptic Gregorian calendar. - -By default, Spark config value `spark.sql.avro.datetimeRebaseModeInRead` (`CORRECTED`) is used. - -#### NOTE -Used only for reading files and by [`parse_column`](#onetl.file.format.avro.Avro.parse_column). - - - -#### *field* positionalFieldMatching *: bool | None* *= None* - -If `True`, match Avro schema field and DataFrame column by position. -If `False`, match by name. - -Default is `False`. - - - -#### *field* enableStableIdentifiersForUnionType *: bool | None* *= None* - -Avro schema may contain union types, which are not supported by Spark. -Different variants of union are split to separated DataFrame columns with respective type. - -If option value is `True`, DataFrame column names are based on Avro variant names, e.g. `member_int`, `member_string`. -If `False`, DataFrame column names are generated using field position, e.g. `member0`, `member1`. - -Default is `False`. - -#### NOTE -Used only for reading files and by [`parse_column`](#onetl.file.format.avro.Avro.parse_column). - - - -#### *classmethod* get_packages(spark_version: str, scala_version: str | None = None) → list[str] - -Get package names to be downloaded by Spark. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -See [Maven package index](https://mvnrepository.com/artifact/org.apache.spark/spark-avro) -for all available packages. - -#### Versionadded -Added in version 0.9.0. - -* **Parameters:** - **spark_version** - : Spark version in format `major.minor.patch`. - - **scala_version** - : Scala version in format `major.minor`. -
- If `None`, `spark_version` is used to determine Scala version. - -### Examples - -```python -from onetl.file.format import Avro - -Avro.get_packages(spark_version="3.5.5") -Avro.get_packages(spark_version="3.5.5", scala_version="2.12") -``` - - - -#### parse_column(column: str | Column) → Column - -Parses an Avro binary column into a structured Spark SQL column using Spark’s -[from_avro](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.avro.functions.from_avro.html) function, -based on the schema provided within the class. - -#### NOTE -Can be used only with Spark 3.x+ - -#### WARNING -If `schema_url` is provided, `requests` library is used to fetch the schema from the URL. -It should be installed manually, like this: - -```bash -pip install requests -``` - -#### Versionadded -Added in version 0.11.0. - -* **Parameters:** - **column** - : The name of the column or the column object containing Avro bytes to deserialize. - Schema should match the provided Avro schema. -* **Returns:** - Column with deserialized data. Schema is matching the provided Avro schema. Column name is the same as input column. -* **Raises:** - ValueError - : If the Spark version is less than 3.x or if neither `avroSchema` nor `avroSchemaUrl` are defined. - - ImportError - : If `schema_url` is used and the `requests` library is not installed. - -### Examples - -```pycon ->>> from pyspark.sql.functions import decode ->>> from onetl.file.format import Avro ->>> df.show() -+----+----------------------+----------+---------+------+-----------------------+-------------+ -|key |value |topic |partition|offset|timestamp |timestampType| -+----+----------------------+----------+---------+------+-----------------------+-------------+ -|[31]|[0A 41 6C 69 63 65 28]|topicAvro |0 |0 |2024-04-24 13:02:25.911|0 | -|[32]|[06 42 6F 62 32] |topicAvro |0 |1 |2024-04-24 13:02:25.922|0 | -+----+----------------------+----------+---------+------+-----------------------+-------------+ ->>> df.printSchema() -root -|-- key: binary (nullable = true) -|-- value: binary (nullable = true) -|-- topic: string (nullable = true) -|-- partition: integer (nullable = true) -|-- offset: integer (nullable = true) -|-- timestamp: timestamp (nullable = true) -|-- timestampType: integer (nullable = true) ->>> avro = Avro( -... avroSchema={ # or avroSchemaUrl=... -... "type": "record", -... "name": "Person", -... "fields": [ -... {"name": "name", "type": "string"}, -... {"name": "age", "type": "int"}, -... ], -... } -... ) ->>> parsed_df = df.select(decode("key", "UTF-8").alias("key"), avro.parse_column("value")) ->>> parsed_df.show(truncate=False) -+---+-----------+ -|key|value | -+---+-----------+ -|1 |{Alice, 20}| -|2 |{Bob, 25} | -+---+-----------+ ->>> parsed_df.printSchema() -root -|-- key: string (nullable = true) -|-- value: struct (nullable = true) -| |-- name: string (nullable = true) -| |-- age: integer (nullable = true) -``` - - - -#### serialize_column(column: str | Column) → Column - -Serializes a structured Spark SQL column into an Avro binary column using Spark’s -[to_avro](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.avro.functions.to_avro.html#pyspark.sql.avro.functions.to_avro) function. - -#### NOTE -Can be used only with Spark 3.x+ - -#### WARNING -If `schema_url` is provided, `requests` library is used to fetch the schema from the URL. It should be installed manually, like this: - -```bash -pip install requests -``` - -#### Versionadded -Added in version 0.11.0. - -* **Parameters:** - **column** - : The name of the column or the column object containing the data to serialize to Avro format. -* **Returns:** - Column with binary Avro data. Column name is the same as input column. -* **Raises:** - ValueError - : If the Spark version is less than 3.x. - - ImportError - : If `schema_url` is used and the `requests` library is not installed. - -### Examples - -```pycon ->>> from pyspark.sql.functions import decode ->>> from onetl.file.format import Avro ->>> df.show() -+---+-----------+ -|key|value | -+---+-----------+ -|1 |{Alice, 20}| -|2 | {Bob, 25}| -+---+-----------+ ->>> df.printSchema() -root -|-- key: string (nullable = true) -|-- value: struct (nullable = true) -| |-- name: string (nullable = true) -| |-- age: integer (nullable = true) ->>> # serializing data into Avro format ->>> avro = Avro( -... avroSchema={ # or avroSchemaUrl=... -... "type": "record", -... "name": "Person", -... "fields": [ -... {"name": "name", "type": "string"}, -... {"name": "age", "type": "int"}, -... ], -... } -... ) ->>> serialized_df = df.select("key", avro.serialize_column("value")) ->>> serialized_df.show(truncate=False) -+---+----------------------+ -|key|value | -+---+----------------------+ -| 1|[0A 41 6C 69 63 65 28]| -| 2|[06 42 6F 62 32] | -+---+----------------------+ ->>> serialized_df.printSchema() -root -|-- key: string (nullable = true) -|-- value: binary (nullable = true) -``` - - diff --git a/mddocs/markdown/file_df/file_formats/base.md b/mddocs/markdown/file_df/file_formats/base.md deleted file mode 100644 index 68ca0e042..000000000 --- a/mddocs/markdown/file_df/file_formats/base.md +++ /dev/null @@ -1,73 +0,0 @@ - - -# Base interface - -### *class* onetl.base.base_file_format.BaseReadableFileFormat - -Representation of readable file format. - -#### Versionadded -Added in version 0.9.0. - - - -#### *abstract* check_if_supported(spark: SparkSession) → None - -Check if Spark session does support this file format. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.9.0. - -* **Raises:** - RuntimeError - : If file format is not supported. - - - -#### *abstract* apply_to_reader(reader: DataFrameReader) → DataFrameReader - -Apply provided format to `pyspark.sql.DataFrameReader`. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.9.0. - -* **Returns:** - `pyspark.sql.DataFrameReader` - : DataFrameReader with options applied. - - - -### *class* onetl.base.base_file_format.BaseWritableFileFormat - -Representation of writable file format. - -#### Versionadded -Added in version 0.9.0. - - - -#### *abstract* check_if_supported(spark: SparkSession) → None - -Check if Spark session does support this file format. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.9.0. - -* **Raises:** - RuntimeError - : If file format is not supported. - - - -#### *abstract* apply_to_writer(writer: DataFrameWriter) → DataFrameWriter - -Apply provided format to `pyspark.sql.DataFrameWriter`. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.9.0. - -* **Returns:** - `pyspark.sql.DataFrameWriter` - : DataFrameWriter with options applied. - - diff --git a/mddocs/markdown/file_df/file_formats/csv.md b/mddocs/markdown/file_df/file_formats/csv.md deleted file mode 100644 index a02edd58e..000000000 --- a/mddocs/markdown/file_df/file_formats/csv.md +++ /dev/null @@ -1,539 +0,0 @@ - - -# CSV - -### *class* onetl.file.format.csv.CSV(\*, sep: str = ',', header: bool | None = None, quote: ConstrainedStrValue = '"', quoteAll: bool | None = None, escape: ConstrainedStrValue = '\\\\', lineSep: ConstrainedStrValue | None = None, encoding: ConstrainedStrValue | None = None, compression: str | Literal['none', 'bzip2', 'gzip', 'lz4', 'snappy', 'deflate'] | None = None, inferSchema: bool | None = None, samplingRatio: ConstrainedFloatValue | None = None, comment: ConstrainedStrValue | None = None, enforceSchema: bool | None = None, escapeQuotes: bool | None = None, unescapedQuoteHandling: None | Literal['STOP_AT_CLOSING_QUOTE', 'BACK_TO_DELIMITER', 'STOP_AT_DELIMITER', 'SKIP_VALUE', 'RAISE_ERROR'] = None, ignoreLeadingWhiteSpace: bool | None = None, ignoreTrailingWhiteSpace: bool | None = None, emptyValue: str | None = None, nullValue: str | None = None, nanValue: str | None = None, positiveInf: ConstrainedStrValue | None = None, negativeInf: ConstrainedStrValue | None = None, preferDate: bool | None = None, dateFormat: ConstrainedStrValue | None = None, timestampFormat: ConstrainedStrValue | None = None, timestampNTZFormat: ConstrainedStrValue | None = None, locale: ConstrainedStrValue | None = None, maxCharsPerColumn: int | None = None, mode: Literal['PERMISSIVE', 'DROPMALFORMED', 'FAILFAST'] | None = None, columnNameOfCorruptRecord: ConstrainedStrValue | None = None, multiLine: bool | None = None, charToEscapeQuoteEscaping: ConstrainedStrValue | None = None, \*\*kwargs) - -CSV file format. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on [Spark CSV](https://spark.apache.org/docs/latest/sql-data-sources-csv.html) file format. - -Supports reading/writing files with `.csv` extension with content like: - -```csv -"some","value" -"another","value" -``` - -#### Versionadded -Added in version 0.9.0. - -### Examples - -#### NOTE -You can pass any option mentioned in -[official documentation](https://spark.apache.org/docs/latest/sql-data-sources-csv.html). -**Option names should be in** `camelCase`! - -The set of supported options depends on Spark version. - -Reading files - -```py -from onetl.file.format import CSV - -csv = CSV(header=True, inferSchema=True, mode="PERMISSIVE") -``` - -Writing files - -```py -from onetl.file.format import CSV - -csv = CSV(header=True, compression="gzip") -``` - - - -#### *field* delimiter *: str* *= ','* *(alias 'sep')* - -Character used to separate fields in CSV row. - - - -#### *field* header *: bool | None* *= None* - -If `True`, the first row of the file is considered a header. -Default `False`. - - - -#### *field* quote *: str* *= '"'* - -Character used to quote field values within CSV field. - -Empty string is considered as `\u0000` (`NUL`) character. - - -* **Constraints:** - - **maxLength** = 1 - -#### *field* quoteAll *: bool | None* *= None* - -If `True`, all fields are quoted: - -```csv -"some","field with \"quote","123","" -``` - -If `False`, only quote fields containing [`quote`](#onetl.file.format.csv.CSV.quote) symbols. - -```csv -any,"field with \"quote",123, -``` - -Default `False`. - -#### NOTE -Used only for writing files. - - - -#### *field* compression *: str | Literal['none', 'bzip2', 'gzip', 'lz4', 'snappy', 'deflate'] | None* *= None* - -Compression codec of the CSV file. -Default `none`. - -#### NOTE -Used only for writing files. Ignored by [`parse_column`](#onetl.file.format.csv.CSV.parse_column) method. - - - -#### *field* inferSchema *: bool | None* *= None* - -If `True`, try to infer the input schema by reading a sample of the file (see [`samplingRatio`](#onetl.file.format.csv.CSV.samplingRatio)). -Default `False` which means that all parsed columns will be `StringType()`. - -#### NOTE -Used only for reading files, and only if user haven’t provider explicit DataFrame schema. -Ignored by [`parse_column`](#onetl.file.format.csv.CSV.parse_column) function. - - - -#### *field* samplingRatio *: float | None* *= None* - -For `inferSchema=True`, read the specified fraction of rows to infer the schema. -Default `1`. - -#### NOTE -Used only for reading files. Ignored by [`parse_column`](#onetl.file.format.csv.CSV.parse_column) function. - - -* **Constraints:** - - **minimum** = 0 - - **maximum** = 1 - -#### *field* comment *: str | None* *= None* - -If set, all lines starting with specified character (e.g. `#`) are considered a comment, and skipped. -Default is not set, meaning that CSV lines should not contain comments. - -#### NOTE -Used only for reading files and [`parse_column`](#onetl.file.format.csv.CSV.parse_column) method. - - -* **Constraints:** - - **maxLength** = 1 - -#### *field* enforceSchema *: bool | None* *= None* - -If `True`, inferred or user-provided schema has higher priority than CSV file headers. -This means that all input files should have the same structure. - -If `False`, CSV headers are used as a primary source of information about column names and their position. - -Default `True`. - -#### NOTE -Used only for reading files. Ignored by [`parse_column`](#onetl.file.format.csv.CSV.parse_column) function. - - - -#### *field* escapeQuotes *: bool | None* *= None* - -If `True`, escape quotes within CSV field. - -```csv -any,field with \"quote,123, -``` - -If `False`, wrap fields containing [`quote`](#onetl.file.format.csv.CSV.quote) symbols with quotes. - -```csv -any,"field with ""quote ",123, -``` - -Default `True`. - -#### NOTE -Used only for writing files. - - - -#### *field* unescapedQuoteHandling *: None | Literal['STOP_AT_CLOSING_QUOTE', 'BACK_TO_DELIMITER', 'STOP_AT_DELIMITER', 'SKIP_VALUE', 'RAISE_ERROR']* *= None* - -Define how to handle unescaped quotes within CSV field. - -* `STOP_AT_CLOSING_QUOTE` - collect all characters until closing quote. -* `BACK_TO_DELIMITER` - collect all characters until delimiter or line end. -* `STOP_AT_DELIMITER` - collect all characters until delimiter or line end. - : If quotes are not closed, this may produce incorrect results (e.g. including delimiter inside field value). -* `SKIP_VALUE` - skip field and consider it as [`nullValue`](#onetl.file.format.csv.CSV.nullValue). -* `RAISE_ERROR` - raise error immediately. - -Default `STOP_AT_DELIMITER`. - -#### NOTE -Used only for reading files and [`parse_column`](#onetl.file.format.csv.CSV.parse_column) method. - - - -#### *field* ignoreLeadingWhiteSpace *: bool | None* *= None* - -If `True`, trim leading whitespaces in field value. - -Defaults: -: * `True` for reading. - * `False` for writing. - - - -#### *field* ignoreTrailingWhiteSpace *: bool | None* *= None* - -If `True`, trim trailing whitespaces in field value. - -Defaults: -: * `True` for reading. - * `False` for writing. - - - -#### *field* emptyValue *: str | None* *= None* - -Value used for empty string fields. - -Defaults: -: * empty string for reading. - * `""` for writing. - - - -#### *field* nullValue *: str | None* *= None* - -If set, this value will be converted to `null`. -Default is empty string. - - - -#### *field* nanValue *: str | None* *= None* - -If set, this string will be considered as Not-A-Number (NaN) value for `FloatType()` and `DoubleType()`. -Default is `NaN`. - -#### NOTE -Used only for reading files and [`parse_column`](#onetl.file.format.csv.CSV.parse_column) method. - - - -#### *field* positiveInf *: str | None* *= None* - -If set, this string will be considered as positive infinity value for `FloatType()` and `DoubleType()`. -Default is `Inf`. - -#### NOTE -Used only for reading files and [`parse_column`](#onetl.file.format.csv.CSV.parse_column) method. - - -* **Constraints:** - - **minLength** = 1 - -#### \_\_init_\_(\*\*kwargs) - - - -#### *field* negativeInf *: str | None* *= None* - -If set, this string will be considered as negative infinity value for `FloatType()` and `DoubleType()`. -Default is `-Inf`. - -#### NOTE -Used only for reading files and [`parse_column`](#onetl.file.format.csv.CSV.parse_column) method. - - -* **Constraints:** - - **minLength** = 1 - -#### *field* preferDate *: bool | None* *= None* - -If `True` and `inferSchema=True` and column does match [`dateFormat`](#onetl.file.format.csv.CSV.dateFormat), consider it as `DateType()`. -For columns matching both [`timestampFormat`](#onetl.file.format.csv.CSV.timestampFormat) and [`dateFormat`](#onetl.file.format.csv.CSV.dateFormat), consider it as `TimestampType()`. - -If `False`, date and timestamp columns will be considered as `StringType()`. - -Default `True`. - -#### NOTE -Used only for reading files. Ignored by [`parse_column`](#onetl.file.format.csv.CSV.parse_column) function. - - - -#### *field* dateFormat *: str | None* *= None* - -String format for `DateType()` representation. -Default is `yyyy-MM-dd`. - - -* **Constraints:** - - **minLength** = 1 - -#### *field* timestampFormat *: str | None* *= None* - -String format for TimestampType()\` representation. -Default is `yyyy-MM-dd'T'HH:mm:ss[.SSS][XXX]`. - - -* **Constraints:** - - **minLength** = 1 - -#### *field* timestampNTZFormat *: str | None* *= None* - -String format for TimestampNTZType()\` representation. -Default is `yyyy-MM-dd'T'HH:mm:ss[.SSS]`. - -#### NOTE -Added in Spark 3.2.0 - - -* **Constraints:** - - **minLength** = 1 - -#### *field* locale *: str | None* *= None* - -Locale name used to parse dates and timestamps. -Default is `en-US` - -#### NOTE -Used only for reading files and [`parse_column`](#onetl.file.format.csv.CSV.parse_column) method. - - -* **Constraints:** - - **minLength** = 1 - -#### *field* maxCharsPerColumn *: int | None* *= None* - -Maximum number of characters to read per column. -Default is `-1`, which means no limit. - -#### NOTE -Used only for reading files and [`parse_column`](#onetl.file.format.csv.CSV.parse_column) method. - - - -#### *field* mode *: Literal['PERMISSIVE', 'DROPMALFORMED', 'FAILFAST'] | None* *= None* - -How to handle parsing errors: -: * `PERMISSIVE` - set field value as `null`, move raw data to [`columnNameOfCorruptRecord`](#onetl.file.format.csv.CSV.columnNameOfCorruptRecord) column. - * `DROPMALFORMED` - skip the malformed row. - * `FAILFAST` - throw an error immediately. - -Default is `PERMISSIVE`. - -#### NOTE -Used only for reading files and [`parse_column`](#onetl.file.format.csv.CSV.parse_column) method. - - - -#### *field* columnNameOfCorruptRecord *: str | None* *= None* - -Name of column to put corrupt records in. -Default is `_corrupt_record`. - -#### WARNING -If DataFrame schema is provided, this column should be added to schema explicitly: - -```python -from onetl.connection import SparkLocalFS -from onetl.file import FileDFReader -from onetl.file.format import CSV - -from pyspark.sql.types import StructType, StructField, TimestampType, StringType - -spark = ... - -schema = StructType( - [ - StructField("my_field", TimestampType()), - StructField("_corrupt_record", StringType()), # <-- important - ] -) - -csv = CSV(mode="PERMISSIVE", columnNameOfCorruptRecord="_corrupt_record") - -reader = FileDFReader( - connection=connection, - format=csv, - schema=schema, # < --- -) -df = reader.run(["/some/file.csv"]) -``` - -#### NOTE -Used only for reading files and [`parse_column`](#onetl.file.format.csv.CSV.parse_column) method. - - -* **Constraints:** - - **minLength** = 1 - -#### *field* multiLine *: bool | None* *= None* - -If `True`, fields may contain line separators. -If `False`, the input is expected to have one record per file. - -Default is `True`. - -#### NOTE -Used only for reading files. -Ignored by [`parse_column`](#onetl.file.format.csv.CSV.parse_column) method, as it expects that each DataFrame row will contain exactly one CSV line. - - - -#### *field* charToEscapeQuoteEscaping *: str | None* *= None* - -If CSV field value contains `escape` character, it should be escaped as well. -For example, if `escape="\"`, when line: - -```csv -"some \" quoted value",other -"some \\ backslashed value",another -``` - -will be parsed as: - -```python -[ - ('some " quoted value', "other"), - ("some \ backslashed value", "another"), -] -``` - -And vice-versa, for writing CSV rows to file. - -Default is same as `escape`. - - -* **Constraints:** - - **maxLength** = 1 - -#### parse_column(column: str | Column, schema: StructType) → Column - -Parses a CSV string column to a structured Spark SQL column using Spark’s -[from_csv](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.functions.from_csv.html) function, based on the provided schema. - -#### NOTE -Can be used only with Spark 3.x+ - -#### Versionadded -Added in version 0.11.0. - -* **Parameters:** - **column** - : The name of the column or the column object containing CSV strings/bytes to parse. - - **schema** - : The schema to apply when parsing the CSV data. This defines the structure of the output DataFrame column. -* **Returns:** - Column with deserialized data, with the same structure as the provided schema. Column name is the same as input column. - -### Examples - -```pycon ->>> from pyspark.sql.types import StructType, StructField, IntegerType, StringType ->>> from onetl.file.format import CSV ->>> df.show() -+--+--------+ -|id|value | -+--+--------+ -|1 |Alice;20| -|2 |Bob;25 | -+--+--------+ ->>> df.printSchema() -root -|-- id: integer (nullable = true) -|-- value: string (nullable = true) ->>> csv = CSV(delimiter=";") ->>> csv_schema = StructType( -... [ -... StructField("name", StringType(), nullable=True), -... StructField("age", IntegerType(), nullable=True), -... ], -... ) ->>> parsed_df = df.select("id", csv.parse_column("value", csv_schema)) ->>> parsed_df.show() -+--+-----------+ -|id|value | -+--+-----------+ -|1 |{Alice, 20}| -|2 | {Bob, 25}| -+--+-----------+ ->>> parsed_df.printSchema() -root -|-- id: integer (nullable = true) -|-- value: struct (nullable = true) -| |-- name: string (nullable = true) -| |-- age: integer (nullable = true) -``` - - - -#### serialize_column(column: str | Column) → Column - -Serializes a structured Spark SQL column into a CSV string column using Spark’s -[to_csv](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.functions.to_csv.html) function. - -#### NOTE -Can be used only with Spark 3.x+ - -#### Versionadded -Added in version 0.11.0. - -* **Parameters:** - **column** - : The name of the column or the Column object containing the data to serialize to CSV. -* **Returns:** - Column with string CSV data. Column name is the same as input column. - -### Examples - -```pycon ->>> from pyspark.sql.functions import decode ->>> from onetl.file.format import CSV ->>> df.show() -+--+-----------+ -|id|value | -+--+-----------+ -|1 |{Alice, 20}| -|2 | {Bob, 25}| -+--+-----------+ ->>> df.printSchema() -root -|-- id: integer (nullable = true) -|-- value: struct (nullable = true) -| |-- name: string (nullable = true) -| |-- age: integer (nullable = true) ->>> # serializing data into CSV format ->>> csv = CSV(delimiter=";") ->>> serialized_df = df.select("id", csv.serialize_column("value")) ->>> serialized_df.show(truncate=False) -+--+--------+ -|id|value | -+--+--------+ -|1 |Alice;20| -|2 |Bob;25 | -+--+--------+ ->>> serialized_df.printSchema() -root -|-- id: integer (nullable = true) -|-- value: string (nullable = true) -``` - - diff --git a/mddocs/markdown/file_df/file_formats/excel.md b/mddocs/markdown/file_df/file_formats/excel.md deleted file mode 100644 index 6925c91ca..000000000 --- a/mddocs/markdown/file_df/file_formats/excel.md +++ /dev/null @@ -1,232 +0,0 @@ - - -# Excel - -### *class* onetl.file.format.excel.Excel(\*, header: bool = False, dataAddress: str | None = None, timestampFormat: str | None = None, dateFormat: str | None = None, treatEmptyValuesAsNulls: bool | None = None, setErrorCellsToFallbackValues: bool | None = None, usePlainNumberFormat: bool | None = None, inferSchema: bool | None = None, workbookPassword: SecretStr | None = None, maxRowsInMemory: int | None = None, maxByteArraySize: ByteSize | None = None, tempFileThreshold: ByteSize | None = None, excerptSize: int | None = None, \*\*kwargs) - -Excel file format. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on [Spark Excel](https://github.com/crealytics/spark-excel) file format. - -Supports reading/writing files with `.xlsx` (read/write) and `.xls` (read only) extensions. - -### Version compatibility - -* Spark versions: 3.2.x - 3.5.x - > #### WARNING - > Not all combinations of Spark version and package version are supported. - > See [Maven index](https://mvnrepository.com/artifact/com.crealytics/spark-excel) - > and [official documentation](https://github.com/crealytics/spark-excel). -* Java versions: 8 - 20 - -See documentation from link above. - -#### Versionadded -Added in version 0.9.4. - -### Examples - -#### NOTE -You can pass any option mentioned in -[official documentation](https://github.com/crealytics/spark-excel). -**Option names should be in** `camelCase`! - -The set of supported options depends on `spark-excel` package version. - -Reading files - -```py -from pyspark.sql import SparkSession -from onetl.file.format import Excel - -# Create Spark session with Excel package loaded -maven_packages = Excel.get_packages(spark_version="3.5.1") -spark = ( - SparkSession.builder.appName("spark-app-name") - .config("spark.jars.packages", ",".join(maven_packages)) - .getOrCreate() -) - -excel = Excel(header=True, inferSchema=True) -``` - -Writing files - -```py -# Create Spark session with Excel package loaded -spark = ... - -from onetl.file.format import XML - -excel = Excel(header=True, dataAddress="'Sheet1'!A1") -``` - - - -#### *field* header *: bool* *= False* - -If `True`, the first row in file is conditioned as a header. -Default `False`. - - - -#### *field* dataAddress *: str | None* *= None* - -Cell address used as starting point. -For example: `'A1'` or `'Sheet1'!A1` - - - -#### *field* timestampFormat *: str | None* *= None* - -Format string used for parsing or serializing timestamp values. -Default `yyyy-mm-dd hh:mm:ss[.fffffffff]`. - - - -#### *field* treatEmptyValuesAsNulls *: bool | None* *= None* - -If `True`, empty cells are parsed as `null` values. -If `False`, empty cells are parsed as empty strings. -Default `True`. - -#### NOTE -Used only for reading files. - - - -#### *field* setErrorCellsToFallbackValues *: bool | None* *= None* - -If `True`, cells containing `#N/A` value are replaced with default value for column type, -e.g. 0 for `IntegerType()`. If `False`, `#N/A` values are replaced with `null`. -Default `False`. - -#### NOTE -Used only for reading files. - - - -#### *field* usePlainNumberFormat *: bool | None* *= None* - -If `True`, read or write numeric values with plain format, without using scientific notation or rounding. -Default `False`. - - - -#### *field* inferSchema *: bool | None* *= None* - -If `True`, infer DataFrame schema based on cell content. -If `False` and no explicit DataFrame schema is passed, all columns are `StringType()`. - -#### NOTE -Used only for reading files. - - - -#### *field* workbookPassword *: SecretStr | None* *= None* - -If Excel file is encrypted, provide password to open it. - -#### NOTE -Used only for reading files. Cannot be used to write files. - - -* **Constraints:** - - **type** = string - - **writeOnly** = True - - **format** = password - -#### *field* maxRowsInMemory *: int | None* *= None* - -If set, use streaming reader and fetch only specified number of rows per iteration. -This reduces memory usage for large files. -Default `None`, which means reading the entire file content to memory. - -#### WARNING -Can be used only with `.xlsx` files, but fails on `.xls`. - -#### NOTE -Used only for reading files. - - - -#### *field* maxByteArraySize *: ByteSize | None* *= None* - -If set, overrides memory limit (in bytes) of byte array size used for reading rows from input file. -Default `0`, which means using default limit. - -See [IOUtils.setByteArrayMaxOverride](https://poi.apache.org/apidocs/5.0/org/apache/poi/util/IOUtils.html#setByteArrayMaxOverride-int-) -documentation. - -#### NOTE -Used only for reading files. - - - -#### *field* tempFileThreshold *: ByteSize | None* *= None* - -If value is greater than 0, large zip entries will be written to temporary files after reaching this threshold. -If value is 0, all zip entries will be written to temporary files. -If value is -1, no temp files will be created, which may cause errors if zip entry is larger than 2GiB. - -#### NOTE -Used only for reading files. - - - -#### *field* excerptSize *: int | None* *= None* - -If `inferSchema=True`, set number of rows to infer schema from. -Default `10`. - -#### NOTE -Used only for reading files. - - - -#### *classmethod* get_packages(spark_version: str, scala_version: str | None = None, package_version: str | None = None) → list[str] - -Get package names to be downloaded by Spark. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### WARNING -Not all combinations of Spark version and package version are supported. -See [Maven index](https://mvnrepository.com/artifact/com.crealytics/spark-excel) -and [official documentation](https://github.com/crealytics/spark-excel). - -#### Versionadded -Added in version 0.9.4. - -* **Parameters:** - **spark_version** - : Spark version in format `major.minor.patch`. - - **scala_version** - : Scala version in format `major.minor`. -
- If `None`, `spark_version` is used to determine Scala version. - - **package_version** - : Package version in format `major.minor.patch`. Default is `0.20.4`. -
- #### WARNING - Version `0.14` and below are not supported. -
- #### NOTE - It is not guaranteed that custom package versions are supported. - Tests are performed only for default version. - -### Examples - -```python -from onetl.file.format import Excel - -Excel.get_packages(spark_version="3.5.1") -Excel.get_packages(spark_version="3.5.1", scala_version="2.12") -Excel.get_packages( - spark_version="3.5.1", - scala_version="2.12", - package_version="0.20.4", -) -``` - - diff --git a/mddocs/markdown/file_df/file_formats/index.md b/mddocs/markdown/file_df/file_formats/index.md deleted file mode 100644 index a6500bdf3..000000000 --- a/mddocs/markdown/file_df/file_formats/index.md +++ /dev/null @@ -1,18 +0,0 @@ - - -# File Formats - -# File formats - -* [Avro](avro.md) -* [CSV](csv.md) -* [Excel](excel.md) -* [JSON](json.md) -* [JSONLine](jsonline.md) -* [ORC](orc.md) -* [Parquet](parquet.md) -* [XML](xml.md) - -# For developers - -* [Base interface](base.md) diff --git a/mddocs/markdown/file_df/file_formats/json.md b/mddocs/markdown/file_df/file_formats/json.md deleted file mode 100644 index fd6ee8da7..000000000 --- a/mddocs/markdown/file_df/file_formats/json.md +++ /dev/null @@ -1,414 +0,0 @@ - - -# JSON - -### *class* onetl.file.format.json.JSON(\*, multiLine: Literal[True] = True, encoding: str | None = None, lineSep: str | None = None, allowComments: bool | None = None, allowUnquotedFieldNames: bool | None = None, allowSingleQuotes: bool | None = None, allowNumericLeadingZeros: bool | None = None, allowNonNumericNumbers: bool | None = None, allowBackslashEscapingAnyCharacter: bool | None = None, allowUnquotedControlChars: bool | None = None, mode: Literal['PERMISSIVE', 'DROPMALFORMED', 'FAILFAST'] | None = None, columnNameOfCorruptRecord: ConstrainedStrValue | None = None, samplingRatio: ConstrainedFloatValue | None = None, primitivesAsString: bool | None = None, prefersDecimal: bool | None = None, dropFieldIfAllNull: bool | None = None, dateFormat: ConstrainedStrValue | None = None, timestampFormat: ConstrainedStrValue | None = None, timestampNTZFormat: ConstrainedStrValue | None = None, timeZone: ConstrainedStrValue | None = None, locale: ConstrainedStrValue | None = None, \*\*kwargs) - -JSON file format. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on [Spark JSON](https://spark.apache.org/docs/latest/sql-data-sources-json.html) file format. - -Supports reading (but **NOT** writing) files with `.json` extension with content like: - -```json -[ - {"key": "value1"}, - {"key": "value2"} -] -``` - -#### Versionadded -Added in version 0.9.0. - -### Examples - -#### NOTE -You can pass any option mentioned in -[official documentation](https://spark.apache.org/docs/latest/sql-data-sources-json.html). -**Option names should be in** `camelCase`! - -The set of supported options depends on Spark version. - -Reading files: - -```python -from onetl.file.format import JSON - -json = JSON(encoding="UTF-8") -``` - -Writing files: - -#### WARNING -Not supported. Use [`JSONLine`](jsonline.md#onetl.file.format.jsonline.JSONLine). - - - -#### *field* encoding *: str | None* *= None* - -Encoding of the JSON file. -Default `UTF-8`. - -#### NOTE -Used only for reading and writing files. - -Ignored by [`parse_column`](#onetl.file.format.json.JSON.parse_column) and [`serialize_column`](#onetl.file.format.json.JSON.serialize_column) methods. - - - -#### *field* lineSep *: str | None* *= None* - -Character used to separate lines in the JSON file. - -Defaults: -: * Try to detect for reading (`\r\n`, `\r`, `\n`) - * `\n` for writing - -#### NOTE -Used only for reading and writing files. - -Ignored by [`parse_column`](#onetl.file.format.json.JSON.parse_column) and [`serialize_column`](#onetl.file.format.json.JSON.serialize_column) methods, -as they handle each DataFrame row separately. - - - -#### *field* allowComments *: bool | None* *= None* - -If `True`, add support for C/C++/Java style comments (`//`, `/* */`). -Default `False`, meaning that JSON files should not contain comments. - -#### NOTE -Used only for reading files and [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. - - - -#### *field* allowUnquotedFieldNames *: bool | None* *= None* - -If `True`, allow JSON object field names without quotes (JavaScript-style). -Default `False`. - -#### NOTE -Used only for reading files and [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. - - - -#### *field* allowSingleQuotes *: bool | None* *= None* - -If `True`, allow JSON object field names to be wrapped with single quotes (`'`). -Default `True`. - -#### NOTE -Used only for reading files and [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. - - - -#### *field* allowNumericLeadingZeros *: bool | None* *= None* - -If `True`, allow leading zeros in numbers (e.g. `00012`). -Default `False`. - -#### NOTE -Used only for reading files and [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. - - - -#### *field* allowNonNumericNumbers *: bool | None* *= None* - -If `True`, allow numbers to contain non-numeric characters, like: -: * scientific notation (e.g. `12e10`). - * positive infinity floating point value (`Infinity`, `+Infinity`, `+INF`). - * negative infinity floating point value (`-Infinity`, `-INF`). - * Not-a-Number floating point value (`NaN`). - -Default `True`. - -#### NOTE -Used only for reading files and [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. - - - -#### *field* allowBackslashEscapingAnyCharacter *: bool | None* *= None* - -If `True`, prefix `\` can escape any character. -Default `False`. - -#### NOTE -Used only for reading files and [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. - - - -#### *field* allowUnquotedControlChars *: bool | None* *= None* - -If `True`, allow unquoted control characters (ASCII values 0-31) in strings without escaping them with `\`. -Default `False`. - -#### NOTE -Used only for reading files and [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. - - - -#### *field* mode *: Literal['PERMISSIVE', 'DROPMALFORMED', 'FAILFAST'] | None* *= None* - -How to handle parsing errors: -: * `PERMISSIVE` - set field value as `null`, move raw data to [`columnNameOfCorruptRecord`](#onetl.file.format.json.JSON.columnNameOfCorruptRecord) column. - * `DROPMALFORMED` - skip the malformed row. - * `FAILFAST` - throw an error immediately. - -Default is `PERMISSIVE`. - -#### NOTE -Used only for reading files and [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. - - - -#### *field* columnNameOfCorruptRecord *: str | None* *= None* - -Name of column to put corrupt records in. -Default is `_corrupt_record`. - -#### WARNING -If DataFrame schema is provided, this column should be added to schema explicitly: - -```python -from onetl.connection import SparkLocalFS -from onetl.file import FileDFReader -from onetl.file.format import JSON - -from pyspark.sql.types import StructType, StructField, TimestampType, StringType - -spark = ... - -schema = StructType( - [ - StructField("my_field", TimestampType()), - StructField("_corrupt_record", StringType()), # <-- important - ] -) - -json = JSON(mode="PERMISSIVE", columnNameOfCorruptRecord="_corrupt_record") - -reader = FileDFReader( - connection=connection, - format=json, - schema=schema, # < --- -) -df = reader.run(["/some/file.json"]) -``` - -#### NOTE -Used only for reading files and [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. - - -* **Constraints:** - - **minLength** = 1 - -#### *field* samplingRatio *: float | None* *= None* - -While inferring schema, read the specified fraction of file rows. -Default `1`. - -#### NOTE -Used only for reading files. Ignored by [`parse_column`](#onetl.file.format.json.JSON.parse_column) function. - - -* **Constraints:** - - **minimum** = 0 - - **maximum** = 1 - -#### *field* primitivesAsString *: bool | None* *= None* - -If `True`, infer all primitive types (string, integer, float, boolean) as strings. -Default `False`. - -#### NOTE -Used only for reading files. Ignored by [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. - - - -#### *field* prefersDecimal *: bool | None* *= None* - -If `True`, infer all floating-point values as `Decimal`. -Default `False`. - -#### NOTE -Used only for reading files. Ignored by [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. - - - -#### \_\_init_\_(\*\*kwargs) - - - -#### *field* dropFieldIfAllNull *: bool | None* *= None* - -If `True` and inferred column is always null or empty array, exclude if from DataFrame schema. -Default `False`. - -#### NOTE -Used only for reading files. Ignored by [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. - - - -#### *field* dateFormat *: str | None* *= None* - -String format for `DateType()` representation. -Default is `yyyy-MM-dd`. - - -* **Constraints:** - - **minLength** = 1 - -#### *field* timestampFormat *: str | None* *= None* - -String format for TimestampType()\` representation. -Default is `yyyy-MM-dd'T'HH:mm:ss[.SSS][XXX]`. - - -* **Constraints:** - - **minLength** = 1 - -#### *field* timestampNTZFormat *: str | None* *= None* - -String format for TimestampNTZType()\` representation. -Default is `yyyy-MM-dd'T'HH:mm:ss[.SSS]`. - -#### NOTE -Added in Spark 3.2.0 - - -* **Constraints:** - - **minLength** = 1 - -#### *field* timezone *: str | None* *= None* *(alias 'timeZone')* - -Allows to override timezone used for parsing or serializing date and timestamp values. -By default, `spark.sql.session.timeZone` is used. - - -* **Constraints:** - - **minLength** = 1 - -#### *field* locale *: str | None* *= None* - -Locale name used to parse dates and timestamps. -Default is `en-US`. - -#### NOTE -Used only for reading files and [`parse_column`](#onetl.file.format.json.JSON.parse_column) method. - - -* **Constraints:** - - **minLength** = 1 - -#### parse_column(column: str | Column, schema: StructType | ArrayType | MapType) → Column - -Parses a JSON string column to a structured Spark SQL column using Spark’s [from_json](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.functions.from_json.html) function, based on the provided schema. - -#### Versionadded -Added in version 0.11.0. - -* **Parameters:** - **column** - : The name of the column or the column object containing JSON strings/bytes to parse. - - **schema** - : The schema to apply when parsing the JSON data. This defines the structure of the output DataFrame column. -* **Returns:** - Column with deserialized data, with the same structure as the provided schema. Column name is the same as input column. - -### Examples - -```pycon ->>> from pyspark.sql.types import StructType, StructField, IntegerType, StringType ->>> from pyspark.sql.functions import decode ->>> from onetl.file.format import JSON ->>> df.show() -+----+--------------------+----------+---------+------+-----------------------+-------------+ -|key |value |topic |partition|offset|timestamp |timestampType| -+----+--------------------+----------+---------+------+-----------------------+-------------+ -|[31]|[7B 22 6E 61 6D 6...|topicJSON |0 |0 |2024-04-24 16:51:11.739|0 | -|[32]|[7B 22 6E 61 6D 6...|topicJSON |0 |1 |2024-04-24 16:51:11.749|0 | -+----+--------------------+----------+---------+------+-----------------------+-------------+ ->>> df.printSchema() -root -|-- key: binary (nullable = true) -|-- value: binary (nullable = true) -|-- topic: string (nullable = true) -|-- partition: integer (nullable = true) -|-- offset: integer (nullable = true) -|-- timestamp: timestamp (nullable = true) -|-- timestampType: integer (nullable = true) ->>> json = JSON() ->>> json_schema = StructType( -... [ -... StructField("name", StringType(), nullable=True), -... StructField("age", IntegerType(), nullable=True), -... ], -... ) ->>> parsed_df = df.select(decode("key", "UTF-8").alias("key"), json.parse_column("value", json_schema)) ->>> parsed_df.show() -+---+-----------+ -|key|value | -+---+-----------+ -|1 |{Alice, 20}| -|2 | {Bob, 25}| -+---+-----------+ ->>> parsed_df.printSchema() -root -|-- key: string (nullable = true) -|-- value: struct (nullable = true) -| |-- name: string (nullable = true) -| |-- age: integer (nullable = true) -``` - - - -#### serialize_column(column: str | Column) → Column - -Serializes a structured Spark SQL column into a JSON string column using Spark’s -[to_json](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.functions.to_json.html) function. - -#### Versionadded -Added in version 0.11.0. - -* **Parameters:** - **column** - : The name of the column or the column object containing the data to serialize to JSON format. -* **Returns:** - Column with string JSON data. Column name is the same as input column. - -### Examples - -```pycon ->>> from pyspark.sql.functions import decode ->>> from onetl.file.format import JSON ->>> df.show() -+---+-----------+ -|key|value | -+---+-----------+ -|1 |{Alice, 20}| -|2 | {Bob, 25}| -+---+-----------+ ->>> df.printSchema() -root -|-- key: string (nullable = true) -|-- value: struct (nullable = true) -| |-- name: string (nullable = true) -| |-- age: integer (nullable = true) ->>> # serializing data into JSON format ->>> json = JSON() ->>> serialized_df = df.select("key", json.serialize_column("value")) ->>> serialized_df.show(truncate=False) -+---+-------------------------+ -|key|value | -+---+-------------------------+ -| 1|{"name":"Alice","age":20}| -| 2|{"name":"Bob","age":25} | -+---+-------------------------+ ->>> serialized_df.printSchema() -root -|-- key: string (nullable = true) -|-- value: string (nullable = true) -``` - - diff --git a/mddocs/markdown/file_df/file_formats/jsonline.md b/mddocs/markdown/file_df/file_formats/jsonline.md deleted file mode 100644 index 644b66bcb..000000000 --- a/mddocs/markdown/file_df/file_formats/jsonline.md +++ /dev/null @@ -1,314 +0,0 @@ - - -# JSONLine - -### *class* onetl.file.format.jsonline.JSONLine(\*, multiLine: Literal[False] = False, encoding: str | None = None, lineSep: str | None = None, compression: str | Literal['none', 'bzip2', 'gzip', 'lz4', 'snappy', 'deflate'] | None = None, ignoreNullFields: bool | None = None, allowComments: bool | None = None, allowUnquotedFieldNames: bool | None = None, allowSingleQuotes: bool | None = None, allowNumericLeadingZeros: bool | None = None, allowNonNumericNumbers: bool | None = None, allowBackslashEscapingAnyCharacter: bool | None = None, allowUnquotedControlChars: bool | None = None, mode: Literal['PERMISSIVE', 'DROPMALFORMED', 'FAILFAST'] | None = None, columnNameOfCorruptRecord: ConstrainedStrValue | None = None, samplingRatio: ConstrainedFloatValue | None = None, primitivesAsString: bool | None = None, prefersDecimal: bool | None = None, dropFieldIfAllNull: bool | None = None, dateFormat: ConstrainedStrValue | None = None, timestampFormat: ConstrainedStrValue | None = None, timestampNTZFormat: ConstrainedStrValue | None = None, timeZone: ConstrainedStrValue | None = None, locale: ConstrainedStrValue | None = None, \*\*kwargs) - -JSONLine file format (each line of file contains a JSON object). [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on [Spark JSON](https://spark.apache.org/docs/latest/sql-data-sources-json.html) file format. - -Supports reading/writing files with `.json` extension with content like: - -```json -{"key": "value1"} -{"key": "value2"} -``` - -#### Versionadded -Added in version 0.9.0. - -### Examples - -#### NOTE -You can pass any option mentioned in -[official documentation](https://spark.apache.org/docs/latest/sql-data-sources-json.html). -**Option names should be in** `camelCase`! - -The set of supported options depends on Spark version. - -Reading files - -```py -from onetl.file.format import JSONLine - -jsonline = JSONLine(encoding="UTF-8", mode="PERMISSIVE") -``` - -Writing files - -#### WARNING -Written files have extension `.json`, not `.jsonl` or `.jsonline`. - -```python -from onetl.file.format import JSONLine - -jsonline = JSONLine(encoding="UTF-8", compression="gzip") -``` - - - -#### *field* encoding *: str | None* *= None* - -Encoding of the JSONLine files. -Default `UTF-8`. - - - -#### *field* lineSep *: str | None* *= None* - -Character used to separate lines in the JSONLine files. - -Defaults: -: * Try to detect for reading (`\r\n`, `\r`, `\n`) - * `\n` for writing. - - - -#### *field* compression *: str | Literal['none', 'bzip2', 'gzip', 'lz4', 'snappy', 'deflate'] | None* *= None* - -Compression codec of the JSONLine file. -Default `none`. - -#### NOTE -Used only for writing files. - - - -#### *field* ignoreNullFields *: bool | None* *= None* - -If `True` and field value is `null`, don’t add field into resulting object -Default is value of `spark.sql.jsonGenerator.ignoreNullFields` (`True`). - -#### NOTE -Used only for writing files. - - - -#### *field* allowComments *: bool | None* *= None* - -If `True`, add support for C/C++/Java style comments (`//`, `/* */`). -Default `False`, meaning that JSONLine files should not contain comments. - -#### NOTE -Used only for reading files. - - - -#### *field* allowUnquotedFieldNames *: bool | None* *= None* - -If `True`, allow JSON object field names without quotes (JavaScript-style). -Default `False`. - -#### NOTE -Used only for reading files. - - - -#### *field* allowSingleQuotes *: bool | None* *= None* - -If `True`, allow JSON object field names to be wrapped with single quotes (`'`). -Default `True`. - -#### NOTE -Used only for reading files. - - - -#### *field* allowNumericLeadingZeros *: bool | None* *= None* - -If `True`, allow leading zeros in numbers (e.g. `00012`). -Default `False`. - -#### NOTE -Used only for reading files. - - - -#### *field* allowNonNumericNumbers *: bool | None* *= None* - -If `True`, allow numbers to contain non-numeric characters, like: -: * scientific notation (e.g. `12e10`). - * positive infinity floating point value (`Infinity`, `+Infinity`, `+INF`). - * negative infinity floating point value (`-Infinity`, `-INF`). - * Not-a-Number floating point value (`NaN`). - -Default `True`. - -#### NOTE -Used only for reading files. - - - -#### *field* allowBackslashEscapingAnyCharacter *: bool | None* *= None* - -If `True`, prefix `\` can escape any character. -Default `False`. - -#### NOTE -Used only for reading files. - - - -#### *field* allowUnquotedControlChars *: bool | None* *= None* - -If `True`, allow unquoted control characters (ASCII values 0-31) in strings without escaping them with `\`. -Default `False`. - -#### NOTE -Used only for reading files. - - - -#### *field* mode *: Literal['PERMISSIVE', 'DROPMALFORMED', 'FAILFAST'] | None* *= None* - -How to handle parsing errors: -: * `PERMISSIVE` - set field value as `null`, move raw data to [`columnNameOfCorruptRecord`](#onetl.file.format.jsonline.JSONLine.columnNameOfCorruptRecord) column. - * `DROPMALFORMED` - skip the malformed row. - * `FAILFAST` - throw an error immediately. - -Default is `PERMISSIVE`. - -#### NOTE -Used only for reading files. - - - -#### *field* columnNameOfCorruptRecord *: str | None* *= None* - -Name of column to put corrupt records in. -Default is `_corrupt_record`. - -#### WARNING -If DataFrame schema is provided, this column should be added to schema explicitly: - -```python -from onetl.connection import SparkLocalFS -from onetl.file import FileDFReader -from onetl.file.format import JSONLine - -from pyspark.sql.types import StructType, StructField, TimestampType, StringType - -spark = ... - -schema = StructType( - [ - StructField("my_field", TimestampType()), - StructField("_corrupt_record", StringType()), # <-- important - ] -) - -jsonline = JSONLine(mode="PERMISSIVE", columnNameOfCorruptRecord="_corrupt_record") - -reader = FileDFReader( - connection=connection, - format=jsonline, - schema=schema, # < --- -) -df = reader.run(["/some/file.jsonl"]) -``` - -#### NOTE -Used only for reading files. - - -* **Constraints:** - - **minLength** = 1 - -#### \_\_init_\_(\*\*kwargs) - - - -#### *field* samplingRatio *: float | None* *= None* - -While inferring schema, read the specified fraction of file rows. -Default `1`. - -#### NOTE -Used only for reading files. - - -* **Constraints:** - - **minimum** = 0 - - **maximum** = 1 - -#### *field* primitivesAsString *: bool | None* *= None* - -If `True`, infer all primitive types (string, integer, float, boolean) as strings. -Default `False`. - -#### NOTE -Used only for reading files. - - - -#### *field* prefersDecimal *: bool | None* *= None* - -If `True`, infer all floating-point values as `Decimal`. -Default `False`. - -#### NOTE -Used only for reading files. - - - -#### *field* dropFieldIfAllNull *: bool | None* *= None* - -If `True` and inferred column is always null or empty array, exclude if from DataFrame schema. -Default `False`. - -#### NOTE -Used only for reading files. - - - -#### *field* dateFormat *: str | None* *= None* - -String format for `DateType()` representation. -Default is `yyyy-MM-dd`. - - -* **Constraints:** - - **minLength** = 1 - -#### *field* timestampFormat *: str | None* *= None* - -String format for TimestampType()\` representation. -Default is `yyyy-MM-dd'T'HH:mm:ss[.SSS][XXX]`. - - -* **Constraints:** - - **minLength** = 1 - -#### *field* timestampNTZFormat *: str | None* *= None* - -String format for TimestampNTZType()\` representation. -Default is `yyyy-MM-dd'T'HH:mm:ss[.SSS]`. - -#### NOTE -Added in Spark 3.2.0 - - -* **Constraints:** - - **minLength** = 1 - -#### *field* timezone *: str | None* *= None* *(alias 'timeZone')* - -Allows to override timezone used for parsing or serializing date and timestamp values. -By default, `spark.sql.session.timeZone` is used. - - -* **Constraints:** - - **minLength** = 1 - -#### *field* locale *: str | None* *= None* - -Locale name used to parse dates and timestamps. -Default is `en-US`. - -#### NOTE -Used only for reading files. - - -* **Constraints:** - - **minLength** = 1 diff --git a/mddocs/markdown/file_df/file_formats/orc.md b/mddocs/markdown/file_df/file_formats/orc.md deleted file mode 100644 index d922a7e47..000000000 --- a/mddocs/markdown/file_df/file_formats/orc.md +++ /dev/null @@ -1,80 +0,0 @@ - - -# ORC - -### *class* onetl.file.format.orc.ORC(\*, mergeSchema: bool | None = None, compression: str | Literal['uncompressed', 'snappy', 'zlib', 'lzo', 'zstd', 'lz4'] | None = None, \*\*kwargs) - -ORC file format (columnar). [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on [Spark ORC Files](https://spark.apache.org/docs/latest/sql-data-sources-orc.html) file format. - -Supports reading/writing files with `.orc` extension. - -#### Versionadded -Added in version 0.9.0. - -### Examples - -#### NOTE -You can pass any option mentioned in -[official documentation](https://spark.apache.org/docs/latest/sql-data-sources-orc.html). -**Option names should be in** `camelCase`! - -The set of supported options depends on Spark version. - -You may also set options mentioned [orc-java documentation](https://orc.apache.org/docs/core-java-config.html). -They are prefixed with `orc.` with dots in names, so instead of calling constructor `ORC(orc.option=True)` (invalid in Python) -you should call method `ORC.parse({"orc.option": True})`. - -Reading files - -```py -from onetl.file.format import ORC - -orc = ORC(mergeSchema=True) -``` - -Writing files - -```python -from onetl.file.format import ORC - -orc = ORC.parse( - { - "compression": "snappy", - # Enable Bloom filter for columns 'id' and 'name' - "orc.bloom.filter.columns": "id,name", - # Set Bloom filter false positive probability - "orc.bloom.filter.fpp": 0.01, - # Do not use dictionary for 'highly_selective_column' - "orc.column.encoding.direct": "highly_selective_column", - # other options - } -) -``` - - - -#### *field* mergeSchema *: bool | None* *= None* - -Merge schemas of all ORC files being read into a single schema. -By default, Spark config option `spark.sql.orc.mergeSchema` value is used (`False`). - -#### NOTE -Used only for reading files. - - - -#### *field* compression *: str | Literal['uncompressed', 'snappy', 'zlib', 'lzo', 'zstd', 'lz4'] | None* *= None* - -Compression codec of the ORC files. -By default, Spark config option `spark.sql.orc.compression.codec` value is used (`snappy`). - -#### NOTE -Used only for writing files. - - - -#### \_\_init_\_(\*\*kwargs) - - diff --git a/mddocs/markdown/file_df/file_formats/parquet.md b/mddocs/markdown/file_df/file_formats/parquet.md deleted file mode 100644 index 6ff8aa6e0..000000000 --- a/mddocs/markdown/file_df/file_formats/parquet.md +++ /dev/null @@ -1,79 +0,0 @@ - - -# Parquet - -### *class* onetl.file.format.parquet.Parquet(\*, mergeSchema: bool | None = None, compression: str | Literal['uncompressed', 'snappy', 'gzip', 'lzo', 'brotli', 'lz4', 'lz4raw', 'zstd'] | None = None, \*\*kwargs) - -Parquet file format (columnar). [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on [Spark Parquet Files](https://spark.apache.org/docs/latest/sql-data-sources-parquet.html) file format. - -Supports reading/writing files with `.parquet` extension. - -#### Versionadded -Added in version 0.9.0. - -### Examples - -#### NOTE -You can pass any option mentioned in -[official documentation](https://spark.apache.org/docs/latest/sql-data-sources-parquet.html). -**Option names should be in** `camelCase`! - -The set of supported options depends on Spark version. - -You may also set options mentioned [parquet-hadoop documentation](https://github.com/apache/parquet-java/blob/master/parquet-hadoop/README.md). -They are prefixed with `parquet.` with dots in names, so instead of calling constructor `Parquet(parquet.option=True)` (invalid in Python) -you should call method `Parquet.parse({"parquet.option": True})`. - -Reading files - -```py -from onetl.file.format import Parquet - -parquet = Parquet(mergeSchema=True) -``` - -Writing files - -```py -from onetl.file.format import Parquet - -parquet = Parquet.parse( - { - "compression": "snappy", - # Enable Bloom filter for columns 'id' and 'name' - "parquet.bloom.filter.enabled#id": True, - "parquet.bloom.filter.enabled#name": True, - # Set expected number of distinct values for column 'id' - "parquet.bloom.filter.expected.ndv#id": 10_000_000, - # other options - } -) -``` - - - -#### *field* mergeSchema *: bool | None* *= None* - -Merge schemas of all Parquet files being read into a single schema. -By default, Spark config option `spark.sql.parquet.mergeSchema` value is used (`false`). - -#### NOTE -Used only for reading files. - - - -#### *field* compression *: str | Literal['uncompressed', 'snappy', 'gzip', 'lzo', 'brotli', 'lz4', 'lz4raw', 'zstd'] | None* *= None* - -Compression codec of the Parquet files. -By default, Spark config option `spark.sql.parquet.compression.codec` value is used (`snappy`). - -#### NOTE -Used only for writing files. - - - -#### \_\_init_\_(\*\*kwargs) - - diff --git a/mddocs/markdown/file_df/file_formats/xml.md b/mddocs/markdown/file_df/file_formats/xml.md deleted file mode 100644 index 1da916bc1..000000000 --- a/mddocs/markdown/file_df/file_formats/xml.md +++ /dev/null @@ -1,440 +0,0 @@ - - -# XML - -### *class* onetl.file.format.xml.XML(\*, rowTag: str, rootTag: str | None = None, compression: str | Literal['bzip2', 'gzip', 'lz4', 'snappy'] | None = None, timestampFormat: str | None = None, dateFormat: str | None = None, timezone: str | None = None, nullValue: str | None = None, ignoreSurroundingSpaces: bool | None = None, mode: Literal['PERMISSIVE', 'DROPMALFORMED', 'FAILFAST'] | None = None, columnNameOfCorruptRecord: str | None = None, inferSchema: bool | None = None, samplingRatio: ConstrainedFloatValue | None = None, charset: str | None = None, valueTag: str | None = None, attributePrefix: str | None = None, excludeAttribute: bool | None = None, wildcardColName: str | None = None, ignoreNamespace: bool | None = None, rowValidationXSDPath: str | None = None, declaration: str | None = None, arrayElementName: str | None = None, \*\*kwargs) - -XML file format. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -Based on [Databricks Spark XML](https://github.com/databricks/spark-xml) file format. - -Supports reading/writing files with `.xml` extension. - -#### Versionadded -Added in version 0.9.5. - -### Version compatibility - -* Spark versions: 3.2.x - 3.5.x -* Java versions: 8 - 20 - -See [official documentation](https://github.com/databricks/spark-xml). - -### Examples - -#### NOTE -You can pass any option mentioned in -[official documentation](https://github.com/databricks/spark-xml). -**Option names should be in** `camelCase`! - -The set of supported options depends on `spark-xml` version. - -Reading files - -```py -from onetl.file.format import XML -from pyspark.sql import SparkSession - -# Create Spark session with XML package loaded -maven_packages = XML.get_packages(spark_version="3.5.5") -spark = ( - SparkSession.builder.appName("spark-app-name") - .config("spark.jars.packages", ",".join(maven_packages)) - .getOrCreate() -) - -xml = XML(rowTag="item", mode="PERMISSIVE") -``` - -Writing files - -#### WARNING -Due to [bug](https://github.com/databricks/spark-xml/issues/664) written files currently does not have `.xml` extension. - -```python -# Create Spark session with XML package loaded -spark = ... - -from onetl.file.format import XML - -xml = XML(rowTag="item", rootTag="data", compression="gzip") -``` - - - -#### *field* row_tag *: str* *[Required]* *(alias 'rowTag')* - -XML tag that encloses each row in XML. Required. - - - -#### *field* rootTag *: str | None* *= None* - -XML tag that encloses content of all DataFrame. Default is `ROWS`. - -#### NOTE -Used only for writing files. - - - -#### *field* compression *: str | Literal['bzip2', 'gzip', 'lz4', 'snappy'] | None* *= None* - -Compression codec. By default no compression is used. - -#### NOTE -Used only for writing files. - - - -#### *field* timestampFormat *: str | None* *= None* - -Format string used for parsing or serializing timestamp values. -By default, ISO 8601 format is used (`yyyy-MM-ddTHH:mm:ss.SSSZ`). - - - -#### *field* dateFormat *: str | None* *= None* - -Format string used for parsing or serializing date values. -By default, ISO 8601 format is used (`yyyy-MM-dd`). - - - -#### *field* nullValue *: str | None* *= None* - -String value used to represent null. Default is `null` string. - - - -#### *field* ignoreSurroundingSpaces *: bool | None* *= None* - -If `True`, trim surrounding spaces while parsing values. Default `false`. - -#### NOTE -Used only for reading files or by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. - - - -#### *field* mode *: Literal['PERMISSIVE', 'DROPMALFORMED', 'FAILFAST'] | None* *= None* - -How to handle parsing errors: -: * `PERMISSIVE` - set field value as `null`, move raw data to [`columnNameOfCorruptRecord`](#onetl.file.format.xml.XML.columnNameOfCorruptRecord) column. - * `DROPMALFORMED` - skip the malformed row. - * `FAILFAST` - throw an error immediately. - -Default is `PERMISSIVE`. - -#### NOTE -Used only for reading files or by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. - - - -#### *field* columnNameOfCorruptRecord *: str | None* *= None* - -Name of DataFrame column there corrupted row is stored with `mode=PERMISSIVE`. - -#### WARNING -If DataFrame schema is provided, this column should be added to schema explicitly: - -```python -from onetl.connection import SparkLocalFS -from onetl.file import FileDFReader -from onetl.file.format import XML - -from pyspark.sql.types import StructType, StructField, TImestampType, StringType - -spark = ... -schema = StructType( - [ - StructField("my_field", TimestampType()), - StructField("_corrupt_record", StringType()), # <-- important - ] -) -xml = XML(rowTag="item", columnNameOfCorruptRecord="_corrupt_record") - -reader = FileDFReader( - connection=connection, - format=xml, - schema=schema, # < --- -) -df = reader.run(["/some/file.xml"]) -``` - -#### NOTE -Used only for reading files or by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. - - - -#### *field* inferSchema *: bool | None* *= None* - -If `True`, try to infer the input schema by reading a sample of the file (see [`samplingRatio`](#onetl.file.format.xml.XML.samplingRatio)). -Default `False` which means that all parsed columns will be `StringType()`. - -#### NOTE -Used only for reading files. Ignored by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. - - - -#### *field* samplingRatio *: float | None* *= None* - -For `inferSchema=True`, read the specified fraction of rows to infer the schema. -Default `1`. - -#### NOTE -Used only for reading files. Ignored by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. - - -* **Constraints:** - - **minimum** = 0 - - **maximum** = 1 - -#### *field* charset *: str | None* *= None* - -File encoding. Default is `UTF-8` - -#### NOTE -Used only for reading files or by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. - - - -#### *field* valueTag *: str | None* *= None* - -Value used to replace missing values while parsing attributes like ``. -Default `_VALUE`. - -#### NOTE -Used only for reading files or by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. - - - -#### *field* attributePrefix *: str | None* *= None* - -While parsing tags containing attributes like ``, attributes are stored as -DataFrame schema columns with specified prefix, e.g. `_attr`. -Default `_`. - -#### NOTE -Used only for reading files or by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. - - - -#### *field* excludeAttribute *: bool | None* *= None* - -If `True`, exclude attributes while parsing tags like ``. -Default `false`. - -#### NOTE -Used only for reading files or by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. - - - -#### *field* wildcardColName *: str | None* *= None* - -Name of column or columns which should be preserved as raw XML string, and not parsed. - -#### WARNING -If DataFrame schema is provided, this column should be added to schema explicitly. -See [`columnNameOfCorruptRecord`](#onetl.file.format.xml.XML.columnNameOfCorruptRecord) example. - -#### NOTE -Used only for reading files or by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. - - - -#### *field* ignoreNamespace *: bool | None* *= None* - -If `True`, all namespaces like `` will be ignored and treated as just ``. -Default `False`. - -#### NOTE -Used only for reading files or by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. - - - -#### *field* rowValidationXSDPath *: str | None* *= None* - -Path to XSD file which should be used to validate each row. -If row does not match XSD, it will be treated as error, behavior depends on [`mode`](#onetl.file.format.xml.XML.mode) value. - -Default is no validation. - -#### NOTE -If Spark session is created with `master=yarn` or `master=k8s`, XSD -file should be accessible from all Spark nodes. This can be achieved by calling: - -```python -spark.addFile("/path/to/file.xsd") -``` - -And then by passing `rowValidationXSDPath=file.xsd` (relative path). - -#### NOTE -Used only for reading files or by [`parse_column`](#onetl.file.format.xml.XML.parse_column) function. - - - -#### *field* declaration *: str | None* *= None* - -Content of declaration. -Default is `version="1.0" encoding="UTF-8" standalone="yes"`. - -#### NOTE -Used only for writing files. - - - -#### *field* arrayElementName *: str | None* *= None* - -If DataFrame column is `ArrayType`, its content will be written to XML -inside `...` tag. -Default is `item`. - -#### NOTE -Used only for writing files. - - - -#### *classmethod* get_packages(spark_version: str, scala_version: str | None = None, package_version: str | None = None) → list[str] - -Get package names to be downloaded by Spark. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -#### Versionadded -Added in version 0.9.5. - -* **Parameters:** - **spark_version** - : Spark version in format `major.minor.patch`. - - **scala_version** - : Scala version in format `major.minor`. -
- If `None`, `spark_version` is used to determine Scala version. - - **package_version** - : Package version in format `major.minor.patch`. Default is `0.18.0`. -
- See [Maven index](https://mvnrepository.com/artifact/com.databricks/spark-xml) - for list of available versions. -
- #### WARNING - Version `0.13` and below are not supported. -
- #### NOTE - It is not guaranteed that custom package versions are supported. - Tests are performed only for default version. - -### Examples - -```python -from onetl.file.format import XML - -XML.get_packages(spark_version="3.5.5") -XML.get_packages(spark_version="3.5.5", scala_version="2.12") -XML.get_packages( - spark_version="3.5.5", - scala_version="2.12", - package_version="0.18.0", -) -``` - - - -#### parse_column(column: str | Column, schema: StructType) → Column - -Parses an XML string column into a structured Spark SQL column using the `from_xml` function -provided by the [Databricks Spark XML library](https://github.com/databricks/spark-xml#pyspark-notes) -based on the provided schema. - -#### NOTE -This method assumes that the `spark-xml` package is installed: [`get_packages`](#onetl.file.format.xml.XML.get_packages). - -#### NOTE -This method parses each DataFrame row individually. Therefore, for a specific column, each row must contain exactly one occurrence of the `rowTag` specified. -If your XML data includes a root tag that encapsulates multiple row tags, you can adjust the schema to use an `ArrayType` to keep all child elements under the single root. - -```xml - - Book OneAuthor A - Book TwoAuthor B - -``` - -And the corresponding schema in Spark using an `ArrayType`: - -```python -from pyspark.sql.types import StructType, StructField, ArrayType, StringType -from onetl.file.format import XML - -# each DataFrame row has exactly one tag -xml = XML(rowTag="books") -# each tag have multiple tags, so using ArrayType for such field -schema = StructType( - [ - StructField( - "book", - ArrayType( - StructType( - [ - StructField("title", StringType(), nullable=True), - StructField("author", StringType(), nullable=True), - ], - ), - ), - nullable=True, - ), - ], -) -``` - -#### Versionadded -Added in version 0.11.0. - -* **Parameters:** - **column** - : The name of the column or the column object containing XML strings/bytes to parse. - - **schema** - : The schema to apply when parsing the XML data. This defines the structure of the output DataFrame column. -* **Returns:** - Column with deserialized data, with the same structure as the provided schema. Column name is the same as input column. - -### Examples - -```pycon ->>> from pyspark.sql.types import StructType, StructField, IntegerType, StringType ->>> from onetl.file.format import XML ->>> df.show() -+--+------------------------------------------------+ -|id|value | -+--+------------------------------------------------+ -|1 |Alice20| -|2 |Bob25 | -+--+------------------------------------------------+ ->>> df.printSchema() -root -|-- id: integer (nullable = true) -|-- value: string (nullable = true) ->>> xml = XML(rowTag="person") ->>> xml_schema = StructType( -... [ -... StructField("name", StringType(), nullable=True), -... StructField("age", IntegerType(), nullable=True), -... ], -... ) ->>> parsed_df = df.select("key", xml.parse_column("value", xml_schema)) ->>> parsed_df.show() -+--+-----------+ -|id|value | -+--+-----------+ -|1 |{Alice, 20}| -|2 | {Bob, 25}| -+--+-----------+ ->>> parsed_df.printSchema() -root -|-- id: integer (nullable = true) -|-- value: struct (nullable = true) -| |-- name: string (nullable = true) -| |-- age: integer (nullable = true) -``` - - diff --git a/mddocs/markdown/file_df/index.md b/mddocs/markdown/file_df/index.md deleted file mode 100644 index c225567d6..000000000 --- a/mddocs/markdown/file_df/index.md +++ /dev/null @@ -1,7 +0,0 @@ - - - File DataFrame classes - -* [FileDF Reader](file_df_reader/index.md) -* [FileDF Writer](file_df_writer/index.md) -* [File Formats](file_formats/index.md) diff --git a/mddocs/markdown/hooks/design.md b/mddocs/markdown/hooks/design.md deleted file mode 100644 index 041f206ee..000000000 --- a/mddocs/markdown/hooks/design.md +++ /dev/null @@ -1,642 +0,0 @@ - - -# High level design - -## What are hooks? - -Hook mechanism is a part of onETL which allows to inject some additional behavior into -existing methods of (almost) any class. - -### Features - -Hooks mechanism allows to: - -* Inspect and validate input arguments and output results of method call -* Access, modify or replace method call result (but NOT input arguments) -* Wrap method calls with a context manager and catch raised exceptions - -Hooks can be placed into [Plugins](../plugins.md#plugins), allowing to modify onETL behavior by installing some additional package. - -### Limitations - -* Hooks can be bound to methods of a class only (not functions). -* Only methods decorated with [@slot decorator](slot.md#slot-decorator) implement hooks mechanism. These class and methods are marked as [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html). -* Hooks can be bound to public methods only. - -## Terms - -* [@slot decorator](slot.md#slot-decorator) - method of a class with a special decorator -* `Callback` - function which implements some additional logic which modifies slot behavior -* [@hook decorator](hook.md#hook-decorator) - wrapper around callback which stores hook state, priority and some useful methods -* `Hooks mechanism` - calling `Slot()` will call all enabled hooks which are bound to the slot. Implemented by [@support_hooks decorator](support_hooks.md#support-hooks-decorator). - -## How to implement hooks? - -### TL;DR - -```python -from onetl.hooks import support_hooks, slot, hook - - -@support_hooks # enabling hook mechanism for the class -class MyClass: - def __init__(self, data): - self.data = data - - # this is slot - @slot - def method(self, arg): - pass - - -@MyClass.method.bind # bound hook to the slot -@hook # this is hook -def callback(obj, arg): # this is callback - print(obj.data, arg) - - -obj = MyClass(1) -obj.method(2) # will call callback(obj, 1) - -# prints "1 2" -``` - -#### Define a slot - -* Create a class with a method: - -```python -class MyClass: - def __init__(self, data): - self.data = data - - def method(self, arg): - return self.data, arg -``` - -* Add [@slot decorator](slot.md#slot-decorator) to the method: - -```python -from onetl.hooks import support_hooks, slot, hook - - -class MyClass: - @slot - def method(self, arg): - return self.data, arg -``` - -If method has other decorators like `@classmethod` or `@staticmethod`, `@slot` should be placed on the top: - -```python -from onetl.hooks import support_hooks, slot, hook - - -class MyClass: - @slot - @classmethod - def class_method(cls, arg): - return cls, arg - - @slot - @staticmethod - def static_method(arg): - return arg -``` - -* Add [@support_hooks decorator](support_hooks.md#support-hooks-decorator) to the class: - -```python -from onetl.hooks import support_hooks, slot, hook - - -@support_hooks -class MyClass: - @slot - def method(self, arg): - return self.data, arg -``` - -Slot is created. - -#### Define a callback - -Define some function (a.k.a callback): - -```python -def callback(self, arg): - print(self.data, arg) -``` - -It should have signature *compatible* with `MyClass.method`. *Compatible* does not mean *exactly the same* - -for example, you can rename positional arguments: - -```python -def callback(obj, arg): - print(obj.data, arg) -``` - -Use `*args` and `**kwargs` to omit arguments you don’t care about: - -```python -def callback(obj, *args, **kwargs): - print(obj.data, args, kwargs) -``` - -There is also an argument `method_name` which has a special meaning - -the method name which the callback is bound to is passed into this argument: - -```python -def callback(obj, *args, method_name: str, **kwargs): - print(obj.data, args, method_name, kwargs) -``` - -#### NOTE -`method_name` should always be a keyword argument, **NOT** positional. - -#### WARNING -If callback signature is not compatible with slot signature, an exception will be raised, -but **ONLY** while slot is called. - -#### Define a hook - -Add [@hook decorator](hook.md#hook-decorator) to create a hook from your callback: - -```python -@hook -def callback(obj, arg): - print(obj.data, arg) -``` - -You can pass more options to the `@hook` decorator, like state or priority. -See decorator documentation for more details. - -#### Bind hook to the slot - -Use `Slot.bind` method to bind hook to the slot: - -```python -@MyClass.method.bind -@hook -def callback(obj, arg): - print(obj, arg) -``` - -You can bind more than one hook to the same slot, and bind same hook to multiple slots: - -```python -@MyClass.method1.bind -@MyClass.method2.bind -@hook -def callback1(obj, arg): - "Will be called by both MyClass.method1 and MyClass.method2" - - -@MyClass.method1.bind -@hook -def callback2(obj, arg): - "Will be called by MyClass.method1 too" -``` - -## How hooks are called? - -### General - -Just call the method decorated by `@slot` to trigger the hook: - -```python -obj = MyClass(1) -obj.method(2) # will call callback(obj, 2) - -# prints "1 2" -``` - -There are some special callback types that has a slightly different behavior. - -### Context managers - -`@hook` decorator can be placed on a context manager class: - -```python -@hook -class ContextManager: - def __init__(self, obj, arg): - self.obj = obj - self.arg = arg - - def __enter__(self): - # do something on enter - print(obj.data, arg) - return self - - def __exit__(self, exc_type, exc_value, traceback): - # do something on exit - return False -``` - -Context manager is entered while calling the `Slot()`, and exited then the call is finished. - -If present, method `process_result` has a special meaning - -it can receive `MyClass.method` call result, and also modify/replace it: - -```python -@hook -class ContextManager: - def __init__(self, obj, arg): - self.obj = obj - self.arg = arg - - def __enter__(self): - # do something on enter - print(obj.data, arg) - return self - - def __exit__(self, exc_type, exc_value, traceback): - # do something on exit - return False - - def process_result(self, result): - # do something with method call result - return modified(result) -``` - -See examples below for more information. - -### Generator function - -`@hook` decorator can be placed on a generator function: - -```python -@hook -def callback(obj, arg): - print(obj.data, arg) - # this is called before original method body - - yield # method is called here - - # this is called after original method body -``` - -It is converted to a context manager, in the same manner as -[contextlib.contextmanager](https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager). - -Generator body can be wrapped with `try..except..finally` to catch exceptions: - -```python -@hook -def callback(obj, arg): - print(obj.data, arg) - - try: - # this is called before original method body - - yield # method is called here - except Exception as e: - process_exception(a) - finally: - # this is called after original method body - finalizer() -``` - -There is also a special syntax which allows generator to access and modify/replace method call result: - -```default -@hook -def callback(obj, arg): - original_result = yield # method is called here - - new_result = do_something(original_result) - - yield new_result # modify/replace the result -``` - -### Calling hooks in details - -* The callback will be called with the same arguments as the original method. - * If slot is a regular method: - ```python - callback_result = callback(self, *args, **kwargs) - ``` - - Here `self` is a class instance (`obj`). - * If slot is a class method: - ```python - callback_result = callback(cls, *args, **kwargs) - ``` - - Here `cls` is the class itself (`MyClass`). - * If slot is a static method: - ```python - callback_result = callback(*args, **kwargs) - ``` - - Neither object not class are passed to the callback in this case. -* If `callback_result` is a context manager, enter the context. Context manager can catch all the exceptions raised. - > If there are multiple hooks bound the the slot, every context manager will be entered. -* Then call the original method wrapped by `@slot`: - ```python - original_result = method(*args, **kwargs) - ``` -* Process `original_result`: - * If `callback_result` object has method `process_result`, or is a generator wrapped with `@hook`, call it: - ```python - new_result = callback_result.process_result(original_result) - ``` - * Otherwise set `new_result = callback_result`. - * If there are multiple hooks bound the the method, pass `new_result` through the chain: - ```python - new_result = callback1_result.process_result(original_result) - new_result = callback2_result.process_result(new_result or original_result) - new_result = callback3_result.process_result(new_result or original_result) - ``` -* Finally return: - ```python - return new_result or original_result - ``` - - All `None` values are ignored on every step above. -* Exit all the context managers entered during the slot call. - -### Hooks priority - -Hooks are executed in the following order: - -1. Parent class slot + [`FIRST`](hook.md#onetl.hooks.hook.HookPriority.FIRST) -2. Inherited class slot + [`FIRST`](hook.md#onetl.hooks.hook.HookPriority.FIRST) -3. Parent class slot + [`NORMAL`](hook.md#onetl.hooks.hook.HookPriority.NORMAL) -4. Inherited class slot + [`NORMAL`](hook.md#onetl.hooks.hook.HookPriority.NORMAL) -5. Parent class slot + [`LAST`](hook.md#onetl.hooks.hook.HookPriority.LAST) -6. Inherited class slot + [`LAST`](hook.md#onetl.hooks.hook.HookPriority.LAST) - -Hooks with the same priority and inheritance will be executed in the same order they were registered (`Slot.bind` call). - -#### NOTE -Calls of `super()` inside inherited class methods does not trigger hooks call. -Hooks are triggered only if method is called explicitly. - -This allow to wrap with a hook the entire slot call without influencing its internal logic. - -### Hook types - -Here are several examples of using hooks. These types are not exceptional, they can be mixed - for example, -hook can both modify method result and catch exceptions. - -#### Before hook - -Can be used for inspecting or validating input args of the original function: - -```python -@hook -def before1(obj, arg): - print(obj, arg) - # original method is called after exiting this function - - -@hook -def before2(obj, arg): - if arg == 1: - raise ValueError("arg=1 is not allowed") - return None # return None is the same as no return statement -``` - -Executed before calling the original method wrapped by `@slot`. -If hook raises an exception, method will not be called at all. - -#### After hook - -Can be used for performing some actions after original method was successfully executed: - -```python -@hook -def after1(obj, arg): - yield # original method is called here - print(obj, arg) - - -@hook -def after2(obj, arg): - yield None # yielding None is the same as empty yield - if arg == 1: - raise ValueError("arg=1 is not allowed") -``` - -If original method raises an exception, the block of code after `yield` will not be called. - -#### Context hook - -Can be used for catching and handling some exceptions, or to determine that there was no exception during slot call: - -Generator syntax - -```py -# This is just the same as using @contextlib.contextmanager - -@hook -def context_generator(obj, arg): - try: - yield # original method is called here - print(obj, arg) # <-- this line will not be called if method raised an exception - except SomeException as e: - magic(e) - finally: - finalizer() -``` - -Context manager syntax - -```py -@hook -class ContextManager: - def __init__(self, obj, args): - self.obj = obj - self.args = args - - def __enter__(self): - return self - - # original method is called between __enter__ and __exit__ - - def __exit__(self, exc_type, exc_value, traceback): - result = False - if exc_type is not None and isinstance(exc_value, SomeException): - magic(exc_value) - result = True # suppress exception - else: - print(self.obj, self.arg) - finalizer() - return result -``` - -#### NOTE -Contexts are exited in the reverse order of the hook calls. -So if some hook raised an exception, it will be passed into the previous hook, not the next one. - -It is recommended to specify the proper priority for the hook, e.g. [`FIRST`](hook.md#onetl.hooks.hook.HookPriority.FIRST) - -#### Replacing result hook - -Replaces the output result of the original method. - -Can be used for delegating some implementation details for third-party extensions. -See [Hive](../connection/db_connection/hive/index.md#hive) and [HDFS](../connection/file_connection/hdfs/index.md#hdfs) as an example. - -```python -@hook -def replace1(obj, arg): - result = arg + 10 # any non-None return result - - # original method call result is ignored, output will always be arg + 10 - return result - - -@hook -def replace2(obj, arg): - yield arg + 10 # same as above -``` - -#### NOTE -If there are multiple hooks bound to the same slot, the result of last hook will be used. -It is recommended to specify the proper priority for the hook, e.g. [`LAST`](hook.md#onetl.hooks.hook.HookPriority.LAST) - -#### Accessing result hook - -Can access output result of the original method and inspect or validate it: - -Generator syntax - -```py -@hook -def access_result(obj, arg): - result = yield # original method is called here, and result can be used in the hook - print(result) - yield # does not modify result -``` - -Context manager syntax - -```py -@hook -class ModifiesResult: - def __init__(self, obj, args): - self.obj = obj - self.args = args - - def __enter__(self): - return self - - # original method is called between __enter__ and __exit__ - # result is passed into process_result method of context manager, if present - - def process_result(self, result): - print(result) # result can be used in the hook - return None # does not modify result. same as no return statement in the method - - def __exit__(self, exc_type, exc_value, traceback): - return False -``` - -#### Modifying result hook - -Can access output result of the original method, and return the modified one: - -Generator syntax - -```py -@hook -def modifies_result(obj, arg): - result = yield # original method is called here, and result can be used in the hook - yield result + 10 # modify output result. None values are ignored -``` - -Context manager syntax - -```py -@hook -class ModifiesResult: - def __init__(self, obj, args): - self.obj = obj - self.args = args - - def __enter__(self): - return self - - # original method is called between __enter__ and __exit__ - # result is passed into process_result method of context manager, if present - - def process_result(self, result): - print(result) # result can be used in the hook - return result + 10 # modify output result. None values are ignored - - def __exit__(self, exc_type, exc_value, traceback): - return False -``` - -#### NOTE -If there are multiple hooks bound to the same slot, the result of last hook will be used. -It is recommended to specify the proper priority for the hook, e.g. [`LAST`](hook.md#onetl.hooks.hook.HookPriority.LAST) - -## How to enable/disable hooks? - -You can enable/disable/temporary disable hooks on 4 different levels: - -* Manage global hooks state (level 1): - > * [`onetl.hooks.hooks_state.stop_all_hooks`](global_state.md#onetl.hooks.hooks_state.stop_all_hooks) - > * [`onetl.hooks.hooks_state.resume_all_hooks`](global_state.md#onetl.hooks.hooks_state.resume_all_hooks) - > * [`onetl.hooks.hooks_state.skip_all_hooks`](global_state.md#onetl.hooks.hooks_state.skip_all_hooks) -* Manage all hooks bound to a specific class (level 2): - > * [`onetl.hooks.support_hooks.suspend_hooks`](support_hooks.md#onetl.hooks.support_hooks.suspend_hooks) - > * [`onetl.hooks.support_hooks.resume_hooks`](support_hooks.md#onetl.hooks.support_hooks.resume_hooks) - > * [`onetl.hooks.support_hooks.skip_hooks`](support_hooks.md#onetl.hooks.support_hooks.skip_hooks) -* Manage all hooks bound to a specific slot (level 3): - > * [`onetl.hooks.slot.Slot.suspend_hooks`](slot.md#onetl.hooks.slot.Slot.suspend_hooks) - > * [`onetl.hooks.slot.Slot.resume_hooks`](slot.md#onetl.hooks.slot.Slot.resume_hooks) - > * [`onetl.hooks.slot.Slot.skip_hooks`](slot.md#onetl.hooks.slot.Slot.skip_hooks) -* Manage state of a specific hook (level 4): - > * [`onetl.hooks.hook.Hook.enable`](hook.md#onetl.hooks.hook.Hook.enable) - > * [`onetl.hooks.hook.Hook.disable`](hook.md#onetl.hooks.hook.Hook.disable) - -More details in the documentation above. - -#### NOTE -All of these levels are independent. - -Calling `stop` on the level 1 has higher priority than level 2, and so on. -But calling `resume` on the level 1 does not automatically resume hooks stopped in the level 2, -they should be resumed explicitly. - -## How to see logs of the hook mechanism? - -Hooks registration emits logs with `DEBUG` level: - -```python -from onetl.logs import setup_logging - -setup_logging() -``` - -```text -DEBUG |onETL| Registered hook 'mymodule.callback1' for 'MyClass.method' (enabled=True, priority=HookPriority.NORMAL) -DEBUG |onETL| Registered hook 'mymodule.callback2' for 'MyClass.method' (enabled=True, priority=HookPriority.NORMAL) -DEBUG |onETL| Registered hook 'mymodule.callback3' for 'MyClass.method' (enabled=False, priority=HookPriority.NORMAL) -``` - -But most of logs are emitted with even lower level `NOTICE`, to make output less verbose: - -```python -from onetl.logs import NOTICE, setup_logging - -setup_logging(level=NOTICE) -``` - -```default -NOTICE |Hooks| 2 hooks registered for 'MyClass.method' -NOTICE |Hooks| Calling hook 'mymodule.callback1' (1/2) -NOTICE |Hooks| Hook is finished with returning non-None result -NOTICE |Hooks| Calling hook 'mymodule.callback2' (2/2) -NOTICE |Hooks| This is a context manager, entering ... -NOTICE |Hooks| Calling original method 'MyClass.method' -NOTICE |Hooks| Method call is finished -NOTICE |Hooks| Method call result (*NOT* None) will be replaced with result of hook 'mymodule.callback1' -NOTICE |Hooks| Passing result to 'process_result' method of context manager 'mymodule.callback2' -NOTICE |Hooks| Method call result (*NOT* None) is modified by hook! -``` diff --git a/mddocs/markdown/hooks/global_state.md b/mddocs/markdown/hooks/global_state.md deleted file mode 100644 index 537389dd1..000000000 --- a/mddocs/markdown/hooks/global_state.md +++ /dev/null @@ -1,101 +0,0 @@ - - -# Hooks global state - -| [`skip_all_hooks`](#onetl.hooks.hooks_state.skip_all_hooks)() | Temporary stop all onETL hooks. | -|-------------------------------------------------------------------|-----------------------------------| -| [`stop_all_hooks`](#onetl.hooks.hooks_state.stop_all_hooks)() | Stop all hooks for all classes. | -| [`resume_all_hooks`](#onetl.hooks.hooks_state.resume_all_hooks)() | Resume all onETL hooks. | - -### onetl.hooks.hooks_state.skip_all_hooks() - -Temporary stop all onETL hooks. Designed to be used as context manager or decorator. - -#### NOTE -If hooks were stopped by [`stop_all_hooks`](#onetl.hooks.hooks_state.stop_all_hooks), they will not be resumed -after exiting the context/decorated function. -You should call [`resume_all_hooks`](#onetl.hooks.hooks_state.resume_all_hooks) explicitly. - -#### Versionadded -Added in version 0.7.0. - -### Examples - -Context manager syntax - -```py -from onetl.hooks import skip_all_hooks - -# hooks are enabled - -with skip_all_hooks(): - # hooks are stopped here - ... - -# hook state is restored -``` - -Decorator syntax - -```py -from onetl.hooks import skip_all_hooks - -# hooks are enabled - -@skip_all_hooks() -def main(): - # hooks are stopped here - ... - -main() -``` - - - -### onetl.hooks.hooks_state.stop_all_hooks() → None - -Stop all hooks for all classes. - -#### Versionadded -Added in version 0.7.0. - -### Examples - -```python -from onetl.hooks import stop_all_hooks - -# hooks are executed - -stop_all_hooks() - -# all hooks are stopped now -``` - - - -### onetl.hooks.hooks_state.resume_all_hooks() → None - -Resume all onETL hooks. - -#### NOTE -This function does not enable hooks which were disabled by [`onetl.hooks.hook.Hook.disable`](hook.md#onetl.hooks.hook.Hook.disable), -or stopped by [`onetl.hooks.support_hooks.suspend_hooks`](support_hooks.md#onetl.hooks.support_hooks.suspend_hooks). - -#### Versionadded -Added in version 0.7.0. - -### Examples - -```python -from onetl.hooks import resume_all_hooks, stop_all_hooks - -stop_all_hooks() - -# hooks are stopped - -resume_all_hooks() - -# all hooks are executed now -``` - - diff --git a/mddocs/markdown/hooks/hook.md b/mddocs/markdown/hooks/hook.md deleted file mode 100644 index 402f79688..000000000 --- a/mddocs/markdown/hooks/hook.md +++ /dev/null @@ -1,223 +0,0 @@ - - -# `@hook` decorator - -| [`hook`](#onetl.hooks.hook.hook)([inp, enabled, priority]) | Initialize hook from callable/context manager. | -|-------------------------------------------------------------------------------|--------------------------------------------------| -| [`HookPriority`](#onetl.hooks.hook.HookPriority)(value[, names, module, ...]) | Hook priority enum. | -| [`Hook`](#onetl.hooks.hook.Hook)(callback[, enabled, priority]) | Hook representation. | -| [`Hook.enable`](#onetl.hooks.hook.Hook.enable)() | Enable the hook. | -| [`Hook.disable`](#onetl.hooks.hook.Hook.disable)() | Disable the hook. | -| [`Hook.skip`](#onetl.hooks.hook.Hook.skip)() | Temporary disable the hook. | - -### @onetl.hooks.hook.hook(inp: Callable[[...], T] | None = None, enabled: bool = True, priority: [HookPriority](#onetl.hooks.hook.HookPriority) = HookPriority.NORMAL) - -Initialize hook from callable/context manager. - -#### Versionadded -Added in version 0.7.0. - -### Examples - -Decorate a function or generator - -```py -from onetl.hooks import hook, HookPriority - -@hook -def some_func(*args, **kwargs): - ... - -@hook(enabled=True, priority=HookPriority.FIRST) -def another_func(*args, **kwargs): - ... -``` - -Decorate a context manager - -```py -from onetl.hooks import hook, HookPriority - -@hook -class SimpleContextManager: - def __init__(self, *args, **kwargs): - ... - - def __enter__(self): - ... - return self - - def __exit__(self, exc_type, exc_value, traceback): - ... - return False - -@hook(enabled=True, priority=HookPriority.FIRST) -class ContextManagerWithProcessResult: - def __init__(self, *args, **kwargs): - ... - - def __enter__(self): - ... - return self - - def __exit__(self, exc_type, exc_value, traceback): - ... - return False - - def process_result(self, result): - # special method to handle method result call - return modify(result) - - ... -``` - - - -### *class* onetl.hooks.hook.HookPriority(value, names=None, \*, module=None, qualname=None, type=None, start=1, boundary=None) - -Hook priority enum. - -All hooks within the same priority are executed in the same order they were registered. - -#### Versionadded -Added in version 0.7.0. - - - -#### FIRST *= -1* - -Hooks with this priority will run first. - - - -#### NORMAL *= 0* - -Hooks with this priority will run after [`FIRST`](#onetl.hooks.hook.HookPriority.FIRST) but before [`LAST`](#onetl.hooks.hook.HookPriority.LAST). - - - -#### LAST *= 1* - -Hooks with this priority will run last. - - - -### *class* onetl.hooks.hook.Hook(callback: Callable[[...], T], enabled: bool = True, priority: [HookPriority](#onetl.hooks.hook.HookPriority) = HookPriority.NORMAL) - -Hook representation. - -#### Versionadded -Added in version 0.7.0. - -* **Parameters:** - **callback** - : Some callable object which will be wrapped into a Hook, like function or ContextManager class. - - **enabled** - : Will hook be executed or not. Useful for debugging. - - **priority** - : Changes hooks priority, see `HookPriority` documentation. - -### Examples - -```python -from onetl.hooks.hook import Hook, HookPriority - -def some_func(*args, **kwargs): ... - -hook = Hook(callback=some_func, enabled=True, priority=HookPriority.FIRST) -``` - - - -#### enable() - -Enable the hook. - -#### Versionadded -Added in version 0.7.0. - -### Examples - -```pycon ->>> def func1(): ... ->>> hook = Hook(callback=func1, enabled=False) ->>> hook.enabled -False ->>> hook.enable() ->>> hook.enabled -True -``` - - - -#### disable() - -Disable the hook. - -#### Versionadded -Added in version 0.7.0. - -### Examples - -```pycon ->>> def func1(): ... ->>> hook = Hook(callback=func1, enabled=True) ->>> hook.enabled -True ->>> hook.disable() ->>> hook.enabled -False -``` - - - -#### skip() - -Temporary disable the hook. - -#### NOTE -If hook was created with `enabled=False`, or was disabled by [`disable`](#onetl.hooks.hook.Hook.disable), -its state will left intact after exiting the context. - -You should call [`enable`](#onetl.hooks.hook.Hook.enable) explicitly to change its state. - -#### Versionadded -Added in version 0.7.0. - -### Examples - -Context manager syntax - -```pycon ->>> def func1(): ... ->>> hook = Hook(callback=func1, enabled=True) ->>> hook.enabled -True ->>> with hook.skip(): -... print(hook.enabled) -False ->>> # hook state is restored as it was before entering the context manager ->>> hook.enabled -True -``` - -Decorator syntax - -```pycon ->>> def func1(): ... ->>> hook = Hook(callback=func1, enabled=True) ->>> hook.enabled -True ->>> @hook.skip() -... def hook_disabled(): -... print(hook.enabled) ->>> hook_disabled() -False ->>> # hook state is restored as it was before entering the context manager ->>> hook.enabled -True -``` - - diff --git a/mddocs/markdown/hooks/index.md b/mddocs/markdown/hooks/index.md deleted file mode 100644 index 8e1ba7fb9..000000000 --- a/mddocs/markdown/hooks/index.md +++ /dev/null @@ -1,14 +0,0 @@ - - -# Hooks - -#### Versionadded -Added in version 0.6.0. - -# Hooks - -* [High level design](design.md) -* [@hook decorator](hook.md) -* [@slot decorator](slot.md) -* [@support_hooks decorator](support_hooks.md) -* [Hooks global state](global_state.md) diff --git a/mddocs/markdown/hooks/slot.md b/mddocs/markdown/hooks/slot.md deleted file mode 100644 index 936ee3859..000000000 --- a/mddocs/markdown/hooks/slot.md +++ /dev/null @@ -1,261 +0,0 @@ - - -# `@slot` decorator - -| [`slot`](#onetl.hooks.slot.slot)(method) | Decorator which enables hooks functionality on a specific class method. | -|----------------------------------------------------------------|------------------------------------------------------------------------------------------------------| -| [`Slot`](#onetl.hooks.slot.Slot) | Protocol which is implemented by a method after applying [`slot`](#onetl.hooks.slot.slot) decorator. | -| [`Slot.bind`](#onetl.hooks.slot.Slot.bind)([inp]) | Bind a hook to the slot. | -| [`Slot.skip_hooks`](#onetl.hooks.slot.Slot.skip_hooks)() | Context manager which temporary stops all the hooks bound to the slot. | -| [`Slot.suspend_hooks`](#onetl.hooks.slot.Slot.suspend_hooks)() | Stop all the hooks bound to the slot. | -| [`Slot.resume_hooks`](#onetl.hooks.slot.Slot.resume_hooks)() | Resume all hooks bound to the slot. | - -### @onetl.hooks.slot.slot - -Decorator which enables hooks functionality on a specific class method. - -Decorated methods get additional nested methods: - -> * [`onetl.hooks.slot.Slot.bind`](#onetl.hooks.slot.Slot.bind) -> * [`onetl.hooks.slot.Slot.suspend_hooks`](#onetl.hooks.slot.Slot.suspend_hooks) -> * [`onetl.hooks.slot.Slot.resume_hooks`](#onetl.hooks.slot.Slot.resume_hooks) -> * [`onetl.hooks.slot.Slot.skip_hooks`](#onetl.hooks.slot.Slot.skip_hooks) - -#### NOTE -Supported method types are: - -> * Regular methods -> * `@classmethod` -> * `@staticmethod` - -It is not allowed to use this decorator over `_private` and `__protected` methods and `@property`. -But is allowed to use on `__dunder__` methods, like `__init__`. - -#### Versionadded -Added in version 0.7.0. - -### Examples - -```python -from onetl.hooks import support_hooks, slot, hook - -@support_hooks -class MyClass: - @slot - def my_method(self, arg): ... - - @slot # decorator should be on top of all other decorators - @classmethod - def class_method(cls): ... - - @slot # decorator should be on top of all other decorators - @staticmethod - def static_method(arg): ... - -@MyClass.my_method.bind -@hook -def callback1(self, arg): ... - -@MyClass.class_method.bind -@hook -def callback2(cls): ... - -@MyClass.static_method.bind -@hook -def callback3(arg): ... - -obj = MyClass() -obj.my_method(1) # will execute callback1(obj, 1) -MyClass.class_method(2) # will execute callback2(MyClass, 2) -MyClass.static_method(3) # will execute callback3(3) -``` - - - -### *protocol* onetl.hooks.slot.Slot - -Protocol which is implemented by a method after applying [`slot`](#onetl.hooks.slot.slot) decorator. - -#### Versionadded -Added in version 0.7.0. - - - -Classes that implement this protocol must have the following methods / attributes: - -#### \_\_call_\_(\*args, \*\*kwargs) - -Call self as a function. - - - -#### *property* \_\_hooks_\_ *: HookCollection* - -Collection of hooks bound to the slot - - - -#### bind(inp=None) - -Bind a hook to the slot. - -See [High level design](design.md#hooks-design) for more details. - -#### Versionadded -Added in version 0.7.0. - -### Examples - -```python -from onetl.hooks import support_hooks, slot, hook, HookPriority - -@support_hooks -class MyClass: - @slot - def method(self, arg): - pass - -@MyClass.method.bind -@hook -def callable(self, arg): - if arg == "some": - do_something() - -@MyClass.method.bind -@hook(priority=HookPriority.FIRST, enabled=True) -def another_callable(self, arg): - if arg == "another": - raise NotAllowed() - -obj = MyClass() -obj.method(1) # will call both callable(obj, 1) and another_callable(obj, 1) -``` - - - -#### resume_hooks() - -Resume all hooks bound to the slot. - -#### NOTE -If hook is disabled by [`onetl.hooks.hook.Hook.disable`](hook.md#onetl.hooks.hook.Hook.disable), it will stay disabled. -You should call [`onetl.hooks.hook.Hook.enable`](hook.md#onetl.hooks.hook.Hook.enable) explicitly. - -### Examples - -```python -from onetl.hooks.hook import hook, support_hooks, slot - -@support_hooks -class MyClass: - @slot - def my_method(self, arg): ... - -@MyClass.my_method.bind -@hook -def callback1(self, arg): ... - -obj = MyClass() -obj.my_method(1) # will call callback1(obj, 1) - -MyClass.my_method.suspend_hooks() -obj.my_method(1) # will NOT call callback1 - -MyClass.my_method.resume_hooks() -obj.my_method(2) # will call callback1(obj, 2) -``` - - - -#### skip_hooks() → ContextManager[None] - -Context manager which temporary stops all the hooks bound to the slot. - -#### NOTE -If hooks were stopped by [`suspend_hooks`](#onetl.hooks.slot.Slot.suspend_hooks), they will not be resumed -after exiting the context/decorated function. -You should call [`resume_hooks`](#onetl.hooks.slot.Slot.resume_hooks) explicitly. - -### Examples - -Context manager syntax - -```py -from onetl.hooks.hook import hook, support_hooks, slot - -@support_hooks -class MyClass: - @slot - def my_method(self, arg): - ... - -@MyClass.my_method.bind -@hook -def callback1(self, arg): - ... - -obj = MyClass() -obj.my_method(1) # will call callback1(obj, 1) - -with MyClass.my_method.skip_hooks(): - obj.my_method(1) # will NOT call callback1 - -obj.my_method(2) # will call callback1(obj, 2) -``` - -Decorator syntax - -```py -from onetl.hooks.hook import hook, support_hooks, slot - -@support_hooks -class MyClass: - @slot - def my_method(self, arg): - ... - -@MyClass.my_method.bind -@hook -def callback1(self, arg): - ... - -@MyClass.my_method.skip_hooks() -def method_without_hooks(obj, arg): - obj.my_method(arg) - -obj = MyClass() -obj.my_method(1) # will call callback1(obj, 1) - -method_without_hooks(obj, 1) # will NOT call callback1 - -obj.my_method(2) # will call callback1(obj, 2) -``` - - - -#### suspend_hooks() - -Stop all the hooks bound to the slot. - -### Examples - -```python -from onetl.hooks.hook import hook, support_hooks, slot - -@support_hooks -class MyClass: - @slot - def my_method(self, arg): ... - -@MyClass.my_method.bind -@hook -def callback1(self, arg): ... - -obj = MyClass() -obj.my_method(1) # will call callback1(obj, 1) - -MyClass.my_method.suspend_hooks() -obj.my_method(1) # will NOT call callback1 -``` - - diff --git a/mddocs/markdown/hooks/support_hooks.md b/mddocs/markdown/hooks/support_hooks.md deleted file mode 100644 index b876a3e52..000000000 --- a/mddocs/markdown/hooks/support_hooks.md +++ /dev/null @@ -1,161 +0,0 @@ - - -# `@support_hooks` decorator - -| [`support_hooks`](#onetl.hooks.support_hooks.support_hooks)(cls) | Decorator which adds hooks functionality to a specific class. | -|--------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------| -| [`skip_hooks`](#onetl.hooks.support_hooks.skip_hooks)(cls) | Context manager (and decorator) which temporary disables hooks for all the methods of a specific class. | -| [`suspend_hooks`](#onetl.hooks.support_hooks.suspend_hooks)(cls) | Disables all hooks for all the methods of a specific class. | -| [`resume_hooks`](#onetl.hooks.support_hooks.resume_hooks)(cls) | Enables all hooks for all the methods of a specific class. | - -### @onetl.hooks.support_hooks.support_hooks - -Decorator which adds hooks functionality to a specific class. - -Only methods decorated with `slot` can be used for connecting hooks. - -Adds [`skip_hooks`](#onetl.hooks.support_hooks.skip_hooks), [`suspend_hooks`](#onetl.hooks.support_hooks.suspend_hooks) and [`resume_hooks`](#onetl.hooks.support_hooks.resume_hooks) to the class. - -#### Versionadded -Added in version 0.7.0. - -### Examples - -```python -from onetl.hooks.hook import support_hooks, slot - -@support_hooks -class MyClass: - @slot - def my_method(self, arg): ... - -@MyClass.my_method.hook -def callback(self, arg): ... - -MyClass().my_method() # will execute callback function -``` - - - -### onetl.hooks.support_hooks.skip_hooks(cls: type) - -Context manager (and decorator) which temporary disables hooks for all the methods of a specific class. - -### Examples - -Context manager syntax - -```py -@support_hooks -class MyClass: - @slot - def my_method(self, arg): - ... - -@MyClass.my_method.hook -def callback(self, arg): - ... - -obj = MyClass() -obj.my_method(1) # will execute callback(obj, 1) - -with MyClass.skip_hooks(): - obj.my_method() # will NOT execute callback - -# running outside the context restores previous behavior -obj.my_method(2) # will execute callback(obj, 2) -``` - -Decorator syntax - -```py -@support_hooks -class MyClass: - @slot - def my_method(self, arg): - ... - -@MyClass.my_method.hook -def callback(self, arg): - ... - -def with_hook_enabled(): - obj = MyClass() - obj.my_method(1) - -with_hook_enabled() # will execute callback(obj, 1) - -@MyClass.skip_hooks() -def with_all_hooks_disabled(): - obj = MyClass() - obj.my_method(1) - -with_all_hooks_disabled() # will NOT execute callback function - -# running outside a decorated function restores previous behavior -obj = MyClass() -obj.my_method(2) # will execute callback(obj, 2) -``` - -#### Versionadded -Added in version 0.7.0. - - - -### onetl.hooks.support_hooks.suspend_hooks(cls: type) → None - -Disables all hooks for all the methods of a specific class. - -### Examples - -```python -@support_hooks -class MyClass: - @slot - def my_method(self, arg): ... - -@MyClass.my_method.hook -def callback(self, arg): ... - -obj = MyClass() -obj.my_method(1) # will execute callback(obj, 1) - -MyClass.suspend_hooks() - -obj.my_method(2) # will NOT execute callback -``` - -#### Versionadded -Added in version 0.7.0. - - - -### onetl.hooks.support_hooks.resume_hooks(cls: type) → None - -Enables all hooks for all the methods of a specific class. - -### Examples - -```python -@support_hooks -class MyClass: - @slot - def my_method(self, arg): ... - -@MyClass.my_method.hook -def callback(self, arg): ... - -obj = MyClass() - -MyClass.suspend_hooks() -obj.my_method(1) # will NOT execute callback - -MyClass.resume_hooks() - -obj.my_method(2) # will execute callback(obj, 2) -``` - -#### Versionadded -Added in version 0.7.0. - - diff --git a/mddocs/markdown/hwm_store/index.md b/mddocs/markdown/hwm_store/index.md deleted file mode 100644 index c59cc0096..000000000 --- a/mddocs/markdown/hwm_store/index.md +++ /dev/null @@ -1,9 +0,0 @@ - - -# HWM - -Since onETL v0.10.0, the `HWMStore` and `HWM` classes have been moved to a separate library [etl-entities](https://etl-entities.readthedocs.io/en/stable/). - -The only class was left intact is [YAMLHWMStore](yaml_hwm_store.md#yaml-hwm-store), **which is default** in onETL. - -Other known implementation is [HorizonHWMStore](https://horizon-hwm-store.readthedocs.io/). diff --git a/mddocs/markdown/hwm_store/yaml_hwm_store.md b/mddocs/markdown/hwm_store/yaml_hwm_store.md deleted file mode 100644 index 425a7e030..000000000 --- a/mddocs/markdown/hwm_store/yaml_hwm_store.md +++ /dev/null @@ -1,161 +0,0 @@ - - -# YAMLHWMStore - -### *class* onetl.hwm.store.yaml_hwm_store.YAMLHWMStore(\*, path: LocalPath = LocalPosixPath('/home/sattar/.local/share/onETL/yml_hwm_store'), encoding: str = 'utf-8') - -YAML **local store** for HWM values. Used as default HWM store. [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html) - -* **Parameters:** - **path** - : Folder name there HWM value files will be stored. -
- Default: - * `~/.local/share/onETL/yml_hwm_store` on Linux - * `C:\Documents and Settings\\Application Data\oneTools\onETL\yml_hwm_store` on Windows - * `~/Library/Application Support/onETL/yml_hwm_store` on MacOS - - **encoding** - : Encoding of files with HWM value - -### Examples - -Default parameters - -```python -from onetl.connection import Hive, Postgres -from onetl.db import DBReader, DBWriter -from onetl.strategy import IncrementalStrategy -from onetl.hwm.store import YAMLHWMStore - -postgres = Postgres(...) -hive = Hive(...) - -reader = DBReader( - connection=postgres, - source="public.mydata", - columns=["id", "data"], - hwm=DBReader.AutoDetectHWM(name="some_unique_name", expression="id"), -) - -writer = DBWriter(connection=hive, target="db.newtable") - -with YAMLHWMStore(): - with IncrementalStrategy(): - df = reader.run() - writer.run(df) - -# will create file -# "~/.local/share/onETL/id__public.mydata__postgres_postgres.domain.com_5432__myprocess__myhostname.yml" -# with encoding="utf-8" and save a serialized HWM values to this file -``` - -With all options - -```python -with YAMLHWMStore(path="/my/store", encoding="utf-8"): - with IncrementalStrategy(): - df = reader.run() - writer.run(df) - -# will create file -# "/my/store/id__public.mydata__postgres_postgres.domain.com_5432__myprocess__myhostname.yml" -# with encoding="utf-8" and save a serialized HWM values to this file -``` - -File content example: - -```yaml -- column: - name: id - partition: {} - modified_time: '2023-02-11T17:10:49.659019' - process: - dag: '' - host: myhostname - name: myprocess - task: '' - source: - db: public - instance: postgres://postgres.domain.com:5432/target_database - name: mydata - type: int - value: '1500' -- column: - name: id - partition: {} - modified_time: '2023-02-11T16:00:31.962150' - process: - dag: '' - host: myhostname - name: myprocess - task: '' - source: - db: public - instance: postgres://postgres.domain.com:5432/target_database - name: mydata - type: int - value: '1000' -``` - - - -#### \_\_enter_\_() - -HWM store context manager. - -Enter this context to use this HWM store instance as current one (instead default). - -### Examples - -```pycon ->>> from etl_entities.hwm_store import HWMStoreStackManager ->>> with SomeHWMStore(...) as hwm_store: -... print(HWMStoreStackManager.get_current()) -SomeHWMStore(...) ->>> HWMStoreStackManager.get_current() -DefaultHWMStore() -``` - - - -#### get_hwm(name: str) → HWM | None - -Get HWM by name from HWM store. - -* **Parameters:** - **name** - : HWM unique name -* **Returns:** - `HWM` - : HWM object, if it exists in HWM store, or None - -### Examples - -```python -hwm = hwm_store.get_hwm(hwm_unique_name) -``` - - - -#### set_hwm(hwm: HWM) → LocalPath - -Save HWM object to HWM Store. - -* **Parameters:** - **hwm** - : HWM object -* **Returns:** - Any - : HWM location, like URL of file path. Result type is implementation-specific. - -### Examples - -```python -from etl_entities.hwm import ColumnIntHWM - -hwm = ColumnIntHWM(name=..., value=...) -hwm_location = hwm_store.set_hwm(hwm) -``` - - diff --git a/mddocs/markdown/index.md b/mddocs/markdown/index.md deleted file mode 100644 index 436e5eb85..000000000 --- a/mddocs/markdown/index.md +++ /dev/null @@ -1,59 +0,0 @@ - - -# onETL - -[![Repo status - Active](https://www.repostatus.org/badges/latest/active.svg)](https://github.com/MobileTeleSystems/onetl) [![PyPI - Latest Release](https://img.shields.io/pypi/v/onetl)](https://pypi.org/project/onetl/) [![PyPI - License](https://img.shields.io/pypi/l/onetl.svg)](https://github.com/MobileTeleSystems/onetl/blob/develop/LICENSE.txt) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/onetl.svg)](https://pypi.org/project/onetl/) [![PyPI - Downloads](https://img.shields.io/pypi/dm/onetl)](https://pypi.org/project/onetl/) -[![Documentation - ReadTheDocs](https://readthedocs.org/projects/onetl/badge/?version=stable)](https://onetl.readthedocs.io/) [![Github Actions - latest CI build status](https://github.com/MobileTeleSystems/onetl/workflows/Tests/badge.svg)](https://github.com/MobileTeleSystems/onetl/actions) [![Test coverage - percent](https://codecov.io/gh/MobileTeleSystems/onetl/branch/develop/graph/badge.svg?token=RIO8URKNZJ)](https://codecov.io/gh/MobileTeleSystems/onetl) [![pre-commit.ci - status](https://results.pre-commit.ci/badge/github/MobileTeleSystems/onetl/develop.svg)](https://results.pre-commit.ci/latest/github/MobileTeleSystems/onetl/develop) - -![onETL logo](_static/logo_wide.svg) - -## What is onETL? - -Python ETL/ELT library powered by [Apache Spark](https://spark.apache.org/) & other open-source tools. - -## Goals - -* Provide unified classes to extract data from (**E**) & load data to (**L**) various stores. -* Provides [Spark DataFrame API](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.DataFrame.html) for performing transformations (**T**) in terms of *ETL*. -* Provide direct assess to database, allowing to execute SQL queries, as well as DDL, DML, and call functions/procedures. This can be used for building up *ELT* pipelines. -* Support different [read strategies](https://onetl.readthedocs.io/en/stable/strategy/index.html) for incremental and batch data fetching. -* Provide [hooks](https://onetl.readthedocs.io/en/stable/hooks/index.html) & [plugins](https://onetl.readthedocs.io/en/stable/plugins.html) mechanism for altering behavior of internal classes. - -## Non-goals - -* onETL is not a Spark replacement. It just provides additional functionality that Spark does not have, and improves UX for end users. -* onETL is not a framework, as it does not have requirements to project structure, naming, the way of running ETL/ELT processes, configuration, etc. All of that should be implemented in some other tool. -* onETL is deliberately developed without any integration with scheduling software like Apache Airflow. All integrations should be implemented as separated tools. -* Only batch operations, no streaming. For streaming prefer [Apache Flink](https://flink.apache.org/). - -## Requirements - -* **Python 3.7 - 3.13** -* PySpark 2.3.x - 3.5.x (depends on used connector) -* Java 8+ (required by Spark, see below) -* Kerberos libs & GCC (required by `Hive`, `HDFS` and `SparkHDFS` connectors) - -## Supported storages - -| Type | Storage | Powered by | -|--------------------|-----------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------| -| Database | Clickhouse | Apache Spark [JDBC Data Source](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html) | -| MSSQL | | | -| MySQL | | | -| Postgres | | | -| Oracle | | | -| Teradata | | | -| Hive | Apache Spark [Hive integration](https://spark.apache.org/docs/latest/sql-data-sources-hive-tables.html) | | -| Kafka | Apache Spark [Kafka integration](https://spark.apache.org/docs/latest/structured-streaming-kafka-integration.html) | | -| Greenplum | VMware [Greenplum Spark connector](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/index.html) | | -| MongoDB | [MongoDB Spark connector](https://www.mongodb.com/docs/spark-connector/current) | | -| File | HDFS | [HDFS Python client](https://pypi.org/project/hdfs/) | -| S3 | [minio-py client](https://pypi.org/project/minio/) | | -| SFTP | [Paramiko library](https://pypi.org/project/paramiko/) | | -| FTP | [FTPUtil library](https://pypi.org/project/ftputil/) | | -| FTPS | | | -| WebDAV | [WebdavClient3 library](https://pypi.org/project/webdavclient3/) | | -| Samba | [pysmb library](https://pypi.org/project/pysmb/) | | -| Files as DataFrame | SparkLocalFS | Apache Spark [File Data Source](https://spark.apache.org/docs/latest/sql-data-sources-generic-options.html) | -| SparkHDFS | | | -| SparkS3 | [Hadoop AWS](https://hadoop.apache.org/docs/current3/hadoop-aws/tools/hadoop-aws/index.html) library | | diff --git a/mddocs/markdown/install/files.md b/mddocs/markdown/install/files.md deleted file mode 100644 index 60c747462..000000000 --- a/mddocs/markdown/install/files.md +++ /dev/null @@ -1,20 +0,0 @@ - - -# File connections - -All File (but not *FileDF*) connection classes (`FTP`, `SFTP`, `HDFS` and so on) requires specific Python clients to be installed. - -Each client can be installed explicitly by passing connector name (in lowercase) to `extras`: - -```bash -pip install onetl[ftp] # specific connector -pip install onetl[ftp,ftps,sftp,hdfs,s3,webdav,samba] # multiple connectors -``` - -To install all file connectors at once you can pass `files` to `extras`: - -```bash -pip install onetl[files] -``` - -**Otherwise class import will fail.** diff --git a/mddocs/markdown/install/full.md b/mddocs/markdown/install/full.md deleted file mode 100644 index 889f6c401..000000000 --- a/mddocs/markdown/install/full.md +++ /dev/null @@ -1,15 +0,0 @@ - - -# Full bundle - -To install all connectors and dependencies, you can pass `all` into `extras`: - -```bash -pip install onetl[all] - -# this is just the same as -pip install onetl[spark,files,kerberos] -``` - -#### WARNING -This method consumes a lot of disk space, and requires for Java & Kerberos libraries to be installed into your OS. diff --git a/mddocs/markdown/install/index.md b/mddocs/markdown/install/index.md deleted file mode 100644 index 1a5fb63cc..000000000 --- a/mddocs/markdown/install/index.md +++ /dev/null @@ -1,33 +0,0 @@ - - -# How to install - -Base `onetl` package contains: - -* `DBReader`, `DBWriter` and related classes -* `FileDownloader`, `FileUploader`, `FileMover` and related classes, like file filters & limits -* `FileDFReader`, `FileDFWriter` and related classes, like file formats -* Read Strategies & HWM classes -* Plugins support - -It can be installed via: - -```bash -pip install onetl -``` - -#### WARNING -This method does NOT include any connections. - -This method is recommended for use in third-party libraries which require for `onetl` to be installed, -but do not use its connection classes. - -## Installation in details - -## How to install - -* [How to install]() -* [Spark](spark.md) -* [File connections](files.md) -* [Kerberos support](kerberos.md) -* [Full bundle](full.md) diff --git a/mddocs/markdown/install/kerberos.md b/mddocs/markdown/install/kerberos.md deleted file mode 100644 index 1826124c2..000000000 --- a/mddocs/markdown/install/kerberos.md +++ /dev/null @@ -1,32 +0,0 @@ - - -# Kerberos support - -Most of Hadoop instances set up with Kerberos support, -so some connections require additional setup to work properly. - -* `HDFS` - Uses [requests-kerberos](https://pypi.org/project/requests-kerberos/) and - [GSSApi](https://pypi.org/project/gssapi/) for authentication. - It also uses `kinit` executable to generate Kerberos ticket. -* `Hive` and `SparkHDFS` - require Kerberos ticket to exist before creating Spark session. - -So you need to install OS packages with: - -* `krb5` libs -* Headers for `krb5` -* `gcc` or other compiler for C sources - -The exact installation instruction depends on your OS, here are some examples: - -```bash -apt install libkrb5-dev krb5-user gcc # Debian-based -dnf install krb5-devel krb5-libs krb5-workstation gcc # CentOS, OracleLinux -``` - -Also you should pass `kerberos` to `extras` to install required Python packages: - -```bash -pip install onetl[kerberos] -``` diff --git a/mddocs/markdown/install/spark.md b/mddocs/markdown/install/spark.md deleted file mode 100644 index 914f71611..000000000 --- a/mddocs/markdown/install/spark.md +++ /dev/null @@ -1,342 +0,0 @@ - - -# Spark - -All DB connection classes (`Clickhouse`, `Greenplum`, `Hive` and others) -and all FileDF connection classes (`SparkHDFS`, `SparkLocalFS`, `SparkS3`) -require Spark to be installed. - -## Installing Java - -Firstly, you should install JDK. The exact installation instruction depends on your OS, here are some examples: - -```bash -yum install java-1.8.0-openjdk-devel # CentOS 7 + Spark 2 -dnf install java-11-openjdk-devel # CentOS 8 + Spark 3 -apt-get install openjdk-11-jdk # Debian-based + Spark 3 -``` - - - -### Compatibility matrix - -| Spark | Python | Java | Scala | -|-----------------------------------------------------------|------------|------------|---------| -| [2.3.x](https://spark.apache.org/docs/2.3.1/#downloading) | 3.7 only | 8 only | 2.11 | -| [2.4.x](https://spark.apache.org/docs/2.4.8/#downloading) | 3.7 only | 8 only | 2.11 | -| [3.2.x](https://spark.apache.org/docs/3.2.4/#downloading) | 3.7 - 3.10 | 8u201 - 11 | 2.12 | -| [3.3.x](https://spark.apache.org/docs/3.3.4/#downloading) | 3.7 - 3.12 | 8u201 - 17 | 2.12 | -| [3.4.x](https://spark.apache.org/docs/3.4.4/#downloading) | 3.7 - 3.12 | 8u362 - 20 | 2.12 | -| [3.5.x](https://spark.apache.org/docs/3.5.5/#downloading) | 3.8 - 3.13 | 8u371 - 20 | 2.12 | - -## Installing PySpark - -Then you should install PySpark via passing `spark` to `extras`: - -```bash -pip install onetl[spark] # install latest PySpark -``` - -or install PySpark explicitly: - -```bash -pip install onetl pyspark==3.5.5 # install a specific PySpark version -``` - -or inject PySpark to `sys.path` in some other way BEFORE creating a class instance. -**Otherwise connection object cannot be created.** - - - -## Injecting Java packages - -Some DB and FileDF connection classes require specific packages to be inserted to `CLASSPATH` of Spark session, -like JDBC drivers. - -This is usually done by setting up `spark.jars.packages` option while creating Spark session: - -```python -# here is a list of packages to be downloaded: -maven_packages = ( - Greenplum.get_packages(spark_version="3.2") - + MySQL.get_packages() - + Teradata.get_packages() -) - -spark = ( - SparkSession.builder.config("spark.app.name", "onetl") - .config("spark.jars.packages", ",".join(maven_packages)) - .getOrCreate() -) -``` - -Spark automatically resolves package and all its dependencies, download them and inject to Spark session -(both driver and all executors). - -This requires internet access, because package metadata and `.jar` files are fetched from [Maven Repository](https://mvnrepository.com/). - -But sometimes it is required to: - -* Install package without direct internet access (isolated network) -* Install package which is not available in Maven - -There are several ways to do that. - -### Using `spark.jars` - -The most simple solution, but this requires to store raw `.jar` files somewhere on filesystem or web server. - -* Download `package.jar` files (it’s usually something like `some-package_1.0.0.jar`). Local file name does not matter, but it should be unique. -* (For `spark.submit.deployMode=cluster`) place downloaded files to HDFS or deploy to any HTTP web server serving static files. See [official documentation](https://spark.apache.org/docs/latest/submitting-applications.html#advanced-dependency-management) for more details. -* Create Spark session with passing `.jar` absolute file path to `spark.jars` Spark config option: - -for spark.submit.deployMode=client (default) - -```py -jar_files = ["/path/to/package.jar"] - -# do not pass spark.jars.packages -spark = ( - SparkSession.builder.config("spark.app.name", "onetl") - .config("spark.jars", ",".join(jar_files)) - .getOrCreate() -) -``` - -for spark.submit.deployMode=cluster - -```py -# you can also pass URLs like http://domain.com/path/to/downloadable/package.jar -jar_files = ["hdfs:///path/to/package.jar"] - -# do not pass spark.jars.packages -spark = ( - SparkSession.builder.config("spark.app.name", "onetl") - .config("spark.jars", ",".join(jar_files)) - .getOrCreate() -) -``` - -### Using `spark.jars.repositories` - -#### NOTE -In this case Spark still will try to fetch packages from the internet, so if you don’t have internet access, -Spark session will be created with significant delay because of all attempts to fetch packages. - -Can be used if you have access both to public repos (like Maven) and a private Artifactory/Nexus repo. - -* Setup private Maven repository in [JFrog Artifactory](https://jfrog.com/artifactory/) or [Sonatype Nexus](https://www.sonatype.com/products/sonatype-nexus-repository). -* Download `package.jar` file (it’s usually something like `some-package_1.0.0.jar`). Local file name does not matter. -* Upload `package.jar` file to private repository (with same `groupId` and `artifactoryId` as in source package in Maven). -* Pass repo URL to `spark.jars.repositories` Spark config option. -* Create Spark session with passing Package name to `spark.jars.packages` Spark config option: - -```python -maven_packages = ( - Greenplum.get_packages(spark_version="3.2") - + MySQL.get_packages() - + Teradata.get_packages() -) - -spark = ( - SparkSession.builder.config("spark.app.name", "onetl") - .config("spark.jars.repositories", "http://nexus.mydomain.com/private-repo/") - .config("spark.jars.packages", ",".join(maven_packages)) - .getOrCreate() -) -``` - -### Using `spark.jars.ivySettings` - -Same as above, but can be used even if there is no network access to public repos like Maven. - -* Setup private Maven repository in [JFrog Artifactory](https://jfrog.com/artifactory/) or [Sonatype Nexus](https://www.sonatype.com/products/sonatype-nexus-repository). -* Download `package.jar` file (it’s usually something like `some-package_1.0.0.jar`). Local file name does not matter. -* Upload `package.jar` file to [private repository](https://help.sonatype.com/repomanager3/nexus-repository-administration/repository-management#RepositoryManagement-HostedRepository) (with same `groupId` and `artifactoryId` as in source package in Maven). -* Create `ivysettings.xml` file (see below). -* Add here a resolver with repository URL (and credentials, if required). -* Pass `ivysettings.xml` absolute path to `spark.jars.ivySettings` Spark config option. -* Create Spark session with passing package name to `spark.jars.packages` Spark config option: - -ivysettings-all-packages-uploaded-to-nexus.xml - -```xml - - - - - - - - - - - - - -``` - -ivysettings-private-packages-in-nexus-public-in-maven.xml - -```xml - - - - - - - - - - - - - - - - - -``` - -ivysettings-private-packages-in-nexus-public-fetched-using-proxy-repo.xml - -```xml - - - - - - - - - - - - - - - -``` - -ivysettings-nexus-with-auth-required.xml - -```xml - - - - - - - - - - - - - - - - - - - -``` - -```python -maven_packages = ( - Greenplum.get_packages(spark_version="3.2") - + MySQL.get_packages() - + Teradata.get_packages() -) - -spark = ( - SparkSession.builder.config("spark.app.name", "onetl") - .config("spark.jars.ivySettings", "/path/to/ivysettings.xml") - .config("spark.jars.packages", ",".join(maven_packages)) - .getOrCreate() -) -``` - -### Place `.jar` file to `-/.ivy2/jars/` - -Can be used to pass already downloaded file to Ivy, and skip resolving package from Maven. - -* Download `package.jar` file (it’s usually something like `some-package_1.0.0.jar`). Local file name does not matter, but it should be unique. -* Move it to `-/.ivy2/jars/` folder. -* Create Spark session with passing package name to `spark.jars.packages` Spark config option: - -```python -maven_packages = ( - Greenplum.get_packages(spark_version="3.2") - + MySQL.get_packages() - + Teradata.get_packages() -) - -spark = ( - SparkSession.builder.config("spark.app.name", "onetl") - .config("spark.jars.packages", ",".join(maven_packages)) - .getOrCreate() -) -``` - -### Place `.jar` file to Spark jars folder - -#### NOTE -Package file should be placed on all hosts/containers Spark is running, -both driver and all executors. - -Usually this is used only with either: -: * `spark.master=local` (driver and executors are running on the same host), - * `spark.master=k8s://...` (`.jar` files are added to image or to volume mounted to all pods). - -Can be used to embed `.jar` files to a default Spark classpath. - -* Download `package.jar` file (it’s usually something like `some-package_1.0.0.jar`). Local file name does not matter, but it should be unique. -* Move it to `$SPARK_HOME/jars/` folder, e.g. `^/.local/lib/python3.7/site-packages/pyspark/jars/` or `/opt/spark/3.2.3/jars/`. -* Create Spark session **WITHOUT** passing Package name to `spark.jars.packages` - -```python -# no need to set spark.jars.packages or any other spark.jars.* option -# all jars already present in CLASSPATH, and loaded automatically - -spark = SparkSession.builder.config("spark.app.name", "onetl").getOrCreate() -``` - -### Manually adding `.jar` files to `CLASSPATH` - -#### NOTE -Package file should be placed on all hosts/containers Spark is running, -both driver and all executors. - -Usually this is used only with either: -: * `spark.master=local` (driver and executors are running on the same host), - * `spark.master=k8s://...` (`.jar` files are added to image or to volume mounted to all pods). - -Can be used to embed `.jar` files to a default Java classpath. - -* Download `package.jar` file (it’s usually something like `some-package_1.0.0.jar`). Local file name does not matter. -* Set environment variable `CLASSPATH` to `/path/to/package.jar`. You can set multiple file paths -* Create Spark session **WITHOUT** passing Package name to `spark.jars.packages` - -```python -# no need to set spark.jars.packages or any other spark.jars.* option -# all jars already present in CLASSPATH, and loaded automatically - -import os - -jar_files = ["/path/to/package.jar"] -# different delimiters for Windows and Linux -delimiter = ";" if os.name == "nt" else ":" -spark = ( - SparkSession.builder.config("spark.app.name", "onetl") - .config("spark.driver.extraClassPath", delimiter.join(jar_files)) - .config("spark.executor.extraClassPath", delimiter.join(jar_files)) - .getOrCreate() -) -``` diff --git a/mddocs/markdown/logging.md b/mddocs/markdown/logging.md deleted file mode 100644 index f9900f789..000000000 --- a/mddocs/markdown/logging.md +++ /dev/null @@ -1,236 +0,0 @@ - - -# Logging - -Logging is quite important to understand what’s going on under the hood of onETL. - -Default logging level for Python interpreters is `WARNING`, -but most of onETL logs are in `INFO` level, so users usually don’t see much. - -To change logging level, there is a function [`setup_logging`](#onetl.log.setup_logging) -which should be called at the top of the script: - -```python -from onetl.log import setup_logging -from other.lib import some, more, imports - -setup_logging() - -# rest of code -... -``` - -This changes both log level and log formatting to something like this: - -### See logs - -```text -2024-04-12 10:12:10,834 [INFO ] MainThread: |onETL| Using IncrementalStrategy as a strategy -2024-04-12 10:12:10,835 [INFO ] MainThread: =================================== DBReader.run() starts =================================== -2024-04-12 10:12:10,835 [INFO ] MainThread: |DBReader| Getting Spark type for HWM expression: 'updated_at' -2024-04-12 10:12:10,836 [INFO ] MainThread: |MSSQL| Fetching schema of table 'source_schema.table' ... -2024-04-12 10:12:11,636 [INFO ] MainThread: |MSSQL| Schema fetched. -2024-04-12 10:12:11,642 [INFO ] MainThread: |DBReader| Got Spark field: StructField('updated_at', TimestampType(), True) -2024-04-12 10:12:11,642 [INFO ] MainThread: |DBReader| Detected HWM type: 'ColumnDateTimeHWM' -2024-04-12 10:12:11,643 [INFO ] MainThread: |IncrementalStrategy| Fetching HWM from HorizonHWMStore: -2024-04-12 10:12:11,643 [INFO ] MainThread: name = 'updated_at#source_schema.table@mssql:/mssql.host:1433/somedb' -2024-04-12 10:12:12,181 [INFO ] MainThread: |IncrementalStrategy| Fetched HWM: -2024-04-12 10:12:12,182 [INFO ] MainThread: hwm = ColumnDateTimeHWM( -2024-04-12 10:12:12,182 [INFO ] MainThread: name = 'updated_at#source_schema.table@mssql:/mssql.host:1433/somedb', -2024-04-12 10:12:12,182 [INFO ] MainThread: entity = 'source_schema.table', -2024-04-12 10:12:12,182 [INFO ] MainThread: expression = 'updated_at', -2024-04-12 10:12:12,184 [INFO ] MainThread: value = datetime.datetime(2024, 4, 11, 18, 10, 2, 120000), -2024-04-12 10:12:12,184 [INFO ] MainThread: ) -2024-04-12 10:12:12,184 [INFO ] MainThread: |MSSQL| -> |Spark| Reading DataFrame from source using parameters: -2024-04-12 10:12:12,185 [INFO ] MainThread: source = 'source_schema.table' -2024-04-12 10:12:12,185 [INFO ] MainThread: columns = [ -2024-04-12 10:12:12,185 [INFO ] MainThread: 'id', -2024-04-12 10:12:12,186 [INFO ] MainThread: 'new_value', -2024-04-12 10:12:12,186 [INFO ] MainThread: 'old_value', -2024-04-12 10:12:12,186 [INFO ] MainThread: 'updated_at', -2024-04-12 10:12:12,186 [INFO ] MainThread: ] -2024-04-12 10:12:12,187 [INFO ] MainThread: where = "field = 'some'" -2024-04-12 10:12:12,187 [INFO ] MainThread: hwm = AutoDetectHWM( -2024-04-12 10:12:12,187 [INFO ] MainThread: name = 'updated_at#source_schema.table@mssql:/mssql.host:1433/somedb', -2024-04-12 10:12:12,187 [INFO ] MainThread: entity = 'source_schema.table', -2024-04-12 10:12:12,187 [INFO ] MainThread: expression = 'updated_at', -2024-04-12 10:12:12,188 [INFO ] MainThread: ) -2024-04-12 10:12:12,188 [INFO ] MainThread: options = { -2024-04-12 10:12:12,188 [INFO ] MainThread: 'fetchsize': 100000, -2024-04-12 10:12:12,188 [INFO ] MainThread: 'numPartitions': 1, -2024-04-12 10:12:12,189 [INFO ] MainThread: 'partitioningMode': 'range', -2024-04-12 10:12:12,189 [INFO ] MainThread: } -2024-04-12 10:12:12,189 [INFO ] MainThread: |MSSQL| Checking connection availability... -2024-04-12 10:12:12,189 [INFO ] MainThread: |MSSQL| Using connection parameters: -2024-04-12 10:12:12,190 [INFO ] MainThread: user = 'db_user' -2024-04-12 10:12:12,190 [INFO ] MainThread: password = SecretStr('**********') -2024-04-12 10:12:12,190 [INFO ] MainThread: host = 'mssql.host' -2024-04-12 10:12:12,190 [INFO ] MainThread: port = 1433 -2024-04-12 10:12:12,191 [INFO ] MainThread: database = 'somedb' -2024-04-12 10:12:12,191 [INFO ] MainThread: extra = {'applicationIntent': 'ReadOnly', 'trustServerCertificate': 'true'} -2024-04-12 10:12:12,191 [INFO ] MainThread: jdbc_url = 'jdbc:sqlserver:/mssql.host:1433' -2024-04-12 10:12:12,579 [INFO ] MainThread: |MSSQL| Connection is available. -2024-04-12 10:12:12,581 [INFO ] MainThread: |MSSQL| Executing SQL query (on driver): -2024-04-12 10:12:12,581 [INFO ] MainThread: SELECT -2024-04-12 10:12:12,581 [INFO ] MainThread: MIN(updated_at) AS "min", -2024-04-12 10:12:12,582 [INFO ] MainThread: MAX(updated_at) AS "max" -2024-04-12 10:12:12,582 [INFO ] MainThread: FROM -2024-04-12 10:12:12,582 [INFO ] MainThread: source_schema.table -2024-04-12 10:12:12,582 [INFO ] MainThread: WHERE -2024-04-12 10:12:12,582 [INFO ] MainThread: (field = 'some') -2024-04-12 10:12:12,583 [INFO ] MainThread: AND -2024-04-12 10:12:12,583 [INFO ] MainThread: (updated_at >= CAST('2024-04-11T18:10:02.120000' AS datetime2)) -2024-04-12 10:16:22,537 [INFO ] MainThread: |MSSQL| Received values: -2024-04-12 10:16:22,538 [INFO ] MainThread: MIN(updated_at) = datetime.datetime(2024, 4, 11, 21, 10, 7, 397000) -2024-04-12 10:16:22,538 [INFO ] MainThread: MAX(updated_at) = datetime.datetime(2024, 4, 12, 13, 12, 2, 123000) -2024-04-12 10:16:22,540 [INFO ] MainThread: |MSSQL| Executing SQL query (on executor): -2024-04-12 10:16:22,540 [INFO ] MainThread: SELECT -2024-04-12 10:16:22,540 [INFO ] MainThread: id, -2024-04-12 10:16:22,541 [INFO ] MainThread: new_value, -2024-04-12 10:16:22,541 [INFO ] MainThread: old_value, -2024-04-12 10:16:22,541 [INFO ] MainThread: updated_at -2024-04-12 10:16:22,541 [INFO ] MainThread: FROM -2024-04-12 10:16:22,541 [INFO ] MainThread: source_schema.table -2024-04-12 10:16:22,542 [INFO ] MainThread: WHERE -2024-04-12 10:16:22,542 [INFO ] MainThread: (field = 'some') -2024-04-12 10:16:22,542 [INFO ] MainThread: AND -2024-04-12 10:16:22,542 [INFO ] MainThread: (updated_at > CAST('2024-04-11T18:10:02.120000' AS datetime2)) -2024-04-12 10:16:22,542 [INFO ] MainThread: AND -2024-04-12 10:16:22,542 [INFO ] MainThread: (updated_at <= CAST('2024-04-12T13:12:02.123000' AS datetime2)) -2024-04-12 10:16:22,892 [INFO ] MainThread: |Spark| DataFrame successfully created from SQL statement -2024-04-12 10:16:22,892 [INFO ] MainThread: ------------------------------------ DBReader.run() ends ------------------------------------ -2024-04-12 10:40:42,409 [INFO ] MainThread: =================================== DBWriter.run() starts =================================== -2024-04-12 10:40:42,409 [INFO ] MainThread: |Spark| -> |Hive| Writing DataFrame to target using parameters: -2024-04-12 10:40:42,410 [INFO ] MainThread: target = 'target_source_schema.table' -2024-04-12 10:40:42,410 [INFO ] MainThread: options = { -2024-04-12 10:40:42,410 [INFO ] MainThread: 'mode': 'append', -2024-04-12 10:40:42,410 [INFO ] MainThread: 'format': 'orc', -2024-04-12 10:40:42,410 [INFO ] MainThread: 'partitionBy': 'part_dt', -2024-04-12 10:40:42,410 [INFO ] MainThread: } -2024-04-12 10:40:42,411 [INFO ] MainThread: df_schema: -2024-04-12 10:40:42,412 [INFO ] MainThread: root -2024-04-12 10:40:42,412 [INFO ] MainThread: |-- id: integer (nullable = true) -2024-04-12 10:40:42,413 [INFO ] MainThread: |-- new_value: string (nullable = true) -2024-04-12 10:40:42,413 [INFO ] MainThread: |-- old_value: string (nullable = true) -2024-04-12 10:40:42,413 [INFO ] MainThread: |-- updated_at: timestamp (nullable = true) -2024-04-12 10:40:42,413 [INFO ] MainThread: |-- part_dt: date (nullable = true) -2024-04-12 10:40:42,414 [INFO ] MainThread: -2024-04-12 10:40:42,421 [INFO ] MainThread: |Hive| Checking connection availability... -2024-04-12 10:40:42,421 [INFO ] MainThread: |Hive| Using connection parameters: -2024-04-12 10:40:42,421 [INFO ] MainThread: cluster = 'dwh' -2024-04-12 10:40:42,475 [INFO ] MainThread: |Hive| Connection is available. -2024-04-12 10:40:42,476 [INFO ] MainThread: |Hive| Fetching schema of table 'target_source_schema.table' ... -2024-04-12 10:40:43,518 [INFO ] MainThread: |Hive| Schema fetched. -2024-04-12 10:40:43,521 [INFO ] MainThread: |Hive| Table 'target_source_schema.table' already exists -2024-04-12 10:40:43,521 [WARNING ] MainThread: |Hive| User-specified options {'partitionBy': 'part_dt'} are ignored while inserting into existing table. Using only table parameters from Hive metastore -2024-04-12 10:40:43,782 [INFO ] MainThread: |Hive| Inserting data into existing table 'target_source_schema.table' ... -2024-04-12 11:06:07,396 [INFO ] MainThread: |Hive| Data is successfully inserted into table 'target_source_schema.table'. -2024-04-12 11:06:07,397 [INFO ] MainThread: ------------------------------------ DBWriter.run() ends ------------------------------------ -2024-04-12 11:06:07,397 [INFO ] MainThread: |onETL| Exiting IncrementalStrategy -2024-04-12 11:06:07,397 [INFO ] MainThread: |IncrementalStrategy| Saving HWM to 'HorizonHWMStore': -2024-04-12 11:06:07,397 [INFO ] MainThread: hwm = ColumnDateTimeHWM( -2024-04-12 11:06:07,397 [INFO ] MainThread: name = 'updated_at#source_schema.table@mssql:/mssql.host:1433/somedb', -2024-04-12 11:06:07,397 [INFO ] MainThread: entity = 'source_source_schema.table', -2024-04-12 11:06:07,397 [INFO ] MainThread: expression = 'updated_at', -2024-04-12 11:06:07,397 [INFO ] MainThread: value = datetime.datetime(2024, 4, 12, 13, 12, 2, 123000), -2024-04-12 11:06:07,397 [INFO ] MainThread: ) -2024-04-12 11:06:07,495 [INFO ] MainThread: |IncrementalStrategy| HWM has been saved -``` - -Each step performed by onETL is extensively logged, which should help with debugging. - -You can make logs even more verbose by changing level to `DEBUG`: - -```python -from onetl.log import setup_logging - -setup_logging(level="DEBUG", enable_clients=True) - -# rest of code -... -``` - -This also changes log level for all underlying Python libraries, e.g. showing each HTTP request being made, and so on. - -### onetl.log.setup_logging(level: int | str = 20, enable_clients: bool = False) → None - -Set up onETL logging. - -What this function does: -: * Adds stderr logging handler - * Changes root logger format to `2023-05-31 11:22:33.456 [INFO] MainThread: message` - * Changes root logger level to `level` - * Changes onETL logger level to `level` - * Sets up logging level of underlying client modules - -#### NOTE -Should be used only in IDEs (like Jupyter notebooks or PyCharm), -or scripts (ETL pipelines). - -#### Versionchanged -Changed in version 0.5.0: Renamed `setup_notebook_logging` → `setup_logging` - -* **Parameters:** - **level** - : Log level for onETL module - - **enable_clients** - : If `True`, enable logging of underlying client modules. - Otherwise, set client modules log level to `DISABLED`. -
- #### NOTE - For `level="DEBUG"` it is recommended to use `enable_clients=True` -
- #### Versionadded - Added in version 0.9.0. - - - -### onetl.log.setup_clients_logging(level: int | str = 9999) → None - -Set logging of underlying client modules used by onETL. - -Affected modules: -: * `ftputil` - * `hdfs` - * `minio` - * `paramiko` - * `py4j` - * `pyspark` - * `webdav3` - -#### NOTE -Can be used in applications, but it is recommended to set up these loggers -according to your framework documentation. - -#### Versionchanged -Changed in version 0.9.0: Renamed `disable_clients_logging` → `setup_clients_logging` - -* **Parameters:** - **level** - : Log level for client modules -
- #### NOTE - For `py4j`, logging level with maximum verbosity is `INFO` because `DEBUG` logs are - totally unreadable. -
- #### Versionadded - Added in version 0.9.0. - - - -### onetl.log.set_default_logging_format() → None - -Sets default logging format to preferred by onETL. - -Example log message: `2023-05-31 11:22:33.456 [INFO] MainThread: message` - -#### NOTE -Should be used only in IDEs (like Jupyter notebooks or PyCharm), -or scripts (ETL pipelines). - -#### WARNING -Should **NOT** be used in applications, you should set up logging settings manually, -according to your framework documentation. - - diff --git a/mddocs/markdown/plugins.md b/mddocs/markdown/plugins.md deleted file mode 100644 index d42ded034..000000000 --- a/mddocs/markdown/plugins.md +++ /dev/null @@ -1,147 +0,0 @@ - - -# Plugins - -#### Versionadded -Added in version 0.6.0. - -## What are plugins? - -### Terms - -* `Plugin` - some Python package which implements some extra functionality for onETL, like [Hooks](hooks/index.md#hooks) -* `Plugin autoimport` - onETL behavior which allows to automatically import this package if it contains proper metadata (`entry_points`) - -### Features - -Plugins mechanism allows to: - -* Automatically register [Hooks](hooks/index.md#hooks) which can alter onETL behavior -* Automatically register new classes, like HWM type, HWM stores and so on - -### Limitations - -Unlike other projects (like *Airflow 1.x*), plugins does not inject imported classes or functions to `onetl.*` namespace. -Users should import classes from the plugin package **explicitly** to avoid name collisions. - -## How to implement plugin? - -Create a Python package `some-plugin` with a file `some_plugin/setup.py`: - -```python -# some_plugin/setup.py -from setuptools import setup - -setup( - # if you want to import something from onETL, add it to requirements list - install_requires=["onetl"], - entry_points={ - # this key enables plugins autoimport functionality - "onetl.plugins": [ - "some-plugin-name=some_plugin.module", # automatically import all module content - "some-plugin-class=some_plugin.module.internals:MyClass", # import a specific class - "some-plugin-function=some_plugin.module.internals:my_function", # import a specific function - ], - }, -) -``` - -See [setuptools documentation for entry_points](https://setuptools.pypa.io/en/latest/userguide/entry_point.html) - -## How plugins are imported? - -* User should install a package implementing the plugin: - -```bash -pip install some-package -``` - -* Then user should import something from `onetl` module or its submodules: - -```python -import onetl -from onetl.connection import SomeConnection - -# and so on -``` - -* This import automatically executes something like: - -```python -import some_plugin.module -from some_plugin.module.internals import MyClass -from some_plugin.module.internals import my_function -``` - -If specific module/class/function uses some registration capabilities of onETL, -like [@hook decorator](hooks/hook.md#hook-decorator), it will be executed during this import. - -## How to enable/disable plugins? - -#### Versionadded -Added in version 0.7.0. - -### Disable/enable all plugins - -By default plugins are enabled. - -To disabled them, you can set environment variable `ONETL_PLUGINS_ENABLED` to `false` BEFORE -importing onETL. This will disable all plugins autoimport. - -But user is still be able to explicitly import `some_plugin.module`, executing -all decorators and registration capabilities of onETL. - -### Disable a specific plugin (blacklist) - -If some plugin is failing during import, you can disable it by setting up environment variable -`ONETL_PLUGINS_BLACKLIST=some-failing-plugin`. Multiple plugin names could be passed with `,` as delimiter. - -Again, this environment variable should be set BEFORE importing onETL. - -### Disable all plugins except a specific one (whitelist) - -You can also disable all plugins except a specific one by setting up environment variable -`ONETL_PLUGINS_WHITELIST=some-not-failing-plugin`. Multiple plugin names could be passed with `,` as delimiter. - -Again, this environment variable should be set BEFORE importing onETL. - -If both whitelist and blacklist environment variables are set, blacklist has a higher priority. - -## How to see logs of the plugins mechanism? - -Plugins registration emits logs with `INFO` level: - -```python -import logging - -logging.basicConfig(level=logging.INFO) -``` - -```text -INFO |onETL| Found 2 plugins -INFO |onETL| Loading plugin 'my-plugin' -INFO |onETL| Skipping plugin 'failing' because it is in a blacklist -``` - -More detailed logs are emitted with `DEBUG` level, to make output less verbose: - -```python -import logging - -logging.basicConfig(level=logging.DEBUG) -``` - -```text -DEBUG |onETL| Searching for plugins with group 'onetl.plugins' -DEBUG |Plugins| Plugins whitelist: [] -DEBUG |Plugins| Plugins blacklist: ['failing-plugin'] -INFO |Plugins| Found 2 plugins -INFO |onETL| Loading plugin (1/2): -DEBUG name: 'my-plugin' -DEBUG package: 'my-package' -DEBUG version: '0.1.0' -DEBUG importing: 'my_package.my_module:MyClass' -DEBUG |onETL| Successfully loaded plugin 'my-plugin' -DEBUG source: '/usr/lib/python3.11/site-packages/my_package/my_module/my_class.py' -INFO |onETL| Skipping plugin 'failing' because it is in a blacklist -``` diff --git a/mddocs/markdown/quickstart.md b/mddocs/markdown/quickstart.md deleted file mode 100644 index 7a9bd0652..000000000 --- a/mddocs/markdown/quickstart.md +++ /dev/null @@ -1,365 +0,0 @@ -: - -# Quick start - -## MSSQL → Hive - -Read data from MSSQL, transform & write to Hive. - -```bash -# install onETL and PySpark -pip install onetl[spark] -``` - -```python -# Import pyspark to initialize the SparkSession -from pyspark.sql import SparkSession - -# import function to setup onETL logging -from onetl.log import setup_logging - -# Import required connections -from onetl.connection import MSSQL, Hive - -# Import onETL classes to read & write data -from onetl.db import DBReader, DBWriter - -# change logging level to INFO, and set up default logging format and handler -setup_logging() - -# Initialize new SparkSession with MSSQL driver loaded -maven_packages = MSSQL.get_packages() -spark = ( - SparkSession.builder.appName("spark_app_onetl_demo") - .config("spark.jars.packages", ",".join(maven_packages)) - .enableHiveSupport() # for Hive - .getOrCreate() -) - -# Initialize MSSQL connection and check if database is accessible -mssql = MSSQL( - host="mssqldb.demo.com", - user="onetl", - password="onetl", - database="Telecom", - spark=spark, - # These options are passed to MSSQL JDBC Driver: - extra={"applicationIntent": "ReadOnly"}, -).check() - -# >>> INFO:|MSSQL| Connection is available - -# Initialize DBReader -reader = DBReader( - connection=mssql, - source="dbo.demo_table", - columns=["on", "etl"], - # Set some MSSQL read options: - options=MSSQL.ReadOptions(fetchsize=10000), -) - -# checks that there is data in the table, otherwise raises exception -reader.raise_if_no_data() - -# Read data to DataFrame -df = reader.run() -df.printSchema() -# root -# |-- id: integer (nullable = true) -# |-- phone_number: string (nullable = true) -# |-- region: string (nullable = true) -# |-- birth_date: date (nullable = true) -# |-- registered_at: timestamp (nullable = true) -# |-- account_balance: double (nullable = true) - -# Apply any PySpark transformations -from pyspark.sql.functions import lit - -df_to_write = df.withColumn("engine", lit("onetl")) -df_to_write.printSchema() -# root -# |-- id: integer (nullable = true) -# |-- phone_number: string (nullable = true) -# |-- region: string (nullable = true) -# |-- birth_date: date (nullable = true) -# |-- registered_at: timestamp (nullable = true) -# |-- account_balance: double (nullable = true) -# |-- engine: string (nullable = false) - -# Initialize Hive connection -hive = Hive(cluster="rnd-dwh", spark=spark) - -# Initialize DBWriter -db_writer = DBWriter( - connection=hive, - target="dl_sb.demo_table", - # Set some Hive write options: - options=Hive.WriteOptions(if_exists="replace_entire_table"), -) - -# Write data from DataFrame to Hive -db_writer.run(df_to_write) - -# Success! -``` - -## SFTP → HDFS - -Download files from SFTP & upload them to HDFS. - -```bash -# install onETL with SFTP and HDFS clients, and Kerberos support -pip install onetl[hdfs,sftp,kerberos] -``` - -```python -# import function to setup onETL logging -from onetl.log import setup_logging - -# Import required connections -from onetl.connection import SFTP, HDFS - -# Import onETL classes to download & upload files -from onetl.file import FileDownloader, FileUploader - -# import filter & limit classes -from onetl.file.filter import Glob, ExcludeDir -from onetl.file.limit import MaxFilesCount - -# change logging level to INFO, and set up default logging format and handler -setup_logging() - -# Initialize SFTP connection and check it -sftp = SFTP( - host="sftp.test.com", - user="someuser", - password="somepassword", -).check() - -# >>> INFO:|SFTP| Connection is available - -# Initialize downloader -file_downloader = FileDownloader( - connection=sftp, - source_path="/remote/tests/Report", # path on SFTP - local_path="/local/onetl/Report", # local fs path - filters=[ - # download only files matching the glob - Glob("*.csv"), - # exclude files from this directory - ExcludeDir("/remote/tests/Report/exclude_dir/"), - ], - limits=[ - # download max 1000 files per run - MaxFilesCount(1000), - ], - options=FileDownloader.Options( - # delete files from SFTP after successful download - delete_source=True, - # mark file as failed if it already exist in local_path - if_exists="error", - ), -) - -# Download files to local filesystem -download_result = downloader.run() - -# Method run returns a DownloadResult object, -# which contains collection of downloaded files, divided to 4 categories -download_result - -# DownloadResult( -# successful=[ -# LocalPath('/local/onetl/Report/file_1.json'), -# LocalPath('/local/onetl/Report/file_2.json'), -# ], -# failed=[FailedRemoteFile('/remote/onetl/Report/file_3.json')], -# ignored=[RemoteFile('/remote/onetl/Report/file_4.json')], -# missing=[], -# ) - -# Raise exception if there are failed files, or there were no files in the remote filesystem -download_result.raise_if_failed() or download_result.raise_if_empty() - -# Do any kind of magic with files: rename files, remove header for csv files, ... -renamed_files = my_rename_function(download_result.success) - -# function removed "_" from file names -# [ -# LocalPath('/home/onetl/Report/file1.json'), -# LocalPath('/home/onetl/Report/file2.json'), -# ] - -# Initialize HDFS connection -hdfs = HDFS( - host="my.name.node", - user="someuser", - password="somepassword", # or keytab -) - -# Initialize uploader -file_uploader = FileUploader( - connection=hdfs, - target_path="/user/onetl/Report/", # hdfs path -) - -# Upload files from local fs to HDFS -upload_result = file_uploader.run(renamed_files) - -# Method run returns a UploadResult object, -# which contains collection of uploaded files, divided to 4 categories -upload_result - -# UploadResult( -# successful=[RemoteFile('/user/onetl/Report/file1.json')], -# failed=[FailedLocalFile('/local/onetl/Report/file2.json')], -# ignored=[], -# missing=[], -# ) - -# Raise exception if there are failed files, or there were no files in the local filesystem, or some input file is missing -upload_result.raise_if_failed() or upload_result.raise_if_empty() or upload_result.raise_if_missing() - -# Success! -``` - -## S3 → Postgres - -Read files directly from S3 path, convert them to dataframe, transform it and then write to a database. - -```bash -# install onETL and PySpark -pip install onetl[spark] -``` - -```python -# Import pyspark to initialize the SparkSession -from pyspark.sql import SparkSession - -# import function to setup onETL logging -from onetl.log import setup_logging - -# Import required connections -from onetl.connection import Postgres, SparkS3 - -# Import onETL classes to read files -from onetl.file import FileDFReader -from onetl.file.format import CSV - -# Import onETL classes to write data -from onetl.db import DBWriter - -# change logging level to INFO, and set up default logging format and handler -setup_logging() - -# Initialize new SparkSession with Hadoop AWS libraries and Postgres driver loaded -maven_packages = SparkS3.get_packages(spark_version="3.5.5") + Postgres.get_packages() -exclude_packages = SparkS3.get_exclude_packages() -spark = ( - SparkSession.builder.appName("spark_app_onetl_demo") - .config("spark.jars.packages", ",".join(maven_packages)) - .config("spark.jars.excludes", ",".join(exclude_packages)) - .getOrCreate() -) - -# Initialize S3 connection and check it -spark_s3 = SparkS3( - host="s3.test.com", - protocol="https", - bucket="my-bucket", - access_key="somekey", - secret_key="somesecret", - # Access bucket as s3.test.com/my-bucket - extra={"path.style.access": True}, - spark=spark, -).check() - -# >>> INFO:|SparkS3| Connection is available - -# Describe file format and parsing options -csv = CSV( - delimiter=";", - header=True, - encoding="utf-8", -) - -# Describe DataFrame schema of files -from pyspark.sql.types import ( - DateType, - DoubleType, - IntegerType, - StringType, - StructField, - StructType, - TimestampType, -) - -df_schema = StructType( - [ - StructField("id", IntegerType()), - StructField("phone_number", StringType()), - StructField("region", StringType()), - StructField("birth_date", DateType()), - StructField("registered_at", TimestampType()), - StructField("account_balance", DoubleType()), - ], -) - -# Initialize file df reader -reader = FileDFReader( - connection=spark_s3, - source_path="/remote/tests/Report", # path on S3 there *.csv files are located - format=csv, # file format with specific parsing options - df_schema=df_schema, # columns & types -) - -# Read files directly from S3 as Spark DataFrame -df = reader.run() - -# Check that DataFrame schema is same as expected -df.printSchema() -# root -# |-- id: integer (nullable = true) -# |-- phone_number: string (nullable = true) -# |-- region: string (nullable = true) -# |-- birth_date: date (nullable = true) -# |-- registered_at: timestamp (nullable = true) -# |-- account_balance: double (nullable = true) - -# Apply any PySpark transformations -from pyspark.sql.functions import lit - -df_to_write = df.withColumn("engine", lit("onetl")) -df_to_write.printSchema() -# root -# |-- id: integer (nullable = true) -# |-- phone_number: string (nullable = true) -# |-- region: string (nullable = true) -# |-- birth_date: date (nullable = true) -# |-- registered_at: timestamp (nullable = true) -# |-- account_balance: double (nullable = true) -# |-- engine: string (nullable = false) - -# Initialize Postgres connection -postgres = Postgres( - host="192.169.11.23", - user="onetl", - password="somepassword", - database="mydb", - spark=spark, -) - -# Initialize DBWriter -db_writer = DBWriter( - connection=postgres, - # write to specific table - target="public.my_table", - # with some writing options - options=Postgres.WriteOptions(if_exists="append"), -) - -# Write DataFrame to Postgres table -db_writer.run(df_to_write) - -# Success! -``` diff --git a/mddocs/markdown/security.md b/mddocs/markdown/security.md deleted file mode 100644 index 3048c040b..000000000 --- a/mddocs/markdown/security.md +++ /dev/null @@ -1,25 +0,0 @@ -# Security - -## Supported Python versions - -3.7 or above - -## Product development security recommendations - -1. Update dependencies to last stable version -2. Build SBOM for the project -3. Perform SAST (Static Application Security Testing) where possible - -## Product development security requirements - -1. No binaries in repository -2. No passwords, keys, access tokens in source code -3. No “Critical” and/or “High” vulnerabilities in contributed source code - -## Vulnerability reports - -Please, use email [mailto:onetools@mts.ru](mailto:onetools@mts.ru) for reporting security issues or anything that can cause any consequences for security. - -Please avoid any public disclosure (including registering issues) at least until it is fixed. - -Thank you in advance for understanding. diff --git a/mddocs/markdown/strategy/incremental_batch_strategy.md b/mddocs/markdown/strategy/incremental_batch_strategy.md deleted file mode 100644 index 2d30a6767..000000000 --- a/mddocs/markdown/strategy/incremental_batch_strategy.md +++ /dev/null @@ -1,301 +0,0 @@ - - -# Incremental Batch Strategy - -### *class* onetl.strategy.incremental_strategy.IncrementalBatchStrategy(\*, hwm: HWM | None = None, step: Any = None, start: Any = None, stop: Any = None, offset: Any = None) - -Incremental batch strategy for [DB Reader](../db/db_reader.md#db-reader). - -#### NOTE -Cannot be used with [File Downloader](../file/file_downloader/file_downloader.md#file-downloader) - -Same as [`IncrementalStrategy`](incremental_strategy.md#onetl.strategy.incremental_strategy.IncrementalStrategy), -but reads data from the source in sequential batches (1..N) like: - -```sql -1: SELECT id, data - FROM public.mydata - WHERE id > 1000 AND id <= 1100; -- previous HWM value is 1000, step is 100 - -2: WHERE id > 1100 AND id <= 1200; -- + step -3: WHERE id > 1200 AND id <= 1200; -- + step -N: WHERE id > 1300 AND id <= 1400; -- until stop -``` - -This allows to use less CPU and RAM than reading all the data in the one batch, -but takes proportionally more time. - -#### WARNING -Unlike [`SnapshotBatchStrategy`](snapshot_batch_strategy.md#onetl.strategy.snapshot_strategy.SnapshotBatchStrategy), -it **saves** current HWM value after **each batch** into [HWM Store](../hwm_store/index.md#hwm). - -So if code inside the context manager raised an exception, like: - -```python -with IncrementalBatchStrategy() as batches: - for _ in batches: - df = reader.run() # something went wrong here - writer.run(df) # or here - # or here... -``` - -DBReader will **NOT** update HWM in HWM Store for the failed batch. - -All of that allows to resume reading process from the *last successful batch*. - -#### WARNING -Not every [DB connection](../connection/db_connection/index.md#db-connections) -supports batch strategy. For example, Kafka connection doesn’t support it. -Make sure the connection you use is compatible with the IncrementalBatchStrategy. - -#### Versionadded -Added in version 0.1.0. - -* **Parameters:** - **step** - : Step size used for generating batch SQL queries like: - ```sql - SELECT id, data - FROM public.mydata - WHERE id > 1000 AND id <= 1100; -- 1000 is previous HWM value, step is 100 - ``` -
- #### NOTE - Step defines a range of values will be fetched by each batch. This is **not** - a number of rows, it depends on a table content and value distribution across the rows. -
- #### NOTE - `step` value will be added to the HWM, so it should have a proper type. -
- For example, for `TIMESTAMP` column `step` type should be `datetime.timedelta`, not `int` - - **stop** - : If passed, the value will be used for generating WHERE clauses with `hwm.expression` filter, - as a stop value for the last batch. -
- If not set, the value is determined by a separated query: - ```sql - SELECT MAX(id) as stop - FROM public.mydata - WHERE id > 1000; -- 1000 is previous HWM value (if any) - ``` -
- #### NOTE - `stop` should be the same type as `hwm.expression` value, - e.g. `datetime.datetime` for `TIMESTAMP` column, `datetime.date` for `DATE`, and so on - - **offset** - : If passed, the offset value will be used to read rows which appeared in the source after the previous read. -
- For example, previous incremental run returned rows: - ```default - 898 - 899 - 900 - 1000 - ``` -
- Current HWM value is 1000. -
- But since then few more rows appeared in the source: - ```default - 898 - 899 - 900 - 901 # new - 902 # new - ... - 999 # new - 1000 - ``` -
- and you need to read them too. -
- So you can set `offset=100`, so the first batch of a next incremental run will look like: - ```sql - SELECT id, data - FROM public.mydata - WHERE id > 900 AND id <= 1000; -- 900 = 1000 - 100 = HWM - offset - ``` -
- and return rows from 901 (**not** 900) to **1000** (duplicate). -
- #### WARNING - This can lead to reading duplicated values from the table. - You probably need additional deduplication step to handle them -
- #### NOTE - `offset` value will be subtracted from the HWM, so it should have a proper type. -
- For example, for `TIMESTAMP` column `offset` type should be `datetime.timedelta`, not `int` - -### Examples - -IncrementalBatch run - -```python -from onetl.db import DBReader, DBWriter -from onetl.strategy import IncrementalBatchStrategy - -reader = DBReader( - connection=postgres, - source="public.mydata", - columns=["id", "data"], - hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="id"), -) - -writer = DBWriter(connection=hive, target="db.newtable") - -with IncrementalBatchStrategy(step=100) as batches: - for _ in batches: - df = reader.run() - writer.run(df) -``` - -```sql --- previous HWM value was 1000 --- each batch (1..N) will perform a query which return some part of input data - -1: SELECT id, data - FROM public.mydata - WHERE id > 1100 AND id <= 1200; --- from HWM to HWM+step (EXCLUDING first row) - -2: WHERE id > 1200 AND id <= 1300; -- + step -N: WHERE id > 1300 AND id <= 1400; -- until max value of HWM column -``` - -IncrementalBatch run with `stop` value - -```python -... - -with IncrementalBatchStrategy(step=100, stop=2000) as batches: - for _ in batches: - df = reader.run() - writer.run(df) -``` - -```sql --- previous HWM value was 1000 --- each batch (1..N) will perform a query which return some part of input data - -1: SELECT id, data - FROM public.mydata - WHERE id > 1000 AND id <= 1100; --- from HWM to HWM+step (EXCLUDING first row) - -2: WHERE id > 1100 AND id <= 1200; -- + step -... -N: WHERE id > 1900 AND id <= 2000; -- until stop -``` - -IncrementalBatch run with `offset` value - -```python -... - -with IncrementalBatchStrategy(step=100, offset=100) as batches: - for _ in batches: - df = reader.run() - writer.run(df) -``` - -```sql --- previous HWM value was 1000 --- each batch (1..N) will perform a query which return some part of input data - -1: SELECT id, data - FROM public.mydata - WHERE id > 900 AND id <= 1000; --- from HWM-offset to HWM-offset+step (EXCLUDING first row) - -2: WHERE id > 1000 AND id <= 1100; -- + step -3: WHERE id > 1100 AND id <= 1200; -- + step -... -N: WHERE id > 1300 AND id <= 1400; -- until max value of HWM column -``` - -IncrementalBatch run with all possible options - -```python -... - -with IncrementalBatchStrategy( - step=100, - stop=2000, - offset=100, -) as batches: - for _ in batches: - df = reader.run() - writer.run(df) -``` - -```sql --- previous HWM value was 1000 --- each batch (1..N) will perform a query which return some part of input data - -1: SELECT id, data - FROM public.mydata - WHERE id > 900 AND id <= 1000; --- from HWM-offset to HWM-offset+step (EXCLUDING first row) - -2: WHERE id > 1000 AND id <= 1100; -- + step -3: WHERE id > 1100 AND id <= 1200; -- + step -... -N: WHERE id > 1900 AND id <= 2000; -- until stop -``` - -IncrementalBatch run over non-integer column - -`hwm.expression`, `offset` and `stop` can be a date or datetime, not only integer: - -```python -from onetl.db import DBReader, DBWriter -from datetime import date, timedelta - -reader = DBReader( - connection=postgres, - source="public.mydata", - columns=["business_dt", "data"], - hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="business_dt"), -) - -writer = DBWriter(connection=hive, target="db.newtable") - -with IncrementalBatchStrategy( - step=timedelta(days=5), - stop=date("2021-01-31"), - offset=timedelta(days=1), -) as batches: - for _ in batches: - df = reader.run() - writer.run(df) -``` - -```sql --- previous HWM value was '2021-01-10' --- each batch (1..N) will perform a query which return some part of input data - -1: SELECT business_dt, data - FROM public.mydata - WHERE business_dt > CAST('2021-01-09' AS DATE) -- from HWM-offset (EXCLUDING first row) - AND business_dt <= CAST('2021-01-14' AS DATE); -- to HWM-offset+step - -2: WHERE business_dt > CAST('2021-01-14' AS DATE) -- + step - AND business_dt <= CAST('2021-01-19' AS DATE); - -3: WHERE business_dt > CAST('2021-01-19' AS DATE) -- + step - AND business_dt <= CAST('2021-01-24' AS DATE); - -... - -N: WHERE business_dt > CAST('2021-01-29' AS DATE) - AND business_dt <= CAST('2021-01-31' AS DATE); -- until stop -``` - - - -#### \_\_init_\_(\*\*kwargs) - -Create a new model by parsing and validating input data from keyword arguments. - -Raises ValidationError if the input data cannot be parsed to form a valid model. - - diff --git a/mddocs/markdown/strategy/incremental_strategy.md b/mddocs/markdown/strategy/incremental_strategy.md deleted file mode 100644 index a40dc5b94..000000000 --- a/mddocs/markdown/strategy/incremental_strategy.md +++ /dev/null @@ -1,396 +0,0 @@ - - -# Incremental Strategy - -### *class* onetl.strategy.incremental_strategy.IncrementalStrategy(\*, hwm: HWM | None = None, offset: Any = None) - -Incremental strategy for [DB Reader](../db/db_reader.md#db-reader)/[File Downloader](../file/file_downloader/file_downloader.md#file-downloader). - -Used for fetching only new rows/files from a source -by filtering items not covered by the previous [HWM](../hwm_store/index.md#hwm) value. - -For [DB Reader](../db/db_reader.md#db-reader): -: First incremental run is just the same as [`SnapshotStrategy`](snapshot_strategy.md#onetl.strategy.snapshot_strategy.SnapshotStrategy): -
- ```sql - SELECT id, data FROM mydata; - ``` -
- Then the max value of `id` column (e.g. `1000`) will be saved as `HWM` to [HWM Store](../hwm_store/index.md#hwm). -
- Next incremental run will read only new data from the source: -
- ```sql - SELECT id, data FROM mydata WHERE id > 1000; -- hwm value - ``` -
- Pay attention to resulting dataframe **does not include** row with `id=1000` because it has been read before. -
- #### WARNING - If code inside the context manager raised an exception, like: -
- ```python - with IncrementalStrategy(): - df = reader.run() # something went wrong here - writer.run(df) # or here - # or here... - ``` -
- When DBReader will **NOT** update HWM in HWM Store. - This allows to resume reading process from the *last successful run*. - -For [File Downloader](../file/file_downloader/file_downloader.md#file-downloader): -: Behavior depends on `hwm` type. -
- FileListHWM -
- First incremental run is just the same as [`SnapshotStrategy`](snapshot_strategy.md#onetl.strategy.snapshot_strategy.SnapshotStrategy) - - all files are downloaded: -
- ```bash - $ hdfs dfs -ls /path -
- /path/my/file1 - /path/my/file2 - ``` -
- ```python - DownloadResult( - ..., - successful={ - LocalFile("/downloaded/file1"), - LocalFile("/downloaded/file2"), - }, - ) - ``` -
- Then the list of original file paths is saved as `FileListHWM` object into [HWM Store](../hwm_store/index.md#hwm): -
- ```python - FileListHWM( - ..., - entity="/path", - value=[ - "/path/my/file1", - "/path/my/file2", - ], - ) - ``` -
- Next incremental run will download only new files which were added to the source since previous run: -
- ```bash - $ hdfs dfs -ls /path -
- /path/my/file1 - /path/my/file2 - /path/my/file3 - ``` -
- ```python - # only files which are not covered by FileListHWM - DownloadResult( - ..., - successful={ - LocalFile("/downloaded/file3"), - }, - ) - ``` -
- Value of `FileListHWM` will be updated and saved to [HWM Store](../hwm_store/index.md#hwm): -
- ```python - FileListHWM( - ..., - directory="/path", - value=[ - "/path/my/file1", - "/path/my/file2", - "/path/my/file3", - ], - ) - ``` -
- FileModifiedTimeHWM -
- First incremental run is just the same as [`SnapshotStrategy`](snapshot_strategy.md#onetl.strategy.snapshot_strategy.SnapshotStrategy) - - all files are downloaded: -
- ```bash - $ hdfs dfs -ls /path -
- /path/my/file1 - /path/my/file2 - ``` -
- ```python - DownloadResult( - ..., - successful={ - LocalFile("/downloaded/file1"), - LocalFile("/downloaded/file2"), - }, - ) - ``` -
- Then the maximum modified time of original files is saved as `FileModifiedTimeHWM` object into [HWM Store](../hwm_store/index.md#hwm): -
- ```python - FileModifiedTimeHWM( - ..., - directory="/path", - value=datetime.datetime(2025, 1, 1, 11, 22, 33, 456789, tzinfo=timezone.utc), - ) - ``` -
- Next incremental run will download only files from the source which were modified or created since previous run: -
- ```bash - $ hdfs dfs -ls /path -
- /path/my/file1 - /path/my/file2 - /path/my/file3 - ``` -
- ```python - # only files which are not covered by FileModifiedTimeHWM - DownloadResult( - ..., - successful={ - LocalFile("/downloaded/file3"), - }, - ) - ``` -
- Value of `FileModifiedTimeHWM` will be updated and and saved to [HWM Store](../hwm_store/index.md#hwm): -
- ```python - FileModifiedTimeHWM( - ..., - directory="/path", - value=datetime.datetime(2025, 1, 1, 22, 33, 44, 567890, tzinfo=timezone.utc), - ) - ``` -
- #### WARNING - FileDownload updates HWM in HWM Store at the end of `.run()` call, - **NOT** while exiting strategy context. This is because: -
- * FileDownloader does not raise exceptions if some file cannot be downloaded. - * FileDownloader creates files on local filesystem, and file content may differ for different `modes`. - * It can remove files from the source if `delete_source` is set to `True`. - -#### Versionadded -Added in version 0.1.0. - -* **Parameters:** - **offset** - : If passed, the offset value will be used to read rows which appeared in the source after the previous read. -
- For example, previous incremental run returned rows: - ```default - 898 - 899 - 900 - 1000 - ``` -
- Current HWM value is 1000. -
- But since then few more rows appeared in the source: - ```default - 898 - 899 - 900 - 901 # new - 902 # new - ... - 999 # new - 1000 - ``` -
- and you need to read them too. -
- So you can set `offset=100`, so a next incremental run will generate SQL query like: - ```sql - SELECT id, data FROM public.mydata WHERE id > 900; - -- 900 = 1000 - 100 = hwm - offset - ``` -
- and return rows since 901 (**not** 900), **including** 1000 which was already captured by HWM. -
- #### WARNING - This can lead to reading duplicated values from the table. - You probably need additional deduplication step to handle them -
- #### WARNING - Cannot be used with [File Downloader](../file/file_downloader/file_downloader.md#file-downloader) -
- #### NOTE - `offset` value will be subtracted from the HWM, so it should have a proper type. -
- For example, for `TIMESTAMP` column `offset` type should be `datetime.timedelta`, not `int` - -### Examples - -Incremental run with [DB Reader](../db/db_reader.md#db-reader) - -```python -from onetl.db import DBReader, DBWriter -from onetl.strategy import IncrementalStrategy - -reader = DBReader( - connection=postgres, - source="public.mydata", - columns=["id", "data"], - hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="id"), -) - -writer = DBWriter(connection=hive, target="db.newtable") - -with IncrementalStrategy(): - df = reader.run() - writer.run(df) -``` - -```sql --- previous HWM value was 1000 --- DBReader will generate query like: - -SELECT id, data -FROM public.mydata -WHERE id > 1000; --- from HWM (EXCLUDING first row) -``` - -Incremental run with [DB Reader](../db/db_reader.md#db-reader) and `IncrementalStrategy(offset=...)` - -```python -from onetl.db import DBReader, DBWriter -from onetl.strategy import IncrementalStrategy - -reader = DBReader( - connection=postgres, - source="public.mydata", - columns=["id", "data"], - hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="id"), -) - -writer = DBWriter(connection=hive, target="db.newtable") - -with IncrementalStrategy(offset=100): - df = reader.run() - writer.run(df) -``` - -```sql --- previous HWM value was 1000 --- DBReader will generate query like: - -SELECT id, data -FROM public.mydata -WHERE id > 900; -- from HWM-offset (EXCLUDING first row) -``` - -`offset` and `hwm.expression` can be a date or datetime, not only integer: - -```python -from onetl.db import DBReader, DBWriter -from datetime import timedelta - -reader = DBReader( - connection=postgres, - source="public.mydata", - columns=["business_dt", "data"], - hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="business_dt"), -) - -writer = DBWriter(connection=hive, target="db.newtable") - -with IncrementalStrategy(offset=timedelta(days=1)): - df = reader.run() - writer.run(df) -``` - -```sql --- previous HWM value was '2021-01-10' --- DBReader will generate query like: - -SELECT business_dt, data -FROM public.mydata -WHERE business_dt > CAST('2021-01-09' AS DATE); -- from HWM-offset (EXCLUDING first row) -``` - -Incremental run with [DB Reader](../db/db_reader.md#db-reader) and [Kafka](../connection/db_connection/kafka/index.md#kafka) - -```py -from onetl.db import DBReader, DBWriter -from onetl.strategy import IncrementalStrategy - -reader = DBReader( - connection=kafka, - source="topic_name", - hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="offset"), -) - -writer = DBWriter(connection=hive, target="db.newtable") - -with IncrementalStrategy(): - df = reader.run() - -# current run will fetch only messages which were added since previous run -``` - -Incremental run with [File Downloader](../file/file_downloader/file_downloader.md#file-downloader) and `hwm=FileListHWM(...)` - -```py -from onetl.file import FileDownloader -from onetl.strategy import SnapshotStrategy -from etl_entities.hwm import FileListHWM - -downloader = FileDownloader( - connection=sftp, - source_path="/remote", - local_path="/local", - hwm=FileListHWM( # mandatory for IncrementalStrategy - name="my_unique_hwm_name", - ), -) - -with IncrementalStrategy(): - df = downloader.run() - -# current run will download only files which were added since previous run -``` - -Incremental run with [File Downloader](../file/file_downloader/file_downloader.md#file-downloader) and `hwm=FileModifiedTimeHWM(...)` - -```py -from onetl.file import FileDownloader -from onetl.strategy import SnapshotStrategy -from etl_entities.hwm import FileModifiedTimeHWM - -downloader = FileDownloader( - connection=sftp, - source_path="/remote", - local_path="/local", - hwm=FileModifiedTimeHWM( # mandatory for IncrementalStrategy - name="my_unique_hwm_name", - ), -) - -with IncrementalStrategy(): - df = downloader.run() - -# current run will download only files which were modified/created since previous run -``` - - - -#### \_\_init_\_(\*\*kwargs) - -Create a new model by parsing and validating input data from keyword arguments. - -Raises ValidationError if the input data cannot be parsed to form a valid model. - - diff --git a/mddocs/markdown/strategy/index.md b/mddocs/markdown/strategy/index.md deleted file mode 100644 index 7791fed74..000000000 --- a/mddocs/markdown/strategy/index.md +++ /dev/null @@ -1,10 +0,0 @@ - - -# Read Strategies - -onETL have several builtin strategies for reading data: - -1. [Snapshot Strategy](snapshot_strategy.md) -2. [Incremental Strategy](incremental_strategy.md) -3. [Snapshot Batch Strategy](snapshot_batch_strategy.md) -4. [Incremental Batch Strategy](incremental_batch_strategy.md) diff --git a/mddocs/markdown/strategy/snapshot_batch_strategy.md b/mddocs/markdown/strategy/snapshot_batch_strategy.md deleted file mode 100644 index da95f8133..000000000 --- a/mddocs/markdown/strategy/snapshot_batch_strategy.md +++ /dev/null @@ -1,281 +0,0 @@ - - -# Snapshot Batch Strategy - -### *class* onetl.strategy.snapshot_strategy.SnapshotBatchStrategy(\*, hwm: HWM | None = None, step: Any = None, start: Any = None, stop: Any = None) - -Snapshot batch strategy for [DB Reader](../db/db_reader.md#db-reader). - -#### NOTE -Cannot be used with [File Downloader](../file/file_downloader/file_downloader.md#file-downloader) - -Same as [`SnapshotStrategy`](snapshot_strategy.md#onetl.strategy.snapshot_strategy.SnapshotStrategy), -but reads data from the source in sequential batches (1..N) like: - -```sql -1: SELECT id, data - FROM public.mydata - WHERE id >= 1000 AND id <= 1100; -- from start to start+step (INCLUDING first row) - -2: WHERE id > 1100 AND id <= 1200; -- + step -3: WHERE id > 1200 AND id <= 1200; -- + step -N: WHERE id > 1300 AND id <= 1400; -- until stop -``` - -This allows to use less CPU and RAM on Spark cluster than reading all the data in parallel, -but takes proportionally more time. - -#### NOTE -This strategy uses HWM column value to filter data for each batch, -but does **NOT** save it into [HWM Store](../hwm_store/index.md#hwm). -So every run starts from the beginning, not from the previous HWM value. - -#### NOTE -If you only need to reduce number of rows read by Spark from opened cursor, -use `onetl.connection.db_connection.postgres.Postgres.ReadOptions.fetchsize` instead - -#### WARNING -Not every [DB connection](../connection/db_connection/index.md#db-connections) -supports batch strategy. For example, Kafka connection doesn’t support it. -Make sure the connection you use is compatible with the SnapshotBatchStrategy. - -#### Versionadded -Added in version 0.1.0. - -* **Parameters:** - **step** - : Step size used for generating batch SQL queries like: - ```sql - SELECT id, data - FROM public.mydata - WHERE id >= 1000 AND id <= 1100; -- 1000 is start value, step is 100 - ``` -
- #### NOTE - Step defines a range of values will be fetched by each batch. This is **not** - a number of rows, it depends on a table content and value distribution across the rows. -
- #### NOTE - `step` value will be added to the HWM, so it should have a proper type. -
- For example, for `TIMESTAMP` column `step` type should be `datetime.timedelta`, not `int` - - **start** - : If passed, the value will be used for generating WHERE clauses with `hwm.expression` filter, - as a start value for the first batch. -
- If not set, the value is determined by a separated query: - ```sql - SELECT MIN(id) as start - FROM public.mydata - WHERE id <= 1400; -- 1400 here is stop value (if set) - ``` -
- #### NOTE - `start` should be the same type as `hwm.expression` value, - e.g. `datetime.datetime` for `TIMESTAMP` column, `datetime.date` for `DATE`, and so on - - **stop** - : If passed, the value will be used for generating WHERE clauses with `hwm.expression` filter, - as a stop value for the last batch. -
- If not set, the value is determined by a separated query: - ```sql - SELECT MAX(id) as stop - FROM public.mydata - WHERE id >= 1000; -- 1000 here is start value (if set) - ``` -
- #### NOTE - `stop` should be the same type as `hwm.expression` value, - e.g. `datetime.datetime` for `TIMESTAMP` column, `datetime.date` for `DATE`, and so on - -### Examples - -SnapshotBatch run - -```python -from onetl.db import DBReader, DBWriter -from onetl.strategy import SnapshotBatchStrategy - -reader = DBReader( - connection=postgres, - source="public.mydata", - columns=["id", "data"], - hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="id"), -) - -writer = DBWriter(connection=hive, target="db.newtable") - -with SnapshotBatchStrategy(step=100) as batches: - for _ in batches: - df = reader.run() - writer.run(df) -``` - -```sql --- get start and stop values - - SELECT MIN(id) as start, MAX(id) as stop - FROM public.mydata; - --- for example, start=1000 and stop=2345 - --- when each batch (1..N) will perform a query which return some part of input data - -1: SELECT id, data - FROM public.mydata - WHERE id >= 1000 AND id <= 1100; -- from start to start+step (INCLUDING first row) - -2: WHERE id > 1100 AND id <= 1200; -- + step -3: WHERE id > 1200 AND id <= 1300; -- + step -N: WHERE id > 2300 AND id <= 2345; -- until stop -``` - -SnapshotBatch run with `stop` value - -```python -... - -with SnapshotBatchStrategy(step=100, stop=1234) as batches: - for _ in batches: - df = reader.run() - writer.run(df) -``` - -```sql --- stop value is set, so there is no need to fetch it from DB --- get start value - - SELECT MIN(id) as start - FROM public.mydata - WHERE id <= 1234; -- until stop - --- for example, start=1000. --- when each batch (1..N) will perform a query which return some part of input data - -1: SELECT id, data - FROM public.mydata - WHERE id >= 1000 AND id <= 1100; -- from start to start+step (INCLUDING first row) - -2: WHERE id > 1100 AND id <= 1200; -- + step -3: WHERE id > 1200 AND id <= 1300; -- + step -N: WHERE id > 1300 AND id <= 1234; -- until stop -``` - -SnapshotBatch run with `start` value - -```python -... - -with SnapshotBatchStrategy(step=100, start=500) as batches: - for _ in batches: - df = reader.run() - writer.run(df) -``` - -```sql --- start value is set, so there is no need to fetch it from DB --- get only stop value - - SELECT MAX(id) as stop - FROM public.mydata - WHERE id >= 500; -- from start - --- for example, stop=2345. --- when each batch (1..N) will perform a query which return some part of input data - -1: SELECT id, data - FROM public.mydata - WHERE id >= 500 AND id <= 600; -- from start to start+step (INCLUDING first row) - -2: WHERE id > 600 AND id <= 700; -- + step -3: WHERE id > 700 AND id <= 800; -- + step -... -N: WHERE id > 2300 AND id <= 2345; -- until stop -``` - -SnapshotBatch run with all options - -```python -... - -with SnapshotBatchStrategy( - start=1000, - step=100, - stop=2000, -) as batches: - for _ in batches: - df = reader.run() - writer.run(df) -``` - -```sql --- start and stop values are set, so no need to fetch boundaries from DB --- each batch (1..N) will perform a query which return some part of input data - -1: SELECT id, data - FROM public.mydata - WHERE id >= 1000 AND id <= 1100; -- from start to start+step (INCLUDING first row) - -2: WHERE id > 1100 AND id <= 1200; -- + step -3: WHERE id > 1200 AND id <= 1300; -- + step -... -N: WHERE id > 1900 AND id <= 2000; -- until stop -``` - -SnapshotBatch run over non-integer column - -`hwm.expression`, `start` and `stop` can be a date or datetime, not only integer: - -```python -from datetime import date, timedelta - -reader = DBReader( - connection=postgres, - source="public.mydata", - columns=["business_dt", "data"], - hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="business_dt"), -) - -with SnapshotBatchStrategy( - start=date("2021-01-01"), - step=timedelta(days=5), - stop=date("2021-01-31"), -) as batches: - for _ in batches: - df = reader.run() - writer.run(df) -``` - -```sql --- start and stop values are set, so no need to fetch boundaries from DB --- each batch will perform a query which return some part of input data --- HWM value will casted to match column type - -1: SELECT business_dt, data - FROM public.mydata - WHERE business_dt >= CAST('2020-01-01' AS DATE) -- from start to start+step (INCLUDING first row) - AND business_dt <= CAST('2021-01-05' AS DATE); - -2: WHERE business_dt > CAST('2021-01-05' AS DATE) -- + step - AND business_dt <= CAST('2021-01-10' AS DATE); - -3: WHERE business_dt > CAST('2021-01-10' AS DATE) -- + step - AND business_dt <= CAST('2021-01-15' AS DATE); - -... - -N: WHERE business_dt > CAST('2021-01-30' AS DATE) - AND business_dt <= CAST('2021-01-31' AS DATE); -- until stop -``` - - - -#### \_\_init_\_(\*\*kwargs) - -Create a new model by parsing and validating input data from keyword arguments. - -Raises ValidationError if the input data cannot be parsed to form a valid model. - - diff --git a/mddocs/markdown/strategy/snapshot_strategy.md b/mddocs/markdown/strategy/snapshot_strategy.md deleted file mode 100644 index 38ef7768c..000000000 --- a/mddocs/markdown/strategy/snapshot_strategy.md +++ /dev/null @@ -1,96 +0,0 @@ - - -# Snapshot Strategy - -### *class* onetl.strategy.snapshot_strategy.SnapshotStrategy - -Snapshot strategy for [DB Reader](../db/db_reader.md#db-reader)/[File Downloader](../file/file_downloader/file_downloader.md#file-downloader). - -Used for fetching all the rows/files from a source. Does not support HWM. - -#### NOTE -This is a default strategy. - -For [DB Reader](../db/db_reader.md#db-reader): -: Every snapshot run is executing the simple query which fetches all the table data: -
- ```sql - SELECT id, data FROM public.mydata; - ``` - -For [File Downloader](../file/file_downloader/file_downloader.md#file-downloader): -: Every snapshot run is downloading all the files (from the source, or user-defined list): -
- ```bash - $ hdfs dfs -ls /path -
- /path/my/file1 - /path/my/file2 - ``` -
- ```python - DownloadResult( - ..., - successful={ - LocalFile("/downloaded/file1"), - LocalFile("/downloaded/file2"), - }, - ) - ``` - -#### Versionadded -Added in version 0.1.0. - -### Examples - -Snapshot run with [DB Reader](../db/db_reader.md#db-reader) - -```py -from onetl.db import DBReader, DBWriter -from onetl.strategy import SnapshotStrategy - -reader = DBReader( - connection=postgres, - source="public.mydata", - columns=["id", "data"], - hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="id"), -) - -writer = DBWriter(connection=hive, target="db.newtable") - -with SnapshotStrategy(): - df = reader.run() - writer.run(df) - -# current run will execute following query: - -# SELECT id, data FROM public.mydata; -``` - -Snapshot run with [File Downloader](../file/file_downloader/file_downloader.md#file-downloader) - -```py -from onetl.file import FileDownloader -from onetl.strategy import SnapshotStrategy - -downloader = FileDownloader( - connection=sftp, - source_path="/remote", - local_path="/local", -) - -with SnapshotStrategy(): - df = downloader.run() - -# current run will download all files from 'source_path' -``` - - - -#### \_\_init_\_(\*\*kwargs) - -Create a new model by parsing and validating input data from keyword arguments. - -Raises ValidationError if the input data cannot be parsed to form a valid model. - - diff --git a/mddocs/markdown/troubleshooting/index.md b/mddocs/markdown/troubleshooting/index.md deleted file mode 100644 index 847f426e9..000000000 --- a/mddocs/markdown/troubleshooting/index.md +++ /dev/null @@ -1,17 +0,0 @@ - - -# Troubleshooting - -In case of error please follow instructions below: - -* Read the logs or exception messages you’ve faced with. - : * If Python logs are note verbose enough, [increase the log level](../logging.md#logging). - * If Spark logs are note verbose enough, [increase the log level](spark.md#troubleshooting-spark). -* Read documentation related to a class or method you are using. -* [Google](https://google.com) the error message, and carefully read the search result: - : * [StackOverflow](https://stackoverflow.com/) answers. - * [Spark](https://spark.apache.org/docs/latest/) documentation. - * Documentation of database or filesystem you are connecting to. - * Documentation of underlying connector. -* Search for known [issues](https://github.com/MobileTeleSystems/onetl/issues), or create a new one. -* Always use the most resent versions of onETL, PySpark and connector packages, [compatible with your environment](../install/spark.md#install-spark). diff --git a/mddocs/markdown/troubleshooting/spark.md b/mddocs/markdown/troubleshooting/spark.md deleted file mode 100644 index 812b38acc..000000000 --- a/mddocs/markdown/troubleshooting/spark.md +++ /dev/null @@ -1,72 +0,0 @@ - - -# Spark Troubleshooting - -## Restarting Spark session - -Sometimes it is required to stop current Spark session and start a new one, e.g. to add some .jar packages, or change session config. -But PySpark not only starts Spark session, but also starts Java virtual machine (JVM) process in the background, -which. So calling `sparkSession.stop()` [does not shutdown JVM](https://issues.apache.org/jira/browse/SPARK-47740), -and this can cause some issue. - -Also apart from JVM properties, stopping Spark session does not clear Spark context, which is a global object. So new -Spark sessions are created using the same context object, and thus using the same Spark config options. - -To properly stop Spark session, it is **required** to: -\* Stop Spark session by calling `sparkSession.stop()`. -\* **STOP PYTHON INTERPRETER**, e.g. by calling `sys.exit()`. -\* Start new Python interpreter. -\* Start new Spark session with config options you need. - -Skipping some of these steps can lead to issues with creating new Spark session. - -## Driver log level - -Default logging level for Spark session is `WARN`. To show more verbose logs, use: - -```python -spark.sparkContext.setLogLevel("INFO") -``` - -or increase verbosity even more: - -```python -spark.sparkContext.setLogLevel("DEBUG") -``` - -After getting all information you need, you can return back the previous log level: - -```python -spark.sparkContext.setLogLevel("WARN") -``` - -## Executors log level - -`sparkContext.setLogLevel` changes only log level of Spark session on Spark **driver**. -To make Spark executor logs more verbose, perform following steps: - -* Create `log4j.properties` file with content like this: - ```jproperties - log4j.rootCategory=DEBUG, console - - log4j.appender.console=org.apache.log4j.ConsoleAppender - log4j.appender.console.target=System.err - log4j.appender.console.layout=org.apache.log4j.PatternLayout - log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{1}: %m%n - ``` -* Stop existing Spark session and create a new one with following options: - ```python - from pyspark.sql import SparkSession - - spark = ( - SparkSesion.builder.config("spark.files", "file:log4j.properties").config( - "spark.executor.extraJavaOptions", "-Dlog4j.configuration=file:log4j.properties" - ) - # you can apply the same logging settings to Spark driver, by uncommenting the line below - # .config("spark.driver.extraJavaOptions", "-Dlog4j.configuration=file:log4j.properties") - .getOrCreate() - ) - ``` - -Each Spark executor will receive a copy of `log4j.properties` file during start, and load it to change own log level. -Same approach can be used for Spark driver as well, to investigate issue when Spark session cannot properly start. From 8a8f515f9732f8126a764f8ec1526b851d3d7bd1 Mon Sep 17 00:00:00 2001 From: Sattar Gyulmamedov Date: Mon, 9 Jun 2025 12:31:29 +0300 Subject: [PATCH 06/22] attempt to convert docs to md --- mddocs/docs/Makefile | 20 + mddocs/docs/_build/doctrees/changelog.doctree | Bin 0 -> 3515 bytes .../_build/doctrees/changelog/0.10.0.doctree | Bin 0 -> 89726 bytes .../_build/doctrees/changelog/0.10.1.doctree | Bin 0 -> 14288 bytes .../_build/doctrees/changelog/0.10.2.doctree | Bin 0 -> 17371 bytes .../_build/doctrees/changelog/0.11.0.doctree | Bin 0 -> 61611 bytes .../_build/doctrees/changelog/0.11.1.doctree | Bin 0 -> 5875 bytes .../_build/doctrees/changelog/0.11.2.doctree | Bin 0 -> 4279 bytes .../_build/doctrees/changelog/0.12.0.doctree | Bin 0 -> 22523 bytes .../_build/doctrees/changelog/0.12.1.doctree | Bin 0 -> 9237 bytes .../_build/doctrees/changelog/0.12.2.doctree | Bin 0 -> 8618 bytes .../_build/doctrees/changelog/0.12.3.doctree | Bin 0 -> 4324 bytes .../_build/doctrees/changelog/0.12.4.doctree | Bin 0 -> 4543 bytes .../_build/doctrees/changelog/0.12.5.doctree | Bin 0 -> 8465 bytes .../_build/doctrees/changelog/0.13.0.doctree | Bin 0 -> 53197 bytes .../_build/doctrees/changelog/0.13.1.doctree | Bin 0 -> 5168 bytes .../_build/doctrees/changelog/0.13.3.doctree | Bin 0 -> 4173 bytes .../_build/doctrees/changelog/0.13.4.doctree | Bin 0 -> 8188 bytes .../_build/doctrees/changelog/0.7.0.doctree | Bin 0 -> 56112 bytes .../_build/doctrees/changelog/0.7.1.doctree | Bin 0 -> 8208 bytes .../_build/doctrees/changelog/0.7.2.doctree | Bin 0 -> 9590 bytes .../_build/doctrees/changelog/0.8.0.doctree | Bin 0 -> 43939 bytes .../_build/doctrees/changelog/0.8.1.doctree | Bin 0 -> 15121 bytes .../_build/doctrees/changelog/0.9.0.doctree | Bin 0 -> 52908 bytes .../_build/doctrees/changelog/0.9.1.doctree | Bin 0 -> 4980 bytes .../_build/doctrees/changelog/0.9.2.doctree | Bin 0 -> 11080 bytes .../_build/doctrees/changelog/0.9.3.doctree | Bin 0 -> 3709 bytes .../_build/doctrees/changelog/0.9.4.doctree | Bin 0 -> 14825 bytes .../_build/doctrees/changelog/0.9.5.doctree | Bin 0 -> 7063 bytes .../_build/doctrees/changelog/DRAFT.doctree | Bin 0 -> 2898 bytes .../doctrees/changelog/NEXT_RELEASE.doctree | Bin 0 -> 3091 bytes .../_build/doctrees/changelog/index.doctree | Bin 0 -> 3923 bytes mddocs/docs/_build/doctrees/concepts.doctree | Bin 0 -> 70091 bytes .../clickhouse/connection.doctree | Bin 0 -> 3727 bytes .../db_connection/clickhouse/execute.doctree | Bin 0 -> 23021 bytes .../db_connection/clickhouse/index.doctree | Bin 0 -> 5083 bytes .../clickhouse/prerequisites.doctree | Bin 0 -> 16167 bytes .../db_connection/clickhouse/read.doctree | Bin 0 -> 17494 bytes .../db_connection/clickhouse/sql.doctree | Bin 0 -> 11917 bytes .../db_connection/clickhouse/types.doctree | Bin 0 -> 121722 bytes .../db_connection/clickhouse/write.doctree | Bin 0 -> 8777 bytes .../greenplum/connection.doctree | Bin 0 -> 3719 bytes .../db_connection/greenplum/execute.doctree | Bin 0 -> 25629 bytes .../db_connection/greenplum/index.doctree | Bin 0 -> 5016 bytes .../greenplum/prerequisites.doctree | Bin 0 -> 75612 bytes .../db_connection/greenplum/read.doctree | Bin 0 -> 51663 bytes .../db_connection/greenplum/types.doctree | Bin 0 -> 91709 bytes .../db_connection/greenplum/write.doctree | Bin 0 -> 18353 bytes .../db_connection/hive/connection.doctree | Bin 0 -> 3679 bytes .../db_connection/hive/execute.doctree | Bin 0 -> 10194 bytes .../db_connection/hive/index.doctree | Bin 0 -> 4974 bytes .../db_connection/hive/prerequisites.doctree | Bin 0 -> 21586 bytes .../db_connection/hive/read.doctree | Bin 0 -> 23064 bytes .../db_connection/hive/slots.doctree | Bin 0 -> 3639 bytes .../connection/db_connection/hive/sql.doctree | Bin 0 -> 16924 bytes .../db_connection/hive/write.doctree | Bin 0 -> 52533 bytes .../connection/db_connection/index.doctree | Bin 0 -> 4613 bytes .../db_connection/kafka/auth.doctree | Bin 0 -> 3639 bytes .../db_connection/kafka/basic_auth.doctree | Bin 0 -> 3691 bytes .../db_connection/kafka/connection.doctree | Bin 0 -> 3687 bytes .../db_connection/kafka/index.doctree | Bin 0 -> 5696 bytes .../db_connection/kafka/kerberos_auth.doctree | Bin 0 -> 3718 bytes .../kafka/plaintext_protocol.doctree | Bin 0 -> 3763 bytes .../db_connection/kafka/prerequisites.doctree | Bin 0 -> 25211 bytes .../db_connection/kafka/protocol.doctree | Bin 0 -> 3671 bytes .../db_connection/kafka/read.doctree | Bin 0 -> 21896 bytes .../db_connection/kafka/scram_auth.doctree | Bin 0 -> 3691 bytes .../db_connection/kafka/slots.doctree | Bin 0 -> 3647 bytes .../db_connection/kafka/ssl_protocol.doctree | Bin 0 -> 3709 bytes .../kafka/troubleshooting.doctree | Bin 0 -> 5030 bytes .../db_connection/kafka/write.doctree | Bin 0 -> 12554 bytes .../db_connection/mongodb/connection.doctree | Bin 0 -> 3703 bytes .../db_connection/mongodb/index.doctree | Bin 0 -> 4983 bytes .../db_connection/mongodb/pipeline.doctree | Bin 0 -> 7528 bytes .../mongodb/prerequisites.doctree | Bin 0 -> 15809 bytes .../db_connection/mongodb/read.doctree | Bin 0 -> 19848 bytes .../db_connection/mongodb/types.doctree | Bin 0 -> 56489 bytes .../db_connection/mongodb/write.doctree | Bin 0 -> 6983 bytes .../db_connection/mssql/connection.doctree | Bin 0 -> 3687 bytes .../db_connection/mssql/execute.doctree | Bin 0 -> 21078 bytes .../db_connection/mssql/index.doctree | Bin 0 -> 4993 bytes .../db_connection/mssql/prerequisites.doctree | Bin 0 -> 18921 bytes .../db_connection/mssql/read.doctree | Bin 0 -> 17286 bytes .../db_connection/mssql/sql.doctree | Bin 0 -> 11727 bytes .../db_connection/mssql/types.doctree | Bin 0 -> 95394 bytes .../db_connection/mssql/write.doctree | Bin 0 -> 8314 bytes .../db_connection/mysql/connection.doctree | Bin 0 -> 3687 bytes .../db_connection/mysql/execute.doctree | Bin 0 -> 21387 bytes .../db_connection/mysql/index.doctree | Bin 0 -> 4993 bytes .../db_connection/mysql/prerequisites.doctree | Bin 0 -> 14364 bytes .../db_connection/mysql/read.doctree | Bin 0 -> 17971 bytes .../db_connection/mysql/sql.doctree | Bin 0 -> 12035 bytes .../db_connection/mysql/types.doctree | Bin 0 -> 90824 bytes .../db_connection/mysql/write.doctree | Bin 0 -> 8552 bytes .../db_connection/oracle/connection.doctree | Bin 0 -> 3695 bytes .../db_connection/oracle/execute.doctree | Bin 0 -> 21845 bytes .../db_connection/oracle/index.doctree | Bin 0 -> 5011 bytes .../oracle/prerequisites.doctree | Bin 0 -> 21687 bytes .../db_connection/oracle/read.doctree | Bin 0 -> 18115 bytes .../db_connection/oracle/sql.doctree | Bin 0 -> 12091 bytes .../db_connection/oracle/types.doctree | Bin 0 -> 90841 bytes .../db_connection/oracle/write.doctree | Bin 0 -> 8354 bytes .../db_connection/postgres/connection.doctree | Bin 0 -> 3711 bytes .../db_connection/postgres/execute.doctree | Bin 0 -> 21207 bytes .../db_connection/postgres/index.doctree | Bin 0 -> 5047 bytes .../postgres/prerequisites.doctree | Bin 0 -> 15639 bytes .../db_connection/postgres/read.doctree | Bin 0 -> 17378 bytes .../db_connection/postgres/sql.doctree | Bin 0 -> 11841 bytes .../db_connection/postgres/types.doctree | Bin 0 -> 113678 bytes .../db_connection/postgres/write.doctree | Bin 0 -> 8449 bytes .../db_connection/teradata/connection.doctree | Bin 0 -> 3711 bytes .../db_connection/teradata/execute.doctree | Bin 0 -> 21310 bytes .../db_connection/teradata/index.doctree | Bin 0 -> 4805 bytes .../teradata/prerequisites.doctree | Bin 0 -> 13774 bytes .../db_connection/teradata/read.doctree | Bin 0 -> 24880 bytes .../db_connection/teradata/sql.doctree | Bin 0 -> 11614 bytes .../db_connection/teradata/write.doctree | Bin 0 -> 25133 bytes .../connection/file_connection/ftp.doctree | Bin 0 -> 3624 bytes .../connection/file_connection/ftps.doctree | Bin 0 -> 3633 bytes .../file_connection/hdfs/connection.doctree | Bin 0 -> 3681 bytes .../file_connection/hdfs/index.doctree | Bin 0 -> 4566 bytes .../file_connection/hdfs/slots.doctree | Bin 0 -> 3641 bytes .../connection/file_connection/index.doctree | Bin 0 -> 4404 bytes .../connection/file_connection/s3.doctree | Bin 0 -> 3615 bytes .../connection/file_connection/samba.doctree | Bin 0 -> 3642 bytes .../connection/file_connection/sftp.doctree | Bin 0 -> 3633 bytes .../connection/file_connection/webdav.doctree | Bin 0 -> 3651 bytes .../file_df_connection/base.doctree | Bin 0 -> 3708 bytes .../file_df_connection/index.doctree | Bin 0 -> 4576 bytes .../spark_hdfs/connection.doctree | Bin 0 -> 3732 bytes .../spark_hdfs/index.doctree | Bin 0 -> 4435 bytes .../spark_hdfs/prerequisites.doctree | Bin 0 -> 11394 bytes .../spark_hdfs/slots.doctree | Bin 0 -> 3692 bytes .../file_df_connection/spark_local_fs.doctree | Bin 0 -> 3678 bytes .../spark_s3/connection.doctree | Bin 0 -> 3716 bytes .../file_df_connection/spark_s3/index.doctree | Bin 0 -> 4243 bytes .../spark_s3/prerequisites.doctree | Bin 0 -> 13870 bytes .../spark_s3/troubleshooting.doctree | Bin 0 -> 73472 bytes .../_build/doctrees/connection/index.doctree | Bin 0 -> 4261 bytes .../docs/_build/doctrees/contributing.doctree | Bin 0 -> 57669 bytes .../docs/_build/doctrees/db/db_reader.doctree | Bin 0 -> 4247 bytes .../docs/_build/doctrees/db/db_writer.doctree | Bin 0 -> 4247 bytes mddocs/docs/_build/doctrees/db/index.doctree | Bin 0 -> 3763 bytes .../file_downloader/file_downloader.doctree | Bin 0 -> 4313 bytes .../file/file_downloader/index.doctree | Bin 0 -> 4189 bytes .../file/file_downloader/options.doctree | Bin 0 -> 3723 bytes .../file/file_downloader/result.doctree | Bin 0 -> 3715 bytes .../doctrees/file/file_filters/base.doctree | Bin 0 -> 4303 bytes .../file/file_filters/exclude_dir.doctree | Bin 0 -> 3667 bytes .../file/file_filters/file_filter.doctree | Bin 0 -> 3677 bytes .../file_filters/file_mtime_filter.doctree | Bin 0 -> 3713 bytes .../file_filters/file_size_filter.doctree | Bin 0 -> 3672 bytes .../doctrees/file/file_filters/glob.doctree | Bin 0 -> 3608 bytes .../doctrees/file/file_filters/index.doctree | Bin 0 -> 4687 bytes .../file_filters/match_all_filters.doctree | Bin 0 -> 3688 bytes .../doctrees/file/file_filters/regexp.doctree | Bin 0 -> 3626 bytes .../doctrees/file/file_limits/base.doctree | Bin 0 -> 4278 bytes .../file/file_limits/file_limit.doctree | Bin 0 -> 3667 bytes .../doctrees/file/file_limits/index.doctree | Bin 0 -> 4643 bytes .../file/file_limits/limits_reached.doctree | Bin 0 -> 3663 bytes .../file/file_limits/limits_stop_at.doctree | Bin 0 -> 3663 bytes .../file/file_limits/max_files_count.doctree | Bin 0 -> 3670 bytes .../file/file_limits/reset_limits.doctree | Bin 0 -> 3647 bytes .../file/file_limits/total_files_size.doctree | Bin 0 -> 3703 bytes .../file/file_mover/file_mover.doctree | Bin 0 -> 4268 bytes .../doctrees/file/file_mover/index.doctree | Bin 0 -> 4114 bytes .../doctrees/file/file_mover/options.doctree | Bin 0 -> 3683 bytes .../doctrees/file/file_mover/result.doctree | Bin 0 -> 3675 bytes .../file/file_uploader/file_uploader.doctree | Bin 0 -> 4295 bytes .../doctrees/file/file_uploader/index.doctree | Bin 0 -> 4159 bytes .../file/file_uploader/options.doctree | Bin 0 -> 3707 bytes .../file/file_uploader/result.doctree | Bin 0 -> 3699 bytes .../docs/_build/doctrees/file/index.doctree | Bin 0 -> 3893 bytes .../file_df_reader/file_df_reader.doctree | Bin 0 -> 3671 bytes .../file_df/file_df_reader/index.doctree | Bin 0 -> 4147 bytes .../file_df/file_df_reader/options.doctree | Bin 0 -> 3672 bytes .../file_df_writer/file_df_writer.doctree | Bin 0 -> 3671 bytes .../file_df/file_df_writer/index.doctree | Bin 0 -> 4147 bytes .../file_df/file_df_writer/options.doctree | Bin 0 -> 3672 bytes .../file_df/file_formats/avro.doctree | Bin 0 -> 3631 bytes .../file_df/file_formats/base.doctree | Bin 0 -> 3671 bytes .../doctrees/file_df/file_formats/csv.doctree | Bin 0 -> 3622 bytes .../file_df/file_formats/excel.doctree | Bin 0 -> 3640 bytes .../file_df/file_formats/index.doctree | Bin 0 -> 4513 bytes .../file_df/file_formats/json.doctree | Bin 0 -> 3631 bytes .../file_df/file_formats/jsonline.doctree | Bin 0 -> 3667 bytes .../doctrees/file_df/file_formats/orc.doctree | Bin 0 -> 3622 bytes .../file_df/file_formats/parquet.doctree | Bin 0 -> 3658 bytes .../doctrees/file_df/file_formats/xml.doctree | Bin 0 -> 3622 bytes .../_build/doctrees/file_df/index.doctree | Bin 0 -> 3871 bytes .../docs/_build/doctrees/hooks/design.doctree | Bin 0 -> 87065 bytes .../doctrees/hooks/global_state.doctree | Bin 0 -> 4316 bytes .../docs/_build/doctrees/hooks/hook.doctree | Bin 0 -> 4394 bytes .../docs/_build/doctrees/hooks/index.doctree | Bin 0 -> 4859 bytes .../docs/_build/doctrees/hooks/slot.doctree | Bin 0 -> 4394 bytes .../doctrees/hooks/support_hooks.doctree | Bin 0 -> 4475 bytes .../_build/doctrees/hwm_store/index.doctree | Bin 0 -> 6473 bytes .../doctrees/hwm_store/yaml_hwm_store.doctree | Bin 0 -> 3654 bytes mddocs/docs/_build/doctrees/index.doctree | Bin 0 -> 42084 bytes .../_build/doctrees/install/files.doctree | Bin 0 -> 6284 bytes .../docs/_build/doctrees/install/full.doctree | Bin 0 -> 4917 bytes .../_build/doctrees/install/index.doctree | Bin 0 -> 8294 bytes .../_build/doctrees/install/kerberos.doctree | Bin 0 -> 9141 bytes .../_build/doctrees/install/spark.doctree | Bin 0 -> 68747 bytes mddocs/docs/_build/doctrees/logging.doctree | Bin 0 -> 27666 bytes mddocs/docs/_build/doctrees/plugins.doctree | Bin 0 -> 24191 bytes .../docs/_build/doctrees/quickstart.doctree | Bin 0 -> 24475 bytes mddocs/docs/_build/doctrees/security.doctree | Bin 0 -> 7490 bytes .../incremental_batch_strategy.doctree | Bin 0 -> 3751 bytes .../strategy/incremental_strategy.doctree | Bin 0 -> 3703 bytes .../_build/doctrees/strategy/index.doctree | Bin 0 -> 6430 bytes .../strategy/snapshot_batch_strategy.doctree | Bin 0 -> 3727 bytes .../strategy/snapshot_strategy.doctree | Bin 0 -> 3679 bytes .../doctrees/troubleshooting/index.doctree | Bin 0 -> 12252 bytes .../doctrees/troubleshooting/spark.doctree | Bin 0 -> 14303 bytes .../_sphinx_design_static/design-tabs.js | 101 +++ .../sphinx-design.min.css | 1 + .../markdown/_static/autodoc_pydantic.css | 11 + mddocs/docs/_build/markdown/changelog.md | 31 + .../docs/_build/markdown/changelog/0.10.0.md | 359 +++++++++ .../docs/_build/markdown/changelog/0.10.1.md | 25 + .../docs/_build/markdown/changelog/0.10.2.md | 34 + .../docs/_build/markdown/changelog/0.11.0.md | 202 +++++ .../docs/_build/markdown/changelog/0.11.1.md | 9 + .../docs/_build/markdown/changelog/0.11.2.md | 5 + .../docs/_build/markdown/changelog/0.12.0.md | 48 ++ .../docs/_build/markdown/changelog/0.12.1.md | 17 + .../docs/_build/markdown/changelog/0.12.2.md | 18 + .../docs/_build/markdown/changelog/0.12.3.md | 5 + .../docs/_build/markdown/changelog/0.12.4.md | 5 + .../docs/_build/markdown/changelog/0.12.5.md | 13 + .../docs/_build/markdown/changelog/0.13.0.md | 197 +++++ .../docs/_build/markdown/changelog/0.13.1.md | 9 + .../docs/_build/markdown/changelog/0.13.3.md | 5 + .../docs/_build/markdown/changelog/0.13.4.md | 10 + .../docs/_build/markdown/changelog/0.7.0.md | 211 ++++++ .../docs/_build/markdown/changelog/0.7.1.md | 31 + .../docs/_build/markdown/changelog/0.7.2.md | 33 + .../docs/_build/markdown/changelog/0.8.0.md | 137 ++++ .../docs/_build/markdown/changelog/0.8.1.md | 34 + .../docs/_build/markdown/changelog/0.9.0.md | 107 +++ .../docs/_build/markdown/changelog/0.9.1.md | 7 + .../docs/_build/markdown/changelog/0.9.2.md | 21 + .../docs/_build/markdown/changelog/0.9.3.md | 5 + .../docs/_build/markdown/changelog/0.9.4.md | 24 + .../docs/_build/markdown/changelog/0.9.5.md | 14 + .../docs/_build/markdown/changelog/DRAFT.md | 0 .../_build/markdown/changelog/NEXT_RELEASE.md | 1 + .../docs/_build/markdown/changelog/index.md | 29 + mddocs/docs/_build/markdown/concepts.md | 340 +++++++++ .../db_connection/clickhouse/connection.md | 3 + .../db_connection/clickhouse/execute.md | 98 +++ .../db_connection/clickhouse/index.md | 19 + .../db_connection/clickhouse/prerequisites.md | 73 ++ .../db_connection/clickhouse/read.md | 78 ++ .../db_connection/clickhouse/sql.md | 62 ++ .../db_connection/clickhouse/types.md | 347 +++++++++ .../db_connection/clickhouse/write.md | 42 ++ .../db_connection/greenplum/connection.md | 3 + .../db_connection/greenplum/execute.md | 102 +++ .../db_connection/greenplum/index.md | 18 + .../db_connection/greenplum/prerequisites.md | 358 +++++++++ .../db_connection/greenplum/read.md | 285 +++++++ .../db_connection/greenplum/types.md | 318 ++++++++ .../db_connection/greenplum/write.md | 47 ++ .../db_connection/hive/connection.md | 3 + .../connection/db_connection/hive/execute.md | 44 ++ .../connection/db_connection/hive/index.md | 19 + .../db_connection/hive/prerequisites.md | 128 ++++ .../connection/db_connection/hive/read.md | 92 +++ .../connection/db_connection/hive/slots.md | 3 + .../connection/db_connection/hive/sql.md | 68 ++ .../connection/db_connection/hive/write.md | 167 +++++ .../connection/db_connection/index.md | 16 + .../connection/db_connection/kafka/auth.md | 3 + .../db_connection/kafka/basic_auth.md | 3 + .../db_connection/kafka/connection.md | 3 + .../connection/db_connection/kafka/index.md | 31 + .../db_connection/kafka/kerberos_auth.md | 3 + .../db_connection/kafka/plaintext_protocol.md | 3 + .../db_connection/kafka/prerequisites.md | 65 ++ .../db_connection/kafka/protocol.md | 3 + .../connection/db_connection/kafka/read.md | 123 ++++ .../db_connection/kafka/scram_auth.md | 3 + .../connection/db_connection/kafka/slots.md | 3 + .../db_connection/kafka/ssl_protocol.md | 3 + .../db_connection/kafka/troubleshooting.md | 10 + .../connection/db_connection/kafka/write.md | 64 ++ .../db_connection/mongodb/connection.md | 3 + .../connection/db_connection/mongodb/index.md | 18 + .../db_connection/mongodb/pipeline.md | 19 + .../db_connection/mongodb/prerequisites.md | 72 ++ .../connection/db_connection/mongodb/read.md | 127 ++++ .../connection/db_connection/mongodb/types.md | 214 ++++++ .../connection/db_connection/mongodb/write.md | 33 + .../db_connection/mssql/connection.md | 3 + .../connection/db_connection/mssql/execute.md | 91 +++ .../connection/db_connection/mssql/index.md | 19 + .../db_connection/mssql/prerequisites.md | 78 ++ .../connection/db_connection/mssql/read.md | 78 ++ .../connection/db_connection/mssql/sql.md | 62 ++ .../connection/db_connection/mssql/types.md | 281 +++++++ .../connection/db_connection/mssql/write.md | 38 + .../db_connection/mysql/connection.md | 3 + .../connection/db_connection/mysql/execute.md | 92 +++ .../connection/db_connection/mysql/index.md | 19 + .../db_connection/mysql/prerequisites.md | 61 ++ .../connection/db_connection/mysql/read.md | 80 ++ .../connection/db_connection/mysql/sql.md | 63 ++ .../connection/db_connection/mysql/types.md | 292 ++++++++ .../connection/db_connection/mysql/write.md | 42 ++ .../db_connection/oracle/connection.md | 3 + .../db_connection/oracle/execute.md | 92 +++ .../connection/db_connection/oracle/index.md | 19 + .../db_connection/oracle/prerequisites.md | 110 +++ .../connection/db_connection/oracle/read.md | 80 ++ .../connection/db_connection/oracle/sql.md | 63 ++ .../connection/db_connection/oracle/types.md | 303 ++++++++ .../connection/db_connection/oracle/write.md | 38 + .../db_connection/postgres/connection.md | 3 + .../db_connection/postgres/execute.md | 90 +++ .../db_connection/postgres/index.md | 19 + .../db_connection/postgres/prerequisites.md | 71 ++ .../connection/db_connection/postgres/read.md | 78 ++ .../connection/db_connection/postgres/sql.md | 62 ++ .../db_connection/postgres/types.md | 372 ++++++++++ .../db_connection/postgres/write.md | 38 + .../db_connection/teradata/connection.md | 3 + .../db_connection/teradata/execute.md | 90 +++ .../db_connection/teradata/index.md | 15 + .../db_connection/teradata/prerequisites.md | 57 ++ .../connection/db_connection/teradata/read.md | 107 +++ .../connection/db_connection/teradata/sql.md | 61 ++ .../db_connection/teradata/write.md | 105 +++ .../connection/file_connection/ftp.md | 3 + .../connection/file_connection/ftps.md | 3 + .../file_connection/hdfs/connection.md | 3 + .../connection/file_connection/hdfs/index.md | 11 + .../connection/file_connection/hdfs/slots.md | 3 + .../connection/file_connection/index.md | 13 + .../markdown/connection/file_connection/s3.md | 3 + .../connection/file_connection/samba.md | 3 + .../connection/file_connection/sftp.md | 3 + .../connection/file_connection/webdav.md | 3 + .../connection/file_df_connection/base.md | 3 + .../connection/file_df_connection/index.md | 19 + .../spark_hdfs/connection.md | 3 + .../file_df_connection/spark_hdfs/index.md | 12 + .../spark_hdfs/prerequisites.md | 46 ++ .../file_df_connection/spark_hdfs/slots.md | 3 + .../file_df_connection/spark_local_fs.md | 3 + .../file_df_connection/spark_s3/connection.md | 3 + .../file_df_connection/spark_s3/index.md | 9 + .../spark_s3/prerequisites.md | 61 ++ .../spark_s3/troubleshooting.md | 366 +++++++++ .../docs/_build/markdown/connection/index.md | 34 + mddocs/docs/_build/markdown/contributing.md | 391 ++++++++++ mddocs/docs/_build/markdown/db/db_reader.md | 3 + mddocs/docs/_build/markdown/db/db_writer.md | 3 + mddocs/docs/_build/markdown/db/index.md | 6 + .../file/file_downloader/file_downloader.md | 3 + .../markdown/file/file_downloader/index.md | 9 + .../markdown/file/file_downloader/options.md | 3 + .../markdown/file/file_downloader/result.md | 3 + .../_build/markdown/file/file_filters/base.md | 3 + .../markdown/file/file_filters/exclude_dir.md | 3 + .../markdown/file/file_filters/file_filter.md | 3 + .../file/file_filters/file_mtime_filter.md | 3 + .../file/file_filters/file_size_filter.md | 3 + .../_build/markdown/file/file_filters/glob.md | 3 + .../markdown/file/file_filters/index.md | 20 + .../file/file_filters/match_all_filters.md | 3 + .../markdown/file/file_filters/regexp.md | 3 + .../_build/markdown/file/file_limits/base.md | 3 + .../markdown/file/file_limits/file_limit.md | 3 + .../_build/markdown/file/file_limits/index.md | 19 + .../file/file_limits/limits_reached.md | 3 + .../file/file_limits/limits_stop_at.md | 3 + .../file/file_limits/max_files_count.md | 3 + .../markdown/file/file_limits/reset_limits.md | 3 + .../file/file_limits/total_files_size.md | 3 + .../markdown/file/file_mover/file_mover.md | 3 + .../_build/markdown/file/file_mover/index.md | 9 + .../markdown/file/file_mover/options.md | 3 + .../_build/markdown/file/file_mover/result.md | 3 + .../file/file_uploader/file_uploader.md | 3 + .../markdown/file/file_uploader/index.md | 9 + .../markdown/file/file_uploader/options.md | 3 + .../markdown/file/file_uploader/result.md | 3 + mddocs/docs/_build/markdown/file/index.md | 9 + .../file_df/file_df_reader/file_df_reader.md | 3 + .../markdown/file_df/file_df_reader/index.md | 8 + .../file_df/file_df_reader/options.md | 3 + .../file_df/file_df_writer/file_df_writer.md | 3 + .../markdown/file_df/file_df_writer/index.md | 8 + .../file_df/file_df_writer/options.md | 3 + .../markdown/file_df/file_formats/avro.md | 3 + .../markdown/file_df/file_formats/base.md | 3 + .../markdown/file_df/file_formats/csv.md | 3 + .../markdown/file_df/file_formats/excel.md | 3 + .../markdown/file_df/file_formats/index.md | 18 + .../markdown/file_df/file_formats/json.md | 3 + .../markdown/file_df/file_formats/jsonline.md | 3 + .../markdown/file_df/file_formats/orc.md | 3 + .../markdown/file_df/file_formats/parquet.md | 3 + .../markdown/file_df/file_formats/xml.md | 3 + mddocs/docs/_build/markdown/file_df/index.md | 7 + mddocs/docs/_build/markdown/hooks/design.md | 642 ++++++++++++++++ .../_build/markdown/hooks/global_state.md | 3 + mddocs/docs/_build/markdown/hooks/hook.md | 3 + mddocs/docs/_build/markdown/hooks/index.md | 14 + mddocs/docs/_build/markdown/hooks/slot.md | 3 + .../_build/markdown/hooks/support_hooks.md | 3 + .../docs/_build/markdown/hwm_store/index.md | 9 + .../markdown/hwm_store/yaml_hwm_store.md | 3 + mddocs/docs/_build/markdown/index.md | 59 ++ mddocs/docs/_build/markdown/install/files.md | 20 + mddocs/docs/_build/markdown/install/full.md | 15 + mddocs/docs/_build/markdown/install/index.md | 33 + .../docs/_build/markdown/install/kerberos.md | 32 + mddocs/docs/_build/markdown/install/spark.md | 342 +++++++++ mddocs/docs/_build/markdown/logging.md | 153 ++++ mddocs/docs/_build/markdown/plugins.md | 147 ++++ mddocs/docs/_build/markdown/quickstart.md | 365 +++++++++ mddocs/docs/_build/markdown/security.md | 25 + .../strategy/incremental_batch_strategy.md | 3 + .../markdown/strategy/incremental_strategy.md | 3 + mddocs/docs/_build/markdown/strategy/index.md | 10 + .../strategy/snapshot_batch_strategy.md | 3 + .../markdown/strategy/snapshot_strategy.md | 3 + .../_build/markdown/troubleshooting/index.md | 17 + .../_build/markdown/troubleshooting/spark.md | 72 ++ mddocs/docs/_static/icon.svg | 11 + mddocs/docs/_static/logo.svg | 214 ++++++ mddocs/docs/_static/logo_wide.svg | 329 +++++++++ mddocs/docs/changelog.md | 8 + mddocs/docs/changelog/0.10.0.md | 543 ++++++++++++++ mddocs/docs/changelog/0.10.1.md | 29 + mddocs/docs/changelog/0.10.2.md | 39 + mddocs/docs/changelog/0.11.0.md | 212 ++++++ mddocs/docs/changelog/0.11.1.md | 9 + mddocs/docs/changelog/0.11.2.md | 5 + mddocs/docs/changelog/0.12.0.md | 54 ++ mddocs/docs/changelog/0.12.1.md | 17 + mddocs/docs/changelog/0.12.2.md | 18 + mddocs/docs/changelog/0.12.3.md | 5 + mddocs/docs/changelog/0.12.4.md | 5 + mddocs/docs/changelog/0.12.5.md | 13 + mddocs/docs/changelog/0.13.0.md | 222 ++++++ mddocs/docs/changelog/0.13.1.md | 9 + mddocs/docs/changelog/0.13.3.md | 5 + mddocs/docs/changelog/0.13.4.md | 10 + mddocs/docs/changelog/0.7.0.md | 239 ++++++ mddocs/docs/changelog/0.7.1.md | 40 + mddocs/docs/changelog/0.7.2.md | 37 + mddocs/docs/changelog/0.8.0.md | 166 +++++ mddocs/docs/changelog/0.8.1.md | 42 ++ mddocs/docs/changelog/0.9.0.md | 122 +++ mddocs/docs/changelog/0.9.1.md | 7 + mddocs/docs/changelog/0.9.2.md | 23 + mddocs/docs/changelog/0.9.3.md | 5 + mddocs/docs/changelog/0.9.4.md | 24 + mddocs/docs/changelog/0.9.5.md | 14 + mddocs/docs/changelog/DRAFT.md | 3 + mddocs/docs/changelog/NEXT_RELEASE.md | 1 + mddocs/docs/changelog/index.md | 29 + mddocs/docs/changelog/next_release/.keep | 0 mddocs/docs/concepts.md | 458 ++++++++++++ mddocs/docs/conf.py | 169 +++++ .../db_connection/clickhouse/connection.md | 12 + .../db_connection/clickhouse/execute.md | 125 ++++ .../db_connection/clickhouse/index.md | 28 + .../db_connection/clickhouse/prerequisites.md | 73 ++ .../db_connection/clickhouse/read.md | 93 +++ .../db_connection/clickhouse/sql.md | 80 ++ .../db_connection/clickhouse/types.md | 457 ++++++++++++ .../db_connection/clickhouse/write.md | 60 ++ .../db_connection/greenplum/connection.md | 12 + .../db_connection/greenplum/execute.md | 159 ++++ .../db_connection/greenplum/index.md | 27 + .../db_connection/greenplum/prerequisites.md | 382 ++++++++++ .../db_connection/greenplum/read.md | 386 ++++++++++ .../db_connection/greenplum/types.md | 406 ++++++++++ .../db_connection/greenplum/write.md | 140 ++++ .../db_connection/hive/connection.md | 13 + .../connection/db_connection/hive/execute.md | 55 ++ .../connection/db_connection/hive/index.md | 28 + .../db_connection/hive/prerequisites.md | 134 ++++ .../connection/db_connection/hive/read.md | 95 +++ .../connection/db_connection/hive/slots.md | 13 + .../docs/connection/db_connection/hive/sql.md | 79 ++ .../connection/db_connection/hive/write.md | 180 +++++ mddocs/docs/connection/db_connection/index.md | 19 + .../connection/db_connection/kafka/auth.md | 13 + .../db_connection/kafka/basic_auth.md | 14 + .../db_connection/kafka/connection.md | 12 + .../connection/db_connection/kafka/index.md | 46 ++ .../db_connection/kafka/kerberos_auth.md | 14 + .../db_connection/kafka/plaintext_protocol.md | 14 + .../db_connection/kafka/prerequisites.md | 65 ++ .../db_connection/kafka/protocol.md | 13 + .../connection/db_connection/kafka/read.md | 137 ++++ .../db_connection/kafka/scram_auth.md | 14 + .../connection/db_connection/kafka/slots.md | 13 + .../db_connection/kafka/ssl_protocol.md | 14 + .../db_connection/kafka/troubleshooting.md | 13 + .../connection/db_connection/kafka/write.md | 75 ++ .../db_connection/mongodb/connection.md | 13 + .../connection/db_connection/mongodb/index.md | 27 + .../db_connection/mongodb/pipeline.md | 41 ++ .../db_connection/mongodb/prerequisites.md | 72 ++ .../connection/db_connection/mongodb/read.md | 141 ++++ .../connection/db_connection/mongodb/types.md | 269 +++++++ .../connection/db_connection/mongodb/write.md | 47 ++ .../db_connection/mssql/connection.md | 12 + .../connection/db_connection/mssql/execute.md | 117 +++ .../connection/db_connection/mssql/index.md | 28 + .../db_connection/mssql/prerequisites.md | 76 ++ .../connection/db_connection/mssql/read.md | 93 +++ .../connection/db_connection/mssql/sql.md | 80 ++ .../connection/db_connection/mssql/types.md | 376 ++++++++++ .../connection/db_connection/mssql/write.md | 56 ++ .../db_connection/mysql/connection.md | 12 + .../connection/db_connection/mysql/execute.md | 115 +++ .../connection/db_connection/mysql/index.md | 28 + .../db_connection/mysql/prerequisites.md | 61 ++ .../connection/db_connection/mysql/read.md | 93 +++ .../connection/db_connection/mysql/sql.md | 81 ++ .../connection/db_connection/mysql/types.md | 382 ++++++++++ .../connection/db_connection/mysql/write.md | 60 ++ .../db_connection/oracle/connection.md | 12 + .../db_connection/oracle/execute.md | 115 +++ .../connection/db_connection/oracle/index.md | 28 + .../db_connection/oracle/prerequisites.md | 108 +++ .../connection/db_connection/oracle/read.md | 93 +++ .../connection/db_connection/oracle/sql.md | 81 ++ .../connection/db_connection/oracle/types.md | 400 ++++++++++ .../connection/db_connection/oracle/write.md | 56 ++ .../db_connection/postgres/connection.md | 12 + .../db_connection/postgres/execute.md | 113 +++ .../db_connection/postgres/index.md | 28 + .../db_connection/postgres/prerequisites.md | 71 ++ .../connection/db_connection/postgres/read.md | 91 +++ .../connection/db_connection/postgres/sql.md | 80 ++ .../db_connection/postgres/types.md | 490 +++++++++++++ .../db_connection/postgres/write.md | 56 ++ .../db_connection/teradata/connection.md | 12 + .../db_connection/teradata/execute.md | 110 +++ .../db_connection/teradata/index.md | 21 + .../db_connection/teradata/prerequisites.md | 57 ++ .../connection/db_connection/teradata/read.md | 117 +++ .../connection/db_connection/teradata/sql.md | 76 ++ .../db_connection/teradata/write.md | 120 +++ mddocs/docs/connection/file_connection/ftp.md | 12 + .../docs/connection/file_connection/ftps.md | 12 + .../file_connection/hdfs/connection.md | 12 + .../connection/file_connection/hdfs/index.md | 17 + .../connection/file_connection/hdfs/slots.md | 13 + .../docs/connection/file_connection/index.md | 16 + mddocs/docs/connection/file_connection/s3.md | 12 + .../docs/connection/file_connection/samba.md | 12 + .../docs/connection/file_connection/sftp.md | 12 + .../docs/connection/file_connection/webdav.md | 12 + .../connection/file_df_connection/base.md | 12 + .../connection/file_df_connection/index.md | 19 + .../spark_hdfs/connection.md | 12 + .../file_df_connection/spark_hdfs/index.md | 18 + .../spark_hdfs/prerequisites.md | 46 ++ .../file_df_connection/spark_hdfs/slots.md | 13 + .../file_df_connection/spark_local_fs.md | 12 + .../file_df_connection/spark_s3/connection.md | 12 + .../file_df_connection/spark_s3/index.md | 12 + .../spark_s3/prerequisites.md | 61 ++ .../spark_s3/troubleshooting.md | 377 ++++++++++ mddocs/docs/connection/index.md | 22 + mddocs/docs/contributing.md | 3 + mddocs/docs/db/db_reader.md | 21 + mddocs/docs/db/db_writer.md | 19 + mddocs/docs/db/index.md | 9 + .../file/file_downloader/file_downloader.md | 21 + mddocs/docs/file/file_downloader/index.md | 12 + mddocs/docs/file/file_downloader/options.md | 14 + mddocs/docs/file/file_downloader/result.md | 12 + mddocs/docs/file/file_filters/base.md | 19 + mddocs/docs/file/file_filters/exclude_dir.md | 12 + mddocs/docs/file/file_filters/file_filter.md | 12 + .../file/file_filters/file_mtime_filter.md | 12 + .../file/file_filters/file_size_filter.md | 12 + mddocs/docs/file/file_filters/glob.md | 12 + mddocs/docs/file/file_filters/index.md | 29 + .../file/file_filters/match_all_filters.md | 11 + mddocs/docs/file/file_filters/regexp.md | 12 + mddocs/docs/file/file_limits/base.md | 21 + mddocs/docs/file/file_limits/file_limit.md | 12 + mddocs/docs/file/file_limits/index.md | 28 + .../docs/file/file_limits/limits_reached.md | 11 + .../docs/file/file_limits/limits_stop_at.md | 11 + .../docs/file/file_limits/max_files_count.md | 12 + mddocs/docs/file/file_limits/reset_limits.md | 11 + .../docs/file/file_limits/total_files_size.md | 12 + mddocs/docs/file/file_mover/file_mover.md | 21 + mddocs/docs/file/file_mover/index.md | 12 + mddocs/docs/file/file_mover/options.md | 14 + mddocs/docs/file/file_mover/result.md | 12 + .../docs/file/file_uploader/file_uploader.md | 21 + mddocs/docs/file/file_uploader/index.md | 12 + mddocs/docs/file/file_uploader/options.md | 14 + mddocs/docs/file/file_uploader/result.md | 12 + mddocs/docs/file/index.md | 12 + .../file_df/file_df_reader/file_df_reader.md | 13 + mddocs/docs/file_df/file_df_reader/index.md | 11 + mddocs/docs/file_df/file_df_reader/options.md | 13 + .../file_df/file_df_writer/file_df_writer.md | 13 + mddocs/docs/file_df/file_df_writer/index.md | 11 + mddocs/docs/file_df/file_df_writer/options.md | 13 + mddocs/docs/file_df/file_formats/avro.md | 13 + mddocs/docs/file_df/file_formats/base.md | 19 + mddocs/docs/file_df/file_formats/csv.md | 13 + mddocs/docs/file_df/file_formats/excel.md | 13 + mddocs/docs/file_df/file_formats/index.md | 24 + mddocs/docs/file_df/file_formats/json.md | 13 + mddocs/docs/file_df/file_formats/jsonline.md | 13 + mddocs/docs/file_df/file_formats/orc.md | 13 + mddocs/docs/file_df/file_formats/parquet.md | 13 + mddocs/docs/file_df/file_formats/xml.md | 13 + mddocs/docs/file_df/index.md | 10 + mddocs/docs/hooks/design.md | 694 ++++++++++++++++++ mddocs/docs/hooks/global_state.md | 27 + mddocs/docs/hooks/hook.md | 34 + mddocs/docs/hooks/index.md | 17 + mddocs/docs/hooks/slot.md | 26 + mddocs/docs/hooks/support_hooks.md | 32 + mddocs/docs/hwm_store/index.md | 16 + mddocs/docs/hwm_store/yaml_hwm_store.md | 12 + mddocs/docs/index.md | 87 +++ mddocs/docs/install/files.md | 9 + mddocs/docs/install/full.md | 9 + mddocs/docs/install/index.md | 22 + mddocs/docs/install/kerberos.md | 9 + mddocs/docs/install/spark.md | 328 +++++++++ mddocs/docs/logging.md | 171 +++++ mddocs/docs/make.bat | 35 + mddocs/docs/plugins.md | 147 ++++ mddocs/docs/quickstart.md | 4 + mddocs/docs/robots.txt | 5 + mddocs/docs/security.md | 3 + .../strategy/incremental_batch_strategy.md | 12 + mddocs/docs/strategy/incremental_strategy.md | 12 + mddocs/docs/strategy/index.md | 21 + .../docs/strategy/snapshot_batch_strategy.md | 12 + mddocs/docs/strategy/snapshot_strategy.md | 12 + mddocs/docs/troubleshooting/index.md | 25 + mddocs/docs/troubleshooting/spark.md | 75 ++ mddocs/mkdocs.yml | 46 ++ 654 files changed, 26366 insertions(+) create mode 100644 mddocs/docs/Makefile create mode 100644 mddocs/docs/_build/doctrees/changelog.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.10.0.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.10.1.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.10.2.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.11.0.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.11.1.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.11.2.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.12.0.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.12.1.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.12.2.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.12.3.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.12.4.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.12.5.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.13.0.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.13.1.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.13.3.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.13.4.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.7.0.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.7.1.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.7.2.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.8.0.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.8.1.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.9.0.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.9.1.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.9.2.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.9.3.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.9.4.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/0.9.5.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/DRAFT.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/NEXT_RELEASE.doctree create mode 100644 mddocs/docs/_build/doctrees/changelog/index.doctree create mode 100644 mddocs/docs/_build/doctrees/concepts.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/clickhouse/connection.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/clickhouse/execute.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/clickhouse/index.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/clickhouse/prerequisites.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/clickhouse/read.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/clickhouse/sql.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/clickhouse/types.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/clickhouse/write.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/greenplum/connection.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/greenplum/execute.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/greenplum/index.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/greenplum/prerequisites.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/greenplum/read.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/greenplum/types.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/greenplum/write.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/hive/connection.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/hive/execute.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/hive/index.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/hive/prerequisites.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/hive/read.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/hive/slots.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/hive/sql.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/hive/write.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/index.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/kafka/auth.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/kafka/basic_auth.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/kafka/connection.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/kafka/index.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/kafka/kerberos_auth.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/kafka/plaintext_protocol.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/kafka/prerequisites.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/kafka/protocol.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/kafka/read.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/kafka/scram_auth.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/kafka/slots.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/kafka/ssl_protocol.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/kafka/troubleshooting.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/kafka/write.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mongodb/connection.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mongodb/index.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mongodb/pipeline.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mongodb/prerequisites.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mongodb/read.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mongodb/types.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mongodb/write.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mssql/connection.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mssql/execute.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mssql/index.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mssql/prerequisites.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mssql/read.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mssql/sql.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mssql/types.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mssql/write.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mysql/connection.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mysql/execute.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mysql/index.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mysql/prerequisites.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mysql/read.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mysql/sql.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mysql/types.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/mysql/write.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/oracle/connection.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/oracle/execute.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/oracle/index.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/oracle/prerequisites.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/oracle/read.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/oracle/sql.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/oracle/types.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/oracle/write.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/postgres/connection.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/postgres/execute.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/postgres/index.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/postgres/prerequisites.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/postgres/read.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/postgres/sql.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/postgres/types.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/postgres/write.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/teradata/connection.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/teradata/execute.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/teradata/index.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/teradata/prerequisites.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/teradata/read.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/teradata/sql.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/db_connection/teradata/write.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/file_connection/ftp.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/file_connection/ftps.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/file_connection/hdfs/connection.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/file_connection/hdfs/index.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/file_connection/hdfs/slots.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/file_connection/index.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/file_connection/s3.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/file_connection/samba.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/file_connection/sftp.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/file_connection/webdav.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/file_df_connection/base.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/file_df_connection/index.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/file_df_connection/spark_hdfs/connection.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/file_df_connection/spark_hdfs/index.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/file_df_connection/spark_hdfs/prerequisites.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/file_df_connection/spark_hdfs/slots.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/file_df_connection/spark_local_fs.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/file_df_connection/spark_s3/connection.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/file_df_connection/spark_s3/index.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/file_df_connection/spark_s3/prerequisites.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/file_df_connection/spark_s3/troubleshooting.doctree create mode 100644 mddocs/docs/_build/doctrees/connection/index.doctree create mode 100644 mddocs/docs/_build/doctrees/contributing.doctree create mode 100644 mddocs/docs/_build/doctrees/db/db_reader.doctree create mode 100644 mddocs/docs/_build/doctrees/db/db_writer.doctree create mode 100644 mddocs/docs/_build/doctrees/db/index.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_downloader/file_downloader.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_downloader/index.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_downloader/options.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_downloader/result.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_filters/base.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_filters/exclude_dir.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_filters/file_filter.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_filters/file_mtime_filter.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_filters/file_size_filter.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_filters/glob.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_filters/index.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_filters/match_all_filters.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_filters/regexp.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_limits/base.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_limits/file_limit.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_limits/index.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_limits/limits_reached.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_limits/limits_stop_at.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_limits/max_files_count.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_limits/reset_limits.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_limits/total_files_size.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_mover/file_mover.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_mover/index.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_mover/options.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_mover/result.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_uploader/file_uploader.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_uploader/index.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_uploader/options.doctree create mode 100644 mddocs/docs/_build/doctrees/file/file_uploader/result.doctree create mode 100644 mddocs/docs/_build/doctrees/file/index.doctree create mode 100644 mddocs/docs/_build/doctrees/file_df/file_df_reader/file_df_reader.doctree create mode 100644 mddocs/docs/_build/doctrees/file_df/file_df_reader/index.doctree create mode 100644 mddocs/docs/_build/doctrees/file_df/file_df_reader/options.doctree create mode 100644 mddocs/docs/_build/doctrees/file_df/file_df_writer/file_df_writer.doctree create mode 100644 mddocs/docs/_build/doctrees/file_df/file_df_writer/index.doctree create mode 100644 mddocs/docs/_build/doctrees/file_df/file_df_writer/options.doctree create mode 100644 mddocs/docs/_build/doctrees/file_df/file_formats/avro.doctree create mode 100644 mddocs/docs/_build/doctrees/file_df/file_formats/base.doctree create mode 100644 mddocs/docs/_build/doctrees/file_df/file_formats/csv.doctree create mode 100644 mddocs/docs/_build/doctrees/file_df/file_formats/excel.doctree create mode 100644 mddocs/docs/_build/doctrees/file_df/file_formats/index.doctree create mode 100644 mddocs/docs/_build/doctrees/file_df/file_formats/json.doctree create mode 100644 mddocs/docs/_build/doctrees/file_df/file_formats/jsonline.doctree create mode 100644 mddocs/docs/_build/doctrees/file_df/file_formats/orc.doctree create mode 100644 mddocs/docs/_build/doctrees/file_df/file_formats/parquet.doctree create mode 100644 mddocs/docs/_build/doctrees/file_df/file_formats/xml.doctree create mode 100644 mddocs/docs/_build/doctrees/file_df/index.doctree create mode 100644 mddocs/docs/_build/doctrees/hooks/design.doctree create mode 100644 mddocs/docs/_build/doctrees/hooks/global_state.doctree create mode 100644 mddocs/docs/_build/doctrees/hooks/hook.doctree create mode 100644 mddocs/docs/_build/doctrees/hooks/index.doctree create mode 100644 mddocs/docs/_build/doctrees/hooks/slot.doctree create mode 100644 mddocs/docs/_build/doctrees/hooks/support_hooks.doctree create mode 100644 mddocs/docs/_build/doctrees/hwm_store/index.doctree create mode 100644 mddocs/docs/_build/doctrees/hwm_store/yaml_hwm_store.doctree create mode 100644 mddocs/docs/_build/doctrees/index.doctree create mode 100644 mddocs/docs/_build/doctrees/install/files.doctree create mode 100644 mddocs/docs/_build/doctrees/install/full.doctree create mode 100644 mddocs/docs/_build/doctrees/install/index.doctree create mode 100644 mddocs/docs/_build/doctrees/install/kerberos.doctree create mode 100644 mddocs/docs/_build/doctrees/install/spark.doctree create mode 100644 mddocs/docs/_build/doctrees/logging.doctree create mode 100644 mddocs/docs/_build/doctrees/plugins.doctree create mode 100644 mddocs/docs/_build/doctrees/quickstart.doctree create mode 100644 mddocs/docs/_build/doctrees/security.doctree create mode 100644 mddocs/docs/_build/doctrees/strategy/incremental_batch_strategy.doctree create mode 100644 mddocs/docs/_build/doctrees/strategy/incremental_strategy.doctree create mode 100644 mddocs/docs/_build/doctrees/strategy/index.doctree create mode 100644 mddocs/docs/_build/doctrees/strategy/snapshot_batch_strategy.doctree create mode 100644 mddocs/docs/_build/doctrees/strategy/snapshot_strategy.doctree create mode 100644 mddocs/docs/_build/doctrees/troubleshooting/index.doctree create mode 100644 mddocs/docs/_build/doctrees/troubleshooting/spark.doctree create mode 100644 mddocs/docs/_build/markdown/_sphinx_design_static/design-tabs.js create mode 100644 mddocs/docs/_build/markdown/_sphinx_design_static/sphinx-design.min.css create mode 100644 mddocs/docs/_build/markdown/_static/autodoc_pydantic.css create mode 100644 mddocs/docs/_build/markdown/changelog.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.10.0.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.10.1.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.10.2.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.11.0.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.11.1.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.11.2.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.12.0.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.12.1.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.12.2.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.12.3.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.12.4.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.12.5.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.13.0.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.13.1.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.13.3.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.13.4.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.7.0.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.7.1.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.7.2.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.8.0.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.8.1.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.9.0.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.9.1.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.9.2.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.9.3.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.9.4.md create mode 100644 mddocs/docs/_build/markdown/changelog/0.9.5.md create mode 100644 mddocs/docs/_build/markdown/changelog/DRAFT.md create mode 100644 mddocs/docs/_build/markdown/changelog/NEXT_RELEASE.md create mode 100644 mddocs/docs/_build/markdown/changelog/index.md create mode 100644 mddocs/docs/_build/markdown/concepts.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/clickhouse/connection.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/clickhouse/execute.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/clickhouse/index.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/clickhouse/prerequisites.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/clickhouse/read.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/clickhouse/sql.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/clickhouse/types.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/clickhouse/write.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/greenplum/connection.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/greenplum/execute.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/greenplum/index.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/greenplum/prerequisites.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/greenplum/read.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/greenplum/types.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/greenplum/write.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/hive/connection.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/hive/execute.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/hive/index.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/hive/prerequisites.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/hive/read.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/hive/slots.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/hive/sql.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/hive/write.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/index.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/kafka/auth.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/kafka/basic_auth.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/kafka/connection.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/kafka/index.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/kafka/kerberos_auth.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/kafka/plaintext_protocol.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/kafka/prerequisites.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/kafka/protocol.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/kafka/read.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/kafka/scram_auth.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/kafka/slots.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/kafka/ssl_protocol.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/kafka/troubleshooting.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/kafka/write.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mongodb/connection.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mongodb/index.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mongodb/pipeline.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mongodb/prerequisites.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mongodb/read.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mongodb/types.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mongodb/write.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mssql/connection.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mssql/execute.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mssql/index.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mssql/prerequisites.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mssql/read.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mssql/sql.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mssql/types.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mssql/write.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mysql/connection.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mysql/execute.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mysql/index.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mysql/prerequisites.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mysql/read.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mysql/sql.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mysql/types.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/mysql/write.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/oracle/connection.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/oracle/execute.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/oracle/index.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/oracle/prerequisites.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/oracle/read.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/oracle/sql.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/oracle/types.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/oracle/write.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/postgres/connection.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/postgres/execute.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/postgres/index.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/postgres/prerequisites.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/postgres/read.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/postgres/sql.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/postgres/types.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/postgres/write.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/teradata/connection.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/teradata/execute.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/teradata/index.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/teradata/prerequisites.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/teradata/read.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/teradata/sql.md create mode 100644 mddocs/docs/_build/markdown/connection/db_connection/teradata/write.md create mode 100644 mddocs/docs/_build/markdown/connection/file_connection/ftp.md create mode 100644 mddocs/docs/_build/markdown/connection/file_connection/ftps.md create mode 100644 mddocs/docs/_build/markdown/connection/file_connection/hdfs/connection.md create mode 100644 mddocs/docs/_build/markdown/connection/file_connection/hdfs/index.md create mode 100644 mddocs/docs/_build/markdown/connection/file_connection/hdfs/slots.md create mode 100644 mddocs/docs/_build/markdown/connection/file_connection/index.md create mode 100644 mddocs/docs/_build/markdown/connection/file_connection/s3.md create mode 100644 mddocs/docs/_build/markdown/connection/file_connection/samba.md create mode 100644 mddocs/docs/_build/markdown/connection/file_connection/sftp.md create mode 100644 mddocs/docs/_build/markdown/connection/file_connection/webdav.md create mode 100644 mddocs/docs/_build/markdown/connection/file_df_connection/base.md create mode 100644 mddocs/docs/_build/markdown/connection/file_df_connection/index.md create mode 100644 mddocs/docs/_build/markdown/connection/file_df_connection/spark_hdfs/connection.md create mode 100644 mddocs/docs/_build/markdown/connection/file_df_connection/spark_hdfs/index.md create mode 100644 mddocs/docs/_build/markdown/connection/file_df_connection/spark_hdfs/prerequisites.md create mode 100644 mddocs/docs/_build/markdown/connection/file_df_connection/spark_hdfs/slots.md create mode 100644 mddocs/docs/_build/markdown/connection/file_df_connection/spark_local_fs.md create mode 100644 mddocs/docs/_build/markdown/connection/file_df_connection/spark_s3/connection.md create mode 100644 mddocs/docs/_build/markdown/connection/file_df_connection/spark_s3/index.md create mode 100644 mddocs/docs/_build/markdown/connection/file_df_connection/spark_s3/prerequisites.md create mode 100644 mddocs/docs/_build/markdown/connection/file_df_connection/spark_s3/troubleshooting.md create mode 100644 mddocs/docs/_build/markdown/connection/index.md create mode 100644 mddocs/docs/_build/markdown/contributing.md create mode 100644 mddocs/docs/_build/markdown/db/db_reader.md create mode 100644 mddocs/docs/_build/markdown/db/db_writer.md create mode 100644 mddocs/docs/_build/markdown/db/index.md create mode 100644 mddocs/docs/_build/markdown/file/file_downloader/file_downloader.md create mode 100644 mddocs/docs/_build/markdown/file/file_downloader/index.md create mode 100644 mddocs/docs/_build/markdown/file/file_downloader/options.md create mode 100644 mddocs/docs/_build/markdown/file/file_downloader/result.md create mode 100644 mddocs/docs/_build/markdown/file/file_filters/base.md create mode 100644 mddocs/docs/_build/markdown/file/file_filters/exclude_dir.md create mode 100644 mddocs/docs/_build/markdown/file/file_filters/file_filter.md create mode 100644 mddocs/docs/_build/markdown/file/file_filters/file_mtime_filter.md create mode 100644 mddocs/docs/_build/markdown/file/file_filters/file_size_filter.md create mode 100644 mddocs/docs/_build/markdown/file/file_filters/glob.md create mode 100644 mddocs/docs/_build/markdown/file/file_filters/index.md create mode 100644 mddocs/docs/_build/markdown/file/file_filters/match_all_filters.md create mode 100644 mddocs/docs/_build/markdown/file/file_filters/regexp.md create mode 100644 mddocs/docs/_build/markdown/file/file_limits/base.md create mode 100644 mddocs/docs/_build/markdown/file/file_limits/file_limit.md create mode 100644 mddocs/docs/_build/markdown/file/file_limits/index.md create mode 100644 mddocs/docs/_build/markdown/file/file_limits/limits_reached.md create mode 100644 mddocs/docs/_build/markdown/file/file_limits/limits_stop_at.md create mode 100644 mddocs/docs/_build/markdown/file/file_limits/max_files_count.md create mode 100644 mddocs/docs/_build/markdown/file/file_limits/reset_limits.md create mode 100644 mddocs/docs/_build/markdown/file/file_limits/total_files_size.md create mode 100644 mddocs/docs/_build/markdown/file/file_mover/file_mover.md create mode 100644 mddocs/docs/_build/markdown/file/file_mover/index.md create mode 100644 mddocs/docs/_build/markdown/file/file_mover/options.md create mode 100644 mddocs/docs/_build/markdown/file/file_mover/result.md create mode 100644 mddocs/docs/_build/markdown/file/file_uploader/file_uploader.md create mode 100644 mddocs/docs/_build/markdown/file/file_uploader/index.md create mode 100644 mddocs/docs/_build/markdown/file/file_uploader/options.md create mode 100644 mddocs/docs/_build/markdown/file/file_uploader/result.md create mode 100644 mddocs/docs/_build/markdown/file/index.md create mode 100644 mddocs/docs/_build/markdown/file_df/file_df_reader/file_df_reader.md create mode 100644 mddocs/docs/_build/markdown/file_df/file_df_reader/index.md create mode 100644 mddocs/docs/_build/markdown/file_df/file_df_reader/options.md create mode 100644 mddocs/docs/_build/markdown/file_df/file_df_writer/file_df_writer.md create mode 100644 mddocs/docs/_build/markdown/file_df/file_df_writer/index.md create mode 100644 mddocs/docs/_build/markdown/file_df/file_df_writer/options.md create mode 100644 mddocs/docs/_build/markdown/file_df/file_formats/avro.md create mode 100644 mddocs/docs/_build/markdown/file_df/file_formats/base.md create mode 100644 mddocs/docs/_build/markdown/file_df/file_formats/csv.md create mode 100644 mddocs/docs/_build/markdown/file_df/file_formats/excel.md create mode 100644 mddocs/docs/_build/markdown/file_df/file_formats/index.md create mode 100644 mddocs/docs/_build/markdown/file_df/file_formats/json.md create mode 100644 mddocs/docs/_build/markdown/file_df/file_formats/jsonline.md create mode 100644 mddocs/docs/_build/markdown/file_df/file_formats/orc.md create mode 100644 mddocs/docs/_build/markdown/file_df/file_formats/parquet.md create mode 100644 mddocs/docs/_build/markdown/file_df/file_formats/xml.md create mode 100644 mddocs/docs/_build/markdown/file_df/index.md create mode 100644 mddocs/docs/_build/markdown/hooks/design.md create mode 100644 mddocs/docs/_build/markdown/hooks/global_state.md create mode 100644 mddocs/docs/_build/markdown/hooks/hook.md create mode 100644 mddocs/docs/_build/markdown/hooks/index.md create mode 100644 mddocs/docs/_build/markdown/hooks/slot.md create mode 100644 mddocs/docs/_build/markdown/hooks/support_hooks.md create mode 100644 mddocs/docs/_build/markdown/hwm_store/index.md create mode 100644 mddocs/docs/_build/markdown/hwm_store/yaml_hwm_store.md create mode 100644 mddocs/docs/_build/markdown/index.md create mode 100644 mddocs/docs/_build/markdown/install/files.md create mode 100644 mddocs/docs/_build/markdown/install/full.md create mode 100644 mddocs/docs/_build/markdown/install/index.md create mode 100644 mddocs/docs/_build/markdown/install/kerberos.md create mode 100644 mddocs/docs/_build/markdown/install/spark.md create mode 100644 mddocs/docs/_build/markdown/logging.md create mode 100644 mddocs/docs/_build/markdown/plugins.md create mode 100644 mddocs/docs/_build/markdown/quickstart.md create mode 100644 mddocs/docs/_build/markdown/security.md create mode 100644 mddocs/docs/_build/markdown/strategy/incremental_batch_strategy.md create mode 100644 mddocs/docs/_build/markdown/strategy/incremental_strategy.md create mode 100644 mddocs/docs/_build/markdown/strategy/index.md create mode 100644 mddocs/docs/_build/markdown/strategy/snapshot_batch_strategy.md create mode 100644 mddocs/docs/_build/markdown/strategy/snapshot_strategy.md create mode 100644 mddocs/docs/_build/markdown/troubleshooting/index.md create mode 100644 mddocs/docs/_build/markdown/troubleshooting/spark.md create mode 100644 mddocs/docs/_static/icon.svg create mode 100644 mddocs/docs/_static/logo.svg create mode 100644 mddocs/docs/_static/logo_wide.svg create mode 100644 mddocs/docs/changelog.md create mode 100644 mddocs/docs/changelog/0.10.0.md create mode 100644 mddocs/docs/changelog/0.10.1.md create mode 100644 mddocs/docs/changelog/0.10.2.md create mode 100644 mddocs/docs/changelog/0.11.0.md create mode 100644 mddocs/docs/changelog/0.11.1.md create mode 100644 mddocs/docs/changelog/0.11.2.md create mode 100644 mddocs/docs/changelog/0.12.0.md create mode 100644 mddocs/docs/changelog/0.12.1.md create mode 100644 mddocs/docs/changelog/0.12.2.md create mode 100644 mddocs/docs/changelog/0.12.3.md create mode 100644 mddocs/docs/changelog/0.12.4.md create mode 100644 mddocs/docs/changelog/0.12.5.md create mode 100644 mddocs/docs/changelog/0.13.0.md create mode 100644 mddocs/docs/changelog/0.13.1.md create mode 100644 mddocs/docs/changelog/0.13.3.md create mode 100644 mddocs/docs/changelog/0.13.4.md create mode 100644 mddocs/docs/changelog/0.7.0.md create mode 100644 mddocs/docs/changelog/0.7.1.md create mode 100644 mddocs/docs/changelog/0.7.2.md create mode 100644 mddocs/docs/changelog/0.8.0.md create mode 100644 mddocs/docs/changelog/0.8.1.md create mode 100644 mddocs/docs/changelog/0.9.0.md create mode 100644 mddocs/docs/changelog/0.9.1.md create mode 100644 mddocs/docs/changelog/0.9.2.md create mode 100644 mddocs/docs/changelog/0.9.3.md create mode 100644 mddocs/docs/changelog/0.9.4.md create mode 100644 mddocs/docs/changelog/0.9.5.md create mode 100644 mddocs/docs/changelog/DRAFT.md create mode 100644 mddocs/docs/changelog/NEXT_RELEASE.md create mode 100644 mddocs/docs/changelog/index.md create mode 100644 mddocs/docs/changelog/next_release/.keep create mode 100644 mddocs/docs/concepts.md create mode 100644 mddocs/docs/conf.py create mode 100644 mddocs/docs/connection/db_connection/clickhouse/connection.md create mode 100644 mddocs/docs/connection/db_connection/clickhouse/execute.md create mode 100644 mddocs/docs/connection/db_connection/clickhouse/index.md create mode 100644 mddocs/docs/connection/db_connection/clickhouse/prerequisites.md create mode 100644 mddocs/docs/connection/db_connection/clickhouse/read.md create mode 100644 mddocs/docs/connection/db_connection/clickhouse/sql.md create mode 100644 mddocs/docs/connection/db_connection/clickhouse/types.md create mode 100644 mddocs/docs/connection/db_connection/clickhouse/write.md create mode 100644 mddocs/docs/connection/db_connection/greenplum/connection.md create mode 100644 mddocs/docs/connection/db_connection/greenplum/execute.md create mode 100644 mddocs/docs/connection/db_connection/greenplum/index.md create mode 100644 mddocs/docs/connection/db_connection/greenplum/prerequisites.md create mode 100644 mddocs/docs/connection/db_connection/greenplum/read.md create mode 100644 mddocs/docs/connection/db_connection/greenplum/types.md create mode 100644 mddocs/docs/connection/db_connection/greenplum/write.md create mode 100644 mddocs/docs/connection/db_connection/hive/connection.md create mode 100644 mddocs/docs/connection/db_connection/hive/execute.md create mode 100644 mddocs/docs/connection/db_connection/hive/index.md create mode 100644 mddocs/docs/connection/db_connection/hive/prerequisites.md create mode 100644 mddocs/docs/connection/db_connection/hive/read.md create mode 100644 mddocs/docs/connection/db_connection/hive/slots.md create mode 100644 mddocs/docs/connection/db_connection/hive/sql.md create mode 100644 mddocs/docs/connection/db_connection/hive/write.md create mode 100644 mddocs/docs/connection/db_connection/index.md create mode 100644 mddocs/docs/connection/db_connection/kafka/auth.md create mode 100644 mddocs/docs/connection/db_connection/kafka/basic_auth.md create mode 100644 mddocs/docs/connection/db_connection/kafka/connection.md create mode 100644 mddocs/docs/connection/db_connection/kafka/index.md create mode 100644 mddocs/docs/connection/db_connection/kafka/kerberos_auth.md create mode 100644 mddocs/docs/connection/db_connection/kafka/plaintext_protocol.md create mode 100644 mddocs/docs/connection/db_connection/kafka/prerequisites.md create mode 100644 mddocs/docs/connection/db_connection/kafka/protocol.md create mode 100644 mddocs/docs/connection/db_connection/kafka/read.md create mode 100644 mddocs/docs/connection/db_connection/kafka/scram_auth.md create mode 100644 mddocs/docs/connection/db_connection/kafka/slots.md create mode 100644 mddocs/docs/connection/db_connection/kafka/ssl_protocol.md create mode 100644 mddocs/docs/connection/db_connection/kafka/troubleshooting.md create mode 100644 mddocs/docs/connection/db_connection/kafka/write.md create mode 100644 mddocs/docs/connection/db_connection/mongodb/connection.md create mode 100644 mddocs/docs/connection/db_connection/mongodb/index.md create mode 100644 mddocs/docs/connection/db_connection/mongodb/pipeline.md create mode 100644 mddocs/docs/connection/db_connection/mongodb/prerequisites.md create mode 100644 mddocs/docs/connection/db_connection/mongodb/read.md create mode 100644 mddocs/docs/connection/db_connection/mongodb/types.md create mode 100644 mddocs/docs/connection/db_connection/mongodb/write.md create mode 100644 mddocs/docs/connection/db_connection/mssql/connection.md create mode 100644 mddocs/docs/connection/db_connection/mssql/execute.md create mode 100644 mddocs/docs/connection/db_connection/mssql/index.md create mode 100644 mddocs/docs/connection/db_connection/mssql/prerequisites.md create mode 100644 mddocs/docs/connection/db_connection/mssql/read.md create mode 100644 mddocs/docs/connection/db_connection/mssql/sql.md create mode 100644 mddocs/docs/connection/db_connection/mssql/types.md create mode 100644 mddocs/docs/connection/db_connection/mssql/write.md create mode 100644 mddocs/docs/connection/db_connection/mysql/connection.md create mode 100644 mddocs/docs/connection/db_connection/mysql/execute.md create mode 100644 mddocs/docs/connection/db_connection/mysql/index.md create mode 100644 mddocs/docs/connection/db_connection/mysql/prerequisites.md create mode 100644 mddocs/docs/connection/db_connection/mysql/read.md create mode 100644 mddocs/docs/connection/db_connection/mysql/sql.md create mode 100644 mddocs/docs/connection/db_connection/mysql/types.md create mode 100644 mddocs/docs/connection/db_connection/mysql/write.md create mode 100644 mddocs/docs/connection/db_connection/oracle/connection.md create mode 100644 mddocs/docs/connection/db_connection/oracle/execute.md create mode 100644 mddocs/docs/connection/db_connection/oracle/index.md create mode 100644 mddocs/docs/connection/db_connection/oracle/prerequisites.md create mode 100644 mddocs/docs/connection/db_connection/oracle/read.md create mode 100644 mddocs/docs/connection/db_connection/oracle/sql.md create mode 100644 mddocs/docs/connection/db_connection/oracle/types.md create mode 100644 mddocs/docs/connection/db_connection/oracle/write.md create mode 100644 mddocs/docs/connection/db_connection/postgres/connection.md create mode 100644 mddocs/docs/connection/db_connection/postgres/execute.md create mode 100644 mddocs/docs/connection/db_connection/postgres/index.md create mode 100644 mddocs/docs/connection/db_connection/postgres/prerequisites.md create mode 100644 mddocs/docs/connection/db_connection/postgres/read.md create mode 100644 mddocs/docs/connection/db_connection/postgres/sql.md create mode 100644 mddocs/docs/connection/db_connection/postgres/types.md create mode 100644 mddocs/docs/connection/db_connection/postgres/write.md create mode 100644 mddocs/docs/connection/db_connection/teradata/connection.md create mode 100644 mddocs/docs/connection/db_connection/teradata/execute.md create mode 100644 mddocs/docs/connection/db_connection/teradata/index.md create mode 100644 mddocs/docs/connection/db_connection/teradata/prerequisites.md create mode 100644 mddocs/docs/connection/db_connection/teradata/read.md create mode 100644 mddocs/docs/connection/db_connection/teradata/sql.md create mode 100644 mddocs/docs/connection/db_connection/teradata/write.md create mode 100644 mddocs/docs/connection/file_connection/ftp.md create mode 100644 mddocs/docs/connection/file_connection/ftps.md create mode 100644 mddocs/docs/connection/file_connection/hdfs/connection.md create mode 100644 mddocs/docs/connection/file_connection/hdfs/index.md create mode 100644 mddocs/docs/connection/file_connection/hdfs/slots.md create mode 100644 mddocs/docs/connection/file_connection/index.md create mode 100644 mddocs/docs/connection/file_connection/s3.md create mode 100644 mddocs/docs/connection/file_connection/samba.md create mode 100644 mddocs/docs/connection/file_connection/sftp.md create mode 100644 mddocs/docs/connection/file_connection/webdav.md create mode 100644 mddocs/docs/connection/file_df_connection/base.md create mode 100644 mddocs/docs/connection/file_df_connection/index.md create mode 100644 mddocs/docs/connection/file_df_connection/spark_hdfs/connection.md create mode 100644 mddocs/docs/connection/file_df_connection/spark_hdfs/index.md create mode 100644 mddocs/docs/connection/file_df_connection/spark_hdfs/prerequisites.md create mode 100644 mddocs/docs/connection/file_df_connection/spark_hdfs/slots.md create mode 100644 mddocs/docs/connection/file_df_connection/spark_local_fs.md create mode 100644 mddocs/docs/connection/file_df_connection/spark_s3/connection.md create mode 100644 mddocs/docs/connection/file_df_connection/spark_s3/index.md create mode 100644 mddocs/docs/connection/file_df_connection/spark_s3/prerequisites.md create mode 100644 mddocs/docs/connection/file_df_connection/spark_s3/troubleshooting.md create mode 100644 mddocs/docs/connection/index.md create mode 100644 mddocs/docs/contributing.md create mode 100644 mddocs/docs/db/db_reader.md create mode 100644 mddocs/docs/db/db_writer.md create mode 100644 mddocs/docs/db/index.md create mode 100644 mddocs/docs/file/file_downloader/file_downloader.md create mode 100644 mddocs/docs/file/file_downloader/index.md create mode 100644 mddocs/docs/file/file_downloader/options.md create mode 100644 mddocs/docs/file/file_downloader/result.md create mode 100644 mddocs/docs/file/file_filters/base.md create mode 100644 mddocs/docs/file/file_filters/exclude_dir.md create mode 100644 mddocs/docs/file/file_filters/file_filter.md create mode 100644 mddocs/docs/file/file_filters/file_mtime_filter.md create mode 100644 mddocs/docs/file/file_filters/file_size_filter.md create mode 100644 mddocs/docs/file/file_filters/glob.md create mode 100644 mddocs/docs/file/file_filters/index.md create mode 100644 mddocs/docs/file/file_filters/match_all_filters.md create mode 100644 mddocs/docs/file/file_filters/regexp.md create mode 100644 mddocs/docs/file/file_limits/base.md create mode 100644 mddocs/docs/file/file_limits/file_limit.md create mode 100644 mddocs/docs/file/file_limits/index.md create mode 100644 mddocs/docs/file/file_limits/limits_reached.md create mode 100644 mddocs/docs/file/file_limits/limits_stop_at.md create mode 100644 mddocs/docs/file/file_limits/max_files_count.md create mode 100644 mddocs/docs/file/file_limits/reset_limits.md create mode 100644 mddocs/docs/file/file_limits/total_files_size.md create mode 100644 mddocs/docs/file/file_mover/file_mover.md create mode 100644 mddocs/docs/file/file_mover/index.md create mode 100644 mddocs/docs/file/file_mover/options.md create mode 100644 mddocs/docs/file/file_mover/result.md create mode 100644 mddocs/docs/file/file_uploader/file_uploader.md create mode 100644 mddocs/docs/file/file_uploader/index.md create mode 100644 mddocs/docs/file/file_uploader/options.md create mode 100644 mddocs/docs/file/file_uploader/result.md create mode 100644 mddocs/docs/file/index.md create mode 100644 mddocs/docs/file_df/file_df_reader/file_df_reader.md create mode 100644 mddocs/docs/file_df/file_df_reader/index.md create mode 100644 mddocs/docs/file_df/file_df_reader/options.md create mode 100644 mddocs/docs/file_df/file_df_writer/file_df_writer.md create mode 100644 mddocs/docs/file_df/file_df_writer/index.md create mode 100644 mddocs/docs/file_df/file_df_writer/options.md create mode 100644 mddocs/docs/file_df/file_formats/avro.md create mode 100644 mddocs/docs/file_df/file_formats/base.md create mode 100644 mddocs/docs/file_df/file_formats/csv.md create mode 100644 mddocs/docs/file_df/file_formats/excel.md create mode 100644 mddocs/docs/file_df/file_formats/index.md create mode 100644 mddocs/docs/file_df/file_formats/json.md create mode 100644 mddocs/docs/file_df/file_formats/jsonline.md create mode 100644 mddocs/docs/file_df/file_formats/orc.md create mode 100644 mddocs/docs/file_df/file_formats/parquet.md create mode 100644 mddocs/docs/file_df/file_formats/xml.md create mode 100644 mddocs/docs/file_df/index.md create mode 100644 mddocs/docs/hooks/design.md create mode 100644 mddocs/docs/hooks/global_state.md create mode 100644 mddocs/docs/hooks/hook.md create mode 100644 mddocs/docs/hooks/index.md create mode 100644 mddocs/docs/hooks/slot.md create mode 100644 mddocs/docs/hooks/support_hooks.md create mode 100644 mddocs/docs/hwm_store/index.md create mode 100644 mddocs/docs/hwm_store/yaml_hwm_store.md create mode 100644 mddocs/docs/index.md create mode 100644 mddocs/docs/install/files.md create mode 100644 mddocs/docs/install/full.md create mode 100644 mddocs/docs/install/index.md create mode 100644 mddocs/docs/install/kerberos.md create mode 100644 mddocs/docs/install/spark.md create mode 100644 mddocs/docs/logging.md create mode 100644 mddocs/docs/make.bat create mode 100644 mddocs/docs/plugins.md create mode 100644 mddocs/docs/quickstart.md create mode 100644 mddocs/docs/robots.txt create mode 100644 mddocs/docs/security.md create mode 100644 mddocs/docs/strategy/incremental_batch_strategy.md create mode 100644 mddocs/docs/strategy/incremental_strategy.md create mode 100644 mddocs/docs/strategy/index.md create mode 100644 mddocs/docs/strategy/snapshot_batch_strategy.md create mode 100644 mddocs/docs/strategy/snapshot_strategy.md create mode 100644 mddocs/docs/troubleshooting/index.md create mode 100644 mddocs/docs/troubleshooting/spark.md create mode 100644 mddocs/mkdocs.yml diff --git a/mddocs/docs/Makefile b/mddocs/docs/Makefile new file mode 100644 index 000000000..d4bb2cbb9 --- /dev/null +++ b/mddocs/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/mddocs/docs/_build/doctrees/changelog.doctree b/mddocs/docs/_build/doctrees/changelog.doctree new file mode 100644 index 0000000000000000000000000000000000000000..e58d0e72598d7e7c03efaa5c79d388ee974d8a57 GIT binary patch literal 3515 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/changelog/0.10.1.doctree b/mddocs/docs/_build/doctrees/changelog/0.10.1.doctree new file mode 100644 index 0000000000000000000000000000000000000000..2c58bfd96919c755de5a2d1ad71a825076b6e3c7 GIT binary patch literal 14288 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/changelog/0.10.2.doctree b/mddocs/docs/_build/doctrees/changelog/0.10.2.doctree new file mode 100644 index 0000000000000000000000000000000000000000..165c1bac7515d351f57cfc75882bdc5e716d5765 GIT binary patch literal 17371 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 G&HX>%W)1xS literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/changelog/0.11.1.doctree b/mddocs/docs/_build/doctrees/changelog/0.11.1.doctree new file mode 100644 index 0000000000000000000000000000000000000000..c974eb7a4e945381cd9ea4f355896f5c985497ae GIT binary patch literal 5875 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 ROgNW3Q!DeblJz;w{{pDWku(4R literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/changelog/0.11.2.doctree b/mddocs/docs/_build/doctrees/changelog/0.11.2.doctree new file mode 100644 index 0000000000000000000000000000000000000000..13b83b82942edb9adc36f65ba05911fbbe103b95 GIT binary patch literal 4279 zcmc&%TW=f371qU+MBOY|aa$l3$|P_rJ#b0O4w?$RCV zE*6ExMOq+$1Nm4o-eA{BUrK<$?%6YE&gDC|&l_L~1O&qj{#HLZnnVyWt5rEwp>?j{oMKx+B|^vWJD+$`xjE_xRfFj!(TajE8_)qXDfECBnS(X**AT{UWZZa z5E1(^{%_#_7XJ5P)EYDoy8f)z^?M@08P;aE*#oxq!g~%#eU;~HvWRt9_iP3HtF{?W zGKQ_1KG3wTXtIz1Y|SIhXG;T_lFh;F0O))WowSZlx+jm?-JiF+C&x9|Zhb&UJ`?Q= znZ9r#L7~R)*l*Y>i+Zfvd!YeiM|tfZgZ;gV-FA@@IqfLmhRBZ2%TDlH$EQvwVWb#R z1jMHg6pRoe=go$Z?^dOOHEP4&uL-vKt{Ju-^ASK6vg>@7GS&Fc-%sO@Dt7iE6ler5 zZ}m-(Ug=Vc1*}#io>*C^lNoi4LCO?9oCHduF-V~(| zoqF@M6-Ck094@j z6{2x&Td#7{%ms7y*?X#Fycoq=h^#M0QRU8vD`fy7+x)$M=%G1H%(q*NsKBpz>2B-wvW5J2YJLjU$Gx`C|=pU~Y5TeGo_c81sgbvmJ)?4Ah zH;D7`%QQq^em5UY3KRRMch~cYyOxh%<`4SPD(58_lpdb~>yY&6t1?~8FlS!j|8~Hx zRr%!XaywMRPi9Vs>@)qTZnoJs!}cI#IjPHWAhv4aOv(A}wB*zYJR=HS>VcaId10lS ztG`~{Y88MW4h3l{u2!Zl*pnW^KD4hP#S4{%)b-6~D57zj@oK!& zm+fw1r0+xVxL@_`c1;PLs%JM8)f#02h~4-7AdFN4DGE)C6bIWT^j9r z>~YUFhqUk&MM#I2&@na1OT27JA$(uNP&GniI?irYDxmBYmtfc&zj~^r&t z3RN1=SzbGPX8uCb_jSq~+3%EtnFydwO357dX>?Xr35szORcAt(oyr_vbYD#US6mY_IyH`XO@FF-+ zZgWtY2uDJ_NnSr*z-KY$G?9V}#lh%7`<=Gpc)mCPF{V-q8KZ}#8L9$c0NWDn?Cr|) zGg2BsinE6cUlv5Lf7{(k=v0CF=)L(53n%TKHvqs>4F1Vrkh41(x~sti40ofp*%i#w z_CV4^$P`0#3E1Eo4$SoIS|W-8Y=k>_TBCuj*vf(W z=eA)Cwu2%h9fa2%j~1_i(wqZJ@L8sXRg zy?wv?2>#ragJeOxr%c8Fq!y3NB_6MrR7RZYLAV3epyQVfT-Ma2j}*dk;?6egApn`j zlgykL5UbK7q3)J%=nOH_yfGRGU0K*t#X1z>QjT1R&dloc&}dLOeAFN+C$86 z&>i^u2}#j8cs|?k3>6jA>4I7Lyd4W7tKW+5PdH46J5@s-WH2Ra!i87-H>4RZ5V% zKq7WFj@;03webbq>@V(}zLc{Awx#Tyxlh%S^Awq}u+SYSf6HL<-LDaTWdiHECsxl# z>|6E?`3^g)!BL>ON* z&~y@kSvxE>=ql^)jp@(fg-SpQ-;kag0qf7K3OSNYY literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/changelog/0.12.0.doctree b/mddocs/docs/_build/doctrees/changelog/0.12.0.doctree new file mode 100644 index 0000000000000000000000000000000000000000..e0c6fb79f669f86fc0f78093c2fe05e57c57b12d GIT binary patch literal 22523 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 P%Kjm!hVh3dW2pZFctq1< literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/changelog/0.12.1.doctree b/mddocs/docs/_build/doctrees/changelog/0.12.1.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ec5d48c033b5bd28456226dd633bafa54bab8735 GIT binary patch literal 9237 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 DD)=~o literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/changelog/0.12.2.doctree b/mddocs/docs/_build/doctrees/changelog/0.12.2.doctree new file mode 100644 index 0000000000000000000000000000000000000000..960636614b55e9e336793d8f1372a8742c15701a GIT binary patch literal 8618 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/changelog/0.12.3.doctree b/mddocs/docs/_build/doctrees/changelog/0.12.3.doctree new file mode 100644 index 0000000000000000000000000000000000000000..7daded797d6afe30ddfda274fea4006f1c671b10 GIT binary patch literal 4324 zcmc&%TW=h<71nJft!~!EZVkjjw@KZ~4?8O<30fHGB~IZM5UhtFNE0Bzm^;H=(&7v! zB*&79q)7_|umCNgOk+ZWqyOrI>FIixLnZq-O=kk4rhhMb*@$c=q z@UvT~OiIIrj&hk&?d+--WHdMKD|hd&?lX5_+lriPS*nCOI|q$~agi#TyN}%go3lUG zWnUX^U`FO~N(VgW{&8%8wd_BmsPm~Vi$W?BGbzvXryZV=A-#XU!;C5P{a%k}!$>nu zMXDn%dwr4)Y47gwVR!T$9>bU2z9{Kh2fzDo@u9lK?hYglc(rL>6)C50UR#@QvobLvhO>~cX+7C5t0_6d6h zL9u;Q>?ipD6#v)ozl)%j;d$`&m$k3o6A7+lU3P;VvGr^B*(3EwpjLorWH+-83M3nk2KGg7MLQYV73Qz9!IZt4vr6x-{>A4b&rql)nHqCk6w5t z+G~}*bb+97`E$?C|v307i-O-amx;JC(XU1SK-s)4&Z;J(Cr^khh+vPA_33 zA5s*=(+3VlD3SAFBgh9;YhbN9us_rS+kQ|EI}iBEh7!cbuwqyY8{&5we(4!w_8c6{D~EhPPS9K zQA@&^c4}8OH3q#0v(}D3Il}NYP%<|(PjQsJPcYu-F?;Q@EKN}wcgh}~+V+sXYU)Q9B# z<2iB`W1^EOw4801X3+grS93hynO=;kQc}e@?NUJ%D&SpBjI%eY%r~sGf)!`?XI5rR zJXxBbo3mS$-H8wN*LS8DW{)Zzgb2o96pkNguh(NqO)(%f51QSx=%Qc$Yu^<>`ec)kHS`4B)9Jmbba+Wv*z zDrgap$9*5nhL`jSeD~C_x9brSVjUHihRHmVY6wQ%@|wW`XvUZ2=fwu7Dny@O%IajQYZ^Zxpx>&Zvb8680h$8U1*F<^Y+jPg5}X z5*{#04feQ|KzgNdr9@(ob9o7ZFi@ZsvFT~V8+btZ%_MO2IF;q=kWI{sIGWn^?bm-W8boG z*w>|Pd#SlBkaT}<`R5y z>U34D$07>>DbiZ16Dc@yLtHrWr$ovnQvMZwRXyF)Guj0yy@B z|GhQme`YflkybpBK_+440b4J=OwP%ls_}Iv-1&=b}&{6XINc+~Fx1(xameQ!1B7y&g}8 zfn=N}u?)EA^+`OWy}O6|-SIg*`jg#$qUmY}zavG8AypF*BBLq^3QA(dC=Bs}Xx7^g z4(YZp~|7D?rw) zMkI@QOiFFc{9BgoNo|%D8Met`cE@qY#XyD(}AntNSeuXKGuBv{S5>^eJOYtOuAn^adhzBUV3kL@2{ z1OI|)hjqrV1>FalmL-j55`Zl^q`7QqAybkmm~8`{hr#{M?&1F7{qDg*_we<-3T%t- z(UHqU^Gv2stxr&>@hkQ=TVTN{+dqA#0b@rw?H_{uos!+`BPCMWlfVrrdMeF(-fumZ zI=zUIY)BChmp)K1K!~g}8%91TO9N|FhW)Z4nDzrb>^$TnfXrvtNs}_s_;)X+@dqV4 zdmjq4yqDMdI!UMs6ArZ|Sz9XPa@u_exuR*~tNA-L>ztka;=#Kp613QjM3%iN3KAvs z#~qQou*o-ne+RHbobbZ4VVI&a-Z|>rVMMY!Pxj8vItdPUl;{B5q$AJiMTch{z~RI=gr9)3ZA6ZO9?Rg5<2BJd-7e zPRydS!cLrd(R8LmU;?`fk~9vRx4)^?RXE@HCeCXp{Yg{l=O?$r>>q&H3q<|I9KN{k z8&^*I>5nyL*PHRF>|Cir))4TQucLFjj(9`Tlq%-7%o)!{p%NnLi&0P_Bj8eMOJS@*nQrqezx?8#g6rd?8WkyvxDs*7X6kw|+dqR$ih9!=

LzK`Hwo5^Lx+p2Yqg-cM>$} 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/changelog/0.13.3.doctree b/mddocs/docs/_build/doctrees/changelog/0.13.3.doctree new file mode 100644 index 0000000000000000000000000000000000000000..9583852629db22a724ef0c93fa34def6caa138f4 GIT binary patch literal 4173 zcmc&%>uMy&6_!>zl18`H#hV0d5rwdh_Q!OOv{@%C#)&W30kQg#nBag-yQaHls-o(y zPF3};1U4jr4H6Uv<4PXD;6DbxP@W)X;9!%1fv!`hPMzC#&Z$55{{GhP zQux`OOs19LLPv$nsCIVU6LMM@_l0}-7x$?lQ1bS^e0C=ClmVQ$q_TA)KB7==aWb? zPDQ38F5?l&CNzF4R0^#-R1l1|KF0JoZTSCC_btT z0_g*{!ZVE^*|k&}p967AUhOFB)CHWhS_a4cI+)!ZvTc=YTXf2Sj>iJcfh}5 z`$>x!wo;9dO>3H_3jkoNKGQr}dO(VpOS6NJosW)=PluGWl#yaW z5s(@_k0OM~d9z{UTXkt*y~eQj8iL(=hn5r&D^iX;3jlk%K%D9Q;kVQK4>ddc0BZDt zulq<4GEt;tT}1LXg^10)fO64ghi(QDE0==CN~Cfu-U6s0ncpUM#z z^nEJm@2<3=xsJOBV}%U@5XH#fI7oTp<0J|O9y3_jz0%Dr6kQA1ZtJEGzfZLqx9@i? zs7>#vX1c2V7j)eXaJ9Dyn<4L=@4}-k3`Gd;YF*#Ek$vww3SI4?rl8E*HaH!;HW)h6 zl$rV6sT~~>6m_N-Dz$2`gw6H9bBS3%)~V=Y|3X_@!1FV zC8J281qovVO8-PrUH7e?k%kC&wq!RN9tvMBRsOzZyOUPRcZFoyJr{Dyu@Nt zN$I;3Mx0A$mt$u)YZVgQdjW=x>9SG+t8F9G#P?Od3srj1SzS51KZck>8rs>s#2F0d5+?hG`$`7O%F_)J=~&b7`Oy|M&Zso zc7vg_C(~um?$JvQFB)PhJ)`zAI;tkPZs$2s1-u9jj8r)&eS{;W-XyOdFW|ETgB}Ho_e| zYqEi@+8t6EnaoJ`4o9wSXH{M%Z9c-QfxwZ9svyFL^=fQ@wDJIB5!Et!NjN~W$o#6$&s5cnEw@N#e=PG3|duJDhsFdj~8 zzzwoTro0#<2c|_~-&YAdxE9#L3j}wO8i{^>zHmsHd_*(A_X-*?N)7I~pMvWmp*i6q zAsOCr;bm}b_oh=Ta#f7$f_7t9Xz|Qpq_pdGDFJqoiFiSAV8bMJe-3%GxA+S5rJNnG zO>Ot*w(2Lxr$ENS^XfqRTgL6aAL{)B2CQqISbrb0Z`jxDEA}P(oPAc=zTYudB^>V` z2jASVuWs1q*A+YT%dau%eu=L68N06Rb~m^|>~_kLBL3Bit7?kJaKl&I)uq&YcyJ&Y z@{ts6z692n7Kkl(e@ug!SaK8m)Fe3wec=l;jxcb(%+RTwRTXG2WCNEM^WdOOH__yOm<_qGq KcKDW$$@$+V$UVLQ literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/changelog/0.13.4.doctree b/mddocs/docs/_build/doctrees/changelog/0.13.4.doctree new file mode 100644 index 0000000000000000000000000000000000000000..1caaa4c6c360803377b72492f8aa935655a947a0 GIT binary patch literal 8188 zcmds6TW{RP71qToX?3$?#Ys`y@CJq(39xo0S(0tF>CH(RLzR*yMwLy6?j zoS`KF37QlLV4>3|vwaQv(iXiu75x=`NZ0DURYKK8jN&~Jv%G?HB*KJf#2Rtj(-QI4*Dc%qBdR6N=ywU&IgNrH|qd;zd~x%|p(j&?2!M z0*6H%*A9svzZWmkA^8H2+FUpSVwi8(q~rLGTIK?1P5!dkY`o8-AYh>|C}Riw{f6V2 zU9z>+ph5(EtJQM6Zj)1oxHfM(tkpK{E@@p|TUkz@!%{!F+;$@}*}&hHFvBhpg$U*o z;Wk5J+JX`Q(SnHIT4}CcZ*H{o<1NRx$x)LE&z+3v_#tYA8L^qj1Js z(|VWre)`2D(aOn5Q;5)MM*@yijvIAs4kyWR%QZO%5$l?7ddX_Sj)LN%ZCVGgg60`D zT@lj-IU0pdEH^4SD3+6!>D!KNCfdm1Z^d+pM{-=4UA1jY&&#natfs#aa$y7^b6NMB zu{`5CK8d$S^(B>9gL1i(&;EAitZG23pI2eJ>-(xmTFgV7`0Y zxUB{71k28V8-@Nqem4EzP1xx>P^YGCKGlv~mk7gkpk$H5nR-ZZb;oc7@k+PlXK(7v zZ|v>8g`)9Bpxnk65p?hpmbaN3dH&1IX7gIZkGwZCI!{RVA`%|obv(i%p(smxdy|gO z1w=Ji=QN>))hpG=w=0V^M33BSlY%uoQ&_a|Huw)3SJY%_RMr4(E|NM%C}A#V*cNc~r?MYI4mloFG1t)0)>n zU1^gr-q{9Fp!m=>^3~%&t-xfg9uj zd6-wNAB9NUtYkYKgB+p4>k^7O+~}#pJ**y_l2+?RhlPzhue{peQMXHQh(+}uP)pbI zT6#a%#UU&>w$?tzFZ}a53^J!9d$_^$lbp@ZSE@OM|1bDI2!72%ZBN?4VIj?)JZA*&?0uD zg94`Hr!}Sc$KSqVVMesE$#q z&+NJ)boVb04-J*0m-eKLRWW5YvXsH1NSTe&zAyFddwQ|Tvu1w@&!mZ?_14vELHcR_{y#kz<9}kDFYLL9$>k?8C!;K6B76pbIB@Ay=!0>}g7&;X( z+{`dwQDC@P!eCXvKq_JAR>W{C!+=GB;Z_O5VFe7wl`zn17&cc@3~G^Mz(szV-Tzqy z3_q`g!KsL0HN${Kfnl|T;gbp&epLy>enkw~SH&h41%|Z}hCfxn@aI7o%14w(UK&q# zN|`}LyP#x0rd_DYd-jn5{_LP~`UY|==`O``cv*J}_e-BBx)&9-i>KurdNa$a1Gb#B z(0FWS9ZP*L>5dimO3k_NK%KNXmo>Lag3-@iEy)_#6YS|!DsiL9n^%iN>#h)&>6EoL z^KLACM?NP))90>kE~&O`HzYjiDc1xOXq9Xa$;pg|*tm?8RfpxQ6@{u}YowjsT{)dm zbWe9zPH-ZG<9G3qJg=Hqdic-@nJ&E~Y}pR0kR3ha>o@70ana1+)bH129acm+I?!SI!7#8LH>4?n0FsbPkVW zd9DL7J`<#L^8qFS=;-1@-8fQ5)A)g${o`A65hh#vL9g zb-2SK3X;;#@nJIq6AfmA3;l>lbe3-}a|i2-y*CXKhAcGDthHS~RVcxxf_585gbS2;(AH932%o zg5i@~2#Li)8xyAhk%MgY1=Jil3K%Cw`AHommN4!++(|{^D5sE zIer&`=sKPw;+?sdJzUhh4`;!;*q?j3fTh^HXR~5dJhzYmYU3Av$`biGM%F#79mf)dcB_NW_5CL2d{m8IS6?neQLcs%lOb=oFXddTu2X!4Hrbebdhc zu6F6<_tnfz%@@Hsu87HcasOX<`|kf!{riXd_nCSb%evwa#{nFG8J^}j(f3axdPz~# z1N64$rtd~_rtE3G$BgA5UR20q--MzPI^#<~mMIAzhJ%Owi^bTH;C^r_1R58K(2y-77ZUF|oaK>+^^oOv>{uLC=?P30L z{1#FO(;6frrnDRtFWES+K0V~AoU9!`<@lBx*+_cURioZXTNTK(mZ(!e)NcMCoo~qO literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/changelog/0.7.0.doctree b/mddocs/docs/_build/doctrees/changelog/0.7.0.doctree new file mode 100644 index 0000000000000000000000000000000000000000..10f3b8c2af04c7f05822454902300119ad3340e7 GIT binary patch literal 56112 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�kqDyE2CT4}pGVx0hCh^f4Uk>E9$<7P1sFUJ{A_48aDB&7)ddy_JU^Qh`FU)Q3MeTlzS9QIKXux9+t z@(JBU)1L7+iBmk>w!fXy{RmaKMI1{4OW3Hi{lTB6FoPRhLEsOfhOyc5+B4>{8GmTO zXy9xd&vpQ5i0&9cU17b6KZeO%0#%&cvE24A8E^Z;9EB44T||R|_>-=rn(?=IcDXc% zdQy6eu-T9N9rFNyI#ok?{G7kVh0VsbR;6Xw*?Rjqf2)g{*Cwt(q4Qm^A7RG7hCjho zR_sZMs#Z%Zg%2{&llY)UK_7as837{O@k%wAe=zb=zhLUM;LibnyRmF}CD7%yT-j*A zAE+2m8GOD~M3m41$)h@+46#C2B8Kp@8sJvRXq4>=;!e1|tL4p)-8tr3`2E`G%Mz=7 zEwSkb*LFbxKwH^s``e?r^_v*K{#Fh*#6f<2ZS+O#V_a1Q8lZKq zDPm}Ce@hL8AuSwM1CHbVG5=Dd(e`&zAke9laM=oU2HjYUv4cE>NF1>$QWeZWe=8j! zR0<#Yms(J?EtOWiSqhX9jAaj44RHg3IDol~Jz;82(hnasU87a8OUrdqrERz=$99d9 z`X$qnvR9%>nk(yNC9w$*gi!PoA8wR`1Osj}h;eba4I6QIw?#B7F_)l?d%Uis-_QvH zgLJ46WJC4A6^MM;(U*chPh8?}r;!HDX)HhyYgXOz+SA+i_*WpD0abPu=cvLiCA(O#p8Uu zfy<1A_k z!SID9T8)Z@vEh~YJuZy6?LZ2e<-yPb2Fjqwj+;BpVb|Xgc|~DzdIyWuYBCFDQl&ZC zy41^j@Sd`nR)sa)NDuk%Bh^o1>{maDALh@SPgVaE2e(x}OFv%34y)=v(2pyq=wJu^ z_;vWy)eq8-LHeuvTgk%nRC2s@ya+F zK>P{WEZL6BQGLSO(d~ZjN`20*1>=J?y*&0XXgw5xI3#;zAMJ?1XXX01JjcHx4okej z{11!DPNJfzV_3Nyt?h3-LMK$=`MV&r>R*FxCpNloYd-=jfxGfxZ^U*B-f>`HBaerJ f$4s|}k#4EADm0cBXv`KWGzSQCNiI%nuK51}q&%tR literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/changelog/0.7.1.doctree b/mddocs/docs/_build/doctrees/changelog/0.7.1.doctree new file mode 100644 index 0000000000000000000000000000000000000000..6fb74f382645babebcc3dacb01a5fdce33af81af GIT binary patch literal 8208 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/changelog/0.7.2.doctree b/mddocs/docs/_build/doctrees/changelog/0.7.2.doctree new file mode 100644 index 0000000000000000000000000000000000000000..86250e0c48ac2f15aa500391ff56fc2fbd88045b GIT binary patch literal 9590 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 SaX!2Dg}jf9i(X9JZu}ny2LN0E literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/changelog/0.9.0.doctree b/mddocs/docs/_build/doctrees/changelog/0.9.0.doctree new file mode 100644 index 0000000000000000000000000000000000000000..8b22b83442255f73b43fa541a3e82ff06e57b7d5 GIT binary patch literal 52908 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/changelog/0.9.1.doctree b/mddocs/docs/_build/doctrees/changelog/0.9.1.doctree new file mode 100644 index 0000000000000000000000000000000000000000..29398da9b5c325e75db0e45f2cc20e496bd6c584 GIT binary patch literal 4980 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 F`47Aa#GC*C literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/changelog/0.9.3.doctree b/mddocs/docs/_build/doctrees/changelog/0.9.3.doctree new file mode 100644 index 0000000000000000000000000000000000000000..7d5431f37e6ce3f9b03f15171dafacd7b5cfc80b GIT binary patch literal 3709 zcmc&%TZTLGm_#Y79oypkPVCXA%tKYGGxZo`pJ0A%OcT?Q<3Y0 z%kh-t1s&geG#Rz$@EK2zroz&LA^x5iq6%u3CS*m8NE9WxVHANxL$n%Cl829zx5n}K zm{&QSC(M-Mz_YziJwoU!I-`gP5q43->EktrJKbLNdsXl6|Nls$bMAl`qxjSs6fzWU zhvynaa(kH|T0@BMUPa30Yd^R3axo>@C9)88c8D;Zow^;XxOZ>;PX+SsAR|@IbJBR@ zw%+mVfwpeXkRn{`*=t{-4qtlDDe0~XdVQ9#F`JxkLx0Eh(*4J9#|3|7h~)@o4h)X!5HE9oBCCkr-@DL$N9 zA*e|Ohg(a^HVy?shmDjQTCVK6SK-;Ps}>0*8cr>GS(olU&_)VHYPNEU={Az@J-v+U z`F|gm1&BgCdnm|fKU6ijqAbE4|LK6+ZIa{N)+ploFDs{GD(#dmxa9g%Ta0G0WTYcj z6b?J#3VhyO!l$doiYfG)hkj|~WuRRnQn0qOQd@v)X|406W8R`!u#epRfTF612nKIONkn=m{ZdqGYIB*+%YdfabY4qM)76s^BAH2_ zam^dN-b!<>$y9_uS}*rHo`7>l2nJ(b6-fu9$E{jA;{w6q75+*1!X=ujI117#Sj$oG zLrjpUQ?oqZawmaofK62Zg@Tvd_>XS?!VPL#r;AZ61alN5Qv%-uFWgVN=N)35)K`Yd zDv_!Ho$dzB=m0cRG=7xFpd9; zXBL#!NYrUuH_KGWjI^me!*dLOQ22&y$Z|{}PG8y@SA;n}P8MwsxCVM;#;X}H&^CoT zp-W&^-%yLgEAAo<6T|3r=>eH?N^{Wn3LY>@4eq#`LF+Q5CE+3^IsOHRL*)y1(hfYp zs+ctm?b_|o>Y2wp;`W+Oo6o{VnDr&pxs4 zc(tO@)^Wyx3yci`MU5m7x6^BaS(P+;pau|7PNi6YB&xo$AzWzp*DUtCTjQZSYxdEA zmGNNI-A!BV4&Fhul}y^uH1OzT5_40WPVuVjz2C~e3JL$$`Ii3D{{cL~7zD=Q-vo`e l*4(;iF>B((g*aCCkXM_GY2zd|Qe$uLcJ6kM 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

5%phyC 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/changelog/index.doctree b/mddocs/docs/_build/doctrees/changelog/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..07b758030a407c9d345753006dd92d3c1ed139e5 GIT binary patch literal 3923 zcmc&%TW=&s6<*t!@yz(X&H_?mu?Q_}9^5l_vf8jhK(q*uEc_605kjk0O?SQ=<%nzDod$MOj6$HPalh%P5Fx#kI9%)zZechK8-X9o~1exayVw` zlnNtI|a+@r(Rl>%fpx@H;6*u*eS{)c5hW!; zVxL~GzW?dRAN}lC?yv6J9xpG2e)yo3`hF|*qLn&opjpGJ7*tGAf}Lp zue|&M^O+|G$&Y6fsdD#)J=DO!G6wwm1x7>QJ2i!8JW%3WVJMzYL+EQRG~*G$Q9P_D z8Gp?Y)NMN;%)+=icI*kC3B=McQ|TqOlk#yjg}TFArV7Lo99TkfQ2Iz*!o5r0K2acY zG4>3Zg3H-{WhUnzo@;^Uch_IWTq&v0P(_-dl28orEio7&b^KSPbb=IT&oOlfce#T*I#Vhw8!27l%FQVCxb)I9%Pt66*f48JH>e$&1p{+Pozq*1ePcssPPl& z+IACJOyDEJA<}s@P}d%^%E)+0vfBa`caTZ^|yuhPC2|eLcza*8K zSE-C=xv!>UJ}af>@#@V`B8iD#3oYt;J&IGI**No#G*<4;y9J%M4`;C57NPYZ^q^sj z=lEy8;h&&Ha~0Z?7hYP5eD48%kf-_xh&%Buhq5WAsKQL-!nj`^f8QRKyo?tcsCQ=H zQ^pLo`@XQ>nr}5A>!`dlB#TI@Dcb3g=L`mbGd4U&dJNLh1O5ET=mW&_P)(8z@t(32 z|Gl|*bT0Gtdc##DxS0eUz=WY+mZ7X~*cdH@<=S2K>=_C&j~AV}a3I#DuaJ9ttnUiL zW-*B=W%#3b-^!$~`+c1=dd|7)0Kc#YiLaXtQ4S%*?VGBE z3jg^MP^iHGj0aFeF+mM9pzzS61U^wV*uu^ZU99dzzj59;lnh2miuS#O1;n|5jz0E$|;C z0=f6jKt@gR%{k|*4PK?xd{}9b<$Nr&MIi*%w>O9j?*5v@{#_c7W4pAu*&eEevjR;_S>AZz5k1vw;d YuUR-yEK~mlHAp?kHF)4>c{Psy1L*VcX8-^I literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/concepts.doctree b/mddocs/docs/_build/doctrees/concepts.doctree new file mode 100644 index 0000000000000000000000000000000000000000..64587be493869b0bcd2593e752cebcfc1897cc03 GIT binary patch literal 70091 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 T`C|yL!@mz*VN&j1Y3lz05e}37 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/clickhouse/connection.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/clickhouse/connection.doctree new file mode 100644 index 0000000000000000000000000000000000000000..e33d705145f0bb89c889c7babfb9b76e59397078 GIT binary patch literal 3727 zcmc&%TW=&s74~h$p0Pcvfa-t7fKP7C9|@bCZ4zw~ErpvVg?txBnP8{kM87n!1kf9B8FhI?Dvxi;KDj4YCj zE_lJiYb${D+`lH1@n_nWrBo(iQeNxN#ylrWdU-i!#+3SUI_3Ftq8X#pl)_ks)#0Jkv;$ z+f4=08brMJ3X)fEdzmd)hdD{F5rxRJO@#66*lkB!9G^zsMfKAwxU0iK}f7=a`wqn?}odZq% z3YJ}nd>8mnQo++}CatFDsVoYbhAO$NaP^h0_23%MygPa0?P&LJU`sF9!hVfs;${GP zPNy1ZAZlvzavGyC4Fxe>X`W{Dq;p!y@9<2J79XA3)s?*+uUNk>pB}1T+$o)t$E~m9 zm5!!_cK%3jLVs~z!S=b2jR!9MxpHa7E@p2`ObvjLm5;&xv|?vZ5Rskkg)=q_)$qvE z20Lfnh<0u4(mQ#=&5p7^tioL>bKjviB_foxog_=VQ`_m)wl(1OH%i<(&~s;h$U8EJ^{R~v5F@X!oL8v?ZHMyXYyoRV7B%pEk8 zSa379r>QZ#SYi+y1z5+)uu!rvv;YYEeCtKHr-JuHo&G>0)>L--1v{~|HzF>TCUnhaONOT<^;Bf zx^Ulah8xIwQr;RSi-}ZAl+#Jzj0S)+F*HYd4AQX&`uW3)pCFziwMf^*2g*wP4;%5Q zToUklP1S@`vxqv-4W?mdg0g`jbCeKvEPu7gP&TIv__zg_OtIVndW;2yy$`mQWG4*f@jg z9`G9Iktr`0z(Cy;9)vD|7Q4n4S2yS)RV9YStcpd|&|bT3T0HldS#GatC8%8>5vwOhZJ4Cm5aP0Rw7x!vR?d#u zzIG4pe5!V}m#B=jz4TZIEsOSh*oMO97skpn&mI%)cEiVtc7|azV}fz(27zjBO9Ve` zR{1L?UR!n=-e;JloISPfu$$Sy=MGa2d|-76XsRfofE&KbsV=4B!NcvQ(sGU;g?R_w(N& z3o3wbTtQ>}-fAD~Y$5NBxgKKzj@vTJCcavi*rQK|n;RSYYxZPp0iW!F2dq8(>n=#m zTGT8#u2&DcO}7&ef|~vP?6*(C0IGfoq9@i{dM}9GuH?D>HbfEe`@Ben&Cu=SGW_}& PW??eaO*AZPc0Tzp9ZReQ literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/clickhouse/execute.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/clickhouse/execute.doctree new file mode 100644 index 0000000000000000000000000000000000000000..c2d696c98d7940232b9fceb39dc980c1199235d8 GIT binary patch literal 23021 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 o=oR#UM6zA!XjFw9O3qhEbTL1t6 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/clickhouse/index.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/clickhouse/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..72fbcd8e76b2d1c0911b8abcb875e2473c1a4455 GIT binary patch literal 5083 zcmc&&TW=gm74~h$p7AZVH@l!+3$rM!BjgzySb;3WML~#_#RNsW@Ul^>rn_dU?7pR| zIv$ISNEQ-eQ4$X+cmfF_gtU+R0VJMRiueutOZZOpWyX^@n0)|CBTt_`b?V%{bD4*Y z-~VfC#{HSKAn|i8L)D3sfGca3h!F8u+h_LXBm2PanpVb6Rgz~uw`K+$zK~&%@z~zA zyJE(?q4K`cQbSAE=DfXwzeCNkA=hIONz8TF$v6wN;4s7q z;&FGsbMTGMyIps@E8~EVIzmTb+ls}z7ACYZKH#tj7FJe6>D3DgSG*0U_axuP|9?xI za;D9+&SXE=2qXn=UIq$5GK+r5lmZdo8!?NDZ%aX*jvxBWKYh}f6#`7%CV!RH2yYC?0T>$fUh|I19Ux!4oy;y^6F^qwtNT}6B? z(Ggv-e>4aFdDHT0#EAL4k8EldX*ro9>&So452b%1l3ek_BAG6=rvZCpKrXD0I1PV* zTN`w%_&z=z^Bz(<;$4Nfv#hJ5wCn8dQtEg8B#yaHNbClEZ{lezw8R2@Z=L3gsyHyA zx@Zzc2k0`J|9PQ!Pwe+z>gY0{0IU5mZfCYGIFN zfqIs@I*eGp4lDy?{Y9u-*6Fm=PKOlUg(&wlrN_l!tL0=plDrJ?KZXGz{ij-z#J84oYB_Mem1z7A{u{<*B>bz#^QJ!+o^2te6v zE)-+e52h$+M5M(}aeG;xi@F7nRMSOZpPDc7QHm<-jfr0W%yS*_ffdiiEoI(#DGM3` z7OrRWTVfbfQ^Uz`NveXwS)lS+nNn8TDPS z?9V;3Qc+xI?wKXUH3b9{;|BFVH$o1wB-R|Y*))H~Fr=xvNSkJo>onK4CqC$z){w`P zze$Gj*K!krW>JcV>d@*&{Tq7HhdrH7XA#Ac{HDvKH$IuUt2Y8-C_c2=; z_meEZ-jE`5a1FK7o@t^?4qzrcB7-U+MANLXTqoX`Waem2S`G3j_3D&_V;w;v8?%^& zByinP93UwAV^XSk9wgoI|XSm4u}B%rN&ygj zMs137jXSRU;5zYm#AN7U9;LeLwB9HOY~*SmC3ZQ#W+ zGA$w$4H|6EpDM9Ui&zii`KRJeZdNA)N}|lFFOe3M*$`h)2)7kqVap_?;KL?|1c>@c zSm%gieQkiVQLwFxHpsy_6 zD(TN)-SIl=;?b;nZ0`UmSWtmEtkepHjV{Kfw5HA!a8*(3E?7^n_{9O|*bqaon0RaU zewe3NsQ^d(Izr#1-YLSrLoqnqd3(IOz1=C6$PdlIe>(Nq!#|GCefIEA9b0c?$0so6 z%1ha?B<_Rb0sg%(U`;IK;!|cCWfPn=3zRzm@_W4>-KNG={FH=3S+4mD7&?vvCwY3~ zV`j51*_AuV$;TY*7uSH};}=NcL$m*5+?^3pD@RVca7U^gj0rn+Aqpzjz{qpRQ?K~d zfwV6D@dT`|^2ZC#IDUi^FL6bqas{NB<=S*t5thp}4HalezvQ$7>YQbhp%CW%&c6Yj C4Y2$G literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/clickhouse/prerequisites.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/clickhouse/prerequisites.doctree new file mode 100644 index 0000000000000000000000000000000000000000..72f71e305cafbf4c0e7444967b1a1cb1c47ce634 GIT binary patch literal 16167 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 AcK`qY literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/clickhouse/read.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/clickhouse/read.doctree new file mode 100644 index 0000000000000000000000000000000000000000..3438545b530db91accaf9f8a5240f747c0c9cf67 GIT binary patch literal 17494 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 WHsCjC5o(uha#bm+Q$jDUSN{c?Tc`{G literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/clickhouse/types.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/clickhouse/types.doctree new file mode 100644 index 0000000000000000000000000000000000000000..6ac8a2dd316357011507d97da88806c06cd9a5f7 GIT binary patch literal 121722 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/greenplum/connection.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/greenplum/connection.doctree new file mode 100644 index 0000000000000000000000000000000000000000..0daf2675ae3dc3b1e5ef823e3884e3b6d91a8f75 GIT binary patch literal 3719 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/greenplum/index.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/greenplum/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..9c9663de471bf0bd1f2e527b5486acd087c9b49b GIT binary patch literal 5016 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/greenplum/prerequisites.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/greenplum/prerequisites.doctree new file mode 100644 index 0000000000000000000000000000000000000000..a75a1e6d89abb0f1b063bd6ec2f3d843859594a1 GIT binary patch literal 75612 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/greenplum/read.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/greenplum/read.doctree new file mode 100644 index 0000000000000000000000000000000000000000..cfd1f4ddc64b0db2a6517a1cbc9c5b17d7de076f GIT binary patch literal 51663 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 IQXk3uKdjI&*Z=?k literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/greenplum/write.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/greenplum/write.doctree new file mode 100644 index 0000000000000000000000000000000000000000..6d1b16d013d3cb0494d0c53b4037b3bdfec81183 GIT binary patch literal 18353 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 eaH%Ri8b@n3p>|8%qw|G7~ib8EQJ zNhJ%az1s_fl2*olGK#|v4T9s4pHo%cHE(%2}|I(ka4flbznKs-&jI7dv z&UnSc=RyGMxql?d_zP|8S}Kz=DX;YxV_uRuy}TSVV`_alnecL+XvV21bi(B%BgLFf z9{uRzynTny`0_jxmJY}GcWH>4Q?nEyD{4fdC@Bo1a3l(%)#M_1{Nv=wBtD<;s-QOs zGo=`Mw)@h~gKM11!uQZ`?Eg{_x|jO5pdLgd*d!gzM#wyomcJ^7y!DedR zx?Mx&L9J&GUB83X!OxX8X|1G?^Iv$kBX~vq)t2km1ZjaW$2KGELpa4wk+JXM_dWc+ zkKZFW)raNL)}MB^{+CGbmYuUB_L%LzSI-IQR0X~POW1^6Ty24W+YQo1#jtIg0Zsh| zma7nX0Q{$nzoO^4tSXv^`nYTm^&g$;!6{yNceM5{+WKqg?*;wrSNJ9F8NglAi3S3Q znwYYl#0X465logkCqz*tp^Y>$kNDg>v)^a>h>=o^_?N z^C`UveZ+l@;Y%M|3mo`kAO7>K{58lgy#DQT72Ak1u0B&0V z?74ej6sfczv5TS-=8Ed3jC)2JBK*~c8+JUjz|n@FY`RfyRVb3Ql{Iw-9VM30)a_|% z46o*B@5ceGvER>>tPHIHu>KziI;vje+V!P1wKaaqKAXD1oK{p3BbCP?ma`#cjl;f@ zG88RfdgI;Z#Jl~*gcN$O!7yx(RtcOAjm*=~X2LB@>A_}o>)rhs#8lGImCLtizKDBe z%k*X@Rq5Zlkw$N;f!;DqCk=rgHWVsoq@=I8VQJUups#(<_CAC~(KvqueMJ$@EjMIn zbZL9+xnp|65k*6c)k|uxpqZUR-RLz@6`}|ZOqdds0n(Av;8L)USBP1PAtDQKc{i|T zcK)+-&GG%w-OH3JB~{AMAO%&(Krl5oz-JTr6)C+a#k7*apKjC$T^i6m{pjw+ z+DmsYH~`>jhWH3LYhpr1dw_zB`UQnP$be4s4F|DY3($|V7>*Hk5( znpxC=ZZHX(5R?rJ$xuSrocz_EI|U%ibWxd${+H@HAmp99r*A7mZZOp5OyCFb?g$xh zf1pc(E$`13C9M&tlXz~9sgOBoLwlMt0(+47rq~eW7(%?hwl!3Q-89acwgPH9QFNJ)XUHZDqU z-ElhsfmJbU8rnOzO{-TPbIR>Ctpv3TBx0fDs11`gb3d+7$LmXRXyxpL?Q3`c&Q-I4 zy+LKHZJQ@LXqheU!;Ta7xiD5Es9POVZJJ}h5ALYYai083P5AH}D zbXw!1`*-A{5i8?SSr6A`wKtfDXfL_6p?P5V^^PjUxkSM>uOVf>0h8; zuu!7%umWqew?^xt)vS`S$Bp!@M$vWD#e8JF#V#~}I5JnHW?#SklY8@ z6R?45{q6KO&%@|x{<1?iY`5-S5WC&TbNh?nM6B=gDi^jupOf?O*Tk?0GofvtVKuW^ F@^2m%mvsOD literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/hive/execute.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/hive/execute.doctree new file mode 100644 index 0000000000000000000000000000000000000000..1b7c553fd909c88d7e2c57f32b173c72115febbb GIT binary patch literal 10194 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/hive/index.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/hive/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..f94f4bea6fc5dd6a7743b070859e8a90931518e5 GIT binary patch literal 4974 zcmc&&TW?%f6}Dr~*fYMxcGH4di)pG*m&!8GU0aw;66CvWUwomOl5AA)sYg!q*r;juuRMpZ?&RJe%z3+mzkSWJ5!VwDNz8TF%{U9R;4s7q z;&JbK_r|xn@AcgAo{R%N?g|}+Z7Y^;TbR(w_>jXQSXfyNrPt0VT=6!X-qU=a{{Jm; z%9%FPI+KH3Bajrh1sNy=$t?LHQwl_UZ^SGrzAXoNI(Zl{{}^6yJPRz;R&1MvJd@VE z_oV`4P1|R2AOlutW9ENg#XBlDOPY=7S}U%YW(BL&?`2ARX_kb^==;_zhBD^%@RDh! z3}JyXoB4o=*I|^{LBzg>&ztzXh0iq@)r96w*Y8xi{+CFwx!4mM;)Yl`_nvK5T}6B? z(G@*${qPd_7fj2mGe#`r1E8r{z;YTQE#Ti1@-E*mVDv&u8n8qL@BlG8t$u`?8g!HR zAwFGm02oKSrx0tF^>mc>oUJ{Ad(ThenERA{yGgbxKjuq>P}f zZXlz>GwHDS0jX4G%;L}Zm`a2xsE@2G6;SgRg_?bFy??GN%aD;DKLY!$f?eE%m5WZR zWj~41B#(pV)G`|?6*3-oSrC-fYDp))&iJ&Rg$Xajinw9kn)1twyo96y?lS38Ww&qM zz4%O;p?uuQrILj9$F^^FFFr7N3vO;!W$cGJ)y}ZEQT$Te=Vm!#L%W)nJpkQDR=S_wHpZO?hV7bY9Z=(b*sa6hahzMKsD##WT+@N)AXGixa@J zZ>~=KcOKBG1Gp%>c`={56FRn+AmUzzusX(T7t7;gj&4eOgi=r7{0;&0;bTs%CS z7oVE;P56jW5S10bITepi#p6@)+rv5WOng>MH)h8d4EiAOMZUqy&V@LA zoTs>g)>1d8lw|-z4Ja}m0t01T*rGy#Dm%mGb}`(=ie#is;@AQ*7=Qv~@Ch^!oNL^1 z#Ru21$0H^~4x{%!b*nqHbw)l)aHeV0y)rASe1qu~RmI140O+9Y_(>ST`>+9hW$B=#KZSKi z8>owi^Xiekg-pQ~iJZgUt5DeJVk|i8>O>*0Dr(&Y>#-C+3*a0ZVX_rdZ_V33&eKr( zNJspHLEohA6yfhs4E8%$Y1z8kEmozE&ATt-|NOz9=)~s_{@k_oB6V~O1ODS(>PQmz zjiVv{JvT^AZOY;^W*TMVn>UMu7bNdb`tRSQW>oyfgW_23--{SDjshonc2Q$yt1i=( z3(2uZ9PDT3`J8kG|u&8*Z$yZm1%H!akg5&gK*4yaF-Ero)Y54v9fCh&rO literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/hive/prerequisites.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/hive/prerequisites.doctree new file mode 100644 index 0000000000000000000000000000000000000000..f51140fcfc0b161a5629ac28974754cfd8a5e3b0 GIT binary patch literal 21586 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/hive/slots.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/hive/slots.doctree new file mode 100644 index 0000000000000000000000000000000000000000..f510ab3a41d4f615370de8b793cb023df1e87590 GIT binary patch literal 3639 zcmc&%TWci871nJ=nvpbGt-Nu9Ei!i4LoL*jznK8A#oJ@E*Pc-9H6guH@l96Ig zCl7yoao)beXMA~{2}_4#{JS(n&8b<6kQFr|QIr&hQ8*F>(Q0y$Jo-uU(Ih^f@T#CU z2{WY_dbaz_!-avOGYXI3VdpiBKD=Xa$J>i`uk!uv|L;jO&JBq%if7g!ks)#0ywFIJ z+sy^h8bo~eDpEFIdxfo+hm7Rch(hGqCc=1j;4)xj^6HfgP-kn>-9wF|>>W78PLZ+iBz=bgBa1fF*3gF0Qt~zwHL;qGH&#&48wU z1Ity290C6+Yyq|&nX~WK*z~PZjD&?J$h5a>vU24MVj5F5t}bPE7@c1-hLwsQUiu57;Hwr0hKMf z*%SAcQKZs>#1;vd&lS~8jrNQ*MEI)>H|%(5fujwf*mR@Zs!#!GD{JZwI!dgOsoT@k z7+%fMyN?4BV>6#ASs7XZLj6Ayv`f9ntLsZ^YHR$IeKvK2IjyK7MkZ()+V6Gd~W3XJa>uz?y5%uHP$ZDR%>5e{B- zXkdLeBG$-sNpf4ET^$y-tkVS^5!FcGfTF612m+5HB_chfeo3k|wS`P?%7CVOC_AZX zy81Fg1l9VSYu@1XC@r`qnFx-wR_=Cnf|@&kGuTV2SacA2*s`TFE}9AWo$v)EnyOfs zvRngfM`m)!W@-}{jp)wEu=kKoKfpkxHLhq`dz?dB24 zI;n3BlT{+s9Od*Na7F{bnHpLmJqGF61O5Er#ZM8>k(%Xe;sa$V{)e4-R4xg4y{0PR z)Xbs|bc0D)bD(TsNQM%^TI8?x+$jKAri;p4^uJWs0U@v5J$+jla)XIAX97QfcSp#8 z`vYAPtZ-jgl(a^mPU5*arb6bV4ee>p2<$=Pn_@$hV+is3+SX7J7SA|W+8*#4=#e?E zX23w(6z+#Effl>Q7Pl?vB26WRUE$gTGG#^!lwno16IYXX=tzAHm#m}OeeS3v=Y=Vkce%Oqc%+14E4Al9j|Y^p_Q`}wy)j&J6Fwm z^%9k_wo0Dppk=nW4+~6K*1}kM=GkMS-EO#SXlEEkQzjUhGq%eG;Zl^A{T$VY^NDg4pdwp4(rN;7zb_V47-@k Gl79n%n1@XO literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/hive/sql.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/hive/sql.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ba98fd4abd2529c308dd82d23f98cae02282239d GIT binary patch literal 16924 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 Y4Wz+7)yWPbw-f7Xx&%@BK~gRK58vQ>egFUf literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/hive/write.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/hive/write.doctree new file mode 100644 index 0000000000000000000000000000000000000000..167160a102e9edc42623fee7c22c389dce41f80e GIT binary patch literal 52533 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 I8oA8>2ar%ctN;K2 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/index.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ad0335723e2615553595c340338338e79f82ac1c GIT binary patch literal 4613 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 HPNN?H_b7$M literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/kafka/basic_auth.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/kafka/basic_auth.doctree new file mode 100644 index 0000000000000000000000000000000000000000..3bdc987ed0d1ce33d8e8c3936e05b5df554e5195 GIT binary patch literal 3691 zcmc&%-)|eo5!NqKA|+9h6(ubaA#4h#&V!C}+oXk1v`q~Z353#t(X=lyw#&OEcf-5e z>;AAMAO_k30c>F3vh8F4)_%KpM^cO%pnZt}!nd=tvoo{bH#7QM=bwKWY{Y+Vkjc~< zk?W+C8PneF1wp||6eB8ggSQeKi`1VwPwwSf1%b$kBvm zGd8;a)60wI96sa8i%D)-e~5oqhN>AeOB1qWCQlTjnc)n9L_@3`T_z8ImV7vh$48>f z*iFJsk@r2{ed-ZHPq8UOM2N7B8crYHak$m(M88+{{_*`s5}k8>YK#&SYw(ca;kHGl z@knkr&8gN9;=5DQqW;>;Y_(iWXgWt0qRuwWjpwIs+bZGRhyT|DdDl;AnTd?n-nh-* zc>aO5Zr9LRxYqMC*KJ{S@GGSasg%s+?APAy^ z???Fk7{B`nsteDfufJ%0{U4FwEx+JL{2||e?LDWoy(;MSc)~~g@_GyU+pb3zjN#jM z0yK4NSgu0k4EQH=I-S#t3DqLKpw{rK8dCo?st%$OnRmzQ2cx$i!-G!nfd3Z1#C-~Q z7i^?~0jfr(s75gdqdj=jDa{SutH?JI0|2fp3)av{( z60oy+;DI|@$yA8O^D%3|((VppuyOY_nPsaF{n;|T^=_<6!#*oe}*XJmFq*->{3A+)rL>c=iJjrP!E>rgWDy$ZI0Ym< zJV(kxNWni*B4-3mMP|_Qu4m2k;+Gd%;QO7sn}jJP72znDIa7Fn;A(2XnmY3gi!8HPy8M`j3%M>vp9U^Pdz`Je$%1N>mxvfy!4l`R+WPwLyH3|fvs7jg#gGW&k zksh9YDXKKHnItzwK+`d3Cp9ChCnH3_)@MSC8m~tr6Pix)5J= zwJaYYn1dieQDP+U!u_CaVG!%2x;0#uiBvPt=`3hQ2cSs|EATu9&#?#j#r?~lBcB5` zP1no^$~awVa$z#(@S^Kv_oJupVkHKmD^_JGmoa__Ucjs?gEJzFa@|_NIeI{ zk?Lf9NDif(pYna}?%mm{C$bk{#@h6Gs>3bQ#eSH1!h9Fn%2UrDQSElaW-WVwW;EtG z7uNNH)ZA7`ewd$%mpp%E`FZ#rqnQf+#Ja=PmkoOEFcrWD28Mv9S`rA{>eWH5N@_g} zV?aVNk@*5FQT2^A;X=E2$6~L&H6FTm$366m5R1Du+nN{LA_})bcPwtPLlrs=v0~` literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/kafka/connection.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/kafka/connection.doctree new file mode 100644 index 0000000000000000000000000000000000000000..3ffd057dd531160587d038d4761c431213bb67d1 GIT binary patch literal 3687 zcmc&%-)|eo5!NqKA|=t16*UPGp==7b%7aL`ZQ4Kxk`@Mv214n=Xxf(;hrPQccf-Bg z>;AAMAO_k10c>F3vgu?0g8q^HcJGd)7$-pcQUio>#7rUjp6x#M2%)Fwlp-QT*hUSf5AQhK>UN^vt9oDk|09Xcxjr#Q@zfeTWO%r3 zo@+dk+sy>g8bW+`N>bEcd%3Nai_jS9>}|XM#`M$r1r*b ze&X5t+PYmsX5m`TPF=T!)xk$fo1{`w$l1rnPZz0_6~w#XLzw6 zX^tW9vSuloe z+Z1T(*05ZK$SLrj%*k|4E;3n`Gz$c|sv-5gQFRa%&%HZZKN!9J9X#j+57=+<8@W#b z?}CmsFhJDU6xBH9U>s;LUS2xR(`028O|NW^@D4skv8!t@SHfW3hA$n^UEJZy(RB!J z_-GMaOP8HL)09MRm>Qj**54f4$-Qtq#qxv>`2 zL^e&_o~Fj|a)v5@955Tp{#41*&=N4~{)M2h>cxAzuC%7I#!uL16W5#3k}6`P@>oPx z8&XtQ>?5KY?bj|OxOWbPezUYnV0CC@mV{~(cHv3~KC4^r?oVN+l!mTs zzC;a1-b-5~H&dw!|I!UKN?Zj97okaM7<{LeP(h<4dCm<>+KPw!ngdMbbmv%aq3633MKh^#H2Mm#!8IJ1nYb

*ToCI~b!(U`M^eo|r_-Pr9e`$HXo2T3c#b{DFCJX}3i%wUX|`rQNS5Y*)|v-% zNx9ufm-P%uK_LuK{(8@y0gy$qVCJI!CAta-dF77uZE46&!uX5{ z`~cn^Ap`CYbU`re{mp{X8i_iI>w1|8nUN;7XBi{#2M=G94OxyU#Ob-Ma77qVW4~!? zz$>6fX1tsN15Hu5AF2dO?3!Af%WxN|nHZ*uIjTP`QksLlxA1^bYH-JC2CaEQ3&KT0 za!j{zUV7<{n=S~fifLWYUb$^rKJ(~QZm%vS;4YAexsrn$CaK$hoTQG|XX8-H*$La% z?*5&vdJua7W~>dJCpz3RUF?TZCyaHWtvvPY5z%fpY}T@KG@}U0?w-TmDC`VV*o z6~H&Fp)q`KeWH7B$n_A-Z@6GsKJxW^#2$Xp-`v>HKQRAd0Qh1LI#BK4ztxGTorBu> z#&w!Lx9N5QI6&7=C%=CjdQbfq9?D@eeRqP{?b@E(Ut}kOeV><^usI5yoQ1zC`gv#y MP5lhhnN3Ik0UiXJ6#xJL literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/kafka/index.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/kafka/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..a46e71e6b7e1fb72859407c8e0a8184ea2092350 GIT binary patch literal 5696 zcmd5=O>ZPe8MfD+v1e?L*XwKoN-TzakX2bV(sM()&I_=kVmZpg^o=B1-{aRZ_ zY{>Wb+rpSs?{~X08g{ghJPdS4Cfz;@hP?aKr?2dm^YG|q?)JlsH{1BzH%tw=nJkgS z+=Ly)SzrW*A#NcacdvBzKGV6{b#SI4pvNmDNys}LiCNe8zi1C2wnD}Knd#vq<+F^h_))gVhJFMZ}80R<VR_F^jRFpfEC)<#V_aLvd-*^VME$B7Z+{4qSg8rl{Q|gk}w&5 zDYwg^jCp==&el_gV}UYTS)YkVVU*axiG2dUPvZ9keow)uIy85>ey-B>zhpw5iCwWN z_Qcu)_t|FERwUOD9nlq64$eV-$u_*P$B3n@4>r{cT251>3I2OWY;eT(3m%v3)v}MMV*|29J>am4o`VR?SM?EJYo5pd_-Zwmcw^*7cZdcw+l^s;!5v(L8-=*I4nvonUaNi*rAZbC_bFyKbt+^$>aZXvL)MB(e53fZriBq_5=QLVC#Rq} z6DYV2*r%j(m`gqT?3_udLKJy5lUfq#-^hFR@|+oy&rZBEd4J&X8I@%HkZVLTBjX|F zaJw#JKg=lZ;)9FH-x0UPce!1S*eKws5&6L)VgO5BRJP4)V~W7jhGEjrPi$StAV5ez zu^Z)bu`Dk7q62ay(@mj#rF>u#Sd)b#d0~Fi#JZh4^PCs6LE5|AbxZrevDA|6Y=Y5 z&{|OWty$2-Pya_C&0}t#Y3X|`1HFjo^IYSIE08GBlNs|i(7s|(C0>I0JMo_QeFf+G zbJ)BcaU&Asxib*`<20fdf#`2OGDM%4!yl?!9G3H9(=CPgLcU)x?9bva6(r5;a3e;3 zekLG)od#qFfc)wI7LYNyo}Po;DL0*mo(>a3!MQ~5@8TcgpA~+s7ZTMD_)Q)r0P;)> z|22)_lfdwA9|}W0PEeObkE*pjC{@-rRRQ2CkI4(F9oqXv>{*rrMb<>@txDJ+zh^(r zM=3J9HyQN$$DS*@Zs&*f#f1fNP1{EvD23|db}o7l$odlvE4`{hXk5FA>-Hg|Sgb?G zJBolq#dT4E)=XkpSR{^8#^sTWj99cSKT}lSd1W2gv+LD}s{ng;RdYj`4xPys)mg4p z8K?x|8O4FEe~Y0R(Bmq>)|1SnnaO+NrJijJc}(t*CXlM3UeK#lH|uQj{H zM-nI+W->h)tp&WF4Y6J87E>`$gam?2gpmf0%;#hh=_g}g=Ar*d0;(!(BQt~D&+lr9 z=f_Te^te)q@&s!85E&aZfNj3X?Ph`e)G9T%N^W;%zRWCf_yy3>wu*HpI#jbCJN;p1 zvOP~WfO}d1q7XLab|Yl*Fhg?*!~?vaLtSIH755Vrpg&E)9Be~HUC-7LSq3l@h{&KK zgs9sVVo%~thU_^i$~S^6O1&{90jwLKVnM|$qy;zKh;s-!{mD?Oc@`wzC?Z0ghvy}l zd($%=OW?CNseE*l-Sh&f8R|W%;|ncUDp!Hqt-}~J-m&vLEV@yOc~vE7AEqa4qPe1+ zfU}|1C_t>CavQ&*JlrCG19Rdbh#1O9n*1B*pSG=(r;{K;#;nr_azFRTHSEW#h6}^G zlO7`%$DKqC;ZYY!(kTE*&+rJxgS2H=F~0lMmCpjtmKyjo%hSkY`A=2L!+9A|7G*?7 zax-vyARu(9kH=;W!}{=KV}<|v`uXHGyi*2!{?S)gH ziu;4x@Qj(el5I`9494H7L#Nva$}FgKuQ6>`=wmTfWTc+94H`4YDU2xd)IBYBsO|5G z8;47jw{B#1WBO!C(r)+?{2+>BpJId@xRNW3njR_SaCHDCMExWjlf{WXG&4AsZk$@N zQSIs`T{yKqVzDyRcwL-btNemq3gN{^lM4ic3p`%JM>K8D+=e+ivmMO9y5zjDvQC zBcXfv4#ZEln1}D)HzZ!R&wLGgEArI}!4kW+)NRxV?Qs+eDqvvZAF!1EfU7N1xJ2VU wh<*?n_u+8(Tg-R>3bhIatY+87R=aSpR(AggKSR1MYzCAz%M3#YnDsmV2Jk$VH2?qr literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/kafka/kerberos_auth.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/kafka/kerberos_auth.doctree new file mode 100644 index 0000000000000000000000000000000000000000..30a7012fba738792ea5514af9e983d46f879098b GIT binary patch literal 3718 zcmc&%-ESmE5%<@wy=!~#Z0AT&VzCK2xIB2qM-l-R0-}=;$Yc+l2)u9__005ccXnoa z=#M*Ffj2F42m3!_Uu`T)C%s(fCVc>RL#hFd^pZ%P}v>j9y)hnbx(siep~RBE>i@ausnArzD@z z_~DN(FWP;04LdK=!qDLue^;8w8P!V@qN2KpBqh0K6oCXow2CjI#~($X#9@2PtDN3M zOqa#bvHj-`Aq*s)QbdFZyQtyx@tVV(ZZG(~s`vN*eXUL2l~hA|=@zSqM72q|lC?*|Vhu>aKTezb3dAICElB!rnzt>>M}tJv{H@ z`97Y92&xaygRejCeElzx;0(K9_t;~0c;`81q&v#%4Oqltc6q%6{argq7K~xLCIy=M z4J=n7@(%Ey%*k|4F6LCGR0?%LjAmC2s=hI>9s=XJvuAhg4L*MgKYHE|_DejG{VhOV z(pUirM8>+TIi`YmyK;C-=1 zn$v3^<@mad@>7`Vn6vkLdgIHAy-VjS7m5u+`C}8xgk4VVyo~nJ{Y`uh^=FMbdx}i# zt!{W^PuH{N!*XmwJNR@8<1X-^aCffhDgb`BOn7sw{Vu(!k)>o&NAe`SO#;kcIaabK z%D#Q43N9B46DXLJeha``qOhm-EiFl<3KFU<7&DVpHFWQ3p-JJcx9qUfp*fbur)t}d zG9x{ilGfJ59(9sHw~0MaRBK+%&<0LCs6$_vN>OQA0fhZO5L8~Zz=7=xqidtxgnd4- zgBh);Bw9#^MWDSVWsSw56vC4mP`+_?J9hT4aUsFEb0`ekrI7_z$692GFGv0>TzFg1(?g=Z+mR6vU)m_UsA0;mD#U+NhAFq(J1kFa|&~kQQ^z`B<7mDNkz4ea?l~Txrp^z3-;s(5{nRfPGllc`Zy9pxgT1`iG63_P!4Qz*7wQDG-pe#|5co25lVa&avLXP+-p_&4kP` z3DkfMj^V(}#P(5dric;g;CY7z*0&?DP9#f_-SGwQI5%aTEbxe|1_1{YWkm{a@LrGv zr2DI1ifTnoE|Qzlqv<|qClpOqcLs=ntuMIZ4PNghIaeetd?2lt`yEffxg!LF5vK~I zgVEzwEt&G7X{O)tZ#YCz8Hh(*v&=9Drt`X^HF6xQ+wJFCJcgjC_vd zG}|!mB}?-^=*)w;#N!o3G2&ED!x^Xsv7eZ5*g%sM6vBk%t`F=v09hsrW-jVqqH2$j zH}<}|tu(nw7@x5M55T)u2#@R}BLZZ&Xx>+WL$Vi*o^NbPrgNtv-hAf8^ zV)xwCIKt1cp%1k+;0@3tGG0xAfwm}|`YM4EyP*~aH=IQpCi<~s?f{uGr8($(3lA8j z8fQGnpfyiuNqCWv9OG{os9xKXwmSl=#k47CZ|p9uUO03wd(e~;a2H6#;K{)aoiy`7 z7`09|hi6~P*%>=j_H^x2GpW4-Gd8BuGv!a2F24I&$j^Pgtvq+^2~l?6e{5ur(2ORm zVBFY&m+IRZ$@g(&X5H7R_YZeFHsbSOUntc>vsU4X0VAEFn1^p1^B@@OM4>X@l6a}sU zF`&EbKWXJ(Bf78LGf)LHCrF1WS)sBu2QI42su-i*NZl&%t&=WVBkL_jqyfy4nItv) z&#!;7Km9c>KziJb8&r%xSRY!#nE1h%t1(*Nc)_ncg0p^NVvoNVZf|X=ZyAr#{=YbY z4pe*iw>l$rqfj^9*k0S^w(XvW2FUuG$*-RJ&eQw=L@{iK^PV@m+t{;*iv$IzAMz?I XOpY=qX8uRVF!wE?t)6~fGimg1m_eqD literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/kafka/plaintext_protocol.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/kafka/plaintext_protocol.doctree new file mode 100644 index 0000000000000000000000000000000000000000..87187b446a2889bf4f682670e25d2da5ad9d958c GIT binary patch literal 3763 zcmc&%TW=h<71nK6+Lg4nthi2*2-&7^>pW;yZX2`^idCezAr zp`$`(R6D!p2{|o{`__H%FZbNtvjat*X<4b1I=camlyQ+MTDT|f9^0^Ybd_ks4aCSI z&ghI6+%G!;tY^QEqVdBnp3kBAq#3mq$tS@qc9{0qQ&Gay8qkg!%4V4;YCI- zBW7|jbZqyDg9!sgXA~B}!q#dieRxgbmbVx5Ugi7u|KF0JoE;Kl6i+IHK>EOK^GqX1 zb~hD7YY?&S6(p}$ds$U3Hwj4>@Iv6(Cc-#&Y_}`Lo&E5C3XrqIloT1yNUe?C{MfM% zbY*u9nfqrQyKVa|tPcLDw24b4g`9un?2h0Cbr)N}Vd$kfg}FwiQk>V&daZRmSjRJG-+rBdp#7)NsOL3epWut^ zp8@xrPBbt<)WqcFB=}_F(_)g!qM)hI(n*%YE4%Q-q|?!dB}LTzI3$k6NQ2HS*eQ+W8AS0a5m~jNiCWg<#p=YRjhVZ2HP5HE8f5`V#Dq zYj*Y!j@engaL?XZ^JHtKV^i9KtK}MZ&gi(_5DB^plt;^O)LPh|)5{X>i@SQ0#mQA1 z(&4#dIeVb(oma9T{$QAbp-Si*5}`>b_Rzj%6e+YIp(;c5xuUu*3q2zZ5$4^R!U%Z2w=v( zZ242D(u2|Wq@#`)SKk>;{|*cqbbP@T+R+EGdummspeR}d;Kt`N=X$n6kI_SBH&d`jk7mu z&#y@71u4#+tevchU_aPVEp(|sd-dJx2WvO&O>Y2zry2YsV34zif)w)#?H=yVu;0S) zV2>3|rOGfdlz6&;?S&IKvD;}9kJYE-6MVy*hcmvg7;wLLSHZUYX3Skm+7kl;ufXw4A zGrRg1>(V3SrM;=I3PUbq#^+4n19*3Y^teCJIl#++I{Ap7677Xi!ZJcnS2#lovB#peYKse3d|nU1JLa9^N8#Ci?MY;Q*OD zp&8Qm3K}p<4c@q&f@=}eoNy773}bQ_yk6L&rfmYNVpbQlmv);LPaT?>-K$Fpau-O% zfXa~@Ca&j*Fp?du581wyvtzce?X7D^^+fj^nXxv#9&7)~tULEJlb;WLTY2Kx1ETG& zKdfo@(2S-`Fkab#r<$u0&iC_I{(^~@6+7|kDVi*2k1Kn)8d=Y04^s|&V8HNbswqB! zTfW+;RZ7i=VGc;h6Dc~d1lG6Kh#mLfn#4i-YPfXkntT*uWgIf=_PVU*1pN@@C6$#g z9%w!o%>=>#8_->LA2jrDVcj?GF;WGyDUuG;v_@sCAMC2kDj6f;NMC8>TTAU)BkOgu z(*WYgT#=If=jq?=N1r1A%HwWaqhkF2+6>c;lkbnY9;5$_yClmZXzZsbcK@s4=EjD8 z!z_&%;Hy3GfVGEzt8-I38MPyh?KQ1#)9!eDAY;Fne)iC}pZeD#Dq=Iq_q^EcTAtnS fycF=h&x=%48ET!J`(G%-%=d++g!&n-N}`_t^t8JI literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/kafka/prerequisites.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/kafka/prerequisites.doctree new file mode 100644 index 0000000000000000000000000000000000000000..cbb10830e1e51aace83c00aaaa7a8bbf889dab8f GIT binary patch literal 25211 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 CydJOs literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/kafka/protocol.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/kafka/protocol.doctree new file mode 100644 index 0000000000000000000000000000000000000000..fb58724c726641858e517589826a7f24a6c2e071 GIT binary patch literal 3671 zcmc&%TaO$^72ey-?#%9vz22A*B`bzRhRwrvuOSg=g#yS@0$LbHRxU4Ct?KTYsmgSB zHFdFfrC0)_NVZgODR|@;@FV%Ex_fRhk>CYOBX^xTb*|sJ>|c6+|HEi2{IjE6WJdEs zMU}{@a`wOzN?K|6t$Y8k?u9$I14*7MVPr;~-2z9(c#%t5xu@=&ZP_=KNtNarVnmhX zbjmC4KNkX6&;BEd#$PK_*Fx%q2{Bh+k9kRE^y+HNw64`v9P@G(DaL7$tB8v@CHaiT z4}Nxe(Z0iHczKZ)h7QO0ccqD(QN0u)DyoY}Qj%*%;YbiftN1c{`19z)I6RMemD8Ju z>9QC)w*S<@g@L3~3XkAn=QWHzUNgAk?FGA6`Tq9*_aqo+heT`1Q=^ecpSWF~DQ!q;!IQ(&3r}1d77-8g5bEbCqf2Xuzz1Q>?;niep@L$6ne2%C8KNa*7(!b zt*%V=KBG6jhuF>gZ(Qgqu>Q-&`U$(7yf*4>c74b{2m6zTojr#0d#ex5*~CNrp`#rz z&fBPsJLkR|M!K5OPnWT5#M`&%O^qldi^h;A>1`5l`@*r3JyQ0~*RmjGV3>fxr1X0b z+ajMmwr^-jDpiosK4I`oQq|ONPYX>7cfDnY9S_ZMG(IHTc9a?Et0QS;P3%!e3H39v z2a0OVs~P(GiAQN@^iwG+O)EgF{{=xSRf{~^zA(Br+D+K!6FZpEib|q|bU1`M)}*X) zIFv&8Y6U=VoZXI{J#0)!pmz?2VS6;P!08xxPkevzw=ks#o8_&u_ofh22~AfnU!vBq9Q#0-CohI@tQhGs(vu7JO8zR_0?WhsDG@yI> z&icj1OMBNl0N^Qx_!Mx++2ev#GlRK|aOXJhV9MK5Ni!jHjP@F^ff^3XOl%*mWC|Y< z4xV>tV0}9xMvG)gvOB(w9p|R3lLa0T)j;5YqO3^a1>OmifOMbwC8<`_dOETRO>UYc!SqFNzN5X3-3s4<$hNusJSCJgPo-cMF*kBEn70>MKc|L;J=_m zQ5g!8R$f|;d>>*RLY|uA^|n3n#Rk|^GoY~GCD-nccYkU}HLaKJ!#i{2DJg;NzAo$! zyIBRYit1aC2q!j{n8qUozDMUuv(4J+Cz#b&NDK|2LQBGngyh&~ z!-n+Ip0v{sSS_YaLwjX+Y4yxuO4);^m7sQkL~N2AwV{({;D_DmWOEz#t(={*LuK!+ zT{WxM3slC&nt7_cmg(Z&FE@UX^JC?yV~>ck`~I?_onshHSi!il15eerHG=OKrSc^! zUKw`gzbBZUoINr2cr~-0&mLzS_`r7H(bQ0U0e5_jQ>&DQ4_g_2Mh-};FC>v~&# zU+dHYKlYQS`HK$Cu-&$MUhHlo&mJyf6QF*`tE@0N+MJmAzaoaY9|>*u K^m~~}qkjYQ#FPa9 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/kafka/read.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/kafka/read.doctree new file mode 100644 index 0000000000000000000000000000000000000000..56b6c40fd0928b9878709dd153e7801d1c145c0f GIT binary patch literal 21896 zcmeHP|BoHlRkyv~Z_oDndlTxooosOPHulciP13Zh*u-_3HkhnK{3RB3GH>2{^LA!- z-ptEBC=FOdR?>+aN&pr3tbI;sQPyXudt2_9AY_1tJlGyPg-4B{p)MYbN z(6;=z`$~7=7rM8*RW=)%x1t~k8&;R?07b)gyk=AdC$=2-c#PG^cH@TFc9t9mkz$d1=XMuj-NQSY9*IonWbM zHdn2s6CXUinAXEjUURYTC00elf6K8Mu3B;4MBrPor-zo=jBN`8;SE{-(rNw7gZjfu zy!w*kH?4Kuj@w?PYfnGd#Smsgt7TzC7@-_BnBG@lSkg{%zE!%<{O^y1Gsh}s9EVOl ziJ?eR+=SDNpd>ck@XRPeA%0K#W?TH4X(pZgp>8(TAO%h{VR~`bKFlVP(CM;=x22#i zt29i%=`>Bj4cqxsU3(!)*mP{JQny|ED4UY7+WTl2#YQI#ykPZQm+kf(-|Al6#ilwY z)BCUw(3hC$iKp|SVl-@W*EAO4-dpr*h%=k*cE>zGLBCibGe-#%mS>DkX=Q+7pk zv$k$8*{3h=0{sM=HL@9FPb77qX-dFSg~)#3-&iwSYvv-Mb6J3??|3FL9;XQr=2bX{ z=h5pV^=dzce>%GWNVlz}2zocerMTT$;^ST-=r1(_-?thx6PKEGLwVwjE)gF(q~7H` zPe+1OVcMRe*+DaW60_SYOpQa&ptj?$YOOG6YZqx?a(?W-Vy%<$V7+Kw?sAiYAv`OF zuwtKH=_&G5wIuJWAb&=X+m|4L-JIiDK*L-O&5o_`%wB!mj7<&5)9RK+LsU4_;y}BM zT5DPo5%0@E-CeFd{!to>6>5(P%rxsd#6?xDQ28av&einFld)!cQJ|&vA<~wiujN`4 zhXDKPMonu4q2~CF&>}->dKxBy$+^fviID)rCvpfALQtN< z03E>j20PpXR*EbOO1C4iGGmR}UxJ?0)s*9N8=5a?_HRj5Nt`Ope|3PW?9Z}^D26qf z#$TAP)>b{ehGBepBt6-mFX-U+R2@*$MICJBI=Ey1I-AKjCva?);roV6ygqZt>btN| zH>--OxD|rBepbp~+Q@6NfE~Lmyd0~k$tj_=EHh3*th%WN2GZnA{$+oqlqU+A$ZrX9 zj;jhck$dWi=UK7gIWY4I5;DU+OK%L!>bCNloqY2*m{zMb0xxO%QLUy`ok;V8SlT-b zt-c|Q{(=~iEr)M^N30n)*lub>LD*aBVC)T9f$dQ`lJc2OyH=`ySFK*OC1qKV(Rho` zse1-;o(sCb+h}Omi%$M-5aJ0sf15&wh`&t(09S8jBVQ0=zGcJn981XV+|C_4`pDTm zK<~JBAeL4|)U&l(+L;veB!AAu?A}$XJyg=Xzj1`Ug~8F}z0W@5?gewBiX8MkM1JDU zFTMQFzyCRa+rHJV)lMmhPECtrMX7U^VcLB}5%+Z3f+Dkzlw_tzd^+t&%6kIcDm5P~ z)f_Hu>WfSJu712{sCLVIjV9

M~(%V2IKwn5)R(Dy!|rGiS=C*SNub`UR03tkf< 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/kafka/scram_auth.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/kafka/scram_auth.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ed5351f2b7fda371ffa74a04159e391e401a1377 GIT binary patch literal 3691 zcmc&%-)|&G5%#ZLd)M~bKF1_cVzC4rTpm2*0|Lm3fas(sk;NW52)u9_dS-gJJ3BKy z^p87R%1KG2NOw}drQwmkm9KkdcGsID61;HI>ZYr!tE;NNud4iI?;n2~ZG?Yzl#9%0 zUZ|)NIaSW?c|l1l?Y?#If8}1d2X-LIb0v(-sIwc;$QUnjNh|l%JzyL5O=VK0xrP~0 zB{`k)iu=!n0oJqsjH2-u%G9-xI$=W0)fZ!4k{P|a8Z)hHbrr|FoJERpTI4F?B2Gy@ zqw)QpU0$^3@EJ~Cq=liwG5%d?B4<=DO^Ax>B9fHkno$H24ACmSj2`|x`XCO+V_xO- zCStlQhK}t%bqHY~>69WOMA$_Qrw{Kq-0Aj$->Z6G|NkQi&eocFBd7v=Ey?O*(QZ{?9^@>$({Y+e|jKihZ(7Io|DEKyZKwk z-dD!%YBKZJI(BCJ9jp#Mlu9SH6or`m#@U^MSJYi^*?vv%EO6$;q=dbLpx8NH?1%XM z2)`fWcOOCZ;d$`&SDml_B@&!r7wm{VWc#nZ=ah6;dA$LPSj;Z3x1hgm2g!mlY}=$j zQ@?@bDn!nJ|71?4b8?|FNy-aiG`ngb^&g|^Au66bd%S)yc>57N=y?y=@9>N4r+{}! zV+9Nl8SAo+Lk?n3gE$jaMKjOJI8T$6RWKa~pAzp=gm-Wuie1$lSP6smTfX!__u>v$ zj;?)h<3IY~K7yT&EjxdrH@=kE?VXILXeCoV8pkHI1539%4#9@q*JPHiKJ*`!>1}SaZ_}F^X-O9KB2UuWB*6KFV0tJ)O?*Vd42=>^%p(UwQK|)mpGiH*ihSohTG%4Kmh8=b~G{@5T3~ky` zW~3)g(%PEXgH94?HL-h&YR#(|%KeE4Z>axMDJo4X;I02>f(ok^4{!Uz=-OyEVV_Ox zU`8t{i5Ajf5$LT+S!1y;h43T>YHys~jGf(YTu5;5916pBX=H)bp%z)<%Z}fLD?Ruu zZ=Jn2g_%ldy0ZBa#Ta?7OqtwFg)H4mJ5s21HLzUzZlz%Gokl_n8YIbcu36HRKHS$H zXlo(TB5CMH1${=5&MiA+D11q~?Aa4~!;wWzv{6fGFQ=)Q;kwatA}eGO8t6DBBm+E0 zMtw-$KVBhc37U$?q2=tr=;_6;FBHf3yLUGeDy5JKL%}Sl#0z*=GY!@>nP0Ke3s#&x zU)xzT!Tx1OjnSn8-O+dNZmgZO$36f6Pch`DKtRqO7NnXPv}>e0$9fCR-=0dE37Mng z*MJSK;lRwq_EBA?h!N@Fd4~qpwo;c2v`P zSw4I)2VRmA`0jaO-|AWz#44(9H4{}N>^7~QIW#T1*OU@)7f8f_$-xbsG;=^0 zsZQ31WM9hJDce`}-kq&xB6|U5tWBS%%HJ|w?E9I=&v(A9Jaz05QFhmFHnIn3MiW*r zZtTEI^=*yh`}wJS$%bj6Ga^S+8dgGY))UVDM;aB%Z*XUK7-+q|w7L z1|*cJC>CG|s&A|b7uth676;v};n2N1_ECtXc3{@ox~+Bx{Sf6P6UG-0G@ncq1)c#h zpu6loYUSS|x^LVQPzAFkNQY@yp|Uj>E~?C`7$en&!Y0nCw^BsKf) z?qBUMKfwb?kGpY$it&3Zvq~5T-y3r^M&BDR$dyO%)K5w5;pfB6jSclZGcbDp=X=nB zY7hUd&PCk>)QvW_*S5G#yW?>IrhYp4__1$0&0l$_gzW_0^Jcdjdv<@3oq+RwUS);J WQRBqS|79`EeLrYRr=QVG8vPqTIhxl1 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/kafka/slots.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/kafka/slots.doctree new file mode 100644 index 0000000000000000000000000000000000000000..d9a2c42c296df25b1329404779117104ad1a04a7 GIT binary patch literal 3647 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 Ws+^qr-x|ZrcZ8;R`dO_?qW=PPwWC)6 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/kafka/troubleshooting.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/kafka/troubleshooting.doctree new file mode 100644 index 0000000000000000000000000000000000000000..26632d591d41cbd38abaa75ad6b6c0e688b7840f GIT binary patch literal 5030 zcmc&&+iv8>5tUXNjYiki%CeK#LTCr;$PdGj5<3YLBew-|f#O-mx4i6v>LGikTf*B-Wl|L1aiUZTh*h7`%na>Ne0102nFvxgZ z+%FEqf_YBm1Er;gnk4oDK9sSf+ld0vGJkVj=Y5r@X_9GAB*|F4@5qRa`0=qLv`*D= zuP39CtAylXpj?^s1}qry-tF%kcB}7jYrpIc!kn);_&e4t8*x2Vk;GhwZpK-l1&1S6 z5s!O^?$LMM*LwEzo{R&2?g|}+YlUdvFW^Ea<3kRQ;9>1GjNY6vxRz~MyPI-<{{MSo zjWcUZ>r4)Ejf13vTatmoA(^%xGNrJH`&P`N@@_TA)9J&2`C~-E$}F)^7h=~e<(VwZ zYyZ^&6=uz6aUcU$T4NS}T!>dxZrYlSXxl>UnUxw=8?R?dduf)0$>>LgSq^2)i<28> zC1p4k7_*ZPn0OveiG7^dH*vj)>sz>P!>JWmZf*U3ZR>xCgd!JRu`Q0o`jz|avU)3$ z>j+o$#No*e$S;|WH^~^Wln;QWl@gXsh|Z}%C8z;i4{s8DgiB|e>jsndne?q^UkcZbq+1R!_TR}n5T$6E^Ot2ProgF z>Wjnvl_ygnfR5!|h`&{ei@ONPvbB3TPV|grFaLtaJY%6V%4NWhonr*}_;k+iQ@1v? z0|c^B#!}Jrd|Y~d&uq_WE2gF=P1DV`N`;KiT^0mYrQJw5@*^I3=dhrm+&p`=Nwq~Q zGPdb4tyJRowvNG?IX4>C{Gn;hY$_d?Wov?2QW{8W{eR2tPuEFRnWVn}~o} zVcZ6?z5>n)TY`fmVlpno2d0hv^+k$?vqYAdb^Pb&X()ZEi%a|ZAFL{V|N8XQL1EO1pM(|y2XJBUO7VtUA2*L18QKn|Ce$}^)~?i- zz`AFauvtUJKbtCM5i<{rf56F)ZEQPJTc?>YW6bq2rX>ET+}NHP>amC?QF{H&kTb;{ zUvPiyXu%LLo@qIhKH7{kl&Gr?I)+1`{23&>-+R?TTL-lXs*Jv8{x>C4um1Jb<5MGN zv-U0@Ov7MaM5Bbk-RkVS_UyY#h26=yv%2Ng!gD}@@HFr<&Fak>e-$1P(CO9(qE95@ z2&rDR)Pt(gIP;a{X$^=sJ0opYb)m8Zo3A5l(KtNHUrVq8Z*bw+{{N^DUx+)(Ja?rC zdL9J&5NH})n^|ufRGsab;`?@+h5gn5`Hu|^38{dkXolB!8=nUy5hX|9XDc(-pl zBinFvk`;Kcd`Pn>#lw0A4gl7J4u4*l#UAQhX@ZA+#}HVn9`h{3(1r$BgM5VTwy0!N!-HPVBCK?9 z96l$P$UYe(W}Zz~NTDUAEvuz4 z`*UyRRGc}B3kzne)H}7I{`!U47jqw*o8$wqrvyS2!l%M)hAbZC7;zBs0M9otFqz$q z`$-mHN}}eEZ6sjRH!Dc}A>2enWKaV_te7p9>%^NbnHxj}H-kJ%y$K`{Si2yB#w=zb zDcrUz7VLET)1_4LJV?CrhyZmHnU^T;HNUhdLBc+eN|un__5!IGC@ZnW(#m$7DoDEx zID_#ows{Aoo0XVnt1*on&(a+>QBaMkVr+moONzz{)Yvs=3-Z@D|2h+RXN2zLT1cO?7NC~8BX5QoJcb>ChV@p|**LFP@0cFU6;qx!9*Mw2xWiGhCmDk8-owJY*6# zn;@!XC&HHH3gvPwSNhjnApqJ{fFc?s;RH8!@tL{BiF9X1W2fHLespU_Kgx2Z3nbaz zysqjEx+5x$pX5|W(2Bq}L!1aQ7QLo@r&|9I-krIql_!hpaq$+i2LlrF5I-s?6uWZc zN#SWOgN1IX3xyP{S0@d#X2esfS2AQU-5>rECy-Icb4qA9FI_nazvM_|X&>TPF^!t$ zi=(p-V6eL87le}DAD#ULa@R3?)2f=?qFFBIljUFae}0!5Q27HH3S#x+MGHF59+&#e f`XnM2?&~u4!#qH#OGcE&YXP-|s)7=&%m?n5k8+wd literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/kafka/write.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/kafka/write.doctree new file mode 100644 index 0000000000000000000000000000000000000000..6c75d9337261a7f3682726fa650c6330d44d7aa7 GIT binary patch literal 12554 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 w`?R&G{|N!EwkQyR-LLDsSV8+lILYXATM literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/mongodb/connection.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/mongodb/connection.doctree new file mode 100644 index 0000000000000000000000000000000000000000..7732e0744adb9216685ab432766fea826c2d0bf7 GIT binary patch literal 3703 zcmc&%TW{RP71nK6+Lg4nq&RMo2;C-dt2~G+Hwh95L3(jfv=Fi`j3#-BK@7=R&O}2F zJ2SK;AO@NM0c>F2GVNo3L4Rbw8IrrJwHly(sey$#hlgj*<@?S#`djbsVzd_i*-<9b z+Hj$xQf5>;yW<4~EsguZz4xtq;ZE#8k>^_0Dy7b@K_g{cWQvyVsXJk7_P(wYZMcCM zS;iTi@sj(;xdGO*|A?aTH@dDWsZ7kIywu-}c|me|em-W#RQh}};YA*4#;M43#N{L* zSxzSpesy-*euu~K)QCt?k{L!3NH9dp$yxO9{piC<_o`Z|wT7 z9eYpLcH5BLU+dUC+wWkt_leRbu9Orq|J2zn!At5cHf+BlC<~l9s1w58LQw1&75f=} zKgaJ~{2m~vK0FV;e$@H;Un0TP?3CSM583Xm_Z*S#Dz7(S5u32Hiw)>++Ce;L4BM;| zpsC-$av37`f&aLWWiGSiG?isZQ%{le22}qst{&pznX`wle;PdgExhP?FW7JJi|hp; zUeJjK4v3nVqMC#}Ogt4Pi)$xY5-*)v<4Xz=s^LN`+q&^|DHzsoS8;@;$}grt`fEod%J7*+epBXVY6N(N^4-;d7`zY1G+cBw}m%gA+FO z@PFuN2bPn07RQU&Z%-{scPeCZ)6(#-s9iGl^%Rn*C@*jW=d)3 z(#x0V$jEzH7xC3hs=~drBaMbv0nCLTSsDi4Y9v(9Ac>!I!{V<0;lB2Rwjm-diiV*i z=rf9RZrCA1_lw(O&mPb#jw~8tYQ2#5GMdymt{Xikszer{fw5CSGC(;}>O=DW@e(%scF3YWq=6S`iyJd;PpVpT$&n&ZW~J@8}$Y|0!c6ujWZeRlVkc2v=7(LQ`IdtQw58z#$0svLB>=QV=^(2NZ&P#%MF>_C3{;OqnBbEIbJig_$|K~Jy{oTFL#|@R zb0+Wuc((*bg0p+NAXxqWT7%LWi8>1B<`@f^k~X!+DI@R)g>T4)EQb{0>&v>r6@Fn2 zbEoYAuYewz@^S_Yv`yjAcL}uE6}7OX;V#lJ(eD_S4v;Alnt{I8@PJWjaL2tAT9+{` z2p2KQu7_krry;N^W=%tTWjAU0%wbyDou-w5yFengOb%|CxLF3mUUjg#Df?E= zj@YiYhc{j|YuO7hV`T+B(*Bm&{Jmd%{F3L#%2UT45pB2q%SLvBVKile@!Af&)Ld6c zzF(q>mrT5>*|C3~Vv2J1q_+FZmGyddKjpv&78Q@CM&b$F=`}$uOBy|FWI#fZNHGUX zP<>-XIM*KBusG;$4Idreu#ZNpj03aot=ekeU>>5qq_XzS1H&hkLZA$Y0n=ssQ7iw! zXY5}W>-u~51bo4k3Fcuh)@X0dt@Bp1EXHy-($^ZK>!kDf$a?d=XaI90SEOREzx~R7 z{6|zldGL)JXpG-^eY<;S%=H-KZ#?H%7P)3iVh_I_uCJ}>AK8bo0(`v#9jNy3-|}A6 zZA0CLV|(qSTen*t9w6+Wrhj literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/mongodb/index.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/mongodb/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..7874aa86a0d344718f0c105a1d0b034fb08df14b GIT binary patch literal 4983 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/mongodb/pipeline.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/mongodb/pipeline.doctree new file mode 100644 index 0000000000000000000000000000000000000000..0345725eaed25959daaa2436b9d7854ff8405f77 GIT binary patch literal 7528 zcmdT}>u()L6?bf3Kd$3foX}Qj$l^4OBXaj9Kt*LW1sXstAx;Q=@WIh~@9x|?Yws&N zvx%=-L#c=qO9KUU;R_$(BN9TW-w;Cm%wJN8zo1gZ@60~#-kaEILJ-2zUGL1!oH>u* zIcMg~`;Fh;IyI*M)Pfs1sq}ndhLOuere>%kV4=)D%Fh2h+soF}Y+~OPQJOd`Q)3w6 zaL;!W7G}4yH9n@E5ot$APl8MoS}yB)p+~P30p3u*Gfm??k;ZY9NQ+0&j(E@T0=vi7 z*9|UZEY{m?FX)+qd(3x*=|%01?e(WkoOb$pyA(OtD zFx!=!K@d%dh3$*xrLUVWwe|G27rJcMkwH2{;ePidy(5|iQE2qj-~?z zYCs^*&D30Nj^^<@Shqpz@^9m3sy6`YfVBlIZzpXT#BD8ko50_8qA+9*`C{AcShZF` zwHZFTUu-)HTfpnr4XY|9zr3zm+1%udEd$9-QX8Daz1@evc&dle5&IIuE;ml(kL@E z;!d6T!7wA++r&%SVFzN72EBzrH*=c&It(_cS)K)!?OtNXyw3C=*Q3t%dZj90!+0eJ z@4#N>koC;MdS-3FsBo`t8Mg07cZCtd^T`@cDkMx)n&LteVZv}DCX6tWMiJ=@FLV+{ z(cktBArts+&tt;O&88Mb%)M~ev|YEz7Dr>z)6SSh?SH^322=`~w}q zH`QFJ0VuF$*d*Prj&Nto@c*|@(RL~aT<+)y)U=+KUL zN?}FZQLp{y0MfzcrwDX}-zng^4-9XrYfl$e1OM*V4RyG8-K}w@y(|w1q#z)%4#L(A zpV@*L(%xZ!wu}thaiTPo#yYgMzEwCd*!T(+WdVYwx+Crby7a9&$Yy4MgLP+F|X*^qy@j*QQ1GfCz7s50D z{SZq3Sw|_69!BZ@0Hyc&AJho@4r7-n|G7ZKEC!c|_)%$r zGJU;WcBsXh%!vX-IqXf48$}*dHXrOC!1G@RD}6LzQ1JgWAFiEe0q{J8nKL)+zJctG zmx<0F^3md(G5B|ghOK*fiVwYh=U8C#y$oiDdU^?4? zzJ83Px?>2>W!0|3?V9aU3!1 zK!+ZPp_v!J-|)5xw%6iZFtBkp0>XtaBAk=J>K$f)+isKuHcly|3nF-Ty4{Z+7999@K9Lhyp=K}){~?<9ozj@l_**=YLy|pl_v?iJQnMr!#SlKT14uh z8(mo*Xr~wU4hxlwtqv)xgyjv z4;_UP#2)y>fV!L|*X6oZ8HZn2$8l&3g|9bORL|)pOytF=hKz8k%&v~9W;uqrXr=^~ zBWl4(6DsvsMM-Ew9W8tG7I{O>2qqTTL8iE96^rlM5F zsmwO`TR0x|ScrOoj1oF4>7tahgBXpuB#Nl?go4X%rjE3s-JAsr>+WDcvuLJ?kJh}5 z9E*x&^cAc$z;l8F6J&kLb53#HJAfQ*A`D!Z+hTf)Qp^TQu4mLySPaqL!nmcRnU zsv|SCm}|a9rJ<>0>eR@W5fa;<0FG)Qr#s-Goc;Xphmpx@iDUrw1c!=z$dsvh-wt~z z>der%i}pCG)@p@9TH>NqP1Or5gAxbyFoolL7h*yqo?8NfPpJhvm60{*QsY#an|IS7 zwkk+MvAP4NOqPV$w}H%}?$KZ;>kqnO!BRJ}b^`*`5b!Y9VIvAc&AKDE`DiJ9sD8P|udNS9A+A?n@qpW|Z{cQ734m zW&k5C$pV;1!W=W`Z=JpP4d`a5`C1rb}jDpxQ`$3JL#j4nW>{9uyI{+JB3#Z zC~8Hw^QPrTj$Nqsl*3Wkb71(~-=Jl!Ld@PtV=O_JEV^`Bgn@maCvv>73k(!-;TQ!9 zMA?yH`ep%Z>8z61#dk75Cg?C19^3~9oH2G=cj9i8wEK@C|9@g(Q6!usnLZL(!bC@HwH54SmY5687L3BiAM? zf>!BP->Fzn7Za3OP`|%o3pGt2^S-q_C2ef~8lhnOm-L9};nSl>50@VI>2VK_RL#<4 zUBaPD4?X?9p1rSU?(13mdd7arPthI12EVCE;~&xGpK7A~Gfh!%7*aJ~#|&|+=24By z6B{JRdnh!N<8pP?dkD|E7Z?jVkzc`3cOD< zALsI*?5*-Hin98`U~q~r+MDXq>nOA(lsHk6qnkTpwcI12Y`bdU)J(-_Xf5B8xn{AK z#xCx{_}G{@*O07-@i`TE5G7IG%c>58lbs- kE~lsFDm`dlnM0-APhG^xs7KdoO_y>?5q_zhk#@{~1F*9QX#fBK literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/mongodb/prerequisites.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/mongodb/prerequisites.doctree new file mode 100644 index 0000000000000000000000000000000000000000..e101947254003142f0d7c10dc175cd5cbc1e05e8 GIT binary patch literal 15809 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 F{14=5D!~8% literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/mongodb/read.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/mongodb/read.doctree new file mode 100644 index 0000000000000000000000000000000000000000..522cc2c2b5e50449856ccd46c64c733f2a26445b GIT binary patch literal 19848 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 OXbMgJ4AYsNB>w@QWSZpw literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/mssql/execute.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/mssql/execute.doctree new file mode 100644 index 0000000000000000000000000000000000000000..3c191db76b619cf3841f75b6e05aca62f8b65f88 GIT binary patch literal 21078 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 E3$m}f6aWAK literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/mssql/index.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/mssql/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..c7f9620086d1d710f2f15b9a8087658747ad2a68 GIT binary patch literal 4993 zcmc&&-EUk+6}MyW+PnUWH`6K$5@H=zw-Cb{z8u|d1R^EH=nKS3}ch1@S zjX&Jon)82lJ<7tu$XIvMEaKYPWg;XzHSUSK`oP_DJGQ0RkG^>rjovn1&D_IW2dwIp)T?~Qzj5ucBntxmI)j3y}fN=Os@C3 zU6~9!T1Xy8x+Al0pG8C7z4G;IyVW^-^(S}xalzZ$_}eo~4Y`?!$Wm_Nj^Zpbg2NCm zh^O6ao$KG|yw&x`yE2XVs3S}gx1CtJ?O;Mn@d1ZLu&}loO0S(#xaMtmy{Gv;`u|(v zm9uSTjFSDrAdnQe1sQ1s$u5O4(;7s4Z=@_Kzb!{aK6&V~@DN_`JPRx~PHfqQLP=-e z`dk5WwjHuGk`XJlvGd<^;;Js}l3_!-)`^R@S*O+Nca%0kuCh2Ae%IN>Sf)J zAuLd4v*$hrM|I18ph1eAv;<{LQ?mb(qzKZx- zq9eNE+WtB4FW6Qvt{AaU^pQ=?GA*Z5q>cO!60PsVdu1NI(3A#DkrCqLxxHRLz+DZx zOMD-nj(r=6obawjw3+IfB=34_yOi+VFiTS&QvG$KelYdq$MzaNuCvH^o(M5L+3)KqYjIr3bK|=78=E;H7$*JVZw;(_A67ad7-0FI>VtDL2TC#IGq%36h@T(?q=FY*6h+6@uL(#qyjh$ENF(&JRz=8F?W@*(sAj`r;96aPIo-KiD$I9xu15tZrr&Pt0Th0uD=H_njAM;x7%c)9o$71W=_U&LR< z-?(_NKQBJA?VIouqd;mWesdxoo`^>$;4s$aMH3L;p2x=*uwlAD_!mJ}&qj(WCBn9lo!6 zMGdGP$tMr!9(4Clac2duB?_Se>X9IFkL?%vC@QL_xH zrrNWsHO05no?X`5P#7@gZBo`L!MH? zW(u&@@@?I)B*(*wLUf@jP;2DueAn5PQiTBb9)h7=Jr*j)(;BcMpmBrVLX`&L&atx> z1`v~GhEKhGj3Equ2W|zUL8cOfVqF7}b9AQ$QS;)yG z(od%FS%6_Oizwx7t1yGzZ|`b}@2{PH8E~aC6$lJUF+dY(0NX<2>_+MNDJcyv#o0?U zZ)QXs%+Jrw+4WNHM2GtHYo}k#9Jc4l2IQU=a8V4KoL!4qIxH}$!{ZU2&!M&0Eycr3 zMHu#TWDc&O4%xF!K;i&q!Xq-O6GAlYIx9>TOi1<|O*m^&k>tTRCE-|4kjO@rvX}&J zc#1CwivEO@YhFZIFiI$)&ZF`&&4cNaK1)!rcc~Zql-&p-sTshChN4o-rMgs5cB?Q3 zTTJTf4no%|wxDjs_GS8pOEg!6+K3HFBXn3n^Bg~-LvK>OA#>6p5;2yEH13`AU$yHw z&nE~%#;g*h&!9V%hW$c4y@ISe`LPjM+R4-q6?Ks~y#nA23{MaqG#9%J`tFr$--JKc z)gYV^Pn3!HH)`=HUPdW<$W=#jGw^o+5W2J!;j)%teN+*aD7U|CFCizBU|gJI2n=*i z8FFIJ>*Lh0Q6S_{#P}e87vqdle^n<83*VgrRjuKuE&p6T25}a$a>}unLcyR90$R!U;lc$13ZU!^o8QfF7b}aAHj6`tl))erp@NU8 zIdN`q$Hfp_hXGHRi~~%Ugu7nr&1y(SuEv8B(@yLHPwzTRF?P8`6O=D95xb>CZJ3~3 z0sJ<#IlCVdnu{&5qV0uKugYcY9x7vI@!ZmM%V7MT)*V{ysI%O5;s(=piC$K;r=q8A ziwI>~2HVreT3n*-sfS_v6LG7sYtsQGQFbkqNDInrh%YHrw>4jB%QU6r!}^5;Ncvek z&JoZ0(hPCT-8v<)Rp06lT{tBl5UdQ1yccI>Rc|oG0#!mR3P>HaoiK}Icpo;PuPoiD z=ucqX!3J<~e_lUyw@@kADp7OTgEfGSF2=UAu8%e9s-}*+;CMX6&jdKfhL~{0)LXmr zc9F+2L^zlWWlrbpZANPKEliE@Fs}JC@+Q=6%Y8*sf^6b{e z?1eE<*G?qY9&oUqp6m|}o*{`3?ALyTyA{=E?Y_!!+fr|1F4!I;PgHva_MJeK{*2!S zNa4yAkHGp_u6V`|2M=)KIbLYgUVt>aGPc@7z)H1r0dj`)drLc_R#|lwieAz0{2M7# Bi?jd$ literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/mssql/prerequisites.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/mssql/prerequisites.doctree new file mode 100644 index 0000000000000000000000000000000000000000..755e052e3920304ec49740ce68f0d27b9bdd3939 GIT binary patch literal 18921 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 TlgFMiX_`tsI>m6~TIIh16jeYP literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/mssql/sql.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/mssql/sql.doctree new file mode 100644 index 0000000000000000000000000000000000000000..15216b311c699f14d162a520722c85f93022d948 GIT binary patch literal 11727 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/mysql/connection.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/mysql/connection.doctree new file mode 100644 index 0000000000000000000000000000000000000000..20a927311826a6fc9e7bbd141097aa05be2d8d39 GIT binary patch literal 3687 zcmc&%ZEqXL5!M?ik&q2k zMwV$#r@Z9hb76pW+&_|J_@%a0C6!5;lsEdzAuq^`UR@2DF_pd=jd(FjG~-m{I^l9O zA^D7s9(-_l(VW9)Jb5t@miCAEcV&p0QL{85OKL=-D9H_@2qYS!<>)eb_@m^*Q9M54 zWlnDsW(v{wZ1<^02t7ro6cHi9HflJ1c*o&Zw-fzd)%)B3Ka%L2>l0%XpIC#33=g-> zbB#xGyO|(bLx}HANs9VwFSpflF(KIvvJiE)i7=j>xNWPrcOU*w59D1xBW2EWQhVbz zf8p8t+PYmsX5m`TPF=T!)xpn{Hfg1#kh7nAwgXRs_!iXO8WJu(uHuJHv~8 z55Mo@_XGSMAgC@pkG}q-_4U6*g178~-D3~g{%h|!A?;N`ug4NLVwcxj(BF2wbio+5 zZ6`oew}#~^L{5SKw3zEJ#6>2{l4gM*S2d*mV^keP#dGhD)(=K+e+3UZ!2|Y7{1W#u z;9by>1_p>4nW7rS9E<`DM$1b_`6OLgMbj(W3EsiSD0X%2d-=((|vXL&zM4nFOX+-f0&kFWPySHAeg6D(6 z7z%bmzXiVy57=Y(hEb%{g2ZA9Ld+D^HJdv|8Y2AlhU>RFG{@40v~0RTW>w%y+SnSq zgH{sRG^-; z5m{|WQDL#Kqzv2zSZ}@C9C^22yO84E8z}UfrBwo}LnE^^RGY91S32-n&Aq!ng_%+s zy0ZBaH5hp>ZIRwir7HYOH_#|?6(C%MCZ%EU?OH+wjgs^^H!N){9`0)nG$jyeQ8cz8 zL7!2ibIbJ^>Rs9_JMNg?a%9mEWA#$n$?3$-aNXcJQ6;hn4K$hpk{+HTqamc=A1{%! z6um^|(DJTl&Gh1@7ngcVq3OI|=~+c$y(U0s(nw@}I#E5k8yhQ`+x&g69rc06A3MK6@w?&mM@QAEN0S6RSNklMsFG?cP!_zNCm8LeA z>1`3vbOhQ-P1Dtr5h7sgGp>1!*L!KsHJOMINaN*h%M)bfwOcgh1M6{UD9Q2*T14gOA9j6(zZcew@;mZPW6ZN2hXobtwUNfke!e9NaKz-Tvbwb-X?shf>Z? z*uHl6?`+kB*b6XYZRkAF;g;!QKa4tItP5@Bsb`OfcDrG-mYt&+jhSHFx?Yf)d4=SM zv8i~;#4F3r!uJ>*l(Q$+9j?A?&~t|w2R<;I1T@u>K;Txd4r*0W>tO~15{iiw3$R4h zH`as;?cN=Wz4q34=>8r1D8$NmFza;PR-PbP)HGav?Zm)%E={Ogdh ze_e0tuly761#=~she=qYyw$fZO3kVm^cHAvS=7yXfS7V}UK=EzKuihXnUGxyPN z@Cqt`Z&*WP`0n~d_wJDEA)4QC!LU5>^?bx0e%{~Q*wEiH|6&06d=ENM?cl%FiKv}} z+WE$Hnm)Jbb^x7^0-IF; literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/mysql/execute.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/mysql/execute.doctree new file mode 100644 index 0000000000000000000000000000000000000000..2622559cccbedc47801045449f52f96e4b63cbde GIT binary patch literal 21387 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/mysql/prerequisites.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/mysql/prerequisites.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ffedb15f2de61ff9c83b2dadaaa14a2cee9943ab GIT binary patch literal 14364 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/mysql/read.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/mysql/read.doctree new file mode 100644 index 0000000000000000000000000000000000000000..46287b7d7366d985ad8be5141e535166b6d6fd32 GIT binary patch literal 17971 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/mysql/sql.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/mysql/sql.doctree new file mode 100644 index 0000000000000000000000000000000000000000..adb582d40042f3273455cf61a141aa91b04bbe46 GIT binary patch literal 12035 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 S5nz{X@=__FQvx@xSN{cHvbgL3 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/mysql/types.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/mysql/types.doctree new file mode 100644 index 0000000000000000000000000000000000000000..6120ae9e90ced00f7a97e93cce8e17a39646d8a3 GIT binary patch literal 90824 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 DFgIzu literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/oracle/connection.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/oracle/connection.doctree new file mode 100644 index 0000000000000000000000000000000000000000..daeaf6b3ab237376501fd213a85897afd09964b8 GIT binary patch literal 3695 zcmc&%TW=f371oWENJ+G1#Yu}qD3idg@}Q-hHb@~9y}&@xRw!K@G>sPJr~K1_BYsE8B^o?vwv1LEa5>QWd-)jWurbSDt;O zt=lzZ9@Khv-}O6K9sELRlh#TKIsc`1JAzl#Uv9a6O^_BCb7V8Z-i1@_1R47ge(&S= zA$||wR3DZ{TR-h={hmnhmYuOX>>=BKqn=~ZsS11pmaqvszuW@Y04;6CJVCq}l>cK5ucz5*L%V_U!U_&q1z5FJ*Tn zv-4+q9r}rT1IyPwHW|3_m&T1LJDkBdJDi+$ z5nQ#cqpjovFIJTNX&Kr^iTfeFt`U)R(JhKJyGbLMUwBrsN7}vfMiwLy3{x=JjD81< z+j3x!-CIVHN(&NuD(YgcsBVh7XQUy*Uv9Wz$3qJoZ3xV!8|7Aol1W=xQ+Lo&Vo^=q zo~Fj|YK~@q6rdZs|4hls&OgaO%W?=~mi?KdW*(0c`jVSBVn;B;tYo`$9qZedCfHme)&?#&>kl7_BazC#I$aGMmtj_E2z>>TPw&xxuKMQ~u=l%Ncdj+_RUf_=O~%uH4*IJZqx`}8qhs` z@Ak#oOLsRo0N`nc_y{=U-Ju}W++tWG+y%~C82#>8(Ojwm^S%abpoRl8Q`blJ&)_4% z!HW(JtnWs|8ksIhZYwml!@`zzy1*l%8VMXwR230H;GIZ`NDrxBl4?zDA=B$Jpy@8k zPHLL2zKjq-wLasTH+a317F?4|1V>scce^@4%^koQEHYIrItV>%+0q#o&B%N^d_jq( zDi$WKg0uqp-p7uFJT=G5O?MQE4X~-^K%w9zH~x=zf8s_pt(Wa1ICBsv8G-GgF5C~g z0S2;8>Knskl}I&5Io%JO(ExC!hL%W=K|1z8KYMWgGsJVGX8D@t1(p@gtU`O7_b0zj7OqB0l#FV%HG$SZeO-&BTNr;N{;zz^Wv z5i;QZK$iqN-cy5lVkU42Xdy+E(dyx31*bwCyLcG4RHB^L6HO`#22fPA$ zWX`J@Fwi!Id!b99#jdf%^$fa5Q;A`@xblEZnb88}djks?r3O0g=is_ZX-T+9Nr5Fd zu1zo9Q9B8NRWWNC+AFtBt7jgw%I!6+1hoq!V!h<34U;xoKwPMf)>q`v%Goj7*Y4h} zt7ad2fy!9hJ&$$JGF#k-jVEk(VXQp$>=Ds!H(WNfQw*ai6O3Co2vl=ZBlux^Dqk}3 z%CeL2J;e;=?1^=UtC3UV6C(DX%T4{Y ze}a0!dWp)zLafo=8m)^~vr5KRH_|s6Mb}Xm^O5xyJJA5*$Xt<{y?*;w_v!DD1r@+I zZlE##;I+N(gE7}*OuzA>#EQf>D-wJ7#c*?DLx0B}j1Az6J@A0FhyPZWqHY!H797`W z2i>OI3Ftt*{(kz~$6@$1f9atgwp(~Fh}~}Fx&1|WBHH(Pl?z*-)5&@G>ta}hsn9mi Ju%Ov2`7d2Zo+SVP literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/oracle/execute.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/oracle/execute.doctree new file mode 100644 index 0000000000000000000000000000000000000000..d71059378e4cf849be3cf76c84483a02b78823f5 GIT binary patch literal 21845 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 Uyk+trhN|Q?2@iwhJz6aMFBJs6$p8QV literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/oracle/index.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/oracle/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..9511463ce81325d6d15f48e4620d6ca7c0a500c1 GIT binary patch literal 5011 zcmc&&-EUk+6}MyW+PnUW=Nf zP1d3jNg*K?Bk_=dCy-Euka*-DK;pSl{)ql1{Lb8uU2l>a`T&+z-h1wuGw1Vn&e;d8 zKRnr4@PBqQ%EH3PSohK_;@a6&A|yOD?y0-_(A{@eY)7$sIxAGjom~J&C}bQdp1Rxa zide9(>0+pjG!T=eLBvNgmGrd{AX@g1z25Ezy2$fPnLuROk^W#;CTz_2_jiRcx!&*h zWisw*A$c6>p3M3~7L9rT^4G5IRp;>4pWGY91@G?SZ{ILA=4L7)OSy@AinGWF4nw>k zp7yWxu7ADvPTwEz%QWJXo-j$=bzUw6nz!Zkp5^=S|8I#` z&UTqGN)8KyKvLkAWTX)!yAsAsYY_3hm9nJ#wi*@r^kK-tBY45{EV0-)v16ACC7pfe zGX==mZphL|My%AvE`HaEtGci&hK=c3CobA{omT7LR@wx)%HnMN9cPzgnR0h<&bD)g zut1sZV#vhnFiKoP#J+&f7x8%upUW_+4b8o-->h~0FEhavVoz*|>tgMN_w2CxD&p&i zp6H8f2j{@QWII7qF=DA0BAeP}TF$1(8uCBL6boa%UnbH^ZE3+48NsHK-D~!J+}5Jo z#P{&&*&ia66W-T|H&cC+HICTtS%+z5BDh^l6} z$64FG(S#heGmf*NJF#scqX-~f*=Izn3RpV#{IXJkq=`62 zq7Lkrrv7_z(o;|Hk$CGYPJJi#jA!P?J%#Xk*0;}+$tN6LmYBKt6;;+>#7E+<;%{6$ zJXjPT+U`yGicv7N6TdkTk50tn6Y<-F1@YuWoB-s0C;mR`{0*4?bl&;m_x}rL56&(c zbDaCMXJo7RC##IydRJw|5c!NYY-BoqnWg`lwe(e3`j5|KDQ^n@iWpEUybkAAjiUiq zkLxo}XeM;~&v0vnwIv>*5b6mba!>5%`6LHw22)}%JPv$UyYIv^aYNhJUdRGq2g3ji z#c--%tybM+n!YG*+E%gvRZYU6#`nIWu`7v;$vea;3u5JIEVbIbI_`*$v$qVBK;=O_GjCo2qn<>Cs z$9H+dk{l0f3h{-i0I!L&i+yL;N)-a!djy7V^;oDFPaA-YfTj<63sqVKKgZ5q7(q;$ z89wv!31%|*9q<)QMwvZc*Hi zr7^$D;m&imD=_~A)nm(U^NEBP4KszFQd3v zp9PpLvxt)3b_z4v`_`V8`2NP}mjPEQQ-Q$T6azbv2CywO&Tf^SpOMn?Qk=as_hwGS z;o{=Lg550TPIahHzj6A-++lm3Y(Va50T;!v$=QvVrQ-sFJ3JoY`5Zco-BCQuRD{_- zN9N!fYLx@q1}u(XCOjgeIw3^cZnDB;!IWgr(Y&(}6-gd6DGA4Vf6Jf9*6 z8M985A%pHz8uoMbFblHo<;O;3X)jY_RMbV{^a_A8Fg!tc&|mB-=zEv1eFOg7RHJZC zJW;0Nzg&w)@iI!;Bd&Uqn~}c*fY7HM372&Y8={J^Q@MjxdkHz21Wj=^5E$s3GUUXb z*T<=0lR(I^i19)GF2@;#JEgDA-DT8?^LIodFd*a;S zj*B64jB%Pghj!#0KlNQPP5<6@5Omp)f)`6K$Q?H15yWVC(Pm)-iHn7D=RlD`cqhU zxCLB1Sk#Z)EmR7YOw=6KVhv!Ui?R7^>SK+%s;NyEY^GHFYJhWWj6qk-ytP+TG5v=Y#c^j^85zJ?1cuZ zYd4Z}4>{ORkNAg&&ymFY_Nza}-HIx-c3|zgU8#358tgXU6V+~kfhUlqKjqf~(zx=) xW3YajFP?M6;X|Bwfg4)28z9ZDHD-GhSgY1Apw5_nf$2unEvv>t;VXu{e*+-#k;DK1 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/oracle/prerequisites.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/oracle/prerequisites.doctree new file mode 100644 index 0000000000000000000000000000000000000000..351475dd178854d571a2c2995d635b1146cc65ce GIT binary patch literal 21687 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 WPCFGE-f^oMVw!OHCv`Rp+5Z6v%i_xb literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/oracle/read.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/oracle/read.doctree new file mode 100644 index 0000000000000000000000000000000000000000..12781523413a9993f00333ded88f472895715c44 GIT binary patch literal 18115 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 Zr5D?9y+M;a_Lxm!D)s18%8hH4e*uxr%P#-` literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/oracle/types.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/oracle/types.doctree new file mode 100644 index 0000000000000000000000000000000000000000..7f62848570745ece237480dd76ce9c265a74b798 GIT binary patch literal 90841 zcmeHw34B~vb+;GEmgIfOhU4T#Nr+^mk!3rZiA^M1PK;&A$nq8|nHkMIX=Xf&GH=GV z$U+jJSnVTh!&U;MPzY-YP@sWANlT%8EM*I&Y~|yl@U=i`fv}Vh`kiz4_vXEK-y4mR z^jGvt#xw7od*3<#d(OG%p1ZtzT3@+%(c(q;XRl6|Qq@W}Z^eqGv}x7sHoQ3FIx|x$SK^sc>6CS+ku4;r&7C`qOrsitZXLJm5d33kc!M=-_}_F z_Sn^Zq)NCXmcse$QL4u&~xE8$zEyYI=DgfM4! zBrBD2cCuQ5hQx+jnoV2KNcQqnK51ES5x%z;lLh*%EnS_FKPHo@Q-A`YS(?mOYMBl8 z(rP(dv#)NbfogU~Dp^ct(@DyWz4%$ROt)3FmsgV0_}E(JJbM{etCcsDtx9~RT*{ZG zch~Hee70!T4lJ>k%_N~&fSGmG$z)~|2$k6kEp{pVe;NFLIsAVm2(=72C%j(Ed5x$E zwQ8m}vnJD@S>gGd4N3keq-)Q_GJTn?2bKW+QoB7aEGDzGItkjejH;!oBI`i^LJ`37DS?;S8X8MTi zeW?C@sZz0MrqCMu(vxxJ7p=CB@Dc;;YlQRV7A00#&n!b5K|5~+8_zooWb-JI$he_* zw{aL+>Xd=?8HMD`Otv`94W?z>Jd57M){wujM%)dEc8rQPlG!@q*&M9du`P}O`4}b7 zi~=$(M2?nXsp8;td^}?sWz(wWE0!^7=1XS`P%PtgGM`NwQ>8LvADl$&>K+617KE5J z(%C6+3r4AEl)#^GqSI#C0zj=87qA^fPfnMUGZ_`PQ^dDS*bDP$305gQSOrjA1gQ-` z%j%GwX2i=l!hq~^XpfL-(K60tD;fAv2GyAyngqd*9LG~lNh~1#R$xDc|KbK2J(vT*&R|ks-zR( zYKgc?MXy&Y@^`LiWv>IUUjYD5*k_(Bw0rOqXJZ{51Ftdm61Gu zGCa>5$n~6e;CYd~VzQdgo0T|PjiP?sIY3;6cjBO*1?9F~-UpJHNw7I)580w|d(|w@ zG7oKZB@%~+hKKfyGlKpe_GMd-5rdv|-`MCuqmrD=oA@)4=r$k<F1H#+%MdX+3)H5PTsj%^Vl?}b&{u43W&JZ9;)o@4} z&c-%^YJ5Z2sr)1BRFL?qCF=_Q%14mCG*k>{#*+P7*@@J-b2lA-UC`(F=cE*`y1*4 zNyK5zn5pbkHpLXxAj34`)FkFsj16CfSF>+wQpLQ5n^vF1eNQCy;N8yofcvu|;12X0 zcmIa9m(lL4W-s1CMpj=@oD=DwtCO;{y#ZYh1P8qs9Q2mR$fuU%Ls4;?wxzhO3{?&R zQ)^2xCmGY;}bnU=m)pod% z@F%LF`DQ3Q2Zh7BRHXj{CL*+>Y_`}TI@rRnfCi6S)If1~|H1v^##V?Eh@Y+N&Od~8 zves+kRVlDO!k(Fl)%D?*YM#h#bW;Fd%9B3sdXj8FFC1ZLmP6hEiwS!<7fxD>n{VmS zylUgtatYM>0a9!5(|J6H^q#F!98aDyVO*S_Wqp}t%z&0aPR~#+%IKP$B{m0%Oe@{M}I+ZC^p@>(UpZgACi8x6fBB?4`H)b>BN`xwpatau30W(k&wwxwpYb_zu~C=6M`z37Bxf z4xGDwr1$hYudla1_mG{A;vH`23`D&qj_{RMUx>DHM{jAwo%R2k;rx`ViIm}dBwH%D z>e1UbKACj%b@m;=(*-U^&k^SKwZQ{_MIoQpIF}3Gh_aJ)0}w9GxeSzdl7JcvK38_6 z_f+E^k@D29(O}ZlG|=-3f5Mp1>IlJ6CR`_fqcTS3q|Vpx@~d%&x&uRio&>8jKT# zFhvFmOefB{hLg$UEn8qxjVZR3O`BV`#5%}$;B2xmlQ(;e*-{m(rf3ki#*30HS_S5v zjWkREV%3R6wpbzE3eN2H7+F5a2StjNBn&v%Y%q|S(=gc!bHi1T3u}Ur3R?;SY?AAc z9WGAOX$Bb1q_Q|6T$hhKe);cl3xgZ!h;Sy<<(9tbGRx+?;OUMgxOKT06 zx-0%EfZ=)hr(ZPA?sWk@N1)vWh~*61*VkJpk1SR@O;@S1ZE1v|4G-Q$8(h?1&5%Nd|DLFapGL6&II#L3*vkf7Tv;y>n$z^R=-BHJlGm|Z-Ml(xA1>P z?pE-Ex!Z6=0iy<C{j%~bO+ zQIj?xIYql5=Htn7sC*I@5~4L% zXx0=Z9e1IkIQJDwV{$~7v*uN5m~hn)thQ*0_)Ox&TZxV+bp>#<%Arb}pLU^MN-f34 zIXb|6Lo;+#;m9qQznQ8z`KyEiO8VSez=Gv;7ihRx6PbGuZ#nZ1yAcYy+U)6ysf%1h z?pZ(}Qa>9{m{t#2(}F8s&Y0P9np;|KNF{_+2{$n{Y4Wltz;hFOF%)u`*aiJ$P_vWbe@4 z%P5<|Q|G>jmC`wfz+#f|FAFxJ%xG>00sbCJY7%w1&tdJ<6#EsK9jYH`DwYWssf-l% z`waDxCnly*#!B^ggb-tpH6N4UJ>Z(PVJU1p@b$Svf+MNJ9340^I}4n_)6Gk=Qdf1c9+JJ}@{u zWOTtI%zj))3Cd5GzL%>-BU`CUAnw5CV4Au$>O+-gy~fq7iW+YU;Eij|1wPy>PuS&p zLNLM@86rJH|Cw=&XuwsO}ooEK5XF3=}ai-E)$X=a-B zty>dzRmg%I-q7?Cns_9*`TX|vAnM5o&>+2p#vgv~@g|xN@9iBu-@}8|3ePXfb=E9x zMHepQvjxCDZ4|48N!$)HMb;X?b^#b?Ci8_7Y#G2+q*bc|tHxog6|6?Ej4ozthX%&R z_mA%%9We$^pn)Y4`ypyLj$h46syo(kxP+@6DzLZ=Yc8AlSu|b9oUo;Syq#$?vqj&S zExUC=IakbS*44g>!LCgtoC0z+TB%-E>bloeSFl;2Y2wp~X)fte=PUx4Y8M!DZdwWE z3LCFzoDImRHP~>{V%*I8YqL5md!|!|VR=1EU7KYkR^}&e zq^WZbj^mo-@qJMs$~}zK{keDJKQz|FMq6aBV!Kn~llfBWl*-BDk>9wxxRGbWRtX-Y3 zE6psXJG*;2x;yBzW6;xerKh?%5>F+TXhQ&`e+`gY0dw(o>r@H1=FZuxGui1(9{#Vy zVP7n60kzxD7V%BS&b9;PpBUcPR`6$GO$?}6fO60*?Kp!#td4< zXD`cI@pSfd?VP<9_6}#Ki?GA60Q;nH$0lqfE|+G~r87l%25gZk!m7YIy9HytTJE3V zL3TT&-{N#n;W@h{3+-MTfvu_;^E52*x>^-d|&Zl?i>A)CE#fbIj<*FJKS zv3Fp6V9OS|9|v7?ELwevWBL(ttHkEcT>Aw+Pv#1|!GkZQ@}IjZ4|Kjf>y8`||GRjn zQk9#}a2{;GqXBa7f~t8Ah`FuYdzEWZ+|`;^Cvd^CF?Y2R3ki3XdsKP$@h(VeQ3n(= z&zi7F-lYDiv$K;pBu5wuU49vt&QiZ*P)gjQ4%7>fv)=2A$1)1wS5x4?lN)5DX8My4dF}_mZUa92fWw6-6C`(F zXhTB)!#TpefgcJ$PK01K4LwyKoD6|;Nt3YRxQ||D|MZH>+qU;vx97`KeRSz ztVy1}Fxq6G$*vqYl_7`tK_4_IP#ZIv-l!BNejtOrl8&zHlH%+-9gWG{yZ|A05_yH0 z&H=ka?A*W&5mJl^dyQLvWZ3Oim2e$v!eJG%jD0Z^0}||1lwOix?}eDKd`ijnGdjba zi05$kYp*Ik!TzbLfgBR@D_@B&eS%on7BS?U<$r^*O!)Ju|)*NAGvL+&{xcWDdT;1mW9}BH$UfW*kLbc%ctyn4;T4ZAO#8Y#<3`-n-&>jz(}u*{qo8hb{v2Jh* z_ngynh+qyrZPGJ8h>wB+C_n5)oVCzcVQK4M1yR)jp1*z}G(1_FbIKAh{q^F+v-b|} zF~o^HqMw|Gg^bn77{)zGNC;%$$RO+k`XDhg*>{?pe1oA592r!GTjf+=0TxB}S*c_` z2~p9svk(&I(vzt^>Rt9`lX*DzB}S+(AHHsT?{NRt>mg2}sv}%e?q5)PGKV1ifVc+X z(<%q=iU#~!Xw-O&;;M?8<8!$=KJj1(tNkH};;`Dgq}4w0Z0&X#cL%m=U39PtucA)5 z!U1W^zeTB>woC;LXRCS8?;j9gg6)CNlOX-~m`(tgayING>`VyYmFUuwq5l2-{h5-)Z&k{ewQ4MlWFkW`HEQlwFGRQ^pSjL}&YHO4!h1Y_h) zBdl=~$l|cZyPp(mJ+d1tI+x}cL$RFZNF@ztjs-BU3kWj) zZHv`&b3}S+j{eW^o|{8s<^io^EFlL_mLk3Ls&ioCj?ghM4j4478iAuOVT!~#WN=-T zefNpbijUx6oPJc4AaOcOUcj74D_u7wce2vlC)`<9nJ$;AGs-lHoXuGd^YnNwNZn1} z6R_Z<&QktN7S6(~9q0s(a5!i@2F+-E^^Jw%!- z&=7NP!P@(CZz80*x57WJmK7nGtsdLMI9D%8jvvK3L%DfCUqq_N@y7@oP2<>egCb+y z^PZI9YYofrSaW6YUhBk!@V+Uf_({W3{3xmv{=OSc>_KD%HkE~_^>vh#`Wj-5I5$^k zT3xw60Yay;c4-tik)Ts5i3AN^#X)SjiXI;2((x6(uLMGp$qE-0-RhC ze>u`c(t)K=B5co!Cb39Q?-YMQoyo-tdeSO(v(!7%^fqZ{{2B+e7oq83qf;-!&cu=Qd&V3^a>J7HK*w*wuy zfCTnyV87MdzwO#sthe7In)#4K@*>}23Yw2ZM)L!DbOf4rfa7_eo}Q!m9zq)Ic-B^n z@^p!l+u8APxC&>bdPfoQF_(z$Gr4#jP{rcyO1W>j%E`B!%uVv0n)FD`YrN0o zLN3HU*g%DGp!X#LiF~v>M~CBcmIm@i&o9B(+`@rE@gzc{kvy#K-#0uu02}??W8jP0 zMZeNYNR%erg!dx#9~VH0up!D_pxqnw^t^r65z=7ojxzYa9)$}%_9G7xIGVS>Onm{ zcQ3CGLCP|@zD_t8s;SBX>z9ZLg=?*wT7mA@NEQjY$My0F=$;SI{ZvoS zq5DY)QXO=f3_t8D9Na%LFm@vD)6*Lr0=JbarGi-}l0?Gn5-1V2!+#-Qwn09_`I&n9C?cy_sQ&A5JhQ_? z@PrgxW}!H_BK~A#@o&?M;}HL(hFWC zTY}xOrk@A-3`#^j$V!z`5&CmT63K;pS}%mKAr$(PdU~hO3#uv*^Bq1oFg(0}#A7`9 zbBR|Qr&6%`MP#ggs+V10H3wMzL{HDL`Vk=wR*?P?K-H5z(9XA=e$)$wxS>H)c00wOyhPOe~aZDcIAYh+Ws24b;QL%;A? z91V%Z@yJ+=Yh-g{@f;0(Fc$OkEWx%0XBdJ{8M(VDwJB70Kf~q!&cE-WLN#f2ybF z82t$$4OR!}n-9aQxr@_&ee7xh@ExTuUbg;f< z|3wPhf)t97Ka4Eo5A;ksg#4bKz9~YsuM{on7IFpNiqw+Jp+wjY6VQo9A0GuPmK-7syDr)veXA(jgci&k`IX&yH(iGiBJb_mYKnF?J3Up88$9s^W7j>2^97;hHxZh|69hMw=A46gln8JySBk+D3&|w5>`1SN-n;VxN^YwN-KUxsqvVr#=yBRa@02R z088vA?DdXjgLiAgadvV&t(#SUHC4OLmxEf<#*y^7AHl4g-1-Q!hGL&%?gx0w_q5iR zLTy}&J!S*#RdCuEZcXT|xVBgF8}5CT$SUlacJF||Ee8QRAXbl?aAFU-3u%r@wz1y;*+9zqik$=4JgLnkhTf5YTikGoHk=~Ha2{#VUEAvT#h|AFEG$O z?@82u-mnb+(Oen4_bxCYyl+Y=R;-E=2=PrZs5@;?QWFq4bw~PauZ0tA4D-aJ&RSQO+4d zH46k02CGhbj^Il5vI5_IE=dD9C-B{^hYv{_gYS<2X7D}al2i}hoAvM^Nn`Lm7!|%i z2f>$nJ%iL5JuZQDP(tgpnhWphP>8wOwu@_u&OYYCM-TcJi3EWrk0jASe*j$P=Sk&( zOmLI`3?Y50xX%A{i5w^bvHf2V{sVDDlE!fS8L8Z#2{j|EEVo@GW3^Lke=%^d zhu!$aP=XA2{ktI~X$;GYNag-aJf*SR=Mvjr436cX9!n%?49jQD3(NU=*kD%yYx9MF z?803@J%(R&

h}qz=g64SHs9m)cb946p!8?d{=4h9*TWM6`6~|6zD(F7 z$F3Vl&BrU=i1&gw2_yc4#wnat%m`|yG2*|d!G;*|qxA=-7*WEgez<;noe=~g7|~t4 zy2fe5#rJyXPm9wCM960M{!(!oiSw6vFKs^fW(f6BF#{b{!QL4iapWq>Pf+1DE!O?< zP7pRDai`F{?cmIbaRI;L1Fbw;pcejOi`;g~Hec6U6?wNq+*vYQtFaK;$Fbv&BbM=o zOR;$Fh3RW!C!Zci!T>v*aZ`S~iL%JZCWr>7(15z*`$C$EA)yq;xrwmM^>fny6O zA;8^1mW0GlY~O&<(RUUq&^!ach=@0nb8#=;N3}}3sVN#XOfj8`JoEkt(d_qgDOM&< zknNOOPG&$(<&d-t8jf=*9NA8e3?}u;a}OgO&5MS+7y8HwrRX~~uHm?`dHUKmw9>R> z8hqu$ER6SAKHf~e=st`c9t`uL;A2C)8jHM5DTde_Bt(oZx9o?_^IfC zso+6ik#o>FWcvJuXW5sJ1RIJhzrnc+ zc{$o+WTEv&uzGL3$v5k$|EC)YhTX#JOs4Ua%N4R0b*5oO|%ATJ#h_NEx0l#0^}; z%Iyy#k1nQ`-ghyzgRz!NE4vLkuh~Q^1(|8%xPeu)?2K=>^0bYHGJ_ao3)_+%lBgZp zQw`J7=81b}-8{;NPYtz^XYlaB&S@6O%KgH+>p2(=@9O3`suyjlrRnR=E`frq zLNzJZMj$V&1Jc!%67czTfIIXjTxs_qb?uMV)hBy!-CBB=Kt#rx4+XP`{H!#6N6E8EEJG?-c~!`SuY0s(l`|a>c@>QlwQ&xFBkn zWxC8Cp!6rNlqa`c^RH;dQe@e@NXXd5K2Haz-SlC~x;*XA(36 z@b78_@eXfN?5cdfC`}duwf`n^#L90(j;)PF8tFeyk>2jA#!w||-6DIn{{)UK>%(^) zsm@+|nXKB@0qpelTtd(Oy;=MBXHhTfJa$>yiUe(De)*QKETZ=sq>$!N*QpM>n<_g< z?WNS}Fl;auDNV<@kg zB|v;vUjeKsC2DC$`FXjNxSl9i!|m*#D0NpzE)En*D+PYF(5ZKph z&Du9mpD`{n==oHv5nD!95Tarhe(dl)kBZi!PU|ubE>EJ$n;-%A8XAjekc)ptzKdVG za7C&>Wp?Q;Na@G*Eg$tO_g8@i$FVH!z!~i5A${tK-QqrlV}pkcd9M7g#_MWshoxqEcoQRwtgF2poKWwEbnl z@_o-Qhy%|)W`Ia7hb^{8a9+dG&#|=Fq12lcirOu*CA$UQga!%yX#0y4FxyLG2*4;C zEOL1%qg}FW5jc*sUu-MtU9$t^X8j6^TvO>Sbc2g>;KKl%OBKTeN1Tcj2z!9-HlxtP z{cQP&ZKZ;~yDCw=Sx_Pg#_|Uaq|tAfE+O2*pKxKP*-sud11qX{1(aMutG&=XFR%(a zB$twMSh$S}mLV6}J%xC4kzAmla!{%Y)NUsRLpQs*-T~>ZxLKj6 z9TmQ<2H&d0ldoG#C_?Z9>{XJE;bH zNZtU$;~+1pNaQ_#0U9+zEja;bi3-Z7L;?+@N>f&I;>eSys6et`EvL}alLY-IBzoHK zC6Pr9iM(TlRo(|(w4`F-=xi8!&Cntg4{u!>Vuv7;^@37PWd#ekAkQmX?G08Mp}Aa8 z#jL1sHP!ZEJA*o1UwTRv(1VE=`UWm@76N)PYNB`+ zm1pM#mt!-q3hD96PNNDLi0#6DG8C|~sb;(;Ir=h%GEznD#TG;c1*Iy`;1y6{TULlZ zM#^ZlSg=sj)-9m;FWFsA^}e_cG-TDS3Y)gdwpoqaEmUG=L&BO^WES^gj~f*>Aa|n1 z9Ut7jbX|C|=Jo-5N5J;Cy&{i7@75GtKM+taRrQ|Ad{aqt@+>&{bIuIdAiWgN#)M0n ztsR9as69a+d8AfqpQMi;z`NJ}jy|^Iyhg2$KHfmMUt6J%7wG2cAg9kb0FNa>r143Ykfx#k_kb}fqb*U@eUvgJ;%s$%QyV9&#o`Dt z?Cf}P%eT9!%?Aq$U5aBP=vUs5;{i_E~?<1ms73AyJOFg-JNAF zZ6Z+#kdRtw-WYh~Z{;^LyS8Hr(m5|gT4m>(Z@=sByZHOgFGqv5@Mj0P$c*NNN-B|4 zOm(bLl*)4EotqY*D>iDH}(LHGFwIDGcoo@pr0;oKd|HAu6hiL{gG#M&U>hM61zBa`)Zj{ZaUQ#H*ZMButk@ z-?8n74leX0olC7&3LMEb;S@?0TFb~`JG zQXt~JQ;{;h?c}Cj93~_?M-&3jCMmRIhj!CQ?(F;jSAv}FXQaw`PNFq-{S(LTC}X!Z znR&I2-L%~nR(l^wrPEr9Ld-sPcB|kOb!Qv4TN9)O#vGUlVXwj|c7%+*j^_?U}FO`kze$LnFA;z{hs0C7o23YjKyq|15~JRNzkk1|nJ zH1oALn(~6C%V%9D2(b$WZmY<(WFhM|rO-ho7gx$)@cCC?4i}=@I@tP(Uic4qE|3st5>}34hur}%QG5s3s40jy<3k98uIn z8?~Tzayl_Hs2e;cvO*NWf!R@l(nC5j>Rs~o@d`0ZF&;z?E@yj2Pme!5RvhoIUA;`H zltQKqt*@XG8StiN+SzL{@=H=WL5j0SD>o}5*l%_ag)R(eKYi`$#mblVx_1D;Qw;Gb z;E=QX1*v8RgBRh>ao)ffw}+BuLgtv=HDCiZ9GDr~E(&f69}y0ow`gEpJ0M1jbV0Hk zzGdy_rmWLB9ud_*;DDm6NZ|!u3zUF#pZW!fG>jX8o2WPO9RH5h~bl*2au%*5+h9UQ2Xpy{9REEN&m6w(y-@Dj% zkf(S&Tek%zX=4kM6NQeSE&szk^c z%IT)(3VJ8BQjo1fq|we-0)oj zEp~-1tXj}TVkP=T;oJc-<%H%a-%D7)DAmw$GXvLoN=w3vl;l`g!wU4&9yHSrSS_Zp zp*^#kw0h()rR+{@C8%8>5vw9cZRj*k^nja#)pggma(2jemA!H0D(+TKP#G(G?r{JXK%T2)^Hh%BQS&X4sK`A7grQ_Q2Tv z<;;3MyPt93151HNQ>6F;Zuz29%aq86wF*cmC!&~xC9uA*LY#B=u1M^)TEj;-uE<9t zmfE4RZm!B|ZZHqgUNT{P^T6=QL{T6O@B!0h`(8u;8Q%Tm9-vgPC8FrC*D7?jsBqq8 zmdRMo2I^9w-dgH>G_uZo1?oW@nMqQ!Ki>Y=-uWjIAU*De5f#HXv7Ed)QXNFhzxelywS+4ppZss1mwUgj@B1`*PK2ygT*`+1**@ zrA;I%aVH_Q(tKm!BY!KunccM=(?dGGT%=WYo_Y3p{XH-KuJhwNgSGhQ2ANE)5xGuE znKA9%P9PMlH2w$w-oO1bf7|sGeX6BZDf4a(94Qxhrda78``diYy{7F%8(|9GFvSU6PlhQ3Xx}%=En0Qw`rB|?xWw8An*DqEi;kP+8Vch&-3@R zb=!u{f?CgSxNZxpy}u}JNTp;hXMgo>D;Fj6XB)0tQKSXN9NG!xufr*RjEudB=Pf*M zN%pVs=(Ld2_Ny(vkmZXx*nNV4Bxa9ps8EK zav35$;Gawb&TAO`S5G?dL}Z9SV2)OO3Qe7$iGPA8arXe@f{iqCP1VR0)hPNp3UD8# zvMgB|s&6zEIU~zw2uu)VA8p*$HQSPryxUYm2gRIUsf2^izIgbL;X+tj3tL~Xi_j?C zuNc1fv3J1kf7W)7`RVwjv2Ah~0{bP{AJy#q0o>kN{@}Jd2r)TXwqnd$C^WarHa0P2 z%c*;~2xP6n{gGW%2nCsUfQ(EoNd)LK&kKHEyVqXIf-Hey35v{%Hvv<11CN>%vh zZlKYotYtEKKCL+Gn+|rQ3NzyL)l5q$nuvFBB<6U zLW>%&2P6}kPU>D)Te;oV32JT+&R{tyW6?qAerSncBcVOUVfSiml%mv3#lmD|kd`6e zyV!e>r}}ud?hZq-0XF3fD9lA6jQ{-FyKYdiYSBJ|GkbwDp|CyFh5JK0lt9)=b!oUP z6RBn>ryGGY8URjWSb_8yq+nk@%6c_pdxIaao99H;04elQ&CQVfu<>3 z4_yK+c7-jjT+l`9N(>9bxd&v53CmEvm#}~{W}xFn3a)d)3Mz6!Gc2xg6?*Otn-K`C z=F_^Ny>OeXeBv>v+)mv}P`f}PRz`u^Fr=RB5jTgcYj0@f{D|*rcm2v$y<=Sk&hNt(6E?OmRvvqPpK7-qE^FFt45Km6xv;JmsOGXl@WVz_Jm>if%a6nR7z0%B zN7n5xXEyM;{Zs%SSPcT2YDy^JmalednNss%y#f-7iOlC_OK8_YwrmsHx&JTQDxnde9Ye86EGC7G4H?L0S{O^__w_Ow3|-5&bUr9*4EutzyhlC@8i!N wgi%xfRzvq|_UKL!yIIR~yYq-dZ10LP&25IhCTHRIK|c#KplO$37qgS(7t5}Nb^rhX literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/file_connection/hdfs/connection.doctree b/mddocs/docs/_build/doctrees/connection/file_connection/hdfs/connection.doctree new file mode 100644 index 0000000000000000000000000000000000000000..9cef569bd89076ffe05e8fe558ed54bc3ba0e7bf GIT binary patch literal 3681 zcmc&%-)|hZ5!SCe=}ywgvh1coBJ`5Lt@5Bfxrxz2DAEQlg0w=ban$IFEMcNayT3gXTEP{^pDQBUkuj5KRd`p zW;8EUQi+@@XLr1yq?LBxxcC3*p1Tv~>ZVr69z2ry^zi+R075SWHNEi6{h}O;Tvbj_jt9+}Q^|C_&ElGg9R|C(#?b z{#(c1SH^B@GV|9ucGq@WSnd5rDxKC+6k_&~vs(qPsJqy(-I^dRaOTiV2zv{UV#mnX z&+vN(zxVLFhevhcdGPgDt*_q`3C^%ncEIkl-Phi8MB1yoUXLYg#Lg}@pucH*>6|fa z(@cP-ZiMABMD76pDa)toG!s=tGtZCnh^YVjs16>*b7v1$&IMmT#{C_CKl>eiiTxwM zUDA;P0*D;xvL1y9j64ZOi#tbCUeI)D?QJjZCrE<}kJwhx%cWPaZbO$2*k0VA%E70< z|Ku;jh254cTc6P@UrFpM3SYZWT=0ay#wU!~+4!}=Z<6dI|2fnjM|JiH5!hNjaKgqO z@%J5V@o^Gkx9r!e(WJ|%{qrKE@dEoMy{Zw3bY3CybaI^r06%xEWDk{nS84?SE%#h*%1X<7kd-7g4gs+#B8c7@Tk(QeE>AKTuHR#Xx#q{AW* zT9dNIVpj^`X$zoUIlDe`b~n0^;@(Rr^qZxT1y=i7WT`JSeiyEE;Iq7T_VyHJDxvAp z<_nZx#Jw_QdNmcYbT8~cp}N&TZ|VD!g2A_rPw$D)N(q`GQhxCdgikfJn7Sc{mCuWB02G5AB5JhO9$CQxtkdBP{Cwc#Pg_xyi zBO-^Evpu7yr@uZ`9N%x>+)SyILZ%EQvY-+f@UCVWM2V4KveF4woIPIISuw%>V+Yab z!h!ba+c!5>PTE`k0RWz2h);nBIlEtwYG%-&5$+u84YYfEBxxpOjvikFHn@fZGh^EY z@27Yp!ol+v4XkSi#AuN&M0UfMv;EwZbvnl*q8bDoP?QxZyupJY2}t*;Ux;c&O)k=_ z(xd4XXeSg+mrn+WfUQrt;t{V0Y0edy#Hts)+-`XS&h6nb7+$JCIvCye#Sv<$FO;Df z{v?!4UMVUAF=^$k<;eFg<|5=NE-%*Yp(h()Q_XoTP! z;YCVv47OoZdSMTn4hXCk(^$}6+D%$Lb?8%eCzcX$7f8fN$-xbs#?BvbbGSMd`%=!1 z*sij-Z*0XG>^YdRGH)I!f6H{f@24C;(fPLW#Ic7&*=@fWWhZDxV^%P3Y|l&eb&cTr ziK%?SikF5R`|mOOCufh1-CxeE*R%T>2R<;Ecr-`=4oB?%m;Lp%HT4~nFZ%zNJJ5k@2mdXPL+udM zjyJZ`w7GS=<*@;_{$c#dBj0-B-*%{m&D`DbW;dffyE_j~!1^w)vclx3b7JQIp6KVk NCp6{Lk7j0){0}2Umzw|p literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/file_connection/hdfs/index.doctree b/mddocs/docs/_build/doctrees/connection/file_connection/hdfs/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ec1d9c19c02411a2f132cae5ffe054ac7caeb3a3 GIT binary patch literal 4566 zcmc&&UvFGT6}MyW+Pk*baokW)Yq1oCrbXVhfeK_H{wQhGVzEJi!b_v8*?VVq#=iH? z<<8t>EgF#)QetU%Kpl7jB%Ukfi|{Gv7vL-KJ9F>dU2l@6@PegP?wmPu=KTAeb0!ZP zfBxsrlK-=vNQH%wvF@cR;@a7DA|yOD?i2UMhwg#9Zd*CKuT_zU+}R~?ghIwq&Qo{S zT^CFCWnB!lkp^N^8bo{~Q%Rpw0it35((83U&_$N1+yp|YQ~g0#CTz@)j=I8_Ods|8 zG8y-@kUWlbPpbZqMPuH-_N{~c>Ks1($^Btm@OBq}M~3BNZe}7>%1zwMIg5cF5tqTg*ybZ7SJl{wEe@nb_ zw#|&m<*+aaBn56oMjAn~Yhlc^1`*$llqKcYdQ@by#gK)k@Pg-AVX<*y*RB+~boQ

=vRjkH8aCSA8DR;-0Y%^mB z3zXR@hD^Kyqr@H}_Er48hTqrmy9T40(A?|#-CEcGG80@O_Qke16dTXHXP4Di5noI6 zL|+^nUjqM%Z3R=rh?Qc9Y-*NiIiDgesgxc+-wnt_S&3Zp6@3B2f3F^50`;` z^F@bd`|e}M?50e^xS)C%dkNw<%&sSF67kFkcf1T(vdkAs+rBZ48~izr)zF>WrjSvD zDtm6XDoInqdRiny6s*V{FWI(_lU8Yb2HrM`^-OEOtgjItlFt)lFZsF&-^;}S+3m`m z`}cE}WjuFm9%yuad@*K-u@GgW5RJ<7>B9PX*$zn)ady`YUK|h|E2GJ+;sG%KcP4&& zyeuBt&MlR9BYuy^3d(9HJ~|h_I~Tt{7mtpY#K-6258yr*|Cl#?6W9J}(Qxs{FJLI2 z=G1jDpz3<)cwLJs#QH)p{YGt~n}3EIEBKaaj7oTWNuzE`8(j&rWdX3;Rpu((X9ipS?y zK^83APFUnrii4_T5A0@5@x^*z*EKgrreh2dJ5&<=R58j`YB->5o4;h}+j@#=+f;?g z3gZUiy@73wdCGHURE}(D`KDo5l3}rtD@CmVIGQ-S+;?`PR3X5G_66q&X_$me*jW%E{$)F*(3t}D(N`~SES$8L$p9pt7Vu9DgPh%pSvoE-1j5}B)|XK1 z>~79Ol}DH(Go%f!q4GPhO%&G=Y=k>xRA+-|+72s>3T7mGiMr=jR3uq2%}03E6C_eG zPgzU?w>`z9fkHncWtta}3ML6<(`CR;X&%g<^eF@-g6al{mcu;0z2;5FUeYtb@LP z?ch7`XD1(p3*w0~6aS4`Jdn#MUr%}7liZB_9jFF(bpc3gIBM6g%ViL&kd;l>?uCLu z9|XPx8$#(_h|{MgL89a09Z zIRbpopn>4r;Et;yxK0C}Fc}A!U8v}Kt#_&c47nPQ%7S)oS9p5Qp|9HYvXlV3$V5C_ zB(PzEa=P?``p)9hi%PlJ6&u=Kxv*8v`451Mg*VNvrdvkSeVU}{)j)0Kt`j$ywrjLm z(VmEbwk;x|sH6~XYrfK!c}mHL z$sP%i3{^bM5zqS40&&XSx*)Mt-|7!txga0)H#andU0sw_?O?7#RS7YMB6ZMnLKVmG zK5Rf!S-VluKOqnN`*>MDa(4g|yjB1>ystE>8r_O9x}(oDKvh$x{ck!g;rWSkY>a_R z%cLb-+uE&{GUDiE1mf4;oo|0IMPTeqnVmH(H3%2>VgRxx6n z-1L$cp99QZnbzpqh2+?ix50jK%slzYQzY@eee)gMof9-`=ap{jO5Mdc(4E$tsCEj> zlaQo8=fO$(R-PDv_47ROlp9VMPCUa2joJy2W;dord+={m4*(RKF+Jkj5jDxGpHT3M GVedceN#oK0 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/file_connection/hdfs/slots.doctree b/mddocs/docs/_build/doctrees/connection/file_connection/hdfs/slots.doctree new file mode 100644 index 0000000000000000000000000000000000000000..c51e148f4d43915b894701eceeb125f607f0ac75 GIT binary patch literal 3641 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/file_connection/samba.doctree b/mddocs/docs/_build/doctrees/connection/file_connection/samba.doctree new file mode 100644 index 0000000000000000000000000000000000000000..22c9e9a97d596adaca47efa4dcbf59d988541c1d GIT binary patch literal 3642 zcmc&%?Qh&f8qeEqvYTX^H1xO=L@c1*6`|NE9O4w6fK#OtC}N8W<-S~HjqTZ;A-2cN zOPfek;!Z+prTNCd$NO9FH)DHuvlJxwLL@7mXC6PVzvpGY?fnps*2141bm zPL;Deo>0YFhy$&8+zjhWW9I*Vgo&LYJ)Epioc5vL@d(fIZ| zrzh=qcnx2kq=liwG5*dpku$0nB1A=X5lKpN%_tlRf@l?=Mt9$h-iyQMF|TrZ88KZJ zL&vrsIk+&8bV}h7JnX!N(fij7?s$8_?q$CJ{{Ef>S5~(dg+^m{VB(H zE;JCB{;$UL2|Jy{WFKJ0CRglmx zf%%!Fswvo>7Mc|9e9aC!9-8B5d|=k?C^OQRL(31<{7trVRUV@o3MuyJDAamN}`2yIE2F0q^xn+l|uOP1T-(5U5}mJ zZA?g@_W}&V_Gn~*)4mp2;v0&;g(*GQEU%osIfa-?Xu5Ry3~d&1uS}U-PK7MpGdoh~ zVl|Lk`Z=T^@Rf!_3mPcN6Rug(H8tpKKWMug!XjyyGzEQ35zY-eWN2wgd+gamddU$* zO|(%9YA>g$nL*v?36T||2oB7W5|jbbkx}oGw~tqdS%PsPa&S32FnW6Ow-d$j{_6G1 zgi0x7!q5r}Dv<$iYNnmN(L{bpN-s!p_ITxHMFjhw9W_E126Ugkdi`SMOMBoQ0Pqw; zdD`bP&4l8za~f-z3A(`$K4#yi!z#!lad#mLuQ0 z*ng0x=6Jqt4}Gx#Hq{I$EO^Pa`}E*1c2v`P(LTI0d!CXK*zW7X{;?ZQAgidp(o9s5 zkTaCiEzcPY0B53UiS%fsV+Zt;+oyj;JV$bxt%&!Oh4?>r;!(N8O$Niov3HG_K z4N6)eP)Fh19Fsz1qz&zH#t7^|;+tYaDnkhI^@XXS!f&Br;

BInX0AUQL04wkh27 zT>>q3g)OXJ&_$X`^ozp_oV}T7T(KCD4&Ou!rK zq`a2t{N8Ufesl9<<&k6eh_c)MvZ39KW<8(X&p7ab1;L}Kq4)ys_!_5{DGeXiEFhswMKK3UV0~$YIOiT*lQ`(KhL3Ju zlaEF$wL@jyT9wt_U>>5qWWxC7f#H*hqCgtp1E$ONy_Wtxy!*~QK&fDBMA2c-Rp@Mu z!g-fjCSzF}sVjwg>!|b5$a?c-XaI3!CP~eHdi!tt_Qyzo^tc;0s2INyR+Kl!T#Yg8 z#`B8GBbetmB6jzy;riN|`h}Gi1OKZX@PM_4f6F^ix9oKLjP12^ZQX8pFrYj?nf&v< wpEb>oHnhKXq3(IHn~glXI}b?!_b#up!sKXdV&;D+40AsO+J5QxF_T9B2PVyk#{d8T literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/file_connection/sftp.doctree b/mddocs/docs/_build/doctrees/connection/file_connection/sftp.doctree new file mode 100644 index 0000000000000000000000000000000000000000..b5c16074df59fcad50787faf211e7017e1df08c1 GIT binary patch literal 3633 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/file_df_connection/spark_hdfs/index.doctree b/mddocs/docs/_build/doctrees/connection/file_df_connection/spark_hdfs/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..18b3603cd5a6c14ddee5a5825a7c3eee494e206f GIT binary patch literal 4435 zcmc&&TW=gm6}Dr~*fX}ralBzcyB5QufcL>OHp>FC5ElhSS&f*i5*A*FS~cA@Q?>5y z>aD6y#$tn(g@g>M2c#`{2Z`sEcK_1;0)7EsRbS?kL^8Z!X_W5Mr%s(y=ljk%l}{Rf zyL+(a|4b(mVXk?sdZ~!0vS!-}5}IoJnSJ{Y_K7_w+wGZtv zTQje#e55qjFeB0+qGO(N_gEWX4fFS2ulupevrI@GFd;6~$6cO~2|YjWGOaUp-tY5d z(o>AnI8r?>`XdreX#dvtPEX2pc=RhzMsZHtUHqMEA}3VOO^B50xF;!zG@}T_8=`6d zw0HJS@7=y%-sfpVr#+^VxNX_iLyHhvl8z}NLWEV+aC&da;YzpR{a)1j^#31;ch0nl z){>8MjYPV{ZSY7TNoFgIiBb^axsj5jc-oHgZ2mGL;RQ~?>uiu%TXtwRa>=cE_kT)| zHSLh35sygWjamPxWpAt8Y-uua+gf(hG%Hx`{#YsSW1jA0x32xw{+uv~=5KJXtX1aoo1qOm$J!1T&^8i#LO=;+u*dOzl2GKZJ5P8dibEYH&5@5aiTdG*K z;6H0RqBycQK=gQtCG7_;tnS@k+<8z@Wb;>a>gtDC{PxuPY69woY$F+(&Xw)V6=8&$U7m(|o8bJtCAy z<4fqa(JSUh!JZH=sdi$2a!F0VAD7G~lFL^SVD`&=ojjH#%c!(uk?L;!WEm6y5o1NG zVvRDj>B{@7mD&>r8_`EJ7I0o! z_H@Rc&Dfu3?D=4g&1UQ`(EbNuhW%^tEcW0D=(kei{(6PjX^rD!HgqNb%+jhr9+wrQ z>v+CgPUvDqdmp=(HKm}L`39Y4C<8&=7^7fx8Th*W#Ii5hZDn46sS3oyd9M|0OI zhpJ8M%FphZ*R&+5iisZ|&;llss-k~2w9q6jKvwE_%;oOn)=ekOrRxqs*+qtCx03k& zF*MtXYR%ILVn1+I+7CKoDN;>QJWTV~1fztitB7feTxYqqL-xVYv?esAl4v0QuQ;t*AMCWQQZA5gGPq1C*F_9@w5gHh+5=dG|Sx8+-&Oe^w%mPeL zB7&ARtz3^!esrQZp5I*l7*HvN3>YSZ7}N#~5L>9N*)L9h!AiqhvF2#y%Zds1mFW~l z=MGd$-(3E%veI040RVW4;e28QWX)bo(n*fF6vrLm{Td3OIg~UMGQwz^0XEpi)%l@m zqUMefBaVYd6&hI6bV#m6Fc+C?u9fdad6ES+9&xH(zyU>>lGqvC_Y%(rm-@LVQ#6l6 zFijkqu7h?$(O_|PZKqd!<@XH4O7L z-}ZQ_sPx37sk0U#-#aMr$W!r(K62p52H2z%pfKhM*Y=m!ziT=f&F1C91+(iUBLd$Y zFU;4gSq);<%Put&X-~)r=ycO*dIz8xXqq5B8tK@E{N&c@_i>&bISyCMJIUPqcPjH> zE^&Chpt8rQ9{W8|4f^g1fX!N(j6fk=8SG%&908C?P&2cxe{OmMLT2W=x=b~h28>Tw zj325mYrFY*jqxJ#Mu zs$~s83x+cQ+tG7W{%Gn{? zQRc?dr{e1I1k6~uW*sWG%eemThI@CBac$+HWw(hkTkc~aJ4Q1avM<=@xoJ76zRVDY zyC{75oP9CN*^zr5Vv78n-OtV5VrHG5*$X-FfhpOcsgO7VS9(QIi;_YQGXs#2j6__6 z#jC!yBCNGrOBP$zu71&tCHpAEQo9N5=Blmo3x+O~mk<*$Fofn4ia16Z5Cgi)*6mXM znLDw657yOFdmntkln&~T&y>88|sAr4Y0XzHf{q?mqwK@ka-1X4X*QhM_X>Mvr#=@C^PO`11+6CT2HlQa OJ18rsyPM>r-hTjc!N5uY literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/file_df_connection/spark_hdfs/prerequisites.doctree b/mddocs/docs/_build/doctrees/connection/file_df_connection/spark_hdfs/prerequisites.doctree new file mode 100644 index 0000000000000000000000000000000000000000..a295676c51e10e1faab1e6231819bca42e391607 GIT binary patch literal 11394 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/file_df_connection/spark_local_fs.doctree b/mddocs/docs/_build/doctrees/connection/file_df_connection/spark_local_fs.doctree new file mode 100644 index 0000000000000000000000000000000000000000..d46154abb69a40cf2c2cfcfe17f9c5849c8bf906 GIT binary patch literal 3678 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 aDAU|#=yh@y{^aOqVJzN5_m literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/file_df_connection/spark_s3/index.doctree b/mddocs/docs/_build/doctrees/connection/file_df_connection/spark_s3/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..612c0bbad11d699c780db96643420b5bd70b1840 GIT binary patch literal 4243 zcmc&&ZEqw;5%$}zch_FOoO1+}SS&#Yj)Zq@oP)?hyyzqZa@a$c1io+@_0IHecRX+D z?zvlw97>Q9&QgC%gA^(GOZ){~7&@C8e&o2jX;uBv{Xs_Of&@%QHkYyQvf zMOj!F8S8$UMO-`EB0<7a<34q-f8?II6Wdnosm=-&a%b0|5egYcil^?eI}vO4WnGN4 zkp^b6G>G_Erjj0K21LXDqu=j+po=`ulnF$ZUF#2eGGP;bdD#=jizZqABlI)c9=0rjtYZ= zq=Va(k;Wm}?J#CqLx|@_%98S_6&3mXWyHd3WWnofve-CrWH$>XoxT4*9gwq~kfo7~ zSm}*j|AiB;>%wjuHlb~uxNDm=tag82Ga zlsLwTeHXv);rD&~?jfiqJomo-wD$GCM1m{Csn{3iV&{eX9I<*U(rb&p7>Kj04d`#$ zb}(a%*epgsQ?rESB1F2tf1nY}^{KwNETQz3(KHZ^j6k${f%ooL*s(!7ieKW_w{HQ? z2_I;jIa33Zs6Q&z#Y2R> z<-Kc#S(0Z(8hyzrhF0ckWMcmvl@+6yYg9NR(}}Y`nJauP0?f+7 zPaLn(jpll36Z;6vmFmBou%;&bUSD@3t1J<#E0Aq>&?X92p|ad`PhTvOk}uYT-@{_X%o4KpJA9gh zPQk3TM8W7L@RjeG6JLl2+P?fk6;K}vLnth|RXbB}I;)-Hp?%3Hmg<=K28<#)QCycD zpkXq@;xf;bj!%0*Sy;DwVWFtL22~v%+TB{>EASAJb3=&2WN=7z&W{RXm8FJ*h_?9$ zhGwj1jI_|?Fmo0VkT1#FTN8PmgIQZQ2;9HJuo(Pc75RNPU%8`eXpU= zsa^{e;obN1#Ur)oa5Z@~K1?4KszFOB)d% z6%%Z?_mrs=S%e0LlLV4Bjx6L968R@n zV?Y})Ky0Bw4Jb3eV5Q-$ID5SEWyOT}r`;=!&K;&OA>9$)H&6rYk>X*dA`FK)V1sR_Y7T7^MQV%~kq#NvXb?@i#|o1LbCKPkcG!)I zBoAhIL{_~(0*WeSF&W(V63+%Y`nf3Ayoj=3nh=@}K)X!yU~#2~2-y0B>axe{eh^8` zP*kaNmR@exJOSr+5e%l@)RPWIyOml{S55mZ-$G=nx$?y1DOrnf-aDvrIH&RzrSy=< z2H2z%pfHw+H12l?KeT%}&*$ZXg4rd>h{1Q_h5dFt?Le&i`Hc}-+RxMkbh=BL-T`O^ zh9@{4gX3sHetPfh4dioAjl&i5B$=E4L2Vw)WrWvjuKJRjvEKvLU_e7OHftL;0);Si zyQ`Kx1|XAQ#>`p$qxlC2xwQxSCN*pt2ssfkegN-QoKe@>)d|A|;r#-X)=1Qme=lEy zI15?XknC|N82rJ(mt@0H`V?aIb&+EWy4Lu{Qq=%-AxLC81_r94aEGb{O6-c7pXIR^ zE15`>=(Pi6(6J)W_lBA*=LUP+4WV@%@Px@Yz~n(i*L!_fjZwgAJT42`t=;756Ng4) zTV*K$cY#FQMI^Xkf^uZ^)A!-(eTquCI1)SB-dXxo4)4#vjFpSZk)~b7v+pz>(^Y`l z%3~)UFm1Q#V<|g9Ga8D|#b?Jk}y>Dg{5 zfe*}@gr-tL1g`bUpcW;i9_Dr+AsJ=y3@l#tr4`{!yS-$wUGM4_-C44aLaYo8DR)GK90OuNciDbW$v>rx{p)I7f9xKCFPL`0Jlvi%%3HbXtkf)u zF)Z)t8x7Lc(pi5LjcN+TofGER1QUw*^4CAv{%>#!ion+^q0xJ-z74$Ale&kd*PAgb z>br8z7U%C>uCJ}>uemFs@xS*bbf9`4|EvyAi<6RVRQ+w;ZV@Oz)87t%{g8T2`M&`u ngVjx~L1r&Xdv<4*6%X>AYW@SMCUgVsMAQtbx=B-hG3x&t`H*#? literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/file_df_connection/spark_s3/prerequisites.doctree b/mddocs/docs/_build/doctrees/connection/file_df_connection/spark_s3/prerequisites.doctree new file mode 100644 index 0000000000000000000000000000000000000000..43c7f8ce397a59e6db784382ce8555a1dd3b421f GIT binary patch literal 13870 zcmeHOO>7)Tb|ytq6e*FEMAO!;tf(!mb}3uzp+(E9<@zU*Y+)6_lq1P@7NF77GhH*? z&7SUY|45u5 zf2y0D5hcssO9XH<-BtDK)qCH2_3G7|?@s;U*;mKJKR#~-?Id=*NcRJaMJYc-FM7<6 z(;ueizn4Bpm-$R+-j9MLY_pV)p-0+6~w$Ngx%(QtZQJ+d9xQr z)s<`tBgrj?bEL96GHeh9XIE6OI)@ zNqoBPnNft7_&(*EJ^AgBmGsMpmf7Bd6hxm1(~DDkkxwL{lk!VXq@a|~woTu1EK_pB z$KOot^HIX5W3x-sruGs)n8WJGYhe@{{V?!??i(qe^c{HO#Y5cu_zq9x|heaI(=YrR-=Da>864Jz8u@~%@?ZbP{vuNg1QNI~mw;T5A%>(E^ z!Do!ZV(f{e1vDL$uv8&(68JYFEM{vZT3hLd0NY226FHF0x&+qHwM}7dj+OFf_Z}`d zzXEQih#UJi@u%~OU0k3L9@2YFfZD{f?+a?FY=c>&gr^4f4B_GI@Y}l(C zduk-pJgMV7biXaT+qWQyN$~mG(skKWIdAXhFZj~?ER3*t?OM?5o3Yb!JSQGV&Nhd& zmW;WN$5>ZkY+4BLa4YdV78{-ek(S;jXB5teS`0v=r!@07?htykdy@HRU0c&H>f721 z{=bC(mjmA$X!o7i*1lm{LD0whZOwC9p&2?XI-g5^22{*$X!dP|xcxoh2RA?UB&C=> z-oKc>4|5{D%|MUed6cprD4l?n@7kzywl+4x=Sdu;eEdb>1GW{(2-GF5ax`DcfV0xBpx(?~Y0*Ke zcTg-@O}X zNyIcU_nkGYO}M@w)CNJKMRt&QmS&sxSk9m3NS2ueiwg1N!rS2?4zy-78bmrghTUwQ zpS}9_^;@^!*4l_QVp<>K4TZX(f@ne)>s_`t`OT-uNm@$k7pkP-fT!WhSZI2Raq}Z8 z=eYt7<%>~>{i{lB_-;!+_n#s5UqIBG{QUl9F@)_Br8yYY%{KQAv^fh!R}_5h4rAIn zcF6T+rnwcdfi-hQLOXEv)D|XU&(k{4YcB|yX0h0$z_-xRBoR~O@k>=6d?D)Fj=!y& zmX(FdNBRr_j^8!5!GJ39XZGSqN*>XHB5)b+~KTHr;{4hS*cHeW_PMq$D=KE&orw_%@EPUcMSrG32Cv$Sgv4&p?{Jr!z znl2Go(e3^aNONWgEC=x8nZkgBmHt40FD<>Q(yEoMBU~!^uMsZw=(Q|(BbO6sch+)y z-rZ2F)xA6tIyHn)IOSd{MhK_6G@7Z!t4Vu{#hTe}vnbM{cz_r%!}FePC2Kc|iS|PE zUaVa&MhMnURNTRXD<9m^aO%(^guhw4s^mtl{AP1@Il^wyeCNi-ja$ZB zx8AvOV?%qbkfq~O3PT*Xdc;8kE-__CeAYd-aRwaRqAgv zjq@eMZa@7+D;m0A)AZih8Y-Jz*c#GPnejWd86S-;;!$Ahpj3rIY!~}S3)ApFUybFG zIHGD&;q(XlUN)Z;NfTbBRAxRlKTGW8=@B;*`qe_7#%BzADZo5A!jDM1$%2b=pOx>C zi4j3Zk*z7jZLuo$-#SU7pDrvVYrjDD23Dr1`joxQYtx5lk}*J{ijdE z1|+SJ8o|a)p^%)9DWM=`^nW}#BibXiT%xni^H&le*^iyJ*sybf)?b%Po;@(?1hiMII9-eovEmspG{zUo z8;ZSiaOm?)thUKI$|r2DZUg(037!t4JmJtaIlJIv$9I9$6`CMvgl-e_C=Vu zl)16R2HHy+va2DIgLr@kpcDks7a@6mwDpn1zaNCw`BB3JjffJQMjUK0ACZa>Ev08C zksGKa6|X;?W#qbhTI5QdpDQ%DkMn_hmU_!%;+KHO(|8mrpDop?9MfgS)gI(~S)PF{ z(}}MtIx!{k<7I$;as=UAn9mKth2m?jK8|!KXXCypMY74iFce%0ed?{| z97=~VDo{U%dA=^@8NCz#zu7}W`TC+ zf3nm7k~9~zo;lFOT`W-325N#H<@7CzOH8j!8R;6u)wH|QCQL}{iTpwVqt9)=5_tdc zM8LaPk(m3}Ad+u@zcY*2wE{NW(uU^izb8OO1s7)vi6Ey2K@<&VP$8n1g2O#YmTWt- zuAK?Ou5LoPHq&i!H>r0bWY0{UVk7-Z6rtY4x*d4zS|Ry&^WhTUa&DQ=3;LAOM+D_` zG@n6LmcoUkrK663=}0Kk?D%gT+^suiZ+-o1tE;cRdKrJKs~6WUuT#GUF64nY6i(1h z&lx%4gA22{wfw(Ss!}N|{vSVu#k-$iu=^AG_I0(E*eNom0oxNtm@zBjq^^9WVu#Aw5L#kL7N{Qo3 znVGBIBDw#D^{M-VT_&Tx{p(#p9%;^&a#TW@f3NX9Je0{(Qz{+z&*4kBpC_e0AFUK= zDv~*3gfdO_(dP~jDcmA)HCrN2xgM&%tyeR-|3%B7q?P+OkfumhWh+CXr;fjJW2hr; zWK?G75y_hsTPw%#`RWl?VSjf^^=7M{5x@alp?fsHT*S>;9H1^rp{%q~l1Q{wX;(^q z9&tgna5oGRm_IK31G=rxfxqj0DZ>{gs#ZH3{iGneGy9EVgh)1At;DEbD(|Tg=Ey3r zxXP4re^hrNje>M`)$^--%jT!$Rr64yVyW%~;FAw=zed#L3FpMHaf%=KdTsKmB$nNI zGW7jjJG);6t$p1*yC;b%m1XAl$Zuol{_m097ij$O<&o0zyX3c^Jd!XJw#h3ORduXP08F8S%686TK5tPAYTx3|AnNoZ zPi(AI6xv06CM#N};s6yz>CG`doA+TzDaosZE5E=r#}Vc^H8<-jrHeG|pSQE~j@2ZJQ;Cvm!Azqi3>y39v)O&o+N9zW7Y@*fq_0N= z7sML@cG7(pmZit!%l^@1$1d7L-qhI0kebz6X2rULhRDGZwypw706M4jl*EmIrL%&W@e9D)or(gg*4V1@1J+Vj_ zu>Q)q)mI?T`LNTjk*7B$`7h_>VZ0_mc8i6&!{UyZ0}i1e>MkV?8mPUC1aK;gl*QKUAghCLwYABlP%uKYW z+BPb4aRd#ey+M^igc!Y*^eJru?nHTY<_30vo}lgc9bh2y3&+V(z{}RUiJvE6E>jvK z71wX2fK0E&ELbqAbt0QFD#ThsrHr{{Fx;Yg1`6HC?+UJu5oOYN#JrC5X*+xZRSGG} zv-u(EO<=x2B92iGY$G;g8JED#vHDL$$eY`X_Th*huQbt)A~Hht-(D<6G)t#wr;302 zbp!1zNY-96BR)-y^4+rBk~Ul)uE5=*?dhlXC+Jc2}@x|4i~mWmu9oDtwW={Uv=9yixj4^7h~Kjd&ArN5bb+ zq)Kt3&`xfrkehsis6NR1NhPUo$cn^UAiLKJyaN2<O$^J`oaza>BxE zs;_YmHjm<0_>cT;vD@cA7bp`A*&Wd)f9)I4cSx3_NnC4ZirBdlex1>rNW&J6tYTK{ zW93x^hzn;YM`uYXjQKwLO{3q)KzSgAuCkiVkZ){w@Mcb$Rp%&bE_9L1p&~&FH kJ$|?li7?i~j^FkY3ywPI(vO31QHD(|^Z%5zNLu>;0pOO6ApigX literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/file_df_connection/spark_s3/troubleshooting.doctree b/mddocs/docs/_build/doctrees/connection/file_df_connection/spark_s3/troubleshooting.doctree new file mode 100644 index 0000000000000000000000000000000000000000..357ff740f53f41fa52cbaa3e443f9208c8e07bb4 GIT binary patch literal 73472 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 PX<@#?LOT{*^7H=zLWg|= literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/index.doctree b/mddocs/docs/_build/doctrees/connection/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ed3519c981b89579c06c75bff75e18670731fac2 GIT binary patch literal 4261 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 UF>$xhUg8=;jh*fmP)5Oj00iV}z5oCK literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/db/index.doctree b/mddocs/docs/_build/doctrees/db/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..0d3d24be740270b1855b1b0d28dffec4b4db0884 GIT binary patch literal 3763 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 Wqut3_DCK@0Mnls@!@g!G$$tR4XQgZa literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file/file_filters/base.doctree b/mddocs/docs/_build/doctrees/file/file_filters/base.doctree new file mode 100644 index 0000000000000000000000000000000000000000..8130b24d889e233c1bca99573130b56b8183a976 GIT binary patch literal 4303 zcmc&&ZEqw;5%$~K-nG59&v61uES8`H=Yw~BfdphB-cCXwD|_ff07Z8i_0IHecRX)& z_xRSz84*z6EcLfEeB#GM%Kat2^Hk5wdUo$FD12e1_0DuxS65Zn^HjC}-1^7!y(Ry# z-6#tSBV*l9vxsYFH)$Z@sd1mXcRqF3?%1{!d!n;Kh1}UCNQ6Shk>aU)?2g5feM1)` zZKQ#kEDa()mZ_x2nF7(W|LpgBPj!*!nKFUMvJ3sRClfZ|XJxc1DQ&AUZ>UekCiq-uCh3r{KVO{Sf<>auh{jR;aFhIelcR= zO*kcvaAM!W_uKe>2j9DJY8{q)TYp&F`ZY7b72-tfihE+~rTZMPdMlD^i@q3$)AJR` zuiADnLyTB0M#!f1GA)}aa)|s7MojaQv5fs6!_`@tQm^!?1+Qe}?B3!+Z|%=vL5nO9 zKf||g-$!~Ue4vp7Obtwu4}1&;1cL$X>i-0I!qyt_(!7tLJ8$xa=HUWn#X5n50Nw() z<2ht>e}UCKnL^W;0W=ki)_%*U#2a>T{xj#91lIn(w00;?hcAtEb)ppJ4{>ChyE+##o%UH2ReZH)*rhZe&a2GMf2fW>)=xdskJy=SxI>_QCX3{-~gS$2Hg9a z-fd#Z6A31C=N*dY-{8<+aoT?`Xdj@-OLr&TwQX1 z^p{xM%sd?Zt=Y%6>+#sg{vod!shVs+<0a_jO)~in_NpLUX1V<)pCXM-Fsn&XFuDpn zWnMe+T-?|8jhBjmD52#THKSOnW9BP2*mr^`l%=C(GQ(nbzGOSK z43F?yP;8fNH!Ktpb5IfZ&~Dcwp1Oy2Q*&cvIzdaZPXy_EjIqkV87Vkz{dWviT+dR- zu4jeG3*(03!=Y_Yc*+$snR0mXCB(2K$IF(=GAa~c#;LQ*1828N69Vje0fA2STBsOr zJ0=UkyWs`R!ju+lCcNGmLrt0)-k5xbf{(bTMG{QMnM&L<+tsM2IjAL}jz~k{o8<_P zxgQChNF#!}ykfsdL529Y?UqV!4XBsCH9xVil0Xq&An~+7d}0s5otUMQ0xcoJ z9RZRnXfW-8;$fyDbd@>M2HQyF%+RifSvrP~2#1X7Y!K_VOY{)j4%ro|pF2^J;|8 zR&LZd0p_;h4BF+?gAPh}Dlw|eWn=a_Jz*0Nk_RSFNn3>T-a;pib1GlYm+d}*4QT@w z6aYXXjr(Bld$ybN{FXk*nQa;&+6o94_8WDZgkjy!uZ+mjex@dX(;-QE10Wd~p5Sa6vtd+^YX>tscl_l&=?D^(8lBzX#Z0K;1VsYlFN2Az!05?GX|( z31-Ng@gK@MWyp=)(^sis(?H0Hi19`8u2Bhac3Yzko8XTHAgvLo1OHyW25}a$DzrzT zV6X>$O9?ha*@qCTFHm`J{C&u`I~51qAbYYocQ zL|kGduwl?_&=ttH?fZ)tF`{yDAhxuQC;HmL;7qzbC)Tca_4@D3=?4)jLKVQ z%nIs#QGC#{#5e}{fa-hJjC0xGz_0Cc!ZrQF;UI3}T9{%zHm7VfG31GtNme(S+AC>y- g)+{R literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file/file_filters/exclude_dir.doctree b/mddocs/docs/_build/doctrees/file/file_filters/exclude_dir.doctree new file mode 100644 index 0000000000000000000000000000000000000000..b09e950962ad6b73f05f1680fd8e07601348e841 GIT binary patch literal 3667 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 Mpy`@nL$j0QUp-lq-v9sr literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file/file_filters/file_mtime_filter.doctree b/mddocs/docs/_build/doctrees/file/file_filters/file_mtime_filter.doctree new file mode 100644 index 0000000000000000000000000000000000000000..bc332a564731838e27d483baf7666a967c1aa2f9 GIT binary patch literal 3713 zcmc&%-EZ8+5!ct9bSLR7S#i_C5qeGG)_D+5ZX2`^f_}h2&_c+$Fp~DA2C+x(aW^8l z?C#Q*fEZ{C1h9d9%eIgGTli(3l~J`$xOW@SNQBH&%}f8nKNcJAeN)ejl@?}H z8K-TVl1q!%*A9Pi!3ri@;o(>RFfG? zGd_9rv-7j|J3NLj&t`eehhzM?uuNy%E={PC+dR^orB-kR5)AQjavnYTdGzrld_Iw7 z%C93~i+t$C?hB6)2Aa<~B0_{+)NuOnmcyNHFZjKx_mBU7B*8g1WY%gqt1Svih1-^? zL6O{Uk~3o<#B;A?Mf0?m*46T5#*!;!A?R$g+}@9wI1lii-UZ zzaQcEWBeW=s6IRozW%cF^}j@duf>_TC!UD?JMTGR-BqMF5RsUO^UE#hZ@WRfV2s$V zXFyZGf#oVh9s>XIT;}|&P^p~Dgn=Q&nUw{2_l}k_pD~6$q6_mF(uR7G&Jv7Wm)boH{7t(;VE8g%G9PC zCAB6J#jUNWJLn{VW>dFkxV5s((Eg7J%wZnPwJI$yfxP~o7&@+5;Mw(6ZL8Y)srX{* z1{p88W>#sBmq2&RiV83LS}7tm@O$mu=ES@G#)TO7UO{2lzScU&+o4rSOl^ox;Ytra z>l^Rx&taximaiPXL32jl%esiK=Smm;jT;&CzA8>sLDS2?;KN430R@u8ucQ@m*9vi8 z`$5|qkrvIve9HMtj&yFhp+F;y+t;2u=GPKgw9M9KDea|vR%f_w^or>cS%d~APyxvR zhNXZcy9#*{{xw#PbKY4`Z&i(y>4T&ACPe$koJpcdyC(ij`il z;@#=m$(jlAZ#Qa;E*Zp zr`P~0zy{YyU}oz2i7Mxa5$TX=hX&DiBUW1#FGX&Py4+z}7gfB#BeEI<5>V77%gNxq zAPGpP(l14o;dQFw>w?g91llRXyFG#Y1wrwWF~U_0N$Nk5$+F6!LSAXwFadP5_J;Zo7Xs33CkhIP(MusgFh&ILpEeN zq!3?U)fKLw-8GD$wg9IIWW*Rh5OVc&|=rr!or5TSi?kGH?A<7croKC=z9YX z1m_laJV>B*74w40JZ34@-mpl$amVdo1XlBT)6m|!ZC<|g7+!9#X(iw;kcfp-f*Uq& z_JR=H@%qwCtz4XledF%mI%+nw*I>rlHhN;{migkGb|BjKXso>O;wdw3mkt}*V+^CI z$c3!kfTZ@OLY8TtD&C0vZ7ojed5WPb#q-)7u2z=x++iYt51Ku=r;!kWJG~~TRY{|V zIsysBOyvu(1l9M}gbVG#EsKNh*6`8&TlUe2we?`ugLPZ&3FaZ%OQLFO9vD7}%5#(f zF<`pvK5gaSAiA&pb5I4lCP;_v*r2mD7cRQYsu(NY$lMt4t&=WBqv$QRqXEpZOtVV7 zd-@mm$?s7BO}HC3s2G1pM1FTY{BSJI7}IaOpjaBgU)q_(lP`yx8yn_3He+l6U+zH% zsy+O(x+Qh%P`BW?UOVVE-43AxeEq}p(`Ph%n*aNuAGUjWkIZg2_T2s=M*;2ovP|+i VMW<64v3Qu$RA?J0ZEW={`VZJgrBwg` literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file/file_filters/file_size_filter.doctree b/mddocs/docs/_build/doctrees/file/file_filters/file_size_filter.doctree new file mode 100644 index 0000000000000000000000000000000000000000..caf6f879ba7c20c7c060cb64e4acb80b48977728 GIT binary patch literal 3672 zcmc&%-ESPX5!ct9bSLR-S$5MR5pqr7)_KsK+}3De6#X&~G!SwwoUfM}3*Ij8$cVe# zl3ZIVVjw9Hzy|mhwvYW=`y1}=-RbNGXkQ{=rQP9hI5YhHW=3Ck{`v7>BmVe7rqjwv zWk-e1xD9@f1UWBU_%3|#O?VOR`JQ3Vt*%VUgWrHgDx}H`FT&GsPi*+NY&Eq`I+)Q# zlJS`=B)u*Sh>ri)Xf*uFR%NM;ON7=J_N$@H*_=OoI26v6_ThLe^ZCdM$yH`YQje!B zoAdGAU!0vb`|uifo=#Q8`$POZbj-}TTbj@XcWPuf%bef{BpTwy_-u6lm!k*cxP2^( zj9-p~%T+&!-KPN|^bDVIM1%<2sNwYCHHTZ>PV{?K?;k&WB+)tFXU-WptsE|rE^b?9 z7MJ9AQ^l->5bvFW<@MWMR+Y<-DN8Sqg{ZU5lndh6Z&yYJ|KR_+py2x{D>9j}+8e+5 z`yf8BmEU!2PR9mu$9G#;9sJfzwLX;f-z#d zngUJT8kVaNxefd$GpYEgm4D}_h82K*ji~<`R0l!HEchF1`=YOZ#Q7aMU;F{j$o~o8 z&iU8^0nChDUXEi1#zcZK8Hm5g1;zhR5!s z_%n`lZu!1InM<0_jz8j;5?OT2Rdy-uWPDo9aopfJGX=5;4fK~Bk{+%jipZTwhGMG1?>o(B$>2qswj}g%iWeI;M@U%!QfIv(!uCqqZSKwY=l3e z;(|jgH<6gUAZr=!dml3q?y3Gf-}FaBHo&Hs1BFWF(uI$2{@f2rUM|ZA1#>`>DTD9C z3;*M`VL@z1<&_h<7-=&Ho$iokbO4%(<2kO!;X3vpKfQbQE97%vX6c%Fk}S>ttThkj zGQ#TxHzUd2ES`aCFs3;OhxHtrf3HfNae{#Jp~7Ku8J-}PsrbjnbzX%0C_1%p4h_?m3Ua!et1UsNTIpvg4$ zo2CZ50(x{Riy1J`6op$T_ikb zQY9?IP#edjm;R{fg21Yp)dlU9-{!@$fPUro>QVyk0*M$WCAi^|y6eZ_j@CzFD&^u> z>|1~9+EP7#y#O=TX3b+ur_2`XG~v)RM{VV45RaJkyR=x#?x7h?gc7pyJ(9Ys5?Q8c zDSs){t4f^E`viSdiYFC%W?OS%mGt~!DuEB0-xm@haI04bwJNFgaF0MjKGkXgmZ*Ar zO}NnRU9;G0PmP;yU9*ouY+L}d?yTEtR?rVoUQ%6A@j&xQwNkhS#DMOy`>2tBhv>cy zPe2t+k{}&sUyI6CAGoM8t6~gr1AAq`w^q7njiR#{g?cc@=7yEx_0_-n2Yk^lez literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file/file_filters/glob.doctree b/mddocs/docs/_build/doctrees/file/file_filters/glob.doctree new file mode 100644 index 0000000000000000000000000000000000000000..bb1a6a7e656d4fae4ca6f7c58a6952c1ed20089b GIT binary patch literal 3608 zcmc&%TW=f371oWENJ+G0#Z8MuD3ije^Pr{NHc25AZ3+WP3!!viH0?`_#q92oGtunM zb}qIA#6VjhfDO!Brg`jd?KiW#B*nS`nwJPzY3J;jbN$XmfA9V4aI_JB+^CSbHC*VV zk_FY??FB+fE91ZM@BhQU@DJQTk>^@kl~eCFz>za93Pmgb)IVSw?oDkoZMcCLS)~P? z@rsAng#gxb-zLfUD{bpqDw8rPuk=@AUXnSzxEM2IYJD-8@N%AL#;GWD!sR3*#hgy= z|Kj|ty@%I$^DGmV4#)VpFhtF%S&EPqH6l@z6oyea5(Uv}a-KZ=W%9u!UZ3!)pw|gA zr5Jj)`_#jQfub`CkKkeFHH<#IWpKyai*~Q_{qu+SBpT<2#2CdhYmms0xNTl&B+2dO zf@lpQ-g^}(o438f*2_ai@+(9k@@x}fJUemQR&nn>_+AO}ZkUs*;00-{aht#M?0s$B zt|9ZF*0X!A-@)qOH%gndR#M3MZ@t?QyrTYc%k^u5w7{5Sn-TUFoMNZQ*iZ4igXd>> z?!&1*ERVK+)YNl`l zg~$=`pU#EM&SqQ~sxBHd{ZEH_aEKS)?XTR4HvS%ZdqFSz7*FCp1gJ|o(a1be6I0fc z==mgoe*#@J{z@CIdJ@DX2(^!f?rNH^RVwSZCDB7gi(BQe^C>QORj~7CdL3GXYp7rQ z*eYPyUmL@w?0ov#P_`)xar_+YPa1ai7=G=nUU=Ykg0Y@WX_xMfbUP|^H8r0tL(_m_2rH7)2^ANbG~a@?25f z6k^XvLxjKFaKny=7C71vhD|rhtqL$tTUk?g&{1L`Ox>QQ#_(#627Mgh7CZJ#$;!|Q zFzNq=peO1D;#^-^Q(NPw?DMG`%xOgxF;aOPVnG^G);R1dDMQ%-Y}ejxP5`*ZgcN$O zz%Xo&RtcOAjm*=~D8g5m(u2+F#=E;Sh^eHZD<5B?eIo9aEz|3nRHc9EMjG9zPIFm? z`Jy54t%gDcjg<5`H!SU%74)?i+U|w0C>ke-pwB46x#flottxGgJ$Fp6IihHYv3g1E z6*RMRs2e>eszMaOff-SPGC(?V8e9tY@d`0ZF$QD-F7F1`%+7v&ra9iLi|< zV=81$+R&cnjKCfwz9}|DIff9ouWSt!VY`fTr0oH(fF7CiY6c9nP2q0n5@@k&Y;mQ6 zF49zDSO~5>AX8?vK>6Ol0!FEUj(a({u2NbOE>copF^#LvOLyE(H(*uFnuhkuZPV(R z$7FJQO)Ej|0*P1&IcmeC&E$?t(DC|O8(KL#Vf)(Mz4fWtpkd~l8~EH| z&Vi4xgF{b42?gBoHBPNk8a}K~Kth>Gu>ebCePfNd;2zwPIOw#-OLuR{M9YH%rGEqOzV=U0D%b{5bl6`tI$NV~(PdW2Si(m7 zMx)+3>S8pq-eScWKpdGXQnUa5`h|P=2P8lR+>IMljNe^Z#@-!sJ;ulzFY2mDU}V^W z*uyV}n;RSYI~G=q{V(^x1J)k?tnNMCveRufuGdbtO}7&ufg=6m^pnS7;xzw~p$WF@ naxaM8ZsfWBMPMSt_j#2ITcEwkdH6SAScFl~cFnMZ*(~`FZH{`| literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file/file_filters/index.doctree b/mddocs/docs/_build/doctrees/file/file_filters/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..0a752fcfbf189acc85e473f403b9cf255d0b1935 GIT binary patch literal 4687 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 HZaVrm9#or8 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file/file_filters/regexp.doctree b/mddocs/docs/_build/doctrees/file/file_filters/regexp.doctree new file mode 100644 index 0000000000000000000000000000000000000000..d970b9110861e7a1392369f2e189a93d27349e11 GIT binary patch literal 3626 zcmc&%TW=&s74~h$p0PcB}7CP;-Zxh$YK_)2)ty~s_Cxjs<^tV zsmrb{WhFq0v@6wH3Lg2bd{x~&Hap@!$B5|K?x$M{c0VOD(O+sCQf7$QT#7qLqK{AF(a>uC_C6xPcg1r8%AR ziig*Q0M>K=Op@_e+SavHCS_9I=SBn7>?8Z%>ReKnczvPd-JROCA0axx=%K_?G? za(U6-!)v^GF%y;!$N0H2L>1I5MaYU8ktj-X!zdhyf@n3lOdfxdd^(BOC%nq(ZNf|` zhMw&|_i$mL=$yhMc-VOjqmS1N?s$9A?p40O|NlLS#=I!-J9E2Maqm9;pAzKVFe6pYbJAGjwtwl_$J)Am zLyDl*vj?uzrvHa&j9R_PBgMl)Wnqa zBzivy1u%g&8h@pYUOj09CJ4BX=I(2nZPY94w}sI|U5mA%=s$~Ibk(r;M|vAth3n{F z``9#K-k%%urtEV1)}Xe@3laSs?9Uo@_7vXjtzLK((i-= z!R`kT1b>l?w&L@2c9%v#zVfVOPqcgYtt?0e7^YybGx{x1Y$45_x_68sl@=uSMPR#7 zR5yj$Gtv;@ueaQ=!q6oQ{pm($F}cUc($JNUuhC8s_sW*(?OdwTzjh;yZdIq5 zEW^Cf5cpn0p@K$A`jQ)#cFha=+6!$rLs%4z6GhM$6ye-)Lx$Fsw#S}3p|>1SG{jiF zr1o+;vjx@eyzc zZA6f&uo$=qcaHN8#2^>&V6%j$;y-0~j52;_0YE5k})7vtj=|0L%YMQRzj1WP!zTlcSc)geA zT$7myj3SM&Ke}DhSZdB8H**=0ZM}aaUuszg;`+hfsK-NipXPB%KsS1?SgTNUL z0B34wiS!tx;{f!FhnGJ^?T*wu+YlcpOYt9d;!(LI;Pr;8gi|w*I?xR!VYh*@fgv-L z5OyJdec;Xk$TD42=A!?lx(*0=Lb%8=WX@q!6F0PmiV0ry9`B-q>jVo}l>fjW!l z=9mhZkv6pF86&Un5co;UXnD7S*`=ymlw;oC8+HylH4}+%BzN zc+4kv(6kcNE|7>7k)t+D+D!7e6rF6YxuKP_Gj^!m>Ds4er+S5&+t?q^bkH(id=Fbo z*wDgQdG6U0qTPP@*w7wf7)_aA+`2)an!6go4;xVVnu#}-orm`+<|b#)tUF%KY~XXp z83#VXP7XZ{B@}SS*EqFGY51^C0SVpL671@~Z0;-J$SFP*N*M9YT%rGEqOzV^>hD%cKDbl7J#I$NV~(PdW2SjtBF zPNUvB>S8pq-eTDqKpZI)so8&i{lb0nTO>dQ+>IMlj6Ymk%|0A+J;u-*FDfffU}o5c z*yAsU+gn@uTUJ;M{x1)}1J)k?tnNSE%G2#NuGh}DZMPRdfinHw^fyn#%xV5fLmO-t n=w1-J+sJc=i_k=nAMz>_Hb;Y#Mfk^Hn1^A|_RX-1*;(>$hW3LV literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file/file_limits/base.doctree b/mddocs/docs/_build/doctrees/file/file_limits/base.doctree new file mode 100644 index 0000000000000000000000000000000000000000..080fddd4f347c43d98424dfcd8a59066cdc37f62 GIT binary patch literal 4278 zcmc&&TW=&s74~gx&)6Q@>udr_EQX*3=fN}HKm@W7x0MjcViv6kplDaEn(msZiu*=Y zwRfzn5dj6(QoW_%iGRSKh?M*#-uX^-_e{_1Zcun(q+WNQI(2S!&Ua4jpIU$a*TIVa z*>04Dg^{uDr&+|cv)e>Scxv3I?wya_jXSYz#h&V{P$74A1stJ}ainM&CQE~ek7X+9btXWx>_7Vb-ZNd~d8SMtvg}en>&b*o`1yHH7?bPs!9XUHz7~?l zk?zZEFk;b!5AMBtc3SPjtKWG#iVNQ9;dgGBns750k)_oU?fB)b*HOluJF-bz_gzHLWEKK~f8@Dfq*JnJkrP8`|wLP=-e`=1WT*-psP zNJgx*#;*R@iFb5iw+x$*S|{$>%{r}if2gzxa+Sr|Y7>@wTfbl1`V}+572;Ivi~C~drFxE7t%~^CqAv#G>|zc4 z>$V-tDn_gqBV^NNnU>8I*+>2dBc}OjEE8$Y%Vc_`KP~tpBZP3i#asAOXl;>J@e@3K zODUc3fyTKrH84p&@O}>{^#@}abN^4^b!t?U=6%H3c`LUx50}bRY*IZ?O}9|b@j@|l ze}>&XnHsLf40Tdf&c<)}l%T>cuYck^c)-}-mBtRm+3=;otqzeQ`##tomF(gneBCHU zc3y_OQ$d);fzEl@h;-?~t21T8^V@^RzyIj|%}4Lv)PH?=#gh6i z)W2S-hnsy?m`oQ*!qm01hmEfQ7A=Nrqk~@^q}K9sWhJG`L}f+(f&+90TX63ydbf=& zPbI+5U34g(e}zMT!D;`kL~_ykZ{cYO_@F6(GGdfP*Do0Q%1G1hf0pX`E1B#D`G`U% z?vKI!$K?z;u{(`8JKK)4bW(ggi{?N83Jmrd+OgVh`eqmUW<6}o<8FdAK;ndmHfwMcM2|%>;5)7T{vrsX<_DmK6 zWW!5Zg()rAOqF_P3^8eDcw_N7x;x^Y7D+H2XDV^eZC9gt<^Yw1#vcuVZrIM-}PppypGXUiV)sYDbFGlia0TM-`>6R7JxWhzA! z!GRGb0i})O2syb#_Q@163oxZ*5x7tbg&Cjz;8aVzzqPm+aHTR82z20>E1UqC8X9N! z%g8sRw7e8&kC#@KM2LUeZYgwbK)v;?#f7Dv_JABf;%R~S#J>3UVwO${%zp@XgkoI7 zG-;0%4>J{Egv^mPP$Q8uL%SJf=@>pD95Sl2L2TMC0Yfk+*)?jJdr^_(!7LvURZsAx zn=*jS(C&MR&vZKaIVsn?h_V160j>j-U8Z@^-04#U)p{khEc10gh@>X)ha;7hTXmhF z=62x>X5-Ws9fa;xYymZAKWn{CZ%_g#@`cG$k{02-cQAJ2oXXFOReMOqhO`0xiL#}; zUmbkMc5|N3;e(vnB?wbQ1M3(k5H9C zXWv6SyJ{RRi6_cj{I_fIs9Z+*ddXE^ax?ZifCd8^wV|vH*hUHY?zC->k&sC+tIQex zp`lZT+}Z5QaR$^pfZ-OGe?@VjA!dKMAL+Z6wY6ble@DgSPO}%J7 z7*=8&1AIVt*?LgXKZSRnxJM`zTvkwYxL#=>Td8n{GEFjuyRN?0sJEIrYmKuJ4VO}g zV-v-4@#5-Zd-e+)fTFzXm08hy+s`O|)_=Pvbq^h{H!G{C4;yJB7xzD8t1Bz|ORiJs z`X2`10qbM@`O=B1RW-L&yFnR&8vW()=MSmjl>cD>46H75EfV`dDbMcA;^H%WN2Xz1 XL_oJ}LYLG|M4g}lO}Y;hqyB#Yn8|ry literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file/file_limits/file_limit.doctree b/mddocs/docs/_build/doctrees/file/file_limits/file_limit.doctree new file mode 100644 index 0000000000000000000000000000000000000000..824a5061742c744e74f514d678e92a2ab22a9796 GIT binary patch literal 3667 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 arTS*IEugewi;f$SZ3z{6+Wo5>bpH)D+zhw? literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file/file_limits/limits_reached.doctree b/mddocs/docs/_build/doctrees/file/file_limits/limits_reached.doctree new file mode 100644 index 0000000000000000000000000000000000000000..7d1d961b12608c99cf4b8a9e1f7e548af1ec724c GIT binary patch literal 3663 zcmc&%TW=h<71nK6+Lg4nthi~B2-&7^t2}5{ZWFW+f+U55Ac2r|VbtwQjRALtyJW-} zPI<8y|@@MV=8?y8u4P5XvV3?b;9LnLh>0M z-TVFdS#uAs@#fh?SlS=r=fV&*qh=vOmehzuQIZ=*;Ybuj%h7pq{}0J~qj-J9%bZ>( z%oL*U+3ph$7kY|LDLjIQt=BO6aK+%3w-fDN=KI(G-;-#Z>l0%XpIC!LhQw|2Tq8+t zHxoo_5b@qANm0M;<+fUUOh|TxC`6uZB8+FpZrdvE-FyF2g1qZzq|A9vYHQr)hn~Ho zt=lzZ7Swun*L7Q19lWo!Nh>9VoPFTkj^HKrms_q|5u^pi9N7tBZ@?*bf{eX|=ht|C zgXbQc>caA9>mOTN|4Srz%g)#>cAxFPQqM7IRRz8tOW25=Uv7ba+x60U#jtHV0h+os zESDj24E(2p7u@JGz%v6nFKR&jq)#3A#B=WktJk8Xe}?u>(9S->leiB7?ShUp@=w&r z6xAsDKMEx7C^Zr)(S`e&5p;5T`%V+7jBR zgLg^%UluW}#k*h8>k3hT)lEal)5%R5@%r4ef<4gg^;fbWRbUu{!A|JUAhkg}d+1&> zij-QA*fs(EnWDO`+m4Zj2!FZZ`YjL5@o7UyHr*hzDpW<<$QrwYmJ;h`?DjM@hLblaJ${IgrkH@Yzqa{_uNagVn>(`K?!pFXnGE^ym zdF|ci$h-a81R$%t0z<#~v`XOX(8w$eeI=a2ln!iGH{RWuLQE+QT{?V$Zi~2=wn(q1 zQWgG%8)!7MD$QgO#*v1=H);wMG*Z%M+_1FmZP3?TXqp|uqG%jAfpqDn*&9H^B7lpfNN(cn_BkC%v9im4%UaCz6WW_tFg zGtKe-=IUWem69rD=!k+UWFVND8Sie@kzbP1iBh~fSvy%1!T#e0wa|qD?bSC|57utF z+ra?iK5^tuRWx{b1vnx@M) zBScWGPr2qbUT>v2*JL7sBaM~2ZJnUz4&V%Sl`<9`gdR3*X=svh_WdHfL5Zd+7A7r& zv>f@~#~Oq@)t{G}?kE%+U{lV3!q`*&=eK|71{JLq?ISpI5GWG@+e2NrU$!#}WSvwu zhRHIKYKC&U8#to@;7koIkRF3{?16rE@BG~`4%9ST6CWrG@xN`wqjE{W>lIZAr)C;; zpc{H}pxLnbI8tV;fJ&z%5}MLMs{dH+jw6%g{$-PSjyA=fG6GbZo=ygNb$+#l$I zV6FS7MM-M}>NtMapQ(@;X+nFFF#>y#_`28- zG=)2%OQ6NBvBeDxx=3A#VSl*tfJ`x=Im-727BET;bllCrb(PYBaFLQ6du-f{Ubv%X z`T?tAS~s+pZkv`*J*Je~t6K?b7f8eg$x$07tp|O?&C&W699lU$X8YRRSvjg#uIH$X zwe|8?2QAb2d01+~0vE>06VDzH?RLXqO*_Rf8Z*JTb-h3}Hx+^(7Np_@6E7`03GZV} zPtG1$cetF{z~>G#4t#`_9eQd?DBzZ_c50bY^I_Wp5{iiwbFf6#*Vc%0?%s;TUaK`; zy0ao5jaV6v%DTHQt2x0uM0?4k4b20?CzC=T4e$ZeW%oft|29PI`^!!JjemrC!3K%S z!_KSG-fFG$Rb*qiv9TXAMV{xkp&gNH>{yCe0z0! zdwau2Ln9)`(N|0joL*lgLIAa=W!=l18ZiBR9?WhQKnHYaD{zlVMvMncm)!(L`5$qzG| Bk$wOG literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file/file_limits/limits_stop_at.doctree b/mddocs/docs/_build/doctrees/file/file_limits/limits_stop_at.doctree new file mode 100644 index 0000000000000000000000000000000000000000..d4d85830c2385e401a7d29ed4b278366ef96d0bf GIT binary patch literal 3663 zcmc&%TW=&s74~h$p0PcyZh9sbN$Z6f9?F^SA(te;|ICQoE6fH zN|iGc{6QoXthC|F@ZsOWt8ngnn!Ys3>5K)x1&)jhnQK;t=i!`h`FD+*8Y?WssFLJt zCQ1=s7Xo<4|8q1Ner{Yc)IN6a}5LtE(ZmwlY`au_)#v!v&MM83{F>(tOUw zkA8G{(cHspx_L2`j`fH5xw2HxnO%xdC9`s*8O<$ca3l$0<@j>+_{XD<$Lac5lsUT{ zaa+iK;QP-5TwWAF$bwqC>N<28d@-cGW6mGAHWe@~KezE7>yV(Kgs856fFa)TuK z{Y+A0K*W2eq(%L9kh^O6F{RlJqL6rYskDKg`dz0*@E`wA2@1ZS(J~h~t*!Cfp9KD) zaem*@c~l$tL*H#-b@VfBEUC1TYX0-!_oOIUxZd&IiXtsA=EO}Ye-BRaGi2<$c)o|{ z`*M%j+HR@A@8DR1DvBQ=q9^ z!*UfOr@%jvq7c?x7^|uaYOiWQeXUO&_$2b+2kY08rN4ysPSnnSfoJ4D0kjJ?HpoBK zV_Q_?^Am}*6zjm7a?^MSpBEk>IuJ`yfxZQR%56?1^cs_oj--sd#e}D`6NR8abPVV&cC9y zCA3io?~3@}FJoAX_upZ+6{0{E4IwAfJCgAFD)53oG5+1RvLIDpn1I1e**74yK|Fuz z-?5sOM$*(a0sXmVrmowLRhG(dz2*BY56kiCVo0|AAagoa1!-hW{839ubu;k?hFL4h zIr{lYL}zO7Gp$O?NZEu6V=0el#5EjkSz>(|)LpXPQpQE#p z=Ck8Z*sVYmEw#=pshyln-5lx$FR3mOMQ~uG6rl8wj*LZ@qJ5%7%m}82%E1+U&)M0< zPcMwX`}^yMglVlb;pm8xX=EUpn%Us*)sbJ3(n(T+KifFj5W)ZD2er_p0qxcI*AF&s z`uouV0MBs5N5Y}tk0mYV4)Yk{&hfp2>F!T8%aqPB(ks9QY6LJd@!d?7Gx&&bh`dDu z@A?6CR*@yi@5J_XoV%hT3p^sKi68()T~ZkZ-b<8(^qBf3sWQyvirf|vP4`iD$}qBe zGeHE^`a&2{Y#-5?qez)j*dFV`f4iMgAe&Kj zXSpgzO3zVF4vLK7fT=YL;s)&#`{=T^@ExjdN%(=t^@a{xQs`3Q1^=JvMDdul-3g z{eV?Ds~g%Izst%O0aMB!)U5=y3nXHL6sQeL>Or4ybF#Sw$5zfy`JwR-){g3x>lG?v zW4$~zQOj&`9+#T9z{RohJn$#f`2BcT)6OxBCR}pid@oY%U4`Js1*v$=C zQ}AcbAFpOM^7-RT03UH>hn|`e3%KR0om!>TeAu>tgkq}X0xXI3oekoGySFB>*J@3d z9<0enBi1&cvL0^AYECc@(Oxp;V)MZ8$&{2x1AM@A*?-c|zl;(4_w}~E zVdphyZ?)D%t63#uaT}OBgQ9Dxi}}bqi#@0ZadfU}#lL#`XaCV}kOdvVH>{yC{9t{1 z`(P-{5TkFnsIPn!>dlBh{;a>fwPn6x&&Bxv>;ODq?cmSqF4S&7?RMik&6L~rdl4Ea z*56Kk{WMOV`ad}|!)D9wM6tWIJb$=|O@jKNC^PADv^h18|2_2cI1-xf8TT?b9sLK< ClaeL? literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file/file_limits/max_files_count.doctree b/mddocs/docs/_build/doctrees/file/file_limits/max_files_count.doctree new file mode 100644 index 0000000000000000000000000000000000000000..1843fe153930c5057d5527321c8bcd8d00ce1dbe GIT binary patch literal 3670 zcmc&%-)|ea5%w?1mTW6=oHTdH8C9=Hn>={sv<+Gqhii%ehZe>uMbK+s8Z4+?N@UDk zZuujTfdps^9FPEfJGPJgTlN}FN|wXnaAx>@Gn2n|{&6wbh=1H5mzgzO z=%kc6)!yv|K|xF7zw;0O=HL1UuBXUrEv?F^cN@^i7#F#srGMccunl)t+le;Zz>F-@ zoKAVk!{@>P>$ra=$?%P~RV9^4nUt6M&5##lM$gZO%$Q1_k4C(hC7N+6a-DEFnvi@( zNB4hrcG{f7XFPd25tjCc_;YTEno+YfAxmmRqA1A?qX;A#qUGo;dGzz-@hBc2@iM3L zgqcG0J==ZZ5kgPVDMdtxu#Fl{A6|2~)$K&TSM~n>|Boa(=laAL#V6L_A;ZIM^IYSR z+-@d_))3;mQ<9?o+RJUVTuexIi7Z5&Z6b_k$8Osy?%m`6>4CiKXQa$|PHJ!5=C3__ zsIA*IWEQUV?2hZUusZlcX_Hn;3OW1IyB)zx>Myojw<35JICEqtgxy0>>;y0NL;QY( z-;eRTkD$8nJo@^}*4O_M3Er|(c7r`)`yajMn6y^~y&g;0h@D+*L4Vuz(gkDKww(Y? z-5Qpw5V;Nfrv;gxPPw4^G?TV8=QX1KV^AFg#dGg&teuO#{s#AV!u{+Ceu;Yma2Ir> zfdHaLrl>|S1EWBKQMfezl?q;PqerlqMo-e{NrL42==!dv*&26Qw;@Rf92eIK@->-% z6#{x1;%N!8^9MQ))x#ZL-~QIeassFSR69LpXXB5iyLr}->u;d`yjEw=5cAIJfd}po zk;Fh^9G;D7i;WNVKFlLsebnDClUZNnKBe;t*+>_)Ax|e)X@u@u&kFWbySpD%!J|Q8 z3C`n&)!_v0Q;lAcTQ}>V-MPu&~ z^c6)qw_Km0$feD)jtlhDv?EKpt}^1^za-R4Iu^pc!`{) zXdp6&mUlgCrl-F;)g0fSUEfTpQc|T1#Zgd&7YMFq20X7bzhb2mt$25`wzFn}{mTt% zqe}>s{?rXBS}n^*2<9M2CIr3* zUbs)%b_KCcsw=}}nMgGQo$dt9=m0cRLkm2Q!E@|EetQ4x7s%&8O|v!gL9#Uelh!<# zO9EamsY*CC(|8A}!6-~YxU6T$1Qf#5@B73uZ3rU#hEskoWGUzA6n`2;(y* z@B?^vgbcVp&;`MS_ZJIFYb5G8uIptgWJb^|LTXPkM&J(~z9t*898-wXm$t$cVJ?mR zrl|q%fgYLhataJIMd4Pc5-722YH=9DU8H7W7$q(}AX7|e4*Fif14gOA9d|NlU8b}k zT%;t&NE?TwckZa^g21Yn)&=do+ot6!kACI$>QVyk0*M$VIk;icy64B>j@Ad_P|Ddc z+t=>awXJ&kdJAT(O`69#+%jG4hxsPVa-prf@a!qkZZ~Y!vIl5JV;X;=N@j;d_ie%Gq=44p(0`=()p;10P{}$33+q5V+N=gIbl;dU!`5p_oXq083Q8 zy(U~}_pVv&wYSDYx31YoAy&qNS$EcLH9P2sC@-<=ha$S4Ng?nIhymSYH_juFe~0M4 z_0K^S%#k1+CSQ%pR$sWNGOJ>YZv%a$!M9erXpOA17=?N;M`nst?8DtZyT`x91E_$z zVU3F6=NMB~N59X9To2Lqh6`@x2^wmci9jJEj&+0_fPCxBr z<2p@;+jKht79i^H#$P=PU8nvJ9SUJHY2v+g#ou-Q_5NTj{wZntNH>i8kCoj4acf zPI<}0>s$cqxSx|`_^q~8C6!5;lo$HjAuq^`o}CSuF_k_Wjd(FjG~-m{I^l9OA^D7s z?!0$;(tL;4_~pq&SlS=r=gbf_qh=vOmehzuQIZ=*;Ybuj%h73a_fN?OqxkuVmpQ#m zm?=cxv+YM7F7yg)we&83Oze>dcZISgPqXd!DfSTcHg~Z6e+bJ zu~7o-Gi0N#)Q*vc2!Foj`YjL5akL>E>u!))73v^uWR2ZkONo^-b~}KU;pGe+{2<^l zw)ClzrJ*HY)cprR)6{cFx~{aQvc`|u!?EklXh{_@Qh6L=#TrsnIP5AZLtO%lm)@<9 zyxXl!Kz_>$F!YGB!+E8IP4UDiK9+V2Bi;^pK8>2A6_;yhO}W%n6x;%e$U6)02-*G{^hv z*Dq75lvF804-`}(1HshHcz2_Y{F0PTl;Yjd%FT)h_KO?TLKgX+ION^FAmz+r0wdfx&KsEL?oiQ8svJYS0&Jj$12bdS&15-+j|d0PTQsn)8xU(` zx*)lY(6aV(TU6;BkBDj{a6nO&Ly0$$noLA+q_J|ltrOJT9-P5;QpTc#(EWxj4XrUwyFY|CDA82K!lY%8mLuQ0 zSbUJD`gp$X4nna3HsuT`j1ATQ?dCgfP|<49K7uoQfifYmJ=BGJrJYJ3>!i9eOqPjM zGnCWqz!?nyXKHAH^cbXL2lSIWr|%=412xT7#0Sbk{M)T~R4xg4y`U=L)J&rebc0b? zcA%_h$OI*X<;b7!xFZ0vNavL~?|-ST0z#gtaKcV+ir}g{`0>te4EYqiFYZe5d) zMy!lSW!+wt)!bknqP=9&hUS6clSv_v2Ka#KvVE_i{}dwj>-oC=(LX@FU}r?-VawHM zZ?)EWt63&vRU7CljiPI*^ZCd+^Zlm>ab%`Q#s2;FEBD^#$bt&s8`jVmzPY@yy*cE1 zh`~3US680+dM9FczwWQEt?A#{Ycc%4-T@C-JNUD_1+}|RyW6-!*-$uF+OzRk=JSyilB>*)q#jRM zHs|Aezdk)_&fznjJejJB_lNj%=9rmtw=|&(?$pR|mN~%@NHoNY@#*NnZ$=-Cj`3pe z;`ej>eu3XT1l5J-(bwO#zW$d;2$eVyH^l?7_u6}oSbG)e^~6Yw#p(GC=x_O6vS5tZ zs-{3ww}#~^MD7CriPMg$lbKZ9o>=*JexhV9-B}H+AC0Yp*kl&`o3B3}J^vWqbjTa= zNBlEyNfZ{C@b{}t39*XrUC z!ropzaNiGNFfkSiC5Q=c(emo%kLHxGKKB>P#MU?ZxA|p>#3YNVktNfsB!c>R5V?40 z{ade9!8<}>0)=YIe*(@8EyN@LhBK_NipAOre#{NGHMu)6(r~`v`>hVou&gLcn|_d1 zhNzP?wkCeRl|*t){I2EB$zqP8e@GCHU0`N(;dlYOb^pvzXYB&TzN;%&RxV7$R}Hh;P+}50Jq`-3jJnT8HLq>(`iD*hj!sg2R@st z;BU`hrqGVBY`#EAM&64mPcCQLItGZWuUbumMXNQcZ?G>EPru*&IVDe^a{wjE?uUM34XBCAm#0Yy_VMFww1Nklq5 z{Zdp~US&GD%n41mKs#-DvU)N?1Z;gOt*r5SGs&c7Q$>L^UhcF!0q6D+3 z8nwDaQWJfX3Jfl>+(csXf~;kD?>)>)c&B=KzUdE%Y=BKM2MS}o4WHlol^>M6T$T?C zW}hTe2H%Mn{%36?gV>JBD<^a@(q;}i-675B05lWFb3Bj3bL>KXa_{uF$mhV!(lzrW zS(^W4YaYyHgx3phMv}W(yaUx>O!E;g>p3t&*J%Fs0^wa2Mo@COfHlMPvpDa7fE zs>BsEvBuWZ)PR>jk4|MV0|uI+aGRhc#zg>R@+r?i-z#_^ICr?? zP71Aygy&4Egk>0ZXQQ7JX7gc6ejKOYTuPpf1N*AqBbQV)l59Zk1uu{Cb`&a+r zr+5HExEt1}7`_*~_$x!xUl^)7Uv+QC1o z6H+@2wF8dtG>vZ4ZxcGe*Uu)OJfh}P|Bny#u$jg?WOl2z=l2#lifG@HMXIU{l}^uz R#r=%BLQ_C#TC1j`{{XB`q51#- literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file/file_mover/file_mover.doctree b/mddocs/docs/_build/doctrees/file/file_mover/file_mover.doctree new file mode 100644 index 0000000000000000000000000000000000000000..6390f90c3c7d7ebb384956c30fe65c97bc706b53 GIT binary patch literal 4268 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file/file_mover/options.doctree b/mddocs/docs/_build/doctrees/file/file_mover/options.doctree new file mode 100644 index 0000000000000000000000000000000000000000..b1ecda68e11580b0d2422eb6ba01f0171b877148 GIT binary patch literal 3683 zcmc&%TW=f371oWENJ+FM#c_*7D3iji@}Q;MBuF3xO#=f#3n6r1G;JShEQY&7&P202 z+qu{h5Cds|05&jhne;Kgpg*$T%(j~nDN zvxWDBLE` zHHzf6GeNY55YL^G6!p_iZmY%1gk+b|4Srz%TC!H_K@w~de0GQuL^oSmaq{!yV!vKrt771#;{F0 z0h+osESDj2ANWtFT+q`(KBMYXR)Hb|S52HYZQLqvJN}~^>#)kx%_tEcdO|uo^vTnnW4hSx8u;cJk*c*Np z!WllU!)Yn9^=En&`iHws=UX4U3cUVn?e&Ljr%3Ns*sg*-XQXHa-Bx}zVNJIkF>jgs|v~n zg)tQDg#HY28wRk)?lq%GsRfB`6jYcgs%sv1j5I{}i#6A8b!d)P8&b3G2ANfXC23=8 z?DkqohC7(hW44Ta{+A2;)h^;2X7s3K}Kpb8cAL_B!0x9B7&!(xPY_ zK!QG_Nau#@GjzJNdF{9Zdc~1NLyXl6X(y)>JHvH@=R}prA~Y~!3P^e=M@Bv6yDMN=8RG|XF)y#Nzr_TJ6l}@zc-SNto6%*_S zH>iy+9B7ZedGo`{Nq08{0N`nc{0Ic(-M%2@%wj$x-8tSjFzwxuqM1}VMtlX>;2I9h zj9oXAse?__CQ{8nr+Yy&Isnbo&;sQ#D8~-urw`8FLp}#;nyr`*l7;!- zwC2HF67YISRl=#6#yijrMqw4gWj#YCpb%Ckf3f3^0mveqGjrbmQe6dvymEK-b!o^| z%J_^4`~co9Ap`FBbV0D@{lkLN8i_iJ@AYdcWJUzUnA*n~Bk%`>ugQii#}wl9rLAy9 zSWe^IX?nmbphsrBoB{()Q#cG=0xfn$EpBAEi_}aE`^2ROWQqyRLEme5z$i7i<6Z`> z%aj&`ijkN~ zu8`%hFcmMEcxBmfcphVla`wc!{pHFAJ-456-~(GpKvOLV1a9@}pq3@I9_k1r6cZ`t zV2P@)tqAAZy&D#L?XB_9;SKv}#L9Ru>)xuZ<_qQ_+Dj&FXdW0onG^zL2%8m_-bao6 zyO6PeU##nI{S)v78zq>B9ay8i)wj-D&9WGa+(2Jzkgk=^=OgRP_o5!mk(nYD`|;Pm zyAS_}DyRUyVGWJp+kwqDm%z7&Tn{n+hI5YPiLbXL_VCO8`r4ZQncWvFz?VDFfocc; zEbm3_Hq>r7uG37qb+;AZ0mA-i{ORK`ed_<+p&>S#cPE(LtnInod43|?cX^o!o1@jq SS)g%04`ZR}pkYU|ljJ`{9++eR literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file/file_mover/result.doctree b/mddocs/docs/_build/doctrees/file/file_mover/result.doctree new file mode 100644 index 0000000000000000000000000000000000000000..94456269f0607a6a63697f7a10521e4a6640c974 GIT binary patch literal 3675 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 e2~9}$4PGRfNda_X!E#{ zL>5GJ$}{dB8w0Fm{y7}>zf?t@3#kJp#FhH8&r=f9i;F(fI#(B?5l`cxVw@(C8gen3 zkSM03`#(J!RNvvze>s>W1?~0mccF=lsh*n<8P&;9QW9xKaS(5aW}~y=`G>=gNB;8> z&muY-GMy$p%eJ3d9HA@el;T7iy|?6Wt=sZ`FY5jM|9=wioaqs*C7%=;iFAqE z%yyU%r69y}D<9S$5WgSccOQpp!*lQJ&ud@*OC;EW4cGxYXFIp3oL-j=*@&H8Za{z2bc2R5 zY_pgEP3;nvix9a7{0CE>&_R(Wf<#mfq!9X|1l1eQ)xx=WWX+w|KlL8}241wB7wp&g z4b1~UJf$NA91uCuX+H9C7&$78oDKg^Q2S6(0in0v@oh!J73Q+Gqllw<3lvY5`SA%X z_McP-s`;_?8J)S}VfL1nUs_*H;O!quZ^!Ixd~39;th!J?f%@Z8ojt-~x4fU7P^7si zvS=a1rK9FR<&0;uA&H`@plxdrYDpJ8FLmDtCw6Y$ep9eOCg4$WPodMX>EEL0L`{tC zS9WZ^e~XsLU8t2`f9IBUNmT(Fer2iI%NbdlMGPMCO4;M&z-uVjb&q6)4y8l*Kod_ zCv=gzzsFV;SxP*acjzn!>Vu{OM8V`b@CE9HW#6%f%G|qE1-yj97z&I2*1px7HYJWd zGH+=~GL;bDCsFHSq_`~9EiE)j%FtIjJ`;rt&$`(U3+c*WP?gKE*{vnMP>xMUQLT9v zqk$i}a^ySuREkVfu*9@KC+L}~p^IsYLg$6HWAJF_TZJnv2b*h`7MRI|rVER&&|VRDz%H0g zg-q=$v#&s`JOH`f{Gnj*-BLmm>LtN*u31nwGwfG=P<1ziMN&UuB=i|YI5$j>p_K*I zYs(zb8AlW~(S@2zTM?ZUF}B-(PGp8CLIX2I3P~3!3#mJj^N(kUS%5J?M9{LPTj=TF z!9a05zqh;?P$`8B7+PQg%mW5ETd1u$C?mgMrRA+ybF#9sVuJn4?3YI84%DB%x4f|O zr8#s50Pqw;d=eZ8B_bhNTwnl0xFfu8pe&eUNkbtc%y2nigKb=y8=H10vMJ7paPX)` z18bXoQfLv(MP|eGs=cU4^Ps^aqUr@4P?Q--oWTPx@oaFZpNn!ui%0~s)S>ARv=fR3 zi(h((fUVEC;w4@Wf`}_JLB(yn+^%^7&h6qb7)~=!IvCxn)O<7c!_N0yeZeM*N>5Ch zIcpK}y@SGsJe98~ut$z;fK3(yg$Yl&wx1sU#O&uZpO+7Jm|Z8C5cuwRVcxEX5{T6> zzt&7-Lm^|(>8{iC4nQ-|G(~ze($Rr@aR2Nh#B*Ox!xi&RGB^L@+B}#`9A2-e9CE6s zeh*ZGk(+g~Syz(@D1_PAUUtk00GS32Gn@JssN5ms#vH2aOp{r_c+3*~0N$-cINa~5 zlwgkkQvpgV1nStom#;x0LQ=LVa}qKFe~|c+Y>2WCA-=vUa%|ylAHLUAH2{qX5}r+g zfvPCnaa95(c16vP+}MkhOmqX|l?7zb#3Io58Xho8HTJk0LhCA^Dd9hW|L;mEIN?sl%)jR1rl*(;NXT1%E`{-=4kcW<4QR@W;@E=$6h z%6;Nkxm~8sx|?F%#O~V4Q_CI_Wwza7DLX|o8nbWNH-+gsslLu}3^x&f_lkXcQ?L{F zJjS^64SQUmZPqmxR-K;N3pwzCnZu!}lsE#{dgY-OC8Zw5Yak(=h@=6FSAA#Kh>4}OaTNQb+AiHiRF zOOuKp@Zaxq)ko*+Bjn`;GaABIHwm-zFCMI~t*QTTuR-_!;%Cr->WBDmb+lTXqfD!6 za_eTxAp>0f-T0G7uJM#Nchth_R@HK5&r5q|r-_e8`c5?$f?2VL4D3ij8@}Q-h3sR^>)51W|LI@ogar;tZG29(;CYs&Z z%*B>~7-$Owuz`8Yw2%1({gM41eZd8(yV8TD=r8X4mvSG4qx{RvxhZ)iKwh8viX zWt!6|FL`*J8(PrT%8f3o@hU=R;;prO!trUd$5BI2E~0xExJL zKBJ?%ADo>w=kOR$o=$|N{UQFG8=_{^EKJCf8j&bUa>FPBiH2x7I!o?-n0!2n$49)( z=~co^A^M(eKlTWrr|6U-B1G6m4X5{SINa)XqTkDU|N8kOiO#t`F-Gx;H7H~#+$PU8 zisZI4L9~Vt&z+JK_0vvntHsNNWS7W7)Y&A$cy{PEt>WH&{F4ggT|Xmb&T~?G!owXuuVGw znz}VCmm%^i;6I&mK~HTZB+02dl~o{#KCf~0%FsFpjpyDSz4UbS`%mzt6MSKR#4mB@ z0D3`38fYMDWQuAOvoQ*U7zH!&ue9}K)OeDh1U?3^t!cKxVAgG@(gD-O4VoN%29v`_ zO7v++xd&=fmV;1Sdx*6Qp5qTgCRaKgp` z`S(0+adJ{;Z`ejWY$ASTR_SuNzgVQUzRdlSURB6YI`0~JI=N0GsGoUOu>0D*@lq8O z5(;A|*a`g+I5)Il58P`;kx~m1dn@=cQ&iXF?igu^@E2>Y-|EmDuQnuT-3>CU0(H{H z*4XW}l1Q$x+tJh*Ue3_;4+4ba6qqVm8d?Hy-Mq=`XYy6l!8oS<%mQ)cV zmB&jYxgkY`mt7@gpfX^4<=y(oyWQG_6!%_2q2Ih(CGfUyWR`~J6TZTg4t!SE-rbqP zOeqar`uH3z8F?>lkzP%uD*SUd(CBzon#m%}EDeKi))Fdcl%!9&VQJd{abI(w>4->+ zqH!t-`h+5#8?MjL{?g{P;|}N*M-~k+RxhNToKEZv*A1Q$RU(Vfz}zVy>7g7M4Iu^p zc!`{)7%eh~mUlgCrl-F>)f~@n-TatRrKCz3+NPii6$q|o#=Bc}=9jE=q80CsSH7&6 zVBfnzZFJ#4d-ScFA68Df+aUk|Pc!64ARzDd1u171LmTPN@xFl-z#S@@NtI*nSAY$! z;lRw;bu(E`5hK#U^A-)P>juOcnJz?bBQ&@D+!j?j$0M>D1sqURB@w~kttg2|52as- zDot%J)2kw&={9I5HBFaKMu>o|Pq^kaUT>v2*JL6>AdQ#XEl{I??#~lNZMLK8Zy#J-T3J7`OZtLsP zkgJsO858&cyjwyB-0$gvVE6mlg3=m^I*jl2Ybs<$1jHES#~CB=2ZgW6hAhVv;`F7h za7EZy;|ywgzzd*9X1tsN15H!76S@Ri?21}k({LB5nHZLgOAp8t6Pkm**YJQ*YH-I< z2Cd7K7KDqGOefeBv>)+)mv}z+E5_YbFObOj>UPF}Q=( zRXMbBcF1?r|FwImR@)vJSAmehKvBal!` zq?m&xs=l@&oNM=PSnRd8#zS{**heE)#)DZ$tG1din1^UDnY5vKVEANG2$Ui0TG)i| zH}da7#{PS;uD|sU!56HVU>+7@jrLaGI&U@0Vr+K{13*qqAUOa literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file/file_uploader/result.doctree b/mddocs/docs/_build/doctrees/file/file_uploader/result.doctree new file mode 100644 index 0000000000000000000000000000000000000000..c1fd6ea9d9c126f93d2dc5c13db159d245fcb1be GIT binary patch literal 3699 zcmc&%ZEqa65!TzCbSLR-S$5MP5qeGGR{5Ykxoyx^2=W30K?)(~!l>IXH5R;G-jNY^ zx#f#30Wr`92w(&J7A7C_3-TlLkh^<#y2t_2FBP!Ta&|bJ89vX<=s%rr*kCRGxIr#6 zYq-!!DRZj5+X;e#md1bW-~F$D;g4NUk>^@kl~M23pph{yaz#u3)E~1o_lC9;ZMcCM zS*AIi@{)(wxdGO3-zLfM3vH`PDw8rPFZ35fUXU3*I~y`%Dt$H@@nV)}#;M44!sTc} z@);f7|NZGnvk$Lv=gCA^+8^TQ%n&uBW?@2>)QCh;k{d=5NHj#t(P{GF56OF@xP8RS zoL(l(6r%6h_EV1#dWuddB0_|1)Np$Ln!~MbC;Gjt_vQaTlIWc46Jr#gSc8iU7q`iC zjZ1RdnIKw2i1$uOiu!FQx7A`XA=w485Op?*FrFQ{O{=(f@BL2~VsR^-ry@KNAVwvJ-ZTJz%>xo^wdrqk>+KC2Yh_&o`jI>3Zp$F>KRL zfTnH@%VmhX3H+y1F6fD^gd{mtCyHt-%vlYq*GASsWIXro?oFGc*B`@^PVj_%geP%- z0n7_J(!c^yBU4nPn2S-M!zfsZztYx^QSC>9{QKzrwx-z%eOb5RNe46+*En+b6IdKR zX@VL)jzP6l+4?)Z40Xiato4!aM8^s+*B(s{Ya)5%pD(fq=*f<4skjT=>P zg-{qn!A|H;;JD!fd*tpKMM^D5EUF;J435{V?igu^@aJo;-|EmDOB>R&?gp7vfir1i zYwY$~No3X7?PzKYFK4Ls2LZdW{7;oE4J`q+?mq|$te(Tyb)_|xHGa&Vj9qU=OR9*G z%3~4PZAejJv8$vE90p)7y;~o7w_CfA;@k@;^qZws0;_!^voutl@D;9f;Iq2&?#>iu zN@?iQ$Cs$Z$a`sv^l~ay;a|FeMyadPOctS8X&8K~mQX>XBz?{eOWVqa^O_A!DMVTn zjqOO#XB6q&aD9flmp03eJD`^wSv160y^wZtIkagNcc^G4RgQ*V0X8^> z12bdS&15-6j7SI1TQsn)8xU(`x)8aIP}cTyTU6;BkH~5ia6nO&LU~+ zX=-zsUKRmOw?R9pX}Y{KLIiAm#x<|;dMnMjCKC|?X}sKSc>>PuAs9?DWh5Pp?l)?& za>vg2i%>vuh^8tMla|3+j{Dxlkc4}xm*?y5Adn5PDQ7^T-~~7Sv)jLOgNjy*@)3gB z3z7+e?|~QYmu&}wSSQt$VX{o5nt@LDf@X98nyH}$uE*dyb|62wfBHw{bD*Z#iuoW} znE!2S9?T^HuNPD$oSJDo1Jz&@1|uBSGh_k^VUY6YJMIX8EYdkM=k+huRY1rqcUxbT zhFqqM&zQgi@NNkiaKEPuf}!u<7L?XV)L~rL%T&mW2#C?ck1|H!4=%nY8?qczh}{>q z!VzIqjs2&o0k41_nelQ83^YaIPN)(nu`6nEKEqk0W@4BwE<7MpOlS`JUcm!Kslgfd zGH6|-v>;rhB*&B+=cbqLpy`Uhs+iUV?Umc4VfP9n6WZ^9_nz)bpAbzJYl>GZRM$F4~cf$;bSd3Ml%{S!MJt3 zAT?JNvK+>z;w2NWEISJCV{}r^9$UA+e6vB%?PnbL2m?LNsU?BHtzI3}vZU6-Jpu{E zM2b0BqUzlh;at0S&0?=THEz0d%{~gTG9Jvjw`!~Tf_{kdl1Uqi2bxbNg}^l+26UJ0 zhmHK}kg@O2*Y#KaG5CV{63oLytWnr|lXMxE5JT!);hK5PaPLdx0`3#=4 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file/index.doctree b/mddocs/docs/_build/doctrees/file/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..cea2d5f23e60fd7dcc6e4337fe6734f9a6ebea8f GIT binary patch literal 3893 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file_df/file_df_reader/options.doctree b/mddocs/docs/_build/doctrees/file_df/file_df_reader/options.doctree new file mode 100644 index 0000000000000000000000000000000000000000..878ec8f4dabe8c3f1e9b8db739a5f6fc6919b737 GIT binary patch literal 3672 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 Hs#)@HF=UeQ literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file_df/file_df_writer/index.doctree b/mddocs/docs/_build/doctrees/file_df/file_df_writer/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..58090887dce08c69fc42d68e4b127e9fd6f81a37 GIT binary patch literal 4147 zcmc&%S#Kn_5!N-M8QofWV+UR(V+R|{51Nr2JBMK8zy^yLf@gz;9lvDJ;B<3_w7Q!e zlD#`Zyg&{(SOI)9H?aRwepNn_-93j_29g&G7^u6tSS(h3UsdsMt(PYUEB?>yB_b|0 zPgOq`302l~oFJpQwqMzI|8Ae#6VsLCnG&UpsWmImh#5~4Npt(ep0E}3x++IXa}6^h zj}kiOId_kZ0oF4A?)Q6NsIn-8)DaWnN`2Ae8JWzjt<8t;3^Vc{)l)|C4y<%ofpF@=>XgNSC;E zo+u>AY{n^33PL=$a*~CoPEr=Lml27t5CyNZPEu{zky$S#x8{TYD?!$5#UxL7LV`DD z^*5Hit4gz}$;54I*HR!LKZqzV_ zt(PO9sU2WB50N*3|7grpdYX(+pGmH%Je5M|i-4+I57xrLcw)`bE7p3Se+NHW&JXrm z{QBlYfIOoE1tbtT&{;9?kr+5K44jcD8Q1@#;7COXB*1$2Hx-SS_{-XkD30tc5ItRF z$wOHE{lng8{!}$fHa?+KS3=D0;^!}|uPN~Ov*7WNoef_Z^D4J4;GaVMaZqQEaNrH^ zXD1d}A<8_N3v_VQ?5l$DeA*{TQkAt$En+R{yyrppR#35XbMLzX23i4`l6x|p49)#p zlq+!+Z2K=eG(WmURTM7n3Ody6@H|dSS54Ef1N$p`PE98x(}Wh9*~?Y%n)nspin7_5 zq+(=mOq=l}L8ZPiyOq60(8P?%f#vYm1v?RGo ziEoW4ZxcyXsLd@cG)Y73D;=MT(#2=h?8T*Yl`pC)pKrlY9VJfEOvAG!+U zoA_9YTvJfOwEslVCRM`?(-x&JN^OVi)1m23Xig>3LIRFmU!yh23cPH)vV=+u;7zSr z9ays+T!^sm6%@9r*HWf<+eNM;*E`%-xYBaCxwhuc7-n*z>DgQSa;#nc_i`Nwm_EW%_U5@=b|E%o^Hm#2#3 z`QGBkh)OAB#L)FpfF3Zw*m6o?+&GFKgB@^sFW-l0>IZ!XXxA;owP)2G%xv zq|_psiOiboPP<8&6;XpnMAZv8peS>aI)nRO;@RL*KNA&-mWha_nM2b7XeShn=2v=% zfUQrt;sLMwQNk4&p_esYZq_^j=XP)y%%iy{9gOZ)YQF#a`Q`_{{_4`~mXDva# zw^8|!r|^mjd+5jp*yIzSFy$H7_Tz(h%w9o@S^02>*>RE)f$xqN=8bv|fmrp6Yt2O7 z7jgnR-F2GY0cb{=W=M}lIy#V_-amUE@!XT+c*(qz%*_9^HV@_!hu14A`<&{r-viZP z;Ko~Q*41PL3Sk_!mmPBqKxR?H%%=WDs&ELoF$d~8*JK(oK4B?-0PjXB9PW2iMlibn zr39rF0(Ioy!)ug^n1n86j$=mP4-y~9hE)0xV)a#7U<-HQ@Xe;G0q9JS@O%smR7K&A zs}d-&OKN_~#$F^a(anfg7LY*~OF-Xic)%#t*yC;tt*eM;gr^Z^09SOq*N4?;1FWXw zP|$A7I?bP2v>?+7r3Bms5^+o5;D(OEFz0b|xO~%brJNnHZDsB(d| zF5~9A8(iIx?%K)|%N`JAHr>Y{J3%uVvai|8(sZ3vUl%xr8-l-i!M?sJ*|B>bVm5lo z9+&8wbz~c9{(uBXhr3=tMel7#w7wo<9xjzhAI`eDmz{t9^VO9V^&NK^wExfFhYnQF@o#y!nxCXhtLk#AX2U@P zWc_IP@R930;kJ%qSly&r&g>xAGuusoJk+|Aq@j{5%vR@_ai literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file_df/file_df_writer/options.doctree b/mddocs/docs/_build/doctrees/file_df/file_df_writer/options.doctree new file mode 100644 index 0000000000000000000000000000000000000000..d8b437d46e0af3841dc819ab345b7960878d051e GIT binary patch literal 3672 zcmc&%-ESO85%<@wy=!|D#}^KivsexVat}M}04I=jIzW*S$hr-RICvo%_0IHe_qk_! z=#RvfE)pP}lt}${4Uhb-{JQ63*Ng8Yc)`*t)7910Rn@;=Rpqa}e|$FDihpjD%gh=s zbW+NkYVQt$prEDkU;B^#=HK`yZlK6(Ev?F^cU#cN7#F#srGM$4ur2q#wsUQ`ff-q* zIbHCQhtJLc>$!g>$@oict4b=9GAXb0mt$U#CB3*9Gh-@!F`e>anP|qT$aTWybWZXm zoj(4_`J_3A&v%DQIzC{Q3MhV(QNjA(`mWGx9RR50zg;aMib$w|~WsRS)&u4D1q$O3vNaeAJEHxDuLCpky#q5OW1`gJ@~9{ynC>KnNk|Mw)qw{7kMvj zkzOyPD*RhF(kN+Fn#m$GB@Kg*Y6%rIO48Tdu(YjkxUV_Tlsu$G(b#qbeMOPZ9XDjC zZ)vmaxf6QLkwrs{)hlT)r*pf+b)(lrmB=DA&{zsc26&E)hLD1PyhP4Y^bVOr%e#R! zi^(r0n&bO}+nXs>N~)BhE()sf0>RbHcz3VP{F;?swBp^_#?FQb_AfW8jjkMMkA84_ zW8mBrXcdBS6RgT790XDdX12Z$%&t$nkj7SI1 zTQsn~8xd<{x)QmaP`r+FTU2R>M`Se$IH0IXB7(ttQ4*0Jo_-~&G_|=*uZw`D`=FiF zG+jR#Ap*9(;+ofZy_e=(lR4^K=jDFO6L9Vb!C+!3Bk5rDxKWE0I(EQVY;lREDiV{H z!CH>@KEyDDcdD0{+wLTg4X`PfK%w9TH~zExKX#*vR;%(6f;kG3If3ti7w)@lr-E1~ z)s11YOr%LgJ~FmaM{3+IVgmI$zLA0GXSzkJ7#wEFV$5*$UApm-;{=2r;IO|zz^Wv6Efib zNEZad-Cryyt&ynHxUQF}kQosWqgS70jKCi}d`&iFIi?V&uWW@Y!blq1OH%{h0X;I~ zK&F_}9Q3_`2aHmKJ051xx=Lw5xJXHkX*SMB zZ{11L^ng{ds0-RVw@b@c9<9n9)TIR61rjkwa&W_>b-Rzjoovp+p_H>zcBtKhTU+(u z^#;t?7&1?FxMk7phtVdCaiOie^z0eYZa-|+vL|RpGbR|fZV;sArb3p(m{h!F;+MZ(%x89<{2LhC Bm1O_` literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file_df/file_formats/avro.doctree b/mddocs/docs/_build/doctrees/file_df/file_formats/avro.doctree new file mode 100644 index 0000000000000000000000000000000000000000..49daff6eb8ad4991754feabd8a08698dbfede39f GIT binary patch literal 3631 zcmc&%TW=f371oWENJ+G0MM;Z92%FT6^Pr`idxfHDU?4~!gbs`*d8siN?hZKTTl2m>*)BpI8E zQiRvJ0N!!`N|NFC+E$fRhHxn__4h+j&>1^FA97^m53lj&=_I$TKg8d;p=!p=LWC@t$rHtBW;lZ*Q4lLfXUV;HllMpQ`bd-+yGpnz z^1kQWk33xHDK=&B2p+ax!|44R2DiMOX!kPTKmPxoMB`ka8l%L-8YD6#Zc}6$Npjn1 zPPGOR@12qs_1jKntHohL(@R7l@@&%Fcz)zItrFh7|34+jyM9W`Ok}jS#;t$s`P?$C+rnz^Pf8n7DVfXJpS|14MalfdhU-=oX@N0^c0&29aEhNGW3S`+9iBJw z+<{YFSRQTtu(kESM1r^cl;7g_`0fk!9MM));Op^(kNDZe2KYB!kIXBEZ`uja)U9E; z43T5tpU}rjo=!#1PNz~8)STCl`j1|9;FZX{J6OFKt^EWRbb6<(L3!slYGdmYTy9HZ>#yu8 z^b1#0fAq0$z_xE{+s6EC{KB|4`3ph)3hWPScK!gaZ7pB8>$ZZio{w3Z`A=&JkJPS~%2u>m!I;9fF{mO7`g zGXmW+#dKYo9U~3R{l%K=w>&Jv(S}&8yFqGI06uACjon^LiKQ`iJDM3I${AYrVE|z4 z;Zr3`!%ASL`!z+E)N>5FuC%7I#*g_|W7nIpk|}DW@;Jm2HMFR3*i}-7LIm=zyjvf6 zw_BS)p!X6C{pM&@j?=!8DG9A3e1$0;*sQL-yFG=NQX01O@fjK_;$GTc}ri=|m~soveIW5y5|UgIegqfcENZH$SZ0bO*r!08ewo zCx=7c?dPKeI(e=6FO@BS8R)s-$@kcq>vO(nIPOq)IcJNpe*LG##Mqq-JFKW`qc;^|8>R z#_KJT2~8(xXmcyK+d4tb?ZFwWBV{Z)2;FbkNa%rayuBQ|p(r&|u`pQ~q-DtWE;b$H zsXkt;yTedyfK53A3Ug5is>+;yF;$bVYoiEX4nz6_3iL0k4-#C4!l0)PZg=3fm2o z^$eY$gs=_yiye0YKo(?Pne+ZfbQKWt+#Tra($Fiy#f;~80N$-!2HfxIf?{*~&Z49> z0(BJ6^^xQb^QW-;t+n2V2im-3SVbb(~=Rl84ML7iqnx=3&bP2TB z6}Gr!K^LtnF{}uen2oHMungsU4GTD920D&Ya9t8sP>~awVO5RG&og(}j5uI5pVkfS zx!YvrV~_FVcIsAw+65A^Bns4qA@wYexH(*1bVDoWM|@Yi+c!SdTh&uk#>xhHq=S~} z{Cn72!j2Zk$|KM3Q|-3H$C`E*!)VNNF0AVXs=2NZ{ICNR&v^da@{{mB#@H16p>_Mq znGJkyKNY}7*vg@&ri21+`D&+@DK#G!Dj=bl$b1f#$okR>an9YlA+gtLjhAlUkdH>J zj7Mc1ugYq^U>>5qq|%1wf#H+NJVzSf1E$ON{f7P%y!+8VM5$muMA2cR)#z-s!g-fj zCSxrd=xdF7YpL_m$UE~rrw4I#rf9`~`Sow^gU^rv6>vAKQ89dDbu)WoDD)6>Z#XZm zEP0jAnG5f#W0S{O^__w?PwQEni+PF?L;?~_(zy&Jx^YNz-!q};Q ssi6}#TXZLg-K^!g-Fb8(&UZzb<~Bo*le6&apr3_#(6r95joC@^Z>~3lDF6Tf literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file_df/file_formats/base.doctree b/mddocs/docs/_build/doctrees/file_df/file_formats/base.doctree new file mode 100644 index 0000000000000000000000000000000000000000..445559a3e474c7ed55448dff9d11ec458fc3aea3 GIT binary patch literal 3671 zcmc&%-)|eo5!NqKA|=t56*VmqA*=(p&V!C}+cc>VBy9}@36#=-(X=l$7R$ROcf-5e z>;AAMAO_k30c>F3vgu>~)_%KpM^cOvpna);lkR3`XJ===Z)Wt5&bOZpHsX&PWHPlz z|MbuOeb-a;nU+?i%)1R}q+H~gVx@oL@AD1!y0#N-gn=1Z zl8jA7DZ=Z*0PnbeC&}in$Phu66CY?52nAL8f2P&H#_X+oCFnYdC#)!{Ju96a8M*``7>fCeb<9r^YBTu?C3@iQ5*L zMv~lanp3SI#CxZtMg6vy*=qSQq3Ik^h&tOeH=ZB6ZL5TL@BU8-@~)rKG7}lCy>Xi# zdH#;JZr9LRIM(wM*KJ{S@PX2XR7&P@_Mvw>xhR>x+;ZKDA}w&{$WADK1DE2b$kaYx-HpFMs4N|KDK}ch3><(H< zq|n&yX=aQlXDIDQ0jROoPnAUGN?@z|7m8}C7g%*&X-#E~AM-E9t~X;PQ`AW1@e!$P zXi?!~Ur8AV3xr;Iw>k1|zjlG(+&L8b&8Jm4z7CB{f&X+KR^dtqKC5f*?o45(l!mP= zzChtc+)G=Ks{mF1!VNU)T18S>gwCX4@Qqr+auy}znJ}ERWe(>x8=AU@uqYOLPtKk) zgmcUFIf@)d3EH=|n5uovy8{nc&~KL2Y#D zK)dzLn+t0@11ZuufTua)lLsQZ!5ND3A@XDQ7@oE(&4%pKkxs4JuYG%SX7(L6C&9MFKC} zPuq3{u}-RM!)2LBH3OYaf@X98n#8aI=`l#h9^_~D&VPe=4%9SVGan>N^S@}#gSj-| zb2KrvZ1J0Pi87C>U=7beg^_>&nCDzr*9%f}T_N~kRw`cb z{H5im;eCu&D)?jT4p%c9^xR=8fR8Y}}33-LOW* z@NS$`0-KxB@9t3OA^P5ML9Q%;r(s6o55DYgZfxiunRn6qzubckR6F>yIu*48P&?YV zPSfHx-A=#-nEL7X<42+G)c?_;5;oIzCz#!??YaF$bRy38MVaO{LyeQO@Lxnf3;m!e Konb<=ljJ|}6q3aN literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file_df/file_formats/csv.doctree b/mddocs/docs/_build/doctrees/file_df/file_formats/csv.doctree new file mode 100644 index 0000000000000000000000000000000000000000..3f065ef48f00999ec6dadb6bd6aa368569d109b1 GIT binary patch literal 3622 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 Fz6X@Dl4}3} literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file_df/file_formats/orc.doctree b/mddocs/docs/_build/doctrees/file_df/file_formats/orc.doctree new file mode 100644 index 0000000000000000000000000000000000000000..45ffba59d73d1741cada398b1dc0858ec2674ed1 GIT binary patch literal 3622 zcmc&%TW=gi7WQq%p0PcN<7C-Yu$Tp9H$1qPqU}T*VIW49Bx6%i zitsuYz&q~8BpH6KZB;9=`EjNZRuaLe0?b}#e&|Iha%8t3}d7$qjwAdw+)nPiXS0kui^PSo;UE^ zfm2;r9&P+yt-_{rG@_%~gT%qxa(+6mCqtzo$g zk(>~6E z7m45b*fL<)=e1#DelmV(NSoA!czz1@M>RWt2*0+LF95}`)blZGv))jC85g#kq)!&X zsfD_~v5N{`l6kFXWO7L&6rXus@CVwx@=_Kg0Ssd>*a`a$5H?`u58X|pXsL4=J0eg$ zQ%u(-*)h`4+@Gzve#^r$9Bl~1x*Mcc1;CR=*4XW}lvomDx1*UcqMV^s9|YjV-aS>a zG^_+zx}Q;WM?J@$>q=`XYy6mhI(EGoE19B3Dvv`fO+$+chg~IQC_q5&!n^g6ce}L- z1bWZG&~J`bW>g*8=|m~s9j$y>5y5|QgIegqfcEOES3j)Wbk~Ce0G{TEPY#E? z+s|n^vlz7qcZTx@2Ddv@ER`z5w5|Xfs1d--*mYA`PT?cMA+i0w4=AugrP>Bf1I*dEu_>%hJ#b!o`f|cmUq5Tn60l>4IWo`^uuEH3D@Q z&-IbyGNnyuk5W!y4-#J&8&VlVh}-A3f{L(b#yQgTfEPfIOhq{b2AZaDBXkM0*cG<8 zTtOGDD>1AF=N^zLCM-kwUcv&-n1PO4DY(uFE2zi`&9I`zrRTXjXr>#mnosM7_QGwl z@`=Z6ayxY^LG1#GSPlhh!;pH0N8B8&F1VqU^FzL?-Hj`s>TT*7Dr04HJk&wUbpAc; zD`7VaW96~u52$wA;bTp^i(xe8ITzOT0@Ykr2!7aoisw9kVfj&bA7f?;{>Zxh<;(^? zx1S2&BW&Z)Q&U0#w|uoz%aocAixiMhOk_R>OJu#dLY#B=u1M^)TH~b~SLCA+E8|gF zw^n5}Uoa2RUQ%g8^T6;)Wu7Aq@B!0h`$0qh9^QTDAE8vR7ozB}$!c`ATH(CQER(U0 z4fLf(y|vW&Xyl#wiqnHQI#aabzx?`zyZ<2)paSlOH7bU$udHOR4}~6L><#C&l_fAV z>_YtBm;Lp%HT@gwD@Om9JKzCp2mhA0pLXYIHyhV!CfvH)3a~(-el-5zVVFAg4>UBw pW_j)ev75C#w>u9`g!!&0)7)lgadH-Z6!fz&4w}vxHZeO%eg^AVe}e!3 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file_df/file_formats/parquet.doctree b/mddocs/docs/_build/doctrees/file_df/file_formats/parquet.doctree new file mode 100644 index 0000000000000000000000000000000000000000..3d7406d677d08d36cd127bb7e72053d68cb4cabb GIT binary patch literal 3658 zcmc&%TW=&s74~grJY#!yz0Q)L#9|0q*gUw$3q*hw5ztBqWHB492)wW=)pXZPRovax zsmrb{WhFq0v@6wH3Lg2bd{up!@n9ms3zk~$I(_O~zjK+t^#1DOUOy{xRQj?`k{OMi_{ZCCS)A zlp?%#0(j5;BS|J-X8w6uTMppvFn7J zA|H7^c;Vr~P_YGrNAR%q8b%-8GPvdKMZ4Ge{`UX(BpT;N)EFh^)*z7~al0bZNRk_* zIn^3Oy!T34)NlKltyYIQO|KAz$g@jxF=`2)?;jr!&NrcipQ^ZZtk3_h#9YRhBq z59~Vh4%dD7+Q&8mBmYzzIpgQEJHy)W|vsKV)1@61-x`OAV>jfE^-;fB^*Pa*rsdn$)$$|udVFm^}XWs(S2JHNqd&ekR z>YT=o3DhqY({;)Aj5IX&ms@Vs@~{j?8-lX!#;H}IAV?!?<_=p*ESZ_x*UT7EF44-5 z12AK6Unp4`Rsu-p0-C@)9r8Si`e#Sqax#5zPOi?41$03%kp+$wmfs!&5DByVQ z-S*VGgW3cFy;oouHAky*oQ{l4NoXnID@^IZW_9D;g9XHt(y+CUZ_sEF_tF;RI)Ku@ zabt}>R*_T|VG?Nwe7~l!oJ9(GB@8ERTZ6vlLeu9E7RBPM$=OSWaPGJfM^husvFDE2 zwLlaNHCC^vy^PK666(gUs45XfaA2AgpbU|Ylm(Z9eWFCn2nL4Cz~$Z0n#I}A&$Ph% z2e&^Grj%5KqZ#H*Ap^nG)OdHVj{KUGUXBg505&WNSTnk+p&|dxE_J@s|?tX9p zz|$P@$>ET9M>#E*7NZ#9&T!trV0R~qrBY>><`rNAH3FEKxqd3k1$;y}MAo8#_uZIU zBgu;7c0$uS%4|`Q4v&axBnUuJl{60m??p;PdPx0>RB2{2Nv?~4ru!&6sTo4x|~S&9E)D;||g175F~N(3{Dr~}<# z8nzxN8yY%C31Lg}m;3G%fGkK?nO*-Qx(Wz+>+b8D($H(d#ggZE0N%Y^2HYR&f?}il z(xRj_0(BD4^^xQb^QW-;t+gG-Nim->q+0yiYw?L0fMY#Y5nx^m| zbP2TB4Ys&^K^LtnF{}<(9*`;KEJOL;zyi*gfsThMxUL8*sK^P;u)@Zr=#4vWCLXYw zFY1Q&*6p(LrN^9d`*kZp?E;Bd9tCQ{kb1^P+#GK%z@e4%6MmrGgIk~KZR<5EV`H;C z(Lu|i`yTe0u)Bq^^1}0{RJ%d=SkoS37|nRjg>}O~H8&N4A9kbS4bR_Nej46qn4f|_ zx9(^?vw_bYr2_Z}+dA~rlu*DeU+vU7rRKw;1tb)6nRj4`tnX|PJMQ5viNjWFy!7Cf zd^BQZJSywqrmW@*<{{ckDs5;U7(S`YbEE-2V7d&RHuP`c-Pis(N(K7?iVmBuMrW%P zc3ozjjCF0SZ#3$yrFNr{_qw%b2yt|&XvP2i>z~~(ev1UCfV)YJipht~da}OieK-+% zg3&kW>MKiNY}k$XlP^ZwTU+{DR$Pq#FZRI$)*k+?Z$s_=({4Ae*G#!>w-=y+V*TCh yH_yW4seiX@ZcT?HdZjj5&8ZLBF z%A9KNb^@WGrSU)bcfRw_{9V^mbITTR*S=gWao%NWI@|DO`%T|Xmb&T~>*>X|0 zwjr~i*0Wo#+rnz^uS%P=Qc}p-2i|Q7UQ&Oy;kp$;T42n9oe*{#PO&3o>I*4k>D*mX4lv~w);{&hon^%_ejGa zhR99eKfNf#@staCJe8^-=CnrCZ#vb1Q#|+X+RD9X>&MXF3HsSbcoO$Mz+KRh1_Fp0 znW7rS2#i7zjDpfMpVt3t<$46E34-pU$=jM{D>ciyO>uNk+u}-LZ2t3$&)ag?`a8V{ zy~0J}_dd1^81|3aurWIszci#x>Owrf1pA|!ojrtKTgw-KVp!_gn6_DOD8G&iT~5;9 z7Qv~7y4UDM1uxTit>@|FGL2At=2^iWX!puXS&#%UjKN?h^mjnmfSEmXH;p2t79@5= zpn9gLu1m6Gq#?qet+{^7LvtK$2*kP@WL5>hr;V(!+iNMYB*tz>Q)75JL#sXrz>B?m zs$^+s39xj(BIu5Kjy>0v)>PK`G5d1tdNW#5MT}G)hgh116crA;O3F}xfZT<5>m%=W zYZFrFJqJU-Ia(!f+BY&wL(2$XVM+%!t4r^0Od+O}hAw@4jz)^Om$pbR0{HxMH_+%) zRhr2nOd1V=w`&R&G*Z&1+_1E5ThP~BX!;n!qG+5YfSbu%F$a7P>H?z54pq4=Xp__22-2ry1fS z;E;Fwf|N6hQHyZrIB#HZyF*1Ysd7x~3b2714$O>QH4M}o zLSx#`ZBeCjJR+)*zyU>75)lMmiQ z*V3G8GC?bwTe;oV32JT+&R`8GW6?qAe#4fA?ia_}A7UTmrKTztCM|=s9QoeGhJ!rS z$Fp^J5Q+`3DQ7^T-~~7Si|cQ@K}D-Y`v}hL1OoctN_9H?owB0f+S;{Vi&N9B@$*K?{8PR%sxKsOkLtp>_^ zhD=aG*n<4ojynP%i*#O@^Zu9WDj?*AyRI)wLoQOrXH4J$c(;TMxZl$S!N&HDMM-M} z>M)+`V=81un$R9)jKCfwzAiSTGKLVh&us-2Vb6?nr0D@KfF7CgataJIP2oo95@@k2 zY;n1QE>c%wSPjlSAX7|ej`F>P1&mSy9k()You{-QT%;t&iW--m=kB1HZosOT)(!22 z+oa_akJ;pQ>Q;i<1ro6wa@2-N>lq$#bFjMLhE~oF*{*gsu6(Musb{E+mCf-`2QAb2 z_pq;o-7Jii$DTbP+HHrAHSI2j(U=Lwt?LDd&cbR1} z*0F)U)TpDyJ3xr;hQTf*_%VIhZuXqd2Qth z3=O*wyZ3c}eQizu&iabc|Md=dz}msTzh_Im0GqC&@4T3V-+j literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/file_df/index.doctree b/mddocs/docs/_build/doctrees/file_df/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..8de4e9ff1c11469d4ef0dcef6b6bd1b666b26c39 GIT binary patch literal 3871 zcmc&%TW=f371oUuNlBDtSxK5$2%EyK^FY#08zc~lHiZkLg%G+hio6+%*_|P0qTQYC z%&bKLGSCJ{VFUA)X@5q4W4@W)cf1w;Y@f=cG$Pfb zTvDc;?RY}Qa^rq*pZ>$Wau01+k!vkWl`v#o~QpIxj!ad|$_I+K(+6V(NGLI;m zid^`|g#g~N{~nD7x4J9}sZ7MBywbM=k&zj@xEOF_3Vksii)=R1T(Fetk&xq<&>0&) z`0UZJzK6$fa~P*3>kaUCVThVBvlJn7X3~*jgc{D^ND##G@uSh>UynW?hwEdJQ#K!Q zlchb!_g^@;&{b^8;1N7*yoS+7YX&#Gtzh>m-*^9iPl9o_M~qP-E)5dt6Spg!#B(brS@qPRWwCsTNpgiK1fE@z8pluVZmEQ`pZ`w@a<-R{oC-=RYwY sg99 zcHfYhSL^thZ8xww{H@X^DwIs+?03%Yr6OnUa>up{g0#SxlQJg!BRIv+k+F~Q`#FBU z!0!Q^YQyqi>lcl!|0NP!$%p)yKjsH-)pJT3Ri3ZQM|{j5UG9K?*LI^t#qixS2AbLx zELS0N0{ll)k+LD34waPVqJq;qJ!-)tL7hEZy%enc12nh1X8sj^BRd4FGd9-9JyBzm z731Lh*u#J9Z%1@mV;(Vf!NN`=vqF|RU9pk3eKFW$U2u`lM}*S4BkUWQ7{yjgqvCyM zKKNsMw5AylL>83@z@k4#E_>7@EkNXWA|pMjNKdC=7Lgm zy*Ku#7QG;J@-mCjK&ZRivOV-dsFZnn1Ku8*=hFA^+Nk8YN7FYz(=+6B7dd^thQ0~$ zK)1(r{D;?y6a`a`cQLxBQ8=@4uA= z5Cp>n3}wvTneRm%^Jn&+Q6$$X3Bv#dJyT3q-Kb@xA!!w%nkNLT^ikNh{iIaB)}y*7 zOzdGp3Ef~~JDM3I@)@SoiLbdZwx&wvh5^a8{SCoj&jO9!b zBb9#%y~&WQz{`QJN^~ILW$x_u*x7^1kO2&-AA-sO58fg!8TwJHA@HM$!crC}(X}u=mZ$xTVep*D*h`{T0x4l=Nl&*BKUu7 zzY@AMpt*W~{b1v!z2hAK{4_^=QaI%7QA+YziCqKXrg+~$4ck-25~(QGl>*>_8sD2H zww=g)3Lg;;K^rviw(XPB$Y@EjJH9y|(K0Kd1u7BMKo9_=%1P=49tTRO37`5UsnD#X zGMZ-|O?OayQnP6FW`GE4_NCCG!s~HFg(fjN`ohZnrdm*Whj0daSRP6bLXT>;Fn7XS ze=oF5k!z+xd9vI~qsaFGx)kzMy`n9i_>u!`@)=N=icA>y^_^eYe!+@m6YlITY!OS%1Ku;L^wGPUsMGLXy+)}_NHtvSdBO?oLE@`oLn=cEar>$)pu#WO zp$pYL08<1Mkxzkvx+&cCT>_1EgDq^A&_$|B^t;iO17t99D9ZN+7I4N4betvNx{6pv zL>gg$`_>z*KJk*Vk^`&hv}$N?>@Le+x=9(^PSr|KyFengV}aT*QMJSb+?;G~_`ad@ zQ+}ZB-L<1?oqUDL*jSxUwbwFToco2%FQo3iQ?Yzk;73Dz*62ID4?X%SZM<8MFQ-rCYXvA1LJfBOM=zq?)zI={Pb+!$U=gAK?}K`CLpja-*HDl8F;I6*zfhO) F=tmP5=J5ak literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/hooks/design.doctree b/mddocs/docs/_build/doctrees/hooks/design.doctree new file mode 100644 index 0000000000000000000000000000000000000000..80b1c000d4f564eaafde26e76888d817dc3cfaca GIT binary patch literal 87065 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

^TV6p3mp?AlwgDlmD=)L5!xKCqs(< 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 Ti2Du~NmY?joz(i~Z1(>HIu?~1 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/hooks/global_state.doctree b/mddocs/docs/_build/doctrees/hooks/global_state.doctree new file mode 100644 index 0000000000000000000000000000000000000000..685181553e27d10dcc31809c085c0348315d205f GIT binary patch literal 4316 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 J+^dRV@4uWeqIv)T literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/hooks/index.doctree b/mddocs/docs/_build/doctrees/hooks/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..23f86ad6b10e3766bdb36f4ac8bacc704abfa3db GIT binary patch literal 4859 zcmc&&-EJgD6}H!&@yz&dZD-k_#9|0qa1=b_1p>%Iu+bv4$YK^n1a5ZKs_CxjD!c#a zs`iehth7W**hO`hB0>Tl00aVo;EHF6w6BmS;1T#vb@%*XZxn8@G}hFqQ&s1j`p$Pw z&6CEj|Gm59{>)a8`h}K}>LqEwl{GCA#5~dVL;LzK>|=Xi+Btiq(jxb{HA~R&g^Yrn zC-$K|5KHE!Duzl+jcwAz3;0MTlD+sdBJRC*^?{x8ZtXUp%J>4QrxQ==`XMq+R zj<}6@(m(3m{(A4tzFXdxNx;WFq2s7y#o9v)7uq==ad-p|Yp-GS=A6N`Zo}CPl>j7YGBI27CBwphRPoIO?_ zMS5+~6Mb=Xat-<`rtM8KMywP=ps88Hauy;T;J?KEafzcVU1`7-8Q`8ASe;#O;j9Lo zCGOzUGj9OMG4CtfHp}}u⁣VJ^_9Xf%29SuKWUTV+Bf;~oowihQ=u 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 UW4CE{sx=7i$LxT$tQZFW0kMI_w*UYD literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/hwm_store/index.doctree b/mddocs/docs/_build/doctrees/hwm_store/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..238a739cede13cc931565c4d2a6f6ec755a7117b GIT binary patch literal 6473 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 M&Ah;n*OE@-ziZ}-;{X5v literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/hwm_store/yaml_hwm_store.doctree b/mddocs/docs/_build/doctrees/hwm_store/yaml_hwm_store.doctree new file mode 100644 index 0000000000000000000000000000000000000000..6ced2b1301a733e103baa469421d57d5aeb3aa21 GIT binary patch literal 3654 zcmc&%TW=h<71nK6+Lg4nthi~B2-&1?;yh?pP7^dxi?)G*Ac5j_;4}#e)F8Ms+$AH< za7uD)35bEVKmZ%yTi8DKxAr4vW^ZuM-C!gBbAwE!l@-z? zh02)mZZ8mWR#^YNfA=5$nZN6Lnm#qE(kb(90~{$AGSjT^kNsV~;a)Y>#8_b=MinGu zQ&EWUITye??%zo={MJ-usk9|rsY~#R&d-P3+R~hlMk1djh6^S$lL$4M&}_y= zciuZYZQkKCzC4}CiuH&1cW$YkF}o0<3T9=Z8OF5b@n9XkLHqWmUO2OlW$EC`6uZDy`?oZoAULyAS@S1bNp_X_1MH*4DVqKYISI zsobumv!K@V+pgQf>fraw)$9-6?MP8Df3fAdB}H0b%uzL={53emPmr-U@Ou-# zU*mTNPIX~Nf8yc?`OiE$&zBsze~jycuLu~Gg^@J2Ou&AonW<~GW0j@SUu?L3%fm7ps}Pw@H%KcTDuOh!#_ph{#F`nq zJ;SUO#SFdtC?GR7_o-HeWd-2W{feS}nmIUKS5>yGtRM3)$F4VH1=AFn^*F@ZwKOkr z*w;#h3I!;yyxSakw_lq;p!X6C{pMI{iPND~DG5C#+`^O&Y}VJ_-I_v7p)6ave2z|w zxEEDUuBJ-o{<#|%G_sPUDi5Q`K;UaNg-I4EmzLs&G6!$z_v4B^~z zeU7e1nq$Wuu`7WnT52n^pms7gsb)|&cuIAFD1rmyBnPF3bfhe}6zmfPVn#4AR0b~Z zdX=4?et2pGzF%LxBus0i2}d`SOd|uq)YN)+qmKNNlunf5-O1X`nh5?MH>ia!3}~Oe zzIw6t(%lRW0C zoidCpzl;z;wLTF>)Ofu?GGXXM21gnzciTEa%^koQ>?uVoItV>%*vM4KdLsTJd_jp} zIu<4?g0u|z-p2}rJk`gGO?MQE4X`O@K%o@5u>Qf#cif<4<)VEAXAS~oLScKT3-`-* zK7nkK^4fA$BudXvPPYSRGyt5$vK;BLNXH)Nr+3cY52HX&(>3vdvJn4vD;||g170tg zP6V^lr~}<#6xJRn>sdNM31Ln07klmmfXvCfGUxq|m@**bg}Z643rnvE7c(yL19*3& z3b;QoImJr%w+ba~5UAsLu8%~jls2I~NjZf*NPJyvh;j@eUSC!vRD?w|j+&+iya0Ms zDvBvE&@_cxp-Z5}uCc}K3%Y1siD7rR^ngr0VHwKz8WwQIEOgvX!F5SkPK6{i!wws_ zqUY|Y8Gyj5oYoEPh1+Ju6OUo#_UcxG+65A^Jqpx@CH0Vx`_j?+1{_*BKj!#~{~%tN%7R8^sQVECj;N~8fkV7lzyZ|L8{yYKuXlnNF{ z6dhJwgU(hfoOhXJGWNBBxi+Y`mO3Aeyffc|dJsovnwI>Bw|{l-e}V+)fV*Lhis4(U zOWRvRVTPD`!+B|C3G58J5x@6Me{*BQ{KR^T$^XqBc);4hf6LoYyZW??jq5Z6Zqw}q zRG>~j9e?~V44wK9IP}40x$Xq9+qFEmKaWjB`MxMpS!L*OY8L)z=x1RXG>tQCX4NG5 EFQe^^ssI20 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/index.doctree b/mddocs/docs/_build/doctrees/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..6db1cb4f05e175ec86ab09a612dfc85ddd4fc63a GIT binary patch literal 42084 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/install/files.doctree b/mddocs/docs/_build/doctrees/install/files.doctree new file mode 100644 index 0000000000000000000000000000000000000000..5ac466012e573b41ae0a89499e2563aff81381d0 GIT binary patch literal 6284 zcmc&&eQz8`8TZ@SKEK5;O&iokY+D5!%f3rUE06`0C{0updWq5$ByiZ9+nu|a^WN@e zXEwH_Mxsb5u{08YbV1@X@G*sS&EBOLk{xp68k8`8}_{ zsr}*83lru~t@x3bN*;(t82MCWYF;l4XehJKv&)}mkF!lxPso9Y(!`^gn!pZ^@xV`L zm_5ig*@Svcq#Yr-1eqvwecI(A*N;O2tfv0hXgHsUG>)T0x-5$J#U~CQkRILLb(oZ~ z*ljlXpw|$L)4&%E9yL3}@6qO^?_Jr+&*9OW-01`DhMy=bA zvHhOi&hFrzGe+7vU_g2aiCI;t$Jc|vF@kLZ^G+B^XG_1ne{1Vg1}We#2;ElkKVXXGO|NJ@Bd?pyzoyh#3)FRBtt3+`+1NPtDppRp^K+ozvYy>+>P> z$)UqN_3FtwsgNBPZ!X}HCM2jzH<#KsL>@g?mra0a~%zHpjnymJ<+73h6c z1#boU^&#g1_%$O*d9*)0IWbW^{bJ~UWuQN!?^4oXr-|?~&euPGMj=N0Z$yHZ^S`Bv zjsHFjh!;+9AZSkzu-f0jjm`>J_HXtd_T>qXT&aj##_vm8TX)b1B?nxf zc+))qnl9afIR{)Ll<=UjwPj^T*(zibg}q92>0j5{w>Nw9g_KH@_iuh3tMzZ_-BQBV}rf7TrSrxyVrHP|@y=mDjXd=iddByvDY*sQMOi z9tvQ0*e?2R=g2kP%;T*8YkiL!LcMn4E)?^RGuFa}v{TAl2v_hOwnXfXdIf1R6ag`P zHF~2S!jo<6YBEA6k=>q93&kGl<2BV8ep0P?X`GHbDqTdLlpA?iXIB^h2!gD1%##>-+7MY@ec z>vEW>$!4Y&tq3mo?qkD3{+cEM-j-$Lxw`SxUm;2jGABowTJM4k%6VG(_!R9pR3D}T z_Ym=XkUdo^04$GPFB<51g#f|V?FtR3S#b}zWNy*?f?s|h?}nk4gqn#&Kp#=4b4o2R zw7zcsT2pKEkVA`-$W)Z8HJ^6U9@woMkR*f_u>&)Q0aofThe!35w0t~-&RmQYA|Jbu zY*N|X`N59hcz$E_qf3({N?e96B%lc_phfj$rp{T-SE$qsl}ue6`!YtN|9ikutysFH z48`d;Mn8-lR;&6Bz@A`GQDD;Ja*-3EaUY3s3KJl;kx(y6e2jf!AO~zXVAE2w@WC#w z35{^S00f&=D z&eEeCom1437!Zv99;WbW0Yz4!nqUMm=NN$J~MrI@E^;A3*{}sRWPn9(L`! zbbtiu5+AcpGd+8%*7B(duo`r2OgmQ7G<=j{u%YH{G=cj9iI_5CgaF*xVUW4p+W4eY zM{~Bp7KK_L`DDj(C{M>nkQ+jC=?=f^!IvKL>CA!xHo2`VSBa`oGlq zRn_%pc@#t4n5LNxPtEt^lwH*GwHCW)wz7MvTCUn!A5hC^_-sM~XSmwBF1y^%ueS|)ub0RbN+WB!>`4=Qf1RO6)bx=S?@}eMs zVQl`NyOFPdj@v!S?!kF5Yl0hMNGeeOTZY59Q(eXYXGI(dcwn(wDV^*vt?Z@q_dRv( z1L!`{*mEp0oVP|Md~Z2im{At=W=GN(FAa;wFJZ&y6_2*VzuNzow)Jy+qCCeLi z_~}nux9;e?V+Z|+gZU7)2AchUT6$`6s0oAeMIL%V>LbEMJ?*_~&rTaRTlkaNXBklo PzRneSSk;|H+G+e3+W1p= literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/install/full.doctree b/mddocs/docs/_build/doctrees/install/full.doctree new file mode 100644 index 0000000000000000000000000000000000000000..bbfcc9350bfe0eae07ad5e7667bf597ac216e2c3 GIT binary patch literal 4917 zcmc&&@oyYQ6}ID??K|6-IBqDYjaXWxxG26$3Kd`>pbD*^Rxc1zkRZ`|c6aV(?Ae{o z%xq#ytprFxi8Q};;WrTf1VV^^qEh<@^_!X9yYrC*h=dyDq}|y!Z{Bl*0Fyc45E*8RhCkjm`Qo8KZmj% zWS|+RBGUsdhY86hba?fx>wC>UJo=q`iKu8V!oPh()P$Ou3RzGi28xo*FbYS!B3cZu z4{m&W@b=JeAMzrjCj(}3(W}|oy&5ib6&+J}1P@!UVf5A+gIn2-w|idh4`03~-ZpD^&tCtp-@mLu4EHk9naD5n^v#39%pG^h}RB@Q7!%-JIX#t$YV3 zcihSB$M_BGHNZNjLyfy9YH0Fu=zSkL_ygj5&XYo;ZrIXFfivJ!8PSUkB?(?E2`b5p8bHh4Vv*-wxvJ7Mwfp zZS3Oa$wZNo&ByTT4PEGUVqZhGZd4EpBnd9j*GuL@B0f@H- zgW>IW-}%W0?|PI11uHEoJQ}u|IL3*P>G6Cte^c^u3JH(=-y)v(dcD^p!?=#{|ESUi zFErAiorvh(zi-h%+wrwA7r@OiRS8v6gIip6xNHBPM+E4=*T3oK5nN|1UHN@t`;1SR zz|X{}eU9dB_aqnlaKM%4^-@ty)dTk^NHM8UVnL?~(ae*_UqM}eX$ZvUT;PL;L>0W4 z%*R#TanZ}EVKQ@RrqL82$s!?5B*#(4^>O46z2-Ad(fbw0*%1tl-X{-8^hR{h1Tqpl zQA8mpBjjE}+v}oPKrQlAMYn!A2=QhuRM|h9_M?cEeHjUQ4l0QzZhi+ON@*5_B0)+np>`UO+LJQ)XLKOB1z!AEVj*%#X^}&+u zwK6orYvssb+4j>)xtbq0y}`(Cwky6X7}<4AjR9QIt!}&8>)Y3{l7#{JqZ#-WL4&BL zV79BWGG%4z5&LjtyAxV~kx?>gy!h_Kki5jphLY0tASk>iwOt;fwgnSnoO=v`Uh`Th zfwwIq)7W)Q?kh~`z-Dz?+e>4pDWsuuA0MNaMBIxik57;wdHvY-HCnYYPG#Y5os1^c z1jqH8q=y6=<{T?sTt`ZsOU^!CAZ9UYsmvTT*luOUd*9pB9M7+w{TNfFq>33zu%HSz z;7mS}*laI4AAmj05F+7;ajRuR zL_7l}p2PTJcN9&f$}qFc+6Kg76?8X-n}`U{T0pQ>+b5Ng@odSSa~;}NR^??pg(L#& z7tm)=RS@BB_JUvWV0X7aTPiiJG8u!wA$1X%CpATzINRw_f`om*H4l)z5QBP2A}Abb ztXykT1!=blXE2NvKJTD(s}YNRZF2*_SKO29MXB<+Neibf!NHsx5U3QY*07%A$ z=C~f?>lQ8du3rBR;@MZ@bV0panW=xHRgdH)4zeiN15VA@p8-M`y4engbqz_7MVJNa zgLQian9Sp8a!x5Q4yE&ny{JzMLr&spg>`d?bkN__Ilhu0Vl2|?T7 zLVG1;Xj)TTU&uE^*@qCjkE_yEZ&>Hs>xKrN06jA0#TXc9c;S*G3b5>gm>)55775Ad z2BhN}kjWF8A%jn00i)F5jLRvcj$@h=E@F~lQ1_$XW4qmS{lKai2TprpS7`B|MvHFO z1DhayfkceRDCWRjm}IgG&pX~>|h=5N-?*{zfV zAMS4wI47*Q1a9SmQ}Zi94)+KotlezZKY@3T>f1;abgf7_G-euoUN~^dGV{xrMEd$v zBj4K9X?0|sF#54s-{nU^^u2HW6d_j*X;HvI^xFJ1`kL#lQRt#+DrJLa0$?|eBnbR@ zEp6!hZyX88J+D==ou;5I+w%?rNXnm$-n-?BO!$)&NU!;GK?gdIj)FeBF%5)=^ad|d kQDvYrIdQ29h%ZdWyCeKF^&e|udzq^X4TZY)uSy1A0N3SZP5=M^ literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/install/index.doctree b/mddocs/docs/_build/doctrees/install/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..d60d223d0dbba383b2ed4b98b14073e5021a1ce4 GIT binary patch literal 8294 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/logging.doctree b/mddocs/docs/_build/doctrees/logging.doctree new file mode 100644 index 0000000000000000000000000000000000000000..4df378cd141da606391408851c86d0e819b13cd6 GIT binary patch literal 27666 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 KXp|e&%l```0}ado literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/plugins.doctree b/mddocs/docs/_build/doctrees/plugins.doctree new file mode 100644 index 0000000000000000000000000000000000000000..a1363c34b82c80385f1b513c733e87ebebe4ceeb GIT binary patch literal 24191 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%0A^~DqUpU{TOfPIr`gB{d#WN2eqA(hE1j%HHEl#K< zu6CR8G9>BaioR=-F{)#tFGcsgV-U@N^_bD? ztDA>(Hn-whH1eQmtx#k|+J=Z^MB~X*ZQLF?#?v2$x-}vJ5o}4G5BFY#P0`+q#Q#W4dTu>!O6sUUn?9_xsdms| zmzz!>*=1V6B3f$A;hss!L%t?AnWDirA3|JppHqxEeb6s=2&^le}WNk%wsm4Xx@zgsn&RtBYs&1|H@QTURJ z(m?t=-9O=aiyh@CT|3mF!rlYN=`c6PEZq>P=1_)|U&B)Iac2^@e^sd9w$Gy}bYN2y z!o7B-HZuvvCzVCgPJEdw|FU7Ep!~~oeNEU0wN>FNCx<3x^y*ltLO zWh_YtI^RWsPTGF5eaYI0WPaLAdNc(O3#{Z35$%i|$3mG57V<}EMn#a+WjPi29HWO` z-VcnmykrgEr+P4JxSSFAhc1dd(#%%c!G)HHW5E)q>-DpXPd)=jzw2Cgx;jt#(1v;& z+WIL4%NqN!Lrx-%!giX|==)5r@A{gaW@DOuN&>Fwn1@8OQ<38QS%aSW42nm@K^+z! zbwt{wlK4_VeuLM@lya}n2R0P~kekfIy%pi=9?rMCbj|Dc z&8A|@dKkOWrj6q_I%c8cG2GwODp~T;Pa0TixmMO$E@Mf|beWi{y$nCme?7B0Mo9bF z3%GuWJv#2P6`iYDf33{ye@=u&uxbl<~bL_eTt7p^wvLx;!nuoOWh$_1@g2>T2 z;5{1|+7RQ_oERIyG=xh91aj>r*l(lv7jwPWR{}I2Qv&bdN?;wdw1&t7x-kuLGqVKQ z9IN+_uOS%zN=0mb0L65_x(?Ib0v++$(Ij}Wg)A?IlU5U#96LnKGH3#-l_HEbk1n;t z070$8JP${_Ib4$ch4FdCl2%ALAh_i4^5|yDX+yKa+345dmzC~OqmQS?=1x=}JTfmQ zryP0yk78)^mZ{_f8nS^UlO9DYUTH>QtDA{JKxl@#X_ezcTByStHdIX{@>P)3!4nwd zH4D{MYV1o%vk4(zLe-oVC<&XYIdQTVL(c1jVK!uRht;mvsPc^Sla&)Esu4VEnM@c} z0fc~uzKVnjd}BEkND;ra%KvSC(?<1ee!HFgrWEY&t)pPax9VTW=u>Tm(mAp3!Iq;; zCcPXim9gcJXDwy5rC>&YNWaPeU<6 z5e-b}Buug}gsN-kK9k-htFoBMny8VunC_HMShc#vbcZ;XLDkN^beVyb@x*08Tz2zOk81LH8-+6U?euh#yIF%p z_sU{A-FAFb07F{X06$oj#@O!l@na?o0;nwYqA_^!Zk$yVS zavDh+ffvdhfJekIFG#x^T;PZUArZv39HQ3mP#xi`5XI;BbEj?MCMH4L)%ec2be&gmY) z0hIzH7bHS-A2+pj$xB zWI9V3x;6bMy@SeC?rJ5yeoe6?2+JO*tCtvNH+#aEomQXb;E(!|leB^wE}Y@f9!MTG z05!8G;}Xd1bq~g#vFz@eh{6C70p*kw6^PwVAcLd;pJhs7&|RWgjV@@jBB$@5Z4=V6i3BoocYCpAP0H9?J)d8_QF_vQoo|g|De+cxP4VRj^;pvGU{-@>e zRPKye_8QJcJttmbb3h?fsm3iPn?|KbND*q{4$kdJ?*~uzYDzej7Oe5Beyi!8XayzJ zmTT%NAJt=}A`0owsAr>o@5d5Ibp%4~W54;&8tz8hg0%OeY7^kWl|?RZpfV#NdS6TW zxH?ExhgsE8p$1mLJwem+m%sx;FWgCr0$MiLjFsBOTx@603&ma=fMt3Orv(XK0R*n& zP)WS~O|-gJb5KFATf?-I_YW`y!fUNxi4ye^jZLEDMTdp0=ZOieB<( zbKE2?b{>WG>U@AcE}{fT=K_8F4Sjr{J|3bMC+OoZ=;P1mL=id6=|Dw}$BpXkaNMX` z4abeDHgMd2i+v;9C_k0sM!A6;H%f2gxKWA($BmBf7;Y?dnWVc+X&@xiT_~%_R0l~Q ze?m%6w&RRoKH=?9iWt(|sVU=Cxt%H1j7ade*6_xe6{P4A3 z5}khd+Amj(;7ka>%^OO^!5LJ=?F8silyl0s&Q2$Ux3D!8#c_FT*mUGXP3r9wuj~;V z+1e&3W^AW-D%&PWM+EKoXfBh2^8^&XucqavAz>joQIdX&auj-`f=WtPBSlng2fl#8 zi(<0mO%f$Ow+3SxjXC4_&2G|ywG7%+ZM-D%>A;0>J6Ube G%KrlZU&KuS literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/quickstart.doctree b/mddocs/docs/_build/doctrees/quickstart.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ff3f3de81169eaad23faf010d667fd12c958c9a2 GIT binary patch literal 24475 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/strategy/incremental_batch_strategy.doctree b/mddocs/docs/_build/doctrees/strategy/incremental_batch_strategy.doctree new file mode 100644 index 0000000000000000000000000000000000000000..04fc84aeeff6fdadeb464017b51603a8c34600d0 GIT binary patch literal 3751 zcmc&%TaO$^72ey-?#%9vz22BWl2wKv!{%Xo)<6VWAqZGPL@UO~N#F&mRoz`PRoUvU zrY`ob6ia{<$(HIZ1y6`yz>nms>h77@!LbA{SQ@$O^yyRQ@_px={d4bc{}^q=e{NLB z+!`))Qptj9@AiVAq?Pes`w#!(U-(luP~^FmR^`;Y4QS+yi$c-LKlP_p2+3r)15C)3QC?Z0HUDR;;@Rq}!ZZG=1s`uCbe_xJhQe+0 zLZe7-Hy1=}2=UyjNZCB?6}Da;GLl~*3sGmA2;sWEz4Of`Vg$d0SH0jB z`xw8(Jp$xQI?=!dQ4>?vlbDxDpvI)T9E4c-l}5kP?p1=S_!!f!ruiDFS-)jZ59AlO znDfpjOOxYYg;2+zwxM=3+WCxLhaThJr2CbR9S9Nqp^0e9&Zn=fP>Y06p3kBFq)}&& zk&vC$4^G)M(Bh$|9f(eEWpx}AFzyoY=9JS_RQzt4_vTvn9eQ0OYw4o76lr#oM(n@v ztYnY0d;7I2C^ZzOP_P+&1L|A$u*dE#WVh0S#1;%1%@x&659k>Qy!p!wH|%t1fujwn z+H|Abs=%h?4>hF5dUhNFP=I4ovLR)$u9WdHXB?N~3s?)uW2+8RG) zpHJOjPAdwGOXYEhy}*#N#$jJc85j>>UwgMX@ov9yA;rB{P#CsHs{~GmM&SOH$p*qo#wI({L?V_ZX=;0GE%3{xnXJ7 zCvjivrKrKp2 zNCqfJPD4n+KVBhcDJGCCpyl1bn%UV;&osyLd$(VvR4J)ahVCk;LIr}Wx$*8!llc`Z zy=cX|leIT%CfGmSs4=>9p!@W_+b`C>baz7l0G?*Zk3c})9STy-EoM8?UEsWhO~D;2 znoCt+J*WX2T*HBxsq5#mnjuD{gBKkdSl^9^H8Nd_+*W9RhlMTcbb&`?H3~SOs460Y z!8=hBkseCF6xEvALZ;VcK+|2&PHKvdy!TeWn?MKfW)6`pX3rYaJXR>4|5=50a(%?|0_GToUklMODJ7nZ-NM4JKiY!es+PGEfL>m%rR|CjexbE||IK zf2pnmLSDJM`UX?*I%Ry$1bzVTj*tQO2f8F!5&vXCX^li3$8&Q`h0IBt+LN3S_=EB_ zWJ8u?3i0)ot#L(IY~x&Nd%!E8N9Mem0RwGQxEHzvTI`xy+~#l>X_y#xkt+|#lo>5R z-y3+qC^fj_eh#gxl$L~xloZ&Bmf7NcSdhXp7{!{!Dgl$jI@ute3j)`Sb~!7Yn}?$-Fxy<7Ith?VhR*8O!`?HkNPw3l4k&;l@g zaw!DLfEX}cb|1C!uS3TEb-Ag(@=w4QY@=Wv_GgXu*4(;iHLGGQfg^pRLAp-5n2)Tt z*qa70N9Kyu>_6ZB(S7_IR6zysjT>l;KUi7R))vSQ#$1mv2FDAo6^U=QC-(4*;pWDM ze#8EZRp5&~=s>lH|5o>E5QqqM(I9%>eRV>-#N#B>HS@dHsU`w%4KE^ z7dokAPPKP?K~U1l_^fHu3GR8%&Xysq{Q?}vW(RQW{H!vfs zG^cZ3@$k4XzH|Q3W+i6SAU4B#M&UFp5B;AzDq&lP5n+KAyzm6JF)? zI$@?1L(g_!c!V%ebWRZwBJ84u(}%Yl?sR+6?^V6O{r@A0&bc8mM)8?7C}b$yHqSMR zvT;@*AyKNZNkVMeN)=cMt*ZT`Zu zkF<5Wh7{pi&mOsc2djghDs9qQNg<1$d$%KaMg8TL>(>Njfip*TM%a4@ik+Zh-@)&@ z_*%ucDZLK8#NDm+rH?%Z{{OM@f6C6Mch;b-btu4RP=DU2vu6l@XZ64-n+D)N@w9`< z>8IMa^DDW$l$4g|mA!Uu1 zeI;e!F+h9m-R8u*{lE z-|nUu#5$>O43kwNRRKCZ3YyUYXr_jiD33uo_8>odeEuWkbEM|kn)x7En*UyB9?T^H zuUAwhoSJ#O1KnT}mLps?Fk}V_VVUxmd+r2)EYk%u7yU2QbwJ2lcVFLNwq2);7fj#> z@a_m1aDSjng2nI87L?XV)Ny=oUQ;16(x&z#V+8)7d=1%<<(NX8zOps02nf!s;UXnD zHr=>8y>UnFR0LMVylH4}-8QXWdQ2?0*R&FF7f8g8$-xbiHmg9~s*cw8cchR zLVIw_;-I@V9(r)gJ{qwy9?W{QZma!*d5HFsNgG-KhEFDiKp7ANrpxZrR{mAU*uO3} z^_TuR_<|i1%)?f!(cYR{7p-PhjMZ+WZ!}2PNf+~x^%ncl0Om-cNX`E9>z~}kZ%_pl zz&CE7G5%m>6`Ls(=flm74gC%KFqVMN_n-sS9{yY1j=G(w z+i_g4opqaTCjbPL{bc&P0h}de*dxg}gYLo1N+1?)B`< zFx_)!i<}@xfmmt@qzqqw0YU;E5CSCNfhYJE_zQSI2#IGN_*M7J?CkpEeBptU9PM;h zS69`qs=BK3?b;7Fu1=YMdc}{tREa>g!pLVb)AO`2WTDDF$X-C zhHjtj>@>MjvD|65#jxL!T(H2GEfKZ5uHR?vTOYfIq->#ye>=)e`b>>j zL?Kf_D`Bp$ID;aF5ewV5TX#O*daZ4aw?*i)gBDlAU?JmkZ)Z@Uk+2?vM$oX(8bU9R z30%w5dJ zf}xpl1C{X&J(DIP)31FffHJ+{xuGw7*Gi+OKa=rSWvb_t+ox+Yep%NGw_5sCB9#*- zQ4sY%o$1*?ge=>=pzE;xHb(=m}ZM zxA`i+!xvAyXTvS7BDw}|@ixD`dja$_y5W=&!)MYiys2*8vg#ts@P9|D1jgte?pRkk zRht^L5k3N~IAEmv9B!=9jr_CtYv~r;dC1xln7c_^4db@axlL~0E>7mo4&!DK19m_OLLVb|FEcpoG(^ecw6!s=BSa}gf2T;I>r)|`qwm9VPxmiAPq;dk`P zxS0?r@U?{Wgh4v`TDdJlH@_eJO>jFP4sjFU7=l9EN&RURLwdby;k zGCD@c^b+=ilu~S9h2-DlPx!a^yG+jy-23o0O7vMXTpOFUKK92B;FX-2!@beG8+~n>f)|BiKOWdH@O+}zY zSlAs{HhWRhq$fdy{U)-Jn}^@thd)1Z@}HjH$$y+1=3=3s(QK8$){tB=-w6c5boBm= z0<%Foj!{lq@sc*w4tMrq?Y&`U8xst)_k0a8!p4*r`w z;OinZDY+t@-P+S@#{|em_W9H4_Sw?2<~BVe6_VW?{<{r@GB)WEe}KV$Kj}$TB9s2< zC_gYSxgQN(5oY|adJgKS!x(kT4DIVhe6xc%5S~zQX4-!2CKLly0pw}>B#p>3f13li z4taikmOMmUpiRWTdK&RBDx7~#7K3a2vPAEOMX#C~PKWyX8N?Rt-xZAcKMlBlocP|; zmrlB>0_wld0G0pcTzLNLER&pu=YP(RC;!_iT2z6&1Z@6smLOFiiTLkNBQAg>;^RQR zI1z^93I2H4zHm1E1e}BB^wiWiqNAlfRO!VVHh%30*TF4fV2X>A9__93K|8n!E@#>4 z6m$n|oO>pKPt~Z3@pJsn;Dx71RE3%7ubqRwKpONXApO$uC>*!?LknJ+q6hbIrva}F z&(TsDp`)ceeXSa#RXGkAB#UQ6a>f+opL3)-_RaCD4rLR6ks4A*N|He7! z3sga0pvq1yAoV=G-S&B@qdw^hyfF0`4~aO)cpc46+M}O(x@8LZpMhz$%3V*pBj#Ir zm1>mjJhPixyTgh`C*Q49HP!BbRhyHq;Fi2au(h2ZChd+3-J>`6BG z0nEbhO8xwaRA@tirdUVhaF@@T$k153(o^w04=p|98P`$wPUw1JWkY)Lz z>rtxWRAn9hW=A*rETlRsN>I5sOjoMhVT_N(Btm7M@MX%gUT z86~Vk-7@V$lp2-8hnZgQflU}GR@p>tO$`^^#5q72Iz*vCJ%@-L^fuHlNHDx^S6E;= z28QLk#vCXNe&YQ&?(c)fgAQy z44PnZAKw?y@#u|&c~RnHRz-C$uAy-2=(-n$J*Wwb2)}R$1oDcTs>m5F=?m1IFZ<~* zc1o9oVaF6U%U(JJca=oc=LEHSakLgMBF@_m z`TbNcSFxr8dKvAWeUNaoU7?UJ=&YL674k!~3f~xZqo8yjvv_G@v7~N{acmT~nnUYj z{E?j#l_AzHPx8ulFn2-1@i6{^SK&qSq96b;7^~ySnY){>e*o>?%kCq1Fd#t~VzMid z>+FSP-l;BQn6e@dB_gm`E#FIC%O_@KRhobA>DCuv`-C36ruB^GO{97=%)HqYvWaI8 zBbK7PXypYcims1dn3|GjOm1zFc4x2!x-*-GTK>FE>secI&3>u#g?sem*#9ws6qpZ< jYv4I}#q!gOr4fvSFXodj1XQ1f;R`-J1bNQ1jd$z6`TVHs literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/strategy/snapshot_batch_strategy.doctree b/mddocs/docs/_build/doctrees/strategy/snapshot_batch_strategy.doctree new file mode 100644 index 0000000000000000000000000000000000000000..628c7b9367710b7e7f2129f9f79be72fa31a7108 GIT binary patch literal 3727 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/troubleshooting/spark.doctree b/mddocs/docs/_build/doctrees/troubleshooting/spark.doctree new file mode 100644 index 0000000000000000000000000000000000000000..845cb219165ba51ca1d7ffee77f5903f97537606 GIT binary patch literal 14303 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 + */ +let sd_id_to_elements = {}; +const storageKeyPrefix = "sphinx-design-tab-id-"; + +/** + * Create a key for a tab element. + * @param {HTMLElement} el - The tab element. + * @returns {[string, string, string] | null} - The key. + * + */ +function create_key(el) { + let syncId = el.getAttribute("data-sync-id"); + let syncGroup = el.getAttribute("data-sync-group"); + if (!syncId || !syncGroup) return null; + return [syncGroup, syncId, syncGroup + "--" + syncId]; +} + +/** + * Initialize the tab selection. + * + */ +function ready() { + // Find all tabs with sync data + + /** @type {string[]} */ + let groups = []; + + document.querySelectorAll(".sd-tab-label").forEach((label) => { + if (label instanceof HTMLElement) { + let data = create_key(label); + if (data) { + let [group, id, key] = data; + + // add click event listener + // @ts-ignore + label.onclick = onSDLabelClick; + + // store map of key to elements + if (!sd_id_to_elements[key]) { + sd_id_to_elements[key] = []; + } + sd_id_to_elements[key].push(label); + + if (groups.indexOf(group) === -1) { + groups.push(group); + // Check if a specific tab has been selected via URL parameter + const tabParam = new URLSearchParams(window.location.search).get( + group + ); + if (tabParam) { + console.log( + "sphinx-design: Selecting tab id for group '" + + group + + "' from URL parameter: " + + tabParam + ); + window.sessionStorage.setItem(storageKeyPrefix + group, tabParam); + } + } + + // Check is a specific tab has been selected previously + let previousId = window.sessionStorage.getItem( + storageKeyPrefix + group + ); + if (previousId === id) { + // console.log( + // "sphinx-design: Selecting tab from session storage: " + id + // ); + // @ts-ignore + label.previousElementSibling.checked = true; + } + } + } + }); +} + +/** + * Activate other tabs with the same sync id. + * + * @this {HTMLElement} - The element that was clicked. + */ +function onSDLabelClick() { + let data = create_key(this); + if (!data) return; + let [group, id, key] = data; + for (const label of sd_id_to_elements[key]) { + if (label === this) continue; + // @ts-ignore + label.previousElementSibling.checked = true; + } + window.sessionStorage.setItem(storageKeyPrefix + group, id); +} + +document.addEventListener("DOMContentLoaded", ready, false); diff --git a/mddocs/docs/_build/markdown/_sphinx_design_static/sphinx-design.min.css b/mddocs/docs/_build/markdown/_sphinx_design_static/sphinx-design.min.css new file mode 100644 index 000000000..860c36da0 --- /dev/null +++ b/mddocs/docs/_build/markdown/_sphinx_design_static/sphinx-design.min.css @@ -0,0 +1 @@ +.sd-bg-primary{background-color:var(--sd-color-primary) !important}.sd-bg-text-primary{color:var(--sd-color-primary-text) !important}button.sd-bg-primary:focus,button.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}a.sd-bg-primary:focus,a.sd-bg-primary:hover{background-color:var(--sd-color-primary-highlight) !important}.sd-bg-secondary{background-color:var(--sd-color-secondary) !important}.sd-bg-text-secondary{color:var(--sd-color-secondary-text) !important}button.sd-bg-secondary:focus,button.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}a.sd-bg-secondary:focus,a.sd-bg-secondary:hover{background-color:var(--sd-color-secondary-highlight) !important}.sd-bg-success{background-color:var(--sd-color-success) !important}.sd-bg-text-success{color:var(--sd-color-success-text) !important}button.sd-bg-success:focus,button.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}a.sd-bg-success:focus,a.sd-bg-success:hover{background-color:var(--sd-color-success-highlight) !important}.sd-bg-info{background-color:var(--sd-color-info) !important}.sd-bg-text-info{color:var(--sd-color-info-text) !important}button.sd-bg-info:focus,button.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}a.sd-bg-info:focus,a.sd-bg-info:hover{background-color:var(--sd-color-info-highlight) !important}.sd-bg-warning{background-color:var(--sd-color-warning) !important}.sd-bg-text-warning{color:var(--sd-color-warning-text) !important}button.sd-bg-warning:focus,button.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}a.sd-bg-warning:focus,a.sd-bg-warning:hover{background-color:var(--sd-color-warning-highlight) !important}.sd-bg-danger{background-color:var(--sd-color-danger) !important}.sd-bg-text-danger{color:var(--sd-color-danger-text) !important}button.sd-bg-danger:focus,button.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}a.sd-bg-danger:focus,a.sd-bg-danger:hover{background-color:var(--sd-color-danger-highlight) !important}.sd-bg-light{background-color:var(--sd-color-light) !important}.sd-bg-text-light{color:var(--sd-color-light-text) !important}button.sd-bg-light:focus,button.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}a.sd-bg-light:focus,a.sd-bg-light:hover{background-color:var(--sd-color-light-highlight) !important}.sd-bg-muted{background-color:var(--sd-color-muted) !important}.sd-bg-text-muted{color:var(--sd-color-muted-text) !important}button.sd-bg-muted:focus,button.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}a.sd-bg-muted:focus,a.sd-bg-muted:hover{background-color:var(--sd-color-muted-highlight) !important}.sd-bg-dark{background-color:var(--sd-color-dark) !important}.sd-bg-text-dark{color:var(--sd-color-dark-text) !important}button.sd-bg-dark:focus,button.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}a.sd-bg-dark:focus,a.sd-bg-dark:hover{background-color:var(--sd-color-dark-highlight) !important}.sd-bg-black{background-color:var(--sd-color-black) !important}.sd-bg-text-black{color:var(--sd-color-black-text) !important}button.sd-bg-black:focus,button.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}a.sd-bg-black:focus,a.sd-bg-black:hover{background-color:var(--sd-color-black-highlight) !important}.sd-bg-white{background-color:var(--sd-color-white) !important}.sd-bg-text-white{color:var(--sd-color-white-text) !important}button.sd-bg-white:focus,button.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}a.sd-bg-white:focus,a.sd-bg-white:hover{background-color:var(--sd-color-white-highlight) !important}.sd-text-primary,.sd-text-primary>p{color:var(--sd-color-primary) !important}a.sd-text-primary:focus,a.sd-text-primary:hover{color:var(--sd-color-primary-highlight) !important}.sd-text-secondary,.sd-text-secondary>p{color:var(--sd-color-secondary) !important}a.sd-text-secondary:focus,a.sd-text-secondary:hover{color:var(--sd-color-secondary-highlight) !important}.sd-text-success,.sd-text-success>p{color:var(--sd-color-success) !important}a.sd-text-success:focus,a.sd-text-success:hover{color:var(--sd-color-success-highlight) !important}.sd-text-info,.sd-text-info>p{color:var(--sd-color-info) !important}a.sd-text-info:focus,a.sd-text-info:hover{color:var(--sd-color-info-highlight) !important}.sd-text-warning,.sd-text-warning>p{color:var(--sd-color-warning) !important}a.sd-text-warning:focus,a.sd-text-warning:hover{color:var(--sd-color-warning-highlight) !important}.sd-text-danger,.sd-text-danger>p{color:var(--sd-color-danger) !important}a.sd-text-danger:focus,a.sd-text-danger:hover{color:var(--sd-color-danger-highlight) !important}.sd-text-light,.sd-text-light>p{color:var(--sd-color-light) !important}a.sd-text-light:focus,a.sd-text-light:hover{color:var(--sd-color-light-highlight) !important}.sd-text-muted,.sd-text-muted>p{color:var(--sd-color-muted) !important}a.sd-text-muted:focus,a.sd-text-muted:hover{color:var(--sd-color-muted-highlight) !important}.sd-text-dark,.sd-text-dark>p{color:var(--sd-color-dark) !important}a.sd-text-dark:focus,a.sd-text-dark:hover{color:var(--sd-color-dark-highlight) !important}.sd-text-black,.sd-text-black>p{color:var(--sd-color-black) !important}a.sd-text-black:focus,a.sd-text-black:hover{color:var(--sd-color-black-highlight) !important}.sd-text-white,.sd-text-white>p{color:var(--sd-color-white) !important}a.sd-text-white:focus,a.sd-text-white:hover{color:var(--sd-color-white-highlight) !important}.sd-outline-primary{border-color:var(--sd-color-primary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-primary:focus,a.sd-outline-primary:hover{border-color:var(--sd-color-primary-highlight) !important}.sd-outline-secondary{border-color:var(--sd-color-secondary) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-secondary:focus,a.sd-outline-secondary:hover{border-color:var(--sd-color-secondary-highlight) !important}.sd-outline-success{border-color:var(--sd-color-success) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-success:focus,a.sd-outline-success:hover{border-color:var(--sd-color-success-highlight) !important}.sd-outline-info{border-color:var(--sd-color-info) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-info:focus,a.sd-outline-info:hover{border-color:var(--sd-color-info-highlight) !important}.sd-outline-warning{border-color:var(--sd-color-warning) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-warning:focus,a.sd-outline-warning:hover{border-color:var(--sd-color-warning-highlight) !important}.sd-outline-danger{border-color:var(--sd-color-danger) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-danger:focus,a.sd-outline-danger:hover{border-color:var(--sd-color-danger-highlight) !important}.sd-outline-light{border-color:var(--sd-color-light) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-light:focus,a.sd-outline-light:hover{border-color:var(--sd-color-light-highlight) !important}.sd-outline-muted{border-color:var(--sd-color-muted) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-muted:focus,a.sd-outline-muted:hover{border-color:var(--sd-color-muted-highlight) !important}.sd-outline-dark{border-color:var(--sd-color-dark) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-dark:focus,a.sd-outline-dark:hover{border-color:var(--sd-color-dark-highlight) !important}.sd-outline-black{border-color:var(--sd-color-black) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-black:focus,a.sd-outline-black:hover{border-color:var(--sd-color-black-highlight) !important}.sd-outline-white{border-color:var(--sd-color-white) !important;border-style:solid !important;border-width:1px !important}a.sd-outline-white:focus,a.sd-outline-white:hover{border-color:var(--sd-color-white-highlight) !important}.sd-bg-transparent{background-color:transparent !important}.sd-outline-transparent{border-color:transparent !important}.sd-text-transparent{color:transparent !important}.sd-p-0{padding:0 !important}.sd-pt-0,.sd-py-0{padding-top:0 !important}.sd-pr-0,.sd-px-0{padding-right:0 !important}.sd-pb-0,.sd-py-0{padding-bottom:0 !important}.sd-pl-0,.sd-px-0{padding-left:0 !important}.sd-p-1{padding:.25rem !important}.sd-pt-1,.sd-py-1{padding-top:.25rem !important}.sd-pr-1,.sd-px-1{padding-right:.25rem !important}.sd-pb-1,.sd-py-1{padding-bottom:.25rem !important}.sd-pl-1,.sd-px-1{padding-left:.25rem !important}.sd-p-2{padding:.5rem !important}.sd-pt-2,.sd-py-2{padding-top:.5rem !important}.sd-pr-2,.sd-px-2{padding-right:.5rem !important}.sd-pb-2,.sd-py-2{padding-bottom:.5rem !important}.sd-pl-2,.sd-px-2{padding-left:.5rem !important}.sd-p-3{padding:1rem !important}.sd-pt-3,.sd-py-3{padding-top:1rem !important}.sd-pr-3,.sd-px-3{padding-right:1rem !important}.sd-pb-3,.sd-py-3{padding-bottom:1rem !important}.sd-pl-3,.sd-px-3{padding-left:1rem !important}.sd-p-4{padding:1.5rem !important}.sd-pt-4,.sd-py-4{padding-top:1.5rem !important}.sd-pr-4,.sd-px-4{padding-right:1.5rem !important}.sd-pb-4,.sd-py-4{padding-bottom:1.5rem !important}.sd-pl-4,.sd-px-4{padding-left:1.5rem !important}.sd-p-5{padding:3rem !important}.sd-pt-5,.sd-py-5{padding-top:3rem !important}.sd-pr-5,.sd-px-5{padding-right:3rem !important}.sd-pb-5,.sd-py-5{padding-bottom:3rem !important}.sd-pl-5,.sd-px-5{padding-left:3rem !important}.sd-m-auto{margin:auto !important}.sd-mt-auto,.sd-my-auto{margin-top:auto !important}.sd-mr-auto,.sd-mx-auto{margin-right:auto !important}.sd-mb-auto,.sd-my-auto{margin-bottom:auto !important}.sd-ml-auto,.sd-mx-auto{margin-left:auto !important}.sd-m-0{margin:0 !important}.sd-mt-0,.sd-my-0{margin-top:0 !important}.sd-mr-0,.sd-mx-0{margin-right:0 !important}.sd-mb-0,.sd-my-0{margin-bottom:0 !important}.sd-ml-0,.sd-mx-0{margin-left:0 !important}.sd-m-1{margin:.25rem !important}.sd-mt-1,.sd-my-1{margin-top:.25rem !important}.sd-mr-1,.sd-mx-1{margin-right:.25rem !important}.sd-mb-1,.sd-my-1{margin-bottom:.25rem !important}.sd-ml-1,.sd-mx-1{margin-left:.25rem !important}.sd-m-2{margin:.5rem !important}.sd-mt-2,.sd-my-2{margin-top:.5rem !important}.sd-mr-2,.sd-mx-2{margin-right:.5rem !important}.sd-mb-2,.sd-my-2{margin-bottom:.5rem !important}.sd-ml-2,.sd-mx-2{margin-left:.5rem !important}.sd-m-3{margin:1rem !important}.sd-mt-3,.sd-my-3{margin-top:1rem !important}.sd-mr-3,.sd-mx-3{margin-right:1rem !important}.sd-mb-3,.sd-my-3{margin-bottom:1rem !important}.sd-ml-3,.sd-mx-3{margin-left:1rem !important}.sd-m-4{margin:1.5rem !important}.sd-mt-4,.sd-my-4{margin-top:1.5rem !important}.sd-mr-4,.sd-mx-4{margin-right:1.5rem !important}.sd-mb-4,.sd-my-4{margin-bottom:1.5rem !important}.sd-ml-4,.sd-mx-4{margin-left:1.5rem !important}.sd-m-5{margin:3rem !important}.sd-mt-5,.sd-my-5{margin-top:3rem !important}.sd-mr-5,.sd-mx-5{margin-right:3rem !important}.sd-mb-5,.sd-my-5{margin-bottom:3rem !important}.sd-ml-5,.sd-mx-5{margin-left:3rem !important}.sd-w-25{width:25% !important}.sd-w-50{width:50% !important}.sd-w-75{width:75% !important}.sd-w-100{width:100% !important}.sd-w-auto{width:auto !important}.sd-h-25{height:25% !important}.sd-h-50{height:50% !important}.sd-h-75{height:75% !important}.sd-h-100{height:100% !important}.sd-h-auto{height:auto !important}.sd-d-none{display:none !important}.sd-d-inline{display:inline !important}.sd-d-inline-block{display:inline-block !important}.sd-d-block{display:block !important}.sd-d-grid{display:grid !important}.sd-d-flex-row{display:-ms-flexbox !important;display:flex !important;flex-direction:row !important}.sd-d-flex-column{display:-ms-flexbox !important;display:flex !important;flex-direction:column !important}.sd-d-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}@media(min-width: 576px){.sd-d-sm-none{display:none !important}.sd-d-sm-inline{display:inline !important}.sd-d-sm-inline-block{display:inline-block !important}.sd-d-sm-block{display:block !important}.sd-d-sm-grid{display:grid !important}.sd-d-sm-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-sm-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 768px){.sd-d-md-none{display:none !important}.sd-d-md-inline{display:inline !important}.sd-d-md-inline-block{display:inline-block !important}.sd-d-md-block{display:block !important}.sd-d-md-grid{display:grid !important}.sd-d-md-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-md-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 992px){.sd-d-lg-none{display:none !important}.sd-d-lg-inline{display:inline !important}.sd-d-lg-inline-block{display:inline-block !important}.sd-d-lg-block{display:block !important}.sd-d-lg-grid{display:grid !important}.sd-d-lg-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-lg-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}@media(min-width: 1200px){.sd-d-xl-none{display:none !important}.sd-d-xl-inline{display:inline !important}.sd-d-xl-inline-block{display:inline-block !important}.sd-d-xl-block{display:block !important}.sd-d-xl-grid{display:grid !important}.sd-d-xl-flex{display:-ms-flexbox !important;display:flex !important}.sd-d-xl-inline-flex{display:-ms-inline-flexbox !important;display:inline-flex !important}}.sd-align-major-start{justify-content:flex-start !important}.sd-align-major-end{justify-content:flex-end !important}.sd-align-major-center{justify-content:center !important}.sd-align-major-justify{justify-content:space-between !important}.sd-align-major-spaced{justify-content:space-evenly !important}.sd-align-minor-start{align-items:flex-start !important}.sd-align-minor-end{align-items:flex-end !important}.sd-align-minor-center{align-items:center !important}.sd-align-minor-stretch{align-items:stretch !important}.sd-text-justify{text-align:justify !important}.sd-text-left{text-align:left !important}.sd-text-right{text-align:right !important}.sd-text-center{text-align:center !important}.sd-font-weight-light{font-weight:300 !important}.sd-font-weight-lighter{font-weight:lighter !important}.sd-font-weight-normal{font-weight:400 !important}.sd-font-weight-bold{font-weight:700 !important}.sd-font-weight-bolder{font-weight:bolder !important}.sd-font-italic{font-style:italic !important}.sd-text-decoration-none{text-decoration:none !important}.sd-text-lowercase{text-transform:lowercase !important}.sd-text-uppercase{text-transform:uppercase !important}.sd-text-capitalize{text-transform:capitalize !important}.sd-text-wrap{white-space:normal !important}.sd-text-nowrap{white-space:nowrap !important}.sd-text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sd-fs-1,.sd-fs-1>p{font-size:calc(1.375rem + 1.5vw) !important;line-height:unset !important}.sd-fs-2,.sd-fs-2>p{font-size:calc(1.325rem + 0.9vw) !important;line-height:unset !important}.sd-fs-3,.sd-fs-3>p{font-size:calc(1.3rem + 0.6vw) !important;line-height:unset !important}.sd-fs-4,.sd-fs-4>p{font-size:calc(1.275rem + 0.3vw) !important;line-height:unset !important}.sd-fs-5,.sd-fs-5>p{font-size:1.25rem !important;line-height:unset !important}.sd-fs-6,.sd-fs-6>p{font-size:1rem !important;line-height:unset !important}.sd-border-0{border:0 solid !important}.sd-border-top-0{border-top:0 solid !important}.sd-border-bottom-0{border-bottom:0 solid !important}.sd-border-right-0{border-right:0 solid !important}.sd-border-left-0{border-left:0 solid !important}.sd-border-1{border:1px solid !important}.sd-border-top-1{border-top:1px solid !important}.sd-border-bottom-1{border-bottom:1px solid !important}.sd-border-right-1{border-right:1px solid !important}.sd-border-left-1{border-left:1px solid !important}.sd-border-2{border:2px solid !important}.sd-border-top-2{border-top:2px solid !important}.sd-border-bottom-2{border-bottom:2px solid !important}.sd-border-right-2{border-right:2px solid !important}.sd-border-left-2{border-left:2px solid !important}.sd-border-3{border:3px solid !important}.sd-border-top-3{border-top:3px solid !important}.sd-border-bottom-3{border-bottom:3px solid !important}.sd-border-right-3{border-right:3px solid !important}.sd-border-left-3{border-left:3px solid !important}.sd-border-4{border:4px solid !important}.sd-border-top-4{border-top:4px solid !important}.sd-border-bottom-4{border-bottom:4px solid !important}.sd-border-right-4{border-right:4px solid !important}.sd-border-left-4{border-left:4px solid !important}.sd-border-5{border:5px solid !important}.sd-border-top-5{border-top:5px solid !important}.sd-border-bottom-5{border-bottom:5px solid !important}.sd-border-right-5{border-right:5px solid !important}.sd-border-left-5{border-left:5px solid !important}.sd-rounded-0{border-radius:0 !important}.sd-rounded-1{border-radius:.2rem !important}.sd-rounded-2{border-radius:.3rem !important}.sd-rounded-3{border-radius:.5rem !important}.sd-rounded-pill{border-radius:50rem !important}.sd-rounded-circle{border-radius:50% !important}.shadow-none{box-shadow:none !important}.sd-shadow-sm{box-shadow:0 .125rem .25rem var(--sd-color-shadow) !important}.sd-shadow-md{box-shadow:0 .5rem 1rem var(--sd-color-shadow) !important}.sd-shadow-lg{box-shadow:0 1rem 3rem var(--sd-color-shadow) !important}@keyframes sd-slide-from-left{0%{transform:translateX(-100%)}100%{transform:translateX(0)}}@keyframes sd-slide-from-right{0%{transform:translateX(200%)}100%{transform:translateX(0)}}@keyframes sd-grow100{0%{transform:scale(0);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50{0%{transform:scale(0.5);opacity:.5}100%{transform:scale(1);opacity:1}}@keyframes sd-grow50-rot20{0%{transform:scale(0.5) rotateZ(-20deg);opacity:.5}75%{transform:scale(1) rotateZ(5deg);opacity:1}95%{transform:scale(1) rotateZ(-1deg);opacity:1}100%{transform:scale(1) rotateZ(0);opacity:1}}.sd-animate-slide-from-left{animation:1s ease-out 0s 1 normal none running sd-slide-from-left}.sd-animate-slide-from-right{animation:1s ease-out 0s 1 normal none running sd-slide-from-right}.sd-animate-grow100{animation:1s ease-out 0s 1 normal none running sd-grow100}.sd-animate-grow50{animation:1s ease-out 0s 1 normal none running sd-grow50}.sd-animate-grow50-rot20{animation:1s ease-out 0s 1 normal none running sd-grow50-rot20}.sd-badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.sd-badge:empty{display:none}a.sd-badge{text-decoration:none}.sd-btn .sd-badge{position:relative;top:-1px}.sd-btn{background-color:transparent;border:1px solid transparent;border-radius:.25rem;cursor:pointer;display:inline-block;font-weight:400;font-size:1rem;line-height:1.5;padding:.375rem .75rem;text-align:center;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;vertical-align:middle;user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none}.sd-btn:hover{text-decoration:none}@media(prefers-reduced-motion: reduce){.sd-btn{transition:none}}.sd-btn-primary,.sd-btn-outline-primary:hover,.sd-btn-outline-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-primary:hover,.sd-btn-primary:focus{color:var(--sd-color-primary-text) !important;background-color:var(--sd-color-primary-highlight) !important;border-color:var(--sd-color-primary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-primary{color:var(--sd-color-primary) !important;border-color:var(--sd-color-primary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary,.sd-btn-outline-secondary:hover,.sd-btn-outline-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-secondary:hover,.sd-btn-secondary:focus{color:var(--sd-color-secondary-text) !important;background-color:var(--sd-color-secondary-highlight) !important;border-color:var(--sd-color-secondary-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-secondary{color:var(--sd-color-secondary) !important;border-color:var(--sd-color-secondary) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success,.sd-btn-outline-success:hover,.sd-btn-outline-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-success:hover,.sd-btn-success:focus{color:var(--sd-color-success-text) !important;background-color:var(--sd-color-success-highlight) !important;border-color:var(--sd-color-success-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-success{color:var(--sd-color-success) !important;border-color:var(--sd-color-success) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info,.sd-btn-outline-info:hover,.sd-btn-outline-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-info:hover,.sd-btn-info:focus{color:var(--sd-color-info-text) !important;background-color:var(--sd-color-info-highlight) !important;border-color:var(--sd-color-info-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-info{color:var(--sd-color-info) !important;border-color:var(--sd-color-info) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning,.sd-btn-outline-warning:hover,.sd-btn-outline-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-warning:hover,.sd-btn-warning:focus{color:var(--sd-color-warning-text) !important;background-color:var(--sd-color-warning-highlight) !important;border-color:var(--sd-color-warning-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-warning{color:var(--sd-color-warning) !important;border-color:var(--sd-color-warning) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger,.sd-btn-outline-danger:hover,.sd-btn-outline-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-danger:hover,.sd-btn-danger:focus{color:var(--sd-color-danger-text) !important;background-color:var(--sd-color-danger-highlight) !important;border-color:var(--sd-color-danger-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-danger{color:var(--sd-color-danger) !important;border-color:var(--sd-color-danger) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light,.sd-btn-outline-light:hover,.sd-btn-outline-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-light:hover,.sd-btn-light:focus{color:var(--sd-color-light-text) !important;background-color:var(--sd-color-light-highlight) !important;border-color:var(--sd-color-light-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-light{color:var(--sd-color-light) !important;border-color:var(--sd-color-light) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted,.sd-btn-outline-muted:hover,.sd-btn-outline-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-muted:hover,.sd-btn-muted:focus{color:var(--sd-color-muted-text) !important;background-color:var(--sd-color-muted-highlight) !important;border-color:var(--sd-color-muted-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-muted{color:var(--sd-color-muted) !important;border-color:var(--sd-color-muted) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark,.sd-btn-outline-dark:hover,.sd-btn-outline-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-dark:hover,.sd-btn-dark:focus{color:var(--sd-color-dark-text) !important;background-color:var(--sd-color-dark-highlight) !important;border-color:var(--sd-color-dark-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-dark{color:var(--sd-color-dark) !important;border-color:var(--sd-color-dark) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black,.sd-btn-outline-black:hover,.sd-btn-outline-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-black:hover,.sd-btn-black:focus{color:var(--sd-color-black-text) !important;background-color:var(--sd-color-black-highlight) !important;border-color:var(--sd-color-black-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-black{color:var(--sd-color-black) !important;border-color:var(--sd-color-black) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white,.sd-btn-outline-white:hover,.sd-btn-outline-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-btn-white:hover,.sd-btn-white:focus{color:var(--sd-color-white-text) !important;background-color:var(--sd-color-white-highlight) !important;border-color:var(--sd-color-white-highlight) !important;border-width:1px !important;border-style:solid !important}.sd-btn-outline-white{color:var(--sd-color-white) !important;border-color:var(--sd-color-white) !important;border-width:1px !important;border-style:solid !important}.sd-stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.sd-hide-link-text{font-size:0}.sd-octicon,.sd-material-icon{display:inline-block;fill:currentColor;vertical-align:middle}.sd-avatar-xs{border-radius:50%;object-fit:cover;object-position:center;width:1rem;height:1rem}.sd-avatar-sm{border-radius:50%;object-fit:cover;object-position:center;width:3rem;height:3rem}.sd-avatar-md{border-radius:50%;object-fit:cover;object-position:center;width:5rem;height:5rem}.sd-avatar-lg{border-radius:50%;object-fit:cover;object-position:center;width:7rem;height:7rem}.sd-avatar-xl{border-radius:50%;object-fit:cover;object-position:center;width:10rem;height:10rem}.sd-avatar-inherit{border-radius:50%;object-fit:cover;object-position:center;width:inherit;height:inherit}.sd-avatar-initial{border-radius:50%;object-fit:cover;object-position:center;width:initial;height:initial}.sd-card{background-clip:border-box;background-color:var(--sd-color-card-background);border:1px solid var(--sd-color-card-border);border-radius:.25rem;color:var(--sd-color-card-text);display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;position:relative;word-wrap:break-word}.sd-card>hr{margin-left:0;margin-right:0}.sd-card-hover:hover{border-color:var(--sd-color-card-border-hover);transform:scale(1.01)}.sd-card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem 1rem}.sd-card-title{margin-bottom:.5rem}.sd-card-subtitle{margin-top:-0.25rem;margin-bottom:0}.sd-card-text:last-child{margin-bottom:0}.sd-card-link:hover{text-decoration:none}.sd-card-link+.card-link{margin-left:1rem}.sd-card-header{padding:.5rem 1rem;margin-bottom:0;background-color:var(--sd-color-card-header);border-bottom:1px solid var(--sd-color-card-border)}.sd-card-header:first-child{border-radius:calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0}.sd-card-footer{padding:.5rem 1rem;background-color:var(--sd-color-card-footer);border-top:1px solid var(--sd-color-card-border)}.sd-card-footer:last-child{border-radius:0 0 calc(0.25rem - 1px) calc(0.25rem - 1px)}.sd-card-header-tabs{margin-right:-0.5rem;margin-bottom:-0.5rem;margin-left:-0.5rem;border-bottom:0}.sd-card-header-pills{margin-right:-0.5rem;margin-left:-0.5rem}.sd-card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom,.sd-card-img-top{width:100%}.sd-card-img,.sd-card-img-top{border-top-left-radius:calc(0.25rem - 1px);border-top-right-radius:calc(0.25rem - 1px)}.sd-card-img,.sd-card-img-bottom{border-bottom-left-radius:calc(0.25rem - 1px);border-bottom-right-radius:calc(0.25rem - 1px)}.sd-cards-carousel{width:100%;display:flex;flex-wrap:nowrap;-ms-flex-direction:row;flex-direction:row;overflow-x:hidden;scroll-snap-type:x mandatory}.sd-cards-carousel.sd-show-scrollbar{overflow-x:auto}.sd-cards-carousel:hover,.sd-cards-carousel:focus{overflow-x:auto}.sd-cards-carousel>.sd-card{flex-shrink:0;scroll-snap-align:start}.sd-cards-carousel>.sd-card:not(:last-child){margin-right:3px}.sd-card-cols-1>.sd-card{width:90%}.sd-card-cols-2>.sd-card{width:45%}.sd-card-cols-3>.sd-card{width:30%}.sd-card-cols-4>.sd-card{width:22.5%}.sd-card-cols-5>.sd-card{width:18%}.sd-card-cols-6>.sd-card{width:15%}.sd-card-cols-7>.sd-card{width:12.8571428571%}.sd-card-cols-8>.sd-card{width:11.25%}.sd-card-cols-9>.sd-card{width:10%}.sd-card-cols-10>.sd-card{width:9%}.sd-card-cols-11>.sd-card{width:8.1818181818%}.sd-card-cols-12>.sd-card{width:7.5%}.sd-container,.sd-container-fluid,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container-xl{margin-left:auto;margin-right:auto;padding-left:var(--sd-gutter-x, 0.75rem);padding-right:var(--sd-gutter-x, 0.75rem);width:100%}@media(min-width: 576px){.sd-container-sm,.sd-container{max-width:540px}}@media(min-width: 768px){.sd-container-md,.sd-container-sm,.sd-container{max-width:720px}}@media(min-width: 992px){.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:960px}}@media(min-width: 1200px){.sd-container-xl,.sd-container-lg,.sd-container-md,.sd-container-sm,.sd-container{max-width:1140px}}.sd-row{--sd-gutter-x: 1.5rem;--sd-gutter-y: 0;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-top:calc(var(--sd-gutter-y) * -1);margin-right:calc(var(--sd-gutter-x) * -0.5);margin-left:calc(var(--sd-gutter-x) * -0.5)}.sd-row>*{box-sizing:border-box;flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--sd-gutter-x) * 0.5);padding-left:calc(var(--sd-gutter-x) * 0.5);margin-top:var(--sd-gutter-y)}.sd-col{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-auto>*{flex:0 0 auto;width:auto}.sd-row-cols-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}@media(min-width: 576px){.sd-col-sm{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-sm-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-sm-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-sm-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-sm-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-sm-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-sm-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-sm-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-sm-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-sm-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-sm-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-sm-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-sm-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-sm-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 768px){.sd-col-md{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-md-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-md-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-md-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-md-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-md-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-md-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-md-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-md-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-md-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-md-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-md-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-md-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-md-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 992px){.sd-col-lg{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-lg-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-lg-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-lg-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-lg-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-lg-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-lg-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-lg-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-lg-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-lg-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-lg-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-lg-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-lg-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-lg-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}@media(min-width: 1200px){.sd-col-xl{flex:1 0 0%;-ms-flex:1 0 0%}.sd-row-cols-xl-auto{flex:1 0 auto;-ms-flex:1 0 auto;width:100%}.sd-row-cols-xl-1>*{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-row-cols-xl-2>*{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-row-cols-xl-3>*{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-row-cols-xl-4>*{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-row-cols-xl-5>*{flex:0 0 auto;-ms-flex:0 0 auto;width:20%}.sd-row-cols-xl-6>*{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-row-cols-xl-7>*{flex:0 0 auto;-ms-flex:0 0 auto;width:14.2857142857%}.sd-row-cols-xl-8>*{flex:0 0 auto;-ms-flex:0 0 auto;width:12.5%}.sd-row-cols-xl-9>*{flex:0 0 auto;-ms-flex:0 0 auto;width:11.1111111111%}.sd-row-cols-xl-10>*{flex:0 0 auto;-ms-flex:0 0 auto;width:10%}.sd-row-cols-xl-11>*{flex:0 0 auto;-ms-flex:0 0 auto;width:9.0909090909%}.sd-row-cols-xl-12>*{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}}.sd-col-auto{flex:0 0 auto;-ms-flex:0 0 auto;width:auto}.sd-col-1{flex:0 0 auto;-ms-flex:0 0 auto;width:8.3333333333%}.sd-col-2{flex:0 0 auto;-ms-flex:0 0 auto;width:16.6666666667%}.sd-col-3{flex:0 0 auto;-ms-flex:0 0 auto;width:25%}.sd-col-4{flex:0 0 auto;-ms-flex:0 0 auto;width:33.3333333333%}.sd-col-5{flex:0 0 auto;-ms-flex:0 0 auto;width:41.6666666667%}.sd-col-6{flex:0 0 auto;-ms-flex:0 0 auto;width:50%}.sd-col-7{flex:0 0 auto;-ms-flex:0 0 auto;width:58.3333333333%}.sd-col-8{flex:0 0 auto;-ms-flex:0 0 auto;width:66.6666666667%}.sd-col-9{flex:0 0 auto;-ms-flex:0 0 auto;width:75%}.sd-col-10{flex:0 0 auto;-ms-flex:0 0 auto;width:83.3333333333%}.sd-col-11{flex:0 0 auto;-ms-flex:0 0 auto;width:91.6666666667%}.sd-col-12{flex:0 0 auto;-ms-flex:0 0 auto;width:100%}.sd-g-0,.sd-gy-0{--sd-gutter-y: 0}.sd-g-0,.sd-gx-0{--sd-gutter-x: 0}.sd-g-1,.sd-gy-1{--sd-gutter-y: 0.25rem}.sd-g-1,.sd-gx-1{--sd-gutter-x: 0.25rem}.sd-g-2,.sd-gy-2{--sd-gutter-y: 0.5rem}.sd-g-2,.sd-gx-2{--sd-gutter-x: 0.5rem}.sd-g-3,.sd-gy-3{--sd-gutter-y: 1rem}.sd-g-3,.sd-gx-3{--sd-gutter-x: 1rem}.sd-g-4,.sd-gy-4{--sd-gutter-y: 1.5rem}.sd-g-4,.sd-gx-4{--sd-gutter-x: 1.5rem}.sd-g-5,.sd-gy-5{--sd-gutter-y: 3rem}.sd-g-5,.sd-gx-5{--sd-gutter-x: 3rem}@media(min-width: 576px){.sd-col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-sm-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-sm-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-sm-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-sm-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-sm-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-sm-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-sm-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-sm-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-sm-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-sm-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-sm-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-sm-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-sm-0,.sd-gy-sm-0{--sd-gutter-y: 0}.sd-g-sm-0,.sd-gx-sm-0{--sd-gutter-x: 0}.sd-g-sm-1,.sd-gy-sm-1{--sd-gutter-y: 0.25rem}.sd-g-sm-1,.sd-gx-sm-1{--sd-gutter-x: 0.25rem}.sd-g-sm-2,.sd-gy-sm-2{--sd-gutter-y: 0.5rem}.sd-g-sm-2,.sd-gx-sm-2{--sd-gutter-x: 0.5rem}.sd-g-sm-3,.sd-gy-sm-3{--sd-gutter-y: 1rem}.sd-g-sm-3,.sd-gx-sm-3{--sd-gutter-x: 1rem}.sd-g-sm-4,.sd-gy-sm-4{--sd-gutter-y: 1.5rem}.sd-g-sm-4,.sd-gx-sm-4{--sd-gutter-x: 1.5rem}.sd-g-sm-5,.sd-gy-sm-5{--sd-gutter-y: 3rem}.sd-g-sm-5,.sd-gx-sm-5{--sd-gutter-x: 3rem}}@media(min-width: 768px){.sd-col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-md-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-md-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-md-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-md-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-md-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-md-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-md-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-md-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-md-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-md-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-md-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-md-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-md-0,.sd-gy-md-0{--sd-gutter-y: 0}.sd-g-md-0,.sd-gx-md-0{--sd-gutter-x: 0}.sd-g-md-1,.sd-gy-md-1{--sd-gutter-y: 0.25rem}.sd-g-md-1,.sd-gx-md-1{--sd-gutter-x: 0.25rem}.sd-g-md-2,.sd-gy-md-2{--sd-gutter-y: 0.5rem}.sd-g-md-2,.sd-gx-md-2{--sd-gutter-x: 0.5rem}.sd-g-md-3,.sd-gy-md-3{--sd-gutter-y: 1rem}.sd-g-md-3,.sd-gx-md-3{--sd-gutter-x: 1rem}.sd-g-md-4,.sd-gy-md-4{--sd-gutter-y: 1.5rem}.sd-g-md-4,.sd-gx-md-4{--sd-gutter-x: 1.5rem}.sd-g-md-5,.sd-gy-md-5{--sd-gutter-y: 3rem}.sd-g-md-5,.sd-gx-md-5{--sd-gutter-x: 3rem}}@media(min-width: 992px){.sd-col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-lg-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-lg-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-lg-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-lg-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-lg-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-lg-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-lg-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-lg-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-lg-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-lg-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-lg-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-lg-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-lg-0,.sd-gy-lg-0{--sd-gutter-y: 0}.sd-g-lg-0,.sd-gx-lg-0{--sd-gutter-x: 0}.sd-g-lg-1,.sd-gy-lg-1{--sd-gutter-y: 0.25rem}.sd-g-lg-1,.sd-gx-lg-1{--sd-gutter-x: 0.25rem}.sd-g-lg-2,.sd-gy-lg-2{--sd-gutter-y: 0.5rem}.sd-g-lg-2,.sd-gx-lg-2{--sd-gutter-x: 0.5rem}.sd-g-lg-3,.sd-gy-lg-3{--sd-gutter-y: 1rem}.sd-g-lg-3,.sd-gx-lg-3{--sd-gutter-x: 1rem}.sd-g-lg-4,.sd-gy-lg-4{--sd-gutter-y: 1.5rem}.sd-g-lg-4,.sd-gx-lg-4{--sd-gutter-x: 1.5rem}.sd-g-lg-5,.sd-gy-lg-5{--sd-gutter-y: 3rem}.sd-g-lg-5,.sd-gx-lg-5{--sd-gutter-x: 3rem}}@media(min-width: 1200px){.sd-col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.sd-col-xl-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.3333333333%}.sd-col-xl-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.6666666667%}.sd-col-xl-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.sd-col-xl-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.3333333333%}.sd-col-xl-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.6666666667%}.sd-col-xl-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.sd-col-xl-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.3333333333%}.sd-col-xl-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.6666666667%}.sd-col-xl-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.sd-col-xl-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.3333333333%}.sd-col-xl-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.6666666667%}.sd-col-xl-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.sd-g-xl-0,.sd-gy-xl-0{--sd-gutter-y: 0}.sd-g-xl-0,.sd-gx-xl-0{--sd-gutter-x: 0}.sd-g-xl-1,.sd-gy-xl-1{--sd-gutter-y: 0.25rem}.sd-g-xl-1,.sd-gx-xl-1{--sd-gutter-x: 0.25rem}.sd-g-xl-2,.sd-gy-xl-2{--sd-gutter-y: 0.5rem}.sd-g-xl-2,.sd-gx-xl-2{--sd-gutter-x: 0.5rem}.sd-g-xl-3,.sd-gy-xl-3{--sd-gutter-y: 1rem}.sd-g-xl-3,.sd-gx-xl-3{--sd-gutter-x: 1rem}.sd-g-xl-4,.sd-gy-xl-4{--sd-gutter-y: 1.5rem}.sd-g-xl-4,.sd-gx-xl-4{--sd-gutter-x: 1.5rem}.sd-g-xl-5,.sd-gy-xl-5{--sd-gutter-y: 3rem}.sd-g-xl-5,.sd-gx-xl-5{--sd-gutter-x: 3rem}}.sd-flex-row-reverse{flex-direction:row-reverse !important}details.sd-dropdown{position:relative;font-size:var(--sd-fontsize-dropdown)}details.sd-dropdown:hover{cursor:pointer}details.sd-dropdown .sd-summary-content{cursor:default}details.sd-dropdown summary.sd-summary-title{padding:.5em .6em .5em 1em;font-size:var(--sd-fontsize-dropdown-title);font-weight:var(--sd-fontweight-dropdown-title);user-select:none;-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;list-style:none;display:inline-flex;justify-content:space-between}details.sd-dropdown summary.sd-summary-title::-webkit-details-marker{display:none}details.sd-dropdown summary.sd-summary-title:focus{outline:none}details.sd-dropdown summary.sd-summary-title .sd-summary-icon{margin-right:.6em;display:inline-flex;align-items:center}details.sd-dropdown summary.sd-summary-title .sd-summary-icon svg{opacity:.8}details.sd-dropdown summary.sd-summary-title .sd-summary-text{flex-grow:1;line-height:1.5;padding-right:.5rem}details.sd-dropdown summary.sd-summary-title .sd-summary-state-marker{pointer-events:none;display:inline-flex;align-items:center}details.sd-dropdown summary.sd-summary-title .sd-summary-state-marker svg{opacity:.6}details.sd-dropdown summary.sd-summary-title:hover .sd-summary-state-marker svg{opacity:1;transform:scale(1.1)}details.sd-dropdown[open] summary .sd-octicon.no-title{visibility:hidden}details.sd-dropdown .sd-summary-chevron-right{transition:.25s}details.sd-dropdown[open]>.sd-summary-title .sd-summary-chevron-right{transform:rotate(90deg)}details.sd-dropdown[open]>.sd-summary-title .sd-summary-chevron-down{transform:rotate(180deg)}details.sd-dropdown:not([open]).sd-card{border:none}details.sd-dropdown:not([open])>.sd-card-header{border:1px solid var(--sd-color-card-border);border-radius:.25rem}details.sd-dropdown.sd-fade-in[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out;animation:sd-fade-in .5s ease-in-out}details.sd-dropdown.sd-fade-in-slide-down[open] summary~*{-moz-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;-webkit-animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out;animation:sd-fade-in .5s ease-in-out,sd-slide-down .5s ease-in-out}.sd-col>.sd-dropdown{width:100%}.sd-summary-content>.sd-tab-set:first-child{margin-top:0}@keyframes sd-fade-in{0%{opacity:0}100%{opacity:1}}@keyframes sd-slide-down{0%{transform:translate(0, -10px)}100%{transform:translate(0, 0)}}.sd-tab-set{border-radius:.125rem;display:flex;flex-wrap:wrap;margin:1em 0;position:relative}.sd-tab-set>input{opacity:0;position:absolute}.sd-tab-set>input:checked+label{border-color:var(--sd-color-tabs-underline-active);color:var(--sd-color-tabs-label-active)}.sd-tab-set>input:checked+label+.sd-tab-content{display:block}.sd-tab-set>input:not(:checked)+label:hover{color:var(--sd-color-tabs-label-hover);border-color:var(--sd-color-tabs-underline-hover)}.sd-tab-set>input:focus+label{outline-style:auto}.sd-tab-set>input:not(.focus-visible)+label{outline:none;-webkit-tap-highlight-color:transparent}.sd-tab-set>label{border-bottom:.125rem solid transparent;margin-bottom:0;color:var(--sd-color-tabs-label-inactive);border-color:var(--sd-color-tabs-underline-inactive);cursor:pointer;font-size:var(--sd-fontsize-tabs-label);font-weight:700;padding:1em 1.25em .5em;transition:color 250ms;width:auto;z-index:1}html .sd-tab-set>label:hover{color:var(--sd-color-tabs-label-active)}.sd-col>.sd-tab-set{width:100%}.sd-tab-content{box-shadow:0 -0.0625rem var(--sd-color-tabs-overline),0 .0625rem var(--sd-color-tabs-underline);display:none;order:99;padding-bottom:.75rem;padding-top:.75rem;width:100%}.sd-tab-content>:first-child{margin-top:0 !important}.sd-tab-content>:last-child{margin-bottom:0 !important}.sd-tab-content>.sd-tab-set{margin:0}.sd-sphinx-override,.sd-sphinx-override *{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.sd-sphinx-override p{margin-top:0}:root{--sd-color-primary: #0071bc;--sd-color-secondary: #6c757d;--sd-color-success: #28a745;--sd-color-info: #17a2b8;--sd-color-warning: #f0b37e;--sd-color-danger: #dc3545;--sd-color-light: #f8f9fa;--sd-color-muted: #6c757d;--sd-color-dark: #212529;--sd-color-black: black;--sd-color-white: white;--sd-color-primary-highlight: #0060a0;--sd-color-secondary-highlight: #5c636a;--sd-color-success-highlight: #228e3b;--sd-color-info-highlight: #148a9c;--sd-color-warning-highlight: #cc986b;--sd-color-danger-highlight: #bb2d3b;--sd-color-light-highlight: #d3d4d5;--sd-color-muted-highlight: #5c636a;--sd-color-dark-highlight: #1c1f23;--sd-color-black-highlight: black;--sd-color-white-highlight: #d9d9d9;--sd-color-primary-bg: rgba(0, 113, 188, 0.2);--sd-color-secondary-bg: rgba(108, 117, 125, 0.2);--sd-color-success-bg: rgba(40, 167, 69, 0.2);--sd-color-info-bg: rgba(23, 162, 184, 0.2);--sd-color-warning-bg: rgba(240, 179, 126, 0.2);--sd-color-danger-bg: rgba(220, 53, 69, 0.2);--sd-color-light-bg: rgba(248, 249, 250, 0.2);--sd-color-muted-bg: rgba(108, 117, 125, 0.2);--sd-color-dark-bg: rgba(33, 37, 41, 0.2);--sd-color-black-bg: rgba(0, 0, 0, 0.2);--sd-color-white-bg: rgba(255, 255, 255, 0.2);--sd-color-primary-text: #fff;--sd-color-secondary-text: #fff;--sd-color-success-text: #fff;--sd-color-info-text: #fff;--sd-color-warning-text: #212529;--sd-color-danger-text: #fff;--sd-color-light-text: #212529;--sd-color-muted-text: #fff;--sd-color-dark-text: #fff;--sd-color-black-text: #fff;--sd-color-white-text: #212529;--sd-color-shadow: rgba(0, 0, 0, 0.15);--sd-color-card-border: rgba(0, 0, 0, 0.125);--sd-color-card-border-hover: hsla(231, 99%, 66%, 1);--sd-color-card-background: transparent;--sd-color-card-text: inherit;--sd-color-card-header: transparent;--sd-color-card-footer: transparent;--sd-color-tabs-label-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-hover: hsla(231, 99%, 66%, 1);--sd-color-tabs-label-inactive: hsl(0, 0%, 66%);--sd-color-tabs-underline-active: hsla(231, 99%, 66%, 1);--sd-color-tabs-underline-hover: rgba(178, 206, 245, 0.62);--sd-color-tabs-underline-inactive: transparent;--sd-color-tabs-overline: rgb(222, 222, 222);--sd-color-tabs-underline: rgb(222, 222, 222);--sd-fontsize-tabs-label: 1rem;--sd-fontsize-dropdown: inherit;--sd-fontsize-dropdown-title: 1rem;--sd-fontweight-dropdown-title: 700} diff --git a/mddocs/docs/_build/markdown/_static/autodoc_pydantic.css b/mddocs/docs/_build/markdown/_static/autodoc_pydantic.css new file mode 100644 index 000000000..994a3e548 --- /dev/null +++ b/mddocs/docs/_build/markdown/_static/autodoc_pydantic.css @@ -0,0 +1,11 @@ +.autodoc_pydantic_validator_arrow { + padding-left: 8px; + } + +.autodoc_pydantic_collapsable_json { + cursor: pointer; + } + +.autodoc_pydantic_collapsable_erd { + cursor: pointer; + } \ No newline at end of file diff --git a/mddocs/docs/_build/markdown/changelog.md b/mddocs/docs/_build/markdown/changelog.md new file mode 100644 index 000000000..4ba142635 --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog.md @@ -0,0 +1,31 @@ +# Changelog + +# Changelog + +* [0.13.4 (2025-03-20)](changelog/0.13.4.md) +* [0.13.3 (2025-03-11)](changelog/0.13.3.md) +* [0.13.1 (2025-03-06)](changelog/0.13.1.md) +* [0.13.0 (2025-02-24)](changelog/0.13.0.md) +* [0.12.5 (2024-12-03)](changelog/0.12.5.md) +* [0.12.4 (2024-11-27)](changelog/0.12.4.md) +* [0.12.3 (2024-11-22)](changelog/0.12.3.md) +* [0.12.2 (2024-11-12)](changelog/0.12.2.md) +* [0.12.1 (2024-10-28)](changelog/0.12.1.md) +* [0.12.0 (2024-09-03)](changelog/0.12.0.md) +* [0.11.2 (2024-09-02)](changelog/0.11.2.md) +* [0.11.1 (2024-05-29)](changelog/0.11.1.md) +* [0.11.0 (2024-05-27)](changelog/0.11.0.md) +* [0.10.2 (2024-03-21)](changelog/0.10.2.md) +* [0.10.1 (2024-02-05)](changelog/0.10.1.md) +* [0.10.0 (2023-12-18)](changelog/0.10.0.md) +* [0.9.5 (2023-10-10)](changelog/0.9.5.md) +* [0.9.4 (2023-09-26)](changelog/0.9.4.md) +* [0.9.3 (2023-09-06)](changelog/0.9.3.md) +* [0.9.2 (2023-09-06)](changelog/0.9.2.md) +* [0.9.1 (2023-08-17)](changelog/0.9.1.md) +* [0.9.0 (2023-08-17)](changelog/0.9.0.md) +* [0.8.1 (2023-07-10)](changelog/0.8.1.md) +* [0.8.0 (2023-05-31)](changelog/0.8.0.md) +* [0.7.2 (2023-05-24)](changelog/0.7.2.md) +* [0.7.1 (2023-05-23)](changelog/0.7.1.md) +* [0.7.0 (2023-05-15)](changelog/0.7.0.md) diff --git a/mddocs/docs/_build/markdown/changelog/0.10.0.md b/mddocs/docs/_build/markdown/changelog/0.10.0.md new file mode 100644 index 000000000..ff0fe78bc --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.10.0.md @@ -0,0 +1,359 @@ +# 0.10.0 (2023-12-18) + +## Breaking Changes + +- Upgrade `etl-entities` from v1 to v2 ([#172](https://github.com/MobileTeleSystems/onetl/pull/172)). + + This implies that `HWM` classes are now have different internal structure than they used to. + + Before: + ```python + from etl_entities.old_hwm import IntHWM as OldIntHWM + from etl_entities.source import Column, Table + from etl_entities.process import Process + + hwm = OldIntHWM( + process=Process(name="myprocess", task="abc", dag="cde", host="myhost"), + source=Table(name="schema.table", instance="postgres://host:5432/db"), + column=Column(name="col1"), + value=123, + ) + ``` + + After: + ```python + from etl_entities.hwm import ColumnIntHWM + + hwm = ColumnIntHWM( + name="some_unique_name", + description="any value you want", + source="schema.table", + expression="col1", + value=123, + ) + ``` + + **Breaking change:** If you used HWM classes from `etl_entities` module, you should rewrite your code to make it compatible with new version. + + ### More details + + - `HWM` classes used by previous onETL versions were moved from `etl_entities` to `etl_entities.old_hwm` submodule. They are here for compatibility reasons, but are planned to be removed in `etl-entities` v3 release. + - New `HWM` classes have flat structure instead of nested. + - New `HWM` classes have mandatory `name` attribute (it was known as `qualified_name` before). + - Type aliases used while serializing and deserializing `HWM` objects to `dict` representation were changed too: `int` → `column_int`. + + To make migration simpler, you can use new method: + ```python + old_hwm = OldIntHWM(...) + new_hwm = old_hwm.as_new_hwm() + ``` + + Which automatically converts all fields from old structure to new one, including `qualified_name` → `name`. +- **Breaking changes:** + * Methods `BaseHWMStore.get()` and `BaseHWMStore.save()` were renamed to `get_hwm()` and `set_hwm()`. + * They now can be used only with new HWM classes from `etl_entities.hwm`, **old HWM classes are not supported**. + + If you used them in your code, please update it accordingly. +- YAMLHWMStore **CANNOT read files created by older onETL versions** (0.9.x or older). + + ### Update procedure + + ```python + # pip install onetl==0.9.5 + + # Get qualified_name for HWM + + + # Option 1. HWM is built manually + from etl_entities import IntHWM, FileListHWM + from etl_entities.source import Column, Table, RemoteFolder + from etl_entities.process import Process + + # for column HWM + old_column_hwm = IntHWM( + process=Process(name="myprocess", task="abc", dag="cde", host="myhost"), + source=Table(name="schema.table", instance="postgres://host:5432/db"), + column=Column(name="col1"), + ) + qualified_name = old_column_hwm.qualified_name + # "col1#schema.table@postgres://host:5432/db#cde.abc.myprocess@myhost" + + # for file HWM + old_file_hwm = FileListHWM( + process=Process(name="myprocess", task="abc", dag="cde", host="myhost"), + source=RemoteFolder(name="/absolute/path", instance="ftp://ftp.server:21"), + ) + qualified_name = old_file_hwm.qualified_name + # "file_list#/absolute/path@ftp://ftp.server:21#cde.abc.myprocess@myhost" + + + # Option 2. HWM is generated automatically (by DBReader/FileDownloader) + # See onETL logs and search for string like qualified_name = '...' + + qualified_name = "col1#schema.table@postgres://host:5432/db#cde.abc.myprocess@myhost" + + + # Get .yml file path by qualified_name + + import os + from pathlib import PurePosixPath + from onetl.hwm.store import YAMLHWMStore + + # here you should pass the same arguments as used on production, if any + yaml_hwm_store = YAMLHWMStore() + hwm_path = yaml_hwm_store.get_file_path(qualified_name) + print(hwm_path) + + # for column HWM + # LocalPosixPath('/home/maxim/.local/share/onETL/yml_hwm_store/col1__schema.table__postgres_host_5432_db__cde.abc.myprocess__myhost.yml') + + # for file HWM + # LocalPosixPath('/home/maxim/.local/share/onETL/yml_hwm_store/file_list__absolute_path__ftp_ftp.server_21__cde.abc.myprocess__myhost.yml') + + + # Read raw .yml file content + + from yaml import safe_load, dump + + raw_old_hwm_items = safe_load(hwm_path.read_text()) + print(raw_old_hwm_items) + + # for column HWM + # [ + # { + # "column": { "name": "col1", "partition": {} }, + # "modified_time": "2023-12-18T10: 39: 47.377378", + # "process": { "dag": "cde", "host": "myhost", "name": "myprocess", "task": "abc" }, + # "source": { "instance": "postgres: //host:5432/db", "name": "schema.table" }, + # "type": "int", + # "value": "123", + # }, + # ] + + # for file HWM + # [ + # { + # "modified_time": "2023-12-18T11:15:36.478462", + # "process": { "dag": "cde", "host": "myhost", "name": "myprocess", "task": "abc" }, + # "source": { "instance": "ftp://ftp.server:21", "name": "/absolute/path" }, + # "type": "file_list", + # "value": ["file1.txt", "file2.txt"], + # }, + # ] + + + # Convert file content to new structure, compatible with onETL 0.10.x + raw_new_hwm_items = [] + for old_hwm in raw_old_hwm_items: + new_hwm = {"name": qualified_name, "modified_time": old_hwm["modified_time"]} + + if "column" in old_hwm: + new_hwm["expression"] = old_hwm["column"]["name"] + new_hwm["entity"] = old_hwm["source"]["name"] + old_hwm.pop("process", None) + + if old_hwm["type"] == "int": + new_hwm["type"] = "column_int" + new_hwm["value"] = old_hwm["value"] + + elif old_hwm["type"] == "date": + new_hwm["type"] = "column_date" + new_hwm["value"] = old_hwm["value"] + + elif old_hwm["type"] == "datetime": + new_hwm["type"] = "column_datetime" + new_hwm["value"] = old_hwm["value"] + + elif old_hwm["type"] == "file_list": + new_hwm["type"] = "file_list" + new_hwm["value"] = [ + os.fspath(PurePosixPath(old_hwm["source"]["name"]).joinpath(path)) + for path in old_hwm["value"] + ] + + else: + raise ValueError("WAT?") + + raw_new_hwm_items.append(new_hwm) + + + print(raw_new_hwm_items) + # for column HWM + # [ + # { + # "name": "col1#schema.table@postgres://host:5432/db#cde.abc.myprocess@myhost", + # "modified_time": "2023-12-18T10:39:47.377378", + # "expression": "col1", + # "source": "schema.table", + # "type": "column_int", + # "value": 123, + # }, + # ] + + # for file HWM + # [ + # { + # "name": "file_list#/absolute/path@ftp://ftp.server:21#cde.abc.myprocess@myhost", + # "modified_time": "2023-12-18T11:15:36.478462", + # "entity": "/absolute/path", + # "type": "file_list", + # "value": ["/absolute/path/file1.txt", "/absolute/path/file2.txt"], + # }, + # ] + + + # Save file with new content + with open(hwm_path, "w") as file: + dump(raw_new_hwm_items, file) + + + # Stop Python interpreter and update onETL + # pip install onetl==0.10.0 + # Check that new .yml file can be read + + from onetl.hwm.store import YAMLHWMStore + + qualified_name = ... + + # here you should pass the same arguments as used on production, if any + yaml_hwm_store = YAMLHWMStore() + yaml_hwm_store.get_hwm(qualified_name) + + # for column HWM + # ColumnIntHWM( + # name='col1#schema.table@postgres://host:5432/db#cde.abc.myprocess@myhost', + # description='', + # entity='schema.table', + # value=123, + # expression='col1', + # modified_time=datetime.datetime(2023, 12, 18, 10, 39, 47, 377378), + # ) + + # for file HWM + # FileListHWM( + # name='file_list#/absolute/path@ftp://ftp.server:21#cde.abc.myprocess@myhost', + # description='', + # entity=AbsolutePath('/absolute/path'), + # value=frozenset({AbsolutePath('/absolute/path/file1.txt'), AbsolutePath('/absolute/path/file2.txt')}), + # expression=None, + # modified_time=datetime.datetime(2023, 12, 18, 11, 15, 36, 478462) + # ) + + + # That's all! + ``` + + But most of users use other HWM store implementations which do not have such issues. +- Several classes and functions were moved from `onetl` to `etl_entities`: + + | onETL `0.9.x` and older | onETL `0.10.x` and newer | + |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + | ```python
from onetl.hwm.store import (
detect_hwm_store,
BaseHWMStore,
HWMStoreClassRegistry,
register_hwm_store_class,
HWMStoreManager,
MemoryHWMStore,
)
``` | ```python
from etl_entities.hwm_store import (
detect_hwm_store,
BaseHWMStore,
HWMStoreClassRegistry,
register_hwm_store_class,
HWMStoreManager,
MemoryHWMStore,
)
``` | + + They still can be imported from old module, but this is deprecated and will be removed in v1.0.0 release. +- Change the way of passing `HWM` to `DBReader` and `FileDownloader` classes: + + | onETL `0.9.x` and older | onETL `0.10.x` and newer | + |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + | ```python
reader = DBReader(
connection=...,
source=...,
hwm_column="col1",
)
``` | ```python
reader = DBReader(
connection=...,
source=...,
hwm=DBReader.AutoDetectHWM(
# name is mandatory now!
name="my_unique_hwm_name",
expression="col1",
),
)
``` | + | ```python
reader = DBReader(
connection=...,
source=...,
hwm_column=(
"col1",
"cast(col1 as date)",
),
)
``` | ```python
reader = DBReader(
connection=...,
source=...,
hwm=DBReader.AutoDetectHWM(
# name is mandatory now!
name="my_unique_hwm_name",
expression="cast(col1 as date)",
),
)
``` | + | ```python
downloader = FileDownloader(
connection=...,
source_path=...,
target_path=...,
hwm_type="file_list",
)
``` | ```python
downloader = FileDownloader(
connection=...,
source_path=...,
target_path=...,
hwm=FileListHWM(
# name is mandatory now!
name="another_unique_hwm_name",
),
)
``` | + + New HWM classes have **mandatory** `name` attribute which should be passed explicitly, + instead of generating if automatically under the hood. + + Automatic `name` generation using the old `DBReader.hwm_column` / `FileDownloader.hwm_type` + syntax is still supported, but will be removed in v1.0.0 release. ([#179](https://github.com/MobileTeleSystems/onetl/pull/179)) +- Performance of read Incremental and Batch strategies has been drastically improved. ([#182](https://github.com/MobileTeleSystems/onetl/pull/182)). + + ### Before and after in details + + `DBReader.run()` + incremental/batch strategy behavior in versions 0.9.x and older: + - Get table schema by making query `SELECT * FROM table WHERE 1=0` (if `DBReader.columns` has `*`) + - Expand `*` to real column names from table, add here `hwm_column`, remove duplicates (as some RDBMS does not allow that). + - Create dataframe from query like `SELECT hwm_expression AS hwm_column, ...other table columns... FROM table WHERE hwm_expression > prev_hwm.value`. + - Determine HWM class using dataframe schema: `df.schema[hwm_column].dataType`. + - Determine x HWM column value using Spark: `df.select(max(hwm_column)).collect()`. + - Use `max(hwm_column)` as next HWM value, and save it to HWM Store. + - Return dataframe to user. + + This was far from ideal: + - Dataframe content (all rows or just changed ones) was loaded from the source to Spark only to get min/max values of specific column. + - Step of fetching table schema and then substituting column names in the next query caused some unexpected errors. + > For example, source contains columns with mixed name case, like `"CamelColumn"` or `"spaced column"`. + + > Column names were *not* escaped during query generation, leading to queries that cannot be executed by database. + + > So users have to *explicitly* pass column names `DBReader`, wrapping columns with mixed naming with `"`: + > ```python + > reader = DBReader( + > connection=..., + > source=..., + > columns=[ # passing '*' here leads to wrong SQL query generation + > "normal_column", + > '"CamelColumn"', + > '"spaced column"', + > ..., + > ], + > ) + > ``` + - Using `DBReader` with `IncrementalStrategy` could lead to reading rows already read before. + > Dataframe was created from query with WHERE clause like `hwm.expression > prev_hwm.value`, + > not `hwm.expression > prev_hwm.value AND hwm.expression <= current_hwm.value`. + + > So if new rows appeared in the source **after** HWM value is determined, + > they can be read by accessing dataframe content (because Spark dataframes are lazy), + > leading to inconsistencies between HWM value and dataframe content. + + > This may lead to issues then `DBReader.run()` read some data, updated HWM value, and next call of `DBReader.run()` + > will read rows that were already read in previous run. + + `DBReader.run()` + incremental/batch strategy behavior in versions 0.10.x and newer: + - Detect type of HWM expression: `SELECT hwm.expression FROM table WHERE 1=0`. + - Determine corresponding Spark type `df.schema[0]` and when determine matching HWM class (if `DReader.AutoDetectHWM` is used). + - Get min/max values by querying the source: `SELECT MAX(hwm.expression) FROM table WHERE hwm.expression >= prev_hwm.value`. + - Use `max(hwm.expression)` as next HWM value, and save it to HWM Store. + - Create dataframe from query `SELECT ... table columns ... FROM table WHERE hwm.expression > prev_hwm.value AND hwm.expression <= current_hwm.value`, baking new HWM value into the query. + - Return dataframe to user. + + Improvements: + - Allow source to calculate min/max instead of loading everything to Spark. This should be **faster** on large amounts of data (**up to x2**), because we do not transfer all the data from the source to Spark. This can be even faster if source have indexes for HWM column. + - Columns list is passed to source as-is, without any resolving on `DBReader` side. So you can pass `DBReader(columns=["*"])` to read tables with mixed columns naming. + - Restrict dataframe content to always match HWM values, which leads to never reading the same row twice. + + **Breaking change**: HWM column is not being implicitly added to dataframe. It was a part of `SELECT` clause, but now it is mentioned only in `WHERE` clause. + + So if you had code like this, you have to rewrite it: + + | onETL `0.9.x` and older | onETL `0.10.x` and newer | + |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| + | ```python
reader = DBReader(
connection=...,
source=...,
columns=[
"col1",
"col2",
],
hwm_column="hwm_col",
)

df = reader.run()
# hwm_column value is in the dataframe
assert df.columns == ["col1", "col2", "hwm_col"]
``` | ```python
reader = DBReader(
connection=...,
source=...,
columns=[
"col1",
"col2",
# add hwm_column explicitly
"hwm_col",
],
hwm_column="hwm_col",
)

df = reader.run()
# if columns list is not updated,
# this fill fail
assert df.columns == ["col1", "col2", "hwm_col"]
``` | + | ```python
reader = DBReader(
connection=...,
source=...,
columns=[
"col1",
"col2",
],
hwm_column=(
"hwm_col",
"cast(hwm_col as int)",
),
)

df = reader.run()
# hwm_expression value is in the dataframe
assert df.columns == ["col1", "col2", "hwm_col"]
``` | ```python
reader = DBReader(
connection=...,
source=...,
columns=[
"col1",
"col2",
# add hwm_expression explicitly
"cast(hwm_col as int) as hwm_col",
],
hwm_column=(
"hwm_col",
"cast(hwm_col as int)",
),
)

df = reader.run()
# if columns list is not updated,
# this fill fail
assert df.columns == ["col1", "col2", "hwm_col"]
``` | + + But most users just use `columns=["*"]` anyway, they won’t see any changes. +- `FileDownloader.run()` now updates HWM in HWM Store not after each file is being successfully downloaded, + but after all files were handled. + + This is because: + * FileDownloader can be used with `DownloadOptions(workers=N)`, which could lead to race condition - one thread can save to HWM store one HWM value when another thread will save different value. + * FileDownloader can download hundreds and thousands of files, and issuing a request to HWM Store for each file could potentially DDoS HWM Store. ([#189](https://github.com/MobileTeleSystems/onetl/pull/189)) + + There is a exception handler which tries to save HWM to HWM store if download process was interrupted. But if it was interrupted by force, like sending `SIGKILL` event, + HWM will not be saved to HWM store, so some already downloaded files may be downloaded again next time. + + But unexpected process kill may produce other negative impact, like some file will be downloaded partially, so this is an expected behavior. + +## Features + +- Add Python 3.12 compatibility. ([#167](https://github.com/MobileTeleSystems/onetl/pull/167)) +- `Excel` file format now can be used with Spark 3.5.0. ([#187](https://github.com/MobileTeleSystems/onetl/pull/187)) +- `SnapshotBatchStagy` and `IncrementalBatchStrategy` does no raise exceptions if source does not contain any data. + Instead they stop at first iteration and return empty dataframe. ([#188](https://github.com/MobileTeleSystems/onetl/pull/188)) +- Cache result of `connection.check()` in high-level classes like `DBReader`, `FileDownloader` and so on. This makes logs less verbose. ([#190](https://github.com/MobileTeleSystems/onetl/pull/190)) + +## Bug Fixes + +- Fix `@slot` and `@hook` decorators returning methods with missing arguments in signature (Pylance, VS Code). ([#183](https://github.com/MobileTeleSystems/onetl/pull/183)) +- Kafka connector documentation said that it does support reading topic data incrementally by passing `group.id` or `groupIdPrefix`. + Actually, this is not true, because Spark does not send information to Kafka which messages were consumed. + So currently users can only read the whole topic, no incremental reads are supported. diff --git a/mddocs/docs/_build/markdown/changelog/0.10.1.md b/mddocs/docs/_build/markdown/changelog/0.10.1.md new file mode 100644 index 000000000..1bc703cb4 --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.10.1.md @@ -0,0 +1,25 @@ +# 0.10.1 (2024-02-05) + +## Features + +- Add support of `Incremental Strategies` for `Kafka` connection: + ```python + reader = DBReader( + connection=Kafka(...), + source="topic_name", + hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="offset"), + ) + + with IncrementalStrategy(): + df = reader.run() + ``` + + This lets you resume reading data from a Kafka topic starting at the last committed offset from your previous run. ([#202](https://github.com/MobileTeleSystems/onetl/pull/202)) +- Add `has_data`, `raise_if_no_data` methods to `DBReader` class. ([#203](https://github.com/MobileTeleSystems/onetl/pull/203)) +- Updare VMware Greenplum connector from `2.1.4` to `2.3.0`. This implies: + : * Greenplum 7.x support + * [Kubernetes support](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/configure.html#k8scfg) + * New read option [gpdb.matchDistributionPolicy](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#distpolmotion) + which allows to match each Spark executor with specific Greenplum segment, avoiding redundant data transfer between Greenplum segments + * Allows overriding [Greenplum optimizer parameters](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#greenplum-gucs) in read/write operations ([#208](https://github.com/MobileTeleSystems/onetl/pull/208)) +- `Greenplum.get_packages()` method now accepts optional arg `package_version` which allows to override version of Greenplum connector package. ([#208](https://github.com/MobileTeleSystems/onetl/pull/208)) diff --git a/mddocs/docs/_build/markdown/changelog/0.10.2.md b/mddocs/docs/_build/markdown/changelog/0.10.2.md new file mode 100644 index 000000000..3ca2693c2 --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.10.2.md @@ -0,0 +1,34 @@ +# 0.10.2 (2024-03-21) + +## Features + +- Add support of Pydantic v2. ([#230](https://github.com/MobileTeleSystems/onetl/pull/230)) + +## Improvements + +- Improve database connections documentation: + : * Add “Types” section describing mapping between Clickhouse and Spark types + * Add “Prerequisites” section describing different aspects of connecting to Clickhouse + * Separate documentation of `DBReader` and `.sql()` / `.pipeline(...)` + * Add examples for `.fetch()` and `.execute()` ([#211](https://github.com/MobileTeleSystems/onetl/pull/211), [#228](https://github.com/MobileTeleSystems/onetl/pull/228), [#229](https://github.com/MobileTeleSystems/onetl/pull/229), [#233](https://github.com/MobileTeleSystems/onetl/pull/233), [#234](https://github.com/MobileTeleSystems/onetl/pull/234), [#235](https://github.com/MobileTeleSystems/onetl/pull/235), [#236](https://github.com/MobileTeleSystems/onetl/pull/236), [#240](https://github.com/MobileTeleSystems/onetl/pull/240)) +- Add notes to Greenplum documentation about issues with IP resolution and building `gpfdist` URL ([#228](https://github.com/MobileTeleSystems/onetl/pull/228)) +- Allow calling `MongoDB.pipeline(...)` with passing just collection name, without explicit aggregation pipeline. ([#237](https://github.com/MobileTeleSystems/onetl/pull/237)) +- Update default `Postgres(extra={...})` to include `{"stringtype": "unspecified"}` option. + This allows to write text data to non-text column (or vice versa), relying to Postgres cast capabilities. + + For example, now it is possible to read column of type `money` as Spark’s `StringType()`, and write it back to the same column, + without using intermediate columns or tables. ([#229](https://github.com/MobileTeleSystems/onetl/pull/229)) + +## Bug Fixes + +- Return back handling of `DBReader(columns="string")`. This was a valid syntax up to v0.10 release, but it was removed because + most of users neved used it. It looks that we were wrong, returning this behavior back, but with deprecation warning. ([#238](https://github.com/MobileTeleSystems/onetl/pull/238)) +- Downgrade Greenplum package version from `2.3.0` to `2.2.0`. ([#239](https://github.com/MobileTeleSystems/onetl/pull/239)) + + This is because version 2.3.0 introduced issues with writing data to Greenplum 6.x. + Connector can open transaction with `SELECT * FROM table LIMIT 0` query, but does not close it, which leads to deadlocks. + + For using this connector with Greenplum 7.x, please pass package version explicitly: + ```python + maven_packages = Greenplum.get_packages(package_version="2.3.0", ...) + ``` diff --git a/mddocs/docs/_build/markdown/changelog/0.11.0.md b/mddocs/docs/_build/markdown/changelog/0.11.0.md new file mode 100644 index 000000000..2069d081f --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.11.0.md @@ -0,0 +1,202 @@ +# 0.11.0 (2024-05-27) + +## Breaking Changes + +There can be some changes in connection behavior, related to version upgrades. So we mark these changes as **breaking** although +most of users will not see any differences. + +- Update Clickhouse JDBC driver to latest version ([#249](https://github.com/MobileTeleSystems/onetl/pull/249)): + : * Package was renamed `ru.yandex.clickhouse:clickhouse-jdbc` → `com.clickhouse:clickhouse-jdbc`. + * Package version changed `0.3.2` → `0.6.0-patch5`. + * Driver name changed `ru.yandex.clickhouse.ClickHouseDriver` → `com.clickhouse.jdbc.ClickHouseDriver`. + + This brings up several fixes for Spark <-> Clickhouse type compatibility, and also Clickhouse clusters support. +- Update other JDBC drivers to latest versions: + : * MSSQL `12.2.0` → `12.6.2` ([#254](https://github.com/MobileTeleSystems/onetl/pull/254)). + * MySQL `8.0.33` → `8.4.0` ([#253](https://github.com/MobileTeleSystems/onetl/pull/253), [#285](https://github.com/MobileTeleSystems/onetl/pull/285)). + * Oracle `23.2.0.0` → `23.4.0.24.05` ([#252](https://github.com/MobileTeleSystems/onetl/pull/252), [#284](https://github.com/MobileTeleSystems/onetl/pull/284)). + * Postgres `42.6.0` → `42.7.3` ([#251](https://github.com/MobileTeleSystems/onetl/pull/251)). +- Update MongoDB connector to latest version: `10.1.1` → `10.3.0` ([#255](https://github.com/MobileTeleSystems/onetl/pull/255), [#283](https://github.com/MobileTeleSystems/onetl/pull/283)). + + This brings up Spark 3.5 support. +- Update `XML` package to latest version: `0.17.0` → `0.18.0` ([#259](https://github.com/MobileTeleSystems/onetl/pull/259)). + + This brings few bugfixes with datetime format handling. +- For JDBC connections add new `SQLOptions` class for `DB.sql(query, options=...)` method ([#272](https://github.com/MobileTeleSystems/onetl/pull/272)). + + Firsly, to keep naming more consistent. + + Secondly, some of options are not supported by `DB.sql(...)` method, but supported by `DBReader`. + For example, `SQLOptions` do not support `partitioning_mode` and require explicit definition of `lower_bound` and `upper_bound` when `num_partitions` is greater than 1. + `ReadOptions` does support `partitioning_mode` and allows skipping `lower_bound` and `upper_bound` values. + + This require some code changes. Before: + ```python + from onetl.connection import Postgres + + postgres = Postgres(...) + df = postgres.sql( + """ + SELECT * + FROM some.mytable + WHERE key = 'something' + """, + options=Postgres.ReadOptions( + partitioning_mode="range", + partition_column="id", + num_partitions=10, + ), + ) + ``` + + After: + ```python + from onetl.connection import Postgres + + postgres = Postgres(...) + df = postgres.sql( + """ + SELECT * + FROM some.mytable + WHERE key = 'something' + """, + options=Postgres.SQLOptions( + # partitioning_mode is not supported! + partition_column="id", + num_partitions=10, + lower_bound=0, # <-- set explicitly + upper_bound=1000, # <-- set explicitly + ), + ) + ``` + + For now, `DB.sql(query, options=...)` can accept `ReadOptions` to keep backward compatibility, but emits deprecation warning. + The support will be removed in `v1.0.0`. +- Split up `JDBCOptions` class into `FetchOptions` and `ExecuteOptions` ([#274](https://github.com/MobileTeleSystems/onetl/pull/274)). + + New classes are used by `DB.fetch(query, options=...)` and `DB.execute(query, options=...)` methods respectively. + This is mostly to keep naming more consistent. + + This require some code changes. Before: + ```python + from onetl.connection import Postgres + + postgres = Postgres(...) + df = postgres.fetch( + "SELECT * FROM some.mytable WHERE key = 'something'", + options=Postgres.JDBCOptions( + fetchsize=1000, + query_timeout=30, + ), + ) + + postgres.execute( + "UPDATE some.mytable SET value = 'new' WHERE key = 'something'", + options=Postgres.JDBCOptions(query_timeout=30), + ) + ``` + + After: + ```python + from onetl.connection import Postgres + + # Using FetchOptions for fetching data + postgres = Postgres(...) + df = postgres.fetch( + "SELECT * FROM some.mytable WHERE key = 'something'", + options=Postgres.FetchOptions( # <-- change class name + fetchsize=1000, + query_timeout=30, + ), + ) + + # Using ExecuteOptions for executing statements + postgres.execute( + "UPDATE some.mytable SET value = 'new' WHERE key = 'something'", + options=Postgres.ExecuteOptions(query_timeout=30), # <-- change class name + ) + ``` + + For now, `DB.fetch(query, options=...)` and `DB.execute(query, options=...)` can accept `JDBCOptions`, to keep backward compatibility, + but emit a deprecation warning. The old class will be removed in `v1.0.0`. +- Serialize `ColumnDatetimeHWM` to Clickhouse’s `DateTime64(6)` (precision up to microseconds) instead of `DateTime` (precision up to seconds) ([#267](https://github.com/MobileTeleSystems/onetl/pull/267)). + + In previous onETL versions, `ColumnDatetimeHWM` value was rounded to the second, and thus reading some rows that were read in previous runs, + producing duplicates. + + For Clickhouse versions below 21.1 comparing column of type `DateTime` with a value of type `DateTime64` is not supported, returning an empty dataframe. + To avoid this, replace: + ```python + DBReader( + ..., + hwm=DBReader.AutoDetectHWM( + name="my_hwm", + expression="hwm_column", # <-- + ), + ) + ``` + + with: + ```python + DBReader( + ..., + hwm=DBReader.AutoDetectHWM( + name="my_hwm", + expression="CAST(hwm_column AS DateTime64)", # <-- add explicit CAST + ), + ) + ``` +- Pass JDBC connection extra params as `properties` dict instead of URL with query part ([#268](https://github.com/MobileTeleSystems/onetl/pull/268)). + + This allows passing custom connection parameters like `Clickhouse(extra={"custom_http_options": "option1=value1,option2=value2"})` + without need to apply urlencode to parameter value, like `option1%3Dvalue1%2Coption2%3Dvalue2`. + +## Features + +Improve user experience with Kafka messages and Database tables with serialized columns, like JSON/XML. + +- Allow passing custom package version as argument for `DB.get_packages(...)` method of several DB connectors: + : * `Clickhouse.get_packages(package_version=..., apache_http_client_version=...)` ([#249](https://github.com/MobileTeleSystems/onetl/pull/249)). + * `MongoDB.get_packages(scala_version=..., spark_version=..., package_version=...)` ([#255](https://github.com/MobileTeleSystems/onetl/pull/255)). + * `MySQL.get_packages(package_version=...)` ([#253](https://github.com/MobileTeleSystems/onetl/pull/253)). + * `MSSQL.get_packages(java_version=..., package_version=...)` ([#254](https://github.com/MobileTeleSystems/onetl/pull/254)). + * `Oracle.get_packages(java_version=..., package_version=...)` ([#252](https://github.com/MobileTeleSystems/onetl/pull/252)). + * `Postgres.get_packages(package_version=...)` ([#251](https://github.com/MobileTeleSystems/onetl/pull/251)). + * `Teradata.get_packages(package_version=...)` ([#256](https://github.com/MobileTeleSystems/onetl/pull/256)). + + Now users can downgrade or upgrade connection without waiting for next onETL release. Previously only `Kafka` and `Greenplum` supported this feature. +- Add `FileFormat.parse_column(...)` method to several classes: + : * `Avro.parse_column(col)` ([#265](https://github.com/MobileTeleSystems/onetl/pull/265)). + * `JSON.parse_column(col, schema=...)` ([#257](https://github.com/MobileTeleSystems/onetl/pull/257)). + * `CSV.parse_column(col, schema=...)` ([#258](https://github.com/MobileTeleSystems/onetl/pull/258)). + * `XML.parse_column(col, schema=...)` ([#269](https://github.com/MobileTeleSystems/onetl/pull/269)). + + This allows parsing data in `value` field of Kafka message or string/binary column of some table as a nested Spark structure. +- Add `FileFormat.serialize_column(...)` method to several classes: + : * `Avro.serialize_column(col)` ([#265](https://github.com/MobileTeleSystems/onetl/pull/265)). + * `JSON.serialize_column(col)` ([#257](https://github.com/MobileTeleSystems/onetl/pull/257)). + * `CSV.serialize_column(col)` ([#258](https://github.com/MobileTeleSystems/onetl/pull/258)). + + This allows saving Spark nested structures or arrays to `value` field of Kafka message or string/binary column of some table. + +## Improvements + +Few documentation improvements. + +- Replace all `assert` in documentation with doctest syntax. This should make documentation more readable ([#273](https://github.com/MobileTeleSystems/onetl/pull/273)). +- Add generic `Troubleshooting` guide ([#275](https://github.com/MobileTeleSystems/onetl/pull/275)). +- Improve Kafka documentation: + : * Add “Prerequisites” page describing different aspects of connecting to Kafka. + * Improve “Reading from” and “Writing to” page of Kafka documentation, add more examples and usage notes. + * Add “Troubleshooting” page ([#276](https://github.com/MobileTeleSystems/onetl/pull/276)). +- Improve Hive documentation: + : * Add “Prerequisites” page describing different aspects of connecting to Hive. + * Improve “Reading from” and “Writing to” page of Hive documentation, add more examples and recommendations. + * Improve “Executing statements in Hive” page of Hive documentation. ([#278](https://github.com/MobileTeleSystems/onetl/pull/278)). +- Add “Prerequisites” page describing different aspects of using SparkHDFS and SparkS3 connectors. ([#279](https://github.com/MobileTeleSystems/onetl/pull/279)). +- Add note about connecting to Clickhouse cluster. ([#280](https://github.com/MobileTeleSystems/onetl/pull/280)). +- Add notes about versions when specific class/method/attribute/argument was added, renamed or changed behavior ([#282](https://github.com/MobileTeleSystems/onetl/pull/282)). + +## Bug Fixes + +- Fix missing `pysmb` package after installing `pip install onetl[files]` . diff --git a/mddocs/docs/_build/markdown/changelog/0.11.1.md b/mddocs/docs/_build/markdown/changelog/0.11.1.md new file mode 100644 index 000000000..823afe3be --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.11.1.md @@ -0,0 +1,9 @@ +# 0.11.1 (2024-05-29) + +## Features + +- Change `MSSQL.port` default from `1433` to `None`, allowing use of `instanceName` to detect port number. ([#287](https://github.com/MobileTeleSystems/onetl/pull/287)) + +## Bug Fixes + +- Remove `fetchsize` from `JDBC.WriteOptions`. ([#288](https://github.com/MobileTeleSystems/onetl/pull/288)) diff --git a/mddocs/docs/_build/markdown/changelog/0.11.2.md b/mddocs/docs/_build/markdown/changelog/0.11.2.md new file mode 100644 index 000000000..9278d22f8 --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.11.2.md @@ -0,0 +1,5 @@ +# 0.11.2 (2024-09-02) + +## Bug Fixes + +- Fix passing `Greenplum(extra={"options": ...})` during read/write operations. ([#308](https://github.com/MobileTeleSystems/onetl/pull/308)) diff --git a/mddocs/docs/_build/markdown/changelog/0.12.0.md b/mddocs/docs/_build/markdown/changelog/0.12.0.md new file mode 100644 index 000000000..d212c0062 --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.12.0.md @@ -0,0 +1,48 @@ +# 0.12.0 (2024-09-03) + +## Breaking Changes + +- Change connection URL used for generating HWM names of S3 and Samba sources: + : * `smb://host:port` -> `smb://host:port/share` + * `s3://host:port` -> `s3://host:port/bucket` ([#304](https://github.com/MobileTeleSystems/onetl/pull/304)) +- Update DB connectors/drivers to latest versions: + : * Clickhouse `0.6.0-patch5` → `0.6.5` + * MongoDB `10.3.0` → `10.4.0` + * MSSQL `12.6.2` → `12.8.1` + * MySQL `8.4.0` → `9.0.0` + * Oracle `23.4.0.24.05` → `23.5.0.24.07` + * Postgres `42.7.3` → `42.7.4` +- Update `Excel` package from `0.20.3` to `0.20.4`, to include Spark 3.5.1 support. ([#306](https://github.com/MobileTeleSystems/onetl/pull/306)) + +## Features + +- Add support for specifying file formats (`ORC`, `Parquet`, `CSV`, etc.) in `HiveWriteOptions.format` ([#292](https://github.com/MobileTeleSystems/onetl/pull/292)): + ```python + Hive.WriteOptions(format=ORC(compression="snappy")) + ``` +- Collect Spark execution metrics in following methods, and log then in DEBUG mode: + : * `DBWriter.run()` + * `FileDFWriter.run()` + * `Hive.sql()` + * `Hive.execute()` + + This is implemented using custom `SparkListener` which wraps the entire method call, and + then report collected metrics. But these metrics sometimes may be missing due to Spark architecture, + so they are not reliable source of information. That’s why logs are printed only in DEBUG mode, and + are not returned as method call result. ([#303](https://github.com/MobileTeleSystems/onetl/pull/303)) +- Generate default `jobDescription` based on currently executed method. Examples: + : * `DBWriter.run(schema.table) -> Postgres[host:5432/database]` + * `MongoDB[localhost:27017/admin] -> DBReader.has_data(mycollection)` + * `Hive[cluster].execute()` + + If user already set custom `jobDescription`, it will left intact. ([#304](https://github.com/MobileTeleSystems/onetl/pull/304)) +- Add log.info about JDBC dialect usage ([#305](https://github.com/MobileTeleSystems/onetl/pull/305)): + ```text + |MySQL| Detected dialect: 'org.apache.spark.sql.jdbc.MySQLDialect' + ``` +- Log estimated size of in-memory dataframe created by `JDBC.fetch` and `JDBC.execute` methods. ([#303](https://github.com/MobileTeleSystems/onetl/pull/303)) + +## Bug Fixes + +- Fix passing `Greenplum(extra={"options": ...})` during read/write operations. ([#308](https://github.com/MobileTeleSystems/onetl/pull/308)) +- Do not raise exception if yield-based hook whas something past (and only one) `yield`. diff --git a/mddocs/docs/_build/markdown/changelog/0.12.1.md b/mddocs/docs/_build/markdown/changelog/0.12.1.md new file mode 100644 index 000000000..3575634d5 --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.12.1.md @@ -0,0 +1,17 @@ +# 0.12.1 (2024-10-28) + +## Features + +- Log detected JDBC dialect while using `DBWriter`. + +## Bug Fixes + +- Fix `SparkMetricsRecorder` failing when receiving `SparkListenerTaskEnd` without `taskMetrics` (e.g. executor was killed by OOM). ([#313](https://github.com/MobileTeleSystems/onetl/pull/313)) +- Call `kinit` before checking for HDFS active namenode. +- Wrap `kinit` with `threading.Lock` to avoid multithreading issues. +- Immediately show `kinit` errors to user, instead of hiding them. +- Use `AttributeError` instead of `ImportError` in module’s `__getattr__` method, to make code compliant with Python spec. + +## Doc only Changes + +- Add note about [spark-dialect-extension](https://github.com/MobileTeleSystems/spark-dialect-extension) package to Clickhouse connector documentation. ([#310](https://github.com/MobileTeleSystems/onetl/pull/310)) diff --git a/mddocs/docs/_build/markdown/changelog/0.12.2.md b/mddocs/docs/_build/markdown/changelog/0.12.2.md new file mode 100644 index 000000000..23a8d383d --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.12.2.md @@ -0,0 +1,18 @@ +# 0.12.2 (2024-11-12) + +## Improvements + +- Change Spark `jobDescription` for DBReader & FileDFReader from `DBReader.run() -> Connection` to `Connection -> DBReader.run()`. + +## Bug Fixes + +- Fix `log_hwm` result for `KeyValueIntHWM` (used by Kafka). ([#316](https://github.com/MobileTeleSystems/onetl/pull/316)) +- Fix `log_collection` hiding values of `Kafka.addresses` in logs with `INFO` level. ([#316](https://github.com/MobileTeleSystems/onetl/pull/316)) + +## Dependencies + +- Allow using [etl-entities==2.4.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.4.0). + +## Doc only Changes + +- Fix links to MSSQL date & time type documentation. diff --git a/mddocs/docs/_build/markdown/changelog/0.12.3.md b/mddocs/docs/_build/markdown/changelog/0.12.3.md new file mode 100644 index 000000000..741c74e1f --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.12.3.md @@ -0,0 +1,5 @@ +# 0.12.3 (2024-11-22) + +## Bug Fixes + +- Allow passing table names in format `schema."table.with.dots"` to `DBReader(source=...)` and `DBWriter(target=...)`. diff --git a/mddocs/docs/_build/markdown/changelog/0.12.4.md b/mddocs/docs/_build/markdown/changelog/0.12.4.md new file mode 100644 index 000000000..3ebc57a87 --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.12.4.md @@ -0,0 +1,5 @@ +# 0.12.4 (2024-11-27) + +## Bug Fixes + +- Fix `DBReader(conn=oracle, options={"partitioning_mode": "hash"})` lead to data skew in last partition due to wrong `ora_hash` usage. ([#319](https://github.com/MobileTeleSystems/onetl/pull/319)) diff --git a/mddocs/docs/_build/markdown/changelog/0.12.5.md b/mddocs/docs/_build/markdown/changelog/0.12.5.md new file mode 100644 index 000000000..c542a50fd --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.12.5.md @@ -0,0 +1,13 @@ +# 0.12.5 (2024-12-03) + +## Improvements + +- Use `sipHash64` instead of `md5` in Clickhouse for reading data with `{"partitioning_mode": "hash"}`, as it is 5 times faster. +- Use `hashtext` instead of `md5` in Postgres for reading data with `{"partitioning_mode": "hash"}`, as it is 3-5 times faster. +- Use `BINARY_CHECKSUM` instead of `HASHBYTES` in MSSQL for reading data with `{"partitioning_mode": "hash"}`, as it is 5 times faster. + +## Big fixes + +- In JDBC sources wrap `MOD(partitionColumn, numPartitions)` with `ABS(...)` to make al returned values positive. This prevents data skew. +- Fix reading table data from MSSQL using `{"partitioning_mode": "hash"}` with `partitionColumn` of integer type. +- Fix reading table data from Postgres using `{"partitioning_mode": "hash"}` lead to data skew (all the data was read into one Spark partition). diff --git a/mddocs/docs/_build/markdown/changelog/0.13.0.md b/mddocs/docs/_build/markdown/changelog/0.13.0.md new file mode 100644 index 000000000..0b7256299 --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.13.0.md @@ -0,0 +1,197 @@ +# 0.13.0 (2025-02-24) + +🎉 3 years since first release 0.1.0 🎉 + +## Breaking Changes + +- Add Python 3.13. support. ([#298](https://github.com/MobileTeleSystems/onetl/pull/298)) +- Change the logic of `FileConnection.walk` and `FileConnection.list_dir`. ([#327](https://github.com/MobileTeleSystems/onetl/pull/327)) + + Previously `limits.stops_at(path) == True` considered as “return current file and stop”, and could lead to exceeding some limit. + Not it means “stop immediately”. +- Change default value for `FileDFWriter.Options(if_exists=...)` from `error` to `append`, + to make it consistent with other `.Options()` classes within onETL. ([#343](https://github.com/MobileTeleSystems/onetl/pull/343)) + +## Features + +- Add support for `FileModifiedTimeHWM` HWM class (see [etl-entities 2.5.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.5.0)): + ```python + from etl_entitites.hwm import FileModifiedTimeHWM + from onetl.file import FileDownloader + from onetl.strategy import IncrementalStrategy + + downloader = FileDownloader( + ..., + hwm=FileModifiedTimeHWM(name="somename"), + ) + + with IncrementalStrategy(): + downloader.run() + ``` +- Introduce `FileSizeRange(min=..., max=...)` filter class. ([#325](https://github.com/MobileTeleSystems/onetl/pull/325)) + + Now users can set `FileDownloader` / `FileMover` to download/move only files with specific file size range: + ```python + from onetl.file import FileDownloader + from onetl.file.filter import FileSizeRange + + downloader = FileDownloader( + ..., + filters=[FileSizeRange(min="10KiB", max="1GiB")], + ) + ``` +- Introduce `TotalFilesSize(...)` limit class. ([#326](https://github.com/MobileTeleSystems/onetl/pull/326)) + + Now users can set `FileDownloader` / `FileMover` to stop downloading/moving files after reaching a certain amount of data: + ```python + from datetime import datetime, timedelta + from onetl.file import FileDownloader + from onetl.file.limit import TotalFilesSize + + downloader = FileDownloader( + ..., + limits=[TotalFilesSize("1GiB")], + ) + ``` +- Implement `FileModifiedTime(since=..., until=...)` file filter. ([#330](https://github.com/MobileTeleSystems/onetl/pull/330)) + + Now users can set `FileDownloader` / `FileMover` to download/move only files with specific file modification time: + ```python + from datetime import datetime, timedelta + from onetl.file import FileDownloader + from onetl.file.filter import FileModifiedTime + + downloader = FileDownloader( + ..., + filters=[FileModifiedTime(before=datetime.now() - timedelta(hours=1))], + ) + ``` +- Add `SparkS3.get_exclude_packages()` and `Kafka.get_exclude_packages()` methods. ([#341](https://github.com/MobileTeleSystems/onetl/pull/341)) + + Using them allows to skip downloading dependencies not required by this specific connector, or which are already a part of Spark/PySpark: + ```python + from onetl.connection import SparkS3, Kafka + + maven_packages = [ + *SparkS3.get_packages(spark_version="3.5.4"), + *Kafka.get_packages(spark_version="3.5.4"), + ] + exclude_packages = SparkS3.get_exclude_packages() + Kafka.get_exclude_packages() + spark = ( + SparkSession.builder.appName("spark_app_onetl_demo") + .config("spark.jars.packages", ",".join(maven_packages)) + .config("spark.jars.excludes", ",".join(exclude_packages)) + .getOrCreate() + ) + ``` + +## Improvements + +- All DB connections opened by `JDBC.fetch(...)`, `JDBC.execute(...)` or `JDBC.check()` + are immediately closed after the statements is executed. ([#334](https://github.com/MobileTeleSystems/onetl/pull/334)) + + Previously Spark session with `master=local[3]` actually opened up to 5 connections to target DB - one for `JDBC.check()`, + another for Spark driver interaction with DB to create tables, and one for each Spark executor. Now only max 4 connections are opened, + as `JDBC.check()` does not hold opened connection. + + This is important for RDBMS like Postgres or Greenplum where number of connections is strictly limited and limit is usually quite low. +- Set up `ApplicationName` (client info) for Clickhouse, MongoDB, MSSQL, MySQL and Oracle. ([#339](https://github.com/MobileTeleSystems/onetl/pull/339), [#248](https://github.com/MobileTeleSystems/onetl/pull/248)) + + Also update `ApplicationName` format for Greenplum, Postgres, Kafka and SparkS3. + Now all connectors have the same `ApplicationName` format: `${spark.applicationId} ${spark.appName} onETL/${onetl.version} Spark/${spark.version}` + + The only connections not sending `ApplicationName` are Teradata and FileConnection implementations. +- Now `DB.check()` will test connection availability not only on Spark driver, but also from some Spark executor. ([#346](https://github.com/MobileTeleSystems/onetl/pull/346)) + + This allows to fail immediately if Spark driver host has network access to target DB, but Spark executors have not. + + #### NOTE + Now `Greenplum.check()` requires the same user grants as `DBReader(connection=greenplum)`: + ```sql + -- yes, "writable" for reading data from GP, it's not a mistake + ALTER USER username CREATEEXTTABLE(type = 'writable', protocol = 'gpfdist'); + + -- for both reading and writing to GP + -- ALTER USER username CREATEEXTTABLE(type = 'readable', protocol = 'gpfdist') CREATEEXTTABLE(type = 'writable', protocol = 'gpfdist'); + ``` + + Please ask your Greenplum administrators to provide these grants. + +## Bug Fixes + +- Avoid suppressing Hive Metastore errors while using `DBWriter`. ([#329](https://github.com/MobileTeleSystems/onetl/pull/329)) + + Previously this was implemented as: + ```python + try: + spark.sql(f"SELECT * FROM {table}") + table_exists = True + except Exception: + table_exists = False + ``` + + If Hive Metastore was overloaded and responded with an exception, it was considered as non-existing table, resulting + to full table override instead of append or override only partitions subset. +- Fix using onETL to write data to PostgreSQL or Greenplum instances behind *pgbouncer* with `pool_mode=transaction`. ([#336](https://github.com/MobileTeleSystems/onetl/pull/336)) + + Previously `Postgres.check()` opened a read-only transaction, pgbouncer changed the entire connection type from read-write to read-only, + and when `DBWriter.run(df)` executed in read-only connection, producing errors like: + ```default + org.postgresql.util.PSQLException: ERROR: cannot execute INSERT in a read-only transaction + org.postgresql.util.PSQLException: ERROR: cannot execute TRUNCATE TABLE in a read-only transaction + ``` + + Added a workaround by passing `readOnly=True` to JDBC params for read-only connections, so pgbouncer may differ read-only and read-write connections properly. + + After upgrading onETL 0.13.x or higher the same error still may appear of pgbouncer still holds read-only connections and returns them for DBWriter. + To this this, user can manually convert read-only connection to read-write: + ```python + postgres.execute("BEGIN READ WRITE;") # <-- add this line + DBWriter(...).run() + ``` + + After all connections in pgbouncer pool were converted from read-only to read-write, and error fixed, this additional line could be removed. + + See [Postgres JDBC driver documentation](https://jdbc.postgresql.org/documentation/use/). +- Fix `MSSQL.fetch(...)` and `MySQL.fetch(...)` opened a read-write connection instead of read-only. ([#337](https://github.com/MobileTeleSystems/onetl/pull/337)) + + Now this is fixed: + : * `MSSQL.fetch(...)` establishes connection with `ApplicationIntent=ReadOnly`. + * `MySQL.fetch(...)` calls `SET SESSION TRANSACTION READ ONLY` statement. +- Fixed passing multiple filters to `FileDownloader` and `FileMover`. ([#338](https://github.com/MobileTeleSystems/onetl/pull/338)) + If was caused by sorting filters list in internal logging method, but `FileFilter` subclasses are not sortable. +- Fix a false warning about a lof of parallel connections to Grenplum. ([#342](https://github.com/MobileTeleSystems/onetl/pull/342)) + + Creating Spark session with `.master("local[5]")` may open up to 6 connections to Greenplum (=number of Spark executors + 1 for driver), + but onETL instead used number of *CPU cores* on the host as a number of parallel connections. + + This lead to showing a false warning that number of Greenplum connections is too high, + which actually should be the case only if number of executors is higher than 30. +- Fix MongoDB trying to use current database name as `authSource`. ([#347](https://github.com/MobileTeleSystems/onetl/pull/347)) + + Use default connector value which is `admin` database. Previous onETL versions could be fixed by: + ```python + from onetl.connection import MongoDB + + mongodb = MongoDB( + ..., + database="mydb", + extra={ + "authSource": "admin", + }, + ) + ``` + +## Dependencies + +- Minimal `etl-entities` version is now [2.5.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.5.0). ([#331](https://github.com/MobileTeleSystems/onetl/pull/331)) +- Update DB connectors/drivers to latest versions: ([#345](https://github.com/MobileTeleSystems/onetl/pull/345)) + : * Clickhouse `0.6.5` → `0.7.2` + * MongoDB `10.4.0` → `10.4.1` + * MySQL `9.0.0` → `9.2.0` + * Oracle `23.5.0.24.07` → `23.7.0.25.01` + * Postgres `42.7.4` → `42.7.5` + +## Doc only Changes + +- Split large code examples to tabs. ([#344](https://github.com/MobileTeleSystems/onetl/pull/344)) diff --git a/mddocs/docs/_build/markdown/changelog/0.13.1.md b/mddocs/docs/_build/markdown/changelog/0.13.1.md new file mode 100644 index 000000000..e8eb120d2 --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.13.1.md @@ -0,0 +1,9 @@ +# 0.13.1 (2025-03-06) + +## Bug Fixes + +In 0.13.0, using `DBWriter(connection=hive, target="SOMEDB.SOMETABLE")` lead to executing `df.write.saveAsTable()` +instead of `df.write.insertInto()` if target table `somedb.sometable` already exist. + +This is caused by table name normalization (Hive uses lower-case names), which wasn’t properly handled by method used for checking table existence. +([#350](https://github.com/MobileTeleSystems/onetl/pull/350)) diff --git a/mddocs/docs/_build/markdown/changelog/0.13.3.md b/mddocs/docs/_build/markdown/changelog/0.13.3.md new file mode 100644 index 000000000..1aa289b49 --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.13.3.md @@ -0,0 +1,5 @@ +# 0.13.3 (2025-03-11) + +## Dependencies + +Allow using [etl-entities 2.6.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.6.0). diff --git a/mddocs/docs/_build/markdown/changelog/0.13.4.md b/mddocs/docs/_build/markdown/changelog/0.13.4.md new file mode 100644 index 000000000..10f695e0c --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.13.4.md @@ -0,0 +1,10 @@ +# 0.13.4 (2025-03-20) + +## Doc only Changes + +- Prefer `ReadOptions(partitionColumn=..., numPartitions=..., queryTimeout=...)` + instead of `ReadOptions(partition_column=..., num_partitions=..., query_timeout=...)`, + to match Spark documentation. ([#352](https://github.com/MobileTeleSystems/onetl/pull/352)) +- Prefer `WriteOptions(if_exists=...)` instead of `WriteOptions(mode=...)` for IDE suggestions. ([#354](https://github.com/MobileTeleSystems/onetl/pull/354)) +- Document all options of supported file formats. + ([#355](https://github.com/MobileTeleSystems/onetl/pull/355), [#356](https://github.com/MobileTeleSystems/onetl/pull/356), [#357](https://github.com/MobileTeleSystems/onetl/pull/357), [#358](https://github.com/MobileTeleSystems/onetl/pull/358), [#359](https://github.com/MobileTeleSystems/onetl/pull/359), [#360](https://github.com/MobileTeleSystems/onetl/pull/360), [#361](https://github.com/MobileTeleSystems/onetl/pull/361), [#362](https://github.com/MobileTeleSystems/onetl/pull/362)) diff --git a/mddocs/docs/_build/markdown/changelog/0.7.0.md b/mddocs/docs/_build/markdown/changelog/0.7.0.md new file mode 100644 index 000000000..3f896b82b --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.7.0.md @@ -0,0 +1,211 @@ +# 0.7.0 (2023-05-15) + +## 🎉 onETL is now open source 🎉 + +That was long road, but we finally did it! + +## Breaking Changes + +* Changed installation method. + + **TL;DR What should I change to restore previous behavior** + + Simple way: + + | onETL < 0.7.0 | onETL >= 0.7.0 | + |-------------------|-----------------------------------| + | pip install onetl | pip install onetl[files,kerberos] | + + Right way - enumerate connectors should be installed: + ```bash + pip install onetl[hdfs,ftp,kerberos] # except DB connections + ``` + + **Details** + + In onetl<0.7 the package installation looks like: + ```bash + pip install onetl + ``` + + But this includes all dependencies for all connectors, even if user does not use them. + This caused some issues, for example user had to install Kerberos libraries to be able to install onETL, even if user uses only S3 (without Kerberos support). + + Since 0.7.0 installation process was changed: + ```bash + pip install onetl # minimal installation, only onETL core + # there is no extras for DB connections because they are using Java packages which are installed in runtime + + pip install onetl[ftp,ftps,hdfs,sftp,s3,webdav] # install dependencies for specified file connections + pip install onetl[files] # install dependencies for all file connections + + pip install onetl[kerberos] # Kerberos auth support + pip install onetl[spark] # install PySpark to use DB connections + + pip install onetl[spark,kerberos,files] # all file connections + Kerberos + PySpark + pip install onetl[all] # alias for previous case + ``` + + There are corresponding documentation items for each extras. + + Also onETL checks that some requirements are missing, and raises exception with recommendation how to install them: + ```text + Cannot import module "pyspark". + + Since onETL v0.7.0 you should install package as follows: + pip install onetl[spark] + + or inject PySpark to sys.path in some other way BEFORE creating MongoDB instance. + ``` + + ```text + Cannot import module "ftputil". + + Since onETL v0.7.0 you should install package as follows: + pip install onetl[ftp] + + or + pip install onetl[files] + ``` +* Added new `cluster` argument to `Hive` and `HDFS` connections. + + `Hive` qualified name (used in HWM) contains cluster name. But in onETL<0.7.0 cluster name had hard coded value `rnd-dwh` which was not OK for some users. + + `HDFS` connection qualified name contains host (active namenode of Hadoop cluster), but its value can change over time, leading to creating of new HWM. + + Since onETL 0.7.0 both `Hive` and `HDFS` connections have `cluster` attribute which can be set to a specific cluster name. + For `Hive` it is mandatory, for `HDFS` it can be omitted (using host as a fallback). + + But passing cluster name every time could lead to errors. + + Now `Hive` and `HDFS` have nested class named `slots` with methods: + * `normalize_cluster_name` + * `get_known_clusters` + * `get_current_cluster` + * `normalize_namenode_host` (only `HDFS`) + * `get_cluster_namenodes` (only `HDFS`) + * `get_webhdfs_port` (only `HDFS`) + * `is_namenode_active` (only `HDFS`) + + And new method `HDFS.get_current` / `Hive.get_current`. + + Developers can implement hooks validating user input or substituting values for automatic cluster detection. + This should improve user experience while using these connectors. + + See slots documentation. +* Update JDBC connection drivers. + * Greenplum `2.1.3` → `2.1.4`. + * MSSQL `10.2.1.jre8` → `12.2.0.jre8`. Minimal supported version of MSSQL is now 2014 instead 2021. + * MySQL `8.0.30` → `8.0.33`: + \* Package was renamed `mysql:mysql-connector-java` → `com.mysql:mysql-connector-j`. + \* Driver class was renamed `com.mysql.jdbc.Driver` → `com.mysql.cj.jdbc.Driver`. + * Oracle `21.6.0.0.1` → `23.2.0.0`. + * Postgres `42.4.0` → `42.6.0`. + * Teradata `17.20.00.08` → `17.20.00.15`: + \* Package was renamed `com.teradata.jdbc:terajdbc4` → `com.teradata.jdbc:terajdbc`. + \* Teradata driver is now published to Maven. + + See [#31](https://github.com/MobileTeleSystems/onetl/pull/31). + +## Features + +* Added MongoDB connection. + + Using official [MongoDB connector for Spark v10](https://www.mongodb.com/docs/spark-connector/current/). Only Spark 3.2+ is supported. + + There are some differences between MongoDB and other database sources: + * Instead of `mongodb.sql` method there is `mongodb.pipeline`. + * No methods `mongodb.fetch` and `mongodb.execute`. + * `DBReader.hint` and `DBReader.where` have different types than in SQL databases: + + ```python + where = { + "col1": { + "$eq": 10, + }, + } + + hint = { + "col1": 1, + } + ``` + + * Because MongoDB does not have schemas of collections, but Spark cannot create dataframe with dynamic schema, new option `DBReader.df_schema` was introduced. + It is mandatory for MongoDB, but optional for other sources. + * Currently DBReader cannot be used with MongoDB and hwm expression, e.g. `hwm_column=("mycolumn", {"$cast": {"col1": "date"}})` + + Because there are no tables in MongoDB, some options were renamed in core classes: + * `DBReader(table=...)` → `DBReader(source=...)` + * `DBWriter(table=...)` → `DBWriter(target=...)` + + Old names can be used too, they are not deprecated ([#30](https://github.com/MobileTeleSystems/onetl/pull/30)). +* Added option for disabling some plugins during import. + + Previously if some plugin were failing during the import, the only way to import onETL would be to disable all plugins + using environment variable. + + Now there are several variables with different behavior: + * `ONETL_PLUGINS_ENABLED=false` - disable all plugins autoimport. Previously it was named `ONETL_ENABLE_PLUGINS`. + * `ONETL_PLUGINS_BLACKLIST=plugin-name,another-plugin` - set list of plugins which should NOT be imported automatically. + * `ONETL_PLUGINS_WHITELIST=plugin-name,another-plugin` - set list of plugins which should ONLY be imported automatically. + + Also we improved exception message with recommendation how to disable a failing plugin: + ```text + Error while importing plugin 'mtspark' from package 'mtspark' v4.0.0. + + Statement: + import mtspark.onetl + + Check if plugin is compatible with current onETL version 0.7.0. + + You can disable loading this plugin by setting environment variable: + ONETL_PLUGINS_BLACKLIST='mtspark,failing-plugin' + + You can also define a whitelist of packages which can be loaded by onETL: + ONETL_PLUGINS_WHITELIST='not-failing-plugin1,not-failing-plugin2' + + Please take into account that plugin name may differ from package or module name. + See package metadata for more details + ``` + +## Improvements + +* Added compatibility with Python 3.11 and PySpark 3.4.0. + + File connections were OK, but `jdbc.fetch` and `jdbc.execute` were failing. Fixed in [#28](https://github.com/MobileTeleSystems/onetl/pull/28). +* Added check for missing Java packages. + + Previously if DB connection tried to use some Java class which were not loaded into Spark version, it raised an exception + with long Java stacktrace. Most users failed to interpret this trace. + + Now onETL shows the following error message: + ```text + |Spark| Cannot import Java class 'com.mongodb.spark.sql.connector.MongoTableProvider'. + + It looks like you've created Spark session without this option: + SparkSession.builder.config("spark.jars.packages", MongoDB.package_spark_3_2) + + Please call `spark.stop()`, restart the interpreter, + and then create new SparkSession with proper options. + ``` +* Documentation improvements. + * Changed documentation site theme - using [furo](https://github.com/pradyunsg/furo) + instead of default [ReadTheDocs](https://github.com/readthedocs/sphinx_rtd_theme). + + New theme supports wide screens and dark mode. + See [#10](https://github.com/MobileTeleSystems/onetl/pull/10). + * Now each connection class have compatibility table for Spark + Java + Python. + * Added global compatibility table for Spark + Java + Python + Scala. + +## Bug Fixes + +* Fixed several SFTP issues. + * If SSH config file `~/.ssh/config` contains some options not recognized by Paramiko (unknown syntax, unknown option name), + previous versions were raising exception until fixing or removing this file. Since 0.7.0 exception is replaced with warning. + * If user passed `host_key_check=False` but server changed SSH keys, previous versions raised exception until new key is accepted. + Since 0.7.0 exception is replaced with warning if option value is `False`. + + Fixed in [#19](https://github.com/MobileTeleSystems/onetl/pull/19). +* Fixed several S3 issues. + + There was a bug in S3 connection which prevented handling files in the root of a bucket - they were invisible for the connector. Fixed in [#29](https://github.com/MobileTeleSystems/onetl/pull/29). diff --git a/mddocs/docs/_build/markdown/changelog/0.7.1.md b/mddocs/docs/_build/markdown/changelog/0.7.1.md new file mode 100644 index 000000000..fc423f756 --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.7.1.md @@ -0,0 +1,31 @@ +# 0.7.1 (2023-05-23) + +## Bug Fixes + +* Fixed `setup_logging` function. + + In onETL==0.7.0 calling `onetl.log.setup_logging()` broke the logging: + ```text + Traceback (most recent call last): + File "/opt/anaconda/envs/py39/lib/python3.9/logging/__init__.py", line 434, in format + return self._format(record) + File "/opt/anaconda/envs/py39/lib/python3.9/logging/__init__.py", line 430, in _format + return self._fmt % record.dict + KeyError: 'levelname:8s' + ``` +* Fixed installation examples. + + In onETL==0.7.0 there are examples of installing onETL with extras: + ```bash + pip install onetl[files, kerberos, spark] + ``` + + But pip fails to install such package: + ```text + ERROR: Invalid requirement: 'onet[files,' + ``` + + This is because of spaces in extras clause. Fixed: + ```bash + pip install onetl[files,kerberos,spark] + ``` diff --git a/mddocs/docs/_build/markdown/changelog/0.7.2.md b/mddocs/docs/_build/markdown/changelog/0.7.2.md new file mode 100644 index 000000000..80bb2cfc5 --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.7.2.md @@ -0,0 +1,33 @@ +# 0.7.2 (2023-05-24) + +## Dependencies + +* Limited `typing-extensions` version. + + `typing-extensions==4.6.0` release contains some breaking changes causing errors like: + ```text + Traceback (most recent call last): + File "/Users/project/lib/python3.9/typing.py", line 852, in __subclasscheck__ + return issubclass(cls, self.__origin__) + TypeError: issubclass() arg 1 must be a class + ``` + + `typing-extensions==4.6.1` was causing another error: + ```text + Traceback (most recent call last): + File "/home/maxim/Repo/typing_extensions/1.py", line 33, in + isinstance(file, ContainsException) + File "/home/maxim/Repo/typing_extensions/src/typing_extensions.py", line 599, in __instancecheck__ + if super().__instancecheck__(instance): + File "/home/maxim/.pyenv/versions/3.7.8/lib/python3.7/abc.py", line 139, in __instancecheck__ + return _abc_instancecheck(cls, instance) + File "/home/maxim/Repo/typing_extensions/src/typing_extensions.py", line 583, in __subclasscheck__ + return super().__subclasscheck__(other) + File "/home/maxim/.pyenv/versions/3.7.8/lib/python3.7/abc.py", line 143, in __subclasscheck__ + return _abc_subclasscheck(cls, subclass) + File "/home/maxim/Repo/typing_extensions/src/typing_extensions.py", line 661, in _proto_hook + and other._is_protocol + AttributeError: type object 'PathWithFailure' has no attribute '_is_protocol' + ``` + + We updated requirements with `typing-extensions<4.6` until fixing compatibility issues. diff --git a/mddocs/docs/_build/markdown/changelog/0.8.0.md b/mddocs/docs/_build/markdown/changelog/0.8.0.md new file mode 100644 index 000000000..3fe438549 --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.8.0.md @@ -0,0 +1,137 @@ +# 0.8.0 (2023-05-31) + +## Breaking Changes + +- Rename methods of `FileConnection` classes: + * `get_directory` → `resolve_dir` + * `get_file` → `resolve_file` + * `listdir` → `list_dir` + * `mkdir` → `create_dir` + * `rmdir` → `remove_dir` + + New naming should be more consistent. + + They were undocumented in previous versions, but someone could use these methods, so this is a breaking change. ([#36](https://github.com/MobileTeleSystems/onetl/pull/36)) +- Deprecate `onetl.core.FileFilter` class, replace it with new classes: + * `onetl.file.filter.Glob` + * `onetl.file.filter.Regexp` + * `onetl.file.filter.ExcludeDir` + + Old class will be removed in v1.0.0. ([#43](https://github.com/MobileTeleSystems/onetl/pull/43)) +- Deprecate `onetl.core.FileLimit` class, replace it with new class `onetl.file.limit.MaxFilesCount`. + + Old class will be removed in v1.0.0. ([#44](https://github.com/MobileTeleSystems/onetl/pull/44)) +- Change behavior of `BaseFileLimit.reset` method. + + This method should now return `self` instead of `None`. + Return value could be the same limit object or a copy, this is an implementation detail. ([#44](https://github.com/MobileTeleSystems/onetl/pull/44)) +- Replaced `FileDownloader.filter` and `.limit` with new options `.filters` and `.limits`: + ```python + FileDownloader( + ..., + filter=FileFilter(glob="*.txt", exclude_dir="/path"), + limit=FileLimit(count_limit=10), + ) + ``` + + ```python + FileDownloader( + ..., + filters=[Glob("*.txt"), ExcludeDir("/path")], + limits=[MaxFilesCount(10)], + ) + ``` + + This allows to developers to implement their own filter and limit classes, and combine them with existing ones. + + Old behavior still supported, but it will be removed in v1.0.0. ([#45](https://github.com/MobileTeleSystems/onetl/pull/45)) +- Removed default value for `FileDownloader.limits`, user should pass limits list explicitly. ([#45](https://github.com/MobileTeleSystems/onetl/pull/45)) +- Move classes from module `onetl.core`: + ```python + from onetl.core import DBReader + from onetl.core import DBWriter + from onetl.core import FileDownloader + from onetl.core import FileUploader + ``` + + with new modules `onetl.db` and `onetl.file`: + ```python + from onetl.db import DBReader + from onetl.db import DBWriter + + from onetl.file import FileDownloader + from onetl.file import FileUploader + ``` + + Imports from old module `onetl.core` still can be used, but marked as deprecated. Module will be removed in v1.0.0. ([#46](https://github.com/MobileTeleSystems/onetl/pull/46)) + +## Features + +- Add `rename_dir` method. + + Method was added to following connections: + * `FTP` + * `FTPS` + * `HDFS` + * `SFTP` + * `WebDAV` + + It allows to rename/move directory to new path with all its content. + + `S3` does not have directories, so there is no such method in that class. ([#40](https://github.com/MobileTeleSystems/onetl/pull/40)) +- Add `onetl.file.FileMover` class. + + It allows to move files between directories of remote file system. + Signature is almost the same as in `FileDownloader`, but without HWM support. ([#42](https://github.com/MobileTeleSystems/onetl/pull/42)) + +## Improvements + +- Document all public methods in `FileConnection` classes: + * `download_file` + * `resolve_dir` + * `resolve_file` + * `get_stat` + * `is_dir` + * `is_file` + * `list_dir` + * `create_dir` + * `path_exists` + * `remove_file` + * `rename_file` + * `remove_dir` + * `upload_file` + * `walk` ([#39](https://github.com/MobileTeleSystems/onetl/pull/39)) +- Update documentation of `check` method of all connections - add usage example and document result type. ([#39](https://github.com/MobileTeleSystems/onetl/pull/39)) +- Add new exception type `FileSizeMismatchError`. + + Methods `connection.download_file` and `connection.upload_file` now raise new exception type instead of `RuntimeError`, + if target file after download/upload has different size than source. ([#39](https://github.com/MobileTeleSystems/onetl/pull/39)) +- Add new exception type `DirectoryExistsError` - it is raised if target directory already exists. ([#40](https://github.com/MobileTeleSystems/onetl/pull/40)) +- Improved `FileDownloader` / `FileUploader` exception logging. + + If `DEBUG` logging is enabled, print exception with stacktrace instead of + printing only exception message. ([#42](https://github.com/MobileTeleSystems/onetl/pull/42)) +- Updated documentation of `FileUploader`. + * Class does not support read strategies, added note to documentation. + * Added examples of using `run` method with explicit files list passing, both absolute and relative paths. + * Fix outdated imports and class names in examples. ([#42](https://github.com/MobileTeleSystems/onetl/pull/42)) +- Updated documentation of `DownloadResult` class - fix outdated imports and class names. ([#42](https://github.com/MobileTeleSystems/onetl/pull/42)) +- Improved file filters documentation section. + + Document interface class `onetl.base.BaseFileFilter` and function `match_all_filters`. ([#43](https://github.com/MobileTeleSystems/onetl/pull/43)) +- Improved file limits documentation section. + + Document interface class `onetl.base.BaseFileLimit` and functions `limits_stop_at` / `limits_reached` / `reset_limits`. ([#44](https://github.com/MobileTeleSystems/onetl/pull/44)) +- Added changelog. + + Changelog is generated from separated news files using [towncrier](https://pypi.org/project/towncrier/). ([#47](https://github.com/MobileTeleSystems/onetl/pull/47)) + +## Misc + +- Improved CI workflow for tests. + * If developer haven’t changed source core of a specific connector or its dependencies, + run tests only against maximum supported versions of Spark, Python, Java and db/file server. + * If developed made some changes in a specific connector, or in core classes, or in dependencies, + run tests for both minimal and maximum versions. + * Once a week run all aganst for minimal and latest versions to detect breaking changes in dependencies + * Minimal tested Spark version is 2.3.1 instead on 2.4.8. ([#32](https://github.com/MobileTeleSystems/onetl/pull/32)) diff --git a/mddocs/docs/_build/markdown/changelog/0.8.1.md b/mddocs/docs/_build/markdown/changelog/0.8.1.md new file mode 100644 index 000000000..d3bd32564 --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.8.1.md @@ -0,0 +1,34 @@ +# 0.8.1 (2023-07-10) + +## Features + +- Add `@slot` decorator to public methods of: + * `DBConnection` + * `FileConnection` + * `DBReader` + * `DBWriter` + * `FileDownloader` + * `FileUploader` + * `FileMover` ([#49](https://github.com/MobileTeleSystems/onetl/pull/49)) +- Add `workers` field to `FileDownloader` / `FileUploader` / `FileMover`. `Options` classes. + + This allows to speed up all file operations using parallel threads. ([#57](https://github.com/MobileTeleSystems/onetl/pull/57)) + +## Improvements + +- Add documentation for HWM store `.get` and `.save` methods. ([#49](https://github.com/MobileTeleSystems/onetl/pull/49)) +- Improve Readme: + * Move `Quick start` section from documentation + * Add `Non-goals` section + * Fix code blocks indentation ([#50](https://github.com/MobileTeleSystems/onetl/pull/50)) +- Improve Contributing guide: + * Move `Develop` section from Readme + * Move `docs/changelog/README.rst` content + * Add `Limitations` section + * Add instruction of creating a fork and building documentation ([#50](https://github.com/MobileTeleSystems/onetl/pull/50)) +- Remove duplicated checks for source file existence in `FileDownloader` / `FileMover`. ([#57](https://github.com/MobileTeleSystems/onetl/pull/57)) +- Update default logging format to include thread name. ([#57](https://github.com/MobileTeleSystems/onetl/pull/57)) + +## Bug Fixes + +- Fix `S3.list_dir('/')` returns empty list on latest Minio version. ([#58](https://github.com/MobileTeleSystems/onetl/pull/58)) diff --git a/mddocs/docs/_build/markdown/changelog/0.9.0.md b/mddocs/docs/_build/markdown/changelog/0.9.0.md new file mode 100644 index 000000000..b2d10f257 --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.9.0.md @@ -0,0 +1,107 @@ +# 0.9.0 (2023-08-17) + +## Breaking Changes + +- Rename methods: + * `DBConnection.read_df` → `DBConnection.read_source_as_df` + * `DBConnection.write_df` → `DBConnection.write_df_to_target` ([#66](https://github.com/MobileTeleSystems/onetl/pull/66)) +- Rename classes: + * `HDFS.slots` → `HDFS.Slots` + * `Hive.slots` → `Hive.Slots` + + Old names are left intact, but will be removed in v1.0.0 ([#103](https://github.com/MobileTeleSystems/onetl/pull/103)) +- Rename options to make them self-explanatory: + * `Hive.WriteOptions(mode="append")` → `Hive.WriteOptions(if_exists="append")` + * `Hive.WriteOptions(mode="overwrite_table")` → `Hive.WriteOptions(if_exists="replace_entire_table")` + * `Hive.WriteOptions(mode="overwrite_partitions")` → `Hive.WriteOptions(if_exists="replace_overlapping_partitions")` + * `JDBC.WriteOptions(mode="append")` → `JDBC.WriteOptions(if_exists="append")` + * `JDBC.WriteOptions(mode="overwrite")` → `JDBC.WriteOptions(if_exists="replace_entire_table")` + * `Greenplum.WriteOptions(mode="append")` → `Greenplum.WriteOptions(if_exists="append")` + * `Greenplum.WriteOptions(mode="overwrite")` → `Greenplum.WriteOptions(if_exists="replace_entire_table")` + * `MongoDB.WriteOptions(mode="append")` → `Greenplum.WriteOptions(if_exists="append")` + * `MongoDB.WriteOptions(mode="overwrite")` → `Greenplum.WriteOptions(if_exists="replace_entire_collection")` + * `FileDownloader.Options(mode="error")` → `FileDownloader.Options(if_exists="error")` + * `FileDownloader.Options(mode="ignore")` → `FileDownloader.Options(if_exists="ignore")` + * `FileDownloader.Options(mode="overwrite")` → `FileDownloader.Options(if_exists="replace_file")` + * `FileDownloader.Options(mode="delete_all")` → `FileDownloader.Options(if_exists="replace_entire_directory")` + * `FileUploader.Options(mode="error")` → `FileUploader.Options(if_exists="error")` + * `FileUploader.Options(mode="ignore")` → `FileUploader.Options(if_exists="ignore")` + * `FileUploader.Options(mode="overwrite")` → `FileUploader.Options(if_exists="replace_file")` + * `FileUploader.Options(mode="delete_all")` → `FileUploader.Options(if_exists="replace_entire_directory")` + * `FileMover.Options(mode="error")` → `FileMover.Options(if_exists="error")` + * `FileMover.Options(mode="ignore")` → `FileMover.Options(if_exists="ignore")` + * `FileMover.Options(mode="overwrite")` → `FileMover.Options(if_exists="replace_file")` + * `FileMover.Options(mode="delete_all")` → `FileMover.Options(if_exists="replace_entire_directory")` + + Old names are left intact, but will be removed in v1.0.0 ([#108](https://github.com/MobileTeleSystems/onetl/pull/108)) +- Rename `onetl.log.disable_clients_logging()` to `onetl.log.setup_clients_logging()`. ([#120](https://github.com/MobileTeleSystems/onetl/pull/120)) + +## Features + +- Add new methods returning Maven packages for specific connection class: + * `Clickhouse.get_packages()` + * `MySQL.get_packages()` + * `Postgres.get_packages()` + * `Teradata.get_packages()` + * `MSSQL.get_packages(java_version="8")` + * `Oracle.get_packages(java_version="8")` + * `Greenplum.get_packages(scala_version="2.12")` + * `MongoDB.get_packages(scala_version="2.12")` + * `Kafka.get_packages(spark_version="3.4.1", scala_version="2.12")` + + Deprecate old syntax: + * `Clickhouse.package` + * `MySQL.package` + * `Postgres.package` + * `Teradata.package` + * `MSSQL.package` + * `Oracle.package` + * `Greenplum.package_spark_2_3` + * `Greenplum.package_spark_2_4` + * `Greenplum.package_spark_3_2` + * `MongoDB.package_spark_3_2` + * `MongoDB.package_spark_3_3` + * `MongoDB.package_spark_3_4` ([#87](https://github.com/MobileTeleSystems/onetl/pull/87)) +- Allow to set client modules log level in `onetl.log.setup_clients_logging()`. + + Allow to enable underlying client modules logging in `onetl.log.setup_logging()` by providing additional argument `enable_clients=True`. + This is useful for debug. ([#120](https://github.com/MobileTeleSystems/onetl/pull/120)) +- Added support for reading and writing data to Kafka topics. + + For these operations, new classes were added. + * `Kafka` ([#54](https://github.com/MobileTeleSystems/onetl/pull/54), [#60](https://github.com/MobileTeleSystems/onetl/pull/60), [#72](https://github.com/MobileTeleSystems/onetl/pull/72), [#84](https://github.com/MobileTeleSystems/onetl/pull/84), [#87](https://github.com/MobileTeleSystems/onetl/pull/87), [#89](https://github.com/MobileTeleSystems/onetl/pull/89), [#93](https://github.com/MobileTeleSystems/onetl/pull/93), [#96](https://github.com/MobileTeleSystems/onetl/pull/96), [#102](https://github.com/MobileTeleSystems/onetl/pull/102), [#104](https://github.com/MobileTeleSystems/onetl/pull/104)) + * `Kafka.PlaintextProtocol` ([#79](https://github.com/MobileTeleSystems/onetl/pull/79)) + * `Kafka.SSLProtocol` ([#118](https://github.com/MobileTeleSystems/onetl/pull/118)) + * `Kafka.BasicAuth` ([#63](https://github.com/MobileTeleSystems/onetl/pull/63), [#77](https://github.com/MobileTeleSystems/onetl/pull/77)) + * `Kafka.KerberosAuth` ([#63](https://github.com/MobileTeleSystems/onetl/pull/63), [#77](https://github.com/MobileTeleSystems/onetl/pull/77), [#110](https://github.com/MobileTeleSystems/onetl/pull/110)) + * `Kafka.ScramAuth` ([#115](https://github.com/MobileTeleSystems/onetl/pull/115)) + * `Kafka.Slots` ([#109](https://github.com/MobileTeleSystems/onetl/pull/109)) + * `Kafka.ReadOptions` ([#68](https://github.com/MobileTeleSystems/onetl/pull/68)) + * `Kafka.WriteOptions` ([#68](https://github.com/MobileTeleSystems/onetl/pull/68)) + + Currently, Kafka does not support incremental read strategies, this will be implemented in future releases. +- Added support for reading files as Spark DataFrame and saving DataFrame as Files. + + For these operations, new classes were added. + + FileDFConnections: + * `SparkHDFS` ([#98](https://github.com/MobileTeleSystems/onetl/pull/98)) + * `SparkS3` ([#94](https://github.com/MobileTeleSystems/onetl/pull/94), [#100](https://github.com/MobileTeleSystems/onetl/pull/100), [#124](https://github.com/MobileTeleSystems/onetl/pull/124)) + * `SparkLocalFS` ([#67](https://github.com/MobileTeleSystems/onetl/pull/67)) + + High-level classes: + * `FileDFReader` ([#73](https://github.com/MobileTeleSystems/onetl/pull/73)) + * `FileDFWriter` ([#81](https://github.com/MobileTeleSystems/onetl/pull/81)) + + File formats: + * `Avro` ([#69](https://github.com/MobileTeleSystems/onetl/pull/69)) + * `CSV` ([#92](https://github.com/MobileTeleSystems/onetl/pull/92)) + * `JSON` ([#83](https://github.com/MobileTeleSystems/onetl/pull/83)) + * `JSONLine` ([#83](https://github.com/MobileTeleSystems/onetl/pull/83)) + * `ORC` ([#86](https://github.com/MobileTeleSystems/onetl/pull/86)) + * `Parquet` ([#88](https://github.com/MobileTeleSystems/onetl/pull/88)) + +## Improvements + +- Remove redundant checks for driver availability in Greenplum and MongoDB connections. ([#67](https://github.com/MobileTeleSystems/onetl/pull/67)) +- Check of Java class availability moved from `.check()` method to connection constructor. ([#97](https://github.com/MobileTeleSystems/onetl/pull/97)) diff --git a/mddocs/docs/_build/markdown/changelog/0.9.1.md b/mddocs/docs/_build/markdown/changelog/0.9.1.md new file mode 100644 index 000000000..1779274b1 --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.9.1.md @@ -0,0 +1,7 @@ +# 0.9.1 (2023-08-17) + +## Bug Fixes + +- Fixed bug then number of threads created by `FileDownloader` / `FileUploader` / `FileMover` was + not `min(workers, len(files))`, but `max(workers, len(files))`. leading to create too much workers + on large files list. diff --git a/mddocs/docs/_build/markdown/changelog/0.9.2.md b/mddocs/docs/_build/markdown/changelog/0.9.2.md new file mode 100644 index 000000000..4ca4fcff9 --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.9.2.md @@ -0,0 +1,21 @@ +# 0.9.2 (2023-09-06) + +## Features + +- Add `if_exists="ignore"` and `error` to `Greenplum.WriteOptions` ([#142](https://github.com/MobileTeleSystems/onetl/pull/142)) + +## Improvements + +- Improve validation messages while writing dataframe to Kafka. ([#131](https://github.com/MobileTeleSystems/onetl/pull/131)) +- Improve documentation: + * Add notes about reading and writing to database connections documentation + * Add notes about executing statements in JDBC and Greenplum connections + +## Bug Fixes + +- Fixed validation of `headers` column is written to Kafka with default `Kafka.WriteOptions()` - default value was `False`, + but instead of raising an exception, column value was just ignored. ([#131](https://github.com/MobileTeleSystems/onetl/pull/131)) +- Fix reading data from Oracle with `partitioningMode="range"` without explicitly set `lowerBound` / `upperBound`. ([#133](https://github.com/MobileTeleSystems/onetl/pull/133)) +- Update Kafka documentation with SSLProtocol usage. ([#136](https://github.com/MobileTeleSystems/onetl/pull/136)) +- Raise exception if someone tries to read data from Kafka topic which does not exist. ([#138](https://github.com/MobileTeleSystems/onetl/pull/138)) +- Allow to pass Kafka topics with name like `some.topic.name` to DBReader. Same for MongoDB collections. ([#139](https://github.com/MobileTeleSystems/onetl/pull/139)) diff --git a/mddocs/docs/_build/markdown/changelog/0.9.3.md b/mddocs/docs/_build/markdown/changelog/0.9.3.md new file mode 100644 index 000000000..1a8c25d4d --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.9.3.md @@ -0,0 +1,5 @@ +# 0.9.3 (2023-09-06) + +## Bug Fixes + +- Fix documentation build diff --git a/mddocs/docs/_build/markdown/changelog/0.9.4.md b/mddocs/docs/_build/markdown/changelog/0.9.4.md new file mode 100644 index 000000000..cf9288760 --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.9.4.md @@ -0,0 +1,24 @@ +# 0.9.4 (2023-09-26) + +## Features + +- Add `Excel` file format support. ([#148](https://github.com/MobileTeleSystems/onetl/pull/148)) +- Add `Samba` file connection. + It is now possible to download and upload files to Samba shared folders using `FileDownloader`/`FileUploader`. ([#150](https://github.com/MobileTeleSystems/onetl/pull/150)) +- Add `if_exists="ignore"` and `error` to `Hive.WriteOptions` ([#143](https://github.com/MobileTeleSystems/onetl/pull/143)) +- Add `if_exists="ignore"` and `error` to `JDBC.WriteOptions` ([#144](https://github.com/MobileTeleSystems/onetl/pull/144)) +- Add `if_exists="ignore"` and `error` to `MongoDB.WriteOptions` ([#145](https://github.com/MobileTeleSystems/onetl/pull/145)) + +## Improvements + +- Add documentation about different ways of passing packages to Spark session. ([#151](https://github.com/MobileTeleSystems/onetl/pull/151)) +- Drastically improve `Greenplum` documentation: + : * Added information about network ports, grants, `pg_hba.conf` and so on. + * Added interaction schemas for reading, writing and executing statements in Greenplum. + * Added recommendations about reading data from views and `JOIN` results from Greenplum. ([#154](https://github.com/MobileTeleSystems/onetl/pull/154)) +- Make `.fetch` and `.execute` methods of DB connections thread-safe. Each thread works with its own connection. ([#156](https://github.com/MobileTeleSystems/onetl/pull/156)) +- Call `.close()` on `FileConnection` then it is removed by garbage collector. ([#156](https://github.com/MobileTeleSystems/onetl/pull/156)) + +## Bug Fixes + +- Fix issue when stopping Python interpreter calls `JDBCMixin.close()`, but it is finished with exceptions. ([#156](https://github.com/MobileTeleSystems/onetl/pull/156)) diff --git a/mddocs/docs/_build/markdown/changelog/0.9.5.md b/mddocs/docs/_build/markdown/changelog/0.9.5.md new file mode 100644 index 000000000..1d7358c0b --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/0.9.5.md @@ -0,0 +1,14 @@ +# 0.9.5 (2023-10-10) + +## Features + +- Add `XML` file format support. ([#163](https://github.com/MobileTeleSystems/onetl/pull/163)) +- Tested compatibility with Spark 3.5.0. `MongoDB` and `Excel` are not supported yet, but other packages do. ([#159](https://github.com/MobileTeleSystems/onetl/pull/159)) + +## Improvements + +- Add check to all DB and FileDF connections that Spark session is alive. ([#164](https://github.com/MobileTeleSystems/onetl/pull/164)) + +## Bug Fixes + +- Fix `Hive.check()` behavior when Hive Metastore is not available. ([#164](https://github.com/MobileTeleSystems/onetl/pull/164)) diff --git a/mddocs/docs/_build/markdown/changelog/DRAFT.md b/mddocs/docs/_build/markdown/changelog/DRAFT.md new file mode 100644 index 000000000..e69de29bb diff --git a/mddocs/docs/_build/markdown/changelog/NEXT_RELEASE.md b/mddocs/docs/_build/markdown/changelog/NEXT_RELEASE.md new file mode 100644 index 000000000..8cf66aa33 --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/NEXT_RELEASE.md @@ -0,0 +1 @@ + diff --git a/mddocs/docs/_build/markdown/changelog/index.md b/mddocs/docs/_build/markdown/changelog/index.md new file mode 100644 index 000000000..37d5d85ae --- /dev/null +++ b/mddocs/docs/_build/markdown/changelog/index.md @@ -0,0 +1,29 @@ +Changelog + +* [0.13.4 (2025-03-20)](0.13.4.md) +* [0.13.3 (2025-03-11)](0.13.3.md) +* [0.13.1 (2025-03-06)](0.13.1.md) +* [0.13.0 (2025-02-24)](0.13.0.md) +* [0.12.5 (2024-12-03)](0.12.5.md) +* [0.12.4 (2024-11-27)](0.12.4.md) +* [0.12.3 (2024-11-22)](0.12.3.md) +* [0.12.2 (2024-11-12)](0.12.2.md) +* [0.12.1 (2024-10-28)](0.12.1.md) +* [0.12.0 (2024-09-03)](0.12.0.md) +* [0.11.2 (2024-09-02)](0.11.2.md) +* [0.11.1 (2024-05-29)](0.11.1.md) +* [0.11.0 (2024-05-27)](0.11.0.md) +* [0.10.2 (2024-03-21)](0.10.2.md) +* [0.10.1 (2024-02-05)](0.10.1.md) +* [0.10.0 (2023-12-18)](0.10.0.md) +* [0.9.5 (2023-10-10)](0.9.5.md) +* [0.9.4 (2023-09-26)](0.9.4.md) +* [0.9.3 (2023-09-06)](0.9.3.md) +* [0.9.2 (2023-09-06)](0.9.2.md) +* [0.9.1 (2023-08-17)](0.9.1.md) +* [0.9.0 (2023-08-17)](0.9.0.md) +* [0.8.1 (2023-07-10)](0.8.1.md) +* [0.8.0 (2023-05-31)](0.8.0.md) +* [0.7.2 (2023-05-24)](0.7.2.md) +* [0.7.1 (2023-05-23)](0.7.1.md) +* [0.7.0 (2023-05-15)](0.7.0.md) diff --git a/mddocs/docs/_build/markdown/concepts.md b/mddocs/docs/_build/markdown/concepts.md new file mode 100644 index 000000000..cf74e9eaa --- /dev/null +++ b/mddocs/docs/_build/markdown/concepts.md @@ -0,0 +1,340 @@ +# Concepts + +Here you can find detailed documentation about each one of the onETL concepts and how to use them. + +## Connection + +### Basics + +onETL is used to pull and push data into other systems, and so it has a first-class `Connection` concept for storing credentials that are used to communicate with external systems. + +A `Connection` is essentially a set of parameters, such as username, password, hostname. + +To create a connection to a specific storage type, you must use a class that matches the storage type. The class name is the same as the storage type name (`Oracle`, `MSSQL`, `SFTP`, etc): + +```python +from onetl.connection import SFTP + +sftp = SFTP( + host="sftp.test.com", + user="onetl", + password="onetl", +) +``` + +All connection types are inherited from the parent class `BaseConnection`. + +### Class diagram + +### DBConnection + +Classes inherited from `DBConnection` could be used for accessing databases. + +A `DBConnection` could be instantiated as follows: + +```python +from onetl.connection import MSSQL + +mssql = MSSQL( + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, +) +``` + +where **spark** is the current SparkSession. +`onETL` uses `Spark` and specific Java connectors under the hood to work with databases. + +For a description of other parameters, see the documentation for the [available DBConnections](connection/db_connection/index.md#db-connections). + +### FileConnection + +Classes inherited from `FileConnection` could be used to access files stored on the different file systems/file servers + +A `FileConnection` could be instantiated as follows: + +```python +from onetl.connection import SFTP + +sftp = SFTP( + host="sftp.test.com", + user="onetl", + password="onetl", +) +``` + +For a description of other parameters, see the documentation for the [available FileConnections](connection/file_connection/index.md#file-connections). + +### FileDFConnection + +Classes inherited from `FileDFConnection` could be used for accessing files as Spark DataFrames. + +A `FileDFConnection` could be instantiated as follows: + +```python +from onetl.connection import SparkHDFS + +spark_hdfs = SparkHDFS( + host="namenode1.domain.com", + cluster="mycluster", + spark=spark, +) +``` + +where **spark** is the current SparkSession. +`onETL` uses `Spark` and specific Java connectors under the hood to work with DataFrames. + +For a description of other parameters, see the documentation for the [available FileDFConnections](connection/file_df_connection/index.md#file-df-connections). + +### Checking connection availability + +Once you have created a connection, you can check the database/filesystem availability using the method `check()`: + +```python +mssql.check() +sftp.check() +spark_hdfs.check() +``` + +It will raise an exception if database/filesystem cannot be accessed. + +This method returns connection itself, so you can create connection and immediately check its availability: + +```Python +mssql = MSSQL( + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, +).check() # <-- +``` + +## Extract/Load data + +### Basics + +As we said above, onETL is used to extract data from and load data into remote systems. + +onETL provides several classes for this: + +> * [DBReader](db/db_reader.md#db-reader) +> * [DBWriter](db/db_writer.md#db-writer) +> * [FileDFReader](file_df/file_df_reader/file_df_reader.md#file-df-reader) +> * [FileDFWriter](file_df/file_df_writer/file_df_writer.md#file-df-writer) +> * [FileDownloader](file/file_downloader/file_downloader.md#file-downloader) +> * [FileUploader](file/file_uploader/file_uploader.md#file-uploader) +> * [FileMover](file/file_mover/file_mover.md#file-mover) + +All of these classes have a method `run()` that starts extracting/loading the data: + +```python +from onetl.db import DBReader, DBWriter + +reader = DBReader( + connection=mssql, + source="dbo.demo_table", + columns=["column_1", "column_2"], +) + +# Read data as Spark DataFrame +df = reader.run() + +db_writer = DBWriter( + connection=hive, + target="dl_sb.demo_table", +) + +# Save Spark DataFrame to Hive table +writer.run(df) +``` + +### Extract data + +To extract data you can use classes: + +| | Use case | Connection | `run()` gets | `run()` returns | +|-------------------------------------------------------------------------|-------------------------------------------|------------------------------------------------------------------------------------|---------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------| +| [DBReader](db/db_reader.md#db-reader) | Reading data from a database | Any [DBConnection](connection/db_connection/index.md#db-connections) | - | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | +| [FileDFReader](file_df/file_df_reader/file_df_reader.md#file-df-reader) | Read data from a file or set of files | Any [FileDFConnection](connection/file_df_connection/index.md#file-df-connections) | No input, or List[File path on FileSystem] | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | +| [FileDownloader](db/db_reader.md#db-reader) | Download files from remote FS to local FS | Any [FileConnection](connection/file_connection/index.md#file-connections) | No input, or List[File path on remote FileSystem] | [DownloadResult](file/file_downloader/result.md#file-downloader-result) | + +### Load data + +To load data you can use classes: + +| | Use case | Connection | `run()` gets | `run()` returns | +|-------------------------------------------------------------------|----------------------------------------------|------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------| +| [DBWriter](db/db_writer.md#db-writer) | Writing data from a DataFrame to a database | Any [DBConnection](connection/db_connection/index.md#db-connections) | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | None | +| [FileDFWriter](db/db_writer.md#db-writer) | Writing data from a DataFrame to a folder | Any [FileDFConnection](connection/file_df_connection/index.md#file-df-connections) | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | None | +| [FileUploader](file/file_uploader/file_uploader.md#file-uploader) | Uploading files from a local FS to remote FS | Any [FileConnection](connection/file_connection/index.md#file-connections) | List[File path on local FileSystem] | [UploadResult](file/file_uploader/result.md#file-uploader-result) | + +### Manipulate data + +To manipulate data you can use classes: + +| | Use case | Connection | `run()` gets | `run()` returns | +|-------------------------------------------------------|---------------------------------------------|----------------------------------------------------------------------------|--------------------------------------|-----------------------------------------------------------| +| [FileMover](file/file_mover/file_mover.md#file-mover) | Move files between directories in remote FS | Any [FileConnection](connection/file_connection/index.md#file-connections) | List[File path on remote FileSystem] | [MoveResult](file/file_mover/result.md#file-mover-result) | + +### Options + +Extract and load classes have a `options` parameter, which has a special meaning: + +> * all other parameters - *WHAT* we extract / *WHERE* we load to +> * `options` parameter - *HOW* we extract/load data +```python +db_reader = DBReader( + # WHAT do we read: + connection=mssql, + source="dbo.demo_table", # some table from MSSQL + columns=["column_1", "column_2"], # but only specific set of columns + where="column_2 > 1000", # only rows matching the clause + # HOW do we read: + options=MSSQL.ReadOptions( + numPartitions=10, # read in 10 parallel jobs + partitionColumn="id", # balance data read by assigning each job a part of data using `hash(id) mod N` expression + partitioningMode="hash", + fetchsize=1000, # each job will fetch block of 1000 rows each on every read attempt + ), +) + +db_writer = DBWriter( + # WHERE do we write to - to some table in Hive + connection=hive, + target="dl_sb.demo_table", + # HOW do we write - overwrite all the data in the existing table + options=Hive.WriteOptions(if_exists="replace_entire_table"), +) + +file_downloader = FileDownloader( + # WHAT do we download - files from some dir in SFTP + connection=sftp, + source_path="/source", + filters=[Glob("*.csv")], # only CSV files + limits=[MaxFilesCount(1000)], # 1000 files max + # WHERE do we download to - a specific dir on local FS + local_path="/some", + # HOW do we download: + options=FileDownloader.Options( + delete_source=True, # after downloading each file remove it from source_path + if_exists="replace_file", # replace existing files in the local_path + ), +) + +file_uploader = FileUploader( + # WHAT do we upload - files from some local dir + local_path="/source", + # WHERE do we upload to- specific remote dir in HDFS + connection=hdfs, + target_path="/some", + # HOW do we upload: + options=FileUploader.Options( + delete_local=True, # after uploading each file remove it from local_path + if_exists="replace_file", # replace existing files in the target_path + ), +) + +file_mover = FileMover( + # WHAT do we move - files in some remote dir in HDFS + source_path="/source", + connection=hdfs, + # WHERE do we move files to + target_path="/some", # a specific remote dir within the same HDFS connection + # HOW do we load - replace existing files in the target_path + options=FileMover.Options(if_exists="replace_file"), +) + +file_df_reader = FileDFReader( + # WHAT do we read - *.csv files from some dir in S3 + connection=s3, + source_path="/source", + file_format=CSV(), + # HOW do we read - load files from /source/*.csv, not from /source/nested/*.csv + options=FileDFReader.Options(recursive=False), +) + +file_df_writer = FileDFWriter( + # WHERE do we write to - as .csv files in some dir in S3 + connection=s3, + target_path="/target", + file_format=CSV(), + # HOW do we write - replace all existing files in /target, if exists + options=FileDFWriter.Options(if_exists="replace_entire_directory"), +) +``` + +More information about `options` could be found on [DB connection](connection/db_connection/index.md#db-connections). and +[File Downloader](file/file_downloader/file_downloader.md#file-downloader) / [File Uploader](file/file_uploader/file_uploader.md#file-uploader) / [File Mover](file/file_mover/file_mover.md#file-mover) / [FileDF Reader](file_df/file_df_reader/file_df_reader.md#file-df-reader) / [FileDF Writer](file_df/file_df_writer/file_df_writer.md#file-df-writer) documentation + +### Read Strategies + +onETL have several builtin strategies for reading data: + +1. [Snapshot strategy](strategy/snapshot_strategy.html) (default strategy) +2. [Incremental strategy](strategy/incremental_strategy.html) +3. [Snapshot batch strategy](strategy/snapshot_batch_strategy.html) +4. [Incremental batch strategy](strategy/incremental_batch_strategy.html) + +For example, an incremental strategy allows you to get only new data from the table: + +```python +from onetl.strategy import IncrementalStrategy + +reader = DBReader( + connection=mssql, + source="dbo.demo_table", + hwm_column="id", # detect new data based on value of "id" column +) + +# first run +with IncrementalStrategy(): + df = reader.run() + +sleep(3600) + +# second run +with IncrementalStrategy(): + # only rows, that appeared in the source since previous run + df = reader.run() +``` + +or get only files which were not downloaded before: + +```python +from onetl.strategy import IncrementalStrategy + +file_downloader = FileDownloader( + connection=sftp, + source_path="/remote", + local_path="/local", + hwm_type="file_list", # save all downloaded files to a list, and exclude files already present in this list +) + +# first run +with IncrementalStrategy(): + files = file_downloader.run() + +sleep(3600) + +# second run +with IncrementalStrategy(): + # only files, that appeared in the source since previous run + files = file_downloader.run() +``` + +Most of strategies are based on [HWM](hwm_store/index.md#hwm), Please check each strategy documentation for more details + +### Why just not use Connection class for extract/load? + +Connections are very simple, they have only a set of some basic operations, +like `mkdir`, `remove_file`, `get_table_schema`, and so on. + +High-level operations, like +: * [Read Strategies](strategy/index.md#strategy) support + * Handling metadata push/pull + * Handling different options, like `if_exists="replace_file"` in case of file download/upload + +is moved to a separate class which calls the connection object methods to perform some complex logic. diff --git a/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/connection.md b/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/connection.md new file mode 100644 index 000000000..8a13aad02 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/connection.md @@ -0,0 +1,3 @@ +
+ +# Clickhouse connection diff --git a/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/execute.md b/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/execute.md new file mode 100644 index 000000000..c766d6d8c --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/execute.md @@ -0,0 +1,98 @@ + + +# Executing statements in Clickhouse + +#### WARNING +Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + +Do **NOT** use them to read large amounts of data. Use [DBReader](read.md#clickhouse-read) or [Clickhouse.sql](sql.md#clickhouse-sql) instead. + +## How to + +There are 2 ways to execute some statement in Clickhouse + +### Use `Clickhouse.fetch` + +Use this method to perform some `SELECT` query which returns **small number or rows**, like reading +Clickhouse config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts `Clickhouse.FetchOptions`. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### WARNING +Please take into account [Clickhouse <-> Spark type mapping](types.md#clickhouse-types). + +#### Syntax support + +This method supports **any** query syntax supported by Clickhouse, like: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ✅︎ `SELECT func(arg1, arg2)` - call function +* ✅︎ `SHOW ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Clickhouse + +clickhouse = Clickhouse(...) + +df = clickhouse.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=Clickhouse.FetchOptions(queryTimeout=10), +) +clickhouse.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `Clickhouse.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts `Clickhouse.ExecuteOptions`. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Clickhouse, like: + +* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +* ✅︎ `ALTER ...` +* ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +* ✅︎ other statements not mentioned here +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Clickhouse + +clickhouse = Clickhouse(...) + +clickhouse.execute("DROP TABLE schema.table") +clickhouse.execute( + """ + CREATE TABLE schema.table ( + id UInt8, + key String, + value Float32 + ) + ENGINE = MergeTree() + ORDER BY id + """, + options=Clickhouse.ExecuteOptions(queryTimeout=10), +) +``` + +## Notes + +These methods **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + +So it should **NOT** be used to read large amounts of data. Use [DBReader](read.md#clickhouse-read) or [Clickhouse.sql](sql.md#clickhouse-sql) instead. + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/index.md b/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/index.md new file mode 100644 index 000000000..3b226910d --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/index.md @@ -0,0 +1,19 @@ + + +# Clickhouse + +# Connection + +* [Prerequisites](prerequisites.md) +* [Clickhouse connection](connection.md) + +# Operations + +* [Reading from Clickhouse using `DBReader`](read.md) +* [Reading from Clickhouse using `Clickhouse.sql`](sql.md) +* [Writing to Clickhouse using `DBWriter`](write.md) +* [Executing statements in Clickhouse](execute.md) + +# Troubleshooting + +* [Clickhouse <-> Spark type mapping](types.md) diff --git a/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/prerequisites.md b/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/prerequisites.md new file mode 100644 index 000000000..86551b41a --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/prerequisites.md @@ -0,0 +1,73 @@ + + +# Prerequisites + +## Version Compatibility + +* Clickhouse server versions: + : * Officially declared: 22.8 or higher + * Actually tested: 21.1, 25.1 +* Spark versions: 2.3.x - 3.5.x +* Java versions: 8 - 20 + +See [official documentation](https://clickhouse.com/docs/en/integrations/java#jdbc-driver). + +## Installing PySpark + +To use Clickhouse connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Connecting to Clickhouse + +### Connection port + +Connector can only use **HTTP** (usually `8123` port) or **HTTPS** (usually `8443` port) protocol. + +TCP and GRPC protocols are NOT supported. + +### Connecting to cluster + +It is possible to connect to Clickhouse cluster, and use it’s load balancing capabilities to read or write data in parallel. +Each Spark executor can connect to random Clickhouse nodes, instead of sending all the data to a node specified in connection params. + +This requires all Clickhouse servers to run on different hosts, and **listen the same HTTP port**. +Set `auto_discovery=True` to enable this feature (disabled by default): + +```python +Clickhouse( + host="node1.of.cluster", + port=8123, + extra={ + "auto_discovery": True, + "load_balancing_policy": "roundRobin", + }, +) +``` + +See [official documentation](https://clickhouse.com/docs/en/integrations/java#configuring-node-discovery-load-balancing-and-failover). + +### Required grants + +Ask your Clickhouse cluster administrator to set following grants for a user, +used for creating a connection: + +Read + Write + +```sql +-- allow creating tables in the target schema +GRANT CREATE TABLE ON myschema.* TO username; + +-- allow read & write access to specific table +GRANT SELECT, INSERT ON myschema.mytable TO username; +``` + +Read only + +```sql +-- allow read access to specific table +GRANT SELECT ON myschema.mytable TO username; +``` + +More details can be found in [official documentation](https://clickhouse.com/docs/en/sql-reference/statements/grant). diff --git a/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/read.md b/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/read.md new file mode 100644 index 000000000..0f0492035 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/read.md @@ -0,0 +1,78 @@ + + +# Reading from Clickhouse using `DBReader` + +`DBReader` supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, +but does not support custom queries, like `JOIN`. + +#### WARNING +Please take into account [Clickhouse <-> Spark type mapping](types.md#clickhouse-types) + +## Supported DBReader features + +* ✅︎ `columns` +* ✅︎ `where` +* ✅︎ `hwm`, supported strategies: +* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) +* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) +* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) +* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) +* ❌ `hint` (is not supported by Clickhouse) +* ❌ `df_schema` +* ✅︎ `options` (see `Clickhouse.ReadOptions`) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Clickhouse +from onetl.db import DBReader + +clickhouse = Clickhouse(...) + +reader = DBReader( + connection=clickhouse, + source="schema.table", + columns=["id", "key", "CAST(value AS String) value", "updated_dt"], + where="key = 'something'", + options=Clickhouse.ReadOptions(partitionColumn="id", numPartitions=10), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Clickhouse +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +clickhouse = Clickhouse(...) + +reader = DBReader( + connection=clickhouse, + source="schema.table", + columns=["id", "key", "CAST(value AS String) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="clickhouse_hwm", expression="updated_dt"), + options=Clickhouse.ReadOptions(partitionColumn="id", numPartitions=10), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Clickhouse to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Clickhouse to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/sql.md b/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/sql.md new file mode 100644 index 000000000..98c17143e --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/sql.md @@ -0,0 +1,62 @@ + + +# Reading from Clickhouse using `Clickhouse.sql` + +`Clickhouse.sql` allows passing custom SQL query, but does not support incremental strategies. + +#### WARNING +Please take into account [Clickhouse <-> Spark type mapping](types.md#clickhouse-types) + +#### WARNING +Statement is executed in **read-write** connection, so if you’re calling some functions/procedures with DDL/DML statements inside, +they can change data in your database. + +## Syntax support + +Only queries with the following syntax are supported: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import Clickhouse + +clickhouse = Clickhouse(...) +df = clickhouse.sql( + """ + SELECT + id, + key, + CAST(value AS String) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """, + options=Clickhouse.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from Clickhouse to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from Clickhouse to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/types.md b/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/types.md new file mode 100644 index 000000000..d75e28142 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/types.md @@ -0,0 +1,347 @@ + + +# Clickhouse <-> Spark type mapping + +#### NOTE +The results below are valid for Spark 3.5.5, and may differ on other Spark versions. + +#### NOTE +It is recommended to use [spark-dialect-extension](https://github.com/MobileTeleSystems/spark-dialect-extension) package, +which implements writing Arrays from Spark to Clickhouse, fixes dropping fractions of seconds in `TimestampType`, +and fixes other type conversion issues. + +## Type detection & casting + +Spark’s DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from Clickhouse + +This is how Clickhouse connector performs this: + +* For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and Clickhouse type. +* Find corresponding `Clickhouse type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* Create DataFrame from query with specific column names and Spark types. + +### Writing to some existing Clickhouse table + +This is how Clickhouse connector performs this: + +* Get names of columns in DataFrame. [1](#id3) +* Perform `SELECT * FROM table LIMIT 0` query. +* Take only columns present in DataFrame (by name, case insensitive). For each found column get Clickhouse type. +* **Find corresponding** `Clickhouse type (read)` → `Spark type` **combination** (see below) for each DataFrame column. If no combination is found, raise exception. [2](#id4) +* Find corresponding `Spark type` → `Clickhousetype (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* If `Clickhousetype (write)` match `Clickhouse type (read)`, no additional casts will be performed, DataFrame column will be written to Clickhouse as is. +* If `Clickhousetype (write)` does not match `Clickhouse type (read)`, DataFrame column will be casted to target column type **on Clickhouse side**. For example, you can write column with text data to `Int32` column, if column contains valid integer values within supported value range and precision. + +* **[1]** This allows to write data to tables with `DEFAULT` columns - if DataFrame has no such column, it will be populated by Clickhouse. +* **[2]** Yes, this is weird. + +### Create new table using Spark + +#### WARNING +ABSOLUTELY NOT RECOMMENDED! + +This is how Clickhouse connector performs this: + +* Find corresponding `Spark type` → `Clickhouse type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* Generate DDL for creating table in Clickhouse, like `CREATE TABLE (col1 ...)`, and run it. +* Write DataFrame to created table as is. + +But Spark does not have specific dialect for Clickhouse, so Generic JDBC dialect is used. +Generic dialect is using SQL ANSI type names while creating tables in target database, not database-specific types. + +If some cases this may lead to using wrong column type. For example, Spark creates column of type `TIMESTAMP` +which corresponds to Clickhouse type `DateTime32` (precision up to seconds) +instead of more precise `DateTime64` (precision up to nanoseconds). +This may lead to incidental precision loss, or sometimes data cannot be written to created table at all. + +So instead of relying on Spark to create tables: + +### See example + +```python +writer = DBWriter( + connection=clickhouse, + target="default.target_tbl", + options=Clickhouse.WriteOptions( + if_exists="append", + # ENGINE is required by Clickhouse + createTableOptions="ENGINE = MergeTree() ORDER BY id", + ), +) +writer.run(df) +``` + +Always prefer creating tables with specific types **BEFORE WRITING DATA**: + +### See example + +```python +clickhouse.execute( + """ + CREATE TABLE default.target_tbl ( + id UInt8, + value DateTime64(6) -- specific type and precision + ) + ENGINE = MergeTree() + ORDER BY id + """, +) + +writer = DBWriter( + connection=clickhouse, + target="default.target_tbl", + options=Clickhouse.WriteOptions(if_exists="append"), +) +writer.run(df) +``` + +### References + +Here you can find source code with type conversions: + +* [Clickhouse -> JDBC](https://github.com/ClickHouse/clickhouse-java/blob/0.3.2/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/JdbcTypeMapping.java#L39-L176) +* [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/jdbc/JdbcUtils.scala#L307) +* [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/jdbc/JdbcUtils.scala#L141-L164) +* [JDBC -> Clickhouse](https://github.com/ClickHouse/clickhouse-java/blob/0.3.2/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/JdbcTypeMapping.java#L185-L311) + +## Supported types + +See [official documentation](https://clickhouse.com/docs/en/sql-reference/data-types) + +### Generic types + +* `LowCardinality(T)` is same as `T` +* `Nullable(T)` is same as `T`, but Spark column is inferred as `nullable=True` + +### Numeric types + +| Clickhouse type (read) | Spark type | Clickhouse type (write) | Clickhouse type (create) | +|------------------------------|----------------------------------|-----------------------------|-----------------------------| +| `Bool` | `BooleanType()` | `Bool` | `UInt64` | +| `Decimal` | `DecimalType(P=10, S=0)` | `Decimal(P=10, S=0)` | `Decimal(P=10, S=0)` | +| `Decimal(P=0..38)` | `DecimalType(P=0..38, S=0)` | `Decimal(P=0..38, S=0)` | `Decimal(P=0..38, S=0)` | +| `Decimal(P=0..38, S=0..38)` | `DecimalType(P=0..38, S=0..38)` | `Decimal(P=0..38, S=0..38)` | `Decimal(P=0..38, S=0..38)` | +| `Decimal(P=39..76, S=0..76)` | unsupported [3](#id9) | | | +| `Decimal32(P=0..9)` | `DecimalType(P=9, S=0..9)` | `Decimal(P=9, S=0..9)` | `Decimal(P=9, S=0..9)` | +| `Decimal64(S=0..18)` | `DecimalType(P=18, S=0..18)` | `Decimal(P=18, S=0..18)` | `Decimal(P=18, S=0..18)` | +| `Decimal128(S=0..38)` | `DecimalType(P=38, S=0..38)` | `Decimal(P=38, S=0..38)` | `Decimal(P=38, S=0..38)` | +| `Decimal256(S=0..76)` | unsupported [3](#id9) | | | +| `Float32` | `FloatType()` | `Float32` | `Float32` | +| `Float64` | `DoubleType()` | `Float64` | `Float64` | +| `Int8` | `IntegerType()` | `Int32` | `Int32` | +| `Int16` | | | | +| `Int32` | | | | +| `Int64` | `LongType()` | `Int64` | `Int64` | +| `Int128` | unsupported [3](#id9) | | | +| `Int256` | | | | +| `-` | `ByteType()` | `Int8` | `Int8` | +| `-` | `ShortType()` | `Int32` | `Int32` | +| `UInt8` | `IntegerType()` | `Int32` | `Int32` | +| `UInt16` | `LongType()` | `Int64` | `Int64` | +| `UInt32` | `DecimalType(20,0)` | `Decimal(20,0)` | `Decimal(20,0)` | +| `UInt64` | | | | +| `UInt128` | unsupported [3](#id9) | | | +| `UInt256` | | | | +* **[3]** Clickhouse support numeric types up to 256 bit - `Int256`, `UInt256`, `Decimal256(S)`, `Decimal(P=39..76, S=0..76)`. But Spark’s `DecimalType(P, S)` supports maximum `P=38` (128 bit). It is impossible to read, write or operate with values of larger precision, this leads to an exception. + +### Temporal types + +Notes: +: * Datetime with timezone has the same precision as without timezone + * `DateTime` is alias for `DateTime32` + * `TIMESTAMP` is alias for `DateTime32`, but `TIMESTAMP(N)` is alias for `DateTime64(N)` + +| Clickhouse type (read) | Spark type | Clickhouse type (write) | Clickhouse type (create) | +|---------------------------------|-------------------------------------------------------------------------------|-------------------------------|---------------------------------------------------------------------| +| `Date` | `DateType()` | `Date` | `Date` | +| `Date32` | `DateType()` | `Date` | `Date`,
**cannot insert data** [4](#id15) | +| `DateTime32`, seconds | `TimestampType()`, microseconds | `DateTime64(6)`, microseconds | `DateTime32`, seconds | +| `DateTime64(3)`, milliseconds | `TimestampType()`, microseconds | `DateTime64(6)`, microseconds | `DateTime32`, seconds,
**precision loss** [5](#id16) | +| `DateTime64(6)`, microseconds | `TimestampType()`, microseconds | | `DateTime32`, seconds,
**precision loss** [7](#id18) | +| `DateTime64(7..9)`, nanoseconds | `TimestampType()`, microseconds,
**precision loss** [6](#id17) | | | +| `-` | `TimestampNTZType()`, microseconds | | | +| `DateTime32(TZ)` | unsupported [7](#id18) | | | +| `DateTime64(P, TZ)` | | | | +| `IntervalNanosecond` | `LongType()` | `Int64` | `Int64` | +| `IntervalMicrosecond` | | | | +| `IntervalMillisecond` | | | | +| `IntervalSecond` | | | | +| `IntervalMinute` | | | | +| `IntervalHour` | | | | +| `IntervalDay` | | | | +| `IntervalMonth` | | | | +| `IntervalQuarter` | | | | +| `IntervalWeek` | | | | +| `IntervalYear` | | | | + +#### WARNING +Note that types in Clickhouse and Spark have different value ranges: + +| Clickhouse type | Min value | Max value | Spark type | Min value | Max value | +|----------------------|---------------------------------|---------------------------------|-------------------|------------------------------|------------------------------| +| `Date` | `1970-01-01` | `2149-06-06` | `DateType()` | `0001-01-01` | `9999-12-31` | +| `DateTime32` | `1970-01-01 00:00:00` | `2106-02-07 06:28:15` | `TimestampType()` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | +| `DateTime64(P=0..8)` | `1900-01-01 00:00:00.00000000` | `2299-12-31 23:59:59.99999999` | | | | +| `DateTime64(P=9)` | `1900-01-01 00:00:00.000000000` | `2262-04-11 23:47:16.999999999` | | | | + +So not all of values in Spark DataFrame can be written to Clickhouse. + +References: +: * [Clickhouse Date documentation](https://clickhouse.com/docs/en/sql-reference/data-types/date) + * [Clickhouse Datetime32 documentation](https://clickhouse.com/docs/en/sql-reference/data-types/datetime) + * [Clickhouse Datetime64 documentation](https://clickhouse.com/docs/en/sql-reference/data-types/datetime64) + * [Spark DateType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/DateType.html) + * [Spark TimestampType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/TimestampType.html) + +* **[4]** `Date32` has different bytes representation than `Date`, and inserting value of type `Date32` to `Date` column leads to errors on Clickhouse side, e.g. `Date(106617) should be between 0 and 65535 inclusive of both values`. Although Spark does properly read the `Date32` column as `DateType()`, and there should be no difference at all. Probably this is some bug in Clickhouse driver. +* **[5]** Generic JDBC dialect generates DDL with Clickhouse type `TIMESTAMP` which is alias for `DateTime32` with precision up to seconds (`23:59:59`). Inserting data with milliseconds precision (`23:59:59.999`) will lead to **throwing away milliseconds**. Solution: create table manually, with proper column type. +* **[6]** Clickhouse support datetime up to nanoseconds precision (`23:59:59.999999999`), but Spark `TimestampType()` supports datetime up to microseconds precision (`23:59:59.999999`). Nanoseconds will be lost during read or write operations. Solution: create table manually, with proper column type. +* **[7]** Clickhouse will raise an exception that data in format `2001-01-01 23:59:59.999999` has data `.999999` which does not match format `YYYY-MM-DD hh:mm:ss` of `DateTime32` column type (see [5](#id16)). So Spark can create Clickhouse table, but cannot write data to column of this type. Solution: create table manually, with proper column type. + +### String types + +| Clickhouse type (read) | Spark type | Clickhousetype (write) | Clickhouse type (create) | +|--------------------------|----------------|--------------------------|----------------------------| +| `FixedString(N)` | `StringType()` | `String` | `String` | +| `String` | | | | +| `Enum8` | | | | +| `Enum16` | | | | +| `IPv4` | | | | +| `IPv6` | | | | +| `UUID` | | | | +| `-` | `BinaryType()` | | | + +## Unsupported types + +Columns of these Clickhouse types cannot be read by Spark: +: * `AggregateFunction(func, T)` + * `Array(T)` + * `JSON` + * `Map(K, V)` + * `MultiPolygon` + * `Nested(field1 T1, ...)` + * `Nothing` + * `Point` + * `Polygon` + * `Ring` + * `SimpleAggregateFunction(func, T)` + * `Tuple(T1, T2, ...)` + +Dataframe with these Spark types cannot be written to Clickhouse: +: * `ArrayType(T)` + * `BinaryType()` + * `CharType(N)` + * `DayTimeIntervalType(P, S)` + * `MapType(K, V)` + * `NullType()` + * `StructType([...])` + * `TimestampNTZType()` + * `VarcharType(N)` + +This is because Spark does not have dedicated Clickhouse dialect, and uses Generic JDBC dialect instead. +This dialect does not have type conversion between some types, like Clickhouse `Array` -> Spark `ArrayType()`, and vice versa. + +The is a way to avoid this - just cast everything to `String`. + +## Explicit type cast + +### `DBReader` + +Use `CAST` or `toJSONString` to get column data as string in JSON format, + +For parsing JSON columns in ClickHouse, `JSON.parse_column` method. + +```python +from pyspark.sql.types import ArrayType, IntegerType + +from onetl.file.format import JSON +from onetl.connection import ClickHouse +from onetl.db import DBReader + +reader = DBReader( + connection=clickhouse, + target="default.source_tbl", + columns=[ + "id", + "toJSONString(array_column) array_column", + ], +) +df = reader.run() + +# Spark requires all columns to have some specific type, describe it +column_type = ArrayType(IntegerType()) + +json = JSON() +df = df.select( + df.id, + json.parse_column("array_column", column_type), +) +``` + +### `DBWriter` + +For writing JSON data to ClickHouse, use the `JSON.serialize_column` method to convert a DataFrame column to JSON format efficiently and write it as a `String` column in Clickhouse. + +```python +from onetl.file.format import JSON +from onetl.connection import ClickHouse +from onetl.db import DBWriter + +clickhouse = ClickHouse(...) + +clickhouse.execute( + """ + CREATE TABLE default.target_tbl ( + id Int32, + array_column_json String, + ) + ENGINE = MergeTree() + ORDER BY id + """, +) + +json = JSON() +df = df.select( + df.id, + json.serialize_column(df.array_column).alias("array_column_json"), +) + +writer.run(df) +``` + +Then you can parse this column on Clickhouse side - for example, by creating a view: + +```sql +SELECT + id, + JSONExtract(json_column, 'Array(String)') AS array_column +FROM target_tbl +``` + +You can also use [ALIAS](https://clickhouse.com/docs/en/sql-reference/statements/create/table#alias) +or [MATERIALIZED](https://clickhouse.com/docs/en/sql-reference/statements/create/table#materialized) columns +to avoid writing such expression in every `SELECT` clause all the time: + +```sql +CREATE TABLE default.target_tbl ( + id Int32, + array_column_json String, + -- computed column + array_column Array(String) ALIAS JSONExtract(json_column, 'Array(String)') + -- or materialized column + -- array_column Array(String) MATERIALIZED JSONExtract(json_column, 'Array(String)') +) +ENGINE = MergeTree() +ORDER BY id +``` + +Downsides: + +* Using `SELECT JSONExtract(...)` or `ALIAS` column can be expensive, because value is calculated on every row access. This can be especially harmful if such column is used in `WHERE` clause. +* `ALIAS` and `MATERIALIZED` columns are not included in `SELECT *` clause, they should be added explicitly: `SELECT *, calculated_column FROM table`. + +#### WARNING +[EPHEMERAL](https://clickhouse.com/docs/en/sql-reference/statements/create/table#ephemeral) columns are not supported by Spark +because they cannot be selected to determine target column type. diff --git a/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/write.md b/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/write.md new file mode 100644 index 000000000..28d5f4676 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/clickhouse/write.md @@ -0,0 +1,42 @@ + + +# Writing to Clickhouse using `DBWriter` + +For writing data to Clickhouse, use `DBWriter`. + +#### WARNING +Please take into account [Clickhouse <-> Spark type mapping](types.md#clickhouse-types) + +#### WARNING +It is always recommended to create table explicitly using [Clickhouse.execute](execute.md#clickhouse-execute) +instead of relying on Spark’s table DDL generation. + +This is because Spark’s DDL generator can create columns with different precision and types than it is expected, +causing precision loss or other issues. + +## Examples + +```python +from onetl.connection import Clickhouse +from onetl.db import DBWriter + +clickhouse = Clickhouse(...) + +df = ... # data is here + +writer = DBWriter( + connection=clickhouse, + target="schema.table", + options=Clickhouse.WriteOptions( + if_exists="append", + # ENGINE is required by Clickhouse + createTableOptions="ENGINE = MergeTree() ORDER BY id", + ), +) + +writer.run(df) +``` + +## Options + +Method above accepts `Clickhouse.WriteOptions` diff --git a/mddocs/docs/_build/markdown/connection/db_connection/greenplum/connection.md b/mddocs/docs/_build/markdown/connection/db_connection/greenplum/connection.md new file mode 100644 index 000000000..8f3d32d76 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/greenplum/connection.md @@ -0,0 +1,3 @@ + + +# Greenplum connection diff --git a/mddocs/docs/_build/markdown/connection/db_connection/greenplum/execute.md b/mddocs/docs/_build/markdown/connection/db_connection/greenplum/execute.md new file mode 100644 index 000000000..715addad7 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/greenplum/execute.md @@ -0,0 +1,102 @@ + + +# Executing statements in Greenplum + +#### WARNING +Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + +Do **NOT** use them to read large amounts of data. Use [DBReader](read.md#greenplum-read) instead. + +## How to + +There are 2 ways to execute some statement in Greenplum + +### Use `Greenplum.fetch` + +Use this method to perform some `SELECT` query which returns **small number or rows**, like reading +Greenplum config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts `Greenplum.FetchOptions`. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### WARNING +`Greenplum.fetch` is implemented using Postgres JDBC connection, +so types are handled a bit differently than in `DBReader`. See [Postgres <-> Spark type mapping](../postgres/types.md#postgres-types). + +#### Syntax support + +This method supports **any** query syntax supported by Greenplum, like: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ✅︎ `SELECT func(arg1, arg2)` or `{call func(arg1, arg2)}` - special syntax for calling functions +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Greenplum + +greenplum = Greenplum(...) + +df = greenplum.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=Greenplum.FetchOptions(queryTimeout=10), +) +greenplum.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `Greenplum.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts `Greenplum.ExecuteOptions`. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Greenplum, like: + +* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +* ✅︎ `ALTER ...` +* ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +* ✅︎ `CALL procedure(arg1, arg2) ...` +* ✅︎ `SELECT func(arg1, arg2)` or `{call func(arg1, arg2)}` - special syntax for calling functions +* ✅︎ other statements not mentioned here +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Greenplum + +greenplum = Greenplum(...) + +greenplum.execute("DROP TABLE schema.table") +greenplum.execute( + """ + CREATE TABLE schema.table ( + id int, + key text, + value real + ) + DISTRIBUTED BY id + """, + options=Greenplum.ExecuteOptions(queryTimeout=10), +) +``` + +## Interaction schema + +Unlike reading & writing, executing statements in Greenplum is done **only** through Greenplum master node, +without any interaction between Greenplum segments and Spark executors. More than that, Spark executors are not used in this case. + +The only port used while interacting with Greenplum in this case is `5432` (Greenplum master port). + +### Spark <-> Greenplum interaction during Greenplum.execute()/Greenplum.fetch() + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/greenplum/index.md b/mddocs/docs/_build/markdown/connection/db_connection/greenplum/index.md new file mode 100644 index 000000000..9fcc68f66 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/greenplum/index.md @@ -0,0 +1,18 @@ + + +# Greenplum + +# Connection + +* [Prerequisites](prerequisites.md) +* [Greenplum connection](connection.md) + +# Operations + +* [Reading from Greenplum using `DBReader`](read.md) +* [Writing to Greenplum using `DBWriter`](write.md) +* [Executing statements in Greenplum](execute.md) + +# Troubleshooting + +* [Greenplum <-> Spark type mapping](types.md) diff --git a/mddocs/docs/_build/markdown/connection/db_connection/greenplum/prerequisites.md b/mddocs/docs/_build/markdown/connection/db_connection/greenplum/prerequisites.md new file mode 100644 index 000000000..6c0b92846 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/greenplum/prerequisites.md @@ -0,0 +1,358 @@ + + +# Prerequisites + +## Version Compatibility + +* Greenplum server versions: + : * Officially declared: 5.x, 6.x, and 7.x (which requires `Greenplum.get_packages(package_version="2.3.0")` or higher) + * Actually tested: 6.23, 7.0 +* Spark versions: 2.3.x - 3.2.x (Spark 3.3+ is not supported yet) +* Java versions: 8 - 11 + +See [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.2/greenplum-connector-spark/release_notes.html). + +## Installing PySpark + +To use Greenplum connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Downloading VMware package + +To use Greenplum connector you should download connector `.jar` file from +[VMware website](https://network.tanzu.vmware.com/products/vmware-greenplum#/releases/1413479/file_groups/16966) +and then pass it to Spark session. + +#### WARNING +Please pay attention to [Spark & Scala version compatibility](../../../install/spark.md#spark-compatibility-matrix). + +#### WARNING +There are issues with using package of version 2.3.0/2.3.1 with Greenplum 6.x - connector can +open transaction with `SELECT * FROM table LIMIT 0` query, but does not close it, which leads to deadlocks +during write. + +There are several ways to do that. See [Injecting Java packages](../../../install/spark.md#java-packages) for details. + +#### NOTE +If you’re uploading package to private package repo, use `groupId=io.pivotal` and `artifactoryId=greenplum-spark_2.12` +(`2.12` is Scala version) to give uploaded package a proper name. + +## Connecting to Greenplum + +### Interaction schema + +Spark executors open ports to listen incoming requests. +Greenplum segments are initiating connections to Spark executors using [EXTERNAL TABLE](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-sql_commands-CREATE_EXTERNAL_TABLE.html) +functionality, and send/read data using [gpfdist protocol](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/admin_guide-external-g-using-the-greenplum-parallel-file-server--gpfdist-.html#about-gpfdist-setup-and-performance-1). + +Data is **not** send through Greenplum master. +Greenplum master only receives commands to start reading/writing process, and manages all the metadata (external table location, schema and so on). + +More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/overview.html). + +### Set number of connections + +#### WARNING +This is very important!!! + +If you don’t limit number of connections, you can exceed the [max_connections](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/admin_guide-client_auth.html#limiting-concurrent-connections#limiting-concurrent-connections-2) +limit set on the Greenplum side. It’s usually not so high, e.g. 500-1000 connections max, +depending on your Greenplum instance settings and using connection balancers like `pgbouncer`. + +Consuming all available connections means **nobody** (even admin users) can connect to Greenplum. + +Each job on the Spark executor makes its own connection to Greenplum master node, +so you need to limit number of connections to avoid opening too many of them. + +* Reading about `5-10Gb` of data requires about `3-5` parallel connections. +* Reading about `20-30Gb` of data requires about `5-10` parallel connections. +* Reading about `50Gb` of data requires ~ `10-20` parallel connections. +* Reading about `100+Gb` of data requires `20-30` parallel connections. +* Opening more than `30-50` connections is not recommended. + +Number of connections can be limited by 2 ways: + +* By limiting number of Spark executors and number of cores per-executor. Max number of parallel jobs is `executors * cores`. + +Spark with master=local + +```py +spark = ( + SparkSession.builder + # Spark will run with 5 threads in local mode, allowing up to 5 parallel tasks + .config("spark.master", "local[5]") + .config("spark.executor.cores", 1) +).getOrCreate() +``` + +Spark with master=yarn or master=k8s, dynamic allocation + +```py +spark = ( + SparkSession.builder + .config("spark.master", "yarn") + # Spark will start MAX 10 executors with 1 core each (dynamically), so max number of parallel jobs is 10 + .config("spark.dynamicAllocation.maxExecutors", 10) + .config("spark.executor.cores", 1) +).getOrCreate() +``` + +Spark with master=yarn or master=k8s, static allocation + +```py +spark = ( + SparkSession.builder + .config("spark.master", "yarn") + # Spark will start EXACTLY 10 executors with 1 core each, so max number of parallel jobs is 10 + .config("spark.executor.instances", 10) + .config("spark.executor.cores", 1) +).getOrCreate() +``` + +* By limiting connection pool size user by Spark (**only** for Spark with `master=local`): + +```python +spark = SparkSession.builder.config("spark.master", "local[*]").getOrCreate() + +# No matter how many executors are started and how many cores they have, +# number of connections cannot exceed pool size: +Greenplum( + ..., + extra={ + "pool.maxSize": 10, + }, +) +``` + +See [connection pooling](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/using_the_connector.html#jdbcconnpool) +documentation. + +* By setting `num_partitions` + and `partition_column` (not recommended). + +### Allowing connection to Greenplum master + +Ask your Greenplum cluster administrator to allow your user to connect to Greenplum master node, +e.g. by updating `pg_hba.conf` file. + +More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/admin_guide-client_auth.html#limiting-concurrent-connections#allowing-connections-to-greenplum-database-0). + +### Set connection port + +#### Spark with `master=k8s` + +Please follow [the official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/configure.html#k8scfg) + +#### Spark with `master=yarn` or `master=local` + +To read data from Greenplum using Spark, following ports should be opened in firewall between Spark and Greenplum: + +* Spark driver and all Spark executors -> port `5432` on Greenplum master node. + + This port number should be set while connecting to Greenplum: + ```python + greenplum = Greenplum(host="master.host", port=5432, ...) + ``` +* Greenplum segments -> some port range (e.g. `41000-42000`) **listened by Spark executors**. + + This range should be set in `extra` option: + ```python + greenplum = Greenplum( + ..., + extra={ + "server.port": "41000-42000", + }, + ) + ``` + + Number of ports in this range is `number of parallel running Spark sessions` \* `number of parallel connections per session`. + + Number of connections per session (see below) is usually less than `30` (see above). + + Number of session depends on your environment: + : * For `master=local` only few ones-tens sessions can be started on the same host, depends on available RAM and CPU. + * For `master=yarn` hundreds or thousands of sessions can be started simultaneously, + but they are executing on different cluster nodes, so one port can be opened on different nodes at the same time. + +More details can be found in official documentation: +: * [port requirements](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/sys_reqs.html#network-port-requirements) + * [format of server.port value](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#server.port) + * [port troubleshooting](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/troubleshooting.html#port-errors) + +### Set connection host + +#### Spark with `master=k8s` + +Please follow [the official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/configure.html#k8scfg) + +#### Spark with `master=local` + +By default, Greenplum connector tries to resolve IP of current host, and then pass it as `gpfdist` URL to Greenplum segment. +This may fail in some cases. + +For example, IP can be resolved using `/etc/hosts` content like this: + +```text +127.0.0.1 localhost real-host-name +``` + +```bash +$ hostname -f +localhost + +$ hostname -i +127.0.0.1 +``` + +Reading/writing data to Greenplum will fail with following exception: + +```text +org.postgresql.util.PSQLException: ERROR: connection with gpfdist failed for +"gpfdist://127.0.0.1:49152/local-1709739764667/exec/driver", +effective url: "http://127.0.0.1:49152/local-1709739764667/exec/driver": +error code = 111 (Connection refused); (seg3 slice1 12.34.56.78:10003 pid=123456) +``` + +There are 2 ways to fix that: + +* Explicitly pass your host IP address to connector, like this + ```python + import os + + # pass here real host IP (accessible from GP segments) + os.environ["HOST_IP"] = "192.168.1.1" + + greenplum = Greenplum( + ..., + extra={ + # connector will read IP from this environment variable + "server.hostEnv": "env.HOST_IP", + }, + spark=spark, + ) + ``` + + More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#server.hostenv). +* Update `/etc/hosts` file to include real host IP: + ```text + 127.0.0.1 localhost + # this IP should be accessible from GP segments + 192.168.1.1 driver-host-name + ``` + + So Greenplum connector will properly resolve host IP. + +#### Spark with `master=yarn` + +The same issue with resolving IP address can occur on Hadoop cluster node, but it’s tricky to fix, because each node has a different IP. + +There are 3 ways to fix that: + +* Pass node hostname to `gpfdist` URL. So IP will be resolved on segment side: + ```python + greenplum = Greenplum( + ..., + extra={ + "server.useHostname": "true", + }, + ) + ``` + + But this may fail if Hadoop cluster node hostname cannot be resolved from Greenplum segment side. + + More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#server.usehostname). +* Set specific network interface to get IP address from: + ```python + greenplum = Greenplum( + ..., + extra={ + "server.nic": "eth0", + }, + ) + ``` + + You can get list of network interfaces using this command. + + #### NOTE + This command should be executed on Hadoop cluster node, **not** Spark driver host! + + ```bash + $ ip address + 1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 + inet 127.0.0.1/8 scope host lo + valid_lft forever preferred_lft forever + 2: eth0: mtu 1500 qdisc fq_codel state UP group default qlen 1000 + inet 192.168.1.1/24 brd 192.168.1.255 scope global dynamic noprefixroute eth0 + valid_lft 83457sec preferred_lft 83457sec + ``` + + Note that in this case **each** Hadoop cluster node node should have network interface with name `eth0`. + + More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#server.nic). +* Update `/etc/hosts` on each Hadoop cluster node to include real node IP: + ```text + 127.0.0.1 localhost + # this IP should be accessible from GP segments + 192.168.1.1 cluster-node-name + ``` + + So Greenplum connector will properly resolve node IP. + +### Set required grants + +Ask your Greenplum cluster administrator to set following grants for a user, +used for creating a connection: + +Read + Write + +```sql +-- get access to get tables metadata & cluster information +GRANT SELECT ON information_schema.tables TO username; +GRANT SELECT ON pg_attribute TO username; +GRANT SELECT ON pg_class TO username; +GRANT SELECT ON pg_namespace TO username; +GRANT SELECT ON pg_settings TO username; +GRANT SELECT ON pg_stats TO username; +GRANT SELECT ON gp_distributed_xacts TO username; +GRANT SELECT ON gp_segment_configuration TO username; +-- Greenplum 5.x only +GRANT SELECT ON gp_distribution_policy TO username; + +-- allow creating external tables in the same schema as source/target table +GRANT USAGE ON SCHEMA myschema TO username; +GRANT CREATE ON SCHEMA myschema TO username; +ALTER USER username CREATEEXTTABLE(type = 'readable', protocol = 'gpfdist') CREATEEXTTABLE(type = 'writable', protocol = 'gpfdist'); + +-- allow read access to specific table (to get column types) +-- allow write access to specific table +GRANT SELECT, INSERT ON myschema.mytable TO username; +``` + +Read only + +```sql +-- get access to get tables metadata & cluster information +GRANT SELECT ON information_schema.tables TO username; +GRANT SELECT ON pg_attribute TO username; +GRANT SELECT ON pg_class TO username; +GRANT SELECT ON pg_namespace TO username; +GRANT SELECT ON pg_settings TO username; +GRANT SELECT ON pg_stats TO username; +GRANT SELECT ON gp_distributed_xacts TO username; +GRANT SELECT ON gp_segment_configuration TO username; +-- Greenplum 5.x only +GRANT SELECT ON gp_distribution_policy TO username; + +-- allow creating external tables in the same schema as source table +GRANT USAGE ON SCHEMA schema_to_read TO username; +GRANT CREATE ON SCHEMA schema_to_read TO username; +-- yes, ``writable`` for reading from GP, because data is written from Greenplum to Spark executor. +ALTER USER username CREATEEXTTABLE(type = 'writable', protocol = 'gpfdist'); + +-- allow read access to specific table +GRANT SELECT ON schema_to_read.table_to_read TO username; +``` + +More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/install_cfg.html#role-privileges). diff --git a/mddocs/docs/_build/markdown/connection/db_connection/greenplum/read.md b/mddocs/docs/_build/markdown/connection/db_connection/greenplum/read.md new file mode 100644 index 000000000..64af11703 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/greenplum/read.md @@ -0,0 +1,285 @@ + + +# Reading from Greenplum using `DBReader` + +Data can be read from Greenplum to Spark using `DBReader`. +It also supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading. + +#### WARNING +Please take into account [Greenplum <-> Spark type mapping](types.md#greenplum-types). + +#### NOTE +Unlike JDBC connectors, *Greenplum connector for Spark* does not support +executing **custom** SQL queries using `.sql` method. Connector can be used to only read data from a table or view. + +## Supported DBReader features + +* ✅︎ `columns` (see note below) +* ✅︎ `where` (see note below) +* ✅︎ `hwm` (see note below), supported strategies: +* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) +* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) +* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) +* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) +* ❌ `hint` (is not supported by Greenplum) +* ❌ `df_schema` +* ✅︎ `options` (see `Greenplum.ReadOptions`) + +#### WARNING +In case of Greenplum connector, `DBReader` does not generate raw `SELECT` query. Instead it relies on Spark SQL syntax +which in some cases (using column projection and predicate pushdown) can be converted to Greenplum SQL. + +So `columns`, `where` and `hwm.expression` should be specified in [Spark SQL](https://spark.apache.org/docs/latest/sql-ref-syntax.html) syntax, +not Greenplum SQL. + +This is OK: + +```python +DBReader( + columns=[ + "some_column", + # this cast is executed on Spark side + "CAST(another_column AS STRING)", + ], + # this predicate is parsed by Spark, and can be pushed down to Greenplum + where="some_column LIKE 'val1%'", +) +``` + +This is will fail: + +```python +DBReader( + columns=[ + "some_column", + # Spark does not have `text` type + "CAST(another_column AS text)", + ], + # Spark does not support ~ syntax for regexp matching + where="some_column ~ 'val1.*'", +) +``` + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Greenplum +from onetl.db import DBReader + +greenplum = Greenplum(...) + +reader = DBReader( + connection=greenplum, + source="schema.table", + columns=["id", "key", "CAST(value AS string) value", "updated_dt"], + where="key = 'something'", +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Greenplum +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +greenplum = Greenplum(...) + +reader = DBReader( + connection=greenplum, + source="schema.table", + columns=["id", "key", "CAST(value AS string) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="greenplum_hwm", expression="updated_dt"), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Interaction schema + +High-level schema is described in [Prerequisites](prerequisites.md#greenplum-prerequisites). You can find detailed interaction schema below. + +### Spark <-> Greenplum interaction during DBReader.run() + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Greenplum to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Greenplum to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +### Read data in parallel + +`DBReader` in case of Greenplum connector requires view or table to have a column which is used by Spark +for parallel reads. + +Choosing proper column allows each Spark executor to read only part of data stored in the specified segment, +avoiding moving large amounts of data between segments, which improves reading performance. + +#### Using `gp_segment_id` + +By default, `DBReader` will use [gp_segment_id](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/troubleshooting.html#reading-from-a-view) +column for parallel data reading. Each DataFrame partition will contain data of a specific Greenplum segment. + +This allows each Spark executor read only data from specific Greenplum segment, avoiding moving large amounts of data between segments. + +If view is used, it is recommended to include `gp_segment_id` column to this view: + +### Reading from view with gp_segment_id column + +```python +from onetl.connection import Greenplum +from onetl.db import DBReader + +greenplum = Greenplum(...) + +greenplum.execute( + """ + CREATE VIEW schema.view_with_gp_segment_id AS + SELECT + id, + some_column, + another_column, + gp_segment_id -- IMPORTANT + FROM schema.some_table + """, +) + +reader = DBReader( + connection=greenplum, + source="schema.view_with_gp_segment_id", +) +df = reader.run() +``` + +#### Using custom `partition_column` + +Sometimes table or view is lack of `gp_segment_id` column, but there is some column +with value range correlated with Greenplum segment distribution. + +In this case, custom column can be used instead: + +### Reading from view with custom partition_column + +```python +from onetl.connection import Greenplum +from onetl.db import DBReader + +greenplum = Greenplum(...) + +greenplum.execute( + """ + CREATE VIEW schema.view_with_partition_column AS + SELECT + id, + some_column, + part_column -- correlated to greenplum segment ID + FROM schema.some_table + """, +) + +reader = DBReader( + connection=greenplum, + source="schema.view_with_partition_column", + options=Greenplum.ReadOptions( + # parallelize data using specified column + partitionColumn="part_column", + # create 10 Spark tasks, each will read only part of table data + partitions=10, + ), +) +df = reader.run() +``` + +#### Reading `DISTRIBUTED REPLICATED` tables + +Replicated tables do not have `gp_segment_id` column at all, so you need to set `partition_column` to some column name +of type integer/bigint/smallint. + +### Parallel `JOIN` execution + +In case of using views which require some data motion between Greenplum segments, like `JOIN` queries, another approach should be used. + +Each Spark executor N will run the same query, so each of N query will start its own JOIN process, leading to really heavy load on Greenplum segments. +**This should be avoided**. + +Instead is recommended to run `JOIN` query on Greenplum side, save the result to an intermediate table, +and then read this table using `DBReader`: + +### Reading from view using intermediate table + +```python +from onetl.connection import Greenplum +from onetl.db import DBReader + +greenplum = Greenplum(...) + +greenplum.execute( + """ + CREATE UNLOGGED TABLE schema.intermediate_table AS + SELECT + id, + tbl1.col1, + tbl1.data, + tbl2.another_data + FROM + schema.table1 as tbl1 + JOIN + schema.table2 as tbl2 + ON + tbl1.col1 = tbl2.col2 + WHERE ... + """, +) + +reader = DBReader( + connection=greenplum, + source="schema.intermediate_table", +) +df = reader.run() + +# write dataframe somethere + +greenplum.execute( + """ + DROP TABLE schema.intermediate_table + """, +) +``` + +#### WARNING +**NEVER** do that: + +```python +df1 = DBReader(connection=greenplum, target="public.table1", ...).run() +df2 = DBReader(connection=greenplum, target="public.table2", ...).run() + +joined_df = df1.join(df2, on="col") +``` + +This will lead to sending all the data from both `table1` and `table2` to Spark executor memory, and then `JOIN` +will be performed on Spark side, not inside Greenplum. This is **VERY** inefficient. + +#### `TEMPORARY` tables notice + +Someone could think that writing data from view or result of `JOIN` to `TEMPORARY` table, +and then passing it to `DBReader`, is an efficient way to read data from Greenplum. This is because temp tables are not generating WAL files, +and are automatically deleted after finishing the transaction. + +That will **NOT** work. Each Spark executor establishes its own connection to Greenplum. +And each connection starts its own transaction which means that every executor will read empty temporary table. + +You should use [UNLOGGED](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-sql_commands-CREATE_TABLE.html) tables +to write data to intermediate table without generating WAL logs. + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/greenplum/types.md b/mddocs/docs/_build/markdown/connection/db_connection/greenplum/types.md new file mode 100644 index 000000000..755f86751 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/greenplum/types.md @@ -0,0 +1,318 @@ + + +# Greenplum <-> Spark type mapping + +#### NOTE +The results below are valid for Spark 3.2.4, and may differ on other Spark versions. + +## Type detection & casting + +Spark’s DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from Greenplum + +This is how Greenplum connector performs this: + +* Execute query `SELECT * FROM table LIMIT 0` [1](#id2). +* For each column in query result get column name and Greenplum type. +* Find corresponding `Greenplum type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* Use Spark column projection and predicate pushdown features to build a final query. +* Create DataFrame from generated query with inferred schema. + +* **[1]** Yes, **all columns of a table**, not just selected ones. This means that if source table **contains** columns with unsupported type, the entire table cannot be read. + +### Writing to some existing Greenplum table + +This is how Greenplum connector performs this: + +* Get names of columns in DataFrame. +* Perform `SELECT * FROM table LIMIT 0` query. +* For each column in query result get column name and Greenplum type. +* Match table columns with DataFrame columns (by name, case insensitive). + If some column is present only in target table, but not in DataFrame (like `DEFAULT` or `SERIAL` column), and vice versa, raise an exception. + See [Explicit type cast](). +* Find corresponding `Spark type` → `Greenplumtype (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* If `Greenplumtype (write)` match `Greenplum type (read)`, no additional casts will be performed, DataFrame column will be written to Greenplum as is. +* If `Greenplumtype (write)` does not match `Greenplum type (read)`, DataFrame column will be casted to target column type **on Greenplum side**. For example, you can write column with text data to `json` column which Greenplum connector currently does not support. + +### Create new table using Spark + +#### WARNING +ABSOLUTELY NOT RECOMMENDED! + +This is how Greenplum connector performs this: + +* Find corresponding `Spark type` → `Greenplum type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* Generate DDL for creating table in Greenplum, like `CREATE TABLE (col1 ...)`, and run it. +* Write DataFrame to created table as is. + +More details [can be found here](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/write_to_gpdb.html). + +But Greenplum connector support only limited number of types and almost no custom clauses (like `PARTITION BY`). +So instead of relying on Spark to create tables: + +### See example + +```python +writer = DBWriter( + connection=greenplum, + target="public.table", + options=Greenplum.WriteOptions( + if_exists="append", + # by default distribution is random + distributedBy="id", + # partitionBy is not supported + ), +) +writer.run(df) +``` + +Always prefer creating table with desired DDL **BEFORE WRITING DATA**: + +### See example + +```python +greenplum.execute( + """ + CREATE TABLE public.table ( + id int32, + business_dt timestamp(6), + value json + ) + PARTITION BY RANGE (business_dt) + DISTRIBUTED BY id + """, +) + +writer = DBWriter( + connection=greenplum, + target="public.table", + options=Greenplum.WriteOptions(if_exists="append"), +) +writer.run(df) +``` + +See Greenplum [CREATE TABLE](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-sql_commands-CREATE_TABLE.html) documentation. + +## Supported types + +See: +: * [official connector documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/reference-datatype_mapping.html) + * [list of Greenplum types](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-data_types.html) + +### Numeric types + +| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | +|-----------------------------|----------------------------------|-----------------------------|---------------------------| +| `decimal` | `DecimalType(P=38, S=18)` | `decimal(P=38, S=18)` | `decimal` (unbounded) | +| `decimal(P=0..38)` | `DecimalType(P=0..38, S=0)` | `decimal(P=0..38, S=0)` | | +| `decimal(P=0..38, S=0..38)` | `DecimalType(P=0..38, S=0..38)` | `decimal(P=0..38, S=0..38)` | | +| `decimal(P=39.., S=0..)` | unsupported [2](#id4) | | | +| `real` | `FloatType()` | `real` | `real` | +| `double precision` | `DoubleType()` | `double precision` | `double precision` | +| `-` | `ByteType()` | unsupported | unsupported | +| `smallint` | `ShortType()` | `smallint` | `smallint` | +| `integer` | `IntegerType()` | `integer` | `integer` | +| `bigint` | `LongType()` | `bigint` | `bigint` | +| `money` | unsupported | | | +| `int4range` | | | | +| `int8range` | | | | +| `numrange` | | | | +| `int2vector` | | | | +* **[2]** Greenplum support decimal types with unlimited precision. But Spark’s `DecimalType(P, S)` supports maximum `P=38` (128 bit). It is impossible to read, write or operate with values of larger precision, this leads to an exception. + +### Temporal types + +| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | +|----------------------------------|----------------------------------------------------------------|-------------------------|---------------------------| +| `date` | `DateType()` | `date` | `date` | +| `time` | `TimestampType()`,
time format quirks [3](#id6) | `timestamp` | `timestamp` | +| `time(0..6)` | | | | +| `time with time zone` | | | | +| `time(0..6) with time zone` | | | | +| `timestamp` | `TimestampType()` | `timestamp` | `timestamp` | +| `timestamp(0..6)` | | | | +| `timestamp with time zone` | | | | +| `timestamp(0..6) with time zone` | | | | +| `interval` or any precision | unsupported | | | +| `daterange` | | | | +| `tsrange` | | | | +| `tstzrange` | | | | + +#### WARNING +Note that types in Greenplum and Spark have different value ranges: + +| Greenplum type | Min value | Max value | Spark type | Min value | Max value | +|------------------|-------------------------------|--------------------------------|-------------------|------------------------------|------------------------------| +| `date` | `-4713-01-01` | `5874897-01-01` | `DateType()` | `0001-01-01` | `9999-12-31` | +| `timestamp` | `-4713-01-01 00:00:00.000000` | `294276-12-31 23:59:59.999999` | `TimestampType()` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | +| `time` | `00:00:00.000000` | `24:00:00.000000` | | | | + +So not all of values can be read from Greenplum to Spark. + +References: +: * [Greenplum types documentation](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-data_types.html) + * [Spark DateType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/DateType.html) + * [Spark TimestampType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/TimestampType.html) + +* **[3]** `time` type is the same as `timestamp` with date `1970-01-01`. So instead of reading data from Postgres like `23:59:59` it is actually read `1970-01-01 23:59:59`, and vice versa. + +### String types + +| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | +|---------------------------|----------------|-------------------------|---------------------------| +| `character` | `StringType()` | `text` | `text` | +| `character(N)` | | | | +| `character varying` | | | | +| `character varying(N)` | | | | +| `text` | | | | +| `xml` | | | | +| `CREATE TYPE ... AS ENUM` | | | | +| `json` | unsupported | | | +| `jsonb` | | | | + +### Binary types + +| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | +|-------------------------|----------------------------------|-------------------------|---------------------------| +| `boolean` | `BooleanType()` | `boolean` | `boolean` | +| `bit` | unsupported | | | +| `bit(N)` | | | | +| `bit varying` | | | | +| `bit varying(N)` | | | | +| `bytea` | unsupported [4](#id8) | | | +| `-` | `BinaryType()` | `bytea` | `bytea` | +* **[4]** Yes, that’s weird. + +### Struct types + +| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | +|------------------------------|----------------|-------------------------|---------------------------| +| `T[]` | unsupported | | | +| `-` | `ArrayType()` | unsupported | | +| `CREATE TYPE sometype (...)` | `StringType()` | `text` | `text` | +| `-` | `StructType()` | unsupported | | +| `-` | `MapType()` | | | + +## Unsupported types + +Columns of these types cannot be read/written by Spark: +: * `cidr` + * `inet` + * `macaddr` + * `macaddr8` + * `circle` + * `box` + * `line` + * `lseg` + * `path` + * `point` + * `polygon` + * `tsvector` + * `tsquery` + * `uuid` + +The is a way to avoid this - just cast unsupported types to `text`. But the way this can be done is not a straightforward. + +## Explicit type cast + +### `DBReader` + +Direct casting of Greenplum types is not supported by DBReader due to the connector’s implementation specifics. + +```python +reader = DBReader( + connection=greenplum, + # will fail + columns=["CAST(unsupported_column AS text)"], +) +``` + +But there is a workaround - create a view with casting unsupported column to text (or any other supported type). +For example, you can use [to_json](https://www.postgresql.org/docs/current/functions-json.html) Postgres function to convert column of any type to string representation and then parse this column on Spark side using `JSON.parse_column` method. + +```python +from pyspark.sql.types import ArrayType, IntegerType + +from onetl.connection import Greenplum +from onetl.db import DBReader +from onetl.file.format import JSON + +greenplum = Greenplum(...) + +greenplum.execute( + """ + CREATE VIEW schema.view_with_json_column AS + SELECT + id, + supported_column, + to_json(array_column) array_column_as_json, + gp_segment_id -- ! important ! + FROM + schema.table_with_unsupported_columns + """, +) + +# create dataframe using this view +reader = DBReader( + connection=greenplum, + source="schema.view_with_json_column", +) +df = reader.run() + +# Define the schema for the JSON data +json_scheme = ArrayType(IntegerType()) + +df = df.select( + df.id, + df.supported_column, + JSON().parse_column(df.array_column_as_json, json_scheme).alias("array_column"), +) +``` + +### `DBWriter` + +To write data to a `text` or `json` column in a Greenplum table, use `JSON.serialize_column` method. + +```python +from onetl.connection import Greenplum +from onetl.db import DBWriter +from onetl.file.format import JSON + +greenplum = Greenplum(...) + +greenplum.execute( + """ + CREATE TABLE schema.target_table ( + id int, + supported_column timestamp, + array_column_as_json jsonb, -- or text + ) + DISTRIBUTED BY id + """, +) + +write_df = df.select( + df.id, + df.supported_column, + JSON().serialize_column(df.array_column).alias("array_column_json"), +) + +writer = DBWriter( + connection=greenplum, + target="schema.target_table", +) +writer.run(write_df) +``` + +Then you can parse this column on Greenplum side: + +```sql +SELECT + id, + supported_column, + -- access first item of an array + array_column_as_json->0 +FROM + schema.target_table +``` diff --git a/mddocs/docs/_build/markdown/connection/db_connection/greenplum/write.md b/mddocs/docs/_build/markdown/connection/db_connection/greenplum/write.md new file mode 100644 index 000000000..fe25f225f --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/greenplum/write.md @@ -0,0 +1,47 @@ + + +# Writing to Greenplum using `DBWriter` + +For writing data to Greenplum, use `DBWriter` +with `GreenplumWriteOptions`. + +#### WARNING +Please take into account [Greenplum <-> Spark type mapping](types.md#greenplum-types). + +#### WARNING +It is always recommended to create table explicitly using [Greenplum.execute](execute.md#greenplum-execute) +instead of relying on Spark’s table DDL generation. + +This is because Spark’s DDL generator can create columns with different types than it is expected. + +## Examples + +```python +from onetl.connection import Greenplum +from onetl.db import DBWriter + +greenplum = Greenplum(...) + +df = ... # data is here + +writer = DBWriter( + connection=greenplum, + target="schema.table", + options=Greenplum.WriteOptions( + if_exists="append", + # by default distribution is random + distributedBy="id", + # partitionBy is not supported + ), +) + +writer.run(df) +``` + +## Interaction schema + +High-level schema is described in [Prerequisites](prerequisites.md#greenplum-prerequisites). You can find detailed interaction schema below. + +### Spark <-> Greenplum interaction during DBWriter.run() + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/hive/connection.md b/mddocs/docs/_build/markdown/connection/db_connection/hive/connection.md new file mode 100644 index 000000000..a9e0744d3 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/hive/connection.md @@ -0,0 +1,3 @@ + + +# Hive Connection diff --git a/mddocs/docs/_build/markdown/connection/db_connection/hive/execute.md b/mddocs/docs/_build/markdown/connection/db_connection/hive/execute.md new file mode 100644 index 000000000..33544a205 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/hive/execute.md @@ -0,0 +1,44 @@ + + +# Executing statements in Hive + +Use `Hive.execute(...)` to execute DDL and DML operations. + +## Syntax support + +This method supports **any** query syntax supported by Hive, like: + +* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +* ✅︎ `LOAD DATA ...`, and so on +* ✅︎ `ALTER ...` +* ✅︎ `INSERT INTO ... SELECT ...`, and so on +* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, and so on +* ✅︎ `MSCK REPAIR TABLE ...`, and so on +* ✅︎ other statements not mentioned here +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### WARNING +Actually, query should be written using [SparkSQL](https://spark.apache.org/docs/latest/sql-ref-syntax.html#ddl-statements) syntax, not HiveQL. + +## Examples + +```python +from onetl.connection import Hive + +hive = Hive(...) + +hive.execute("DROP TABLE schema.table") +hive.execute( + """ + CREATE TABLE schema.table ( + id NUMBER, + key VARCHAR, + value DOUBLE + ) + PARTITION BY (business_date DATE) + STORED AS orc + """ +) +``` + +### Details diff --git a/mddocs/docs/_build/markdown/connection/db_connection/hive/index.md b/mddocs/docs/_build/markdown/connection/db_connection/hive/index.md new file mode 100644 index 000000000..bc551b745 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/hive/index.md @@ -0,0 +1,19 @@ + + +# Hive + +# Connection + +* [Prerequisites](prerequisites.md) +* [Hive Connection](connection.md) + +# Operations + +* [Reading from Hive using `DBReader`](read.md) +* [Reading from Hive using `Hive.sql`](sql.md) +* [Writing to Hive using `DBWriter`](write.md) +* [Executing statements in Hive](execute.md) + +# For developers + +* [Hive Slots](slots.md) diff --git a/mddocs/docs/_build/markdown/connection/db_connection/hive/prerequisites.md b/mddocs/docs/_build/markdown/connection/db_connection/hive/prerequisites.md new file mode 100644 index 000000000..aa0fb49c7 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/hive/prerequisites.md @@ -0,0 +1,128 @@ + + +# Prerequisites + +#### NOTE +onETL’s Hive connection is actually SparkSession with access to [Hive Thrift Metastore](https://docs.cloudera.com/cdw-runtime/1.5.0/hive-hms-overview/topics/hive-hms-introduction.html) +and HDFS/S3. +All data motion is made using Spark. Hive Metastore is used only to store tables and partitions metadata. + +This connector does **NOT** require Hive server. It also does **NOT** use Hive JDBC connector. + +## Version Compatibility + +* Hive Metastore version: + : * Officially declared: 0.12 - 3.1.3 (may require to add proper .jar file explicitly) + * Actually tested: 1.2.100, 2.3.10, 3.1.3 +* Spark versions: 2.3.x - 3.5.x +* Java versions: 8 - 20 + +See [official documentation](https://spark.apache.org/docs/latest/sql-data-sources-hive-tables.html). + +## Installing PySpark + +To use Hive connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Connecting to Hive Metastore + +#### NOTE +If you’re using managed Hadoop cluster, skip this step, as all Spark configs are should already present on the host. + +Create `$SPARK_CONF_DIR/hive-site.xml` with Hive Metastore URL: + +```xml + + + + + hive.metastore.uris + thrift://metastore.host.name:9083 + + +``` + +Create `$SPARK_CONF_DIR/core-site.xml` with warehouse location ,e.g. HDFS IPC port of Hadoop namenode, or S3 bucket address & credentials: + +HDFS + +```xml + + + + + fs.defaultFS + hdfs://myhadoopcluster:9820 + + +``` + +S3 + +```xml + + + + !-- See https://hadoop.apache.org/docs/current/hadoop-aws/tools/hadoop-aws/index.html#General_S3A_Client_configuration + + fs.defaultFS + s3a://mys3bucket/ + + + fs.s3a.bucket.mybucket.endpoint + http://s3.somain + + + fs.s3a.bucket.mybucket.connection.ssl.enabled + false + + + fs.s3a.bucket.mybucket.path.style.access + true + + + fs.s3a.bucket.mybucket.aws.credentials.provider + org.apache.hadoop.fs.s3a.SimpleAWSCredentialsProvider + + + fs.s3a.bucket.mybucket.access.key + some-user + + + fs.s3a.bucket.mybucket.secret.key + mysecrettoken + + +``` + +## Using Kerberos + +Some of Hadoop managed clusters use Kerberos authentication. In this case, you should call [kinit](https://web.mit.edu/kerberos/krb5-1.12/doc/user/user_commands/kinit.html) command +**BEFORE** starting Spark session to generate Kerberos ticket. See [Kerberos support](../../../install/kerberos.md#install-kerberos). + +Sometimes it is also required to pass keytab file to Spark config, allowing Spark executors to generate own Kerberos tickets: + +Spark 3 + +```python +SparkSession.builder + .option("spark.kerberos.access.hadoopFileSystems", "hdfs://namenode1.domain.com:9820,hdfs://namenode2.domain.com:9820") + .option("spark.kerberos.principal", "user") + .option("spark.kerberos.keytab", "/path/to/keytab") + .gerOrCreate() +``` + +Spark 2 + +```python +SparkSession.builder + .option("spark.yarn.access.hadoopFileSystems", "hdfs://namenode1.domain.com:9820,hdfs://namenode2.domain.com:9820") + .option("spark.yarn.principal", "user") + .option("spark.yarn.keytab", "/path/to/keytab") + .gerOrCreate() +``` + +See [Spark security documentation](https://spark.apache.org/docs/latest/security.html#kerberos) +for more details. diff --git a/mddocs/docs/_build/markdown/connection/db_connection/hive/read.md b/mddocs/docs/_build/markdown/connection/db_connection/hive/read.md new file mode 100644 index 000000000..2caa0ba73 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/hive/read.md @@ -0,0 +1,92 @@ + + +# Reading from Hive using `DBReader` + +`DBReader` supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, +but does not support custom queries, like `JOIN`. + +## Supported DBReader features + +* ✅︎ `columns` +* ✅︎ `where` +* ✅︎ `hwm`, supported strategies: +* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) +* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) +* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) +* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) +* ❌ `hint` (is not supported by Hive) +* ❌ `df_schema` +* ❌ `options` (only Spark config params are used) + +#### WARNING +Actually, `columns`, `where` and `hwm.expression` should be written using [SparkSQL](https://spark.apache.org/docs/latest/sql-ref-syntax.html#data-retrieval-statements) syntax, +not HiveQL. + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Hive +from onetl.db import DBReader + +hive = Hive(...) + +reader = DBReader( + connection=hive, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Hive +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +hive = Hive(...) + +reader = DBReader( + connection=hive, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="hive_hwm", expression="updated_dt"), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Use column-based write formats + +Prefer these write formats: +: * [ORC](https://spark.apache.org/docs/latest/sql-data-sources-orc.html) + * [Parquet](https://spark.apache.org/docs/latest/sql-data-sources-parquet.html) + * [Iceberg](https://iceberg.apache.org/spark-quickstart/) + * [Hudi](https://hudi.apache.org/docs/quick-start-guide/) + * [Delta](https://docs.delta.io/latest/quick-start.html#set-up-apache-spark-with-delta-lake) + +For colum-based write formats, each file contains separated sections there column data is stored. The file footer contains +location of each column section/group. Spark can use this information to load only sections required by specific query, e.g. only selected columns, +to drastically speed up the query. + +Another advantage is high compression ratio, e.g. 10x-100x in comparison to JSON or CSV. + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. +This drastically reduces the amount of data read by Spark, **if column-based file formats are used**. + +### Use partition columns in `where` clause + +Queries should include `WHERE` clause with filters on Hive partitioning columns. +This allows Spark to read only small set of files (*partition pruning*) instead of scanning the entire table, so this drastically increases performance. + +Supported operators are: `=`, `>`, `<` and `BETWEEN`, and only against some **static** value. diff --git a/mddocs/docs/_build/markdown/connection/db_connection/hive/slots.md b/mddocs/docs/_build/markdown/connection/db_connection/hive/slots.md new file mode 100644 index 000000000..1ecf2bfd0 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/hive/slots.md @@ -0,0 +1,3 @@ + + +# Hive Slots diff --git a/mddocs/docs/_build/markdown/connection/db_connection/hive/sql.md b/mddocs/docs/_build/markdown/connection/db_connection/hive/sql.md new file mode 100644 index 000000000..13a8aa158 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/hive/sql.md @@ -0,0 +1,68 @@ + + +# Reading from Hive using `Hive.sql` + +`Hive.sql` allows passing custom SQL query, but does not support incremental strategies. + +## Syntax support + +Only queries with the following syntax are supported: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### WARNING +Actually, query should be written using [SparkSQL](https://spark.apache.org/docs/latest/sql-ref-syntax.html#data-retrieval-statements) syntax, not HiveQL. + +## Examples + +```python +from onetl.connection import Hive + +hive = Hive(...) +df = hive.sql( + """ + SELECT + id, + key, + CAST(value AS text) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """ +) +``` + +## Recommendations + +### Use column-based write formats + +Prefer these write formats: +: * [ORC](https://spark.apache.org/docs/latest/sql-data-sources-orc.html) + * [Parquet](https://spark.apache.org/docs/latest/sql-data-sources-parquet.html) + * [Iceberg](https://iceberg.apache.org/spark-quickstart/) + * [Hudi](https://hudi.apache.org/docs/quick-start-guide/) + * [Delta](https://docs.delta.io/latest/quick-start.html#set-up-apache-spark-with-delta-lake) + +For colum-based write formats, each file contains separated sections there column data is stored. The file footer contains +location of each column section/group. Spark can use this information to load only sections required by specific query, e.g. only selected columns, +to drastically speed up the query. + +Another advantage is high compression ratio, e.g. 10x-100x in comparison to JSON or CSV. + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This drastically reduces the amount of data read by Spark, **if column-based file formats are used**. + +### Use partition columns in `where` clause + +Queries should include `WHERE` clause with filters on Hive partitioning columns. +This allows Spark to read only small set of files (*partition pruning*) instead of scanning the entire table, so this drastically increases performance. + +Supported operators are: `=`, `>`, `<` and `BETWEEN`, and only against some **static** value. + +## Details diff --git a/mddocs/docs/_build/markdown/connection/db_connection/hive/write.md b/mddocs/docs/_build/markdown/connection/db_connection/hive/write.md new file mode 100644 index 000000000..8e513e41a --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/hive/write.md @@ -0,0 +1,167 @@ + + +# Writing to Hive using `DBWriter` + +For writing data to Hive, use `DBWriter`. + +## Examples + +```python +from onetl.connection import Hive +from onetl.db import DBWriter + +hive = Hive(...) + +df = ... # data is here + +# Create dataframe with specific number of Spark partitions. +# Use the Hive partitioning columns to group the data. Create max 20 files per Hive partition. +# Also sort the data by column which most data is correlated with (e.g. user_id), reducing files size. + +num_files_per_partition = 20 +partition_columns = ["country", "business_date"] +sort_columns = ["user_id"] +write_df = df.repartition( + num_files_per_partition, + *partition_columns, + *sort_columns, +).sortWithinPartitions(*partition_columns, *sort_columns) + +writer = DBWriter( + connection=hive, + target="schema.table", + options=Hive.WriteOptions( + if_exists="append", + # Hive partitioning columns. + partitionBy=partition_columns, + ), +) + +writer.run(write_df) +``` + +## Recommendations + +### Use column-based write formats + +Prefer these write formats: +: * [ORC](https://spark.apache.org/docs/latest/sql-data-sources-orc.html) (**default**) + * [Parquet](https://spark.apache.org/docs/latest/sql-data-sources-parquet.html) + * [Iceberg](https://iceberg.apache.org/spark-quickstart/) + * [Hudi](https://hudi.apache.org/docs/quick-start-guide/) + * [Delta](https://docs.delta.io/latest/quick-start.html#set-up-apache-spark-with-delta-lake) + +#### WARNING +When using `DBWriter`, the default spark data format configured in `spark.sql.sources.default` is ignored, as `Hive.WriteOptions(format=...)` default value is explicitly set to `orc`. + +For column-based write formats, each file contains separated sections where column data is stored. The file footer contains +location of each column section/group. Spark can use this information to load only sections required by specific query, e.g. only selected columns, +to drastically speed up the query. + +Another advantage is high compression ratio, e.g. 10x-100x in comparison to JSON or CSV. + +### Use partitioning + +#### How does it work + +Hive support splitting data to partitions, which are different directories in filesystem with names like `some_col=value1/another_col=value2`. + +For example, dataframe with content like this: + +| country: string | business_date: date | user_id: int | bytes: long | +|-------------------|-----------------------|----------------|---------------| +| RU | 2024-01-01 | 1234 | 25325253525 | +| RU | 2024-01-01 | 2345 | 23234535243 | +| RU | 2024-01-02 | 1234 | 62346634564 | +| US | 2024-01-01 | 5678 | 4252345354 | +| US | 2024-01-02 | 5678 | 5474575745 | +| US | 2024-01-03 | 5678 | 3464574567 | + +With `partitionBy=["country", "business_dt"]` data will be stored as files in the following subfolders: +: * `/country=RU/business_date=2024-01-01/` + * `/country=RU/business_date=2024-01-02/` + * `/country=US/business_date=2024-01-01/` + * `/country=US/business_date=2024-01-02/` + * `/country=US/business_date=2024-01-03/` + +A separated subdirectory is created for each distinct combination of column values in the dataframe. + +Please do not confuse Spark dataframe partitions (a.k.a batches of data handled by Spark executors, usually in parallel) +and Hive partitioning (store data in different subdirectories). +Number of Spark dataframe partitions is correlated the number of files created in **each** Hive partition. +For example, Spark dataframe with 10 partitions and 5 distinct values of Hive partition columns will be saved as 5 subfolders with 10 files each = 50 files in total. +Without Hive partitioning, all the files are placed into one flat directory. + +#### But why? + +Queries which has `WHERE` clause with filters on Hive partitioning columns, like `WHERE country = 'RU' AND business_date='2024-01-01'`, will +read only files from this exact partitions, like `/country=RU/business_date=2024-01-01/`, and skip files from other partitions. + +This drastically increases performance and reduces the amount of memory used by Spark. +Consider using Hive partitioning in all tables. + +#### Which columns should I use? + +Usually Hive partitioning columns are based on event date or location, like `country: string`, `business_date: date`, `run_date: date` and so on. + +**Partition columns should contain data with low cardinality.** +Dates, small integers, strings with low number of possible values are OK. +But timestamp, float, decimals, longs (like user id), strings with lots oj unique values (like user name or email) should **NOT** be used as Hive partitioning columns. +Unlike some other databases, range and hash-based partitions are not supported. + +Partition column should be a part of a dataframe. If you want to partition values by date component of `business_dt: timestamp` column, +add a new column to dataframe like this: `df.withColumn("business_date", date(df.business_dt))`. + +### Use compression + +Using compression algorithms like `snappy`, `lz4` or `zstd` can reduce the size of files (up to 10x). + +### Prefer creating large files + +Storing millions of small files is not that HDFS and S3 are designed for. Minimal file size should be at least 10Mb, but usually it is like 128Mb+ or 256Mb+ (HDFS block size). +**NEVER** create files with few Kbytes in size. + +Number of files can be different in different cases. +On one hand, Spark Adaptive Query Execution (AQE) can merge small Spark dataframe partitions into one larger. +On the other hand, dataframes with skewed data can produce a larger number of files than expected. + +To create small amount of large files, you can reduce number of Spark dataframe partitions. +Use [df.repartition(N, columns…)](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.DataFrame.repartition.html) function, +like this: `df.repartition(20, "col1", "col2")`. +This creates new Spark dataframe with partitions using `hash(df.col1 + df.col2) mod 20` expression, avoiding data skew. + +Note: larger dataframe partitions requires more resources (CPU, RAM) on Spark executor. The exact number of partitions +should be determined empirically, as it depends on the amount of data and available resources. + +### Sort data before writing + +Dataframe with sorted content: + +| country: string | business_date: date | user_id: int | business_dt: timestamp | bytes: long | +|-------------------|-----------------------|----------------|--------------------------|---------------| +| RU | 2024-01-01 | 1234 | 2024-01-01T11:22:33.456 | 25325253525 | +| RU | 2024-01-01 | 1234 | 2024-01-01T12:23:44.567 | 25325253525 | +| RU | 2024-01-02 | 1234 | 2024-01-01T13:25:56.789 | 34335645635 | +| US | 2024-01-01 | 2345 | 2024-01-01T10:00:00.000 | 12341 | +| US | 2024-01-02 | 2345 | 2024-01-01T15:11:22.345 | 13435 | +| US | 2024-01-03 | 2345 | 2024-01-01T20:22:33.567 | 14564 | + +Has a much better compression rate than unsorted one, e.g. 2x or even higher: + +| country: string | business_date: date | user_id: int | business_dt: timestamp | bytes: long | +|-------------------|-----------------------|----------------|--------------------------|---------------| +| RU | 2024-01-01 | 1234 | 2024-01-01T11:22:33.456 | 25325253525 | +| RU | 2024-01-01 | 6345 | 2024-12-01T23:03:44.567 | 25365 | +| RU | 2024-01-02 | 5234 | 2024-07-01T06:10:56.789 | 45643456747 | +| US | 2024-01-01 | 4582 | 2024-04-01T17:59:00.000 | 362546475 | +| US | 2024-01-02 | 2345 | 2024-09-01T04:24:22.345 | 3235 | +| US | 2024-01-03 | 3575 | 2024-03-01T21:37:33.567 | 346345764 | + +Choosing columns to sort data by is really depends on the data. If data is correlated with some specific +column, like in example above the amount of traffic is correlated with both `user_id` and `timestamp`, +use `df.sortWithinPartitions("user_id", "timestamp")` before writing the data. + +If `df.repartition(N, repartition_columns...)` is used in combination with `df.sortWithinPartitions(sort_columns...)`, +then `sort_columns` should start with `repartition_columns` or be equal to it. + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/index.md b/mddocs/docs/_build/markdown/connection/db_connection/index.md new file mode 100644 index 000000000..cd8f76477 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/index.md @@ -0,0 +1,16 @@ + + +# DB Connections + +# DB Connections + +* [Clickhouse](clickhouse/index.md) +* [Greenplum](greenplum/index.md) +* [Kafka](kafka/index.md) +* [Hive](hive/index.md) +* [MongoDB](mongodb/index.md) +* [MSSQL](mssql/index.md) +* [MySQL](mysql/index.md) +* [Oracle](oracle/index.md) +* [Postgres](postgres/index.md) +* [Teradata](teradata/index.md) diff --git a/mddocs/docs/_build/markdown/connection/db_connection/kafka/auth.md b/mddocs/docs/_build/markdown/connection/db_connection/kafka/auth.md new file mode 100644 index 000000000..000cfa210 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/kafka/auth.md @@ -0,0 +1,3 @@ + + +# Kafka Auth diff --git a/mddocs/docs/_build/markdown/connection/db_connection/kafka/basic_auth.md b/mddocs/docs/_build/markdown/connection/db_connection/kafka/basic_auth.md new file mode 100644 index 000000000..d6153e780 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/kafka/basic_auth.md @@ -0,0 +1,3 @@ + + +# Kafka BasicAuth diff --git a/mddocs/docs/_build/markdown/connection/db_connection/kafka/connection.md b/mddocs/docs/_build/markdown/connection/db_connection/kafka/connection.md new file mode 100644 index 000000000..12c48de50 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/kafka/connection.md @@ -0,0 +1,3 @@ + + +# Kafka Connection diff --git a/mddocs/docs/_build/markdown/connection/db_connection/kafka/index.md b/mddocs/docs/_build/markdown/connection/db_connection/kafka/index.md new file mode 100644 index 000000000..5e5f45ba1 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/kafka/index.md @@ -0,0 +1,31 @@ + + +# Kafka + +# Connection + +* [Prerequisites](prerequisites.md) +* [Kafka Connection](connection.md) +* [Kafka Troubleshooting](troubleshooting.md) + +# Protocols + +* [Kafka PlaintextProtocol](plaintext_protocol.md) +* [Kafka SSLProtocol](ssl_protocol.md) + +# Auth methods + +* [Kafka BasicAuth](basic_auth.md) +* [Kafka KerberosAuth](kerberos_auth.md) +* [Kafka ScramAuth](scram_auth.md) + +# Operations + +* [Reading from Kafka](read.md) +* [Writing to Kafka](write.md) + +# For developers + +* [Kafka Auth](auth.md) +* [Kafka Protocol](protocol.md) +* [Kafka Slots](slots.md) diff --git a/mddocs/docs/_build/markdown/connection/db_connection/kafka/kerberos_auth.md b/mddocs/docs/_build/markdown/connection/db_connection/kafka/kerberos_auth.md new file mode 100644 index 000000000..f2a2a540b --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/kafka/kerberos_auth.md @@ -0,0 +1,3 @@ + + +# Kafka KerberosAuth diff --git a/mddocs/docs/_build/markdown/connection/db_connection/kafka/plaintext_protocol.md b/mddocs/docs/_build/markdown/connection/db_connection/kafka/plaintext_protocol.md new file mode 100644 index 000000000..aa2c7fa78 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/kafka/plaintext_protocol.md @@ -0,0 +1,3 @@ + + +# Kafka PlaintextProtocol diff --git a/mddocs/docs/_build/markdown/connection/db_connection/kafka/prerequisites.md b/mddocs/docs/_build/markdown/connection/db_connection/kafka/prerequisites.md new file mode 100644 index 000000000..3299560cf --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/kafka/prerequisites.md @@ -0,0 +1,65 @@ + + +# Prerequisites + +## Version Compatibility + +* Kafka server versions: + : * Officially declared: 0.10 or higher + * Actually tested: 3.2.3, 3.9.0 (only Kafka 3.x supports message headers) +* Spark versions: 2.4.x - 3.5.x +* Java versions: 8 - 17 + +See [official documentation](https://spark.apache.org/docs/latest/structured-streaming-kafka-integration.html). + +## Installing PySpark + +To use Kafka connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Connecting to Kafka + +### Connection address + +Kafka is a distributed service, and usually has a list of brokers you can connect to (unlike other connectors, there only one host+port can be set). +Please contact your Kafka administrator to get addresses of these brokers, as there are no defaults. + +Also Kafka has a feature called *advertised listeners* - client connects to one broker, and received list of other brokers in the clusters. +So you don’t have to pass all brokers to `addresses`, it can be some subset. Other broker addresses will be fetched directly from the cluster. + +### Connection protocol + +Kafka can support different connection protocols. List of currently supported protocols: +: * `PLAINTEXT` (not secure) + * `SSL` (secure, recommended) + +Note that specific port can listen for only one of these protocols, so it is important to set +proper port number + protocol combination. + +### Authentication mechanism + +Kafka can support different authentication mechanism (also known as [SASL](https://en.wikipedia.org/wiki/Simple_Authentication_and_Security_Layer)). + +List of currently supported mechanisms: +: * `PLAIN`. To no confuse this with `PLAINTEXT` connection protocol, onETL uses name `BasicAuth`. + * `GSSAPI`. To simplify naming, onETL uses name `KerberosAuth`. + * `SCRAM-SHA-256 or SCRAM-SHA-512` (recommended). + +Different mechanisms use different types of credentials (login + password, keytab file, and so on). + +Note that connection protocol and auth mechanism are set in pairs: +: * If you see `SASL_PLAINTEXT` this means `PLAINTEXT` connection protocol + some auth mechanism. + * If you see `SASL_SSL` this means `SSL` connection protocol + some auth mechanism. + * If you see just `PLAINTEXT` or `SSL` (**no** `SASL`), this means that authentication is disabled (anonymous access). + +Please contact your Kafka administrator to get details about enabled auth mechanism in a specific Kafka instance. + +### Required grants + +Ask your Kafka administrator to set following grants for a user, *if Kafka instance uses ACL*: +: * `Describe` + `Read` for reading data from Kafka (Consumer). + * `Describe` + `Write` for writing data from Kafka (Producer). + +More details can be found in [documentation](https://kafka.apache.org/documentation/#operations_in_kafka). diff --git a/mddocs/docs/_build/markdown/connection/db_connection/kafka/protocol.md b/mddocs/docs/_build/markdown/connection/db_connection/kafka/protocol.md new file mode 100644 index 000000000..135322bfb --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/kafka/protocol.md @@ -0,0 +1,3 @@ + + +# Kafka Protocol diff --git a/mddocs/docs/_build/markdown/connection/db_connection/kafka/read.md b/mddocs/docs/_build/markdown/connection/db_connection/kafka/read.md new file mode 100644 index 000000000..2836aaa1f --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/kafka/read.md @@ -0,0 +1,123 @@ + + +# Reading from Kafka + +Data can be read from Kafka to Spark using `DBReader`. +It also supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading. + +## Supported DBReader features + +* ❌ `columns` (is not supported by Kafka) +* ❌ `where` (is not supported by Kafka) +* ✅︎ `hwm`, supported strategies: +* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) +* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) +* * ❌ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) +* * ❌ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) +* ❌ `hint` (is not supported by Kafka) +* ❌ `df_schema` (see note below) +* ✅︎ `options` (see `Kafka.ReadOptions`) + +## Dataframe schema + +Unlike other DB connections, Kafka does not have concept of columns. +All the topics messages have the same set of fields, see structure below: + +```text +root +|-- key: binary (nullable = true) +|-- value: binary (nullable = true) +|-- topic: string (nullable = false) +|-- partition: integer (nullable = false) +|-- offset: integer (nullable = false) +|-- timestamp: timestamp (nullable = false) +|-- timestampType: integer (nullable = false) +|-- headers: struct (nullable = true) + |-- key: string (nullable = false) + |-- value: binary (nullable = true) +``` + +`headers` field is present in the dataframe only if `Kafka.ReadOptions(include_headers=True)` is passed (compatibility with Kafka 1.x). + +## Value deserialization + +To read `value` or `key` of other type than bytes (e.g. struct or integer), users have to deserialize values manually. + +This could be done using following methods: +: * `Avro.parse_column` + * `JSON.parse_column` + * `CSV.parse_column` + * `XML.parse_column` + +## Examples + +Snapshot strategy, `value` is Avro binary data: + +```python +from onetl.connection import Kafka +from onetl.db import DBReader, DBWriter +from onetl.file.format import Avro +from pyspark.sql.functions import decode + +# read all topic data from Kafka +kafka = Kafka(...) +reader = DBReader(connection=kafka, source="avro_topic") +read_df = reader.run() + +# parse Avro format to Spark struct +avro = Avro( + schema_dict={ + "type": "record", + "name": "Person", + "fields": [ + {"name": "name", "type": "string"}, + {"name": "age", "type": "int"}, + ], + } +) +deserialized_df = read_df.select( + # cast binary key to string + decode("key", "UTF-8").alias("key"), + avro.parse_column("value"), +) +``` + +Incremental strategy, `value` is JSON string: + +#### NOTE +Currently Kafka connector does support only HWMs based on `offset` field. Other fields, like `timestamp`, are not yet supported. + +```python +from onetl.connection import Kafka +from onetl.db import DBReader, DBWriter +from onetl.file.format import JSON +from pyspark.sql.functions import decode + +kafka = Kafka(...) + +# read only new data from Kafka topic +reader = DBReader( + connection=kafka, + source="topic_name", + hwm=DBReader.AutoDetectHWM(name="kafka_hwm", expression="offset"), +) + +with IncrementalStrategy(): + read_df = reader.run() + +# parse JSON format to Spark struct +json = JSON() +schema = StructType( + [ + StructField("name", StringType(), nullable=True), + StructField("age", IntegerType(), nullable=True), + ], +) +deserialized_df = read_df.select( + # cast binary key to string + decode("key", "UTF-8").alias("key"), + json.parse_column("value", json), +) +``` + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/kafka/scram_auth.md b/mddocs/docs/_build/markdown/connection/db_connection/kafka/scram_auth.md new file mode 100644 index 000000000..a850d28ad --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/kafka/scram_auth.md @@ -0,0 +1,3 @@ + + +# Kafka ScramAuth diff --git a/mddocs/docs/_build/markdown/connection/db_connection/kafka/slots.md b/mddocs/docs/_build/markdown/connection/db_connection/kafka/slots.md new file mode 100644 index 000000000..2dc47acf7 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/kafka/slots.md @@ -0,0 +1,3 @@ + + +# Kafka Slots diff --git a/mddocs/docs/_build/markdown/connection/db_connection/kafka/ssl_protocol.md b/mddocs/docs/_build/markdown/connection/db_connection/kafka/ssl_protocol.md new file mode 100644 index 000000000..35e661a6a --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/kafka/ssl_protocol.md @@ -0,0 +1,3 @@ + + +# Kafka SSLProtocol diff --git a/mddocs/docs/_build/markdown/connection/db_connection/kafka/troubleshooting.md b/mddocs/docs/_build/markdown/connection/db_connection/kafka/troubleshooting.md new file mode 100644 index 000000000..cd15b5ba7 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/kafka/troubleshooting.md @@ -0,0 +1,10 @@ + + +# Kafka Troubleshooting + +#### NOTE +General guide: [Troubleshooting](../../../troubleshooting/index.md#troubleshooting). + +## Cannot connect using `SSL` protocol + +Please check that certificate files are not Base-64 encoded. diff --git a/mddocs/docs/_build/markdown/connection/db_connection/kafka/write.md b/mddocs/docs/_build/markdown/connection/db_connection/kafka/write.md new file mode 100644 index 000000000..48e6e2b3e --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/kafka/write.md @@ -0,0 +1,64 @@ + + +# Writing to Kafka + +For writing data to Kafka, use `DBWriter` with specific options (see below). + +## Dataframe schema + +Unlike other DB connections, Kafka does not have concept of columns. +All the topics messages have the same set of fields. Only some of them can be written: + +```text +root +|-- key: binary (nullable = true) +|-- value: binary (nullable = true) +|-- headers: struct (nullable = true) + |-- key: string (nullable = false) + |-- value: binary (nullable = true) +``` + +`headers` can be passed only with `Kafka.WriteOptions(include_headers=True)` (compatibility with Kafka 1.x). + +Field `topic` should not be present in the dataframe, as it is passed to `DBWriter(target=...)`. + +Other fields, like `partition`, `offset`, `timestamp` are set by Kafka, and cannot be passed explicitly. + +## Value serialization + +To write `value` or `key` of other type than bytes (e.g. struct or integer), users have to serialize values manually. + +This could be done using following methods: +: * `Avro.serialize_column` + * `JSON.serialize_column` + * `CSV.serialize_column` + +## Examples + +Convert `value` to JSON string, and write to Kafka: + +```python +from onetl.connection import Kafka +from onetl.db import DBWriter +from onetl.file.format import JSON + +df = ... # original data is here + +# serialize struct data as JSON +json = JSON() +write_df = df.select( + df.key, + json.serialize_column(df.value), +) + +# write data to Kafka +kafka = Kafka(...) + +writer = DBWriter( + connection=kafka, + target="topic_name", +) +writer.run(write_df) +``` + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mongodb/connection.md b/mddocs/docs/_build/markdown/connection/db_connection/mongodb/connection.md new file mode 100644 index 000000000..c4af27d58 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mongodb/connection.md @@ -0,0 +1,3 @@ + + +# MongoDB Connection diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mongodb/index.md b/mddocs/docs/_build/markdown/connection/db_connection/mongodb/index.md new file mode 100644 index 000000000..45ab522a3 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mongodb/index.md @@ -0,0 +1,18 @@ + + +# MongoDB + +# Connection + +* [Prerequisites](prerequisites.md) +* [MongoDB Connection](connection.md) + +# Operations + +* [Reading from MongoDB using `DBReader`](read.md) +* [Reading from MongoDB using `MongoDB.pipeline`](pipeline.md) +* [Writing to MongoDB using `DBWriter`](write.md) + +# Troubleshooting + +* [MongoDB <-> Spark type mapping](types.md) diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mongodb/pipeline.md b/mddocs/docs/_build/markdown/connection/db_connection/mongodb/pipeline.md new file mode 100644 index 000000000..02a77f855 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mongodb/pipeline.md @@ -0,0 +1,19 @@ + + +# Reading from MongoDB using `MongoDB.pipeline` + +`MongoDB.sql` allows passing custom pipeline, +but does not support incremental strategies. + +#### WARNING +Please take into account [MongoDB <-> Spark type mapping](types.md#mongodb-types) + +## Recommendations + +### Pay attention to `pipeline` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `mongodb.pipeline(..., pipeline={"$match": {"column": {"$eq": "value"}}})` value. +This both reduces the amount of data send from MongoDB to Spark, and may also improve performance of the query. +Especially if there are indexes for columns used in `pipeline` value. + +## References diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mongodb/prerequisites.md b/mddocs/docs/_build/markdown/connection/db_connection/mongodb/prerequisites.md new file mode 100644 index 000000000..4572fb4f6 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mongodb/prerequisites.md @@ -0,0 +1,72 @@ + + +# Prerequisites + +## Version Compatibility + +* MongoDB server versions: + : * Officially declared: 4.0 or higher + * Actually tested: 4.0.0, 8.0.4 +* Spark versions: 3.2.x - 3.5.x +* Java versions: 8 - 20 + +See [official documentation](https://www.mongodb.com/docs/spark-connector/). + +## Installing PySpark + +To use MongoDB connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Connecting to MongoDB + +### Connection host + +It is possible to connect to MongoDB host by using either DNS name of host or it’s IP address. + +It is also possible to connect to MongoDB shared cluster: + +```python +mongo = MongoDB( + host="master.host.or.ip", + user="user", + password="*****", + database="target_database", + spark=spark, + extra={ + # read data from secondary cluster node, switch to primary if not available + "readPreference": "secondaryPreferred", + }, +) +``` + +Supported `readPreference` values are described in [official documentation](https://www.mongodb.com/docs/manual/core/read-preference/). + +### Connection port + +Connection is usually performed to port `27017`. Port may differ for different MongoDB instances. +Please ask your MongoDB administrator to provide required information. + +### Required grants + +Ask your MongoDB cluster administrator to set following grants for a user, +used for creating a connection: + +Read + Write + +```js +// allow writing data to specific database +db.grantRolesToUser("username", [{db: "somedb", role: "readWrite"}]) +``` + +Read only + +```js +// allow reading data from specific database +db.grantRolesToUser("username", [{db: "somedb", role: "read"}]) +``` + +See: +: * [db.grantRolesToUser documentation](https://www.mongodb.com/docs/manual/reference/method/db.grantRolesToUser) + * [MongoDB builtin roles](https://www.mongodb.com/docs/manual/reference/built-in-roles) diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mongodb/read.md b/mddocs/docs/_build/markdown/connection/db_connection/mongodb/read.md new file mode 100644 index 000000000..f51f362e7 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mongodb/read.md @@ -0,0 +1,127 @@ + + +# Reading from MongoDB using `DBReader` + +`DBReader` supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, +but does not support custom pipelines, e.g. aggregation. + +#### WARNING +Please take into account [MongoDB <-> Spark type mapping](types.md#mongodb-types) + +## Supported DBReader features + +* ❌ `columns` (for now, all document fields are read) +* ✅︎ `where` (passed to `{"$match": ...}` aggregation pipeline) +* ✅︎ `hwm`, supported strategies: +* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) +* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) +* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) +* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) +* * Note that `expression` field of HWM can only be a field name, not a custom expression +* ✅︎ `hint` (see [official documentation](https://www.mongodb.com/docs/v5.0/reference/operator/meta/hint/)) +* ✅︎ `df_schema` (mandatory) +* ✅︎ `options` (see `MongoDB.ReadOptions`) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import MongoDB +from onetl.db import DBReader + +from pyspark.sql.types import ( + StructType, + StructField, + IntegerType, + StringType, + TimestampType, +) + +mongodb = MongoDB(...) + +# mandatory +df_schema = StructType( + [ + StructField("_id", StringType()), + StructField("some", StringType()), + StructField( + "field", + StructType( + [ + StructField("nested", IntegerType()), + ], + ), + ), + StructField("updated_dt", TimestampType()), + ] +) + +reader = DBReader( + connection=mongodb, + source="some_collection", + df_schema=df_schema, + where={"field": {"$eq": 123}}, + hint={"field": 1}, + options=MongoDBReadOptions(batchSize=10000), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import MongoDB +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +from pyspark.sql.types import ( + StructType, + StructField, + IntegerType, + StringType, + TimestampType, +) + +mongodb = MongoDB(...) + +# mandatory +df_schema = StructType( + [ + StructField("_id", StringType()), + StructField("some", StringType()), + StructField( + "field", + StructType( + [ + StructField("nested", IntegerType()), + ], + ), + ), + StructField("updated_dt", TimestampType()), + ] +) + +reader = DBReader( + connection=mongodb, + source="some_collection", + df_schema=df_schema, + where={"field": {"$eq": 123}}, + hint={"field": 1}, + hwm=DBReader.AutoDetectHWM(name="mongodb_hwm", expression="updated_dt"), + options=MongoDBReadOptions(batchSize=10000), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where={"column": {"$eq": "value"}})` clause. +This both reduces the amount of data send from MongoDB to Spark, and may also improve performance of the query. +Especially if there are indexes for columns used in `where` clause. + +## Read options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mongodb/types.md b/mddocs/docs/_build/markdown/connection/db_connection/mongodb/types.md new file mode 100644 index 000000000..2d75b5e52 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mongodb/types.md @@ -0,0 +1,214 @@ + + +# MongoDB <-> Spark type mapping + +#### NOTE +The results below are valid for Spark 3.5.5, and may differ on other Spark versions. + +## Type detection & casting + +Spark’s DataFrames always have a `schema` which is a list of fields with corresponding Spark types. All operations on a field are performed using field type. + +MongoDB is, by design, \_\_schemaless_\_. So there are 2 ways how this can be handled: + +* User provides DataFrame schema explicitly: + + ### See example + + ```python + from onetl.connection import MongoDB + from onetl.db import DBReader + + from pyspark.sql.types import ( + StructType, + StructField, + IntegerType, + StringType, + TimestampType, + ) + + mongodb = MongoDB(...) + + df_schema = StructType( + [ + StructField("_id", StringType()), + StructField("some", StringType()), + StructField( + "field", + StructType( + [ + StructField("nested", IntegerType()), + ] + ), + ), + ] + ) + + reader = DBReader( + connection=mongodb, + source="some_collection", + df_schema=df_schema, + ) + df = reader.run() + + # or + + df = mongodb.pipeline( + collection="some_collection", + df_schema=df_schema, + ) + ``` +* Rely on MongoDB connector schema infer: + ```python + df = mongodb.pipeline(collection="some_collection") + ``` + + In this case MongoDB connector read a sample of collection documents, and build DataFrame schema based on document fields and values. + +It is highly recommended to pass `df_schema` explicitly, to avoid type conversion issues. + +### References + +Here you can find source code with type conversions: + +* [MongoDB -> Spark](https://github.com/mongodb/mongo-spark/blob/r10.4.1/src/main/java/com/mongodb/spark/sql/connector/schema/InferSchema.java#L214-L260) +* [Spark -> MongoDB](https://github.com/mongodb/mongo-spark/blob/r10.4.1/src/main/java/com/mongodb/spark/sql/connector/schema/RowToBsonDocumentConverter.java#L157-L260) + +## Supported types + +See [official documentation](https://www.mongodb.com/docs/manual/reference/bson-types/) + +### Numeric types + +| MongoDB type (read) | Spark type | MongoDB type (write) | +|-----------------------|---------------------------|------------------------| +| `Decimal128` | `DecimalType(P=34, S=32)` | `Decimal128` | +| `-` | `FloatType()` | `Double` | +| `Double` | `DoubleType()` | | +| `-` | `ByteType()` | `Int32` | +| `-` | `ShortType()` | | +| `Int32` | `IntegerType()` | | +| `Int64` | `LongType()` | `Int64` | + +### Temporal types + +| MongoDB type (read) | Spark type | MongoDB type (write) | +|-----------------------|---------------------------------|-------------------------------------------------------------------| +| `-` | `DateType()`, days | `Date`, milliseconds | +| `Date`, milliseconds | `TimestampType()`, microseconds | `Date`, milliseconds,
**precision loss** [2](#id2) | +| `Timestamp`, seconds | `TimestampType()`, microseconds | `Date`, milliseconds | +| `-` | `TimestampNTZType()` | unsupported | +| `-` | `DayTimeIntervalType()` | | + +#### WARNING +Note that types in MongoDB and Spark have different value ranges: + +| MongoDB type | Min value | Max value | Spark type | Min value | Max value | +|----------------|-----------------------|-----------------------|-------------------|------------------------------|------------------------------| +| `Date` | -290 million years | 290 million years | `TimestampType()` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | +| `Timestamp` | `1970-01-01 00:00:00` | `2106-02-07 09:28:16` | | | | + +So not all values can be read from MongoDB to Spark, and can written from Spark DataFrame to MongoDB. + +References: +: * [MongoDB Date type documentation](https://www.mongodb.com/docs/manual/reference/bson-types/#date) + * [MongoDB Timestamp documentation](https://www.mongodb.com/docs/manual/reference/bson-types/#timestamps) + * [Spark DateType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/DateType.html) + * [Spark TimestampType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/TimestampType.html) + +* **[2]** MongoDB `Date` type has precision up to milliseconds (`23:59:59.999`). Inserting data with microsecond precision (`23:59:59.999999`) will lead to **throwing away microseconds**. + +### String types + +Note: fields of deprecated MongoDB type `Symbol` are excluded during read. + +| MongoDB type (read) | Spark type | MongoDB type (write) | +|-----------------------|----------------|------------------------| +| `String` | `StringType()` | `String` | +| `Code` | | | +| `RegExp` | | | + +### Binary types + +| MongoDB type (read) | Spark type | MongoDB type (write) | +|-----------------------|-----------------|------------------------| +| `Boolean` | `BooleanType()` | `Boolean` | +| `Binary` | `BinaryType()` | `Binary` | + +### Struct types + +| MongoDB type (read) | Spark type | MongoDB type (write) | +|-----------------------|---------------------|------------------------| +| `Array[T]` | `ArrayType(T)` | `Array[T]` | +| `Object[...]` | `StructType([...])` | `Object[...]` | +| `-` | `MapType(...)` | | + +### Special types + +| MongoDB type (read) | Spark type | MongoDB type (write) | +|-----------------------|-------------------------------------------------------|-------------------------------------| +| `ObjectId` | `StringType()` | `String` | +| `MaxKey` | | | +| `MinKey` | | | +| `Null` | `NullType()` | `Null` | +| `Undefined` | | | +| `DBRef` | `StructType([$ref: StringType(), $id: StringType()])` | `Object[$ref: String, $id: String]` | + +## Explicit type cast + +### `DBReader` + +Currently it is not possible to cast field types using `DBReader`. But this can be done using `MongoDB.pipeline`. + +### `MongoDB.pipeline` + +You can use `$project` aggregation to cast field types: + +```python +from pyspark.sql.types import IntegerType, StructField, StructType + +from onetl.connection import MongoDB +from onetl.db import DBReader + +mongodb = MongoDB(...) + +df = mongodb.pipeline( + collection="my_collection", + pipeline=[ + { + "$project": { + # convert unsupported_field to string + "unsupported_field_str": { + "$convert": { + "input": "$unsupported_field", + "to": "string", + }, + }, + # skip unsupported_field from result + "unsupported_field": 0, + } + } + ], +) + +# cast field content to proper Spark type +df = df.select( + df.id, + df.supported_field, + # explicit cast + df.unsupported_field_str.cast("integer").alias("parsed_integer"), +) +``` + +### `DBWriter` + +Convert dataframe field to string on Spark side, and then write it to MongoDB: + +```python +df = df.select( + df.id, + df.unsupported_field.cast("string").alias("array_field_json"), +) + +writer.run(df) +``` diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mongodb/write.md b/mddocs/docs/_build/markdown/connection/db_connection/mongodb/write.md new file mode 100644 index 000000000..14703402f --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mongodb/write.md @@ -0,0 +1,33 @@ + + +# Writing to MongoDB using `DBWriter` + +For writing data to MongoDB, use `DBWriter`. + +#### WARNING +Please take into account [MongoDB <-> Spark type mapping](types.md#mongodb-types) + +## Examples + +```python +from onetl.connection import MongoDB +from onetl.db import DBWriter + +mongodb = MongoDB(...) + +df = ... # data is here + +writer = DBWriter( + connection=mongodb, + target="schema.table", + options=MongoDB.WriteOptions( + if_exists="append", + ), +) + +writer.run(df) +``` + +## Write options + +Method above accepts `MongoDB.WriteOptions` diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mssql/connection.md b/mddocs/docs/_build/markdown/connection/db_connection/mssql/connection.md new file mode 100644 index 000000000..9148ad994 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mssql/connection.md @@ -0,0 +1,3 @@ + + +# MSSQL connection diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mssql/execute.md b/mddocs/docs/_build/markdown/connection/db_connection/mssql/execute.md new file mode 100644 index 000000000..f0f40a5f6 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mssql/execute.md @@ -0,0 +1,91 @@ + + +# Executing statements in MSSQL + +#### WARNING +Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + +Do **NOT** use them to read large amounts of data. Use [DBReader](read.md#mssql-read) or [MSSQL.sql](sql.md#mssql-sql) instead. + +## How to + +There are 2 ways to execute some statement in MSSQL + +### Use `MSSQL.fetch` + +Use this method to perform some `SELECT` query which returns **small number or rows**, like reading +MSSQL config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts `MSSQL.FetchOptions`. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### WARNING +Please take into account [MSSQL <-> Spark type mapping](types.md#mssql-types). + +#### Syntax support + +This method supports **any** query syntax supported by MSSQL, like: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ✅︎ `SELECT func(arg1, arg2) FROM DUAL` - call function +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import MSSQL + +mssql = MSSQL(...) + +df = mssql.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=MSSQL.FetchOptions(queryTimeout=10), +) +mssql.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `MSSQL.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts `MSSQL.ExecuteOptions`. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by MSSQL, like: + +* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...` +* ✅︎ `ALTER ...` +* ✅︎ `INSERT INTO ... AS SELECT ...` +* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +* ✅︎ `EXEC procedure(arg1, arg2) ...` or `{call procedure(arg1, arg2)}` - special syntax for calling procedure +* ✅︎ `DECLARE ... BEGIN ... END` - execute PL/SQL statement +* ✅︎ other statements not mentioned here +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import MSSQL + +mssql = MSSQL(...) + +mssql.execute("DROP TABLE schema.table") +mssql.execute( + """ + CREATE TABLE schema.table ( + id bigint GENERATED ALWAYS AS IDENTITY, + key VARCHAR2(4000), + value NUMBER + ) + """, + options=MSSQL.ExecuteOptions(queryTimeout=10), +) +``` + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mssql/index.md b/mddocs/docs/_build/markdown/connection/db_connection/mssql/index.md new file mode 100644 index 000000000..a35b4acbb --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mssql/index.md @@ -0,0 +1,19 @@ + + +# MSSQL + +# Connection + +* [Prerequisites](prerequisites.md) +* [MSSQL connection](connection.md) + +# Operations + +* [Reading from MSSQL using `DBReader`](read.md) +* [Reading from MSSQL using `MSSQL.sql`](sql.md) +* [Writing to MSSQL using `DBWriter`](write.md) +* [Executing statements in MSSQL](execute.md) + +# Troubleshooting + +* [MSSQL <-> Spark type mapping](types.md) diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mssql/prerequisites.md b/mddocs/docs/_build/markdown/connection/db_connection/mssql/prerequisites.md new file mode 100644 index 000000000..e103ced3e --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mssql/prerequisites.md @@ -0,0 +1,78 @@ + + +# Prerequisites + +## Version Compatibility + +* SQL Server versions: + : * Officially declared: 2016 - 2022 + * Actually tested: 2017, 2022 +* Spark versions: 2.3.x - 3.5.x +* Java versions: 8 - 20 + +See [official documentation](https://learn.microsoft.com/en-us/sql/connect/jdbc/system-requirements-for-the-jdbc-driver) +and [official compatibility matrix](https://learn.microsoft.com/en-us/sql/connect/jdbc/microsoft-jdbc-driver-for-sql-server-support-matrix). + +## Installing PySpark + +To use MSSQL connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Connecting to MSSQL + +### Connection port + +Connection is usually performed to port 1433. Port may differ for different MSSQL instances. +Please ask your MSSQL administrator to provide required information. + +For named MSSQL instances (`instanceName` option), [port number is optional](https://learn.microsoft.com/en-us/sql/connect/jdbc/building-the-connection-url?view=sql-server-ver16#named-and-multiple-sql-server-instances), and could be omitted. + +### Connection host + +It is possible to connect to MSSQL by using either DNS name of host or it’s IP address. + +If you’re using MSSQL cluster, it is currently possible to connect only to **one specific node**. +Connecting to multiple nodes to perform load balancing, as well as automatic failover to new master/replica are not supported. + +### Required grants + +Ask your MSSQL cluster administrator to set following grants for a user, +used for creating a connection: + +Read + Write (schema is owned by user) + +```sql +-- allow creating tables for user +GRANT CREATE TABLE TO username; + +-- allow read & write access to specific table +GRANT SELECT, INSERT ON username.mytable TO username; + +-- only if if_exists="replace_entire_table" is used: +-- allow dropping/truncating tables in any schema +GRANT ALTER ON username.mytable TO username; +``` + +Read + Write (schema is not owned by user) + +```sql +-- allow creating tables for user +GRANT CREATE TABLE TO username; + +-- allow managing tables in specific schema, and inserting data to tables +GRANT ALTER, SELECT, INSERT ON SCHEMA::someschema TO username; +``` + +Read only + +```sql +-- allow read access to specific table +GRANT SELECT ON someschema.mytable TO username; +``` + +More details can be found in official documentation: +: * [GRANT ON DATABASE](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-database-permissions-transact-sql) + * [GRANT ON OBJECT](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-object-permissions-transact-sql) + * [GRANT ON SCHEMA](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-schema-permissions-transact-sql) diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mssql/read.md b/mddocs/docs/_build/markdown/connection/db_connection/mssql/read.md new file mode 100644 index 000000000..2572b365c --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mssql/read.md @@ -0,0 +1,78 @@ + + +# Reading from MSSQL using `DBReader` + +`DBReader` supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, +but does not support custom queries, like `JOIN`. + +#### WARNING +Please take into account [MSSQL <-> Spark type mapping](types.md#mssql-types) + +## Supported DBReader features + +* ✅︎ `columns` +* ✅︎ `where` +* ✅︎ `hwm`, supported strategies: +* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) +* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) +* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) +* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) +* ❌ `hint` (MSSQL does support hints, but DBReader not, at least for now) +* ❌ `df_schema` +* ✅︎ `options` (see `MSSQL.ReadOptions`) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import MSSQL +from onetl.db import DBReader + +mssql = MSSQL(...) + +reader = DBReader( + connection=mssql, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + options=MSSQL.ReadOptions(partitionColumn="id", numPartitions=10), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import MSSQL +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +mssql = MSSQL(...) + +reader = DBReader( + connection=mssql, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="mssql_hwm", expression="updated_dt"), + options=MSSQL.ReadOptions(partitionColumn="id", numPartitions=10), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from MSSQL to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from MSSQL to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mssql/sql.md b/mddocs/docs/_build/markdown/connection/db_connection/mssql/sql.md new file mode 100644 index 000000000..c0a1ce365 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mssql/sql.md @@ -0,0 +1,62 @@ + + +# Reading from MSSQL using `MSSQL.sql` + +`MSSQL.sql` allows passing custom SQL query, but does not support incremental strategies. + +#### WARNING +Please take into account [MSSQL <-> Spark type mapping](types.md#mssql-types) + +#### WARNING +Statement is executed in **read-write** connection, so if you’re calling some functions/procedures with DDL/DML statements inside, +they can change data in your database. + +## Syntax support + +Only queries with the following syntax are supported: + +* ✅︎ `SELECT ... FROM ...` +* ❌ `WITH alias AS (...) SELECT ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import MSSQL + +mssql = MSSQL(...) +df = mssql.sql( + """ + SELECT + id, + key, + CAST(value AS text) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """, + options=MSSQL.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from MSSQL to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from MSSQL to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mssql/types.md b/mddocs/docs/_build/markdown/connection/db_connection/mssql/types.md new file mode 100644 index 000000000..5981107ad --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mssql/types.md @@ -0,0 +1,281 @@ + + +# MSSQL <-> Spark type mapping + +#### NOTE +The results below are valid for Spark 3.5.5, and may differ on other Spark versions. + +## Type detection & casting + +Spark’s DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from MSSQL + +This is how MSSQL connector performs this: + +* For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and MSSQL type. +* Find corresponding `MSSQL type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* Create DataFrame from query with specific column names and Spark types. + +### Writing to some existing MSSQL table + +This is how MSSQL connector performs this: + +* Get names of columns in DataFrame. [1](#id3) +* Perform `SELECT * FROM table LIMIT 0` query. +* Take only columns present in DataFrame (by name, case insensitive). For each found column get MSSQL type. +* Find corresponding `Spark type` → `MSSQL type (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* If `MSSQL type (write)` match `MSSQL type (read)`, no additional casts will be performed, DataFrame column will be written to MSSQL as is. +* If `MSSQL type (write)` does not match `MSSQL type (read)`, DataFrame column will be casted to target column type **on MSSQL side**. + For example, you can write column with text data to `int` column, if column contains valid integer values within supported value range and precision [2](#id4). + +* **[1]** This allows to write data to tables with `DEFAULT` and `GENERATED` columns - if DataFrame has no such column, it will be populated by MSSQL. +* **[2]** This is true only if DataFrame column is a `StringType()`, because text value is parsed automatically to target column type. But other types cannot be silently converted, like `int -> text`. This requires explicit casting, see [DBWriter](). + +### Create new table using Spark + +#### WARNING +ABSOLUTELY NOT RECOMMENDED! + +This is how MSSQL connector performs this: + +* Find corresponding `Spark type` → `MSSQL type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* Generate DDL for creating table in MSSQL, like `CREATE TABLE (col1 ...)`, and run it. +* Write DataFrame to created table as is. + +But some cases this may lead to using wrong column type. For example, Spark creates column of type `timestamp` +which corresponds to MSSQL’s type `timestamp(0)` (precision up to seconds) +instead of more precise `timestamp(6)` (precision up to nanoseconds). +This may lead to incidental precision loss, or sometimes data cannot be written to created table at all. + +So instead of relying on Spark to create tables: + +### See example + +```python +writer = DBWriter( + connection=mssql, + target="myschema.target_tbl", + options=MSSQL.WriteOptions( + if_exists="append", + ), +) +writer.run(df) +``` + +Always prefer creating tables with specific types **BEFORE WRITING DATA**: + +### See example + +```python +mssql.execute( + """ + CREATE TABLE schema.table ( + id bigint, + key text, + value datetime2(6) -- specific type and precision + ) + """, +) + +writer = DBWriter( + connection=mssql, + target="myschema.target_tbl", + options=MSSQL.WriteOptions(if_exists="append"), +) +writer.run(df) +``` + +### References + +Here you can find source code with type conversions: + +* [MSSQL -> JDBC](https://github.com/microsoft/mssql-jdbc/blob/v12.2.0/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSetMetaData.java#L117-L170) +* [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MsSqlServerDialect.scala#L135-L152) +* [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MsSqlServerDialect.scala#L154-L163) +* [JDBC -> MSSQL](https://github.com/microsoft/mssql-jdbc/blob/v12.2.0/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java#L625-L676) + +## Supported types + +See [official documentation](https://learn.microsoft.com/en-us/sql/t-sql/data-types/data-types-transact-sql) + +### Numeric types + +| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | +|-----------------------------|---------------------------------|-----------------------------|-----------------------------| +| `decimal` | `DecimalType(P=18, S=0)` | `decimal(P=18, S=0)` | `decimal(P=18, S=0)` | +| `decimal(P=0..38)` | `DecimalType(P=0..38, S=0)` | `decimal(P=0..38, S=0)` | `decimal(P=0..38, S=0)` | +| `decimal(P=0..38, S=0..38)` | `DecimalType(P=0..38, S=0..38)` | `decimal(P=0..38, S=0..38)` | `decimal(P=0..38, S=0..38)` | +| `real` | `FloatType()` | `real` | `real` | +| `float` | `DoubleType()` | `float` | `float` | +| `smallint` | `ShortType()` | `smallint` | `smallint` | +| `tinyint` | `IntegerType()` | `int` | `int` | +| `int` | | | | +| `bigint` | `LongType()` | `bigint` | `bigint` | + +### Temporal types + +#### NOTE +MSSQL `timestamp` type is alias for `rowversion` (see [Special types]()). It is not a temporal type! + +| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | +|-------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------|------------------------------------------------------------------------| +| `date` | `DateType()` | `date` | `date` | +| `smalldatetime`, minutes | `TimestampType()`, microseconds | `datetime2(6)`, microseconds | `datetime`, milliseconds | +| `datetime`, milliseconds | | | | +| `datetime2(0)`, seconds | | | | +| `datetime2(3)`, milliseconds | | | | +| `datetime2(6)`, microseconds | `TimestampType()`, microseconds | `datetime2(6)`, microseconds | `datetime`, milliseconds,
**precision loss** [3](#id14) | +| `datetime2(7)`, 100s of nanoseconds | `TimestampType()`, microseconds,
**precision loss** [4](#id15) | `datetime2(6)`, microseconds,
**precision loss** [4](#id15) | | +| `time(0)`, seconds | `TimestampType()`, microseconds,
with time format quirks [5](#id16) | `datetime2(6)`, microseconds | `datetime`, milliseconds | +| `time(3)`, milliseconds | | | | +| `time(6)`, microseconds | `TimestampType()`, microseconds,
with time format quirks [5](#id16) | `datetime2(6)`, microseconds | `datetime`, milliseconds,
**precision loss** [3](#id14) | +| | | | | +| `time`, 100s of nanoseconds | `TimestampType()`, microseconds,
**precision loss** [4](#id15),
with time format quirks [5](#id16) | `datetime2(6)`, microseconds
**precision loss** [3](#id14) | | +| `time(7)`, 100s of nanoseconds | | | | +| `datetimeoffset` | `StringType()` | `nvarchar` | `nvarchar` | + +#### WARNING +Note that types in MSSQL and Spark have different value ranges: + +| MySQL type | Min value | Max value | Spark type | Min value | Max value | +|-----------------|------------------------------|------------------------------|-------------------|------------------------------|------------------------------| +| `smalldatetime` | `1900-01-01 00:00:00` | `2079-06-06 23:59:00` | `TimestampType()` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | +| `datetime` | `1753-01-01 00:00:00.000` | `9999-12-31 23:59:59.997` | | | | +| `datetime2` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | | | | +| `time` | `00:00:00.0000000` | `23:59:59.9999999` | | | | + +So not all of values in Spark DataFrame can be written to MSSQL. + +References: +: * [MSSQL date & time types documentation](https://learn.microsoft.com/en-us/sql/t-sql/data-types/date-and-time-types) + * [Spark DateType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/DateType.html) + * [Spark TimestampType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/TimestampType.html) + +* **[3]** MSSQL dialect for Spark generates DDL with type `datetime` which has precision up to milliseconds (`23:59:59.999`, 10-3 seconds). Inserting data with microsecond and higher precision (`23:59:59.999999` .. `23.59:59.9999999`, 10-6 .. 10-7 seconds) will lead to **throwing away microseconds**. +* **[4]** MSSQL support timestamp up to 100s of nanoseconds precision (`23:59:59.9999999999`, 10-7 seconds), but Spark `TimestampType()` supports datetime up to microseconds precision (`23:59:59.999999`, 10-6 seconds). Last digit will be lost during read or write operations. +* **[5]** `time` type is the same as `datetime2` with date `1970-01-01`. So instead of reading data from MSSQL like `23:59:59.999999` it is actually read `1970-01-01 23:59:59.999999`, and vice versa. + +### String types + +| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | +|---------------------|----------------|----------------------|-----------------------| +| `char` | `StringType()` | `nvarchar` | `nvarchar` | +| `char(N)` | | | | +| `nchar` | | | | +| `nchar(N)` | | | | +| `varchar` | | | | +| `varchar(N)` | | | | +| `nvarchar` | | | | +| `nvarchar(N)` | | | | +| `mediumtext` | | | | +| `text` | | | | +| `ntext` | | | | +| `xml` | | | | + +### Binary types + +| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | +|---------------------|-----------------|----------------------|-----------------------| +| `bit` | `BooleanType()` | `bit` | `bit` | +| `binary` | `BinaryType()` | `varbinary` | `varbinary` | +| `binary(N)` | | | | +| `varbinary` | | | | +| `varbinary(N)` | | | | +| `image` | | | | + +### Special types + +| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | +|---------------------|----------------|----------------------|-----------------------| +| `geography` | `BinaryType()` | `varbinary` | `varbinary` | +| `geometry` | | | | +| `hierarchyid` | | | | +| `rowversion` | | | | +| `sql_variant` | unsupported | | | +| `sysname` | `StringType()` | `nvarchar` | `nvarchar` | +| `uniqueidentifier` | | | | + +## Explicit type cast + +### `DBReader` + +It is possible to explicitly cast column type using `DBReader(columns=...)` syntax. + +For example, you can use `CAST(column AS text)` to convert data to string representation on MSSQL side, and so it will be read as Spark’s `StringType()`: + +```python +from onetl.connection import MSSQL +from onetl.db import DBReader + +mssql = MSSQL(...) + +DBReader( + connection=mssql, + columns=[ + "id", + "supported_column", + "CAST(unsupported_column AS text) unsupported_column_str", + ], +) +df = reader.run() + +# cast column content to proper Spark type +df = df.select( + df.id, + df.supported_column, + # explicit cast + df.unsupported_column_str.cast("integer").alias("parsed_integer"), +) +``` + +### `DBWriter` + +Convert dataframe column to JSON using [to_json](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.functions.to_json.html), +and write it as `text` column in MSSQL: + +```python +mssql.execute( + """ + CREATE TABLE schema.target_tbl ( + id bigint, + struct_column_json text -- any string type, actually + ) + """, +) + +from pyspark.sql.functions import to_json + +df = df.select( + df.id, + to_json(df.struct_column).alias("struct_column_json"), +) + +writer.run(df) +``` + +Then you can parse this column on MSSQL side - for example, by creating a view: + +```sql +SELECT + id, + JSON_VALUE(struct_column_json, "$.nested.field") AS nested_field +FROM target_tbl +``` + +Or by using [computed column](https://learn.microsoft.com/en-us/sql/relational-databases/tables/specify-computed-columns-in-a-table): + +```sql +CREATE TABLE schema.target_table ( + id bigint, + supported_column datetime2(6), + struct_column_json text, -- any string type, actually + -- computed column + nested_field AS (JSON_VALUE(struct_column_json, "$.nested.field")) + -- or persisted column + -- nested_field AS (JSON_VALUE(struct_column_json, "$.nested.field")) PERSISTED +) +``` + +By default, column value is calculated on every table read. +Column marked as `PERSISTED` is calculated during insert, but this require additional space. diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mssql/write.md b/mddocs/docs/_build/markdown/connection/db_connection/mssql/write.md new file mode 100644 index 000000000..cae7470e8 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mssql/write.md @@ -0,0 +1,38 @@ + + +# Writing to MSSQL using `DBWriter` + +For writing data to MSSQL, use `DBWriter`. + +#### WARNING +Please take into account [MSSQL <-> Spark type mapping](types.md#mssql-types) + +#### WARNING +It is always recommended to create table explicitly using [MSSQL.execute](execute.md#mssql-execute) +instead of relying on Spark’s table DDL generation. + +This is because Spark’s DDL generator can create columns with different precision and types than it is expected, +causing precision loss or other issues. + +## Examples + +```python +from onetl.connection import MSSQL +from onetl.db import DBWriter + +mssql = MSSQL(...) + +df = ... # data is here + +writer = DBWriter( + connection=mssql, + target="schema.table", + options=MSSQL.WriteOptions(if_exists="append"), +) + +writer.run(df) +``` + +## Options + +Method above accepts `MSSQL.WriteOptions` diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mysql/connection.md b/mddocs/docs/_build/markdown/connection/db_connection/mysql/connection.md new file mode 100644 index 000000000..3cfb0cc04 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mysql/connection.md @@ -0,0 +1,3 @@ + + +# MySQL connection diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mysql/execute.md b/mddocs/docs/_build/markdown/connection/db_connection/mysql/execute.md new file mode 100644 index 000000000..de7c953b0 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mysql/execute.md @@ -0,0 +1,92 @@ + + +# Executing statements in MySQL + +#### WARNING +Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + +Do **NOT** use them to read large amounts of data. Use [DBReader](read.md#mysql-read) or [MySQL.sql](sql.md#mysql-sql) instead. + +## How to + +There are 2 ways to execute some statement in MySQL + +### Use `MySQL.fetch` + +Use this method to perform some `SELECT` query which returns **small number or rows**, like reading +MySQL config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts `MySQL.FetchOptions`. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### WARNING +Please take into account [MySQL <-> Spark type mapping](types.md#mysql-types). + +#### Syntax support + +This method supports **any** query syntax supported by MySQL, like: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ✅︎ `SELECT func(arg1, arg2)` or `{?= call func(arg1, arg2)}` - special syntax for calling function +* ✅︎ `SHOW ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import MySQL + +mysql = MySQL(...) + +df = mysql.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=MySQL.FetchOptions(queryTimeout=10), +) +mysql.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `MySQL.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts `MySQL.ExecuteOptions`. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by MySQL, like: + +* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +* ✅︎ `ALTER ...` +* ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, and so on +* ✅︎ `CALL procedure(arg1, arg2) ...` or `{call procedure(arg1, arg2)}` - special syntax for calling procedure +* ✅︎ other statements not mentioned here +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import MySQL + +mysql = MySQL(...) + +mysql.execute("DROP TABLE schema.table") +mysql.execute( + """ + CREATE TABLE schema.table ( + id bigint, + key text, + value float + ) + ENGINE = InnoDB + """, + options=MySQL.ExecuteOptions(queryTimeout=10), +) +``` + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mysql/index.md b/mddocs/docs/_build/markdown/connection/db_connection/mysql/index.md new file mode 100644 index 000000000..6722ca77d --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mysql/index.md @@ -0,0 +1,19 @@ + + +# MySQL + +# Connection + +* [Prerequisites](prerequisites.md) +* [MySQL connection](connection.md) + +# Operations + +* [Reading from MySQL using `DBReader`](read.md) +* [Reading from MySQL using `MySQL.sql`](sql.md) +* [Writing to MySQL using `DBWriter`](write.md) +* [Executing statements in MySQL](execute.md) + +# Troubleshooting + +* [MySQL <-> Spark type mapping](types.md) diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mysql/prerequisites.md b/mddocs/docs/_build/markdown/connection/db_connection/mysql/prerequisites.md new file mode 100644 index 000000000..d0e551443 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mysql/prerequisites.md @@ -0,0 +1,61 @@ + + +# Prerequisites + +## Version Compatibility + +* MySQL server versions: + : * Officially declared: 8.0 - 9.2 + * Actually tested: 5.7.13, 9.2.0 +* Spark versions: 2.3.x - 3.5.x +* Java versions: 8 - 20 + +See [official documentation](https://dev.mysql.com/doc/connector-j/en/connector-j-versions.html). + +## Installing PySpark + +To use MySQL connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Connecting to MySQL + +### Connection host + +It is possible to connect to MySQL by using either DNS name of host or it’s IP address. + +If you’re using MySQL cluster, it is currently possible to connect only to **one specific node**. +Connecting to multiple nodes to perform load balancing, as well as automatic failover to new master/replica are not supported. + +### Connection port + +Connection is usually performed to port 3306. Port may differ for different MySQL instances. +Please ask your MySQL administrator to provide required information. + +### Required grants + +Ask your MySQL cluster administrator to set following grants for a user, +used for creating a connection: + +Read + Write + +```sql +-- allow creating tables in the target schema +GRANT CREATE ON myschema.* TO username@'192.168.1.%'; + +-- allow read & write access to specific table +GRANT SELECT, INSERT ON myschema.mytable TO username@'192.168.1.%'; +``` + +Read only + +```sql +-- allow read access to specific table +GRANT SELECT ON myschema.mytable TO username@'192.168.1.%'; +``` + +In example above `'192.168.1.%''` is a network subnet `192.168.1.0 - 192.168.1.255` +where Spark driver and executors are running. To allow connecting user from any IP, use `'%'` (not secure!). + +More details can be found in [official documentation](https://dev.mysql.com/doc/refman/en/grant.html). diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mysql/read.md b/mddocs/docs/_build/markdown/connection/db_connection/mysql/read.md new file mode 100644 index 000000000..85204c1da --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mysql/read.md @@ -0,0 +1,80 @@ + + +# Reading from MySQL using `DBReader` + +`DBReader` supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, +but does not support custom queries, like `JOIN`. + +#### WARNING +Please take into account [MySQL <-> Spark type mapping](types.md#mysql-types) + +## Supported DBReader features + +* ✅︎ `columns` +* ✅︎ `where` +* ✅︎ `hwm`, supported strategies: +* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) +* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) +* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) +* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) +* ✅︎ `hint` (see [official documentation](https://dev.mysql.com/doc/refman/en/optimizer-hints.html)) +* ❌ `df_schema` +* ✅︎ `options` (see `MySQL.ReadOptions`) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import MySQL +from onetl.db import DBReader + +mysql = MySQL(...) + +reader = DBReader( + connection=mysql, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + hint="SKIP_SCAN(schema.table key_index)", + options=MySQL.ReadOptions(partitionColumn="id", numPartitions=10), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import MySQL +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +mysql = MySQL(...) + +reader = DBReader( + connection=mysql, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + hint="SKIP_SCAN(schema.table key_index)", + hwm=DBReader.AutoDetectHWM(name="mysql_hwm", expression="updated_dt"), + options=MySQL.ReadOptions(partitionColumn="id", numPartitions=10), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Oracle to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Oracle to Spark, and may also improve performance of the query. +Especially if there are indexes for columns used in `where` clause. + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mysql/sql.md b/mddocs/docs/_build/markdown/connection/db_connection/mysql/sql.md new file mode 100644 index 000000000..c4f4f8bbf --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mysql/sql.md @@ -0,0 +1,63 @@ + + +# Reading from MySQL using `MySQL.sql` + +`MySQL.sql` allows passing custom SQL query, but does not support incremental strategies. + +#### WARNING +Please take into account [MySQL <-> Spark type mapping](types.md#mysql-types) + +#### WARNING +Statement is executed in **read-write** connection, so if you’re calling some functions/procedures with DDL/DML statements inside, +they can change data in your database. + +## Syntax support + +Only queries with the following syntax are supported: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ❌ `SHOW ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import MySQL + +mysql = MySQL(...) +df = mysql.sql( + """ + SELECT + id, + key, + CAST(value AS text) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """, + options=MySQL.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from MySQL to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from MySQL to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mysql/types.md b/mddocs/docs/_build/markdown/connection/db_connection/mysql/types.md new file mode 100644 index 000000000..6e49eb2a4 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mysql/types.md @@ -0,0 +1,292 @@ + + +# MySQL <-> Spark type mapping + +#### NOTE +The results below are valid for Spark 3.5.5, and may differ on other Spark versions. + +## Type detection & casting + +Spark’s DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from MySQL + +This is how MySQL connector performs this: + +* For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and MySQL type. +* Find corresponding `MySQL type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* Create DataFrame from query with specific column names and Spark types. + +### Writing to some existing MySQL table + +This is how MySQL connector performs this: + +* Get names of columns in DataFrame. [1](#id2) +* Perform `SELECT * FROM table LIMIT 0` query. +* Take only columns present in DataFrame (by name, case insensitive). For each found column get MySQL type. +* Find corresponding `Spark type` → `MySQL type (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* If `MySQL type (write)` match `MySQL type (read)`, no additional casts will be performed, DataFrame column will be written to MySQL as is. +* If `MySQL type (write)` does not match `MySQL type (read)`, DataFrame column will be casted to target column type **on MySQL side**. For example, you can write column with text data to `int` column, if column contains valid integer values within supported value range and precision. + +* **[1]** This allows to write data to tables with `DEFAULT` and `GENERATED` columns - if DataFrame has no such column, it will be populated by MySQL. + +### Create new table using Spark + +#### WARNING +ABSOLUTELY NOT RECOMMENDED! + +This is how MySQL connector performs this: + +* Find corresponding `Spark type` → `MySQL type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* Generate DDL for creating table in MySQL, like `CREATE TABLE (col1 ...)`, and run it. +* Write DataFrame to created table as is. + +But some cases this may lead to using wrong column type. For example, Spark creates column of type `timestamp` +which corresponds to MySQL type `timestamp(0)` (precision up to seconds) +instead of more precise `timestamp(6)` (precision up to nanoseconds). +This may lead to incidental precision loss, or sometimes data cannot be written to created table at all. + +So instead of relying on Spark to create tables: + +### See example + +```python +writer = DBWriter( + connection=mysql, + target="myschema.target_tbl", + options=MySQL.WriteOptions( + if_exists="append", + createTableOptions="ENGINE = InnoDB", + ), +) +writer.run(df) +``` + +Always prefer creating tables with specific types **BEFORE WRITING DATA**: + +### See example + +```python +mysql.execute( + """ + CREATE TABLE schema.table ( + id bigint, + key text, + value timestamp(6) -- specific type and precision + ) + ENGINE = InnoDB + """, +) + +writer = DBWriter( + connection=mysql, + target="myschema.target_tbl", + options=MySQL.WriteOptions(if_exists="append"), +) +writer.run(df) +``` + +### References + +Here you can find source code with type conversions: + +* [MySQL -> JDBC](https://github.com/mysql/mysql-connector-j/blob/8.0.33/src/main/core-api/java/com/mysql/cj/MysqlType.java#L44-L623) +* [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MySQLDialect.scala#L104-L132) +* [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MySQLDialect.scala#L204-L211) +* [JDBC -> MySQL](https://github.com/mysql/mysql-connector-j/blob/8.0.33/src/main/core-api/java/com/mysql/cj/MysqlType.java#L625-L867) + +## Supported types + +See [official documentation](https://dev.mysql.com/doc/refman/en/data-types.html) + +### Numeric types + +| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | +|-----------------------------|----------------------------------|-----------------------------|-----------------------------| +| `decimal` | `DecimalType(P=10, S=0)` | `decimal(P=10, S=0)` | `decimal(P=10, S=0)` | +| `decimal(P=0..38)` | `DecimalType(P=0..38, S=0)` | `decimal(P=0..38, S=0)` | `decimal(P=0..38, S=0)` | +| `decimal(P=0..38, S=0..30)` | `DecimalType(P=0..38, S=0..30)` | `decimal(P=0..38, S=0..30)` | `decimal(P=0..38, S=0..30)` | +| `decimal(P=39..65, S=...)` | unsupported [2](#id4) | | | +| `float` | `DoubleType()` | `double` | `double` | +| `double` | | | | +| `tinyint` | `IntegerType()` | `int` | `int` | +| `smallint` | | | | +| `mediumint` | | | | +| `int` | | | | +| `bigint` | `LongType()` | `bigint` | `bigint` | +* **[2]** MySQL support decimal types with precision `P` up to 65. But Spark’s `DecimalType(P, S)` supports maximum `P=38`. It is impossible to read, write or operate with values of larger precision, this leads to an exception. + +### Temporal types + +| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | +|------------------------------|------------------------------------------------------------------------------------|------------------------------|-----------------------------------------------------------------------| +| `year` | `DateType()` | `date` | `date` | +| `date` | | | | +| `datetime`, seconds | `TimestampType()`, microseconds | `timestamp(6)`, microseconds | `timestamp(0)`, seconds | +| `timestamp`, seconds | | | | +| `datetime(0)`, seconds | | | | +| `timestamp(0)`, seconds | | | | +| `datetime(3)`, milliseconds | `TimestampType()`, microseconds | `timestamp(6)`, microseconds | `timestamp(0)`, seconds,
**precision loss** [4](#id9), | +| `timestamp(3)`, milliseconds | | | | +| `datetime(6)`, microseconds | | | | +| `timestamp(6)`, microseconds | | | | +| `time`, seconds | `TimestampType()`, microseconds,
with time format quirks [5](#id10) | `timestamp(6)`, microseconds | `timestamp(0)`, seconds | +| `time(0)`, seconds | | | | +| `time(3)`, milliseconds | `TimestampType()`, microseconds
with time format quirks [5](#id10) | `timestamp(6)`, microseconds | `timestamp(0)`, seconds,
**precision loss** [4](#id9), | +| `time(6)`, microseconds | | | | + +#### WARNING +Note that types in MySQL and Spark have different value ranges: + +| MySQL type | Min value | Max value | Spark type | Min value | Max value | +|--------------|------------------------------|------------------------------|-------------------|------------------------------|------------------------------| +| `year` | `1901` | `2155` | `DateType()` | `0001-01-01` | `9999-12-31` | +| `date` | `1000-01-01` | `9999-12-31` | | | | +| `datetime` | `1000-01-01 00:00:00.000000` | `9999-12-31 23:59:59.499999` | `TimestampType()` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | +| `timestamp` | `1970-01-01 00:00:01.000000` | `9999-12-31 23:59:59.499999` | | | | +| `time` | `-838:59:59.000000` | `838:59:59.000000` | | | | + +So Spark can read all the values from MySQL, but not all of values in Spark DataFrame can be written to MySQL. + +References: +: * [MySQL year documentation](https://dev.mysql.com/doc/refman/en/year.html) + * [MySQL date, datetime & timestamp documentation](https://dev.mysql.com/doc/refman/en/datetime.html) + * [MySQL time documentation](https://dev.mysql.com/doc/refman/en/time.html) + * [Spark DateType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/DateType.html) + * [Spark TimestampType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/TimestampType.html) + +* **[4]** MySQL dialect generates DDL with MySQL type `timestamp` which is alias for `timestamp(0)` with precision up to seconds (`23:59:59`). Inserting data with microseconds precision (`23:59:59.999999`) will lead to **throwing away microseconds**. +* **[5]** `time` type is the same as `timestamp` with date `1970-01-01`. So instead of reading data from MySQL like `23:59:59` it is actually read `1970-01-01 23:59:59`, and vice versa. + +### String types + +| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | +|-----------------------------|----------------|----------------------|-----------------------| +| `char` | `StringType()` | `longtext` | `longtext` | +| `char(N)` | | | | +| `varchar(N)` | | | | +| `mediumtext` | | | | +| `text` | | | | +| `longtext` | | | | +| `json` | | | | +| `enum("val1", "val2", ...)` | | | | +| `set("val1", "val2", ...)` | | | | + +### Binary types + +| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | +|---------------------|----------------|----------------------|-----------------------| +| `binary` | `BinaryType()` | `blob` | `blob` | +| `binary(N)` | | | | +| `varbinary(N)` | | | | +| `mediumblob` | | | | +| `blob` | | | | +| `longblob` | | | | + +### Geometry types + +| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | +|----------------------|----------------|----------------------|-----------------------| +| `point` | `BinaryType()` | `blob` | `blob` | +| `linestring` | | | | +| `polygon` | | | | +| `geometry` | | | | +| `multipoint` | | | | +| `multilinestring` | | | | +| `multipolygon` | | | | +| `geometrycollection` | | | | + +## Explicit type cast + +### `DBReader` + +It is possible to explicitly cast column type using `DBReader(columns=...)` syntax. + +For example, you can use `CAST(column AS text)` to convert data to string representation on MySQL side, and so it will be read as Spark’s `StringType()`. + +It is also possible to use [JSON_OBJECT](https://dev.mysql.com/doc/refman/en/json.html) MySQL function and parse JSON columns in MySQL with the `JSON.parse_column` method. + +```python +from pyspark.sql.types import IntegerType, StructType, StructField + +from onetl.connection import MySQL +from onetl.db import DBReader +from onetl.file.format import JSON + +mysql = MySQL(...) + +DBReader( + connection=mysql, + columns=[ + "id", + "supported_column", + "CAST(unsupported_column AS text) unsupported_column_str", + # or + "JSON_OBJECT('key', value_column) json_column", + ], +) +df = reader.run() + +json_scheme = StructType([StructField("key", IntegerType())]) + +df = df.select( + df.id, + df.supported_column, + # explicit cast + df.unsupported_column_str.cast("integer").alias("parsed_integer"), + JSON().parse_column("json_column", json_scheme).alias("struct_column"), +) +``` + +### `DBWriter` + +To write JSON data to a `json` or `text` column in a MySQL table, use the `JSON.serialize_column` method. + +```python +from onetl.connection import MySQL +from onetl.db import DBWriter +from onetl.file.format import JSON + +mysql.execute( + """ + CREATE TABLE schema.target_tbl ( + id bigint, + array_column_json json -- any string type, actually + ) + ENGINE = InnoDB + """, +) + +df = df.select( + df.id, + JSON().serialize_column(df.array_column).alias("array_column_json"), +) + +writer.run(df) +``` + +Then you can parse this column on MySQL side - for example, by creating a view: + +```sql +SELECT + id, + array_column_json->"$[0]" AS array_item +FROM target_tbl +``` + +Or by using [GENERATED column](https://dev.mysql.com/doc/refman/en/create-table-generated-columns.html): + +```sql +CREATE TABLE schema.target_table ( + id bigint, + supported_column timestamp, + array_column_json json, -- any string type, actually + -- virtual column + array_item_0 GENERATED ALWAYS AS (array_column_json->"$[0]")) VIRTUAL + -- or stired column + -- array_item_0 GENERATED ALWAYS AS (array_column_json->"$[0]")) STORED +) +``` + +`VIRTUAL` column value is calculated on every table read. +`STORED` column value is calculated during insert, but this require additional space. diff --git a/mddocs/docs/_build/markdown/connection/db_connection/mysql/write.md b/mddocs/docs/_build/markdown/connection/db_connection/mysql/write.md new file mode 100644 index 000000000..aac236e57 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/mysql/write.md @@ -0,0 +1,42 @@ + + +# Writing to MySQL using `DBWriter` + +For writing data to MySQL, use `DBWriter`. + +#### WARNING +Please take into account [MySQL <-> Spark type mapping](types.md#mysql-types) + +#### WARNING +It is always recommended to create table explicitly using [MySQL.execute](execute.md#mysql-execute) +instead of relying on Spark’s table DDL generation. + +This is because Spark’s DDL generator can create columns with different precision and types than it is expected, +causing precision loss or other issues. + +## Examples + +```python +from onetl.connection import MySQL +from onetl.db import DBWriter + +mysql = MySQL(...) + +df = ... # data is here + +writer = DBWriter( + connection=mysql, + target="schema.table", + options=MySQL.WriteOptions( + if_exists="append", + # ENGINE is required by MySQL + createTableOptions="ENGINE = MergeTree() ORDER BY id", + ), +) + +writer.run(df) +``` + +## Options + +Method above accepts `MySQL.WriteOptions` diff --git a/mddocs/docs/_build/markdown/connection/db_connection/oracle/connection.md b/mddocs/docs/_build/markdown/connection/db_connection/oracle/connection.md new file mode 100644 index 000000000..cdbad7c09 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/oracle/connection.md @@ -0,0 +1,3 @@ + + +# Oracle connection diff --git a/mddocs/docs/_build/markdown/connection/db_connection/oracle/execute.md b/mddocs/docs/_build/markdown/connection/db_connection/oracle/execute.md new file mode 100644 index 000000000..92d3c73c9 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/oracle/execute.md @@ -0,0 +1,92 @@ + + +# Executing statements in Oracle + +#### WARNING +Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + +Do **NOT** use them to read large amounts of data. Use [DBReader](read.md#oracle-read) or [Oracle.sql](sql.md#oracle-sql) instead. + +## How to + +There are 2 ways to execute some statement in Oracle + +### Use `Oracle.fetch` + +Use this method to execute some `SELECT` query which returns **small number or rows**, like reading +Oracle config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts `Oracle.FetchOptions`. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### WARNING +Please take into account [Oracle <-> Spark type mapping](types.md#oracle-types). + +#### Syntax support + +This method supports **any** query syntax supported by Oracle, like: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ✅︎ `SELECT func(arg1, arg2) FROM DUAL` - call function +* ✅︎ `SHOW ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Oracle + +oracle = Oracle(...) + +df = oracle.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=Oracle.FetchOptions(queryTimeout=10), +) +oracle.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `Oracle.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts `Oracle.ExecuteOptions`. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Oracle, like: + +* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...` +* ✅︎ `ALTER ...` +* ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +* ✅︎ `CALL procedure(arg1, arg2) ...` or `{call procedure(arg1, arg2)}` - special syntax for calling procedure +* ✅︎ `DECLARE ... BEGIN ... END` - execute PL/SQL statement +* ✅︎ other statements not mentioned here +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Oracle + +oracle = Oracle(...) + +oracle.execute("DROP TABLE schema.table") +oracle.execute( + """ + CREATE TABLE schema.table ( + id bigint GENERATED ALWAYS AS IDENTITY, + key VARCHAR2(4000), + value NUMBER + ) + """, + options=Oracle.ExecuteOptions(queryTimeout=10), +) +``` + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/oracle/index.md b/mddocs/docs/_build/markdown/connection/db_connection/oracle/index.md new file mode 100644 index 000000000..39987fb24 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/oracle/index.md @@ -0,0 +1,19 @@ + + +# Oracle + +# Connection + +* [Prerequisites](prerequisites.md) +* [Oracle connection](connection.md) + +# Operations + +* [Reading from Oracle using `DBReader`](read.md) +* [Reading from Oracle using `Oracle.sql`](sql.md) +* [Writing to Oracle using `DBWriter`](write.md) +* [Executing statements in Oracle](execute.md) + +# Troubleshooting + +* [Oracle <-> Spark type mapping](types.md) diff --git a/mddocs/docs/_build/markdown/connection/db_connection/oracle/prerequisites.md b/mddocs/docs/_build/markdown/connection/db_connection/oracle/prerequisites.md new file mode 100644 index 000000000..6dff632ce --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/oracle/prerequisites.md @@ -0,0 +1,110 @@ + + +# Prerequisites + +## Version Compatibility + +* Oracle Server versions: + : * Officially declared: 19c, 21c, 23ai + * Actually tested: 11.2, 23.5 +* Spark versions: 2.3.x - 3.5.x +* Java versions: 8 - 20 + +See [official documentation](https://www.oracle.com/cis/database/technologies/appdev/jdbc-downloads.html). + +## Installing PySpark + +To use Oracle connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Connecting to Oracle + +### Connection port + +Connection is usually performed to port 1521. Port may differ for different Oracle instances. +Please ask your Oracle administrator to provide required information. + +### Connection host + +It is possible to connect to Oracle by using either DNS name of host or it’s IP address. + +If you’re using Oracle cluster, it is currently possible to connect only to **one specific node**. +Connecting to multiple nodes to perform load balancing, as well as automatic failover to new master/replica are not supported. + +### Connect as proxy user + +It is possible to connect to database as another user without knowing this user password. + +This can be enabled by granting user a special `CONNECT THROUGH` permission: + +```sql +ALTER USER schema_owner GRANT CONNECT THROUGH proxy_user; +``` + +Then you can connect to Oracle using credentials of `proxy_user` but specify that you need permissions of `schema_owner`: + +```python +oracle = Oracle( + ..., + user="proxy_user[schema_owner]", + password="proxy_user password", +) +``` + +See [official documentation](https://oracle-base.com/articles/misc/proxy-users-and-connect-through). + +### Required grants + +Ask your Oracle cluster administrator to set following grants for a user, +used for creating a connection: + +Read + Write (schema is owned by user) + +```sql +-- allow user to log in +GRANT CREATE SESSION TO username; + +-- allow creating tables in user schema +GRANT CREATE TABLE TO username; + +-- allow read & write access to specific table +GRANT SELECT, INSERT ON username.mytable TO username; +``` + +Read + Write (schema is not owned by user) + +```sql +-- allow user to log in +GRANT CREATE SESSION TO username; + +-- allow creating tables in any schema, +-- as Oracle does not support specifying exact schema name +GRANT CREATE ANY TABLE TO username; + +-- allow read & write access to specific table +GRANT SELECT, INSERT ON someschema.mytable TO username; + +-- only if if_exists="replace_entire_table" is used: +-- allow dropping/truncating tables in any schema, +-- as Oracle does not support specifying exact schema name +GRANT DROP ANY TABLE TO username; +``` + +Read only + +```sql +-- allow user to log in +GRANT CREATE SESSION TO username; + +-- allow read access to specific table +GRANT SELECT ON someschema.mytable TO username; +``` + +More details can be found in official documentation: +: * [GRANT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/GRANT.html) + * [SELECT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/SELECT.html) + * [CREATE TABLE](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/SELECT.html) + * [INSERT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/INSERT.html) + * [TRUNCATE TABLE](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/TRUNCATE-TABLE.html) diff --git a/mddocs/docs/_build/markdown/connection/db_connection/oracle/read.md b/mddocs/docs/_build/markdown/connection/db_connection/oracle/read.md new file mode 100644 index 000000000..6b32f12f5 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/oracle/read.md @@ -0,0 +1,80 @@ + + +# Reading from Oracle using `DBReader` + +`DBReader` supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, +but does not support custom queries, like `JOIN`. + +#### WARNING +Please take into account [Oracle <-> Spark type mapping](types.md#oracle-types) + +## Supported DBReader features + +* ✅︎ `columns` +* ✅︎ `where` +* ✅︎ `hwm`, supported strategies: +* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) +* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) +* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) +* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) +* ✅︎ `hint` (see [official documentation](https://docs.oracle.com/cd/B10500_01/server.920/a96533/hintsref.htm)) +* ❌ `df_schema` +* ✅︎ `options` (see `Oracle.ReadOptions`) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Oracle +from onetl.db import DBReader + +oracle = Oracle(...) + +reader = DBReader( + connection=oracle, + source="schema.table", + columns=["id", "key", "CAST(value AS VARCHAR2(4000)) value", "updated_dt"], + where="key = 'something'", + hint="INDEX(schema.table key_index)", + options=Oracle.ReadOptions(partitionColumn="id", numPartitions=10), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Oracle +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +oracle = Oracle(...) + +reader = DBReader( + connection=oracle, + source="schema.table", + columns=["id", "key", "CAST(value AS VARCHAR2(4000)) value", "updated_dt"], + where="key = 'something'", + hint="INDEX(schema.table key_index)", + hwm=DBReader.AutoDetectHWM(name="oracle_hwm", expression="updated_dt"), + options=Oracle.ReadOptions(partitionColumn="id", numPartitions=10), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Oracle to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Oracle to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/oracle/sql.md b/mddocs/docs/_build/markdown/connection/db_connection/oracle/sql.md new file mode 100644 index 000000000..b98dd6952 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/oracle/sql.md @@ -0,0 +1,63 @@ + + +# Reading from Oracle using `Oracle.sql` + +`Oracle.sql` allows passing custom SQL query, but does not support incremental strategies. + +#### WARNING +Please take into account [Oracle <-> Spark type mapping](types.md#oracle-types) + +#### WARNING +Statement is executed in **read-write** connection, so if you’re calling some functions/procedures with DDL/DML statements inside, +they can change data in your database. + +## Syntax support + +Only queries with the following syntax are supported: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ❌ `SHOW ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import Oracle + +oracle = Oracle(...) +df = oracle.sql( + """ + SELECT + id, + key, + CAST(value AS VARCHAR2(4000)) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """, + options=Oracle.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from Oracle to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from Oracle to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/oracle/types.md b/mddocs/docs/_build/markdown/connection/db_connection/oracle/types.md new file mode 100644 index 000000000..9f4f2e13e --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/oracle/types.md @@ -0,0 +1,303 @@ + + +# Oracle <-> Spark type mapping + +#### NOTE +The results below are valid for Spark 3.5.5, and may differ on other Spark versions. + +## Type detection & casting + +Spark’s DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from Oracle + +This is how Oracle connector performs this: + +* For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and Oracle type. +* Find corresponding `Oracle type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* Create DataFrame from query with specific column names and Spark types. + +### Writing to some existing Oracle table + +This is how Oracle connector performs this: + +* Get names of columns in DataFrame. [1](#id3) +* Perform `SELECT * FROM table LIMIT 0` query. +* Take only columns present in DataFrame (by name, case insensitive). For each found column get Clickhouse type. +* **Find corresponding** `Oracle type (read)` → `Spark type` **combination** (see below) for each DataFrame column. If no combination is found, raise exception. [2](#id4) +* Find corresponding `Spark type` → `Oracle type (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* If `Oracle type (write)` match `Oracle type (read)`, no additional casts will be performed, DataFrame column will be written to Oracle as is. +* If `Oracle type (write)` does not match `Oracle type (read)`, DataFrame column will be casted to target column type **on Oracle side**. + For example, you can write column with text data to `int` column, if column contains valid integer values within supported value range and precision. + +* **[1]** This allows to write data to tables with `DEFAULT` and `GENERATED` columns - if DataFrame has no such column, it will be populated by Oracle. +* **[2]** Yes, this is weird. + +### Create new table using Spark + +#### WARNING +ABSOLUTELY NOT RECOMMENDED! + +This is how Oracle connector performs this: + +* Find corresponding `Spark type` → `Oracle type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* Generate DDL for creating table in Oracle, like `CREATE TABLE (col1 ...)`, and run it. +* Write DataFrame to created table as is. + +But Oracle connector support only limited number of types and almost no custom clauses (like `PARTITION BY`, `INDEX`, etc). +So instead of relying on Spark to create tables: + +### See example + +```python +writer = DBWriter( + connection=oracle, + target="public.table", + options=Oracle.WriteOptions(if_exists="append"), +) +writer.run(df) +``` + +Always prefer creating table with desired DDL **BEFORE WRITING DATA**: + +### See example + +```python +oracle.execute( + """ + CREATE TABLE username.table ( + id NUMBER, + business_dt TIMESTAMP(6), + value VARCHAR2(2000) + ) + """, +) + +writer = DBWriter( + connection=oracle, + target="public.table", + options=Oracle.WriteOptions(if_exists="append"), +) +writer.run(df) +``` + +See Oracle [CREATE TABLE](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/SELECT.html) documentation. + +## Supported types + +### References + +See [List of Oracle types](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/Data-Types.html). + +Here you can find source code with type conversions: + +* [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/OracleDialect.scala#L83-L109) +* [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/OracleDialect.scala#L111-L123) + +### Numeric types + +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | +|-----------------------------|----------------------------------|----------------------------|-------------------------| +| `NUMBER` | `DecimalType(P=38, S=10)` | `NUMBER(P=38, S=10)` | `NUMBER(P=38, S=10)` | +| `NUMBER(P=0..38)` | `DecimalType(P=0..38, S=0)` | `NUMBER(P=0..38, S=0)` | `NUMBER(P=38, S=0)` | +| `NUMBER(P=0..38, S=0..38)` | `DecimalType(P=0..38, S=0..38)` | `NUMBER(P=0..38, S=0..38)` | `NUMBER(P=38, S=0..38)` | +| `NUMBER(P=..., S=-127..-1)` | unsupported [3](#id6) | | | +| `FLOAT` | `DecimalType(P=38, S=10)` | `NUMBER(P=38, S=10)` | `NUMBER(P=38, S=10)` | +| `FLOAT(N)` | | | | +| `REAL` | | | | +| `DOUBLE PRECISION` | | | | +| `BINARY_FLOAT` | `FloatType()` | `NUMBER(P=19, S=4)` | `NUMBER(P=19, S=4)` | +| `BINARY_DOUBLE` | `DoubleType()` | | | +| `SMALLINT` | `DecimalType(P=38, S=0)` | `NUMBER(P=38, S=0)` | `NUMBER(P=38, S=0)` | +| `INTEGER` | | | | +| `LONG` | `StringType()` | `CLOB` | `CLOB` | +* **[3]** Oracle support decimal types with negative scale, like `NUMBER(38, -10)`. Spark doesn’t. + +### Temporal types + +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | +|-------------------------------------|------------------------------------------------------------------------------|------------------------------------------------------|------------------------------------------------------| +| `DATE`, days | `TimestampType()`, microseconds | `TIMESTAMP(6)`, microseconds | `TIMESTAMP(6)`, microseconds | +| `TIMESTAMP`, microseconds | `TimestampType()`, microseconds | `TIMESTAMP(6)`, microseconds | `TIMESTAMP(6)`, microseconds | +| `TIMESTAMP(0)`, seconds | | | | +| `TIMESTAMP(3)`, milliseconds | | | | +| `TIMESTAMP(6)`, microseconds | | | | +| `TIMESTAMP(9)`, nanoseconds | `TimestampType()`, microseconds,
**precision loss** [4](#id8) | `TIMESTAMP(6)`, microseconds,
**precision loss** | `TIMESTAMP(6)`, microseconds,
**precision loss** | +| `TIMESTAMP WITH TIME ZONE` | unsupported | | | +| `TIMESTAMP(N) WITH TIME ZONE` | | | | +| `TIMESTAMP WITH LOCAL TIME ZONE` | | | | +| `TIMESTAMP(N) WITH LOCAL TIME ZONE` | | | | +| `INTERVAL YEAR TO MONTH` | | | | +| `INTERVAL DAY TO SECOND` | | | | + +#### WARNING +Note that types in Oracle and Spark have different value ranges: + +| Oracle type | Min value | Max value | Spark type | Min value | Max value | +|---------------|----------------------------------|---------------------------------|-------------------|------------------------------|------------------------------| +| `date` | `-4712-01-01` | `9999-01-01` | `DateType()` | `0001-01-01` | `9999-12-31` | +| `timestamp` | `-4712-01-01 00:00:00.000000000` | `9999-12-31 23:59:59.999999999` | `TimestampType()` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | + +So not all of values can be read from Oracle to Spark. + +References: +: * [Oracle date, timestamp and intervals documentation](https://oracle-base.com/articles/misc/oracle-dates-timestamps-and-intervals#DATE) + * [Spark DateType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/DateType.html) + * [Spark TimestampType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/TimestampType.html) + +* **[4]** Oracle support timestamp up to nanoseconds precision (`23:59:59.999999999`), but Spark `TimestampType()` supports datetime up to microseconds precision (`23:59:59.999999`). Nanoseconds will be lost during read or write operations. + +### String types + +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | +|----------------------|----------------|-----------------------|------------------------| +| `CHAR` | `StringType()` | `CLOB` | `CLOB` | +| `CHAR(N CHAR)` | | | | +| `CHAR(N BYTE)` | | | | +| `NCHAR` | | | | +| `NCHAR(N)` | | | | +| `VARCHAR(N)` | | | | +| `LONG VARCHAR` | | | | +| `VARCHAR2(N CHAR)` | | | | +| `VARCHAR2(N BYTE)` | | | | +| `NVARCHAR2(N)` | | | | +| `CLOB` | | | | +| `NCLOB` | | | | + +### Binary types + +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | +|----------------------|----------------|-----------------------|------------------------| +| `RAW(N)` | `BinaryType()` | `BLOB` | `BLOB` | +| `LONG RAW` | | | | +| `BLOB` | | | | +| `BFILE` | unsupported | | | + +### Struct types + +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | +|-----------------------------------|----------------|-----------------------|------------------------| +| `XMLType` | `StringType()` | `CLOB` | `CLOB` | +| `URIType` | | | | +| `DBURIType` | | | | +| `XDBURIType` | | | | +| `HTTPURIType` | | | | +| `CREATE TYPE ... AS OBJECT (...)` | | | | +| `JSON` | unsupported | | | +| `CREATE TYPE ... AS VARRAY ...` | | | | +| `CREATE TYPE ... AS TABLE OF ...` | | | | + +### Special types + +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | +|----------------------|-----------------|-----------------------|------------------------| +| `BOOLEAN` | `BooleanType()` | `BOOLEAN` | `NUMBER(P=1, S=0)` | +| `ROWID` | `StringType()` | `CLOB` | `CLOB` | +| `UROWID` | | | | +| `UROWID(N)` | | | | +| `ANYTYPE` | unsupported | | | +| `ANYDATA` | | | | +| `ANYDATASET` | | | | + +## Explicit type cast + +### `DBReader` + +It is possible to explicitly cast column of unsupported type using `DBReader(columns=...)` syntax. + +For example, you can use `CAST(column AS CLOB)` to convert data to string representation on Oracle side, and so it will be read as Spark’s `StringType()`. + +It is also possible to use [JSON_ARRAY](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/JSON_ARRAY.html) +or [JSON_OBJECT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/JSON_OBJECT.html) Oracle functions +to convert column of any type to string representation. Then this JSON string can then be effectively parsed using the `JSON.parse_column` method. + +```python +from onetl.file.format import JSON +from pyspark.sql.types import IntegerType, StructType, StructField + +from onetl.connection import Oracle +from onetl.db import DBReader + +oracle = Oracle(...) + +DBReader( + connection=oracle, + columns=[ + "id", + "supported_column", + "CAST(unsupported_column AS VARCHAR2(4000)) unsupported_column_str", + # or + "JSON_ARRAY(array_column) array_column_json", + ], +) +df = reader.run() + +json_scheme = StructType([StructField("key", IntegerType())]) + +df = df.select( + df.id, + df.supported_column, + df.unsupported_column_str.cast("integer").alias("parsed_integer"), + JSON().parse_column("array_column_json", json_scheme).alias("array_column"), +) +``` + +### `DBWriter` + +It is always possible to convert data on Spark side to string, and then write it to text column in Oracle table. + +To serialize and write JSON data to a `text` or `json` column in an Oracle table use the `JSON.serialize_column` method. + +```python +from onetl.connection import Oracle +from onetl.db import DBWriter +from onetl.file.format import JSON + +oracle = Oracle(...) + +oracle.execute( + """ + CREATE TABLE schema.target_table ( + id INTEGER, + supported_column TIMESTAMP, + array_column_json VARCHAR2(4000) -- any string type, actually + ) + """, +) + +write_df = df.select( + df.id, + df.supported_column, + JSON().serialize_column(df.unsupported_column).alias("array_column_json"), +) + +writer = DBWriter( + connection=oracle, + target="schema.target_table", +) +writer.run(write_df) +``` + +Then you can parse this column on Oracle side - for example, by creating a view: + +```sql +SELECT + id, + supported_column, + JSON_VALUE(array_column_json, '$[0]' RETURNING NUMBER) AS array_item_0 +FROM + schema.target_table +``` + +Or by using [VIRTUAL column](https://oracle-base.com/articles/11g/virtual-columns-11gr1): + +```sql +CREATE TABLE schema.target_table ( + id INTEGER, + supported_column TIMESTAMP, + array_column_json VARCHAR2(4000), -- any string type, actually + array_item_0 GENERATED ALWAYS AS (JSON_VALUE(array_column_json, '$[0]' RETURNING NUMBER)) VIRTUAL +) +``` + +But data will be parsed on each table read in any case, as Oracle does no support `GENERATED ALWAYS AS (...) STORED` columns. diff --git a/mddocs/docs/_build/markdown/connection/db_connection/oracle/write.md b/mddocs/docs/_build/markdown/connection/db_connection/oracle/write.md new file mode 100644 index 000000000..202aa831d --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/oracle/write.md @@ -0,0 +1,38 @@ + + +# Writing to Oracle using `DBWriter` + +For writing data to Oracle, use `DBWriter`. + +#### WARNING +Please take into account [Oracle <-> Spark type mapping](types.md#oracle-types) + +#### WARNING +It is always recommended to create table explicitly using [Oracle.execute](execute.md#oracle-execute) +instead of relying on Spark’s table DDL generation. + +This is because Spark’s DDL generator can create columns with different precision and types than it is expected, +causing precision loss or other issues. + +## Examples + +```python +from onetl.connection import Oracle +from onetl.db import DBWriter + +oracle = Oracle(...) + +df = ... # data is here + +writer = DBWriter( + connection=oracle, + target="schema.table", + options=Oracle.WriteOptions(if_exists="append"), +) + +writer.run(df) +``` + +## Options + +Method above accepts `OracleWriteOptions` diff --git a/mddocs/docs/_build/markdown/connection/db_connection/postgres/connection.md b/mddocs/docs/_build/markdown/connection/db_connection/postgres/connection.md new file mode 100644 index 000000000..655ec9480 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/postgres/connection.md @@ -0,0 +1,3 @@ + + +# Postgres connection diff --git a/mddocs/docs/_build/markdown/connection/db_connection/postgres/execute.md b/mddocs/docs/_build/markdown/connection/db_connection/postgres/execute.md new file mode 100644 index 000000000..cee848800 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/postgres/execute.md @@ -0,0 +1,90 @@ + + +# Executing statements in Postgres + +#### WARNING +Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + +Do **NOT** use them to read large amounts of data. Use [DBReader](read.md#postgres-read) or [Postgres.sql](sql.md#postgres-sql) instead. + +## How to + +There are 2 ways to execute some statement in Postgres + +### Use `Postgres.fetch` + +Use this method to execute some `SELECT` query which returns **small number or rows**, like reading +Postgres config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts `Postgres.FetchOptions`. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### WARNING +Please take into account [Postgres <-> Spark type mapping](types.md#postgres-types). + +#### Syntax support + +This method supports **any** query syntax supported by Postgres, like: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Postgres + +postgres = Postgres(...) + +df = postgres.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=Postgres.FetchOptions(queryTimeout=10), +) +postgres.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `Postgres.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts `Postgres.ExecuteOptions`. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Postgres, like: + +* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +* ✅︎ `ALTER ...` +* ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +* ✅︎ `CALL procedure(arg1, arg2) ...` +* ✅︎ `SELECT func(arg1, arg2)` or `{call func(arg1, arg2)}` - special syntax for calling functions +* ✅︎ other statements not mentioned here +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Postgres + +postgres = Postgres(...) + +postgres.execute("DROP TABLE schema.table") +postgres.execute( + """ + CREATE TABLE schema.table ( + id bigint GENERATED ALWAYS AS IDENTITY, + key text, + value real + ) + """, + options=Postgres.ExecuteOptions(queryTimeout=10), +) +``` + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/postgres/index.md b/mddocs/docs/_build/markdown/connection/db_connection/postgres/index.md new file mode 100644 index 000000000..e1a8f76eb --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/postgres/index.md @@ -0,0 +1,19 @@ + + +# Postgres + +# Connection + +* [Prerequisites](prerequisites.md) +* [Postgres connection](connection.md) + +# Operations + +* [Reading from Postgres using `DBReader`](read.md) +* [Reading from Postgres using `Postgres.sql`](sql.md) +* [Writing to Postgres using `DBWriter`](write.md) +* [Executing statements in Postgres](execute.md) + +# Troubleshooting + +* [Postgres <-> Spark type mapping](types.md) diff --git a/mddocs/docs/_build/markdown/connection/db_connection/postgres/prerequisites.md b/mddocs/docs/_build/markdown/connection/db_connection/postgres/prerequisites.md new file mode 100644 index 000000000..2bfb04e0f --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/postgres/prerequisites.md @@ -0,0 +1,71 @@ + + +# Prerequisites + +## Version Compatibility + +* PostgreSQL server versions: + : * Officially declared: 8.2 - 17 + * Actually tested: 9.4.26, 17.3 +* Spark versions: 2.3.x - 3.5.x +* Java versions: 8 - 20 + +See [official documentation](https://jdbc.postgresql.org/). + +## Installing PySpark + +To use Postgres connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Connecting to Postgres + +### Allowing connection to Postgres instance + +Ask your Postgres administrator to allow your user (and probably IP) to connect to instance, +e.g. by updating `pg_hba.conf` file. + +See [official documentation](https://www.postgresql.org/docs/current/auth-pg-hba-conf.html). + +### Connection port + +Connection is usually performed to port 5432. Port may differ for different Postgres instances. +Please ask your Postgres administrator to provide required information. + +### Connection host + +It is possible to connect to Postgres by using either DNS name of host or it’s IP address. + +If you’re using Postgres cluster, it is currently possible to connect only to **one specific node**. +Connecting to multiple nodes to perform load balancing, as well as automatic failover to new master/replica are not supported. + +### Required grants + +Ask your Postgres cluster administrator to set following grants for a user, +used for creating a connection: + +Read + Write + +```sql +-- allow creating tables in specific schema +GRANT USAGE, CREATE ON SCHEMA myschema TO username; + +-- allow read & write access to specific table +GRANT SELECT, INSERT ON myschema.mytable TO username; + +-- only if if_exists="replace_entire_table" is used: +GRANT TRUNCATE ON myschema.mytable TO username; +``` + +Read only + +```sql +-- allow creating tables in specific schema +GRANT USAGE ON SCHEMA myschema TO username; + +-- allow read access to specific table +GRANT SELECT ON myschema.mytable TO username; +``` + +More details can be found in [official documentation](https://www.postgresql.org/docs/current/sql-grant.html). diff --git a/mddocs/docs/_build/markdown/connection/db_connection/postgres/read.md b/mddocs/docs/_build/markdown/connection/db_connection/postgres/read.md new file mode 100644 index 000000000..81565aeec --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/postgres/read.md @@ -0,0 +1,78 @@ + + +# Reading from Postgres using `DBReader` + +`DBReader` supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, +but does not support custom queries, like `JOIN`. + +#### WARNING +Please take into account [Postgres <-> Spark type mapping](types.md#postgres-types) + +## Supported DBReader features + +* ✅︎ `columns` +* ✅︎ `where` +* ✅︎ `hwm`, supported strategies: +* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) +* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) +* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) +* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) +* ❌ `hint` (is not supported by Postgres) +* ❌ `df_schema` +* ✅︎ `options` (see `Postgres.ReadOptions`) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Postgres +from onetl.db import DBReader + +postgres = Postgres(...) + +reader = DBReader( + connection=postgres, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + options=Postgres.ReadOptions(partitionColumn="id", numPartitions=10), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Postgres +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +postgres = Postgres(...) + +reader = DBReader( + connection=postgres, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="postgres_hwm", expression="updated_dt"), + options=Postgres.ReadOptions(partitionColumn="id", numPartitions=10), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Postgres to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Postgres to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/postgres/sql.md b/mddocs/docs/_build/markdown/connection/db_connection/postgres/sql.md new file mode 100644 index 000000000..a5b648f9a --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/postgres/sql.md @@ -0,0 +1,62 @@ + + +# Reading from Postgres using `Postgres.sql` + +`Postgres.sql` allows passing custom SQL query, but does not support incremental strategies. + +#### WARNING +Please take into account [Postgres <-> Spark type mapping](types.md#postgres-types) + +#### WARNING +Statement is executed in **read-write** connection, so if you’re calling some functions/procedures with DDL/DML statements inside, +they can change data in your database. + +## Syntax support + +Only queries with the following syntax are supported: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import Postgres + +postgres = Postgres(...) +df = postgres.sql( + """ + SELECT + id, + key, + CAST(value AS text) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """, + options=Postgres.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from Postgres to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from Postgres to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/postgres/types.md b/mddocs/docs/_build/markdown/connection/db_connection/postgres/types.md new file mode 100644 index 000000000..5fda197e5 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/postgres/types.md @@ -0,0 +1,372 @@ + + +# Postgres <-> Spark type mapping + +#### NOTE +The results below are valid for Spark 3.5.5, and may differ on other Spark versions. + +## Type detection & casting + +Spark’s DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from Postgres + +This is how Postgres connector performs this: + +* For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and Postgres type. +* Find corresponding `Postgres type (read)` → `Spark type` combination (see below) for each DataFrame column [1](#id2). If no combination is found, raise exception. +* Create DataFrame from query with specific column names and Spark types. + +* **[1]** All Postgres types that doesn’t have corresponding Java type are converted to `String`. + +### Writing to some existing Postgres table + +This is how Postgres connector performs this: + +* Get names of columns in DataFrame. [1](#id2) +* Perform `SELECT * FROM table LIMIT 0` query. +* Take only columns present in DataFrame (by name, case insensitive) [2](#id6). For each found column get Postgres type. +* Find corresponding `Spark type` → `Postgres type (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* If `Postgres type (write)` match `Postgres type (read)`, no additional casts will be performed, DataFrame column will be written to Postgres as is. +* If `Postgres type (write)` does not match `Postgres type (read)`, DataFrame column will be casted to target column type **on Postgres side**. + For example, you can write column with text data to `int` column, if column contains valid integer values within supported value range and precision [3](#id7). + +* **[2]** This allows to write data to tables with `DEFAULT` and `GENERATED` columns - if DataFrame has no such column, it will be populated by Postgres. +* **[3]** This is true only if either DataFrame column is a `StringType()`, or target column is `text` type. But other types cannot be silently converted, like `bytea -> bit(N)`. This requires explicit casting, see [Manual conversion to string](). + +### Create new table using Spark + +#### WARNING +ABSOLUTELY NOT RECOMMENDED! + +This is how Postgres connector performs this: + +* Find corresponding `Spark type` → `Postgres type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +* Generate DDL for creating table in Postgres, like `CREATE TABLE (col1 ...)`, and run it. +* Write DataFrame to created table as is. + +But Postgres connector support only limited number of types and almost no custom clauses (like `PARTITION BY`, `INDEX`, etc). +So instead of relying on Spark to create tables: + +### See example + +```python +writer = DBWriter( + connection=postgres, + target="public.table", + options=Postgres.WriteOptions( + if_exists="append", + createTableOptions="PARTITION BY RANGE (id)", + ), +) +writer.run(df) +``` + +Always prefer creating table with desired DDL **BEFORE WRITING DATA**: + +### See example + +```python +postgres.execute( + """ + CREATE TABLE public.table ( + id bigint, + business_dt timestamp(6), + value json + ) + PARTITION BY RANGE (Id) + """, +) + +writer = DBWriter( + connection=postgres, + target="public.table", + options=Postgres.WriteOptions(if_exists="append"), +) +writer.run(df) +``` + +See Postgres [CREATE TABLE](https://www.postgresql.org/docs/current/sql-createtable.html) documentation. + +## Supported types + +### References + +See [List of Postgres types](https://www.postgresql.org/docs/current/datatype.html). + +Here you can find source code with type conversions: + +* [Postgres <-> JDBC](https://github.com/pgjdbc/pgjdbc/blob/REL42.6.0/pgjdbc/src/main/java/org/postgresql/jdbc/TypeInfoCache.java#L78-L112) +* [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/PostgresDialect.scala#L52-L108) +* [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/PostgresDialect.scala#L118-L132) + +### Numeric types + +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | +|-----------------------------|-------------------------------------|-----------------------------|--------------------------| +| `decimal` | `DecimalType(P=38, S=18)` | `decimal(P=38, S=18)` | `decimal` (unbounded) | +| `decimal(P=0..38)` | `DecimalType(P=0..38, S=0)` | `decimal(P=0..38, S=0)` | | +| `decimal(P=0..38, S=0..38)` | `DecimalType(P=0..38, S=0..38)` | `decimal(P=0..38, S=0..38)` | | +| `decimal(P=39.., S=0..)` | unsupported [4](#id11) | | | +| `decimal(P=.., S=..-1)` | unsupported [5](#id12) | | | +| `real` | `FloatType()` | `real` | `real` | +| `double precision` | `DoubleType()` | `double precision` | `double precision` | +| `smallint` | `ShortType()` | `smallint` | `smallint` | +| `-` | `ByteType()` | | | +| `integer` | `IntegerType()` | `integer` | `integer` | +| `bigint` | `LongType()` | `bigint` | `bigint` | +| `money` | `StringType()` [1](#id2) | `text` | `text` | +| `int4range` | | | | +| `int8range` | | | | +| `numrange` | | | | +| `int2vector` | | | | +* **[4]** Postgres support decimal types with unlimited precision. But Spark’s `DecimalType(P, S)` supports maximum `P=38` (128 bit). It is impossible to read, write or operate with values of larger precision, this leads to an exception. +* **[5]** Postgres support decimal types with negative scale, like `decimal(38, -10)`. Spark doesn’t. + +### Temporal types + +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | +|----------------------------------|----------------------------------------------------------------------|-------------------------|--------------------------| +| `date` | `DateType()` | `date` | `date` | +| `time` | `TimestampType()`,
with time format quirks [6](#id16) | `timestamp(6)` | `timestamp(6)` | +| `time(0..6)` | | | | +| `time with time zone` | | | | +| `time(0..6) with time zone` | | | | +| `timestamp` | `TimestampType()` | `timestamp(6)` | `timestamp(6)` | +| `timestamp(0..6)` | | | | +| `timestamp with time zone` | | | | +| `timestamp(0..6) with time zone` | | | | +| `-` | `TimestampNTZType()` | `timestamp(6)` | `timestamp(6)` | +| `interval` of any precision | `StringType()` [1](#id2) | `text` | `text` | +| `-` | `DayTimeIntervalType()` | unsupported | unsupported | +| `-` | `YearMonthIntervalType()` | unsupported | unsupported | +| `daterange` | `StringType()` [1](#id2) | `text` | `text` | +| `tsrange` | | | | +| `tstzrange` | | | | + +#### WARNING +Note that types in Postgres and Spark have different value ranges: + +| Postgres type | Min value | Max value | Spark type | Min value | Max value | +|-----------------|-------------------------------|--------------------------------|-------------------|------------------------------|------------------------------| +| `date` | `-4713-01-01` | `5874897-01-01` | `DateType()` | `0001-01-01` | `9999-12-31` | +| `timestamp` | `-4713-01-01 00:00:00.000000` | `294276-12-31 23:59:59.999999` | `TimestampType()` | `0001-01-01 00:00:00.000000` | `9999-12-31 23:59:59.999999` | +| `time` | `00:00:00.000000` | `24:00:00.000000` | | | | + +So not all of values can be read from Postgres to Spark. + +References: +: * [Postgres date/time types documentation](https://www.postgresql.org/docs/current/datatype-datetime.html) + * [Spark DateType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/DateType.html) + * [Spark TimestampType documentation](https://spark.apache.org/docs/latest/api/java/org/apache/spark/sql/types/TimestampType.html) + +* **[6]** `time` type is the same as `timestamp` with date `1970-01-01`. So instead of reading data from Postgres like `23:59:59` it is actually read `1970-01-01 23:59:59`, and vice versa. + +### String types + +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | +|---------------------------|-------------------------------------|-------------------------|--------------------------| +| `character` | `StringType()` | `text` | `text` | +| `character(N)` | | | | +| `character varying` | | | | +| `character varying(N)` | | | | +| `text` | | | | +| `json` | | | | +| `jsonb` | | | | +| `xml` | | | | +| `CREATE TYPE ... AS ENUM` | `StringType()` [1](#id2) | | | +| `tsvector` | | | | +| `tsquery` | | | | +| `-` | `CharType()` | `unsupported` | `unsupported` | +| `-` | `VarcharType()` | `unsupported` | `unsupported` | + +### Binary types + +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | +|------------------------|-------------------------------------|----------------------------------------------------------|--------------------------| +| `boolean` | `BooleanType()` | `boolean` | `boolean` | +| `bit` | `BooleanType()` | `bool`,
**cannot insert data** [3](#id7) | `bool` | +| `bit(N=1)` | | | | +| `bit(N=2..)` | `ByteType()` | `bytea`,
**cannot insert data** [3](#id7) | `bytea` | +| `bit varying` | `StringType()` [1](#id2) | `text` | `text` | +| `bit varying(N)` | | | | +| `bytea` | `BinaryType()` | `bytea` | `bytea` | + +### Struct types + +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | +|------------------------------|-------------------------------------|-------------------------|--------------------------| +| `T[]` | `ArrayType(T)` | `T[]` | `T[]` | +| `T[][]` | unsupported | | | +| `CREATE TYPE sometype (...)` | `StringType()` [1](#id2) | `text` | `text` | +| `-` | `StructType()` | unsupported | | +| `-` | `MapType()` | | | + +### Network types + +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | +|------------------------|-------------------------------------|-------------------------|--------------------------| +| `cidr` | `StringType()` [1](#id2) | `text` | `text` | +| `inet` | | | | +| `macaddr` | | | | +| `macaddr8` | | | | + +### Geo types + +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | +|------------------------|-------------------------------------|-------------------------|--------------------------| +| `circle` | `StringType()` [1](#id2) | `text` | `text` | +| `box` | | | | +| `line` | | | | +| `lseg` | | | | +| `path` | | | | +| `point` | | | | +| `polygon` | | | | +| `polygon` | | | | + +## Explicit type cast + +### `DBReader` + +It is possible to explicitly cast column of unsupported type using `DBReader(columns=...)` syntax. + +For example, you can use `CAST(column AS text)` to convert data to string representation on Postgres side, and so it will be read as Spark’s `StringType()`. + +It is also possible to use [to_json](https://www.postgresql.org/docs/current/functions-json.html) Postgres function to convert column of any type to string representation, and then parse this column on Spark side you can use the `JSON.parse_column` method: + +```python +from pyspark.sql.types import IntegerType + +from onetl.connection import Postgres +from onetl.db import DBReader +from onetl.file.format import JSON + +postgres = Postgres(...) + +DBReader( + connection=postgres, + columns=[ + "id", + "supported_column", + "CAST(unsupported_column AS text) unsupported_column_str", + # or + "to_json(unsupported_column) array_column_json", + ], +) +df = reader.run() + +json_schema = StructType( + [ + StructField("id", IntegerType(), nullable=True), + StructField("name", StringType(), nullable=True), + ..., + ] +) +df = df.select( + df.id, + df.supported_column, + # explicit cast + df.unsupported_column_str.cast("integer").alias("parsed_integer"), + JSON().parse_column("array_column_json", json_schema).alias("json_string"), +) +``` + +### `DBWriter` + +It is always possible to convert data on the Spark side to a string, and then write it to a text column in a Postgres table. + +#### Using JSON.serialize_column + +You can use the `JSON.serialize_column` method for data serialization: + +```python +from onetl.file.format import JSON +from pyspark.sql.functions import col + +from onetl.connection import Postgres +from onetl.db import DBWriter + +postgres = Postgres(...) + +postgres.execute( + """ + CREATE TABLE schema.target_table ( + id int, + supported_column timestamp, + array_column_json jsonb -- any column type, actually + ) + """, +) + +write_df = df.select( + df.id, + df.supported_column, + JSON().serialize_column(df.unsupported_column).alias("array_column_json"), +) + +writer = DBWriter( + connection=postgres, + target="schema.target_table", +) +writer.run(write_df) +``` + +Then you can parse this column on the Postgres side (for example, by creating a view): + +```sql +SELECT + id, + supported_column, + array_column_json->'0' AS array_item_0 +FROM + schema.target_table +``` + +To avoid casting the value on every table read you can use [GENERATED ALWAYS STORED](https://www.postgresql.org/docs/current/ddl-generated-columns.html) column, but this requires 2x space (for original and parsed value). + +#### Manual conversion to string + +Postgres connector also supports conversion text value directly to target column type, if this value has a proper format. + +For example, you can write data like `[123, 345)` to `int8range` type because Postgres allows cast `'[123, 345)'::int8range'`: + +```python +from pyspark.sql.ftypes import StringType +from pyspark.sql.functions import udf + +from onetl.connection import Postgres +from onetl.db import DBReader + +postgres = Postgres(...) + +postgres.execute( + """ + CREATE TABLE schema.target_table ( + id int, + range_column int8range -- any column type, actually + ) + """, +) + + +@udf(returnType=StringType()) +def array_to_range(value: tuple): + """This UDF allows to convert tuple[start, end] to Postgres' range format""" + start, end = value + return f"[{start},{end})" + + +write_df = df.select( + df.id, + array_to_range(df.range_column).alias("range_column"), +) + +writer = DBWriter( + connection=postgres, + target="schema.target_table", +) +writer.run(write_df) +``` + +This can be tricky to implement and may lead to longer write process. +But this does not require extra space on Postgres side, and allows to avoid explicit value cast on every table read. diff --git a/mddocs/docs/_build/markdown/connection/db_connection/postgres/write.md b/mddocs/docs/_build/markdown/connection/db_connection/postgres/write.md new file mode 100644 index 000000000..823b98c9d --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/postgres/write.md @@ -0,0 +1,38 @@ + + +# Writing to Postgres using `DBWriter` + +For writing data to Postgres, use `DBWriter`. + +#### WARNING +Please take into account [Postgres <-> Spark type mapping](types.md#postgres-types) + +#### WARNING +It is always recommended to create table explicitly using [Postgres.execute](execute.md#postgres-execute) +instead of relying on Spark’s table DDL generation. + +This is because Spark’s DDL generator can create columns with different precision and types than it is expected, +causing precision loss or other issues. + +## Examples + +```python +from onetl.connection import Postgres +from onetl.db import DBWriter + +postgres = Postgres(...) + +df = ... # data is here + +writer = DBWriter( + connection=postgres, + target="schema.table", + options=Postgres.WriteOptions(if_exists="append"), +) + +writer.run(df) +``` + +## Options + +Method above accepts `Postgres.WriteOptions` diff --git a/mddocs/docs/_build/markdown/connection/db_connection/teradata/connection.md b/mddocs/docs/_build/markdown/connection/db_connection/teradata/connection.md new file mode 100644 index 000000000..3f4d2e832 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/teradata/connection.md @@ -0,0 +1,3 @@ + + +# Teradata connection diff --git a/mddocs/docs/_build/markdown/connection/db_connection/teradata/execute.md b/mddocs/docs/_build/markdown/connection/db_connection/teradata/execute.md new file mode 100644 index 000000000..cf5e6d40c --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/teradata/execute.md @@ -0,0 +1,90 @@ + + +# Executing statements in Teradata + +#### WARNING +Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + +Do **NOT** use them to read large amounts of data. Use [DBReader](read.md#teradata-read) or [Teradata.sql](sql.md#teradata-sql) instead. + +## How to + +There are 2 ways to execute some statement in Teradata + +### Use `Teradata.fetch` + +Use this method to execute some `SELECT` query which returns **small number or rows**, like reading +Teradata config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts `Teradata.FetchOptions`. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Teradata, like: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ✅︎ `SHOW ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Teradata + +teradata = Teradata(...) + +df = teradata.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=Teradata.FetchOptions(queryTimeout=10), +) +teradata.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `Teradata.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts `Teradata.ExecuteOptions`. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Teradata, like: + +* ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +* ✅︎ `ALTER ...` +* ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +* ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +* ✅︎ `CALL procedure(arg1, arg2) ...` or `{call procedure(arg1, arg2)}` - special syntax for calling procedure +* ✅︎ `EXECUTE macro(arg1, arg2)` +* ✅︎ `EXECUTE FUNCTION ...` +* ✅︎ other statements not mentioned here +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Teradata + +teradata = Teradata(...) + +teradata.execute("DROP TABLE database.table") +teradata.execute( + """ + CREATE MULTISET TABLE database.table AS ( + id BIGINT, + key VARCHAR, + value REAL + ) + NO PRIMARY INDEX + """, + options=Teradata.ExecuteOptions(queryTimeout=10), +) +``` + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/teradata/index.md b/mddocs/docs/_build/markdown/connection/db_connection/teradata/index.md new file mode 100644 index 000000000..f35e86981 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/teradata/index.md @@ -0,0 +1,15 @@ + + +# Teradata + +# Connection + +* [Prerequisites](prerequisites.md) +* [Teradata connection](connection.md) + +# Operations + +* [Reading from Teradata using `DBReader`](read.md) +* [Reading from Teradata using `Teradata.sql`](sql.md) +* [Writing to Teradata using `DBWriter`](write.md) +* [Executing statements in Teradata](execute.md) diff --git a/mddocs/docs/_build/markdown/connection/db_connection/teradata/prerequisites.md b/mddocs/docs/_build/markdown/connection/db_connection/teradata/prerequisites.md new file mode 100644 index 000000000..3217390e2 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/teradata/prerequisites.md @@ -0,0 +1,57 @@ + + +# Prerequisites + +## Version Compatibility + +* Teradata server versions: + : * Officially declared: 16.10 - 20.0 + * Actually tested: 16.10 +* Spark versions: 2.3.x - 3.5.x +* Java versions: 8 - 20 + +See [official documentation](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/platformMatrix.html). + +## Installing PySpark + +To use Teradata connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Connecting to Teradata + +### Connection host + +It is possible to connect to Teradata by using either DNS name Parsing Engine (PE) host, or it’s IP address. + +### Connection port + +Connection is usually performed to port `1025`. Port may differ for different Teradata instances. +Please ask your Teradata administrator to provide required information. + +### Required grants + +Ask your Teradata cluster administrator to set following grants for a user, +used for creating a connection: + +Read + Write + +```sql +-- allow creating tables in the target schema +GRANT CREATE TABLE ON database TO username; + +-- allow read & write access to specific table +GRANT SELECT, INSERT ON database.mytable TO username; +``` + +Read only + +```sql +-- allow read access to specific table +GRANT SELECT ON database.mytable TO username; +``` + +See: +: * [Teradata access rights](https://www.dwhpro.com/teradata-access-rights/) + * [GRANT documentation](https://teradata.github.io/presto/docs/0.167-t/sql/grant.html) diff --git a/mddocs/docs/_build/markdown/connection/db_connection/teradata/read.md b/mddocs/docs/_build/markdown/connection/db_connection/teradata/read.md new file mode 100644 index 000000000..e06d2cebc --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/teradata/read.md @@ -0,0 +1,107 @@ + + +# Reading from Teradata using `DBReader` + +`DBReader` supports [Read Strategies](../../../strategy/index.md#strategy) for incremental data reading, +but does not support custom queries, like `JOIN`. + +## Supported DBReader features + +* ✅︎ `columns` +* ✅︎ `where` +* ✅︎ `hwm`, supported strategies: +* * ✅︎ [Snapshot Strategy](../../../strategy/snapshot_strategy.md#snapshot-strategy) +* * ✅︎ [Incremental Strategy](../../../strategy/incremental_strategy.md#incremental-strategy) +* * ✅︎ [Snapshot Batch Strategy](../../../strategy/snapshot_batch_strategy.md#snapshot-batch-strategy) +* * ✅︎ [Incremental Batch Strategy](../../../strategy/incremental_batch_strategy.md#incremental-batch-strategy) +* ❌ `hint` (is not supported by Teradata) +* ❌ `df_schema` +* ✅︎ `options` (see `Teradata.ReadOptions`) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Teradata +from onetl.db import DBReader + +teradata = Teradata(...) + +reader = DBReader( + connection=teradata, + source="database.table", + columns=["id", "key", "CAST(value AS VARCHAR) value", "updated_dt"], + where="key = 'something'", + options=Teradata.ReadOptions( + partitioning_mode="hash", + partitionColumn="id", + numPartitions=10, + ), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Teradata +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +teradata = Teradata(...) + +reader = DBReader( + connection=teradata, + source="database.table", + columns=["id", "key", "CAST(value AS VARCHAR) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="teradata_hwm", expression="updated_dt"), + options=Teradata.ReadOptions( + partitioning_mode="hash", + partitionColumn="id", + numPartitions=10, + ), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Teradata to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Teradata to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +### Read data in parallel + +`DBReader` can read data in multiple parallel connections by passing `Teradata.ReadOptions(numPartitions=..., partitionColumn=...)`. + +In the example above, Spark opens 10 parallel connections, and data is evenly distributed between all these connections using expression +`HASHAMP(HASHBUCKET(HASHROW({partition_column}))) MOD {num_partitions}`. +This allows sending each Spark worker only some piece of data, reducing resource consumption. +`partition_column` here can be table column of any type. + +It is also possible to use `partitioning_mode="mod"` or `partitioning_mode="range"`, but in this case +`partition_column` have to be an integer, should not contain `NULL`, and values to be uniformly distributed. +It is also less performant than `partitioning_mode="hash"` due to Teradata `HASHAMP` implementation. + +### Do **NOT** use `TYPE=FASTEXPORT` + +Teradata supports several [different connection types](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/jdbcug_chapter_2.html#BABFGFAF): +: * `TYPE=DEFAULT` - perform plain `SELECT` queries + * `TYPE=FASTEXPORT` - uses special FastExport protocol for select queries + +But `TYPE=FASTEXPORT` uses exclusive lock on the source table, so it is impossible to use multiple Spark workers parallel data read. +This leads to sending all the data to just one Spark worker, which is slow and takes a lot of RAM. + +Prefer using `partitioning_mode="hash"` from example above. + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/teradata/sql.md b/mddocs/docs/_build/markdown/connection/db_connection/teradata/sql.md new file mode 100644 index 000000000..d52c3f407 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/teradata/sql.md @@ -0,0 +1,61 @@ + + +# Reading from Teradata using `Teradata.sql` + +`Teradata.sql` allows passing custom SQL query, but does not support incremental strategies. + +#### WARNING +Statement is executed in **read-write** connection, so if you’re calling some functions/procedures with DDL/DML statements inside, +they can change data in your database. + +## Syntax support + +Only queries with the following syntax are supported: + +* ✅︎ `SELECT ... FROM ...` +* ✅︎ `WITH alias AS (...) SELECT ...` +* ❌ `SHOW ...` +* ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import Teradata + +teradata = Teradata(...) +df = teradata.sql( + """ + SELECT + id, + key, + CAST(value AS VARCHAR) AS value, + updated_at, + HASHAMP(HASHBUCKET(HASHROW(id))) MOD 10 AS part_column + FROM + database.mytable + WHERE + key = 'something' + """, + options=Teradata.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from Teradata to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from Teradata to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options diff --git a/mddocs/docs/_build/markdown/connection/db_connection/teradata/write.md b/mddocs/docs/_build/markdown/connection/db_connection/teradata/write.md new file mode 100644 index 000000000..9ee2f5c6e --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/db_connection/teradata/write.md @@ -0,0 +1,105 @@ + + +# Writing to Teradata using `DBWriter` + +For writing data to Teradata, use `DBWriter`. + +#### WARNING +It is always recommended to create table explicitly using [Teradata.execute](execute.md#teradata-execute) +instead of relying on Spark’s table DDL generation. + +This is because Spark’s DDL generator can create columns with different precision and types than it is expected, +causing precision loss or other issues. + +## Examples + +```python +from onetl.connection import Teradata +from onetl.db import DBWriter + +teradata = Teradata( + ..., + extra={"TYPE": "FASTLOAD", "TMODE": "TERA"}, +) + +df = ... # data is here + +writer = DBWriter( + connection=teradata, + target="database.table", + options=Teradata.WriteOptions( + if_exists="append", + # avoid creating SET table, use MULTISET + createTableOptions="NO PRIMARY INDEX", + ), +) + +writer.run(df.repartition(1)) +``` + +## Recommendations + +### Number of connections + +Teradata is not MVCC based, so write operations take exclusive lock on the entire table. +So **it is impossible to write data to Teradata table in multiple parallel connections**, no exceptions. + +The only way to write to Teradata without making deadlocks is write dataframe with exactly 1 partition. + +It can be implemented using `df.repartition(1)`: + +```python +# do NOT use df.coalesce(1) as it can freeze +writer.run(df.repartition(1)) +``` + +This moves all the data to just one Spark worker, so it may consume a lot of RAM. It is usually require to increase `spark.executor.memory` to handle this. + +Another way is to write all dataframe partitions one-by-one: + +```python +from pyspark.sql.functions import spark_partition_id + +# get list of all partitions in the dataframe +partitions = sorted(df.select(spark_partition_id()).distinct().collect()) + +for partition in partitions: + # get only part of data within this exact partition + part_df = df.where(**partition.asDict()).coalesce(1) + + writer.run(part_df) +``` + +This require even data distribution for all partitions to avoid data skew and spikes of RAM consuming. + +### Choosing connection type + +Teradata supports several [different connection types](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/jdbcug_chapter_2.html#BABFGFAF): +: * `TYPE=DEFAULT` - perform plain `INSERT` queries + * `TYPE=FASTLOAD` - uses special FastLoad protocol for insert queries + +It is always recommended to use `TYPE=FASTLOAD` because: +: * It provides higher performance + * It properly handles inserting `NULL` values (`TYPE=DEFAULT` raises an exception) + +But it can be used only during write, not read. + +### Choosing transaction mode + +Teradata supports [2 different transaction modes](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/jdbcug_chapter_2.html#TMODESEC): +: * `TMODE=ANSI` + * `TMODE=TERA` + +Choosing one of the modes can alter connector behavior. For example: +: * Inserting data which exceeds table column length, like insert `CHAR(25)` to column with type `CHAR(24)`: + * * `TMODE=ANSI` - raises exception + * * `TMODE=TERA` - truncates input string to 24 symbols + * Creating table using Spark: + * * `TMODE=ANSI` - creates `MULTISET` table + * * `TMODE=TERA` - creates `SET` table with `PRIMARY KEY` is a first column in dataframe. + This can lead to slower insert time, because each row will be checked against a unique index. + Fortunately, this can be disabled by passing custom `createTableOptions`. + +## Options + +Method above accepts `Teradata.WriteOptions` diff --git a/mddocs/docs/_build/markdown/connection/file_connection/ftp.md b/mddocs/docs/_build/markdown/connection/file_connection/ftp.md new file mode 100644 index 000000000..30502b16f --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/file_connection/ftp.md @@ -0,0 +1,3 @@ + + +# FTP connection diff --git a/mddocs/docs/_build/markdown/connection/file_connection/ftps.md b/mddocs/docs/_build/markdown/connection/file_connection/ftps.md new file mode 100644 index 000000000..d880128ed --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/file_connection/ftps.md @@ -0,0 +1,3 @@ + + +# FTPS connection diff --git a/mddocs/docs/_build/markdown/connection/file_connection/hdfs/connection.md b/mddocs/docs/_build/markdown/connection/file_connection/hdfs/connection.md new file mode 100644 index 000000000..cec7cfdc3 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/file_connection/hdfs/connection.md @@ -0,0 +1,3 @@ + + +# HDFS connection diff --git a/mddocs/docs/_build/markdown/connection/file_connection/hdfs/index.md b/mddocs/docs/_build/markdown/connection/file_connection/hdfs/index.md new file mode 100644 index 000000000..a65c43ea3 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/file_connection/hdfs/index.md @@ -0,0 +1,11 @@ + + +# HDFS + +# Connection + +* [HDFS connection](connection.md) + +# For developers + +* [HDFS Slots](slots.md) diff --git a/mddocs/docs/_build/markdown/connection/file_connection/hdfs/slots.md b/mddocs/docs/_build/markdown/connection/file_connection/hdfs/slots.md new file mode 100644 index 000000000..598fca6e2 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/file_connection/hdfs/slots.md @@ -0,0 +1,3 @@ + + +# HDFS Slots diff --git a/mddocs/docs/_build/markdown/connection/file_connection/index.md b/mddocs/docs/_build/markdown/connection/file_connection/index.md new file mode 100644 index 000000000..874845c78 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/file_connection/index.md @@ -0,0 +1,13 @@ + + +# File Connections + +# File Connections + +* [FTP](ftp.md) +* [FTPS](ftps.md) +* [HDFS](hdfs/index.md) +* [Samba](samba.md) +* [SFTP](sftp.md) +* [S3](s3.md) +* [Webdav](webdav.md) diff --git a/mddocs/docs/_build/markdown/connection/file_connection/s3.md b/mddocs/docs/_build/markdown/connection/file_connection/s3.md new file mode 100644 index 000000000..1d1fddc84 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/file_connection/s3.md @@ -0,0 +1,3 @@ + + +# S3 connection diff --git a/mddocs/docs/_build/markdown/connection/file_connection/samba.md b/mddocs/docs/_build/markdown/connection/file_connection/samba.md new file mode 100644 index 000000000..47ec6a315 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/file_connection/samba.md @@ -0,0 +1,3 @@ + + +# Samba connection diff --git a/mddocs/docs/_build/markdown/connection/file_connection/sftp.md b/mddocs/docs/_build/markdown/connection/file_connection/sftp.md new file mode 100644 index 000000000..28af73f44 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/file_connection/sftp.md @@ -0,0 +1,3 @@ + + +# SFTP connection diff --git a/mddocs/docs/_build/markdown/connection/file_connection/webdav.md b/mddocs/docs/_build/markdown/connection/file_connection/webdav.md new file mode 100644 index 000000000..f3bc2daf7 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/file_connection/webdav.md @@ -0,0 +1,3 @@ + + +# WebDAV connection diff --git a/mddocs/docs/_build/markdown/connection/file_df_connection/base.md b/mddocs/docs/_build/markdown/connection/file_df_connection/base.md new file mode 100644 index 000000000..00056fbf0 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/file_df_connection/base.md @@ -0,0 +1,3 @@ + + +# Base interface diff --git a/mddocs/docs/_build/markdown/connection/file_df_connection/index.md b/mddocs/docs/_build/markdown/connection/file_df_connection/index.md new file mode 100644 index 000000000..423e1c82a --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/file_df_connection/index.md @@ -0,0 +1,19 @@ + + +# File DataFrame Connections + +# File DataFrame Connections + +* [Spark LocalFS](spark_local_fs.md) +* [Spark HDFS](spark_hdfs/index.md) + * [Prerequisites](spark_hdfs/prerequisites.md) + * [Connection](spark_hdfs/connection.md) + * [Slots](spark_hdfs/slots.md) +* [Spark S3](spark_s3/index.md) + * [Prerequisites](spark_s3/prerequisites.md) + * [Connection](spark_s3/connection.md) + * [Troubleshooting](spark_s3/troubleshooting.md) + +# For developers + +* [Base interface](base.md) diff --git a/mddocs/docs/_build/markdown/connection/file_df_connection/spark_hdfs/connection.md b/mddocs/docs/_build/markdown/connection/file_df_connection/spark_hdfs/connection.md new file mode 100644 index 000000000..f18513c1c --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/file_df_connection/spark_hdfs/connection.md @@ -0,0 +1,3 @@ + + +# Spark HDFS Connection diff --git a/mddocs/docs/_build/markdown/connection/file_df_connection/spark_hdfs/index.md b/mddocs/docs/_build/markdown/connection/file_df_connection/spark_hdfs/index.md new file mode 100644 index 000000000..d5eae4c27 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/file_df_connection/spark_hdfs/index.md @@ -0,0 +1,12 @@ + + +# Spark HDFS + +# Connection + +* [Prerequisites](prerequisites.md) +* [Connection](connection.md) + +# For developers + +* [Slots](slots.md) diff --git a/mddocs/docs/_build/markdown/connection/file_df_connection/spark_hdfs/prerequisites.md b/mddocs/docs/_build/markdown/connection/file_df_connection/spark_hdfs/prerequisites.md new file mode 100644 index 000000000..2011410da --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/file_df_connection/spark_hdfs/prerequisites.md @@ -0,0 +1,46 @@ + + +# Prerequisites + +## Version Compatibility + +* Hadoop versions: 2.x, 3.x (only with Hadoop 3.x libraries) +* Spark versions: 2.3.x - 3.5.x +* Java versions: 8 - 20 + +## Installing PySpark + +To use SparkHDFS connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Using Kerberos + +Some of Hadoop managed clusters use Kerberos authentication. In this case, you should call [kinit](https://web.mit.edu/kerberos/krb5-1.12/doc/user/user_commands/kinit.html) command +**BEFORE** starting Spark session to generate Kerberos ticket. See [Kerberos support](../../../install/kerberos.md#install-kerberos). + +Sometimes it is also required to pass keytab file to Spark config, allowing Spark executors to generate own Kerberos tickets: + +Spark 3 + +```python +SparkSession.builder + .option("spark.kerberos.access.hadoopFileSystems", "hdfs://namenode1.domain.com:9820,hdfs://namenode2.domain.com:9820") + .option("spark.kerberos.principal", "user") + .option("spark.kerberos.keytab", "/path/to/keytab") + .gerOrCreate() +``` + +Spark 2 + +```python +SparkSession.builder + .option("spark.yarn.access.hadoopFileSystems", "hdfs://namenode1.domain.com:9820,hdfs://namenode2.domain.com:9820") + .option("spark.yarn.principal", "user") + .option("spark.yarn.keytab", "/path/to/keytab") + .gerOrCreate() +``` + +See [Spark security documentation](https://spark.apache.org/docs/latest/security.html#kerberos) +for more details. diff --git a/mddocs/docs/_build/markdown/connection/file_df_connection/spark_hdfs/slots.md b/mddocs/docs/_build/markdown/connection/file_df_connection/spark_hdfs/slots.md new file mode 100644 index 000000000..295e641da --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/file_df_connection/spark_hdfs/slots.md @@ -0,0 +1,3 @@ + + +# Spark HDFS Slots diff --git a/mddocs/docs/_build/markdown/connection/file_df_connection/spark_local_fs.md b/mddocs/docs/_build/markdown/connection/file_df_connection/spark_local_fs.md new file mode 100644 index 000000000..b16a60faa --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/file_df_connection/spark_local_fs.md @@ -0,0 +1,3 @@ + + +# Spark LocalFS diff --git a/mddocs/docs/_build/markdown/connection/file_df_connection/spark_s3/connection.md b/mddocs/docs/_build/markdown/connection/file_df_connection/spark_s3/connection.md new file mode 100644 index 000000000..fdb762c4f --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/file_df_connection/spark_s3/connection.md @@ -0,0 +1,3 @@ + + +# Spark S3 Connection diff --git a/mddocs/docs/_build/markdown/connection/file_df_connection/spark_s3/index.md b/mddocs/docs/_build/markdown/connection/file_df_connection/spark_s3/index.md new file mode 100644 index 000000000..be66a0adc --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/file_df_connection/spark_s3/index.md @@ -0,0 +1,9 @@ + + +# Spark S3 + +# Connection + +* [Prerequisites](prerequisites.md) +* [Connection](connection.md) +* [Troubleshooting](troubleshooting.md) diff --git a/mddocs/docs/_build/markdown/connection/file_df_connection/spark_s3/prerequisites.md b/mddocs/docs/_build/markdown/connection/file_df_connection/spark_s3/prerequisites.md new file mode 100644 index 000000000..1bfd0846a --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/file_df_connection/spark_s3/prerequisites.md @@ -0,0 +1,61 @@ + + +# Prerequisites + +## Version Compatibility + +* Spark versions: 3.2.x - 3.5.x (only with Hadoop 3.x libraries) +* Java versions: 8 - 20 + +## Installing PySpark + +To use SparkS3 connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See [Spark](../../../install/spark.md#install-spark) installation instruction for more details. + +## Connecting to S3 + +### Bucket access style + +AWS and some other S3 cloud providers allows bucket access using domain style only, e.g. `https://mybucket.s3provider.com`. + +Other implementations, like Minio, by default allows path style access only, e.g. `https://s3provider.com/mybucket` +(see [MINIO_DOMAIN](https://min.io/docs/minio/linux/reference/minio-server/minio-server.html#envvar.MINIO_DOMAIN)). + +You should set `path.style.access` to `True` or `False`, to choose the preferred style. + +### Authentication + +Different S3 instances can use different authentication methods, like: +: * `access_key + secret_key` (or username + password) + * `access_key + secret_key + session_token` + +Usually these are just passed to SparkS3 constructor: + +```python +SparkS3( + access_key=..., + secret_key=..., + session_token=..., +) +``` + +But some S3 cloud providers, like AWS, may require custom credential providers. You can pass them like: + +```python +SparkS3( + extra={ + # provider class + "aws.credentials.provider": "org.apache.hadoop.fs.s3a.auth.AssumedRoleCredentialProvider", + # other options, if needed + "assumed.role.arn": "arn:aws:iam::90066806600238:role/s3-restricted", + }, +) +``` + +See [Hadoop-AWS](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/index.html#Changing_Authentication_Providers) documentation. + +## Troubleshooting + +See [Spark S3 Troubleshooting](troubleshooting.md#spark-s3-troubleshooting). diff --git a/mddocs/docs/_build/markdown/connection/file_df_connection/spark_s3/troubleshooting.md b/mddocs/docs/_build/markdown/connection/file_df_connection/spark_s3/troubleshooting.md new file mode 100644 index 000000000..32ea15667 --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/file_df_connection/spark_s3/troubleshooting.md @@ -0,0 +1,366 @@ + + +# Spark S3 Troubleshooting + +#### NOTE +General guide: [Troubleshooting](../../../troubleshooting/index.md#troubleshooting). + +More details: + +* [Hadoop AWS Troubleshooting Guide](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/troubleshooting_s3a.html) +* [Hadoop AWS Performance Guide](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/performance.html) +* [Spark integration with Cloud Infrastructures](https://spark.apache.org/docs/latest/cloud-integration.html) + +## `SparkS3.check()` and other methods hang + +### Details + +S3 may not respond for connection attempts for a long time if it’s under heavy load. +To handle this, Hadoop AWS library has retry mechanism. By default it retries 7 times with 500ms interval. + +Hadoop AWS is based on AWS SDK library, which also has retry mechanism. This mechanism is not disabled because it handles different +errors than Hadoop AWS, so they complement each other. Default number of attempts in AWS SDK is 20 with minimal 5s interval, +which is exponentially increasing with each failed attempt. + +It is not a problem if S3 source is not accessible at all, like hostname cannot be resolved, or port is not opened. +These errors are not recoverable, and retry mechanism is not activated. + +But errors like SSL issues, are considered recoverable, and this causing retry of retry over increasing interval. +So user is waiting for [almost 15 minutes](https://issues.apache.org/jira/browse/HADOOP-18839) just to get exception message. + +### How to determine reason + +#### Make logging more verbose + +Change Spark session log level to [DEBUG](../../../troubleshooting/spark.md#troubleshooting-spark) to print result of each attempt. +Resulting logs will look like this + +### See log + +```text +23/08/03 11:25:10 DEBUG S3AFileSystem: Using S3ABlockOutputStream with buffer = disk; block=67108864; queue limit=4 +23/08/03 11:25:10 DEBUG S3Guard: Metastore option source [core-default.xml] +23/08/03 11:25:10 DEBUG S3Guard: Using NullMetadataStore metadata store for s3a filesystem +23/08/03 11:25:10 DEBUG S3AFileSystem: S3Guard is disabled on this bucket: test-bucket +23/08/03 11:25:10 DEBUG DirectoryPolicyImpl: Directory markers will be deleted +23/08/03 11:25:10 DEBUG S3AFileSystem: Directory marker retention policy is DirectoryMarkerRetention{policy='delete'} +23/08/03 11:25:10 DEBUG S3AUtils: Value of fs.s3a.multipart.purge.age is 86400 +23/08/03 11:25:10 DEBUG S3AUtils: Value of fs.s3a.bulk.delete.page.size is 250 +23/08/03 11:25:10 DEBUG FileSystem: Creating FS s3a://test-bucket/fake: duration 0:01.029s +23/08/03 11:25:10 DEBUG IOStatisticsStoreImpl: Incrementing counter op_is_directory by 1 with final value 1 +23/08/03 11:25:10 DEBUG S3AFileSystem: Getting path status for s3a://test-bucket/fake (fake); needEmptyDirectory=false +23/08/03 11:25:10 DEBUG S3AFileSystem: S3GetFileStatus s3a://test-bucket/fake +23/08/03 11:25:10 DEBUG S3AFileSystem: LIST List test-bucket:/fake/ delimiter=/ keys=2 requester pays=false +23/08/03 11:25:10 DEBUG S3AFileSystem: Starting: LIST +23/08/03 11:25:10 DEBUG IOStatisticsStoreImpl: Incrementing counter object_list_request by 1 with final value 1 +23/08/03 11:25:10 DEBUG AWSCredentialProviderList: Using credentials from SimpleAWSCredentialsProvider +23/08/03 11:25:10 DEBUG request: Sending Request: GET https://test-bucket.localhost:9000 / Parameters: ({"list-type":["2"],"delimiter":["/"],"max-keys":["2"],"prefix":["fake/"],"fetch-owner":["false"]}Headers: (amz-sdk-invocation-id: e6d62603-96e4-a80f-10a1-816e0822bc71, Content-Type: application/octet-stream, User-Agent: Hadoop 3.3.4, aws-sdk-java/1.12.262 Linux/6.4.7-1-MANJARO OpenJDK_64-Bit_Server_VM/25.292-b10 java/1.8.0_292 scala/2.12.17 vendor/AdoptOpenJDK cfg/retry-mode/legacy, ) +23/08/03 11:25:10 DEBUG AWS4Signer: AWS4 Canonical Request: '"GET +/ +delimiter=%2F&fetch-owner=false&list-type=2&max-keys=2&prefix=fake%2F +amz-sdk-invocation-id:e6d62603-96e4-a80f-10a1-816e0822bc71 +amz-sdk-request:attempt=1;max=21 +amz-sdk-retry:0/0/500 +content-type:application/octet-stream +host:test-bucket.localhost:9000 +user-agent:Hadoop 3.3.4, aws-sdk-java/1.12.262 Linux/6.4.7-1-MANJARO OpenJDK_64-Bit_Server_VM/25.292-b10 java/1.8.0_292 scala/2.12.17 vendor/AdoptOpenJDK cfg/retry-mode/legacy +x-amz-content-sha256:UNSIGNED-PAYLOAD +x-amz-date:20230803T112510Z + +amz-sdk-invocation-id;amz-sdk-request;amz-sdk-retry;content-type;host;user-agent;x-amz-content-sha256;x-amz-date +UNSIGNED-PAYLOAD" +23/08/03 11:25:10 DEBUG AWS4Signer: AWS4 String to Sign: '"AWS4-HMAC-SHA256 +20230803T112510Z +20230803/us-east-1/s3/aws4_request +31a317bb7f6d97248dd0cf03429d701cbb3e29ce889cfbb98ba7a34c57a3bfba" +23/08/03 11:25:10 DEBUG AWS4Signer: Generating a new signing key as the signing key not available in the cache for the date 1691020800000 +23/08/03 11:25:10 DEBUG RequestAddCookies: CookieSpec selected: default +23/08/03 11:25:10 DEBUG RequestAuthCache: Auth cache not set in the context +23/08/03 11:25:10 DEBUG PoolingHttpClientConnectionManager: Connection request: [route: {s}->https://test-bucket.localhost:9000][total available: 0; route allocated: 0 of 96; total allocated: 0 of 96] +23/08/03 11:25:10 DEBUG PoolingHttpClientConnectionManager: Connection leased: [id: 0][route: {s}->https://test-bucket.localhost:9000][total available: 0; route allocated: 1 of 96; total allocated: 1 of 96] +23/08/03 11:25:10 DEBUG MainClientExec: Opening connection {s}->https://test-bucket.localhost:9000 +23/08/03 11:25:10 DEBUG DefaultHttpClientConnectionOperator: Connecting to test-bucket.localhost/127.0.0.1:9000 +23/08/03 11:25:10 DEBUG SSLConnectionSocketFactory: Connecting socket to test-bucket.localhost/127.0.0.1:9000 with timeout 5000 +23/08/03 11:25:10 DEBUG SSLConnectionSocketFactory: Enabled protocols: [TLSv1.2] +23/08/03 11:25:10 DEBUG SSLConnectionSocketFactory: Enabled cipher suites:[TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV] +23/08/03 11:25:10 DEBUG SSLConnectionSocketFactory: Starting handshake +23/08/03 11:25:10 DEBUG ClientConnectionManagerFactory: +java.lang.reflect.InvocationTargetException + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:498) + at com.amazonaws.http.conn.ClientConnectionManagerFactory$Handler.invoke(ClientConnectionManagerFactory.java:76) + at com.amazonaws.http.conn.$Proxy32.connect(Unknown Source) + at com.amazonaws.thirdparty.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:393) + at com.amazonaws.thirdparty.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236) + at com.amazonaws.thirdparty.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186) + at com.amazonaws.thirdparty.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185) + at com.amazonaws.thirdparty.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83) + at com.amazonaws.thirdparty.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56) + at com.amazonaws.http.apache.client.impl.SdkHttpClient.execute(SdkHttpClient.java:72) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeOneRequest(AmazonHttpClient.java:1346) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeHelper(AmazonHttpClient.java:1157) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.doExecute(AmazonHttpClient.java:814) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeWithTimer(AmazonHttpClient.java:781) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.execute(AmazonHttpClient.java:755) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.access$500(AmazonHttpClient.java:715) + at com.amazonaws.http.AmazonHttpClient$RequestExecutionBuilderImpl.execute(AmazonHttpClient.java:697) + at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:561) + at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:541) + at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5456) + at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5403) + at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5397) + at com.amazonaws.services.s3.AmazonS3Client.listObjectsV2(AmazonS3Client.java:971) + at org.apache.hadoop.fs.s3a.S3AFileSystem.lambda$listObjects$11(S3AFileSystem.java:2595) + at org.apache.hadoop.fs.statistics.impl.IOStatisticsBinding.lambda$trackDurationOfOperation$5(IOStatisticsBinding.java:499) + at org.apache.hadoop.fs.s3a.Invoker.retryUntranslated(Invoker.java:414) + at org.apache.hadoop.fs.s3a.Invoker.retryUntranslated(Invoker.java:377) + at org.apache.hadoop.fs.s3a.S3AFileSystem.listObjects(S3AFileSystem.java:2586) + at org.apache.hadoop.fs.s3a.S3AFileSystem.s3GetFileStatus(S3AFileSystem.java:3832) + at org.apache.hadoop.fs.s3a.S3AFileSystem.innerGetFileStatus(S3AFileSystem.java:3688) + at org.apache.hadoop.fs.s3a.S3AFileSystem.lambda$isDirectory$35(S3AFileSystem.java:4724) + at org.apache.hadoop.fs.statistics.impl.IOStatisticsBinding.lambda$trackDurationOfOperation$5(IOStatisticsBinding.java:499) + at org.apache.hadoop.fs.statistics.impl.IOStatisticsBinding.trackDuration(IOStatisticsBinding.java:444) + at org.apache.hadoop.fs.s3a.S3AFileSystem.trackDurationAndSpan(S3AFileSystem.java:2337) + at org.apache.hadoop.fs.s3a.S3AFileSystem.trackDurationAndSpan(S3AFileSystem.java:2356) + at org.apache.hadoop.fs.s3a.S3AFileSystem.isDirectory(S3AFileSystem.java:4722) + at org.apache.spark.sql.execution.streaming.FileStreamSink$.hasMetadata(FileStreamSink.scala:54) + at org.apache.spark.sql.execution.datasources.DataSource.resolveRelation(DataSource.scala:366) + at org.apache.spark.sql.DataFrameReader.loadV1Source(DataFrameReader.scala:229) + at org.apache.spark.sql.DataFrameReader.$anonfun$load$2(DataFrameReader.scala:211) + at scala.Option.getOrElse(Option.scala:189) + at org.apache.spark.sql.DataFrameReader.load(DataFrameReader.scala:211) + at org.apache.spark.sql.DataFrameReader.load(DataFrameReader.scala:186) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:498) + at py4j.reflection.MethodInvoker.invoke(MethodInvoker.java:244) + at py4j.reflection.ReflectionEngine.invoke(ReflectionEngine.java:374) + at py4j.Gateway.invoke(Gateway.java:282) + at py4j.commands.AbstractCommand.invokeMethod(AbstractCommand.java:132) + at py4j.commands.CallCommand.execute(CallCommand.java:79) + at py4j.ClientServerConnection.waitForCommands(ClientServerConnection.java:182) + at py4j.ClientServerConnection.run(ClientServerConnection.java:106) + at java.lang.Thread.run(Thread.java:748) +Caused by: javax.net.ssl.SSLException: Unsupported or unrecognized SSL message + at sun.security.ssl.SSLSocketInputRecord.handleUnknownRecord(SSLSocketInputRecord.java:448) + at sun.security.ssl.SSLSocketInputRecord.decode(SSLSocketInputRecord.java:184) + at sun.security.ssl.SSLTransport.decode(SSLTransport.java:109) + at sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1383) + at sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1291) + at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:435) + at com.amazonaws.thirdparty.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:436) + at com.amazonaws.thirdparty.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:384) + at com.amazonaws.thirdparty.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142) + at com.amazonaws.thirdparty.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:376) + ... 58 more +23/08/03 11:25:10 DEBUG DefaultManagedHttpClientConnection: http-outgoing-0: Shutdown connection +23/08/03 11:25:10 DEBUG MainClientExec: Connection discarded +23/08/03 11:25:10 DEBUG PoolingHttpClientConnectionManager: Connection released: [id: 0][route: {s}->https://test-bucket.localhost:9000][total available: 0; route allocated: 0 of 96; total allocated: 0 of 96] +23/08/03 11:25:10 DEBUG AmazonHttpClient: Unable to execute HTTP request: Unsupported or unrecognized SSL message Request will be retried. +23/08/03 11:25:10 DEBUG request: Retrying Request: GET https://test-bucket.localhost:9000 / Parameters: ({"list-type":["2"],"delimiter":["/"],"max-keys":["2"],"prefix":["fake/"],"fetch-owner":["false"]}Headers: (amz-sdk-invocation-id: e6d62603-96e4-a80f-10a1-816e0822bc71, Content-Type: application/octet-stream, User-Agent: Hadoop 3.3.4, aws-sdk-java/1.12.262 Linux/6.4.7-1-MANJARO OpenJDK_64-Bit_Server_VM/25.292-b10 java/1.8.0_292 scala/2.12.17 vendor/AdoptOpenJDK cfg/retry-mode/legacy, ) +23/08/03 11:25:10 DEBUG AmazonHttpClient: Retriable error detected, will retry in 49ms, attempt number: 0 +``` + +#### Change number of retries + +You can also change number of retries performed by both libraries using `extra` parameter: + +```python +spark_s3 = SparkS3( + ..., + extra={ + "attempts.maximum": 1, + "retry.limit": 1, + }, +) +``` + +So accessing S3 will fail almost immediately if there is any error. + +### Most common mistakes + +#### No network access + +```text +Caused by: java.net.ConnectException: Connection refused +``` + +Mostly caused by: + +* Trying to access port number which S3 server does not listen +* You’re trying to access host which is unreachable from your network (e.g. running behind some proxy or VPN) +* There are some firewall restrictions for accessing specific host or port + +#### Using HTTPS protocol for HTTP port + +```text +Caused by: javax.net.ssl.SSLException: Unsupported or unrecognized SSL message +``` + +By default, SparkS3 uses HTTPS protocol for connection. +If you change port number, this does not lead to changing protocol: + +```python +spark_s3 = SparkS3(host="s3provider.com", port=8080, ...) +``` + +You should pass protocol explicitly: + +```python +spark_s3 = SparkS3(host="s3provider.com", port=8080, protocol="http", ...) +``` + +#### SSL certificate is self-signed + +```text +sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target +``` + +To connect to HTTPS port with self-signed certificate, you should +[add certificate chain to Java TrustedStore](https://stackoverflow.com/questions/373295/digital-certificate-how-to-import-cer-file-in-to-truststore-file-using). + +Another option is to disable SSL check: + +```python +spark_s3 = SparkS3( + ..., + extra={ + "connection.ssl.enabled": False, + }, +) +``` + +But is is **NOT** recommended. + +#### Accessing S3 without domain-style access style support + +```text +Caused by: java.net.UnknownHostException: my-bucket.s3provider.com +``` + +To use path-style access, use option below: + +```python +spark_s3 = SparkS3( + host="s3provider.com", + bucket="my-bucket", + ..., + extra={ + "path.style.access": True, + }, +) +``` + +## Slow or unstable writing to S3 + +Hadoop AWS allows to use different writing strategies for different S3 implementations, depending +on list of supported features by server. + +These strategies are called [committers](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/committers.html). +There are [different types of committers](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/committers.html#Switching_to_an_S3A_Committer): + +* `file` (default) +* `directory` +* `partitioned` +* `magic` + +### `file` committer + +This committer is quite slow and unstable, so it is not recommended to use: + +```text +WARN AbstractS3ACommitterFactory: Using standard FileOutputCommitter to commit work. This is slow and potentially unsafe. +``` + +This is caused by the fact it creates files in the temp directory on remote filesystem, and after all of them are written successfully, +they are moved to target directory on same remote filesystem. + +This is not an issue for HDFS which does support file move operations and also support renaming directory +as atomic operation with `O(1)` time complexity. + +But S3 does support only file copying, so moving is performed via copy + delete. +Also it does not support atomic directory rename operation. Instead, renaming files with the same prefix has time complexity `O(n)`. + +### `directory` and `partitioned` committers + +These are [staging committers](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/committer_architecture.html), +meaning that they create temp directories on local filesystem, and after all files are written successfully, +they will be uploaded to S3. Local filesystems do support file moving and directory renaming, +so these committers does not have issues that `file` committer has. + +But they both require free space on local filesystem, and this may be an issue if user need to write large amount of data. +Also this can be an issue for container environment, like Kubernetes, there resources should be allocated before starting a container. + +### `magic` committer + +This committer uses multipart upload feature of S3 API, allowing to create multiple files +and after all of them were written successfully finish the transaction. Before transaction is finished, +files will not be accessible by other clients. + +Because it does not require neither file moving operations, nor directory atomic rename, +upload process is done in most efficient way S3 support. +This [drastically increases writing performance](https://spot.io/blog/improve-apache-spark-performance-with-the-s3-magic-committer/). + +To use this committer, set [following properties](https://github.com/apache/spark/pull/32518) while creating Spark session. + +S3 your main distributed filesystem (Spark on Kubernetes) + +```py +# https://issues.apache.org/jira/browse/SPARK-23977 +# https://spark.apache.org/docs/latest/cloud-integration.html#committing-work-into-cloud-storage-safely-and-fast +spark = ( + SparkSession.builder.appName("spark-app-name") + .config("spark.hadoop.fs.s3a.committer.magic.enabled", "true") + .config("spark.hadoop.fs.s3a.committer.name", "magic") + .config("spark.hadoop.mapreduce.outputcommitter.factory.scheme.s3a", "org.apache.hadoop.fs.s3a.commit.S3ACommitterFactory") + .config("spark.sql.parquet.output.committer.class", "org.apache.spark.internal.io.cloud.BindingParquetOutputCommitter") + .config("spark.sql.sources.commitProtocolClass", "org.apache.spark.internal.io.cloud.PathOutputCommitProtocol") + .getOrCreate() +) +``` + +HDFS is your main distributed filesystem (Spark on Hadoop) + +```py +# https://community.cloudera.com/t5/Support-Questions/spark-sql-sources-partitionOverwriteMode-dynamic-quot-not/m-p/343483/highlight/true +spark = ( + SparkSession.builder.appName("spark-app-name") + .config("spark.hadoop.fs.s3a.committer.magic.enabled", "true") + .config("spark.hadoop.fs.s3a.committer.name", "magic") + .getOrCreate() +) +``` + +#### WARNING +`magic` committer requires S3 implementation to have strong consistency - file upload API return response only +if it was written on enough number of cluster nodes, and any cluster node error does not lead to missing or corrupting files. + +Some S3 implementations does have strong consistency +(like [AWS S3](https://aws.amazon.com/ru/blogs/aws/amazon-s3-update-strong-read-after-write-consistency/) and +[MinIO](https://blog.min.io/migrating-hdfs-to-object-storage/)), some not. Please contact your S3 provider +to get information about S3 implementation consistency. + +#### WARNING +`magic` committer does not support `if_exists="replace_overlapping_partitions"`. +Either use another `if_exists` value, or use `partitioned` committer. + +### See also + +* [directory.marker.retention=”keep”](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/directory_markers.html) + +## Slow reading from S3 + +Please read following documentation: + +* [prefetch.enabled](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/prefetching.html) +* [experimental.input.fadvise](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/performance.html#Improving_data_input_performance_through_fadvise) +* [Parquet and ORC I/O settings](https://spark.apache.org/docs/latest/cloud-integration.html#parquet-io-settings) + +If you’re reading data from row-based formats, like [CSV](../../../file_df/file_formats/csv.md#csv-file-format), prefer +[experimental.input.fadvise=”sequential” with increased readahead.range](https://issues.apache.org/jira/browse/HADOOP-17789?focusedCommentId=17383559#comment-17383559). + +But for other file formats, especially using compression, prefer +[experimental.input.fadvise=”normal”](https://issues.apache.org/jira/browse/HADOOP-17789?focusedCommentId=17383743#comment-17383743) diff --git a/mddocs/docs/_build/markdown/connection/index.md b/mddocs/docs/_build/markdown/connection/index.md new file mode 100644 index 000000000..5ad28edef --- /dev/null +++ b/mddocs/docs/_build/markdown/connection/index.md @@ -0,0 +1,34 @@ + + + DB Connection + +* [DB Connections](db_connection/index.md) + * [Clickhouse](db_connection/clickhouse/index.md) + * [Greenplum](db_connection/greenplum/index.md) + * [Kafka](db_connection/kafka/index.md) + * [Hive](db_connection/hive/index.md) + * [MongoDB](db_connection/mongodb/index.md) + * [MSSQL](db_connection/mssql/index.md) + * [MySQL](db_connection/mysql/index.md) + * [Oracle](db_connection/oracle/index.md) + * [Postgres](db_connection/postgres/index.md) + * [Teradata](db_connection/teradata/index.md) + + File Connection + +* [File Connections](file_connection/index.md) + * [FTP](file_connection/ftp.md) + * [FTPS](file_connection/ftps.md) + * [HDFS](file_connection/hdfs/index.md) + * [Samba](file_connection/samba.md) + * [SFTP](file_connection/sftp.md) + * [S3](file_connection/s3.md) + * [Webdav](file_connection/webdav.md) + + File DataFrame Connection + +* [File DataFrame Connections](file_df_connection/index.md) + * [Spark LocalFS](file_df_connection/spark_local_fs.md) + * [Spark HDFS](file_df_connection/spark_hdfs/index.md) + * [Spark S3](file_df_connection/spark_s3/index.md) + * [Base interface](file_df_connection/base.md) diff --git a/mddocs/docs/_build/markdown/contributing.md b/mddocs/docs/_build/markdown/contributing.md new file mode 100644 index 000000000..739798897 --- /dev/null +++ b/mddocs/docs/_build/markdown/contributing.md @@ -0,0 +1,391 @@ +# Contributing Guide + +Welcome! There are many ways to contribute, including submitting bug +reports, improving documentation, submitting feature requests, reviewing +new submissions, or contributing code that can be incorporated into the +project. + +## Limitations + +We should keep close to these items during development: + +* Some companies still use old Spark versions, like 2.3.1. So it is required to keep compatibility if possible, e.g. adding branches for different Spark versions. +* Different users uses onETL in different ways - some uses only DB connectors, some only files. Connector-specific dependencies should be optional. +* Instead of creating classes with a lot of different options, prefer splitting them into smaller classes, e.g. options class, context manager, etc, and using composition. + +## Initial setup for local development + +### Install Git + +Please follow [instruction](https://docs.github.com/en/get-started/quickstart/set-up-git). + +### Create a fork + +If you are not a member of a development team building onETL, you should create a fork before making any changes. + +Please follow [instruction](https://docs.github.com/en/get-started/quickstart/fork-a-repo). + +### Clone the repo + +Open terminal and run these commands: + +```bash +git clone git@github.com:myuser/onetl.git -b develop + +cd onetl +``` + +### Setup environment + +Create virtualenv and install dependencies: + +```bash +python -m venv venv +source venv/bin/activate +pip install -U wheel +pip install -U pip setuptools +pip install -U \ + -r requirements/core.txt \ + -r requirements/ftp.txt \ + -r requirements/hdfs.txt \ + -r requirements/kerberos.txt \ + -r requirements/s3.txt \ + -r requirements/sftp.txt \ + -r requirements/webdav.txt \ + -r requirements/dev.txt \ + -r requirements/docs.txt \ + -r requirements/tests/base.txt \ + -r requirements/tests/clickhouse.txt \ + -r requirements/tests/kafka.txt \ + -r requirements/tests/mongodb.txt \ + -r requirements/tests/mssql.txt \ + -r requirements/tests/mysql.txt \ + -r requirements/tests/postgres.txt \ + -r requirements/tests/oracle.txt \ + -r requirements/tests/pydantic-2.txt \ + -r requirements/tests/spark-3.5.5.txt + +# TODO: remove after https://github.com/zqmillet/sphinx-plantuml/pull/4 +pip install sphinx-plantuml --no-deps +``` + +### Enable pre-commit hooks + +Install pre-commit hooks: + +```bash +pre-commit install --install-hooks +``` + +Test pre-commit hooks run: + +```bash +pre-commit run +``` + +## How to + +### Run tests locally + +#### Using docker-compose + +Build image for running tests: + +```bash +docker-compose build +``` + +Start all containers with dependencies: + +```bash +docker-compose --profile all up -d +``` + +You can run limited set of dependencies: + +```bash +docker-compose --profile mongodb up -d +``` + +Run tests: + +```bash +docker-compose run --rm onetl ./run_tests.sh +``` + +You can pass additional arguments, they will be passed to pytest: + +```bash +docker-compose run --rm onetl ./run_tests.sh -m mongodb -lsx -vvvv --log-cli-level=INFO +``` + +You can run interactive bash session and use it: + +```bash +docker-compose run --rm onetl bash + +./run_tests.sh -m mongodb -lsx -vvvv --log-cli-level=INFO +``` + +See logs of test container: + +```bash +docker-compose logs -f onetl +``` + +Stop all containers and remove created volumes: + +```bash +docker-compose --profile all down -v +``` + +#### Without docker-compose + +#### WARNING +To run HDFS tests locally you should add the following line to your `/etc/hosts` (file path depends on OS): + +```default +# HDFS server returns container hostname as connection address, causing error in DNS resolution +127.0.0.1 hdfs +``` + +#### NOTE +To run Oracle tests you need to install [Oracle instantclient](https://www.oracle.com/database/technologies/instant-client.html), +and pass its path to `ONETL_ORA_CLIENT_PATH` and `LD_LIBRARY_PATH` environment variables, +e.g. `ONETL_ORA_CLIENT_PATH=/path/to/client64/lib`. + +It may also require to add the same path into `LD_LIBRARY_PATH` environment variable + +#### NOTE +To run Greenplum tests, you should: + +* Download [VMware Greenplum connector for Spark](https://onetl.readthedocs.io/en/latest/connection/db_connection/greenplum/prerequisites.html) +* Either move it to `~/.ivy2/jars/`, or pass file path to `CLASSPATH` +* Set environment variable `ONETL_GP_PACKAGE_VERSION=local`. + +Start all containers with dependencies: + +```bash +docker-compose --profile all up -d +``` + +You can run limited set of dependencies: + +```bash +docker-compose --profile mongodb up -d +``` + +Load environment variables with connection properties: + +```bash +source .env.local +``` + +Run tests: + +```bash +./run_tests.sh +``` + +You can pass additional arguments, they will be passed to pytest: + +```bash +./run_tests.sh -m mongodb -lsx -vvvv --log-cli-level=INFO +``` + +Stop all containers and remove created volumes: + +```bash +docker-compose --profile all down -v +``` + +### Build documentation + +Build documentation using Sphinx: + +```bash +cd docs +make html +``` + +Then open in browser `docs/_build/index.html`. + +## Review process + +Please create a new GitHub issue for any significant changes and +enhancements that you wish to make. Provide the feature you would like +to see, why you need it, and how it will work. Discuss your ideas +transparently and get community feedback before proceeding. + +Significant Changes that you wish to contribute to the project should be +discussed first in a GitHub issue that clearly outlines the changes and +benefits of the feature. + +Small Changes can directly be crafted and submitted to the GitHub +Repository as a Pull Request. + +### Create pull request + +Commit your changes: + +```bash +git commit -m "Commit message" +git push +``` + +Then open Github interface and [create pull request](https://docs.github.com/en/get-started/quickstart/contributing-to-projects#making-a-pull-request). +Please follow guide from PR body template. + +After pull request is created, it get a corresponding number, e.g. 123 (`pr_number`). + +### Write release notes + +`onETL` uses [towncrier](https://pypi.org/project/towncrier/) +for changelog management. + +To submit a change note about your PR, add a text file into the +[docs/changelog/next_release](./next_release) folder. It should contain an +explanation of what applying this PR will change in the way +end-users interact with the project. One sentence is usually +enough but feel free to add as many details as you feel necessary +for the users to understand what it means. + +**Use the past tense** for the text in your fragment because, +combined with others, it will be a part of the “news digest” +telling the readers **what changed** in a specific version of +the library *since the previous version*. + +You should also use +reStructuredText syntax for highlighting code (inline or block), +linking parts of the docs or external sites. +If you wish to sign your change, feel free to add `-- by +:user:`github-username`` at the end (replace `github-username` +with your own!). + +Finally, name your file following the convention that Towncrier +understands: it should start with the number of an issue or a +PR followed by a dot, then add a patch type, like `feature`, +`doc`, `misc` etc., and add `.rst` as a suffix. If you +need to add more than one fragment, you may add an optional +sequence number (delimited with another period) between the type +and the suffix. + +In general the name will follow `..rst` pattern, +where the categories are: + +- `feature`: Any new feature +- `bugfix`: A bug fix +- `improvement`: An improvement +- `doc`: A change to the documentation +- `dependency`: Dependency-related changes +- `misc`: Changes internal to the repo like CI, test and build changes + +A pull request may have more than one of these components, for example +a code change may introduce a new feature that deprecates an old +feature, in which case two fragments should be added. It is not +necessary to make a separate documentation fragment for documentation +changes accompanying the relevant code changes. + +#### Examples for adding changelog entries to your Pull Requests + +```rst +Added a ``:github:user:`` role to Sphinx config -- by :github:user:`someuser` +``` + +```rst +Fixed behavior of ``WebDAV`` connector -- by :github:user:`someuser` +``` + +```rst +Added support of ``timeout`` in ``S3`` connector +-- by :github:user:`someuser`, :github:user:`anotheruser` and :github:user:`otheruser` +``` + +#### How to skip change notes check? + +Just add `ci:skip-changelog` label to pull request. + +#### Release Process + +Before making a release from the `develop` branch, follow these steps: + +1. Checkout to `develop` branch and update it to the actual state + +```bash +git checkout develop +git pull -p +``` + +1. Backup `NEXT_RELEASE.rst` + +```bash +cp "docs/changelog/NEXT_RELEASE.rst" "docs/changelog/temp_NEXT_RELEASE.rst" +``` + +1. Build the Release notes with Towncrier + +```bash +VERSION=$(cat onetl/VERSION) +towncrier build "--version=${VERSION}" --yes +``` + +1. Change file with changelog to release version number + +```bash +mv docs/changelog/NEXT_RELEASE.rst "docs/changelog/${VERSION}.rst" +``` + +1. Remove content above the version number heading in the `${VERSION}.rst` file + +```bash +awk '!/^.*towncrier release notes start/' "docs/changelog/${VERSION}.rst" > temp && mv temp "docs/changelog/${VERSION}.rst" +``` + +1. Update Changelog Index + +```bash +awk -v version=${VERSION} '/DRAFT/{print;print " " version;next}1' docs/changelog/index.rst > temp && mv temp docs/changelog/index.rst +``` + +1. Restore `NEXT_RELEASE.rst` file from backup + +```bash +mv "docs/changelog/temp_NEXT_RELEASE.rst" "docs/changelog/NEXT_RELEASE.rst" +``` + +1. Commit and push changes to `develop` branch + +```bash +git add . +git commit -m "Prepare for release ${VERSION}" +git push +``` + +1. Merge `develop` branch to `master`, **WITHOUT** squashing + +```bash +git checkout master +git pull +git merge develop +git push +``` + +1. Add git tag to the latest commit in `master` branch + +```bash +git tag "$VERSION" +git push origin "$VERSION" +``` + +1. Update version in `develop` branch **after release**: + +```bash +git checkout develop + +NEXT_VERSION=$(echo "$VERSION" | awk -F. '/[0-9]+\./{$NF++;print}' OFS=.) +echo "$NEXT_VERSION" > onetl/VERSION + +git add . +git commit -m "Bump version" +git push +``` diff --git a/mddocs/docs/_build/markdown/db/db_reader.md b/mddocs/docs/_build/markdown/db/db_reader.md new file mode 100644 index 000000000..0c7be1def --- /dev/null +++ b/mddocs/docs/_build/markdown/db/db_reader.md @@ -0,0 +1,3 @@ + + +# DB Reader diff --git a/mddocs/docs/_build/markdown/db/db_writer.md b/mddocs/docs/_build/markdown/db/db_writer.md new file mode 100644 index 000000000..313801b96 --- /dev/null +++ b/mddocs/docs/_build/markdown/db/db_writer.md @@ -0,0 +1,3 @@ + + +# DB Writer diff --git a/mddocs/docs/_build/markdown/db/index.md b/mddocs/docs/_build/markdown/db/index.md new file mode 100644 index 000000000..861a539ff --- /dev/null +++ b/mddocs/docs/_build/markdown/db/index.md @@ -0,0 +1,6 @@ + + + DB classes + +* [DB Reader](db_reader.md) +* [DB Writer](db_writer.md) diff --git a/mddocs/docs/_build/markdown/file/file_downloader/file_downloader.md b/mddocs/docs/_build/markdown/file/file_downloader/file_downloader.md new file mode 100644 index 000000000..fd9de1c6a --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_downloader/file_downloader.md @@ -0,0 +1,3 @@ + + +# File Downloader diff --git a/mddocs/docs/_build/markdown/file/file_downloader/index.md b/mddocs/docs/_build/markdown/file/file_downloader/index.md new file mode 100644 index 000000000..3a7b4164b --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_downloader/index.md @@ -0,0 +1,9 @@ + + +# File Downloader + +# File Downloader + +* [File Downloader](file_downloader.md) +* [File Downloader Options](options.md) +* [File Downloader Result](result.md) diff --git a/mddocs/docs/_build/markdown/file/file_downloader/options.md b/mddocs/docs/_build/markdown/file/file_downloader/options.md new file mode 100644 index 000000000..1699758c8 --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_downloader/options.md @@ -0,0 +1,3 @@ + + +# File Downloader Options diff --git a/mddocs/docs/_build/markdown/file/file_downloader/result.md b/mddocs/docs/_build/markdown/file/file_downloader/result.md new file mode 100644 index 000000000..56d7f76a0 --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_downloader/result.md @@ -0,0 +1,3 @@ + + +# File Downloader Result diff --git a/mddocs/docs/_build/markdown/file/file_filters/base.md b/mddocs/docs/_build/markdown/file/file_filters/base.md new file mode 100644 index 000000000..168e50ede --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_filters/base.md @@ -0,0 +1,3 @@ + + +# Base interface diff --git a/mddocs/docs/_build/markdown/file/file_filters/exclude_dir.md b/mddocs/docs/_build/markdown/file/file_filters/exclude_dir.md new file mode 100644 index 000000000..1a1d5c327 --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_filters/exclude_dir.md @@ -0,0 +1,3 @@ + + +# ExcludeDir diff --git a/mddocs/docs/_build/markdown/file/file_filters/file_filter.md b/mddocs/docs/_build/markdown/file/file_filters/file_filter.md new file mode 100644 index 000000000..9e2ff94a4 --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_filters/file_filter.md @@ -0,0 +1,3 @@ + + +# File Filter (legacy) diff --git a/mddocs/docs/_build/markdown/file/file_filters/file_mtime_filter.md b/mddocs/docs/_build/markdown/file/file_filters/file_mtime_filter.md new file mode 100644 index 000000000..1b09868e0 --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_filters/file_mtime_filter.md @@ -0,0 +1,3 @@ + + +# FileModifiedTime diff --git a/mddocs/docs/_build/markdown/file/file_filters/file_size_filter.md b/mddocs/docs/_build/markdown/file/file_filters/file_size_filter.md new file mode 100644 index 000000000..92260d249 --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_filters/file_size_filter.md @@ -0,0 +1,3 @@ + + +# FileSizeRange diff --git a/mddocs/docs/_build/markdown/file/file_filters/glob.md b/mddocs/docs/_build/markdown/file/file_filters/glob.md new file mode 100644 index 000000000..115269b2f --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_filters/glob.md @@ -0,0 +1,3 @@ + + +# Glob diff --git a/mddocs/docs/_build/markdown/file/file_filters/index.md b/mddocs/docs/_build/markdown/file/file_filters/index.md new file mode 100644 index 000000000..40155ae9a --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_filters/index.md @@ -0,0 +1,20 @@ + + +# File Filters + +# File filters + +* [Glob](glob.md) +* [Regexp](regexp.md) +* [ExcludeDir](exclude_dir.md) +* [FileSizeRange](file_size_filter.md) +* [FileModifiedTime](file_mtime_filter.md) + +# Legacy + +* [File Filter (legacy)](file_filter.md) + +# For developers + +* [Base interface](base.md) +* [match_all_filters](match_all_filters.md) diff --git a/mddocs/docs/_build/markdown/file/file_filters/match_all_filters.md b/mddocs/docs/_build/markdown/file/file_filters/match_all_filters.md new file mode 100644 index 000000000..aa9d094ce --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_filters/match_all_filters.md @@ -0,0 +1,3 @@ + + +# match_all_filters diff --git a/mddocs/docs/_build/markdown/file/file_filters/regexp.md b/mddocs/docs/_build/markdown/file/file_filters/regexp.md new file mode 100644 index 000000000..80fe7c2a5 --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_filters/regexp.md @@ -0,0 +1,3 @@ + + +# Regexp diff --git a/mddocs/docs/_build/markdown/file/file_limits/base.md b/mddocs/docs/_build/markdown/file/file_limits/base.md new file mode 100644 index 000000000..cdf6d9b1b --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_limits/base.md @@ -0,0 +1,3 @@ + + +# Base interface diff --git a/mddocs/docs/_build/markdown/file/file_limits/file_limit.md b/mddocs/docs/_build/markdown/file/file_limits/file_limit.md new file mode 100644 index 000000000..ddf6ecf6e --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_limits/file_limit.md @@ -0,0 +1,3 @@ + + +# File Limit (legacy) diff --git a/mddocs/docs/_build/markdown/file/file_limits/index.md b/mddocs/docs/_build/markdown/file/file_limits/index.md new file mode 100644 index 000000000..be23f82da --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_limits/index.md @@ -0,0 +1,19 @@ + + +# File Limits + +# File limits + +* [MaxFilesCount](max_files_count.md) +* [TotalFilesSize](total_files_size.md) + +# Legacy + +* [File Limit (legacy)](file_limit.md) + +# For developers + +* [Base interface](base.md) +* [limits_stop_at](limits_stop_at.md) +* [limits_reached](limits_reached.md) +* [reset_limits](reset_limits.md) diff --git a/mddocs/docs/_build/markdown/file/file_limits/limits_reached.md b/mddocs/docs/_build/markdown/file/file_limits/limits_reached.md new file mode 100644 index 000000000..5d04fbf86 --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_limits/limits_reached.md @@ -0,0 +1,3 @@ + + +# limits_reached diff --git a/mddocs/docs/_build/markdown/file/file_limits/limits_stop_at.md b/mddocs/docs/_build/markdown/file/file_limits/limits_stop_at.md new file mode 100644 index 000000000..8fc53c1b0 --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_limits/limits_stop_at.md @@ -0,0 +1,3 @@ + + +# limits_stop_at diff --git a/mddocs/docs/_build/markdown/file/file_limits/max_files_count.md b/mddocs/docs/_build/markdown/file/file_limits/max_files_count.md new file mode 100644 index 000000000..e01ca0a84 --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_limits/max_files_count.md @@ -0,0 +1,3 @@ + + +# MaxFilesCount diff --git a/mddocs/docs/_build/markdown/file/file_limits/reset_limits.md b/mddocs/docs/_build/markdown/file/file_limits/reset_limits.md new file mode 100644 index 000000000..80ce0389c --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_limits/reset_limits.md @@ -0,0 +1,3 @@ + + +# reset_limits diff --git a/mddocs/docs/_build/markdown/file/file_limits/total_files_size.md b/mddocs/docs/_build/markdown/file/file_limits/total_files_size.md new file mode 100644 index 000000000..33f96fe55 --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_limits/total_files_size.md @@ -0,0 +1,3 @@ + + +# TotalFilesSize diff --git a/mddocs/docs/_build/markdown/file/file_mover/file_mover.md b/mddocs/docs/_build/markdown/file/file_mover/file_mover.md new file mode 100644 index 000000000..eb25aaf6d --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_mover/file_mover.md @@ -0,0 +1,3 @@ + + +# File Mover diff --git a/mddocs/docs/_build/markdown/file/file_mover/index.md b/mddocs/docs/_build/markdown/file/file_mover/index.md new file mode 100644 index 000000000..c8b55c7d0 --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_mover/index.md @@ -0,0 +1,9 @@ + + +# File Mover + +# File Mover + +* [File Mover](file_mover.md) +* [File Mover Options](options.md) +* [File Mover Result](result.md) diff --git a/mddocs/docs/_build/markdown/file/file_mover/options.md b/mddocs/docs/_build/markdown/file/file_mover/options.md new file mode 100644 index 000000000..1bbc40fc8 --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_mover/options.md @@ -0,0 +1,3 @@ + + +# File Mover Options diff --git a/mddocs/docs/_build/markdown/file/file_mover/result.md b/mddocs/docs/_build/markdown/file/file_mover/result.md new file mode 100644 index 000000000..34121e2f4 --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_mover/result.md @@ -0,0 +1,3 @@ + + +# File Mover Result diff --git a/mddocs/docs/_build/markdown/file/file_uploader/file_uploader.md b/mddocs/docs/_build/markdown/file/file_uploader/file_uploader.md new file mode 100644 index 000000000..1a0488c5b --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_uploader/file_uploader.md @@ -0,0 +1,3 @@ + + +# File Uploader diff --git a/mddocs/docs/_build/markdown/file/file_uploader/index.md b/mddocs/docs/_build/markdown/file/file_uploader/index.md new file mode 100644 index 000000000..e11581a9c --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_uploader/index.md @@ -0,0 +1,9 @@ + + +# File Uploader + +# File Uploader + +* [File Uploader](file_uploader.md) +* [File Uploader Options](options.md) +* [File Uploader Result](result.md) diff --git a/mddocs/docs/_build/markdown/file/file_uploader/options.md b/mddocs/docs/_build/markdown/file/file_uploader/options.md new file mode 100644 index 000000000..4cf5cf73b --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_uploader/options.md @@ -0,0 +1,3 @@ + + +# File Uploader Options diff --git a/mddocs/docs/_build/markdown/file/file_uploader/result.md b/mddocs/docs/_build/markdown/file/file_uploader/result.md new file mode 100644 index 000000000..8050abb92 --- /dev/null +++ b/mddocs/docs/_build/markdown/file/file_uploader/result.md @@ -0,0 +1,3 @@ + + +# File Uploader Result diff --git a/mddocs/docs/_build/markdown/file/index.md b/mddocs/docs/_build/markdown/file/index.md new file mode 100644 index 000000000..2da812bab --- /dev/null +++ b/mddocs/docs/_build/markdown/file/index.md @@ -0,0 +1,9 @@ + + + File classes + +* [File Downloader](file_downloader/index.md) +* [File Uploader](file_uploader/index.md) +* [File Mover](file_mover/index.md) +* [File Filters](file_filters/index.md) +* [File Limits](file_limits/index.md) diff --git a/mddocs/docs/_build/markdown/file_df/file_df_reader/file_df_reader.md b/mddocs/docs/_build/markdown/file_df/file_df_reader/file_df_reader.md new file mode 100644 index 000000000..f0099864b --- /dev/null +++ b/mddocs/docs/_build/markdown/file_df/file_df_reader/file_df_reader.md @@ -0,0 +1,3 @@ + + +# FileDF Reader diff --git a/mddocs/docs/_build/markdown/file_df/file_df_reader/index.md b/mddocs/docs/_build/markdown/file_df/file_df_reader/index.md new file mode 100644 index 000000000..0087e74ac --- /dev/null +++ b/mddocs/docs/_build/markdown/file_df/file_df_reader/index.md @@ -0,0 +1,8 @@ + + +# FileDF Reader + +# FileDF Reader + +* [FileDF Reader](file_df_reader.md) +* [Options](options.md) diff --git a/mddocs/docs/_build/markdown/file_df/file_df_reader/options.md b/mddocs/docs/_build/markdown/file_df/file_df_reader/options.md new file mode 100644 index 000000000..126620300 --- /dev/null +++ b/mddocs/docs/_build/markdown/file_df/file_df_reader/options.md @@ -0,0 +1,3 @@ + + +# Options diff --git a/mddocs/docs/_build/markdown/file_df/file_df_writer/file_df_writer.md b/mddocs/docs/_build/markdown/file_df/file_df_writer/file_df_writer.md new file mode 100644 index 000000000..e3a9f5ad0 --- /dev/null +++ b/mddocs/docs/_build/markdown/file_df/file_df_writer/file_df_writer.md @@ -0,0 +1,3 @@ + + +# FileDF Writer diff --git a/mddocs/docs/_build/markdown/file_df/file_df_writer/index.md b/mddocs/docs/_build/markdown/file_df/file_df_writer/index.md new file mode 100644 index 000000000..9813ed1d4 --- /dev/null +++ b/mddocs/docs/_build/markdown/file_df/file_df_writer/index.md @@ -0,0 +1,8 @@ + + +# FileDF Writer + +# FileDF Writer + +* [FileDF Writer](file_df_writer.md) +* [Options](options.md) diff --git a/mddocs/docs/_build/markdown/file_df/file_df_writer/options.md b/mddocs/docs/_build/markdown/file_df/file_df_writer/options.md new file mode 100644 index 000000000..d53a2df29 --- /dev/null +++ b/mddocs/docs/_build/markdown/file_df/file_df_writer/options.md @@ -0,0 +1,3 @@ + + +# Options diff --git a/mddocs/docs/_build/markdown/file_df/file_formats/avro.md b/mddocs/docs/_build/markdown/file_df/file_formats/avro.md new file mode 100644 index 000000000..80f484f01 --- /dev/null +++ b/mddocs/docs/_build/markdown/file_df/file_formats/avro.md @@ -0,0 +1,3 @@ + + +# Avro diff --git a/mddocs/docs/_build/markdown/file_df/file_formats/base.md b/mddocs/docs/_build/markdown/file_df/file_formats/base.md new file mode 100644 index 000000000..e7522ea8b --- /dev/null +++ b/mddocs/docs/_build/markdown/file_df/file_formats/base.md @@ -0,0 +1,3 @@ + + +# Base interface diff --git a/mddocs/docs/_build/markdown/file_df/file_formats/csv.md b/mddocs/docs/_build/markdown/file_df/file_formats/csv.md new file mode 100644 index 000000000..6a52871e2 --- /dev/null +++ b/mddocs/docs/_build/markdown/file_df/file_formats/csv.md @@ -0,0 +1,3 @@ + + +# CSV diff --git a/mddocs/docs/_build/markdown/file_df/file_formats/excel.md b/mddocs/docs/_build/markdown/file_df/file_formats/excel.md new file mode 100644 index 000000000..3d71a09d0 --- /dev/null +++ b/mddocs/docs/_build/markdown/file_df/file_formats/excel.md @@ -0,0 +1,3 @@ + + +# Excel diff --git a/mddocs/docs/_build/markdown/file_df/file_formats/index.md b/mddocs/docs/_build/markdown/file_df/file_formats/index.md new file mode 100644 index 000000000..a6500bdf3 --- /dev/null +++ b/mddocs/docs/_build/markdown/file_df/file_formats/index.md @@ -0,0 +1,18 @@ + + +# File Formats + +# File formats + +* [Avro](avro.md) +* [CSV](csv.md) +* [Excel](excel.md) +* [JSON](json.md) +* [JSONLine](jsonline.md) +* [ORC](orc.md) +* [Parquet](parquet.md) +* [XML](xml.md) + +# For developers + +* [Base interface](base.md) diff --git a/mddocs/docs/_build/markdown/file_df/file_formats/json.md b/mddocs/docs/_build/markdown/file_df/file_formats/json.md new file mode 100644 index 000000000..7ce67ce99 --- /dev/null +++ b/mddocs/docs/_build/markdown/file_df/file_formats/json.md @@ -0,0 +1,3 @@ + + +# JSON diff --git a/mddocs/docs/_build/markdown/file_df/file_formats/jsonline.md b/mddocs/docs/_build/markdown/file_df/file_formats/jsonline.md new file mode 100644 index 000000000..f65f59bdd --- /dev/null +++ b/mddocs/docs/_build/markdown/file_df/file_formats/jsonline.md @@ -0,0 +1,3 @@ + + +# JSONLine diff --git a/mddocs/docs/_build/markdown/file_df/file_formats/orc.md b/mddocs/docs/_build/markdown/file_df/file_formats/orc.md new file mode 100644 index 000000000..bbcdbbf34 --- /dev/null +++ b/mddocs/docs/_build/markdown/file_df/file_formats/orc.md @@ -0,0 +1,3 @@ + + +# ORC diff --git a/mddocs/docs/_build/markdown/file_df/file_formats/parquet.md b/mddocs/docs/_build/markdown/file_df/file_formats/parquet.md new file mode 100644 index 000000000..e831eb1bd --- /dev/null +++ b/mddocs/docs/_build/markdown/file_df/file_formats/parquet.md @@ -0,0 +1,3 @@ + + +# Parquet diff --git a/mddocs/docs/_build/markdown/file_df/file_formats/xml.md b/mddocs/docs/_build/markdown/file_df/file_formats/xml.md new file mode 100644 index 000000000..0853f499f --- /dev/null +++ b/mddocs/docs/_build/markdown/file_df/file_formats/xml.md @@ -0,0 +1,3 @@ + + +# XML diff --git a/mddocs/docs/_build/markdown/file_df/index.md b/mddocs/docs/_build/markdown/file_df/index.md new file mode 100644 index 000000000..c225567d6 --- /dev/null +++ b/mddocs/docs/_build/markdown/file_df/index.md @@ -0,0 +1,7 @@ + + + File DataFrame classes + +* [FileDF Reader](file_df_reader/index.md) +* [FileDF Writer](file_df_writer/index.md) +* [File Formats](file_formats/index.md) diff --git a/mddocs/docs/_build/markdown/hooks/design.md b/mddocs/docs/_build/markdown/hooks/design.md new file mode 100644 index 000000000..e4f786f65 --- /dev/null +++ b/mddocs/docs/_build/markdown/hooks/design.md @@ -0,0 +1,642 @@ + + +# High level design + +## What are hooks? + +Hook mechanism is a part of onETL which allows to inject some additional behavior into +existing methods of (almost) any class. + +### Features + +Hooks mechanism allows to: + +* Inspect and validate input arguments and output results of method call +* Access, modify or replace method call result (but NOT input arguments) +* Wrap method calls with a context manager and catch raised exceptions + +Hooks can be placed into [Plugins](../plugins.md#plugins), allowing to modify onETL behavior by installing some additional package. + +### Limitations + +* Hooks can be bound to methods of a class only (not functions). +* Only methods decorated with [@slot decorator](slot.md#slot-decorator) implement hooks mechanism. These class and methods are marked as [![support_hooks](https://img.shields.io/badge/%20-support%20hooks-blue)](https://onetl.readthedocs.io/en/0.13.5/hooks/index.html). +* Hooks can be bound to public methods only. + +## Terms + +* [@slot decorator](slot.md#slot-decorator) - method of a class with a special decorator +* `Callback` - function which implements some additional logic which modifies slot behavior +* [@hook decorator](hook.md#hook-decorator) - wrapper around callback which stores hook state, priority and some useful methods +* `Hooks mechanism` - calling `Slot()` will call all enabled hooks which are bound to the slot. Implemented by [@support_hooks decorator](support_hooks.md#support-hooks-decorator). + +## How to implement hooks? + +### TL;DR + +```python +from onetl.hooks import support_hooks, slot, hook + + +@support_hooks # enabling hook mechanism for the class +class MyClass: + def __init__(self, data): + self.data = data + + # this is slot + @slot + def method(self, arg): + pass + + +@MyClass.method.bind # bound hook to the slot +@hook # this is hook +def callback(obj, arg): # this is callback + print(obj.data, arg) + + +obj = MyClass(1) +obj.method(2) # will call callback(obj, 1) + +# prints "1 2" +``` + +#### Define a slot + +* Create a class with a method: + +```python +class MyClass: + def __init__(self, data): + self.data = data + + def method(self, arg): + return self.data, arg +``` + +* Add [@slot decorator](slot.md#slot-decorator) to the method: + +```python +from onetl.hooks import support_hooks, slot, hook + + +class MyClass: + @slot + def method(self, arg): + return self.data, arg +``` + +If method has other decorators like `@classmethod` or `@staticmethod`, `@slot` should be placed on the top: + +```python +from onetl.hooks import support_hooks, slot, hook + + +class MyClass: + @slot + @classmethod + def class_method(cls, arg): + return cls, arg + + @slot + @staticmethod + def static_method(arg): + return arg +``` + +* Add [@support_hooks decorator](support_hooks.md#support-hooks-decorator) to the class: + +```python +from onetl.hooks import support_hooks, slot, hook + + +@support_hooks +class MyClass: + @slot + def method(self, arg): + return self.data, arg +``` + +Slot is created. + +#### Define a callback + +Define some function (a.k.a callback): + +```python +def callback(self, arg): + print(self.data, arg) +``` + +It should have signature *compatible* with `MyClass.method`. *Compatible* does not mean *exactly the same* - +for example, you can rename positional arguments: + +```python +def callback(obj, arg): + print(obj.data, arg) +``` + +Use `*args` and `**kwargs` to omit arguments you don’t care about: + +```python +def callback(obj, *args, **kwargs): + print(obj.data, args, kwargs) +``` + +There is also an argument `method_name` which has a special meaning - +the method name which the callback is bound to is passed into this argument: + +```python +def callback(obj, *args, method_name: str, **kwargs): + print(obj.data, args, method_name, kwargs) +``` + +#### NOTE +`method_name` should always be a keyword argument, **NOT** positional. + +#### WARNING +If callback signature is not compatible with slot signature, an exception will be raised, +but **ONLY** while slot is called. + +#### Define a hook + +Add [@hook decorator](hook.md#hook-decorator) to create a hook from your callback: + +```python +@hook +def callback(obj, arg): + print(obj.data, arg) +``` + +You can pass more options to the `@hook` decorator, like state or priority. +See decorator documentation for more details. + +#### Bind hook to the slot + +Use `Slot.bind` method to bind hook to the slot: + +```python +@MyClass.method.bind +@hook +def callback(obj, arg): + print(obj, arg) +``` + +You can bind more than one hook to the same slot, and bind same hook to multiple slots: + +```python +@MyClass.method1.bind +@MyClass.method2.bind +@hook +def callback1(obj, arg): + "Will be called by both MyClass.method1 and MyClass.method2" + + +@MyClass.method1.bind +@hook +def callback2(obj, arg): + "Will be called by MyClass.method1 too" +``` + +## How hooks are called? + +### General + +Just call the method decorated by `@slot` to trigger the hook: + +```python +obj = MyClass(1) +obj.method(2) # will call callback(obj, 2) + +# prints "1 2" +``` + +There are some special callback types that has a slightly different behavior. + +### Context managers + +`@hook` decorator can be placed on a context manager class: + +```python +@hook +class ContextManager: + def __init__(self, obj, arg): + self.obj = obj + self.arg = arg + + def __enter__(self): + # do something on enter + print(obj.data, arg) + return self + + def __exit__(self, exc_type, exc_value, traceback): + # do something on exit + return False +``` + +Context manager is entered while calling the `Slot()`, and exited then the call is finished. + +If present, method `process_result` has a special meaning - +it can receive `MyClass.method` call result, and also modify/replace it: + +```python +@hook +class ContextManager: + def __init__(self, obj, arg): + self.obj = obj + self.arg = arg + + def __enter__(self): + # do something on enter + print(obj.data, arg) + return self + + def __exit__(self, exc_type, exc_value, traceback): + # do something on exit + return False + + def process_result(self, result): + # do something with method call result + return modified(result) +``` + +See examples below for more information. + +### Generator function + +`@hook` decorator can be placed on a generator function: + +```python +@hook +def callback(obj, arg): + print(obj.data, arg) + # this is called before original method body + + yield # method is called here + + # this is called after original method body +``` + +It is converted to a context manager, in the same manner as +[contextlib.contextmanager](https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager). + +Generator body can be wrapped with `try..except..finally` to catch exceptions: + +```python +@hook +def callback(obj, arg): + print(obj.data, arg) + + try: + # this is called before original method body + + yield # method is called here + except Exception as e: + process_exception(a) + finally: + # this is called after original method body + finalizer() +``` + +There is also a special syntax which allows generator to access and modify/replace method call result: + +```default +@hook +def callback(obj, arg): + original_result = yield # method is called here + + new_result = do_something(original_result) + + yield new_result # modify/replace the result +``` + +### Calling hooks in details + +* The callback will be called with the same arguments as the original method. + * If slot is a regular method: + ```python + callback_result = callback(self, *args, **kwargs) + ``` + + Here `self` is a class instance (`obj`). + * If slot is a class method: + ```python + callback_result = callback(cls, *args, **kwargs) + ``` + + Here `cls` is the class itself (`MyClass`). + * If slot is a static method: + ```python + callback_result = callback(*args, **kwargs) + ``` + + Neither object not class are passed to the callback in this case. +* If `callback_result` is a context manager, enter the context. Context manager can catch all the exceptions raised. + > If there are multiple hooks bound the the slot, every context manager will be entered. +* Then call the original method wrapped by `@slot`: + ```python + original_result = method(*args, **kwargs) + ``` +* Process `original_result`: + * If `callback_result` object has method `process_result`, or is a generator wrapped with `@hook`, call it: + ```python + new_result = callback_result.process_result(original_result) + ``` + * Otherwise set `new_result = callback_result`. + * If there are multiple hooks bound the the method, pass `new_result` through the chain: + ```python + new_result = callback1_result.process_result(original_result) + new_result = callback2_result.process_result(new_result or original_result) + new_result = callback3_result.process_result(new_result or original_result) + ``` +* Finally return: + ```python + return new_result or original_result + ``` + + All `None` values are ignored on every step above. +* Exit all the context managers entered during the slot call. + +### Hooks priority + +Hooks are executed in the following order: + +1. Parent class slot + `FIRST` +2. Inherited class slot + `FIRST` +3. Parent class slot + `NORMAL` +4. Inherited class slot + `NORMAL` +5. Parent class slot + `LAST` +6. Inherited class slot + `LAST` + +Hooks with the same priority and inheritance will be executed in the same order they were registered (`Slot.bind` call). + +#### NOTE +Calls of `super()` inside inherited class methods does not trigger hooks call. +Hooks are triggered only if method is called explicitly. + +This allow to wrap with a hook the entire slot call without influencing its internal logic. + +### Hook types + +Here are several examples of using hooks. These types are not exceptional, they can be mixed - for example, +hook can both modify method result and catch exceptions. + +#### Before hook + +Can be used for inspecting or validating input args of the original function: + +```python +@hook +def before1(obj, arg): + print(obj, arg) + # original method is called after exiting this function + + +@hook +def before2(obj, arg): + if arg == 1: + raise ValueError("arg=1 is not allowed") + return None # return None is the same as no return statement +``` + +Executed before calling the original method wrapped by `@slot`. +If hook raises an exception, method will not be called at all. + +#### After hook + +Can be used for performing some actions after original method was successfully executed: + +```python +@hook +def after1(obj, arg): + yield # original method is called here + print(obj, arg) + + +@hook +def after2(obj, arg): + yield None # yielding None is the same as empty yield + if arg == 1: + raise ValueError("arg=1 is not allowed") +``` + +If original method raises an exception, the block of code after `yield` will not be called. + +#### Context hook + +Can be used for catching and handling some exceptions, or to determine that there was no exception during slot call: + +Generator syntax + +```py +# This is just the same as using @contextlib.contextmanager + +@hook +def context_generator(obj, arg): + try: + yield # original method is called here + print(obj, arg) # <-- this line will not be called if method raised an exception + except SomeException as e: + magic(e) + finally: + finalizer() +``` + +Context manager syntax + +```py +@hook +class ContextManager: + def __init__(self, obj, args): + self.obj = obj + self.args = args + + def __enter__(self): + return self + + # original method is called between __enter__ and __exit__ + + def __exit__(self, exc_type, exc_value, traceback): + result = False + if exc_type is not None and isinstance(exc_value, SomeException): + magic(exc_value) + result = True # suppress exception + else: + print(self.obj, self.arg) + finalizer() + return result +``` + +#### NOTE +Contexts are exited in the reverse order of the hook calls. +So if some hook raised an exception, it will be passed into the previous hook, not the next one. + +It is recommended to specify the proper priority for the hook, e.g. `FIRST` + +#### Replacing result hook + +Replaces the output result of the original method. + +Can be used for delegating some implementation details for third-party extensions. +See [Hive](../connection/db_connection/hive/index.md#hive) and [HDFS](../connection/file_connection/hdfs/index.md#hdfs) as an example. + +```python +@hook +def replace1(obj, arg): + result = arg + 10 # any non-None return result + + # original method call result is ignored, output will always be arg + 10 + return result + + +@hook +def replace2(obj, arg): + yield arg + 10 # same as above +``` + +#### NOTE +If there are multiple hooks bound to the same slot, the result of last hook will be used. +It is recommended to specify the proper priority for the hook, e.g. `LAST` + +#### Accessing result hook + +Can access output result of the original method and inspect or validate it: + +Generator syntax + +```py +@hook +def access_result(obj, arg): + result = yield # original method is called here, and result can be used in the hook + print(result) + yield # does not modify result +``` + +Context manager syntax + +```py +@hook +class ModifiesResult: + def __init__(self, obj, args): + self.obj = obj + self.args = args + + def __enter__(self): + return self + + # original method is called between __enter__ and __exit__ + # result is passed into process_result method of context manager, if present + + def process_result(self, result): + print(result) # result can be used in the hook + return None # does not modify result. same as no return statement in the method + + def __exit__(self, exc_type, exc_value, traceback): + return False +``` + +#### Modifying result hook + +Can access output result of the original method, and return the modified one: + +Generator syntax + +```py +@hook +def modifies_result(obj, arg): + result = yield # original method is called here, and result can be used in the hook + yield result + 10 # modify output result. None values are ignored +``` + +Context manager syntax + +```py +@hook +class ModifiesResult: + def __init__(self, obj, args): + self.obj = obj + self.args = args + + def __enter__(self): + return self + + # original method is called between __enter__ and __exit__ + # result is passed into process_result method of context manager, if present + + def process_result(self, result): + print(result) # result can be used in the hook + return result + 10 # modify output result. None values are ignored + + def __exit__(self, exc_type, exc_value, traceback): + return False +``` + +#### NOTE +If there are multiple hooks bound to the same slot, the result of last hook will be used. +It is recommended to specify the proper priority for the hook, e.g. `LAST` + +## How to enable/disable hooks? + +You can enable/disable/temporary disable hooks on 4 different levels: + +* Manage global hooks state (level 1): + > * `onetl.hooks.hooks_state.stop_all_hooks` + > * `onetl.hooks.hooks_state.resume_all_hooks` + > * `onetl.hooks.hooks_state.skip_all_hooks` +* Manage all hooks bound to a specific class (level 2): + > * `onetl.hooks.support_hooks.suspend_hooks` + > * `onetl.hooks.support_hooks.resume_hooks` + > * `onetl.hooks.support_hooks.skip_hooks` +* Manage all hooks bound to a specific slot (level 3): + > * `onetl.hooks.slot.Slot.suspend_hooks` + > * `onetl.hooks.slot.Slot.resume_hooks` + > * `onetl.hooks.slot.Slot.skip_hooks` +* Manage state of a specific hook (level 4): + > * `onetl.hooks.hook.Hook.enable` + > * `onetl.hooks.hook.Hook.disable` + +More details in the documentation above. + +#### NOTE +All of these levels are independent. + +Calling `stop` on the level 1 has higher priority than level 2, and so on. +But calling `resume` on the level 1 does not automatically resume hooks stopped in the level 2, +they should be resumed explicitly. + +## How to see logs of the hook mechanism? + +Hooks registration emits logs with `DEBUG` level: + +```python +from onetl.logs import setup_logging + +setup_logging() +``` + +```text +DEBUG |onETL| Registered hook 'mymodule.callback1' for 'MyClass.method' (enabled=True, priority=HookPriority.NORMAL) +DEBUG |onETL| Registered hook 'mymodule.callback2' for 'MyClass.method' (enabled=True, priority=HookPriority.NORMAL) +DEBUG |onETL| Registered hook 'mymodule.callback3' for 'MyClass.method' (enabled=False, priority=HookPriority.NORMAL) +``` + +But most of logs are emitted with even lower level `NOTICE`, to make output less verbose: + +```python +from onetl.logs import NOTICE, setup_logging + +setup_logging(level=NOTICE) +``` + +```default +NOTICE |Hooks| 2 hooks registered for 'MyClass.method' +NOTICE |Hooks| Calling hook 'mymodule.callback1' (1/2) +NOTICE |Hooks| Hook is finished with returning non-None result +NOTICE |Hooks| Calling hook 'mymodule.callback2' (2/2) +NOTICE |Hooks| This is a context manager, entering ... +NOTICE |Hooks| Calling original method 'MyClass.method' +NOTICE |Hooks| Method call is finished +NOTICE |Hooks| Method call result (*NOT* None) will be replaced with result of hook 'mymodule.callback1' +NOTICE |Hooks| Passing result to 'process_result' method of context manager 'mymodule.callback2' +NOTICE |Hooks| Method call result (*NOT* None) is modified by hook! +``` diff --git a/mddocs/docs/_build/markdown/hooks/global_state.md b/mddocs/docs/_build/markdown/hooks/global_state.md new file mode 100644 index 000000000..fbebfae6a --- /dev/null +++ b/mddocs/docs/_build/markdown/hooks/global_state.md @@ -0,0 +1,3 @@ + + +# Hooks global state diff --git a/mddocs/docs/_build/markdown/hooks/hook.md b/mddocs/docs/_build/markdown/hooks/hook.md new file mode 100644 index 000000000..2b94b2235 --- /dev/null +++ b/mddocs/docs/_build/markdown/hooks/hook.md @@ -0,0 +1,3 @@ + + +# `@hook` decorator diff --git a/mddocs/docs/_build/markdown/hooks/index.md b/mddocs/docs/_build/markdown/hooks/index.md new file mode 100644 index 000000000..8e1ba7fb9 --- /dev/null +++ b/mddocs/docs/_build/markdown/hooks/index.md @@ -0,0 +1,14 @@ + + +# Hooks + +#### Versionadded +Added in version 0.6.0. + +# Hooks + +* [High level design](design.md) +* [@hook decorator](hook.md) +* [@slot decorator](slot.md) +* [@support_hooks decorator](support_hooks.md) +* [Hooks global state](global_state.md) diff --git a/mddocs/docs/_build/markdown/hooks/slot.md b/mddocs/docs/_build/markdown/hooks/slot.md new file mode 100644 index 000000000..3bfe4d730 --- /dev/null +++ b/mddocs/docs/_build/markdown/hooks/slot.md @@ -0,0 +1,3 @@ + + +# `@slot` decorator diff --git a/mddocs/docs/_build/markdown/hooks/support_hooks.md b/mddocs/docs/_build/markdown/hooks/support_hooks.md new file mode 100644 index 000000000..2fddf4442 --- /dev/null +++ b/mddocs/docs/_build/markdown/hooks/support_hooks.md @@ -0,0 +1,3 @@ + + +# `@support_hooks` decorator diff --git a/mddocs/docs/_build/markdown/hwm_store/index.md b/mddocs/docs/_build/markdown/hwm_store/index.md new file mode 100644 index 000000000..c59cc0096 --- /dev/null +++ b/mddocs/docs/_build/markdown/hwm_store/index.md @@ -0,0 +1,9 @@ + + +# HWM + +Since onETL v0.10.0, the `HWMStore` and `HWM` classes have been moved to a separate library [etl-entities](https://etl-entities.readthedocs.io/en/stable/). + +The only class was left intact is [YAMLHWMStore](yaml_hwm_store.md#yaml-hwm-store), **which is default** in onETL. + +Other known implementation is [HorizonHWMStore](https://horizon-hwm-store.readthedocs.io/). diff --git a/mddocs/docs/_build/markdown/hwm_store/yaml_hwm_store.md b/mddocs/docs/_build/markdown/hwm_store/yaml_hwm_store.md new file mode 100644 index 000000000..44a228144 --- /dev/null +++ b/mddocs/docs/_build/markdown/hwm_store/yaml_hwm_store.md @@ -0,0 +1,3 @@ + + +# YAMLHWMStore diff --git a/mddocs/docs/_build/markdown/index.md b/mddocs/docs/_build/markdown/index.md new file mode 100644 index 000000000..436e5eb85 --- /dev/null +++ b/mddocs/docs/_build/markdown/index.md @@ -0,0 +1,59 @@ + + +# onETL + +[![Repo status - Active](https://www.repostatus.org/badges/latest/active.svg)](https://github.com/MobileTeleSystems/onetl) [![PyPI - Latest Release](https://img.shields.io/pypi/v/onetl)](https://pypi.org/project/onetl/) [![PyPI - License](https://img.shields.io/pypi/l/onetl.svg)](https://github.com/MobileTeleSystems/onetl/blob/develop/LICENSE.txt) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/onetl.svg)](https://pypi.org/project/onetl/) [![PyPI - Downloads](https://img.shields.io/pypi/dm/onetl)](https://pypi.org/project/onetl/) +[![Documentation - ReadTheDocs](https://readthedocs.org/projects/onetl/badge/?version=stable)](https://onetl.readthedocs.io/) [![Github Actions - latest CI build status](https://github.com/MobileTeleSystems/onetl/workflows/Tests/badge.svg)](https://github.com/MobileTeleSystems/onetl/actions) [![Test coverage - percent](https://codecov.io/gh/MobileTeleSystems/onetl/branch/develop/graph/badge.svg?token=RIO8URKNZJ)](https://codecov.io/gh/MobileTeleSystems/onetl) [![pre-commit.ci - status](https://results.pre-commit.ci/badge/github/MobileTeleSystems/onetl/develop.svg)](https://results.pre-commit.ci/latest/github/MobileTeleSystems/onetl/develop) + +![onETL logo](_static/logo_wide.svg) + +## What is onETL? + +Python ETL/ELT library powered by [Apache Spark](https://spark.apache.org/) & other open-source tools. + +## Goals + +* Provide unified classes to extract data from (**E**) & load data to (**L**) various stores. +* Provides [Spark DataFrame API](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.DataFrame.html) for performing transformations (**T**) in terms of *ETL*. +* Provide direct assess to database, allowing to execute SQL queries, as well as DDL, DML, and call functions/procedures. This can be used for building up *ELT* pipelines. +* Support different [read strategies](https://onetl.readthedocs.io/en/stable/strategy/index.html) for incremental and batch data fetching. +* Provide [hooks](https://onetl.readthedocs.io/en/stable/hooks/index.html) & [plugins](https://onetl.readthedocs.io/en/stable/plugins.html) mechanism for altering behavior of internal classes. + +## Non-goals + +* onETL is not a Spark replacement. It just provides additional functionality that Spark does not have, and improves UX for end users. +* onETL is not a framework, as it does not have requirements to project structure, naming, the way of running ETL/ELT processes, configuration, etc. All of that should be implemented in some other tool. +* onETL is deliberately developed without any integration with scheduling software like Apache Airflow. All integrations should be implemented as separated tools. +* Only batch operations, no streaming. For streaming prefer [Apache Flink](https://flink.apache.org/). + +## Requirements + +* **Python 3.7 - 3.13** +* PySpark 2.3.x - 3.5.x (depends on used connector) +* Java 8+ (required by Spark, see below) +* Kerberos libs & GCC (required by `Hive`, `HDFS` and `SparkHDFS` connectors) + +## Supported storages + +| Type | Storage | Powered by | +|--------------------|-----------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------| +| Database | Clickhouse | Apache Spark [JDBC Data Source](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html) | +| MSSQL | | | +| MySQL | | | +| Postgres | | | +| Oracle | | | +| Teradata | | | +| Hive | Apache Spark [Hive integration](https://spark.apache.org/docs/latest/sql-data-sources-hive-tables.html) | | +| Kafka | Apache Spark [Kafka integration](https://spark.apache.org/docs/latest/structured-streaming-kafka-integration.html) | | +| Greenplum | VMware [Greenplum Spark connector](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/index.html) | | +| MongoDB | [MongoDB Spark connector](https://www.mongodb.com/docs/spark-connector/current) | | +| File | HDFS | [HDFS Python client](https://pypi.org/project/hdfs/) | +| S3 | [minio-py client](https://pypi.org/project/minio/) | | +| SFTP | [Paramiko library](https://pypi.org/project/paramiko/) | | +| FTP | [FTPUtil library](https://pypi.org/project/ftputil/) | | +| FTPS | | | +| WebDAV | [WebdavClient3 library](https://pypi.org/project/webdavclient3/) | | +| Samba | [pysmb library](https://pypi.org/project/pysmb/) | | +| Files as DataFrame | SparkLocalFS | Apache Spark [File Data Source](https://spark.apache.org/docs/latest/sql-data-sources-generic-options.html) | +| SparkHDFS | | | +| SparkS3 | [Hadoop AWS](https://hadoop.apache.org/docs/current3/hadoop-aws/tools/hadoop-aws/index.html) library | | diff --git a/mddocs/docs/_build/markdown/install/files.md b/mddocs/docs/_build/markdown/install/files.md new file mode 100644 index 000000000..60c747462 --- /dev/null +++ b/mddocs/docs/_build/markdown/install/files.md @@ -0,0 +1,20 @@ + + +# File connections + +All File (but not *FileDF*) connection classes (`FTP`, `SFTP`, `HDFS` and so on) requires specific Python clients to be installed. + +Each client can be installed explicitly by passing connector name (in lowercase) to `extras`: + +```bash +pip install onetl[ftp] # specific connector +pip install onetl[ftp,ftps,sftp,hdfs,s3,webdav,samba] # multiple connectors +``` + +To install all file connectors at once you can pass `files` to `extras`: + +```bash +pip install onetl[files] +``` + +**Otherwise class import will fail.** diff --git a/mddocs/docs/_build/markdown/install/full.md b/mddocs/docs/_build/markdown/install/full.md new file mode 100644 index 000000000..889f6c401 --- /dev/null +++ b/mddocs/docs/_build/markdown/install/full.md @@ -0,0 +1,15 @@ + + +# Full bundle + +To install all connectors and dependencies, you can pass `all` into `extras`: + +```bash +pip install onetl[all] + +# this is just the same as +pip install onetl[spark,files,kerberos] +``` + +#### WARNING +This method consumes a lot of disk space, and requires for Java & Kerberos libraries to be installed into your OS. diff --git a/mddocs/docs/_build/markdown/install/index.md b/mddocs/docs/_build/markdown/install/index.md new file mode 100644 index 000000000..1a5fb63cc --- /dev/null +++ b/mddocs/docs/_build/markdown/install/index.md @@ -0,0 +1,33 @@ + + +# How to install + +Base `onetl` package contains: + +* `DBReader`, `DBWriter` and related classes +* `FileDownloader`, `FileUploader`, `FileMover` and related classes, like file filters & limits +* `FileDFReader`, `FileDFWriter` and related classes, like file formats +* Read Strategies & HWM classes +* Plugins support + +It can be installed via: + +```bash +pip install onetl +``` + +#### WARNING +This method does NOT include any connections. + +This method is recommended for use in third-party libraries which require for `onetl` to be installed, +but do not use its connection classes. + +## Installation in details + +## How to install + +* [How to install]() +* [Spark](spark.md) +* [File connections](files.md) +* [Kerberos support](kerberos.md) +* [Full bundle](full.md) diff --git a/mddocs/docs/_build/markdown/install/kerberos.md b/mddocs/docs/_build/markdown/install/kerberos.md new file mode 100644 index 000000000..1826124c2 --- /dev/null +++ b/mddocs/docs/_build/markdown/install/kerberos.md @@ -0,0 +1,32 @@ + + +# Kerberos support + +Most of Hadoop instances set up with Kerberos support, +so some connections require additional setup to work properly. + +* `HDFS` + Uses [requests-kerberos](https://pypi.org/project/requests-kerberos/) and + [GSSApi](https://pypi.org/project/gssapi/) for authentication. + It also uses `kinit` executable to generate Kerberos ticket. +* `Hive` and `SparkHDFS` + require Kerberos ticket to exist before creating Spark session. + +So you need to install OS packages with: + +* `krb5` libs +* Headers for `krb5` +* `gcc` or other compiler for C sources + +The exact installation instruction depends on your OS, here are some examples: + +```bash +apt install libkrb5-dev krb5-user gcc # Debian-based +dnf install krb5-devel krb5-libs krb5-workstation gcc # CentOS, OracleLinux +``` + +Also you should pass `kerberos` to `extras` to install required Python packages: + +```bash +pip install onetl[kerberos] +``` diff --git a/mddocs/docs/_build/markdown/install/spark.md b/mddocs/docs/_build/markdown/install/spark.md new file mode 100644 index 000000000..914f71611 --- /dev/null +++ b/mddocs/docs/_build/markdown/install/spark.md @@ -0,0 +1,342 @@ + + +# Spark + +All DB connection classes (`Clickhouse`, `Greenplum`, `Hive` and others) +and all FileDF connection classes (`SparkHDFS`, `SparkLocalFS`, `SparkS3`) +require Spark to be installed. + +## Installing Java + +Firstly, you should install JDK. The exact installation instruction depends on your OS, here are some examples: + +```bash +yum install java-1.8.0-openjdk-devel # CentOS 7 + Spark 2 +dnf install java-11-openjdk-devel # CentOS 8 + Spark 3 +apt-get install openjdk-11-jdk # Debian-based + Spark 3 +``` + + + +### Compatibility matrix + +| Spark | Python | Java | Scala | +|-----------------------------------------------------------|------------|------------|---------| +| [2.3.x](https://spark.apache.org/docs/2.3.1/#downloading) | 3.7 only | 8 only | 2.11 | +| [2.4.x](https://spark.apache.org/docs/2.4.8/#downloading) | 3.7 only | 8 only | 2.11 | +| [3.2.x](https://spark.apache.org/docs/3.2.4/#downloading) | 3.7 - 3.10 | 8u201 - 11 | 2.12 | +| [3.3.x](https://spark.apache.org/docs/3.3.4/#downloading) | 3.7 - 3.12 | 8u201 - 17 | 2.12 | +| [3.4.x](https://spark.apache.org/docs/3.4.4/#downloading) | 3.7 - 3.12 | 8u362 - 20 | 2.12 | +| [3.5.x](https://spark.apache.org/docs/3.5.5/#downloading) | 3.8 - 3.13 | 8u371 - 20 | 2.12 | + +## Installing PySpark + +Then you should install PySpark via passing `spark` to `extras`: + +```bash +pip install onetl[spark] # install latest PySpark +``` + +or install PySpark explicitly: + +```bash +pip install onetl pyspark==3.5.5 # install a specific PySpark version +``` + +or inject PySpark to `sys.path` in some other way BEFORE creating a class instance. +**Otherwise connection object cannot be created.** + + + +## Injecting Java packages + +Some DB and FileDF connection classes require specific packages to be inserted to `CLASSPATH` of Spark session, +like JDBC drivers. + +This is usually done by setting up `spark.jars.packages` option while creating Spark session: + +```python +# here is a list of packages to be downloaded: +maven_packages = ( + Greenplum.get_packages(spark_version="3.2") + + MySQL.get_packages() + + Teradata.get_packages() +) + +spark = ( + SparkSession.builder.config("spark.app.name", "onetl") + .config("spark.jars.packages", ",".join(maven_packages)) + .getOrCreate() +) +``` + +Spark automatically resolves package and all its dependencies, download them and inject to Spark session +(both driver and all executors). + +This requires internet access, because package metadata and `.jar` files are fetched from [Maven Repository](https://mvnrepository.com/). + +But sometimes it is required to: + +* Install package without direct internet access (isolated network) +* Install package which is not available in Maven + +There are several ways to do that. + +### Using `spark.jars` + +The most simple solution, but this requires to store raw `.jar` files somewhere on filesystem or web server. + +* Download `package.jar` files (it’s usually something like `some-package_1.0.0.jar`). Local file name does not matter, but it should be unique. +* (For `spark.submit.deployMode=cluster`) place downloaded files to HDFS or deploy to any HTTP web server serving static files. See [official documentation](https://spark.apache.org/docs/latest/submitting-applications.html#advanced-dependency-management) for more details. +* Create Spark session with passing `.jar` absolute file path to `spark.jars` Spark config option: + +for spark.submit.deployMode=client (default) + +```py +jar_files = ["/path/to/package.jar"] + +# do not pass spark.jars.packages +spark = ( + SparkSession.builder.config("spark.app.name", "onetl") + .config("spark.jars", ",".join(jar_files)) + .getOrCreate() +) +``` + +for spark.submit.deployMode=cluster + +```py +# you can also pass URLs like http://domain.com/path/to/downloadable/package.jar +jar_files = ["hdfs:///path/to/package.jar"] + +# do not pass spark.jars.packages +spark = ( + SparkSession.builder.config("spark.app.name", "onetl") + .config("spark.jars", ",".join(jar_files)) + .getOrCreate() +) +``` + +### Using `spark.jars.repositories` + +#### NOTE +In this case Spark still will try to fetch packages from the internet, so if you don’t have internet access, +Spark session will be created with significant delay because of all attempts to fetch packages. + +Can be used if you have access both to public repos (like Maven) and a private Artifactory/Nexus repo. + +* Setup private Maven repository in [JFrog Artifactory](https://jfrog.com/artifactory/) or [Sonatype Nexus](https://www.sonatype.com/products/sonatype-nexus-repository). +* Download `package.jar` file (it’s usually something like `some-package_1.0.0.jar`). Local file name does not matter. +* Upload `package.jar` file to private repository (with same `groupId` and `artifactoryId` as in source package in Maven). +* Pass repo URL to `spark.jars.repositories` Spark config option. +* Create Spark session with passing Package name to `spark.jars.packages` Spark config option: + +```python +maven_packages = ( + Greenplum.get_packages(spark_version="3.2") + + MySQL.get_packages() + + Teradata.get_packages() +) + +spark = ( + SparkSession.builder.config("spark.app.name", "onetl") + .config("spark.jars.repositories", "http://nexus.mydomain.com/private-repo/") + .config("spark.jars.packages", ",".join(maven_packages)) + .getOrCreate() +) +``` + +### Using `spark.jars.ivySettings` + +Same as above, but can be used even if there is no network access to public repos like Maven. + +* Setup private Maven repository in [JFrog Artifactory](https://jfrog.com/artifactory/) or [Sonatype Nexus](https://www.sonatype.com/products/sonatype-nexus-repository). +* Download `package.jar` file (it’s usually something like `some-package_1.0.0.jar`). Local file name does not matter. +* Upload `package.jar` file to [private repository](https://help.sonatype.com/repomanager3/nexus-repository-administration/repository-management#RepositoryManagement-HostedRepository) (with same `groupId` and `artifactoryId` as in source package in Maven). +* Create `ivysettings.xml` file (see below). +* Add here a resolver with repository URL (and credentials, if required). +* Pass `ivysettings.xml` absolute path to `spark.jars.ivySettings` Spark config option. +* Create Spark session with passing package name to `spark.jars.packages` Spark config option: + +ivysettings-all-packages-uploaded-to-nexus.xml + +```xml + + + + + + + + + + + + + +``` + +ivysettings-private-packages-in-nexus-public-in-maven.xml + +```xml + + + + + + + + + + + + + + + + + +``` + +ivysettings-private-packages-in-nexus-public-fetched-using-proxy-repo.xml + +```xml + + + + + + + + + + + + + + + +``` + +ivysettings-nexus-with-auth-required.xml + +```xml + + + + + + + + + + + + + + + + + + + +``` + +```python +maven_packages = ( + Greenplum.get_packages(spark_version="3.2") + + MySQL.get_packages() + + Teradata.get_packages() +) + +spark = ( + SparkSession.builder.config("spark.app.name", "onetl") + .config("spark.jars.ivySettings", "/path/to/ivysettings.xml") + .config("spark.jars.packages", ",".join(maven_packages)) + .getOrCreate() +) +``` + +### Place `.jar` file to `-/.ivy2/jars/` + +Can be used to pass already downloaded file to Ivy, and skip resolving package from Maven. + +* Download `package.jar` file (it’s usually something like `some-package_1.0.0.jar`). Local file name does not matter, but it should be unique. +* Move it to `-/.ivy2/jars/` folder. +* Create Spark session with passing package name to `spark.jars.packages` Spark config option: + +```python +maven_packages = ( + Greenplum.get_packages(spark_version="3.2") + + MySQL.get_packages() + + Teradata.get_packages() +) + +spark = ( + SparkSession.builder.config("spark.app.name", "onetl") + .config("spark.jars.packages", ",".join(maven_packages)) + .getOrCreate() +) +``` + +### Place `.jar` file to Spark jars folder + +#### NOTE +Package file should be placed on all hosts/containers Spark is running, +both driver and all executors. + +Usually this is used only with either: +: * `spark.master=local` (driver and executors are running on the same host), + * `spark.master=k8s://...` (`.jar` files are added to image or to volume mounted to all pods). + +Can be used to embed `.jar` files to a default Spark classpath. + +* Download `package.jar` file (it’s usually something like `some-package_1.0.0.jar`). Local file name does not matter, but it should be unique. +* Move it to `$SPARK_HOME/jars/` folder, e.g. `^/.local/lib/python3.7/site-packages/pyspark/jars/` or `/opt/spark/3.2.3/jars/`. +* Create Spark session **WITHOUT** passing Package name to `spark.jars.packages` + +```python +# no need to set spark.jars.packages or any other spark.jars.* option +# all jars already present in CLASSPATH, and loaded automatically + +spark = SparkSession.builder.config("spark.app.name", "onetl").getOrCreate() +``` + +### Manually adding `.jar` files to `CLASSPATH` + +#### NOTE +Package file should be placed on all hosts/containers Spark is running, +both driver and all executors. + +Usually this is used only with either: +: * `spark.master=local` (driver and executors are running on the same host), + * `spark.master=k8s://...` (`.jar` files are added to image or to volume mounted to all pods). + +Can be used to embed `.jar` files to a default Java classpath. + +* Download `package.jar` file (it’s usually something like `some-package_1.0.0.jar`). Local file name does not matter. +* Set environment variable `CLASSPATH` to `/path/to/package.jar`. You can set multiple file paths +* Create Spark session **WITHOUT** passing Package name to `spark.jars.packages` + +```python +# no need to set spark.jars.packages or any other spark.jars.* option +# all jars already present in CLASSPATH, and loaded automatically + +import os + +jar_files = ["/path/to/package.jar"] +# different delimiters for Windows and Linux +delimiter = ";" if os.name == "nt" else ":" +spark = ( + SparkSession.builder.config("spark.app.name", "onetl") + .config("spark.driver.extraClassPath", delimiter.join(jar_files)) + .config("spark.executor.extraClassPath", delimiter.join(jar_files)) + .getOrCreate() +) +``` diff --git a/mddocs/docs/_build/markdown/logging.md b/mddocs/docs/_build/markdown/logging.md new file mode 100644 index 000000000..27918f939 --- /dev/null +++ b/mddocs/docs/_build/markdown/logging.md @@ -0,0 +1,153 @@ + + +# Logging + +Logging is quite important to understand what’s going on under the hood of onETL. + +Default logging level for Python interpreters is `WARNING`, +but most of onETL logs are in `INFO` level, so users usually don’t see much. + +To change logging level, there is a function `setup_logging` +which should be called at the top of the script: + +```python +from onetl.log import setup_logging +from other.lib import some, more, imports + +setup_logging() + +# rest of code +... +``` + +This changes both log level and log formatting to something like this: + +### See logs + +```text +2024-04-12 10:12:10,834 [INFO ] MainThread: |onETL| Using IncrementalStrategy as a strategy +2024-04-12 10:12:10,835 [INFO ] MainThread: =================================== DBReader.run() starts =================================== +2024-04-12 10:12:10,835 [INFO ] MainThread: |DBReader| Getting Spark type for HWM expression: 'updated_at' +2024-04-12 10:12:10,836 [INFO ] MainThread: |MSSQL| Fetching schema of table 'source_schema.table' ... +2024-04-12 10:12:11,636 [INFO ] MainThread: |MSSQL| Schema fetched. +2024-04-12 10:12:11,642 [INFO ] MainThread: |DBReader| Got Spark field: StructField('updated_at', TimestampType(), True) +2024-04-12 10:12:11,642 [INFO ] MainThread: |DBReader| Detected HWM type: 'ColumnDateTimeHWM' +2024-04-12 10:12:11,643 [INFO ] MainThread: |IncrementalStrategy| Fetching HWM from HorizonHWMStore: +2024-04-12 10:12:11,643 [INFO ] MainThread: name = 'updated_at#source_schema.table@mssql:/mssql.host:1433/somedb' +2024-04-12 10:12:12,181 [INFO ] MainThread: |IncrementalStrategy| Fetched HWM: +2024-04-12 10:12:12,182 [INFO ] MainThread: hwm = ColumnDateTimeHWM( +2024-04-12 10:12:12,182 [INFO ] MainThread: name = 'updated_at#source_schema.table@mssql:/mssql.host:1433/somedb', +2024-04-12 10:12:12,182 [INFO ] MainThread: entity = 'source_schema.table', +2024-04-12 10:12:12,182 [INFO ] MainThread: expression = 'updated_at', +2024-04-12 10:12:12,184 [INFO ] MainThread: value = datetime.datetime(2024, 4, 11, 18, 10, 2, 120000), +2024-04-12 10:12:12,184 [INFO ] MainThread: ) +2024-04-12 10:12:12,184 [INFO ] MainThread: |MSSQL| -> |Spark| Reading DataFrame from source using parameters: +2024-04-12 10:12:12,185 [INFO ] MainThread: source = 'source_schema.table' +2024-04-12 10:12:12,185 [INFO ] MainThread: columns = [ +2024-04-12 10:12:12,185 [INFO ] MainThread: 'id', +2024-04-12 10:12:12,186 [INFO ] MainThread: 'new_value', +2024-04-12 10:12:12,186 [INFO ] MainThread: 'old_value', +2024-04-12 10:12:12,186 [INFO ] MainThread: 'updated_at', +2024-04-12 10:12:12,186 [INFO ] MainThread: ] +2024-04-12 10:12:12,187 [INFO ] MainThread: where = "field = 'some'" +2024-04-12 10:12:12,187 [INFO ] MainThread: hwm = AutoDetectHWM( +2024-04-12 10:12:12,187 [INFO ] MainThread: name = 'updated_at#source_schema.table@mssql:/mssql.host:1433/somedb', +2024-04-12 10:12:12,187 [INFO ] MainThread: entity = 'source_schema.table', +2024-04-12 10:12:12,187 [INFO ] MainThread: expression = 'updated_at', +2024-04-12 10:12:12,188 [INFO ] MainThread: ) +2024-04-12 10:12:12,188 [INFO ] MainThread: options = { +2024-04-12 10:12:12,188 [INFO ] MainThread: 'fetchsize': 100000, +2024-04-12 10:12:12,188 [INFO ] MainThread: 'numPartitions': 1, +2024-04-12 10:12:12,189 [INFO ] MainThread: 'partitioningMode': 'range', +2024-04-12 10:12:12,189 [INFO ] MainThread: } +2024-04-12 10:12:12,189 [INFO ] MainThread: |MSSQL| Checking connection availability... +2024-04-12 10:12:12,189 [INFO ] MainThread: |MSSQL| Using connection parameters: +2024-04-12 10:12:12,190 [INFO ] MainThread: user = 'db_user' +2024-04-12 10:12:12,190 [INFO ] MainThread: password = SecretStr('**********') +2024-04-12 10:12:12,190 [INFO ] MainThread: host = 'mssql.host' +2024-04-12 10:12:12,190 [INFO ] MainThread: port = 1433 +2024-04-12 10:12:12,191 [INFO ] MainThread: database = 'somedb' +2024-04-12 10:12:12,191 [INFO ] MainThread: extra = {'applicationIntent': 'ReadOnly', 'trustServerCertificate': 'true'} +2024-04-12 10:12:12,191 [INFO ] MainThread: jdbc_url = 'jdbc:sqlserver:/mssql.host:1433' +2024-04-12 10:12:12,579 [INFO ] MainThread: |MSSQL| Connection is available. +2024-04-12 10:12:12,581 [INFO ] MainThread: |MSSQL| Executing SQL query (on driver): +2024-04-12 10:12:12,581 [INFO ] MainThread: SELECT +2024-04-12 10:12:12,581 [INFO ] MainThread: MIN(updated_at) AS "min", +2024-04-12 10:12:12,582 [INFO ] MainThread: MAX(updated_at) AS "max" +2024-04-12 10:12:12,582 [INFO ] MainThread: FROM +2024-04-12 10:12:12,582 [INFO ] MainThread: source_schema.table +2024-04-12 10:12:12,582 [INFO ] MainThread: WHERE +2024-04-12 10:12:12,582 [INFO ] MainThread: (field = 'some') +2024-04-12 10:12:12,583 [INFO ] MainThread: AND +2024-04-12 10:12:12,583 [INFO ] MainThread: (updated_at >= CAST('2024-04-11T18:10:02.120000' AS datetime2)) +2024-04-12 10:16:22,537 [INFO ] MainThread: |MSSQL| Received values: +2024-04-12 10:16:22,538 [INFO ] MainThread: MIN(updated_at) = datetime.datetime(2024, 4, 11, 21, 10, 7, 397000) +2024-04-12 10:16:22,538 [INFO ] MainThread: MAX(updated_at) = datetime.datetime(2024, 4, 12, 13, 12, 2, 123000) +2024-04-12 10:16:22,540 [INFO ] MainThread: |MSSQL| Executing SQL query (on executor): +2024-04-12 10:16:22,540 [INFO ] MainThread: SELECT +2024-04-12 10:16:22,540 [INFO ] MainThread: id, +2024-04-12 10:16:22,541 [INFO ] MainThread: new_value, +2024-04-12 10:16:22,541 [INFO ] MainThread: old_value, +2024-04-12 10:16:22,541 [INFO ] MainThread: updated_at +2024-04-12 10:16:22,541 [INFO ] MainThread: FROM +2024-04-12 10:16:22,541 [INFO ] MainThread: source_schema.table +2024-04-12 10:16:22,542 [INFO ] MainThread: WHERE +2024-04-12 10:16:22,542 [INFO ] MainThread: (field = 'some') +2024-04-12 10:16:22,542 [INFO ] MainThread: AND +2024-04-12 10:16:22,542 [INFO ] MainThread: (updated_at > CAST('2024-04-11T18:10:02.120000' AS datetime2)) +2024-04-12 10:16:22,542 [INFO ] MainThread: AND +2024-04-12 10:16:22,542 [INFO ] MainThread: (updated_at <= CAST('2024-04-12T13:12:02.123000' AS datetime2)) +2024-04-12 10:16:22,892 [INFO ] MainThread: |Spark| DataFrame successfully created from SQL statement +2024-04-12 10:16:22,892 [INFO ] MainThread: ------------------------------------ DBReader.run() ends ------------------------------------ +2024-04-12 10:40:42,409 [INFO ] MainThread: =================================== DBWriter.run() starts =================================== +2024-04-12 10:40:42,409 [INFO ] MainThread: |Spark| -> |Hive| Writing DataFrame to target using parameters: +2024-04-12 10:40:42,410 [INFO ] MainThread: target = 'target_source_schema.table' +2024-04-12 10:40:42,410 [INFO ] MainThread: options = { +2024-04-12 10:40:42,410 [INFO ] MainThread: 'mode': 'append', +2024-04-12 10:40:42,410 [INFO ] MainThread: 'format': 'orc', +2024-04-12 10:40:42,410 [INFO ] MainThread: 'partitionBy': 'part_dt', +2024-04-12 10:40:42,410 [INFO ] MainThread: } +2024-04-12 10:40:42,411 [INFO ] MainThread: df_schema: +2024-04-12 10:40:42,412 [INFO ] MainThread: root +2024-04-12 10:40:42,412 [INFO ] MainThread: |-- id: integer (nullable = true) +2024-04-12 10:40:42,413 [INFO ] MainThread: |-- new_value: string (nullable = true) +2024-04-12 10:40:42,413 [INFO ] MainThread: |-- old_value: string (nullable = true) +2024-04-12 10:40:42,413 [INFO ] MainThread: |-- updated_at: timestamp (nullable = true) +2024-04-12 10:40:42,413 [INFO ] MainThread: |-- part_dt: date (nullable = true) +2024-04-12 10:40:42,414 [INFO ] MainThread: +2024-04-12 10:40:42,421 [INFO ] MainThread: |Hive| Checking connection availability... +2024-04-12 10:40:42,421 [INFO ] MainThread: |Hive| Using connection parameters: +2024-04-12 10:40:42,421 [INFO ] MainThread: cluster = 'dwh' +2024-04-12 10:40:42,475 [INFO ] MainThread: |Hive| Connection is available. +2024-04-12 10:40:42,476 [INFO ] MainThread: |Hive| Fetching schema of table 'target_source_schema.table' ... +2024-04-12 10:40:43,518 [INFO ] MainThread: |Hive| Schema fetched. +2024-04-12 10:40:43,521 [INFO ] MainThread: |Hive| Table 'target_source_schema.table' already exists +2024-04-12 10:40:43,521 [WARNING ] MainThread: |Hive| User-specified options {'partitionBy': 'part_dt'} are ignored while inserting into existing table. Using only table parameters from Hive metastore +2024-04-12 10:40:43,782 [INFO ] MainThread: |Hive| Inserting data into existing table 'target_source_schema.table' ... +2024-04-12 11:06:07,396 [INFO ] MainThread: |Hive| Data is successfully inserted into table 'target_source_schema.table'. +2024-04-12 11:06:07,397 [INFO ] MainThread: ------------------------------------ DBWriter.run() ends ------------------------------------ +2024-04-12 11:06:07,397 [INFO ] MainThread: |onETL| Exiting IncrementalStrategy +2024-04-12 11:06:07,397 [INFO ] MainThread: |IncrementalStrategy| Saving HWM to 'HorizonHWMStore': +2024-04-12 11:06:07,397 [INFO ] MainThread: hwm = ColumnDateTimeHWM( +2024-04-12 11:06:07,397 [INFO ] MainThread: name = 'updated_at#source_schema.table@mssql:/mssql.host:1433/somedb', +2024-04-12 11:06:07,397 [INFO ] MainThread: entity = 'source_source_schema.table', +2024-04-12 11:06:07,397 [INFO ] MainThread: expression = 'updated_at', +2024-04-12 11:06:07,397 [INFO ] MainThread: value = datetime.datetime(2024, 4, 12, 13, 12, 2, 123000), +2024-04-12 11:06:07,397 [INFO ] MainThread: ) +2024-04-12 11:06:07,495 [INFO ] MainThread: |IncrementalStrategy| HWM has been saved +``` + +Each step performed by onETL is extensively logged, which should help with debugging. + +You can make logs even more verbose by changing level to `DEBUG`: + +```python +from onetl.log import setup_logging + +setup_logging(level="DEBUG", enable_clients=True) + +# rest of code +... +``` + +This also changes log level for all underlying Python libraries, e.g. showing each HTTP request being made, and so on. diff --git a/mddocs/docs/_build/markdown/plugins.md b/mddocs/docs/_build/markdown/plugins.md new file mode 100644 index 000000000..d42ded034 --- /dev/null +++ b/mddocs/docs/_build/markdown/plugins.md @@ -0,0 +1,147 @@ + + +# Plugins + +#### Versionadded +Added in version 0.6.0. + +## What are plugins? + +### Terms + +* `Plugin` - some Python package which implements some extra functionality for onETL, like [Hooks](hooks/index.md#hooks) +* `Plugin autoimport` - onETL behavior which allows to automatically import this package if it contains proper metadata (`entry_points`) + +### Features + +Plugins mechanism allows to: + +* Automatically register [Hooks](hooks/index.md#hooks) which can alter onETL behavior +* Automatically register new classes, like HWM type, HWM stores and so on + +### Limitations + +Unlike other projects (like *Airflow 1.x*), plugins does not inject imported classes or functions to `onetl.*` namespace. +Users should import classes from the plugin package **explicitly** to avoid name collisions. + +## How to implement plugin? + +Create a Python package `some-plugin` with a file `some_plugin/setup.py`: + +```python +# some_plugin/setup.py +from setuptools import setup + +setup( + # if you want to import something from onETL, add it to requirements list + install_requires=["onetl"], + entry_points={ + # this key enables plugins autoimport functionality + "onetl.plugins": [ + "some-plugin-name=some_plugin.module", # automatically import all module content + "some-plugin-class=some_plugin.module.internals:MyClass", # import a specific class + "some-plugin-function=some_plugin.module.internals:my_function", # import a specific function + ], + }, +) +``` + +See [setuptools documentation for entry_points](https://setuptools.pypa.io/en/latest/userguide/entry_point.html) + +## How plugins are imported? + +* User should install a package implementing the plugin: + +```bash +pip install some-package +``` + +* Then user should import something from `onetl` module or its submodules: + +```python +import onetl +from onetl.connection import SomeConnection + +# and so on +``` + +* This import automatically executes something like: + +```python +import some_plugin.module +from some_plugin.module.internals import MyClass +from some_plugin.module.internals import my_function +``` + +If specific module/class/function uses some registration capabilities of onETL, +like [@hook decorator](hooks/hook.md#hook-decorator), it will be executed during this import. + +## How to enable/disable plugins? + +#### Versionadded +Added in version 0.7.0. + +### Disable/enable all plugins + +By default plugins are enabled. + +To disabled them, you can set environment variable `ONETL_PLUGINS_ENABLED` to `false` BEFORE +importing onETL. This will disable all plugins autoimport. + +But user is still be able to explicitly import `some_plugin.module`, executing +all decorators and registration capabilities of onETL. + +### Disable a specific plugin (blacklist) + +If some plugin is failing during import, you can disable it by setting up environment variable +`ONETL_PLUGINS_BLACKLIST=some-failing-plugin`. Multiple plugin names could be passed with `,` as delimiter. + +Again, this environment variable should be set BEFORE importing onETL. + +### Disable all plugins except a specific one (whitelist) + +You can also disable all plugins except a specific one by setting up environment variable +`ONETL_PLUGINS_WHITELIST=some-not-failing-plugin`. Multiple plugin names could be passed with `,` as delimiter. + +Again, this environment variable should be set BEFORE importing onETL. + +If both whitelist and blacklist environment variables are set, blacklist has a higher priority. + +## How to see logs of the plugins mechanism? + +Plugins registration emits logs with `INFO` level: + +```python +import logging + +logging.basicConfig(level=logging.INFO) +``` + +```text +INFO |onETL| Found 2 plugins +INFO |onETL| Loading plugin 'my-plugin' +INFO |onETL| Skipping plugin 'failing' because it is in a blacklist +``` + +More detailed logs are emitted with `DEBUG` level, to make output less verbose: + +```python +import logging + +logging.basicConfig(level=logging.DEBUG) +``` + +```text +DEBUG |onETL| Searching for plugins with group 'onetl.plugins' +DEBUG |Plugins| Plugins whitelist: [] +DEBUG |Plugins| Plugins blacklist: ['failing-plugin'] +INFO |Plugins| Found 2 plugins +INFO |onETL| Loading plugin (1/2): +DEBUG name: 'my-plugin' +DEBUG package: 'my-package' +DEBUG version: '0.1.0' +DEBUG importing: 'my_package.my_module:MyClass' +DEBUG |onETL| Successfully loaded plugin 'my-plugin' +DEBUG source: '/usr/lib/python3.11/site-packages/my_package/my_module/my_class.py' +INFO |onETL| Skipping plugin 'failing' because it is in a blacklist +``` diff --git a/mddocs/docs/_build/markdown/quickstart.md b/mddocs/docs/_build/markdown/quickstart.md new file mode 100644 index 000000000..7a9bd0652 --- /dev/null +++ b/mddocs/docs/_build/markdown/quickstart.md @@ -0,0 +1,365 @@ +: + +# Quick start + +## MSSQL → Hive + +Read data from MSSQL, transform & write to Hive. + +```bash +# install onETL and PySpark +pip install onetl[spark] +``` + +```python +# Import pyspark to initialize the SparkSession +from pyspark.sql import SparkSession + +# import function to setup onETL logging +from onetl.log import setup_logging + +# Import required connections +from onetl.connection import MSSQL, Hive + +# Import onETL classes to read & write data +from onetl.db import DBReader, DBWriter + +# change logging level to INFO, and set up default logging format and handler +setup_logging() + +# Initialize new SparkSession with MSSQL driver loaded +maven_packages = MSSQL.get_packages() +spark = ( + SparkSession.builder.appName("spark_app_onetl_demo") + .config("spark.jars.packages", ",".join(maven_packages)) + .enableHiveSupport() # for Hive + .getOrCreate() +) + +# Initialize MSSQL connection and check if database is accessible +mssql = MSSQL( + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, + # These options are passed to MSSQL JDBC Driver: + extra={"applicationIntent": "ReadOnly"}, +).check() + +# >>> INFO:|MSSQL| Connection is available + +# Initialize DBReader +reader = DBReader( + connection=mssql, + source="dbo.demo_table", + columns=["on", "etl"], + # Set some MSSQL read options: + options=MSSQL.ReadOptions(fetchsize=10000), +) + +# checks that there is data in the table, otherwise raises exception +reader.raise_if_no_data() + +# Read data to DataFrame +df = reader.run() +df.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) + +# Apply any PySpark transformations +from pyspark.sql.functions import lit + +df_to_write = df.withColumn("engine", lit("onetl")) +df_to_write.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) +# |-- engine: string (nullable = false) + +# Initialize Hive connection +hive = Hive(cluster="rnd-dwh", spark=spark) + +# Initialize DBWriter +db_writer = DBWriter( + connection=hive, + target="dl_sb.demo_table", + # Set some Hive write options: + options=Hive.WriteOptions(if_exists="replace_entire_table"), +) + +# Write data from DataFrame to Hive +db_writer.run(df_to_write) + +# Success! +``` + +## SFTP → HDFS + +Download files from SFTP & upload them to HDFS. + +```bash +# install onETL with SFTP and HDFS clients, and Kerberos support +pip install onetl[hdfs,sftp,kerberos] +``` + +```python +# import function to setup onETL logging +from onetl.log import setup_logging + +# Import required connections +from onetl.connection import SFTP, HDFS + +# Import onETL classes to download & upload files +from onetl.file import FileDownloader, FileUploader + +# import filter & limit classes +from onetl.file.filter import Glob, ExcludeDir +from onetl.file.limit import MaxFilesCount + +# change logging level to INFO, and set up default logging format and handler +setup_logging() + +# Initialize SFTP connection and check it +sftp = SFTP( + host="sftp.test.com", + user="someuser", + password="somepassword", +).check() + +# >>> INFO:|SFTP| Connection is available + +# Initialize downloader +file_downloader = FileDownloader( + connection=sftp, + source_path="/remote/tests/Report", # path on SFTP + local_path="/local/onetl/Report", # local fs path + filters=[ + # download only files matching the glob + Glob("*.csv"), + # exclude files from this directory + ExcludeDir("/remote/tests/Report/exclude_dir/"), + ], + limits=[ + # download max 1000 files per run + MaxFilesCount(1000), + ], + options=FileDownloader.Options( + # delete files from SFTP after successful download + delete_source=True, + # mark file as failed if it already exist in local_path + if_exists="error", + ), +) + +# Download files to local filesystem +download_result = downloader.run() + +# Method run returns a DownloadResult object, +# which contains collection of downloaded files, divided to 4 categories +download_result + +# DownloadResult( +# successful=[ +# LocalPath('/local/onetl/Report/file_1.json'), +# LocalPath('/local/onetl/Report/file_2.json'), +# ], +# failed=[FailedRemoteFile('/remote/onetl/Report/file_3.json')], +# ignored=[RemoteFile('/remote/onetl/Report/file_4.json')], +# missing=[], +# ) + +# Raise exception if there are failed files, or there were no files in the remote filesystem +download_result.raise_if_failed() or download_result.raise_if_empty() + +# Do any kind of magic with files: rename files, remove header for csv files, ... +renamed_files = my_rename_function(download_result.success) + +# function removed "_" from file names +# [ +# LocalPath('/home/onetl/Report/file1.json'), +# LocalPath('/home/onetl/Report/file2.json'), +# ] + +# Initialize HDFS connection +hdfs = HDFS( + host="my.name.node", + user="someuser", + password="somepassword", # or keytab +) + +# Initialize uploader +file_uploader = FileUploader( + connection=hdfs, + target_path="/user/onetl/Report/", # hdfs path +) + +# Upload files from local fs to HDFS +upload_result = file_uploader.run(renamed_files) + +# Method run returns a UploadResult object, +# which contains collection of uploaded files, divided to 4 categories +upload_result + +# UploadResult( +# successful=[RemoteFile('/user/onetl/Report/file1.json')], +# failed=[FailedLocalFile('/local/onetl/Report/file2.json')], +# ignored=[], +# missing=[], +# ) + +# Raise exception if there are failed files, or there were no files in the local filesystem, or some input file is missing +upload_result.raise_if_failed() or upload_result.raise_if_empty() or upload_result.raise_if_missing() + +# Success! +``` + +## S3 → Postgres + +Read files directly from S3 path, convert them to dataframe, transform it and then write to a database. + +```bash +# install onETL and PySpark +pip install onetl[spark] +``` + +```python +# Import pyspark to initialize the SparkSession +from pyspark.sql import SparkSession + +# import function to setup onETL logging +from onetl.log import setup_logging + +# Import required connections +from onetl.connection import Postgres, SparkS3 + +# Import onETL classes to read files +from onetl.file import FileDFReader +from onetl.file.format import CSV + +# Import onETL classes to write data +from onetl.db import DBWriter + +# change logging level to INFO, and set up default logging format and handler +setup_logging() + +# Initialize new SparkSession with Hadoop AWS libraries and Postgres driver loaded +maven_packages = SparkS3.get_packages(spark_version="3.5.5") + Postgres.get_packages() +exclude_packages = SparkS3.get_exclude_packages() +spark = ( + SparkSession.builder.appName("spark_app_onetl_demo") + .config("spark.jars.packages", ",".join(maven_packages)) + .config("spark.jars.excludes", ",".join(exclude_packages)) + .getOrCreate() +) + +# Initialize S3 connection and check it +spark_s3 = SparkS3( + host="s3.test.com", + protocol="https", + bucket="my-bucket", + access_key="somekey", + secret_key="somesecret", + # Access bucket as s3.test.com/my-bucket + extra={"path.style.access": True}, + spark=spark, +).check() + +# >>> INFO:|SparkS3| Connection is available + +# Describe file format and parsing options +csv = CSV( + delimiter=";", + header=True, + encoding="utf-8", +) + +# Describe DataFrame schema of files +from pyspark.sql.types import ( + DateType, + DoubleType, + IntegerType, + StringType, + StructField, + StructType, + TimestampType, +) + +df_schema = StructType( + [ + StructField("id", IntegerType()), + StructField("phone_number", StringType()), + StructField("region", StringType()), + StructField("birth_date", DateType()), + StructField("registered_at", TimestampType()), + StructField("account_balance", DoubleType()), + ], +) + +# Initialize file df reader +reader = FileDFReader( + connection=spark_s3, + source_path="/remote/tests/Report", # path on S3 there *.csv files are located + format=csv, # file format with specific parsing options + df_schema=df_schema, # columns & types +) + +# Read files directly from S3 as Spark DataFrame +df = reader.run() + +# Check that DataFrame schema is same as expected +df.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) + +# Apply any PySpark transformations +from pyspark.sql.functions import lit + +df_to_write = df.withColumn("engine", lit("onetl")) +df_to_write.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) +# |-- engine: string (nullable = false) + +# Initialize Postgres connection +postgres = Postgres( + host="192.169.11.23", + user="onetl", + password="somepassword", + database="mydb", + spark=spark, +) + +# Initialize DBWriter +db_writer = DBWriter( + connection=postgres, + # write to specific table + target="public.my_table", + # with some writing options + options=Postgres.WriteOptions(if_exists="append"), +) + +# Write DataFrame to Postgres table +db_writer.run(df_to_write) + +# Success! +``` diff --git a/mddocs/docs/_build/markdown/security.md b/mddocs/docs/_build/markdown/security.md new file mode 100644 index 000000000..3048c040b --- /dev/null +++ b/mddocs/docs/_build/markdown/security.md @@ -0,0 +1,25 @@ +# Security + +## Supported Python versions + +3.7 or above + +## Product development security recommendations + +1. Update dependencies to last stable version +2. Build SBOM for the project +3. Perform SAST (Static Application Security Testing) where possible + +## Product development security requirements + +1. No binaries in repository +2. No passwords, keys, access tokens in source code +3. No “Critical” and/or “High” vulnerabilities in contributed source code + +## Vulnerability reports + +Please, use email [mailto:onetools@mts.ru](mailto:onetools@mts.ru) for reporting security issues or anything that can cause any consequences for security. + +Please avoid any public disclosure (including registering issues) at least until it is fixed. + +Thank you in advance for understanding. diff --git a/mddocs/docs/_build/markdown/strategy/incremental_batch_strategy.md b/mddocs/docs/_build/markdown/strategy/incremental_batch_strategy.md new file mode 100644 index 000000000..9c10d9e59 --- /dev/null +++ b/mddocs/docs/_build/markdown/strategy/incremental_batch_strategy.md @@ -0,0 +1,3 @@ + + +# Incremental Batch Strategy diff --git a/mddocs/docs/_build/markdown/strategy/incremental_strategy.md b/mddocs/docs/_build/markdown/strategy/incremental_strategy.md new file mode 100644 index 000000000..540dbdad8 --- /dev/null +++ b/mddocs/docs/_build/markdown/strategy/incremental_strategy.md @@ -0,0 +1,3 @@ + + +# Incremental Strategy diff --git a/mddocs/docs/_build/markdown/strategy/index.md b/mddocs/docs/_build/markdown/strategy/index.md new file mode 100644 index 000000000..7791fed74 --- /dev/null +++ b/mddocs/docs/_build/markdown/strategy/index.md @@ -0,0 +1,10 @@ + + +# Read Strategies + +onETL have several builtin strategies for reading data: + +1. [Snapshot Strategy](snapshot_strategy.md) +2. [Incremental Strategy](incremental_strategy.md) +3. [Snapshot Batch Strategy](snapshot_batch_strategy.md) +4. [Incremental Batch Strategy](incremental_batch_strategy.md) diff --git a/mddocs/docs/_build/markdown/strategy/snapshot_batch_strategy.md b/mddocs/docs/_build/markdown/strategy/snapshot_batch_strategy.md new file mode 100644 index 000000000..bc86cab8f --- /dev/null +++ b/mddocs/docs/_build/markdown/strategy/snapshot_batch_strategy.md @@ -0,0 +1,3 @@ + + +# Snapshot Batch Strategy diff --git a/mddocs/docs/_build/markdown/strategy/snapshot_strategy.md b/mddocs/docs/_build/markdown/strategy/snapshot_strategy.md new file mode 100644 index 000000000..d775c15b0 --- /dev/null +++ b/mddocs/docs/_build/markdown/strategy/snapshot_strategy.md @@ -0,0 +1,3 @@ + + +# Snapshot Strategy diff --git a/mddocs/docs/_build/markdown/troubleshooting/index.md b/mddocs/docs/_build/markdown/troubleshooting/index.md new file mode 100644 index 000000000..847f426e9 --- /dev/null +++ b/mddocs/docs/_build/markdown/troubleshooting/index.md @@ -0,0 +1,17 @@ + + +# Troubleshooting + +In case of error please follow instructions below: + +* Read the logs or exception messages you’ve faced with. + : * If Python logs are note verbose enough, [increase the log level](../logging.md#logging). + * If Spark logs are note verbose enough, [increase the log level](spark.md#troubleshooting-spark). +* Read documentation related to a class or method you are using. +* [Google](https://google.com) the error message, and carefully read the search result: + : * [StackOverflow](https://stackoverflow.com/) answers. + * [Spark](https://spark.apache.org/docs/latest/) documentation. + * Documentation of database or filesystem you are connecting to. + * Documentation of underlying connector. +* Search for known [issues](https://github.com/MobileTeleSystems/onetl/issues), or create a new one. +* Always use the most resent versions of onETL, PySpark and connector packages, [compatible with your environment](../install/spark.md#install-spark). diff --git a/mddocs/docs/_build/markdown/troubleshooting/spark.md b/mddocs/docs/_build/markdown/troubleshooting/spark.md new file mode 100644 index 000000000..812b38acc --- /dev/null +++ b/mddocs/docs/_build/markdown/troubleshooting/spark.md @@ -0,0 +1,72 @@ + + +# Spark Troubleshooting + +## Restarting Spark session + +Sometimes it is required to stop current Spark session and start a new one, e.g. to add some .jar packages, or change session config. +But PySpark not only starts Spark session, but also starts Java virtual machine (JVM) process in the background, +which. So calling `sparkSession.stop()` [does not shutdown JVM](https://issues.apache.org/jira/browse/SPARK-47740), +and this can cause some issue. + +Also apart from JVM properties, stopping Spark session does not clear Spark context, which is a global object. So new +Spark sessions are created using the same context object, and thus using the same Spark config options. + +To properly stop Spark session, it is **required** to: +\* Stop Spark session by calling `sparkSession.stop()`. +\* **STOP PYTHON INTERPRETER**, e.g. by calling `sys.exit()`. +\* Start new Python interpreter. +\* Start new Spark session with config options you need. + +Skipping some of these steps can lead to issues with creating new Spark session. + +## Driver log level + +Default logging level for Spark session is `WARN`. To show more verbose logs, use: + +```python +spark.sparkContext.setLogLevel("INFO") +``` + +or increase verbosity even more: + +```python +spark.sparkContext.setLogLevel("DEBUG") +``` + +After getting all information you need, you can return back the previous log level: + +```python +spark.sparkContext.setLogLevel("WARN") +``` + +## Executors log level + +`sparkContext.setLogLevel` changes only log level of Spark session on Spark **driver**. +To make Spark executor logs more verbose, perform following steps: + +* Create `log4j.properties` file with content like this: + ```jproperties + log4j.rootCategory=DEBUG, console + + log4j.appender.console=org.apache.log4j.ConsoleAppender + log4j.appender.console.target=System.err + log4j.appender.console.layout=org.apache.log4j.PatternLayout + log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{1}: %m%n + ``` +* Stop existing Spark session and create a new one with following options: + ```python + from pyspark.sql import SparkSession + + spark = ( + SparkSesion.builder.config("spark.files", "file:log4j.properties").config( + "spark.executor.extraJavaOptions", "-Dlog4j.configuration=file:log4j.properties" + ) + # you can apply the same logging settings to Spark driver, by uncommenting the line below + # .config("spark.driver.extraJavaOptions", "-Dlog4j.configuration=file:log4j.properties") + .getOrCreate() + ) + ``` + +Each Spark executor will receive a copy of `log4j.properties` file during start, and load it to change own log level. +Same approach can be used for Spark driver as well, to investigate issue when Spark session cannot properly start. diff --git a/mddocs/docs/_static/icon.svg b/mddocs/docs/_static/icon.svg new file mode 100644 index 000000000..a4d737f81 --- /dev/null +++ b/mddocs/docs/_static/icon.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/mddocs/docs/_static/logo.svg b/mddocs/docs/_static/logo.svg new file mode 100644 index 000000000..76527ebf1 --- /dev/null +++ b/mddocs/docs/_static/logo.svg @@ -0,0 +1,214 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mddocs/docs/_static/logo_wide.svg b/mddocs/docs/_static/logo_wide.svg new file mode 100644 index 000000000..981bf0148 --- /dev/null +++ b/mddocs/docs/_static/logo_wide.svg @@ -0,0 +1,329 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mddocs/docs/changelog.md b/mddocs/docs/changelog.md new file mode 100644 index 000000000..b66b72579 --- /dev/null +++ b/mddocs/docs/changelog.md @@ -0,0 +1,8 @@ +# Changelog + +```{toctree} +:caption: Changelog +:maxdepth: 1 + +changelog/index +``` diff --git a/mddocs/docs/changelog/0.10.0.md b/mddocs/docs/changelog/0.10.0.md new file mode 100644 index 000000000..68a609eb6 --- /dev/null +++ b/mddocs/docs/changelog/0.10.0.md @@ -0,0 +1,543 @@ +# 0.10.0 (2023-12-18) + +## Breaking Changes + +- Upgrade `etl-entities` from v1 to v2 ([#172](https://github.com/MobileTeleSystems/onetl/pull/172)). + + This implies that `HWM` classes are now have different internal structure than they used to. + + Before: + + ```python + from etl_entities.old_hwm import IntHWM as OldIntHWM + from etl_entities.source import Column, Table + from etl_entities.process import Process + + hwm = OldIntHWM( + process=Process(name="myprocess", task="abc", dag="cde", host="myhost"), + source=Table(name="schema.table", instance="postgres://host:5432/db"), + column=Column(name="col1"), + value=123, + ) + ``` + + After: + + ```python + from etl_entities.hwm import ColumnIntHWM + + hwm = ColumnIntHWM( + name="some_unique_name", + description="any value you want", + source="schema.table", + expression="col1", + value=123, + ) + ``` + + **Breaking change:** If you used HWM classes from `etl_entities` module, you should rewrite your code to make it compatible with new version. + +??? "More details" + + - ``HWM`` classes used by previous onETL versions were moved from ``etl_entities`` to ``etl_entities.old_hwm`` submodule. They are here for compatibility reasons, but are planned to be removed in ``etl-entities`` v3 release. + - New ``HWM`` classes have flat structure instead of nested. + - New ``HWM`` classes have mandatory ``name`` attribute (it was known as ``qualified_name`` before). + - Type aliases used while serializing and deserializing ``HWM`` objects to ``dict`` representation were changed too: ``int`` → ``column_int``. + + + To make migration simpler, you can use new method: + + ```python + old_hwm = OldIntHWM(...) + new_hwm = old_hwm.as_new_hwm() + ``` + + Which automatically converts all fields from old structure to new one, including `qualified_name` → `name`. + +- **Breaking changes:** + + - Methods `BaseHWMStore.get()` and `BaseHWMStore.save()` were renamed to `get_hwm()` and `set_hwm()`. + - They now can be used only with new HWM classes from `etl_entities.hwm`, **old HWM classes are not supported**. + + If you used them in your code, please update it accordingly. + +- YAMLHWMStore **CANNOT read files created by older onETL versions** (0.9.x or older). + +??? "Update procedure" + + ```python + # pip install onetl==0.9.5 + + # Get qualified_name for HWM + + + # Option 1. HWM is built manually + from etl_entities import IntHWM, FileListHWM + from etl_entities.source import Column, Table, RemoteFolder + from etl_entities.process import Process + + # for column HWM + old_column_hwm = IntHWM( + process=Process(name="myprocess", task="abc", dag="cde", host="myhost"), + source=Table(name="schema.table", instance="postgres://host:5432/db"), + column=Column(name="col1"), + ) + qualified_name = old_column_hwm.qualified_name + # "col1#schema.table@postgres://host:5432/db#cde.abc.myprocess@myhost" + + # for file HWM + old_file_hwm = FileListHWM( + process=Process(name="myprocess", task="abc", dag="cde", host="myhost"), + source=RemoteFolder(name="/absolute/path", instance="ftp://ftp.server:21"), + ) + qualified_name = old_file_hwm.qualified_name + # "file_list#/absolute/path@ftp://ftp.server:21#cde.abc.myprocess@myhost" + + + # Option 2. HWM is generated automatically (by DBReader/FileDownloader) + # See onETL logs and search for string like qualified_name = '...' + + qualified_name = "col1#schema.table@postgres://host:5432/db#cde.abc.myprocess@myhost" + + + # Get .yml file path by qualified_name + + import os + from pathlib import PurePosixPath + from onetl.hwm.store import YAMLHWMStore + + # here you should pass the same arguments as used on production, if any + yaml_hwm_store = YAMLHWMStore() + hwm_path = yaml_hwm_store.get_file_path(qualified_name) + print(hwm_path) + + # for column HWM + # LocalPosixPath('/home/maxim/.local/share/onETL/yml_hwm_store/col1__schema.table__postgres_host_5432_db__cde.abc.myprocess__myhost.yml') + + # for file HWM + # LocalPosixPath('/home/maxim/.local/share/onETL/yml_hwm_store/file_list__absolute_path__ftp_ftp.server_21__cde.abc.myprocess__myhost.yml') + + + # Read raw .yml file content + + from yaml import safe_load, dump + + raw_old_hwm_items = safe_load(hwm_path.read_text()) + print(raw_old_hwm_items) + + # for column HWM + # [ + # { + # "column": { "name": "col1", "partition": {} }, + # "modified_time": "2023-12-18T10: 39: 47.377378", + # "process": { "dag": "cde", "host": "myhost", "name": "myprocess", "task": "abc" }, + # "source": { "instance": "postgres: //host:5432/db", "name": "schema.table" }, + # "type": "int", + # "value": "123", + # }, + # ] + + # for file HWM + # [ + # { + # "modified_time": "2023-12-18T11:15:36.478462", + # "process": { "dag": "cde", "host": "myhost", "name": "myprocess", "task": "abc" }, + # "source": { "instance": "ftp://ftp.server:21", "name": "/absolute/path" }, + # "type": "file_list", + # "value": ["file1.txt", "file2.txt"], + # }, + # ] + + + # Convert file content to new structure, compatible with onETL 0.10.x + raw_new_hwm_items = [] + for old_hwm in raw_old_hwm_items: + new_hwm = {"name": qualified_name, "modified_time": old_hwm["modified_time"]} + + if "column" in old_hwm: + new_hwm["expression"] = old_hwm["column"]["name"] + new_hwm["entity"] = old_hwm["source"]["name"] + old_hwm.pop("process", None) + + if old_hwm["type"] == "int": + new_hwm["type"] = "column_int" + new_hwm["value"] = old_hwm["value"] + + elif old_hwm["type"] == "date": + new_hwm["type"] = "column_date" + new_hwm["value"] = old_hwm["value"] + + elif old_hwm["type"] == "datetime": + new_hwm["type"] = "column_datetime" + new_hwm["value"] = old_hwm["value"] + + elif old_hwm["type"] == "file_list": + new_hwm["type"] = "file_list" + new_hwm["value"] = [ + os.fspath(PurePosixPath(old_hwm["source"]["name"]).joinpath(path)) + for path in old_hwm["value"] + ] + + else: + raise ValueError("WAT?") + + raw_new_hwm_items.append(new_hwm) + + + print(raw_new_hwm_items) + # for column HWM + # [ + # { + # "name": "col1#schema.table@postgres://host:5432/db#cde.abc.myprocess@myhost", + # "modified_time": "2023-12-18T10:39:47.377378", + # "expression": "col1", + # "source": "schema.table", + # "type": "column_int", + # "value": 123, + # }, + # ] + + # for file HWM + # [ + # { + # "name": "file_list#/absolute/path@ftp://ftp.server:21#cde.abc.myprocess@myhost", + # "modified_time": "2023-12-18T11:15:36.478462", + # "entity": "/absolute/path", + # "type": "file_list", + # "value": ["/absolute/path/file1.txt", "/absolute/path/file2.txt"], + # }, + # ] + + + # Save file with new content + with open(hwm_path, "w") as file: + dump(raw_new_hwm_items, file) + + + # Stop Python interpreter and update onETL + # pip install onetl==0.10.0 + # Check that new .yml file can be read + + from onetl.hwm.store import YAMLHWMStore + + qualified_name = ... + + # here you should pass the same arguments as used on production, if any + yaml_hwm_store = YAMLHWMStore() + yaml_hwm_store.get_hwm(qualified_name) + + # for column HWM + # ColumnIntHWM( + # name='col1#schema.table@postgres://host:5432/db#cde.abc.myprocess@myhost', + # description='', + # entity='schema.table', + # value=123, + # expression='col1', + # modified_time=datetime.datetime(2023, 12, 18, 10, 39, 47, 377378), + # ) + + # for file HWM + # FileListHWM( + # name='file_list#/absolute/path@ftp://ftp.server:21#cde.abc.myprocess@myhost', + # description='', + # entity=AbsolutePath('/absolute/path'), + # value=frozenset({AbsolutePath('/absolute/path/file1.txt'), AbsolutePath('/absolute/path/file2.txt')}), + # expression=None, + # modified_time=datetime.datetime(2023, 12, 18, 11, 15, 36, 478462) + # ) + + + # That's all! + ``` + +But most of users use other HWM store implementations which do not have such issues. + +- Several classes and functions were moved from `onetl` to `etl_entities`: + +=== "onETL ``0.9.x`` and older" + + ```python + + from onetl.hwm.store import ( + detect_hwm_store, + BaseHWMStore, + HWMStoreClassRegistry, + register_hwm_store_class, + HWMStoreManager, + MemoryHWMStore, + ) + ``` + +=== "nETL ``0.10.x`` and newer" + + ```python + + from etl_entities.hwm_store import ( + detect_hwm_store, + BaseHWMStore, + HWMStoreClassRegistry, + register_hwm_store_class, + HWMStoreManager, + MemoryHWMStore, + ) + ``` + + They still can be imported from old module, but this is deprecated and will be removed in v1.0.0 release. + +- Change the way of passing `HWM` to `DBReader` and `FileDownloader` classes: + +=== "onETL ``0.9.x`` and older" + + ```python linenums="1" hl_lines="12-21" + # Simple + reader = DBReader( + connection=..., + source=..., + hwm_column="col1", + ) + + + + + + # Complex + reader = DBReader( + connection=..., + source=..., + hwm_column=( + "col1", + "cast(col1 as date)", + ), + ) + + + # Files + downloader = FileDownloader( + connection=..., + source_path=..., + target_path=..., + hwm_type="file_list", + ) + ``` + +=== "onETL ``0.10.x`` and newer" + + ```python linenums="1" hl_lines="12-21" + # Simple + reader = DBReader( + connection=..., + source=..., + hwm=DBReader.AutoDetectHWM( + # name is mandatory now! + name="my_unique_hwm_name", + expression="col1", + ), + ) + + # Complex + reader = DBReader( + connection=..., + source=..., + hwm=DBReader.AutoDetectHWM( + # name is mandatory now! + name="my_unique_hwm_name", + expression="cast(col1 as date)", + ), + ) + + # Files + downloader = FileDownloader( + connection=..., + source_path=..., + target_path=..., + hwm=FileListHWM( + # name is mandatory now! + name="another_unique_hwm_name", + ), + ) + ``` + + + New HWM classes have **mandatory** `name` attribute which should be passed explicitly, + instead of generating if automatically under the hood. + + Automatic `name` generation using the old `DBReader.hwm_column` / `FileDownloader.hwm_type` + syntax is still supported, but will be removed in v1.0.0 release. ([#179](https://github.com/MobileTeleSystems/onetl/pull/179)) + +- Performance of read Incremental and Batch strategies has been drastically improved. ([#182](https://github.com/MobileTeleSystems/onetl/pull/182)). + +??? "Before and after in details" + + ``DBReader.run()`` + incremental/batch strategy behavior in versions 0.9.x and older: + + - Get table schema by making query ``SELECT * FROM table WHERE 1=0`` (if ``DBReader.columns`` has ``*``) + - Expand ``*`` to real column names from table, add here ``hwm_column``, remove duplicates (as some RDBMS does not allow that). + - Create dataframe from query like ``SELECT hwm_expression AS hwm_column, ...other table columns... FROM table WHERE hwm_expression > prev_hwm.value``. + - Determine HWM class using dataframe schema: ``df.schema[hwm_column].dataType``. + - Determine x HWM column value using Spark: ``df.select(max(hwm_column)).collect()``. + - Use ``max(hwm_column)`` as next HWM value, and save it to HWM Store. + - Return dataframe to user. + + This was far from ideal: + + - Dataframe content (all rows or just changed ones) was loaded from the source to Spark only to get min/max values of specific column. + + - Step of fetching table schema and then substituting column names in the next query caused some unexpected errors. + + For example, source contains columns with mixed name case, like ``"CamelColumn"`` or ``"spaced column"``. + + Column names were *not* escaped during query generation, leading to queries that cannot be executed by database. + + So users have to *explicitly* pass column names ``DBReader``, wrapping columns with mixed naming with ``"``: + + ```python + reader = DBReader( + connection=..., + source=..., + columns=[ # passing '*' here leads to wrong SQL query generation + "normal_column", + '"CamelColumn"', + '"spaced column"', + ..., + ], + ) + ``` + - Using ``DBReader`` with ``IncrementalStrategy`` could lead to reading rows already read before. + + Dataframe was created from query with WHERE clause like ``hwm.expression > prev_hwm.value``, + not ``hwm.expression > prev_hwm.value AND hwm.expression <= current_hwm.value``. + + So if new rows appeared in the source **after** HWM value is determined, + they can be read by accessing dataframe content (because Spark dataframes are lazy), + leading to inconsistencies between HWM value and dataframe content. + + This may lead to issues then ``DBReader.run()`` read some data, updated HWM value, and next call of ``DBReader.run()`` + will read rows that were already read in previous run. + + ``DBReader.run()`` + incremental/batch strategy behavior in versions 0.10.x and newer: + + - Detect type of HWM expression: ``SELECT hwm.expression FROM table WHERE 1=0``. + - Determine corresponding Spark type ``df.schema[0]`` and when determine matching HWM class (if ``DReader.AutoDetectHWM`` is used). + - Get min/max values by querying the source: ``SELECT MAX(hwm.expression) FROM table WHERE hwm.expression >= prev_hwm.value``. + - Use ``max(hwm.expression)`` as next HWM value, and save it to HWM Store. + - Create dataframe from query ``SELECT ... table columns ... FROM table WHERE hwm.expression > prev_hwm.value AND hwm.expression <= current_hwm.value``, baking new HWM value into the query. + - Return dataframe to user. + + Improvements: + + - Allow source to calculate min/max instead of loading everything to Spark. This should be **faster** on large amounts of data (**up to x2**), because we do not transfer all the data from the source to Spark. This can be even faster if source have indexes for HWM column. + - Columns list is passed to source as-is, without any resolving on `DBReader` side. So you can pass `DBReader(columns=["*"])` to read tables with mixed columns naming. + - Restrict dataframe content to always match HWM values, which leads to never reading the same row twice. + + **Breaking change**: HWM column is not being implicitly added to dataframe. It was a part of `SELECT` clause, but now it is mentioned only in `WHERE` clause. + + So if you had code like this, you have to rewrite it: + +=== "onETL ``0.9.x`` and older" + + ```python linenums="1" hl_lines="1-16" + reader = DBReader( + connection=..., + source=..., + columns=[ + "col1", + "col2", + ], + hwm_column="hwm_col", + ) + + df = reader.run() + # hwm_column value is in the dataframe + assert df.columns == ["col1", "col2", "hwm_col"] + + + + + reader = DBReader( + connection=..., + source=..., + columns=[ + "col1", + "col2", + ], + hwm_column=( + "hwm_col", + "cast(hwm_col as int)", + ), + ) + + df = reader.run() + # hwm_expression value is in the dataframe + assert df.columns == ["col1", "col2", "hwm_col"] + + ``` + +=== "onETL ``0.10.x`` and newer" + + ```python linenums="1" hl_lines="1-16" + reader = DBReader( + connection=..., + source=..., + columns=[ + "col1", + "col2", + # add hwm_column explicitly + "hwm_col", + ], + hwm_column="hwm_col", + ) + + df = reader.run() + # if columns list is not updated, + # this fill fail + assert df.columns == ["col1", "col2", "hwm_col"] + + reader = DBReader( + connection=..., + source=..., + columns=[ + "col1", + "col2", + # add hwm_expression explicitly + "cast(hwm_col as int) as hwm_col", + ], + hwm_column=( + "hwm_col", + "cast(hwm_col as int)", + ), + ) + df = reader.run() + # if columns list is not updated, + # this fill fail + assert df.columns == ["col1", "col2", "hwm_col"] + ``` + + But most users just use `columns=["*"]` anyway, they won't see any changes. + +- `FileDownloader.run()` now updates HWM in HWM Store not after each file is being successfully downloaded, + but after all files were handled. + + This is because: + + - FileDownloader can be used with `DownloadOptions(workers=N)`, which could lead to race condition - one thread can save to HWM store one HWM value when another thread will save different value. + - FileDownloader can download hundreds and thousands of files, and issuing a request to HWM Store for each file could potentially DDoS HWM Store. ([#189](https://github.com/MobileTeleSystems/onetl/pull/189)) + + There is a exception handler which tries to save HWM to HWM store if download process was interrupted. But if it was interrupted by force, like sending `SIGKILL` event, + HWM will not be saved to HWM store, so some already downloaded files may be downloaded again next time. + + But unexpected process kill may produce other negative impact, like some file will be downloaded partially, so this is an expected behavior. + +## Features + +- Add Python 3.12 compatibility. ([#167](https://github.com/MobileTeleSystems/onetl/pull/167)) +- `Excel` file format now can be used with Spark 3.5.0. ([#187](https://github.com/MobileTeleSystems/onetl/pull/187)) +- `SnapshotBatchStagy` and `IncrementalBatchStrategy` does no raise exceptions if source does not contain any data. + Instead they stop at first iteration and return empty dataframe. ([#188](https://github.com/MobileTeleSystems/onetl/pull/188)) +- Cache result of `connection.check()` in high-level classes like `DBReader`, `FileDownloader` and so on. This makes logs less verbose. ([#190](https://github.com/MobileTeleSystems/onetl/pull/190)) + +## Bug Fixes + +- Fix `@slot` and `@hook` decorators returning methods with missing arguments in signature (Pylance, VS Code). ([#183](https://github.com/MobileTeleSystems/onetl/pull/183)) +- Kafka connector documentation said that it does support reading topic data incrementally by passing `group.id` or `groupIdPrefix`. + Actually, this is not true, because Spark does not send information to Kafka which messages were consumed. + So currently users can only read the whole topic, no incremental reads are supported. diff --git a/mddocs/docs/changelog/0.10.1.md b/mddocs/docs/changelog/0.10.1.md new file mode 100644 index 000000000..ba3ed40bb --- /dev/null +++ b/mddocs/docs/changelog/0.10.1.md @@ -0,0 +1,29 @@ +# 0.10.1 (2024-02-05) + +## Features + +- Add support of `Incremental Strategies` for `Kafka` connection: + + ```python + reader = DBReader( + connection=Kafka(...), + source="topic_name", + hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="offset"), + ) + + with IncrementalStrategy(): + df = reader.run() + ``` + + This lets you resume reading data from a Kafka topic starting at the last committed offset from your previous run. ([#202](https://github.com/MobileTeleSystems/onetl/pull/202)) + +- Add `has_data`, `raise_if_no_data` methods to `DBReader` class. ([#203](https://github.com/MobileTeleSystems/onetl/pull/203)) + +- Updare VMware Greenplum connector from `2.1.4` to `2.3.0`. This implies: + : - Greenplum 7.x support + - [Kubernetes support](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/configure.html#k8scfg) + - New read option [gpdb.matchDistributionPolicy](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#distpolmotion) + which allows to match each Spark executor with specific Greenplum segment, avoiding redundant data transfer between Greenplum segments + - Allows overriding [Greenplum optimizer parameters](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#greenplum-gucs) in read/write operations ([#208](https://github.com/MobileTeleSystems/onetl/pull/208)) + +- `Greenplum.get_packages()` method now accepts optional arg `package_version` which allows to override version of Greenplum connector package. ([#208](https://github.com/MobileTeleSystems/onetl/pull/208)) diff --git a/mddocs/docs/changelog/0.10.2.md b/mddocs/docs/changelog/0.10.2.md new file mode 100644 index 000000000..3ae7ad822 --- /dev/null +++ b/mddocs/docs/changelog/0.10.2.md @@ -0,0 +1,39 @@ +# 0.10.2 (2024-03-21) + +## Features + +- Add support of Pydantic v2. ([#230](https://github.com/MobileTeleSystems/onetl/pull/230)) + +## Improvements + +- Improve database connections documentation: + - Add "Types" section describing mapping between Clickhouse and Spark types + - Add "Prerequisites" section describing different aspects of connecting to Clickhouse + - Separate documentation of `DBReader` and `.sql()` / `.pipeline(...)` + - Add examples for `.fetch()` and `.execute()` ([#211](https://github.com/MobileTeleSystems/onetl/pull/211), [#228](https://github.com/MobileTeleSystems/onetl/pull/228), [#229](https://github.com/MobileTeleSystems/onetl/pull/229), [#233](https://github.com/MobileTeleSystems/onetl/pull/233), [#234](https://github.com/MobileTeleSystems/onetl/pull/234), [#235](https://github.com/MobileTeleSystems/onetl/pull/235), [#236](https://github.com/MobileTeleSystems/onetl/pull/236), [#240](https://github.com/MobileTeleSystems/onetl/pull/240)) + +- Add notes to Greenplum documentation about issues with IP resolution and building `gpfdist` URL ([#228](https://github.com/MobileTeleSystems/onetl/pull/228)) + +- Allow calling `MongoDB.pipeline(...)` with passing just collection name, without explicit aggregation pipeline. ([#237](https://github.com/MobileTeleSystems/onetl/pull/237)) + +- Update default `Postgres(extra={...})` to include `{"stringtype": "unspecified"}` option. + This allows to write text data to non-text column (or vice versa), relying to Postgres cast capabilities. + + For example, now it is possible to read column of type `money` as Spark's `StringType()`, and write it back to the same column, + without using intermediate columns or tables. ([#229](https://github.com/MobileTeleSystems/onetl/pull/229)) + +## Bug Fixes + +- Return back handling of `DBReader(columns="string")`. This was a valid syntax up to v0.10 release, but it was removed because + most of users neved used it. It looks that we were wrong, returning this behavior back, but with deprecation warning. ([#238](https://github.com/MobileTeleSystems/onetl/pull/238)) + +- Downgrade Greenplum package version from `2.3.0` to `2.2.0`. ([#239](https://github.com/MobileTeleSystems/onetl/pull/239)) + + This is because version 2.3.0 introduced issues with writing data to Greenplum 6.x. + Connector can open transaction with `SELECT * FROM table LIMIT 0` query, but does not close it, which leads to deadlocks. + + For using this connector with Greenplum 7.x, please pass package version explicitly: + + ```python + maven_packages = Greenplum.get_packages(package_version="2.3.0", ...) + ``` diff --git a/mddocs/docs/changelog/0.11.0.md b/mddocs/docs/changelog/0.11.0.md new file mode 100644 index 000000000..c77c977b0 --- /dev/null +++ b/mddocs/docs/changelog/0.11.0.md @@ -0,0 +1,212 @@ +# 0.11.0 (2024-05-27) + +## Breaking Changes + +There can be some changes in connection behavior, related to version upgrades. So we mark these changes as **breaking** although +most of users will not see any differences. + +- Update Clickhouse JDBC driver to latest version ([#249](https://github.com/MobileTeleSystems/onetl/pull/249)): + - Package was renamed `ru.yandex.clickhouse:clickhouse-jdbc` → `com.clickhouse:clickhouse-jdbc`. + - Package version changed `0.3.2` → `0.6.0-patch5`. + - Driver name changed `ru.yandex.clickhouse.ClickHouseDriver` → `com.clickhouse.jdbc.ClickHouseDriver`. + + This brings up several fixes for Spark \<-> Clickhouse type compatibility, and also Clickhouse clusters support. + +- Update other JDBC drivers to latest versions: + - MSSQL `12.2.0` → `12.6.2` ([#254](https://github.com/MobileTeleSystems/onetl/pull/254)). + - MySQL `8.0.33` → `8.4.0` ([#253](https://github.com/MobileTeleSystems/onetl/pull/253), [#285](https://github.com/MobileTeleSystems/onetl/pull/285)). + - Oracle `23.2.0.0` → `23.4.0.24.05` ([#252](https://github.com/MobileTeleSystems/onetl/pull/252), [#284](https://github.com/MobileTeleSystems/onetl/pull/284)). + - Postgres `42.6.0` → `42.7.3` ([#251](https://github.com/MobileTeleSystems/onetl/pull/251)). + +- Update MongoDB connector to latest version: `10.1.1` → `10.3.0` ([#255](https://github.com/MobileTeleSystems/onetl/pull/255), [#283](https://github.com/MobileTeleSystems/onetl/pull/283)). + + This brings up Spark 3.5 support. + +- Update `XML` package to latest version: `0.17.0` → `0.18.0` ([#259](https://github.com/MobileTeleSystems/onetl/pull/259)). + + This brings few bugfixes with datetime format handling. + +- For JDBC connections add new `SQLOptions` class for `DB.sql(query, options=...)` method ([#272](https://github.com/MobileTeleSystems/onetl/pull/272)). + + Firsly, to keep naming more consistent. + + Secondly, some of options are not supported by `DB.sql(...)` method, but supported by `DBReader`. + For example, `SQLOptions` do not support `partitioning_mode` and require explicit definition of `lower_bound` and `upper_bound` when `num_partitions` is greater than 1. + `ReadOptions` does support `partitioning_mode` and allows skipping `lower_bound` and `upper_bound` values. + + This require some code changes. Before: + + ```python + from onetl.connection import Postgres + + postgres = Postgres(...) + df = postgres.sql( + """ + SELECT * + FROM some.mytable + WHERE key = 'something' + """, + options=Postgres.ReadOptions( + partitioning_mode="range", + partition_column="id", + num_partitions=10, + ), + ) + ``` + + After: + + ```python + from onetl.connection import Postgres + + postgres = Postgres(...) + df = postgres.sql( + """ + SELECT * + FROM some.mytable + WHERE key = 'something' + """, + options=Postgres.SQLOptions( + # partitioning_mode is not supported! + partition_column="id", + num_partitions=10, + lower_bound=0, # <-- set explicitly + upper_bound=1000, # <-- set explicitly + ), + ) + ``` + + For now, `DB.sql(query, options=...)` can accept `ReadOptions` to keep backward compatibility, but emits deprecation warning. + The support will be removed in `v1.0.0`. + +- Split up `JDBCOptions` class into `FetchOptions` and `ExecuteOptions` ([#274](https://github.com/MobileTeleSystems/onetl/pull/274)). + + New classes are used by `DB.fetch(query, options=...)` and `DB.execute(query, options=...)` methods respectively. + This is mostly to keep naming more consistent. + + This require some code changes. Before: + + ```python + from onetl.connection import Postgres + + postgres = Postgres(...) + df = postgres.fetch( + "SELECT * FROM some.mytable WHERE key = 'something'", + options=Postgres.JDBCOptions( + fetchsize=1000, + query_timeout=30, + ), + ) + + postgres.execute( + "UPDATE some.mytable SET value = 'new' WHERE key = 'something'", + options=Postgres.JDBCOptions(query_timeout=30), + ) + ``` + + After: + + ```python + from onetl.connection import Postgres + + # Using FetchOptions for fetching data + postgres = Postgres(...) + df = postgres.fetch( + "SELECT * FROM some.mytable WHERE key = 'something'", + options=Postgres.FetchOptions( # <-- change class name + fetchsize=1000, + query_timeout=30, + ), + ) + + # Using ExecuteOptions for executing statements + postgres.execute( + "UPDATE some.mytable SET value = 'new' WHERE key = 'something'", + options=Postgres.ExecuteOptions(query_timeout=30), # <-- change class name + ) + ``` + + For now, `DB.fetch(query, options=...)` and `DB.execute(query, options=...)` can accept `JDBCOptions`, to keep backward compatibility, + but emit a deprecation warning. The old class will be removed in `v1.0.0`. + +- Serialize `ColumnDatetimeHWM` to Clickhouse's `DateTime64(6)` (precision up to microseconds) instead of `DateTime` (precision up to seconds) ([#267](https://github.com/MobileTeleSystems/onetl/pull/267)). + + In previous onETL versions, `ColumnDatetimeHWM` value was rounded to the second, and thus reading some rows that were read in previous runs, + producing duplicates. + + For Clickhouse versions below 21.1 comparing column of type `DateTime` with a value of type `DateTime64` is not supported, returning an empty dataframe. + To avoid this, replace: + + ```python + DBReader( + ..., + hwm=DBReader.AutoDetectHWM( + name="my_hwm", + expression="hwm_column", # <-- + ), + ) + ``` + + with: + + ```python + DBReader( + ..., + hwm=DBReader.AutoDetectHWM( + name="my_hwm", + expression="CAST(hwm_column AS DateTime64)", # <-- add explicit CAST + ), + ) + ``` + +- Pass JDBC connection extra params as `properties` dict instead of URL with query part ([#268](https://github.com/MobileTeleSystems/onetl/pull/268)). + + This allows passing custom connection parameters like `Clickhouse(extra={"custom_http_options": "option1=value1,option2=value2"})` + without need to apply urlencode to parameter value, like `option1%3Dvalue1%2Coption2%3Dvalue2`. + +## Features + +Improve user experience with Kafka messages and Database tables with serialized columns, like JSON/XML. + +- Allow passing custom package version as argument for `DB.get_packages(...)` method of several DB connectors: + - `Clickhouse.get_packages(package_version=..., apache_http_client_version=...)` ([#249](https://github.com/MobileTeleSystems/onetl/pull/249)). + - `MongoDB.get_packages(scala_version=..., spark_version=..., package_version=...)` ([#255](https://github.com/MobileTeleSystems/onetl/pull/255)). + - `MySQL.get_packages(package_version=...)` ([#253](https://github.com/MobileTeleSystems/onetl/pull/253)). + - `MSSQL.get_packages(java_version=..., package_version=...)` ([#254](https://github.com/MobileTeleSystems/onetl/pull/254)). + - `Oracle.get_packages(java_version=..., package_version=...)` ([#252](https://github.com/MobileTeleSystems/onetl/pull/252)). + - `Postgres.get_packages(package_version=...)` ([#251](https://github.com/MobileTeleSystems/onetl/pull/251)). + - `Teradata.get_packages(package_version=...)` ([#256](https://github.com/MobileTeleSystems/onetl/pull/256)). + Now users can downgrade or upgrade connection without waiting for next onETL release. Previously only `Kafka` and `Greenplum` supported this feature. +- Add `FileFormat.parse_column(...)` method to several classes: + - `Avro.parse_column(col)` ([#265](https://github.com/MobileTeleSystems/onetl/pull/265)). + - `JSON.parse_column(col, schema=...)` ([#257](https://github.com/MobileTeleSystems/onetl/pull/257)). + - `CSV.parse_column(col, schema=...)` ([#258](https://github.com/MobileTeleSystems/onetl/pull/258)). + - `XML.parse_column(col, schema=...)` ([#269](https://github.com/MobileTeleSystems/onetl/pull/269)). + This allows parsing data in `value` field of Kafka message or string/binary column of some table as a nested Spark structure. +- Add `FileFormat.serialize_column(...)` method to several classes: + - `Avro.serialize_column(col)` ([#265](https://github.com/MobileTeleSystems/onetl/pull/265)). + - `JSON.serialize_column(col)` ([#257](https://github.com/MobileTeleSystems/onetl/pull/257)). + - `CSV.serialize_column(col)` ([#258](https://github.com/MobileTeleSystems/onetl/pull/258)). + This allows saving Spark nested structures or arrays to `value` field of Kafka message or string/binary column of some table. + +## Improvements + +Few documentation improvements. + +- Replace all `assert` in documentation with doctest syntax. This should make documentation more readable ([#273](https://github.com/MobileTeleSystems/onetl/pull/273)). +- Add generic `Troubleshooting` guide ([#275](https://github.com/MobileTeleSystems/onetl/pull/275)). +- Improve Kafka documentation: + - Add "Prerequisites" page describing different aspects of connecting to Kafka. + - Improve "Reading from" and "Writing to" page of Kafka documentation, add more examples and usage notes. + - Add "Troubleshooting" page ([#276](https://github.com/MobileTeleSystems/onetl/pull/276)). +- Improve Hive documentation: + - Add "Prerequisites" page describing different aspects of connecting to Hive. + - Improve "Reading from" and "Writing to" page of Hive documentation, add more examples and recommendations. + - Improve "Executing statements in Hive" page of Hive documentation. ([#278](https://github.com/MobileTeleSystems/onetl/pull/278)). +- Add "Prerequisites" page describing different aspects of using SparkHDFS and SparkS3 connectors. ([#279](https://github.com/MobileTeleSystems/onetl/pull/279)). +- Add note about connecting to Clickhouse cluster. ([#280](https://github.com/MobileTeleSystems/onetl/pull/280)). +- Add notes about versions when specific class/method/attribute/argument was added, renamed or changed behavior ([#282](https://github.com/MobileTeleSystems/onetl/pull/282)). + +## Bug Fixes + +- Fix missing `pysmb` package after installing `pip install onetl[files]` . diff --git a/mddocs/docs/changelog/0.11.1.md b/mddocs/docs/changelog/0.11.1.md new file mode 100644 index 000000000..823afe3be --- /dev/null +++ b/mddocs/docs/changelog/0.11.1.md @@ -0,0 +1,9 @@ +# 0.11.1 (2024-05-29) + +## Features + +- Change `MSSQL.port` default from `1433` to `None`, allowing use of `instanceName` to detect port number. ([#287](https://github.com/MobileTeleSystems/onetl/pull/287)) + +## Bug Fixes + +- Remove `fetchsize` from `JDBC.WriteOptions`. ([#288](https://github.com/MobileTeleSystems/onetl/pull/288)) diff --git a/mddocs/docs/changelog/0.11.2.md b/mddocs/docs/changelog/0.11.2.md new file mode 100644 index 000000000..9278d22f8 --- /dev/null +++ b/mddocs/docs/changelog/0.11.2.md @@ -0,0 +1,5 @@ +# 0.11.2 (2024-09-02) + +## Bug Fixes + +- Fix passing `Greenplum(extra={"options": ...})` during read/write operations. ([#308](https://github.com/MobileTeleSystems/onetl/pull/308)) diff --git a/mddocs/docs/changelog/0.12.0.md b/mddocs/docs/changelog/0.12.0.md new file mode 100644 index 000000000..aeaca1d23 --- /dev/null +++ b/mddocs/docs/changelog/0.12.0.md @@ -0,0 +1,54 @@ +# 0.12.0 (2024-09-03) + +## Breaking Changes + +- Change connection URL used for generating HWM names of S3 and Samba sources: + - `smb://host:port` -> `smb://host:port/share` + - `s3://host:port` -> `s3://host:port/bucket` ([#304](https://github.com/MobileTeleSystems/onetl/pull/304)) +- Update DB connectors/drivers to latest versions: + - Clickhouse `0.6.0-patch5` → `0.6.5` + - MongoDB `10.3.0` → `10.4.0` + - MSSQL `12.6.2` → `12.8.1` + - MySQL `8.4.0` → `9.0.0` + - Oracle `23.4.0.24.05` → `23.5.0.24.07` + - Postgres `42.7.3` → `42.7.4` +- Update `Excel` package from `0.20.3` to `0.20.4`, to include Spark 3.5.1 support. ([#306](https://github.com/MobileTeleSystems/onetl/pull/306)) + +## Features + +- Add support for specifying file formats (`ORC`, `Parquet`, `CSV`, etc.) in `HiveWriteOptions.format` ([#292](https://github.com/MobileTeleSystems/onetl/pull/292)): + + ```python + Hive.WriteOptions(format=ORC(compression="snappy")) + ``` + +- Collect Spark execution metrics in following methods, and log then in DEBUG mode: + - `DBWriter.run()` + - `FileDFWriter.run()` + - `Hive.sql()` + - `Hive.execute()` + + This is implemented using custom `SparkListener` which wraps the entire method call, and + then report collected metrics. But these metrics sometimes may be missing due to Spark architecture, + so they are not reliable source of information. That's why logs are printed only in DEBUG mode, and + are not returned as method call result. ([#303](https://github.com/MobileTeleSystems/onetl/pull/303)) + +- Generate default `jobDescription` based on currently executed method. Examples: + - `DBWriter.run(schema.table) -> Postgres[host:5432/database]` + - `MongoDB[localhost:27017/admin] -> DBReader.has_data(mycollection)` + - `Hive[cluster].execute()` + + If user already set custom `jobDescription`, it will left intact. ([#304](https://github.com/MobileTeleSystems/onetl/pull/304)) + +- Add log.info about JDBC dialect usage ([#305](https://github.com/MobileTeleSystems/onetl/pull/305)): + + ```text + |MySQL| Detected dialect: 'org.apache.spark.sql.jdbc.MySQLDialect' + ``` + +- Log estimated size of in-memory dataframe created by `JDBC.fetch` and `JDBC.execute` methods. ([#303](https://github.com/MobileTeleSystems/onetl/pull/303)) + +## Bug Fixes + +- Fix passing `Greenplum(extra={"options": ...})` during read/write operations. ([#308](https://github.com/MobileTeleSystems/onetl/pull/308)) +- Do not raise exception if yield-based hook whas something past (and only one) `yield`. diff --git a/mddocs/docs/changelog/0.12.1.md b/mddocs/docs/changelog/0.12.1.md new file mode 100644 index 000000000..d32401517 --- /dev/null +++ b/mddocs/docs/changelog/0.12.1.md @@ -0,0 +1,17 @@ +# 0.12.1 (2024-10-28) + +## Features + +- Log detected JDBC dialect while using `DBWriter`. + +## Bug Fixes + +- Fix `SparkMetricsRecorder` failing when receiving `SparkListenerTaskEnd` without `taskMetrics` (e.g. executor was killed by OOM). ([#313](https://github.com/MobileTeleSystems/onetl/pull/313)) +- Call `kinit` before checking for HDFS active namenode. +- Wrap `kinit` with `threading.Lock` to avoid multithreading issues. +- Immediately show `kinit` errors to user, instead of hiding them. +- Use `AttributeError` instead of `ImportError` in module's `__getattr__` method, to make code compliant with Python spec. + +## Doc only Changes + +- Add note about [spark-dialect-extension](https://github.com/MobileTeleSystems/spark-dialect-extension) package to Clickhouse connector documentation. ([#310](https://github.com/MobileTeleSystems/onetl/pull/310)) diff --git a/mddocs/docs/changelog/0.12.2.md b/mddocs/docs/changelog/0.12.2.md new file mode 100644 index 000000000..23a8d383d --- /dev/null +++ b/mddocs/docs/changelog/0.12.2.md @@ -0,0 +1,18 @@ +# 0.12.2 (2024-11-12) + +## Improvements + +- Change Spark `jobDescription` for DBReader & FileDFReader from `DBReader.run() -> Connection` to `Connection -> DBReader.run()`. + +## Bug Fixes + +- Fix `log_hwm` result for `KeyValueIntHWM` (used by Kafka). ([#316](https://github.com/MobileTeleSystems/onetl/pull/316)) +- Fix `log_collection` hiding values of `Kafka.addresses` in logs with `INFO` level. ([#316](https://github.com/MobileTeleSystems/onetl/pull/316)) + +## Dependencies + +- Allow using [etl-entities==2.4.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.4.0). + +## Doc only Changes + +- Fix links to MSSQL date & time type documentation. diff --git a/mddocs/docs/changelog/0.12.3.md b/mddocs/docs/changelog/0.12.3.md new file mode 100644 index 000000000..741c74e1f --- /dev/null +++ b/mddocs/docs/changelog/0.12.3.md @@ -0,0 +1,5 @@ +# 0.12.3 (2024-11-22) + +## Bug Fixes + +- Allow passing table names in format `schema."table.with.dots"` to `DBReader(source=...)` and `DBWriter(target=...)`. diff --git a/mddocs/docs/changelog/0.12.4.md b/mddocs/docs/changelog/0.12.4.md new file mode 100644 index 000000000..3ebc57a87 --- /dev/null +++ b/mddocs/docs/changelog/0.12.4.md @@ -0,0 +1,5 @@ +# 0.12.4 (2024-11-27) + +## Bug Fixes + +- Fix `DBReader(conn=oracle, options={"partitioning_mode": "hash"})` lead to data skew in last partition due to wrong `ora_hash` usage. ([#319](https://github.com/MobileTeleSystems/onetl/pull/319)) diff --git a/mddocs/docs/changelog/0.12.5.md b/mddocs/docs/changelog/0.12.5.md new file mode 100644 index 000000000..c542a50fd --- /dev/null +++ b/mddocs/docs/changelog/0.12.5.md @@ -0,0 +1,13 @@ +# 0.12.5 (2024-12-03) + +## Improvements + +- Use `sipHash64` instead of `md5` in Clickhouse for reading data with `{"partitioning_mode": "hash"}`, as it is 5 times faster. +- Use `hashtext` instead of `md5` in Postgres for reading data with `{"partitioning_mode": "hash"}`, as it is 3-5 times faster. +- Use `BINARY_CHECKSUM` instead of `HASHBYTES` in MSSQL for reading data with `{"partitioning_mode": "hash"}`, as it is 5 times faster. + +## Big fixes + +- In JDBC sources wrap `MOD(partitionColumn, numPartitions)` with `ABS(...)` to make al returned values positive. This prevents data skew. +- Fix reading table data from MSSQL using `{"partitioning_mode": "hash"}` with `partitionColumn` of integer type. +- Fix reading table data from Postgres using `{"partitioning_mode": "hash"}` lead to data skew (all the data was read into one Spark partition). diff --git a/mddocs/docs/changelog/0.13.0.md b/mddocs/docs/changelog/0.13.0.md new file mode 100644 index 000000000..37ae8368a --- /dev/null +++ b/mddocs/docs/changelog/0.13.0.md @@ -0,0 +1,222 @@ +# 0.13.0 (2025-02-24) + +🎉 3 years since first release 0.1.0 🎉 + +## Breaking Changes + +- Add Python 3.13. support. ([#298](https://github.com/MobileTeleSystems/onetl/pull/298)) + +- Change the logic of `FileConnection.walk` and `FileConnection.list_dir`. ([#327](https://github.com/MobileTeleSystems/onetl/pull/327)) + + Previously `limits.stops_at(path) == True` considered as "return current file and stop", and could lead to exceeding some limit. + Not it means "stop immediately". + +- Change default value for `FileDFWriter.Options(if_exists=...)` from `error` to `append`, + to make it consistent with other `.Options()` classes within onETL. ([#343](https://github.com/MobileTeleSystems/onetl/pull/343)) + +## Features + +- Add support for `FileModifiedTimeHWM` HWM class (see [etl-entities 2.5.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.5.0)): + + ```python + from etl_entitites.hwm import FileModifiedTimeHWM + from onetl.file import FileDownloader + from onetl.strategy import IncrementalStrategy + + downloader = FileDownloader( + ..., + hwm=FileModifiedTimeHWM(name="somename"), + ) + + with IncrementalStrategy(): + downloader.run() + ``` + +- Introduce `FileSizeRange(min=..., max=...)` filter class. ([#325](https://github.com/MobileTeleSystems/onetl/pull/325)) + + Now users can set `FileDownloader` / `FileMover` to download/move only files with specific file size range: + + ```python + from onetl.file import FileDownloader + from onetl.file.filter import FileSizeRange + + downloader = FileDownloader( + ..., + filters=[FileSizeRange(min="10KiB", max="1GiB")], + ) + ``` + +- Introduce `TotalFilesSize(...)` limit class. ([#326](https://github.com/MobileTeleSystems/onetl/pull/326)) + + Now users can set `FileDownloader` / `FileMover` to stop downloading/moving files after reaching a certain amount of data: + + ```python + from datetime import datetime, timedelta + from onetl.file import FileDownloader + from onetl.file.limit import TotalFilesSize + + downloader = FileDownloader( + ..., + limits=[TotalFilesSize("1GiB")], + ) + ``` + +- Implement `FileModifiedTime(since=..., until=...)` file filter. ([#330](https://github.com/MobileTeleSystems/onetl/pull/330)) + + Now users can set `FileDownloader` / `FileMover` to download/move only files with specific file modification time: + + ```python + from datetime import datetime, timedelta + from onetl.file import FileDownloader + from onetl.file.filter import FileModifiedTime + + downloader = FileDownloader( + ..., + filters=[FileModifiedTime(before=datetime.now() - timedelta(hours=1))], + ) + ``` + +- Add `SparkS3.get_exclude_packages()` and `Kafka.get_exclude_packages()` methods. ([#341](https://github.com/MobileTeleSystems/onetl/pull/341)) + + Using them allows to skip downloading dependencies not required by this specific connector, or which are already a part of Spark/PySpark: + + ```python + from onetl.connection import SparkS3, Kafka + + maven_packages = [ + *SparkS3.get_packages(spark_version="3.5.4"), + *Kafka.get_packages(spark_version="3.5.4"), + ] + exclude_packages = SparkS3.get_exclude_packages() + Kafka.get_exclude_packages() + spark = ( + SparkSession.builder.appName("spark_app_onetl_demo") + .config("spark.jars.packages", ",".join(maven_packages)) + .config("spark.jars.excludes", ",".join(exclude_packages)) + .getOrCreate() + ) + ``` + +## Improvements + +- All DB connections opened by `JDBC.fetch(...)`, `JDBC.execute(...)` or `JDBC.check()` + are immediately closed after the statements is executed. ([#334](https://github.com/MobileTeleSystems/onetl/pull/334)) + + Previously Spark session with `master=local[3]` actually opened up to 5 connections to target DB - one for `JDBC.check()`, + another for Spark driver interaction with DB to create tables, and one for each Spark executor. Now only max 4 connections are opened, + as `JDBC.check()` does not hold opened connection. + + This is important for RDBMS like Postgres or Greenplum where number of connections is strictly limited and limit is usually quite low. + +- Set up `ApplicationName` (client info) for Clickhouse, MongoDB, MSSQL, MySQL and Oracle. ([#339](https://github.com/MobileTeleSystems/onetl/pull/339), [#248](https://github.com/MobileTeleSystems/onetl/pull/248)) + + Also update `ApplicationName` format for Greenplum, Postgres, Kafka and SparkS3. + Now all connectors have the same `ApplicationName` format: `${spark.applicationId} ${spark.appName} onETL/${onetl.version} Spark/${spark.version}` + + The only connections not sending `ApplicationName` are Teradata and FileConnection implementations. + +- Now `DB.check()` will test connection availability not only on Spark driver, but also from some Spark executor. ([#346](https://github.com/MobileTeleSystems/onetl/pull/346)) + + This allows to fail immediately if Spark driver host has network access to target DB, but Spark executors have not. + +!!! note + + Now ``Greenplum.check()`` requires the same user grants as ``DBReader(connection=greenplum)``: + + ```sql + -- yes, "writable" for reading data from GP, it's not a mistake + ALTER USER username CREATEEXTTABLE(type = 'writable', protocol = 'gpfdist'); + + -- for both reading and writing to GP + -- ALTER USER username CREATEEXTTABLE(type = 'readable', protocol = 'gpfdist') CREATEEXTTABLE(type = 'writable', protocol = 'gpfdist'); + ``` + + Please ask your Greenplum administrators to provide these grants. + + +## Bug Fixes + +- Avoid suppressing Hive Metastore errors while using `DBWriter`. ([#329](https://github.com/MobileTeleSystems/onetl/pull/329)) + + Previously this was implemented as: + + ```python + try: + spark.sql(f"SELECT * FROM {table}") + table_exists = True + except Exception: + table_exists = False + ``` + + If Hive Metastore was overloaded and responded with an exception, it was considered as non-existing table, resulting + to full table override instead of append or override only partitions subset. + +- Fix using onETL to write data to PostgreSQL or Greenplum instances behind *pgbouncer* with `pool_mode=transaction`. ([#336](https://github.com/MobileTeleSystems/onetl/pull/336)) + + Previously `Postgres.check()` opened a read-only transaction, pgbouncer changed the entire connection type from read-write to read-only, + and when `DBWriter.run(df)` executed in read-only connection, producing errors like: + + ``` + org.postgresql.util.PSQLException: ERROR: cannot execute INSERT in a read-only transaction + org.postgresql.util.PSQLException: ERROR: cannot execute TRUNCATE TABLE in a read-only transaction + ``` + + Added a workaround by passing `readOnly=True` to JDBC params for read-only connections, so pgbouncer may differ read-only and read-write connections properly. + + After upgrading onETL 0.13.x or higher the same error still may appear of pgbouncer still holds read-only connections and returns them for DBWriter. + To this this, user can manually convert read-only connection to read-write: + + ```python + postgres.execute("BEGIN READ WRITE;") # <-- add this line + DBWriter(...).run() + ``` + + After all connections in pgbouncer pool were converted from read-only to read-write, and error fixed, this additional line could be removed. + + See [Postgres JDBC driver documentation](https://jdbc.postgresql.org/documentation/use/). + +- Fix `MSSQL.fetch(...)` and `MySQL.fetch(...)` opened a read-write connection instead of read-only. ([#337](https://github.com/MobileTeleSystems/onetl/pull/337)) + + Now this is fixed: + : - `MSSQL.fetch(...)` establishes connection with `ApplicationIntent=ReadOnly`. + - `MySQL.fetch(...)` calls `SET SESSION TRANSACTION READ ONLY` statement. + +- Fixed passing multiple filters to `FileDownloader` and `FileMover`. ([#338](https://github.com/MobileTeleSystems/onetl/pull/338)) + If was caused by sorting filters list in internal logging method, but `FileFilter` subclasses are not sortable. + +- Fix a false warning about a lof of parallel connections to Grenplum. ([#342](https://github.com/MobileTeleSystems/onetl/pull/342)) + + Creating Spark session with `.master("local[5]")` may open up to 6 connections to Greenplum (=number of Spark executors + 1 for driver), + but onETL instead used number of *CPU cores* on the host as a number of parallel connections. + + This lead to showing a false warning that number of Greenplum connections is too high, + which actually should be the case only if number of executors is higher than 30. + +- Fix MongoDB trying to use current database name as `authSource`. ([#347](https://github.com/MobileTeleSystems/onetl/pull/347)) + + Use default connector value which is `admin` database. Previous onETL versions could be fixed by: + + ```python + from onetl.connection import MongoDB + + mongodb = MongoDB( + ..., + database="mydb", + extra={ + "authSource": "admin", + }, + ) + ``` + +## Dependencies + +- Minimal `etl-entities` version is now [2.5.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.5.0). ([#331](https://github.com/MobileTeleSystems/onetl/pull/331)) +- Update DB connectors/drivers to latest versions: ([#345](https://github.com/MobileTeleSystems/onetl/pull/345)) + : - Clickhouse `0.6.5` → `0.7.2` + - MongoDB `10.4.0` → `10.4.1` + - MySQL `9.0.0` → `9.2.0` + - Oracle `23.5.0.24.07` → `23.7.0.25.01` + - Postgres `42.7.4` → `42.7.5` + +## Doc only Changes + +- Split large code examples to tabs. ([#344](https://github.com/MobileTeleSystems/onetl/pull/344)) diff --git a/mddocs/docs/changelog/0.13.1.md b/mddocs/docs/changelog/0.13.1.md new file mode 100644 index 000000000..4045c7cbf --- /dev/null +++ b/mddocs/docs/changelog/0.13.1.md @@ -0,0 +1,9 @@ +# 0.13.1 (2025-03-06) + +## Bug Fixes + +In 0.13.0, using `DBWriter(connection=hive, target="SOMEDB.SOMETABLE")` lead to executing `df.write.saveAsTable()` +instead of `df.write.insertInto()` if target table `somedb.sometable` already exist. + +This is caused by table name normalization (Hive uses lower-case names), which wasn't properly handled by method used for checking table existence. +([#350](https://github.com/MobileTeleSystems/onetl/pull/350)) diff --git a/mddocs/docs/changelog/0.13.3.md b/mddocs/docs/changelog/0.13.3.md new file mode 100644 index 000000000..1aa289b49 --- /dev/null +++ b/mddocs/docs/changelog/0.13.3.md @@ -0,0 +1,5 @@ +# 0.13.3 (2025-03-11) + +## Dependencies + +Allow using [etl-entities 2.6.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.6.0). diff --git a/mddocs/docs/changelog/0.13.4.md b/mddocs/docs/changelog/0.13.4.md new file mode 100644 index 000000000..10f695e0c --- /dev/null +++ b/mddocs/docs/changelog/0.13.4.md @@ -0,0 +1,10 @@ +# 0.13.4 (2025-03-20) + +## Doc only Changes + +- Prefer `ReadOptions(partitionColumn=..., numPartitions=..., queryTimeout=...)` + instead of `ReadOptions(partition_column=..., num_partitions=..., query_timeout=...)`, + to match Spark documentation. ([#352](https://github.com/MobileTeleSystems/onetl/pull/352)) +- Prefer `WriteOptions(if_exists=...)` instead of `WriteOptions(mode=...)` for IDE suggestions. ([#354](https://github.com/MobileTeleSystems/onetl/pull/354)) +- Document all options of supported file formats. + ([#355](https://github.com/MobileTeleSystems/onetl/pull/355), [#356](https://github.com/MobileTeleSystems/onetl/pull/356), [#357](https://github.com/MobileTeleSystems/onetl/pull/357), [#358](https://github.com/MobileTeleSystems/onetl/pull/358), [#359](https://github.com/MobileTeleSystems/onetl/pull/359), [#360](https://github.com/MobileTeleSystems/onetl/pull/360), [#361](https://github.com/MobileTeleSystems/onetl/pull/361), [#362](https://github.com/MobileTeleSystems/onetl/pull/362)) diff --git a/mddocs/docs/changelog/0.7.0.md b/mddocs/docs/changelog/0.7.0.md new file mode 100644 index 000000000..7325faa96 --- /dev/null +++ b/mddocs/docs/changelog/0.7.0.md @@ -0,0 +1,239 @@ +# 0.7.0 (2023-05-15) + +## 🎉 onETL is now open source 🎉 + +That was long road, but we finally did it! + +## Breaking Changes + +- Changed installation method. + + **TL;DR What should I change to restore previous behavior** + + Simple way: + + | onETL < 0.7.0 | onETL >= 0.7.0 | + | ----------------- | --------------------------------- | + | pip install onetl | pip install onetl[files,kerberos] | + + Right way - enumerate connectors should be installed: + + ```bash + pip install onetl[hdfs,ftp,kerberos] # except DB connections + ``` + + **Details** + + In onetl<0.7 the package installation looks like: + + ```bash title="before" + + pip install onetl + ``` + + But this includes all dependencies for all connectors, even if user does not use them. + This caused some issues, for example user had to install Kerberos libraries to be able to install onETL, even if user uses only S3 (without Kerberos support). + + Since 0.7.0 installation process was changed: + + ``` bash title="after" + + pip install onetl # minimal installation, only onETL core + # there is no extras for DB connections because they are using Java packages which are installed in runtime + + pip install onetl[ftp,ftps,hdfs,sftp,s3,webdav] # install dependencies for specified file connections + pip install onetl[files] # install dependencies for all file connections + + pip install onetl[kerberos] # Kerberos auth support + pip install onetl[spark] # install PySpark to use DB connections + + pip install onetl[spark,kerberos,files] # all file connections + Kerberos + PySpark + pip install onetl[all] # alias for previous case + ``` + + There are corresponding documentation items for each extras. + + Also onETL checks that some requirements are missing, and raises exception with recommendation how to install them: + + ``` text title="exception while import Clickhouse connection" + + Cannot import module "pyspark". + + Since onETL v0.7.0 you should install package as follows: + pip install onetl[spark] + + or inject PySpark to sys.path in some other way BEFORE creating MongoDB instance. + ``` + + ``` text title="exception while import FTP connection" + + Cannot import module "ftputil". + + Since onETL v0.7.0 you should install package as follows: + pip install onetl[ftp] + + or + pip install onetl[files] + ``` + +- Added new `cluster` argument to `Hive` and `HDFS` connections. + + `Hive` qualified name (used in HWM) contains cluster name. But in onETL\<0.7.0 cluster name had hard coded value `rnd-dwh` which was not OK for some users. + + `HDFS` connection qualified name contains host (active namenode of Hadoop cluster), but its value can change over time, leading to creating of new HWM. + + Since onETL 0.7.0 both `Hive` and `HDFS` connections have `cluster` attribute which can be set to a specific cluster name. + For `Hive` it is mandatory, for `HDFS` it can be omitted (using host as a fallback). + + But passing cluster name every time could lead to errors. + + Now `Hive` and `HDFS` have nested class named `slots` with methods: + + - `normalize_cluster_name` + - `get_known_clusters` + - `get_current_cluster` + - `normalize_namenode_host` (only `HDFS`) + - `get_cluster_namenodes` (only `HDFS`) + - `get_webhdfs_port` (only `HDFS`) + - `is_namenode_active` (only `HDFS`) + + And new method `HDFS.get_current` / `Hive.get_current`. + + Developers can implement hooks validating user input or substituting values for automatic cluster detection. + This should improve user experience while using these connectors. + + See slots documentation. + +- Update JDBC connection drivers. + + - Greenplum `2.1.3` → `2.1.4`. + - MSSQL `10.2.1.jre8` → `12.2.0.jre8`. Minimal supported version of MSSQL is now 2014 instead 2021. + - MySQL `8.0.30` → `8.0.33`: + - Package was renamed `mysql:mysql-connector-java` → `com.mysql:mysql-connector-j`. + - Driver class was renamed `com.mysql.jdbc.Driver` → `com.mysql.cj.jdbc.Driver`. + - Oracle `21.6.0.0.1` → `23.2.0.0`. + - Postgres `42.4.0` → `42.6.0`. + - Teradata `17.20.00.08` → `17.20.00.15`: + - Package was renamed `com.teradata.jdbc:terajdbc4` → `com.teradata.jdbc:terajdbc`. + - Teradata driver is now published to Maven. + + See [#31](https://github.com/MobileTeleSystems/onetl/pull/31). + +## Features + +- Added MongoDB connection. + + Using official [MongoDB connector for Spark v10](https://www.mongodb.com/docs/spark-connector/current/). Only Spark 3.2+ is supported. + + There are some differences between MongoDB and other database sources: + + - Instead of `mongodb.sql` method there is `mongodb.pipeline`. + - No methods `mongodb.fetch` and `mongodb.execute`. + - `DBReader.hint` and `DBReader.where` have different types than in SQL databases: + + ```python + where = { + "col1": { + "$eq": 10, + }, + } + + hint = { + "col1": 1, + } + ``` + + - Because MongoDB does not have schemas of collections, but Spark cannot create dataframe with dynamic schema, new option `DBReader.df_schema` was introduced. + It is mandatory for MongoDB, but optional for other sources. + - Currently DBReader cannot be used with MongoDB and hwm expression, e.g. `hwm_column=("mycolumn", {"$cast": {"col1": "date"}})` + + Because there are no tables in MongoDB, some options were renamed in core classes: + + - `DBReader(table=...)` → `DBReader(source=...)` + - `DBWriter(table=...)` → `DBWriter(target=...)` + + Old names can be used too, they are not deprecated ([#30](https://github.com/MobileTeleSystems/onetl/pull/30)). + +- Added option for disabling some plugins during import. + + Previously if some plugin were failing during the import, the only way to import onETL would be to disable all plugins + using environment variable. + + Now there are several variables with different behavior: + + - `ONETL_PLUGINS_ENABLED=false` - disable all plugins autoimport. Previously it was named `ONETL_ENABLE_PLUGINS`. + - `ONETL_PLUGINS_BLACKLIST=plugin-name,another-plugin` - set list of plugins which should NOT be imported automatically. + - `ONETL_PLUGINS_WHITELIST=plugin-name,another-plugin` - set list of plugins which should ONLY be imported automatically. + + Also we improved exception message with recommendation how to disable a failing plugin: + + ``` text title="exception message example" + + Error while importing plugin 'mtspark' from package 'mtspark' v4.0.0. + + Statement: + import mtspark.onetl + + Check if plugin is compatible with current onETL version 0.7.0. + + You can disable loading this plugin by setting environment variable: + ONETL_PLUGINS_BLACKLIST='mtspark,failing-plugin' + + You can also define a whitelist of packages which can be loaded by onETL: + ONETL_PLUGINS_WHITELIST='not-failing-plugin1,not-failing-plugin2' + + Please take into account that plugin name may differ from package or module name. + See package metadata for more details + ``` + +## Improvements + +- Added compatibility with Python 3.11 and PySpark 3.4.0. + + File connections were OK, but `jdbc.fetch` and `jdbc.execute` were failing. Fixed in [#28](https://github.com/MobileTeleSystems/onetl/pull/28). + +- Added check for missing Java packages. + + Previously if DB connection tried to use some Java class which were not loaded into Spark version, it raised an exception + with long Java stacktrace. Most users failed to interpret this trace. + + Now onETL shows the following error message: + + ``` text title="exception message example" + + |Spark| Cannot import Java class 'com.mongodb.spark.sql.connector.MongoTableProvider'. + + It looks like you've created Spark session without this option: + SparkSession.builder.config("spark.jars.packages", MongoDB.package_spark_3_2) + + Please call `spark.stop()`, restart the interpreter, + and then create new SparkSession with proper options. + ``` + +- Documentation improvements. + + - Changed documentation site theme - using [furo](https://github.com/pradyunsg/furo) + instead of default [ReadTheDocs](https://github.com/readthedocs/sphinx_rtd_theme). + + New theme supports wide screens and dark mode. + See [#10](https://github.com/MobileTeleSystems/onetl/pull/10). + + - Now each connection class have compatibility table for Spark + Java + Python. + + - Added global compatibility table for Spark + Java + Python + Scala. + +## Bug Fixes + +- Fixed several SFTP issues. + + - If SSH config file `~/.ssh/config` contains some options not recognized by Paramiko (unknown syntax, unknown option name), + previous versions were raising exception until fixing or removing this file. Since 0.7.0 exception is replaced with warning. + + - If user passed `host_key_check=False` but server changed SSH keys, previous versions raised exception until new key is accepted. + Since 0.7.0 exception is replaced with warning if option value is `False`. + + Fixed in [#19](https://github.com/MobileTeleSystems/onetl/pull/19). + +- Fixed several S3 issues. + + There was a bug in S3 connection which prevented handling files in the root of a bucket - they were invisible for the connector. Fixed in [#29](https://github.com/MobileTeleSystems/onetl/pull/29). diff --git a/mddocs/docs/changelog/0.7.1.md b/mddocs/docs/changelog/0.7.1.md new file mode 100644 index 000000000..9eac47a1a --- /dev/null +++ b/mddocs/docs/changelog/0.7.1.md @@ -0,0 +1,40 @@ +# 0.7.1 (2023-05-23) + +## Bug Fixes + +- Fixed `setup_logging` function. + + In onETL==0.7.0 calling `onetl.log.setup_logging()` broke the logging: + + ``` text title="exception message" + + Traceback (most recent call last): + File "/opt/anaconda/envs/py39/lib/python3.9/logging/__init__.py", line 434, in format + return self._format(record) + File "/opt/anaconda/envs/py39/lib/python3.9/logging/__init__.py", line 430, in _format + return self._fmt % record.dict + KeyError: 'levelname:8s' + ``` + +- Fixed installation examples. + + In onETL==0.7.0 there are examples of installing onETL with extras: + + ``` bash title="before" + + pip install onetl[files, kerberos, spark] + ``` + + But pip fails to install such package: + + ``` text title="exception" + + ERROR: Invalid requirement: 'onet[files,' + ``` + + This is because of spaces in extras clause. Fixed: + + ``` bash title="after" + + pip install onetl[files,kerberos,spark] + ``` diff --git a/mddocs/docs/changelog/0.7.2.md b/mddocs/docs/changelog/0.7.2.md new file mode 100644 index 000000000..505b03725 --- /dev/null +++ b/mddocs/docs/changelog/0.7.2.md @@ -0,0 +1,37 @@ +# 0.7.2 (2023-05-24) + +## Dependencies + +- Limited `typing-extensions` version. + + `typing-extensions==4.6.0` release contains some breaking changes causing errors like: + + ``` text title="typing-extensions 4.6.0" + + Traceback (most recent call last): + File "/Users/project/lib/python3.9/typing.py", line 852, in __subclasscheck__ + return issubclass(cls, self.__origin__) + TypeError: issubclass() arg 1 must be a class + ``` + + `typing-extensions==4.6.1` was causing another error: + + ``` text title="typing-extensions 4.6.1" + + Traceback (most recent call last): + File "/home/maxim/Repo/typing_extensions/1.py", line 33, in + isinstance(file, ContainsException) + File "/home/maxim/Repo/typing_extensions/src/typing_extensions.py", line 599, in __instancecheck__ + if super().__instancecheck__(instance): + File "/home/maxim/.pyenv/versions/3.7.8/lib/python3.7/abc.py", line 139, in __instancecheck__ + return _abc_instancecheck(cls, instance) + File "/home/maxim/Repo/typing_extensions/src/typing_extensions.py", line 583, in __subclasscheck__ + return super().__subclasscheck__(other) + File "/home/maxim/.pyenv/versions/3.7.8/lib/python3.7/abc.py", line 143, in __subclasscheck__ + return _abc_subclasscheck(cls, subclass) + File "/home/maxim/Repo/typing_extensions/src/typing_extensions.py", line 661, in _proto_hook + and other._is_protocol + AttributeError: type object 'PathWithFailure' has no attribute '_is_protocol' + ``` + + We updated requirements with `typing-extensions<4.6` until fixing compatibility issues. diff --git a/mddocs/docs/changelog/0.8.0.md b/mddocs/docs/changelog/0.8.0.md new file mode 100644 index 000000000..49d6015f9 --- /dev/null +++ b/mddocs/docs/changelog/0.8.0.md @@ -0,0 +1,166 @@ +# 0.8.0 (2023-05-31) + +## Breaking Changes + +- Rename methods of `FileConnection` classes: + + - `get_directory` → `resolve_dir` + - `get_file` → `resolve_file` + - `listdir` → `list_dir` + - `mkdir` → `create_dir` + - `rmdir` → `remove_dir` + + New naming should be more consistent. + + They were undocumented in previous versions, but someone could use these methods, so this is a breaking change. ([#36](https://github.com/MobileTeleSystems/onetl/pull/36)) + +- Deprecate `onetl.core.FileFilter` class, replace it with new classes: + + - `onetl.file.filter.Glob` + - `onetl.file.filter.Regexp` + - `onetl.file.filter.ExcludeDir` + + Old class will be removed in v1.0.0. ([#43](https://github.com/MobileTeleSystems/onetl/pull/43)) + +- Deprecate `onetl.core.FileLimit` class, replace it with new class `onetl.file.limit.MaxFilesCount`. + + Old class will be removed in v1.0.0. ([#44](https://github.com/MobileTeleSystems/onetl/pull/44)) + +- Change behavior of `BaseFileLimit.reset` method. + + This method should now return `self` instead of `None`. + Return value could be the same limit object or a copy, this is an implementation detail. ([#44](https://github.com/MobileTeleSystems/onetl/pull/44)) + +- Replaced `FileDownloader.filter` and `.limit` with new options `.filters` and `.limits`: + + ``` python title="onETL < 0.8.0" + + FileDownloader( + ..., + filter=FileFilter(glob="*.txt", exclude_dir="/path"), + limit=FileLimit(count_limit=10), + ) + ``` + + ``` python title="onETL >= 0.8.0" + + FileDownloader( + ..., + filters=[Glob("*.txt"), ExcludeDir("/path")], + limits=[MaxFilesCount(10)], + ) + ``` + + This allows to developers to implement their own filter and limit classes, and combine them with existing ones. + + Old behavior still supported, but it will be removed in v1.0.0. ([#45](https://github.com/MobileTeleSystems/onetl/pull/45)) + +- Removed default value for `FileDownloader.limits`, user should pass limits list explicitly. ([#45](https://github.com/MobileTeleSystems/onetl/pull/45)) + +- Move classes from module `onetl.core`: + + ``` python title="before" + + from onetl.core import DBReader + from onetl.core import DBWriter + from onetl.core import FileDownloader + from onetl.core import FileUploader + ``` + + with new modules `onetl.db` and `onetl.file`: + + ``` python title="after" + + from onetl.db import DBReader + from onetl.db import DBWriter + + from onetl.file import FileDownloader + from onetl.file import FileUploader + ``` + + Imports from old module `onetl.core` still can be used, but marked as deprecated. Module will be removed in v1.0.0. ([#46](https://github.com/MobileTeleSystems/onetl/pull/46)) + +## Features + +- Add `rename_dir` method. + + Method was added to following connections: + + - `FTP` + - `FTPS` + - `HDFS` + - `SFTP` + - `WebDAV` + + It allows to rename/move directory to new path with all its content. + + `S3` does not have directories, so there is no such method in that class. ([#40](https://github.com/MobileTeleSystems/onetl/pull/40)) + +- Add `onetl.file.FileMover` class. + + It allows to move files between directories of remote file system. + Signature is almost the same as in `FileDownloader`, but without HWM support. ([#42](https://github.com/MobileTeleSystems/onetl/pull/42)) + +## Improvements + +- Document all public methods in `FileConnection` classes: + + - `download_file` + - `resolve_dir` + - `resolve_file` + - `get_stat` + - `is_dir` + - `is_file` + - `list_dir` + - `create_dir` + - `path_exists` + - `remove_file` + - `rename_file` + - `remove_dir` + - `upload_file` + - `walk` ([#39](https://github.com/MobileTeleSystems/onetl/pull/39)) + +- Update documentation of `check` method of all connections - add usage example and document result type. ([#39](https://github.com/MobileTeleSystems/onetl/pull/39)) + +- Add new exception type `FileSizeMismatchError`. + + Methods `connection.download_file` and `connection.upload_file` now raise new exception type instead of `RuntimeError`, + if target file after download/upload has different size than source. ([#39](https://github.com/MobileTeleSystems/onetl/pull/39)) + +- Add new exception type `DirectoryExistsError` - it is raised if target directory already exists. ([#40](https://github.com/MobileTeleSystems/onetl/pull/40)) + +- Improved `FileDownloader` / `FileUploader` exception logging. + + If `DEBUG` logging is enabled, print exception with stacktrace instead of + printing only exception message. ([#42](https://github.com/MobileTeleSystems/onetl/pull/42)) + +- Updated documentation of `FileUploader`. + + - Class does not support read strategies, added note to documentation. + - Added examples of using `run` method with explicit files list passing, both absolute and relative paths. + - Fix outdated imports and class names in examples. ([#42](https://github.com/MobileTeleSystems/onetl/pull/42)) + +- Updated documentation of `DownloadResult` class - fix outdated imports and class names. ([#42](https://github.com/MobileTeleSystems/onetl/pull/42)) + +- Improved file filters documentation section. + + Document interface class `onetl.base.BaseFileFilter` and function `match_all_filters`. ([#43](https://github.com/MobileTeleSystems/onetl/pull/43)) + +- Improved file limits documentation section. + + Document interface class `onetl.base.BaseFileLimit` and functions `limits_stop_at` / `limits_reached` / `reset_limits`. ([#44](https://github.com/MobileTeleSystems/onetl/pull/44)) + +- Added changelog. + + Changelog is generated from separated news files using [towncrier](https://pypi.org/project/towncrier/). ([#47](https://github.com/MobileTeleSystems/onetl/pull/47)) + +## Misc + +- Improved CI workflow for tests. + + - If developer haven't changed source core of a specific connector or its dependencies, + run tests only against maximum supported versions of Spark, Python, Java and db/file server. + - If developed made some changes in a specific connector, or in core classes, or in dependencies, + run tests for both minimal and maximum versions. + - Once a week run all aganst for minimal and latest versions to detect breaking changes in dependencies + - Minimal tested Spark version is 2.3.1 instead on 2.4.8. ([#32](https://github.com/MobileTeleSystems/onetl/pull/32)) diff --git a/mddocs/docs/changelog/0.8.1.md b/mddocs/docs/changelog/0.8.1.md new file mode 100644 index 000000000..aaf777091 --- /dev/null +++ b/mddocs/docs/changelog/0.8.1.md @@ -0,0 +1,42 @@ +# 0.8.1 (2023-07-10) + +## Features + +- Add `@slot` decorator to public methods of: + + - `DBConnection` + - `FileConnection` + - `DBReader` + - `DBWriter` + - `FileDownloader` + - `FileUploader` + - `FileMover` ([#49](https://github.com/MobileTeleSystems/onetl/pull/49)) + +- Add `workers` field to `FileDownloader` / `FileUploader` / `FileMover`. `Options` classes. + + This allows to speed up all file operations using parallel threads. ([#57](https://github.com/MobileTeleSystems/onetl/pull/57)) + +## Improvements + +- Add documentation for HWM store `.get` and `.save` methods. ([#49](https://github.com/MobileTeleSystems/onetl/pull/49)) + +- Improve Readme: + + - Move `Quick start` section from documentation + - Add `Non-goals` section + - Fix code blocks indentation ([#50](https://github.com/MobileTeleSystems/onetl/pull/50)) + +- Improve Contributing guide: + + - Move `Develop` section from Readme + - Move `docs/changelog/README.rst` content + - Add `Limitations` section + - Add instruction of creating a fork and building documentation ([#50](https://github.com/MobileTeleSystems/onetl/pull/50)) + +- Remove duplicated checks for source file existence in `FileDownloader` / `FileMover`. ([#57](https://github.com/MobileTeleSystems/onetl/pull/57)) + +- Update default logging format to include thread name. ([#57](https://github.com/MobileTeleSystems/onetl/pull/57)) + +## Bug Fixes + +- Fix `S3.list_dir('/')` returns empty list on latest Minio version. ([#58](https://github.com/MobileTeleSystems/onetl/pull/58)) diff --git a/mddocs/docs/changelog/0.9.0.md b/mddocs/docs/changelog/0.9.0.md new file mode 100644 index 000000000..8aee9a3eb --- /dev/null +++ b/mddocs/docs/changelog/0.9.0.md @@ -0,0 +1,122 @@ +# 0.9.0 (2023-08-17) + +## Breaking Changes + +- Rename methods: + + - `DBConnection.read_df` → `DBConnection.read_source_as_df` + - `DBConnection.write_df` → `DBConnection.write_df_to_target` ([#66](https://github.com/MobileTeleSystems/onetl/pull/66)) + +- Rename classes: + + - `HDFS.slots` → `HDFS.Slots` + - `Hive.slots` → `Hive.Slots` + + Old names are left intact, but will be removed in v1.0.0 ([#103](https://github.com/MobileTeleSystems/onetl/pull/103)) + +- Rename options to make them self-explanatory: + + - `Hive.WriteOptions(mode="append")` → `Hive.WriteOptions(if_exists="append")` + - `Hive.WriteOptions(mode="overwrite_table")` → `Hive.WriteOptions(if_exists="replace_entire_table")` + - `Hive.WriteOptions(mode="overwrite_partitions")` → `Hive.WriteOptions(if_exists="replace_overlapping_partitions")` + - `JDBC.WriteOptions(mode="append")` → `JDBC.WriteOptions(if_exists="append")` + - `JDBC.WriteOptions(mode="overwrite")` → `JDBC.WriteOptions(if_exists="replace_entire_table")` + - `Greenplum.WriteOptions(mode="append")` → `Greenplum.WriteOptions(if_exists="append")` + - `Greenplum.WriteOptions(mode="overwrite")` → `Greenplum.WriteOptions(if_exists="replace_entire_table")` + - `MongoDB.WriteOptions(mode="append")` → `Greenplum.WriteOptions(if_exists="append")` + - `MongoDB.WriteOptions(mode="overwrite")` → `Greenplum.WriteOptions(if_exists="replace_entire_collection")` + - `FileDownloader.Options(mode="error")` → `FileDownloader.Options(if_exists="error")` + - `FileDownloader.Options(mode="ignore")` → `FileDownloader.Options(if_exists="ignore")` + - `FileDownloader.Options(mode="overwrite")` → `FileDownloader.Options(if_exists="replace_file")` + - `FileDownloader.Options(mode="delete_all")` → `FileDownloader.Options(if_exists="replace_entire_directory")` + - `FileUploader.Options(mode="error")` → `FileUploader.Options(if_exists="error")` + - `FileUploader.Options(mode="ignore")` → `FileUploader.Options(if_exists="ignore")` + - `FileUploader.Options(mode="overwrite")` → `FileUploader.Options(if_exists="replace_file")` + - `FileUploader.Options(mode="delete_all")` → `FileUploader.Options(if_exists="replace_entire_directory")` + - `FileMover.Options(mode="error")` → `FileMover.Options(if_exists="error")` + - `FileMover.Options(mode="ignore")` → `FileMover.Options(if_exists="ignore")` + - `FileMover.Options(mode="overwrite")` → `FileMover.Options(if_exists="replace_file")` + - `FileMover.Options(mode="delete_all")` → `FileMover.Options(if_exists="replace_entire_directory")` + + Old names are left intact, but will be removed in v1.0.0 ([#108](https://github.com/MobileTeleSystems/onetl/pull/108)) + +- Rename `onetl.log.disable_clients_logging()` to `onetl.log.setup_clients_logging()`. ([#120](https://github.com/MobileTeleSystems/onetl/pull/120)) + +## Features + +- Add new methods returning Maven packages for specific connection class: + + - `Clickhouse.get_packages()` + - `MySQL.get_packages()` + - `Postgres.get_packages()` + - `Teradata.get_packages()` + - `MSSQL.get_packages(java_version="8")` + - `Oracle.get_packages(java_version="8")` + - `Greenplum.get_packages(scala_version="2.12")` + - `MongoDB.get_packages(scala_version="2.12")` + - `Kafka.get_packages(spark_version="3.4.1", scala_version="2.12")` + + Deprecate old syntax: + + - `Clickhouse.package` + - `MySQL.package` + - `Postgres.package` + - `Teradata.package` + - `MSSQL.package` + - `Oracle.package` + - `Greenplum.package_spark_2_3` + - `Greenplum.package_spark_2_4` + - `Greenplum.package_spark_3_2` + - `MongoDB.package_spark_3_2` + - `MongoDB.package_spark_3_3` + - `MongoDB.package_spark_3_4` ([#87](https://github.com/MobileTeleSystems/onetl/pull/87)) + +- Allow to set client modules log level in `onetl.log.setup_clients_logging()`. + + Allow to enable underlying client modules logging in `onetl.log.setup_logging()` by providing additional argument `enable_clients=True`. + This is useful for debug. ([#120](https://github.com/MobileTeleSystems/onetl/pull/120)) + +- Added support for reading and writing data to Kafka topics. + + For these operations, new classes were added. + + - `Kafka` ([#54](https://github.com/MobileTeleSystems/onetl/pull/54), [#60](https://github.com/MobileTeleSystems/onetl/pull/60), [#72](https://github.com/MobileTeleSystems/onetl/pull/72), [#84](https://github.com/MobileTeleSystems/onetl/pull/84), [#87](https://github.com/MobileTeleSystems/onetl/pull/87), [#89](https://github.com/MobileTeleSystems/onetl/pull/89), [#93](https://github.com/MobileTeleSystems/onetl/pull/93), [#96](https://github.com/MobileTeleSystems/onetl/pull/96), [#102](https://github.com/MobileTeleSystems/onetl/pull/102), [#104](https://github.com/MobileTeleSystems/onetl/pull/104)) + - `Kafka.PlaintextProtocol` ([#79](https://github.com/MobileTeleSystems/onetl/pull/79)) + - `Kafka.SSLProtocol` ([#118](https://github.com/MobileTeleSystems/onetl/pull/118)) + - `Kafka.BasicAuth` ([#63](https://github.com/MobileTeleSystems/onetl/pull/63), [#77](https://github.com/MobileTeleSystems/onetl/pull/77)) + - `Kafka.KerberosAuth` ([#63](https://github.com/MobileTeleSystems/onetl/pull/63), [#77](https://github.com/MobileTeleSystems/onetl/pull/77), [#110](https://github.com/MobileTeleSystems/onetl/pull/110)) + - `Kafka.ScramAuth` ([#115](https://github.com/MobileTeleSystems/onetl/pull/115)) + - `Kafka.Slots` ([#109](https://github.com/MobileTeleSystems/onetl/pull/109)) + - `Kafka.ReadOptions` ([#68](https://github.com/MobileTeleSystems/onetl/pull/68)) + - `Kafka.WriteOptions` ([#68](https://github.com/MobileTeleSystems/onetl/pull/68)) + + Currently, Kafka does not support incremental read strategies, this will be implemented in future releases. + +- Added support for reading files as Spark DataFrame and saving DataFrame as Files. + + For these operations, new classes were added. + + FileDFConnections: + + - `SparkHDFS` ([#98](https://github.com/MobileTeleSystems/onetl/pull/98)) + - `SparkS3` ([#94](https://github.com/MobileTeleSystems/onetl/pull/94), [#100](https://github.com/MobileTeleSystems/onetl/pull/100), [#124](https://github.com/MobileTeleSystems/onetl/pull/124)) + - `SparkLocalFS` ([#67](https://github.com/MobileTeleSystems/onetl/pull/67)) + + High-level classes: + + - `FileDFReader` ([#73](https://github.com/MobileTeleSystems/onetl/pull/73)) + - `FileDFWriter` ([#81](https://github.com/MobileTeleSystems/onetl/pull/81)) + + File formats: + + - `Avro` ([#69](https://github.com/MobileTeleSystems/onetl/pull/69)) + - `CSV` ([#92](https://github.com/MobileTeleSystems/onetl/pull/92)) + - `JSON` ([#83](https://github.com/MobileTeleSystems/onetl/pull/83)) + - `JSONLine` ([#83](https://github.com/MobileTeleSystems/onetl/pull/83)) + - `ORC` ([#86](https://github.com/MobileTeleSystems/onetl/pull/86)) + - `Parquet` ([#88](https://github.com/MobileTeleSystems/onetl/pull/88)) + +## Improvements + +- Remove redundant checks for driver availability in Greenplum and MongoDB connections. ([#67](https://github.com/MobileTeleSystems/onetl/pull/67)) +- Check of Java class availability moved from `.check()` method to connection constructor. ([#97](https://github.com/MobileTeleSystems/onetl/pull/97)) diff --git a/mddocs/docs/changelog/0.9.1.md b/mddocs/docs/changelog/0.9.1.md new file mode 100644 index 000000000..1779274b1 --- /dev/null +++ b/mddocs/docs/changelog/0.9.1.md @@ -0,0 +1,7 @@ +# 0.9.1 (2023-08-17) + +## Bug Fixes + +- Fixed bug then number of threads created by `FileDownloader` / `FileUploader` / `FileMover` was + not `min(workers, len(files))`, but `max(workers, len(files))`. leading to create too much workers + on large files list. diff --git a/mddocs/docs/changelog/0.9.2.md b/mddocs/docs/changelog/0.9.2.md new file mode 100644 index 000000000..9c34d16a0 --- /dev/null +++ b/mddocs/docs/changelog/0.9.2.md @@ -0,0 +1,23 @@ +# 0.9.2 (2023-09-06) + +## Features + +- Add `if_exists="ignore"` and `error` to `Greenplum.WriteOptions` ([#142](https://github.com/MobileTeleSystems/onetl/pull/142)) + +## Improvements + +- Improve validation messages while writing dataframe to Kafka. ([#131](https://github.com/MobileTeleSystems/onetl/pull/131)) + +- Improve documentation: + + - Add notes about reading and writing to database connections documentation + - Add notes about executing statements in JDBC and Greenplum connections + +## Bug Fixes + +- Fixed validation of `headers` column is written to Kafka with default `Kafka.WriteOptions()` - default value was `False`, + but instead of raising an exception, column value was just ignored. ([#131](https://github.com/MobileTeleSystems/onetl/pull/131)) +- Fix reading data from Oracle with `partitioningMode="range"` without explicitly set `lowerBound` / `upperBound`. ([#133](https://github.com/MobileTeleSystems/onetl/pull/133)) +- Update Kafka documentation with SSLProtocol usage. ([#136](https://github.com/MobileTeleSystems/onetl/pull/136)) +- Raise exception if someone tries to read data from Kafka topic which does not exist. ([#138](https://github.com/MobileTeleSystems/onetl/pull/138)) +- Allow to pass Kafka topics with name like `some.topic.name` to DBReader. Same for MongoDB collections. ([#139](https://github.com/MobileTeleSystems/onetl/pull/139)) diff --git a/mddocs/docs/changelog/0.9.3.md b/mddocs/docs/changelog/0.9.3.md new file mode 100644 index 000000000..1a8c25d4d --- /dev/null +++ b/mddocs/docs/changelog/0.9.3.md @@ -0,0 +1,5 @@ +# 0.9.3 (2023-09-06) + +## Bug Fixes + +- Fix documentation build diff --git a/mddocs/docs/changelog/0.9.4.md b/mddocs/docs/changelog/0.9.4.md new file mode 100644 index 000000000..a330de366 --- /dev/null +++ b/mddocs/docs/changelog/0.9.4.md @@ -0,0 +1,24 @@ +# 0.9.4 (2023-09-26) + +## Features + +- Add `Excel` file format support. ([#148](https://github.com/MobileTeleSystems/onetl/pull/148)) +- Add `Samba` file connection. + It is now possible to download and upload files to Samba shared folders using `FileDownloader`/`FileUploader`. ([#150](https://github.com/MobileTeleSystems/onetl/pull/150)) +- Add `if_exists="ignore"` and `error` to `Hive.WriteOptions` ([#143](https://github.com/MobileTeleSystems/onetl/pull/143)) +- Add `if_exists="ignore"` and `error` to `JDBC.WriteOptions` ([#144](https://github.com/MobileTeleSystems/onetl/pull/144)) +- Add `if_exists="ignore"` and `error` to `MongoDB.WriteOptions` ([#145](https://github.com/MobileTeleSystems/onetl/pull/145)) + +## Improvements + +- Add documentation about different ways of passing packages to Spark session. ([#151](https://github.com/MobileTeleSystems/onetl/pull/151)) +- Drastically improve `Greenplum` documentation: + : - Added information about network ports, grants, `pg_hba.conf` and so on. + - Added interaction schemas for reading, writing and executing statements in Greenplum. + - Added recommendations about reading data from views and `JOIN` results from Greenplum. ([#154](https://github.com/MobileTeleSystems/onetl/pull/154)) +- Make `.fetch` and `.execute` methods of DB connections thread-safe. Each thread works with its own connection. ([#156](https://github.com/MobileTeleSystems/onetl/pull/156)) +- Call `.close()` on `FileConnection` then it is removed by garbage collector. ([#156](https://github.com/MobileTeleSystems/onetl/pull/156)) + +## Bug Fixes + +- Fix issue when stopping Python interpreter calls `JDBCMixin.close()`, but it is finished with exceptions. ([#156](https://github.com/MobileTeleSystems/onetl/pull/156)) diff --git a/mddocs/docs/changelog/0.9.5.md b/mddocs/docs/changelog/0.9.5.md new file mode 100644 index 000000000..1d7358c0b --- /dev/null +++ b/mddocs/docs/changelog/0.9.5.md @@ -0,0 +1,14 @@ +# 0.9.5 (2023-10-10) + +## Features + +- Add `XML` file format support. ([#163](https://github.com/MobileTeleSystems/onetl/pull/163)) +- Tested compatibility with Spark 3.5.0. `MongoDB` and `Excel` are not supported yet, but other packages do. ([#159](https://github.com/MobileTeleSystems/onetl/pull/159)) + +## Improvements + +- Add check to all DB and FileDF connections that Spark session is alive. ([#164](https://github.com/MobileTeleSystems/onetl/pull/164)) + +## Bug Fixes + +- Fix `Hive.check()` behavior when Hive Metastore is not available. ([#164](https://github.com/MobileTeleSystems/onetl/pull/164)) diff --git a/mddocs/docs/changelog/DRAFT.md b/mddocs/docs/changelog/DRAFT.md new file mode 100644 index 000000000..912b7d7f7 --- /dev/null +++ b/mddocs/docs/changelog/DRAFT.md @@ -0,0 +1,3 @@ +```{eval-rst} +.. towncrier-draft-entries:: |release| [UNRELEASED] +``` diff --git a/mddocs/docs/changelog/NEXT_RELEASE.md b/mddocs/docs/changelog/NEXT_RELEASE.md new file mode 100644 index 000000000..a9831f9d1 --- /dev/null +++ b/mddocs/docs/changelog/NEXT_RELEASE.md @@ -0,0 +1 @@ +% towncrier release notes start diff --git a/mddocs/docs/changelog/index.md b/mddocs/docs/changelog/index.md new file mode 100644 index 000000000..04704744d --- /dev/null +++ b/mddocs/docs/changelog/index.md @@ -0,0 +1,29 @@ +# Changelog + +- [0.13.4](../changelog/0.13.4) +- [0.13.3](../changelog/0.13.3) +- [0.13.1](../changelog/0.13.1) +- [0.13.0](../changelog/0.13.0) +- [0.12.5](../changelog/0.12.5) +- [0.12.4](../changelog/0.12.4) +- [0.12.3](../changelog/0.12.3) +- [0.12.2](../changelog/0.12.2) +- [0.12.1](../changelog/0.12.1) +- [0.12.0](../changelog/0.12.0) +- [0.11.2](../changelog/0.11.2) +- [0.11.1](../changelog/0.11.1) +- [0.11.0](../changelog/0.11.0) +- [0.10.2](../changelog/0.10.2) +- [0.10.1](../changelog/0.10.1) +- [0.10.0](../changelog/0.10.0) +- [0.9.5](../changelog/0.9.5) +- [0.9.4](../changelog/0.9.4) +- [0.9.3](../changelog/0.9.3) +- [0.9.2](../changelog/0.9.2) +- [0.9.1](../changelog/0.9.1) +- [0.9.0](../changelog/0.9.0) +- [0.8.1](../changelog/0.8.1) +- [0.8.0](../changelog/0.8.0) +- [0.7.2](../changelog/0.7.2) +- [0.7.1](../changelog/0.7.1) +- [0.7.0](../changelog/0.7.0) diff --git a/mddocs/docs/changelog/next_release/.keep b/mddocs/docs/changelog/next_release/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/mddocs/docs/concepts.md b/mddocs/docs/concepts.md new file mode 100644 index 000000000..b8b0baf4a --- /dev/null +++ b/mddocs/docs/concepts.md @@ -0,0 +1,458 @@ +# Concepts + +Here you can find detailed documentation about each one of the onETL concepts and how to use them. + +## Connection + +### Basics + +onETL is used to pull and push data into other systems, and so it has a first-class `Connection` concept for storing credentials that are used to communicate with external systems. + +A `Connection` is essentially a set of parameters, such as username, password, hostname. + +To create a connection to a specific storage type, you must use a class that matches the storage type. The class name is the same as the storage type name (`Oracle`, `MSSQL`, `SFTP`, etc): + +```python +from onetl.connection import SFTP + +sftp = SFTP( + host="sftp.test.com", + user="onetl", + password="onetl", +) +``` + +All connection types are inherited from the parent class `BaseConnection`. + +### Class diagram + +```{eval-rst} +.. plantuml:: + + @startuml + left to right direction + skinparam classFontSize 20 + skinparam class { + BackgroundColor<> LightGreen + BackgroundColor<> Khaki + BackgroundColor<> LightBlue + StereotypeFontColor<> Transparent + StereotypeFontColor<> Transparent + StereotypeFontColor<> Transparent + } + + class BaseConnection { + } + + class DBConnection <>{ + } + DBConnection --|> BaseConnection + + class Hive <>{ + } + Hive --|> DBConnection + + class Greenplum <>{ + } + Greenplum --|> DBConnection + + class MongoDB <>{ + } + MongoDB --|> DBConnection + + class Kafka <>{ + } + Kafka --|> DBConnection + + class JDBCConnection <>{ + } + JDBCConnection --|> DBConnection + + class Clickhouse <>{ + } + Clickhouse --|> JDBCConnection + + class MSSQL <>{ + } + MSSQL --|> JDBCConnection + + class MySQL <>{ + } + MySQL --|> JDBCConnection + + class Postgres <>{ + } + Postgres --|> JDBCConnection + + class Oracle <>{ + } + Oracle --|> JDBCConnection + + class Teradata <>{ + } + Teradata --|> JDBCConnection + + class FileConnection <>{ + } + FileConnection --|> BaseConnection + + class FTP <>{ + } + FTP --|> FileConnection + + class FTPS <>{ + } + FTPS --|> FileConnection + + class HDFS <>{ + } + HDFS --|> FileConnection + + class WebDAV <>{ + } + WebDAV --|> FileConnection + + class Samba <>{ + } + Samba --|> FileConnection + + class SFTP <>{ + } + SFTP --|> FileConnection + + class S3 <>{ + } + S3 --|> FileConnection + + class FileDFConnection <>{ + } + FileDFConnection --|> BaseConnection + + class SparkHDFS <>{ + } + SparkHDFS --|> FileDFConnection + + class SparkLocalFS <>{ + } + SparkLocalFS --|> FileDFConnection + + class SparkS3 <>{ + } + SparkS3 --|> FileDFConnection + + @enduml +``` + +### DBConnection + +Classes inherited from `DBConnection` could be used for accessing databases. + +A `DBConnection` could be instantiated as follows: + +```python +from onetl.connection import MSSQL + +mssql = MSSQL( + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, +) +``` + +where **spark** is the current SparkSession. +`onETL` uses `Spark` and specific Java connectors under the hood to work with databases. + +For a description of other parameters, see the documentation for the {ref}`available DBConnections `. + +### FileConnection + +Classes inherited from `FileConnection` could be used to access files stored on the different file systems/file servers + +A `FileConnection` could be instantiated as follows: + +```python +from onetl.connection import SFTP + +sftp = SFTP( + host="sftp.test.com", + user="onetl", + password="onetl", +) +``` + +For a description of other parameters, see the documentation for the {ref}`available FileConnections `. + +### FileDFConnection + +Classes inherited from `FileDFConnection` could be used for accessing files as Spark DataFrames. + +A `FileDFConnection` could be instantiated as follows: + +```python +from onetl.connection import SparkHDFS + +spark_hdfs = SparkHDFS( + host="namenode1.domain.com", + cluster="mycluster", + spark=spark, +) +``` + +where **spark** is the current SparkSession. +`onETL` uses `Spark` and specific Java connectors under the hood to work with DataFrames. + +For a description of other parameters, see the documentation for the {ref}`available FileDFConnections `. + +### Checking connection availability + +Once you have created a connection, you can check the database/filesystem availability using the method `check()`: + +```python +mssql.check() +sftp.check() +spark_hdfs.check() +``` + +It will raise an exception if database/filesystem cannot be accessed. + +This method returns connection itself, so you can create connection and immediately check its availability: + +```Python +mssql = MSSQL( + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, +).check() # <-- +``` + +## Extract/Load data + +### Basics + +As we said above, onETL is used to extract data from and load data into remote systems. + +onETL provides several classes for this: + +> - {ref}`DBReader ` +> - {ref}`DBWriter ` +> - {ref}`FileDFReader ` +> - {ref}`FileDFWriter ` +> - {ref}`FileDownloader ` +> - {ref}`FileUploader ` +> - {ref}`FileMover ` + +All of these classes have a method `run()` that starts extracting/loading the data: + +```python +from onetl.db import DBReader, DBWriter + +reader = DBReader( + connection=mssql, + source="dbo.demo_table", + columns=["column_1", "column_2"], +) + +# Read data as Spark DataFrame +df = reader.run() + +db_writer = DBWriter( + connection=hive, + target="dl_sb.demo_table", +) + +# Save Spark DataFrame to Hive table +writer.run(df) +``` + +### Extract data + +To extract data you can use classes: + +| | Use case | Connection | `run()` gets | `run()` returns | +| ------------------------------------ | ----------------------------------------- | ------------------------------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| {ref}`DBReader ` | Reading data from a database | Any {ref}`DBConnection ` | - | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | +| {ref}`FileDFReader ` | Read data from a file or set of files | Any {ref}`FileDFConnection ` | No input, or List[File path on FileSystem] | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | +| {ref}`FileDownloader ` | Download files from remote FS to local FS | Any {ref}`FileConnection ` | No input, or List[File path on remote FileSystem] | {ref}`DownloadResult ` | + +### Load data + +To load data you can use classes: + +| | Use case | Connection | `run()` gets | `run()` returns | +| ----------------------------------- | -------------------------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | +| {ref}`DBWriter ` | Writing data from a DataFrame to a database | Any {ref}`DBConnection ` | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | None | +| {ref}`FileDFWriter ` | Writing data from a DataFrame to a folder | Any {ref}`FileDFConnection ` | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | None | +| {ref}`FileUploader ` | Uploading files from a local FS to remote FS | Any {ref}`FileConnection ` | List[File path on local FileSystem] | {ref}`UploadResult ` | + +### Manipulate data + +To manipulate data you can use classes: + +| | Use case | Connection | `run()` gets | `run()` returns | +| ----------------------------- | ------------------------------------------- | -------------------------------------------- | ------------------------------------ | ------------------------------------- | +| {ref}`FileMover ` | Move files between directories in remote FS | Any {ref}`FileConnection ` | List[File path on remote FileSystem] | {ref}`MoveResult ` | + +### Options + +Extract and load classes have a `options` parameter, which has a special meaning: + +> - all other parameters - *WHAT* we extract / *WHERE* we load to +> - `options` parameter - *HOW* we extract/load data + +```python +db_reader = DBReader( + # WHAT do we read: + connection=mssql, + source="dbo.demo_table", # some table from MSSQL + columns=["column_1", "column_2"], # but only specific set of columns + where="column_2 > 1000", # only rows matching the clause + # HOW do we read: + options=MSSQL.ReadOptions( + numPartitions=10, # read in 10 parallel jobs + partitionColumn="id", # balance data read by assigning each job a part of data using `hash(id) mod N` expression + partitioningMode="hash", + fetchsize=1000, # each job will fetch block of 1000 rows each on every read attempt + ), +) + +db_writer = DBWriter( + # WHERE do we write to - to some table in Hive + connection=hive, + target="dl_sb.demo_table", + # HOW do we write - overwrite all the data in the existing table + options=Hive.WriteOptions(if_exists="replace_entire_table"), +) + +file_downloader = FileDownloader( + # WHAT do we download - files from some dir in SFTP + connection=sftp, + source_path="/source", + filters=[Glob("*.csv")], # only CSV files + limits=[MaxFilesCount(1000)], # 1000 files max + # WHERE do we download to - a specific dir on local FS + local_path="/some", + # HOW do we download: + options=FileDownloader.Options( + delete_source=True, # after downloading each file remove it from source_path + if_exists="replace_file", # replace existing files in the local_path + ), +) + +file_uploader = FileUploader( + # WHAT do we upload - files from some local dir + local_path="/source", + # WHERE do we upload to- specific remote dir in HDFS + connection=hdfs, + target_path="/some", + # HOW do we upload: + options=FileUploader.Options( + delete_local=True, # after uploading each file remove it from local_path + if_exists="replace_file", # replace existing files in the target_path + ), +) + +file_mover = FileMover( + # WHAT do we move - files in some remote dir in HDFS + source_path="/source", + connection=hdfs, + # WHERE do we move files to + target_path="/some", # a specific remote dir within the same HDFS connection + # HOW do we load - replace existing files in the target_path + options=FileMover.Options(if_exists="replace_file"), +) + +file_df_reader = FileDFReader( + # WHAT do we read - *.csv files from some dir in S3 + connection=s3, + source_path="/source", + file_format=CSV(), + # HOW do we read - load files from /source/*.csv, not from /source/nested/*.csv + options=FileDFReader.Options(recursive=False), +) + +file_df_writer = FileDFWriter( + # WHERE do we write to - as .csv files in some dir in S3 + connection=s3, + target_path="/target", + file_format=CSV(), + # HOW do we write - replace all existing files in /target, if exists + options=FileDFWriter.Options(if_exists="replace_entire_directory"), +) +``` + +More information about `options` could be found on {ref}`DB connection `. and +{ref}`file-downloader` / {ref}`file-uploader` / {ref}`file-mover` / {ref}`file-df-reader` / {ref}`file-df-writer` documentation + +### Read Strategies + +onETL have several builtin strategies for reading data: + +1. [Snapshot strategy](strategy/snapshot_strategy.html) (default strategy) +2. [Incremental strategy](strategy/incremental_strategy.html) +3. [Snapshot batch strategy](strategy/snapshot_batch_strategy.html) +4. [Incremental batch strategy](strategy/incremental_batch_strategy.html) + +For example, an incremental strategy allows you to get only new data from the table: + +```python +from onetl.strategy import IncrementalStrategy + +reader = DBReader( + connection=mssql, + source="dbo.demo_table", + hwm_column="id", # detect new data based on value of "id" column +) + +# first run +with IncrementalStrategy(): + df = reader.run() + +sleep(3600) + +# second run +with IncrementalStrategy(): + # only rows, that appeared in the source since previous run + df = reader.run() +``` + +or get only files which were not downloaded before: + +```python +from onetl.strategy import IncrementalStrategy + +file_downloader = FileDownloader( + connection=sftp, + source_path="/remote", + local_path="/local", + hwm_type="file_list", # save all downloaded files to a list, and exclude files already present in this list +) + +# first run +with IncrementalStrategy(): + files = file_downloader.run() + +sleep(3600) + +# second run +with IncrementalStrategy(): + # only files, that appeared in the source since previous run + files = file_downloader.run() +``` + +Most of strategies are based on {ref}`hwm`, Please check each strategy documentation for more details + +### Why just not use Connection class for extract/load? + +Connections are very simple, they have only a set of some basic operations, +like `mkdir`, `remove_file`, `get_table_schema`, and so on. + +High-level operations, like +: - {ref}`strategy` support + - Handling metadata push/pull + - Handling different options, like `if_exists="replace_file"` in case of file download/upload + +is moved to a separate class which calls the connection object methods to perform some complex logic. diff --git a/mddocs/docs/conf.py b/mddocs/docs/conf.py new file mode 100644 index 000000000..018e6e870 --- /dev/null +++ b/mddocs/docs/conf.py @@ -0,0 +1,169 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. + + +import os +import subprocess +import sys +from pathlib import Path + +from packaging.version import Version + +PROJECT_ROOT_DIR = Path(__file__).parent.parent.resolve() + +sys.path.insert(0, os.fspath(PROJECT_ROOT_DIR)) + +# -- Project information ----------------------------------------------------- + +project = "onETL" +copyright = "2021-2025 MTS PJSC" +author = "MWS Data Bridge" + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. + +ver = Version(subprocess.check_output("python ../setup.py --version", shell=True, text=True).strip()) +version = ver.base_version +# The full version, including alpha/beta/rc tags. +release = ver.public + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "numpydoc", + "sphinx_design", + "sphinx_substitution_extensions", + "sphinx_tabs.tabs", + "sphinx_toolbox.more_autodoc.autoprotocol", + "sphinx_toolbox.github", + "sphinx_copybutton", + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinxcontrib.towncrier", # provides `towncrier-draft-entries` directive + "sphinxcontrib.plantuml", + "sphinx.ext.extlinks", + "sphinx_favicon", + "sphinxcontrib.autodoc_pydantic", + "sphinx_last_updated_by_git", + "myst_parser", +] + + +myst_enable_extensions = [ + "amsmath", + "attrs_inline", + "colon_fence", + "deflist", + "dollarmath", + "fieldlist", + "html_admonition", + "html_image", + "linkify", + "replacements", + "smartquotes", + "strikethrough", + "substitution", + "tasklist", +] + + +numpydoc_show_class_members = False +autodoc_pydantic_model_show_config = False +autodoc_pydantic_model_show_config_summary = False +autodoc_pydantic_model_show_config_member = False +autodoc_pydantic_model_show_json = False +autodoc_pydantic_model_show_validator_summary = False +autodoc_pydantic_model_show_validator_members = False +autodoc_pydantic_field_list_validators = False +sphinx_tabs_disable_tab_closing = True + +# prevent >>>, ... and doctest outputs from copying +copybutton_prompt_text = r">>> |\.\.\. |\$ |In \[\d*\]: | {2,5}\.\.\.: | {5,8}: " +copybutton_prompt_is_regexp = True +copybutton_copy_empty_lines = False +copybutton_only_copy_prompt_lines = True + +towncrier_draft_autoversion_mode = "draft" +towncrier_draft_include_empty = False +towncrier_draft_working_directory = PROJECT_ROOT_DIR + +github_username = "MobileTeleSystems" +github_repository = "onetl" + +rst_prolog = f""" +.. |support_hooks| image:: https://img.shields.io/badge/%20-support%20hooks-blue + :target: https://onetl.readthedocs.io/en/{ver}/hooks/index.html +""" + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. + +html_theme = "furo" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] +html_extra_path = ["robots.txt"] +html_logo = "./_static/logo.svg" +favicons = [ + {"rel": "icon", "href": "icon.svg", "type": "image/svg+xml"}, +] + +# The master toctree document. +master_doc = "index" + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = "en" + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" + +# Create an alias for etl-entities lib in onetl documentation +extlinks = { + "etl-entities": ("https://etl-entities.readthedocs.io/en/stable/%s", None), +} + + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = "onetl-doc" diff --git a/mddocs/docs/connection/db_connection/clickhouse/connection.md b/mddocs/docs/connection/db_connection/clickhouse/connection.md new file mode 100644 index 000000000..06fa5a462 --- /dev/null +++ b/mddocs/docs/connection/db_connection/clickhouse/connection.md @@ -0,0 +1,12 @@ +(clickhouse-connection)= + +# Clickhouse connection + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.clickhouse.connection +``` + +```{eval-rst} +.. autoclass:: Clickhouse + :members: get_packages, check +``` diff --git a/mddocs/docs/connection/db_connection/clickhouse/execute.md b/mddocs/docs/connection/db_connection/clickhouse/execute.md new file mode 100644 index 000000000..501fed530 --- /dev/null +++ b/mddocs/docs/connection/db_connection/clickhouse/execute.md @@ -0,0 +1,125 @@ +(clickhouse-execute)= + +# Executing statements in Clickhouse + +```{eval-rst} +.. warning:: + + Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + + Do **NOT** use them to read large amounts of data. Use :ref:`DBReader ` or :ref:`Clickhouse.sql ` instead. +``` + +## How to + +There are 2 ways to execute some statement in Clickhouse + +### Use `Clickhouse.fetch` + +Use this method to perform some `SELECT` query which returns **small number or rows**, like reading +Clickhouse config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts {obj}`Clickhouse.FetchOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`clickhouse-types`. +``` + +#### Syntax support + +This method supports **any** query syntax supported by Clickhouse, like: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ✅︎ `SELECT func(arg1, arg2)` - call function +- ✅︎ `SHOW ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Clickhouse + +clickhouse = Clickhouse(...) + +df = clickhouse.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=Clickhouse.FetchOptions(queryTimeout=10), +) +clickhouse.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `Clickhouse.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts {obj}`Clickhouse.ExecuteOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Clickhouse, like: + +- ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +- ✅︎ `ALTER ...` +- ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +- ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +- ✅︎ other statements not mentioned here +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Clickhouse + +clickhouse = Clickhouse(...) + +clickhouse.execute("DROP TABLE schema.table") +clickhouse.execute( + """ + CREATE TABLE schema.table ( + id UInt8, + key String, + value Float32 + ) + ENGINE = MergeTree() + ORDER BY id + """, + options=Clickhouse.ExecuteOptions(queryTimeout=10), +) +``` + +## Notes + +These methods **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + +So it should **NOT** be used to read large amounts of data. Use {ref}`DBReader ` or {ref}`Clickhouse.sql ` instead. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.clickhouse.options +``` + +```{eval-rst} +.. autopydantic_model:: ClickhouseFetchOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false + +``` + +```{eval-rst} +.. autopydantic_model:: ClickhouseExecuteOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/clickhouse/index.md b/mddocs/docs/connection/db_connection/clickhouse/index.md new file mode 100644 index 000000000..28c20dec1 --- /dev/null +++ b/mddocs/docs/connection/db_connection/clickhouse/index.md @@ -0,0 +1,28 @@ +(clickhouse)= + +# Clickhouse + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +connection +``` + +```{toctree} +:caption: Operations +:maxdepth: 1 + +read +sql +write +execute +``` + +```{toctree} +:caption: Troubleshooting +:maxdepth: 1 + +types +``` diff --git a/mddocs/docs/connection/db_connection/clickhouse/prerequisites.md b/mddocs/docs/connection/db_connection/clickhouse/prerequisites.md new file mode 100644 index 000000000..563dbad3f --- /dev/null +++ b/mddocs/docs/connection/db_connection/clickhouse/prerequisites.md @@ -0,0 +1,73 @@ +(clickhouse-prerequisites)= + +# Prerequisites + +## Version Compatibility + +- Clickhouse server versions: + : - Officially declared: 22.8 or higher + - Actually tested: 21.1, 25.1 +- Spark versions: 2.3.x - 3.5.x +- Java versions: 8 - 20 + +See [official documentation](https://clickhouse.com/docs/en/integrations/java#jdbc-driver). + +## Installing PySpark + +To use Clickhouse connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Connecting to Clickhouse + +### Connection port + +Connector can only use **HTTP** (usually `8123` port) or **HTTPS** (usually `8443` port) protocol. + +TCP and GRPC protocols are NOT supported. + +### Connecting to cluster + +It is possible to connect to Clickhouse cluster, and use it's load balancing capabilities to read or write data in parallel. +Each Spark executor can connect to random Clickhouse nodes, instead of sending all the data to a node specified in connection params. + +This requires all Clickhouse servers to run on different hosts, and **listen the same HTTP port**. +Set `auto_discovery=True` to enable this feature (disabled by default): + +```python +Clickhouse( + host="node1.of.cluster", + port=8123, + extra={ + "auto_discovery": True, + "load_balancing_policy": "roundRobin", + }, +) +``` + +See [official documentation](https://clickhouse.com/docs/en/integrations/java#configuring-node-discovery-load-balancing-and-failover). + +### Required grants + +Ask your Clickhouse cluster administrator to set following grants for a user, +used for creating a connection: + +```{eval-rst} +.. tabs:: + + .. code-tab:: sql Read + Write + + -- allow creating tables in the target schema + GRANT CREATE TABLE ON myschema.* TO username; + + -- allow read & write access to specific table + GRANT SELECT, INSERT ON myschema.mytable TO username; + + .. code-tab:: sql Read only + + -- allow read access to specific table + GRANT SELECT ON myschema.mytable TO username; +``` + +More details can be found in [official documentation](https://clickhouse.com/docs/en/sql-reference/statements/grant). diff --git a/mddocs/docs/connection/db_connection/clickhouse/read.md b/mddocs/docs/connection/db_connection/clickhouse/read.md new file mode 100644 index 000000000..2c62132ed --- /dev/null +++ b/mddocs/docs/connection/db_connection/clickhouse/read.md @@ -0,0 +1,93 @@ +(clickhouse-read)= + +# Reading from Clickhouse using `DBReader` + +{obj}`DBReader ` supports {ref}`strategy` for incremental data reading, +but does not support custom queries, like `JOIN`. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`clickhouse-types` +``` + +## Supported DBReader features + +- ✅︎ `columns` +- ✅︎ `where` +- ✅︎ `hwm`, supported strategies: +- - ✅︎ {ref}`snapshot-strategy` +- - ✅︎ {ref}`incremental-strategy` +- - ✅︎ {ref}`snapshot-batch-strategy` +- - ✅︎ {ref}`incremental-batch-strategy` +- ❌ `hint` (is not supported by Clickhouse) +- ❌ `df_schema` +- ✅︎ `options` (see {obj}`Clickhouse.ReadOptions `) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Clickhouse +from onetl.db import DBReader + +clickhouse = Clickhouse(...) + +reader = DBReader( + connection=clickhouse, + source="schema.table", + columns=["id", "key", "CAST(value AS String) value", "updated_dt"], + where="key = 'something'", + options=Clickhouse.ReadOptions(partitionColumn="id", numPartitions=10), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Clickhouse +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +clickhouse = Clickhouse(...) + +reader = DBReader( + connection=clickhouse, + source="schema.table", + columns=["id", "key", "CAST(value AS String) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="clickhouse_hwm", expression="updated_dt"), + options=Clickhouse.ReadOptions(partitionColumn="id", numPartitions=10), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Clickhouse to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Clickhouse to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.clickhouse.options +``` + +```{eval-rst} +.. autopydantic_model:: ClickhouseReadOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/clickhouse/sql.md b/mddocs/docs/connection/db_connection/clickhouse/sql.md new file mode 100644 index 000000000..bbe62adaa --- /dev/null +++ b/mddocs/docs/connection/db_connection/clickhouse/sql.md @@ -0,0 +1,80 @@ +(clickhouse-sql)= + +# Reading from Clickhouse using `Clickhouse.sql` + +`Clickhouse.sql` allows passing custom SQL query, but does not support incremental strategies. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`clickhouse-types` +``` + +```{eval-rst} +.. warning:: + + Statement is executed in **read-write** connection, so if you're calling some functions/procedures with DDL/DML statements inside, + they can change data in your database. +``` + +## Syntax support + +Only queries with the following syntax are supported: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import Clickhouse + +clickhouse = Clickhouse(...) +df = clickhouse.sql( + """ + SELECT + id, + key, + CAST(value AS String) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """, + options=Clickhouse.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from Clickhouse to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from Clickhouse to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.clickhouse.options +``` + +```{eval-rst} +.. autopydantic_model:: ClickhouseSQLOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/clickhouse/types.md b/mddocs/docs/connection/db_connection/clickhouse/types.md new file mode 100644 index 000000000..f288bfb21 --- /dev/null +++ b/mddocs/docs/connection/db_connection/clickhouse/types.md @@ -0,0 +1,457 @@ +(clickhouse-types)= + +# Clickhouse \<-> Spark type mapping + +```{eval-rst} +.. note:: + + The results below are valid for Spark 3.5.5, and may differ on other Spark versions. +``` + +```{eval-rst} +.. note:: + + It is recommended to use `spark-dialect-extension `_ package, + which implements writing Arrays from Spark to Clickhouse, fixes dropping fractions of seconds in ``TimestampType``, + and fixes other type conversion issues. +``` + +## Type detection & casting + +Spark's DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from Clickhouse + +This is how Clickhouse connector performs this: + +- For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and Clickhouse type. +- Find corresponding `Clickhouse type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- Create DataFrame from query with specific column names and Spark types. + +### Writing to some existing Clickhouse table + +This is how Clickhouse connector performs this: + +- Get names of columns in DataFrame. [^footnote-1] +- Perform `SELECT * FROM table LIMIT 0` query. +- Take only columns present in DataFrame (by name, case insensitive). For each found column get Clickhouse type. +- **Find corresponding** `Clickhouse type (read)` → `Spark type` **combination** (see below) for each DataFrame column. If no combination is found, raise exception. [^footnote-2] +- Find corresponding `Spark type` → `Clickhousetype (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- If `Clickhousetype (write)` match `Clickhouse type (read)`, no additional casts will be performed, DataFrame column will be written to Clickhouse as is. +- If `Clickhousetype (write)` does not match `Clickhouse type (read)`, DataFrame column will be casted to target column type **on Clickhouse side**. For example, you can write column with text data to `Int32` column, if column contains valid integer values within supported value range and precision. + +[^footnote-1]: This allows to write data to tables with `DEFAULT` columns - if DataFrame has no such column, + it will be populated by Clickhouse. + +[^footnote-2]: Yes, this is weird. + +### Create new table using Spark + +```{eval-rst} +.. warning:: + + ABSOLUTELY NOT RECOMMENDED! +``` + +This is how Clickhouse connector performs this: + +- Find corresponding `Spark type` → `Clickhouse type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- Generate DDL for creating table in Clickhouse, like `CREATE TABLE (col1 ...)`, and run it. +- Write DataFrame to created table as is. + +But Spark does not have specific dialect for Clickhouse, so Generic JDBC dialect is used. +Generic dialect is using SQL ANSI type names while creating tables in target database, not database-specific types. + +If some cases this may lead to using wrong column type. For example, Spark creates column of type `TIMESTAMP` +which corresponds to Clickhouse type `DateTime32` (precision up to seconds) +instead of more precise `DateTime64` (precision up to nanoseconds). +This may lead to incidental precision loss, or sometimes data cannot be written to created table at all. + +So instead of relying on Spark to create tables: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + writer = DBWriter( + connection=clickhouse, + target="default.target_tbl", + options=Clickhouse.WriteOptions( + if_exists="append", + # ENGINE is required by Clickhouse + createTableOptions="ENGINE = MergeTree() ORDER BY id", + ), + ) + writer.run(df) +``` + +Always prefer creating tables with specific types **BEFORE WRITING DATA**: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + clickhouse.execute( + """ + CREATE TABLE default.target_tbl ( + id UInt8, + value DateTime64(6) -- specific type and precision + ) + ENGINE = MergeTree() + ORDER BY id + """, + ) + + writer = DBWriter( + connection=clickhouse, + target="default.target_tbl", + options=Clickhouse.WriteOptions(if_exists="append"), + ) + writer.run(df) +``` + +### References + +Here you can find source code with type conversions: + +- [Clickhouse -> JDBC](https://github.com/ClickHouse/clickhouse-java/blob/0.3.2/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/JdbcTypeMapping.java#L39-L176) +- [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/jdbc/JdbcUtils.scala#L307) +- [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/jdbc/JdbcUtils.scala#L141-L164) +- [JDBC -> Clickhouse](https://github.com/ClickHouse/clickhouse-java/blob/0.3.2/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/JdbcTypeMapping.java#L185-L311) + +## Supported types + +See [official documentation](https://clickhouse.com/docs/en/sql-reference/data-types) + +### Generic types + +- `LowCardinality(T)` is same as `T` +- `Nullable(T)` is same as `T`, but Spark column is inferred as `nullable=True` + +### Numeric types + +```{eval-rst} ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| Clickhouse type (read) | Spark type | Clickhouse type (write) | Clickhouse type (create) | ++================================+===================================+===============================+===============================+ +| ``Bool`` | ``BooleanType()`` | ``Bool`` | ``UInt64`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Decimal`` | ``DecimalType(P=10, S=0)`` | ``Decimal(P=10, S=0)`` | ``Decimal(P=10, S=0)`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Decimal(P=0..38)`` | ``DecimalType(P=0..38, S=0)`` | ``Decimal(P=0..38, S=0)`` | ``Decimal(P=0..38, S=0)`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Decimal(P=0..38, S=0..38)`` | ``DecimalType(P=0..38, S=0..38)`` | ``Decimal(P=0..38, S=0..38)`` | ``Decimal(P=0..38, S=0..38)`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Decimal(P=39..76, S=0..76)`` | unsupported [3]_ | | | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Decimal32(P=0..9)`` | ``DecimalType(P=9, S=0..9)`` | ``Decimal(P=9, S=0..9)`` | ``Decimal(P=9, S=0..9)`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Decimal64(S=0..18)`` | ``DecimalType(P=18, S=0..18)`` | ``Decimal(P=18, S=0..18)`` | ``Decimal(P=18, S=0..18)`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Decimal128(S=0..38)`` | ``DecimalType(P=38, S=0..38)`` | ``Decimal(P=38, S=0..38)`` | ``Decimal(P=38, S=0..38)`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Decimal256(S=0..76)`` | unsupported [3]_ | | | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Float32`` | ``FloatType()`` | ``Float32`` | ``Float32`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Float64`` | ``DoubleType()`` | ``Float64`` | ``Float64`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Int8`` | ``IntegerType()`` | ``Int32`` | ``Int32`` | ++--------------------------------+ | | | +| ``Int16`` | | | | ++--------------------------------+ | | | +| ``Int32`` | | | | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Int64`` | ``LongType()`` | ``Int64`` | ``Int64`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Int128`` | unsupported [3]_ | | | ++--------------------------------+ | | | +| ``Int256`` | | | | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``-`` | ``ByteType()`` | ``Int8`` | ``Int8`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``-`` | ``ShortType()`` | ``Int32`` | ``Int32`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``UInt8`` | ``IntegerType()`` | ``Int32`` | ``Int32`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``UInt16`` | ``LongType()`` | ``Int64`` | ``Int64`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``UInt32`` | ``DecimalType(20,0)`` | ``Decimal(20,0)`` | ``Decimal(20,0)`` | ++--------------------------------+ | | | +| ``UInt64`` | | | | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``UInt128`` | unsupported [3]_ | | | ++--------------------------------+ | | | +| ``UInt256`` | | | | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +``` + +[^footnote-3]: Clickhouse support numeric types up to 256 bit - `Int256`, `UInt256`, `Decimal256(S)`, `Decimal(P=39..76, S=0..76)`. + + But Spark's `DecimalType(P, S)` supports maximum `P=38` (128 bit). It is impossible to read, write or operate with values of larger precision, + this leads to an exception. + +### Temporal types + +Notes: +: - Datetime with timezone has the same precision as without timezone + - `DateTime` is alias for `DateTime32` + - `TIMESTAMP` is alias for `DateTime32`, but `TIMESTAMP(N)` is alias for `DateTime64(N)` + +```{eval-rst} ++-----------------------------------+--------------------------------------+----------------------------------+-------------------------------+ +| Clickhouse type (read) | Spark type | Clickhouse type (write) | Clickhouse type (create) | ++===================================+======================================+==================================+===============================+ +| ``Date`` | ``DateType()`` | ``Date`` | ``Date`` | ++-----------------------------------+--------------------------------------+----------------------------------+-------------------------------+ +| ``Date32`` | ``DateType()`` | ``Date`` | ``Date``, | +| | | | **cannot insert data** [4]_ | ++-----------------------------------+--------------------------------------+----------------------------------+-------------------------------+ +| ``DateTime32``, seconds | ``TimestampType()``, microseconds | ``DateTime64(6)``, microseconds | ``DateTime32``, seconds | ++-----------------------------------+--------------------------------------+----------------------------------+-------------------------------+ +| ``DateTime64(3)``, milliseconds | ``TimestampType()``, microseconds | ``DateTime64(6)``, microseconds | ``DateTime32``, seconds, | +| | | | **precision loss** [5]_ | ++-----------------------------------+--------------------------------------+----------------------------------+-------------------------------+ +| ``DateTime64(6)``, microseconds | ``TimestampType()``, microseconds | | ``DateTime32``, seconds, | ++-----------------------------------+--------------------------------------+ | **precision loss** [7]_ | +| ``DateTime64(7..9)``, nanoseconds | ``TimestampType()``, microseconds, | | | +| | **precision loss** [6]_ | | | +| | | | | ++-----------------------------------+--------------------------------------+ | | +| ``-`` | ``TimestampNTZType()``, microseconds | | | ++-----------------------------------+--------------------------------------+----------------------------------+-------------------------------+ +| ``DateTime32(TZ)`` | unsupported [7]_ | | | ++-----------------------------------+ | | | +| ``DateTime64(P, TZ)`` | | | | ++-----------------------------------+--------------------------------------+----------------------------------+-------------------------------+ +| ``IntervalNanosecond`` | ``LongType()`` | ``Int64`` | ``Int64`` | ++-----------------------------------+ | | | +| ``IntervalMicrosecond`` | | | | ++-----------------------------------+ | | | +| ``IntervalMillisecond`` | | | | ++-----------------------------------+ | | | +| ``IntervalSecond`` | | | | ++-----------------------------------+ | | | +| ``IntervalMinute`` | | | | ++-----------------------------------+ | | | +| ``IntervalHour`` | | | | ++-----------------------------------+ | | | +| ``IntervalDay`` | | | | ++-----------------------------------+ | | | +| ``IntervalMonth`` | | | | ++-----------------------------------+ | | | +| ``IntervalQuarter`` | | | | ++-----------------------------------+ | | | +| ``IntervalWeek`` | | | | ++-----------------------------------+ | | | +| ``IntervalYear`` | | | | ++-----------------------------------+--------------------------------------+----------------------------------+-------------------------------+ +``` + +```{eval-rst} +.. warning:: + + Note that types in Clickhouse and Spark have different value ranges: + + +------------------------+-----------------------------------+-----------------------------------+---------------------+--------------------------------+--------------------------------+ + | Clickhouse type | Min value | Max value | Spark type | Min value | Max value | + +========================+===================================+===================================+=====================+================================+================================+ + | ``Date`` | ``1970-01-01`` | ``2149-06-06`` | ``DateType()`` | ``0001-01-01`` | ``9999-12-31`` | + +------------------------+-----------------------------------+-----------------------------------+---------------------+--------------------------------+--------------------------------+ + | ``DateTime32`` | ``1970-01-01 00:00:00`` | ``2106-02-07 06:28:15`` | ``TimestampType()`` | ``0001-01-01 00:00:00.000000`` | ``9999-12-31 23:59:59.999999`` | + +------------------------+-----------------------------------+-----------------------------------+ | | | + | ``DateTime64(P=0..8)`` | ``1900-01-01 00:00:00.00000000`` | ``2299-12-31 23:59:59.99999999`` | | | | + +------------------------+-----------------------------------+-----------------------------------+ | | | + | ``DateTime64(P=9)`` | ``1900-01-01 00:00:00.000000000`` | ``2262-04-11 23:47:16.999999999`` | | | | + +------------------------+-----------------------------------+-----------------------------------+---------------------+--------------------------------+--------------------------------+ + + So not all of values in Spark DataFrame can be written to Clickhouse. + + References: + * `Clickhouse Date documentation `_ + * `Clickhouse Datetime32 documentation `_ + * `Clickhouse Datetime64 documentation `_ + * `Spark DateType documentation `_ + * `Spark TimestampType documentation `_ +``` + +[^footnote-4]: `Date32` has different bytes representation than `Date`, and inserting value of type `Date32` to `Date` column + leads to errors on Clickhouse side, e.g. `Date(106617) should be between 0 and 65535 inclusive of both values`. + Although Spark does properly read the `Date32` column as `DateType()`, and there should be no difference at all. + Probably this is some bug in Clickhouse driver. + +[^footnote-5]: Generic JDBC dialect generates DDL with Clickhouse type `TIMESTAMP` which is alias for `DateTime32` with precision up to seconds (`23:59:59`). + Inserting data with milliseconds precision (`23:59:59.999`) will lead to **throwing away milliseconds**. + Solution: create table manually, with proper column type. + +[^footnote-6]: Clickhouse support datetime up to nanoseconds precision (`23:59:59.999999999`), + but Spark `TimestampType()` supports datetime up to microseconds precision (`23:59:59.999999`). + Nanoseconds will be lost during read or write operations. + Solution: create table manually, with proper column type. + +[^footnote-7]: Clickhouse will raise an exception that data in format `2001-01-01 23:59:59.999999` has data `.999999` which does not match format `YYYY-MM-DD hh:mm:ss` + of `DateTime32` column type (see [^footnote-5]). + So Spark can create Clickhouse table, but cannot write data to column of this type. + Solution: create table manually, with proper column type. + +### String types + +```{eval-rst} ++--------------------------------------+------------------+------------------------+--------------------------+ +| Clickhouse type (read) | Spark type | Clickhousetype (write) | Clickhouse type (create) | ++======================================+==================+========================+==========================+ +| ``FixedString(N)`` | ``StringType()`` | ``String`` | ``String`` | ++--------------------------------------+ | | | +| ``String`` | | | | ++--------------------------------------+ | | | +| ``Enum8`` | | | | ++--------------------------------------+ | | | +| ``Enum16`` | | | | ++--------------------------------------+ | | | +| ``IPv4`` | | | | ++--------------------------------------+ | | | +| ``IPv6`` | | | | ++--------------------------------------+ | | | +| ``UUID`` | | | | ++--------------------------------------+------------------+ | | +| ``-`` | ``BinaryType()`` | | | ++--------------------------------------+------------------+------------------------+--------------------------+ +``` + +## Unsupported types + +Columns of these Clickhouse types cannot be read by Spark: +: - `AggregateFunction(func, T)` + - `Array(T)` + - `JSON` + - `Map(K, V)` + - `MultiPolygon` + - `Nested(field1 T1, ...)` + - `Nothing` + - `Point` + - `Polygon` + - `Ring` + - `SimpleAggregateFunction(func, T)` + - `Tuple(T1, T2, ...)` + +Dataframe with these Spark types cannot be written to Clickhouse: +: - `ArrayType(T)` + - `BinaryType()` + - `CharType(N)` + - `DayTimeIntervalType(P, S)` + - `MapType(K, V)` + - `NullType()` + - `StructType([...])` + - `TimestampNTZType()` + - `VarcharType(N)` + +This is because Spark does not have dedicated Clickhouse dialect, and uses Generic JDBC dialect instead. +This dialect does not have type conversion between some types, like Clickhouse `Array` -> Spark `ArrayType()`, and vice versa. + +The is a way to avoid this - just cast everything to `String`. + +## Explicit type cast + +### `DBReader` + +Use `CAST` or `toJSONString` to get column data as string in JSON format, + +For parsing JSON columns in ClickHouse, {obj}`JSON.parse_column ` method. + +```python +from pyspark.sql.types import ArrayType, IntegerType + +from onetl.file.format import JSON +from onetl.connection import ClickHouse +from onetl.db import DBReader + +reader = DBReader( + connection=clickhouse, + target="default.source_tbl", + columns=[ + "id", + "toJSONString(array_column) array_column", + ], +) +df = reader.run() + +# Spark requires all columns to have some specific type, describe it +column_type = ArrayType(IntegerType()) + +json = JSON() +df = df.select( + df.id, + json.parse_column("array_column", column_type), +) +``` + +### `DBWriter` + +For writing JSON data to ClickHouse, use the {obj}`JSON.serialize_column ` method to convert a DataFrame column to JSON format efficiently and write it as a `String` column in Clickhouse. + +```python +from onetl.file.format import JSON +from onetl.connection import ClickHouse +from onetl.db import DBWriter + +clickhouse = ClickHouse(...) + +clickhouse.execute( + """ + CREATE TABLE default.target_tbl ( + id Int32, + array_column_json String, + ) + ENGINE = MergeTree() + ORDER BY id + """, +) + +json = JSON() +df = df.select( + df.id, + json.serialize_column(df.array_column).alias("array_column_json"), +) + +writer.run(df) +``` + +Then you can parse this column on Clickhouse side - for example, by creating a view: + +```sql +SELECT + id, + JSONExtract(json_column, 'Array(String)') AS array_column +FROM target_tbl +``` + +You can also use [ALIAS](https://clickhouse.com/docs/en/sql-reference/statements/create/table#alias) +or [MATERIALIZED](https://clickhouse.com/docs/en/sql-reference/statements/create/table#materialized) columns +to avoid writing such expression in every `SELECT` clause all the time: + +```sql +CREATE TABLE default.target_tbl ( + id Int32, + array_column_json String, + -- computed column + array_column Array(String) ALIAS JSONExtract(json_column, 'Array(String)') + -- or materialized column + -- array_column Array(String) MATERIALIZED JSONExtract(json_column, 'Array(String)') +) +ENGINE = MergeTree() +ORDER BY id +``` + +Downsides: + +- Using `SELECT JSONExtract(...)` or `ALIAS` column can be expensive, because value is calculated on every row access. This can be especially harmful if such column is used in `WHERE` clause. +- `ALIAS` and `MATERIALIZED` columns are not included in `SELECT *` clause, they should be added explicitly: `SELECT *, calculated_column FROM table`. + +```{eval-rst} +.. warning:: + + `EPHEMERAL `_ columns are not supported by Spark + because they cannot be selected to determine target column type. +``` diff --git a/mddocs/docs/connection/db_connection/clickhouse/write.md b/mddocs/docs/connection/db_connection/clickhouse/write.md new file mode 100644 index 000000000..fa39d81a2 --- /dev/null +++ b/mddocs/docs/connection/db_connection/clickhouse/write.md @@ -0,0 +1,60 @@ +(clickhouse-write)= + +# Writing to Clickhouse using `DBWriter` + +For writing data to Clickhouse, use {obj}`DBWriter `. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`clickhouse-types` +``` + +```{eval-rst} +.. warning:: + + It is always recommended to create table explicitly using :ref:`Clickhouse.execute ` + instead of relying on Spark's table DDL generation. + + This is because Spark's DDL generator can create columns with different precision and types than it is expected, + causing precision loss or other issues. +``` + +## Examples + +```python +from onetl.connection import Clickhouse +from onetl.db import DBWriter + +clickhouse = Clickhouse(...) + +df = ... # data is here + +writer = DBWriter( + connection=clickhouse, + target="schema.table", + options=Clickhouse.WriteOptions( + if_exists="append", + # ENGINE is required by Clickhouse + createTableOptions="ENGINE = MergeTree() ORDER BY id", + ), +) + +writer.run(df) +``` + +## Options + +Method above accepts {obj}`Clickhouse.WriteOptions ` + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.clickhouse.options +``` + +```{eval-rst} +.. autopydantic_model:: ClickhouseWriteOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/greenplum/connection.md b/mddocs/docs/connection/db_connection/greenplum/connection.md new file mode 100644 index 000000000..e95a4e6a8 --- /dev/null +++ b/mddocs/docs/connection/db_connection/greenplum/connection.md @@ -0,0 +1,12 @@ +(greenplum-connection)= + +# Greenplum connection + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.greenplum.connection +``` + +```{eval-rst} +.. autoclass:: Greenplum + :members: get_packages, check +``` diff --git a/mddocs/docs/connection/db_connection/greenplum/execute.md b/mddocs/docs/connection/db_connection/greenplum/execute.md new file mode 100644 index 000000000..0b10c0bc7 --- /dev/null +++ b/mddocs/docs/connection/db_connection/greenplum/execute.md @@ -0,0 +1,159 @@ +(greenplum-execute)= + +# Executing statements in Greenplum + +```{eval-rst} +.. warning:: + + Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + + Do **NOT** use them to read large amounts of data. Use :ref:`DBReader ` instead. +``` + +## How to + +There are 2 ways to execute some statement in Greenplum + +### Use `Greenplum.fetch` + +Use this method to perform some `SELECT` query which returns **small number or rows**, like reading +Greenplum config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts {obj}`Greenplum.FetchOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +```{eval-rst} +.. warning:: + + ``Greenplum.fetch`` is implemented using Postgres JDBC connection, + so types are handled a bit differently than in ``DBReader``. See :ref:`postgres-types`. +``` + +#### Syntax support + +This method supports **any** query syntax supported by Greenplum, like: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ✅︎ `SELECT func(arg1, arg2)` or `{call func(arg1, arg2)}` - special syntax for calling functions +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Greenplum + +greenplum = Greenplum(...) + +df = greenplum.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=Greenplum.FetchOptions(queryTimeout=10), +) +greenplum.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `Greenplum.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts {obj}`Greenplum.ExecuteOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Greenplum, like: + +- ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +- ✅︎ `ALTER ...` +- ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +- ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +- ✅︎ `CALL procedure(arg1, arg2) ...` +- ✅︎ `SELECT func(arg1, arg2)` or `{call func(arg1, arg2)}` - special syntax for calling functions +- ✅︎ other statements not mentioned here +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Greenplum + +greenplum = Greenplum(...) + +greenplum.execute("DROP TABLE schema.table") +greenplum.execute( + """ + CREATE TABLE schema.table ( + id int, + key text, + value real + ) + DISTRIBUTED BY id + """, + options=Greenplum.ExecuteOptions(queryTimeout=10), +) +``` + +## Interaction schema + +Unlike reading & writing, executing statements in Greenplum is done **only** through Greenplum master node, +without any interaction between Greenplum segments and Spark executors. More than that, Spark executors are not used in this case. + +The only port used while interacting with Greenplum in this case is `5432` (Greenplum master port). + +```{eval-rst} +.. dropdown:: Spark <-> Greenplum interaction during Greenplum.execute()/Greenplum.fetch() + + .. plantuml:: + + @startuml + title Greenplum master <-> Spark driver + box "Spark" + participant "Spark driver" + end box + + box "Greenplum" + participant "Greenplum master" + end box + + == Greenplum.check() == + + activate "Spark driver" + "Spark driver" -> "Greenplum master" ++ : CONNECT + + == Greenplum.execute(statement) == + "Spark driver" --> "Greenplum master" : EXECUTE statement + "Greenplum master" -> "Spark driver" : RETURN result + + == Greenplum.close() == + "Spark driver" --> "Greenplum master" : CLOSE CONNECTION + + deactivate "Greenplum master" + deactivate "Spark driver" + @enduml +``` + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.greenplum.options +``` + +```{eval-rst} +.. autopydantic_model:: GreenplumFetchOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false + +``` + +```{eval-rst} +.. autopydantic_model:: GreenplumExecuteOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/greenplum/index.md b/mddocs/docs/connection/db_connection/greenplum/index.md new file mode 100644 index 000000000..1707fc960 --- /dev/null +++ b/mddocs/docs/connection/db_connection/greenplum/index.md @@ -0,0 +1,27 @@ +(greenplum)= + +# Greenplum + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +connection +``` + +```{toctree} +:caption: Operations +:maxdepth: 1 + +read +write +execute +``` + +```{toctree} +:caption: Troubleshooting +:maxdepth: 1 + +types +``` diff --git a/mddocs/docs/connection/db_connection/greenplum/prerequisites.md b/mddocs/docs/connection/db_connection/greenplum/prerequisites.md new file mode 100644 index 000000000..a2e55b950 --- /dev/null +++ b/mddocs/docs/connection/db_connection/greenplum/prerequisites.md @@ -0,0 +1,382 @@ +(greenplum-prerequisites)= + +# Prerequisites + +## Version Compatibility + +- Greenplum server versions: + : - Officially declared: 5.x, 6.x, and 7.x (which requires `Greenplum.get_packages(package_version="2.3.0")` or higher) + - Actually tested: 6.23, 7.0 +- Spark versions: 2.3.x - 3.2.x (Spark 3.3+ is not supported yet) +- Java versions: 8 - 11 + +See [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.2/greenplum-connector-spark/release_notes.html). + +## Installing PySpark + +To use Greenplum connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Downloading VMware package + +To use Greenplum connector you should download connector `.jar` file from +[VMware website](https://network.tanzu.vmware.com/products/vmware-greenplum#/releases/1413479/file_groups/16966) +and then pass it to Spark session. + +```{eval-rst} +.. warning:: + + Please pay attention to :ref:`Spark & Scala version compatibility `. +``` + +```{eval-rst} +.. warning:: + + There are issues with using package of version 2.3.0/2.3.1 with Greenplum 6.x - connector can + open transaction with ``SELECT * FROM table LIMIT 0`` query, but does not close it, which leads to deadlocks + during write. +``` + +There are several ways to do that. See {ref}`java-packages` for details. + +```{eval-rst} +.. note:: + + If you're uploading package to private package repo, use ``groupId=io.pivotal`` and ``artifactoryId=greenplum-spark_2.12`` + (``2.12`` is Scala version) to give uploaded package a proper name. +``` + +## Connecting to Greenplum + +### Interaction schema + +Spark executors open ports to listen incoming requests. +Greenplum segments are initiating connections to Spark executors using [EXTERNAL TABLE](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-sql_commands-CREATE_EXTERNAL_TABLE.html) +functionality, and send/read data using [gpfdist protocol](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/admin_guide-external-g-using-the-greenplum-parallel-file-server--gpfdist-.html#about-gpfdist-setup-and-performance-1). + +Data is **not** send through Greenplum master. +Greenplum master only receives commands to start reading/writing process, and manages all the metadata (external table location, schema and so on). + +More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/overview.html). + +### Set number of connections + +```{eval-rst} +.. warning:: + + This is very important!!! + + If you don't limit number of connections, you can exceed the `max_connections `_ + limit set on the Greenplum side. It's usually not so high, e.g. 500-1000 connections max, + depending on your Greenplum instance settings and using connection balancers like ``pgbouncer``. + + Consuming all available connections means **nobody** (even admin users) can connect to Greenplum. +``` + +Each job on the Spark executor makes its own connection to Greenplum master node, +so you need to limit number of connections to avoid opening too many of them. + +- Reading about `5-10Gb` of data requires about `3-5` parallel connections. +- Reading about `20-30Gb` of data requires about `5-10` parallel connections. +- Reading about `50Gb` of data requires ~ `10-20` parallel connections. +- Reading about `100+Gb` of data requires `20-30` parallel connections. +- Opening more than `30-50` connections is not recommended. + +Number of connections can be limited by 2 ways: + +- By limiting number of Spark executors and number of cores per-executor. Max number of parallel jobs is `executors * cores`. + +```{eval-rst} +.. tabs:: + + .. code-tab:: py Spark with master=local + + spark = ( + SparkSession.builder + # Spark will run with 5 threads in local mode, allowing up to 5 parallel tasks + .config("spark.master", "local[5]") + .config("spark.executor.cores", 1) + ).getOrCreate() + + .. code-tab:: py Spark with master=yarn or master=k8s, dynamic allocation + + spark = ( + SparkSession.builder + .config("spark.master", "yarn") + # Spark will start MAX 10 executors with 1 core each (dynamically), so max number of parallel jobs is 10 + .config("spark.dynamicAllocation.maxExecutors", 10) + .config("spark.executor.cores", 1) + ).getOrCreate() + + .. code-tab:: py Spark with master=yarn or master=k8s, static allocation + + spark = ( + SparkSession.builder + .config("spark.master", "yarn") + # Spark will start EXACTLY 10 executors with 1 core each, so max number of parallel jobs is 10 + .config("spark.executor.instances", 10) + .config("spark.executor.cores", 1) + ).getOrCreate() +``` + +- By limiting connection pool size user by Spark (**only** for Spark with `master=local`): + +```python +spark = SparkSession.builder.config("spark.master", "local[*]").getOrCreate() + +# No matter how many executors are started and how many cores they have, +# number of connections cannot exceed pool size: +Greenplum( + ..., + extra={ + "pool.maxSize": 10, + }, +) +``` + +See [connection pooling](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/using_the_connector.html#jdbcconnpool) +documentation. + +- By setting {obj}`num_partitions ` + and {obj}`partition_column ` (not recommended). + +### Allowing connection to Greenplum master + +Ask your Greenplum cluster administrator to allow your user to connect to Greenplum master node, +e.g. by updating `pg_hba.conf` file. + +More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/admin_guide-client_auth.html#limiting-concurrent-connections#allowing-connections-to-greenplum-database-0). + +### Set connection port + +#### Spark with `master=k8s` + +Please follow [the official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/configure.html#k8scfg) + +#### Spark with `master=yarn` or `master=local` + +To read data from Greenplum using Spark, following ports should be opened in firewall between Spark and Greenplum: + +- Spark driver and all Spark executors -> port `5432` on Greenplum master node. + + This port number should be set while connecting to Greenplum: + + ```python + greenplum = Greenplum(host="master.host", port=5432, ...) + ``` + +- Greenplum segments -> some port range (e.g. `41000-42000`) **listened by Spark executors**. + + This range should be set in `extra` option: + + ```python + greenplum = Greenplum( + ..., + extra={ + "server.port": "41000-42000", + }, + ) + ``` + + Number of ports in this range is `number of parallel running Spark sessions` * `number of parallel connections per session`. + + Number of connections per session (see below) is usually less than `30` (see above). + + Number of session depends on your environment: + : - For `master=local` only few ones-tens sessions can be started on the same host, depends on available RAM and CPU. + - For `master=yarn` hundreds or thousands of sessions can be started simultaneously, + but they are executing on different cluster nodes, so one port can be opened on different nodes at the same time. + +More details can be found in official documentation: +: - [port requirements](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/sys_reqs.html#network-port-requirements) + - [format of server.port value](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#server.port) + - [port troubleshooting](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/troubleshooting.html#port-errors) + +### Set connection host + +#### Spark with `master=k8s` + +Please follow [the official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/configure.html#k8scfg) + +#### Spark with `master=local` + +By default, Greenplum connector tries to resolve IP of current host, and then pass it as `gpfdist` URL to Greenplum segment. +This may fail in some cases. + +For example, IP can be resolved using `/etc/hosts` content like this: + +```text +127.0.0.1 localhost real-host-name +``` + +```bash +$ hostname -f +localhost + +$ hostname -i +127.0.0.1 +``` + +Reading/writing data to Greenplum will fail with following exception: + +```text +org.postgresql.util.PSQLException: ERROR: connection with gpfdist failed for +"gpfdist://127.0.0.1:49152/local-1709739764667/exec/driver", +effective url: "http://127.0.0.1:49152/local-1709739764667/exec/driver": +error code = 111 (Connection refused); (seg3 slice1 12.34.56.78:10003 pid=123456) +``` + +There are 2 ways to fix that: + +- Explicitly pass your host IP address to connector, like this + + ```python + import os + + # pass here real host IP (accessible from GP segments) + os.environ["HOST_IP"] = "192.168.1.1" + + greenplum = Greenplum( + ..., + extra={ + # connector will read IP from this environment variable + "server.hostEnv": "env.HOST_IP", + }, + spark=spark, + ) + ``` + + More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#server.hostenv). + +- Update `/etc/hosts` file to include real host IP: + + ```text + 127.0.0.1 localhost + # this IP should be accessible from GP segments + 192.168.1.1 driver-host-name + ``` + + So Greenplum connector will properly resolve host IP. + +#### Spark with `master=yarn` + +The same issue with resolving IP address can occur on Hadoop cluster node, but it's tricky to fix, because each node has a different IP. + +There are 3 ways to fix that: + +- Pass node hostname to `gpfdist` URL. So IP will be resolved on segment side: + + ```python + greenplum = Greenplum( + ..., + extra={ + "server.useHostname": "true", + }, + ) + ``` + + But this may fail if Hadoop cluster node hostname cannot be resolved from Greenplum segment side. + + More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#server.usehostname). + +- Set specific network interface to get IP address from: + + ```python + greenplum = Greenplum( + ..., + extra={ + "server.nic": "eth0", + }, + ) + ``` + + You can get list of network interfaces using this command. + + ```{eval-rst} + .. note:: + + This command should be executed on Hadoop cluster node, **not** Spark driver host! + ``` + + ```bash + $ ip address + 1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 + inet 127.0.0.1/8 scope host lo + valid_lft forever preferred_lft forever + 2: eth0: mtu 1500 qdisc fq_codel state UP group default qlen 1000 + inet 192.168.1.1/24 brd 192.168.1.255 scope global dynamic noprefixroute eth0 + valid_lft 83457sec preferred_lft 83457sec + ``` + + Note that in this case **each** Hadoop cluster node node should have network interface with name `eth0`. + + More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#server.nic). + +- Update `/etc/hosts` on each Hadoop cluster node to include real node IP: + + ```text + 127.0.0.1 localhost + # this IP should be accessible from GP segments + 192.168.1.1 cluster-node-name + ``` + + So Greenplum connector will properly resolve node IP. + +### Set required grants + +Ask your Greenplum cluster administrator to set following grants for a user, +used for creating a connection: + +```{eval-rst} +.. tabs:: + + .. code-tab:: sql Read + Write + + -- get access to get tables metadata & cluster information + GRANT SELECT ON information_schema.tables TO username; + GRANT SELECT ON pg_attribute TO username; + GRANT SELECT ON pg_class TO username; + GRANT SELECT ON pg_namespace TO username; + GRANT SELECT ON pg_settings TO username; + GRANT SELECT ON pg_stats TO username; + GRANT SELECT ON gp_distributed_xacts TO username; + GRANT SELECT ON gp_segment_configuration TO username; + -- Greenplum 5.x only + GRANT SELECT ON gp_distribution_policy TO username; + + -- allow creating external tables in the same schema as source/target table + GRANT USAGE ON SCHEMA myschema TO username; + GRANT CREATE ON SCHEMA myschema TO username; + ALTER USER username CREATEEXTTABLE(type = 'readable', protocol = 'gpfdist') CREATEEXTTABLE(type = 'writable', protocol = 'gpfdist'); + + -- allow read access to specific table (to get column types) + -- allow write access to specific table + GRANT SELECT, INSERT ON myschema.mytable TO username; + + .. code-tab:: sql Read only + + -- get access to get tables metadata & cluster information + GRANT SELECT ON information_schema.tables TO username; + GRANT SELECT ON pg_attribute TO username; + GRANT SELECT ON pg_class TO username; + GRANT SELECT ON pg_namespace TO username; + GRANT SELECT ON pg_settings TO username; + GRANT SELECT ON pg_stats TO username; + GRANT SELECT ON gp_distributed_xacts TO username; + GRANT SELECT ON gp_segment_configuration TO username; + -- Greenplum 5.x only + GRANT SELECT ON gp_distribution_policy TO username; + + -- allow creating external tables in the same schema as source table + GRANT USAGE ON SCHEMA schema_to_read TO username; + GRANT CREATE ON SCHEMA schema_to_read TO username; + -- yes, ``writable`` for reading from GP, because data is written from Greenplum to Spark executor. + ALTER USER username CREATEEXTTABLE(type = 'writable', protocol = 'gpfdist'); + + -- allow read access to specific table + GRANT SELECT ON schema_to_read.table_to_read TO username; +``` + +More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/install_cfg.html#role-privileges). diff --git a/mddocs/docs/connection/db_connection/greenplum/read.md b/mddocs/docs/connection/db_connection/greenplum/read.md new file mode 100644 index 000000000..502bbb915 --- /dev/null +++ b/mddocs/docs/connection/db_connection/greenplum/read.md @@ -0,0 +1,386 @@ +(greenplum-read)= + +# Reading from Greenplum using `DBReader` + +Data can be read from Greenplum to Spark using {obj}`DBReader `. +It also supports {ref}`strategy` for incremental data reading. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`greenplum-types`. +``` + +```{eval-rst} +.. note:: + + Unlike JDBC connectors, *Greenplum connector for Spark* does not support + executing **custom** SQL queries using ``.sql`` method. Connector can be used to only read data from a table or view. +``` + +## Supported DBReader features + +- ✅︎ `columns` (see note below) +- ✅︎ `where` (see note below) +- ✅︎ `hwm` (see note below), supported strategies: +- - ✅︎ {ref}`snapshot-strategy` +- - ✅︎ {ref}`incremental-strategy` +- - ✅︎ {ref}`snapshot-batch-strategy` +- - ✅︎ {ref}`incremental-batch-strategy` +- ❌ `hint` (is not supported by Greenplum) +- ❌ `df_schema` +- ✅︎ `options` (see {obj}`Greenplum.ReadOptions `) + +```{eval-rst} +.. warning:: + + In case of Greenplum connector, ``DBReader`` does not generate raw ``SELECT`` query. Instead it relies on Spark SQL syntax + which in some cases (using column projection and predicate pushdown) can be converted to Greenplum SQL. + + So ``columns``, ``where`` and ``hwm.expression`` should be specified in `Spark SQL `_ syntax, + not Greenplum SQL. + + This is OK: + + .. code-block:: python + + DBReader( + columns=[ + "some_column", + # this cast is executed on Spark side + "CAST(another_column AS STRING)", + ], + # this predicate is parsed by Spark, and can be pushed down to Greenplum + where="some_column LIKE 'val1%'", + ) + + This is will fail: + + .. code-block:: python + + DBReader( + columns=[ + "some_column", + # Spark does not have `text` type + "CAST(another_column AS text)", + ], + # Spark does not support ~ syntax for regexp matching + where="some_column ~ 'val1.*'", + ) +``` + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Greenplum +from onetl.db import DBReader + +greenplum = Greenplum(...) + +reader = DBReader( + connection=greenplum, + source="schema.table", + columns=["id", "key", "CAST(value AS string) value", "updated_dt"], + where="key = 'something'", +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Greenplum +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +greenplum = Greenplum(...) + +reader = DBReader( + connection=greenplum, + source="schema.table", + columns=["id", "key", "CAST(value AS string) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="greenplum_hwm", expression="updated_dt"), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Interaction schema + +High-level schema is described in {ref}`greenplum-prerequisites`. You can find detailed interaction schema below. + +```{eval-rst} +.. dropdown:: Spark <-> Greenplum interaction during DBReader.run() + + .. plantuml:: + + @startuml + title Greenplum master <-> Spark driver + box "Spark" + participant "Spark driver" + participant "Spark executor1" + participant "Spark executor2" + participant "Spark executorN" + end box + + box "Greenplum" + participant "Greenplum master" + participant "Greenplum segment1" + participant "Greenplum segment2" + participant "Greenplum segmentN" + end box + + == Greenplum.check() == + + activate "Spark driver" + "Spark driver" -> "Greenplum master" ++ : CONNECT + + "Spark driver" --> "Greenplum master" : CHECK IF TABLE EXISTS gp_table + "Greenplum master" --> "Spark driver" : TABLE EXISTS + "Spark driver" -> "Greenplum master" : SHOW SCHEMA FOR gp_table + "Greenplum master" --> "Spark driver" : (id bigint, col1 int, col2 text, ...) + + == DBReader.run() == + + "Spark driver" -> "Spark executor1" ++ : START EXECUTOR FOR df(id bigint, col1 int, col2 text, ...) PARTITION 1 + "Spark driver" -> "Spark executor2" ++ : START EXECUTOR FOR df(id bigint, col1 int, col2 text, ...) PARTITION 2 + "Spark driver" -> "Spark executorN" ++ : START EXECUTOR FOR df(id bigint, col1 int, col2 text, ...) PARTITION N + + note right of "Spark driver" : This is done in parallel,\nexecutors are independent\n|\n|\n|\nV + "Spark executor1" -> "Greenplum master" ++ : CREATE WRITABLE EXTERNAL TABLE spark_executor1 (id bigint, col1 int, col2 text, ...) USING address=executor1_host:executor1_port;\nINSERT INTO EXTERNAL TABLE spark_executor1 FROM gp_table WHERE gp_segment_id = 1 + note right of "Greenplum master" : Each white vertical line here is a opened connection to master.\nUsually, **N+1** connections are created from Spark to Greenplum master + "Greenplum master" --> "Greenplum segment1" ++ : SELECT DATA FROM gp_table_data_on_segment1 TO spark_executor1 + note right of "Greenplum segment1" : No direct requests between Greenplum segments & Spark.\nData transfer is always initiated by Greenplum segments. + + "Spark executor2" -> "Greenplum master" ++ : CREATE WRITABLE EXTERNAL TABLE spark_executor2 (id bigint, col1 int, col2 text, ...) USING address=executor2_host:executor2_port;\nINSERT INTO EXTERNAL TABLE spark_executor2 FROM gp_table WHERE gp_segment_id = 2 + "Greenplum master" --> "Greenplum segment2" ++ : SELECT DATA FROM gp_table_data_on_segment2 TO spark_executor2 + + "Spark executorN" -> "Greenplum master" ++ : CREATE WRITABLE EXTERNAL TABLE spark_executorN (id bigint, col1 int, col2 text, ...) USING address=executorN_host:executorN_port;\nINSERT INTO EXTERNAL TABLE spark_executorN FROM gp_table WHERE gp_segment_id = N + "Greenplum master" --> "Greenplum segmentN" ++ : SELECT DATA FROM gp_table_data_on_segmentN TO spark_executorN + + "Greenplum segment1" ->o "Spark executor1" -- : INITIALIZE CONNECTION TO Spark executor1\nPUSH DATA TO Spark executor1 + note left of "Spark executor1" : Circle is an open GPFDIST port,\nlistened by executor + + "Greenplum segment2" ->o "Spark executor2" -- : INITIALIZE CONNECTION TO Spark executor2\nPUSH DATA TO Spark executor2 + "Greenplum segmentN" ->o "Spark executorN" -- : INITIALIZE CONNECTION TO Spark executorN\nPUSH DATA TO Spark executorN + + == Spark.stop() == + + "Spark executor1" --> "Greenplum master" : DROP TABLE spark_executor1 + deactivate "Greenplum master" + "Spark executor2" --> "Greenplum master" : DROP TABLE spark_executor2 + deactivate "Greenplum master" + "Spark executorN" --> "Greenplum master" : DROP TABLE spark_executorN + deactivate "Greenplum master" + + "Spark executor1" --> "Spark driver" -- : DONE + "Spark executor2" --> "Spark driver" -- : DONE + "Spark executorN" --> "Spark driver" -- : DONE + + "Spark driver" --> "Greenplum master" : CLOSE CONNECTION + deactivate "Greenplum master" + deactivate "Spark driver" + @enduml +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Greenplum to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Greenplum to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +### Read data in parallel + +`DBReader` in case of Greenplum connector requires view or table to have a column which is used by Spark +for parallel reads. + +Choosing proper column allows each Spark executor to read only part of data stored in the specified segment, +avoiding moving large amounts of data between segments, which improves reading performance. + +#### Using `gp_segment_id` + +By default, `DBReader` will use [gp_segment_id](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/troubleshooting.html#reading-from-a-view) +column for parallel data reading. Each DataFrame partition will contain data of a specific Greenplum segment. + +This allows each Spark executor read only data from specific Greenplum segment, avoiding moving large amounts of data between segments. + +If view is used, it is recommended to include `gp_segment_id` column to this view: + +```{eval-rst} +.. dropdown:: Reading from view with gp_segment_id column + + .. code-block:: python + + from onetl.connection import Greenplum + from onetl.db import DBReader + + greenplum = Greenplum(...) + + greenplum.execute( + """ + CREATE VIEW schema.view_with_gp_segment_id AS + SELECT + id, + some_column, + another_column, + gp_segment_id -- IMPORTANT + FROM schema.some_table + """, + ) + + reader = DBReader( + connection=greenplum, + source="schema.view_with_gp_segment_id", + ) + df = reader.run() +``` + +#### Using custom `partition_column` + +Sometimes table or view is lack of `gp_segment_id` column, but there is some column +with value range correlated with Greenplum segment distribution. + +In this case, custom column can be used instead: + +```{eval-rst} +.. dropdown:: Reading from view with custom partition_column + + .. code-block:: python + + from onetl.connection import Greenplum + from onetl.db import DBReader + + greenplum = Greenplum(...) + + greenplum.execute( + """ + CREATE VIEW schema.view_with_partition_column AS + SELECT + id, + some_column, + part_column -- correlated to greenplum segment ID + FROM schema.some_table + """, + ) + + reader = DBReader( + connection=greenplum, + source="schema.view_with_partition_column", + options=Greenplum.ReadOptions( + # parallelize data using specified column + partitionColumn="part_column", + # create 10 Spark tasks, each will read only part of table data + partitions=10, + ), + ) + df = reader.run() +``` + +#### Reading `DISTRIBUTED REPLICATED` tables + +Replicated tables do not have `gp_segment_id` column at all, so you need to set `partition_column` to some column name +of type integer/bigint/smallint. + +### Parallel `JOIN` execution + +In case of using views which require some data motion between Greenplum segments, like `JOIN` queries, another approach should be used. + +Each Spark executor N will run the same query, so each of N query will start its own JOIN process, leading to really heavy load on Greenplum segments. +**This should be avoided**. + +Instead is recommended to run `JOIN` query on Greenplum side, save the result to an intermediate table, +and then read this table using `DBReader`: + +```{eval-rst} +.. dropdown:: Reading from view using intermediate table + + .. code-block:: python + + from onetl.connection import Greenplum + from onetl.db import DBReader + + greenplum = Greenplum(...) + + greenplum.execute( + """ + CREATE UNLOGGED TABLE schema.intermediate_table AS + SELECT + id, + tbl1.col1, + tbl1.data, + tbl2.another_data + FROM + schema.table1 as tbl1 + JOIN + schema.table2 as tbl2 + ON + tbl1.col1 = tbl2.col2 + WHERE ... + """, + ) + + reader = DBReader( + connection=greenplum, + source="schema.intermediate_table", + ) + df = reader.run() + + # write dataframe somethere + + greenplum.execute( + """ + DROP TABLE schema.intermediate_table + """, + ) +``` + +```{eval-rst} +.. warning:: + + **NEVER** do that: + + .. code-block:: python + + df1 = DBReader(connection=greenplum, target="public.table1", ...).run() + df2 = DBReader(connection=greenplum, target="public.table2", ...).run() + + joined_df = df1.join(df2, on="col") + + This will lead to sending all the data from both ``table1`` and ``table2`` to Spark executor memory, and then ``JOIN`` + will be performed on Spark side, not inside Greenplum. This is **VERY** inefficient. +``` + +#### `TEMPORARY` tables notice + +Someone could think that writing data from view or result of `JOIN` to `TEMPORARY` table, +and then passing it to `DBReader`, is an efficient way to read data from Greenplum. This is because temp tables are not generating WAL files, +and are automatically deleted after finishing the transaction. + +That will **NOT** work. Each Spark executor establishes its own connection to Greenplum. +And each connection starts its own transaction which means that every executor will read empty temporary table. + +You should use [UNLOGGED](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-sql_commands-CREATE_TABLE.html) tables +to write data to intermediate table without generating WAL logs. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.greenplum.options +``` + +```{eval-rst} +.. autopydantic_model:: GreenplumReadOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/greenplum/types.md b/mddocs/docs/connection/db_connection/greenplum/types.md new file mode 100644 index 000000000..1f9d83627 --- /dev/null +++ b/mddocs/docs/connection/db_connection/greenplum/types.md @@ -0,0 +1,406 @@ +(greenplum-types)= + +# Greenplum \<-> Spark type mapping + +```{eval-rst} +.. note:: + + The results below are valid for Spark 3.2.4, and may differ on other Spark versions. +``` + +## Type detection & casting + +Spark's DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from Greenplum + +This is how Greenplum connector performs this: + +- Execute query `SELECT * FROM table LIMIT 0` [^footnote-1]. +- For each column in query result get column name and Greenplum type. +- Find corresponding `Greenplum type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- Use Spark column projection and predicate pushdown features to build a final query. +- Create DataFrame from generated query with inferred schema. + +[^footnote-1]: Yes, **all columns of a table**, not just selected ones. + This means that if source table **contains** columns with unsupported type, the entire table cannot be read. + +### Writing to some existing Greenplum table + +This is how Greenplum connector performs this: + +- Get names of columns in DataFrame. +- Perform `SELECT * FROM table LIMIT 0` query. +- For each column in query result get column name and Greenplum type. +- Match table columns with DataFrame columns (by name, case insensitive). + If some column is present only in target table, but not in DataFrame (like `DEFAULT` or `SERIAL` column), and vice versa, raise an exception. + See [Explicit type cast]. +- Find corresponding `Spark type` → `Greenplumtype (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- If `Greenplumtype (write)` match `Greenplum type (read)`, no additional casts will be performed, DataFrame column will be written to Greenplum as is. +- If `Greenplumtype (write)` does not match `Greenplum type (read)`, DataFrame column will be casted to target column type **on Greenplum side**. For example, you can write column with text data to `json` column which Greenplum connector currently does not support. + +### Create new table using Spark + +```{eval-rst} +.. warning:: + + ABSOLUTELY NOT RECOMMENDED! +``` + +This is how Greenplum connector performs this: + +- Find corresponding `Spark type` → `Greenplum type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- Generate DDL for creating table in Greenplum, like `CREATE TABLE (col1 ...)`, and run it. +- Write DataFrame to created table as is. + +More details [can be found here](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/write_to_gpdb.html). + +But Greenplum connector support only limited number of types and almost no custom clauses (like `PARTITION BY`). +So instead of relying on Spark to create tables: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + writer = DBWriter( + connection=greenplum, + target="public.table", + options=Greenplum.WriteOptions( + if_exists="append", + # by default distribution is random + distributedBy="id", + # partitionBy is not supported + ), + ) + writer.run(df) +``` + +Always prefer creating table with desired DDL **BEFORE WRITING DATA**: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + greenplum.execute( + """ + CREATE TABLE public.table ( + id int32, + business_dt timestamp(6), + value json + ) + PARTITION BY RANGE (business_dt) + DISTRIBUTED BY id + """, + ) + + writer = DBWriter( + connection=greenplum, + target="public.table", + options=Greenplum.WriteOptions(if_exists="append"), + ) + writer.run(df) +``` + +See Greenplum [CREATE TABLE](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-sql_commands-CREATE_TABLE.html) documentation. + +## Supported types + +See: +: - [official connector documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/reference-datatype_mapping.html) + - [list of Greenplum types](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-data_types.html) + +### Numeric types + +```{eval-rst} ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | ++==================================+===================================+===============================+=========================+ +| ``decimal`` | ``DecimalType(P=38, S=18)`` | ``decimal(P=38, S=18)`` | ``decimal`` (unbounded) | ++----------------------------------+-----------------------------------+-------------------------------+ | +| ``decimal(P=0..38)`` | ``DecimalType(P=0..38, S=0)`` | ``decimal(P=0..38, S=0)`` | | ++----------------------------------+-----------------------------------+-------------------------------+ | +| ``decimal(P=0..38, S=0..38)`` | ``DecimalType(P=0..38, S=0..38)`` | ``decimal(P=0..38, S=0..38)`` | | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``decimal(P=39.., S=0..)`` | unsupported [2]_ | | | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``real`` | ``FloatType()`` | ``real`` | ``real`` | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``double precision`` | ``DoubleType()`` | ``double precision`` | ``double precision`` | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``-`` | ``ByteType()`` | unsupported | unsupported | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``smallint`` | ``ShortType()`` | ``smallint`` | ``smallint`` | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``integer`` | ``IntegerType()`` | ``integer`` | ``integer`` | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``bigint`` | ``LongType()`` | ``bigint`` | ``bigint`` | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``money`` | unsupported | | | ++----------------------------------+ | | | +| ``int4range`` | | | | ++----------------------------------+ | | | +| ``int8range`` | | | | ++----------------------------------+ | | | +| ``numrange`` | | | | ++----------------------------------+ | | | +| ``int2vector`` | | | | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +``` + +[^footnote-2]: Greenplum support decimal types with unlimited precision. + + But Spark's `DecimalType(P, S)` supports maximum `P=38` (128 bit). It is impossible to read, write or operate with values of larger precision, + this leads to an exception. + +### Temporal types + +```{eval-rst} ++------------------------------------+-------------------------+-----------------------+-------------------------+ +| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | ++====================================+=========================+=======================+=========================+ +| ``date`` | ``DateType()`` | ``date`` | ``date`` | ++------------------------------------+-------------------------+-----------------------+-------------------------+ +| ``time`` | ``TimestampType()``, | ``timestamp`` | ``timestamp`` | ++------------------------------------+ time format quirks [3]_ | | | +| ``time(0..6)`` | | | | ++------------------------------------+ | | | +| ``time with time zone`` | | | | ++------------------------------------+ | | | +| ``time(0..6) with time zone`` | | | | ++------------------------------------+-------------------------+-----------------------+-------------------------+ +| ``timestamp`` | ``TimestampType()`` | ``timestamp`` | ``timestamp`` | ++------------------------------------+ | | | +| ``timestamp(0..6)`` | | | | ++------------------------------------+ | | | +| ``timestamp with time zone`` | | | | ++------------------------------------+ | | | +| ``timestamp(0..6) with time zone`` | | | | ++------------------------------------+-------------------------+-----------------------+-------------------------+ +| ``interval`` or any precision | unsupported | | | ++------------------------------------+ | | | +| ``daterange`` | | | | ++------------------------------------+ | | | +| ``tsrange`` | | | | ++------------------------------------+ | | | +| ``tstzrange`` | | | | ++------------------------------------+-------------------------+-----------------------+-------------------------+ +``` + +```{eval-rst} +.. warning:: + + Note that types in Greenplum and Spark have different value ranges: + + +----------------+---------------------------------+----------------------------------+---------------------+--------------------------------+--------------------------------+ + | Greenplum type | Min value | Max value | Spark type | Min value | Max value | + +================+=================================+==================================+=====================+================================+================================+ + | ``date`` | ``-4713-01-01`` | ``5874897-01-01`` | ``DateType()`` | ``0001-01-01`` | ``9999-12-31`` | + +----------------+---------------------------------+----------------------------------+---------------------+--------------------------------+--------------------------------+ + | ``timestamp`` | ``-4713-01-01 00:00:00.000000`` | ``294276-12-31 23:59:59.999999`` | ``TimestampType()`` | ``0001-01-01 00:00:00.000000`` | ``9999-12-31 23:59:59.999999`` | + +----------------+---------------------------------+----------------------------------+ | | | + | ``time`` | ``00:00:00.000000`` | ``24:00:00.000000`` | | | | + +----------------+---------------------------------+----------------------------------+---------------------+--------------------------------+--------------------------------+ + + So not all of values can be read from Greenplum to Spark. + + References: + * `Greenplum types documentation `_ + * `Spark DateType documentation `_ + * `Spark TimestampType documentation `_ +``` + +[^footnote-3]: `time` type is the same as `timestamp` with date `1970-01-01`. So instead of reading data from Postgres like `23:59:59` + it is actually read `1970-01-01 23:59:59`, and vice versa. + +### String types + +```{eval-rst} ++-----------------------------+------------------+-----------------------+-------------------------+ +| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | ++=============================+==================+=======================+=========================+ +| ``character`` | ``StringType()`` | ``text`` | ``text`` | ++-----------------------------+ | | | +| ``character(N)`` | | | | ++-----------------------------+ | | | +| ``character varying`` | | | | ++-----------------------------+ | | | +| ``character varying(N)`` | | | | ++-----------------------------+ | | | +| ``text`` | | | | ++-----------------------------+ | | | +| ``xml`` | | | | ++-----------------------------+ | | | +| ``CREATE TYPE ... AS ENUM`` | | | | ++-----------------------------+------------------+-----------------------+-------------------------+ +| ``json`` | unsupported | | | ++-----------------------------+ | | | +| ``jsonb`` | | | | ++-----------------------------+------------------+-----------------------+-------------------------+ +``` + +### Binary types + +```{eval-rst} ++--------------------------+-------------------+-----------------------+-------------------------+ +| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | ++==========================+===================+=======================+=========================+ +| ``boolean`` | ``BooleanType()`` | ``boolean`` | ``boolean`` | ++--------------------------+-------------------+-----------------------+-------------------------+ +| ``bit`` | unsupported | | | ++--------------------------+ | | | +| ``bit(N)`` | | | | ++--------------------------+ | | | +| ``bit varying`` | | | | ++--------------------------+ | | | +| ``bit varying(N)`` | | | | ++--------------------------+-------------------+-----------------------+-------------------------+ +| ``bytea`` | unsupported [4]_ | | | ++--------------------------+-------------------+-----------------------+-------------------------+ +| ``-`` | ``BinaryType()`` | ``bytea`` | ``bytea`` | ++--------------------------+-------------------+-----------------------+-------------------------+ +``` + +[^footnote-4]: Yes, that's weird. + +### Struct types + +```{eval-rst} ++--------------------------------+------------------+-----------------------+-------------------------+ +| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | ++================================+==================+=======================+=========================+ +| ``T[]`` | unsupported | | | ++--------------------------------+------------------+-----------------------+-------------------------+ +| ``-`` | ``ArrayType()`` | unsupported | | ++--------------------------------+------------------+-----------------------+-------------------------+ +| ``CREATE TYPE sometype (...)`` | ``StringType()`` | ``text`` | ``text`` | ++--------------------------------+------------------+-----------------------+-------------------------+ +| ``-`` | ``StructType()`` | unsupported | | ++--------------------------------+------------------+ | | +| ``-`` | ``MapType()`` | | | ++--------------------------------+------------------+-----------------------+-------------------------+ +``` + +## Unsupported types + +Columns of these types cannot be read/written by Spark: +: - `cidr` + - `inet` + - `macaddr` + - `macaddr8` + - `circle` + - `box` + - `line` + - `lseg` + - `path` + - `point` + - `polygon` + - `tsvector` + - `tsquery` + - `uuid` + +The is a way to avoid this - just cast unsupported types to `text`. But the way this can be done is not a straightforward. + +## Explicit type cast + +### `DBReader` + +Direct casting of Greenplum types is not supported by DBReader due to the connector’s implementation specifics. + +```python +reader = DBReader( + connection=greenplum, + # will fail + columns=["CAST(unsupported_column AS text)"], +) +``` + +But there is a workaround - create a view with casting unsupported column to text (or any other supported type). +For example, you can use [to_json](https://www.postgresql.org/docs/current/functions-json.html) Postgres function to convert column of any type to string representation and then parse this column on Spark side using {obj}`JSON.parse_column ` method. + +```python +from pyspark.sql.types import ArrayType, IntegerType + +from onetl.connection import Greenplum +from onetl.db import DBReader +from onetl.file.format import JSON + +greenplum = Greenplum(...) + +greenplum.execute( + """ + CREATE VIEW schema.view_with_json_column AS + SELECT + id, + supported_column, + to_json(array_column) array_column_as_json, + gp_segment_id -- ! important ! + FROM + schema.table_with_unsupported_columns + """, +) + +# create dataframe using this view +reader = DBReader( + connection=greenplum, + source="schema.view_with_json_column", +) +df = reader.run() + +# Define the schema for the JSON data +json_scheme = ArrayType(IntegerType()) + +df = df.select( + df.id, + df.supported_column, + JSON().parse_column(df.array_column_as_json, json_scheme).alias("array_column"), +) +``` + +### `DBWriter` + +To write data to a `text` or `json` column in a Greenplum table, use {obj}`JSON.serialize_column ` method. + +```python +from onetl.connection import Greenplum +from onetl.db import DBWriter +from onetl.file.format import JSON + +greenplum = Greenplum(...) + +greenplum.execute( + """ + CREATE TABLE schema.target_table ( + id int, + supported_column timestamp, + array_column_as_json jsonb, -- or text + ) + DISTRIBUTED BY id + """, +) + +write_df = df.select( + df.id, + df.supported_column, + JSON().serialize_column(df.array_column).alias("array_column_json"), +) + +writer = DBWriter( + connection=greenplum, + target="schema.target_table", +) +writer.run(write_df) +``` + +Then you can parse this column on Greenplum side: + +```sql +SELECT + id, + supported_column, + -- access first item of an array + array_column_as_json->0 +FROM + schema.target_table +``` diff --git a/mddocs/docs/connection/db_connection/greenplum/write.md b/mddocs/docs/connection/db_connection/greenplum/write.md new file mode 100644 index 000000000..af367ad75 --- /dev/null +++ b/mddocs/docs/connection/db_connection/greenplum/write.md @@ -0,0 +1,140 @@ +(greenplum-write)= + +# Writing to Greenplum using `DBWriter` + +For writing data to Greenplum, use {obj}`DBWriter ` +with {obj}`GreenplumWriteOptions `. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`greenplum-types`. +``` + +```{eval-rst} +.. warning:: + + It is always recommended to create table explicitly using :ref:`Greenplum.execute ` + instead of relying on Spark's table DDL generation. + + This is because Spark's DDL generator can create columns with different types than it is expected. +``` + +## Examples + +```python +from onetl.connection import Greenplum +from onetl.db import DBWriter + +greenplum = Greenplum(...) + +df = ... # data is here + +writer = DBWriter( + connection=greenplum, + target="schema.table", + options=Greenplum.WriteOptions( + if_exists="append", + # by default distribution is random + distributedBy="id", + # partitionBy is not supported + ), +) + +writer.run(df) +``` + +## Interaction schema + +High-level schema is described in {ref}`greenplum-prerequisites`. You can find detailed interaction schema below. + +```{eval-rst} +.. dropdown:: Spark <-> Greenplum interaction during DBWriter.run() + + .. plantuml:: + + @startuml + title Greenplum master <-> Spark driver + box "Spark" + participant "Spark driver" + participant "Spark executor1" + participant "Spark executor2" + participant "Spark executorN" + end box + + box "Greenplum" + participant "Greenplum master" + participant "Greenplum segment1" + participant "Greenplum segment2" + participant "Greenplum segmentN" + end box + + == Greenplum.check() == + + activate "Spark driver" + "Spark driver" -> "Greenplum master" ++ : CONNECT + "Spark driver" --> "Greenplum master" ++ : CHECK IF TABLE EXISTS gp_table + "Greenplum master" --> "Spark driver" : TABLE NOT EXISTS + + == DBWriter.run(df) == + + "Spark driver" -> "Spark executor1" ++ : START EXECUTOR FOR df(id bigint, col1 int, col2 text, ...) PARTITION 1 + "Spark driver" -> "Spark executor2" ++ : START EXECUTOR FOR df(id bigint, col1 int, col2 text, ...) PARTITION 2 + "Spark driver" -> "Spark executorN" ++ : START EXECUTOR FOR df(id bigint, col1 int, col2 text, ...) PARTITION N + + note right of "Spark driver" : This is done in parallel,\nexecutors are independent\n|\n|\n|\nV + "Spark executor1" -> "Greenplum master" ++ : CREATE READABLE EXTERNAL TABLE spark_executor1 (id bigint, col1 int, col2 text, ...) USING address=executor1_host:executor1_port;\nINSERT INTO gp_table FROM spark_executor1 + note right of "Greenplum master" : Each white vertical line here is a opened connection to master.\nUsually, **N+1** connections are created from Spark to Greenplum master + "Greenplum master" --> "Greenplum segment1" ++ : SELECT DATA FROM spark_executor1 TO gp_table_data_on_segment1 + note right of "Greenplum segment1" : No direct requests between Greenplum segments & Spark.\nData transfer is always initiated by Greenplum segments. + + "Spark executor2" -> "Greenplum master" ++ : CREATE READABLE EXTERNAL TABLE spark_executor2 (id bigint, col1 int, col2 text, ...) USING address=executor2_host:executor2_port;\nINSERT INTO gp_table FROM spark_executor2 + "Greenplum master" --> "Greenplum segment2" ++ : SELECT DATA FROM spark_executor2 TO gp_table_data_on_segment2 + + "Spark executorN" -> "Greenplum master" ++ : CREATE READABLE EXTERNAL TABLE spark_executorN (id bigint, col1 int, col2 text, ...) USING address=executorN_host:executorN_port;\nINSERT INTO gp_table FROM spark_executorN + "Greenplum master" --> "Greenplum segmentN" ++ : SELECT DATA FROM spark_executorN TO gp_table_data_on_segmentN + + "Greenplum segment1" -->o "Spark executor1" : INITIALIZE CONNECTION TO Spark executor1 + "Spark executor1" -> "Greenplum segment1" : READ DATA FROM Spark executor1 + note left of "Spark executor1" : Circle is an open GPFDIST port,\nlistened by executor + deactivate "Greenplum segment1" + + "Greenplum segment2" -->o "Spark executor2" : INITIALIZE CONNECTION TO Spark executor2 + "Spark executor2" -> "Greenplum segment2" : READ DATA FROM Spark executor2 + deactivate "Greenplum segment2" + + "Greenplum segmentN" -->o "Spark executorN" : INITIALIZE CONNECTION TO Spark executorN + "Spark executorN" -> "Greenplum segmentN" : READ DATA FROM Spark executorN + deactivate "Greenplum segmentN" + + == Finished == + + "Spark executor1" --> "Greenplum master" : DROP TABLE spark_executor1 + deactivate "Greenplum master" + "Spark executor2" --> "Greenplum master" : DROP TABLE spark_executor2 + deactivate "Greenplum master" + "Spark executorN" --> "Greenplum master" : DROP TABLE spark_executorN + deactivate "Greenplum master" + + "Spark executor1" --> "Spark driver" -- : DONE + "Spark executor2" --> "Spark driver" -- : DONE + "Spark executorN" --> "Spark driver" -- : DONE + + "Spark driver" --> "Greenplum master" : CLOSE CONNECTION + deactivate "Greenplum master" + deactivate "Spark driver" + @enduml +``` + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.greenplum.options +``` + +```{eval-rst} +.. autopydantic_model:: GreenplumWriteOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/hive/connection.md b/mddocs/docs/connection/db_connection/hive/connection.md new file mode 100644 index 000000000..a9a0cfc55 --- /dev/null +++ b/mddocs/docs/connection/db_connection/hive/connection.md @@ -0,0 +1,13 @@ +(hive-connection)= + +# Hive Connection + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.hive.connection +``` + +```{eval-rst} +.. autoclass:: Hive + :members: get_current, check + :member-order: bysource +``` diff --git a/mddocs/docs/connection/db_connection/hive/execute.md b/mddocs/docs/connection/db_connection/hive/execute.md new file mode 100644 index 000000000..d7b0694c4 --- /dev/null +++ b/mddocs/docs/connection/db_connection/hive/execute.md @@ -0,0 +1,55 @@ +(hive-execute)= + +# Executing statements in Hive + +Use `Hive.execute(...)` to execute DDL and DML operations. + +## Syntax support + +This method supports **any** query syntax supported by Hive, like: + +- ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +- ✅︎ `LOAD DATA ...`, and so on +- ✅︎ `ALTER ...` +- ✅︎ `INSERT INTO ... SELECT ...`, and so on +- ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, and so on +- ✅︎ `MSCK REPAIR TABLE ...`, and so on +- ✅︎ other statements not mentioned here +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +```{eval-rst} +.. warning:: + + Actually, query should be written using `SparkSQL `_ syntax, not HiveQL. +``` + +## Examples + +```python +from onetl.connection import Hive + +hive = Hive(...) + +hive.execute("DROP TABLE schema.table") +hive.execute( + """ + CREATE TABLE schema.table ( + id NUMBER, + key VARCHAR, + value DOUBLE + ) + PARTITION BY (business_date DATE) + STORED AS orc + """ +) +``` + +### Details + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.hive.connection +``` + +```{eval-rst} +.. automethod:: Hive.execute +``` diff --git a/mddocs/docs/connection/db_connection/hive/index.md b/mddocs/docs/connection/db_connection/hive/index.md new file mode 100644 index 000000000..bb8e5ebcc --- /dev/null +++ b/mddocs/docs/connection/db_connection/hive/index.md @@ -0,0 +1,28 @@ +(hive)= + +# Hive + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +connection +``` + +```{toctree} +:caption: Operations +:maxdepth: 1 + +read +sql +write +execute +``` + +```{toctree} +:caption: For developers +:maxdepth: 1 + +slots +``` diff --git a/mddocs/docs/connection/db_connection/hive/prerequisites.md b/mddocs/docs/connection/db_connection/hive/prerequisites.md new file mode 100644 index 000000000..79ff0e5dc --- /dev/null +++ b/mddocs/docs/connection/db_connection/hive/prerequisites.md @@ -0,0 +1,134 @@ +(hive-prerequisites)= + +# Prerequisites + +```{eval-rst} +.. note:: + + onETL's Hive connection is actually SparkSession with access to `Hive Thrift Metastore `_ + and HDFS/S3. + All data motion is made using Spark. Hive Metastore is used only to store tables and partitions metadata. + + This connector does **NOT** require Hive server. It also does **NOT** use Hive JDBC connector. +``` + +## Version Compatibility + +- Hive Metastore version: + : - Officially declared: 0.12 - 3.1.3 (may require to add proper .jar file explicitly) + - Actually tested: 1.2.100, 2.3.10, 3.1.3 +- Spark versions: 2.3.x - 3.5.x +- Java versions: 8 - 20 + +See [official documentation](https://spark.apache.org/docs/latest/sql-data-sources-hive-tables.html). + +## Installing PySpark + +To use Hive connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Connecting to Hive Metastore + +```{eval-rst} +.. note:: + + If you're using managed Hadoop cluster, skip this step, as all Spark configs are should already present on the host. +``` + +Create `$SPARK_CONF_DIR/hive-site.xml` with Hive Metastore URL: + +```xml + + + + + hive.metastore.uris + thrift://metastore.host.name:9083 + + +``` + +Create `$SPARK_CONF_DIR/core-site.xml` with warehouse location ,e.g. HDFS IPC port of Hadoop namenode, or S3 bucket address & credentials: + +```{eval-rst} +.. tabs:: + + .. code-tab:: xml HDFS + + + + + + fs.defaultFS + hdfs://myhadoopcluster:9820 + + + + .. code-tab:: xml S3 + + + + + !-- See https://hadoop.apache.org/docs/current/hadoop-aws/tools/hadoop-aws/index.html#General_S3A_Client_configuration + + fs.defaultFS + s3a://mys3bucket/ + + + fs.s3a.bucket.mybucket.endpoint + http://s3.somain + + + fs.s3a.bucket.mybucket.connection.ssl.enabled + false + + + fs.s3a.bucket.mybucket.path.style.access + true + + + fs.s3a.bucket.mybucket.aws.credentials.provider + org.apache.hadoop.fs.s3a.SimpleAWSCredentialsProvider + + + fs.s3a.bucket.mybucket.access.key + some-user + + + fs.s3a.bucket.mybucket.secret.key + mysecrettoken + + +``` + +## Using Kerberos + +Some of Hadoop managed clusters use Kerberos authentication. In this case, you should call [kinit](https://web.mit.edu/kerberos/krb5-1.12/doc/user/user_commands/kinit.html) command +**BEFORE** starting Spark session to generate Kerberos ticket. See {ref}`install-kerberos`. + +Sometimes it is also required to pass keytab file to Spark config, allowing Spark executors to generate own Kerberos tickets: + +```{eval-rst} +.. tabs:: + + .. code-tab:: python Spark 3 + + SparkSession.builder + .option("spark.kerberos.access.hadoopFileSystems", "hdfs://namenode1.domain.com:9820,hdfs://namenode2.domain.com:9820") + .option("spark.kerberos.principal", "user") + .option("spark.kerberos.keytab", "/path/to/keytab") + .gerOrCreate() + + .. code-tab:: python Spark 2 + + SparkSession.builder + .option("spark.yarn.access.hadoopFileSystems", "hdfs://namenode1.domain.com:9820,hdfs://namenode2.domain.com:9820") + .option("spark.yarn.principal", "user") + .option("spark.yarn.keytab", "/path/to/keytab") + .gerOrCreate() +``` + +See [Spark security documentation](https://spark.apache.org/docs/latest/security.html#kerberos) +for more details. diff --git a/mddocs/docs/connection/db_connection/hive/read.md b/mddocs/docs/connection/db_connection/hive/read.md new file mode 100644 index 000000000..82433c988 --- /dev/null +++ b/mddocs/docs/connection/db_connection/hive/read.md @@ -0,0 +1,95 @@ +(hive-read)= + +# Reading from Hive using `DBReader` + +{obj}`DBReader ` supports {ref}`strategy` for incremental data reading, +but does not support custom queries, like `JOIN`. + +## Supported DBReader features + +- ✅︎ `columns` +- ✅︎ `where` +- ✅︎ `hwm`, supported strategies: +- - ✅︎ {ref}`snapshot-strategy` +- - ✅︎ {ref}`incremental-strategy` +- - ✅︎ {ref}`snapshot-batch-strategy` +- - ✅︎ {ref}`incremental-batch-strategy` +- ❌ `hint` (is not supported by Hive) +- ❌ `df_schema` +- ❌ `options` (only Spark config params are used) + +```{eval-rst} +.. warning:: + + Actually, ``columns``, ``where`` and ``hwm.expression`` should be written using `SparkSQL `_ syntax, + not HiveQL. +``` + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Hive +from onetl.db import DBReader + +hive = Hive(...) + +reader = DBReader( + connection=hive, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Hive +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +hive = Hive(...) + +reader = DBReader( + connection=hive, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="hive_hwm", expression="updated_dt"), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Use column-based write formats + +Prefer these write formats: +: - [ORC](https://spark.apache.org/docs/latest/sql-data-sources-orc.html) + - [Parquet](https://spark.apache.org/docs/latest/sql-data-sources-parquet.html) + - [Iceberg](https://iceberg.apache.org/spark-quickstart/) + - [Hudi](https://hudi.apache.org/docs/quick-start-guide/) + - [Delta](https://docs.delta.io/latest/quick-start.html#set-up-apache-spark-with-delta-lake) + +For colum-based write formats, each file contains separated sections there column data is stored. The file footer contains +location of each column section/group. Spark can use this information to load only sections required by specific query, e.g. only selected columns, +to drastically speed up the query. + +Another advantage is high compression ratio, e.g. 10x-100x in comparison to JSON or CSV. + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. +This drastically reduces the amount of data read by Spark, **if column-based file formats are used**. + +### Use partition columns in `where` clause + +Queries should include `WHERE` clause with filters on Hive partitioning columns. +This allows Spark to read only small set of files (*partition pruning*) instead of scanning the entire table, so this drastically increases performance. + +Supported operators are: `=`, `>`, `<` and `BETWEEN`, and only against some **static** value. diff --git a/mddocs/docs/connection/db_connection/hive/slots.md b/mddocs/docs/connection/db_connection/hive/slots.md new file mode 100644 index 000000000..8db1f7610 --- /dev/null +++ b/mddocs/docs/connection/db_connection/hive/slots.md @@ -0,0 +1,13 @@ +(hive-slots)= + +# Hive Slots + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.hive.slots +``` + +```{eval-rst} +.. autoclass:: HiveSlots + :members: normalize_cluster_name, get_known_clusters, get_current_cluster + :member-order: bysource +``` diff --git a/mddocs/docs/connection/db_connection/hive/sql.md b/mddocs/docs/connection/db_connection/hive/sql.md new file mode 100644 index 000000000..9b035f8ff --- /dev/null +++ b/mddocs/docs/connection/db_connection/hive/sql.md @@ -0,0 +1,79 @@ +(hive-sql)= + +# Reading from Hive using `Hive.sql` + +`Hive.sql` allows passing custom SQL query, but does not support incremental strategies. + +## Syntax support + +Only queries with the following syntax are supported: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +```{eval-rst} +.. warning:: + + Actually, query should be written using `SparkSQL `_ syntax, not HiveQL. +``` + +## Examples + +```python +from onetl.connection import Hive + +hive = Hive(...) +df = hive.sql( + """ + SELECT + id, + key, + CAST(value AS text) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """ +) +``` + +## Recommendations + +### Use column-based write formats + +Prefer these write formats: +: - [ORC](https://spark.apache.org/docs/latest/sql-data-sources-orc.html) + - [Parquet](https://spark.apache.org/docs/latest/sql-data-sources-parquet.html) + - [Iceberg](https://iceberg.apache.org/spark-quickstart/) + - [Hudi](https://hudi.apache.org/docs/quick-start-guide/) + - [Delta](https://docs.delta.io/latest/quick-start.html#set-up-apache-spark-with-delta-lake) + +For colum-based write formats, each file contains separated sections there column data is stored. The file footer contains +location of each column section/group. Spark can use this information to load only sections required by specific query, e.g. only selected columns, +to drastically speed up the query. + +Another advantage is high compression ratio, e.g. 10x-100x in comparison to JSON or CSV. + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This drastically reduces the amount of data read by Spark, **if column-based file formats are used**. + +### Use partition columns in `where` clause + +Queries should include `WHERE` clause with filters on Hive partitioning columns. +This allows Spark to read only small set of files (*partition pruning*) instead of scanning the entire table, so this drastically increases performance. + +Supported operators are: `=`, `>`, `<` and `BETWEEN`, and only against some **static** value. + +## Details + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.hive.connection +``` + +```{eval-rst} +.. automethod:: Hive.sql +``` diff --git a/mddocs/docs/connection/db_connection/hive/write.md b/mddocs/docs/connection/db_connection/hive/write.md new file mode 100644 index 000000000..8e6761146 --- /dev/null +++ b/mddocs/docs/connection/db_connection/hive/write.md @@ -0,0 +1,180 @@ +(hive-write)= + +# Writing to Hive using `DBWriter` + +For writing data to Hive, use {obj}`DBWriter `. + +## Examples + +```python +from onetl.connection import Hive +from onetl.db import DBWriter + +hive = Hive(...) + +df = ... # data is here + +# Create dataframe with specific number of Spark partitions. +# Use the Hive partitioning columns to group the data. Create max 20 files per Hive partition. +# Also sort the data by column which most data is correlated with (e.g. user_id), reducing files size. + +num_files_per_partition = 20 +partition_columns = ["country", "business_date"] +sort_columns = ["user_id"] +write_df = df.repartition( + num_files_per_partition, + *partition_columns, + *sort_columns, +).sortWithinPartitions(*partition_columns, *sort_columns) + +writer = DBWriter( + connection=hive, + target="schema.table", + options=Hive.WriteOptions( + if_exists="append", + # Hive partitioning columns. + partitionBy=partition_columns, + ), +) + +writer.run(write_df) +``` + +## Recommendations + +### Use column-based write formats + +Prefer these write formats: +: - [ORC](https://spark.apache.org/docs/latest/sql-data-sources-orc.html) (**default**) + - [Parquet](https://spark.apache.org/docs/latest/sql-data-sources-parquet.html) + - [Iceberg](https://iceberg.apache.org/spark-quickstart/) + - [Hudi](https://hudi.apache.org/docs/quick-start-guide/) + - [Delta](https://docs.delta.io/latest/quick-start.html#set-up-apache-spark-with-delta-lake) + +```{eval-rst} +.. warning:: + When using ``DBWriter``, the default spark data format configured in ``spark.sql.sources.default`` is ignored, as ``Hive.WriteOptions(format=...)`` default value is explicitly set to ``orc``. +``` + +For column-based write formats, each file contains separated sections where column data is stored. The file footer contains +location of each column section/group. Spark can use this information to load only sections required by specific query, e.g. only selected columns, +to drastically speed up the query. + +Another advantage is high compression ratio, e.g. 10x-100x in comparison to JSON or CSV. + +### Use partitioning + +#### How does it work + +Hive support splitting data to partitions, which are different directories in filesystem with names like `some_col=value1/another_col=value2`. + +For example, dataframe with content like this: + +| country: string | business_date: date | user_id: int | bytes: long | +| --------------- | ------------------- | ------------ | ----------- | +| RU | 2024-01-01 | 1234 | 25325253525 | +| RU | 2024-01-01 | 2345 | 23234535243 | +| RU | 2024-01-02 | 1234 | 62346634564 | +| US | 2024-01-01 | 5678 | 4252345354 | +| US | 2024-01-02 | 5678 | 5474575745 | +| US | 2024-01-03 | 5678 | 3464574567 | + +With `partitionBy=["country", "business_dt"]` data will be stored as files in the following subfolders: +: - `/country=RU/business_date=2024-01-01/` + - `/country=RU/business_date=2024-01-02/` + - `/country=US/business_date=2024-01-01/` + - `/country=US/business_date=2024-01-02/` + - `/country=US/business_date=2024-01-03/` + +A separated subdirectory is created for each distinct combination of column values in the dataframe. + +Please do not confuse Spark dataframe partitions (a.k.a batches of data handled by Spark executors, usually in parallel) +and Hive partitioning (store data in different subdirectories). +Number of Spark dataframe partitions is correlated the number of files created in **each** Hive partition. +For example, Spark dataframe with 10 partitions and 5 distinct values of Hive partition columns will be saved as 5 subfolders with 10 files each = 50 files in total. +Without Hive partitioning, all the files are placed into one flat directory. + +#### But why? + +Queries which has `WHERE` clause with filters on Hive partitioning columns, like `WHERE country = 'RU' AND business_date='2024-01-01'`, will +read only files from this exact partitions, like `/country=RU/business_date=2024-01-01/`, and skip files from other partitions. + +This drastically increases performance and reduces the amount of memory used by Spark. +Consider using Hive partitioning in all tables. + +#### Which columns should I use? + +Usually Hive partitioning columns are based on event date or location, like `country: string`, `business_date: date`, `run_date: date` and so on. + +**Partition columns should contain data with low cardinality.** +Dates, small integers, strings with low number of possible values are OK. +But timestamp, float, decimals, longs (like user id), strings with lots oj unique values (like user name or email) should **NOT** be used as Hive partitioning columns. +Unlike some other databases, range and hash-based partitions are not supported. + +Partition column should be a part of a dataframe. If you want to partition values by date component of `business_dt: timestamp` column, +add a new column to dataframe like this: `df.withColumn("business_date", date(df.business_dt))`. + +### Use compression + +Using compression algorithms like `snappy`, `lz4` or `zstd` can reduce the size of files (up to 10x). + +### Prefer creating large files + +Storing millions of small files is not that HDFS and S3 are designed for. Minimal file size should be at least 10Mb, but usually it is like 128Mb+ or 256Mb+ (HDFS block size). +**NEVER** create files with few Kbytes in size. + +Number of files can be different in different cases. +On one hand, Spark Adaptive Query Execution (AQE) can merge small Spark dataframe partitions into one larger. +On the other hand, dataframes with skewed data can produce a larger number of files than expected. + +To create small amount of large files, you can reduce number of Spark dataframe partitions. +Use [df.repartition(N, columns...)](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.DataFrame.repartition.html) function, +like this: `df.repartition(20, "col1", "col2")`. +This creates new Spark dataframe with partitions using `hash(df.col1 + df.col2) mod 20` expression, avoiding data skew. + +Note: larger dataframe partitions requires more resources (CPU, RAM) on Spark executor. The exact number of partitions +should be determined empirically, as it depends on the amount of data and available resources. + +### Sort data before writing + +Dataframe with sorted content: + +| country: string | business_date: date | user_id: int | business_dt: timestamp | bytes: long | +| --------------- | ------------------- | ------------ | ----------------------- | ----------- | +| RU | 2024-01-01 | 1234 | 2024-01-01T11:22:33.456 | 25325253525 | +| RU | 2024-01-01 | 1234 | 2024-01-01T12:23:44.567 | 25325253525 | +| RU | 2024-01-02 | 1234 | 2024-01-01T13:25:56.789 | 34335645635 | +| US | 2024-01-01 | 2345 | 2024-01-01T10:00:00.000 | 12341 | +| US | 2024-01-02 | 2345 | 2024-01-01T15:11:22.345 | 13435 | +| US | 2024-01-03 | 2345 | 2024-01-01T20:22:33.567 | 14564 | + +Has a much better compression rate than unsorted one, e.g. 2x or even higher: + +| country: string | business_date: date | user_id: int | business_dt: timestamp | bytes: long | +| --------------- | ------------------- | ------------ | ----------------------- | ----------- | +| RU | 2024-01-01 | 1234 | 2024-01-01T11:22:33.456 | 25325253525 | +| RU | 2024-01-01 | 6345 | 2024-12-01T23:03:44.567 | 25365 | +| RU | 2024-01-02 | 5234 | 2024-07-01T06:10:56.789 | 45643456747 | +| US | 2024-01-01 | 4582 | 2024-04-01T17:59:00.000 | 362546475 | +| US | 2024-01-02 | 2345 | 2024-09-01T04:24:22.345 | 3235 | +| US | 2024-01-03 | 3575 | 2024-03-01T21:37:33.567 | 346345764 | + +Choosing columns to sort data by is really depends on the data. If data is correlated with some specific +column, like in example above the amount of traffic is correlated with both `user_id` and `timestamp`, +use `df.sortWithinPartitions("user_id", "timestamp")` before writing the data. + +If `df.repartition(N, repartition_columns...)` is used in combination with `df.sortWithinPartitions(sort_columns...)`, +then `sort_columns` should start with `repartition_columns` or be equal to it. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.hive.options +``` + +```{eval-rst} +.. autopydantic_model:: HiveWriteOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/index.md b/mddocs/docs/connection/db_connection/index.md new file mode 100644 index 000000000..4977cf5d5 --- /dev/null +++ b/mddocs/docs/connection/db_connection/index.md @@ -0,0 +1,19 @@ +(db-connections)= + +# DB Connections + +```{toctree} +:caption: DB Connections +:maxdepth: 1 + +Clickhouse +Greenplum +Kafka +Hive +MongoDB +MSSQL +MySQL +Oracle +Postgres +Teradata +``` diff --git a/mddocs/docs/connection/db_connection/kafka/auth.md b/mddocs/docs/connection/db_connection/kafka/auth.md new file mode 100644 index 000000000..7d112d59a --- /dev/null +++ b/mddocs/docs/connection/db_connection/kafka/auth.md @@ -0,0 +1,13 @@ +(kafka-auth)= + +# Kafka Auth + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.kafka.kafka_auth +``` + +```{eval-rst} +.. autoclass:: KafkaAuth + :members: get_options, cleanup + :member-order: bysource +``` diff --git a/mddocs/docs/connection/db_connection/kafka/basic_auth.md b/mddocs/docs/connection/db_connection/kafka/basic_auth.md new file mode 100644 index 000000000..cb3cded81 --- /dev/null +++ b/mddocs/docs/connection/db_connection/kafka/basic_auth.md @@ -0,0 +1,14 @@ +(kafka-basic-auth)= + +# Kafka BasicAuth + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.kafka.kafka_basic_auth +``` + +```{eval-rst} +.. autopydantic_model:: KafkaBasicAuth + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/kafka/connection.md b/mddocs/docs/connection/db_connection/kafka/connection.md new file mode 100644 index 000000000..169b5b701 --- /dev/null +++ b/mddocs/docs/connection/db_connection/kafka/connection.md @@ -0,0 +1,12 @@ +(kafka-connection)= + +# Kafka Connection + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.kafka.connection +``` + +```{eval-rst} +.. autoclass:: Kafka + :members: get_packages, get_exclude_packages, check, close +``` diff --git a/mddocs/docs/connection/db_connection/kafka/index.md b/mddocs/docs/connection/db_connection/kafka/index.md new file mode 100644 index 000000000..903feea96 --- /dev/null +++ b/mddocs/docs/connection/db_connection/kafka/index.md @@ -0,0 +1,46 @@ +(kafka)= + +# Kafka + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +connection +troubleshooting +``` + +```{toctree} +:caption: Protocols +:maxdepth: 1 + +plaintext_protocol +ssl_protocol +``` + +```{toctree} +:caption: Auth methods +:maxdepth: 1 + +basic_auth +kerberos_auth +scram_auth +``` + +```{toctree} +:caption: Operations +:maxdepth: 1 + +read +write +``` + +```{toctree} +:caption: For developers +:maxdepth: 1 + +auth +protocol +slots +``` diff --git a/mddocs/docs/connection/db_connection/kafka/kerberos_auth.md b/mddocs/docs/connection/db_connection/kafka/kerberos_auth.md new file mode 100644 index 000000000..637c9e16e --- /dev/null +++ b/mddocs/docs/connection/db_connection/kafka/kerberos_auth.md @@ -0,0 +1,14 @@ +(kafka-kerberos-auth)= + +# Kafka KerberosAuth + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.kafka.kafka_kerberos_auth +``` + +```{eval-rst} +.. autopydantic_model:: KafkaKerberosAuth + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/kafka/plaintext_protocol.md b/mddocs/docs/connection/db_connection/kafka/plaintext_protocol.md new file mode 100644 index 000000000..5bc9ae035 --- /dev/null +++ b/mddocs/docs/connection/db_connection/kafka/plaintext_protocol.md @@ -0,0 +1,14 @@ +(kafka-plaintext-protocol)= + +# Kafka PlaintextProtocol + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.kafka.kafka_plaintext_protocol +``` + +```{eval-rst} +.. autopydantic_model:: KafkaPlaintextProtocol + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/kafka/prerequisites.md b/mddocs/docs/connection/db_connection/kafka/prerequisites.md new file mode 100644 index 000000000..3020648e4 --- /dev/null +++ b/mddocs/docs/connection/db_connection/kafka/prerequisites.md @@ -0,0 +1,65 @@ +(kafka-prerequisites)= + +# Prerequisites + +## Version Compatibility + +- Kafka server versions: + : - Officially declared: 0.10 or higher + - Actually tested: 3.2.3, 3.9.0 (only Kafka 3.x supports message headers) +- Spark versions: 2.4.x - 3.5.x +- Java versions: 8 - 17 + +See [official documentation](https://spark.apache.org/docs/latest/structured-streaming-kafka-integration.html). + +## Installing PySpark + +To use Kafka connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Connecting to Kafka + +### Connection address + +Kafka is a distributed service, and usually has a list of brokers you can connect to (unlike other connectors, there only one host+port can be set). +Please contact your Kafka administrator to get addresses of these brokers, as there are no defaults. + +Also Kafka has a feature called *advertised listeners* - client connects to one broker, and received list of other brokers in the clusters. +So you don't have to pass all brokers to `addresses`, it can be some subset. Other broker addresses will be fetched directly from the cluster. + +### Connection protocol + +Kafka can support different connection protocols. List of currently supported protocols: +: - {obj}`PLAINTEXT ` (not secure) + - {obj}`SSL ` (secure, recommended) + +Note that specific port can listen for only one of these protocols, so it is important to set +proper port number + protocol combination. + +### Authentication mechanism + +Kafka can support different authentication mechanism (also known as [SASL](https://en.wikipedia.org/wiki/Simple_Authentication_and_Security_Layer)). + +List of currently supported mechanisms: +: - {obj}`PLAIN `. To no confuse this with `PLAINTEXT` connection protocol, onETL uses name `BasicAuth`. + - {obj}`GSSAPI `. To simplify naming, onETL uses name `KerberosAuth`. + - {obj}`SCRAM-SHA-256 or SCRAM-SHA-512 ` (recommended). + +Different mechanisms use different types of credentials (login + password, keytab file, and so on). + +Note that connection protocol and auth mechanism are set in pairs: +: - If you see `SASL_PLAINTEXT` this means `PLAINTEXT` connection protocol + some auth mechanism. + - If you see `SASL_SSL` this means `SSL` connection protocol + some auth mechanism. + - If you see just `PLAINTEXT` or `SSL` (**no** `SASL`), this means that authentication is disabled (anonymous access). + +Please contact your Kafka administrator to get details about enabled auth mechanism in a specific Kafka instance. + +### Required grants + +Ask your Kafka administrator to set following grants for a user, *if Kafka instance uses ACL*: +: - `Describe` + `Read` for reading data from Kafka (Consumer). + - `Describe` + `Write` for writing data from Kafka (Producer). + +More details can be found in [documentation](https://kafka.apache.org/documentation/#operations_in_kafka). diff --git a/mddocs/docs/connection/db_connection/kafka/protocol.md b/mddocs/docs/connection/db_connection/kafka/protocol.md new file mode 100644 index 000000000..b9278d009 --- /dev/null +++ b/mddocs/docs/connection/db_connection/kafka/protocol.md @@ -0,0 +1,13 @@ +(kafka-protocol)= + +# Kafka Protocol + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.kafka.kafka_protocol +``` + +```{eval-rst} +.. autoclass:: KafkaProtocol + :members: get_options, cleanup + :member-order: bysource +``` diff --git a/mddocs/docs/connection/db_connection/kafka/read.md b/mddocs/docs/connection/db_connection/kafka/read.md new file mode 100644 index 000000000..be707f260 --- /dev/null +++ b/mddocs/docs/connection/db_connection/kafka/read.md @@ -0,0 +1,137 @@ +(kafka-read)= + +# Reading from Kafka + +Data can be read from Kafka to Spark using {obj}`DBReader `. +It also supports {ref}`strategy` for incremental data reading. + +## Supported DBReader features + +- ❌ `columns` (is not supported by Kafka) +- ❌ `where` (is not supported by Kafka) +- ✅︎ `hwm`, supported strategies: +- - ✅︎ {ref}`snapshot-strategy` +- - ✅︎ {ref}`incremental-strategy` +- - ❌ {ref}`snapshot-batch-strategy` +- - ❌ {ref}`incremental-batch-strategy` +- ❌ `hint` (is not supported by Kafka) +- ❌ `df_schema` (see note below) +- ✅︎ `options` (see {obj}`Kafka.ReadOptions `) + +## Dataframe schema + +Unlike other DB connections, Kafka does not have concept of columns. +All the topics messages have the same set of fields, see structure below: + +```text +root +|-- key: binary (nullable = true) +|-- value: binary (nullable = true) +|-- topic: string (nullable = false) +|-- partition: integer (nullable = false) +|-- offset: integer (nullable = false) +|-- timestamp: timestamp (nullable = false) +|-- timestampType: integer (nullable = false) +|-- headers: struct (nullable = true) + |-- key: string (nullable = false) + |-- value: binary (nullable = true) +``` + +`headers` field is present in the dataframe only if `Kafka.ReadOptions(include_headers=True)` is passed (compatibility with Kafka 1.x). + +## Value deserialization + +To read `value` or `key` of other type than bytes (e.g. struct or integer), users have to deserialize values manually. + +This could be done using following methods: +: - {obj}`Avro.parse_column ` + - {obj}`JSON.parse_column ` + - {obj}`CSV.parse_column ` + - {obj}`XML.parse_column ` + +## Examples + +Snapshot strategy, `value` is Avro binary data: + +```python +from onetl.connection import Kafka +from onetl.db import DBReader, DBWriter +from onetl.file.format import Avro +from pyspark.sql.functions import decode + +# read all topic data from Kafka +kafka = Kafka(...) +reader = DBReader(connection=kafka, source="avro_topic") +read_df = reader.run() + +# parse Avro format to Spark struct +avro = Avro( + schema_dict={ + "type": "record", + "name": "Person", + "fields": [ + {"name": "name", "type": "string"}, + {"name": "age", "type": "int"}, + ], + } +) +deserialized_df = read_df.select( + # cast binary key to string + decode("key", "UTF-8").alias("key"), + avro.parse_column("value"), +) +``` + +Incremental strategy, `value` is JSON string: + +```{eval-rst} +.. note:: + + Currently Kafka connector does support only HWMs based on ``offset`` field. Other fields, like ``timestamp``, are not yet supported. +``` + +```python +from onetl.connection import Kafka +from onetl.db import DBReader, DBWriter +from onetl.file.format import JSON +from pyspark.sql.functions import decode + +kafka = Kafka(...) + +# read only new data from Kafka topic +reader = DBReader( + connection=kafka, + source="topic_name", + hwm=DBReader.AutoDetectHWM(name="kafka_hwm", expression="offset"), +) + +with IncrementalStrategy(): + read_df = reader.run() + +# parse JSON format to Spark struct +json = JSON() +schema = StructType( + [ + StructField("name", StringType(), nullable=True), + StructField("age", IntegerType(), nullable=True), + ], +) +deserialized_df = read_df.select( + # cast binary key to string + decode("key", "UTF-8").alias("key"), + json.parse_column("value", json), +) +``` + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.kafka.options +``` + +```{eval-rst} +.. autopydantic_model:: KafkaReadOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/kafka/scram_auth.md b/mddocs/docs/connection/db_connection/kafka/scram_auth.md new file mode 100644 index 000000000..5a27a46ea --- /dev/null +++ b/mddocs/docs/connection/db_connection/kafka/scram_auth.md @@ -0,0 +1,14 @@ +(kafka-scram-auth)= + +# Kafka ScramAuth + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.kafka.kafka_scram_auth +``` + +```{eval-rst} +.. autopydantic_model:: KafkaScramAuth + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/kafka/slots.md b/mddocs/docs/connection/db_connection/kafka/slots.md new file mode 100644 index 000000000..0c58413fb --- /dev/null +++ b/mddocs/docs/connection/db_connection/kafka/slots.md @@ -0,0 +1,13 @@ +(kafka-slots)= + +# Kafka Slots + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.kafka.slots +``` + +```{eval-rst} +.. autoclass:: KafkaSlots + :members: normalize_cluster_name, get_known_clusters, normalize_address, get_cluster_addresses + :member-order: bysource +``` diff --git a/mddocs/docs/connection/db_connection/kafka/ssl_protocol.md b/mddocs/docs/connection/db_connection/kafka/ssl_protocol.md new file mode 100644 index 000000000..6e1a7641d --- /dev/null +++ b/mddocs/docs/connection/db_connection/kafka/ssl_protocol.md @@ -0,0 +1,14 @@ +(kafka-ssl-protocol)= + +# Kafka SSLProtocol + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.kafka.kafka_ssl_protocol +``` + +```{eval-rst} +.. autopydantic_model:: KafkaSSLProtocol + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/kafka/troubleshooting.md b/mddocs/docs/connection/db_connection/kafka/troubleshooting.md new file mode 100644 index 000000000..7a3536fbf --- /dev/null +++ b/mddocs/docs/connection/db_connection/kafka/troubleshooting.md @@ -0,0 +1,13 @@ +(kafka-troubleshooting)= + +# Kafka Troubleshooting + +```{eval-rst} +.. note:: + + General guide: :ref:`troubleshooting`. +``` + +## Cannot connect using `SSL` protocol + +Please check that certificate files are not Base-64 encoded. diff --git a/mddocs/docs/connection/db_connection/kafka/write.md b/mddocs/docs/connection/db_connection/kafka/write.md new file mode 100644 index 000000000..cb3e3019a --- /dev/null +++ b/mddocs/docs/connection/db_connection/kafka/write.md @@ -0,0 +1,75 @@ +(kafka-write)= + +# Writing to Kafka + +For writing data to Kafka, use {obj}`DBWriter ` with specific options (see below). + +## Dataframe schema + +Unlike other DB connections, Kafka does not have concept of columns. +All the topics messages have the same set of fields. Only some of them can be written: + +```text +root +|-- key: binary (nullable = true) +|-- value: binary (nullable = true) +|-- headers: struct (nullable = true) + |-- key: string (nullable = false) + |-- value: binary (nullable = true) +``` + +`headers` can be passed only with `Kafka.WriteOptions(include_headers=True)` (compatibility with Kafka 1.x). + +Field `topic` should not be present in the dataframe, as it is passed to `DBWriter(target=...)`. + +Other fields, like `partition`, `offset`, `timestamp` are set by Kafka, and cannot be passed explicitly. + +## Value serialization + +To write `value` or `key` of other type than bytes (e.g. struct or integer), users have to serialize values manually. + +This could be done using following methods: +: - {obj}`Avro.serialize_column ` + - {obj}`JSON.serialize_column ` + - {obj}`CSV.serialize_column ` + +## Examples + +Convert `value` to JSON string, and write to Kafka: + +```python +from onetl.connection import Kafka +from onetl.db import DBWriter +from onetl.file.format import JSON + +df = ... # original data is here + +# serialize struct data as JSON +json = JSON() +write_df = df.select( + df.key, + json.serialize_column(df.value), +) + +# write data to Kafka +kafka = Kafka(...) + +writer = DBWriter( + connection=kafka, + target="topic_name", +) +writer.run(write_df) +``` + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.kafka.options +``` + +```{eval-rst} +.. autopydantic_model:: KafkaWriteOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/mongodb/connection.md b/mddocs/docs/connection/db_connection/mongodb/connection.md new file mode 100644 index 000000000..b81c56ffc --- /dev/null +++ b/mddocs/docs/connection/db_connection/mongodb/connection.md @@ -0,0 +1,13 @@ +(mongodb-connection)= + +# MongoDB Connection + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mongodb.connection +``` + +```{eval-rst} +.. autoclass:: MongoDB + :members: get_packages, check + :member-order: bysource +``` diff --git a/mddocs/docs/connection/db_connection/mongodb/index.md b/mddocs/docs/connection/db_connection/mongodb/index.md new file mode 100644 index 000000000..fb9656117 --- /dev/null +++ b/mddocs/docs/connection/db_connection/mongodb/index.md @@ -0,0 +1,27 @@ +(mongodb)= + +# MongoDB + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +connection +``` + +```{toctree} +:caption: Operations +:maxdepth: 1 + +read +pipeline +write +``` + +```{toctree} +:caption: Troubleshooting +:maxdepth: 1 + +types +``` diff --git a/mddocs/docs/connection/db_connection/mongodb/pipeline.md b/mddocs/docs/connection/db_connection/mongodb/pipeline.md new file mode 100644 index 000000000..2cc30458c --- /dev/null +++ b/mddocs/docs/connection/db_connection/mongodb/pipeline.md @@ -0,0 +1,41 @@ +(mongodb-sql)= + +# Reading from MongoDB using `MongoDB.pipeline` + +{obj}`MongoDB.sql ` allows passing custom pipeline, +but does not support incremental strategies. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`mongodb-types` +``` + +## Recommendations + +### Pay attention to `pipeline` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `mongodb.pipeline(..., pipeline={"$match": {"column": {"$eq": "value"}}})` value. +This both reduces the amount of data send from MongoDB to Spark, and may also improve performance of the query. +Especially if there are indexes for columns used in `pipeline` value. + +## References + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mongodb.connection +``` + +```{eval-rst} +.. automethod:: MongoDB.pipeline +``` + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mongodb.options +``` + +```{eval-rst} +.. autopydantic_model:: MongoDBPipelineOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/mongodb/prerequisites.md b/mddocs/docs/connection/db_connection/mongodb/prerequisites.md new file mode 100644 index 000000000..f491ca10e --- /dev/null +++ b/mddocs/docs/connection/db_connection/mongodb/prerequisites.md @@ -0,0 +1,72 @@ +(mongodb-prerequisites)= + +# Prerequisites + +## Version Compatibility + +- MongoDB server versions: + : - Officially declared: 4.0 or higher + - Actually tested: 4.0.0, 8.0.4 +- Spark versions: 3.2.x - 3.5.x +- Java versions: 8 - 20 + +See [official documentation](https://www.mongodb.com/docs/spark-connector/). + +## Installing PySpark + +To use MongoDB connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Connecting to MongoDB + +### Connection host + +It is possible to connect to MongoDB host by using either DNS name of host or it's IP address. + +It is also possible to connect to MongoDB shared cluster: + +```python +mongo = MongoDB( + host="master.host.or.ip", + user="user", + password="*****", + database="target_database", + spark=spark, + extra={ + # read data from secondary cluster node, switch to primary if not available + "readPreference": "secondaryPreferred", + }, +) +``` + +Supported `readPreference` values are described in [official documentation](https://www.mongodb.com/docs/manual/core/read-preference/). + +### Connection port + +Connection is usually performed to port `27017`. Port may differ for different MongoDB instances. +Please ask your MongoDB administrator to provide required information. + +### Required grants + +Ask your MongoDB cluster administrator to set following grants for a user, +used for creating a connection: + +```{eval-rst} +.. tabs:: + + .. code-tab:: js Read + Write + + // allow writing data to specific database + db.grantRolesToUser("username", [{db: "somedb", role: "readWrite"}]) + + .. code-tab:: js Read only + + // allow reading data from specific database + db.grantRolesToUser("username", [{db: "somedb", role: "read"}]) +``` + +See: +: - [db.grantRolesToUser documentation](https://www.mongodb.com/docs/manual/reference/method/db.grantRolesToUser) + - [MongoDB builtin roles](https://www.mongodb.com/docs/manual/reference/built-in-roles) diff --git a/mddocs/docs/connection/db_connection/mongodb/read.md b/mddocs/docs/connection/db_connection/mongodb/read.md new file mode 100644 index 000000000..15cf34bb1 --- /dev/null +++ b/mddocs/docs/connection/db_connection/mongodb/read.md @@ -0,0 +1,141 @@ +(mongodb-read)= + +# Reading from MongoDB using `DBReader` + +{obj}`DBReader ` supports {ref}`strategy` for incremental data reading, +but does not support custom pipelines, e.g. aggregation. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`mongodb-types` +``` + +## Supported DBReader features + +- ❌ `columns` (for now, all document fields are read) +- ✅︎ `where` (passed to `{"$match": ...}` aggregation pipeline) +- ✅︎ `hwm`, supported strategies: +- - ✅︎ {ref}`snapshot-strategy` +- - ✅︎ {ref}`incremental-strategy` +- - ✅︎ {ref}`snapshot-batch-strategy` +- - ✅︎ {ref}`incremental-batch-strategy` +- - Note that `expression` field of HWM can only be a field name, not a custom expression +- ✅︎ `hint` (see [official documentation](https://www.mongodb.com/docs/v5.0/reference/operator/meta/hint/)) +- ✅︎ `df_schema` (mandatory) +- ✅︎ `options` (see {obj}`MongoDB.ReadOptions `) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import MongoDB +from onetl.db import DBReader + +from pyspark.sql.types import ( + StructType, + StructField, + IntegerType, + StringType, + TimestampType, +) + +mongodb = MongoDB(...) + +# mandatory +df_schema = StructType( + [ + StructField("_id", StringType()), + StructField("some", StringType()), + StructField( + "field", + StructType( + [ + StructField("nested", IntegerType()), + ], + ), + ), + StructField("updated_dt", TimestampType()), + ] +) + +reader = DBReader( + connection=mongodb, + source="some_collection", + df_schema=df_schema, + where={"field": {"$eq": 123}}, + hint={"field": 1}, + options=MongoDBReadOptions(batchSize=10000), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import MongoDB +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +from pyspark.sql.types import ( + StructType, + StructField, + IntegerType, + StringType, + TimestampType, +) + +mongodb = MongoDB(...) + +# mandatory +df_schema = StructType( + [ + StructField("_id", StringType()), + StructField("some", StringType()), + StructField( + "field", + StructType( + [ + StructField("nested", IntegerType()), + ], + ), + ), + StructField("updated_dt", TimestampType()), + ] +) + +reader = DBReader( + connection=mongodb, + source="some_collection", + df_schema=df_schema, + where={"field": {"$eq": 123}}, + hint={"field": 1}, + hwm=DBReader.AutoDetectHWM(name="mongodb_hwm", expression="updated_dt"), + options=MongoDBReadOptions(batchSize=10000), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where={"column": {"$eq": "value"}})` clause. +This both reduces the amount of data send from MongoDB to Spark, and may also improve performance of the query. +Especially if there are indexes for columns used in `where` clause. + +## Read options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mongodb.options +``` + +```{eval-rst} +.. autopydantic_model:: MongoDBReadOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/mongodb/types.md b/mddocs/docs/connection/db_connection/mongodb/types.md new file mode 100644 index 000000000..5c6c506d0 --- /dev/null +++ b/mddocs/docs/connection/db_connection/mongodb/types.md @@ -0,0 +1,269 @@ +(mongodb-types)= + +# MongoDB \<-> Spark type mapping + +```{eval-rst} +.. note:: + + The results below are valid for Spark 3.5.5, and may differ on other Spark versions. +``` + +## Type detection & casting + +Spark's DataFrames always have a `schema` which is a list of fields with corresponding Spark types. All operations on a field are performed using field type. + +MongoDB is, by design, \_\_schemaless\_\_. So there are 2 ways how this can be handled: + +- User provides DataFrame schema explicitly: + + ```{eval-rst} + .. dropdown:: See example + + .. code-block:: python + + from onetl.connection import MongoDB + from onetl.db import DBReader + + from pyspark.sql.types import ( + StructType, + StructField, + IntegerType, + StringType, + TimestampType, + ) + + mongodb = MongoDB(...) + + df_schema = StructType( + [ + StructField("_id", StringType()), + StructField("some", StringType()), + StructField( + "field", + StructType( + [ + StructField("nested", IntegerType()), + ] + ), + ), + ] + ) + + reader = DBReader( + connection=mongodb, + source="some_collection", + df_schema=df_schema, + ) + df = reader.run() + + # or + + df = mongodb.pipeline( + collection="some_collection", + df_schema=df_schema, + ) + ``` + +- Rely on MongoDB connector schema infer: + + ```python + df = mongodb.pipeline(collection="some_collection") + ``` + + In this case MongoDB connector read a sample of collection documents, and build DataFrame schema based on document fields and values. + +It is highly recommended to pass `df_schema` explicitly, to avoid type conversion issues. + +### References + +Here you can find source code with type conversions: + +- [MongoDB -> Spark](https://github.com/mongodb/mongo-spark/blob/r10.4.1/src/main/java/com/mongodb/spark/sql/connector/schema/InferSchema.java#L214-L260) +- [Spark -> MongoDB](https://github.com/mongodb/mongo-spark/blob/r10.4.1/src/main/java/com/mongodb/spark/sql/connector/schema/RowToBsonDocumentConverter.java#L157-L260) + +## Supported types + +See [official documentation](https://www.mongodb.com/docs/manual/reference/bson-types/) + +### Numeric types + +```{eval-rst} ++---------------------+-----------------------------+----------------------+ +| MongoDB type (read) | Spark type | MongoDB type (write) | ++=====================+=============================+======================+ +| ``Decimal128`` | ``DecimalType(P=34, S=32)`` | ``Decimal128`` | ++---------------------+-----------------------------+----------------------+ +| ``-`` | ``FloatType()`` | ``Double`` | ++---------------------+-----------------------------+ | +| ``Double`` | ``DoubleType()`` | | ++---------------------+-----------------------------+----------------------+ +| ``-`` | ``ByteType()`` | ``Int32`` | ++---------------------+-----------------------------+ | +| ``-`` | ``ShortType()`` | | ++---------------------+-----------------------------+ | +| ``Int32`` | ``IntegerType()`` | | ++---------------------+-----------------------------+----------------------+ +| ``Int64`` | ``LongType()`` | ``Int64`` | ++---------------------+-----------------------------+----------------------+ +``` + +### Temporal types + +```{eval-rst} ++------------------------+-----------------------------------+-------------------------+ +| MongoDB type (read) | Spark type | MongoDB type (write) | ++========================+===================================+=========================+ +| ``-`` | ``DateType()``, days | ``Date``, milliseconds | ++------------------------+-----------------------------------+-------------------------+ +| ``Date``, milliseconds | ``TimestampType()``, microseconds | ``Date``, milliseconds, | +| | | **precision loss** [2]_ | ++------------------------+-----------------------------------+-------------------------+ +| ``Timestamp``, seconds | ``TimestampType()``, microseconds | ``Date``, milliseconds | ++------------------------+-----------------------------------+-------------------------+ +| ``-`` | ``TimestampNTZType()`` | unsupported | ++------------------------+-----------------------------------+ | +| ``-`` | ``DayTimeIntervalType()`` | | ++------------------------+-----------------------------------+-------------------------+ +``` + +```{eval-rst} +.. warning:: + + Note that types in MongoDB and Spark have different value ranges: + + +---------------+--------------------------------+--------------------------------+---------------------+--------------------------------+--------------------------------+ + | MongoDB type | Min value | Max value | Spark type | Min value | Max value | + +===============+================================+================================+=====================+================================+================================+ + | ``Date`` | -290 million years | 290 million years | ``TimestampType()`` | ``0001-01-01 00:00:00.000000`` | ``9999-12-31 23:59:59.999999`` | + +---------------+--------------------------------+--------------------------------+ | | | + | ``Timestamp`` | ``1970-01-01 00:00:00`` | ``2106-02-07 09:28:16`` | | | | + +---------------+--------------------------------+--------------------------------+---------------------+--------------------------------+--------------------------------+ + + So not all values can be read from MongoDB to Spark, and can written from Spark DataFrame to MongoDB. + + References: + * `MongoDB Date type documentation `_ + * `MongoDB Timestamp documentation `_ + * `Spark DateType documentation `_ + * `Spark TimestampType documentation `_ +``` + +[^footnote-1]: MongoDB `Date` type has precision up to milliseconds (`23:59:59.999`). + Inserting data with microsecond precision (`23:59:59.999999`) + will lead to **throwing away microseconds**. + +### String types + +Note: fields of deprecated MongoDB type `Symbol` are excluded during read. + +```{eval-rst} ++---------------------+------------------+----------------------+ +| MongoDB type (read) | Spark type | MongoDB type (write) | ++=====================+==================+======================+ +| ``String`` | ``StringType()`` | ``String`` | ++---------------------+ | | +| ``Code`` | | | ++---------------------+ | | +| ``RegExp`` | | | ++---------------------+------------------+----------------------+ +``` + +### Binary types + +| MongoDB type (read) | Spark type | MongoDB type (write) | +| ------------------- | --------------- | -------------------- | +| `Boolean` | `BooleanType()` | `Boolean` | +| `Binary` | `BinaryType()` | `Binary` | + +### Struct types + +```{eval-rst} ++---------------------+-----------------------+----------------------+ +| MongoDB type (read) | Spark type | MongoDB type (write) | ++=====================+=======================+======================+ +| ``Array[T]`` | ``ArrayType(T)`` | ``Array[T]`` | ++---------------------+-----------------------+----------------------+ +| ``Object[...]`` | ``StructType([...])`` | ``Object[...]`` | ++---------------------+-----------------------+ | +| ``-`` | ``MapType(...)`` | | ++---------------------+-----------------------+----------------------+ +``` + +### Special types + +```{eval-rst} ++---------------------+---------------------------------------------------------+---------------------------------------+ +| MongoDB type (read) | Spark type | MongoDB type (write) | ++=====================+=========================================================+=======================================+ +| ``ObjectId`` | ``StringType()`` | ``String`` | ++---------------------+ | | +| ``MaxKey`` | | | ++---------------------+ | | +| ``MinKey`` | | | ++---------------------+---------------------------------------------------------+---------------------------------------+ +| ``Null`` | ``NullType()`` | ``Null`` | ++---------------------+ | | +| ``Undefined`` | | | ++---------------------+---------------------------------------------------------+---------------------------------------+ +| ``DBRef`` | ``StructType([$ref: StringType(), $id: StringType()])`` | ``Object[$ref: String, $id: String]`` | ++---------------------+---------------------------------------------------------+---------------------------------------+ +``` + +## Explicit type cast + +### `DBReader` + +Currently it is not possible to cast field types using `DBReader`. But this can be done using `MongoDB.pipeline`. + +### `MongoDB.pipeline` + +You can use `$project` aggregation to cast field types: + +```python +from pyspark.sql.types import IntegerType, StructField, StructType + +from onetl.connection import MongoDB +from onetl.db import DBReader + +mongodb = MongoDB(...) + +df = mongodb.pipeline( + collection="my_collection", + pipeline=[ + { + "$project": { + # convert unsupported_field to string + "unsupported_field_str": { + "$convert": { + "input": "$unsupported_field", + "to": "string", + }, + }, + # skip unsupported_field from result + "unsupported_field": 0, + } + } + ], +) + +# cast field content to proper Spark type +df = df.select( + df.id, + df.supported_field, + # explicit cast + df.unsupported_field_str.cast("integer").alias("parsed_integer"), +) +``` + +### `DBWriter` + +Convert dataframe field to string on Spark side, and then write it to MongoDB: + +```python +df = df.select( + df.id, + df.unsupported_field.cast("string").alias("array_field_json"), +) + +writer.run(df) +``` diff --git a/mddocs/docs/connection/db_connection/mongodb/write.md b/mddocs/docs/connection/db_connection/mongodb/write.md new file mode 100644 index 000000000..5b44d8613 --- /dev/null +++ b/mddocs/docs/connection/db_connection/mongodb/write.md @@ -0,0 +1,47 @@ +(mongodb-write)= + +# Writing to MongoDB using `DBWriter` + +For writing data to MongoDB, use {obj}`DBWriter `. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`mongodb-types` +``` + +## Examples + +```python +from onetl.connection import MongoDB +from onetl.db import DBWriter + +mongodb = MongoDB(...) + +df = ... # data is here + +writer = DBWriter( + connection=mongodb, + target="schema.table", + options=MongoDB.WriteOptions( + if_exists="append", + ), +) + +writer.run(df) +``` + +## Write options + +Method above accepts {obj}`MongoDB.WriteOptions ` + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mongodb.options +``` + +```{eval-rst} +.. autopydantic_model:: MongoDBWriteOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/mssql/connection.md b/mddocs/docs/connection/db_connection/mssql/connection.md new file mode 100644 index 000000000..e35ef6ed9 --- /dev/null +++ b/mddocs/docs/connection/db_connection/mssql/connection.md @@ -0,0 +1,12 @@ +(mssql-connection)= + +# MSSQL connection + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mssql.connection +``` + +```{eval-rst} +.. autoclass:: MSSQL + :members: get_packages, check +``` diff --git a/mddocs/docs/connection/db_connection/mssql/execute.md b/mddocs/docs/connection/db_connection/mssql/execute.md new file mode 100644 index 000000000..278d84229 --- /dev/null +++ b/mddocs/docs/connection/db_connection/mssql/execute.md @@ -0,0 +1,117 @@ +(mssql-execute)= + +# Executing statements in MSSQL + +```{eval-rst} +.. warning:: + + Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + + Do **NOT** use them to read large amounts of data. Use :ref:`DBReader ` or :ref:`MSSQL.sql ` instead. +``` + +## How to + +There are 2 ways to execute some statement in MSSQL + +### Use `MSSQL.fetch` + +Use this method to perform some `SELECT` query which returns **small number or rows**, like reading +MSSQL config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts {obj}`MSSQL.FetchOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`mssql-types`. +``` + +#### Syntax support + +This method supports **any** query syntax supported by MSSQL, like: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ✅︎ `SELECT func(arg1, arg2) FROM DUAL` - call function +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import MSSQL + +mssql = MSSQL(...) + +df = mssql.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=MSSQL.FetchOptions(queryTimeout=10), +) +mssql.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `MSSQL.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts {obj}`MSSQL.ExecuteOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by MSSQL, like: + +- ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...` +- ✅︎ `ALTER ...` +- ✅︎ `INSERT INTO ... AS SELECT ...` +- ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +- ✅︎ `EXEC procedure(arg1, arg2) ...` or `{call procedure(arg1, arg2)}` - special syntax for calling procedure +- ✅︎ `DECLARE ... BEGIN ... END` - execute PL/SQL statement +- ✅︎ other statements not mentioned here +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import MSSQL + +mssql = MSSQL(...) + +mssql.execute("DROP TABLE schema.table") +mssql.execute( + """ + CREATE TABLE schema.table ( + id bigint GENERATED ALWAYS AS IDENTITY, + key VARCHAR2(4000), + value NUMBER + ) + """, + options=MSSQL.ExecuteOptions(queryTimeout=10), +) +``` + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mssql.options +``` + +```{eval-rst} +.. autopydantic_model:: MSSQLFetchOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` + +```{eval-rst} +.. autopydantic_model:: MSSQLExecuteOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/mssql/index.md b/mddocs/docs/connection/db_connection/mssql/index.md new file mode 100644 index 000000000..16448c6a1 --- /dev/null +++ b/mddocs/docs/connection/db_connection/mssql/index.md @@ -0,0 +1,28 @@ +(mssql)= + +# MSSQL + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +connection +``` + +```{toctree} +:caption: Operations +:maxdepth: 1 + +read +sql +write +execute +``` + +```{toctree} +:caption: Troubleshooting +:maxdepth: 1 + +types +``` diff --git a/mddocs/docs/connection/db_connection/mssql/prerequisites.md b/mddocs/docs/connection/db_connection/mssql/prerequisites.md new file mode 100644 index 000000000..89e62f322 --- /dev/null +++ b/mddocs/docs/connection/db_connection/mssql/prerequisites.md @@ -0,0 +1,76 @@ +(mssql-prerequisites)= + +# Prerequisites + +## Version Compatibility + +- SQL Server versions: + : - Officially declared: 2016 - 2022 + - Actually tested: 2017, 2022 +- Spark versions: 2.3.x - 3.5.x +- Java versions: 8 - 20 + +See [official documentation](https://learn.microsoft.com/en-us/sql/connect/jdbc/system-requirements-for-the-jdbc-driver) +and [official compatibility matrix](https://learn.microsoft.com/en-us/sql/connect/jdbc/microsoft-jdbc-driver-for-sql-server-support-matrix). + +## Installing PySpark + +To use MSSQL connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Connecting to MSSQL + +### Connection port + +Connection is usually performed to port 1433. Port may differ for different MSSQL instances. +Please ask your MSSQL administrator to provide required information. + +For named MSSQL instances (`instanceName` option), [port number is optional](https://learn.microsoft.com/en-us/sql/connect/jdbc/building-the-connection-url?view=sql-server-ver16#named-and-multiple-sql-server-instances), and could be omitted. + +### Connection host + +It is possible to connect to MSSQL by using either DNS name of host or it's IP address. + +If you're using MSSQL cluster, it is currently possible to connect only to **one specific node**. +Connecting to multiple nodes to perform load balancing, as well as automatic failover to new master/replica are not supported. + +### Required grants + +Ask your MSSQL cluster administrator to set following grants for a user, +used for creating a connection: + +```{eval-rst} +.. tabs:: + + .. code-tab:: sql Read + Write (schema is owned by user) + + -- allow creating tables for user + GRANT CREATE TABLE TO username; + + -- allow read & write access to specific table + GRANT SELECT, INSERT ON username.mytable TO username; + + -- only if if_exists="replace_entire_table" is used: + -- allow dropping/truncating tables in any schema + GRANT ALTER ON username.mytable TO username; + + .. code-tab:: sql Read + Write (schema is not owned by user) + + -- allow creating tables for user + GRANT CREATE TABLE TO username; + + -- allow managing tables in specific schema, and inserting data to tables + GRANT ALTER, SELECT, INSERT ON SCHEMA::someschema TO username; + + .. code-tab:: sql Read only + + -- allow read access to specific table + GRANT SELECT ON someschema.mytable TO username; +``` + +More details can be found in official documentation: +: - [GRANT ON DATABASE](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-database-permissions-transact-sql) + - [GRANT ON OBJECT](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-object-permissions-transact-sql) + - [GRANT ON SCHEMA](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-schema-permissions-transact-sql) diff --git a/mddocs/docs/connection/db_connection/mssql/read.md b/mddocs/docs/connection/db_connection/mssql/read.md new file mode 100644 index 000000000..924d10e2f --- /dev/null +++ b/mddocs/docs/connection/db_connection/mssql/read.md @@ -0,0 +1,93 @@ +(mssql-read)= + +# Reading from MSSQL using `DBReader` + +{obj}`DBReader ` supports {ref}`strategy` for incremental data reading, +but does not support custom queries, like `JOIN`. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`mssql-types` +``` + +## Supported DBReader features + +- ✅︎ `columns` +- ✅︎ `where` +- ✅︎ `hwm`, supported strategies: +- - ✅︎ {ref}`snapshot-strategy` +- - ✅︎ {ref}`incremental-strategy` +- - ✅︎ {ref}`snapshot-batch-strategy` +- - ✅︎ {ref}`incremental-batch-strategy` +- ❌ `hint` (MSSQL does support hints, but DBReader not, at least for now) +- ❌ `df_schema` +- ✅︎ `options` (see {obj}`MSSQL.ReadOptions `) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import MSSQL +from onetl.db import DBReader + +mssql = MSSQL(...) + +reader = DBReader( + connection=mssql, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + options=MSSQL.ReadOptions(partitionColumn="id", numPartitions=10), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import MSSQL +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +mssql = MSSQL(...) + +reader = DBReader( + connection=mssql, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="mssql_hwm", expression="updated_dt"), + options=MSSQL.ReadOptions(partitionColumn="id", numPartitions=10), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from MSSQL to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from MSSQL to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mssql.options +``` + +```{eval-rst} +.. autopydantic_model:: MSSQLReadOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/mssql/sql.md b/mddocs/docs/connection/db_connection/mssql/sql.md new file mode 100644 index 000000000..de932e2d3 --- /dev/null +++ b/mddocs/docs/connection/db_connection/mssql/sql.md @@ -0,0 +1,80 @@ +(mssql-sql)= + +# Reading from MSSQL using `MSSQL.sql` + +`MSSQL.sql` allows passing custom SQL query, but does not support incremental strategies. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`mssql-types` +``` + +```{eval-rst} +.. warning:: + + Statement is executed in **read-write** connection, so if you're calling some functions/procedures with DDL/DML statements inside, + they can change data in your database. +``` + +## Syntax support + +Only queries with the following syntax are supported: + +- ✅︎ `SELECT ... FROM ...` +- ❌ `WITH alias AS (...) SELECT ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import MSSQL + +mssql = MSSQL(...) +df = mssql.sql( + """ + SELECT + id, + key, + CAST(value AS text) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """, + options=MSSQL.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from MSSQL to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from MSSQL to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mssql.options +``` + +```{eval-rst} +.. autopydantic_model:: MSSQLSQLOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/mssql/types.md b/mddocs/docs/connection/db_connection/mssql/types.md new file mode 100644 index 000000000..23e4972c9 --- /dev/null +++ b/mddocs/docs/connection/db_connection/mssql/types.md @@ -0,0 +1,376 @@ +(mssql-types)= + +# MSSQL \<-> Spark type mapping + +```{eval-rst} +.. note:: + + The results below are valid for Spark 3.5.5, and may differ on other Spark versions. +``` + +## Type detection & casting + +Spark's DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from MSSQL + +This is how MSSQL connector performs this: + +- For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and MSSQL type. +- Find corresponding `MSSQL type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- Create DataFrame from query with specific column names and Spark types. + +### Writing to some existing MSSQL table + +This is how MSSQL connector performs this: + +- Get names of columns in DataFrame. [^footnote-1] +- Perform `SELECT * FROM table LIMIT 0` query. +- Take only columns present in DataFrame (by name, case insensitive). For each found column get MSSQL type. +- Find corresponding `Spark type` → `MSSQL type (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- If `MSSQL type (write)` match `MSSQL type (read)`, no additional casts will be performed, DataFrame column will be written to MSSQL as is. +- If `MSSQL type (write)` does not match `MSSQL type (read)`, DataFrame column will be casted to target column type **on MSSQL side**. + For example, you can write column with text data to `int` column, if column contains valid integer values within supported value range and precision [^footnote-2]. + +[^footnote-1]: This allows to write data to tables with `DEFAULT` and `GENERATED` columns - if DataFrame has no such column, + it will be populated by MSSQL. + +[^footnote-2]: This is true only if DataFrame column is a `StringType()`, because text value is parsed automatically to target column type. + + But other types cannot be silently converted, like `int -> text`. This requires explicit casting, see [DBWriter]. + +### Create new table using Spark + +```{eval-rst} +.. warning:: + + ABSOLUTELY NOT RECOMMENDED! +``` + +This is how MSSQL connector performs this: + +- Find corresponding `Spark type` → `MSSQL type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- Generate DDL for creating table in MSSQL, like `CREATE TABLE (col1 ...)`, and run it. +- Write DataFrame to created table as is. + +But some cases this may lead to using wrong column type. For example, Spark creates column of type `timestamp` +which corresponds to MSSQL's type `timestamp(0)` (precision up to seconds) +instead of more precise `timestamp(6)` (precision up to nanoseconds). +This may lead to incidental precision loss, or sometimes data cannot be written to created table at all. + +So instead of relying on Spark to create tables: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + writer = DBWriter( + connection=mssql, + target="myschema.target_tbl", + options=MSSQL.WriteOptions( + if_exists="append", + ), + ) + writer.run(df) +``` + +Always prefer creating tables with specific types **BEFORE WRITING DATA**: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + mssql.execute( + """ + CREATE TABLE schema.table ( + id bigint, + key text, + value datetime2(6) -- specific type and precision + ) + """, + ) + + writer = DBWriter( + connection=mssql, + target="myschema.target_tbl", + options=MSSQL.WriteOptions(if_exists="append"), + ) + writer.run(df) +``` + +### References + +Here you can find source code with type conversions: + +- [MSSQL -> JDBC](https://github.com/microsoft/mssql-jdbc/blob/v12.2.0/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSetMetaData.java#L117-L170) +- [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MsSqlServerDialect.scala#L135-L152) +- [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MsSqlServerDialect.scala#L154-L163) +- [JDBC -> MSSQL](https://github.com/microsoft/mssql-jdbc/blob/v12.2.0/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java#L625-L676) + +## Supported types + +See [official documentation](https://learn.microsoft.com/en-us/sql/t-sql/data-types/data-types-transact-sql) + +### Numeric types + +```{eval-rst} ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | ++===============================+===================================+===============================+===============================+ +| ``decimal`` | ``DecimalType(P=18, S=0)`` | ``decimal(P=18, S=0)`` | ``decimal(P=18, S=0)`` | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``decimal(P=0..38)`` | ``DecimalType(P=0..38, S=0)`` | ``decimal(P=0..38, S=0)`` | ``decimal(P=0..38, S=0)`` | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``decimal(P=0..38, S=0..38)`` | ``DecimalType(P=0..38, S=0..38)`` | ``decimal(P=0..38, S=0..38)`` | ``decimal(P=0..38, S=0..38)`` | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``real`` | ``FloatType()`` | ``real`` | ``real`` | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``float`` | ``DoubleType()`` | ``float`` | ``float`` | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``smallint`` | ``ShortType()`` | ``smallint`` | ``smallint`` | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``tinyint`` | ``IntegerType()`` | ``int`` | ``int`` | ++-------------------------------+ | | | +| ``int`` | | | | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``bigint`` | ``LongType()`` | ``bigint`` | ``bigint`` | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +``` + +### Temporal types + +```{eval-rst} +.. note:: + + MSSQL ``timestamp`` type is alias for ``rowversion`` (see `Special types`_). It is not a temporal type! +``` + +```{eval-rst} ++------------------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | ++==========================================+======================================+===================================+===============================+ +| ``date`` | ``DateType()`` | ``date`` | ``date`` | ++------------------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +| ``smalldatetime``, minutes | ``TimestampType()``, microseconds | ``datetime2(6)``, microseconds | ``datetime``, milliseconds | ++------------------------------------------+ | | | +| ``datetime``, milliseconds | | | | ++------------------------------------------+ | | | +| ``datetime2(0)``, seconds | | | | ++------------------------------------------+ | | | +| ``datetime2(3)``, milliseconds | | | | ++------------------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +| ``datetime2(6)``, microseconds | ``TimestampType()``, microseconds | ``datetime2(6)``, microseconds | ``datetime``, milliseconds, | ++------------------------------------------+--------------------------------------+-----------------------------------+ **precision loss** [3]_ | +| ``datetime2(7)``, 100s of nanoseconds | ``TimestampType()``, microseconds, | ``datetime2(6)``, microseconds, | | +| | **precision loss** [4]_ | **precision loss** [4]_ | | ++------------------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +| ``time(0)``, seconds | ``TimestampType()``, microseconds, | ``datetime2(6)``, microseconds | ``datetime``, milliseconds | ++------------------------------------------+ with time format quirks [5]_ | | | +| ``time(3)``, milliseconds | | | | ++------------------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +| ``time(6)``, microseconds | ``TimestampType()``, microseconds, | ``datetime2(6)``, microseconds | ``datetime``, milliseconds, | ++ | with time format quirks [5]_ | | **precision loss** [3]_ | ++------------------------------------------+--------------------------------------+-----------------------------------+ + +| ``time``, 100s of nanoseconds | ``TimestampType()``, microseconds, | ``datetime2(6)``, microseconds | | ++------------------------------------------+ **precision loss** [4]_, | **precision loss** [3]_ | | +| ``time(7)``, 100s of nanoseconds | with time format quirks [5]_ | | | ++------------------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +| ``datetimeoffset`` | ``StringType()`` | ``nvarchar`` | ``nvarchar`` | ++------------------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +``` + +```{eval-rst} +.. warning:: + + Note that types in MSSQL and Spark have different value ranges: + + +-------------------+--------------------------------+--------------------------------+---------------------+--------------------------------+--------------------------------+ + | MySQL type | Min value | Max value | Spark type | Min value | Max value | + +===================+================================+================================+=====================+================================+================================+ + | ``smalldatetime`` | ``1900-01-01 00:00:00`` | ``2079-06-06 23:59:00`` | ``TimestampType()`` | ``0001-01-01 00:00:00.000000`` | ``9999-12-31 23:59:59.999999`` | + +-------------------+--------------------------------+--------------------------------+ | | | + | ``datetime`` | ``1753-01-01 00:00:00.000`` | ``9999-12-31 23:59:59.997`` | | | | + +-------------------+--------------------------------+--------------------------------+ | | | + | ``datetime2`` | ``0001-01-01 00:00:00.000000`` | ``9999-12-31 23:59:59.999999`` | | | | + +-------------------+--------------------------------+--------------------------------+ | | | + | ``time`` | ``00:00:00.0000000`` | ``23:59:59.9999999`` | | | | + +-------------------+--------------------------------+--------------------------------+---------------------+--------------------------------+--------------------------------+ + + So not all of values in Spark DataFrame can be written to MSSQL. + + References: + * `MSSQL date & time types documentation `_ + * `Spark DateType documentation `_ + * `Spark TimestampType documentation `_ +``` + +[^footnote-3]: MSSQL dialect for Spark generates DDL with type `datetime` which has precision up to milliseconds (`23:59:59.999`, 10{superscript}`-3` seconds). + Inserting data with microsecond and higher precision (`23:59:59.999999` .. `23.59:59.9999999`, 10{superscript}`-6` .. 10{superscript}`-7` seconds) + will lead to **throwing away microseconds**. + +[^footnote-4]: MSSQL support timestamp up to 100s of nanoseconds precision (`23:59:59.9999999999`, 10{superscript}`-7` seconds), + but Spark `TimestampType()` supports datetime up to microseconds precision (`23:59:59.999999`, 10{superscript}`-6` seconds). + Last digit will be lost during read or write operations. + +[^footnote-5]: `time` type is the same as `datetime2` with date `1970-01-01`. So instead of reading data from MSSQL like `23:59:59.999999` + it is actually read `1970-01-01 23:59:59.999999`, and vice versa. + +### String types + +```{eval-rst} ++-------------------+------------------+--------------------+---------------------+ +| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | ++===================+==================+====================+=====================+ +| ``char`` | ``StringType()`` | ``nvarchar`` | ``nvarchar`` | ++-------------------+ | | | +| ``char(N)`` | | | | ++-------------------+ | | | +| ``nchar`` | | | | ++-------------------+ | | | +| ``nchar(N)`` | | | | ++-------------------+ | | | +| ``varchar`` | | | | ++-------------------+ | | | +| ``varchar(N)`` | | | | ++-------------------+ | | | +| ``nvarchar`` | | | | ++-------------------+ | | | +| ``nvarchar(N)`` | | | | ++-------------------+ | | | +| ``mediumtext`` | | | | ++-------------------+ | | | +| ``text`` | | | | ++-------------------+ | | | +| ``ntext`` | | | | ++-------------------+ | | | +| ``xml`` | | | | ++-------------------+------------------+--------------------+---------------------+ +``` + +### Binary types + +```{eval-rst} ++--------------------+-------------------+--------------------+---------------------+ +| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | ++====================+===================+====================+=====================+ +| ``bit`` | ``BooleanType()`` | ``bit`` | ``bit`` | ++--------------------+-------------------+--------------------+---------------------+ +| ``binary`` | ``BinaryType()`` | ``varbinary`` | ``varbinary`` | ++--------------------+ | | | +| ``binary(N)`` | | | | ++--------------------+ | | | +| ``varbinary`` | | | | ++--------------------+ | | | +| ``varbinary(N)`` | | | | ++--------------------+ | | | +| ``image`` | | | | ++--------------------+-------------------+--------------------+---------------------+ +``` + +### Special types + +```{eval-rst} ++---------------------------+------------------+--------------------+---------------------+ +| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | ++===========================+==================+====================+=====================+ +| ``geography`` | ``BinaryType()`` | ``varbinary`` | ``varbinary`` | ++---------------------------+ | | | +| ``geometry`` | | | | ++---------------------------+ | | | +| ``hierarchyid`` | | | | ++---------------------------+ | | | +| ``rowversion`` | | | | ++---------------------------+------------------+--------------------+---------------------+ +| ``sql_variant`` | unsupported | | | ++---------------------------+------------------+--------------------+---------------------+ +| ``sysname`` | ``StringType()`` | ``nvarchar`` | ``nvarchar`` | ++---------------------------+ | | | +| ``uniqueidentifier`` | | | | ++---------------------------+------------------+--------------------+---------------------+ +``` + +## Explicit type cast + +### `DBReader` + +It is possible to explicitly cast column type using `DBReader(columns=...)` syntax. + +For example, you can use `CAST(column AS text)` to convert data to string representation on MSSQL side, and so it will be read as Spark's `StringType()`: + +```python +from onetl.connection import MSSQL +from onetl.db import DBReader + +mssql = MSSQL(...) + +DBReader( + connection=mssql, + columns=[ + "id", + "supported_column", + "CAST(unsupported_column AS text) unsupported_column_str", + ], +) +df = reader.run() + +# cast column content to proper Spark type +df = df.select( + df.id, + df.supported_column, + # explicit cast + df.unsupported_column_str.cast("integer").alias("parsed_integer"), +) +``` + +### `DBWriter` + +Convert dataframe column to JSON using [to_json](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.functions.to_json.html), +and write it as `text` column in MSSQL: + +```python +mssql.execute( + """ + CREATE TABLE schema.target_tbl ( + id bigint, + struct_column_json text -- any string type, actually + ) + """, +) + +from pyspark.sql.functions import to_json + +df = df.select( + df.id, + to_json(df.struct_column).alias("struct_column_json"), +) + +writer.run(df) +``` + +Then you can parse this column on MSSQL side - for example, by creating a view: + +```sql +SELECT + id, + JSON_VALUE(struct_column_json, "$.nested.field") AS nested_field +FROM target_tbl +``` + +Or by using [computed column](https://learn.microsoft.com/en-us/sql/relational-databases/tables/specify-computed-columns-in-a-table): + +```sql +CREATE TABLE schema.target_table ( + id bigint, + supported_column datetime2(6), + struct_column_json text, -- any string type, actually + -- computed column + nested_field AS (JSON_VALUE(struct_column_json, "$.nested.field")) + -- or persisted column + -- nested_field AS (JSON_VALUE(struct_column_json, "$.nested.field")) PERSISTED +) +``` + +By default, column value is calculated on every table read. +Column marked as `PERSISTED` is calculated during insert, but this require additional space. diff --git a/mddocs/docs/connection/db_connection/mssql/write.md b/mddocs/docs/connection/db_connection/mssql/write.md new file mode 100644 index 000000000..0b6d8db19 --- /dev/null +++ b/mddocs/docs/connection/db_connection/mssql/write.md @@ -0,0 +1,56 @@ +(mssql-write)= + +# Writing to MSSQL using `DBWriter` + +For writing data to MSSQL, use {obj}`DBWriter `. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`mssql-types` +``` + +```{eval-rst} +.. warning:: + + It is always recommended to create table explicitly using :ref:`MSSQL.execute ` + instead of relying on Spark's table DDL generation. + + This is because Spark's DDL generator can create columns with different precision and types than it is expected, + causing precision loss or other issues. +``` + +## Examples + +```python +from onetl.connection import MSSQL +from onetl.db import DBWriter + +mssql = MSSQL(...) + +df = ... # data is here + +writer = DBWriter( + connection=mssql, + target="schema.table", + options=MSSQL.WriteOptions(if_exists="append"), +) + +writer.run(df) +``` + +## Options + +Method above accepts {obj}`MSSQL.WriteOptions ` + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mssql.options +``` + +```{eval-rst} +.. autopydantic_model:: MSSQLWriteOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/mysql/connection.md b/mddocs/docs/connection/db_connection/mysql/connection.md new file mode 100644 index 000000000..1b2ba94c3 --- /dev/null +++ b/mddocs/docs/connection/db_connection/mysql/connection.md @@ -0,0 +1,12 @@ +(mysql-connection)= + +# MySQL connection + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mysql.connection +``` + +```{eval-rst} +.. autoclass:: MySQL + :members: get_packages, check +``` diff --git a/mddocs/docs/connection/db_connection/mysql/execute.md b/mddocs/docs/connection/db_connection/mysql/execute.md new file mode 100644 index 000000000..99c86be5b --- /dev/null +++ b/mddocs/docs/connection/db_connection/mysql/execute.md @@ -0,0 +1,115 @@ +(mysql-execute)= + +# Executing statements in MySQL + +```{eval-rst} +.. warning:: + + Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + + Do **NOT** use them to read large amounts of data. Use :ref:`DBReader ` or :ref:`MySQL.sql ` instead. +``` + +## How to + +There are 2 ways to execute some statement in MySQL + +### Use `MySQL.fetch` + +Use this method to perform some `SELECT` query which returns **small number or rows**, like reading +MySQL config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts {obj}`MySQL.FetchOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`mysql-types`. +``` + +#### Syntax support + +This method supports **any** query syntax supported by MySQL, like: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ✅︎ `SELECT func(arg1, arg2)` or `{?= call func(arg1, arg2)}` - special syntax for calling function +- ✅︎ `SHOW ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import MySQL + +mysql = MySQL(...) + +df = mysql.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=MySQL.FetchOptions(queryTimeout=10), +) +mysql.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `MySQL.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts {obj}`MySQL.ExecuteOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by MySQL, like: + +- ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +- ✅︎ `ALTER ...` +- ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +- ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, and so on +- ✅︎ `CALL procedure(arg1, arg2) ...` or `{call procedure(arg1, arg2)}` - special syntax for calling procedure +- ✅︎ other statements not mentioned here +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import MySQL + +mysql = MySQL(...) + +mysql.execute("DROP TABLE schema.table") +mysql.execute( + """ + CREATE TABLE schema.table ( + id bigint, + key text, + value float + ) + ENGINE = InnoDB + """, + options=MySQL.ExecuteOptions(queryTimeout=10), +) +``` + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mysql.options +``` + +```{eval-rst} +.. autopydantic_model:: MySQLFetchOptions + :inherited-members: GenericOptions + :member-order: bysource + +``` + +```{eval-rst} +.. autopydantic_model:: MySQLExecuteOptions + :inherited-members: GenericOptions + :member-order: bysource +``` diff --git a/mddocs/docs/connection/db_connection/mysql/index.md b/mddocs/docs/connection/db_connection/mysql/index.md new file mode 100644 index 000000000..6b69e1c1b --- /dev/null +++ b/mddocs/docs/connection/db_connection/mysql/index.md @@ -0,0 +1,28 @@ +(mysql)= + +# MySQL + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +connection +``` + +```{toctree} +:caption: Operations +:maxdepth: 1 + +read +sql +write +execute +``` + +```{toctree} +:caption: Troubleshooting +:maxdepth: 1 + +types +``` diff --git a/mddocs/docs/connection/db_connection/mysql/prerequisites.md b/mddocs/docs/connection/db_connection/mysql/prerequisites.md new file mode 100644 index 000000000..6292e7761 --- /dev/null +++ b/mddocs/docs/connection/db_connection/mysql/prerequisites.md @@ -0,0 +1,61 @@ +(mysql-prerequisites)= + +# Prerequisites + +## Version Compatibility + +- MySQL server versions: + : - Officially declared: 8.0 - 9.2 + - Actually tested: 5.7.13, 9.2.0 +- Spark versions: 2.3.x - 3.5.x +- Java versions: 8 - 20 + +See [official documentation](https://dev.mysql.com/doc/connector-j/en/connector-j-versions.html). + +## Installing PySpark + +To use MySQL connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Connecting to MySQL + +### Connection host + +It is possible to connect to MySQL by using either DNS name of host or it's IP address. + +If you're using MySQL cluster, it is currently possible to connect only to **one specific node**. +Connecting to multiple nodes to perform load balancing, as well as automatic failover to new master/replica are not supported. + +### Connection port + +Connection is usually performed to port 3306. Port may differ for different MySQL instances. +Please ask your MySQL administrator to provide required information. + +### Required grants + +Ask your MySQL cluster administrator to set following grants for a user, +used for creating a connection: + +```{eval-rst} +.. tabs:: + + .. code-tab:: sql Read + Write + + -- allow creating tables in the target schema + GRANT CREATE ON myschema.* TO username@'192.168.1.%'; + + -- allow read & write access to specific table + GRANT SELECT, INSERT ON myschema.mytable TO username@'192.168.1.%'; + + .. code-tab:: sql Read only + + -- allow read access to specific table + GRANT SELECT ON myschema.mytable TO username@'192.168.1.%'; +``` + +In example above `'192.168.1.%''` is a network subnet `192.168.1.0 - 192.168.1.255` +where Spark driver and executors are running. To allow connecting user from any IP, use `'%'` (not secure!). + +More details can be found in [official documentation](https://dev.mysql.com/doc/refman/en/grant.html). diff --git a/mddocs/docs/connection/db_connection/mysql/read.md b/mddocs/docs/connection/db_connection/mysql/read.md new file mode 100644 index 000000000..c3751dca2 --- /dev/null +++ b/mddocs/docs/connection/db_connection/mysql/read.md @@ -0,0 +1,93 @@ +(mysql-read)= + +# Reading from MySQL using `DBReader` + +{obj}`DBReader ` supports {ref}`strategy` for incremental data reading, +but does not support custom queries, like `JOIN`. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`mysql-types` +``` + +## Supported DBReader features + +- ✅︎ `columns` +- ✅︎ `where` +- ✅︎ `hwm`, supported strategies: +- - ✅︎ {ref}`snapshot-strategy` +- - ✅︎ {ref}`incremental-strategy` +- - ✅︎ {ref}`snapshot-batch-strategy` +- - ✅︎ {ref}`incremental-batch-strategy` +- ✅︎ `hint` (see [official documentation](https://dev.mysql.com/doc/refman/en/optimizer-hints.html)) +- ❌ `df_schema` +- ✅︎ `options` (see {obj}`MySQL.ReadOptions `) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import MySQL +from onetl.db import DBReader + +mysql = MySQL(...) + +reader = DBReader( + connection=mysql, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + hint="SKIP_SCAN(schema.table key_index)", + options=MySQL.ReadOptions(partitionColumn="id", numPartitions=10), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import MySQL +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +mysql = MySQL(...) + +reader = DBReader( + connection=mysql, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + hint="SKIP_SCAN(schema.table key_index)", + hwm=DBReader.AutoDetectHWM(name="mysql_hwm", expression="updated_dt"), + options=MySQL.ReadOptions(partitionColumn="id", numPartitions=10), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Oracle to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Oracle to Spark, and may also improve performance of the query. +Especially if there are indexes for columns used in `where` clause. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mysql.options +``` + +```{eval-rst} +.. autopydantic_model:: MySQLReadOptions + :inherited-members: GenericOptions + :member-order: bysource +``` diff --git a/mddocs/docs/connection/db_connection/mysql/sql.md b/mddocs/docs/connection/db_connection/mysql/sql.md new file mode 100644 index 000000000..7d2dd4e51 --- /dev/null +++ b/mddocs/docs/connection/db_connection/mysql/sql.md @@ -0,0 +1,81 @@ +(mysql-sql)= + +# Reading from MySQL using `MySQL.sql` + +`MySQL.sql` allows passing custom SQL query, but does not support incremental strategies. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`mysql-types` +``` + +```{eval-rst} +.. warning:: + + Statement is executed in **read-write** connection, so if you're calling some functions/procedures with DDL/DML statements inside, + they can change data in your database. +``` + +## Syntax support + +Only queries with the following syntax are supported: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ❌ `SHOW ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import MySQL + +mysql = MySQL(...) +df = mysql.sql( + """ + SELECT + id, + key, + CAST(value AS text) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """, + options=MySQL.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from MySQL to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from MySQL to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mysql.options +``` + +```{eval-rst} +.. autopydantic_model:: MySQLSQLOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/mysql/types.md b/mddocs/docs/connection/db_connection/mysql/types.md new file mode 100644 index 000000000..df3d6849b --- /dev/null +++ b/mddocs/docs/connection/db_connection/mysql/types.md @@ -0,0 +1,382 @@ +(mysql-types)= + +# MySQL \<-> Spark type mapping + +```{eval-rst} +.. note:: + + The results below are valid for Spark 3.5.5, and may differ on other Spark versions. +``` + +## Type detection & casting + +Spark's DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from MySQL + +This is how MySQL connector performs this: + +- For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and MySQL type. +- Find corresponding `MySQL type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- Create DataFrame from query with specific column names and Spark types. + +### Writing to some existing MySQL table + +This is how MySQL connector performs this: + +- Get names of columns in DataFrame. [^footnote-1] +- Perform `SELECT * FROM table LIMIT 0` query. +- Take only columns present in DataFrame (by name, case insensitive). For each found column get MySQL type. +- Find corresponding `Spark type` → `MySQL type (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- If `MySQL type (write)` match `MySQL type (read)`, no additional casts will be performed, DataFrame column will be written to MySQL as is. +- If `MySQL type (write)` does not match `MySQL type (read)`, DataFrame column will be casted to target column type **on MySQL side**. For example, you can write column with text data to `int` column, if column contains valid integer values within supported value range and precision. + +[^footnote-1]: This allows to write data to tables with `DEFAULT` and `GENERATED` columns - if DataFrame has no such column, + it will be populated by MySQL. + +### Create new table using Spark + +```{eval-rst} +.. warning:: + + ABSOLUTELY NOT RECOMMENDED! +``` + +This is how MySQL connector performs this: + +- Find corresponding `Spark type` → `MySQL type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- Generate DDL for creating table in MySQL, like `CREATE TABLE (col1 ...)`, and run it. +- Write DataFrame to created table as is. + +But some cases this may lead to using wrong column type. For example, Spark creates column of type `timestamp` +which corresponds to MySQL type `timestamp(0)` (precision up to seconds) +instead of more precise `timestamp(6)` (precision up to nanoseconds). +This may lead to incidental precision loss, or sometimes data cannot be written to created table at all. + +So instead of relying on Spark to create tables: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + writer = DBWriter( + connection=mysql, + target="myschema.target_tbl", + options=MySQL.WriteOptions( + if_exists="append", + createTableOptions="ENGINE = InnoDB", + ), + ) + writer.run(df) +``` + +Always prefer creating tables with specific types **BEFORE WRITING DATA**: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + mysql.execute( + """ + CREATE TABLE schema.table ( + id bigint, + key text, + value timestamp(6) -- specific type and precision + ) + ENGINE = InnoDB + """, + ) + + writer = DBWriter( + connection=mysql, + target="myschema.target_tbl", + options=MySQL.WriteOptions(if_exists="append"), + ) + writer.run(df) +``` + +### References + +Here you can find source code with type conversions: + +- [MySQL -> JDBC](https://github.com/mysql/mysql-connector-j/blob/8.0.33/src/main/core-api/java/com/mysql/cj/MysqlType.java#L44-L623) +- [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MySQLDialect.scala#L104-L132) +- [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MySQLDialect.scala#L204-L211) +- [JDBC -> MySQL](https://github.com/mysql/mysql-connector-j/blob/8.0.33/src/main/core-api/java/com/mysql/cj/MysqlType.java#L625-L867) + +## Supported types + +See [official documentation](https://dev.mysql.com/doc/refman/en/data-types.html) + +### Numeric types + +```{eval-rst} ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | ++===============================+===================================+===============================+===============================+ +| ``decimal`` | ``DecimalType(P=10, S=0)`` | ``decimal(P=10, S=0)`` | ``decimal(P=10, S=0)`` | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``decimal(P=0..38)`` | ``DecimalType(P=0..38, S=0)`` | ``decimal(P=0..38, S=0)`` | ``decimal(P=0..38, S=0)`` | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``decimal(P=0..38, S=0..30)`` | ``DecimalType(P=0..38, S=0..30)`` | ``decimal(P=0..38, S=0..30)`` | ``decimal(P=0..38, S=0..30)`` | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``decimal(P=39..65, S=...)`` | unsupported [2]_ | | | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``float`` | ``DoubleType()`` | ``double`` | ``double`` | ++-------------------------------+ | | | +| ``double`` | | | | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``tinyint`` | ``IntegerType()`` | ``int`` | ``int`` | ++-------------------------------+ | | | +| ``smallint`` | | | | ++-------------------------------+ | | | +| ``mediumint`` | | | | ++-------------------------------+ | | | +| ``int`` | | | | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``bigint`` | ``LongType()`` | ``bigint`` | ``bigint`` | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +``` + +[^footnote-2]: MySQL support decimal types with precision `P` up to 65. + + But Spark's `DecimalType(P, S)` supports maximum `P=38`. It is impossible to read, write or operate with values of larger precision, + this leads to an exception. + +### Temporal types + +```{eval-rst} ++-----------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | ++===================================+======================================+===================================+===============================+ +| ``year`` | ``DateType()`` | ``date`` | ``date`` | ++-----------------------------------+ | | | +| ``date`` | | | | ++-----------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +| ``datetime``, seconds | ``TimestampType()``, microseconds | ``timestamp(6)``, microseconds | ``timestamp(0)``, seconds | ++-----------------------------------+ | | | +| ``timestamp``, seconds | | | | ++-----------------------------------+ | | | +| ``datetime(0)``, seconds | | | | ++-----------------------------------+ | | | +| ``timestamp(0)``, seconds | | | | ++-----------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +| ``datetime(3)``, milliseconds | ``TimestampType()``, microseconds | ``timestamp(6)``, microseconds | ``timestamp(0)``, seconds, | ++-----------------------------------+ | | **precision loss** [4]_, | +| ``timestamp(3)``, milliseconds | | | | ++-----------------------------------+ | | | +| ``datetime(6)``, microseconds | | | | ++-----------------------------------+ | | | +| ``timestamp(6)``, microseconds | | | | ++-----------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +| ``time``, seconds | ``TimestampType()``, microseconds, | ``timestamp(6)``, microseconds | ``timestamp(0)``, seconds | ++-----------------------------------+ with time format quirks [5]_ | | | +| ``time(0)``, seconds | | | | ++-----------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +| ``time(3)``, milliseconds | ``TimestampType()``, microseconds | ``timestamp(6)``, microseconds | ``timestamp(0)``, seconds, | ++-----------------------------------+ with time format quirks [5]_ | | **precision loss** [4]_, | +| ``time(6)``, microseconds | | | | ++-----------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +``` + +```{eval-rst} +.. warning:: + + Note that types in MySQL and Spark have different value ranges: + + +---------------+--------------------------------+--------------------------------+---------------------+--------------------------------+--------------------------------+ + | MySQL type | Min value | Max value | Spark type | Min value | Max value | + +===============+================================+================================+=====================+================================+================================+ + | ``year`` | ``1901`` | ``2155`` | ``DateType()`` | ``0001-01-01`` | ``9999-12-31`` | + +---------------+--------------------------------+--------------------------------+ | | | + | ``date`` | ``1000-01-01`` | ``9999-12-31`` | | | | + +---------------+--------------------------------+--------------------------------+---------------------+--------------------------------+--------------------------------+ + | ``datetime`` | ``1000-01-01 00:00:00.000000`` | ``9999-12-31 23:59:59.499999`` | ``TimestampType()`` | ``0001-01-01 00:00:00.000000`` | ``9999-12-31 23:59:59.999999`` | + +---------------+--------------------------------+--------------------------------+ | | | + | ``timestamp`` | ``1970-01-01 00:00:01.000000`` | ``9999-12-31 23:59:59.499999`` | | | | + +---------------+--------------------------------+--------------------------------+ | | | + | ``time`` | ``-838:59:59.000000`` | ``838:59:59.000000`` | | | | + +---------------+--------------------------------+--------------------------------+---------------------+--------------------------------+--------------------------------+ + + So Spark can read all the values from MySQL, but not all of values in Spark DataFrame can be written to MySQL. + + References: + * `MySQL year documentation `_ + * `MySQL date, datetime & timestamp documentation `_ + * `MySQL time documentation `_ + * `Spark DateType documentation `_ + * `Spark TimestampType documentation `_ +``` + +[^footnote-3]: MySQL dialect generates DDL with MySQL type `timestamp` which is alias for `timestamp(0)` with precision up to seconds (`23:59:59`). + Inserting data with microseconds precision (`23:59:59.999999`) will lead to **throwing away microseconds**. + +[^footnote-4]: `time` type is the same as `timestamp` with date `1970-01-01`. So instead of reading data from MySQL like `23:59:59` + it is actually read `1970-01-01 23:59:59`, and vice versa. + +### String types + +```{eval-rst} ++-------------------------------+------------------+--------------------+---------------------+ +| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | ++===============================+==================+====================+=====================+ +| ``char`` | ``StringType()`` | ``longtext`` | ``longtext`` | ++-------------------------------+ | | | +| ``char(N)`` | | | | ++-------------------------------+ | | | +| ``varchar(N)`` | | | | ++-------------------------------+ | | | +| ``mediumtext`` | | | | ++-------------------------------+ | | | +| ``text`` | | | | ++-------------------------------+ | | | +| ``longtext`` | | | | ++-------------------------------+ | | | +| ``json`` | | | | ++-------------------------------+ | | | +| ``enum("val1", "val2", ...)`` | | | | ++-------------------------------+ | | | +| ``set("val1", "val2", ...)`` | | | | ++-------------------------------+------------------+--------------------+---------------------+ +``` + +### Binary types + +```{eval-rst} ++-------------------+------------------+--------------------+---------------------+ +| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | ++===================+==================+====================+=====================+ +| ``binary`` | ``BinaryType()`` | ``blob`` | ``blob`` | ++-------------------+ | | | +| ``binary(N)`` | | | | ++-------------------+ | | | +| ``varbinary(N)`` | | | | ++-------------------+ | | | +| ``mediumblob`` | | | | ++-------------------+ | | | +| ``blob`` | | | | ++-------------------+ | | | +| ``longblob`` | | | | ++-------------------+------------------+--------------------+---------------------+ +``` + +### Geometry types + +```{eval-rst} ++------------------------+------------------+--------------------+---------------------+ +| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | ++========================+==================+====================+=====================+ +| ``point`` | ``BinaryType()`` | ``blob`` | ``blob`` | ++------------------------+ | | | +| ``linestring`` | | | | ++------------------------+ | | | +| ``polygon`` | | | | ++------------------------+ | | | +| ``geometry`` | | | | ++------------------------+ | | | +| ``multipoint`` | | | | ++------------------------+ | | | +| ``multilinestring`` | | | | ++------------------------+ | | | +| ``multipolygon`` | | | | ++------------------------+ | | | +| ``geometrycollection`` | | | | ++------------------------+------------------+--------------------+---------------------+ +``` + +## Explicit type cast + +### `DBReader` + +It is possible to explicitly cast column type using `DBReader(columns=...)` syntax. + +For example, you can use `CAST(column AS text)` to convert data to string representation on MySQL side, and so it will be read as Spark's `StringType()`. + +It is also possible to use [JSON_OBJECT](https://dev.mysql.com/doc/refman/en/json.html) MySQL function and parse JSON columns in MySQL with the {obj}`JSON.parse_column ` method. + +```python +from pyspark.sql.types import IntegerType, StructType, StructField + +from onetl.connection import MySQL +from onetl.db import DBReader +from onetl.file.format import JSON + +mysql = MySQL(...) + +DBReader( + connection=mysql, + columns=[ + "id", + "supported_column", + "CAST(unsupported_column AS text) unsupported_column_str", + # or + "JSON_OBJECT('key', value_column) json_column", + ], +) +df = reader.run() + +json_scheme = StructType([StructField("key", IntegerType())]) + +df = df.select( + df.id, + df.supported_column, + # explicit cast + df.unsupported_column_str.cast("integer").alias("parsed_integer"), + JSON().parse_column("json_column", json_scheme).alias("struct_column"), +) +``` + +### `DBWriter` + +To write JSON data to a `json` or `text` column in a MySQL table, use the {obj}`JSON.serialize_column ` method. + +```python +from onetl.connection import MySQL +from onetl.db import DBWriter +from onetl.file.format import JSON + +mysql.execute( + """ + CREATE TABLE schema.target_tbl ( + id bigint, + array_column_json json -- any string type, actually + ) + ENGINE = InnoDB + """, +) + +df = df.select( + df.id, + JSON().serialize_column(df.array_column).alias("array_column_json"), +) + +writer.run(df) +``` + +Then you can parse this column on MySQL side - for example, by creating a view: + +```sql +SELECT + id, + array_column_json->"$[0]" AS array_item +FROM target_tbl +``` + +Or by using [GENERATED column](https://dev.mysql.com/doc/refman/en/create-table-generated-columns.html): + +```sql +CREATE TABLE schema.target_table ( + id bigint, + supported_column timestamp, + array_column_json json, -- any string type, actually + -- virtual column + array_item_0 GENERATED ALWAYS AS (array_column_json->"$[0]")) VIRTUAL + -- or stired column + -- array_item_0 GENERATED ALWAYS AS (array_column_json->"$[0]")) STORED +) +``` + +`VIRTUAL` column value is calculated on every table read. +`STORED` column value is calculated during insert, but this require additional space. diff --git a/mddocs/docs/connection/db_connection/mysql/write.md b/mddocs/docs/connection/db_connection/mysql/write.md new file mode 100644 index 000000000..7f7f2a5d2 --- /dev/null +++ b/mddocs/docs/connection/db_connection/mysql/write.md @@ -0,0 +1,60 @@ +(mysql-write)= + +# Writing to MySQL using `DBWriter` + +For writing data to MySQL, use {obj}`DBWriter `. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`mysql-types` +``` + +```{eval-rst} +.. warning:: + + It is always recommended to create table explicitly using :ref:`MySQL.execute ` + instead of relying on Spark's table DDL generation. + + This is because Spark's DDL generator can create columns with different precision and types than it is expected, + causing precision loss or other issues. +``` + +## Examples + +```python +from onetl.connection import MySQL +from onetl.db import DBWriter + +mysql = MySQL(...) + +df = ... # data is here + +writer = DBWriter( + connection=mysql, + target="schema.table", + options=MySQL.WriteOptions( + if_exists="append", + # ENGINE is required by MySQL + createTableOptions="ENGINE = MergeTree() ORDER BY id", + ), +) + +writer.run(df) +``` + +## Options + +Method above accepts {obj}`MySQL.WriteOptions ` + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mysql.options +``` + +```{eval-rst} +.. autopydantic_model:: MySQLWriteOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/oracle/connection.md b/mddocs/docs/connection/db_connection/oracle/connection.md new file mode 100644 index 000000000..26fc8a01e --- /dev/null +++ b/mddocs/docs/connection/db_connection/oracle/connection.md @@ -0,0 +1,12 @@ +(oracle-connection)= + +# Oracle connection + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.oracle.connection +``` + +```{eval-rst} +.. autoclass:: Oracle + :members: get_packages, check +``` diff --git a/mddocs/docs/connection/db_connection/oracle/execute.md b/mddocs/docs/connection/db_connection/oracle/execute.md new file mode 100644 index 000000000..28b5baa39 --- /dev/null +++ b/mddocs/docs/connection/db_connection/oracle/execute.md @@ -0,0 +1,115 @@ +(oracle-execute)= + +# Executing statements in Oracle + +```{eval-rst} +.. warning:: + + Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + + Do **NOT** use them to read large amounts of data. Use :ref:`DBReader ` or :ref:`Oracle.sql ` instead. +``` + +## How to + +There are 2 ways to execute some statement in Oracle + +### Use `Oracle.fetch` + +Use this method to execute some `SELECT` query which returns **small number or rows**, like reading +Oracle config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts {obj}`Oracle.FetchOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`oracle-types`. +``` + +#### Syntax support + +This method supports **any** query syntax supported by Oracle, like: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ✅︎ `SELECT func(arg1, arg2) FROM DUAL` - call function +- ✅︎ `SHOW ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Oracle + +oracle = Oracle(...) + +df = oracle.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=Oracle.FetchOptions(queryTimeout=10), +) +oracle.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `Oracle.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts {obj}`Oracle.ExecuteOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Oracle, like: + +- ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...` +- ✅︎ `ALTER ...` +- ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +- ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +- ✅︎ `CALL procedure(arg1, arg2) ...` or `{call procedure(arg1, arg2)}` - special syntax for calling procedure +- ✅︎ `DECLARE ... BEGIN ... END` - execute PL/SQL statement +- ✅︎ other statements not mentioned here +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Oracle + +oracle = Oracle(...) + +oracle.execute("DROP TABLE schema.table") +oracle.execute( + """ + CREATE TABLE schema.table ( + id bigint GENERATED ALWAYS AS IDENTITY, + key VARCHAR2(4000), + value NUMBER + ) + """, + options=Oracle.ExecuteOptions(queryTimeout=10), +) +``` + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.oracle.options +``` + +```{eval-rst} +.. autopydantic_model:: OracleFetchOptions + :inherited-members: GenericOptions + :member-order: bysource + +``` + +```{eval-rst} +.. autopydantic_model:: OracleExecuteOptions + :inherited-members: GenericOptions + :member-order: bysource +``` diff --git a/mddocs/docs/connection/db_connection/oracle/index.md b/mddocs/docs/connection/db_connection/oracle/index.md new file mode 100644 index 000000000..5218cc287 --- /dev/null +++ b/mddocs/docs/connection/db_connection/oracle/index.md @@ -0,0 +1,28 @@ +(oracle)= + +# Oracle + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +connection +``` + +```{toctree} +:caption: Operations +:maxdepth: 1 + +read +sql +write +execute +``` + +```{toctree} +:caption: Troubleshooting +:maxdepth: 1 + +types +``` diff --git a/mddocs/docs/connection/db_connection/oracle/prerequisites.md b/mddocs/docs/connection/db_connection/oracle/prerequisites.md new file mode 100644 index 000000000..5a350937b --- /dev/null +++ b/mddocs/docs/connection/db_connection/oracle/prerequisites.md @@ -0,0 +1,108 @@ +(oracle-prerequisites)= + +# Prerequisites + +## Version Compatibility + +- Oracle Server versions: + : - Officially declared: 19c, 21c, 23ai + - Actually tested: 11.2, 23.5 +- Spark versions: 2.3.x - 3.5.x +- Java versions: 8 - 20 + +See [official documentation](https://www.oracle.com/cis/database/technologies/appdev/jdbc-downloads.html). + +## Installing PySpark + +To use Oracle connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Connecting to Oracle + +### Connection port + +Connection is usually performed to port 1521. Port may differ for different Oracle instances. +Please ask your Oracle administrator to provide required information. + +### Connection host + +It is possible to connect to Oracle by using either DNS name of host or it's IP address. + +If you're using Oracle cluster, it is currently possible to connect only to **one specific node**. +Connecting to multiple nodes to perform load balancing, as well as automatic failover to new master/replica are not supported. + +### Connect as proxy user + +It is possible to connect to database as another user without knowing this user password. + +This can be enabled by granting user a special `CONNECT THROUGH` permission: + +```sql +ALTER USER schema_owner GRANT CONNECT THROUGH proxy_user; +``` + +Then you can connect to Oracle using credentials of `proxy_user` but specify that you need permissions of `schema_owner`: + +```python +oracle = Oracle( + ..., + user="proxy_user[schema_owner]", + password="proxy_user password", +) +``` + +See [official documentation](https://oracle-base.com/articles/misc/proxy-users-and-connect-through). + +### Required grants + +Ask your Oracle cluster administrator to set following grants for a user, +used for creating a connection: + +```{eval-rst} +.. tabs:: + + .. code-tab:: sql Read + Write (schema is owned by user) + + -- allow user to log in + GRANT CREATE SESSION TO username; + + -- allow creating tables in user schema + GRANT CREATE TABLE TO username; + + -- allow read & write access to specific table + GRANT SELECT, INSERT ON username.mytable TO username; + + .. code-tab:: sql Read + Write (schema is not owned by user) + + -- allow user to log in + GRANT CREATE SESSION TO username; + + -- allow creating tables in any schema, + -- as Oracle does not support specifying exact schema name + GRANT CREATE ANY TABLE TO username; + + -- allow read & write access to specific table + GRANT SELECT, INSERT ON someschema.mytable TO username; + + -- only if if_exists="replace_entire_table" is used: + -- allow dropping/truncating tables in any schema, + -- as Oracle does not support specifying exact schema name + GRANT DROP ANY TABLE TO username; + + .. code-tab:: sql Read only + + -- allow user to log in + GRANT CREATE SESSION TO username; + + -- allow read access to specific table + GRANT SELECT ON someschema.mytable TO username; +``` + +More details can be found in official documentation: +: - [GRANT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/GRANT.html) + - [SELECT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/SELECT.html) + - [CREATE TABLE](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/SELECT.html) + - [INSERT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/INSERT.html) + - [TRUNCATE TABLE](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/TRUNCATE-TABLE.html) diff --git a/mddocs/docs/connection/db_connection/oracle/read.md b/mddocs/docs/connection/db_connection/oracle/read.md new file mode 100644 index 000000000..4f11e86e7 --- /dev/null +++ b/mddocs/docs/connection/db_connection/oracle/read.md @@ -0,0 +1,93 @@ +(oracle-read)= + +# Reading from Oracle using `DBReader` + +{obj}`DBReader ` supports {ref}`strategy` for incremental data reading, +but does not support custom queries, like `JOIN`. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`oracle-types` +``` + +## Supported DBReader features + +- ✅︎ `columns` +- ✅︎ `where` +- ✅︎ `hwm`, supported strategies: +- - ✅︎ {ref}`snapshot-strategy` +- - ✅︎ {ref}`incremental-strategy` +- - ✅︎ {ref}`snapshot-batch-strategy` +- - ✅︎ {ref}`incremental-batch-strategy` +- ✅︎ `hint` (see [official documentation](https://docs.oracle.com/cd/B10500_01/server.920/a96533/hintsref.htm)) +- ❌ `df_schema` +- ✅︎ `options` (see {obj}`Oracle.ReadOptions `) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Oracle +from onetl.db import DBReader + +oracle = Oracle(...) + +reader = DBReader( + connection=oracle, + source="schema.table", + columns=["id", "key", "CAST(value AS VARCHAR2(4000)) value", "updated_dt"], + where="key = 'something'", + hint="INDEX(schema.table key_index)", + options=Oracle.ReadOptions(partitionColumn="id", numPartitions=10), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Oracle +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +oracle = Oracle(...) + +reader = DBReader( + connection=oracle, + source="schema.table", + columns=["id", "key", "CAST(value AS VARCHAR2(4000)) value", "updated_dt"], + where="key = 'something'", + hint="INDEX(schema.table key_index)", + hwm=DBReader.AutoDetectHWM(name="oracle_hwm", expression="updated_dt"), + options=Oracle.ReadOptions(partitionColumn="id", numPartitions=10), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Oracle to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Oracle to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.oracle.options +``` + +```{eval-rst} +.. autopydantic_model:: OracleReadOptions + :inherited-members: GenericOptions + :member-order: bysource +``` diff --git a/mddocs/docs/connection/db_connection/oracle/sql.md b/mddocs/docs/connection/db_connection/oracle/sql.md new file mode 100644 index 000000000..73d82e739 --- /dev/null +++ b/mddocs/docs/connection/db_connection/oracle/sql.md @@ -0,0 +1,81 @@ +(oracle-sql)= + +# Reading from Oracle using `Oracle.sql` + +`Oracle.sql` allows passing custom SQL query, but does not support incremental strategies. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`oracle-types` +``` + +```{eval-rst} +.. warning:: + + Statement is executed in **read-write** connection, so if you're calling some functions/procedures with DDL/DML statements inside, + they can change data in your database. +``` + +## Syntax support + +Only queries with the following syntax are supported: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ❌ `SHOW ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import Oracle + +oracle = Oracle(...) +df = oracle.sql( + """ + SELECT + id, + key, + CAST(value AS VARCHAR2(4000)) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """, + options=Oracle.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from Oracle to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from Oracle to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.oracle.options +``` + +```{eval-rst} +.. autopydantic_model:: OracleSQLOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/oracle/types.md b/mddocs/docs/connection/db_connection/oracle/types.md new file mode 100644 index 000000000..7097bd439 --- /dev/null +++ b/mddocs/docs/connection/db_connection/oracle/types.md @@ -0,0 +1,400 @@ +(oracle-types)= + +# Oracle \<-> Spark type mapping + +```{eval-rst} +.. note:: + + The results below are valid for Spark 3.5.5, and may differ on other Spark versions. +``` + +## Type detection & casting + +Spark's DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from Oracle + +This is how Oracle connector performs this: + +- For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and Oracle type. +- Find corresponding `Oracle type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- Create DataFrame from query with specific column names and Spark types. + +### Writing to some existing Oracle table + +This is how Oracle connector performs this: + +- Get names of columns in DataFrame. [^footnote-1] +- Perform `SELECT * FROM table LIMIT 0` query. +- Take only columns present in DataFrame (by name, case insensitive). For each found column get Clickhouse type. +- **Find corresponding** `Oracle type (read)` → `Spark type` **combination** (see below) for each DataFrame column. If no combination is found, raise exception. [^footnote-2] +- Find corresponding `Spark type` → `Oracle type (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- If `Oracle type (write)` match `Oracle type (read)`, no additional casts will be performed, DataFrame column will be written to Oracle as is. +- If `Oracle type (write)` does not match `Oracle type (read)`, DataFrame column will be casted to target column type **on Oracle side**. + For example, you can write column with text data to `int` column, if column contains valid integer values within supported value range and precision. + +[^footnote-1]: This allows to write data to tables with `DEFAULT` and `GENERATED` columns - if DataFrame has no such column, + it will be populated by Oracle. + +[^footnote-2]: Yes, this is weird. + +### Create new table using Spark + +```{eval-rst} +.. warning:: + + ABSOLUTELY NOT RECOMMENDED! +``` + +This is how Oracle connector performs this: + +- Find corresponding `Spark type` → `Oracle type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- Generate DDL for creating table in Oracle, like `CREATE TABLE (col1 ...)`, and run it. +- Write DataFrame to created table as is. + +But Oracle connector support only limited number of types and almost no custom clauses (like `PARTITION BY`, `INDEX`, etc). +So instead of relying on Spark to create tables: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + writer = DBWriter( + connection=oracle, + target="public.table", + options=Oracle.WriteOptions(if_exists="append"), + ) + writer.run(df) +``` + +Always prefer creating table with desired DDL **BEFORE WRITING DATA**: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + oracle.execute( + """ + CREATE TABLE username.table ( + id NUMBER, + business_dt TIMESTAMP(6), + value VARCHAR2(2000) + ) + """, + ) + + writer = DBWriter( + connection=oracle, + target="public.table", + options=Oracle.WriteOptions(if_exists="append"), + ) + writer.run(df) +``` + +See Oracle [CREATE TABLE](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/SELECT.html) documentation. + +## Supported types + +### References + +See [List of Oracle types](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/Data-Types.html). + +Here you can find source code with type conversions: + +- [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/OracleDialect.scala#L83-L109) +- [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/OracleDialect.scala#L111-L123) + +### Numeric types + +```{eval-rst} ++----------------------------------+-----------------------------------+-------------------------------+---------------------------+ +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | ++==================================+===================================+===============================+===========================+ +| ``NUMBER`` | ``DecimalType(P=38, S=10)`` | ``NUMBER(P=38, S=10)`` | ``NUMBER(P=38, S=10)`` | ++----------------------------------+-----------------------------------+-------------------------------+---------------------------+ +| ``NUMBER(P=0..38)`` | ``DecimalType(P=0..38, S=0)`` | ``NUMBER(P=0..38, S=0)`` | ``NUMBER(P=38, S=0)`` | ++----------------------------------+-----------------------------------+-------------------------------+---------------------------+ +| ``NUMBER(P=0..38, S=0..38)`` | ``DecimalType(P=0..38, S=0..38)`` | ``NUMBER(P=0..38, S=0..38)`` | ``NUMBER(P=38, S=0..38)`` | ++----------------------------------+-----------------------------------+-------------------------------+---------------------------+ +| ``NUMBER(P=..., S=-127..-1)`` | unsupported [3]_ | | | ++----------------------------------+-----------------------------------+-------------------------------+---------------------------+ +| ``FLOAT`` | ``DecimalType(P=38, S=10)`` | ``NUMBER(P=38, S=10)`` | ``NUMBER(P=38, S=10)`` | ++----------------------------------+ | | | +| ``FLOAT(N)`` | | | | ++----------------------------------+ | | | +| ``REAL`` | | | | ++----------------------------------+ | | | +| ``DOUBLE PRECISION`` | | | | ++----------------------------------+-----------------------------------+-------------------------------+---------------------------+ +| ``BINARY_FLOAT`` | ``FloatType()`` | ``NUMBER(P=19, S=4)`` | ``NUMBER(P=19, S=4)`` | ++----------------------------------+-----------------------------------+ | | +| ``BINARY_DOUBLE`` | ``DoubleType()`` | | | ++----------------------------------+-----------------------------------+-------------------------------+---------------------------+ +| ``SMALLINT`` | ``DecimalType(P=38, S=0)`` | ``NUMBER(P=38, S=0)`` | ``NUMBER(P=38, S=0)`` | ++----------------------------------+ | | | +| ``INTEGER`` | | | | ++----------------------------------+-----------------------------------+-------------------------------+---------------------------+ +| ``LONG`` | ``StringType()`` | ``CLOB`` | ``CLOB`` | ++----------------------------------+-----------------------------------+-------------------------------+---------------------------+ +``` + +[^footnote-3]: Oracle support decimal types with negative scale, like `NUMBER(38, -10)`. Spark doesn't. + +### Temporal types + +```{eval-rst} ++--------------------------------------------+------------------------------------+---------------------------------+---------------------------------+ +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | ++============================================+====================================+=================================+=================================+ +| ``DATE``, days | ``TimestampType()``, microseconds | ``TIMESTAMP(6)``, microseconds | ``TIMESTAMP(6)``, microseconds | ++--------------------------------------------+------------------------------------+---------------------------------+---------------------------------+ +| ``TIMESTAMP``, microseconds | ``TimestampType()``, microseconds | ``TIMESTAMP(6)``, microseconds | ``TIMESTAMP(6)``, microseconds | ++--------------------------------------------+ | | | +| ``TIMESTAMP(0)``, seconds | | | | ++--------------------------------------------+ | | | +| ``TIMESTAMP(3)``, milliseconds | | | | ++--------------------------------------------+ | | | +| ``TIMESTAMP(6)``, microseconds | | | | ++--------------------------------------------+------------------------------------+---------------------------------+---------------------------------+ +| ``TIMESTAMP(9)``, nanoseconds | ``TimestampType()``, microseconds, | ``TIMESTAMP(6)``, microseconds, | ``TIMESTAMP(6)``, microseconds, | +| | **precision loss** [4]_ | **precision loss** | **precision loss** | ++--------------------------------------------+------------------------------------+---------------------------------+---------------------------------+ +| ``TIMESTAMP WITH TIME ZONE`` | unsupported | | | ++--------------------------------------------+ | | | +| ``TIMESTAMP(N) WITH TIME ZONE`` | | | | ++--------------------------------------------+ | | | +| ``TIMESTAMP WITH LOCAL TIME ZONE`` | | | | ++--------------------------------------------+ | | | +| ``TIMESTAMP(N) WITH LOCAL TIME ZONE`` | | | | ++--------------------------------------------+ | | | +| ``INTERVAL YEAR TO MONTH`` | | | | ++--------------------------------------------+ | | | +| ``INTERVAL DAY TO SECOND`` | | | | ++--------------------------------------------+------------------------------------+---------------------------------+---------------------------------+ +``` + +```{eval-rst} +.. warning:: + + Note that types in Oracle and Spark have different value ranges: + + +---------------+------------------------------------+-----------------------------------+---------------------+--------------------------------+--------------------------------+ + | Oracle type | Min value | Max value | Spark type | Min value | Max value | + +===============+====================================+===================================+=====================+================================+================================+ + | ``date`` | ``-4712-01-01`` | ``9999-01-01`` | ``DateType()`` | ``0001-01-01`` | ``9999-12-31`` | + +---------------+------------------------------------+-----------------------------------+---------------------+--------------------------------+--------------------------------+ + | ``timestamp`` | ``-4712-01-01 00:00:00.000000000`` | ``9999-12-31 23:59:59.999999999`` | ``TimestampType()`` | ``0001-01-01 00:00:00.000000`` | ``9999-12-31 23:59:59.999999`` | + +---------------+------------------------------------+-----------------------------------+---------------------+--------------------------------+--------------------------------+ + + So not all of values can be read from Oracle to Spark. + + References: + * `Oracle date, timestamp and intervals documentation `_ + * `Spark DateType documentation `_ + * `Spark TimestampType documentation `_ +``` + +[^footnote-4]: Oracle support timestamp up to nanoseconds precision (`23:59:59.999999999`), + but Spark `TimestampType()` supports datetime up to microseconds precision (`23:59:59.999999`). + Nanoseconds will be lost during read or write operations. + +### String types + +```{eval-rst} ++-----------------------------+------------------+---------------------+----------------------+ +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | ++=============================+==================+=====================+======================+ +| ``CHAR`` | ``StringType()`` | ``CLOB`` | ``CLOB`` | ++-----------------------------+ | | | +| ``CHAR(N CHAR)`` | | | | ++-----------------------------+ | | | +| ``CHAR(N BYTE)`` | | | | ++-----------------------------+ | | | +| ``NCHAR`` | | | | ++-----------------------------+ | | | +| ``NCHAR(N)`` | | | | ++-----------------------------+ | | | +| ``VARCHAR(N)`` | | | | ++-----------------------------+ | | | +| ``LONG VARCHAR`` | | | | ++-----------------------------+ | | | +| ``VARCHAR2(N CHAR)`` | | | | ++-----------------------------+ | | | +| ``VARCHAR2(N BYTE)`` | | | | ++-----------------------------+ | | | +| ``NVARCHAR2(N)`` | | | | ++-----------------------------+ | | | +| ``CLOB`` | | | | ++-----------------------------+ | | | +| ``NCLOB`` | | | | ++-----------------------------+------------------+---------------------+----------------------+ +``` + +### Binary types + +```{eval-rst} ++--------------------------+------------------+---------------------+----------------------+ +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | ++==========================+==================+=====================+======================+ +| ``RAW(N)`` | ``BinaryType()`` | ``BLOB`` | ``BLOB`` | ++--------------------------+ | | | +| ``LONG RAW`` | | | | ++--------------------------+ | | | +| ``BLOB`` | | | | ++--------------------------+------------------+---------------------+----------------------+ +| ``BFILE`` | unsupported | | | ++--------------------------+------------------+---------------------+----------------------+ +``` + +### Struct types + +```{eval-rst} ++-------------------------------------+------------------+---------------------+----------------------+ +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | ++=====================================+==================+=====================+======================+ +| ``XMLType`` | ``StringType()`` | ``CLOB`` | ``CLOB`` | ++-------------------------------------+ | | | +| ``URIType`` | | | | ++-------------------------------------+ | | | +| ``DBURIType`` | | | | ++-------------------------------------+ | | | +| ``XDBURIType`` | | | | ++-------------------------------------+ | | | +| ``HTTPURIType`` | | | | ++-------------------------------------+ | | | +| ``CREATE TYPE ... AS OBJECT (...)`` | | | | ++-------------------------------------+------------------+---------------------+----------------------+ +| ``JSON`` | unsupported | | | ++-------------------------------------+ | | | +| ``CREATE TYPE ... AS VARRAY ...`` | | | | ++-------------------------------------+ | | | +| ``CREATE TYPE ... AS TABLE OF ...`` | | | | ++-------------------------------------+------------------+---------------------+----------------------+ +``` + +### Special types + +```{eval-rst} ++--------------------+-------------------+---------------------+----------------------+ +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | ++====================+===================+=====================+======================+ +| ``BOOLEAN`` | ``BooleanType()`` | ``BOOLEAN`` | ``NUMBER(P=1, S=0)`` | ++--------------------+-------------------+---------------------+----------------------+ +| ``ROWID`` | ``StringType()`` | ``CLOB`` | ``CLOB`` | ++--------------------+ | | | +| ``UROWID`` | | | | ++--------------------+ | | | +| ``UROWID(N)`` | | | | ++--------------------+-------------------+---------------------+----------------------+ +| ``ANYTYPE`` | unsupported | | | ++--------------------+ | | | +| ``ANYDATA`` | | | | ++--------------------+ | | | +| ``ANYDATASET`` | | | | ++--------------------+-------------------+---------------------+----------------------+ +``` + +## Explicit type cast + +### `DBReader` + +It is possible to explicitly cast column of unsupported type using `DBReader(columns=...)` syntax. + +For example, you can use `CAST(column AS CLOB)` to convert data to string representation on Oracle side, and so it will be read as Spark's `StringType()`. + +It is also possible to use [JSON_ARRAY](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/JSON_ARRAY.html) +or [JSON_OBJECT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/JSON_OBJECT.html) Oracle functions +to convert column of any type to string representation. Then this JSON string can then be effectively parsed using the {obj}`JSON.parse_column ` method. + +```python +from onetl.file.format import JSON +from pyspark.sql.types import IntegerType, StructType, StructField + +from onetl.connection import Oracle +from onetl.db import DBReader + +oracle = Oracle(...) + +DBReader( + connection=oracle, + columns=[ + "id", + "supported_column", + "CAST(unsupported_column AS VARCHAR2(4000)) unsupported_column_str", + # or + "JSON_ARRAY(array_column) array_column_json", + ], +) +df = reader.run() + +json_scheme = StructType([StructField("key", IntegerType())]) + +df = df.select( + df.id, + df.supported_column, + df.unsupported_column_str.cast("integer").alias("parsed_integer"), + JSON().parse_column("array_column_json", json_scheme).alias("array_column"), +) +``` + +### `DBWriter` + +It is always possible to convert data on Spark side to string, and then write it to text column in Oracle table. + +To serialize and write JSON data to a `text` or `json` column in an Oracle table use the {obj}`JSON.serialize_column ` method. + +```python +from onetl.connection import Oracle +from onetl.db import DBWriter +from onetl.file.format import JSON + +oracle = Oracle(...) + +oracle.execute( + """ + CREATE TABLE schema.target_table ( + id INTEGER, + supported_column TIMESTAMP, + array_column_json VARCHAR2(4000) -- any string type, actually + ) + """, +) + +write_df = df.select( + df.id, + df.supported_column, + JSON().serialize_column(df.unsupported_column).alias("array_column_json"), +) + +writer = DBWriter( + connection=oracle, + target="schema.target_table", +) +writer.run(write_df) +``` + +Then you can parse this column on Oracle side - for example, by creating a view: + +```sql +SELECT + id, + supported_column, + JSON_VALUE(array_column_json, '$[0]' RETURNING NUMBER) AS array_item_0 +FROM + schema.target_table +``` + +Or by using [VIRTUAL column](https://oracle-base.com/articles/11g/virtual-columns-11gr1): + +```sql +CREATE TABLE schema.target_table ( + id INTEGER, + supported_column TIMESTAMP, + array_column_json VARCHAR2(4000), -- any string type, actually + array_item_0 GENERATED ALWAYS AS (JSON_VALUE(array_column_json, '$[0]' RETURNING NUMBER)) VIRTUAL +) +``` + +But data will be parsed on each table read in any case, as Oracle does no support `GENERATED ALWAYS AS (...) STORED` columns. diff --git a/mddocs/docs/connection/db_connection/oracle/write.md b/mddocs/docs/connection/db_connection/oracle/write.md new file mode 100644 index 000000000..09e021c79 --- /dev/null +++ b/mddocs/docs/connection/db_connection/oracle/write.md @@ -0,0 +1,56 @@ +(oracle-write)= + +# Writing to Oracle using `DBWriter` + +For writing data to Oracle, use {obj}`DBWriter `. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`oracle-types` +``` + +```{eval-rst} +.. warning:: + + It is always recommended to create table explicitly using :ref:`Oracle.execute ` + instead of relying on Spark's table DDL generation. + + This is because Spark's DDL generator can create columns with different precision and types than it is expected, + causing precision loss or other issues. +``` + +## Examples + +```python +from onetl.connection import Oracle +from onetl.db import DBWriter + +oracle = Oracle(...) + +df = ... # data is here + +writer = DBWriter( + connection=oracle, + target="schema.table", + options=Oracle.WriteOptions(if_exists="append"), +) + +writer.run(df) +``` + +## Options + +Method above accepts {obj}`OracleWriteOptions ` + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.oracle.options +``` + +```{eval-rst} +.. autopydantic_model:: OracleWriteOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/postgres/connection.md b/mddocs/docs/connection/db_connection/postgres/connection.md new file mode 100644 index 000000000..80c59021c --- /dev/null +++ b/mddocs/docs/connection/db_connection/postgres/connection.md @@ -0,0 +1,12 @@ +(postgres-connection)= + +# Postgres connection + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.postgres.connection +``` + +```{eval-rst} +.. autoclass:: Postgres + :members: get_packages, check +``` diff --git a/mddocs/docs/connection/db_connection/postgres/execute.md b/mddocs/docs/connection/db_connection/postgres/execute.md new file mode 100644 index 000000000..6964b43e9 --- /dev/null +++ b/mddocs/docs/connection/db_connection/postgres/execute.md @@ -0,0 +1,113 @@ +(postgres-execute)= + +# Executing statements in Postgres + +```{eval-rst} +.. warning:: + + Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + + Do **NOT** use them to read large amounts of data. Use :ref:`DBReader ` or :ref:`Postgres.sql ` instead. +``` + +## How to + +There are 2 ways to execute some statement in Postgres + +### Use `Postgres.fetch` + +Use this method to execute some `SELECT` query which returns **small number or rows**, like reading +Postgres config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts {obj}`Postgres.FetchOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`postgres-types`. +``` + +#### Syntax support + +This method supports **any** query syntax supported by Postgres, like: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Postgres + +postgres = Postgres(...) + +df = postgres.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=Postgres.FetchOptions(queryTimeout=10), +) +postgres.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `Postgres.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts {obj}`Postgres.ExecuteOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Postgres, like: + +- ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +- ✅︎ `ALTER ...` +- ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +- ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +- ✅︎ `CALL procedure(arg1, arg2) ...` +- ✅︎ `SELECT func(arg1, arg2)` or `{call func(arg1, arg2)}` - special syntax for calling functions +- ✅︎ other statements not mentioned here +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Postgres + +postgres = Postgres(...) + +postgres.execute("DROP TABLE schema.table") +postgres.execute( + """ + CREATE TABLE schema.table ( + id bigint GENERATED ALWAYS AS IDENTITY, + key text, + value real + ) + """, + options=Postgres.ExecuteOptions(queryTimeout=10), +) +``` + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.postgres.options +``` + +```{eval-rst} +.. autopydantic_model:: PostgresFetchOptions + :inherited-members: GenericOptions + :member-order: bysource + +``` + +```{eval-rst} +.. autopydantic_model:: PostgresExecuteOptions + :inherited-members: GenericOptions + :member-order: bysource +``` diff --git a/mddocs/docs/connection/db_connection/postgres/index.md b/mddocs/docs/connection/db_connection/postgres/index.md new file mode 100644 index 000000000..56442bfb0 --- /dev/null +++ b/mddocs/docs/connection/db_connection/postgres/index.md @@ -0,0 +1,28 @@ +(postgres)= + +# Postgres + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +connection +``` + +```{toctree} +:caption: Operations +:maxdepth: 1 + +read +sql +write +execute +``` + +```{toctree} +:caption: Troubleshooting +:maxdepth: 1 + +types +``` diff --git a/mddocs/docs/connection/db_connection/postgres/prerequisites.md b/mddocs/docs/connection/db_connection/postgres/prerequisites.md new file mode 100644 index 000000000..d2221552d --- /dev/null +++ b/mddocs/docs/connection/db_connection/postgres/prerequisites.md @@ -0,0 +1,71 @@ +(postgres-prerequisites)= + +# Prerequisites + +## Version Compatibility + +- PostgreSQL server versions: + : - Officially declared: 8.2 - 17 + - Actually tested: 9.4.26, 17.3 +- Spark versions: 2.3.x - 3.5.x +- Java versions: 8 - 20 + +See [official documentation](https://jdbc.postgresql.org/). + +## Installing PySpark + +To use Postgres connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Connecting to Postgres + +### Allowing connection to Postgres instance + +Ask your Postgres administrator to allow your user (and probably IP) to connect to instance, +e.g. by updating `pg_hba.conf` file. + +See [official documentation](https://www.postgresql.org/docs/current/auth-pg-hba-conf.html). + +### Connection port + +Connection is usually performed to port 5432. Port may differ for different Postgres instances. +Please ask your Postgres administrator to provide required information. + +### Connection host + +It is possible to connect to Postgres by using either DNS name of host or it's IP address. + +If you're using Postgres cluster, it is currently possible to connect only to **one specific node**. +Connecting to multiple nodes to perform load balancing, as well as automatic failover to new master/replica are not supported. + +### Required grants + +Ask your Postgres cluster administrator to set following grants for a user, +used for creating a connection: + +```{eval-rst} +.. tabs:: + + .. code-tab:: sql Read + Write + + -- allow creating tables in specific schema + GRANT USAGE, CREATE ON SCHEMA myschema TO username; + + -- allow read & write access to specific table + GRANT SELECT, INSERT ON myschema.mytable TO username; + + -- only if if_exists="replace_entire_table" is used: + GRANT TRUNCATE ON myschema.mytable TO username; + + .. code-tab:: sql Read only + + -- allow creating tables in specific schema + GRANT USAGE ON SCHEMA myschema TO username; + + -- allow read access to specific table + GRANT SELECT ON myschema.mytable TO username; +``` + +More details can be found in [official documentation](https://www.postgresql.org/docs/current/sql-grant.html). diff --git a/mddocs/docs/connection/db_connection/postgres/read.md b/mddocs/docs/connection/db_connection/postgres/read.md new file mode 100644 index 000000000..71db1908c --- /dev/null +++ b/mddocs/docs/connection/db_connection/postgres/read.md @@ -0,0 +1,91 @@ +(postgres-read)= + +# Reading from Postgres using `DBReader` + +{obj}`DBReader ` supports {ref}`strategy` for incremental data reading, +but does not support custom queries, like `JOIN`. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`postgres-types` +``` + +## Supported DBReader features + +- ✅︎ `columns` +- ✅︎ `where` +- ✅︎ `hwm`, supported strategies: +- - ✅︎ {ref}`snapshot-strategy` +- - ✅︎ {ref}`incremental-strategy` +- - ✅︎ {ref}`snapshot-batch-strategy` +- - ✅︎ {ref}`incremental-batch-strategy` +- ❌ `hint` (is not supported by Postgres) +- ❌ `df_schema` +- ✅︎ `options` (see {obj}`Postgres.ReadOptions `) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Postgres +from onetl.db import DBReader + +postgres = Postgres(...) + +reader = DBReader( + connection=postgres, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + options=Postgres.ReadOptions(partitionColumn="id", numPartitions=10), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Postgres +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +postgres = Postgres(...) + +reader = DBReader( + connection=postgres, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="postgres_hwm", expression="updated_dt"), + options=Postgres.ReadOptions(partitionColumn="id", numPartitions=10), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Postgres to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Postgres to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.postgres.options +``` + +```{eval-rst} +.. autopydantic_model:: PostgresReadOptions + :inherited-members: GenericOptions + :member-order: bysource +``` diff --git a/mddocs/docs/connection/db_connection/postgres/sql.md b/mddocs/docs/connection/db_connection/postgres/sql.md new file mode 100644 index 000000000..adceab0dc --- /dev/null +++ b/mddocs/docs/connection/db_connection/postgres/sql.md @@ -0,0 +1,80 @@ +(postgres-sql)= + +# Reading from Postgres using `Postgres.sql` + +`Postgres.sql` allows passing custom SQL query, but does not support incremental strategies. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`postgres-types` +``` + +```{eval-rst} +.. warning:: + + Statement is executed in **read-write** connection, so if you're calling some functions/procedures with DDL/DML statements inside, + they can change data in your database. +``` + +## Syntax support + +Only queries with the following syntax are supported: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import Postgres + +postgres = Postgres(...) +df = postgres.sql( + """ + SELECT + id, + key, + CAST(value AS text) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """, + options=Postgres.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from Postgres to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from Postgres to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.postgres.options +``` + +```{eval-rst} +.. autopydantic_model:: PostgresSQLOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/postgres/types.md b/mddocs/docs/connection/db_connection/postgres/types.md new file mode 100644 index 000000000..3b3540383 --- /dev/null +++ b/mddocs/docs/connection/db_connection/postgres/types.md @@ -0,0 +1,490 @@ +(postgres-types)= + +# Postgres \<-> Spark type mapping + +```{eval-rst} +.. note:: + + The results below are valid for Spark 3.5.5, and may differ on other Spark versions. +``` + +## Type detection & casting + +Spark's DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from Postgres + +This is how Postgres connector performs this: + +- For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and Postgres type. +- Find corresponding `Postgres type (read)` → `Spark type` combination (see below) for each DataFrame column [^footnote-1]. If no combination is found, raise exception. +- Create DataFrame from query with specific column names and Spark types. + +[^footnote-1]: All Postgres types that doesn't have corresponding Java type are converted to `String`. + +### Writing to some existing Postgres table + +This is how Postgres connector performs this: + +- Get names of columns in DataFrame. [^footnote-1] +- Perform `SELECT * FROM table LIMIT 0` query. +- Take only columns present in DataFrame (by name, case insensitive) [^footnote-2]. For each found column get Postgres type. +- Find corresponding `Spark type` → `Postgres type (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- If `Postgres type (write)` match `Postgres type (read)`, no additional casts will be performed, DataFrame column will be written to Postgres as is. +- If `Postgres type (write)` does not match `Postgres type (read)`, DataFrame column will be casted to target column type **on Postgres side**. + For example, you can write column with text data to `int` column, if column contains valid integer values within supported value range and precision [^footnote-3]. + +[^footnote-2]: This allows to write data to tables with `DEFAULT` and `GENERATED` columns - if DataFrame has no such column, + it will be populated by Postgres. + +[^footnote-3]: This is true only if either DataFrame column is a `StringType()`, or target column is `text` type. + + But other types cannot be silently converted, like `bytea -> bit(N)`. This requires explicit casting, see [Manual conversion to string]. + +### Create new table using Spark + +```{eval-rst} +.. warning:: + + ABSOLUTELY NOT RECOMMENDED! +``` + +This is how Postgres connector performs this: + +- Find corresponding `Spark type` → `Postgres type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- Generate DDL for creating table in Postgres, like `CREATE TABLE (col1 ...)`, and run it. +- Write DataFrame to created table as is. + +But Postgres connector support only limited number of types and almost no custom clauses (like `PARTITION BY`, `INDEX`, etc). +So instead of relying on Spark to create tables: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + writer = DBWriter( + connection=postgres, + target="public.table", + options=Postgres.WriteOptions( + if_exists="append", + createTableOptions="PARTITION BY RANGE (id)", + ), + ) + writer.run(df) +``` + +Always prefer creating table with desired DDL **BEFORE WRITING DATA**: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + postgres.execute( + """ + CREATE TABLE public.table ( + id bigint, + business_dt timestamp(6), + value json + ) + PARTITION BY RANGE (Id) + """, + ) + + writer = DBWriter( + connection=postgres, + target="public.table", + options=Postgres.WriteOptions(if_exists="append"), + ) + writer.run(df) +``` + +See Postgres [CREATE TABLE](https://www.postgresql.org/docs/current/sql-createtable.html) documentation. + +## Supported types + +### References + +See [List of Postgres types](https://www.postgresql.org/docs/current/datatype.html). + +Here you can find source code with type conversions: + +- [Postgres \<-> JDBC](https://github.com/pgjdbc/pgjdbc/blob/REL42.6.0/pgjdbc/src/main/java/org/postgresql/jdbc/TypeInfoCache.java#L78-L112) +- [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/PostgresDialect.scala#L52-L108) +- [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/PostgresDialect.scala#L118-L132) + +### Numeric types + +```{eval-rst} ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | ++==================================+===================================+===============================+=========================+ +| ``decimal`` | ``DecimalType(P=38, S=18)`` | ``decimal(P=38, S=18)`` | ``decimal`` (unbounded) | ++----------------------------------+-----------------------------------+-------------------------------+ | +| ``decimal(P=0..38)`` | ``DecimalType(P=0..38, S=0)`` | ``decimal(P=0..38, S=0)`` | | ++----------------------------------+-----------------------------------+-------------------------------+ | +| ``decimal(P=0..38, S=0..38)`` | ``DecimalType(P=0..38, S=0..38)`` | ``decimal(P=0..38, S=0..38)`` | | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``decimal(P=39.., S=0..)`` | unsupported [4]_ | | | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``decimal(P=.., S=..-1)`` | unsupported [5]_ | | | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``real`` | ``FloatType()`` | ``real`` | ``real`` | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``double precision`` | ``DoubleType()`` | ``double precision`` | ``double precision`` | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``smallint`` | ``ShortType()`` | ``smallint`` | ``smallint`` | ++----------------------------------+-----------------------------------+ | | +| ``-`` | ``ByteType()`` | | | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``integer`` | ``IntegerType()`` | ``integer`` | ``integer`` | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``bigint`` | ``LongType()`` | ``bigint`` | ``bigint`` | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``money`` | ``StringType()`` [1]_ | ``text`` | ``text`` | ++----------------------------------+ | | | +| ``int4range`` | | | | ++----------------------------------+ | | | +| ``int8range`` | | | | ++----------------------------------+ | | | +| ``numrange`` | | | | ++----------------------------------+ | | | +| ``int2vector`` | | | | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +``` + +[^footnote-4]: Postgres support decimal types with unlimited precision. + + But Spark's `DecimalType(P, S)` supports maximum `P=38` (128 bit). It is impossible to read, write or operate with values of larger precision, + this leads to an exception. + +[^footnote-5]: Postgres support decimal types with negative scale, like `decimal(38, -10)`. Spark doesn't. + +### Temporal types + +```{eval-rst} ++------------------------------------+------------------------------+-----------------------+-------------------------+ +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | ++====================================+==============================+=======================+=========================+ +| ``date`` | ``DateType()`` | ``date`` | ``date`` | ++------------------------------------+------------------------------+-----------------------+-------------------------+ +| ``time`` | ``TimestampType()``, | ``timestamp(6)`` | ``timestamp(6)`` | ++------------------------------------+ with time format quirks [6]_ | | | +| ``time(0..6)`` | | | | ++------------------------------------+ | | | +| ``time with time zone`` | | | | ++------------------------------------+ | | | +| ``time(0..6) with time zone`` | | | | ++------------------------------------+------------------------------+-----------------------+-------------------------+ +| ``timestamp`` | ``TimestampType()`` | ``timestamp(6)`` | ``timestamp(6)`` | ++------------------------------------+ | | | +| ``timestamp(0..6)`` | | | | ++------------------------------------+ | | | +| ``timestamp with time zone`` | | | | ++------------------------------------+ | | | +| ``timestamp(0..6) with time zone`` | | | | ++------------------------------------+------------------------------+-----------------------+-------------------------+ +| ``-`` | ``TimestampNTZType()`` | ``timestamp(6)`` | ``timestamp(6)`` | ++------------------------------------+------------------------------+-----------------------+-------------------------+ +| ``interval`` of any precision | ``StringType()`` [1]_ | ``text`` | ``text`` | ++------------------------------------+------------------------------+-----------------------+-------------------------+ +| ``-`` | ``DayTimeIntervalType()`` | unsupported | unsupported | ++------------------------------------+------------------------------+-----------------------+-------------------------+ +| ``-`` | ``YearMonthIntervalType()`` | unsupported | unsupported | ++------------------------------------+------------------------------+-----------------------+-------------------------+ +| ``daterange`` | ``StringType()`` [1]_ | ``text`` | ``text`` | ++------------------------------------+ | | | +| ``tsrange`` | | | | ++------------------------------------+ | | | +| ``tstzrange`` | | | | ++------------------------------------+------------------------------+-----------------------+-------------------------+ +``` + +```{eval-rst} +.. warning:: + + Note that types in Postgres and Spark have different value ranges: + + +---------------+---------------------------------+----------------------------------+---------------------+--------------------------------+--------------------------------+ + | Postgres type | Min value | Max value | Spark type | Min value | Max value | + +===============+=================================+==================================+=====================+================================+================================+ + | ``date`` | ``-4713-01-01`` | ``5874897-01-01`` | ``DateType()`` | ``0001-01-01`` | ``9999-12-31`` | + +---------------+---------------------------------+----------------------------------+---------------------+--------------------------------+--------------------------------+ + | ``timestamp`` | ``-4713-01-01 00:00:00.000000`` | ``294276-12-31 23:59:59.999999`` | ``TimestampType()`` | ``0001-01-01 00:00:00.000000`` | ``9999-12-31 23:59:59.999999`` | + +---------------+---------------------------------+----------------------------------+ | | | + | ``time`` | ``00:00:00.000000`` | ``24:00:00.000000`` | | | | + +---------------+---------------------------------+----------------------------------+---------------------+--------------------------------+--------------------------------+ + + So not all of values can be read from Postgres to Spark. + + References: + * `Postgres date/time types documentation `_ + * `Spark DateType documentation `_ + * `Spark TimestampType documentation `_ +``` + +[^footnote-6]: `time` type is the same as `timestamp` with date `1970-01-01`. So instead of reading data from Postgres like `23:59:59` + it is actually read `1970-01-01 23:59:59`, and vice versa. + +### String types + +```{eval-rst} ++-----------------------------+-----------------------+-----------------------+-------------------------+ +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | ++=============================+=======================+=======================+=========================+ +| ``character`` | ``StringType()`` | ``text`` | ``text`` | ++-----------------------------+ | | | +| ``character(N)`` | | | | ++-----------------------------+ | | | +| ``character varying`` | | | | ++-----------------------------+ | | | +| ``character varying(N)`` | | | | ++-----------------------------+ | | | +| ``text`` | | | | ++-----------------------------+ | | | +| ``json`` | | | | ++-----------------------------+ | | | +| ``jsonb`` | | | | ++-----------------------------+ | | | +| ``xml`` | | | | ++-----------------------------+-----------------------+ | | +| ``CREATE TYPE ... AS ENUM`` | ``StringType()`` [1]_ | | | ++-----------------------------+ | | | +| ``tsvector`` | | | | ++-----------------------------+ | | | +| ``tsquery`` | | | | ++-----------------------------+-----------------------+-----------------------+-------------------------+ +| ``-`` | ``CharType()`` | ``unsupported`` | ``unsupported`` | ++-----------------------------+-----------------------+-----------------------+-------------------------+ +| ``-`` | ``VarcharType()`` | ``unsupported`` | ``unsupported`` | ++-----------------------------+-----------------------+-----------------------+-------------------------+ +``` + +### Binary types + +```{eval-rst} ++--------------------------+-----------------------+-----------------------------+-------------------------+ +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | ++==========================+=======================+=============================+=========================+ +| ``boolean`` | ``BooleanType()`` | ``boolean`` | ``boolean`` | ++--------------------------+-----------------------+-----------------------------+-------------------------+ +| ``bit`` | ``BooleanType()`` | ``bool``, | ``bool`` | ++--------------------------+ | **cannot insert data** [3]_ | | +| ``bit(N=1)`` | | | | ++--------------------------+-----------------------+-----------------------------+-------------------------+ +| ``bit(N=2..)`` | ``ByteType()`` | ``bytea``, | ``bytea`` | +| | | **cannot insert data** [3]_ | | ++--------------------------+-----------------------+-----------------------------+-------------------------+ +| ``bit varying`` | ``StringType()`` [1]_ | ``text`` | ``text`` | ++--------------------------+ | | | +| ``bit varying(N)`` | | | | ++--------------------------+-----------------------+-----------------------------+-------------------------+ +| ``bytea`` | ``BinaryType()`` | ``bytea`` | ``bytea`` | ++--------------------------+-----------------------+-----------------------------+-------------------------+ +``` + +### Struct types + +```{eval-rst} ++--------------------------------+-----------------------+-----------------------+-------------------------+ +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | ++================================+=======================+=======================+=========================+ +| ``T[]`` | ``ArrayType(T)`` | ``T[]`` | ``T[]`` | ++--------------------------------+-----------------------+-----------------------+-------------------------+ +| ``T[][]`` | unsupported | | | ++--------------------------------+-----------------------+-----------------------+-------------------------+ +| ``CREATE TYPE sometype (...)`` | ``StringType()`` [1]_ | ``text`` | ``text`` | ++--------------------------------+-----------------------+-----------------------+-------------------------+ +| ``-`` | ``StructType()`` | unsupported | | ++--------------------------------+-----------------------+ | | +| ``-`` | ``MapType()`` | | | ++--------------------------------+-----------------------+-----------------------+-------------------------+ +``` + +### Network types + +```{eval-rst} ++----------------------+-----------------------+-----------------------+-------------------------+ +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | ++======================+=======================+=======================+=========================+ +| ``cidr`` | ``StringType()`` [1]_ | ``text`` | ``text`` | ++----------------------+ | | | +| ``inet`` | | | | ++----------------------+ | | | +| ``macaddr`` | | | | ++----------------------+ | | | +| ``macaddr8`` | | | | ++----------------------+-----------------------+-----------------------+-------------------------+ +``` + +### Geo types + +```{eval-rst} ++----------------------+-----------------------+-----------------------+-------------------------+ +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | ++======================+=======================+=======================+=========================+ +| ``circle`` | ``StringType()`` [1]_ | ``text`` | ``text`` | ++----------------------+ | | | +| ``box`` | | | | ++----------------------+ | | | +| ``line`` | | | | ++----------------------+ | | | +| ``lseg`` | | | | ++----------------------+ | | | +| ``path`` | | | | ++----------------------+ | | | +| ``point`` | | | | ++----------------------+ | | | +| ``polygon`` | | | | ++----------------------+ | | | +| ``polygon`` | | | | ++----------------------+-----------------------+-----------------------+-------------------------+ +``` + +## Explicit type cast + +### `DBReader` + +It is possible to explicitly cast column of unsupported type using `DBReader(columns=...)` syntax. + +For example, you can use `CAST(column AS text)` to convert data to string representation on Postgres side, and so it will be read as Spark's `StringType()`. + +It is also possible to use [to_json](https://www.postgresql.org/docs/current/functions-json.html) Postgres function to convert column of any type to string representation, and then parse this column on Spark side you can use the {obj}`JSON.parse_column ` method: + +```python +from pyspark.sql.types import IntegerType + +from onetl.connection import Postgres +from onetl.db import DBReader +from onetl.file.format import JSON + +postgres = Postgres(...) + +DBReader( + connection=postgres, + columns=[ + "id", + "supported_column", + "CAST(unsupported_column AS text) unsupported_column_str", + # or + "to_json(unsupported_column) array_column_json", + ], +) +df = reader.run() + +json_schema = StructType( + [ + StructField("id", IntegerType(), nullable=True), + StructField("name", StringType(), nullable=True), + ..., + ] +) +df = df.select( + df.id, + df.supported_column, + # explicit cast + df.unsupported_column_str.cast("integer").alias("parsed_integer"), + JSON().parse_column("array_column_json", json_schema).alias("json_string"), +) +``` + +### `DBWriter` + +It is always possible to convert data on the Spark side to a string, and then write it to a text column in a Postgres table. + +#### Using JSON.serialize_column + +You can use the {obj}`JSON.serialize_column ` method for data serialization: + +```python +from onetl.file.format import JSON +from pyspark.sql.functions import col + +from onetl.connection import Postgres +from onetl.db import DBWriter + +postgres = Postgres(...) + +postgres.execute( + """ + CREATE TABLE schema.target_table ( + id int, + supported_column timestamp, + array_column_json jsonb -- any column type, actually + ) + """, +) + +write_df = df.select( + df.id, + df.supported_column, + JSON().serialize_column(df.unsupported_column).alias("array_column_json"), +) + +writer = DBWriter( + connection=postgres, + target="schema.target_table", +) +writer.run(write_df) +``` + +Then you can parse this column on the Postgres side (for example, by creating a view): + +```sql +SELECT + id, + supported_column, + array_column_json->'0' AS array_item_0 +FROM + schema.target_table +``` + +To avoid casting the value on every table read you can use [GENERATED ALWAYS STORED](https://www.postgresql.org/docs/current/ddl-generated-columns.html) column, but this requires 2x space (for original and parsed value). + +#### Manual conversion to string + +Postgres connector also supports conversion text value directly to target column type, if this value has a proper format. + +For example, you can write data like `[123, 345)` to `int8range` type because Postgres allows cast `'[123, 345)'::int8range'`: + +```python +from pyspark.sql.ftypes import StringType +from pyspark.sql.functions import udf + +from onetl.connection import Postgres +from onetl.db import DBReader + +postgres = Postgres(...) + +postgres.execute( + """ + CREATE TABLE schema.target_table ( + id int, + range_column int8range -- any column type, actually + ) + """, +) + + +@udf(returnType=StringType()) +def array_to_range(value: tuple): + """This UDF allows to convert tuple[start, end] to Postgres' range format""" + start, end = value + return f"[{start},{end})" + + +write_df = df.select( + df.id, + array_to_range(df.range_column).alias("range_column"), +) + +writer = DBWriter( + connection=postgres, + target="schema.target_table", +) +writer.run(write_df) +``` + +This can be tricky to implement and may lead to longer write process. +But this does not require extra space on Postgres side, and allows to avoid explicit value cast on every table read. diff --git a/mddocs/docs/connection/db_connection/postgres/write.md b/mddocs/docs/connection/db_connection/postgres/write.md new file mode 100644 index 000000000..8f01de26a --- /dev/null +++ b/mddocs/docs/connection/db_connection/postgres/write.md @@ -0,0 +1,56 @@ +(postgres-write)= + +# Writing to Postgres using `DBWriter` + +For writing data to Postgres, use {obj}`DBWriter `. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`postgres-types` +``` + +```{eval-rst} +.. warning:: + + It is always recommended to create table explicitly using :ref:`Postgres.execute ` + instead of relying on Spark's table DDL generation. + + This is because Spark's DDL generator can create columns with different precision and types than it is expected, + causing precision loss or other issues. +``` + +## Examples + +```python +from onetl.connection import Postgres +from onetl.db import DBWriter + +postgres = Postgres(...) + +df = ... # data is here + +writer = DBWriter( + connection=postgres, + target="schema.table", + options=Postgres.WriteOptions(if_exists="append"), +) + +writer.run(df) +``` + +## Options + +Method above accepts {obj}`Postgres.WriteOptions ` + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.postgres.options +``` + +```{eval-rst} +.. autopydantic_model:: PostgresWriteOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/teradata/connection.md b/mddocs/docs/connection/db_connection/teradata/connection.md new file mode 100644 index 000000000..0804437cf --- /dev/null +++ b/mddocs/docs/connection/db_connection/teradata/connection.md @@ -0,0 +1,12 @@ +(teradata-connection)= + +# Teradata connection + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.teradata.connection +``` + +```{eval-rst} +.. autoclass:: Teradata + :members: get_packages, check +``` diff --git a/mddocs/docs/connection/db_connection/teradata/execute.md b/mddocs/docs/connection/db_connection/teradata/execute.md new file mode 100644 index 000000000..de1b018fe --- /dev/null +++ b/mddocs/docs/connection/db_connection/teradata/execute.md @@ -0,0 +1,110 @@ +(teradata-execute)= + +# Executing statements in Teradata + +```{eval-rst} +.. warning:: + + Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + + Do **NOT** use them to read large amounts of data. Use :ref:`DBReader ` or :ref:`Teradata.sql ` instead. +``` + +## How to + +There are 2 ways to execute some statement in Teradata + +### Use `Teradata.fetch` + +Use this method to execute some `SELECT` query which returns **small number or rows**, like reading +Teradata config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts {obj}`Teradata.FetchOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Teradata, like: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ✅︎ `SHOW ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Teradata + +teradata = Teradata(...) + +df = teradata.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=Teradata.FetchOptions(queryTimeout=10), +) +teradata.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `Teradata.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts {obj}`Teradata.ExecuteOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Teradata, like: + +- ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +- ✅︎ `ALTER ...` +- ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +- ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +- ✅︎ `CALL procedure(arg1, arg2) ...` or `{call procedure(arg1, arg2)}` - special syntax for calling procedure +- ✅︎ `EXECUTE macro(arg1, arg2)` +- ✅︎ `EXECUTE FUNCTION ...` +- ✅︎ other statements not mentioned here +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Teradata + +teradata = Teradata(...) + +teradata.execute("DROP TABLE database.table") +teradata.execute( + """ + CREATE MULTISET TABLE database.table AS ( + id BIGINT, + key VARCHAR, + value REAL + ) + NO PRIMARY INDEX + """, + options=Teradata.ExecuteOptions(queryTimeout=10), +) +``` + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.teradata.options +``` + +```{eval-rst} +.. autopydantic_model:: TeradataFetchOptions + :inherited-members: GenericOptions + :member-order: bysource + +``` + +```{eval-rst} +.. autopydantic_model:: TeradataExecuteOptions + :inherited-members: GenericOptions + :member-order: bysource +``` diff --git a/mddocs/docs/connection/db_connection/teradata/index.md b/mddocs/docs/connection/db_connection/teradata/index.md new file mode 100644 index 000000000..f86e25726 --- /dev/null +++ b/mddocs/docs/connection/db_connection/teradata/index.md @@ -0,0 +1,21 @@ +(teradata)= + +# Teradata + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +connection +``` + +```{toctree} +:caption: Operations +:maxdepth: 1 + +read +sql +write +execute +``` diff --git a/mddocs/docs/connection/db_connection/teradata/prerequisites.md b/mddocs/docs/connection/db_connection/teradata/prerequisites.md new file mode 100644 index 000000000..d180ffdeb --- /dev/null +++ b/mddocs/docs/connection/db_connection/teradata/prerequisites.md @@ -0,0 +1,57 @@ +(teradata-prerequisites)= + +# Prerequisites + +## Version Compatibility + +- Teradata server versions: + : - Officially declared: 16.10 - 20.0 + - Actually tested: 16.10 +- Spark versions: 2.3.x - 3.5.x +- Java versions: 8 - 20 + +See [official documentation](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/platformMatrix.html). + +## Installing PySpark + +To use Teradata connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Connecting to Teradata + +### Connection host + +It is possible to connect to Teradata by using either DNS name Parsing Engine (PE) host, or it's IP address. + +### Connection port + +Connection is usually performed to port `1025`. Port may differ for different Teradata instances. +Please ask your Teradata administrator to provide required information. + +### Required grants + +Ask your Teradata cluster administrator to set following grants for a user, +used for creating a connection: + +```{eval-rst} +.. tabs:: + + .. code-tab:: sql Read + Write + + -- allow creating tables in the target schema + GRANT CREATE TABLE ON database TO username; + + -- allow read & write access to specific table + GRANT SELECT, INSERT ON database.mytable TO username; + + .. code-tab:: sql Read only + + -- allow read access to specific table + GRANT SELECT ON database.mytable TO username; +``` + +See: +: - [Teradata access rights](https://www.dwhpro.com/teradata-access-rights/) + - [GRANT documentation](https://teradata.github.io/presto/docs/0.167-t/sql/grant.html) diff --git a/mddocs/docs/connection/db_connection/teradata/read.md b/mddocs/docs/connection/db_connection/teradata/read.md new file mode 100644 index 000000000..c57b09119 --- /dev/null +++ b/mddocs/docs/connection/db_connection/teradata/read.md @@ -0,0 +1,117 @@ +(teradata-read)= + +# Reading from Teradata using `DBReader` + +{obj}`DBReader ` supports {ref}`strategy` for incremental data reading, +but does not support custom queries, like `JOIN`. + +## Supported DBReader features + +- ✅︎ `columns` +- ✅︎ `where` +- ✅︎ `hwm`, supported strategies: +- - ✅︎ {ref}`snapshot-strategy` +- - ✅︎ {ref}`incremental-strategy` +- - ✅︎ {ref}`snapshot-batch-strategy` +- - ✅︎ {ref}`incremental-batch-strategy` +- ❌ `hint` (is not supported by Teradata) +- ❌ `df_schema` +- ✅︎ `options` (see {obj}`Teradata.ReadOptions `) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Teradata +from onetl.db import DBReader + +teradata = Teradata(...) + +reader = DBReader( + connection=teradata, + source="database.table", + columns=["id", "key", "CAST(value AS VARCHAR) value", "updated_dt"], + where="key = 'something'", + options=Teradata.ReadOptions( + partitioning_mode="hash", + partitionColumn="id", + numPartitions=10, + ), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Teradata +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +teradata = Teradata(...) + +reader = DBReader( + connection=teradata, + source="database.table", + columns=["id", "key", "CAST(value AS VARCHAR) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="teradata_hwm", expression="updated_dt"), + options=Teradata.ReadOptions( + partitioning_mode="hash", + partitionColumn="id", + numPartitions=10, + ), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Teradata to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Teradata to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +### Read data in parallel + +`DBReader` can read data in multiple parallel connections by passing `Teradata.ReadOptions(numPartitions=..., partitionColumn=...)`. + +In the example above, Spark opens 10 parallel connections, and data is evenly distributed between all these connections using expression +`HASHAMP(HASHBUCKET(HASHROW({partition_column}))) MOD {num_partitions}`. +This allows sending each Spark worker only some piece of data, reducing resource consumption. +`partition_column` here can be table column of any type. + +It is also possible to use `partitioning_mode="mod"` or `partitioning_mode="range"`, but in this case +`partition_column` have to be an integer, should not contain `NULL`, and values to be uniformly distributed. +It is also less performant than `partitioning_mode="hash"` due to Teradata `HASHAMP` implementation. + +### Do **NOT** use `TYPE=FASTEXPORT` + +Teradata supports several [different connection types](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/jdbcug_chapter_2.html#BABFGFAF): +: - `TYPE=DEFAULT` - perform plain `SELECT` queries + - `TYPE=FASTEXPORT` - uses special FastExport protocol for select queries + +But `TYPE=FASTEXPORT` uses exclusive lock on the source table, so it is impossible to use multiple Spark workers parallel data read. +This leads to sending all the data to just one Spark worker, which is slow and takes a lot of RAM. + +Prefer using `partitioning_mode="hash"` from example above. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.teradata.options +``` + +```{eval-rst} +.. autopydantic_model:: TeradataReadOptions + :inherited-members: GenericOptions + :member-order: bysource +``` diff --git a/mddocs/docs/connection/db_connection/teradata/sql.md b/mddocs/docs/connection/db_connection/teradata/sql.md new file mode 100644 index 000000000..ad0919dce --- /dev/null +++ b/mddocs/docs/connection/db_connection/teradata/sql.md @@ -0,0 +1,76 @@ +(teradata-sql)= + +# Reading from Teradata using `Teradata.sql` + +`Teradata.sql` allows passing custom SQL query, but does not support incremental strategies. + +```{eval-rst} +.. warning:: + + Statement is executed in **read-write** connection, so if you're calling some functions/procedures with DDL/DML statements inside, + they can change data in your database. +``` + +## Syntax support + +Only queries with the following syntax are supported: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ❌ `SHOW ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import Teradata + +teradata = Teradata(...) +df = teradata.sql( + """ + SELECT + id, + key, + CAST(value AS VARCHAR) AS value, + updated_at, + HASHAMP(HASHBUCKET(HASHROW(id))) MOD 10 AS part_column + FROM + database.mytable + WHERE + key = 'something' + """, + options=Teradata.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from Teradata to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from Teradata to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.teradata.options +``` + +```{eval-rst} +.. autopydantic_model:: TeradataSQLOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/db_connection/teradata/write.md b/mddocs/docs/connection/db_connection/teradata/write.md new file mode 100644 index 000000000..6d1f6644f --- /dev/null +++ b/mddocs/docs/connection/db_connection/teradata/write.md @@ -0,0 +1,120 @@ +(teradata-write)= + +# Writing to Teradata using `DBWriter` + +For writing data to Teradata, use {obj}`DBWriter `. + +```{eval-rst} +.. warning:: + + It is always recommended to create table explicitly using :ref:`Teradata.execute ` + instead of relying on Spark's table DDL generation. + + This is because Spark's DDL generator can create columns with different precision and types than it is expected, + causing precision loss or other issues. +``` + +## Examples + +```python +from onetl.connection import Teradata +from onetl.db import DBWriter + +teradata = Teradata( + ..., + extra={"TYPE": "FASTLOAD", "TMODE": "TERA"}, +) + +df = ... # data is here + +writer = DBWriter( + connection=teradata, + target="database.table", + options=Teradata.WriteOptions( + if_exists="append", + # avoid creating SET table, use MULTISET + createTableOptions="NO PRIMARY INDEX", + ), +) + +writer.run(df.repartition(1)) +``` + +## Recommendations + +### Number of connections + +Teradata is not MVCC based, so write operations take exclusive lock on the entire table. +So **it is impossible to write data to Teradata table in multiple parallel connections**, no exceptions. + +The only way to write to Teradata without making deadlocks is write dataframe with exactly 1 partition. + +It can be implemented using `df.repartition(1)`: + +```python +# do NOT use df.coalesce(1) as it can freeze +writer.run(df.repartition(1)) +``` + +This moves all the data to just one Spark worker, so it may consume a lot of RAM. It is usually require to increase `spark.executor.memory` to handle this. + +Another way is to write all dataframe partitions one-by-one: + +```python +from pyspark.sql.functions import spark_partition_id + +# get list of all partitions in the dataframe +partitions = sorted(df.select(spark_partition_id()).distinct().collect()) + +for partition in partitions: + # get only part of data within this exact partition + part_df = df.where(**partition.asDict()).coalesce(1) + + writer.run(part_df) +``` + +This require even data distribution for all partitions to avoid data skew and spikes of RAM consuming. + +### Choosing connection type + +Teradata supports several [different connection types](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/jdbcug_chapter_2.html#BABFGFAF): +: - `TYPE=DEFAULT` - perform plain `INSERT` queries + - `TYPE=FASTLOAD` - uses special FastLoad protocol for insert queries + +It is always recommended to use `TYPE=FASTLOAD` because: +: - It provides higher performance + - It properly handles inserting `NULL` values (`TYPE=DEFAULT` raises an exception) + +But it can be used only during write, not read. + +### Choosing transaction mode + +Teradata supports [2 different transaction modes](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/jdbcug_chapter_2.html#TMODESEC): +: - `TMODE=ANSI` + - `TMODE=TERA` + +Choosing one of the modes can alter connector behavior. For example: +: - Inserting data which exceeds table column length, like insert `CHAR(25)` to column with type `CHAR(24)`: + - - `TMODE=ANSI` - raises exception + - - `TMODE=TERA` - truncates input string to 24 symbols + - Creating table using Spark: + - - `TMODE=ANSI` - creates `MULTISET` table + - - `TMODE=TERA` - creates `SET` table with `PRIMARY KEY` is a first column in dataframe. + This can lead to slower insert time, because each row will be checked against a unique index. + Fortunately, this can be disabled by passing custom `createTableOptions`. + +## Options + +Method above accepts {obj}`Teradata.WriteOptions ` + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.teradata.options +``` + +```{eval-rst} +.. autopydantic_model:: TeradataWriteOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/connection/file_connection/ftp.md b/mddocs/docs/connection/file_connection/ftp.md new file mode 100644 index 000000000..8877f479e --- /dev/null +++ b/mddocs/docs/connection/file_connection/ftp.md @@ -0,0 +1,12 @@ +(ftp)= + +# FTP connection + +```{eval-rst} +.. currentmodule:: onetl.connection.file_connection.ftp +``` + +```{eval-rst} +.. autoclass:: FTP + :members: __init__, check, path_exists, is_file, is_dir, get_stat, resolve_dir, resolve_file, create_dir, remove_file, remove_dir, rename_dir, rename_file, list_dir, walk, download_file, upload_file +``` diff --git a/mddocs/docs/connection/file_connection/ftps.md b/mddocs/docs/connection/file_connection/ftps.md new file mode 100644 index 000000000..668be45b5 --- /dev/null +++ b/mddocs/docs/connection/file_connection/ftps.md @@ -0,0 +1,12 @@ +(ftps)= + +# FTPS connection + +```{eval-rst} +.. currentmodule:: onetl.connection.file_connection.ftps +``` + +```{eval-rst} +.. autoclass:: FTPS + :members: __init__, check, path_exists, is_file, is_dir, get_stat, resolve_dir, resolve_file, create_dir, remove_file, remove_dir, rename_dir, rename_file, list_dir, walk, download_file, upload_file +``` diff --git a/mddocs/docs/connection/file_connection/hdfs/connection.md b/mddocs/docs/connection/file_connection/hdfs/connection.md new file mode 100644 index 000000000..b1cffa679 --- /dev/null +++ b/mddocs/docs/connection/file_connection/hdfs/connection.md @@ -0,0 +1,12 @@ +(hdfs-connection)= + +# HDFS connection + +```{eval-rst} +.. currentmodule:: onetl.connection.file_connection.hdfs.connection +``` + +```{eval-rst} +.. autoclass:: HDFS + :members: get_current, check, path_exists, is_file, is_dir, get_stat, resolve_dir, resolve_file, create_dir, remove_file, remove_dir, rename_dir, rename_file, list_dir, walk, download_file, upload_file +``` diff --git a/mddocs/docs/connection/file_connection/hdfs/index.md b/mddocs/docs/connection/file_connection/hdfs/index.md new file mode 100644 index 000000000..a4c9fa148 --- /dev/null +++ b/mddocs/docs/connection/file_connection/hdfs/index.md @@ -0,0 +1,17 @@ +(hdfs)= + +# HDFS + +```{toctree} +:caption: Connection +:maxdepth: 1 + +connection +``` + +```{toctree} +:caption: For developers +:maxdepth: 1 + +slots +``` diff --git a/mddocs/docs/connection/file_connection/hdfs/slots.md b/mddocs/docs/connection/file_connection/hdfs/slots.md new file mode 100644 index 000000000..88f7e2943 --- /dev/null +++ b/mddocs/docs/connection/file_connection/hdfs/slots.md @@ -0,0 +1,13 @@ +(hdfs-slots)= + +# HDFS Slots + +```{eval-rst} +.. currentmodule:: onetl.connection.file_connection.hdfs.slots +``` + +```{eval-rst} +.. autoclass:: HDFSSlots + :members: normalize_cluster_name, normalize_namenode_host, get_known_clusters, get_cluster_namenodes, get_current_cluster, get_webhdfs_port, is_namenode_active + :member-order: bysource +``` diff --git a/mddocs/docs/connection/file_connection/index.md b/mddocs/docs/connection/file_connection/index.md new file mode 100644 index 000000000..4b581e0fd --- /dev/null +++ b/mddocs/docs/connection/file_connection/index.md @@ -0,0 +1,16 @@ +(file-connections)= + +# File Connections + +```{toctree} +:caption: File Connections +:maxdepth: 1 + +FTP +FTPS +HDFS +Samba +SFTP +S3 +Webdav +``` diff --git a/mddocs/docs/connection/file_connection/s3.md b/mddocs/docs/connection/file_connection/s3.md new file mode 100644 index 000000000..c69a3ed8b --- /dev/null +++ b/mddocs/docs/connection/file_connection/s3.md @@ -0,0 +1,12 @@ +(s3)= + +# S3 connection + +```{eval-rst} +.. currentmodule:: onetl.connection.file_connection.s3 +``` + +```{eval-rst} +.. autoclass:: S3 + :members: __init__, check, path_exists, is_file, is_dir, get_stat, resolve_dir, resolve_file, create_dir, remove_file, remove_dir, rename_file, list_dir, walk, download_file, upload_file +``` diff --git a/mddocs/docs/connection/file_connection/samba.md b/mddocs/docs/connection/file_connection/samba.md new file mode 100644 index 000000000..29f9f3f81 --- /dev/null +++ b/mddocs/docs/connection/file_connection/samba.md @@ -0,0 +1,12 @@ +(samba)= + +# Samba connection + +```{eval-rst} +.. currentmodule:: onetl.connection.file_connection.samba +``` + +```{eval-rst} +.. autoclass:: Samba + :members: __init__, check, path_exists, is_file, is_dir, get_stat, resolve_dir, resolve_file, create_dir, remove_file, remove_dir, rename_file, list_dir, download_file, upload_file +``` diff --git a/mddocs/docs/connection/file_connection/sftp.md b/mddocs/docs/connection/file_connection/sftp.md new file mode 100644 index 000000000..45c7affa6 --- /dev/null +++ b/mddocs/docs/connection/file_connection/sftp.md @@ -0,0 +1,12 @@ +(sftp)= + +# SFTP connection + +```{eval-rst} +.. currentmodule:: onetl.connection.file_connection.sftp +``` + +```{eval-rst} +.. autoclass:: SFTP + :members: __init__, check, path_exists, is_file, is_dir, get_stat, resolve_dir, resolve_file, create_dir, remove_file, remove_dir, rename_dir, rename_file, list_dir, walk, download_file, upload_file +``` diff --git a/mddocs/docs/connection/file_connection/webdav.md b/mddocs/docs/connection/file_connection/webdav.md new file mode 100644 index 000000000..5fd5ba006 --- /dev/null +++ b/mddocs/docs/connection/file_connection/webdav.md @@ -0,0 +1,12 @@ +(webdav)= + +# WebDAV connection + +```{eval-rst} +.. currentmodule:: onetl.connection.file_connection.webdav +``` + +```{eval-rst} +.. autoclass:: WebDAV + :members: __init__, check, path_exists, is_file, is_dir, get_stat, resolve_dir, resolve_file, create_dir, remove_file, remove_dir, rename_file, list_dir, walk, download_file, upload_file +``` diff --git a/mddocs/docs/connection/file_df_connection/base.md b/mddocs/docs/connection/file_df_connection/base.md new file mode 100644 index 000000000..4b3935bf7 --- /dev/null +++ b/mddocs/docs/connection/file_df_connection/base.md @@ -0,0 +1,12 @@ +(base-file-df-connection)= + +# Base interface + +```{eval-rst} +.. currentmodule:: onetl.base.base_file_df_connection +``` + +```{eval-rst} +.. autoclass:: BaseFileDFConnection + :members: check, check_if_format_supported, read_files_as_df, write_df_as_files +``` diff --git a/mddocs/docs/connection/file_df_connection/index.md b/mddocs/docs/connection/file_df_connection/index.md new file mode 100644 index 000000000..8f8d24bf0 --- /dev/null +++ b/mddocs/docs/connection/file_df_connection/index.md @@ -0,0 +1,19 @@ +(file-df-connections)= + +# File DataFrame Connections + +```{toctree} +:caption: File DataFrame Connections +:maxdepth: 2 + +Spark LocalFS +Spark HDFS +Spark S3 +``` + +```{toctree} +:caption: For developers +:maxdepth: 1 + +base +``` diff --git a/mddocs/docs/connection/file_df_connection/spark_hdfs/connection.md b/mddocs/docs/connection/file_df_connection/spark_hdfs/connection.md new file mode 100644 index 000000000..08b8e4c3b --- /dev/null +++ b/mddocs/docs/connection/file_df_connection/spark_hdfs/connection.md @@ -0,0 +1,12 @@ +(spark-hdfs-connection)= + +# Spark HDFS Connection + +```{eval-rst} +.. currentmodule:: onetl.connection.file_df_connection.spark_hdfs.connection +``` + +```{eval-rst} +.. autoclass:: SparkHDFS + :members: check, get_current +``` diff --git a/mddocs/docs/connection/file_df_connection/spark_hdfs/index.md b/mddocs/docs/connection/file_df_connection/spark_hdfs/index.md new file mode 100644 index 000000000..77d347d7a --- /dev/null +++ b/mddocs/docs/connection/file_df_connection/spark_hdfs/index.md @@ -0,0 +1,18 @@ +(spark-hdfs)= + +# Spark HDFS + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +Connection +``` + +```{toctree} +:caption: For developers +:maxdepth: 1 + +Slots +``` diff --git a/mddocs/docs/connection/file_df_connection/spark_hdfs/prerequisites.md b/mddocs/docs/connection/file_df_connection/spark_hdfs/prerequisites.md new file mode 100644 index 000000000..f946b3a70 --- /dev/null +++ b/mddocs/docs/connection/file_df_connection/spark_hdfs/prerequisites.md @@ -0,0 +1,46 @@ +(spark-hdfs-prerequisites)= + +# Prerequisites + +## Version Compatibility + +- Hadoop versions: 2.x, 3.x (only with Hadoop 3.x libraries) +- Spark versions: 2.3.x - 3.5.x +- Java versions: 8 - 20 + +## Installing PySpark + +To use SparkHDFS connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Using Kerberos + +Some of Hadoop managed clusters use Kerberos authentication. In this case, you should call [kinit](https://web.mit.edu/kerberos/krb5-1.12/doc/user/user_commands/kinit.html) command +**BEFORE** starting Spark session to generate Kerberos ticket. See {ref}`install-kerberos`. + +Sometimes it is also required to pass keytab file to Spark config, allowing Spark executors to generate own Kerberos tickets: + +```{eval-rst} +.. tabs:: + + .. code-tab:: python Spark 3 + + SparkSession.builder + .option("spark.kerberos.access.hadoopFileSystems", "hdfs://namenode1.domain.com:9820,hdfs://namenode2.domain.com:9820") + .option("spark.kerberos.principal", "user") + .option("spark.kerberos.keytab", "/path/to/keytab") + .gerOrCreate() + + .. code-tab:: python Spark 2 + + SparkSession.builder + .option("spark.yarn.access.hadoopFileSystems", "hdfs://namenode1.domain.com:9820,hdfs://namenode2.domain.com:9820") + .option("spark.yarn.principal", "user") + .option("spark.yarn.keytab", "/path/to/keytab") + .gerOrCreate() +``` + +See [Spark security documentation](https://spark.apache.org/docs/latest/security.html#kerberos) +for more details. diff --git a/mddocs/docs/connection/file_df_connection/spark_hdfs/slots.md b/mddocs/docs/connection/file_df_connection/spark_hdfs/slots.md new file mode 100644 index 000000000..715578579 --- /dev/null +++ b/mddocs/docs/connection/file_df_connection/spark_hdfs/slots.md @@ -0,0 +1,13 @@ +(spark-hdfs-slots)= + +# Spark HDFS Slots + +```{eval-rst} +.. currentmodule:: onetl.connection.file_df_connection.spark_hdfs.slots +``` + +```{eval-rst} +.. autoclass:: SparkHDFSSlots + :members: normalize_cluster_name, normalize_namenode_host, get_known_clusters, get_cluster_namenodes, get_current_cluster, get_ipc_port, is_namenode_active + :member-order: bysource +``` diff --git a/mddocs/docs/connection/file_df_connection/spark_local_fs.md b/mddocs/docs/connection/file_df_connection/spark_local_fs.md new file mode 100644 index 000000000..524d1c676 --- /dev/null +++ b/mddocs/docs/connection/file_df_connection/spark_local_fs.md @@ -0,0 +1,12 @@ +(spark-local-fs)= + +# Spark LocalFS + +```{eval-rst} +.. currentmodule:: onetl.connection.file_df_connection.spark_local_fs +``` + +```{eval-rst} +.. autoclass:: SparkLocalFS + :members: check +``` diff --git a/mddocs/docs/connection/file_df_connection/spark_s3/connection.md b/mddocs/docs/connection/file_df_connection/spark_s3/connection.md new file mode 100644 index 000000000..82de3999f --- /dev/null +++ b/mddocs/docs/connection/file_df_connection/spark_s3/connection.md @@ -0,0 +1,12 @@ +(spark-s3-connection)= + +# Spark S3 Connection + +```{eval-rst} +.. currentmodule:: onetl.connection.file_df_connection.spark_s3.connection +``` + +```{eval-rst} +.. autoclass:: SparkS3 + :members: check, close, get_packages, get_exclude_packages +``` diff --git a/mddocs/docs/connection/file_df_connection/spark_s3/index.md b/mddocs/docs/connection/file_df_connection/spark_s3/index.md new file mode 100644 index 000000000..1ecf94737 --- /dev/null +++ b/mddocs/docs/connection/file_df_connection/spark_s3/index.md @@ -0,0 +1,12 @@ +(spark-s3)= + +# Spark S3 + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +Connection +Troubleshooting +``` diff --git a/mddocs/docs/connection/file_df_connection/spark_s3/prerequisites.md b/mddocs/docs/connection/file_df_connection/spark_s3/prerequisites.md new file mode 100644 index 000000000..9135275f3 --- /dev/null +++ b/mddocs/docs/connection/file_df_connection/spark_s3/prerequisites.md @@ -0,0 +1,61 @@ +(spark-s3-prerequisites)= + +# Prerequisites + +## Version Compatibility + +- Spark versions: 3.2.x - 3.5.x (only with Hadoop 3.x libraries) +- Java versions: 8 - 20 + +## Installing PySpark + +To use SparkS3 connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Connecting to S3 + +### Bucket access style + +AWS and some other S3 cloud providers allows bucket access using domain style only, e.g. `https://mybucket.s3provider.com`. + +Other implementations, like Minio, by default allows path style access only, e.g. `https://s3provider.com/mybucket` +(see [MINIO_DOMAIN](https://min.io/docs/minio/linux/reference/minio-server/minio-server.html#envvar.MINIO_DOMAIN)). + +You should set `path.style.access` to `True` or `False`, to choose the preferred style. + +### Authentication + +Different S3 instances can use different authentication methods, like: +: - `access_key + secret_key` (or username + password) + - `access_key + secret_key + session_token` + +Usually these are just passed to SparkS3 constructor: + +```python +SparkS3( + access_key=..., + secret_key=..., + session_token=..., +) +``` + +But some S3 cloud providers, like AWS, may require custom credential providers. You can pass them like: + +```python +SparkS3( + extra={ + # provider class + "aws.credentials.provider": "org.apache.hadoop.fs.s3a.auth.AssumedRoleCredentialProvider", + # other options, if needed + "assumed.role.arn": "arn:aws:iam::90066806600238:role/s3-restricted", + }, +) +``` + +See [Hadoop-AWS](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/index.html#Changing_Authentication_Providers) documentation. + +## Troubleshooting + +See {ref}`spark-s3-troubleshooting`. diff --git a/mddocs/docs/connection/file_df_connection/spark_s3/troubleshooting.md b/mddocs/docs/connection/file_df_connection/spark_s3/troubleshooting.md new file mode 100644 index 000000000..5f998779d --- /dev/null +++ b/mddocs/docs/connection/file_df_connection/spark_s3/troubleshooting.md @@ -0,0 +1,377 @@ +(spark-s3-troubleshooting)= + +# Spark S3 Troubleshooting + +```{eval-rst} +.. note:: + + General guide: :ref:`troubleshooting`. +``` + +More details: + +- [Hadoop AWS Troubleshooting Guide](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/troubleshooting_s3a.html) +- [Hadoop AWS Performance Guide](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/performance.html) +- [Spark integration with Cloud Infrastructures](https://spark.apache.org/docs/latest/cloud-integration.html) + +## `SparkS3.check()` and other methods hang + +### Details + +S3 may not respond for connection attempts for a long time if it's under heavy load. +To handle this, Hadoop AWS library has retry mechanism. By default it retries 7 times with 500ms interval. + +Hadoop AWS is based on AWS SDK library, which also has retry mechanism. This mechanism is not disabled because it handles different +errors than Hadoop AWS, so they complement each other. Default number of attempts in AWS SDK is 20 with minimal 5s interval, +which is exponentially increasing with each failed attempt. + +It is not a problem if S3 source is not accessible at all, like hostname cannot be resolved, or port is not opened. +These errors are not recoverable, and retry mechanism is not activated. + +But errors like SSL issues, are considered recoverable, and this causing retry of retry over increasing interval. +So user is waiting for [almost 15 minutes](https://issues.apache.org/jira/browse/HADOOP-18839) just to get exception message. + +### How to determine reason + +#### Make logging more verbose + +Change Spark session log level to {ref}`DEBUG ` to print result of each attempt. +Resulting logs will look like this + +```{eval-rst} +.. dropdown:: See log + + .. code:: text + + 23/08/03 11:25:10 DEBUG S3AFileSystem: Using S3ABlockOutputStream with buffer = disk; block=67108864; queue limit=4 + 23/08/03 11:25:10 DEBUG S3Guard: Metastore option source [core-default.xml] + 23/08/03 11:25:10 DEBUG S3Guard: Using NullMetadataStore metadata store for s3a filesystem + 23/08/03 11:25:10 DEBUG S3AFileSystem: S3Guard is disabled on this bucket: test-bucket + 23/08/03 11:25:10 DEBUG DirectoryPolicyImpl: Directory markers will be deleted + 23/08/03 11:25:10 DEBUG S3AFileSystem: Directory marker retention policy is DirectoryMarkerRetention{policy='delete'} + 23/08/03 11:25:10 DEBUG S3AUtils: Value of fs.s3a.multipart.purge.age is 86400 + 23/08/03 11:25:10 DEBUG S3AUtils: Value of fs.s3a.bulk.delete.page.size is 250 + 23/08/03 11:25:10 DEBUG FileSystem: Creating FS s3a://test-bucket/fake: duration 0:01.029s + 23/08/03 11:25:10 DEBUG IOStatisticsStoreImpl: Incrementing counter op_is_directory by 1 with final value 1 + 23/08/03 11:25:10 DEBUG S3AFileSystem: Getting path status for s3a://test-bucket/fake (fake); needEmptyDirectory=false + 23/08/03 11:25:10 DEBUG S3AFileSystem: S3GetFileStatus s3a://test-bucket/fake + 23/08/03 11:25:10 DEBUG S3AFileSystem: LIST List test-bucket:/fake/ delimiter=/ keys=2 requester pays=false + 23/08/03 11:25:10 DEBUG S3AFileSystem: Starting: LIST + 23/08/03 11:25:10 DEBUG IOStatisticsStoreImpl: Incrementing counter object_list_request by 1 with final value 1 + 23/08/03 11:25:10 DEBUG AWSCredentialProviderList: Using credentials from SimpleAWSCredentialsProvider + 23/08/03 11:25:10 DEBUG request: Sending Request: GET https://test-bucket.localhost:9000 / Parameters: ({"list-type":["2"],"delimiter":["/"],"max-keys":["2"],"prefix":["fake/"],"fetch-owner":["false"]}Headers: (amz-sdk-invocation-id: e6d62603-96e4-a80f-10a1-816e0822bc71, Content-Type: application/octet-stream, User-Agent: Hadoop 3.3.4, aws-sdk-java/1.12.262 Linux/6.4.7-1-MANJARO OpenJDK_64-Bit_Server_VM/25.292-b10 java/1.8.0_292 scala/2.12.17 vendor/AdoptOpenJDK cfg/retry-mode/legacy, ) + 23/08/03 11:25:10 DEBUG AWS4Signer: AWS4 Canonical Request: '"GET + / + delimiter=%2F&fetch-owner=false&list-type=2&max-keys=2&prefix=fake%2F + amz-sdk-invocation-id:e6d62603-96e4-a80f-10a1-816e0822bc71 + amz-sdk-request:attempt=1;max=21 + amz-sdk-retry:0/0/500 + content-type:application/octet-stream + host:test-bucket.localhost:9000 + user-agent:Hadoop 3.3.4, aws-sdk-java/1.12.262 Linux/6.4.7-1-MANJARO OpenJDK_64-Bit_Server_VM/25.292-b10 java/1.8.0_292 scala/2.12.17 vendor/AdoptOpenJDK cfg/retry-mode/legacy + x-amz-content-sha256:UNSIGNED-PAYLOAD + x-amz-date:20230803T112510Z + + amz-sdk-invocation-id;amz-sdk-request;amz-sdk-retry;content-type;host;user-agent;x-amz-content-sha256;x-amz-date + UNSIGNED-PAYLOAD" + 23/08/03 11:25:10 DEBUG AWS4Signer: AWS4 String to Sign: '"AWS4-HMAC-SHA256 + 20230803T112510Z + 20230803/us-east-1/s3/aws4_request + 31a317bb7f6d97248dd0cf03429d701cbb3e29ce889cfbb98ba7a34c57a3bfba" + 23/08/03 11:25:10 DEBUG AWS4Signer: Generating a new signing key as the signing key not available in the cache for the date 1691020800000 + 23/08/03 11:25:10 DEBUG RequestAddCookies: CookieSpec selected: default + 23/08/03 11:25:10 DEBUG RequestAuthCache: Auth cache not set in the context + 23/08/03 11:25:10 DEBUG PoolingHttpClientConnectionManager: Connection request: [route: {s}->https://test-bucket.localhost:9000][total available: 0; route allocated: 0 of 96; total allocated: 0 of 96] + 23/08/03 11:25:10 DEBUG PoolingHttpClientConnectionManager: Connection leased: [id: 0][route: {s}->https://test-bucket.localhost:9000][total available: 0; route allocated: 1 of 96; total allocated: 1 of 96] + 23/08/03 11:25:10 DEBUG MainClientExec: Opening connection {s}->https://test-bucket.localhost:9000 + 23/08/03 11:25:10 DEBUG DefaultHttpClientConnectionOperator: Connecting to test-bucket.localhost/127.0.0.1:9000 + 23/08/03 11:25:10 DEBUG SSLConnectionSocketFactory: Connecting socket to test-bucket.localhost/127.0.0.1:9000 with timeout 5000 + 23/08/03 11:25:10 DEBUG SSLConnectionSocketFactory: Enabled protocols: [TLSv1.2] + 23/08/03 11:25:10 DEBUG SSLConnectionSocketFactory: Enabled cipher suites:[TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV] + 23/08/03 11:25:10 DEBUG SSLConnectionSocketFactory: Starting handshake + 23/08/03 11:25:10 DEBUG ClientConnectionManagerFactory: + java.lang.reflect.InvocationTargetException + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:498) + at com.amazonaws.http.conn.ClientConnectionManagerFactory$Handler.invoke(ClientConnectionManagerFactory.java:76) + at com.amazonaws.http.conn.$Proxy32.connect(Unknown Source) + at com.amazonaws.thirdparty.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:393) + at com.amazonaws.thirdparty.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236) + at com.amazonaws.thirdparty.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186) + at com.amazonaws.thirdparty.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185) + at com.amazonaws.thirdparty.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83) + at com.amazonaws.thirdparty.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56) + at com.amazonaws.http.apache.client.impl.SdkHttpClient.execute(SdkHttpClient.java:72) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeOneRequest(AmazonHttpClient.java:1346) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeHelper(AmazonHttpClient.java:1157) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.doExecute(AmazonHttpClient.java:814) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeWithTimer(AmazonHttpClient.java:781) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.execute(AmazonHttpClient.java:755) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.access$500(AmazonHttpClient.java:715) + at com.amazonaws.http.AmazonHttpClient$RequestExecutionBuilderImpl.execute(AmazonHttpClient.java:697) + at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:561) + at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:541) + at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5456) + at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5403) + at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5397) + at com.amazonaws.services.s3.AmazonS3Client.listObjectsV2(AmazonS3Client.java:971) + at org.apache.hadoop.fs.s3a.S3AFileSystem.lambda$listObjects$11(S3AFileSystem.java:2595) + at org.apache.hadoop.fs.statistics.impl.IOStatisticsBinding.lambda$trackDurationOfOperation$5(IOStatisticsBinding.java:499) + at org.apache.hadoop.fs.s3a.Invoker.retryUntranslated(Invoker.java:414) + at org.apache.hadoop.fs.s3a.Invoker.retryUntranslated(Invoker.java:377) + at org.apache.hadoop.fs.s3a.S3AFileSystem.listObjects(S3AFileSystem.java:2586) + at org.apache.hadoop.fs.s3a.S3AFileSystem.s3GetFileStatus(S3AFileSystem.java:3832) + at org.apache.hadoop.fs.s3a.S3AFileSystem.innerGetFileStatus(S3AFileSystem.java:3688) + at org.apache.hadoop.fs.s3a.S3AFileSystem.lambda$isDirectory$35(S3AFileSystem.java:4724) + at org.apache.hadoop.fs.statistics.impl.IOStatisticsBinding.lambda$trackDurationOfOperation$5(IOStatisticsBinding.java:499) + at org.apache.hadoop.fs.statistics.impl.IOStatisticsBinding.trackDuration(IOStatisticsBinding.java:444) + at org.apache.hadoop.fs.s3a.S3AFileSystem.trackDurationAndSpan(S3AFileSystem.java:2337) + at org.apache.hadoop.fs.s3a.S3AFileSystem.trackDurationAndSpan(S3AFileSystem.java:2356) + at org.apache.hadoop.fs.s3a.S3AFileSystem.isDirectory(S3AFileSystem.java:4722) + at org.apache.spark.sql.execution.streaming.FileStreamSink$.hasMetadata(FileStreamSink.scala:54) + at org.apache.spark.sql.execution.datasources.DataSource.resolveRelation(DataSource.scala:366) + at org.apache.spark.sql.DataFrameReader.loadV1Source(DataFrameReader.scala:229) + at org.apache.spark.sql.DataFrameReader.$anonfun$load$2(DataFrameReader.scala:211) + at scala.Option.getOrElse(Option.scala:189) + at org.apache.spark.sql.DataFrameReader.load(DataFrameReader.scala:211) + at org.apache.spark.sql.DataFrameReader.load(DataFrameReader.scala:186) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:498) + at py4j.reflection.MethodInvoker.invoke(MethodInvoker.java:244) + at py4j.reflection.ReflectionEngine.invoke(ReflectionEngine.java:374) + at py4j.Gateway.invoke(Gateway.java:282) + at py4j.commands.AbstractCommand.invokeMethod(AbstractCommand.java:132) + at py4j.commands.CallCommand.execute(CallCommand.java:79) + at py4j.ClientServerConnection.waitForCommands(ClientServerConnection.java:182) + at py4j.ClientServerConnection.run(ClientServerConnection.java:106) + at java.lang.Thread.run(Thread.java:748) + Caused by: javax.net.ssl.SSLException: Unsupported or unrecognized SSL message + at sun.security.ssl.SSLSocketInputRecord.handleUnknownRecord(SSLSocketInputRecord.java:448) + at sun.security.ssl.SSLSocketInputRecord.decode(SSLSocketInputRecord.java:184) + at sun.security.ssl.SSLTransport.decode(SSLTransport.java:109) + at sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1383) + at sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1291) + at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:435) + at com.amazonaws.thirdparty.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:436) + at com.amazonaws.thirdparty.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:384) + at com.amazonaws.thirdparty.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142) + at com.amazonaws.thirdparty.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:376) + ... 58 more + 23/08/03 11:25:10 DEBUG DefaultManagedHttpClientConnection: http-outgoing-0: Shutdown connection + 23/08/03 11:25:10 DEBUG MainClientExec: Connection discarded + 23/08/03 11:25:10 DEBUG PoolingHttpClientConnectionManager: Connection released: [id: 0][route: {s}->https://test-bucket.localhost:9000][total available: 0; route allocated: 0 of 96; total allocated: 0 of 96] + 23/08/03 11:25:10 DEBUG AmazonHttpClient: Unable to execute HTTP request: Unsupported or unrecognized SSL message Request will be retried. + 23/08/03 11:25:10 DEBUG request: Retrying Request: GET https://test-bucket.localhost:9000 / Parameters: ({"list-type":["2"],"delimiter":["/"],"max-keys":["2"],"prefix":["fake/"],"fetch-owner":["false"]}Headers: (amz-sdk-invocation-id: e6d62603-96e4-a80f-10a1-816e0822bc71, Content-Type: application/octet-stream, User-Agent: Hadoop 3.3.4, aws-sdk-java/1.12.262 Linux/6.4.7-1-MANJARO OpenJDK_64-Bit_Server_VM/25.292-b10 java/1.8.0_292 scala/2.12.17 vendor/AdoptOpenJDK cfg/retry-mode/legacy, ) + 23/08/03 11:25:10 DEBUG AmazonHttpClient: Retriable error detected, will retry in 49ms, attempt number: 0 +``` + +#### Change number of retries + +You can also change number of retries performed by both libraries using `extra` parameter: + +```python +spark_s3 = SparkS3( + ..., + extra={ + "attempts.maximum": 1, + "retry.limit": 1, + }, +) +``` + +So accessing S3 will fail almost immediately if there is any error. + +### Most common mistakes + +#### No network access + +```text +Caused by: java.net.ConnectException: Connection refused +``` + +Mostly caused by: + +- Trying to access port number which S3 server does not listen +- You're trying to access host which is unreachable from your network (e.g. running behind some proxy or VPN) +- There are some firewall restrictions for accessing specific host or port + +#### Using HTTPS protocol for HTTP port + +```text +Caused by: javax.net.ssl.SSLException: Unsupported or unrecognized SSL message +``` + +By default, SparkS3 uses HTTPS protocol for connection. +If you change port number, this does not lead to changing protocol: + +```python +spark_s3 = SparkS3(host="s3provider.com", port=8080, ...) +``` + +You should pass protocol explicitly: + +```python +spark_s3 = SparkS3(host="s3provider.com", port=8080, protocol="http", ...) +``` + +#### SSL certificate is self-signed + +```text +sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target +``` + +To connect to HTTPS port with self-signed certificate, you should +[add certificate chain to Java TrustedStore](https://stackoverflow.com/questions/373295/digital-certificate-how-to-import-cer-file-in-to-truststore-file-using). + +Another option is to disable SSL check: + +```python +spark_s3 = SparkS3( + ..., + extra={ + "connection.ssl.enabled": False, + }, +) +``` + +But is is **NOT** recommended. + +#### Accessing S3 without domain-style access style support + +```text +Caused by: java.net.UnknownHostException: my-bucket.s3provider.com +``` + +To use path-style access, use option below: + +```python +spark_s3 = SparkS3( + host="s3provider.com", + bucket="my-bucket", + ..., + extra={ + "path.style.access": True, + }, +) +``` + +## Slow or unstable writing to S3 + +Hadoop AWS allows to use different writing strategies for different S3 implementations, depending +on list of supported features by server. + +These strategies are called [committers](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/committers.html). +There are [different types of committers](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/committers.html#Switching_to_an_S3A_Committer): + +- `file` (default) +- `directory` +- `partitioned` +- `magic` + +### `file` committer + +This committer is quite slow and unstable, so it is not recommended to use: + +```text +WARN AbstractS3ACommitterFactory: Using standard FileOutputCommitter to commit work. This is slow and potentially unsafe. +``` + +This is caused by the fact it creates files in the temp directory on remote filesystem, and after all of them are written successfully, +they are moved to target directory on same remote filesystem. + +This is not an issue for HDFS which does support file move operations and also support renaming directory +as atomic operation with `O(1)` time complexity. + +But S3 does support only file copying, so moving is performed via copy + delete. +Also it does not support atomic directory rename operation. Instead, renaming files with the same prefix has time complexity `O(n)`. + +### `directory` and `partitioned` committers + +These are [staging committers](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/committer_architecture.html), +meaning that they create temp directories on local filesystem, and after all files are written successfully, +they will be uploaded to S3. Local filesystems do support file moving and directory renaming, +so these committers does not have issues that `file` committer has. + +But they both require free space on local filesystem, and this may be an issue if user need to write large amount of data. +Also this can be an issue for container environment, like Kubernetes, there resources should be allocated before starting a container. + +### `magic` committer + +This committer uses multipart upload feature of S3 API, allowing to create multiple files +and after all of them were written successfully finish the transaction. Before transaction is finished, +files will not be accessible by other clients. + +Because it does not require neither file moving operations, nor directory atomic rename, +upload process is done in most efficient way S3 support. +This [drastically increases writing performance](https://spot.io/blog/improve-apache-spark-performance-with-the-s3-magic-committer/). + +To use this committer, set [following properties](https://github.com/apache/spark/pull/32518) while creating Spark session. + +```{eval-rst} +.. tabs:: + + .. code-tab:: py S3 your main distributed filesystem (Spark on Kubernetes) + + # https://issues.apache.org/jira/browse/SPARK-23977 + # https://spark.apache.org/docs/latest/cloud-integration.html#committing-work-into-cloud-storage-safely-and-fast + spark = ( + SparkSession.builder.appName("spark-app-name") + .config("spark.hadoop.fs.s3a.committer.magic.enabled", "true") + .config("spark.hadoop.fs.s3a.committer.name", "magic") + .config("spark.hadoop.mapreduce.outputcommitter.factory.scheme.s3a", "org.apache.hadoop.fs.s3a.commit.S3ACommitterFactory") + .config("spark.sql.parquet.output.committer.class", "org.apache.spark.internal.io.cloud.BindingParquetOutputCommitter") + .config("spark.sql.sources.commitProtocolClass", "org.apache.spark.internal.io.cloud.PathOutputCommitProtocol") + .getOrCreate() + ) + + .. code-tab:: py HDFS is your main distributed filesystem (Spark on Hadoop) + + # https://community.cloudera.com/t5/Support-Questions/spark-sql-sources-partitionOverwriteMode-dynamic-quot-not/m-p/343483/highlight/true + spark = ( + SparkSession.builder.appName("spark-app-name") + .config("spark.hadoop.fs.s3a.committer.magic.enabled", "true") + .config("spark.hadoop.fs.s3a.committer.name", "magic") + .getOrCreate() + ) +``` + +```{eval-rst} +.. warning:: + + ``magic`` committer requires S3 implementation to have strong consistency - file upload API return response only + if it was written on enough number of cluster nodes, and any cluster node error does not lead to missing or corrupting files. + + Some S3 implementations does have strong consistency + (like `AWS S3 `_ and + `MinIO `_), some not. Please contact your S3 provider + to get information about S3 implementation consistency. +``` + +```{eval-rst} +.. warning:: + + ``magic`` committer does not support ``if_exists="replace_overlapping_partitions"``. + Either use another ``if_exists`` value, or use ``partitioned`` committer. +``` + +### See also + +- [directory.marker.retention="keep"](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/directory_markers.html) + +## Slow reading from S3 + +Please read following documentation: + +- [prefetch.enabled](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/prefetching.html) +- [experimental.input.fadvise](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/performance.html#Improving_data_input_performance_through_fadvise) +- [Parquet and ORC I/O settings](https://spark.apache.org/docs/latest/cloud-integration.html#parquet-io-settings) + +If you're reading data from row-based formats, like {ref}`csv-file-format`, prefer +[experimental.input.fadvise="sequential" with increased readahead.range](https://issues.apache.org/jira/browse/HADOOP-17789?focusedCommentId=17383559#comment-17383559). + +But for other file formats, especially using compression, prefer +[experimental.input.fadvise="normal"](https://issues.apache.org/jira/browse/HADOOP-17789?focusedCommentId=17383743#comment-17383743) diff --git a/mddocs/docs/connection/index.md b/mddocs/docs/connection/index.md new file mode 100644 index 000000000..00cbfc41a --- /dev/null +++ b/mddocs/docs/connection/index.md @@ -0,0 +1,22 @@ +(connection)= + +```{toctree} +:caption: DB Connection +:maxdepth: 2 + +db_connection/index +``` + +```{toctree} +:caption: File Connection +:maxdepth: 2 + +file_connection/index +``` + +```{toctree} +:caption: File DataFrame Connection +:maxdepth: 2 + +file_df_connection/index +``` diff --git a/mddocs/docs/contributing.md b/mddocs/docs/contributing.md new file mode 100644 index 000000000..cc9ee4419 --- /dev/null +++ b/mddocs/docs/contributing.md @@ -0,0 +1,3 @@ +```{eval-rst} +.. include:: ../CONTRIBUTING.rst +``` diff --git a/mddocs/docs/db/db_reader.md b/mddocs/docs/db/db_reader.md new file mode 100644 index 000000000..5368093b1 --- /dev/null +++ b/mddocs/docs/db/db_reader.md @@ -0,0 +1,21 @@ +(db-reader)= + +# DB Reader + +```{eval-rst} +.. currentmodule:: onetl.db.db_reader.db_reader +``` + +```{eval-rst} +.. autosummary:: + + DBReader + DBReader.run + DBReader.has_data + DBReader.raise_if_no_data +``` + +```{eval-rst} +.. autoclass:: DBReader + :members: run, has_data, raise_if_no_data +``` diff --git a/mddocs/docs/db/db_writer.md b/mddocs/docs/db/db_writer.md new file mode 100644 index 000000000..d2d789c40 --- /dev/null +++ b/mddocs/docs/db/db_writer.md @@ -0,0 +1,19 @@ +(db-writer)= + +# DB Writer + +```{eval-rst} +.. currentmodule:: onetl.db.db_writer.db_writer +``` + +```{eval-rst} +.. autosummary:: + + DBWriter + DBWriter.run +``` + +```{eval-rst} +.. autoclass:: DBWriter + :members: run +``` diff --git a/mddocs/docs/db/index.md b/mddocs/docs/db/index.md new file mode 100644 index 000000000..baf632bf9 --- /dev/null +++ b/mddocs/docs/db/index.md @@ -0,0 +1,9 @@ +(db-root)= + +```{toctree} +:caption: DB classes +:maxdepth: 1 + +db_reader +db_writer +``` diff --git a/mddocs/docs/file/file_downloader/file_downloader.md b/mddocs/docs/file/file_downloader/file_downloader.md new file mode 100644 index 000000000..46fa1b406 --- /dev/null +++ b/mddocs/docs/file/file_downloader/file_downloader.md @@ -0,0 +1,21 @@ +(file-downloader)= + +# File Downloader + +```{eval-rst} +.. currentmodule:: onetl.file.file_downloader.file_downloader +``` + +```{eval-rst} +.. autosummary:: + + FileDownloader + FileDownloader.run + FileDownloader.view_files +``` + +```{eval-rst} +.. autoclass:: FileDownloader + :members: run, view_files + :member-order: bysource +``` diff --git a/mddocs/docs/file/file_downloader/index.md b/mddocs/docs/file/file_downloader/index.md new file mode 100644 index 000000000..4fd38db2c --- /dev/null +++ b/mddocs/docs/file/file_downloader/index.md @@ -0,0 +1,12 @@ +(file-downloader-root)= + +# File Downloader + +```{toctree} +:caption: File Downloader +:maxdepth: 1 + +file_downloader +options +result +``` diff --git a/mddocs/docs/file/file_downloader/options.md b/mddocs/docs/file/file_downloader/options.md new file mode 100644 index 000000000..2189236b1 --- /dev/null +++ b/mddocs/docs/file/file_downloader/options.md @@ -0,0 +1,14 @@ +(file-downloader-options)= + +# File Downloader Options + +```{eval-rst} +.. currentmodule:: onetl.file.file_downloader.options +``` + +```{eval-rst} +.. autopydantic_model:: FileDownloaderOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/file/file_downloader/result.md b/mddocs/docs/file/file_downloader/result.md new file mode 100644 index 000000000..fe6a165c9 --- /dev/null +++ b/mddocs/docs/file/file_downloader/result.md @@ -0,0 +1,12 @@ +(file-downloader-result)= + +# File Downloader Result + +```{eval-rst} +.. currentmodule:: onetl.file.file_downloader.result +``` + +```{eval-rst} +.. autoclass:: DownloadResult + :members: successful, failed, skipped, missing, successful_count, failed_count, skipped_count, missing_count, total_count, successful_size, failed_size, skipped_size, total_size, raise_if_failed, raise_if_missing, raise_if_skipped, raise_if_empty, is_empty, raise_if_contains_zero_size, details, summary, dict, json +``` diff --git a/mddocs/docs/file/file_filters/base.md b/mddocs/docs/file/file_filters/base.md new file mode 100644 index 000000000..375bb9a9c --- /dev/null +++ b/mddocs/docs/file/file_filters/base.md @@ -0,0 +1,19 @@ +(base-file-filter)= + +# Base interface + +```{eval-rst} +.. currentmodule:: onetl.base.base_file_filter +``` + +```{eval-rst} +.. autosummary:: + + BaseFileFilter + BaseFileFilter.match +``` + +```{eval-rst} +.. autoclass:: BaseFileFilter + :members: match +``` diff --git a/mddocs/docs/file/file_filters/exclude_dir.md b/mddocs/docs/file/file_filters/exclude_dir.md new file mode 100644 index 000000000..6b9aaf80e --- /dev/null +++ b/mddocs/docs/file/file_filters/exclude_dir.md @@ -0,0 +1,12 @@ +(exclude-dir-filter)= + +# ExcludeDir + +```{eval-rst} +.. currentmodule:: onetl.file.filter.exclude_dir +``` + +```{eval-rst} +.. autoclass:: ExcludeDir + :members: match +``` diff --git a/mddocs/docs/file/file_filters/file_filter.md b/mddocs/docs/file/file_filters/file_filter.md new file mode 100644 index 000000000..6ef16e7b1 --- /dev/null +++ b/mddocs/docs/file/file_filters/file_filter.md @@ -0,0 +1,12 @@ +(file-filter)= + +# File Filter (legacy) + +```{eval-rst} +.. currentmodule:: onetl.core.file_filter.file_filter +``` + +```{eval-rst} +.. autoclass:: FileFilter + :members: match +``` diff --git a/mddocs/docs/file/file_filters/file_mtime_filter.md b/mddocs/docs/file/file_filters/file_mtime_filter.md new file mode 100644 index 000000000..4414d9bac --- /dev/null +++ b/mddocs/docs/file/file_filters/file_mtime_filter.md @@ -0,0 +1,12 @@ +(file-modificatiom-time)= + +# FileModifiedTime + +```{eval-rst} +.. currentmodule:: onetl.file.filter.file_mtime +``` + +```{eval-rst} +.. autoclass:: FileModifiedTime + :members: match +``` diff --git a/mddocs/docs/file/file_filters/file_size_filter.md b/mddocs/docs/file/file_filters/file_size_filter.md new file mode 100644 index 000000000..a8eed5e22 --- /dev/null +++ b/mddocs/docs/file/file_filters/file_size_filter.md @@ -0,0 +1,12 @@ +(file-size-range)= + +# FileSizeRange + +```{eval-rst} +.. currentmodule:: onetl.file.filter.file_size +``` + +```{eval-rst} +.. autoclass:: FileSizeRange + :members: match +``` diff --git a/mddocs/docs/file/file_filters/glob.md b/mddocs/docs/file/file_filters/glob.md new file mode 100644 index 000000000..9234b7d3c --- /dev/null +++ b/mddocs/docs/file/file_filters/glob.md @@ -0,0 +1,12 @@ +(glob-filter)= + +# Glob + +```{eval-rst} +.. currentmodule:: onetl.file.filter.glob +``` + +```{eval-rst} +.. autoclass:: Glob + :members: match +``` diff --git a/mddocs/docs/file/file_filters/index.md b/mddocs/docs/file/file_filters/index.md new file mode 100644 index 000000000..d6b12f0dd --- /dev/null +++ b/mddocs/docs/file/file_filters/index.md @@ -0,0 +1,29 @@ +(file-filters)= + +# File Filters + +```{toctree} +:caption: File filters +:maxdepth: 1 + +glob +regexp +exclude_dir +file_size_filter +file_mtime_filter +``` + +```{toctree} +:caption: Legacy +:maxdepth: 1 + +file_filter +``` + +```{toctree} +:caption: For developers +:maxdepth: 1 + +base +match_all_filters +``` diff --git a/mddocs/docs/file/file_filters/match_all_filters.md b/mddocs/docs/file/file_filters/match_all_filters.md new file mode 100644 index 000000000..26818a46d --- /dev/null +++ b/mddocs/docs/file/file_filters/match_all_filters.md @@ -0,0 +1,11 @@ +(match-all-filters)= + +# match_all_filters + +```{eval-rst} +.. currentmodule:: onetl.file.filter.match_all_filters +``` + +```{eval-rst} +.. autofunction:: match_all_filters +``` diff --git a/mddocs/docs/file/file_filters/regexp.md b/mddocs/docs/file/file_filters/regexp.md new file mode 100644 index 000000000..e62fe9345 --- /dev/null +++ b/mddocs/docs/file/file_filters/regexp.md @@ -0,0 +1,12 @@ +(regexp-filter)= + +# Regexp + +```{eval-rst} +.. currentmodule:: onetl.file.filter.regexp +``` + +```{eval-rst} +.. autoclass:: Regexp + :members: match +``` diff --git a/mddocs/docs/file/file_limits/base.md b/mddocs/docs/file/file_limits/base.md new file mode 100644 index 000000000..7990c28a9 --- /dev/null +++ b/mddocs/docs/file/file_limits/base.md @@ -0,0 +1,21 @@ +(base-limit)= + +# Base interface + +```{eval-rst} +.. currentmodule:: onetl.base.base_file_limit +``` + +```{eval-rst} +.. autosummary:: + + BaseFileLimit + BaseFileLimit.reset + BaseFileLimit.stops_at + BaseFileLimit.is_reached +``` + +```{eval-rst} +.. autoclass:: BaseFileLimit + :members: reset, stops_at, is_reached +``` diff --git a/mddocs/docs/file/file_limits/file_limit.md b/mddocs/docs/file/file_limits/file_limit.md new file mode 100644 index 000000000..5034bf0f5 --- /dev/null +++ b/mddocs/docs/file/file_limits/file_limit.md @@ -0,0 +1,12 @@ +(file-limit)= + +# File Limit (legacy) + +```{eval-rst} +.. currentmodule:: onetl.core.file_limit.file_limit +``` + +```{eval-rst} +.. autoclass:: FileLimit + :members: reset, stops_at, is_reached +``` diff --git a/mddocs/docs/file/file_limits/index.md b/mddocs/docs/file/file_limits/index.md new file mode 100644 index 000000000..52d226b3e --- /dev/null +++ b/mddocs/docs/file/file_limits/index.md @@ -0,0 +1,28 @@ +(file-limits)= + +# File Limits + +```{toctree} +:caption: File limits +:maxdepth: 1 + +max_files_count +total_files_size +``` + +```{toctree} +:caption: Legacy +:maxdepth: 1 + +file_limit +``` + +```{toctree} +:caption: For developers +:maxdepth: 1 + +base +limits_stop_at +limits_reached +reset_limits +``` diff --git a/mddocs/docs/file/file_limits/limits_reached.md b/mddocs/docs/file/file_limits/limits_reached.md new file mode 100644 index 000000000..2d9a837b9 --- /dev/null +++ b/mddocs/docs/file/file_limits/limits_reached.md @@ -0,0 +1,11 @@ +(limits-reached)= + +# limits_reached + +```{eval-rst} +.. currentmodule:: onetl.file.limit.limits_reached +``` + +```{eval-rst} +.. autofunction:: limits_reached +``` diff --git a/mddocs/docs/file/file_limits/limits_stop_at.md b/mddocs/docs/file/file_limits/limits_stop_at.md new file mode 100644 index 000000000..a53aba6d7 --- /dev/null +++ b/mddocs/docs/file/file_limits/limits_stop_at.md @@ -0,0 +1,11 @@ +(limits-stop-at)= + +# limits_stop_at + +```{eval-rst} +.. currentmodule:: onetl.file.limit.limits_stop_at +``` + +```{eval-rst} +.. autofunction:: limits_stop_at +``` diff --git a/mddocs/docs/file/file_limits/max_files_count.md b/mddocs/docs/file/file_limits/max_files_count.md new file mode 100644 index 000000000..b82896718 --- /dev/null +++ b/mddocs/docs/file/file_limits/max_files_count.md @@ -0,0 +1,12 @@ +(max-files-count)= + +# MaxFilesCount + +```{eval-rst} +.. currentmodule:: onetl.file.limit.max_files_count +``` + +```{eval-rst} +.. autoclass:: MaxFilesCount + :members: reset, stops_at, is_reached +``` diff --git a/mddocs/docs/file/file_limits/reset_limits.md b/mddocs/docs/file/file_limits/reset_limits.md new file mode 100644 index 000000000..a55769041 --- /dev/null +++ b/mddocs/docs/file/file_limits/reset_limits.md @@ -0,0 +1,11 @@ +(reset-limits)= + +# reset_limits + +```{eval-rst} +.. currentmodule:: onetl.file.limit.reset_limits +``` + +```{eval-rst} +.. autofunction:: reset_limits +``` diff --git a/mddocs/docs/file/file_limits/total_files_size.md b/mddocs/docs/file/file_limits/total_files_size.md new file mode 100644 index 000000000..bc06c4f76 --- /dev/null +++ b/mddocs/docs/file/file_limits/total_files_size.md @@ -0,0 +1,12 @@ +(total-files-size-limit)= + +# TotalFilesSize + +```{eval-rst} +.. currentmodule:: onetl.file.limit.total_files_size +``` + +```{eval-rst} +.. autoclass:: TotalFilesSize + :members: reset, stops_at, is_reached +``` diff --git a/mddocs/docs/file/file_mover/file_mover.md b/mddocs/docs/file/file_mover/file_mover.md new file mode 100644 index 000000000..39e2cb1b7 --- /dev/null +++ b/mddocs/docs/file/file_mover/file_mover.md @@ -0,0 +1,21 @@ +(file-mover)= + +# File Mover + +```{eval-rst} +.. currentmodule:: onetl.file.file_mover.file_mover +``` + +```{eval-rst} +.. autosummary:: + + FileMover + FileMover.run + FileMover.view_files +``` + +```{eval-rst} +.. autoclass:: FileMover + :members: run, view_files + :member-order: bysource +``` diff --git a/mddocs/docs/file/file_mover/index.md b/mddocs/docs/file/file_mover/index.md new file mode 100644 index 000000000..6e9b44d39 --- /dev/null +++ b/mddocs/docs/file/file_mover/index.md @@ -0,0 +1,12 @@ +(file-mover-root)= + +# File Mover + +```{toctree} +:caption: File Mover +:maxdepth: 1 + +file_mover +options +result +``` diff --git a/mddocs/docs/file/file_mover/options.md b/mddocs/docs/file/file_mover/options.md new file mode 100644 index 000000000..57d42d500 --- /dev/null +++ b/mddocs/docs/file/file_mover/options.md @@ -0,0 +1,14 @@ +(file-mover-options)= + +# File Mover Options + +```{eval-rst} +.. currentmodule:: onetl.file.file_mover.options +``` + +```{eval-rst} +.. autopydantic_model:: FileMoverOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/file/file_mover/result.md b/mddocs/docs/file/file_mover/result.md new file mode 100644 index 000000000..f0fec0fb7 --- /dev/null +++ b/mddocs/docs/file/file_mover/result.md @@ -0,0 +1,12 @@ +(file-mover-result)= + +# File Mover Result + +```{eval-rst} +.. currentmodule:: onetl.file.file_mover.result +``` + +```{eval-rst} +.. autoclass:: MoveResult + :members: successful, failed, skipped, missing, successful_count, failed_count, skipped_count, missing_count, total_count, successful_size, failed_size, skipped_size, total_size, raise_if_failed, raise_if_missing, raise_if_skipped, raise_if_empty, is_empty, raise_if_contains_zero_size, details, summary, dict, json +``` diff --git a/mddocs/docs/file/file_uploader/file_uploader.md b/mddocs/docs/file/file_uploader/file_uploader.md new file mode 100644 index 000000000..770ae8660 --- /dev/null +++ b/mddocs/docs/file/file_uploader/file_uploader.md @@ -0,0 +1,21 @@ +(file-uploader)= + +# File Uploader + +```{eval-rst} +.. currentmodule:: onetl.file.file_uploader.file_uploader +``` + +```{eval-rst} +.. autosummary:: + + FileUploader + FileUploader.run + FileUploader.view_files +``` + +```{eval-rst} +.. autoclass:: FileUploader + :members: run, view_files + :member-order: bysource +``` diff --git a/mddocs/docs/file/file_uploader/index.md b/mddocs/docs/file/file_uploader/index.md new file mode 100644 index 000000000..ada740442 --- /dev/null +++ b/mddocs/docs/file/file_uploader/index.md @@ -0,0 +1,12 @@ +(file-uploader-root)= + +# File Uploader + +```{toctree} +:caption: File Uploader +:maxdepth: 1 + +file_uploader +options +result +``` diff --git a/mddocs/docs/file/file_uploader/options.md b/mddocs/docs/file/file_uploader/options.md new file mode 100644 index 000000000..8b987e1d8 --- /dev/null +++ b/mddocs/docs/file/file_uploader/options.md @@ -0,0 +1,14 @@ +(file-uploader-options)= + +# File Uploader Options + +```{eval-rst} +.. currentmodule:: onetl.file.file_uploader.options +``` + +```{eval-rst} +.. autopydantic_model:: FileUploaderOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/file/file_uploader/result.md b/mddocs/docs/file/file_uploader/result.md new file mode 100644 index 000000000..5a8137636 --- /dev/null +++ b/mddocs/docs/file/file_uploader/result.md @@ -0,0 +1,12 @@ +(file-uploader-result)= + +# File Uploader Result + +```{eval-rst} +.. currentmodule:: onetl.file.file_uploader.result +``` + +```{eval-rst} +.. autoclass:: UploadResult + :members: successful, failed, skipped, missing, successful_count, failed_count, skipped_count, missing_count, total_count, successful_size, failed_size, skipped_size, total_size, raise_if_failed, raise_if_missing, raise_if_skipped, raise_if_empty, is_empty, raise_if_contains_zero_size, details, summary, dict, json +``` diff --git a/mddocs/docs/file/index.md b/mddocs/docs/file/index.md new file mode 100644 index 000000000..49365dce5 --- /dev/null +++ b/mddocs/docs/file/index.md @@ -0,0 +1,12 @@ +(file-root)= + +```{toctree} +:caption: File classes +:maxdepth: 1 + +file_downloader/index +file_uploader/index +file_mover/index +file_filters/index +file_limits/index +``` diff --git a/mddocs/docs/file_df/file_df_reader/file_df_reader.md b/mddocs/docs/file_df/file_df_reader/file_df_reader.md new file mode 100644 index 000000000..713916f77 --- /dev/null +++ b/mddocs/docs/file_df/file_df_reader/file_df_reader.md @@ -0,0 +1,13 @@ +(file-df-reader)= + +# FileDF Reader + +```{eval-rst} +.. currentmodule:: onetl.file.file_df_reader.file_df_reader +``` + +```{eval-rst} +.. autoclass:: FileDFReader + :members: run + :member-order: bysource +``` diff --git a/mddocs/docs/file_df/file_df_reader/index.md b/mddocs/docs/file_df/file_df_reader/index.md new file mode 100644 index 000000000..99ac82d09 --- /dev/null +++ b/mddocs/docs/file_df/file_df_reader/index.md @@ -0,0 +1,11 @@ +(file-df-reader-root)= + +# FileDF Reader + +```{toctree} +:caption: FileDF Reader +:maxdepth: 1 + +file_df_reader +options +``` diff --git a/mddocs/docs/file_df/file_df_reader/options.md b/mddocs/docs/file_df/file_df_reader/options.md new file mode 100644 index 000000000..4318d288a --- /dev/null +++ b/mddocs/docs/file_df/file_df_reader/options.md @@ -0,0 +1,13 @@ +(file-df-reader-options)= + +# Options + +```{eval-rst} +.. currentmodule:: onetl.file.file_df_reader.options +``` + +```{eval-rst} +.. autoclass:: FileDFReaderOptions + :members: recursive + :member-order: bysource +``` diff --git a/mddocs/docs/file_df/file_df_writer/file_df_writer.md b/mddocs/docs/file_df/file_df_writer/file_df_writer.md new file mode 100644 index 000000000..6d53044e9 --- /dev/null +++ b/mddocs/docs/file_df/file_df_writer/file_df_writer.md @@ -0,0 +1,13 @@ +(file-df-writer)= + +# FileDF Writer + +```{eval-rst} +.. currentmodule:: onetl.file.file_df_writer.file_df_writer +``` + +```{eval-rst} +.. autoclass:: FileDFWriter + :members: run + :member-order: bysource +``` diff --git a/mddocs/docs/file_df/file_df_writer/index.md b/mddocs/docs/file_df/file_df_writer/index.md new file mode 100644 index 000000000..473031415 --- /dev/null +++ b/mddocs/docs/file_df/file_df_writer/index.md @@ -0,0 +1,11 @@ +(file-df-writer-root)= + +# FileDF Writer + +```{toctree} +:caption: FileDF Writer +:maxdepth: 1 + +file_df_writer +options +``` diff --git a/mddocs/docs/file_df/file_df_writer/options.md b/mddocs/docs/file_df/file_df_writer/options.md new file mode 100644 index 000000000..7afee67c7 --- /dev/null +++ b/mddocs/docs/file_df/file_df_writer/options.md @@ -0,0 +1,13 @@ +(file-df-writer-options)= + +# Options + +```{eval-rst} +.. currentmodule:: onetl.file.file_df_writer.options +``` + +```{eval-rst} +.. autoclass:: FileDFWriterOptions + :members: if_exists, partition_by + :member-order: bysource +``` diff --git a/mddocs/docs/file_df/file_formats/avro.md b/mddocs/docs/file_df/file_formats/avro.md new file mode 100644 index 000000000..528e3810a --- /dev/null +++ b/mddocs/docs/file_df/file_formats/avro.md @@ -0,0 +1,13 @@ +(avro-file-format)= + +# Avro + +```{eval-rst} +.. currentmodule:: onetl.file.format.avro +``` + +```{eval-rst} +.. autoclass:: Avro + :members: get_packages, parse_column, serialize_column, schema_dict, schema_url, recordName,recordNamespace,compression,mode,datetimeRebaseMode,positionalFieldMatching,enableStableIdentifiersForUnionType + :member-order: bysource +``` diff --git a/mddocs/docs/file_df/file_formats/base.md b/mddocs/docs/file_df/file_formats/base.md new file mode 100644 index 000000000..91a175871 --- /dev/null +++ b/mddocs/docs/file_df/file_formats/base.md @@ -0,0 +1,19 @@ +(base-file-format)= + +# Base interface + +```{eval-rst} +.. currentmodule:: onetl.base.base_file_format +``` + +```{eval-rst} +.. autoclass:: BaseReadableFileFormat + :members: check_if_supported, apply_to_reader + :member-order: bysource +``` + +```{eval-rst} +.. autoclass:: BaseWritableFileFormat + :members: check_if_supported, apply_to_writer + :member-order: bysource +``` diff --git a/mddocs/docs/file_df/file_formats/csv.md b/mddocs/docs/file_df/file_formats/csv.md new file mode 100644 index 000000000..30098ea66 --- /dev/null +++ b/mddocs/docs/file_df/file_formats/csv.md @@ -0,0 +1,13 @@ +(csv-file-format)= + +# CSV + +```{eval-rst} +.. currentmodule:: onetl.file.format.csv +``` + +```{eval-rst} +.. autoclass:: CSV + :members: __init__, parse_column, serialize_column, charToEscapeQuoteEscaping,columnNameOfCorruptRecord,comment,compression,dateFormat,delimiter,emptyValue,enforceSchema,escapeQuotes,header,ignoreLeadingWhiteSpace,ignoreTrailingWhiteSpace,inferSchema,locale,maxCharsPerColumn,mode,multiLine,nanValue,negativeInf,nullValue,positiveInf,preferDate,quote,quoteAll,samplingRatio,timestampFormat,timestampNTZFormat,unescapedQuoteHandling + :member-order: bysource +``` diff --git a/mddocs/docs/file_df/file_formats/excel.md b/mddocs/docs/file_df/file_formats/excel.md new file mode 100644 index 000000000..98e96003e --- /dev/null +++ b/mddocs/docs/file_df/file_formats/excel.md @@ -0,0 +1,13 @@ +(excel-file-format)= + +# Excel + +```{eval-rst} +.. currentmodule:: onetl.file.format.excel +``` + +```{eval-rst} +.. autoclass:: Excel + :members: get_packages,header,dataAddress,treatEmptyValuesAsNulls,setErrorCellsToFallbackValues,usePlainNumberFormat,inferSchema,timestampFormat,maxRowsInMemory,maxByteArraySize,tempFileThreshold,excerptSize,workbookPassword + :member-order: bysource +``` diff --git a/mddocs/docs/file_df/file_formats/index.md b/mddocs/docs/file_df/file_formats/index.md new file mode 100644 index 000000000..155cb7121 --- /dev/null +++ b/mddocs/docs/file_df/file_formats/index.md @@ -0,0 +1,24 @@ +(file-formats)= + +# File Formats + +```{toctree} +:caption: File formats +:maxdepth: 1 + +avro +csv +excel +json +jsonline +orc +parquet +xml +``` + +```{toctree} +:caption: For developers +:maxdepth: 1 + +base +``` diff --git a/mddocs/docs/file_df/file_formats/json.md b/mddocs/docs/file_df/file_formats/json.md new file mode 100644 index 000000000..976855782 --- /dev/null +++ b/mddocs/docs/file_df/file_formats/json.md @@ -0,0 +1,13 @@ +(json-file-format)= + +# JSON + +```{eval-rst} +.. currentmodule:: onetl.file.format.json +``` + +```{eval-rst} +.. autoclass:: JSON + :members: __init__, parse_column, serialize_column, allowBackslashEscapingAnyCharacter,allowComments,allowNonNumericNumbers,allowNumericLeadingZeros,allowSingleQuotes,allowUnquotedControlChars,allowUnquotedFieldNames,columnNameOfCorruptRecord,dateFormat,dropFieldIfAllNull,encoding,lineSep,locale,mode,prefersDecimal,primitivesAsString,samplingRatio,timestampFormat,timestampNTZFormat,timezone + :member-order: bysource +``` diff --git a/mddocs/docs/file_df/file_formats/jsonline.md b/mddocs/docs/file_df/file_formats/jsonline.md new file mode 100644 index 000000000..6a3794de7 --- /dev/null +++ b/mddocs/docs/file_df/file_formats/jsonline.md @@ -0,0 +1,13 @@ +(jsonline-file-format)= + +# JSONLine + +```{eval-rst} +.. currentmodule:: onetl.file.format.jsonline +``` + +```{eval-rst} +.. autoclass:: JSONLine + :members: __init__, allowBackslashEscapingAnyCharacter,allowComments,allowNonNumericNumbers,allowNumericLeadingZeros,allowSingleQuotes,allowUnquotedControlChars,allowUnquotedFieldNames,columnNameOfCorruptRecord,compression,dateFormat,dropFieldIfAllNull,encoding,ignoreNullFields,lineSep,locale,mode,prefersDecimal,primitivesAsString,samplingRatio,timestampFormat,timestampNTZFormat,timezone + :member-order: bysource +``` diff --git a/mddocs/docs/file_df/file_formats/orc.md b/mddocs/docs/file_df/file_formats/orc.md new file mode 100644 index 000000000..be830b88f --- /dev/null +++ b/mddocs/docs/file_df/file_formats/orc.md @@ -0,0 +1,13 @@ +(orc-file-format)= + +# ORC + +```{eval-rst} +.. currentmodule:: onetl.file.format.orc +``` + +```{eval-rst} +.. autoclass:: ORC + :members: __init__, mergeSchema,compression + :member-order: bysource +``` diff --git a/mddocs/docs/file_df/file_formats/parquet.md b/mddocs/docs/file_df/file_formats/parquet.md new file mode 100644 index 000000000..2d9b91bbc --- /dev/null +++ b/mddocs/docs/file_df/file_formats/parquet.md @@ -0,0 +1,13 @@ +(parquet-file-format)= + +# Parquet + +```{eval-rst} +.. currentmodule:: onetl.file.format.parquet +``` + +```{eval-rst} +.. autoclass:: Parquet + :members: __init__, mergeSchema,compression + :member-order: bysource +``` diff --git a/mddocs/docs/file_df/file_formats/xml.md b/mddocs/docs/file_df/file_formats/xml.md new file mode 100644 index 000000000..92b127bce --- /dev/null +++ b/mddocs/docs/file_df/file_formats/xml.md @@ -0,0 +1,13 @@ +(xml-file-format)= + +# XML + +```{eval-rst} +.. currentmodule:: onetl.file.format.xml +``` + +```{eval-rst} +.. autoclass:: XML + :members: get_packages, parse_column,arrayElementName,attributePrefix,charset,columnNameOfCorruptRecord,compression,dateFormat,declaration,excludeAttribute,ignoreNamespace,ignoreSurroundingSpaces,inferSchema,mode,nullValue,rootTag,row_tag,rowValidationXSDPath,samplingRatio,timestampFormat,valueTag,wildcardColName + :member-order: bysource +``` diff --git a/mddocs/docs/file_df/index.md b/mddocs/docs/file_df/index.md new file mode 100644 index 000000000..a955dcca5 --- /dev/null +++ b/mddocs/docs/file_df/index.md @@ -0,0 +1,10 @@ +(file-df-root)= + +```{toctree} +:caption: File DataFrame classes +:maxdepth: 1 + +file_df_reader/index +file_df_writer/index +file_formats/index +``` diff --git a/mddocs/docs/hooks/design.md b/mddocs/docs/hooks/design.md new file mode 100644 index 000000000..94e0f6188 --- /dev/null +++ b/mddocs/docs/hooks/design.md @@ -0,0 +1,694 @@ +(hooks-design)= + +# High level design + +## What are hooks? + +Hook mechanism is a part of onETL which allows to inject some additional behavior into +existing methods of (almost) any class. + +### Features + +Hooks mechanism allows to: + +- Inspect and validate input arguments and output results of method call +- Access, modify or replace method call result (but NOT input arguments) +- Wrap method calls with a context manager and catch raised exceptions + +Hooks can be placed into {ref}`plugins`, allowing to modify onETL behavior by installing some additional package. + +### Limitations + +- Hooks can be bound to methods of a class only (not functions). +- Only methods decorated with {ref}`slot-decorator` implement hooks mechanism. These class and methods are marked as {{ support_hooks }}. +- Hooks can be bound to public methods only. + +## Terms + +- {ref}`slot-decorator` - method of a class with a special decorator +- `Callback` - function which implements some additional logic which modifies slot behavior +- {ref}`hook-decorator` - wrapper around callback which stores hook state, priority and some useful methods +- `Hooks mechanism` - calling `Slot()` will call all enabled hooks which are bound to the slot. Implemented by {ref}`support-hooks-decorator`. + +## How to implement hooks? + +### TL;DR + +```python +from onetl.hooks import support_hooks, slot, hook + + +@support_hooks # enabling hook mechanism for the class +class MyClass: + def __init__(self, data): + self.data = data + + # this is slot + @slot + def method(self, arg): + pass + + +@MyClass.method.bind # bound hook to the slot +@hook # this is hook +def callback(obj, arg): # this is callback + print(obj.data, arg) + + +obj = MyClass(1) +obj.method(2) # will call callback(obj, 1) + +# prints "1 2" +``` + +#### Define a slot + +- Create a class with a method: + +```python +class MyClass: + def __init__(self, data): + self.data = data + + def method(self, arg): + return self.data, arg +``` + +- Add {ref}`slot-decorator` to the method: + +```python +from onetl.hooks import support_hooks, slot, hook + + +class MyClass: + @slot + def method(self, arg): + return self.data, arg +``` + +If method has other decorators like `@classmethod` or `@staticmethod`, `@slot` should be placed on the top: + +```python +from onetl.hooks import support_hooks, slot, hook + + +class MyClass: + @slot + @classmethod + def class_method(cls, arg): + return cls, arg + + @slot + @staticmethod + def static_method(arg): + return arg +``` + +- Add {ref}`support-hooks-decorator` to the class: + +```python +from onetl.hooks import support_hooks, slot, hook + + +@support_hooks +class MyClass: + @slot + def method(self, arg): + return self.data, arg +``` + +Slot is created. + +#### Define a callback + +Define some function (a.k.a callback): + +```python +def callback(self, arg): + print(self.data, arg) +``` + +It should have signature *compatible* with `MyClass.method`. *Compatible* does not mean *exactly the same* - +for example, you can rename positional arguments: + +```python +def callback(obj, arg): + print(obj.data, arg) +``` + +Use `*args` and `**kwargs` to omit arguments you don't care about: + +```python +def callback(obj, *args, **kwargs): + print(obj.data, args, kwargs) +``` + +There is also an argument `method_name` which has a special meaning - +the method name which the callback is bound to is passed into this argument: + +```python +def callback(obj, *args, method_name: str, **kwargs): + print(obj.data, args, method_name, kwargs) +``` + +```{eval-rst} +.. note:: + + ``method_name`` should always be a keyword argument, **NOT** positional. +``` + +```{eval-rst} +.. warning:: + + If callback signature is not compatible with slot signature, an exception will be raised, + but **ONLY** while slot is called. +``` + +#### Define a hook + +Add {ref}`hook-decorator` to create a hook from your callback: + +```python +@hook +def callback(obj, arg): + print(obj.data, arg) +``` + +You can pass more options to the `@hook` decorator, like state or priority. +See decorator documentation for more details. + +#### Bind hook to the slot + +Use `Slot.bind` method to bind hook to the slot: + +```python +@MyClass.method.bind +@hook +def callback(obj, arg): + print(obj, arg) +``` + +You can bind more than one hook to the same slot, and bind same hook to multiple slots: + +```python +@MyClass.method1.bind +@MyClass.method2.bind +@hook +def callback1(obj, arg): + "Will be called by both MyClass.method1 and MyClass.method2" + + +@MyClass.method1.bind +@hook +def callback2(obj, arg): + "Will be called by MyClass.method1 too" +``` + +## How hooks are called? + +### General + +Just call the method decorated by `@slot` to trigger the hook: + +```python +obj = MyClass(1) +obj.method(2) # will call callback(obj, 2) + +# prints "1 2" +``` + +There are some special callback types that has a slightly different behavior. + +### Context managers + +`@hook` decorator can be placed on a context manager class: + +```python +@hook +class ContextManager: + def __init__(self, obj, arg): + self.obj = obj + self.arg = arg + + def __enter__(self): + # do something on enter + print(obj.data, arg) + return self + + def __exit__(self, exc_type, exc_value, traceback): + # do something on exit + return False +``` + +Context manager is entered while calling the `Slot()`, and exited then the call is finished. + +If present, method `process_result` has a special meaning - +it can receive `MyClass.method` call result, and also modify/replace it: + +```python +@hook +class ContextManager: + def __init__(self, obj, arg): + self.obj = obj + self.arg = arg + + def __enter__(self): + # do something on enter + print(obj.data, arg) + return self + + def __exit__(self, exc_type, exc_value, traceback): + # do something on exit + return False + + def process_result(self, result): + # do something with method call result + return modified(result) +``` + +See examples below for more information. + +### Generator function + +`@hook` decorator can be placed on a generator function: + +```python +@hook +def callback(obj, arg): + print(obj.data, arg) + # this is called before original method body + + yield # method is called here + + # this is called after original method body +``` + +It is converted to a context manager, in the same manner as +[contextlib.contextmanager](https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager). + +Generator body can be wrapped with `try..except..finally` to catch exceptions: + +```python +@hook +def callback(obj, arg): + print(obj.data, arg) + + try: + # this is called before original method body + + yield # method is called here + except Exception as e: + process_exception(a) + finally: + # this is called after original method body + finalizer() +``` + +There is also a special syntax which allows generator to access and modify/replace method call result: + +``` +@hook +def callback(obj, arg): + original_result = yield # method is called here + + new_result = do_something(original_result) + + yield new_result # modify/replace the result +``` + +### Calling hooks in details + +- The callback will be called with the same arguments as the original method. + + - If slot is a regular method: + + ```python + callback_result = callback(self, *args, **kwargs) + ``` + + Here `self` is a class instance (`obj`). + + - If slot is a class method: + + ```python + callback_result = callback(cls, *args, **kwargs) + ``` + + Here `cls` is the class itself (`MyClass`). + + - If slot is a static method: + + ```python + callback_result = callback(*args, **kwargs) + ``` + + Neither object not class are passed to the callback in this case. + +- If `callback_result` is a context manager, enter the context. Context manager can catch all the exceptions raised. + + > If there are multiple hooks bound the the slot, every context manager will be entered. + +- Then call the original method wrapped by `@slot`: + + ```python + original_result = method(*args, **kwargs) + ``` + +- Process `original_result`: + + - If `callback_result` object has method `process_result`, or is a generator wrapped with `@hook`, call it: + + ```python + new_result = callback_result.process_result(original_result) + ``` + + - Otherwise set `new_result = callback_result`. + + - If there are multiple hooks bound the the method, pass `new_result` through the chain: + + ```python + new_result = callback1_result.process_result(original_result) + new_result = callback2_result.process_result(new_result or original_result) + new_result = callback3_result.process_result(new_result or original_result) + ``` + +- Finally return: + + ```python + return new_result or original_result + ``` + + All `None` values are ignored on every step above. + +- Exit all the context managers entered during the slot call. + +### Hooks priority + +Hooks are executed in the following order: + +1. Parent class slot + {obj}`FIRST ` +2. Inherited class slot + {obj}`FIRST ` +3. Parent class slot + {obj}`NORMAL ` +4. Inherited class slot + {obj}`NORMAL ` +5. Parent class slot + {obj}`LAST ` +6. Inherited class slot + {obj}`LAST ` + +Hooks with the same priority and inheritance will be executed in the same order they were registered (`Slot.bind` call). + +```{eval-rst} +.. note:: + + Calls of ``super()`` inside inherited class methods does not trigger hooks call. + Hooks are triggered only if method is called explicitly. + + This allow to wrap with a hook the entire slot call without influencing its internal logic. + +``` + +### Hook types + +Here are several examples of using hooks. These types are not exceptional, they can be mixed - for example, +hook can both modify method result and catch exceptions. + +#### Before hook + +Can be used for inspecting or validating input args of the original function: + +```python +@hook +def before1(obj, arg): + print(obj, arg) + # original method is called after exiting this function + + +@hook +def before2(obj, arg): + if arg == 1: + raise ValueError("arg=1 is not allowed") + return None # return None is the same as no return statement +``` + +Executed before calling the original method wrapped by `@slot`. +If hook raises an exception, method will not be called at all. + +#### After hook + +Can be used for performing some actions after original method was successfully executed: + +```python +@hook +def after1(obj, arg): + yield # original method is called here + print(obj, arg) + + +@hook +def after2(obj, arg): + yield None # yielding None is the same as empty yield + if arg == 1: + raise ValueError("arg=1 is not allowed") +``` + +If original method raises an exception, the block of code after `yield` will not be called. + +#### Context hook + +Can be used for catching and handling some exceptions, or to determine that there was no exception during slot call: + +```{eval-rst} +.. tabs:: + + .. code-tab:: py Generator syntax + + # This is just the same as using @contextlib.contextmanager + + @hook + def context_generator(obj, arg): + try: + yield # original method is called here + print(obj, arg) # <-- this line will not be called if method raised an exception + except SomeException as e: + magic(e) + finally: + finalizer() + + .. code-tab:: py Context manager syntax + + @hook + class ContextManager: + def __init__(self, obj, args): + self.obj = obj + self.args = args + + def __enter__(self): + return self + + # original method is called between __enter__ and __exit__ + + def __exit__(self, exc_type, exc_value, traceback): + result = False + if exc_type is not None and isinstance(exc_value, SomeException): + magic(exc_value) + result = True # suppress exception + else: + print(self.obj, self.arg) + finalizer() + return result +``` + +```{eval-rst} +.. note:: + + Contexts are exited in the reverse order of the hook calls. + So if some hook raised an exception, it will be passed into the previous hook, not the next one. + + It is recommended to specify the proper priority for the hook, e.g. :obj:`FIRST ` +``` + +#### Replacing result hook + +Replaces the output result of the original method. + +Can be used for delegating some implementation details for third-party extensions. +See {ref}`hive` and {ref}`hdfs` as an example. + +```python +@hook +def replace1(obj, arg): + result = arg + 10 # any non-None return result + + # original method call result is ignored, output will always be arg + 10 + return result + + +@hook +def replace2(obj, arg): + yield arg + 10 # same as above +``` + +```{eval-rst} +.. note:: + + If there are multiple hooks bound to the same slot, the result of last hook will be used. + It is recommended to specify the proper priority for the hook, e.g. :obj:`LAST ` + +``` + +#### Accessing result hook + +Can access output result of the original method and inspect or validate it: + +```{eval-rst} +.. tabs:: + + .. code-tab:: py Generator syntax + + @hook + def access_result(obj, arg): + result = yield # original method is called here, and result can be used in the hook + print(result) + yield # does not modify result + + .. code-tab:: py Context manager syntax + + @hook + class ModifiesResult: + def __init__(self, obj, args): + self.obj = obj + self.args = args + + def __enter__(self): + return self + + # original method is called between __enter__ and __exit__ + # result is passed into process_result method of context manager, if present + + def process_result(self, result): + print(result) # result can be used in the hook + return None # does not modify result. same as no return statement in the method + + def __exit__(self, exc_type, exc_value, traceback): + return False + +``` + +#### Modifying result hook + +Can access output result of the original method, and return the modified one: + +```{eval-rst} +.. tabs:: + + .. code-tab:: py Generator syntax + + @hook + def modifies_result(obj, arg): + result = yield # original method is called here, and result can be used in the hook + yield result + 10 # modify output result. None values are ignored + + .. code-tab:: py Context manager syntax + + @hook + class ModifiesResult: + def __init__(self, obj, args): + self.obj = obj + self.args = args + + def __enter__(self): + return self + + # original method is called between __enter__ and __exit__ + # result is passed into process_result method of context manager, if present + + def process_result(self, result): + print(result) # result can be used in the hook + return result + 10 # modify output result. None values are ignored + + def __exit__(self, exc_type, exc_value, traceback): + return False +``` + +```{eval-rst} +.. note:: + + If there are multiple hooks bound to the same slot, the result of last hook will be used. + It is recommended to specify the proper priority for the hook, e.g. :obj:`LAST ` + +``` + +## How to enable/disable hooks? + +You can enable/disable/temporary disable hooks on 4 different levels: + +- Manage global hooks state (level 1): + + > - {obj}`onetl.hooks.hooks_state.stop_all_hooks` + > - {obj}`onetl.hooks.hooks_state.resume_all_hooks` + > - {obj}`onetl.hooks.hooks_state.skip_all_hooks` + +- Manage all hooks bound to a specific class (level 2): + + > - {obj}`onetl.hooks.support_hooks.suspend_hooks` + > - {obj}`onetl.hooks.support_hooks.resume_hooks` + > - {obj}`onetl.hooks.support_hooks.skip_hooks` + +- Manage all hooks bound to a specific slot (level 3): + + > - {obj}`onetl.hooks.slot.Slot.suspend_hooks` + > - {obj}`onetl.hooks.slot.Slot.resume_hooks` + > - {obj}`onetl.hooks.slot.Slot.skip_hooks` + +- Manage state of a specific hook (level 4): + + > - {obj}`onetl.hooks.hook.Hook.enable` + > - {obj}`onetl.hooks.hook.Hook.disable` + +More details in the documentation above. + +```{eval-rst} +.. note:: + + All of these levels are independent. + + Calling ``stop`` on the level 1 has higher priority than level 2, and so on. + But calling ``resume`` on the level 1 does not automatically resume hooks stopped in the level 2, + they should be resumed explicitly. + +``` + +## How to see logs of the hook mechanism? + +Hooks registration emits logs with `DEBUG` level: + +```python +from onetl.logs import setup_logging + +setup_logging() +``` + +```text +DEBUG |onETL| Registered hook 'mymodule.callback1' for 'MyClass.method' (enabled=True, priority=HookPriority.NORMAL) +DEBUG |onETL| Registered hook 'mymodule.callback2' for 'MyClass.method' (enabled=True, priority=HookPriority.NORMAL) +DEBUG |onETL| Registered hook 'mymodule.callback3' for 'MyClass.method' (enabled=False, priority=HookPriority.NORMAL) +``` + +But most of logs are emitted with even lower level `NOTICE`, to make output less verbose: + +```python +from onetl.logs import NOTICE, setup_logging + +setup_logging(level=NOTICE) +``` + +``` +NOTICE |Hooks| 2 hooks registered for 'MyClass.method' +NOTICE |Hooks| Calling hook 'mymodule.callback1' (1/2) +NOTICE |Hooks| Hook is finished with returning non-None result +NOTICE |Hooks| Calling hook 'mymodule.callback2' (2/2) +NOTICE |Hooks| This is a context manager, entering ... +NOTICE |Hooks| Calling original method 'MyClass.method' +NOTICE |Hooks| Method call is finished +NOTICE |Hooks| Method call result (*NOT* None) will be replaced with result of hook 'mymodule.callback1' +NOTICE |Hooks| Passing result to 'process_result' method of context manager 'mymodule.callback2' +NOTICE |Hooks| Method call result (*NOT* None) is modified by hook! +``` diff --git a/mddocs/docs/hooks/global_state.md b/mddocs/docs/hooks/global_state.md new file mode 100644 index 000000000..f4fd50a43 --- /dev/null +++ b/mddocs/docs/hooks/global_state.md @@ -0,0 +1,27 @@ +(hooks-global-state)= + +# Hooks global state + +```{eval-rst} +.. currentmodule:: onetl.hooks.hooks_state +``` + +```{eval-rst} +.. autosummary:: + + skip_all_hooks + stop_all_hooks + resume_all_hooks +``` + +```{eval-rst} +.. autofunction:: skip_all_hooks +``` + +```{eval-rst} +.. autofunction:: stop_all_hooks +``` + +```{eval-rst} +.. autofunction:: resume_all_hooks +``` diff --git a/mddocs/docs/hooks/hook.md b/mddocs/docs/hooks/hook.md new file mode 100644 index 000000000..8dc54cf56 --- /dev/null +++ b/mddocs/docs/hooks/hook.md @@ -0,0 +1,34 @@ +(hook-decorator)= + +# `@hook` decorator + +```{eval-rst} +.. currentmodule:: onetl.hooks.hook +``` + +```{eval-rst} +.. autosummary:: + + hook + HookPriority + Hook + Hook.enable + Hook.disable + Hook.skip +``` + +```{eval-rst} +.. autodecorator:: hook +``` + +```{eval-rst} +.. autoclass:: HookPriority + :members: FIRST, NORMAL, LAST + :member-order: bysource +``` + +```{eval-rst} +.. autoclass:: Hook + :members: enable, disable, skip + :member-order: bysource +``` diff --git a/mddocs/docs/hooks/index.md b/mddocs/docs/hooks/index.md new file mode 100644 index 000000000..9b489afe6 --- /dev/null +++ b/mddocs/docs/hooks/index.md @@ -0,0 +1,17 @@ +(hooks)= + +# Hooks + +:::{versionadded} 0.6.0 +::: + +```{toctree} +:caption: Hooks +:maxdepth: 1 + +High level design +@hook decorator +@slot decorator +@support_hooks decorator +Hooks global state +``` diff --git a/mddocs/docs/hooks/slot.md b/mddocs/docs/hooks/slot.md new file mode 100644 index 000000000..ac3c8d74b --- /dev/null +++ b/mddocs/docs/hooks/slot.md @@ -0,0 +1,26 @@ +(slot-decorator)= + +# `@slot` decorator + +```{eval-rst} +.. currentmodule:: onetl.hooks.slot +``` + +```{eval-rst} +.. autosummary:: + + slot + Slot + Slot.bind + Slot.skip_hooks + Slot.suspend_hooks + Slot.resume_hooks +``` + +```{eval-rst} +.. autodecorator:: slot +``` + +```{eval-rst} +.. autoprotocol:: Slot +``` diff --git a/mddocs/docs/hooks/support_hooks.md b/mddocs/docs/hooks/support_hooks.md new file mode 100644 index 000000000..a2fd9fb60 --- /dev/null +++ b/mddocs/docs/hooks/support_hooks.md @@ -0,0 +1,32 @@ +(support-hooks-decorator)= + +# `@support_hooks` decorator + +```{eval-rst} +.. currentmodule:: onetl.hooks.support_hooks +``` + +```{eval-rst} +.. autosummary:: + + support_hooks + skip_hooks + suspend_hooks + resume_hooks +``` + +```{eval-rst} +.. autodecorator:: support_hooks +``` + +```{eval-rst} +.. autofunction:: skip_hooks +``` + +```{eval-rst} +.. autofunction:: suspend_hooks +``` + +```{eval-rst} +.. autofunction:: resume_hooks +``` diff --git a/mddocs/docs/hwm_store/index.md b/mddocs/docs/hwm_store/index.md new file mode 100644 index 000000000..099f56154 --- /dev/null +++ b/mddocs/docs/hwm_store/index.md @@ -0,0 +1,16 @@ +(hwm)= + +# HWM + +Since onETL v0.10.0, the `HWMStore` and `HWM` classes have been moved to a separate library {etl-entities}`etl-entities <>`. + +The only class was left intact is {ref}`yaml-hwm-store`, **which is default** in onETL. + +Other known implementation is [HorizonHWMStore](https://horizon-hwm-store.readthedocs.io/). + +```{toctree} +:hidden: true +:maxdepth: 2 + +yaml_hwm_store +``` diff --git a/mddocs/docs/hwm_store/yaml_hwm_store.md b/mddocs/docs/hwm_store/yaml_hwm_store.md new file mode 100644 index 000000000..63beb672e --- /dev/null +++ b/mddocs/docs/hwm_store/yaml_hwm_store.md @@ -0,0 +1,12 @@ +(yaml-hwm-store)= + +# YAMLHWMStore + +```{eval-rst} +.. currentmodule:: onetl.hwm.store.yaml_hwm_store +``` + +```{eval-rst} +.. autoclass:: YAMLHWMStore + :members: get_hwm, set_hwm, __enter__ +``` diff --git a/mddocs/docs/index.md b/mddocs/docs/index.md new file mode 100644 index 000000000..46642bf4e --- /dev/null +++ b/mddocs/docs/index.md @@ -0,0 +1,87 @@ +```{eval-rst} +.. include:: ../README.rst + :end-before: |Logo| +``` + +```{image} _static/logo_wide.svg +:alt: onETL logo +``` + +```{eval-rst} +.. include:: ../README.rst + :start-after: |Logo| + :end-before: documentation +``` + +```{toctree} +:caption: How to +:hidden: true +:maxdepth: 2 + +self +install/index +quickstart +concepts +logging +troubleshooting/index +``` + +```{toctree} +:caption: Connection +:hidden: true +:maxdepth: 3 + +connection/index +``` + +```{toctree} +:caption: DB classes +:hidden: true +:maxdepth: 3 + +db/index +``` + +```{toctree} +:caption: File classes +:hidden: true +:maxdepth: 3 + +file/index +``` + +```{toctree} +:caption: File DataFrame classes +:hidden: true +:maxdepth: 3 + +file_df/index +``` + +```{toctree} +:caption: Read strategies and HWM +:hidden: true +:maxdepth: 2 + +strategy/index +hwm_store/index +``` + +```{toctree} +:caption: Hooks & plugins +:hidden: true +:maxdepth: 2 + +hooks/index +plugins +``` + +```{toctree} +:caption: Development +:hidden: true +:maxdepth: 2 + +changelog +contributing +security +``` diff --git a/mddocs/docs/install/files.md b/mddocs/docs/install/files.md new file mode 100644 index 000000000..7a9867f53 --- /dev/null +++ b/mddocs/docs/install/files.md @@ -0,0 +1,9 @@ +(install-files)= + +# File connections + +```{eval-rst} +.. include:: ../../README.rst + :start-after: .. _files-install: + :end-before: With Kerberos support +``` diff --git a/mddocs/docs/install/full.md b/mddocs/docs/install/full.md new file mode 100644 index 000000000..f6d1cc107 --- /dev/null +++ b/mddocs/docs/install/full.md @@ -0,0 +1,9 @@ +(install-full)= + +# Full bundle + +```{eval-rst} +.. include:: ../../README.rst + :start-after: .. _full-bundle: + :end-before: .. _quick-start: +``` diff --git a/mddocs/docs/install/index.md b/mddocs/docs/install/index.md new file mode 100644 index 000000000..d71a13de9 --- /dev/null +++ b/mddocs/docs/install/index.md @@ -0,0 +1,22 @@ +(install)= + +# How to install + +```{eval-rst} +.. include:: ../../README.rst + :start-after: .. _minimal-install: + :end-before: With DB and FileDF connections +``` + +## Installation in details + +```{toctree} +:caption: How to install +:maxdepth: 1 + +self +spark +files +kerberos +full +``` diff --git a/mddocs/docs/install/kerberos.md b/mddocs/docs/install/kerberos.md new file mode 100644 index 000000000..7c772b94a --- /dev/null +++ b/mddocs/docs/install/kerberos.md @@ -0,0 +1,9 @@ +(install-kerberos)= + +# Kerberos support + +```{eval-rst} +.. include:: ../../README.rst + :start-after: .. _kerberos-install: + :end-before: Full bundle +``` diff --git a/mddocs/docs/install/spark.md b/mddocs/docs/install/spark.md new file mode 100644 index 000000000..40c15766a --- /dev/null +++ b/mddocs/docs/install/spark.md @@ -0,0 +1,328 @@ +(install-spark)= + +# Spark + +```{eval-rst} +.. include:: ../../README.rst + :start-after: .. _spark-install: + :end-before: .. _java-install: +``` + +## Installing Java + +```{eval-rst} +.. include:: ../../README.rst + :start-after: .. _java-install: + :end-before: .. _pyspark-install: +``` + +## Installing PySpark + +```{eval-rst} +.. include:: ../../README.rst + :start-after: .. _pyspark-install: + :end-before: With File connections +``` + +(java-packages)= + +## Injecting Java packages + +Some DB and FileDF connection classes require specific packages to be inserted to `CLASSPATH` of Spark session, +like JDBC drivers. + +This is usually done by setting up `spark.jars.packages` option while creating Spark session: + +```python +# here is a list of packages to be downloaded: +maven_packages = ( + Greenplum.get_packages(spark_version="3.2") + + MySQL.get_packages() + + Teradata.get_packages() +) + +spark = ( + SparkSession.builder.config("spark.app.name", "onetl") + .config("spark.jars.packages", ",".join(maven_packages)) + .getOrCreate() +) +``` + +Spark automatically resolves package and all its dependencies, download them and inject to Spark session +(both driver and all executors). + +This requires internet access, because package metadata and `.jar` files are fetched from [Maven Repository](https://mvnrepository.com/). + +But sometimes it is required to: + +- Install package without direct internet access (isolated network) +- Install package which is not available in Maven + +There are several ways to do that. + +### Using `spark.jars` + +The most simple solution, but this requires to store raw `.jar` files somewhere on filesystem or web server. + +- Download `package.jar` files (it's usually something like `some-package_1.0.0.jar`). Local file name does not matter, but it should be unique. +- (For `spark.submit.deployMode=cluster`) place downloaded files to HDFS or deploy to any HTTP web server serving static files. See [official documentation](https://spark.apache.org/docs/latest/submitting-applications.html#advanced-dependency-management) for more details. +- Create Spark session with passing `.jar` absolute file path to `spark.jars` Spark config option: + +```{eval-rst} +.. tabs:: + + .. code-tab:: py for spark.submit.deployMode=client (default) + + jar_files = ["/path/to/package.jar"] + + # do not pass spark.jars.packages + spark = ( + SparkSession.builder.config("spark.app.name", "onetl") + .config("spark.jars", ",".join(jar_files)) + .getOrCreate() + ) + + .. code-tab:: py for spark.submit.deployMode=cluster + + # you can also pass URLs like http://domain.com/path/to/downloadable/package.jar + jar_files = ["hdfs:///path/to/package.jar"] + + # do not pass spark.jars.packages + spark = ( + SparkSession.builder.config("spark.app.name", "onetl") + .config("spark.jars", ",".join(jar_files)) + .getOrCreate() + ) +``` + +### Using `spark.jars.repositories` + +```{eval-rst} +.. note:: + + In this case Spark still will try to fetch packages from the internet, so if you don't have internet access, + Spark session will be created with significant delay because of all attempts to fetch packages. +``` + +Can be used if you have access both to public repos (like Maven) and a private Artifactory/Nexus repo. + +- Setup private Maven repository in [JFrog Artifactory](https://jfrog.com/artifactory/) or [Sonatype Nexus](https://www.sonatype.com/products/sonatype-nexus-repository). +- Download `package.jar` file (it's usually something like `some-package_1.0.0.jar`). Local file name does not matter. +- Upload `package.jar` file to private repository (with same `groupId` and `artifactoryId` as in source package in Maven). +- Pass repo URL to `spark.jars.repositories` Spark config option. +- Create Spark session with passing Package name to `spark.jars.packages` Spark config option: + +```python +maven_packages = ( + Greenplum.get_packages(spark_version="3.2") + + MySQL.get_packages() + + Teradata.get_packages() +) + +spark = ( + SparkSession.builder.config("spark.app.name", "onetl") + .config("spark.jars.repositories", "http://nexus.mydomain.com/private-repo/") + .config("spark.jars.packages", ",".join(maven_packages)) + .getOrCreate() +) +``` + +### Using `spark.jars.ivySettings` + +Same as above, but can be used even if there is no network access to public repos like Maven. + +- Setup private Maven repository in [JFrog Artifactory](https://jfrog.com/artifactory/) or [Sonatype Nexus](https://www.sonatype.com/products/sonatype-nexus-repository). +- Download `package.jar` file (it's usually something like `some-package_1.0.0.jar`). Local file name does not matter. +- Upload `package.jar` file to [private repository](https://help.sonatype.com/repomanager3/nexus-repository-administration/repository-management#RepositoryManagement-HostedRepository) (with same `groupId` and `artifactoryId` as in source package in Maven). +- Create `ivysettings.xml` file (see below). +- Add here a resolver with repository URL (and credentials, if required). +- Pass `ivysettings.xml` absolute path to `spark.jars.ivySettings` Spark config option. +- Create Spark session with passing package name to `spark.jars.packages` Spark config option: + +```{eval-rst} +.. tabs:: + + .. code-tab:: xml ivysettings-all-packages-uploaded-to-nexus.xml + + + + + + + + + + + + + + + + .. code-tab:: xml ivysettings-private-packages-in-nexus-public-in-maven.xml + + + + + + + + + + + + + + + + + + + + .. code-tab:: xml ivysettings-private-packages-in-nexus-public-fetched-using-proxy-repo.xml + + + + + + + + + + + + + + + + + + .. code-tab:: xml ivysettings-nexus-with-auth-required.xml + + + + + + + + + + + + + + + + + + + + + +``` + +```{code-block} python +:caption: script.py + +maven_packages = ( + Greenplum.get_packages(spark_version="3.2") + + MySQL.get_packages() + + Teradata.get_packages() +) + +spark = ( + SparkSession.builder.config("spark.app.name", "onetl") + .config("spark.jars.ivySettings", "/path/to/ivysettings.xml") + .config("spark.jars.packages", ",".join(maven_packages)) + .getOrCreate() +) +``` + +### Place `.jar` file to `-/.ivy2/jars/` + +Can be used to pass already downloaded file to Ivy, and skip resolving package from Maven. + +- Download `package.jar` file (it's usually something like `some-package_1.0.0.jar`). Local file name does not matter, but it should be unique. +- Move it to `-/.ivy2/jars/` folder. +- Create Spark session with passing package name to `spark.jars.packages` Spark config option: + +```python +maven_packages = ( + Greenplum.get_packages(spark_version="3.2") + + MySQL.get_packages() + + Teradata.get_packages() +) + +spark = ( + SparkSession.builder.config("spark.app.name", "onetl") + .config("spark.jars.packages", ",".join(maven_packages)) + .getOrCreate() +) +``` + +### Place `.jar` file to Spark jars folder + +```{eval-rst} +.. note:: + + Package file should be placed on all hosts/containers Spark is running, + both driver and all executors. + + Usually this is used only with either: + * ``spark.master=local`` (driver and executors are running on the same host), + * ``spark.master=k8s://...`` (``.jar`` files are added to image or to volume mounted to all pods). +``` + +Can be used to embed `.jar` files to a default Spark classpath. + +- Download `package.jar` file (it's usually something like `some-package_1.0.0.jar`). Local file name does not matter, but it should be unique. +- Move it to `$SPARK_HOME/jars/` folder, e.g. `^/.local/lib/python3.7/site-packages/pyspark/jars/` or `/opt/spark/3.2.3/jars/`. +- Create Spark session **WITHOUT** passing Package name to `spark.jars.packages` + +```python +# no need to set spark.jars.packages or any other spark.jars.* option +# all jars already present in CLASSPATH, and loaded automatically + +spark = SparkSession.builder.config("spark.app.name", "onetl").getOrCreate() +``` + +### Manually adding `.jar` files to `CLASSPATH` + +```{eval-rst} +.. note:: + + Package file should be placed on all hosts/containers Spark is running, + both driver and all executors. + + Usually this is used only with either: + * ``spark.master=local`` (driver and executors are running on the same host), + * ``spark.master=k8s://...`` (``.jar`` files are added to image or to volume mounted to all pods). +``` + +Can be used to embed `.jar` files to a default Java classpath. + +- Download `package.jar` file (it's usually something like `some-package_1.0.0.jar`). Local file name does not matter. +- Set environment variable `CLASSPATH` to `/path/to/package.jar`. You can set multiple file paths +- Create Spark session **WITHOUT** passing Package name to `spark.jars.packages` + +```python +# no need to set spark.jars.packages or any other spark.jars.* option +# all jars already present in CLASSPATH, and loaded automatically + +import os + +jar_files = ["/path/to/package.jar"] +# different delimiters for Windows and Linux +delimiter = ";" if os.name == "nt" else ":" +spark = ( + SparkSession.builder.config("spark.app.name", "onetl") + .config("spark.driver.extraClassPath", delimiter.join(jar_files)) + .config("spark.executor.extraClassPath", delimiter.join(jar_files)) + .getOrCreate() +) +``` diff --git a/mddocs/docs/logging.md b/mddocs/docs/logging.md new file mode 100644 index 000000000..dae7a9d73 --- /dev/null +++ b/mddocs/docs/logging.md @@ -0,0 +1,171 @@ +(logging)= + +# Logging + +Logging is quite important to understand what's going on under the hood of onETL. + +Default logging level for Python interpreters is `WARNING`, +but most of onETL logs are in `INFO` level, so users usually don't see much. + +To change logging level, there is a function {obj}`setup_logging ` +which should be called at the top of the script: + +```python +from onetl.log import setup_logging +from other.lib import some, more, imports + +setup_logging() + +# rest of code +... +``` + +This changes both log level and log formatting to something like this: + +```{eval-rst} +.. dropdown:: See logs + + .. code:: text + + 2024-04-12 10:12:10,834 [INFO ] MainThread: |onETL| Using IncrementalStrategy as a strategy + 2024-04-12 10:12:10,835 [INFO ] MainThread: =================================== DBReader.run() starts =================================== + 2024-04-12 10:12:10,835 [INFO ] MainThread: |DBReader| Getting Spark type for HWM expression: 'updated_at' + 2024-04-12 10:12:10,836 [INFO ] MainThread: |MSSQL| Fetching schema of table 'source_schema.table' ... + 2024-04-12 10:12:11,636 [INFO ] MainThread: |MSSQL| Schema fetched. + 2024-04-12 10:12:11,642 [INFO ] MainThread: |DBReader| Got Spark field: StructField('updated_at', TimestampType(), True) + 2024-04-12 10:12:11,642 [INFO ] MainThread: |DBReader| Detected HWM type: 'ColumnDateTimeHWM' + 2024-04-12 10:12:11,643 [INFO ] MainThread: |IncrementalStrategy| Fetching HWM from HorizonHWMStore: + 2024-04-12 10:12:11,643 [INFO ] MainThread: name = 'updated_at#source_schema.table@mssql:/mssql.host:1433/somedb' + 2024-04-12 10:12:12,181 [INFO ] MainThread: |IncrementalStrategy| Fetched HWM: + 2024-04-12 10:12:12,182 [INFO ] MainThread: hwm = ColumnDateTimeHWM( + 2024-04-12 10:12:12,182 [INFO ] MainThread: name = 'updated_at#source_schema.table@mssql:/mssql.host:1433/somedb', + 2024-04-12 10:12:12,182 [INFO ] MainThread: entity = 'source_schema.table', + 2024-04-12 10:12:12,182 [INFO ] MainThread: expression = 'updated_at', + 2024-04-12 10:12:12,184 [INFO ] MainThread: value = datetime.datetime(2024, 4, 11, 18, 10, 2, 120000), + 2024-04-12 10:12:12,184 [INFO ] MainThread: ) + 2024-04-12 10:12:12,184 [INFO ] MainThread: |MSSQL| -> |Spark| Reading DataFrame from source using parameters: + 2024-04-12 10:12:12,185 [INFO ] MainThread: source = 'source_schema.table' + 2024-04-12 10:12:12,185 [INFO ] MainThread: columns = [ + 2024-04-12 10:12:12,185 [INFO ] MainThread: 'id', + 2024-04-12 10:12:12,186 [INFO ] MainThread: 'new_value', + 2024-04-12 10:12:12,186 [INFO ] MainThread: 'old_value', + 2024-04-12 10:12:12,186 [INFO ] MainThread: 'updated_at', + 2024-04-12 10:12:12,186 [INFO ] MainThread: ] + 2024-04-12 10:12:12,187 [INFO ] MainThread: where = "field = 'some'" + 2024-04-12 10:12:12,187 [INFO ] MainThread: hwm = AutoDetectHWM( + 2024-04-12 10:12:12,187 [INFO ] MainThread: name = 'updated_at#source_schema.table@mssql:/mssql.host:1433/somedb', + 2024-04-12 10:12:12,187 [INFO ] MainThread: entity = 'source_schema.table', + 2024-04-12 10:12:12,187 [INFO ] MainThread: expression = 'updated_at', + 2024-04-12 10:12:12,188 [INFO ] MainThread: ) + 2024-04-12 10:12:12,188 [INFO ] MainThread: options = { + 2024-04-12 10:12:12,188 [INFO ] MainThread: 'fetchsize': 100000, + 2024-04-12 10:12:12,188 [INFO ] MainThread: 'numPartitions': 1, + 2024-04-12 10:12:12,189 [INFO ] MainThread: 'partitioningMode': 'range', + 2024-04-12 10:12:12,189 [INFO ] MainThread: } + 2024-04-12 10:12:12,189 [INFO ] MainThread: |MSSQL| Checking connection availability... + 2024-04-12 10:12:12,189 [INFO ] MainThread: |MSSQL| Using connection parameters: + 2024-04-12 10:12:12,190 [INFO ] MainThread: user = 'db_user' + 2024-04-12 10:12:12,190 [INFO ] MainThread: password = SecretStr('**********') + 2024-04-12 10:12:12,190 [INFO ] MainThread: host = 'mssql.host' + 2024-04-12 10:12:12,190 [INFO ] MainThread: port = 1433 + 2024-04-12 10:12:12,191 [INFO ] MainThread: database = 'somedb' + 2024-04-12 10:12:12,191 [INFO ] MainThread: extra = {'applicationIntent': 'ReadOnly', 'trustServerCertificate': 'true'} + 2024-04-12 10:12:12,191 [INFO ] MainThread: jdbc_url = 'jdbc:sqlserver:/mssql.host:1433' + 2024-04-12 10:12:12,579 [INFO ] MainThread: |MSSQL| Connection is available. + 2024-04-12 10:12:12,581 [INFO ] MainThread: |MSSQL| Executing SQL query (on driver): + 2024-04-12 10:12:12,581 [INFO ] MainThread: SELECT + 2024-04-12 10:12:12,581 [INFO ] MainThread: MIN(updated_at) AS "min", + 2024-04-12 10:12:12,582 [INFO ] MainThread: MAX(updated_at) AS "max" + 2024-04-12 10:12:12,582 [INFO ] MainThread: FROM + 2024-04-12 10:12:12,582 [INFO ] MainThread: source_schema.table + 2024-04-12 10:12:12,582 [INFO ] MainThread: WHERE + 2024-04-12 10:12:12,582 [INFO ] MainThread: (field = 'some') + 2024-04-12 10:12:12,583 [INFO ] MainThread: AND + 2024-04-12 10:12:12,583 [INFO ] MainThread: (updated_at >= CAST('2024-04-11T18:10:02.120000' AS datetime2)) + 2024-04-12 10:16:22,537 [INFO ] MainThread: |MSSQL| Received values: + 2024-04-12 10:16:22,538 [INFO ] MainThread: MIN(updated_at) = datetime.datetime(2024, 4, 11, 21, 10, 7, 397000) + 2024-04-12 10:16:22,538 [INFO ] MainThread: MAX(updated_at) = datetime.datetime(2024, 4, 12, 13, 12, 2, 123000) + 2024-04-12 10:16:22,540 [INFO ] MainThread: |MSSQL| Executing SQL query (on executor): + 2024-04-12 10:16:22,540 [INFO ] MainThread: SELECT + 2024-04-12 10:16:22,540 [INFO ] MainThread: id, + 2024-04-12 10:16:22,541 [INFO ] MainThread: new_value, + 2024-04-12 10:16:22,541 [INFO ] MainThread: old_value, + 2024-04-12 10:16:22,541 [INFO ] MainThread: updated_at + 2024-04-12 10:16:22,541 [INFO ] MainThread: FROM + 2024-04-12 10:16:22,541 [INFO ] MainThread: source_schema.table + 2024-04-12 10:16:22,542 [INFO ] MainThread: WHERE + 2024-04-12 10:16:22,542 [INFO ] MainThread: (field = 'some') + 2024-04-12 10:16:22,542 [INFO ] MainThread: AND + 2024-04-12 10:16:22,542 [INFO ] MainThread: (updated_at > CAST('2024-04-11T18:10:02.120000' AS datetime2)) + 2024-04-12 10:16:22,542 [INFO ] MainThread: AND + 2024-04-12 10:16:22,542 [INFO ] MainThread: (updated_at <= CAST('2024-04-12T13:12:02.123000' AS datetime2)) + 2024-04-12 10:16:22,892 [INFO ] MainThread: |Spark| DataFrame successfully created from SQL statement + 2024-04-12 10:16:22,892 [INFO ] MainThread: ------------------------------------ DBReader.run() ends ------------------------------------ + 2024-04-12 10:40:42,409 [INFO ] MainThread: =================================== DBWriter.run() starts =================================== + 2024-04-12 10:40:42,409 [INFO ] MainThread: |Spark| -> |Hive| Writing DataFrame to target using parameters: + 2024-04-12 10:40:42,410 [INFO ] MainThread: target = 'target_source_schema.table' + 2024-04-12 10:40:42,410 [INFO ] MainThread: options = { + 2024-04-12 10:40:42,410 [INFO ] MainThread: 'mode': 'append', + 2024-04-12 10:40:42,410 [INFO ] MainThread: 'format': 'orc', + 2024-04-12 10:40:42,410 [INFO ] MainThread: 'partitionBy': 'part_dt', + 2024-04-12 10:40:42,410 [INFO ] MainThread: } + 2024-04-12 10:40:42,411 [INFO ] MainThread: df_schema: + 2024-04-12 10:40:42,412 [INFO ] MainThread: root + 2024-04-12 10:40:42,412 [INFO ] MainThread: |-- id: integer (nullable = true) + 2024-04-12 10:40:42,413 [INFO ] MainThread: |-- new_value: string (nullable = true) + 2024-04-12 10:40:42,413 [INFO ] MainThread: |-- old_value: string (nullable = true) + 2024-04-12 10:40:42,413 [INFO ] MainThread: |-- updated_at: timestamp (nullable = true) + 2024-04-12 10:40:42,413 [INFO ] MainThread: |-- part_dt: date (nullable = true) + 2024-04-12 10:40:42,414 [INFO ] MainThread: + 2024-04-12 10:40:42,421 [INFO ] MainThread: |Hive| Checking connection availability... + 2024-04-12 10:40:42,421 [INFO ] MainThread: |Hive| Using connection parameters: + 2024-04-12 10:40:42,421 [INFO ] MainThread: cluster = 'dwh' + 2024-04-12 10:40:42,475 [INFO ] MainThread: |Hive| Connection is available. + 2024-04-12 10:40:42,476 [INFO ] MainThread: |Hive| Fetching schema of table 'target_source_schema.table' ... + 2024-04-12 10:40:43,518 [INFO ] MainThread: |Hive| Schema fetched. + 2024-04-12 10:40:43,521 [INFO ] MainThread: |Hive| Table 'target_source_schema.table' already exists + 2024-04-12 10:40:43,521 [WARNING ] MainThread: |Hive| User-specified options {'partitionBy': 'part_dt'} are ignored while inserting into existing table. Using only table parameters from Hive metastore + 2024-04-12 10:40:43,782 [INFO ] MainThread: |Hive| Inserting data into existing table 'target_source_schema.table' ... + 2024-04-12 11:06:07,396 [INFO ] MainThread: |Hive| Data is successfully inserted into table 'target_source_schema.table'. + 2024-04-12 11:06:07,397 [INFO ] MainThread: ------------------------------------ DBWriter.run() ends ------------------------------------ + 2024-04-12 11:06:07,397 [INFO ] MainThread: |onETL| Exiting IncrementalStrategy + 2024-04-12 11:06:07,397 [INFO ] MainThread: |IncrementalStrategy| Saving HWM to 'HorizonHWMStore': + 2024-04-12 11:06:07,397 [INFO ] MainThread: hwm = ColumnDateTimeHWM( + 2024-04-12 11:06:07,397 [INFO ] MainThread: name = 'updated_at#source_schema.table@mssql:/mssql.host:1433/somedb', + 2024-04-12 11:06:07,397 [INFO ] MainThread: entity = 'source_source_schema.table', + 2024-04-12 11:06:07,397 [INFO ] MainThread: expression = 'updated_at', + 2024-04-12 11:06:07,397 [INFO ] MainThread: value = datetime.datetime(2024, 4, 12, 13, 12, 2, 123000), + 2024-04-12 11:06:07,397 [INFO ] MainThread: ) + 2024-04-12 11:06:07,495 [INFO ] MainThread: |IncrementalStrategy| HWM has been saved +``` + +Each step performed by onETL is extensively logged, which should help with debugging. + +You can make logs even more verbose by changing level to `DEBUG`: + +```python +from onetl.log import setup_logging + +setup_logging(level="DEBUG", enable_clients=True) + +# rest of code +... +``` + +This also changes log level for all underlying Python libraries, e.g. showing each HTTP request being made, and so on. + +```{eval-rst} +.. currentmodule:: onetl.log +``` + +```{eval-rst} +.. autofunction:: setup_logging +``` + +```{eval-rst} +.. autofunction:: setup_clients_logging +``` + +```{eval-rst} +.. autofunction:: set_default_logging_format +``` diff --git a/mddocs/docs/make.bat b/mddocs/docs/make.bat new file mode 100644 index 000000000..53ad1e82c --- /dev/null +++ b/mddocs/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/mddocs/docs/plugins.md b/mddocs/docs/plugins.md new file mode 100644 index 000000000..a7904b076 --- /dev/null +++ b/mddocs/docs/plugins.md @@ -0,0 +1,147 @@ +(plugins)= + +# Plugins + +:::{versionadded} 0.6.0 +::: + +## What are plugins? + +### Terms + +- `Plugin` - some Python package which implements some extra functionality for onETL, like {ref}`hooks` +- `Plugin autoimport` - onETL behavior which allows to automatically import this package if it contains proper metadata (`entry_points`) + +### Features + +Plugins mechanism allows to: + +- Automatically register {ref}`hooks` which can alter onETL behavior +- Automatically register new classes, like HWM type, HWM stores and so on + +### Limitations + +Unlike other projects (like *Airflow 1.x*), plugins does not inject imported classes or functions to `onetl.*` namespace. +Users should import classes from the plugin package **explicitly** to avoid name collisions. + +## How to implement plugin? + +Create a Python package `some-plugin` with a file `some_plugin/setup.py`: + +```python +# some_plugin/setup.py +from setuptools import setup + +setup( + # if you want to import something from onETL, add it to requirements list + install_requires=["onetl"], + entry_points={ + # this key enables plugins autoimport functionality + "onetl.plugins": [ + "some-plugin-name=some_plugin.module", # automatically import all module content + "some-plugin-class=some_plugin.module.internals:MyClass", # import a specific class + "some-plugin-function=some_plugin.module.internals:my_function", # import a specific function + ], + }, +) +``` + +See [setuptools documentation for entry_points](https://setuptools.pypa.io/en/latest/userguide/entry_point.html) + +## How plugins are imported? + +- User should install a package implementing the plugin: + +```bash +pip install some-package +``` + +- Then user should import something from `onetl` module or its submodules: + +```python +import onetl +from onetl.connection import SomeConnection + +# and so on +``` + +- This import automatically executes something like: + +```python +import some_plugin.module +from some_plugin.module.internals import MyClass +from some_plugin.module.internals import my_function +``` + +If specific module/class/function uses some registration capabilities of onETL, +like {ref}`hook-decorator`, it will be executed during this import. + +## How to enable/disable plugins? + +:::{versionadded} 0.7.0 +::: + +### Disable/enable all plugins + +By default plugins are enabled. + +To disabled them, you can set environment variable `ONETL_PLUGINS_ENABLED` to `false` BEFORE +importing onETL. This will disable all plugins autoimport. + +But user is still be able to explicitly import `some_plugin.module`, executing +all decorators and registration capabilities of onETL. + +### Disable a specific plugin (blacklist) + +If some plugin is failing during import, you can disable it by setting up environment variable +`ONETL_PLUGINS_BLACKLIST=some-failing-plugin`. Multiple plugin names could be passed with `,` as delimiter. + +Again, this environment variable should be set BEFORE importing onETL. + +### Disable all plugins except a specific one (whitelist) + +You can also disable all plugins except a specific one by setting up environment variable +`ONETL_PLUGINS_WHITELIST=some-not-failing-plugin`. Multiple plugin names could be passed with `,` as delimiter. + +Again, this environment variable should be set BEFORE importing onETL. + +If both whitelist and blacklist environment variables are set, blacklist has a higher priority. + +## How to see logs of the plugins mechanism? + +Plugins registration emits logs with `INFO` level: + +```python +import logging + +logging.basicConfig(level=logging.INFO) +``` + +```text +INFO |onETL| Found 2 plugins +INFO |onETL| Loading plugin 'my-plugin' +INFO |onETL| Skipping plugin 'failing' because it is in a blacklist +``` + +More detailed logs are emitted with `DEBUG` level, to make output less verbose: + +```python +import logging + +logging.basicConfig(level=logging.DEBUG) +``` + +```text +DEBUG |onETL| Searching for plugins with group 'onetl.plugins' +DEBUG |Plugins| Plugins whitelist: [] +DEBUG |Plugins| Plugins blacklist: ['failing-plugin'] +INFO |Plugins| Found 2 plugins +INFO |onETL| Loading plugin (1/2): +DEBUG name: 'my-plugin' +DEBUG package: 'my-package' +DEBUG version: '0.1.0' +DEBUG importing: 'my_package.my_module:MyClass' +DEBUG |onETL| Successfully loaded plugin 'my-plugin' +DEBUG source: '/usr/lib/python3.11/site-packages/my_package/my_module/my_class.py' +INFO |onETL| Skipping plugin 'failing' because it is in a blacklist +``` diff --git a/mddocs/docs/quickstart.md b/mddocs/docs/quickstart.md new file mode 100644 index 000000000..d2367a3de --- /dev/null +++ b/mddocs/docs/quickstart.md @@ -0,0 +1,4 @@ +```{eval-rst} +.. include:: ../README.rst + :start-after: quick-start +``` diff --git a/mddocs/docs/robots.txt b/mddocs/docs/robots.txt new file mode 100644 index 000000000..dcab3dc9e --- /dev/null +++ b/mddocs/docs/robots.txt @@ -0,0 +1,5 @@ +User-agent: * +Allow: /*/stable/ +Allow: /en/stable/ # Fallback for bots that don't understand wildcards +Disallow: / +Sitemap: https://onel.readthedocs.io/sitemap.xml diff --git a/mddocs/docs/security.md b/mddocs/docs/security.md new file mode 100644 index 000000000..05a7bef5a --- /dev/null +++ b/mddocs/docs/security.md @@ -0,0 +1,3 @@ +```{eval-rst} +.. include:: ../SECURITY.rst +``` diff --git a/mddocs/docs/strategy/incremental_batch_strategy.md b/mddocs/docs/strategy/incremental_batch_strategy.md new file mode 100644 index 000000000..8884066a2 --- /dev/null +++ b/mddocs/docs/strategy/incremental_batch_strategy.md @@ -0,0 +1,12 @@ +(incremental-batch-strategy)= + +# Incremental Batch Strategy + +```{eval-rst} +.. currentmodule:: onetl.strategy.incremental_strategy +``` + +```{eval-rst} +.. autoclass:: IncrementalBatchStrategy + :members: __init__ +``` diff --git a/mddocs/docs/strategy/incremental_strategy.md b/mddocs/docs/strategy/incremental_strategy.md new file mode 100644 index 000000000..35c4fff36 --- /dev/null +++ b/mddocs/docs/strategy/incremental_strategy.md @@ -0,0 +1,12 @@ +(incremental-strategy)= + +# Incremental Strategy + +```{eval-rst} +.. currentmodule:: onetl.strategy.incremental_strategy +``` + +```{eval-rst} +.. autoclass:: IncrementalStrategy + :members: __init__ +``` diff --git a/mddocs/docs/strategy/index.md b/mddocs/docs/strategy/index.md new file mode 100644 index 000000000..eebe727fe --- /dev/null +++ b/mddocs/docs/strategy/index.md @@ -0,0 +1,21 @@ +(strategy)= + +# Read Strategies + +```{toctree} +:caption: Read Strategies +:hidden: true +:maxdepth: 3 + +snapshot_strategy +incremental_strategy +snapshot_batch_strategy +incremental_batch_strategy +``` + +onETL have several builtin strategies for reading data: + +1. {doc}`snapshot_strategy` +2. {doc}`incremental_strategy` +3. {doc}`snapshot_batch_strategy` +4. {doc}`incremental_batch_strategy` diff --git a/mddocs/docs/strategy/snapshot_batch_strategy.md b/mddocs/docs/strategy/snapshot_batch_strategy.md new file mode 100644 index 000000000..dcff0670d --- /dev/null +++ b/mddocs/docs/strategy/snapshot_batch_strategy.md @@ -0,0 +1,12 @@ +(snapshot-batch-strategy)= + +# Snapshot Batch Strategy + +```{eval-rst} +.. currentmodule:: onetl.strategy.snapshot_strategy +``` + +```{eval-rst} +.. autoclass:: SnapshotBatchStrategy + :members: __init__ +``` diff --git a/mddocs/docs/strategy/snapshot_strategy.md b/mddocs/docs/strategy/snapshot_strategy.md new file mode 100644 index 000000000..e5d973831 --- /dev/null +++ b/mddocs/docs/strategy/snapshot_strategy.md @@ -0,0 +1,12 @@ +(snapshot-strategy)= + +# Snapshot Strategy + +```{eval-rst} +.. currentmodule:: onetl.strategy.snapshot_strategy +``` + +```{eval-rst} +.. autoclass:: SnapshotStrategy + :members: __init__ +``` diff --git a/mddocs/docs/troubleshooting/index.md b/mddocs/docs/troubleshooting/index.md new file mode 100644 index 000000000..de93ac11e --- /dev/null +++ b/mddocs/docs/troubleshooting/index.md @@ -0,0 +1,25 @@ +(troubleshooting)= + +# Troubleshooting + +In case of error please follow instructions below: + +- Read the logs or exception messages you've faced with. + : - If Python logs are note verbose enough, {ref}`increase the log level `. + - If Spark logs are note verbose enough, {ref}`increase the log level `. +- Read documentation related to a class or method you are using. +- [Google](https://google.com) the error message, and carefully read the search result: + : - [StackOverflow](https://stackoverflow.com/) answers. + - [Spark](https://spark.apache.org/docs/latest/) documentation. + - Documentation of database or filesystem you are connecting to. + - Documentation of underlying connector. +- Search for known [issues](https://github.com/MobileTeleSystems/onetl/issues), or create a new one. +- Always use the most resent versions of onETL, PySpark and connector packages, {ref}`compatible with your environment `. + +```{toctree} +:caption: Troubleshooting +:hidden: true +:maxdepth: 3 + +spark +``` diff --git a/mddocs/docs/troubleshooting/spark.md b/mddocs/docs/troubleshooting/spark.md new file mode 100644 index 000000000..a71d8b732 --- /dev/null +++ b/mddocs/docs/troubleshooting/spark.md @@ -0,0 +1,75 @@ +(troubleshooting-spark)= + +# Spark Troubleshooting + +## Restarting Spark session + +Sometimes it is required to stop current Spark session and start a new one, e.g. to add some .jar packages, or change session config. +But PySpark not only starts Spark session, but also starts Java virtual machine (JVM) process in the background, +which. So calling `sparkSession.stop()` [does not shutdown JVM](https://issues.apache.org/jira/browse/SPARK-47740), +and this can cause some issue. + +Also apart from JVM properties, stopping Spark session does not clear Spark context, which is a global object. So new +Spark sessions are created using the same context object, and thus using the same Spark config options. + +To properly stop Spark session, it is **required** to: +\* Stop Spark session by calling `sparkSession.stop()`. +\* **STOP PYTHON INTERPRETER**, e.g. by calling `sys.exit()`. +\* Start new Python interpreter. +\* Start new Spark session with config options you need. + +Skipping some of these steps can lead to issues with creating new Spark session. + +## Driver log level + +Default logging level for Spark session is `WARN`. To show more verbose logs, use: + +```python +spark.sparkContext.setLogLevel("INFO") +``` + +or increase verbosity even more: + +```python +spark.sparkContext.setLogLevel("DEBUG") +``` + +After getting all information you need, you can return back the previous log level: + +```python +spark.sparkContext.setLogLevel("WARN") +``` + +## Executors log level + +`sparkContext.setLogLevel` changes only log level of Spark session on Spark **driver**. +To make Spark executor logs more verbose, perform following steps: + +- Create `log4j.properties` file with content like this: + + ```jproperties + log4j.rootCategory=DEBUG, console + + log4j.appender.console=org.apache.log4j.ConsoleAppender + log4j.appender.console.target=System.err + log4j.appender.console.layout=org.apache.log4j.PatternLayout + log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{1}: %m%n + ``` + +- Stop existing Spark session and create a new one with following options: + + ```python + from pyspark.sql import SparkSession + + spark = ( + SparkSesion.builder.config("spark.files", "file:log4j.properties").config( + "spark.executor.extraJavaOptions", "-Dlog4j.configuration=file:log4j.properties" + ) + # you can apply the same logging settings to Spark driver, by uncommenting the line below + # .config("spark.driver.extraJavaOptions", "-Dlog4j.configuration=file:log4j.properties") + .getOrCreate() + ) + ``` + +Each Spark executor will receive a copy of `log4j.properties` file during start, and load it to change own log level. +Same approach can be used for Spark driver as well, to investigate issue when Spark session cannot properly start. diff --git a/mddocs/mkdocs.yml b/mddocs/mkdocs.yml new file mode 100644 index 000000000..6dcd08d5a --- /dev/null +++ b/mddocs/mkdocs.yml @@ -0,0 +1,46 @@ +site_name: onETL Docs + +theme: + name: material + features: + - navigation.indexes + - content.tabs.link + palette: + - scheme: default + toggle: + icon: material/weather-night + name: Switch to dark mode + - scheme: slate + toggle: + icon: material/weather-sunny + name: Switch to light mode + locale: ru + highlightjs: true + hljs_languages: + - yaml + +markdown_extensions: + - attr_list + - md_in_html + - admonition + - pymdownx.details + - pymdownx.critic + - mdx_gh_links + - pymdownx.tabbed: + alternate_style: true + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + +extension_configs: + - mdx_gh_links: + - user: "foo" + - repo: "bar" + +nav: + - Test: changelog/0.7.0.md \ No newline at end of file From bf69d94c2aa7360178af4304893936a44c7cb812 Mon Sep 17 00:00:00 2001 From: Sattar Gyulmamedov Date: Mon, 9 Jun 2025 18:31:09 +0300 Subject: [PATCH 07/22] added changelog translation from scratch --- mddocs/docs/changelog/0.7.0.md | 2 +- mddocs/ru/changelog/0.10.0.md | 543 +++++++++++++++++++++++++++++++++ mddocs/ru/changelog/0.10.1.md | 29 ++ mddocs/ru/changelog/0.10.2.md | 39 +++ mddocs/ru/changelog/0.11.0.md | 212 +++++++++++++ mddocs/ru/changelog/0.11.1.md | 9 + mddocs/ru/changelog/0.11.2.md | 5 + mddocs/ru/changelog/0.12.0.md | 54 ++++ mddocs/ru/changelog/0.12.1.md | 17 ++ mddocs/ru/changelog/0.12.2.md | 18 ++ mddocs/ru/changelog/0.12.3.md | 5 + mddocs/ru/changelog/0.12.4.md | 5 + mddocs/ru/changelog/0.12.5.md | 13 + mddocs/ru/changelog/0.13.0.md | 222 ++++++++++++++ mddocs/ru/changelog/0.13.1.md | 9 + mddocs/ru/changelog/0.13.3.md | 5 + mddocs/ru/changelog/0.13.4.md | 10 + mddocs/ru/changelog/0.7.0.md | 237 ++++++++++++++ mddocs/ru/changelog/0.7.1.md | 40 +++ mddocs/ru/changelog/0.7.2.md | 37 +++ mddocs/ru/changelog/0.8.0.md | 162 ++++++++++ mddocs/ru/changelog/0.8.1.md | 42 +++ mddocs/ru/changelog/0.9.0.md | 122 ++++++++ mddocs/ru/changelog/0.9.1.md | 7 + mddocs/ru/changelog/0.9.2.md | 23 ++ mddocs/ru/changelog/0.9.3.md | 5 + mddocs/ru/changelog/0.9.4.md | 23 ++ mddocs/ru/changelog/0.9.5.md | 14 + 28 files changed, 1908 insertions(+), 1 deletion(-) create mode 100644 mddocs/ru/changelog/0.10.0.md create mode 100644 mddocs/ru/changelog/0.10.1.md create mode 100644 mddocs/ru/changelog/0.10.2.md create mode 100644 mddocs/ru/changelog/0.11.0.md create mode 100644 mddocs/ru/changelog/0.11.1.md create mode 100644 mddocs/ru/changelog/0.11.2.md create mode 100644 mddocs/ru/changelog/0.12.0.md create mode 100644 mddocs/ru/changelog/0.12.1.md create mode 100644 mddocs/ru/changelog/0.12.2.md create mode 100644 mddocs/ru/changelog/0.12.3.md create mode 100644 mddocs/ru/changelog/0.12.4.md create mode 100644 mddocs/ru/changelog/0.12.5.md create mode 100644 mddocs/ru/changelog/0.13.0.md create mode 100644 mddocs/ru/changelog/0.13.1.md create mode 100644 mddocs/ru/changelog/0.13.3.md create mode 100644 mddocs/ru/changelog/0.13.4.md create mode 100644 mddocs/ru/changelog/0.7.0.md create mode 100644 mddocs/ru/changelog/0.7.1.md create mode 100644 mddocs/ru/changelog/0.7.2.md create mode 100644 mddocs/ru/changelog/0.8.0.md create mode 100644 mddocs/ru/changelog/0.8.1.md create mode 100644 mddocs/ru/changelog/0.9.0.md create mode 100644 mddocs/ru/changelog/0.9.1.md create mode 100644 mddocs/ru/changelog/0.9.2.md create mode 100644 mddocs/ru/changelog/0.9.3.md create mode 100644 mddocs/ru/changelog/0.9.4.md create mode 100644 mddocs/ru/changelog/0.9.5.md diff --git a/mddocs/docs/changelog/0.7.0.md b/mddocs/docs/changelog/0.7.0.md index 7325faa96..2140962fa 100644 --- a/mddocs/docs/changelog/0.7.0.md +++ b/mddocs/docs/changelog/0.7.0.md @@ -78,7 +78,7 @@ That was long road, but we finally did it! - Added new `cluster` argument to `Hive` and `HDFS` connections. - `Hive` qualified name (used in HWM) contains cluster name. But in onETL\<0.7.0 cluster name had hard coded value `rnd-dwh` which was not OK for some users. + `Hive` qualified name (used in HWM) contains cluster name. But in onETL<0.7.0 cluster name had hard coded value `rnd-dwh` which was not OK for some users. `HDFS` connection qualified name contains host (active namenode of Hadoop cluster), but its value can change over time, leading to creating of new HWM. diff --git a/mddocs/ru/changelog/0.10.0.md b/mddocs/ru/changelog/0.10.0.md new file mode 100644 index 000000000..68a609eb6 --- /dev/null +++ b/mddocs/ru/changelog/0.10.0.md @@ -0,0 +1,543 @@ +# 0.10.0 (2023-12-18) + +## Breaking Changes + +- Upgrade `etl-entities` from v1 to v2 ([#172](https://github.com/MobileTeleSystems/onetl/pull/172)). + + This implies that `HWM` classes are now have different internal structure than they used to. + + Before: + + ```python + from etl_entities.old_hwm import IntHWM as OldIntHWM + from etl_entities.source import Column, Table + from etl_entities.process import Process + + hwm = OldIntHWM( + process=Process(name="myprocess", task="abc", dag="cde", host="myhost"), + source=Table(name="schema.table", instance="postgres://host:5432/db"), + column=Column(name="col1"), + value=123, + ) + ``` + + After: + + ```python + from etl_entities.hwm import ColumnIntHWM + + hwm = ColumnIntHWM( + name="some_unique_name", + description="any value you want", + source="schema.table", + expression="col1", + value=123, + ) + ``` + + **Breaking change:** If you used HWM classes from `etl_entities` module, you should rewrite your code to make it compatible with new version. + +??? "More details" + + - ``HWM`` classes used by previous onETL versions were moved from ``etl_entities`` to ``etl_entities.old_hwm`` submodule. They are here for compatibility reasons, but are planned to be removed in ``etl-entities`` v3 release. + - New ``HWM`` classes have flat structure instead of nested. + - New ``HWM`` classes have mandatory ``name`` attribute (it was known as ``qualified_name`` before). + - Type aliases used while serializing and deserializing ``HWM`` objects to ``dict`` representation were changed too: ``int`` → ``column_int``. + + + To make migration simpler, you can use new method: + + ```python + old_hwm = OldIntHWM(...) + new_hwm = old_hwm.as_new_hwm() + ``` + + Which automatically converts all fields from old structure to new one, including `qualified_name` → `name`. + +- **Breaking changes:** + + - Methods `BaseHWMStore.get()` and `BaseHWMStore.save()` were renamed to `get_hwm()` and `set_hwm()`. + - They now can be used only with new HWM classes from `etl_entities.hwm`, **old HWM classes are not supported**. + + If you used them in your code, please update it accordingly. + +- YAMLHWMStore **CANNOT read files created by older onETL versions** (0.9.x or older). + +??? "Update procedure" + + ```python + # pip install onetl==0.9.5 + + # Get qualified_name for HWM + + + # Option 1. HWM is built manually + from etl_entities import IntHWM, FileListHWM + from etl_entities.source import Column, Table, RemoteFolder + from etl_entities.process import Process + + # for column HWM + old_column_hwm = IntHWM( + process=Process(name="myprocess", task="abc", dag="cde", host="myhost"), + source=Table(name="schema.table", instance="postgres://host:5432/db"), + column=Column(name="col1"), + ) + qualified_name = old_column_hwm.qualified_name + # "col1#schema.table@postgres://host:5432/db#cde.abc.myprocess@myhost" + + # for file HWM + old_file_hwm = FileListHWM( + process=Process(name="myprocess", task="abc", dag="cde", host="myhost"), + source=RemoteFolder(name="/absolute/path", instance="ftp://ftp.server:21"), + ) + qualified_name = old_file_hwm.qualified_name + # "file_list#/absolute/path@ftp://ftp.server:21#cde.abc.myprocess@myhost" + + + # Option 2. HWM is generated automatically (by DBReader/FileDownloader) + # See onETL logs and search for string like qualified_name = '...' + + qualified_name = "col1#schema.table@postgres://host:5432/db#cde.abc.myprocess@myhost" + + + # Get .yml file path by qualified_name + + import os + from pathlib import PurePosixPath + from onetl.hwm.store import YAMLHWMStore + + # here you should pass the same arguments as used on production, if any + yaml_hwm_store = YAMLHWMStore() + hwm_path = yaml_hwm_store.get_file_path(qualified_name) + print(hwm_path) + + # for column HWM + # LocalPosixPath('/home/maxim/.local/share/onETL/yml_hwm_store/col1__schema.table__postgres_host_5432_db__cde.abc.myprocess__myhost.yml') + + # for file HWM + # LocalPosixPath('/home/maxim/.local/share/onETL/yml_hwm_store/file_list__absolute_path__ftp_ftp.server_21__cde.abc.myprocess__myhost.yml') + + + # Read raw .yml file content + + from yaml import safe_load, dump + + raw_old_hwm_items = safe_load(hwm_path.read_text()) + print(raw_old_hwm_items) + + # for column HWM + # [ + # { + # "column": { "name": "col1", "partition": {} }, + # "modified_time": "2023-12-18T10: 39: 47.377378", + # "process": { "dag": "cde", "host": "myhost", "name": "myprocess", "task": "abc" }, + # "source": { "instance": "postgres: //host:5432/db", "name": "schema.table" }, + # "type": "int", + # "value": "123", + # }, + # ] + + # for file HWM + # [ + # { + # "modified_time": "2023-12-18T11:15:36.478462", + # "process": { "dag": "cde", "host": "myhost", "name": "myprocess", "task": "abc" }, + # "source": { "instance": "ftp://ftp.server:21", "name": "/absolute/path" }, + # "type": "file_list", + # "value": ["file1.txt", "file2.txt"], + # }, + # ] + + + # Convert file content to new structure, compatible with onETL 0.10.x + raw_new_hwm_items = [] + for old_hwm in raw_old_hwm_items: + new_hwm = {"name": qualified_name, "modified_time": old_hwm["modified_time"]} + + if "column" in old_hwm: + new_hwm["expression"] = old_hwm["column"]["name"] + new_hwm["entity"] = old_hwm["source"]["name"] + old_hwm.pop("process", None) + + if old_hwm["type"] == "int": + new_hwm["type"] = "column_int" + new_hwm["value"] = old_hwm["value"] + + elif old_hwm["type"] == "date": + new_hwm["type"] = "column_date" + new_hwm["value"] = old_hwm["value"] + + elif old_hwm["type"] == "datetime": + new_hwm["type"] = "column_datetime" + new_hwm["value"] = old_hwm["value"] + + elif old_hwm["type"] == "file_list": + new_hwm["type"] = "file_list" + new_hwm["value"] = [ + os.fspath(PurePosixPath(old_hwm["source"]["name"]).joinpath(path)) + for path in old_hwm["value"] + ] + + else: + raise ValueError("WAT?") + + raw_new_hwm_items.append(new_hwm) + + + print(raw_new_hwm_items) + # for column HWM + # [ + # { + # "name": "col1#schema.table@postgres://host:5432/db#cde.abc.myprocess@myhost", + # "modified_time": "2023-12-18T10:39:47.377378", + # "expression": "col1", + # "source": "schema.table", + # "type": "column_int", + # "value": 123, + # }, + # ] + + # for file HWM + # [ + # { + # "name": "file_list#/absolute/path@ftp://ftp.server:21#cde.abc.myprocess@myhost", + # "modified_time": "2023-12-18T11:15:36.478462", + # "entity": "/absolute/path", + # "type": "file_list", + # "value": ["/absolute/path/file1.txt", "/absolute/path/file2.txt"], + # }, + # ] + + + # Save file with new content + with open(hwm_path, "w") as file: + dump(raw_new_hwm_items, file) + + + # Stop Python interpreter and update onETL + # pip install onetl==0.10.0 + # Check that new .yml file can be read + + from onetl.hwm.store import YAMLHWMStore + + qualified_name = ... + + # here you should pass the same arguments as used on production, if any + yaml_hwm_store = YAMLHWMStore() + yaml_hwm_store.get_hwm(qualified_name) + + # for column HWM + # ColumnIntHWM( + # name='col1#schema.table@postgres://host:5432/db#cde.abc.myprocess@myhost', + # description='', + # entity='schema.table', + # value=123, + # expression='col1', + # modified_time=datetime.datetime(2023, 12, 18, 10, 39, 47, 377378), + # ) + + # for file HWM + # FileListHWM( + # name='file_list#/absolute/path@ftp://ftp.server:21#cde.abc.myprocess@myhost', + # description='', + # entity=AbsolutePath('/absolute/path'), + # value=frozenset({AbsolutePath('/absolute/path/file1.txt'), AbsolutePath('/absolute/path/file2.txt')}), + # expression=None, + # modified_time=datetime.datetime(2023, 12, 18, 11, 15, 36, 478462) + # ) + + + # That's all! + ``` + +But most of users use other HWM store implementations which do not have such issues. + +- Several classes and functions were moved from `onetl` to `etl_entities`: + +=== "onETL ``0.9.x`` and older" + + ```python + + from onetl.hwm.store import ( + detect_hwm_store, + BaseHWMStore, + HWMStoreClassRegistry, + register_hwm_store_class, + HWMStoreManager, + MemoryHWMStore, + ) + ``` + +=== "nETL ``0.10.x`` and newer" + + ```python + + from etl_entities.hwm_store import ( + detect_hwm_store, + BaseHWMStore, + HWMStoreClassRegistry, + register_hwm_store_class, + HWMStoreManager, + MemoryHWMStore, + ) + ``` + + They still can be imported from old module, but this is deprecated and will be removed in v1.0.0 release. + +- Change the way of passing `HWM` to `DBReader` and `FileDownloader` classes: + +=== "onETL ``0.9.x`` and older" + + ```python linenums="1" hl_lines="12-21" + # Simple + reader = DBReader( + connection=..., + source=..., + hwm_column="col1", + ) + + + + + + # Complex + reader = DBReader( + connection=..., + source=..., + hwm_column=( + "col1", + "cast(col1 as date)", + ), + ) + + + # Files + downloader = FileDownloader( + connection=..., + source_path=..., + target_path=..., + hwm_type="file_list", + ) + ``` + +=== "onETL ``0.10.x`` and newer" + + ```python linenums="1" hl_lines="12-21" + # Simple + reader = DBReader( + connection=..., + source=..., + hwm=DBReader.AutoDetectHWM( + # name is mandatory now! + name="my_unique_hwm_name", + expression="col1", + ), + ) + + # Complex + reader = DBReader( + connection=..., + source=..., + hwm=DBReader.AutoDetectHWM( + # name is mandatory now! + name="my_unique_hwm_name", + expression="cast(col1 as date)", + ), + ) + + # Files + downloader = FileDownloader( + connection=..., + source_path=..., + target_path=..., + hwm=FileListHWM( + # name is mandatory now! + name="another_unique_hwm_name", + ), + ) + ``` + + + New HWM classes have **mandatory** `name` attribute which should be passed explicitly, + instead of generating if automatically under the hood. + + Automatic `name` generation using the old `DBReader.hwm_column` / `FileDownloader.hwm_type` + syntax is still supported, but will be removed in v1.0.0 release. ([#179](https://github.com/MobileTeleSystems/onetl/pull/179)) + +- Performance of read Incremental and Batch strategies has been drastically improved. ([#182](https://github.com/MobileTeleSystems/onetl/pull/182)). + +??? "Before and after in details" + + ``DBReader.run()`` + incremental/batch strategy behavior in versions 0.9.x and older: + + - Get table schema by making query ``SELECT * FROM table WHERE 1=0`` (if ``DBReader.columns`` has ``*``) + - Expand ``*`` to real column names from table, add here ``hwm_column``, remove duplicates (as some RDBMS does not allow that). + - Create dataframe from query like ``SELECT hwm_expression AS hwm_column, ...other table columns... FROM table WHERE hwm_expression > prev_hwm.value``. + - Determine HWM class using dataframe schema: ``df.schema[hwm_column].dataType``. + - Determine x HWM column value using Spark: ``df.select(max(hwm_column)).collect()``. + - Use ``max(hwm_column)`` as next HWM value, and save it to HWM Store. + - Return dataframe to user. + + This was far from ideal: + + - Dataframe content (all rows or just changed ones) was loaded from the source to Spark only to get min/max values of specific column. + + - Step of fetching table schema and then substituting column names in the next query caused some unexpected errors. + + For example, source contains columns with mixed name case, like ``"CamelColumn"`` or ``"spaced column"``. + + Column names were *not* escaped during query generation, leading to queries that cannot be executed by database. + + So users have to *explicitly* pass column names ``DBReader``, wrapping columns with mixed naming with ``"``: + + ```python + reader = DBReader( + connection=..., + source=..., + columns=[ # passing '*' here leads to wrong SQL query generation + "normal_column", + '"CamelColumn"', + '"spaced column"', + ..., + ], + ) + ``` + - Using ``DBReader`` with ``IncrementalStrategy`` could lead to reading rows already read before. + + Dataframe was created from query with WHERE clause like ``hwm.expression > prev_hwm.value``, + not ``hwm.expression > prev_hwm.value AND hwm.expression <= current_hwm.value``. + + So if new rows appeared in the source **after** HWM value is determined, + they can be read by accessing dataframe content (because Spark dataframes are lazy), + leading to inconsistencies between HWM value and dataframe content. + + This may lead to issues then ``DBReader.run()`` read some data, updated HWM value, and next call of ``DBReader.run()`` + will read rows that were already read in previous run. + + ``DBReader.run()`` + incremental/batch strategy behavior in versions 0.10.x and newer: + + - Detect type of HWM expression: ``SELECT hwm.expression FROM table WHERE 1=0``. + - Determine corresponding Spark type ``df.schema[0]`` and when determine matching HWM class (if ``DReader.AutoDetectHWM`` is used). + - Get min/max values by querying the source: ``SELECT MAX(hwm.expression) FROM table WHERE hwm.expression >= prev_hwm.value``. + - Use ``max(hwm.expression)`` as next HWM value, and save it to HWM Store. + - Create dataframe from query ``SELECT ... table columns ... FROM table WHERE hwm.expression > prev_hwm.value AND hwm.expression <= current_hwm.value``, baking new HWM value into the query. + - Return dataframe to user. + + Improvements: + + - Allow source to calculate min/max instead of loading everything to Spark. This should be **faster** on large amounts of data (**up to x2**), because we do not transfer all the data from the source to Spark. This can be even faster if source have indexes for HWM column. + - Columns list is passed to source as-is, without any resolving on `DBReader` side. So you can pass `DBReader(columns=["*"])` to read tables with mixed columns naming. + - Restrict dataframe content to always match HWM values, which leads to never reading the same row twice. + + **Breaking change**: HWM column is not being implicitly added to dataframe. It was a part of `SELECT` clause, but now it is mentioned only in `WHERE` clause. + + So if you had code like this, you have to rewrite it: + +=== "onETL ``0.9.x`` and older" + + ```python linenums="1" hl_lines="1-16" + reader = DBReader( + connection=..., + source=..., + columns=[ + "col1", + "col2", + ], + hwm_column="hwm_col", + ) + + df = reader.run() + # hwm_column value is in the dataframe + assert df.columns == ["col1", "col2", "hwm_col"] + + + + + reader = DBReader( + connection=..., + source=..., + columns=[ + "col1", + "col2", + ], + hwm_column=( + "hwm_col", + "cast(hwm_col as int)", + ), + ) + + df = reader.run() + # hwm_expression value is in the dataframe + assert df.columns == ["col1", "col2", "hwm_col"] + + ``` + +=== "onETL ``0.10.x`` and newer" + + ```python linenums="1" hl_lines="1-16" + reader = DBReader( + connection=..., + source=..., + columns=[ + "col1", + "col2", + # add hwm_column explicitly + "hwm_col", + ], + hwm_column="hwm_col", + ) + + df = reader.run() + # if columns list is not updated, + # this fill fail + assert df.columns == ["col1", "col2", "hwm_col"] + + reader = DBReader( + connection=..., + source=..., + columns=[ + "col1", + "col2", + # add hwm_expression explicitly + "cast(hwm_col as int) as hwm_col", + ], + hwm_column=( + "hwm_col", + "cast(hwm_col as int)", + ), + ) + df = reader.run() + # if columns list is not updated, + # this fill fail + assert df.columns == ["col1", "col2", "hwm_col"] + ``` + + But most users just use `columns=["*"]` anyway, they won't see any changes. + +- `FileDownloader.run()` now updates HWM in HWM Store not after each file is being successfully downloaded, + but after all files were handled. + + This is because: + + - FileDownloader can be used with `DownloadOptions(workers=N)`, which could lead to race condition - one thread can save to HWM store one HWM value when another thread will save different value. + - FileDownloader can download hundreds and thousands of files, and issuing a request to HWM Store for each file could potentially DDoS HWM Store. ([#189](https://github.com/MobileTeleSystems/onetl/pull/189)) + + There is a exception handler which tries to save HWM to HWM store if download process was interrupted. But if it was interrupted by force, like sending `SIGKILL` event, + HWM will not be saved to HWM store, so some already downloaded files may be downloaded again next time. + + But unexpected process kill may produce other negative impact, like some file will be downloaded partially, so this is an expected behavior. + +## Features + +- Add Python 3.12 compatibility. ([#167](https://github.com/MobileTeleSystems/onetl/pull/167)) +- `Excel` file format now can be used with Spark 3.5.0. ([#187](https://github.com/MobileTeleSystems/onetl/pull/187)) +- `SnapshotBatchStagy` and `IncrementalBatchStrategy` does no raise exceptions if source does not contain any data. + Instead they stop at first iteration and return empty dataframe. ([#188](https://github.com/MobileTeleSystems/onetl/pull/188)) +- Cache result of `connection.check()` in high-level classes like `DBReader`, `FileDownloader` and so on. This makes logs less verbose. ([#190](https://github.com/MobileTeleSystems/onetl/pull/190)) + +## Bug Fixes + +- Fix `@slot` and `@hook` decorators returning methods with missing arguments in signature (Pylance, VS Code). ([#183](https://github.com/MobileTeleSystems/onetl/pull/183)) +- Kafka connector documentation said that it does support reading topic data incrementally by passing `group.id` or `groupIdPrefix`. + Actually, this is not true, because Spark does not send information to Kafka which messages were consumed. + So currently users can only read the whole topic, no incremental reads are supported. diff --git a/mddocs/ru/changelog/0.10.1.md b/mddocs/ru/changelog/0.10.1.md new file mode 100644 index 000000000..ba3ed40bb --- /dev/null +++ b/mddocs/ru/changelog/0.10.1.md @@ -0,0 +1,29 @@ +# 0.10.1 (2024-02-05) + +## Features + +- Add support of `Incremental Strategies` for `Kafka` connection: + + ```python + reader = DBReader( + connection=Kafka(...), + source="topic_name", + hwm=DBReader.AutoDetectHWM(name="some_hwm_name", expression="offset"), + ) + + with IncrementalStrategy(): + df = reader.run() + ``` + + This lets you resume reading data from a Kafka topic starting at the last committed offset from your previous run. ([#202](https://github.com/MobileTeleSystems/onetl/pull/202)) + +- Add `has_data`, `raise_if_no_data` methods to `DBReader` class. ([#203](https://github.com/MobileTeleSystems/onetl/pull/203)) + +- Updare VMware Greenplum connector from `2.1.4` to `2.3.0`. This implies: + : - Greenplum 7.x support + - [Kubernetes support](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/configure.html#k8scfg) + - New read option [gpdb.matchDistributionPolicy](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#distpolmotion) + which allows to match each Spark executor with specific Greenplum segment, avoiding redundant data transfer between Greenplum segments + - Allows overriding [Greenplum optimizer parameters](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#greenplum-gucs) in read/write operations ([#208](https://github.com/MobileTeleSystems/onetl/pull/208)) + +- `Greenplum.get_packages()` method now accepts optional arg `package_version` which allows to override version of Greenplum connector package. ([#208](https://github.com/MobileTeleSystems/onetl/pull/208)) diff --git a/mddocs/ru/changelog/0.10.2.md b/mddocs/ru/changelog/0.10.2.md new file mode 100644 index 000000000..3ae7ad822 --- /dev/null +++ b/mddocs/ru/changelog/0.10.2.md @@ -0,0 +1,39 @@ +# 0.10.2 (2024-03-21) + +## Features + +- Add support of Pydantic v2. ([#230](https://github.com/MobileTeleSystems/onetl/pull/230)) + +## Improvements + +- Improve database connections documentation: + - Add "Types" section describing mapping between Clickhouse and Spark types + - Add "Prerequisites" section describing different aspects of connecting to Clickhouse + - Separate documentation of `DBReader` and `.sql()` / `.pipeline(...)` + - Add examples for `.fetch()` and `.execute()` ([#211](https://github.com/MobileTeleSystems/onetl/pull/211), [#228](https://github.com/MobileTeleSystems/onetl/pull/228), [#229](https://github.com/MobileTeleSystems/onetl/pull/229), [#233](https://github.com/MobileTeleSystems/onetl/pull/233), [#234](https://github.com/MobileTeleSystems/onetl/pull/234), [#235](https://github.com/MobileTeleSystems/onetl/pull/235), [#236](https://github.com/MobileTeleSystems/onetl/pull/236), [#240](https://github.com/MobileTeleSystems/onetl/pull/240)) + +- Add notes to Greenplum documentation about issues with IP resolution and building `gpfdist` URL ([#228](https://github.com/MobileTeleSystems/onetl/pull/228)) + +- Allow calling `MongoDB.pipeline(...)` with passing just collection name, without explicit aggregation pipeline. ([#237](https://github.com/MobileTeleSystems/onetl/pull/237)) + +- Update default `Postgres(extra={...})` to include `{"stringtype": "unspecified"}` option. + This allows to write text data to non-text column (or vice versa), relying to Postgres cast capabilities. + + For example, now it is possible to read column of type `money` as Spark's `StringType()`, and write it back to the same column, + without using intermediate columns or tables. ([#229](https://github.com/MobileTeleSystems/onetl/pull/229)) + +## Bug Fixes + +- Return back handling of `DBReader(columns="string")`. This was a valid syntax up to v0.10 release, but it was removed because + most of users neved used it. It looks that we were wrong, returning this behavior back, but with deprecation warning. ([#238](https://github.com/MobileTeleSystems/onetl/pull/238)) + +- Downgrade Greenplum package version from `2.3.0` to `2.2.0`. ([#239](https://github.com/MobileTeleSystems/onetl/pull/239)) + + This is because version 2.3.0 introduced issues with writing data to Greenplum 6.x. + Connector can open transaction with `SELECT * FROM table LIMIT 0` query, but does not close it, which leads to deadlocks. + + For using this connector with Greenplum 7.x, please pass package version explicitly: + + ```python + maven_packages = Greenplum.get_packages(package_version="2.3.0", ...) + ``` diff --git a/mddocs/ru/changelog/0.11.0.md b/mddocs/ru/changelog/0.11.0.md new file mode 100644 index 000000000..c77c977b0 --- /dev/null +++ b/mddocs/ru/changelog/0.11.0.md @@ -0,0 +1,212 @@ +# 0.11.0 (2024-05-27) + +## Breaking Changes + +There can be some changes in connection behavior, related to version upgrades. So we mark these changes as **breaking** although +most of users will not see any differences. + +- Update Clickhouse JDBC driver to latest version ([#249](https://github.com/MobileTeleSystems/onetl/pull/249)): + - Package was renamed `ru.yandex.clickhouse:clickhouse-jdbc` → `com.clickhouse:clickhouse-jdbc`. + - Package version changed `0.3.2` → `0.6.0-patch5`. + - Driver name changed `ru.yandex.clickhouse.ClickHouseDriver` → `com.clickhouse.jdbc.ClickHouseDriver`. + + This brings up several fixes for Spark \<-> Clickhouse type compatibility, and also Clickhouse clusters support. + +- Update other JDBC drivers to latest versions: + - MSSQL `12.2.0` → `12.6.2` ([#254](https://github.com/MobileTeleSystems/onetl/pull/254)). + - MySQL `8.0.33` → `8.4.0` ([#253](https://github.com/MobileTeleSystems/onetl/pull/253), [#285](https://github.com/MobileTeleSystems/onetl/pull/285)). + - Oracle `23.2.0.0` → `23.4.0.24.05` ([#252](https://github.com/MobileTeleSystems/onetl/pull/252), [#284](https://github.com/MobileTeleSystems/onetl/pull/284)). + - Postgres `42.6.0` → `42.7.3` ([#251](https://github.com/MobileTeleSystems/onetl/pull/251)). + +- Update MongoDB connector to latest version: `10.1.1` → `10.3.0` ([#255](https://github.com/MobileTeleSystems/onetl/pull/255), [#283](https://github.com/MobileTeleSystems/onetl/pull/283)). + + This brings up Spark 3.5 support. + +- Update `XML` package to latest version: `0.17.0` → `0.18.0` ([#259](https://github.com/MobileTeleSystems/onetl/pull/259)). + + This brings few bugfixes with datetime format handling. + +- For JDBC connections add new `SQLOptions` class for `DB.sql(query, options=...)` method ([#272](https://github.com/MobileTeleSystems/onetl/pull/272)). + + Firsly, to keep naming more consistent. + + Secondly, some of options are not supported by `DB.sql(...)` method, but supported by `DBReader`. + For example, `SQLOptions` do not support `partitioning_mode` and require explicit definition of `lower_bound` and `upper_bound` when `num_partitions` is greater than 1. + `ReadOptions` does support `partitioning_mode` and allows skipping `lower_bound` and `upper_bound` values. + + This require some code changes. Before: + + ```python + from onetl.connection import Postgres + + postgres = Postgres(...) + df = postgres.sql( + """ + SELECT * + FROM some.mytable + WHERE key = 'something' + """, + options=Postgres.ReadOptions( + partitioning_mode="range", + partition_column="id", + num_partitions=10, + ), + ) + ``` + + After: + + ```python + from onetl.connection import Postgres + + postgres = Postgres(...) + df = postgres.sql( + """ + SELECT * + FROM some.mytable + WHERE key = 'something' + """, + options=Postgres.SQLOptions( + # partitioning_mode is not supported! + partition_column="id", + num_partitions=10, + lower_bound=0, # <-- set explicitly + upper_bound=1000, # <-- set explicitly + ), + ) + ``` + + For now, `DB.sql(query, options=...)` can accept `ReadOptions` to keep backward compatibility, but emits deprecation warning. + The support will be removed in `v1.0.0`. + +- Split up `JDBCOptions` class into `FetchOptions` and `ExecuteOptions` ([#274](https://github.com/MobileTeleSystems/onetl/pull/274)). + + New classes are used by `DB.fetch(query, options=...)` and `DB.execute(query, options=...)` methods respectively. + This is mostly to keep naming more consistent. + + This require some code changes. Before: + + ```python + from onetl.connection import Postgres + + postgres = Postgres(...) + df = postgres.fetch( + "SELECT * FROM some.mytable WHERE key = 'something'", + options=Postgres.JDBCOptions( + fetchsize=1000, + query_timeout=30, + ), + ) + + postgres.execute( + "UPDATE some.mytable SET value = 'new' WHERE key = 'something'", + options=Postgres.JDBCOptions(query_timeout=30), + ) + ``` + + After: + + ```python + from onetl.connection import Postgres + + # Using FetchOptions for fetching data + postgres = Postgres(...) + df = postgres.fetch( + "SELECT * FROM some.mytable WHERE key = 'something'", + options=Postgres.FetchOptions( # <-- change class name + fetchsize=1000, + query_timeout=30, + ), + ) + + # Using ExecuteOptions for executing statements + postgres.execute( + "UPDATE some.mytable SET value = 'new' WHERE key = 'something'", + options=Postgres.ExecuteOptions(query_timeout=30), # <-- change class name + ) + ``` + + For now, `DB.fetch(query, options=...)` and `DB.execute(query, options=...)` can accept `JDBCOptions`, to keep backward compatibility, + but emit a deprecation warning. The old class will be removed in `v1.0.0`. + +- Serialize `ColumnDatetimeHWM` to Clickhouse's `DateTime64(6)` (precision up to microseconds) instead of `DateTime` (precision up to seconds) ([#267](https://github.com/MobileTeleSystems/onetl/pull/267)). + + In previous onETL versions, `ColumnDatetimeHWM` value was rounded to the second, and thus reading some rows that were read in previous runs, + producing duplicates. + + For Clickhouse versions below 21.1 comparing column of type `DateTime` with a value of type `DateTime64` is not supported, returning an empty dataframe. + To avoid this, replace: + + ```python + DBReader( + ..., + hwm=DBReader.AutoDetectHWM( + name="my_hwm", + expression="hwm_column", # <-- + ), + ) + ``` + + with: + + ```python + DBReader( + ..., + hwm=DBReader.AutoDetectHWM( + name="my_hwm", + expression="CAST(hwm_column AS DateTime64)", # <-- add explicit CAST + ), + ) + ``` + +- Pass JDBC connection extra params as `properties` dict instead of URL with query part ([#268](https://github.com/MobileTeleSystems/onetl/pull/268)). + + This allows passing custom connection parameters like `Clickhouse(extra={"custom_http_options": "option1=value1,option2=value2"})` + without need to apply urlencode to parameter value, like `option1%3Dvalue1%2Coption2%3Dvalue2`. + +## Features + +Improve user experience with Kafka messages and Database tables with serialized columns, like JSON/XML. + +- Allow passing custom package version as argument for `DB.get_packages(...)` method of several DB connectors: + - `Clickhouse.get_packages(package_version=..., apache_http_client_version=...)` ([#249](https://github.com/MobileTeleSystems/onetl/pull/249)). + - `MongoDB.get_packages(scala_version=..., spark_version=..., package_version=...)` ([#255](https://github.com/MobileTeleSystems/onetl/pull/255)). + - `MySQL.get_packages(package_version=...)` ([#253](https://github.com/MobileTeleSystems/onetl/pull/253)). + - `MSSQL.get_packages(java_version=..., package_version=...)` ([#254](https://github.com/MobileTeleSystems/onetl/pull/254)). + - `Oracle.get_packages(java_version=..., package_version=...)` ([#252](https://github.com/MobileTeleSystems/onetl/pull/252)). + - `Postgres.get_packages(package_version=...)` ([#251](https://github.com/MobileTeleSystems/onetl/pull/251)). + - `Teradata.get_packages(package_version=...)` ([#256](https://github.com/MobileTeleSystems/onetl/pull/256)). + Now users can downgrade or upgrade connection without waiting for next onETL release. Previously only `Kafka` and `Greenplum` supported this feature. +- Add `FileFormat.parse_column(...)` method to several classes: + - `Avro.parse_column(col)` ([#265](https://github.com/MobileTeleSystems/onetl/pull/265)). + - `JSON.parse_column(col, schema=...)` ([#257](https://github.com/MobileTeleSystems/onetl/pull/257)). + - `CSV.parse_column(col, schema=...)` ([#258](https://github.com/MobileTeleSystems/onetl/pull/258)). + - `XML.parse_column(col, schema=...)` ([#269](https://github.com/MobileTeleSystems/onetl/pull/269)). + This allows parsing data in `value` field of Kafka message or string/binary column of some table as a nested Spark structure. +- Add `FileFormat.serialize_column(...)` method to several classes: + - `Avro.serialize_column(col)` ([#265](https://github.com/MobileTeleSystems/onetl/pull/265)). + - `JSON.serialize_column(col)` ([#257](https://github.com/MobileTeleSystems/onetl/pull/257)). + - `CSV.serialize_column(col)` ([#258](https://github.com/MobileTeleSystems/onetl/pull/258)). + This allows saving Spark nested structures or arrays to `value` field of Kafka message or string/binary column of some table. + +## Improvements + +Few documentation improvements. + +- Replace all `assert` in documentation with doctest syntax. This should make documentation more readable ([#273](https://github.com/MobileTeleSystems/onetl/pull/273)). +- Add generic `Troubleshooting` guide ([#275](https://github.com/MobileTeleSystems/onetl/pull/275)). +- Improve Kafka documentation: + - Add "Prerequisites" page describing different aspects of connecting to Kafka. + - Improve "Reading from" and "Writing to" page of Kafka documentation, add more examples and usage notes. + - Add "Troubleshooting" page ([#276](https://github.com/MobileTeleSystems/onetl/pull/276)). +- Improve Hive documentation: + - Add "Prerequisites" page describing different aspects of connecting to Hive. + - Improve "Reading from" and "Writing to" page of Hive documentation, add more examples and recommendations. + - Improve "Executing statements in Hive" page of Hive documentation. ([#278](https://github.com/MobileTeleSystems/onetl/pull/278)). +- Add "Prerequisites" page describing different aspects of using SparkHDFS and SparkS3 connectors. ([#279](https://github.com/MobileTeleSystems/onetl/pull/279)). +- Add note about connecting to Clickhouse cluster. ([#280](https://github.com/MobileTeleSystems/onetl/pull/280)). +- Add notes about versions when specific class/method/attribute/argument was added, renamed or changed behavior ([#282](https://github.com/MobileTeleSystems/onetl/pull/282)). + +## Bug Fixes + +- Fix missing `pysmb` package after installing `pip install onetl[files]` . diff --git a/mddocs/ru/changelog/0.11.1.md b/mddocs/ru/changelog/0.11.1.md new file mode 100644 index 000000000..823afe3be --- /dev/null +++ b/mddocs/ru/changelog/0.11.1.md @@ -0,0 +1,9 @@ +# 0.11.1 (2024-05-29) + +## Features + +- Change `MSSQL.port` default from `1433` to `None`, allowing use of `instanceName` to detect port number. ([#287](https://github.com/MobileTeleSystems/onetl/pull/287)) + +## Bug Fixes + +- Remove `fetchsize` from `JDBC.WriteOptions`. ([#288](https://github.com/MobileTeleSystems/onetl/pull/288)) diff --git a/mddocs/ru/changelog/0.11.2.md b/mddocs/ru/changelog/0.11.2.md new file mode 100644 index 000000000..9278d22f8 --- /dev/null +++ b/mddocs/ru/changelog/0.11.2.md @@ -0,0 +1,5 @@ +# 0.11.2 (2024-09-02) + +## Bug Fixes + +- Fix passing `Greenplum(extra={"options": ...})` during read/write operations. ([#308](https://github.com/MobileTeleSystems/onetl/pull/308)) diff --git a/mddocs/ru/changelog/0.12.0.md b/mddocs/ru/changelog/0.12.0.md new file mode 100644 index 000000000..aeaca1d23 --- /dev/null +++ b/mddocs/ru/changelog/0.12.0.md @@ -0,0 +1,54 @@ +# 0.12.0 (2024-09-03) + +## Breaking Changes + +- Change connection URL used for generating HWM names of S3 and Samba sources: + - `smb://host:port` -> `smb://host:port/share` + - `s3://host:port` -> `s3://host:port/bucket` ([#304](https://github.com/MobileTeleSystems/onetl/pull/304)) +- Update DB connectors/drivers to latest versions: + - Clickhouse `0.6.0-patch5` → `0.6.5` + - MongoDB `10.3.0` → `10.4.0` + - MSSQL `12.6.2` → `12.8.1` + - MySQL `8.4.0` → `9.0.0` + - Oracle `23.4.0.24.05` → `23.5.0.24.07` + - Postgres `42.7.3` → `42.7.4` +- Update `Excel` package from `0.20.3` to `0.20.4`, to include Spark 3.5.1 support. ([#306](https://github.com/MobileTeleSystems/onetl/pull/306)) + +## Features + +- Add support for specifying file formats (`ORC`, `Parquet`, `CSV`, etc.) in `HiveWriteOptions.format` ([#292](https://github.com/MobileTeleSystems/onetl/pull/292)): + + ```python + Hive.WriteOptions(format=ORC(compression="snappy")) + ``` + +- Collect Spark execution metrics in following methods, and log then in DEBUG mode: + - `DBWriter.run()` + - `FileDFWriter.run()` + - `Hive.sql()` + - `Hive.execute()` + + This is implemented using custom `SparkListener` which wraps the entire method call, and + then report collected metrics. But these metrics sometimes may be missing due to Spark architecture, + so they are not reliable source of information. That's why logs are printed only in DEBUG mode, and + are not returned as method call result. ([#303](https://github.com/MobileTeleSystems/onetl/pull/303)) + +- Generate default `jobDescription` based on currently executed method. Examples: + - `DBWriter.run(schema.table) -> Postgres[host:5432/database]` + - `MongoDB[localhost:27017/admin] -> DBReader.has_data(mycollection)` + - `Hive[cluster].execute()` + + If user already set custom `jobDescription`, it will left intact. ([#304](https://github.com/MobileTeleSystems/onetl/pull/304)) + +- Add log.info about JDBC dialect usage ([#305](https://github.com/MobileTeleSystems/onetl/pull/305)): + + ```text + |MySQL| Detected dialect: 'org.apache.spark.sql.jdbc.MySQLDialect' + ``` + +- Log estimated size of in-memory dataframe created by `JDBC.fetch` and `JDBC.execute` methods. ([#303](https://github.com/MobileTeleSystems/onetl/pull/303)) + +## Bug Fixes + +- Fix passing `Greenplum(extra={"options": ...})` during read/write operations. ([#308](https://github.com/MobileTeleSystems/onetl/pull/308)) +- Do not raise exception if yield-based hook whas something past (and only one) `yield`. diff --git a/mddocs/ru/changelog/0.12.1.md b/mddocs/ru/changelog/0.12.1.md new file mode 100644 index 000000000..d32401517 --- /dev/null +++ b/mddocs/ru/changelog/0.12.1.md @@ -0,0 +1,17 @@ +# 0.12.1 (2024-10-28) + +## Features + +- Log detected JDBC dialect while using `DBWriter`. + +## Bug Fixes + +- Fix `SparkMetricsRecorder` failing when receiving `SparkListenerTaskEnd` without `taskMetrics` (e.g. executor was killed by OOM). ([#313](https://github.com/MobileTeleSystems/onetl/pull/313)) +- Call `kinit` before checking for HDFS active namenode. +- Wrap `kinit` with `threading.Lock` to avoid multithreading issues. +- Immediately show `kinit` errors to user, instead of hiding them. +- Use `AttributeError` instead of `ImportError` in module's `__getattr__` method, to make code compliant with Python spec. + +## Doc only Changes + +- Add note about [spark-dialect-extension](https://github.com/MobileTeleSystems/spark-dialect-extension) package to Clickhouse connector documentation. ([#310](https://github.com/MobileTeleSystems/onetl/pull/310)) diff --git a/mddocs/ru/changelog/0.12.2.md b/mddocs/ru/changelog/0.12.2.md new file mode 100644 index 000000000..23a8d383d --- /dev/null +++ b/mddocs/ru/changelog/0.12.2.md @@ -0,0 +1,18 @@ +# 0.12.2 (2024-11-12) + +## Improvements + +- Change Spark `jobDescription` for DBReader & FileDFReader from `DBReader.run() -> Connection` to `Connection -> DBReader.run()`. + +## Bug Fixes + +- Fix `log_hwm` result for `KeyValueIntHWM` (used by Kafka). ([#316](https://github.com/MobileTeleSystems/onetl/pull/316)) +- Fix `log_collection` hiding values of `Kafka.addresses` in logs with `INFO` level. ([#316](https://github.com/MobileTeleSystems/onetl/pull/316)) + +## Dependencies + +- Allow using [etl-entities==2.4.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.4.0). + +## Doc only Changes + +- Fix links to MSSQL date & time type documentation. diff --git a/mddocs/ru/changelog/0.12.3.md b/mddocs/ru/changelog/0.12.3.md new file mode 100644 index 000000000..741c74e1f --- /dev/null +++ b/mddocs/ru/changelog/0.12.3.md @@ -0,0 +1,5 @@ +# 0.12.3 (2024-11-22) + +## Bug Fixes + +- Allow passing table names in format `schema."table.with.dots"` to `DBReader(source=...)` and `DBWriter(target=...)`. diff --git a/mddocs/ru/changelog/0.12.4.md b/mddocs/ru/changelog/0.12.4.md new file mode 100644 index 000000000..3ebc57a87 --- /dev/null +++ b/mddocs/ru/changelog/0.12.4.md @@ -0,0 +1,5 @@ +# 0.12.4 (2024-11-27) + +## Bug Fixes + +- Fix `DBReader(conn=oracle, options={"partitioning_mode": "hash"})` lead to data skew in last partition due to wrong `ora_hash` usage. ([#319](https://github.com/MobileTeleSystems/onetl/pull/319)) diff --git a/mddocs/ru/changelog/0.12.5.md b/mddocs/ru/changelog/0.12.5.md new file mode 100644 index 000000000..c542a50fd --- /dev/null +++ b/mddocs/ru/changelog/0.12.5.md @@ -0,0 +1,13 @@ +# 0.12.5 (2024-12-03) + +## Improvements + +- Use `sipHash64` instead of `md5` in Clickhouse for reading data with `{"partitioning_mode": "hash"}`, as it is 5 times faster. +- Use `hashtext` instead of `md5` in Postgres for reading data with `{"partitioning_mode": "hash"}`, as it is 3-5 times faster. +- Use `BINARY_CHECKSUM` instead of `HASHBYTES` in MSSQL for reading data with `{"partitioning_mode": "hash"}`, as it is 5 times faster. + +## Big fixes + +- In JDBC sources wrap `MOD(partitionColumn, numPartitions)` with `ABS(...)` to make al returned values positive. This prevents data skew. +- Fix reading table data from MSSQL using `{"partitioning_mode": "hash"}` with `partitionColumn` of integer type. +- Fix reading table data from Postgres using `{"partitioning_mode": "hash"}` lead to data skew (all the data was read into one Spark partition). diff --git a/mddocs/ru/changelog/0.13.0.md b/mddocs/ru/changelog/0.13.0.md new file mode 100644 index 000000000..37ae8368a --- /dev/null +++ b/mddocs/ru/changelog/0.13.0.md @@ -0,0 +1,222 @@ +# 0.13.0 (2025-02-24) + +🎉 3 years since first release 0.1.0 🎉 + +## Breaking Changes + +- Add Python 3.13. support. ([#298](https://github.com/MobileTeleSystems/onetl/pull/298)) + +- Change the logic of `FileConnection.walk` and `FileConnection.list_dir`. ([#327](https://github.com/MobileTeleSystems/onetl/pull/327)) + + Previously `limits.stops_at(path) == True` considered as "return current file and stop", and could lead to exceeding some limit. + Not it means "stop immediately". + +- Change default value for `FileDFWriter.Options(if_exists=...)` from `error` to `append`, + to make it consistent with other `.Options()` classes within onETL. ([#343](https://github.com/MobileTeleSystems/onetl/pull/343)) + +## Features + +- Add support for `FileModifiedTimeHWM` HWM class (see [etl-entities 2.5.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.5.0)): + + ```python + from etl_entitites.hwm import FileModifiedTimeHWM + from onetl.file import FileDownloader + from onetl.strategy import IncrementalStrategy + + downloader = FileDownloader( + ..., + hwm=FileModifiedTimeHWM(name="somename"), + ) + + with IncrementalStrategy(): + downloader.run() + ``` + +- Introduce `FileSizeRange(min=..., max=...)` filter class. ([#325](https://github.com/MobileTeleSystems/onetl/pull/325)) + + Now users can set `FileDownloader` / `FileMover` to download/move only files with specific file size range: + + ```python + from onetl.file import FileDownloader + from onetl.file.filter import FileSizeRange + + downloader = FileDownloader( + ..., + filters=[FileSizeRange(min="10KiB", max="1GiB")], + ) + ``` + +- Introduce `TotalFilesSize(...)` limit class. ([#326](https://github.com/MobileTeleSystems/onetl/pull/326)) + + Now users can set `FileDownloader` / `FileMover` to stop downloading/moving files after reaching a certain amount of data: + + ```python + from datetime import datetime, timedelta + from onetl.file import FileDownloader + from onetl.file.limit import TotalFilesSize + + downloader = FileDownloader( + ..., + limits=[TotalFilesSize("1GiB")], + ) + ``` + +- Implement `FileModifiedTime(since=..., until=...)` file filter. ([#330](https://github.com/MobileTeleSystems/onetl/pull/330)) + + Now users can set `FileDownloader` / `FileMover` to download/move only files with specific file modification time: + + ```python + from datetime import datetime, timedelta + from onetl.file import FileDownloader + from onetl.file.filter import FileModifiedTime + + downloader = FileDownloader( + ..., + filters=[FileModifiedTime(before=datetime.now() - timedelta(hours=1))], + ) + ``` + +- Add `SparkS3.get_exclude_packages()` and `Kafka.get_exclude_packages()` methods. ([#341](https://github.com/MobileTeleSystems/onetl/pull/341)) + + Using them allows to skip downloading dependencies not required by this specific connector, or which are already a part of Spark/PySpark: + + ```python + from onetl.connection import SparkS3, Kafka + + maven_packages = [ + *SparkS3.get_packages(spark_version="3.5.4"), + *Kafka.get_packages(spark_version="3.5.4"), + ] + exclude_packages = SparkS3.get_exclude_packages() + Kafka.get_exclude_packages() + spark = ( + SparkSession.builder.appName("spark_app_onetl_demo") + .config("spark.jars.packages", ",".join(maven_packages)) + .config("spark.jars.excludes", ",".join(exclude_packages)) + .getOrCreate() + ) + ``` + +## Improvements + +- All DB connections opened by `JDBC.fetch(...)`, `JDBC.execute(...)` or `JDBC.check()` + are immediately closed after the statements is executed. ([#334](https://github.com/MobileTeleSystems/onetl/pull/334)) + + Previously Spark session with `master=local[3]` actually opened up to 5 connections to target DB - one for `JDBC.check()`, + another for Spark driver interaction with DB to create tables, and one for each Spark executor. Now only max 4 connections are opened, + as `JDBC.check()` does not hold opened connection. + + This is important for RDBMS like Postgres or Greenplum where number of connections is strictly limited and limit is usually quite low. + +- Set up `ApplicationName` (client info) for Clickhouse, MongoDB, MSSQL, MySQL and Oracle. ([#339](https://github.com/MobileTeleSystems/onetl/pull/339), [#248](https://github.com/MobileTeleSystems/onetl/pull/248)) + + Also update `ApplicationName` format for Greenplum, Postgres, Kafka and SparkS3. + Now all connectors have the same `ApplicationName` format: `${spark.applicationId} ${spark.appName} onETL/${onetl.version} Spark/${spark.version}` + + The only connections not sending `ApplicationName` are Teradata and FileConnection implementations. + +- Now `DB.check()` will test connection availability not only on Spark driver, but also from some Spark executor. ([#346](https://github.com/MobileTeleSystems/onetl/pull/346)) + + This allows to fail immediately if Spark driver host has network access to target DB, but Spark executors have not. + +!!! note + + Now ``Greenplum.check()`` requires the same user grants as ``DBReader(connection=greenplum)``: + + ```sql + -- yes, "writable" for reading data from GP, it's not a mistake + ALTER USER username CREATEEXTTABLE(type = 'writable', protocol = 'gpfdist'); + + -- for both reading and writing to GP + -- ALTER USER username CREATEEXTTABLE(type = 'readable', protocol = 'gpfdist') CREATEEXTTABLE(type = 'writable', protocol = 'gpfdist'); + ``` + + Please ask your Greenplum administrators to provide these grants. + + +## Bug Fixes + +- Avoid suppressing Hive Metastore errors while using `DBWriter`. ([#329](https://github.com/MobileTeleSystems/onetl/pull/329)) + + Previously this was implemented as: + + ```python + try: + spark.sql(f"SELECT * FROM {table}") + table_exists = True + except Exception: + table_exists = False + ``` + + If Hive Metastore was overloaded and responded with an exception, it was considered as non-existing table, resulting + to full table override instead of append or override only partitions subset. + +- Fix using onETL to write data to PostgreSQL or Greenplum instances behind *pgbouncer* with `pool_mode=transaction`. ([#336](https://github.com/MobileTeleSystems/onetl/pull/336)) + + Previously `Postgres.check()` opened a read-only transaction, pgbouncer changed the entire connection type from read-write to read-only, + and when `DBWriter.run(df)` executed in read-only connection, producing errors like: + + ``` + org.postgresql.util.PSQLException: ERROR: cannot execute INSERT in a read-only transaction + org.postgresql.util.PSQLException: ERROR: cannot execute TRUNCATE TABLE in a read-only transaction + ``` + + Added a workaround by passing `readOnly=True` to JDBC params for read-only connections, so pgbouncer may differ read-only and read-write connections properly. + + After upgrading onETL 0.13.x or higher the same error still may appear of pgbouncer still holds read-only connections and returns them for DBWriter. + To this this, user can manually convert read-only connection to read-write: + + ```python + postgres.execute("BEGIN READ WRITE;") # <-- add this line + DBWriter(...).run() + ``` + + After all connections in pgbouncer pool were converted from read-only to read-write, and error fixed, this additional line could be removed. + + See [Postgres JDBC driver documentation](https://jdbc.postgresql.org/documentation/use/). + +- Fix `MSSQL.fetch(...)` and `MySQL.fetch(...)` opened a read-write connection instead of read-only. ([#337](https://github.com/MobileTeleSystems/onetl/pull/337)) + + Now this is fixed: + : - `MSSQL.fetch(...)` establishes connection with `ApplicationIntent=ReadOnly`. + - `MySQL.fetch(...)` calls `SET SESSION TRANSACTION READ ONLY` statement. + +- Fixed passing multiple filters to `FileDownloader` and `FileMover`. ([#338](https://github.com/MobileTeleSystems/onetl/pull/338)) + If was caused by sorting filters list in internal logging method, but `FileFilter` subclasses are not sortable. + +- Fix a false warning about a lof of parallel connections to Grenplum. ([#342](https://github.com/MobileTeleSystems/onetl/pull/342)) + + Creating Spark session with `.master("local[5]")` may open up to 6 connections to Greenplum (=number of Spark executors + 1 for driver), + but onETL instead used number of *CPU cores* on the host as a number of parallel connections. + + This lead to showing a false warning that number of Greenplum connections is too high, + which actually should be the case only if number of executors is higher than 30. + +- Fix MongoDB trying to use current database name as `authSource`. ([#347](https://github.com/MobileTeleSystems/onetl/pull/347)) + + Use default connector value which is `admin` database. Previous onETL versions could be fixed by: + + ```python + from onetl.connection import MongoDB + + mongodb = MongoDB( + ..., + database="mydb", + extra={ + "authSource": "admin", + }, + ) + ``` + +## Dependencies + +- Minimal `etl-entities` version is now [2.5.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.5.0). ([#331](https://github.com/MobileTeleSystems/onetl/pull/331)) +- Update DB connectors/drivers to latest versions: ([#345](https://github.com/MobileTeleSystems/onetl/pull/345)) + : - Clickhouse `0.6.5` → `0.7.2` + - MongoDB `10.4.0` → `10.4.1` + - MySQL `9.0.0` → `9.2.0` + - Oracle `23.5.0.24.07` → `23.7.0.25.01` + - Postgres `42.7.4` → `42.7.5` + +## Doc only Changes + +- Split large code examples to tabs. ([#344](https://github.com/MobileTeleSystems/onetl/pull/344)) diff --git a/mddocs/ru/changelog/0.13.1.md b/mddocs/ru/changelog/0.13.1.md new file mode 100644 index 000000000..4045c7cbf --- /dev/null +++ b/mddocs/ru/changelog/0.13.1.md @@ -0,0 +1,9 @@ +# 0.13.1 (2025-03-06) + +## Bug Fixes + +In 0.13.0, using `DBWriter(connection=hive, target="SOMEDB.SOMETABLE")` lead to executing `df.write.saveAsTable()` +instead of `df.write.insertInto()` if target table `somedb.sometable` already exist. + +This is caused by table name normalization (Hive uses lower-case names), which wasn't properly handled by method used for checking table existence. +([#350](https://github.com/MobileTeleSystems/onetl/pull/350)) diff --git a/mddocs/ru/changelog/0.13.3.md b/mddocs/ru/changelog/0.13.3.md new file mode 100644 index 000000000..1aa289b49 --- /dev/null +++ b/mddocs/ru/changelog/0.13.3.md @@ -0,0 +1,5 @@ +# 0.13.3 (2025-03-11) + +## Dependencies + +Allow using [etl-entities 2.6.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.6.0). diff --git a/mddocs/ru/changelog/0.13.4.md b/mddocs/ru/changelog/0.13.4.md new file mode 100644 index 000000000..10f695e0c --- /dev/null +++ b/mddocs/ru/changelog/0.13.4.md @@ -0,0 +1,10 @@ +# 0.13.4 (2025-03-20) + +## Doc only Changes + +- Prefer `ReadOptions(partitionColumn=..., numPartitions=..., queryTimeout=...)` + instead of `ReadOptions(partition_column=..., num_partitions=..., query_timeout=...)`, + to match Spark documentation. ([#352](https://github.com/MobileTeleSystems/onetl/pull/352)) +- Prefer `WriteOptions(if_exists=...)` instead of `WriteOptions(mode=...)` for IDE suggestions. ([#354](https://github.com/MobileTeleSystems/onetl/pull/354)) +- Document all options of supported file formats. + ([#355](https://github.com/MobileTeleSystems/onetl/pull/355), [#356](https://github.com/MobileTeleSystems/onetl/pull/356), [#357](https://github.com/MobileTeleSystems/onetl/pull/357), [#358](https://github.com/MobileTeleSystems/onetl/pull/358), [#359](https://github.com/MobileTeleSystems/onetl/pull/359), [#360](https://github.com/MobileTeleSystems/onetl/pull/360), [#361](https://github.com/MobileTeleSystems/onetl/pull/361), [#362](https://github.com/MobileTeleSystems/onetl/pull/362)) diff --git a/mddocs/ru/changelog/0.7.0.md b/mddocs/ru/changelog/0.7.0.md new file mode 100644 index 000000000..e1b285d79 --- /dev/null +++ b/mddocs/ru/changelog/0.7.0.md @@ -0,0 +1,237 @@ +# 0.7.0 (2023-05-15) + +## 🎉 onETL теперь с открытым исходным кодом 🎉 + +Это была долгая дорога, но мы наконец-то преодолели её! + +## Критические изменения + +- Изменен способ установки. + + **TL;DR Что нужно изменить, чтобы восстановить предыдущее поведение** + + Простой способ: + + | onETL < 0.7.0 | onETL >= 0.7.0 | + | ----------------- | --------------------------------- | + | pip install onetl | pip install onetl[files,kerberos] | + + Правильный способ - перечислить коннекторы, которые должны быть установлены: + + ```bash + pip install onetl[hdfs,ftp,kerberos] # кроме DB соединений + ``` + + **Подробности** + + В onetl<0.7 установка пакета выглядит так: + + ```bash title="before" + + pip install onetl + ``` + + Но это включает в себя все зависимости для всех коннекторов, даже если пользователь их не использует. + Это вызывало некоторые проблемы, например, пользователю приходилось устанавливать библиотеки Kerberos, чтобы иметь возможность установить onETL, даже если он использует только S3 (без поддержки Kerberos). + + Начиная с версии 0.7.0 процесс установки был изменен: + + ``` bash title="after" + + pip install onetl # минимальная установка, только ядро onETL + # нет extras для подключений к БД, потому что они используют Java пакеты, которые устанавливаются во время выполнения + + pip install onetl[ftp,ftps,hdfs,sftp,s3,webdav] # установить зависимости для указанных файловых подключений + pip install onetl[files] # установить зависимости для всех файловых подключений + + pip install onetl[kerberos] # Поддержка аутентификации Kerberos + pip install onetl[spark] # установить PySpark для использования подключений к БД + + pip install onetl[spark,kerberos,files] # все файловые подключения + Kerberos + PySpark + pip install onetl[all] # алиас для предыдущего случая + ``` + + Для каждого extras есть соответствующие элементы документации. + + Кроме того, onETL проверяет, что некоторые требования отсутствуют, и выдает исключение с рекомендацией, как их установить: + + ``` text title="exception while import Clickhouse connection" + + Cannot import module "pyspark". + + Since onETL v0.7.0 you should install package as follows: + pip install onetl[spark] + + or inject PySpark to sys.path in some other way BEFORE creating MongoDB instance. + ``` + + ``` text title="exception while import FTP connection" + + Cannot import module "ftputil". + + Since onETL v0.7.0 you should install package as follows: + pip install onetl[ftp] + + or + pip install onetl[files] + ``` + +- Добавлен новый аргумент `cluster` для подключений `Hive` и `HDFS`. + + Полное имя `Hive` (используемое в HWM) содержит имя кластера. Но в onETL<0.7.0 имя кластера имело не изменяемое значение `rnd-dwh`, что не подходило некоторым пользователям. + + Полное имя соединения `HDFS` содержит имя хоста (активной namenode кластера Hadoop), но его значение может меняться со временем, что приводит к созданию нового HWM. + + Начиная с onETL 0.7.0, оба подключения `Hive` и `HDFS` имеют атрибут `cluster`, в котором можно установить определенное имя кластера. + Для `Hive` это обязательно, для `HDFS` это можно опустить (используя хост в качестве запасного варианта). + + Но передача имени кластера каждый раз может привести к ошибкам. + + Теперь `Hive` и `HDFS` имеют вложенный класс с именем `slots` с методами: + + - `normalize_cluster_name` + - `get_known_clusters` + - `get_current_cluster` + - `normalize_namenode_host` (только `HDFS`) + - `get_cluster_namenodes` (только `HDFS`) + - `get_webhdfs_port` (только `HDFS`) + - `is_namenode_active` (только `HDFS`) + + И новый метод `HDFS.get_current` / `Hive.get_current`. + + Разработчики могут реализовать хуки, проверяющие ввод пользователя или подставляющие значения для автоматического определения кластера. + На наш взгляд, описанная функциональность улучшит пользовательский опыт при использовании этих коннекторов. + + См. документацию по slots. + +- Обновление драйверов JDBC соединений. + + - Greenplum `2.1.3` → `2.1.4`. + - MSSQL `10.2.1.jre8` → `12.2.0.jre8`. Минимальная поддерживаемая версия MSSQL теперь 2014 вместо 2021. + - MySQL `8.0.30` → `8.0.33`: + - Переименован пакет `mysql:mysql-connector-java` → `com.mysql:mysql-connector-j`. + - Переименован класс драйвера `com.mysql.jdbc.Driver` → `com.mysql.cj.jdbc.Driver`. + - Oracle `21.6.0.0.1` → `23.2.0.0`. + - Postgres `42.4.0` → `42.6.0`. + - Teradata `17.20.00.08` → `17.20.00.15`: + - Переименован пакет `com.teradata.jdbc:terajdbc4` → `com.teradata.jdbc:terajdbc`. + - Драйвер Teradata теперь опубликован в Maven. + + См. [#31](https://github.com/MobileTeleSystems/onetl/pull/31). + +## Функциональность + +- Добавлено подключение MongoDB. + + Используется официальный [MongoDB connector for Spark v10](https://www.mongodb.com/docs/spark-connector/current/). Поддерживается только Spark 3.2+. + + Есть некоторые различия между MongoDB и другими БД-источниками: + + - Метод `mongodb.pipeline` применяется вместо `mongodb.sql`. + - Нет методов `mongodb.fetch` и `mongodb.execute`. + - Типы `DBReader.hint` и `DBReader.where` отличаются от типов для БД, поддерживающих SQL: + + ```python + where = { + "col1": { + "$eq": 10, + }, + } + + hint = { + "col1": 1, + } + ``` + + - Поскольку MongoDB не имеет схем для коллекций и Spark не может создавать dataframe с динамической схемой, была введена новая опция `DBReader.df_schema`. + Она обязательна для MongoDB, но необязательна для других источников. + - В настоящее время DBReader нельзя использовать с MongoDB и hwm выражения, например, `hwm_column=("mycolumn", {"$cast": {"col1": "date"}})` + + Поскольку в MongoDB нет таблиц, в основных классах были переименованы некоторые опции: + + - `DBReader(table=...)` → `DBReader(source=...)` + - `DBWriter(table=...)` → `DBWriter(target=...)` + + Старые имена также можно использовать, они не устарели ([#30](https://github.com/MobileTeleSystems/onetl/pull/30)). + +- Добавлена опция для отключения некоторых плагинов во время импорта. + + Ранее, если какой-либо плагин не работал во время импорта, единственным способом импортировать onETL было отключить все плагины + с помощью переменной окружения. + + Теперь есть несколько переменных с разным поведением: + + - `ONETL_PLUGINS_ENABLED=false` - отключить автоматический импорт всех плагинов. Ранее она называлась `ONETL_ENABLE_PLUGINS`. + - `ONETL_PLUGINS_BLACKLIST=plugin-name,another-plugin` - установить список плагинов, которые НЕ должны импортироваться автоматически. + - `ONETL_PLUGINS_WHITELIST=plugin-name,another-plugin` - установить список плагинов, которые должны импортироваться ТОЛЬКО автоматически. + + Ещё мы улучшили сообщение об исключении с рекомендацией, как отключить неработающий плагин: + + ``` text title="exception message example" + + Error while importing plugin 'mtspark' from package 'mtspark' v4.0.0. + + Statement: + import mtspark.onetl + + Check if plugin is compatible with current onETL version 0.7.0. + + You can disable loading this plugin by setting environment variable: + ONETL_PLUGINS_BLACKLIST='mtspark,failing-plugin' + + You can also define a whitelist of packages which can be loaded by onETL: + ONETL_PLUGINS_WHITELIST='not-failing-plugin1,not-failing-plugin2' + + Please take into account that plugin name may differ from package or module name. + See package metadata for more details + ``` + +## Улучшения + +- Добавлена совместимость с Python 3.11 и PySpark 3.4.0. + + Файловые подключения работали нормально, но `jdbc.fetch` и `jdbc.execute` не работали. Исправлено в [#28](https://github.com/MobileTeleSystems/onetl/pull/28). + +- Добавлена проверка отсутствующих пакетов Java. + + Ранее, если подключение к БД пыталось использовать какой-либо Java-класс, который не был загружен в версию Spark, возникало исключение + с длинным stacktrace. Большинство пользователей не могли интерпретировать эту информацию. + + Теперь onETL показывает следующее сообщение об ошибке: + + ``` text title="exception message example" + + |Spark| Cannot import Java class 'com.mongodb.spark.sql.connector.MongoTableProvider'. + + It looks like you've created Spark session without this option: + SparkSession.builder.config("spark.jars.packages", MongoDB.package_spark_3_2) + + Please call `spark.stop()`, restart the interpreter, + and then create new SparkSession with proper options. + ``` + +- Улучшения документации. + + - Изменена тема сайта документации - используется [furo](https://github.com/pradyunsg/furo) + вместо темы по умолчанию [ReadTheDocs](https://github.com/readthedocs/sphinx_rtd_theme). + + Новая тема поддерживает широкие экраны и темный режим. + См. [#10](https://github.com/MobileTeleSystems/onetl/pull/10). + + - Теперь каждый класс подключения имеет таблицу совместимости для Spark + Java + Python. + + - Добавлена глобальная таблица совместимости для Spark + Java + Python + Scala. + +## Исправление ошибок + +- Исправлено несколько проблем с SFTP. + + - Если файл конфигурации SSH `~/.ssh/config` содержит некоторые параметры, не распознаваемые Paramiko (неизвестный синтаксис, неизвестное имя параметра), предыдущие версии будут выбрасывать исключение до исправления или удаления этого файла. Начиная с версии 0.7.0 исключение заменено предупреждением. + + - Если пользователь передал `host_key_check=False`, но сервер изменил SSH ключи, предыдущие версии выдавали исключение до принятия нового люча. Начиная с версии 0.7.0 исключение заменено предупреждением, если значение параметра равно `False`. + + Исправлено в [#19](https://github.com/MobileTeleSystems/onetl/pull/19). + +- Исправлено несколько проблем с S3. + + Была ошибка в подключении к S3, которая не позволяла обрабатывать файлы в корне бакета - они были невидимы для коннектора. Исправлено в [#29](https://github.com/MobileTeleSystems/onetl/pull/29). diff --git a/mddocs/ru/changelog/0.7.1.md b/mddocs/ru/changelog/0.7.1.md new file mode 100644 index 000000000..7e0fdad57 --- /dev/null +++ b/mddocs/ru/changelog/0.7.1.md @@ -0,0 +1,40 @@ +# 0.7.1 (2023-05-23) + +## Bug Fixes + +- Исправлена функция `setup_logging`. + + В onETL==0.7.0 вызов `onetl.log.setup_logging()` приводил к ошибке логирования: + + ``` text title="exception message" + + Traceback (most recent call last): + File "/opt/anaconda/envs/py39/lib/python3.9/logging/__init__.py", line 434, in format + return self._format(record) + File "/opt/anaconda/envs/py39/lib/python3.9/logging/__init__.py", line 430, in _format + return self._fmt % record.dict + KeyError: 'levelname:8s' + ``` + +- Исправлены примеры установки. + + В onETL==0.7.0 приводились примеры установки с применением extras: + + ``` bash title="before" + + pip install onetl[files, kerberos, spark] + ``` + + Однако, это приводило к ошибке pip: + + ``` text title="exception" + + ERROR: Invalid requirement: 'onet[files,' + ``` + + Это происходило из-за наличия пробелов в extras. Исправлено: + + ``` bash title="after" + + pip install onetl[files,kerberos,spark] + ``` diff --git a/mddocs/ru/changelog/0.7.2.md b/mddocs/ru/changelog/0.7.2.md new file mode 100644 index 000000000..f92fd9944 --- /dev/null +++ b/mddocs/ru/changelog/0.7.2.md @@ -0,0 +1,37 @@ +# 0.7.2 (2023-05-24) + +## Dependencies + +- Ограничена версия `typing-extensions`. + + Релиз `typing-extensions==4.6.0` содержит критические изменения вызывающие ошибки вида: + + ``` text title="typing-extensions 4.6.0" + + Traceback (most recent call last): + File "/Users/project/lib/python3.9/typing.py", line 852, in __subclasscheck__ + return issubclass(cls, self.__origin__) + TypeError: issubclass() arg 1 must be a class + ``` + + Релиз `typing-extensions==4.6.1` вызывает другую ошибку: + + ``` text title="typing-extensions 4.6.1" + + Traceback (most recent call last): + File "/home/maxim/Repo/typing_extensions/1.py", line 33, in + isinstance(file, ContainsException) + File "/home/maxim/Repo/typing_extensions/src/typing_extensions.py", line 599, in __instancecheck__ + if super().__instancecheck__(instance): + File "/home/maxim/.pyenv/versions/3.7.8/lib/python3.7/abc.py", line 139, in __instancecheck__ + return _abc_instancecheck(cls, instance) + File "/home/maxim/Repo/typing_extensions/src/typing_extensions.py", line 583, in __subclasscheck__ + return super().__subclasscheck__(other) + File "/home/maxim/.pyenv/versions/3.7.8/lib/python3.7/abc.py", line 143, in __subclasscheck__ + return _abc_subclasscheck(cls, subclass) + File "/home/maxim/Repo/typing_extensions/src/typing_extensions.py", line 661, in _proto_hook + and other._is_protocol + AttributeError: type object 'PathWithFailure' has no attribute '_is_protocol' + ``` + + Мы указали в requirements.txt ограничение `typing-extensions<4.6` и будем его придерживаться пока причины ошибок не будут устранены. diff --git a/mddocs/ru/changelog/0.8.0.md b/mddocs/ru/changelog/0.8.0.md new file mode 100644 index 000000000..39501dedf --- /dev/null +++ b/mddocs/ru/changelog/0.8.0.md @@ -0,0 +1,162 @@ +# 0.8.0 (2023-05-31) + +## Критические изменения + +- Переименованы методы классов `FileConnection`: + + - `get_directory` → `resolve_dir` + - `get_file` → `resolve_file` + - `listdir` → `list_dir` + - `mkdir` → `create_dir` + - `rmdir` → `remove_dir` + + Мы считаем, что новое именование будет более последовательным. + + Эти методы были не задокументированы в предыдущих версиях, но кто-то мог использовать их, поэтому это изменение не является обратно совместимым. ([#36](https://github.com/MobileTeleSystems/onetl/pull/36)) + +- Класс `onetl.core.FileFilter` устарел, его заменяют новые классы: + + - `onetl.file.filter.Glob` + - `onetl.file.filter.Regexp` + - `onetl.file.filter.ExcludeDir` + + Старый класс будет удален в v1.0.0. ([#43](https://github.com/MobileTeleSystems/onetl/pull/43)) + +- Класс `onetl.core.FileLimit` также устарел, его заменяет `onetl.file.limit.MaxFilesCount`. + + Старый класс будет удален в v1.0.0. ([#44](https://github.com/MobileTeleSystems/onetl/pull/44)) + +- Изменено поведение метода `BaseFileLimit.reset`. + + Этот метод теперь возвращает `self` вместо `None`. + Возвращаемое значение может быть тем же объектом или его копией, это зависит от конкретной реализации. ([#44](https://github.com/MobileTeleSystems/onetl/pull/44)) + +- `FileDownloader.filter` и `.limit` заменены новыми опциями `.filters` и `.limits`: + + ``` python title="onETL < 0.8.0" + + FileDownloader( + ..., + filter=FileFilter(glob="*.txt", exclude_dir="/path"), + limit=FileLimit(count_limit=10), + ) + ``` + + ``` python title="onETL >= 0.8.0" + + FileDownloader( + ..., + filters=[Glob("*.txt"), ExcludeDir("/path")], + limits=[MaxFilesCount(10)], + ) + ``` + + Это позволяет разработчикам реализовывать свои собственные классы фильтров и лимитов, а также комбинировать их с существующими. + + Старое поведение все еще поддерживается, но будет удалено в v1.0.0. ([#45](https://github.com/MobileTeleSystems/onetl/pull/45)) + +- Удалено значение по умолчанию для `FileDownloader.limits`, теперь пользователь должен явно передавать список лимитов. ([#45](https://github.com/MobileTeleSystems/onetl/pull/45)) + +- Классы из модуля `onetl.core`: + + ``` python title="before" + + from onetl.core import DBReader + from onetl.core import DBWriter + from onetl.core import FileDownloader + from onetl.core import FileUploader + ``` + + перемещены в новые модули `onetl.db` и `onetl.file`: + + ``` python title="after" + + from onetl.db import DBReader + from onetl.db import DBWriter + + from onetl.file import FileDownloader + from onetl.file import FileUploader + ``` + + Импорты из старого модуля `onetl.core` все еще можно использовать, но они помечены как устаревшие. Модуль будет удален в v1.0.0. ([#46](https://github.com/MobileTeleSystems/onetl/pull/46)) + +## Features + +- Добавлен метод `rename_dir`. + + Метод был добавлен к следующим подключениям: + + - `FTP` + - `FTPS` + - `HDFS` + - `SFTP` + - `WebDAV` + + Он позволяет переименовывать/перемещать каталог со всем его содержимым в новое расположение / новый путь. + + `S3` не имеет каталогов, поэтому для этого подключения нет такого метода. ([#40](https://github.com/MobileTeleSystems/onetl/pull/40)) + +- Добавлен класс `onetl.file.FileMover`. + + Он позволяет перемещать файлы между каталогами удаленной файловой системы. + Сигнатура почти такая же, как и в `FileDownloader`, но без поддержки HWM. ([#42](https://github.com/MobileTeleSystems/onetl/pull/42)) + +## Improvements + +- Задокументированы все публичные методы в классах `FileConnection`: + + - `download_file` + - `resolve_dir` + - `resolve_file` + - `get_stat` + - `is_dir` + - `is_file` + - `list_dir` + - `create_dir` + - `path_exists` + - `remove_file` + - `rename_file` + - `remove_dir` + - `upload_file` + - `walk` ([#39](https://github.com/MobileTeleSystems/onetl/pull/39)) + +- Обновлена документация метода `check` для всех подключений - добавлен пример использования и задокументирован тип результата. ([#39](https://github.com/MobileTeleSystems/onetl/pull/39)) + +- Добавлен новый тип исключения `FileSizeMismatchError`. + + Если целевой файл после загрузки/выгрузки имеет размер, отличный от исходного методы `connection.download_file` и `connection.upload_file` теперь вызывают новый тип исключения вместо `RuntimeError`. ([#39](https://github.com/MobileTeleSystems/onetl/pull/39)) + +- Добавлен новый тип исключения `DirectoryExistsError` - он возникает, если целевой каталог уже существует. ([#40](https://github.com/MobileTeleSystems/onetl/pull/40)) + +- Улучшено логирование исключений `FileDownloader` / `FileUploader`. + + Если включено логирование `DEBUG`, то вместо вывода только сообщения об исключении выводится исключение со стектрейсом . ([#42](https://github.com/MobileTeleSystems/onetl/pull/42)) + +- Обновлена документация `FileUploader`. + + - В документацию добавлено примечание, что класс не поддерживает стратегии чтения. + - Добавлены примеры использования метода `run` с явной передачей списка файлов, посредством как абсолютных, так и относительных путей. + - Исправлены устаревшие импорты и имена классов в примерах. ([#42](https://github.com/MobileTeleSystems/onetl/pull/42)) + +- Обновлена документация класса `DownloadResult` - исправлены устаревшие импорты и имена классов. ([#42](https://github.com/MobileTeleSystems/onetl/pull/42)) + +- Улучшен раздел документации по фильтрам файлов. + + Задокументирован интерфейсный класс `onetl.base.BaseFileFilter` и функция `match_all_filters`. ([#43](https://github.com/MobileTeleSystems/onetl/pull/43)) + +- Улучшен раздел документации по лимитам файлов. + + Задокументирован интерфейсный класс `onetl.base.BaseFileLimit` и функции `limits_stop_at` / `limits_reached` / `reset_limits`. ([#44](https://github.com/MobileTeleSystems/onetl/pull/44)) + +- Добавлен changelog. + + Changelog генерируется из отдельных файлов новостей с использованием [towncrier](https://pypi.org/project/towncrier/). ([#47](https://github.com/MobileTeleSystems/onetl/pull/47)) + +## Misc + +- Улучшен CI для тестов. + + - Если разработчик не изменил исходный код конкретного коннектора или его зависимости, запускать тесты только для максимально поддерживаемых версий Spark, Python, Java и поключения к БД или файловому хранилищу. + - Если разработчик внес какие-либо изменения в конкретный коннектор, в основные классы или в зависимости, запускать тесты для минимальной и максимальной версий. + - Раз в неделю запускать все тесты для минимальной и последней версий, чтобы выявить критические изменения в зависимостях. + - Минимальная протестированная версия Spark - 2.3.1 вместо 2.4.8. ([#32](https://github.com/MobileTeleSystems/onetl/pull/32)) diff --git a/mddocs/ru/changelog/0.8.1.md b/mddocs/ru/changelog/0.8.1.md new file mode 100644 index 000000000..db1f17ca6 --- /dev/null +++ b/mddocs/ru/changelog/0.8.1.md @@ -0,0 +1,42 @@ +# 0.8.1 (2023-07-10) + +## Новые возможности + +- Добавлен декоратор `@slot` к публичным методам: + + - `DBConnection` + - `FileConnection` + - `DBReader` + - `DBWriter` + - `FileDownloader` + - `FileUploader` + - `FileMover` ([#49](https://github.com/MobileTeleSystems/onetl/pull/49)) + +- Добавлено поле `workers` в классы `FileDownloader` / `FileUploader` / `FileMover` / `Options`. + + Это позволяет ускорить все файловые операции, используя параллельные потоки. ([#57](https://github.com/MobileTeleSystems/onetl/pull/57)) + +## Улучшения + +- Добавлена документация для методов `.get` и `.save` хранилища HWM. ([#49](https://github.com/MobileTeleSystems/onetl/pull/49)) + +- Улучшен Readme: + + - Раздел `Quick start` вынесен из документации + - Добавлен раздел `Non-goals` + - Исправлен отступ блоков кода ([#50](https://github.com/MobileTeleSystems/onetl/pull/50)) + +- Улучшено руководство по участию в разработке (Contributing guide): + + - Перемещен раздел `Develop` из Readme + - Перемещено содержимое `docs/changelog/README.rst` + - Добавлен раздел `Limitations` + - Добавлена инструкция по созданию форка и сборке документации ([#50](https://github.com/MobileTeleSystems/onetl/pull/50)) + +- Удалены дублирующиеся проверки существования исходного файла в `FileDownloader` / `FileMover`. ([#57](https://github.com/MobileTeleSystems/onetl/pull/57)) + +- Обновлен формат логирования по умолчанию, таким образом, чтобы в нем содержалось имя потока. ([#57](https://github.com/MobileTeleSystems/onetl/pull/57)) + +## Исправления ошибок + +- Исправлена ошибка, из-за которой `S3.list_dir('/')` возвращал пустой список в последней версии Minio. ([#58](https://github.com/MobileTeleSystems/onetl/pull/58)) diff --git a/mddocs/ru/changelog/0.9.0.md b/mddocs/ru/changelog/0.9.0.md new file mode 100644 index 000000000..c5f383e9a --- /dev/null +++ b/mddocs/ru/changelog/0.9.0.md @@ -0,0 +1,122 @@ +# 0.9.0 (2023-08-17) + +## Критические изменения + +- Переименованы методы: + + - `DBConnection.read_df` → `DBConnection.read_source_as_df` + - `DBConnection.write_df` → `DBConnection.write_df_to_target` ([#66](https://github.com/MobileTeleSystems/onetl/pull/66)) + +- Переименованы классы: + + - `HDFS.slots` → `HDFS.Slots` + - `Hive.slots` → `Hive.Slots` + + Старые имена сохранены, но будут удалены в v1.0.0 ([#103](https://github.com/MobileTeleSystems/onetl/pull/103)) + +- Для большей понятности переименованы некоторые опции: + + - `Hive.WriteOptions(mode="append")` → `Hive.WriteOptions(if_exists="append")` + - `Hive.WriteOptions(mode="overwrite_table")` → `Hive.WriteOptions(if_exists="replace_entire_table")` + - `Hive.WriteOptions(mode="overwrite_partitions")` → `Hive.WriteOptions(if_exists="replace_overlapping_partitions")` + - `JDBC.WriteOptions(mode="append")` → `JDBC.WriteOptions(if_exists="append")` + - `JDBC.WriteOptions(mode="overwrite")` → `JDBC.WriteOptions(if_exists="replace_entire_table")` + - `Greenplum.WriteOptions(mode="append")` → `Greenplum.WriteOptions(if_exists="append")` + - `Greenplum.WriteOptions(mode="overwrite")` → `Greenplum.WriteOptions(if_exists="replace_entire_table")` + - `MongoDB.WriteOptions(mode="append")` → `Greenplum.WriteOptions(if_exists="append")` + - `MongoDB.WriteOptions(mode="overwrite")` → `Greenplum.WriteOptions(if_exists="replace_entire_collection")` + - `FileDownloader.Options(mode="error")` → `FileDownloader.Options(if_exists="error")` + - `FileDownloader.Options(mode="ignore")` → `FileDownloader.Options(if_exists="ignore")` + - `FileDownloader.Options(mode="overwrite")` → `FileDownloader.Options(if_exists="replace_file")` + - `FileDownloader.Options(mode="delete_all")` → `FileDownloader.Options(if_exists="replace_entire_directory")` + - `FileUploader.Options(mode="error")` → `FileUploader.Options(if_exists="error")` + - `FileUploader.Options(mode="ignore")` → `FileUploader.Options(if_exists="ignore")` + - `FileUploader.Options(mode="overwrite")` → `FileUploader.Options(if_exists="replace_file")` + - `FileUploader.Options(mode="delete_all")` → `FileUploader.Options(if_exists="replace_entire_directory")` + - `FileMover.Options(mode="error")` → `FileMover.Options(if_exists="error")` + - `FileMover.Options(mode="ignore")` → `FileMover.Options(if_exists="ignore")` + - `FileMover.Options(mode="overwrite")` → `FileMover.Options(if_exists="replace_file")` + - `FileMover.Options(mode="delete_all")` → `FileMover.Options(if_exists="replace_entire_directory")` + + Старые имена сохранены, но будут удалены в v1.0.0 ([#108](https://github.com/MobileTeleSystems/onetl/pull/108)) + +- `onetl.log.disable_clients_logging()` переименован в `onetl.log.setup_clients_logging()`. ([#120](https://github.com/MobileTeleSystems/onetl/pull/120)) + +## Новая функциональность + +- Добавлены новые методы, возвращающие пакеты Maven для определенного подключения: + + - `Clickhouse.get_packages()` + - `MySQL.get_packages()` + - `Postgres.get_packages()` + - `Teradata.get_packages()` + - `MSSQL.get_packages(java_version="8")` + - `Oracle.get_packages(java_version="8")` + - `Greenplum.get_packages(scala_version="2.12")` + - `MongoDB.get_packages(scala_version="2.12")` + - `Kafka.get_packages(spark_version="3.4.1", scala_version="2.12")` + + Устаревший синтаксис: + + - `Clickhouse.package` + - `MySQL.package` + - `Postgres.package` + - `Teradata.package` + - `MSSQL.package` + - `Oracle.package` + - `Greenplum.package_spark_2_3` + - `Greenplum.package_spark_2_4` + - `Greenplum.package_spark_3_2` + - `MongoDB.package_spark_3_2` + - `MongoDB.package_spark_3_3` + - `MongoDB.package_spark_3_4` ([#87](https://github.com/MobileTeleSystems/onetl/pull/87)) + +- Разрешено устанавливать уровень логирования клиентских модулей в `onetl.log.setup_clients_logging()`. + + В `onetl.log.setup_logging()` новый дополнительный аргумент `enable_clients=True` позволяет включать логирование базовых клиентских модулей. + Это полезно для отладки. ([#120](https://github.com/MobileTeleSystems/onetl/pull/120)) + +- Добавлена поддержка чтения и записи данных в топики Kafka. + + Для этих операций были добавлены новые классы. + + - `Kafka` ([#54](https://github.com/MobileTeleSystems/onetl/pull/54), [#60](https://github.com/MobileTeleSystems/onetl/pull/60), [#72](https://github.com/MobileTeleSystems/onetl/pull/72), [#84](https://github.com/MobileTeleSystems/onetl/pull/84), [#87](https://github.com/MobileTeleSystems/onetl/pull/87), [#89](https://github.com/MobileTeleSystems/onetl/pull/89), [#93](https://github.com/MobileTeleSystems/onetl/pull/93), [#96](https://github.com/MobileTeleSystems/onetl/pull/96), [#102](https://github.com/MobileTeleSystems/onetl/pull/102), [#104](https://github.com/MobileTeleSystems/onetl/pull/104)) + - `Kafka.PlaintextProtocol` ([#79](https://github.com/MobileTeleSystems/onetl/pull/79)) + - `Kafka.SSLProtocol` ([#118](https://github.com/MobileTeleSystems/onetl/pull/118)) + - `Kafka.BasicAuth` ([#63](https://github.com/MobileTeleSystems/onetl/pull/63), [#77](https://github.com/MobileTeleSystems/onetl/pull/77)) + - `Kafka.KerberosAuth` ([#63](https://github.com/MobileTeleSystems/onetl/pull/63), [#77](https://github.com/MobileTeleSystems/onetl/pull/77), [#110](https://github.com/MobileTeleSystems/onetl/pull/110)) + - `Kafka.ScramAuth` ([#115](https://github.com/MobileTeleSystems/onetl/pull/115)) + - `Kafka.Slots` ([#109](https://github.com/MobileTeleSystems/onetl/pull/109)) + - `Kafka.ReadOptions` ([#68](https://github.com/MobileTeleSystems/onetl/pull/68)) + - `Kafka.WriteOptions` ([#68](https://github.com/MobileTeleSystems/onetl/pull/68)) + + В настоящее время Kafka не поддерживает инкрементальные стратегии чтения, такая возможность будет реализована в будущих выпусках. + +- Добавлена поддержка чтения файлов в Spark DataFrame и сохранения DataFrame в файлы. + + Для этих операций были добавлены новые классы. + + FileDFConnections: + + - `SparkHDFS` ([#98](https://github.com/MobileTeleSystems/onetl/pull/98)) + - `SparkS3` ([#94](https://github.com/MobileTeleSystems/onetl/pull/94), [#100](https://github.com/MobileTeleSystems/onetl/pull/100), [#124](https://github.com/MobileTeleSystems/onetl/pull/124)) + - `SparkLocalFS` ([#67](https://github.com/MobileTeleSystems/onetl/pull/67)) + + Классы верхнего уровня абстракции: + + - `FileDFReader` ([#73](https://github.com/MobileTeleSystems/onetl/pull/73)) + - `FileDFWriter` ([#81](https://github.com/MobileTeleSystems/onetl/pull/81)) + + Форматы файлов: + + - `Avro` ([#69](https://github.com/MobileTeleSystems/onetl/pull/69)) + - `CSV` ([#92](https://github.com/MobileTeleSystems/onetl/pull/92)) + - `JSON` ([#83](https://github.com/MobileTeleSystems/onetl/pull/83)) + - `JSONLine` ([#83](https://github.com/MobileTeleSystems/onetl/pull/83)) + - `ORC` ([#86](https://github.com/MobileTeleSystems/onetl/pull/86)) + - `Parquet` ([#88](https://github.com/MobileTeleSystems/onetl/pull/88)) + +## Улучшения + +- Удалены избыточные проверки доступности драйвера в соединениях Greenplum и MongoDB. ([#67](https://github.com/MobileTeleSystems/onetl/pull/67)) +- Проверка доступности класса Java перенесена из метода `.check()` в конструктор соединения. ([#97](https://github.com/MobileTeleSystems/onetl/pull/97)) diff --git a/mddocs/ru/changelog/0.9.1.md b/mddocs/ru/changelog/0.9.1.md new file mode 100644 index 000000000..78bf28d7e --- /dev/null +++ b/mddocs/ru/changelog/0.9.1.md @@ -0,0 +1,7 @@ +# 0.9.1 (2023-08-17) + +## Исправления ошибок + +- Исправлена ошибка, из-за которой количество потоков, создаваемых `FileDownloader` / `FileUploader` / `FileMover`, было + не `min(workers, len(files))`, а `max(workers, len(files))`, что приводило к созданию слишком большого количества рабочих потоков + в случаях больших списков файлов. diff --git a/mddocs/ru/changelog/0.9.2.md b/mddocs/ru/changelog/0.9.2.md new file mode 100644 index 000000000..dbd0bf7bf --- /dev/null +++ b/mddocs/ru/changelog/0.9.2.md @@ -0,0 +1,23 @@ +# 0.9.2 (2023-09-06) + +## Новые возможности + +- Добавлены `if_exists="ignore"` и `error` в `Greenplum.WriteOptions` ([#142](https://github.com/MobileTeleSystems/onetl/pull/142)) + +## Улучшения + +- Улучшены сообщения валидации при записи dataframe в Kafka. ([#131](https://github.com/MobileTeleSystems/onetl/pull/131)) + +- Улучшена документация: + + - В документации по подключениям к базам данных добавлены некоторые нюансы о чтении и записи + - Расширена информация о выполнении операторов для подключений JDBC и Greenplum + +## Исправления ошибок + +- Исправлена валидация столбца `headers` при записи в Kafka с `Kafka.WriteOptions()` по умолчанию - значение по умолчанию было `False`, + но вместо того, чтобы вызывать исключение, значение столбца просто игнорировалось. ([#131](https://github.com/MobileTeleSystems/onetl/pull/131)) +- Исправлено чтение данных из Oracle с `partitioningMode="range"` без явно установленных `lowerBound` / `upperBound`. ([#133](https://github.com/MobileTeleSystems/onetl/pull/133)) +- Обновлена документация Kafka об использовании SSLProtocol. ([#136](https://github.com/MobileTeleSystems/onetl/pull/136)) +- Выбрасывается исключение, если кто-то пытается прочитать данные из несуществующей темы Kafka. ([#138](https://github.com/MobileTeleSystems/onetl/pull/138)) +- В DBReader разрешено передавать Kafka имя топик вида `some.topic.name`. Аналогично для коллекций MongoDB. ([#139](https://github.com/MobileTeleSystems/onetl/pull/139)) diff --git a/mddocs/ru/changelog/0.9.3.md b/mddocs/ru/changelog/0.9.3.md new file mode 100644 index 000000000..eb43b4352 --- /dev/null +++ b/mddocs/ru/changelog/0.9.3.md @@ -0,0 +1,5 @@ +# 0.9.3 (2023-09-06) + +## Исправление ошибок + +- Исправлена сборка документации diff --git a/mddocs/ru/changelog/0.9.4.md b/mddocs/ru/changelog/0.9.4.md new file mode 100644 index 000000000..f871309c6 --- /dev/null +++ b/mddocs/ru/changelog/0.9.4.md @@ -0,0 +1,23 @@ +# 0.9.4 (2023-09-26) + +## Функциональность + +- Добавлена поддержка файлов формата `Excel`. ([#148](https://github.com/MobileTeleSystems/onetl/pull/148)) +- Добавлено файловое подключение `Samba`. Теперь можно скачивать и загружать файлы в общие папки Samba с помощью `FileDownloader`/`FileUploader`. ([#150](https://github.com/MobileTeleSystems/onetl/pull/150)) +- Добавлены `if_exists="ignore"` и `error` в `Hive.WriteOptions` ([#143](https://github.com/MobileTeleSystems/onetl/pull/143)) +- Добавлены `if_exists="ignore"` и `error` в `JDBC.WriteOptions` ([#144](https://github.com/MobileTeleSystems/onetl/pull/144)) +- Добавлены `if_exists="ignore"` и `error` в `MongoDB.WriteOptions` ([#145](https://github.com/MobileTeleSystems/onetl/pull/145)) + +## Улучшения + +- Добавлена документация о различных способах передачи пакетов в Spark session. ([#151](https://github.com/MobileTeleSystems/onetl/pull/151)) +- Значительно улучшена документация по `Greenplum`: + - Добавлена информация о сетевых портах, правах доступа, `pg_hba.conf` и т.д. + - Добавлены схемы взаимодействия для чтения, записи и выполнения операторов в Greenplum. + - Добавлены рекомендации по чтению данных из представлений и результатов `JOIN` Greenplum. ([#154](https://github.com/MobileTeleSystems/onetl/pull/154)) +- Методы `.fetch` и `.execute` для подключений к БД стали потокобезопасными. Каждый поток работает со своим собственным экземпляром подключения. ([#156](https://github.com/MobileTeleSystems/onetl/pull/156)) +- Для `FileConnection` вызывается `.close()`, когда первый удаляется сборщиком мусора. ([#156](https://github.com/MobileTeleSystems/onetl/pull/156)) + +## Исправления ошибок + +- Исправлена проблема, когда остановка интерпретатора Python вызывает `JDBCMixin.close()`, но он завершается исключениями. ([#156](https://github.com/MobileTeleSystems/onetl/pull/156)) diff --git a/mddocs/ru/changelog/0.9.5.md b/mddocs/ru/changelog/0.9.5.md new file mode 100644 index 000000000..1d7358c0b --- /dev/null +++ b/mddocs/ru/changelog/0.9.5.md @@ -0,0 +1,14 @@ +# 0.9.5 (2023-10-10) + +## Features + +- Add `XML` file format support. ([#163](https://github.com/MobileTeleSystems/onetl/pull/163)) +- Tested compatibility with Spark 3.5.0. `MongoDB` and `Excel` are not supported yet, but other packages do. ([#159](https://github.com/MobileTeleSystems/onetl/pull/159)) + +## Improvements + +- Add check to all DB and FileDF connections that Spark session is alive. ([#164](https://github.com/MobileTeleSystems/onetl/pull/164)) + +## Bug Fixes + +- Fix `Hive.check()` behavior when Hive Metastore is not available. ([#164](https://github.com/MobileTeleSystems/onetl/pull/164)) From a7808b1c95b6fb2710156cbc298c6ab594d417bb Mon Sep 17 00:00:00 2001 From: Sattar Gyulmamedov Date: Tue, 10 Jun 2025 00:12:47 +0300 Subject: [PATCH 08/22] changelog converted & translated v.1 --- mddocs/docs/changelog/0.10.0.md | 2 +- mddocs/docs/changelog/0.10.1.md | 2 +- mddocs/docs/changelog/0.11.0.md | 2 +- mddocs/docs/changelog/0.13.0.md | 4 +- mddocs/ru/changelog/0.10.0.md | 169 +++++++++++++++----------------- mddocs/ru/changelog/0.10.1.md | 22 ++--- mddocs/ru/changelog/0.10.2.md | 38 ++++--- mddocs/ru/changelog/0.11.0.md | 124 ++++++++++++----------- mddocs/ru/changelog/0.11.1.md | 9 +- mddocs/ru/changelog/0.11.2.md | 4 +- mddocs/ru/changelog/0.12.0.md | 33 +++---- mddocs/ru/changelog/0.12.1.md | 21 ++-- mddocs/ru/changelog/0.12.2.md | 19 ++-- mddocs/ru/changelog/0.12.3.md | 4 +- mddocs/ru/changelog/0.12.4.md | 4 +- mddocs/ru/changelog/0.12.5.md | 16 +-- mddocs/ru/changelog/0.13.0.md | 119 ++++++++++------------ mddocs/ru/changelog/0.13.1.md | 8 +- mddocs/ru/changelog/0.13.3.md | 4 +- mddocs/ru/changelog/0.13.4.md | 11 +-- mddocs/ru/changelog/0.9.5.md | 9 +- 21 files changed, 297 insertions(+), 327 deletions(-) diff --git a/mddocs/docs/changelog/0.10.0.md b/mddocs/docs/changelog/0.10.0.md index 68a609eb6..508aac654 100644 --- a/mddocs/docs/changelog/0.10.0.md +++ b/mddocs/docs/changelog/0.10.0.md @@ -417,7 +417,7 @@ But most of users use other HWM store implementations which do not have such iss ``DBReader.run()`` + incremental/batch strategy behavior in versions 0.10.x and newer: - Detect type of HWM expression: ``SELECT hwm.expression FROM table WHERE 1=0``. - - Determine corresponding Spark type ``df.schema[0]`` and when determine matching HWM class (if ``DReader.AutoDetectHWM`` is used). + - Determine corresponding Spark type ``df.schema[0]`` and when determine matching HWM class (if ``DBReader.AutoDetectHWM`` is used). - Get min/max values by querying the source: ``SELECT MAX(hwm.expression) FROM table WHERE hwm.expression >= prev_hwm.value``. - Use ``max(hwm.expression)`` as next HWM value, and save it to HWM Store. - Create dataframe from query ``SELECT ... table columns ... FROM table WHERE hwm.expression > prev_hwm.value AND hwm.expression <= current_hwm.value``, baking new HWM value into the query. diff --git a/mddocs/docs/changelog/0.10.1.md b/mddocs/docs/changelog/0.10.1.md index ba3ed40bb..6280dc26b 100644 --- a/mddocs/docs/changelog/0.10.1.md +++ b/mddocs/docs/changelog/0.10.1.md @@ -20,7 +20,7 @@ - Add `has_data`, `raise_if_no_data` methods to `DBReader` class. ([#203](https://github.com/MobileTeleSystems/onetl/pull/203)) - Updare VMware Greenplum connector from `2.1.4` to `2.3.0`. This implies: - : - Greenplum 7.x support + - Greenplum 7.x support - [Kubernetes support](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/configure.html#k8scfg) - New read option [gpdb.matchDistributionPolicy](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#distpolmotion) which allows to match each Spark executor with specific Greenplum segment, avoiding redundant data transfer between Greenplum segments diff --git a/mddocs/docs/changelog/0.11.0.md b/mddocs/docs/changelog/0.11.0.md index c77c977b0..64dee376f 100644 --- a/mddocs/docs/changelog/0.11.0.md +++ b/mddocs/docs/changelog/0.11.0.md @@ -10,7 +10,7 @@ most of users will not see any differences. - Package version changed `0.3.2` → `0.6.0-patch5`. - Driver name changed `ru.yandex.clickhouse.ClickHouseDriver` → `com.clickhouse.jdbc.ClickHouseDriver`. - This brings up several fixes for Spark \<-> Clickhouse type compatibility, and also Clickhouse clusters support. + This brings up several fixes for Spark <-> Clickhouse type compatibility, and also Clickhouse clusters support. - Update other JDBC drivers to latest versions: - MSSQL `12.2.0` → `12.6.2` ([#254](https://github.com/MobileTeleSystems/onetl/pull/254)). diff --git a/mddocs/docs/changelog/0.13.0.md b/mddocs/docs/changelog/0.13.0.md index 37ae8368a..1a39a060a 100644 --- a/mddocs/docs/changelog/0.13.0.md +++ b/mddocs/docs/changelog/0.13.0.md @@ -177,7 +177,7 @@ - Fix `MSSQL.fetch(...)` and `MySQL.fetch(...)` opened a read-write connection instead of read-only. ([#337](https://github.com/MobileTeleSystems/onetl/pull/337)) Now this is fixed: - : - `MSSQL.fetch(...)` establishes connection with `ApplicationIntent=ReadOnly`. + - `MSSQL.fetch(...)` establishes connection with `ApplicationIntent=ReadOnly`. - `MySQL.fetch(...)` calls `SET SESSION TRANSACTION READ ONLY` statement. - Fixed passing multiple filters to `FileDownloader` and `FileMover`. ([#338](https://github.com/MobileTeleSystems/onetl/pull/338)) @@ -211,7 +211,7 @@ - Minimal `etl-entities` version is now [2.5.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.5.0). ([#331](https://github.com/MobileTeleSystems/onetl/pull/331)) - Update DB connectors/drivers to latest versions: ([#345](https://github.com/MobileTeleSystems/onetl/pull/345)) - : - Clickhouse `0.6.5` → `0.7.2` + - Clickhouse `0.6.5` → `0.7.2` - MongoDB `10.4.0` → `10.4.1` - MySQL `9.0.0` → `9.2.0` - Oracle `23.5.0.24.07` → `23.7.0.25.01` diff --git a/mddocs/ru/changelog/0.10.0.md b/mddocs/ru/changelog/0.10.0.md index 68a609eb6..4c5893a68 100644 --- a/mddocs/ru/changelog/0.10.0.md +++ b/mddocs/ru/changelog/0.10.0.md @@ -1,12 +1,12 @@ # 0.10.0 (2023-12-18) -## Breaking Changes +## Критические изменения -- Upgrade `etl-entities` from v1 to v2 ([#172](https://github.com/MobileTeleSystems/onetl/pull/172)). +- Обновление `etl-entities` с v1 до v2 ([#172](https://github.com/MobileTeleSystems/onetl/pull/172)). - This implies that `HWM` classes are now have different internal structure than they used to. + Это означает, что классы `HWM` теперь имеют другую внутреннюю структуру, чем раньше. - Before: + До: ```python from etl_entities.old_hwm import IntHWM as OldIntHWM @@ -21,7 +21,7 @@ ) ``` - After: + После: ```python from etl_entities.hwm import ColumnIntHWM @@ -35,35 +35,35 @@ ) ``` - **Breaking change:** If you used HWM classes from `etl_entities` module, you should rewrite your code to make it compatible with new version. + **Критическое изменение:** Если вы использовали классы HWM из модуля `etl_entities`, вам следует переписать свой код, чтобы сделать его совместимым с новой версией. -??? "More details" +??? "Подробнее" - - ``HWM`` classes used by previous onETL versions were moved from ``etl_entities`` to ``etl_entities.old_hwm`` submodule. They are here for compatibility reasons, but are planned to be removed in ``etl-entities`` v3 release. - - New ``HWM`` classes have flat structure instead of nested. - - New ``HWM`` classes have mandatory ``name`` attribute (it was known as ``qualified_name`` before). - - Type aliases used while serializing and deserializing ``HWM`` objects to ``dict`` representation were changed too: ``int`` → ``column_int``. + - Классы ``HWM``, используемые предыдущими версиями onETL, были перемещены из ``etl_entities`` в подмодуль ``etl_entities.old_hwm``. Они сохранены для обеспечения совместимости, но будут удалены в выпуске ``etl-entities`` v3. + - Новые классы ``HWM`` имеют плоскую структуру вместо вложенной. + - Новые классы ``HWM`` имеют обязательный атрибут ``name`` (ранее известный как ``qualified_name``). + - Типы псевдонимов, используемые при сериализации и десериализации объектов ``HWM`` в представлении ``dict``, также были изменены: ``int`` → ``column_int``. - To make migration simpler, you can use new method: + Чтобы упростить миграцию, вы можете использовать новый метод: ```python old_hwm = OldIntHWM(...) new_hwm = old_hwm.as_new_hwm() ``` - Which automatically converts all fields from old structure to new one, including `qualified_name` → `name`. + Он автоматически преобразует все поля из старой структуры в новую, включая `qualified_name` → `name`. -- **Breaking changes:** +- **Критические изменения:** - - Methods `BaseHWMStore.get()` and `BaseHWMStore.save()` were renamed to `get_hwm()` and `set_hwm()`. - - They now can be used only with new HWM classes from `etl_entities.hwm`, **old HWM classes are not supported**. + - Методы `BaseHWMStore.get()` и `BaseHWMStore.save()` были переименованы в `get_hwm()` и `set_hwm()`. + - Теперь их можно использовать только с новыми классами HWM из `etl_entities.hwm`, **старые классы HWM не поддерживаются**. - If you used them in your code, please update it accordingly. + Если вы использовали их в своем коде, пожалуйста, обновите его соответствующим образом. -- YAMLHWMStore **CANNOT read files created by older onETL versions** (0.9.x or older). +- YAMLHWMStore **НЕ МОЖЕТ читать файлы, созданные более старыми версиями onETL** (0.9.x или старше). -??? "Update procedure" +??? "Процедура обновления" ```python # pip install onetl==0.9.5 @@ -250,11 +250,11 @@ # That's all! ``` -But most of users use other HWM store implementations which do not have such issues. +Большинство пользователей используют другие реализации хранилища HWM, для которых указанная процедура не требуется. -- Several classes and functions were moved from `onetl` to `etl_entities`: +- Некоторые классы и функции были перемещены из `onetl` в `etl_entities`: -=== "onETL ``0.9.x`` and older" +=== "onETL ``0.9.x`` и старше" ```python @@ -268,7 +268,7 @@ But most of users use other HWM store implementations which do not have such iss ) ``` -=== "nETL ``0.10.x`` and newer" +=== "nETL ``0.10.x`` и новее" ```python @@ -282,11 +282,11 @@ But most of users use other HWM store implementations which do not have such iss ) ``` - They still can be imported from old module, but this is deprecated and will be removed in v1.0.0 release. + Их все еще можно импортировать из старого модуля, но они устарели и будут удалены в выпуске v1.0.0. -- Change the way of passing `HWM` to `DBReader` and `FileDownloader` classes: +- Изменился способ передачи `HWM` классам `DBReader` и `FileDownloader`: -=== "onETL ``0.9.x`` and older" +=== "onETL ``0.9.x`` и старше" ```python linenums="1" hl_lines="12-21" # Simple @@ -320,7 +320,7 @@ But most of users use other HWM store implementations which do not have such iss ) ``` -=== "onETL ``0.10.x`` and newer" +=== "onETL ``0.10.x`` и новее" ```python linenums="1" hl_lines="12-21" # Simple @@ -358,37 +358,35 @@ But most of users use other HWM store implementations which do not have such iss ``` - New HWM classes have **mandatory** `name` attribute which should be passed explicitly, - instead of generating if automatically under the hood. + Новые классы HWM имеют **обязательный** атрибут `name`, который должен быть передан явно, вместо автоматической генерации. - Automatic `name` generation using the old `DBReader.hwm_column` / `FileDownloader.hwm_type` - syntax is still supported, but will be removed in v1.0.0 release. ([#179](https://github.com/MobileTeleSystems/onetl/pull/179)) + Автоматическая генерация `name` с использованием старого синтаксиса `DBReader.hwm_column` / `FileDownloader.hwm_type` все еще поддерживается, но будет удалена в выпуске v1.0.0. ([#179](https://github.com/MobileTeleSystems/onetl/pull/179)) -- Performance of read Incremental and Batch strategies has been drastically improved. ([#182](https://github.com/MobileTeleSystems/onetl/pull/182)). +- Значительно улучшена производительность стратегий чтения Incremental и Batch. ([#182](https://github.com/MobileTeleSystems/onetl/pull/182)). -??? "Before and after in details" +??? "Подробнее о том, что было до и после" - ``DBReader.run()`` + incremental/batch strategy behavior in versions 0.9.x and older: + ``DBReader.run()`` + поведение инкрементной/пакетной стратегии в версиях 0.9.x и старше: - - Get table schema by making query ``SELECT * FROM table WHERE 1=0`` (if ``DBReader.columns`` has ``*``) - - Expand ``*`` to real column names from table, add here ``hwm_column``, remove duplicates (as some RDBMS does not allow that). - - Create dataframe from query like ``SELECT hwm_expression AS hwm_column, ...other table columns... FROM table WHERE hwm_expression > prev_hwm.value``. - - Determine HWM class using dataframe schema: ``df.schema[hwm_column].dataType``. - - Determine x HWM column value using Spark: ``df.select(max(hwm_column)).collect()``. - - Use ``max(hwm_column)`` as next HWM value, and save it to HWM Store. - - Return dataframe to user. + - Получить схему таблицы, выполнив запрос ``SELECT * FROM table WHERE 1=0`` (если ``DBReader.columns`` содержит ``*``) + - Развернуть ``*`` в реальные имена столбцов из таблицы, добавить сюда ``hwm_column``, удалить дубликаты (поскольку некоторые СУБД этого не позволяют). + - Создать dataframe из запроса, подобного ``SELECT hwm_expression AS hwm_column, ...other table columns... FROM table WHERE hwm_expression > prev_hwm.value``. + - Определить класс HWM, используя схему dataframe: ``df.schema[hwm_column].dataType``. + - Определить значение столбца x HWM, используя Spark: ``df.select(max(hwm_column)).collect()``. + - Использовать ``max(hwm_column)`` в качестве следующего значения HWM и сохранить его в HWM Store. + - Вернуть dataframe пользователю. - This was far from ideal: + Это было далеко от идеала: - - Dataframe content (all rows or just changed ones) was loaded from the source to Spark only to get min/max values of specific column. + - Содержимое Dataframe (все строки или только измененные) загружалось из источника в Spark только для получения минимальных/максимальных значений определенного столбца. - - Step of fetching table schema and then substituting column names in the next query caused some unexpected errors. + - Шаг получения схемы таблицы, а затем подстановки имен столбцов в следующем запросе вызывал некоторые неожиданные ошибки. - For example, source contains columns with mixed name case, like ``"CamelColumn"`` or ``"spaced column"``. + Например, если источник содержит столбцы со смешанным регистром имен, например ``"CamelColumn"`` или ``"spaced column"``. - Column names were *not* escaped during query generation, leading to queries that cannot be executed by database. + Имена столбцов *не* экранировались во время генерации запроса, что приводило к запросам, которые не могли быть выполнены базой данных. - So users have to *explicitly* pass column names ``DBReader``, wrapping columns with mixed naming with ``"``: + Поэтому пользователи должны были *явно* передавать имена столбцов ``DBReader``, заключая столбцы со смешанным регистром именования в ``"``: ```python reader = DBReader( @@ -402,38 +400,34 @@ But most of users use other HWM store implementations which do not have such iss ], ) ``` - - Using ``DBReader`` with ``IncrementalStrategy`` could lead to reading rows already read before. + - Использование ``DBReader`` с ``IncrementalStrategy`` могло привести к чтению строк, уже прочитанных ранее. - Dataframe was created from query with WHERE clause like ``hwm.expression > prev_hwm.value``, - not ``hwm.expression > prev_hwm.value AND hwm.expression <= current_hwm.value``. + Dataframe был создан из запроса с предложением WHERE, подобным ``hwm.expression > prev_hwm.value``, а не ``hwm.expression > prev_hwm.value AND hwm.expression <= current_hwm.value``. - So if new rows appeared in the source **after** HWM value is determined, - they can be read by accessing dataframe content (because Spark dataframes are lazy), - leading to inconsistencies between HWM value and dataframe content. + Поэтому, если в источнике появились новые строки **после** определения значения HWM, они могли быть прочитаны при доступе к содержимому dataframe (поскольку Spark использует "ленивые" вычисления), и это могло привести к несоответствиям между значением HWM и содержимого dataframe. - This may lead to issues then ``DBReader.run()`` read some data, updated HWM value, and next call of ``DBReader.run()`` - will read rows that were already read in previous run. + Что впоследствии могло привести к проблемам, в случае если ``DBReader.run()`` прочитает некоторые данные, обновит значение HWM, а следующий вызов ``DBReader.run()`` прочитает строки, которые уже были прочитаны в предыдущем запуске. - ``DBReader.run()`` + incremental/batch strategy behavior in versions 0.10.x and newer: + ``DBReader.run()`` + поведение инкрементной/пакетной стратегии в версиях 0.10.x и новее: - - Detect type of HWM expression: ``SELECT hwm.expression FROM table WHERE 1=0``. - - Determine corresponding Spark type ``df.schema[0]`` and when determine matching HWM class (if ``DReader.AutoDetectHWM`` is used). - - Get min/max values by querying the source: ``SELECT MAX(hwm.expression) FROM table WHERE hwm.expression >= prev_hwm.value``. - - Use ``max(hwm.expression)`` as next HWM value, and save it to HWM Store. - - Create dataframe from query ``SELECT ... table columns ... FROM table WHERE hwm.expression > prev_hwm.value AND hwm.expression <= current_hwm.value``, baking new HWM value into the query. - - Return dataframe to user. + - Определить тип выражения HWM: ``SELECT hwm.expression FROM table WHERE 1=0``. + - Определить соответствующий тип Spark ``df.schema[0]`` и затем определить соответствующий класс HWM (если используется ``DBReader.AutoDetectHWM``). + - Получить минимальные/максимальные значения, запросив источник: ``SELECT MAX(hwm.expression) FROM table WHERE hwm.expression >= prev_hwm.value``. + - Использовать ``max(hwm.expression)`` в качестве следующего значения HWM и сохранить его в HWM Store. + - Создать dataframe из запроса ``SELECT ... table columns ... FROM table WHERE hwm.expression > prev_hwm.value AND hwm.expression <= current_hwm.value``, внедрив новое значение HWM в запрос. + - Вернуть dataframe пользователю. - Improvements: + Улучшения: - - Allow source to calculate min/max instead of loading everything to Spark. This should be **faster** on large amounts of data (**up to x2**), because we do not transfer all the data from the source to Spark. This can be even faster if source have indexes for HWM column. - - Columns list is passed to source as-is, without any resolving on `DBReader` side. So you can pass `DBReader(columns=["*"])` to read tables with mixed columns naming. - - Restrict dataframe content to always match HWM values, which leads to never reading the same row twice. + - Разрешение источнику вычислять min/max вместо загрузки всего набора данных в Spark. Это должно быть **быстрее** на больших объемах данных (**до x2**), потому что мы не передаем все данные из источника в Spark. Это может быть даже еще быстрее, если источник имеет индексы для столбца HWM. + - Список столбцов передается в источник как есть, без какой-либо обработки на стороне `DBReader`. Таким образом, вы можете передать `DBReader(columns=["*"])` для чтения таблиц со смешанным именованием столбцов. + - Ограничение содержимого dataframe, чтобы оно всегда соответствовало значениям HWM, гарантирующее, что одна и та же строка никогда не будет прочитана дважды. - **Breaking change**: HWM column is not being implicitly added to dataframe. It was a part of `SELECT` clause, but now it is mentioned only in `WHERE` clause. + **Критическое изменение**: Столбец HWM больше не добавляется неявно в dataframe. Раньше он был частью предложения `SELECT`, но теперь он упоминается только в предложении `WHERE`. - So if you had code like this, you have to rewrite it: + Поэтому, если у вас был код, подобный этому, вам придется его переписать: -=== "onETL ``0.9.x`` and older" +=== "onETL ``0.9.x`` и старше" ```python linenums="1" hl_lines="1-16" reader = DBReader( @@ -472,7 +466,7 @@ But most of users use other HWM store implementations which do not have such iss ``` -=== "onETL ``0.10.x`` and newer" +=== "onETL ``0.10.x`` и новее" ```python linenums="1" hl_lines="1-16" reader = DBReader( @@ -512,32 +506,27 @@ But most of users use other HWM store implementations which do not have such iss assert df.columns == ["col1", "col2", "hwm_col"] ``` - But most users just use `columns=["*"]` anyway, they won't see any changes. + Но поскольку большинство пользователей все равно используют `columns=["*"]`, они не увидят никаких изменений. -- `FileDownloader.run()` now updates HWM in HWM Store not after each file is being successfully downloaded, - but after all files were handled. +- `FileDownloader.run()` теперь обновляет HWM в HWM Store после обработки всех файлов, а не после загрузки каждого отдельного файла. - This is because: + Причины изменений: - - FileDownloader can be used with `DownloadOptions(workers=N)`, which could lead to race condition - one thread can save to HWM store one HWM value when another thread will save different value. - - FileDownloader can download hundreds and thousands of files, and issuing a request to HWM Store for each file could potentially DDoS HWM Store. ([#189](https://github.com/MobileTeleSystems/onetl/pull/189)) + - FileDownloader можно использовать с `DownloadOptions(workers=N)`, что может привести к состоянию гонки - один поток может сохранить в HWM store одно значение HWM, а другой -- другое. + - FileDownloader может загружать сотни и даже тысячи файлов, и отправка запроса в HWM Store для каждого файла может потенциально привести к DDoS HWM Store. ([#189](https://github.com/MobileTeleSystems/onetl/pull/189)) - There is a exception handler which tries to save HWM to HWM store if download process was interrupted. But if it was interrupted by force, like sending `SIGKILL` event, - HWM will not be saved to HWM store, so some already downloaded files may be downloaded again next time. + Обработчик исключений пытается сохранить HWM в HWM store, если процесс загрузки был прерван. Но если он был прерван принудительно, например, отправкой события `SIGKILL`, HWM не будет сохранен в HWM store, и тогда некоторые уже загруженные файлы могут быть загружены снова. - But unexpected process kill may produce other negative impact, like some file will be downloaded partially, so this is an expected behavior. + Кроме того, неожиданное завершение процесса может привести к другим негативным последствиям, например, некоторые файлы будут загружены частично, поэтому мы считаем такую реализацию ожидаемым поведением. -## Features +## Функциональность -- Add Python 3.12 compatibility. ([#167](https://github.com/MobileTeleSystems/onetl/pull/167)) -- `Excel` file format now can be used with Spark 3.5.0. ([#187](https://github.com/MobileTeleSystems/onetl/pull/187)) -- `SnapshotBatchStagy` and `IncrementalBatchStrategy` does no raise exceptions if source does not contain any data. - Instead they stop at first iteration and return empty dataframe. ([#188](https://github.com/MobileTeleSystems/onetl/pull/188)) -- Cache result of `connection.check()` in high-level classes like `DBReader`, `FileDownloader` and so on. This makes logs less verbose. ([#190](https://github.com/MobileTeleSystems/onetl/pull/190)) +- Добавлена совместимость с Python 3.12. ([#167](https://github.com/MobileTeleSystems/onetl/pull/167)) +- Формат файлов `Excel` теперь можно использовать с Spark 3.5.0. ([#187](https://github.com/MobileTeleSystems/onetl/pull/187)) +- `SnapshotBatchStagy` и `IncrementalBatchStrategy` не вызывают исключений, если источник не содержит данных. Вместо этого они останавливаются на первой итерации и возвращают пустой dataframe. ([#188](https://github.com/MobileTeleSystems/onetl/pull/188)) +- Добавлено кэширование результата `connection.check()` в классах высокого уровня, таких как `DBReader`, `FileDownloader` и т. д. Это устраняет избыточную информативность логов. ([#190](https://github.com/MobileTeleSystems/onetl/pull/190)) -## Bug Fixes +## Исправление ошибок -- Fix `@slot` and `@hook` decorators returning methods with missing arguments in signature (Pylance, VS Code). ([#183](https://github.com/MobileTeleSystems/onetl/pull/183)) -- Kafka connector documentation said that it does support reading topic data incrementally by passing `group.id` or `groupIdPrefix`. - Actually, this is not true, because Spark does not send information to Kafka which messages were consumed. - So currently users can only read the whole topic, no incremental reads are supported. +- Исправлены декораторы `@slot` и `@hook`, возвращающие методы с отсутствующими аргументами в сигнатуре (Pylance, VS Code). ([#183](https://github.com/MobileTeleSystems/onetl/pull/183)) +- В документации Kafka connector говорилось, что он поддерживает инкрементное чтение данных топика путем передачи `group.id` или `groupIdPrefix`. На самом деле это не так, потому что Spark не отправляет в Kafka информацию о том, какие сообщения были получены. Поэтому в настоящее время пользователи могут читать весь топик целиком, а инкрементное чтение не поддерживается. diff --git a/mddocs/ru/changelog/0.10.1.md b/mddocs/ru/changelog/0.10.1.md index ba3ed40bb..b4b0d90c2 100644 --- a/mddocs/ru/changelog/0.10.1.md +++ b/mddocs/ru/changelog/0.10.1.md @@ -1,8 +1,8 @@ # 0.10.1 (2024-02-05) -## Features +## Новые возможности -- Add support of `Incremental Strategies` for `Kafka` connection: +- Добавлена поддержка `Incremental Strategies` для подключения `Kafka`: ```python reader = DBReader( @@ -15,15 +15,15 @@ df = reader.run() ``` - This lets you resume reading data from a Kafka topic starting at the last committed offset from your previous run. ([#202](https://github.com/MobileTeleSystems/onetl/pull/202)) + Использование этой стратегии позволяет возобновить чтение данных из топика Kafka, начиная с последнего зафиксированного смещения. ([#202](https://github.com/MobileTeleSystems/onetl/pull/202)) -- Add `has_data`, `raise_if_no_data` methods to `DBReader` class. ([#203](https://github.com/MobileTeleSystems/onetl/pull/203)) +- Добавлены методы `has_data`, `raise_if_no_data` в класс `DBReader`. ([#203](https://github.com/MobileTeleSystems/onetl/pull/203)) -- Updare VMware Greenplum connector from `2.1.4` to `2.3.0`. This implies: - : - Greenplum 7.x support - - [Kubernetes support](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/configure.html#k8scfg) - - New read option [gpdb.matchDistributionPolicy](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#distpolmotion) - which allows to match each Spark executor with specific Greenplum segment, avoiding redundant data transfer between Greenplum segments - - Allows overriding [Greenplum optimizer parameters](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#greenplum-gucs) in read/write operations ([#208](https://github.com/MobileTeleSystems/onetl/pull/208)) +- Обновлен коннектор VMware Greenplum с `2.1.4` до `2.3.0`. Что предоставляет: + - Поддержку Greenplum 7.x + - [Поддержку Kubernetes](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/configure.html#k8scfg) + - Новую опцию чтения [gpdb.matchDistributionPolicy](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#distpolmotion) которая позволяет сопоставить каждый Spark executor с определенным сегментом Greenplum, избегая избыточной передачи данных между сегментами Greenplum + - Возможность переопределять [параметры оптимизатора Greenplum](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#greenplum-gucs) в операциях чтения/записи ([#208](https://github.com/MobileTeleSystems/onetl/pull/208)) + +- Метод `Greenplum.get_packages()` теперь принимает необязательный аргумент `package_version`, который позволяет переопределить версию пакета коннектора Greenplum. ([#208](https://github.com/MobileTeleSystems/onetl/pull/208)) -- `Greenplum.get_packages()` method now accepts optional arg `package_version` which allows to override version of Greenplum connector package. ([#208](https://github.com/MobileTeleSystems/onetl/pull/208)) diff --git a/mddocs/ru/changelog/0.10.2.md b/mddocs/ru/changelog/0.10.2.md index 3ae7ad822..443356408 100644 --- a/mddocs/ru/changelog/0.10.2.md +++ b/mddocs/ru/changelog/0.10.2.md @@ -1,38 +1,34 @@ # 0.10.2 (2024-03-21) -## Features +## Функциональность -- Add support of Pydantic v2. ([#230](https://github.com/MobileTeleSystems/onetl/pull/230)) +- Добавлена поддержка Pydantic v2. ([#230](https://github.com/MobileTeleSystems/onetl/pull/230)) -## Improvements +## Улучшения -- Improve database connections documentation: - - Add "Types" section describing mapping between Clickhouse and Spark types - - Add "Prerequisites" section describing different aspects of connecting to Clickhouse - - Separate documentation of `DBReader` and `.sql()` / `.pipeline(...)` - - Add examples for `.fetch()` and `.execute()` ([#211](https://github.com/MobileTeleSystems/onetl/pull/211), [#228](https://github.com/MobileTeleSystems/onetl/pull/228), [#229](https://github.com/MobileTeleSystems/onetl/pull/229), [#233](https://github.com/MobileTeleSystems/onetl/pull/233), [#234](https://github.com/MobileTeleSystems/onetl/pull/234), [#235](https://github.com/MobileTeleSystems/onetl/pull/235), [#236](https://github.com/MobileTeleSystems/onetl/pull/236), [#240](https://github.com/MobileTeleSystems/onetl/pull/240)) +- Улучшена документация по подключениям к базам данных: + - Добавлен раздел "Типы" с описанием соответствия типов Clickhouse и Spark + - Добавлен раздел "Prerequisites" с описанием различных аспектов подключения к Clickhouse + - Разделена документация `DBReader` и `.sql()` / `.pipeline(...)` + - Добавлены примеры для `.fetch()` и `.execute()` ([#211](https://github.com/MobileTeleSystems/onetl/pull/211), [#228](https://github.com/MobileTeleSystems/onetl/pull/228), [#229](https://github.com/MobileTeleSystems/onetl/pull/229), [#233](https://github.com/MobileTeleSystems/onetl/pull/233), [#234](https://github.com/MobileTeleSystems/onetl/pull/234), [#235](https://github.com/MobileTeleSystems/onetl/pull/235), [#236](https://github.com/MobileTeleSystems/onetl/pull/236), [#240](https://github.com/MobileTeleSystems/onetl/pull/240)) -- Add notes to Greenplum documentation about issues with IP resolution and building `gpfdist` URL ([#228](https://github.com/MobileTeleSystems/onetl/pull/228)) +- Добавлены примечания в документацию Greenplum о проблемах с разрешением IP-адресов и построением URL `gpfdist` ([#228](https://github.com/MobileTeleSystems/onetl/pull/228)) -- Allow calling `MongoDB.pipeline(...)` with passing just collection name, without explicit aggregation pipeline. ([#237](https://github.com/MobileTeleSystems/onetl/pull/237)) +- Разрешен вызов `MongoDB.pipeline(...)` с передачей только имени коллекции, без явного указания aggregation pipeline. ([#237](https://github.com/MobileTeleSystems/onetl/pull/237)) -- Update default `Postgres(extra={...})` to include `{"stringtype": "unspecified"}` option. - This allows to write text data to non-text column (or vice versa), relying to Postgres cast capabilities. +- Обновлен default `Postgres(extra={...})` для включения опции `{"stringtype": "unspecified"}`. Это позволяет записывать текстовые данные в нетекстовый столбец (или наоборот), полагаясь на возможности приведения типов Postgres. - For example, now it is possible to read column of type `money` as Spark's `StringType()`, and write it back to the same column, - without using intermediate columns or tables. ([#229](https://github.com/MobileTeleSystems/onetl/pull/229)) + Например, теперь можно прочитать столбец типа `money` как тип `StringType()` Apache Spark, и записать его обратно в тот же столбец, без использования промежуточных столбцов или таблиц. ([#229](https://github.com/MobileTeleSystems/onetl/pull/229)) -## Bug Fixes +## Исправление ошибок -- Return back handling of `DBReader(columns="string")`. This was a valid syntax up to v0.10 release, but it was removed because - most of users neved used it. It looks that we were wrong, returning this behavior back, but with deprecation warning. ([#238](https://github.com/MobileTeleSystems/onetl/pull/238)) +- Возвращена обработка `DBReader(columns="string")`. Это был валидный синтаксис до версии v0.10, но он был удален, потому что большинство пользователей им не пользовались. Похоже, мы ошибались, возвращаем это поведение обратно, но с предупреждением об устаревании. ([#238](https://github.com/MobileTeleSystems/onetl/pull/238)) -- Downgrade Greenplum package version from `2.3.0` to `2.2.0`. ([#239](https://github.com/MobileTeleSystems/onetl/pull/239)) +- Понижена версия пакета Greenplum с `2.3.0` до `2.2.0`. ([#239](https://github.com/MobileTeleSystems/onetl/pull/239)) - This is because version 2.3.0 introduced issues with writing data to Greenplum 6.x. - Connector can open transaction with `SELECT * FROM table LIMIT 0` query, but does not close it, which leads to deadlocks. + Это связано с тем, что версия 2.3.0 вызвала проблемы с записью данных в Greenplum 6.x. Коннектор может открыть транзакцию с запросом `SELECT * FROM table LIMIT 0`, но не закрывает ее, что приводит к deadlock'ам. - For using this connector with Greenplum 7.x, please pass package version explicitly: + Для использования этого коннектора с Greenplum 7.x, пожалуйста, передайте версию пакета явно: ```python maven_packages = Greenplum.get_packages(package_version="2.3.0", ...) diff --git a/mddocs/ru/changelog/0.11.0.md b/mddocs/ru/changelog/0.11.0.md index c77c977b0..4479d91ce 100644 --- a/mddocs/ru/changelog/0.11.0.md +++ b/mddocs/ru/changelog/0.11.0.md @@ -1,40 +1,41 @@ # 0.11.0 (2024-05-27) -## Breaking Changes +## Критические изменения -There can be some changes in connection behavior, related to version upgrades. So we mark these changes as **breaking** although -most of users will not see any differences. +Могут быть некоторые изменения в поведении подключения, связанные с обновлениями версий. Поэтому мы отмечаем такие изменения как **breaking**, хотя большинство пользователей не увидят никаких различий. -- Update Clickhouse JDBC driver to latest version ([#249](https://github.com/MobileTeleSystems/onetl/pull/249)): - - Package was renamed `ru.yandex.clickhouse:clickhouse-jdbc` → `com.clickhouse:clickhouse-jdbc`. - - Package version changed `0.3.2` → `0.6.0-patch5`. - - Driver name changed `ru.yandex.clickhouse.ClickHouseDriver` → `com.clickhouse.jdbc.ClickHouseDriver`. +- Обновление JDBC драйвера Clickhouse до последней версии ([#249](https://github.com/MobileTeleSystems/onetl/pull/249)): + - Пакет был переименован `ru.yandex.clickhouse:clickhouse-jdbc` → `com.clickhouse:clickhouse-jdbc`. + - Версия пакета изменена `0.3.2` → `0.6.0-patch5`. + - Имя драйвера изменено `ru.yandex.clickhouse.ClickHouseDriver` → `com.clickhouse.jdbc.ClickHouseDriver`. - This brings up several fixes for Spark \<-> Clickhouse type compatibility, and also Clickhouse clusters support. + Это приводит к необходимости нескольких изменений для совместимости типов Spark <-> Clickhouse, а также поддержки кластеров Clickhouse. -- Update other JDBC drivers to latest versions: +- Обновление других JDBC драйверов до последних версий: - MSSQL `12.2.0` → `12.6.2` ([#254](https://github.com/MobileTeleSystems/onetl/pull/254)). - MySQL `8.0.33` → `8.4.0` ([#253](https://github.com/MobileTeleSystems/onetl/pull/253), [#285](https://github.com/MobileTeleSystems/onetl/pull/285)). - Oracle `23.2.0.0` → `23.4.0.24.05` ([#252](https://github.com/MobileTeleSystems/onetl/pull/252), [#284](https://github.com/MobileTeleSystems/onetl/pull/284)). - Postgres `42.6.0` → `42.7.3` ([#251](https://github.com/MobileTeleSystems/onetl/pull/251)). -- Update MongoDB connector to latest version: `10.1.1` → `10.3.0` ([#255](https://github.com/MobileTeleSystems/onetl/pull/255), [#283](https://github.com/MobileTeleSystems/onetl/pull/283)). +- Обновление коннектора MongoDB до последней версии: `10.1.1` → `10.3.0` ([#255](https://github.com/MobileTeleSystems/onetl/pull/255), [#283](https://github.com/MobileTeleSystems/onetl/pull/283)). - This brings up Spark 3.5 support. + Благодаря которому появляется поддержка Spark 3.5. -- Update `XML` package to latest version: `0.17.0` → `0.18.0` ([#259](https://github.com/MobileTeleSystems/onetl/pull/259)). +- Обновление пакета `XML` до последней версии: `0.17.0` → `0.18.0` ([#259](https://github.com/MobileTeleSystems/onetl/pull/259)). - This brings few bugfixes with datetime format handling. + Которое устраняет несколько ошибок обработки формата datetime. -- For JDBC connections add new `SQLOptions` class for `DB.sql(query, options=...)` method ([#272](https://github.com/MobileTeleSystems/onetl/pull/272)). +- Для JDBC соединений добавлен новый класс `SQLOptions` для метода `DB.sql(query, options=...)` ([#272](https://github.com/MobileTeleSystems/onetl/pull/272)). - Firsly, to keep naming more consistent. + Благодаря этому: - Secondly, some of options are not supported by `DB.sql(...)` method, but supported by `DBReader`. - For example, `SQLOptions` do not support `partitioning_mode` and require explicit definition of `lower_bound` and `upper_bound` when `num_partitions` is greater than 1. - `ReadOptions` does support `partitioning_mode` and allows skipping `lower_bound` and `upper_bound` values. + Во-первых, лучше поддерживается согласованность именования. - This require some code changes. Before: + Во-вторых, некоторые опции не поддерживаются методом `DB.sql(...)`, но поддерживаются `DBReader`. + Например, `SQLOptions` не поддерживает `partitioning_mode` и требует явного определения `lower_bound` и `upper_bound`, когда `num_partitions` больше 1. + `ReadOptions` поддерживает `partitioning_mode` и позволяет пропускать значения `lower_bound` и `upper_bound`. + + Это требует некоторых изменений в коде. Раньше: ```python from onetl.connection import Postgres @@ -54,7 +55,7 @@ most of users will not see any differences. ) ``` - After: + Теперь: ```python from onetl.connection import Postgres @@ -76,15 +77,15 @@ most of users will not see any differences. ) ``` - For now, `DB.sql(query, options=...)` can accept `ReadOptions` to keep backward compatibility, but emits deprecation warning. - The support will be removed in `v1.0.0`. + На данный момент `DB.sql(query, options=...)` может принимать `ReadOptions` для сохранения обратной совместимости, но выдает предупреждение об устаревании. + Поддержка будет удалена в `v1.0.0`. -- Split up `JDBCOptions` class into `FetchOptions` and `ExecuteOptions` ([#274](https://github.com/MobileTeleSystems/onetl/pull/274)). +- Разделение класса `JDBCOptions` на `FetchOptions` и `ExecuteOptions` ([#274](https://github.com/MobileTeleSystems/onetl/pull/274)). - New classes are used by `DB.fetch(query, options=...)` and `DB.execute(query, options=...)` methods respectively. - This is mostly to keep naming more consistent. + Новые классы используются методами `DB.fetch(query, options=...)` и `DB.execute(query, options=...)` соответственно. + Это изменение необходимо в основном для большей консистентности именования. - This require some code changes. Before: + Оно также требует некоторых изменений в коде. Раньше: ```python from onetl.connection import Postgres @@ -104,7 +105,7 @@ most of users will not see any differences. ) ``` - After: + Теперь: ```python from onetl.connection import Postgres @@ -126,16 +127,14 @@ most of users will not see any differences. ) ``` - For now, `DB.fetch(query, options=...)` and `DB.execute(query, options=...)` can accept `JDBCOptions`, to keep backward compatibility, - but emit a deprecation warning. The old class will be removed in `v1.0.0`. + На данный момент `DB.fetch(query, options=...)` и `DB.execute(query, options=...)` могут принимать `JDBCOptions` для сохранения обратной совместимости, но выдают предупреждение об устаревании. Старый класс будет удален в `v1.0.0`. -- Serialize `ColumnDatetimeHWM` to Clickhouse's `DateTime64(6)` (precision up to microseconds) instead of `DateTime` (precision up to seconds) ([#267](https://github.com/MobileTeleSystems/onetl/pull/267)). +- Сериализация `ColumnDatetimeHWM` в тип `DateTime64(6)` Clickhouse (точность до микросекунд) вместо `DateTime` (точность до секунд) ([#267](https://github.com/MobileTeleSystems/onetl/pull/267)). - In previous onETL versions, `ColumnDatetimeHWM` value was rounded to the second, and thus reading some rows that were read in previous runs, - producing duplicates. + В предыдущих версиях onETL значение `ColumnDatetimeHWM` округлялось до секунды, и, таким образом, чтение некоторых строк, которые были прочитаны раньше, приводило к дублированию данных. - For Clickhouse versions below 21.1 comparing column of type `DateTime` with a value of type `DateTime64` is not supported, returning an empty dataframe. - To avoid this, replace: + Для версий Clickhouse ниже 21.1 сравнение столбца типа `DateTime` со значением типа `DateTime64` не поддерживается, возвращая пустой dataframe. + Чтобы избежать этого, замените: ```python DBReader( @@ -147,7 +146,7 @@ most of users will not see any differences. ) ``` - with: + на: ```python DBReader( @@ -159,16 +158,15 @@ most of users will not see any differences. ) ``` -- Pass JDBC connection extra params as `properties` dict instead of URL with query part ([#268](https://github.com/MobileTeleSystems/onetl/pull/268)). +- Передача дополнительных параметров подключения JDBC как словаря `properties` вместо URL с "query part" ([#268](https://github.com/MobileTeleSystems/onetl/pull/268)). - This allows passing custom connection parameters like `Clickhouse(extra={"custom_http_options": "option1=value1,option2=value2"})` - without need to apply urlencode to parameter value, like `option1%3Dvalue1%2Coption2%3Dvalue2`. + Это позволяет передавать пользовательские параметры соединения, такие как `Clickhouse(extra={"custom_http_options": "option1=value1,option2=value2"})` без необходимости применять urlencode к значению параметра, например `option1%3Dvalue1%2Coption2%3Dvalue2`. -## Features +## Функциональность -Improve user experience with Kafka messages and Database tables with serialized columns, like JSON/XML. +Улучшение пользовательского опыта при работе с сообщениями Kafka и таблицами баз данных, содержащими сериализованные столбцы, такие как JSON или XML. -- Allow passing custom package version as argument for `DB.get_packages(...)` method of several DB connectors: +- Разрешена передача пользовательской версии пакета в качестве аргумента для метода `DB.get_packages(...)` нескольких коннекторах к БД: - `Clickhouse.get_packages(package_version=..., apache_http_client_version=...)` ([#249](https://github.com/MobileTeleSystems/onetl/pull/249)). - `MongoDB.get_packages(scala_version=..., spark_version=..., package_version=...)` ([#255](https://github.com/MobileTeleSystems/onetl/pull/255)). - `MySQL.get_packages(package_version=...)` ([#253](https://github.com/MobileTeleSystems/onetl/pull/253)). @@ -176,37 +174,37 @@ Improve user experience with Kafka messages and Database tables with serialized - `Oracle.get_packages(java_version=..., package_version=...)` ([#252](https://github.com/MobileTeleSystems/onetl/pull/252)). - `Postgres.get_packages(package_version=...)` ([#251](https://github.com/MobileTeleSystems/onetl/pull/251)). - `Teradata.get_packages(package_version=...)` ([#256](https://github.com/MobileTeleSystems/onetl/pull/256)). - Now users can downgrade or upgrade connection without waiting for next onETL release. Previously only `Kafka` and `Greenplum` supported this feature. -- Add `FileFormat.parse_column(...)` method to several classes: + Теперь пользователи могут понижать или повышать версию подключения, не дожидаясь следующего релиза onETL. Раньше такую возможность поддерживали только `Kafka` и `Greenplum`. +- Добавлен метод `FileFormat.parse_column(...)` в несколько классов: - `Avro.parse_column(col)` ([#265](https://github.com/MobileTeleSystems/onetl/pull/265)). - `JSON.parse_column(col, schema=...)` ([#257](https://github.com/MobileTeleSystems/onetl/pull/257)). - `CSV.parse_column(col, schema=...)` ([#258](https://github.com/MobileTeleSystems/onetl/pull/258)). - `XML.parse_column(col, schema=...)` ([#269](https://github.com/MobileTeleSystems/onetl/pull/269)). - This allows parsing data in `value` field of Kafka message or string/binary column of some table as a nested Spark structure. -- Add `FileFormat.serialize_column(...)` method to several classes: + Это позволяет анализировать данные в поле `value` сообщения Kafka или строковый/двоичный столбец некоторой таблицы как вложенную структуру Spark. +- Добавлен метод `FileFormat.serialize_column(...)` в несколько классов: - `Avro.serialize_column(col)` ([#265](https://github.com/MobileTeleSystems/onetl/pull/265)). - `JSON.serialize_column(col)` ([#257](https://github.com/MobileTeleSystems/onetl/pull/257)). - `CSV.serialize_column(col)` ([#258](https://github.com/MobileTeleSystems/onetl/pull/258)). - This allows saving Spark nested structures or arrays to `value` field of Kafka message or string/binary column of some table. + Это позволяет сохранять вложенные структуры Spark или массивы в поле `value` сообщения Kafka или строковый/двоичный столбец некоторой таблицы. -## Improvements +## Улучшения -Few documentation improvements. +Несколько улучшений документации. -- Replace all `assert` in documentation with doctest syntax. This should make documentation more readable ([#273](https://github.com/MobileTeleSystems/onetl/pull/273)). -- Add generic `Troubleshooting` guide ([#275](https://github.com/MobileTeleSystems/onetl/pull/275)). -- Improve Kafka documentation: - - Add "Prerequisites" page describing different aspects of connecting to Kafka. - - Improve "Reading from" and "Writing to" page of Kafka documentation, add more examples and usage notes. - - Add "Troubleshooting" page ([#276](https://github.com/MobileTeleSystems/onetl/pull/276)). -- Improve Hive documentation: - - Add "Prerequisites" page describing different aspects of connecting to Hive. - - Improve "Reading from" and "Writing to" page of Hive documentation, add more examples and recommendations. - - Improve "Executing statements in Hive" page of Hive documentation. ([#278](https://github.com/MobileTeleSystems/onetl/pull/278)). -- Add "Prerequisites" page describing different aspects of using SparkHDFS and SparkS3 connectors. ([#279](https://github.com/MobileTeleSystems/onetl/pull/279)). -- Add note about connecting to Clickhouse cluster. ([#280](https://github.com/MobileTeleSystems/onetl/pull/280)). -- Add notes about versions when specific class/method/attribute/argument was added, renamed or changed behavior ([#282](https://github.com/MobileTeleSystems/onetl/pull/282)). +- `assert` заменены синтаксисом doctest. Это должно сделать документацию более читаемой ([#273](https://github.com/MobileTeleSystems/onetl/pull/273)). +- Добавлено общее руководство `Troubleshooting` ([#275](https://github.com/MobileTeleSystems/onetl/pull/275)). +- Улучшена документация Kafka: + - Добавлена страница "Prerequisites", описывающая различные аспекты подключения к Kafka. + - Дополнительные примеры и примечания добавлены на страницы "Reading from" и "Writing to" документации Kafka. + - Добавлена страница "Troubleshooting" ([#276](https://github.com/MobileTeleSystems/onetl/pull/276)). +- Улучшена документация Hive: + - Добавлена страница "Prerequisites", описывающая различные аспекты подключения к Hive. + - Дополнительные примеры и примечания добавлены на страницы "Reading from" и "Writing to" документации Hive. + - Страница "Executing statements in Hive" документации Hive стала более содержательной. ([#278](https://github.com/MobileTeleSystems/onetl/pull/278)). +- Добавлена страница "Prerequisites", описывающая различные аспекты использования коннекторов SparkHDFS и SparkS3. ([#279](https://github.com/MobileTeleSystems/onetl/pull/279)). +- Добавлены примечания о подключении к кластеру Clickhouse. ([#280](https://github.com/MobileTeleSystems/onetl/pull/280)). +- Добавлены примечания о версиях, когда был добавлен, переименован или изменен класс/метод/атрибут/аргумент ([#282](https://github.com/MobileTeleSystems/onetl/pull/282)). -## Bug Fixes +## Исправление ошибок -- Fix missing `pysmb` package after installing `pip install onetl[files]` . +- После установки `pip install onetl[files]` ранее отсутствовал пакет `pysmb`. Теперь этот недостаток устранен. diff --git a/mddocs/ru/changelog/0.11.1.md b/mddocs/ru/changelog/0.11.1.md index 823afe3be..2aebc25e1 100644 --- a/mddocs/ru/changelog/0.11.1.md +++ b/mddocs/ru/changelog/0.11.1.md @@ -1,9 +1,10 @@ # 0.11.1 (2024-05-29) -## Features +## Функциональность -- Change `MSSQL.port` default from `1433` to `None`, allowing use of `instanceName` to detect port number. ([#287](https://github.com/MobileTeleSystems/onetl/pull/287)) +- Изменено значение по умолчанию атрибута `MSSQL.port` с `1433` на `None`, что позволяет использовать `instanceName` для определения номера порта. ([#287](https://github.com/MobileTeleSystems/onetl/pull/287)) -## Bug Fixes +## Исправление ошибок + +- Удален `fetchsize` из `JDBC.WriteOptions`. ([#288](https://github.com/MobileTeleSystems/onetl/pull/288)) -- Remove `fetchsize` from `JDBC.WriteOptions`. ([#288](https://github.com/MobileTeleSystems/onetl/pull/288)) diff --git a/mddocs/ru/changelog/0.11.2.md b/mddocs/ru/changelog/0.11.2.md index 9278d22f8..bf7e1cc2d 100644 --- a/mddocs/ru/changelog/0.11.2.md +++ b/mddocs/ru/changelog/0.11.2.md @@ -1,5 +1,5 @@ # 0.11.2 (2024-09-02) -## Bug Fixes +## Исправление ошибок -- Fix passing `Greenplum(extra={"options": ...})` during read/write operations. ([#308](https://github.com/MobileTeleSystems/onetl/pull/308)) +- Исправлена передача `Greenplum(extra={"options": ...})` во время операций чтения/записи. ([#308](https://github.com/MobileTeleSystems/onetl/pull/308)) diff --git a/mddocs/ru/changelog/0.12.0.md b/mddocs/ru/changelog/0.12.0.md index aeaca1d23..10def116b 100644 --- a/mddocs/ru/changelog/0.12.0.md +++ b/mddocs/ru/changelog/0.12.0.md @@ -1,54 +1,51 @@ # 0.12.0 (2024-09-03) -## Breaking Changes +## Критические изменения -- Change connection URL used for generating HWM names of S3 and Samba sources: +- Изменен URL-адрес подключения, используемый для генерации HWM-имен источников S3 и Samba: - `smb://host:port` -> `smb://host:port/share` - `s3://host:port` -> `s3://host:port/bucket` ([#304](https://github.com/MobileTeleSystems/onetl/pull/304)) -- Update DB connectors/drivers to latest versions: +- Обновлены коннекторы/драйверы БД до последних версий: - Clickhouse `0.6.0-patch5` → `0.6.5` - MongoDB `10.3.0` → `10.4.0` - MSSQL `12.6.2` → `12.8.1` - MySQL `8.4.0` → `9.0.0` - Oracle `23.4.0.24.05` → `23.5.0.24.07` - Postgres `42.7.3` → `42.7.4` -- Update `Excel` package from `0.20.3` to `0.20.4`, to include Spark 3.5.1 support. ([#306](https://github.com/MobileTeleSystems/onetl/pull/306)) +- Обновлен пакет `Excel` с `0.20.3` до `0.20.4`, для включения поддержки Spark 3.5.1. ([#306](https://github.com/MobileTeleSystems/onetl/pull/306)) -## Features +## Функциональность -- Add support for specifying file formats (`ORC`, `Parquet`, `CSV`, etc.) in `HiveWriteOptions.format` ([#292](https://github.com/MobileTeleSystems/onetl/pull/292)): +- Добавлена поддержка указания форматов файлов (`ORC`, `Parquet`, `CSV` и т.д.) в `HiveWriteOptions.format` ([#292](https://github.com/MobileTeleSystems/onetl/pull/292)): ```python Hive.WriteOptions(format=ORC(compression="snappy")) ``` -- Collect Spark execution metrics in following methods, and log then in DEBUG mode: +- Сбор метрик выполнения Spark и их логирование в режиме DEBUG в следующих методах: - `DBWriter.run()` - `FileDFWriter.run()` - `Hive.sql()` - `Hive.execute()` - This is implemented using custom `SparkListener` which wraps the entire method call, and - then report collected metrics. But these metrics sometimes may be missing due to Spark architecture, - so they are not reliable source of information. That's why logs are printed only in DEBUG mode, and - are not returned as method call result. ([#303](https://github.com/MobileTeleSystems/onetl/pull/303)) + Это реализовано с использованием пользовательского `SparkListener`, который оборачивает весь вызов метода, и затем сообщает собранные метрики. Но эти метрики иногда могут отсутствовать из-за архитектуры Spark, и таким образом они не являются надежным источником информации. Поэтому логи выводятся только в режиме DEBUG, и не возвращаются как результат вызова метода. ([#303](https://github.com/MobileTeleSystems/onetl/pull/303)) -- Generate default `jobDescription` based on currently executed method. Examples: +- Генерация `jobDescription` по умолчанию на основе текущего выполняемого метода. Примеры: - `DBWriter.run(schema.table) -> Postgres[host:5432/database]` - `MongoDB[localhost:27017/admin] -> DBReader.has_data(mycollection)` - `Hive[cluster].execute()` - If user already set custom `jobDescription`, it will left intact. ([#304](https://github.com/MobileTeleSystems/onetl/pull/304)) + Если пользователь уже установил пользовательский `jobDescription`, он останется нетронутым. ([#304](https://github.com/MobileTeleSystems/onetl/pull/304)) -- Add log.info about JDBC dialect usage ([#305](https://github.com/MobileTeleSystems/onetl/pull/305)): +- Добавлен log.info об использовании диалекта JDBC ([#305](https://github.com/MobileTeleSystems/onetl/pull/305)): ```text |MySQL| Detected dialect: 'org.apache.spark.sql.jdbc.MySQLDialect' ``` -- Log estimated size of in-memory dataframe created by `JDBC.fetch` and `JDBC.execute` methods. ([#303](https://github.com/MobileTeleSystems/onetl/pull/303)) +- Логирование оценочного размера dataframe в памяти, созданного методами `JDBC.fetch` и `JDBC.execute`. ([#303](https://github.com/MobileTeleSystems/onetl/pull/303)) -## Bug Fixes +## Исправление ошибок -- Fix passing `Greenplum(extra={"options": ...})` during read/write operations. ([#308](https://github.com/MobileTeleSystems/onetl/pull/308)) -- Do not raise exception if yield-based hook whas something past (and only one) `yield`. +- Исправлена передача `Greenplum(extra={"options": ...})` во время операций чтения/записи. ([#308](https://github.com/MobileTeleSystems/onetl/pull/308)) +- Не вызывается исключение, если хук на базе yield выдал что-то единственное после `yield`. diff --git a/mddocs/ru/changelog/0.12.1.md b/mddocs/ru/changelog/0.12.1.md index d32401517..4e97ad677 100644 --- a/mddocs/ru/changelog/0.12.1.md +++ b/mddocs/ru/changelog/0.12.1.md @@ -1,17 +1,18 @@ # 0.12.1 (2024-10-28) -## Features +## Функциональность -- Log detected JDBC dialect while using `DBWriter`. +- При использовании `DBWriter` логируется обнаруженный диалект JDBC. -## Bug Fixes +## Исправление ошибок -- Fix `SparkMetricsRecorder` failing when receiving `SparkListenerTaskEnd` without `taskMetrics` (e.g. executor was killed by OOM). ([#313](https://github.com/MobileTeleSystems/onetl/pull/313)) -- Call `kinit` before checking for HDFS active namenode. -- Wrap `kinit` with `threading.Lock` to avoid multithreading issues. -- Immediately show `kinit` errors to user, instead of hiding them. -- Use `AttributeError` instead of `ImportError` in module's `__getattr__` method, to make code compliant with Python spec. +- Исправлена ошибка `SparkMetricsRecorder`, возникающая при получении `SparkListenerTaskEnd` без `taskMetrics` (например, executor был убит из-за OOM). ([#313](https://github.com/MobileTeleSystems/onetl/pull/313)) +- Вызывается `kinit` перед проверкой активного namenode HDFS. +- `kinit` обернут в `threading.Lock`, чтобы избежать проблем с многопоточностью. +- Ошибки `kinit` немедленно отображаюся пользователю, вместо того, чтобы скрывать их. +- `AttributeError` используется вместо `ImportError` в методе `__getattr__` модуля, чтобы код соответствовал спецификации Python. -## Doc only Changes +## Изменения в документации (только) + +- Добавлено примечание о пакете [spark-dialect-extension](https://github.com/MobileTeleSystems/spark-dialect-extension) в документацию коннектора Clickhouse. ([#310](https://github.com/MobileTeleSystems/onetl/pull/310)) -- Add note about [spark-dialect-extension](https://github.com/MobileTeleSystems/spark-dialect-extension) package to Clickhouse connector documentation. ([#310](https://github.com/MobileTeleSystems/onetl/pull/310)) diff --git a/mddocs/ru/changelog/0.12.2.md b/mddocs/ru/changelog/0.12.2.md index 23a8d383d..28f9edb84 100644 --- a/mddocs/ru/changelog/0.12.2.md +++ b/mddocs/ru/changelog/0.12.2.md @@ -1,18 +1,19 @@ # 0.12.2 (2024-11-12) -## Improvements +## Улучшения -- Change Spark `jobDescription` for DBReader & FileDFReader from `DBReader.run() -> Connection` to `Connection -> DBReader.run()`. +- Изменен Spark `jobDescription` для DBReader и FileDFReader с `DBReader.run() -> Connection` на `Connection -> DBReader.run()`. -## Bug Fixes +## Исправления ошибок -- Fix `log_hwm` result for `KeyValueIntHWM` (used by Kafka). ([#316](https://github.com/MobileTeleSystems/onetl/pull/316)) -- Fix `log_collection` hiding values of `Kafka.addresses` in logs with `INFO` level. ([#316](https://github.com/MobileTeleSystems/onetl/pull/316)) +- Исправлен результат `log_hwm` для `KeyValueIntHWM` (используется Kafka). ([#316](https://github.com/MobileTeleSystems/onetl/pull/316)) +- Исправлено сокрытие значений `Kafka.addresses` в логах с уровнем `INFO` в `log_collection`. ([#316](https://github.com/MobileTeleSystems/onetl/pull/316)) -## Dependencies +## Зависимости -- Allow using [etl-entities==2.4.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.4.0). +- Разрешено использование [etl-entities==2.4.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.4.0). -## Doc only Changes +## Изменения только в документации + +- Исправлены ссылки на документацию по типам даты и времени MSSQL. -- Fix links to MSSQL date & time type documentation. diff --git a/mddocs/ru/changelog/0.12.3.md b/mddocs/ru/changelog/0.12.3.md index 741c74e1f..ff44131b8 100644 --- a/mddocs/ru/changelog/0.12.3.md +++ b/mddocs/ru/changelog/0.12.3.md @@ -1,5 +1,5 @@ # 0.12.3 (2024-11-22) -## Bug Fixes +## Исправление ошибок -- Allow passing table names in format `schema."table.with.dots"` to `DBReader(source=...)` and `DBWriter(target=...)`. +- Разрешена передача имен таблиц в формате `schema."table.with.dots"` для `DBReader(source=...)` и `DBWriter(target=...)`. diff --git a/mddocs/ru/changelog/0.12.4.md b/mddocs/ru/changelog/0.12.4.md index 3ebc57a87..9ab18b4d7 100644 --- a/mddocs/ru/changelog/0.12.4.md +++ b/mddocs/ru/changelog/0.12.4.md @@ -1,5 +1,5 @@ # 0.12.4 (2024-11-27) -## Bug Fixes +## Исправление ошибок -- Fix `DBReader(conn=oracle, options={"partitioning_mode": "hash"})` lead to data skew in last partition due to wrong `ora_hash` usage. ([#319](https://github.com/MobileTeleSystems/onetl/pull/319)) +- Исправлен `DBReader(conn=oracle, options={"partitioning_mode": "hash"})` приводивший к перекосу данных в последней партиции из-за некорректного использования `ora_hash`. ([#319](https://github.com/MobileTeleSystems/onetl/pull/319)) diff --git a/mddocs/ru/changelog/0.12.5.md b/mddocs/ru/changelog/0.12.5.md index c542a50fd..2e4855a8d 100644 --- a/mddocs/ru/changelog/0.12.5.md +++ b/mddocs/ru/changelog/0.12.5.md @@ -1,13 +1,13 @@ # 0.12.5 (2024-12-03) -## Improvements +## Улучшения -- Use `sipHash64` instead of `md5` in Clickhouse for reading data with `{"partitioning_mode": "hash"}`, as it is 5 times faster. -- Use `hashtext` instead of `md5` in Postgres for reading data with `{"partitioning_mode": "hash"}`, as it is 3-5 times faster. -- Use `BINARY_CHECKSUM` instead of `HASHBYTES` in MSSQL for reading data with `{"partitioning_mode": "hash"}`, as it is 5 times faster. +- `sipHash64` используется вместо `md5` в Clickhouse для чтения данных с `{"partitioning_mode": "hash"}`, так как это в 5 раз быстрее. +- `hashtext` используется вместо `md5` в Postgres для чтения данных с `{"partitioning_mode": "hash"}`, так как это в 3-5 раз быстрее. +- `BINARY_CHECKSUM` используется вместо `HASHBYTES` в MSSQL для чтения данных с `{"partitioning_mode": "hash"}`, так как это в 5 раз быстрее. -## Big fixes +## Исправления -- In JDBC sources wrap `MOD(partitionColumn, numPartitions)` with `ABS(...)` to make al returned values positive. This prevents data skew. -- Fix reading table data from MSSQL using `{"partitioning_mode": "hash"}` with `partitionColumn` of integer type. -- Fix reading table data from Postgres using `{"partitioning_mode": "hash"}` lead to data skew (all the data was read into one Spark partition). +- Для JDBC источников `MOD(partitionColumn, numPartitions)` оборачивается в `ABS(...)`, чтобы все возвращаемые значения были положительными. Это предотвращает перекос данных. +- Исправлено чтение данных из MSSQL с использованием `{"partitioning_mode": "hash"}` в случае `partitionColumn` целочисленного типа. +- Исправлено чтение данных из Postgres с использованием `{"partitioning_mode": "hash"}` приводившее к перекосу данных (все данные считывались в один раздел Spark). diff --git a/mddocs/ru/changelog/0.13.0.md b/mddocs/ru/changelog/0.13.0.md index 37ae8368a..951019bf7 100644 --- a/mddocs/ru/changelog/0.13.0.md +++ b/mddocs/ru/changelog/0.13.0.md @@ -1,22 +1,20 @@ # 0.13.0 (2025-02-24) -🎉 3 years since first release 0.1.0 🎉 +🎉 3 года с первого релиза 0.1.0 🎉 -## Breaking Changes +## Критические изменения -- Add Python 3.13. support. ([#298](https://github.com/MobileTeleSystems/onetl/pull/298)) +- Добавлена поддержка Python 3.13. ([#298](https://github.com/MobileTeleSystems/onetl/pull/298)) -- Change the logic of `FileConnection.walk` and `FileConnection.list_dir`. ([#327](https://github.com/MobileTeleSystems/onetl/pull/327)) +- Изменена логика `FileConnection.walk` и `FileConnection.list_dir`. ([#327](https://github.com/MobileTeleSystems/onetl/pull/327)) - Previously `limits.stops_at(path) == True` considered as "return current file and stop", and could lead to exceeding some limit. - Not it means "stop immediately". + Ранее `limits.stops_at(path) == True` выполнялось как "вернуть текущий файл и остановиться", и могло привести к превышению некоторого лимита. Теперь это означает "остановиться немедленно". -- Change default value for `FileDFWriter.Options(if_exists=...)` from `error` to `append`, - to make it consistent with other `.Options()` classes within onETL. ([#343](https://github.com/MobileTeleSystems/onetl/pull/343)) +- Изменено значение по умолчанию для `FileDFWriter.Options(if_exists=...)` с `error` на `append`, чтобы соответствовать другим классам `.Options()` внутри onETL. ([#343](https://github.com/MobileTeleSystems/onetl/pull/343)) -## Features +## Функциональность -- Add support for `FileModifiedTimeHWM` HWM class (see [etl-entities 2.5.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.5.0)): +- Добавлена поддержка для класса HWM `FileModifiedTimeHWM` (см. [etl-entities 2.5.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.5.0)): ```python from etl_entitites.hwm import FileModifiedTimeHWM @@ -32,9 +30,9 @@ downloader.run() ``` -- Introduce `FileSizeRange(min=..., max=...)` filter class. ([#325](https://github.com/MobileTeleSystems/onetl/pull/325)) +- Добавлен класс фильтра `FileSizeRange(min=..., max=...)`. ([#325](https://github.com/MobileTeleSystems/onetl/pull/325)) - Now users can set `FileDownloader` / `FileMover` to download/move only files with specific file size range: + Теперь пользователи могут настроить `FileDownloader` / `FileMover` для скачивания/перемещения только файлов с определенным диапазоном их размеров: ```python from onetl.file import FileDownloader @@ -46,9 +44,9 @@ ) ``` -- Introduce `TotalFilesSize(...)` limit class. ([#326](https://github.com/MobileTeleSystems/onetl/pull/326)) +- Добавлен класс лимита `TotalFilesSize(...)`. ([#326](https://github.com/MobileTeleSystems/onetl/pull/326)) - Now users can set `FileDownloader` / `FileMover` to stop downloading/moving files after reaching a certain amount of data: + Теперь пользователи могут настроить `FileDownloader` / `FileMover` для остановки скачивания/перемещения файлов после достижения определенного объема обработанных данных: ```python from datetime import datetime, timedelta @@ -61,9 +59,9 @@ ) ``` -- Implement `FileModifiedTime(since=..., until=...)` file filter. ([#330](https://github.com/MobileTeleSystems/onetl/pull/330)) +- Реализован фильтр файлов `FileModifiedTime(since=..., until=...)`. ([#330](https://github.com/MobileTeleSystems/onetl/pull/330)) - Now users can set `FileDownloader` / `FileMover` to download/move only files with specific file modification time: + Теперь пользователи могут настроить `FileDownloader` / `FileMover` для скачивания/перемещения только файлов с определенным временем изменения: ```python from datetime import datetime, timedelta @@ -76,9 +74,9 @@ ) ``` -- Add `SparkS3.get_exclude_packages()` and `Kafka.get_exclude_packages()` methods. ([#341](https://github.com/MobileTeleSystems/onetl/pull/341)) +- Добавлены методы `SparkS3.get_exclude_packages()` и `Kafka.get_exclude_packages()`. ([#341](https://github.com/MobileTeleSystems/onetl/pull/341)) - Using them allows to skip downloading dependencies not required by this specific connector, or which are already a part of Spark/PySpark: + Их использование позволяет пропустить скачивание зависимостей, не требующихся этим конкретным коннекторам, или таких, которые уже являются частью Spark/PySpark: ```python from onetl.connection import SparkS3, Kafka @@ -96,31 +94,28 @@ ) ``` -## Improvements +## Улучшения -- All DB connections opened by `JDBC.fetch(...)`, `JDBC.execute(...)` or `JDBC.check()` - are immediately closed after the statements is executed. ([#334](https://github.com/MobileTeleSystems/onetl/pull/334)) +- Все подключения к БД, открытые `JDBC.fetch(...)`, `JDBC.execute(...)` или `JDBC.check()`, немедленно закрываются после выполнения операторов. ([#334](https://github.com/MobileTeleSystems/onetl/pull/334)) - Previously Spark session with `master=local[3]` actually opened up to 5 connections to target DB - one for `JDBC.check()`, - another for Spark driver interaction with DB to create tables, and one for each Spark executor. Now only max 4 connections are opened, - as `JDBC.check()` does not hold opened connection. + Ранее Spark сессия с `master=local[3]` фактически открывала до 5 соединений: одно для `JDBC.check()`, другое для взаимодействия драйвера Spark с БД для создания таблиц, и по одному для каждого Spark executor. Теперь открывается максимум 4 соединения, так как `JDBC.check()` не удерживает открытое соединение. - This is important for RDBMS like Postgres or Greenplum where number of connections is strictly limited and limit is usually quite low. + Это важно для СУБД, таких как Postgres или Greenplum, где количество соединений строго ограничено, и лимит обычно довольно низкий. -- Set up `ApplicationName` (client info) for Clickhouse, MongoDB, MSSQL, MySQL and Oracle. ([#339](https://github.com/MobileTeleSystems/onetl/pull/339), [#248](https://github.com/MobileTeleSystems/onetl/pull/248)) +- Настроен `ApplicationName` (client info) для Clickhouse, MongoDB, MSSQL, MySQL и Oracle. ([#339](https://github.com/MobileTeleSystems/onetl/pull/339), [#248](https://github.com/MobileTeleSystems/onetl/pull/248)) - Also update `ApplicationName` format for Greenplum, Postgres, Kafka and SparkS3. - Now all connectors have the same `ApplicationName` format: `${spark.applicationId} ${spark.appName} onETL/${onetl.version} Spark/${spark.version}` + Обновлен формат `ApplicationName` для Greenplum, Postgres, Kafka и SparkS3. + Теперь все коннекторы имеют одинаковый формат `ApplicationName`: `${spark.applicationId} ${spark.appName} onETL/${onetl.version} Spark/${spark.version}` - The only connections not sending `ApplicationName` are Teradata and FileConnection implementations. + Единственные подключения, не отправляющие `ApplicationName`, - это реализации Teradata и FileConnection. -- Now `DB.check()` will test connection availability not only on Spark driver, but also from some Spark executor. ([#346](https://github.com/MobileTeleSystems/onetl/pull/346)) +- Теперь `DB.check()` будет проверять доступность соединения не только на драйвере Spark, но и на одном из экзекуторов Spark. ([#346](https://github.com/MobileTeleSystems/onetl/pull/346)) - This allows to fail immediately if Spark driver host has network access to target DB, but Spark executors have not. + Это позволяет немедленно завершиться с ошибкой, если хост драйвера Spark имеет сетевой доступ к целевой БД, а экзекутор Spark - нет. !!! note - Now ``Greenplum.check()`` requires the same user grants as ``DBReader(connection=greenplum)``: + Теперь ``Greenplum.check()`` требует тех же прав пользователя, что и ``DBReader(connection=greenplum)``: ```sql -- yes, "writable" for reading data from GP, it's not a mistake @@ -130,14 +125,14 @@ -- ALTER USER username CREATEEXTTABLE(type = 'readable', protocol = 'gpfdist') CREATEEXTTABLE(type = 'writable', protocol = 'gpfdist'); ``` - Please ask your Greenplum administrators to provide these grants. + Пожалуйста, попросите ваших администраторов Greenplum предоставить эти права. -## Bug Fixes +## Исправление ошибок -- Avoid suppressing Hive Metastore errors while using `DBWriter`. ([#329](https://github.com/MobileTeleSystems/onetl/pull/329)) +- Предотвращено подавление ошибок Hive Metastore при использовании `DBWriter`. ([#329](https://github.com/MobileTeleSystems/onetl/pull/329)) - Previously this was implemented as: + Ранее это было реализовано как: ```python try: @@ -147,53 +142,47 @@ table_exists = False ``` - If Hive Metastore was overloaded and responded with an exception, it was considered as non-existing table, resulting - to full table override instead of append or override only partitions subset. + Если Hive Metastore был перегружен и отвечал исключением, считалось, что таблица не существует и это приводило к ее полному переопределению, вместо добавления или переопределения только подмножества разделов. -- Fix using onETL to write data to PostgreSQL or Greenplum instances behind *pgbouncer* with `pool_mode=transaction`. ([#336](https://github.com/MobileTeleSystems/onetl/pull/336)) +- Исправлено использование onETL для записи данных в экземпляры PostgreSQL или Greenplum, использующих *pgbouncer* с `pool_mode=transaction`. ([#336](https://github.com/MobileTeleSystems/onetl/pull/336)) - Previously `Postgres.check()` opened a read-only transaction, pgbouncer changed the entire connection type from read-write to read-only, - and when `DBWriter.run(df)` executed in read-only connection, producing errors like: + Ранее `Postgres.check()` открывал транзакцию только для чтения, pgbouncer изменял весь тип соединения с "чтения-записи" на "только для чтения", и когда `DBWriter.run(df)` выполнялся в соединении только для чтения, возникали ошибки, такие как: ``` org.postgresql.util.PSQLException: ERROR: cannot execute INSERT in a read-only transaction org.postgresql.util.PSQLException: ERROR: cannot execute TRUNCATE TABLE in a read-only transaction ``` - Added a workaround by passing `readOnly=True` to JDBC params for read-only connections, so pgbouncer may differ read-only and read-write connections properly. + Добавлено обходное решение путем передачи `readOnly=True` в параметры JDBC для соединений только для чтения, чтобы pgbouncer мог правильно различать соединения "только для чтения" и "чтения-записи". - After upgrading onETL 0.13.x or higher the same error still may appear of pgbouncer still holds read-only connections and returns them for DBWriter. - To this this, user can manually convert read-only connection to read-write: + После обновления onETL 0.13.x или выше та же ошибка все еще может появляться, если pgbouncer все еще удерживает соединения "только для чтения" и возвращает их для DBWriter. Чтобы это исправить, пользователь может вручную преобразовать соединение "только для чтения" в соединение "чтения-записи": ```python postgres.execute("BEGIN READ WRITE;") # <-- add this line DBWriter(...).run() ``` - After all connections in pgbouncer pool were converted from read-only to read-write, and error fixed, this additional line could be removed. + После того, как все соединения в пуле pgbouncer были преобразованы из "только для чтения" в "чтение-запись", и ошибка исправлена, эту дополнительную строку можно удалить. - See [Postgres JDBC driver documentation](https://jdbc.postgresql.org/documentation/use/). + См. [Документацию драйвера Postgres JDBC](https://jdbc.postgresql.org/documentation/use/). -- Fix `MSSQL.fetch(...)` and `MySQL.fetch(...)` opened a read-write connection instead of read-only. ([#337](https://github.com/MobileTeleSystems/onetl/pull/337)) +- Исправлены `MSSQL.fetch(...)` и `MySQL.fetch(...)`, открывавшие соединение "чтения-записи" вместо соединения "только для чтения". ([#337](https://github.com/MobileTeleSystems/onetl/pull/337)) - Now this is fixed: - : - `MSSQL.fetch(...)` establishes connection with `ApplicationIntent=ReadOnly`. - - `MySQL.fetch(...)` calls `SET SESSION TRANSACTION READ ONLY` statement. + Теперь: + - `MSSQL.fetch(...)` устанавливает соединение с `ApplicationIntent=ReadOnly`. + - `MySQL.fetch(...)` вызывает оператор `SET SESSION TRANSACTION READ ONLY`. -- Fixed passing multiple filters to `FileDownloader` and `FileMover`. ([#338](https://github.com/MobileTeleSystems/onetl/pull/338)) - If was caused by sorting filters list in internal logging method, but `FileFilter` subclasses are not sortable. +- Исправлена передача нескольких фильтров в `FileDownloader` и `FileMover`. ([#338](https://github.com/MobileTeleSystems/onetl/pull/338)) Необходимость этого была вызвана сортировкой списка фильтров во внутреннем методе ведения журнала, но подклассы `FileFilter` не поддерживают сортировку. -- Fix a false warning about a lof of parallel connections to Grenplum. ([#342](https://github.com/MobileTeleSystems/onetl/pull/342)) +- Исправлено ложное предупреждение о большом количестве параллельных соединений с Grenplum. ([#342](https://github.com/MobileTeleSystems/onetl/pull/342)) - Creating Spark session with `.master("local[5]")` may open up to 6 connections to Greenplum (=number of Spark executors + 1 for driver), - but onETL instead used number of *CPU cores* on the host as a number of parallel connections. + Создание сессии Spark с `.master("local[5]")` может открыть до 6 соединений с Greenplum (количество экзекуторов Spark + 1 для драйвера), но onETL вместо этого использовала количество *ядер CPU* на хосте в качестве количества параллельных соединений. - This lead to showing a false warning that number of Greenplum connections is too high, - which actually should be the case only if number of executors is higher than 30. + Это приводило к отображению ложного предупреждения о том, что количество соединений Greenplum слишком велико, что на самом деле должно быть только в том случае, если количество экзекуторов превышает 30. -- Fix MongoDB trying to use current database name as `authSource`. ([#347](https://github.com/MobileTeleSystems/onetl/pull/347)) +- Исправлена ошибка, из-за которой подключение к MongoDB пыталось использовать имя указанной базы данных в качестве `authSource`. ([#347](https://github.com/MobileTeleSystems/onetl/pull/347)) - Use default connector value which is `admin` database. Previous onETL versions could be fixed by: + Теперь используется значение коннектора по умолчанию, т.е. `admin`. Предыдущие версии onETL можно исправить следующим образом: ```python from onetl.connection import MongoDB @@ -207,16 +196,16 @@ ) ``` -## Dependencies +## Зависимости -- Minimal `etl-entities` version is now [2.5.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.5.0). ([#331](https://github.com/MobileTeleSystems/onetl/pull/331)) -- Update DB connectors/drivers to latest versions: ([#345](https://github.com/MobileTeleSystems/onetl/pull/345)) - : - Clickhouse `0.6.5` → `0.7.2` +- Минимальная версия `etl-entities` теперь [2.5.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.5.0). ([#331](https://github.com/MobileTeleSystems/onetl/pull/331)) +- До последних версий обновлены коннекторы/драйверы БД: ([#345](https://github.com/MobileTeleSystems/onetl/pull/345)) + - Clickhouse `0.6.5` → `0.7.2` - MongoDB `10.4.0` → `10.4.1` - MySQL `9.0.0` → `9.2.0` - Oracle `23.5.0.24.07` → `23.7.0.25.01` - Postgres `42.7.4` → `42.7.5` -## Doc only Changes +## Изменения только в документации -- Split large code examples to tabs. ([#344](https://github.com/MobileTeleSystems/onetl/pull/344)) +- Большие примеры кода разделены на вкладки. ([#344](https://github.com/MobileTeleSystems/onetl/pull/344)) diff --git a/mddocs/ru/changelog/0.13.1.md b/mddocs/ru/changelog/0.13.1.md index 4045c7cbf..cda08f438 100644 --- a/mddocs/ru/changelog/0.13.1.md +++ b/mddocs/ru/changelog/0.13.1.md @@ -1,9 +1,9 @@ # 0.13.1 (2025-03-06) -## Bug Fixes +## Исправления ошибок -In 0.13.0, using `DBWriter(connection=hive, target="SOMEDB.SOMETABLE")` lead to executing `df.write.saveAsTable()` -instead of `df.write.insertInto()` if target table `somedb.sometable` already exist. +В версии 0.13.0 использование `DBWriter(connection=hive, target="SOMEDB.SOMETABLE")` приводило к выполнению `df.write.saveAsTable()` вместо `df.write.insertInto()`, если целевая таблица `somedb.sometable` уже существовала. -This is caused by table name normalization (Hive uses lower-case names), which wasn't properly handled by method used for checking table existence. +Это происходило из-за нормализации имени таблицы (Hive использует имена в нижнем регистре), которое неправильно обрабатывалась методом, использующимся для проверки существования таблицы. ([#350](https://github.com/MobileTeleSystems/onetl/pull/350)) + diff --git a/mddocs/ru/changelog/0.13.3.md b/mddocs/ru/changelog/0.13.3.md index 1aa289b49..0ad3a100d 100644 --- a/mddocs/ru/changelog/0.13.3.md +++ b/mddocs/ru/changelog/0.13.3.md @@ -1,5 +1,5 @@ # 0.13.3 (2025-03-11) -## Dependencies +## Зависимости -Allow using [etl-entities 2.6.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.6.0). +Используется [etl-entities 2.6.0](https://github.com/MobileTeleSystems/etl-entities/releases/tag/2.6.0). diff --git a/mddocs/ru/changelog/0.13.4.md b/mddocs/ru/changelog/0.13.4.md index 10f695e0c..59a04350a 100644 --- a/mddocs/ru/changelog/0.13.4.md +++ b/mddocs/ru/changelog/0.13.4.md @@ -1,10 +1,7 @@ # 0.13.4 (2025-03-20) -## Doc only Changes +## Изменения только в документации -- Prefer `ReadOptions(partitionColumn=..., numPartitions=..., queryTimeout=...)` - instead of `ReadOptions(partition_column=..., num_partitions=..., query_timeout=...)`, - to match Spark documentation. ([#352](https://github.com/MobileTeleSystems/onetl/pull/352)) -- Prefer `WriteOptions(if_exists=...)` instead of `WriteOptions(mode=...)` for IDE suggestions. ([#354](https://github.com/MobileTeleSystems/onetl/pull/354)) -- Document all options of supported file formats. - ([#355](https://github.com/MobileTeleSystems/onetl/pull/355), [#356](https://github.com/MobileTeleSystems/onetl/pull/356), [#357](https://github.com/MobileTeleSystems/onetl/pull/357), [#358](https://github.com/MobileTeleSystems/onetl/pull/358), [#359](https://github.com/MobileTeleSystems/onetl/pull/359), [#360](https://github.com/MobileTeleSystems/onetl/pull/360), [#361](https://github.com/MobileTeleSystems/onetl/pull/361), [#362](https://github.com/MobileTeleSystems/onetl/pull/362)) +- Предпочитается использование `ReadOptions(partitionColumn=..., numPartitions=..., queryTimeout=...)` вместо `ReadOptions(partition_column=..., num_partitions=..., query_timeout=...)`, чтобы лучше соответствовать документации Spark. ([#352](https://github.com/MobileTeleSystems/onetl/pull/352)) +- Предпочитается `WriteOptions(if_exists=...)` вместо `WriteOptions(mode=...)` для предложений IDE. ([#354](https://github.com/MobileTeleSystems/onetl/pull/354)) +- Документированы все опции поддерживаемых форматов файлов. ([#355](https://github.com/MobileTeleSystems/onetl/pull/355), [#356](https://github.com/MobileTeleSystems/onetl/pull/356), [#357](https://github.com/MobileTeleSystems/onetl/pull/357), [#358](https://github.com/MobileTeleSystems/onetl/pull/358), [#359](https://github.com/MobileTeleSystems/onetl/pull/359), [#360](https://github.com/MobileTeleSystems/onetl/pull/360), [#361](https://github.com/MobileTeleSystems/onetl/pull/361), [#362](https://github.com/MobileTeleSystems/onetl/pull/362)) diff --git a/mddocs/ru/changelog/0.9.5.md b/mddocs/ru/changelog/0.9.5.md index 1d7358c0b..e44185e85 100644 --- a/mddocs/ru/changelog/0.9.5.md +++ b/mddocs/ru/changelog/0.9.5.md @@ -2,13 +2,14 @@ ## Features -- Add `XML` file format support. ([#163](https://github.com/MobileTeleSystems/onetl/pull/163)) -- Tested compatibility with Spark 3.5.0. `MongoDB` and `Excel` are not supported yet, but other packages do. ([#159](https://github.com/MobileTeleSystems/onetl/pull/159)) +- Добавлена поддержка файлов формата `XML`. ([#163](https://github.com/MobileTeleSystems/onetl/pull/163)) +- Протестирована совместимость со Spark 3.5.0. Поддерживаются все пакеты, кроме `MongoDB` и `Excel`. ([#159](https://github.com/MobileTeleSystems/onetl/pull/159)) ## Improvements -- Add check to all DB and FileDF connections that Spark session is alive. ([#164](https://github.com/MobileTeleSystems/onetl/pull/164)) +- Добавлена проверка активности сессии Spark для всех подключений к БД и объектов чтения/записи файлов в DataFrame (FileDF). ([#164](https://github.com/MobileTeleSystems/onetl/pull/164)) ## Bug Fixes -- Fix `Hive.check()` behavior when Hive Metastore is not available. ([#164](https://github.com/MobileTeleSystems/onetl/pull/164)) +- Исправлено поведение `Hive.check()`, когда Hive Metastore недоступен. ([#164](https://github.com/MobileTeleSystems/onetl/pull/164)) + From 52836a0b27f9d2ab67840c98e1e88ca506057398 Mon Sep 17 00:00:00 2001 From: Sattar Gyulmamedov Date: Tue, 10 Jun 2025 00:46:11 +0300 Subject: [PATCH 09/22] add common doc files --- mddocs/common_rst/CONTRIBUTING.md | 415 +++++++++++++++++++ mddocs/common_rst/README.md | 660 ++++++++++++++++++++++++++++++ mddocs/common_rst/SECURITY.md | 25 ++ mddocs/ru/CONTRIBUTING.md | 370 +++++++++++++++++ mddocs/ru/README.md | 660 ++++++++++++++++++++++++++++++ mddocs/ru/SECURITY.md | 25 ++ mddocs/ru/concepts.md | 458 +++++++++++++++++++++ mddocs/ru/contributing.md | 3 + mddocs/ru/index.md | 87 ++++ mddocs/ru/logging.md | 171 ++++++++ mddocs/ru/plugins.md | 147 +++++++ mddocs/ru/quickstart.md | 4 + mddocs/ru/security.md | 3 + 13 files changed, 3028 insertions(+) create mode 100644 mddocs/common_rst/CONTRIBUTING.md create mode 100644 mddocs/common_rst/README.md create mode 100644 mddocs/common_rst/SECURITY.md create mode 100644 mddocs/ru/CONTRIBUTING.md create mode 100644 mddocs/ru/README.md create mode 100644 mddocs/ru/SECURITY.md create mode 100644 mddocs/ru/concepts.md create mode 100644 mddocs/ru/contributing.md create mode 100644 mddocs/ru/index.md create mode 100644 mddocs/ru/logging.md create mode 100644 mddocs/ru/plugins.md create mode 100644 mddocs/ru/quickstart.md create mode 100644 mddocs/ru/security.md diff --git a/mddocs/common_rst/CONTRIBUTING.md b/mddocs/common_rst/CONTRIBUTING.md new file mode 100644 index 000000000..eb33c2be5 --- /dev/null +++ b/mddocs/common_rst/CONTRIBUTING.md @@ -0,0 +1,415 @@ +# Contributing Guide + +Welcome! There are many ways to contribute, including submitting bug +reports, improving documentation, submitting feature requests, reviewing +new submissions, or contributing code that can be incorporated into the +project. + +## Limitations + +We should keep close to these items during development: + +- Some companies still use old Spark versions, like 2.3.1. So it is required to keep compatibility if possible, e.g. adding branches for different Spark versions. +- Different users uses onETL in different ways - some uses only DB connectors, some only files. Connector-specific dependencies should be optional. +- Instead of creating classes with a lot of different options, prefer splitting them into smaller classes, e.g. options class, context manager, etc, and using composition. + +## Initial setup for local development + +### Install Git + +Please follow [instruction](https://docs.github.com/en/get-started/quickstart/set-up-git). + +### Create a fork + +If you are not a member of a development team building onETL, you should create a fork before making any changes. + +Please follow [instruction](https://docs.github.com/en/get-started/quickstart/fork-a-repo). + +### Clone the repo + +Open terminal and run these commands: + +```bash +git clone git@github.com:myuser/onetl.git -b develop + +cd onetl +``` + +### Setup environment + +Create virtualenv and install dependencies: + +```bash +python -m venv venv +source venv/bin/activate +pip install -U wheel +pip install -U pip setuptools +pip install -U \ + -r requirements/core.txt \ + -r requirements/ftp.txt \ + -r requirements/hdfs.txt \ + -r requirements/kerberos.txt \ + -r requirements/s3.txt \ + -r requirements/sftp.txt \ + -r requirements/webdav.txt \ + -r requirements/dev.txt \ + -r requirements/docs.txt \ + -r requirements/tests/base.txt \ + -r requirements/tests/clickhouse.txt \ + -r requirements/tests/kafka.txt \ + -r requirements/tests/mongodb.txt \ + -r requirements/tests/mssql.txt \ + -r requirements/tests/mysql.txt \ + -r requirements/tests/postgres.txt \ + -r requirements/tests/oracle.txt \ + -r requirements/tests/pydantic-2.txt \ + -r requirements/tests/spark-3.5.5.txt + +# TODO: remove after https://github.com/zqmillet/sphinx-plantuml/pull/4 +pip install sphinx-plantuml --no-deps +``` + +### Enable pre-commit hooks + +Install pre-commit hooks: + +```bash +pre-commit install --install-hooks +``` + +Test pre-commit hooks run: + +```bash +pre-commit run +``` + +## How to + +### Run tests locally + +#### Using docker-compose + +Build image for running tests: + +```bash +docker-compose build +``` + +Start all containers with dependencies: + +```bash +docker-compose --profile all up -d +``` + +You can run limited set of dependencies: + +```bash +docker-compose --profile mongodb up -d +``` + +Run tests: + +```bash +docker-compose run --rm onetl ./run_tests.sh +``` + +You can pass additional arguments, they will be passed to pytest: + +```bash +docker-compose run --rm onetl ./run_tests.sh -m mongodb -lsx -vvvv --log-cli-level=INFO +``` + +You can run interactive bash session and use it: + +```bash +docker-compose run --rm onetl bash + +./run_tests.sh -m mongodb -lsx -vvvv --log-cli-level=INFO +``` + +See logs of test container: + +```bash +docker-compose logs -f onetl +``` + +Stop all containers and remove created volumes: + +```bash +docker-compose --profile all down -v +``` + +#### Without docker-compose + +```{eval-rst} +.. warning:: + + To run HDFS tests locally you should add the following line to your ``/etc/hosts`` (file path depends on OS): + + .. code:: + + # HDFS server returns container hostname as connection address, causing error in DNS resolution + 127.0.0.1 hdfs +``` + +```{eval-rst} +.. note:: + + To run Oracle tests you need to install `Oracle instantclient `__, + and pass its path to ``ONETL_ORA_CLIENT_PATH`` and ``LD_LIBRARY_PATH`` environment variables, + e.g. ``ONETL_ORA_CLIENT_PATH=/path/to/client64/lib``. + + It may also require to add the same path into ``LD_LIBRARY_PATH`` environment variable +``` + +```{eval-rst} +.. note:: + + To run Greenplum tests, you should: + + * Download `VMware Greenplum connector for Spark `_ + * Either move it to ``~/.ivy2/jars/``, or pass file path to ``CLASSPATH`` + * Set environment variable ``ONETL_GP_PACKAGE_VERSION=local``. +``` + +Start all containers with dependencies: + +```bash +docker-compose --profile all up -d +``` + +You can run limited set of dependencies: + +```bash +docker-compose --profile mongodb up -d +``` + +Load environment variables with connection properties: + +```bash +source .env.local +``` + +Run tests: + +```bash +./run_tests.sh +``` + +You can pass additional arguments, they will be passed to pytest: + +```bash +./run_tests.sh -m mongodb -lsx -vvvv --log-cli-level=INFO +``` + +Stop all containers and remove created volumes: + +```bash +docker-compose --profile all down -v +``` + +### Build documentation + +Build documentation using Sphinx: + +```bash +cd docs +make html +``` + +Then open in browser `docs/_build/index.html`. + +## Review process + +Please create a new GitHub issue for any significant changes and +enhancements that you wish to make. Provide the feature you would like +to see, why you need it, and how it will work. Discuss your ideas +transparently and get community feedback before proceeding. + +Significant Changes that you wish to contribute to the project should be +discussed first in a GitHub issue that clearly outlines the changes and +benefits of the feature. + +Small Changes can directly be crafted and submitted to the GitHub +Repository as a Pull Request. + +### Create pull request + +Commit your changes: + +```bash +git commit -m "Commit message" +git push +``` + +Then open Github interface and [create pull request](https://docs.github.com/en/get-started/quickstart/contributing-to-projects#making-a-pull-request). +Please follow guide from PR body template. + +After pull request is created, it get a corresponding number, e.g. 123 (`pr_number`). + +### Write release notes + +`onETL` uses [towncrier](https://pypi.org/project/towncrier/) +for changelog management. + +To submit a change note about your PR, add a text file into the +[docs/changelog/next_release](./next_release) folder. It should contain an +explanation of what applying this PR will change in the way +end-users interact with the project. One sentence is usually +enough but feel free to add as many details as you feel necessary +for the users to understand what it means. + +**Use the past tense** for the text in your fragment because, +combined with others, it will be a part of the "news digest" +telling the readers **what changed** in a specific version of +the library *since the previous version*. + +You should also use +reStructuredText syntax for highlighting code (inline or block), +linking parts of the docs or external sites. +If you wish to sign your change, feel free to add `` -- by +\:user:`github-username` `` at the end (replace `github-username` +with your own!). + +Finally, name your file following the convention that Towncrier +understands: it should start with the number of an issue or a +PR followed by a dot, then add a patch type, like `feature`, +`doc`, `misc` etc., and add `.rst` as a suffix. If you +need to add more than one fragment, you may add an optional +sequence number (delimited with another period) between the type +and the suffix. + +In general the name will follow `..rst` pattern, +where the categories are: + +- `feature`: Any new feature +- `bugfix`: A bug fix +- `improvement`: An improvement +- `doc`: A change to the documentation +- `dependency`: Dependency-related changes +- `misc`: Changes internal to the repo like CI, test and build changes + +A pull request may have more than one of these components, for example +a code change may introduce a new feature that deprecates an old +feature, in which case two fragments should be added. It is not +necessary to make a separate documentation fragment for documentation +changes accompanying the relevant code changes. + +#### Examples for adding changelog entries to your Pull Requests + +```{code-block} rst +:caption: docs/changelog/next_release/1234.doc.1.rst + +Added a ``:github:user:`` role to Sphinx config -- by :github:user:`someuser` +``` + +```{code-block} rst +:caption: docs/changelog/next_release/2345.bugfix.rst + +Fixed behavior of ``WebDAV`` connector -- by :github:user:`someuser` +``` + +```{code-block} rst +:caption: docs/changelog/next_release/3456.feature.rst + +Added support of ``timeout`` in ``S3`` connector +-- by :github:user:`someuser`, :github:user:`anotheruser` and :github:user:`otheruser` +``` + +```{eval-rst} +.. tip:: + + See `pyproject.toml `_ for all available categories + (``tool.towncrier.type``). +``` + +#### How to skip change notes check? + +Just add `ci:skip-changelog` label to pull request. + +#### Release Process + +Before making a release from the `develop` branch, follow these steps: + +0. Checkout to `develop` branch and update it to the actual state + +```bash +git checkout develop +git pull -p +``` + +1. Backup `NEXT_RELEASE.rst` + +```bash +cp "docs/changelog/NEXT_RELEASE.rst" "docs/changelog/temp_NEXT_RELEASE.rst" +``` + +2. Build the Release notes with Towncrier + +```bash +VERSION=$(cat onetl/VERSION) +towncrier build "--version=${VERSION}" --yes +``` + +3. Change file with changelog to release version number + +```bash +mv docs/changelog/NEXT_RELEASE.rst "docs/changelog/${VERSION}.rst" +``` + +4. Remove content above the version number heading in the `${VERSION}.rst` file + +```bash +awk '!/^.*towncrier release notes start/' "docs/changelog/${VERSION}.rst" > temp && mv temp "docs/changelog/${VERSION}.rst" +``` + +5. Update Changelog Index + +```bash +awk -v version=${VERSION} '/DRAFT/{print;print " " version;next}1' docs/changelog/index.rst > temp && mv temp docs/changelog/index.rst +``` + +6. Restore `NEXT_RELEASE.rst` file from backup + +```bash +mv "docs/changelog/temp_NEXT_RELEASE.rst" "docs/changelog/NEXT_RELEASE.rst" +``` + +7. Commit and push changes to `develop` branch + +```bash +git add . +git commit -m "Prepare for release ${VERSION}" +git push +``` + +8. Merge `develop` branch to `master`, **WITHOUT** squashing + +```bash +git checkout master +git pull +git merge develop +git push +``` + +9. Add git tag to the latest commit in `master` branch + +```bash +git tag "$VERSION" +git push origin "$VERSION" +``` + +10. Update version in `develop` branch **after release**: + +```bash +git checkout develop + +NEXT_VERSION=$(echo "$VERSION" | awk -F. '/[0-9]+\./{$NF++;print}' OFS=.) +echo "$NEXT_VERSION" > onetl/VERSION + +git add . +git commit -m "Bump version" +git push +``` + +[towncrier philosophy]: https://towncrier.readthedocs.io/en/stable/#philosophy diff --git a/mddocs/common_rst/README.md b/mddocs/common_rst/README.md new file mode 100644 index 000000000..bed7994b8 --- /dev/null +++ b/mddocs/common_rst/README.md @@ -0,0 +1,660 @@ +--- +substitutions: + CI Status: |- + ```{image} https://github.com/MobileTeleSystems/onetl/workflows/Tests/badge.svg + :alt: Github Actions - latest CI build status + :target: https://github.com/MobileTeleSystems/onetl/actions + ``` + Documentation: |- + ```{image} https://readthedocs.org/projects/onetl/badge/?version=stable + :alt: Documentation - ReadTheDocs + :target: https://onetl.readthedocs.io/ + ``` + Logo: |- + ```{image} docs/_static/logo_wide.svg + :alt: onETL logo + :target: https://github.com/MobileTeleSystems/onetl + ``` + PyPI Downloads: |- + ```{image} https://img.shields.io/pypi/dm/onetl + :alt: PyPI - Downloads + :target: https://pypi.org/project/onetl/ + ``` + PyPI Latest Release: |- + ```{image} https://img.shields.io/pypi/v/onetl + :alt: PyPI - Latest Release + :target: https://pypi.org/project/onetl/ + ``` + PyPI License: |- + ```{image} https://img.shields.io/pypi/l/onetl.svg + :alt: PyPI - License + :target: https://github.com/MobileTeleSystems/onetl/blob/develop/LICENSE.txt + ``` + PyPI Python Version: |- + ```{image} https://img.shields.io/pypi/pyversions/onetl.svg + :alt: PyPI - Python Version + :target: https://pypi.org/project/onetl/ + ``` + Repo Status: |- + ```{image} https://www.repostatus.org/badges/latest/active.svg + :alt: Repo status - Active + :target: https://github.com/MobileTeleSystems/onetl + ``` + Test Coverage: |- + ```{image} https://codecov.io/gh/MobileTeleSystems/onetl/branch/develop/graph/badge.svg?token=RIO8URKNZJ + :alt: Test coverage - percent + :target: https://codecov.io/gh/MobileTeleSystems/onetl + ``` + pre-commit.ci Status: |- + ```{image} https://results.pre-commit.ci/badge/github/MobileTeleSystems/onetl/develop.svg + :alt: pre-commit.ci - status + :target: https://results.pre-commit.ci/latest/github/MobileTeleSystems/onetl/develop + ``` +--- + +(readme)= + +# onETL + +{{ Repo Status }} {{ PyPI Latest Release }} {{ PyPI License }} {{ PyPI Python Version }} {{ PyPI Downloads }} +{{ Documentation }} {{ CI Status }} {{ Test Coverage }} {{ pre-commit.ci Status }} + +{{ Logo }} + +## What is onETL? + +Python ETL/ELT library powered by [Apache Spark](https://spark.apache.org/) & other open-source tools. + +## Goals + +- Provide unified classes to extract data from (**E**) & load data to (**L**) various stores. +- Provides [Spark DataFrame API](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.DataFrame.html) for performing transformations (**T**) in terms of *ETL*. +- Provide direct assess to database, allowing to execute SQL queries, as well as DDL, DML, and call functions/procedures. This can be used for building up *ELT* pipelines. +- Support different [read strategies](https://onetl.readthedocs.io/en/stable/strategy/index.html) for incremental and batch data fetching. +- Provide [hooks](https://onetl.readthedocs.io/en/stable/hooks/index.html) & [plugins](https://onetl.readthedocs.io/en/stable/plugins.html) mechanism for altering behavior of internal classes. + +## Non-goals + +- onETL is not a Spark replacement. It just provides additional functionality that Spark does not have, and improves UX for end users. +- onETL is not a framework, as it does not have requirements to project structure, naming, the way of running ETL/ELT processes, configuration, etc. All of that should be implemented in some other tool. +- onETL is deliberately developed without any integration with scheduling software like Apache Airflow. All integrations should be implemented as separated tools. +- Only batch operations, no streaming. For streaming prefer [Apache Flink](https://flink.apache.org/). + +## Requirements + +- **Python 3.7 - 3.13** +- PySpark 2.3.x - 3.5.x (depends on used connector) +- Java 8+ (required by Spark, see below) +- Kerberos libs & GCC (required by `Hive`, `HDFS` and `SparkHDFS` connectors) + +## Supported storages + +```{eval-rst} ++--------------------+--------------+-------------------------------------------------------------------------------------------------------------------------+ +| Type | Storage | Powered by | ++====================+==============+=========================================================================================================================+ +| Database | Clickhouse | Apache Spark `JDBC Data Source `_ | ++ +--------------+ + +| | MSSQL | | ++ +--------------+ + +| | MySQL | | ++ +--------------+ + +| | Postgres | | ++ +--------------+ + +| | Oracle | | ++ +--------------+ + +| | Teradata | | ++ +--------------+-------------------------------------------------------------------------------------------------------------------------+ +| | Hive | Apache Spark `Hive integration `_ | ++ +--------------+-------------------------------------------------------------------------------------------------------------------------+ +| | Kafka | Apache Spark `Kafka integration `_ | ++ +--------------+-------------------------------------------------------------------------------------------------------------------------+ +| | Greenplum | VMware `Greenplum Spark connector `_ | ++ +--------------+-------------------------------------------------------------------------------------------------------------------------+ +| | MongoDB | `MongoDB Spark connector `_ | ++--------------------+--------------+-------------------------------------------------------------------------------------------------------------------------+ +| File | HDFS | `HDFS Python client `_ | ++ +--------------+-------------------------------------------------------------------------------------------------------------------------+ +| | S3 | `minio-py client `_ | ++ +--------------+-------------------------------------------------------------------------------------------------------------------------+ +| | SFTP | `Paramiko library `_ | ++ +--------------+-------------------------------------------------------------------------------------------------------------------------+ +| | FTP | `FTPUtil library `_ | ++ +--------------+ + +| | FTPS | | ++ +--------------+-------------------------------------------------------------------------------------------------------------------------+ +| | WebDAV | `WebdavClient3 library `_ | ++ +--------------+-------------------------------------------------------------------------------------------------------------------------+ +| | Samba | `pysmb library `_ | ++--------------------+--------------+-------------------------------------------------------------------------------------------------------------------------+ +| Files as DataFrame | SparkLocalFS | Apache Spark `File Data Source `_ | +| +--------------+ + +| | SparkHDFS | | +| +--------------+-------------------------------------------------------------------------------------------------------------------------+ +| | SparkS3 | `Hadoop AWS `_ library | ++--------------------+--------------+-------------------------------------------------------------------------------------------------------------------------+ +``` + +% documentation + +## Documentation + +See + +## How to install + +(install)= + +### Minimal installation + +(minimal-install)= + +Base `onetl` package contains: + +- `DBReader`, `DBWriter` and related classes +- `FileDownloader`, `FileUploader`, `FileMover` and related classes, like file filters & limits +- `FileDFReader`, `FileDFWriter` and related classes, like file formats +- Read Strategies & HWM classes +- Plugins support + +It can be installed via: + +```bash +pip install onetl +``` + +```{eval-rst} +.. warning:: + + This method does NOT include any connections. + + This method is recommended for use in third-party libraries which require for ``onetl`` to be installed, + but do not use its connection classes. +``` + +### With DB and FileDF connections + +(spark-install)= + +All DB connection classes (`Clickhouse`, `Greenplum`, `Hive` and others) +and all FileDF connection classes (`SparkHDFS`, `SparkLocalFS`, `SparkS3`) +require Spark to be installed. + +(java-install)= + +Firstly, you should install JDK. The exact installation instruction depends on your OS, here are some examples: + +```bash +yum install java-1.8.0-openjdk-devel # CentOS 7 + Spark 2 +dnf install java-11-openjdk-devel # CentOS 8 + Spark 3 +apt-get install openjdk-11-jdk # Debian-based + Spark 3 +``` + +(spark-compatibility-matrix)= + +#### Compatibility matrix + +| Spark | Python | Java | Scala | +| --------------------------------------------------------- | ---------- | ---------- | ----- | +| [2.3.x](https://spark.apache.org/docs/2.3.1/#downloading) | 3.7 only | 8 only | 2.11 | +| [2.4.x](https://spark.apache.org/docs/2.4.8/#downloading) | 3.7 only | 8 only | 2.11 | +| [3.2.x](https://spark.apache.org/docs/3.2.4/#downloading) | 3.7 - 3.10 | 8u201 - 11 | 2.12 | +| [3.3.x](https://spark.apache.org/docs/3.3.4/#downloading) | 3.7 - 3.12 | 8u201 - 17 | 2.12 | +| [3.4.x](https://spark.apache.org/docs/3.4.4/#downloading) | 3.7 - 3.12 | 8u362 - 20 | 2.12 | +| [3.5.x](https://spark.apache.org/docs/3.5.5/#downloading) | 3.8 - 3.13 | 8u371 - 20 | 2.12 | + +(pyspark-install)= + +Then you should install PySpark via passing `spark` to `extras`: + +```bash +pip install onetl[spark] # install latest PySpark +``` + +or install PySpark explicitly: + +```bash +pip install onetl pyspark==3.5.5 # install a specific PySpark version +``` + +or inject PySpark to `sys.path` in some other way BEFORE creating a class instance. +**Otherwise connection object cannot be created.** + +### With File connections + +(files-install)= + +All File (but not *FileDF*) connection classes (`FTP`, `SFTP`, `HDFS` and so on) requires specific Python clients to be installed. + +Each client can be installed explicitly by passing connector name (in lowercase) to `extras`: + +```bash +pip install onetl[ftp] # specific connector +pip install onetl[ftp,ftps,sftp,hdfs,s3,webdav,samba] # multiple connectors +``` + +To install all file connectors at once you can pass `files` to `extras`: + +```bash +pip install onetl[files] +``` + +**Otherwise class import will fail.** + +### With Kerberos support + +(kerberos-install)= + +Most of Hadoop instances set up with Kerberos support, +so some connections require additional setup to work properly. + +- `HDFS` + Uses [requests-kerberos](https://pypi.org/project/requests-kerberos/) and + [GSSApi](https://pypi.org/project/gssapi/) for authentication. + It also uses `kinit` executable to generate Kerberos ticket. +- `Hive` and `SparkHDFS` + require Kerberos ticket to exist before creating Spark session. + +So you need to install OS packages with: + +- `krb5` libs +- Headers for `krb5` +- `gcc` or other compiler for C sources + +The exact installation instruction depends on your OS, here are some examples: + +```bash +apt install libkrb5-dev krb5-user gcc # Debian-based +dnf install krb5-devel krb5-libs krb5-workstation gcc # CentOS, OracleLinux +``` + +Also you should pass `kerberos` to `extras` to install required Python packages: + +```bash +pip install onetl[kerberos] +``` + +### Full bundle + +(full-bundle-1)= + +To install all connectors and dependencies, you can pass `all` into `extras`: + +```bash +pip install onetl[all] + +# this is just the same as +pip install onetl[spark,files,kerberos] +``` + +```{eval-rst} +.. warning:: + + This method consumes a lot of disk space, and requires for Java & Kerberos libraries to be installed into your OS. +``` + +(quick-start)= + +## Quick start + +### MSSQL → Hive + +Read data from MSSQL, transform & write to Hive. + +```bash +# install onETL and PySpark +pip install onetl[spark] +``` + +```python +# Import pyspark to initialize the SparkSession +from pyspark.sql import SparkSession + +# import function to setup onETL logging +from onetl.log import setup_logging + +# Import required connections +from onetl.connection import MSSQL, Hive + +# Import onETL classes to read & write data +from onetl.db import DBReader, DBWriter + +# change logging level to INFO, and set up default logging format and handler +setup_logging() + +# Initialize new SparkSession with MSSQL driver loaded +maven_packages = MSSQL.get_packages() +spark = ( + SparkSession.builder.appName("spark_app_onetl_demo") + .config("spark.jars.packages", ",".join(maven_packages)) + .enableHiveSupport() # for Hive + .getOrCreate() +) + +# Initialize MSSQL connection and check if database is accessible +mssql = MSSQL( + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, + # These options are passed to MSSQL JDBC Driver: + extra={"applicationIntent": "ReadOnly"}, +).check() + +# >>> INFO:|MSSQL| Connection is available + +# Initialize DBReader +reader = DBReader( + connection=mssql, + source="dbo.demo_table", + columns=["on", "etl"], + # Set some MSSQL read options: + options=MSSQL.ReadOptions(fetchsize=10000), +) + +# checks that there is data in the table, otherwise raises exception +reader.raise_if_no_data() + +# Read data to DataFrame +df = reader.run() +df.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) + +# Apply any PySpark transformations +from pyspark.sql.functions import lit + +df_to_write = df.withColumn("engine", lit("onetl")) +df_to_write.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) +# |-- engine: string (nullable = false) + +# Initialize Hive connection +hive = Hive(cluster="rnd-dwh", spark=spark) + +# Initialize DBWriter +db_writer = DBWriter( + connection=hive, + target="dl_sb.demo_table", + # Set some Hive write options: + options=Hive.WriteOptions(if_exists="replace_entire_table"), +) + +# Write data from DataFrame to Hive +db_writer.run(df_to_write) + +# Success! +``` + +### SFTP → HDFS + +Download files from SFTP & upload them to HDFS. + +```bash +# install onETL with SFTP and HDFS clients, and Kerberos support +pip install onetl[hdfs,sftp,kerberos] +``` + +```python +# import function to setup onETL logging +from onetl.log import setup_logging + +# Import required connections +from onetl.connection import SFTP, HDFS + +# Import onETL classes to download & upload files +from onetl.file import FileDownloader, FileUploader + +# import filter & limit classes +from onetl.file.filter import Glob, ExcludeDir +from onetl.file.limit import MaxFilesCount + +# change logging level to INFO, and set up default logging format and handler +setup_logging() + +# Initialize SFTP connection and check it +sftp = SFTP( + host="sftp.test.com", + user="someuser", + password="somepassword", +).check() + +# >>> INFO:|SFTP| Connection is available + +# Initialize downloader +file_downloader = FileDownloader( + connection=sftp, + source_path="/remote/tests/Report", # path on SFTP + local_path="/local/onetl/Report", # local fs path + filters=[ + # download only files matching the glob + Glob("*.csv"), + # exclude files from this directory + ExcludeDir("/remote/tests/Report/exclude_dir/"), + ], + limits=[ + # download max 1000 files per run + MaxFilesCount(1000), + ], + options=FileDownloader.Options( + # delete files from SFTP after successful download + delete_source=True, + # mark file as failed if it already exist in local_path + if_exists="error", + ), +) + +# Download files to local filesystem +download_result = downloader.run() + +# Method run returns a DownloadResult object, +# which contains collection of downloaded files, divided to 4 categories +download_result + +# DownloadResult( +# successful=[ +# LocalPath('/local/onetl/Report/file_1.json'), +# LocalPath('/local/onetl/Report/file_2.json'), +# ], +# failed=[FailedRemoteFile('/remote/onetl/Report/file_3.json')], +# ignored=[RemoteFile('/remote/onetl/Report/file_4.json')], +# missing=[], +# ) + +# Raise exception if there are failed files, or there were no files in the remote filesystem +download_result.raise_if_failed() or download_result.raise_if_empty() + +# Do any kind of magic with files: rename files, remove header for csv files, ... +renamed_files = my_rename_function(download_result.success) + +# function removed "_" from file names +# [ +# LocalPath('/home/onetl/Report/file1.json'), +# LocalPath('/home/onetl/Report/file2.json'), +# ] + +# Initialize HDFS connection +hdfs = HDFS( + host="my.name.node", + user="someuser", + password="somepassword", # or keytab +) + +# Initialize uploader +file_uploader = FileUploader( + connection=hdfs, + target_path="/user/onetl/Report/", # hdfs path +) + +# Upload files from local fs to HDFS +upload_result = file_uploader.run(renamed_files) + +# Method run returns a UploadResult object, +# which contains collection of uploaded files, divided to 4 categories +upload_result + +# UploadResult( +# successful=[RemoteFile('/user/onetl/Report/file1.json')], +# failed=[FailedLocalFile('/local/onetl/Report/file2.json')], +# ignored=[], +# missing=[], +# ) + +# Raise exception if there are failed files, or there were no files in the local filesystem, or some input file is missing +upload_result.raise_if_failed() or upload_result.raise_if_empty() or upload_result.raise_if_missing() + +# Success! +``` + +### S3 → Postgres + +Read files directly from S3 path, convert them to dataframe, transform it and then write to a database. + +```bash +# install onETL and PySpark +pip install onetl[spark] +``` + +```python +# Import pyspark to initialize the SparkSession +from pyspark.sql import SparkSession + +# import function to setup onETL logging +from onetl.log import setup_logging + +# Import required connections +from onetl.connection import Postgres, SparkS3 + +# Import onETL classes to read files +from onetl.file import FileDFReader +from onetl.file.format import CSV + +# Import onETL classes to write data +from onetl.db import DBWriter + +# change logging level to INFO, and set up default logging format and handler +setup_logging() + +# Initialize new SparkSession with Hadoop AWS libraries and Postgres driver loaded +maven_packages = SparkS3.get_packages(spark_version="3.5.5") + Postgres.get_packages() +exclude_packages = SparkS3.get_exclude_packages() +spark = ( + SparkSession.builder.appName("spark_app_onetl_demo") + .config("spark.jars.packages", ",".join(maven_packages)) + .config("spark.jars.excludes", ",".join(exclude_packages)) + .getOrCreate() +) + +# Initialize S3 connection and check it +spark_s3 = SparkS3( + host="s3.test.com", + protocol="https", + bucket="my-bucket", + access_key="somekey", + secret_key="somesecret", + # Access bucket as s3.test.com/my-bucket + extra={"path.style.access": True}, + spark=spark, +).check() + +# >>> INFO:|SparkS3| Connection is available + +# Describe file format and parsing options +csv = CSV( + delimiter=";", + header=True, + encoding="utf-8", +) + +# Describe DataFrame schema of files +from pyspark.sql.types import ( + DateType, + DoubleType, + IntegerType, + StringType, + StructField, + StructType, + TimestampType, +) + +df_schema = StructType( + [ + StructField("id", IntegerType()), + StructField("phone_number", StringType()), + StructField("region", StringType()), + StructField("birth_date", DateType()), + StructField("registered_at", TimestampType()), + StructField("account_balance", DoubleType()), + ], +) + +# Initialize file df reader +reader = FileDFReader( + connection=spark_s3, + source_path="/remote/tests/Report", # path on S3 there *.csv files are located + format=csv, # file format with specific parsing options + df_schema=df_schema, # columns & types +) + +# Read files directly from S3 as Spark DataFrame +df = reader.run() + +# Check that DataFrame schema is same as expected +df.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) + +# Apply any PySpark transformations +from pyspark.sql.functions import lit + +df_to_write = df.withColumn("engine", lit("onetl")) +df_to_write.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) +# |-- engine: string (nullable = false) + +# Initialize Postgres connection +postgres = Postgres( + host="192.169.11.23", + user="onetl", + password="somepassword", + database="mydb", + spark=spark, +) + +# Initialize DBWriter +db_writer = DBWriter( + connection=postgres, + # write to specific table + target="public.my_table", + # with some writing options + options=Postgres.WriteOptions(if_exists="append"), +) + +# Write DataFrame to Postgres table +db_writer.run(df_to_write) + +# Success! +``` diff --git a/mddocs/common_rst/SECURITY.md b/mddocs/common_rst/SECURITY.md new file mode 100644 index 000000000..5d9dce52b --- /dev/null +++ b/mddocs/common_rst/SECURITY.md @@ -0,0 +1,25 @@ +# Security + +## Supported Python versions + +3.7 or above + +## Product development security recommendations + +1. Update dependencies to last stable version +2. Build SBOM for the project +3. Perform SAST (Static Application Security Testing) where possible + +## Product development security requirements + +1. No binaries in repository +2. No passwords, keys, access tokens in source code +3. No "Critical" and/or "High" vulnerabilities in contributed source code + +## Vulnerability reports + +Please, use email [mailto:onetools@mts.ru](mailto:onetools@mts.ru) for reporting security issues or anything that can cause any consequences for security. + +Please avoid any public disclosure (including registering issues) at least until it is fixed. + +Thank you in advance for understanding. diff --git a/mddocs/ru/CONTRIBUTING.md b/mddocs/ru/CONTRIBUTING.md new file mode 100644 index 000000000..cb3d79377 --- /dev/null +++ b/mddocs/ru/CONTRIBUTING.md @@ -0,0 +1,370 @@ +# Руководство по участию + +Добро пожаловать! Существует множество способов внести свой вклад, включая отправку отчетов об ошибках, улучшение документации, отправку запросов на добавление функций, рецензирование новых материалов или внесение кода, который можно включить в проект. + +## Ограничения + +При разработке следует придерживаться следующих пунктов: + +- Некоторые компании до сих пор используют старые версии Spark, например 2.3.1. Поэтому, по возможности, необходимо поддерживать такую совместимость, например, добавлять ветки для разных версий Spark. +- Разные пользователи используют onETL по-разному - некоторые используют только коннекторы к БД, другие только файловые подключения. Зависимости, специфичные для коннекторов, должны быть необязательными. +- Вместо создания классов с большим количеством различных опций, лучше разделить их на более мелкие классы, например, класс опций, контекстный менеджер и т.д., и использовать композицию. + +## Начальная настройка для локальной разработки + +### Установите Git + +Пожалуйста, следуйте [инструкции](https://docs.github.com/en/get-started/quickstart/set-up-git). + +### Создайте форк + +Если вы не являетесь членом команды разработчиков, создающей onETL, вам следует создать форк, прежде чем вносить какие-либо изменения. + +Пожалуйста, следуйте [инструкции](https://docs.github.com/en/get-started/quickstart/fork-a-repo). + +### Клонируйте репозиторий + +Откройте терминал и выполните следующие команды: + +```bash +git clone git@github.com:myuser/onetl.git -b develop + +cd onetl +``` + +### Настройте окружение + +Создайте virtualenv и установите зависимости: + +```bash +python -m venv venv +source venv/bin/activate +pip install -U wheel +pip install -U pip setuptools +pip install -U \ + -r requirements/core.txt \ + -r requirements/ftp.txt \ + -r requirements/hdfs.txt \ + -r requirements/kerberos.txt \ + -r requirements/s3.txt \ + -r requirements/sftp.txt \ + -r requirements/webdav.txt \ + -r requirements/dev.txt \ + -r requirements/docs.txt \ + -r requirements/tests/base.txt \ + -r requirements/tests/clickhouse.txt \ + -r requirements/tests/kafka.txt \ + -r requirements/tests/mongodb.txt \ + -r requirements/tests/mssql.txt \ + -r requirements/tests/mysql.txt \ + -r requirements/tests/postgres.txt \ + -r requirements/tests/oracle.txt \ + -r requirements/tests/pydantic-2.txt \ + -r requirements/tests/spark-3.5.5.txt + +# TODO: remove after https://github.com/zqmillet/sphinx-plantuml/pull/4 +pip install sphinx-plantuml --no-deps +``` + +### Включите pre-commit hooks + +Установите pre-commit hooks: + +```bash +pre-commit install --install-hooks +``` + +Проверьте запуск pre-commit hooks: + +```bash +pre-commit run +``` + +## Как + +### Запустите тесты локально + +#### Используя docker-compose + +Соберите образ для запуска тестов: + +```bash +docker-compose build +``` + +Запустите все контейнеры с зависимостями: + +```bash +docker-compose --profile all up -d +``` + +Вы можете запустить ограниченный набор зависимостей: + +```bash +docker-compose --profile mongodb up -d +``` + +Запустите тесты: + +```bash +docker-compose run --rm onetl ./run_tests.sh +``` + +Вы можете передать дополнительные аргументы и они будут переданы в pytest: + +```bash +docker-compose run --rm onetl ./run_tests.sh -m mongodb -lsx -vvvv --log-cli-level=INFO +``` + +Вы можете запустить интерактивную bash сессию и использовать ее: + +```bash +docker-compose run --rm onetl bash + +./run_tests.sh -m mongodb -lsx -vvvv --log-cli-level=INFO +``` + +Посмотрите логи тестового контейнера: + +```bash +docker-compose logs -f onetl +``` + +Остановите все контейнеры и удалите созданные тома: + +```bash +docker-compose --profile all down -v +``` + +#### Без docker-compose + +!!! warning + + Для локального запуска тестов HDFS необходимо добавить следующую строку в ``/etc/hosts`` (путь к файлу зависит от ОС): + + ```text + # HDFS server returns container hostname as connection address, causing error in DNS resolution + 127.0.0.1 hdfs + ``` + +!!! note + + Для запуска тестов Oracle необходимо установить `Oracle instantclient `__, + и передать его путь в переменные окружения ``ONETL_ORA_CLIENT_PATH`` и ``LD_LIBRARY_PATH``, + например, ``ONETL_ORA_CLIENT_PATH=/path/to/client64/lib``. + + Также может потребоваться добавить этот же путь в переменную окружения ``LD_LIBRARY_PATH`` + + +!!! note + + Для запуска тестов Greenplum необходимо: + + * Скачать `VMware Greenplum connector for Spark `_ + * Либо переместить его в ``~/.ivy2/jars/``, либо передать путь к файлу в ``CLASSPATH`` + * Установить переменную окружения ``ONETL_GP_PACKAGE_VERSION=local``. + + +Запустите все контейнеры с зависимостями: + +```bash +docker-compose --profile all up -d +``` + +Вы можете запустить ограниченный набор зависимостей: + +```bash +docker-compose --profile mongodb up -d +``` + +Загрузите переменные окружения со свойствами подключения: + +```bash +source .env.local +``` + +Запустите тесты: + +```bash +./run_tests.sh +``` + +Вы можете передать дополнительные аргументы, они будут переданы в pytest: + +```bash +./run_tests.sh -m mongodb -lsx -vvvv --log-cli-level=INFO +``` + +Остановите все контейнеры и удалите созданные тома: + +```bash +docker-compose --profile all down -v +``` + +### Соберите документацию + +Соберите документацию с помощью Sphinx: + +```bash +cd docs +make html +``` + +Затем откройте в браузере `docs/_build/index.html`. + +## Процесс рецензирования + +Пожалуйста, создайте новую задачу GitHub для любых значительных изменений и улучшений, которые вы хотите внести. Опишите функцию, которую вы хотели бы видеть, зачем она вам нужна и как она будет работать. Обсудите свои идеи прозрачно и получите отзывы сообщества, прежде чем продолжить. + +Значительные изменения, которые вы хотите внести в проект, должны быть предварительно обсуждены в задаче GitHub, в которой четко изложены изменения и преимущества этой функции. + +Небольшие изменения можно сразу же создать и отправить в репозиторий GitHub в виде запроса на включение изменений (Pull Request). + +### Создайте pull request + +Зафиксируйте свои изменения: + +```bash +git commit -m "Commit message" +git push +``` + +Затем откройте интерфейс Github и [создайте pull request](https://docs.github.com/en/get-started/quickstart/contributing-to-projects#making-a-pull-request). Пожалуйста, следуйте руководству из шаблона тела PR. + +После создания pull request он получает соответствующий номер, например, 123 (`pr_number`). + +### Напишите release notes + +`onETL` использует [towncrier](https://pypi.org/project/towncrier/) для управления журналом изменений. + +Чтобы отправить заметку об изменении для вашего PR, добавьте текстовый файл в папку [docs/changelog/next_release](./next_release). Он должен содержать объяснение того, как применение этого PR изменит способ взаимодействия конечных пользователей с проектом. Одного предложения обычно достаточно, но не стесняйтесь добавлять столько деталей, сколько считаете необходимым для того, чтобы пользователи поняли, что это значит. + +**Используйте прошедшее время** для текста в вашем фрагменте, потому что, в сочетании с другими, он станет частью "сводки новостей", сообщающей читателям **что изменилось** в конкретной версии библиотеки *с момента предыдущей версии*. + +Вам также следует использовать синтаксис reStructuredText для выделения кода (встроенного или блочного), связывания частей документации или внешних сайтов. Если вы хотите подписать свое изменение, не стесняйтесь добавить `` -- by\:user:`github-username` `` в конце (замените `github-username` на свое собственное!). + +Наконец, назовите свой файл в соответствии с соглашением, которое понимает Towncrier: он должен начинаться с номера задачи или PR, за которым следует точка, затем добавьте тип патча, например, `feature`, `doc`, `misc` и т.д., и добавьте `.rst` в качестве суффикса. Если вам нужно добавить более одного фрагмента, вы можете добавить необязательный порядковый номер (разделенный другой точкой) между типом и суффиксом. + +В общем, имя будет соответствовать шаблону `..rst`, где категории: + +- `feature`: Любая новая функция +- `bugfix`: Исправление ошибки +- `improvement`: Улучшение +- `doc`: Изменение в документации +- `dependency`: Изменения, связанные с зависимостями +- `misc`: Внутренние изменения в репозитории, такие как CI, тесты и изменения сборки + +Pull request может иметь более одного из этих компонентов, например, изменение кода может представить новую функцию, которая устаревает старую функцию, в этом случае следует добавить два фрагмента. Нет необходимо делать отдельный фрагмент документации для изменений документации, сопровождающих соответствующие изменения кода. + +#### Примеры добавления записей в журнал изменений в ваши Pull Requests + +```rst title="docs/changelog/next_release/1234.doc.1.rst" + +Added a ``:github:user:`` role to Sphinx config -- by :github:user:`someuser` +``` + +```rst title="docs/changelog/next_release/2345.bugfix.rst" + +Fixed behavior of ``WebDAV`` connector -- by :github:user:`someuser` +``` + +```rst title="docs/changelog/next_release/3456.feature.rst" + +Added support of ``timeout`` in ``S3`` connector +-- by :github:user:`someuser`, :github:user:`anotheruser` and :github:user:`otheruser` +``` + +!!! tip + + См. `pyproject.toml `_ для всех доступных категорий (``tool.towncrier.type``). + +#### Как пропустить проверку change notes? + +Просто добавьте метку `ci:skip-changelog` к pull request. + +#### Процесс релиза + +Перед тем, как делать релиз из ветки `develop`, выполните следующие шаги: + +0. Перейдите в ветку `develop` и обновите ее до актуального состояния + +```bash +git checkout develop +git pull -p +``` + +1. Сделайте резервную копию `NEXT_RELEASE.rst` + +```bash +cp "docs/changelog/NEXT_RELEASE.rst" "docs/changelog/temp_NEXT_RELEASE.rst" +``` + +2. Соберите Release notes с помощью Towncrier + +```bash +VERSION=$(cat onetl/VERSION) +towncrier build "--version=${VERSION}" --yes +``` + +3. Измените файл с журналом изменений на номер версии релиза + +```bash +mv docs/changelog/NEXT_RELEASE.rst "docs/changelog/${VERSION}.rst" +``` + +4. Удалите содержимое над заголовком номера версии в файле `${VERSION}.rst` + +```bash +awk '!/^.*towncrier release notes start/' "docs/changelog/${VERSION}.rst" > temp && mv temp "docs/changelog/${VERSION}.rst" +``` + +5. Обновите индекс журнала изменений + +```bash +awk -v version=${VERSION} '/DRAFT/{print;print " " version;next}1' docs/changelog/index.rst > temp && mv temp docs/changelog/index.rst +``` + +6. Восстановите файл `NEXT_RELEASE.rst` из резервной копии + +```bash +mv "docs/changelog/temp_NEXT_RELEASE.rst" "docs/changelog/NEXT_RELEASE.rst" +``` + +7. Зафиксируйте и отправьте изменения в ветку `develop` + +```bash +git add . +git commit -m "Prepare for release ${VERSION}" +git push +``` + +8. Объедините ветку `develop` с `master`, **БЕЗ** squashing + +```bash +git checkout master +git pull +git merge develop +git push +``` + +9. Добавьте git tag к последнему коммиту в ветке `master` + +```bash +git tag "$VERSION" +git push origin "$VERSION" +``` + +10. Обновите версию в ветке `develop` **после релиза**: + +```bash +git checkout develop + +NEXT_VERSION=$(echo "$VERSION" | awk -F. '/[0-9]+\./{$NF++;print}' OFS=.) +echo "$NEXT_VERSION" > onetl/VERSION + +git add . +git commit -m "Bump version" +git push +``` + +[towncrier philosophy](https://towncrier.readthedocs.io/en/stable/#philosophy) diff --git a/mddocs/ru/README.md b/mddocs/ru/README.md new file mode 100644 index 000000000..bed7994b8 --- /dev/null +++ b/mddocs/ru/README.md @@ -0,0 +1,660 @@ +--- +substitutions: + CI Status: |- + ```{image} https://github.com/MobileTeleSystems/onetl/workflows/Tests/badge.svg + :alt: Github Actions - latest CI build status + :target: https://github.com/MobileTeleSystems/onetl/actions + ``` + Documentation: |- + ```{image} https://readthedocs.org/projects/onetl/badge/?version=stable + :alt: Documentation - ReadTheDocs + :target: https://onetl.readthedocs.io/ + ``` + Logo: |- + ```{image} docs/_static/logo_wide.svg + :alt: onETL logo + :target: https://github.com/MobileTeleSystems/onetl + ``` + PyPI Downloads: |- + ```{image} https://img.shields.io/pypi/dm/onetl + :alt: PyPI - Downloads + :target: https://pypi.org/project/onetl/ + ``` + PyPI Latest Release: |- + ```{image} https://img.shields.io/pypi/v/onetl + :alt: PyPI - Latest Release + :target: https://pypi.org/project/onetl/ + ``` + PyPI License: |- + ```{image} https://img.shields.io/pypi/l/onetl.svg + :alt: PyPI - License + :target: https://github.com/MobileTeleSystems/onetl/blob/develop/LICENSE.txt + ``` + PyPI Python Version: |- + ```{image} https://img.shields.io/pypi/pyversions/onetl.svg + :alt: PyPI - Python Version + :target: https://pypi.org/project/onetl/ + ``` + Repo Status: |- + ```{image} https://www.repostatus.org/badges/latest/active.svg + :alt: Repo status - Active + :target: https://github.com/MobileTeleSystems/onetl + ``` + Test Coverage: |- + ```{image} https://codecov.io/gh/MobileTeleSystems/onetl/branch/develop/graph/badge.svg?token=RIO8URKNZJ + :alt: Test coverage - percent + :target: https://codecov.io/gh/MobileTeleSystems/onetl + ``` + pre-commit.ci Status: |- + ```{image} https://results.pre-commit.ci/badge/github/MobileTeleSystems/onetl/develop.svg + :alt: pre-commit.ci - status + :target: https://results.pre-commit.ci/latest/github/MobileTeleSystems/onetl/develop + ``` +--- + +(readme)= + +# onETL + +{{ Repo Status }} {{ PyPI Latest Release }} {{ PyPI License }} {{ PyPI Python Version }} {{ PyPI Downloads }} +{{ Documentation }} {{ CI Status }} {{ Test Coverage }} {{ pre-commit.ci Status }} + +{{ Logo }} + +## What is onETL? + +Python ETL/ELT library powered by [Apache Spark](https://spark.apache.org/) & other open-source tools. + +## Goals + +- Provide unified classes to extract data from (**E**) & load data to (**L**) various stores. +- Provides [Spark DataFrame API](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.DataFrame.html) for performing transformations (**T**) in terms of *ETL*. +- Provide direct assess to database, allowing to execute SQL queries, as well as DDL, DML, and call functions/procedures. This can be used for building up *ELT* pipelines. +- Support different [read strategies](https://onetl.readthedocs.io/en/stable/strategy/index.html) for incremental and batch data fetching. +- Provide [hooks](https://onetl.readthedocs.io/en/stable/hooks/index.html) & [plugins](https://onetl.readthedocs.io/en/stable/plugins.html) mechanism for altering behavior of internal classes. + +## Non-goals + +- onETL is not a Spark replacement. It just provides additional functionality that Spark does not have, and improves UX for end users. +- onETL is not a framework, as it does not have requirements to project structure, naming, the way of running ETL/ELT processes, configuration, etc. All of that should be implemented in some other tool. +- onETL is deliberately developed without any integration with scheduling software like Apache Airflow. All integrations should be implemented as separated tools. +- Only batch operations, no streaming. For streaming prefer [Apache Flink](https://flink.apache.org/). + +## Requirements + +- **Python 3.7 - 3.13** +- PySpark 2.3.x - 3.5.x (depends on used connector) +- Java 8+ (required by Spark, see below) +- Kerberos libs & GCC (required by `Hive`, `HDFS` and `SparkHDFS` connectors) + +## Supported storages + +```{eval-rst} ++--------------------+--------------+-------------------------------------------------------------------------------------------------------------------------+ +| Type | Storage | Powered by | ++====================+==============+=========================================================================================================================+ +| Database | Clickhouse | Apache Spark `JDBC Data Source `_ | ++ +--------------+ + +| | MSSQL | | ++ +--------------+ + +| | MySQL | | ++ +--------------+ + +| | Postgres | | ++ +--------------+ + +| | Oracle | | ++ +--------------+ + +| | Teradata | | ++ +--------------+-------------------------------------------------------------------------------------------------------------------------+ +| | Hive | Apache Spark `Hive integration `_ | ++ +--------------+-------------------------------------------------------------------------------------------------------------------------+ +| | Kafka | Apache Spark `Kafka integration `_ | ++ +--------------+-------------------------------------------------------------------------------------------------------------------------+ +| | Greenplum | VMware `Greenplum Spark connector `_ | ++ +--------------+-------------------------------------------------------------------------------------------------------------------------+ +| | MongoDB | `MongoDB Spark connector `_ | ++--------------------+--------------+-------------------------------------------------------------------------------------------------------------------------+ +| File | HDFS | `HDFS Python client `_ | ++ +--------------+-------------------------------------------------------------------------------------------------------------------------+ +| | S3 | `minio-py client `_ | ++ +--------------+-------------------------------------------------------------------------------------------------------------------------+ +| | SFTP | `Paramiko library `_ | ++ +--------------+-------------------------------------------------------------------------------------------------------------------------+ +| | FTP | `FTPUtil library `_ | ++ +--------------+ + +| | FTPS | | ++ +--------------+-------------------------------------------------------------------------------------------------------------------------+ +| | WebDAV | `WebdavClient3 library `_ | ++ +--------------+-------------------------------------------------------------------------------------------------------------------------+ +| | Samba | `pysmb library `_ | ++--------------------+--------------+-------------------------------------------------------------------------------------------------------------------------+ +| Files as DataFrame | SparkLocalFS | Apache Spark `File Data Source `_ | +| +--------------+ + +| | SparkHDFS | | +| +--------------+-------------------------------------------------------------------------------------------------------------------------+ +| | SparkS3 | `Hadoop AWS `_ library | ++--------------------+--------------+-------------------------------------------------------------------------------------------------------------------------+ +``` + +% documentation + +## Documentation + +See + +## How to install + +(install)= + +### Minimal installation + +(minimal-install)= + +Base `onetl` package contains: + +- `DBReader`, `DBWriter` and related classes +- `FileDownloader`, `FileUploader`, `FileMover` and related classes, like file filters & limits +- `FileDFReader`, `FileDFWriter` and related classes, like file formats +- Read Strategies & HWM classes +- Plugins support + +It can be installed via: + +```bash +pip install onetl +``` + +```{eval-rst} +.. warning:: + + This method does NOT include any connections. + + This method is recommended for use in third-party libraries which require for ``onetl`` to be installed, + but do not use its connection classes. +``` + +### With DB and FileDF connections + +(spark-install)= + +All DB connection classes (`Clickhouse`, `Greenplum`, `Hive` and others) +and all FileDF connection classes (`SparkHDFS`, `SparkLocalFS`, `SparkS3`) +require Spark to be installed. + +(java-install)= + +Firstly, you should install JDK. The exact installation instruction depends on your OS, here are some examples: + +```bash +yum install java-1.8.0-openjdk-devel # CentOS 7 + Spark 2 +dnf install java-11-openjdk-devel # CentOS 8 + Spark 3 +apt-get install openjdk-11-jdk # Debian-based + Spark 3 +``` + +(spark-compatibility-matrix)= + +#### Compatibility matrix + +| Spark | Python | Java | Scala | +| --------------------------------------------------------- | ---------- | ---------- | ----- | +| [2.3.x](https://spark.apache.org/docs/2.3.1/#downloading) | 3.7 only | 8 only | 2.11 | +| [2.4.x](https://spark.apache.org/docs/2.4.8/#downloading) | 3.7 only | 8 only | 2.11 | +| [3.2.x](https://spark.apache.org/docs/3.2.4/#downloading) | 3.7 - 3.10 | 8u201 - 11 | 2.12 | +| [3.3.x](https://spark.apache.org/docs/3.3.4/#downloading) | 3.7 - 3.12 | 8u201 - 17 | 2.12 | +| [3.4.x](https://spark.apache.org/docs/3.4.4/#downloading) | 3.7 - 3.12 | 8u362 - 20 | 2.12 | +| [3.5.x](https://spark.apache.org/docs/3.5.5/#downloading) | 3.8 - 3.13 | 8u371 - 20 | 2.12 | + +(pyspark-install)= + +Then you should install PySpark via passing `spark` to `extras`: + +```bash +pip install onetl[spark] # install latest PySpark +``` + +or install PySpark explicitly: + +```bash +pip install onetl pyspark==3.5.5 # install a specific PySpark version +``` + +or inject PySpark to `sys.path` in some other way BEFORE creating a class instance. +**Otherwise connection object cannot be created.** + +### With File connections + +(files-install)= + +All File (but not *FileDF*) connection classes (`FTP`, `SFTP`, `HDFS` and so on) requires specific Python clients to be installed. + +Each client can be installed explicitly by passing connector name (in lowercase) to `extras`: + +```bash +pip install onetl[ftp] # specific connector +pip install onetl[ftp,ftps,sftp,hdfs,s3,webdav,samba] # multiple connectors +``` + +To install all file connectors at once you can pass `files` to `extras`: + +```bash +pip install onetl[files] +``` + +**Otherwise class import will fail.** + +### With Kerberos support + +(kerberos-install)= + +Most of Hadoop instances set up with Kerberos support, +so some connections require additional setup to work properly. + +- `HDFS` + Uses [requests-kerberos](https://pypi.org/project/requests-kerberos/) and + [GSSApi](https://pypi.org/project/gssapi/) for authentication. + It also uses `kinit` executable to generate Kerberos ticket. +- `Hive` and `SparkHDFS` + require Kerberos ticket to exist before creating Spark session. + +So you need to install OS packages with: + +- `krb5` libs +- Headers for `krb5` +- `gcc` or other compiler for C sources + +The exact installation instruction depends on your OS, here are some examples: + +```bash +apt install libkrb5-dev krb5-user gcc # Debian-based +dnf install krb5-devel krb5-libs krb5-workstation gcc # CentOS, OracleLinux +``` + +Also you should pass `kerberos` to `extras` to install required Python packages: + +```bash +pip install onetl[kerberos] +``` + +### Full bundle + +(full-bundle-1)= + +To install all connectors and dependencies, you can pass `all` into `extras`: + +```bash +pip install onetl[all] + +# this is just the same as +pip install onetl[spark,files,kerberos] +``` + +```{eval-rst} +.. warning:: + + This method consumes a lot of disk space, and requires for Java & Kerberos libraries to be installed into your OS. +``` + +(quick-start)= + +## Quick start + +### MSSQL → Hive + +Read data from MSSQL, transform & write to Hive. + +```bash +# install onETL and PySpark +pip install onetl[spark] +``` + +```python +# Import pyspark to initialize the SparkSession +from pyspark.sql import SparkSession + +# import function to setup onETL logging +from onetl.log import setup_logging + +# Import required connections +from onetl.connection import MSSQL, Hive + +# Import onETL classes to read & write data +from onetl.db import DBReader, DBWriter + +# change logging level to INFO, and set up default logging format and handler +setup_logging() + +# Initialize new SparkSession with MSSQL driver loaded +maven_packages = MSSQL.get_packages() +spark = ( + SparkSession.builder.appName("spark_app_onetl_demo") + .config("spark.jars.packages", ",".join(maven_packages)) + .enableHiveSupport() # for Hive + .getOrCreate() +) + +# Initialize MSSQL connection and check if database is accessible +mssql = MSSQL( + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, + # These options are passed to MSSQL JDBC Driver: + extra={"applicationIntent": "ReadOnly"}, +).check() + +# >>> INFO:|MSSQL| Connection is available + +# Initialize DBReader +reader = DBReader( + connection=mssql, + source="dbo.demo_table", + columns=["on", "etl"], + # Set some MSSQL read options: + options=MSSQL.ReadOptions(fetchsize=10000), +) + +# checks that there is data in the table, otherwise raises exception +reader.raise_if_no_data() + +# Read data to DataFrame +df = reader.run() +df.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) + +# Apply any PySpark transformations +from pyspark.sql.functions import lit + +df_to_write = df.withColumn("engine", lit("onetl")) +df_to_write.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) +# |-- engine: string (nullable = false) + +# Initialize Hive connection +hive = Hive(cluster="rnd-dwh", spark=spark) + +# Initialize DBWriter +db_writer = DBWriter( + connection=hive, + target="dl_sb.demo_table", + # Set some Hive write options: + options=Hive.WriteOptions(if_exists="replace_entire_table"), +) + +# Write data from DataFrame to Hive +db_writer.run(df_to_write) + +# Success! +``` + +### SFTP → HDFS + +Download files from SFTP & upload them to HDFS. + +```bash +# install onETL with SFTP and HDFS clients, and Kerberos support +pip install onetl[hdfs,sftp,kerberos] +``` + +```python +# import function to setup onETL logging +from onetl.log import setup_logging + +# Import required connections +from onetl.connection import SFTP, HDFS + +# Import onETL classes to download & upload files +from onetl.file import FileDownloader, FileUploader + +# import filter & limit classes +from onetl.file.filter import Glob, ExcludeDir +from onetl.file.limit import MaxFilesCount + +# change logging level to INFO, and set up default logging format and handler +setup_logging() + +# Initialize SFTP connection and check it +sftp = SFTP( + host="sftp.test.com", + user="someuser", + password="somepassword", +).check() + +# >>> INFO:|SFTP| Connection is available + +# Initialize downloader +file_downloader = FileDownloader( + connection=sftp, + source_path="/remote/tests/Report", # path on SFTP + local_path="/local/onetl/Report", # local fs path + filters=[ + # download only files matching the glob + Glob("*.csv"), + # exclude files from this directory + ExcludeDir("/remote/tests/Report/exclude_dir/"), + ], + limits=[ + # download max 1000 files per run + MaxFilesCount(1000), + ], + options=FileDownloader.Options( + # delete files from SFTP after successful download + delete_source=True, + # mark file as failed if it already exist in local_path + if_exists="error", + ), +) + +# Download files to local filesystem +download_result = downloader.run() + +# Method run returns a DownloadResult object, +# which contains collection of downloaded files, divided to 4 categories +download_result + +# DownloadResult( +# successful=[ +# LocalPath('/local/onetl/Report/file_1.json'), +# LocalPath('/local/onetl/Report/file_2.json'), +# ], +# failed=[FailedRemoteFile('/remote/onetl/Report/file_3.json')], +# ignored=[RemoteFile('/remote/onetl/Report/file_4.json')], +# missing=[], +# ) + +# Raise exception if there are failed files, or there were no files in the remote filesystem +download_result.raise_if_failed() or download_result.raise_if_empty() + +# Do any kind of magic with files: rename files, remove header for csv files, ... +renamed_files = my_rename_function(download_result.success) + +# function removed "_" from file names +# [ +# LocalPath('/home/onetl/Report/file1.json'), +# LocalPath('/home/onetl/Report/file2.json'), +# ] + +# Initialize HDFS connection +hdfs = HDFS( + host="my.name.node", + user="someuser", + password="somepassword", # or keytab +) + +# Initialize uploader +file_uploader = FileUploader( + connection=hdfs, + target_path="/user/onetl/Report/", # hdfs path +) + +# Upload files from local fs to HDFS +upload_result = file_uploader.run(renamed_files) + +# Method run returns a UploadResult object, +# which contains collection of uploaded files, divided to 4 categories +upload_result + +# UploadResult( +# successful=[RemoteFile('/user/onetl/Report/file1.json')], +# failed=[FailedLocalFile('/local/onetl/Report/file2.json')], +# ignored=[], +# missing=[], +# ) + +# Raise exception if there are failed files, or there were no files in the local filesystem, or some input file is missing +upload_result.raise_if_failed() or upload_result.raise_if_empty() or upload_result.raise_if_missing() + +# Success! +``` + +### S3 → Postgres + +Read files directly from S3 path, convert them to dataframe, transform it and then write to a database. + +```bash +# install onETL and PySpark +pip install onetl[spark] +``` + +```python +# Import pyspark to initialize the SparkSession +from pyspark.sql import SparkSession + +# import function to setup onETL logging +from onetl.log import setup_logging + +# Import required connections +from onetl.connection import Postgres, SparkS3 + +# Import onETL classes to read files +from onetl.file import FileDFReader +from onetl.file.format import CSV + +# Import onETL classes to write data +from onetl.db import DBWriter + +# change logging level to INFO, and set up default logging format and handler +setup_logging() + +# Initialize new SparkSession with Hadoop AWS libraries and Postgres driver loaded +maven_packages = SparkS3.get_packages(spark_version="3.5.5") + Postgres.get_packages() +exclude_packages = SparkS3.get_exclude_packages() +spark = ( + SparkSession.builder.appName("spark_app_onetl_demo") + .config("spark.jars.packages", ",".join(maven_packages)) + .config("spark.jars.excludes", ",".join(exclude_packages)) + .getOrCreate() +) + +# Initialize S3 connection and check it +spark_s3 = SparkS3( + host="s3.test.com", + protocol="https", + bucket="my-bucket", + access_key="somekey", + secret_key="somesecret", + # Access bucket as s3.test.com/my-bucket + extra={"path.style.access": True}, + spark=spark, +).check() + +# >>> INFO:|SparkS3| Connection is available + +# Describe file format and parsing options +csv = CSV( + delimiter=";", + header=True, + encoding="utf-8", +) + +# Describe DataFrame schema of files +from pyspark.sql.types import ( + DateType, + DoubleType, + IntegerType, + StringType, + StructField, + StructType, + TimestampType, +) + +df_schema = StructType( + [ + StructField("id", IntegerType()), + StructField("phone_number", StringType()), + StructField("region", StringType()), + StructField("birth_date", DateType()), + StructField("registered_at", TimestampType()), + StructField("account_balance", DoubleType()), + ], +) + +# Initialize file df reader +reader = FileDFReader( + connection=spark_s3, + source_path="/remote/tests/Report", # path on S3 there *.csv files are located + format=csv, # file format with specific parsing options + df_schema=df_schema, # columns & types +) + +# Read files directly from S3 as Spark DataFrame +df = reader.run() + +# Check that DataFrame schema is same as expected +df.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) + +# Apply any PySpark transformations +from pyspark.sql.functions import lit + +df_to_write = df.withColumn("engine", lit("onetl")) +df_to_write.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) +# |-- engine: string (nullable = false) + +# Initialize Postgres connection +postgres = Postgres( + host="192.169.11.23", + user="onetl", + password="somepassword", + database="mydb", + spark=spark, +) + +# Initialize DBWriter +db_writer = DBWriter( + connection=postgres, + # write to specific table + target="public.my_table", + # with some writing options + options=Postgres.WriteOptions(if_exists="append"), +) + +# Write DataFrame to Postgres table +db_writer.run(df_to_write) + +# Success! +``` diff --git a/mddocs/ru/SECURITY.md b/mddocs/ru/SECURITY.md new file mode 100644 index 000000000..5d9dce52b --- /dev/null +++ b/mddocs/ru/SECURITY.md @@ -0,0 +1,25 @@ +# Security + +## Supported Python versions + +3.7 or above + +## Product development security recommendations + +1. Update dependencies to last stable version +2. Build SBOM for the project +3. Perform SAST (Static Application Security Testing) where possible + +## Product development security requirements + +1. No binaries in repository +2. No passwords, keys, access tokens in source code +3. No "Critical" and/or "High" vulnerabilities in contributed source code + +## Vulnerability reports + +Please, use email [mailto:onetools@mts.ru](mailto:onetools@mts.ru) for reporting security issues or anything that can cause any consequences for security. + +Please avoid any public disclosure (including registering issues) at least until it is fixed. + +Thank you in advance for understanding. diff --git a/mddocs/ru/concepts.md b/mddocs/ru/concepts.md new file mode 100644 index 000000000..b8b0baf4a --- /dev/null +++ b/mddocs/ru/concepts.md @@ -0,0 +1,458 @@ +# Concepts + +Here you can find detailed documentation about each one of the onETL concepts and how to use them. + +## Connection + +### Basics + +onETL is used to pull and push data into other systems, and so it has a first-class `Connection` concept for storing credentials that are used to communicate with external systems. + +A `Connection` is essentially a set of parameters, such as username, password, hostname. + +To create a connection to a specific storage type, you must use a class that matches the storage type. The class name is the same as the storage type name (`Oracle`, `MSSQL`, `SFTP`, etc): + +```python +from onetl.connection import SFTP + +sftp = SFTP( + host="sftp.test.com", + user="onetl", + password="onetl", +) +``` + +All connection types are inherited from the parent class `BaseConnection`. + +### Class diagram + +```{eval-rst} +.. plantuml:: + + @startuml + left to right direction + skinparam classFontSize 20 + skinparam class { + BackgroundColor<> LightGreen + BackgroundColor<> Khaki + BackgroundColor<> LightBlue + StereotypeFontColor<> Transparent + StereotypeFontColor<> Transparent + StereotypeFontColor<> Transparent + } + + class BaseConnection { + } + + class DBConnection <>{ + } + DBConnection --|> BaseConnection + + class Hive <>{ + } + Hive --|> DBConnection + + class Greenplum <>{ + } + Greenplum --|> DBConnection + + class MongoDB <>{ + } + MongoDB --|> DBConnection + + class Kafka <>{ + } + Kafka --|> DBConnection + + class JDBCConnection <>{ + } + JDBCConnection --|> DBConnection + + class Clickhouse <>{ + } + Clickhouse --|> JDBCConnection + + class MSSQL <>{ + } + MSSQL --|> JDBCConnection + + class MySQL <>{ + } + MySQL --|> JDBCConnection + + class Postgres <>{ + } + Postgres --|> JDBCConnection + + class Oracle <>{ + } + Oracle --|> JDBCConnection + + class Teradata <>{ + } + Teradata --|> JDBCConnection + + class FileConnection <>{ + } + FileConnection --|> BaseConnection + + class FTP <>{ + } + FTP --|> FileConnection + + class FTPS <>{ + } + FTPS --|> FileConnection + + class HDFS <>{ + } + HDFS --|> FileConnection + + class WebDAV <>{ + } + WebDAV --|> FileConnection + + class Samba <>{ + } + Samba --|> FileConnection + + class SFTP <>{ + } + SFTP --|> FileConnection + + class S3 <>{ + } + S3 --|> FileConnection + + class FileDFConnection <>{ + } + FileDFConnection --|> BaseConnection + + class SparkHDFS <>{ + } + SparkHDFS --|> FileDFConnection + + class SparkLocalFS <>{ + } + SparkLocalFS --|> FileDFConnection + + class SparkS3 <>{ + } + SparkS3 --|> FileDFConnection + + @enduml +``` + +### DBConnection + +Classes inherited from `DBConnection` could be used for accessing databases. + +A `DBConnection` could be instantiated as follows: + +```python +from onetl.connection import MSSQL + +mssql = MSSQL( + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, +) +``` + +where **spark** is the current SparkSession. +`onETL` uses `Spark` and specific Java connectors under the hood to work with databases. + +For a description of other parameters, see the documentation for the {ref}`available DBConnections `. + +### FileConnection + +Classes inherited from `FileConnection` could be used to access files stored on the different file systems/file servers + +A `FileConnection` could be instantiated as follows: + +```python +from onetl.connection import SFTP + +sftp = SFTP( + host="sftp.test.com", + user="onetl", + password="onetl", +) +``` + +For a description of other parameters, see the documentation for the {ref}`available FileConnections `. + +### FileDFConnection + +Classes inherited from `FileDFConnection` could be used for accessing files as Spark DataFrames. + +A `FileDFConnection` could be instantiated as follows: + +```python +from onetl.connection import SparkHDFS + +spark_hdfs = SparkHDFS( + host="namenode1.domain.com", + cluster="mycluster", + spark=spark, +) +``` + +where **spark** is the current SparkSession. +`onETL` uses `Spark` and specific Java connectors under the hood to work with DataFrames. + +For a description of other parameters, see the documentation for the {ref}`available FileDFConnections `. + +### Checking connection availability + +Once you have created a connection, you can check the database/filesystem availability using the method `check()`: + +```python +mssql.check() +sftp.check() +spark_hdfs.check() +``` + +It will raise an exception if database/filesystem cannot be accessed. + +This method returns connection itself, so you can create connection and immediately check its availability: + +```Python +mssql = MSSQL( + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, +).check() # <-- +``` + +## Extract/Load data + +### Basics + +As we said above, onETL is used to extract data from and load data into remote systems. + +onETL provides several classes for this: + +> - {ref}`DBReader ` +> - {ref}`DBWriter ` +> - {ref}`FileDFReader ` +> - {ref}`FileDFWriter ` +> - {ref}`FileDownloader ` +> - {ref}`FileUploader ` +> - {ref}`FileMover ` + +All of these classes have a method `run()` that starts extracting/loading the data: + +```python +from onetl.db import DBReader, DBWriter + +reader = DBReader( + connection=mssql, + source="dbo.demo_table", + columns=["column_1", "column_2"], +) + +# Read data as Spark DataFrame +df = reader.run() + +db_writer = DBWriter( + connection=hive, + target="dl_sb.demo_table", +) + +# Save Spark DataFrame to Hive table +writer.run(df) +``` + +### Extract data + +To extract data you can use classes: + +| | Use case | Connection | `run()` gets | `run()` returns | +| ------------------------------------ | ----------------------------------------- | ------------------------------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| {ref}`DBReader ` | Reading data from a database | Any {ref}`DBConnection ` | - | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | +| {ref}`FileDFReader ` | Read data from a file or set of files | Any {ref}`FileDFConnection ` | No input, or List[File path on FileSystem] | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | +| {ref}`FileDownloader ` | Download files from remote FS to local FS | Any {ref}`FileConnection ` | No input, or List[File path on remote FileSystem] | {ref}`DownloadResult ` | + +### Load data + +To load data you can use classes: + +| | Use case | Connection | `run()` gets | `run()` returns | +| ----------------------------------- | -------------------------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | +| {ref}`DBWriter ` | Writing data from a DataFrame to a database | Any {ref}`DBConnection ` | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | None | +| {ref}`FileDFWriter ` | Writing data from a DataFrame to a folder | Any {ref}`FileDFConnection ` | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | None | +| {ref}`FileUploader ` | Uploading files from a local FS to remote FS | Any {ref}`FileConnection ` | List[File path on local FileSystem] | {ref}`UploadResult ` | + +### Manipulate data + +To manipulate data you can use classes: + +| | Use case | Connection | `run()` gets | `run()` returns | +| ----------------------------- | ------------------------------------------- | -------------------------------------------- | ------------------------------------ | ------------------------------------- | +| {ref}`FileMover ` | Move files between directories in remote FS | Any {ref}`FileConnection ` | List[File path on remote FileSystem] | {ref}`MoveResult ` | + +### Options + +Extract and load classes have a `options` parameter, which has a special meaning: + +> - all other parameters - *WHAT* we extract / *WHERE* we load to +> - `options` parameter - *HOW* we extract/load data + +```python +db_reader = DBReader( + # WHAT do we read: + connection=mssql, + source="dbo.demo_table", # some table from MSSQL + columns=["column_1", "column_2"], # but only specific set of columns + where="column_2 > 1000", # only rows matching the clause + # HOW do we read: + options=MSSQL.ReadOptions( + numPartitions=10, # read in 10 parallel jobs + partitionColumn="id", # balance data read by assigning each job a part of data using `hash(id) mod N` expression + partitioningMode="hash", + fetchsize=1000, # each job will fetch block of 1000 rows each on every read attempt + ), +) + +db_writer = DBWriter( + # WHERE do we write to - to some table in Hive + connection=hive, + target="dl_sb.demo_table", + # HOW do we write - overwrite all the data in the existing table + options=Hive.WriteOptions(if_exists="replace_entire_table"), +) + +file_downloader = FileDownloader( + # WHAT do we download - files from some dir in SFTP + connection=sftp, + source_path="/source", + filters=[Glob("*.csv")], # only CSV files + limits=[MaxFilesCount(1000)], # 1000 files max + # WHERE do we download to - a specific dir on local FS + local_path="/some", + # HOW do we download: + options=FileDownloader.Options( + delete_source=True, # after downloading each file remove it from source_path + if_exists="replace_file", # replace existing files in the local_path + ), +) + +file_uploader = FileUploader( + # WHAT do we upload - files from some local dir + local_path="/source", + # WHERE do we upload to- specific remote dir in HDFS + connection=hdfs, + target_path="/some", + # HOW do we upload: + options=FileUploader.Options( + delete_local=True, # after uploading each file remove it from local_path + if_exists="replace_file", # replace existing files in the target_path + ), +) + +file_mover = FileMover( + # WHAT do we move - files in some remote dir in HDFS + source_path="/source", + connection=hdfs, + # WHERE do we move files to + target_path="/some", # a specific remote dir within the same HDFS connection + # HOW do we load - replace existing files in the target_path + options=FileMover.Options(if_exists="replace_file"), +) + +file_df_reader = FileDFReader( + # WHAT do we read - *.csv files from some dir in S3 + connection=s3, + source_path="/source", + file_format=CSV(), + # HOW do we read - load files from /source/*.csv, not from /source/nested/*.csv + options=FileDFReader.Options(recursive=False), +) + +file_df_writer = FileDFWriter( + # WHERE do we write to - as .csv files in some dir in S3 + connection=s3, + target_path="/target", + file_format=CSV(), + # HOW do we write - replace all existing files in /target, if exists + options=FileDFWriter.Options(if_exists="replace_entire_directory"), +) +``` + +More information about `options` could be found on {ref}`DB connection `. and +{ref}`file-downloader` / {ref}`file-uploader` / {ref}`file-mover` / {ref}`file-df-reader` / {ref}`file-df-writer` documentation + +### Read Strategies + +onETL have several builtin strategies for reading data: + +1. [Snapshot strategy](strategy/snapshot_strategy.html) (default strategy) +2. [Incremental strategy](strategy/incremental_strategy.html) +3. [Snapshot batch strategy](strategy/snapshot_batch_strategy.html) +4. [Incremental batch strategy](strategy/incremental_batch_strategy.html) + +For example, an incremental strategy allows you to get only new data from the table: + +```python +from onetl.strategy import IncrementalStrategy + +reader = DBReader( + connection=mssql, + source="dbo.demo_table", + hwm_column="id", # detect new data based on value of "id" column +) + +# first run +with IncrementalStrategy(): + df = reader.run() + +sleep(3600) + +# second run +with IncrementalStrategy(): + # only rows, that appeared in the source since previous run + df = reader.run() +``` + +or get only files which were not downloaded before: + +```python +from onetl.strategy import IncrementalStrategy + +file_downloader = FileDownloader( + connection=sftp, + source_path="/remote", + local_path="/local", + hwm_type="file_list", # save all downloaded files to a list, and exclude files already present in this list +) + +# first run +with IncrementalStrategy(): + files = file_downloader.run() + +sleep(3600) + +# second run +with IncrementalStrategy(): + # only files, that appeared in the source since previous run + files = file_downloader.run() +``` + +Most of strategies are based on {ref}`hwm`, Please check each strategy documentation for more details + +### Why just not use Connection class for extract/load? + +Connections are very simple, they have only a set of some basic operations, +like `mkdir`, `remove_file`, `get_table_schema`, and so on. + +High-level operations, like +: - {ref}`strategy` support + - Handling metadata push/pull + - Handling different options, like `if_exists="replace_file"` in case of file download/upload + +is moved to a separate class which calls the connection object methods to perform some complex logic. diff --git a/mddocs/ru/contributing.md b/mddocs/ru/contributing.md new file mode 100644 index 000000000..cc9ee4419 --- /dev/null +++ b/mddocs/ru/contributing.md @@ -0,0 +1,3 @@ +```{eval-rst} +.. include:: ../CONTRIBUTING.rst +``` diff --git a/mddocs/ru/index.md b/mddocs/ru/index.md new file mode 100644 index 000000000..46642bf4e --- /dev/null +++ b/mddocs/ru/index.md @@ -0,0 +1,87 @@ +```{eval-rst} +.. include:: ../README.rst + :end-before: |Logo| +``` + +```{image} _static/logo_wide.svg +:alt: onETL logo +``` + +```{eval-rst} +.. include:: ../README.rst + :start-after: |Logo| + :end-before: documentation +``` + +```{toctree} +:caption: How to +:hidden: true +:maxdepth: 2 + +self +install/index +quickstart +concepts +logging +troubleshooting/index +``` + +```{toctree} +:caption: Connection +:hidden: true +:maxdepth: 3 + +connection/index +``` + +```{toctree} +:caption: DB classes +:hidden: true +:maxdepth: 3 + +db/index +``` + +```{toctree} +:caption: File classes +:hidden: true +:maxdepth: 3 + +file/index +``` + +```{toctree} +:caption: File DataFrame classes +:hidden: true +:maxdepth: 3 + +file_df/index +``` + +```{toctree} +:caption: Read strategies and HWM +:hidden: true +:maxdepth: 2 + +strategy/index +hwm_store/index +``` + +```{toctree} +:caption: Hooks & plugins +:hidden: true +:maxdepth: 2 + +hooks/index +plugins +``` + +```{toctree} +:caption: Development +:hidden: true +:maxdepth: 2 + +changelog +contributing +security +``` diff --git a/mddocs/ru/logging.md b/mddocs/ru/logging.md new file mode 100644 index 000000000..dae7a9d73 --- /dev/null +++ b/mddocs/ru/logging.md @@ -0,0 +1,171 @@ +(logging)= + +# Logging + +Logging is quite important to understand what's going on under the hood of onETL. + +Default logging level for Python interpreters is `WARNING`, +but most of onETL logs are in `INFO` level, so users usually don't see much. + +To change logging level, there is a function {obj}`setup_logging ` +which should be called at the top of the script: + +```python +from onetl.log import setup_logging +from other.lib import some, more, imports + +setup_logging() + +# rest of code +... +``` + +This changes both log level and log formatting to something like this: + +```{eval-rst} +.. dropdown:: See logs + + .. code:: text + + 2024-04-12 10:12:10,834 [INFO ] MainThread: |onETL| Using IncrementalStrategy as a strategy + 2024-04-12 10:12:10,835 [INFO ] MainThread: =================================== DBReader.run() starts =================================== + 2024-04-12 10:12:10,835 [INFO ] MainThread: |DBReader| Getting Spark type for HWM expression: 'updated_at' + 2024-04-12 10:12:10,836 [INFO ] MainThread: |MSSQL| Fetching schema of table 'source_schema.table' ... + 2024-04-12 10:12:11,636 [INFO ] MainThread: |MSSQL| Schema fetched. + 2024-04-12 10:12:11,642 [INFO ] MainThread: |DBReader| Got Spark field: StructField('updated_at', TimestampType(), True) + 2024-04-12 10:12:11,642 [INFO ] MainThread: |DBReader| Detected HWM type: 'ColumnDateTimeHWM' + 2024-04-12 10:12:11,643 [INFO ] MainThread: |IncrementalStrategy| Fetching HWM from HorizonHWMStore: + 2024-04-12 10:12:11,643 [INFO ] MainThread: name = 'updated_at#source_schema.table@mssql:/mssql.host:1433/somedb' + 2024-04-12 10:12:12,181 [INFO ] MainThread: |IncrementalStrategy| Fetched HWM: + 2024-04-12 10:12:12,182 [INFO ] MainThread: hwm = ColumnDateTimeHWM( + 2024-04-12 10:12:12,182 [INFO ] MainThread: name = 'updated_at#source_schema.table@mssql:/mssql.host:1433/somedb', + 2024-04-12 10:12:12,182 [INFO ] MainThread: entity = 'source_schema.table', + 2024-04-12 10:12:12,182 [INFO ] MainThread: expression = 'updated_at', + 2024-04-12 10:12:12,184 [INFO ] MainThread: value = datetime.datetime(2024, 4, 11, 18, 10, 2, 120000), + 2024-04-12 10:12:12,184 [INFO ] MainThread: ) + 2024-04-12 10:12:12,184 [INFO ] MainThread: |MSSQL| -> |Spark| Reading DataFrame from source using parameters: + 2024-04-12 10:12:12,185 [INFO ] MainThread: source = 'source_schema.table' + 2024-04-12 10:12:12,185 [INFO ] MainThread: columns = [ + 2024-04-12 10:12:12,185 [INFO ] MainThread: 'id', + 2024-04-12 10:12:12,186 [INFO ] MainThread: 'new_value', + 2024-04-12 10:12:12,186 [INFO ] MainThread: 'old_value', + 2024-04-12 10:12:12,186 [INFO ] MainThread: 'updated_at', + 2024-04-12 10:12:12,186 [INFO ] MainThread: ] + 2024-04-12 10:12:12,187 [INFO ] MainThread: where = "field = 'some'" + 2024-04-12 10:12:12,187 [INFO ] MainThread: hwm = AutoDetectHWM( + 2024-04-12 10:12:12,187 [INFO ] MainThread: name = 'updated_at#source_schema.table@mssql:/mssql.host:1433/somedb', + 2024-04-12 10:12:12,187 [INFO ] MainThread: entity = 'source_schema.table', + 2024-04-12 10:12:12,187 [INFO ] MainThread: expression = 'updated_at', + 2024-04-12 10:12:12,188 [INFO ] MainThread: ) + 2024-04-12 10:12:12,188 [INFO ] MainThread: options = { + 2024-04-12 10:12:12,188 [INFO ] MainThread: 'fetchsize': 100000, + 2024-04-12 10:12:12,188 [INFO ] MainThread: 'numPartitions': 1, + 2024-04-12 10:12:12,189 [INFO ] MainThread: 'partitioningMode': 'range', + 2024-04-12 10:12:12,189 [INFO ] MainThread: } + 2024-04-12 10:12:12,189 [INFO ] MainThread: |MSSQL| Checking connection availability... + 2024-04-12 10:12:12,189 [INFO ] MainThread: |MSSQL| Using connection parameters: + 2024-04-12 10:12:12,190 [INFO ] MainThread: user = 'db_user' + 2024-04-12 10:12:12,190 [INFO ] MainThread: password = SecretStr('**********') + 2024-04-12 10:12:12,190 [INFO ] MainThread: host = 'mssql.host' + 2024-04-12 10:12:12,190 [INFO ] MainThread: port = 1433 + 2024-04-12 10:12:12,191 [INFO ] MainThread: database = 'somedb' + 2024-04-12 10:12:12,191 [INFO ] MainThread: extra = {'applicationIntent': 'ReadOnly', 'trustServerCertificate': 'true'} + 2024-04-12 10:12:12,191 [INFO ] MainThread: jdbc_url = 'jdbc:sqlserver:/mssql.host:1433' + 2024-04-12 10:12:12,579 [INFO ] MainThread: |MSSQL| Connection is available. + 2024-04-12 10:12:12,581 [INFO ] MainThread: |MSSQL| Executing SQL query (on driver): + 2024-04-12 10:12:12,581 [INFO ] MainThread: SELECT + 2024-04-12 10:12:12,581 [INFO ] MainThread: MIN(updated_at) AS "min", + 2024-04-12 10:12:12,582 [INFO ] MainThread: MAX(updated_at) AS "max" + 2024-04-12 10:12:12,582 [INFO ] MainThread: FROM + 2024-04-12 10:12:12,582 [INFO ] MainThread: source_schema.table + 2024-04-12 10:12:12,582 [INFO ] MainThread: WHERE + 2024-04-12 10:12:12,582 [INFO ] MainThread: (field = 'some') + 2024-04-12 10:12:12,583 [INFO ] MainThread: AND + 2024-04-12 10:12:12,583 [INFO ] MainThread: (updated_at >= CAST('2024-04-11T18:10:02.120000' AS datetime2)) + 2024-04-12 10:16:22,537 [INFO ] MainThread: |MSSQL| Received values: + 2024-04-12 10:16:22,538 [INFO ] MainThread: MIN(updated_at) = datetime.datetime(2024, 4, 11, 21, 10, 7, 397000) + 2024-04-12 10:16:22,538 [INFO ] MainThread: MAX(updated_at) = datetime.datetime(2024, 4, 12, 13, 12, 2, 123000) + 2024-04-12 10:16:22,540 [INFO ] MainThread: |MSSQL| Executing SQL query (on executor): + 2024-04-12 10:16:22,540 [INFO ] MainThread: SELECT + 2024-04-12 10:16:22,540 [INFO ] MainThread: id, + 2024-04-12 10:16:22,541 [INFO ] MainThread: new_value, + 2024-04-12 10:16:22,541 [INFO ] MainThread: old_value, + 2024-04-12 10:16:22,541 [INFO ] MainThread: updated_at + 2024-04-12 10:16:22,541 [INFO ] MainThread: FROM + 2024-04-12 10:16:22,541 [INFO ] MainThread: source_schema.table + 2024-04-12 10:16:22,542 [INFO ] MainThread: WHERE + 2024-04-12 10:16:22,542 [INFO ] MainThread: (field = 'some') + 2024-04-12 10:16:22,542 [INFO ] MainThread: AND + 2024-04-12 10:16:22,542 [INFO ] MainThread: (updated_at > CAST('2024-04-11T18:10:02.120000' AS datetime2)) + 2024-04-12 10:16:22,542 [INFO ] MainThread: AND + 2024-04-12 10:16:22,542 [INFO ] MainThread: (updated_at <= CAST('2024-04-12T13:12:02.123000' AS datetime2)) + 2024-04-12 10:16:22,892 [INFO ] MainThread: |Spark| DataFrame successfully created from SQL statement + 2024-04-12 10:16:22,892 [INFO ] MainThread: ------------------------------------ DBReader.run() ends ------------------------------------ + 2024-04-12 10:40:42,409 [INFO ] MainThread: =================================== DBWriter.run() starts =================================== + 2024-04-12 10:40:42,409 [INFO ] MainThread: |Spark| -> |Hive| Writing DataFrame to target using parameters: + 2024-04-12 10:40:42,410 [INFO ] MainThread: target = 'target_source_schema.table' + 2024-04-12 10:40:42,410 [INFO ] MainThread: options = { + 2024-04-12 10:40:42,410 [INFO ] MainThread: 'mode': 'append', + 2024-04-12 10:40:42,410 [INFO ] MainThread: 'format': 'orc', + 2024-04-12 10:40:42,410 [INFO ] MainThread: 'partitionBy': 'part_dt', + 2024-04-12 10:40:42,410 [INFO ] MainThread: } + 2024-04-12 10:40:42,411 [INFO ] MainThread: df_schema: + 2024-04-12 10:40:42,412 [INFO ] MainThread: root + 2024-04-12 10:40:42,412 [INFO ] MainThread: |-- id: integer (nullable = true) + 2024-04-12 10:40:42,413 [INFO ] MainThread: |-- new_value: string (nullable = true) + 2024-04-12 10:40:42,413 [INFO ] MainThread: |-- old_value: string (nullable = true) + 2024-04-12 10:40:42,413 [INFO ] MainThread: |-- updated_at: timestamp (nullable = true) + 2024-04-12 10:40:42,413 [INFO ] MainThread: |-- part_dt: date (nullable = true) + 2024-04-12 10:40:42,414 [INFO ] MainThread: + 2024-04-12 10:40:42,421 [INFO ] MainThread: |Hive| Checking connection availability... + 2024-04-12 10:40:42,421 [INFO ] MainThread: |Hive| Using connection parameters: + 2024-04-12 10:40:42,421 [INFO ] MainThread: cluster = 'dwh' + 2024-04-12 10:40:42,475 [INFO ] MainThread: |Hive| Connection is available. + 2024-04-12 10:40:42,476 [INFO ] MainThread: |Hive| Fetching schema of table 'target_source_schema.table' ... + 2024-04-12 10:40:43,518 [INFO ] MainThread: |Hive| Schema fetched. + 2024-04-12 10:40:43,521 [INFO ] MainThread: |Hive| Table 'target_source_schema.table' already exists + 2024-04-12 10:40:43,521 [WARNING ] MainThread: |Hive| User-specified options {'partitionBy': 'part_dt'} are ignored while inserting into existing table. Using only table parameters from Hive metastore + 2024-04-12 10:40:43,782 [INFO ] MainThread: |Hive| Inserting data into existing table 'target_source_schema.table' ... + 2024-04-12 11:06:07,396 [INFO ] MainThread: |Hive| Data is successfully inserted into table 'target_source_schema.table'. + 2024-04-12 11:06:07,397 [INFO ] MainThread: ------------------------------------ DBWriter.run() ends ------------------------------------ + 2024-04-12 11:06:07,397 [INFO ] MainThread: |onETL| Exiting IncrementalStrategy + 2024-04-12 11:06:07,397 [INFO ] MainThread: |IncrementalStrategy| Saving HWM to 'HorizonHWMStore': + 2024-04-12 11:06:07,397 [INFO ] MainThread: hwm = ColumnDateTimeHWM( + 2024-04-12 11:06:07,397 [INFO ] MainThread: name = 'updated_at#source_schema.table@mssql:/mssql.host:1433/somedb', + 2024-04-12 11:06:07,397 [INFO ] MainThread: entity = 'source_source_schema.table', + 2024-04-12 11:06:07,397 [INFO ] MainThread: expression = 'updated_at', + 2024-04-12 11:06:07,397 [INFO ] MainThread: value = datetime.datetime(2024, 4, 12, 13, 12, 2, 123000), + 2024-04-12 11:06:07,397 [INFO ] MainThread: ) + 2024-04-12 11:06:07,495 [INFO ] MainThread: |IncrementalStrategy| HWM has been saved +``` + +Each step performed by onETL is extensively logged, which should help with debugging. + +You can make logs even more verbose by changing level to `DEBUG`: + +```python +from onetl.log import setup_logging + +setup_logging(level="DEBUG", enable_clients=True) + +# rest of code +... +``` + +This also changes log level for all underlying Python libraries, e.g. showing each HTTP request being made, and so on. + +```{eval-rst} +.. currentmodule:: onetl.log +``` + +```{eval-rst} +.. autofunction:: setup_logging +``` + +```{eval-rst} +.. autofunction:: setup_clients_logging +``` + +```{eval-rst} +.. autofunction:: set_default_logging_format +``` diff --git a/mddocs/ru/plugins.md b/mddocs/ru/plugins.md new file mode 100644 index 000000000..a7904b076 --- /dev/null +++ b/mddocs/ru/plugins.md @@ -0,0 +1,147 @@ +(plugins)= + +# Plugins + +:::{versionadded} 0.6.0 +::: + +## What are plugins? + +### Terms + +- `Plugin` - some Python package which implements some extra functionality for onETL, like {ref}`hooks` +- `Plugin autoimport` - onETL behavior which allows to automatically import this package if it contains proper metadata (`entry_points`) + +### Features + +Plugins mechanism allows to: + +- Automatically register {ref}`hooks` which can alter onETL behavior +- Automatically register new classes, like HWM type, HWM stores and so on + +### Limitations + +Unlike other projects (like *Airflow 1.x*), plugins does not inject imported classes or functions to `onetl.*` namespace. +Users should import classes from the plugin package **explicitly** to avoid name collisions. + +## How to implement plugin? + +Create a Python package `some-plugin` with a file `some_plugin/setup.py`: + +```python +# some_plugin/setup.py +from setuptools import setup + +setup( + # if you want to import something from onETL, add it to requirements list + install_requires=["onetl"], + entry_points={ + # this key enables plugins autoimport functionality + "onetl.plugins": [ + "some-plugin-name=some_plugin.module", # automatically import all module content + "some-plugin-class=some_plugin.module.internals:MyClass", # import a specific class + "some-plugin-function=some_plugin.module.internals:my_function", # import a specific function + ], + }, +) +``` + +See [setuptools documentation for entry_points](https://setuptools.pypa.io/en/latest/userguide/entry_point.html) + +## How plugins are imported? + +- User should install a package implementing the plugin: + +```bash +pip install some-package +``` + +- Then user should import something from `onetl` module or its submodules: + +```python +import onetl +from onetl.connection import SomeConnection + +# and so on +``` + +- This import automatically executes something like: + +```python +import some_plugin.module +from some_plugin.module.internals import MyClass +from some_plugin.module.internals import my_function +``` + +If specific module/class/function uses some registration capabilities of onETL, +like {ref}`hook-decorator`, it will be executed during this import. + +## How to enable/disable plugins? + +:::{versionadded} 0.7.0 +::: + +### Disable/enable all plugins + +By default plugins are enabled. + +To disabled them, you can set environment variable `ONETL_PLUGINS_ENABLED` to `false` BEFORE +importing onETL. This will disable all plugins autoimport. + +But user is still be able to explicitly import `some_plugin.module`, executing +all decorators and registration capabilities of onETL. + +### Disable a specific plugin (blacklist) + +If some plugin is failing during import, you can disable it by setting up environment variable +`ONETL_PLUGINS_BLACKLIST=some-failing-plugin`. Multiple plugin names could be passed with `,` as delimiter. + +Again, this environment variable should be set BEFORE importing onETL. + +### Disable all plugins except a specific one (whitelist) + +You can also disable all plugins except a specific one by setting up environment variable +`ONETL_PLUGINS_WHITELIST=some-not-failing-plugin`. Multiple plugin names could be passed with `,` as delimiter. + +Again, this environment variable should be set BEFORE importing onETL. + +If both whitelist and blacklist environment variables are set, blacklist has a higher priority. + +## How to see logs of the plugins mechanism? + +Plugins registration emits logs with `INFO` level: + +```python +import logging + +logging.basicConfig(level=logging.INFO) +``` + +```text +INFO |onETL| Found 2 plugins +INFO |onETL| Loading plugin 'my-plugin' +INFO |onETL| Skipping plugin 'failing' because it is in a blacklist +``` + +More detailed logs are emitted with `DEBUG` level, to make output less verbose: + +```python +import logging + +logging.basicConfig(level=logging.DEBUG) +``` + +```text +DEBUG |onETL| Searching for plugins with group 'onetl.plugins' +DEBUG |Plugins| Plugins whitelist: [] +DEBUG |Plugins| Plugins blacklist: ['failing-plugin'] +INFO |Plugins| Found 2 plugins +INFO |onETL| Loading plugin (1/2): +DEBUG name: 'my-plugin' +DEBUG package: 'my-package' +DEBUG version: '0.1.0' +DEBUG importing: 'my_package.my_module:MyClass' +DEBUG |onETL| Successfully loaded plugin 'my-plugin' +DEBUG source: '/usr/lib/python3.11/site-packages/my_package/my_module/my_class.py' +INFO |onETL| Skipping plugin 'failing' because it is in a blacklist +``` diff --git a/mddocs/ru/quickstart.md b/mddocs/ru/quickstart.md new file mode 100644 index 000000000..d2367a3de --- /dev/null +++ b/mddocs/ru/quickstart.md @@ -0,0 +1,4 @@ +```{eval-rst} +.. include:: ../README.rst + :start-after: quick-start +``` diff --git a/mddocs/ru/security.md b/mddocs/ru/security.md new file mode 100644 index 000000000..05a7bef5a --- /dev/null +++ b/mddocs/ru/security.md @@ -0,0 +1,3 @@ +```{eval-rst} +.. include:: ../SECURITY.rst +``` From d48ddfc7e5b867a7e7a13c9a590ca062b2b194fd Mon Sep 17 00:00:00 2001 From: Sattar Gyulmamedov Date: Thu, 19 Jun 2025 16:30:47 +0300 Subject: [PATCH 10/22] mddocs working version updated --- mddocs/config/en/mkdocs.yml | 141 + mddocs/config/ru/mkdocs.yml | 138 + mddocs/docs/Makefile | 20 - mddocs/docs/concepts.md | 458 -- mddocs/docs/conf.py | 169 - .../connection/file_df_connection/index.md | 19 - mddocs/docs/connection/index.md | 22 - mddocs/docs/contributing.md | 3 - mddocs/docs/{ => en}/changelog.md | 0 mddocs/docs/{ => en}/changelog/0.10.0.md | 0 mddocs/docs/{ => en}/changelog/0.10.1.md | 0 mddocs/docs/{ => en}/changelog/0.10.2.md | 0 mddocs/docs/{ => en}/changelog/0.11.0.md | 0 .../markdown => en}/changelog/0.11.1.md | 0 .../markdown => en}/changelog/0.11.2.md | 0 mddocs/docs/{ => en}/changelog/0.12.0.md | 0 mddocs/docs/{ => en}/changelog/0.12.1.md | 0 .../markdown => en}/changelog/0.12.2.md | 0 .../markdown => en}/changelog/0.12.3.md | 0 .../markdown => en}/changelog/0.12.4.md | 0 .../markdown => en}/changelog/0.12.5.md | 0 mddocs/docs/{ => en}/changelog/0.13.0.md | 0 mddocs/docs/{ => en}/changelog/0.13.1.md | 0 .../markdown => en}/changelog/0.13.3.md | 0 .../markdown => en}/changelog/0.13.4.md | 0 mddocs/docs/{ => en}/changelog/0.7.0.md | 0 mddocs/docs/{ => en}/changelog/0.7.1.md | 0 mddocs/docs/{ => en}/changelog/0.7.2.md | 0 mddocs/docs/{ => en}/changelog/0.8.0.md | 0 mddocs/docs/{ => en}/changelog/0.8.1.md | 0 mddocs/docs/{ => en}/changelog/0.9.0.md | 0 .../markdown => en}/changelog/0.9.1.md | 0 mddocs/docs/{ => en}/changelog/0.9.2.md | 0 .../markdown => en}/changelog/0.9.3.md | 0 mddocs/docs/{ => en}/changelog/0.9.4.md | 0 .../markdown => en}/changelog/0.9.5.md | 0 mddocs/docs/{ => en}/changelog/DRAFT.md | 0 .../docs/{ => en}/changelog/NEXT_RELEASE.md | 0 mddocs/docs/{ => en}/changelog/index.md | 0 .../{ => en}/changelog/next_release/.keep | 0 mddocs/docs/en/concepts.md | 486 ++ .../db_connection/clickhouse/connection.md | 0 .../db_connection/clickhouse/execute.md | 0 .../db_connection/clickhouse/index.md | 0 .../db_connection/clickhouse/prerequisites.md | 0 .../db_connection/clickhouse/read.md | 0 .../db_connection/clickhouse/sql.md | 0 .../db_connection/clickhouse/types.md | 0 .../db_connection/clickhouse/write.md | 0 .../db_connection/greenplum/connection.md | 0 .../db_connection/greenplum/execute.md | 0 .../db_connection/greenplum/index.md | 0 .../db_connection/greenplum/prerequisites.md | 0 .../db_connection/greenplum/read.md | 0 .../db_connection/greenplum/types.md | 0 .../db_connection/greenplum/write.md | 0 .../db_connection/hive/connection.md | 0 .../connection/db_connection/hive/execute.md | 0 .../connection/db_connection/hive/index.md | 0 .../db_connection/hive/prerequisites.md | 0 .../connection/db_connection/hive/read.md | 0 .../connection/db_connection/hive/slots.md | 0 .../connection/db_connection/hive/sql.md | 0 .../connection/db_connection/hive/write.md | 0 .../connection/db_connection/index.md | 0 .../connection/db_connection/kafka/auth.md | 0 .../db_connection/kafka/basic_auth.md | 0 .../db_connection/kafka/connection.md | 0 .../connection/db_connection/kafka/index.md | 0 .../db_connection/kafka/kerberos_auth.md | 0 .../db_connection/kafka/plaintext_protocol.md | 0 .../db_connection/kafka/prerequisites.md | 0 .../db_connection/kafka/protocol.md | 0 .../connection/db_connection/kafka/read.md | 0 .../db_connection/kafka/scram_auth.md | 0 .../connection/db_connection/kafka/slots.md | 0 .../db_connection/kafka/ssl_protocol.md | 0 .../db_connection/kafka/troubleshooting.md | 0 .../connection/db_connection/kafka/write.md | 0 .../db_connection/mongodb/connection.md | 0 .../connection/db_connection/mongodb/index.md | 0 .../db_connection/mongodb/pipeline.md | 0 .../db_connection/mongodb/prerequisites.md | 0 .../connection/db_connection/mongodb/read.md | 0 .../connection/db_connection/mongodb/types.md | 0 .../connection/db_connection/mongodb/write.md | 0 .../db_connection/mssql/connection.md | 0 .../connection/db_connection/mssql/execute.md | 0 .../connection/db_connection/mssql/index.md | 0 .../db_connection/mssql/prerequisites.md | 0 .../connection/db_connection/mssql/read.md | 0 .../connection/db_connection/mssql/sql.md | 0 .../connection/db_connection/mssql/types.md | 0 .../connection/db_connection/mssql/write.md | 0 .../db_connection/mysql/connection.md | 0 .../connection/db_connection/mysql/execute.md | 0 .../connection/db_connection/mysql/index.md | 0 .../db_connection/mysql/prerequisites.md | 0 .../connection/db_connection/mysql/read.md | 0 .../connection/db_connection/mysql/sql.md | 0 .../connection/db_connection/mysql/types.md | 0 .../connection/db_connection/mysql/write.md | 0 .../db_connection/oracle/connection.md | 0 .../db_connection/oracle/execute.md | 0 .../connection/db_connection/oracle/index.md | 0 .../db_connection/oracle/prerequisites.md | 0 .../connection/db_connection/oracle/read.md | 0 .../connection/db_connection/oracle/sql.md | 0 .../connection/db_connection/oracle/types.md | 0 .../connection/db_connection/oracle/write.md | 0 .../db_connection/postgres/connection.md | 0 .../db_connection/postgres/execute.md | 0 .../db_connection/postgres/index.md | 0 .../db_connection/postgres/prerequisites.md | 0 .../connection/db_connection/postgres/read.md | 0 .../connection/db_connection/postgres/sql.md | 0 .../db_connection/postgres/types.md | 0 .../db_connection/postgres/write.md | 0 .../db_connection/teradata/connection.md | 0 .../db_connection/teradata/execute.md | 0 .../db_connection/teradata/index.md | 0 .../db_connection/teradata/prerequisites.md | 0 .../connection/db_connection/teradata/read.md | 0 .../connection/db_connection/teradata/sql.md | 0 .../db_connection/teradata/write.md | 0 .../connection/file_connection/ftp.md | 0 .../connection/file_connection/ftps.md | 0 .../file_connection/hdfs/connection.md | 0 .../connection/file_connection/hdfs/index.md | 0 .../connection/file_connection/hdfs/slots.md | 0 .../connection/file_connection/index.md | 0 .../{ => en}/connection/file_connection/s3.md | 0 .../connection/file_connection/samba.md | 0 .../connection/file_connection/sftp.md | 0 .../connection/file_connection/webdav.md | 0 .../connection/file_df_connection/base.md | 0 .../en/connection/file_df_connection/index.md | 15 + .../spark_hdfs/connection.md | 0 .../file_df_connection/spark_hdfs/index.md | 0 .../spark_hdfs/prerequisites.md | 0 .../file_df_connection/spark_hdfs/slots.md | 0 .../file_df_connection/spark_local_fs.md | 0 .../file_df_connection/spark_s3/connection.md | 0 .../file_df_connection/spark_s3/index.md | 0 .../spark_s3/prerequisites.md | 0 .../spark_s3/troubleshooting.md | 0 mddocs/docs/en/connection/index.md | 34 + .../{_build/markdown => en}/contributing.md | 0 mddocs/docs/{db => en/db_}/index.md | 4 +- .../{db/db_reader.md => en/db_/reader.md} | 5 +- .../{db/db_writer.md => en/db_/writer.md} | 4 +- .../file/file_downloader/file_downloader.md | 0 .../{ => en}/file/file_downloader/index.md | 0 .../{ => en}/file/file_downloader/options.md | 0 .../{ => en}/file/file_downloader/result.md | 0 .../docs/{ => en}/file/file_filters/base.md | 0 .../{ => en}/file/file_filters/exclude_dir.md | 0 .../{ => en}/file/file_filters/file_filter.md | 0 .../file/file_filters/file_mtime_filter.md | 0 .../file/file_filters/file_size_filter.md | 0 .../docs/{ => en}/file/file_filters/glob.md | 0 .../docs/{ => en}/file/file_filters/index.md | 0 .../file/file_filters/match_all_filters.md | 0 .../docs/{ => en}/file/file_filters/regexp.md | 0 mddocs/docs/{ => en}/file/file_limits/base.md | 0 .../{ => en}/file/file_limits/file_limit.md | 0 .../docs/{ => en}/file/file_limits/index.md | 0 .../file/file_limits/limits_reached.md | 0 .../file/file_limits/limits_stop_at.md | 0 .../file/file_limits/max_files_count.md | 0 .../{ => en}/file/file_limits/reset_limits.md | 0 .../file/file_limits/total_files_size.md | 0 .../{ => en}/file/file_mover/file_mover.md | 0 mddocs/docs/{ => en}/file/file_mover/index.md | 0 .../docs/{ => en}/file/file_mover/options.md | 0 .../docs/{ => en}/file/file_mover/result.md | 0 .../file/file_uploader/file_uploader.md | 0 .../docs/{ => en}/file/file_uploader/index.md | 0 .../{ => en}/file/file_uploader/options.md | 0 .../{ => en}/file/file_uploader/result.md | 0 mddocs/docs/{ => en}/file/index.md | 0 .../file_df/file_df_reader/file_df_reader.md | 0 .../{ => en}/file_df/file_df_reader/index.md | 0 .../file_df/file_df_reader/options.md | 0 .../file_df/file_df_writer/file_df_writer.md | 0 .../{ => en}/file_df/file_df_writer/index.md | 0 .../file_df/file_df_writer/options.md | 0 .../{ => en}/file_df/file_formats/avro.md | 0 .../{ => en}/file_df/file_formats/base.md | 0 .../docs/{ => en}/file_df/file_formats/csv.md | 0 .../{ => en}/file_df/file_formats/excel.md | 0 .../{ => en}/file_df/file_formats/index.md | 0 .../{ => en}/file_df/file_formats/json.md | 0 .../{ => en}/file_df/file_formats/jsonline.md | 0 .../docs/{ => en}/file_df/file_formats/orc.md | 0 .../{ => en}/file_df/file_formats/parquet.md | 0 .../docs/{ => en}/file_df/file_formats/xml.md | 0 mddocs/docs/{ => en}/file_df/index.md | 0 mddocs/docs/{ => en}/hooks/design.md | 0 mddocs/docs/{ => en}/hooks/global_state.md | 0 mddocs/docs/{ => en}/hooks/hook.md | 0 mddocs/docs/{ => en}/hooks/index.md | 6 +- mddocs/docs/{ => en}/hooks/slot.md | 0 mddocs/docs/{ => en}/hooks/support_hooks.md | 0 mddocs/docs/{ => en}/hwm_store/index.md | 0 .../docs/{ => en}/hwm_store/yaml_hwm_store.md | 0 mddocs/docs/en/index.md | 18 + mddocs/docs/{ => en}/install/files.md | 0 mddocs/docs/{ => en}/install/full.md | 0 mddocs/docs/{ => en}/install/index.md | 0 mddocs/docs/{ => en}/install/kerberos.md | 0 mddocs/docs/{ => en}/install/spark.md | 0 mddocs/docs/{ => en}/logging.md | 36 +- mddocs/docs/{ => en}/plugins.md | 15 +- mddocs/docs/en/quickstart.md | 540 ++ .../{ru/SECURITY.md => docs/en/security.md} | 6 +- mddocs/docs/en/snippet_0.md | 44 + .../strategy/incremental_batch_strategy.md | 0 .../{ => en}/strategy/incremental_strategy.md | 0 mddocs/docs/{ => en}/strategy/index.md | 0 .../strategy/snapshot_batch_strategy.md | 0 .../{ => en}/strategy/snapshot_strategy.md | 0 mddocs/docs/{ => en}/troubleshooting/index.md | 0 mddocs/docs/{ => en}/troubleshooting/spark.md | 0 mddocs/docs/index.md | 87 - mddocs/docs/make.bat | 35 - mddocs/docs/quickstart.md | 4 - mddocs/{ => docs}/ru/README.md | 2 + mddocs/{ => docs}/ru/changelog/0.10.0.md | 0 mddocs/{ => docs}/ru/changelog/0.10.1.md | 0 mddocs/{ => docs}/ru/changelog/0.10.2.md | 0 mddocs/{ => docs}/ru/changelog/0.11.0.md | 0 mddocs/{ => docs}/ru/changelog/0.11.1.md | 0 mddocs/{ => docs}/ru/changelog/0.11.2.md | 0 mddocs/{ => docs}/ru/changelog/0.12.0.md | 0 mddocs/{ => docs}/ru/changelog/0.12.1.md | 0 mddocs/{ => docs}/ru/changelog/0.12.2.md | 0 mddocs/{ => docs}/ru/changelog/0.12.3.md | 0 mddocs/{ => docs}/ru/changelog/0.12.4.md | 0 mddocs/{ => docs}/ru/changelog/0.12.5.md | 0 mddocs/{ => docs}/ru/changelog/0.13.0.md | 0 mddocs/{ => docs}/ru/changelog/0.13.1.md | 0 mddocs/{ => docs}/ru/changelog/0.13.3.md | 0 mddocs/{ => docs}/ru/changelog/0.13.4.md | 0 mddocs/{ => docs}/ru/changelog/0.7.0.md | 14 +- mddocs/{ => docs}/ru/changelog/0.7.1.md | 0 mddocs/{ => docs}/ru/changelog/0.7.2.md | 0 mddocs/{ => docs}/ru/changelog/0.8.0.md | 0 mddocs/{ => docs}/ru/changelog/0.8.1.md | 0 mddocs/{ => docs}/ru/changelog/0.9.0.md | 0 mddocs/{ => docs}/ru/changelog/0.9.1.md | 0 mddocs/{ => docs}/ru/changelog/0.9.2.md | 0 mddocs/{ => docs}/ru/changelog/0.9.3.md | 0 mddocs/{ => docs}/ru/changelog/0.9.4.md | 0 mddocs/{ => docs}/ru/changelog/0.9.5.md | 0 mddocs/docs/ru/changelog/index.md | 29 + mddocs/docs/ru/concepts.md | 484 ++ .../db_connection/clickhouse/connection.md | 12 + .../db_connection/clickhouse/execute.md | 125 + .../db_connection/clickhouse/index.md | 28 + .../db_connection/clickhouse/prerequisites.md | 73 + .../db_connection/clickhouse/read.md | 93 + .../db_connection/clickhouse/sql.md | 80 + .../db_connection/clickhouse/types.md | 457 ++ .../db_connection/clickhouse/write.md | 60 + .../db_connection/greenplum/connection.md | 12 + .../db_connection/greenplum/execute.md | 159 + .../db_connection/greenplum/index.md | 27 + .../db_connection/greenplum/prerequisites.md | 382 + .../db_connection/greenplum/read.md | 386 + .../db_connection/greenplum/types.md | 406 + .../db_connection/greenplum/write.md | 140 + .../db_connection/hive/connection.md | 13 + .../connection/db_connection/hive/execute.md | 55 + .../ru/connection/db_connection/hive/index.md | 28 + .../db_connection/hive/prerequisites.md | 134 + .../ru/connection/db_connection/hive/read.md | 95 + .../ru/connection/db_connection/hive/slots.md | 13 + .../ru/connection/db_connection/hive/sql.md | 79 + .../ru/connection/db_connection/hive/write.md | 180 + .../docs/ru/connection/db_connection/index.md | 19 + .../ru/connection/db_connection/kafka/auth.md | 13 + .../db_connection/kafka/basic_auth.md | 14 + .../db_connection/kafka/connection.md | 12 + .../connection/db_connection/kafka/index.md | 46 + .../db_connection/kafka/kerberos_auth.md | 14 + .../db_connection/kafka/plaintext_protocol.md | 14 + .../db_connection/kafka/prerequisites.md | 65 + .../db_connection/kafka/protocol.md | 13 + .../ru/connection/db_connection/kafka/read.md | 137 + .../db_connection/kafka/scram_auth.md | 14 + .../connection/db_connection/kafka/slots.md | 13 + .../db_connection/kafka/ssl_protocol.md | 14 + .../db_connection/kafka/troubleshooting.md | 13 + .../connection/db_connection/kafka/write.md | 75 + .../db_connection/mongodb/connection.md | 13 + .../connection/db_connection/mongodb/index.md | 27 + .../db_connection/mongodb/pipeline.md | 41 + .../db_connection/mongodb/prerequisites.md | 72 + .../connection/db_connection/mongodb/read.md | 141 + .../connection/db_connection/mongodb/types.md | 269 + .../connection/db_connection/mongodb/write.md | 47 + .../db_connection/mssql/connection.md | 12 + .../connection/db_connection/mssql/execute.md | 117 + .../connection/db_connection/mssql/index.md | 28 + .../db_connection/mssql/prerequisites.md | 76 + .../ru/connection/db_connection/mssql/read.md | 93 + .../ru/connection/db_connection/mssql/sql.md | 80 + .../connection/db_connection/mssql/types.md | 376 + .../connection/db_connection/mssql/write.md | 56 + .../db_connection/mysql/connection.md | 12 + .../connection/db_connection/mysql/execute.md | 115 + .../connection/db_connection/mysql/index.md | 28 + .../db_connection/mysql/prerequisites.md | 61 + .../ru/connection/db_connection/mysql/read.md | 93 + .../ru/connection/db_connection/mysql/sql.md | 81 + .../connection/db_connection/mysql/types.md | 382 + .../connection/db_connection/mysql/write.md | 60 + .../db_connection/oracle/connection.md | 12 + .../db_connection/oracle/execute.md | 115 + .../connection/db_connection/oracle/index.md | 28 + .../db_connection/oracle/prerequisites.md | 108 + .../connection/db_connection/oracle/read.md | 93 + .../ru/connection/db_connection/oracle/sql.md | 81 + .../connection/db_connection/oracle/types.md | 400 + .../connection/db_connection/oracle/write.md | 56 + .../db_connection/postgres/connection.md | 12 + .../db_connection/postgres/execute.md | 113 + .../db_connection/postgres/index.md | 28 + .../db_connection/postgres/prerequisites.md | 71 + .../connection/db_connection/postgres/read.md | 91 + .../connection/db_connection/postgres/sql.md | 80 + .../db_connection/postgres/types.md | 490 ++ .../db_connection/postgres/write.md | 56 + .../db_connection/teradata/connection.md | 12 + .../db_connection/teradata/execute.md | 110 + .../db_connection/teradata/index.md | 21 + .../db_connection/teradata/prerequisites.md | 57 + .../connection/db_connection/teradata/read.md | 117 + .../connection/db_connection/teradata/sql.md | 76 + .../db_connection/teradata/write.md | 120 + .../docs/ru/connection/file_connection/ftp.md | 12 + .../ru/connection/file_connection/ftps.md | 12 + .../file_connection/hdfs/connection.md | 12 + .../connection/file_connection/hdfs/index.md | 17 + .../connection/file_connection/hdfs/slots.md | 13 + .../ru/connection/file_connection/index.md | 16 + .../docs/ru/connection/file_connection/s3.md | 12 + .../ru/connection/file_connection/samba.md | 12 + .../ru/connection/file_connection/sftp.md | 12 + .../ru/connection/file_connection/webdav.md | 12 + .../ru/connection/file_df_connection/base.md | 12 + .../ru/connection/file_df_connection/index.md | 15 + .../spark_hdfs/connection.md | 12 + .../file_df_connection/spark_hdfs/index.md | 18 + .../spark_hdfs/prerequisites.md | 46 + .../file_df_connection/spark_hdfs/slots.md | 13 + .../file_df_connection/spark_local_fs.md | 12 + .../file_df_connection/spark_s3/connection.md | 12 + .../file_df_connection/spark_s3/index.md | 12 + .../spark_s3/prerequisites.md | 61 + .../spark_s3/troubleshooting.md | 377 + mddocs/docs/ru/connection/index.md | 34 + .../ru/contributing.md} | 133 +- mddocs/docs/ru/db_/index.md | 11 + mddocs/docs/ru/db_/reader.md | 22 + mddocs/docs/ru/db_/writer.md | 19 + mddocs/docs/ru/index.md | 18 + mddocs/{ => docs}/ru/logging.md | 53 +- mddocs/docs/ru/plugins.md | 140 + mddocs/docs/ru/quickstart.md | 540 ++ mddocs/docs/ru/security.md | 25 + mddocs/docs/ru/snippet_0.md | 44 + mddocs/docs/ru/test.md | 5 + mddocs/docs/security.md | 3 - mddocs/generated/en/404.html | 1244 +++ mddocs/generated/en/assets/_mkdocstrings.css | 181 + mddocs/generated/en/assets/images/favicon.png | Bin 0 -> 1870 bytes .../en/assets/images}/icon.svg | 0 .../en/assets/images}/logo.svg | 0 .../en/assets/images}/logo_wide.svg | 0 .../assets/javascripts/bundle.13a4f30d.min.js | 16 + .../javascripts/bundle.13a4f30d.min.js.map | 7 + .../javascripts/lunr/min/lunr.ar.min.js | 1 + .../javascripts/lunr/min/lunr.da.min.js | 18 + .../javascripts/lunr/min/lunr.de.min.js | 18 + .../javascripts/lunr/min/lunr.du.min.js | 18 + .../javascripts/lunr/min/lunr.el.min.js | 1 + .../javascripts/lunr/min/lunr.es.min.js | 18 + .../javascripts/lunr/min/lunr.fi.min.js | 18 + .../javascripts/lunr/min/lunr.fr.min.js | 18 + .../javascripts/lunr/min/lunr.he.min.js | 1 + .../javascripts/lunr/min/lunr.hi.min.js | 1 + .../javascripts/lunr/min/lunr.hu.min.js | 18 + .../javascripts/lunr/min/lunr.hy.min.js | 1 + .../javascripts/lunr/min/lunr.it.min.js | 18 + .../javascripts/lunr/min/lunr.ja.min.js | 1 + .../javascripts/lunr/min/lunr.jp.min.js | 1 + .../javascripts/lunr/min/lunr.kn.min.js | 1 + .../javascripts/lunr/min/lunr.ko.min.js | 1 + .../javascripts/lunr/min/lunr.multi.min.js | 1 + .../javascripts/lunr/min/lunr.nl.min.js | 18 + .../javascripts/lunr/min/lunr.no.min.js | 18 + .../javascripts/lunr/min/lunr.pt.min.js | 18 + .../javascripts/lunr/min/lunr.ro.min.js | 18 + .../javascripts/lunr/min/lunr.ru.min.js | 18 + .../javascripts/lunr/min/lunr.sa.min.js | 1 + .../lunr/min/lunr.stemmer.support.min.js | 1 + .../javascripts/lunr/min/lunr.sv.min.js | 18 + .../javascripts/lunr/min/lunr.ta.min.js | 1 + .../javascripts/lunr/min/lunr.te.min.js | 1 + .../javascripts/lunr/min/lunr.th.min.js | 1 + .../javascripts/lunr/min/lunr.tr.min.js | 18 + .../javascripts/lunr/min/lunr.vi.min.js | 1 + .../javascripts/lunr/min/lunr.zh.min.js | 1 + .../en/assets/javascripts/lunr/tinyseg.js | 206 + .../en/assets/javascripts/lunr/wordcut.js | 6708 +++++++++++++++++ .../workers/search.d50fe291.min.js | 42 + .../workers/search.d50fe291.min.js.map | 7 + .../en/assets/mkdocs_puml/interaction.css | 130 + .../en/assets/mkdocs_puml/interaction.js | 130 + .../generated/en/assets/mkdocs_puml/puml.css | 52 + .../generated/en/assets/mkdocs_puml/puml.js | 23 + .../assets/stylesheets}/autodoc_pydantic.css | 0 .../assets/stylesheets/main.342714a4.min.css | 1 + .../stylesheets/main.342714a4.min.css.map | 1 + .../stylesheets/palette.06af60db.min.css | 1 + .../stylesheets/palette.06af60db.min.css.map | 1 + .../generated/en/changelog/0.10.0/index.html | 1950 +++++ .../generated/en/changelog/0.10.1/index.html | 1316 ++++ .../generated/en/changelog/0.10.2/index.html | 1345 ++++ .../generated/en/changelog/0.11.0/index.html | 1514 ++++ .../generated/en/changelog/0.11.1/index.html | 1301 ++++ .../generated/en/changelog/0.11.2/index.html | 1288 ++++ .../generated/en/changelog/0.12.0/index.html | 1361 ++++ .../generated/en/changelog/0.12.1/index.html | 1318 ++++ .../generated/en/changelog/0.12.2/index.html | 1328 ++++ .../generated/en/changelog/0.12.3/index.html | 1288 ++++ .../generated/en/changelog/0.12.4/index.html | 1288 ++++ .../generated/en/changelog/0.12.5/index.html | 1305 ++++ .../generated/en/changelog/0.13.0/index.html | 1525 ++++ .../generated/en/changelog/0.13.1/index.html | 1289 ++++ .../generated/en/changelog/0.13.3/index.html | 1286 ++++ .../generated/en/changelog/0.13.4/index.html | 1293 ++++ .../generated/en/changelog/0.7.0/index.html | 1540 ++++ .../generated/en/changelog/0.7.1/index.html | 1308 ++++ .../generated/en/changelog/0.7.2/index.html | 1311 ++++ .../generated/en/changelog/0.8.0/index.html | 1486 ++++ .../generated/en/changelog/0.8.1/index.html | 1360 ++++ .../generated/en/changelog/0.9.0/index.html | 1438 ++++ .../generated/en/changelog/0.9.1/index.html | 1290 ++++ .../generated/en/changelog/0.9.2/index.html | 1328 ++++ .../generated/en/changelog/0.9.3/index.html | 1288 ++++ .../generated/en/changelog/0.9.4/index.html | 1327 ++++ .../generated/en/changelog/0.9.5/index.html | 1315 ++++ .../generated/en/changelog/DRAFT/index.html | 1263 ++++ .../en/changelog/NEXT_RELEASE/index.html | 1262 ++++ mddocs/generated/en/changelog/index.html | 1300 ++++ mddocs/generated/en/concepts/index.html | 1800 +++++ .../clickhouse/connection/index.html | 1273 ++++ .../clickhouse/execute/index.html | 1464 ++++ .../db_connection/clickhouse/index.html | 1287 ++++ .../clickhouse/prerequisites/index.html | 1389 ++++ .../db_connection/clickhouse/read/index.html | 1422 ++++ .../db_connection/clickhouse/sql/index.html | 1394 ++++ .../db_connection/clickhouse/types/index.html | 1810 +++++ .../db_connection/clickhouse/write/index.html | 1337 ++++ .../greenplum/connection/index.html | 1273 ++++ .../greenplum/execute/index.html | 1496 ++++ .../db_connection/greenplum/index.html | 1286 ++++ .../greenplum/prerequisites/index.html | 1731 +++++ .../db_connection/greenplum/read/index.html | 1754 +++++ .../db_connection/greenplum/types/index.html | 1765 +++++ .../db_connection/greenplum/write/index.html | 1423 ++++ .../db_connection/hive/connection/index.html | 1274 ++++ .../db_connection/hive/execute/index.html | 1349 ++++ .../connection/db_connection/hive/index.html | 1287 ++++ .../hive/prerequisites/index.html | 1424 ++++ .../db_connection/hive/read/index.html | 1424 ++++ .../db_connection/hive/slots/index.html | 1274 ++++ .../db_connection/hive/sql/index.html | 1399 ++++ .../db_connection/hive/write/index.html | 1653 ++++ .../en/connection/db_connection/index.html | 1282 ++++ .../db_connection/kafka/auth/index.html | 1274 ++++ .../db_connection/kafka/basic_auth/index.html | 1275 ++++ .../db_connection/kafka/connection/index.html | 1273 ++++ .../connection/db_connection/kafka/index.html | 1301 ++++ .../kafka/kerberos_auth/index.html | 1275 ++++ .../kafka/plaintext_protocol/index.html | 1275 ++++ .../kafka/prerequisites/index.html | 1389 ++++ .../db_connection/kafka/protocol/index.html | 1274 ++++ .../db_connection/kafka/read/index.html | 1448 ++++ .../db_connection/kafka/scram_auth/index.html | 1275 ++++ .../db_connection/kafka/slots/index.html | 1274 ++++ .../kafka/ssl_protocol/index.html | 1275 ++++ .../kafka/troubleshooting/index.html | 1291 ++++ .../db_connection/kafka/write/index.html | 1364 ++++ .../mongodb/connection/index.html | 1274 ++++ .../db_connection/mongodb/index.html | 1286 ++++ .../db_connection/mongodb/pipeline/index.html | 1332 ++++ .../mongodb/prerequisites/index.html | 1389 ++++ .../db_connection/mongodb/read/index.html | 1467 ++++ .../db_connection/mongodb/types/index.html | 1650 ++++ .../db_connection/mongodb/write/index.html | 1326 ++++ .../db_connection/mssql/connection/index.html | 1273 ++++ .../db_connection/mssql/execute/index.html | 1451 ++++ .../connection/db_connection/mssql/index.html | 1287 ++++ .../mssql/prerequisites/index.html | 1395 ++++ .../db_connection/mssql/read/index.html | 1422 ++++ .../db_connection/mssql/sql/index.html | 1394 ++++ .../db_connection/mssql/types/index.html | 1733 +++++ .../db_connection/mssql/write/index.html | 1333 ++++ .../db_connection/mysql/connection/index.html | 1273 ++++ .../db_connection/mysql/execute/index.html | 1448 ++++ .../connection/db_connection/mysql/index.html | 1287 ++++ .../mysql/prerequisites/index.html | 1380 ++++ .../db_connection/mysql/read/index.html | 1422 ++++ .../db_connection/mysql/sql/index.html | 1395 ++++ .../db_connection/mysql/types/index.html | 1741 +++++ .../db_connection/mysql/write/index.html | 1337 ++++ .../oracle/connection/index.html | 1273 ++++ .../db_connection/oracle/execute/index.html | 1448 ++++ .../db_connection/oracle/index.html | 1287 ++++ .../oracle/prerequisites/index.html | 1428 ++++ .../db_connection/oracle/read/index.html | 1422 ++++ .../db_connection/oracle/sql/index.html | 1395 ++++ .../db_connection/oracle/types/index.html | 1764 +++++ .../db_connection/oracle/write/index.html | 1333 ++++ .../postgres/connection/index.html | 1273 ++++ .../db_connection/postgres/execute/index.html | 1446 ++++ .../db_connection/postgres/index.html | 1287 ++++ .../postgres/prerequisites/index.html | 1397 ++++ .../db_connection/postgres/read/index.html | 1420 ++++ .../db_connection/postgres/sql/index.html | 1394 ++++ .../db_connection/postgres/types/index.html | 1878 +++++ .../db_connection/postgres/write/index.html | 1333 ++++ .../teradata/connection/index.html | 1273 ++++ .../db_connection/teradata/execute/index.html | 1445 ++++ .../db_connection/teradata/index.html | 1282 ++++ .../teradata/prerequisites/index.html | 1378 ++++ .../db_connection/teradata/read/index.html | 1458 ++++ .../db_connection/teradata/sql/index.html | 1392 ++++ .../db_connection/teradata/write/index.html | 1422 ++++ .../connection/file_connection/ftp/index.html | 1273 ++++ .../file_connection/ftps/index.html | 1273 ++++ .../hdfs/connection/index.html | 1273 ++++ .../file_connection/hdfs/index.html | 1278 ++++ .../file_connection/hdfs/slots/index.html | 1274 ++++ .../en/connection/file_connection/index.html | 1279 ++++ .../connection/file_connection/s3/index.html | 1273 ++++ .../file_connection/samba/index.html | 1273 ++++ .../file_connection/sftp/index.html | 1273 ++++ .../file_connection/webdav/index.html | 1273 ++++ .../file_df_connection/base/index.html | 1273 ++++ .../connection/file_df_connection/index.html | 1282 ++++ .../spark_hdfs/connection/index.html | 1273 ++++ .../file_df_connection/spark_hdfs/index.html | 1279 ++++ .../spark_hdfs/prerequisites/index.html | 1337 ++++ .../spark_hdfs/slots/index.html | 1274 ++++ .../spark_local_fs/index.html | 1273 ++++ .../spark_s3/connection/index.html | 1273 ++++ .../file_df_connection/spark_s3/index.html | 1275 ++++ .../spark_s3/prerequisites/index.html | 1375 ++++ .../spark_s3/troubleshooting/index.html | 1748 +++++ mddocs/generated/en/connection/index.html | 1300 ++++ mddocs/generated/en/contributing/index.html | 1766 +++++ mddocs/generated/en/db_/index.html | 1278 ++++ mddocs/generated/en/db_/reader/index.html | 1280 ++++ mddocs/generated/en/db_/writer/index.html | 1278 ++++ .../file_downloader/index.html | 1280 ++++ .../en/file/file_downloader/index.html | 1275 ++++ .../file/file_downloader/options/index.html | 1275 ++++ .../en/file/file_downloader/result/index.html | 1273 ++++ .../en/file/file_filters/base/index.html | 1278 ++++ .../file/file_filters/exclude_dir/index.html | 1273 ++++ .../file/file_filters/file_filter/index.html | 1273 ++++ .../file_filters/file_mtime_filter/index.html | 1273 ++++ .../file_filters/file_size_filter/index.html | 1273 ++++ .../en/file/file_filters/glob/index.html | 1273 ++++ .../generated/en/file/file_filters/index.html | 1288 ++++ .../file_filters/match_all_filters/index.html | 1272 ++++ .../en/file/file_filters/regexp/index.html | 1273 ++++ .../en/file/file_limits/base/index.html | 1280 ++++ .../en/file/file_limits/file_limit/index.html | 1273 ++++ .../generated/en/file/file_limits/index.html | 1287 ++++ .../file_limits/limits_reached/index.html | 1272 ++++ .../file_limits/limits_stop_at/index.html | 1272 ++++ .../file_limits/max_files_count/index.html | 1273 ++++ .../file/file_limits/reset_limits/index.html | 1272 ++++ .../file_limits/total_files_size/index.html | 1273 ++++ .../en/file/file_mover/file_mover/index.html | 1280 ++++ .../generated/en/file/file_mover/index.html | 1275 ++++ .../en/file/file_mover/options/index.html | 1275 ++++ .../en/file/file_mover/result/index.html | 1273 ++++ .../file_uploader/file_uploader/index.html | 1280 ++++ .../en/file/file_uploader/index.html | 1275 ++++ .../en/file/file_uploader/options/index.html | 1275 ++++ .../en/file/file_uploader/result/index.html | 1273 ++++ mddocs/generated/en/file/index.html | 1271 ++++ .../file_df_reader/file_df_reader/index.html | 1274 ++++ .../en/file_df/file_df_reader/index.html | 1274 ++++ .../file_df/file_df_reader/options/index.html | 1274 ++++ .../file_df_writer/file_df_writer/index.html | 1274 ++++ .../en/file_df/file_df_writer/index.html | 1274 ++++ .../file_df/file_df_writer/options/index.html | 1274 ++++ .../en/file_df/file_formats/avro/index.html | 1274 ++++ .../en/file_df/file_formats/base/index.html | 1278 ++++ .../en/file_df/file_formats/csv/index.html | 1274 ++++ .../en/file_df/file_formats/excel/index.html | 1274 ++++ .../en/file_df/file_formats/index.html | 1285 ++++ .../en/file_df/file_formats/json/index.html | 1274 ++++ .../file_df/file_formats/jsonline/index.html | 1274 ++++ .../en/file_df/file_formats/orc/index.html | 1274 ++++ .../file_df/file_formats/parquet/index.html | 1274 ++++ .../en/file_df/file_formats/xml/index.html | 1274 ++++ mddocs/generated/en/file_df/index.html | 1269 ++++ mddocs/generated/en/hooks/design/index.html | 2105 ++++++ .../en/hooks/global_state/index.html | 1282 ++++ mddocs/generated/en/hooks/hook/index.html | 1289 ++++ mddocs/generated/en/hooks/index.html | 1277 ++++ mddocs/generated/en/hooks/slot/index.html | 1283 ++++ .../en/hooks/support_hooks/index.html | 1285 ++++ mddocs/generated/en/hwm_store/index.html | 1276 ++++ .../en/hwm_store/yaml_hwm_store/index.html | 1273 ++++ mddocs/generated/en/index.html | 1432 ++++ mddocs/generated/en/install/files/index.html | 1272 ++++ mddocs/generated/en/install/full/index.html | 1272 ++++ mddocs/generated/en/install/index.html | 1299 ++++ .../generated/en/install/kerberos/index.html | 1272 ++++ mddocs/generated/en/install/spark/index.html | 1635 ++++ mddocs/generated/en/logging/index.html | 1676 ++++ mddocs/generated/en/objects.inv | Bin 0 -> 208 bytes mddocs/generated/en/plugins/index.html | 1489 ++++ mddocs/generated/en/quickstart/index.html | 2057 +++++ mddocs/generated/en/security/index.html | 1329 ++++ mddocs/generated/en/sitemap.xml | 3 + mddocs/generated/en/sitemap.xml.gz | Bin 0 -> 127 bytes mddocs/generated/en/snippet_0/index.html | 1420 ++++ .../incremental_batch_strategy/index.html | 1273 ++++ .../strategy/incremental_strategy/index.html | 1273 ++++ mddocs/generated/en/strategy/index.html | 1284 ++++ .../snapshot_batch_strategy/index.html | 1273 ++++ .../en/strategy/snapshot_strategy/index.html | 1273 ++++ .../generated/en/troubleshooting/index.html | 1292 ++++ .../en/troubleshooting/spark/index.html | 1355 ++++ mddocs/generated/ru/404.html | 1156 +++ mddocs/generated/ru/assets/_mkdocstrings.css | 181 + mddocs/generated/ru/assets/images/favicon.png | Bin 0 -> 1870 bytes mddocs/generated/ru/assets/images/icon.svg | 11 + mddocs/generated/ru/assets/images/logo.svg | 214 + .../generated/ru/assets/images/logo_wide.svg | 329 + .../assets/javascripts/bundle.13a4f30d.min.js | 16 + .../javascripts/bundle.13a4f30d.min.js.map | 7 + .../javascripts/lunr/min/lunr.ar.min.js | 1 + .../javascripts/lunr/min/lunr.da.min.js | 18 + .../javascripts/lunr/min/lunr.de.min.js | 18 + .../javascripts/lunr/min/lunr.du.min.js | 18 + .../javascripts/lunr/min/lunr.el.min.js | 1 + .../javascripts/lunr/min/lunr.es.min.js | 18 + .../javascripts/lunr/min/lunr.fi.min.js | 18 + .../javascripts/lunr/min/lunr.fr.min.js | 18 + .../javascripts/lunr/min/lunr.he.min.js | 1 + .../javascripts/lunr/min/lunr.hi.min.js | 1 + .../javascripts/lunr/min/lunr.hu.min.js | 18 + .../javascripts/lunr/min/lunr.hy.min.js | 1 + .../javascripts/lunr/min/lunr.it.min.js | 18 + .../javascripts/lunr/min/lunr.ja.min.js | 1 + .../javascripts/lunr/min/lunr.jp.min.js | 1 + .../javascripts/lunr/min/lunr.kn.min.js | 1 + .../javascripts/lunr/min/lunr.ko.min.js | 1 + .../javascripts/lunr/min/lunr.multi.min.js | 1 + .../javascripts/lunr/min/lunr.nl.min.js | 18 + .../javascripts/lunr/min/lunr.no.min.js | 18 + .../javascripts/lunr/min/lunr.pt.min.js | 18 + .../javascripts/lunr/min/lunr.ro.min.js | 18 + .../javascripts/lunr/min/lunr.ru.min.js | 18 + .../javascripts/lunr/min/lunr.sa.min.js | 1 + .../lunr/min/lunr.stemmer.support.min.js | 1 + .../javascripts/lunr/min/lunr.sv.min.js | 18 + .../javascripts/lunr/min/lunr.ta.min.js | 1 + .../javascripts/lunr/min/lunr.te.min.js | 1 + .../javascripts/lunr/min/lunr.th.min.js | 1 + .../javascripts/lunr/min/lunr.tr.min.js | 18 + .../javascripts/lunr/min/lunr.vi.min.js | 1 + .../javascripts/lunr/min/lunr.zh.min.js | 1 + .../ru/assets/javascripts/lunr/tinyseg.js | 206 + .../ru/assets/javascripts/lunr/wordcut.js | 6708 +++++++++++++++++ .../workers/search.d50fe291.min.js | 42 + .../workers/search.d50fe291.min.js.map | 7 + .../ru/assets/mkdocs_puml/interaction.css | 130 + .../ru/assets/mkdocs_puml/interaction.js | 130 + .../generated/ru/assets/mkdocs_puml/puml.css | 52 + .../generated/ru/assets/mkdocs_puml/puml.js | 23 + .../assets/stylesheets/autodoc_pydantic.css | 11 + .../assets/stylesheets/main.342714a4.min.css | 1 + .../stylesheets/main.342714a4.min.css.map | 1 + .../stylesheets/palette.06af60db.min.css | 1 + .../stylesheets/palette.06af60db.min.css.map | 1 + .../generated/ru/changelog/0.10.0/index.html | 1851 +++++ .../generated/ru/changelog/0.10.1/index.html | 1227 +++ .../generated/ru/changelog/0.10.2/index.html | 1253 +++ .../generated/ru/changelog/0.11.0/index.html | 1423 ++++ .../generated/ru/changelog/0.11.1/index.html | 1213 +++ .../generated/ru/changelog/0.11.2/index.html | 1200 +++ .../generated/ru/changelog/0.12.0/index.html | 1270 ++++ .../generated/ru/changelog/0.12.1/index.html | 1230 +++ .../generated/ru/changelog/0.12.2/index.html | 1240 +++ .../generated/ru/changelog/0.12.3/index.html | 1200 +++ .../generated/ru/changelog/0.12.4/index.html | 1200 +++ .../generated/ru/changelog/0.12.5/index.html | 1217 +++ .../generated/ru/changelog/0.13.0/index.html | 1426 ++++ .../generated/ru/changelog/0.13.1/index.html | 1200 +++ .../generated/ru/changelog/0.13.3/index.html | 1198 +++ .../generated/ru/changelog/0.13.4/index.html | 1202 +++ .../generated/ru/changelog/0.7.0/index.html | 1460 ++++ .../generated/ru/changelog/0.7.1/index.html | 1220 +++ .../generated/ru/changelog/0.7.2/index.html | 1223 +++ .../generated/ru/changelog/0.8.0/index.html | 1394 ++++ .../generated/ru/changelog/0.8.1/index.html | 1272 ++++ .../generated/ru/changelog/0.9.0/index.html | 1350 ++++ .../generated/ru/changelog/0.9.1/index.html | 1202 +++ .../generated/ru/changelog/0.9.2/index.html | 1240 +++ .../generated/ru/changelog/0.9.3/index.html | 1200 +++ .../generated/ru/changelog/0.9.4/index.html | 1238 +++ .../generated/ru/changelog/0.9.5/index.html | 1227 +++ mddocs/generated/ru/changelog/index.html | 1210 +++ mddocs/generated/ru/concepts/index.html | 1710 +++++ .../clickhouse/connection/index.html | 1185 +++ .../clickhouse/execute/index.html | 1376 ++++ .../db_connection/clickhouse/index.html | 1199 +++ .../clickhouse/prerequisites/index.html | 1301 ++++ .../db_connection/clickhouse/read/index.html | 1334 ++++ .../db_connection/clickhouse/sql/index.html | 1306 ++++ .../db_connection/clickhouse/types/index.html | 1722 +++++ .../db_connection/clickhouse/write/index.html | 1249 +++ .../greenplum/connection/index.html | 1185 +++ .../greenplum/execute/index.html | 1408 ++++ .../db_connection/greenplum/index.html | 1198 +++ .../greenplum/prerequisites/index.html | 1643 ++++ .../db_connection/greenplum/read/index.html | 1666 ++++ .../db_connection/greenplum/types/index.html | 1677 +++++ .../db_connection/greenplum/write/index.html | 1335 ++++ .../db_connection/hive/connection/index.html | 1186 +++ .../db_connection/hive/execute/index.html | 1261 ++++ .../connection/db_connection/hive/index.html | 1199 +++ .../hive/prerequisites/index.html | 1336 ++++ .../db_connection/hive/read/index.html | 1336 ++++ .../db_connection/hive/slots/index.html | 1186 +++ .../db_connection/hive/sql/index.html | 1311 ++++ .../db_connection/hive/write/index.html | 1565 ++++ .../ru/connection/db_connection/index.html | 1194 +++ .../db_connection/kafka/auth/index.html | 1186 +++ .../db_connection/kafka/basic_auth/index.html | 1187 +++ .../db_connection/kafka/connection/index.html | 1185 +++ .../connection/db_connection/kafka/index.html | 1213 +++ .../kafka/kerberos_auth/index.html | 1187 +++ .../kafka/plaintext_protocol/index.html | 1187 +++ .../kafka/prerequisites/index.html | 1301 ++++ .../db_connection/kafka/protocol/index.html | 1186 +++ .../db_connection/kafka/read/index.html | 1360 ++++ .../db_connection/kafka/scram_auth/index.html | 1187 +++ .../db_connection/kafka/slots/index.html | 1186 +++ .../kafka/ssl_protocol/index.html | 1187 +++ .../kafka/troubleshooting/index.html | 1203 +++ .../db_connection/kafka/write/index.html | 1276 ++++ .../mongodb/connection/index.html | 1186 +++ .../db_connection/mongodb/index.html | 1198 +++ .../db_connection/mongodb/pipeline/index.html | 1244 +++ .../mongodb/prerequisites/index.html | 1301 ++++ .../db_connection/mongodb/read/index.html | 1379 ++++ .../db_connection/mongodb/types/index.html | 1562 ++++ .../db_connection/mongodb/write/index.html | 1238 +++ .../db_connection/mssql/connection/index.html | 1185 +++ .../db_connection/mssql/execute/index.html | 1363 ++++ .../connection/db_connection/mssql/index.html | 1199 +++ .../mssql/prerequisites/index.html | 1307 ++++ .../db_connection/mssql/read/index.html | 1334 ++++ .../db_connection/mssql/sql/index.html | 1306 ++++ .../db_connection/mssql/types/index.html | 1645 ++++ .../db_connection/mssql/write/index.html | 1245 +++ .../db_connection/mysql/connection/index.html | 1185 +++ .../db_connection/mysql/execute/index.html | 1360 ++++ .../connection/db_connection/mysql/index.html | 1199 +++ .../mysql/prerequisites/index.html | 1292 ++++ .../db_connection/mysql/read/index.html | 1334 ++++ .../db_connection/mysql/sql/index.html | 1307 ++++ .../db_connection/mysql/types/index.html | 1653 ++++ .../db_connection/mysql/write/index.html | 1249 +++ .../oracle/connection/index.html | 1185 +++ .../db_connection/oracle/execute/index.html | 1360 ++++ .../db_connection/oracle/index.html | 1199 +++ .../oracle/prerequisites/index.html | 1340 ++++ .../db_connection/oracle/read/index.html | 1334 ++++ .../db_connection/oracle/sql/index.html | 1307 ++++ .../db_connection/oracle/types/index.html | 1676 ++++ .../db_connection/oracle/write/index.html | 1245 +++ .../postgres/connection/index.html | 1185 +++ .../db_connection/postgres/execute/index.html | 1358 ++++ .../db_connection/postgres/index.html | 1199 +++ .../postgres/prerequisites/index.html | 1309 ++++ .../db_connection/postgres/read/index.html | 1332 ++++ .../db_connection/postgres/sql/index.html | 1306 ++++ .../db_connection/postgres/types/index.html | 1790 +++++ .../db_connection/postgres/write/index.html | 1245 +++ .../teradata/connection/index.html | 1185 +++ .../db_connection/teradata/execute/index.html | 1357 ++++ .../db_connection/teradata/index.html | 1194 +++ .../teradata/prerequisites/index.html | 1290 ++++ .../db_connection/teradata/read/index.html | 1370 ++++ .../db_connection/teradata/sql/index.html | 1304 ++++ .../db_connection/teradata/write/index.html | 1334 ++++ .../connection/file_connection/ftp/index.html | 1185 +++ .../file_connection/ftps/index.html | 1185 +++ .../hdfs/connection/index.html | 1185 +++ .../file_connection/hdfs/index.html | 1190 +++ .../file_connection/hdfs/slots/index.html | 1186 +++ .../ru/connection/file_connection/index.html | 1191 +++ .../connection/file_connection/s3/index.html | 1185 +++ .../file_connection/samba/index.html | 1185 +++ .../file_connection/sftp/index.html | 1185 +++ .../file_connection/webdav/index.html | 1185 +++ .../file_df_connection/base/index.html | 1185 +++ .../connection/file_df_connection/index.html | 1194 +++ .../spark_hdfs/connection/index.html | 1185 +++ .../file_df_connection/spark_hdfs/index.html | 1191 +++ .../spark_hdfs/prerequisites/index.html | 1249 +++ .../spark_hdfs/slots/index.html | 1186 +++ .../spark_local_fs/index.html | 1185 +++ .../spark_s3/connection/index.html | 1185 +++ .../file_df_connection/spark_s3/index.html | 1187 +++ .../spark_s3/prerequisites/index.html | 1287 ++++ .../spark_s3/troubleshooting/index.html | 1660 ++++ mddocs/generated/ru/connection/index.html | 1212 +++ mddocs/generated/ru/contributing/index.html | 1648 ++++ mddocs/generated/ru/db_/index.html | 1186 +++ mddocs/generated/ru/db_/reader/index.html | 1192 +++ mddocs/generated/ru/db_/writer/index.html | 1190 +++ mddocs/generated/ru/index.html | 1344 ++++ mddocs/generated/ru/logging/index.html | 1587 ++++ mddocs/generated/ru/objects.inv | Bin 0 -> 228 bytes mddocs/generated/ru/plugins/index.html | 1397 ++++ mddocs/generated/ru/quickstart/index.html | 1970 +++++ mddocs/generated/ru/security/index.html | 1241 +++ mddocs/generated/ru/sitemap.xml | 3 + mddocs/generated/ru/sitemap.xml.gz | Bin 0 -> 127 bytes mddocs/generated/ru/snippet_0/index.html | 1332 ++++ mddocs/generated/ru/test/index.html | 1792 +++++ .../{common_rst => includes}/CONTRIBUTING.md | 0 mddocs/{common_rst => includes}/README.md | 0 mddocs/{common_rst => includes}/SECURITY.md | 0 .../_sphinx_design_static/design-tabs.js | 0 .../sphinx-design.min.css | 0 mddocs/{docs => includes}/robots.txt | 0 mddocs/mkdocs.yml | 46 - mddocs/overrides/assets/images/icon.svg | 11 + mddocs/overrides/assets/images/logo.svg | 214 + mddocs/overrides/assets/images/logo_wide.svg | 329 + .../assets/stylesheets/autodoc_pydantic.css | 11 + mddocs/ru/concepts.md | 458 -- mddocs/ru/contributing.md | 3 - mddocs/ru/index.md | 87 - mddocs/ru/plugins.md | 147 - mddocs/ru/quickstart.md | 4 - mddocs/ru/security.md | 3 - .../doctrees/changelog.doctree | Bin .../doctrees/changelog/0.10.0.doctree | Bin .../doctrees/changelog/0.10.1.doctree | Bin .../doctrees/changelog/0.10.2.doctree | Bin .../doctrees/changelog/0.11.0.doctree | Bin .../doctrees/changelog/0.11.1.doctree | Bin .../doctrees/changelog/0.11.2.doctree | Bin .../doctrees/changelog/0.12.0.doctree | Bin .../doctrees/changelog/0.12.1.doctree | Bin .../doctrees/changelog/0.12.2.doctree | Bin .../doctrees/changelog/0.12.3.doctree | Bin .../doctrees/changelog/0.12.4.doctree | Bin .../doctrees/changelog/0.12.5.doctree | Bin .../doctrees/changelog/0.13.0.doctree | Bin .../doctrees/changelog/0.13.1.doctree | Bin .../doctrees/changelog/0.13.3.doctree | Bin .../doctrees/changelog/0.13.4.doctree | Bin .../doctrees/changelog/0.7.0.doctree | Bin .../doctrees/changelog/0.7.1.doctree | Bin .../doctrees/changelog/0.7.2.doctree | Bin .../doctrees/changelog/0.8.0.doctree | Bin .../doctrees/changelog/0.8.1.doctree | Bin .../doctrees/changelog/0.9.0.doctree | Bin .../doctrees/changelog/0.9.1.doctree | Bin .../doctrees/changelog/0.9.2.doctree | Bin .../doctrees/changelog/0.9.3.doctree | Bin .../doctrees/changelog/0.9.4.doctree | Bin .../doctrees/changelog/0.9.5.doctree | Bin .../doctrees/changelog/DRAFT.doctree | Bin .../doctrees/changelog/NEXT_RELEASE.doctree | Bin .../doctrees/changelog/index.doctree | Bin .../doctrees/concepts.doctree | Bin .../clickhouse/connection.doctree | Bin .../db_connection/clickhouse/execute.doctree | Bin .../db_connection/clickhouse/index.doctree | Bin .../clickhouse/prerequisites.doctree | Bin .../db_connection/clickhouse/read.doctree | Bin .../db_connection/clickhouse/sql.doctree | Bin .../db_connection/clickhouse/types.doctree | Bin .../db_connection/clickhouse/write.doctree | Bin .../greenplum/connection.doctree | Bin .../db_connection/greenplum/execute.doctree | Bin .../db_connection/greenplum/index.doctree | Bin .../greenplum/prerequisites.doctree | Bin .../db_connection/greenplum/read.doctree | Bin .../db_connection/greenplum/types.doctree | Bin .../db_connection/greenplum/write.doctree | Bin .../db_connection/hive/connection.doctree | Bin .../db_connection/hive/execute.doctree | Bin .../db_connection/hive/index.doctree | Bin .../db_connection/hive/prerequisites.doctree | Bin .../db_connection/hive/read.doctree | Bin .../db_connection/hive/slots.doctree | Bin .../connection/db_connection/hive/sql.doctree | Bin .../db_connection/hive/write.doctree | Bin .../connection/db_connection/index.doctree | Bin .../db_connection/kafka/auth.doctree | Bin .../db_connection/kafka/basic_auth.doctree | Bin .../db_connection/kafka/connection.doctree | Bin .../db_connection/kafka/index.doctree | Bin .../db_connection/kafka/kerberos_auth.doctree | Bin .../kafka/plaintext_protocol.doctree | Bin .../db_connection/kafka/prerequisites.doctree | Bin .../db_connection/kafka/protocol.doctree | Bin .../db_connection/kafka/read.doctree | Bin .../db_connection/kafka/scram_auth.doctree | Bin .../db_connection/kafka/slots.doctree | Bin .../db_connection/kafka/ssl_protocol.doctree | Bin .../kafka/troubleshooting.doctree | Bin .../db_connection/kafka/write.doctree | Bin .../db_connection/mongodb/connection.doctree | Bin .../db_connection/mongodb/index.doctree | Bin .../db_connection/mongodb/pipeline.doctree | Bin .../mongodb/prerequisites.doctree | Bin .../db_connection/mongodb/read.doctree | Bin .../db_connection/mongodb/types.doctree | Bin .../db_connection/mongodb/write.doctree | Bin .../db_connection/mssql/connection.doctree | Bin .../db_connection/mssql/execute.doctree | Bin .../db_connection/mssql/index.doctree | Bin .../db_connection/mssql/prerequisites.doctree | Bin .../db_connection/mssql/read.doctree | Bin .../db_connection/mssql/sql.doctree | Bin .../db_connection/mssql/types.doctree | Bin .../db_connection/mssql/write.doctree | Bin .../db_connection/mysql/connection.doctree | Bin .../db_connection/mysql/execute.doctree | Bin .../db_connection/mysql/index.doctree | Bin .../db_connection/mysql/prerequisites.doctree | Bin .../db_connection/mysql/read.doctree | Bin .../db_connection/mysql/sql.doctree | Bin .../db_connection/mysql/types.doctree | Bin .../db_connection/mysql/write.doctree | Bin .../db_connection/oracle/connection.doctree | Bin .../db_connection/oracle/execute.doctree | Bin .../db_connection/oracle/index.doctree | Bin .../oracle/prerequisites.doctree | Bin .../db_connection/oracle/read.doctree | Bin .../db_connection/oracle/sql.doctree | Bin .../db_connection/oracle/types.doctree | Bin .../db_connection/oracle/write.doctree | Bin .../db_connection/postgres/connection.doctree | Bin .../db_connection/postgres/execute.doctree | Bin .../db_connection/postgres/index.doctree | Bin .../postgres/prerequisites.doctree | Bin .../db_connection/postgres/read.doctree | Bin .../db_connection/postgres/sql.doctree | Bin .../db_connection/postgres/types.doctree | Bin .../db_connection/postgres/write.doctree | Bin .../db_connection/teradata/connection.doctree | Bin .../db_connection/teradata/execute.doctree | Bin .../db_connection/teradata/index.doctree | Bin .../teradata/prerequisites.doctree | Bin .../db_connection/teradata/read.doctree | Bin .../db_connection/teradata/sql.doctree | Bin .../db_connection/teradata/write.doctree | Bin .../connection/file_connection/ftp.doctree | Bin .../connection/file_connection/ftps.doctree | Bin .../file_connection/hdfs/connection.doctree | Bin .../file_connection/hdfs/index.doctree | Bin .../file_connection/hdfs/slots.doctree | Bin .../connection/file_connection/index.doctree | Bin .../connection/file_connection/s3.doctree | Bin .../connection/file_connection/samba.doctree | Bin .../connection/file_connection/sftp.doctree | Bin .../connection/file_connection/webdav.doctree | Bin .../file_df_connection/base.doctree | Bin .../file_df_connection/index.doctree | Bin .../spark_hdfs/connection.doctree | Bin .../spark_hdfs/index.doctree | Bin .../spark_hdfs/prerequisites.doctree | Bin .../spark_hdfs/slots.doctree | Bin .../file_df_connection/spark_local_fs.doctree | Bin .../spark_s3/connection.doctree | Bin .../file_df_connection/spark_s3/index.doctree | Bin .../spark_s3/prerequisites.doctree | Bin .../spark_s3/troubleshooting.doctree | Bin .../doctrees/connection/index.doctree | Bin .../doctrees/contributing.doctree | Bin .../doctrees/db/db_reader.doctree | Bin .../doctrees/db/db_writer.doctree | Bin .../doctrees/db/index.doctree | Bin .../file_downloader/file_downloader.doctree | Bin .../file/file_downloader/index.doctree | Bin .../file/file_downloader/options.doctree | Bin .../file/file_downloader/result.doctree | Bin .../doctrees/file/file_filters/base.doctree | Bin .../file/file_filters/exclude_dir.doctree | Bin .../file/file_filters/file_filter.doctree | Bin .../file_filters/file_mtime_filter.doctree | Bin .../file_filters/file_size_filter.doctree | Bin .../doctrees/file/file_filters/glob.doctree | Bin .../doctrees/file/file_filters/index.doctree | Bin .../file_filters/match_all_filters.doctree | Bin .../doctrees/file/file_filters/regexp.doctree | Bin .../doctrees/file/file_limits/base.doctree | Bin .../file/file_limits/file_limit.doctree | Bin .../doctrees/file/file_limits/index.doctree | Bin .../file/file_limits/limits_reached.doctree | Bin .../file/file_limits/limits_stop_at.doctree | Bin .../file/file_limits/max_files_count.doctree | Bin .../file/file_limits/reset_limits.doctree | Bin .../file/file_limits/total_files_size.doctree | Bin .../file/file_mover/file_mover.doctree | Bin .../doctrees/file/file_mover/index.doctree | Bin .../doctrees/file/file_mover/options.doctree | Bin .../doctrees/file/file_mover/result.doctree | Bin .../file/file_uploader/file_uploader.doctree | Bin .../doctrees/file/file_uploader/index.doctree | Bin .../file/file_uploader/options.doctree | Bin .../file/file_uploader/result.doctree | Bin .../doctrees/file/index.doctree | Bin .../file_df_reader/file_df_reader.doctree | Bin .../file_df/file_df_reader/index.doctree | Bin .../file_df/file_df_reader/options.doctree | Bin .../file_df_writer/file_df_writer.doctree | Bin .../file_df/file_df_writer/index.doctree | Bin .../file_df/file_df_writer/options.doctree | Bin .../file_df/file_formats/avro.doctree | Bin .../file_df/file_formats/base.doctree | Bin .../doctrees/file_df/file_formats/csv.doctree | Bin .../file_df/file_formats/excel.doctree | Bin .../file_df/file_formats/index.doctree | Bin .../file_df/file_formats/json.doctree | Bin .../file_df/file_formats/jsonline.doctree | Bin .../doctrees/file_df/file_formats/orc.doctree | Bin .../file_df/file_formats/parquet.doctree | Bin .../doctrees/file_df/file_formats/xml.doctree | Bin .../doctrees/file_df/index.doctree | Bin .../doctrees/hooks/design.doctree | Bin .../doctrees/hooks/global_state.doctree | Bin .../doctrees/hooks/hook.doctree | Bin .../doctrees/hooks/index.doctree | Bin .../doctrees/hooks/slot.doctree | Bin .../doctrees/hooks/support_hooks.doctree | Bin .../doctrees/hwm_store/index.doctree | Bin .../doctrees/hwm_store/yaml_hwm_store.doctree | Bin .../doctrees/index.doctree | Bin .../doctrees/install/files.doctree | Bin .../doctrees/install/full.doctree | Bin .../doctrees/install/index.doctree | Bin .../doctrees/install/kerberos.doctree | Bin .../doctrees/install/spark.doctree | Bin .../doctrees/logging.doctree | Bin .../doctrees/plugins.doctree | Bin .../doctrees/quickstart.doctree | Bin .../doctrees/security.doctree | Bin .../incremental_batch_strategy.doctree | Bin .../strategy/incremental_strategy.doctree | Bin .../doctrees/strategy/index.doctree | Bin .../strategy/snapshot_batch_strategy.doctree | Bin .../strategy/snapshot_strategy.doctree | Bin .../doctrees/troubleshooting/index.doctree | Bin .../doctrees/troubleshooting/spark.doctree | Bin .../markdown/changelog.md | 0 .../markdown/changelog/0.10.0.md | 0 .../markdown/changelog/0.10.1.md | 0 .../markdown/changelog/0.10.2.md | 0 .../markdown/changelog/0.11.0.md | 0 .../markdown}/changelog/0.11.1.md | 0 .../markdown}/changelog/0.11.2.md | 0 .../markdown/changelog/0.12.0.md | 0 .../markdown/changelog/0.12.1.md | 0 .../markdown}/changelog/0.12.2.md | 0 .../markdown}/changelog/0.12.3.md | 0 .../markdown}/changelog/0.12.4.md | 0 .../markdown}/changelog/0.12.5.md | 0 .../markdown/changelog/0.13.0.md | 0 .../markdown/changelog/0.13.1.md | 0 .../markdown}/changelog/0.13.3.md | 0 .../markdown}/changelog/0.13.4.md | 0 .../markdown/changelog/0.7.0.md | 0 .../markdown/changelog/0.7.1.md | 0 .../markdown/changelog/0.7.2.md | 0 .../markdown/changelog/0.8.0.md | 0 .../markdown/changelog/0.8.1.md | 0 .../markdown/changelog/0.9.0.md | 0 .../markdown}/changelog/0.9.1.md | 0 .../markdown/changelog/0.9.2.md | 0 .../markdown}/changelog/0.9.3.md | 0 .../markdown/changelog/0.9.4.md | 0 .../markdown}/changelog/0.9.5.md | 0 .../markdown/changelog/DRAFT.md | 0 .../markdown/changelog/NEXT_RELEASE.md | 0 .../markdown/changelog/index.md | 0 .../markdown/concepts.md | 0 .../db_connection/clickhouse/connection.md | 0 .../db_connection/clickhouse/execute.md | 0 .../db_connection/clickhouse/index.md | 0 .../db_connection/clickhouse/prerequisites.md | 0 .../db_connection/clickhouse/read.md | 0 .../db_connection/clickhouse/sql.md | 0 .../db_connection/clickhouse/types.md | 0 .../db_connection/clickhouse/write.md | 0 .../db_connection/greenplum/connection.md | 0 .../db_connection/greenplum/execute.md | 0 .../db_connection/greenplum/index.md | 0 .../db_connection/greenplum/prerequisites.md | 0 .../db_connection/greenplum/read.md | 0 .../db_connection/greenplum/types.md | 0 .../db_connection/greenplum/write.md | 0 .../db_connection/hive/connection.md | 0 .../connection/db_connection/hive/execute.md | 0 .../connection/db_connection/hive/index.md | 0 .../db_connection/hive/prerequisites.md | 0 .../connection/db_connection/hive/read.md | 0 .../connection/db_connection/hive/slots.md | 0 .../connection/db_connection/hive/sql.md | 0 .../connection/db_connection/hive/write.md | 0 .../connection/db_connection/index.md | 0 .../connection/db_connection/kafka/auth.md | 0 .../db_connection/kafka/basic_auth.md | 0 .../db_connection/kafka/connection.md | 0 .../connection/db_connection/kafka/index.md | 0 .../db_connection/kafka/kerberos_auth.md | 0 .../db_connection/kafka/plaintext_protocol.md | 0 .../db_connection/kafka/prerequisites.md | 0 .../db_connection/kafka/protocol.md | 0 .../connection/db_connection/kafka/read.md | 0 .../db_connection/kafka/scram_auth.md | 0 .../connection/db_connection/kafka/slots.md | 0 .../db_connection/kafka/ssl_protocol.md | 0 .../db_connection/kafka/troubleshooting.md | 0 .../connection/db_connection/kafka/write.md | 0 .../db_connection/mongodb/connection.md | 0 .../connection/db_connection/mongodb/index.md | 0 .../db_connection/mongodb/pipeline.md | 0 .../db_connection/mongodb/prerequisites.md | 0 .../connection/db_connection/mongodb/read.md | 0 .../connection/db_connection/mongodb/types.md | 0 .../connection/db_connection/mongodb/write.md | 0 .../db_connection/mssql/connection.md | 0 .../connection/db_connection/mssql/execute.md | 0 .../connection/db_connection/mssql/index.md | 0 .../db_connection/mssql/prerequisites.md | 0 .../connection/db_connection/mssql/read.md | 0 .../connection/db_connection/mssql/sql.md | 0 .../connection/db_connection/mssql/types.md | 0 .../connection/db_connection/mssql/write.md | 0 .../db_connection/mysql/connection.md | 0 .../connection/db_connection/mysql/execute.md | 0 .../connection/db_connection/mysql/index.md | 0 .../db_connection/mysql/prerequisites.md | 0 .../connection/db_connection/mysql/read.md | 0 .../connection/db_connection/mysql/sql.md | 0 .../connection/db_connection/mysql/types.md | 0 .../connection/db_connection/mysql/write.md | 0 .../db_connection/oracle/connection.md | 0 .../db_connection/oracle/execute.md | 0 .../connection/db_connection/oracle/index.md | 0 .../db_connection/oracle/prerequisites.md | 0 .../connection/db_connection/oracle/read.md | 0 .../connection/db_connection/oracle/sql.md | 0 .../connection/db_connection/oracle/types.md | 0 .../connection/db_connection/oracle/write.md | 0 .../db_connection/postgres/connection.md | 0 .../db_connection/postgres/execute.md | 0 .../db_connection/postgres/index.md | 0 .../db_connection/postgres/prerequisites.md | 0 .../connection/db_connection/postgres/read.md | 0 .../connection/db_connection/postgres/sql.md | 0 .../db_connection/postgres/types.md | 0 .../db_connection/postgres/write.md | 0 .../db_connection/teradata/connection.md | 0 .../db_connection/teradata/execute.md | 0 .../db_connection/teradata/index.md | 0 .../db_connection/teradata/prerequisites.md | 0 .../connection/db_connection/teradata/read.md | 0 .../connection/db_connection/teradata/sql.md | 0 .../db_connection/teradata/write.md | 0 .../connection/file_connection/ftp.md | 0 .../connection/file_connection/ftps.md | 0 .../file_connection/hdfs/connection.md | 0 .../connection/file_connection/hdfs/index.md | 0 .../connection/file_connection/hdfs/slots.md | 0 .../connection/file_connection/index.md | 0 .../markdown/connection/file_connection/s3.md | 0 .../connection/file_connection/samba.md | 0 .../connection/file_connection/sftp.md | 0 .../connection/file_connection/webdav.md | 0 .../connection/file_df_connection/base.md | 0 .../connection/file_df_connection/index.md | 0 .../spark_hdfs/connection.md | 0 .../file_df_connection/spark_hdfs/index.md | 0 .../spark_hdfs/prerequisites.md | 0 .../file_df_connection/spark_hdfs/slots.md | 0 .../file_df_connection/spark_local_fs.md | 0 .../file_df_connection/spark_s3/connection.md | 0 .../file_df_connection/spark_s3/index.md | 0 .../spark_s3/prerequisites.md | 0 .../spark_s3/troubleshooting.md | 0 .../markdown/connection/index.md | 0 mddocs/sphinx_build/markdown/contributing.md | 391 + .../markdown/db/db_reader.md | 0 .../markdown/db/db_writer.md | 0 .../markdown/db/index.md | 0 .../file/file_downloader/file_downloader.md | 0 .../markdown/file/file_downloader/index.md | 0 .../markdown/file/file_downloader/options.md | 0 .../markdown/file/file_downloader/result.md | 0 .../markdown/file/file_filters/base.md | 0 .../markdown/file/file_filters/exclude_dir.md | 0 .../markdown/file/file_filters/file_filter.md | 0 .../file/file_filters/file_mtime_filter.md | 0 .../file/file_filters/file_size_filter.md | 0 .../markdown/file/file_filters/glob.md | 0 .../markdown/file/file_filters/index.md | 0 .../file/file_filters/match_all_filters.md | 0 .../markdown/file/file_filters/regexp.md | 0 .../markdown/file/file_limits/base.md | 0 .../markdown/file/file_limits/file_limit.md | 0 .../markdown/file/file_limits/index.md | 0 .../file/file_limits/limits_reached.md | 0 .../file/file_limits/limits_stop_at.md | 0 .../file/file_limits/max_files_count.md | 0 .../markdown/file/file_limits/reset_limits.md | 0 .../file/file_limits/total_files_size.md | 0 .../markdown/file/file_mover/file_mover.md | 0 .../markdown/file/file_mover/index.md | 0 .../markdown/file/file_mover/options.md | 0 .../markdown/file/file_mover/result.md | 0 .../file/file_uploader/file_uploader.md | 0 .../markdown/file/file_uploader/index.md | 0 .../markdown/file/file_uploader/options.md | 0 .../markdown/file/file_uploader/result.md | 0 .../markdown/file/index.md | 0 .../file_df/file_df_reader/file_df_reader.md | 0 .../markdown/file_df/file_df_reader/index.md | 0 .../file_df/file_df_reader/options.md | 0 .../file_df/file_df_writer/file_df_writer.md | 0 .../markdown/file_df/file_df_writer/index.md | 0 .../file_df/file_df_writer/options.md | 0 .../markdown/file_df/file_formats/avro.md | 0 .../markdown/file_df/file_formats/base.md | 0 .../markdown/file_df/file_formats/csv.md | 0 .../markdown/file_df/file_formats/excel.md | 0 .../markdown/file_df/file_formats/index.md | 0 .../markdown/file_df/file_formats/json.md | 0 .../markdown/file_df/file_formats/jsonline.md | 0 .../markdown/file_df/file_formats/orc.md | 0 .../markdown/file_df/file_formats/parquet.md | 0 .../markdown/file_df/file_formats/xml.md | 0 .../markdown/file_df/index.md | 0 .../markdown/hooks/design.md | 0 .../markdown/hooks/global_state.md | 0 .../markdown/hooks/hook.md | 0 .../markdown/hooks/index.md | 0 .../markdown/hooks/slot.md | 0 .../markdown/hooks/support_hooks.md | 0 .../markdown/hwm_store/index.md | 0 .../markdown/hwm_store/yaml_hwm_store.md | 0 .../_build => sphinx_build}/markdown/index.md | 0 .../markdown/install/files.md | 0 .../markdown/install/full.md | 0 .../markdown/install/index.md | 0 .../markdown/install/kerberos.md | 0 .../markdown/install/spark.md | 0 .../markdown/logging.md | 0 .../markdown/plugins.md | 0 .../markdown/quickstart.md | 0 .../markdown/security.md | 0 .../strategy/incremental_batch_strategy.md | 0 .../markdown/strategy/incremental_strategy.md | 0 .../markdown/strategy/index.md | 0 .../strategy/snapshot_batch_strategy.md | 0 .../markdown/strategy/snapshot_strategy.md | 0 .../markdown/troubleshooting/index.md | 0 .../markdown/troubleshooting/spark.md | 0 onetl/log.py | 168 +- 1293 files changed, 512126 insertions(+), 1800 deletions(-) create mode 100644 mddocs/config/en/mkdocs.yml create mode 100644 mddocs/config/ru/mkdocs.yml delete mode 100644 mddocs/docs/Makefile delete mode 100644 mddocs/docs/concepts.md delete mode 100644 mddocs/docs/conf.py delete mode 100644 mddocs/docs/connection/file_df_connection/index.md delete mode 100644 mddocs/docs/connection/index.md delete mode 100644 mddocs/docs/contributing.md rename mddocs/docs/{ => en}/changelog.md (100%) rename mddocs/docs/{ => en}/changelog/0.10.0.md (100%) rename mddocs/docs/{ => en}/changelog/0.10.1.md (100%) rename mddocs/docs/{ => en}/changelog/0.10.2.md (100%) rename mddocs/docs/{ => en}/changelog/0.11.0.md (100%) rename mddocs/docs/{_build/markdown => en}/changelog/0.11.1.md (100%) rename mddocs/docs/{_build/markdown => en}/changelog/0.11.2.md (100%) rename mddocs/docs/{ => en}/changelog/0.12.0.md (100%) rename mddocs/docs/{ => en}/changelog/0.12.1.md (100%) rename mddocs/docs/{_build/markdown => en}/changelog/0.12.2.md (100%) rename mddocs/docs/{_build/markdown => en}/changelog/0.12.3.md (100%) rename mddocs/docs/{_build/markdown => en}/changelog/0.12.4.md (100%) rename mddocs/docs/{_build/markdown => en}/changelog/0.12.5.md (100%) rename mddocs/docs/{ => en}/changelog/0.13.0.md (100%) rename mddocs/docs/{ => en}/changelog/0.13.1.md (100%) rename mddocs/docs/{_build/markdown => en}/changelog/0.13.3.md (100%) rename mddocs/docs/{_build/markdown => en}/changelog/0.13.4.md (100%) rename mddocs/docs/{ => en}/changelog/0.7.0.md (100%) rename mddocs/docs/{ => en}/changelog/0.7.1.md (100%) rename mddocs/docs/{ => en}/changelog/0.7.2.md (100%) rename mddocs/docs/{ => en}/changelog/0.8.0.md (100%) rename mddocs/docs/{ => en}/changelog/0.8.1.md (100%) rename mddocs/docs/{ => en}/changelog/0.9.0.md (100%) rename mddocs/docs/{_build/markdown => en}/changelog/0.9.1.md (100%) rename mddocs/docs/{ => en}/changelog/0.9.2.md (100%) rename mddocs/docs/{_build/markdown => en}/changelog/0.9.3.md (100%) rename mddocs/docs/{ => en}/changelog/0.9.4.md (100%) rename mddocs/docs/{_build/markdown => en}/changelog/0.9.5.md (100%) rename mddocs/docs/{ => en}/changelog/DRAFT.md (100%) rename mddocs/docs/{ => en}/changelog/NEXT_RELEASE.md (100%) rename mddocs/docs/{ => en}/changelog/index.md (100%) rename mddocs/docs/{ => en}/changelog/next_release/.keep (100%) create mode 100644 mddocs/docs/en/concepts.md rename mddocs/docs/{ => en}/connection/db_connection/clickhouse/connection.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/clickhouse/execute.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/clickhouse/index.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/clickhouse/prerequisites.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/clickhouse/read.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/clickhouse/sql.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/clickhouse/types.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/clickhouse/write.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/greenplum/connection.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/greenplum/execute.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/greenplum/index.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/greenplum/prerequisites.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/greenplum/read.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/greenplum/types.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/greenplum/write.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/hive/connection.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/hive/execute.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/hive/index.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/hive/prerequisites.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/hive/read.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/hive/slots.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/hive/sql.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/hive/write.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/index.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/kafka/auth.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/kafka/basic_auth.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/kafka/connection.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/kafka/index.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/kafka/kerberos_auth.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/kafka/plaintext_protocol.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/kafka/prerequisites.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/kafka/protocol.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/kafka/read.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/kafka/scram_auth.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/kafka/slots.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/kafka/ssl_protocol.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/kafka/troubleshooting.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/kafka/write.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mongodb/connection.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mongodb/index.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mongodb/pipeline.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mongodb/prerequisites.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mongodb/read.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mongodb/types.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mongodb/write.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mssql/connection.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mssql/execute.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mssql/index.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mssql/prerequisites.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mssql/read.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mssql/sql.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mssql/types.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mssql/write.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mysql/connection.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mysql/execute.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mysql/index.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mysql/prerequisites.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mysql/read.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mysql/sql.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mysql/types.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/mysql/write.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/oracle/connection.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/oracle/execute.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/oracle/index.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/oracle/prerequisites.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/oracle/read.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/oracle/sql.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/oracle/types.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/oracle/write.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/postgres/connection.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/postgres/execute.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/postgres/index.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/postgres/prerequisites.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/postgres/read.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/postgres/sql.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/postgres/types.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/postgres/write.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/teradata/connection.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/teradata/execute.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/teradata/index.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/teradata/prerequisites.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/teradata/read.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/teradata/sql.md (100%) rename mddocs/docs/{ => en}/connection/db_connection/teradata/write.md (100%) rename mddocs/docs/{ => en}/connection/file_connection/ftp.md (100%) rename mddocs/docs/{ => en}/connection/file_connection/ftps.md (100%) rename mddocs/docs/{ => en}/connection/file_connection/hdfs/connection.md (100%) rename mddocs/docs/{ => en}/connection/file_connection/hdfs/index.md (100%) rename mddocs/docs/{ => en}/connection/file_connection/hdfs/slots.md (100%) rename mddocs/docs/{ => en}/connection/file_connection/index.md (100%) rename mddocs/docs/{ => en}/connection/file_connection/s3.md (100%) rename mddocs/docs/{ => en}/connection/file_connection/samba.md (100%) rename mddocs/docs/{ => en}/connection/file_connection/sftp.md (100%) rename mddocs/docs/{ => en}/connection/file_connection/webdav.md (100%) rename mddocs/docs/{ => en}/connection/file_df_connection/base.md (100%) create mode 100644 mddocs/docs/en/connection/file_df_connection/index.md rename mddocs/docs/{ => en}/connection/file_df_connection/spark_hdfs/connection.md (100%) rename mddocs/docs/{ => en}/connection/file_df_connection/spark_hdfs/index.md (100%) rename mddocs/docs/{ => en}/connection/file_df_connection/spark_hdfs/prerequisites.md (100%) rename mddocs/docs/{ => en}/connection/file_df_connection/spark_hdfs/slots.md (100%) rename mddocs/docs/{ => en}/connection/file_df_connection/spark_local_fs.md (100%) rename mddocs/docs/{ => en}/connection/file_df_connection/spark_s3/connection.md (100%) rename mddocs/docs/{ => en}/connection/file_df_connection/spark_s3/index.md (100%) rename mddocs/docs/{ => en}/connection/file_df_connection/spark_s3/prerequisites.md (100%) rename mddocs/docs/{ => en}/connection/file_df_connection/spark_s3/troubleshooting.md (100%) create mode 100644 mddocs/docs/en/connection/index.md rename mddocs/docs/{_build/markdown => en}/contributing.md (100%) rename mddocs/docs/{db => en/db_}/index.md (85%) rename mddocs/docs/{db/db_reader.md => en/db_/reader.md} (95%) rename mddocs/docs/{db/db_writer.md => en/db_/writer.md} (94%) rename mddocs/docs/{ => en}/file/file_downloader/file_downloader.md (100%) rename mddocs/docs/{ => en}/file/file_downloader/index.md (100%) rename mddocs/docs/{ => en}/file/file_downloader/options.md (100%) rename mddocs/docs/{ => en}/file/file_downloader/result.md (100%) rename mddocs/docs/{ => en}/file/file_filters/base.md (100%) rename mddocs/docs/{ => en}/file/file_filters/exclude_dir.md (100%) rename mddocs/docs/{ => en}/file/file_filters/file_filter.md (100%) rename mddocs/docs/{ => en}/file/file_filters/file_mtime_filter.md (100%) rename mddocs/docs/{ => en}/file/file_filters/file_size_filter.md (100%) rename mddocs/docs/{ => en}/file/file_filters/glob.md (100%) rename mddocs/docs/{ => en}/file/file_filters/index.md (100%) rename mddocs/docs/{ => en}/file/file_filters/match_all_filters.md (100%) rename mddocs/docs/{ => en}/file/file_filters/regexp.md (100%) rename mddocs/docs/{ => en}/file/file_limits/base.md (100%) rename mddocs/docs/{ => en}/file/file_limits/file_limit.md (100%) rename mddocs/docs/{ => en}/file/file_limits/index.md (100%) rename mddocs/docs/{ => en}/file/file_limits/limits_reached.md (100%) rename mddocs/docs/{ => en}/file/file_limits/limits_stop_at.md (100%) rename mddocs/docs/{ => en}/file/file_limits/max_files_count.md (100%) rename mddocs/docs/{ => en}/file/file_limits/reset_limits.md (100%) rename mddocs/docs/{ => en}/file/file_limits/total_files_size.md (100%) rename mddocs/docs/{ => en}/file/file_mover/file_mover.md (100%) rename mddocs/docs/{ => en}/file/file_mover/index.md (100%) rename mddocs/docs/{ => en}/file/file_mover/options.md (100%) rename mddocs/docs/{ => en}/file/file_mover/result.md (100%) rename mddocs/docs/{ => en}/file/file_uploader/file_uploader.md (100%) rename mddocs/docs/{ => en}/file/file_uploader/index.md (100%) rename mddocs/docs/{ => en}/file/file_uploader/options.md (100%) rename mddocs/docs/{ => en}/file/file_uploader/result.md (100%) rename mddocs/docs/{ => en}/file/index.md (100%) rename mddocs/docs/{ => en}/file_df/file_df_reader/file_df_reader.md (100%) rename mddocs/docs/{ => en}/file_df/file_df_reader/index.md (100%) rename mddocs/docs/{ => en}/file_df/file_df_reader/options.md (100%) rename mddocs/docs/{ => en}/file_df/file_df_writer/file_df_writer.md (100%) rename mddocs/docs/{ => en}/file_df/file_df_writer/index.md (100%) rename mddocs/docs/{ => en}/file_df/file_df_writer/options.md (100%) rename mddocs/docs/{ => en}/file_df/file_formats/avro.md (100%) rename mddocs/docs/{ => en}/file_df/file_formats/base.md (100%) rename mddocs/docs/{ => en}/file_df/file_formats/csv.md (100%) rename mddocs/docs/{ => en}/file_df/file_formats/excel.md (100%) rename mddocs/docs/{ => en}/file_df/file_formats/index.md (100%) rename mddocs/docs/{ => en}/file_df/file_formats/json.md (100%) rename mddocs/docs/{ => en}/file_df/file_formats/jsonline.md (100%) rename mddocs/docs/{ => en}/file_df/file_formats/orc.md (100%) rename mddocs/docs/{ => en}/file_df/file_formats/parquet.md (100%) rename mddocs/docs/{ => en}/file_df/file_formats/xml.md (100%) rename mddocs/docs/{ => en}/file_df/index.md (100%) rename mddocs/docs/{ => en}/hooks/design.md (100%) rename mddocs/docs/{ => en}/hooks/global_state.md (100%) rename mddocs/docs/{ => en}/hooks/hook.md (100%) rename mddocs/docs/{ => en}/hooks/index.md (81%) rename mddocs/docs/{ => en}/hooks/slot.md (100%) rename mddocs/docs/{ => en}/hooks/support_hooks.md (100%) rename mddocs/docs/{ => en}/hwm_store/index.md (100%) rename mddocs/docs/{ => en}/hwm_store/yaml_hwm_store.md (100%) create mode 100644 mddocs/docs/en/index.md rename mddocs/docs/{ => en}/install/files.md (100%) rename mddocs/docs/{ => en}/install/full.md (100%) rename mddocs/docs/{ => en}/install/index.md (100%) rename mddocs/docs/{ => en}/install/kerberos.md (100%) rename mddocs/docs/{ => en}/install/spark.md (100%) rename mddocs/docs/{ => en}/logging.md (95%) rename mddocs/docs/{ => en}/plugins.md (93%) create mode 100644 mddocs/docs/en/quickstart.md rename mddocs/{ru/SECURITY.md => docs/en/security.md} (72%) create mode 100644 mddocs/docs/en/snippet_0.md rename mddocs/docs/{ => en}/strategy/incremental_batch_strategy.md (100%) rename mddocs/docs/{ => en}/strategy/incremental_strategy.md (100%) rename mddocs/docs/{ => en}/strategy/index.md (100%) rename mddocs/docs/{ => en}/strategy/snapshot_batch_strategy.md (100%) rename mddocs/docs/{ => en}/strategy/snapshot_strategy.md (100%) rename mddocs/docs/{ => en}/troubleshooting/index.md (100%) rename mddocs/docs/{ => en}/troubleshooting/spark.md (100%) delete mode 100644 mddocs/docs/index.md delete mode 100644 mddocs/docs/make.bat delete mode 100644 mddocs/docs/quickstart.md rename mddocs/{ => docs}/ru/README.md (99%) rename mddocs/{ => docs}/ru/changelog/0.10.0.md (100%) rename mddocs/{ => docs}/ru/changelog/0.10.1.md (100%) rename mddocs/{ => docs}/ru/changelog/0.10.2.md (100%) rename mddocs/{ => docs}/ru/changelog/0.11.0.md (100%) rename mddocs/{ => docs}/ru/changelog/0.11.1.md (100%) rename mddocs/{ => docs}/ru/changelog/0.11.2.md (100%) rename mddocs/{ => docs}/ru/changelog/0.12.0.md (100%) rename mddocs/{ => docs}/ru/changelog/0.12.1.md (100%) rename mddocs/{ => docs}/ru/changelog/0.12.2.md (100%) rename mddocs/{ => docs}/ru/changelog/0.12.3.md (100%) rename mddocs/{ => docs}/ru/changelog/0.12.4.md (100%) rename mddocs/{ => docs}/ru/changelog/0.12.5.md (100%) rename mddocs/{ => docs}/ru/changelog/0.13.0.md (100%) rename mddocs/{ => docs}/ru/changelog/0.13.1.md (100%) rename mddocs/{ => docs}/ru/changelog/0.13.3.md (100%) rename mddocs/{ => docs}/ru/changelog/0.13.4.md (100%) rename mddocs/{ => docs}/ru/changelog/0.7.0.md (96%) rename mddocs/{ => docs}/ru/changelog/0.7.1.md (100%) rename mddocs/{ => docs}/ru/changelog/0.7.2.md (100%) rename mddocs/{ => docs}/ru/changelog/0.8.0.md (100%) rename mddocs/{ => docs}/ru/changelog/0.8.1.md (100%) rename mddocs/{ => docs}/ru/changelog/0.9.0.md (100%) rename mddocs/{ => docs}/ru/changelog/0.9.1.md (100%) rename mddocs/{ => docs}/ru/changelog/0.9.2.md (100%) rename mddocs/{ => docs}/ru/changelog/0.9.3.md (100%) rename mddocs/{ => docs}/ru/changelog/0.9.4.md (100%) rename mddocs/{ => docs}/ru/changelog/0.9.5.md (100%) create mode 100644 mddocs/docs/ru/changelog/index.md create mode 100644 mddocs/docs/ru/concepts.md create mode 100644 mddocs/docs/ru/connection/db_connection/clickhouse/connection.md create mode 100644 mddocs/docs/ru/connection/db_connection/clickhouse/execute.md create mode 100644 mddocs/docs/ru/connection/db_connection/clickhouse/index.md create mode 100644 mddocs/docs/ru/connection/db_connection/clickhouse/prerequisites.md create mode 100644 mddocs/docs/ru/connection/db_connection/clickhouse/read.md create mode 100644 mddocs/docs/ru/connection/db_connection/clickhouse/sql.md create mode 100644 mddocs/docs/ru/connection/db_connection/clickhouse/types.md create mode 100644 mddocs/docs/ru/connection/db_connection/clickhouse/write.md create mode 100644 mddocs/docs/ru/connection/db_connection/greenplum/connection.md create mode 100644 mddocs/docs/ru/connection/db_connection/greenplum/execute.md create mode 100644 mddocs/docs/ru/connection/db_connection/greenplum/index.md create mode 100644 mddocs/docs/ru/connection/db_connection/greenplum/prerequisites.md create mode 100644 mddocs/docs/ru/connection/db_connection/greenplum/read.md create mode 100644 mddocs/docs/ru/connection/db_connection/greenplum/types.md create mode 100644 mddocs/docs/ru/connection/db_connection/greenplum/write.md create mode 100644 mddocs/docs/ru/connection/db_connection/hive/connection.md create mode 100644 mddocs/docs/ru/connection/db_connection/hive/execute.md create mode 100644 mddocs/docs/ru/connection/db_connection/hive/index.md create mode 100644 mddocs/docs/ru/connection/db_connection/hive/prerequisites.md create mode 100644 mddocs/docs/ru/connection/db_connection/hive/read.md create mode 100644 mddocs/docs/ru/connection/db_connection/hive/slots.md create mode 100644 mddocs/docs/ru/connection/db_connection/hive/sql.md create mode 100644 mddocs/docs/ru/connection/db_connection/hive/write.md create mode 100644 mddocs/docs/ru/connection/db_connection/index.md create mode 100644 mddocs/docs/ru/connection/db_connection/kafka/auth.md create mode 100644 mddocs/docs/ru/connection/db_connection/kafka/basic_auth.md create mode 100644 mddocs/docs/ru/connection/db_connection/kafka/connection.md create mode 100644 mddocs/docs/ru/connection/db_connection/kafka/index.md create mode 100644 mddocs/docs/ru/connection/db_connection/kafka/kerberos_auth.md create mode 100644 mddocs/docs/ru/connection/db_connection/kafka/plaintext_protocol.md create mode 100644 mddocs/docs/ru/connection/db_connection/kafka/prerequisites.md create mode 100644 mddocs/docs/ru/connection/db_connection/kafka/protocol.md create mode 100644 mddocs/docs/ru/connection/db_connection/kafka/read.md create mode 100644 mddocs/docs/ru/connection/db_connection/kafka/scram_auth.md create mode 100644 mddocs/docs/ru/connection/db_connection/kafka/slots.md create mode 100644 mddocs/docs/ru/connection/db_connection/kafka/ssl_protocol.md create mode 100644 mddocs/docs/ru/connection/db_connection/kafka/troubleshooting.md create mode 100644 mddocs/docs/ru/connection/db_connection/kafka/write.md create mode 100644 mddocs/docs/ru/connection/db_connection/mongodb/connection.md create mode 100644 mddocs/docs/ru/connection/db_connection/mongodb/index.md create mode 100644 mddocs/docs/ru/connection/db_connection/mongodb/pipeline.md create mode 100644 mddocs/docs/ru/connection/db_connection/mongodb/prerequisites.md create mode 100644 mddocs/docs/ru/connection/db_connection/mongodb/read.md create mode 100644 mddocs/docs/ru/connection/db_connection/mongodb/types.md create mode 100644 mddocs/docs/ru/connection/db_connection/mongodb/write.md create mode 100644 mddocs/docs/ru/connection/db_connection/mssql/connection.md create mode 100644 mddocs/docs/ru/connection/db_connection/mssql/execute.md create mode 100644 mddocs/docs/ru/connection/db_connection/mssql/index.md create mode 100644 mddocs/docs/ru/connection/db_connection/mssql/prerequisites.md create mode 100644 mddocs/docs/ru/connection/db_connection/mssql/read.md create mode 100644 mddocs/docs/ru/connection/db_connection/mssql/sql.md create mode 100644 mddocs/docs/ru/connection/db_connection/mssql/types.md create mode 100644 mddocs/docs/ru/connection/db_connection/mssql/write.md create mode 100644 mddocs/docs/ru/connection/db_connection/mysql/connection.md create mode 100644 mddocs/docs/ru/connection/db_connection/mysql/execute.md create mode 100644 mddocs/docs/ru/connection/db_connection/mysql/index.md create mode 100644 mddocs/docs/ru/connection/db_connection/mysql/prerequisites.md create mode 100644 mddocs/docs/ru/connection/db_connection/mysql/read.md create mode 100644 mddocs/docs/ru/connection/db_connection/mysql/sql.md create mode 100644 mddocs/docs/ru/connection/db_connection/mysql/types.md create mode 100644 mddocs/docs/ru/connection/db_connection/mysql/write.md create mode 100644 mddocs/docs/ru/connection/db_connection/oracle/connection.md create mode 100644 mddocs/docs/ru/connection/db_connection/oracle/execute.md create mode 100644 mddocs/docs/ru/connection/db_connection/oracle/index.md create mode 100644 mddocs/docs/ru/connection/db_connection/oracle/prerequisites.md create mode 100644 mddocs/docs/ru/connection/db_connection/oracle/read.md create mode 100644 mddocs/docs/ru/connection/db_connection/oracle/sql.md create mode 100644 mddocs/docs/ru/connection/db_connection/oracle/types.md create mode 100644 mddocs/docs/ru/connection/db_connection/oracle/write.md create mode 100644 mddocs/docs/ru/connection/db_connection/postgres/connection.md create mode 100644 mddocs/docs/ru/connection/db_connection/postgres/execute.md create mode 100644 mddocs/docs/ru/connection/db_connection/postgres/index.md create mode 100644 mddocs/docs/ru/connection/db_connection/postgres/prerequisites.md create mode 100644 mddocs/docs/ru/connection/db_connection/postgres/read.md create mode 100644 mddocs/docs/ru/connection/db_connection/postgres/sql.md create mode 100644 mddocs/docs/ru/connection/db_connection/postgres/types.md create mode 100644 mddocs/docs/ru/connection/db_connection/postgres/write.md create mode 100644 mddocs/docs/ru/connection/db_connection/teradata/connection.md create mode 100644 mddocs/docs/ru/connection/db_connection/teradata/execute.md create mode 100644 mddocs/docs/ru/connection/db_connection/teradata/index.md create mode 100644 mddocs/docs/ru/connection/db_connection/teradata/prerequisites.md create mode 100644 mddocs/docs/ru/connection/db_connection/teradata/read.md create mode 100644 mddocs/docs/ru/connection/db_connection/teradata/sql.md create mode 100644 mddocs/docs/ru/connection/db_connection/teradata/write.md create mode 100644 mddocs/docs/ru/connection/file_connection/ftp.md create mode 100644 mddocs/docs/ru/connection/file_connection/ftps.md create mode 100644 mddocs/docs/ru/connection/file_connection/hdfs/connection.md create mode 100644 mddocs/docs/ru/connection/file_connection/hdfs/index.md create mode 100644 mddocs/docs/ru/connection/file_connection/hdfs/slots.md create mode 100644 mddocs/docs/ru/connection/file_connection/index.md create mode 100644 mddocs/docs/ru/connection/file_connection/s3.md create mode 100644 mddocs/docs/ru/connection/file_connection/samba.md create mode 100644 mddocs/docs/ru/connection/file_connection/sftp.md create mode 100644 mddocs/docs/ru/connection/file_connection/webdav.md create mode 100644 mddocs/docs/ru/connection/file_df_connection/base.md create mode 100644 mddocs/docs/ru/connection/file_df_connection/index.md create mode 100644 mddocs/docs/ru/connection/file_df_connection/spark_hdfs/connection.md create mode 100644 mddocs/docs/ru/connection/file_df_connection/spark_hdfs/index.md create mode 100644 mddocs/docs/ru/connection/file_df_connection/spark_hdfs/prerequisites.md create mode 100644 mddocs/docs/ru/connection/file_df_connection/spark_hdfs/slots.md create mode 100644 mddocs/docs/ru/connection/file_df_connection/spark_local_fs.md create mode 100644 mddocs/docs/ru/connection/file_df_connection/spark_s3/connection.md create mode 100644 mddocs/docs/ru/connection/file_df_connection/spark_s3/index.md create mode 100644 mddocs/docs/ru/connection/file_df_connection/spark_s3/prerequisites.md create mode 100644 mddocs/docs/ru/connection/file_df_connection/spark_s3/troubleshooting.md create mode 100644 mddocs/docs/ru/connection/index.md rename mddocs/{ru/CONTRIBUTING.md => docs/ru/contributing.md} (61%) create mode 100644 mddocs/docs/ru/db_/index.md create mode 100644 mddocs/docs/ru/db_/reader.md create mode 100644 mddocs/docs/ru/db_/writer.md create mode 100644 mddocs/docs/ru/index.md rename mddocs/{ => docs}/ru/logging.md (87%) create mode 100644 mddocs/docs/ru/plugins.md create mode 100644 mddocs/docs/ru/quickstart.md create mode 100644 mddocs/docs/ru/security.md create mode 100644 mddocs/docs/ru/snippet_0.md create mode 100644 mddocs/docs/ru/test.md delete mode 100644 mddocs/docs/security.md create mode 100644 mddocs/generated/en/404.html create mode 100644 mddocs/generated/en/assets/_mkdocstrings.css create mode 100644 mddocs/generated/en/assets/images/favicon.png rename mddocs/{docs/_static => generated/en/assets/images}/icon.svg (100%) rename mddocs/{docs/_static => generated/en/assets/images}/logo.svg (100%) rename mddocs/{docs/_static => generated/en/assets/images}/logo_wide.svg (100%) create mode 100644 mddocs/generated/en/assets/javascripts/bundle.13a4f30d.min.js create mode 100644 mddocs/generated/en/assets/javascripts/bundle.13a4f30d.min.js.map create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.ar.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.da.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.de.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.du.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.el.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.es.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.fi.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.fr.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.he.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.hi.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.hu.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.hy.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.it.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.ja.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.jp.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.kn.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.ko.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.multi.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.nl.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.no.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.pt.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.ro.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.ru.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.sa.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.stemmer.support.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.sv.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.ta.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.te.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.th.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.tr.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.vi.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/min/lunr.zh.min.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/tinyseg.js create mode 100644 mddocs/generated/en/assets/javascripts/lunr/wordcut.js create mode 100644 mddocs/generated/en/assets/javascripts/workers/search.d50fe291.min.js create mode 100644 mddocs/generated/en/assets/javascripts/workers/search.d50fe291.min.js.map create mode 100644 mddocs/generated/en/assets/mkdocs_puml/interaction.css create mode 100644 mddocs/generated/en/assets/mkdocs_puml/interaction.js create mode 100644 mddocs/generated/en/assets/mkdocs_puml/puml.css create mode 100644 mddocs/generated/en/assets/mkdocs_puml/puml.js rename mddocs/{docs/_build/markdown/_static => generated/en/assets/stylesheets}/autodoc_pydantic.css (100%) create mode 100644 mddocs/generated/en/assets/stylesheets/main.342714a4.min.css create mode 100644 mddocs/generated/en/assets/stylesheets/main.342714a4.min.css.map create mode 100644 mddocs/generated/en/assets/stylesheets/palette.06af60db.min.css create mode 100644 mddocs/generated/en/assets/stylesheets/palette.06af60db.min.css.map create mode 100644 mddocs/generated/en/changelog/0.10.0/index.html create mode 100644 mddocs/generated/en/changelog/0.10.1/index.html create mode 100644 mddocs/generated/en/changelog/0.10.2/index.html create mode 100644 mddocs/generated/en/changelog/0.11.0/index.html create mode 100644 mddocs/generated/en/changelog/0.11.1/index.html create mode 100644 mddocs/generated/en/changelog/0.11.2/index.html create mode 100644 mddocs/generated/en/changelog/0.12.0/index.html create mode 100644 mddocs/generated/en/changelog/0.12.1/index.html create mode 100644 mddocs/generated/en/changelog/0.12.2/index.html create mode 100644 mddocs/generated/en/changelog/0.12.3/index.html create mode 100644 mddocs/generated/en/changelog/0.12.4/index.html create mode 100644 mddocs/generated/en/changelog/0.12.5/index.html create mode 100644 mddocs/generated/en/changelog/0.13.0/index.html create mode 100644 mddocs/generated/en/changelog/0.13.1/index.html create mode 100644 mddocs/generated/en/changelog/0.13.3/index.html create mode 100644 mddocs/generated/en/changelog/0.13.4/index.html create mode 100644 mddocs/generated/en/changelog/0.7.0/index.html create mode 100644 mddocs/generated/en/changelog/0.7.1/index.html create mode 100644 mddocs/generated/en/changelog/0.7.2/index.html create mode 100644 mddocs/generated/en/changelog/0.8.0/index.html create mode 100644 mddocs/generated/en/changelog/0.8.1/index.html create mode 100644 mddocs/generated/en/changelog/0.9.0/index.html create mode 100644 mddocs/generated/en/changelog/0.9.1/index.html create mode 100644 mddocs/generated/en/changelog/0.9.2/index.html create mode 100644 mddocs/generated/en/changelog/0.9.3/index.html create mode 100644 mddocs/generated/en/changelog/0.9.4/index.html create mode 100644 mddocs/generated/en/changelog/0.9.5/index.html create mode 100644 mddocs/generated/en/changelog/DRAFT/index.html create mode 100644 mddocs/generated/en/changelog/NEXT_RELEASE/index.html create mode 100644 mddocs/generated/en/changelog/index.html create mode 100644 mddocs/generated/en/concepts/index.html create mode 100644 mddocs/generated/en/connection/db_connection/clickhouse/connection/index.html create mode 100644 mddocs/generated/en/connection/db_connection/clickhouse/execute/index.html create mode 100644 mddocs/generated/en/connection/db_connection/clickhouse/index.html create mode 100644 mddocs/generated/en/connection/db_connection/clickhouse/prerequisites/index.html create mode 100644 mddocs/generated/en/connection/db_connection/clickhouse/read/index.html create mode 100644 mddocs/generated/en/connection/db_connection/clickhouse/sql/index.html create mode 100644 mddocs/generated/en/connection/db_connection/clickhouse/types/index.html create mode 100644 mddocs/generated/en/connection/db_connection/clickhouse/write/index.html create mode 100644 mddocs/generated/en/connection/db_connection/greenplum/connection/index.html create mode 100644 mddocs/generated/en/connection/db_connection/greenplum/execute/index.html create mode 100644 mddocs/generated/en/connection/db_connection/greenplum/index.html create mode 100644 mddocs/generated/en/connection/db_connection/greenplum/prerequisites/index.html create mode 100644 mddocs/generated/en/connection/db_connection/greenplum/read/index.html create mode 100644 mddocs/generated/en/connection/db_connection/greenplum/types/index.html create mode 100644 mddocs/generated/en/connection/db_connection/greenplum/write/index.html create mode 100644 mddocs/generated/en/connection/db_connection/hive/connection/index.html create mode 100644 mddocs/generated/en/connection/db_connection/hive/execute/index.html create mode 100644 mddocs/generated/en/connection/db_connection/hive/index.html create mode 100644 mddocs/generated/en/connection/db_connection/hive/prerequisites/index.html create mode 100644 mddocs/generated/en/connection/db_connection/hive/read/index.html create mode 100644 mddocs/generated/en/connection/db_connection/hive/slots/index.html create mode 100644 mddocs/generated/en/connection/db_connection/hive/sql/index.html create mode 100644 mddocs/generated/en/connection/db_connection/hive/write/index.html create mode 100644 mddocs/generated/en/connection/db_connection/index.html create mode 100644 mddocs/generated/en/connection/db_connection/kafka/auth/index.html create mode 100644 mddocs/generated/en/connection/db_connection/kafka/basic_auth/index.html create mode 100644 mddocs/generated/en/connection/db_connection/kafka/connection/index.html create mode 100644 mddocs/generated/en/connection/db_connection/kafka/index.html create mode 100644 mddocs/generated/en/connection/db_connection/kafka/kerberos_auth/index.html create mode 100644 mddocs/generated/en/connection/db_connection/kafka/plaintext_protocol/index.html create mode 100644 mddocs/generated/en/connection/db_connection/kafka/prerequisites/index.html create mode 100644 mddocs/generated/en/connection/db_connection/kafka/protocol/index.html create mode 100644 mddocs/generated/en/connection/db_connection/kafka/read/index.html create mode 100644 mddocs/generated/en/connection/db_connection/kafka/scram_auth/index.html create mode 100644 mddocs/generated/en/connection/db_connection/kafka/slots/index.html create mode 100644 mddocs/generated/en/connection/db_connection/kafka/ssl_protocol/index.html create mode 100644 mddocs/generated/en/connection/db_connection/kafka/troubleshooting/index.html create mode 100644 mddocs/generated/en/connection/db_connection/kafka/write/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mongodb/connection/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mongodb/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mongodb/pipeline/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mongodb/prerequisites/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mongodb/read/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mongodb/types/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mongodb/write/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mssql/connection/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mssql/execute/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mssql/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mssql/prerequisites/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mssql/read/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mssql/sql/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mssql/types/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mssql/write/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mysql/connection/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mysql/execute/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mysql/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mysql/prerequisites/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mysql/read/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mysql/sql/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mysql/types/index.html create mode 100644 mddocs/generated/en/connection/db_connection/mysql/write/index.html create mode 100644 mddocs/generated/en/connection/db_connection/oracle/connection/index.html create mode 100644 mddocs/generated/en/connection/db_connection/oracle/execute/index.html create mode 100644 mddocs/generated/en/connection/db_connection/oracle/index.html create mode 100644 mddocs/generated/en/connection/db_connection/oracle/prerequisites/index.html create mode 100644 mddocs/generated/en/connection/db_connection/oracle/read/index.html create mode 100644 mddocs/generated/en/connection/db_connection/oracle/sql/index.html create mode 100644 mddocs/generated/en/connection/db_connection/oracle/types/index.html create mode 100644 mddocs/generated/en/connection/db_connection/oracle/write/index.html create mode 100644 mddocs/generated/en/connection/db_connection/postgres/connection/index.html create mode 100644 mddocs/generated/en/connection/db_connection/postgres/execute/index.html create mode 100644 mddocs/generated/en/connection/db_connection/postgres/index.html create mode 100644 mddocs/generated/en/connection/db_connection/postgres/prerequisites/index.html create mode 100644 mddocs/generated/en/connection/db_connection/postgres/read/index.html create mode 100644 mddocs/generated/en/connection/db_connection/postgres/sql/index.html create mode 100644 mddocs/generated/en/connection/db_connection/postgres/types/index.html create mode 100644 mddocs/generated/en/connection/db_connection/postgres/write/index.html create mode 100644 mddocs/generated/en/connection/db_connection/teradata/connection/index.html create mode 100644 mddocs/generated/en/connection/db_connection/teradata/execute/index.html create mode 100644 mddocs/generated/en/connection/db_connection/teradata/index.html create mode 100644 mddocs/generated/en/connection/db_connection/teradata/prerequisites/index.html create mode 100644 mddocs/generated/en/connection/db_connection/teradata/read/index.html create mode 100644 mddocs/generated/en/connection/db_connection/teradata/sql/index.html create mode 100644 mddocs/generated/en/connection/db_connection/teradata/write/index.html create mode 100644 mddocs/generated/en/connection/file_connection/ftp/index.html create mode 100644 mddocs/generated/en/connection/file_connection/ftps/index.html create mode 100644 mddocs/generated/en/connection/file_connection/hdfs/connection/index.html create mode 100644 mddocs/generated/en/connection/file_connection/hdfs/index.html create mode 100644 mddocs/generated/en/connection/file_connection/hdfs/slots/index.html create mode 100644 mddocs/generated/en/connection/file_connection/index.html create mode 100644 mddocs/generated/en/connection/file_connection/s3/index.html create mode 100644 mddocs/generated/en/connection/file_connection/samba/index.html create mode 100644 mddocs/generated/en/connection/file_connection/sftp/index.html create mode 100644 mddocs/generated/en/connection/file_connection/webdav/index.html create mode 100644 mddocs/generated/en/connection/file_df_connection/base/index.html create mode 100644 mddocs/generated/en/connection/file_df_connection/index.html create mode 100644 mddocs/generated/en/connection/file_df_connection/spark_hdfs/connection/index.html create mode 100644 mddocs/generated/en/connection/file_df_connection/spark_hdfs/index.html create mode 100644 mddocs/generated/en/connection/file_df_connection/spark_hdfs/prerequisites/index.html create mode 100644 mddocs/generated/en/connection/file_df_connection/spark_hdfs/slots/index.html create mode 100644 mddocs/generated/en/connection/file_df_connection/spark_local_fs/index.html create mode 100644 mddocs/generated/en/connection/file_df_connection/spark_s3/connection/index.html create mode 100644 mddocs/generated/en/connection/file_df_connection/spark_s3/index.html create mode 100644 mddocs/generated/en/connection/file_df_connection/spark_s3/prerequisites/index.html create mode 100644 mddocs/generated/en/connection/file_df_connection/spark_s3/troubleshooting/index.html create mode 100644 mddocs/generated/en/connection/index.html create mode 100644 mddocs/generated/en/contributing/index.html create mode 100644 mddocs/generated/en/db_/index.html create mode 100644 mddocs/generated/en/db_/reader/index.html create mode 100644 mddocs/generated/en/db_/writer/index.html create mode 100644 mddocs/generated/en/file/file_downloader/file_downloader/index.html create mode 100644 mddocs/generated/en/file/file_downloader/index.html create mode 100644 mddocs/generated/en/file/file_downloader/options/index.html create mode 100644 mddocs/generated/en/file/file_downloader/result/index.html create mode 100644 mddocs/generated/en/file/file_filters/base/index.html create mode 100644 mddocs/generated/en/file/file_filters/exclude_dir/index.html create mode 100644 mddocs/generated/en/file/file_filters/file_filter/index.html create mode 100644 mddocs/generated/en/file/file_filters/file_mtime_filter/index.html create mode 100644 mddocs/generated/en/file/file_filters/file_size_filter/index.html create mode 100644 mddocs/generated/en/file/file_filters/glob/index.html create mode 100644 mddocs/generated/en/file/file_filters/index.html create mode 100644 mddocs/generated/en/file/file_filters/match_all_filters/index.html create mode 100644 mddocs/generated/en/file/file_filters/regexp/index.html create mode 100644 mddocs/generated/en/file/file_limits/base/index.html create mode 100644 mddocs/generated/en/file/file_limits/file_limit/index.html create mode 100644 mddocs/generated/en/file/file_limits/index.html create mode 100644 mddocs/generated/en/file/file_limits/limits_reached/index.html create mode 100644 mddocs/generated/en/file/file_limits/limits_stop_at/index.html create mode 100644 mddocs/generated/en/file/file_limits/max_files_count/index.html create mode 100644 mddocs/generated/en/file/file_limits/reset_limits/index.html create mode 100644 mddocs/generated/en/file/file_limits/total_files_size/index.html create mode 100644 mddocs/generated/en/file/file_mover/file_mover/index.html create mode 100644 mddocs/generated/en/file/file_mover/index.html create mode 100644 mddocs/generated/en/file/file_mover/options/index.html create mode 100644 mddocs/generated/en/file/file_mover/result/index.html create mode 100644 mddocs/generated/en/file/file_uploader/file_uploader/index.html create mode 100644 mddocs/generated/en/file/file_uploader/index.html create mode 100644 mddocs/generated/en/file/file_uploader/options/index.html create mode 100644 mddocs/generated/en/file/file_uploader/result/index.html create mode 100644 mddocs/generated/en/file/index.html create mode 100644 mddocs/generated/en/file_df/file_df_reader/file_df_reader/index.html create mode 100644 mddocs/generated/en/file_df/file_df_reader/index.html create mode 100644 mddocs/generated/en/file_df/file_df_reader/options/index.html create mode 100644 mddocs/generated/en/file_df/file_df_writer/file_df_writer/index.html create mode 100644 mddocs/generated/en/file_df/file_df_writer/index.html create mode 100644 mddocs/generated/en/file_df/file_df_writer/options/index.html create mode 100644 mddocs/generated/en/file_df/file_formats/avro/index.html create mode 100644 mddocs/generated/en/file_df/file_formats/base/index.html create mode 100644 mddocs/generated/en/file_df/file_formats/csv/index.html create mode 100644 mddocs/generated/en/file_df/file_formats/excel/index.html create mode 100644 mddocs/generated/en/file_df/file_formats/index.html create mode 100644 mddocs/generated/en/file_df/file_formats/json/index.html create mode 100644 mddocs/generated/en/file_df/file_formats/jsonline/index.html create mode 100644 mddocs/generated/en/file_df/file_formats/orc/index.html create mode 100644 mddocs/generated/en/file_df/file_formats/parquet/index.html create mode 100644 mddocs/generated/en/file_df/file_formats/xml/index.html create mode 100644 mddocs/generated/en/file_df/index.html create mode 100644 mddocs/generated/en/hooks/design/index.html create mode 100644 mddocs/generated/en/hooks/global_state/index.html create mode 100644 mddocs/generated/en/hooks/hook/index.html create mode 100644 mddocs/generated/en/hooks/index.html create mode 100644 mddocs/generated/en/hooks/slot/index.html create mode 100644 mddocs/generated/en/hooks/support_hooks/index.html create mode 100644 mddocs/generated/en/hwm_store/index.html create mode 100644 mddocs/generated/en/hwm_store/yaml_hwm_store/index.html create mode 100644 mddocs/generated/en/index.html create mode 100644 mddocs/generated/en/install/files/index.html create mode 100644 mddocs/generated/en/install/full/index.html create mode 100644 mddocs/generated/en/install/index.html create mode 100644 mddocs/generated/en/install/kerberos/index.html create mode 100644 mddocs/generated/en/install/spark/index.html create mode 100644 mddocs/generated/en/logging/index.html create mode 100644 mddocs/generated/en/objects.inv create mode 100644 mddocs/generated/en/plugins/index.html create mode 100644 mddocs/generated/en/quickstart/index.html create mode 100644 mddocs/generated/en/security/index.html create mode 100644 mddocs/generated/en/sitemap.xml create mode 100644 mddocs/generated/en/sitemap.xml.gz create mode 100644 mddocs/generated/en/snippet_0/index.html create mode 100644 mddocs/generated/en/strategy/incremental_batch_strategy/index.html create mode 100644 mddocs/generated/en/strategy/incremental_strategy/index.html create mode 100644 mddocs/generated/en/strategy/index.html create mode 100644 mddocs/generated/en/strategy/snapshot_batch_strategy/index.html create mode 100644 mddocs/generated/en/strategy/snapshot_strategy/index.html create mode 100644 mddocs/generated/en/troubleshooting/index.html create mode 100644 mddocs/generated/en/troubleshooting/spark/index.html create mode 100644 mddocs/generated/ru/404.html create mode 100644 mddocs/generated/ru/assets/_mkdocstrings.css create mode 100644 mddocs/generated/ru/assets/images/favicon.png create mode 100644 mddocs/generated/ru/assets/images/icon.svg create mode 100644 mddocs/generated/ru/assets/images/logo.svg create mode 100644 mddocs/generated/ru/assets/images/logo_wide.svg create mode 100644 mddocs/generated/ru/assets/javascripts/bundle.13a4f30d.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/bundle.13a4f30d.min.js.map create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.ar.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.da.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.de.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.du.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.el.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.es.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.fi.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.fr.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.he.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.hi.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.hu.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.hy.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.it.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.ja.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.jp.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.kn.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.ko.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.multi.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.nl.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.no.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.pt.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.ro.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.ru.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.sa.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.stemmer.support.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.sv.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.ta.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.te.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.th.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.tr.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.vi.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/min/lunr.zh.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/tinyseg.js create mode 100644 mddocs/generated/ru/assets/javascripts/lunr/wordcut.js create mode 100644 mddocs/generated/ru/assets/javascripts/workers/search.d50fe291.min.js create mode 100644 mddocs/generated/ru/assets/javascripts/workers/search.d50fe291.min.js.map create mode 100644 mddocs/generated/ru/assets/mkdocs_puml/interaction.css create mode 100644 mddocs/generated/ru/assets/mkdocs_puml/interaction.js create mode 100644 mddocs/generated/ru/assets/mkdocs_puml/puml.css create mode 100644 mddocs/generated/ru/assets/mkdocs_puml/puml.js create mode 100644 mddocs/generated/ru/assets/stylesheets/autodoc_pydantic.css create mode 100644 mddocs/generated/ru/assets/stylesheets/main.342714a4.min.css create mode 100644 mddocs/generated/ru/assets/stylesheets/main.342714a4.min.css.map create mode 100644 mddocs/generated/ru/assets/stylesheets/palette.06af60db.min.css create mode 100644 mddocs/generated/ru/assets/stylesheets/palette.06af60db.min.css.map create mode 100644 mddocs/generated/ru/changelog/0.10.0/index.html create mode 100644 mddocs/generated/ru/changelog/0.10.1/index.html create mode 100644 mddocs/generated/ru/changelog/0.10.2/index.html create mode 100644 mddocs/generated/ru/changelog/0.11.0/index.html create mode 100644 mddocs/generated/ru/changelog/0.11.1/index.html create mode 100644 mddocs/generated/ru/changelog/0.11.2/index.html create mode 100644 mddocs/generated/ru/changelog/0.12.0/index.html create mode 100644 mddocs/generated/ru/changelog/0.12.1/index.html create mode 100644 mddocs/generated/ru/changelog/0.12.2/index.html create mode 100644 mddocs/generated/ru/changelog/0.12.3/index.html create mode 100644 mddocs/generated/ru/changelog/0.12.4/index.html create mode 100644 mddocs/generated/ru/changelog/0.12.5/index.html create mode 100644 mddocs/generated/ru/changelog/0.13.0/index.html create mode 100644 mddocs/generated/ru/changelog/0.13.1/index.html create mode 100644 mddocs/generated/ru/changelog/0.13.3/index.html create mode 100644 mddocs/generated/ru/changelog/0.13.4/index.html create mode 100644 mddocs/generated/ru/changelog/0.7.0/index.html create mode 100644 mddocs/generated/ru/changelog/0.7.1/index.html create mode 100644 mddocs/generated/ru/changelog/0.7.2/index.html create mode 100644 mddocs/generated/ru/changelog/0.8.0/index.html create mode 100644 mddocs/generated/ru/changelog/0.8.1/index.html create mode 100644 mddocs/generated/ru/changelog/0.9.0/index.html create mode 100644 mddocs/generated/ru/changelog/0.9.1/index.html create mode 100644 mddocs/generated/ru/changelog/0.9.2/index.html create mode 100644 mddocs/generated/ru/changelog/0.9.3/index.html create mode 100644 mddocs/generated/ru/changelog/0.9.4/index.html create mode 100644 mddocs/generated/ru/changelog/0.9.5/index.html create mode 100644 mddocs/generated/ru/changelog/index.html create mode 100644 mddocs/generated/ru/concepts/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/clickhouse/connection/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/clickhouse/execute/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/clickhouse/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/clickhouse/prerequisites/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/clickhouse/read/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/clickhouse/sql/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/clickhouse/types/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/clickhouse/write/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/greenplum/connection/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/greenplum/execute/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/greenplum/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/greenplum/prerequisites/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/greenplum/read/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/greenplum/types/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/greenplum/write/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/hive/connection/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/hive/execute/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/hive/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/hive/prerequisites/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/hive/read/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/hive/slots/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/hive/sql/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/hive/write/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/kafka/auth/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/kafka/basic_auth/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/kafka/connection/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/kafka/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/kafka/kerberos_auth/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/kafka/plaintext_protocol/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/kafka/prerequisites/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/kafka/protocol/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/kafka/read/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/kafka/scram_auth/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/kafka/slots/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/kafka/ssl_protocol/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/kafka/troubleshooting/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/kafka/write/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mongodb/connection/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mongodb/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mongodb/pipeline/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mongodb/prerequisites/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mongodb/read/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mongodb/types/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mongodb/write/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mssql/connection/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mssql/execute/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mssql/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mssql/prerequisites/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mssql/read/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mssql/sql/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mssql/types/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mssql/write/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mysql/connection/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mysql/execute/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mysql/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mysql/prerequisites/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mysql/read/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mysql/sql/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mysql/types/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/mysql/write/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/oracle/connection/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/oracle/execute/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/oracle/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/oracle/prerequisites/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/oracle/read/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/oracle/sql/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/oracle/types/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/oracle/write/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/postgres/connection/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/postgres/execute/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/postgres/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/postgres/prerequisites/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/postgres/read/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/postgres/sql/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/postgres/types/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/postgres/write/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/teradata/connection/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/teradata/execute/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/teradata/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/teradata/prerequisites/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/teradata/read/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/teradata/sql/index.html create mode 100644 mddocs/generated/ru/connection/db_connection/teradata/write/index.html create mode 100644 mddocs/generated/ru/connection/file_connection/ftp/index.html create mode 100644 mddocs/generated/ru/connection/file_connection/ftps/index.html create mode 100644 mddocs/generated/ru/connection/file_connection/hdfs/connection/index.html create mode 100644 mddocs/generated/ru/connection/file_connection/hdfs/index.html create mode 100644 mddocs/generated/ru/connection/file_connection/hdfs/slots/index.html create mode 100644 mddocs/generated/ru/connection/file_connection/index.html create mode 100644 mddocs/generated/ru/connection/file_connection/s3/index.html create mode 100644 mddocs/generated/ru/connection/file_connection/samba/index.html create mode 100644 mddocs/generated/ru/connection/file_connection/sftp/index.html create mode 100644 mddocs/generated/ru/connection/file_connection/webdav/index.html create mode 100644 mddocs/generated/ru/connection/file_df_connection/base/index.html create mode 100644 mddocs/generated/ru/connection/file_df_connection/index.html create mode 100644 mddocs/generated/ru/connection/file_df_connection/spark_hdfs/connection/index.html create mode 100644 mddocs/generated/ru/connection/file_df_connection/spark_hdfs/index.html create mode 100644 mddocs/generated/ru/connection/file_df_connection/spark_hdfs/prerequisites/index.html create mode 100644 mddocs/generated/ru/connection/file_df_connection/spark_hdfs/slots/index.html create mode 100644 mddocs/generated/ru/connection/file_df_connection/spark_local_fs/index.html create mode 100644 mddocs/generated/ru/connection/file_df_connection/spark_s3/connection/index.html create mode 100644 mddocs/generated/ru/connection/file_df_connection/spark_s3/index.html create mode 100644 mddocs/generated/ru/connection/file_df_connection/spark_s3/prerequisites/index.html create mode 100644 mddocs/generated/ru/connection/file_df_connection/spark_s3/troubleshooting/index.html create mode 100644 mddocs/generated/ru/connection/index.html create mode 100644 mddocs/generated/ru/contributing/index.html create mode 100644 mddocs/generated/ru/db_/index.html create mode 100644 mddocs/generated/ru/db_/reader/index.html create mode 100644 mddocs/generated/ru/db_/writer/index.html create mode 100644 mddocs/generated/ru/index.html create mode 100644 mddocs/generated/ru/logging/index.html create mode 100644 mddocs/generated/ru/objects.inv create mode 100644 mddocs/generated/ru/plugins/index.html create mode 100644 mddocs/generated/ru/quickstart/index.html create mode 100644 mddocs/generated/ru/security/index.html create mode 100644 mddocs/generated/ru/sitemap.xml create mode 100644 mddocs/generated/ru/sitemap.xml.gz create mode 100644 mddocs/generated/ru/snippet_0/index.html create mode 100644 mddocs/generated/ru/test/index.html rename mddocs/{common_rst => includes}/CONTRIBUTING.md (100%) rename mddocs/{common_rst => includes}/README.md (100%) rename mddocs/{common_rst => includes}/SECURITY.md (100%) rename mddocs/{docs/_build/markdown => includes}/_sphinx_design_static/design-tabs.js (100%) rename mddocs/{docs/_build/markdown => includes}/_sphinx_design_static/sphinx-design.min.css (100%) rename mddocs/{docs => includes}/robots.txt (100%) delete mode 100644 mddocs/mkdocs.yml create mode 100644 mddocs/overrides/assets/images/icon.svg create mode 100644 mddocs/overrides/assets/images/logo.svg create mode 100644 mddocs/overrides/assets/images/logo_wide.svg create mode 100644 mddocs/overrides/assets/stylesheets/autodoc_pydantic.css delete mode 100644 mddocs/ru/concepts.md delete mode 100644 mddocs/ru/contributing.md delete mode 100644 mddocs/ru/index.md delete mode 100644 mddocs/ru/plugins.md delete mode 100644 mddocs/ru/quickstart.md delete mode 100644 mddocs/ru/security.md rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.10.0.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.10.1.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.10.2.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.11.0.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.11.1.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.11.2.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.12.0.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.12.1.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.12.2.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.12.3.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.12.4.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.12.5.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.13.0.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.13.1.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.13.3.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.13.4.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.7.0.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.7.1.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.7.2.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.8.0.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.8.1.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.9.0.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.9.1.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.9.2.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.9.3.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.9.4.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/0.9.5.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/DRAFT.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/NEXT_RELEASE.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/changelog/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/concepts.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/clickhouse/connection.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/clickhouse/execute.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/clickhouse/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/clickhouse/prerequisites.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/clickhouse/read.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/clickhouse/sql.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/clickhouse/types.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/clickhouse/write.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/greenplum/connection.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/greenplum/execute.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/greenplum/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/greenplum/prerequisites.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/greenplum/read.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/greenplum/types.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/greenplum/write.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/hive/connection.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/hive/execute.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/hive/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/hive/prerequisites.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/hive/read.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/hive/slots.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/hive/sql.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/hive/write.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/kafka/auth.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/kafka/basic_auth.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/kafka/connection.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/kafka/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/kafka/kerberos_auth.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/kafka/plaintext_protocol.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/kafka/prerequisites.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/kafka/protocol.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/kafka/read.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/kafka/scram_auth.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/kafka/slots.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/kafka/ssl_protocol.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/kafka/troubleshooting.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/kafka/write.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mongodb/connection.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mongodb/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mongodb/pipeline.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mongodb/prerequisites.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mongodb/read.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mongodb/types.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mongodb/write.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mssql/connection.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mssql/execute.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mssql/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mssql/prerequisites.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mssql/read.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mssql/sql.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mssql/types.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mssql/write.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mysql/connection.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mysql/execute.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mysql/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mysql/prerequisites.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mysql/read.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mysql/sql.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mysql/types.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/mysql/write.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/oracle/connection.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/oracle/execute.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/oracle/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/oracle/prerequisites.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/oracle/read.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/oracle/sql.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/oracle/types.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/oracle/write.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/postgres/connection.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/postgres/execute.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/postgres/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/postgres/prerequisites.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/postgres/read.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/postgres/sql.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/postgres/types.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/postgres/write.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/teradata/connection.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/teradata/execute.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/teradata/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/teradata/prerequisites.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/teradata/read.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/teradata/sql.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/db_connection/teradata/write.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/file_connection/ftp.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/file_connection/ftps.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/file_connection/hdfs/connection.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/file_connection/hdfs/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/file_connection/hdfs/slots.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/file_connection/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/file_connection/s3.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/file_connection/samba.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/file_connection/sftp.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/file_connection/webdav.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/file_df_connection/base.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/file_df_connection/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/file_df_connection/spark_hdfs/connection.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/file_df_connection/spark_hdfs/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/file_df_connection/spark_hdfs/prerequisites.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/file_df_connection/spark_hdfs/slots.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/file_df_connection/spark_local_fs.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/file_df_connection/spark_s3/connection.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/file_df_connection/spark_s3/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/file_df_connection/spark_s3/prerequisites.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/file_df_connection/spark_s3/troubleshooting.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/connection/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/contributing.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/db/db_reader.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/db/db_writer.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/db/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_downloader/file_downloader.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_downloader/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_downloader/options.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_downloader/result.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_filters/base.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_filters/exclude_dir.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_filters/file_filter.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_filters/file_mtime_filter.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_filters/file_size_filter.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_filters/glob.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_filters/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_filters/match_all_filters.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_filters/regexp.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_limits/base.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_limits/file_limit.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_limits/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_limits/limits_reached.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_limits/limits_stop_at.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_limits/max_files_count.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_limits/reset_limits.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_limits/total_files_size.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_mover/file_mover.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_mover/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_mover/options.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_mover/result.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_uploader/file_uploader.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_uploader/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_uploader/options.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/file_uploader/result.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file_df/file_df_reader/file_df_reader.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file_df/file_df_reader/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file_df/file_df_reader/options.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file_df/file_df_writer/file_df_writer.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file_df/file_df_writer/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file_df/file_df_writer/options.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file_df/file_formats/avro.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file_df/file_formats/base.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file_df/file_formats/csv.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file_df/file_formats/excel.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file_df/file_formats/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file_df/file_formats/json.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file_df/file_formats/jsonline.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file_df/file_formats/orc.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file_df/file_formats/parquet.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file_df/file_formats/xml.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/file_df/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/hooks/design.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/hooks/global_state.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/hooks/hook.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/hooks/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/hooks/slot.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/hooks/support_hooks.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/hwm_store/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/hwm_store/yaml_hwm_store.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/install/files.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/install/full.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/install/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/install/kerberos.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/install/spark.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/logging.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/plugins.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/quickstart.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/security.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/strategy/incremental_batch_strategy.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/strategy/incremental_strategy.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/strategy/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/strategy/snapshot_batch_strategy.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/strategy/snapshot_strategy.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/troubleshooting/index.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/doctrees/troubleshooting/spark.doctree (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/changelog.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/changelog/0.10.0.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/changelog/0.10.1.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/changelog/0.10.2.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/changelog/0.11.0.md (100%) rename mddocs/{docs => sphinx_build/markdown}/changelog/0.11.1.md (100%) rename mddocs/{docs => sphinx_build/markdown}/changelog/0.11.2.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/changelog/0.12.0.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/changelog/0.12.1.md (100%) rename mddocs/{docs => sphinx_build/markdown}/changelog/0.12.2.md (100%) rename mddocs/{docs => sphinx_build/markdown}/changelog/0.12.3.md (100%) rename mddocs/{docs => sphinx_build/markdown}/changelog/0.12.4.md (100%) rename mddocs/{docs => sphinx_build/markdown}/changelog/0.12.5.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/changelog/0.13.0.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/changelog/0.13.1.md (100%) rename mddocs/{docs => sphinx_build/markdown}/changelog/0.13.3.md (100%) rename mddocs/{docs => sphinx_build/markdown}/changelog/0.13.4.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/changelog/0.7.0.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/changelog/0.7.1.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/changelog/0.7.2.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/changelog/0.8.0.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/changelog/0.8.1.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/changelog/0.9.0.md (100%) rename mddocs/{docs => sphinx_build/markdown}/changelog/0.9.1.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/changelog/0.9.2.md (100%) rename mddocs/{docs => sphinx_build/markdown}/changelog/0.9.3.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/changelog/0.9.4.md (100%) rename mddocs/{docs => sphinx_build/markdown}/changelog/0.9.5.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/changelog/DRAFT.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/changelog/NEXT_RELEASE.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/changelog/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/concepts.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/clickhouse/connection.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/clickhouse/execute.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/clickhouse/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/clickhouse/prerequisites.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/clickhouse/read.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/clickhouse/sql.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/clickhouse/types.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/clickhouse/write.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/greenplum/connection.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/greenplum/execute.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/greenplum/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/greenplum/prerequisites.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/greenplum/read.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/greenplum/types.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/greenplum/write.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/hive/connection.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/hive/execute.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/hive/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/hive/prerequisites.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/hive/read.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/hive/slots.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/hive/sql.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/hive/write.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/kafka/auth.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/kafka/basic_auth.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/kafka/connection.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/kafka/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/kafka/kerberos_auth.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/kafka/plaintext_protocol.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/kafka/prerequisites.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/kafka/protocol.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/kafka/read.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/kafka/scram_auth.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/kafka/slots.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/kafka/ssl_protocol.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/kafka/troubleshooting.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/kafka/write.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mongodb/connection.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mongodb/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mongodb/pipeline.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mongodb/prerequisites.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mongodb/read.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mongodb/types.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mongodb/write.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mssql/connection.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mssql/execute.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mssql/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mssql/prerequisites.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mssql/read.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mssql/sql.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mssql/types.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mssql/write.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mysql/connection.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mysql/execute.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mysql/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mysql/prerequisites.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mysql/read.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mysql/sql.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mysql/types.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/mysql/write.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/oracle/connection.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/oracle/execute.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/oracle/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/oracle/prerequisites.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/oracle/read.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/oracle/sql.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/oracle/types.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/oracle/write.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/postgres/connection.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/postgres/execute.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/postgres/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/postgres/prerequisites.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/postgres/read.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/postgres/sql.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/postgres/types.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/postgres/write.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/teradata/connection.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/teradata/execute.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/teradata/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/teradata/prerequisites.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/teradata/read.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/teradata/sql.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/db_connection/teradata/write.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/file_connection/ftp.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/file_connection/ftps.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/file_connection/hdfs/connection.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/file_connection/hdfs/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/file_connection/hdfs/slots.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/file_connection/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/file_connection/s3.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/file_connection/samba.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/file_connection/sftp.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/file_connection/webdav.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/file_df_connection/base.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/file_df_connection/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/file_df_connection/spark_hdfs/connection.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/file_df_connection/spark_hdfs/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/file_df_connection/spark_hdfs/prerequisites.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/file_df_connection/spark_hdfs/slots.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/file_df_connection/spark_local_fs.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/file_df_connection/spark_s3/connection.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/file_df_connection/spark_s3/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/file_df_connection/spark_s3/prerequisites.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/file_df_connection/spark_s3/troubleshooting.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/connection/index.md (100%) create mode 100644 mddocs/sphinx_build/markdown/contributing.md rename mddocs/{docs/_build => sphinx_build}/markdown/db/db_reader.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/db/db_writer.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/db/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_downloader/file_downloader.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_downloader/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_downloader/options.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_downloader/result.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_filters/base.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_filters/exclude_dir.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_filters/file_filter.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_filters/file_mtime_filter.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_filters/file_size_filter.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_filters/glob.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_filters/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_filters/match_all_filters.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_filters/regexp.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_limits/base.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_limits/file_limit.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_limits/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_limits/limits_reached.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_limits/limits_stop_at.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_limits/max_files_count.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_limits/reset_limits.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_limits/total_files_size.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_mover/file_mover.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_mover/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_mover/options.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_mover/result.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_uploader/file_uploader.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_uploader/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_uploader/options.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/file_uploader/result.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file_df/file_df_reader/file_df_reader.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file_df/file_df_reader/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file_df/file_df_reader/options.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file_df/file_df_writer/file_df_writer.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file_df/file_df_writer/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file_df/file_df_writer/options.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file_df/file_formats/avro.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file_df/file_formats/base.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file_df/file_formats/csv.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file_df/file_formats/excel.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file_df/file_formats/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file_df/file_formats/json.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file_df/file_formats/jsonline.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file_df/file_formats/orc.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file_df/file_formats/parquet.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file_df/file_formats/xml.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/file_df/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/hooks/design.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/hooks/global_state.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/hooks/hook.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/hooks/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/hooks/slot.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/hooks/support_hooks.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/hwm_store/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/hwm_store/yaml_hwm_store.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/install/files.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/install/full.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/install/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/install/kerberos.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/install/spark.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/logging.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/plugins.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/quickstart.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/security.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/strategy/incremental_batch_strategy.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/strategy/incremental_strategy.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/strategy/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/strategy/snapshot_batch_strategy.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/strategy/snapshot_strategy.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/troubleshooting/index.md (100%) rename mddocs/{docs/_build => sphinx_build}/markdown/troubleshooting/spark.md (100%) diff --git a/mddocs/config/en/mkdocs.yml b/mddocs/config/en/mkdocs.yml new file mode 100644 index 000000000..33818c23c --- /dev/null +++ b/mddocs/config/en/mkdocs.yml @@ -0,0 +1,141 @@ +site_name: onETL Docs +docs_dir: '../../docs/en' # Where to find the English markdown files +site_dir: '../../generated/en' # Where to put the English HTML files + +theme: + name: material + custom_dir: '../../overrides/' # This is where the customization of the theme lives + logo: assets/images/logo.svg # The logo is shared by all languages + favicon: assets/images/icon.svg # The favicon is shared by all languages + language: en + features: + - navigation.indexes + - content.tabs.link + - content.code.copy + - content.code.select + palette: + - scheme: default + toggle: + icon: material/weather-night + name: Switch to dark mode + - scheme: slate + toggle: + icon: material/weather-sunny + name: Switch to light mode + locale: en + highlightjs: true + hljs_languages: + - yaml + +extra_css: + - assets/stylesheets/autodoc_pydantic.css # CSS is shared by all languages + +extra: # Language Selection + onetl_logo_wide: "[![onETL logo](../en/assets/images/logo_wide.svg)](https://github.com/MobileTeleSystems/onetl)" + repo_status_badge: "[![Repo status - Active](https://www.repostatus.org/badges/latest/active.svg)](https://github.com/MobileTeleSystems/onetl)" + pypi_release_bage: "[![PyPI - Latest Release](https://img.shields.io/pypi/v/onetl)](https://pypi.org/project/onetl/)" + pypi_license_bage: "[![PyPI - License](https://img.shields.io/pypi/l/onetl.svg)](https://github.com/MobileTeleSystems/onetl/blob/develop/LICENSE.txt)" + pypi_pyversion_bage: "[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/onetl.svg)](https://pypi.org/project/onetl/)" + pypi_downloads_bage: "[![PyPI - Downloads](https://img.shields.io/pypi/dm/onetl)](https://pypi.org/project/onetl/)" + docs_status_badge: "[![Documentation - ReadTheDocs](https://readthedocs.org/projects/onetl/badge/?version=stable)](https://onetl.readthedocs.io/)" + ci_status_badge: "[![Github Actions - latest CI build status](https://github.com/MobileTeleSystems/onetl/workflows/Tests/badge.svg)](https://github.com/MobileTeleSystems/onetl/actions)" + precommit_badge: "[![pre-commit.ci Status](https://results.pre-commit.ci/badge/github/MobileTeleSystems/onetl/develop.svg)](https://results.pre-commit.ci/latest/github/MobileTeleSystems/onetl/develop)" + test_cov_badge: "[![Test coverage - percent](https://codecov.io/gh/MobileTeleSystems/onetl/branch/develop/graph/badge.svg?token=RIO8URKNZJ)](https://codecov.io/gh/MobileTeleSystems/onetl)" + alternate: + + # Switch to English + - name: English + link: /en/ + lang: en + + # Switch to Russian + - name: Русский + link: /ru/ + lang: ru + +plugins: + - autorefs + - mkdocstrings: + default_handler: python + handlers: + python: + options: + show_source: false + show_root_heading: false + show_root_toc_entry: false + - macros + - plantuml: + puml_url: https://www.plantuml.com/plantuml/ + puml_keyword: plantuml + # - i18n: + # docs_structure: folder + # languages: + # - locale: en + # default: true + # name: English + # build: true + # - locale: ru + # name: Русский + # build: false + +markdown_extensions: + - attr_list + - md_in_html + - admonition + - pymdownx.details + - pymdownx.critic + - pymdownx.snippets: + base_path: ["."] + check_paths: true + - toc: + permalink: true + - pymdownx.tabbed: + alternate_style: true + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + +nav: + - "Concepts": concepts + - "Quickstart": quickstart + - "Logging": logging + - "Security": security + - "Contributing Guide": contributing + - changelog: + - changelog/index.md + - "0.13.4": changelog/0.13.4 + - "0.13.3": changelog/0.13.3 + - "0.13.1": changelog/0.13.1 + - "0.13.0": changelog/0.13.0 + - "0.12.5": changelog/0.12.5 + - "0.12.4": changelog/0.12.4 + - "0.12.3": changelog/0.12.3 + - "0.12.2": changelog/0.12.2 + - "0.12.1": changelog/0.12.1 + - "0.12.0": changelog/0.12.0 + - "0.11.2": changelog/0.11.2 + - "0.11.1": changelog/0.11.1 + - "0.11.0": changelog/0.11.0 + - "0.10.2": changelog/0.10.2 + - "0.10.1": changelog/0.10.1 + - "0.10.0": changelog/0.10.0 + - "0.9.5": changelog/0.9.5 + - "0.9.4": changelog/0.9.4 + - "0.9.3": changelog/0.9.3 + - "0.9.2": changelog/0.9.2 + - "0.9.1": changelog/0.9.1 + - "0.9.0": changelog/0.9.0 + - "0.8.1": changelog/0.8.1 + - "0.8.0": changelog/0.8.0 + - "0.7.2": changelog/0.7.2 + - "0.7.1": changelog/0.7.1 + - "0.7.0": changelog/0.7.0 + - "DB": + - db_/index.md + - "DBReader": db_/reader + - "DBWriter": db_/writer \ No newline at end of file diff --git a/mddocs/config/ru/mkdocs.yml b/mddocs/config/ru/mkdocs.yml new file mode 100644 index 000000000..96dd0b9ac --- /dev/null +++ b/mddocs/config/ru/mkdocs.yml @@ -0,0 +1,138 @@ +site_name: Документация onETL +docs_dir: '../../docs/ru' # Where to find the English markdown files +site_dir: '../../generated/ru' # Where to put the English HTML files + +theme: + name: material + custom_dir: '../../overrides/' # This is where the customization of the theme lives + logo: assets/images/logo.svg # The logo is shared by all languages + favicon: assets/images/icon.svg # The favicon is shared by all languages + language: ru + features: + - navigation.indexes + - content.tabs.link + - content.code.copy + - content.code.select + palette: + - scheme: default + toggle: + icon: material/weather-night + name: Switch to dark mode + - scheme: slate + toggle: + icon: material/weather-sunny + name: Switch to light mode + locale: ru + highlightjs: true + hljs_languages: + - yaml + +extra_css: + - assets/stylesheets/autodoc_pydantic.css # CSS is shared by all languages + +extra: + onetl_logo_wide: "[![onETL logo](../ru/assets/images/logo_wide.svg)](https://github.com/MobileTeleSystems/onetl)" + repo_status_badge: "[![Repo status - Active](https://www.repostatus.org/badges/latest/active.svg)](https://github.com/MobileTeleSystems/onetl)" + pypi_release_bage: "[![PyPI - Latest Release](https://img.shields.io/pypi/v/onetl)](https://pypi.org/project/onetl/)" + pypi_license_bage: "[![PyPI - License](https://img.shields.io/pypi/l/onetl.svg)](https://github.com/MobileTeleSystems/onetl/blob/develop/LICENSE.txt)" + pypi_pyversion_bage: "[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/onetl.svg)](https://pypi.org/project/onetl/)" + pypi_downloads_bage: "[![PyPI - Downloads](https://img.shields.io/pypi/dm/onetl)](https://pypi.org/project/onetl/)" + docs_status_badge: "[![Documentation - ReadTheDocs](https://readthedocs.org/projects/onetl/badge/?version=stable)](https://onetl.readthedocs.io/)" + ci_status_badge: "[![Github Actions - latest CI build status](https://github.com/MobileTeleSystems/onetl/workflows/Tests/badge.svg)](https://github.com/MobileTeleSystems/onetl/actions)" + precommit_badge: "[![pre-commit.ci Status](https://results.pre-commit.ci/badge/github/MobileTeleSystems/onetl/develop.svg)](https://results.pre-commit.ci/latest/github/MobileTeleSystems/onetl/develop)" + test_cov_badge: "[![Test coverage - percent](https://codecov.io/gh/MobileTeleSystems/onetl/branch/develop/graph/badge.svg?token=RIO8URKNZJ)](https://codecov.io/gh/MobileTeleSystems/onetl)" + alternate: # Language Selection + + # Switch to English + - name: English + link: /en/ + lang: en + + # Switch to Russian + - name: Русский + link: /ru/ + lang: ru + +plugins: + - autorefs + - mkdocstrings: + default_handler: python + handlers: + python: + options: + show_source: false + show_root_heading: false + show_root_toc_entry: false + - macros + - plantuml: + puml_url: https://www.plantuml.com/plantuml/ + puml_keyword: plantuml + # - i18n: + # docs_structure: folder + # languages: + # - locale: en + # name: English + # build: false + # - locale: ru + # default: true + # name: Русский + # build: true + +markdown_extensions: + - attr_list + - md_in_html + - admonition + - pymdownx.details + - pymdownx.critic + - pymdownx.snippets: + base_path: ["."] + check_paths: true + - toc: + permalink: true + - pymdownx.tabbed: + alternate_style: true + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + +nav: + - "Архитектура библиотеки": concepts + - "Быстрый старт": quickstart + - "Руководство по участию": contributing + - "Безопасность": security + - "Логирование": logging + - "Плагины": plugins + - changelog: + - changelog/index.md + - "0.13.4": changelog/0.13.4 + - "0.13.3": changelog/0.13.3 + - "0.13.1": changelog/0.13.1 + - "0.13.0": changelog/0.13.0 + - "0.12.5": changelog/0.12.5 + - "0.12.4": changelog/0.12.4 + - "0.12.3": changelog/0.12.3 + - "0.12.2": changelog/0.12.2 + - "0.12.1": changelog/0.12.1 + - "0.12.0": changelog/0.12.0 + - "0.11.2": changelog/0.11.2 + - "0.11.1": changelog/0.11.1 + - "0.11.0": changelog/0.11.0 + - "0.10.2": changelog/0.10.2 + - "0.10.1": changelog/0.10.1 + - "0.10.0": changelog/0.10.0 + - "0.9.5": changelog/0.9.5 + - "0.9.4": changelog/0.9.4 + - "0.9.3": changelog/0.9.3 + - "0.9.2": changelog/0.9.2 + - "0.9.1": changelog/0.9.1 + - "0.9.0": changelog/0.9.0 + - "0.8.1": changelog/0.8.1 + - "0.8.0": changelog/0.8.0 + - "0.7.2": changelog/0.7.2 + - "0.7.1": changelog/0.7.1 + - "0.7.0": changelog/0.7.0 \ No newline at end of file diff --git a/mddocs/docs/Makefile b/mddocs/docs/Makefile deleted file mode 100644 index d4bb2cbb9..000000000 --- a/mddocs/docs/Makefile +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/mddocs/docs/concepts.md b/mddocs/docs/concepts.md deleted file mode 100644 index b8b0baf4a..000000000 --- a/mddocs/docs/concepts.md +++ /dev/null @@ -1,458 +0,0 @@ -# Concepts - -Here you can find detailed documentation about each one of the onETL concepts and how to use them. - -## Connection - -### Basics - -onETL is used to pull and push data into other systems, and so it has a first-class `Connection` concept for storing credentials that are used to communicate with external systems. - -A `Connection` is essentially a set of parameters, such as username, password, hostname. - -To create a connection to a specific storage type, you must use a class that matches the storage type. The class name is the same as the storage type name (`Oracle`, `MSSQL`, `SFTP`, etc): - -```python -from onetl.connection import SFTP - -sftp = SFTP( - host="sftp.test.com", - user="onetl", - password="onetl", -) -``` - -All connection types are inherited from the parent class `BaseConnection`. - -### Class diagram - -```{eval-rst} -.. plantuml:: - - @startuml - left to right direction - skinparam classFontSize 20 - skinparam class { - BackgroundColor<> LightGreen - BackgroundColor<> Khaki - BackgroundColor<> LightBlue - StereotypeFontColor<> Transparent - StereotypeFontColor<> Transparent - StereotypeFontColor<> Transparent - } - - class BaseConnection { - } - - class DBConnection <>{ - } - DBConnection --|> BaseConnection - - class Hive <>{ - } - Hive --|> DBConnection - - class Greenplum <>{ - } - Greenplum --|> DBConnection - - class MongoDB <>{ - } - MongoDB --|> DBConnection - - class Kafka <>{ - } - Kafka --|> DBConnection - - class JDBCConnection <>{ - } - JDBCConnection --|> DBConnection - - class Clickhouse <>{ - } - Clickhouse --|> JDBCConnection - - class MSSQL <>{ - } - MSSQL --|> JDBCConnection - - class MySQL <>{ - } - MySQL --|> JDBCConnection - - class Postgres <>{ - } - Postgres --|> JDBCConnection - - class Oracle <>{ - } - Oracle --|> JDBCConnection - - class Teradata <>{ - } - Teradata --|> JDBCConnection - - class FileConnection <>{ - } - FileConnection --|> BaseConnection - - class FTP <>{ - } - FTP --|> FileConnection - - class FTPS <>{ - } - FTPS --|> FileConnection - - class HDFS <>{ - } - HDFS --|> FileConnection - - class WebDAV <>{ - } - WebDAV --|> FileConnection - - class Samba <>{ - } - Samba --|> FileConnection - - class SFTP <>{ - } - SFTP --|> FileConnection - - class S3 <>{ - } - S3 --|> FileConnection - - class FileDFConnection <>{ - } - FileDFConnection --|> BaseConnection - - class SparkHDFS <>{ - } - SparkHDFS --|> FileDFConnection - - class SparkLocalFS <>{ - } - SparkLocalFS --|> FileDFConnection - - class SparkS3 <>{ - } - SparkS3 --|> FileDFConnection - - @enduml -``` - -### DBConnection - -Classes inherited from `DBConnection` could be used for accessing databases. - -A `DBConnection` could be instantiated as follows: - -```python -from onetl.connection import MSSQL - -mssql = MSSQL( - host="mssqldb.demo.com", - user="onetl", - password="onetl", - database="Telecom", - spark=spark, -) -``` - -where **spark** is the current SparkSession. -`onETL` uses `Spark` and specific Java connectors under the hood to work with databases. - -For a description of other parameters, see the documentation for the {ref}`available DBConnections `. - -### FileConnection - -Classes inherited from `FileConnection` could be used to access files stored on the different file systems/file servers - -A `FileConnection` could be instantiated as follows: - -```python -from onetl.connection import SFTP - -sftp = SFTP( - host="sftp.test.com", - user="onetl", - password="onetl", -) -``` - -For a description of other parameters, see the documentation for the {ref}`available FileConnections `. - -### FileDFConnection - -Classes inherited from `FileDFConnection` could be used for accessing files as Spark DataFrames. - -A `FileDFConnection` could be instantiated as follows: - -```python -from onetl.connection import SparkHDFS - -spark_hdfs = SparkHDFS( - host="namenode1.domain.com", - cluster="mycluster", - spark=spark, -) -``` - -where **spark** is the current SparkSession. -`onETL` uses `Spark` and specific Java connectors under the hood to work with DataFrames. - -For a description of other parameters, see the documentation for the {ref}`available FileDFConnections `. - -### Checking connection availability - -Once you have created a connection, you can check the database/filesystem availability using the method `check()`: - -```python -mssql.check() -sftp.check() -spark_hdfs.check() -``` - -It will raise an exception if database/filesystem cannot be accessed. - -This method returns connection itself, so you can create connection and immediately check its availability: - -```Python -mssql = MSSQL( - host="mssqldb.demo.com", - user="onetl", - password="onetl", - database="Telecom", - spark=spark, -).check() # <-- -``` - -## Extract/Load data - -### Basics - -As we said above, onETL is used to extract data from and load data into remote systems. - -onETL provides several classes for this: - -> - {ref}`DBReader ` -> - {ref}`DBWriter ` -> - {ref}`FileDFReader ` -> - {ref}`FileDFWriter ` -> - {ref}`FileDownloader ` -> - {ref}`FileUploader ` -> - {ref}`FileMover ` - -All of these classes have a method `run()` that starts extracting/loading the data: - -```python -from onetl.db import DBReader, DBWriter - -reader = DBReader( - connection=mssql, - source="dbo.demo_table", - columns=["column_1", "column_2"], -) - -# Read data as Spark DataFrame -df = reader.run() - -db_writer = DBWriter( - connection=hive, - target="dl_sb.demo_table", -) - -# Save Spark DataFrame to Hive table -writer.run(df) -``` - -### Extract data - -To extract data you can use classes: - -| | Use case | Connection | `run()` gets | `run()` returns | -| ------------------------------------ | ----------------------------------------- | ------------------------------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -| {ref}`DBReader ` | Reading data from a database | Any {ref}`DBConnection ` | - | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | -| {ref}`FileDFReader ` | Read data from a file or set of files | Any {ref}`FileDFConnection ` | No input, or List[File path on FileSystem] | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | -| {ref}`FileDownloader ` | Download files from remote FS to local FS | Any {ref}`FileConnection ` | No input, or List[File path on remote FileSystem] | {ref}`DownloadResult ` | - -### Load data - -To load data you can use classes: - -| | Use case | Connection | `run()` gets | `run()` returns | -| ----------------------------------- | -------------------------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -| {ref}`DBWriter ` | Writing data from a DataFrame to a database | Any {ref}`DBConnection ` | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | None | -| {ref}`FileDFWriter ` | Writing data from a DataFrame to a folder | Any {ref}`FileDFConnection ` | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/api/pyspark.sql.DataFrame.html#pyspark.sql.DataFrame) | None | -| {ref}`FileUploader ` | Uploading files from a local FS to remote FS | Any {ref}`FileConnection ` | List[File path on local FileSystem] | {ref}`UploadResult ` | - -### Manipulate data - -To manipulate data you can use classes: - -| | Use case | Connection | `run()` gets | `run()` returns | -| ----------------------------- | ------------------------------------------- | -------------------------------------------- | ------------------------------------ | ------------------------------------- | -| {ref}`FileMover ` | Move files between directories in remote FS | Any {ref}`FileConnection ` | List[File path on remote FileSystem] | {ref}`MoveResult ` | - -### Options - -Extract and load classes have a `options` parameter, which has a special meaning: - -> - all other parameters - *WHAT* we extract / *WHERE* we load to -> - `options` parameter - *HOW* we extract/load data - -```python -db_reader = DBReader( - # WHAT do we read: - connection=mssql, - source="dbo.demo_table", # some table from MSSQL - columns=["column_1", "column_2"], # but only specific set of columns - where="column_2 > 1000", # only rows matching the clause - # HOW do we read: - options=MSSQL.ReadOptions( - numPartitions=10, # read in 10 parallel jobs - partitionColumn="id", # balance data read by assigning each job a part of data using `hash(id) mod N` expression - partitioningMode="hash", - fetchsize=1000, # each job will fetch block of 1000 rows each on every read attempt - ), -) - -db_writer = DBWriter( - # WHERE do we write to - to some table in Hive - connection=hive, - target="dl_sb.demo_table", - # HOW do we write - overwrite all the data in the existing table - options=Hive.WriteOptions(if_exists="replace_entire_table"), -) - -file_downloader = FileDownloader( - # WHAT do we download - files from some dir in SFTP - connection=sftp, - source_path="/source", - filters=[Glob("*.csv")], # only CSV files - limits=[MaxFilesCount(1000)], # 1000 files max - # WHERE do we download to - a specific dir on local FS - local_path="/some", - # HOW do we download: - options=FileDownloader.Options( - delete_source=True, # after downloading each file remove it from source_path - if_exists="replace_file", # replace existing files in the local_path - ), -) - -file_uploader = FileUploader( - # WHAT do we upload - files from some local dir - local_path="/source", - # WHERE do we upload to- specific remote dir in HDFS - connection=hdfs, - target_path="/some", - # HOW do we upload: - options=FileUploader.Options( - delete_local=True, # after uploading each file remove it from local_path - if_exists="replace_file", # replace existing files in the target_path - ), -) - -file_mover = FileMover( - # WHAT do we move - files in some remote dir in HDFS - source_path="/source", - connection=hdfs, - # WHERE do we move files to - target_path="/some", # a specific remote dir within the same HDFS connection - # HOW do we load - replace existing files in the target_path - options=FileMover.Options(if_exists="replace_file"), -) - -file_df_reader = FileDFReader( - # WHAT do we read - *.csv files from some dir in S3 - connection=s3, - source_path="/source", - file_format=CSV(), - # HOW do we read - load files from /source/*.csv, not from /source/nested/*.csv - options=FileDFReader.Options(recursive=False), -) - -file_df_writer = FileDFWriter( - # WHERE do we write to - as .csv files in some dir in S3 - connection=s3, - target_path="/target", - file_format=CSV(), - # HOW do we write - replace all existing files in /target, if exists - options=FileDFWriter.Options(if_exists="replace_entire_directory"), -) -``` - -More information about `options` could be found on {ref}`DB connection `. and -{ref}`file-downloader` / {ref}`file-uploader` / {ref}`file-mover` / {ref}`file-df-reader` / {ref}`file-df-writer` documentation - -### Read Strategies - -onETL have several builtin strategies for reading data: - -1. [Snapshot strategy](strategy/snapshot_strategy.html) (default strategy) -2. [Incremental strategy](strategy/incremental_strategy.html) -3. [Snapshot batch strategy](strategy/snapshot_batch_strategy.html) -4. [Incremental batch strategy](strategy/incremental_batch_strategy.html) - -For example, an incremental strategy allows you to get only new data from the table: - -```python -from onetl.strategy import IncrementalStrategy - -reader = DBReader( - connection=mssql, - source="dbo.demo_table", - hwm_column="id", # detect new data based on value of "id" column -) - -# first run -with IncrementalStrategy(): - df = reader.run() - -sleep(3600) - -# second run -with IncrementalStrategy(): - # only rows, that appeared in the source since previous run - df = reader.run() -``` - -or get only files which were not downloaded before: - -```python -from onetl.strategy import IncrementalStrategy - -file_downloader = FileDownloader( - connection=sftp, - source_path="/remote", - local_path="/local", - hwm_type="file_list", # save all downloaded files to a list, and exclude files already present in this list -) - -# first run -with IncrementalStrategy(): - files = file_downloader.run() - -sleep(3600) - -# second run -with IncrementalStrategy(): - # only files, that appeared in the source since previous run - files = file_downloader.run() -``` - -Most of strategies are based on {ref}`hwm`, Please check each strategy documentation for more details - -### Why just not use Connection class for extract/load? - -Connections are very simple, they have only a set of some basic operations, -like `mkdir`, `remove_file`, `get_table_schema`, and so on. - -High-level operations, like -: - {ref}`strategy` support - - Handling metadata push/pull - - Handling different options, like `if_exists="replace_file"` in case of file download/upload - -is moved to a separate class which calls the connection object methods to perform some complex logic. diff --git a/mddocs/docs/conf.py b/mddocs/docs/conf.py deleted file mode 100644 index 018e6e870..000000000 --- a/mddocs/docs/conf.py +++ /dev/null @@ -1,169 +0,0 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. - - -import os -import subprocess -import sys -from pathlib import Path - -from packaging.version import Version - -PROJECT_ROOT_DIR = Path(__file__).parent.parent.resolve() - -sys.path.insert(0, os.fspath(PROJECT_ROOT_DIR)) - -# -- Project information ----------------------------------------------------- - -project = "onETL" -copyright = "2021-2025 MTS PJSC" -author = "MWS Data Bridge" - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. - -ver = Version(subprocess.check_output("python ../setup.py --version", shell=True, text=True).strip()) -version = ver.base_version -# The full version, including alpha/beta/rc tags. -release = ver.public - -# -- General configuration --------------------------------------------------- - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. -extensions = [ - "numpydoc", - "sphinx_design", - "sphinx_substitution_extensions", - "sphinx_tabs.tabs", - "sphinx_toolbox.more_autodoc.autoprotocol", - "sphinx_toolbox.github", - "sphinx_copybutton", - "sphinx.ext.autodoc", - "sphinx.ext.autosummary", - "sphinxcontrib.towncrier", # provides `towncrier-draft-entries` directive - "sphinxcontrib.plantuml", - "sphinx.ext.extlinks", - "sphinx_favicon", - "sphinxcontrib.autodoc_pydantic", - "sphinx_last_updated_by_git", - "myst_parser", -] - - -myst_enable_extensions = [ - "amsmath", - "attrs_inline", - "colon_fence", - "deflist", - "dollarmath", - "fieldlist", - "html_admonition", - "html_image", - "linkify", - "replacements", - "smartquotes", - "strikethrough", - "substitution", - "tasklist", -] - - -numpydoc_show_class_members = False -autodoc_pydantic_model_show_config = False -autodoc_pydantic_model_show_config_summary = False -autodoc_pydantic_model_show_config_member = False -autodoc_pydantic_model_show_json = False -autodoc_pydantic_model_show_validator_summary = False -autodoc_pydantic_model_show_validator_members = False -autodoc_pydantic_field_list_validators = False -sphinx_tabs_disable_tab_closing = True - -# prevent >>>, ... and doctest outputs from copying -copybutton_prompt_text = r">>> |\.\.\. |\$ |In \[\d*\]: | {2,5}\.\.\.: | {5,8}: " -copybutton_prompt_is_regexp = True -copybutton_copy_empty_lines = False -copybutton_only_copy_prompt_lines = True - -towncrier_draft_autoversion_mode = "draft" -towncrier_draft_include_empty = False -towncrier_draft_working_directory = PROJECT_ROOT_DIR - -github_username = "MobileTeleSystems" -github_repository = "onetl" - -rst_prolog = f""" -.. |support_hooks| image:: https://img.shields.io/badge/%20-support%20hooks-blue - :target: https://onetl.readthedocs.io/en/{ver}/hooks/index.html -""" - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] - - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. - -html_theme = "furo" - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -html_theme_options = {} - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] -html_extra_path = ["robots.txt"] -html_logo = "./_static/logo.svg" -favicons = [ - {"rel": "icon", "href": "icon.svg", "type": "image/svg+xml"}, -] - -# The master toctree document. -master_doc = "index" - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = "en" - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = "sphinx" - -# Create an alias for etl-entities lib in onetl documentation -extlinks = { - "etl-entities": ("https://etl-entities.readthedocs.io/en/stable/%s", None), -} - - -# If true, `todo` and `todoList` produce output, else they produce nothing. -todo_include_todos = False - -# -- Options for HTMLHelp output ------------------------------------------ - -# Output file base name for HTML help builder. -htmlhelp_basename = "onetl-doc" diff --git a/mddocs/docs/connection/file_df_connection/index.md b/mddocs/docs/connection/file_df_connection/index.md deleted file mode 100644 index 8f8d24bf0..000000000 --- a/mddocs/docs/connection/file_df_connection/index.md +++ /dev/null @@ -1,19 +0,0 @@ -(file-df-connections)= - -# File DataFrame Connections - -```{toctree} -:caption: File DataFrame Connections -:maxdepth: 2 - -Spark LocalFS -Spark HDFS -Spark S3 -``` - -```{toctree} -:caption: For developers -:maxdepth: 1 - -base -``` diff --git a/mddocs/docs/connection/index.md b/mddocs/docs/connection/index.md deleted file mode 100644 index 00cbfc41a..000000000 --- a/mddocs/docs/connection/index.md +++ /dev/null @@ -1,22 +0,0 @@ -(connection)= - -```{toctree} -:caption: DB Connection -:maxdepth: 2 - -db_connection/index -``` - -```{toctree} -:caption: File Connection -:maxdepth: 2 - -file_connection/index -``` - -```{toctree} -:caption: File DataFrame Connection -:maxdepth: 2 - -file_df_connection/index -``` diff --git a/mddocs/docs/contributing.md b/mddocs/docs/contributing.md deleted file mode 100644 index cc9ee4419..000000000 --- a/mddocs/docs/contributing.md +++ /dev/null @@ -1,3 +0,0 @@ -```{eval-rst} -.. include:: ../CONTRIBUTING.rst -``` diff --git a/mddocs/docs/changelog.md b/mddocs/docs/en/changelog.md similarity index 100% rename from mddocs/docs/changelog.md rename to mddocs/docs/en/changelog.md diff --git a/mddocs/docs/changelog/0.10.0.md b/mddocs/docs/en/changelog/0.10.0.md similarity index 100% rename from mddocs/docs/changelog/0.10.0.md rename to mddocs/docs/en/changelog/0.10.0.md diff --git a/mddocs/docs/changelog/0.10.1.md b/mddocs/docs/en/changelog/0.10.1.md similarity index 100% rename from mddocs/docs/changelog/0.10.1.md rename to mddocs/docs/en/changelog/0.10.1.md diff --git a/mddocs/docs/changelog/0.10.2.md b/mddocs/docs/en/changelog/0.10.2.md similarity index 100% rename from mddocs/docs/changelog/0.10.2.md rename to mddocs/docs/en/changelog/0.10.2.md diff --git a/mddocs/docs/changelog/0.11.0.md b/mddocs/docs/en/changelog/0.11.0.md similarity index 100% rename from mddocs/docs/changelog/0.11.0.md rename to mddocs/docs/en/changelog/0.11.0.md diff --git a/mddocs/docs/_build/markdown/changelog/0.11.1.md b/mddocs/docs/en/changelog/0.11.1.md similarity index 100% rename from mddocs/docs/_build/markdown/changelog/0.11.1.md rename to mddocs/docs/en/changelog/0.11.1.md diff --git a/mddocs/docs/_build/markdown/changelog/0.11.2.md b/mddocs/docs/en/changelog/0.11.2.md similarity index 100% rename from mddocs/docs/_build/markdown/changelog/0.11.2.md rename to mddocs/docs/en/changelog/0.11.2.md diff --git a/mddocs/docs/changelog/0.12.0.md b/mddocs/docs/en/changelog/0.12.0.md similarity index 100% rename from mddocs/docs/changelog/0.12.0.md rename to mddocs/docs/en/changelog/0.12.0.md diff --git a/mddocs/docs/changelog/0.12.1.md b/mddocs/docs/en/changelog/0.12.1.md similarity index 100% rename from mddocs/docs/changelog/0.12.1.md rename to mddocs/docs/en/changelog/0.12.1.md diff --git a/mddocs/docs/_build/markdown/changelog/0.12.2.md b/mddocs/docs/en/changelog/0.12.2.md similarity index 100% rename from mddocs/docs/_build/markdown/changelog/0.12.2.md rename to mddocs/docs/en/changelog/0.12.2.md diff --git a/mddocs/docs/_build/markdown/changelog/0.12.3.md b/mddocs/docs/en/changelog/0.12.3.md similarity index 100% rename from mddocs/docs/_build/markdown/changelog/0.12.3.md rename to mddocs/docs/en/changelog/0.12.3.md diff --git a/mddocs/docs/_build/markdown/changelog/0.12.4.md b/mddocs/docs/en/changelog/0.12.4.md similarity index 100% rename from mddocs/docs/_build/markdown/changelog/0.12.4.md rename to mddocs/docs/en/changelog/0.12.4.md diff --git a/mddocs/docs/_build/markdown/changelog/0.12.5.md b/mddocs/docs/en/changelog/0.12.5.md similarity index 100% rename from mddocs/docs/_build/markdown/changelog/0.12.5.md rename to mddocs/docs/en/changelog/0.12.5.md diff --git a/mddocs/docs/changelog/0.13.0.md b/mddocs/docs/en/changelog/0.13.0.md similarity index 100% rename from mddocs/docs/changelog/0.13.0.md rename to mddocs/docs/en/changelog/0.13.0.md diff --git a/mddocs/docs/changelog/0.13.1.md b/mddocs/docs/en/changelog/0.13.1.md similarity index 100% rename from mddocs/docs/changelog/0.13.1.md rename to mddocs/docs/en/changelog/0.13.1.md diff --git a/mddocs/docs/_build/markdown/changelog/0.13.3.md b/mddocs/docs/en/changelog/0.13.3.md similarity index 100% rename from mddocs/docs/_build/markdown/changelog/0.13.3.md rename to mddocs/docs/en/changelog/0.13.3.md diff --git a/mddocs/docs/_build/markdown/changelog/0.13.4.md b/mddocs/docs/en/changelog/0.13.4.md similarity index 100% rename from mddocs/docs/_build/markdown/changelog/0.13.4.md rename to mddocs/docs/en/changelog/0.13.4.md diff --git a/mddocs/docs/changelog/0.7.0.md b/mddocs/docs/en/changelog/0.7.0.md similarity index 100% rename from mddocs/docs/changelog/0.7.0.md rename to mddocs/docs/en/changelog/0.7.0.md diff --git a/mddocs/docs/changelog/0.7.1.md b/mddocs/docs/en/changelog/0.7.1.md similarity index 100% rename from mddocs/docs/changelog/0.7.1.md rename to mddocs/docs/en/changelog/0.7.1.md diff --git a/mddocs/docs/changelog/0.7.2.md b/mddocs/docs/en/changelog/0.7.2.md similarity index 100% rename from mddocs/docs/changelog/0.7.2.md rename to mddocs/docs/en/changelog/0.7.2.md diff --git a/mddocs/docs/changelog/0.8.0.md b/mddocs/docs/en/changelog/0.8.0.md similarity index 100% rename from mddocs/docs/changelog/0.8.0.md rename to mddocs/docs/en/changelog/0.8.0.md diff --git a/mddocs/docs/changelog/0.8.1.md b/mddocs/docs/en/changelog/0.8.1.md similarity index 100% rename from mddocs/docs/changelog/0.8.1.md rename to mddocs/docs/en/changelog/0.8.1.md diff --git a/mddocs/docs/changelog/0.9.0.md b/mddocs/docs/en/changelog/0.9.0.md similarity index 100% rename from mddocs/docs/changelog/0.9.0.md rename to mddocs/docs/en/changelog/0.9.0.md diff --git a/mddocs/docs/_build/markdown/changelog/0.9.1.md b/mddocs/docs/en/changelog/0.9.1.md similarity index 100% rename from mddocs/docs/_build/markdown/changelog/0.9.1.md rename to mddocs/docs/en/changelog/0.9.1.md diff --git a/mddocs/docs/changelog/0.9.2.md b/mddocs/docs/en/changelog/0.9.2.md similarity index 100% rename from mddocs/docs/changelog/0.9.2.md rename to mddocs/docs/en/changelog/0.9.2.md diff --git a/mddocs/docs/_build/markdown/changelog/0.9.3.md b/mddocs/docs/en/changelog/0.9.3.md similarity index 100% rename from mddocs/docs/_build/markdown/changelog/0.9.3.md rename to mddocs/docs/en/changelog/0.9.3.md diff --git a/mddocs/docs/changelog/0.9.4.md b/mddocs/docs/en/changelog/0.9.4.md similarity index 100% rename from mddocs/docs/changelog/0.9.4.md rename to mddocs/docs/en/changelog/0.9.4.md diff --git a/mddocs/docs/_build/markdown/changelog/0.9.5.md b/mddocs/docs/en/changelog/0.9.5.md similarity index 100% rename from mddocs/docs/_build/markdown/changelog/0.9.5.md rename to mddocs/docs/en/changelog/0.9.5.md diff --git a/mddocs/docs/changelog/DRAFT.md b/mddocs/docs/en/changelog/DRAFT.md similarity index 100% rename from mddocs/docs/changelog/DRAFT.md rename to mddocs/docs/en/changelog/DRAFT.md diff --git a/mddocs/docs/changelog/NEXT_RELEASE.md b/mddocs/docs/en/changelog/NEXT_RELEASE.md similarity index 100% rename from mddocs/docs/changelog/NEXT_RELEASE.md rename to mddocs/docs/en/changelog/NEXT_RELEASE.md diff --git a/mddocs/docs/changelog/index.md b/mddocs/docs/en/changelog/index.md similarity index 100% rename from mddocs/docs/changelog/index.md rename to mddocs/docs/en/changelog/index.md diff --git a/mddocs/docs/changelog/next_release/.keep b/mddocs/docs/en/changelog/next_release/.keep similarity index 100% rename from mddocs/docs/changelog/next_release/.keep rename to mddocs/docs/en/changelog/next_release/.keep diff --git a/mddocs/docs/en/concepts.md b/mddocs/docs/en/concepts.md new file mode 100644 index 000000000..8011e79af --- /dev/null +++ b/mddocs/docs/en/concepts.md @@ -0,0 +1,486 @@ +# Concepts + +Here you can find detailed documentation about each one of the onETL concepts and how to use them. + +## Connection + +### Basics + +onETL is used to pull and push data into other systems, and so it has a first-class `Connection` concept for storing credentials that are used to communicate with external systems. + +A `Connection` is essentially a set of parameters, such as username, password, hostname. + +To create a connection to a specific storage type, you must use a class that matches the storage type. The class name is the same as the storage type name (`Oracle`, `MSSQL`, `SFTP`, etc): + +```python +from onetl.connection import SFTP + +sftp = SFTP( + host="sftp.test.com", + user="onetl", + password="onetl", +) +``` + +All connection types are inherited from the parent class `BaseConnection`. + +### Class diagram + +```plantuml + + @startuml + left to right direction + skinparam classFontSize 20 + skinparam class { + BackgroundColor<> LightGreen + BackgroundColor<> Khaki + BackgroundColor<> LightBlue + StereotypeFontColor<> Transparent + StereotypeFontColor<> Transparent + StereotypeFontColor<> Transparent + } + + class BaseConnection { + } + + class DBConnection <>{ + } + DBConnection --|> BaseConnection + + class Hive <>{ + } + Hive --|> DBConnection + + class Greenplum <>{ + } + Greenplum --|> DBConnection + + class MongoDB <>{ + } + MongoDB --|> DBConnection + + class Kafka <>{ + } + Kafka --|> DBConnection + + class JDBCConnection <>{ + } + JDBCConnection --|> DBConnection + + class Clickhouse <>{ + } + Clickhouse --|> JDBCConnection + + class MSSQL <>{ + } + MSSQL --|> JDBCConnection + + class MySQL <>{ + } + MySQL --|> JDBCConnection + + class Postgres <>{ + } + Postgres --|> JDBCConnection + + class Oracle <>{ + } + Oracle --|> JDBCConnection + + class Teradata <>{ + } + Teradata --|> JDBCConnection + + class FileConnection <>{ + } + FileConnection --|> BaseConnection + + class FTP <>{ + } + FTP --|> FileConnection + + class FTPS <>{ + } + FTPS --|> FileConnection + + class HDFS <>{ + } + HDFS --|> FileConnection + + class WebDAV <>{ + } + WebDAV --|> FileConnection + + class Samba <>{ + } + Samba --|> FileConnection + + class SFTP <>{ + } + SFTP --|> FileConnection + + class S3 <>{ + } + S3 --|> FileConnection + + class FileDFConnection <>{ + } + FileDFConnection --|> BaseConnection + + class SparkHDFS <>{ + } + SparkHDFS --|> FileDFConnection + + class SparkLocalFS <>{ + } + SparkLocalFS --|> FileDFConnection + + class SparkS3 <>{ + } + SparkS3 --|> FileDFConnection + + @enduml +``` + +```mermaid +classDiagram + BaseConnection <|-- DBConnection + DBConnection <|-- Hive + DBConnection <|-- Greenplum + DBConnection <|-- MongoDB + DBConnection <|-- Kafka + DBConnection <|-- JDBCConnection + JDBCConnection <|-- Clickhouse + JDBCConnection <|-- MSSQL + JDBCConnection <|-- MySQL + JDBCConnection <|-- Postgres + JDBCConnection <|-- Oracle + JDBCConnection <|-- Teradata + BaseConnection <|-- FileConnection + FileConnection <|-- FTP + FileConnection <|-- FTPS + FileConnection <|-- HDFS + FileConnection <|-- WebDAV + FileConnection <|-- Samba + FileConnection <|-- SFTP + FileConnection <|-- S3 + BaseConnection <|-- FileDFConnection + FileDFConnection <|-- SparkHDFS + FileDFConnection <|-- SparkLocalFS + FileDFConnection <|-- SparkS3 +``` + +### DBConnection + +Classes inherited from `DBConnection` could be used for accessing databases. + +A `DBConnection` could be instantiated as follows: + +```python +from onetl.connection import MSSQL + +mssql = MSSQL( + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, +) +``` + +where **spark** is the current SparkSession. +`onETL` uses `Spark` and specific Java connectors under the hood to work with databases. + +For a description of other parameters, see the documentation for the [available DBConnections](../connection/file_df_connection/). + +### FileConnection + +Classes inherited from `FileConnection` could be used to access files stored on the different file systems/file servers + +A `FileConnection` could be instantiated as follows: + +```python +from onetl.connection import SFTP + +sftp = SFTP( + host="sftp.test.com", + user="onetl", + password="onetl", +) +``` + +For a description of other parameters, see the documentation for the {ref}`available FileConnections `. + +### FileDFConnection + +Classes inherited from `FileDFConnection` could be used for accessing files as Spark DataFrames. + +A `FileDFConnection` could be instantiated as follows: + +```python +from onetl.connection import SparkHDFS + +spark_hdfs = SparkHDFS( + host="namenode1.domain.com", + cluster="mycluster", + spark=spark, +) +``` + +where **spark** is the current SparkSession. +`onETL` uses `Spark` and specific Java connectors under the hood to work with DataFrames. + +For a description of other parameters, see the documentation for the {ref}`available FileDFConnections `. + +### Checking connection availability + +Once you have created a connection, you can check the database/filesystem availability using the method `check()`: + +```python +mssql.check() +sftp.check() +spark_hdfs.check() +``` + +It will raise an exception if database/filesystem cannot be accessed. + +This method returns connection itself, so you can create connection and immediately check its availability: + +```Python +mssql = MSSQL( + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, +).check() # <-- +``` + +## Extract/Load data + +### Basics + +As we said above, onETL is used to extract data from and load data into remote systems. + +onETL provides several classes for this: + +> * [DBReader](../db_/reader) +> * [DBWriter](../db_/writer) +> * [FileDFReader](../file_df/file_df_reader/file_df_reader) +> * [FileDFWriter](../file_df/file_df_writer/file_df_writer) +> * [FileDownloader](../file/file_downloader/file_downloader) +> * [FileUploader](../file/file_uploader/file_uploader) +> * [FileMover](../file/file_mover/file_mover) + +All of these classes have a method `run()` that starts extracting/loading the data: + +```python +from onetl.db import DBReader, DBWriter + +reader = DBReader( + connection=mssql, + source="dbo.demo_table", + columns=["column_1", "column_2"], +) + +# Read data as Spark DataFrame +df = reader.run() + +db_writer = DBWriter( + connection=hive, + target="dl_sb.demo_table", +) + +# Save Spark DataFrame to Hive table +writer.run(df) +``` + +### Extract data + +To extract data you can use classes: + +| | Use case | Connection | `run()` gets | `run()` returns | +| -- | - | - | - | --- | +| [`DBReader`](../db_/reader) | Reading data from a database | Any [`DBConnection`](../connection/db_connection) | - | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/dataframe.html#dataframe) | +| [`FileDFReader`](../file_df/file_df_reader/file_df_reader) | Read data from a file or set of files | Any [`FileDFConnection`](../connection/file_df_connection) | No input, or List[File path on FileSystem] | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/dataframe.html#dataframe) | +| [`FileDownloader`](../file/file_downloader/file_downloader) | Download files from remote FS to local FS | Any [`FileConnection`](../connection/file_connection) | No input, or List[File path on remote FileSystem] | [`DownloadResult`](../file/file_downloader/result) | + +### Load data + +To load data you can use classes: + +| | Use case | Connection | `run()` gets | `run()` returns | +| - | -- | - | --- | -- | +| [`DBWriter`](../db_/writer) | Writing data from a DataFrame to a database | Any [`DBConnection`](../connection/db_connection) | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/dataframe.html#dataframe) | None | +| [`FileDFWriter`](../file_df/file_df_writer/file_df_writer) | Writing data from a DataFrame to a folder | Any [`FileDFConnection`](../connection/file_df_connection) | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/dataframe.html#dataframe) | None | +| [`FileUploader`](../file/file_uploader/file_uploader) | Uploading files from a local FS to remote FS | Any [`FileConnection`](../connection/file_connection) | List[File path on local FileSystem] | [`UploadResult`](../file/file_uploader/result) | + +### Manipulate data + +To manipulate data you can use classes: + +| | Use case | Connection | `run()` gets | `run()` returns | +| - | - | -- | -- | - | +| [`FileMover`](../file/file_mover/file_mover) | Move files between directories in remote FS | Any [`FileConnection`](../connection/file_connection) | List[File path on remote FileSystem] | [`MoveResult`](../file/file_mover/result) | + +### Options + +Extract and load classes have a `options` parameter, which has a special meaning: + +> - all other parameters - *WHAT* we extract / *WHERE* we load to +> - `options` parameter - *HOW* we extract/load data + +```python +db_reader = DBReader( + # WHAT do we read: + connection=mssql, + source="dbo.demo_table", # some table from MSSQL + columns=["column_1", "column_2"], # but only specific set of columns + where="column_2 > 1000", # only rows matching the clause + # HOW do we read: + options=MSSQL.ReadOptions( + numPartitions=10, # read in 10 parallel jobs + partitionColumn="id", # balance data read by assigning each job a part of data using `hash(id) mod N` expression + partitioningMode="hash", + fetchsize=1000, # each job will fetch block of 1000 rows each on every read attempt + ), +) + +db_writer = DBWriter( + # WHERE do we write to - to some table in Hive + connection=hive, + target="dl_sb.demo_table", + # HOW do we write - overwrite all the data in the existing table + options=Hive.WriteOptions(if_exists="replace_entire_table"), +) + +file_downloader = FileDownloader( + # WHAT do we download - files from some dir in SFTP + connection=sftp, + source_path="/source", + filters=[Glob("*.csv")], # only CSV files + limits=[MaxFilesCount(1000)], # 1000 files max + # WHERE do we download to - a specific dir on local FS + local_path="/some", + # HOW do we download: + options=FileDownloader.Options( + delete_source=True, # after downloading each file remove it from source_path + if_exists="replace_file", # replace existing files in the local_path + ), +) + +file_uploader = FileUploader( + # WHAT do we upload - files from some local dir + local_path="/source", + # WHERE do we upload to- specific remote dir in HDFS + connection=hdfs, + target_path="/some", + # HOW do we upload: + options=FileUploader.Options( + delete_local=True, # after uploading each file remove it from local_path + if_exists="replace_file", # replace existing files in the target_path + ), +) + +file_mover = FileMover( + # WHAT do we move - files in some remote dir in HDFS + source_path="/source", + connection=hdfs, + # WHERE do we move files to + target_path="/some", # a specific remote dir within the same HDFS connection + # HOW do we load - replace existing files in the target_path + options=FileMover.Options(if_exists="replace_file"), +) + +file_df_reader = FileDFReader( + # WHAT do we read - *.csv files from some dir in S3 + connection=s3, + source_path="/source", + file_format=CSV(), + # HOW do we read - load files from /source/*.csv, not from /source/nested/*.csv + options=FileDFReader.Options(recursive=False), +) + +file_df_writer = FileDFWriter( + # WHERE do we write to - as .csv files in some dir in S3 + connection=s3, + target_path="/target", + file_format=CSV(), + # HOW do we write - replace all existing files in /target, if exists + options=FileDFWriter.Options(if_exists="replace_entire_directory"), +) +``` + +More information about `options` could be found on [`DBConnection`](../connection/db_connection) and [`FileDownloader`](../file/file_downloader/file_downloader) / [`FileUploader`](../file/file_uploader/file_uploader) / [`FileMover`](../file/file_mover/file_mover) / [`FileDFReader`](../file_df/file_df_reader/file_df_reader) / [`FileDFWriter`](../file_df/file_df_writer/file_df_writer) documentation. + + +### Read Strategies + +onETL have several builtin strategies for reading data: + +1. [Snapshot strategy](../strategy/snapshot_strategy) (default strategy) +2. [Incremental strategy](../strategy/incremental_strategy) +3. [Snapshot batch strategy](../strategy/snapshot_batch_strategy) +4. [Incremental batch strategy](../strategy/incremental_batch_strategy) + +For example, an incremental strategy allows you to get only new data from the table: + +```python +from onetl.strategy import IncrementalStrategy + +reader = DBReader( + connection=mssql, + source="dbo.demo_table", + hwm_column="id", # detect new data based on value of "id" column +) + +# first run +with IncrementalStrategy(): + df = reader.run() + +sleep(3600) + +# second run +with IncrementalStrategy(): + # only rows, that appeared in the source since previous run + df = reader.run() +``` + +or get only files which were not downloaded before: + +```python +from onetl.strategy import IncrementalStrategy + +file_downloader = FileDownloader( + connection=sftp, + source_path="/remote", + local_path="/local", + hwm_type="file_list", # save all downloaded files to a list, and exclude files already present in this list +) + +# first run +with IncrementalStrategy(): + files = file_downloader.run() + +sleep(3600) + +# second run +with IncrementalStrategy(): + # only files, that appeared in the source since previous run + files = file_downloader.run() +``` + +Most of strategies are based on [`HWM`](../hwm_store/), Please check each strategy documentation for more details + +### Why just not use Connection class for extract/load? + +Connections are very simple, they have only a set of some basic operations, +like `mkdir`, `remove_file`, `get_table_schema`, and so on. + +High-level operations, like + + * [`strategy`](../strategy/) support + * Handling metadata push/pull + * Handling different options, like `if_exists="replace_file"` in case of file download/upload + +is moved to a separate class which calls the connection object methods to perform some complex logic. diff --git a/mddocs/docs/connection/db_connection/clickhouse/connection.md b/mddocs/docs/en/connection/db_connection/clickhouse/connection.md similarity index 100% rename from mddocs/docs/connection/db_connection/clickhouse/connection.md rename to mddocs/docs/en/connection/db_connection/clickhouse/connection.md diff --git a/mddocs/docs/connection/db_connection/clickhouse/execute.md b/mddocs/docs/en/connection/db_connection/clickhouse/execute.md similarity index 100% rename from mddocs/docs/connection/db_connection/clickhouse/execute.md rename to mddocs/docs/en/connection/db_connection/clickhouse/execute.md diff --git a/mddocs/docs/connection/db_connection/clickhouse/index.md b/mddocs/docs/en/connection/db_connection/clickhouse/index.md similarity index 100% rename from mddocs/docs/connection/db_connection/clickhouse/index.md rename to mddocs/docs/en/connection/db_connection/clickhouse/index.md diff --git a/mddocs/docs/connection/db_connection/clickhouse/prerequisites.md b/mddocs/docs/en/connection/db_connection/clickhouse/prerequisites.md similarity index 100% rename from mddocs/docs/connection/db_connection/clickhouse/prerequisites.md rename to mddocs/docs/en/connection/db_connection/clickhouse/prerequisites.md diff --git a/mddocs/docs/connection/db_connection/clickhouse/read.md b/mddocs/docs/en/connection/db_connection/clickhouse/read.md similarity index 100% rename from mddocs/docs/connection/db_connection/clickhouse/read.md rename to mddocs/docs/en/connection/db_connection/clickhouse/read.md diff --git a/mddocs/docs/connection/db_connection/clickhouse/sql.md b/mddocs/docs/en/connection/db_connection/clickhouse/sql.md similarity index 100% rename from mddocs/docs/connection/db_connection/clickhouse/sql.md rename to mddocs/docs/en/connection/db_connection/clickhouse/sql.md diff --git a/mddocs/docs/connection/db_connection/clickhouse/types.md b/mddocs/docs/en/connection/db_connection/clickhouse/types.md similarity index 100% rename from mddocs/docs/connection/db_connection/clickhouse/types.md rename to mddocs/docs/en/connection/db_connection/clickhouse/types.md diff --git a/mddocs/docs/connection/db_connection/clickhouse/write.md b/mddocs/docs/en/connection/db_connection/clickhouse/write.md similarity index 100% rename from mddocs/docs/connection/db_connection/clickhouse/write.md rename to mddocs/docs/en/connection/db_connection/clickhouse/write.md diff --git a/mddocs/docs/connection/db_connection/greenplum/connection.md b/mddocs/docs/en/connection/db_connection/greenplum/connection.md similarity index 100% rename from mddocs/docs/connection/db_connection/greenplum/connection.md rename to mddocs/docs/en/connection/db_connection/greenplum/connection.md diff --git a/mddocs/docs/connection/db_connection/greenplum/execute.md b/mddocs/docs/en/connection/db_connection/greenplum/execute.md similarity index 100% rename from mddocs/docs/connection/db_connection/greenplum/execute.md rename to mddocs/docs/en/connection/db_connection/greenplum/execute.md diff --git a/mddocs/docs/connection/db_connection/greenplum/index.md b/mddocs/docs/en/connection/db_connection/greenplum/index.md similarity index 100% rename from mddocs/docs/connection/db_connection/greenplum/index.md rename to mddocs/docs/en/connection/db_connection/greenplum/index.md diff --git a/mddocs/docs/connection/db_connection/greenplum/prerequisites.md b/mddocs/docs/en/connection/db_connection/greenplum/prerequisites.md similarity index 100% rename from mddocs/docs/connection/db_connection/greenplum/prerequisites.md rename to mddocs/docs/en/connection/db_connection/greenplum/prerequisites.md diff --git a/mddocs/docs/connection/db_connection/greenplum/read.md b/mddocs/docs/en/connection/db_connection/greenplum/read.md similarity index 100% rename from mddocs/docs/connection/db_connection/greenplum/read.md rename to mddocs/docs/en/connection/db_connection/greenplum/read.md diff --git a/mddocs/docs/connection/db_connection/greenplum/types.md b/mddocs/docs/en/connection/db_connection/greenplum/types.md similarity index 100% rename from mddocs/docs/connection/db_connection/greenplum/types.md rename to mddocs/docs/en/connection/db_connection/greenplum/types.md diff --git a/mddocs/docs/connection/db_connection/greenplum/write.md b/mddocs/docs/en/connection/db_connection/greenplum/write.md similarity index 100% rename from mddocs/docs/connection/db_connection/greenplum/write.md rename to mddocs/docs/en/connection/db_connection/greenplum/write.md diff --git a/mddocs/docs/connection/db_connection/hive/connection.md b/mddocs/docs/en/connection/db_connection/hive/connection.md similarity index 100% rename from mddocs/docs/connection/db_connection/hive/connection.md rename to mddocs/docs/en/connection/db_connection/hive/connection.md diff --git a/mddocs/docs/connection/db_connection/hive/execute.md b/mddocs/docs/en/connection/db_connection/hive/execute.md similarity index 100% rename from mddocs/docs/connection/db_connection/hive/execute.md rename to mddocs/docs/en/connection/db_connection/hive/execute.md diff --git a/mddocs/docs/connection/db_connection/hive/index.md b/mddocs/docs/en/connection/db_connection/hive/index.md similarity index 100% rename from mddocs/docs/connection/db_connection/hive/index.md rename to mddocs/docs/en/connection/db_connection/hive/index.md diff --git a/mddocs/docs/connection/db_connection/hive/prerequisites.md b/mddocs/docs/en/connection/db_connection/hive/prerequisites.md similarity index 100% rename from mddocs/docs/connection/db_connection/hive/prerequisites.md rename to mddocs/docs/en/connection/db_connection/hive/prerequisites.md diff --git a/mddocs/docs/connection/db_connection/hive/read.md b/mddocs/docs/en/connection/db_connection/hive/read.md similarity index 100% rename from mddocs/docs/connection/db_connection/hive/read.md rename to mddocs/docs/en/connection/db_connection/hive/read.md diff --git a/mddocs/docs/connection/db_connection/hive/slots.md b/mddocs/docs/en/connection/db_connection/hive/slots.md similarity index 100% rename from mddocs/docs/connection/db_connection/hive/slots.md rename to mddocs/docs/en/connection/db_connection/hive/slots.md diff --git a/mddocs/docs/connection/db_connection/hive/sql.md b/mddocs/docs/en/connection/db_connection/hive/sql.md similarity index 100% rename from mddocs/docs/connection/db_connection/hive/sql.md rename to mddocs/docs/en/connection/db_connection/hive/sql.md diff --git a/mddocs/docs/connection/db_connection/hive/write.md b/mddocs/docs/en/connection/db_connection/hive/write.md similarity index 100% rename from mddocs/docs/connection/db_connection/hive/write.md rename to mddocs/docs/en/connection/db_connection/hive/write.md diff --git a/mddocs/docs/connection/db_connection/index.md b/mddocs/docs/en/connection/db_connection/index.md similarity index 100% rename from mddocs/docs/connection/db_connection/index.md rename to mddocs/docs/en/connection/db_connection/index.md diff --git a/mddocs/docs/connection/db_connection/kafka/auth.md b/mddocs/docs/en/connection/db_connection/kafka/auth.md similarity index 100% rename from mddocs/docs/connection/db_connection/kafka/auth.md rename to mddocs/docs/en/connection/db_connection/kafka/auth.md diff --git a/mddocs/docs/connection/db_connection/kafka/basic_auth.md b/mddocs/docs/en/connection/db_connection/kafka/basic_auth.md similarity index 100% rename from mddocs/docs/connection/db_connection/kafka/basic_auth.md rename to mddocs/docs/en/connection/db_connection/kafka/basic_auth.md diff --git a/mddocs/docs/connection/db_connection/kafka/connection.md b/mddocs/docs/en/connection/db_connection/kafka/connection.md similarity index 100% rename from mddocs/docs/connection/db_connection/kafka/connection.md rename to mddocs/docs/en/connection/db_connection/kafka/connection.md diff --git a/mddocs/docs/connection/db_connection/kafka/index.md b/mddocs/docs/en/connection/db_connection/kafka/index.md similarity index 100% rename from mddocs/docs/connection/db_connection/kafka/index.md rename to mddocs/docs/en/connection/db_connection/kafka/index.md diff --git a/mddocs/docs/connection/db_connection/kafka/kerberos_auth.md b/mddocs/docs/en/connection/db_connection/kafka/kerberos_auth.md similarity index 100% rename from mddocs/docs/connection/db_connection/kafka/kerberos_auth.md rename to mddocs/docs/en/connection/db_connection/kafka/kerberos_auth.md diff --git a/mddocs/docs/connection/db_connection/kafka/plaintext_protocol.md b/mddocs/docs/en/connection/db_connection/kafka/plaintext_protocol.md similarity index 100% rename from mddocs/docs/connection/db_connection/kafka/plaintext_protocol.md rename to mddocs/docs/en/connection/db_connection/kafka/plaintext_protocol.md diff --git a/mddocs/docs/connection/db_connection/kafka/prerequisites.md b/mddocs/docs/en/connection/db_connection/kafka/prerequisites.md similarity index 100% rename from mddocs/docs/connection/db_connection/kafka/prerequisites.md rename to mddocs/docs/en/connection/db_connection/kafka/prerequisites.md diff --git a/mddocs/docs/connection/db_connection/kafka/protocol.md b/mddocs/docs/en/connection/db_connection/kafka/protocol.md similarity index 100% rename from mddocs/docs/connection/db_connection/kafka/protocol.md rename to mddocs/docs/en/connection/db_connection/kafka/protocol.md diff --git a/mddocs/docs/connection/db_connection/kafka/read.md b/mddocs/docs/en/connection/db_connection/kafka/read.md similarity index 100% rename from mddocs/docs/connection/db_connection/kafka/read.md rename to mddocs/docs/en/connection/db_connection/kafka/read.md diff --git a/mddocs/docs/connection/db_connection/kafka/scram_auth.md b/mddocs/docs/en/connection/db_connection/kafka/scram_auth.md similarity index 100% rename from mddocs/docs/connection/db_connection/kafka/scram_auth.md rename to mddocs/docs/en/connection/db_connection/kafka/scram_auth.md diff --git a/mddocs/docs/connection/db_connection/kafka/slots.md b/mddocs/docs/en/connection/db_connection/kafka/slots.md similarity index 100% rename from mddocs/docs/connection/db_connection/kafka/slots.md rename to mddocs/docs/en/connection/db_connection/kafka/slots.md diff --git a/mddocs/docs/connection/db_connection/kafka/ssl_protocol.md b/mddocs/docs/en/connection/db_connection/kafka/ssl_protocol.md similarity index 100% rename from mddocs/docs/connection/db_connection/kafka/ssl_protocol.md rename to mddocs/docs/en/connection/db_connection/kafka/ssl_protocol.md diff --git a/mddocs/docs/connection/db_connection/kafka/troubleshooting.md b/mddocs/docs/en/connection/db_connection/kafka/troubleshooting.md similarity index 100% rename from mddocs/docs/connection/db_connection/kafka/troubleshooting.md rename to mddocs/docs/en/connection/db_connection/kafka/troubleshooting.md diff --git a/mddocs/docs/connection/db_connection/kafka/write.md b/mddocs/docs/en/connection/db_connection/kafka/write.md similarity index 100% rename from mddocs/docs/connection/db_connection/kafka/write.md rename to mddocs/docs/en/connection/db_connection/kafka/write.md diff --git a/mddocs/docs/connection/db_connection/mongodb/connection.md b/mddocs/docs/en/connection/db_connection/mongodb/connection.md similarity index 100% rename from mddocs/docs/connection/db_connection/mongodb/connection.md rename to mddocs/docs/en/connection/db_connection/mongodb/connection.md diff --git a/mddocs/docs/connection/db_connection/mongodb/index.md b/mddocs/docs/en/connection/db_connection/mongodb/index.md similarity index 100% rename from mddocs/docs/connection/db_connection/mongodb/index.md rename to mddocs/docs/en/connection/db_connection/mongodb/index.md diff --git a/mddocs/docs/connection/db_connection/mongodb/pipeline.md b/mddocs/docs/en/connection/db_connection/mongodb/pipeline.md similarity index 100% rename from mddocs/docs/connection/db_connection/mongodb/pipeline.md rename to mddocs/docs/en/connection/db_connection/mongodb/pipeline.md diff --git a/mddocs/docs/connection/db_connection/mongodb/prerequisites.md b/mddocs/docs/en/connection/db_connection/mongodb/prerequisites.md similarity index 100% rename from mddocs/docs/connection/db_connection/mongodb/prerequisites.md rename to mddocs/docs/en/connection/db_connection/mongodb/prerequisites.md diff --git a/mddocs/docs/connection/db_connection/mongodb/read.md b/mddocs/docs/en/connection/db_connection/mongodb/read.md similarity index 100% rename from mddocs/docs/connection/db_connection/mongodb/read.md rename to mddocs/docs/en/connection/db_connection/mongodb/read.md diff --git a/mddocs/docs/connection/db_connection/mongodb/types.md b/mddocs/docs/en/connection/db_connection/mongodb/types.md similarity index 100% rename from mddocs/docs/connection/db_connection/mongodb/types.md rename to mddocs/docs/en/connection/db_connection/mongodb/types.md diff --git a/mddocs/docs/connection/db_connection/mongodb/write.md b/mddocs/docs/en/connection/db_connection/mongodb/write.md similarity index 100% rename from mddocs/docs/connection/db_connection/mongodb/write.md rename to mddocs/docs/en/connection/db_connection/mongodb/write.md diff --git a/mddocs/docs/connection/db_connection/mssql/connection.md b/mddocs/docs/en/connection/db_connection/mssql/connection.md similarity index 100% rename from mddocs/docs/connection/db_connection/mssql/connection.md rename to mddocs/docs/en/connection/db_connection/mssql/connection.md diff --git a/mddocs/docs/connection/db_connection/mssql/execute.md b/mddocs/docs/en/connection/db_connection/mssql/execute.md similarity index 100% rename from mddocs/docs/connection/db_connection/mssql/execute.md rename to mddocs/docs/en/connection/db_connection/mssql/execute.md diff --git a/mddocs/docs/connection/db_connection/mssql/index.md b/mddocs/docs/en/connection/db_connection/mssql/index.md similarity index 100% rename from mddocs/docs/connection/db_connection/mssql/index.md rename to mddocs/docs/en/connection/db_connection/mssql/index.md diff --git a/mddocs/docs/connection/db_connection/mssql/prerequisites.md b/mddocs/docs/en/connection/db_connection/mssql/prerequisites.md similarity index 100% rename from mddocs/docs/connection/db_connection/mssql/prerequisites.md rename to mddocs/docs/en/connection/db_connection/mssql/prerequisites.md diff --git a/mddocs/docs/connection/db_connection/mssql/read.md b/mddocs/docs/en/connection/db_connection/mssql/read.md similarity index 100% rename from mddocs/docs/connection/db_connection/mssql/read.md rename to mddocs/docs/en/connection/db_connection/mssql/read.md diff --git a/mddocs/docs/connection/db_connection/mssql/sql.md b/mddocs/docs/en/connection/db_connection/mssql/sql.md similarity index 100% rename from mddocs/docs/connection/db_connection/mssql/sql.md rename to mddocs/docs/en/connection/db_connection/mssql/sql.md diff --git a/mddocs/docs/connection/db_connection/mssql/types.md b/mddocs/docs/en/connection/db_connection/mssql/types.md similarity index 100% rename from mddocs/docs/connection/db_connection/mssql/types.md rename to mddocs/docs/en/connection/db_connection/mssql/types.md diff --git a/mddocs/docs/connection/db_connection/mssql/write.md b/mddocs/docs/en/connection/db_connection/mssql/write.md similarity index 100% rename from mddocs/docs/connection/db_connection/mssql/write.md rename to mddocs/docs/en/connection/db_connection/mssql/write.md diff --git a/mddocs/docs/connection/db_connection/mysql/connection.md b/mddocs/docs/en/connection/db_connection/mysql/connection.md similarity index 100% rename from mddocs/docs/connection/db_connection/mysql/connection.md rename to mddocs/docs/en/connection/db_connection/mysql/connection.md diff --git a/mddocs/docs/connection/db_connection/mysql/execute.md b/mddocs/docs/en/connection/db_connection/mysql/execute.md similarity index 100% rename from mddocs/docs/connection/db_connection/mysql/execute.md rename to mddocs/docs/en/connection/db_connection/mysql/execute.md diff --git a/mddocs/docs/connection/db_connection/mysql/index.md b/mddocs/docs/en/connection/db_connection/mysql/index.md similarity index 100% rename from mddocs/docs/connection/db_connection/mysql/index.md rename to mddocs/docs/en/connection/db_connection/mysql/index.md diff --git a/mddocs/docs/connection/db_connection/mysql/prerequisites.md b/mddocs/docs/en/connection/db_connection/mysql/prerequisites.md similarity index 100% rename from mddocs/docs/connection/db_connection/mysql/prerequisites.md rename to mddocs/docs/en/connection/db_connection/mysql/prerequisites.md diff --git a/mddocs/docs/connection/db_connection/mysql/read.md b/mddocs/docs/en/connection/db_connection/mysql/read.md similarity index 100% rename from mddocs/docs/connection/db_connection/mysql/read.md rename to mddocs/docs/en/connection/db_connection/mysql/read.md diff --git a/mddocs/docs/connection/db_connection/mysql/sql.md b/mddocs/docs/en/connection/db_connection/mysql/sql.md similarity index 100% rename from mddocs/docs/connection/db_connection/mysql/sql.md rename to mddocs/docs/en/connection/db_connection/mysql/sql.md diff --git a/mddocs/docs/connection/db_connection/mysql/types.md b/mddocs/docs/en/connection/db_connection/mysql/types.md similarity index 100% rename from mddocs/docs/connection/db_connection/mysql/types.md rename to mddocs/docs/en/connection/db_connection/mysql/types.md diff --git a/mddocs/docs/connection/db_connection/mysql/write.md b/mddocs/docs/en/connection/db_connection/mysql/write.md similarity index 100% rename from mddocs/docs/connection/db_connection/mysql/write.md rename to mddocs/docs/en/connection/db_connection/mysql/write.md diff --git a/mddocs/docs/connection/db_connection/oracle/connection.md b/mddocs/docs/en/connection/db_connection/oracle/connection.md similarity index 100% rename from mddocs/docs/connection/db_connection/oracle/connection.md rename to mddocs/docs/en/connection/db_connection/oracle/connection.md diff --git a/mddocs/docs/connection/db_connection/oracle/execute.md b/mddocs/docs/en/connection/db_connection/oracle/execute.md similarity index 100% rename from mddocs/docs/connection/db_connection/oracle/execute.md rename to mddocs/docs/en/connection/db_connection/oracle/execute.md diff --git a/mddocs/docs/connection/db_connection/oracle/index.md b/mddocs/docs/en/connection/db_connection/oracle/index.md similarity index 100% rename from mddocs/docs/connection/db_connection/oracle/index.md rename to mddocs/docs/en/connection/db_connection/oracle/index.md diff --git a/mddocs/docs/connection/db_connection/oracle/prerequisites.md b/mddocs/docs/en/connection/db_connection/oracle/prerequisites.md similarity index 100% rename from mddocs/docs/connection/db_connection/oracle/prerequisites.md rename to mddocs/docs/en/connection/db_connection/oracle/prerequisites.md diff --git a/mddocs/docs/connection/db_connection/oracle/read.md b/mddocs/docs/en/connection/db_connection/oracle/read.md similarity index 100% rename from mddocs/docs/connection/db_connection/oracle/read.md rename to mddocs/docs/en/connection/db_connection/oracle/read.md diff --git a/mddocs/docs/connection/db_connection/oracle/sql.md b/mddocs/docs/en/connection/db_connection/oracle/sql.md similarity index 100% rename from mddocs/docs/connection/db_connection/oracle/sql.md rename to mddocs/docs/en/connection/db_connection/oracle/sql.md diff --git a/mddocs/docs/connection/db_connection/oracle/types.md b/mddocs/docs/en/connection/db_connection/oracle/types.md similarity index 100% rename from mddocs/docs/connection/db_connection/oracle/types.md rename to mddocs/docs/en/connection/db_connection/oracle/types.md diff --git a/mddocs/docs/connection/db_connection/oracle/write.md b/mddocs/docs/en/connection/db_connection/oracle/write.md similarity index 100% rename from mddocs/docs/connection/db_connection/oracle/write.md rename to mddocs/docs/en/connection/db_connection/oracle/write.md diff --git a/mddocs/docs/connection/db_connection/postgres/connection.md b/mddocs/docs/en/connection/db_connection/postgres/connection.md similarity index 100% rename from mddocs/docs/connection/db_connection/postgres/connection.md rename to mddocs/docs/en/connection/db_connection/postgres/connection.md diff --git a/mddocs/docs/connection/db_connection/postgres/execute.md b/mddocs/docs/en/connection/db_connection/postgres/execute.md similarity index 100% rename from mddocs/docs/connection/db_connection/postgres/execute.md rename to mddocs/docs/en/connection/db_connection/postgres/execute.md diff --git a/mddocs/docs/connection/db_connection/postgres/index.md b/mddocs/docs/en/connection/db_connection/postgres/index.md similarity index 100% rename from mddocs/docs/connection/db_connection/postgres/index.md rename to mddocs/docs/en/connection/db_connection/postgres/index.md diff --git a/mddocs/docs/connection/db_connection/postgres/prerequisites.md b/mddocs/docs/en/connection/db_connection/postgres/prerequisites.md similarity index 100% rename from mddocs/docs/connection/db_connection/postgres/prerequisites.md rename to mddocs/docs/en/connection/db_connection/postgres/prerequisites.md diff --git a/mddocs/docs/connection/db_connection/postgres/read.md b/mddocs/docs/en/connection/db_connection/postgres/read.md similarity index 100% rename from mddocs/docs/connection/db_connection/postgres/read.md rename to mddocs/docs/en/connection/db_connection/postgres/read.md diff --git a/mddocs/docs/connection/db_connection/postgres/sql.md b/mddocs/docs/en/connection/db_connection/postgres/sql.md similarity index 100% rename from mddocs/docs/connection/db_connection/postgres/sql.md rename to mddocs/docs/en/connection/db_connection/postgres/sql.md diff --git a/mddocs/docs/connection/db_connection/postgres/types.md b/mddocs/docs/en/connection/db_connection/postgres/types.md similarity index 100% rename from mddocs/docs/connection/db_connection/postgres/types.md rename to mddocs/docs/en/connection/db_connection/postgres/types.md diff --git a/mddocs/docs/connection/db_connection/postgres/write.md b/mddocs/docs/en/connection/db_connection/postgres/write.md similarity index 100% rename from mddocs/docs/connection/db_connection/postgres/write.md rename to mddocs/docs/en/connection/db_connection/postgres/write.md diff --git a/mddocs/docs/connection/db_connection/teradata/connection.md b/mddocs/docs/en/connection/db_connection/teradata/connection.md similarity index 100% rename from mddocs/docs/connection/db_connection/teradata/connection.md rename to mddocs/docs/en/connection/db_connection/teradata/connection.md diff --git a/mddocs/docs/connection/db_connection/teradata/execute.md b/mddocs/docs/en/connection/db_connection/teradata/execute.md similarity index 100% rename from mddocs/docs/connection/db_connection/teradata/execute.md rename to mddocs/docs/en/connection/db_connection/teradata/execute.md diff --git a/mddocs/docs/connection/db_connection/teradata/index.md b/mddocs/docs/en/connection/db_connection/teradata/index.md similarity index 100% rename from mddocs/docs/connection/db_connection/teradata/index.md rename to mddocs/docs/en/connection/db_connection/teradata/index.md diff --git a/mddocs/docs/connection/db_connection/teradata/prerequisites.md b/mddocs/docs/en/connection/db_connection/teradata/prerequisites.md similarity index 100% rename from mddocs/docs/connection/db_connection/teradata/prerequisites.md rename to mddocs/docs/en/connection/db_connection/teradata/prerequisites.md diff --git a/mddocs/docs/connection/db_connection/teradata/read.md b/mddocs/docs/en/connection/db_connection/teradata/read.md similarity index 100% rename from mddocs/docs/connection/db_connection/teradata/read.md rename to mddocs/docs/en/connection/db_connection/teradata/read.md diff --git a/mddocs/docs/connection/db_connection/teradata/sql.md b/mddocs/docs/en/connection/db_connection/teradata/sql.md similarity index 100% rename from mddocs/docs/connection/db_connection/teradata/sql.md rename to mddocs/docs/en/connection/db_connection/teradata/sql.md diff --git a/mddocs/docs/connection/db_connection/teradata/write.md b/mddocs/docs/en/connection/db_connection/teradata/write.md similarity index 100% rename from mddocs/docs/connection/db_connection/teradata/write.md rename to mddocs/docs/en/connection/db_connection/teradata/write.md diff --git a/mddocs/docs/connection/file_connection/ftp.md b/mddocs/docs/en/connection/file_connection/ftp.md similarity index 100% rename from mddocs/docs/connection/file_connection/ftp.md rename to mddocs/docs/en/connection/file_connection/ftp.md diff --git a/mddocs/docs/connection/file_connection/ftps.md b/mddocs/docs/en/connection/file_connection/ftps.md similarity index 100% rename from mddocs/docs/connection/file_connection/ftps.md rename to mddocs/docs/en/connection/file_connection/ftps.md diff --git a/mddocs/docs/connection/file_connection/hdfs/connection.md b/mddocs/docs/en/connection/file_connection/hdfs/connection.md similarity index 100% rename from mddocs/docs/connection/file_connection/hdfs/connection.md rename to mddocs/docs/en/connection/file_connection/hdfs/connection.md diff --git a/mddocs/docs/connection/file_connection/hdfs/index.md b/mddocs/docs/en/connection/file_connection/hdfs/index.md similarity index 100% rename from mddocs/docs/connection/file_connection/hdfs/index.md rename to mddocs/docs/en/connection/file_connection/hdfs/index.md diff --git a/mddocs/docs/connection/file_connection/hdfs/slots.md b/mddocs/docs/en/connection/file_connection/hdfs/slots.md similarity index 100% rename from mddocs/docs/connection/file_connection/hdfs/slots.md rename to mddocs/docs/en/connection/file_connection/hdfs/slots.md diff --git a/mddocs/docs/connection/file_connection/index.md b/mddocs/docs/en/connection/file_connection/index.md similarity index 100% rename from mddocs/docs/connection/file_connection/index.md rename to mddocs/docs/en/connection/file_connection/index.md diff --git a/mddocs/docs/connection/file_connection/s3.md b/mddocs/docs/en/connection/file_connection/s3.md similarity index 100% rename from mddocs/docs/connection/file_connection/s3.md rename to mddocs/docs/en/connection/file_connection/s3.md diff --git a/mddocs/docs/connection/file_connection/samba.md b/mddocs/docs/en/connection/file_connection/samba.md similarity index 100% rename from mddocs/docs/connection/file_connection/samba.md rename to mddocs/docs/en/connection/file_connection/samba.md diff --git a/mddocs/docs/connection/file_connection/sftp.md b/mddocs/docs/en/connection/file_connection/sftp.md similarity index 100% rename from mddocs/docs/connection/file_connection/sftp.md rename to mddocs/docs/en/connection/file_connection/sftp.md diff --git a/mddocs/docs/connection/file_connection/webdav.md b/mddocs/docs/en/connection/file_connection/webdav.md similarity index 100% rename from mddocs/docs/connection/file_connection/webdav.md rename to mddocs/docs/en/connection/file_connection/webdav.md diff --git a/mddocs/docs/connection/file_df_connection/base.md b/mddocs/docs/en/connection/file_df_connection/base.md similarity index 100% rename from mddocs/docs/connection/file_df_connection/base.md rename to mddocs/docs/en/connection/file_df_connection/base.md diff --git a/mddocs/docs/en/connection/file_df_connection/index.md b/mddocs/docs/en/connection/file_df_connection/index.md new file mode 100644 index 000000000..4b0829450 --- /dev/null +++ b/mddocs/docs/en/connection/file_df_connection/index.md @@ -0,0 +1,15 @@ +# File DataFrame Connections + +* [Spark LocalFS](spark_local_fs.md) +* [Spark HDFS](spark_hdfs/index.md) + * [Prerequisites](spark_hdfs/prerequisites.md) + * [Connection](spark_hdfs/connection.md) + * [Slots](spark_hdfs/slots.md) +* [Spark S3](spark_s3/index.md) + * [Prerequisites](spark_s3/prerequisites.md) + * [Connection](spark_s3/connection.md) + * [Troubleshooting](spark_s3/troubleshooting.md) + +# For developers + +* [Base interface](base.md) \ No newline at end of file diff --git a/mddocs/docs/connection/file_df_connection/spark_hdfs/connection.md b/mddocs/docs/en/connection/file_df_connection/spark_hdfs/connection.md similarity index 100% rename from mddocs/docs/connection/file_df_connection/spark_hdfs/connection.md rename to mddocs/docs/en/connection/file_df_connection/spark_hdfs/connection.md diff --git a/mddocs/docs/connection/file_df_connection/spark_hdfs/index.md b/mddocs/docs/en/connection/file_df_connection/spark_hdfs/index.md similarity index 100% rename from mddocs/docs/connection/file_df_connection/spark_hdfs/index.md rename to mddocs/docs/en/connection/file_df_connection/spark_hdfs/index.md diff --git a/mddocs/docs/connection/file_df_connection/spark_hdfs/prerequisites.md b/mddocs/docs/en/connection/file_df_connection/spark_hdfs/prerequisites.md similarity index 100% rename from mddocs/docs/connection/file_df_connection/spark_hdfs/prerequisites.md rename to mddocs/docs/en/connection/file_df_connection/spark_hdfs/prerequisites.md diff --git a/mddocs/docs/connection/file_df_connection/spark_hdfs/slots.md b/mddocs/docs/en/connection/file_df_connection/spark_hdfs/slots.md similarity index 100% rename from mddocs/docs/connection/file_df_connection/spark_hdfs/slots.md rename to mddocs/docs/en/connection/file_df_connection/spark_hdfs/slots.md diff --git a/mddocs/docs/connection/file_df_connection/spark_local_fs.md b/mddocs/docs/en/connection/file_df_connection/spark_local_fs.md similarity index 100% rename from mddocs/docs/connection/file_df_connection/spark_local_fs.md rename to mddocs/docs/en/connection/file_df_connection/spark_local_fs.md diff --git a/mddocs/docs/connection/file_df_connection/spark_s3/connection.md b/mddocs/docs/en/connection/file_df_connection/spark_s3/connection.md similarity index 100% rename from mddocs/docs/connection/file_df_connection/spark_s3/connection.md rename to mddocs/docs/en/connection/file_df_connection/spark_s3/connection.md diff --git a/mddocs/docs/connection/file_df_connection/spark_s3/index.md b/mddocs/docs/en/connection/file_df_connection/spark_s3/index.md similarity index 100% rename from mddocs/docs/connection/file_df_connection/spark_s3/index.md rename to mddocs/docs/en/connection/file_df_connection/spark_s3/index.md diff --git a/mddocs/docs/connection/file_df_connection/spark_s3/prerequisites.md b/mddocs/docs/en/connection/file_df_connection/spark_s3/prerequisites.md similarity index 100% rename from mddocs/docs/connection/file_df_connection/spark_s3/prerequisites.md rename to mddocs/docs/en/connection/file_df_connection/spark_s3/prerequisites.md diff --git a/mddocs/docs/connection/file_df_connection/spark_s3/troubleshooting.md b/mddocs/docs/en/connection/file_df_connection/spark_s3/troubleshooting.md similarity index 100% rename from mddocs/docs/connection/file_df_connection/spark_s3/troubleshooting.md rename to mddocs/docs/en/connection/file_df_connection/spark_s3/troubleshooting.md diff --git a/mddocs/docs/en/connection/index.md b/mddocs/docs/en/connection/index.md new file mode 100644 index 000000000..8f5eef7d7 --- /dev/null +++ b/mddocs/docs/en/connection/index.md @@ -0,0 +1,34 @@ +# Connection + + DB Connection + +* [DB Connections](db_connection/index.md) + * [Clickhouse](db_connection/clickhouse/index.md) + * [Greenplum](db_connection/greenplum/index.md) + * [Kafka](db_connection/kafka/index.md) + * [Hive](db_connection/hive/index.md) + * [MongoDB](db_connection/mongodb/index.md) + * [MSSQL](db_connection/mssql/index.md) + * [MySQL](db_connection/mysql/index.md) + * [Oracle](db_connection/oracle/index.md) + * [Postgres](db_connection/postgres/index.md) + * [Teradata](db_connection/teradata/index.md) + + File Connection + +* [File Connections](file_connection/index.md) + * [FTP](file_connection/ftp.md) + * [FTPS](file_connection/ftps.md) + * [HDFS](file_connection/hdfs/index.md) + * [Samba](file_connection/samba.md) + * [SFTP](file_connection/sftp.md) + * [S3](file_connection/s3.md) + * [Webdav](file_connection/webdav.md) + + File DataFrame Connection + +* [File DataFrame Connections](file_df_connection/index.md) + * [Spark LocalFS](file_df_connection/spark_local_fs.md) + * [Spark HDFS](file_df_connection/spark_hdfs/index.md) + * [Spark S3](file_df_connection/spark_s3/index.md) + * [Base interface](file_df_connection/base.md) \ No newline at end of file diff --git a/mddocs/docs/_build/markdown/contributing.md b/mddocs/docs/en/contributing.md similarity index 100% rename from mddocs/docs/_build/markdown/contributing.md rename to mddocs/docs/en/contributing.md diff --git a/mddocs/docs/db/index.md b/mddocs/docs/en/db_/index.md similarity index 85% rename from mddocs/docs/db/index.md rename to mddocs/docs/en/db_/index.md index baf632bf9..494a026b1 100644 --- a/mddocs/docs/db/index.md +++ b/mddocs/docs/en/db_/index.md @@ -1,4 +1,6 @@ -(db-root)= +# DB + +lslsv ```{toctree} :caption: DB classes diff --git a/mddocs/docs/db/db_reader.md b/mddocs/docs/en/db_/reader.md similarity index 95% rename from mddocs/docs/db/db_reader.md rename to mddocs/docs/en/db_/reader.md index 5368093b1..912e3d127 100644 --- a/mddocs/docs/db/db_reader.md +++ b/mddocs/docs/en/db_/reader.md @@ -1,7 +1,8 @@ -(db-reader)= - # DB Reader + +ыыыыы + ```{eval-rst} .. currentmodule:: onetl.db.db_reader.db_reader ``` diff --git a/mddocs/docs/db/db_writer.md b/mddocs/docs/en/db_/writer.md similarity index 94% rename from mddocs/docs/db/db_writer.md rename to mddocs/docs/en/db_/writer.md index d2d789c40..ea66d2834 100644 --- a/mddocs/docs/db/db_writer.md +++ b/mddocs/docs/en/db_/writer.md @@ -1,7 +1,7 @@ -(db-writer)= - # DB Writer + +бубуб ```{eval-rst} .. currentmodule:: onetl.db.db_writer.db_writer ``` diff --git a/mddocs/docs/file/file_downloader/file_downloader.md b/mddocs/docs/en/file/file_downloader/file_downloader.md similarity index 100% rename from mddocs/docs/file/file_downloader/file_downloader.md rename to mddocs/docs/en/file/file_downloader/file_downloader.md diff --git a/mddocs/docs/file/file_downloader/index.md b/mddocs/docs/en/file/file_downloader/index.md similarity index 100% rename from mddocs/docs/file/file_downloader/index.md rename to mddocs/docs/en/file/file_downloader/index.md diff --git a/mddocs/docs/file/file_downloader/options.md b/mddocs/docs/en/file/file_downloader/options.md similarity index 100% rename from mddocs/docs/file/file_downloader/options.md rename to mddocs/docs/en/file/file_downloader/options.md diff --git a/mddocs/docs/file/file_downloader/result.md b/mddocs/docs/en/file/file_downloader/result.md similarity index 100% rename from mddocs/docs/file/file_downloader/result.md rename to mddocs/docs/en/file/file_downloader/result.md diff --git a/mddocs/docs/file/file_filters/base.md b/mddocs/docs/en/file/file_filters/base.md similarity index 100% rename from mddocs/docs/file/file_filters/base.md rename to mddocs/docs/en/file/file_filters/base.md diff --git a/mddocs/docs/file/file_filters/exclude_dir.md b/mddocs/docs/en/file/file_filters/exclude_dir.md similarity index 100% rename from mddocs/docs/file/file_filters/exclude_dir.md rename to mddocs/docs/en/file/file_filters/exclude_dir.md diff --git a/mddocs/docs/file/file_filters/file_filter.md b/mddocs/docs/en/file/file_filters/file_filter.md similarity index 100% rename from mddocs/docs/file/file_filters/file_filter.md rename to mddocs/docs/en/file/file_filters/file_filter.md diff --git a/mddocs/docs/file/file_filters/file_mtime_filter.md b/mddocs/docs/en/file/file_filters/file_mtime_filter.md similarity index 100% rename from mddocs/docs/file/file_filters/file_mtime_filter.md rename to mddocs/docs/en/file/file_filters/file_mtime_filter.md diff --git a/mddocs/docs/file/file_filters/file_size_filter.md b/mddocs/docs/en/file/file_filters/file_size_filter.md similarity index 100% rename from mddocs/docs/file/file_filters/file_size_filter.md rename to mddocs/docs/en/file/file_filters/file_size_filter.md diff --git a/mddocs/docs/file/file_filters/glob.md b/mddocs/docs/en/file/file_filters/glob.md similarity index 100% rename from mddocs/docs/file/file_filters/glob.md rename to mddocs/docs/en/file/file_filters/glob.md diff --git a/mddocs/docs/file/file_filters/index.md b/mddocs/docs/en/file/file_filters/index.md similarity index 100% rename from mddocs/docs/file/file_filters/index.md rename to mddocs/docs/en/file/file_filters/index.md diff --git a/mddocs/docs/file/file_filters/match_all_filters.md b/mddocs/docs/en/file/file_filters/match_all_filters.md similarity index 100% rename from mddocs/docs/file/file_filters/match_all_filters.md rename to mddocs/docs/en/file/file_filters/match_all_filters.md diff --git a/mddocs/docs/file/file_filters/regexp.md b/mddocs/docs/en/file/file_filters/regexp.md similarity index 100% rename from mddocs/docs/file/file_filters/regexp.md rename to mddocs/docs/en/file/file_filters/regexp.md diff --git a/mddocs/docs/file/file_limits/base.md b/mddocs/docs/en/file/file_limits/base.md similarity index 100% rename from mddocs/docs/file/file_limits/base.md rename to mddocs/docs/en/file/file_limits/base.md diff --git a/mddocs/docs/file/file_limits/file_limit.md b/mddocs/docs/en/file/file_limits/file_limit.md similarity index 100% rename from mddocs/docs/file/file_limits/file_limit.md rename to mddocs/docs/en/file/file_limits/file_limit.md diff --git a/mddocs/docs/file/file_limits/index.md b/mddocs/docs/en/file/file_limits/index.md similarity index 100% rename from mddocs/docs/file/file_limits/index.md rename to mddocs/docs/en/file/file_limits/index.md diff --git a/mddocs/docs/file/file_limits/limits_reached.md b/mddocs/docs/en/file/file_limits/limits_reached.md similarity index 100% rename from mddocs/docs/file/file_limits/limits_reached.md rename to mddocs/docs/en/file/file_limits/limits_reached.md diff --git a/mddocs/docs/file/file_limits/limits_stop_at.md b/mddocs/docs/en/file/file_limits/limits_stop_at.md similarity index 100% rename from mddocs/docs/file/file_limits/limits_stop_at.md rename to mddocs/docs/en/file/file_limits/limits_stop_at.md diff --git a/mddocs/docs/file/file_limits/max_files_count.md b/mddocs/docs/en/file/file_limits/max_files_count.md similarity index 100% rename from mddocs/docs/file/file_limits/max_files_count.md rename to mddocs/docs/en/file/file_limits/max_files_count.md diff --git a/mddocs/docs/file/file_limits/reset_limits.md b/mddocs/docs/en/file/file_limits/reset_limits.md similarity index 100% rename from mddocs/docs/file/file_limits/reset_limits.md rename to mddocs/docs/en/file/file_limits/reset_limits.md diff --git a/mddocs/docs/file/file_limits/total_files_size.md b/mddocs/docs/en/file/file_limits/total_files_size.md similarity index 100% rename from mddocs/docs/file/file_limits/total_files_size.md rename to mddocs/docs/en/file/file_limits/total_files_size.md diff --git a/mddocs/docs/file/file_mover/file_mover.md b/mddocs/docs/en/file/file_mover/file_mover.md similarity index 100% rename from mddocs/docs/file/file_mover/file_mover.md rename to mddocs/docs/en/file/file_mover/file_mover.md diff --git a/mddocs/docs/file/file_mover/index.md b/mddocs/docs/en/file/file_mover/index.md similarity index 100% rename from mddocs/docs/file/file_mover/index.md rename to mddocs/docs/en/file/file_mover/index.md diff --git a/mddocs/docs/file/file_mover/options.md b/mddocs/docs/en/file/file_mover/options.md similarity index 100% rename from mddocs/docs/file/file_mover/options.md rename to mddocs/docs/en/file/file_mover/options.md diff --git a/mddocs/docs/file/file_mover/result.md b/mddocs/docs/en/file/file_mover/result.md similarity index 100% rename from mddocs/docs/file/file_mover/result.md rename to mddocs/docs/en/file/file_mover/result.md diff --git a/mddocs/docs/file/file_uploader/file_uploader.md b/mddocs/docs/en/file/file_uploader/file_uploader.md similarity index 100% rename from mddocs/docs/file/file_uploader/file_uploader.md rename to mddocs/docs/en/file/file_uploader/file_uploader.md diff --git a/mddocs/docs/file/file_uploader/index.md b/mddocs/docs/en/file/file_uploader/index.md similarity index 100% rename from mddocs/docs/file/file_uploader/index.md rename to mddocs/docs/en/file/file_uploader/index.md diff --git a/mddocs/docs/file/file_uploader/options.md b/mddocs/docs/en/file/file_uploader/options.md similarity index 100% rename from mddocs/docs/file/file_uploader/options.md rename to mddocs/docs/en/file/file_uploader/options.md diff --git a/mddocs/docs/file/file_uploader/result.md b/mddocs/docs/en/file/file_uploader/result.md similarity index 100% rename from mddocs/docs/file/file_uploader/result.md rename to mddocs/docs/en/file/file_uploader/result.md diff --git a/mddocs/docs/file/index.md b/mddocs/docs/en/file/index.md similarity index 100% rename from mddocs/docs/file/index.md rename to mddocs/docs/en/file/index.md diff --git a/mddocs/docs/file_df/file_df_reader/file_df_reader.md b/mddocs/docs/en/file_df/file_df_reader/file_df_reader.md similarity index 100% rename from mddocs/docs/file_df/file_df_reader/file_df_reader.md rename to mddocs/docs/en/file_df/file_df_reader/file_df_reader.md diff --git a/mddocs/docs/file_df/file_df_reader/index.md b/mddocs/docs/en/file_df/file_df_reader/index.md similarity index 100% rename from mddocs/docs/file_df/file_df_reader/index.md rename to mddocs/docs/en/file_df/file_df_reader/index.md diff --git a/mddocs/docs/file_df/file_df_reader/options.md b/mddocs/docs/en/file_df/file_df_reader/options.md similarity index 100% rename from mddocs/docs/file_df/file_df_reader/options.md rename to mddocs/docs/en/file_df/file_df_reader/options.md diff --git a/mddocs/docs/file_df/file_df_writer/file_df_writer.md b/mddocs/docs/en/file_df/file_df_writer/file_df_writer.md similarity index 100% rename from mddocs/docs/file_df/file_df_writer/file_df_writer.md rename to mddocs/docs/en/file_df/file_df_writer/file_df_writer.md diff --git a/mddocs/docs/file_df/file_df_writer/index.md b/mddocs/docs/en/file_df/file_df_writer/index.md similarity index 100% rename from mddocs/docs/file_df/file_df_writer/index.md rename to mddocs/docs/en/file_df/file_df_writer/index.md diff --git a/mddocs/docs/file_df/file_df_writer/options.md b/mddocs/docs/en/file_df/file_df_writer/options.md similarity index 100% rename from mddocs/docs/file_df/file_df_writer/options.md rename to mddocs/docs/en/file_df/file_df_writer/options.md diff --git a/mddocs/docs/file_df/file_formats/avro.md b/mddocs/docs/en/file_df/file_formats/avro.md similarity index 100% rename from mddocs/docs/file_df/file_formats/avro.md rename to mddocs/docs/en/file_df/file_formats/avro.md diff --git a/mddocs/docs/file_df/file_formats/base.md b/mddocs/docs/en/file_df/file_formats/base.md similarity index 100% rename from mddocs/docs/file_df/file_formats/base.md rename to mddocs/docs/en/file_df/file_formats/base.md diff --git a/mddocs/docs/file_df/file_formats/csv.md b/mddocs/docs/en/file_df/file_formats/csv.md similarity index 100% rename from mddocs/docs/file_df/file_formats/csv.md rename to mddocs/docs/en/file_df/file_formats/csv.md diff --git a/mddocs/docs/file_df/file_formats/excel.md b/mddocs/docs/en/file_df/file_formats/excel.md similarity index 100% rename from mddocs/docs/file_df/file_formats/excel.md rename to mddocs/docs/en/file_df/file_formats/excel.md diff --git a/mddocs/docs/file_df/file_formats/index.md b/mddocs/docs/en/file_df/file_formats/index.md similarity index 100% rename from mddocs/docs/file_df/file_formats/index.md rename to mddocs/docs/en/file_df/file_formats/index.md diff --git a/mddocs/docs/file_df/file_formats/json.md b/mddocs/docs/en/file_df/file_formats/json.md similarity index 100% rename from mddocs/docs/file_df/file_formats/json.md rename to mddocs/docs/en/file_df/file_formats/json.md diff --git a/mddocs/docs/file_df/file_formats/jsonline.md b/mddocs/docs/en/file_df/file_formats/jsonline.md similarity index 100% rename from mddocs/docs/file_df/file_formats/jsonline.md rename to mddocs/docs/en/file_df/file_formats/jsonline.md diff --git a/mddocs/docs/file_df/file_formats/orc.md b/mddocs/docs/en/file_df/file_formats/orc.md similarity index 100% rename from mddocs/docs/file_df/file_formats/orc.md rename to mddocs/docs/en/file_df/file_formats/orc.md diff --git a/mddocs/docs/file_df/file_formats/parquet.md b/mddocs/docs/en/file_df/file_formats/parquet.md similarity index 100% rename from mddocs/docs/file_df/file_formats/parquet.md rename to mddocs/docs/en/file_df/file_formats/parquet.md diff --git a/mddocs/docs/file_df/file_formats/xml.md b/mddocs/docs/en/file_df/file_formats/xml.md similarity index 100% rename from mddocs/docs/file_df/file_formats/xml.md rename to mddocs/docs/en/file_df/file_formats/xml.md diff --git a/mddocs/docs/file_df/index.md b/mddocs/docs/en/file_df/index.md similarity index 100% rename from mddocs/docs/file_df/index.md rename to mddocs/docs/en/file_df/index.md diff --git a/mddocs/docs/hooks/design.md b/mddocs/docs/en/hooks/design.md similarity index 100% rename from mddocs/docs/hooks/design.md rename to mddocs/docs/en/hooks/design.md diff --git a/mddocs/docs/hooks/global_state.md b/mddocs/docs/en/hooks/global_state.md similarity index 100% rename from mddocs/docs/hooks/global_state.md rename to mddocs/docs/en/hooks/global_state.md diff --git a/mddocs/docs/hooks/hook.md b/mddocs/docs/en/hooks/hook.md similarity index 100% rename from mddocs/docs/hooks/hook.md rename to mddocs/docs/en/hooks/hook.md diff --git a/mddocs/docs/hooks/index.md b/mddocs/docs/en/hooks/index.md similarity index 81% rename from mddocs/docs/hooks/index.md rename to mddocs/docs/en/hooks/index.md index 9b489afe6..6d8218027 100644 --- a/mddocs/docs/hooks/index.md +++ b/mddocs/docs/en/hooks/index.md @@ -1,9 +1,7 @@ -(hooks)= - # Hooks -:::{versionadded} 0.6.0 -::: +:octicons-versions-16: **version added 0.6.0** + ```{toctree} :caption: Hooks diff --git a/mddocs/docs/hooks/slot.md b/mddocs/docs/en/hooks/slot.md similarity index 100% rename from mddocs/docs/hooks/slot.md rename to mddocs/docs/en/hooks/slot.md diff --git a/mddocs/docs/hooks/support_hooks.md b/mddocs/docs/en/hooks/support_hooks.md similarity index 100% rename from mddocs/docs/hooks/support_hooks.md rename to mddocs/docs/en/hooks/support_hooks.md diff --git a/mddocs/docs/hwm_store/index.md b/mddocs/docs/en/hwm_store/index.md similarity index 100% rename from mddocs/docs/hwm_store/index.md rename to mddocs/docs/en/hwm_store/index.md diff --git a/mddocs/docs/hwm_store/yaml_hwm_store.md b/mddocs/docs/en/hwm_store/yaml_hwm_store.md similarity index 100% rename from mddocs/docs/hwm_store/yaml_hwm_store.md rename to mddocs/docs/en/hwm_store/yaml_hwm_store.md diff --git a/mddocs/docs/en/index.md b/mddocs/docs/en/index.md new file mode 100644 index 000000000..d5db83211 --- /dev/null +++ b/mddocs/docs/en/index.md @@ -0,0 +1,18 @@ +# onETL + +{{ repo_status_badge }} +{{ pypi_release_bage }} +{{ pypi_license_bage }} +{{ pypi_pyversion_bage }} +{{ pypi_downloads_bage }} + +{{ docs_status_badge }} +{{ ci_status_badge }} +{{ precommit_badge }} + + +{{ onetl_logo_wide }} + +----8<---- +../mddocs/docs/en/snippet_0.md +----8<---- \ No newline at end of file diff --git a/mddocs/docs/install/files.md b/mddocs/docs/en/install/files.md similarity index 100% rename from mddocs/docs/install/files.md rename to mddocs/docs/en/install/files.md diff --git a/mddocs/docs/install/full.md b/mddocs/docs/en/install/full.md similarity index 100% rename from mddocs/docs/install/full.md rename to mddocs/docs/en/install/full.md diff --git a/mddocs/docs/install/index.md b/mddocs/docs/en/install/index.md similarity index 100% rename from mddocs/docs/install/index.md rename to mddocs/docs/en/install/index.md diff --git a/mddocs/docs/install/kerberos.md b/mddocs/docs/en/install/kerberos.md similarity index 100% rename from mddocs/docs/install/kerberos.md rename to mddocs/docs/en/install/kerberos.md diff --git a/mddocs/docs/install/spark.md b/mddocs/docs/en/install/spark.md similarity index 100% rename from mddocs/docs/install/spark.md rename to mddocs/docs/en/install/spark.md diff --git a/mddocs/docs/logging.md b/mddocs/docs/en/logging.md similarity index 95% rename from mddocs/docs/logging.md rename to mddocs/docs/en/logging.md index dae7a9d73..2a36b0a75 100644 --- a/mddocs/docs/logging.md +++ b/mddocs/docs/en/logging.md @@ -1,14 +1,10 @@ -(logging)= - # Logging Logging is quite important to understand what's going on under the hood of onETL. -Default logging level for Python interpreters is `WARNING`, -but most of onETL logs are in `INFO` level, so users usually don't see much. +Default logging level for Python interpreters is `WARNING`, but most of onETL logs are in `INFO` level, so users usually don't see much. -To change logging level, there is a function {obj}`setup_logging ` -which should be called at the top of the script: +To change logging level, there is a function [setup_logging](../logging/#onetl.log.setup_logging) which should be called at the top of the script: ```python from onetl.log import setup_logging @@ -22,10 +18,9 @@ setup_logging() This changes both log level and log formatting to something like this: -```{eval-rst} -.. dropdown:: See logs +??? "See logs" - .. code:: text + ```text 2024-04-12 10:12:10,834 [INFO ] MainThread: |onETL| Using IncrementalStrategy as a strategy 2024-04-12 10:12:10,835 [INFO ] MainThread: =================================== DBReader.run() starts =================================== @@ -137,7 +132,7 @@ This changes both log level and log formatting to something like this: 2024-04-12 11:06:07,397 [INFO ] MainThread: value = datetime.datetime(2024, 4, 12, 13, 12, 2, 123000), 2024-04-12 11:06:07,397 [INFO ] MainThread: ) 2024-04-12 11:06:07,495 [INFO ] MainThread: |IncrementalStrategy| HWM has been saved -``` + ``` Each step performed by onETL is extensively logged, which should help with debugging. @@ -154,18 +149,9 @@ setup_logging(level="DEBUG", enable_clients=True) This also changes log level for all underlying Python libraries, e.g. showing each HTTP request being made, and so on. -```{eval-rst} -.. currentmodule:: onetl.log -``` - -```{eval-rst} -.. autofunction:: setup_logging -``` - -```{eval-rst} -.. autofunction:: setup_clients_logging -``` - -```{eval-rst} -.. autofunction:: set_default_logging_format -``` +::: onetl.log + options: + members: + - setup_logging + - setup_clients_logging + - set_default_logging_format diff --git a/mddocs/docs/plugins.md b/mddocs/docs/en/plugins.md similarity index 93% rename from mddocs/docs/plugins.md rename to mddocs/docs/en/plugins.md index a7904b076..d00730747 100644 --- a/mddocs/docs/plugins.md +++ b/mddocs/docs/en/plugins.md @@ -1,22 +1,20 @@ -(plugins)= - # Plugins -:::{versionadded} 0.6.0 -::: +:octicons-versions-16: **version added 0.6.0** + ## What are plugins? ### Terms -- `Plugin` - some Python package which implements some extra functionality for onETL, like {ref}`hooks` +- `Plugin` - some Python package which implements some extra functionality for onETL, like [hooks](../hooks/) - `Plugin autoimport` - onETL behavior which allows to automatically import this package if it contains proper metadata (`entry_points`) ### Features Plugins mechanism allows to: -- Automatically register {ref}`hooks` which can alter onETL behavior +- Automatically register [hooks](../hooks/) which can alter onETL behavior - Automatically register new classes, like HWM type, HWM stores and so on ### Limitations @@ -74,12 +72,11 @@ from some_plugin.module.internals import my_function ``` If specific module/class/function uses some registration capabilities of onETL, -like {ref}`hook-decorator`, it will be executed during this import. +like [hooks](../hooks/), it will be executed during this import. ## How to enable/disable plugins? -:::{versionadded} 0.7.0 -::: +:octicons-versions-16: **version added 0.7.0** ### Disable/enable all plugins diff --git a/mddocs/docs/en/quickstart.md b/mddocs/docs/en/quickstart.md new file mode 100644 index 000000000..39201e990 --- /dev/null +++ b/mddocs/docs/en/quickstart.md @@ -0,0 +1,540 @@ +# onETL + +{{ repo_status_badge }} +{{ pypi_release_bage }} +{{ pypi_license_bage }} +{{ pypi_pyversion_bage }} +{{ pypi_downloads_bage }} + +{{ docs_status_badge }} +{{ ci_status_badge }} +{{ precommit_badge }} + + +{{ onetl_logo_wide }} + +----8<---- +../mddocs/docs/en/snippet_0.md +----8<---- + + + +## Documentation + +See at [ReadTheDocs](https://onetl.readthedocs.io/en/latest/) + +## How to install + + + +### Minimal installation + + + +Base `onetl` package contains: + +- `DBReader`, `DBWriter` and related classes +- `FileDownloader`, `FileUploader`, `FileMover` and related classes, like file filters & limits +- `FileDFReader`, `FileDFWriter` and related classes, like file formats +- Read Strategies & HWM classes +- Plugins support + +It can be installed via: + +```bash +pip install onetl +``` + +!!! warning + + This method does NOT include any connections. + + This method is recommended for use in third-party libraries which require for ``onetl`` to be installed, + but do not use its connection classes. + + +### With DB and FileDF connections + + + +All DB connection classes (`Clickhouse`, `Greenplum`, `Hive` and others) +and all FileDF connection classes (`SparkHDFS`, `SparkLocalFS`, `SparkS3`) +require Spark to be installed. + + + +Firstly, you should install JDK. The exact installation instruction depends on your OS, here are some examples: + +```bash +yum install java-1.8.0-openjdk-devel # CentOS 7 | Spark 2 +dnf install java-11-openjdk-devel # CentOS 8 | Spark 3 +apt-get install openjdk-11-jdk # Debian-based | Spark 3 +``` + + + +#### Compatibility matrix + +| Spark | Python | Java | Scala | +| --------------------------------------------------------- | ---------- | ---------- | ----- | +| [2.3.x](https://spark.apache.org/docs/2.3.1/#downloading) | 3.7 only | 8 only | 2.11 | +| [2.4.x](https://spark.apache.org/docs/2.4.8/#downloading) | 3.7 only | 8 only | 2.11 | +| [3.2.x](https://spark.apache.org/docs/3.2.4/#downloading) | 3.7 - 3.10 | 8u201 - 11 | 2.12 | +| [3.3.x](https://spark.apache.org/docs/3.3.4/#downloading) | 3.7 - 3.12 | 8u201 - 17 | 2.12 | +| [3.4.x](https://spark.apache.org/docs/3.4.4/#downloading) | 3.7 - 3.12 | 8u362 - 20 | 2.12 | +| [3.5.x](https://spark.apache.org/docs/3.5.5/#downloading) | 3.8 - 3.13 | 8u371 - 20 | 2.12 | + + + +Then you should install PySpark via passing `spark` to `extras`: + +```bash +pip install onetl[spark] # install latest PySpark +``` + +or install PySpark explicitly: + +```bash +pip install onetl pyspark==3.5.5 # install a specific PySpark version +``` + +or inject PySpark to `sys.path` in some other way BEFORE creating a class instance. +**Otherwise connection object cannot be created.** + +### With File connections + + + +All File (but not *FileDF*) connection classes (`FTP`, `SFTP`, `HDFS` and so on) requires specific Python clients to be installed. + +Each client can be installed explicitly by passing connector name (in lowercase) to `extras`: + +```bash +pip install onetl[ftp] # specific connector +pip install onetl[ftp,ftps,sftp,hdfs,s3,webdav,samba] # multiple connectors +``` + +To install all file connectors at once you can pass `files` to `extras`: + +```bash +pip install onetl[files] +``` + +**Otherwise class import will fail.** + +### With Kerberos support + + + +Most of Hadoop instances set up with Kerberos support, +so some connections require additional setup to work properly. + +- `HDFS` + Uses [requests-kerberos](https://pypi.org/project/requests-kerberos/) and + [GSSApi](https://pypi.org/project/gssapi/) for authentication. + It also uses `kinit` executable to generate Kerberos ticket. +- `Hive` and `SparkHDFS` + require Kerberos ticket to exist before creating Spark session. + +So you need to install OS packages with: + +- `krb5` libs +- Headers for `krb5` +- `gcc` or other compiler for C sources + +The exact installation instruction depends on your OS, here are some examples: + +```bash +apt install libkrb5-dev krb5-user gcc # Debian-based +dnf install krb5-devel krb5-libs krb5-workstation gcc # CentOS, OracleLinux +``` + +Also you should pass `kerberos` to `extras` to install required Python packages: + +```bash +pip install onetl[kerberos] +``` + +### Full bundle + + + +To install all connectors and dependencies, you can pass `all` into `extras`: + +```bash +pip install onetl[all] + +# this is just the same as +pip install onetl[spark,files,kerberos] +``` + +!!! warning + + This method consumes a lot of disk space, and requires for Java & Kerberos libraries to be installed into your OS. + + + + +## Quick start + +### MSSQL → Hive + +Read data from MSSQL, transform & write to Hive. + +```bash +# install onETL and PySpark +pip install onetl[spark] +``` + +```python +# Import pyspark to initialize the SparkSession +from pyspark.sql import SparkSession + +# import function to setup onETL logging +from onetl.log import setup_logging + +# Import required connections +from onetl.connection import MSSQL, Hive + +# Import onETL classes to read & write data +from onetl.db import DBReader, DBWriter + +# change logging level to INFO, and set up default logging format and handler +setup_logging() + +# Initialize new SparkSession with MSSQL driver loaded +maven_packages = MSSQL.get_packages() +spark = ( + SparkSession.builder.appName("spark_app_onetl_demo") + .config("spark.jars.packages", ",".join(maven_packages)) + .enableHiveSupport() # for Hive + .getOrCreate() +) + +# Initialize MSSQL connection and check if database is accessible +mssql = MSSQL( + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, + # These options are passed to MSSQL JDBC Driver: + extra={"applicationIntent": "ReadOnly"}, +).check() + +# >>> INFO:|MSSQL| Connection is available + +# Initialize DBReader +reader = DBReader( + connection=mssql, + source="dbo.demo_table", + columns=["on", "etl"], + # Set some MSSQL read options: + options=MSSQL.ReadOptions(fetchsize=10000), +) + +# checks that there is data in the table, otherwise raises exception +reader.raise_if_no_data() + +# Read data to DataFrame +df = reader.run() +df.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) + +# Apply any PySpark transformations +from pyspark.sql.functions import lit + +df_to_write = df.withColumn("engine", lit("onetl")) +df_to_write.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) +# |-- engine: string (nullable = false) + +# Initialize Hive connection +hive = Hive(cluster="rnd-dwh", spark=spark) + +# Initialize DBWriter +db_writer = DBWriter( + connection=hive, + target="dl_sb.demo_table", + # Set some Hive write options: + options=Hive.WriteOptions(if_exists="replace_entire_table"), +) + +# Write data from DataFrame to Hive +db_writer.run(df_to_write) + +# Success! +``` + +### SFTP → HDFS + +Download files from SFTP & upload them to HDFS. + +```bash +# install onETL with SFTP and HDFS clients, and Kerberos support +pip install onetl[hdfs,sftp,kerberos] +``` + +```python +# import function to setup onETL logging +from onetl.log import setup_logging + +# Import required connections +from onetl.connection import SFTP, HDFS + +# Import onETL classes to download & upload files +from onetl.file import FileDownloader, FileUploader + +# import filter & limit classes +from onetl.file.filter import Glob, ExcludeDir +from onetl.file.limit import MaxFilesCount + +# change logging level to INFO, and set up default logging format and handler +setup_logging() + +# Initialize SFTP connection and check it +sftp = SFTP( + host="sftp.test.com", + user="someuser", + password="somepassword", +).check() + +# >>> INFO:|SFTP| Connection is available + +# Initialize downloader +file_downloader = FileDownloader( + connection=sftp, + source_path="/remote/tests/Report", # path on SFTP + local_path="/local/onetl/Report", # local fs path + filters=[ + # download only files matching the glob + Glob("*.csv"), + # exclude files from this directory + ExcludeDir("/remote/tests/Report/exclude_dir/"), + ], + limits=[ + # download max 1000 files per run + MaxFilesCount(1000), + ], + options=FileDownloader.Options( + # delete files from SFTP after successful download + delete_source=True, + # mark file as failed if it already exist in local_path + if_exists="error", + ), +) + +# Download files to local filesystem +download_result = downloader.run() + +# Method run returns a DownloadResult object, +# which contains collection of downloaded files, divided to 4 categories +download_result + +# DownloadResult( +# successful=[ +# LocalPath('/local/onetl/Report/file_1.json'), +# LocalPath('/local/onetl/Report/file_2.json'), +# ], +# failed=[FailedRemoteFile('/remote/onetl/Report/file_3.json')], +# ignored=[RemoteFile('/remote/onetl/Report/file_4.json')], +# missing=[], +# ) + +# Raise exception if there are failed files, or there were no files in the remote filesystem +download_result.raise_if_failed() or download_result.raise_if_empty() + +# Do any kind of magic with files: rename files, remove header for csv files, ... +renamed_files = my_rename_function(download_result.success) + +# function removed "_" from file names +# [ +# LocalPath('/home/onetl/Report/file1.json'), +# LocalPath('/home/onetl/Report/file2.json'), +# ] + +# Initialize HDFS connection +hdfs = HDFS( + host="my.name.node", + user="someuser", + password="somepassword", # or keytab +) + +# Initialize uploader +file_uploader = FileUploader( + connection=hdfs, + target_path="/user/onetl/Report/", # hdfs path +) + +# Upload files from local fs to HDFS +upload_result = file_uploader.run(renamed_files) + +# Method run returns a UploadResult object, +# which contains collection of uploaded files, divided to 4 categories +upload_result + +# UploadResult( +# successful=[RemoteFile('/user/onetl/Report/file1.json')], +# failed=[FailedLocalFile('/local/onetl/Report/file2.json')], +# ignored=[], +# missing=[], +# ) + +# Raise exception if there are failed files, or there were no files in the local filesystem, or some input file is missing +upload_result.raise_if_failed() or upload_result.raise_if_empty() or upload_result.raise_if_missing() + +# Success! +``` + +### S3 → Postgres + +Read files directly from S3 path, convert them to dataframe, transform it and then write to a database. + +```bash +# install onETL and PySpark +pip install onetl[spark] +``` + +```python +# Import pyspark to initialize the SparkSession +from pyspark.sql import SparkSession + +# import function to setup onETL logging +from onetl.log import setup_logging + +# Import required connections +from onetl.connection import Postgres, SparkS3 + +# Import onETL classes to read files +from onetl.file import FileDFReader +from onetl.file.format import CSV + +# Import onETL classes to write data +from onetl.db import DBWriter + +# change logging level to INFO, and set up default logging format and handler +setup_logging() + +# Initialize new SparkSession with Hadoop AWS libraries and Postgres driver loaded +maven_packages = SparkS3.get_packages(spark_version="3.5.5") | Postgres.get_packages() +exclude_packages = SparkS3.get_exclude_packages() +spark = ( + SparkSession.builder.appName("spark_app_onetl_demo") + .config("spark.jars.packages", ",".join(maven_packages)) + .config("spark.jars.excludes", ",".join(exclude_packages)) + .getOrCreate() +) + +# Initialize S3 connection and check it +spark_s3 = SparkS3( + host="s3.test.com", + protocol="https", + bucket="my-bucket", + access_key="somekey", + secret_key="somesecret", + # Access bucket as s3.test.com/my-bucket + extra={"path.style.access": True}, + spark=spark, +).check() + +# >>> INFO:|SparkS3| Connection is available + +# Describe file format and parsing options +csv = CSV( + delimiter=";", + header=True, + encoding="utf-8", +) + +# Describe DataFrame schema of files +from pyspark.sql.types import ( + DateType, + DoubleType, + IntegerType, + StringType, + StructField, + StructType, + TimestampType, +) + +df_schema = StructType( + [ + StructField("id", IntegerType()), + StructField("phone_number", StringType()), + StructField("region", StringType()), + StructField("birth_date", DateType()), + StructField("registered_at", TimestampType()), + StructField("account_balance", DoubleType()), + ], +) + +# Initialize file df reader +reader = FileDFReader( + connection=spark_s3, + source_path="/remote/tests/Report", # path on S3 there *.csv files are located + format=csv, # file format with specific parsing options + df_schema=df_schema, # columns & types +) + +# Read files directly from S3 as Spark DataFrame +df = reader.run() + +# Check that DataFrame schema is same as expected +df.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) + +# Apply any PySpark transformations +from pyspark.sql.functions import lit + +df_to_write = df.withColumn("engine", lit("onetl")) +df_to_write.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) +# |-- engine: string (nullable = false) + +# Initialize Postgres connection +postgres = Postgres( + host="192.169.11.23", + user="onetl", + password="somepassword", + database="mydb", + spark=spark, +) + +# Initialize DBWriter +db_writer = DBWriter( + connection=postgres, + # write to specific table + target="public.my_table", + # with some writing options + options=Postgres.WriteOptions(if_exists="append"), +) + +# Write DataFrame to Postgres table +db_writer.run(df_to_write) + +# Success! +``` diff --git a/mddocs/ru/SECURITY.md b/mddocs/docs/en/security.md similarity index 72% rename from mddocs/ru/SECURITY.md rename to mddocs/docs/en/security.md index 5d9dce52b..8ca24e077 100644 --- a/mddocs/ru/SECURITY.md +++ b/mddocs/docs/en/security.md @@ -14,12 +14,12 @@ 1. No binaries in repository 2. No passwords, keys, access tokens in source code -3. No "Critical" and/or "High" vulnerabilities in contributed source code +3. No “Critical” and/or “High” vulnerabilities in contributed source code ## Vulnerability reports -Please, use email [mailto:onetools@mts.ru](mailto:onetools@mts.ru) for reporting security issues or anything that can cause any consequences for security. +Please, use email [mailto:onetools@mts.ru](mailto:onetools@mts.ru) for reporting security issues or anything that can cause any consequences for security. Please avoid any public disclosure (including registering issues) at least until it is fixed. -Thank you in advance for understanding. +Thank you in advance for understanding. \ No newline at end of file diff --git a/mddocs/docs/en/snippet_0.md b/mddocs/docs/en/snippet_0.md new file mode 100644 index 000000000..98839961c --- /dev/null +++ b/mddocs/docs/en/snippet_0.md @@ -0,0 +1,44 @@ +## What is onETL? + +Python ETL/ELT library powered by [Apache Spark](https://spark.apache.org/) & other open-source tools. + +## Goals + +- Provide unified classes to extract data from (**E**) & load data to (**L**) various stores. +- Provides [Spark DataFrame API](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.DataFrame.html) for performing transformations (**T**) in terms of *ETL*. +- Provide direct assess to database, allowing to execute SQL queries, as well as DDL, DML, and call functions/procedures. This can be used for building up *ELT* pipelines. +- Support different [read strategies](../strategy/) for incremental and batch data fetching. +- Provide [hooks](../hooks/) & [plugins](../plugins) mechanism for altering behavior of internal classes. + +## Non-goals + +- onETL is not a Spark replacement. It just provides additional functionality that Spark does not have, and improves UX for end users. +- onETL is not a framework, as it does not have requirements to project structure, naming, the way of running ETL/ELT processes, configuration, etc. All of that should be implemented in some other tool. +- onETL is deliberately developed without any integration with scheduling software like Apache Airflow. All integrations should be implemented as separated tools. +- Only batch operations, no streaming. For streaming prefer [Apache Flink](https://flink.apache.org/). + +## Requirements + +- **Python** 3.7 - 3.13 +- PySpark 2.3.x - 3.5.x (depends on used connector) +- Java 8+ (required by Spark, see below) +- Kerberos libs & GCC (required by `Hive`, `HDFS` and `SparkHDFS` connectors) + +## Supported storages + + +| Type | Storage | Powered by | +|--------------------|--------------|-------------------------------------------------------------------------------------------------------------------------| +| Database {: rowspan=5} | Clickhouse
MSSQL
MySQL
Postgres
Oracle
Teradata |

Apache Spark [JDBC Data Source](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html) | +| Hive | Apache Spark [Hive integration](https://spark.apache.org/docs/latest/sql-data-sources-hive-tables.html) | +| Kafka | Apache Spark [Kafka integration](https://spark.apache.org/docs/latest/structured-streaming-kafka-integration.html) | +| Greenplum | VMware [Greenplum Spark connector](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/index.html) | +| MongoDB | [MongoDB Spark connector](https://www.mongodb.com/docs/spark-connector/current) | +| File {: rowspan=6} | HDFS | [HDFS Python client](https://pypi.org/project/hdfs/) | +| S3 | [minio-py client](https://pypi.org/project/minio/) | +| SFTP | [Paramiko library](https://pypi.org/project/paramiko/) | +| FTP
FTPS | [FTPUtil library](https://pypi.org/project/ftputil/) | +| WebDAV | [WebdavClient3 library](https://pypi.org/project/webdavclient3/) | +| Samba | [pysmb library](https://pypi.org/project/pysmb/) | +| Files as DataFrame {: rowspan=2} | SparkLocalFS
SparkHDFS | Apache Spark [File Data Source](https://spark.apache.org/docs/latest/sql-data-sources-generic-options.html) | +| SparkS3 | [Hadoop AWS](https://hadoop.apache.org/docs/current3/hadoop-aws/tools/hadoop-aws/index.html) library | \ No newline at end of file diff --git a/mddocs/docs/strategy/incremental_batch_strategy.md b/mddocs/docs/en/strategy/incremental_batch_strategy.md similarity index 100% rename from mddocs/docs/strategy/incremental_batch_strategy.md rename to mddocs/docs/en/strategy/incremental_batch_strategy.md diff --git a/mddocs/docs/strategy/incremental_strategy.md b/mddocs/docs/en/strategy/incremental_strategy.md similarity index 100% rename from mddocs/docs/strategy/incremental_strategy.md rename to mddocs/docs/en/strategy/incremental_strategy.md diff --git a/mddocs/docs/strategy/index.md b/mddocs/docs/en/strategy/index.md similarity index 100% rename from mddocs/docs/strategy/index.md rename to mddocs/docs/en/strategy/index.md diff --git a/mddocs/docs/strategy/snapshot_batch_strategy.md b/mddocs/docs/en/strategy/snapshot_batch_strategy.md similarity index 100% rename from mddocs/docs/strategy/snapshot_batch_strategy.md rename to mddocs/docs/en/strategy/snapshot_batch_strategy.md diff --git a/mddocs/docs/strategy/snapshot_strategy.md b/mddocs/docs/en/strategy/snapshot_strategy.md similarity index 100% rename from mddocs/docs/strategy/snapshot_strategy.md rename to mddocs/docs/en/strategy/snapshot_strategy.md diff --git a/mddocs/docs/troubleshooting/index.md b/mddocs/docs/en/troubleshooting/index.md similarity index 100% rename from mddocs/docs/troubleshooting/index.md rename to mddocs/docs/en/troubleshooting/index.md diff --git a/mddocs/docs/troubleshooting/spark.md b/mddocs/docs/en/troubleshooting/spark.md similarity index 100% rename from mddocs/docs/troubleshooting/spark.md rename to mddocs/docs/en/troubleshooting/spark.md diff --git a/mddocs/docs/index.md b/mddocs/docs/index.md deleted file mode 100644 index 46642bf4e..000000000 --- a/mddocs/docs/index.md +++ /dev/null @@ -1,87 +0,0 @@ -```{eval-rst} -.. include:: ../README.rst - :end-before: |Logo| -``` - -```{image} _static/logo_wide.svg -:alt: onETL logo -``` - -```{eval-rst} -.. include:: ../README.rst - :start-after: |Logo| - :end-before: documentation -``` - -```{toctree} -:caption: How to -:hidden: true -:maxdepth: 2 - -self -install/index -quickstart -concepts -logging -troubleshooting/index -``` - -```{toctree} -:caption: Connection -:hidden: true -:maxdepth: 3 - -connection/index -``` - -```{toctree} -:caption: DB classes -:hidden: true -:maxdepth: 3 - -db/index -``` - -```{toctree} -:caption: File classes -:hidden: true -:maxdepth: 3 - -file/index -``` - -```{toctree} -:caption: File DataFrame classes -:hidden: true -:maxdepth: 3 - -file_df/index -``` - -```{toctree} -:caption: Read strategies and HWM -:hidden: true -:maxdepth: 2 - -strategy/index -hwm_store/index -``` - -```{toctree} -:caption: Hooks & plugins -:hidden: true -:maxdepth: 2 - -hooks/index -plugins -``` - -```{toctree} -:caption: Development -:hidden: true -:maxdepth: 2 - -changelog -contributing -security -``` diff --git a/mddocs/docs/make.bat b/mddocs/docs/make.bat deleted file mode 100644 index 53ad1e82c..000000000 --- a/mddocs/docs/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=. -set BUILDDIR=_build - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.https://www.sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/mddocs/docs/quickstart.md b/mddocs/docs/quickstart.md deleted file mode 100644 index d2367a3de..000000000 --- a/mddocs/docs/quickstart.md +++ /dev/null @@ -1,4 +0,0 @@ -```{eval-rst} -.. include:: ../README.rst - :start-after: quick-start -``` diff --git a/mddocs/ru/README.md b/mddocs/docs/ru/README.md similarity index 99% rename from mddocs/ru/README.md rename to mddocs/docs/ru/README.md index bed7994b8..9b1b76c68 100644 --- a/mddocs/ru/README.md +++ b/mddocs/docs/ru/README.md @@ -54,6 +54,8 @@ substitutions: (readme)= +[![Built with Material for MkDocs](https://img.shields.io/badge/Material_for_MkDocs-526CFE?style=for-the-badge&logo=MaterialForMkDocs&logoColor=white)](https://squidfunk.github.io/mkdocs-material/) + # onETL {{ Repo Status }} {{ PyPI Latest Release }} {{ PyPI License }} {{ PyPI Python Version }} {{ PyPI Downloads }} diff --git a/mddocs/ru/changelog/0.10.0.md b/mddocs/docs/ru/changelog/0.10.0.md similarity index 100% rename from mddocs/ru/changelog/0.10.0.md rename to mddocs/docs/ru/changelog/0.10.0.md diff --git a/mddocs/ru/changelog/0.10.1.md b/mddocs/docs/ru/changelog/0.10.1.md similarity index 100% rename from mddocs/ru/changelog/0.10.1.md rename to mddocs/docs/ru/changelog/0.10.1.md diff --git a/mddocs/ru/changelog/0.10.2.md b/mddocs/docs/ru/changelog/0.10.2.md similarity index 100% rename from mddocs/ru/changelog/0.10.2.md rename to mddocs/docs/ru/changelog/0.10.2.md diff --git a/mddocs/ru/changelog/0.11.0.md b/mddocs/docs/ru/changelog/0.11.0.md similarity index 100% rename from mddocs/ru/changelog/0.11.0.md rename to mddocs/docs/ru/changelog/0.11.0.md diff --git a/mddocs/ru/changelog/0.11.1.md b/mddocs/docs/ru/changelog/0.11.1.md similarity index 100% rename from mddocs/ru/changelog/0.11.1.md rename to mddocs/docs/ru/changelog/0.11.1.md diff --git a/mddocs/ru/changelog/0.11.2.md b/mddocs/docs/ru/changelog/0.11.2.md similarity index 100% rename from mddocs/ru/changelog/0.11.2.md rename to mddocs/docs/ru/changelog/0.11.2.md diff --git a/mddocs/ru/changelog/0.12.0.md b/mddocs/docs/ru/changelog/0.12.0.md similarity index 100% rename from mddocs/ru/changelog/0.12.0.md rename to mddocs/docs/ru/changelog/0.12.0.md diff --git a/mddocs/ru/changelog/0.12.1.md b/mddocs/docs/ru/changelog/0.12.1.md similarity index 100% rename from mddocs/ru/changelog/0.12.1.md rename to mddocs/docs/ru/changelog/0.12.1.md diff --git a/mddocs/ru/changelog/0.12.2.md b/mddocs/docs/ru/changelog/0.12.2.md similarity index 100% rename from mddocs/ru/changelog/0.12.2.md rename to mddocs/docs/ru/changelog/0.12.2.md diff --git a/mddocs/ru/changelog/0.12.3.md b/mddocs/docs/ru/changelog/0.12.3.md similarity index 100% rename from mddocs/ru/changelog/0.12.3.md rename to mddocs/docs/ru/changelog/0.12.3.md diff --git a/mddocs/ru/changelog/0.12.4.md b/mddocs/docs/ru/changelog/0.12.4.md similarity index 100% rename from mddocs/ru/changelog/0.12.4.md rename to mddocs/docs/ru/changelog/0.12.4.md diff --git a/mddocs/ru/changelog/0.12.5.md b/mddocs/docs/ru/changelog/0.12.5.md similarity index 100% rename from mddocs/ru/changelog/0.12.5.md rename to mddocs/docs/ru/changelog/0.12.5.md diff --git a/mddocs/ru/changelog/0.13.0.md b/mddocs/docs/ru/changelog/0.13.0.md similarity index 100% rename from mddocs/ru/changelog/0.13.0.md rename to mddocs/docs/ru/changelog/0.13.0.md diff --git a/mddocs/ru/changelog/0.13.1.md b/mddocs/docs/ru/changelog/0.13.1.md similarity index 100% rename from mddocs/ru/changelog/0.13.1.md rename to mddocs/docs/ru/changelog/0.13.1.md diff --git a/mddocs/ru/changelog/0.13.3.md b/mddocs/docs/ru/changelog/0.13.3.md similarity index 100% rename from mddocs/ru/changelog/0.13.3.md rename to mddocs/docs/ru/changelog/0.13.3.md diff --git a/mddocs/ru/changelog/0.13.4.md b/mddocs/docs/ru/changelog/0.13.4.md similarity index 100% rename from mddocs/ru/changelog/0.13.4.md rename to mddocs/docs/ru/changelog/0.13.4.md diff --git a/mddocs/ru/changelog/0.7.0.md b/mddocs/docs/ru/changelog/0.7.0.md similarity index 96% rename from mddocs/ru/changelog/0.7.0.md rename to mddocs/docs/ru/changelog/0.7.0.md index e1b285d79..43a6d0a8d 100644 --- a/mddocs/ru/changelog/0.7.0.md +++ b/mddocs/docs/ru/changelog/0.7.0.md @@ -19,14 +19,14 @@ Правильный способ - перечислить коннекторы, которые должны быть установлены: ```bash - pip install onetl[hdfs,ftp,kerberos] # кроме DB соединений + pip install onetl[hdfs,ftp,kerberos] # кроме подключений к БД ``` **Подробности** В onetl<0.7 установка пакета выглядит так: - ```bash title="before" + ```bash title="До" pip install onetl ``` @@ -36,7 +36,7 @@ Начиная с версии 0.7.0 процесс установки был изменен: - ``` bash title="after" + ``` bash title="После" linenums="1" pip install onetl # минимальная установка, только ядро onETL # нет extras для подключений к БД, потому что они используют Java пакеты, которые устанавливаются во время выполнения @@ -55,7 +55,7 @@ Кроме того, onETL проверяет, что некоторые требования отсутствуют, и выдает исключение с рекомендацией, как их установить: - ``` text title="exception while import Clickhouse connection" + ``` text title="исключение при импорте подключения Clickhouse" Cannot import module "pyspark". @@ -65,7 +65,7 @@ or inject PySpark to sys.path in some other way BEFORE creating MongoDB instance. ``` - ``` text title="exception while import FTP connection" + ``` text title="исключение при импорте подключения FTP" Cannot import module "ftputil". @@ -167,7 +167,7 @@ Ещё мы улучшили сообщение об исключении с рекомендацией, как отключить неработающий плагин: - ``` text title="exception message example" + ``` text title="пример сообщения об ошибке" Error while importing plugin 'mtspark' from package 'mtspark' v4.0.0. @@ -199,7 +199,7 @@ Теперь onETL показывает следующее сообщение об ошибке: - ``` text title="exception message example" + ``` text title="пример сообщения об ошибке" |Spark| Cannot import Java class 'com.mongodb.spark.sql.connector.MongoTableProvider'. diff --git a/mddocs/ru/changelog/0.7.1.md b/mddocs/docs/ru/changelog/0.7.1.md similarity index 100% rename from mddocs/ru/changelog/0.7.1.md rename to mddocs/docs/ru/changelog/0.7.1.md diff --git a/mddocs/ru/changelog/0.7.2.md b/mddocs/docs/ru/changelog/0.7.2.md similarity index 100% rename from mddocs/ru/changelog/0.7.2.md rename to mddocs/docs/ru/changelog/0.7.2.md diff --git a/mddocs/ru/changelog/0.8.0.md b/mddocs/docs/ru/changelog/0.8.0.md similarity index 100% rename from mddocs/ru/changelog/0.8.0.md rename to mddocs/docs/ru/changelog/0.8.0.md diff --git a/mddocs/ru/changelog/0.8.1.md b/mddocs/docs/ru/changelog/0.8.1.md similarity index 100% rename from mddocs/ru/changelog/0.8.1.md rename to mddocs/docs/ru/changelog/0.8.1.md diff --git a/mddocs/ru/changelog/0.9.0.md b/mddocs/docs/ru/changelog/0.9.0.md similarity index 100% rename from mddocs/ru/changelog/0.9.0.md rename to mddocs/docs/ru/changelog/0.9.0.md diff --git a/mddocs/ru/changelog/0.9.1.md b/mddocs/docs/ru/changelog/0.9.1.md similarity index 100% rename from mddocs/ru/changelog/0.9.1.md rename to mddocs/docs/ru/changelog/0.9.1.md diff --git a/mddocs/ru/changelog/0.9.2.md b/mddocs/docs/ru/changelog/0.9.2.md similarity index 100% rename from mddocs/ru/changelog/0.9.2.md rename to mddocs/docs/ru/changelog/0.9.2.md diff --git a/mddocs/ru/changelog/0.9.3.md b/mddocs/docs/ru/changelog/0.9.3.md similarity index 100% rename from mddocs/ru/changelog/0.9.3.md rename to mddocs/docs/ru/changelog/0.9.3.md diff --git a/mddocs/ru/changelog/0.9.4.md b/mddocs/docs/ru/changelog/0.9.4.md similarity index 100% rename from mddocs/ru/changelog/0.9.4.md rename to mddocs/docs/ru/changelog/0.9.4.md diff --git a/mddocs/ru/changelog/0.9.5.md b/mddocs/docs/ru/changelog/0.9.5.md similarity index 100% rename from mddocs/ru/changelog/0.9.5.md rename to mddocs/docs/ru/changelog/0.9.5.md diff --git a/mddocs/docs/ru/changelog/index.md b/mddocs/docs/ru/changelog/index.md new file mode 100644 index 000000000..04704744d --- /dev/null +++ b/mddocs/docs/ru/changelog/index.md @@ -0,0 +1,29 @@ +# Changelog + +- [0.13.4](../changelog/0.13.4) +- [0.13.3](../changelog/0.13.3) +- [0.13.1](../changelog/0.13.1) +- [0.13.0](../changelog/0.13.0) +- [0.12.5](../changelog/0.12.5) +- [0.12.4](../changelog/0.12.4) +- [0.12.3](../changelog/0.12.3) +- [0.12.2](../changelog/0.12.2) +- [0.12.1](../changelog/0.12.1) +- [0.12.0](../changelog/0.12.0) +- [0.11.2](../changelog/0.11.2) +- [0.11.1](../changelog/0.11.1) +- [0.11.0](../changelog/0.11.0) +- [0.10.2](../changelog/0.10.2) +- [0.10.1](../changelog/0.10.1) +- [0.10.0](../changelog/0.10.0) +- [0.9.5](../changelog/0.9.5) +- [0.9.4](../changelog/0.9.4) +- [0.9.3](../changelog/0.9.3) +- [0.9.2](../changelog/0.9.2) +- [0.9.1](../changelog/0.9.1) +- [0.9.0](../changelog/0.9.0) +- [0.8.1](../changelog/0.8.1) +- [0.8.0](../changelog/0.8.0) +- [0.7.2](../changelog/0.7.2) +- [0.7.1](../changelog/0.7.1) +- [0.7.0](../changelog/0.7.0) diff --git a/mddocs/docs/ru/concepts.md b/mddocs/docs/ru/concepts.md new file mode 100644 index 000000000..20de36e1c --- /dev/null +++ b/mddocs/docs/ru/concepts.md @@ -0,0 +1,484 @@ +# Концепции + +Здесь вы найдете подробную информацию о каждом из концептов onETL и о том, как их использовать. + +## Подключение (Connection) + +### Основы + +onETL предназначена для извлечения и загрузки данных в хранилища (БД и файловые), поэтому в ней есть концепт первого класса `Connection` для хранения учетных данных, используемых для взаимодействия с внешними системами. + +`Connection` - это, по сути, набор параметров, таких как имя пользователя, пароль, имя хоста. + +Чтобы создать подключение к определенному типу хранилища, необходимо использовать класс, соответствующий типу хранилища. Имя класса совпадает с именем типа хранилища (`Oracle`, `MSSQL`, `SFTP` и т. д.): + +```python +from onetl.connection import SFTP + +sftp = SFTP( + host="sftp.test.com", + user="onetl", + password="onetl", +) +``` + +Все типы подключений наследуются от родительского класса `BaseConnection`. + +### Диаграмма классов + +```plantuml + + @startuml + left to right direction + skinparam classFontSize 20 + skinparam class { + BackgroundColor<> LightGreen + BackgroundColor<> Khaki + BackgroundColor<> LightBlue + StereotypeFontColor<> Transparent + StereotypeFontColor<> Transparent + StereotypeFontColor<> Transparent + } + + class BaseConnection { + } + + class DBConnection <>{ + } + DBConnection --|> BaseConnection + + class Hive <>{ + } + Hive --|> DBConnection + + class Greenplum <>{ + } + Greenplum --|> DBConnection + + class MongoDB <>{ + } + MongoDB --|> DBConnection + + class Kafka <>{ + } + Kafka --|> DBConnection + + class JDBCConnection <>{ + } + JDBCConnection --|> DBConnection + + class Clickhouse <>{ + } + Clickhouse --|> JDBCConnection + + class MSSQL <>{ + } + MSSQL --|> JDBCConnection + + class MySQL <>{ + } + MySQL --|> JDBCConnection + + class Postgres <>{ + } + Postgres --|> JDBCConnection + + class Oracle <>{ + } + Oracle --|> JDBCConnection + + class Teradata <>{ + } + Teradata --|> JDBCConnection + + class FileConnection <>{ + } + FileConnection --|> BaseConnection + + class FTP <>{ + } + FTP --|> FileConnection + + class FTPS <>{ + } + FTPS --|> FileConnection + + class HDFS <>{ + } + HDFS --|> FileConnection + + class WebDAV <>{ + } + WebDAV --|> FileConnection + + class Samba <>{ + } + Samba --|> FileConnection + + class SFTP <>{ + } + SFTP --|> FileConnection + + class S3 <>{ + } + S3 --|> FileConnection + + class FileDFConnection <>{ + } + FileDFConnection --|> BaseConnection + + class SparkHDFS <>{ + } + SparkHDFS --|> FileDFConnection + + class SparkLocalFS <>{ + } + SparkLocalFS --|> FileDFConnection + + class SparkS3 <>{ + } + SparkS3 --|> FileDFConnection + + @enduml +``` + +```mermaid +classDiagram + BaseConnection <|-- DBConnection + DBConnection <|-- Hive + DBConnection <|-- Greenplum + DBConnection <|-- MongoDB + DBConnection <|-- Kafka + DBConnection <|-- JDBCConnection + JDBCConnection <|-- Clickhouse + JDBCConnection <|-- MSSQL + JDBCConnection <|-- MySQL + JDBCConnection <|-- Postgres + JDBCConnection <|-- Oracle + JDBCConnection <|-- Teradata + BaseConnection <|-- FileConnection + FileConnection <|-- FTP + FileConnection <|-- FTPS + FileConnection <|-- HDFS + FileConnection <|-- WebDAV + FileConnection <|-- Samba + FileConnection <|-- SFTP + FileConnection <|-- S3 + BaseConnection <|-- FileDFConnection + FileDFConnection <|-- SparkHDFS + FileDFConnection <|-- SparkLocalFS + FileDFConnection <|-- SparkS3 +``` + + +### Подключения к БД (DBConnection) + +Классы, унаследованные от `DBConnection`, можно использовать для доступа к базам данных. + +`DBConnection` можно создать следующим образом: + +```python +from onetl.connection import MSSQL + +mssql = MSSQL( + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, +) +``` + +где **spark** - это текущая сессия Apache Spark (SparkSession). `onETL` "под капотом" использует `Spark` и специальные Java-коннекторы для работы с базами данных. + +Описание других параметров см. в документации для {ref}`доступных DBConnections `. + +### Подключения к файловым хранилищам (FileConnection) + +Классы, унаследованные от `FileConnection`, можно использовать для доступа к файлам, хранящимся в различных файловых системах/файловых серверах. + +`FileConnection` можно создать следующим образом: + +```python +from onetl.connection import SFTP + +sftp = SFTP( + host="sftp.test.com", + user="onetl", + password="onetl", +) +``` + +Описание других параметров см. в документации для {ref}`доступных FileConnections `. + +### FileDFConnection + +Классы, унаследованные от `FileDFConnection`, можно использовать для доступа к файлам в виде Spark DataFrames. + +`FileDFConnection` можно создать следующим образом: + +```python +from onetl.connection import SparkHDFS + +spark_hdfs = SparkHDFS( + host="namenode1.domain.com", + cluster="mycluster", + spark=spark, +) +``` + +где **spark** - это текущая SparkSession. +`onETL` использует `Spark` и специальные Java-коннекторы под капотом для работы с DataFrames. + +Описание других параметров см. в документации для [доступных FileDFConnections](../connection/file_df_connection/). + +### Проверка доступности соединения + +После создания соединения вы можете проверить доступность базы данных/файловой системы с помощью метода `check()`: + +```python +mssql.check() +sftp.check() +spark_hdfs.check() +``` + +Он вызовет исключение, если база данных/файловая система недоступна. + +Этот метод возвращает само соединение, поэтому вы можете создать соединение и сразу же проверить его доступность: + +```Python +mssql = MSSQL( + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, +).check() # <-- +``` + +## Извлечение/Загрузка данных + +### Основы + +Как мы говорили выше, onETL используется для извлечения данных из удаленных систем и загрузки данных в них. + +onETL предоставляет несколько классов для этого: + +> * [DBReader](../db_/reader) +> * [DBWriter](../db_/writer) +> * [FileDFReader](../file_df/file_df_reader/file_df_reader) +> * [FileDFWriter](../file_df/file_df_writer/file_df_writer) +> * [FileDownloader](../file/file_downloader/file_downloader) +> * [FileUploader](../file/file_uploader/file_uploader) +> * [FileMover](../file/file_mover/file_mover) + +Все эти классы имеют метод `run()`, который запускает извлечение/загрузку данных: + +```python +from onetl.db import DBReader, DBWriter + +reader = DBReader( + connection=mssql, + source="dbo.demo_table", + columns=["column_1", "column_2"], +) + +# Read data as Spark DataFrame +df = reader.run() + +db_writer = DBWriter( + connection=hive, + target="dl_sb.demo_table", +) + +# Save Spark DataFrame to Hive table +writer.run(df) +``` + +### Извлечение данных + +Для извлечения данных используйте классы: + +| | Вариант использования | Connection | `run()` получает | `run()` возвращает | +| -- | - | - | - | --- | +| [`DBReader`](../db_/reader) | Чтение данных из базы данных | Любое [`DBConnection`](../connection/db_connection) | - | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/dataframe.html#dataframe) | +| [`FileDFReader`](../file_df/file_df_reader/file_df_reader) | Чтение данных из файла или набора файлов | Любое [`FileDFConnection`](../connection/file_df_connection) | Нет входных данных или List[File path on FileSystem] | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/dataframe.html#dataframe) | +| [`FileDownloader`](../file/file_downloader/file_downloader) | Загрузка файлов из удаленной ФС в локальную ФС | Любое [`FileConnection`](../connection/file_connection) | Нет входных данных или List[File path on remote FileSystem] | [`DownloadResult`](../file/file_downloader/result) | + +### Загрузка данных + +Для загрузки данных используйте классы: + +| | Вариант использования | Connection | `run()` получает | `run()` возвращает | +| - | -- | - | --- | -- | +| [`DBWriter`](../db_/writer) | Запись данных из DataFrame в базу данных | Любое [`DBConnection`](../connection/db_connection) | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/dataframe.html#dataframe) | None | +| [`FileDFWriter`](../file_df/file_df_writer/file_df_writer) | Запись данных из DataFrame в папку | Любое [`FileDFConnection`](../connection/file_df_connection) | [Spark DataFrame](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/dataframe.html#dataframe) | None | +| [`FileUploader`](../file/file_uploader/file_uploader) | Загрузка файлов из локальной ФС в удаленную ФС | Любое [`FileConnection`](../connection/file_connection) | List[File path on local FileSystem] | [`UploadResult`](../file/file_uploader/result) | + +### Манипулирование данными + +Для манипулирования данными используйте классы: + +| | Вариант использования | Connection | `run()` получает | `run()` возвращает | +| - | - | -- | -- | - | +| [`FileMover`](../file/file_mover/file_mover) | Перемещение файлов между каталогами в удаленной ФС | Любое [`FileConnection`](../connection/file_connection) | List[File path on remote FileSystem] | [`MoveResult`](../file/file_mover/result) | + +### Опции + +Классы извлечения и загрузки имеют параметр `options`, который имеет особое значение: + +> - все остальные параметры - *ЧТО* мы извлекаем / *КУДА* мы загружаем +> - параметр `options` - *КАК* мы извлекаем/загружаем данные + +```python +db_reader = DBReader( + # ЧТО мы читаем: + connection=mssql, + source="dbo.demo_table", # некоторая таблица из MSSQL + columns=["column_1", "column_2"], # но только определенный набор столбцов + where="column_2 > 1000", # только строки, соответствующие условию + # КАК мы читаем: + options=MSSQL.ReadOptions( + numPartitions=10, # чтение в 10 параллельных задачах + partitionColumn="id", # балансировка чтения данных путем назначения каждой задаче части данных с использованием выражения `hash(id) mod N` + partitioningMode="hash", + fetchsize=1000, # каждая задача будет получать блок из 1000 строк при каждой попытке чтения + ), +) + +db_writer = DBWriter( + # КУДА мы пишем - в некоторую таблицу в Hive + connection=hive, + target="dl_sb.demo_table", + # КАК мы пишем - перезаписываем все данные в существующей таблице + options=Hive.WriteOptions(if_exists="replace_entire_table"), +) + +file_downloader = FileDownloader( + # ЧТО мы загружаем - файлы из некоторого каталога в SFTP + connection=sftp, + source_path="/source", + filters=[Glob("*.csv")], # только CSV файлы + limits=[MaxFilesCount(1000)], # максимум 1000 файлов + # КУДА мы загружаем - в определенный каталог в локальной ФС + local_path="/some", + # КАК мы загружаем: + options=FileDownloader.Options( + delete_source=True, # после загрузки каждого файла удалите его из source_path + if_exists="replace_file", # заменить существующие файлы в local_path + ), +) + +file_uploader = FileUploader( + # ЧТО мы загружаем - файлы из некоторого локального каталога + local_path="/source", + # КУДА мы загружаем - определенный удаленный каталог в HDFS + connection=hdfs, + target_path="/some", + # КАК мы загружаем: + options=FileUploader.Options( + delete_local=True, # после загрузки каждого файла удалите его из local_path + if_exists="replace_file", # заменить существующие файлы в target_path + ), +) + +file_mover = FileMover( + # ЧТО мы перемещаем - файлы в некотором удаленном каталоге в HDFS + source_path="/source", + connection=hdfs, + # КУДА мы перемещаем файлы + target_path="/some", # определенный удаленный каталог в том же соединении HDFS + # КАК мы загружаем - заменить существующие файлы в target_path + options=FileMover.Options(if_exists="replace_file"), +) + +file_df_reader = FileDFReader( + # ЧТО мы читаем - *.csv файлы из некоторого каталога в S3 + connection=s3, + source_path="/source", + file_format=CSV(), + # КАК мы читаем - загружать файлы из /source/*.csv, а не из /source/nested/*.csv + options=FileDFReader.Options(recursive=False), +) + +file_df_writer = FileDFWriter( + # КУДА мы пишем - в виде .csv файлов в некотором каталоге в S3 + connection=s3, + target_path="/target", + file_format=CSV(), + # КАК мы пишем - заменить все существующие файлы в /target, если они есть + options=FileDFWriter.Options(if_exists="replace_entire_directory"), +) +``` + +Более подробную информацию об `options` можно найти в документации к основным классам: [`DBConnection`](../connection/db_connection) и [`FileDownloader`](../file/file_downloader/file_downloader) / [`FileUploader`](../file/file_uploader/file_uploader) / [`FileMover`](../file/file_mover/file_mover) / [`FileDFReader`](../file_df/file_df_reader/file_df_reader) / [`FileDFWriter`](../file_df/file_df_writer/file_df_writer) + +### Стратегии чтения + +onETL имеет несколько встроенных стратегий для чтения данных: + +1. [Стратегия моментального снимка](../strategy/snapshot_strategy) (стратегия по умолчанию) +2. [Инкрементная стратегия](../strategy/incremental_strategy) +3. [Пакетная стратегия моментального снимка](../strategy/snapshot_batch_strategy) +4. [Инкрементная пакетная стратегия](../strategy/incremental_batch_strategy) + +Например, инкрементная стратегия позволяет получать только новые данные из таблицы: + +```python +from onetl.strategy import IncrementalStrategy + +reader = DBReader( + connection=mssql, + source="dbo.demo_table", + hwm_column="id", # обнаруживать новые данные на основе значения столбца "id" +) + +# первый запуск +with IncrementalStrategy(): + df = reader.run() + +sleep(3600) + +# второй запуск +with IncrementalStrategy(): + # только строки, которые появились в источнике с момента предыдущего запуска + df = reader.run() +``` + +или получать только файлы, которые не были загружены ранее: + +```python +from onetl.strategy import IncrementalStrategy + +file_downloader = FileDownloader( + connection=sftp, + source_path="/remote", + local_path="/local", + hwm_type="file_list", # сохранить все загруженные файлы в список и исключить файлы, уже присутствующие в этом списке +) + +# первый запуск +with IncrementalStrategy(): + files = file_downloader.run() + +sleep(3600) + +# второй запуск +with IncrementalStrategy(): + # только файлы, которые появились в источнике с момента предыдущего запуска + files = file_downloader.run() +``` + +Большинство стратегий основаны на [`HWM`](../hwm_store/), пожалуйста, ознакомьтесь с документацией по каждой стратегии для получения более подробной информации. + +### Почему просто не использовать класс Connection для извлечения/загрузки? + +Соединения очень просты, у них есть только набор некоторых основных операций, например, `mkdir`, `remove_file`, `get_table_schema` и так далее. + +Высокоуровневые операции, такие как + + - Поддержка [`strategy`](../strategy/) + - Обработка отправки/получения метаданных + - Обработка различных опций, таких как `if_exists="replace_file"` в случае загрузки/выгрузки файлов + +перенесена в отдельный класс, который вызывает методы объекта соединения для выполнения некоторой сложной логики. diff --git a/mddocs/docs/ru/connection/db_connection/clickhouse/connection.md b/mddocs/docs/ru/connection/db_connection/clickhouse/connection.md new file mode 100644 index 000000000..06fa5a462 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/clickhouse/connection.md @@ -0,0 +1,12 @@ +(clickhouse-connection)= + +# Clickhouse connection + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.clickhouse.connection +``` + +```{eval-rst} +.. autoclass:: Clickhouse + :members: get_packages, check +``` diff --git a/mddocs/docs/ru/connection/db_connection/clickhouse/execute.md b/mddocs/docs/ru/connection/db_connection/clickhouse/execute.md new file mode 100644 index 000000000..501fed530 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/clickhouse/execute.md @@ -0,0 +1,125 @@ +(clickhouse-execute)= + +# Executing statements in Clickhouse + +```{eval-rst} +.. warning:: + + Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + + Do **NOT** use them to read large amounts of data. Use :ref:`DBReader ` or :ref:`Clickhouse.sql ` instead. +``` + +## How to + +There are 2 ways to execute some statement in Clickhouse + +### Use `Clickhouse.fetch` + +Use this method to perform some `SELECT` query which returns **small number or rows**, like reading +Clickhouse config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts {obj}`Clickhouse.FetchOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`clickhouse-types`. +``` + +#### Syntax support + +This method supports **any** query syntax supported by Clickhouse, like: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ✅︎ `SELECT func(arg1, arg2)` - call function +- ✅︎ `SHOW ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Clickhouse + +clickhouse = Clickhouse(...) + +df = clickhouse.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=Clickhouse.FetchOptions(queryTimeout=10), +) +clickhouse.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `Clickhouse.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts {obj}`Clickhouse.ExecuteOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Clickhouse, like: + +- ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +- ✅︎ `ALTER ...` +- ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +- ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +- ✅︎ other statements not mentioned here +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Clickhouse + +clickhouse = Clickhouse(...) + +clickhouse.execute("DROP TABLE schema.table") +clickhouse.execute( + """ + CREATE TABLE schema.table ( + id UInt8, + key String, + value Float32 + ) + ENGINE = MergeTree() + ORDER BY id + """, + options=Clickhouse.ExecuteOptions(queryTimeout=10), +) +``` + +## Notes + +These methods **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + +So it should **NOT** be used to read large amounts of data. Use {ref}`DBReader ` or {ref}`Clickhouse.sql ` instead. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.clickhouse.options +``` + +```{eval-rst} +.. autopydantic_model:: ClickhouseFetchOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false + +``` + +```{eval-rst} +.. autopydantic_model:: ClickhouseExecuteOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/clickhouse/index.md b/mddocs/docs/ru/connection/db_connection/clickhouse/index.md new file mode 100644 index 000000000..28c20dec1 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/clickhouse/index.md @@ -0,0 +1,28 @@ +(clickhouse)= + +# Clickhouse + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +connection +``` + +```{toctree} +:caption: Operations +:maxdepth: 1 + +read +sql +write +execute +``` + +```{toctree} +:caption: Troubleshooting +:maxdepth: 1 + +types +``` diff --git a/mddocs/docs/ru/connection/db_connection/clickhouse/prerequisites.md b/mddocs/docs/ru/connection/db_connection/clickhouse/prerequisites.md new file mode 100644 index 000000000..563dbad3f --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/clickhouse/prerequisites.md @@ -0,0 +1,73 @@ +(clickhouse-prerequisites)= + +# Prerequisites + +## Version Compatibility + +- Clickhouse server versions: + : - Officially declared: 22.8 or higher + - Actually tested: 21.1, 25.1 +- Spark versions: 2.3.x - 3.5.x +- Java versions: 8 - 20 + +See [official documentation](https://clickhouse.com/docs/en/integrations/java#jdbc-driver). + +## Installing PySpark + +To use Clickhouse connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Connecting to Clickhouse + +### Connection port + +Connector can only use **HTTP** (usually `8123` port) or **HTTPS** (usually `8443` port) protocol. + +TCP and GRPC protocols are NOT supported. + +### Connecting to cluster + +It is possible to connect to Clickhouse cluster, and use it's load balancing capabilities to read or write data in parallel. +Each Spark executor can connect to random Clickhouse nodes, instead of sending all the data to a node specified in connection params. + +This requires all Clickhouse servers to run on different hosts, and **listen the same HTTP port**. +Set `auto_discovery=True` to enable this feature (disabled by default): + +```python +Clickhouse( + host="node1.of.cluster", + port=8123, + extra={ + "auto_discovery": True, + "load_balancing_policy": "roundRobin", + }, +) +``` + +See [official documentation](https://clickhouse.com/docs/en/integrations/java#configuring-node-discovery-load-balancing-and-failover). + +### Required grants + +Ask your Clickhouse cluster administrator to set following grants for a user, +used for creating a connection: + +```{eval-rst} +.. tabs:: + + .. code-tab:: sql Read + Write + + -- allow creating tables in the target schema + GRANT CREATE TABLE ON myschema.* TO username; + + -- allow read & write access to specific table + GRANT SELECT, INSERT ON myschema.mytable TO username; + + .. code-tab:: sql Read only + + -- allow read access to specific table + GRANT SELECT ON myschema.mytable TO username; +``` + +More details can be found in [official documentation](https://clickhouse.com/docs/en/sql-reference/statements/grant). diff --git a/mddocs/docs/ru/connection/db_connection/clickhouse/read.md b/mddocs/docs/ru/connection/db_connection/clickhouse/read.md new file mode 100644 index 000000000..2c62132ed --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/clickhouse/read.md @@ -0,0 +1,93 @@ +(clickhouse-read)= + +# Reading from Clickhouse using `DBReader` + +{obj}`DBReader ` supports {ref}`strategy` for incremental data reading, +but does not support custom queries, like `JOIN`. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`clickhouse-types` +``` + +## Supported DBReader features + +- ✅︎ `columns` +- ✅︎ `where` +- ✅︎ `hwm`, supported strategies: +- - ✅︎ {ref}`snapshot-strategy` +- - ✅︎ {ref}`incremental-strategy` +- - ✅︎ {ref}`snapshot-batch-strategy` +- - ✅︎ {ref}`incremental-batch-strategy` +- ❌ `hint` (is not supported by Clickhouse) +- ❌ `df_schema` +- ✅︎ `options` (see {obj}`Clickhouse.ReadOptions `) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Clickhouse +from onetl.db import DBReader + +clickhouse = Clickhouse(...) + +reader = DBReader( + connection=clickhouse, + source="schema.table", + columns=["id", "key", "CAST(value AS String) value", "updated_dt"], + where="key = 'something'", + options=Clickhouse.ReadOptions(partitionColumn="id", numPartitions=10), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Clickhouse +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +clickhouse = Clickhouse(...) + +reader = DBReader( + connection=clickhouse, + source="schema.table", + columns=["id", "key", "CAST(value AS String) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="clickhouse_hwm", expression="updated_dt"), + options=Clickhouse.ReadOptions(partitionColumn="id", numPartitions=10), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Clickhouse to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Clickhouse to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.clickhouse.options +``` + +```{eval-rst} +.. autopydantic_model:: ClickhouseReadOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/clickhouse/sql.md b/mddocs/docs/ru/connection/db_connection/clickhouse/sql.md new file mode 100644 index 000000000..bbe62adaa --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/clickhouse/sql.md @@ -0,0 +1,80 @@ +(clickhouse-sql)= + +# Reading from Clickhouse using `Clickhouse.sql` + +`Clickhouse.sql` allows passing custom SQL query, but does not support incremental strategies. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`clickhouse-types` +``` + +```{eval-rst} +.. warning:: + + Statement is executed in **read-write** connection, so if you're calling some functions/procedures with DDL/DML statements inside, + they can change data in your database. +``` + +## Syntax support + +Only queries with the following syntax are supported: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import Clickhouse + +clickhouse = Clickhouse(...) +df = clickhouse.sql( + """ + SELECT + id, + key, + CAST(value AS String) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """, + options=Clickhouse.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from Clickhouse to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from Clickhouse to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.clickhouse.options +``` + +```{eval-rst} +.. autopydantic_model:: ClickhouseSQLOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/clickhouse/types.md b/mddocs/docs/ru/connection/db_connection/clickhouse/types.md new file mode 100644 index 000000000..f288bfb21 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/clickhouse/types.md @@ -0,0 +1,457 @@ +(clickhouse-types)= + +# Clickhouse \<-> Spark type mapping + +```{eval-rst} +.. note:: + + The results below are valid for Spark 3.5.5, and may differ on other Spark versions. +``` + +```{eval-rst} +.. note:: + + It is recommended to use `spark-dialect-extension `_ package, + which implements writing Arrays from Spark to Clickhouse, fixes dropping fractions of seconds in ``TimestampType``, + and fixes other type conversion issues. +``` + +## Type detection & casting + +Spark's DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from Clickhouse + +This is how Clickhouse connector performs this: + +- For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and Clickhouse type. +- Find corresponding `Clickhouse type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- Create DataFrame from query with specific column names and Spark types. + +### Writing to some existing Clickhouse table + +This is how Clickhouse connector performs this: + +- Get names of columns in DataFrame. [^footnote-1] +- Perform `SELECT * FROM table LIMIT 0` query. +- Take only columns present in DataFrame (by name, case insensitive). For each found column get Clickhouse type. +- **Find corresponding** `Clickhouse type (read)` → `Spark type` **combination** (see below) for each DataFrame column. If no combination is found, raise exception. [^footnote-2] +- Find corresponding `Spark type` → `Clickhousetype (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- If `Clickhousetype (write)` match `Clickhouse type (read)`, no additional casts will be performed, DataFrame column will be written to Clickhouse as is. +- If `Clickhousetype (write)` does not match `Clickhouse type (read)`, DataFrame column will be casted to target column type **on Clickhouse side**. For example, you can write column with text data to `Int32` column, if column contains valid integer values within supported value range and precision. + +[^footnote-1]: This allows to write data to tables with `DEFAULT` columns - if DataFrame has no such column, + it will be populated by Clickhouse. + +[^footnote-2]: Yes, this is weird. + +### Create new table using Spark + +```{eval-rst} +.. warning:: + + ABSOLUTELY NOT RECOMMENDED! +``` + +This is how Clickhouse connector performs this: + +- Find corresponding `Spark type` → `Clickhouse type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- Generate DDL for creating table in Clickhouse, like `CREATE TABLE (col1 ...)`, and run it. +- Write DataFrame to created table as is. + +But Spark does not have specific dialect for Clickhouse, so Generic JDBC dialect is used. +Generic dialect is using SQL ANSI type names while creating tables in target database, not database-specific types. + +If some cases this may lead to using wrong column type. For example, Spark creates column of type `TIMESTAMP` +which corresponds to Clickhouse type `DateTime32` (precision up to seconds) +instead of more precise `DateTime64` (precision up to nanoseconds). +This may lead to incidental precision loss, or sometimes data cannot be written to created table at all. + +So instead of relying on Spark to create tables: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + writer = DBWriter( + connection=clickhouse, + target="default.target_tbl", + options=Clickhouse.WriteOptions( + if_exists="append", + # ENGINE is required by Clickhouse + createTableOptions="ENGINE = MergeTree() ORDER BY id", + ), + ) + writer.run(df) +``` + +Always prefer creating tables with specific types **BEFORE WRITING DATA**: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + clickhouse.execute( + """ + CREATE TABLE default.target_tbl ( + id UInt8, + value DateTime64(6) -- specific type and precision + ) + ENGINE = MergeTree() + ORDER BY id + """, + ) + + writer = DBWriter( + connection=clickhouse, + target="default.target_tbl", + options=Clickhouse.WriteOptions(if_exists="append"), + ) + writer.run(df) +``` + +### References + +Here you can find source code with type conversions: + +- [Clickhouse -> JDBC](https://github.com/ClickHouse/clickhouse-java/blob/0.3.2/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/JdbcTypeMapping.java#L39-L176) +- [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/jdbc/JdbcUtils.scala#L307) +- [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/jdbc/JdbcUtils.scala#L141-L164) +- [JDBC -> Clickhouse](https://github.com/ClickHouse/clickhouse-java/blob/0.3.2/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/JdbcTypeMapping.java#L185-L311) + +## Supported types + +See [official documentation](https://clickhouse.com/docs/en/sql-reference/data-types) + +### Generic types + +- `LowCardinality(T)` is same as `T` +- `Nullable(T)` is same as `T`, but Spark column is inferred as `nullable=True` + +### Numeric types + +```{eval-rst} ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| Clickhouse type (read) | Spark type | Clickhouse type (write) | Clickhouse type (create) | ++================================+===================================+===============================+===============================+ +| ``Bool`` | ``BooleanType()`` | ``Bool`` | ``UInt64`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Decimal`` | ``DecimalType(P=10, S=0)`` | ``Decimal(P=10, S=0)`` | ``Decimal(P=10, S=0)`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Decimal(P=0..38)`` | ``DecimalType(P=0..38, S=0)`` | ``Decimal(P=0..38, S=0)`` | ``Decimal(P=0..38, S=0)`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Decimal(P=0..38, S=0..38)`` | ``DecimalType(P=0..38, S=0..38)`` | ``Decimal(P=0..38, S=0..38)`` | ``Decimal(P=0..38, S=0..38)`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Decimal(P=39..76, S=0..76)`` | unsupported [3]_ | | | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Decimal32(P=0..9)`` | ``DecimalType(P=9, S=0..9)`` | ``Decimal(P=9, S=0..9)`` | ``Decimal(P=9, S=0..9)`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Decimal64(S=0..18)`` | ``DecimalType(P=18, S=0..18)`` | ``Decimal(P=18, S=0..18)`` | ``Decimal(P=18, S=0..18)`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Decimal128(S=0..38)`` | ``DecimalType(P=38, S=0..38)`` | ``Decimal(P=38, S=0..38)`` | ``Decimal(P=38, S=0..38)`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Decimal256(S=0..76)`` | unsupported [3]_ | | | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Float32`` | ``FloatType()`` | ``Float32`` | ``Float32`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Float64`` | ``DoubleType()`` | ``Float64`` | ``Float64`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Int8`` | ``IntegerType()`` | ``Int32`` | ``Int32`` | ++--------------------------------+ | | | +| ``Int16`` | | | | ++--------------------------------+ | | | +| ``Int32`` | | | | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Int64`` | ``LongType()`` | ``Int64`` | ``Int64`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``Int128`` | unsupported [3]_ | | | ++--------------------------------+ | | | +| ``Int256`` | | | | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``-`` | ``ByteType()`` | ``Int8`` | ``Int8`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``-`` | ``ShortType()`` | ``Int32`` | ``Int32`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``UInt8`` | ``IntegerType()`` | ``Int32`` | ``Int32`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``UInt16`` | ``LongType()`` | ``Int64`` | ``Int64`` | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``UInt32`` | ``DecimalType(20,0)`` | ``Decimal(20,0)`` | ``Decimal(20,0)`` | ++--------------------------------+ | | | +| ``UInt64`` | | | | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``UInt128`` | unsupported [3]_ | | | ++--------------------------------+ | | | +| ``UInt256`` | | | | ++--------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +``` + +[^footnote-3]: Clickhouse support numeric types up to 256 bit - `Int256`, `UInt256`, `Decimal256(S)`, `Decimal(P=39..76, S=0..76)`. + + But Spark's `DecimalType(P, S)` supports maximum `P=38` (128 bit). It is impossible to read, write or operate with values of larger precision, + this leads to an exception. + +### Temporal types + +Notes: +: - Datetime with timezone has the same precision as without timezone + - `DateTime` is alias for `DateTime32` + - `TIMESTAMP` is alias for `DateTime32`, but `TIMESTAMP(N)` is alias for `DateTime64(N)` + +```{eval-rst} ++-----------------------------------+--------------------------------------+----------------------------------+-------------------------------+ +| Clickhouse type (read) | Spark type | Clickhouse type (write) | Clickhouse type (create) | ++===================================+======================================+==================================+===============================+ +| ``Date`` | ``DateType()`` | ``Date`` | ``Date`` | ++-----------------------------------+--------------------------------------+----------------------------------+-------------------------------+ +| ``Date32`` | ``DateType()`` | ``Date`` | ``Date``, | +| | | | **cannot insert data** [4]_ | ++-----------------------------------+--------------------------------------+----------------------------------+-------------------------------+ +| ``DateTime32``, seconds | ``TimestampType()``, microseconds | ``DateTime64(6)``, microseconds | ``DateTime32``, seconds | ++-----------------------------------+--------------------------------------+----------------------------------+-------------------------------+ +| ``DateTime64(3)``, milliseconds | ``TimestampType()``, microseconds | ``DateTime64(6)``, microseconds | ``DateTime32``, seconds, | +| | | | **precision loss** [5]_ | ++-----------------------------------+--------------------------------------+----------------------------------+-------------------------------+ +| ``DateTime64(6)``, microseconds | ``TimestampType()``, microseconds | | ``DateTime32``, seconds, | ++-----------------------------------+--------------------------------------+ | **precision loss** [7]_ | +| ``DateTime64(7..9)``, nanoseconds | ``TimestampType()``, microseconds, | | | +| | **precision loss** [6]_ | | | +| | | | | ++-----------------------------------+--------------------------------------+ | | +| ``-`` | ``TimestampNTZType()``, microseconds | | | ++-----------------------------------+--------------------------------------+----------------------------------+-------------------------------+ +| ``DateTime32(TZ)`` | unsupported [7]_ | | | ++-----------------------------------+ | | | +| ``DateTime64(P, TZ)`` | | | | ++-----------------------------------+--------------------------------------+----------------------------------+-------------------------------+ +| ``IntervalNanosecond`` | ``LongType()`` | ``Int64`` | ``Int64`` | ++-----------------------------------+ | | | +| ``IntervalMicrosecond`` | | | | ++-----------------------------------+ | | | +| ``IntervalMillisecond`` | | | | ++-----------------------------------+ | | | +| ``IntervalSecond`` | | | | ++-----------------------------------+ | | | +| ``IntervalMinute`` | | | | ++-----------------------------------+ | | | +| ``IntervalHour`` | | | | ++-----------------------------------+ | | | +| ``IntervalDay`` | | | | ++-----------------------------------+ | | | +| ``IntervalMonth`` | | | | ++-----------------------------------+ | | | +| ``IntervalQuarter`` | | | | ++-----------------------------------+ | | | +| ``IntervalWeek`` | | | | ++-----------------------------------+ | | | +| ``IntervalYear`` | | | | ++-----------------------------------+--------------------------------------+----------------------------------+-------------------------------+ +``` + +```{eval-rst} +.. warning:: + + Note that types in Clickhouse and Spark have different value ranges: + + +------------------------+-----------------------------------+-----------------------------------+---------------------+--------------------------------+--------------------------------+ + | Clickhouse type | Min value | Max value | Spark type | Min value | Max value | + +========================+===================================+===================================+=====================+================================+================================+ + | ``Date`` | ``1970-01-01`` | ``2149-06-06`` | ``DateType()`` | ``0001-01-01`` | ``9999-12-31`` | + +------------------------+-----------------------------------+-----------------------------------+---------------------+--------------------------------+--------------------------------+ + | ``DateTime32`` | ``1970-01-01 00:00:00`` | ``2106-02-07 06:28:15`` | ``TimestampType()`` | ``0001-01-01 00:00:00.000000`` | ``9999-12-31 23:59:59.999999`` | + +------------------------+-----------------------------------+-----------------------------------+ | | | + | ``DateTime64(P=0..8)`` | ``1900-01-01 00:00:00.00000000`` | ``2299-12-31 23:59:59.99999999`` | | | | + +------------------------+-----------------------------------+-----------------------------------+ | | | + | ``DateTime64(P=9)`` | ``1900-01-01 00:00:00.000000000`` | ``2262-04-11 23:47:16.999999999`` | | | | + +------------------------+-----------------------------------+-----------------------------------+---------------------+--------------------------------+--------------------------------+ + + So not all of values in Spark DataFrame can be written to Clickhouse. + + References: + * `Clickhouse Date documentation `_ + * `Clickhouse Datetime32 documentation `_ + * `Clickhouse Datetime64 documentation `_ + * `Spark DateType documentation `_ + * `Spark TimestampType documentation `_ +``` + +[^footnote-4]: `Date32` has different bytes representation than `Date`, and inserting value of type `Date32` to `Date` column + leads to errors on Clickhouse side, e.g. `Date(106617) should be between 0 and 65535 inclusive of both values`. + Although Spark does properly read the `Date32` column as `DateType()`, and there should be no difference at all. + Probably this is some bug in Clickhouse driver. + +[^footnote-5]: Generic JDBC dialect generates DDL with Clickhouse type `TIMESTAMP` which is alias for `DateTime32` with precision up to seconds (`23:59:59`). + Inserting data with milliseconds precision (`23:59:59.999`) will lead to **throwing away milliseconds**. + Solution: create table manually, with proper column type. + +[^footnote-6]: Clickhouse support datetime up to nanoseconds precision (`23:59:59.999999999`), + but Spark `TimestampType()` supports datetime up to microseconds precision (`23:59:59.999999`). + Nanoseconds will be lost during read or write operations. + Solution: create table manually, with proper column type. + +[^footnote-7]: Clickhouse will raise an exception that data in format `2001-01-01 23:59:59.999999` has data `.999999` which does not match format `YYYY-MM-DD hh:mm:ss` + of `DateTime32` column type (see [^footnote-5]). + So Spark can create Clickhouse table, but cannot write data to column of this type. + Solution: create table manually, with proper column type. + +### String types + +```{eval-rst} ++--------------------------------------+------------------+------------------------+--------------------------+ +| Clickhouse type (read) | Spark type | Clickhousetype (write) | Clickhouse type (create) | ++======================================+==================+========================+==========================+ +| ``FixedString(N)`` | ``StringType()`` | ``String`` | ``String`` | ++--------------------------------------+ | | | +| ``String`` | | | | ++--------------------------------------+ | | | +| ``Enum8`` | | | | ++--------------------------------------+ | | | +| ``Enum16`` | | | | ++--------------------------------------+ | | | +| ``IPv4`` | | | | ++--------------------------------------+ | | | +| ``IPv6`` | | | | ++--------------------------------------+ | | | +| ``UUID`` | | | | ++--------------------------------------+------------------+ | | +| ``-`` | ``BinaryType()`` | | | ++--------------------------------------+------------------+------------------------+--------------------------+ +``` + +## Unsupported types + +Columns of these Clickhouse types cannot be read by Spark: +: - `AggregateFunction(func, T)` + - `Array(T)` + - `JSON` + - `Map(K, V)` + - `MultiPolygon` + - `Nested(field1 T1, ...)` + - `Nothing` + - `Point` + - `Polygon` + - `Ring` + - `SimpleAggregateFunction(func, T)` + - `Tuple(T1, T2, ...)` + +Dataframe with these Spark types cannot be written to Clickhouse: +: - `ArrayType(T)` + - `BinaryType()` + - `CharType(N)` + - `DayTimeIntervalType(P, S)` + - `MapType(K, V)` + - `NullType()` + - `StructType([...])` + - `TimestampNTZType()` + - `VarcharType(N)` + +This is because Spark does not have dedicated Clickhouse dialect, and uses Generic JDBC dialect instead. +This dialect does not have type conversion between some types, like Clickhouse `Array` -> Spark `ArrayType()`, and vice versa. + +The is a way to avoid this - just cast everything to `String`. + +## Explicit type cast + +### `DBReader` + +Use `CAST` or `toJSONString` to get column data as string in JSON format, + +For parsing JSON columns in ClickHouse, {obj}`JSON.parse_column ` method. + +```python +from pyspark.sql.types import ArrayType, IntegerType + +from onetl.file.format import JSON +from onetl.connection import ClickHouse +from onetl.db import DBReader + +reader = DBReader( + connection=clickhouse, + target="default.source_tbl", + columns=[ + "id", + "toJSONString(array_column) array_column", + ], +) +df = reader.run() + +# Spark requires all columns to have some specific type, describe it +column_type = ArrayType(IntegerType()) + +json = JSON() +df = df.select( + df.id, + json.parse_column("array_column", column_type), +) +``` + +### `DBWriter` + +For writing JSON data to ClickHouse, use the {obj}`JSON.serialize_column ` method to convert a DataFrame column to JSON format efficiently and write it as a `String` column in Clickhouse. + +```python +from onetl.file.format import JSON +from onetl.connection import ClickHouse +from onetl.db import DBWriter + +clickhouse = ClickHouse(...) + +clickhouse.execute( + """ + CREATE TABLE default.target_tbl ( + id Int32, + array_column_json String, + ) + ENGINE = MergeTree() + ORDER BY id + """, +) + +json = JSON() +df = df.select( + df.id, + json.serialize_column(df.array_column).alias("array_column_json"), +) + +writer.run(df) +``` + +Then you can parse this column on Clickhouse side - for example, by creating a view: + +```sql +SELECT + id, + JSONExtract(json_column, 'Array(String)') AS array_column +FROM target_tbl +``` + +You can also use [ALIAS](https://clickhouse.com/docs/en/sql-reference/statements/create/table#alias) +or [MATERIALIZED](https://clickhouse.com/docs/en/sql-reference/statements/create/table#materialized) columns +to avoid writing such expression in every `SELECT` clause all the time: + +```sql +CREATE TABLE default.target_tbl ( + id Int32, + array_column_json String, + -- computed column + array_column Array(String) ALIAS JSONExtract(json_column, 'Array(String)') + -- or materialized column + -- array_column Array(String) MATERIALIZED JSONExtract(json_column, 'Array(String)') +) +ENGINE = MergeTree() +ORDER BY id +``` + +Downsides: + +- Using `SELECT JSONExtract(...)` or `ALIAS` column can be expensive, because value is calculated on every row access. This can be especially harmful if such column is used in `WHERE` clause. +- `ALIAS` and `MATERIALIZED` columns are not included in `SELECT *` clause, they should be added explicitly: `SELECT *, calculated_column FROM table`. + +```{eval-rst} +.. warning:: + + `EPHEMERAL `_ columns are not supported by Spark + because they cannot be selected to determine target column type. +``` diff --git a/mddocs/docs/ru/connection/db_connection/clickhouse/write.md b/mddocs/docs/ru/connection/db_connection/clickhouse/write.md new file mode 100644 index 000000000..fa39d81a2 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/clickhouse/write.md @@ -0,0 +1,60 @@ +(clickhouse-write)= + +# Writing to Clickhouse using `DBWriter` + +For writing data to Clickhouse, use {obj}`DBWriter `. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`clickhouse-types` +``` + +```{eval-rst} +.. warning:: + + It is always recommended to create table explicitly using :ref:`Clickhouse.execute ` + instead of relying on Spark's table DDL generation. + + This is because Spark's DDL generator can create columns with different precision and types than it is expected, + causing precision loss or other issues. +``` + +## Examples + +```python +from onetl.connection import Clickhouse +from onetl.db import DBWriter + +clickhouse = Clickhouse(...) + +df = ... # data is here + +writer = DBWriter( + connection=clickhouse, + target="schema.table", + options=Clickhouse.WriteOptions( + if_exists="append", + # ENGINE is required by Clickhouse + createTableOptions="ENGINE = MergeTree() ORDER BY id", + ), +) + +writer.run(df) +``` + +## Options + +Method above accepts {obj}`Clickhouse.WriteOptions ` + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.clickhouse.options +``` + +```{eval-rst} +.. autopydantic_model:: ClickhouseWriteOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/greenplum/connection.md b/mddocs/docs/ru/connection/db_connection/greenplum/connection.md new file mode 100644 index 000000000..e95a4e6a8 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/greenplum/connection.md @@ -0,0 +1,12 @@ +(greenplum-connection)= + +# Greenplum connection + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.greenplum.connection +``` + +```{eval-rst} +.. autoclass:: Greenplum + :members: get_packages, check +``` diff --git a/mddocs/docs/ru/connection/db_connection/greenplum/execute.md b/mddocs/docs/ru/connection/db_connection/greenplum/execute.md new file mode 100644 index 000000000..0b10c0bc7 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/greenplum/execute.md @@ -0,0 +1,159 @@ +(greenplum-execute)= + +# Executing statements in Greenplum + +```{eval-rst} +.. warning:: + + Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + + Do **NOT** use them to read large amounts of data. Use :ref:`DBReader ` instead. +``` + +## How to + +There are 2 ways to execute some statement in Greenplum + +### Use `Greenplum.fetch` + +Use this method to perform some `SELECT` query which returns **small number or rows**, like reading +Greenplum config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts {obj}`Greenplum.FetchOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +```{eval-rst} +.. warning:: + + ``Greenplum.fetch`` is implemented using Postgres JDBC connection, + so types are handled a bit differently than in ``DBReader``. See :ref:`postgres-types`. +``` + +#### Syntax support + +This method supports **any** query syntax supported by Greenplum, like: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ✅︎ `SELECT func(arg1, arg2)` or `{call func(arg1, arg2)}` - special syntax for calling functions +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Greenplum + +greenplum = Greenplum(...) + +df = greenplum.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=Greenplum.FetchOptions(queryTimeout=10), +) +greenplum.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `Greenplum.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts {obj}`Greenplum.ExecuteOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Greenplum, like: + +- ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +- ✅︎ `ALTER ...` +- ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +- ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +- ✅︎ `CALL procedure(arg1, arg2) ...` +- ✅︎ `SELECT func(arg1, arg2)` or `{call func(arg1, arg2)}` - special syntax for calling functions +- ✅︎ other statements not mentioned here +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Greenplum + +greenplum = Greenplum(...) + +greenplum.execute("DROP TABLE schema.table") +greenplum.execute( + """ + CREATE TABLE schema.table ( + id int, + key text, + value real + ) + DISTRIBUTED BY id + """, + options=Greenplum.ExecuteOptions(queryTimeout=10), +) +``` + +## Interaction schema + +Unlike reading & writing, executing statements in Greenplum is done **only** through Greenplum master node, +without any interaction between Greenplum segments and Spark executors. More than that, Spark executors are not used in this case. + +The only port used while interacting with Greenplum in this case is `5432` (Greenplum master port). + +```{eval-rst} +.. dropdown:: Spark <-> Greenplum interaction during Greenplum.execute()/Greenplum.fetch() + + .. plantuml:: + + @startuml + title Greenplum master <-> Spark driver + box "Spark" + participant "Spark driver" + end box + + box "Greenplum" + participant "Greenplum master" + end box + + == Greenplum.check() == + + activate "Spark driver" + "Spark driver" -> "Greenplum master" ++ : CONNECT + + == Greenplum.execute(statement) == + "Spark driver" --> "Greenplum master" : EXECUTE statement + "Greenplum master" -> "Spark driver" : RETURN result + + == Greenplum.close() == + "Spark driver" --> "Greenplum master" : CLOSE CONNECTION + + deactivate "Greenplum master" + deactivate "Spark driver" + @enduml +``` + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.greenplum.options +``` + +```{eval-rst} +.. autopydantic_model:: GreenplumFetchOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false + +``` + +```{eval-rst} +.. autopydantic_model:: GreenplumExecuteOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/greenplum/index.md b/mddocs/docs/ru/connection/db_connection/greenplum/index.md new file mode 100644 index 000000000..1707fc960 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/greenplum/index.md @@ -0,0 +1,27 @@ +(greenplum)= + +# Greenplum + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +connection +``` + +```{toctree} +:caption: Operations +:maxdepth: 1 + +read +write +execute +``` + +```{toctree} +:caption: Troubleshooting +:maxdepth: 1 + +types +``` diff --git a/mddocs/docs/ru/connection/db_connection/greenplum/prerequisites.md b/mddocs/docs/ru/connection/db_connection/greenplum/prerequisites.md new file mode 100644 index 000000000..a2e55b950 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/greenplum/prerequisites.md @@ -0,0 +1,382 @@ +(greenplum-prerequisites)= + +# Prerequisites + +## Version Compatibility + +- Greenplum server versions: + : - Officially declared: 5.x, 6.x, and 7.x (which requires `Greenplum.get_packages(package_version="2.3.0")` or higher) + - Actually tested: 6.23, 7.0 +- Spark versions: 2.3.x - 3.2.x (Spark 3.3+ is not supported yet) +- Java versions: 8 - 11 + +See [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.2/greenplum-connector-spark/release_notes.html). + +## Installing PySpark + +To use Greenplum connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Downloading VMware package + +To use Greenplum connector you should download connector `.jar` file from +[VMware website](https://network.tanzu.vmware.com/products/vmware-greenplum#/releases/1413479/file_groups/16966) +and then pass it to Spark session. + +```{eval-rst} +.. warning:: + + Please pay attention to :ref:`Spark & Scala version compatibility `. +``` + +```{eval-rst} +.. warning:: + + There are issues with using package of version 2.3.0/2.3.1 with Greenplum 6.x - connector can + open transaction with ``SELECT * FROM table LIMIT 0`` query, but does not close it, which leads to deadlocks + during write. +``` + +There are several ways to do that. See {ref}`java-packages` for details. + +```{eval-rst} +.. note:: + + If you're uploading package to private package repo, use ``groupId=io.pivotal`` and ``artifactoryId=greenplum-spark_2.12`` + (``2.12`` is Scala version) to give uploaded package a proper name. +``` + +## Connecting to Greenplum + +### Interaction schema + +Spark executors open ports to listen incoming requests. +Greenplum segments are initiating connections to Spark executors using [EXTERNAL TABLE](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-sql_commands-CREATE_EXTERNAL_TABLE.html) +functionality, and send/read data using [gpfdist protocol](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/admin_guide-external-g-using-the-greenplum-parallel-file-server--gpfdist-.html#about-gpfdist-setup-and-performance-1). + +Data is **not** send through Greenplum master. +Greenplum master only receives commands to start reading/writing process, and manages all the metadata (external table location, schema and so on). + +More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/overview.html). + +### Set number of connections + +```{eval-rst} +.. warning:: + + This is very important!!! + + If you don't limit number of connections, you can exceed the `max_connections `_ + limit set on the Greenplum side. It's usually not so high, e.g. 500-1000 connections max, + depending on your Greenplum instance settings and using connection balancers like ``pgbouncer``. + + Consuming all available connections means **nobody** (even admin users) can connect to Greenplum. +``` + +Each job on the Spark executor makes its own connection to Greenplum master node, +so you need to limit number of connections to avoid opening too many of them. + +- Reading about `5-10Gb` of data requires about `3-5` parallel connections. +- Reading about `20-30Gb` of data requires about `5-10` parallel connections. +- Reading about `50Gb` of data requires ~ `10-20` parallel connections. +- Reading about `100+Gb` of data requires `20-30` parallel connections. +- Opening more than `30-50` connections is not recommended. + +Number of connections can be limited by 2 ways: + +- By limiting number of Spark executors and number of cores per-executor. Max number of parallel jobs is `executors * cores`. + +```{eval-rst} +.. tabs:: + + .. code-tab:: py Spark with master=local + + spark = ( + SparkSession.builder + # Spark will run with 5 threads in local mode, allowing up to 5 parallel tasks + .config("spark.master", "local[5]") + .config("spark.executor.cores", 1) + ).getOrCreate() + + .. code-tab:: py Spark with master=yarn or master=k8s, dynamic allocation + + spark = ( + SparkSession.builder + .config("spark.master", "yarn") + # Spark will start MAX 10 executors with 1 core each (dynamically), so max number of parallel jobs is 10 + .config("spark.dynamicAllocation.maxExecutors", 10) + .config("spark.executor.cores", 1) + ).getOrCreate() + + .. code-tab:: py Spark with master=yarn or master=k8s, static allocation + + spark = ( + SparkSession.builder + .config("spark.master", "yarn") + # Spark will start EXACTLY 10 executors with 1 core each, so max number of parallel jobs is 10 + .config("spark.executor.instances", 10) + .config("spark.executor.cores", 1) + ).getOrCreate() +``` + +- By limiting connection pool size user by Spark (**only** for Spark with `master=local`): + +```python +spark = SparkSession.builder.config("spark.master", "local[*]").getOrCreate() + +# No matter how many executors are started and how many cores they have, +# number of connections cannot exceed pool size: +Greenplum( + ..., + extra={ + "pool.maxSize": 10, + }, +) +``` + +See [connection pooling](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/using_the_connector.html#jdbcconnpool) +documentation. + +- By setting {obj}`num_partitions ` + and {obj}`partition_column ` (not recommended). + +### Allowing connection to Greenplum master + +Ask your Greenplum cluster administrator to allow your user to connect to Greenplum master node, +e.g. by updating `pg_hba.conf` file. + +More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/admin_guide-client_auth.html#limiting-concurrent-connections#allowing-connections-to-greenplum-database-0). + +### Set connection port + +#### Spark with `master=k8s` + +Please follow [the official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/configure.html#k8scfg) + +#### Spark with `master=yarn` or `master=local` + +To read data from Greenplum using Spark, following ports should be opened in firewall between Spark and Greenplum: + +- Spark driver and all Spark executors -> port `5432` on Greenplum master node. + + This port number should be set while connecting to Greenplum: + + ```python + greenplum = Greenplum(host="master.host", port=5432, ...) + ``` + +- Greenplum segments -> some port range (e.g. `41000-42000`) **listened by Spark executors**. + + This range should be set in `extra` option: + + ```python + greenplum = Greenplum( + ..., + extra={ + "server.port": "41000-42000", + }, + ) + ``` + + Number of ports in this range is `number of parallel running Spark sessions` * `number of parallel connections per session`. + + Number of connections per session (see below) is usually less than `30` (see above). + + Number of session depends on your environment: + : - For `master=local` only few ones-tens sessions can be started on the same host, depends on available RAM and CPU. + - For `master=yarn` hundreds or thousands of sessions can be started simultaneously, + but they are executing on different cluster nodes, so one port can be opened on different nodes at the same time. + +More details can be found in official documentation: +: - [port requirements](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/sys_reqs.html#network-port-requirements) + - [format of server.port value](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#server.port) + - [port troubleshooting](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/troubleshooting.html#port-errors) + +### Set connection host + +#### Spark with `master=k8s` + +Please follow [the official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/configure.html#k8scfg) + +#### Spark with `master=local` + +By default, Greenplum connector tries to resolve IP of current host, and then pass it as `gpfdist` URL to Greenplum segment. +This may fail in some cases. + +For example, IP can be resolved using `/etc/hosts` content like this: + +```text +127.0.0.1 localhost real-host-name +``` + +```bash +$ hostname -f +localhost + +$ hostname -i +127.0.0.1 +``` + +Reading/writing data to Greenplum will fail with following exception: + +```text +org.postgresql.util.PSQLException: ERROR: connection with gpfdist failed for +"gpfdist://127.0.0.1:49152/local-1709739764667/exec/driver", +effective url: "http://127.0.0.1:49152/local-1709739764667/exec/driver": +error code = 111 (Connection refused); (seg3 slice1 12.34.56.78:10003 pid=123456) +``` + +There are 2 ways to fix that: + +- Explicitly pass your host IP address to connector, like this + + ```python + import os + + # pass here real host IP (accessible from GP segments) + os.environ["HOST_IP"] = "192.168.1.1" + + greenplum = Greenplum( + ..., + extra={ + # connector will read IP from this environment variable + "server.hostEnv": "env.HOST_IP", + }, + spark=spark, + ) + ``` + + More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#server.hostenv). + +- Update `/etc/hosts` file to include real host IP: + + ```text + 127.0.0.1 localhost + # this IP should be accessible from GP segments + 192.168.1.1 driver-host-name + ``` + + So Greenplum connector will properly resolve host IP. + +#### Spark with `master=yarn` + +The same issue with resolving IP address can occur on Hadoop cluster node, but it's tricky to fix, because each node has a different IP. + +There are 3 ways to fix that: + +- Pass node hostname to `gpfdist` URL. So IP will be resolved on segment side: + + ```python + greenplum = Greenplum( + ..., + extra={ + "server.useHostname": "true", + }, + ) + ``` + + But this may fail if Hadoop cluster node hostname cannot be resolved from Greenplum segment side. + + More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#server.usehostname). + +- Set specific network interface to get IP address from: + + ```python + greenplum = Greenplum( + ..., + extra={ + "server.nic": "eth0", + }, + ) + ``` + + You can get list of network interfaces using this command. + + ```{eval-rst} + .. note:: + + This command should be executed on Hadoop cluster node, **not** Spark driver host! + ``` + + ```bash + $ ip address + 1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 + inet 127.0.0.1/8 scope host lo + valid_lft forever preferred_lft forever + 2: eth0: mtu 1500 qdisc fq_codel state UP group default qlen 1000 + inet 192.168.1.1/24 brd 192.168.1.255 scope global dynamic noprefixroute eth0 + valid_lft 83457sec preferred_lft 83457sec + ``` + + Note that in this case **each** Hadoop cluster node node should have network interface with name `eth0`. + + More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/options.html#server.nic). + +- Update `/etc/hosts` on each Hadoop cluster node to include real node IP: + + ```text + 127.0.0.1 localhost + # this IP should be accessible from GP segments + 192.168.1.1 cluster-node-name + ``` + + So Greenplum connector will properly resolve node IP. + +### Set required grants + +Ask your Greenplum cluster administrator to set following grants for a user, +used for creating a connection: + +```{eval-rst} +.. tabs:: + + .. code-tab:: sql Read + Write + + -- get access to get tables metadata & cluster information + GRANT SELECT ON information_schema.tables TO username; + GRANT SELECT ON pg_attribute TO username; + GRANT SELECT ON pg_class TO username; + GRANT SELECT ON pg_namespace TO username; + GRANT SELECT ON pg_settings TO username; + GRANT SELECT ON pg_stats TO username; + GRANT SELECT ON gp_distributed_xacts TO username; + GRANT SELECT ON gp_segment_configuration TO username; + -- Greenplum 5.x only + GRANT SELECT ON gp_distribution_policy TO username; + + -- allow creating external tables in the same schema as source/target table + GRANT USAGE ON SCHEMA myschema TO username; + GRANT CREATE ON SCHEMA myschema TO username; + ALTER USER username CREATEEXTTABLE(type = 'readable', protocol = 'gpfdist') CREATEEXTTABLE(type = 'writable', protocol = 'gpfdist'); + + -- allow read access to specific table (to get column types) + -- allow write access to specific table + GRANT SELECT, INSERT ON myschema.mytable TO username; + + .. code-tab:: sql Read only + + -- get access to get tables metadata & cluster information + GRANT SELECT ON information_schema.tables TO username; + GRANT SELECT ON pg_attribute TO username; + GRANT SELECT ON pg_class TO username; + GRANT SELECT ON pg_namespace TO username; + GRANT SELECT ON pg_settings TO username; + GRANT SELECT ON pg_stats TO username; + GRANT SELECT ON gp_distributed_xacts TO username; + GRANT SELECT ON gp_segment_configuration TO username; + -- Greenplum 5.x only + GRANT SELECT ON gp_distribution_policy TO username; + + -- allow creating external tables in the same schema as source table + GRANT USAGE ON SCHEMA schema_to_read TO username; + GRANT CREATE ON SCHEMA schema_to_read TO username; + -- yes, ``writable`` for reading from GP, because data is written from Greenplum to Spark executor. + ALTER USER username CREATEEXTTABLE(type = 'writable', protocol = 'gpfdist'); + + -- allow read access to specific table + GRANT SELECT ON schema_to_read.table_to_read TO username; +``` + +More details can be found in [official documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/install_cfg.html#role-privileges). diff --git a/mddocs/docs/ru/connection/db_connection/greenplum/read.md b/mddocs/docs/ru/connection/db_connection/greenplum/read.md new file mode 100644 index 000000000..502bbb915 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/greenplum/read.md @@ -0,0 +1,386 @@ +(greenplum-read)= + +# Reading from Greenplum using `DBReader` + +Data can be read from Greenplum to Spark using {obj}`DBReader `. +It also supports {ref}`strategy` for incremental data reading. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`greenplum-types`. +``` + +```{eval-rst} +.. note:: + + Unlike JDBC connectors, *Greenplum connector for Spark* does not support + executing **custom** SQL queries using ``.sql`` method. Connector can be used to only read data from a table or view. +``` + +## Supported DBReader features + +- ✅︎ `columns` (see note below) +- ✅︎ `where` (see note below) +- ✅︎ `hwm` (see note below), supported strategies: +- - ✅︎ {ref}`snapshot-strategy` +- - ✅︎ {ref}`incremental-strategy` +- - ✅︎ {ref}`snapshot-batch-strategy` +- - ✅︎ {ref}`incremental-batch-strategy` +- ❌ `hint` (is not supported by Greenplum) +- ❌ `df_schema` +- ✅︎ `options` (see {obj}`Greenplum.ReadOptions `) + +```{eval-rst} +.. warning:: + + In case of Greenplum connector, ``DBReader`` does not generate raw ``SELECT`` query. Instead it relies on Spark SQL syntax + which in some cases (using column projection and predicate pushdown) can be converted to Greenplum SQL. + + So ``columns``, ``where`` and ``hwm.expression`` should be specified in `Spark SQL `_ syntax, + not Greenplum SQL. + + This is OK: + + .. code-block:: python + + DBReader( + columns=[ + "some_column", + # this cast is executed on Spark side + "CAST(another_column AS STRING)", + ], + # this predicate is parsed by Spark, and can be pushed down to Greenplum + where="some_column LIKE 'val1%'", + ) + + This is will fail: + + .. code-block:: python + + DBReader( + columns=[ + "some_column", + # Spark does not have `text` type + "CAST(another_column AS text)", + ], + # Spark does not support ~ syntax for regexp matching + where="some_column ~ 'val1.*'", + ) +``` + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Greenplum +from onetl.db import DBReader + +greenplum = Greenplum(...) + +reader = DBReader( + connection=greenplum, + source="schema.table", + columns=["id", "key", "CAST(value AS string) value", "updated_dt"], + where="key = 'something'", +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Greenplum +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +greenplum = Greenplum(...) + +reader = DBReader( + connection=greenplum, + source="schema.table", + columns=["id", "key", "CAST(value AS string) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="greenplum_hwm", expression="updated_dt"), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Interaction schema + +High-level schema is described in {ref}`greenplum-prerequisites`. You can find detailed interaction schema below. + +```{eval-rst} +.. dropdown:: Spark <-> Greenplum interaction during DBReader.run() + + .. plantuml:: + + @startuml + title Greenplum master <-> Spark driver + box "Spark" + participant "Spark driver" + participant "Spark executor1" + participant "Spark executor2" + participant "Spark executorN" + end box + + box "Greenplum" + participant "Greenplum master" + participant "Greenplum segment1" + participant "Greenplum segment2" + participant "Greenplum segmentN" + end box + + == Greenplum.check() == + + activate "Spark driver" + "Spark driver" -> "Greenplum master" ++ : CONNECT + + "Spark driver" --> "Greenplum master" : CHECK IF TABLE EXISTS gp_table + "Greenplum master" --> "Spark driver" : TABLE EXISTS + "Spark driver" -> "Greenplum master" : SHOW SCHEMA FOR gp_table + "Greenplum master" --> "Spark driver" : (id bigint, col1 int, col2 text, ...) + + == DBReader.run() == + + "Spark driver" -> "Spark executor1" ++ : START EXECUTOR FOR df(id bigint, col1 int, col2 text, ...) PARTITION 1 + "Spark driver" -> "Spark executor2" ++ : START EXECUTOR FOR df(id bigint, col1 int, col2 text, ...) PARTITION 2 + "Spark driver" -> "Spark executorN" ++ : START EXECUTOR FOR df(id bigint, col1 int, col2 text, ...) PARTITION N + + note right of "Spark driver" : This is done in parallel,\nexecutors are independent\n|\n|\n|\nV + "Spark executor1" -> "Greenplum master" ++ : CREATE WRITABLE EXTERNAL TABLE spark_executor1 (id bigint, col1 int, col2 text, ...) USING address=executor1_host:executor1_port;\nINSERT INTO EXTERNAL TABLE spark_executor1 FROM gp_table WHERE gp_segment_id = 1 + note right of "Greenplum master" : Each white vertical line here is a opened connection to master.\nUsually, **N+1** connections are created from Spark to Greenplum master + "Greenplum master" --> "Greenplum segment1" ++ : SELECT DATA FROM gp_table_data_on_segment1 TO spark_executor1 + note right of "Greenplum segment1" : No direct requests between Greenplum segments & Spark.\nData transfer is always initiated by Greenplum segments. + + "Spark executor2" -> "Greenplum master" ++ : CREATE WRITABLE EXTERNAL TABLE spark_executor2 (id bigint, col1 int, col2 text, ...) USING address=executor2_host:executor2_port;\nINSERT INTO EXTERNAL TABLE spark_executor2 FROM gp_table WHERE gp_segment_id = 2 + "Greenplum master" --> "Greenplum segment2" ++ : SELECT DATA FROM gp_table_data_on_segment2 TO spark_executor2 + + "Spark executorN" -> "Greenplum master" ++ : CREATE WRITABLE EXTERNAL TABLE spark_executorN (id bigint, col1 int, col2 text, ...) USING address=executorN_host:executorN_port;\nINSERT INTO EXTERNAL TABLE spark_executorN FROM gp_table WHERE gp_segment_id = N + "Greenplum master" --> "Greenplum segmentN" ++ : SELECT DATA FROM gp_table_data_on_segmentN TO spark_executorN + + "Greenplum segment1" ->o "Spark executor1" -- : INITIALIZE CONNECTION TO Spark executor1\nPUSH DATA TO Spark executor1 + note left of "Spark executor1" : Circle is an open GPFDIST port,\nlistened by executor + + "Greenplum segment2" ->o "Spark executor2" -- : INITIALIZE CONNECTION TO Spark executor2\nPUSH DATA TO Spark executor2 + "Greenplum segmentN" ->o "Spark executorN" -- : INITIALIZE CONNECTION TO Spark executorN\nPUSH DATA TO Spark executorN + + == Spark.stop() == + + "Spark executor1" --> "Greenplum master" : DROP TABLE spark_executor1 + deactivate "Greenplum master" + "Spark executor2" --> "Greenplum master" : DROP TABLE spark_executor2 + deactivate "Greenplum master" + "Spark executorN" --> "Greenplum master" : DROP TABLE spark_executorN + deactivate "Greenplum master" + + "Spark executor1" --> "Spark driver" -- : DONE + "Spark executor2" --> "Spark driver" -- : DONE + "Spark executorN" --> "Spark driver" -- : DONE + + "Spark driver" --> "Greenplum master" : CLOSE CONNECTION + deactivate "Greenplum master" + deactivate "Spark driver" + @enduml +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Greenplum to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Greenplum to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +### Read data in parallel + +`DBReader` in case of Greenplum connector requires view or table to have a column which is used by Spark +for parallel reads. + +Choosing proper column allows each Spark executor to read only part of data stored in the specified segment, +avoiding moving large amounts of data between segments, which improves reading performance. + +#### Using `gp_segment_id` + +By default, `DBReader` will use [gp_segment_id](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/troubleshooting.html#reading-from-a-view) +column for parallel data reading. Each DataFrame partition will contain data of a specific Greenplum segment. + +This allows each Spark executor read only data from specific Greenplum segment, avoiding moving large amounts of data between segments. + +If view is used, it is recommended to include `gp_segment_id` column to this view: + +```{eval-rst} +.. dropdown:: Reading from view with gp_segment_id column + + .. code-block:: python + + from onetl.connection import Greenplum + from onetl.db import DBReader + + greenplum = Greenplum(...) + + greenplum.execute( + """ + CREATE VIEW schema.view_with_gp_segment_id AS + SELECT + id, + some_column, + another_column, + gp_segment_id -- IMPORTANT + FROM schema.some_table + """, + ) + + reader = DBReader( + connection=greenplum, + source="schema.view_with_gp_segment_id", + ) + df = reader.run() +``` + +#### Using custom `partition_column` + +Sometimes table or view is lack of `gp_segment_id` column, but there is some column +with value range correlated with Greenplum segment distribution. + +In this case, custom column can be used instead: + +```{eval-rst} +.. dropdown:: Reading from view with custom partition_column + + .. code-block:: python + + from onetl.connection import Greenplum + from onetl.db import DBReader + + greenplum = Greenplum(...) + + greenplum.execute( + """ + CREATE VIEW schema.view_with_partition_column AS + SELECT + id, + some_column, + part_column -- correlated to greenplum segment ID + FROM schema.some_table + """, + ) + + reader = DBReader( + connection=greenplum, + source="schema.view_with_partition_column", + options=Greenplum.ReadOptions( + # parallelize data using specified column + partitionColumn="part_column", + # create 10 Spark tasks, each will read only part of table data + partitions=10, + ), + ) + df = reader.run() +``` + +#### Reading `DISTRIBUTED REPLICATED` tables + +Replicated tables do not have `gp_segment_id` column at all, so you need to set `partition_column` to some column name +of type integer/bigint/smallint. + +### Parallel `JOIN` execution + +In case of using views which require some data motion between Greenplum segments, like `JOIN` queries, another approach should be used. + +Each Spark executor N will run the same query, so each of N query will start its own JOIN process, leading to really heavy load on Greenplum segments. +**This should be avoided**. + +Instead is recommended to run `JOIN` query on Greenplum side, save the result to an intermediate table, +and then read this table using `DBReader`: + +```{eval-rst} +.. dropdown:: Reading from view using intermediate table + + .. code-block:: python + + from onetl.connection import Greenplum + from onetl.db import DBReader + + greenplum = Greenplum(...) + + greenplum.execute( + """ + CREATE UNLOGGED TABLE schema.intermediate_table AS + SELECT + id, + tbl1.col1, + tbl1.data, + tbl2.another_data + FROM + schema.table1 as tbl1 + JOIN + schema.table2 as tbl2 + ON + tbl1.col1 = tbl2.col2 + WHERE ... + """, + ) + + reader = DBReader( + connection=greenplum, + source="schema.intermediate_table", + ) + df = reader.run() + + # write dataframe somethere + + greenplum.execute( + """ + DROP TABLE schema.intermediate_table + """, + ) +``` + +```{eval-rst} +.. warning:: + + **NEVER** do that: + + .. code-block:: python + + df1 = DBReader(connection=greenplum, target="public.table1", ...).run() + df2 = DBReader(connection=greenplum, target="public.table2", ...).run() + + joined_df = df1.join(df2, on="col") + + This will lead to sending all the data from both ``table1`` and ``table2`` to Spark executor memory, and then ``JOIN`` + will be performed on Spark side, not inside Greenplum. This is **VERY** inefficient. +``` + +#### `TEMPORARY` tables notice + +Someone could think that writing data from view or result of `JOIN` to `TEMPORARY` table, +and then passing it to `DBReader`, is an efficient way to read data from Greenplum. This is because temp tables are not generating WAL files, +and are automatically deleted after finishing the transaction. + +That will **NOT** work. Each Spark executor establishes its own connection to Greenplum. +And each connection starts its own transaction which means that every executor will read empty temporary table. + +You should use [UNLOGGED](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-sql_commands-CREATE_TABLE.html) tables +to write data to intermediate table without generating WAL logs. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.greenplum.options +``` + +```{eval-rst} +.. autopydantic_model:: GreenplumReadOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/greenplum/types.md b/mddocs/docs/ru/connection/db_connection/greenplum/types.md new file mode 100644 index 000000000..1f9d83627 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/greenplum/types.md @@ -0,0 +1,406 @@ +(greenplum-types)= + +# Greenplum \<-> Spark type mapping + +```{eval-rst} +.. note:: + + The results below are valid for Spark 3.2.4, and may differ on other Spark versions. +``` + +## Type detection & casting + +Spark's DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from Greenplum + +This is how Greenplum connector performs this: + +- Execute query `SELECT * FROM table LIMIT 0` [^footnote-1]. +- For each column in query result get column name and Greenplum type. +- Find corresponding `Greenplum type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- Use Spark column projection and predicate pushdown features to build a final query. +- Create DataFrame from generated query with inferred schema. + +[^footnote-1]: Yes, **all columns of a table**, not just selected ones. + This means that if source table **contains** columns with unsupported type, the entire table cannot be read. + +### Writing to some existing Greenplum table + +This is how Greenplum connector performs this: + +- Get names of columns in DataFrame. +- Perform `SELECT * FROM table LIMIT 0` query. +- For each column in query result get column name and Greenplum type. +- Match table columns with DataFrame columns (by name, case insensitive). + If some column is present only in target table, but not in DataFrame (like `DEFAULT` or `SERIAL` column), and vice versa, raise an exception. + See [Explicit type cast]. +- Find corresponding `Spark type` → `Greenplumtype (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- If `Greenplumtype (write)` match `Greenplum type (read)`, no additional casts will be performed, DataFrame column will be written to Greenplum as is. +- If `Greenplumtype (write)` does not match `Greenplum type (read)`, DataFrame column will be casted to target column type **on Greenplum side**. For example, you can write column with text data to `json` column which Greenplum connector currently does not support. + +### Create new table using Spark + +```{eval-rst} +.. warning:: + + ABSOLUTELY NOT RECOMMENDED! +``` + +This is how Greenplum connector performs this: + +- Find corresponding `Spark type` → `Greenplum type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- Generate DDL for creating table in Greenplum, like `CREATE TABLE (col1 ...)`, and run it. +- Write DataFrame to created table as is. + +More details [can be found here](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/write_to_gpdb.html). + +But Greenplum connector support only limited number of types and almost no custom clauses (like `PARTITION BY`). +So instead of relying on Spark to create tables: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + writer = DBWriter( + connection=greenplum, + target="public.table", + options=Greenplum.WriteOptions( + if_exists="append", + # by default distribution is random + distributedBy="id", + # partitionBy is not supported + ), + ) + writer.run(df) +``` + +Always prefer creating table with desired DDL **BEFORE WRITING DATA**: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + greenplum.execute( + """ + CREATE TABLE public.table ( + id int32, + business_dt timestamp(6), + value json + ) + PARTITION BY RANGE (business_dt) + DISTRIBUTED BY id + """, + ) + + writer = DBWriter( + connection=greenplum, + target="public.table", + options=Greenplum.WriteOptions(if_exists="append"), + ) + writer.run(df) +``` + +See Greenplum [CREATE TABLE](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-sql_commands-CREATE_TABLE.html) documentation. + +## Supported types + +See: +: - [official connector documentation](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/2.3/greenplum-connector-spark/reference-datatype_mapping.html) + - [list of Greenplum types](https://docs.vmware.com/en/VMware-Greenplum/7/greenplum-database/ref_guide-data_types.html) + +### Numeric types + +```{eval-rst} ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | ++==================================+===================================+===============================+=========================+ +| ``decimal`` | ``DecimalType(P=38, S=18)`` | ``decimal(P=38, S=18)`` | ``decimal`` (unbounded) | ++----------------------------------+-----------------------------------+-------------------------------+ | +| ``decimal(P=0..38)`` | ``DecimalType(P=0..38, S=0)`` | ``decimal(P=0..38, S=0)`` | | ++----------------------------------+-----------------------------------+-------------------------------+ | +| ``decimal(P=0..38, S=0..38)`` | ``DecimalType(P=0..38, S=0..38)`` | ``decimal(P=0..38, S=0..38)`` | | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``decimal(P=39.., S=0..)`` | unsupported [2]_ | | | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``real`` | ``FloatType()`` | ``real`` | ``real`` | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``double precision`` | ``DoubleType()`` | ``double precision`` | ``double precision`` | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``-`` | ``ByteType()`` | unsupported | unsupported | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``smallint`` | ``ShortType()`` | ``smallint`` | ``smallint`` | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``integer`` | ``IntegerType()`` | ``integer`` | ``integer`` | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``bigint`` | ``LongType()`` | ``bigint`` | ``bigint`` | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``money`` | unsupported | | | ++----------------------------------+ | | | +| ``int4range`` | | | | ++----------------------------------+ | | | +| ``int8range`` | | | | ++----------------------------------+ | | | +| ``numrange`` | | | | ++----------------------------------+ | | | +| ``int2vector`` | | | | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +``` + +[^footnote-2]: Greenplum support decimal types with unlimited precision. + + But Spark's `DecimalType(P, S)` supports maximum `P=38` (128 bit). It is impossible to read, write or operate with values of larger precision, + this leads to an exception. + +### Temporal types + +```{eval-rst} ++------------------------------------+-------------------------+-----------------------+-------------------------+ +| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | ++====================================+=========================+=======================+=========================+ +| ``date`` | ``DateType()`` | ``date`` | ``date`` | ++------------------------------------+-------------------------+-----------------------+-------------------------+ +| ``time`` | ``TimestampType()``, | ``timestamp`` | ``timestamp`` | ++------------------------------------+ time format quirks [3]_ | | | +| ``time(0..6)`` | | | | ++------------------------------------+ | | | +| ``time with time zone`` | | | | ++------------------------------------+ | | | +| ``time(0..6) with time zone`` | | | | ++------------------------------------+-------------------------+-----------------------+-------------------------+ +| ``timestamp`` | ``TimestampType()`` | ``timestamp`` | ``timestamp`` | ++------------------------------------+ | | | +| ``timestamp(0..6)`` | | | | ++------------------------------------+ | | | +| ``timestamp with time zone`` | | | | ++------------------------------------+ | | | +| ``timestamp(0..6) with time zone`` | | | | ++------------------------------------+-------------------------+-----------------------+-------------------------+ +| ``interval`` or any precision | unsupported | | | ++------------------------------------+ | | | +| ``daterange`` | | | | ++------------------------------------+ | | | +| ``tsrange`` | | | | ++------------------------------------+ | | | +| ``tstzrange`` | | | | ++------------------------------------+-------------------------+-----------------------+-------------------------+ +``` + +```{eval-rst} +.. warning:: + + Note that types in Greenplum and Spark have different value ranges: + + +----------------+---------------------------------+----------------------------------+---------------------+--------------------------------+--------------------------------+ + | Greenplum type | Min value | Max value | Spark type | Min value | Max value | + +================+=================================+==================================+=====================+================================+================================+ + | ``date`` | ``-4713-01-01`` | ``5874897-01-01`` | ``DateType()`` | ``0001-01-01`` | ``9999-12-31`` | + +----------------+---------------------------------+----------------------------------+---------------------+--------------------------------+--------------------------------+ + | ``timestamp`` | ``-4713-01-01 00:00:00.000000`` | ``294276-12-31 23:59:59.999999`` | ``TimestampType()`` | ``0001-01-01 00:00:00.000000`` | ``9999-12-31 23:59:59.999999`` | + +----------------+---------------------------------+----------------------------------+ | | | + | ``time`` | ``00:00:00.000000`` | ``24:00:00.000000`` | | | | + +----------------+---------------------------------+----------------------------------+---------------------+--------------------------------+--------------------------------+ + + So not all of values can be read from Greenplum to Spark. + + References: + * `Greenplum types documentation `_ + * `Spark DateType documentation `_ + * `Spark TimestampType documentation `_ +``` + +[^footnote-3]: `time` type is the same as `timestamp` with date `1970-01-01`. So instead of reading data from Postgres like `23:59:59` + it is actually read `1970-01-01 23:59:59`, and vice versa. + +### String types + +```{eval-rst} ++-----------------------------+------------------+-----------------------+-------------------------+ +| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | ++=============================+==================+=======================+=========================+ +| ``character`` | ``StringType()`` | ``text`` | ``text`` | ++-----------------------------+ | | | +| ``character(N)`` | | | | ++-----------------------------+ | | | +| ``character varying`` | | | | ++-----------------------------+ | | | +| ``character varying(N)`` | | | | ++-----------------------------+ | | | +| ``text`` | | | | ++-----------------------------+ | | | +| ``xml`` | | | | ++-----------------------------+ | | | +| ``CREATE TYPE ... AS ENUM`` | | | | ++-----------------------------+------------------+-----------------------+-------------------------+ +| ``json`` | unsupported | | | ++-----------------------------+ | | | +| ``jsonb`` | | | | ++-----------------------------+------------------+-----------------------+-------------------------+ +``` + +### Binary types + +```{eval-rst} ++--------------------------+-------------------+-----------------------+-------------------------+ +| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | ++==========================+===================+=======================+=========================+ +| ``boolean`` | ``BooleanType()`` | ``boolean`` | ``boolean`` | ++--------------------------+-------------------+-----------------------+-------------------------+ +| ``bit`` | unsupported | | | ++--------------------------+ | | | +| ``bit(N)`` | | | | ++--------------------------+ | | | +| ``bit varying`` | | | | ++--------------------------+ | | | +| ``bit varying(N)`` | | | | ++--------------------------+-------------------+-----------------------+-------------------------+ +| ``bytea`` | unsupported [4]_ | | | ++--------------------------+-------------------+-----------------------+-------------------------+ +| ``-`` | ``BinaryType()`` | ``bytea`` | ``bytea`` | ++--------------------------+-------------------+-----------------------+-------------------------+ +``` + +[^footnote-4]: Yes, that's weird. + +### Struct types + +```{eval-rst} ++--------------------------------+------------------+-----------------------+-------------------------+ +| Greenplum type (read) | Spark type | Greenplumtype (write) | Greenplum type (create) | ++================================+==================+=======================+=========================+ +| ``T[]`` | unsupported | | | ++--------------------------------+------------------+-----------------------+-------------------------+ +| ``-`` | ``ArrayType()`` | unsupported | | ++--------------------------------+------------------+-----------------------+-------------------------+ +| ``CREATE TYPE sometype (...)`` | ``StringType()`` | ``text`` | ``text`` | ++--------------------------------+------------------+-----------------------+-------------------------+ +| ``-`` | ``StructType()`` | unsupported | | ++--------------------------------+------------------+ | | +| ``-`` | ``MapType()`` | | | ++--------------------------------+------------------+-----------------------+-------------------------+ +``` + +## Unsupported types + +Columns of these types cannot be read/written by Spark: +: - `cidr` + - `inet` + - `macaddr` + - `macaddr8` + - `circle` + - `box` + - `line` + - `lseg` + - `path` + - `point` + - `polygon` + - `tsvector` + - `tsquery` + - `uuid` + +The is a way to avoid this - just cast unsupported types to `text`. But the way this can be done is not a straightforward. + +## Explicit type cast + +### `DBReader` + +Direct casting of Greenplum types is not supported by DBReader due to the connector’s implementation specifics. + +```python +reader = DBReader( + connection=greenplum, + # will fail + columns=["CAST(unsupported_column AS text)"], +) +``` + +But there is a workaround - create a view with casting unsupported column to text (or any other supported type). +For example, you can use [to_json](https://www.postgresql.org/docs/current/functions-json.html) Postgres function to convert column of any type to string representation and then parse this column on Spark side using {obj}`JSON.parse_column ` method. + +```python +from pyspark.sql.types import ArrayType, IntegerType + +from onetl.connection import Greenplum +from onetl.db import DBReader +from onetl.file.format import JSON + +greenplum = Greenplum(...) + +greenplum.execute( + """ + CREATE VIEW schema.view_with_json_column AS + SELECT + id, + supported_column, + to_json(array_column) array_column_as_json, + gp_segment_id -- ! important ! + FROM + schema.table_with_unsupported_columns + """, +) + +# create dataframe using this view +reader = DBReader( + connection=greenplum, + source="schema.view_with_json_column", +) +df = reader.run() + +# Define the schema for the JSON data +json_scheme = ArrayType(IntegerType()) + +df = df.select( + df.id, + df.supported_column, + JSON().parse_column(df.array_column_as_json, json_scheme).alias("array_column"), +) +``` + +### `DBWriter` + +To write data to a `text` or `json` column in a Greenplum table, use {obj}`JSON.serialize_column ` method. + +```python +from onetl.connection import Greenplum +from onetl.db import DBWriter +from onetl.file.format import JSON + +greenplum = Greenplum(...) + +greenplum.execute( + """ + CREATE TABLE schema.target_table ( + id int, + supported_column timestamp, + array_column_as_json jsonb, -- or text + ) + DISTRIBUTED BY id + """, +) + +write_df = df.select( + df.id, + df.supported_column, + JSON().serialize_column(df.array_column).alias("array_column_json"), +) + +writer = DBWriter( + connection=greenplum, + target="schema.target_table", +) +writer.run(write_df) +``` + +Then you can parse this column on Greenplum side: + +```sql +SELECT + id, + supported_column, + -- access first item of an array + array_column_as_json->0 +FROM + schema.target_table +``` diff --git a/mddocs/docs/ru/connection/db_connection/greenplum/write.md b/mddocs/docs/ru/connection/db_connection/greenplum/write.md new file mode 100644 index 000000000..af367ad75 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/greenplum/write.md @@ -0,0 +1,140 @@ +(greenplum-write)= + +# Writing to Greenplum using `DBWriter` + +For writing data to Greenplum, use {obj}`DBWriter ` +with {obj}`GreenplumWriteOptions `. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`greenplum-types`. +``` + +```{eval-rst} +.. warning:: + + It is always recommended to create table explicitly using :ref:`Greenplum.execute ` + instead of relying on Spark's table DDL generation. + + This is because Spark's DDL generator can create columns with different types than it is expected. +``` + +## Examples + +```python +from onetl.connection import Greenplum +from onetl.db import DBWriter + +greenplum = Greenplum(...) + +df = ... # data is here + +writer = DBWriter( + connection=greenplum, + target="schema.table", + options=Greenplum.WriteOptions( + if_exists="append", + # by default distribution is random + distributedBy="id", + # partitionBy is not supported + ), +) + +writer.run(df) +``` + +## Interaction schema + +High-level schema is described in {ref}`greenplum-prerequisites`. You can find detailed interaction schema below. + +```{eval-rst} +.. dropdown:: Spark <-> Greenplum interaction during DBWriter.run() + + .. plantuml:: + + @startuml + title Greenplum master <-> Spark driver + box "Spark" + participant "Spark driver" + participant "Spark executor1" + participant "Spark executor2" + participant "Spark executorN" + end box + + box "Greenplum" + participant "Greenplum master" + participant "Greenplum segment1" + participant "Greenplum segment2" + participant "Greenplum segmentN" + end box + + == Greenplum.check() == + + activate "Spark driver" + "Spark driver" -> "Greenplum master" ++ : CONNECT + "Spark driver" --> "Greenplum master" ++ : CHECK IF TABLE EXISTS gp_table + "Greenplum master" --> "Spark driver" : TABLE NOT EXISTS + + == DBWriter.run(df) == + + "Spark driver" -> "Spark executor1" ++ : START EXECUTOR FOR df(id bigint, col1 int, col2 text, ...) PARTITION 1 + "Spark driver" -> "Spark executor2" ++ : START EXECUTOR FOR df(id bigint, col1 int, col2 text, ...) PARTITION 2 + "Spark driver" -> "Spark executorN" ++ : START EXECUTOR FOR df(id bigint, col1 int, col2 text, ...) PARTITION N + + note right of "Spark driver" : This is done in parallel,\nexecutors are independent\n|\n|\n|\nV + "Spark executor1" -> "Greenplum master" ++ : CREATE READABLE EXTERNAL TABLE spark_executor1 (id bigint, col1 int, col2 text, ...) USING address=executor1_host:executor1_port;\nINSERT INTO gp_table FROM spark_executor1 + note right of "Greenplum master" : Each white vertical line here is a opened connection to master.\nUsually, **N+1** connections are created from Spark to Greenplum master + "Greenplum master" --> "Greenplum segment1" ++ : SELECT DATA FROM spark_executor1 TO gp_table_data_on_segment1 + note right of "Greenplum segment1" : No direct requests between Greenplum segments & Spark.\nData transfer is always initiated by Greenplum segments. + + "Spark executor2" -> "Greenplum master" ++ : CREATE READABLE EXTERNAL TABLE spark_executor2 (id bigint, col1 int, col2 text, ...) USING address=executor2_host:executor2_port;\nINSERT INTO gp_table FROM spark_executor2 + "Greenplum master" --> "Greenplum segment2" ++ : SELECT DATA FROM spark_executor2 TO gp_table_data_on_segment2 + + "Spark executorN" -> "Greenplum master" ++ : CREATE READABLE EXTERNAL TABLE spark_executorN (id bigint, col1 int, col2 text, ...) USING address=executorN_host:executorN_port;\nINSERT INTO gp_table FROM spark_executorN + "Greenplum master" --> "Greenplum segmentN" ++ : SELECT DATA FROM spark_executorN TO gp_table_data_on_segmentN + + "Greenplum segment1" -->o "Spark executor1" : INITIALIZE CONNECTION TO Spark executor1 + "Spark executor1" -> "Greenplum segment1" : READ DATA FROM Spark executor1 + note left of "Spark executor1" : Circle is an open GPFDIST port,\nlistened by executor + deactivate "Greenplum segment1" + + "Greenplum segment2" -->o "Spark executor2" : INITIALIZE CONNECTION TO Spark executor2 + "Spark executor2" -> "Greenplum segment2" : READ DATA FROM Spark executor2 + deactivate "Greenplum segment2" + + "Greenplum segmentN" -->o "Spark executorN" : INITIALIZE CONNECTION TO Spark executorN + "Spark executorN" -> "Greenplum segmentN" : READ DATA FROM Spark executorN + deactivate "Greenplum segmentN" + + == Finished == + + "Spark executor1" --> "Greenplum master" : DROP TABLE spark_executor1 + deactivate "Greenplum master" + "Spark executor2" --> "Greenplum master" : DROP TABLE spark_executor2 + deactivate "Greenplum master" + "Spark executorN" --> "Greenplum master" : DROP TABLE spark_executorN + deactivate "Greenplum master" + + "Spark executor1" --> "Spark driver" -- : DONE + "Spark executor2" --> "Spark driver" -- : DONE + "Spark executorN" --> "Spark driver" -- : DONE + + "Spark driver" --> "Greenplum master" : CLOSE CONNECTION + deactivate "Greenplum master" + deactivate "Spark driver" + @enduml +``` + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.greenplum.options +``` + +```{eval-rst} +.. autopydantic_model:: GreenplumWriteOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/hive/connection.md b/mddocs/docs/ru/connection/db_connection/hive/connection.md new file mode 100644 index 000000000..a9a0cfc55 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/hive/connection.md @@ -0,0 +1,13 @@ +(hive-connection)= + +# Hive Connection + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.hive.connection +``` + +```{eval-rst} +.. autoclass:: Hive + :members: get_current, check + :member-order: bysource +``` diff --git a/mddocs/docs/ru/connection/db_connection/hive/execute.md b/mddocs/docs/ru/connection/db_connection/hive/execute.md new file mode 100644 index 000000000..d7b0694c4 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/hive/execute.md @@ -0,0 +1,55 @@ +(hive-execute)= + +# Executing statements in Hive + +Use `Hive.execute(...)` to execute DDL and DML operations. + +## Syntax support + +This method supports **any** query syntax supported by Hive, like: + +- ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +- ✅︎ `LOAD DATA ...`, and so on +- ✅︎ `ALTER ...` +- ✅︎ `INSERT INTO ... SELECT ...`, and so on +- ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, and so on +- ✅︎ `MSCK REPAIR TABLE ...`, and so on +- ✅︎ other statements not mentioned here +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +```{eval-rst} +.. warning:: + + Actually, query should be written using `SparkSQL `_ syntax, not HiveQL. +``` + +## Examples + +```python +from onetl.connection import Hive + +hive = Hive(...) + +hive.execute("DROP TABLE schema.table") +hive.execute( + """ + CREATE TABLE schema.table ( + id NUMBER, + key VARCHAR, + value DOUBLE + ) + PARTITION BY (business_date DATE) + STORED AS orc + """ +) +``` + +### Details + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.hive.connection +``` + +```{eval-rst} +.. automethod:: Hive.execute +``` diff --git a/mddocs/docs/ru/connection/db_connection/hive/index.md b/mddocs/docs/ru/connection/db_connection/hive/index.md new file mode 100644 index 000000000..bb8e5ebcc --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/hive/index.md @@ -0,0 +1,28 @@ +(hive)= + +# Hive + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +connection +``` + +```{toctree} +:caption: Operations +:maxdepth: 1 + +read +sql +write +execute +``` + +```{toctree} +:caption: For developers +:maxdepth: 1 + +slots +``` diff --git a/mddocs/docs/ru/connection/db_connection/hive/prerequisites.md b/mddocs/docs/ru/connection/db_connection/hive/prerequisites.md new file mode 100644 index 000000000..79ff0e5dc --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/hive/prerequisites.md @@ -0,0 +1,134 @@ +(hive-prerequisites)= + +# Prerequisites + +```{eval-rst} +.. note:: + + onETL's Hive connection is actually SparkSession with access to `Hive Thrift Metastore `_ + and HDFS/S3. + All data motion is made using Spark. Hive Metastore is used only to store tables and partitions metadata. + + This connector does **NOT** require Hive server. It also does **NOT** use Hive JDBC connector. +``` + +## Version Compatibility + +- Hive Metastore version: + : - Officially declared: 0.12 - 3.1.3 (may require to add proper .jar file explicitly) + - Actually tested: 1.2.100, 2.3.10, 3.1.3 +- Spark versions: 2.3.x - 3.5.x +- Java versions: 8 - 20 + +See [official documentation](https://spark.apache.org/docs/latest/sql-data-sources-hive-tables.html). + +## Installing PySpark + +To use Hive connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Connecting to Hive Metastore + +```{eval-rst} +.. note:: + + If you're using managed Hadoop cluster, skip this step, as all Spark configs are should already present on the host. +``` + +Create `$SPARK_CONF_DIR/hive-site.xml` with Hive Metastore URL: + +```xml + + + + + hive.metastore.uris + thrift://metastore.host.name:9083 + + +``` + +Create `$SPARK_CONF_DIR/core-site.xml` with warehouse location ,e.g. HDFS IPC port of Hadoop namenode, or S3 bucket address & credentials: + +```{eval-rst} +.. tabs:: + + .. code-tab:: xml HDFS + + + + + + fs.defaultFS + hdfs://myhadoopcluster:9820 + + + + .. code-tab:: xml S3 + + + + + !-- See https://hadoop.apache.org/docs/current/hadoop-aws/tools/hadoop-aws/index.html#General_S3A_Client_configuration + + fs.defaultFS + s3a://mys3bucket/ + + + fs.s3a.bucket.mybucket.endpoint + http://s3.somain + + + fs.s3a.bucket.mybucket.connection.ssl.enabled + false + + + fs.s3a.bucket.mybucket.path.style.access + true + + + fs.s3a.bucket.mybucket.aws.credentials.provider + org.apache.hadoop.fs.s3a.SimpleAWSCredentialsProvider + + + fs.s3a.bucket.mybucket.access.key + some-user + + + fs.s3a.bucket.mybucket.secret.key + mysecrettoken + + +``` + +## Using Kerberos + +Some of Hadoop managed clusters use Kerberos authentication. In this case, you should call [kinit](https://web.mit.edu/kerberos/krb5-1.12/doc/user/user_commands/kinit.html) command +**BEFORE** starting Spark session to generate Kerberos ticket. See {ref}`install-kerberos`. + +Sometimes it is also required to pass keytab file to Spark config, allowing Spark executors to generate own Kerberos tickets: + +```{eval-rst} +.. tabs:: + + .. code-tab:: python Spark 3 + + SparkSession.builder + .option("spark.kerberos.access.hadoopFileSystems", "hdfs://namenode1.domain.com:9820,hdfs://namenode2.domain.com:9820") + .option("spark.kerberos.principal", "user") + .option("spark.kerberos.keytab", "/path/to/keytab") + .gerOrCreate() + + .. code-tab:: python Spark 2 + + SparkSession.builder + .option("spark.yarn.access.hadoopFileSystems", "hdfs://namenode1.domain.com:9820,hdfs://namenode2.domain.com:9820") + .option("spark.yarn.principal", "user") + .option("spark.yarn.keytab", "/path/to/keytab") + .gerOrCreate() +``` + +See [Spark security documentation](https://spark.apache.org/docs/latest/security.html#kerberos) +for more details. diff --git a/mddocs/docs/ru/connection/db_connection/hive/read.md b/mddocs/docs/ru/connection/db_connection/hive/read.md new file mode 100644 index 000000000..82433c988 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/hive/read.md @@ -0,0 +1,95 @@ +(hive-read)= + +# Reading from Hive using `DBReader` + +{obj}`DBReader ` supports {ref}`strategy` for incremental data reading, +but does not support custom queries, like `JOIN`. + +## Supported DBReader features + +- ✅︎ `columns` +- ✅︎ `where` +- ✅︎ `hwm`, supported strategies: +- - ✅︎ {ref}`snapshot-strategy` +- - ✅︎ {ref}`incremental-strategy` +- - ✅︎ {ref}`snapshot-batch-strategy` +- - ✅︎ {ref}`incremental-batch-strategy` +- ❌ `hint` (is not supported by Hive) +- ❌ `df_schema` +- ❌ `options` (only Spark config params are used) + +```{eval-rst} +.. warning:: + + Actually, ``columns``, ``where`` and ``hwm.expression`` should be written using `SparkSQL `_ syntax, + not HiveQL. +``` + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Hive +from onetl.db import DBReader + +hive = Hive(...) + +reader = DBReader( + connection=hive, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Hive +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +hive = Hive(...) + +reader = DBReader( + connection=hive, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="hive_hwm", expression="updated_dt"), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Use column-based write formats + +Prefer these write formats: +: - [ORC](https://spark.apache.org/docs/latest/sql-data-sources-orc.html) + - [Parquet](https://spark.apache.org/docs/latest/sql-data-sources-parquet.html) + - [Iceberg](https://iceberg.apache.org/spark-quickstart/) + - [Hudi](https://hudi.apache.org/docs/quick-start-guide/) + - [Delta](https://docs.delta.io/latest/quick-start.html#set-up-apache-spark-with-delta-lake) + +For colum-based write formats, each file contains separated sections there column data is stored. The file footer contains +location of each column section/group. Spark can use this information to load only sections required by specific query, e.g. only selected columns, +to drastically speed up the query. + +Another advantage is high compression ratio, e.g. 10x-100x in comparison to JSON or CSV. + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. +This drastically reduces the amount of data read by Spark, **if column-based file formats are used**. + +### Use partition columns in `where` clause + +Queries should include `WHERE` clause with filters on Hive partitioning columns. +This allows Spark to read only small set of files (*partition pruning*) instead of scanning the entire table, so this drastically increases performance. + +Supported operators are: `=`, `>`, `<` and `BETWEEN`, and only against some **static** value. diff --git a/mddocs/docs/ru/connection/db_connection/hive/slots.md b/mddocs/docs/ru/connection/db_connection/hive/slots.md new file mode 100644 index 000000000..8db1f7610 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/hive/slots.md @@ -0,0 +1,13 @@ +(hive-slots)= + +# Hive Slots + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.hive.slots +``` + +```{eval-rst} +.. autoclass:: HiveSlots + :members: normalize_cluster_name, get_known_clusters, get_current_cluster + :member-order: bysource +``` diff --git a/mddocs/docs/ru/connection/db_connection/hive/sql.md b/mddocs/docs/ru/connection/db_connection/hive/sql.md new file mode 100644 index 000000000..9b035f8ff --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/hive/sql.md @@ -0,0 +1,79 @@ +(hive-sql)= + +# Reading from Hive using `Hive.sql` + +`Hive.sql` allows passing custom SQL query, but does not support incremental strategies. + +## Syntax support + +Only queries with the following syntax are supported: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +```{eval-rst} +.. warning:: + + Actually, query should be written using `SparkSQL `_ syntax, not HiveQL. +``` + +## Examples + +```python +from onetl.connection import Hive + +hive = Hive(...) +df = hive.sql( + """ + SELECT + id, + key, + CAST(value AS text) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """ +) +``` + +## Recommendations + +### Use column-based write formats + +Prefer these write formats: +: - [ORC](https://spark.apache.org/docs/latest/sql-data-sources-orc.html) + - [Parquet](https://spark.apache.org/docs/latest/sql-data-sources-parquet.html) + - [Iceberg](https://iceberg.apache.org/spark-quickstart/) + - [Hudi](https://hudi.apache.org/docs/quick-start-guide/) + - [Delta](https://docs.delta.io/latest/quick-start.html#set-up-apache-spark-with-delta-lake) + +For colum-based write formats, each file contains separated sections there column data is stored. The file footer contains +location of each column section/group. Spark can use this information to load only sections required by specific query, e.g. only selected columns, +to drastically speed up the query. + +Another advantage is high compression ratio, e.g. 10x-100x in comparison to JSON or CSV. + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This drastically reduces the amount of data read by Spark, **if column-based file formats are used**. + +### Use partition columns in `where` clause + +Queries should include `WHERE` clause with filters on Hive partitioning columns. +This allows Spark to read only small set of files (*partition pruning*) instead of scanning the entire table, so this drastically increases performance. + +Supported operators are: `=`, `>`, `<` and `BETWEEN`, and only against some **static** value. + +## Details + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.hive.connection +``` + +```{eval-rst} +.. automethod:: Hive.sql +``` diff --git a/mddocs/docs/ru/connection/db_connection/hive/write.md b/mddocs/docs/ru/connection/db_connection/hive/write.md new file mode 100644 index 000000000..8e6761146 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/hive/write.md @@ -0,0 +1,180 @@ +(hive-write)= + +# Writing to Hive using `DBWriter` + +For writing data to Hive, use {obj}`DBWriter `. + +## Examples + +```python +from onetl.connection import Hive +from onetl.db import DBWriter + +hive = Hive(...) + +df = ... # data is here + +# Create dataframe with specific number of Spark partitions. +# Use the Hive partitioning columns to group the data. Create max 20 files per Hive partition. +# Also sort the data by column which most data is correlated with (e.g. user_id), reducing files size. + +num_files_per_partition = 20 +partition_columns = ["country", "business_date"] +sort_columns = ["user_id"] +write_df = df.repartition( + num_files_per_partition, + *partition_columns, + *sort_columns, +).sortWithinPartitions(*partition_columns, *sort_columns) + +writer = DBWriter( + connection=hive, + target="schema.table", + options=Hive.WriteOptions( + if_exists="append", + # Hive partitioning columns. + partitionBy=partition_columns, + ), +) + +writer.run(write_df) +``` + +## Recommendations + +### Use column-based write formats + +Prefer these write formats: +: - [ORC](https://spark.apache.org/docs/latest/sql-data-sources-orc.html) (**default**) + - [Parquet](https://spark.apache.org/docs/latest/sql-data-sources-parquet.html) + - [Iceberg](https://iceberg.apache.org/spark-quickstart/) + - [Hudi](https://hudi.apache.org/docs/quick-start-guide/) + - [Delta](https://docs.delta.io/latest/quick-start.html#set-up-apache-spark-with-delta-lake) + +```{eval-rst} +.. warning:: + When using ``DBWriter``, the default spark data format configured in ``spark.sql.sources.default`` is ignored, as ``Hive.WriteOptions(format=...)`` default value is explicitly set to ``orc``. +``` + +For column-based write formats, each file contains separated sections where column data is stored. The file footer contains +location of each column section/group. Spark can use this information to load only sections required by specific query, e.g. only selected columns, +to drastically speed up the query. + +Another advantage is high compression ratio, e.g. 10x-100x in comparison to JSON or CSV. + +### Use partitioning + +#### How does it work + +Hive support splitting data to partitions, which are different directories in filesystem with names like `some_col=value1/another_col=value2`. + +For example, dataframe with content like this: + +| country: string | business_date: date | user_id: int | bytes: long | +| --------------- | ------------------- | ------------ | ----------- | +| RU | 2024-01-01 | 1234 | 25325253525 | +| RU | 2024-01-01 | 2345 | 23234535243 | +| RU | 2024-01-02 | 1234 | 62346634564 | +| US | 2024-01-01 | 5678 | 4252345354 | +| US | 2024-01-02 | 5678 | 5474575745 | +| US | 2024-01-03 | 5678 | 3464574567 | + +With `partitionBy=["country", "business_dt"]` data will be stored as files in the following subfolders: +: - `/country=RU/business_date=2024-01-01/` + - `/country=RU/business_date=2024-01-02/` + - `/country=US/business_date=2024-01-01/` + - `/country=US/business_date=2024-01-02/` + - `/country=US/business_date=2024-01-03/` + +A separated subdirectory is created for each distinct combination of column values in the dataframe. + +Please do not confuse Spark dataframe partitions (a.k.a batches of data handled by Spark executors, usually in parallel) +and Hive partitioning (store data in different subdirectories). +Number of Spark dataframe partitions is correlated the number of files created in **each** Hive partition. +For example, Spark dataframe with 10 partitions and 5 distinct values of Hive partition columns will be saved as 5 subfolders with 10 files each = 50 files in total. +Without Hive partitioning, all the files are placed into one flat directory. + +#### But why? + +Queries which has `WHERE` clause with filters on Hive partitioning columns, like `WHERE country = 'RU' AND business_date='2024-01-01'`, will +read only files from this exact partitions, like `/country=RU/business_date=2024-01-01/`, and skip files from other partitions. + +This drastically increases performance and reduces the amount of memory used by Spark. +Consider using Hive partitioning in all tables. + +#### Which columns should I use? + +Usually Hive partitioning columns are based on event date or location, like `country: string`, `business_date: date`, `run_date: date` and so on. + +**Partition columns should contain data with low cardinality.** +Dates, small integers, strings with low number of possible values are OK. +But timestamp, float, decimals, longs (like user id), strings with lots oj unique values (like user name or email) should **NOT** be used as Hive partitioning columns. +Unlike some other databases, range and hash-based partitions are not supported. + +Partition column should be a part of a dataframe. If you want to partition values by date component of `business_dt: timestamp` column, +add a new column to dataframe like this: `df.withColumn("business_date", date(df.business_dt))`. + +### Use compression + +Using compression algorithms like `snappy`, `lz4` or `zstd` can reduce the size of files (up to 10x). + +### Prefer creating large files + +Storing millions of small files is not that HDFS and S3 are designed for. Minimal file size should be at least 10Mb, but usually it is like 128Mb+ or 256Mb+ (HDFS block size). +**NEVER** create files with few Kbytes in size. + +Number of files can be different in different cases. +On one hand, Spark Adaptive Query Execution (AQE) can merge small Spark dataframe partitions into one larger. +On the other hand, dataframes with skewed data can produce a larger number of files than expected. + +To create small amount of large files, you can reduce number of Spark dataframe partitions. +Use [df.repartition(N, columns...)](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.DataFrame.repartition.html) function, +like this: `df.repartition(20, "col1", "col2")`. +This creates new Spark dataframe with partitions using `hash(df.col1 + df.col2) mod 20` expression, avoiding data skew. + +Note: larger dataframe partitions requires more resources (CPU, RAM) on Spark executor. The exact number of partitions +should be determined empirically, as it depends on the amount of data and available resources. + +### Sort data before writing + +Dataframe with sorted content: + +| country: string | business_date: date | user_id: int | business_dt: timestamp | bytes: long | +| --------------- | ------------------- | ------------ | ----------------------- | ----------- | +| RU | 2024-01-01 | 1234 | 2024-01-01T11:22:33.456 | 25325253525 | +| RU | 2024-01-01 | 1234 | 2024-01-01T12:23:44.567 | 25325253525 | +| RU | 2024-01-02 | 1234 | 2024-01-01T13:25:56.789 | 34335645635 | +| US | 2024-01-01 | 2345 | 2024-01-01T10:00:00.000 | 12341 | +| US | 2024-01-02 | 2345 | 2024-01-01T15:11:22.345 | 13435 | +| US | 2024-01-03 | 2345 | 2024-01-01T20:22:33.567 | 14564 | + +Has a much better compression rate than unsorted one, e.g. 2x or even higher: + +| country: string | business_date: date | user_id: int | business_dt: timestamp | bytes: long | +| --------------- | ------------------- | ------------ | ----------------------- | ----------- | +| RU | 2024-01-01 | 1234 | 2024-01-01T11:22:33.456 | 25325253525 | +| RU | 2024-01-01 | 6345 | 2024-12-01T23:03:44.567 | 25365 | +| RU | 2024-01-02 | 5234 | 2024-07-01T06:10:56.789 | 45643456747 | +| US | 2024-01-01 | 4582 | 2024-04-01T17:59:00.000 | 362546475 | +| US | 2024-01-02 | 2345 | 2024-09-01T04:24:22.345 | 3235 | +| US | 2024-01-03 | 3575 | 2024-03-01T21:37:33.567 | 346345764 | + +Choosing columns to sort data by is really depends on the data. If data is correlated with some specific +column, like in example above the amount of traffic is correlated with both `user_id` and `timestamp`, +use `df.sortWithinPartitions("user_id", "timestamp")` before writing the data. + +If `df.repartition(N, repartition_columns...)` is used in combination with `df.sortWithinPartitions(sort_columns...)`, +then `sort_columns` should start with `repartition_columns` or be equal to it. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.hive.options +``` + +```{eval-rst} +.. autopydantic_model:: HiveWriteOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/index.md b/mddocs/docs/ru/connection/db_connection/index.md new file mode 100644 index 000000000..4977cf5d5 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/index.md @@ -0,0 +1,19 @@ +(db-connections)= + +# DB Connections + +```{toctree} +:caption: DB Connections +:maxdepth: 1 + +Clickhouse +Greenplum +Kafka +Hive +MongoDB +MSSQL +MySQL +Oracle +Postgres +Teradata +``` diff --git a/mddocs/docs/ru/connection/db_connection/kafka/auth.md b/mddocs/docs/ru/connection/db_connection/kafka/auth.md new file mode 100644 index 000000000..7d112d59a --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/kafka/auth.md @@ -0,0 +1,13 @@ +(kafka-auth)= + +# Kafka Auth + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.kafka.kafka_auth +``` + +```{eval-rst} +.. autoclass:: KafkaAuth + :members: get_options, cleanup + :member-order: bysource +``` diff --git a/mddocs/docs/ru/connection/db_connection/kafka/basic_auth.md b/mddocs/docs/ru/connection/db_connection/kafka/basic_auth.md new file mode 100644 index 000000000..cb3cded81 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/kafka/basic_auth.md @@ -0,0 +1,14 @@ +(kafka-basic-auth)= + +# Kafka BasicAuth + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.kafka.kafka_basic_auth +``` + +```{eval-rst} +.. autopydantic_model:: KafkaBasicAuth + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/kafka/connection.md b/mddocs/docs/ru/connection/db_connection/kafka/connection.md new file mode 100644 index 000000000..169b5b701 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/kafka/connection.md @@ -0,0 +1,12 @@ +(kafka-connection)= + +# Kafka Connection + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.kafka.connection +``` + +```{eval-rst} +.. autoclass:: Kafka + :members: get_packages, get_exclude_packages, check, close +``` diff --git a/mddocs/docs/ru/connection/db_connection/kafka/index.md b/mddocs/docs/ru/connection/db_connection/kafka/index.md new file mode 100644 index 000000000..903feea96 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/kafka/index.md @@ -0,0 +1,46 @@ +(kafka)= + +# Kafka + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +connection +troubleshooting +``` + +```{toctree} +:caption: Protocols +:maxdepth: 1 + +plaintext_protocol +ssl_protocol +``` + +```{toctree} +:caption: Auth methods +:maxdepth: 1 + +basic_auth +kerberos_auth +scram_auth +``` + +```{toctree} +:caption: Operations +:maxdepth: 1 + +read +write +``` + +```{toctree} +:caption: For developers +:maxdepth: 1 + +auth +protocol +slots +``` diff --git a/mddocs/docs/ru/connection/db_connection/kafka/kerberos_auth.md b/mddocs/docs/ru/connection/db_connection/kafka/kerberos_auth.md new file mode 100644 index 000000000..637c9e16e --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/kafka/kerberos_auth.md @@ -0,0 +1,14 @@ +(kafka-kerberos-auth)= + +# Kafka KerberosAuth + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.kafka.kafka_kerberos_auth +``` + +```{eval-rst} +.. autopydantic_model:: KafkaKerberosAuth + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/kafka/plaintext_protocol.md b/mddocs/docs/ru/connection/db_connection/kafka/plaintext_protocol.md new file mode 100644 index 000000000..5bc9ae035 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/kafka/plaintext_protocol.md @@ -0,0 +1,14 @@ +(kafka-plaintext-protocol)= + +# Kafka PlaintextProtocol + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.kafka.kafka_plaintext_protocol +``` + +```{eval-rst} +.. autopydantic_model:: KafkaPlaintextProtocol + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/kafka/prerequisites.md b/mddocs/docs/ru/connection/db_connection/kafka/prerequisites.md new file mode 100644 index 000000000..3020648e4 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/kafka/prerequisites.md @@ -0,0 +1,65 @@ +(kafka-prerequisites)= + +# Prerequisites + +## Version Compatibility + +- Kafka server versions: + : - Officially declared: 0.10 or higher + - Actually tested: 3.2.3, 3.9.0 (only Kafka 3.x supports message headers) +- Spark versions: 2.4.x - 3.5.x +- Java versions: 8 - 17 + +See [official documentation](https://spark.apache.org/docs/latest/structured-streaming-kafka-integration.html). + +## Installing PySpark + +To use Kafka connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Connecting to Kafka + +### Connection address + +Kafka is a distributed service, and usually has a list of brokers you can connect to (unlike other connectors, there only one host+port can be set). +Please contact your Kafka administrator to get addresses of these brokers, as there are no defaults. + +Also Kafka has a feature called *advertised listeners* - client connects to one broker, and received list of other brokers in the clusters. +So you don't have to pass all brokers to `addresses`, it can be some subset. Other broker addresses will be fetched directly from the cluster. + +### Connection protocol + +Kafka can support different connection protocols. List of currently supported protocols: +: - {obj}`PLAINTEXT ` (not secure) + - {obj}`SSL ` (secure, recommended) + +Note that specific port can listen for only one of these protocols, so it is important to set +proper port number + protocol combination. + +### Authentication mechanism + +Kafka can support different authentication mechanism (also known as [SASL](https://en.wikipedia.org/wiki/Simple_Authentication_and_Security_Layer)). + +List of currently supported mechanisms: +: - {obj}`PLAIN `. To no confuse this with `PLAINTEXT` connection protocol, onETL uses name `BasicAuth`. + - {obj}`GSSAPI `. To simplify naming, onETL uses name `KerberosAuth`. + - {obj}`SCRAM-SHA-256 or SCRAM-SHA-512 ` (recommended). + +Different mechanisms use different types of credentials (login + password, keytab file, and so on). + +Note that connection protocol and auth mechanism are set in pairs: +: - If you see `SASL_PLAINTEXT` this means `PLAINTEXT` connection protocol + some auth mechanism. + - If you see `SASL_SSL` this means `SSL` connection protocol + some auth mechanism. + - If you see just `PLAINTEXT` or `SSL` (**no** `SASL`), this means that authentication is disabled (anonymous access). + +Please contact your Kafka administrator to get details about enabled auth mechanism in a specific Kafka instance. + +### Required grants + +Ask your Kafka administrator to set following grants for a user, *if Kafka instance uses ACL*: +: - `Describe` + `Read` for reading data from Kafka (Consumer). + - `Describe` + `Write` for writing data from Kafka (Producer). + +More details can be found in [documentation](https://kafka.apache.org/documentation/#operations_in_kafka). diff --git a/mddocs/docs/ru/connection/db_connection/kafka/protocol.md b/mddocs/docs/ru/connection/db_connection/kafka/protocol.md new file mode 100644 index 000000000..b9278d009 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/kafka/protocol.md @@ -0,0 +1,13 @@ +(kafka-protocol)= + +# Kafka Protocol + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.kafka.kafka_protocol +``` + +```{eval-rst} +.. autoclass:: KafkaProtocol + :members: get_options, cleanup + :member-order: bysource +``` diff --git a/mddocs/docs/ru/connection/db_connection/kafka/read.md b/mddocs/docs/ru/connection/db_connection/kafka/read.md new file mode 100644 index 000000000..be707f260 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/kafka/read.md @@ -0,0 +1,137 @@ +(kafka-read)= + +# Reading from Kafka + +Data can be read from Kafka to Spark using {obj}`DBReader `. +It also supports {ref}`strategy` for incremental data reading. + +## Supported DBReader features + +- ❌ `columns` (is not supported by Kafka) +- ❌ `where` (is not supported by Kafka) +- ✅︎ `hwm`, supported strategies: +- - ✅︎ {ref}`snapshot-strategy` +- - ✅︎ {ref}`incremental-strategy` +- - ❌ {ref}`snapshot-batch-strategy` +- - ❌ {ref}`incremental-batch-strategy` +- ❌ `hint` (is not supported by Kafka) +- ❌ `df_schema` (see note below) +- ✅︎ `options` (see {obj}`Kafka.ReadOptions `) + +## Dataframe schema + +Unlike other DB connections, Kafka does not have concept of columns. +All the topics messages have the same set of fields, see structure below: + +```text +root +|-- key: binary (nullable = true) +|-- value: binary (nullable = true) +|-- topic: string (nullable = false) +|-- partition: integer (nullable = false) +|-- offset: integer (nullable = false) +|-- timestamp: timestamp (nullable = false) +|-- timestampType: integer (nullable = false) +|-- headers: struct (nullable = true) + |-- key: string (nullable = false) + |-- value: binary (nullable = true) +``` + +`headers` field is present in the dataframe only if `Kafka.ReadOptions(include_headers=True)` is passed (compatibility with Kafka 1.x). + +## Value deserialization + +To read `value` or `key` of other type than bytes (e.g. struct or integer), users have to deserialize values manually. + +This could be done using following methods: +: - {obj}`Avro.parse_column ` + - {obj}`JSON.parse_column ` + - {obj}`CSV.parse_column ` + - {obj}`XML.parse_column ` + +## Examples + +Snapshot strategy, `value` is Avro binary data: + +```python +from onetl.connection import Kafka +from onetl.db import DBReader, DBWriter +from onetl.file.format import Avro +from pyspark.sql.functions import decode + +# read all topic data from Kafka +kafka = Kafka(...) +reader = DBReader(connection=kafka, source="avro_topic") +read_df = reader.run() + +# parse Avro format to Spark struct +avro = Avro( + schema_dict={ + "type": "record", + "name": "Person", + "fields": [ + {"name": "name", "type": "string"}, + {"name": "age", "type": "int"}, + ], + } +) +deserialized_df = read_df.select( + # cast binary key to string + decode("key", "UTF-8").alias("key"), + avro.parse_column("value"), +) +``` + +Incremental strategy, `value` is JSON string: + +```{eval-rst} +.. note:: + + Currently Kafka connector does support only HWMs based on ``offset`` field. Other fields, like ``timestamp``, are not yet supported. +``` + +```python +from onetl.connection import Kafka +from onetl.db import DBReader, DBWriter +from onetl.file.format import JSON +from pyspark.sql.functions import decode + +kafka = Kafka(...) + +# read only new data from Kafka topic +reader = DBReader( + connection=kafka, + source="topic_name", + hwm=DBReader.AutoDetectHWM(name="kafka_hwm", expression="offset"), +) + +with IncrementalStrategy(): + read_df = reader.run() + +# parse JSON format to Spark struct +json = JSON() +schema = StructType( + [ + StructField("name", StringType(), nullable=True), + StructField("age", IntegerType(), nullable=True), + ], +) +deserialized_df = read_df.select( + # cast binary key to string + decode("key", "UTF-8").alias("key"), + json.parse_column("value", json), +) +``` + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.kafka.options +``` + +```{eval-rst} +.. autopydantic_model:: KafkaReadOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/kafka/scram_auth.md b/mddocs/docs/ru/connection/db_connection/kafka/scram_auth.md new file mode 100644 index 000000000..5a27a46ea --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/kafka/scram_auth.md @@ -0,0 +1,14 @@ +(kafka-scram-auth)= + +# Kafka ScramAuth + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.kafka.kafka_scram_auth +``` + +```{eval-rst} +.. autopydantic_model:: KafkaScramAuth + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/kafka/slots.md b/mddocs/docs/ru/connection/db_connection/kafka/slots.md new file mode 100644 index 000000000..0c58413fb --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/kafka/slots.md @@ -0,0 +1,13 @@ +(kafka-slots)= + +# Kafka Slots + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.kafka.slots +``` + +```{eval-rst} +.. autoclass:: KafkaSlots + :members: normalize_cluster_name, get_known_clusters, normalize_address, get_cluster_addresses + :member-order: bysource +``` diff --git a/mddocs/docs/ru/connection/db_connection/kafka/ssl_protocol.md b/mddocs/docs/ru/connection/db_connection/kafka/ssl_protocol.md new file mode 100644 index 000000000..6e1a7641d --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/kafka/ssl_protocol.md @@ -0,0 +1,14 @@ +(kafka-ssl-protocol)= + +# Kafka SSLProtocol + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.kafka.kafka_ssl_protocol +``` + +```{eval-rst} +.. autopydantic_model:: KafkaSSLProtocol + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/kafka/troubleshooting.md b/mddocs/docs/ru/connection/db_connection/kafka/troubleshooting.md new file mode 100644 index 000000000..7a3536fbf --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/kafka/troubleshooting.md @@ -0,0 +1,13 @@ +(kafka-troubleshooting)= + +# Kafka Troubleshooting + +```{eval-rst} +.. note:: + + General guide: :ref:`troubleshooting`. +``` + +## Cannot connect using `SSL` protocol + +Please check that certificate files are not Base-64 encoded. diff --git a/mddocs/docs/ru/connection/db_connection/kafka/write.md b/mddocs/docs/ru/connection/db_connection/kafka/write.md new file mode 100644 index 000000000..cb3e3019a --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/kafka/write.md @@ -0,0 +1,75 @@ +(kafka-write)= + +# Writing to Kafka + +For writing data to Kafka, use {obj}`DBWriter ` with specific options (see below). + +## Dataframe schema + +Unlike other DB connections, Kafka does not have concept of columns. +All the topics messages have the same set of fields. Only some of them can be written: + +```text +root +|-- key: binary (nullable = true) +|-- value: binary (nullable = true) +|-- headers: struct (nullable = true) + |-- key: string (nullable = false) + |-- value: binary (nullable = true) +``` + +`headers` can be passed only with `Kafka.WriteOptions(include_headers=True)` (compatibility with Kafka 1.x). + +Field `topic` should not be present in the dataframe, as it is passed to `DBWriter(target=...)`. + +Other fields, like `partition`, `offset`, `timestamp` are set by Kafka, and cannot be passed explicitly. + +## Value serialization + +To write `value` or `key` of other type than bytes (e.g. struct or integer), users have to serialize values manually. + +This could be done using following methods: +: - {obj}`Avro.serialize_column ` + - {obj}`JSON.serialize_column ` + - {obj}`CSV.serialize_column ` + +## Examples + +Convert `value` to JSON string, and write to Kafka: + +```python +from onetl.connection import Kafka +from onetl.db import DBWriter +from onetl.file.format import JSON + +df = ... # original data is here + +# serialize struct data as JSON +json = JSON() +write_df = df.select( + df.key, + json.serialize_column(df.value), +) + +# write data to Kafka +kafka = Kafka(...) + +writer = DBWriter( + connection=kafka, + target="topic_name", +) +writer.run(write_df) +``` + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.kafka.options +``` + +```{eval-rst} +.. autopydantic_model:: KafkaWriteOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/mongodb/connection.md b/mddocs/docs/ru/connection/db_connection/mongodb/connection.md new file mode 100644 index 000000000..b81c56ffc --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mongodb/connection.md @@ -0,0 +1,13 @@ +(mongodb-connection)= + +# MongoDB Connection + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mongodb.connection +``` + +```{eval-rst} +.. autoclass:: MongoDB + :members: get_packages, check + :member-order: bysource +``` diff --git a/mddocs/docs/ru/connection/db_connection/mongodb/index.md b/mddocs/docs/ru/connection/db_connection/mongodb/index.md new file mode 100644 index 000000000..fb9656117 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mongodb/index.md @@ -0,0 +1,27 @@ +(mongodb)= + +# MongoDB + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +connection +``` + +```{toctree} +:caption: Operations +:maxdepth: 1 + +read +pipeline +write +``` + +```{toctree} +:caption: Troubleshooting +:maxdepth: 1 + +types +``` diff --git a/mddocs/docs/ru/connection/db_connection/mongodb/pipeline.md b/mddocs/docs/ru/connection/db_connection/mongodb/pipeline.md new file mode 100644 index 000000000..2cc30458c --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mongodb/pipeline.md @@ -0,0 +1,41 @@ +(mongodb-sql)= + +# Reading from MongoDB using `MongoDB.pipeline` + +{obj}`MongoDB.sql ` allows passing custom pipeline, +but does not support incremental strategies. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`mongodb-types` +``` + +## Recommendations + +### Pay attention to `pipeline` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `mongodb.pipeline(..., pipeline={"$match": {"column": {"$eq": "value"}}})` value. +This both reduces the amount of data send from MongoDB to Spark, and may also improve performance of the query. +Especially if there are indexes for columns used in `pipeline` value. + +## References + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mongodb.connection +``` + +```{eval-rst} +.. automethod:: MongoDB.pipeline +``` + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mongodb.options +``` + +```{eval-rst} +.. autopydantic_model:: MongoDBPipelineOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/mongodb/prerequisites.md b/mddocs/docs/ru/connection/db_connection/mongodb/prerequisites.md new file mode 100644 index 000000000..f491ca10e --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mongodb/prerequisites.md @@ -0,0 +1,72 @@ +(mongodb-prerequisites)= + +# Prerequisites + +## Version Compatibility + +- MongoDB server versions: + : - Officially declared: 4.0 or higher + - Actually tested: 4.0.0, 8.0.4 +- Spark versions: 3.2.x - 3.5.x +- Java versions: 8 - 20 + +See [official documentation](https://www.mongodb.com/docs/spark-connector/). + +## Installing PySpark + +To use MongoDB connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Connecting to MongoDB + +### Connection host + +It is possible to connect to MongoDB host by using either DNS name of host or it's IP address. + +It is also possible to connect to MongoDB shared cluster: + +```python +mongo = MongoDB( + host="master.host.or.ip", + user="user", + password="*****", + database="target_database", + spark=spark, + extra={ + # read data from secondary cluster node, switch to primary if not available + "readPreference": "secondaryPreferred", + }, +) +``` + +Supported `readPreference` values are described in [official documentation](https://www.mongodb.com/docs/manual/core/read-preference/). + +### Connection port + +Connection is usually performed to port `27017`. Port may differ for different MongoDB instances. +Please ask your MongoDB administrator to provide required information. + +### Required grants + +Ask your MongoDB cluster administrator to set following grants for a user, +used for creating a connection: + +```{eval-rst} +.. tabs:: + + .. code-tab:: js Read + Write + + // allow writing data to specific database + db.grantRolesToUser("username", [{db: "somedb", role: "readWrite"}]) + + .. code-tab:: js Read only + + // allow reading data from specific database + db.grantRolesToUser("username", [{db: "somedb", role: "read"}]) +``` + +See: +: - [db.grantRolesToUser documentation](https://www.mongodb.com/docs/manual/reference/method/db.grantRolesToUser) + - [MongoDB builtin roles](https://www.mongodb.com/docs/manual/reference/built-in-roles) diff --git a/mddocs/docs/ru/connection/db_connection/mongodb/read.md b/mddocs/docs/ru/connection/db_connection/mongodb/read.md new file mode 100644 index 000000000..15cf34bb1 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mongodb/read.md @@ -0,0 +1,141 @@ +(mongodb-read)= + +# Reading from MongoDB using `DBReader` + +{obj}`DBReader ` supports {ref}`strategy` for incremental data reading, +but does not support custom pipelines, e.g. aggregation. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`mongodb-types` +``` + +## Supported DBReader features + +- ❌ `columns` (for now, all document fields are read) +- ✅︎ `where` (passed to `{"$match": ...}` aggregation pipeline) +- ✅︎ `hwm`, supported strategies: +- - ✅︎ {ref}`snapshot-strategy` +- - ✅︎ {ref}`incremental-strategy` +- - ✅︎ {ref}`snapshot-batch-strategy` +- - ✅︎ {ref}`incremental-batch-strategy` +- - Note that `expression` field of HWM can only be a field name, not a custom expression +- ✅︎ `hint` (see [official documentation](https://www.mongodb.com/docs/v5.0/reference/operator/meta/hint/)) +- ✅︎ `df_schema` (mandatory) +- ✅︎ `options` (see {obj}`MongoDB.ReadOptions `) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import MongoDB +from onetl.db import DBReader + +from pyspark.sql.types import ( + StructType, + StructField, + IntegerType, + StringType, + TimestampType, +) + +mongodb = MongoDB(...) + +# mandatory +df_schema = StructType( + [ + StructField("_id", StringType()), + StructField("some", StringType()), + StructField( + "field", + StructType( + [ + StructField("nested", IntegerType()), + ], + ), + ), + StructField("updated_dt", TimestampType()), + ] +) + +reader = DBReader( + connection=mongodb, + source="some_collection", + df_schema=df_schema, + where={"field": {"$eq": 123}}, + hint={"field": 1}, + options=MongoDBReadOptions(batchSize=10000), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import MongoDB +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +from pyspark.sql.types import ( + StructType, + StructField, + IntegerType, + StringType, + TimestampType, +) + +mongodb = MongoDB(...) + +# mandatory +df_schema = StructType( + [ + StructField("_id", StringType()), + StructField("some", StringType()), + StructField( + "field", + StructType( + [ + StructField("nested", IntegerType()), + ], + ), + ), + StructField("updated_dt", TimestampType()), + ] +) + +reader = DBReader( + connection=mongodb, + source="some_collection", + df_schema=df_schema, + where={"field": {"$eq": 123}}, + hint={"field": 1}, + hwm=DBReader.AutoDetectHWM(name="mongodb_hwm", expression="updated_dt"), + options=MongoDBReadOptions(batchSize=10000), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where={"column": {"$eq": "value"}})` clause. +This both reduces the amount of data send from MongoDB to Spark, and may also improve performance of the query. +Especially if there are indexes for columns used in `where` clause. + +## Read options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mongodb.options +``` + +```{eval-rst} +.. autopydantic_model:: MongoDBReadOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/mongodb/types.md b/mddocs/docs/ru/connection/db_connection/mongodb/types.md new file mode 100644 index 000000000..5c6c506d0 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mongodb/types.md @@ -0,0 +1,269 @@ +(mongodb-types)= + +# MongoDB \<-> Spark type mapping + +```{eval-rst} +.. note:: + + The results below are valid for Spark 3.5.5, and may differ on other Spark versions. +``` + +## Type detection & casting + +Spark's DataFrames always have a `schema` which is a list of fields with corresponding Spark types. All operations on a field are performed using field type. + +MongoDB is, by design, \_\_schemaless\_\_. So there are 2 ways how this can be handled: + +- User provides DataFrame schema explicitly: + + ```{eval-rst} + .. dropdown:: See example + + .. code-block:: python + + from onetl.connection import MongoDB + from onetl.db import DBReader + + from pyspark.sql.types import ( + StructType, + StructField, + IntegerType, + StringType, + TimestampType, + ) + + mongodb = MongoDB(...) + + df_schema = StructType( + [ + StructField("_id", StringType()), + StructField("some", StringType()), + StructField( + "field", + StructType( + [ + StructField("nested", IntegerType()), + ] + ), + ), + ] + ) + + reader = DBReader( + connection=mongodb, + source="some_collection", + df_schema=df_schema, + ) + df = reader.run() + + # or + + df = mongodb.pipeline( + collection="some_collection", + df_schema=df_schema, + ) + ``` + +- Rely on MongoDB connector schema infer: + + ```python + df = mongodb.pipeline(collection="some_collection") + ``` + + In this case MongoDB connector read a sample of collection documents, and build DataFrame schema based on document fields and values. + +It is highly recommended to pass `df_schema` explicitly, to avoid type conversion issues. + +### References + +Here you can find source code with type conversions: + +- [MongoDB -> Spark](https://github.com/mongodb/mongo-spark/blob/r10.4.1/src/main/java/com/mongodb/spark/sql/connector/schema/InferSchema.java#L214-L260) +- [Spark -> MongoDB](https://github.com/mongodb/mongo-spark/blob/r10.4.1/src/main/java/com/mongodb/spark/sql/connector/schema/RowToBsonDocumentConverter.java#L157-L260) + +## Supported types + +See [official documentation](https://www.mongodb.com/docs/manual/reference/bson-types/) + +### Numeric types + +```{eval-rst} ++---------------------+-----------------------------+----------------------+ +| MongoDB type (read) | Spark type | MongoDB type (write) | ++=====================+=============================+======================+ +| ``Decimal128`` | ``DecimalType(P=34, S=32)`` | ``Decimal128`` | ++---------------------+-----------------------------+----------------------+ +| ``-`` | ``FloatType()`` | ``Double`` | ++---------------------+-----------------------------+ | +| ``Double`` | ``DoubleType()`` | | ++---------------------+-----------------------------+----------------------+ +| ``-`` | ``ByteType()`` | ``Int32`` | ++---------------------+-----------------------------+ | +| ``-`` | ``ShortType()`` | | ++---------------------+-----------------------------+ | +| ``Int32`` | ``IntegerType()`` | | ++---------------------+-----------------------------+----------------------+ +| ``Int64`` | ``LongType()`` | ``Int64`` | ++---------------------+-----------------------------+----------------------+ +``` + +### Temporal types + +```{eval-rst} ++------------------------+-----------------------------------+-------------------------+ +| MongoDB type (read) | Spark type | MongoDB type (write) | ++========================+===================================+=========================+ +| ``-`` | ``DateType()``, days | ``Date``, milliseconds | ++------------------------+-----------------------------------+-------------------------+ +| ``Date``, milliseconds | ``TimestampType()``, microseconds | ``Date``, milliseconds, | +| | | **precision loss** [2]_ | ++------------------------+-----------------------------------+-------------------------+ +| ``Timestamp``, seconds | ``TimestampType()``, microseconds | ``Date``, milliseconds | ++------------------------+-----------------------------------+-------------------------+ +| ``-`` | ``TimestampNTZType()`` | unsupported | ++------------------------+-----------------------------------+ | +| ``-`` | ``DayTimeIntervalType()`` | | ++------------------------+-----------------------------------+-------------------------+ +``` + +```{eval-rst} +.. warning:: + + Note that types in MongoDB and Spark have different value ranges: + + +---------------+--------------------------------+--------------------------------+---------------------+--------------------------------+--------------------------------+ + | MongoDB type | Min value | Max value | Spark type | Min value | Max value | + +===============+================================+================================+=====================+================================+================================+ + | ``Date`` | -290 million years | 290 million years | ``TimestampType()`` | ``0001-01-01 00:00:00.000000`` | ``9999-12-31 23:59:59.999999`` | + +---------------+--------------------------------+--------------------------------+ | | | + | ``Timestamp`` | ``1970-01-01 00:00:00`` | ``2106-02-07 09:28:16`` | | | | + +---------------+--------------------------------+--------------------------------+---------------------+--------------------------------+--------------------------------+ + + So not all values can be read from MongoDB to Spark, and can written from Spark DataFrame to MongoDB. + + References: + * `MongoDB Date type documentation `_ + * `MongoDB Timestamp documentation `_ + * `Spark DateType documentation `_ + * `Spark TimestampType documentation `_ +``` + +[^footnote-1]: MongoDB `Date` type has precision up to milliseconds (`23:59:59.999`). + Inserting data with microsecond precision (`23:59:59.999999`) + will lead to **throwing away microseconds**. + +### String types + +Note: fields of deprecated MongoDB type `Symbol` are excluded during read. + +```{eval-rst} ++---------------------+------------------+----------------------+ +| MongoDB type (read) | Spark type | MongoDB type (write) | ++=====================+==================+======================+ +| ``String`` | ``StringType()`` | ``String`` | ++---------------------+ | | +| ``Code`` | | | ++---------------------+ | | +| ``RegExp`` | | | ++---------------------+------------------+----------------------+ +``` + +### Binary types + +| MongoDB type (read) | Spark type | MongoDB type (write) | +| ------------------- | --------------- | -------------------- | +| `Boolean` | `BooleanType()` | `Boolean` | +| `Binary` | `BinaryType()` | `Binary` | + +### Struct types + +```{eval-rst} ++---------------------+-----------------------+----------------------+ +| MongoDB type (read) | Spark type | MongoDB type (write) | ++=====================+=======================+======================+ +| ``Array[T]`` | ``ArrayType(T)`` | ``Array[T]`` | ++---------------------+-----------------------+----------------------+ +| ``Object[...]`` | ``StructType([...])`` | ``Object[...]`` | ++---------------------+-----------------------+ | +| ``-`` | ``MapType(...)`` | | ++---------------------+-----------------------+----------------------+ +``` + +### Special types + +```{eval-rst} ++---------------------+---------------------------------------------------------+---------------------------------------+ +| MongoDB type (read) | Spark type | MongoDB type (write) | ++=====================+=========================================================+=======================================+ +| ``ObjectId`` | ``StringType()`` | ``String`` | ++---------------------+ | | +| ``MaxKey`` | | | ++---------------------+ | | +| ``MinKey`` | | | ++---------------------+---------------------------------------------------------+---------------------------------------+ +| ``Null`` | ``NullType()`` | ``Null`` | ++---------------------+ | | +| ``Undefined`` | | | ++---------------------+---------------------------------------------------------+---------------------------------------+ +| ``DBRef`` | ``StructType([$ref: StringType(), $id: StringType()])`` | ``Object[$ref: String, $id: String]`` | ++---------------------+---------------------------------------------------------+---------------------------------------+ +``` + +## Explicit type cast + +### `DBReader` + +Currently it is not possible to cast field types using `DBReader`. But this can be done using `MongoDB.pipeline`. + +### `MongoDB.pipeline` + +You can use `$project` aggregation to cast field types: + +```python +from pyspark.sql.types import IntegerType, StructField, StructType + +from onetl.connection import MongoDB +from onetl.db import DBReader + +mongodb = MongoDB(...) + +df = mongodb.pipeline( + collection="my_collection", + pipeline=[ + { + "$project": { + # convert unsupported_field to string + "unsupported_field_str": { + "$convert": { + "input": "$unsupported_field", + "to": "string", + }, + }, + # skip unsupported_field from result + "unsupported_field": 0, + } + } + ], +) + +# cast field content to proper Spark type +df = df.select( + df.id, + df.supported_field, + # explicit cast + df.unsupported_field_str.cast("integer").alias("parsed_integer"), +) +``` + +### `DBWriter` + +Convert dataframe field to string on Spark side, and then write it to MongoDB: + +```python +df = df.select( + df.id, + df.unsupported_field.cast("string").alias("array_field_json"), +) + +writer.run(df) +``` diff --git a/mddocs/docs/ru/connection/db_connection/mongodb/write.md b/mddocs/docs/ru/connection/db_connection/mongodb/write.md new file mode 100644 index 000000000..5b44d8613 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mongodb/write.md @@ -0,0 +1,47 @@ +(mongodb-write)= + +# Writing to MongoDB using `DBWriter` + +For writing data to MongoDB, use {obj}`DBWriter `. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`mongodb-types` +``` + +## Examples + +```python +from onetl.connection import MongoDB +from onetl.db import DBWriter + +mongodb = MongoDB(...) + +df = ... # data is here + +writer = DBWriter( + connection=mongodb, + target="schema.table", + options=MongoDB.WriteOptions( + if_exists="append", + ), +) + +writer.run(df) +``` + +## Write options + +Method above accepts {obj}`MongoDB.WriteOptions ` + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mongodb.options +``` + +```{eval-rst} +.. autopydantic_model:: MongoDBWriteOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/mssql/connection.md b/mddocs/docs/ru/connection/db_connection/mssql/connection.md new file mode 100644 index 000000000..e35ef6ed9 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mssql/connection.md @@ -0,0 +1,12 @@ +(mssql-connection)= + +# MSSQL connection + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mssql.connection +``` + +```{eval-rst} +.. autoclass:: MSSQL + :members: get_packages, check +``` diff --git a/mddocs/docs/ru/connection/db_connection/mssql/execute.md b/mddocs/docs/ru/connection/db_connection/mssql/execute.md new file mode 100644 index 000000000..278d84229 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mssql/execute.md @@ -0,0 +1,117 @@ +(mssql-execute)= + +# Executing statements in MSSQL + +```{eval-rst} +.. warning:: + + Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + + Do **NOT** use them to read large amounts of data. Use :ref:`DBReader ` or :ref:`MSSQL.sql ` instead. +``` + +## How to + +There are 2 ways to execute some statement in MSSQL + +### Use `MSSQL.fetch` + +Use this method to perform some `SELECT` query which returns **small number or rows**, like reading +MSSQL config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts {obj}`MSSQL.FetchOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`mssql-types`. +``` + +#### Syntax support + +This method supports **any** query syntax supported by MSSQL, like: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ✅︎ `SELECT func(arg1, arg2) FROM DUAL` - call function +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import MSSQL + +mssql = MSSQL(...) + +df = mssql.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=MSSQL.FetchOptions(queryTimeout=10), +) +mssql.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `MSSQL.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts {obj}`MSSQL.ExecuteOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by MSSQL, like: + +- ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...` +- ✅︎ `ALTER ...` +- ✅︎ `INSERT INTO ... AS SELECT ...` +- ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +- ✅︎ `EXEC procedure(arg1, arg2) ...` or `{call procedure(arg1, arg2)}` - special syntax for calling procedure +- ✅︎ `DECLARE ... BEGIN ... END` - execute PL/SQL statement +- ✅︎ other statements not mentioned here +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import MSSQL + +mssql = MSSQL(...) + +mssql.execute("DROP TABLE schema.table") +mssql.execute( + """ + CREATE TABLE schema.table ( + id bigint GENERATED ALWAYS AS IDENTITY, + key VARCHAR2(4000), + value NUMBER + ) + """, + options=MSSQL.ExecuteOptions(queryTimeout=10), +) +``` + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mssql.options +``` + +```{eval-rst} +.. autopydantic_model:: MSSQLFetchOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` + +```{eval-rst} +.. autopydantic_model:: MSSQLExecuteOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/mssql/index.md b/mddocs/docs/ru/connection/db_connection/mssql/index.md new file mode 100644 index 000000000..16448c6a1 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mssql/index.md @@ -0,0 +1,28 @@ +(mssql)= + +# MSSQL + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +connection +``` + +```{toctree} +:caption: Operations +:maxdepth: 1 + +read +sql +write +execute +``` + +```{toctree} +:caption: Troubleshooting +:maxdepth: 1 + +types +``` diff --git a/mddocs/docs/ru/connection/db_connection/mssql/prerequisites.md b/mddocs/docs/ru/connection/db_connection/mssql/prerequisites.md new file mode 100644 index 000000000..89e62f322 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mssql/prerequisites.md @@ -0,0 +1,76 @@ +(mssql-prerequisites)= + +# Prerequisites + +## Version Compatibility + +- SQL Server versions: + : - Officially declared: 2016 - 2022 + - Actually tested: 2017, 2022 +- Spark versions: 2.3.x - 3.5.x +- Java versions: 8 - 20 + +See [official documentation](https://learn.microsoft.com/en-us/sql/connect/jdbc/system-requirements-for-the-jdbc-driver) +and [official compatibility matrix](https://learn.microsoft.com/en-us/sql/connect/jdbc/microsoft-jdbc-driver-for-sql-server-support-matrix). + +## Installing PySpark + +To use MSSQL connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Connecting to MSSQL + +### Connection port + +Connection is usually performed to port 1433. Port may differ for different MSSQL instances. +Please ask your MSSQL administrator to provide required information. + +For named MSSQL instances (`instanceName` option), [port number is optional](https://learn.microsoft.com/en-us/sql/connect/jdbc/building-the-connection-url?view=sql-server-ver16#named-and-multiple-sql-server-instances), and could be omitted. + +### Connection host + +It is possible to connect to MSSQL by using either DNS name of host or it's IP address. + +If you're using MSSQL cluster, it is currently possible to connect only to **one specific node**. +Connecting to multiple nodes to perform load balancing, as well as automatic failover to new master/replica are not supported. + +### Required grants + +Ask your MSSQL cluster administrator to set following grants for a user, +used for creating a connection: + +```{eval-rst} +.. tabs:: + + .. code-tab:: sql Read + Write (schema is owned by user) + + -- allow creating tables for user + GRANT CREATE TABLE TO username; + + -- allow read & write access to specific table + GRANT SELECT, INSERT ON username.mytable TO username; + + -- only if if_exists="replace_entire_table" is used: + -- allow dropping/truncating tables in any schema + GRANT ALTER ON username.mytable TO username; + + .. code-tab:: sql Read + Write (schema is not owned by user) + + -- allow creating tables for user + GRANT CREATE TABLE TO username; + + -- allow managing tables in specific schema, and inserting data to tables + GRANT ALTER, SELECT, INSERT ON SCHEMA::someschema TO username; + + .. code-tab:: sql Read only + + -- allow read access to specific table + GRANT SELECT ON someschema.mytable TO username; +``` + +More details can be found in official documentation: +: - [GRANT ON DATABASE](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-database-permissions-transact-sql) + - [GRANT ON OBJECT](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-object-permissions-transact-sql) + - [GRANT ON SCHEMA](https://learn.microsoft.com/en-us/sql/t-sql/statements/grant-schema-permissions-transact-sql) diff --git a/mddocs/docs/ru/connection/db_connection/mssql/read.md b/mddocs/docs/ru/connection/db_connection/mssql/read.md new file mode 100644 index 000000000..924d10e2f --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mssql/read.md @@ -0,0 +1,93 @@ +(mssql-read)= + +# Reading from MSSQL using `DBReader` + +{obj}`DBReader ` supports {ref}`strategy` for incremental data reading, +but does not support custom queries, like `JOIN`. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`mssql-types` +``` + +## Supported DBReader features + +- ✅︎ `columns` +- ✅︎ `where` +- ✅︎ `hwm`, supported strategies: +- - ✅︎ {ref}`snapshot-strategy` +- - ✅︎ {ref}`incremental-strategy` +- - ✅︎ {ref}`snapshot-batch-strategy` +- - ✅︎ {ref}`incremental-batch-strategy` +- ❌ `hint` (MSSQL does support hints, but DBReader not, at least for now) +- ❌ `df_schema` +- ✅︎ `options` (see {obj}`MSSQL.ReadOptions `) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import MSSQL +from onetl.db import DBReader + +mssql = MSSQL(...) + +reader = DBReader( + connection=mssql, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + options=MSSQL.ReadOptions(partitionColumn="id", numPartitions=10), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import MSSQL +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +mssql = MSSQL(...) + +reader = DBReader( + connection=mssql, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="mssql_hwm", expression="updated_dt"), + options=MSSQL.ReadOptions(partitionColumn="id", numPartitions=10), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from MSSQL to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from MSSQL to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mssql.options +``` + +```{eval-rst} +.. autopydantic_model:: MSSQLReadOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/mssql/sql.md b/mddocs/docs/ru/connection/db_connection/mssql/sql.md new file mode 100644 index 000000000..de932e2d3 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mssql/sql.md @@ -0,0 +1,80 @@ +(mssql-sql)= + +# Reading from MSSQL using `MSSQL.sql` + +`MSSQL.sql` allows passing custom SQL query, but does not support incremental strategies. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`mssql-types` +``` + +```{eval-rst} +.. warning:: + + Statement is executed in **read-write** connection, so if you're calling some functions/procedures with DDL/DML statements inside, + they can change data in your database. +``` + +## Syntax support + +Only queries with the following syntax are supported: + +- ✅︎ `SELECT ... FROM ...` +- ❌ `WITH alias AS (...) SELECT ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import MSSQL + +mssql = MSSQL(...) +df = mssql.sql( + """ + SELECT + id, + key, + CAST(value AS text) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """, + options=MSSQL.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from MSSQL to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from MSSQL to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mssql.options +``` + +```{eval-rst} +.. autopydantic_model:: MSSQLSQLOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/mssql/types.md b/mddocs/docs/ru/connection/db_connection/mssql/types.md new file mode 100644 index 000000000..23e4972c9 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mssql/types.md @@ -0,0 +1,376 @@ +(mssql-types)= + +# MSSQL \<-> Spark type mapping + +```{eval-rst} +.. note:: + + The results below are valid for Spark 3.5.5, and may differ on other Spark versions. +``` + +## Type detection & casting + +Spark's DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from MSSQL + +This is how MSSQL connector performs this: + +- For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and MSSQL type. +- Find corresponding `MSSQL type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- Create DataFrame from query with specific column names and Spark types. + +### Writing to some existing MSSQL table + +This is how MSSQL connector performs this: + +- Get names of columns in DataFrame. [^footnote-1] +- Perform `SELECT * FROM table LIMIT 0` query. +- Take only columns present in DataFrame (by name, case insensitive). For each found column get MSSQL type. +- Find corresponding `Spark type` → `MSSQL type (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- If `MSSQL type (write)` match `MSSQL type (read)`, no additional casts will be performed, DataFrame column will be written to MSSQL as is. +- If `MSSQL type (write)` does not match `MSSQL type (read)`, DataFrame column will be casted to target column type **on MSSQL side**. + For example, you can write column with text data to `int` column, if column contains valid integer values within supported value range and precision [^footnote-2]. + +[^footnote-1]: This allows to write data to tables with `DEFAULT` and `GENERATED` columns - if DataFrame has no such column, + it will be populated by MSSQL. + +[^footnote-2]: This is true only if DataFrame column is a `StringType()`, because text value is parsed automatically to target column type. + + But other types cannot be silently converted, like `int -> text`. This requires explicit casting, see [DBWriter]. + +### Create new table using Spark + +```{eval-rst} +.. warning:: + + ABSOLUTELY NOT RECOMMENDED! +``` + +This is how MSSQL connector performs this: + +- Find corresponding `Spark type` → `MSSQL type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- Generate DDL for creating table in MSSQL, like `CREATE TABLE (col1 ...)`, and run it. +- Write DataFrame to created table as is. + +But some cases this may lead to using wrong column type. For example, Spark creates column of type `timestamp` +which corresponds to MSSQL's type `timestamp(0)` (precision up to seconds) +instead of more precise `timestamp(6)` (precision up to nanoseconds). +This may lead to incidental precision loss, or sometimes data cannot be written to created table at all. + +So instead of relying on Spark to create tables: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + writer = DBWriter( + connection=mssql, + target="myschema.target_tbl", + options=MSSQL.WriteOptions( + if_exists="append", + ), + ) + writer.run(df) +``` + +Always prefer creating tables with specific types **BEFORE WRITING DATA**: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + mssql.execute( + """ + CREATE TABLE schema.table ( + id bigint, + key text, + value datetime2(6) -- specific type and precision + ) + """, + ) + + writer = DBWriter( + connection=mssql, + target="myschema.target_tbl", + options=MSSQL.WriteOptions(if_exists="append"), + ) + writer.run(df) +``` + +### References + +Here you can find source code with type conversions: + +- [MSSQL -> JDBC](https://github.com/microsoft/mssql-jdbc/blob/v12.2.0/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSetMetaData.java#L117-L170) +- [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MsSqlServerDialect.scala#L135-L152) +- [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MsSqlServerDialect.scala#L154-L163) +- [JDBC -> MSSQL](https://github.com/microsoft/mssql-jdbc/blob/v12.2.0/src/main/java/com/microsoft/sqlserver/jdbc/DataTypes.java#L625-L676) + +## Supported types + +See [official documentation](https://learn.microsoft.com/en-us/sql/t-sql/data-types/data-types-transact-sql) + +### Numeric types + +```{eval-rst} ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | ++===============================+===================================+===============================+===============================+ +| ``decimal`` | ``DecimalType(P=18, S=0)`` | ``decimal(P=18, S=0)`` | ``decimal(P=18, S=0)`` | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``decimal(P=0..38)`` | ``DecimalType(P=0..38, S=0)`` | ``decimal(P=0..38, S=0)`` | ``decimal(P=0..38, S=0)`` | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``decimal(P=0..38, S=0..38)`` | ``DecimalType(P=0..38, S=0..38)`` | ``decimal(P=0..38, S=0..38)`` | ``decimal(P=0..38, S=0..38)`` | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``real`` | ``FloatType()`` | ``real`` | ``real`` | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``float`` | ``DoubleType()`` | ``float`` | ``float`` | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``smallint`` | ``ShortType()`` | ``smallint`` | ``smallint`` | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``tinyint`` | ``IntegerType()`` | ``int`` | ``int`` | ++-------------------------------+ | | | +| ``int`` | | | | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``bigint`` | ``LongType()`` | ``bigint`` | ``bigint`` | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +``` + +### Temporal types + +```{eval-rst} +.. note:: + + MSSQL ``timestamp`` type is alias for ``rowversion`` (see `Special types`_). It is not a temporal type! +``` + +```{eval-rst} ++------------------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | ++==========================================+======================================+===================================+===============================+ +| ``date`` | ``DateType()`` | ``date`` | ``date`` | ++------------------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +| ``smalldatetime``, minutes | ``TimestampType()``, microseconds | ``datetime2(6)``, microseconds | ``datetime``, milliseconds | ++------------------------------------------+ | | | +| ``datetime``, milliseconds | | | | ++------------------------------------------+ | | | +| ``datetime2(0)``, seconds | | | | ++------------------------------------------+ | | | +| ``datetime2(3)``, milliseconds | | | | ++------------------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +| ``datetime2(6)``, microseconds | ``TimestampType()``, microseconds | ``datetime2(6)``, microseconds | ``datetime``, milliseconds, | ++------------------------------------------+--------------------------------------+-----------------------------------+ **precision loss** [3]_ | +| ``datetime2(7)``, 100s of nanoseconds | ``TimestampType()``, microseconds, | ``datetime2(6)``, microseconds, | | +| | **precision loss** [4]_ | **precision loss** [4]_ | | ++------------------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +| ``time(0)``, seconds | ``TimestampType()``, microseconds, | ``datetime2(6)``, microseconds | ``datetime``, milliseconds | ++------------------------------------------+ with time format quirks [5]_ | | | +| ``time(3)``, milliseconds | | | | ++------------------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +| ``time(6)``, microseconds | ``TimestampType()``, microseconds, | ``datetime2(6)``, microseconds | ``datetime``, milliseconds, | ++ | with time format quirks [5]_ | | **precision loss** [3]_ | ++------------------------------------------+--------------------------------------+-----------------------------------+ + +| ``time``, 100s of nanoseconds | ``TimestampType()``, microseconds, | ``datetime2(6)``, microseconds | | ++------------------------------------------+ **precision loss** [4]_, | **precision loss** [3]_ | | +| ``time(7)``, 100s of nanoseconds | with time format quirks [5]_ | | | ++------------------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +| ``datetimeoffset`` | ``StringType()`` | ``nvarchar`` | ``nvarchar`` | ++------------------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +``` + +```{eval-rst} +.. warning:: + + Note that types in MSSQL and Spark have different value ranges: + + +-------------------+--------------------------------+--------------------------------+---------------------+--------------------------------+--------------------------------+ + | MySQL type | Min value | Max value | Spark type | Min value | Max value | + +===================+================================+================================+=====================+================================+================================+ + | ``smalldatetime`` | ``1900-01-01 00:00:00`` | ``2079-06-06 23:59:00`` | ``TimestampType()`` | ``0001-01-01 00:00:00.000000`` | ``9999-12-31 23:59:59.999999`` | + +-------------------+--------------------------------+--------------------------------+ | | | + | ``datetime`` | ``1753-01-01 00:00:00.000`` | ``9999-12-31 23:59:59.997`` | | | | + +-------------------+--------------------------------+--------------------------------+ | | | + | ``datetime2`` | ``0001-01-01 00:00:00.000000`` | ``9999-12-31 23:59:59.999999`` | | | | + +-------------------+--------------------------------+--------------------------------+ | | | + | ``time`` | ``00:00:00.0000000`` | ``23:59:59.9999999`` | | | | + +-------------------+--------------------------------+--------------------------------+---------------------+--------------------------------+--------------------------------+ + + So not all of values in Spark DataFrame can be written to MSSQL. + + References: + * `MSSQL date & time types documentation `_ + * `Spark DateType documentation `_ + * `Spark TimestampType documentation `_ +``` + +[^footnote-3]: MSSQL dialect for Spark generates DDL with type `datetime` which has precision up to milliseconds (`23:59:59.999`, 10{superscript}`-3` seconds). + Inserting data with microsecond and higher precision (`23:59:59.999999` .. `23.59:59.9999999`, 10{superscript}`-6` .. 10{superscript}`-7` seconds) + will lead to **throwing away microseconds**. + +[^footnote-4]: MSSQL support timestamp up to 100s of nanoseconds precision (`23:59:59.9999999999`, 10{superscript}`-7` seconds), + but Spark `TimestampType()` supports datetime up to microseconds precision (`23:59:59.999999`, 10{superscript}`-6` seconds). + Last digit will be lost during read or write operations. + +[^footnote-5]: `time` type is the same as `datetime2` with date `1970-01-01`. So instead of reading data from MSSQL like `23:59:59.999999` + it is actually read `1970-01-01 23:59:59.999999`, and vice versa. + +### String types + +```{eval-rst} ++-------------------+------------------+--------------------+---------------------+ +| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | ++===================+==================+====================+=====================+ +| ``char`` | ``StringType()`` | ``nvarchar`` | ``nvarchar`` | ++-------------------+ | | | +| ``char(N)`` | | | | ++-------------------+ | | | +| ``nchar`` | | | | ++-------------------+ | | | +| ``nchar(N)`` | | | | ++-------------------+ | | | +| ``varchar`` | | | | ++-------------------+ | | | +| ``varchar(N)`` | | | | ++-------------------+ | | | +| ``nvarchar`` | | | | ++-------------------+ | | | +| ``nvarchar(N)`` | | | | ++-------------------+ | | | +| ``mediumtext`` | | | | ++-------------------+ | | | +| ``text`` | | | | ++-------------------+ | | | +| ``ntext`` | | | | ++-------------------+ | | | +| ``xml`` | | | | ++-------------------+------------------+--------------------+---------------------+ +``` + +### Binary types + +```{eval-rst} ++--------------------+-------------------+--------------------+---------------------+ +| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | ++====================+===================+====================+=====================+ +| ``bit`` | ``BooleanType()`` | ``bit`` | ``bit`` | ++--------------------+-------------------+--------------------+---------------------+ +| ``binary`` | ``BinaryType()`` | ``varbinary`` | ``varbinary`` | ++--------------------+ | | | +| ``binary(N)`` | | | | ++--------------------+ | | | +| ``varbinary`` | | | | ++--------------------+ | | | +| ``varbinary(N)`` | | | | ++--------------------+ | | | +| ``image`` | | | | ++--------------------+-------------------+--------------------+---------------------+ +``` + +### Special types + +```{eval-rst} ++---------------------------+------------------+--------------------+---------------------+ +| MSSQL type (read) | Spark type | MSSQL type (write) | MSSQL type (create) | ++===========================+==================+====================+=====================+ +| ``geography`` | ``BinaryType()`` | ``varbinary`` | ``varbinary`` | ++---------------------------+ | | | +| ``geometry`` | | | | ++---------------------------+ | | | +| ``hierarchyid`` | | | | ++---------------------------+ | | | +| ``rowversion`` | | | | ++---------------------------+------------------+--------------------+---------------------+ +| ``sql_variant`` | unsupported | | | ++---------------------------+------------------+--------------------+---------------------+ +| ``sysname`` | ``StringType()`` | ``nvarchar`` | ``nvarchar`` | ++---------------------------+ | | | +| ``uniqueidentifier`` | | | | ++---------------------------+------------------+--------------------+---------------------+ +``` + +## Explicit type cast + +### `DBReader` + +It is possible to explicitly cast column type using `DBReader(columns=...)` syntax. + +For example, you can use `CAST(column AS text)` to convert data to string representation on MSSQL side, and so it will be read as Spark's `StringType()`: + +```python +from onetl.connection import MSSQL +from onetl.db import DBReader + +mssql = MSSQL(...) + +DBReader( + connection=mssql, + columns=[ + "id", + "supported_column", + "CAST(unsupported_column AS text) unsupported_column_str", + ], +) +df = reader.run() + +# cast column content to proper Spark type +df = df.select( + df.id, + df.supported_column, + # explicit cast + df.unsupported_column_str.cast("integer").alias("parsed_integer"), +) +``` + +### `DBWriter` + +Convert dataframe column to JSON using [to_json](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.functions.to_json.html), +and write it as `text` column in MSSQL: + +```python +mssql.execute( + """ + CREATE TABLE schema.target_tbl ( + id bigint, + struct_column_json text -- any string type, actually + ) + """, +) + +from pyspark.sql.functions import to_json + +df = df.select( + df.id, + to_json(df.struct_column).alias("struct_column_json"), +) + +writer.run(df) +``` + +Then you can parse this column on MSSQL side - for example, by creating a view: + +```sql +SELECT + id, + JSON_VALUE(struct_column_json, "$.nested.field") AS nested_field +FROM target_tbl +``` + +Or by using [computed column](https://learn.microsoft.com/en-us/sql/relational-databases/tables/specify-computed-columns-in-a-table): + +```sql +CREATE TABLE schema.target_table ( + id bigint, + supported_column datetime2(6), + struct_column_json text, -- any string type, actually + -- computed column + nested_field AS (JSON_VALUE(struct_column_json, "$.nested.field")) + -- or persisted column + -- nested_field AS (JSON_VALUE(struct_column_json, "$.nested.field")) PERSISTED +) +``` + +By default, column value is calculated on every table read. +Column marked as `PERSISTED` is calculated during insert, but this require additional space. diff --git a/mddocs/docs/ru/connection/db_connection/mssql/write.md b/mddocs/docs/ru/connection/db_connection/mssql/write.md new file mode 100644 index 000000000..0b6d8db19 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mssql/write.md @@ -0,0 +1,56 @@ +(mssql-write)= + +# Writing to MSSQL using `DBWriter` + +For writing data to MSSQL, use {obj}`DBWriter `. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`mssql-types` +``` + +```{eval-rst} +.. warning:: + + It is always recommended to create table explicitly using :ref:`MSSQL.execute ` + instead of relying on Spark's table DDL generation. + + This is because Spark's DDL generator can create columns with different precision and types than it is expected, + causing precision loss or other issues. +``` + +## Examples + +```python +from onetl.connection import MSSQL +from onetl.db import DBWriter + +mssql = MSSQL(...) + +df = ... # data is here + +writer = DBWriter( + connection=mssql, + target="schema.table", + options=MSSQL.WriteOptions(if_exists="append"), +) + +writer.run(df) +``` + +## Options + +Method above accepts {obj}`MSSQL.WriteOptions ` + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mssql.options +``` + +```{eval-rst} +.. autopydantic_model:: MSSQLWriteOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/mysql/connection.md b/mddocs/docs/ru/connection/db_connection/mysql/connection.md new file mode 100644 index 000000000..1b2ba94c3 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mysql/connection.md @@ -0,0 +1,12 @@ +(mysql-connection)= + +# MySQL connection + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mysql.connection +``` + +```{eval-rst} +.. autoclass:: MySQL + :members: get_packages, check +``` diff --git a/mddocs/docs/ru/connection/db_connection/mysql/execute.md b/mddocs/docs/ru/connection/db_connection/mysql/execute.md new file mode 100644 index 000000000..99c86be5b --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mysql/execute.md @@ -0,0 +1,115 @@ +(mysql-execute)= + +# Executing statements in MySQL + +```{eval-rst} +.. warning:: + + Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + + Do **NOT** use them to read large amounts of data. Use :ref:`DBReader ` or :ref:`MySQL.sql ` instead. +``` + +## How to + +There are 2 ways to execute some statement in MySQL + +### Use `MySQL.fetch` + +Use this method to perform some `SELECT` query which returns **small number or rows**, like reading +MySQL config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts {obj}`MySQL.FetchOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`mysql-types`. +``` + +#### Syntax support + +This method supports **any** query syntax supported by MySQL, like: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ✅︎ `SELECT func(arg1, arg2)` or `{?= call func(arg1, arg2)}` - special syntax for calling function +- ✅︎ `SHOW ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import MySQL + +mysql = MySQL(...) + +df = mysql.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=MySQL.FetchOptions(queryTimeout=10), +) +mysql.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `MySQL.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts {obj}`MySQL.ExecuteOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by MySQL, like: + +- ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +- ✅︎ `ALTER ...` +- ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +- ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, and so on +- ✅︎ `CALL procedure(arg1, arg2) ...` or `{call procedure(arg1, arg2)}` - special syntax for calling procedure +- ✅︎ other statements not mentioned here +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import MySQL + +mysql = MySQL(...) + +mysql.execute("DROP TABLE schema.table") +mysql.execute( + """ + CREATE TABLE schema.table ( + id bigint, + key text, + value float + ) + ENGINE = InnoDB + """, + options=MySQL.ExecuteOptions(queryTimeout=10), +) +``` + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mysql.options +``` + +```{eval-rst} +.. autopydantic_model:: MySQLFetchOptions + :inherited-members: GenericOptions + :member-order: bysource + +``` + +```{eval-rst} +.. autopydantic_model:: MySQLExecuteOptions + :inherited-members: GenericOptions + :member-order: bysource +``` diff --git a/mddocs/docs/ru/connection/db_connection/mysql/index.md b/mddocs/docs/ru/connection/db_connection/mysql/index.md new file mode 100644 index 000000000..6b69e1c1b --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mysql/index.md @@ -0,0 +1,28 @@ +(mysql)= + +# MySQL + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +connection +``` + +```{toctree} +:caption: Operations +:maxdepth: 1 + +read +sql +write +execute +``` + +```{toctree} +:caption: Troubleshooting +:maxdepth: 1 + +types +``` diff --git a/mddocs/docs/ru/connection/db_connection/mysql/prerequisites.md b/mddocs/docs/ru/connection/db_connection/mysql/prerequisites.md new file mode 100644 index 000000000..6292e7761 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mysql/prerequisites.md @@ -0,0 +1,61 @@ +(mysql-prerequisites)= + +# Prerequisites + +## Version Compatibility + +- MySQL server versions: + : - Officially declared: 8.0 - 9.2 + - Actually tested: 5.7.13, 9.2.0 +- Spark versions: 2.3.x - 3.5.x +- Java versions: 8 - 20 + +See [official documentation](https://dev.mysql.com/doc/connector-j/en/connector-j-versions.html). + +## Installing PySpark + +To use MySQL connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Connecting to MySQL + +### Connection host + +It is possible to connect to MySQL by using either DNS name of host or it's IP address. + +If you're using MySQL cluster, it is currently possible to connect only to **one specific node**. +Connecting to multiple nodes to perform load balancing, as well as automatic failover to new master/replica are not supported. + +### Connection port + +Connection is usually performed to port 3306. Port may differ for different MySQL instances. +Please ask your MySQL administrator to provide required information. + +### Required grants + +Ask your MySQL cluster administrator to set following grants for a user, +used for creating a connection: + +```{eval-rst} +.. tabs:: + + .. code-tab:: sql Read + Write + + -- allow creating tables in the target schema + GRANT CREATE ON myschema.* TO username@'192.168.1.%'; + + -- allow read & write access to specific table + GRANT SELECT, INSERT ON myschema.mytable TO username@'192.168.1.%'; + + .. code-tab:: sql Read only + + -- allow read access to specific table + GRANT SELECT ON myschema.mytable TO username@'192.168.1.%'; +``` + +In example above `'192.168.1.%''` is a network subnet `192.168.1.0 - 192.168.1.255` +where Spark driver and executors are running. To allow connecting user from any IP, use `'%'` (not secure!). + +More details can be found in [official documentation](https://dev.mysql.com/doc/refman/en/grant.html). diff --git a/mddocs/docs/ru/connection/db_connection/mysql/read.md b/mddocs/docs/ru/connection/db_connection/mysql/read.md new file mode 100644 index 000000000..c3751dca2 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mysql/read.md @@ -0,0 +1,93 @@ +(mysql-read)= + +# Reading from MySQL using `DBReader` + +{obj}`DBReader ` supports {ref}`strategy` for incremental data reading, +but does not support custom queries, like `JOIN`. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`mysql-types` +``` + +## Supported DBReader features + +- ✅︎ `columns` +- ✅︎ `where` +- ✅︎ `hwm`, supported strategies: +- - ✅︎ {ref}`snapshot-strategy` +- - ✅︎ {ref}`incremental-strategy` +- - ✅︎ {ref}`snapshot-batch-strategy` +- - ✅︎ {ref}`incremental-batch-strategy` +- ✅︎ `hint` (see [official documentation](https://dev.mysql.com/doc/refman/en/optimizer-hints.html)) +- ❌ `df_schema` +- ✅︎ `options` (see {obj}`MySQL.ReadOptions `) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import MySQL +from onetl.db import DBReader + +mysql = MySQL(...) + +reader = DBReader( + connection=mysql, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + hint="SKIP_SCAN(schema.table key_index)", + options=MySQL.ReadOptions(partitionColumn="id", numPartitions=10), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import MySQL +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +mysql = MySQL(...) + +reader = DBReader( + connection=mysql, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + hint="SKIP_SCAN(schema.table key_index)", + hwm=DBReader.AutoDetectHWM(name="mysql_hwm", expression="updated_dt"), + options=MySQL.ReadOptions(partitionColumn="id", numPartitions=10), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Oracle to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Oracle to Spark, and may also improve performance of the query. +Especially if there are indexes for columns used in `where` clause. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mysql.options +``` + +```{eval-rst} +.. autopydantic_model:: MySQLReadOptions + :inherited-members: GenericOptions + :member-order: bysource +``` diff --git a/mddocs/docs/ru/connection/db_connection/mysql/sql.md b/mddocs/docs/ru/connection/db_connection/mysql/sql.md new file mode 100644 index 000000000..7d2dd4e51 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mysql/sql.md @@ -0,0 +1,81 @@ +(mysql-sql)= + +# Reading from MySQL using `MySQL.sql` + +`MySQL.sql` allows passing custom SQL query, but does not support incremental strategies. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`mysql-types` +``` + +```{eval-rst} +.. warning:: + + Statement is executed in **read-write** connection, so if you're calling some functions/procedures with DDL/DML statements inside, + they can change data in your database. +``` + +## Syntax support + +Only queries with the following syntax are supported: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ❌ `SHOW ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import MySQL + +mysql = MySQL(...) +df = mysql.sql( + """ + SELECT + id, + key, + CAST(value AS text) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """, + options=MySQL.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from MySQL to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from MySQL to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mysql.options +``` + +```{eval-rst} +.. autopydantic_model:: MySQLSQLOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/mysql/types.md b/mddocs/docs/ru/connection/db_connection/mysql/types.md new file mode 100644 index 000000000..df3d6849b --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mysql/types.md @@ -0,0 +1,382 @@ +(mysql-types)= + +# MySQL \<-> Spark type mapping + +```{eval-rst} +.. note:: + + The results below are valid for Spark 3.5.5, and may differ on other Spark versions. +``` + +## Type detection & casting + +Spark's DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from MySQL + +This is how MySQL connector performs this: + +- For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and MySQL type. +- Find corresponding `MySQL type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- Create DataFrame from query with specific column names and Spark types. + +### Writing to some existing MySQL table + +This is how MySQL connector performs this: + +- Get names of columns in DataFrame. [^footnote-1] +- Perform `SELECT * FROM table LIMIT 0` query. +- Take only columns present in DataFrame (by name, case insensitive). For each found column get MySQL type. +- Find corresponding `Spark type` → `MySQL type (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- If `MySQL type (write)` match `MySQL type (read)`, no additional casts will be performed, DataFrame column will be written to MySQL as is. +- If `MySQL type (write)` does not match `MySQL type (read)`, DataFrame column will be casted to target column type **on MySQL side**. For example, you can write column with text data to `int` column, if column contains valid integer values within supported value range and precision. + +[^footnote-1]: This allows to write data to tables with `DEFAULT` and `GENERATED` columns - if DataFrame has no such column, + it will be populated by MySQL. + +### Create new table using Spark + +```{eval-rst} +.. warning:: + + ABSOLUTELY NOT RECOMMENDED! +``` + +This is how MySQL connector performs this: + +- Find corresponding `Spark type` → `MySQL type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- Generate DDL for creating table in MySQL, like `CREATE TABLE (col1 ...)`, and run it. +- Write DataFrame to created table as is. + +But some cases this may lead to using wrong column type. For example, Spark creates column of type `timestamp` +which corresponds to MySQL type `timestamp(0)` (precision up to seconds) +instead of more precise `timestamp(6)` (precision up to nanoseconds). +This may lead to incidental precision loss, or sometimes data cannot be written to created table at all. + +So instead of relying on Spark to create tables: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + writer = DBWriter( + connection=mysql, + target="myschema.target_tbl", + options=MySQL.WriteOptions( + if_exists="append", + createTableOptions="ENGINE = InnoDB", + ), + ) + writer.run(df) +``` + +Always prefer creating tables with specific types **BEFORE WRITING DATA**: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + mysql.execute( + """ + CREATE TABLE schema.table ( + id bigint, + key text, + value timestamp(6) -- specific type and precision + ) + ENGINE = InnoDB + """, + ) + + writer = DBWriter( + connection=mysql, + target="myschema.target_tbl", + options=MySQL.WriteOptions(if_exists="append"), + ) + writer.run(df) +``` + +### References + +Here you can find source code with type conversions: + +- [MySQL -> JDBC](https://github.com/mysql/mysql-connector-j/blob/8.0.33/src/main/core-api/java/com/mysql/cj/MysqlType.java#L44-L623) +- [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MySQLDialect.scala#L104-L132) +- [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/MySQLDialect.scala#L204-L211) +- [JDBC -> MySQL](https://github.com/mysql/mysql-connector-j/blob/8.0.33/src/main/core-api/java/com/mysql/cj/MysqlType.java#L625-L867) + +## Supported types + +See [official documentation](https://dev.mysql.com/doc/refman/en/data-types.html) + +### Numeric types + +```{eval-rst} ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | ++===============================+===================================+===============================+===============================+ +| ``decimal`` | ``DecimalType(P=10, S=0)`` | ``decimal(P=10, S=0)`` | ``decimal(P=10, S=0)`` | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``decimal(P=0..38)`` | ``DecimalType(P=0..38, S=0)`` | ``decimal(P=0..38, S=0)`` | ``decimal(P=0..38, S=0)`` | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``decimal(P=0..38, S=0..30)`` | ``DecimalType(P=0..38, S=0..30)`` | ``decimal(P=0..38, S=0..30)`` | ``decimal(P=0..38, S=0..30)`` | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``decimal(P=39..65, S=...)`` | unsupported [2]_ | | | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``float`` | ``DoubleType()`` | ``double`` | ``double`` | ++-------------------------------+ | | | +| ``double`` | | | | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``tinyint`` | ``IntegerType()`` | ``int`` | ``int`` | ++-------------------------------+ | | | +| ``smallint`` | | | | ++-------------------------------+ | | | +| ``mediumint`` | | | | ++-------------------------------+ | | | +| ``int`` | | | | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +| ``bigint`` | ``LongType()`` | ``bigint`` | ``bigint`` | ++-------------------------------+-----------------------------------+-------------------------------+-------------------------------+ +``` + +[^footnote-2]: MySQL support decimal types with precision `P` up to 65. + + But Spark's `DecimalType(P, S)` supports maximum `P=38`. It is impossible to read, write or operate with values of larger precision, + this leads to an exception. + +### Temporal types + +```{eval-rst} ++-----------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | ++===================================+======================================+===================================+===============================+ +| ``year`` | ``DateType()`` | ``date`` | ``date`` | ++-----------------------------------+ | | | +| ``date`` | | | | ++-----------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +| ``datetime``, seconds | ``TimestampType()``, microseconds | ``timestamp(6)``, microseconds | ``timestamp(0)``, seconds | ++-----------------------------------+ | | | +| ``timestamp``, seconds | | | | ++-----------------------------------+ | | | +| ``datetime(0)``, seconds | | | | ++-----------------------------------+ | | | +| ``timestamp(0)``, seconds | | | | ++-----------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +| ``datetime(3)``, milliseconds | ``TimestampType()``, microseconds | ``timestamp(6)``, microseconds | ``timestamp(0)``, seconds, | ++-----------------------------------+ | | **precision loss** [4]_, | +| ``timestamp(3)``, milliseconds | | | | ++-----------------------------------+ | | | +| ``datetime(6)``, microseconds | | | | ++-----------------------------------+ | | | +| ``timestamp(6)``, microseconds | | | | ++-----------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +| ``time``, seconds | ``TimestampType()``, microseconds, | ``timestamp(6)``, microseconds | ``timestamp(0)``, seconds | ++-----------------------------------+ with time format quirks [5]_ | | | +| ``time(0)``, seconds | | | | ++-----------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +| ``time(3)``, milliseconds | ``TimestampType()``, microseconds | ``timestamp(6)``, microseconds | ``timestamp(0)``, seconds, | ++-----------------------------------+ with time format quirks [5]_ | | **precision loss** [4]_, | +| ``time(6)``, microseconds | | | | ++-----------------------------------+--------------------------------------+-----------------------------------+-------------------------------+ +``` + +```{eval-rst} +.. warning:: + + Note that types in MySQL and Spark have different value ranges: + + +---------------+--------------------------------+--------------------------------+---------------------+--------------------------------+--------------------------------+ + | MySQL type | Min value | Max value | Spark type | Min value | Max value | + +===============+================================+================================+=====================+================================+================================+ + | ``year`` | ``1901`` | ``2155`` | ``DateType()`` | ``0001-01-01`` | ``9999-12-31`` | + +---------------+--------------------------------+--------------------------------+ | | | + | ``date`` | ``1000-01-01`` | ``9999-12-31`` | | | | + +---------------+--------------------------------+--------------------------------+---------------------+--------------------------------+--------------------------------+ + | ``datetime`` | ``1000-01-01 00:00:00.000000`` | ``9999-12-31 23:59:59.499999`` | ``TimestampType()`` | ``0001-01-01 00:00:00.000000`` | ``9999-12-31 23:59:59.999999`` | + +---------------+--------------------------------+--------------------------------+ | | | + | ``timestamp`` | ``1970-01-01 00:00:01.000000`` | ``9999-12-31 23:59:59.499999`` | | | | + +---------------+--------------------------------+--------------------------------+ | | | + | ``time`` | ``-838:59:59.000000`` | ``838:59:59.000000`` | | | | + +---------------+--------------------------------+--------------------------------+---------------------+--------------------------------+--------------------------------+ + + So Spark can read all the values from MySQL, but not all of values in Spark DataFrame can be written to MySQL. + + References: + * `MySQL year documentation `_ + * `MySQL date, datetime & timestamp documentation `_ + * `MySQL time documentation `_ + * `Spark DateType documentation `_ + * `Spark TimestampType documentation `_ +``` + +[^footnote-3]: MySQL dialect generates DDL with MySQL type `timestamp` which is alias for `timestamp(0)` with precision up to seconds (`23:59:59`). + Inserting data with microseconds precision (`23:59:59.999999`) will lead to **throwing away microseconds**. + +[^footnote-4]: `time` type is the same as `timestamp` with date `1970-01-01`. So instead of reading data from MySQL like `23:59:59` + it is actually read `1970-01-01 23:59:59`, and vice versa. + +### String types + +```{eval-rst} ++-------------------------------+------------------+--------------------+---------------------+ +| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | ++===============================+==================+====================+=====================+ +| ``char`` | ``StringType()`` | ``longtext`` | ``longtext`` | ++-------------------------------+ | | | +| ``char(N)`` | | | | ++-------------------------------+ | | | +| ``varchar(N)`` | | | | ++-------------------------------+ | | | +| ``mediumtext`` | | | | ++-------------------------------+ | | | +| ``text`` | | | | ++-------------------------------+ | | | +| ``longtext`` | | | | ++-------------------------------+ | | | +| ``json`` | | | | ++-------------------------------+ | | | +| ``enum("val1", "val2", ...)`` | | | | ++-------------------------------+ | | | +| ``set("val1", "val2", ...)`` | | | | ++-------------------------------+------------------+--------------------+---------------------+ +``` + +### Binary types + +```{eval-rst} ++-------------------+------------------+--------------------+---------------------+ +| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | ++===================+==================+====================+=====================+ +| ``binary`` | ``BinaryType()`` | ``blob`` | ``blob`` | ++-------------------+ | | | +| ``binary(N)`` | | | | ++-------------------+ | | | +| ``varbinary(N)`` | | | | ++-------------------+ | | | +| ``mediumblob`` | | | | ++-------------------+ | | | +| ``blob`` | | | | ++-------------------+ | | | +| ``longblob`` | | | | ++-------------------+------------------+--------------------+---------------------+ +``` + +### Geometry types + +```{eval-rst} ++------------------------+------------------+--------------------+---------------------+ +| MySQL type (read) | Spark type | MySQL type (write) | MySQL type (create) | ++========================+==================+====================+=====================+ +| ``point`` | ``BinaryType()`` | ``blob`` | ``blob`` | ++------------------------+ | | | +| ``linestring`` | | | | ++------------------------+ | | | +| ``polygon`` | | | | ++------------------------+ | | | +| ``geometry`` | | | | ++------------------------+ | | | +| ``multipoint`` | | | | ++------------------------+ | | | +| ``multilinestring`` | | | | ++------------------------+ | | | +| ``multipolygon`` | | | | ++------------------------+ | | | +| ``geometrycollection`` | | | | ++------------------------+------------------+--------------------+---------------------+ +``` + +## Explicit type cast + +### `DBReader` + +It is possible to explicitly cast column type using `DBReader(columns=...)` syntax. + +For example, you can use `CAST(column AS text)` to convert data to string representation on MySQL side, and so it will be read as Spark's `StringType()`. + +It is also possible to use [JSON_OBJECT](https://dev.mysql.com/doc/refman/en/json.html) MySQL function and parse JSON columns in MySQL with the {obj}`JSON.parse_column ` method. + +```python +from pyspark.sql.types import IntegerType, StructType, StructField + +from onetl.connection import MySQL +from onetl.db import DBReader +from onetl.file.format import JSON + +mysql = MySQL(...) + +DBReader( + connection=mysql, + columns=[ + "id", + "supported_column", + "CAST(unsupported_column AS text) unsupported_column_str", + # or + "JSON_OBJECT('key', value_column) json_column", + ], +) +df = reader.run() + +json_scheme = StructType([StructField("key", IntegerType())]) + +df = df.select( + df.id, + df.supported_column, + # explicit cast + df.unsupported_column_str.cast("integer").alias("parsed_integer"), + JSON().parse_column("json_column", json_scheme).alias("struct_column"), +) +``` + +### `DBWriter` + +To write JSON data to a `json` or `text` column in a MySQL table, use the {obj}`JSON.serialize_column ` method. + +```python +from onetl.connection import MySQL +from onetl.db import DBWriter +from onetl.file.format import JSON + +mysql.execute( + """ + CREATE TABLE schema.target_tbl ( + id bigint, + array_column_json json -- any string type, actually + ) + ENGINE = InnoDB + """, +) + +df = df.select( + df.id, + JSON().serialize_column(df.array_column).alias("array_column_json"), +) + +writer.run(df) +``` + +Then you can parse this column on MySQL side - for example, by creating a view: + +```sql +SELECT + id, + array_column_json->"$[0]" AS array_item +FROM target_tbl +``` + +Or by using [GENERATED column](https://dev.mysql.com/doc/refman/en/create-table-generated-columns.html): + +```sql +CREATE TABLE schema.target_table ( + id bigint, + supported_column timestamp, + array_column_json json, -- any string type, actually + -- virtual column + array_item_0 GENERATED ALWAYS AS (array_column_json->"$[0]")) VIRTUAL + -- or stired column + -- array_item_0 GENERATED ALWAYS AS (array_column_json->"$[0]")) STORED +) +``` + +`VIRTUAL` column value is calculated on every table read. +`STORED` column value is calculated during insert, but this require additional space. diff --git a/mddocs/docs/ru/connection/db_connection/mysql/write.md b/mddocs/docs/ru/connection/db_connection/mysql/write.md new file mode 100644 index 000000000..7f7f2a5d2 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/mysql/write.md @@ -0,0 +1,60 @@ +(mysql-write)= + +# Writing to MySQL using `DBWriter` + +For writing data to MySQL, use {obj}`DBWriter `. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`mysql-types` +``` + +```{eval-rst} +.. warning:: + + It is always recommended to create table explicitly using :ref:`MySQL.execute ` + instead of relying on Spark's table DDL generation. + + This is because Spark's DDL generator can create columns with different precision and types than it is expected, + causing precision loss or other issues. +``` + +## Examples + +```python +from onetl.connection import MySQL +from onetl.db import DBWriter + +mysql = MySQL(...) + +df = ... # data is here + +writer = DBWriter( + connection=mysql, + target="schema.table", + options=MySQL.WriteOptions( + if_exists="append", + # ENGINE is required by MySQL + createTableOptions="ENGINE = MergeTree() ORDER BY id", + ), +) + +writer.run(df) +``` + +## Options + +Method above accepts {obj}`MySQL.WriteOptions ` + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.mysql.options +``` + +```{eval-rst} +.. autopydantic_model:: MySQLWriteOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/oracle/connection.md b/mddocs/docs/ru/connection/db_connection/oracle/connection.md new file mode 100644 index 000000000..26fc8a01e --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/oracle/connection.md @@ -0,0 +1,12 @@ +(oracle-connection)= + +# Oracle connection + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.oracle.connection +``` + +```{eval-rst} +.. autoclass:: Oracle + :members: get_packages, check +``` diff --git a/mddocs/docs/ru/connection/db_connection/oracle/execute.md b/mddocs/docs/ru/connection/db_connection/oracle/execute.md new file mode 100644 index 000000000..28b5baa39 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/oracle/execute.md @@ -0,0 +1,115 @@ +(oracle-execute)= + +# Executing statements in Oracle + +```{eval-rst} +.. warning:: + + Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + + Do **NOT** use them to read large amounts of data. Use :ref:`DBReader ` or :ref:`Oracle.sql ` instead. +``` + +## How to + +There are 2 ways to execute some statement in Oracle + +### Use `Oracle.fetch` + +Use this method to execute some `SELECT` query which returns **small number or rows**, like reading +Oracle config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts {obj}`Oracle.FetchOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`oracle-types`. +``` + +#### Syntax support + +This method supports **any** query syntax supported by Oracle, like: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ✅︎ `SELECT func(arg1, arg2) FROM DUAL` - call function +- ✅︎ `SHOW ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Oracle + +oracle = Oracle(...) + +df = oracle.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=Oracle.FetchOptions(queryTimeout=10), +) +oracle.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `Oracle.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts {obj}`Oracle.ExecuteOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Oracle, like: + +- ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...` +- ✅︎ `ALTER ...` +- ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +- ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +- ✅︎ `CALL procedure(arg1, arg2) ...` or `{call procedure(arg1, arg2)}` - special syntax for calling procedure +- ✅︎ `DECLARE ... BEGIN ... END` - execute PL/SQL statement +- ✅︎ other statements not mentioned here +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Oracle + +oracle = Oracle(...) + +oracle.execute("DROP TABLE schema.table") +oracle.execute( + """ + CREATE TABLE schema.table ( + id bigint GENERATED ALWAYS AS IDENTITY, + key VARCHAR2(4000), + value NUMBER + ) + """, + options=Oracle.ExecuteOptions(queryTimeout=10), +) +``` + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.oracle.options +``` + +```{eval-rst} +.. autopydantic_model:: OracleFetchOptions + :inherited-members: GenericOptions + :member-order: bysource + +``` + +```{eval-rst} +.. autopydantic_model:: OracleExecuteOptions + :inherited-members: GenericOptions + :member-order: bysource +``` diff --git a/mddocs/docs/ru/connection/db_connection/oracle/index.md b/mddocs/docs/ru/connection/db_connection/oracle/index.md new file mode 100644 index 000000000..5218cc287 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/oracle/index.md @@ -0,0 +1,28 @@ +(oracle)= + +# Oracle + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +connection +``` + +```{toctree} +:caption: Operations +:maxdepth: 1 + +read +sql +write +execute +``` + +```{toctree} +:caption: Troubleshooting +:maxdepth: 1 + +types +``` diff --git a/mddocs/docs/ru/connection/db_connection/oracle/prerequisites.md b/mddocs/docs/ru/connection/db_connection/oracle/prerequisites.md new file mode 100644 index 000000000..5a350937b --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/oracle/prerequisites.md @@ -0,0 +1,108 @@ +(oracle-prerequisites)= + +# Prerequisites + +## Version Compatibility + +- Oracle Server versions: + : - Officially declared: 19c, 21c, 23ai + - Actually tested: 11.2, 23.5 +- Spark versions: 2.3.x - 3.5.x +- Java versions: 8 - 20 + +See [official documentation](https://www.oracle.com/cis/database/technologies/appdev/jdbc-downloads.html). + +## Installing PySpark + +To use Oracle connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Connecting to Oracle + +### Connection port + +Connection is usually performed to port 1521. Port may differ for different Oracle instances. +Please ask your Oracle administrator to provide required information. + +### Connection host + +It is possible to connect to Oracle by using either DNS name of host or it's IP address. + +If you're using Oracle cluster, it is currently possible to connect only to **one specific node**. +Connecting to multiple nodes to perform load balancing, as well as automatic failover to new master/replica are not supported. + +### Connect as proxy user + +It is possible to connect to database as another user without knowing this user password. + +This can be enabled by granting user a special `CONNECT THROUGH` permission: + +```sql +ALTER USER schema_owner GRANT CONNECT THROUGH proxy_user; +``` + +Then you can connect to Oracle using credentials of `proxy_user` but specify that you need permissions of `schema_owner`: + +```python +oracle = Oracle( + ..., + user="proxy_user[schema_owner]", + password="proxy_user password", +) +``` + +See [official documentation](https://oracle-base.com/articles/misc/proxy-users-and-connect-through). + +### Required grants + +Ask your Oracle cluster administrator to set following grants for a user, +used for creating a connection: + +```{eval-rst} +.. tabs:: + + .. code-tab:: sql Read + Write (schema is owned by user) + + -- allow user to log in + GRANT CREATE SESSION TO username; + + -- allow creating tables in user schema + GRANT CREATE TABLE TO username; + + -- allow read & write access to specific table + GRANT SELECT, INSERT ON username.mytable TO username; + + .. code-tab:: sql Read + Write (schema is not owned by user) + + -- allow user to log in + GRANT CREATE SESSION TO username; + + -- allow creating tables in any schema, + -- as Oracle does not support specifying exact schema name + GRANT CREATE ANY TABLE TO username; + + -- allow read & write access to specific table + GRANT SELECT, INSERT ON someschema.mytable TO username; + + -- only if if_exists="replace_entire_table" is used: + -- allow dropping/truncating tables in any schema, + -- as Oracle does not support specifying exact schema name + GRANT DROP ANY TABLE TO username; + + .. code-tab:: sql Read only + + -- allow user to log in + GRANT CREATE SESSION TO username; + + -- allow read access to specific table + GRANT SELECT ON someschema.mytable TO username; +``` + +More details can be found in official documentation: +: - [GRANT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/GRANT.html) + - [SELECT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/SELECT.html) + - [CREATE TABLE](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/SELECT.html) + - [INSERT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/INSERT.html) + - [TRUNCATE TABLE](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/TRUNCATE-TABLE.html) diff --git a/mddocs/docs/ru/connection/db_connection/oracle/read.md b/mddocs/docs/ru/connection/db_connection/oracle/read.md new file mode 100644 index 000000000..4f11e86e7 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/oracle/read.md @@ -0,0 +1,93 @@ +(oracle-read)= + +# Reading from Oracle using `DBReader` + +{obj}`DBReader ` supports {ref}`strategy` for incremental data reading, +but does not support custom queries, like `JOIN`. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`oracle-types` +``` + +## Supported DBReader features + +- ✅︎ `columns` +- ✅︎ `where` +- ✅︎ `hwm`, supported strategies: +- - ✅︎ {ref}`snapshot-strategy` +- - ✅︎ {ref}`incremental-strategy` +- - ✅︎ {ref}`snapshot-batch-strategy` +- - ✅︎ {ref}`incremental-batch-strategy` +- ✅︎ `hint` (see [official documentation](https://docs.oracle.com/cd/B10500_01/server.920/a96533/hintsref.htm)) +- ❌ `df_schema` +- ✅︎ `options` (see {obj}`Oracle.ReadOptions `) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Oracle +from onetl.db import DBReader + +oracle = Oracle(...) + +reader = DBReader( + connection=oracle, + source="schema.table", + columns=["id", "key", "CAST(value AS VARCHAR2(4000)) value", "updated_dt"], + where="key = 'something'", + hint="INDEX(schema.table key_index)", + options=Oracle.ReadOptions(partitionColumn="id", numPartitions=10), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Oracle +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +oracle = Oracle(...) + +reader = DBReader( + connection=oracle, + source="schema.table", + columns=["id", "key", "CAST(value AS VARCHAR2(4000)) value", "updated_dt"], + where="key = 'something'", + hint="INDEX(schema.table key_index)", + hwm=DBReader.AutoDetectHWM(name="oracle_hwm", expression="updated_dt"), + options=Oracle.ReadOptions(partitionColumn="id", numPartitions=10), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Oracle to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Oracle to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.oracle.options +``` + +```{eval-rst} +.. autopydantic_model:: OracleReadOptions + :inherited-members: GenericOptions + :member-order: bysource +``` diff --git a/mddocs/docs/ru/connection/db_connection/oracle/sql.md b/mddocs/docs/ru/connection/db_connection/oracle/sql.md new file mode 100644 index 000000000..73d82e739 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/oracle/sql.md @@ -0,0 +1,81 @@ +(oracle-sql)= + +# Reading from Oracle using `Oracle.sql` + +`Oracle.sql` allows passing custom SQL query, but does not support incremental strategies. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`oracle-types` +``` + +```{eval-rst} +.. warning:: + + Statement is executed in **read-write** connection, so if you're calling some functions/procedures with DDL/DML statements inside, + they can change data in your database. +``` + +## Syntax support + +Only queries with the following syntax are supported: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ❌ `SHOW ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import Oracle + +oracle = Oracle(...) +df = oracle.sql( + """ + SELECT + id, + key, + CAST(value AS VARCHAR2(4000)) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """, + options=Oracle.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from Oracle to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from Oracle to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.oracle.options +``` + +```{eval-rst} +.. autopydantic_model:: OracleSQLOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/oracle/types.md b/mddocs/docs/ru/connection/db_connection/oracle/types.md new file mode 100644 index 000000000..7097bd439 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/oracle/types.md @@ -0,0 +1,400 @@ +(oracle-types)= + +# Oracle \<-> Spark type mapping + +```{eval-rst} +.. note:: + + The results below are valid for Spark 3.5.5, and may differ on other Spark versions. +``` + +## Type detection & casting + +Spark's DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from Oracle + +This is how Oracle connector performs this: + +- For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and Oracle type. +- Find corresponding `Oracle type (read)` → `Spark type` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- Create DataFrame from query with specific column names and Spark types. + +### Writing to some existing Oracle table + +This is how Oracle connector performs this: + +- Get names of columns in DataFrame. [^footnote-1] +- Perform `SELECT * FROM table LIMIT 0` query. +- Take only columns present in DataFrame (by name, case insensitive). For each found column get Clickhouse type. +- **Find corresponding** `Oracle type (read)` → `Spark type` **combination** (see below) for each DataFrame column. If no combination is found, raise exception. [^footnote-2] +- Find corresponding `Spark type` → `Oracle type (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- If `Oracle type (write)` match `Oracle type (read)`, no additional casts will be performed, DataFrame column will be written to Oracle as is. +- If `Oracle type (write)` does not match `Oracle type (read)`, DataFrame column will be casted to target column type **on Oracle side**. + For example, you can write column with text data to `int` column, if column contains valid integer values within supported value range and precision. + +[^footnote-1]: This allows to write data to tables with `DEFAULT` and `GENERATED` columns - if DataFrame has no such column, + it will be populated by Oracle. + +[^footnote-2]: Yes, this is weird. + +### Create new table using Spark + +```{eval-rst} +.. warning:: + + ABSOLUTELY NOT RECOMMENDED! +``` + +This is how Oracle connector performs this: + +- Find corresponding `Spark type` → `Oracle type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- Generate DDL for creating table in Oracle, like `CREATE TABLE (col1 ...)`, and run it. +- Write DataFrame to created table as is. + +But Oracle connector support only limited number of types and almost no custom clauses (like `PARTITION BY`, `INDEX`, etc). +So instead of relying on Spark to create tables: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + writer = DBWriter( + connection=oracle, + target="public.table", + options=Oracle.WriteOptions(if_exists="append"), + ) + writer.run(df) +``` + +Always prefer creating table with desired DDL **BEFORE WRITING DATA**: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + oracle.execute( + """ + CREATE TABLE username.table ( + id NUMBER, + business_dt TIMESTAMP(6), + value VARCHAR2(2000) + ) + """, + ) + + writer = DBWriter( + connection=oracle, + target="public.table", + options=Oracle.WriteOptions(if_exists="append"), + ) + writer.run(df) +``` + +See Oracle [CREATE TABLE](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/SELECT.html) documentation. + +## Supported types + +### References + +See [List of Oracle types](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/Data-Types.html). + +Here you can find source code with type conversions: + +- [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/OracleDialect.scala#L83-L109) +- [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/OracleDialect.scala#L111-L123) + +### Numeric types + +```{eval-rst} ++----------------------------------+-----------------------------------+-------------------------------+---------------------------+ +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | ++==================================+===================================+===============================+===========================+ +| ``NUMBER`` | ``DecimalType(P=38, S=10)`` | ``NUMBER(P=38, S=10)`` | ``NUMBER(P=38, S=10)`` | ++----------------------------------+-----------------------------------+-------------------------------+---------------------------+ +| ``NUMBER(P=0..38)`` | ``DecimalType(P=0..38, S=0)`` | ``NUMBER(P=0..38, S=0)`` | ``NUMBER(P=38, S=0)`` | ++----------------------------------+-----------------------------------+-------------------------------+---------------------------+ +| ``NUMBER(P=0..38, S=0..38)`` | ``DecimalType(P=0..38, S=0..38)`` | ``NUMBER(P=0..38, S=0..38)`` | ``NUMBER(P=38, S=0..38)`` | ++----------------------------------+-----------------------------------+-------------------------------+---------------------------+ +| ``NUMBER(P=..., S=-127..-1)`` | unsupported [3]_ | | | ++----------------------------------+-----------------------------------+-------------------------------+---------------------------+ +| ``FLOAT`` | ``DecimalType(P=38, S=10)`` | ``NUMBER(P=38, S=10)`` | ``NUMBER(P=38, S=10)`` | ++----------------------------------+ | | | +| ``FLOAT(N)`` | | | | ++----------------------------------+ | | | +| ``REAL`` | | | | ++----------------------------------+ | | | +| ``DOUBLE PRECISION`` | | | | ++----------------------------------+-----------------------------------+-------------------------------+---------------------------+ +| ``BINARY_FLOAT`` | ``FloatType()`` | ``NUMBER(P=19, S=4)`` | ``NUMBER(P=19, S=4)`` | ++----------------------------------+-----------------------------------+ | | +| ``BINARY_DOUBLE`` | ``DoubleType()`` | | | ++----------------------------------+-----------------------------------+-------------------------------+---------------------------+ +| ``SMALLINT`` | ``DecimalType(P=38, S=0)`` | ``NUMBER(P=38, S=0)`` | ``NUMBER(P=38, S=0)`` | ++----------------------------------+ | | | +| ``INTEGER`` | | | | ++----------------------------------+-----------------------------------+-------------------------------+---------------------------+ +| ``LONG`` | ``StringType()`` | ``CLOB`` | ``CLOB`` | ++----------------------------------+-----------------------------------+-------------------------------+---------------------------+ +``` + +[^footnote-3]: Oracle support decimal types with negative scale, like `NUMBER(38, -10)`. Spark doesn't. + +### Temporal types + +```{eval-rst} ++--------------------------------------------+------------------------------------+---------------------------------+---------------------------------+ +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | ++============================================+====================================+=================================+=================================+ +| ``DATE``, days | ``TimestampType()``, microseconds | ``TIMESTAMP(6)``, microseconds | ``TIMESTAMP(6)``, microseconds | ++--------------------------------------------+------------------------------------+---------------------------------+---------------------------------+ +| ``TIMESTAMP``, microseconds | ``TimestampType()``, microseconds | ``TIMESTAMP(6)``, microseconds | ``TIMESTAMP(6)``, microseconds | ++--------------------------------------------+ | | | +| ``TIMESTAMP(0)``, seconds | | | | ++--------------------------------------------+ | | | +| ``TIMESTAMP(3)``, milliseconds | | | | ++--------------------------------------------+ | | | +| ``TIMESTAMP(6)``, microseconds | | | | ++--------------------------------------------+------------------------------------+---------------------------------+---------------------------------+ +| ``TIMESTAMP(9)``, nanoseconds | ``TimestampType()``, microseconds, | ``TIMESTAMP(6)``, microseconds, | ``TIMESTAMP(6)``, microseconds, | +| | **precision loss** [4]_ | **precision loss** | **precision loss** | ++--------------------------------------------+------------------------------------+---------------------------------+---------------------------------+ +| ``TIMESTAMP WITH TIME ZONE`` | unsupported | | | ++--------------------------------------------+ | | | +| ``TIMESTAMP(N) WITH TIME ZONE`` | | | | ++--------------------------------------------+ | | | +| ``TIMESTAMP WITH LOCAL TIME ZONE`` | | | | ++--------------------------------------------+ | | | +| ``TIMESTAMP(N) WITH LOCAL TIME ZONE`` | | | | ++--------------------------------------------+ | | | +| ``INTERVAL YEAR TO MONTH`` | | | | ++--------------------------------------------+ | | | +| ``INTERVAL DAY TO SECOND`` | | | | ++--------------------------------------------+------------------------------------+---------------------------------+---------------------------------+ +``` + +```{eval-rst} +.. warning:: + + Note that types in Oracle and Spark have different value ranges: + + +---------------+------------------------------------+-----------------------------------+---------------------+--------------------------------+--------------------------------+ + | Oracle type | Min value | Max value | Spark type | Min value | Max value | + +===============+====================================+===================================+=====================+================================+================================+ + | ``date`` | ``-4712-01-01`` | ``9999-01-01`` | ``DateType()`` | ``0001-01-01`` | ``9999-12-31`` | + +---------------+------------------------------------+-----------------------------------+---------------------+--------------------------------+--------------------------------+ + | ``timestamp`` | ``-4712-01-01 00:00:00.000000000`` | ``9999-12-31 23:59:59.999999999`` | ``TimestampType()`` | ``0001-01-01 00:00:00.000000`` | ``9999-12-31 23:59:59.999999`` | + +---------------+------------------------------------+-----------------------------------+---------------------+--------------------------------+--------------------------------+ + + So not all of values can be read from Oracle to Spark. + + References: + * `Oracle date, timestamp and intervals documentation `_ + * `Spark DateType documentation `_ + * `Spark TimestampType documentation `_ +``` + +[^footnote-4]: Oracle support timestamp up to nanoseconds precision (`23:59:59.999999999`), + but Spark `TimestampType()` supports datetime up to microseconds precision (`23:59:59.999999`). + Nanoseconds will be lost during read or write operations. + +### String types + +```{eval-rst} ++-----------------------------+------------------+---------------------+----------------------+ +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | ++=============================+==================+=====================+======================+ +| ``CHAR`` | ``StringType()`` | ``CLOB`` | ``CLOB`` | ++-----------------------------+ | | | +| ``CHAR(N CHAR)`` | | | | ++-----------------------------+ | | | +| ``CHAR(N BYTE)`` | | | | ++-----------------------------+ | | | +| ``NCHAR`` | | | | ++-----------------------------+ | | | +| ``NCHAR(N)`` | | | | ++-----------------------------+ | | | +| ``VARCHAR(N)`` | | | | ++-----------------------------+ | | | +| ``LONG VARCHAR`` | | | | ++-----------------------------+ | | | +| ``VARCHAR2(N CHAR)`` | | | | ++-----------------------------+ | | | +| ``VARCHAR2(N BYTE)`` | | | | ++-----------------------------+ | | | +| ``NVARCHAR2(N)`` | | | | ++-----------------------------+ | | | +| ``CLOB`` | | | | ++-----------------------------+ | | | +| ``NCLOB`` | | | | ++-----------------------------+------------------+---------------------+----------------------+ +``` + +### Binary types + +```{eval-rst} ++--------------------------+------------------+---------------------+----------------------+ +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | ++==========================+==================+=====================+======================+ +| ``RAW(N)`` | ``BinaryType()`` | ``BLOB`` | ``BLOB`` | ++--------------------------+ | | | +| ``LONG RAW`` | | | | ++--------------------------+ | | | +| ``BLOB`` | | | | ++--------------------------+------------------+---------------------+----------------------+ +| ``BFILE`` | unsupported | | | ++--------------------------+------------------+---------------------+----------------------+ +``` + +### Struct types + +```{eval-rst} ++-------------------------------------+------------------+---------------------+----------------------+ +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | ++=====================================+==================+=====================+======================+ +| ``XMLType`` | ``StringType()`` | ``CLOB`` | ``CLOB`` | ++-------------------------------------+ | | | +| ``URIType`` | | | | ++-------------------------------------+ | | | +| ``DBURIType`` | | | | ++-------------------------------------+ | | | +| ``XDBURIType`` | | | | ++-------------------------------------+ | | | +| ``HTTPURIType`` | | | | ++-------------------------------------+ | | | +| ``CREATE TYPE ... AS OBJECT (...)`` | | | | ++-------------------------------------+------------------+---------------------+----------------------+ +| ``JSON`` | unsupported | | | ++-------------------------------------+ | | | +| ``CREATE TYPE ... AS VARRAY ...`` | | | | ++-------------------------------------+ | | | +| ``CREATE TYPE ... AS TABLE OF ...`` | | | | ++-------------------------------------+------------------+---------------------+----------------------+ +``` + +### Special types + +```{eval-rst} ++--------------------+-------------------+---------------------+----------------------+ +| Oracle type (read) | Spark type | Oracle type (write) | Oracle type (create) | ++====================+===================+=====================+======================+ +| ``BOOLEAN`` | ``BooleanType()`` | ``BOOLEAN`` | ``NUMBER(P=1, S=0)`` | ++--------------------+-------------------+---------------------+----------------------+ +| ``ROWID`` | ``StringType()`` | ``CLOB`` | ``CLOB`` | ++--------------------+ | | | +| ``UROWID`` | | | | ++--------------------+ | | | +| ``UROWID(N)`` | | | | ++--------------------+-------------------+---------------------+----------------------+ +| ``ANYTYPE`` | unsupported | | | ++--------------------+ | | | +| ``ANYDATA`` | | | | ++--------------------+ | | | +| ``ANYDATASET`` | | | | ++--------------------+-------------------+---------------------+----------------------+ +``` + +## Explicit type cast + +### `DBReader` + +It is possible to explicitly cast column of unsupported type using `DBReader(columns=...)` syntax. + +For example, you can use `CAST(column AS CLOB)` to convert data to string representation on Oracle side, and so it will be read as Spark's `StringType()`. + +It is also possible to use [JSON_ARRAY](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/JSON_ARRAY.html) +or [JSON_OBJECT](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/JSON_OBJECT.html) Oracle functions +to convert column of any type to string representation. Then this JSON string can then be effectively parsed using the {obj}`JSON.parse_column ` method. + +```python +from onetl.file.format import JSON +from pyspark.sql.types import IntegerType, StructType, StructField + +from onetl.connection import Oracle +from onetl.db import DBReader + +oracle = Oracle(...) + +DBReader( + connection=oracle, + columns=[ + "id", + "supported_column", + "CAST(unsupported_column AS VARCHAR2(4000)) unsupported_column_str", + # or + "JSON_ARRAY(array_column) array_column_json", + ], +) +df = reader.run() + +json_scheme = StructType([StructField("key", IntegerType())]) + +df = df.select( + df.id, + df.supported_column, + df.unsupported_column_str.cast("integer").alias("parsed_integer"), + JSON().parse_column("array_column_json", json_scheme).alias("array_column"), +) +``` + +### `DBWriter` + +It is always possible to convert data on Spark side to string, and then write it to text column in Oracle table. + +To serialize and write JSON data to a `text` or `json` column in an Oracle table use the {obj}`JSON.serialize_column ` method. + +```python +from onetl.connection import Oracle +from onetl.db import DBWriter +from onetl.file.format import JSON + +oracle = Oracle(...) + +oracle.execute( + """ + CREATE TABLE schema.target_table ( + id INTEGER, + supported_column TIMESTAMP, + array_column_json VARCHAR2(4000) -- any string type, actually + ) + """, +) + +write_df = df.select( + df.id, + df.supported_column, + JSON().serialize_column(df.unsupported_column).alias("array_column_json"), +) + +writer = DBWriter( + connection=oracle, + target="schema.target_table", +) +writer.run(write_df) +``` + +Then you can parse this column on Oracle side - for example, by creating a view: + +```sql +SELECT + id, + supported_column, + JSON_VALUE(array_column_json, '$[0]' RETURNING NUMBER) AS array_item_0 +FROM + schema.target_table +``` + +Or by using [VIRTUAL column](https://oracle-base.com/articles/11g/virtual-columns-11gr1): + +```sql +CREATE TABLE schema.target_table ( + id INTEGER, + supported_column TIMESTAMP, + array_column_json VARCHAR2(4000), -- any string type, actually + array_item_0 GENERATED ALWAYS AS (JSON_VALUE(array_column_json, '$[0]' RETURNING NUMBER)) VIRTUAL +) +``` + +But data will be parsed on each table read in any case, as Oracle does no support `GENERATED ALWAYS AS (...) STORED` columns. diff --git a/mddocs/docs/ru/connection/db_connection/oracle/write.md b/mddocs/docs/ru/connection/db_connection/oracle/write.md new file mode 100644 index 000000000..09e021c79 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/oracle/write.md @@ -0,0 +1,56 @@ +(oracle-write)= + +# Writing to Oracle using `DBWriter` + +For writing data to Oracle, use {obj}`DBWriter `. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`oracle-types` +``` + +```{eval-rst} +.. warning:: + + It is always recommended to create table explicitly using :ref:`Oracle.execute ` + instead of relying on Spark's table DDL generation. + + This is because Spark's DDL generator can create columns with different precision and types than it is expected, + causing precision loss or other issues. +``` + +## Examples + +```python +from onetl.connection import Oracle +from onetl.db import DBWriter + +oracle = Oracle(...) + +df = ... # data is here + +writer = DBWriter( + connection=oracle, + target="schema.table", + options=Oracle.WriteOptions(if_exists="append"), +) + +writer.run(df) +``` + +## Options + +Method above accepts {obj}`OracleWriteOptions ` + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.oracle.options +``` + +```{eval-rst} +.. autopydantic_model:: OracleWriteOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/postgres/connection.md b/mddocs/docs/ru/connection/db_connection/postgres/connection.md new file mode 100644 index 000000000..80c59021c --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/postgres/connection.md @@ -0,0 +1,12 @@ +(postgres-connection)= + +# Postgres connection + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.postgres.connection +``` + +```{eval-rst} +.. autoclass:: Postgres + :members: get_packages, check +``` diff --git a/mddocs/docs/ru/connection/db_connection/postgres/execute.md b/mddocs/docs/ru/connection/db_connection/postgres/execute.md new file mode 100644 index 000000000..6964b43e9 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/postgres/execute.md @@ -0,0 +1,113 @@ +(postgres-execute)= + +# Executing statements in Postgres + +```{eval-rst} +.. warning:: + + Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + + Do **NOT** use them to read large amounts of data. Use :ref:`DBReader ` or :ref:`Postgres.sql ` instead. +``` + +## How to + +There are 2 ways to execute some statement in Postgres + +### Use `Postgres.fetch` + +Use this method to execute some `SELECT` query which returns **small number or rows**, like reading +Postgres config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts {obj}`Postgres.FetchOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`postgres-types`. +``` + +#### Syntax support + +This method supports **any** query syntax supported by Postgres, like: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Postgres + +postgres = Postgres(...) + +df = postgres.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=Postgres.FetchOptions(queryTimeout=10), +) +postgres.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `Postgres.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts {obj}`Postgres.ExecuteOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Postgres, like: + +- ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +- ✅︎ `ALTER ...` +- ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +- ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +- ✅︎ `CALL procedure(arg1, arg2) ...` +- ✅︎ `SELECT func(arg1, arg2)` or `{call func(arg1, arg2)}` - special syntax for calling functions +- ✅︎ other statements not mentioned here +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Postgres + +postgres = Postgres(...) + +postgres.execute("DROP TABLE schema.table") +postgres.execute( + """ + CREATE TABLE schema.table ( + id bigint GENERATED ALWAYS AS IDENTITY, + key text, + value real + ) + """, + options=Postgres.ExecuteOptions(queryTimeout=10), +) +``` + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.postgres.options +``` + +```{eval-rst} +.. autopydantic_model:: PostgresFetchOptions + :inherited-members: GenericOptions + :member-order: bysource + +``` + +```{eval-rst} +.. autopydantic_model:: PostgresExecuteOptions + :inherited-members: GenericOptions + :member-order: bysource +``` diff --git a/mddocs/docs/ru/connection/db_connection/postgres/index.md b/mddocs/docs/ru/connection/db_connection/postgres/index.md new file mode 100644 index 000000000..56442bfb0 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/postgres/index.md @@ -0,0 +1,28 @@ +(postgres)= + +# Postgres + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +connection +``` + +```{toctree} +:caption: Operations +:maxdepth: 1 + +read +sql +write +execute +``` + +```{toctree} +:caption: Troubleshooting +:maxdepth: 1 + +types +``` diff --git a/mddocs/docs/ru/connection/db_connection/postgres/prerequisites.md b/mddocs/docs/ru/connection/db_connection/postgres/prerequisites.md new file mode 100644 index 000000000..d2221552d --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/postgres/prerequisites.md @@ -0,0 +1,71 @@ +(postgres-prerequisites)= + +# Prerequisites + +## Version Compatibility + +- PostgreSQL server versions: + : - Officially declared: 8.2 - 17 + - Actually tested: 9.4.26, 17.3 +- Spark versions: 2.3.x - 3.5.x +- Java versions: 8 - 20 + +See [official documentation](https://jdbc.postgresql.org/). + +## Installing PySpark + +To use Postgres connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Connecting to Postgres + +### Allowing connection to Postgres instance + +Ask your Postgres administrator to allow your user (and probably IP) to connect to instance, +e.g. by updating `pg_hba.conf` file. + +See [official documentation](https://www.postgresql.org/docs/current/auth-pg-hba-conf.html). + +### Connection port + +Connection is usually performed to port 5432. Port may differ for different Postgres instances. +Please ask your Postgres administrator to provide required information. + +### Connection host + +It is possible to connect to Postgres by using either DNS name of host or it's IP address. + +If you're using Postgres cluster, it is currently possible to connect only to **one specific node**. +Connecting to multiple nodes to perform load balancing, as well as automatic failover to new master/replica are not supported. + +### Required grants + +Ask your Postgres cluster administrator to set following grants for a user, +used for creating a connection: + +```{eval-rst} +.. tabs:: + + .. code-tab:: sql Read + Write + + -- allow creating tables in specific schema + GRANT USAGE, CREATE ON SCHEMA myschema TO username; + + -- allow read & write access to specific table + GRANT SELECT, INSERT ON myschema.mytable TO username; + + -- only if if_exists="replace_entire_table" is used: + GRANT TRUNCATE ON myschema.mytable TO username; + + .. code-tab:: sql Read only + + -- allow creating tables in specific schema + GRANT USAGE ON SCHEMA myschema TO username; + + -- allow read access to specific table + GRANT SELECT ON myschema.mytable TO username; +``` + +More details can be found in [official documentation](https://www.postgresql.org/docs/current/sql-grant.html). diff --git a/mddocs/docs/ru/connection/db_connection/postgres/read.md b/mddocs/docs/ru/connection/db_connection/postgres/read.md new file mode 100644 index 000000000..71db1908c --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/postgres/read.md @@ -0,0 +1,91 @@ +(postgres-read)= + +# Reading from Postgres using `DBReader` + +{obj}`DBReader ` supports {ref}`strategy` for incremental data reading, +but does not support custom queries, like `JOIN`. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`postgres-types` +``` + +## Supported DBReader features + +- ✅︎ `columns` +- ✅︎ `where` +- ✅︎ `hwm`, supported strategies: +- - ✅︎ {ref}`snapshot-strategy` +- - ✅︎ {ref}`incremental-strategy` +- - ✅︎ {ref}`snapshot-batch-strategy` +- - ✅︎ {ref}`incremental-batch-strategy` +- ❌ `hint` (is not supported by Postgres) +- ❌ `df_schema` +- ✅︎ `options` (see {obj}`Postgres.ReadOptions `) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Postgres +from onetl.db import DBReader + +postgres = Postgres(...) + +reader = DBReader( + connection=postgres, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + options=Postgres.ReadOptions(partitionColumn="id", numPartitions=10), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Postgres +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +postgres = Postgres(...) + +reader = DBReader( + connection=postgres, + source="schema.table", + columns=["id", "key", "CAST(value AS text) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="postgres_hwm", expression="updated_dt"), + options=Postgres.ReadOptions(partitionColumn="id", numPartitions=10), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Postgres to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Postgres to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.postgres.options +``` + +```{eval-rst} +.. autopydantic_model:: PostgresReadOptions + :inherited-members: GenericOptions + :member-order: bysource +``` diff --git a/mddocs/docs/ru/connection/db_connection/postgres/sql.md b/mddocs/docs/ru/connection/db_connection/postgres/sql.md new file mode 100644 index 000000000..adceab0dc --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/postgres/sql.md @@ -0,0 +1,80 @@ +(postgres-sql)= + +# Reading from Postgres using `Postgres.sql` + +`Postgres.sql` allows passing custom SQL query, but does not support incremental strategies. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`postgres-types` +``` + +```{eval-rst} +.. warning:: + + Statement is executed in **read-write** connection, so if you're calling some functions/procedures with DDL/DML statements inside, + they can change data in your database. +``` + +## Syntax support + +Only queries with the following syntax are supported: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import Postgres + +postgres = Postgres(...) +df = postgres.sql( + """ + SELECT + id, + key, + CAST(value AS text) value, + updated_at + FROM + some.mytable + WHERE + key = 'something' + """, + options=Postgres.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from Postgres to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from Postgres to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.postgres.options +``` + +```{eval-rst} +.. autopydantic_model:: PostgresSQLOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/postgres/types.md b/mddocs/docs/ru/connection/db_connection/postgres/types.md new file mode 100644 index 000000000..3b3540383 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/postgres/types.md @@ -0,0 +1,490 @@ +(postgres-types)= + +# Postgres \<-> Spark type mapping + +```{eval-rst} +.. note:: + + The results below are valid for Spark 3.5.5, and may differ on other Spark versions. +``` + +## Type detection & casting + +Spark's DataFrames always have a `schema` which is a list of columns with corresponding Spark types. All operations on a column are performed using column type. + +### Reading from Postgres + +This is how Postgres connector performs this: + +- For each column in query result (`SELECT column1, column2, ... FROM table ...`) get column name and Postgres type. +- Find corresponding `Postgres type (read)` → `Spark type` combination (see below) for each DataFrame column [^footnote-1]. If no combination is found, raise exception. +- Create DataFrame from query with specific column names and Spark types. + +[^footnote-1]: All Postgres types that doesn't have corresponding Java type are converted to `String`. + +### Writing to some existing Postgres table + +This is how Postgres connector performs this: + +- Get names of columns in DataFrame. [^footnote-1] +- Perform `SELECT * FROM table LIMIT 0` query. +- Take only columns present in DataFrame (by name, case insensitive) [^footnote-2]. For each found column get Postgres type. +- Find corresponding `Spark type` → `Postgres type (write)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- If `Postgres type (write)` match `Postgres type (read)`, no additional casts will be performed, DataFrame column will be written to Postgres as is. +- If `Postgres type (write)` does not match `Postgres type (read)`, DataFrame column will be casted to target column type **on Postgres side**. + For example, you can write column with text data to `int` column, if column contains valid integer values within supported value range and precision [^footnote-3]. + +[^footnote-2]: This allows to write data to tables with `DEFAULT` and `GENERATED` columns - if DataFrame has no such column, + it will be populated by Postgres. + +[^footnote-3]: This is true only if either DataFrame column is a `StringType()`, or target column is `text` type. + + But other types cannot be silently converted, like `bytea -> bit(N)`. This requires explicit casting, see [Manual conversion to string]. + +### Create new table using Spark + +```{eval-rst} +.. warning:: + + ABSOLUTELY NOT RECOMMENDED! +``` + +This is how Postgres connector performs this: + +- Find corresponding `Spark type` → `Postgres type (create)` combination (see below) for each DataFrame column. If no combination is found, raise exception. +- Generate DDL for creating table in Postgres, like `CREATE TABLE (col1 ...)`, and run it. +- Write DataFrame to created table as is. + +But Postgres connector support only limited number of types and almost no custom clauses (like `PARTITION BY`, `INDEX`, etc). +So instead of relying on Spark to create tables: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + writer = DBWriter( + connection=postgres, + target="public.table", + options=Postgres.WriteOptions( + if_exists="append", + createTableOptions="PARTITION BY RANGE (id)", + ), + ) + writer.run(df) +``` + +Always prefer creating table with desired DDL **BEFORE WRITING DATA**: + +```{eval-rst} +.. dropdown:: See example + + .. code:: python + + postgres.execute( + """ + CREATE TABLE public.table ( + id bigint, + business_dt timestamp(6), + value json + ) + PARTITION BY RANGE (Id) + """, + ) + + writer = DBWriter( + connection=postgres, + target="public.table", + options=Postgres.WriteOptions(if_exists="append"), + ) + writer.run(df) +``` + +See Postgres [CREATE TABLE](https://www.postgresql.org/docs/current/sql-createtable.html) documentation. + +## Supported types + +### References + +See [List of Postgres types](https://www.postgresql.org/docs/current/datatype.html). + +Here you can find source code with type conversions: + +- [Postgres \<-> JDBC](https://github.com/pgjdbc/pgjdbc/blob/REL42.6.0/pgjdbc/src/main/java/org/postgresql/jdbc/TypeInfoCache.java#L78-L112) +- [JDBC -> Spark](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/PostgresDialect.scala#L52-L108) +- [Spark -> JDBC](https://github.com/apache/spark/blob/v3.5.5/sql/core/src/main/scala/org/apache/spark/sql/jdbc/PostgresDialect.scala#L118-L132) + +### Numeric types + +```{eval-rst} ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | ++==================================+===================================+===============================+=========================+ +| ``decimal`` | ``DecimalType(P=38, S=18)`` | ``decimal(P=38, S=18)`` | ``decimal`` (unbounded) | ++----------------------------------+-----------------------------------+-------------------------------+ | +| ``decimal(P=0..38)`` | ``DecimalType(P=0..38, S=0)`` | ``decimal(P=0..38, S=0)`` | | ++----------------------------------+-----------------------------------+-------------------------------+ | +| ``decimal(P=0..38, S=0..38)`` | ``DecimalType(P=0..38, S=0..38)`` | ``decimal(P=0..38, S=0..38)`` | | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``decimal(P=39.., S=0..)`` | unsupported [4]_ | | | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``decimal(P=.., S=..-1)`` | unsupported [5]_ | | | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``real`` | ``FloatType()`` | ``real`` | ``real`` | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``double precision`` | ``DoubleType()`` | ``double precision`` | ``double precision`` | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``smallint`` | ``ShortType()`` | ``smallint`` | ``smallint`` | ++----------------------------------+-----------------------------------+ | | +| ``-`` | ``ByteType()`` | | | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``integer`` | ``IntegerType()`` | ``integer`` | ``integer`` | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``bigint`` | ``LongType()`` | ``bigint`` | ``bigint`` | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +| ``money`` | ``StringType()`` [1]_ | ``text`` | ``text`` | ++----------------------------------+ | | | +| ``int4range`` | | | | ++----------------------------------+ | | | +| ``int8range`` | | | | ++----------------------------------+ | | | +| ``numrange`` | | | | ++----------------------------------+ | | | +| ``int2vector`` | | | | ++----------------------------------+-----------------------------------+-------------------------------+-------------------------+ +``` + +[^footnote-4]: Postgres support decimal types with unlimited precision. + + But Spark's `DecimalType(P, S)` supports maximum `P=38` (128 bit). It is impossible to read, write or operate with values of larger precision, + this leads to an exception. + +[^footnote-5]: Postgres support decimal types with negative scale, like `decimal(38, -10)`. Spark doesn't. + +### Temporal types + +```{eval-rst} ++------------------------------------+------------------------------+-----------------------+-------------------------+ +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | ++====================================+==============================+=======================+=========================+ +| ``date`` | ``DateType()`` | ``date`` | ``date`` | ++------------------------------------+------------------------------+-----------------------+-------------------------+ +| ``time`` | ``TimestampType()``, | ``timestamp(6)`` | ``timestamp(6)`` | ++------------------------------------+ with time format quirks [6]_ | | | +| ``time(0..6)`` | | | | ++------------------------------------+ | | | +| ``time with time zone`` | | | | ++------------------------------------+ | | | +| ``time(0..6) with time zone`` | | | | ++------------------------------------+------------------------------+-----------------------+-------------------------+ +| ``timestamp`` | ``TimestampType()`` | ``timestamp(6)`` | ``timestamp(6)`` | ++------------------------------------+ | | | +| ``timestamp(0..6)`` | | | | ++------------------------------------+ | | | +| ``timestamp with time zone`` | | | | ++------------------------------------+ | | | +| ``timestamp(0..6) with time zone`` | | | | ++------------------------------------+------------------------------+-----------------------+-------------------------+ +| ``-`` | ``TimestampNTZType()`` | ``timestamp(6)`` | ``timestamp(6)`` | ++------------------------------------+------------------------------+-----------------------+-------------------------+ +| ``interval`` of any precision | ``StringType()`` [1]_ | ``text`` | ``text`` | ++------------------------------------+------------------------------+-----------------------+-------------------------+ +| ``-`` | ``DayTimeIntervalType()`` | unsupported | unsupported | ++------------------------------------+------------------------------+-----------------------+-------------------------+ +| ``-`` | ``YearMonthIntervalType()`` | unsupported | unsupported | ++------------------------------------+------------------------------+-----------------------+-------------------------+ +| ``daterange`` | ``StringType()`` [1]_ | ``text`` | ``text`` | ++------------------------------------+ | | | +| ``tsrange`` | | | | ++------------------------------------+ | | | +| ``tstzrange`` | | | | ++------------------------------------+------------------------------+-----------------------+-------------------------+ +``` + +```{eval-rst} +.. warning:: + + Note that types in Postgres and Spark have different value ranges: + + +---------------+---------------------------------+----------------------------------+---------------------+--------------------------------+--------------------------------+ + | Postgres type | Min value | Max value | Spark type | Min value | Max value | + +===============+=================================+==================================+=====================+================================+================================+ + | ``date`` | ``-4713-01-01`` | ``5874897-01-01`` | ``DateType()`` | ``0001-01-01`` | ``9999-12-31`` | + +---------------+---------------------------------+----------------------------------+---------------------+--------------------------------+--------------------------------+ + | ``timestamp`` | ``-4713-01-01 00:00:00.000000`` | ``294276-12-31 23:59:59.999999`` | ``TimestampType()`` | ``0001-01-01 00:00:00.000000`` | ``9999-12-31 23:59:59.999999`` | + +---------------+---------------------------------+----------------------------------+ | | | + | ``time`` | ``00:00:00.000000`` | ``24:00:00.000000`` | | | | + +---------------+---------------------------------+----------------------------------+---------------------+--------------------------------+--------------------------------+ + + So not all of values can be read from Postgres to Spark. + + References: + * `Postgres date/time types documentation `_ + * `Spark DateType documentation `_ + * `Spark TimestampType documentation `_ +``` + +[^footnote-6]: `time` type is the same as `timestamp` with date `1970-01-01`. So instead of reading data from Postgres like `23:59:59` + it is actually read `1970-01-01 23:59:59`, and vice versa. + +### String types + +```{eval-rst} ++-----------------------------+-----------------------+-----------------------+-------------------------+ +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | ++=============================+=======================+=======================+=========================+ +| ``character`` | ``StringType()`` | ``text`` | ``text`` | ++-----------------------------+ | | | +| ``character(N)`` | | | | ++-----------------------------+ | | | +| ``character varying`` | | | | ++-----------------------------+ | | | +| ``character varying(N)`` | | | | ++-----------------------------+ | | | +| ``text`` | | | | ++-----------------------------+ | | | +| ``json`` | | | | ++-----------------------------+ | | | +| ``jsonb`` | | | | ++-----------------------------+ | | | +| ``xml`` | | | | ++-----------------------------+-----------------------+ | | +| ``CREATE TYPE ... AS ENUM`` | ``StringType()`` [1]_ | | | ++-----------------------------+ | | | +| ``tsvector`` | | | | ++-----------------------------+ | | | +| ``tsquery`` | | | | ++-----------------------------+-----------------------+-----------------------+-------------------------+ +| ``-`` | ``CharType()`` | ``unsupported`` | ``unsupported`` | ++-----------------------------+-----------------------+-----------------------+-------------------------+ +| ``-`` | ``VarcharType()`` | ``unsupported`` | ``unsupported`` | ++-----------------------------+-----------------------+-----------------------+-------------------------+ +``` + +### Binary types + +```{eval-rst} ++--------------------------+-----------------------+-----------------------------+-------------------------+ +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | ++==========================+=======================+=============================+=========================+ +| ``boolean`` | ``BooleanType()`` | ``boolean`` | ``boolean`` | ++--------------------------+-----------------------+-----------------------------+-------------------------+ +| ``bit`` | ``BooleanType()`` | ``bool``, | ``bool`` | ++--------------------------+ | **cannot insert data** [3]_ | | +| ``bit(N=1)`` | | | | ++--------------------------+-----------------------+-----------------------------+-------------------------+ +| ``bit(N=2..)`` | ``ByteType()`` | ``bytea``, | ``bytea`` | +| | | **cannot insert data** [3]_ | | ++--------------------------+-----------------------+-----------------------------+-------------------------+ +| ``bit varying`` | ``StringType()`` [1]_ | ``text`` | ``text`` | ++--------------------------+ | | | +| ``bit varying(N)`` | | | | ++--------------------------+-----------------------+-----------------------------+-------------------------+ +| ``bytea`` | ``BinaryType()`` | ``bytea`` | ``bytea`` | ++--------------------------+-----------------------+-----------------------------+-------------------------+ +``` + +### Struct types + +```{eval-rst} ++--------------------------------+-----------------------+-----------------------+-------------------------+ +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | ++================================+=======================+=======================+=========================+ +| ``T[]`` | ``ArrayType(T)`` | ``T[]`` | ``T[]`` | ++--------------------------------+-----------------------+-----------------------+-------------------------+ +| ``T[][]`` | unsupported | | | ++--------------------------------+-----------------------+-----------------------+-------------------------+ +| ``CREATE TYPE sometype (...)`` | ``StringType()`` [1]_ | ``text`` | ``text`` | ++--------------------------------+-----------------------+-----------------------+-------------------------+ +| ``-`` | ``StructType()`` | unsupported | | ++--------------------------------+-----------------------+ | | +| ``-`` | ``MapType()`` | | | ++--------------------------------+-----------------------+-----------------------+-------------------------+ +``` + +### Network types + +```{eval-rst} ++----------------------+-----------------------+-----------------------+-------------------------+ +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | ++======================+=======================+=======================+=========================+ +| ``cidr`` | ``StringType()`` [1]_ | ``text`` | ``text`` | ++----------------------+ | | | +| ``inet`` | | | | ++----------------------+ | | | +| ``macaddr`` | | | | ++----------------------+ | | | +| ``macaddr8`` | | | | ++----------------------+-----------------------+-----------------------+-------------------------+ +``` + +### Geo types + +```{eval-rst} ++----------------------+-----------------------+-----------------------+-------------------------+ +| Postgres type (read) | Spark type | Postgres type (write) | Postgres type (create) | ++======================+=======================+=======================+=========================+ +| ``circle`` | ``StringType()`` [1]_ | ``text`` | ``text`` | ++----------------------+ | | | +| ``box`` | | | | ++----------------------+ | | | +| ``line`` | | | | ++----------------------+ | | | +| ``lseg`` | | | | ++----------------------+ | | | +| ``path`` | | | | ++----------------------+ | | | +| ``point`` | | | | ++----------------------+ | | | +| ``polygon`` | | | | ++----------------------+ | | | +| ``polygon`` | | | | ++----------------------+-----------------------+-----------------------+-------------------------+ +``` + +## Explicit type cast + +### `DBReader` + +It is possible to explicitly cast column of unsupported type using `DBReader(columns=...)` syntax. + +For example, you can use `CAST(column AS text)` to convert data to string representation on Postgres side, and so it will be read as Spark's `StringType()`. + +It is also possible to use [to_json](https://www.postgresql.org/docs/current/functions-json.html) Postgres function to convert column of any type to string representation, and then parse this column on Spark side you can use the {obj}`JSON.parse_column ` method: + +```python +from pyspark.sql.types import IntegerType + +from onetl.connection import Postgres +from onetl.db import DBReader +from onetl.file.format import JSON + +postgres = Postgres(...) + +DBReader( + connection=postgres, + columns=[ + "id", + "supported_column", + "CAST(unsupported_column AS text) unsupported_column_str", + # or + "to_json(unsupported_column) array_column_json", + ], +) +df = reader.run() + +json_schema = StructType( + [ + StructField("id", IntegerType(), nullable=True), + StructField("name", StringType(), nullable=True), + ..., + ] +) +df = df.select( + df.id, + df.supported_column, + # explicit cast + df.unsupported_column_str.cast("integer").alias("parsed_integer"), + JSON().parse_column("array_column_json", json_schema).alias("json_string"), +) +``` + +### `DBWriter` + +It is always possible to convert data on the Spark side to a string, and then write it to a text column in a Postgres table. + +#### Using JSON.serialize_column + +You can use the {obj}`JSON.serialize_column ` method for data serialization: + +```python +from onetl.file.format import JSON +from pyspark.sql.functions import col + +from onetl.connection import Postgres +from onetl.db import DBWriter + +postgres = Postgres(...) + +postgres.execute( + """ + CREATE TABLE schema.target_table ( + id int, + supported_column timestamp, + array_column_json jsonb -- any column type, actually + ) + """, +) + +write_df = df.select( + df.id, + df.supported_column, + JSON().serialize_column(df.unsupported_column).alias("array_column_json"), +) + +writer = DBWriter( + connection=postgres, + target="schema.target_table", +) +writer.run(write_df) +``` + +Then you can parse this column on the Postgres side (for example, by creating a view): + +```sql +SELECT + id, + supported_column, + array_column_json->'0' AS array_item_0 +FROM + schema.target_table +``` + +To avoid casting the value on every table read you can use [GENERATED ALWAYS STORED](https://www.postgresql.org/docs/current/ddl-generated-columns.html) column, but this requires 2x space (for original and parsed value). + +#### Manual conversion to string + +Postgres connector also supports conversion text value directly to target column type, if this value has a proper format. + +For example, you can write data like `[123, 345)` to `int8range` type because Postgres allows cast `'[123, 345)'::int8range'`: + +```python +from pyspark.sql.ftypes import StringType +from pyspark.sql.functions import udf + +from onetl.connection import Postgres +from onetl.db import DBReader + +postgres = Postgres(...) + +postgres.execute( + """ + CREATE TABLE schema.target_table ( + id int, + range_column int8range -- any column type, actually + ) + """, +) + + +@udf(returnType=StringType()) +def array_to_range(value: tuple): + """This UDF allows to convert tuple[start, end] to Postgres' range format""" + start, end = value + return f"[{start},{end})" + + +write_df = df.select( + df.id, + array_to_range(df.range_column).alias("range_column"), +) + +writer = DBWriter( + connection=postgres, + target="schema.target_table", +) +writer.run(write_df) +``` + +This can be tricky to implement and may lead to longer write process. +But this does not require extra space on Postgres side, and allows to avoid explicit value cast on every table read. diff --git a/mddocs/docs/ru/connection/db_connection/postgres/write.md b/mddocs/docs/ru/connection/db_connection/postgres/write.md new file mode 100644 index 000000000..8f01de26a --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/postgres/write.md @@ -0,0 +1,56 @@ +(postgres-write)= + +# Writing to Postgres using `DBWriter` + +For writing data to Postgres, use {obj}`DBWriter `. + +```{eval-rst} +.. warning:: + + Please take into account :ref:`postgres-types` +``` + +```{eval-rst} +.. warning:: + + It is always recommended to create table explicitly using :ref:`Postgres.execute ` + instead of relying on Spark's table DDL generation. + + This is because Spark's DDL generator can create columns with different precision and types than it is expected, + causing precision loss or other issues. +``` + +## Examples + +```python +from onetl.connection import Postgres +from onetl.db import DBWriter + +postgres = Postgres(...) + +df = ... # data is here + +writer = DBWriter( + connection=postgres, + target="schema.table", + options=Postgres.WriteOptions(if_exists="append"), +) + +writer.run(df) +``` + +## Options + +Method above accepts {obj}`Postgres.WriteOptions ` + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.postgres.options +``` + +```{eval-rst} +.. autopydantic_model:: PostgresWriteOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/teradata/connection.md b/mddocs/docs/ru/connection/db_connection/teradata/connection.md new file mode 100644 index 000000000..0804437cf --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/teradata/connection.md @@ -0,0 +1,12 @@ +(teradata-connection)= + +# Teradata connection + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.teradata.connection +``` + +```{eval-rst} +.. autoclass:: Teradata + :members: get_packages, check +``` diff --git a/mddocs/docs/ru/connection/db_connection/teradata/execute.md b/mddocs/docs/ru/connection/db_connection/teradata/execute.md new file mode 100644 index 000000000..de1b018fe --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/teradata/execute.md @@ -0,0 +1,110 @@ +(teradata-execute)= + +# Executing statements in Teradata + +```{eval-rst} +.. warning:: + + Methods below **read all the rows** returned from DB **to Spark driver memory**, and then convert them to DataFrame. + + Do **NOT** use them to read large amounts of data. Use :ref:`DBReader ` or :ref:`Teradata.sql ` instead. +``` + +## How to + +There are 2 ways to execute some statement in Teradata + +### Use `Teradata.fetch` + +Use this method to execute some `SELECT` query which returns **small number or rows**, like reading +Teradata config, or reading data from some reference table. Method returns Spark DataFrame. + +Method accepts {obj}`Teradata.FetchOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Teradata, like: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ✅︎ `SHOW ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Teradata + +teradata = Teradata(...) + +df = teradata.fetch( + "SELECT value FROM some.reference_table WHERE key = 'some_constant'", + options=Teradata.FetchOptions(queryTimeout=10), +) +teradata.close() +value = df.collect()[0][0] # get value from first row and first column +``` + +### Use `Teradata.execute` + +Use this method to execute DDL and DML operations. Each method call runs operation in a separated transaction, and then commits it. + +Method accepts {obj}`Teradata.ExecuteOptions `. + +Connection opened using this method should be then closed with `connection.close()` or `with connection:`. + +#### Syntax support + +This method supports **any** query syntax supported by Teradata, like: + +- ✅︎ `CREATE TABLE ...`, `CREATE VIEW ...`, and so on +- ✅︎ `ALTER ...` +- ✅︎ `INSERT INTO ... SELECT ...`, `UPDATE ...`, `DELETE ...`, and so on +- ✅︎ `DROP TABLE ...`, `DROP VIEW ...`, `TRUNCATE TABLE`, and so on +- ✅︎ `CALL procedure(arg1, arg2) ...` or `{call procedure(arg1, arg2)}` - special syntax for calling procedure +- ✅︎ `EXECUTE macro(arg1, arg2)` +- ✅︎ `EXECUTE FUNCTION ...` +- ✅︎ other statements not mentioned here +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +#### Examples + +```python +from onetl.connection import Teradata + +teradata = Teradata(...) + +teradata.execute("DROP TABLE database.table") +teradata.execute( + """ + CREATE MULTISET TABLE database.table AS ( + id BIGINT, + key VARCHAR, + value REAL + ) + NO PRIMARY INDEX + """, + options=Teradata.ExecuteOptions(queryTimeout=10), +) +``` + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.teradata.options +``` + +```{eval-rst} +.. autopydantic_model:: TeradataFetchOptions + :inherited-members: GenericOptions + :member-order: bysource + +``` + +```{eval-rst} +.. autopydantic_model:: TeradataExecuteOptions + :inherited-members: GenericOptions + :member-order: bysource +``` diff --git a/mddocs/docs/ru/connection/db_connection/teradata/index.md b/mddocs/docs/ru/connection/db_connection/teradata/index.md new file mode 100644 index 000000000..f86e25726 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/teradata/index.md @@ -0,0 +1,21 @@ +(teradata)= + +# Teradata + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +connection +``` + +```{toctree} +:caption: Operations +:maxdepth: 1 + +read +sql +write +execute +``` diff --git a/mddocs/docs/ru/connection/db_connection/teradata/prerequisites.md b/mddocs/docs/ru/connection/db_connection/teradata/prerequisites.md new file mode 100644 index 000000000..d180ffdeb --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/teradata/prerequisites.md @@ -0,0 +1,57 @@ +(teradata-prerequisites)= + +# Prerequisites + +## Version Compatibility + +- Teradata server versions: + : - Officially declared: 16.10 - 20.0 + - Actually tested: 16.10 +- Spark versions: 2.3.x - 3.5.x +- Java versions: 8 - 20 + +See [official documentation](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/platformMatrix.html). + +## Installing PySpark + +To use Teradata connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Connecting to Teradata + +### Connection host + +It is possible to connect to Teradata by using either DNS name Parsing Engine (PE) host, or it's IP address. + +### Connection port + +Connection is usually performed to port `1025`. Port may differ for different Teradata instances. +Please ask your Teradata administrator to provide required information. + +### Required grants + +Ask your Teradata cluster administrator to set following grants for a user, +used for creating a connection: + +```{eval-rst} +.. tabs:: + + .. code-tab:: sql Read + Write + + -- allow creating tables in the target schema + GRANT CREATE TABLE ON database TO username; + + -- allow read & write access to specific table + GRANT SELECT, INSERT ON database.mytable TO username; + + .. code-tab:: sql Read only + + -- allow read access to specific table + GRANT SELECT ON database.mytable TO username; +``` + +See: +: - [Teradata access rights](https://www.dwhpro.com/teradata-access-rights/) + - [GRANT documentation](https://teradata.github.io/presto/docs/0.167-t/sql/grant.html) diff --git a/mddocs/docs/ru/connection/db_connection/teradata/read.md b/mddocs/docs/ru/connection/db_connection/teradata/read.md new file mode 100644 index 000000000..c57b09119 --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/teradata/read.md @@ -0,0 +1,117 @@ +(teradata-read)= + +# Reading from Teradata using `DBReader` + +{obj}`DBReader ` supports {ref}`strategy` for incremental data reading, +but does not support custom queries, like `JOIN`. + +## Supported DBReader features + +- ✅︎ `columns` +- ✅︎ `where` +- ✅︎ `hwm`, supported strategies: +- - ✅︎ {ref}`snapshot-strategy` +- - ✅︎ {ref}`incremental-strategy` +- - ✅︎ {ref}`snapshot-batch-strategy` +- - ✅︎ {ref}`incremental-batch-strategy` +- ❌ `hint` (is not supported by Teradata) +- ❌ `df_schema` +- ✅︎ `options` (see {obj}`Teradata.ReadOptions `) + +## Examples + +Snapshot strategy: + +```python +from onetl.connection import Teradata +from onetl.db import DBReader + +teradata = Teradata(...) + +reader = DBReader( + connection=teradata, + source="database.table", + columns=["id", "key", "CAST(value AS VARCHAR) value", "updated_dt"], + where="key = 'something'", + options=Teradata.ReadOptions( + partitioning_mode="hash", + partitionColumn="id", + numPartitions=10, + ), +) +df = reader.run() +``` + +Incremental strategy: + +```python +from onetl.connection import Teradata +from onetl.db import DBReader +from onetl.strategy import IncrementalStrategy + +teradata = Teradata(...) + +reader = DBReader( + connection=teradata, + source="database.table", + columns=["id", "key", "CAST(value AS VARCHAR) value", "updated_dt"], + where="key = 'something'", + hwm=DBReader.AutoDetectHWM(name="teradata_hwm", expression="updated_dt"), + options=Teradata.ReadOptions( + partitioning_mode="hash", + partitionColumn="id", + numPartitions=10, + ), +) + +with IncrementalStrategy(): + df = reader.run() +``` + +## Recommendations + +### Select only required columns + +Instead of passing `"*"` in `DBReader(columns=[...])` prefer passing exact column names. This reduces the amount of data passed from Teradata to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `DBReader(where="column = 'value'")` clause. +This both reduces the amount of data send from Teradata to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +### Read data in parallel + +`DBReader` can read data in multiple parallel connections by passing `Teradata.ReadOptions(numPartitions=..., partitionColumn=...)`. + +In the example above, Spark opens 10 parallel connections, and data is evenly distributed between all these connections using expression +`HASHAMP(HASHBUCKET(HASHROW({partition_column}))) MOD {num_partitions}`. +This allows sending each Spark worker only some piece of data, reducing resource consumption. +`partition_column` here can be table column of any type. + +It is also possible to use `partitioning_mode="mod"` or `partitioning_mode="range"`, but in this case +`partition_column` have to be an integer, should not contain `NULL`, and values to be uniformly distributed. +It is also less performant than `partitioning_mode="hash"` due to Teradata `HASHAMP` implementation. + +### Do **NOT** use `TYPE=FASTEXPORT` + +Teradata supports several [different connection types](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/jdbcug_chapter_2.html#BABFGFAF): +: - `TYPE=DEFAULT` - perform plain `SELECT` queries + - `TYPE=FASTEXPORT` - uses special FastExport protocol for select queries + +But `TYPE=FASTEXPORT` uses exclusive lock on the source table, so it is impossible to use multiple Spark workers parallel data read. +This leads to sending all the data to just one Spark worker, which is slow and takes a lot of RAM. + +Prefer using `partitioning_mode="hash"` from example above. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.teradata.options +``` + +```{eval-rst} +.. autopydantic_model:: TeradataReadOptions + :inherited-members: GenericOptions + :member-order: bysource +``` diff --git a/mddocs/docs/ru/connection/db_connection/teradata/sql.md b/mddocs/docs/ru/connection/db_connection/teradata/sql.md new file mode 100644 index 000000000..ad0919dce --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/teradata/sql.md @@ -0,0 +1,76 @@ +(teradata-sql)= + +# Reading from Teradata using `Teradata.sql` + +`Teradata.sql` allows passing custom SQL query, but does not support incremental strategies. + +```{eval-rst} +.. warning:: + + Statement is executed in **read-write** connection, so if you're calling some functions/procedures with DDL/DML statements inside, + they can change data in your database. +``` + +## Syntax support + +Only queries with the following syntax are supported: + +- ✅︎ `SELECT ... FROM ...` +- ✅︎ `WITH alias AS (...) SELECT ...` +- ❌ `SHOW ...` +- ❌ `SET ...; SELECT ...;` - multiple statements not supported + +## Examples + +```python +from onetl.connection import Teradata + +teradata = Teradata(...) +df = teradata.sql( + """ + SELECT + id, + key, + CAST(value AS VARCHAR) AS value, + updated_at, + HASHAMP(HASHBUCKET(HASHROW(id))) MOD 10 AS part_column + FROM + database.mytable + WHERE + key = 'something' + """, + options=Teradata.SQLOptions( + partitionColumn="id", + numPartitions=10, + lowerBound=0, + upperBound=1000, + ), +) +``` + +## Recommendations + +### Select only required columns + +Instead of passing `SELECT * FROM ...` prefer passing exact column names `SELECT col1, col2, ...`. +This reduces the amount of data passed from Teradata to Spark. + +### Pay attention to `where` value + +Instead of filtering data on Spark side using `df.filter(df.column == 'value')` pass proper `WHERE column = 'value'` clause. +This both reduces the amount of data send from Teradata to Spark, and may also improve performance of the query. +Especially if there are indexes or partitions for columns used in `where` clause. + +## Options + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.teradata.options +``` + +```{eval-rst} +.. autopydantic_model:: TeradataSQLOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/db_connection/teradata/write.md b/mddocs/docs/ru/connection/db_connection/teradata/write.md new file mode 100644 index 000000000..6d1f6644f --- /dev/null +++ b/mddocs/docs/ru/connection/db_connection/teradata/write.md @@ -0,0 +1,120 @@ +(teradata-write)= + +# Writing to Teradata using `DBWriter` + +For writing data to Teradata, use {obj}`DBWriter `. + +```{eval-rst} +.. warning:: + + It is always recommended to create table explicitly using :ref:`Teradata.execute ` + instead of relying on Spark's table DDL generation. + + This is because Spark's DDL generator can create columns with different precision and types than it is expected, + causing precision loss or other issues. +``` + +## Examples + +```python +from onetl.connection import Teradata +from onetl.db import DBWriter + +teradata = Teradata( + ..., + extra={"TYPE": "FASTLOAD", "TMODE": "TERA"}, +) + +df = ... # data is here + +writer = DBWriter( + connection=teradata, + target="database.table", + options=Teradata.WriteOptions( + if_exists="append", + # avoid creating SET table, use MULTISET + createTableOptions="NO PRIMARY INDEX", + ), +) + +writer.run(df.repartition(1)) +``` + +## Recommendations + +### Number of connections + +Teradata is not MVCC based, so write operations take exclusive lock on the entire table. +So **it is impossible to write data to Teradata table in multiple parallel connections**, no exceptions. + +The only way to write to Teradata without making deadlocks is write dataframe with exactly 1 partition. + +It can be implemented using `df.repartition(1)`: + +```python +# do NOT use df.coalesce(1) as it can freeze +writer.run(df.repartition(1)) +``` + +This moves all the data to just one Spark worker, so it may consume a lot of RAM. It is usually require to increase `spark.executor.memory` to handle this. + +Another way is to write all dataframe partitions one-by-one: + +```python +from pyspark.sql.functions import spark_partition_id + +# get list of all partitions in the dataframe +partitions = sorted(df.select(spark_partition_id()).distinct().collect()) + +for partition in partitions: + # get only part of data within this exact partition + part_df = df.where(**partition.asDict()).coalesce(1) + + writer.run(part_df) +``` + +This require even data distribution for all partitions to avoid data skew and spikes of RAM consuming. + +### Choosing connection type + +Teradata supports several [different connection types](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/jdbcug_chapter_2.html#BABFGFAF): +: - `TYPE=DEFAULT` - perform plain `INSERT` queries + - `TYPE=FASTLOAD` - uses special FastLoad protocol for insert queries + +It is always recommended to use `TYPE=FASTLOAD` because: +: - It provides higher performance + - It properly handles inserting `NULL` values (`TYPE=DEFAULT` raises an exception) + +But it can be used only during write, not read. + +### Choosing transaction mode + +Teradata supports [2 different transaction modes](https://teradata-docs.s3.amazonaws.com/doc/connectivity/jdbc/reference/current/jdbcug_chapter_2.html#TMODESEC): +: - `TMODE=ANSI` + - `TMODE=TERA` + +Choosing one of the modes can alter connector behavior. For example: +: - Inserting data which exceeds table column length, like insert `CHAR(25)` to column with type `CHAR(24)`: + - - `TMODE=ANSI` - raises exception + - - `TMODE=TERA` - truncates input string to 24 symbols + - Creating table using Spark: + - - `TMODE=ANSI` - creates `MULTISET` table + - - `TMODE=TERA` - creates `SET` table with `PRIMARY KEY` is a first column in dataframe. + This can lead to slower insert time, because each row will be checked against a unique index. + Fortunately, this can be disabled by passing custom `createTableOptions`. + +## Options + +Method above accepts {obj}`Teradata.WriteOptions ` + +```{eval-rst} +.. currentmodule:: onetl.connection.db_connection.teradata.options +``` + +```{eval-rst} +.. autopydantic_model:: TeradataWriteOptions + :inherited-members: GenericOptions + :member-order: bysource + :model-show-field-summary: false + :field-show-constraints: false +``` diff --git a/mddocs/docs/ru/connection/file_connection/ftp.md b/mddocs/docs/ru/connection/file_connection/ftp.md new file mode 100644 index 000000000..8877f479e --- /dev/null +++ b/mddocs/docs/ru/connection/file_connection/ftp.md @@ -0,0 +1,12 @@ +(ftp)= + +# FTP connection + +```{eval-rst} +.. currentmodule:: onetl.connection.file_connection.ftp +``` + +```{eval-rst} +.. autoclass:: FTP + :members: __init__, check, path_exists, is_file, is_dir, get_stat, resolve_dir, resolve_file, create_dir, remove_file, remove_dir, rename_dir, rename_file, list_dir, walk, download_file, upload_file +``` diff --git a/mddocs/docs/ru/connection/file_connection/ftps.md b/mddocs/docs/ru/connection/file_connection/ftps.md new file mode 100644 index 000000000..668be45b5 --- /dev/null +++ b/mddocs/docs/ru/connection/file_connection/ftps.md @@ -0,0 +1,12 @@ +(ftps)= + +# FTPS connection + +```{eval-rst} +.. currentmodule:: onetl.connection.file_connection.ftps +``` + +```{eval-rst} +.. autoclass:: FTPS + :members: __init__, check, path_exists, is_file, is_dir, get_stat, resolve_dir, resolve_file, create_dir, remove_file, remove_dir, rename_dir, rename_file, list_dir, walk, download_file, upload_file +``` diff --git a/mddocs/docs/ru/connection/file_connection/hdfs/connection.md b/mddocs/docs/ru/connection/file_connection/hdfs/connection.md new file mode 100644 index 000000000..b1cffa679 --- /dev/null +++ b/mddocs/docs/ru/connection/file_connection/hdfs/connection.md @@ -0,0 +1,12 @@ +(hdfs-connection)= + +# HDFS connection + +```{eval-rst} +.. currentmodule:: onetl.connection.file_connection.hdfs.connection +``` + +```{eval-rst} +.. autoclass:: HDFS + :members: get_current, check, path_exists, is_file, is_dir, get_stat, resolve_dir, resolve_file, create_dir, remove_file, remove_dir, rename_dir, rename_file, list_dir, walk, download_file, upload_file +``` diff --git a/mddocs/docs/ru/connection/file_connection/hdfs/index.md b/mddocs/docs/ru/connection/file_connection/hdfs/index.md new file mode 100644 index 000000000..a4c9fa148 --- /dev/null +++ b/mddocs/docs/ru/connection/file_connection/hdfs/index.md @@ -0,0 +1,17 @@ +(hdfs)= + +# HDFS + +```{toctree} +:caption: Connection +:maxdepth: 1 + +connection +``` + +```{toctree} +:caption: For developers +:maxdepth: 1 + +slots +``` diff --git a/mddocs/docs/ru/connection/file_connection/hdfs/slots.md b/mddocs/docs/ru/connection/file_connection/hdfs/slots.md new file mode 100644 index 000000000..88f7e2943 --- /dev/null +++ b/mddocs/docs/ru/connection/file_connection/hdfs/slots.md @@ -0,0 +1,13 @@ +(hdfs-slots)= + +# HDFS Slots + +```{eval-rst} +.. currentmodule:: onetl.connection.file_connection.hdfs.slots +``` + +```{eval-rst} +.. autoclass:: HDFSSlots + :members: normalize_cluster_name, normalize_namenode_host, get_known_clusters, get_cluster_namenodes, get_current_cluster, get_webhdfs_port, is_namenode_active + :member-order: bysource +``` diff --git a/mddocs/docs/ru/connection/file_connection/index.md b/mddocs/docs/ru/connection/file_connection/index.md new file mode 100644 index 000000000..4b581e0fd --- /dev/null +++ b/mddocs/docs/ru/connection/file_connection/index.md @@ -0,0 +1,16 @@ +(file-connections)= + +# File Connections + +```{toctree} +:caption: File Connections +:maxdepth: 1 + +FTP +FTPS +HDFS +Samba +SFTP +S3 +Webdav +``` diff --git a/mddocs/docs/ru/connection/file_connection/s3.md b/mddocs/docs/ru/connection/file_connection/s3.md new file mode 100644 index 000000000..c69a3ed8b --- /dev/null +++ b/mddocs/docs/ru/connection/file_connection/s3.md @@ -0,0 +1,12 @@ +(s3)= + +# S3 connection + +```{eval-rst} +.. currentmodule:: onetl.connection.file_connection.s3 +``` + +```{eval-rst} +.. autoclass:: S3 + :members: __init__, check, path_exists, is_file, is_dir, get_stat, resolve_dir, resolve_file, create_dir, remove_file, remove_dir, rename_file, list_dir, walk, download_file, upload_file +``` diff --git a/mddocs/docs/ru/connection/file_connection/samba.md b/mddocs/docs/ru/connection/file_connection/samba.md new file mode 100644 index 000000000..29f9f3f81 --- /dev/null +++ b/mddocs/docs/ru/connection/file_connection/samba.md @@ -0,0 +1,12 @@ +(samba)= + +# Samba connection + +```{eval-rst} +.. currentmodule:: onetl.connection.file_connection.samba +``` + +```{eval-rst} +.. autoclass:: Samba + :members: __init__, check, path_exists, is_file, is_dir, get_stat, resolve_dir, resolve_file, create_dir, remove_file, remove_dir, rename_file, list_dir, download_file, upload_file +``` diff --git a/mddocs/docs/ru/connection/file_connection/sftp.md b/mddocs/docs/ru/connection/file_connection/sftp.md new file mode 100644 index 000000000..45c7affa6 --- /dev/null +++ b/mddocs/docs/ru/connection/file_connection/sftp.md @@ -0,0 +1,12 @@ +(sftp)= + +# SFTP connection + +```{eval-rst} +.. currentmodule:: onetl.connection.file_connection.sftp +``` + +```{eval-rst} +.. autoclass:: SFTP + :members: __init__, check, path_exists, is_file, is_dir, get_stat, resolve_dir, resolve_file, create_dir, remove_file, remove_dir, rename_dir, rename_file, list_dir, walk, download_file, upload_file +``` diff --git a/mddocs/docs/ru/connection/file_connection/webdav.md b/mddocs/docs/ru/connection/file_connection/webdav.md new file mode 100644 index 000000000..5fd5ba006 --- /dev/null +++ b/mddocs/docs/ru/connection/file_connection/webdav.md @@ -0,0 +1,12 @@ +(webdav)= + +# WebDAV connection + +```{eval-rst} +.. currentmodule:: onetl.connection.file_connection.webdav +``` + +```{eval-rst} +.. autoclass:: WebDAV + :members: __init__, check, path_exists, is_file, is_dir, get_stat, resolve_dir, resolve_file, create_dir, remove_file, remove_dir, rename_file, list_dir, walk, download_file, upload_file +``` diff --git a/mddocs/docs/ru/connection/file_df_connection/base.md b/mddocs/docs/ru/connection/file_df_connection/base.md new file mode 100644 index 000000000..4b3935bf7 --- /dev/null +++ b/mddocs/docs/ru/connection/file_df_connection/base.md @@ -0,0 +1,12 @@ +(base-file-df-connection)= + +# Base interface + +```{eval-rst} +.. currentmodule:: onetl.base.base_file_df_connection +``` + +```{eval-rst} +.. autoclass:: BaseFileDFConnection + :members: check, check_if_format_supported, read_files_as_df, write_df_as_files +``` diff --git a/mddocs/docs/ru/connection/file_df_connection/index.md b/mddocs/docs/ru/connection/file_df_connection/index.md new file mode 100644 index 000000000..c6caecc4a --- /dev/null +++ b/mddocs/docs/ru/connection/file_df_connection/index.md @@ -0,0 +1,15 @@ +# Подключения к файловым системам с использованием DataFrame + +* [Spark LocalFS](spark_local_fs.md) +* [Spark HDFS](spark_hdfs/index.md) + * [Prerequisites](spark_hdfs/prerequisites.md) + * [Connection](spark_hdfs/connection.md) + * [Slots](spark_hdfs/slots.md) +* [Spark S3](spark_s3/index.md) + * [Prerequisites](spark_s3/prerequisites.md) + * [Connection](spark_s3/connection.md) + * [Troubleshooting](spark_s3/troubleshooting.md) + +# Для разработчиков + +* [Base interface](base.md) \ No newline at end of file diff --git a/mddocs/docs/ru/connection/file_df_connection/spark_hdfs/connection.md b/mddocs/docs/ru/connection/file_df_connection/spark_hdfs/connection.md new file mode 100644 index 000000000..08b8e4c3b --- /dev/null +++ b/mddocs/docs/ru/connection/file_df_connection/spark_hdfs/connection.md @@ -0,0 +1,12 @@ +(spark-hdfs-connection)= + +# Spark HDFS Connection + +```{eval-rst} +.. currentmodule:: onetl.connection.file_df_connection.spark_hdfs.connection +``` + +```{eval-rst} +.. autoclass:: SparkHDFS + :members: check, get_current +``` diff --git a/mddocs/docs/ru/connection/file_df_connection/spark_hdfs/index.md b/mddocs/docs/ru/connection/file_df_connection/spark_hdfs/index.md new file mode 100644 index 000000000..77d347d7a --- /dev/null +++ b/mddocs/docs/ru/connection/file_df_connection/spark_hdfs/index.md @@ -0,0 +1,18 @@ +(spark-hdfs)= + +# Spark HDFS + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +Connection +``` + +```{toctree} +:caption: For developers +:maxdepth: 1 + +Slots +``` diff --git a/mddocs/docs/ru/connection/file_df_connection/spark_hdfs/prerequisites.md b/mddocs/docs/ru/connection/file_df_connection/spark_hdfs/prerequisites.md new file mode 100644 index 000000000..f946b3a70 --- /dev/null +++ b/mddocs/docs/ru/connection/file_df_connection/spark_hdfs/prerequisites.md @@ -0,0 +1,46 @@ +(spark-hdfs-prerequisites)= + +# Prerequisites + +## Version Compatibility + +- Hadoop versions: 2.x, 3.x (only with Hadoop 3.x libraries) +- Spark versions: 2.3.x - 3.5.x +- Java versions: 8 - 20 + +## Installing PySpark + +To use SparkHDFS connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Using Kerberos + +Some of Hadoop managed clusters use Kerberos authentication. In this case, you should call [kinit](https://web.mit.edu/kerberos/krb5-1.12/doc/user/user_commands/kinit.html) command +**BEFORE** starting Spark session to generate Kerberos ticket. See {ref}`install-kerberos`. + +Sometimes it is also required to pass keytab file to Spark config, allowing Spark executors to generate own Kerberos tickets: + +```{eval-rst} +.. tabs:: + + .. code-tab:: python Spark 3 + + SparkSession.builder + .option("spark.kerberos.access.hadoopFileSystems", "hdfs://namenode1.domain.com:9820,hdfs://namenode2.domain.com:9820") + .option("spark.kerberos.principal", "user") + .option("spark.kerberos.keytab", "/path/to/keytab") + .gerOrCreate() + + .. code-tab:: python Spark 2 + + SparkSession.builder + .option("spark.yarn.access.hadoopFileSystems", "hdfs://namenode1.domain.com:9820,hdfs://namenode2.domain.com:9820") + .option("spark.yarn.principal", "user") + .option("spark.yarn.keytab", "/path/to/keytab") + .gerOrCreate() +``` + +See [Spark security documentation](https://spark.apache.org/docs/latest/security.html#kerberos) +for more details. diff --git a/mddocs/docs/ru/connection/file_df_connection/spark_hdfs/slots.md b/mddocs/docs/ru/connection/file_df_connection/spark_hdfs/slots.md new file mode 100644 index 000000000..715578579 --- /dev/null +++ b/mddocs/docs/ru/connection/file_df_connection/spark_hdfs/slots.md @@ -0,0 +1,13 @@ +(spark-hdfs-slots)= + +# Spark HDFS Slots + +```{eval-rst} +.. currentmodule:: onetl.connection.file_df_connection.spark_hdfs.slots +``` + +```{eval-rst} +.. autoclass:: SparkHDFSSlots + :members: normalize_cluster_name, normalize_namenode_host, get_known_clusters, get_cluster_namenodes, get_current_cluster, get_ipc_port, is_namenode_active + :member-order: bysource +``` diff --git a/mddocs/docs/ru/connection/file_df_connection/spark_local_fs.md b/mddocs/docs/ru/connection/file_df_connection/spark_local_fs.md new file mode 100644 index 000000000..524d1c676 --- /dev/null +++ b/mddocs/docs/ru/connection/file_df_connection/spark_local_fs.md @@ -0,0 +1,12 @@ +(spark-local-fs)= + +# Spark LocalFS + +```{eval-rst} +.. currentmodule:: onetl.connection.file_df_connection.spark_local_fs +``` + +```{eval-rst} +.. autoclass:: SparkLocalFS + :members: check +``` diff --git a/mddocs/docs/ru/connection/file_df_connection/spark_s3/connection.md b/mddocs/docs/ru/connection/file_df_connection/spark_s3/connection.md new file mode 100644 index 000000000..82de3999f --- /dev/null +++ b/mddocs/docs/ru/connection/file_df_connection/spark_s3/connection.md @@ -0,0 +1,12 @@ +(spark-s3-connection)= + +# Spark S3 Connection + +```{eval-rst} +.. currentmodule:: onetl.connection.file_df_connection.spark_s3.connection +``` + +```{eval-rst} +.. autoclass:: SparkS3 + :members: check, close, get_packages, get_exclude_packages +``` diff --git a/mddocs/docs/ru/connection/file_df_connection/spark_s3/index.md b/mddocs/docs/ru/connection/file_df_connection/spark_s3/index.md new file mode 100644 index 000000000..1ecf94737 --- /dev/null +++ b/mddocs/docs/ru/connection/file_df_connection/spark_s3/index.md @@ -0,0 +1,12 @@ +(spark-s3)= + +# Spark S3 + +```{toctree} +:caption: Connection +:maxdepth: 1 + +prerequisites +Connection +Troubleshooting +``` diff --git a/mddocs/docs/ru/connection/file_df_connection/spark_s3/prerequisites.md b/mddocs/docs/ru/connection/file_df_connection/spark_s3/prerequisites.md new file mode 100644 index 000000000..9135275f3 --- /dev/null +++ b/mddocs/docs/ru/connection/file_df_connection/spark_s3/prerequisites.md @@ -0,0 +1,61 @@ +(spark-s3-prerequisites)= + +# Prerequisites + +## Version Compatibility + +- Spark versions: 3.2.x - 3.5.x (only with Hadoop 3.x libraries) +- Java versions: 8 - 20 + +## Installing PySpark + +To use SparkS3 connector you should have PySpark installed (or injected to `sys.path`) +BEFORE creating the connector instance. + +See {ref}`install-spark` installation instruction for more details. + +## Connecting to S3 + +### Bucket access style + +AWS and some other S3 cloud providers allows bucket access using domain style only, e.g. `https://mybucket.s3provider.com`. + +Other implementations, like Minio, by default allows path style access only, e.g. `https://s3provider.com/mybucket` +(see [MINIO_DOMAIN](https://min.io/docs/minio/linux/reference/minio-server/minio-server.html#envvar.MINIO_DOMAIN)). + +You should set `path.style.access` to `True` or `False`, to choose the preferred style. + +### Authentication + +Different S3 instances can use different authentication methods, like: +: - `access_key + secret_key` (or username + password) + - `access_key + secret_key + session_token` + +Usually these are just passed to SparkS3 constructor: + +```python +SparkS3( + access_key=..., + secret_key=..., + session_token=..., +) +``` + +But some S3 cloud providers, like AWS, may require custom credential providers. You can pass them like: + +```python +SparkS3( + extra={ + # provider class + "aws.credentials.provider": "org.apache.hadoop.fs.s3a.auth.AssumedRoleCredentialProvider", + # other options, if needed + "assumed.role.arn": "arn:aws:iam::90066806600238:role/s3-restricted", + }, +) +``` + +See [Hadoop-AWS](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/index.html#Changing_Authentication_Providers) documentation. + +## Troubleshooting + +See {ref}`spark-s3-troubleshooting`. diff --git a/mddocs/docs/ru/connection/file_df_connection/spark_s3/troubleshooting.md b/mddocs/docs/ru/connection/file_df_connection/spark_s3/troubleshooting.md new file mode 100644 index 000000000..5f998779d --- /dev/null +++ b/mddocs/docs/ru/connection/file_df_connection/spark_s3/troubleshooting.md @@ -0,0 +1,377 @@ +(spark-s3-troubleshooting)= + +# Spark S3 Troubleshooting + +```{eval-rst} +.. note:: + + General guide: :ref:`troubleshooting`. +``` + +More details: + +- [Hadoop AWS Troubleshooting Guide](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/troubleshooting_s3a.html) +- [Hadoop AWS Performance Guide](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/performance.html) +- [Spark integration with Cloud Infrastructures](https://spark.apache.org/docs/latest/cloud-integration.html) + +## `SparkS3.check()` and other methods hang + +### Details + +S3 may not respond for connection attempts for a long time if it's under heavy load. +To handle this, Hadoop AWS library has retry mechanism. By default it retries 7 times with 500ms interval. + +Hadoop AWS is based on AWS SDK library, which also has retry mechanism. This mechanism is not disabled because it handles different +errors than Hadoop AWS, so they complement each other. Default number of attempts in AWS SDK is 20 with minimal 5s interval, +which is exponentially increasing with each failed attempt. + +It is not a problem if S3 source is not accessible at all, like hostname cannot be resolved, or port is not opened. +These errors are not recoverable, and retry mechanism is not activated. + +But errors like SSL issues, are considered recoverable, and this causing retry of retry over increasing interval. +So user is waiting for [almost 15 minutes](https://issues.apache.org/jira/browse/HADOOP-18839) just to get exception message. + +### How to determine reason + +#### Make logging more verbose + +Change Spark session log level to {ref}`DEBUG ` to print result of each attempt. +Resulting logs will look like this + +```{eval-rst} +.. dropdown:: See log + + .. code:: text + + 23/08/03 11:25:10 DEBUG S3AFileSystem: Using S3ABlockOutputStream with buffer = disk; block=67108864; queue limit=4 + 23/08/03 11:25:10 DEBUG S3Guard: Metastore option source [core-default.xml] + 23/08/03 11:25:10 DEBUG S3Guard: Using NullMetadataStore metadata store for s3a filesystem + 23/08/03 11:25:10 DEBUG S3AFileSystem: S3Guard is disabled on this bucket: test-bucket + 23/08/03 11:25:10 DEBUG DirectoryPolicyImpl: Directory markers will be deleted + 23/08/03 11:25:10 DEBUG S3AFileSystem: Directory marker retention policy is DirectoryMarkerRetention{policy='delete'} + 23/08/03 11:25:10 DEBUG S3AUtils: Value of fs.s3a.multipart.purge.age is 86400 + 23/08/03 11:25:10 DEBUG S3AUtils: Value of fs.s3a.bulk.delete.page.size is 250 + 23/08/03 11:25:10 DEBUG FileSystem: Creating FS s3a://test-bucket/fake: duration 0:01.029s + 23/08/03 11:25:10 DEBUG IOStatisticsStoreImpl: Incrementing counter op_is_directory by 1 with final value 1 + 23/08/03 11:25:10 DEBUG S3AFileSystem: Getting path status for s3a://test-bucket/fake (fake); needEmptyDirectory=false + 23/08/03 11:25:10 DEBUG S3AFileSystem: S3GetFileStatus s3a://test-bucket/fake + 23/08/03 11:25:10 DEBUG S3AFileSystem: LIST List test-bucket:/fake/ delimiter=/ keys=2 requester pays=false + 23/08/03 11:25:10 DEBUG S3AFileSystem: Starting: LIST + 23/08/03 11:25:10 DEBUG IOStatisticsStoreImpl: Incrementing counter object_list_request by 1 with final value 1 + 23/08/03 11:25:10 DEBUG AWSCredentialProviderList: Using credentials from SimpleAWSCredentialsProvider + 23/08/03 11:25:10 DEBUG request: Sending Request: GET https://test-bucket.localhost:9000 / Parameters: ({"list-type":["2"],"delimiter":["/"],"max-keys":["2"],"prefix":["fake/"],"fetch-owner":["false"]}Headers: (amz-sdk-invocation-id: e6d62603-96e4-a80f-10a1-816e0822bc71, Content-Type: application/octet-stream, User-Agent: Hadoop 3.3.4, aws-sdk-java/1.12.262 Linux/6.4.7-1-MANJARO OpenJDK_64-Bit_Server_VM/25.292-b10 java/1.8.0_292 scala/2.12.17 vendor/AdoptOpenJDK cfg/retry-mode/legacy, ) + 23/08/03 11:25:10 DEBUG AWS4Signer: AWS4 Canonical Request: '"GET + / + delimiter=%2F&fetch-owner=false&list-type=2&max-keys=2&prefix=fake%2F + amz-sdk-invocation-id:e6d62603-96e4-a80f-10a1-816e0822bc71 + amz-sdk-request:attempt=1;max=21 + amz-sdk-retry:0/0/500 + content-type:application/octet-stream + host:test-bucket.localhost:9000 + user-agent:Hadoop 3.3.4, aws-sdk-java/1.12.262 Linux/6.4.7-1-MANJARO OpenJDK_64-Bit_Server_VM/25.292-b10 java/1.8.0_292 scala/2.12.17 vendor/AdoptOpenJDK cfg/retry-mode/legacy + x-amz-content-sha256:UNSIGNED-PAYLOAD + x-amz-date:20230803T112510Z + + amz-sdk-invocation-id;amz-sdk-request;amz-sdk-retry;content-type;host;user-agent;x-amz-content-sha256;x-amz-date + UNSIGNED-PAYLOAD" + 23/08/03 11:25:10 DEBUG AWS4Signer: AWS4 String to Sign: '"AWS4-HMAC-SHA256 + 20230803T112510Z + 20230803/us-east-1/s3/aws4_request + 31a317bb7f6d97248dd0cf03429d701cbb3e29ce889cfbb98ba7a34c57a3bfba" + 23/08/03 11:25:10 DEBUG AWS4Signer: Generating a new signing key as the signing key not available in the cache for the date 1691020800000 + 23/08/03 11:25:10 DEBUG RequestAddCookies: CookieSpec selected: default + 23/08/03 11:25:10 DEBUG RequestAuthCache: Auth cache not set in the context + 23/08/03 11:25:10 DEBUG PoolingHttpClientConnectionManager: Connection request: [route: {s}->https://test-bucket.localhost:9000][total available: 0; route allocated: 0 of 96; total allocated: 0 of 96] + 23/08/03 11:25:10 DEBUG PoolingHttpClientConnectionManager: Connection leased: [id: 0][route: {s}->https://test-bucket.localhost:9000][total available: 0; route allocated: 1 of 96; total allocated: 1 of 96] + 23/08/03 11:25:10 DEBUG MainClientExec: Opening connection {s}->https://test-bucket.localhost:9000 + 23/08/03 11:25:10 DEBUG DefaultHttpClientConnectionOperator: Connecting to test-bucket.localhost/127.0.0.1:9000 + 23/08/03 11:25:10 DEBUG SSLConnectionSocketFactory: Connecting socket to test-bucket.localhost/127.0.0.1:9000 with timeout 5000 + 23/08/03 11:25:10 DEBUG SSLConnectionSocketFactory: Enabled protocols: [TLSv1.2] + 23/08/03 11:25:10 DEBUG SSLConnectionSocketFactory: Enabled cipher suites:[TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV] + 23/08/03 11:25:10 DEBUG SSLConnectionSocketFactory: Starting handshake + 23/08/03 11:25:10 DEBUG ClientConnectionManagerFactory: + java.lang.reflect.InvocationTargetException + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:498) + at com.amazonaws.http.conn.ClientConnectionManagerFactory$Handler.invoke(ClientConnectionManagerFactory.java:76) + at com.amazonaws.http.conn.$Proxy32.connect(Unknown Source) + at com.amazonaws.thirdparty.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:393) + at com.amazonaws.thirdparty.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236) + at com.amazonaws.thirdparty.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186) + at com.amazonaws.thirdparty.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185) + at com.amazonaws.thirdparty.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83) + at com.amazonaws.thirdparty.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56) + at com.amazonaws.http.apache.client.impl.SdkHttpClient.execute(SdkHttpClient.java:72) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeOneRequest(AmazonHttpClient.java:1346) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeHelper(AmazonHttpClient.java:1157) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.doExecute(AmazonHttpClient.java:814) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeWithTimer(AmazonHttpClient.java:781) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.execute(AmazonHttpClient.java:755) + at com.amazonaws.http.AmazonHttpClient$RequestExecutor.access$500(AmazonHttpClient.java:715) + at com.amazonaws.http.AmazonHttpClient$RequestExecutionBuilderImpl.execute(AmazonHttpClient.java:697) + at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:561) + at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:541) + at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5456) + at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5403) + at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5397) + at com.amazonaws.services.s3.AmazonS3Client.listObjectsV2(AmazonS3Client.java:971) + at org.apache.hadoop.fs.s3a.S3AFileSystem.lambda$listObjects$11(S3AFileSystem.java:2595) + at org.apache.hadoop.fs.statistics.impl.IOStatisticsBinding.lambda$trackDurationOfOperation$5(IOStatisticsBinding.java:499) + at org.apache.hadoop.fs.s3a.Invoker.retryUntranslated(Invoker.java:414) + at org.apache.hadoop.fs.s3a.Invoker.retryUntranslated(Invoker.java:377) + at org.apache.hadoop.fs.s3a.S3AFileSystem.listObjects(S3AFileSystem.java:2586) + at org.apache.hadoop.fs.s3a.S3AFileSystem.s3GetFileStatus(S3AFileSystem.java:3832) + at org.apache.hadoop.fs.s3a.S3AFileSystem.innerGetFileStatus(S3AFileSystem.java:3688) + at org.apache.hadoop.fs.s3a.S3AFileSystem.lambda$isDirectory$35(S3AFileSystem.java:4724) + at org.apache.hadoop.fs.statistics.impl.IOStatisticsBinding.lambda$trackDurationOfOperation$5(IOStatisticsBinding.java:499) + at org.apache.hadoop.fs.statistics.impl.IOStatisticsBinding.trackDuration(IOStatisticsBinding.java:444) + at org.apache.hadoop.fs.s3a.S3AFileSystem.trackDurationAndSpan(S3AFileSystem.java:2337) + at org.apache.hadoop.fs.s3a.S3AFileSystem.trackDurationAndSpan(S3AFileSystem.java:2356) + at org.apache.hadoop.fs.s3a.S3AFileSystem.isDirectory(S3AFileSystem.java:4722) + at org.apache.spark.sql.execution.streaming.FileStreamSink$.hasMetadata(FileStreamSink.scala:54) + at org.apache.spark.sql.execution.datasources.DataSource.resolveRelation(DataSource.scala:366) + at org.apache.spark.sql.DataFrameReader.loadV1Source(DataFrameReader.scala:229) + at org.apache.spark.sql.DataFrameReader.$anonfun$load$2(DataFrameReader.scala:211) + at scala.Option.getOrElse(Option.scala:189) + at org.apache.spark.sql.DataFrameReader.load(DataFrameReader.scala:211) + at org.apache.spark.sql.DataFrameReader.load(DataFrameReader.scala:186) + at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) + at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) + at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) + at java.lang.reflect.Method.invoke(Method.java:498) + at py4j.reflection.MethodInvoker.invoke(MethodInvoker.java:244) + at py4j.reflection.ReflectionEngine.invoke(ReflectionEngine.java:374) + at py4j.Gateway.invoke(Gateway.java:282) + at py4j.commands.AbstractCommand.invokeMethod(AbstractCommand.java:132) + at py4j.commands.CallCommand.execute(CallCommand.java:79) + at py4j.ClientServerConnection.waitForCommands(ClientServerConnection.java:182) + at py4j.ClientServerConnection.run(ClientServerConnection.java:106) + at java.lang.Thread.run(Thread.java:748) + Caused by: javax.net.ssl.SSLException: Unsupported or unrecognized SSL message + at sun.security.ssl.SSLSocketInputRecord.handleUnknownRecord(SSLSocketInputRecord.java:448) + at sun.security.ssl.SSLSocketInputRecord.decode(SSLSocketInputRecord.java:184) + at sun.security.ssl.SSLTransport.decode(SSLTransport.java:109) + at sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1383) + at sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1291) + at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:435) + at com.amazonaws.thirdparty.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:436) + at com.amazonaws.thirdparty.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:384) + at com.amazonaws.thirdparty.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142) + at com.amazonaws.thirdparty.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:376) + ... 58 more + 23/08/03 11:25:10 DEBUG DefaultManagedHttpClientConnection: http-outgoing-0: Shutdown connection + 23/08/03 11:25:10 DEBUG MainClientExec: Connection discarded + 23/08/03 11:25:10 DEBUG PoolingHttpClientConnectionManager: Connection released: [id: 0][route: {s}->https://test-bucket.localhost:9000][total available: 0; route allocated: 0 of 96; total allocated: 0 of 96] + 23/08/03 11:25:10 DEBUG AmazonHttpClient: Unable to execute HTTP request: Unsupported or unrecognized SSL message Request will be retried. + 23/08/03 11:25:10 DEBUG request: Retrying Request: GET https://test-bucket.localhost:9000 / Parameters: ({"list-type":["2"],"delimiter":["/"],"max-keys":["2"],"prefix":["fake/"],"fetch-owner":["false"]}Headers: (amz-sdk-invocation-id: e6d62603-96e4-a80f-10a1-816e0822bc71, Content-Type: application/octet-stream, User-Agent: Hadoop 3.3.4, aws-sdk-java/1.12.262 Linux/6.4.7-1-MANJARO OpenJDK_64-Bit_Server_VM/25.292-b10 java/1.8.0_292 scala/2.12.17 vendor/AdoptOpenJDK cfg/retry-mode/legacy, ) + 23/08/03 11:25:10 DEBUG AmazonHttpClient: Retriable error detected, will retry in 49ms, attempt number: 0 +``` + +#### Change number of retries + +You can also change number of retries performed by both libraries using `extra` parameter: + +```python +spark_s3 = SparkS3( + ..., + extra={ + "attempts.maximum": 1, + "retry.limit": 1, + }, +) +``` + +So accessing S3 will fail almost immediately if there is any error. + +### Most common mistakes + +#### No network access + +```text +Caused by: java.net.ConnectException: Connection refused +``` + +Mostly caused by: + +- Trying to access port number which S3 server does not listen +- You're trying to access host which is unreachable from your network (e.g. running behind some proxy or VPN) +- There are some firewall restrictions for accessing specific host or port + +#### Using HTTPS protocol for HTTP port + +```text +Caused by: javax.net.ssl.SSLException: Unsupported or unrecognized SSL message +``` + +By default, SparkS3 uses HTTPS protocol for connection. +If you change port number, this does not lead to changing protocol: + +```python +spark_s3 = SparkS3(host="s3provider.com", port=8080, ...) +``` + +You should pass protocol explicitly: + +```python +spark_s3 = SparkS3(host="s3provider.com", port=8080, protocol="http", ...) +``` + +#### SSL certificate is self-signed + +```text +sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target +``` + +To connect to HTTPS port with self-signed certificate, you should +[add certificate chain to Java TrustedStore](https://stackoverflow.com/questions/373295/digital-certificate-how-to-import-cer-file-in-to-truststore-file-using). + +Another option is to disable SSL check: + +```python +spark_s3 = SparkS3( + ..., + extra={ + "connection.ssl.enabled": False, + }, +) +``` + +But is is **NOT** recommended. + +#### Accessing S3 without domain-style access style support + +```text +Caused by: java.net.UnknownHostException: my-bucket.s3provider.com +``` + +To use path-style access, use option below: + +```python +spark_s3 = SparkS3( + host="s3provider.com", + bucket="my-bucket", + ..., + extra={ + "path.style.access": True, + }, +) +``` + +## Slow or unstable writing to S3 + +Hadoop AWS allows to use different writing strategies for different S3 implementations, depending +on list of supported features by server. + +These strategies are called [committers](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/committers.html). +There are [different types of committers](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/committers.html#Switching_to_an_S3A_Committer): + +- `file` (default) +- `directory` +- `partitioned` +- `magic` + +### `file` committer + +This committer is quite slow and unstable, so it is not recommended to use: + +```text +WARN AbstractS3ACommitterFactory: Using standard FileOutputCommitter to commit work. This is slow and potentially unsafe. +``` + +This is caused by the fact it creates files in the temp directory on remote filesystem, and after all of them are written successfully, +they are moved to target directory on same remote filesystem. + +This is not an issue for HDFS which does support file move operations and also support renaming directory +as atomic operation with `O(1)` time complexity. + +But S3 does support only file copying, so moving is performed via copy + delete. +Also it does not support atomic directory rename operation. Instead, renaming files with the same prefix has time complexity `O(n)`. + +### `directory` and `partitioned` committers + +These are [staging committers](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/committer_architecture.html), +meaning that they create temp directories on local filesystem, and after all files are written successfully, +they will be uploaded to S3. Local filesystems do support file moving and directory renaming, +so these committers does not have issues that `file` committer has. + +But they both require free space on local filesystem, and this may be an issue if user need to write large amount of data. +Also this can be an issue for container environment, like Kubernetes, there resources should be allocated before starting a container. + +### `magic` committer + +This committer uses multipart upload feature of S3 API, allowing to create multiple files +and after all of them were written successfully finish the transaction. Before transaction is finished, +files will not be accessible by other clients. + +Because it does not require neither file moving operations, nor directory atomic rename, +upload process is done in most efficient way S3 support. +This [drastically increases writing performance](https://spot.io/blog/improve-apache-spark-performance-with-the-s3-magic-committer/). + +To use this committer, set [following properties](https://github.com/apache/spark/pull/32518) while creating Spark session. + +```{eval-rst} +.. tabs:: + + .. code-tab:: py S3 your main distributed filesystem (Spark on Kubernetes) + + # https://issues.apache.org/jira/browse/SPARK-23977 + # https://spark.apache.org/docs/latest/cloud-integration.html#committing-work-into-cloud-storage-safely-and-fast + spark = ( + SparkSession.builder.appName("spark-app-name") + .config("spark.hadoop.fs.s3a.committer.magic.enabled", "true") + .config("spark.hadoop.fs.s3a.committer.name", "magic") + .config("spark.hadoop.mapreduce.outputcommitter.factory.scheme.s3a", "org.apache.hadoop.fs.s3a.commit.S3ACommitterFactory") + .config("spark.sql.parquet.output.committer.class", "org.apache.spark.internal.io.cloud.BindingParquetOutputCommitter") + .config("spark.sql.sources.commitProtocolClass", "org.apache.spark.internal.io.cloud.PathOutputCommitProtocol") + .getOrCreate() + ) + + .. code-tab:: py HDFS is your main distributed filesystem (Spark on Hadoop) + + # https://community.cloudera.com/t5/Support-Questions/spark-sql-sources-partitionOverwriteMode-dynamic-quot-not/m-p/343483/highlight/true + spark = ( + SparkSession.builder.appName("spark-app-name") + .config("spark.hadoop.fs.s3a.committer.magic.enabled", "true") + .config("spark.hadoop.fs.s3a.committer.name", "magic") + .getOrCreate() + ) +``` + +```{eval-rst} +.. warning:: + + ``magic`` committer requires S3 implementation to have strong consistency - file upload API return response only + if it was written on enough number of cluster nodes, and any cluster node error does not lead to missing or corrupting files. + + Some S3 implementations does have strong consistency + (like `AWS S3 `_ and + `MinIO `_), some not. Please contact your S3 provider + to get information about S3 implementation consistency. +``` + +```{eval-rst} +.. warning:: + + ``magic`` committer does not support ``if_exists="replace_overlapping_partitions"``. + Either use another ``if_exists`` value, or use ``partitioned`` committer. +``` + +### See also + +- [directory.marker.retention="keep"](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/directory_markers.html) + +## Slow reading from S3 + +Please read following documentation: + +- [prefetch.enabled](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/prefetching.html) +- [experimental.input.fadvise](https://hadoop.apache.org/docs/stable/hadoop-aws/tools/hadoop-aws/performance.html#Improving_data_input_performance_through_fadvise) +- [Parquet and ORC I/O settings](https://spark.apache.org/docs/latest/cloud-integration.html#parquet-io-settings) + +If you're reading data from row-based formats, like {ref}`csv-file-format`, prefer +[experimental.input.fadvise="sequential" with increased readahead.range](https://issues.apache.org/jira/browse/HADOOP-17789?focusedCommentId=17383559#comment-17383559). + +But for other file formats, especially using compression, prefer +[experimental.input.fadvise="normal"](https://issues.apache.org/jira/browse/HADOOP-17789?focusedCommentId=17383743#comment-17383743) diff --git a/mddocs/docs/ru/connection/index.md b/mddocs/docs/ru/connection/index.md new file mode 100644 index 000000000..41ab3aafd --- /dev/null +++ b/mddocs/docs/ru/connection/index.md @@ -0,0 +1,34 @@ +# Подключение + + ## Подключения к БД + +* [DB Connections](db_connection/index.md) + * [Clickhouse](db_connection/clickhouse/index.md) + * [Greenplum](db_connection/greenplum/index.md) + * [Kafka](db_connection/kafka/index.md) + * [Hive](db_connection/hive/index.md) + * [MongoDB](db_connection/mongodb/index.md) + * [MSSQL](db_connection/mssql/index.md) + * [MySQL](db_connection/mysql/index.md) + * [Oracle](db_connection/oracle/index.md) + * [Postgres](db_connection/postgres/index.md) + * [Teradata](db_connection/teradata/index.md) + + ## Подключения к файловым системам\серверам + +* [File Connections](file_connection/index.md) + * [FTP](file_connection/ftp.md) + * [FTPS](file_connection/ftps.md) + * [HDFS](file_connection/hdfs/index.md) + * [Samba](file_connection/samba.md) + * [SFTP](file_connection/sftp.md) + * [S3](file_connection/s3.md) + * [Webdav](file_connection/webdav.md) + + ## Подключения к файловым системам с использованием DataFrame + +* [File DataFrame Connections](file_df_connection/index.md) + * [Spark LocalFS](file_df_connection/spark_local_fs.md) + * [Spark HDFS](file_df_connection/spark_hdfs/index.md) + * [Spark S3](file_df_connection/spark_s3/index.md) + * [Base interface](file_df_connection/base.md) \ No newline at end of file diff --git a/mddocs/ru/CONTRIBUTING.md b/mddocs/docs/ru/contributing.md similarity index 61% rename from mddocs/ru/CONTRIBUTING.md rename to mddocs/docs/ru/contributing.md index cb3d79377..de2732222 100644 --- a/mddocs/ru/CONTRIBUTING.md +++ b/mddocs/docs/ru/contributing.md @@ -1,14 +1,14 @@ # Руководство по участию -Добро пожаловать! Существует множество способов внести свой вклад, включая отправку отчетов об ошибках, улучшение документации, отправку запросов на добавление функций, рецензирование новых материалов или внесение кода, который можно включить в проект. +Добро пожаловать! Существует множество способов внести свой вклад, включая отправку отчетов об ошибках, улучшение документации, отправку запросов на добавление функций, рецензирование новых материалов или предоставление кода, который можно включить в проект. ## Ограничения При разработке следует придерживаться следующих пунктов: -- Некоторые компании до сих пор используют старые версии Spark, например 2.3.1. Поэтому, по возможности, необходимо поддерживать такую совместимость, например, добавлять ветки для разных версий Spark. -- Разные пользователи используют onETL по-разному - некоторые используют только коннекторы к БД, другие только файловые подключения. Зависимости, специфичные для коннекторов, должны быть необязательными. -- Вместо создания классов с большим количеством различных опций, лучше разделить их на более мелкие классы, например, класс опций, контекстный менеджер и т.д., и использовать композицию. +* Некоторые компании до сих пор используют старые версии Spark, например 2.3.1. Поэтому, по возможности, необходимо сохранять совместимость, например, добавлять ветки для разных версий Spark. +* Разные пользователи используют onETL по-разному - некоторые используют только DB коннекторы, некоторые только файлы. Зависимости, специфичные для коннекторов, должны быть необязательными. +* Вместо создания классов с большим количеством различных опций, лучше разделить их на более мелкие классы, например, класс опций, контекстный менеджер и т.д., и использовать композицию. ## Начальная настройка для локальной разработки @@ -80,7 +80,7 @@ pre-commit install --install-hooks pre-commit run ``` -## Как +## Как тестировать ### Запустите тесты локально @@ -110,7 +110,7 @@ docker-compose --profile mongodb up -d docker-compose run --rm onetl ./run_tests.sh ``` -Вы можете передать дополнительные аргументы и они будут переданы в pytest: +Вы можете передать дополнительные аргументы, они будут переданы в pytest: ```bash docker-compose run --rm onetl ./run_tests.sh -m mongodb -lsx -vvvv --log-cli-level=INFO @@ -140,30 +140,28 @@ docker-compose --profile all down -v !!! warning - Для локального запуска тестов HDFS необходимо добавить следующую строку в ``/etc/hosts`` (путь к файлу зависит от ОС): + Для локального запуска тестов HDFS необходимо добавить следующую строку в файл `/etc/hosts` (путь к файлу зависит от ОС): - ```text - # HDFS server returns container hostname as connection address, causing error in DNS resolution - 127.0.0.1 hdfs + ```default + # HDFS server returns container hostname as connection address, causing error in DNS resolution + 127.0.0.1 hdfs ``` !!! note - Для запуска тестов Oracle необходимо установить `Oracle instantclient `__, - и передать его путь в переменные окружения ``ONETL_ORA_CLIENT_PATH`` и ``LD_LIBRARY_PATH``, - например, ``ONETL_ORA_CLIENT_PATH=/path/to/client64/lib``. - - Также может потребоваться добавить этот же путь в переменную окружения ``LD_LIBRARY_PATH`` + Для запуска тестов Oracle необходимо установить [Oracle instantclient](https://www.oracle.com/database/technologies/instant-client.html) + и передать его путь в переменные окружения `ONETL_ORA_CLIENT_PATH` и `LD_LIBRARY_PATH`, + например, `ONETL_ORA_CLIENT_PATH=/path/to/client64/lib`. + Также может потребоваться добавить тот же путь в переменную окружения `LD_LIBRARY_PATH`. !!! note Для запуска тестов Greenplum необходимо: - * Скачать `VMware Greenplum connector for Spark `_ - * Либо переместить его в ``~/.ivy2/jars/``, либо передать путь к файлу в ``CLASSPATH`` - * Установить переменную окружения ``ONETL_GP_PACKAGE_VERSION=local``. - + * Скачать [VMware Greenplum connector for Spark](https://onetl.readthedocs.io/en/latest/connection/db_connection/greenplum/prerequisites.html) + * Либо переместить его в `~/.ivy2/jars/`, либо передать путь к файлу в `CLASSPATH` + * Установить переменную окружения `ONETL_GP_PACKAGE_VERSION=local`. Запустите все контейнеры с зависимостями: @@ -214,11 +212,11 @@ make html ## Процесс рецензирования -Пожалуйста, создайте новую задачу GitHub для любых значительных изменений и улучшений, которые вы хотите внести. Опишите функцию, которую вы хотели бы видеть, зачем она вам нужна и как она будет работать. Обсудите свои идеи прозрачно и получите отзывы сообщества, прежде чем продолжить. +Пожалуйста, создайте новую задачу GitHub для любых значительных изменений и улучшений, которые вы хотите внести. Укажите функцию, которую вы хотели бы видеть, зачем она вам нужна и как она будет работать. Обсуждайте свои идеи открыто и получайте отзывы сообщества, прежде чем продолжить. -Значительные изменения, которые вы хотите внести в проект, должны быть предварительно обсуждены в задаче GitHub, в которой четко изложены изменения и преимущества этой функции. +Значительные изменения, которые вы хотите внести в проект, следует сначала обсудить в задаче GitHub, в которой четко изложены изменения и преимущества этой функции. -Небольшие изменения можно сразу же создать и отправить в репозиторий GitHub в виде запроса на включение изменений (Pull Request). +Небольшие изменения можно сразу же создать и отправить в репозиторий GitHub в виде запроса на включение (Pull Request). ### Создайте pull request @@ -229,23 +227,45 @@ git commit -m "Commit message" git push ``` -Затем откройте интерфейс Github и [создайте pull request](https://docs.github.com/en/get-started/quickstart/contributing-to-projects#making-a-pull-request). Пожалуйста, следуйте руководству из шаблона тела PR. +Затем откройте интерфейс Github и [создайте pull request](https://docs.github.com/en/get-started/quickstart/contributing-to-projects#making-a-pull-request). +Пожалуйста, следуйте руководству из шаблона тела PR. После создания pull request он получает соответствующий номер, например, 123 (`pr_number`). ### Напишите release notes -`onETL` использует [towncrier](https://pypi.org/project/towncrier/) для управления журналом изменений. - -Чтобы отправить заметку об изменении для вашего PR, добавьте текстовый файл в папку [docs/changelog/next_release](./next_release). Он должен содержать объяснение того, как применение этого PR изменит способ взаимодействия конечных пользователей с проектом. Одного предложения обычно достаточно, но не стесняйтесь добавлять столько деталей, сколько считаете необходимым для того, чтобы пользователи поняли, что это значит. - -**Используйте прошедшее время** для текста в вашем фрагменте, потому что, в сочетании с другими, он станет частью "сводки новостей", сообщающей читателям **что изменилось** в конкретной версии библиотеки *с момента предыдущей версии*. - -Вам также следует использовать синтаксис reStructuredText для выделения кода (встроенного или блочного), связывания частей документации или внешних сайтов. Если вы хотите подписать свое изменение, не стесняйтесь добавить `` -- by\:user:`github-username` `` в конце (замените `github-username` на свое собственное!). - -Наконец, назовите свой файл в соответствии с соглашением, которое понимает Towncrier: он должен начинаться с номера задачи или PR, за которым следует точка, затем добавьте тип патча, например, `feature`, `doc`, `misc` и т.д., и добавьте `.rst` в качестве суффикса. Если вам нужно добавить более одного фрагмента, вы можете добавить необязательный порядковый номер (разделенный другой точкой) между типом и суффиксом. - -В общем, имя будет соответствовать шаблону `..rst`, где категории: +`onETL` использует [towncrier](https://pypi.org/project/towncrier/) +для управления журналом изменений. + +Чтобы отправить заметку об изменении для вашего PR, добавьте текстовый файл в +папку [docs/changelog/next_release](./next_release). Он должен содержать +объяснение того, как применение этого PR изменит способ +взаимодействия конечных пользователей с проектом. Одного предложения обычно +достаточно, но не стесняйтесь добавлять столько деталей, сколько считаете необходимым +для того, чтобы пользователи поняли, что это значит. + +**Используйте прошедшее время** для текста в вашем фрагменте, потому что, +в сочетании с другими, он будет частью "сводки новостей", +рассказывающей читателям **что изменилось** в конкретной версии +библиотеки *с момента предыдущей версии*. + +Вам также следует использовать +синтаксис reStructuredText для выделения кода (встроенного или блочного), +связывания частей документации или внешних сайтов. +Если вы хотите подписать свое изменение, не стесняйтесь добавить `-- by +:user:`github-username`` в конце (замените `github-username` +на свой собственный!). + +Наконец, назовите свой файл в соответствии с соглашением, которое понимает Towncrier: +он должен начинаться с номера задачи или PR, за которым следует точка, +затем добавьте тип патча, например, `feature`, +`doc`, `misc` и т.д., и добавьте `.rst` в качестве суффикса. Если вам +нужно добавить более одного фрагмента, вы можете добавить необязательный +порядковый номер (разделенный другой точкой) между типом +и суффиксом. + +В общем, имя будет соответствовать шаблону `..rst`, +где категории: - `feature`: Любая новая функция - `bugfix`: Исправление ошибки @@ -254,39 +274,36 @@ git push - `dependency`: Изменения, связанные с зависимостями - `misc`: Внутренние изменения в репозитории, такие как CI, тесты и изменения сборки -Pull request может иметь более одного из этих компонентов, например, изменение кода может представить новую функцию, которая устаревает старую функцию, в этом случае следует добавить два фрагмента. Нет необходимо делать отдельный фрагмент документации для изменений документации, сопровождающих соответствующие изменения кода. +Pull request может иметь более одного из этих компонентов, например, +изменение кода может ввести новую функцию, которая устаревает старую +функцию, в этом случае следует добавить два фрагмента. Нет +необходимо делать отдельный фрагмент документации для изменений документации, +сопровождающих соответствующие изменения кода. #### Примеры добавления записей в журнал изменений в ваши Pull Requests -```rst title="docs/changelog/next_release/1234.doc.1.rst" - +```rst Added a ``:github:user:`` role to Sphinx config -- by :github:user:`someuser` ``` -```rst title="docs/changelog/next_release/2345.bugfix.rst" - +```rst Fixed behavior of ``WebDAV`` connector -- by :github:user:`someuser` ``` -```rst title="docs/changelog/next_release/3456.feature.rst" - +```rst Added support of ``timeout`` in ``S3`` connector -- by :github:user:`someuser`, :github:user:`anotheruser` and :github:user:`otheruser` ``` -!!! tip - - См. `pyproject.toml `_ для всех доступных категорий (``tool.towncrier.type``). - -#### Как пропустить проверку change notes? +#### Как пропустить проверку заметок об изменениях? Просто добавьте метку `ci:skip-changelog` к pull request. #### Процесс релиза -Перед тем, как делать релиз из ветки `develop`, выполните следующие шаги: +Перед тем, как сделать релиз из ветки `develop`, выполните следующие шаги: -0. Перейдите в ветку `develop` и обновите ее до актуального состояния +1. Перейдите в ветку `develop` и обновите ее до актуального состояния ```bash git checkout develop @@ -299,38 +316,38 @@ git pull -p cp "docs/changelog/NEXT_RELEASE.rst" "docs/changelog/temp_NEXT_RELEASE.rst" ``` -2. Соберите Release notes с помощью Towncrier +1. Соберите Release notes с помощью Towncrier ```bash VERSION=$(cat onetl/VERSION) towncrier build "--version=${VERSION}" --yes ``` -3. Измените файл с журналом изменений на номер версии релиза +1. Измените файл с журналом изменений на номер версии релиза ```bash mv docs/changelog/NEXT_RELEASE.rst "docs/changelog/${VERSION}.rst" ``` -4. Удалите содержимое над заголовком номера версии в файле `${VERSION}.rst` +1. Удалите содержимое над заголовком номера версии в файле `${VERSION}.rst` ```bash awk '!/^.*towncrier release notes start/' "docs/changelog/${VERSION}.rst" > temp && mv temp "docs/changelog/${VERSION}.rst" ``` -5. Обновите индекс журнала изменений +1. Обновите индекс журнала изменений ```bash awk -v version=${VERSION} '/DRAFT/{print;print " " version;next}1' docs/changelog/index.rst > temp && mv temp docs/changelog/index.rst ``` -6. Восстановите файл `NEXT_RELEASE.rst` из резервной копии +1. Восстановите файл `NEXT_RELEASE.rst` из резервной копии ```bash mv "docs/changelog/temp_NEXT_RELEASE.rst" "docs/changelog/NEXT_RELEASE.rst" ``` -7. Зафиксируйте и отправьте изменения в ветку `develop` +1. Зафиксируйте и отправьте изменения в ветку `develop` ```bash git add . @@ -338,7 +355,7 @@ git commit -m "Prepare for release ${VERSION}" git push ``` -8. Объедините ветку `develop` с `master`, **БЕЗ** squashing +1. Слейте ветку `develop` в `master`, **БЕЗ** squashing ```bash git checkout master @@ -347,14 +364,14 @@ git merge develop git push ``` -9. Добавьте git tag к последнему коммиту в ветке `master` +1. Добавьте git tag к последнему коммиту в ветке `master` ```bash git tag "$VERSION" git push origin "$VERSION" ``` -10. Обновите версию в ветке `develop` **после релиза**: +1. Обновите версию в ветке `develop` **после релиза**: ```bash git checkout develop @@ -366,5 +383,3 @@ git add . git commit -m "Bump version" git push ``` - -[towncrier philosophy](https://towncrier.readthedocs.io/en/stable/#philosophy) diff --git a/mddocs/docs/ru/db_/index.md b/mddocs/docs/ru/db_/index.md new file mode 100644 index 000000000..494a026b1 --- /dev/null +++ b/mddocs/docs/ru/db_/index.md @@ -0,0 +1,11 @@ +# DB + +lslsv + +```{toctree} +:caption: DB classes +:maxdepth: 1 + +db_reader +db_writer +``` diff --git a/mddocs/docs/ru/db_/reader.md b/mddocs/docs/ru/db_/reader.md new file mode 100644 index 000000000..912e3d127 --- /dev/null +++ b/mddocs/docs/ru/db_/reader.md @@ -0,0 +1,22 @@ +# DB Reader + + +ыыыыы + +```{eval-rst} +.. currentmodule:: onetl.db.db_reader.db_reader +``` + +```{eval-rst} +.. autosummary:: + + DBReader + DBReader.run + DBReader.has_data + DBReader.raise_if_no_data +``` + +```{eval-rst} +.. autoclass:: DBReader + :members: run, has_data, raise_if_no_data +``` diff --git a/mddocs/docs/ru/db_/writer.md b/mddocs/docs/ru/db_/writer.md new file mode 100644 index 000000000..ea66d2834 --- /dev/null +++ b/mddocs/docs/ru/db_/writer.md @@ -0,0 +1,19 @@ +# DB Writer + + +бубуб +```{eval-rst} +.. currentmodule:: onetl.db.db_writer.db_writer +``` + +```{eval-rst} +.. autosummary:: + + DBWriter + DBWriter.run +``` + +```{eval-rst} +.. autoclass:: DBWriter + :members: run +``` diff --git a/mddocs/docs/ru/index.md b/mddocs/docs/ru/index.md new file mode 100644 index 000000000..367dcce11 --- /dev/null +++ b/mddocs/docs/ru/index.md @@ -0,0 +1,18 @@ +# onETL + +{{ repo_status_badge }} +{{ pypi_release_bage }} +{{ pypi_license_bage }} +{{ pypi_pyversion_bage }} +{{ pypi_downloads_bage }} + +{{ docs_status_badge }} +{{ ci_status_badge }} +{{ precommit_badge }} + + +{{ onetl_logo_wide }} + +----8<---- +../mddocs/docs/ru/snippet_0.md +----8<---- diff --git a/mddocs/ru/logging.md b/mddocs/docs/ru/logging.md similarity index 87% rename from mddocs/ru/logging.md rename to mddocs/docs/ru/logging.md index dae7a9d73..feb9a7d8c 100644 --- a/mddocs/ru/logging.md +++ b/mddocs/docs/ru/logging.md @@ -1,14 +1,10 @@ -(logging)= +# Логирование -# Logging +Логирование очень важно для понимания того, что происходит под капотом onETL. -Logging is quite important to understand what's going on under the hood of onETL. +Уровень логирования по умолчанию для интерпретаторов Python - `WARNING`, но большинство логов onETL находятся на уровне `INFO`, поэтому пользователи обычно мало что видят. -Default logging level for Python interpreters is `WARNING`, -but most of onETL logs are in `INFO` level, so users usually don't see much. - -To change logging level, there is a function {obj}`setup_logging ` -which should be called at the top of the script: +Чтобы изменить уровень логирования, есть функция [setup_logging](../logging/#onetl.log.setup_logging), которую следует вызывать в начале скрипта: ```python from onetl.log import setup_logging @@ -16,16 +12,15 @@ from other.lib import some, more, imports setup_logging() -# rest of code +# остальной код ... ``` -This changes both log level and log formatting to something like this: +Это изменяет как уровень логирования, так и формат логирования на что-то вроде этого: -```{eval-rst} -.. dropdown:: See logs +??? "Посмотреть логи" - .. code:: text + ```text 2024-04-12 10:12:10,834 [INFO ] MainThread: |onETL| Using IncrementalStrategy as a strategy 2024-04-12 10:12:10,835 [INFO ] MainThread: =================================== DBReader.run() starts =================================== @@ -135,37 +130,27 @@ This changes both log level and log formatting to something like this: 2024-04-12 11:06:07,397 [INFO ] MainThread: entity = 'source_source_schema.table', 2024-04-12 11:06:07,397 [INFO ] MainThread: expression = 'updated_at', 2024-04-12 11:06:07,397 [INFO ] MainThread: value = datetime.datetime(2024, 4, 12, 13, 12, 2, 123000), - 2024-04-12 11:06:07,397 [INFO ] MainThread: ) 2024-04-12 11:06:07,495 [INFO ] MainThread: |IncrementalStrategy| HWM has been saved -``` + ``` -Each step performed by onETL is extensively logged, which should help with debugging. +Каждый шаг, выполняемый onETL, тщательно логируется, что должно помочь с отладкой. -You can make logs even more verbose by changing level to `DEBUG`: +Вы можете сделать логи еще более подробными, изменив уровень на `DEBUG`: ```python from onetl.log import setup_logging setup_logging(level="DEBUG", enable_clients=True) -# rest of code +# остальной код ... ``` -This also changes log level for all underlying Python libraries, e.g. showing each HTTP request being made, and so on. +Это также изменяет уровень логирования для всех базовых библиотек Python, например, показывая каждый выполняемый HTTP-запрос и т. д. -```{eval-rst} -.. currentmodule:: onetl.log -``` - -```{eval-rst} -.. autofunction:: setup_logging -``` - -```{eval-rst} -.. autofunction:: setup_clients_logging -``` - -```{eval-rst} -.. autofunction:: set_default_logging_format -``` +::: onetl.log + options: + members: + - setup_logging + - setup_clients_logging + - set_default_logging_format diff --git a/mddocs/docs/ru/plugins.md b/mddocs/docs/ru/plugins.md new file mode 100644 index 000000000..afbd7a634 --- /dev/null +++ b/mddocs/docs/ru/plugins.md @@ -0,0 +1,140 @@ +# Плагины + +:octicons-versions-16: **добавлено в версии 0.6.0** + + +## Что такое плагины? + +### Термины + +- `Plugin` - Python пакет, который реализует дополнительную функциональность для onETL, например [hooks](../hooks/) +- `Plugin autoimport` - поведение onETL, которое позволяет автоматически импортировать этот пакет, если он содержит правильные метаданные (`entry_points`) + +### Функциональность + +Механизм плагинов позволяет: + +- Автоматически регистрировать [hooks](../hooks/), которые могут изменять поведение onETL +- Автоматически регистрировать новые классы, такие как тип HWM, хранилища HWM и т.д. + +### Ограничения + +В отличие от других проектов (таких как *Airflow 1.x*), плагины не внедряют импортированные классы или функции в пространство имен `onetl.*`. +Пользователи должны импортировать классы из пакета плагина **явно**, чтобы избежать конфликтов имен. + +## Как реализовать плагин? + +Создайте Python пакет `some-plugin` с файлом `some_plugin/setup.py`: + +```python +# some_plugin/setup.py +from setuptools import setup + +setup( + # если вы хотите импортировать что-то из onETL, добавьте это в список requirements + install_requires=["onetl"], + entry_points={ + # этот ключ включает функциональность автоимпорта плагинов + "onetl.plugins": [ + "some-plugin-name=some_plugin.module", # автоматически импортировать все содержимое модуля + "some-plugin-class=some_plugin.module.internals:MyClass", # импортировать определенный класс + "some-plugin-function=some_plugin.module.internals:my_function", # импортировать определенную функцию + ], + }, +) +``` + +См. [документацию setuptools для entry_points](https://setuptools.pypa.io/en/latest/userguide/entry_point.html) + +## Как импортируются плагины? + +- Пользователь должен установить пакет, реализующий плагин: + +```bash +pip install some-package +``` + +- Затем пользователь должен импортировать что-то из модуля `onetl` или его подмодулей: + +```python +import onetl +from onetl.connection import SomeConnection + +# и так далее +``` + +- Этот импорт автоматически выполняет что-то вроде: + +```python +import some_plugin.module +from some_plugin.module.internals import MyClass +from some_plugin.module.internals import my_function +``` + +Если конкретный модуль/класс/функция использует некоторые возможности регистрации onETL, например [`hook-decorator`](../hooks/hook.html#hook-decorator), он будет выполнен во время этого импорта. + +## Как включить/отключить плагины? + +:octicons-versions-16: **добавлено в версии 0.7.0** + +### Отключить/включить все плагины + +По умолчанию плагины включены. + +Чтобы отключить их, вы можете установить переменную окружения `ONETL_PLUGINS_ENABLED` в `false` ДО импорта onETL. Это отключит весь автоимпорт плагинов. + +Но пользователь по-прежнему сможет явно импортировать `some_plugin.module`, выполняя все декораторы и возможности регистрации onETL. + +### Отключить конкретный плагин (черный список) + +Если какой-то плагин не работает во время импорта, вы можете отключить его, установив переменную окружения `ONETL_PLUGINS_BLACKLIST=some-failing-plugin`. Несколько имен плагинов можно передать используя `,` в качестве разделителя. + +Опять же, эту переменную окружения следует установить ДО импорта onETL. + +### Отключить все плагины, кроме конкретного (белый список) + +Вы также можете отключить все плагины, кроме конкретного, установив переменную окружения +`ONETL_PLUGINS_WHITELIST=some-not-failing-plugin`. Несколько имен плагинов можно передать через `,` в качестве разделителя. + +Опять же, эту переменную окружения следует установить ДО импорта onETL. + +Если установлены обе переменные окружения, белый и черный списки, черный список имеет более высокий приоритет. + +## Как увидеть логи механизма плагинов? + +Регистрация плагинов выдает логи с уровнем `INFO`: + +```python +import logging + +logging.basicConfig(level=logging.INFO) +``` + +```text +INFO |onETL| Found 2 plugins +INFO |onETL| Loading plugin 'my-plugin' +INFO |onETL| Skipping plugin 'failing' because it is in a blacklist +``` + +Более подробные логи выдаются с уровнем `DEBUG`, чтобы сделать вывод менее многословным: + +```python +import logging + +logging.basicConfig(level=logging.DEBUG) +``` + +```text +DEBUG |onETL| Searching for plugins with group 'onetl.plugins' +DEBUG |Plugins| Plugins whitelist: [] +DEBUG |Plugins| Plugins blacklist: ['failing-plugin'] +INFO |Plugins| Found 2 plugins +INFO |onETL| Loading plugin (1/2): +DEBUG name: 'my-plugin' +DEBUG package: 'my-package' +DEBUG version: '0.1.0' +DEBUG importing: 'my_package.my_module:MyClass' +DEBUG |onETL| Successfully loaded plugin 'my-plugin' +DEBUG source: '/usr/lib/python3.11/site-packages/my_package/my_module/my_class.py' +INFO |onETL| Skipping plugin 'failing' because it is in a blacklist +``` diff --git a/mddocs/docs/ru/quickstart.md b/mddocs/docs/ru/quickstart.md new file mode 100644 index 000000000..06a7cb19c --- /dev/null +++ b/mddocs/docs/ru/quickstart.md @@ -0,0 +1,540 @@ +# onETL + +{{ repo_status_badge }} +{{ pypi_release_bage }} +{{ pypi_license_bage }} +{{ pypi_pyversion_bage }} +{{ pypi_downloads_bage }} + +{{ docs_status_badge }} +{{ ci_status_badge }} +{{ precommit_badge }} + + +{{ onetl_logo_wide }} + +----8<---- +../mddocs/docs/ru/snippet_0.md +----8<---- + + + +## Документация + +Смотрите на [ReadTheDocs](https://onetl.readthedocs.io/ru/latest/) + +## Как установить + + + +### Минимальная установка + + + +Базовый пакет `onetl` содержит: + +- `DBReader`, `DBWriter` и связанные с ними классы +- `FileDownloader`, `FileUploader`, `FileMover` и вспомогательные классы, такие как файловые фильтры и лимиты +- `FileDFReader`, `FileDFWriter` и дополняющие их классы, такие как, например, форматы файлов +- Стратегии чтения и классы HWM +- Поддержку плагинов + +Базовый пакет можно установить выполнив: + +```bash +pip install onetl +``` + +!!! warning + + Этот способ установки пакета НЕ включает в него какие-либо подключения. + Этот метод рекомендуется использовать в сторонних библиотеках, которые требуют установки ``onetl``, но не используют ее классы подключений. + + +### С подключениями к БД и возможностью чтения файловых данных в Spark DataFrame + + + +Все классы подключений к БД (`Clickhouse`, `Greenplum`, `Hive` и другие) также как и все классы представления файловых данных как DataFrame (`SparkHDFS`, `SparkLocalFS`, `SparkS3`) требуют установки Spark. + + + +Во-первых, необходимо установить JDK. Точная инструкция по установке зависит от вашей ОС, вот несколько примеров: + +```shell +yum install java-1.8.0-openjdk-devel # CentOS 7 | Spark 2 +dnf install java-11-openjdk-devel # CentOS 8 | Spark 3 +apt-get install openjdk-11-jdk # Debian-based | Spark 3 +``` + + + +#### Матрица совместимости + +| Spark | Python | Java | Scala | +| --------------------------------------------------------- | ---------- | ---------- | ----- | +| [2.3.x](https://spark.apache.org/docs/2.3.1/#downloading) | 3.7 only | 8 only | 2.11 | +| [2.4.x](https://spark.apache.org/docs/2.4.8/#downloading) | 3.7 only | 8 only | 2.11 | +| [3.2.x](https://spark.apache.org/docs/3.2.4/#downloading) | 3.7 - 3.10 | 8u201 - 11 | 2.12 | +| [3.3.x](https://spark.apache.org/docs/3.3.4/#downloading) | 3.7 - 3.12 | 8u201 - 17 | 2.12 | +| [3.4.x](https://spark.apache.org/docs/3.4.4/#downloading) | 3.7 - 3.12 | 8u362 - 20 | 2.12 | +| [3.5.x](https://spark.apache.org/docs/3.5.5/#downloading) | 3.8 - 3.13 | 8u371 - 20 | 2.12 | + + + +Затем необходимо установить PySpark, передав `spark` в `extras`: + +```bash +pip install onetl[spark] # установить последнюю версию PySpark +``` + +или установить PySpark явно: + +```bash +pip install onetl pyspark==3.5.5 # установить определенную версию PySpark +``` + +или внедрите PySpark в `sys.path` каким-либо другим способом ДО создания экземпляра класса. **В противном случае объект подключения не может быть создан.** + +### С файловыми подключениями + + + +Все классы файловых (но не *FileDF*) подключений (`FTP`, `SFTP`, `HDFS` и т.д.) требуют установки определенных Python клиентов. + +Каждый клиент можно установить явно, передав имя коннектора (в нижнем регистре) в `extras`: + +```bash +pip install onetl[ftp] # конкретный коннектор +pip install onetl[ftp,ftps,sftp,hdfs,s3,webdav,samba] # несколько коннекторов +``` + +Чтобы установить все файловые коннекторы сразу, вы можете передать `files` в `extras`: + +```bash +pip install onetl[files] +``` + +**В противном случае импорт класса завершится неудачей.** + +### С поддержкой Kerberos + + + +Многие экземпляры Hadoop развернуты с поддержкой Kerberos, поэтому для правильной работы некоторых соединений требуется дополнительная настройка. + +- `HDFS`
+ Использует [requests-kerberos](https://pypi.org/project/requests-kerberos/) и [GSSApi](https://pypi.org/project/gssapi/) для аутентификации. Он также выполняет `kinit` для создания тикета Kerberos. +- `Hive` и `SparkHDFS`
+ требуют наличия тикета Kerberos перед созданием сеанса Spark. + +Таким образом, необходимо установить пакеты ОС содержащие: + +- Библиотеки `krb5` +- Заголовочные файлы для `krb5` +- `gcc` или другой компилятор для C + +Точная инструкция по установке зависит от вашей ОС, вот несколько примеров: + +```bash +apt install libkrb5-dev krb5-user gcc # Debian-based +dnf install krb5-devel krb5-libs krb5-workstation gcc # CentOS, OracleLinux +``` + +Также вы должны передать `kerberos` в `extras` для установки необходимых пакетов Python: + +```bash +pip install onetl[kerberos] +``` + +### Полный пакет + + + +Чтобы установить все коннекторы и зависимости, вы можете передать `all` в `extras`: + +```bash +pip install onetl[all] + +# это то же самое, что и +pip install onetl[spark,files,kerberos] +``` + +!!! warning + + Этот метод потребляет много дискового пространства и требует установки библиотек Java и Kerberos в вашей ОС. + + + + +## Быстрый старт + +### MSSQL → Hive + +Чтение данных из MSSQL, преобразование и запись в Hive. + +```bash +# установить onETL и PySpark +pip install onetl[spark] +``` + +```python +# Импорт pyspark для инициализации SparkSession +from pyspark.sql import SparkSession + +# Импорт функции для настройки логирования onETL +from onetl.log import setup_logging + +# Импорт необходимых подключений +from onetl.connection import MSSQL, Hive + +# Импорт классов onETL для чтения и записи данных +from onetl.db import DBReader, DBWriter + +# Изменение уровня логирования на INFO, а также настройка формата логирования и обработчика +setup_logging() + +# Инициализация SparkSession с загрузкой драйвера MSSQL +maven_packages = MSSQL.get_packages() +spark = ( + SparkSession.builder.appName("spark_app_onetl_demo") + .config("spark.jars.packages", ",".join(maven_packages)) + .enableHiveSupport() # for Hive + .getOrCreate() +) + +# Инициализация подключения к MSSQL и проверка его доступности +mssql = MSSQL( + host="mssqldb.demo.com", + user="onetl", + password="onetl", + database="Telecom", + spark=spark, + # These options are passed to MSSQL JDBC Driver: + extra={"applicationIntent": "ReadOnly"}, +).check() + +# >>> INFO:|MSSQL| Connection is available + +# Инициализация DBReader +reader = DBReader( + connection=mssql, + source="dbo.demo_table", + columns=["on", "etl"], + # Установка опций чтения MSSQL: + options=MSSQL.ReadOptions(fetchsize=10000), +) + +# Проверка, что таблица содержит данные +reader.raise_if_no_data() + +# Чтение данных в DataFrame +df = reader.run() +df.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) + +# Применение любых трансформаций PySpark +from pyspark.sql.functions import lit + +df_to_write = df.withColumn("engine", lit("onetl")) +df_to_write.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) +# |-- engine: string (nullable = false) + +# Инициализация подключения Hive (не требует загрузки драйвера Hive в сессию Spark) +hive = Hive(cluster="rnd-dwh", spark=spark) + +# Инициализация DBWriter +db_writer = DBWriter( + connection=hive, + target="dl_sb.demo_table", + # Установка опций записи Hive: + options=Hive.WriteOptions(if_exists="replace_entire_table"), +) + +# Запись данных из DataFrame в Hive +db_writer.run(df_to_write) + +# Успех! +``` + +### SFTP → HDFS + +Получение файлов из SFTP и загрузка их в HDFS. + +```bash +# установить onETL с клиентами SFTP и HDFS, и поддержкой Kerberos +pip install onetl[hdfs,sftp,kerberos] +``` + +```python +# Импорт функции для настройки логирования onETL +from onetl.log import setup_logging + +# Импорт необходимых подключений +from onetl.connection import SFTP, HDFS + +# Импорт классов onETL для скачивания и загрузки файлов +from onetl.file import FileDownloader, FileUploader + +# Импорт вспомогательных классов filter & limit +from onetl.file.filter import Glob, ExcludeDir +from onetl.file.limit import MaxFilesCount + +# Изменение уровня логирования на INFO, а также настройка формата логирования и обработчика +setup_logging() + +# Инициализация подключения к SFTP и его проверка +sftp = SFTP( + host="sftp.test.com", + user="someuser", + password="somepassword", +).check() + +# >>> INFO:|SFTP| Connection is available + +# Инициализация объекта скачивания файлов +file_downloader = FileDownloader( + connection=sftp, + source_path="/remote/tests/Report", # path on SFTP + local_path="/local/onetl/Report", # local fs path + filters=[ + # скачивать только если имена файлов совпадают с glob + Glob("*.csv"), + # исключить файлы из директории + ExcludeDir("/remote/tests/Report/exclude_dir/"), + ], + limits=[ + # скачивать не более 1000 за запуск + MaxFilesCount(1000), + ], + options=FileDownloader.Options( + # Удалить файлы из SFTP после успешной загрузки + delete_source=True, + # пометить файлы ошибкой, если в локальном пути существует файл с таким же именем + if_exists="error", + ), +) + +# Скачать файлы на локальную файловую систему +download_result = downloader.run() + +# Метод run возвращает объект DownloadResult, +# который содержит коллекцию полученных файлов разделенную на 4 категории: +# successful - успешно скачанные файлы +# failed - файлы, которые не удалось скачать +# ignored - файлы, которые были игнорированы по фильтрам +# missing - файлы, которые отсутствовали на SFTP +download_result + +# DownloadResult( +# successful=[ +# LocalPath('/local/onetl/Report/file_1.json'), +# LocalPath('/local/onetl/Report/file_2.json'), +# ], +# failed=[FailedRemoteFile('/remote/onetl/Report/file_3.json')], +# ignored=[RemoteFile('/remote/onetl/Report/file_4.json')], +# missing=[], +# ) + +# Выбросить исключение если есть файлы с ошибкой или на удаленном сервере не было файлов +download_result.raise_if_failed() or download_result.raise_if_empty() + +# Выполняйте любые необходимые вам трансформации с файлами: переименуйте, удалите строку заголовков в csv... +renamed_files = my_rename_function(download_result.success) + +# function removed "_" from file names +# [ +# LocalPath('/home/onetl/Report/file1.json'), +# LocalPath('/home/onetl/Report/file2.json'), +# ] + +# Инициализировать подключение к HDFS +hdfs = HDFS( + host="my.name.node", + user="someuser", + password="somepassword", # or keytab +) + +# Инициализировать объект загрузки файлов +file_uploader = FileUploader( + connection=hdfs, + target_path="/user/onetl/Report/", # hdfs path +) + +# Загрузить файлы из локальной ФС в HDFS +upload_result = file_uploader.run(renamed_files) + +# Метод run вернет объект UploadResult, +# который содержит коллекцию полученных файлов разделенную на 4 категории: +# successful - успешно скачанные файлы +# failed - файлы, которые не удалось скачать +# ignored - файлы, которые были игнорированы по фильтрам +# missing - файлы, которые отсутствовали на SFTP +upload_result + +# UploadResult( +# successful=[RemoteFile('/user/onetl/Report/file1.json')], +# failed=[FailedLocalFile('/local/onetl/Report/file2.json')], +# ignored=[], +# missing=[], +# ) + +# Выбросить исключение если есть файлы с ошибкой или на локальной ФС не было файлов +upload_result.raise_if_failed() or upload_result.raise_if_empty() or upload_result.raise_if_missing() + +# Успех! +``` + +### S3 → Postgres + +Чтение файлов непосредственно из пути S3, преобразование их в DataFrame, преобразование его и затем запись в базу данных. + +```bash +# установить onETL и PySpark +pip install onetl[spark] +``` + +```python +# Импорт pyspark для инициализации SparkSession +from pyspark.sql import SparkSession + +# Импорт функции для настройки логирования onETL +from onetl.log import setup_logging + +# Импорт необходимых подключений +from onetl.connection import Postgres, SparkS3 + +# Импорт классов onETL необходимых для чтения файлов +from onetl.file import FileDFReader +from onetl.file.format import CSV + +# импорт классов onETL для записи данных +from onetl.db import DBWriter + +# Изменение уровня логирования на INFO, а также настройка формата логирования и обработчика +setup_logging() + +# Инициализация SparkSession с включение библиотеки Hadoop AWS и JDBC драйвера Postgres +maven_packages = SparkS3.get_packages(spark_version="3.5.5") | Postgres.get_packages() +exclude_packages = SparkS3.get_exclude_packages() +spark = ( + SparkSession.builder.appName("spark_app_onetl_demo") + .config("spark.jars.packages", ",".join(maven_packages)) + .config("spark.jars.excludes", ",".join(exclude_packages)) + .getOrCreate() +) + +# Инициализация подключения к S3 и его проверка +spark_s3 = SparkS3( + host="s3.test.com", + protocol="https", + bucket="my-bucket", + access_key="somekey", + secret_key="somesecret", + # Access bucket as s3.test.com/my-bucket + extra={"path.style.access": True}, + spark=spark, +).check() + +# >>> INFO:|SparkS3| Connection is available + +# Опишите формат файла и опции его парсинга +csv = CSV( + delimiter=";", + header=True, + encoding="utf-8", +) + +# Опишите структуру данных в файле для загрузки их в DataFrame +from pyspark.sql.types import ( + DateType, + DoubleType, + IntegerType, + StringType, + StructField, + StructType, + TimestampType, +) + +df_schema = StructType( + [ + StructField("id", IntegerType()), + StructField("phone_number", StringType()), + StructField("region", StringType()), + StructField("birth_date", DateType()), + StructField("registered_at", TimestampType()), + StructField("account_balance", DoubleType()), + ], +) + +# Инициализация объекта чтения из файлов в DataFrame +reader = FileDFReader( + connection=spark_s3, + source_path="/remote/tests/Report", # путь на S3, по которому расположены файлы *.csv + format=csv, # формат файлов со специфическими опциями разбора + df_schema=df_schema, # колонки и их типы +) + +# Прочитать данные из файла на S3 в Spark DataFrame +df = reader.run() + +# Проверить что схема DataFrame такая как ожидается +df.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) + +# Применить любые трансформации PySpark +from pyspark.sql.functions import lit + +df_to_write = df.withColumn("engine", lit("onetl")) +df_to_write.printSchema() +# root +# |-- id: integer (nullable = true) +# |-- phone_number: string (nullable = true) +# |-- region: string (nullable = true) +# |-- birth_date: date (nullable = true) +# |-- registered_at: timestamp (nullable = true) +# |-- account_balance: double (nullable = true) +# |-- engine: string (nullable = false) + +# Инициализировать подключение к Postgres +postgres = Postgres( + host="192.169.11.23", + user="onetl", + password="somepassword", + database="mydb", + spark=spark, +) + +# Инициализировать объект DBWriter +db_writer = DBWriter( + connection=postgres, + # write to specific table + target="public.my_table", + # with some writing options + options=Postgres.WriteOptions(if_exists="append"), +) + +# Записать DataFrame в таблицу Postgres +db_writer.run(df_to_write) + +# Успех! +``` diff --git a/mddocs/docs/ru/security.md b/mddocs/docs/ru/security.md new file mode 100644 index 000000000..ddd24f99f --- /dev/null +++ b/mddocs/docs/ru/security.md @@ -0,0 +1,25 @@ +# Безопасность + +## Поддерживаемые версии Python + +3.7 или выше + +## Рекомендации по безопасности при разработке продукта + +1. Обновите зависимости до последней стабильной версии +2. Создайте SBOM для проекта +3. Выполните SAST (Static Application Security Testing), где это возможно + +## Требования к безопасности при разработке продукта + +1. Отсутствие бинарных файлов в репозитории +2. Отсутствие паролей, ключей, токенов доступа в исходном коде +3. Отсутствие уязвимостей уровня "Critical" и/или "High" в предоставленном исходном коде + +## Отчеты об уязвимостях + +Пожалуйста, используйте электронную почту [mailto:onetools@mts.ru](mailto:onetools@mts.ru) для сообщения о проблемах безопасности или о чем-либо, что может повлечь за собой какие-либо последствия для безопасности. + +Пожалуйста, избегайте любого публичного раскрытия (включая регистрацию проблем) по крайней мере до тех пор, пока она не будет исправлена. + +Заранее благодарим за понимание. diff --git a/mddocs/docs/ru/snippet_0.md b/mddocs/docs/ru/snippet_0.md new file mode 100644 index 000000000..addf2379c --- /dev/null +++ b/mddocs/docs/ru/snippet_0.md @@ -0,0 +1,44 @@ +## Что такое onETL? + +Python ETL/ELT библиотека, основанная на [Apache Spark](https://spark.apache.org/) и других инструментах с открытым исходным кодом. + +## Цели + +- Предложить унифицированные классы для извлечения (**E**) и загрузки данных (**L**) при работе с различными хранилищами. +- Обеспечить возможность использования [Spark DataFrame API](https://spark.apache.org/docs/latest/api/python/reference/pyspark.sql/api/pyspark.sql.DataFrame.html) для выполнения преобразований (**T**) (в терминах *ETL*). +- Предоставить прямой доступ к базе данных, позволяющий выполнять SQL-запросы, а также DDL, DML и вызывать функции/процедуры. Эта функциональность предназначена для построения *ELT* конвейеров. +- Обеспечить поддержку различных [стратегий чтения](../strategy/) для инкрементной и пакетной выборки данных. +- Предоставить [хуки](../hooks/) и [плагины](../plugins) для изменения поведения внутренних классов. + +## Не цели + +- onETL не является заменой Spark. Она просто предоставляет дополнительные функциональные возможности, которых нет в Spark, и улучшает UX для конечных пользователей. +- onETL не является фреймворком, поскольку не имеет требований к структуре проекта, именованию, способу запуска ETL/ELT процессов, конфигурации и т.д. Все это должно быть реализовано в каком-то другом инструменте. +- onETL намеренно разрабатывается без какой-либо интеграции с программным обеспечением для планирования, таким как, например, Apache Airflow. Все интеграции должны быть реализованы как отдельные инструменты. +- Только пакетные процессы обработки данных, без их потоковой передачи. Для потоковой передачи используйте [Apache Flink](https://flink.apache.org/). + +## Требования + +- **Python** 3.7 - 3.13 +- PySpark 2.3.x - 3.5.x (зависит от используемого коннектора) +- Java 8+ (требуется Spark, см. ниже) +- Kerberos libs & GCC (требуется коннекторами `Hive`, `HDFS` и `SparkHDFS`) + +## Поддерживаемые хранилища + + +| Тип | Хранилище | На базе | +|--------------------|--------------|-------------------------------------------------------------------------------------------------------------------------| +| База данных {: rowspan=5} | Clickhouse
MSSQL
MySQL
Postgres
Oracle
Teradata |

Apache Spark [JDBC Data Source](https://spark.apache.org/docs/latest/sql-data-sources-jdbc.html) | +| Hive | Apache Spark [Hive integration](https://spark.apache.org/docs/latest/sql-data-sources-hive-tables.html) | +| Kafka | Apache Spark [Kafka integration](https://spark.apache.org/docs/latest/structured-streaming-kafka-integration.html) | +| Greenplum | VMware [Greenplum Spark connector](https://docs.vmware.com/en/VMware-Greenplum-Connector-for-Apache-Spark/index.html) | +| MongoDB | [MongoDB Spark connector](https://www.mongodb.com/docs/spark-connector/current) | +| Файл {: rowspan=6} | HDFS | [HDFS Python client](https://pypi.org/project/hdfs/) | +| S3 | [minio-py client](https://pypi.org/project/minio/) | +| SFTP | [Paramiko library](https://pypi.org/project/paramiko/) | +| FTP
FTPS | [FTPUtil library](https://pypi.org/project/ftputil/) | +| WebDAV | [WebdavClient3 library](https://pypi.org/project/webdavclient3/) | +| Samba | [pysmb library](https://pypi.org/project/pysmb/) | +| Файлы как DataFrame {: rowspan=2} | SparkLocalFS
SparkHDFS | Apache Spark [File Data Source](https://spark.apache.org/docs/latest/sql-data-sources-generic-options.html) | +| SparkS3 | Библиотека [Hadoop AWS](https://hadoop.apache.org/docs/current3/hadoop-aws/tools/hadoop-aws/index.html) | diff --git a/mddocs/docs/ru/test.md b/mddocs/docs/ru/test.md new file mode 100644 index 000000000..9407d373b --- /dev/null +++ b/mddocs/docs/ru/test.md @@ -0,0 +1,5 @@ +# Check includes + +----8<---- +../mddocs/docs/ru/quickstart.md +----8<---- \ No newline at end of file diff --git a/mddocs/docs/security.md b/mddocs/docs/security.md deleted file mode 100644 index 05a7bef5a..000000000 --- a/mddocs/docs/security.md +++ /dev/null @@ -1,3 +0,0 @@ -```{eval-rst} -.. include:: ../SECURITY.rst -``` diff --git a/mddocs/generated/en/404.html b/mddocs/generated/en/404.html new file mode 100644 index 000000000..d346b84e9 --- /dev/null +++ b/mddocs/generated/en/404.html @@ -0,0 +1,1244 @@ + + + + + + + + + + + + + + + + + + + onETL Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ +
+
+ +
+ + + + + + +
+ + +
+ +
+ + + + + + +
+
+ + + +
+
+
+ + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + + + + +
+ +
+ + + +
+
+
+
+ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mddocs/generated/en/assets/_mkdocstrings.css b/mddocs/generated/en/assets/_mkdocstrings.css new file mode 100644 index 000000000..850e5927e --- /dev/null +++ b/mddocs/generated/en/assets/_mkdocstrings.css @@ -0,0 +1,181 @@ + +/* Avoid breaking parameter names, etc. in table cells. */ +.doc-contents td code { + word-break: normal !important; +} + +/* No line break before first paragraph of descriptions. */ +.doc-md-description, +.doc-md-description>p:first-child { + display: inline; +} + +/* No text transformation from Material for MkDocs for H5 headings. */ +.md-typeset h5 .doc-object-name { + text-transform: none; +} + +/* Max width for docstring sections tables. */ +.doc .md-typeset__table, +.doc .md-typeset__table table { + display: table !important; + width: 100%; +} + +.doc .md-typeset__table tr { + display: table-row; +} + +/* Defaults in Spacy table style. */ +.doc-param-default { + float: right; +} + +/* Parameter headings must be inline, not blocks. */ +.doc-heading-parameter { + display: inline; +} + +/* Default font size for parameter headings. */ +.md-typeset .doc-heading-parameter { + font-size: inherit; +} + +/* Prefer space on the right, not the left of parameter permalinks. */ +.doc-heading-parameter .headerlink { + margin-left: 0 !important; + margin-right: 0.2rem; +} + +/* Backward-compatibility: docstring section titles in bold. */ +.doc-section-title { + font-weight: bold; +} + +/* Backlinks crumb separator. */ +.doc-backlink-crumb { + display: inline-flex; + gap: .2rem; + white-space: nowrap; + align-items: center; + vertical-align: middle; +} +.doc-backlink-crumb:not(:first-child)::before { + background-color: var(--md-default-fg-color--lighter); + content: ""; + display: inline; + height: 1rem; + --md-path-icon: url('data:image/svg+xml;charset=utf-8,'); + -webkit-mask-image: var(--md-path-icon); + mask-image: var(--md-path-icon); + width: 1rem; +} +.doc-backlink-crumb.last { + font-weight: bold; +} + +/* Symbols in Navigation and ToC. */ +:root, :host, +[data-md-color-scheme="default"] { + --doc-symbol-parameter-fg-color: #df50af; + --doc-symbol-attribute-fg-color: #953800; + --doc-symbol-function-fg-color: #8250df; + --doc-symbol-method-fg-color: #8250df; + --doc-symbol-class-fg-color: #0550ae; + --doc-symbol-module-fg-color: #5cad0f; + + --doc-symbol-parameter-bg-color: #df50af1a; + --doc-symbol-attribute-bg-color: #9538001a; + --doc-symbol-function-bg-color: #8250df1a; + --doc-symbol-method-bg-color: #8250df1a; + --doc-symbol-class-bg-color: #0550ae1a; + --doc-symbol-module-bg-color: #5cad0f1a; +} + +[data-md-color-scheme="slate"] { + --doc-symbol-parameter-fg-color: #ffa8cc; + --doc-symbol-attribute-fg-color: #ffa657; + --doc-symbol-function-fg-color: #d2a8ff; + --doc-symbol-method-fg-color: #d2a8ff; + --doc-symbol-class-fg-color: #79c0ff; + --doc-symbol-module-fg-color: #baff79; + + --doc-symbol-parameter-bg-color: #ffa8cc1a; + --doc-symbol-attribute-bg-color: #ffa6571a; + --doc-symbol-function-bg-color: #d2a8ff1a; + --doc-symbol-method-bg-color: #d2a8ff1a; + --doc-symbol-class-bg-color: #79c0ff1a; + --doc-symbol-module-bg-color: #baff791a; +} + +code.doc-symbol { + border-radius: .1rem; + font-size: .85em; + padding: 0 .3em; + font-weight: bold; +} + +code.doc-symbol-parameter, +a code.doc-symbol-parameter { + color: var(--doc-symbol-parameter-fg-color); + background-color: var(--doc-symbol-parameter-bg-color); +} + +code.doc-symbol-parameter::after { + content: "param"; +} + +code.doc-symbol-attribute, +a code.doc-symbol-attribute { + color: var(--doc-symbol-attribute-fg-color); + background-color: var(--doc-symbol-attribute-bg-color); +} + +code.doc-symbol-attribute::after { + content: "attr"; +} + +code.doc-symbol-function, +a code.doc-symbol-function { + color: var(--doc-symbol-function-fg-color); + background-color: var(--doc-symbol-function-bg-color); +} + +code.doc-symbol-function::after { + content: "func"; +} + +code.doc-symbol-method, +a code.doc-symbol-method { + color: var(--doc-symbol-method-fg-color); + background-color: var(--doc-symbol-method-bg-color); +} + +code.doc-symbol-method::after { + content: "meth"; +} + +code.doc-symbol-class, +a code.doc-symbol-class { + color: var(--doc-symbol-class-fg-color); + background-color: var(--doc-symbol-class-bg-color); +} + +code.doc-symbol-class::after { + content: "class"; +} + +code.doc-symbol-module, +a code.doc-symbol-module { + color: var(--doc-symbol-module-fg-color); + background-color: var(--doc-symbol-module-bg-color); +} + +code.doc-symbol-module::after { + content: "mod"; +} + +.doc-signature .autorefs { + color: inherit; + border-bottom: 1px dotted currentcolor; +} diff --git a/mddocs/generated/en/assets/images/favicon.png b/mddocs/generated/en/assets/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..1cf13b9f9d978896599290a74f77d5dbe7d1655c GIT binary patch literal 1870 zcmV-U2eJ5xP)Gc)JR9QMau)O=X#!i9;T z37kk-upj^(fsR36MHs_+1RCI)NNu9}lD0S{B^g8PN?Ww(5|~L#Ng*g{WsqleV}|#l zz8@ri&cTzw_h33bHI+12+kK6WN$h#n5cD8OQt`5kw6p~9H3()bUQ8OS4Q4HTQ=1Ol z_JAocz`fLbT2^{`8n~UAo=#AUOf=SOq4pYkt;XbC&f#7lb$*7=$na!mWCQ`dBQsO0 zLFBSPj*N?#u5&pf2t4XjEGH|=pPQ8xh7tpx;US5Cx_Ju;!O`ya-yF`)b%TEt5>eP1ZX~}sjjA%FJF?h7cX8=b!DZl<6%Cv z*G0uvvU+vmnpLZ2paivG-(cd*y3$hCIcsZcYOGh{$&)A6*XX&kXZd3G8m)G$Zz-LV z^GF3VAW^Mdv!)4OM8EgqRiz~*Cji;uzl2uC9^=8I84vNp;ltJ|q-*uQwGp2ma6cY7 z;`%`!9UXO@fr&Ebapfs34OmS9^u6$)bJxrucutf>`dKPKT%%*d3XlFVKunp9 zasduxjrjs>f8V=D|J=XNZp;_Zy^WgQ$9WDjgY=z@stwiEBm9u5*|34&1Na8BMjjgf3+SHcr`5~>oz1Y?SW^=K z^bTyO6>Gar#P_W2gEMwq)ot3; zREHn~U&Dp0l6YT0&k-wLwYjb?5zGK`W6S2v+K>AM(95m2C20L|3m~rN8dprPr@t)5lsk9Hu*W z?pS990s;Ez=+Rj{x7p``4>+c0G5^pYnB1^!TL=(?HLHZ+HicG{~4F1d^5Awl_2!1jICM-!9eoLhbbT^;yHcefyTAaqRcY zmuctDopPT!%k+}x%lZRKnzykr2}}XfG_ne?nRQO~?%hkzo;@RN{P6o`&mMUWBYMTe z6i8ChtjX&gXl`nvrU>jah)2iNM%JdjqoaeaU%yVn!^70x-flljp6Q5tK}5}&X8&&G zX3fpb3E(!rH=zVI_9Gjl45w@{(ITqngWFe7@9{mX;tO25Z_8 zQHEpI+FkTU#4xu>RkN>b3Tnc3UpWzPXWm#o55GKF09j^Mh~)K7{QqbO_~(@CVq! zS<8954|P8mXN2MRs86xZ&Q4EfM@JB94b=(YGuk)s&^jiSF=t3*oNK3`rD{H`yQ?d; ztE=laAUoZx5?RC8*WKOj`%LXEkgDd>&^Q4M^z`%u0rg-It=hLCVsq!Z%^6eB-OvOT zFZ28TN&cRmgU}Elrnk43)!>Z1FCPL2K$7}gwzIc48NX}#!A1BpJP?#v5wkNprhV** z?Cpalt1oH&{r!o3eSKc&ap)iz2BTn_VV`4>9M^b3;(YY}4>#ML6{~(4mH+?%07*qo IM6N<$f(jP3KmY&$ literal 0 HcmV?d00001 diff --git a/mddocs/docs/_static/icon.svg b/mddocs/generated/en/assets/images/icon.svg similarity index 100% rename from mddocs/docs/_static/icon.svg rename to mddocs/generated/en/assets/images/icon.svg diff --git a/mddocs/docs/_static/logo.svg b/mddocs/generated/en/assets/images/logo.svg similarity index 100% rename from mddocs/docs/_static/logo.svg rename to mddocs/generated/en/assets/images/logo.svg diff --git a/mddocs/docs/_static/logo_wide.svg b/mddocs/generated/en/assets/images/logo_wide.svg similarity index 100% rename from mddocs/docs/_static/logo_wide.svg rename to mddocs/generated/en/assets/images/logo_wide.svg diff --git a/mddocs/generated/en/assets/javascripts/bundle.13a4f30d.min.js b/mddocs/generated/en/assets/javascripts/bundle.13a4f30d.min.js new file mode 100644 index 000000000..c31fa1ac0 --- /dev/null +++ b/mddocs/generated/en/assets/javascripts/bundle.13a4f30d.min.js @@ -0,0 +1,16 @@ +"use strict";(()=>{var Wi=Object.create;var gr=Object.defineProperty;var Vi=Object.getOwnPropertyDescriptor;var Di=Object.getOwnPropertyNames,Vt=Object.getOwnPropertySymbols,zi=Object.getPrototypeOf,yr=Object.prototype.hasOwnProperty,ao=Object.prototype.propertyIsEnumerable;var io=(e,t,r)=>t in e?gr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,$=(e,t)=>{for(var r in t||(t={}))yr.call(t,r)&&io(e,r,t[r]);if(Vt)for(var r of Vt(t))ao.call(t,r)&&io(e,r,t[r]);return e};var so=(e,t)=>{var r={};for(var o in e)yr.call(e,o)&&t.indexOf(o)<0&&(r[o]=e[o]);if(e!=null&&Vt)for(var o of Vt(e))t.indexOf(o)<0&&ao.call(e,o)&&(r[o]=e[o]);return r};var xr=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Ni=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of Di(t))!yr.call(e,n)&&n!==r&&gr(e,n,{get:()=>t[n],enumerable:!(o=Vi(t,n))||o.enumerable});return e};var Lt=(e,t,r)=>(r=e!=null?Wi(zi(e)):{},Ni(t||!e||!e.__esModule?gr(r,"default",{value:e,enumerable:!0}):r,e));var co=(e,t,r)=>new Promise((o,n)=>{var i=p=>{try{s(r.next(p))}catch(c){n(c)}},a=p=>{try{s(r.throw(p))}catch(c){n(c)}},s=p=>p.done?o(p.value):Promise.resolve(p.value).then(i,a);s((r=r.apply(e,t)).next())});var lo=xr((Er,po)=>{(function(e,t){typeof Er=="object"&&typeof po!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(Er,function(){"use strict";function e(r){var o=!0,n=!1,i=null,a={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function s(k){return!!(k&&k!==document&&k.nodeName!=="HTML"&&k.nodeName!=="BODY"&&"classList"in k&&"contains"in k.classList)}function p(k){var ft=k.type,qe=k.tagName;return!!(qe==="INPUT"&&a[ft]&&!k.readOnly||qe==="TEXTAREA"&&!k.readOnly||k.isContentEditable)}function c(k){k.classList.contains("focus-visible")||(k.classList.add("focus-visible"),k.setAttribute("data-focus-visible-added",""))}function l(k){k.hasAttribute("data-focus-visible-added")&&(k.classList.remove("focus-visible"),k.removeAttribute("data-focus-visible-added"))}function f(k){k.metaKey||k.altKey||k.ctrlKey||(s(r.activeElement)&&c(r.activeElement),o=!0)}function u(k){o=!1}function d(k){s(k.target)&&(o||p(k.target))&&c(k.target)}function y(k){s(k.target)&&(k.target.classList.contains("focus-visible")||k.target.hasAttribute("data-focus-visible-added"))&&(n=!0,window.clearTimeout(i),i=window.setTimeout(function(){n=!1},100),l(k.target))}function L(k){document.visibilityState==="hidden"&&(n&&(o=!0),X())}function X(){document.addEventListener("mousemove",J),document.addEventListener("mousedown",J),document.addEventListener("mouseup",J),document.addEventListener("pointermove",J),document.addEventListener("pointerdown",J),document.addEventListener("pointerup",J),document.addEventListener("touchmove",J),document.addEventListener("touchstart",J),document.addEventListener("touchend",J)}function ee(){document.removeEventListener("mousemove",J),document.removeEventListener("mousedown",J),document.removeEventListener("mouseup",J),document.removeEventListener("pointermove",J),document.removeEventListener("pointerdown",J),document.removeEventListener("pointerup",J),document.removeEventListener("touchmove",J),document.removeEventListener("touchstart",J),document.removeEventListener("touchend",J)}function J(k){k.target.nodeName&&k.target.nodeName.toLowerCase()==="html"||(o=!1,ee())}document.addEventListener("keydown",f,!0),document.addEventListener("mousedown",u,!0),document.addEventListener("pointerdown",u,!0),document.addEventListener("touchstart",u,!0),document.addEventListener("visibilitychange",L,!0),X(),r.addEventListener("focus",d,!0),r.addEventListener("blur",y,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var qr=xr((dy,On)=>{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var $a=/["'&<>]/;On.exports=Pa;function Pa(e){var t=""+e,r=$a.exec(t);if(!r)return t;var o,n="",i=0,a=0;for(i=r.index;i{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Rt=="object"&&typeof Yr=="object"?Yr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Rt=="object"?Rt.ClipboardJS=r():t.ClipboardJS=r()})(Rt,function(){return function(){var e={686:function(o,n,i){"use strict";i.d(n,{default:function(){return Ui}});var a=i(279),s=i.n(a),p=i(370),c=i.n(p),l=i(817),f=i.n(l);function u(D){try{return document.execCommand(D)}catch(A){return!1}}var d=function(A){var M=f()(A);return u("cut"),M},y=d;function L(D){var A=document.documentElement.getAttribute("dir")==="rtl",M=document.createElement("textarea");M.style.fontSize="12pt",M.style.border="0",M.style.padding="0",M.style.margin="0",M.style.position="absolute",M.style[A?"right":"left"]="-9999px";var F=window.pageYOffset||document.documentElement.scrollTop;return M.style.top="".concat(F,"px"),M.setAttribute("readonly",""),M.value=D,M}var X=function(A,M){var F=L(A);M.container.appendChild(F);var V=f()(F);return u("copy"),F.remove(),V},ee=function(A){var M=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},F="";return typeof A=="string"?F=X(A,M):A instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(A==null?void 0:A.type)?F=X(A.value,M):(F=f()(A),u("copy")),F},J=ee;function k(D){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?k=function(M){return typeof M}:k=function(M){return M&&typeof Symbol=="function"&&M.constructor===Symbol&&M!==Symbol.prototype?"symbol":typeof M},k(D)}var ft=function(){var A=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},M=A.action,F=M===void 0?"copy":M,V=A.container,Y=A.target,$e=A.text;if(F!=="copy"&&F!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(Y!==void 0)if(Y&&k(Y)==="object"&&Y.nodeType===1){if(F==="copy"&&Y.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(F==="cut"&&(Y.hasAttribute("readonly")||Y.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if($e)return J($e,{container:V});if(Y)return F==="cut"?y(Y):J(Y,{container:V})},qe=ft;function Fe(D){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?Fe=function(M){return typeof M}:Fe=function(M){return M&&typeof Symbol=="function"&&M.constructor===Symbol&&M!==Symbol.prototype?"symbol":typeof M},Fe(D)}function ki(D,A){if(!(D instanceof A))throw new TypeError("Cannot call a class as a function")}function no(D,A){for(var M=0;M0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof V.action=="function"?V.action:this.defaultAction,this.target=typeof V.target=="function"?V.target:this.defaultTarget,this.text=typeof V.text=="function"?V.text:this.defaultText,this.container=Fe(V.container)==="object"?V.container:document.body}},{key:"listenClick",value:function(V){var Y=this;this.listener=c()(V,"click",function($e){return Y.onClick($e)})}},{key:"onClick",value:function(V){var Y=V.delegateTarget||V.currentTarget,$e=this.action(Y)||"copy",Wt=qe({action:$e,container:this.container,target:this.target(Y),text:this.text(Y)});this.emit(Wt?"success":"error",{action:$e,text:Wt,trigger:Y,clearSelection:function(){Y&&Y.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(V){return vr("action",V)}},{key:"defaultTarget",value:function(V){var Y=vr("target",V);if(Y)return document.querySelector(Y)}},{key:"defaultText",value:function(V){return vr("text",V)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(V){var Y=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return J(V,Y)}},{key:"cut",value:function(V){return y(V)}},{key:"isSupported",value:function(){var V=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],Y=typeof V=="string"?[V]:V,$e=!!document.queryCommandSupported;return Y.forEach(function(Wt){$e=$e&&!!document.queryCommandSupported(Wt)}),$e}}]),M}(s()),Ui=Fi},828:function(o){var n=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function a(s,p){for(;s&&s.nodeType!==n;){if(typeof s.matches=="function"&&s.matches(p))return s;s=s.parentNode}}o.exports=a},438:function(o,n,i){var a=i(828);function s(l,f,u,d,y){var L=c.apply(this,arguments);return l.addEventListener(u,L,y),{destroy:function(){l.removeEventListener(u,L,y)}}}function p(l,f,u,d,y){return typeof l.addEventListener=="function"?s.apply(null,arguments):typeof u=="function"?s.bind(null,document).apply(null,arguments):(typeof l=="string"&&(l=document.querySelectorAll(l)),Array.prototype.map.call(l,function(L){return s(L,f,u,d,y)}))}function c(l,f,u,d){return function(y){y.delegateTarget=a(y.target,f),y.delegateTarget&&d.call(l,y)}}o.exports=p},879:function(o,n){n.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},n.nodeList=function(i){var a=Object.prototype.toString.call(i);return i!==void 0&&(a==="[object NodeList]"||a==="[object HTMLCollection]")&&"length"in i&&(i.length===0||n.node(i[0]))},n.string=function(i){return typeof i=="string"||i instanceof String},n.fn=function(i){var a=Object.prototype.toString.call(i);return a==="[object Function]"}},370:function(o,n,i){var a=i(879),s=i(438);function p(u,d,y){if(!u&&!d&&!y)throw new Error("Missing required arguments");if(!a.string(d))throw new TypeError("Second argument must be a String");if(!a.fn(y))throw new TypeError("Third argument must be a Function");if(a.node(u))return c(u,d,y);if(a.nodeList(u))return l(u,d,y);if(a.string(u))return f(u,d,y);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(u,d,y){return u.addEventListener(d,y),{destroy:function(){u.removeEventListener(d,y)}}}function l(u,d,y){return Array.prototype.forEach.call(u,function(L){L.addEventListener(d,y)}),{destroy:function(){Array.prototype.forEach.call(u,function(L){L.removeEventListener(d,y)})}}}function f(u,d,y){return s(document.body,u,d,y)}o.exports=p},817:function(o){function n(i){var a;if(i.nodeName==="SELECT")i.focus(),a=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var s=i.hasAttribute("readonly");s||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),s||i.removeAttribute("readonly"),a=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var p=window.getSelection(),c=document.createRange();c.selectNodeContents(i),p.removeAllRanges(),p.addRange(c),a=p.toString()}return a}o.exports=n},279:function(o){function n(){}n.prototype={on:function(i,a,s){var p=this.e||(this.e={});return(p[i]||(p[i]=[])).push({fn:a,ctx:s}),this},once:function(i,a,s){var p=this;function c(){p.off(i,c),a.apply(s,arguments)}return c._=a,this.on(i,c,s)},emit:function(i){var a=[].slice.call(arguments,1),s=((this.e||(this.e={}))[i]||[]).slice(),p=0,c=s.length;for(p;p0&&i[i.length-1])&&(c[0]===6||c[0]===2)){r=0;continue}if(c[0]===3&&(!i||c[1]>i[0]&&c[1]=e.length&&(e=void 0),{value:e&&e[o++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function z(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var o=r.call(e),n,i=[],a;try{for(;(t===void 0||t-- >0)&&!(n=o.next()).done;)i.push(n.value)}catch(s){a={error:s}}finally{try{n&&!n.done&&(r=o.return)&&r.call(o)}finally{if(a)throw a.error}}return i}function q(e,t,r){if(r||arguments.length===2)for(var o=0,n=t.length,i;o1||p(d,L)})},y&&(n[d]=y(n[d])))}function p(d,y){try{c(o[d](y))}catch(L){u(i[0][3],L)}}function c(d){d.value instanceof nt?Promise.resolve(d.value.v).then(l,f):u(i[0][2],d)}function l(d){p("next",d)}function f(d){p("throw",d)}function u(d,y){d(y),i.shift(),i.length&&p(i[0][0],i[0][1])}}function uo(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof he=="function"?he(e):e[Symbol.iterator](),r={},o("next"),o("throw"),o("return"),r[Symbol.asyncIterator]=function(){return this},r);function o(i){r[i]=e[i]&&function(a){return new Promise(function(s,p){a=e[i](a),n(s,p,a.done,a.value)})}}function n(i,a,s,p){Promise.resolve(p).then(function(c){i({value:c,done:s})},a)}}function H(e){return typeof e=="function"}function ut(e){var t=function(o){Error.call(o),o.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var zt=ut(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(o,n){return n+1+") "+o.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function Qe(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Ue=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,o,n,i;if(!this.closed){this.closed=!0;var a=this._parentage;if(a)if(this._parentage=null,Array.isArray(a))try{for(var s=he(a),p=s.next();!p.done;p=s.next()){var c=p.value;c.remove(this)}}catch(L){t={error:L}}finally{try{p&&!p.done&&(r=s.return)&&r.call(s)}finally{if(t)throw t.error}}else a.remove(this);var l=this.initialTeardown;if(H(l))try{l()}catch(L){i=L instanceof zt?L.errors:[L]}var f=this._finalizers;if(f){this._finalizers=null;try{for(var u=he(f),d=u.next();!d.done;d=u.next()){var y=d.value;try{ho(y)}catch(L){i=i!=null?i:[],L instanceof zt?i=q(q([],z(i)),z(L.errors)):i.push(L)}}}catch(L){o={error:L}}finally{try{d&&!d.done&&(n=u.return)&&n.call(u)}finally{if(o)throw o.error}}}if(i)throw new zt(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)ho(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Qe(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Qe(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Tr=Ue.EMPTY;function Nt(e){return e instanceof Ue||e&&"closed"in e&&H(e.remove)&&H(e.add)&&H(e.unsubscribe)}function ho(e){H(e)?e():e.unsubscribe()}var Pe={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var dt={setTimeout:function(e,t){for(var r=[],o=2;o0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var o=this,n=this,i=n.hasError,a=n.isStopped,s=n.observers;return i||a?Tr:(this.currentObservers=null,s.push(r),new Ue(function(){o.currentObservers=null,Qe(s,r)}))},t.prototype._checkFinalizedStatuses=function(r){var o=this,n=o.hasError,i=o.thrownError,a=o.isStopped;n?r.error(i):a&&r.complete()},t.prototype.asObservable=function(){var r=new j;return r.source=this,r},t.create=function(r,o){return new To(r,o)},t}(j);var To=function(e){oe(t,e);function t(r,o){var n=e.call(this)||this;return n.destination=r,n.source=o,n}return t.prototype.next=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.next)===null||n===void 0||n.call(o,r)},t.prototype.error=function(r){var o,n;(n=(o=this.destination)===null||o===void 0?void 0:o.error)===null||n===void 0||n.call(o,r)},t.prototype.complete=function(){var r,o;(o=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||o===void 0||o.call(r)},t.prototype._subscribe=function(r){var o,n;return(n=(o=this.source)===null||o===void 0?void 0:o.subscribe(r))!==null&&n!==void 0?n:Tr},t}(g);var _r=function(e){oe(t,e);function t(r){var o=e.call(this)||this;return o._value=r,o}return Object.defineProperty(t.prototype,"value",{get:function(){return this.getValue()},enumerable:!1,configurable:!0}),t.prototype._subscribe=function(r){var o=e.prototype._subscribe.call(this,r);return!o.closed&&r.next(this._value),o},t.prototype.getValue=function(){var r=this,o=r.hasError,n=r.thrownError,i=r._value;if(o)throw n;return this._throwIfClosed(),i},t.prototype.next=function(r){e.prototype.next.call(this,this._value=r)},t}(g);var _t={now:function(){return(_t.delegate||Date).now()},delegate:void 0};var At=function(e){oe(t,e);function t(r,o,n){r===void 0&&(r=1/0),o===void 0&&(o=1/0),n===void 0&&(n=_t);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=o,i._timestampProvider=n,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=o===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,o),i}return t.prototype.next=function(r){var o=this,n=o.isStopped,i=o._buffer,a=o._infiniteTimeWindow,s=o._timestampProvider,p=o._windowTime;n||(i.push(r),!a&&i.push(s.now()+p)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var o=this._innerSubscribe(r),n=this,i=n._infiniteTimeWindow,a=n._buffer,s=a.slice(),p=0;p0?e.prototype.schedule.call(this,r,o):(this.delay=o,this.state=r,this.scheduler.flush(this),this)},t.prototype.execute=function(r,o){return o>0||this.closed?e.prototype.execute.call(this,r,o):this._execute(r,o)},t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!=null&&n>0||n==null&&this.delay>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.flush(this),0)},t}(gt);var Lo=function(e){oe(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t}(yt);var kr=new Lo(Oo);var Mo=function(e){oe(t,e);function t(r,o){var n=e.call(this,r,o)||this;return n.scheduler=r,n.work=o,n}return t.prototype.requestAsyncId=function(r,o,n){return n===void 0&&(n=0),n!==null&&n>0?e.prototype.requestAsyncId.call(this,r,o,n):(r.actions.push(this),r._scheduled||(r._scheduled=vt.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,o,n){var i;if(n===void 0&&(n=0),n!=null?n>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,o,n);var a=r.actions;o!=null&&o===r._scheduled&&((i=a[a.length-1])===null||i===void 0?void 0:i.id)!==o&&(vt.cancelAnimationFrame(o),r._scheduled=void 0)},t}(gt);var _o=function(e){oe(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var o;r?o=r.id:(o=this._scheduled,this._scheduled=void 0);var n=this.actions,i;r=r||n.shift();do if(i=r.execute(r.state,r.delay))break;while((r=n[0])&&r.id===o&&n.shift());if(this._active=!1,i){for(;(r=n[0])&&r.id===o&&n.shift();)r.unsubscribe();throw i}},t}(yt);var me=new _o(Mo);var S=new j(function(e){return e.complete()});function Kt(e){return e&&H(e.schedule)}function Hr(e){return e[e.length-1]}function Xe(e){return H(Hr(e))?e.pop():void 0}function ke(e){return Kt(Hr(e))?e.pop():void 0}function Yt(e,t){return typeof Hr(e)=="number"?e.pop():t}var xt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function Bt(e){return H(e==null?void 0:e.then)}function Gt(e){return H(e[bt])}function Jt(e){return Symbol.asyncIterator&&H(e==null?void 0:e[Symbol.asyncIterator])}function Xt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function Zi(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Zt=Zi();function er(e){return H(e==null?void 0:e[Zt])}function tr(e){return fo(this,arguments,function(){var r,o,n,i;return Dt(this,function(a){switch(a.label){case 0:r=e.getReader(),a.label=1;case 1:a.trys.push([1,,9,10]),a.label=2;case 2:return[4,nt(r.read())];case 3:return o=a.sent(),n=o.value,i=o.done,i?[4,nt(void 0)]:[3,5];case 4:return[2,a.sent()];case 5:return[4,nt(n)];case 6:return[4,a.sent()];case 7:return a.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function rr(e){return H(e==null?void 0:e.getReader)}function U(e){if(e instanceof j)return e;if(e!=null){if(Gt(e))return ea(e);if(xt(e))return ta(e);if(Bt(e))return ra(e);if(Jt(e))return Ao(e);if(er(e))return oa(e);if(rr(e))return na(e)}throw Xt(e)}function ea(e){return new j(function(t){var r=e[bt]();if(H(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function ta(e){return new j(function(t){for(var r=0;r=2;return function(o){return o.pipe(e?b(function(n,i){return e(n,i,o)}):le,Te(1),r?Ve(t):Qo(function(){return new nr}))}}function jr(e){return e<=0?function(){return S}:E(function(t,r){var o=[];t.subscribe(T(r,function(n){o.push(n),e=2,!0))}function pe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new g}:t,o=e.resetOnError,n=o===void 0?!0:o,i=e.resetOnComplete,a=i===void 0?!0:i,s=e.resetOnRefCountZero,p=s===void 0?!0:s;return function(c){var l,f,u,d=0,y=!1,L=!1,X=function(){f==null||f.unsubscribe(),f=void 0},ee=function(){X(),l=u=void 0,y=L=!1},J=function(){var k=l;ee(),k==null||k.unsubscribe()};return E(function(k,ft){d++,!L&&!y&&X();var qe=u=u!=null?u:r();ft.add(function(){d--,d===0&&!L&&!y&&(f=Ur(J,p))}),qe.subscribe(ft),!l&&d>0&&(l=new at({next:function(Fe){return qe.next(Fe)},error:function(Fe){L=!0,X(),f=Ur(ee,n,Fe),qe.error(Fe)},complete:function(){y=!0,X(),f=Ur(ee,a),qe.complete()}}),U(k).subscribe(l))})(c)}}function Ur(e,t){for(var r=[],o=2;oe.next(document)),e}function P(e,t=document){return Array.from(t.querySelectorAll(e))}function R(e,t=document){let r=fe(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function fe(e,t=document){return t.querySelector(e)||void 0}function Ie(){var e,t,r,o;return(o=(r=(t=(e=document.activeElement)==null?void 0:e.shadowRoot)==null?void 0:t.activeElement)!=null?r:document.activeElement)!=null?o:void 0}var wa=O(h(document.body,"focusin"),h(document.body,"focusout")).pipe(_e(1),Q(void 0),m(()=>Ie()||document.body),G(1));function et(e){return wa.pipe(m(t=>e.contains(t)),K())}function Ht(e,t){return C(()=>O(h(e,"mouseenter").pipe(m(()=>!0)),h(e,"mouseleave").pipe(m(()=>!1))).pipe(t?kt(r=>Le(+!r*t)):le,Q(e.matches(":hover"))))}function Jo(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)Jo(e,r)}function x(e,t,...r){let o=document.createElement(e);if(t)for(let n of Object.keys(t))typeof t[n]!="undefined"&&(typeof t[n]!="boolean"?o.setAttribute(n,t[n]):o.setAttribute(n,""));for(let n of r)Jo(o,n);return o}function sr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function wt(e){let t=x("script",{src:e});return C(()=>(document.head.appendChild(t),O(h(t,"load"),h(t,"error").pipe(v(()=>$r(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(m(()=>{}),_(()=>document.head.removeChild(t)),Te(1))))}var Xo=new g,Ta=C(()=>typeof ResizeObserver=="undefined"?wt("https://unpkg.com/resize-observer-polyfill"):I(void 0)).pipe(m(()=>new ResizeObserver(e=>e.forEach(t=>Xo.next(t)))),v(e=>O(Ye,I(e)).pipe(_(()=>e.disconnect()))),G(1));function ce(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ge(e){let t=e;for(;t.clientWidth===0&&t.parentElement;)t=t.parentElement;return Ta.pipe(w(r=>r.observe(t)),v(r=>Xo.pipe(b(o=>o.target===t),_(()=>r.unobserve(t)))),m(()=>ce(e)),Q(ce(e)))}function Tt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function cr(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}function Zo(e){let t=[],r=e.parentElement;for(;r;)(e.clientWidth>r.clientWidth||e.clientHeight>r.clientHeight)&&t.push(r),r=(e=r).parentElement;return t.length===0&&t.push(document.documentElement),t}function De(e){return{x:e.offsetLeft,y:e.offsetTop}}function en(e){let t=e.getBoundingClientRect();return{x:t.x+window.scrollX,y:t.y+window.scrollY}}function tn(e){return O(h(window,"load"),h(window,"resize")).pipe(Me(0,me),m(()=>De(e)),Q(De(e)))}function pr(e){return{x:e.scrollLeft,y:e.scrollTop}}function ze(e){return O(h(e,"scroll"),h(window,"scroll"),h(window,"resize")).pipe(Me(0,me),m(()=>pr(e)),Q(pr(e)))}var rn=new g,Sa=C(()=>I(new IntersectionObserver(e=>{for(let t of e)rn.next(t)},{threshold:0}))).pipe(v(e=>O(Ye,I(e)).pipe(_(()=>e.disconnect()))),G(1));function tt(e){return Sa.pipe(w(t=>t.observe(e)),v(t=>rn.pipe(b(({target:r})=>r===e),_(()=>t.unobserve(e)),m(({isIntersecting:r})=>r))))}function on(e,t=16){return ze(e).pipe(m(({y:r})=>{let o=ce(e),n=Tt(e);return r>=n.height-o.height-t}),K())}var lr={drawer:R("[data-md-toggle=drawer]"),search:R("[data-md-toggle=search]")};function nn(e){return lr[e].checked}function Je(e,t){lr[e].checked!==t&&lr[e].click()}function Ne(e){let t=lr[e];return h(t,"change").pipe(m(()=>t.checked),Q(t.checked))}function Oa(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function La(){return O(h(window,"compositionstart").pipe(m(()=>!0)),h(window,"compositionend").pipe(m(()=>!1))).pipe(Q(!1))}function an(){let e=h(window,"keydown").pipe(b(t=>!(t.metaKey||t.ctrlKey)),m(t=>({mode:nn("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),b(({mode:t,type:r})=>{if(t==="global"){let o=Ie();if(typeof o!="undefined")return!Oa(o,r)}return!0}),pe());return La().pipe(v(t=>t?S:e))}function ye(){return new URL(location.href)}function lt(e,t=!1){if(B("navigation.instant")&&!t){let r=x("a",{href:e.href});document.body.appendChild(r),r.click(),r.remove()}else location.href=e.href}function sn(){return new g}function cn(){return location.hash.slice(1)}function pn(e){let t=x("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Ma(e){return O(h(window,"hashchange"),e).pipe(m(cn),Q(cn()),b(t=>t.length>0),G(1))}function ln(e){return Ma(e).pipe(m(t=>fe(`[id="${t}"]`)),b(t=>typeof t!="undefined"))}function $t(e){let t=matchMedia(e);return ir(r=>t.addListener(()=>r(t.matches))).pipe(Q(t.matches))}function mn(){let e=matchMedia("print");return O(h(window,"beforeprint").pipe(m(()=>!0)),h(window,"afterprint").pipe(m(()=>!1))).pipe(Q(e.matches))}function zr(e,t){return e.pipe(v(r=>r?t():S))}function Nr(e,t){return new j(r=>{let o=new XMLHttpRequest;return o.open("GET",`${e}`),o.responseType="blob",o.addEventListener("load",()=>{o.status>=200&&o.status<300?(r.next(o.response),r.complete()):r.error(new Error(o.statusText))}),o.addEventListener("error",()=>{r.error(new Error("Network error"))}),o.addEventListener("abort",()=>{r.complete()}),typeof(t==null?void 0:t.progress$)!="undefined"&&(o.addEventListener("progress",n=>{var i;if(n.lengthComputable)t.progress$.next(n.loaded/n.total*100);else{let a=(i=o.getResponseHeader("Content-Length"))!=null?i:0;t.progress$.next(n.loaded/+a*100)}}),t.progress$.next(5)),o.send(),()=>o.abort()})}function je(e,t){return Nr(e,t).pipe(v(r=>r.text()),m(r=>JSON.parse(r)),G(1))}function fn(e,t){let r=new DOMParser;return Nr(e,t).pipe(v(o=>o.text()),m(o=>r.parseFromString(o,"text/html")),G(1))}function un(e,t){let r=new DOMParser;return Nr(e,t).pipe(v(o=>o.text()),m(o=>r.parseFromString(o,"text/xml")),G(1))}function dn(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function hn(){return O(h(window,"scroll",{passive:!0}),h(window,"resize",{passive:!0})).pipe(m(dn),Q(dn()))}function bn(){return{width:innerWidth,height:innerHeight}}function vn(){return h(window,"resize",{passive:!0}).pipe(m(bn),Q(bn()))}function gn(){return N([hn(),vn()]).pipe(m(([e,t])=>({offset:e,size:t})),G(1))}function mr(e,{viewport$:t,header$:r}){let o=t.pipe(te("size")),n=N([o,r]).pipe(m(()=>De(e)));return N([r,t,n]).pipe(m(([{height:i},{offset:a,size:s},{x:p,y:c}])=>({offset:{x:a.x-p,y:a.y-c+i},size:s})))}function _a(e){return h(e,"message",t=>t.data)}function Aa(e){let t=new g;return t.subscribe(r=>e.postMessage(r)),t}function yn(e,t=new Worker(e)){let r=_a(t),o=Aa(t),n=new g;n.subscribe(o);let i=o.pipe(Z(),ie(!0));return n.pipe(Z(),Re(r.pipe(W(i))),pe())}var Ca=R("#__config"),St=JSON.parse(Ca.textContent);St.base=`${new URL(St.base,ye())}`;function xe(){return St}function B(e){return St.features.includes(e)}function Ee(e,t){return typeof t!="undefined"?St.translations[e].replace("#",t.toString()):St.translations[e]}function Se(e,t=document){return R(`[data-md-component=${e}]`,t)}function ae(e,t=document){return P(`[data-md-component=${e}]`,t)}function ka(e){let t=R(".md-typeset > :first-child",e);return h(t,"click",{once:!0}).pipe(m(()=>R(".md-typeset",e)),m(r=>({hash:__md_hash(r.innerHTML)})))}function xn(e){if(!B("announce.dismiss")||!e.childElementCount)return S;if(!e.hidden){let t=R(".md-typeset",e);__md_hash(t.innerHTML)===__md_get("__announce")&&(e.hidden=!0)}return C(()=>{let t=new g;return t.subscribe(({hash:r})=>{e.hidden=!0,__md_set("__announce",r)}),ka(e).pipe(w(r=>t.next(r)),_(()=>t.complete()),m(r=>$({ref:e},r)))})}function Ha(e,{target$:t}){return t.pipe(m(r=>({hidden:r!==e})))}function En(e,t){let r=new g;return r.subscribe(({hidden:o})=>{e.hidden=o}),Ha(e,t).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))}function Pt(e,t){return t==="inline"?x("div",{class:"md-tooltip md-tooltip--inline",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"})):x("div",{class:"md-tooltip",id:e,role:"tooltip"},x("div",{class:"md-tooltip__inner md-typeset"}))}function wn(...e){return x("div",{class:"md-tooltip2",role:"tooltip"},x("div",{class:"md-tooltip2__inner md-typeset"},e))}function Tn(e,t){if(t=t?`${t}_annotation_${e}`:void 0,t){let r=t?`#${t}`:void 0;return x("aside",{class:"md-annotation",tabIndex:0},Pt(t),x("a",{href:r,class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}else return x("aside",{class:"md-annotation",tabIndex:0},Pt(t),x("span",{class:"md-annotation__index",tabIndex:-1},x("span",{"data-md-annotation-id":e})))}function Sn(e){return x("button",{class:"md-clipboard md-icon",title:Ee("clipboard.copy"),"data-clipboard-target":`#${e} > code`})}var Ln=Lt(qr());function Qr(e,t){let r=t&2,o=t&1,n=Object.keys(e.terms).filter(p=>!e.terms[p]).reduce((p,c)=>[...p,x("del",null,(0,Ln.default)(c))," "],[]).slice(0,-1),i=xe(),a=new URL(e.location,i.base);B("search.highlight")&&a.searchParams.set("h",Object.entries(e.terms).filter(([,p])=>p).reduce((p,[c])=>`${p} ${c}`.trim(),""));let{tags:s}=xe();return x("a",{href:`${a}`,class:"md-search-result__link",tabIndex:-1},x("article",{class:"md-search-result__article md-typeset","data-md-score":e.score.toFixed(2)},r>0&&x("div",{class:"md-search-result__icon md-icon"}),r>0&&x("h1",null,e.title),r<=0&&x("h2",null,e.title),o>0&&e.text.length>0&&e.text,e.tags&&x("nav",{class:"md-tags"},e.tags.map(p=>{let c=s?p in s?`md-tag-icon md-tag--${s[p]}`:"md-tag-icon":"";return x("span",{class:`md-tag ${c}`},p)})),o>0&&n.length>0&&x("p",{class:"md-search-result__terms"},Ee("search.result.term.missing"),": ",...n)))}function Mn(e){let t=e[0].score,r=[...e],o=xe(),n=r.findIndex(l=>!`${new URL(l.location,o.base)}`.includes("#")),[i]=r.splice(n,1),a=r.findIndex(l=>l.scoreQr(l,1)),...p.length?[x("details",{class:"md-search-result__more"},x("summary",{tabIndex:-1},x("div",null,p.length>0&&p.length===1?Ee("search.result.more.one"):Ee("search.result.more.other",p.length))),...p.map(l=>Qr(l,1)))]:[]];return x("li",{class:"md-search-result__item"},c)}function _n(e){return x("ul",{class:"md-source__facts"},Object.entries(e).map(([t,r])=>x("li",{class:`md-source__fact md-source__fact--${t}`},typeof r=="number"?sr(r):r)))}function Kr(e){let t=`tabbed-control tabbed-control--${e}`;return x("div",{class:t,hidden:!0},x("button",{class:"tabbed-button",tabIndex:-1,"aria-hidden":"true"}))}function An(e){return x("div",{class:"md-typeset__scrollwrap"},x("div",{class:"md-typeset__table"},e))}function Ra(e){var o;let t=xe(),r=new URL(`../${e.version}/`,t.base);return x("li",{class:"md-version__item"},x("a",{href:`${r}`,class:"md-version__link"},e.title,((o=t.version)==null?void 0:o.alias)&&e.aliases.length>0&&x("span",{class:"md-version__alias"},e.aliases[0])))}function Cn(e,t){var o;let r=xe();return e=e.filter(n=>{var i;return!((i=n.properties)!=null&&i.hidden)}),x("div",{class:"md-version"},x("button",{class:"md-version__current","aria-label":Ee("select.version")},t.title,((o=r.version)==null?void 0:o.alias)&&t.aliases.length>0&&x("span",{class:"md-version__alias"},t.aliases[0])),x("ul",{class:"md-version__list"},e.map(Ra)))}var Ia=0;function ja(e){let t=N([et(e),Ht(e)]).pipe(m(([o,n])=>o||n),K()),r=C(()=>Zo(e)).pipe(ne(ze),pt(1),He(t),m(()=>en(e)));return t.pipe(Ae(o=>o),v(()=>N([t,r])),m(([o,n])=>({active:o,offset:n})),pe())}function Fa(e,t){let{content$:r,viewport$:o}=t,n=`__tooltip2_${Ia++}`;return C(()=>{let i=new g,a=new _r(!1);i.pipe(Z(),ie(!1)).subscribe(a);let s=a.pipe(kt(c=>Le(+!c*250,kr)),K(),v(c=>c?r:S),w(c=>c.id=n),pe());N([i.pipe(m(({active:c})=>c)),s.pipe(v(c=>Ht(c,250)),Q(!1))]).pipe(m(c=>c.some(l=>l))).subscribe(a);let p=a.pipe(b(c=>c),re(s,o),m(([c,l,{size:f}])=>{let u=e.getBoundingClientRect(),d=u.width/2;if(l.role==="tooltip")return{x:d,y:8+u.height};if(u.y>=f.height/2){let{height:y}=ce(l);return{x:d,y:-16-y}}else return{x:d,y:16+u.height}}));return N([s,i,p]).subscribe(([c,{offset:l},f])=>{c.style.setProperty("--md-tooltip-host-x",`${l.x}px`),c.style.setProperty("--md-tooltip-host-y",`${l.y}px`),c.style.setProperty("--md-tooltip-x",`${f.x}px`),c.style.setProperty("--md-tooltip-y",`${f.y}px`),c.classList.toggle("md-tooltip2--top",f.y<0),c.classList.toggle("md-tooltip2--bottom",f.y>=0)}),a.pipe(b(c=>c),re(s,(c,l)=>l),b(c=>c.role==="tooltip")).subscribe(c=>{let l=ce(R(":scope > *",c));c.style.setProperty("--md-tooltip-width",`${l.width}px`),c.style.setProperty("--md-tooltip-tail","0px")}),a.pipe(K(),ve(me),re(s)).subscribe(([c,l])=>{l.classList.toggle("md-tooltip2--active",c)}),N([a.pipe(b(c=>c)),s]).subscribe(([c,l])=>{l.role==="dialog"?(e.setAttribute("aria-controls",n),e.setAttribute("aria-haspopup","dialog")):e.setAttribute("aria-describedby",n)}),a.pipe(b(c=>!c)).subscribe(()=>{e.removeAttribute("aria-controls"),e.removeAttribute("aria-describedby"),e.removeAttribute("aria-haspopup")}),ja(e).pipe(w(c=>i.next(c)),_(()=>i.complete()),m(c=>$({ref:e},c)))})}function mt(e,{viewport$:t},r=document.body){return Fa(e,{content$:new j(o=>{let n=e.title,i=wn(n);return o.next(i),e.removeAttribute("title"),r.append(i),()=>{i.remove(),e.setAttribute("title",n)}}),viewport$:t})}function Ua(e,t){let r=C(()=>N([tn(e),ze(t)])).pipe(m(([{x:o,y:n},i])=>{let{width:a,height:s}=ce(e);return{x:o-i.x+a/2,y:n-i.y+s/2}}));return et(e).pipe(v(o=>r.pipe(m(n=>({active:o,offset:n})),Te(+!o||1/0))))}function kn(e,t,{target$:r}){let[o,n]=Array.from(e.children);return C(()=>{let i=new g,a=i.pipe(Z(),ie(!0));return i.subscribe({next({offset:s}){e.style.setProperty("--md-tooltip-x",`${s.x}px`),e.style.setProperty("--md-tooltip-y",`${s.y}px`)},complete(){e.style.removeProperty("--md-tooltip-x"),e.style.removeProperty("--md-tooltip-y")}}),tt(e).pipe(W(a)).subscribe(s=>{e.toggleAttribute("data-md-visible",s)}),O(i.pipe(b(({active:s})=>s)),i.pipe(_e(250),b(({active:s})=>!s))).subscribe({next({active:s}){s?e.prepend(o):o.remove()},complete(){e.prepend(o)}}),i.pipe(Me(16,me)).subscribe(({active:s})=>{o.classList.toggle("md-tooltip--active",s)}),i.pipe(pt(125,me),b(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:s})=>s)).subscribe({next(s){s?e.style.setProperty("--md-tooltip-0",`${-s}px`):e.style.removeProperty("--md-tooltip-0")},complete(){e.style.removeProperty("--md-tooltip-0")}}),h(n,"click").pipe(W(a),b(s=>!(s.metaKey||s.ctrlKey))).subscribe(s=>{s.stopPropagation(),s.preventDefault()}),h(n,"mousedown").pipe(W(a),re(i)).subscribe(([s,{active:p}])=>{var c;if(s.button!==0||s.metaKey||s.ctrlKey)s.preventDefault();else if(p){s.preventDefault();let l=e.parentElement.closest(".md-annotation");l instanceof HTMLElement?l.focus():(c=Ie())==null||c.blur()}}),r.pipe(W(a),b(s=>s===o),Ge(125)).subscribe(()=>e.focus()),Ua(e,t).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))})}function Wa(e){return e.tagName==="CODE"?P(".c, .c1, .cm",e):[e]}function Va(e){let t=[];for(let r of Wa(e)){let o=[],n=document.createNodeIterator(r,NodeFilter.SHOW_TEXT);for(let i=n.nextNode();i;i=n.nextNode())o.push(i);for(let i of o){let a;for(;a=/(\(\d+\))(!)?/.exec(i.textContent);){let[,s,p]=a;if(typeof p=="undefined"){let c=i.splitText(a.index);i=c.splitText(s.length),t.push(c)}else{i.textContent=s,t.push(i);break}}}}return t}function Hn(e,t){t.append(...Array.from(e.childNodes))}function fr(e,t,{target$:r,print$:o}){let n=t.closest("[id]"),i=n==null?void 0:n.id,a=new Map;for(let s of Va(t)){let[,p]=s.textContent.match(/\((\d+)\)/);fe(`:scope > li:nth-child(${p})`,e)&&(a.set(p,Tn(p,i)),s.replaceWith(a.get(p)))}return a.size===0?S:C(()=>{let s=new g,p=s.pipe(Z(),ie(!0)),c=[];for(let[l,f]of a)c.push([R(".md-typeset",f),R(`:scope > li:nth-child(${l})`,e)]);return o.pipe(W(p)).subscribe(l=>{e.hidden=!l,e.classList.toggle("md-annotation-list",l);for(let[f,u]of c)l?Hn(f,u):Hn(u,f)}),O(...[...a].map(([,l])=>kn(l,t,{target$:r}))).pipe(_(()=>s.complete()),pe())})}function $n(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return $n(t)}}function Pn(e,t){return C(()=>{let r=$n(e);return typeof r!="undefined"?fr(r,e,t):S})}var Rn=Lt(Br());var Da=0;function In(e){if(e.nextElementSibling){let t=e.nextElementSibling;if(t.tagName==="OL")return t;if(t.tagName==="P"&&!t.children.length)return In(t)}}function za(e){return ge(e).pipe(m(({width:t})=>({scrollable:Tt(e).width>t})),te("scrollable"))}function jn(e,t){let{matches:r}=matchMedia("(hover)"),o=C(()=>{let n=new g,i=n.pipe(jr(1));n.subscribe(({scrollable:c})=>{c&&r?e.setAttribute("tabindex","0"):e.removeAttribute("tabindex")});let a=[];if(Rn.default.isSupported()&&(e.closest(".copy")||B("content.code.copy")&&!e.closest(".no-copy"))){let c=e.closest("pre");c.id=`__code_${Da++}`;let l=Sn(c.id);c.insertBefore(l,e),B("content.tooltips")&&a.push(mt(l,{viewport$}))}let s=e.closest(".highlight");if(s instanceof HTMLElement){let c=In(s);if(typeof c!="undefined"&&(s.classList.contains("annotate")||B("content.code.annotate"))){let l=fr(c,e,t);a.push(ge(s).pipe(W(i),m(({width:f,height:u})=>f&&u),K(),v(f=>f?l:S)))}}return P(":scope > span[id]",e).length&&e.classList.add("md-code__content"),za(e).pipe(w(c=>n.next(c)),_(()=>n.complete()),m(c=>$({ref:e},c)),Re(...a))});return B("content.lazy")?tt(e).pipe(b(n=>n),Te(1),v(()=>o)):o}function Na(e,{target$:t,print$:r}){let o=!0;return O(t.pipe(m(n=>n.closest("details:not([open])")),b(n=>e===n),m(()=>({action:"open",reveal:!0}))),r.pipe(b(n=>n||!o),w(()=>o=e.open),m(n=>({action:n?"open":"close"}))))}function Fn(e,t){return C(()=>{let r=new g;return r.subscribe(({action:o,reveal:n})=>{e.toggleAttribute("open",o==="open"),n&&e.scrollIntoView()}),Na(e,t).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}var Un=".node circle,.node ellipse,.node path,.node polygon,.node rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}marker{fill:var(--md-mermaid-edge-color)!important}.edgeLabel .label rect{fill:#0000}.flowchartTitleText{fill:var(--md-mermaid-label-fg-color)}.label{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.label foreignObject{line-height:normal;overflow:visible}.label div .edgeLabel{color:var(--md-mermaid-label-fg-color)}.edgeLabel,.edgeLabel p,.label div .edgeLabel{background-color:var(--md-mermaid-label-bg-color)}.edgeLabel,.edgeLabel p{fill:var(--md-mermaid-label-bg-color);color:var(--md-mermaid-edge-color)}.edgePath .path,.flowchart-link{stroke:var(--md-mermaid-edge-color);stroke-width:.05rem}.edgePath .arrowheadPath{fill:var(--md-mermaid-edge-color);stroke:none}.cluster rect{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}.cluster span{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}g #flowchart-circleEnd,g #flowchart-circleStart,g #flowchart-crossEnd,g #flowchart-crossStart,g #flowchart-pointEnd,g #flowchart-pointStart{stroke:none}.classDiagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.classGroup line,g.classGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.classGroup text{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.classLabel .box{fill:var(--md-mermaid-label-bg-color);background-color:var(--md-mermaid-label-bg-color);opacity:1}.classLabel .label{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.node .divider{stroke:var(--md-mermaid-node-fg-color)}.relation{stroke:var(--md-mermaid-edge-color)}.cardinality{fill:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}.cardinality text{fill:inherit!important}defs marker.marker.composition.class path,defs marker.marker.dependency.class path,defs marker.marker.extension.class path{fill:var(--md-mermaid-edge-color)!important;stroke:var(--md-mermaid-edge-color)!important}defs marker.marker.aggregation.class path{fill:var(--md-mermaid-label-bg-color)!important;stroke:var(--md-mermaid-edge-color)!important}.statediagramTitleText{fill:var(--md-mermaid-label-fg-color)}g.stateGroup rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}g.stateGroup .state-title{fill:var(--md-mermaid-label-fg-color)!important;font-family:var(--md-mermaid-font-family)}g.stateGroup .composit{fill:var(--md-mermaid-label-bg-color)}.nodeLabel,.nodeLabel p{color:var(--md-mermaid-label-fg-color);font-family:var(--md-mermaid-font-family)}a .nodeLabel{text-decoration:underline}.node circle.state-end,.node circle.state-start,.start-state{fill:var(--md-mermaid-edge-color);stroke:none}.end-state-inner,.end-state-outer{fill:var(--md-mermaid-edge-color)}.end-state-inner,.node circle.state-end{stroke:var(--md-mermaid-label-bg-color)}.transition{stroke:var(--md-mermaid-edge-color)}[id^=state-fork] rect,[id^=state-join] rect{fill:var(--md-mermaid-edge-color)!important;stroke:none!important}.statediagram-cluster.statediagram-cluster .inner{fill:var(--md-default-bg-color)}.statediagram-cluster rect{fill:var(--md-mermaid-node-bg-color);stroke:var(--md-mermaid-node-fg-color)}.statediagram-state rect.divider{fill:var(--md-default-fg-color--lightest);stroke:var(--md-default-fg-color--lighter)}defs #statediagram-barbEnd{stroke:var(--md-mermaid-edge-color)}[id^=entity] path,[id^=entity] rect{fill:var(--md-default-bg-color)}.relationshipLine{stroke:var(--md-mermaid-edge-color)}defs .marker.oneOrMore.er *,defs .marker.onlyOne.er *,defs .marker.zeroOrMore.er *,defs .marker.zeroOrOne.er *{stroke:var(--md-mermaid-edge-color)!important}text:not([class]):last-child{fill:var(--md-mermaid-label-fg-color)}.actor{fill:var(--md-mermaid-sequence-actor-bg-color);stroke:var(--md-mermaid-sequence-actor-border-color)}text.actor>tspan{fill:var(--md-mermaid-sequence-actor-fg-color);font-family:var(--md-mermaid-font-family)}line{stroke:var(--md-mermaid-sequence-actor-line-color)}.actor-man circle,.actor-man line{fill:var(--md-mermaid-sequence-actorman-bg-color);stroke:var(--md-mermaid-sequence-actorman-line-color)}.messageLine0,.messageLine1{stroke:var(--md-mermaid-sequence-message-line-color)}.note{fill:var(--md-mermaid-sequence-note-bg-color);stroke:var(--md-mermaid-sequence-note-border-color)}.loopText,.loopText>tspan,.messageText,.noteText>tspan{stroke:none;font-family:var(--md-mermaid-font-family)!important}.messageText{fill:var(--md-mermaid-sequence-message-fg-color)}.loopText,.loopText>tspan{fill:var(--md-mermaid-sequence-loop-fg-color)}.noteText>tspan{fill:var(--md-mermaid-sequence-note-fg-color)}#arrowhead path{fill:var(--md-mermaid-sequence-message-line-color);stroke:none}.loopLine{fill:var(--md-mermaid-sequence-loop-bg-color);stroke:var(--md-mermaid-sequence-loop-border-color)}.labelBox{fill:var(--md-mermaid-sequence-label-bg-color);stroke:none}.labelText,.labelText>span{fill:var(--md-mermaid-sequence-label-fg-color);font-family:var(--md-mermaid-font-family)}.sequenceNumber{fill:var(--md-mermaid-sequence-number-fg-color)}rect.rect{fill:var(--md-mermaid-sequence-box-bg-color);stroke:none}rect.rect+text.text{fill:var(--md-mermaid-sequence-box-fg-color)}defs #sequencenumber{fill:var(--md-mermaid-sequence-number-bg-color)!important}";var Gr,Qa=0;function Ka(){return typeof mermaid=="undefined"||mermaid instanceof Element?wt("https://unpkg.com/mermaid@11/dist/mermaid.min.js"):I(void 0)}function Wn(e){return e.classList.remove("mermaid"),Gr||(Gr=Ka().pipe(w(()=>mermaid.initialize({startOnLoad:!1,themeCSS:Un,sequence:{actorFontSize:"16px",messageFontSize:"16px",noteFontSize:"16px"}})),m(()=>{}),G(1))),Gr.subscribe(()=>co(null,null,function*(){e.classList.add("mermaid");let t=`__mermaid_${Qa++}`,r=x("div",{class:"mermaid"}),o=e.textContent,{svg:n,fn:i}=yield mermaid.render(t,o),a=r.attachShadow({mode:"closed"});a.innerHTML=n,e.replaceWith(r),i==null||i(a)})),Gr.pipe(m(()=>({ref:e})))}var Vn=x("table");function Dn(e){return e.replaceWith(Vn),Vn.replaceWith(An(e)),I({ref:e})}function Ya(e){let t=e.find(r=>r.checked)||e[0];return O(...e.map(r=>h(r,"change").pipe(m(()=>R(`label[for="${r.id}"]`))))).pipe(Q(R(`label[for="${t.id}"]`)),m(r=>({active:r})))}function zn(e,{viewport$:t,target$:r}){let o=R(".tabbed-labels",e),n=P(":scope > input",e),i=Kr("prev");e.append(i);let a=Kr("next");return e.append(a),C(()=>{let s=new g,p=s.pipe(Z(),ie(!0));N([s,ge(e),tt(e)]).pipe(W(p),Me(1,me)).subscribe({next([{active:c},l]){let f=De(c),{width:u}=ce(c);e.style.setProperty("--md-indicator-x",`${f.x}px`),e.style.setProperty("--md-indicator-width",`${u}px`);let d=pr(o);(f.xd.x+l.width)&&o.scrollTo({left:Math.max(0,f.x-16),behavior:"smooth"})},complete(){e.style.removeProperty("--md-indicator-x"),e.style.removeProperty("--md-indicator-width")}}),N([ze(o),ge(o)]).pipe(W(p)).subscribe(([c,l])=>{let f=Tt(o);i.hidden=c.x<16,a.hidden=c.x>f.width-l.width-16}),O(h(i,"click").pipe(m(()=>-1)),h(a,"click").pipe(m(()=>1))).pipe(W(p)).subscribe(c=>{let{width:l}=ce(o);o.scrollBy({left:l*c,behavior:"smooth"})}),r.pipe(W(p),b(c=>n.includes(c))).subscribe(c=>c.click()),o.classList.add("tabbed-labels--linked");for(let c of n){let l=R(`label[for="${c.id}"]`);l.replaceChildren(x("a",{href:`#${l.htmlFor}`,tabIndex:-1},...Array.from(l.childNodes))),h(l.firstElementChild,"click").pipe(W(p),b(f=>!(f.metaKey||f.ctrlKey)),w(f=>{f.preventDefault(),f.stopPropagation()})).subscribe(()=>{history.replaceState({},"",`#${l.htmlFor}`),l.click()})}return B("content.tabs.link")&&s.pipe(Ce(1),re(t)).subscribe(([{active:c},{offset:l}])=>{let f=c.innerText.trim();if(c.hasAttribute("data-md-switching"))c.removeAttribute("data-md-switching");else{let u=e.offsetTop-l.y;for(let y of P("[data-tabs]"))for(let L of P(":scope > input",y)){let X=R(`label[for="${L.id}"]`);if(X!==c&&X.innerText.trim()===f){X.setAttribute("data-md-switching",""),L.click();break}}window.scrollTo({top:e.offsetTop-u});let d=__md_get("__tabs")||[];__md_set("__tabs",[...new Set([f,...d])])}}),s.pipe(W(p)).subscribe(()=>{for(let c of P("audio, video",e))c.pause()}),Ya(n).pipe(w(c=>s.next(c)),_(()=>s.complete()),m(c=>$({ref:e},c)))}).pipe(Ke(se))}function Nn(e,{viewport$:t,target$:r,print$:o}){return O(...P(".annotate:not(.highlight)",e).map(n=>Pn(n,{target$:r,print$:o})),...P("pre:not(.mermaid) > code",e).map(n=>jn(n,{target$:r,print$:o})),...P("pre.mermaid",e).map(n=>Wn(n)),...P("table:not([class])",e).map(n=>Dn(n)),...P("details",e).map(n=>Fn(n,{target$:r,print$:o})),...P("[data-tabs]",e).map(n=>zn(n,{viewport$:t,target$:r})),...P("[title]",e).filter(()=>B("content.tooltips")).map(n=>mt(n,{viewport$:t})))}function Ba(e,{alert$:t}){return t.pipe(v(r=>O(I(!0),I(!1).pipe(Ge(2e3))).pipe(m(o=>({message:r,active:o})))))}function qn(e,t){let r=R(".md-typeset",e);return C(()=>{let o=new g;return o.subscribe(({message:n,active:i})=>{e.classList.toggle("md-dialog--active",i),r.textContent=n}),Ba(e,t).pipe(w(n=>o.next(n)),_(()=>o.complete()),m(n=>$({ref:e},n)))})}var Ga=0;function Ja(e,t){document.body.append(e);let{width:r}=ce(e);e.style.setProperty("--md-tooltip-width",`${r}px`),e.remove();let o=cr(t),n=typeof o!="undefined"?ze(o):I({x:0,y:0}),i=O(et(t),Ht(t)).pipe(K());return N([i,n]).pipe(m(([a,s])=>{let{x:p,y:c}=De(t),l=ce(t),f=t.closest("table");return f&&t.parentElement&&(p+=f.offsetLeft+t.parentElement.offsetLeft,c+=f.offsetTop+t.parentElement.offsetTop),{active:a,offset:{x:p-s.x+l.width/2-r/2,y:c-s.y+l.height+8}}}))}function Qn(e){let t=e.title;if(!t.length)return S;let r=`__tooltip_${Ga++}`,o=Pt(r,"inline"),n=R(".md-typeset",o);return n.innerHTML=t,C(()=>{let i=new g;return i.subscribe({next({offset:a}){o.style.setProperty("--md-tooltip-x",`${a.x}px`),o.style.setProperty("--md-tooltip-y",`${a.y}px`)},complete(){o.style.removeProperty("--md-tooltip-x"),o.style.removeProperty("--md-tooltip-y")}}),O(i.pipe(b(({active:a})=>a)),i.pipe(_e(250),b(({active:a})=>!a))).subscribe({next({active:a}){a?(e.insertAdjacentElement("afterend",o),e.setAttribute("aria-describedby",r),e.removeAttribute("title")):(o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t))},complete(){o.remove(),e.removeAttribute("aria-describedby"),e.setAttribute("title",t)}}),i.pipe(Me(16,me)).subscribe(({active:a})=>{o.classList.toggle("md-tooltip--active",a)}),i.pipe(pt(125,me),b(()=>!!e.offsetParent),m(()=>e.offsetParent.getBoundingClientRect()),m(({x:a})=>a)).subscribe({next(a){a?o.style.setProperty("--md-tooltip-0",`${-a}px`):o.style.removeProperty("--md-tooltip-0")},complete(){o.style.removeProperty("--md-tooltip-0")}}),Ja(o,e).pipe(w(a=>i.next(a)),_(()=>i.complete()),m(a=>$({ref:e},a)))}).pipe(Ke(se))}function Xa({viewport$:e}){if(!B("header.autohide"))return I(!1);let t=e.pipe(m(({offset:{y:n}})=>n),Be(2,1),m(([n,i])=>[nMath.abs(i-n.y)>100),m(([,[n]])=>n),K()),o=Ne("search");return N([e,o]).pipe(m(([{offset:n},i])=>n.y>400&&!i),K(),v(n=>n?r:I(!1)),Q(!1))}function Kn(e,t){return C(()=>N([ge(e),Xa(t)])).pipe(m(([{height:r},o])=>({height:r,hidden:o})),K((r,o)=>r.height===o.height&&r.hidden===o.hidden),G(1))}function Yn(e,{header$:t,main$:r}){return C(()=>{let o=new g,n=o.pipe(Z(),ie(!0));o.pipe(te("active"),He(t)).subscribe(([{active:a},{hidden:s}])=>{e.classList.toggle("md-header--shadow",a&&!s),e.hidden=s});let i=ue(P("[title]",e)).pipe(b(()=>B("content.tooltips")),ne(a=>Qn(a)));return r.subscribe(o),t.pipe(W(n),m(a=>$({ref:e},a)),Re(i.pipe(W(n))))})}function Za(e,{viewport$:t,header$:r}){return mr(e,{viewport$:t,header$:r}).pipe(m(({offset:{y:o}})=>{let{height:n}=ce(e);return{active:o>=n}}),te("active"))}function Bn(e,t){return C(()=>{let r=new g;r.subscribe({next({active:n}){e.classList.toggle("md-header__title--active",n)},complete(){e.classList.remove("md-header__title--active")}});let o=fe(".md-content h1");return typeof o=="undefined"?S:Za(o,t).pipe(w(n=>r.next(n)),_(()=>r.complete()),m(n=>$({ref:e},n)))})}function Gn(e,{viewport$:t,header$:r}){let o=r.pipe(m(({height:i})=>i),K()),n=o.pipe(v(()=>ge(e).pipe(m(({height:i})=>({top:e.offsetTop,bottom:e.offsetTop+i})),te("bottom"))));return N([o,n,t]).pipe(m(([i,{top:a,bottom:s},{offset:{y:p},size:{height:c}}])=>(c=Math.max(0,c-Math.max(0,a-p,i)-Math.max(0,c+p-s)),{offset:a-i,height:c,active:a-i<=p})),K((i,a)=>i.offset===a.offset&&i.height===a.height&&i.active===a.active))}function es(e){let t=__md_get("__palette")||{index:e.findIndex(o=>matchMedia(o.getAttribute("data-md-color-media")).matches)},r=Math.max(0,Math.min(t.index,e.length-1));return I(...e).pipe(ne(o=>h(o,"change").pipe(m(()=>o))),Q(e[r]),m(o=>({index:e.indexOf(o),color:{media:o.getAttribute("data-md-color-media"),scheme:o.getAttribute("data-md-color-scheme"),primary:o.getAttribute("data-md-color-primary"),accent:o.getAttribute("data-md-color-accent")}})),G(1))}function Jn(e){let t=P("input",e),r=x("meta",{name:"theme-color"});document.head.appendChild(r);let o=x("meta",{name:"color-scheme"});document.head.appendChild(o);let n=$t("(prefers-color-scheme: light)");return C(()=>{let i=new g;return i.subscribe(a=>{if(document.body.setAttribute("data-md-color-switching",""),a.color.media==="(prefers-color-scheme)"){let s=matchMedia("(prefers-color-scheme: light)"),p=document.querySelector(s.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");a.color.scheme=p.getAttribute("data-md-color-scheme"),a.color.primary=p.getAttribute("data-md-color-primary"),a.color.accent=p.getAttribute("data-md-color-accent")}for(let[s,p]of Object.entries(a.color))document.body.setAttribute(`data-md-color-${s}`,p);for(let s=0;sa.key==="Enter"),re(i,(a,s)=>s)).subscribe(({index:a})=>{a=(a+1)%t.length,t[a].click(),t[a].focus()}),i.pipe(m(()=>{let a=Se("header"),s=window.getComputedStyle(a);return o.content=s.colorScheme,s.backgroundColor.match(/\d+/g).map(p=>(+p).toString(16).padStart(2,"0")).join("")})).subscribe(a=>r.content=`#${a}`),i.pipe(ve(se)).subscribe(()=>{document.body.removeAttribute("data-md-color-switching")}),es(t).pipe(W(n.pipe(Ce(1))),ct(),w(a=>i.next(a)),_(()=>i.complete()),m(a=>$({ref:e},a)))})}function Xn(e,{progress$:t}){return C(()=>{let r=new g;return r.subscribe(({value:o})=>{e.style.setProperty("--md-progress-value",`${o}`)}),t.pipe(w(o=>r.next({value:o})),_(()=>r.complete()),m(o=>({ref:e,value:o})))})}var Jr=Lt(Br());function ts(e){e.setAttribute("data-md-copying","");let t=e.closest("[data-copy]"),r=t?t.getAttribute("data-copy"):e.innerText;return e.removeAttribute("data-md-copying"),r.trimEnd()}function Zn({alert$:e}){Jr.default.isSupported()&&new j(t=>{new Jr.default("[data-clipboard-target], [data-clipboard-text]",{text:r=>r.getAttribute("data-clipboard-text")||ts(R(r.getAttribute("data-clipboard-target")))}).on("success",r=>t.next(r))}).pipe(w(t=>{t.trigger.focus()}),m(()=>Ee("clipboard.copied"))).subscribe(e)}function ei(e,t){return e.protocol=t.protocol,e.hostname=t.hostname,e}function rs(e,t){let r=new Map;for(let o of P("url",e)){let n=R("loc",o),i=[ei(new URL(n.textContent),t)];r.set(`${i[0]}`,i);for(let a of P("[rel=alternate]",o)){let s=a.getAttribute("href");s!=null&&i.push(ei(new URL(s),t))}}return r}function ur(e){return un(new URL("sitemap.xml",e)).pipe(m(t=>rs(t,new URL(e))),de(()=>I(new Map)))}function os(e,t){if(!(e.target instanceof Element))return S;let r=e.target.closest("a");if(r===null)return S;if(r.target||e.metaKey||e.ctrlKey)return S;let o=new URL(r.href);return o.search=o.hash="",t.has(`${o}`)?(e.preventDefault(),I(new URL(r.href))):S}function ti(e){let t=new Map;for(let r of P(":scope > *",e.head))t.set(r.outerHTML,r);return t}function ri(e){for(let t of P("[href], [src]",e))for(let r of["href","src"]){let o=t.getAttribute(r);if(o&&!/^(?:[a-z]+:)?\/\//i.test(o)){t[r]=t[r];break}}return I(e)}function ns(e){for(let o of["[data-md-component=announce]","[data-md-component=container]","[data-md-component=header-topic]","[data-md-component=outdated]","[data-md-component=logo]","[data-md-component=skip]",...B("navigation.tabs.sticky")?["[data-md-component=tabs]"]:[]]){let n=fe(o),i=fe(o,e);typeof n!="undefined"&&typeof i!="undefined"&&n.replaceWith(i)}let t=ti(document);for(let[o,n]of ti(e))t.has(o)?t.delete(o):document.head.appendChild(n);for(let o of t.values()){let n=o.getAttribute("name");n!=="theme-color"&&n!=="color-scheme"&&o.remove()}let r=Se("container");return We(P("script",r)).pipe(v(o=>{let n=e.createElement("script");if(o.src){for(let i of o.getAttributeNames())n.setAttribute(i,o.getAttribute(i));return o.replaceWith(n),new j(i=>{n.onload=()=>i.complete()})}else return n.textContent=o.textContent,o.replaceWith(n),S}),Z(),ie(document))}function oi({location$:e,viewport$:t,progress$:r}){let o=xe();if(location.protocol==="file:")return S;let n=ur(o.base);I(document).subscribe(ri);let i=h(document.body,"click").pipe(He(n),v(([p,c])=>os(p,c)),pe()),a=h(window,"popstate").pipe(m(ye),pe());i.pipe(re(t)).subscribe(([p,{offset:c}])=>{history.replaceState(c,""),history.pushState(null,"",p)}),O(i,a).subscribe(e);let s=e.pipe(te("pathname"),v(p=>fn(p,{progress$:r}).pipe(de(()=>(lt(p,!0),S)))),v(ri),v(ns),pe());return O(s.pipe(re(e,(p,c)=>c)),s.pipe(v(()=>e),te("hash")),e.pipe(K((p,c)=>p.pathname===c.pathname&&p.hash===c.hash),v(()=>i),w(()=>history.back()))).subscribe(p=>{var c,l;history.state!==null||!p.hash?window.scrollTo(0,(l=(c=history.state)==null?void 0:c.y)!=null?l:0):(history.scrollRestoration="auto",pn(p.hash),history.scrollRestoration="manual")}),e.subscribe(()=>{history.scrollRestoration="manual"}),h(window,"beforeunload").subscribe(()=>{history.scrollRestoration="auto"}),t.pipe(te("offset"),_e(100)).subscribe(({offset:p})=>{history.replaceState(p,"")}),s}var ni=Lt(qr());function ii(e){let t=e.separator.split("|").map(n=>n.replace(/(\(\?[!=<][^)]+\))/g,"").length===0?"\uFFFD":n).join("|"),r=new RegExp(t,"img"),o=(n,i,a)=>`${i}${a}`;return n=>{n=n.replace(/[\s*+\-:~^]+/g," ").trim();let i=new RegExp(`(^|${e.separator}|)(${n.replace(/[|\\{}()[\]^$+*?.-]/g,"\\$&").replace(r,"|")})`,"img");return a=>(0,ni.default)(a).replace(i,o).replace(/<\/mark>(\s+)]*>/img,"$1")}}function It(e){return e.type===1}function dr(e){return e.type===3}function ai(e,t){let r=yn(e);return O(I(location.protocol!=="file:"),Ne("search")).pipe(Ae(o=>o),v(()=>t)).subscribe(({config:o,docs:n})=>r.next({type:0,data:{config:o,docs:n,options:{suggest:B("search.suggest")}}})),r}function si(e){var l;let{selectedVersionSitemap:t,selectedVersionBaseURL:r,currentLocation:o,currentBaseURL:n}=e,i=(l=Xr(n))==null?void 0:l.pathname;if(i===void 0)return;let a=ss(o.pathname,i);if(a===void 0)return;let s=ps(t.keys());if(!t.has(s))return;let p=Xr(a,s);if(!p||!t.has(p.href))return;let c=Xr(a,r);if(c)return c.hash=o.hash,c.search=o.search,c}function Xr(e,t){try{return new URL(e,t)}catch(r){return}}function ss(e,t){if(e.startsWith(t))return e.slice(t.length)}function cs(e,t){let r=Math.min(e.length,t.length),o;for(o=0;oS)),o=r.pipe(m(n=>{let[,i]=t.base.match(/([^/]+)\/?$/);return n.find(({version:a,aliases:s})=>a===i||s.includes(i))||n[0]}));r.pipe(m(n=>new Map(n.map(i=>[`${new URL(`../${i.version}/`,t.base)}`,i]))),v(n=>h(document.body,"click").pipe(b(i=>!i.metaKey&&!i.ctrlKey),re(o),v(([i,a])=>{if(i.target instanceof Element){let s=i.target.closest("a");if(s&&!s.target&&n.has(s.href)){let p=s.href;return!i.target.closest(".md-version")&&n.get(p)===a?S:(i.preventDefault(),I(new URL(p)))}}return S}),v(i=>ur(i).pipe(m(a=>{var s;return(s=si({selectedVersionSitemap:a,selectedVersionBaseURL:i,currentLocation:ye(),currentBaseURL:t.base}))!=null?s:i})))))).subscribe(n=>lt(n,!0)),N([r,o]).subscribe(([n,i])=>{R(".md-header__topic").appendChild(Cn(n,i))}),e.pipe(v(()=>o)).subscribe(n=>{var s;let i=new URL(t.base),a=__md_get("__outdated",sessionStorage,i);if(a===null){a=!0;let p=((s=t.version)==null?void 0:s.default)||"latest";Array.isArray(p)||(p=[p]);e:for(let c of p)for(let l of n.aliases.concat(n.version))if(new RegExp(c,"i").test(l)){a=!1;break e}__md_set("__outdated",a,sessionStorage,i)}if(a)for(let p of ae("outdated"))p.hidden=!1})}function ls(e,{worker$:t}){let{searchParams:r}=ye();r.has("q")&&(Je("search",!0),e.value=r.get("q"),e.focus(),Ne("search").pipe(Ae(i=>!i)).subscribe(()=>{let i=ye();i.searchParams.delete("q"),history.replaceState({},"",`${i}`)}));let o=et(e),n=O(t.pipe(Ae(It)),h(e,"keyup"),o).pipe(m(()=>e.value),K());return N([n,o]).pipe(m(([i,a])=>({value:i,focus:a})),G(1))}function pi(e,{worker$:t}){let r=new g,o=r.pipe(Z(),ie(!0));N([t.pipe(Ae(It)),r],(i,a)=>a).pipe(te("value")).subscribe(({value:i})=>t.next({type:2,data:i})),r.pipe(te("focus")).subscribe(({focus:i})=>{i&&Je("search",i)}),h(e.form,"reset").pipe(W(o)).subscribe(()=>e.focus());let n=R("header [for=__search]");return h(n,"click").subscribe(()=>e.focus()),ls(e,{worker$:t}).pipe(w(i=>r.next(i)),_(()=>r.complete()),m(i=>$({ref:e},i)),G(1))}function li(e,{worker$:t,query$:r}){let o=new g,n=on(e.parentElement).pipe(b(Boolean)),i=e.parentElement,a=R(":scope > :first-child",e),s=R(":scope > :last-child",e);Ne("search").subscribe(l=>s.setAttribute("role",l?"list":"presentation")),o.pipe(re(r),Wr(t.pipe(Ae(It)))).subscribe(([{items:l},{value:f}])=>{switch(l.length){case 0:a.textContent=f.length?Ee("search.result.none"):Ee("search.result.placeholder");break;case 1:a.textContent=Ee("search.result.one");break;default:let u=sr(l.length);a.textContent=Ee("search.result.other",u)}});let p=o.pipe(w(()=>s.innerHTML=""),v(({items:l})=>O(I(...l.slice(0,10)),I(...l.slice(10)).pipe(Be(4),Dr(n),v(([f])=>f)))),m(Mn),pe());return p.subscribe(l=>s.appendChild(l)),p.pipe(ne(l=>{let f=fe("details",l);return typeof f=="undefined"?S:h(f,"toggle").pipe(W(o),m(()=>f))})).subscribe(l=>{l.open===!1&&l.offsetTop<=i.scrollTop&&i.scrollTo({top:l.offsetTop})}),t.pipe(b(dr),m(({data:l})=>l)).pipe(w(l=>o.next(l)),_(()=>o.complete()),m(l=>$({ref:e},l)))}function ms(e,{query$:t}){return t.pipe(m(({value:r})=>{let o=ye();return o.hash="",r=r.replace(/\s+/g,"+").replace(/&/g,"%26").replace(/=/g,"%3D"),o.search=`q=${r}`,{url:o}}))}function mi(e,t){let r=new g,o=r.pipe(Z(),ie(!0));return r.subscribe(({url:n})=>{e.setAttribute("data-clipboard-text",e.href),e.href=`${n}`}),h(e,"click").pipe(W(o)).subscribe(n=>n.preventDefault()),ms(e,t).pipe(w(n=>r.next(n)),_(()=>r.complete()),m(n=>$({ref:e},n)))}function fi(e,{worker$:t,keyboard$:r}){let o=new g,n=Se("search-query"),i=O(h(n,"keydown"),h(n,"focus")).pipe(ve(se),m(()=>n.value),K());return o.pipe(He(i),m(([{suggest:s},p])=>{let c=p.split(/([\s-]+)/);if(s!=null&&s.length&&c[c.length-1]){let l=s[s.length-1];l.startsWith(c[c.length-1])&&(c[c.length-1]=l)}else c.length=0;return c})).subscribe(s=>e.innerHTML=s.join("").replace(/\s/g," ")),r.pipe(b(({mode:s})=>s==="search")).subscribe(s=>{switch(s.type){case"ArrowRight":e.innerText.length&&n.selectionStart===n.value.length&&(n.value=e.innerText);break}}),t.pipe(b(dr),m(({data:s})=>s)).pipe(w(s=>o.next(s)),_(()=>o.complete()),m(()=>({ref:e})))}function ui(e,{index$:t,keyboard$:r}){let o=xe();try{let n=ai(o.search,t),i=Se("search-query",e),a=Se("search-result",e);h(e,"click").pipe(b(({target:p})=>p instanceof Element&&!!p.closest("a"))).subscribe(()=>Je("search",!1)),r.pipe(b(({mode:p})=>p==="search")).subscribe(p=>{let c=Ie();switch(p.type){case"Enter":if(c===i){let l=new Map;for(let f of P(":first-child [href]",a)){let u=f.firstElementChild;l.set(f,parseFloat(u.getAttribute("data-md-score")))}if(l.size){let[[f]]=[...l].sort(([,u],[,d])=>d-u);f.click()}p.claim()}break;case"Escape":case"Tab":Je("search",!1),i.blur();break;case"ArrowUp":case"ArrowDown":if(typeof c=="undefined")i.focus();else{let l=[i,...P(":not(details) > [href], summary, details[open] [href]",a)],f=Math.max(0,(Math.max(0,l.indexOf(c))+l.length+(p.type==="ArrowUp"?-1:1))%l.length);l[f].focus()}p.claim();break;default:i!==Ie()&&i.focus()}}),r.pipe(b(({mode:p})=>p==="global")).subscribe(p=>{switch(p.type){case"f":case"s":case"/":i.focus(),i.select(),p.claim();break}});let s=pi(i,{worker$:n});return O(s,li(a,{worker$:n,query$:s})).pipe(Re(...ae("search-share",e).map(p=>mi(p,{query$:s})),...ae("search-suggest",e).map(p=>fi(p,{worker$:n,keyboard$:r}))))}catch(n){return e.hidden=!0,Ye}}function di(e,{index$:t,location$:r}){return N([t,r.pipe(Q(ye()),b(o=>!!o.searchParams.get("h")))]).pipe(m(([o,n])=>ii(o.config)(n.searchParams.get("h"))),m(o=>{var a;let n=new Map,i=document.createNodeIterator(e,NodeFilter.SHOW_TEXT);for(let s=i.nextNode();s;s=i.nextNode())if((a=s.parentElement)!=null&&a.offsetHeight){let p=s.textContent,c=o(p);c.length>p.length&&n.set(s,c)}for(let[s,p]of n){let{childNodes:c}=x("span",null,p);s.replaceWith(...Array.from(c))}return{ref:e,nodes:n}}))}function fs(e,{viewport$:t,main$:r}){let o=e.closest(".md-grid"),n=o.offsetTop-o.parentElement.offsetTop;return N([r,t]).pipe(m(([{offset:i,height:a},{offset:{y:s}}])=>(a=a+Math.min(n,Math.max(0,s-i))-n,{height:a,locked:s>=i+n})),K((i,a)=>i.height===a.height&&i.locked===a.locked))}function Zr(e,o){var n=o,{header$:t}=n,r=so(n,["header$"]);let i=R(".md-sidebar__scrollwrap",e),{y:a}=De(i);return C(()=>{let s=new g,p=s.pipe(Z(),ie(!0)),c=s.pipe(Me(0,me));return c.pipe(re(t)).subscribe({next([{height:l},{height:f}]){i.style.height=`${l-2*a}px`,e.style.top=`${f}px`},complete(){i.style.height="",e.style.top=""}}),c.pipe(Ae()).subscribe(()=>{for(let l of P(".md-nav__link--active[href]",e)){if(!l.clientHeight)continue;let f=l.closest(".md-sidebar__scrollwrap");if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=ce(f);f.scrollTo({top:u-d/2})}}}),ue(P("label[tabindex]",e)).pipe(ne(l=>h(l,"click").pipe(ve(se),m(()=>l),W(p)))).subscribe(l=>{let f=R(`[id="${l.htmlFor}"]`);R(`[aria-labelledby="${l.id}"]`).setAttribute("aria-expanded",`${f.checked}`)}),fs(e,r).pipe(w(l=>s.next(l)),_(()=>s.complete()),m(l=>$({ref:e},l)))})}function hi(e,t){if(typeof t!="undefined"){let r=`https://api.github.com/repos/${e}/${t}`;return st(je(`${r}/releases/latest`).pipe(de(()=>S),m(o=>({version:o.tag_name})),Ve({})),je(r).pipe(de(()=>S),m(o=>({stars:o.stargazers_count,forks:o.forks_count})),Ve({}))).pipe(m(([o,n])=>$($({},o),n)))}else{let r=`https://api.github.com/users/${e}`;return je(r).pipe(m(o=>({repositories:o.public_repos})),Ve({}))}}function bi(e,t){let r=`https://${e}/api/v4/projects/${encodeURIComponent(t)}`;return st(je(`${r}/releases/permalink/latest`).pipe(de(()=>S),m(({tag_name:o})=>({version:o})),Ve({})),je(r).pipe(de(()=>S),m(({star_count:o,forks_count:n})=>({stars:o,forks:n})),Ve({}))).pipe(m(([o,n])=>$($({},o),n)))}function vi(e){let t=e.match(/^.+github\.com\/([^/]+)\/?([^/]+)?/i);if(t){let[,r,o]=t;return hi(r,o)}if(t=e.match(/^.+?([^/]*gitlab[^/]+)\/(.+?)\/?$/i),t){let[,r,o]=t;return bi(r,o)}return S}var us;function ds(e){return us||(us=C(()=>{let t=__md_get("__source",sessionStorage);if(t)return I(t);if(ae("consent").length){let o=__md_get("__consent");if(!(o&&o.github))return S}return vi(e.href).pipe(w(o=>__md_set("__source",o,sessionStorage)))}).pipe(de(()=>S),b(t=>Object.keys(t).length>0),m(t=>({facts:t})),G(1)))}function gi(e){let t=R(":scope > :last-child",e);return C(()=>{let r=new g;return r.subscribe(({facts:o})=>{t.appendChild(_n(o)),t.classList.add("md-source__repository--active")}),ds(e).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}function hs(e,{viewport$:t,header$:r}){return ge(document.body).pipe(v(()=>mr(e,{header$:r,viewport$:t})),m(({offset:{y:o}})=>({hidden:o>=10})),te("hidden"))}function yi(e,t){return C(()=>{let r=new g;return r.subscribe({next({hidden:o}){e.hidden=o},complete(){e.hidden=!1}}),(B("navigation.tabs.sticky")?I({hidden:!1}):hs(e,t)).pipe(w(o=>r.next(o)),_(()=>r.complete()),m(o=>$({ref:e},o)))})}function bs(e,{viewport$:t,header$:r}){let o=new Map,n=P(".md-nav__link",e);for(let s of n){let p=decodeURIComponent(s.hash.substring(1)),c=fe(`[id="${p}"]`);typeof c!="undefined"&&o.set(s,c)}let i=r.pipe(te("height"),m(({height:s})=>{let p=Se("main"),c=R(":scope > :first-child",p);return s+.8*(c.offsetTop-p.offsetTop)}),pe());return ge(document.body).pipe(te("height"),v(s=>C(()=>{let p=[];return I([...o].reduce((c,[l,f])=>{for(;p.length&&o.get(p[p.length-1]).tagName>=f.tagName;)p.pop();let u=f.offsetTop;for(;!u&&f.parentElement;)f=f.parentElement,u=f.offsetTop;let d=f.offsetParent;for(;d;d=d.offsetParent)u+=d.offsetTop;return c.set([...p=[...p,l]].reverse(),u)},new Map))}).pipe(m(p=>new Map([...p].sort(([,c],[,l])=>c-l))),He(i),v(([p,c])=>t.pipe(Fr(([l,f],{offset:{y:u},size:d})=>{let y=u+d.height>=Math.floor(s.height);for(;f.length;){let[,L]=f[0];if(L-c=u&&!y)f=[l.pop(),...f];else break}return[l,f]},[[],[...p]]),K((l,f)=>l[0]===f[0]&&l[1]===f[1])))))).pipe(m(([s,p])=>({prev:s.map(([c])=>c),next:p.map(([c])=>c)})),Q({prev:[],next:[]}),Be(2,1),m(([s,p])=>s.prev.length{let i=new g,a=i.pipe(Z(),ie(!0));if(i.subscribe(({prev:s,next:p})=>{for(let[c]of p)c.classList.remove("md-nav__link--passed"),c.classList.remove("md-nav__link--active");for(let[c,[l]]of s.entries())l.classList.add("md-nav__link--passed"),l.classList.toggle("md-nav__link--active",c===s.length-1)}),B("toc.follow")){let s=O(t.pipe(_e(1),m(()=>{})),t.pipe(_e(250),m(()=>"smooth")));i.pipe(b(({prev:p})=>p.length>0),He(o.pipe(ve(se))),re(s)).subscribe(([[{prev:p}],c])=>{let[l]=p[p.length-1];if(l.offsetHeight){let f=cr(l);if(typeof f!="undefined"){let u=l.offsetTop-f.offsetTop,{height:d}=ce(f);f.scrollTo({top:u-d/2,behavior:c})}}})}return B("navigation.tracking")&&t.pipe(W(a),te("offset"),_e(250),Ce(1),W(n.pipe(Ce(1))),ct({delay:250}),re(i)).subscribe(([,{prev:s}])=>{let p=ye(),c=s[s.length-1];if(c&&c.length){let[l]=c,{hash:f}=new URL(l.href);p.hash!==f&&(p.hash=f,history.replaceState({},"",`${p}`))}else p.hash="",history.replaceState({},"",`${p}`)}),bs(e,{viewport$:t,header$:r}).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))})}function vs(e,{viewport$:t,main$:r,target$:o}){let n=t.pipe(m(({offset:{y:a}})=>a),Be(2,1),m(([a,s])=>a>s&&s>0),K()),i=r.pipe(m(({active:a})=>a));return N([i,n]).pipe(m(([a,s])=>!(a&&s)),K(),W(o.pipe(Ce(1))),ie(!0),ct({delay:250}),m(a=>({hidden:a})))}function Ei(e,{viewport$:t,header$:r,main$:o,target$:n}){let i=new g,a=i.pipe(Z(),ie(!0));return i.subscribe({next({hidden:s}){e.hidden=s,s?(e.setAttribute("tabindex","-1"),e.blur()):e.removeAttribute("tabindex")},complete(){e.style.top="",e.hidden=!0,e.removeAttribute("tabindex")}}),r.pipe(W(a),te("height")).subscribe(({height:s})=>{e.style.top=`${s+16}px`}),h(e,"click").subscribe(s=>{s.preventDefault(),window.scrollTo({top:0})}),vs(e,{viewport$:t,main$:o,target$:n}).pipe(w(s=>i.next(s)),_(()=>i.complete()),m(s=>$({ref:e},s)))}function wi({document$:e,viewport$:t}){e.pipe(v(()=>P(".md-ellipsis")),ne(r=>tt(r).pipe(W(e.pipe(Ce(1))),b(o=>o),m(()=>r),Te(1))),b(r=>r.offsetWidth{let o=r.innerText,n=r.closest("a")||r;return n.title=o,B("content.tooltips")?mt(n,{viewport$:t}).pipe(W(e.pipe(Ce(1))),_(()=>n.removeAttribute("title"))):S})).subscribe(),B("content.tooltips")&&e.pipe(v(()=>P(".md-status")),ne(r=>mt(r,{viewport$:t}))).subscribe()}function Ti({document$:e,tablet$:t}){e.pipe(v(()=>P(".md-toggle--indeterminate")),w(r=>{r.indeterminate=!0,r.checked=!1}),ne(r=>h(r,"change").pipe(Vr(()=>r.classList.contains("md-toggle--indeterminate")),m(()=>r))),re(t)).subscribe(([r,o])=>{r.classList.remove("md-toggle--indeterminate"),o&&(r.checked=!1)})}function gs(){return/(iPad|iPhone|iPod)/.test(navigator.userAgent)}function Si({document$:e}){e.pipe(v(()=>P("[data-md-scrollfix]")),w(t=>t.removeAttribute("data-md-scrollfix")),b(gs),ne(t=>h(t,"touchstart").pipe(m(()=>t)))).subscribe(t=>{let r=t.scrollTop;r===0?t.scrollTop=1:r+t.offsetHeight===t.scrollHeight&&(t.scrollTop=r-1)})}function Oi({viewport$:e,tablet$:t}){N([Ne("search"),t]).pipe(m(([r,o])=>r&&!o),v(r=>I(r).pipe(Ge(r?400:100))),re(e)).subscribe(([r,{offset:{y:o}}])=>{if(r)document.body.setAttribute("data-md-scrolllock",""),document.body.style.top=`-${o}px`;else{let n=-1*parseInt(document.body.style.top,10);document.body.removeAttribute("data-md-scrolllock"),document.body.style.top="",n&&window.scrollTo(0,n)}})}Object.entries||(Object.entries=function(e){let t=[];for(let r of Object.keys(e))t.push([r,e[r]]);return t});Object.values||(Object.values=function(e){let t=[];for(let r of Object.keys(e))t.push(e[r]);return t});typeof Element!="undefined"&&(Element.prototype.scrollTo||(Element.prototype.scrollTo=function(e,t){typeof e=="object"?(this.scrollLeft=e.left,this.scrollTop=e.top):(this.scrollLeft=e,this.scrollTop=t)}),Element.prototype.replaceWith||(Element.prototype.replaceWith=function(...e){let t=this.parentNode;if(t){e.length===0&&t.removeChild(this);for(let r=e.length-1;r>=0;r--){let o=e[r];typeof o=="string"?o=document.createTextNode(o):o.parentNode&&o.parentNode.removeChild(o),r?t.insertBefore(this.previousSibling,o):t.replaceChild(o,this)}}}));function ys(){return location.protocol==="file:"?wt(`${new URL("search/search_index.js",eo.base)}`).pipe(m(()=>__index),G(1)):je(new URL("search/search_index.json",eo.base))}document.documentElement.classList.remove("no-js");document.documentElement.classList.add("js");var ot=Go(),Ft=sn(),Ot=ln(Ft),to=an(),Oe=gn(),hr=$t("(min-width: 960px)"),Mi=$t("(min-width: 1220px)"),_i=mn(),eo=xe(),Ai=document.forms.namedItem("search")?ys():Ye,ro=new g;Zn({alert$:ro});var oo=new g;B("navigation.instant")&&oi({location$:Ft,viewport$:Oe,progress$:oo}).subscribe(ot);var Li;((Li=eo.version)==null?void 0:Li.provider)==="mike"&&ci({document$:ot});O(Ft,Ot).pipe(Ge(125)).subscribe(()=>{Je("drawer",!1),Je("search",!1)});to.pipe(b(({mode:e})=>e==="global")).subscribe(e=>{switch(e.type){case"p":case",":let t=fe("link[rel=prev]");typeof t!="undefined"&<(t);break;case"n":case".":let r=fe("link[rel=next]");typeof r!="undefined"&<(r);break;case"Enter":let o=Ie();o instanceof HTMLLabelElement&&o.click()}});wi({viewport$:Oe,document$:ot});Ti({document$:ot,tablet$:hr});Si({document$:ot});Oi({viewport$:Oe,tablet$:hr});var rt=Kn(Se("header"),{viewport$:Oe}),jt=ot.pipe(m(()=>Se("main")),v(e=>Gn(e,{viewport$:Oe,header$:rt})),G(1)),xs=O(...ae("consent").map(e=>En(e,{target$:Ot})),...ae("dialog").map(e=>qn(e,{alert$:ro})),...ae("palette").map(e=>Jn(e)),...ae("progress").map(e=>Xn(e,{progress$:oo})),...ae("search").map(e=>ui(e,{index$:Ai,keyboard$:to})),...ae("source").map(e=>gi(e))),Es=C(()=>O(...ae("announce").map(e=>xn(e)),...ae("content").map(e=>Nn(e,{viewport$:Oe,target$:Ot,print$:_i})),...ae("content").map(e=>B("search.highlight")?di(e,{index$:Ai,location$:Ft}):S),...ae("header").map(e=>Yn(e,{viewport$:Oe,header$:rt,main$:jt})),...ae("header-title").map(e=>Bn(e,{viewport$:Oe,header$:rt})),...ae("sidebar").map(e=>e.getAttribute("data-md-type")==="navigation"?zr(Mi,()=>Zr(e,{viewport$:Oe,header$:rt,main$:jt})):zr(hr,()=>Zr(e,{viewport$:Oe,header$:rt,main$:jt}))),...ae("tabs").map(e=>yi(e,{viewport$:Oe,header$:rt})),...ae("toc").map(e=>xi(e,{viewport$:Oe,header$:rt,main$:jt,target$:Ot})),...ae("top").map(e=>Ei(e,{viewport$:Oe,header$:rt,main$:jt,target$:Ot})))),Ci=ot.pipe(v(()=>Es),Re(xs),G(1));Ci.subscribe();window.document$=ot;window.location$=Ft;window.target$=Ot;window.keyboard$=to;window.viewport$=Oe;window.tablet$=hr;window.screen$=Mi;window.print$=_i;window.alert$=ro;window.progress$=oo;window.component$=Ci;})(); +//# sourceMappingURL=bundle.13a4f30d.min.js.map + diff --git a/mddocs/generated/en/assets/javascripts/bundle.13a4f30d.min.js.map b/mddocs/generated/en/assets/javascripts/bundle.13a4f30d.min.js.map new file mode 100644 index 000000000..8941cb895 --- /dev/null +++ b/mddocs/generated/en/assets/javascripts/bundle.13a4f30d.min.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["node_modules/focus-visible/dist/focus-visible.js", "node_modules/escape-html/index.js", "node_modules/clipboard/dist/clipboard.js", "src/templates/assets/javascripts/bundle.ts", "node_modules/tslib/tslib.es6.mjs", "node_modules/rxjs/src/internal/util/isFunction.ts", "node_modules/rxjs/src/internal/util/createErrorClass.ts", "node_modules/rxjs/src/internal/util/UnsubscriptionError.ts", "node_modules/rxjs/src/internal/util/arrRemove.ts", "node_modules/rxjs/src/internal/Subscription.ts", "node_modules/rxjs/src/internal/config.ts", "node_modules/rxjs/src/internal/scheduler/timeoutProvider.ts", "node_modules/rxjs/src/internal/util/reportUnhandledError.ts", "node_modules/rxjs/src/internal/util/noop.ts", "node_modules/rxjs/src/internal/NotificationFactories.ts", "node_modules/rxjs/src/internal/util/errorContext.ts", "node_modules/rxjs/src/internal/Subscriber.ts", "node_modules/rxjs/src/internal/symbol/observable.ts", "node_modules/rxjs/src/internal/util/identity.ts", "node_modules/rxjs/src/internal/util/pipe.ts", "node_modules/rxjs/src/internal/Observable.ts", "node_modules/rxjs/src/internal/util/lift.ts", "node_modules/rxjs/src/internal/operators/OperatorSubscriber.ts", "node_modules/rxjs/src/internal/scheduler/animationFrameProvider.ts", "node_modules/rxjs/src/internal/util/ObjectUnsubscribedError.ts", "node_modules/rxjs/src/internal/Subject.ts", "node_modules/rxjs/src/internal/BehaviorSubject.ts", "node_modules/rxjs/src/internal/scheduler/dateTimestampProvider.ts", "node_modules/rxjs/src/internal/ReplaySubject.ts", "node_modules/rxjs/src/internal/scheduler/Action.ts", "node_modules/rxjs/src/internal/scheduler/intervalProvider.ts", "node_modules/rxjs/src/internal/scheduler/AsyncAction.ts", "node_modules/rxjs/src/internal/Scheduler.ts", "node_modules/rxjs/src/internal/scheduler/AsyncScheduler.ts", "node_modules/rxjs/src/internal/scheduler/async.ts", "node_modules/rxjs/src/internal/scheduler/QueueAction.ts", "node_modules/rxjs/src/internal/scheduler/QueueScheduler.ts", "node_modules/rxjs/src/internal/scheduler/queue.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameAction.ts", "node_modules/rxjs/src/internal/scheduler/AnimationFrameScheduler.ts", "node_modules/rxjs/src/internal/scheduler/animationFrame.ts", "node_modules/rxjs/src/internal/observable/empty.ts", "node_modules/rxjs/src/internal/util/isScheduler.ts", "node_modules/rxjs/src/internal/util/args.ts", "node_modules/rxjs/src/internal/util/isArrayLike.ts", "node_modules/rxjs/src/internal/util/isPromise.ts", "node_modules/rxjs/src/internal/util/isInteropObservable.ts", "node_modules/rxjs/src/internal/util/isAsyncIterable.ts", "node_modules/rxjs/src/internal/util/throwUnobservableError.ts", "node_modules/rxjs/src/internal/symbol/iterator.ts", "node_modules/rxjs/src/internal/util/isIterable.ts", "node_modules/rxjs/src/internal/util/isReadableStreamLike.ts", "node_modules/rxjs/src/internal/observable/innerFrom.ts", "node_modules/rxjs/src/internal/util/executeSchedule.ts", "node_modules/rxjs/src/internal/operators/observeOn.ts", "node_modules/rxjs/src/internal/operators/subscribeOn.ts", "node_modules/rxjs/src/internal/scheduled/scheduleObservable.ts", "node_modules/rxjs/src/internal/scheduled/schedulePromise.ts", "node_modules/rxjs/src/internal/scheduled/scheduleArray.ts", "node_modules/rxjs/src/internal/scheduled/scheduleIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleAsyncIterable.ts", "node_modules/rxjs/src/internal/scheduled/scheduleReadableStreamLike.ts", "node_modules/rxjs/src/internal/scheduled/scheduled.ts", "node_modules/rxjs/src/internal/observable/from.ts", "node_modules/rxjs/src/internal/observable/of.ts", "node_modules/rxjs/src/internal/observable/throwError.ts", "node_modules/rxjs/src/internal/util/EmptyError.ts", "node_modules/rxjs/src/internal/util/isDate.ts", "node_modules/rxjs/src/internal/operators/map.ts", "node_modules/rxjs/src/internal/util/mapOneOrManyArgs.ts", "node_modules/rxjs/src/internal/util/argsArgArrayOrObject.ts", "node_modules/rxjs/src/internal/util/createObject.ts", "node_modules/rxjs/src/internal/observable/combineLatest.ts", "node_modules/rxjs/src/internal/operators/mergeInternals.ts", "node_modules/rxjs/src/internal/operators/mergeMap.ts", "node_modules/rxjs/src/internal/operators/mergeAll.ts", "node_modules/rxjs/src/internal/operators/concatAll.ts", "node_modules/rxjs/src/internal/observable/concat.ts", "node_modules/rxjs/src/internal/observable/defer.ts", "node_modules/rxjs/src/internal/observable/fromEvent.ts", "node_modules/rxjs/src/internal/observable/fromEventPattern.ts", "node_modules/rxjs/src/internal/observable/timer.ts", "node_modules/rxjs/src/internal/observable/merge.ts", "node_modules/rxjs/src/internal/observable/never.ts", "node_modules/rxjs/src/internal/util/argsOrArgArray.ts", "node_modules/rxjs/src/internal/operators/filter.ts", "node_modules/rxjs/src/internal/observable/zip.ts", "node_modules/rxjs/src/internal/operators/audit.ts", "node_modules/rxjs/src/internal/operators/auditTime.ts", "node_modules/rxjs/src/internal/operators/bufferCount.ts", "node_modules/rxjs/src/internal/operators/catchError.ts", "node_modules/rxjs/src/internal/operators/scanInternals.ts", "node_modules/rxjs/src/internal/operators/combineLatest.ts", "node_modules/rxjs/src/internal/operators/combineLatestWith.ts", "node_modules/rxjs/src/internal/operators/debounce.ts", "node_modules/rxjs/src/internal/operators/debounceTime.ts", "node_modules/rxjs/src/internal/operators/defaultIfEmpty.ts", "node_modules/rxjs/src/internal/operators/take.ts", "node_modules/rxjs/src/internal/operators/ignoreElements.ts", "node_modules/rxjs/src/internal/operators/mapTo.ts", "node_modules/rxjs/src/internal/operators/delayWhen.ts", "node_modules/rxjs/src/internal/operators/delay.ts", "node_modules/rxjs/src/internal/operators/distinctUntilChanged.ts", "node_modules/rxjs/src/internal/operators/distinctUntilKeyChanged.ts", "node_modules/rxjs/src/internal/operators/throwIfEmpty.ts", "node_modules/rxjs/src/internal/operators/endWith.ts", "node_modules/rxjs/src/internal/operators/finalize.ts", "node_modules/rxjs/src/internal/operators/first.ts", "node_modules/rxjs/src/internal/operators/takeLast.ts", "node_modules/rxjs/src/internal/operators/merge.ts", "node_modules/rxjs/src/internal/operators/mergeWith.ts", "node_modules/rxjs/src/internal/operators/repeat.ts", "node_modules/rxjs/src/internal/operators/scan.ts", "node_modules/rxjs/src/internal/operators/share.ts", "node_modules/rxjs/src/internal/operators/shareReplay.ts", "node_modules/rxjs/src/internal/operators/skip.ts", "node_modules/rxjs/src/internal/operators/skipUntil.ts", "node_modules/rxjs/src/internal/operators/startWith.ts", "node_modules/rxjs/src/internal/operators/switchMap.ts", "node_modules/rxjs/src/internal/operators/takeUntil.ts", "node_modules/rxjs/src/internal/operators/takeWhile.ts", "node_modules/rxjs/src/internal/operators/tap.ts", "node_modules/rxjs/src/internal/operators/throttle.ts", "node_modules/rxjs/src/internal/operators/throttleTime.ts", "node_modules/rxjs/src/internal/operators/withLatestFrom.ts", "node_modules/rxjs/src/internal/operators/zip.ts", "node_modules/rxjs/src/internal/operators/zipWith.ts", "src/templates/assets/javascripts/browser/document/index.ts", "src/templates/assets/javascripts/browser/element/_/index.ts", "src/templates/assets/javascripts/browser/element/focus/index.ts", "src/templates/assets/javascripts/browser/element/hover/index.ts", "src/templates/assets/javascripts/utilities/h/index.ts", "src/templates/assets/javascripts/utilities/round/index.ts", "src/templates/assets/javascripts/browser/script/index.ts", "src/templates/assets/javascripts/browser/element/size/_/index.ts", "src/templates/assets/javascripts/browser/element/size/content/index.ts", "src/templates/assets/javascripts/browser/element/offset/_/index.ts", "src/templates/assets/javascripts/browser/element/offset/content/index.ts", "src/templates/assets/javascripts/browser/element/visibility/index.ts", "src/templates/assets/javascripts/browser/toggle/index.ts", "src/templates/assets/javascripts/browser/keyboard/index.ts", "src/templates/assets/javascripts/browser/location/_/index.ts", "src/templates/assets/javascripts/browser/location/hash/index.ts", "src/templates/assets/javascripts/browser/media/index.ts", "src/templates/assets/javascripts/browser/request/index.ts", "src/templates/assets/javascripts/browser/viewport/offset/index.ts", "src/templates/assets/javascripts/browser/viewport/size/index.ts", "src/templates/assets/javascripts/browser/viewport/_/index.ts", "src/templates/assets/javascripts/browser/viewport/at/index.ts", "src/templates/assets/javascripts/browser/worker/index.ts", "src/templates/assets/javascripts/_/index.ts", "src/templates/assets/javascripts/components/_/index.ts", "src/templates/assets/javascripts/components/announce/index.ts", "src/templates/assets/javascripts/components/consent/index.ts", "src/templates/assets/javascripts/templates/tooltip/index.tsx", "src/templates/assets/javascripts/templates/annotation/index.tsx", "src/templates/assets/javascripts/templates/clipboard/index.tsx", "src/templates/assets/javascripts/templates/search/index.tsx", "src/templates/assets/javascripts/templates/source/index.tsx", "src/templates/assets/javascripts/templates/tabbed/index.tsx", "src/templates/assets/javascripts/templates/table/index.tsx", "src/templates/assets/javascripts/templates/version/index.tsx", "src/templates/assets/javascripts/components/tooltip2/index.ts", "src/templates/assets/javascripts/components/content/annotation/_/index.ts", "src/templates/assets/javascripts/components/content/annotation/list/index.ts", "src/templates/assets/javascripts/components/content/annotation/block/index.ts", "src/templates/assets/javascripts/components/content/code/_/index.ts", "src/templates/assets/javascripts/components/content/details/index.ts", "src/templates/assets/javascripts/components/content/mermaid/index.css", "src/templates/assets/javascripts/components/content/mermaid/index.ts", "src/templates/assets/javascripts/components/content/table/index.ts", "src/templates/assets/javascripts/components/content/tabs/index.ts", "src/templates/assets/javascripts/components/content/_/index.ts", "src/templates/assets/javascripts/components/dialog/index.ts", "src/templates/assets/javascripts/components/tooltip/index.ts", "src/templates/assets/javascripts/components/header/_/index.ts", "src/templates/assets/javascripts/components/header/title/index.ts", "src/templates/assets/javascripts/components/main/index.ts", "src/templates/assets/javascripts/components/palette/index.ts", "src/templates/assets/javascripts/components/progress/index.ts", "src/templates/assets/javascripts/integrations/clipboard/index.ts", "src/templates/assets/javascripts/integrations/sitemap/index.ts", "src/templates/assets/javascripts/integrations/instant/index.ts", "src/templates/assets/javascripts/integrations/search/highlighter/index.ts", "src/templates/assets/javascripts/integrations/search/worker/message/index.ts", "src/templates/assets/javascripts/integrations/search/worker/_/index.ts", "src/templates/assets/javascripts/integrations/version/findurl/index.ts", "src/templates/assets/javascripts/integrations/version/index.ts", "src/templates/assets/javascripts/components/search/query/index.ts", "src/templates/assets/javascripts/components/search/result/index.ts", "src/templates/assets/javascripts/components/search/share/index.ts", "src/templates/assets/javascripts/components/search/suggest/index.ts", "src/templates/assets/javascripts/components/search/_/index.ts", "src/templates/assets/javascripts/components/search/highlight/index.ts", "src/templates/assets/javascripts/components/sidebar/index.ts", "src/templates/assets/javascripts/components/source/facts/github/index.ts", "src/templates/assets/javascripts/components/source/facts/gitlab/index.ts", "src/templates/assets/javascripts/components/source/facts/_/index.ts", "src/templates/assets/javascripts/components/source/_/index.ts", "src/templates/assets/javascripts/components/tabs/index.ts", "src/templates/assets/javascripts/components/toc/index.ts", "src/templates/assets/javascripts/components/top/index.ts", "src/templates/assets/javascripts/patches/ellipsis/index.ts", "src/templates/assets/javascripts/patches/indeterminate/index.ts", "src/templates/assets/javascripts/patches/scrollfix/index.ts", "src/templates/assets/javascripts/patches/scrolllock/index.ts", "src/templates/assets/javascripts/polyfills/index.ts"], + "sourcesContent": ["(function (global, factory) {\n typeof exports === 'object' && typeof module !== 'undefined' ? factory() :\n typeof define === 'function' && define.amd ? define(factory) :\n (factory());\n}(this, (function () { 'use strict';\n\n /**\n * Applies the :focus-visible polyfill at the given scope.\n * A scope in this case is either the top-level Document or a Shadow Root.\n *\n * @param {(Document|ShadowRoot)} scope\n * @see https://github.com/WICG/focus-visible\n */\n function applyFocusVisiblePolyfill(scope) {\n var hadKeyboardEvent = true;\n var hadFocusVisibleRecently = false;\n var hadFocusVisibleRecentlyTimeout = null;\n\n var inputTypesAllowlist = {\n text: true,\n search: true,\n url: true,\n tel: true,\n email: true,\n password: true,\n number: true,\n date: true,\n month: true,\n week: true,\n time: true,\n datetime: true,\n 'datetime-local': true\n };\n\n /**\n * Helper function for legacy browsers and iframes which sometimes focus\n * elements like document, body, and non-interactive SVG.\n * @param {Element} el\n */\n function isValidFocusTarget(el) {\n if (\n el &&\n el !== document &&\n el.nodeName !== 'HTML' &&\n el.nodeName !== 'BODY' &&\n 'classList' in el &&\n 'contains' in el.classList\n ) {\n return true;\n }\n return false;\n }\n\n /**\n * Computes whether the given element should automatically trigger the\n * `focus-visible` class being added, i.e. whether it should always match\n * `:focus-visible` when focused.\n * @param {Element} el\n * @return {boolean}\n */\n function focusTriggersKeyboardModality(el) {\n var type = el.type;\n var tagName = el.tagName;\n\n if (tagName === 'INPUT' && inputTypesAllowlist[type] && !el.readOnly) {\n return true;\n }\n\n if (tagName === 'TEXTAREA' && !el.readOnly) {\n return true;\n }\n\n if (el.isContentEditable) {\n return true;\n }\n\n return false;\n }\n\n /**\n * Add the `focus-visible` class to the given element if it was not added by\n * the author.\n * @param {Element} el\n */\n function addFocusVisibleClass(el) {\n if (el.classList.contains('focus-visible')) {\n return;\n }\n el.classList.add('focus-visible');\n el.setAttribute('data-focus-visible-added', '');\n }\n\n /**\n * Remove the `focus-visible` class from the given element if it was not\n * originally added by the author.\n * @param {Element} el\n */\n function removeFocusVisibleClass(el) {\n if (!el.hasAttribute('data-focus-visible-added')) {\n return;\n }\n el.classList.remove('focus-visible');\n el.removeAttribute('data-focus-visible-added');\n }\n\n /**\n * If the most recent user interaction was via the keyboard;\n * and the key press did not include a meta, alt/option, or control key;\n * then the modality is keyboard. Otherwise, the modality is not keyboard.\n * Apply `focus-visible` to any current active element and keep track\n * of our keyboard modality state with `hadKeyboardEvent`.\n * @param {KeyboardEvent} e\n */\n function onKeyDown(e) {\n if (e.metaKey || e.altKey || e.ctrlKey) {\n return;\n }\n\n if (isValidFocusTarget(scope.activeElement)) {\n addFocusVisibleClass(scope.activeElement);\n }\n\n hadKeyboardEvent = true;\n }\n\n /**\n * If at any point a user clicks with a pointing device, ensure that we change\n * the modality away from keyboard.\n * This avoids the situation where a user presses a key on an already focused\n * element, and then clicks on a different element, focusing it with a\n * pointing device, while we still think we're in keyboard modality.\n * @param {Event} e\n */\n function onPointerDown(e) {\n hadKeyboardEvent = false;\n }\n\n /**\n * On `focus`, add the `focus-visible` class to the target if:\n * - the target received focus as a result of keyboard navigation, or\n * - the event target is an element that will likely require interaction\n * via the keyboard (e.g. a text box)\n * @param {Event} e\n */\n function onFocus(e) {\n // Prevent IE from focusing the document or HTML element.\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) {\n addFocusVisibleClass(e.target);\n }\n }\n\n /**\n * On `blur`, remove the `focus-visible` class from the target.\n * @param {Event} e\n */\n function onBlur(e) {\n if (!isValidFocusTarget(e.target)) {\n return;\n }\n\n if (\n e.target.classList.contains('focus-visible') ||\n e.target.hasAttribute('data-focus-visible-added')\n ) {\n // To detect a tab/window switch, we look for a blur event followed\n // rapidly by a visibility change.\n // If we don't see a visibility change within 100ms, it's probably a\n // regular focus change.\n hadFocusVisibleRecently = true;\n window.clearTimeout(hadFocusVisibleRecentlyTimeout);\n hadFocusVisibleRecentlyTimeout = window.setTimeout(function() {\n hadFocusVisibleRecently = false;\n }, 100);\n removeFocusVisibleClass(e.target);\n }\n }\n\n /**\n * If the user changes tabs, keep track of whether or not the previously\n * focused element had .focus-visible.\n * @param {Event} e\n */\n function onVisibilityChange(e) {\n if (document.visibilityState === 'hidden') {\n // If the tab becomes active again, the browser will handle calling focus\n // on the element (Safari actually calls it twice).\n // If this tab change caused a blur on an element with focus-visible,\n // re-apply the class when the user switches back to the tab.\n if (hadFocusVisibleRecently) {\n hadKeyboardEvent = true;\n }\n addInitialPointerMoveListeners();\n }\n }\n\n /**\n * Add a group of listeners to detect usage of any pointing devices.\n * These listeners will be added when the polyfill first loads, and anytime\n * the window is blurred, so that they are active when the window regains\n * focus.\n */\n function addInitialPointerMoveListeners() {\n document.addEventListener('mousemove', onInitialPointerMove);\n document.addEventListener('mousedown', onInitialPointerMove);\n document.addEventListener('mouseup', onInitialPointerMove);\n document.addEventListener('pointermove', onInitialPointerMove);\n document.addEventListener('pointerdown', onInitialPointerMove);\n document.addEventListener('pointerup', onInitialPointerMove);\n document.addEventListener('touchmove', onInitialPointerMove);\n document.addEventListener('touchstart', onInitialPointerMove);\n document.addEventListener('touchend', onInitialPointerMove);\n }\n\n function removeInitialPointerMoveListeners() {\n document.removeEventListener('mousemove', onInitialPointerMove);\n document.removeEventListener('mousedown', onInitialPointerMove);\n document.removeEventListener('mouseup', onInitialPointerMove);\n document.removeEventListener('pointermove', onInitialPointerMove);\n document.removeEventListener('pointerdown', onInitialPointerMove);\n document.removeEventListener('pointerup', onInitialPointerMove);\n document.removeEventListener('touchmove', onInitialPointerMove);\n document.removeEventListener('touchstart', onInitialPointerMove);\n document.removeEventListener('touchend', onInitialPointerMove);\n }\n\n /**\n * When the polfyill first loads, assume the user is in keyboard modality.\n * If any event is received from a pointing device (e.g. mouse, pointer,\n * touch), turn off keyboard modality.\n * This accounts for situations where focus enters the page from the URL bar.\n * @param {Event} e\n */\n function onInitialPointerMove(e) {\n // Work around a Safari quirk that fires a mousemove on whenever the\n // window blurs, even if you're tabbing out of the page. \u00AF\\_(\u30C4)_/\u00AF\n if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') {\n return;\n }\n\n hadKeyboardEvent = false;\n removeInitialPointerMoveListeners();\n }\n\n // For some kinds of state, we are interested in changes at the global scope\n // only. For example, global pointer input, global key presses and global\n // visibility change should affect the state at every scope:\n document.addEventListener('keydown', onKeyDown, true);\n document.addEventListener('mousedown', onPointerDown, true);\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('touchstart', onPointerDown, true);\n document.addEventListener('visibilitychange', onVisibilityChange, true);\n\n addInitialPointerMoveListeners();\n\n // For focus and blur, we specifically care about state changes in the local\n // scope. This is because focus / blur events that originate from within a\n // shadow root are not re-dispatched from the host element if it was already\n // the active element in its own scope:\n scope.addEventListener('focus', onFocus, true);\n scope.addEventListener('blur', onBlur, true);\n\n // We detect that a node is a ShadowRoot by ensuring that it is a\n // DocumentFragment and also has a host property. This check covers native\n // implementation and polyfill implementation transparently. If we only cared\n // about the native implementation, we could just check if the scope was\n // an instance of a ShadowRoot.\n if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) {\n // Since a ShadowRoot is a special kind of DocumentFragment, it does not\n // have a root element to add a class to. So, we add this attribute to the\n // host element instead:\n scope.host.setAttribute('data-js-focus-visible', '');\n } else if (scope.nodeType === Node.DOCUMENT_NODE) {\n document.documentElement.classList.add('js-focus-visible');\n document.documentElement.setAttribute('data-js-focus-visible', '');\n }\n }\n\n // It is important to wrap all references to global window and document in\n // these checks to support server-side rendering use cases\n // @see https://github.com/WICG/focus-visible/issues/199\n if (typeof window !== 'undefined' && typeof document !== 'undefined') {\n // Make the polyfill helper globally available. This can be used as a signal\n // to interested libraries that wish to coordinate with the polyfill for e.g.,\n // applying the polyfill to a shadow root:\n window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill;\n\n // Notify interested libraries of the polyfill's presence, in case the\n // polyfill was loaded lazily:\n var event;\n\n try {\n event = new CustomEvent('focus-visible-polyfill-ready');\n } catch (error) {\n // IE11 does not support using CustomEvent as a constructor directly:\n event = document.createEvent('CustomEvent');\n event.initCustomEvent('focus-visible-polyfill-ready', false, false, {});\n }\n\n window.dispatchEvent(event);\n }\n\n if (typeof document !== 'undefined') {\n // Apply the polyfill to the global document, so that no JavaScript\n // coordination is required to use the polyfill in the top-level document:\n applyFocusVisiblePolyfill(document);\n }\n\n})));\n", "/*!\n * escape-html\n * Copyright(c) 2012-2013 TJ Holowaychuk\n * Copyright(c) 2015 Andreas Lubbe\n * Copyright(c) 2015 Tiancheng \"Timothy\" Gu\n * MIT Licensed\n */\n\n'use strict';\n\n/**\n * Module variables.\n * @private\n */\n\nvar matchHtmlRegExp = /[\"'&<>]/;\n\n/**\n * Module exports.\n * @public\n */\n\nmodule.exports = escapeHtml;\n\n/**\n * Escape special characters in the given string of html.\n *\n * @param {string} string The string to escape for inserting into HTML\n * @return {string}\n * @public\n */\n\nfunction escapeHtml(string) {\n var str = '' + string;\n var match = matchHtmlRegExp.exec(str);\n\n if (!match) {\n return str;\n }\n\n var escape;\n var html = '';\n var index = 0;\n var lastIndex = 0;\n\n for (index = match.index; index < str.length; index++) {\n switch (str.charCodeAt(index)) {\n case 34: // \"\n escape = '"';\n break;\n case 38: // &\n escape = '&';\n break;\n case 39: // '\n escape = ''';\n break;\n case 60: // <\n escape = '<';\n break;\n case 62: // >\n escape = '>';\n break;\n default:\n continue;\n }\n\n if (lastIndex !== index) {\n html += str.substring(lastIndex, index);\n }\n\n lastIndex = index + 1;\n html += escape;\n }\n\n return lastIndex !== index\n ? html + str.substring(lastIndex, index)\n : html;\n}\n", "/*!\n * clipboard.js v2.0.11\n * https://clipboardjs.com/\n *\n * Licensed MIT \u00A9 Zeno Rocha\n */\n(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"ClipboardJS\"] = factory();\n\telse\n\t\troot[\"ClipboardJS\"] = factory();\n})(this, function() {\nreturn /******/ (function() { // webpackBootstrap\n/******/ \tvar __webpack_modules__ = ({\n\n/***/ 686:\n/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n\n// EXPORTS\n__webpack_require__.d(__webpack_exports__, {\n \"default\": function() { return /* binding */ clipboard; }\n});\n\n// EXTERNAL MODULE: ./node_modules/tiny-emitter/index.js\nvar tiny_emitter = __webpack_require__(279);\nvar tiny_emitter_default = /*#__PURE__*/__webpack_require__.n(tiny_emitter);\n// EXTERNAL MODULE: ./node_modules/good-listener/src/listen.js\nvar listen = __webpack_require__(370);\nvar listen_default = /*#__PURE__*/__webpack_require__.n(listen);\n// EXTERNAL MODULE: ./node_modules/select/src/select.js\nvar src_select = __webpack_require__(817);\nvar select_default = /*#__PURE__*/__webpack_require__.n(src_select);\n;// CONCATENATED MODULE: ./src/common/command.js\n/**\n * Executes a given operation type.\n * @param {String} type\n * @return {Boolean}\n */\nfunction command(type) {\n try {\n return document.execCommand(type);\n } catch (err) {\n return false;\n }\n}\n;// CONCATENATED MODULE: ./src/actions/cut.js\n\n\n/**\n * Cut action wrapper.\n * @param {String|HTMLElement} target\n * @return {String}\n */\n\nvar ClipboardActionCut = function ClipboardActionCut(target) {\n var selectedText = select_default()(target);\n command('cut');\n return selectedText;\n};\n\n/* harmony default export */ var actions_cut = (ClipboardActionCut);\n;// CONCATENATED MODULE: ./src/common/create-fake-element.js\n/**\n * Creates a fake textarea element with a value.\n * @param {String} value\n * @return {HTMLElement}\n */\nfunction createFakeElement(value) {\n var isRTL = document.documentElement.getAttribute('dir') === 'rtl';\n var fakeElement = document.createElement('textarea'); // Prevent zooming on iOS\n\n fakeElement.style.fontSize = '12pt'; // Reset box model\n\n fakeElement.style.border = '0';\n fakeElement.style.padding = '0';\n fakeElement.style.margin = '0'; // Move element out of screen horizontally\n\n fakeElement.style.position = 'absolute';\n fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px'; // Move element to the same position vertically\n\n var yPosition = window.pageYOffset || document.documentElement.scrollTop;\n fakeElement.style.top = \"\".concat(yPosition, \"px\");\n fakeElement.setAttribute('readonly', '');\n fakeElement.value = value;\n return fakeElement;\n}\n;// CONCATENATED MODULE: ./src/actions/copy.js\n\n\n\n/**\n * Create fake copy action wrapper using a fake element.\n * @param {String} target\n * @param {Object} options\n * @return {String}\n */\n\nvar fakeCopyAction = function fakeCopyAction(value, options) {\n var fakeElement = createFakeElement(value);\n options.container.appendChild(fakeElement);\n var selectedText = select_default()(fakeElement);\n command('copy');\n fakeElement.remove();\n return selectedText;\n};\n/**\n * Copy action wrapper.\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @return {String}\n */\n\n\nvar ClipboardActionCopy = function ClipboardActionCopy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n var selectedText = '';\n\n if (typeof target === 'string') {\n selectedText = fakeCopyAction(target, options);\n } else if (target instanceof HTMLInputElement && !['text', 'search', 'url', 'tel', 'password'].includes(target === null || target === void 0 ? void 0 : target.type)) {\n // If input type doesn't support `setSelectionRange`. Simulate it. https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/setSelectionRange\n selectedText = fakeCopyAction(target.value, options);\n } else {\n selectedText = select_default()(target);\n command('copy');\n }\n\n return selectedText;\n};\n\n/* harmony default export */ var actions_copy = (ClipboardActionCopy);\n;// CONCATENATED MODULE: ./src/actions/default.js\nfunction _typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\n\n\n/**\n * Inner function which performs selection from either `text` or `target`\n * properties and then executes copy or cut operations.\n * @param {Object} options\n */\n\nvar ClipboardActionDefault = function ClipboardActionDefault() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n // Defines base properties passed from constructor.\n var _options$action = options.action,\n action = _options$action === void 0 ? 'copy' : _options$action,\n container = options.container,\n target = options.target,\n text = options.text; // Sets the `action` to be performed which can be either 'copy' or 'cut'.\n\n if (action !== 'copy' && action !== 'cut') {\n throw new Error('Invalid \"action\" value, use either \"copy\" or \"cut\"');\n } // Sets the `target` property using an element that will be have its content copied.\n\n\n if (target !== undefined) {\n if (target && _typeof(target) === 'object' && target.nodeType === 1) {\n if (action === 'copy' && target.hasAttribute('disabled')) {\n throw new Error('Invalid \"target\" attribute. Please use \"readonly\" instead of \"disabled\" attribute');\n }\n\n if (action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {\n throw new Error('Invalid \"target\" attribute. You can\\'t cut text from elements with \"readonly\" or \"disabled\" attributes');\n }\n } else {\n throw new Error('Invalid \"target\" value, use a valid Element');\n }\n } // Define selection strategy based on `text` property.\n\n\n if (text) {\n return actions_copy(text, {\n container: container\n });\n } // Defines which selection strategy based on `target` property.\n\n\n if (target) {\n return action === 'cut' ? actions_cut(target) : actions_copy(target, {\n container: container\n });\n }\n};\n\n/* harmony default export */ var actions_default = (ClipboardActionDefault);\n;// CONCATENATED MODULE: ./src/clipboard.js\nfunction clipboard_typeof(obj) { \"@babel/helpers - typeof\"; if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { clipboard_typeof = function _typeof(obj) { return typeof obj; }; } else { clipboard_typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return clipboard_typeof(obj); }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (clipboard_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _isNativeReflectConstruct() { if (typeof Reflect === \"undefined\" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === \"function\") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\n\n\n\n\n\n/**\n * Helper function to retrieve attribute value.\n * @param {String} suffix\n * @param {Element} element\n */\n\nfunction getAttributeValue(suffix, element) {\n var attribute = \"data-clipboard-\".concat(suffix);\n\n if (!element.hasAttribute(attribute)) {\n return;\n }\n\n return element.getAttribute(attribute);\n}\n/**\n * Base class which takes one or more elements, adds event listeners to them,\n * and instantiates a new `ClipboardAction` on each click.\n */\n\n\nvar Clipboard = /*#__PURE__*/function (_Emitter) {\n _inherits(Clipboard, _Emitter);\n\n var _super = _createSuper(Clipboard);\n\n /**\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n * @param {Object} options\n */\n function Clipboard(trigger, options) {\n var _this;\n\n _classCallCheck(this, Clipboard);\n\n _this = _super.call(this);\n\n _this.resolveOptions(options);\n\n _this.listenClick(trigger);\n\n return _this;\n }\n /**\n * Defines if attributes would be resolved using internal setter functions\n * or custom functions that were passed in the constructor.\n * @param {Object} options\n */\n\n\n _createClass(Clipboard, [{\n key: \"resolveOptions\",\n value: function resolveOptions() {\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n this.action = typeof options.action === 'function' ? options.action : this.defaultAction;\n this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;\n this.text = typeof options.text === 'function' ? options.text : this.defaultText;\n this.container = clipboard_typeof(options.container) === 'object' ? options.container : document.body;\n }\n /**\n * Adds a click event listener to the passed trigger.\n * @param {String|HTMLElement|HTMLCollection|NodeList} trigger\n */\n\n }, {\n key: \"listenClick\",\n value: function listenClick(trigger) {\n var _this2 = this;\n\n this.listener = listen_default()(trigger, 'click', function (e) {\n return _this2.onClick(e);\n });\n }\n /**\n * Defines a new `ClipboardAction` on each click event.\n * @param {Event} e\n */\n\n }, {\n key: \"onClick\",\n value: function onClick(e) {\n var trigger = e.delegateTarget || e.currentTarget;\n var action = this.action(trigger) || 'copy';\n var text = actions_default({\n action: action,\n container: this.container,\n target: this.target(trigger),\n text: this.text(trigger)\n }); // Fires an event based on the copy operation result.\n\n this.emit(text ? 'success' : 'error', {\n action: action,\n text: text,\n trigger: trigger,\n clearSelection: function clearSelection() {\n if (trigger) {\n trigger.focus();\n }\n\n window.getSelection().removeAllRanges();\n }\n });\n }\n /**\n * Default `action` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultAction\",\n value: function defaultAction(trigger) {\n return getAttributeValue('action', trigger);\n }\n /**\n * Default `target` lookup function.\n * @param {Element} trigger\n */\n\n }, {\n key: \"defaultTarget\",\n value: function defaultTarget(trigger) {\n var selector = getAttributeValue('target', trigger);\n\n if (selector) {\n return document.querySelector(selector);\n }\n }\n /**\n * Allow fire programmatically a copy action\n * @param {String|HTMLElement} target\n * @param {Object} options\n * @returns Text copied.\n */\n\n }, {\n key: \"defaultText\",\n\n /**\n * Default `text` lookup function.\n * @param {Element} trigger\n */\n value: function defaultText(trigger) {\n return getAttributeValue('text', trigger);\n }\n /**\n * Destroy lifecycle.\n */\n\n }, {\n key: \"destroy\",\n value: function destroy() {\n this.listener.destroy();\n }\n }], [{\n key: \"copy\",\n value: function copy(target) {\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {\n container: document.body\n };\n return actions_copy(target, options);\n }\n /**\n * Allow fire programmatically a cut action\n * @param {String|HTMLElement} target\n * @returns Text cutted.\n */\n\n }, {\n key: \"cut\",\n value: function cut(target) {\n return actions_cut(target);\n }\n /**\n * Returns the support of the given action, or all actions if no action is\n * given.\n * @param {String} [action]\n */\n\n }, {\n key: \"isSupported\",\n value: function isSupported() {\n var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ['copy', 'cut'];\n var actions = typeof action === 'string' ? [action] : action;\n var support = !!document.queryCommandSupported;\n actions.forEach(function (action) {\n support = support && !!document.queryCommandSupported(action);\n });\n return support;\n }\n }]);\n\n return Clipboard;\n}((tiny_emitter_default()));\n\n/* harmony default export */ var clipboard = (Clipboard);\n\n/***/ }),\n\n/***/ 828:\n/***/ (function(module) {\n\nvar DOCUMENT_NODE_TYPE = 9;\n\n/**\n * A polyfill for Element.matches()\n */\nif (typeof Element !== 'undefined' && !Element.prototype.matches) {\n var proto = Element.prototype;\n\n proto.matches = proto.matchesSelector ||\n proto.mozMatchesSelector ||\n proto.msMatchesSelector ||\n proto.oMatchesSelector ||\n proto.webkitMatchesSelector;\n}\n\n/**\n * Finds the closest parent that matches a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @return {Function}\n */\nfunction closest (element, selector) {\n while (element && element.nodeType !== DOCUMENT_NODE_TYPE) {\n if (typeof element.matches === 'function' &&\n element.matches(selector)) {\n return element;\n }\n element = element.parentNode;\n }\n}\n\nmodule.exports = closest;\n\n\n/***/ }),\n\n/***/ 438:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar closest = __webpack_require__(828);\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction _delegate(element, selector, type, callback, useCapture) {\n var listenerFn = listener.apply(this, arguments);\n\n element.addEventListener(type, listenerFn, useCapture);\n\n return {\n destroy: function() {\n element.removeEventListener(type, listenerFn, useCapture);\n }\n }\n}\n\n/**\n * Delegates event to a selector.\n *\n * @param {Element|String|Array} [elements]\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @param {Boolean} useCapture\n * @return {Object}\n */\nfunction delegate(elements, selector, type, callback, useCapture) {\n // Handle the regular Element usage\n if (typeof elements.addEventListener === 'function') {\n return _delegate.apply(null, arguments);\n }\n\n // Handle Element-less usage, it defaults to global delegation\n if (typeof type === 'function') {\n // Use `document` as the first parameter, then apply arguments\n // This is a short way to .unshift `arguments` without running into deoptimizations\n return _delegate.bind(null, document).apply(null, arguments);\n }\n\n // Handle Selector-based usage\n if (typeof elements === 'string') {\n elements = document.querySelectorAll(elements);\n }\n\n // Handle Array-like based usage\n return Array.prototype.map.call(elements, function (element) {\n return _delegate(element, selector, type, callback, useCapture);\n });\n}\n\n/**\n * Finds closest match and invokes callback.\n *\n * @param {Element} element\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Function}\n */\nfunction listener(element, selector, type, callback) {\n return function(e) {\n e.delegateTarget = closest(e.target, selector);\n\n if (e.delegateTarget) {\n callback.call(element, e);\n }\n }\n}\n\nmodule.exports = delegate;\n\n\n/***/ }),\n\n/***/ 879:\n/***/ (function(__unused_webpack_module, exports) {\n\n/**\n * Check if argument is a HTML element.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.node = function(value) {\n return value !== undefined\n && value instanceof HTMLElement\n && value.nodeType === 1;\n};\n\n/**\n * Check if argument is a list of HTML elements.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.nodeList = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return value !== undefined\n && (type === '[object NodeList]' || type === '[object HTMLCollection]')\n && ('length' in value)\n && (value.length === 0 || exports.node(value[0]));\n};\n\n/**\n * Check if argument is a string.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.string = function(value) {\n return typeof value === 'string'\n || value instanceof String;\n};\n\n/**\n * Check if argument is a function.\n *\n * @param {Object} value\n * @return {Boolean}\n */\nexports.fn = function(value) {\n var type = Object.prototype.toString.call(value);\n\n return type === '[object Function]';\n};\n\n\n/***/ }),\n\n/***/ 370:\n/***/ (function(module, __unused_webpack_exports, __webpack_require__) {\n\nvar is = __webpack_require__(879);\nvar delegate = __webpack_require__(438);\n\n/**\n * Validates all params and calls the right\n * listener function based on its target type.\n *\n * @param {String|HTMLElement|HTMLCollection|NodeList} target\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listen(target, type, callback) {\n if (!target && !type && !callback) {\n throw new Error('Missing required arguments');\n }\n\n if (!is.string(type)) {\n throw new TypeError('Second argument must be a String');\n }\n\n if (!is.fn(callback)) {\n throw new TypeError('Third argument must be a Function');\n }\n\n if (is.node(target)) {\n return listenNode(target, type, callback);\n }\n else if (is.nodeList(target)) {\n return listenNodeList(target, type, callback);\n }\n else if (is.string(target)) {\n return listenSelector(target, type, callback);\n }\n else {\n throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');\n }\n}\n\n/**\n * Adds an event listener to a HTML element\n * and returns a remove listener function.\n *\n * @param {HTMLElement} node\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNode(node, type, callback) {\n node.addEventListener(type, callback);\n\n return {\n destroy: function() {\n node.removeEventListener(type, callback);\n }\n }\n}\n\n/**\n * Add an event listener to a list of HTML elements\n * and returns a remove listener function.\n *\n * @param {NodeList|HTMLCollection} nodeList\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenNodeList(nodeList, type, callback) {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.addEventListener(type, callback);\n });\n\n return {\n destroy: function() {\n Array.prototype.forEach.call(nodeList, function(node) {\n node.removeEventListener(type, callback);\n });\n }\n }\n}\n\n/**\n * Add an event listener to a selector\n * and returns a remove listener function.\n *\n * @param {String} selector\n * @param {String} type\n * @param {Function} callback\n * @return {Object}\n */\nfunction listenSelector(selector, type, callback) {\n return delegate(document.body, selector, type, callback);\n}\n\nmodule.exports = listen;\n\n\n/***/ }),\n\n/***/ 817:\n/***/ (function(module) {\n\nfunction select(element) {\n var selectedText;\n\n if (element.nodeName === 'SELECT') {\n element.focus();\n\n selectedText = element.value;\n }\n else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {\n var isReadOnly = element.hasAttribute('readonly');\n\n if (!isReadOnly) {\n element.setAttribute('readonly', '');\n }\n\n element.select();\n element.setSelectionRange(0, element.value.length);\n\n if (!isReadOnly) {\n element.removeAttribute('readonly');\n }\n\n selectedText = element.value;\n }\n else {\n if (element.hasAttribute('contenteditable')) {\n element.focus();\n }\n\n var selection = window.getSelection();\n var range = document.createRange();\n\n range.selectNodeContents(element);\n selection.removeAllRanges();\n selection.addRange(range);\n\n selectedText = selection.toString();\n }\n\n return selectedText;\n}\n\nmodule.exports = select;\n\n\n/***/ }),\n\n/***/ 279:\n/***/ (function(module) {\n\nfunction E () {\n // Keep this empty so it's easier to inherit from\n // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)\n}\n\nE.prototype = {\n on: function (name, callback, ctx) {\n var e = this.e || (this.e = {});\n\n (e[name] || (e[name] = [])).push({\n fn: callback,\n ctx: ctx\n });\n\n return this;\n },\n\n once: function (name, callback, ctx) {\n var self = this;\n function listener () {\n self.off(name, listener);\n callback.apply(ctx, arguments);\n };\n\n listener._ = callback\n return this.on(name, listener, ctx);\n },\n\n emit: function (name) {\n var data = [].slice.call(arguments, 1);\n var evtArr = ((this.e || (this.e = {}))[name] || []).slice();\n var i = 0;\n var len = evtArr.length;\n\n for (i; i < len; i++) {\n evtArr[i].fn.apply(evtArr[i].ctx, data);\n }\n\n return this;\n },\n\n off: function (name, callback) {\n var e = this.e || (this.e = {});\n var evts = e[name];\n var liveEvents = [];\n\n if (evts && callback) {\n for (var i = 0, len = evts.length; i < len; i++) {\n if (evts[i].fn !== callback && evts[i].fn._ !== callback)\n liveEvents.push(evts[i]);\n }\n }\n\n // Remove event from queue to prevent memory leak\n // Suggested by https://github.com/lazd\n // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910\n\n (liveEvents.length)\n ? e[name] = liveEvents\n : delete e[name];\n\n return this;\n }\n};\n\nmodule.exports = E;\nmodule.exports.TinyEmitter = E;\n\n\n/***/ })\n\n/******/ \t});\n/************************************************************************/\n/******/ \t// The module cache\n/******/ \tvar __webpack_module_cache__ = {};\n/******/ \t\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(__webpack_module_cache__[moduleId]) {\n/******/ \t\t\treturn __webpack_module_cache__[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = __webpack_module_cache__[moduleId] = {\n/******/ \t\t\t// no module.id needed\n/******/ \t\t\t// no module.loaded needed\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/ \t\n/******/ \t\t// Execute the module function\n/******/ \t\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n/******/ \t\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/ \t\n/************************************************************************/\n/******/ \t/* webpack/runtime/compat get default export */\n/******/ \t!function() {\n/******/ \t\t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t\t__webpack_require__.n = function(module) {\n/******/ \t\t\tvar getter = module && module.__esModule ?\n/******/ \t\t\t\tfunction() { return module['default']; } :\n/******/ \t\t\t\tfunction() { return module; };\n/******/ \t\t\t__webpack_require__.d(getter, { a: getter });\n/******/ \t\t\treturn getter;\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/define property getters */\n/******/ \t!function() {\n/******/ \t\t// define getter functions for harmony exports\n/******/ \t\t__webpack_require__.d = function(exports, definition) {\n/******/ \t\t\tfor(var key in definition) {\n/******/ \t\t\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n/******/ \t\t\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n/******/ \t\t\t\t}\n/******/ \t\t\t}\n/******/ \t\t};\n/******/ \t}();\n/******/ \t\n/******/ \t/* webpack/runtime/hasOwnProperty shorthand */\n/******/ \t!function() {\n/******/ \t\t__webpack_require__.o = function(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); }\n/******/ \t}();\n/******/ \t\n/************************************************************************/\n/******/ \t// module exports must be returned from runtime so entry inlining is disabled\n/******/ \t// startup\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(686);\n/******/ })()\n.default;\n});", "/*\n * Copyright (c) 2016-2025 Martin Donath \n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to\n * deal in the Software without restriction, including without limitation the\n * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n * sell copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in\n * all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n * IN THE SOFTWARE.\n */\n\nimport \"focus-visible\"\n\nimport {\n EMPTY,\n NEVER,\n Observable,\n Subject,\n defer,\n delay,\n filter,\n map,\n merge,\n mergeWith,\n shareReplay,\n switchMap\n} from \"rxjs\"\n\nimport { configuration, feature } from \"./_\"\nimport {\n at,\n getActiveElement,\n getOptionalElement,\n requestJSON,\n setLocation,\n setToggle,\n watchDocument,\n watchKeyboard,\n watchLocation,\n watchLocationTarget,\n watchMedia,\n watchPrint,\n watchScript,\n watchViewport\n} from \"./browser\"\nimport {\n getComponentElement,\n getComponentElements,\n mountAnnounce,\n mountBackToTop,\n mountConsent,\n mountContent,\n mountDialog,\n mountHeader,\n mountHeaderTitle,\n mountPalette,\n mountProgress,\n mountSearch,\n mountSearchHiglight,\n mountSidebar,\n mountSource,\n mountTableOfContents,\n mountTabs,\n watchHeader,\n watchMain\n} from \"./components\"\nimport {\n SearchIndex,\n setupClipboardJS,\n setupInstantNavigation,\n setupVersionSelector\n} from \"./integrations\"\nimport {\n patchEllipsis,\n patchIndeterminate,\n patchScrollfix,\n patchScrolllock\n} from \"./patches\"\nimport \"./polyfills\"\n\n/* ----------------------------------------------------------------------------\n * Functions - @todo refactor\n * ------------------------------------------------------------------------- */\n\n/**\n * Fetch search index\n *\n * @returns Search index observable\n */\nfunction fetchSearchIndex(): Observable {\n if (location.protocol === \"file:\") {\n return watchScript(\n `${new URL(\"search/search_index.js\", config.base)}`\n )\n .pipe(\n // @ts-ignore - @todo fix typings\n map(() => __index),\n shareReplay(1)\n )\n } else {\n return requestJSON(\n new URL(\"search/search_index.json\", config.base)\n )\n }\n}\n\n/* ----------------------------------------------------------------------------\n * Application\n * ------------------------------------------------------------------------- */\n\n/* Yay, JavaScript is available */\ndocument.documentElement.classList.remove(\"no-js\")\ndocument.documentElement.classList.add(\"js\")\n\n/* Set up navigation observables and subjects */\nconst document$ = watchDocument()\nconst location$ = watchLocation()\nconst target$ = watchLocationTarget(location$)\nconst keyboard$ = watchKeyboard()\n\n/* Set up media observables */\nconst viewport$ = watchViewport()\nconst tablet$ = watchMedia(\"(min-width: 960px)\")\nconst screen$ = watchMedia(\"(min-width: 1220px)\")\nconst print$ = watchPrint()\n\n/* Retrieve search index, if search is enabled */\nconst config = configuration()\nconst index$ = document.forms.namedItem(\"search\")\n ? fetchSearchIndex()\n : NEVER\n\n/* Set up Clipboard.js integration */\nconst alert$ = new Subject()\nsetupClipboardJS({ alert$ })\n\n/* Set up progress indicator */\nconst progress$ = new Subject()\n\n/* Set up instant navigation, if enabled */\nif (feature(\"navigation.instant\"))\n setupInstantNavigation({ location$, viewport$, progress$ })\n .subscribe(document$)\n\n/* Set up version selector */\nif (config.version?.provider === \"mike\")\n setupVersionSelector({ document$ })\n\n/* Always close drawer and search on navigation */\nmerge(location$, target$)\n .pipe(\n delay(125)\n )\n .subscribe(() => {\n setToggle(\"drawer\", false)\n setToggle(\"search\", false)\n })\n\n/* Set up global keyboard handlers */\nkeyboard$\n .pipe(\n filter(({ mode }) => mode === \"global\")\n )\n .subscribe(key => {\n switch (key.type) {\n\n /* Go to previous page */\n case \"p\":\n case \",\":\n const prev = getOptionalElement(\"link[rel=prev]\")\n if (typeof prev !== \"undefined\")\n setLocation(prev)\n break\n\n /* Go to next page */\n case \"n\":\n case \".\":\n const next = getOptionalElement(\"link[rel=next]\")\n if (typeof next !== \"undefined\")\n setLocation(next)\n break\n\n /* Expand navigation, see https://bit.ly/3ZjG5io */\n case \"Enter\":\n const active = getActiveElement()\n if (active instanceof HTMLLabelElement)\n active.click()\n }\n })\n\n/* Set up patches */\npatchEllipsis({ viewport$, document$ })\npatchIndeterminate({ document$, tablet$ })\npatchScrollfix({ document$ })\npatchScrolllock({ viewport$, tablet$ })\n\n/* Set up header and main area observable */\nconst header$ = watchHeader(getComponentElement(\"header\"), { viewport$ })\nconst main$ = document$\n .pipe(\n map(() => getComponentElement(\"main\")),\n switchMap(el => watchMain(el, { viewport$, header$ })),\n shareReplay(1)\n )\n\n/* Set up control component observables */\nconst control$ = merge(\n\n /* Consent */\n ...getComponentElements(\"consent\")\n .map(el => mountConsent(el, { target$ })),\n\n /* Dialog */\n ...getComponentElements(\"dialog\")\n .map(el => mountDialog(el, { alert$ })),\n\n /* Color palette */\n ...getComponentElements(\"palette\")\n .map(el => mountPalette(el)),\n\n /* Progress bar */\n ...getComponentElements(\"progress\")\n .map(el => mountProgress(el, { progress$ })),\n\n /* Search */\n ...getComponentElements(\"search\")\n .map(el => mountSearch(el, { index$, keyboard$ })),\n\n /* Repository information */\n ...getComponentElements(\"source\")\n .map(el => mountSource(el))\n)\n\n/* Set up content component observables */\nconst content$ = defer(() => merge(\n\n /* Announcement bar */\n ...getComponentElements(\"announce\")\n .map(el => mountAnnounce(el)),\n\n /* Content */\n ...getComponentElements(\"content\")\n .map(el => mountContent(el, { viewport$, target$, print$ })),\n\n /* Search highlighting */\n ...getComponentElements(\"content\")\n .map(el => feature(\"search.highlight\")\n ? mountSearchHiglight(el, { index$, location$ })\n : EMPTY\n ),\n\n /* Header */\n ...getComponentElements(\"header\")\n .map(el => mountHeader(el, { viewport$, header$, main$ })),\n\n /* Header title */\n ...getComponentElements(\"header-title\")\n .map(el => mountHeaderTitle(el, { viewport$, header$ })),\n\n /* Sidebar */\n ...getComponentElements(\"sidebar\")\n .map(el => el.getAttribute(\"data-md-type\") === \"navigation\"\n ? at(screen$, () => mountSidebar(el, { viewport$, header$, main$ }))\n : at(tablet$, () => mountSidebar(el, { viewport$, header$, main$ }))\n ),\n\n /* Navigation tabs */\n ...getComponentElements(\"tabs\")\n .map(el => mountTabs(el, { viewport$, header$ })),\n\n /* Table of contents */\n ...getComponentElements(\"toc\")\n .map(el => mountTableOfContents(el, {\n viewport$, header$, main$, target$\n })),\n\n /* Back-to-top button */\n ...getComponentElements(\"top\")\n .map(el => mountBackToTop(el, { viewport$, header$, main$, target$ }))\n))\n\n/* Set up component observables */\nconst component$ = document$\n .pipe(\n switchMap(() => content$),\n mergeWith(control$),\n shareReplay(1)\n )\n\n/* Subscribe to all components */\ncomponent$.subscribe()\n\n/* ----------------------------------------------------------------------------\n * Exports\n * ------------------------------------------------------------------------- */\n\nwindow.document$ = document$ /* Document observable */\nwindow.location$ = location$ /* Location subject */\nwindow.target$ = target$ /* Location target observable */\nwindow.keyboard$ = keyboard$ /* Keyboard observable */\nwindow.viewport$ = viewport$ /* Viewport observable */\nwindow.tablet$ = tablet$ /* Media tablet observable */\nwindow.screen$ = screen$ /* Media screen observable */\nwindow.print$ = print$ /* Media print observable */\nwindow.alert$ = alert$ /* Alert subject */\nwindow.progress$ = progress$ /* Progress indicator subject */\nwindow.component$ = component$ /* Component observable */\n", "/******************************************************************************\nCopyright (c) Microsoft Corporation.\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n***************************************************************************** */\n/* global Reflect, Promise, SuppressedError, Symbol, Iterator */\n\nvar extendStatics = function(d, b) {\n extendStatics = Object.setPrototypeOf ||\n ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||\n function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };\n return extendStatics(d, b);\n};\n\nexport function __extends(d, b) {\n if (typeof b !== \"function\" && b !== null)\n throw new TypeError(\"Class extends value \" + String(b) + \" is not a constructor or null\");\n extendStatics(d, b);\n function __() { this.constructor = d; }\n d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());\n}\n\nexport var __assign = function() {\n __assign = Object.assign || function __assign(t) {\n for (var s, i = 1, n = arguments.length; i < n; i++) {\n s = arguments[i];\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];\n }\n return t;\n }\n return __assign.apply(this, arguments);\n}\n\nexport function __rest(s, e) {\n var t = {};\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\n t[p] = s[p];\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\n t[p[i]] = s[p[i]];\n }\n return t;\n}\n\nexport function __decorate(decorators, target, key, desc) {\n var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n if (typeof Reflect === \"object\" && typeof Reflect.decorate === \"function\") r = Reflect.decorate(decorators, target, key, desc);\n else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n return c > 3 && r && Object.defineProperty(target, key, r), r;\n}\n\nexport function __param(paramIndex, decorator) {\n return function (target, key) { decorator(target, key, paramIndex); }\n}\n\nexport function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {\n function accept(f) { if (f !== void 0 && typeof f !== \"function\") throw new TypeError(\"Function expected\"); return f; }\n var kind = contextIn.kind, key = kind === \"getter\" ? \"get\" : kind === \"setter\" ? \"set\" : \"value\";\n var target = !descriptorIn && ctor ? contextIn[\"static\"] ? ctor : ctor.prototype : null;\n var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});\n var _, done = false;\n for (var i = decorators.length - 1; i >= 0; i--) {\n var context = {};\n for (var p in contextIn) context[p] = p === \"access\" ? {} : contextIn[p];\n for (var p in contextIn.access) context.access[p] = contextIn.access[p];\n context.addInitializer = function (f) { if (done) throw new TypeError(\"Cannot add initializers after decoration has completed\"); extraInitializers.push(accept(f || null)); };\n var result = (0, decorators[i])(kind === \"accessor\" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);\n if (kind === \"accessor\") {\n if (result === void 0) continue;\n if (result === null || typeof result !== \"object\") throw new TypeError(\"Object expected\");\n if (_ = accept(result.get)) descriptor.get = _;\n if (_ = accept(result.set)) descriptor.set = _;\n if (_ = accept(result.init)) initializers.unshift(_);\n }\n else if (_ = accept(result)) {\n if (kind === \"field\") initializers.unshift(_);\n else descriptor[key] = _;\n }\n }\n if (target) Object.defineProperty(target, contextIn.name, descriptor);\n done = true;\n};\n\nexport function __runInitializers(thisArg, initializers, value) {\n var useValue = arguments.length > 2;\n for (var i = 0; i < initializers.length; i++) {\n value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);\n }\n return useValue ? value : void 0;\n};\n\nexport function __propKey(x) {\n return typeof x === \"symbol\" ? x : \"\".concat(x);\n};\n\nexport function __setFunctionName(f, name, prefix) {\n if (typeof name === \"symbol\") name = name.description ? \"[\".concat(name.description, \"]\") : \"\";\n return Object.defineProperty(f, \"name\", { configurable: true, value: prefix ? \"\".concat(prefix, \" \", name) : name });\n};\n\nexport function __metadata(metadataKey, metadataValue) {\n if (typeof Reflect === \"object\" && typeof Reflect.metadata === \"function\") return Reflect.metadata(metadataKey, metadataValue);\n}\n\nexport function __awaiter(thisArg, _arguments, P, generator) {\n function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }\n return new (P || (P = Promise))(function (resolve, reject) {\n function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }\n function rejected(value) { try { step(generator[\"throw\"](value)); } catch (e) { reject(e); } }\n function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }\n step((generator = generator.apply(thisArg, _arguments || [])).next());\n });\n}\n\nexport function __generator(thisArg, body) {\n var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === \"function\" ? Iterator : Object).prototype);\n return g.next = verb(0), g[\"throw\"] = verb(1), g[\"return\"] = verb(2), typeof Symbol === \"function\" && (g[Symbol.iterator] = function() { return this; }), g;\n function verb(n) { return function (v) { return step([n, v]); }; }\n function step(op) {\n if (f) throw new TypeError(\"Generator is already executing.\");\n while (g && (g = 0, op[0] && (_ = 0)), _) try {\n if (f = 1, y && (t = op[0] & 2 ? y[\"return\"] : op[0] ? y[\"throw\"] || ((t = y[\"return\"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;\n if (y = 0, t) op = [op[0] & 2, t.value];\n switch (op[0]) {\n case 0: case 1: t = op; break;\n case 4: _.label++; return { value: op[1], done: false };\n case 5: _.label++; y = op[1]; op = [0]; continue;\n case 7: op = _.ops.pop(); _.trys.pop(); continue;\n default:\n if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }\n if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }\n if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }\n if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }\n if (t[2]) _.ops.pop();\n _.trys.pop(); continue;\n }\n op = body.call(thisArg, _);\n } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }\n if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };\n }\n}\n\nexport var __createBinding = Object.create ? (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n var desc = Object.getOwnPropertyDescriptor(m, k);\n if (!desc || (\"get\" in desc ? !m.__esModule : desc.writable || desc.configurable)) {\n desc = { enumerable: true, get: function() { return m[k]; } };\n }\n Object.defineProperty(o, k2, desc);\n}) : (function(o, m, k, k2) {\n if (k2 === undefined) k2 = k;\n o[k2] = m[k];\n});\n\nexport function __exportStar(m, o) {\n for (var p in m) if (p !== \"default\" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);\n}\n\nexport function __values(o) {\n var s = typeof Symbol === \"function\" && Symbol.iterator, m = s && o[s], i = 0;\n if (m) return m.call(o);\n if (o && typeof o.length === \"number\") return {\n next: function () {\n if (o && i >= o.length) o = void 0;\n return { value: o && o[i++], done: !o };\n }\n };\n throw new TypeError(s ? \"Object is not iterable.\" : \"Symbol.iterator is not defined.\");\n}\n\nexport function __read(o, n) {\n var m = typeof Symbol === \"function\" && o[Symbol.iterator];\n if (!m) return o;\n var i = m.call(o), r, ar = [], e;\n try {\n while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);\n }\n catch (error) { e = { error: error }; }\n finally {\n try {\n if (r && !r.done && (m = i[\"return\"])) m.call(i);\n }\n finally { if (e) throw e.error; }\n }\n return ar;\n}\n\n/** @deprecated */\nexport function __spread() {\n for (var ar = [], i = 0; i < arguments.length; i++)\n ar = ar.concat(__read(arguments[i]));\n return ar;\n}\n\n/** @deprecated */\nexport function __spreadArrays() {\n for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;\n for (var r = Array(s), k = 0, i = 0; i < il; i++)\n for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)\n r[k] = a[j];\n return r;\n}\n\nexport function __spreadArray(to, from, pack) {\n if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {\n if (ar || !(i in from)) {\n if (!ar) ar = Array.prototype.slice.call(from, 0, i);\n ar[i] = from[i];\n }\n }\n return to.concat(ar || Array.prototype.slice.call(from));\n}\n\nexport function __await(v) {\n return this instanceof __await ? (this.v = v, this) : new __await(v);\n}\n\nexport function __asyncGenerator(thisArg, _arguments, generator) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var g = generator.apply(thisArg, _arguments || []), i, q = [];\n return i = Object.create((typeof AsyncIterator === \"function\" ? AsyncIterator : Object).prototype), verb(\"next\"), verb(\"throw\"), verb(\"return\", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;\n function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }\n function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }\n function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }\n function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }\n function fulfill(value) { resume(\"next\", value); }\n function reject(value) { resume(\"throw\", value); }\n function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }\n}\n\nexport function __asyncDelegator(o) {\n var i, p;\n return i = {}, verb(\"next\"), verb(\"throw\", function (e) { throw e; }), verb(\"return\"), i[Symbol.iterator] = function () { return this; }, i;\n function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }\n}\n\nexport function __asyncValues(o) {\n if (!Symbol.asyncIterator) throw new TypeError(\"Symbol.asyncIterator is not defined.\");\n var m = o[Symbol.asyncIterator], i;\n return m ? m.call(o) : (o = typeof __values === \"function\" ? __values(o) : o[Symbol.iterator](), i = {}, verb(\"next\"), verb(\"throw\"), verb(\"return\"), i[Symbol.asyncIterator] = function () { return this; }, i);\n function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }\n function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }\n}\n\nexport function __makeTemplateObject(cooked, raw) {\n if (Object.defineProperty) { Object.defineProperty(cooked, \"raw\", { value: raw }); } else { cooked.raw = raw; }\n return cooked;\n};\n\nvar __setModuleDefault = Object.create ? (function(o, v) {\n Object.defineProperty(o, \"default\", { enumerable: true, value: v });\n}) : function(o, v) {\n o[\"default\"] = v;\n};\n\nexport function __importStar(mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k in mod) if (k !== \"default\" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);\n __setModuleDefault(result, mod);\n return result;\n}\n\nexport function __importDefault(mod) {\n return (mod && mod.__esModule) ? mod : { default: mod };\n}\n\nexport function __classPrivateFieldGet(receiver, state, kind, f) {\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a getter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot read private member from an object whose class did not declare it\");\n return kind === \"m\" ? f : kind === \"a\" ? f.call(receiver) : f ? f.value : state.get(receiver);\n}\n\nexport function __classPrivateFieldSet(receiver, state, value, kind, f) {\n if (kind === \"m\") throw new TypeError(\"Private method is not writable\");\n if (kind === \"a\" && !f) throw new TypeError(\"Private accessor was defined without a setter\");\n if (typeof state === \"function\" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError(\"Cannot write private member to an object whose class did not declare it\");\n return (kind === \"a\" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;\n}\n\nexport function __classPrivateFieldIn(state, receiver) {\n if (receiver === null || (typeof receiver !== \"object\" && typeof receiver !== \"function\")) throw new TypeError(\"Cannot use 'in' operator on non-object\");\n return typeof state === \"function\" ? receiver === state : state.has(receiver);\n}\n\nexport function __addDisposableResource(env, value, async) {\n if (value !== null && value !== void 0) {\n if (typeof value !== \"object\" && typeof value !== \"function\") throw new TypeError(\"Object expected.\");\n var dispose, inner;\n if (async) {\n if (!Symbol.asyncDispose) throw new TypeError(\"Symbol.asyncDispose is not defined.\");\n dispose = value[Symbol.asyncDispose];\n }\n if (dispose === void 0) {\n if (!Symbol.dispose) throw new TypeError(\"Symbol.dispose is not defined.\");\n dispose = value[Symbol.dispose];\n if (async) inner = dispose;\n }\n if (typeof dispose !== \"function\") throw new TypeError(\"Object not disposable.\");\n if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };\n env.stack.push({ value: value, dispose: dispose, async: async });\n }\n else if (async) {\n env.stack.push({ async: true });\n }\n return value;\n}\n\nvar _SuppressedError = typeof SuppressedError === \"function\" ? SuppressedError : function (error, suppressed, message) {\n var e = new Error(message);\n return e.name = \"SuppressedError\", e.error = error, e.suppressed = suppressed, e;\n};\n\nexport function __disposeResources(env) {\n function fail(e) {\n env.error = env.hasError ? new _SuppressedError(e, env.error, \"An error was suppressed during disposal.\") : e;\n env.hasError = true;\n }\n var r, s = 0;\n function next() {\n while (r = env.stack.pop()) {\n try {\n if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);\n if (r.dispose) {\n var result = r.dispose.call(r.value);\n if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });\n }\n else s |= 1;\n }\n catch (e) {\n fail(e);\n }\n }\n if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();\n if (env.hasError) throw env.error;\n }\n return next();\n}\n\nexport default {\n __extends,\n __assign,\n __rest,\n __decorate,\n __param,\n __metadata,\n __awaiter,\n __generator,\n __createBinding,\n __exportStar,\n __values,\n __read,\n __spread,\n __spreadArrays,\n __spreadArray,\n __await,\n __asyncGenerator,\n __asyncDelegator,\n __asyncValues,\n __makeTemplateObject,\n __importStar,\n __importDefault,\n __classPrivateFieldGet,\n __classPrivateFieldSet,\n __classPrivateFieldIn,\n __addDisposableResource,\n __disposeResources,\n};\n", "/**\n * Returns true if the object is a function.\n * @param value The value to check\n */\nexport function isFunction(value: any): value is (...args: any[]) => any {\n return typeof value === 'function';\n}\n", "/**\n * Used to create Error subclasses until the community moves away from ES5.\n *\n * This is because compiling from TypeScript down to ES5 has issues with subclassing Errors\n * as well as other built-in types: https://github.com/Microsoft/TypeScript/issues/12123\n *\n * @param createImpl A factory function to create the actual constructor implementation. The returned\n * function should be a named function that calls `_super` internally.\n */\nexport function createErrorClass(createImpl: (_super: any) => any): T {\n const _super = (instance: any) => {\n Error.call(instance);\n instance.stack = new Error().stack;\n };\n\n const ctorFunc = createImpl(_super);\n ctorFunc.prototype = Object.create(Error.prototype);\n ctorFunc.prototype.constructor = ctorFunc;\n return ctorFunc;\n}\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface UnsubscriptionError extends Error {\n readonly errors: any[];\n}\n\nexport interface UnsubscriptionErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (errors: any[]): UnsubscriptionError;\n}\n\n/**\n * An error thrown when one or more errors have occurred during the\n * `unsubscribe` of a {@link Subscription}.\n */\nexport const UnsubscriptionError: UnsubscriptionErrorCtor = createErrorClass(\n (_super) =>\n function UnsubscriptionErrorImpl(this: any, errors: (Error | string)[]) {\n _super(this);\n this.message = errors\n ? `${errors.length} errors occurred during unsubscription:\n${errors.map((err, i) => `${i + 1}) ${err.toString()}`).join('\\n ')}`\n : '';\n this.name = 'UnsubscriptionError';\n this.errors = errors;\n }\n);\n", "/**\n * Removes an item from an array, mutating it.\n * @param arr The array to remove the item from\n * @param item The item to remove\n */\nexport function arrRemove(arr: T[] | undefined | null, item: T) {\n if (arr) {\n const index = arr.indexOf(item);\n 0 <= index && arr.splice(index, 1);\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { UnsubscriptionError } from './util/UnsubscriptionError';\nimport { SubscriptionLike, TeardownLogic, Unsubscribable } from './types';\nimport { arrRemove } from './util/arrRemove';\n\n/**\n * Represents a disposable resource, such as the execution of an Observable. A\n * Subscription has one important method, `unsubscribe`, that takes no argument\n * and just disposes the resource held by the subscription.\n *\n * Additionally, subscriptions may be grouped together through the `add()`\n * method, which will attach a child Subscription to the current Subscription.\n * When a Subscription is unsubscribed, all its children (and its grandchildren)\n * will be unsubscribed as well.\n */\nexport class Subscription implements SubscriptionLike {\n public static EMPTY = (() => {\n const empty = new Subscription();\n empty.closed = true;\n return empty;\n })();\n\n /**\n * A flag to indicate whether this Subscription has already been unsubscribed.\n */\n public closed = false;\n\n private _parentage: Subscription[] | Subscription | null = null;\n\n /**\n * The list of registered finalizers to execute upon unsubscription. Adding and removing from this\n * list occurs in the {@link #add} and {@link #remove} methods.\n */\n private _finalizers: Exclude[] | null = null;\n\n /**\n * @param initialTeardown A function executed first as part of the finalization\n * process that is kicked off when {@link #unsubscribe} is called.\n */\n constructor(private initialTeardown?: () => void) {}\n\n /**\n * Disposes the resources held by the subscription. May, for instance, cancel\n * an ongoing Observable execution or cancel any other type of work that\n * started when the Subscription was created.\n */\n unsubscribe(): void {\n let errors: any[] | undefined;\n\n if (!this.closed) {\n this.closed = true;\n\n // Remove this from it's parents.\n const { _parentage } = this;\n if (_parentage) {\n this._parentage = null;\n if (Array.isArray(_parentage)) {\n for (const parent of _parentage) {\n parent.remove(this);\n }\n } else {\n _parentage.remove(this);\n }\n }\n\n const { initialTeardown: initialFinalizer } = this;\n if (isFunction(initialFinalizer)) {\n try {\n initialFinalizer();\n } catch (e) {\n errors = e instanceof UnsubscriptionError ? e.errors : [e];\n }\n }\n\n const { _finalizers } = this;\n if (_finalizers) {\n this._finalizers = null;\n for (const finalizer of _finalizers) {\n try {\n execFinalizer(finalizer);\n } catch (err) {\n errors = errors ?? [];\n if (err instanceof UnsubscriptionError) {\n errors = [...errors, ...err.errors];\n } else {\n errors.push(err);\n }\n }\n }\n }\n\n if (errors) {\n throw new UnsubscriptionError(errors);\n }\n }\n }\n\n /**\n * Adds a finalizer to this subscription, so that finalization will be unsubscribed/called\n * when this subscription is unsubscribed. If this subscription is already {@link #closed},\n * because it has already been unsubscribed, then whatever finalizer is passed to it\n * will automatically be executed (unless the finalizer itself is also a closed subscription).\n *\n * Closed Subscriptions cannot be added as finalizers to any subscription. Adding a closed\n * subscription to a any subscription will result in no operation. (A noop).\n *\n * Adding a subscription to itself, or adding `null` or `undefined` will not perform any\n * operation at all. (A noop).\n *\n * `Subscription` instances that are added to this instance will automatically remove themselves\n * if they are unsubscribed. Functions and {@link Unsubscribable} objects that you wish to remove\n * will need to be removed manually with {@link #remove}\n *\n * @param teardown The finalization logic to add to this subscription.\n */\n add(teardown: TeardownLogic): void {\n // Only add the finalizer if it's not undefined\n // and don't add a subscription to itself.\n if (teardown && teardown !== this) {\n if (this.closed) {\n // If this subscription is already closed,\n // execute whatever finalizer is handed to it automatically.\n execFinalizer(teardown);\n } else {\n if (teardown instanceof Subscription) {\n // We don't add closed subscriptions, and we don't add the same subscription\n // twice. Subscription unsubscribe is idempotent.\n if (teardown.closed || teardown._hasParent(this)) {\n return;\n }\n teardown._addParent(this);\n }\n (this._finalizers = this._finalizers ?? []).push(teardown);\n }\n }\n }\n\n /**\n * Checks to see if a this subscription already has a particular parent.\n * This will signal that this subscription has already been added to the parent in question.\n * @param parent the parent to check for\n */\n private _hasParent(parent: Subscription) {\n const { _parentage } = this;\n return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));\n }\n\n /**\n * Adds a parent to this subscription so it can be removed from the parent if it\n * unsubscribes on it's own.\n *\n * NOTE: THIS ASSUMES THAT {@link _hasParent} HAS ALREADY BEEN CHECKED.\n * @param parent The parent subscription to add\n */\n private _addParent(parent: Subscription) {\n const { _parentage } = this;\n this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;\n }\n\n /**\n * Called on a child when it is removed via {@link #remove}.\n * @param parent The parent to remove\n */\n private _removeParent(parent: Subscription) {\n const { _parentage } = this;\n if (_parentage === parent) {\n this._parentage = null;\n } else if (Array.isArray(_parentage)) {\n arrRemove(_parentage, parent);\n }\n }\n\n /**\n * Removes a finalizer from this subscription that was previously added with the {@link #add} method.\n *\n * Note that `Subscription` instances, when unsubscribed, will automatically remove themselves\n * from every other `Subscription` they have been added to. This means that using the `remove` method\n * is not a common thing and should be used thoughtfully.\n *\n * If you add the same finalizer instance of a function or an unsubscribable object to a `Subscription` instance\n * more than once, you will need to call `remove` the same number of times to remove all instances.\n *\n * All finalizer instances are removed to free up memory upon unsubscription.\n *\n * @param teardown The finalizer to remove from this subscription\n */\n remove(teardown: Exclude): void {\n const { _finalizers } = this;\n _finalizers && arrRemove(_finalizers, teardown);\n\n if (teardown instanceof Subscription) {\n teardown._removeParent(this);\n }\n }\n}\n\nexport const EMPTY_SUBSCRIPTION = Subscription.EMPTY;\n\nexport function isSubscription(value: any): value is Subscription {\n return (\n value instanceof Subscription ||\n (value && 'closed' in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe))\n );\n}\n\nfunction execFinalizer(finalizer: Unsubscribable | (() => void)) {\n if (isFunction(finalizer)) {\n finalizer();\n } else {\n finalizer.unsubscribe();\n }\n}\n", "import { Subscriber } from './Subscriber';\nimport { ObservableNotification } from './types';\n\n/**\n * The {@link GlobalConfig} object for RxJS. It is used to configure things\n * like how to react on unhandled errors.\n */\nexport const config: GlobalConfig = {\n onUnhandledError: null,\n onStoppedNotification: null,\n Promise: undefined,\n useDeprecatedSynchronousErrorHandling: false,\n useDeprecatedNextContext: false,\n};\n\n/**\n * The global configuration object for RxJS, used to configure things\n * like how to react on unhandled errors. Accessible via {@link config}\n * object.\n */\nexport interface GlobalConfig {\n /**\n * A registration point for unhandled errors from RxJS. These are errors that\n * cannot were not handled by consuming code in the usual subscription path. For\n * example, if you have this configured, and you subscribe to an observable without\n * providing an error handler, errors from that subscription will end up here. This\n * will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onUnhandledError: ((err: any) => void) | null;\n\n /**\n * A registration point for notifications that cannot be sent to subscribers because they\n * have completed, errored or have been explicitly unsubscribed. By default, next, complete\n * and error notifications sent to stopped subscribers are noops. However, sometimes callers\n * might want a different behavior. For example, with sources that attempt to report errors\n * to stopped subscribers, a caller can configure RxJS to throw an unhandled error instead.\n * This will _always_ be called asynchronously on another job in the runtime. This is because\n * we do not want errors thrown in this user-configured handler to interfere with the\n * behavior of the library.\n */\n onStoppedNotification: ((notification: ObservableNotification, subscriber: Subscriber) => void) | null;\n\n /**\n * The promise constructor used by default for {@link Observable#toPromise toPromise} and {@link Observable#forEach forEach}\n * methods.\n *\n * @deprecated As of version 8, RxJS will no longer support this sort of injection of a\n * Promise constructor. If you need a Promise implementation other than native promises,\n * please polyfill/patch Promise as you see appropriate. Will be removed in v8.\n */\n Promise?: PromiseConstructorLike;\n\n /**\n * If true, turns on synchronous error rethrowing, which is a deprecated behavior\n * in v6 and higher. This behavior enables bad patterns like wrapping a subscribe\n * call in a try/catch block. It also enables producer interference, a nasty bug\n * where a multicast can be broken for all observers by a downstream consumer with\n * an unhandled error. DO NOT USE THIS FLAG UNLESS IT'S NEEDED TO BUY TIME\n * FOR MIGRATION REASONS.\n *\n * @deprecated As of version 8, RxJS will no longer support synchronous throwing\n * of unhandled errors. All errors will be thrown on a separate call stack to prevent bad\n * behaviors described above. Will be removed in v8.\n */\n useDeprecatedSynchronousErrorHandling: boolean;\n\n /**\n * If true, enables an as-of-yet undocumented feature from v5: The ability to access\n * `unsubscribe()` via `this` context in `next` functions created in observers passed\n * to `subscribe`.\n *\n * This is being removed because the performance was severely problematic, and it could also cause\n * issues when types other than POJOs are passed to subscribe as subscribers, as they will likely have\n * their `this` context overwritten.\n *\n * @deprecated As of version 8, RxJS will no longer support altering the\n * context of next functions provided as part of an observer to Subscribe. Instead,\n * you will have access to a subscription or a signal or token that will allow you to do things like\n * unsubscribe and test closed status. Will be removed in v8.\n */\n useDeprecatedNextContext: boolean;\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetTimeoutFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearTimeoutFunction = (handle: TimerHandle) => void;\n\ninterface TimeoutProvider {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n delegate:\n | {\n setTimeout: SetTimeoutFunction;\n clearTimeout: ClearTimeoutFunction;\n }\n | undefined;\n}\n\nexport const timeoutProvider: TimeoutProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setTimeout(handler: () => void, timeout?: number, ...args) {\n const { delegate } = timeoutProvider;\n if (delegate?.setTimeout) {\n return delegate.setTimeout(handler, timeout, ...args);\n }\n return setTimeout(handler, timeout, ...args);\n },\n clearTimeout(handle) {\n const { delegate } = timeoutProvider;\n return (delegate?.clearTimeout || clearTimeout)(handle as any);\n },\n delegate: undefined,\n};\n", "import { config } from '../config';\nimport { timeoutProvider } from '../scheduler/timeoutProvider';\n\n/**\n * Handles an error on another job either with the user-configured {@link onUnhandledError},\n * or by throwing it on that new job so it can be picked up by `window.onerror`, `process.on('error')`, etc.\n *\n * This should be called whenever there is an error that is out-of-band with the subscription\n * or when an error hits a terminal boundary of the subscription and no error handler was provided.\n *\n * @param err the error to report\n */\nexport function reportUnhandledError(err: any) {\n timeoutProvider.setTimeout(() => {\n const { onUnhandledError } = config;\n if (onUnhandledError) {\n // Execute the user-configured error handler.\n onUnhandledError(err);\n } else {\n // Throw so it is picked up by the runtime's uncaught error mechanism.\n throw err;\n }\n });\n}\n", "/* tslint:disable:no-empty */\nexport function noop() { }\n", "import { CompleteNotification, NextNotification, ErrorNotification } from './types';\n\n/**\n * A completion object optimized for memory use and created to be the\n * same \"shape\" as other notifications in v8.\n * @internal\n */\nexport const COMPLETE_NOTIFICATION = (() => createNotification('C', undefined, undefined) as CompleteNotification)();\n\n/**\n * Internal use only. Creates an optimized error notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function errorNotification(error: any): ErrorNotification {\n return createNotification('E', undefined, error) as any;\n}\n\n/**\n * Internal use only. Creates an optimized next notification that is the same \"shape\"\n * as other notifications.\n * @internal\n */\nexport function nextNotification(value: T) {\n return createNotification('N', value, undefined) as NextNotification;\n}\n\n/**\n * Ensures that all notifications created internally have the same \"shape\" in v8.\n *\n * TODO: This is only exported to support a crazy legacy test in `groupBy`.\n * @internal\n */\nexport function createNotification(kind: 'N' | 'E' | 'C', value: any, error: any) {\n return {\n kind,\n value,\n error,\n };\n}\n", "import { config } from '../config';\n\nlet context: { errorThrown: boolean; error: any } | null = null;\n\n/**\n * Handles dealing with errors for super-gross mode. Creates a context, in which\n * any synchronously thrown errors will be passed to {@link captureError}. Which\n * will record the error such that it will be rethrown after the call back is complete.\n * TODO: Remove in v8\n * @param cb An immediately executed function.\n */\nexport function errorContext(cb: () => void) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n const isRoot = !context;\n if (isRoot) {\n context = { errorThrown: false, error: null };\n }\n cb();\n if (isRoot) {\n const { errorThrown, error } = context!;\n context = null;\n if (errorThrown) {\n throw error;\n }\n }\n } else {\n // This is the general non-deprecated path for everyone that\n // isn't crazy enough to use super-gross mode (useDeprecatedSynchronousErrorHandling)\n cb();\n }\n}\n\n/**\n * Captures errors only in super-gross mode.\n * @param err the error to capture\n */\nexport function captureError(err: any) {\n if (config.useDeprecatedSynchronousErrorHandling && context) {\n context.errorThrown = true;\n context.error = err;\n }\n}\n", "import { isFunction } from './util/isFunction';\nimport { Observer, ObservableNotification } from './types';\nimport { isSubscription, Subscription } from './Subscription';\nimport { config } from './config';\nimport { reportUnhandledError } from './util/reportUnhandledError';\nimport { noop } from './util/noop';\nimport { nextNotification, errorNotification, COMPLETE_NOTIFICATION } from './NotificationFactories';\nimport { timeoutProvider } from './scheduler/timeoutProvider';\nimport { captureError } from './util/errorContext';\n\n/**\n * Implements the {@link Observer} interface and extends the\n * {@link Subscription} class. While the {@link Observer} is the public API for\n * consuming the values of an {@link Observable}, all Observers get converted to\n * a Subscriber, in order to provide Subscription-like capabilities such as\n * `unsubscribe`. Subscriber is a common type in RxJS, and crucial for\n * implementing operators, but it is rarely used as a public API.\n */\nexport class Subscriber extends Subscription implements Observer {\n /**\n * A static factory for a Subscriber, given a (potentially partial) definition\n * of an Observer.\n * @param next The `next` callback of an Observer.\n * @param error The `error` callback of an\n * Observer.\n * @param complete The `complete` callback of an\n * Observer.\n * @return A Subscriber wrapping the (partially defined)\n * Observer represented by the given arguments.\n * @deprecated Do not use. Will be removed in v8. There is no replacement for this\n * method, and there is no reason to be creating instances of `Subscriber` directly.\n * If you have a specific use case, please file an issue.\n */\n static create(next?: (x?: T) => void, error?: (e?: any) => void, complete?: () => void): Subscriber {\n return new SafeSubscriber(next, error, complete);\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected isStopped: boolean = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n protected destination: Subscriber | Observer; // this `any` is the escape hatch to erase extra type param (e.g. R)\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * There is no reason to directly create an instance of Subscriber. This type is exported for typings reasons.\n */\n constructor(destination?: Subscriber | Observer) {\n super();\n if (destination) {\n this.destination = destination;\n // Automatically chain subscriptions together here.\n // if destination is a Subscription, then it is a Subscriber.\n if (isSubscription(destination)) {\n destination.add(this);\n }\n } else {\n this.destination = EMPTY_OBSERVER;\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `next` from\n * the Observable, with a value. The Observable may call this method 0 or more\n * times.\n * @param value The `next` value.\n */\n next(value: T): void {\n if (this.isStopped) {\n handleStoppedNotification(nextNotification(value), this);\n } else {\n this._next(value!);\n }\n }\n\n /**\n * The {@link Observer} callback to receive notifications of type `error` from\n * the Observable, with an attached `Error`. Notifies the Observer that\n * the Observable has experienced an error condition.\n * @param err The `error` exception.\n */\n error(err?: any): void {\n if (this.isStopped) {\n handleStoppedNotification(errorNotification(err), this);\n } else {\n this.isStopped = true;\n this._error(err);\n }\n }\n\n /**\n * The {@link Observer} callback to receive a valueless notification of type\n * `complete` from the Observable. Notifies the Observer that the Observable\n * has finished sending push-based notifications.\n */\n complete(): void {\n if (this.isStopped) {\n handleStoppedNotification(COMPLETE_NOTIFICATION, this);\n } else {\n this.isStopped = true;\n this._complete();\n }\n }\n\n unsubscribe(): void {\n if (!this.closed) {\n this.isStopped = true;\n super.unsubscribe();\n this.destination = null!;\n }\n }\n\n protected _next(value: T): void {\n this.destination.next(value);\n }\n\n protected _error(err: any): void {\n try {\n this.destination.error(err);\n } finally {\n this.unsubscribe();\n }\n }\n\n protected _complete(): void {\n try {\n this.destination.complete();\n } finally {\n this.unsubscribe();\n }\n }\n}\n\n/**\n * This bind is captured here because we want to be able to have\n * compatibility with monoid libraries that tend to use a method named\n * `bind`. In particular, a library called Monio requires this.\n */\nconst _bind = Function.prototype.bind;\n\nfunction bind any>(fn: Fn, thisArg: any): Fn {\n return _bind.call(fn, thisArg);\n}\n\n/**\n * Internal optimization only, DO NOT EXPOSE.\n * @internal\n */\nclass ConsumerObserver implements Observer {\n constructor(private partialObserver: Partial>) {}\n\n next(value: T): void {\n const { partialObserver } = this;\n if (partialObserver.next) {\n try {\n partialObserver.next(value);\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n\n error(err: any): void {\n const { partialObserver } = this;\n if (partialObserver.error) {\n try {\n partialObserver.error(err);\n } catch (error) {\n handleUnhandledError(error);\n }\n } else {\n handleUnhandledError(err);\n }\n }\n\n complete(): void {\n const { partialObserver } = this;\n if (partialObserver.complete) {\n try {\n partialObserver.complete();\n } catch (error) {\n handleUnhandledError(error);\n }\n }\n }\n}\n\nexport class SafeSubscriber extends Subscriber {\n constructor(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((e?: any) => void) | null,\n complete?: (() => void) | null\n ) {\n super();\n\n let partialObserver: Partial>;\n if (isFunction(observerOrNext) || !observerOrNext) {\n // The first argument is a function, not an observer. The next\n // two arguments *could* be observers, or they could be empty.\n partialObserver = {\n next: (observerOrNext ?? undefined) as ((value: T) => void) | undefined,\n error: error ?? undefined,\n complete: complete ?? undefined,\n };\n } else {\n // The first argument is a partial observer.\n let context: any;\n if (this && config.useDeprecatedNextContext) {\n // This is a deprecated path that made `this.unsubscribe()` available in\n // next handler functions passed to subscribe. This only exists behind a flag\n // now, as it is *very* slow.\n context = Object.create(observerOrNext);\n context.unsubscribe = () => this.unsubscribe();\n partialObserver = {\n next: observerOrNext.next && bind(observerOrNext.next, context),\n error: observerOrNext.error && bind(observerOrNext.error, context),\n complete: observerOrNext.complete && bind(observerOrNext.complete, context),\n };\n } else {\n // The \"normal\" path. Just use the partial observer directly.\n partialObserver = observerOrNext;\n }\n }\n\n // Wrap the partial observer to ensure it's a full observer, and\n // make sure proper error handling is accounted for.\n this.destination = new ConsumerObserver(partialObserver);\n }\n}\n\nfunction handleUnhandledError(error: any) {\n if (config.useDeprecatedSynchronousErrorHandling) {\n captureError(error);\n } else {\n // Ideal path, we report this as an unhandled error,\n // which is thrown on a new call stack.\n reportUnhandledError(error);\n }\n}\n\n/**\n * An error handler used when no error handler was supplied\n * to the SafeSubscriber -- meaning no error handler was supplied\n * do the `subscribe` call on our observable.\n * @param err The error to handle\n */\nfunction defaultErrorHandler(err: any) {\n throw err;\n}\n\n/**\n * A handler for notifications that cannot be sent to a stopped subscriber.\n * @param notification The notification being sent.\n * @param subscriber The stopped subscriber.\n */\nfunction handleStoppedNotification(notification: ObservableNotification, subscriber: Subscriber) {\n const { onStoppedNotification } = config;\n onStoppedNotification && timeoutProvider.setTimeout(() => onStoppedNotification(notification, subscriber));\n}\n\n/**\n * The observer used as a stub for subscriptions where the user did not\n * pass any arguments to `subscribe`. Comes with the default error handling\n * behavior.\n */\nexport const EMPTY_OBSERVER: Readonly> & { closed: true } = {\n closed: true,\n next: noop,\n error: defaultErrorHandler,\n complete: noop,\n};\n", "/**\n * Symbol.observable or a string \"@@observable\". Used for interop\n *\n * @deprecated We will no longer be exporting this symbol in upcoming versions of RxJS.\n * Instead polyfill and use Symbol.observable directly *or* use https://www.npmjs.com/package/symbol-observable\n */\nexport const observable: string | symbol = (() => (typeof Symbol === 'function' && Symbol.observable) || '@@observable')();\n", "/**\n * This function takes one parameter and just returns it. Simply put,\n * this is like `(x: T): T => x`.\n *\n * ## Examples\n *\n * This is useful in some cases when using things like `mergeMap`\n *\n * ```ts\n * import { interval, take, map, range, mergeMap, identity } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(5));\n *\n * const result$ = source$.pipe(\n * map(i => range(i)),\n * mergeMap(identity) // same as mergeMap(x => x)\n * );\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * Or when you want to selectively apply an operator\n *\n * ```ts\n * import { interval, take, identity } from 'rxjs';\n *\n * const shouldLimit = () => Math.random() < 0.5;\n *\n * const source$ = interval(1000);\n *\n * const result$ = source$.pipe(shouldLimit() ? take(5) : identity);\n *\n * result$.subscribe({\n * next: console.log\n * });\n * ```\n *\n * @param x Any value that is returned by this function\n * @returns The value passed as the first parameter to this function\n */\nexport function identity(x: T): T {\n return x;\n}\n", "import { identity } from './identity';\nimport { UnaryFunction } from '../types';\n\nexport function pipe(): typeof identity;\nexport function pipe(fn1: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction): UnaryFunction;\nexport function pipe(fn1: UnaryFunction, fn2: UnaryFunction, fn3: UnaryFunction): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction\n): UnaryFunction;\nexport function pipe(\n fn1: UnaryFunction,\n fn2: UnaryFunction,\n fn3: UnaryFunction,\n fn4: UnaryFunction,\n fn5: UnaryFunction,\n fn6: UnaryFunction,\n fn7: UnaryFunction,\n fn8: UnaryFunction,\n fn9: UnaryFunction,\n ...fns: UnaryFunction[]\n): UnaryFunction;\n\n/**\n * pipe() can be called on one or more functions, each of which can take one argument (\"UnaryFunction\")\n * and uses it to return a value.\n * It returns a function that takes one argument, passes it to the first UnaryFunction, and then\n * passes the result to the next one, passes that result to the next one, and so on. \n */\nexport function pipe(...fns: Array>): UnaryFunction {\n return pipeFromArray(fns);\n}\n\n/** @internal */\nexport function pipeFromArray(fns: Array>): UnaryFunction {\n if (fns.length === 0) {\n return identity as UnaryFunction;\n }\n\n if (fns.length === 1) {\n return fns[0];\n }\n\n return function piped(input: T): R {\n return fns.reduce((prev: any, fn: UnaryFunction) => fn(prev), input as any);\n };\n}\n", "import { Operator } from './Operator';\nimport { SafeSubscriber, Subscriber } from './Subscriber';\nimport { isSubscription, Subscription } from './Subscription';\nimport { TeardownLogic, OperatorFunction, Subscribable, Observer } from './types';\nimport { observable as Symbol_observable } from './symbol/observable';\nimport { pipeFromArray } from './util/pipe';\nimport { config } from './config';\nimport { isFunction } from './util/isFunction';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A representation of any set of values over any amount of time. This is the most basic building block\n * of RxJS.\n */\nexport class Observable implements Subscribable {\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n source: Observable | undefined;\n\n /**\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n */\n operator: Operator | undefined;\n\n /**\n * @param subscribe The function that is called when the Observable is\n * initially subscribed to. This function is given a Subscriber, to which new values\n * can be `next`ed, or an `error` method can be called to raise an error, or\n * `complete` can be called to notify of a successful completion.\n */\n constructor(subscribe?: (this: Observable, subscriber: Subscriber) => TeardownLogic) {\n if (subscribe) {\n this._subscribe = subscribe;\n }\n }\n\n // HACK: Since TypeScript inherits static properties too, we have to\n // fight against TypeScript here so Subject can have a different static create signature\n /**\n * Creates a new Observable by calling the Observable constructor\n * @param subscribe the subscriber function to be passed to the Observable constructor\n * @return A new observable.\n * @deprecated Use `new Observable()` instead. Will be removed in v8.\n */\n static create: (...args: any[]) => any = (subscribe?: (subscriber: Subscriber) => TeardownLogic) => {\n return new Observable(subscribe);\n };\n\n /**\n * Creates a new Observable, with this Observable instance as the source, and the passed\n * operator defined as the new observable's operator.\n * @param operator the operator defining the operation to take on the observable\n * @return A new observable with the Operator applied.\n * @deprecated Internal implementation detail, do not use directly. Will be made internal in v8.\n * If you have implemented an operator using `lift`, it is recommended that you create an\n * operator by simply returning `new Observable()` directly. See \"Creating new operators from\n * scratch\" section here: https://rxjs.dev/guide/operators\n */\n lift(operator?: Operator): Observable {\n const observable = new Observable();\n observable.source = this;\n observable.operator = operator;\n return observable;\n }\n\n subscribe(observerOrNext?: Partial> | ((value: T) => void)): Subscription;\n /** @deprecated Instead of passing separate callback arguments, use an observer argument. Signatures taking separate callback arguments will be removed in v8. Details: https://rxjs.dev/deprecations/subscribe-arguments */\n subscribe(next?: ((value: T) => void) | null, error?: ((error: any) => void) | null, complete?: (() => void) | null): Subscription;\n /**\n * Invokes an execution of an Observable and registers Observer handlers for notifications it will emit.\n *\n * Use it when you have all these Observables, but still nothing is happening.\n *\n * `subscribe` is not a regular operator, but a method that calls Observable's internal `subscribe` function. It\n * might be for example a function that you passed to Observable's constructor, but most of the time it is\n * a library implementation, which defines what will be emitted by an Observable, and when it be will emitted. This means\n * that calling `subscribe` is actually the moment when Observable starts its work, not when it is created, as it is often\n * the thought.\n *\n * Apart from starting the execution of an Observable, this method allows you to listen for values\n * that an Observable emits, as well as for when it completes or errors. You can achieve this in two\n * of the following ways.\n *\n * The first way is creating an object that implements {@link Observer} interface. It should have methods\n * defined by that interface, but note that it should be just a regular JavaScript object, which you can create\n * yourself in any way you want (ES6 class, classic function constructor, object literal etc.). In particular, do\n * not attempt to use any RxJS implementation details to create Observers - you don't need them. Remember also\n * that your object does not have to implement all methods. If you find yourself creating a method that doesn't\n * do anything, you can simply omit it. Note however, if the `error` method is not provided and an error happens,\n * it will be thrown asynchronously. Errors thrown asynchronously cannot be caught using `try`/`catch`. Instead,\n * use the {@link onUnhandledError} configuration option or use a runtime handler (like `window.onerror` or\n * `process.on('error)`) to be notified of unhandled errors. Because of this, it's recommended that you provide\n * an `error` method to avoid missing thrown errors.\n *\n * The second way is to give up on Observer object altogether and simply provide callback functions in place of its methods.\n * This means you can provide three functions as arguments to `subscribe`, where the first function is equivalent\n * of a `next` method, the second of an `error` method and the third of a `complete` method. Just as in case of an Observer,\n * if you do not need to listen for something, you can omit a function by passing `undefined` or `null`,\n * since `subscribe` recognizes these functions by where they were placed in function call. When it comes\n * to the `error` function, as with an Observer, if not provided, errors emitted by an Observable will be thrown asynchronously.\n *\n * You can, however, subscribe with no parameters at all. This may be the case where you're not interested in terminal events\n * and you also handled emissions internally by using operators (e.g. using `tap`).\n *\n * Whichever style of calling `subscribe` you use, in both cases it returns a Subscription object.\n * This object allows you to call `unsubscribe` on it, which in turn will stop the work that an Observable does and will clean\n * up all resources that an Observable used. Note that cancelling a subscription will not call `complete` callback\n * provided to `subscribe` function, which is reserved for a regular completion signal that comes from an Observable.\n *\n * Remember that callbacks provided to `subscribe` are not guaranteed to be called asynchronously.\n * It is an Observable itself that decides when these functions will be called. For example {@link of}\n * by default emits all its values synchronously. Always check documentation for how given Observable\n * will behave when subscribed and if its default behavior can be modified with a `scheduler`.\n *\n * #### Examples\n *\n * Subscribe with an {@link guide/observer Observer}\n *\n * ```ts\n * import { of } from 'rxjs';\n *\n * const sumObserver = {\n * sum: 0,\n * next(value) {\n * console.log('Adding: ' + value);\n * this.sum = this.sum + value;\n * },\n * error() {\n * // We actually could just remove this method,\n * // since we do not really care about errors right now.\n * },\n * complete() {\n * console.log('Sum equals: ' + this.sum);\n * }\n * };\n *\n * of(1, 2, 3) // Synchronously emits 1, 2, 3 and then completes.\n * .subscribe(sumObserver);\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Subscribe with functions ({@link deprecations/subscribe-arguments deprecated})\n *\n * ```ts\n * import { of } from 'rxjs'\n *\n * let sum = 0;\n *\n * of(1, 2, 3).subscribe(\n * value => {\n * console.log('Adding: ' + value);\n * sum = sum + value;\n * },\n * undefined,\n * () => console.log('Sum equals: ' + sum)\n * );\n *\n * // Logs:\n * // 'Adding: 1'\n * // 'Adding: 2'\n * // 'Adding: 3'\n * // 'Sum equals: 6'\n * ```\n *\n * Cancel a subscription\n *\n * ```ts\n * import { interval } from 'rxjs';\n *\n * const subscription = interval(1000).subscribe({\n * next(num) {\n * console.log(num)\n * },\n * complete() {\n * // Will not be called, even when cancelling subscription.\n * console.log('completed!');\n * }\n * });\n *\n * setTimeout(() => {\n * subscription.unsubscribe();\n * console.log('unsubscribed!');\n * }, 2500);\n *\n * // Logs:\n * // 0 after 1s\n * // 1 after 2s\n * // 'unsubscribed!' after 2.5s\n * ```\n *\n * @param observerOrNext Either an {@link Observer} with some or all callback methods,\n * or the `next` handler that is called for each value emitted from the subscribed Observable.\n * @param error A handler for a terminal event resulting from an error. If no error handler is provided,\n * the error will be thrown asynchronously as unhandled.\n * @param complete A handler for a terminal event resulting from successful completion.\n * @return A subscription reference to the registered handlers.\n */\n subscribe(\n observerOrNext?: Partial> | ((value: T) => void) | null,\n error?: ((error: any) => void) | null,\n complete?: (() => void) | null\n ): Subscription {\n const subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);\n\n errorContext(() => {\n const { operator, source } = this;\n subscriber.add(\n operator\n ? // We're dealing with a subscription in the\n // operator chain to one of our lifted operators.\n operator.call(subscriber, source)\n : source\n ? // If `source` has a value, but `operator` does not, something that\n // had intimate knowledge of our API, like our `Subject`, must have\n // set it. We're going to just call `_subscribe` directly.\n this._subscribe(subscriber)\n : // In all other cases, we're likely wrapping a user-provided initializer\n // function, so we need to catch errors and handle them appropriately.\n this._trySubscribe(subscriber)\n );\n });\n\n return subscriber;\n }\n\n /** @internal */\n protected _trySubscribe(sink: Subscriber): TeardownLogic {\n try {\n return this._subscribe(sink);\n } catch (err) {\n // We don't need to return anything in this case,\n // because it's just going to try to `add()` to a subscription\n // above.\n sink.error(err);\n }\n }\n\n /**\n * Used as a NON-CANCELLABLE means of subscribing to an observable, for use with\n * APIs that expect promises, like `async/await`. You cannot unsubscribe from this.\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * #### Example\n *\n * ```ts\n * import { interval, take } from 'rxjs';\n *\n * const source$ = interval(1000).pipe(take(4));\n *\n * async function getTotal() {\n * let total = 0;\n *\n * await source$.forEach(value => {\n * total += value;\n * console.log('observable -> ' + value);\n * });\n *\n * return total;\n * }\n *\n * getTotal().then(\n * total => console.log('Total: ' + total)\n * );\n *\n * // Expected:\n * // 'observable -> 0'\n * // 'observable -> 1'\n * // 'observable -> 2'\n * // 'observable -> 3'\n * // 'Total: 6'\n * ```\n *\n * @param next A handler for each value emitted by the observable.\n * @return A promise that either resolves on observable completion or\n * rejects with the handled error.\n */\n forEach(next: (value: T) => void): Promise;\n\n /**\n * @param next a handler for each value emitted by the observable\n * @param promiseCtor a constructor function used to instantiate the Promise\n * @return a promise that either resolves on observable completion or\n * rejects with the handled error\n * @deprecated Passing a Promise constructor will no longer be available\n * in upcoming versions of RxJS. This is because it adds weight to the library, for very\n * little benefit. If you need this functionality, it is recommended that you either\n * polyfill Promise, or you create an adapter to convert the returned native promise\n * to whatever promise implementation you wanted. Will be removed in v8.\n */\n forEach(next: (value: T) => void, promiseCtor: PromiseConstructorLike): Promise;\n\n forEach(next: (value: T) => void, promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n const subscriber = new SafeSubscriber({\n next: (value) => {\n try {\n next(value);\n } catch (err) {\n reject(err);\n subscriber.unsubscribe();\n }\n },\n error: reject,\n complete: resolve,\n });\n this.subscribe(subscriber);\n }) as Promise;\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): TeardownLogic {\n return this.source?.subscribe(subscriber);\n }\n\n /**\n * An interop point defined by the es7-observable spec https://github.com/zenparsing/es-observable\n * @return This instance of the observable.\n */\n [Symbol_observable]() {\n return this;\n }\n\n /* tslint:disable:max-line-length */\n pipe(): Observable;\n pipe(op1: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction): Observable;\n pipe(op1: OperatorFunction, op2: OperatorFunction, op3: OperatorFunction): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction\n ): Observable;\n pipe(\n op1: OperatorFunction,\n op2: OperatorFunction,\n op3: OperatorFunction,\n op4: OperatorFunction,\n op5: OperatorFunction,\n op6: OperatorFunction,\n op7: OperatorFunction,\n op8: OperatorFunction,\n op9: OperatorFunction,\n ...operations: OperatorFunction[]\n ): Observable;\n /* tslint:enable:max-line-length */\n\n /**\n * Used to stitch together functional operators into a chain.\n *\n * ## Example\n *\n * ```ts\n * import { interval, filter, map, scan } from 'rxjs';\n *\n * interval(1000)\n * .pipe(\n * filter(x => x % 2 === 0),\n * map(x => x + x),\n * scan((acc, x) => acc + x)\n * )\n * .subscribe(x => console.log(x));\n * ```\n *\n * @return The Observable result of all the operators having been called\n * in the order they were passed in.\n */\n pipe(...operations: OperatorFunction[]): Observable {\n return pipeFromArray(operations)(this);\n }\n\n /* tslint:disable:max-line-length */\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: typeof Promise): Promise;\n /** @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise */\n toPromise(PromiseCtor: PromiseConstructorLike): Promise;\n /* tslint:enable:max-line-length */\n\n /**\n * Subscribe to this Observable and get a Promise resolving on\n * `complete` with the last emission (if any).\n *\n * **WARNING**: Only use this with observables you *know* will complete. If the source\n * observable does not complete, you will end up with a promise that is hung up, and\n * potentially all of the state of an async function hanging out in memory. To avoid\n * this situation, look into adding something like {@link timeout}, {@link take},\n * {@link takeWhile}, or {@link takeUntil} amongst others.\n *\n * @param [promiseCtor] a constructor function used to instantiate\n * the Promise\n * @return A Promise that resolves with the last value emit, or\n * rejects on an error. If there were no emissions, Promise\n * resolves with undefined.\n * @deprecated Replaced with {@link firstValueFrom} and {@link lastValueFrom}. Will be removed in v8. Details: https://rxjs.dev/deprecations/to-promise\n */\n toPromise(promiseCtor?: PromiseConstructorLike): Promise {\n promiseCtor = getPromiseCtor(promiseCtor);\n\n return new promiseCtor((resolve, reject) => {\n let value: T | undefined;\n this.subscribe(\n (x: T) => (value = x),\n (err: any) => reject(err),\n () => resolve(value)\n );\n }) as Promise;\n }\n}\n\n/**\n * Decides between a passed promise constructor from consuming code,\n * A default configured promise constructor, and the native promise\n * constructor and returns it. If nothing can be found, it will throw\n * an error.\n * @param promiseCtor The optional promise constructor to passed by consuming code\n */\nfunction getPromiseCtor(promiseCtor: PromiseConstructorLike | undefined) {\n return promiseCtor ?? config.Promise ?? Promise;\n}\n\nfunction isObserver(value: any): value is Observer {\n return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);\n}\n\nfunction isSubscriber(value: any): value is Subscriber {\n return (value && value instanceof Subscriber) || (isObserver(value) && isSubscription(value));\n}\n", "import { Observable } from '../Observable';\nimport { Subscriber } from '../Subscriber';\nimport { OperatorFunction } from '../types';\nimport { isFunction } from './isFunction';\n\n/**\n * Used to determine if an object is an Observable with a lift function.\n */\nexport function hasLift(source: any): source is { lift: InstanceType['lift'] } {\n return isFunction(source?.lift);\n}\n\n/**\n * Creates an `OperatorFunction`. Used to define operators throughout the library in a concise way.\n * @param init The logic to connect the liftedSource to the subscriber at the moment of subscription.\n */\nexport function operate(\n init: (liftedSource: Observable, subscriber: Subscriber) => (() => void) | void\n): OperatorFunction {\n return (source: Observable) => {\n if (hasLift(source)) {\n return source.lift(function (this: Subscriber, liftedSource: Observable) {\n try {\n return init(liftedSource, this);\n } catch (err) {\n this.error(err);\n }\n });\n }\n throw new TypeError('Unable to lift unknown Observable type');\n };\n}\n", "import { Subscriber } from '../Subscriber';\n\n/**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional teardown logic here. This will only be called on teardown if the\n * subscriber itself is not already closed. This is called after all other teardown logic is executed.\n */\nexport function createOperatorSubscriber(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n onFinalize?: () => void\n): Subscriber {\n return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize);\n}\n\n/**\n * A generic helper for allowing operators to be created with a Subscriber and\n * use closures to capture necessary state from the operator function itself.\n */\nexport class OperatorSubscriber extends Subscriber {\n /**\n * Creates an instance of an `OperatorSubscriber`.\n * @param destination The downstream subscriber.\n * @param onNext Handles next values, only called if this subscriber is not stopped or closed. Any\n * error that occurs in this function is caught and sent to the `error` method of this subscriber.\n * @param onError Handles errors from the subscription, any errors that occur in this handler are caught\n * and send to the `destination` error handler.\n * @param onComplete Handles completion notification from the subscription. Any errors that occur in\n * this handler are sent to the `destination` error handler.\n * @param onFinalize Additional finalization logic here. This will only be called on finalization if the\n * subscriber itself is not already closed. This is called after all other finalization logic is executed.\n * @param shouldUnsubscribe An optional check to see if an unsubscribe call should truly unsubscribe.\n * NOTE: This currently **ONLY** exists to support the strange behavior of {@link groupBy}, where unsubscription\n * to the resulting observable does not actually disconnect from the source if there are active subscriptions\n * to any grouped observable. (DO NOT EXPOSE OR USE EXTERNALLY!!!)\n */\n constructor(\n destination: Subscriber,\n onNext?: (value: T) => void,\n onComplete?: () => void,\n onError?: (err: any) => void,\n private onFinalize?: () => void,\n private shouldUnsubscribe?: () => boolean\n ) {\n // It's important - for performance reasons - that all of this class's\n // members are initialized and that they are always initialized in the same\n // order. This will ensure that all OperatorSubscriber instances have the\n // same hidden class in V8. This, in turn, will help keep the number of\n // hidden classes involved in property accesses within the base class as\n // low as possible. If the number of hidden classes involved exceeds four,\n // the property accesses will become megamorphic and performance penalties\n // will be incurred - i.e. inline caches won't be used.\n //\n // The reasons for ensuring all instances have the same hidden class are\n // further discussed in this blog post from Benedikt Meurer:\n // https://benediktmeurer.de/2018/03/23/impact-of-polymorphism-on-component-based-frameworks-like-react/\n super(destination);\n this._next = onNext\n ? function (this: OperatorSubscriber, value: T) {\n try {\n onNext(value);\n } catch (err) {\n destination.error(err);\n }\n }\n : super._next;\n this._error = onError\n ? function (this: OperatorSubscriber, err: any) {\n try {\n onError(err);\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._error;\n this._complete = onComplete\n ? function (this: OperatorSubscriber) {\n try {\n onComplete();\n } catch (err) {\n // Send any errors that occur down stream.\n destination.error(err);\n } finally {\n // Ensure finalization.\n this.unsubscribe();\n }\n }\n : super._complete;\n }\n\n unsubscribe() {\n if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) {\n const { closed } = this;\n super.unsubscribe();\n // Execute additional teardown if we have any and we didn't already do so.\n !closed && this.onFinalize?.();\n }\n }\n}\n", "import { Subscription } from '../Subscription';\n\ninterface AnimationFrameProvider {\n schedule(callback: FrameRequestCallback): Subscription;\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n delegate:\n | {\n requestAnimationFrame: typeof requestAnimationFrame;\n cancelAnimationFrame: typeof cancelAnimationFrame;\n }\n | undefined;\n}\n\nexport const animationFrameProvider: AnimationFrameProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n schedule(callback) {\n let request = requestAnimationFrame;\n let cancel: typeof cancelAnimationFrame | undefined = cancelAnimationFrame;\n const { delegate } = animationFrameProvider;\n if (delegate) {\n request = delegate.requestAnimationFrame;\n cancel = delegate.cancelAnimationFrame;\n }\n const handle = request((timestamp) => {\n // Clear the cancel function. The request has been fulfilled, so\n // attempting to cancel the request upon unsubscription would be\n // pointless.\n cancel = undefined;\n callback(timestamp);\n });\n return new Subscription(() => cancel?.(handle));\n },\n requestAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.requestAnimationFrame || requestAnimationFrame)(...args);\n },\n cancelAnimationFrame(...args) {\n const { delegate } = animationFrameProvider;\n return (delegate?.cancelAnimationFrame || cancelAnimationFrame)(...args);\n },\n delegate: undefined,\n};\n", "import { createErrorClass } from './createErrorClass';\n\nexport interface ObjectUnsubscribedError extends Error {}\n\nexport interface ObjectUnsubscribedErrorCtor {\n /**\n * @deprecated Internal implementation detail. Do not construct error instances.\n * Cannot be tagged as internal: https://github.com/ReactiveX/rxjs/issues/6269\n */\n new (): ObjectUnsubscribedError;\n}\n\n/**\n * An error thrown when an action is invalid because the object has been\n * unsubscribed.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n *\n * @class ObjectUnsubscribedError\n */\nexport const ObjectUnsubscribedError: ObjectUnsubscribedErrorCtor = createErrorClass(\n (_super) =>\n function ObjectUnsubscribedErrorImpl(this: any) {\n _super(this);\n this.name = 'ObjectUnsubscribedError';\n this.message = 'object unsubscribed';\n }\n);\n", "import { Operator } from './Operator';\nimport { Observable } from './Observable';\nimport { Subscriber } from './Subscriber';\nimport { Subscription, EMPTY_SUBSCRIPTION } from './Subscription';\nimport { Observer, SubscriptionLike, TeardownLogic } from './types';\nimport { ObjectUnsubscribedError } from './util/ObjectUnsubscribedError';\nimport { arrRemove } from './util/arrRemove';\nimport { errorContext } from './util/errorContext';\n\n/**\n * A Subject is a special type of Observable that allows values to be\n * multicasted to many Observers. Subjects are like EventEmitters.\n *\n * Every Subject is an Observable and an Observer. You can subscribe to a\n * Subject, and you can call next to feed values as well as error and complete.\n */\nexport class Subject extends Observable implements SubscriptionLike {\n closed = false;\n\n private currentObservers: Observer[] | null = null;\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n observers: Observer[] = [];\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n isStopped = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n hasError = false;\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n thrownError: any = null;\n\n /**\n * Creates a \"subject\" by basically gluing an observer to an observable.\n *\n * @deprecated Recommended you do not use. Will be removed at some point in the future. Plans for replacement still under discussion.\n */\n static create: (...args: any[]) => any = (destination: Observer, source: Observable): AnonymousSubject => {\n return new AnonymousSubject(destination, source);\n };\n\n constructor() {\n // NOTE: This must be here to obscure Observable's constructor.\n super();\n }\n\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n lift(operator: Operator): Observable {\n const subject = new AnonymousSubject(this, this);\n subject.operator = operator as any;\n return subject as any;\n }\n\n /** @internal */\n protected _throwIfClosed() {\n if (this.closed) {\n throw new ObjectUnsubscribedError();\n }\n }\n\n next(value: T) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n if (!this.currentObservers) {\n this.currentObservers = Array.from(this.observers);\n }\n for (const observer of this.currentObservers) {\n observer.next(value);\n }\n }\n });\n }\n\n error(err: any) {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.hasError = this.isStopped = true;\n this.thrownError = err;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.error(err);\n }\n }\n });\n }\n\n complete() {\n errorContext(() => {\n this._throwIfClosed();\n if (!this.isStopped) {\n this.isStopped = true;\n const { observers } = this;\n while (observers.length) {\n observers.shift()!.complete();\n }\n }\n });\n }\n\n unsubscribe() {\n this.isStopped = this.closed = true;\n this.observers = this.currentObservers = null!;\n }\n\n get observed() {\n return this.observers?.length > 0;\n }\n\n /** @internal */\n protected _trySubscribe(subscriber: Subscriber): TeardownLogic {\n this._throwIfClosed();\n return super._trySubscribe(subscriber);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._checkFinalizedStatuses(subscriber);\n return this._innerSubscribe(subscriber);\n }\n\n /** @internal */\n protected _innerSubscribe(subscriber: Subscriber) {\n const { hasError, isStopped, observers } = this;\n if (hasError || isStopped) {\n return EMPTY_SUBSCRIPTION;\n }\n this.currentObservers = null;\n observers.push(subscriber);\n return new Subscription(() => {\n this.currentObservers = null;\n arrRemove(observers, subscriber);\n });\n }\n\n /** @internal */\n protected _checkFinalizedStatuses(subscriber: Subscriber) {\n const { hasError, thrownError, isStopped } = this;\n if (hasError) {\n subscriber.error(thrownError);\n } else if (isStopped) {\n subscriber.complete();\n }\n }\n\n /**\n * Creates a new Observable with this Subject as the source. You can do this\n * to create custom Observer-side logic of the Subject and conceal it from\n * code that uses the Observable.\n * @return Observable that this Subject casts to.\n */\n asObservable(): Observable {\n const observable: any = new Observable();\n observable.source = this;\n return observable;\n }\n}\n\nexport class AnonymousSubject extends Subject {\n constructor(\n /** @deprecated Internal implementation detail, do not use directly. Will be made internal in v8. */\n public destination?: Observer,\n source?: Observable\n ) {\n super();\n this.source = source;\n }\n\n next(value: T) {\n this.destination?.next?.(value);\n }\n\n error(err: any) {\n this.destination?.error?.(err);\n }\n\n complete() {\n this.destination?.complete?.();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n return this.source?.subscribe(subscriber) ?? EMPTY_SUBSCRIPTION;\n }\n}\n", "import { Subject } from './Subject';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\n\n/**\n * A variant of Subject that requires an initial value and emits its current\n * value whenever it is subscribed to.\n */\nexport class BehaviorSubject extends Subject {\n constructor(private _value: T) {\n super();\n }\n\n get value(): T {\n return this.getValue();\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n const subscription = super._subscribe(subscriber);\n !subscription.closed && subscriber.next(this._value);\n return subscription;\n }\n\n getValue(): T {\n const { hasError, thrownError, _value } = this;\n if (hasError) {\n throw thrownError;\n }\n this._throwIfClosed();\n return _value;\n }\n\n next(value: T): void {\n super.next((this._value = value));\n }\n}\n", "import { TimestampProvider } from '../types';\n\ninterface DateTimestampProvider extends TimestampProvider {\n delegate: TimestampProvider | undefined;\n}\n\nexport const dateTimestampProvider: DateTimestampProvider = {\n now() {\n // Use the variable rather than `this` so that the function can be called\n // without being bound to the provider.\n return (dateTimestampProvider.delegate || Date).now();\n },\n delegate: undefined,\n};\n", "import { Subject } from './Subject';\nimport { TimestampProvider } from './types';\nimport { Subscriber } from './Subscriber';\nimport { Subscription } from './Subscription';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * A variant of {@link Subject} that \"replays\" old values to new subscribers by emitting them when they first subscribe.\n *\n * `ReplaySubject` has an internal buffer that will store a specified number of values that it has observed. Like `Subject`,\n * `ReplaySubject` \"observes\" values by having them passed to its `next` method. When it observes a value, it will store that\n * value for a time determined by the configuration of the `ReplaySubject`, as passed to its constructor.\n *\n * When a new subscriber subscribes to the `ReplaySubject` instance, it will synchronously emit all values in its buffer in\n * a First-In-First-Out (FIFO) manner. The `ReplaySubject` will also complete, if it has observed completion; and it will\n * error if it has observed an error.\n *\n * There are two main configuration items to be concerned with:\n *\n * 1. `bufferSize` - This will determine how many items are stored in the buffer, defaults to infinite.\n * 2. `windowTime` - The amount of time to hold a value in the buffer before removing it from the buffer.\n *\n * Both configurations may exist simultaneously. So if you would like to buffer a maximum of 3 values, as long as the values\n * are less than 2 seconds old, you could do so with a `new ReplaySubject(3, 2000)`.\n *\n * ### Differences with BehaviorSubject\n *\n * `BehaviorSubject` is similar to `new ReplaySubject(1)`, with a couple of exceptions:\n *\n * 1. `BehaviorSubject` comes \"primed\" with a single value upon construction.\n * 2. `ReplaySubject` will replay values, even after observing an error, where `BehaviorSubject` will not.\n *\n * @see {@link Subject}\n * @see {@link BehaviorSubject}\n * @see {@link shareReplay}\n */\nexport class ReplaySubject extends Subject {\n private _buffer: (T | number)[] = [];\n private _infiniteTimeWindow = true;\n\n /**\n * @param _bufferSize The size of the buffer to replay on subscription\n * @param _windowTime The amount of time the buffered items will stay buffered\n * @param _timestampProvider An object with a `now()` method that provides the current timestamp. This is used to\n * calculate the amount of time something has been buffered.\n */\n constructor(\n private _bufferSize = Infinity,\n private _windowTime = Infinity,\n private _timestampProvider: TimestampProvider = dateTimestampProvider\n ) {\n super();\n this._infiniteTimeWindow = _windowTime === Infinity;\n this._bufferSize = Math.max(1, _bufferSize);\n this._windowTime = Math.max(1, _windowTime);\n }\n\n next(value: T): void {\n const { isStopped, _buffer, _infiniteTimeWindow, _timestampProvider, _windowTime } = this;\n if (!isStopped) {\n _buffer.push(value);\n !_infiniteTimeWindow && _buffer.push(_timestampProvider.now() + _windowTime);\n }\n this._trimBuffer();\n super.next(value);\n }\n\n /** @internal */\n protected _subscribe(subscriber: Subscriber): Subscription {\n this._throwIfClosed();\n this._trimBuffer();\n\n const subscription = this._innerSubscribe(subscriber);\n\n const { _infiniteTimeWindow, _buffer } = this;\n // We use a copy here, so reentrant code does not mutate our array while we're\n // emitting it to a new subscriber.\n const copy = _buffer.slice();\n for (let i = 0; i < copy.length && !subscriber.closed; i += _infiniteTimeWindow ? 1 : 2) {\n subscriber.next(copy[i] as T);\n }\n\n this._checkFinalizedStatuses(subscriber);\n\n return subscription;\n }\n\n private _trimBuffer() {\n const { _bufferSize, _timestampProvider, _buffer, _infiniteTimeWindow } = this;\n // If we don't have an infinite buffer size, and we're over the length,\n // use splice to truncate the old buffer values off. Note that we have to\n // double the size for instances where we're not using an infinite time window\n // because we're storing the values and the timestamps in the same array.\n const adjustedBufferSize = (_infiniteTimeWindow ? 1 : 2) * _bufferSize;\n _bufferSize < Infinity && adjustedBufferSize < _buffer.length && _buffer.splice(0, _buffer.length - adjustedBufferSize);\n\n // Now, if we're not in an infinite time window, remove all values where the time is\n // older than what is allowed.\n if (!_infiniteTimeWindow) {\n const now = _timestampProvider.now();\n let last = 0;\n // Search the array for the first timestamp that isn't expired and\n // truncate the buffer up to that point.\n for (let i = 1; i < _buffer.length && (_buffer[i] as number) <= now; i += 2) {\n last = i;\n }\n last && _buffer.splice(0, last + 1);\n }\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Subscription } from '../Subscription';\nimport { SchedulerAction } from '../types';\n\n/**\n * A unit of work to be executed in a `scheduler`. An action is typically\n * created from within a {@link SchedulerLike} and an RxJS user does not need to concern\n * themselves about creating and manipulating an Action.\n *\n * ```ts\n * class Action extends Subscription {\n * new (scheduler: Scheduler, work: (state?: T) => void);\n * schedule(state?: T, delay: number = 0): Subscription;\n * }\n * ```\n */\nexport class Action extends Subscription {\n constructor(scheduler: Scheduler, work: (this: SchedulerAction, state?: T) => void) {\n super();\n }\n /**\n * Schedules this action on its parent {@link SchedulerLike} for execution. May be passed\n * some context object, `state`. May happen at some point in the future,\n * according to the `delay` parameter, if specified.\n * @param state Some contextual data that the `work` function uses when called by the\n * Scheduler.\n * @param delay Time to wait before executing the work, where the time unit is implicit\n * and defined by the Scheduler.\n * @return A subscription in order to be able to unsubscribe the scheduled work.\n */\n public schedule(state?: T, delay: number = 0): Subscription {\n return this;\n }\n}\n", "import type { TimerHandle } from './timerHandle';\ntype SetIntervalFunction = (handler: () => void, timeout?: number, ...args: any[]) => TimerHandle;\ntype ClearIntervalFunction = (handle: TimerHandle) => void;\n\ninterface IntervalProvider {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n delegate:\n | {\n setInterval: SetIntervalFunction;\n clearInterval: ClearIntervalFunction;\n }\n | undefined;\n}\n\nexport const intervalProvider: IntervalProvider = {\n // When accessing the delegate, use the variable rather than `this` so that\n // the functions can be called without being bound to the provider.\n setInterval(handler: () => void, timeout?: number, ...args) {\n const { delegate } = intervalProvider;\n if (delegate?.setInterval) {\n return delegate.setInterval(handler, timeout, ...args);\n }\n return setInterval(handler, timeout, ...args);\n },\n clearInterval(handle) {\n const { delegate } = intervalProvider;\n return (delegate?.clearInterval || clearInterval)(handle as any);\n },\n delegate: undefined,\n};\n", "import { Action } from './Action';\nimport { SchedulerAction } from '../types';\nimport { Subscription } from '../Subscription';\nimport { AsyncScheduler } from './AsyncScheduler';\nimport { intervalProvider } from './intervalProvider';\nimport { arrRemove } from '../util/arrRemove';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncAction extends Action {\n public id: TimerHandle | undefined;\n public state?: T;\n // @ts-ignore: Property has no initializer and is not definitely assigned\n public delay: number;\n protected pending: boolean = false;\n\n constructor(protected scheduler: AsyncScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (this.closed) {\n return this;\n }\n\n // Always replace the current state with the new state.\n this.state = state;\n\n const id = this.id;\n const scheduler = this.scheduler;\n\n //\n // Important implementation note:\n //\n // Actions only execute once by default, unless rescheduled from within the\n // scheduled callback. This allows us to implement single and repeat\n // actions via the same code path, without adding API surface area, as well\n // as mimic traditional recursion but across asynchronous boundaries.\n //\n // However, JS runtimes and timers distinguish between intervals achieved by\n // serial `setTimeout` calls vs. a single `setInterval` call. An interval of\n // serial `setTimeout` calls can be individually delayed, which delays\n // scheduling the next `setTimeout`, and so on. `setInterval` attempts to\n // guarantee the interval callback will be invoked more precisely to the\n // interval period, regardless of load.\n //\n // Therefore, we use `setInterval` to schedule single and repeat actions.\n // If the action reschedules itself with the same delay, the interval is not\n // canceled. If the action doesn't reschedule, or reschedules with a\n // different delay, the interval will be canceled after scheduled callback\n // execution.\n //\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, delay);\n }\n\n // Set the pending flag indicating that this action has been scheduled, or\n // has recursively rescheduled itself.\n this.pending = true;\n\n this.delay = delay;\n // If this action has already an async Id, don't request a new one.\n this.id = this.id ?? this.requestAsyncId(scheduler, this.id, delay);\n\n return this;\n }\n\n protected requestAsyncId(scheduler: AsyncScheduler, _id?: TimerHandle, delay: number = 0): TimerHandle {\n return intervalProvider.setInterval(scheduler.flush.bind(scheduler, this), delay);\n }\n\n protected recycleAsyncId(_scheduler: AsyncScheduler, id?: TimerHandle, delay: number | null = 0): TimerHandle | undefined {\n // If this action is rescheduled with the same delay time, don't clear the interval id.\n if (delay != null && this.delay === delay && this.pending === false) {\n return id;\n }\n // Otherwise, if the action's delay time is different from the current delay,\n // or the action has been rescheduled before it's executed, clear the interval id\n if (id != null) {\n intervalProvider.clearInterval(id);\n }\n\n return undefined;\n }\n\n /**\n * Immediately executes this action and the `work` it contains.\n */\n public execute(state: T, delay: number): any {\n if (this.closed) {\n return new Error('executing a cancelled action');\n }\n\n this.pending = false;\n const error = this._execute(state, delay);\n if (error) {\n return error;\n } else if (this.pending === false && this.id != null) {\n // Dequeue if the action didn't reschedule itself. Don't call\n // unsubscribe(), because the action could reschedule later.\n // For example:\n // ```\n // scheduler.schedule(function doWork(counter) {\n // /* ... I'm a busy worker bee ... */\n // var originalAction = this;\n // /* wait 100ms before rescheduling the action */\n // setTimeout(function () {\n // originalAction.schedule(counter + 1);\n // }, 100);\n // }, 1000);\n // ```\n this.id = this.recycleAsyncId(this.scheduler, this.id, null);\n }\n }\n\n protected _execute(state: T, _delay: number): any {\n let errored: boolean = false;\n let errorValue: any;\n try {\n this.work(state);\n } catch (e) {\n errored = true;\n // HACK: Since code elsewhere is relying on the \"truthiness\" of the\n // return here, we can't have it return \"\" or 0 or false.\n // TODO: Clean this up when we refactor schedulers mid-version-8 or so.\n errorValue = e ? e : new Error('Scheduled action threw falsy error');\n }\n if (errored) {\n this.unsubscribe();\n return errorValue;\n }\n }\n\n unsubscribe() {\n if (!this.closed) {\n const { id, scheduler } = this;\n const { actions } = scheduler;\n\n this.work = this.state = this.scheduler = null!;\n this.pending = false;\n\n arrRemove(actions, this);\n if (id != null) {\n this.id = this.recycleAsyncId(scheduler, id, null);\n }\n\n this.delay = null!;\n super.unsubscribe();\n }\n }\n}\n", "import { Action } from './scheduler/Action';\nimport { Subscription } from './Subscription';\nimport { SchedulerLike, SchedulerAction } from './types';\nimport { dateTimestampProvider } from './scheduler/dateTimestampProvider';\n\n/**\n * An execution context and a data structure to order tasks and schedule their\n * execution. Provides a notion of (potentially virtual) time, through the\n * `now()` getter method.\n *\n * Each unit of work in a Scheduler is called an `Action`.\n *\n * ```ts\n * class Scheduler {\n * now(): number;\n * schedule(work, delay?, state?): Subscription;\n * }\n * ```\n *\n * @deprecated Scheduler is an internal implementation detail of RxJS, and\n * should not be used directly. Rather, create your own class and implement\n * {@link SchedulerLike}. Will be made internal in v8.\n */\nexport class Scheduler implements SchedulerLike {\n public static now: () => number = dateTimestampProvider.now;\n\n constructor(private schedulerActionCtor: typeof Action, now: () => number = Scheduler.now) {\n this.now = now;\n }\n\n /**\n * A getter method that returns a number representing the current time\n * (at the time this function was called) according to the scheduler's own\n * internal clock.\n * @return A number that represents the current time. May or may not\n * have a relation to wall-clock time. May or may not refer to a time unit\n * (e.g. milliseconds).\n */\n public now: () => number;\n\n /**\n * Schedules a function, `work`, for execution. May happen at some point in\n * the future, according to the `delay` parameter, if specified. May be passed\n * some context object, `state`, which will be passed to the `work` function.\n *\n * The given arguments will be processed an stored as an Action object in a\n * queue of actions.\n *\n * @param work A function representing a task, or some unit of work to be\n * executed by the Scheduler.\n * @param delay Time to wait before executing the work, where the time unit is\n * implicit and defined by the Scheduler itself.\n * @param state Some contextual data that the `work` function uses when called\n * by the Scheduler.\n * @return A subscription in order to be able to unsubscribe the scheduled work.\n */\n public schedule(work: (this: SchedulerAction, state?: T) => void, delay: number = 0, state?: T): Subscription {\n return new this.schedulerActionCtor(this, work).schedule(state, delay);\n }\n}\n", "import { Scheduler } from '../Scheduler';\nimport { Action } from './Action';\nimport { AsyncAction } from './AsyncAction';\nimport { TimerHandle } from './timerHandle';\n\nexport class AsyncScheduler extends Scheduler {\n public actions: Array> = [];\n /**\n * A flag to indicate whether the Scheduler is currently executing a batch of\n * queued actions.\n * @internal\n */\n public _active: boolean = false;\n /**\n * An internal ID used to track the latest asynchronous task such as those\n * coming from `setTimeout`, `setInterval`, `requestAnimationFrame`, and\n * others.\n * @internal\n */\n public _scheduled: TimerHandle | undefined;\n\n constructor(SchedulerAction: typeof Action, now: () => number = Scheduler.now) {\n super(SchedulerAction, now);\n }\n\n public flush(action: AsyncAction): void {\n const { actions } = this;\n\n if (this._active) {\n actions.push(action);\n return;\n }\n\n let error: any;\n this._active = true;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions.shift()!)); // exhaust the scheduler queue\n\n this._active = false;\n\n if (error) {\n while ((action = actions.shift()!)) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\n/**\n *\n * Async Scheduler\n *\n * Schedule task as if you used setTimeout(task, duration)\n *\n * `async` scheduler schedules tasks asynchronously, by putting them on the JavaScript\n * event loop queue. It is best used to delay tasks in time or to schedule tasks repeating\n * in intervals.\n *\n * If you just want to \"defer\" task, that is to perform it right after currently\n * executing synchronous code ends (commonly achieved by `setTimeout(deferredTask, 0)`),\n * better choice will be the {@link asapScheduler} scheduler.\n *\n * ## Examples\n * Use async scheduler to delay task\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * const task = () => console.log('it works!');\n *\n * asyncScheduler.schedule(task, 2000);\n *\n * // After 2 seconds logs:\n * // \"it works!\"\n * ```\n *\n * Use async scheduler to repeat task in intervals\n * ```ts\n * import { asyncScheduler } from 'rxjs';\n *\n * function task(state) {\n * console.log(state);\n * this.schedule(state + 1, 1000); // `this` references currently executing Action,\n * // which we reschedule with new state and delay\n * }\n *\n * asyncScheduler.schedule(task, 3000, 0);\n *\n * // Logs:\n * // 0 after 3s\n * // 1 after 4s\n * // 2 after 5s\n * // 3 after 6s\n * ```\n */\n\nexport const asyncScheduler = new AsyncScheduler(AsyncAction);\n\n/**\n * @deprecated Renamed to {@link asyncScheduler}. Will be removed in v8.\n */\nexport const async = asyncScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { Subscription } from '../Subscription';\nimport { QueueScheduler } from './QueueScheduler';\nimport { SchedulerAction } from '../types';\nimport { TimerHandle } from './timerHandle';\n\nexport class QueueAction extends AsyncAction {\n constructor(protected scheduler: QueueScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n public schedule(state?: T, delay: number = 0): Subscription {\n if (delay > 0) {\n return super.schedule(state, delay);\n }\n this.delay = delay;\n this.state = state;\n this.scheduler.flush(this);\n return this;\n }\n\n public execute(state: T, delay: number): any {\n return delay > 0 || this.closed ? super.execute(state, delay) : this._execute(state, delay);\n }\n\n protected requestAsyncId(scheduler: QueueScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n\n if ((delay != null && delay > 0) || (delay == null && this.delay > 0)) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n\n // Otherwise flush the scheduler starting with this action.\n scheduler.flush(this);\n\n // HACK: In the past, this was returning `void`. However, `void` isn't a valid\n // `TimerHandle`, and generally the return value here isn't really used. So the\n // compromise is to return `0` which is both \"falsy\" and a valid `TimerHandle`,\n // as opposed to refactoring every other instanceo of `requestAsyncId`.\n return 0;\n }\n}\n", "import { AsyncScheduler } from './AsyncScheduler';\n\nexport class QueueScheduler extends AsyncScheduler {\n}\n", "import { QueueAction } from './QueueAction';\nimport { QueueScheduler } from './QueueScheduler';\n\n/**\n *\n * Queue Scheduler\n *\n * Put every next task on a queue, instead of executing it immediately\n *\n * `queue` scheduler, when used with delay, behaves the same as {@link asyncScheduler} scheduler.\n *\n * When used without delay, it schedules given task synchronously - executes it right when\n * it is scheduled. However when called recursively, that is when inside the scheduled task,\n * another task is scheduled with queue scheduler, instead of executing immediately as well,\n * that task will be put on a queue and wait for current one to finish.\n *\n * This means that when you execute task with `queue` scheduler, you are sure it will end\n * before any other task scheduled with that scheduler will start.\n *\n * ## Examples\n * Schedule recursively first, then do something\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(() => {\n * queueScheduler.schedule(() => console.log('second')); // will not happen now, but will be put on a queue\n *\n * console.log('first');\n * });\n *\n * // Logs:\n * // \"first\"\n * // \"second\"\n * ```\n *\n * Reschedule itself recursively\n * ```ts\n * import { queueScheduler } from 'rxjs';\n *\n * queueScheduler.schedule(function(state) {\n * if (state !== 0) {\n * console.log('before', state);\n * this.schedule(state - 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * console.log('after', state);\n * }\n * }, 0, 3);\n *\n * // In scheduler that runs recursively, you would expect:\n * // \"before\", 3\n * // \"before\", 2\n * // \"before\", 1\n * // \"after\", 1\n * // \"after\", 2\n * // \"after\", 3\n *\n * // But with queue it logs:\n * // \"before\", 3\n * // \"after\", 3\n * // \"before\", 2\n * // \"after\", 2\n * // \"before\", 1\n * // \"after\", 1\n * ```\n */\n\nexport const queueScheduler = new QueueScheduler(QueueAction);\n\n/**\n * @deprecated Renamed to {@link queueScheduler}. Will be removed in v8.\n */\nexport const queue = queueScheduler;\n", "import { AsyncAction } from './AsyncAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\nimport { SchedulerAction } from '../types';\nimport { animationFrameProvider } from './animationFrameProvider';\nimport { TimerHandle } from './timerHandle';\n\nexport class AnimationFrameAction extends AsyncAction {\n constructor(protected scheduler: AnimationFrameScheduler, protected work: (this: SchedulerAction, state?: T) => void) {\n super(scheduler, work);\n }\n\n protected requestAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle {\n // If delay is greater than 0, request as an async action.\n if (delay !== null && delay > 0) {\n return super.requestAsyncId(scheduler, id, delay);\n }\n // Push the action to the end of the scheduler queue.\n scheduler.actions.push(this);\n // If an animation frame has already been requested, don't request another\n // one. If an animation frame hasn't been requested yet, request one. Return\n // the current animation frame request id.\n return scheduler._scheduled || (scheduler._scheduled = animationFrameProvider.requestAnimationFrame(() => scheduler.flush(undefined)));\n }\n\n protected recycleAsyncId(scheduler: AnimationFrameScheduler, id?: TimerHandle, delay: number = 0): TimerHandle | undefined {\n // If delay exists and is greater than 0, or if the delay is null (the\n // action wasn't rescheduled) but was originally scheduled as an async\n // action, then recycle as an async action.\n if (delay != null ? delay > 0 : this.delay > 0) {\n return super.recycleAsyncId(scheduler, id, delay);\n }\n // If the scheduler queue has no remaining actions with the same async id,\n // cancel the requested animation frame and set the scheduled flag to\n // undefined so the next AnimationFrameAction will request its own.\n const { actions } = scheduler;\n if (id != null && id === scheduler._scheduled && actions[actions.length - 1]?.id !== id) {\n animationFrameProvider.cancelAnimationFrame(id as number);\n scheduler._scheduled = undefined;\n }\n // Return undefined so the action knows to request a new async id if it's rescheduled.\n return undefined;\n }\n}\n", "import { AsyncAction } from './AsyncAction';\nimport { AsyncScheduler } from './AsyncScheduler';\n\nexport class AnimationFrameScheduler extends AsyncScheduler {\n public flush(action?: AsyncAction): void {\n this._active = true;\n // The async id that effects a call to flush is stored in _scheduled.\n // Before executing an action, it's necessary to check the action's async\n // id to determine whether it's supposed to be executed in the current\n // flush.\n // Previous implementations of this method used a count to determine this,\n // but that was unsound, as actions that are unsubscribed - i.e. cancelled -\n // are removed from the actions array and that can shift actions that are\n // scheduled to be executed in a subsequent flush into positions at which\n // they are executed within the current flush.\n let flushId;\n if (action) {\n flushId = action.id;\n } else {\n flushId = this._scheduled;\n this._scheduled = undefined;\n }\n\n const { actions } = this;\n let error: any;\n action = action || actions.shift()!;\n\n do {\n if ((error = action.execute(action.state, action.delay))) {\n break;\n }\n } while ((action = actions[0]) && action.id === flushId && actions.shift());\n\n this._active = false;\n\n if (error) {\n while ((action = actions[0]) && action.id === flushId && actions.shift()) {\n action.unsubscribe();\n }\n throw error;\n }\n }\n}\n", "import { AnimationFrameAction } from './AnimationFrameAction';\nimport { AnimationFrameScheduler } from './AnimationFrameScheduler';\n\n/**\n *\n * Animation Frame Scheduler\n *\n * Perform task when `window.requestAnimationFrame` would fire\n *\n * When `animationFrame` scheduler is used with delay, it will fall back to {@link asyncScheduler} scheduler\n * behaviour.\n *\n * Without delay, `animationFrame` scheduler can be used to create smooth browser animations.\n * It makes sure scheduled task will happen just before next browser content repaint,\n * thus performing animations as efficiently as possible.\n *\n * ## Example\n * Schedule div height animation\n * ```ts\n * // html:
\n * import { animationFrameScheduler } from 'rxjs';\n *\n * const div = document.querySelector('div');\n *\n * animationFrameScheduler.schedule(function(height) {\n * div.style.height = height + \"px\";\n *\n * this.schedule(height + 1); // `this` references currently executing Action,\n * // which we reschedule with new state\n * }, 0, 0);\n *\n * // You will see a div element growing in height\n * ```\n */\n\nexport const animationFrameScheduler = new AnimationFrameScheduler(AnimationFrameAction);\n\n/**\n * @deprecated Renamed to {@link animationFrameScheduler}. Will be removed in v8.\n */\nexport const animationFrame = animationFrameScheduler;\n", "import { Observable } from '../Observable';\nimport { SchedulerLike } from '../types';\n\n/**\n * A simple Observable that emits no items to the Observer and immediately\n * emits a complete notification.\n *\n * Just emits 'complete', and nothing else.\n *\n * ![](empty.png)\n *\n * A simple Observable that only emits the complete notification. It can be used\n * for composing with other Observables, such as in a {@link mergeMap}.\n *\n * ## Examples\n *\n * Log complete notification\n *\n * ```ts\n * import { EMPTY } from 'rxjs';\n *\n * EMPTY.subscribe({\n * next: () => console.log('Next'),\n * complete: () => console.log('Complete!')\n * });\n *\n * // Outputs\n * // Complete!\n * ```\n *\n * Emit the number 7, then complete\n *\n * ```ts\n * import { EMPTY, startWith } from 'rxjs';\n *\n * const result = EMPTY.pipe(startWith(7));\n * result.subscribe(x => console.log(x));\n *\n * // Outputs\n * // 7\n * ```\n *\n * Map and flatten only odd numbers to the sequence `'a'`, `'b'`, `'c'`\n *\n * ```ts\n * import { interval, mergeMap, of, EMPTY } from 'rxjs';\n *\n * const interval$ = interval(1000);\n * const result = interval$.pipe(\n * mergeMap(x => x % 2 === 1 ? of('a', 'b', 'c') : EMPTY),\n * );\n * result.subscribe(x => console.log(x));\n *\n * // Results in the following to the console:\n * // x is equal to the count on the interval, e.g. (0, 1, 2, 3, ...)\n * // x will occur every 1000ms\n * // if x % 2 is equal to 1, print a, b, c (each on its own)\n * // if x % 2 is not equal to 1, nothing will be output\n * ```\n *\n * @see {@link Observable}\n * @see {@link NEVER}\n * @see {@link of}\n * @see {@link throwError}\n */\nexport const EMPTY = new Observable((subscriber) => subscriber.complete());\n\n/**\n * @param scheduler A {@link SchedulerLike} to use for scheduling\n * the emission of the complete notification.\n * @deprecated Replaced with the {@link EMPTY} constant or {@link scheduled} (e.g. `scheduled([], scheduler)`). Will be removed in v8.\n */\nexport function empty(scheduler?: SchedulerLike) {\n return scheduler ? emptyScheduled(scheduler) : EMPTY;\n}\n\nfunction emptyScheduled(scheduler: SchedulerLike) {\n return new Observable((subscriber) => scheduler.schedule(() => subscriber.complete()));\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport function isScheduler(value: any): value is SchedulerLike {\n return value && isFunction(value.schedule);\n}\n", "import { SchedulerLike } from '../types';\nimport { isFunction } from './isFunction';\nimport { isScheduler } from './isScheduler';\n\nfunction last(arr: T[]): T | undefined {\n return arr[arr.length - 1];\n}\n\nexport function popResultSelector(args: any[]): ((...args: unknown[]) => unknown) | undefined {\n return isFunction(last(args)) ? args.pop() : undefined;\n}\n\nexport function popScheduler(args: any[]): SchedulerLike | undefined {\n return isScheduler(last(args)) ? args.pop() : undefined;\n}\n\nexport function popNumber(args: any[], defaultValue: number): number {\n return typeof last(args) === 'number' ? args.pop()! : defaultValue;\n}\n", "export const isArrayLike = ((x: any): x is ArrayLike => x && typeof x.length === 'number' && typeof x !== 'function');", "import { isFunction } from \"./isFunction\";\n\n/**\n * Tests to see if the object is \"thennable\".\n * @param value the object to test\n */\nexport function isPromise(value: any): value is PromiseLike {\n return isFunction(value?.then);\n}\n", "import { InteropObservable } from '../types';\nimport { observable as Symbol_observable } from '../symbol/observable';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being Observable (but not necessary an Rx Observable) */\nexport function isInteropObservable(input: any): input is InteropObservable {\n return isFunction(input[Symbol_observable]);\n}\n", "import { isFunction } from './isFunction';\n\nexport function isAsyncIterable(obj: any): obj is AsyncIterable {\n return Symbol.asyncIterator && isFunction(obj?.[Symbol.asyncIterator]);\n}\n", "/**\n * Creates the TypeError to throw if an invalid object is passed to `from` or `scheduled`.\n * @param input The object that was passed.\n */\nexport function createInvalidObservableTypeError(input: any) {\n // TODO: We should create error codes that can be looked up, so this can be less verbose.\n return new TypeError(\n `You provided ${\n input !== null && typeof input === 'object' ? 'an invalid object' : `'${input}'`\n } where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.`\n );\n}\n", "export function getSymbolIterator(): symbol {\n if (typeof Symbol !== 'function' || !Symbol.iterator) {\n return '@@iterator' as any;\n }\n\n return Symbol.iterator;\n}\n\nexport const iterator = getSymbolIterator();\n", "import { iterator as Symbol_iterator } from '../symbol/iterator';\nimport { isFunction } from './isFunction';\n\n/** Identifies an input as being an Iterable */\nexport function isIterable(input: any): input is Iterable {\n return isFunction(input?.[Symbol_iterator]);\n}\n", "import { ReadableStreamLike } from '../types';\nimport { isFunction } from './isFunction';\n\nexport async function* readableStreamLikeToAsyncGenerator(readableStream: ReadableStreamLike): AsyncGenerator {\n const reader = readableStream.getReader();\n try {\n while (true) {\n const { value, done } = await reader.read();\n if (done) {\n return;\n }\n yield value!;\n }\n } finally {\n reader.releaseLock();\n }\n}\n\nexport function isReadableStreamLike(obj: any): obj is ReadableStreamLike {\n // We don't want to use instanceof checks because they would return\n // false for instances from another Realm, like an

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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/postgres/connection.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/postgres/connection.doctree new file mode 100644 index 0000000000000000000000000000000000000000..c81e3d6d27e024c447e564e7be97f5133459bf19 GIT binary patch literal 3711 zcmc&%TW=&s74~h$p0Pcrw#gxxXjL_$zIzN-C2wDX;WbV_uL2y|@@NV=8?ynebwfXvV3?b;9LjM)C!n zJo@qZS#uAs@#fh~SUMcz@4^tZpk^gPmehzuQIZ=*;Ybuj%gK52_`~GmNxVMcWlpaX zW(qO%Z1=f`3j;;x6du9D)@vAjc+23Hw-@d1^8NMy?@2Vy4T&*|&#XZrL*ll1u8}0S zn+c*dh2)3Xx}<2;ih==v?J4t}n*Nh>9VT>Qej9l=ZLFSlI3B1j92IkGdt-i1@_1R47tp6}!N z0iH*2st?Pftv_jP{V$Q=EjwfP*kiVTr=DZdstSAqmaqvszuW@qhmcd#&hqE@3JMYNdBL5+uY&cXoAcYsVkfZwjY}x)-;K=J@l*%kfHn zTRJ0UTx(@+DCJ#7(l zx{74iz7@5T8{FzB`g|4ST9NxMy{-_MblE%dbasq%XN)Y1;;&ues3lMTAAsIGY50K@rX^ zH)LpnX>;tkBYMpdMMI3$D{3#NGrNGg(MzIAL=haAJ_RTPq$8ujrC=W~5wjEnM&{u1 zZeY#)?5Ag%ziXt_*0e zzIXe>+D&&qH~`>jhWH3LjW~Q!>9x#WG z2nWwwG_bxK5o=_+BDt;5fJK+sV zG*z)MX&I#D$oD=rCgiC;UT(UhP;7urxc~|UFSzla-T#prRkT{QkKoKfpv(wt4|U<* zX~!AJI;n09lVu{+0_F5Da7F{bnHpLkJqGF61O4pL`A3N7NX@e~@qw}u|HD>1DwhPj zUQv~BYUWV~y1^uDNl-R0WQG#LR^>1E+z9|#q|3@&_PYPeSK3Ja-A~1U;+=o zyCY=4{ednBHo!kwl(a^mj^nvLrb1?<3GGS72<$=P>taKcV+e8k%2rSj_SQIinjY{5 z=#d#O=fFVI6dr^wffl>Q7MC^XB6TH(RpZJ7GR2JMDBl}cz$i7)@h}6|RZ0uOMM`q4 zym5(o?T(s>2&{^E-O%2+ZCbwYm{)GEZY8K)AQ8(ZM{St2-U;G*b+o=PhgQyx*}irU zZhfk^vsb8$wN3O`2QBmE_ptwjT`!E4=bk+w+U%_D-C;MgfzKUg9QeSx63|prLIJmYwNqV6&44Ua)MU@~|Fjw6|L8vek6S*zrdCMx*Fj>T*7^-f}k@Kpa^pQn7#k`bYP(Pml!_ zz&Ea;F@C?@!a6(1`(v)hn1ADCk>!c67bW)i%i-q6hW?hl7(2k1d*A_U5C6KWQM(Ye zD~{_mqi)mf1bm=kKb`*OX&68CFF$m|W-IRnvD>vgx4#Tf#QQ!kGhuV|I=KkHHimha N3r!0RYnq)U{{jLtrcD3< literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/postgres/execute.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/postgres/execute.doctree new file mode 100644 index 0000000000000000000000000000000000000000..a85e6dd437dbd05869e1bcaf9eb3c2e47f439fea GIT binary patch literal 21207 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 CPNW_H literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/postgres/prerequisites.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/postgres/prerequisites.doctree new file mode 100644 index 0000000000000000000000000000000000000000..b6842bc3b5414161ae720658521d5aa4c581370f GIT binary patch literal 15639 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 L(2!ol_3HlspvMmE literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/postgres/read.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/postgres/read.doctree new file mode 100644 index 0000000000000000000000000000000000000000..02ebbbf1c61e96afe0ce3ce3361876ee98000525 GIT binary patch literal 17378 zcmeHPYiu3Ib(X~UrAUdA71@;>uj$AlUENE`PU?U#-71O`%StF&@(V46y?gHtxqG>H z_j(@^+1gEN#DR%UlcL$=PnzcSr-f0w=?g8;q6peQMO*Y&kQN1sB7cga4cs3^TcD`X z@67D%&RyOI$)*V!M8LheJ3Djc%$e_;Idf(XKRNdKZ(lhi{_$BmXeP1iMKwRLS(Nfg zDrhr5PCu6}{zCd{y1=JG>uwYzVUwl&5Nb3X*Rw@R<|d>d>Wo-koH{4@ND{g!|G)D_w47SrMjYeQ%=oU zaxPvwg8CzT%FH#!8A%#I)3}7C4v`bUzu5_*cq3#{l>oXd@zi%nV;GWar+lWXmEiL- z`W~acou9&|#yW4cg9IgNY%%n=3bYMEZjtE*WWPK!=P>4$VO-+5tY^JAN~-%XJNfAhD$U?pJQ8O zT=>~RP1Dy?VSF&m7v(Tlor|lIL(h#_Xn7ik{OGQ(oHahFde?|Gbz=PyMbm6i-${e@ zg&kFT9+hrNn$C61%cx*+3b3^{LaXEGOnz-SXtb7dnHWzs;w>_BI`3K^$Nw+?D9KiN~YbrS-GN%_N?jttP4 zR4f{H(6(GZbw0}{AeVT%gC%l=IN;NGW?LQ4ZMt##hIoC~3jK74Pj$AJ+ku@xkyiO6 z9)xwqZ(4;i)4|W}2^+#3{5;GNL2n#T+y?yCcty>2WN6Ue)}`otLP?$$@1CLG1oywOrx|j-*d_Wu)J0FE zyG6fKi2e=d>wGfjoM5qAbXUJ;I;DxGTk<*P`J~c2De^TwN@}Pz{OkTQ?S*hT`-f{9 zg|4m#o3gtaj)X~*)|OBU#n*MdcEe*<#EjU2?Qs1#Fsx=XNc=c6P-acdxqHA?s^*i|J3Op47dMw0Nl2Y96oet zU(hWNK?m?OwCB6S)+#l@RQaCupkWnXJ*;;>+bpd~GE-DX8o8@no z`s$YJ9$mB6AA`yugCVxySqW;{jKxI=yh*UzjrSk6}Wyr>j=0$-DjX0v|lXsdoXC<0+oY6+d{j2LA&pWbabBr z6iA+XMK{%dddLV$Xrm@i4QbLB-PHKxb?~O6b;&SuaoybvzdOn(SWWG>oSh z)BucsfnN8<_=CF*ch9~Gn14Cz3o!q&{v+0q{?$_N2SfUoK;P_rv z9vC)+VsBICaBq8?(-8l+L~jGR(tS~C(og(zQvCM188sc&ws!Bl4~_H}@)3I1TgWr@ z`Yxhtd@3)~#64A_GH_sG3rYR%RX^^<=-=I+z^F+-f&E-&OY8NZLkD6A?HCIYV}?A! zP={AFI+waGYp64t;!J=}XtXC4R;*|?(1l$uWo}+qnxj2oUBzg}Gkx6Osq9%ng=-*A>1(V{QD!8m0Eq*3Dl4W+)ZpfqC zY98*k)cpjlnxU!{QSq)j1vgZfJqsdG40}Mhee$U-tKIR6*m_Y=tWvKHL}7U?d>&){ zMBYLK($9j4!epJel|`9UuyQ*KRj%<~uL?eXIj1V4UHvgl&XhW(c(29}_&CI8vU7j4 z;RVf2EoT1`1%bu=gEEG3$B1qmU@L?ATeB_jq_P2nPFb(WvCW#I{_Ct zumWBYM;t4X5kG0)P%omDOBWX}R2D1tI>^hx*TTeK$Q-$~AIbdtDRZeyP>8QUm7+m6 zp{BOl-g4Bj=&s&m2ZwJgyn~-(xG9379X{z<{zig}cjB@{?BJRnpLN^~$HON!ai5N^ zj*%VxHPTvn{9|;hVqp?aUDYn1sE~SY75cm_48!jo$S_>&;->Xw5K*D~m&V~OYC2x1 zr_yz$4?`@0MnertQP75bE4zsE!(=#cM6{x8TQ>0*51mzvU4VC z!ZhKI3s4rpn5^hl3dRE$G>z|-8LHfMW5?(r`h~@1(NSMJ^&>?5Z=+4>zk?Xr=Nc3Gvu*W{B-xoU-9sF!6I(8%>EJAveX z4R8XF-DJ(6jjMm~4QM;7c4fQD3yb^rU|rw!4|c`KCH%X*g>VT!I+&%ix0nZOVFbRn z4Y%Rt#0?P$Qa2p3-P7Po1+(wuUGK$jBxe65Zy}g{rptf*;#EJ2v9&jXb)$p((D0TJ zy_$by9$^62$4_PU7IF^_zTfRdyoQOx{qVdMW)x$TwG45Ex@J7*ApV8HCQV$p#O-s# zYKt3D=uF(oq#hv(v9PE`$iU61TX4oUvwN4=S)S~{q_%bX?|gu}E=%CBUyMbeU<6NE z%dye@%sd_TQ(q7uZl#fUG*SbYI5R+sim}sunTgqyW;~uA15$E|=y|3nlI0UeAY0tp^(_f-ChW;5wdhSxSf|XR+PX)O6kTa37tliBNQiOz5-`xi-r$!rSXLS#JTqGRv`ojhoovofkF+ z1_Ia(IxsYv7G?W9)kL+JrzW^KoJ6cv5w@ohz(X2ffFi6Deaz1V1DP5|o2K210&;Z2 z;0`mu3APPw3u!TAGs&pYwejs*<*BH{ny%%+$h+c22z!T*NUeeW!?pB$j~R&@wHbyX z4;VpVZL^zQY7jZ7YWowdhPa>3CvBH;e27BYLuNqPrw4}A)Ou``dK`{Y;!&Vcx+|UZ}R^CFa059kV%{)K%C6!KZ%@1w{X zR*ovUg;lk)u8QobJg}TPt4w(lMH6YtRg-L;Q9}ji-+5e zB%~}2(l#60rpVHJHEsPBiQ-DcPxq{aB(+z;A}Z)(Qw*zq^He|QI==^D6F>6AriF6; zC?`%PERMwi(wNd~hj>NRVKyEU+8rI{v&|%=bU-sp8(if_Rf)({Sw(sWi(?2BiL7&! zmM1b>AzUBc4AM`=U!eGEl*f+wc#y=MBu-bI=aGN2!F(i^#X(5PI23S=Vno^TFdYT~ zr6(dKZwpucmXKp2S-|abe-jld*<%uVc$$fWrb!8v^cJj)f#(qENhj7p2FX}Vd-(>^ zOd)y%U(GFAZILT9i;$Ds!6_r{?cxXo&&U!uhoZ!!Xo#tjI?-R&Aj|TESVAT;Z9R5} zL7Yc;#X(A^nLUp2Ikx3OiZSlzM@8wF%^JxDx|_{%K2QSyZKGrg)6rx!B;wEYAu|){ zvH^h?nNUf*Uj1M-a`F3Y_e+z7VGx=Q91M?zm;qw78K?Z1l)Oe|Oi)Sr{iQc060LUu zM?Nd*7CfldXS-jN8uQ~c24F98ARlh{^;@S!j&N!d75GgZ@B7Q{VZrVw^W9E>AU_}9LEU?grs|7O0M2P@9&3;kT ziCAI>Cc?R>bsU-(M9kD%3Y0)+@3@gGA$!cU-G~kv=zv;sIiXYqYIhWaL7uiR^bSO4 zvT7zAQw$DiwC|x`=mbGya{VzkM9s#OPa_39f4p{>&yl_XIsOKK=(%m=xPAQic|O}= zox+IFV30T10ouVksRqAS<>#U}s>y&#EeJQDQKzV;U;s5uWMyD_ViCn=o$9%Z?}z%% zhU?7|c`7N$Kdi`;Cr*&vL`tR0;&ssn48jtneWSA}9LPY6kS3m9o8&>Ktz&s`5i|pFW4D9e3l(_ynFp$}W6J#i0WlPmW_Br|zD~ZgV-b?|Rb^`}S zQM?Tf9L8ewajJ=0n$xoH7g+VyrA^!6Bh0^(BITA(N^1i31rp)n zBc=n~$xKCoo4N8|03d7boOGrmexmD@h?U^ouGIRI5%sd3zo&$5N?xax1t;!TtcXw0 zOIfxcm!#F!U&0i${+zyEL9n8=O<#XQU*DpyH|fQ%)7Sgy>k@r^oxZ+8Uw=kl-^5qK zr--!3RYQl8_#1-uj-b0EXzmDlJA&4ZptB=r>?F?pl$XBh+!UGOm9ATWo4xB2}%HCiBhcOg6fn@ckvHhdCAdCu@!-*>t8AJK! zFuK>%n^0`*7GWarhY2DaP00>(o2VD#7q{+$db*dm%LN+C*5AW? zRAvf~US0jjby^eUAJ>45&i?Sh7#+YhB|Sc!OG0SiG;UaWi4E%;Y>?5e*tAk*MxA22 HNu%~Z#mRZ1 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/postgres/sql.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/postgres/sql.doctree new file mode 100644 index 0000000000000000000000000000000000000000..a2a54d4da135833f198a74ecedf4b0323f530707 GIT binary patch literal 11841 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 NR5_+o`YvtO{{f3RjN||S literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/postgres/types.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/postgres/types.doctree new file mode 100644 index 0000000000000000000000000000000000000000..1a9d5aa5d935835f346cbdd28cd178f391ed0a59 GIT binary patch literal 113678 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�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 x;U2~$W_^D7s}aUEr)^32=uQmaL!i$?nGSf#iVY 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/teradata/connection.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/teradata/connection.doctree new file mode 100644 index 0000000000000000000000000000000000000000..8e927dc9059b3907bda27cd8962f02570aaea872 GIT binary patch literal 3711 zcmc&%TW=&s74~h$p0PcBCanVW$WHO6Z3B0gsRd?4+Rb1WG z)MeL}vJxOg+Lh`p1&{m!ek5O2ch8ImClb71spYQIr_S{|m-$QY?`E_S|J=^I-t7fKNh{;O_8MpkJ- zXT0L!bs>QD+&_|J{H3;aEtN@`lvn!8F)zuSUR;csF}1#!On5m@G~-khI^lAXkz!6K zkA8H1*51Qwym^)hONV3pT^OR~)GS5FiW-q9N(#d$9EpNxH91cn|2X+*60c8qRnY5% znNkcr+kNig!a&g(g-7tP^BP7U-ZHr3?M1s+`Tq9*_aqwUhQt`fGi#8@khpDLXe7z) z=7MMqBHnuyDVw*w!q&?}M)E5}A@Xb!VLUr_+g5S!KKh>$ZquT{y)~kg@OKc^}XB z@jQZ4eOMlC{kXIBzeIw!?2O%GkJv^c9W-3GvaLUESEa;t%V$h0{abi(5r={8{VecqzXt zot@9`Z~Zj@UU3Mg%5P2E99i6u65 zdzu=9j_ z*jG}9f(CrAz1y64x8InMLhltAhV9WRfzzRpc^X^sn7Wqwm!~bQva>hQN0l3KcX`(wE$@v}=RV*IsD*BEq6*oK1qhpa|!d z8!|M(v_1CR5xwS!q9MlWCAC-3%+8^1^pdCwQ3MC3PYKEZ>BwntDcHv=#4N>tkp;NC z8(1?t`{Yb>yuWw*V@j2hDrIP%f+}Pnn3@~!?lqBLkPib-KVKq8bStP*fEWLEybeiAWErUy^D~Z6VX^GN9=` z%1&yUuHK9gLAAc%nm2g8mlj-;Oaw<-D|fp(LCqb&8LTx`EIJ51Y}wKo7tH|uPI!Y7 zO;s#RS_NqZ^1Y9Z33+Ocmz(Y=6dPbu&4EI}OK$w9_kZX{HLaKJBRF#qC>eq6p)TAz z-8chTC-seCvPz_yqnsWF&S(HQFYhpr1WD{|VwbQnP$be4s4F|DY2erc%J` z6;%nRW)^jz8%)BM1Z4w5GL#UuDu21>P5{Uzm4u>y+_16LB0|wfr@E~*vwAeMaxU4}JX(} z#wF^tJ8CB)uqtLvLwn=4Y4yTmUb(%dm7sQkL@b*ewPDg`Cy49S(fYz1S~)vr``SIY z^{Ls;UZFD9Hqm1pw9FRY!~PR?y)agud-jBAw;Mh-v{MYDDHDuaHwaX7QzQ6cmnvT~ z@y4=~@IJ*HG>WdHF6JZaEq0>;#F4ooHT%!6e{w(l4YHsD z_{I%1#vj1a_5JXJG1p_vzwx5Tio`dI5_|l`aC2irf6HEs9pH;S@PM_4f2*rew-9wJ zj_b9fZqw}qe4t`~JN@<3Fn*d}e&~qpR^AI@w;OqGe-WUF_kCXF!WQUtavpwd42v)q M+7=qtG@B*=1~#&!#Q*>R literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/teradata/execute.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/teradata/execute.doctree new file mode 100644 index 0000000000000000000000000000000000000000..481705ef6b7ec7f5592fc091f4101d1588972ff6 GIT binary patch literal 21310 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 XH3S3hD#Z{(H42>K7=tuEtQ7tiZy*)< literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/teradata/index.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/teradata/index.doctree new file mode 100644 index 0000000000000000000000000000000000000000..ed35baa59623bf9d720d30dd6d435496fb73a9ca GIT binary patch literal 4805 zcmc&&TW=gm6}Dr~*fYMx_J#%RT9`%IO$45?ffdL?TolAbW+xDK;bo&%O?S;y*?mh@ zbvzaukzL3Vi;{Rq!4pUbAtatx$^$=V-}nW*@}27LnepTbFGL!p`}C<(=k}e8?>GMN z&#gKC&#p&VSQr`WPMSqrJG)GTgr~+ma@Rj}_uMtxQtVV`g$lW|bKnStj3dQUciUYP zbM{qT^tF)&VzM-d_&}zT9>)Si!~U_;+5JEld7dc~h%7tNAMDD64f)~Wt}rIohuyAB zh8-;=k0afYS+~!kA@5%O%Klz;4v+riUOz5)dl&x>4O2sICL*$wo4BJmi;Unf#0%nS zcfWJ+)y~^pf4nQxh>tqLByrn`rP~fBv=kq3SOg1etD*GT1%+$ghSz(V@1y^}C0;q( zX2vMlFAM@nfm@J~Mv&}M7&EOw#B(EMN%^!K75U_)&%zUU!SgJz*f_Cm7YZevefxh3 zkhATOrICzSsg0fgwiDNNVV4XW(zQ-pvCTTIR==sV338Ri+3;J=F2*wD?&y+j<_uwh zGMh!8iPvD1*g?cTkKY&Y`yzf-t}2f-A(H*boO|<*D~a%`z>gQ)CVK9~iC}+`$gZRC=a04Ok;10IfdYwfin^Y|xG3 zJNR|%50K6Y?`p)Hsjf-#uD7{MY2OXAH02?oup9M*skd5Wg#vipd7YPZ$v&mDXc9UI z;SHcWz5tEocPm?JR4l&BM^rNG0IM`6CYOTT?!Ip2q+t6?D*h(^&c%mE z^Wr1hz6qZ(3aWPEH|OHPx%l{8{Pt*0JUkcYD1N^a|CkQK8eH&bHVESPFX41P2FEqg zqsDOnm8vp3Rfv2Yyzq|lOB(zHH&>OZ#4S>#?x~S`Xg|kCIVwyrAqM@k!1sZBPCOPj zw0-rdEGUFv=z*c=Pn4`xtIP7ZlZc!46{A?HWA8R_iF*3*X zFVHz<$hp0&&r-uifsjKHy3j5EsnRh=-bOLq!DTEkJ>{=Iw+;w)t4EMRv+!JrQU zUxE!@_AbQflOo3zwB-4&Uey4M0HDZpfE=ib!WLBtl-L{VuXOyIoQ#7u#Y*+pP7oFks~l^NogyLCZgtG?AA+PWYg^;a1h46n?}s=i>>LsbbeS0i=MbHXf+;eFVE zrm}RSqCX-J{QGELKXA7I6D(u^99Bq;sz$eBKUmji8lbAFIC0;u)VmlBcE|N6svQFp z??9S<&VEfgSKdg$`aEwu;fiC46HjqOqjm(O*_E;3z7VWb3knL)kp5=Tj;LW)y@di; H^gI6n>|912 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/teradata/prerequisites.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/teradata/prerequisites.doctree new file mode 100644 index 0000000000000000000000000000000000000000..c8db615cf6c3f1333e7c4275f794df39a67c8c43 GIT binary patch literal 13774 zcmdU0>yIQ?b>H22_q_IPh*>C146|!@_l(VAG?>Nf@c_%3-pTBEk)lC0)m?YGs;0WC zU5|MXY)puyo#+aXC=iG|h^!<8I}soF5Ghg+;$J{QLikIRZ;|pbilThT@7!Cr9=+YY zJr>5wN>kf?>psrocg{Wc+|#d3eD{~vPlO_^Gy~ee3}|M%!`umBv-$l zypk;OnZP&*{Wxf`gr7o>mSwwUz`W$k$&z)7KN7~xFtQ^|<9oWvc5Tn5kJ$v)1pi*G zrhPSxyIntsbj$bm!mny}$7r+FRn3Z`Zn#>n+nshTv~1>@Va@jIO~Y)n`r}V6SJQF$ zC`MMBZpyc-{6)c388bj$28r zsV^sBVJ2X^3><+&#WjeYFA=QzP6)b-{r>d5=R^?4D@GIrb~BEkNK)LGZH7=1pK7^A z7@`rMCp@DgKTVr)H~-Q!T6>U!=rd-xQDR--V{u?7{Q5gmP{J!M!!vEukks(gpGmB% zVa%r@qfN^u)+IivU^Vw_5Jq}8@Lj)sBjMw&?Xl$c89v!HpcW8wE^ZpuqhQLq0*!qL ze;>x*NAUMJn3@FTg4SmgttUi65?fVk(OR))`>u1rP)kw28LMX1t>xQi(0`22=$XY> zV{sE`nv}3CLgZ24UyoQ|KpaN38vtxC$98B#Hmed?19_VOZ?>86%l(H7%5Q?238Kb& z4u3U%2T<>@dI&riK|Shp>p~uNGKqT2_dM1j3$2??z3@#fP%qI}gJu%J*HkEn6&SN7 z$%4oWOuz%UW%Jo6vZ>5xe2X2DZ{hRh*RLnS*?@^JNhTWBa-**@Ql*n7UPAXx+1EK>gU?N%luWLGXOMr8FFv; z)6Rsp*B>_XHwvA<_OQ-MVKnnsNTMaBUAYU$?d5Jpd#d(y?GWAsK~(Kf;xgCWf2X>8 z$f#U}|8sH`&VkPV8kH&P{2)`8zhLYe1)e@Byki;yX58>geY+`tGT?2-n{)2;sZ%Fs zZ{AvQ<}q?q4$)t6I2b|JZ>_fpMGBv!Yq2Lm*Ezr6xXYNfTFdWHT#Rs1}cKx99MFVlF zjKOd0=rtkTC2O0H6ER6H%_n-KX`NLbgTMOT(yRb+xxeHJJUrdQ!;mm`T*SqE(42Xg z+kE%+q9S64bO6LWpGltmUSJ^wtfJyvCe;!`6GNQ^@*ZUjqet&4!FhpLf@R#~l?Y(*@CCj6q%YWDQBj(gkHAi;0%HD^kt!KW$?gJWdUZvhE4iN#tZ}Y383no{3FAF(%m9Z(4Dx&>jLHQ-6uaQ$Eh!euj1U6ex*~ zx=8Yk5lws+Pj=XKakz|&;}a)Qcn-&2 z6^{l-T3Pv`Vw}y-r>X-Ys@)@j#KJC8c&p`ahj?Cca6>%r<+U{1LN3YA9G|lIL9cV} z-zH&-tj>rD5$8AA2;qVMU^sz`ia>;WmXEwditq>8bCUiuTVPa@{v#V9NV-z;5ALl< zvqP=xhoRkc8Hp)%+(n6L%_D3bQ;4Gr!fU}5S=V8GtHyXe2Y_Ji`Wx%B=wlf@{~Q?bN{0B0q|5}X%PSs`4n z3jO7QMB$ZGZ59fV-$n@03l-AK5G)rVL5ci+P!a7pvzN}C6usxO5dtcYm7wxw0jz*b z9Lkhdmj!gHkYNE)fY@MV`KjwWJ2h>CzIF`c#_V0>_{G*DKjJ`=*`xGK2oSQfn1$#N`&qtaEsaWaVH^2KCItx+ z=W9Ss7J*MI_LK(3iQB^PCwhjLccI?RCJh>BO-Os2|H*qPwH!OWG8oP8{~=(lFf`Cm z1WNlGO8Tno@p}!0P&v#{HYvMLfI!;Li$bNQ#oqRu*~UpUCIao-*$81MOC>|uyZHcm zTW*ZRZvao&gW`}yunO1p56Fv3iiMdN6sreMR`4`MLl#^c%0t8Jr1uxw{P((MF1hVY z4kz)a*8meGK}Zg%clc4nnjQ-ZA!3@gb@s4^vcAD>eYzDAEpwZ=meiimz7*gzpb$}R z%$YhoDO2EGoL^%M92PNBeCE*PD^4(aS6_7P$DcZ&_cv`(cs@M`=}p&f?G?n# ze_pL>IG*|kh4Y(%SRjN)6(>0vZE9i5VjZLMxy_r6Ep2Uc{pQxXwsrIQ7uU5r4UMkW zn&i2+?g-#gaPX;0C7YVAg|&}r2PA*ZXu;QqD+hMUtC|?dQ!X9 zxVyf&Rh+%nIT9U9t0;B;zvn|D5a9SrXgnNtQP&hz!0v%tv^#*N^cgA)p!0Aq*qoe z*R-81sg^1(AX^LbWbMJhLCrj{5VVS%J*A{XHG=G3mEG&e$J1oe#g=ld`AlkPt&O8t z+@xw9@&J)93k*Sp#? z=xq`sA+x_$>!dOpavS@F-<|a2UXWd|I2y4biX^hji>wX7_T~O+D+Ke8k1PkSE)WLH z>8#-t!J!RVa5WkrvWtMnq*U{ZfR!8(qfK=~%0|Tug_4#M1`Fw!tms8;*b=4vlKLMf z@+hhAY=TP>3`vU1Mp?8%>aGshp9XF|C^#JIr!94JO8p;|63K77@4bt5lkZo~2G;F` zlxhQ{F7z?p0-j!?(IahR3Qv6}lwkU_0@FM42^qFFr6hA~J(pwa9nIVewF|-#4ZJ*( zuOEJMWD=cakV(=<1KACLrXg4z4P?|`f^6{oz1O@1%=eA3hN?uM{?cI%UV~Ux#R%kJ z$3~c6)gw@UwVjm@%1G##o4$u$FT;cSaQ_nA}oETEPkzZ8+D*BbY2b}>5 zJ<13F(j^-*sre#Hd>(tApi`9bC02u}L(l{73ZAY!h!t;A=u$7O;1fssasGbf-*Mli zQzl-Nr*UPb%$R>X3Gn4MUPWG z)nc|OV=4+sCn6u$hso_zyrTLrT$rAU2^gpOLVCZir{$atKBrp59ZG{wBgsJ5awsr9 zM?rNoEhsOb%}BO#yk@C;y3$*aGJzCeaFp=#yO_rFBUX5M zALYLgy%%@%LrUu5+G7E!Kf8;H$qt2UAtroOwxj`Es2?C6(p9l7)=LMZ)wd8!z$m|@ zyKJ99oM(8&LaD9#I>FDeLp#!ORT;%$-a28jX57Yd3v~YiDWV4svmLa|KpibcED?RS z2bt+8WA^F33s*dGv|IgTHMH^h1Lc=G3j#mTEtKfGNO=GT;H?!Ud{IiiFw2CPCE-_k z-tBW%(C9v6j8-77Rc2PI&kdg~jY%8gpQlM7nFfEK+Q!k0JN2W;^1YfLv|&+~sHY$RJyFBlp&}k} z=x5#PGpD)TtaFi_PFX1S>-MesX)7 zUjZgNdS;xN7u03m;h3KdkC3!G)U(tAm5%!a{kgDXz|dZfVbvi7bwPZWUv;FPj8wE& zS{AA+QBn{{dxMmP2r+su?jnytKadcm&8ZtW273G!CHgbJaGo3mylhW5@v8u=Wk_SB zn)bZ}km)p;2@5^~1r}p;wR)+AUVA!HTehpGH&ue_b7{pjuBaZyaf2S8qK8M1Jv?GQLkrU}TI^9`>HA{kW3lS7Snk9q8pkZwa((xbBZM7xQ>lx8>t1OA8n^s9QUCl|N>Fz-iO+k+&`nn?8 zVS@7cBBZ6ZMv^DS73T%?8RlmFap1Zxfy^q-Oo#Xb#AG4R6*|SXu+IWrE=R15p-`?f!{E_6$OwUa3 zcF*>7kNe|xH;$7KCFm9vL68XoVv=A41o0*D5l93P0zwoCiO7nCgpeij2NC&${DFi5 zh`{evb#+(w_VmoH9UDinv~x4vRj*#X`n^|GuU$~`WY=6sbhJj`KRmW`^ zeveJkgO1?@y-)V$eyaCEugs=Aecg9MuW9tyE|h4Rmfi9Ur}u2HZ0=&W_+i5jtN>(O zr`9r7EXSg+F#&Uo{bIGMeb5iPUDpe0rt7ZxAJnXlzG^HiXl4*}{e}5?tFv15P0O%b ze${g48+vQin7{k}QZs2?^IR0=w!NdMhvubc7Q!ocUAL z`yQyibDme9x15%-Q8j~(UFw+=&-T!TDbH9j&?EFv_8Lr2rx=!H$2i|=xsSf_J>kr; zk{$$})d&L&Bn{k%)$%b&Y@%uFzK@6aKIZ5h@olmdc9S0sy}1S{@G>L19rVn@Y$Wuo z9((7G2B^nMP2Fi(EnRTKcK>+Koby9A5$LPbY|lK(#wD!wJ?QyCt?RkAyZTU%joOxD z^v>^L<6RxY0%m5yhHf51r_9?iVt3&0PW-(ce|Mu(LWFnN@S%Jav8#%8#(ATC6eVNZ0_H#sw@@h#Ug`Yk}eE5QknNfG!9;_3hFa zx@5I_Y&uno^SOk!$Ea=dVfoc~XG+rt3IHlk{=E)u<`I zB;9%9rV8=*IP(*}D6MdBj+2^@s*gdlS*NzchS3*xROm?*x-4j#7cnlQoW&`?R$uk>uBkHl(t_J)FT^s@9uy#LHL4gH z8U@2szDedEs%w!Q^0ftkVxjH_9+YZzv#zbUo@O~s&me=X+Zv%uBTA6|$r22K)^ZJB zbKF4E(3+tiV5FW44bL+ClbUU<85pR?FP>Yj*Q;Wb*a5$5TFypQZ?&RnVPDsPGCQla z4X~hg>RmhRlz@LjOflw|`B83)^9Su?{RgegMma}pPj?e`pu1UtjC+P^9pqSkBv-Xn z69R?lv3nF9DbSc6pEO!-N4K1w`EzUnatSuOm?B4r1GX2xjg78tHLaj`gTG$aJ*T(D zrn;L89k&%ik(SvcesJrI?=%ZVri0x*6gIdy_z=tyL2n#T+ywltv638ZUsIsJsY=oO zu#`MIkU^Cg8JIE4;bu6Q5zXI`gCrrSkpBafd|I>0<|o*QAHbST;1lL=W%~&I9=QMQ zp+?C3bV~GJR7Fpu)1u!>M1RBlbv7AuPO#W6x^u6YPAQ^EOFqUto0NJdM1GBpk{T)% z|GvLW^AIjZe|J@(kZQWvl&vc`;wBAPdXGejoCBVc57@CQVmW8DG-;}|6+;g~56g=P zCxvAa3;*rmyGi!;TcS7zeTm!Ky^YYeji6>*n8p;m#uL{Ry{Tc|=qRt*p>MwU@$dZE zCt&`Xt{rw9Kboe@4cW`~?%lg~iP`TOn}~`h;%$%6Yd#A{(^E3Id$&;qg{U2cLMcNb ztvNZP%-b9^Ey=Om2XXyxkyeow{7*FSRQSKBH<0P~qy*T(SV-$8*2vpMbWgE}%9#$D ziHN4^^%Tu(Y>fX_#513%Jy<-{-mv1CHt%cS#mg7dl@dbv90}bWOUPXB)axgu^@V0c z)&?_ewBAdQb)I_0*HyX5x{&F(BDRTooi`-4Yy`g)kzLKyEbbasIc;i-PaL=O<2}|@ zURFi?a5iN>S~#kJ|0%&Mtd;`iJBF&C z4AS!?%l?D(LPpfeta@uPi@z^fZ2xrB1m^7-KieT?SZ`$7N(*wxDYpGFP${wpd@Duk zp!6);{sq*#sfjkHFY1hoy;hr~H>{>o`ON{mtG0~FUmsAOjf-ic$9A9KE2-~BhL9?I z11e`4%R%d18MG=3_2B_Kn*wYBi3GD>1?L5r{TjVir>tZpcuZinpp^u+FGsBawny?R zD9HUrrp28h_v@gt6Xd>vdN+mK{O+jWJqUb|7>5NfmH)~B_EkJl`M(-azWoIhX#Xjd z!W1r%OEtedpk}6}9OT}cL2hi88hX$)hX=2m7&E3Ymr8=)nLUO0Jxs6j@%y%68cHC1 zJZcCKzNJqW6%@ZM)9%htd>g3j1jS|4yD1d+?U4%P8Nh|)IV_N={DA>IP~k=8_YEkY z4`ix2IiO~yr5qri$N*V2RJkbZzj7$|HN?4u5;(sfy(lop&(iCBIN!P59Ot%`AbKTg z3y8k0e_s_)o0-;k2I>~5>;%*v>fIEmi#ny^`Vi1Vf*lsuRQ|mKI-#PC%0D%rd_JzJ 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/teradata/sql.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/teradata/sql.doctree new file mode 100644 index 0000000000000000000000000000000000000000..3d8c561ce4837cfd2ffba3fbf6df84c82c028400 GIT binary patch literal 11614 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 literal 0 HcmV?d00001 diff --git a/mddocs/docs/_build/doctrees/connection/db_connection/teradata/write.doctree b/mddocs/docs/_build/doctrees/connection/db_connection/teradata/write.doctree new file mode 100644 index 0000000000000000000000000000000000000000..6af626f14b6ff54cc56af851e4c1584bb931d873 GIT binary patch literal 25133 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