From a54f1d24204dde96293f8d34ef2f5d0e4c9eb898 Mon Sep 17 00:00:00 2001 From: Neil Shen Date: Fri, 19 Feb 2021 13:46:20 +0800 Subject: [PATCH] cherry pick #665 to release-5.0-rc Signed-off-by: ti-srebot --- .gitattributes | 1 + .github/ISSUE_TEMPLATE/bug-report.md | 18 +- .github/ISSUE_TEMPLATE/feature-request.md | 2 +- .github/ISSUE_TEMPLATE/question.md | 24 + .github/challenge-bot.yml | 1 + .gitignore | 22 +- CHANGELOG.md | 18 - Makefile | 111 +- cmd/{ => br}/backup.go | 2 +- cmd/{ => br}/cmd.go | 3 +- cmd/{ => br}/debug.go | 2 +- main.go => cmd/br/main.go | 15 +- main_test.go => cmd/br/main_test.go | 0 cmd/{ => br}/restore.go | 2 +- cmd/tidb-lightning-ctl/main.go | 406 + cmd/tidb-lightning-ctl/main_test.go | 47 + cmd/tidb-lightning/main.go | 113 + cmd/tidb-lightning/main_test.go | 49 + docker/config/tikv.toml | 3 + go.mod1 | 31 + go.sum1 | 21 + metrics/alertmanager/lightning.rules.yml | 14 + metrics/grafana/lightning.json | 1603 +++ pkg/httputil/http.go | 22 + pkg/lightning/backend/allocator.go | 61 + pkg/lightning/backend/backend.go | 490 + pkg/lightning/backend/backend_test.go | 380 + pkg/lightning/backend/checkreq_test.go | 122 + pkg/lightning/backend/importer.go | 389 + pkg/lightning/backend/importer_test.go | 249 + pkg/lightning/backend/local.go | 2052 ++++ pkg/lightning/backend/local_test.go | 475 + pkg/lightning/backend/local_unix.go | 92 + pkg/lightning/backend/local_windows.go | 31 + pkg/lightning/backend/localhelper.go | 435 + pkg/lightning/backend/localhelper_test.go | 535 + pkg/lightning/backend/session.go | 242 + pkg/lightning/backend/session_test.go | 37 + pkg/lightning/backend/sql2kv.go | 435 + pkg/lightning/backend/sql2kv_test.go | 438 + pkg/lightning/backend/tidb.go | 582 + pkg/lightning/backend/tidb_test.go | 361 + pkg/lightning/backend/tikv.go | 194 + pkg/lightning/backend/tikv_test.go | 148 + pkg/lightning/checkpoints/checkpoints.go | 1605 +++ .../checkpoints/checkpoints_file_test.go | 327 + .../checkpoints/checkpoints_sql_test.go | 493 + pkg/lightning/checkpoints/checkpoints_test.go | 306 + .../checkpoints/file_checkpoints.pb.go | 2389 ++++ .../checkpoints/file_checkpoints.proto | 69 + pkg/lightning/checkpoints/glue_checkpoint.go | 797 ++ pkg/lightning/checkpoints/tidb.go | 30 + pkg/lightning/common/once_error.go | 46 + pkg/lightning/common/once_error_test.go | 59 + pkg/lightning/common/pause.go | 156 + pkg/lightning/common/pause_test.go | 181 + pkg/lightning/common/security.go | 162 + pkg/lightning/common/security_test.go | 99 + pkg/lightning/common/storage.go | 23 + pkg/lightning/common/storage_test.go | 34 + pkg/lightning/common/storage_unix.go | 57 + pkg/lightning/common/storage_windows.go | 44 + pkg/lightning/common/util.go | 370 + pkg/lightning/common/util_test.go | 218 + pkg/lightning/common/version.go | 99 + pkg/lightning/common/version_test.go | 95 + pkg/lightning/config/bytesize.go | 44 + pkg/lightning/config/bytesize_test.go | 129 + pkg/lightning/config/config.go | 790 ++ pkg/lightning/config/config_test.go | 654 + pkg/lightning/config/configlist.go | 153 + pkg/lightning/config/configlist_test.go | 132 + pkg/lightning/config/const.go | 32 + pkg/lightning/config/global.go | 284 + pkg/lightning/glue/glue.go | 191 + pkg/lightning/lightning.go | 701 ++ pkg/lightning/lightning_test.go | 547 + pkg/lightning/log/log.go | 217 + pkg/lightning/log/log_test.go | 51 + pkg/lightning/log/redact.go | 89 + pkg/lightning/log/testlogger.go | 36 + pkg/lightning/manual/manual.go | 65 + pkg/lightning/manual/manual_nocgo.go | 19 + pkg/lightning/metric/metric.go | 258 + pkg/lightning/metric/metric_test.go | 61 + pkg/lightning/mock/backend.go | 456 + pkg/lightning/mock/glue.go | 234 + pkg/lightning/mock/glue_checkpoint.go | 136 + pkg/lightning/mock/importer.go | 377 + pkg/lightning/mock/storage.go | 137 + pkg/lightning/mydump/bytes.go | 39 + pkg/lightning/mydump/csv/split_large_file.csv | 5 + pkg/lightning/mydump/csv_parser.go | 583 + pkg/lightning/mydump/csv_parser_test.go | 909 ++ pkg/lightning/mydump/examples/metadata | 2 + .../examples/mocker_test-schema-create.sql | 1 + .../mydump/examples/mocker_test.i-schema.sql | 6 + .../mydump/examples/mocker_test.i.sql | 1 + ...cker_test.report_case_high_risk-schema.sql | 9 + .../mocker_test.report_case_high_risk.sql | 1 + .../mocker_test.tbl_autoid-schema.sql | 8 + .../examples/mocker_test.tbl_autoid.sql | 10010 ++++++++++++++++ .../mocker_test.tbl_multi_index-schema.sql | 9 + .../examples/mocker_test.tbl_multi_index.sql | 10010 ++++++++++++++++ pkg/lightning/mydump/loader.go | 474 + pkg/lightning/mydump/loader_test.go | 555 + pkg/lightning/mydump/parquet_parser.go | 370 + pkg/lightning/mydump/parquet_parser_test.go | 217 + pkg/lightning/mydump/parser.go | 571 + pkg/lightning/mydump/parser.rl | 187 + pkg/lightning/mydump/parser_generated.go | 2515 ++++ pkg/lightning/mydump/parser_test.go | 884 ++ pkg/lightning/mydump/reader.go | 178 + pkg/lightning/mydump/reader_test.go | 197 + pkg/lightning/mydump/region.go | 397 + pkg/lightning/mydump/region_test.go | 248 + pkg/lightning/mydump/router.go | 339 + pkg/lightning/mydump/router_test.go | 256 + pkg/lightning/restore/checksum.go | 458 + pkg/lightning/restore/checksum_test.go | 406 + pkg/lightning/restore/const.go | 18 + pkg/lightning/restore/restore.go | 2537 ++++ pkg/lightning/restore/restore_test.go | 1288 ++ pkg/lightning/restore/tidb.go | 380 + pkg/lightning/restore/tidb_test.go | 476 + pkg/lightning/sigusr1_other.go | 20 + pkg/lightning/sigusr1_unix.go | 38 + pkg/lightning/verification/checksum.go | 107 + pkg/lightning/verification/checksum_test.go | 92 + pkg/lightning/web/progress.go | 187 + pkg/lightning/web/res.go | 22 + pkg/lightning/web/res_vfsdata.go | 194 + pkg/lightning/worker/worker.go | 65 + pkg/lightning/worker/worker_test.go | 56 + pkg/pdutil/pd.go | 8 +- pkg/pdutil/utils.go | 12 +- pkg/restore/ingester.go | 603 + pkg/restore/log_client.go | 9 +- pkg/restore/split_client.go | 30 +- tests/README.md | 30 +- tests/_utils/check_cluster_version | 26 + tests/_utils/check_contains | 24 + tests/_utils/check_not_contains | 24 + tests/_utils/generate_certs | 30 + tests/_utils/make_tiflash_config | 31 +- tests/_utils/read_result | 21 + tests/_utils/run_br | 8 +- tests/_utils/run_cdc | 22 + tests/_utils/run_curl | 32 + tests/_utils/run_lightning | 32 + tests/_utils/run_lightning_ctl | 30 + tests/_utils/run_pd_ctl | 22 + tests/_utils/run_services | 197 +- tests/_utils/run_sql | 16 +- tests/br_backup_empty/run.sh | 1 - tests/br_db/run.sh | 9 +- tests/br_db_online_newkv/run.sh | 4 +- tests/br_full/run.sh | 6 +- tests/br_full_ddl/run.sh | 29 +- tests/br_full_index/run.sh | 2 - tests/br_gcs/run.sh | 21 +- tests/br_history/run.sh | 2 - .../config/tidb-allow-auto-random.toml | 19 + .../config/tidb-alter-primary-key.toml | 17 + .../config/tidb.toml | 11 - .../config/tikv.toml | 14 - tests/br_incompatible_tidb_config/run.sh | 11 +- tests/br_incremental_only_ddl/run.sh | 2 - tests/br_insert_after_restore/run.sh | 2 - tests/br_key_locked/locker.go | 41 +- tests/br_key_locked/run.sh | 12 +- tests/br_log_restore/run.sh | 26 +- tests/br_move_backup/run.sh | 4 +- tests/br_other/run.sh | 46 +- tests/br_rawkv/client.go | 9 +- tests/br_rawkv/run.sh | 50 +- tests/br_s3/run.sh | 8 - tests/br_shuffle_leader/run.sh | 12 +- tests/br_shuffle_region/run.sh | 12 +- tests/br_single_table/run.sh | 4 +- tests/br_skip_checksum/run.sh | 2 - tests/br_split_region_fail/run.sh | 4 - tests/br_table_partition/run.sh | 2 - tests/br_tiflash/run.sh | 4 +- tests/br_tls/certificates/ca.pem | 21 - tests/br_tls/certificates/client-key.pem | 27 - tests/br_tls/certificates/client.pem | 21 - tests/br_tls/certificates/server-key.pem | 27 - tests/br_tls/certificates/server.pem | 22 - tests/br_tls/config/pd.toml | 9 - tests/br_tls/config/tidb.toml | 14 - tests/br_tls/config/tikv.toml | 19 - tests/br_tls/run.sh | 71 - tests/br_z_gc_safepoint/gc.go | 9 +- tests/br_z_gc_safepoint/run.sh | 14 +- tests/config/.gitignore | 2 - tests/config/importer.toml | 4 + tests/config/ipsan.cnf | 11 + tests/config/pd.toml | 5 + tests/config/restore-tikv.toml | 10 +- tests/config/tidb.toml | 7 + tests/config/tikv.toml | 15 + tests/lightning_alter_random/config.toml | 0 .../data/alter_random-schema-create.sql | 1 + .../data/alter_random.t-schema.sql | 4 + .../data/alter_random.t.sql | 5 + tests/lightning_alter_random/run.sh | 45 + .../lightning_auto_random_default/config.toml | 2 + .../data/auto_random-schema-create.sql | 1 + .../data/auto_random.t-schema.sql | 5 + .../data/auto_random.t.0.sql | 5 + .../data/auto_random.t.1.sql | 5 + tests/lightning_auto_random_default/run.sh | 61 + tests/lightning_black-white-list/config.toml | 0 .../data/firstdb-schema-create.sql | 1 + .../data/firstdb.first-schema.sql | 1 + .../data/firstdb.first.1.sql | 1 + .../data/firstdb.first.2.sql | 1 + .../data/firstdb.second-schema.sql | 1 + .../data/firstdb.second.1.sql | 1 + .../data/mysql-schema-create.sql | 1 + .../data/mysql.testtable-schema.sql | 1 + .../data/seconddb-schema-create.sql | 1 + .../data/seconddb.fourth-schema.sql | 1 + .../data/seconddb.fourth.1.sql | 1 + .../data/seconddb.third-schema.sql | 1 + .../data/seconddb.third.1.sql | 1 + .../even-table-only.toml | 11 + .../firstdb-only.toml | 2 + tests/lightning_black-white-list/run.sh | 66 + tests/lightning_character_sets/auto.toml | 5 + tests/lightning_character_sets/binary.toml | 5 + tests/lightning_character_sets/gb18030.toml | 5 + .../gb18030/charsets-schema-create.sql | 1 + .../gb18030/charsets.gb18030-schema.sql | 1 + .../gb18030/charsets.gb18030.sql | 1 + .../mixed/charsets-schema-create.sql | 1 + .../mixed/charsets.mixed-schema.sql | 1 + .../mixed/charsets.mixed.sql | 1 + tests/lightning_character_sets/run.sh | 76 + tests/lightning_character_sets/utf8mb4.toml | 5 + .../utf8mb4/charsets-schema-create.sql | 1 + .../utf8mb4/charsets.utf8mb4-schema.sql | 1 + .../utf8mb4/charsets.utf8mb4.sql | 1 + .../lightning_check_requirements/config.toml | 0 .../data/checkreq-schema-create.sql | 1 + tests/lightning_check_requirements/run.sh | 43 + tests/lightning_checkpoint/config.toml | 11 + tests/lightning_checkpoint/run.sh | 118 + tests/lightning_checkpoint_chunks/config.toml | 8 + tests/lightning_checkpoint_chunks/file.toml | 9 + tests/lightning_checkpoint_chunks/run.sh | 128 + .../lightning_checkpoint_columns/config.toml | 16 + tests/lightning_checkpoint_columns/run.sh | 49 + .../data/cpdt-schema-create.sql | 1 + .../data/cpdt.t-schema.sql | 1 + .../data/cpdt.t.sql | 1 + .../file.toml | 3 + .../mysql.toml | 3 + .../lightning_checkpoint_dirty_tableid/run.sh | 59 + .../lightning_checkpoint_engines/config.toml | 9 + .../data/cpeng-schema-create.sql | 1 + .../data/cpeng.a-schema.sql | 1 + .../data/cpeng.a.1.sql | 1 + .../data/cpeng.a.2.sql | 1 + .../data/cpeng.a.3.sql | 1 + .../data/cpeng.b-schema.sql | 1 + .../data/cpeng.b.1.sql | 4 + .../data/cpeng.b.2.sql | 1 + tests/lightning_checkpoint_engines/mysql.toml | 9 + tests/lightning_checkpoint_engines/run.sh | 101 + .../bad-data/cped-schema-create.sql | 1 + .../bad-data/cped.t-schema.sql | 1 + .../bad-data/cped.t.sql | 1 + .../file.toml | 4 + .../good-data/cped-schema-create.sql | 1 + .../good-data/cped.t-schema.sql | 1 + .../good-data/cped.t.sql | 1 + .../mysql.toml | 3 + .../lightning_checkpoint_error_destroy/run.sh | 52 + .../lightning_checkpoint_parquet/config.toml | 11 + tests/lightning_checkpoint_parquet/parquet.go | 65 + tests/lightning_checkpoint_parquet/run.sh | 61 + .../config.toml | 11 + .../data/cpts-schema-create.sql | 1 + .../data/cpts.cpts-schema.sql | 1 + .../data/cpts.cpts.1.sql | 4 + .../data/cpts.cpts.2.sql | 3 + .../lightning_checkpoint_timestamp/mysql.toml | 11 + tests/lightning_checkpoint_timestamp/run.sh | 46 + tests/lightning_cmdline_override/config.toml | 18 + .../data/cmdline_override-schema-create.sql | 1 + .../data/cmdline_override.t-schema.sql | 1 + .../data/cmdline_override.t.sql | 1 + tests/lightning_cmdline_override/run.sh | 17 + .../lightning_column_permutation/config.toml | 3 + .../data/perm-schema-create.sql | 1 + .../data/perm.test_perm-schema.sql | 22 + .../data/perm.test_perm.0.csv | 6 + tests/lightning_column_permutation/run.sh | 18 + tests/lightning_common_handle/config.toml | 2 + tests/lightning_common_handle/run.sh | 59 + .../lightning_concurrent-restore/config.toml | 3 + tests/lightning_concurrent-restore/run.sh | 48 + tests/lightning_csv/config.toml | 12 + .../lightning_csv/data/csv-schema-create.sql | 1 + .../data/csv.empty_strings-schema.sql | 5 + .../lightning_csv/data/csv.empty_strings.csv | 4 + .../lightning_csv/data/csv.escapes-schema.sql | 16 + tests/lightning_csv/data/csv.escapes.CSV | 6 + .../lightning_csv/data/csv.threads-schema.sql | 27 + tests/lightning_csv/data/csv.threads.csv | 43 + tests/lightning_csv/run.sh | 43 + tests/lightning_default-columns/config.toml | 0 .../data/defcol-schema-create.sql | 1 + .../data/defcol.t-schema.sql | 6 + .../data/defcol.t.1.sql | 1 + .../data/defcol.t.2.sql | 1 + .../data/defcol.u-schema.sql | 4 + .../data/defcol.u.1.sql | 1 + tests/lightning_default-columns/run.sh | 35 + tests/lightning_disk_quota/config.toml | 7 + .../data/disk_quota-schema-create.sql | 1 + .../data/disk_quota.t-schema.sql | 12 + .../data/disk_quota.t.0.sql | 51 + .../data/disk_quota.t.1.sql | 51 + .../data/disk_quota.t.2.sql | 51 + .../data/disk_quota.t.3.sql | 51 + tests/lightning_disk_quota/run.sh | 74 + tests/lightning_error_summary/config.toml | 4 + .../data/error_summary-schema-create.sql | 1 + .../data/error_summary.a-schema.sql | 4 + .../data/error_summary.a.sql | 1 + .../data/error_summary.b-schema.sql | 4 + .../data/error_summary.b.sql | 1 + .../data/error_summary.c-schema.sql | 4 + .../data/error_summary.c.sql | 1 + tests/lightning_error_summary/run.sh | 64 + tests/lightning_examples/1.toml | 6 + tests/lightning_examples/131072.toml | 6 + tests/lightning_examples/512.toml | 6 + tests/lightning_examples/run.sh | 98 + tests/lightning_exotic_filenames/config.toml | 0 .../data/xfn-schema-create.sql | 1 + .../data/xfn.etn-schema.sql | 1 + .../data/xfn.etn.sql | 7 + .../data/zwk-schema-create.sql | 1 + .../data/zwk.zwb-schema.sql | 1 + .../data/zwk.zwb.sql | 1 + tests/lightning_exotic_filenames/run.sh | 45 + tests/lightning_file_routing/config.toml | 43 + tests/lightning_file_routing/run.sh | 68 + tests/lightning_generated_columns/config.toml | 0 .../data/gencol-schema-create.sql | 1 + .../data/gencol.nested-schema.sql | 7 + .../data/gencol.nested.0.sql | 1 + .../data/gencol.various_types-schema.sql | 19 + .../data/gencol.various_types.0.sql | 1 + tests/lightning_generated_columns/run.sh | 71 + tests/lightning_issue_282/config.toml | 0 .../data/issue282-schema-create.sql | 1 + .../data/issue282.t_access3-schema.sql | 4 + .../data/issue282.t_access3.sql | 5 + tests/lightning_issue_282/run.sh | 29 + tests/lightning_issue_410/config.toml | 0 .../data/issue410-schema-create.sql | 1 + .../data/issue410.row_flow_d-schema.sql | 28 + .../data/issue410.row_flow_d.0.csv | 2 + tests/lightning_issue_410/run.sh | 30 + tests/lightning_issue_519/config.toml | 8 + .../data/issue519-schema-create.sql | 1 + .../data/issue519.t-schema.sql | 1 + tests/lightning_issue_519/data/issue519.t.csv | 10 + tests/lightning_issue_519/run.sh | 25 + tests/lightning_local_backend/config.toml | 13 + .../data/cpeng-schema-create.sql | 1 + .../data/cpeng.a-schema.sql | 1 + .../data/cpeng.a.1.sql | 1 + .../data/cpeng.a.2.sql | 1 + .../data/cpeng.a.3.sql | 1 + .../data/cpeng.b-schema.sql | 1 + .../data/cpeng.b.1.sql | 4 + .../data/cpeng.b.2.sql | 1 + tests/lightning_local_backend/file.toml | 17 + tests/lightning_local_backend/mysql.toml | 17 + tests/lightning_local_backend/run.sh | 123 + tests/lightning_new_collation/config.toml | 0 tests/lightning_new_collation/run.sh | 59 + .../tidb-new-collation.toml | 11 + tests/lightning_no_schema/config.toml | 0 tests/lightning_no_schema/data/noschema.t.sql | 15 + tests/lightning_no_schema/run.sh | 30 + .../schema-data/noschema-schema-create.sql | 1 + .../schema-data/noschema.t-schema.sql | 1 + tests/lightning_parquet/config.toml | 10 + .../data/export_info_ci.json | 1 + .../export_tables_info_ci_from_1_to_9.json | 1 + .../data/test/test.customer/_SUCCESS | 0 ...51c-49ba-bdf3-5864befff481-c000.gz.parquet | Bin 0 -> 15251 bytes .../data/test/test.district/_SUCCESS | 0 ...765-432a-8f18-cd17b4607f2a-c000.gz.parquet | Bin 0 -> 3626 bytes .../data/test/test.history/_SUCCESS | 0 ...169-4335-a93f-8805e02def97-c000.gz.parquet | Bin 0 -> 4133 bytes .../data/test/test.item/_SUCCESS | 0 ...c54-477f-9907-6e3eae50358b-c000.gz.parquet | Bin 0 -> 7078 bytes .../data/test/test.new_order/_SUCCESS | 0 ...629-4445-bd96-d34b6674b09d-c000.gz.parquet | Bin 0 -> 1140 bytes .../data/test/test.order_line/_SUCCESS | 0 ...f59-4ff6-b271-2e4b27ffbcf5-c000.gz.parquet | Bin 0 -> 5468 bytes .../data/test/test.orders/_SUCCESS | 0 ...2c0-4961-9bf7-32a0a04ffee5-c000.gz.parquet | Bin 0 -> 2941 bytes .../data/test/test.stock/_SUCCESS | 0 ...034-4d65-b375-ee55aa479215-c000.gz.parquet | Bin 0 -> 15085 bytes .../data/test/test.warehouse/_SUCCESS | 0 ...d2f-4c5c-8a2f-d162bde6c360-c000.gz.parquet | Bin 0 -> 2719 bytes tests/lightning_parquet/db.sql | 219 + tests/lightning_parquet/run.sh | 52 + tests/lightning_partitioned-table/config.toml | 2 + .../data/partitioned-schema-create.sql | 1 + .../data/partitioned.a-schema.sql | 1 + .../data/partitioned.a.sql | 10 + tests/lightning_partitioned-table/run.sh | 35 + tests/lightning_restore/config.toml | 2 + tests/lightning_restore/run.sh | 42 + tests/lightning_routes/config.toml | 7 + .../data/routes_a0-schema-create.sql | 1 + .../data/routes_a0.t0-schema.sql | 1 + .../lightning_routes/data/routes_a0.t0.1.sql | 1 + .../lightning_routes/data/routes_a0.t0.2.sql | 1 + .../data/routes_a0.t1-schema.sql | 1 + .../lightning_routes/data/routes_a0.t1.1.sql | 1 + .../data/routes_a1-schema-create.sql | 1 + .../data/routes_a1.s1-schema.sql | 1 + tests/lightning_routes/data/routes_a1.s1.sql | 1 + .../data/routes_a1.t2-schema.sql | 1 + tests/lightning_routes/data/routes_a1.t2.sql | 1 + tests/lightning_routes/run.sh | 25 + tests/lightning_row-format-v2/config.toml | 0 .../data/rowformatv2-schema-create.sql | 1 + .../data/rowformatv2.t1-schema.sql | 258 + .../data/rowformatv2.t1.1.sql | 51 + tests/lightning_row-format-v2/run.sh | 28 + tests/lightning_s3/config.toml | 0 tests/lightning_s3/run.sh | 74 + tests/lightning_source_linkfile/config.toml | 1 + tests/lightning_source_linkfile/run.sh | 60 + .../data/sqlmodedb-schema-create.sql | 1 + .../data/sqlmodedb.t-schema.sql | 7 + .../lightning_sqlmode/data/sqlmodedb.t.1.sql | 6 + tests/lightning_sqlmode/off.toml | 2 + tests/lightning_sqlmode/on.toml | 2 + tests/lightning_sqlmode/run.sh | 57 + .../data/dup-schema-create.sql | 1 + .../data/dup.dup-schema.sql | 1 + .../data/dup.dup.sql | 3 + .../lightning_tidb_duplicate_data/error.toml | 3 + .../lightning_tidb_duplicate_data/ignore.toml | 3 + .../replace.toml | 2 + tests/lightning_tidb_duplicate_data/run.sh | 64 + tests/lightning_tidb_rowid/config.toml | 0 .../data/rowid-schema-create.sql | 1 + .../data/rowid.explicit_tidb_rowid-schema.sql | 1 + .../data/rowid.explicit_tidb_rowid.sql | 11 + .../data/rowid.non_pk-schema.sql | 1 + .../data/rowid.non_pk.sql | 11 + .../data/rowid.non_pk_auto_inc-schema.sql | 9 + .../data/rowid.non_pk_auto_inc.sql | 26 + .../data/rowid.pre_rebase-schema.sql | 1 + .../data/rowid.pre_rebase.sql | 1 + .../data/rowid.specific_auto_inc-schema.sql | 1 + .../data/rowid.specific_auto_inc.sql | 7 + tests/lightning_tidb_rowid/run.sh | 80 + tests/lightning_tiflash/config.toml | 2 + tests/lightning_tiflash/run.sh | 71 + tests/lightning_too_many_columns/config.toml | 0 .../data/too_many_columns-schema-create.sql | 1 + .../data/too_many_columns.t-schema.sql | 259 + .../data/too_many_columns.t.0.csv | 2 + tests/lightning_too_many_columns/run.sh | 30 + tests/lightning_tool_135/config.toml | 0 .../data/tool_135-schema-create.sql | 1 + .../data/tool_135.bar1-schema.sql | 1 + .../lightning_tool_135/data/tool_135.bar1.sql | 10 + .../data/tool_135.bar2-schema.sql | 1 + .../lightning_tool_135/data/tool_135.bar2.sql | 10 + .../data/tool_135.bar3-schema.sql | 1 + .../lightning_tool_135/data/tool_135.bar3.sql | 10 + .../data/tool_135.bar4-schema.sql | 1 + .../lightning_tool_135/data/tool_135.bar4.sql | 10 + .../data/tool_135.bar5-schema.sql | 1 + .../lightning_tool_135/data/tool_135.bar5.sql | 10 + tests/lightning_tool_135/run.sh | 94 + tests/lightning_tool_1420/config.toml | 0 .../data/EE1420-schema-create.sql | 1 + .../data/EE1420.pt_role-schema.sql | 3 + .../data/EE1420.pt_role.sql | 1 + tests/lightning_tool_1420/run.sh | 24 + tests/lightning_tool_1472/config.toml | 0 .../data/EE1472-schema-create.sql | 1 + .../data/EE1472.notpk-schema.sql | 5 + .../data/EE1472.notpk.1.sql | 8 + .../data/EE1472.notpk.2.sql | 8 + .../data/EE1472.pk-schema.sql | 3 + .../lightning_tool_1472/data/EE1472.pk.1.sql | 8 + .../lightning_tool_1472/data/EE1472.pk.2.sql | 8 + tests/lightning_tool_1472/run.sh | 31 + tests/lightning_tool_241/config.toml | 5 + .../data/qyjc-schema-create.sql | 1 + .../data/qyjc.q_alarm_group-schema.sql | 16 + .../data/qyjc.q_alarm_group.sql | 0 .../data/qyjc.q_alarm_message_log-schema.sql | 16 + .../data/qyjc.q_alarm_message_log.sql | 0 .../data/qyjc.q_alarm_receiver-schema.sql | 12 + .../data/qyjc.q_config-schema.sql | 9 + .../data/qyjc.q_fish_event-schema.sql | 15 + .../data/qyjc.q_fish_event.sql | 85 + .../qyjc.q_report_circular_data-schema.sql | 16 + .../data/qyjc.q_report_desc-schema.sql | 16 + .../data/qyjc.q_report_summary-schema.sql | 14 + .../data/qyjc.q_system_update-schema.sql | 7 + .../data/qyjc.q_system_update.sql | 0 .../data/qyjc.q_user_log-schema.sql | 12 + .../data/qyjc.q_user_log.sql | 0 tests/lightning_tool_241/run.sh | 45 + .../lightning_unused_config_keys/config.toml | 6 + .../data/unused_config_keys-schema-create.sql | 1 + tests/lightning_unused_config_keys/run.sh | 29 + tests/lightning_various_types/config.toml | 0 .../data/vt-schema-create.sql | 1 + .../data/vt.binary-schema.sql | 4 + .../data/vt.binary.sql | 52 + .../data/vt.bit-schema.sql | 4 + tests/lightning_various_types/data/vt.bit.sql | 6 + .../data/vt.char-schema.sql | 4 + .../lightning_various_types/data/vt.char.sql | 51 + .../data/vt.datetime-schema.sql | 7 + .../data/vt.datetime.sql | 72 + .../data/vt.decimal-schema.sql | 4 + .../data/vt.decimal.sql | 51 + .../data/vt.double-schema.sql | 4 + .../data/vt.double.sql | 42 + .../data/vt.empty_strings-schema.sql | 4 + .../data/vt.empty_strings.sql | 1 + .../data/vt.enum-set-schema.sql | 26 + .../data/vt.enum-set.sql | 36 + .../data/vt.json-schema.sql | 4 + .../lightning_various_types/data/vt.json.sql | 106 + .../data/vt.precise_types-schema.sql | 6 + .../data/vt.precise_types.sql | 6 + tests/lightning_various_types/run.sh | 113 + tests/lightning_view/config.toml | 1 + .../lightning_view/data/db0-schema-create.sql | 1 + .../data/db0.v2-schema-view.sql | 13 + tests/lightning_view/data/db0.v2-schema.sql | 1 + .../lightning_view/data/db1-schema-create.sql | 1 + tests/lightning_view/data/db1.tbl-schema.sql | 1 + tests/lightning_view/data/db1.tbl.0.sql | 5 + .../data/db1.v1-schema-view.sql | 13 + tests/lightning_view/data/db1.v1-schema.sql | 1 + tests/lightning_view/run.sh | 36 + tests/run.sh | 38 +- tidb-lightning.toml | 273 + tools/Makefile | 20 +- tools/go.mod | 3 + tools/go.sum | 8 +- tools/go_mod_guard.go | 13 +- web/README.md | 93 + web/docs/InfoPage.png | Bin 0 -> 48339 bytes web/docs/ProgressPage.png | Bin 0 -> 41010 bytes web/docs/TableProgressPage.png | Bin 0 -> 47424 bytes web/docs/api.yaml | 521 + web/go.mod | 5 + web/go.sum | 1 + web/package-lock.json | 5307 ++++++++ web/package.json | 31 + web/public/index.html | 14 + web/src/ChunksProgressPanel.tsx | 114 + web/src/DottedProgress.tsx | 72 + web/src/EnginesProgressPanel.tsx | 72 + web/src/ErrorButton.tsx | 85 + web/src/InfoButton.tsx | 39 + web/src/InfoPage.tsx | 112 + web/src/MoveTaskButton.tsx | 123 + web/src/PauseButton.tsx | 33 + web/src/ProgressPage.tsx | 116 + web/src/RefreshButton.tsx | 124 + web/src/TableProgressCard.tsx | 113 + web/src/TableProgressPage.tsx | 73 + web/src/TaskButton.tsx | 139 + web/src/TitleBar.tsx | 95 + web/src/TitleLink.tsx | 33 + web/src/api.ts | 268 + web/src/index.tsx | 179 + web/src/json-bigint.d.ts | 17 + web/tsconfig.json | 19 + web/webpack.config.js | 38 + 596 files changed, 74094 insertions(+), 651 deletions(-) create mode 100644 .gitattributes create mode 100644 .github/ISSUE_TEMPLATE/question.md create mode 100644 .github/challenge-bot.yml delete mode 100644 CHANGELOG.md rename cmd/{ => br}/backup.go (99%) rename cmd/{ => br}/cmd.go (99%) rename cmd/{ => br}/debug.go (99%) rename main.go => cmd/br/main.go (86%) rename main_test.go => cmd/br/main_test.go (100%) rename cmd/{ => br}/restore.go (99%) create mode 100644 cmd/tidb-lightning-ctl/main.go create mode 100644 cmd/tidb-lightning-ctl/main_test.go create mode 100644 cmd/tidb-lightning/main.go create mode 100644 cmd/tidb-lightning/main_test.go create mode 100644 metrics/alertmanager/lightning.rules.yml create mode 100644 metrics/grafana/lightning.json create mode 100644 pkg/httputil/http.go create mode 100644 pkg/lightning/backend/allocator.go create mode 100644 pkg/lightning/backend/backend.go create mode 100644 pkg/lightning/backend/backend_test.go create mode 100644 pkg/lightning/backend/checkreq_test.go create mode 100644 pkg/lightning/backend/importer.go create mode 100644 pkg/lightning/backend/importer_test.go create mode 100644 pkg/lightning/backend/local.go create mode 100644 pkg/lightning/backend/local_test.go create mode 100644 pkg/lightning/backend/local_unix.go create mode 100644 pkg/lightning/backend/local_windows.go create mode 100644 pkg/lightning/backend/localhelper.go create mode 100644 pkg/lightning/backend/localhelper_test.go create mode 100644 pkg/lightning/backend/session.go create mode 100644 pkg/lightning/backend/session_test.go create mode 100644 pkg/lightning/backend/sql2kv.go create mode 100644 pkg/lightning/backend/sql2kv_test.go create mode 100644 pkg/lightning/backend/tidb.go create mode 100644 pkg/lightning/backend/tidb_test.go create mode 100644 pkg/lightning/backend/tikv.go create mode 100644 pkg/lightning/backend/tikv_test.go create mode 100644 pkg/lightning/checkpoints/checkpoints.go create mode 100644 pkg/lightning/checkpoints/checkpoints_file_test.go create mode 100644 pkg/lightning/checkpoints/checkpoints_sql_test.go create mode 100644 pkg/lightning/checkpoints/checkpoints_test.go create mode 100644 pkg/lightning/checkpoints/file_checkpoints.pb.go create mode 100644 pkg/lightning/checkpoints/file_checkpoints.proto create mode 100644 pkg/lightning/checkpoints/glue_checkpoint.go create mode 100644 pkg/lightning/checkpoints/tidb.go create mode 100644 pkg/lightning/common/once_error.go create mode 100644 pkg/lightning/common/once_error_test.go create mode 100644 pkg/lightning/common/pause.go create mode 100644 pkg/lightning/common/pause_test.go create mode 100644 pkg/lightning/common/security.go create mode 100644 pkg/lightning/common/security_test.go create mode 100644 pkg/lightning/common/storage.go create mode 100644 pkg/lightning/common/storage_test.go create mode 100644 pkg/lightning/common/storage_unix.go create mode 100644 pkg/lightning/common/storage_windows.go create mode 100644 pkg/lightning/common/util.go create mode 100644 pkg/lightning/common/util_test.go create mode 100644 pkg/lightning/common/version.go create mode 100644 pkg/lightning/common/version_test.go create mode 100644 pkg/lightning/config/bytesize.go create mode 100644 pkg/lightning/config/bytesize_test.go create mode 100644 pkg/lightning/config/config.go create mode 100644 pkg/lightning/config/config_test.go create mode 100644 pkg/lightning/config/configlist.go create mode 100644 pkg/lightning/config/configlist_test.go create mode 100644 pkg/lightning/config/const.go create mode 100644 pkg/lightning/config/global.go create mode 100644 pkg/lightning/glue/glue.go create mode 100755 pkg/lightning/lightning.go create mode 100644 pkg/lightning/lightning_test.go create mode 100644 pkg/lightning/log/log.go create mode 100644 pkg/lightning/log/log_test.go create mode 100644 pkg/lightning/log/redact.go create mode 100644 pkg/lightning/log/testlogger.go create mode 100644 pkg/lightning/manual/manual.go create mode 100644 pkg/lightning/manual/manual_nocgo.go create mode 100644 pkg/lightning/metric/metric.go create mode 100644 pkg/lightning/metric/metric_test.go create mode 100644 pkg/lightning/mock/backend.go create mode 100644 pkg/lightning/mock/glue.go create mode 100644 pkg/lightning/mock/glue_checkpoint.go create mode 100644 pkg/lightning/mock/importer.go create mode 100644 pkg/lightning/mock/storage.go create mode 100644 pkg/lightning/mydump/bytes.go create mode 100644 pkg/lightning/mydump/csv/split_large_file.csv create mode 100644 pkg/lightning/mydump/csv_parser.go create mode 100644 pkg/lightning/mydump/csv_parser_test.go create mode 100644 pkg/lightning/mydump/examples/metadata create mode 100644 pkg/lightning/mydump/examples/mocker_test-schema-create.sql create mode 100644 pkg/lightning/mydump/examples/mocker_test.i-schema.sql create mode 100644 pkg/lightning/mydump/examples/mocker_test.i.sql create mode 100644 pkg/lightning/mydump/examples/mocker_test.report_case_high_risk-schema.sql create mode 100644 pkg/lightning/mydump/examples/mocker_test.report_case_high_risk.sql create mode 100644 pkg/lightning/mydump/examples/mocker_test.tbl_autoid-schema.sql create mode 100644 pkg/lightning/mydump/examples/mocker_test.tbl_autoid.sql create mode 100644 pkg/lightning/mydump/examples/mocker_test.tbl_multi_index-schema.sql create mode 100644 pkg/lightning/mydump/examples/mocker_test.tbl_multi_index.sql create mode 100644 pkg/lightning/mydump/loader.go create mode 100644 pkg/lightning/mydump/loader_test.go create mode 100644 pkg/lightning/mydump/parquet_parser.go create mode 100644 pkg/lightning/mydump/parquet_parser_test.go create mode 100644 pkg/lightning/mydump/parser.go create mode 100644 pkg/lightning/mydump/parser.rl create mode 100644 pkg/lightning/mydump/parser_generated.go create mode 100644 pkg/lightning/mydump/parser_test.go create mode 100644 pkg/lightning/mydump/reader.go create mode 100644 pkg/lightning/mydump/reader_test.go create mode 100644 pkg/lightning/mydump/region.go create mode 100644 pkg/lightning/mydump/region_test.go create mode 100644 pkg/lightning/mydump/router.go create mode 100644 pkg/lightning/mydump/router_test.go create mode 100644 pkg/lightning/restore/checksum.go create mode 100644 pkg/lightning/restore/checksum_test.go create mode 100644 pkg/lightning/restore/const.go create mode 100644 pkg/lightning/restore/restore.go create mode 100644 pkg/lightning/restore/restore_test.go create mode 100644 pkg/lightning/restore/tidb.go create mode 100644 pkg/lightning/restore/tidb_test.go create mode 100644 pkg/lightning/sigusr1_other.go create mode 100644 pkg/lightning/sigusr1_unix.go create mode 100644 pkg/lightning/verification/checksum.go create mode 100644 pkg/lightning/verification/checksum_test.go create mode 100644 pkg/lightning/web/progress.go create mode 100644 pkg/lightning/web/res.go create mode 100644 pkg/lightning/web/res_vfsdata.go create mode 100644 pkg/lightning/worker/worker.go create mode 100644 pkg/lightning/worker/worker_test.go create mode 100644 pkg/restore/ingester.go create mode 100755 tests/_utils/check_cluster_version create mode 100755 tests/_utils/check_contains create mode 100755 tests/_utils/check_not_contains create mode 100755 tests/_utils/generate_certs create mode 100755 tests/_utils/read_result create mode 100755 tests/_utils/run_cdc create mode 100755 tests/_utils/run_curl create mode 100755 tests/_utils/run_lightning create mode 100755 tests/_utils/run_lightning_ctl create mode 100755 tests/_utils/run_pd_ctl create mode 100644 tests/br_incompatible_tidb_config/config/tidb-allow-auto-random.toml create mode 100644 tests/br_incompatible_tidb_config/config/tidb-alter-primary-key.toml delete mode 100644 tests/br_incompatible_tidb_config/config/tidb.toml delete mode 100644 tests/br_incompatible_tidb_config/config/tikv.toml delete mode 100644 tests/br_tls/certificates/ca.pem delete mode 100644 tests/br_tls/certificates/client-key.pem delete mode 100644 tests/br_tls/certificates/client.pem delete mode 100644 tests/br_tls/certificates/server-key.pem delete mode 100644 tests/br_tls/certificates/server.pem delete mode 100644 tests/br_tls/config/pd.toml delete mode 100644 tests/br_tls/config/tidb.toml delete mode 100644 tests/br_tls/config/tikv.toml delete mode 100755 tests/br_tls/run.sh delete mode 100644 tests/config/.gitignore create mode 100644 tests/config/importer.toml create mode 100644 tests/config/ipsan.cnf create mode 100644 tests/lightning_alter_random/config.toml create mode 100644 tests/lightning_alter_random/data/alter_random-schema-create.sql create mode 100644 tests/lightning_alter_random/data/alter_random.t-schema.sql create mode 100644 tests/lightning_alter_random/data/alter_random.t.sql create mode 100644 tests/lightning_alter_random/run.sh create mode 100644 tests/lightning_auto_random_default/config.toml create mode 100644 tests/lightning_auto_random_default/data/auto_random-schema-create.sql create mode 100644 tests/lightning_auto_random_default/data/auto_random.t-schema.sql create mode 100644 tests/lightning_auto_random_default/data/auto_random.t.0.sql create mode 100644 tests/lightning_auto_random_default/data/auto_random.t.1.sql create mode 100644 tests/lightning_auto_random_default/run.sh create mode 100644 tests/lightning_black-white-list/config.toml create mode 100644 tests/lightning_black-white-list/data/firstdb-schema-create.sql create mode 100644 tests/lightning_black-white-list/data/firstdb.first-schema.sql create mode 100644 tests/lightning_black-white-list/data/firstdb.first.1.sql create mode 100644 tests/lightning_black-white-list/data/firstdb.first.2.sql create mode 100644 tests/lightning_black-white-list/data/firstdb.second-schema.sql create mode 100644 tests/lightning_black-white-list/data/firstdb.second.1.sql create mode 100644 tests/lightning_black-white-list/data/mysql-schema-create.sql create mode 100644 tests/lightning_black-white-list/data/mysql.testtable-schema.sql create mode 100644 tests/lightning_black-white-list/data/seconddb-schema-create.sql create mode 100644 tests/lightning_black-white-list/data/seconddb.fourth-schema.sql create mode 100644 tests/lightning_black-white-list/data/seconddb.fourth.1.sql create mode 100644 tests/lightning_black-white-list/data/seconddb.third-schema.sql create mode 100644 tests/lightning_black-white-list/data/seconddb.third.1.sql create mode 100644 tests/lightning_black-white-list/even-table-only.toml create mode 100644 tests/lightning_black-white-list/firstdb-only.toml create mode 100755 tests/lightning_black-white-list/run.sh create mode 100644 tests/lightning_character_sets/auto.toml create mode 100644 tests/lightning_character_sets/binary.toml create mode 100644 tests/lightning_character_sets/gb18030.toml create mode 100644 tests/lightning_character_sets/gb18030/charsets-schema-create.sql create mode 100644 tests/lightning_character_sets/gb18030/charsets.gb18030-schema.sql create mode 100644 tests/lightning_character_sets/gb18030/charsets.gb18030.sql create mode 100644 tests/lightning_character_sets/mixed/charsets-schema-create.sql create mode 100644 tests/lightning_character_sets/mixed/charsets.mixed-schema.sql create mode 100644 tests/lightning_character_sets/mixed/charsets.mixed.sql create mode 100755 tests/lightning_character_sets/run.sh create mode 100644 tests/lightning_character_sets/utf8mb4.toml create mode 100644 tests/lightning_character_sets/utf8mb4/charsets-schema-create.sql create mode 100644 tests/lightning_character_sets/utf8mb4/charsets.utf8mb4-schema.sql create mode 100644 tests/lightning_character_sets/utf8mb4/charsets.utf8mb4.sql create mode 100644 tests/lightning_check_requirements/config.toml create mode 100644 tests/lightning_check_requirements/data/checkreq-schema-create.sql create mode 100755 tests/lightning_check_requirements/run.sh create mode 100644 tests/lightning_checkpoint/config.toml create mode 100755 tests/lightning_checkpoint/run.sh create mode 100644 tests/lightning_checkpoint_chunks/config.toml create mode 100644 tests/lightning_checkpoint_chunks/file.toml create mode 100755 tests/lightning_checkpoint_chunks/run.sh create mode 100644 tests/lightning_checkpoint_columns/config.toml create mode 100755 tests/lightning_checkpoint_columns/run.sh create mode 100644 tests/lightning_checkpoint_dirty_tableid/data/cpdt-schema-create.sql create mode 100644 tests/lightning_checkpoint_dirty_tableid/data/cpdt.t-schema.sql create mode 100644 tests/lightning_checkpoint_dirty_tableid/data/cpdt.t.sql create mode 100644 tests/lightning_checkpoint_dirty_tableid/file.toml create mode 100644 tests/lightning_checkpoint_dirty_tableid/mysql.toml create mode 100755 tests/lightning_checkpoint_dirty_tableid/run.sh create mode 100644 tests/lightning_checkpoint_engines/config.toml create mode 100644 tests/lightning_checkpoint_engines/data/cpeng-schema-create.sql create mode 100644 tests/lightning_checkpoint_engines/data/cpeng.a-schema.sql create mode 100644 tests/lightning_checkpoint_engines/data/cpeng.a.1.sql create mode 100644 tests/lightning_checkpoint_engines/data/cpeng.a.2.sql create mode 100644 tests/lightning_checkpoint_engines/data/cpeng.a.3.sql create mode 100644 tests/lightning_checkpoint_engines/data/cpeng.b-schema.sql create mode 100644 tests/lightning_checkpoint_engines/data/cpeng.b.1.sql create mode 100644 tests/lightning_checkpoint_engines/data/cpeng.b.2.sql create mode 100644 tests/lightning_checkpoint_engines/mysql.toml create mode 100755 tests/lightning_checkpoint_engines/run.sh create mode 100644 tests/lightning_checkpoint_error_destroy/bad-data/cped-schema-create.sql create mode 100644 tests/lightning_checkpoint_error_destroy/bad-data/cped.t-schema.sql create mode 100644 tests/lightning_checkpoint_error_destroy/bad-data/cped.t.sql create mode 100644 tests/lightning_checkpoint_error_destroy/file.toml create mode 100644 tests/lightning_checkpoint_error_destroy/good-data/cped-schema-create.sql create mode 100644 tests/lightning_checkpoint_error_destroy/good-data/cped.t-schema.sql create mode 100644 tests/lightning_checkpoint_error_destroy/good-data/cped.t.sql create mode 100644 tests/lightning_checkpoint_error_destroy/mysql.toml create mode 100755 tests/lightning_checkpoint_error_destroy/run.sh create mode 100644 tests/lightning_checkpoint_parquet/config.toml create mode 100644 tests/lightning_checkpoint_parquet/parquet.go create mode 100755 tests/lightning_checkpoint_parquet/run.sh create mode 100644 tests/lightning_checkpoint_timestamp/config.toml create mode 100644 tests/lightning_checkpoint_timestamp/data/cpts-schema-create.sql create mode 100644 tests/lightning_checkpoint_timestamp/data/cpts.cpts-schema.sql create mode 100644 tests/lightning_checkpoint_timestamp/data/cpts.cpts.1.sql create mode 100644 tests/lightning_checkpoint_timestamp/data/cpts.cpts.2.sql create mode 100644 tests/lightning_checkpoint_timestamp/mysql.toml create mode 100755 tests/lightning_checkpoint_timestamp/run.sh create mode 100644 tests/lightning_cmdline_override/config.toml create mode 100644 tests/lightning_cmdline_override/data/cmdline_override-schema-create.sql create mode 100644 tests/lightning_cmdline_override/data/cmdline_override.t-schema.sql create mode 100644 tests/lightning_cmdline_override/data/cmdline_override.t.sql create mode 100755 tests/lightning_cmdline_override/run.sh create mode 100644 tests/lightning_column_permutation/config.toml create mode 100644 tests/lightning_column_permutation/data/perm-schema-create.sql create mode 100644 tests/lightning_column_permutation/data/perm.test_perm-schema.sql create mode 100644 tests/lightning_column_permutation/data/perm.test_perm.0.csv create mode 100644 tests/lightning_column_permutation/run.sh create mode 100644 tests/lightning_common_handle/config.toml create mode 100644 tests/lightning_common_handle/run.sh create mode 100644 tests/lightning_concurrent-restore/config.toml create mode 100644 tests/lightning_concurrent-restore/run.sh create mode 100644 tests/lightning_csv/config.toml create mode 100644 tests/lightning_csv/data/csv-schema-create.sql create mode 100644 tests/lightning_csv/data/csv.empty_strings-schema.sql create mode 100644 tests/lightning_csv/data/csv.empty_strings.csv create mode 100644 tests/lightning_csv/data/csv.escapes-schema.sql create mode 100644 tests/lightning_csv/data/csv.escapes.CSV create mode 100644 tests/lightning_csv/data/csv.threads-schema.sql create mode 100644 tests/lightning_csv/data/csv.threads.csv create mode 100755 tests/lightning_csv/run.sh create mode 100644 tests/lightning_default-columns/config.toml create mode 100644 tests/lightning_default-columns/data/defcol-schema-create.sql create mode 100644 tests/lightning_default-columns/data/defcol.t-schema.sql create mode 100644 tests/lightning_default-columns/data/defcol.t.1.sql create mode 100644 tests/lightning_default-columns/data/defcol.t.2.sql create mode 100644 tests/lightning_default-columns/data/defcol.u-schema.sql create mode 100644 tests/lightning_default-columns/data/defcol.u.1.sql create mode 100755 tests/lightning_default-columns/run.sh create mode 100644 tests/lightning_disk_quota/config.toml create mode 100644 tests/lightning_disk_quota/data/disk_quota-schema-create.sql create mode 100644 tests/lightning_disk_quota/data/disk_quota.t-schema.sql create mode 100644 tests/lightning_disk_quota/data/disk_quota.t.0.sql create mode 100644 tests/lightning_disk_quota/data/disk_quota.t.1.sql create mode 100644 tests/lightning_disk_quota/data/disk_quota.t.2.sql create mode 100644 tests/lightning_disk_quota/data/disk_quota.t.3.sql create mode 100644 tests/lightning_disk_quota/run.sh create mode 100644 tests/lightning_error_summary/config.toml create mode 100644 tests/lightning_error_summary/data/error_summary-schema-create.sql create mode 100644 tests/lightning_error_summary/data/error_summary.a-schema.sql create mode 100644 tests/lightning_error_summary/data/error_summary.a.sql create mode 100644 tests/lightning_error_summary/data/error_summary.b-schema.sql create mode 100644 tests/lightning_error_summary/data/error_summary.b.sql create mode 100644 tests/lightning_error_summary/data/error_summary.c-schema.sql create mode 100644 tests/lightning_error_summary/data/error_summary.c.sql create mode 100755 tests/lightning_error_summary/run.sh create mode 100644 tests/lightning_examples/1.toml create mode 100644 tests/lightning_examples/131072.toml create mode 100644 tests/lightning_examples/512.toml create mode 100755 tests/lightning_examples/run.sh create mode 100644 tests/lightning_exotic_filenames/config.toml create mode 100644 tests/lightning_exotic_filenames/data/xfn-schema-create.sql create mode 100644 tests/lightning_exotic_filenames/data/xfn.etn-schema.sql create mode 100644 tests/lightning_exotic_filenames/data/xfn.etn.sql create mode 100644 tests/lightning_exotic_filenames/data/zwk-schema-create.sql create mode 100644 tests/lightning_exotic_filenames/data/zwk.zwb-schema.sql create mode 100644 tests/lightning_exotic_filenames/data/zwk.zwb.sql create mode 100755 tests/lightning_exotic_filenames/run.sh create mode 100644 tests/lightning_file_routing/config.toml create mode 100755 tests/lightning_file_routing/run.sh create mode 100644 tests/lightning_generated_columns/config.toml create mode 100644 tests/lightning_generated_columns/data/gencol-schema-create.sql create mode 100644 tests/lightning_generated_columns/data/gencol.nested-schema.sql create mode 100644 tests/lightning_generated_columns/data/gencol.nested.0.sql create mode 100644 tests/lightning_generated_columns/data/gencol.various_types-schema.sql create mode 100644 tests/lightning_generated_columns/data/gencol.various_types.0.sql create mode 100644 tests/lightning_generated_columns/run.sh create mode 100644 tests/lightning_issue_282/config.toml create mode 100644 tests/lightning_issue_282/data/issue282-schema-create.sql create mode 100644 tests/lightning_issue_282/data/issue282.t_access3-schema.sql create mode 100644 tests/lightning_issue_282/data/issue282.t_access3.sql create mode 100644 tests/lightning_issue_282/run.sh create mode 100644 tests/lightning_issue_410/config.toml create mode 100644 tests/lightning_issue_410/data/issue410-schema-create.sql create mode 100644 tests/lightning_issue_410/data/issue410.row_flow_d-schema.sql create mode 100644 tests/lightning_issue_410/data/issue410.row_flow_d.0.csv create mode 100644 tests/lightning_issue_410/run.sh create mode 100644 tests/lightning_issue_519/config.toml create mode 100644 tests/lightning_issue_519/data/issue519-schema-create.sql create mode 100644 tests/lightning_issue_519/data/issue519.t-schema.sql create mode 100644 tests/lightning_issue_519/data/issue519.t.csv create mode 100755 tests/lightning_issue_519/run.sh create mode 100644 tests/lightning_local_backend/config.toml create mode 100644 tests/lightning_local_backend/data/cpeng-schema-create.sql create mode 100644 tests/lightning_local_backend/data/cpeng.a-schema.sql create mode 100644 tests/lightning_local_backend/data/cpeng.a.1.sql create mode 100644 tests/lightning_local_backend/data/cpeng.a.2.sql create mode 100644 tests/lightning_local_backend/data/cpeng.a.3.sql create mode 100644 tests/lightning_local_backend/data/cpeng.b-schema.sql create mode 100644 tests/lightning_local_backend/data/cpeng.b.1.sql create mode 100644 tests/lightning_local_backend/data/cpeng.b.2.sql create mode 100644 tests/lightning_local_backend/file.toml create mode 100644 tests/lightning_local_backend/mysql.toml create mode 100755 tests/lightning_local_backend/run.sh create mode 100644 tests/lightning_new_collation/config.toml create mode 100644 tests/lightning_new_collation/run.sh create mode 100644 tests/lightning_new_collation/tidb-new-collation.toml create mode 100644 tests/lightning_no_schema/config.toml create mode 100644 tests/lightning_no_schema/data/noschema.t.sql create mode 100644 tests/lightning_no_schema/run.sh create mode 100644 tests/lightning_no_schema/schema-data/noschema-schema-create.sql create mode 100644 tests/lightning_no_schema/schema-data/noschema.t-schema.sql create mode 100644 tests/lightning_parquet/config.toml create mode 100644 tests/lightning_parquet/data/export_info_ci.json create mode 100644 tests/lightning_parquet/data/export_tables_info_ci_from_1_to_9.json create mode 100644 tests/lightning_parquet/data/test/test.customer/_SUCCESS create mode 100644 tests/lightning_parquet/data/test/test.customer/part-00000-c3744aeb-351c-49ba-bdf3-5864befff481-c000.gz.parquet create mode 100644 tests/lightning_parquet/data/test/test.district/_SUCCESS create mode 100644 tests/lightning_parquet/data/test/test.district/part-00000-f61f4bef-6765-432a-8f18-cd17b4607f2a-c000.gz.parquet create mode 100644 tests/lightning_parquet/data/test/test.history/_SUCCESS create mode 100644 tests/lightning_parquet/data/test/test.history/part-00000-8cf0e97a-1169-4335-a93f-8805e02def97-c000.gz.parquet create mode 100644 tests/lightning_parquet/data/test/test.item/_SUCCESS create mode 100644 tests/lightning_parquet/data/test/test.item/part-00000-8905ded8-4c54-477f-9907-6e3eae50358b-c000.gz.parquet create mode 100644 tests/lightning_parquet/data/test/test.new_order/_SUCCESS create mode 100644 tests/lightning_parquet/data/test/test.new_order/part-00000-d868200e-b629-4445-bd96-d34b6674b09d-c000.gz.parquet create mode 100644 tests/lightning_parquet/data/test/test.order_line/_SUCCESS create mode 100644 tests/lightning_parquet/data/test/test.order_line/part-00000-e36fecd0-3f59-4ff6-b271-2e4b27ffbcf5-c000.gz.parquet create mode 100644 tests/lightning_parquet/data/test/test.orders/_SUCCESS create mode 100644 tests/lightning_parquet/data/test/test.orders/part-00000-b45481f5-92c0-4961-9bf7-32a0a04ffee5-c000.gz.parquet create mode 100644 tests/lightning_parquet/data/test/test.stock/_SUCCESS create mode 100644 tests/lightning_parquet/data/test/test.stock/part-00000-eef45943-3034-4d65-b375-ee55aa479215-c000.gz.parquet create mode 100644 tests/lightning_parquet/data/test/test.warehouse/_SUCCESS create mode 100644 tests/lightning_parquet/data/test/test.warehouse/part-00000-c6c33252-4d2f-4c5c-8a2f-d162bde6c360-c000.gz.parquet create mode 100644 tests/lightning_parquet/db.sql create mode 100755 tests/lightning_parquet/run.sh create mode 100644 tests/lightning_partitioned-table/config.toml create mode 100644 tests/lightning_partitioned-table/data/partitioned-schema-create.sql create mode 100644 tests/lightning_partitioned-table/data/partitioned.a-schema.sql create mode 100644 tests/lightning_partitioned-table/data/partitioned.a.sql create mode 100755 tests/lightning_partitioned-table/run.sh create mode 100644 tests/lightning_restore/config.toml create mode 100755 tests/lightning_restore/run.sh create mode 100644 tests/lightning_routes/config.toml create mode 100644 tests/lightning_routes/data/routes_a0-schema-create.sql create mode 100644 tests/lightning_routes/data/routes_a0.t0-schema.sql create mode 100644 tests/lightning_routes/data/routes_a0.t0.1.sql create mode 100644 tests/lightning_routes/data/routes_a0.t0.2.sql create mode 100644 tests/lightning_routes/data/routes_a0.t1-schema.sql create mode 100644 tests/lightning_routes/data/routes_a0.t1.1.sql create mode 100644 tests/lightning_routes/data/routes_a1-schema-create.sql create mode 100644 tests/lightning_routes/data/routes_a1.s1-schema.sql create mode 100644 tests/lightning_routes/data/routes_a1.s1.sql create mode 100644 tests/lightning_routes/data/routes_a1.t2-schema.sql create mode 100644 tests/lightning_routes/data/routes_a1.t2.sql create mode 100755 tests/lightning_routes/run.sh create mode 100644 tests/lightning_row-format-v2/config.toml create mode 100644 tests/lightning_row-format-v2/data/rowformatv2-schema-create.sql create mode 100644 tests/lightning_row-format-v2/data/rowformatv2.t1-schema.sql create mode 100644 tests/lightning_row-format-v2/data/rowformatv2.t1.1.sql create mode 100644 tests/lightning_row-format-v2/run.sh create mode 100644 tests/lightning_s3/config.toml create mode 100755 tests/lightning_s3/run.sh create mode 100644 tests/lightning_source_linkfile/config.toml create mode 100644 tests/lightning_source_linkfile/run.sh create mode 100644 tests/lightning_sqlmode/data/sqlmodedb-schema-create.sql create mode 100644 tests/lightning_sqlmode/data/sqlmodedb.t-schema.sql create mode 100644 tests/lightning_sqlmode/data/sqlmodedb.t.1.sql create mode 100644 tests/lightning_sqlmode/off.toml create mode 100644 tests/lightning_sqlmode/on.toml create mode 100755 tests/lightning_sqlmode/run.sh create mode 100644 tests/lightning_tidb_duplicate_data/data/dup-schema-create.sql create mode 100644 tests/lightning_tidb_duplicate_data/data/dup.dup-schema.sql create mode 100644 tests/lightning_tidb_duplicate_data/data/dup.dup.sql create mode 100644 tests/lightning_tidb_duplicate_data/error.toml create mode 100644 tests/lightning_tidb_duplicate_data/ignore.toml create mode 100644 tests/lightning_tidb_duplicate_data/replace.toml create mode 100644 tests/lightning_tidb_duplicate_data/run.sh create mode 100644 tests/lightning_tidb_rowid/config.toml create mode 100644 tests/lightning_tidb_rowid/data/rowid-schema-create.sql create mode 100644 tests/lightning_tidb_rowid/data/rowid.explicit_tidb_rowid-schema.sql create mode 100644 tests/lightning_tidb_rowid/data/rowid.explicit_tidb_rowid.sql create mode 100644 tests/lightning_tidb_rowid/data/rowid.non_pk-schema.sql create mode 100644 tests/lightning_tidb_rowid/data/rowid.non_pk.sql create mode 100644 tests/lightning_tidb_rowid/data/rowid.non_pk_auto_inc-schema.sql create mode 100644 tests/lightning_tidb_rowid/data/rowid.non_pk_auto_inc.sql create mode 100644 tests/lightning_tidb_rowid/data/rowid.pre_rebase-schema.sql create mode 100644 tests/lightning_tidb_rowid/data/rowid.pre_rebase.sql create mode 100644 tests/lightning_tidb_rowid/data/rowid.specific_auto_inc-schema.sql create mode 100644 tests/lightning_tidb_rowid/data/rowid.specific_auto_inc.sql create mode 100755 tests/lightning_tidb_rowid/run.sh create mode 100644 tests/lightning_tiflash/config.toml create mode 100644 tests/lightning_tiflash/run.sh create mode 100644 tests/lightning_too_many_columns/config.toml create mode 100644 tests/lightning_too_many_columns/data/too_many_columns-schema-create.sql create mode 100644 tests/lightning_too_many_columns/data/too_many_columns.t-schema.sql create mode 100644 tests/lightning_too_many_columns/data/too_many_columns.t.0.csv create mode 100644 tests/lightning_too_many_columns/run.sh create mode 100644 tests/lightning_tool_135/config.toml create mode 100644 tests/lightning_tool_135/data/tool_135-schema-create.sql create mode 100644 tests/lightning_tool_135/data/tool_135.bar1-schema.sql create mode 100644 tests/lightning_tool_135/data/tool_135.bar1.sql create mode 100644 tests/lightning_tool_135/data/tool_135.bar2-schema.sql create mode 100644 tests/lightning_tool_135/data/tool_135.bar2.sql create mode 100644 tests/lightning_tool_135/data/tool_135.bar3-schema.sql create mode 100644 tests/lightning_tool_135/data/tool_135.bar3.sql create mode 100644 tests/lightning_tool_135/data/tool_135.bar4-schema.sql create mode 100644 tests/lightning_tool_135/data/tool_135.bar4.sql create mode 100644 tests/lightning_tool_135/data/tool_135.bar5-schema.sql create mode 100644 tests/lightning_tool_135/data/tool_135.bar5.sql create mode 100755 tests/lightning_tool_135/run.sh create mode 100644 tests/lightning_tool_1420/config.toml create mode 100644 tests/lightning_tool_1420/data/EE1420-schema-create.sql create mode 100644 tests/lightning_tool_1420/data/EE1420.pt_role-schema.sql create mode 100644 tests/lightning_tool_1420/data/EE1420.pt_role.sql create mode 100755 tests/lightning_tool_1420/run.sh create mode 100644 tests/lightning_tool_1472/config.toml create mode 100644 tests/lightning_tool_1472/data/EE1472-schema-create.sql create mode 100644 tests/lightning_tool_1472/data/EE1472.notpk-schema.sql create mode 100644 tests/lightning_tool_1472/data/EE1472.notpk.1.sql create mode 100644 tests/lightning_tool_1472/data/EE1472.notpk.2.sql create mode 100644 tests/lightning_tool_1472/data/EE1472.pk-schema.sql create mode 100644 tests/lightning_tool_1472/data/EE1472.pk.1.sql create mode 100644 tests/lightning_tool_1472/data/EE1472.pk.2.sql create mode 100755 tests/lightning_tool_1472/run.sh create mode 100644 tests/lightning_tool_241/config.toml create mode 100644 tests/lightning_tool_241/data/qyjc-schema-create.sql create mode 100644 tests/lightning_tool_241/data/qyjc.q_alarm_group-schema.sql create mode 100644 tests/lightning_tool_241/data/qyjc.q_alarm_group.sql create mode 100644 tests/lightning_tool_241/data/qyjc.q_alarm_message_log-schema.sql create mode 100644 tests/lightning_tool_241/data/qyjc.q_alarm_message_log.sql create mode 100644 tests/lightning_tool_241/data/qyjc.q_alarm_receiver-schema.sql create mode 100644 tests/lightning_tool_241/data/qyjc.q_config-schema.sql create mode 100644 tests/lightning_tool_241/data/qyjc.q_fish_event-schema.sql create mode 100644 tests/lightning_tool_241/data/qyjc.q_fish_event.sql create mode 100644 tests/lightning_tool_241/data/qyjc.q_report_circular_data-schema.sql create mode 100644 tests/lightning_tool_241/data/qyjc.q_report_desc-schema.sql create mode 100644 tests/lightning_tool_241/data/qyjc.q_report_summary-schema.sql create mode 100644 tests/lightning_tool_241/data/qyjc.q_system_update-schema.sql create mode 100644 tests/lightning_tool_241/data/qyjc.q_system_update.sql create mode 100644 tests/lightning_tool_241/data/qyjc.q_user_log-schema.sql create mode 100644 tests/lightning_tool_241/data/qyjc.q_user_log.sql create mode 100755 tests/lightning_tool_241/run.sh create mode 100644 tests/lightning_unused_config_keys/config.toml create mode 100644 tests/lightning_unused_config_keys/data/unused_config_keys-schema-create.sql create mode 100755 tests/lightning_unused_config_keys/run.sh create mode 100644 tests/lightning_various_types/config.toml create mode 100644 tests/lightning_various_types/data/vt-schema-create.sql create mode 100644 tests/lightning_various_types/data/vt.binary-schema.sql create mode 100644 tests/lightning_various_types/data/vt.binary.sql create mode 100644 tests/lightning_various_types/data/vt.bit-schema.sql create mode 100644 tests/lightning_various_types/data/vt.bit.sql create mode 100644 tests/lightning_various_types/data/vt.char-schema.sql create mode 100644 tests/lightning_various_types/data/vt.char.sql create mode 100644 tests/lightning_various_types/data/vt.datetime-schema.sql create mode 100644 tests/lightning_various_types/data/vt.datetime.sql create mode 100644 tests/lightning_various_types/data/vt.decimal-schema.sql create mode 100644 tests/lightning_various_types/data/vt.decimal.sql create mode 100644 tests/lightning_various_types/data/vt.double-schema.sql create mode 100644 tests/lightning_various_types/data/vt.double.sql create mode 100644 tests/lightning_various_types/data/vt.empty_strings-schema.sql create mode 100644 tests/lightning_various_types/data/vt.empty_strings.sql create mode 100644 tests/lightning_various_types/data/vt.enum-set-schema.sql create mode 100644 tests/lightning_various_types/data/vt.enum-set.sql create mode 100644 tests/lightning_various_types/data/vt.json-schema.sql create mode 100644 tests/lightning_various_types/data/vt.json.sql create mode 100644 tests/lightning_various_types/data/vt.precise_types-schema.sql create mode 100644 tests/lightning_various_types/data/vt.precise_types.sql create mode 100755 tests/lightning_various_types/run.sh create mode 100644 tests/lightning_view/config.toml create mode 100644 tests/lightning_view/data/db0-schema-create.sql create mode 100644 tests/lightning_view/data/db0.v2-schema-view.sql create mode 100644 tests/lightning_view/data/db0.v2-schema.sql create mode 100644 tests/lightning_view/data/db1-schema-create.sql create mode 100644 tests/lightning_view/data/db1.tbl-schema.sql create mode 100644 tests/lightning_view/data/db1.tbl.0.sql create mode 100644 tests/lightning_view/data/db1.v1-schema-view.sql create mode 100644 tests/lightning_view/data/db1.v1-schema.sql create mode 100755 tests/lightning_view/run.sh create mode 100644 tidb-lightning.toml create mode 100644 web/README.md create mode 100644 web/docs/InfoPage.png create mode 100644 web/docs/ProgressPage.png create mode 100644 web/docs/TableProgressPage.png create mode 100644 web/docs/api.yaml create mode 100644 web/go.mod create mode 100644 web/go.sum create mode 100644 web/package-lock.json create mode 100644 web/package.json create mode 100644 web/public/index.html create mode 100644 web/src/ChunksProgressPanel.tsx create mode 100644 web/src/DottedProgress.tsx create mode 100644 web/src/EnginesProgressPanel.tsx create mode 100644 web/src/ErrorButton.tsx create mode 100644 web/src/InfoButton.tsx create mode 100644 web/src/InfoPage.tsx create mode 100644 web/src/MoveTaskButton.tsx create mode 100644 web/src/PauseButton.tsx create mode 100644 web/src/ProgressPage.tsx create mode 100644 web/src/RefreshButton.tsx create mode 100644 web/src/TableProgressCard.tsx create mode 100644 web/src/TableProgressPage.tsx create mode 100644 web/src/TaskButton.tsx create mode 100644 web/src/TitleBar.tsx create mode 100644 web/src/TitleLink.tsx create mode 100644 web/src/api.ts create mode 100644 web/src/index.tsx create mode 100644 web/src/json-bigint.d.ts create mode 100644 web/tsconfig.json create mode 100644 web/webpack.config.js diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..ba35fa100 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*_generated.go linguist-generated=true diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 872699919..555efb841 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -2,7 +2,7 @@ name: "🛠Bug Report" about: Something isn't working as expected title: '' -labels: 'bug' +labels: 'type/bug ' --- Please answer these questions before submitting your issue. Thanks! @@ -23,7 +23,23 @@ If possible, provide a recipe for reproducing the error. + +5. Operation logs + - Please upload `br.log` for BR if possible + - Please upload `tidb-lightning.log` for TiDB-Lightning if possible + - Please upload `tikv-importer.log` from TiKV-Importer if possible + - Other interesting logs + + +6. Configuration of the cluster and the task + - `tidb-lightning.toml` for TiDB-Lightning if possible + - `tikv-importer.toml` for TiKV-Importer if possible + - `topology.yml` if deployed by TiUP + + +7. Screenshot/exported-PDF of Grafana dashboard or metrics' graph in Prometheus if possible diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md index e895af84d..ed6b4c5b0 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -1,7 +1,7 @@ --- name: "🚀 Feature Request" about: I have a suggestion -labels: enhancement +labels: 'type/feature-request' --- ## Feature Request diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 000000000..23a811837 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,24 @@ +--- +name: "\U0001F914 Question" +labels: "type/question" +about: Usage question that isn't answered in docs or discussion + +--- + +## Question + + + diff --git a/.github/challenge-bot.yml b/.github/challenge-bot.yml new file mode 100644 index 000000000..15d2f38ec --- /dev/null +++ b/.github/challenge-bot.yml @@ -0,0 +1 @@ +defaultSigLabel: sig/migrate diff --git a/.gitignore b/.gitignore index b5976e436..bec328031 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,15 @@ -br -bin/ -_tools/ -.idea/ -backupmeta +/br +/bin +/.idea *.log *.ngo -*.coverprofile -coverage.txt -docker/data/ -docker/logs/ +/docker/data/ +/docker/logs/ *.swp .DS_Store -go.mod -go.sum +/go.mod +/go.sum + +# for the web interface +web/node_modules/ +web/dist/ diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 6db4cfd21..000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,18 +0,0 @@ -# BR (Backup and Restore) Change Log -All notable changes to this project are documented in this file. -See also, -- [TiDB Changelog](https://github.com/pingcap/tidb/blob/master/CHANGELOG.md), -- [TiKV Changelog](https://github.com/tikv/tikv/blob/master/CHANGELOG.md), -- [PD Changelog](https://github.com/pingcap/pd/blob/master/CHANGELOG.md). - -## [3.1.0-beta.1] - 2020-01-10 - -- Fix the inaccurate backup progress information [#127](https://github.com/pingcap/br/pull/127) -- Improve the performance of splitting Regions [#122](https://github.com/pingcap/br/pull/122) -- Add the backup and restore feature for partitioned tables [#137](https://github.com/pingcap/br/pull/137) -- Add the feature of automatically scheduling PD schedulers [#123](https://github.com/pingcap/br/pull/123) -- Fix the issue that data is overwritten after non `PKIsHandle` tables are restored [#139](https://github.com/pingcap/br/pull/139) - -## [3.1.0-beta] - 2019-12-20 - -Initial release of the distributed backup and restore tool diff --git a/Makefile b/Makefile index 0b44a3e02..5b29576a7 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,10 @@ PROTOC ?= $(shell which protoc) PROTOS := $(shell find $(shell pwd) -type f -name '*.proto' -print) -CWD := $(shell pwd) -PACKAGES := go list ./... +CWD := $(shell pwd) +TOOLS := $(CWD)/tools/bin +PACKAGES := go list ./... | grep -vE 'vendor|test|proto|diff|bin|fuzz' PACKAGE_DIRECTORIES := $(PACKAGES) | sed 's/github.com\/pingcap\/br\/*//' +PACKAGE_FILES := $$(find . -name '*.go' -type f | grep -vE 'vendor|\.pb\.go|lightning/mock|res_vfsdata') CHECKER := awk '{ print } END { if (NR > 0) { exit 1 } }' BR_PKG := github.com/pingcap/br @@ -21,14 +23,31 @@ LDFLAGS += -X "$(BR_PKG)/pkg/utils.BRReleaseVersion=$(VERSION)" LDFLAGS += -X "$(BR_PKG)/pkg/utils.BRBuildTS=$(shell date -u '+%Y-%m-%d %I:%M:%S')" LDFLAGS += -X "$(BR_PKG)/pkg/utils.BRGitHash=$(shell git rev-parse HEAD)" LDFLAGS += -X "$(BR_PKG)/pkg/utils.BRGitBranch=$(shell git rev-parse --abbrev-ref HEAD)" +# TODO: unify LDFLAGS +LDFLAGS += -X "$(BR_PKG)/pkg/lightning/common.ReleaseVersion=$(VERSION)" +LDFLAGS += -X "$(BR_PKG)/pkg/lightning/common.BuildTS=$(shell date -u '+%Y-%m-%d %I:%M:%S')" +LDFLAGS += -X "$(BR_PKG)/pkg/lightning/common.GitHash=$(shell git rev-parse HEAD)" +LDFLAGS += -X "$(BR_PKG)/pkg/lightning/common.GitBranch=$(shell git rev-parse --abbrev-ref HEAD)" +LDFLAGS += -X "$(BR_PKG)/pkg/lightning/common.GoVersion=$(shell go version)" + +LIGHTNING_BIN := bin/tidb-lightning +LIGHTNING_CTL_BIN := bin/tidb-lightning-ctl +BR_BIN := bin/br +VFSGENDEV_BIN := tools/bin/vfsgendev +TEST_DIR := /tmp/backup_restore_test + +path_to_add := $(addsuffix /bin,$(subst :,/bin:,$(GOPATH))) +export PATH := $(path_to_add):$(PATH) GOBUILD := CGO_ENABLED=0 GO111MODULE=on go build -trimpath -ldflags '$(LDFLAGS)' GOTEST := CGO_ENABLED=1 GO111MODULE=on go test -ldflags '$(LDFLAGS)' PREPARE_MOD := cp go.mod1 go.mod && cp go.sum1 go.sum FINISH_MOD := cp go.mod go.mod1 && cp go.sum go.sum1 +RACE_FLAG = ifeq ("$(WITH_RACE)", "1") - RACEFLAG = -race + RACE_FLAG = -race + GOBUILD = CGO_ENABLED=1 GO111MODULE=on $(GO) build -ldflags '$(LDFLAGS)' endif all: build check test @@ -39,40 +58,88 @@ prepare: finish-prepare: $(FINISH_MOD) -build: +%_generated.go: %.rl + ragel -Z -G2 -o tmp_parser.go $< + @echo '// Code generated by ragel DO NOT EDIT.' | cat - tmp_parser.go | sed 's|//line |//.... |g' > $@ + @rm tmp_parser.go + +data_parsers: tools pkg/lightning/mydump/parser_generated.go web + PATH="$(GOPATH)/bin":"$(PATH)":"$(TOOLS)" protoc -I. -I"$(GOPATH)/src" pkg/lightning/checkpoints/file_checkpoints.proto --gogofaster_out=. + $(TOOLS)/vfsgendev -source='"github.com/pingcap/br/pkg/lightning/web".Res' && mv res_vfsdata.go pkg/lightning/web/ + +web: + cd web && npm install && npm run build + +build: br lightning lightning-ctl + +br: + $(PREPARE_MOD) + $(GOBUILD) $(RACEFLAG) -o $(BR_BIN) cmd/br/*.go + +lightning_for_web: + $(PREPARE_MOD) + $(GOBUILD) $(RACE_FLAG) -tags dev -o $(LIGHTNING_BIN) cmd/tidb-lightning/main.go + +lightning: $(PREPARE_MOD) - $(GOBUILD) $(RACEFLAG) -o bin/br + $(GOBUILD) $(RACE_FLAG) -o $(LIGHTNING_BIN) cmd/tidb-lightning/main.go + +lightning-ctl: + $(PREPARE_MOD) + $(GOBUILD) $(RACE_FLAG) -o $(LIGHTNING_CTL_BIN) cmd/tidb-lightning-ctl/main.go build_for_integration_test: $(PREPARE_MOD) @make failpoint-enable ($(GOTEST) -c -cover -covermode=count \ -coverpkg=$(BR_PKG)/... \ - -o bin/br.test && \ + -o $(BR_BIN).test \ + github.com/pingcap/br/cmd/br && \ + $(GOTEST) -c -cover -covermode=count \ + -coverpkg=$(BR_PKG)/... \ + -o $(LIGHTNING_BIN).test \ + github.com/pingcap/br/cmd/tidb-lightning && \ + $(GOTEST) -c -cover -covermode=count \ + -coverpkg=$(BR_PKG)/... \ + -o $(LIGHTNING_CTL_BIN).test \ + github.com/pingcap/br/cmd/tidb-lightning-ctl && \ $(GOBUILD) $(RACEFLAG) -o bin/locker tests/br_key_locked/*.go && \ $(GOBUILD) $(RACEFLAG) -o bin/gc tests/br_z_gc_safepoint/*.go && \ +<<<<<<< HEAD $(GOBUILD) $(RACEFLAG) -o bin/rawkv tests/br_rawkv/*.go) || (make failpoint-disable && exit 1) +======= + $(GOBUILD) $(RACEFLAG) -o bin/oauth tests/br_gcs/*.go && \ + $(GOBUILD) $(RACEFLAG) -o bin/rawkv tests/br_rawkv/*.go && \ + $(GOBUILD) $(RACE_FLAG) -o bin/parquet_gen tests/lightning_checkpoint_parquet/*.go \ + ) || (make failpoint-disable && exit 1) +>>>>>>> 8b9b626... Merge branch 'merging' @make failpoint-disable test: $(PREPARE_MOD) @make failpoint-enable - $(GOTEST) $(RACEFLAG) -tags leak ./... || ( make failpoint-disable && exit 1 ) + $(GOTEST) $(RACEFLAG) -tags leak $$($(PACKAGES)) || ( make failpoint-disable && exit 1 ) @make failpoint-disable testcover: tools + mkdir -p "$(TEST_DIR)" $(PREPARE_MOD) @make failpoint-enable - GO111MODULE=on tools/bin/overalls \ - -project=$(BR_PKG) \ - -covermode=count \ - -ignore='.git,vendor,tests,_tools,docker' \ - -debug \ - -- -coverpkg=./... || ( make failpoint-disable && exit 1 ) + $(GOTEST) -cover -covermode=count -coverprofile="$(TEST_DIR)/cov.unit.out" $$($(PACKAGES)) || ( make failpoint-disable && exit 1 ) + @make failpoint-disable integration_test: bins build build_for_integration_test tests/run.sh +coverage: tools + tools/bin/gocovmerge "$(TEST_DIR)"/cov.* | grep -vE ".*.pb.go|.*__failpoint_binding__.go" > "$(TEST_DIR)/all_cov.out" +ifeq ("$(JenkinsCI)", "1") + tools/bin/goveralls -coverprofile=$(TEST_DIR)/all_cov.out -service=jenkins-ci -repotoken $(COVERALLS_TOKEN) +else + go tool cover -html "$(TEST_DIR)/all_cov.out" -o "$(TEST_DIR)/all_cov.html" + grep -F '&1 | $(CHECKER) - tools/bin/govet --shadow $$($(PACKAGE_DIRECTORIES)) 2>&1 | $(CHECKER) + tools/bin/gofumports -w -d -format-only -local $(BR_PKG) $(PACKAGE_FILES) 2>&1 | $(CHECKER) + # TODO: go vet lightning packages too. + tools/bin/govet --shadow $$($(PACKAGE_DIRECTORIES) | grep -v "lightning") 2>&1 | $(CHECKER) + # TODO: lint lightning packages too. @# why some lints are disabled? @# gochecknoglobals - disabled because we do use quite a lot of globals @# goimports - executed above already, gofumports @@ -142,17 +212,20 @@ static: prepare tools --disable exhaustive \ --disable godot \ --disable gosec \ - $$($(PACKAGE_DIRECTORIES)) + $$($(PACKAGE_DIRECTORIES) | grep -v "lightning") # pingcap/errors APIs are mixed with multiple patterns 'pkg/errors', # 'juju/errors' and 'pingcap/parser'. To avoid confusion and mistake, # we only allow a subset of APIs, that's "Normalize|Annotate|Trace|Cause". + # TODO: check lightning packages. @# TODO: allow more APIs when we need to support "workaound". - grep -Rn --exclude="*_test.go" -E "(\t| )errors\.[A-Z]" cmd pkg | \ + grep -Rn --include="*.go" --exclude="*_test.go" -E "(\t| )errors\.[A-Z]" \ + $$($(PACKAGE_DIRECTORIES) | grep -vE "tests|lightning") | \ grep -vE "Normalize|Annotate|Trace|Cause|RedactLogEnabled" 2>&1 | $(CHECKER) lint: prepare tools @echo "linting" - CGO_ENABLED=0 tools/bin/revive -formatter friendly -config revive.toml $$($(PACKAGES)) + # TODO: lint lightning packages. + CGO_ENABLED=0 tools/bin/revive -formatter friendly -config revive.toml $$($(PACKAGES) | grep -v "lightning") tidy: @echo "go mod tidy" @@ -172,4 +245,4 @@ failpoint-enable: tools failpoint-disable: tools tools/bin/failpoint-ctl disable -.PHONY: tools +.PHONY: tools web diff --git a/cmd/backup.go b/cmd/br/backup.go similarity index 99% rename from cmd/backup.go rename to cmd/br/backup.go index 3fb8868b0..a6b2e00e6 100644 --- a/cmd/backup.go +++ b/cmd/br/backup.go @@ -1,6 +1,6 @@ // Copyright 2020 PingCAP, Inc. Licensed under Apache-2.0. -package cmd +package main import ( "github.com/pingcap/errors" diff --git a/cmd/cmd.go b/cmd/br/cmd.go similarity index 99% rename from cmd/cmd.go rename to cmd/br/cmd.go index e65277970..adb32326f 100644 --- a/cmd/cmd.go +++ b/cmd/br/cmd.go @@ -1,6 +1,6 @@ // Copyright 2020 PingCAP, Inc. Licensed under Apache-2.0. -package cmd +package main import ( "context" @@ -141,6 +141,7 @@ func Init(cmd *cobra.Command) (err error) { } // Initialize the pprof server. + // TODO: Support TLS. statusAddr, e := cmd.Flags().GetString(FlagStatusAddr) if e != nil { err = e diff --git a/cmd/debug.go b/cmd/br/debug.go similarity index 99% rename from cmd/debug.go rename to cmd/br/debug.go index 5d4576b24..8f00b14e3 100644 --- a/cmd/debug.go +++ b/cmd/br/debug.go @@ -1,6 +1,6 @@ // Copyright 2020 PingCAP, Inc. Licensed under Apache-2.0. -package cmd +package main import ( "bytes" diff --git a/main.go b/cmd/br/main.go similarity index 86% rename from main.go rename to cmd/br/main.go index 7c6221ace..34e872c96 100644 --- a/main.go +++ b/cmd/br/main.go @@ -10,8 +10,6 @@ import ( "github.com/pingcap/log" "github.com/spf13/cobra" "go.uber.org/zap" - - "github.com/pingcap/br/cmd" ) func main() { @@ -44,19 +42,20 @@ func main() { TraverseChildren: true, SilenceUsage: true, } - cmd.AddFlags(rootCmd) - cmd.SetDefaultContext(ctx) + AddFlags(rootCmd) + SetDefaultContext(ctx) rootCmd.AddCommand( - cmd.NewDebugCommand(), - cmd.NewBackupCommand(), - cmd.NewRestoreCommand(), + NewDebugCommand(), + NewBackupCommand(), + NewRestoreCommand(), ) // Ouputs cmd.Print to stdout. rootCmd.SetOut(os.Stdout) rootCmd.SetArgs(os.Args[1:]) if err := rootCmd.Execute(); err != nil { + cancel() log.Error("br failed", zap.Error(err)) - os.Exit(1) + os.Exit(1) // nolint:gocritic } } diff --git a/main_test.go b/cmd/br/main_test.go similarity index 100% rename from main_test.go rename to cmd/br/main_test.go diff --git a/cmd/restore.go b/cmd/br/restore.go similarity index 99% rename from cmd/restore.go rename to cmd/br/restore.go index 26aa20c59..85fde87ce 100644 --- a/cmd/restore.go +++ b/cmd/br/restore.go @@ -1,6 +1,6 @@ // Copyright 2020 PingCAP, Inc. Licensed under Apache-2.0. -package cmd +package main import ( "github.com/pingcap/errors" diff --git a/cmd/tidb-lightning-ctl/main.go b/cmd/tidb-lightning-ctl/main.go new file mode 100644 index 000000000..54e04a312 --- /dev/null +++ b/cmd/tidb-lightning-ctl/main.go @@ -0,0 +1,406 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "flag" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/google/uuid" + "github.com/pingcap/errors" + "github.com/pingcap/kvproto/pkg/import_sstpb" + + kv "github.com/pingcap/br/pkg/lightning/backend" + "github.com/pingcap/br/pkg/lightning/checkpoints" + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/config" + "github.com/pingcap/br/pkg/lightning/restore" +) + +func main() { + if err := run(); err != nil { + fmt.Fprintln(os.Stderr, errors.ErrorStack(err)) + exit(1) + } +} + +// main_test.go override exit to pass unit test. +var exit = os.Exit + +func run() error { + var ( + compact, flagFetchMode *bool + mode, flagImportEngine, flagCleanupEngine *string + cpRemove, cpErrIgnore, cpErrDestroy, cpDump *string + localStoringTables *bool + + fsUsage func() + ) + + globalCfg := config.Must(config.LoadGlobalConfig(os.Args[1:], func(fs *flag.FlagSet) { + // change the default of `-d` from empty to 'noop://'. + // there is a check if `-d` points to a valid storage, and '' is not. + // since tidb-lightning-ctl does not need `-d` we change the default to a valid but harmless value. + dFlag := fs.Lookup("d") + dFlag.Value.Set("noop://") + dFlag.DefValue = "noop://" + + compact = fs.Bool("compact", false, "do manual compaction on the target cluster") + mode = fs.String("switch-mode", "", "switch tikv into import mode or normal mode, values can be ['import', 'normal']") + flagFetchMode = fs.Bool("fetch-mode", false, "obtain the current mode of every tikv in the cluster") + + flagImportEngine = fs.String("import-engine", "", "manually import a closed engine (value can be '`db`.`table`:123' or a UUID") + flagCleanupEngine = fs.String("cleanup-engine", "", "manually delete a closed engine") + + cpRemove = fs.String("checkpoint-remove", "", "remove the checkpoint associated with the given table (value can be 'all' or '`db`.`table`')") + cpErrIgnore = fs.String("checkpoint-error-ignore", "", "ignore errors encoutered previously on the given table (value can be 'all' or '`db`.`table`'); may corrupt this table if used incorrectly") + cpErrDestroy = fs.String("checkpoint-error-destroy", "", "deletes imported data with table which has an error before (value can be 'all' or '`db`.`table`')") + cpDump = fs.String("checkpoint-dump", "", "dump the checkpoint information as two CSV files in the given folder") + + localStoringTables = fs.Bool("check-local-storage", false, "show tables that are missing local intermediate files (value can be 'all' or '`db`.`table`')") + + fsUsage = fs.Usage + })) + + ctx := context.Background() + + cfg := config.NewConfig() + if err := cfg.LoadFromGlobal(globalCfg); err != nil { + return err + } + if err := cfg.Adjust(ctx); err != nil { + return err + } + + tls, err := cfg.ToTLS() + if err != nil { + return err + } + if err = cfg.TiDB.Security.RegisterMySQL(); err != nil { + return err + } + + if *compact { + return errors.Trace(compactCluster(ctx, cfg, tls)) + } + if *flagFetchMode { + return errors.Trace(fetchMode(ctx, cfg, tls)) + } + if len(*mode) != 0 { + return errors.Trace(switchMode(ctx, cfg, tls, *mode)) + } + if len(*flagImportEngine) != 0 { + return errors.Trace(importEngine(ctx, cfg, tls, *flagImportEngine)) + } + if len(*flagCleanupEngine) != 0 { + return errors.Trace(cleanupEngine(ctx, cfg, tls, *flagCleanupEngine)) + } + + if len(*cpRemove) != 0 { + return errors.Trace(checkpointRemove(ctx, cfg, *cpRemove)) + } + if len(*cpErrIgnore) != 0 { + return errors.Trace(checkpointErrorIgnore(ctx, cfg, *cpErrIgnore)) + } + if len(*cpErrDestroy) != 0 { + return errors.Trace(checkpointErrorDestroy(ctx, cfg, tls, *cpErrDestroy)) + } + if len(*cpDump) != 0 { + return errors.Trace(checkpointDump(ctx, cfg, *cpDump)) + } + if *localStoringTables { + return errors.Trace(getLocalStoringTables(ctx, cfg)) + } + + fsUsage() + return nil +} + +func compactCluster(ctx context.Context, cfg *config.Config, tls *common.TLS) error { + return kv.ForAllStores( + ctx, + tls.WithHost(cfg.TiDB.PdAddr), + kv.StoreStateDisconnected, + func(c context.Context, store *kv.Store) error { + return kv.Compact(c, tls, store.Address, restore.FullLevelCompact) + }, + ) +} + +func switchMode(ctx context.Context, cfg *config.Config, tls *common.TLS, mode string) error { + var m import_sstpb.SwitchMode + switch mode { + case config.ImportMode: + m = import_sstpb.SwitchMode_Import + case config.NormalMode: + m = import_sstpb.SwitchMode_Normal + default: + return errors.Errorf("invalid mode %s, must use %s or %s", mode, config.ImportMode, config.NormalMode) + } + + return kv.ForAllStores( + ctx, + tls.WithHost(cfg.TiDB.PdAddr), + kv.StoreStateDisconnected, + func(c context.Context, store *kv.Store) error { + return kv.SwitchMode(c, tls, store.Address, m) + }, + ) +} + +func fetchMode(ctx context.Context, cfg *config.Config, tls *common.TLS) error { + return kv.ForAllStores( + ctx, + tls.WithHost(cfg.TiDB.PdAddr), + kv.StoreStateDisconnected, + func(c context.Context, store *kv.Store) error { + mode, err := kv.FetchMode(c, tls, store.Address) + if err != nil { + fmt.Fprintf(os.Stderr, "%-30s | Error: %v\n", store.Address, err) + } else { + fmt.Fprintf(os.Stderr, "%-30s | %s mode\n", store.Address, mode) + } + return nil + }, + ) +} + +func checkpointRemove(ctx context.Context, cfg *config.Config, tableName string) error { + cpdb, err := checkpoints.OpenCheckpointsDB(ctx, cfg) + if err != nil { + return errors.Trace(err) + } + defer cpdb.Close() + + return errors.Trace(cpdb.RemoveCheckpoint(ctx, tableName)) +} + +func checkpointErrorIgnore(ctx context.Context, cfg *config.Config, tableName string) error { + cpdb, err := checkpoints.OpenCheckpointsDB(ctx, cfg) + if err != nil { + return errors.Trace(err) + } + defer cpdb.Close() + + return errors.Trace(cpdb.IgnoreErrorCheckpoint(ctx, tableName)) +} + +func checkpointErrorDestroy(ctx context.Context, cfg *config.Config, tls *common.TLS, tableName string) error { + cpdb, err := checkpoints.OpenCheckpointsDB(ctx, cfg) + if err != nil { + return errors.Trace(err) + } + defer cpdb.Close() + + target, err := restore.NewTiDBManager(cfg.TiDB, tls) + if err != nil { + return errors.Trace(err) + } + defer target.Close() + + targetTables, err := cpdb.DestroyErrorCheckpoint(ctx, tableName) + if err != nil { + return errors.Trace(err) + } + + var lastErr error + + for _, table := range targetTables { + fmt.Fprintln(os.Stderr, "Dropping table:", table.TableName) + err := target.DropTable(ctx, table.TableName) + if err != nil { + fmt.Fprintln(os.Stderr, "* Encountered error while dropping table:", err) + lastErr = err + } + } + + if cfg.TikvImporter.Backend == "importer" { + importer, err := kv.NewImporter(ctx, tls, cfg.TikvImporter.Addr, cfg.TiDB.PdAddr) + if err != nil { + return errors.Trace(err) + } + defer importer.Close() + + for _, table := range targetTables { + for engineID := table.MinEngineID; engineID <= table.MaxEngineID; engineID++ { + fmt.Fprintln(os.Stderr, "Closing and cleaning up engine:", table.TableName, engineID) + closedEngine, err := importer.UnsafeCloseEngine(ctx, table.TableName, engineID) + if err != nil { + fmt.Fprintln(os.Stderr, "* Encountered error while closing engine:", err) + lastErr = err + } else { + closedEngine.Cleanup(ctx) + } + } + } + } + // For importer backend, engine was stored in importer's memory, we can retrieve it from alive importer process. + // But in local backend, if we want to use common API `UnsafeCloseEngine` and `Cleanup`, + // we need either lightning process alive or engine map persistent. + // both of them seems unnecessary if we only need to do is cleanup specify engine directory. + // so we didn't choose to use common API. + if cfg.TikvImporter.Backend == "local" { + for _, table := range targetTables { + for engineID := table.MinEngineID; engineID <= table.MaxEngineID; engineID++ { + fmt.Fprintln(os.Stderr, "Closing and cleaning up engine:", table.TableName, engineID) + _, eID := kv.MakeUUID(table.TableName, engineID) + file := kv.LocalFile{Uuid: eID} + err := file.Cleanup(cfg.TikvImporter.SortedKVDir) + if err != nil { + fmt.Fprintln(os.Stderr, "* Encountered error while cleanup engine:", err) + lastErr = err + } + } + } + } + + return errors.Trace(lastErr) +} + +func checkpointDump(ctx context.Context, cfg *config.Config, dumpFolder string) error { + cpdb, err := checkpoints.OpenCheckpointsDB(ctx, cfg) + if err != nil { + return errors.Trace(err) + } + defer cpdb.Close() + + if err := os.MkdirAll(dumpFolder, 0o755); err != nil { + return errors.Trace(err) + } + + tablesFileName := filepath.Join(dumpFolder, "tables.csv") + tablesFile, err := os.Create(tablesFileName) + if err != nil { + return errors.Annotatef(err, "failed to create %s", tablesFileName) + } + defer tablesFile.Close() + + enginesFileName := filepath.Join(dumpFolder, "engines.csv") + enginesFile, err := os.Create(tablesFileName) + if err != nil { + return errors.Annotatef(err, "failed to create %s", enginesFileName) + } + defer enginesFile.Close() + + chunksFileName := filepath.Join(dumpFolder, "chunks.csv") + chunksFile, err := os.Create(chunksFileName) + if err != nil { + return errors.Annotatef(err, "failed to create %s", chunksFileName) + } + defer chunksFile.Close() + + if err := cpdb.DumpTables(ctx, tablesFile); err != nil { + return errors.Trace(err) + } + if err := cpdb.DumpEngines(ctx, enginesFile); err != nil { + return errors.Trace(err) + } + if err := cpdb.DumpChunks(ctx, chunksFile); err != nil { + return errors.Trace(err) + } + return nil +} + +func getLocalStoringTables(ctx context.Context, cfg *config.Config) (err2 error) { + var tables []string + defer func() { + if err2 == nil { + if len(tables) == 0 { + fmt.Fprintln(os.Stderr, "No table has lost intermediate files according to given config") + } else { + fmt.Fprintln(os.Stderr, "These tables are missing intermediate files:", tables) + } + } + }() + + if cfg.TikvImporter.Backend != config.BackendLocal { + return nil + } + exist, err := checkpoints.IsCheckpointsDBExists(ctx, cfg) + if err != nil { + return errors.Trace(err) + } + if !exist { + return nil + } + cpdb, err := checkpoints.OpenCheckpointsDB(ctx, cfg) + if err != nil { + return errors.Trace(err) + } + defer cpdb.Close() + + tableWithEngine, err := cpdb.GetLocalStoringTables(ctx) + if err != nil { + return errors.Trace(err) + } + tables = make([]string, 0, len(tableWithEngine)) + for tableName := range tableWithEngine { + tables = append(tables, tableName) + } + + return nil +} + +func unsafeCloseEngine(ctx context.Context, importer kv.Backend, engine string) (*kv.ClosedEngine, error) { + if index := strings.LastIndexByte(engine, ':'); index >= 0 { + tableName := engine[:index] + engineID, err := strconv.Atoi(engine[index+1:]) + if err != nil { + return nil, errors.Trace(err) + } + ce, err := importer.UnsafeCloseEngine(ctx, tableName, int32(engineID)) + return ce, errors.Trace(err) + } + + engineUUID, err := uuid.Parse(engine) + if err != nil { + return nil, errors.Trace(err) + } + + ce, err := importer.UnsafeCloseEngineWithUUID(ctx, "", engineUUID) + return ce, errors.Trace(err) +} + +func importEngine(ctx context.Context, cfg *config.Config, tls *common.TLS, engine string) error { + importer, err := kv.NewImporter(ctx, tls, cfg.TikvImporter.Addr, cfg.TiDB.PdAddr) + if err != nil { + return errors.Trace(err) + } + + ce, err := unsafeCloseEngine(ctx, importer, engine) + if err != nil { + return errors.Trace(err) + } + + return errors.Trace(ce.Import(ctx)) +} + +func cleanupEngine(ctx context.Context, cfg *config.Config, tls *common.TLS, engine string) error { + importer, err := kv.NewImporter(ctx, tls, cfg.TikvImporter.Addr, cfg.TiDB.PdAddr) + if err != nil { + return errors.Trace(err) + } + + ce, err := unsafeCloseEngine(ctx, importer, engine) + if err != nil { + return errors.Trace(err) + } + + return errors.Trace(ce.Cleanup(ctx)) +} diff --git a/cmd/tidb-lightning-ctl/main_test.go b/cmd/tidb-lightning-ctl/main_test.go new file mode 100644 index 000000000..bef107137 --- /dev/null +++ b/cmd/tidb-lightning-ctl/main_test.go @@ -0,0 +1,47 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "os" + "strings" + "testing" +) + +func TestRunMain(t *testing.T) { + if _, isIntegrationTest := os.LookupEnv("INTEGRATION_TEST"); !isIntegrationTest { + // override exit to pass unit test. + exit = func(code int) {} + } + + var args []string + for _, arg := range os.Args { + switch { + case arg == "DEVEL": + case strings.HasPrefix(arg, "-test."): + default: + args = append(args, arg) + } + } + + waitCh := make(chan struct{}, 1) + + os.Args = args + go func() { + main() + close(waitCh) + }() + + <-waitCh +} diff --git a/cmd/tidb-lightning/main.go b/cmd/tidb-lightning/main.go new file mode 100644 index 000000000..f577e1109 --- /dev/null +++ b/cmd/tidb-lightning/main.go @@ -0,0 +1,113 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "fmt" + "os" + "os/signal" + "runtime/debug" + "syscall" + + "go.uber.org/zap" + + "github.com/pingcap/br/pkg/lightning" + "github.com/pingcap/br/pkg/lightning/config" + "github.com/pingcap/br/pkg/lightning/log" +) + +func main() { + globalCfg := config.Must(config.LoadGlobalConfig(os.Args[1:], nil)) + fmt.Fprintf(os.Stdout, "Verbose debug logs will be written to %s\n\n", globalCfg.App.Config.File) + + app := lightning.New(globalCfg) + + sc := make(chan os.Signal, 1) + signal.Notify(sc, + syscall.SIGHUP, + syscall.SIGINT, + syscall.SIGTERM, + syscall.SIGQUIT) + + go func() { + sig := <-sc + log.L().Info("got signal to exit", zap.Stringer("signal", sig)) + app.Stop() + }() + + logger := log.L() + + // Lightning allocates too many transient objects and heap size is small, + // so garbage collections happen too frequently and lots of time is spent in GC component. + // + // In a test of loading the table `order_line.csv` of 14k TPCC. + // The time need of `encode kv data and write` step reduce from 52m4s to 37m30s when change + // GOGC from 100 to 500, the total time needed reduce near 15m too. + // The cost of this is the memory of lightnin at runtime grow from about 200M to 700M, but it's acceptable. + // So we set the gc percentage as 500 default to reduce the GC frequency instead of 100. + // + // Local mode need much more memory than importer/tidb mode, if the gc percentage is too high, + // lightning memory usage will also be high. + if globalCfg.TikvImporter.Backend != config.BackendLocal { + gogc := os.Getenv("GOGC") + if gogc == "" { + old := debug.SetGCPercent(500) + log.L().Debug("set gc percentage", zap.Int("old", old), zap.Int("new", 500)) + } + } + + err := app.GoServe() + if err != nil { + logger.Error("failed to start HTTP server", zap.Error(err)) + fmt.Fprintln(os.Stderr, "failed to start HTTP server:", err) + return + } + + err = func() error { + if globalCfg.App.ServerMode { + return app.RunServer() + } else { + cfg := config.NewConfig() + if err := cfg.LoadFromGlobal(globalCfg); err != nil { + return err + } + return app.RunOnce(context.Background(), cfg, nil, nil) + } + }() + + if err != nil { + logger.Error("tidb lightning encountered error stack info", zap.Error(err)) + logger.Error("tidb lightning encountered error", log.ShortError(err)) + fmt.Fprintln(os.Stderr, "tidb lightning encountered error: ", err) + } else { + logger.Info("tidb lightning exit") + fmt.Fprintln(os.Stdout, "tidb lightning exit") + } + + // call Sync() with log to stdout may return error in some case, so just skip it + if globalCfg.App.File != "" { + syncErr := logger.Sync() + if syncErr != nil { + fmt.Fprintln(os.Stderr, "sync log failed", syncErr) + } + } + + if err != nil { + exit(1) + } +} + +// main_test.go override exit to pass unit test. +var exit = os.Exit diff --git a/cmd/tidb-lightning/main_test.go b/cmd/tidb-lightning/main_test.go new file mode 100644 index 000000000..26ee27d45 --- /dev/null +++ b/cmd/tidb-lightning/main_test.go @@ -0,0 +1,49 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +// Reference: https://dzone.com/articles/measuring-integration-test-coverage-rate-in-pouchc + +import ( + "os" + "strings" + "testing" +) + +func TestRunMain(t *testing.T) { + if _, isIntegrationTest := os.LookupEnv("INTEGRATION_TEST"); !isIntegrationTest { + // override exit to pass unit test. + exit = func(code int) {} + } + + var args []string + for _, arg := range os.Args { + switch { + case arg == "DEVEL": + case strings.HasPrefix(arg, "-test."): + default: + args = append(args, arg) + } + } + + waitCh := make(chan struct{}, 1) + + os.Args = args + go func() { + main() + close(waitCh) + }() + + <-waitCh +} diff --git a/docker/config/tikv.toml b/docker/config/tikv.toml index 6528e447f..2f92ae011 100644 --- a/docker/config/tikv.toml +++ b/docker/config/tikv.toml @@ -1,3 +1,6 @@ +[storage] +reserve-space = "1KB" + [raftstore] # true (default value) for high reliability, this can prevent data loss when power failure. sync-log = true diff --git a/go.mod1 b/go.mod1 index d2a99de97..05feb1979 100644 --- a/go.mod1 +++ b/go.mod1 @@ -4,15 +4,30 @@ go 1.13 require ( cloud.google.com/go/storage v1.6.0 + github.com/BurntSushi/toml v0.3.1 + github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/aws/aws-sdk-go v1.35.3 +<<<<<<< HEAD github.com/cheggaaa/pb/v3 v3.0.4 +======= + github.com/carlmjohnson/flagext v0.20.2 + github.com/cheggaaa/pb/v3 v3.0.5 + github.com/cheynewallace/tabby v1.1.0 + github.com/cockroachdb/pebble v0.0.0-20201023120638-f1224da22976 +>>>>>>> 8b9b626... Merge branch 'merging' github.com/coreos/go-semver v0.3.0 + github.com/docker/go-units v0.4.0 github.com/fsouza/fake-gcs-server v1.19.0 github.com/go-sql-driver/mysql v1.5.0 github.com/gogo/protobuf v1.3.1 github.com/golang/mock v1.4.4 github.com/google/btree v1.0.0 github.com/google/uuid v1.1.1 +<<<<<<< HEAD +======= + github.com/joho/sqltocsv v0.0.0-20190824231449-5650f27fd5b6 + github.com/opentracing/opentracing-go v1.1.0 +>>>>>>> 8b9b626... Merge branch 'merging' github.com/pingcap/check v0.0.0-20200212061837-5e12011dc712 github.com/pingcap/errors v0.11.5-0.20201126102027-b0a155152ca3 github.com/pingcap/failpoint v0.0.0-20200702092429-9f69995143ce @@ -23,18 +38,34 @@ require ( github.com/pingcap/tidb-tools v4.0.9-0.20201127090955-2707c97b3853+incompatible github.com/pingcap/tipb v0.0.0-20201209065231-aa39b1b86217 github.com/prometheus/client_golang v1.5.1 +<<<<<<< HEAD github.com/shirou/gopsutil v2.20.6+incompatible // indirect +======= + github.com/prometheus/client_model v0.2.0 + github.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0 +>>>>>>> 8b9b626... Merge branch 'merging' github.com/sirupsen/logrus v1.6.0 github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 github.com/tikv/pd v1.1.0-beta.0.20201125070607-d4b90eee0c70 + github.com/xitongsys/parquet-go v1.5.5-0.20201110004701-b09c49d6d457 + github.com/xitongsys/parquet-go-source v0.0.0-20200817004010-026bad9b25d0 go.etcd.io/etcd v0.5.0-alpha.5.0.20200824191128-ae9734ed278b + go.uber.org/atomic v1.7.0 go.uber.org/multierr v1.6.0 go.uber.org/zap v1.16.0 + golang.org/x/net v0.0.0-20200904194848-62affa334b73 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 + golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f + golang.org/x/text v0.3.4 google.golang.org/api v0.22.0 google.golang.org/grpc v1.27.1 +<<<<<<< HEAD +======= + modernc.org/mathutil v1.1.1 + sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0 +>>>>>>> 8b9b626... Merge branch 'merging' ) replace cloud.google.com/go/storage => github.com/3pointer/google-cloud-go/storage v1.6.1-0.20210108125931-b59bfa0720b2 diff --git a/go.sum1 b/go.sum1 index 428af1938..69dc8be69 100644 --- a/go.sum1 +++ b/go.sum1 @@ -144,6 +144,12 @@ github.com/cheggaaa/pb v2.0.7+incompatible h1:gLKifR1UkZ/kLkda5gC0K6c8g+jU2sINPt github.com/cheggaaa/pb v2.0.7+incompatible/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/cheggaaa/pb/v3 v3.0.4 h1:QZEPYOj2ix6d5oEg63fbHmpolrnNiwjUsk+h74Yt4bM= github.com/cheggaaa/pb/v3 v3.0.4/go.mod h1:7rgWxLrAUcFMkvJuv09+DYi7mMUYi8nO9iOWcvGJPfw= +<<<<<<< HEAD +======= +github.com/cheggaaa/pb/v3 v3.0.5 h1:lmZOti7CraK9RSjzExsY53+WWfub9Qv13B5m4ptEoPE= +github.com/cheggaaa/pb/v3 v3.0.5/go.mod h1:X1L61/+36nz9bjIsrDU52qHKOQukUQe2Ge+YvGuquCw= +github.com/cheynewallace/tabby v1.1.0 h1:XtG/ZanoIvNZHfe0cClhWLzD/16GGF9UD7mMdWwYnCQ= +>>>>>>> 8b9b626... Merge branch 'merging' github.com/cheynewallace/tabby v1.1.0/go.mod h1:Pba/6cUL8uYqvOc9RkyvFbHGrQ9wShyrn6/S/1OYVys= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20171208011716-f6d7a1f6fbf3/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -158,8 +164,15 @@ github.com/cockroachdb/errors v1.2.4 h1:Lap807SXTH5tri2TivECb/4abUkMZC9zRoLarvcK github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f h1:o/kfcElHqOiXqcou5a3rIlMc7oJbMQkeLk0VQJ7zgqY= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +<<<<<<< HEAD github.com/cockroachdb/pebble v0.0.0-20200617141519-3b241b76ed3b h1:YHjo2xnqFCeFa0CdxEccHfUY1/DnXPAZdZt0+s/Mvdg= github.com/cockroachdb/pebble v0.0.0-20200617141519-3b241b76ed3b/go.mod h1:crLnbSFbwAcQNs9FPfI1avHb5BqVgqZcr4r+IzpJ5FM= +======= +github.com/cockroachdb/pebble v0.0.0-20201023120638-f1224da22976 h1:gGjhleKSWZCZFrhQSesjg8spRD+/p8vjwdNEGUv8Ovg= +github.com/cockroachdb/pebble v0.0.0-20201023120638-f1224da22976/go.mod h1:BbtTitvfmE0eZNcncJgJw5BlQhskTzgZgoISnY+8s6k= +github.com/cockroachdb/redact v0.0.0-20200622112456-cd282804bbd3 h1:2+dpIJzYMSbLi0587YXpi8tOJT52qCOI/1I0UNThc/I= +github.com/cockroachdb/redact v0.0.0-20200622112456-cd282804bbd3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +>>>>>>> 8b9b626... Merge branch 'merging' github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codahale/hdrhistogram v0.9.0 h1:9GjrtRI+mLEFPtTfR/AZhcxp+Ii8NZYWq5104FbZQY0= github.com/codahale/hdrhistogram v0.9.0/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= @@ -920,8 +933,13 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xinsnake/go-http-digest-auth-client v0.4.0/go.mod h1:QK1t1v7ylyGb363vGWu+6Irh7gyFj+N7+UZzM0L6g8I= github.com/xitongsys/parquet-go v1.5.1/go.mod h1:xUxwM8ELydxh4edHGegYq1pA8NnMKDx0K/GyB0o2bww= +<<<<<<< HEAD github.com/xitongsys/parquet-go v1.5.4 h1:zsdMNZcCv9t3YnlOfysMI78vBw+cN65jQznQlizVtqE= github.com/xitongsys/parquet-go v1.5.4/go.mod h1:pheqtXeHQFzxJk45lRQ0UIGIivKnLXvialZSFWs81A8= +======= +github.com/xitongsys/parquet-go v1.5.5-0.20201110004701-b09c49d6d457 h1:tBbuFCtyJNKT+BFAv6qjvTFpVdy97IYNaBwGUXifIUs= +github.com/xitongsys/parquet-go v1.5.5-0.20201110004701-b09c49d6d457/go.mod h1:pheqtXeHQFzxJk45lRQ0UIGIivKnLXvialZSFWs81A8= +>>>>>>> 8b9b626... Merge branch 'merging' github.com/xitongsys/parquet-go-source v0.0.0-20190524061010-2b72cbee77d5/go.mod h1:xxCx7Wpym/3QCo6JhujJX51dzSXrwmb0oH6FQb39SEA= github.com/xitongsys/parquet-go-source v0.0.0-20200817004010-026bad9b25d0 h1:a742S4V5A15F93smuVxA60LQWsrCnN8bKeWDBARU1/k= github.com/xitongsys/parquet-go-source v0.0.0-20200817004010-026bad9b25d0/go.mod h1:HYhIKsdns7xz80OgkbgJYrtQY7FjHWHKH6cvN7+czGE= @@ -1032,6 +1050,7 @@ golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367 h1:0IiAsCRByjO2QjX7ZPkw5oU9x+n1YqRL802rjC0c3Aw= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= @@ -1140,6 +1159,8 @@ golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200819171115-d785dc25833f h1:KJuwZVtZBVzDmEDtB2zro9CXkD9O0dpCv4o2LHbQIAw= golang.org/x/sys v0.0.0-20200819171115-d785dc25833f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f h1:Fqb3ao1hUmOR3GkUOg/Y+BadLwykBIzs5q8Ez2SbHyc= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/metrics/alertmanager/lightning.rules.yml b/metrics/alertmanager/lightning.rules.yml new file mode 100644 index 000000000..44782cf34 --- /dev/null +++ b/metrics/alertmanager/lightning.rules.yml @@ -0,0 +1,14 @@ +groups: +- name: alert.rules + rules: + - alert: Lightning_import_failure_tables_count + expr: sum ( lightning_tables{result="failure"} ) > 0 + for: 1m + labels: + env: ENV_LABELS_ENV + level: emergency + expr: sum ( lightning_tables{result="failure"} ) > 0 + annotations: + description: 'cluster: ENV_LABELS_ENV, instance: {{ $labels.instance }}, values:{{ $value }}' + value: '{{ $value }}' + summary: Lightning failed to import a table diff --git a/metrics/grafana/lightning.json b/metrics/grafana/lightning.json new file mode 100644 index 000000000..86e2d37df --- /dev/null +++ b/metrics/grafana/lightning.json @@ -0,0 +1,1603 @@ +{ + "__inputs": [ + { + "name": "DS_LIGHTNING", + "label": "lightning", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "4.6.3" + }, + { + "type": "panel", + "id": "graph", + "name": "Graph", + "version": "" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "singlestat", + "name": "Singlestat", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "hideControls": false, + "id": null, + "links": [], + "refresh": false, + "rows": [ + { + "collapse": false, + "height": "250px", + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_LIGHTNING}", + "fill": 1, + "id": 1, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "span": 6, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(tikv_import_write_chunk_bytes_sum{tidb_cluster=\"$tidb_cluster\"}[1m])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "write from lightning", + "refId": "B" + }, + { + "expr": "sum(rate(tikv_import_upload_chunk_bytes_sum{tidb_cluster=\"$tidb_cluster\"}[1m]))", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "upload to tikv", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Import speed", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "Bps", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_LIGHTNING}", + "fill": 1, + "id": 10, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "span": 6, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "1/rate(lightning_chunks{tidb_cluster=\"$tidb_cluster\", state=\"finished\"}[1m]) ", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Chunk process duration", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": "", + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + } + ], + "repeat": null, + "repeatIteration": null, + "repeatRowId": null, + "showTitle": false, + "title": "Import Speed", + "titleSize": "h6" + }, + { + "collapse": false, + "height": 250, + "panels": [ + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#d44a3a", + "rgba(237, 129, 40, 0.89)", + "#299c46" + ], + "datasource": "${DS_LIGHTNING}", + "decimals": null, + "format": "percentunit", + "gauge": { + "maxValue": 1, + "minValue": 0, + "show": true, + "thresholdLabels": false, + "thresholdMarkers": false + }, + "id": 4, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "span": 3, + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": true, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "lightning_chunks{tidb_cluster=\"$tidb_cluster\", state=\"finished\"} / ignoring(state) lightning_chunks{tidb_cluster=\"$tidb_cluster\", state=\"estimated\"}", + "format": "time_series", + "instant": false, + "intervalFactor": 2, + "refId": "A" + } + ], + "thresholds": "0,0", + "title": "Import Progress", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": false, + "colors": [ + "#d44a3a", + "rgba(237, 129, 40, 0.89)", + "#299c46" + ], + "datasource": "${DS_LIGHTNING}", + "format": "percentunit", + "gauge": { + "maxValue": 1, + "minValue": 0, + "show": true, + "thresholdLabels": false, + "thresholdMarkers": false + }, + "id": 12, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "50%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "span": 3, + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "lightning_tables{tidb_cluster=\"$tidb_cluster\", state=\"completed\"} / ignoring(state) lightning_tables{tidb_cluster=\"$tidb_cluster\", state=\"pending\"}", + "format": "time_series", + "instant": false, + "intervalFactor": 1, + "refId": "A" + } + ], + "thresholds": "0,0", + "title": "Checksum progress", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "avg" + }, + { + "columns": [ + { + "text": "Max", + "value": "max" + } + ], + "datasource": "${DS_LIGHTNING}", + "fontSize": "100%", + "id": 8, + "links": [], + "pageSize": null, + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "span": 6, + "styles": [ + { + "alias": "Time", + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "pattern": "Time", + "type": "date" + }, + { + "alias": "Step", + "colorMode": "cell", + "colors": [ + "rgba(50, 172, 45, 0.97)", + "rgba(237, 129, 40, 0.89)", + "rgba(245, 54, 54, 0.9)" + ], + "decimals": 0, + "pattern": "Metric", + "thresholds": [ + "1", + "2" + ], + "type": "number", + "unit": "none" + }, + { + "alias": "Tables", + "colorMode": "cell", + "colors": [ + "rgba(50, 172, 45, 0.97)", + "rgba(237, 129, 40, 0.89)", + "rgba(245, 54, 54, 0.9)" + ], + "dateFormat": "YYYY-MM-DD HH:mm:ss", + "decimals": 0, + "pattern": "Max", + "thresholds": [ + "0", + "0" + ], + "type": "number", + "unit": "none" + } + ], + "targets": [ + { + "expr": "lightning_tables{tidb_cluster=\"$tidb_cluster\", result=\"failure\"}", + "format": "time_series", + "instant": false, + "intervalFactor": 2, + "legendFormat": "{{state}}", + "refId": "A" + } + ], + "title": "Failures", + "transform": "timeseries_aggregations", + "type": "table" + } + ], + "repeat": null, + "repeatIteration": null, + "repeatRowId": null, + "showTitle": false, + "title": "Import Progress", + "titleSize": "h6" + }, + { + "collapse": false, + "height": 250, + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_LIGHTNING}", + "fill": 1, + "id": 7, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "span": 4, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "process_resident_memory_bytes{tidb_cluster=\"$tidb_cluster\", job=\"tikv-importer\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "importer RSS", + "refId": "A" + }, + { + "expr": "go_memstats_heap_inuse_bytes{tidb_cluster=\"$tidb_cluster\", job=\"lightning\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "lightning heap", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Memory usage", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_LIGHTNING}", + "fill": 1, + "id": 9, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": false, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "span": 4, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "go_goroutines{tidb_cluster=\"$tidb_cluster\", job=\"lightning\"}", + "format": "time_series", + "instant": false, + "intervalFactor": 2, + "legendFormat": "", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Number of Lightning Goroutines", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 0, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_LIGHTNING}", + "fill": 1, + "id": 3, + "legend": { + "alignAsTable": false, + "avg": true, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "span": 4, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(process_cpu_seconds_total{tidb_cluster=\"$tidb_cluster\", job=\"lightning\"}[30s])*100", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Lightning", + "refId": "A" + }, + { + "expr": "rate(process_cpu_seconds_total{tidb_cluster=\"$tidb_cluster\", job=\"tikv-importer\"}[30s])*100", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Importer", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "CPU%", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "percent", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + } + ], + "repeat": null, + "repeatIteration": null, + "repeatRowId": null, + "showTitle": false, + "title": "Resource usage", + "titleSize": "h6" + }, + { + "collapse": false, + "height": 250, + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_LIGHTNING}", + "fill": 1, + "id": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "span": 5, + "stack": false, + "steppedLine": true, + "targets": [ + { + "expr": "lightning_idle_workers{tidb_cluster=\"$tidb_cluster\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{name}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Idle workers", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 0, + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_LIGHTNING}", + "fill": 1, + "id": 6, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "span": 5, + "stack": false, + "steppedLine": true, + "targets": [ + { + "expr": "lightning_kv_encoder{tidb_cluster=\"$tidb_cluster\", type=\"open\"} - ignoring(type) lightning_kv_encoder{tidb_cluster=\"$tidb_cluster\", type=\"closed\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "KV Encoder", + "refId": "A" + }, + { + "expr": "lightning_importer_engine{tidb_cluster=\"$tidb_cluster\", type=\"open\"} - ignoring(type) lightning_importer_engine{tidb_cluster=\"$tidb_cluster\", type=\"closed\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Importer Engines (via Lightning)", + "refId": "B" + }, + { + "expr": "tikv_import_rpc_duration_count{tidb_cluster=\"$tidb_cluster\", request=\"open_engine\",result=\"ok\"} - ignoring(request) tikv_import_rpc_duration_count{tidb_cluster=\"$tidb_cluster\", request=\"close_engine\",result=\"ok\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Importer Engines (via Importer)", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "External resources", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 0, + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + }, + { + "columns": [ + { + "text": "Current", + "value": "current" + } + ], + "datasource": "${DS_LIGHTNING}", + "fontSize": "100%", + "id": 21, + "links": [], + "pageSize": null, + "scroll": true, + "showHeader": true, + "sort": { + "col": 0, + "desc": true + }, + "span": 2, + "styles": [ + { + "alias": "TiKV", + "pattern": "Metric" + }, + { + "alias": "", + "colorMode": "cell", + "colors": [ + "#E0B400", + "rgba(237, 129, 40, 0.89)", + "rgba(50, 172, 45, 0.97)" + ], + "decimals": 2, + "link": false, + "mappingType": 2, + "pattern": "Current", + "rangeMaps": [ + { + "from": "0", + "text": "Import", + "to": "0" + }, + { + "from": "1", + "text": "Normal", + "to": "Infinity" + } + ], + "thresholds": [ + "1", + "1" + ], + "type": "string", + "unit": "short" + } + ], + "targets": [ + { + "expr": "min(tikv_config_rocksdb{tidb_cluster=\"$tidb_cluster\", name=\"hard_pending_compaction_bytes_limit\"}) by (instance)", + "format": "time_series", + "instant": false, + "intervalFactor": 2, + "legendFormat": "{{instance}}", + "refId": "A" + } + ], + "title": "Import/Normal mode", + "transform": "timeseries_aggregations", + "type": "table" + } + ], + "repeat": null, + "repeatIteration": null, + "repeatRowId": null, + "showTitle": false, + "title": "Resource usage", + "titleSize": "h6" + }, + { + "collapse": false, + "height": 223, + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_LIGHTNING}", + "fill": 1, + "id": 13, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "span": 6, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(lightning_chunk_parser_read_block_seconds_sum{tidb_cluster=\"$tidb_cluster\"}[30s]) / rate(lightning_chunk_parser_read_block_seconds_count{tidb_cluster=\"$tidb_cluster\"}[30s])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "read block", + "refId": "A" + }, + { + "expr": "rate(lightning_apply_worker_seconds_sum{tidb_cluster=\"$tidb_cluster\", name = \"io\"}[30s]) /rate(lightning_apply_worker_seconds_count{tidb_cluster=\"$tidb_cluster\", name = \"io\"}[30s]) ", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "apply worker", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Chunk parser read block duration", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_LIGHTNING}", + "fill": 1, + "id": 15, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "span": 6, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(lightning_row_encode_seconds_sum{tidb_cluster=\"$tidb_cluster\"}[30s]) / rate(lightning_row_encode_seconds_count{tidb_cluster=\"$tidb_cluster\"}[30s])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "row encode", + "refId": "A" + }, + { + "expr": "rate(lightning_block_deliver_seconds_sum{tidb_cluster=\"$tidb_cluster\"}[30s]) / rate(lightning_block_deliver_seconds_count{tidb_cluster=\"$tidb_cluster\"}[30s])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "block deliver", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "SQL process duration", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 2, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + } + ], + "repeat": null, + "repeatIteration": null, + "repeatRowId": null, + "showTitle": false, + "title": "Dashboard Row", + "titleSize": "h6" + }, + { + "collapse": false, + "height": 235, + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_LIGHTNING}", + "fill": 1, + "id": 16, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "span": 6, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(lightning_block_deliver_bytes_sum{tidb_cluster=\"$tidb_cluster\"}[30s])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{kind}} deliver rate", + "refId": "B" + }, + { + "expr": "sum(rate(lightning_block_deliver_bytes_sum{tidb_cluster=\"$tidb_cluster\"}[30s]))", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "total deliver rate", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "SQL process rate", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "Bps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_LIGHTNING}", + "fill": 1, + "id": 17, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "span": 6, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "lightning_row_read_bytes_sum{tidb_cluster=\"$tidb_cluster\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "parser read size", + "refId": "A" + }, + { + "expr": "lightning_block_deliver_bytes_sum{tidb_cluster=\"$tidb_cluster\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{kind}} deliver size", + "refId": "B" + }, + { + "expr": "pd_cluster_status{tidb_cluster=\"$tidb_cluster\", type=\"storage_size\"} / ignoring(type) pd_config_status{tidb_cluster=\"$tidb_cluster\", type=\"max_replicas\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "storage_size / replicas", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Total bytes", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "decbytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + } + ], + "repeat": null, + "repeatIteration": null, + "repeatRowId": null, + "showTitle": false, + "title": "Dashboard Row", + "titleSize": "h6" + }, + { + "collapse": false, + "height": 243, + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_LIGHTNING}", + "fill": 1, + "id": 18, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "span": 6, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(tikv_import_range_delivery_duration_sum{tidb_cluster=\"$tidb_cluster\"}[30s]) / rate(tikv_import_range_delivery_duration_count{tidb_cluster=\"$tidb_cluster\"}[30s])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "range deliver", + "refId": "A" + }, + { + "expr": "rate(tikv_import_sst_delivery_duration_sum{tidb_cluster=\"$tidb_cluster\"}[30s]) / rate(tikv_import_sst_delivery_duration_count{tidb_cluster=\"$tidb_cluster\"}[30s])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "SST file deliver", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Deliver duration", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_LIGHTNING}", + "fill": 1, + "id": 19, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "SST size", + "yaxis": 2 + } + ], + "spaceLength": 10, + "span": 6, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(tikv_import_split_sst_duration_sum{tidb_cluster=\"$tidb_cluster\"}[30s]) / rate(tikv_import_split_sst_duration_count{tidb_cluster=\"$tidb_cluster\"}[30s])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "Split SST", + "refId": "C" + }, + { + "expr": "rate(tikv_import_sst_upload_duration_sum{tidb_cluster=\"$tidb_cluster\"}[30s]) / rate(tikv_import_sst_upload_duration_count{tidb_cluster=\"$tidb_cluster\"}[30s])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "SST upload", + "refId": "D" + }, + { + "expr": "rate(tikv_import_sst_ingest_duration_sum{tidb_cluster=\"$tidb_cluster\"}[30s]) / rate(tikv_import_sst_ingest_duration_count{tidb_cluster=\"$tidb_cluster\"}[30s])", + "format": "time_series", + "instant": false, + "intervalFactor": 2, + "legendFormat": "SST ingest", + "refId": "E" + }, + { + "expr": "rate(tikv_import_sst_chunk_bytes_sum{tidb_cluster=\"$tidb_cluster\"}[30s])", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "SST size", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "SST process duration", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "decbytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ] + } + ], + "repeat": null, + "repeatIteration": null, + "repeatRowId": null, + "showTitle": false, + "title": "Dashboard Row", + "titleSize": "h6" + } + ], + "schemaVersion": 14, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": null, + "current": { + }, + "datasource": "${DS_TEST-CLUSTER}", + "hide": 2, + "includeAll": false, + "label": "tidb_cluster", + "multi": false, + "name": "tidb_cluster", + "options": [ + + ], + "query": "label_values(lightning_chunks, tidb_cluster)", + "refresh": 2, + "regex": "", + "sort": 1, + "tagValuesQuery": "", + "tags": [ + + ], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "browser", + "title": "Test-Cluster-Lightning", + "version": 4 +} diff --git a/pkg/httputil/http.go b/pkg/httputil/http.go new file mode 100644 index 000000000..5556c60b2 --- /dev/null +++ b/pkg/httputil/http.go @@ -0,0 +1,22 @@ +// Copyright 2021 PingCAP, Inc. Licensed under Apache-2.0. + +package httputil + +import ( + "crypto/tls" + "net/http" + "time" +) + +// NewClient returns an HTTP(s) client. +func NewClient(tlsConf *tls.Config) *http.Client { + // defaultTimeout for non-context requests. + const defaultTimeout = 30 * time.Second + cli := &http.Client{Timeout: defaultTimeout} + if tlsConf != nil { + transport := http.DefaultTransport.(*http.Transport).Clone() + transport.TLSClientConfig = tlsConf + cli.Transport = transport + } + return cli +} diff --git a/pkg/lightning/backend/allocator.go b/pkg/lightning/backend/allocator.go new file mode 100644 index 000000000..80ceb56da --- /dev/null +++ b/pkg/lightning/backend/allocator.go @@ -0,0 +1,61 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package backend + +import ( + "sync/atomic" + + "github.com/pingcap/tidb/meta/autoid" +) + +// panickingAllocator is an ID allocator which panics on all operations except Rebase +type panickingAllocator struct { + autoid.Allocator + base *int64 + ty autoid.AllocatorType +} + +// NewPanickingAllocator creates a PanickingAllocator shared by all allocation types. +func NewPanickingAllocators(base int64) autoid.Allocators { + sharedBase := &base + return autoid.NewAllocators( + &panickingAllocator{base: sharedBase, ty: autoid.RowIDAllocType}, + &panickingAllocator{base: sharedBase, ty: autoid.AutoIncrementType}, + &panickingAllocator{base: sharedBase, ty: autoid.AutoRandomType}, + ) +} + +// Rebase implements the autoid.Allocator interface +func (alloc *panickingAllocator) Rebase(tableID, newBase int64, allocIDs bool) error { + // CAS + for { + oldBase := atomic.LoadInt64(alloc.base) + if newBase <= oldBase { + break + } + if atomic.CompareAndSwapInt64(alloc.base, oldBase, newBase) { + break + } + } + return nil +} + +// Base implements the autoid.Allocator interface +func (alloc *panickingAllocator) Base() int64 { + return atomic.LoadInt64(alloc.base) +} + +func (alloc *panickingAllocator) GetType() autoid.AllocatorType { + return alloc.ty +} diff --git a/pkg/lightning/backend/backend.go b/pkg/lightning/backend/backend.go new file mode 100644 index 000000000..22300462c --- /dev/null +++ b/pkg/lightning/backend/backend.go @@ -0,0 +1,490 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package backend + +import ( + "context" + "fmt" + "sort" + "time" + + "github.com/google/uuid" + "github.com/pingcap/errors" + "github.com/pingcap/failpoint" + "github.com/pingcap/parser/model" + "github.com/pingcap/tidb/store/tikv/oracle" + "github.com/pingcap/tidb/table" + "github.com/pingcap/tidb/types" + "go.uber.org/zap" + + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/log" + "github.com/pingcap/br/pkg/lightning/metric" + "github.com/pingcap/br/pkg/lightning/verification" +) + +const ( + maxRetryTimes = 3 // tikv-importer has done retry internally. so we don't retry many times. +) + +/* + +Usual workflow: + +1. Create a `Backend` for the whole process. + +2. For each table, + + i. Split into multiple "batches" consisting of data files with roughly equal total size. + + ii. For each batch, + + a. Create an `OpenedEngine` via `backend.OpenEngine()` + + b. For each chunk, deliver data into the engine via `engine.WriteRows()` + + c. When all chunks are written, obtain a `ClosedEngine` via `engine.Close()` + + d. Import data via `engine.Import()` + + e. Cleanup via `engine.Cleanup()` + +3. Close the connection via `backend.Close()` + +*/ + +func makeTag(tableName string, engineID int32) string { + return fmt.Sprintf("%s:%d", tableName, engineID) +} + +func makeLogger(tag string, engineUUID uuid.UUID) log.Logger { + return log.With( + zap.String("engineTag", tag), + zap.Stringer("engineUUID", engineUUID), + ) +} + +func MakeUUID(tableName string, engineID int32) (string, uuid.UUID) { + tag := makeTag(tableName, engineID) + engineUUID := uuid.NewSHA1(engineNamespace, []byte(tag)) + return tag, engineUUID +} + +var engineNamespace = uuid.MustParse("d68d6abe-c59e-45d6-ade8-e2b0ceb7bedf") + +type EngineFileSize struct { + // UUID is the engine's UUID. + UUID uuid.UUID + // DiskSize is the estimated total file size on disk right now. + DiskSize int64 + // MemSize is the total memory size used by the engine. This is the + // estimated additional size saved onto disk after calling Flush(). + MemSize int64 + // IsImporting indicates whether the engine performing Import(). + IsImporting bool +} + +// AbstractBackend is the abstract interface behind Backend. +// Implementations of this interface must be goroutine safe: you can share an +// instance and execute any method anywhere. +type AbstractBackend interface { + // Close the connection to the backend. + Close() + + // MakeEmptyRows creates an empty collection of encoded rows. + MakeEmptyRows() Rows + + // RetryImportDelay returns the duration to sleep when retrying an import + RetryImportDelay() time.Duration + + // ShouldPostProcess returns whether KV-specific post-processing should be + // performed for this backend. Post-processing includes checksum and analyze. + ShouldPostProcess() bool + + // NewEncoder creates an encoder of a TiDB table. + NewEncoder(tbl table.Table, options *SessionOptions) (Encoder, error) + + OpenEngine(ctx context.Context, engineUUID uuid.UUID) error + + CloseEngine(ctx context.Context, engineUUID uuid.UUID) error + + ImportEngine(ctx context.Context, engineUUID uuid.UUID) error + + CleanupEngine(ctx context.Context, engineUUID uuid.UUID) error + + // CheckRequirements performs the check whether the backend satisfies the + // version requirements + CheckRequirements(ctx context.Context) error + + // FetchRemoteTableModels obtains the models of all tables given the schema + // name. The returned table info does not need to be precise if the encoder, + // is not requiring them, but must at least fill in the following fields for + // TablesFromMeta to succeed: + // - Name + // - State (must be model.StatePublic) + // - ID + // - Columns + // * Name + // * State (must be model.StatePublic) + // * Offset (must be 0, 1, 2, ...) + // - PKIsHandle (true = do not generate _tidb_rowid) + FetchRemoteTableModels(ctx context.Context, schemaName string) ([]*model.TableInfo, error) + + // FlushEngine ensures all KV pairs written to an open engine has been + // synchronized, such that kill-9'ing Lightning afterwards and resuming from + // checkpoint can recover the exact same content. + // + // This method is only relevant for local backend, and is no-op for all + // other backends. + FlushEngine(ctx context.Context, engineUUID uuid.UUID) error + + // FlushAllEngines performs FlushEngine on all opened engines. This is a + // very expensive operation and should only be used in some rare situation + // (e.g. preparing to resolve a disk quota violation). + FlushAllEngines(ctx context.Context) error + + // EngineFileSizes obtains the size occupied locally of all engines managed + // by this backend. This method is used to compute disk quota. + // It can return nil if the content are all stored remotely. + EngineFileSizes() []EngineFileSize + + // ResetEngine clears all written KV pairs in this opened engine. + ResetEngine(ctx context.Context, engineUUID uuid.UUID) error + + // LocalWriter obtains a thread-local EngineWriter for writing rows into the given engine. + LocalWriter(ctx context.Context, engineUUID uuid.UUID, maxCacheSize int64) (EngineWriter, error) +} + +func fetchRemoteTableModelsFromTLS(ctx context.Context, tls *common.TLS, schema string) ([]*model.TableInfo, error) { + var tables []*model.TableInfo + err := tls.GetJSON(ctx, "/schema/"+schema, &tables) + if err != nil { + return nil, errors.Annotatef(err, "cannot read schema '%s' from remote", schema) + } + return tables, nil +} + +// Backend is the delivery target for Lightning +type Backend struct { + abstract AbstractBackend +} + +type engine struct { + backend AbstractBackend + logger log.Logger + uuid uuid.UUID +} + +// OpenedEngine is an opened engine, allowing data to be written via WriteRows. +// This type is goroutine safe: you can share an instance and execute any method +// anywhere. +type OpenedEngine struct { + engine + tableName string + ts uint64 +} + +// // import_ the data written to the engine into the target. +// import_(ctx context.Context) error + +// // cleanup deletes the imported data. +// cleanup(ctx context.Context) error + +// ClosedEngine represents a closed engine, allowing ingestion into the target. +// This type is goroutine safe: you can share an instance and execute any method +// anywhere. +type ClosedEngine struct { + engine +} + +type LocalEngineWriter struct { + writer EngineWriter + tableName string + ts uint64 +} + +func MakeBackend(ab AbstractBackend) Backend { + return Backend{abstract: ab} +} + +func (be Backend) Close() { + be.abstract.Close() +} + +func (be Backend) MakeEmptyRows() Rows { + return be.abstract.MakeEmptyRows() +} + +func (be Backend) NewEncoder(tbl table.Table, options *SessionOptions) (Encoder, error) { + return be.abstract.NewEncoder(tbl, options) +} + +func (be Backend) ShouldPostProcess() bool { + return be.abstract.ShouldPostProcess() +} + +func (be Backend) CheckRequirements(ctx context.Context) error { + return be.abstract.CheckRequirements(ctx) +} + +func (be Backend) FetchRemoteTableModels(ctx context.Context, schemaName string) ([]*model.TableInfo, error) { + return be.abstract.FetchRemoteTableModels(ctx, schemaName) +} + +func (be Backend) FlushAll(ctx context.Context) error { + return be.abstract.FlushAllEngines(ctx) +} + +// CheckDiskQuota verifies if the total engine file size is below the given +// quota. If the quota is exceeded, this method returns an array of engines, +// which after importing can decrease the total size below quota. +func (be Backend) CheckDiskQuota(quota int64) ( + largeEngines []uuid.UUID, + inProgressLargeEngines int, + totalDiskSize int64, + totalMemSize int64, +) { + sizes := be.abstract.EngineFileSizes() + sort.Slice(sizes, func(i, j int) bool { + a, b := &sizes[i], &sizes[j] + if a.IsImporting != b.IsImporting { + return a.IsImporting + } + return a.DiskSize+a.MemSize < b.DiskSize+b.MemSize + }) + for _, size := range sizes { + totalDiskSize += size.DiskSize + totalMemSize += size.MemSize + if totalDiskSize+totalMemSize > quota { + if size.IsImporting { + inProgressLargeEngines++ + } else { + largeEngines = append(largeEngines, size.UUID) + } + } + } + return +} + +// UnsafeImportAndReset forces the backend to import the content of an engine +// into the target and then reset the engine to empty. This method will not +// close the engine. Make sure the engine is flushed manually before calling +// this method. +func (be Backend) UnsafeImportAndReset(ctx context.Context, engineUUID uuid.UUID) error { + // DO NOT call be.abstract.CloseEngine()! The engine should still be writable after + // calling UnsafeImportAndReset(). + closedEngine := ClosedEngine{ + engine: engine{ + backend: be.abstract, + logger: makeLogger("", engineUUID), + uuid: engineUUID, + }, + } + if err := closedEngine.Import(ctx); err != nil { + return err + } + return be.abstract.ResetEngine(ctx, engineUUID) +} + +// OpenEngine opens an engine with the given table name and engine ID. +func (be Backend) OpenEngine(ctx context.Context, tableName string, engineID int32) (*OpenedEngine, error) { + tag, engineUUID := MakeUUID(tableName, engineID) + logger := makeLogger(tag, engineUUID) + + if err := be.abstract.OpenEngine(ctx, engineUUID); err != nil { + return nil, err + } + + openCounter := metric.ImporterEngineCounter.WithLabelValues("open") + openCounter.Inc() + + logger.Info("open engine") + + failpoint.Inject("FailIfEngineCountExceeds", func(val failpoint.Value) { + closedCounter := metric.ImporterEngineCounter.WithLabelValues("closed") + openCount := metric.ReadCounter(openCounter) + closedCount := metric.ReadCounter(closedCounter) + if injectValue := val.(int); openCount-closedCount > float64(injectValue) { + panic(fmt.Sprintf("forcing failure due to FailIfEngineCountExceeds: %v - %v >= %d", openCount, closedCount, injectValue)) + } + }) + + return &OpenedEngine{ + engine: engine{ + backend: be.abstract, + logger: logger, + uuid: engineUUID, + }, + tableName: tableName, + ts: oracle.ComposeTS(time.Now().Unix()*1000, 0), + }, nil +} + +// Close the opened engine to prepare it for importing. +func (engine *OpenedEngine) Close(ctx context.Context) (*ClosedEngine, error) { + closedEngine, err := engine.unsafeClose(ctx) + if err == nil { + metric.ImporterEngineCounter.WithLabelValues("closed").Inc() + } + return closedEngine, err +} + +// Flush current written data for local backend +func (engine *OpenedEngine) Flush(ctx context.Context) error { + return engine.backend.FlushEngine(ctx, engine.uuid) +} + +// WriteRows writes a collection of encoded rows into the engine. +func (engine *OpenedEngine) WriteRows(ctx context.Context, columnNames []string, rows Rows) error { + writer, err := engine.backend.LocalWriter(ctx, engine.uuid, LocalMemoryTableSize) + if err != nil { + return err + } + if err = writer.AppendRows(ctx, engine.tableName, columnNames, engine.ts, rows); err != nil { + writer.Close() + return err + } + return writer.Close() +} + +func (engine *OpenedEngine) LocalWriter(ctx context.Context, maxCacheSize int64) (*LocalEngineWriter, error) { + w, err := engine.backend.LocalWriter(ctx, engine.uuid, maxCacheSize) + if err != nil { + return nil, err + } + return &LocalEngineWriter{writer: w, ts: engine.ts, tableName: engine.tableName}, nil +} + +// WriteRows writes a collection of encoded rows into the engine. +func (w *LocalEngineWriter) WriteRows(ctx context.Context, columnNames []string, rows Rows) error { + return w.writer.AppendRows(ctx, w.tableName, columnNames, w.ts, rows) +} + +func (w *LocalEngineWriter) Close() error { + return w.writer.Close() +} + +// UnsafeCloseEngine closes the engine without first opening it. +// This method is "unsafe" as it does not follow the normal operation sequence +// (Open -> Write -> Close -> Import). This method should only be used when one +// knows via other ways that the engine has already been opened, e.g. when +// resuming from a checkpoint. +func (be Backend) UnsafeCloseEngine(ctx context.Context, tableName string, engineID int32) (*ClosedEngine, error) { + tag, engineUUID := MakeUUID(tableName, engineID) + return be.UnsafeCloseEngineWithUUID(ctx, tag, engineUUID) +} + +// UnsafeCloseEngineWithUUID closes the engine without first opening it. +// This method is "unsafe" as it does not follow the normal operation sequence +// (Open -> Write -> Close -> Import). This method should only be used when one +// knows via other ways that the engine has already been opened, e.g. when +// resuming from a checkpoint. +func (be Backend) UnsafeCloseEngineWithUUID(ctx context.Context, tag string, engineUUID uuid.UUID) (*ClosedEngine, error) { + return engine{ + backend: be.abstract, + logger: makeLogger(tag, engineUUID), + uuid: engineUUID, + }.unsafeClose(ctx) +} + +func (en engine) unsafeClose(ctx context.Context) (*ClosedEngine, error) { + task := en.logger.Begin(zap.InfoLevel, "engine close") + err := en.backend.CloseEngine(ctx, en.uuid) + task.End(zap.ErrorLevel, err) + if err != nil { + return nil, err + } + return &ClosedEngine{engine: en}, nil +} + +// Import the data written to the engine into the target. +func (engine *ClosedEngine) Import(ctx context.Context) error { + var err error + + for i := 0; i < maxRetryTimes; i++ { + task := engine.logger.With(zap.Int("retryCnt", i)).Begin(zap.InfoLevel, "import") + err = engine.backend.ImportEngine(ctx, engine.uuid) + if !common.IsRetryableError(err) { + task.End(zap.ErrorLevel, err) + return err + } + task.Warn("import spuriously failed, going to retry again", log.ShortError(err)) + time.Sleep(engine.backend.RetryImportDelay()) + } + + return errors.Annotatef(err, "[%s] import reach max retry %d and still failed", engine.uuid, maxRetryTimes) +} + +// Cleanup deletes the intermediate data from target. +func (engine *ClosedEngine) Cleanup(ctx context.Context) error { + task := engine.logger.Begin(zap.InfoLevel, "cleanup") + err := engine.backend.CleanupEngine(ctx, engine.uuid) + task.End(zap.WarnLevel, err) + return err +} + +func (engine *ClosedEngine) Logger() log.Logger { + return engine.logger +} + +// Encoder encodes a row of SQL values into some opaque type which can be +// consumed by OpenEngine.WriteEncoded. +type Encoder interface { + // Close the encoder. + Close() + + // Encode encodes a row of SQL values into a backend-friendly format. + Encode( + logger log.Logger, + row []types.Datum, + rowID int64, + columnPermutation []int, + ) (Row, error) +} + +// Row represents a single encoded row. +type Row interface { + // ClassifyAndAppend separates the data-like and index-like parts of the + // encoded row, and appends these parts into the existing buffers and + // checksums. + ClassifyAndAppend( + data *Rows, + dataChecksum *verification.KVChecksum, + indices *Rows, + indexChecksum *verification.KVChecksum, + ) +} + +// Rows represents a collection of encoded rows. +type Rows interface { + // SplitIntoChunks splits the rows into multiple consecutive parts, each + // part having total byte size less than `splitSize`. The meaning of "byte + // size" should be consistent with the value used in `Row.ClassifyAndAppend`. + SplitIntoChunks(splitSize int) []Rows + + // Clear returns a new collection with empty content. It may share the + // capacity with the current instance. The typical usage is `x = x.Clear()`. + Clear() Rows +} + +type EngineWriter interface { + AppendRows( + ctx context.Context, + tableName string, + columnNames []string, + commitTS uint64, + rows Rows, + ) error + Close() error +} diff --git a/pkg/lightning/backend/backend_test.go b/pkg/lightning/backend/backend_test.go new file mode 100644 index 000000000..64297af24 --- /dev/null +++ b/pkg/lightning/backend/backend_test.go @@ -0,0 +1,380 @@ +package backend_test + +import ( + "context" + "time" + + "github.com/golang/mock/gomock" + "github.com/google/uuid" + . "github.com/pingcap/check" + "github.com/pingcap/errors" + "github.com/pingcap/parser/mysql" + + kv "github.com/pingcap/br/pkg/lightning/backend" + "github.com/pingcap/br/pkg/lightning/mock" +) + +type backendSuite struct { + controller *gomock.Controller + mockBackend *mock.MockBackend + backend kv.Backend +} + +var _ = Suite(&backendSuite{}) + +// FIXME: Cannot use the real SetUpTest/TearDownTest to set up the mock +// otherwise the mock error will be ignored. + +func (s *backendSuite) setUpTest(c *C) { + s.controller = gomock.NewController(c) + s.mockBackend = mock.NewMockBackend(s.controller) + s.backend = kv.MakeBackend(s.mockBackend) +} + +func (s *backendSuite) tearDownTest() { + s.controller.Finish() +} + +func (s *backendSuite) TestOpenCloseImportCleanUpEngine(c *C) { + s.setUpTest(c) + defer s.tearDownTest() + + ctx := context.Background() + engineUUID := uuid.MustParse("902efee3-a3f9-53d4-8c82-f12fb1900cd1") + + openCall := s.mockBackend.EXPECT(). + OpenEngine(ctx, engineUUID). + Return(nil) + closeCall := s.mockBackend.EXPECT(). + CloseEngine(ctx, engineUUID). + Return(nil). + After(openCall) + importCall := s.mockBackend.EXPECT(). + ImportEngine(ctx, engineUUID). + Return(nil). + After(closeCall) + s.mockBackend.EXPECT(). + CleanupEngine(ctx, engineUUID). + Return(nil). + After(importCall) + + engine, err := s.backend.OpenEngine(ctx, "`db`.`table`", 1) + c.Assert(err, IsNil) + closedEngine, err := engine.Close(ctx) + c.Assert(err, IsNil) + err = closedEngine.Import(ctx) + c.Assert(err, IsNil) + err = closedEngine.Cleanup(ctx) + c.Assert(err, IsNil) +} + +func (s *backendSuite) TestUnsafeCloseEngine(c *C) { + s.setUpTest(c) + defer s.tearDownTest() + + ctx := context.Background() + engineUUID := uuid.MustParse("7e3f3a3c-67ce-506d-af34-417ec138fbcb") + + closeCall := s.mockBackend.EXPECT(). + CloseEngine(ctx, engineUUID). + Return(nil) + s.mockBackend.EXPECT(). + CleanupEngine(ctx, engineUUID). + Return(nil). + After(closeCall) + + closedEngine, err := s.backend.UnsafeCloseEngine(ctx, "`db`.`table`", -1) + c.Assert(err, IsNil) + err = closedEngine.Cleanup(ctx) + c.Assert(err, IsNil) +} + +func (s *backendSuite) TestUnsafeCloseEngineWithUUID(c *C) { + s.setUpTest(c) + defer s.tearDownTest() + + ctx := context.Background() + engineUUID := uuid.MustParse("f1240229-79e0-4d8d-bda0-a211bf493796") + + closeCall := s.mockBackend.EXPECT(). + CloseEngine(ctx, engineUUID). + Return(nil) + s.mockBackend.EXPECT(). + CleanupEngine(ctx, engineUUID). + Return(nil). + After(closeCall) + + closedEngine, err := s.backend.UnsafeCloseEngineWithUUID(ctx, "some_tag", engineUUID) + c.Assert(err, IsNil) + err = closedEngine.Cleanup(ctx) + c.Assert(err, IsNil) +} + +func (s *backendSuite) TestWriteEngine(c *C) { + s.setUpTest(c) + defer s.tearDownTest() + + ctx := context.Background() + engineUUID := uuid.MustParse("902efee3-a3f9-53d4-8c82-f12fb1900cd1") + + rows1 := mock.NewMockRows(s.controller) + rows2 := mock.NewMockRows(s.controller) + + s.mockBackend.EXPECT(). + OpenEngine(ctx, engineUUID). + Return(nil) + + mockWriter := mock.NewMockEngineWriter(s.controller) + s.mockBackend.EXPECT().LocalWriter(ctx, gomock.Any(), int64(kv.LocalMemoryTableSize)).Return(mockWriter, nil).AnyTimes() + mockWriter.EXPECT(). + AppendRows(ctx, "`db`.`table`", []string{"c1", "c2"}, gomock.Any(), rows1). + Return(nil) + mockWriter.EXPECT().Close().Return(nil).AnyTimes() + mockWriter.EXPECT(). + AppendRows(ctx, "`db`.`table`", []string{"c1", "c2"}, gomock.Any(), rows2). + Return(nil) + + engine, err := s.backend.OpenEngine(ctx, "`db`.`table`", 1) + c.Assert(err, IsNil) + err = engine.WriteRows(ctx, []string{"c1", "c2"}, rows1) + c.Assert(err, IsNil) + err = engine.WriteRows(ctx, []string{"c1", "c2"}, rows2) + c.Assert(err, IsNil) +} + +func (s *backendSuite) TestWriteToEngineWithNothing(c *C) { + s.setUpTest(c) + defer s.tearDownTest() + + ctx := context.Background() + emptyRows := mock.NewMockRows(s.controller) + writer := mock.NewMockEngineWriter(s.controller) + + s.mockBackend.EXPECT().OpenEngine(ctx, gomock.Any()).Return(nil) + writer.EXPECT().AppendRows(ctx, gomock.Any(), gomock.Any(), gomock.Any(), emptyRows).Return(nil) + writer.EXPECT().Close().Return(nil) + s.mockBackend.EXPECT().LocalWriter(ctx, gomock.Any(), int64(kv.LocalMemoryTableSize)).Return(writer, nil) + + engine, err := s.backend.OpenEngine(ctx, "`db`.`table`", 1) + c.Assert(err, IsNil) + err = engine.WriteRows(ctx, nil, emptyRows) + c.Assert(err, IsNil) +} + +func (s *backendSuite) TestOpenEngineFailed(c *C) { + s.setUpTest(c) + defer s.tearDownTest() + + ctx := context.Background() + + s.mockBackend.EXPECT().OpenEngine(ctx, gomock.Any()). + Return(errors.New("fake unrecoverable open error")) + + _, err := s.backend.OpenEngine(ctx, "`db`.`table`", 1) + c.Assert(err, ErrorMatches, "fake unrecoverable open error") +} + +func (s *backendSuite) TestWriteEngineFailed(c *C) { + s.setUpTest(c) + defer s.tearDownTest() + + ctx := context.Background() + rows := mock.NewMockRows(s.controller) + + s.mockBackend.EXPECT().OpenEngine(ctx, gomock.Any()).Return(nil) + mockWriter := mock.NewMockEngineWriter(s.controller) + s.mockBackend.EXPECT().LocalWriter(ctx, gomock.Any(), int64(kv.LocalMemoryTableSize)).Return(mockWriter, nil).AnyTimes() + mockWriter.EXPECT(). + AppendRows(ctx, gomock.Any(), gomock.Any(), gomock.Any(), rows). + Return(errors.Annotate(context.Canceled, "fake unrecoverable write error")) + mockWriter.EXPECT().Close().Return(nil) + + engine, err := s.backend.OpenEngine(ctx, "`db`.`table`", 1) + c.Assert(err, IsNil) + err = engine.WriteRows(ctx, nil, rows) + c.Assert(err, ErrorMatches, "fake unrecoverable write error.*") +} + +func (s *backendSuite) TestWriteBatchSendFailedWithRetry(c *C) { + s.setUpTest(c) + defer s.tearDownTest() + + ctx := context.Background() + rows := mock.NewMockRows(s.controller) + + s.mockBackend.EXPECT().OpenEngine(ctx, gomock.Any()).Return(nil) + mockWriter := mock.NewMockEngineWriter(s.controller) + s.mockBackend.EXPECT().LocalWriter(ctx, gomock.Any(), int64(kv.LocalMemoryTableSize)).Return(mockWriter, nil).AnyTimes() + mockWriter.EXPECT().AppendRows(ctx, gomock.Any(), gomock.Any(), gomock.Any(), rows). + Return(errors.New("fake recoverable write batch error")). + MinTimes(1) + mockWriter.EXPECT().Close().Return(nil).MinTimes(1) + + engine, err := s.backend.OpenEngine(ctx, "`db`.`table`", 1) + c.Assert(err, IsNil) + err = engine.WriteRows(ctx, nil, rows) + c.Assert(err, ErrorMatches, ".*fake recoverable write batch error") +} + +func (s *backendSuite) TestImportFailedNoRetry(c *C) { + s.setUpTest(c) + defer s.tearDownTest() + + ctx := context.Background() + + s.mockBackend.EXPECT().CloseEngine(ctx, gomock.Any()).Return(nil) + s.mockBackend.EXPECT(). + ImportEngine(ctx, gomock.Any()). + Return(errors.Annotate(context.Canceled, "fake unrecoverable import error")) + + closedEngine, err := s.backend.UnsafeCloseEngine(ctx, "`db`.`table`", 1) + c.Assert(err, IsNil) + err = closedEngine.Import(ctx) + c.Assert(err, ErrorMatches, "fake unrecoverable import error.*") +} + +func (s *backendSuite) TestImportFailedWithRetry(c *C) { + s.setUpTest(c) + defer s.tearDownTest() + + ctx := context.Background() + + s.mockBackend.EXPECT().CloseEngine(ctx, gomock.Any()).Return(nil) + s.mockBackend.EXPECT(). + ImportEngine(ctx, gomock.Any()). + Return(errors.New("fake recoverable import error")). + MinTimes(2) + s.mockBackend.EXPECT().RetryImportDelay().Return(time.Duration(0)).AnyTimes() + + closedEngine, err := s.backend.UnsafeCloseEngine(ctx, "`db`.`table`", 1) + c.Assert(err, IsNil) + err = closedEngine.Import(ctx) + c.Assert(err, ErrorMatches, ".*fake recoverable import error") +} + +func (s *backendSuite) TestImportFailedRecovered(c *C) { + s.setUpTest(c) + defer s.tearDownTest() + + ctx := context.Background() + + s.mockBackend.EXPECT().CloseEngine(ctx, gomock.Any()).Return(nil) + s.mockBackend.EXPECT(). + ImportEngine(ctx, gomock.Any()). + Return(errors.New("fake recoverable import error")) + s.mockBackend.EXPECT(). + ImportEngine(ctx, gomock.Any()). + Return(nil) + s.mockBackend.EXPECT().RetryImportDelay().Return(time.Duration(0)).AnyTimes() + + closedEngine, err := s.backend.UnsafeCloseEngine(ctx, "`db`.`table`", 1) + c.Assert(err, IsNil) + err = closedEngine.Import(ctx) + c.Assert(err, IsNil) +} + +func (s *backendSuite) TestClose(c *C) { + s.setUpTest(c) + defer s.tearDownTest() + + s.mockBackend.EXPECT().Close().Return() + + s.backend.Close() +} + +func (s *backendSuite) TestMakeEmptyRows(c *C) { + s.setUpTest(c) + defer s.tearDownTest() + + rows := mock.NewMockRows(s.controller) + s.mockBackend.EXPECT().MakeEmptyRows().Return(rows) + + c.Assert(s.mockBackend.MakeEmptyRows(), Equals, rows) +} + +func (s *backendSuite) TestNewEncoder(c *C) { + s.setUpTest(c) + defer s.tearDownTest() + + encoder := mock.NewMockEncoder(s.controller) + options := &kv.SessionOptions{SQLMode: mysql.ModeANSIQuotes, Timestamp: 1234567890} + s.mockBackend.EXPECT().NewEncoder(nil, options).Return(encoder, nil) + + realEncoder, err := s.mockBackend.NewEncoder(nil, options) + c.Assert(realEncoder, Equals, encoder) + c.Assert(err, IsNil) +} + +func (s *backendSuite) TestCheckDiskQuota(c *C) { + s.setUpTest(c) + defer s.tearDownTest() + + uuid1 := uuid.MustParse("11111111-1111-1111-1111-111111111111") + uuid3 := uuid.MustParse("33333333-3333-3333-3333-333333333333") + uuid5 := uuid.MustParse("55555555-5555-5555-5555-555555555555") + uuid7 := uuid.MustParse("77777777-7777-7777-7777-777777777777") + uuid9 := uuid.MustParse("99999999-9999-9999-9999-999999999999") + + fileSizes := []kv.EngineFileSize{ + { + UUID: uuid1, + DiskSize: 1000, + MemSize: 0, + IsImporting: false, + }, + { + UUID: uuid3, + DiskSize: 2000, + MemSize: 1000, + IsImporting: true, + }, + { + UUID: uuid5, + DiskSize: 1500, + MemSize: 3500, + IsImporting: false, + }, + { + UUID: uuid7, + DiskSize: 0, + MemSize: 7000, + IsImporting: true, + }, + { + UUID: uuid9, + DiskSize: 4500, + MemSize: 4500, + IsImporting: false, + }, + } + + s.mockBackend.EXPECT().EngineFileSizes().Return(fileSizes).Times(4) + + // No quota exceeded + le, iple, ds, ms := s.backend.CheckDiskQuota(30000) + c.Assert(le, HasLen, 0) + c.Assert(iple, Equals, 0) + c.Assert(ds, Equals, int64(9000)) + c.Assert(ms, Equals, int64(16000)) + + // Quota exceeded, the largest one is out + le, iple, ds, ms = s.backend.CheckDiskQuota(20000) + c.Assert(le, DeepEquals, []uuid.UUID{uuid9}) + c.Assert(iple, Equals, 0) + c.Assert(ds, Equals, int64(9000)) + c.Assert(ms, Equals, int64(16000)) + + // Quota exceeded, the importing one should be ranked least priority + le, iple, ds, ms = s.backend.CheckDiskQuota(12000) + c.Assert(le, DeepEquals, []uuid.UUID{uuid5, uuid9}) + c.Assert(iple, Equals, 0) + c.Assert(ds, Equals, int64(9000)) + c.Assert(ms, Equals, int64(16000)) + + // Quota exceeded, the importing ones should not be visible + le, iple, ds, ms = s.backend.CheckDiskQuota(5000) + c.Assert(le, DeepEquals, []uuid.UUID{uuid1, uuid5, uuid9}) + c.Assert(iple, Equals, 1) + c.Assert(ds, Equals, int64(9000)) + c.Assert(ms, Equals, int64(16000)) +} diff --git a/pkg/lightning/backend/checkreq_test.go b/pkg/lightning/backend/checkreq_test.go new file mode 100644 index 000000000..5a6c10a24 --- /dev/null +++ b/pkg/lightning/backend/checkreq_test.go @@ -0,0 +1,122 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package backend + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + + "github.com/coreos/go-semver/semver" + . "github.com/pingcap/check" + + "github.com/pingcap/br/pkg/lightning/common" +) + +var _ = Suite(&checkReqSuite{}) + +type checkReqSuite struct{} + +func (s *checkReqSuite) TestCheckVersion(c *C) { + err := checkVersion("TiNB", *semver.New("2.1.0"), *semver.New("2.3.5")) + c.Assert(err, IsNil) + + err = checkVersion("TiNB", *semver.New("2.3.5"), *semver.New("2.1.0")) + c.Assert(err, ErrorMatches, "TiNB version too old.*") +} + +func (s *checkReqSuite) TestCheckTiDBVersion(c *C) { + var version string + ctx := context.Background() + + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + c.Assert(req.URL.Path, Equals, "/status") + w.WriteHeader(http.StatusOK) + err := json.NewEncoder(w).Encode(map[string]interface{}{ + "version": version, + }) + c.Assert(err, IsNil) + })) + + tls := common.NewTLSFromMockServer(mockServer) + + version = "5.7.25-TiDB-v9999.0.0" + c.Assert(checkTiDBVersionByTLS(ctx, tls, requiredTiDBVersion), IsNil) + + version = "5.7.25-TiDB-v1.0.0" + c.Assert(checkTiDBVersionByTLS(ctx, tls, requiredTiDBVersion), ErrorMatches, "TiDB version too old.*") +} + +func (s *checkReqSuite) TestCheckPDVersion(c *C) { + var version string + ctx := context.Background() + + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + c.Assert(req.URL.Path, Equals, "/pd/api/v1/config/cluster-version") + w.WriteHeader(http.StatusOK) + err := json.NewEncoder(w).Encode(version) + c.Assert(err, IsNil) + })) + mockURL, err := url.Parse(mockServer.URL) + c.Assert(err, IsNil) + + tls := common.NewTLSFromMockServer(mockServer) + + version = "9999.0.0" + c.Assert(checkPDVersion(ctx, tls, mockURL.Host, requiredPDVersion), IsNil) + + version = "1.0.0" + c.Assert(checkPDVersion(ctx, tls, mockURL.Host, requiredPDVersion), ErrorMatches, "PD version too old.*") +} + +func (s *checkReqSuite) TestCheckTiKVVersion(c *C) { + var versions []string + ctx := context.Background() + + mockServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + c.Assert(req.URL.Path, Equals, "/pd/api/v1/stores") + w.WriteHeader(http.StatusOK) + + stores := make([]map[string]interface{}, 0, len(versions)) + for i, v := range versions { + stores = append(stores, map[string]interface{}{ + "store": map[string]interface{}{ + "address": fmt.Sprintf("tikv%d.test:20160", i), + "version": v, + }, + }) + } + err := json.NewEncoder(w).Encode(map[string]interface{}{ + "count": len(versions), + "stores": stores, + }) + c.Assert(err, IsNil) + })) + mockURL, err := url.Parse(mockServer.URL) + c.Assert(err, IsNil) + + tls := common.NewTLSFromMockServer(mockServer) + + versions = []string{"9999.0.0", "9999.0.0"} + c.Assert(checkTiKVVersion(ctx, tls, mockURL.Host, requiredTiKVVersion), IsNil) + + versions = []string{"4.1.0", "v4.1.0-alpha-9-ga27a7dd"} + c.Assert(checkTiKVVersion(ctx, tls, mockURL.Host, requiredTiKVVersion), IsNil) + + versions = []string{"9999.0.0", "1.0.0"} + c.Assert(checkTiKVVersion(ctx, tls, mockURL.Host, requiredTiKVVersion), ErrorMatches, `TiKV \(at tikv1\.test:20160\) version too old.*`) +} diff --git a/pkg/lightning/backend/importer.go b/pkg/lightning/backend/importer.go new file mode 100644 index 000000000..de1b686f5 --- /dev/null +++ b/pkg/lightning/backend/importer.go @@ -0,0 +1,389 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package backend + +import ( + "context" + "fmt" + "strings" + "sync" + "time" + + "github.com/coreos/go-semver/semver" + "github.com/google/uuid" + "github.com/pingcap/errors" + kv "github.com/pingcap/kvproto/pkg/import_kvpb" + "github.com/pingcap/parser/model" + "github.com/pingcap/tidb/table" + "go.uber.org/zap" + "google.golang.org/grpc" + + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/glue" + "github.com/pingcap/br/pkg/lightning/log" +) + +const ( + defaultRetryBackoffTime = time.Second * 3 +) + +var ( + requiredTiDBVersion = *semver.New("2.1.0") + requiredPDVersion = *semver.New("2.1.0") + requiredTiKVVersion = *semver.New("2.1.0") +) + +// importer represents a gRPC connection to tikv-importer. This type is +// goroutine safe: you can share this instance and execute any method anywhere. +type importer struct { + conn *grpc.ClientConn + cli kv.ImportKVClient + pdAddr string + tls *common.TLS + + mutationPool sync.Pool +} + +// NewImporter creates a new connection to tikv-importer. A single connection +// per tidb-lightning instance is enough. +func NewImporter(ctx context.Context, tls *common.TLS, importServerAddr string, pdAddr string) (Backend, error) { + conn, err := grpc.DialContext(ctx, importServerAddr, tls.ToGRPCDialOption()) + if err != nil { + return MakeBackend(nil), errors.Trace(err) + } + + return MakeBackend(&importer{ + conn: conn, + cli: kv.NewImportKVClient(conn), + pdAddr: pdAddr, + tls: tls, + mutationPool: sync.Pool{New: func() interface{} { return &kv.Mutation{} }}, + }), nil +} + +// NewMockImporter creates an *unconnected* importer based on a custom +// ImportKVClient. This is provided for testing only. Do not use this function +// outside of tests. +func NewMockImporter(cli kv.ImportKVClient, pdAddr string) Backend { + return MakeBackend(&importer{ + conn: nil, + cli: cli, + pdAddr: pdAddr, + mutationPool: sync.Pool{New: func() interface{} { return &kv.Mutation{} }}, + }) +} + +// Close the importer connection. +func (importer *importer) Close() { + if importer.conn != nil { + if err := importer.conn.Close(); err != nil { + log.L().Warn("close importer gRPC connection failed", zap.Error(err)) + } + } +} + +func (*importer) RetryImportDelay() time.Duration { + return defaultRetryBackoffTime +} + +func (*importer) MaxChunkSize() int { + // 31 MB. hardcoded by importer, so do we + return 31 << 10 +} + +func (*importer) ShouldPostProcess() bool { + return true +} + +// isIgnorableOpenCloseEngineError checks if the error from +// OpenEngine/CloseEngine can be safely ignored. +func isIgnorableOpenCloseEngineError(err error) bool { + // We allow "FileExists" error. This happens when the engine has been opened + // and closed before. This error typically arise when resuming from a + // checkpoint with a partially-imported engine. + // + // If the error is legit in a no-checkpoints settings, the later WriteEngine + // API will bail us out to keep us safe. + return err == nil || strings.Contains(err.Error(), "FileExists") +} + +func (importer *importer) OpenEngine(ctx context.Context, engineUUID uuid.UUID) error { + req := &kv.OpenEngineRequest{ + Uuid: engineUUID[:], + } + + _, err := importer.cli.OpenEngine(ctx, req) + if !isIgnorableOpenCloseEngineError(err) { + return errors.Trace(err) + } + return nil +} + +func (importer *importer) CloseEngine(ctx context.Context, engineUUID uuid.UUID) error { + req := &kv.CloseEngineRequest{ + Uuid: engineUUID[:], + } + + _, err := importer.cli.CloseEngine(ctx, req) + if !isIgnorableOpenCloseEngineError(err) { + return errors.Trace(err) + } + return nil +} + +func (importer *importer) Flush(_ context.Context, _ uuid.UUID) error { + return nil +} + +func (importer *importer) ImportEngine(ctx context.Context, engineUUID uuid.UUID) error { + req := &kv.ImportEngineRequest{ + Uuid: engineUUID[:], + PdAddr: importer.pdAddr, + } + + _, err := importer.cli.ImportEngine(ctx, req) + return errors.Trace(err) +} + +func (importer *importer) CleanupEngine(ctx context.Context, engineUUID uuid.UUID) error { + req := &kv.CleanupEngineRequest{ + Uuid: engineUUID[:], + } + + _, err := importer.cli.CleanupEngine(ctx, req) + return errors.Trace(err) +} + +func (importer *importer) WriteRows( + ctx context.Context, + engineUUID uuid.UUID, + tableName string, + columnNames []string, + ts uint64, + rows Rows, +) (finalErr error) { + var err error +outside: + for _, r := range rows.SplitIntoChunks(importer.MaxChunkSize()) { + for i := 0; i < maxRetryTimes; i++ { + err = importer.WriteRowsToImporter(ctx, engineUUID, ts, r) + switch { + case err == nil: + continue outside + case common.IsRetryableError(err): + // retry next loop + default: + return err + } + } + return errors.Annotatef(err, "[%s] write rows reach max retry %d and still failed", tableName, maxRetryTimes) + } + return nil +} + +func (importer *importer) WriteRowsToImporter( + ctx context.Context, + engineUUID uuid.UUID, + ts uint64, + rows Rows, +) (finalErr error) { + kvs := rows.(kvPairs) + if len(kvs) == 0 { + return nil + } + + wstream, err := importer.cli.WriteEngine(ctx) + if err != nil { + return errors.Trace(err) + } + + logger := log.With(zap.Stringer("engineUUID", engineUUID)) + + defer func() { + if _, closeErr := wstream.CloseAndRecv(); closeErr != nil { + if finalErr == nil { + finalErr = errors.Trace(closeErr) + } else { + // just log the close error, we need to propagate the earlier error instead + logger.Warn("close write stream failed", log.ShortError(closeErr)) + } + } + }() + + // Bind uuid for this write request + req := &kv.WriteEngineRequest{ + Chunk: &kv.WriteEngineRequest_Head{ + Head: &kv.WriteHead{ + Uuid: engineUUID[:], + }, + }, + } + if err := wstream.Send(req); err != nil { + return errors.Trace(err) + } + + // Send kv paris as write request content + mutations := make([]*kv.Mutation, len(kvs)) + for i, pair := range kvs { + mutations[i] = importer.mutationPool.Get().(*kv.Mutation) + mutations[i].Op = kv.Mutation_Put + mutations[i].Key = pair.Key + mutations[i].Value = pair.Val + } + + req.Reset() + req.Chunk = &kv.WriteEngineRequest_Batch{ + Batch: &kv.WriteBatch{ + CommitTs: ts, + Mutations: mutations, + }, + } + + err = wstream.Send(req) + for _, mutation := range mutations { + importer.mutationPool.Put(mutation) + } + + if err != nil { + return errors.Trace(err) + } + + return nil +} + +func (*importer) MakeEmptyRows() Rows { + return kvPairs(nil) +} + +func (*importer) NewEncoder(tbl table.Table, options *SessionOptions) (Encoder, error) { + return NewTableKVEncoder(tbl, options) +} + +func (importer *importer) CheckRequirements(ctx context.Context) error { + if err := checkTiDBVersionByTLS(ctx, importer.tls, requiredTiDBVersion); err != nil { + return err + } + if err := checkPDVersion(ctx, importer.tls, importer.pdAddr, requiredPDVersion); err != nil { + return err + } + if err := checkTiKVVersion(ctx, importer.tls, importer.pdAddr, requiredTiKVVersion); err != nil { + return err + } + return nil +} + +func checkTiDBVersionByTLS(ctx context.Context, tls *common.TLS, requiredVersion semver.Version) error { + var status struct{ Version string } + err := tls.GetJSON(ctx, "/status", &status) + if err != nil { + return err + } + + return checkTiDBVersion(status.Version, requiredVersion) +} + +func checkTiDBVersion(versionStr string, requiredVersion semver.Version) error { + version, err := common.ExtractTiDBVersion(versionStr) + if err != nil { + return errors.Trace(err) + } + return checkVersion("TiDB", requiredVersion, *version) +} + +func checkTiDBVersionBySQL(ctx context.Context, g glue.Glue, requiredVersion semver.Version) error { + versionStr, err := g.GetSQLExecutor().ObtainStringWithLog( + ctx, + "SELECT version();", + "check TiDB version", + log.L()) + if err != nil { + return errors.Trace(err) + } + + return checkTiDBVersion(versionStr, requiredVersion) +} + +func checkPDVersion(ctx context.Context, tls *common.TLS, pdAddr string, requiredVersion semver.Version) error { + version, err := common.FetchPDVersion(ctx, tls, pdAddr) + if err != nil { + return errors.Trace(err) + } + + return checkVersion("PD", requiredVersion, *version) +} + +func checkTiKVVersion(ctx context.Context, tls *common.TLS, pdAddr string, requiredVersion semver.Version) error { + return ForAllStores( + ctx, + tls.WithHost(pdAddr), + StoreStateDown, + func(c context.Context, store *Store) error { + component := fmt.Sprintf("TiKV (at %s)", store.Address) + version, err := semver.NewVersion(strings.TrimPrefix(store.Version, "v")) + if err != nil { + return errors.Annotate(err, component) + } + return checkVersion(component, requiredVersion, *version) + }, + ) +} + +func checkVersion(component string, expected, actual semver.Version) error { + if actual.Compare(expected) >= 0 { + return nil + } + return errors.Errorf( + "%s version too old, expected '>=%s', found '%s'", + component, + expected, + actual, + ) +} + +func (importer *importer) FetchRemoteTableModels(ctx context.Context, schema string) ([]*model.TableInfo, error) { + return fetchRemoteTableModelsFromTLS(ctx, importer.tls, schema) +} + +func (importer *importer) EngineFileSizes() []EngineFileSize { + return nil +} + +func (importer *importer) FlushEngine(context.Context, uuid.UUID) error { + return nil +} + +func (importer *importer) FlushAllEngines(context.Context) error { + return nil +} + +func (importer *importer) ResetEngine(context.Context, uuid.UUID) error { + return errors.New("cannot reset an engine in importer backend") +} + +func (importer *importer) LocalWriter(ctx context.Context, engineUUID uuid.UUID, maxCacheSize int64) (EngineWriter, error) { + return &ImporterWriter{importer: importer, engineUUID: engineUUID}, nil +} + +type ImporterWriter struct { + importer *importer + engineUUID uuid.UUID +} + +func (w *ImporterWriter) Close() error { + return nil +} + +func (w *ImporterWriter) AppendRows(ctx context.Context, tableName string, columnNames []string, ts uint64, rows Rows) error { + return w.importer.WriteRows(ctx, w.engineUUID, tableName, columnNames, ts, rows) +} diff --git a/pkg/lightning/backend/importer_test.go b/pkg/lightning/backend/importer_test.go new file mode 100644 index 000000000..5819e2da9 --- /dev/null +++ b/pkg/lightning/backend/importer_test.go @@ -0,0 +1,249 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package backend_test + +import ( + "context" + "sync" + "testing" + + "github.com/golang/mock/gomock" + "github.com/google/uuid" + . "github.com/pingcap/check" + "github.com/pingcap/errors" + "github.com/pingcap/kvproto/pkg/import_kvpb" + + kvpb "github.com/pingcap/kvproto/pkg/import_kvpb" + + kv "github.com/pingcap/br/pkg/lightning/backend" + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/mock" +) + +type importerSuite struct { + controller *gomock.Controller + mockClient *mock.MockImportKVClient + mockWriter *mock.MockImportKV_WriteEngineClient + ctx context.Context + engineUUID []byte + engine *kv.OpenedEngine + kvPairs kv.Rows +} + +var _ = Suite(&importerSuite{}) + +const testPDAddr = "pd-addr:2379" + +// FIXME: Cannot use the real SetUpTest/TearDownTest to set up the mock +// otherwise the mock error will be ignored. + +func (s *importerSuite) setUpTest(c *C) { + s.controller = gomock.NewController(c) + s.mockClient = mock.NewMockImportKVClient(s.controller) + s.mockWriter = mock.NewMockImportKV_WriteEngineClient(s.controller) + importer := kv.NewMockImporter(s.mockClient, testPDAddr) + + s.ctx = context.Background() + engineUUID := uuid.MustParse("7e3f3a3c-67ce-506d-af34-417ec138fbcb") + s.engineUUID = engineUUID[:] + s.kvPairs = kv.MakeRowsFromKvPairs([]common.KvPair{ + { + Key: []byte("k1"), + Val: []byte("v1"), + }, + { + Key: []byte("k2"), + Val: []byte("v2"), + }, + }) + + s.mockClient.EXPECT(). + OpenEngine(s.ctx, &import_kvpb.OpenEngineRequest{Uuid: s.engineUUID}). + Return(nil, nil) + + var err error + s.engine, err = importer.OpenEngine(s.ctx, "`db`.`table`", -1) + c.Assert(err, IsNil) +} + +func (s *importerSuite) tearDownTest() { + s.controller.Finish() +} + +func (s *importerSuite) TestWriteRows(c *C) { + s.setUpTest(c) + defer s.tearDownTest() + + s.mockClient.EXPECT().WriteEngine(s.ctx).Return(s.mockWriter, nil) + + headSendCall := s.mockWriter.EXPECT(). + Send(&import_kvpb.WriteEngineRequest{ + Chunk: &import_kvpb.WriteEngineRequest_Head{ + Head: &import_kvpb.WriteHead{Uuid: s.engineUUID}, + }, + }). + Return(nil) + batchSendCall := s.mockWriter.EXPECT(). + Send(gomock.Any()). + DoAndReturn(func(x *import_kvpb.WriteEngineRequest) error { + c.Assert(x.GetBatch().GetMutations(), DeepEquals, []*import_kvpb.Mutation{ + {Op: import_kvpb.Mutation_Put, Key: []byte("k1"), Value: []byte("v1")}, + {Op: import_kvpb.Mutation_Put, Key: []byte("k2"), Value: []byte("v2")}, + }) + return nil + }). + After(headSendCall) + s.mockWriter.EXPECT(). + CloseAndRecv(). + Return(nil, nil). + After(batchSendCall) + + err := s.engine.WriteRows(s.ctx, nil, s.kvPairs) + c.Assert(err, IsNil) +} + +func (s *importerSuite) TestWriteHeadSendFailed(c *C) { + s.setUpTest(c) + defer s.tearDownTest() + + s.mockClient.EXPECT().WriteEngine(s.ctx).Return(s.mockWriter, nil) + + headSendCall := s.mockWriter.EXPECT(). + Send(gomock.Any()). + DoAndReturn(func(x *import_kvpb.WriteEngineRequest) error { + c.Assert(x.GetHead(), NotNil) + return errors.Annotate(context.Canceled, "fake unrecoverable write head error") + }) + s.mockWriter.EXPECT(). + CloseAndRecv(). + Return(nil, errors.Annotate(context.Canceled, "fake unrecoverable close stream error")). + After(headSendCall) + + err := s.engine.WriteRows(s.ctx, nil, s.kvPairs) + c.Assert(err, ErrorMatches, "fake unrecoverable write head error.*") +} + +func (s *importerSuite) TestWriteBatchSendFailed(c *C) { + s.setUpTest(c) + defer s.tearDownTest() + + s.mockClient.EXPECT().WriteEngine(s.ctx).Return(s.mockWriter, nil) + + headSendCall := s.mockWriter.EXPECT(). + Send(gomock.Any()). + DoAndReturn(func(x *import_kvpb.WriteEngineRequest) error { + c.Assert(x.GetHead(), NotNil) + return nil + }) + batchSendCall := s.mockWriter.EXPECT(). + Send(gomock.Any()). + DoAndReturn(func(x *import_kvpb.WriteEngineRequest) error { + c.Assert(x.GetBatch(), NotNil) + return errors.Annotate(context.Canceled, "fake unrecoverable write batch error") + }). + After(headSendCall) + s.mockWriter.EXPECT(). + CloseAndRecv(). + Return(nil, errors.Annotate(context.Canceled, "fake unrecoverable close stream error")). + After(batchSendCall) + + err := s.engine.WriteRows(s.ctx, nil, s.kvPairs) + c.Assert(err, ErrorMatches, "fake unrecoverable write batch error.*") +} + +func (s *importerSuite) TestWriteCloseFailed(c *C) { + s.setUpTest(c) + defer s.tearDownTest() + + s.mockClient.EXPECT().WriteEngine(s.ctx).Return(s.mockWriter, nil) + + headSendCall := s.mockWriter.EXPECT(). + Send(gomock.Any()). + DoAndReturn(func(x *import_kvpb.WriteEngineRequest) error { + c.Assert(x.GetHead(), NotNil) + return nil + }) + batchSendCall := s.mockWriter.EXPECT(). + Send(gomock.Any()). + DoAndReturn(func(x *import_kvpb.WriteEngineRequest) error { + c.Assert(x.GetBatch(), NotNil) + return nil + }). + After(headSendCall) + s.mockWriter.EXPECT(). + CloseAndRecv(). + Return(nil, errors.Annotate(context.Canceled, "fake unrecoverable close stream error")). + After(batchSendCall) + + err := s.engine.WriteRows(s.ctx, nil, s.kvPairs) + c.Assert(err, ErrorMatches, "fake unrecoverable close stream error.*") +} + +func (s *importerSuite) TestCloseImportCleanupEngine(c *C) { + s.setUpTest(c) + defer s.tearDownTest() + + s.mockClient.EXPECT(). + CloseEngine(s.ctx, &import_kvpb.CloseEngineRequest{Uuid: s.engineUUID}). + Return(nil, nil) + s.mockClient.EXPECT(). + ImportEngine(s.ctx, &import_kvpb.ImportEngineRequest{Uuid: s.engineUUID, PdAddr: testPDAddr}). + Return(nil, nil) + s.mockClient.EXPECT(). + CleanupEngine(s.ctx, &import_kvpb.CleanupEngineRequest{Uuid: s.engineUUID}). + Return(nil, nil) + + engine, err := s.engine.Close(s.ctx) + c.Assert(err, IsNil) + err = engine.Import(s.ctx) + c.Assert(err, IsNil) + err = engine.Cleanup(s.ctx) + c.Assert(err, IsNil) +} + +func BenchmarkMutationAlloc(b *testing.B) { + var g *kvpb.Mutation + for i := 0; i < b.N; i++ { + m := &kvpb.Mutation{ + Op: kvpb.Mutation_Put, + Key: nil, + Value: nil, + } + g = m + } + + _ = g +} + +func BenchmarkMutationPool(b *testing.B) { + p := sync.Pool{ + New: func() interface{} { + return &kvpb.Mutation{} + }, + } + var g *kvpb.Mutation + + for i := 0; i < b.N; i++ { + m := p.Get().(*kvpb.Mutation) + m.Op = kvpb.Mutation_Put + m.Key = nil + m.Value = nil + + g = m + + p.Put(m) + } + + _ = g +} diff --git a/pkg/lightning/backend/local.go b/pkg/lightning/backend/local.go new file mode 100644 index 000000000..84a2d3872 --- /dev/null +++ b/pkg/lightning/backend/local.go @@ -0,0 +1,2052 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package backend + +import ( + "bytes" + "context" + "encoding/binary" + "encoding/json" + "io" + "os" + "path/filepath" + "sort" + "strings" + "sync" + "time" + + "github.com/cockroachdb/pebble" + "github.com/cockroachdb/pebble/sstable" + "github.com/coreos/go-semver/semver" + "github.com/google/btree" + "github.com/google/uuid" + "github.com/pingcap/errors" + "github.com/pingcap/failpoint" + "github.com/pingcap/kvproto/pkg/errorpb" + sst "github.com/pingcap/kvproto/pkg/import_sstpb" + "github.com/pingcap/kvproto/pkg/kvrpcpb" + "github.com/pingcap/kvproto/pkg/metapb" + "github.com/pingcap/parser/model" + "github.com/pingcap/tidb/table" + "github.com/pingcap/tidb/tablecodec" + "github.com/pingcap/tidb/util/codec" + "github.com/pingcap/tidb/util/hack" + pd "github.com/tikv/pd/client" + "go.uber.org/atomic" + "go.uber.org/multierr" + "go.uber.org/zap" + "golang.org/x/sync/errgroup" + "google.golang.org/grpc" + "google.golang.org/grpc/backoff" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/keepalive" + + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/config" + "github.com/pingcap/br/pkg/lightning/glue" + "github.com/pingcap/br/pkg/lightning/log" + "github.com/pingcap/br/pkg/lightning/manual" + "github.com/pingcap/br/pkg/lightning/metric" + "github.com/pingcap/br/pkg/lightning/worker" + split "github.com/pingcap/br/pkg/restore" + "github.com/pingcap/br/pkg/utils" +) + +const ( + dialTimeout = 5 * time.Second + bigValueSize = 1 << 16 // 64K + + gRPCKeepAliveTime = 10 * time.Second + gRPCKeepAliveTimeout = 3 * time.Second + gRPCBackOffMaxDelay = 3 * time.Second + + LocalMemoryTableSize = config.LocalMemoryTableSize + + // See: https://github.com/tikv/tikv/blob/e030a0aae9622f3774df89c62f21b2171a72a69e/etc/config-template.toml#L360 + regionMaxKeyCount = 1_440_000 + + propRangeIndex = "tikv.range_index" + + defaultPropSizeIndexDistance = 4 * 1024 * 1024 // 4MB + defaultPropKeysIndexDistance = 40 * 1024 + + // the lower threshold of max open files for pebble db. + openFilesLowerThreshold = 128 +) + +var ( + localMinTiDBVersion = *semver.New("4.0.0") + localMinTiKVVersion = *semver.New("4.0.0") + localMinPDVersion = *semver.New("4.0.0") +) + +var ( + engineMetaKey = []byte{0, 'm', 'e', 't', 'a'} + normalIterStartKey = []byte{1} +) + +// Range record start and end key for localStoreDir.DB +// so we can write it to tikv in streaming +type Range struct { + start []byte + end []byte + length int +} + +// localFileMeta contains some field that is necessary to continue the engine restore/import process. +// These field should be written to disk when we update chunk checkpoint +type localFileMeta struct { + Ts uint64 `json:"ts"` + // Length is the number of KV pairs stored by the engine. + Length atomic.Int64 `json:"length"` + // TotalSize is the total pre-compressed KV byte size stored by engine. + TotalSize atomic.Int64 `json:"total_size"` +} + +type importMutexState uint32 + +const ( + importMutexStateImport importMutexState = 1 << iota + importMutexStateFlush + importMutexStateClose + importMutexStateLocalIngest +) + +type LocalFile struct { + localFileMeta + db *pebble.DB + Uuid uuid.UUID + localWriters sync.Map + + // isImportingAtomic is an atomic variable indicating whether the importMutex has been locked. + // This should not be used as a "spin lock" indicator. + isImportingAtomic atomic.Uint32 + mutex sync.Mutex +} + +func (e *LocalFile) Close() error { + log.L().Debug("closing local engine", zap.Stringer("engine", e.Uuid), zap.Stack("stack")) + if e.db == nil { + return nil + } + err := errors.Trace(e.db.Close()) + e.db = nil + return err +} + +// Cleanup remove meta and db files +func (e *LocalFile) Cleanup(dataDir string) error { + dbPath := filepath.Join(dataDir, e.Uuid.String()) + return os.RemoveAll(dbPath) +} + +// Exist checks if db folder existing (meta sometimes won't flush before lightning exit) +func (e *LocalFile) Exist(dataDir string) error { + dbPath := filepath.Join(dataDir, e.Uuid.String()) + if _, err := os.Stat(dbPath); err != nil { + return err + } + return nil +} + +func (e *LocalFile) getSizeProperties() (*sizeProperties, error) { + sstables, err := e.db.SSTables(pebble.WithProperties()) + if err != nil { + log.L().Warn("get table properties failed", zap.Stringer("engine", e.Uuid), log.ShortError(err)) + return nil, errors.Trace(err) + } + + sizeProps := newSizeProperties() + for _, level := range sstables { + for _, info := range level { + if prop, ok := info.Properties.UserProperties[propRangeIndex]; ok { + data := hack.Slice(prop) + rangeProps, err := decodeRangeProperties(data) + if err != nil { + log.L().Warn("decodeRangeProperties failed", zap.Stringer("engine", e.Uuid), + zap.Stringer("fileNum", info.FileNum), log.ShortError(err)) + return nil, errors.Trace(err) + } + + sizeProps.addAll(rangeProps) + } + } + } + + return sizeProps, nil +} + +func (e *LocalFile) isLocked() bool { + return e.isImportingAtomic.Load() != 0 +} + +func (e *LocalFile) getEngineFileSize() EngineFileSize { + metrics := e.db.Metrics() + total := metrics.Total() + var memSize int64 + e.localWriters.Range(func(k, v interface{}) bool { + w := k.(*LocalWriter) + memSize += w.writeBatch.totalSize + if w.writer != nil { + total.Size += int64(w.writer.writer.EstimatedSize()) + } + return true + }) + + return EngineFileSize{ + UUID: e.Uuid, + DiskSize: total.Size, + MemSize: memSize, + IsImporting: e.isLocked(), + } +} + +// lock locks the local file for importing. +func (e *LocalFile) lock(state importMutexState) { + e.mutex.Lock() + e.isImportingAtomic.Store(uint32(state)) +} + +// lockUnless tries to lock the local file unless it is already locked into the state given by +// ignoreStateMask. Returns whether the lock is successful. +func (e *LocalFile) lockUnless(newState, ignoreStateMask importMutexState) bool { + curState := e.isImportingAtomic.Load() + if curState&uint32(ignoreStateMask) != 0 { + return false + } + e.lock(newState) + return true +} + +func (e *LocalFile) unlock() { + if e == nil { + return + } + e.isImportingAtomic.Store(0) + e.mutex.Unlock() +} + +func (e *LocalFile) flushLocalWriters(parentCtx context.Context) error { + eg, ctx := errgroup.WithContext(parentCtx) + e.localWriters.Range(func(k, v interface{}) bool { + eg.Go(func() error { + w := k.(*LocalWriter) + replyErrCh := make(chan error, 1) + w.flushChMutex.RLock() + if w.flushCh != nil { + w.flushCh <- replyErrCh + } else { + replyErrCh <- nil + } + w.flushChMutex.RUnlock() + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-replyErrCh: + return err + } + }) + return true + }) + return eg.Wait() +} + +func (e *LocalFile) flushEngineWithoutLock(ctx context.Context) error { + if err := e.flushLocalWriters(ctx); err != nil { + return err + } + if err := e.saveEngineMeta(); err != nil { + return err + } + flushFinishedCh, err := e.db.AsyncFlush() + if err != nil { + return err + } + select { + case <-flushFinishedCh: + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +// saveEngineMeta saves the metadata about the DB into the DB itself. +// This method should be followed by a Flush to ensure the data is actually synchronized +func (e *LocalFile) saveEngineMeta() error { + jsonBytes, err := json.Marshal(&e.localFileMeta) + if err != nil { + return errors.Trace(err) + } + // note: we can't set Sync to true since we disabled WAL. + return errors.Trace(e.db.Set(engineMetaKey, jsonBytes, &pebble.WriteOptions{Sync: false})) +} + +func (e *LocalFile) loadEngineMeta() { + jsonBytes, closer, err := e.db.Get(engineMetaKey) + if err != nil { + log.L().Debug("local db missing engine meta", zap.Stringer("uuid", e.Uuid), zap.Error(err)) + return + } + defer closer.Close() + + err = json.Unmarshal(jsonBytes, &e.localFileMeta) + if err != nil { + log.L().Warn("local db failed to deserialize meta", zap.Stringer("uuid", e.Uuid), zap.ByteString("content", jsonBytes), zap.Error(err)) + } +} + +type gRPCConns struct { + mu sync.Mutex + conns map[uint64]*connPool +} + +func (conns *gRPCConns) Close() { + conns.mu.Lock() + defer conns.mu.Unlock() + + for _, cp := range conns.conns { + cp.Close() + } +} + +type local struct { + engines sync.Map // sync version of map[uuid.UUID]*LocalFile + + conns gRPCConns + splitCli split.SplitClient + tls *common.TLS + pdAddr string + g glue.Glue + + localStoreDir string + regionSplitSize int64 + + rangeConcurrency *worker.Pool + ingestConcurrency *worker.Pool + batchWriteKVPairs int + checkpointEnabled bool + + tcpConcurrency int + maxOpenFiles int +} + +// connPool is a lazy pool of gRPC channels. +// When `Get` called, it lazily allocates new connection if connection not full. +// If it's full, then it will return allocated channels round-robin. +type connPool struct { + mu sync.Mutex + + conns []*grpc.ClientConn + name string + next int + cap int + newConn func(ctx context.Context) (*grpc.ClientConn, error) +} + +func (p *connPool) takeConns() (conns []*grpc.ClientConn) { + p.mu.Lock() + defer p.mu.Unlock() + p.conns, conns = nil, p.conns + p.next = 0 + return conns +} + +// Close closes the conn pool. +func (p *connPool) Close() { + for _, c := range p.takeConns() { + if err := c.Close(); err != nil { + log.L().Warn("failed to close clientConn", zap.String("target", c.Target()), log.ShortError(err)) + } + } +} + +// get tries to get an existing connection from the pool, or make a new one if the pool not full. +func (p *connPool) get(ctx context.Context) (*grpc.ClientConn, error) { + p.mu.Lock() + defer p.mu.Unlock() + if len(p.conns) < p.cap { + c, err := p.newConn(ctx) + if err != nil { + return nil, err + } + p.conns = append(p.conns, c) + return c, nil + } + + conn := p.conns[p.next] + p.next = (p.next + 1) % p.cap + return conn, nil +} + +// newConnPool creates a new connPool by the specified conn factory function and capacity. +func newConnPool(cap int, newConn func(ctx context.Context) (*grpc.ClientConn, error)) *connPool { + return &connPool{ + cap: cap, + conns: make([]*grpc.ClientConn, 0, cap), + newConn: newConn, + + mu: sync.Mutex{}, + } +} + +// NewLocalBackend creates new connections to tikv. +func NewLocalBackend( + ctx context.Context, + tls *common.TLS, + pdAddr string, + regionSplitSize int64, + localFile string, + rangeConcurrency int, + sendKVPairs int, + enableCheckpoint bool, + g glue.Glue, + maxOpenFiles int, +) (Backend, error) { + pdCli, err := pd.NewClientWithContext(ctx, []string{pdAddr}, tls.ToPDSecurityOption()) + if err != nil { + return MakeBackend(nil), errors.Annotate(err, "construct pd client failed") + } + splitCli := split.NewSplitClient(pdCli, tls.TLSConfig()) + + shouldCreate := true + if enableCheckpoint { + if info, err := os.Stat(localFile); err != nil { + if !os.IsNotExist(err) { + return MakeBackend(nil), err + } + } else if info.IsDir() { + shouldCreate = false + } + } + + if shouldCreate { + err = os.Mkdir(localFile, 0o700) + if err != nil { + return MakeBackend(nil), errors.Annotate(err, "invalid sorted-kv-dir for local backend, please change the config or delete the path") + } + } + + local := &local{ + engines: sync.Map{}, + splitCli: splitCli, + tls: tls, + pdAddr: pdAddr, + g: g, + + localStoreDir: localFile, + regionSplitSize: regionSplitSize, + + rangeConcurrency: worker.NewPool(ctx, rangeConcurrency, "range"), + ingestConcurrency: worker.NewPool(ctx, rangeConcurrency*2, "ingest"), + tcpConcurrency: rangeConcurrency, + batchWriteKVPairs: sendKVPairs, + checkpointEnabled: enableCheckpoint, + maxOpenFiles: utils.MaxInt(maxOpenFiles, openFilesLowerThreshold), + } + local.conns.conns = make(map[uint64]*connPool) + return MakeBackend(local), nil +} + +// lock locks a local file and returns the LocalFile instance if it exists. +func (local *local) lockEngine(engineId uuid.UUID, state importMutexState) *LocalFile { + if e, ok := local.engines.Load(engineId); ok { + engine := e.(*LocalFile) + engine.lock(state) + return engine + } + return nil +} + +// lockAllEnginesUnless tries to lock all engines, unless those which are already locked in the +// state given by ignoreStateMask. Returns the list of locked engines. +func (local *local) lockAllEnginesUnless(newState, ignoreStateMask importMutexState) []*LocalFile { + var allEngines []*LocalFile + local.engines.Range(func(k, v interface{}) bool { + engine := v.(*LocalFile) + if engine.lockUnless(newState, ignoreStateMask) { + allEngines = append(allEngines, engine) + } + return true + }) + return allEngines +} + +func (local *local) makeConn(ctx context.Context, storeID uint64) (*grpc.ClientConn, error) { + store, err := local.splitCli.GetStore(ctx, storeID) + if err != nil { + return nil, errors.Trace(err) + } + opt := grpc.WithInsecure() + if local.tls.TLSConfig() != nil { + opt = grpc.WithTransportCredentials(credentials.NewTLS(local.tls.TLSConfig())) + } + ctx, cancel := context.WithTimeout(ctx, dialTimeout) + + bfConf := backoff.DefaultConfig + bfConf.MaxDelay = gRPCBackOffMaxDelay + // we should use peer address for tiflash. for tikv, peer address is empty + addr := store.GetPeerAddress() + if addr == "" { + addr = store.GetAddress() + } + conn, err := grpc.DialContext( + ctx, + addr, + opt, + grpc.WithConnectParams(grpc.ConnectParams{Backoff: bfConf}), + grpc.WithKeepaliveParams(keepalive.ClientParameters{ + Time: gRPCKeepAliveTime, + Timeout: gRPCKeepAliveTimeout, + PermitWithoutStream: true, + }), + ) + cancel() + if err != nil { + return nil, errors.Trace(err) + } + return conn, nil +} + +func (local *local) getGrpcConnLocked(ctx context.Context, storeID uint64) (*grpc.ClientConn, error) { + if _, ok := local.conns.conns[storeID]; !ok { + local.conns.conns[storeID] = newConnPool(local.tcpConcurrency, func(ctx context.Context) (*grpc.ClientConn, error) { + return local.makeConn(ctx, storeID) + }) + } + return local.conns.conns[storeID].get(ctx) +} + +// Close the local backend. +func (local *local) Close() { + allEngines := local.lockAllEnginesUnless(importMutexStateClose, 0) + local.engines = sync.Map{} + + for _, engine := range allEngines { + engine.Close() + engine.unlock() + } + local.conns.Close() + + // if checkpoint is disable or we finish load all data successfully, then files in this + // dir will be useless, so we clean up this dir and all files in it. + if !local.checkpointEnabled || common.IsEmptyDir(local.localStoreDir) { + err := os.RemoveAll(local.localStoreDir) + if err != nil { + log.L().Warn("remove local db file failed", zap.Error(err)) + } + } +} + +// FlushEngine ensure the written data is saved successfully, to make sure no data lose after restart +func (local *local) FlushEngine(ctx context.Context, engineId uuid.UUID) error { + engineFile := local.lockEngine(engineId, importMutexStateFlush) + + // the engine cannot be deleted after while we've acquired the lock identified by UUID. + + if engineFile == nil { + return errors.Errorf("engine '%s' not found", engineId) + } + defer engineFile.unlock() + return engineFile.flushEngineWithoutLock(ctx) +} + +func (local *local) FlushAllEngines(parentCtx context.Context) (err error) { + allEngines := local.lockAllEnginesUnless(importMutexStateFlush, ^importMutexStateLocalIngest) + defer func() { + for _, engine := range allEngines { + engine.unlock() + } + }() + + eg, ctx := errgroup.WithContext(parentCtx) + for _, engineFile := range allEngines { + ef := engineFile + eg.Go(func() error { + return ef.flushEngineWithoutLock(ctx) + }) + } + return eg.Wait() +} + +func (local *local) RetryImportDelay() time.Duration { + return defaultRetryBackoffTime +} + +func (local *local) MaxChunkSize() int { + // a batch size write to leveldb + return int(local.regionSplitSize) +} + +func (local *local) ShouldPostProcess() bool { + return true +} + +func (local *local) openEngineDB(engineUUID uuid.UUID, readOnly bool) (*pebble.DB, error) { + opt := &pebble.Options{ + MemTableSize: LocalMemoryTableSize, + // the default threshold value may cause write stall. + MemTableStopWritesThreshold: 8, + MaxConcurrentCompactions: 16, + // set to half of the max open files so that if open files is more that estimation, trigger compaction + // to avoid failure due to open files exceeded limit + L0CompactionThreshold: local.maxOpenFiles / 2, + L0StopWritesThreshold: local.maxOpenFiles / 2, + MaxOpenFiles: local.maxOpenFiles, + DisableWAL: true, + ReadOnly: readOnly, + TablePropertyCollectors: []func() pebble.TablePropertyCollector{ + newRangePropertiesCollector, + }, + } + dbPath := filepath.Join(local.localStoreDir, engineUUID.String()) + return pebble.Open(dbPath, opt) +} + +// This method must be called with holding mutex of LocalFile +func (local *local) OpenEngine(ctx context.Context, engineUUID uuid.UUID) error { + db, err := local.openEngineDB(engineUUID, false) + if err != nil { + return err + } + e, _ := local.engines.LoadOrStore(engineUUID, &LocalFile{Uuid: engineUUID}) + engine := e.(*LocalFile) + engine.db = db + engine.loadEngineMeta() + return nil +} + +// Close backend engine by uuid +// NOTE: we will return nil if engine is not exist. This will happen if engine import&cleanup successfully +// but exit before update checkpoint. Thus after restart, we will try to import this engine again. +func (local *local) CloseEngine(ctx context.Context, engineUUID uuid.UUID) error { + // flush mem table to storage, to free memory, + // ask others' advise, looks like unnecessary, but with this we can control memory precisely. + engine, ok := local.engines.Load(engineUUID) + if !ok { + // recovery mode, we should reopen this engine file + db, err := local.openEngineDB(engineUUID, true) + if err != nil { + // if engine db does not exist, just skip + if os.IsNotExist(errors.Cause(err)) { + return nil + } + return err + } + engineFile := &LocalFile{ + Uuid: engineUUID, + db: db, + } + engineFile.loadEngineMeta() + local.engines.Store(engineUUID, engineFile) + return nil + } + engineFile := engine.(*LocalFile) + engineFile.lock(importMutexStateFlush) + defer engineFile.unlock() + return engineFile.flushEngineWithoutLock(ctx) +} + +func (local *local) getImportClient(ctx context.Context, peer *metapb.Peer) (sst.ImportSSTClient, error) { + local.conns.mu.Lock() + defer local.conns.mu.Unlock() + + conn, err := local.getGrpcConnLocked(ctx, peer.GetStoreId()) + if err != nil { + return nil, err + } + return sst.NewImportSSTClient(conn), nil +} + +type rangeStats struct { + count int64 + totalBytes int64 +} + +// WriteToTiKV writer engine key-value pairs to tikv and return the sst meta generated by tikv. +// we don't need to do cleanup for the pairs written to tikv if encounters an error, +// tikv will takes the responsibility to do so. +func (local *local) WriteToTiKV( + ctx context.Context, + engineFile *LocalFile, + region *split.RegionInfo, + start, end []byte, +) ([]*sst.SSTMeta, *Range, rangeStats, error) { + begin := time.Now() + regionRange := intersectRange(region.Region, Range{start: start, end: end}) + opt := &pebble.IterOptions{LowerBound: regionRange.start, UpperBound: regionRange.end} + iter := engineFile.db.NewIter(opt) + defer iter.Close() + + stats := rangeStats{} + + iter.First() + if iter.Error() != nil { + return nil, nil, stats, errors.Annotate(iter.Error(), "failed to read the first key") + } + if !iter.Valid() { + log.L().Info("keys within region is empty, skip ingest", log.ZapRedactBinary("start", start), + log.ZapRedactBinary("regionStart", region.Region.StartKey), log.ZapRedactBinary("end", end), + log.ZapRedactBinary("regionEnd", region.Region.EndKey)) + return nil, nil, stats, nil + } + + firstKey := codec.EncodeBytes([]byte{}, iter.Key()) + iter.Last() + if iter.Error() != nil { + return nil, nil, stats, errors.Annotate(iter.Error(), "failed to seek to the last key") + } + lastKey := codec.EncodeBytes([]byte{}, iter.Key()) + + u := uuid.New() + meta := &sst.SSTMeta{ + Uuid: u[:], + RegionId: region.Region.GetId(), + RegionEpoch: region.Region.GetRegionEpoch(), + Range: &sst.Range{ + Start: firstKey, + End: lastKey, + }, + } + + leaderID := region.Leader.GetId() + clients := make([]sst.ImportSST_WriteClient, 0, len(region.Region.GetPeers())) + requests := make([]*sst.WriteRequest, 0, len(region.Region.GetPeers())) + for _, peer := range region.Region.GetPeers() { + cli, err := local.getImportClient(ctx, peer) + if err != nil { + return nil, nil, stats, err + } + + wstream, err := cli.Write(ctx) + if err != nil { + return nil, nil, stats, errors.Trace(err) + } + + // Bind uuid for this write request + req := &sst.WriteRequest{ + Chunk: &sst.WriteRequest_Meta{ + Meta: meta, + }, + } + if err = wstream.Send(req); err != nil { + return nil, nil, stats, errors.Trace(err) + } + req.Chunk = &sst.WriteRequest_Batch{ + Batch: &sst.WriteBatch{ + CommitTs: engineFile.Ts, + }, + } + clients = append(clients, wstream) + requests = append(requests, req) + } + + bytesBuf := newBytesBuffer() + defer bytesBuf.destroy() + pairs := make([]*sst.Pair, 0, local.batchWriteKVPairs) + count := 0 + size := int64(0) + totalCount := int64(0) + firstLoop := true + regionMaxSize := local.regionSplitSize * 4 / 3 + + for iter.First(); iter.Valid(); iter.Next() { + size += int64(len(iter.Key()) + len(iter.Value())) + // here we reuse the `*sst.Pair`s to optimize object allocation + if firstLoop { + pair := &sst.Pair{ + Key: bytesBuf.addBytes(iter.Key()), + Value: bytesBuf.addBytes(iter.Value()), + } + pairs = append(pairs, pair) + } else { + pairs[count].Key = bytesBuf.addBytes(iter.Key()) + pairs[count].Value = bytesBuf.addBytes(iter.Value()) + } + count++ + totalCount++ + + if count >= local.batchWriteKVPairs { + for i := range clients { + requests[i].Chunk.(*sst.WriteRequest_Batch).Batch.Pairs = pairs[:count] + if err := clients[i].Send(requests[i]); err != nil { + return nil, nil, stats, err + } + } + count = 0 + bytesBuf.reset() + firstLoop = false + } + if size >= regionMaxSize || totalCount >= regionMaxKeyCount { + break + } + } + + if iter.Error() != nil { + return nil, nil, stats, errors.Trace(iter.Error()) + } + + if count > 0 { + for i := range clients { + requests[i].Chunk.(*sst.WriteRequest_Batch).Batch.Pairs = pairs[:count] + if err := clients[i].Send(requests[i]); err != nil { + return nil, nil, stats, err + } + } + } + + var leaderPeerMetas []*sst.SSTMeta + for i, wStream := range clients { + if resp, closeErr := wStream.CloseAndRecv(); closeErr != nil { + return nil, nil, stats, closeErr + } else { + if leaderID == region.Region.Peers[i].GetId() { + leaderPeerMetas = resp.Metas + log.L().Debug("get metas after write kv stream to tikv", zap.Reflect("metas", leaderPeerMetas)) + } + } + } + + // if there is not leader currently, we should directly return an error + if leaderPeerMetas == nil { + log.L().Warn("write to tikv no leader", log.ZapRedactReflect("region", region), + zap.Uint64("leader_id", leaderID), log.ZapRedactReflect("meta", meta), + zap.Int64("kv_pairs", totalCount), zap.Int64("total_bytes", size)) + return nil, nil, stats, errors.Errorf("write to tikv with no leader returned, region '%d', leader: %d", + region.Region.Id, leaderID) + } + + log.L().Debug("write to kv", zap.Reflect("region", region), zap.Uint64("leader", leaderID), + zap.Reflect("meta", meta), zap.Reflect("return metas", leaderPeerMetas), + zap.Int64("kv_pairs", totalCount), zap.Int64("total_bytes", size), + zap.Int64("buf_size", bytesBuf.totalSize()), + zap.Stringer("takeTime", time.Since(begin))) + + var remainRange *Range + if iter.Valid() && iter.Next() { + firstKey := append([]byte{}, iter.Key()...) + remainRange = &Range{start: firstKey, end: regionRange.end} + log.L().Info("write to tikv partial finish", zap.Int64("count", totalCount), + zap.Int64("size", size), log.ZapRedactBinary("startKey", regionRange.start), log.ZapRedactBinary("endKey", regionRange.end), + log.ZapRedactBinary("remainStart", remainRange.start), log.ZapRedactBinary("remainEnd", remainRange.end), + log.ZapRedactReflect("region", region)) + } + stats.count = totalCount + stats.totalBytes = size + + return leaderPeerMetas, remainRange, stats, nil +} + +func (local *local) Ingest(ctx context.Context, meta *sst.SSTMeta, region *split.RegionInfo) (*sst.IngestResponse, error) { + leader := region.Leader + if leader == nil { + leader = region.Region.GetPeers()[0] + } + + cli, err := local.getImportClient(ctx, leader) + if err != nil { + return nil, err + } + reqCtx := &kvrpcpb.Context{ + RegionId: region.Region.GetId(), + RegionEpoch: region.Region.GetRegionEpoch(), + Peer: leader, + } + + req := &sst.IngestRequest{ + Context: reqCtx, + Sst: meta, + } + resp, err := cli.Ingest(ctx, req) + if err != nil { + return nil, err + } + return resp, nil +} + +func splitRangeBySizeProps(fullRange Range, sizeProps *sizeProperties, sizeLimit int64, keysLimit int64) []Range { + ranges := make([]Range, 0, sizeProps.totalSize/uint64(sizeLimit)) + curSize := uint64(0) + curKeys := uint64(0) + curKey := fullRange.start + sizeProps.iter(func(p *rangeProperty) bool { + curSize += p.Size + curKeys += p.Keys + if int64(curSize) >= sizeLimit || int64(curKeys) >= keysLimit { + ranges = append(ranges, Range{start: curKey, end: p.Key}) + curKey = p.Key + curSize = 0 + curKeys = 0 + } + return true + }) + + if curKeys > 0 { + ranges = append(ranges, Range{start: curKey, end: fullRange.end}) + } else { + ranges[len(ranges)-1].end = fullRange.end + } + return ranges +} + +func (local *local) readAndSplitIntoRange(engineFile *LocalFile) ([]Range, error) { + iter := engineFile.db.NewIter(&pebble.IterOptions{LowerBound: normalIterStartKey}) + defer iter.Close() + + iterError := func(e string) error { + err := iter.Error() + if err != nil { + return errors.Annotate(err, e) + } + return errors.New(e) + } + + var firstKey, lastKey []byte + if iter.First() { + firstKey = append([]byte{}, iter.Key()...) + } else { + return nil, iterError("could not find first pair") + } + if iter.Last() { + lastKey = append([]byte{}, iter.Key()...) + } else { + return nil, iterError("could not find last pair") + } + endKey := nextKey(lastKey) + + engineFileTotalSize := engineFile.TotalSize.Load() + engineFileLength := engineFile.Length.Load() + + // <= 96MB no need to split into range + if engineFileTotalSize <= local.regionSplitSize && engineFileLength <= regionMaxKeyCount { + ranges := []Range{{start: firstKey, end: endKey, length: int(engineFileLength)}} + return ranges, nil + } + + sizeProps, err := engineFile.getSizeProperties() + if err != nil { + return nil, errors.Trace(err) + } + + ranges := splitRangeBySizeProps(Range{start: firstKey, end: endKey}, sizeProps, + local.regionSplitSize, regionMaxKeyCount*2/3) + + log.L().Info("split engine key ranges", zap.Stringer("engine", engineFile.Uuid), + zap.Int64("totalSize", engineFileTotalSize), zap.Int64("totalCount", engineFileLength), + log.ZapRedactBinary("firstKey", firstKey), log.ZapRedactBinary("lastKey", lastKey), + zap.Int("ranges", len(ranges))) + + return ranges, nil +} + +type bytesRecycleChan struct { + ch chan []byte +} + +// recycleChan is used for reusing allocated []byte so we can use memory more efficiently +// +// NOTE: we don't used a `sync.Pool` because when will sync.Pool release is depending on the +// garbage collector which always release the memory so late. Use a fixed size chan to reuse +// can decrease the memory usage to 1/3 compare with sync.Pool. +var recycleChan *bytesRecycleChan + +func init() { + recycleChan = &bytesRecycleChan{ + ch: make(chan []byte, 1024), + } +} + +func (c *bytesRecycleChan) Acquire() []byte { + select { + case b := <-c.ch: + return b + default: + return manual.New(1 << 20) // 1M + } +} + +func (c *bytesRecycleChan) Release(w []byte) { + select { + case c.ch <- w: + return + default: + manual.Free(w) + } +} + +type bytesBuffer struct { + bufs [][]byte + curBuf []byte + curIdx int + curBufIdx int + curBufLen int +} + +func newBytesBuffer() *bytesBuffer { + return &bytesBuffer{bufs: make([][]byte, 0, 128), curBufIdx: -1} +} + +func (b *bytesBuffer) addBuf() { + if b.curBufIdx < len(b.bufs)-1 { + b.curBufIdx += 1 + b.curBuf = b.bufs[b.curBufIdx] + } else { + buf := recycleChan.Acquire() + b.bufs = append(b.bufs, buf) + b.curBuf = buf + b.curBufIdx = len(b.bufs) - 1 + } + + b.curBufLen = len(b.curBuf) + b.curIdx = 0 +} + +func (b *bytesBuffer) reset() { + if len(b.bufs) > 0 { + b.curBuf = b.bufs[0] + b.curBufLen = len(b.bufs[0]) + b.curBufIdx = 0 + b.curIdx = 0 + } +} + +func (b *bytesBuffer) destroy() { + for _, buf := range b.bufs { + recycleChan.Release(buf) + } + b.bufs = b.bufs[:0] +} + +func (b *bytesBuffer) totalSize() int64 { + return int64(len(b.bufs)) * int64(1<<20) +} + +func (b *bytesBuffer) addBytes(bytes []byte) []byte { + if len(bytes) > bigValueSize { + return append([]byte{}, bytes...) + } + + if b.curIdx+len(bytes) > b.curBufLen { + b.addBuf() + } + idx := b.curIdx + copy(b.curBuf[idx:], bytes) + b.curIdx += len(bytes) + return b.curBuf[idx:b.curIdx] +} + +func (local *local) writeAndIngestByRange( + ctxt context.Context, + engineFile *LocalFile, + start, end []byte, + remainRanges *syncdRanges, +) error { + ito := &pebble.IterOptions{ + LowerBound: start, + UpperBound: end, + } + + iter := engineFile.db.NewIter(ito) + defer iter.Close() + // Needs seek to first because NewIter returns an iterator that is unpositioned + hasKey := iter.First() + if iter.Error() != nil { + return errors.Annotate(iter.Error(), "failed to read the first key") + } + if !hasKey { + log.L().Info("There is no pairs in iterator", + log.ZapRedactBinary("start", start), + log.ZapRedactBinary("end", end), + log.ZapRedactBinary("next end", nextKey(end))) + return nil + } + pairStart := append([]byte{}, iter.Key()...) + iter.Last() + if iter.Error() != nil { + return errors.Annotate(iter.Error(), "failed to seek to the last key") + } + pairEnd := append([]byte{}, iter.Key()...) + + var regions []*split.RegionInfo + var err error + ctx, cancel := context.WithCancel(ctxt) + defer cancel() + +WriteAndIngest: + for retry := 0; retry < maxRetryTimes; { + if retry != 0 { + select { + case <-time.After(time.Second): + case <-ctx.Done(): + return ctx.Err() + } + } + startKey := codec.EncodeBytes([]byte{}, pairStart) + endKey := codec.EncodeBytes([]byte{}, nextKey(pairEnd)) + regions, err = paginateScanRegion(ctx, local.splitCli, startKey, endKey, 128) + if err != nil || len(regions) == 0 { + log.L().Warn("scan region failed", log.ShortError(err), zap.Int("region_len", len(regions)), + log.ZapRedactBinary("startKey", startKey), log.ZapRedactBinary("endKey", endKey), zap.Int("retry", retry)) + retry++ + continue WriteAndIngest + } + + for _, region := range regions { + log.L().Debug("get region", zap.Int("retry", retry), zap.Binary("startKey", startKey), + zap.Binary("endKey", endKey), zap.Uint64("id", region.Region.GetId()), + zap.Stringer("epoch", region.Region.GetRegionEpoch()), zap.Binary("start", region.Region.GetStartKey()), + zap.Binary("end", region.Region.GetEndKey()), zap.Reflect("peers", region.Region.GetPeers())) + + w := local.ingestConcurrency.Apply() + var rg *Range + rg, err = local.writeAndIngestPairs(ctx, engineFile, region, pairStart, end) + local.ingestConcurrency.Recycle(w) + if err != nil { + _, regionStart, _ := codec.DecodeBytes(region.Region.StartKey, []byte{}) + // if we have at least succeeded one region, retry without increasing the retry count + if bytes.Compare(regionStart, pairStart) > 0 { + pairStart = regionStart + } else { + retry++ + } + log.L().Info("retry write and ingest kv pairs", log.ZapRedactBinary("startKey", pairStart), + log.ZapRedactBinary("endKey", end), log.ShortError(err), zap.Int("retry", retry)) + continue WriteAndIngest + } + if rg != nil { + remainRanges.add(*rg) + } + } + + return err + } + + return err +} + +type retryType int + +const ( + retryNone retryType = iota + retryWrite + retryIngest +) + +func (local *local) writeAndIngestPairs( + ctx context.Context, + engineFile *LocalFile, + region *split.RegionInfo, + start, end []byte, +) (*Range, error) { + var err error + var remainRange *Range + var rangeStats rangeStats +loopWrite: + for i := 0; i < maxRetryTimes; i++ { + var metas []*sst.SSTMeta + metas, remainRange, rangeStats, err = local.WriteToTiKV(ctx, engineFile, region, start, end) + if err != nil { + log.L().Warn("write to tikv failed", log.ShortError(err)) + return nil, err + } + + for _, meta := range metas { + errCnt := 0 + for errCnt < maxRetryTimes { + log.L().Debug("ingest meta", zap.Reflect("meta", meta)) + var resp *sst.IngestResponse + failpoint.Inject("FailIngestMeta", func(val failpoint.Value) { + // only inject the error once + switch val.(string) { + case "notleader": + resp = &sst.IngestResponse{ + Error: &errorpb.Error{ + NotLeader: &errorpb.NotLeader{ + RegionId: region.Region.Id, + Leader: region.Leader, + }, + }, + } + case "epochnotmatch": + resp = &sst.IngestResponse{ + Error: &errorpb.Error{ + EpochNotMatch: &errorpb.EpochNotMatch{ + CurrentRegions: []*metapb.Region{region.Region}, + }, + }, + } + } + if resp != nil { + err = nil + } + }) + if resp == nil { + resp, err = local.Ingest(ctx, meta, region) + } + if err != nil { + if errors.Cause(err) == context.Canceled { + return nil, err + } + log.L().Warn("ingest failed", log.ShortError(err), log.ZapRedactReflect("meta", meta), + log.ZapRedactReflect("region", region)) + errCnt++ + continue + } + var retryTy retryType + var newRegion *split.RegionInfo + retryTy, newRegion, err = local.isIngestRetryable(ctx, resp, region, meta) + if err == nil { + // ingest next meta + break + } + switch retryTy { + case retryNone: + log.L().Warn("ingest failed noretry", log.ShortError(err), log.ZapRedactReflect("meta", meta), + log.ZapRedactReflect("region", region)) + // met non-retryable error retry whole Write procedure + return remainRange, err + case retryWrite: + region = newRegion + continue loopWrite + case retryIngest: + region = newRegion + continue + } + } + } + + if err != nil { + log.L().Warn("write and ingest region, will retry import full range", log.ShortError(err), + log.ZapRedactStringer("region", region.Region), log.ZapRedactBinary("start", start), + log.ZapRedactBinary("end", end)) + } else { + metric.BytesCounter.WithLabelValues(metric.TableStateImported).Add(float64(rangeStats.totalBytes)) + } + return remainRange, errors.Trace(err) + } + + return remainRange, errors.Trace(err) +} + +func (local *local) writeAndIngestByRanges(ctx context.Context, engineFile *LocalFile, ranges []Range, remainRanges *syncdRanges) error { + if engineFile.Length.Load() == 0 { + // engine is empty, this is likes because it's a index engine but the table contains no index + log.L().Info("engine contains no data", zap.Stringer("uuid", engineFile.Uuid)) + return nil + } + log.L().Debug("the ranges Length write to tikv", zap.Int("Length", len(ranges))) + + var allErrLock sync.Mutex + var allErr error + var wg sync.WaitGroup + + wg.Add(len(ranges)) + + for _, r := range ranges { + startKey := r.start + endKey := r.end + w := local.rangeConcurrency.Apply() + go func(w *worker.Worker) { + defer func() { + local.rangeConcurrency.Recycle(w) + wg.Done() + }() + var err error + for i := 0; i < maxRetryTimes; i++ { + err = local.writeAndIngestByRange(ctx, engineFile, startKey, endKey, remainRanges) + if err == nil || errors.Cause(err) == context.Canceled { + return + } + log.L().Warn("write and ingest by range failed", + zap.Int("retry time", i+1), log.ShortError(err)) + } + + allErrLock.Lock() + allErr = multierr.Append(allErr, err) + allErrLock.Unlock() + }(w) + } + + // wait for all sub tasks finish to avoid panic. if we return on the first error, + // the outer tasks may close the pebble db but some sub tasks still read from the db + wg.Wait() + return allErr +} + +type syncdRanges struct { + sync.Mutex + ranges []Range +} + +func (r *syncdRanges) add(g Range) { + r.Lock() + r.ranges = append(r.ranges, g) + r.Unlock() +} + +func (r *syncdRanges) take() []Range { + r.Lock() + rg := r.ranges + r.ranges = []Range{} + r.Unlock() + if len(rg) > 0 { + sort.Slice(rg, func(i, j int) bool { + return bytes.Compare(rg[i].start, rg[j].start) < 0 + }) + } + return rg +} + +func (local *local) ImportEngine(ctx context.Context, engineUUID uuid.UUID) error { + lf := local.lockEngine(engineUUID, importMutexStateImport) + if lf == nil { + // skip if engine not exist. See the comment of `CloseEngine` for more detail. + return nil + } + defer lf.unlock() + + lfTotalSize := lf.TotalSize.Load() + lfLength := lf.Length.Load() + + if lfTotalSize == 0 { + log.L().Info("engine contains no kv, skip import", zap.Stringer("engine", engineUUID)) + return nil + } + + // split sorted file into range by 96MB size per file + ranges, err := local.readAndSplitIntoRange(lf) + if err != nil { + return err + } + remains := &syncdRanges{} + + for { + log.L().Info("start import engine", zap.Stringer("uuid", engineUUID), + zap.Int("ranges", len(ranges))) + + // if all the kv can fit in one region, skip split regions. TiDB will split one region for + // the table when table is created. + needSplit := len(ranges) > 1 || lfTotalSize > local.regionSplitSize || lfLength > regionMaxKeyCount + + // split region by given ranges + for i := 0; i < maxRetryTimes; i++ { + err = local.SplitAndScatterRegionByRanges(ctx, ranges, needSplit) + if err == nil || common.IsContextCanceledError(err) { + break + } + + log.L().Warn("split and scatter failed in retry", zap.Stringer("uuid", engineUUID), + log.ShortError(err), zap.Int("retry", i)) + } + if err != nil { + log.L().Error("split & scatter ranges failed", zap.Stringer("uuid", engineUUID), log.ShortError(err)) + return err + } + + // start to write to kv and ingest + err = local.writeAndIngestByRanges(ctx, lf, ranges, remains) + if err != nil { + log.L().Error("write and ingest engine failed", log.ShortError(err)) + return err + } + + unfinishedRanges := remains.take() + if len(unfinishedRanges) == 0 { + break + } + log.L().Info("ingest ranges unfinished", zap.Int("remain ranges", len(unfinishedRanges))) + ranges = unfinishedRanges + } + + log.L().Info("import engine success", zap.Stringer("uuid", engineUUID), + zap.Int64("size", lfTotalSize), zap.Int64("kvs", lfLength)) + return nil +} + +func (local *local) ResetEngine(ctx context.Context, engineUUID uuid.UUID) error { + // the only way to reset the engine + reclaim the space is to delete and reopen it 🤷 + localEngine := local.lockEngine(engineUUID, importMutexStateClose) + if localEngine == nil { + log.L().Warn("could not find engine in cleanupEngine", zap.Stringer("uuid", engineUUID)) + return nil + } + defer localEngine.unlock() + if err := localEngine.Close(); err != nil { + return err + } + if err := localEngine.Cleanup(local.localStoreDir); err != nil { + return err + } + db, err := local.openEngineDB(engineUUID, false) + if err == nil { + localEngine.db = db + localEngine.localFileMeta = localFileMeta{} + } + return err +} + +func (local *local) CleanupEngine(ctx context.Context, engineUUID uuid.UUID) error { + localEngine := local.lockEngine(engineUUID, importMutexStateClose) + // release this engine after import success + if localEngine == nil { + log.L().Warn("could not find engine in cleanupEngine", zap.Stringer("uuid", engineUUID)) + return nil + } + defer localEngine.unlock() + + // since closing the engine causes all subsequent operations on it panic, + // we make sure to delete it from the engine map before calling Close(). + // (note that Close() returning error does _not_ mean the pebble DB + // remains open/usable.) + local.engines.Delete(engineUUID) + err := localEngine.Close() + if err != nil { + return err + } + err = localEngine.Cleanup(local.localStoreDir) + if err != nil { + return err + } + localEngine.TotalSize.Store(0) + localEngine.Length.Store(0) + return nil +} + +func (local *local) CheckRequirements(ctx context.Context) error { + if err := checkTiDBVersionBySQL(ctx, local.g, localMinTiDBVersion); err != nil { + return err + } + if err := checkPDVersion(ctx, local.tls, local.pdAddr, localMinPDVersion); err != nil { + return err + } + if err := checkTiKVVersion(ctx, local.tls, local.pdAddr, localMinTiKVVersion); err != nil { + return err + } + return nil +} + +func (local *local) FetchRemoteTableModels(ctx context.Context, schemaName string) ([]*model.TableInfo, error) { + return fetchRemoteTableModelsFromTLS(ctx, local.tls, schemaName) +} + +func (local *local) MakeEmptyRows() Rows { + return kvPairs(nil) +} + +func (local *local) NewEncoder(tbl table.Table, options *SessionOptions) (Encoder, error) { + return NewTableKVEncoder(tbl, options) +} + +func (local *local) LocalWriter(ctx context.Context, engineUUID uuid.UUID, maxCacheSize int64) (EngineWriter, error) { + e, ok := local.engines.Load(engineUUID) + if !ok { + return nil, errors.Errorf("could not find engine for %s", engineUUID.String()) + } + engineFile := e.(*LocalFile) + return openLocalWriter(engineFile, local.localStoreDir, maxCacheSize), nil +} + +func openLocalWriter(f *LocalFile, sstDir string, memtableSizeLimit int64) *LocalWriter { + w := &LocalWriter{ + sstDir: sstDir, + kvsChan: make(chan []common.KvPair, 1024), + flushCh: make(chan chan error), + consumeCh: make(chan struct{}, 1), + local: f, + memtableSizeLimit: memtableSizeLimit, + } + f.localWriters.Store(w, nil) + go w.writeRowsLoop() + return w +} + +func (local *local) isIngestRetryable( + ctx context.Context, + resp *sst.IngestResponse, + region *split.RegionInfo, + meta *sst.SSTMeta, +) (retryType, *split.RegionInfo, error) { + if resp.GetError() == nil { + return retryNone, nil, nil + } + + getRegion := func() (*split.RegionInfo, error) { + for i := 0; ; i++ { + newRegion, err := local.splitCli.GetRegion(ctx, region.Region.GetStartKey()) + if err != nil { + return nil, errors.Trace(err) + } + if newRegion != nil { + return newRegion, nil + } + log.L().Warn("get region by key return nil, will retry", log.ZapRedactReflect("region", region), + zap.Int("retry", i)) + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-time.After(time.Second): + } + } + } + + var newRegion *split.RegionInfo + var err error + switch errPb := resp.GetError(); { + case errPb.NotLeader != nil: + if newLeader := errPb.GetNotLeader().GetLeader(); newLeader != nil { + newRegion = &split.RegionInfo{ + Leader: newLeader, + Region: region.Region, + } + } else { + newRegion, err = getRegion() + if err != nil { + return retryNone, nil, errors.Trace(err) + } + } + // TODO: because in some case, TiKV may return retryable error while the ingest is succeeded. + // Thus directly retry ingest may cause TiKV panic. So always return retryWrite here to avoid + // this issue. + // See: https://github.com/tikv/tikv/issues/9496 + return retryWrite, newRegion, errors.Errorf("not leader: %s", errPb.GetMessage()) + case errPb.EpochNotMatch != nil: + if currentRegions := errPb.GetEpochNotMatch().GetCurrentRegions(); currentRegions != nil { + var currentRegion *metapb.Region + for _, r := range currentRegions { + if insideRegion(r, meta) { + currentRegion = r + break + } + } + if currentRegion != nil { + var newLeader *metapb.Peer + for _, p := range currentRegion.Peers { + if p.GetStoreId() == region.Leader.GetStoreId() { + newLeader = p + break + } + } + if newLeader != nil { + newRegion = &split.RegionInfo{ + Leader: newLeader, + Region: currentRegion, + } + } + } + } + retryTy := retryNone + if newRegion != nil { + retryTy = retryWrite + } + return retryTy, newRegion, errors.Errorf("epoch not match: %s", errPb.GetMessage()) + case strings.Contains(errPb.Message, "raft: proposal dropped"): + // TODO: we should change 'Raft raft: proposal dropped' to a error type like 'NotLeader' + newRegion, err = getRegion() + if err != nil { + return retryNone, nil, errors.Trace(err) + } + return retryWrite, newRegion, errors.New(errPb.GetMessage()) + } + return retryNone, nil, errors.Errorf("non-retryable error: %s", resp.GetError().GetMessage()) +} + +// return the smallest []byte that is bigger than current bytes. +// special case when key is empty, empty bytes means infinity in our context, so directly return itself. +func nextKey(key []byte) []byte { + if len(key) == 0 { + return []byte{} + } + + // in tikv <= 4.x, tikv will truncate the row key, so we should fetch the next valid row key + // See: https://github.com/tikv/tikv/blob/f7f22f70e1585d7ca38a59ea30e774949160c3e8/components/raftstore/src/coprocessor/split_observer.rs#L36-L41 + if tablecodec.IsRecordKey(key) { + tableId, handle, _ := tablecodec.DecodeRecordKey(key) + return tablecodec.EncodeRowKeyWithHandle(tableId, handle.Next()) + } + + // if key is an index, directly append a 0x00 to the key. + res := make([]byte, 0, len(key)+1) + res = append(res, key...) + res = append(res, 0) + return res +} + +type rangeOffsets struct { + Size uint64 + Keys uint64 +} + +type rangeProperty struct { + Key []byte + rangeOffsets +} + +func (r *rangeProperty) Less(than btree.Item) bool { + ta := than.(*rangeProperty) + return bytes.Compare(r.Key, ta.Key) < 0 +} + +var _ btree.Item = &rangeProperty{} + +type rangeProperties []rangeProperty + +func decodeRangeProperties(data []byte) (rangeProperties, error) { + r := make(rangeProperties, 0, 16) + for len(data) > 0 { + if len(data) < 4 { + return nil, io.ErrUnexpectedEOF + } + keyLen := int(binary.BigEndian.Uint32(data[:4])) + data = data[4:] + if len(data) < keyLen+8*2 { + return nil, io.ErrUnexpectedEOF + } + key := data[:keyLen] + data = data[keyLen:] + size := binary.BigEndian.Uint64(data[:8]) + keys := binary.BigEndian.Uint64(data[8:]) + data = data[16:] + r = append(r, rangeProperty{Key: key, rangeOffsets: rangeOffsets{Size: size, Keys: keys}}) + } + + return r, nil +} + +func (r rangeProperties) Encode() []byte { + b := make([]byte, 0, 1024) + idx := 0 + for _, p := range r { + b = append(b, 0, 0, 0, 0) + binary.BigEndian.PutUint32(b[idx:], uint32(len(p.Key))) + idx += 4 + b = append(b, p.Key...) + idx += len(p.Key) + + b = append(b, 0, 0, 0, 0, 0, 0, 0, 0) + binary.BigEndian.PutUint64(b[idx:], p.Size) + idx += 8 + + b = append(b, 0, 0, 0, 0, 0, 0, 0, 0) + binary.BigEndian.PutUint64(b[idx:], p.Keys) + idx += 8 + } + return b +} + +func (r rangeProperties) get(key []byte) rangeOffsets { + idx := sort.Search(len(r), func(i int) bool { + return bytes.Compare(r[i].Key, key) >= 0 + }) + return r[idx].rangeOffsets +} + +type RangePropertiesCollector struct { + props rangeProperties + lastOffsets rangeOffsets + lastKey []byte + currentOffsets rangeOffsets + propSizeIdxDistance uint64 + propKeysIdxDistance uint64 +} + +func newRangePropertiesCollector() pebble.TablePropertyCollector { + return &RangePropertiesCollector{ + props: make([]rangeProperty, 0, 1024), + propSizeIdxDistance: defaultPropSizeIndexDistance, + propKeysIdxDistance: defaultPropKeysIndexDistance, + } +} + +func (c *RangePropertiesCollector) sizeInLastRange() uint64 { + return c.currentOffsets.Size - c.lastOffsets.Size +} + +func (c *RangePropertiesCollector) keysInLastRange() uint64 { + return c.currentOffsets.Keys - c.lastOffsets.Keys +} + +func (c *RangePropertiesCollector) insertNewPoint(key []byte) { + c.lastOffsets = c.currentOffsets + c.props = append(c.props, rangeProperty{Key: append([]byte{}, key...), rangeOffsets: c.currentOffsets}) +} + +// implement `pebble.TablePropertyCollector` +// implement `TablePropertyCollector.Add` +func (c *RangePropertiesCollector) Add(key pebble.InternalKey, value []byte) error { + c.currentOffsets.Size += uint64(len(value)) + uint64(len(key.UserKey)) + c.currentOffsets.Keys += 1 + if len(c.lastKey) == 0 || c.sizeInLastRange() >= c.propSizeIdxDistance || + c.keysInLastRange() >= c.propKeysIdxDistance { + c.insertNewPoint(key.UserKey) + } + c.lastKey = append(c.lastKey[:0], key.UserKey...) + return nil +} + +func (c *RangePropertiesCollector) Finish(userProps map[string]string) error { + if c.sizeInLastRange() > 0 || c.keysInLastRange() > 0 { + c.insertNewPoint(c.lastKey) + } + + userProps[propRangeIndex] = string(c.props.Encode()) + return nil +} + +// The name of the property collector. +func (c *RangePropertiesCollector) Name() string { + return propRangeIndex +} + +type sizeProperties struct { + totalSize uint64 + indexHandles *btree.BTree +} + +func newSizeProperties() *sizeProperties { + return &sizeProperties{indexHandles: btree.New(32)} +} + +func (s *sizeProperties) add(item *rangeProperty) { + if old := s.indexHandles.ReplaceOrInsert(item); old != nil { + o := old.(*rangeProperty) + item.Keys += o.Keys + item.Size += o.Size + } +} + +func (s *sizeProperties) addAll(props rangeProperties) { + prevRange := rangeOffsets{} + for _, r := range props { + s.add(&rangeProperty{ + Key: r.Key, + rangeOffsets: rangeOffsets{Keys: r.Keys - prevRange.Keys, Size: r.Size - prevRange.Size}, + }) + prevRange = r.rangeOffsets + } + if len(props) > 0 { + s.totalSize = props[len(props)-1].Size + } +} + +// iter the tree until f return false +func (s *sizeProperties) iter(f func(p *rangeProperty) bool) { + s.indexHandles.Ascend(func(i btree.Item) bool { + prop := i.(*rangeProperty) + return f(prop) + }) +} + +func (local *local) EngineFileSizes() (res []EngineFileSize) { + local.engines.Range(func(k, v interface{}) bool { + engine := v.(*LocalFile) + res = append(res, engine.getEngineFileSize()) + return true + }) + return +} + +type LocalWriter struct { + writeErr common.OnceError + local *LocalFile + consumeCh chan struct{} + kvsChan chan []common.KvPair + flushChMutex sync.RWMutex + flushCh chan chan error + sstDir string + memtableSizeLimit int64 + writeBatch kvMemCache + writer *sstWriter +} + +func (w *LocalWriter) AppendRows(ctx context.Context, tableName string, columnNames []string, ts uint64, rows Rows) error { + kvs := rows.(kvPairs) + if len(kvs) == 0 { + return nil + } + if err := w.writeErr.Get(); err != nil { + return err + } + w.kvsChan <- kvs + w.local.Ts = ts + return nil +} + +func (w *LocalWriter) Close() error { + w.local.localWriters.Delete(w) + close(w.kvsChan) + + w.flushChMutex.Lock() + flushCh := w.flushCh + w.flushCh = nil + w.flushChMutex.Unlock() + + // after closing kvsChan, the writeRowsLoop will ingest all cached KVs. + // during this time, the flushCh might still be receiving data. + // so we have this extra loop to immediately consume them to avoid AsyncFlush + for { + select { + case <-w.consumeCh: + return w.writeErr.Get() + case replyErrCh := <-flushCh: + replyErrCh <- nil + } + } +} + +func (w *LocalWriter) genSSTPath() string { + return filepath.Join(w.sstDir, uuid.New().String()+".sst") +} + +func (w *LocalWriter) writeRowsLoop() { + defer func() { + if w.writer != nil { + w.writer.cleanUp() + w.writer = nil + } + w.consumeCh <- struct{}{} + }() + var err error +outside: + for { + w.flushChMutex.RLock() + flushCh := w.flushCh + w.flushChMutex.RUnlock() + + select { + case kvs, ok := <-w.kvsChan: + if !ok { + break outside + } + + w.writeBatch.append(kvs) + if w.writeBatch.totalSize <= w.memtableSizeLimit { + break + } + if w.writer == nil { + w.writer, err = newSSTWriter(w.genSSTPath()) + if err != nil { + w.writeErr.Set(err) + return + } + } + + if err = w.writeKVsOrIngest(0); err != nil { + w.writeErr.Set(err) + return + } + + case replyErrCh := <-flushCh: + err = w.writeKVsOrIngest(localIngestDescriptionFlushed) + if w.writer != nil { + err = w.writer.ingestInto(w.local, localIngestDescriptionFlushed) + if err == nil { + err = w.writer.reopen() + } + } + replyErrCh <- err + if err != nil { + w.writeErr.Set(err) + return + } + } + } + + if err = w.writeKVsOrIngest(0); err != nil { + w.writeErr.Set(err) + return + } + if w.writer != nil { + if err := w.writer.ingestInto(w.local, 0); err != nil { + w.writeErr.Set(err) + } + } +} + +func (w *LocalWriter) writeKVsOrIngest(desc localIngestDescription) error { + if w.writer != nil { + if err := w.writer.writeKVs(&w.writeBatch); err != errorUnorderedSSTInsertion { + return err + } + } + + // if write failed only because of unorderedness, we immediately ingest the memcache. + immWriter, err := newSSTWriter(w.genSSTPath()) + if err != nil { + return err + } + defer immWriter.cleanUp() + + if err = immWriter.writeKVs(&w.writeBatch); err != nil { + return err + } + + return immWriter.ingestInto(w.local, desc|localIngestDescriptionImmediate) +} + +var errorUnorderedSSTInsertion = errors.New("inserting KVs into SST without order") + +type localIngestDescription uint8 + +const ( + localIngestDescriptionFlushed localIngestDescription = 1 << iota + localIngestDescriptionImmediate +) + +type sstWriter struct { + path string + writer *sstable.Writer + lastKey []byte + totalSize int64 + totalCount int64 +} + +func newSSTWriter(path string) (*sstWriter, error) { + sw := &sstWriter{path: path} + if err := sw.reopen(); err != nil { + return nil, err + } + return sw, nil +} + +// writeKVs moves the KV pairs in the cache into the SST writer. +// On success, the cache will be cleared. +func (sw *sstWriter) writeKVs(m *kvMemCache) error { + if len(m.kvs) == 0 { + return nil + } + m.sort() + if bytes.Compare(m.kvs[0].Key, sw.lastKey) <= 0 { + return errorUnorderedSSTInsertion + } + + internalKey := sstable.InternalKey{ + Trailer: uint64(sstable.InternalKeyKindSet), + } + for _, p := range m.kvs { + internalKey.UserKey = p.Key + if err := sw.writer.Add(internalKey, p.Val); err != nil { + return errors.Trace(err) + } + } + sw.totalSize += m.totalSize + sw.totalCount += int64(len(m.kvs)) + sw.lastKey = m.kvs[len(m.kvs)-1].Key + m.clear() + return nil +} + +// ingestInto finishes the SST file, and ingests itself into the target LocalFile database. +// On success, the entire writer will be reset as empty. +func (sw *sstWriter) ingestInto(e *LocalFile, desc localIngestDescription) error { + if sw.totalCount > 0 { + if err := sw.writer.Close(); err != nil { + return errors.Trace(err) + } + if desc&localIngestDescriptionFlushed == 0 { + // No need to acquire lock around ingestion when flushing. + // we already held the lock before flushing. + e.lock(importMutexStateLocalIngest) + defer e.unlock() + } + meta, _ := sw.writer.Metadata() // this method returns error only if it has not been closed yet. + log.L().Info("write data to local DB", + zap.Int64("size", sw.totalSize), + zap.Int64("kvs", sw.totalCount), + zap.Uint8("description", uint8(desc)), + zap.Uint64("sstFileSize", meta.Size), + log.ZapRedactBinary("firstKey", meta.SmallestPoint.UserKey), + log.ZapRedactBinary("lastKey", meta.LargestPoint.UserKey)) + + if err := e.db.Ingest([]string{sw.path}); err != nil { + return errors.Trace(err) + } + e.TotalSize.Add(sw.totalSize) + e.Length.Add(sw.totalCount) + sw.totalSize = 0 + sw.totalCount = 0 + sw.lastKey = nil + } + sw.writer = nil + return nil +} + +// reopen creates a new SST file after ingestInto is successful. +// Returns error if the SST file was not ingested. +func (sw *sstWriter) reopen() error { + if sw.writer != nil { + return errors.New("cannot reopen an SST writer without ingesting it first") + } + f, err := os.Create(sw.path) + if err != nil { + return errors.Trace(err) + } + sw.writer = sstable.NewWriter(f, sstable.WriterOptions{ + TablePropertyCollectors: []func() pebble.TablePropertyCollector{ + newRangePropertiesCollector, + }, + BlockSize: 16 * 1024, + }) + return nil +} + +// cleanUp removes any un-ingested SST file. +func (sw *sstWriter) cleanUp() { + if sw.writer != nil { + sw.writer.Close() + os.Remove(sw.path) + } +} + +// kvMemCache is an array of KV pairs. It also keep tracks of the total KV size and whether the array is already sorted. +type kvMemCache struct { + kvs []common.KvPair + totalSize int64 + notSorted bool // record "not sorted" instead of "sorted" so that the zero value is correct. +} + +// append more KV pairs to the kvMemCache. +func (m *kvMemCache) append(kvs []common.KvPair) { + if !m.notSorted { + var lastKey []byte + if len(m.kvs) > 0 { + lastKey = m.kvs[len(m.kvs)-1].Key + } + for _, kv := range kvs { + if bytes.Compare(kv.Key, lastKey) <= 0 { + m.notSorted = true + break + } + lastKey = kv.Key + } + } + + m.kvs = append(m.kvs, kvs...) + for _, kv := range kvs { + m.totalSize += int64(len(kv.Key)) + int64(len(kv.Val)) + } +} + +// sort ensures the content is actually sorted. +func (m *kvMemCache) sort() { + if m.notSorted { + sort.Slice(m.kvs, func(i, j int) bool { return bytes.Compare(m.kvs[i].Key, m.kvs[j].Key) < 0 }) + m.notSorted = false + } +} + +// clear resets the cache to contain nothing. +func (m *kvMemCache) clear() { + m.kvs = m.kvs[:0] + m.totalSize = 0 + m.notSorted = false +} diff --git a/pkg/lightning/backend/local_test.go b/pkg/lightning/backend/local_test.go new file mode 100644 index 000000000..12f68bbe3 --- /dev/null +++ b/pkg/lightning/backend/local_test.go @@ -0,0 +1,475 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package backend + +import ( + "bytes" + "context" + "encoding/binary" + "math" + "math/rand" + "os" + "path/filepath" + "sort" + + "github.com/cockroachdb/pebble" + . "github.com/pingcap/check" + "github.com/pingcap/kvproto/pkg/errorpb" + sst "github.com/pingcap/kvproto/pkg/import_sstpb" + "github.com/pingcap/kvproto/pkg/metapb" + "github.com/pingcap/tidb/kv" + "github.com/pingcap/tidb/sessionctx/stmtctx" + "github.com/pingcap/tidb/tablecodec" + "github.com/pingcap/tidb/types" + "github.com/pingcap/tidb/util/codec" + "github.com/pingcap/tidb/util/hack" + + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/restore" +) + +type localSuite struct{} + +var _ = Suite(&localSuite{}) + +func (s *localSuite) TestNextKey(c *C) { + c.Assert(nextKey([]byte{}), DeepEquals, []byte{}) + + cases := [][]byte{ + {0}, + {255}, + {1, 255}, + } + for _, b := range cases { + next := nextKey(b) + c.Assert(next, DeepEquals, append(b, 0)) + } + + // in the old logic, this should return []byte{} which is not the actually smallest eky + next := nextKey([]byte{1, 255}) + c.Assert(bytes.Compare(next, []byte{2}), Equals, -1) + + // another test case, nextkey()'s return should be smaller than key with a prefix of the origin key + next = nextKey([]byte{1, 255}) + c.Assert(bytes.Compare(next, []byte{1, 255, 0, 1, 2}), Equals, -1) + + // test recode key + // key with int handle + for _, handleId := range []int64{1, 255, math.MaxInt32} { + key := tablecodec.EncodeRowKeyWithHandle(1, kv.IntHandle(handleId)) + c.Assert(nextKey(key), DeepEquals, []byte(tablecodec.EncodeRowKeyWithHandle(1, kv.IntHandle(handleId+1)))) + } + + testDatums := [][]types.Datum{ + {types.NewIntDatum(1), types.NewIntDatum(2)}, + {types.NewIntDatum(255), types.NewIntDatum(256)}, + {types.NewIntDatum(math.MaxInt32), types.NewIntDatum(math.MaxInt32 + 1)}, + {types.NewStringDatum("test"), types.NewStringDatum("test\000")}, + {types.NewStringDatum("test\255"), types.NewStringDatum("test\255\000")}, + } + + stmtCtx := new(stmtctx.StatementContext) + for _, datums := range testDatums { + keyBytes, err := codec.EncodeKey(stmtCtx, nil, types.NewIntDatum(123), datums[0]) + c.Assert(err, IsNil) + h, err := kv.NewCommonHandle(keyBytes) + c.Assert(err, IsNil) + key := tablecodec.EncodeRowKeyWithHandle(1, h) + nextKeyBytes, err := codec.EncodeKey(stmtCtx, nil, types.NewIntDatum(123), datums[1]) + c.Assert(err, IsNil) + nextHdl, err := kv.NewCommonHandle(nextKeyBytes) + c.Assert(err, IsNil) + expectNextKey := []byte(tablecodec.EncodeRowKeyWithHandle(1, nextHdl)) + c.Assert(nextKey(key), DeepEquals, expectNextKey) + } + + // dIAAAAAAAAD/PV9pgAAAAAD/AAABA4AAAAD/AAAAAQOAAAD/AAAAAAEAAAD8 + // a index key with: table: 61, index: 1, int64: 1, int64: 1 + a := []byte{116, 128, 0, 0, 0, 0, 0, 0, 255, 61, 95, 105, 128, 0, 0, 0, 0, 255, 0, 0, 1, 3, 128, 0, 0, 0, 255, 0, 0, 0, 1, 3, 128, 0, 0, 255, 0, 0, 0, 0, 1, 0, 0, 0, 252} + c.Assert(nextKey(a), DeepEquals, append(a, 0)) +} + +// The first half of this test is same as the test in tikv: +// https://github.com/tikv/tikv/blob/dbfe7730dd0fddb34cb8c3a7f8a079a1349d2d41/components/engine_rocks/src/properties.rs#L572 +func (s *localSuite) TestRangeProperties(c *C) { + type testCase struct { + key []byte + vLen int + count int + } + cases := []testCase{ + // handle "a": size(size = 1, offset = 1),keys(1,1) + {[]byte("a"), 0, 1}, + {[]byte("b"), defaultPropSizeIndexDistance / 8, 1}, + {[]byte("c"), defaultPropSizeIndexDistance / 4, 1}, + {[]byte("d"), defaultPropSizeIndexDistance / 2, 1}, + {[]byte("e"), defaultPropSizeIndexDistance / 8, 1}, + // handle "e": size(size = DISTANCE + 4, offset = DISTANCE + 5),keys(4,5) + {[]byte("f"), defaultPropSizeIndexDistance / 4, 1}, + {[]byte("g"), defaultPropSizeIndexDistance / 2, 1}, + {[]byte("h"), defaultPropSizeIndexDistance / 8, 1}, + {[]byte("i"), defaultPropSizeIndexDistance / 4, 1}, + // handle "i": size(size = DISTANCE / 8 * 9 + 4, offset = DISTANCE / 8 * 17 + 9),keys(4,5) + {[]byte("j"), defaultPropSizeIndexDistance / 2, 1}, + {[]byte("k"), defaultPropSizeIndexDistance / 2, 1}, + // handle "k": size(size = DISTANCE + 2, offset = DISTANCE / 8 * 25 + 11),keys(2,11) + {[]byte("l"), 0, defaultPropKeysIndexDistance / 2}, + {[]byte("m"), 0, defaultPropKeysIndexDistance / 2}, + // handle "m": keys = DEFAULT_PROP_KEYS_INDEX_DISTANCE,offset = 11+DEFAULT_PROP_KEYS_INDEX_DISTANCE + {[]byte("n"), 1, defaultPropKeysIndexDistance}, + // handle "n": keys = DEFAULT_PROP_KEYS_INDEX_DISTANCE, offset = 11+2*DEFAULT_PROP_KEYS_INDEX_DISTANCE + {[]byte("o"), 1, 1}, + // handle "o": keys = 1, offset = 12 + 2*DEFAULT_PROP_KEYS_INDEX_DISTANCE + } + + collector := newRangePropertiesCollector() + for _, p := range cases { + v := make([]byte, p.vLen) + for i := 0; i < p.count; i++ { + _ = collector.Add(pebble.InternalKey{UserKey: p.key}, v) + } + } + + userProperties := make(map[string]string, 1) + _ = collector.Finish(userProperties) + + props, err := decodeRangeProperties(hack.Slice(userProperties[propRangeIndex])) + c.Assert(err, IsNil) + + // Smallest key in props. + c.Assert(props[0].Key, DeepEquals, cases[0].key) + // Largest key in props. + c.Assert(props[len(props)-1].Key, DeepEquals, cases[len(cases)-1].key) + c.Assert(len(props), Equals, 7) + + a := props.get([]byte("a")) + c.Assert(a.Size, Equals, uint64(1)) + e := props.get([]byte("e")) + c.Assert(e.Size, Equals, uint64(defaultPropSizeIndexDistance+5)) + i := props.get([]byte("i")) + c.Assert(i.Size, Equals, uint64(defaultPropSizeIndexDistance/8*17+9)) + k := props.get([]byte("k")) + c.Assert(k.Size, Equals, uint64(defaultPropSizeIndexDistance/8*25+11)) + m := props.get([]byte("m")) + c.Assert(m.Keys, Equals, uint64(defaultPropKeysIndexDistance+11)) + n := props.get([]byte("n")) + c.Assert(n.Keys, Equals, uint64(defaultPropKeysIndexDistance*2+11)) + o := props.get([]byte("o")) + c.Assert(o.Keys, Equals, uint64(defaultPropKeysIndexDistance*2+12)) + + props2 := rangeProperties([]rangeProperty{ + {[]byte("b"), rangeOffsets{defaultPropSizeIndexDistance + 10, defaultPropKeysIndexDistance / 2}}, + {[]byte("h"), rangeOffsets{defaultPropSizeIndexDistance * 3 / 2, defaultPropKeysIndexDistance * 3 / 2}}, + {[]byte("k"), rangeOffsets{defaultPropSizeIndexDistance * 3, defaultPropKeysIndexDistance * 7 / 4}}, + {[]byte("mm"), rangeOffsets{defaultPropSizeIndexDistance * 5, defaultPropKeysIndexDistance * 2}}, + {[]byte("q"), rangeOffsets{defaultPropSizeIndexDistance * 7, defaultPropKeysIndexDistance*9/4 + 10}}, + {[]byte("y"), rangeOffsets{defaultPropSizeIndexDistance*7 + 100, defaultPropKeysIndexDistance*9/4 + 1010}}, + }) + + sizeProps := newSizeProperties() + sizeProps.addAll(props) + sizeProps.addAll(props2) + + res := []*rangeProperty{ + {[]byte("a"), rangeOffsets{1, 1}}, + {[]byte("b"), rangeOffsets{defaultPropSizeIndexDistance + 10, defaultPropKeysIndexDistance / 2}}, + {[]byte("e"), rangeOffsets{defaultPropSizeIndexDistance + 4, 4}}, + {[]byte("h"), rangeOffsets{defaultPropSizeIndexDistance/2 - 10, defaultPropKeysIndexDistance}}, + {[]byte("i"), rangeOffsets{defaultPropSizeIndexDistance*9/8 + 4, 4}}, + {[]byte("k"), rangeOffsets{defaultPropSizeIndexDistance*5/2 + 2, defaultPropKeysIndexDistance/4 + 2}}, + {[]byte("m"), rangeOffsets{defaultPropKeysIndexDistance, defaultPropKeysIndexDistance}}, + {[]byte("mm"), rangeOffsets{defaultPropSizeIndexDistance * 2, defaultPropKeysIndexDistance / 4}}, + {[]byte("n"), rangeOffsets{defaultPropKeysIndexDistance * 2, defaultPropKeysIndexDistance}}, + {[]byte("o"), rangeOffsets{2, 1}}, + {[]byte("q"), rangeOffsets{defaultPropSizeIndexDistance * 2, defaultPropKeysIndexDistance/4 + 10}}, + {[]byte("y"), rangeOffsets{100, 1000}}, + } + + c.Assert(sizeProps.indexHandles.Len(), Equals, 12) + idx := 0 + sizeProps.iter(func(p *rangeProperty) bool { + c.Assert(p, DeepEquals, res[idx]) + idx++ + return true + }) + + fullRange := Range{start: []byte("a"), end: []byte("z")} + ranges := splitRangeBySizeProps(fullRange, sizeProps, 2*defaultPropSizeIndexDistance, defaultPropKeysIndexDistance*5/2) + + c.Assert(ranges, DeepEquals, []Range{ + {start: []byte("a"), end: []byte("e")}, + {start: []byte("e"), end: []byte("k")}, + {start: []byte("k"), end: []byte("mm")}, + {start: []byte("mm"), end: []byte("q")}, + {start: []byte("q"), end: []byte("z")}, + }) + + ranges = splitRangeBySizeProps(fullRange, sizeProps, 2*defaultPropSizeIndexDistance, defaultPropKeysIndexDistance) + c.Assert(ranges, DeepEquals, []Range{ + {start: []byte("a"), end: []byte("e")}, + {start: []byte("e"), end: []byte("h")}, + {start: []byte("h"), end: []byte("k")}, + {start: []byte("k"), end: []byte("m")}, + {start: []byte("m"), end: []byte("mm")}, + {start: []byte("mm"), end: []byte("n")}, + {start: []byte("n"), end: []byte("q")}, + {start: []byte("q"), end: []byte("z")}, + }) +} + +func (s *localSuite) TestRangePropertiesWithPebble(c *C) { + dir := c.MkDir() + + sizeDistance := uint64(500) + keysDistance := uint64(20) + opt := &pebble.Options{ + MemTableSize: LocalMemoryTableSize, + MaxConcurrentCompactions: 16, + L0CompactionThreshold: math.MaxInt32, // set to max try to disable compaction + L0StopWritesThreshold: math.MaxInt32, // set to max try to disable compaction + MaxOpenFiles: 10000, + DisableWAL: true, + ReadOnly: false, + TablePropertyCollectors: []func() pebble.TablePropertyCollector{ + func() pebble.TablePropertyCollector { + return &RangePropertiesCollector{ + props: make([]rangeProperty, 0, 1024), + propSizeIdxDistance: sizeDistance, + propKeysIdxDistance: keysDistance, + } + }, + }, + } + db, err := pebble.Open(filepath.Join(dir, "test"), opt) + c.Assert(err, IsNil) + defer db.Close() + + // local collector + collector := &RangePropertiesCollector{ + props: make([]rangeProperty, 0, 1024), + propSizeIdxDistance: sizeDistance, + propKeysIdxDistance: keysDistance, + } + writeOpt := &pebble.WriteOptions{Sync: false} + value := make([]byte, 100) + for i := 0; i < 10; i++ { + wb := db.NewBatch() + for j := 0; j < 100; j++ { + key := make([]byte, 8) + valueLen := rand.Intn(50) + binary.BigEndian.PutUint64(key, uint64(i*100+j)) + err = wb.Set(key, value[:valueLen], writeOpt) + c.Assert(err, IsNil) + err = collector.Add(pebble.InternalKey{UserKey: key}, value[:valueLen]) + c.Assert(err, IsNil) + } + c.Assert(wb.Commit(writeOpt), IsNil) + } + // flush one sst + c.Assert(db.Flush(), IsNil) + + props := make(map[string]string, 1) + c.Assert(collector.Finish(props), IsNil) + + sstMetas, err := db.SSTables(pebble.WithProperties()) + c.Assert(err, IsNil) + for i, level := range sstMetas { + if i == 0 { + c.Assert(len(level), Equals, 1) + } else { + c.Assert(len(level), Equals, 0) + } + } + + c.Assert(sstMetas[0][0].Properties.UserProperties, DeepEquals, props) +} + +func testLocalWriter(c *C, needSort bool, partitialSort bool) { + dir := c.MkDir() + opt := &pebble.Options{ + MemTableSize: 1024 * 1024, + MaxConcurrentCompactions: 16, + L0CompactionThreshold: math.MaxInt32, // set to max try to disable compaction + L0StopWritesThreshold: math.MaxInt32, // set to max try to disable compaction + DisableWAL: true, + ReadOnly: false, + } + db, err := pebble.Open(filepath.Join(dir, "test"), opt) + c.Assert(err, IsNil) + tmpPath := filepath.Join(dir, "tmp") + err = os.Mkdir(tmpPath, 0o755) + c.Assert(err, IsNil) + meta := localFileMeta{} + _, engineUUID := MakeUUID("ww", 0) + f := LocalFile{localFileMeta: meta, db: db, Uuid: engineUUID} + w := openLocalWriter(&f, tmpPath, 1024*1024) + + ctx := context.Background() + // kvs := make(kvPairs, 1000) + var kvs kvPairs + value := make([]byte, 128) + for i := 0; i < 16; i++ { + binary.BigEndian.PutUint64(value[i*8:], uint64(i)) + } + var keys [][]byte + for i := 1; i <= 20000; i++ { + var kv common.KvPair + kv.Key = make([]byte, 16) + kv.Val = make([]byte, 128) + copy(kv.Val, value) + key := rand.Intn(1000) + binary.BigEndian.PutUint64(kv.Key, uint64(key)) + binary.BigEndian.PutUint64(kv.Key[8:], uint64(i)) + kvs = append(kvs, kv) + keys = append(keys, kv.Key) + } + var rows1 kvPairs + var rows2 kvPairs + var rows3 kvPairs + rows4 := kvs[:12000] + if partitialSort { + sort.Slice(rows4, func(i, j int) bool { + return bytes.Compare(rows4[i].Key, rows4[j].Key) < 0 + }) + rows1 = rows4[:6000] + rows3 = rows4[6000:] + rows2 = kvs[12000:] + } else { + if needSort { + sort.Slice(kvs, func(i, j int) bool { + return bytes.Compare(kvs[i].Key, kvs[j].Key) < 0 + }) + } + rows1 = kvs[:6000] + rows2 = kvs[6000:12000] + rows3 = kvs[12000:] + } + err = w.AppendRows(ctx, "", []string{}, 1, rows1) + c.Assert(err, IsNil) + err = w.AppendRows(ctx, "", []string{}, 1, rows2) + c.Assert(err, IsNil) + err = w.AppendRows(ctx, "", []string{}, 1, rows3) + c.Assert(err, IsNil) + err = w.Close() + c.Assert(err, IsNil) + err = db.Flush() + c.Assert(err, IsNil) + o := &pebble.IterOptions{} + it := db.NewIter(o) + + sort.Slice(keys, func(i, j int) bool { + return bytes.Compare(keys[i], keys[j]) < 0 + }) + c.Assert(int(f.Length.Load()), Equals, 20000) + c.Assert(int(f.TotalSize.Load()), Equals, 144*20000) + valid := it.SeekGE(keys[0]) + c.Assert(valid, IsTrue) + for _, k := range keys { + c.Assert(it.Key(), DeepEquals, k) + it.Next() + } +} + +func (s *localSuite) TestLocalWriterWithSort(c *C) { + testLocalWriter(c, false, false) +} + +func (s *localSuite) TestLocalWriterWithIngest(c *C) { + testLocalWriter(c, true, false) +} + +func (s *localSuite) TestLocalWriterWithIngestUnsort(c *C) { + testLocalWriter(c, true, true) +} + +type mockSplitClient struct { + restore.SplitClient +} + +func (c *mockSplitClient) GetRegion(ctx context.Context, key []byte) (*restore.RegionInfo, error) { + return &restore.RegionInfo{ + Leader: &metapb.Peer{Id: 1}, + Region: &metapb.Region{ + Id: 1, + StartKey: key, + }, + }, nil +} + +func (s *localSuite) TestIsIngestRetryable(c *C) { + local := &local{ + splitCli: &mockSplitClient{}, + } + + resp := &sst.IngestResponse{ + Error: &errorpb.Error{ + NotLeader: &errorpb.NotLeader{ + Leader: &metapb.Peer{Id: 2}, + }, + }, + } + ctx := context.Background() + region := &restore.RegionInfo{ + Leader: &metapb.Peer{Id: 1}, + Region: &metapb.Region{ + Id: 1, + StartKey: []byte{1}, + EndKey: []byte{3}, + RegionEpoch: &metapb.RegionEpoch{ + ConfVer: 1, + Version: 1, + }, + }, + } + meta := &sst.SSTMeta{ + Range: &sst.Range{ + Start: []byte{1}, + End: []byte{2}, + }, + } + retryType, newRegion, err := local.isIngestRetryable(ctx, resp, region, meta) + c.Assert(retryType, Equals, retryWrite) + c.Assert(newRegion.Leader.Id, Equals, uint64(2)) + c.Assert(err, NotNil) + + resp.Error = &errorpb.Error{ + EpochNotMatch: &errorpb.EpochNotMatch{ + CurrentRegions: []*metapb.Region{ + { + Id: 1, + StartKey: []byte{1}, + EndKey: []byte{3}, + RegionEpoch: &metapb.RegionEpoch{ + ConfVer: 1, + Version: 2, + }, + Peers: []*metapb.Peer{{Id: 1}}, + }, + }, + }, + } + retryType, newRegion, err = local.isIngestRetryable(ctx, resp, region, meta) + c.Assert(retryType, Equals, retryWrite) + c.Assert(newRegion.Region.RegionEpoch.Version, Equals, uint64(2)) + c.Assert(err, NotNil) + + resp.Error = &errorpb.Error{Message: "raft: proposal dropped"} + retryType, newRegion, err = local.isIngestRetryable(ctx, resp, region, meta) + c.Assert(retryType, Equals, retryWrite) + + resp.Error = &errorpb.Error{Message: "unknown error"} + retryType, newRegion, err = local.isIngestRetryable(ctx, resp, region, meta) + c.Assert(retryType, Equals, retryNone) + c.Assert(err, ErrorMatches, "non-retryable error: unknown error") +} diff --git a/pkg/lightning/backend/local_unix.go b/pkg/lightning/backend/local_unix.go new file mode 100644 index 000000000..f6ff59130 --- /dev/null +++ b/pkg/lightning/backend/local_unix.go @@ -0,0 +1,92 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !windows + +package backend + +import ( + "syscall" + + "github.com/pingcap/errors" + "github.com/pingcap/failpoint" + "go.uber.org/zap" + + "github.com/pingcap/br/pkg/lightning/log" +) + +const ( + // mininum max open files value + minRLimit = 1024 +) + +func GetSystemRLimit() (uint64, error) { + var rLimit syscall.Rlimit + err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit) + return rLimit.Cur, err +} + +// VerifyRLimit checks whether the open-file limit is large enough. +// In Local-backend, we need to read and write a lot of L0 SST files, so we need +// to check system max open files limit. +func VerifyRLimit(estimateMaxFiles uint64) error { + if estimateMaxFiles < minRLimit { + estimateMaxFiles = minRLimit + } + var rLimit syscall.Rlimit + err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit) + failpoint.Inject("GetRlimitValue", func(v failpoint.Value) { + limit := uint64(v.(int)) + rLimit.Cur = limit + rLimit.Max = limit + err = nil + }) + if err != nil { + return errors.Trace(err) + } + if rLimit.Cur >= estimateMaxFiles { + return nil + } + if rLimit.Max < estimateMaxFiles { + // If the process is not started by privileged user, this will fail. + rLimit.Max = estimateMaxFiles + } + prevLimit := rLimit.Cur + rLimit.Cur = estimateMaxFiles + failpoint.Inject("SetRlimitError", func(v failpoint.Value) { + if v.(bool) { + err = errors.New("Setrlimit Injected Error") + } + }) + if err == nil { + err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) + } + if err != nil { + return errors.Annotatef(err, "the maximum number of open file descriptors is too small, got %d, expect greater or equal to %d", prevLimit, estimateMaxFiles) + } + + // fetch the rlimit again to make sure our setting has taken effect + err = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit) + if err != nil { + return errors.Trace(err) + } + if rLimit.Cur < estimateMaxFiles { + helper := "Please manually execute `ulimit -n %d` to increase the open files limit." + return errors.Errorf("cannot update the maximum number of open file descriptors, expected: %d, got: %d. %s", + estimateMaxFiles, rLimit.Cur, helper) + } + + log.L().Info("Set the maximum number of open file descriptors(rlimit)", + zap.Uint64("old", prevLimit), zap.Uint64("new", estimateMaxFiles)) + return nil +} diff --git a/pkg/lightning/backend/local_windows.go b/pkg/lightning/backend/local_windows.go new file mode 100644 index 000000000..670f83e7d --- /dev/null +++ b/pkg/lightning/backend/local_windows.go @@ -0,0 +1,31 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build windows + +package backend + +import ( + "math" + + "github.com/pingcap/errors" +) + +// return a big value as unlimited, since rlimit verify is skipped in windows. +func GetSystemRLimit() (uint64, error) { + return math.MaxInt32, nil +} + +func VerifyRLimit(estimateMaxFiles uint64) error { + return errors.New("Local-backend is not tested on Windows. Run with --check-requirements=false to disable this check, but you are on your own risk.") +} diff --git a/pkg/lightning/backend/localhelper.go b/pkg/lightning/backend/localhelper.go new file mode 100644 index 000000000..1051c620d --- /dev/null +++ b/pkg/lightning/backend/localhelper.go @@ -0,0 +1,435 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package backend + +import ( + "bytes" + "context" + "encoding/hex" + "regexp" + "sort" + "strings" + "time" + + "github.com/pingcap/errors" + sst "github.com/pingcap/kvproto/pkg/import_sstpb" + "github.com/pingcap/kvproto/pkg/metapb" + "github.com/pingcap/kvproto/pkg/pdpb" + "github.com/pingcap/tidb/util/codec" + "go.uber.org/zap" + + "github.com/pingcap/br/pkg/lightning/log" + split "github.com/pingcap/br/pkg/restore" + "github.com/pingcap/br/pkg/utils" +) + +const ( + SplitRetryTimes = 8 + retrySplitMaxWaitTime = 4 * time.Second +) + +var ( + // the max keys count in a batch to split one region + maxBatchSplitKeys = 4096 + // the base exponential backoff time + // the variable is only changed in unit test for running test faster. + splitRegionBaseBackOffTime = time.Second +) + +// TODO remove this file and use br internal functions +// This File include region split & scatter operation just like br. +// we can simply call br function, but we need to change some function signature of br +// When the ranges total size is small, we can skip the split to avoid generate empty regions. +func (local *local) SplitAndScatterRegionByRanges(ctx context.Context, ranges []Range, needSplit bool) error { + if len(ranges) == 0 { + return nil + } + + minKey := codec.EncodeBytes([]byte{}, ranges[0].start) + maxKey := codec.EncodeBytes([]byte{}, ranges[len(ranges)-1].end) + + var err error + scatterRegions := make([]*split.RegionInfo, 0) + var retryKeys [][]byte + waitTime := splitRegionBaseBackOffTime + for i := 0; i < SplitRetryTimes; i++ { + log.L().Info("split and scatter region", + log.ZapRedactBinary("minKey", minKey), + log.ZapRedactBinary("maxKey", maxKey), + zap.Int("retry", i), + ) + if i > 0 { + select { + case <-time.After(waitTime): + case <-ctx.Done(): + return ctx.Err() + } + waitTime *= 2 + if waitTime > retrySplitMaxWaitTime { + waitTime = retrySplitMaxWaitTime + } + } + var regions []*split.RegionInfo + regions, err = paginateScanRegion(ctx, local.splitCli, minKey, maxKey, 128) + if err != nil { + log.L().Warn("paginate scan region failed", log.ZapRedactBinary("minKey", minKey), log.ZapRedactBinary("maxKey", maxKey), + log.ShortError(err), zap.Int("retry", i)) + continue + } + + if len(regions) == 0 { + log.L().Warn("paginate scan region returns empty result", log.ZapRedactBinary("minKey", minKey), log.ZapRedactBinary("maxKey", maxKey), + zap.Int("retry", i)) + return errors.New("paginate scan region returns empty result") + } + + log.L().Info("paginate scan region finished", log.ZapRedactBinary("minKey", minKey), log.ZapRedactBinary("maxKey", maxKey), + zap.Int("regions", len(regions))) + + if !needSplit { + scatterRegions = append(scatterRegions, regions...) + break + } + + regionMap := make(map[uint64]*split.RegionInfo) + for _, region := range regions { + regionMap[region.Region.GetId()] = region + } + + var splitKeyMap map[uint64][][]byte + if len(retryKeys) > 0 { + firstKeyEnc := codec.EncodeBytes([]byte{}, retryKeys[0]) + lastKeyEnc := codec.EncodeBytes([]byte{}, retryKeys[len(retryKeys)-1]) + if bytes.Compare(firstKeyEnc, regions[0].Region.StartKey) < 0 || !beforeEnd(lastKeyEnc, regions[len(regions)-1].Region.EndKey) { + log.L().Warn("no valid key for split region", + log.ZapRedactBinary("firstKey", firstKeyEnc), log.ZapRedactBinary("lastKey", lastKeyEnc), + log.ZapRedactBinary("firstRegionStart", regions[0].Region.StartKey), + log.ZapRedactBinary("lastRegionEnd", regions[len(regions)-1].Region.EndKey)) + return errors.New("check split keys failed") + } + splitKeyMap = getSplitKeys(retryKeys, regions) + retryKeys = retryKeys[:0] + } else { + splitKeyMap = getSplitKeysByRanges(ranges, regions) + } + for regionID, keys := range splitKeyMap { + var newRegions []*split.RegionInfo + region := regionMap[regionID] + sort.Slice(keys, func(i, j int) bool { + return bytes.Compare(keys[i], keys[j]) < 0 + }) + splitRegion := region + for j := 0; j < (len(keys)+maxBatchSplitKeys-1)/maxBatchSplitKeys; j++ { + start := j * maxBatchSplitKeys + end := utils.MinInt((j+1)*maxBatchSplitKeys, len(keys)) + splitRegionStart := codec.EncodeBytes([]byte{}, keys[start]) + splitRegionEnd := codec.EncodeBytes([]byte{}, keys[end-1]) + if bytes.Compare(splitRegionStart, splitRegion.Region.StartKey) < 0 || !beforeEnd(splitRegionEnd, splitRegion.Region.EndKey) { + log.L().Fatal("no valid key in region", + log.ZapRedactBinary("startKey", splitRegionStart), log.ZapRedactBinary("endKey", splitRegionEnd), + log.ZapRedactBinary("regionStart", splitRegion.Region.StartKey), log.ZapRedactBinary("regionEnd", splitRegion.Region.EndKey), + log.ZapRedactReflect("region", splitRegion)) + } + splitRegion, newRegions, err = local.BatchSplitRegions(ctx, splitRegion, keys[start:end]) + if err != nil { + if strings.Contains(err.Error(), "no valid key") { + for _, key := range keys { + log.L().Warn("no valid key", + log.ZapRedactBinary("startKey", region.Region.StartKey), + log.ZapRedactBinary("endKey", region.Region.EndKey), + log.ZapRedactBinary("key", codec.EncodeBytes([]byte{}, key))) + } + return errors.Trace(err) + } + log.L().Warn("split regions", log.ShortError(err), zap.Int("retry time", j+1), + zap.Uint64("region_id", regionID)) + retryKeys = append(retryKeys, keys[start:]...) + break + } else { + log.L().Info("batch split region", zap.Uint64("region_id", splitRegion.Region.Id), + zap.Int("keys", end-start), zap.Binary("firstKey", keys[start]), + zap.Binary("end", keys[end-1])) + sort.Slice(newRegions, func(i, j int) bool { + return bytes.Compare(newRegions[i].Region.StartKey, newRegions[j].Region.StartKey) < 0 + }) + scatterRegions = append(scatterRegions, newRegions...) + // the region with the max start key is the region need to be further split. + if bytes.Compare(splitRegion.Region.StartKey, newRegions[len(newRegions)-1].Region.StartKey) < 0 { + splitRegion = newRegions[len(newRegions)-1] + } + } + } + } + if len(retryKeys) == 0 { + break + } else { + sort.Slice(retryKeys, func(i, j int) bool { + return bytes.Compare(retryKeys[i], retryKeys[j]) < 0 + }) + minKey = codec.EncodeBytes([]byte{}, retryKeys[0]) + maxKey = codec.EncodeBytes([]byte{}, nextKey(retryKeys[len(retryKeys)-1])) + } + } + if err != nil { + return errors.Trace(err) + } + + startTime := time.Now() + scatterCount := 0 + for _, region := range scatterRegions { + local.waitForScatterRegion(ctx, region) + if time.Since(startTime) > split.ScatterWaitUpperInterval { + break + } + scatterCount++ + } + if scatterCount == len(scatterRegions) { + log.L().Info("waiting for scattering regions done", + zap.Int("regions", len(scatterRegions)), zap.Duration("take", time.Since(startTime))) + } else { + log.L().Info("waiting for scattering regions timeout", + zap.Int("scatterCount", scatterCount), + zap.Int("regions", len(scatterRegions)), + zap.Duration("take", time.Since(startTime))) + } + return nil +} + +func paginateScanRegion( + ctx context.Context, client split.SplitClient, startKey, endKey []byte, limit int, +) ([]*split.RegionInfo, error) { + if len(endKey) != 0 && bytes.Compare(startKey, endKey) >= 0 { + log.L().Error("startKey > endKey when paginating scan region", + log.ZapRedactString("startKey", hex.EncodeToString(startKey)), + log.ZapRedactString("endKey", hex.EncodeToString(endKey))) + return nil, errors.Errorf("startKey > endKey when paginating scan region") + } + + var regions []*split.RegionInfo + for { + batch, err := client.ScanRegions(ctx, startKey, endKey, limit) + if err != nil { + return nil, errors.Trace(err) + } + regions = append(regions, batch...) + if len(batch) < limit { + // No more region + break + } + startKey = batch[len(batch)-1].Region.GetEndKey() + if len(startKey) == 0 || + (len(endKey) > 0 && bytes.Compare(startKey, endKey) >= 0) { + // All key space have scanned + break + } + } + return regions, nil +} + +func (local *local) BatchSplitRegions(ctx context.Context, region *split.RegionInfo, keys [][]byte) (*split.RegionInfo, []*split.RegionInfo, error) { + region, newRegions, err := local.splitCli.BatchSplitRegionsWithOrigin(ctx, region, keys) + if err != nil { + return nil, nil, errors.Annotatef(err, "batch split regions failed") + } + var failedErr error + retryRegions := make([]*split.RegionInfo, 0) + scatterRegions := newRegions + waitTime := splitRegionBaseBackOffTime + for i := 0; i < maxRetryTimes; i++ { + for _, region := range scatterRegions { + // Wait for a while until the regions successfully splits. + local.waitForSplit(ctx, region.Region.Id) + if err = local.splitCli.ScatterRegion(ctx, region); err != nil { + failedErr = err + retryRegions = append(retryRegions, region) + } + } + if len(retryRegions) == 0 { + break + } + // the scatter operation likely fails because region replicate not finish yet + // pack them to one log to avoid printing a lot warn logs. + log.L().Warn("scatter region failed", zap.Int("regionCount", len(newRegions)), + zap.Int("failedCount", len(retryRegions)), zap.Error(failedErr), zap.Int("retry", i)) + scatterRegions = retryRegions + retryRegions = make([]*split.RegionInfo, 0) + select { + case <-time.After(waitTime): + case <-ctx.Done(): + return nil, nil, ctx.Err() + } + waitTime *= 2 + } + + return region, newRegions, nil +} + +func (local *local) hasRegion(ctx context.Context, regionID uint64) (bool, error) { + regionInfo, err := local.splitCli.GetRegionByID(ctx, regionID) + if err != nil { + return false, err + } + return regionInfo != nil, nil +} + +func (local *local) waitForSplit(ctx context.Context, regionID uint64) { + for i := 0; i < split.SplitCheckMaxRetryTimes; i++ { + ok, err := local.hasRegion(ctx, regionID) + if err != nil { + log.L().Info("wait for split failed", log.ShortError(err)) + return + } + if ok { + break + } + select { + case <-time.After(time.Second): + case <-ctx.Done(): + return + } + } +} + +func (local *local) waitForScatterRegion(ctx context.Context, regionInfo *split.RegionInfo) { + regionID := regionInfo.Region.GetId() + for i := 0; i < split.ScatterWaitMaxRetryTimes; i++ { + ok, err := local.isScatterRegionFinished(ctx, regionID) + if err != nil { + log.L().Warn("scatter region failed: do not have the region", + log.ZapRedactStringer("region", regionInfo.Region)) + return + } + if ok { + break + } + select { + case <-time.After(time.Second): + case <-ctx.Done(): + return + } + } +} + +func (local *local) isScatterRegionFinished(ctx context.Context, regionID uint64) (bool, error) { + resp, err := local.splitCli.GetOperator(ctx, regionID) + if err != nil { + return false, err + } + // Heartbeat may not be sent to PD + if respErr := resp.GetHeader().GetError(); respErr != nil { + if respErr.GetType() == pdpb.ErrorType_REGION_NOT_FOUND { + return true, nil + } + // don't return error if region replicate not complete + // TODO: should add a new error type to avoid this check by string matching + matches, _ := regexp.MatchString("region \\d+ is not fully replicated", respErr.Message) + if matches { + return false, nil + } + return false, errors.Errorf("get operator error: %s", respErr.GetType()) + } + // If the current operator of the region is not 'scatter-region', we could assume + // that 'scatter-operator' has finished or timeout + ok := string(resp.GetDesc()) != "scatter-region" || resp.GetStatus() != pdpb.OperatorStatus_RUNNING + return ok, nil +} + +func getSplitKeysByRanges(ranges []Range, regions []*split.RegionInfo) map[uint64][][]byte { + checkKeys := make([][]byte, 0) + var lastEnd []byte + for _, rg := range ranges { + if !bytes.Equal(lastEnd, rg.start) { + checkKeys = append(checkKeys, rg.start) + } + checkKeys = append(checkKeys, rg.end) + lastEnd = rg.end + } + return getSplitKeys(checkKeys, regions) +} + +func getSplitKeys(checkKeys [][]byte, regions []*split.RegionInfo) map[uint64][][]byte { + splitKeyMap := make(map[uint64][][]byte) + for _, key := range checkKeys { + if region := needSplit(key, regions); region != nil { + splitKeys, ok := splitKeyMap[region.Region.GetId()] + if !ok { + splitKeys = make([][]byte, 0, 1) + } + splitKeyMap[region.Region.GetId()] = append(splitKeys, key) + log.L().Debug("get key for split region", + zap.Binary("key", key), + zap.Binary("startKey", region.Region.StartKey), + zap.Binary("endKey", region.Region.EndKey)) + } + } + return splitKeyMap +} + +// needSplit checks whether a key is necessary to split, if true returns the split region +func needSplit(key []byte, regions []*split.RegionInfo) *split.RegionInfo { + // If splitKey is the max key. + if len(key) == 0 { + return nil + } + splitKey := codec.EncodeBytes([]byte{}, key) + + for _, region := range regions { + // If splitKey is the boundary of the region + if bytes.Equal(splitKey, region.Region.GetStartKey()) { + return nil + } + // If splitKey is in a region + if bytes.Compare(splitKey, region.Region.GetStartKey()) > 0 && beforeEnd(splitKey, region.Region.GetEndKey()) { + log.L().Debug("need split", + zap.Binary("splitKey", key), + zap.Binary("encodedKey", splitKey), + zap.Binary("region start", region.Region.GetStartKey()), + zap.Binary("region end", region.Region.GetEndKey()), + ) + return region + } + } + return nil +} + +func beforeEnd(key []byte, end []byte) bool { + return bytes.Compare(key, end) < 0 || len(end) == 0 +} + +func insideRegion(region *metapb.Region, meta *sst.SSTMeta) bool { + rg := meta.GetRange() + return keyInsideRegion(region, rg.GetStart()) && keyInsideRegion(region, rg.GetEnd()) +} + +func keyInsideRegion(region *metapb.Region, key []byte) bool { + return bytes.Compare(key, region.GetStartKey()) >= 0 && (beforeEnd(key, region.GetEndKey())) +} + +func intersectRange(region *metapb.Region, rg Range) Range { + var startKey, endKey []byte + if len(region.StartKey) > 0 { + _, startKey, _ = codec.DecodeBytes(region.StartKey, []byte{}) + } + if bytes.Compare(startKey, rg.start) < 0 { + startKey = rg.start + } + if len(region.EndKey) > 0 { + _, endKey, _ = codec.DecodeBytes(region.EndKey, []byte{}) + } + if beforeEnd(rg.end, endKey) { + endKey = rg.end + } + + return Range{start: startKey, end: endKey} +} diff --git a/pkg/lightning/backend/localhelper_test.go b/pkg/lightning/backend/localhelper_test.go new file mode 100644 index 000000000..019696c04 --- /dev/null +++ b/pkg/lightning/backend/localhelper_test.go @@ -0,0 +1,535 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package backend + +import ( + "bytes" + "context" + "sync" + "time" + + . "github.com/pingcap/check" + "github.com/pingcap/errors" + "github.com/pingcap/kvproto/pkg/metapb" + "github.com/pingcap/kvproto/pkg/pdpb" + "github.com/pingcap/tidb/kv" + "github.com/pingcap/tidb/sessionctx/stmtctx" + "github.com/pingcap/tidb/tablecodec" + "github.com/pingcap/tidb/types" + "github.com/pingcap/tidb/util/codec" + "github.com/tikv/pd/server/core" + "github.com/tikv/pd/server/schedule/placement" + + "github.com/pingcap/br/pkg/restore" +) + +type testClient struct { + mu sync.RWMutex + stores map[uint64]*metapb.Store + regions map[uint64]*restore.RegionInfo + regionsInfo *core.RegionsInfo // For now it's only used in ScanRegions + nextRegionID uint64 + splitCount int + hook clientHook +} + +func newTestClient( + stores map[uint64]*metapb.Store, + regions map[uint64]*restore.RegionInfo, + nextRegionID uint64, + hook clientHook, +) *testClient { + regionsInfo := core.NewRegionsInfo() + for _, regionInfo := range regions { + regionsInfo.AddRegion(core.NewRegionInfo(regionInfo.Region, regionInfo.Leader)) + } + return &testClient{ + stores: stores, + regions: regions, + regionsInfo: regionsInfo, + nextRegionID: nextRegionID, + hook: hook, + } +} + +func (c *testClient) GetAllRegions() map[uint64]*restore.RegionInfo { + c.mu.RLock() + defer c.mu.RUnlock() + return c.regions +} + +func (c *testClient) GetStore(ctx context.Context, storeID uint64) (*metapb.Store, error) { + c.mu.RLock() + defer c.mu.RUnlock() + store, ok := c.stores[storeID] + if !ok { + return nil, errors.Errorf("store not found") + } + return store, nil +} + +func (c *testClient) GetRegion(ctx context.Context, key []byte) (*restore.RegionInfo, error) { + c.mu.RLock() + defer c.mu.RUnlock() + for _, region := range c.regions { + if bytes.Compare(key, region.Region.StartKey) >= 0 && + (len(region.Region.EndKey) == 0 || bytes.Compare(key, region.Region.EndKey) < 0) { + return region, nil + } + } + return nil, errors.Errorf("region not found: key=%s", string(key)) +} + +func (c *testClient) GetRegionByID(ctx context.Context, regionID uint64) (*restore.RegionInfo, error) { + c.mu.RLock() + defer c.mu.RUnlock() + region, ok := c.regions[regionID] + if !ok { + return nil, errors.Errorf("region not found: id=%d", regionID) + } + return region, nil +} + +func (c *testClient) SplitRegion( + ctx context.Context, + regionInfo *restore.RegionInfo, + key []byte, +) (*restore.RegionInfo, error) { + c.mu.Lock() + defer c.mu.Unlock() + var target *restore.RegionInfo + splitKey := codec.EncodeBytes([]byte{}, key) + for _, region := range c.regions { + if bytes.Compare(splitKey, region.Region.StartKey) >= 0 && + (len(region.Region.EndKey) == 0 || bytes.Compare(splitKey, region.Region.EndKey) < 0) { + target = region + } + } + if target == nil { + return nil, errors.Errorf("region not found: key=%s", string(key)) + } + newRegion := &restore.RegionInfo{ + Region: &metapb.Region{ + Peers: target.Region.Peers, + Id: c.nextRegionID, + StartKey: target.Region.StartKey, + EndKey: splitKey, + RegionEpoch: &metapb.RegionEpoch{ + Version: target.Region.RegionEpoch.Version, + ConfVer: target.Region.RegionEpoch.ConfVer + 1, + }, + }, + } + c.regions[c.nextRegionID] = newRegion + c.regionsInfo.SetRegion(core.NewRegionInfo(newRegion.Region, newRegion.Leader)) + c.nextRegionID++ + target.Region.StartKey = splitKey + target.Region.RegionEpoch.ConfVer++ + c.regions[target.Region.Id] = target + c.regionsInfo.SetRegion(core.NewRegionInfo(target.Region, target.Leader)) + return newRegion, nil +} + +func (c *testClient) BatchSplitRegionsWithOrigin( + ctx context.Context, regionInfo *restore.RegionInfo, keys [][]byte, +) (*restore.RegionInfo, []*restore.RegionInfo, error) { + if c.hook != nil { + regionInfo, keys = c.hook.BeforeSplitRegion(ctx, regionInfo, keys) + } + + c.splitCount++ + c.mu.Lock() + defer c.mu.Unlock() + newRegions := make([]*restore.RegionInfo, 0) + var region *restore.RegionInfo + for _, key := range keys { + var target *restore.RegionInfo + splitKey := codec.EncodeBytes([]byte{}, key) + for _, region := range c.regions { + if region.ContainsInterior(splitKey) { + target = region + } + } + if target == nil { + continue + } + if target.Region.RegionEpoch.Version != regionInfo.Region.RegionEpoch.Version || + target.Region.RegionEpoch.ConfVer != regionInfo.Region.RegionEpoch.ConfVer { + return regionInfo, nil, errors.New("epoch not match") + } + newRegion := &restore.RegionInfo{ + Region: &metapb.Region{ + Peers: target.Region.Peers, + Id: c.nextRegionID, + StartKey: target.Region.StartKey, + EndKey: splitKey, + }, + } + c.regions[c.nextRegionID] = newRegion + c.regionsInfo.SetRegion(core.NewRegionInfo(newRegion.Region, newRegion.Leader)) + c.nextRegionID++ + target.Region.StartKey = splitKey + c.regions[target.Region.Id] = target + region = target + newRegions = append(newRegions, newRegion) + } + if region != nil { + c.regionsInfo.SetRegion(core.NewRegionInfo(region.Region, region.Leader)) + } + var err error + if c.hook != nil { + newRegions, err = c.hook.AfterSplitRegion(ctx, region, keys, newRegions, nil) + } + + return region, newRegions, err +} + +func (c *testClient) BatchSplitRegions( + ctx context.Context, regionInfo *restore.RegionInfo, keys [][]byte, +) ([]*restore.RegionInfo, error) { + _, newRegions, err := c.BatchSplitRegionsWithOrigin(ctx, regionInfo, keys) + return newRegions, err +} + +func (c *testClient) ScatterRegion(ctx context.Context, regionInfo *restore.RegionInfo) error { + return nil +} + +func (c *testClient) GetOperator(ctx context.Context, regionID uint64) (*pdpb.GetOperatorResponse, error) { + return &pdpb.GetOperatorResponse{ + Header: new(pdpb.ResponseHeader), + }, nil +} + +func (c *testClient) ScanRegions(ctx context.Context, key, endKey []byte, limit int) ([]*restore.RegionInfo, error) { + if c.hook != nil { + key, endKey, limit = c.hook.BeforeScanRegions(ctx, key, endKey, limit) + } + + infos := c.regionsInfo.ScanRange(key, endKey, limit) + regions := make([]*restore.RegionInfo, 0, len(infos)) + for _, info := range infos { + regions = append(regions, &restore.RegionInfo{ + Region: info.GetMeta(), + Leader: info.GetLeader(), + }) + } + + var err error + if c.hook != nil { + regions, err = c.hook.AfterScanRegions(regions, nil) + } + return regions, err +} + +func (c *testClient) GetPlacementRule(ctx context.Context, groupID, ruleID string) (r placement.Rule, err error) { + return +} + +func (c *testClient) SetPlacementRule(ctx context.Context, rule placement.Rule) error { + return nil +} + +func (c *testClient) DeletePlacementRule(ctx context.Context, groupID, ruleID string) error { + return nil +} + +func (c *testClient) SetStoresLabel(ctx context.Context, stores []uint64, labelKey, labelValue string) error { + return nil +} + +func cloneRegion(region *restore.RegionInfo) *restore.RegionInfo { + r := &metapb.Region{} + if region.Region != nil { + b, _ := region.Region.Marshal() + _ = r.Unmarshal(b) + } + + l := &metapb.Peer{} + if region.Region != nil { + b, _ := region.Region.Marshal() + _ = l.Unmarshal(b) + } + return &restore.RegionInfo{Region: r, Leader: l} +} + +// region: [, aay), [aay, bba), [bba, bbh), [bbh, cca), [cca, ) +func initTestClient(keys [][]byte, hook clientHook) *testClient { + peers := make([]*metapb.Peer, 1) + peers[0] = &metapb.Peer{ + Id: 1, + StoreId: 1, + } + regions := make(map[uint64]*restore.RegionInfo) + for i := uint64(1); i < uint64(len(keys)); i++ { + startKey := keys[i-1] + if len(startKey) != 0 { + startKey = codec.EncodeBytes([]byte{}, startKey) + } + endKey := keys[i] + if len(endKey) != 0 { + endKey = codec.EncodeBytes([]byte{}, endKey) + } + regions[i] = &restore.RegionInfo{ + Region: &metapb.Region{ + Id: i, + Peers: peers, + StartKey: startKey, + EndKey: endKey, + RegionEpoch: &metapb.RegionEpoch{ConfVer: 1, Version: 1}, + }, + } + } + stores := make(map[uint64]*metapb.Store) + stores[1] = &metapb.Store{ + Id: 1, + } + return newTestClient(stores, regions, uint64(len(keys)), hook) +} + +func checkRegionRanges(c *C, regions []*restore.RegionInfo, keys [][]byte) { + // c.Assert(len(regions)+1, Equals, len(keys)) + for i, r := range regions { + _, regionStart, _ := codec.DecodeBytes(r.Region.StartKey, []byte{}) + _, regionEnd, _ := codec.DecodeBytes(r.Region.EndKey, []byte{}) + c.Assert(regionStart, DeepEquals, keys[i]) + c.Assert(regionEnd, DeepEquals, keys[i+1]) + } +} + +type clientHook interface { + BeforeSplitRegion(ctx context.Context, regionInfo *restore.RegionInfo, keys [][]byte) (*restore.RegionInfo, [][]byte) + AfterSplitRegion(context.Context, *restore.RegionInfo, [][]byte, []*restore.RegionInfo, error) ([]*restore.RegionInfo, error) + BeforeScanRegions(ctx context.Context, key, endKey []byte, limit int) ([]byte, []byte, int) + AfterScanRegions([]*restore.RegionInfo, error) ([]*restore.RegionInfo, error) +} + +type noopHook struct{} + +func (h *noopHook) BeforeSplitRegion(ctx context.Context, regionInfo *restore.RegionInfo, keys [][]byte) (*restore.RegionInfo, [][]byte) { + return regionInfo, keys +} + +func (h *noopHook) AfterSplitRegion(c context.Context, r *restore.RegionInfo, keys [][]byte, res []*restore.RegionInfo, err error) ([]*restore.RegionInfo, error) { + return res, err +} + +func (h *noopHook) BeforeScanRegions(ctx context.Context, key, endKey []byte, limit int) ([]byte, []byte, int) { + return key, endKey, limit +} + +func (h *noopHook) AfterScanRegions(res []*restore.RegionInfo, err error) ([]*restore.RegionInfo, error) { + return res, err +} + +func (s *localSuite) doTestBatchSplitRegionByRanges(c *C, hook clientHook, errPat string) { + oldLimit := maxBatchSplitKeys + oldSplitBackoffTime := splitRegionBaseBackOffTime + maxBatchSplitKeys = 4 + splitRegionBaseBackOffTime = time.Millisecond + defer func() { + maxBatchSplitKeys = oldLimit + splitRegionBaseBackOffTime = oldSplitBackoffTime + }() + + keys := [][]byte{[]byte(""), []byte("aay"), []byte("bba"), []byte("bbh"), []byte("cca"), []byte("")} + client := initTestClient(keys, hook) + local := &local{ + splitCli: client, + } + ctx := context.Background() + + // current region ranges: [, aay), [aay, bba), [bba, bbh), [bbh, cca), [cca, ) + rangeStart := codec.EncodeBytes([]byte{}, []byte("b")) + rangeEnd := codec.EncodeBytes([]byte{}, []byte("c")) + regions, err := paginateScanRegion(ctx, client, rangeStart, rangeEnd, 5) + c.Assert(err, IsNil) + // regions is: [aay, bba), [bba, bbh), [bbh, cca) + checkRegionRanges(c, regions, [][]byte{[]byte("aay"), []byte("bba"), []byte("bbh"), []byte("cca")}) + + // generate: ranges [b, ba), [ba, bb), [bb, bc), ... [by, bz) + ranges := make([]Range, 0) + start := []byte{'b'} + for i := byte('a'); i <= 'z'; i++ { + end := []byte{'b', i} + ranges = append(ranges, Range{start: start, end: end}) + start = end + } + + err = local.SplitAndScatterRegionByRanges(ctx, ranges, true) + if len(errPat) == 0 { + c.Assert(err, IsNil) + } else { + c.Assert(err, ErrorMatches, errPat) + return + } + + // so with a batch split size of 4, there will be 7 time batch split + // 1. region: [aay, bba), keys: [b, ba, bb] + // 2. region: [bbh, cca), keys: [bc, bd, be, bf] + // 3. region: [bf, cca), keys: [bg, bh, bi, bj] + // 4. region: [bj, cca), keys: [bk, bl, bm, bn] + // 5. region: [bn, cca), keys: [bo, bp, bq, br] + // 6. region: [br, cca), keys: [bs, bt, bu, bv] + // 7. region: [bv, cca), keys: [bw, bx, by, bz] + + // since it may encounter error retries, here only check the lower threshold. + c.Assert(client.splitCount >= 7, IsTrue) + + // check split ranges + regions, err = paginateScanRegion(ctx, client, rangeStart, rangeEnd, 5) + c.Assert(err, IsNil) + result := [][]byte{ + []byte("b"), []byte("ba"), []byte("bb"), []byte("bba"), []byte("bbh"), []byte("bc"), + []byte("bd"), []byte("be"), []byte("bf"), []byte("bg"), []byte("bh"), []byte("bi"), []byte("bj"), + []byte("bk"), []byte("bl"), []byte("bm"), []byte("bn"), []byte("bo"), []byte("bp"), []byte("bq"), + []byte("br"), []byte("bs"), []byte("bt"), []byte("bu"), []byte("bv"), []byte("bw"), []byte("bx"), + []byte("by"), []byte("bz"), []byte("cca"), + } + checkRegionRanges(c, regions, result) +} + +func (s *localSuite) TestBatchSplitRegionByRanges(c *C) { + s.doTestBatchSplitRegionByRanges(c, nil, "") +} + +type scanRegionEmptyHook struct { + noopHook + cnt int +} + +func (h *scanRegionEmptyHook) AfterScanRegions(res []*restore.RegionInfo, err error) ([]*restore.RegionInfo, error) { + h.cnt++ + // skip the first call + if h.cnt == 1 { + return res, err + } + return nil, err +} + +func (s *localSuite) TestBatchSplitRegionByRangesScanFailed(c *C) { + s.doTestBatchSplitRegionByRanges(c, &scanRegionEmptyHook{}, "paginate scan region returns empty result") +} + +type splitRegionEpochNotMatchHook struct { + noopHook +} + +func (h *splitRegionEpochNotMatchHook) BeforeSplitRegion(ctx context.Context, regionInfo *restore.RegionInfo, keys [][]byte) (*restore.RegionInfo, [][]byte) { + regionInfo = cloneRegion(regionInfo) + // decrease the region epoch, so split region will fail + regionInfo.Region.RegionEpoch.Version-- + return regionInfo, keys +} + +func (s *localSuite) TestBatchSplitByRangesEpochNotMatch(c *C) { + s.doTestBatchSplitRegionByRanges(c, &splitRegionEpochNotMatchHook{}, "batch split regions failed: epoch not match") +} + +// return epoch not match error in every other call +type splitRegionEpochNotMatchHookRandom struct { + noopHook + cnt int +} + +func (h *splitRegionEpochNotMatchHookRandom) BeforeSplitRegion(ctx context.Context, regionInfo *restore.RegionInfo, keys [][]byte) (*restore.RegionInfo, [][]byte) { + h.cnt++ + if h.cnt%2 != 0 { + return regionInfo, keys + } + regionInfo = cloneRegion(regionInfo) + // decrease the region epoch, so split region will fail + regionInfo.Region.RegionEpoch.Version-- + return regionInfo, keys +} + +func (s *localSuite) TestBatchSplitByRangesEpochNotMatchOnce(c *C) { + s.doTestBatchSplitRegionByRanges(c, &splitRegionEpochNotMatchHookRandom{}, "") +} + +func (s *localSuite) doTestBatchSplitByRangesWithClusteredIndex(c *C, hook clientHook) { + oldLimit := maxBatchSplitKeys + oldSplitBackoffTime := splitRegionBaseBackOffTime + maxBatchSplitKeys = 10 + splitRegionBaseBackOffTime = time.Millisecond + defer func() { + maxBatchSplitKeys = oldLimit + splitRegionBaseBackOffTime = oldSplitBackoffTime + }() + + stmtCtx := new(stmtctx.StatementContext) + + tableId := int64(1) + tableStartKey := tablecodec.EncodeTablePrefix(tableId) + tableEndKey := tablecodec.EncodeTablePrefix(tableId + 1) + keys := [][]byte{[]byte(""), tableStartKey} + // pre split 2 regions + for i := int64(0); i < 2; i++ { + keyBytes, err := codec.EncodeKey(stmtCtx, nil, types.NewIntDatum(i)) + c.Assert(err, IsNil) + h, err := kv.NewCommonHandle(keyBytes) + c.Assert(err, IsNil) + key := tablecodec.EncodeRowKeyWithHandle(tableId, h) + keys = append(keys, key) + } + keys = append(keys, tableEndKey, []byte("")) + client := initTestClient(keys, hook) + local := &local{ + splitCli: client, + } + ctx := context.Background() + + // we batch generate a batch of row keys for table 1 with common handle + rangeKeys := make([][]byte, 0, 20+1) + for i := int64(0); i < 2; i++ { + for j := int64(0); j < 10; j++ { + keyBytes, err := codec.EncodeKey(stmtCtx, nil, types.NewIntDatum(i), types.NewIntDatum(j*10000)) + c.Assert(err, IsNil) + h, err := kv.NewCommonHandle(keyBytes) + c.Assert(err, IsNil) + key := tablecodec.EncodeRowKeyWithHandle(tableId, h) + rangeKeys = append(rangeKeys, key) + } + } + + start := rangeKeys[0] + ranges := make([]Range, 0, len(rangeKeys)-1) + for _, e := range rangeKeys[1:] { + ranges = append(ranges, Range{start: start, end: e}) + start = e + } + + err := local.SplitAndScatterRegionByRanges(ctx, ranges, true) + c.Assert(err, IsNil) + + startKey := codec.EncodeBytes([]byte{}, rangeKeys[0]) + endKey := codec.EncodeBytes([]byte{}, rangeKeys[len(rangeKeys)-1]) + // check split ranges + regions, err := paginateScanRegion(ctx, client, startKey, endKey, 5) + c.Assert(err, IsNil) + c.Assert(len(regions), Equals, len(ranges)+1) + + checkKeys := append([][]byte{}, rangeKeys[:10]...) + checkKeys = append(checkKeys, keys[3]) + checkKeys = append(checkKeys, rangeKeys[10:]...) + checkRegionRanges(c, regions, checkKeys) +} + +func (s *localSuite) TestBatchSplitByRangesWithClusteredIndex(c *C) { + s.doTestBatchSplitByRangesWithClusteredIndex(c, nil) +} + +func (s *localSuite) TestBatchSplitByRangesWithClusteredIndexEpochNotMatch(c *C) { + s.doTestBatchSplitByRangesWithClusteredIndex(c, &splitRegionEpochNotMatchHookRandom{}) +} diff --git a/pkg/lightning/backend/session.go b/pkg/lightning/backend/session.go new file mode 100644 index 000000000..c3c847f01 --- /dev/null +++ b/pkg/lightning/backend/session.go @@ -0,0 +1,242 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package backend + +import ( + "context" + "errors" + "fmt" + "strconv" + + "github.com/pingcap/parser/model" + "github.com/pingcap/parser/mysql" + "github.com/pingcap/tidb/kv" + "github.com/pingcap/tidb/sessionctx" + "github.com/pingcap/tidb/sessionctx/variable" + + "github.com/pingcap/br/pkg/lightning/common" +) + +// invalidIterator is a trimmed down Iterator type which is invalid. +type invalidIterator struct { + kv.Iterator +} + +// Valid implements the kv.Iterator interface +func (*invalidIterator) Valid() bool { + return false +} + +// Close implements the kv.Iterator interface +func (*invalidIterator) Close() { +} + +type kvMemBuf struct { + kv.MemBuffer + kvPairs []common.KvPair + size int +} + +func (mb *kvMemBuf) Set(k kv.Key, v []byte) error { + mb.kvPairs = append(mb.kvPairs, common.KvPair{ + Key: k.Clone(), + Val: append([]byte{}, v...), + }) + mb.size += len(k) + len(v) + return nil +} + +func (mb *kvMemBuf) SetWithFlags(k kv.Key, v []byte, ops ...kv.FlagsOp) error { + return mb.Set(k, v) +} + +func (mb *kvMemBuf) Delete(k kv.Key) error { + return errors.New("unsupported operation") +} + +// Release publish all modifications in the latest staging buffer to upper level. +func (mb *kvMemBuf) Release(h kv.StagingHandle) { +} + +func (mb *kvMemBuf) Staging() kv.StagingHandle { + return 0 +} + +// Cleanup cleanup the resources referenced by the StagingHandle. +// If the changes are not published by `Release`, they will be discarded. +func (mb *kvMemBuf) Cleanup(h kv.StagingHandle) {} + +// Size returns sum of keys and values length. +func (mb *kvMemBuf) Size() int { + return mb.size +} + +// Len returns the number of entries in the DB. +func (t *transaction) Len() int { + return t.GetMemBuffer().Len() +} + +type kvUnionStore struct { + kvMemBuf + kv.UnionStore +} + +func (s *kvUnionStore) GetMemBuffer() kv.MemBuffer { + return &s.kvMemBuf +} + +func (s *kvUnionStore) GetIndexName(tableID, indexID int64) string { + panic("Unsupported Operation") +} + +func (s *kvUnionStore) CacheIndexName(tableID, indexID int64, name string) { +} + +func (s *kvUnionStore) CacheTableInfo(id int64, info *model.TableInfo) { +} + +// transaction is a trimmed down Transaction type which only supports adding a +// new KV pair. +type transaction struct { + kv.Transaction + kvUnionStore +} + +func NewTransaction() *transaction { + return &transaction{ + kvUnionStore: kvUnionStore{}, + } +} + +func (t *transaction) GetMemBuffer() kv.MemBuffer { + return &t.kvUnionStore.kvMemBuf +} + +func (t *transaction) Discard() { + // do nothing +} + +func (t *transaction) Flush() (int, error) { + // do nothing + return 0, nil +} + +// Reset implements the kv.MemBuffer interface +func (t *transaction) Reset() {} + +// Get implements the kv.Retriever interface +func (t *transaction) Get(ctx context.Context, key kv.Key) ([]byte, error) { + return nil, kv.ErrNotExist +} + +// Iter implements the kv.Retriever interface +func (t *transaction) Iter(k kv.Key, upperBound kv.Key) (kv.Iterator, error) { + return &invalidIterator{}, nil +} + +// Set implements the kv.Mutator interface +func (t *transaction) Set(k kv.Key, v []byte) error { + return t.kvMemBuf.Set(k, v) +} + +// SetOption implements the kv.Transaction interface +func (t *transaction) SetOption(opt kv.Option, val interface{}) {} + +// DelOption implements the kv.Transaction interface +func (t *transaction) DelOption(kv.Option) {} + +// SetAssertion implements the kv.Transaction interface +func (t *transaction) SetAssertion(kv.Key, kv.AssertionType) {} + +func (t *transaction) GetUnionStore() kv.UnionStore { + return &t.kvUnionStore +} + +// session is a trimmed down Session type which only wraps our own trimmed-down +// transaction type and provides the session variables to the TiDB library +// optimized for Lightning. +type session struct { + sessionctx.Context + txn transaction + vars *variable.SessionVars + // currently, we only set `CommonAddRecordCtx` + values map[fmt.Stringer]interface{} +} + +// SessionOptions is the initial configuration of the session. +type SessionOptions struct { + SQLMode mysql.SQLMode + Timestamp int64 + SysVars map[string]string + // a seed used for tableKvEncoder's auto random bits value + AutoRandomSeed int64 +} + +func newSession(options *SessionOptions) *session { + sqlMode := options.SQLMode + vars := variable.NewSessionVars() + vars.SkipUTF8Check = true + vars.StmtCtx.InInsertStmt = true + vars.StmtCtx.BatchCheck = true + vars.StmtCtx.BadNullAsWarning = !sqlMode.HasStrictMode() + vars.StmtCtx.TruncateAsWarning = !sqlMode.HasStrictMode() + vars.StmtCtx.OverflowAsWarning = !sqlMode.HasStrictMode() + vars.StmtCtx.AllowInvalidDate = sqlMode.HasAllowInvalidDatesMode() + vars.StmtCtx.IgnoreZeroInDate = !sqlMode.HasStrictMode() || sqlMode.HasAllowInvalidDatesMode() + if options.SysVars != nil { + for k, v := range options.SysVars { + vars.SetSystemVar(k, v) + } + } + vars.StmtCtx.TimeZone = vars.Location() + vars.SetSystemVar("timestamp", strconv.FormatInt(options.Timestamp, 10)) + vars.TxnCtx = nil + + s := &session{ + vars: vars, + values: make(map[fmt.Stringer]interface{}, 1), + } + + return s +} + +func (se *session) takeKvPairs() []common.KvPair { + pairs := se.txn.kvMemBuf.kvPairs + se.txn.kvMemBuf.kvPairs = make([]common.KvPair, 0, len(pairs)) + se.txn.kvMemBuf.size = 0 + return pairs +} + +// Txn implements the sessionctx.Context interface +func (se *session) Txn(active bool) (kv.Transaction, error) { + return &se.txn, nil +} + +// GetSessionVars implements the sessionctx.Context interface +func (se *session) GetSessionVars() *variable.SessionVars { + return se.vars +} + +// SetValue saves a value associated with this context for key. +func (se *session) SetValue(key fmt.Stringer, value interface{}) { + se.values[key] = value +} + +// Value returns the value associated with this context for key. +func (se *session) Value(key fmt.Stringer) interface{} { + return se.values[key] +} + +// StmtAddDirtyTableOP implements the sessionctx.Context interface +func (se *session) StmtAddDirtyTableOP(op int, physicalID int64, handle kv.Handle) {} diff --git a/pkg/lightning/backend/session_test.go b/pkg/lightning/backend/session_test.go new file mode 100644 index 000000000..743b60b09 --- /dev/null +++ b/pkg/lightning/backend/session_test.go @@ -0,0 +1,37 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package backend + +import ( + "testing" + + . "github.com/pingcap/check" + "github.com/pingcap/parser/mysql" + tidbkv "github.com/pingcap/tidb/kv" +) + +type kvSuite struct{} + +var _ = Suite(&kvSuite{}) + +func TestKV(t *testing.T) { + TestingT(t) +} + +func (s *kvSuite) TestSetOption(c *C) { + session := newSession(&SessionOptions{SQLMode: mysql.ModeNone, Timestamp: 1234567890}) + txn, err := session.Txn(true) + c.Assert(err, IsNil) + txn.SetOption(tidbkv.Priority, tidbkv.PriorityHigh) +} diff --git a/pkg/lightning/backend/sql2kv.go b/pkg/lightning/backend/sql2kv.go new file mode 100644 index 000000000..4c745f490 --- /dev/null +++ b/pkg/lightning/backend/sql2kv.go @@ -0,0 +1,435 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package backend + +import ( + "math/rand" + "sort" + + "github.com/pingcap/errors" + "github.com/pingcap/parser/model" + "github.com/pingcap/parser/mysql" + "github.com/pingcap/tidb/expression" + "github.com/pingcap/tidb/meta/autoid" + "github.com/pingcap/tidb/sessionctx/variable" + "github.com/pingcap/tidb/table" + "github.com/pingcap/tidb/table/tables" + "github.com/pingcap/tidb/tablecodec" + "github.com/pingcap/tidb/types" + "github.com/pingcap/tidb/util/chunk" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + // Import tidb/planner/core to initialize expression.RewriteAstExpr + _ "github.com/pingcap/tidb/planner/core" + + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/log" + "github.com/pingcap/br/pkg/lightning/metric" + "github.com/pingcap/br/pkg/lightning/verification" +) + +var extraHandleColumnInfo = model.NewExtraHandleColInfo() + +type genCol struct { + index int + expr expression.Expression +} + +type tableKVEncoder struct { + tbl table.Table + se *session + recordCache []types.Datum + genCols []genCol + // auto random bits value for this chunk + autoRandomHeaderBits int64 +} + +func NewTableKVEncoder(tbl table.Table, options *SessionOptions) (Encoder, error) { + metric.KvEncoderCounter.WithLabelValues("open").Inc() + meta := tbl.Meta() + cols := tbl.Cols() + se := newSession(options) + // Set CommonAddRecordCtx to session to reuse the slices and BufStore in AddRecord + recordCtx := tables.NewCommonAddRecordCtx(len(cols)) + tables.SetAddRecordCtx(se, recordCtx) + + var autoRandomBits int64 + if meta.PKIsHandle && meta.ContainsAutoRandomBits() { + for _, col := range cols { + if mysql.HasPriKeyFlag(col.Flag) { + incrementalBits := autoRandomIncrementBits(col, int(meta.AutoRandomBits)) + autoRandomBits = rand.New(rand.NewSource(options.AutoRandomSeed)).Int63n(1< maxGenColOffset { + maxGenColOffset = col.Offset + } + } + + if maxGenColOffset < 0 { + return nil, nil + } + + // the expression rewriter requires a non-nil TxnCtx. + se.vars.TxnCtx = new(variable.TransactionContext) + defer func() { + se.vars.TxnCtx = nil + }() + + // not using TableInfo2SchemaAndNames to avoid parsing all virtual generated columns again. + exprColumns := make([]*expression.Column, 0, len(cols)) + names := make(types.NameSlice, 0, len(cols)) + for i, col := range cols { + names = append(names, &types.FieldName{ + OrigTblName: meta.Name, + OrigColName: col.Name, + TblName: meta.Name, + ColName: col.Name, + }) + exprColumns = append(exprColumns, &expression.Column{ + RetType: col.FieldType.Clone(), + ID: col.ID, + UniqueID: int64(i), + Index: col.Offset, + OrigName: names[i].String(), + IsHidden: col.Hidden, + }) + } + schema := expression.NewSchema(exprColumns...) + + // as long as we have a stored generated column, all columns it referred to must be evaluated as well. + // for simplicity we just evaluate all generated columns (virtual or not) before the last stored one. + var genCols []genCol + for i, col := range cols { + if col.GeneratedExpr != nil && col.Offset <= maxGenColOffset { + expr, err := expression.RewriteAstExpr(se, col.GeneratedExpr, schema, names) + if err != nil { + return nil, err + } + genCols = append(genCols, genCol{ + index: i, + expr: expr, + }) + } + } + + // order the result by column offset so they match the evaluation order. + sort.Slice(genCols, func(i, j int) bool { + return cols[genCols[i].index].Offset < cols[genCols[j].index].Offset + }) + return genCols, nil +} + +func (kvcodec *tableKVEncoder) Close() { + metric.KvEncoderCounter.WithLabelValues("closed").Inc() +} + +type rowArrayMarshaler []types.Datum + +var kindStr = [...]string{ + types.KindNull: "null", + types.KindInt64: "int64", + types.KindUint64: "uint64", + types.KindFloat32: "float32", + types.KindFloat64: "float64", + types.KindString: "string", + types.KindBytes: "bytes", + types.KindBinaryLiteral: "binary", + types.KindMysqlDecimal: "decimal", + types.KindMysqlDuration: "duration", + types.KindMysqlEnum: "enum", + types.KindMysqlBit: "bit", + types.KindMysqlSet: "set", + types.KindMysqlTime: "time", + types.KindInterface: "interface", + types.KindMinNotNull: "min", + types.KindMaxValue: "max", + types.KindRaw: "raw", + types.KindMysqlJSON: "json", +} + +// MarshalLogArray implements the zapcore.ArrayMarshaler interface +func (row rowArrayMarshaler) MarshalLogArray(encoder zapcore.ArrayEncoder) error { + for _, datum := range row { + kind := datum.Kind() + var str string + var err error + switch kind { + case types.KindNull: + str = "NULL" + case types.KindMinNotNull: + str = "-inf" + case types.KindMaxValue: + str = "+inf" + default: + str, err = datum.ToString() + if err != nil { + return err + } + } + encoder.AppendObject(zapcore.ObjectMarshalerFunc(func(enc zapcore.ObjectEncoder) error { + enc.AddString("kind", kindStr[kind]) + enc.AddString("val", log.RedactString(str)) + return nil + })) + } + return nil +} + +func logKVConvertFailed(logger log.Logger, row []types.Datum, j int, colInfo *model.ColumnInfo, err error) error { + var original types.Datum + if 0 <= j && j < len(row) { + original = row[j] + row = row[j : j+1] + } + + logger.Error("kv convert failed", + zap.Array("original", rowArrayMarshaler(row)), + zap.Int("originalCol", j), + zap.String("colName", colInfo.Name.O), + zap.Stringer("colType", &colInfo.FieldType), + log.ShortError(err), + ) + + log.L().Error("failed to covert kv value", log.ZapRedactReflect("origVal", original.GetValue()), + zap.Stringer("fieldType", &colInfo.FieldType), zap.String("column", colInfo.Name.O), + zap.Int("columnID", j+1)) + return errors.Annotatef( + err, + "failed to cast value as %s for column `%s` (#%d)", &colInfo.FieldType, colInfo.Name.O, j+1, + ) +} + +func logEvalGenExprFailed(logger log.Logger, row []types.Datum, colInfo *model.ColumnInfo, err error) error { + logger.Error("kv convert failed: cannot evaluate generated column expression", + zap.Array("original", rowArrayMarshaler(row)), + zap.String("colName", colInfo.Name.O), + log.ShortError(err), + ) + + return errors.Annotatef( + err, + "failed to evaluate generated column expression for column `%s`", + colInfo.Name.O, + ) +} + +type kvPairs []common.KvPair + +// MakeRowsFromKvPairs converts a KvPair slice into a Rows instance. This is +// mainly used for testing only. The resulting Rows instance should only be used +// for the importer backend. +func MakeRowsFromKvPairs(pairs []common.KvPair) Rows { + return kvPairs(pairs) +} + +// MakeRowFromKvPairs converts a KvPair slice into a Row instance. This is +// mainly used for testing only. The resulting Row instance should only be used +// for the importer backend. +func MakeRowFromKvPairs(pairs []common.KvPair) Row { + return kvPairs(pairs) +} + +// Encode a row of data into KV pairs. +// +// See comments in `(*TableRestore).initializeColumns` for the meaning of the +// `columnPermutation` parameter. +func (kvcodec *tableKVEncoder) Encode( + logger log.Logger, + row []types.Datum, + rowID int64, + columnPermutation []int, +) (Row, error) { + cols := kvcodec.tbl.Cols() + + var value types.Datum + var err error + var record []types.Datum + + if kvcodec.recordCache != nil { + record = kvcodec.recordCache + } else { + record = make([]types.Datum, 0, len(cols)+1) + } + + isAutoRandom := kvcodec.tbl.Meta().PKIsHandle && kvcodec.tbl.Meta().ContainsAutoRandomBits() + for i, col := range cols { + j := columnPermutation[i] + isAutoIncCol := mysql.HasAutoIncrementFlag(col.Flag) + isPk := mysql.HasPriKeyFlag(col.Flag) + if j >= 0 && j < len(row) { + value, err = table.CastValue(kvcodec.se, row[j], col.ToInfo(), false, false) + if err == nil { + err = col.HandleBadNull(&value, kvcodec.se.vars.StmtCtx) + } + } else if isAutoIncCol { + // we still need a conversion, e.g. to catch overflow with a TINYINT column. + value, err = table.CastValue(kvcodec.se, types.NewIntDatum(rowID), col.ToInfo(), false, false) + } else if isAutoRandom && isPk { + var val types.Datum + if mysql.HasUnsignedFlag(col.Flag) { + val = types.NewUintDatum(uint64(kvcodec.autoRandomHeaderBits | rowID)) + } else { + val = types.NewIntDatum(kvcodec.autoRandomHeaderBits | rowID) + } + value, err = table.CastValue(kvcodec.se, val, col.ToInfo(), false, false) + } else if col.IsGenerated() { + // inject some dummy value for gen col so that MutRowFromDatums below sees a real value instead of nil. + // if MutRowFromDatums sees a nil it won't initialize the underlying storage and cause SetDatum to panic. + value = types.GetMinValue(&col.FieldType) + } else { + value, err = table.GetColDefaultValue(kvcodec.se, col.ToInfo()) + } + if err != nil { + return nil, logKVConvertFailed(logger, row, j, col.ToInfo(), err) + } + + record = append(record, value) + + if isAutoRandom && isPk { + incrementalBits := autoRandomIncrementBits(col, int(kvcodec.tbl.Meta().AutoRandomBits)) + kvcodec.tbl.RebaseAutoID(kvcodec.se, value.GetInt64()&((1<= 0 && j < len(row) { + value, err = table.CastValue(kvcodec.se, row[j], extraHandleColumnInfo, false, false) + } else { + value, err = types.NewIntDatum(rowID), nil + } + if err != nil { + return nil, logKVConvertFailed(logger, row, j, extraHandleColumnInfo, err) + } + record = append(record, value) + kvcodec.tbl.RebaseAutoID(kvcodec.se, value.GetInt64(), false, autoid.RowIDAllocType) + } + + if len(kvcodec.genCols) > 0 { + mutRow := chunk.MutRowFromDatums(record) + for _, gc := range kvcodec.genCols { + col := cols[gc.index].ToInfo() + evaluated, err := gc.expr.Eval(mutRow.ToRow()) + if err != nil { + return nil, logEvalGenExprFailed(logger, row, col, err) + } + value, err := table.CastValue(kvcodec.se, evaluated, col, false, false) + if err != nil { + return nil, logEvalGenExprFailed(logger, row, col, err) + } + mutRow.SetDatum(gc.index, value) + record[gc.index] = value + } + } + + _, err = kvcodec.tbl.AddRecord(kvcodec.se, record) + if err != nil { + logger.Error("kv encode failed", + zap.Array("originalRow", rowArrayMarshaler(row)), + zap.Array("convertedRow", rowArrayMarshaler(record)), + log.ShortError(err), + ) + return nil, errors.Trace(err) + } + pairs := kvcodec.se.takeKvPairs() + kvcodec.recordCache = record[:0] + return kvPairs(pairs), nil +} + +func (kvs kvPairs) ClassifyAndAppend( + data *Rows, + dataChecksum *verification.KVChecksum, + indices *Rows, + indexChecksum *verification.KVChecksum, +) { + dataKVs := (*data).(kvPairs) + indexKVs := (*indices).(kvPairs) + + for _, kv := range kvs { + if kv.Key[tablecodec.TableSplitKeyLen+1] == 'r' { + dataKVs = append(dataKVs, kv) + dataChecksum.UpdateOne(kv) + } else { + indexKVs = append(indexKVs, kv) + indexChecksum.UpdateOne(kv) + } + } + + *data = dataKVs + *indices = indexKVs +} + +func (totalKVs kvPairs) SplitIntoChunks(splitSize int) []Rows { + if len(totalKVs) == 0 { + return nil + } + + res := make([]Rows, 0, 1) + i := 0 + cumSize := 0 + + for j, pair := range totalKVs { + size := len(pair.Key) + len(pair.Val) + if i < j && cumSize+size > splitSize { + res = append(res, kvPairs(totalKVs[i:j])) + i = j + cumSize = 0 + } + cumSize += size + } + + return append(res, kvPairs(totalKVs[i:])) +} + +func (kvs kvPairs) Clear() Rows { + return kvPairs(kvs[:0]) +} diff --git a/pkg/lightning/backend/sql2kv_test.go b/pkg/lightning/backend/sql2kv_test.go new file mode 100644 index 000000000..1ee96921d --- /dev/null +++ b/pkg/lightning/backend/sql2kv_test.go @@ -0,0 +1,438 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package backend + +import ( + "errors" + + . "github.com/pingcap/check" + "github.com/pingcap/parser" + "github.com/pingcap/parser/ast" + "github.com/pingcap/parser/model" + "github.com/pingcap/parser/mysql" + "github.com/pingcap/tidb/ddl" + "github.com/pingcap/tidb/kv" + "github.com/pingcap/tidb/meta/autoid" + "github.com/pingcap/tidb/sessionctx" + "github.com/pingcap/tidb/table" + "github.com/pingcap/tidb/table/tables" + "github.com/pingcap/tidb/types" + "github.com/pingcap/tidb/util/mock" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/log" + "github.com/pingcap/br/pkg/lightning/verification" +) + +func (s *kvSuite) TestMarshal(c *C) { + nullDatum := types.Datum{} + nullDatum.SetNull() + minNotNull := types.Datum{} + minNotNull.SetMinNotNull() + encoder := zapcore.NewMapObjectEncoder() + err := encoder.AddArray("test", rowArrayMarshaler{types.NewStringDatum("1"), nullDatum, minNotNull, types.MaxValueDatum()}) + c.Assert(err, IsNil) + c.Assert(encoder.Fields["test"], DeepEquals, []interface{}{ + map[string]interface{}{"kind": "string", "val": "1"}, + map[string]interface{}{"kind": "null", "val": "NULL"}, + map[string]interface{}{"kind": "min", "val": "-inf"}, + map[string]interface{}{"kind": "max", "val": "+inf"}, + }) + + invalid := types.Datum{} + invalid.SetInterface(1) + err = encoder.AddArray("bad-test", rowArrayMarshaler{minNotNull, invalid}) + c.Assert(err, ErrorMatches, "cannot convert.*") + c.Assert(encoder.Fields["bad-test"], DeepEquals, []interface{}{ + map[string]interface{}{"kind": "min", "val": "-inf"}, + }) +} + +type mockTable struct { + table.Table +} + +func (mockTable) AddRecord(ctx sessionctx.Context, r []types.Datum, opts ...table.AddRecordOption) (recordID kv.Handle, err error) { + return kv.IntHandle(-1), errors.New("mock error") +} + +func (s *kvSuite) TestEncode(c *C) { + c1 := &model.ColumnInfo{ID: 1, Name: model.NewCIStr("c1"), State: model.StatePublic, Offset: 0, FieldType: *types.NewFieldType(mysql.TypeTiny)} + cols := []*model.ColumnInfo{c1} + tblInfo := &model.TableInfo{ID: 1, Columns: cols, PKIsHandle: false, State: model.StatePublic} + tbl, err := tables.TableFromMeta(NewPanickingAllocators(0), tblInfo) + c.Assert(err, IsNil) + + logger := log.Logger{Logger: zap.NewNop()} + rows := []types.Datum{ + types.NewIntDatum(10000000), + } + + // Strict mode + strictMode, err := NewTableKVEncoder(tbl, &SessionOptions{ + SQLMode: mysql.ModeStrictAllTables, + Timestamp: 1234567890, + }) + c.Assert(err, IsNil) + pairs, err := strictMode.Encode(logger, rows, 1, []int{0, 1}) + c.Assert(err, ErrorMatches, "failed to cast value as tinyint\\(4\\) for column `c1` \\(#1\\):.*overflows tinyint") + c.Assert(pairs, IsNil) + + rowsWithPk := []types.Datum{ + types.NewIntDatum(1), + types.NewStringDatum("invalid-pk"), + } + pairs, err = strictMode.Encode(logger, rowsWithPk, 2, []int{0, 1}) + c.Assert(err, ErrorMatches, "failed to cast value as bigint\\(20\\) for column `_tidb_rowid`.*Truncated.*") + + rowsWithPk2 := []types.Datum{ + types.NewIntDatum(1), + types.NewStringDatum("1"), + } + pairs, err = strictMode.Encode(logger, rowsWithPk2, 2, []int{0, 1}) + c.Assert(err, IsNil) + c.Assert(pairs, DeepEquals, kvPairs([]common.KvPair{ + { + Key: []uint8{0x74, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5f, 0x72, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, + Val: []uint8{0x8, 0x2, 0x8, 0x2}, + }, + })) + + // Mock add record error + mockTbl := &mockTable{Table: tbl} + mockMode, err := NewTableKVEncoder(mockTbl, &SessionOptions{ + SQLMode: mysql.ModeStrictAllTables, + Timestamp: 1234567891, + }) + c.Assert(err, IsNil) + pairs, err = mockMode.Encode(logger, rowsWithPk2, 2, []int{0, 1}) + c.Assert(err, ErrorMatches, "mock error") + + // Non-strict mode + noneMode, err := NewTableKVEncoder(tbl, &SessionOptions{ + SQLMode: mysql.ModeNone, + Timestamp: 1234567892, + SysVars: map[string]string{"tidb_row_format_version": "1"}, + }) + c.Assert(err, IsNil) + pairs, err = noneMode.Encode(logger, rows, 1, []int{0, 1}) + c.Assert(err, IsNil) + c.Assert(pairs, DeepEquals, kvPairs([]common.KvPair{ + { + Key: []uint8{0x74, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5f, 0x72, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, + Val: []uint8{0x8, 0x2, 0x8, 0xfe, 0x1}, + }, + })) +} + +func (s *kvSuite) TestEncodeRowFormatV2(c *C) { + // Test encoding in row format v2, as described in . + + c1 := &model.ColumnInfo{ID: 1, Name: model.NewCIStr("c1"), State: model.StatePublic, Offset: 0, FieldType: *types.NewFieldType(mysql.TypeTiny)} + cols := []*model.ColumnInfo{c1} + tblInfo := &model.TableInfo{ID: 1, Columns: cols, PKIsHandle: false, State: model.StatePublic} + tbl, err := tables.TableFromMeta(NewPanickingAllocators(0), tblInfo) + c.Assert(err, IsNil) + + logger := log.Logger{Logger: zap.NewNop()} + rows := []types.Datum{ + types.NewIntDatum(10000000), + } + + noneMode, err := NewTableKVEncoder(tbl, &SessionOptions{ + SQLMode: mysql.ModeNone, + Timestamp: 1234567892, + SysVars: map[string]string{"tidb_row_format_version": "2"}, + }) + c.Assert(err, IsNil) + pairs, err := noneMode.Encode(logger, rows, 1, []int{0, 1}) + c.Assert(err, IsNil) + c.Assert(pairs, DeepEquals, kvPairs([]common.KvPair{ + { + // the key should be the same as TestEncode() + Key: []uint8{0x74, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5f, 0x72, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}, + Val: []uint8{ + 0x80, // version + 0x0, // flag = 0 = not big + 0x1, 0x0, // number of not null columns = 1 + 0x0, 0x0, // number of null columns = 0 + 0x1, // column IDs = [1] + 0x1, 0x0, // not null offsets = [1] + 0x7f, // column version = 127 (10000000 clamped to TINYINT) + }, + }, + })) +} + +func (s *kvSuite) TestEncodeTimestamp(c *C) { + ty := *types.NewFieldType(mysql.TypeDatetime) + ty.Flag |= mysql.NotNullFlag + c1 := &model.ColumnInfo{ + ID: 1, + Name: model.NewCIStr("c1"), + State: model.StatePublic, + Offset: 0, + FieldType: ty, + DefaultValue: "CURRENT_TIMESTAMP", + Version: 1, + } + cols := []*model.ColumnInfo{c1} + tblInfo := &model.TableInfo{ID: 1, Columns: cols, PKIsHandle: false, State: model.StatePublic} + tbl, err := tables.TableFromMeta(NewPanickingAllocators(0), tblInfo) + c.Assert(err, IsNil) + + logger := log.Logger{Logger: zap.NewNop()} + + encoder, err := NewTableKVEncoder(tbl, &SessionOptions{ + SQLMode: mysql.ModeStrictAllTables, + Timestamp: 1234567893, + SysVars: map[string]string{ + "tidb_row_format_version": "1", + "time_zone": "+08:00", + }, + }) + c.Assert(err, IsNil) + pairs, err := encoder.Encode(logger, nil, 70, []int{-1, 1}) + c.Assert(err, IsNil) + c.Assert(pairs, DeepEquals, kvPairs([]common.KvPair{ + { + Key: []uint8{0x74, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5f, 0x72, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x46}, + Val: []uint8{0x8, 0x2, 0x9, 0x80, 0x80, 0x80, 0xf0, 0xfd, 0x8e, 0xf7, 0xc0, 0x19}, + }, + })) +} + +func mockTableInfo(c *C, createSql string) *model.TableInfo { + parser := parser.New() + node, err := parser.ParseOneStmt(createSql, "", "") + c.Assert(err, IsNil) + sctx := mock.NewContext() + info, err := ddl.MockTableInfo(sctx, node.(*ast.CreateTableStmt), 1) + c.Assert(err, IsNil) + info.State = model.StatePublic + return info +} + +func (s *kvSuite) TestDefaultAutoRandoms(c *C) { + tblInfo := mockTableInfo(c, "create table t (id bigint unsigned NOT NULL auto_random primary key, a varchar(100));") + // seems parser can't parse auto_random properly. + tblInfo.AutoRandomBits = 5 + tbl, err := tables.TableFromMeta(NewPanickingAllocators(0), tblInfo) + c.Assert(err, IsNil) + encoder, err := NewTableKVEncoder(tbl, &SessionOptions{ + SQLMode: mysql.ModeStrictAllTables, + Timestamp: 1234567893, + SysVars: map[string]string{"tidb_row_format_version": "2"}, + AutoRandomSeed: 456, + }) + c.Assert(err, IsNil) + logger := log.Logger{Logger: zap.NewNop()} + pairs, err := encoder.Encode(logger, []types.Datum{types.NewStringDatum("")}, 70, []int{-1, 0}) + c.Assert(err, IsNil) + c.Assert(pairs, DeepEquals, kvPairs([]common.KvPair{ + { + Key: []uint8{0x74, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5f, 0x72, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x46}, + Val: []uint8{0x80, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0}, + }, + })) + c.Assert(tbl.Allocators(encoder.(*tableKVEncoder).se).Get(autoid.AutoRandomType).Base(), Equals, int64(70)) + + pairs, err = encoder.Encode(logger, []types.Datum{types.NewStringDatum("")}, 71, []int{-1, 0}) + c.Assert(err, IsNil) + c.Assert(pairs, DeepEquals, kvPairs([]common.KvPair{ + { + Key: []uint8{0x74, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5f, 0x72, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x47}, + Val: []uint8{0x80, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0}, + }, + })) + c.Assert(tbl.Allocators(encoder.(*tableKVEncoder).se).Get(autoid.AutoRandomType).Base(), Equals, int64(71)) +} + +func (s *kvSuite) TestSplitIntoChunks(c *C) { + pairs := []common.KvPair{ + { + Key: []byte{1, 2, 3}, + Val: []byte{4, 5, 6}, + }, + { + Key: []byte{7, 8}, + Val: []byte{9, 0}, + }, + { + Key: []byte{1, 2, 3, 4}, + Val: []byte{5, 6, 7, 8}, + }, + { + Key: []byte{9, 0}, + Val: []byte{1, 2}, + }, + } + + splitBy10 := MakeRowsFromKvPairs(pairs).SplitIntoChunks(10) + c.Assert(splitBy10, DeepEquals, []Rows{ + MakeRowsFromKvPairs(pairs[0:2]), + MakeRowsFromKvPairs(pairs[2:3]), + MakeRowsFromKvPairs(pairs[3:4]), + }) + + splitBy12 := MakeRowsFromKvPairs(pairs).SplitIntoChunks(12) + c.Assert(splitBy12, DeepEquals, []Rows{ + MakeRowsFromKvPairs(pairs[0:2]), + MakeRowsFromKvPairs(pairs[2:4]), + }) + + splitBy1000 := MakeRowsFromKvPairs(pairs).SplitIntoChunks(1000) + c.Assert(splitBy1000, DeepEquals, []Rows{ + MakeRowsFromKvPairs(pairs[0:4]), + }) + + splitBy1 := MakeRowsFromKvPairs(pairs).SplitIntoChunks(1) + c.Assert(splitBy1, DeepEquals, []Rows{ + MakeRowsFromKvPairs(pairs[0:1]), + MakeRowsFromKvPairs(pairs[1:2]), + MakeRowsFromKvPairs(pairs[2:3]), + MakeRowsFromKvPairs(pairs[3:4]), + }) +} + +func (s *kvSuite) TestClassifyAndAppend(c *C) { + kvs := MakeRowFromKvPairs([]common.KvPair{ + { + Key: []byte("txxxxxxxx_ryyyyyyyy"), + Val: []byte("value1"), + }, + { + Key: []byte("txxxxxxxx_rwwwwwwww"), + Val: []byte("value2"), + }, + { + Key: []byte("txxxxxxxx_izzzzzzzz"), + Val: []byte("index1"), + }, + }) + + data := MakeRowsFromKvPairs(nil) + indices := MakeRowsFromKvPairs(nil) + dataChecksum := verification.MakeKVChecksum(0, 0, 0) + indexChecksum := verification.MakeKVChecksum(0, 0, 0) + + kvs.ClassifyAndAppend(&data, &dataChecksum, &indices, &indexChecksum) + + c.Assert(data, DeepEquals, MakeRowsFromKvPairs([]common.KvPair{ + { + Key: []byte("txxxxxxxx_ryyyyyyyy"), + Val: []byte("value1"), + }, + { + Key: []byte("txxxxxxxx_rwwwwwwww"), + Val: []byte("value2"), + }, + })) + c.Assert(indices, DeepEquals, MakeRowsFromKvPairs([]common.KvPair{ + { + Key: []byte("txxxxxxxx_izzzzzzzz"), + Val: []byte("index1"), + }, + })) + c.Assert(dataChecksum.SumKVS(), Equals, uint64(2)) + c.Assert(indexChecksum.SumKVS(), Equals, uint64(1)) +} + +type benchSQL2KVSuite struct { + row []types.Datum + colPerm []int + encoder Encoder + logger log.Logger +} + +var _ = Suite(&benchSQL2KVSuite{}) + +func (s *benchSQL2KVSuite) SetUpTest(c *C) { + // First, create the table info corresponding to TPC-C's "CUSTOMER" table. + p := parser.New() + se := mock.NewContext() + node, err := p.ParseOneStmt(` + create table bmsql_customer( + c_w_id integer not null, + c_d_id integer not null, + c_id integer not null, + c_discount decimal(4,4), + c_credit char(2), + c_last varchar(16), + c_first varchar(16), + c_credit_lim decimal(12,2), + c_balance decimal(12,2), + c_ytd_payment decimal(12,2), + c_payment_cnt integer, + c_delivery_cnt integer, + c_street_1 varchar(20), + c_street_2 varchar(20), + c_city varchar(20), + c_state char(2), + c_zip char(9), + c_phone char(16), + c_since timestamp, + c_middle char(2), + c_data varchar(500), + primary key (c_w_id, c_d_id, c_id) + ); + `, "", "") + c.Assert(err, IsNil) + tableInfo, err := ddl.MockTableInfo(se, node.(*ast.CreateTableStmt), 123456) + c.Assert(err, IsNil) + tableInfo.State = model.StatePublic + + // Construct the corresponding KV encoder. + tbl, err := tables.TableFromMeta(NewPanickingAllocators(0), tableInfo) + c.Assert(err, IsNil) + s.encoder, err = NewTableKVEncoder(tbl, &SessionOptions{SysVars: map[string]string{"tidb_row_format_version": "2"}}) + s.logger = log.Logger{Logger: zap.NewNop()} + + // Prepare the row to insert. + s.row = []types.Datum{ + types.NewIntDatum(15), + types.NewIntDatum(10), + types.NewIntDatum(3000), + types.NewStringDatum("0.3646"), + types.NewStringDatum("GC"), + types.NewStringDatum("CALLYPRIANTI"), + types.NewStringDatum("Rg6mDFlVnP5yh"), + types.NewStringDatum("50000.0"), + types.NewStringDatum("-10.0"), + types.NewStringDatum("10.0"), + types.NewIntDatum(1), + types.NewIntDatum(0), + types.NewStringDatum("aJK7CuRnE0NUxNHSX"), + types.NewStringDatum("Q1rps77cXYoj"), + types.NewStringDatum("MigXbS6UoUS"), + types.NewStringDatum("UJ"), + types.NewStringDatum("638611111"), + types.NewStringDatum("7743262784364376"), + types.NewStringDatum("2020-02-05 19:29:58.903970"), + types.NewStringDatum("OE"), + types.NewStringDatum("H5p3dpjp7uu8n1l3j0o1buecfV6FngNNgftpNALDhOzJaSzMCMlrQwXuvLAFPIFg215D3wAYB62kiixIuasfbD729oq8TwgKzPPsx8kHE1b4AdhHwpCml3ELKiwuNGQl7CcBQOiq6aFEMMHzjGwQyXwGey0wutjp2KP3Nd4qj3FHtmHbsD8cJ0pH9TswNmdQBgXsFPZeJJhsG3rTimQpS9Tmn3vNeI9fFas3ClDZuQtBjqoTJlyzmBIYT8HeV3TuS93TNFDaXZpQqh8HsvlPq4uTTLOO9CguiY29zlSmIjkZYtva3iscG3YDOQVLeGpP9dtqEJwlRvJ4oe9jWkvRMlCeslSNEuzLxjUBtJBnGRFAzJF6RMlIWCkdCpIhcnIy3jUEsxTuiAU3hsZxUjLg2dnOG62h5qR"), + } + s.colPerm = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, -1} +} + +// Run `go test github.com/pingcap/br/pkg/lightning/backend -check.b -test.v` to get benchmark result. +func (s *benchSQL2KVSuite) BenchmarkSQL2KV(c *C) { + for i := 0; i < c.N; i++ { + rows, err := s.encoder.Encode(s.logger, s.row, 1, s.colPerm) + c.Assert(err, IsNil) + c.Assert(rows, HasLen, 2) + } +} diff --git a/pkg/lightning/backend/tidb.go b/pkg/lightning/backend/tidb.go new file mode 100644 index 000000000..d9f64bb13 --- /dev/null +++ b/pkg/lightning/backend/tidb.go @@ -0,0 +1,582 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package backend + +import ( + "context" + "database/sql" + "encoding/hex" + "fmt" + "strconv" + "strings" + "time" + + "github.com/google/uuid" + "github.com/pingcap/errors" + "github.com/pingcap/failpoint" + "github.com/pingcap/parser/model" + "github.com/pingcap/parser/mysql" + "github.com/pingcap/tidb/table" + "github.com/pingcap/tidb/types" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/config" + "github.com/pingcap/br/pkg/lightning/log" + "github.com/pingcap/br/pkg/lightning/verification" +) + +var extraHandleTableColumn = &table.Column{ + ColumnInfo: extraHandleColumnInfo, + GeneratedExpr: nil, + DefaultExpr: nil, +} + +type tidbRow string + +type tidbRows []tidbRow + +// MarshalLogArray implements the zapcore.ArrayMarshaler interface +func (row tidbRows) MarshalLogArray(encoder zapcore.ArrayEncoder) error { + for _, r := range row { + encoder.AppendString(string(r)) + } + return nil +} + +type tidbEncoder struct { + mode mysql.SQLMode + tbl table.Table + se *session + // the index of table columns for each data field. + // index == len(table.columns) means this field is `_tidb_rowid` + columnIdx []int + columnCnt int +} + +type tidbBackend struct { + db *sql.DB + onDuplicate string +} + +// NewTiDBBackend creates a new TiDB backend using the given database. +// +// The backend does not take ownership of `db`. Caller should close `db` +// manually after the backend expired. +func NewTiDBBackend(db *sql.DB, onDuplicate string) Backend { + switch onDuplicate { + case config.ReplaceOnDup, config.IgnoreOnDup, config.ErrorOnDup: + default: + log.L().Warn("unsupported action on duplicate, overwrite with `replace`") + onDuplicate = config.ReplaceOnDup + } + return MakeBackend(&tidbBackend{db: db, onDuplicate: onDuplicate}) +} + +func (row tidbRow) ClassifyAndAppend(data *Rows, checksum *verification.KVChecksum, _ *Rows, _ *verification.KVChecksum) { + rows := (*data).(tidbRows) + *data = tidbRows(append(rows, row)) + cs := verification.MakeKVChecksum(uint64(len(row)), 1, 0) + checksum.Add(&cs) +} + +func (rows tidbRows) SplitIntoChunks(splitSize int) []Rows { + if len(rows) == 0 { + return nil + } + + res := make([]Rows, 0, 1) + i := 0 + cumSize := 0 + + for j, row := range rows { + if i < j && cumSize+len(row) > splitSize { + res = append(res, rows[i:j]) + i = j + cumSize = 0 + } + cumSize += len(row) + } + + return append(res, rows[i:]) +} + +func (rows tidbRows) Clear() Rows { + return rows[:0] +} + +func (enc *tidbEncoder) appendSQLBytes(sb *strings.Builder, value []byte) { + sb.Grow(2 + len(value)) + sb.WriteByte('\'') + if enc.mode.HasNoBackslashEscapesMode() { + for _, b := range value { + if b == '\'' { + sb.WriteString(`''`) + } else { + sb.WriteByte(b) + } + } + } else { + for _, b := range value { + switch b { + case 0: + sb.WriteString(`\0`) + case '\b': + sb.WriteString(`\b`) + case '\n': + sb.WriteString(`\n`) + case '\r': + sb.WriteString(`\r`) + case '\t': + sb.WriteString(`\t`) + case 26: + sb.WriteString(`\Z`) + case '\'': + sb.WriteString(`''`) + case '\\': + sb.WriteString(`\\`) + default: + sb.WriteByte(b) + } + } + } + sb.WriteByte('\'') +} + +// appendSQL appends the SQL representation of the Datum into the string builder. +// Note that we cannot use Datum.ToString since it doesn't perform SQL escaping. +func (enc *tidbEncoder) appendSQL(sb *strings.Builder, datum *types.Datum, col *table.Column) error { + switch datum.Kind() { + case types.KindNull: + sb.WriteString("NULL") + + case types.KindMinNotNull: + sb.WriteString("MINVALUE") + + case types.KindMaxValue: + sb.WriteString("MAXVALUE") + + case types.KindInt64: + // longest int64 = -9223372036854775808 which has 20 characters + var buffer [20]byte + value := strconv.AppendInt(buffer[:0], datum.GetInt64(), 10) + sb.Write(value) + + case types.KindUint64, types.KindMysqlEnum, types.KindMysqlSet: + // longest uint64 = 18446744073709551615 which has 20 characters + var buffer [20]byte + value := strconv.AppendUint(buffer[:0], datum.GetUint64(), 10) + sb.Write(value) + + case types.KindFloat32, types.KindFloat64: + // float64 has 16 digits of precision, so a buffer size of 32 is more than enough... + var buffer [32]byte + value := strconv.AppendFloat(buffer[:0], datum.GetFloat64(), 'g', -1, 64) + sb.Write(value) + case types.KindString: + // See: https://github.com/pingcap/tidb-lightning/issues/550 + //if enc.mode.HasStrictMode() { + // d, err := table.CastValue(enc.se, *datum, col.ToInfo(), false, false) + // if err != nil { + // return errors.Trace(err) + // } + // datum = &d + //} + + enc.appendSQLBytes(sb, datum.GetBytes()) + case types.KindBytes: + enc.appendSQLBytes(sb, datum.GetBytes()) + + case types.KindMysqlJSON: + value, err := datum.GetMysqlJSON().MarshalJSON() + if err != nil { + return err + } + enc.appendSQLBytes(sb, value) + + case types.KindBinaryLiteral: + value := datum.GetBinaryLiteral() + sb.Grow(3 + 2*len(value)) + sb.WriteString("x'") + hex.NewEncoder(sb).Write(value) + sb.WriteByte('\'') + + case types.KindMysqlBit: + var buffer [20]byte + intValue, err := datum.GetBinaryLiteral().ToInt(nil) + if err != nil { + return err + } + value := strconv.AppendUint(buffer[:0], intValue, 10) + sb.Write(value) + + // time, duration, decimal + default: + value, err := datum.ToString() + if err != nil { + return err + } + sb.WriteByte('\'') + sb.WriteString(value) + sb.WriteByte('\'') + } + + return nil +} + +func (*tidbEncoder) Close() {} + +func getColumnByIndex(cols []*table.Column, index int) *table.Column { + if index == len(cols) { + return extraHandleTableColumn + } + return cols[index] +} + +func (enc *tidbEncoder) Encode(logger log.Logger, row []types.Datum, _ int64, columnPermutation []int) (Row, error) { + cols := enc.tbl.Cols() + + if len(enc.columnIdx) == 0 { + columnCount := 0 + columnIdx := make([]int, len(columnPermutation)) + for i, idx := range columnPermutation { + if idx >= 0 { + columnIdx[idx] = i + columnCount++ + } + } + enc.columnIdx = columnIdx + enc.columnCnt = columnCount + } + + // TODO: since the column count doesn't exactly reflect the real column names, we only check the upper bound currently. + // See: tests/generated_columns/data/gencol.various_types.0.sql this sql has no columns, so encodeLoop will fill the + // column permutation with default, thus enc.columnCnt > len(row). + if len(row) > enc.columnCnt { + logger.Error("column count mismatch", zap.Ints("column_permutation", columnPermutation), + zap.Array("data", rowArrayMarshaler(row))) + return nil, errors.Errorf("column count mismatch, expected %d, got %d", enc.columnCnt, len(row)) + } + + var encoded strings.Builder + encoded.Grow(8 * len(row)) + encoded.WriteByte('(') + for i, field := range row { + if i != 0 { + encoded.WriteByte(',') + } + if err := enc.appendSQL(&encoded, &field, getColumnByIndex(cols, enc.columnIdx[i])); err != nil { + logger.Error("tidb encode failed", + zap.Array("original", rowArrayMarshaler(row)), + zap.Int("originalCol", i), + log.ShortError(err), + ) + return nil, err + } + } + encoded.WriteByte(')') + return tidbRow(encoded.String()), nil +} + +func (be *tidbBackend) Close() { + // *Not* going to close `be.db`. The db object is normally borrowed from a + // TidbManager, so we let the manager to close it. +} + +func (be *tidbBackend) MakeEmptyRows() Rows { + return tidbRows(nil) +} + +func (be *tidbBackend) RetryImportDelay() time.Duration { + return 0 +} + +func (be *tidbBackend) MaxChunkSize() int { + failpoint.Inject("FailIfImportedSomeRows", func() { + failpoint.Return(1) + }) + return 1048576 +} + +func (be *tidbBackend) ShouldPostProcess() bool { + return false +} + +func (be *tidbBackend) CheckRequirements(ctx context.Context) error { + log.L().Info("skipping check requirements for tidb backend") + return nil +} + +func (be *tidbBackend) NewEncoder(tbl table.Table, options *SessionOptions) (Encoder, error) { + se := newSession(options) + if options.SQLMode.HasStrictMode() { + se.vars.SkipUTF8Check = false + se.vars.SkipASCIICheck = false + } + + return &tidbEncoder{mode: options.SQLMode, tbl: tbl, se: se}, nil +} + +func (be *tidbBackend) OpenEngine(context.Context, uuid.UUID) error { + return nil +} + +func (be *tidbBackend) CloseEngine(context.Context, uuid.UUID) error { + return nil +} + +func (be *tidbBackend) CleanupEngine(context.Context, uuid.UUID) error { + return nil +} + +func (be *tidbBackend) ImportEngine(context.Context, uuid.UUID) error { + return nil +} + +func (be *tidbBackend) WriteRows(ctx context.Context, _ uuid.UUID, tableName string, columnNames []string, _ uint64, rows Rows) error { + var err error +outside: + for _, r := range rows.SplitIntoChunks(be.MaxChunkSize()) { + for i := 0; i < maxRetryTimes; i++ { + err = be.WriteRowsToDB(ctx, tableName, columnNames, r) + switch { + case err == nil: + continue outside + case common.IsRetryableError(err): + // retry next loop + default: + return err + } + } + return errors.Annotatef(err, "[%s] write rows reach max retry %d and still failed", tableName, maxRetryTimes) + } + return nil +} + +func (be *tidbBackend) WriteRowsToDB(ctx context.Context, tableName string, columnNames []string, r Rows) error { + rows := r.(tidbRows) + if len(rows) == 0 { + return nil + } + + var insertStmt strings.Builder + switch be.onDuplicate { + case config.ReplaceOnDup: + insertStmt.WriteString("REPLACE INTO ") + case config.IgnoreOnDup: + insertStmt.WriteString("INSERT IGNORE INTO ") + case config.ErrorOnDup: + insertStmt.WriteString("INSERT INTO ") + } + + insertStmt.WriteString(tableName) + if len(columnNames) > 0 { + insertStmt.WriteByte('(') + for i, colName := range columnNames { + if i != 0 { + insertStmt.WriteByte(',') + } + common.WriteMySQLIdentifier(&insertStmt, colName) + } + insertStmt.WriteByte(')') + } + insertStmt.WriteString(" VALUES") + + // Note: we are not going to do interpolation (prepared statements) to avoid + // complication arise from data length overflow of BIT and BINARY columns + + for i, row := range rows { + if i != 0 { + insertStmt.WriteByte(',') + } + insertStmt.WriteString(string(row)) + } + + // Retry will be done externally, so we're not going to retry here. + _, err := be.db.ExecContext(ctx, insertStmt.String()) + if err != nil { + log.L().Error("execute statement failed", log.ZapRedactString("stmt", insertStmt.String()), + log.ZapRedactArray("rows", rows), zap.Error(err)) + } + failpoint.Inject("FailIfImportedSomeRows", func() { + panic("forcing failure due to FailIfImportedSomeRows, before saving checkpoint") + }) + return errors.Trace(err) +} + +func (be *tidbBackend) FetchRemoteTableModels(ctx context.Context, schemaName string) (tables []*model.TableInfo, err error) { + s := common.SQLWithRetry{ + DB: be.db, + Logger: log.L(), + } + + err = s.Transact(ctx, "fetch table columns", func(c context.Context, tx *sql.Tx) error { + var versionStr string + if err = tx.QueryRowContext(ctx, "SELECT version()").Scan(&versionStr); err != nil { + return err + } + tidbVersion, err := common.ExtractTiDBVersion(versionStr) + if err != nil { + return err + } + + rows, e := tx.Query(` + SELECT table_name, column_name, column_type, extra + FROM information_schema.columns + WHERE table_schema = ? + ORDER BY table_name, ordinal_position; + `, schemaName) + if e != nil { + return e + } + defer rows.Close() + + var ( + curTableName string + curColOffset int + curTable *model.TableInfo + ) + for rows.Next() { + var tableName, columnName, columnType, columnExtra string + if e := rows.Scan(&tableName, &columnName, &columnType, &columnExtra); e != nil { + return e + } + if tableName != curTableName { + curTable = &model.TableInfo{ + Name: model.NewCIStr(tableName), + State: model.StatePublic, + PKIsHandle: true, + } + tables = append(tables, curTable) + curTableName = tableName + curColOffset = 0 + } + + // see: https://github.com/pingcap/parser/blob/3b2fb4b41d73710bc6c4e1f4e8679d8be6a4863e/types/field_type.go#L185-L191 + var flag uint + if strings.HasSuffix(columnType, "unsigned") { + flag |= mysql.UnsignedFlag + } + if strings.Contains(columnExtra, "auto_increment") { + flag |= mysql.AutoIncrementFlag + } + curTable.Columns = append(curTable.Columns, &model.ColumnInfo{ + Name: model.NewCIStr(columnName), + Offset: curColOffset, + State: model.StatePublic, + FieldType: types.FieldType{ + Flag: flag, + }, + }) + curColOffset++ + } + if rows.Err() != nil { + return rows.Err() + } + // for version < v4.0.0 we can use `show table next_row_id` to fetch auto id info, so about should be enough + if tidbVersion.Major < 4 { + return nil + } + // init auto id column for each table + for _, tbl := range tables { + tblName := common.UniqueTable(schemaName, tbl.Name.O) + rows, e = tx.Query(fmt.Sprintf("SHOW TABLE %s NEXT_ROW_ID", tblName)) + if e != nil { + return e + } + for rows.Next() { + var ( + dbName, tblName, columnName, idType string + nextID int64 + ) + columns, err := rows.Columns() + if err != nil { + return err + } + + //+--------------+------------+-------------+--------------------+----------------+ + //| DB_NAME | TABLE_NAME | COLUMN_NAME | NEXT_GLOBAL_ROW_ID | ID_TYPE | + //+--------------+------------+-------------+--------------------+----------------+ + //| testsysbench | t | _tidb_rowid | 1 | AUTO_INCREMENT | + //+--------------+------------+-------------+--------------------+----------------+ + + // if columns length is 4, it doesn't contains the last column `ID_TYPE`, and it will always be 'AUTO_INCREMENT' + // for v4.0.0~v4.0.2 show table t next_row_id only returns 4 columns. + if len(columns) == 4 { + err = rows.Scan(&dbName, &tblName, &columnName, &nextID) + idType = "AUTO_INCREMENT" + } else { + err = rows.Scan(&dbName, &tblName, &columnName, &nextID, &idType) + } + if err != nil { + return err + } + + for _, col := range tbl.Columns { + if col.Name.O == columnName { + switch idType { + case "AUTO_INCREMENT": + col.Flag |= mysql.AutoIncrementFlag + case "AUTO_RANDOM": + col.Flag |= mysql.PriKeyFlag + tbl.PKIsHandle = true + // set a stub here, since we don't really need the real value + tbl.AutoRandomBits = 1 + } + } + } + } + rows.Close() + if rows.Err() != nil { + return rows.Err() + } + } + return nil + }) + return +} + +func (be *tidbBackend) EngineFileSizes() []EngineFileSize { + return nil +} + +func (be *tidbBackend) FlushEngine(context.Context, uuid.UUID) error { + return nil +} + +func (be *tidbBackend) FlushAllEngines(context.Context) error { + return nil +} + +func (be *tidbBackend) ResetEngine(context.Context, uuid.UUID) error { + return errors.New("cannot reset an engine in TiDB backend") +} + +func (be *tidbBackend) LocalWriter(ctx context.Context, engineUUID uuid.UUID, maxCacheSize int64) (EngineWriter, error) { + return &TiDBWriter{be: be, engineUUID: engineUUID}, nil +} + +type TiDBWriter struct { + be *tidbBackend + engineUUID uuid.UUID +} + +func (w *TiDBWriter) Close() error { + return nil +} + +func (w *TiDBWriter) AppendRows(ctx context.Context, tableName string, columnNames []string, arg1 uint64, rows Rows) error { + return w.be.WriteRows(ctx, w.engineUUID, tableName, columnNames, arg1, rows) +} diff --git a/pkg/lightning/backend/tidb_test.go b/pkg/lightning/backend/tidb_test.go new file mode 100644 index 000000000..acb87b9ad --- /dev/null +++ b/pkg/lightning/backend/tidb_test.go @@ -0,0 +1,361 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package backend_test + +import ( + "context" + "database/sql" + "fmt" + + "github.com/pingcap/parser/charset" + + "github.com/DATA-DOG/go-sqlmock" + . "github.com/pingcap/check" + "github.com/pingcap/parser/model" + "github.com/pingcap/parser/mysql" + "github.com/pingcap/tidb/table" + "github.com/pingcap/tidb/table/tables" + "github.com/pingcap/tidb/types" + + kv "github.com/pingcap/br/pkg/lightning/backend" + "github.com/pingcap/br/pkg/lightning/config" + "github.com/pingcap/br/pkg/lightning/log" + "github.com/pingcap/br/pkg/lightning/verification" +) + +var _ = Suite(&mysqlSuite{}) + +type mysqlSuite struct { + dbHandle *sql.DB + mockDB sqlmock.Sqlmock + backend kv.Backend + tbl table.Table +} + +func (s *mysqlSuite) SetUpTest(c *C) { + db, mock, err := sqlmock.New() + c.Assert(err, IsNil) + + tys := []byte{ + mysql.TypeLong, mysql.TypeLong, mysql.TypeTiny, mysql.TypeInt24, mysql.TypeFloat, mysql.TypeDouble, + mysql.TypeDouble, mysql.TypeDouble, mysql.TypeVarchar, mysql.TypeBlob, mysql.TypeBit, mysql.TypeNewDecimal, mysql.TypeEnum, + } + cols := make([]*model.ColumnInfo, 0, len(tys)) + for i, ty := range tys { + col := &model.ColumnInfo{ID: int64(i + 1), Name: model.NewCIStr(fmt.Sprintf("c%d", i)), State: model.StatePublic, Offset: i, FieldType: *types.NewFieldType(ty)} + cols = append(cols, col) + } + tblInfo := &model.TableInfo{ID: 1, Columns: cols, PKIsHandle: false, State: model.StatePublic} + tbl, err := tables.TableFromMeta(kv.NewPanickingAllocators(0), tblInfo) + c.Assert(err, IsNil) + + s.dbHandle = db + s.mockDB = mock + s.backend = kv.NewTiDBBackend(db, config.ReplaceOnDup) + s.tbl = tbl +} + +func (s *mysqlSuite) TearDownTest(c *C) { + s.backend.Close() + c.Assert(s.mockDB.ExpectationsWereMet(), IsNil) +} + +func (s *mysqlSuite) TestWriteRowsReplaceOnDup(c *C) { + s.mockDB. + ExpectExec("\\QREPLACE INTO `foo`.`bar`(`a`,`b`,`c`,`d`,`e`,`f`,`g`,`h`,`i`,`j`,`k`,`l`,`m`,`n`,`o`) VALUES(18446744073709551615,-9223372036854775808,0,NULL,7.5,5e-324,1.7976931348623157e+308,0,'甲乙丙\\r\\n\\0\\Z''\"\\\\`',x'000000abcdef',2557891634,'12.5',51)\\E"). + WillReturnResult(sqlmock.NewResult(1, 1)) + + ctx := context.Background() + logger := log.L() + + engine, err := s.backend.OpenEngine(ctx, "`foo`.`bar`", 1) + c.Assert(err, IsNil) + + dataRows := s.backend.MakeEmptyRows() + dataChecksum := verification.MakeKVChecksum(0, 0, 0) + indexRows := s.backend.MakeEmptyRows() + indexChecksum := verification.MakeKVChecksum(0, 0, 0) + + cols := s.tbl.Cols() + perms := make([]int, 0, len(s.tbl.Cols())+1) + for i := 0; i < len(cols); i++ { + perms = append(perms, i) + } + perms = append(perms, -1) + encoder, err := s.backend.NewEncoder(s.tbl, &kv.SessionOptions{SQLMode: 0, Timestamp: 1234567890}) + c.Assert(err, IsNil) + row, err := encoder.Encode(logger, []types.Datum{ + types.NewUintDatum(18446744073709551615), + types.NewIntDatum(-9223372036854775808), + types.NewUintDatum(0), + {}, + types.NewFloat32Datum(7.5), + types.NewFloat64Datum(5e-324), + types.NewFloat64Datum(1.7976931348623157e+308), + types.NewFloat64Datum(-0.0), + types.NewStringDatum("甲乙丙\r\n\x00\x1a'\"\\`"), + types.NewBinaryLiteralDatum(types.NewBinaryLiteralFromUint(0xabcdef, 6)), + types.NewMysqlBitDatum(types.NewBinaryLiteralFromUint(0x98765432, 4)), + types.NewDecimalDatum(types.NewDecFromFloatForTest(12.5)), + types.NewMysqlEnumDatum(types.Enum{Name: "ENUM_NAME", Value: 51}), + }, 1, perms) + c.Assert(err, IsNil) + row.ClassifyAndAppend(&dataRows, &dataChecksum, &indexRows, &indexChecksum) + + err = engine.WriteRows(ctx, []string{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o"}, dataRows) + c.Assert(err, IsNil) +} + +func (s *mysqlSuite) TestWriteRowsIgnoreOnDup(c *C) { + s.mockDB. + ExpectExec("\\QINSERT IGNORE INTO `foo`.`bar`(`a`) VALUES(1)\\E"). + WillReturnResult(sqlmock.NewResult(1, 1)) + + ctx := context.Background() + logger := log.L() + + ignoreBackend := kv.NewTiDBBackend(s.dbHandle, config.IgnoreOnDup) + engine, err := ignoreBackend.OpenEngine(ctx, "`foo`.`bar`", 1) + c.Assert(err, IsNil) + + dataRows := ignoreBackend.MakeEmptyRows() + dataChecksum := verification.MakeKVChecksum(0, 0, 0) + indexRows := ignoreBackend.MakeEmptyRows() + indexChecksum := verification.MakeKVChecksum(0, 0, 0) + + encoder, err := ignoreBackend.NewEncoder(s.tbl, &kv.SessionOptions{}) + c.Assert(err, IsNil) + row, err := encoder.Encode(logger, []types.Datum{ + types.NewIntDatum(1), + }, 1, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, -1}) + c.Assert(err, IsNil) + row.ClassifyAndAppend(&dataRows, &dataChecksum, &indexRows, &indexChecksum) + + err = engine.WriteRows(ctx, []string{"a"}, dataRows) + c.Assert(err, IsNil) + + // test encode rows with _tidb_rowid + encoder, err = ignoreBackend.NewEncoder(s.tbl, &kv.SessionOptions{}) + c.Assert(err, IsNil) + row, err = encoder.Encode(logger, []types.Datum{ + types.NewIntDatum(1), + types.NewIntDatum(1), // _tidb_rowid field + }, 1, []int{0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1}) + c.Assert(err, IsNil) +} + +func (s *mysqlSuite) TestWriteRowsErrorOnDup(c *C) { + s.mockDB. + ExpectExec("\\QINSERT INTO `foo`.`bar`(`a`) VALUES(1)\\E"). + WillReturnResult(sqlmock.NewResult(1, 1)) + + ctx := context.Background() + logger := log.L() + + ignoreBackend := kv.NewTiDBBackend(s.dbHandle, config.ErrorOnDup) + engine, err := ignoreBackend.OpenEngine(ctx, "`foo`.`bar`", 1) + c.Assert(err, IsNil) + + dataRows := ignoreBackend.MakeEmptyRows() + dataChecksum := verification.MakeKVChecksum(0, 0, 0) + indexRows := ignoreBackend.MakeEmptyRows() + indexChecksum := verification.MakeKVChecksum(0, 0, 0) + + encoder, err := ignoreBackend.NewEncoder(s.tbl, &kv.SessionOptions{}) + c.Assert(err, IsNil) + row, err := encoder.Encode(logger, []types.Datum{ + types.NewIntDatum(1), + }, 1, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, -1}) + c.Assert(err, IsNil) + + row.ClassifyAndAppend(&dataRows, &dataChecksum, &indexRows, &indexChecksum) + + err = engine.WriteRows(ctx, []string{"a"}, dataRows) + c.Assert(err, IsNil) +} + +// TODO: temporarily disable this test before we fix strict mode +func (s *mysqlSuite) testStrictMode(c *C) { + ft := *types.NewFieldType(mysql.TypeVarchar) + ft.Charset = charset.CharsetUTF8MB4 + col0 := &model.ColumnInfo{ID: 1, Name: model.NewCIStr("s0"), State: model.StatePublic, Offset: 0, FieldType: ft} + ft = *types.NewFieldType(mysql.TypeString) + ft.Charset = charset.CharsetASCII + col1 := &model.ColumnInfo{ID: 2, Name: model.NewCIStr("s1"), State: model.StatePublic, Offset: 1, FieldType: ft} + tblInfo := &model.TableInfo{ID: 1, Columns: []*model.ColumnInfo{col0, col1}, PKIsHandle: false, State: model.StatePublic} + tbl, err := tables.TableFromMeta(kv.NewPanickingAllocators(0), tblInfo) + c.Assert(err, IsNil) + + bk := kv.NewTiDBBackend(s.dbHandle, config.ErrorOnDup) + encoder, err := bk.NewEncoder(tbl, &kv.SessionOptions{SQLMode: mysql.ModeStrictAllTables}) + c.Assert(err, IsNil) + + logger := log.L() + _, err = encoder.Encode(logger, []types.Datum{ + types.NewStringDatum("test"), + }, 1, []int{0, -1, -1}) + c.Assert(err, IsNil) + + _, err = encoder.Encode(logger, []types.Datum{ + types.NewStringDatum("\xff\xff\xff\xff"), + }, 1, []int{0, -1, -1}) + c.Assert(err, ErrorMatches, `.*incorrect utf8 value .* for column s0`) + + // oepn a new encode because column count changed. + encoder, err = bk.NewEncoder(tbl, &kv.SessionOptions{SQLMode: mysql.ModeStrictAllTables}) + c.Assert(err, IsNil) + _, err = encoder.Encode(logger, []types.Datum{ + types.NewStringDatum(""), + types.NewStringDatum("éž ASCII 字符串"), + }, 1, []int{0, 1, -1}) + c.Assert(err, ErrorMatches, ".*incorrect ascii value .* for column s1") +} + +func (s *mysqlSuite) TestFetchRemoteTableModels_3_x(c *C) { + s.mockDB.ExpectBegin() + s.mockDB.ExpectQuery("SELECT version()"). + WillReturnRows(sqlmock.NewRows([]string{"version()"}).AddRow("5.7.25-TiDB-v3.0.18")) + s.mockDB.ExpectQuery("\\QSELECT table_name, column_name, column_type, extra FROM information_schema.columns WHERE table_schema = ? ORDER BY table_name, ordinal_position;\\E"). + WithArgs("test"). + WillReturnRows(sqlmock.NewRows([]string{"table_name", "column_name", "column_type", "extra"}). + AddRow("t", "id", "int(10)", "auto_increment")) + s.mockDB.ExpectCommit() + + bk := kv.NewTiDBBackend(s.dbHandle, config.ErrorOnDup) + tableInfos, err := bk.FetchRemoteTableModels(context.Background(), "test") + c.Assert(err, IsNil) + c.Assert(tableInfos, DeepEquals, []*model.TableInfo{ + { + Name: model.NewCIStr("t"), + State: model.StatePublic, + PKIsHandle: true, + Columns: []*model.ColumnInfo{ + { + Name: model.NewCIStr("id"), + Offset: 0, + State: model.StatePublic, + FieldType: types.FieldType{ + Flag: mysql.AutoIncrementFlag, + }, + }, + }, + }, + }) +} + +func (s *mysqlSuite) TestFetchRemoteTableModels_4_0(c *C) { + s.mockDB.ExpectBegin() + s.mockDB.ExpectQuery("SELECT version()"). + WillReturnRows(sqlmock.NewRows([]string{"version()"}).AddRow("5.7.25-TiDB-v4.0.0")) + s.mockDB.ExpectQuery("\\QSELECT table_name, column_name, column_type, extra FROM information_schema.columns WHERE table_schema = ? ORDER BY table_name, ordinal_position;\\E"). + WithArgs("test"). + WillReturnRows(sqlmock.NewRows([]string{"table_name", "column_name", "column_type", "extra"}). + AddRow("t", "id", "bigint(20) unsigned", "auto_increment")) + s.mockDB.ExpectQuery("SHOW TABLE `test`.`t` NEXT_ROW_ID"). + WillReturnRows(sqlmock.NewRows([]string{"DB_NAME", "TABLE_NAME", "COLUMN_NAME", "NEXT_GLOBAL_ROW_ID"}). + AddRow("test", "t", "id", int64(1))) + s.mockDB.ExpectCommit() + + bk := kv.NewTiDBBackend(s.dbHandle, config.ErrorOnDup) + tableInfos, err := bk.FetchRemoteTableModels(context.Background(), "test") + c.Assert(err, IsNil) + c.Assert(tableInfos, DeepEquals, []*model.TableInfo{ + { + Name: model.NewCIStr("t"), + State: model.StatePublic, + PKIsHandle: true, + Columns: []*model.ColumnInfo{ + { + Name: model.NewCIStr("id"), + Offset: 0, + State: model.StatePublic, + FieldType: types.FieldType{ + Flag: mysql.AutoIncrementFlag | mysql.UnsignedFlag, + }, + }, + }, + }, + }) +} + +func (s *mysqlSuite) TestFetchRemoteTableModels_4_x_auto_increment(c *C) { + s.mockDB.ExpectBegin() + s.mockDB.ExpectQuery("SELECT version()"). + WillReturnRows(sqlmock.NewRows([]string{"version()"}).AddRow("5.7.25-TiDB-v4.0.7")) + s.mockDB.ExpectQuery("\\QSELECT table_name, column_name, column_type, extra FROM information_schema.columns WHERE table_schema = ? ORDER BY table_name, ordinal_position;\\E"). + WithArgs("test"). + WillReturnRows(sqlmock.NewRows([]string{"table_name", "column_name", "column_type", "extra"}). + AddRow("t", "id", "bigint(20)", "")) + s.mockDB.ExpectQuery("SHOW TABLE `test`.`t` NEXT_ROW_ID"). + WillReturnRows(sqlmock.NewRows([]string{"DB_NAME", "TABLE_NAME", "COLUMN_NAME", "NEXT_GLOBAL_ROW_ID", "ID_TYPE"}). + AddRow("test", "t", "id", int64(1), "AUTO_INCREMENT")) + s.mockDB.ExpectCommit() + + bk := kv.NewTiDBBackend(s.dbHandle, config.ErrorOnDup) + tableInfos, err := bk.FetchRemoteTableModels(context.Background(), "test") + c.Assert(err, IsNil) + c.Assert(tableInfos, DeepEquals, []*model.TableInfo{ + { + Name: model.NewCIStr("t"), + State: model.StatePublic, + PKIsHandle: true, + Columns: []*model.ColumnInfo{ + { + Name: model.NewCIStr("id"), + Offset: 0, + State: model.StatePublic, + FieldType: types.FieldType{ + Flag: mysql.AutoIncrementFlag, + }, + }, + }, + }, + }) +} + +func (s *mysqlSuite) TestFetchRemoteTableModels_4_x_auto_random(c *C) { + s.mockDB.ExpectBegin() + s.mockDB.ExpectQuery("SELECT version()"). + WillReturnRows(sqlmock.NewRows([]string{"version()"}).AddRow("5.7.25-TiDB-v4.0.7")) + s.mockDB.ExpectQuery("\\QSELECT table_name, column_name, column_type, extra FROM information_schema.columns WHERE table_schema = ? ORDER BY table_name, ordinal_position;\\E"). + WithArgs("test"). + WillReturnRows(sqlmock.NewRows([]string{"table_name", "column_name", "column_type", "extra"}). + AddRow("t", "id", "bigint(20)", "")) + s.mockDB.ExpectQuery("SHOW TABLE `test`.`t` NEXT_ROW_ID"). + WillReturnRows(sqlmock.NewRows([]string{"DB_NAME", "TABLE_NAME", "COLUMN_NAME", "NEXT_GLOBAL_ROW_ID", "ID_TYPE"}). + AddRow("test", "t", "id", int64(1), "AUTO_RANDOM")) + s.mockDB.ExpectCommit() + + bk := kv.NewTiDBBackend(s.dbHandle, config.ErrorOnDup) + tableInfos, err := bk.FetchRemoteTableModels(context.Background(), "test") + c.Assert(err, IsNil) + c.Assert(tableInfos, DeepEquals, []*model.TableInfo{ + { + Name: model.NewCIStr("t"), + State: model.StatePublic, + PKIsHandle: true, + AutoRandomBits: 1, + Columns: []*model.ColumnInfo{ + { + Name: model.NewCIStr("id"), + Offset: 0, + State: model.StatePublic, + FieldType: types.FieldType{ + Flag: mysql.PriKeyFlag, + }, + }, + }, + }, + }) +} diff --git a/pkg/lightning/backend/tikv.go b/pkg/lightning/backend/tikv.go new file mode 100644 index 000000000..006b0066f --- /dev/null +++ b/pkg/lightning/backend/tikv.go @@ -0,0 +1,194 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package backend + +import ( + "context" + "regexp" + + "github.com/pingcap/errors" + "github.com/pingcap/kvproto/pkg/debugpb" + "github.com/pingcap/kvproto/pkg/import_sstpb" + "go.uber.org/zap" + "golang.org/x/sync/errgroup" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/log" +) + +// StoreState is the state of a TiKV store. The numerical value is sorted by +// the store's accessibility (Tombstone < Down < Disconnected < Offline < Up). +// +// The meaning of each state can be found from PingCAP's documentation at +// https://pingcap.com/docs/v3.0/how-to/scale/horizontally/#delete-a-node-dynamically-1 +type StoreState int + +const ( + // StoreStateUp means the TiKV store is in service. + StoreStateUp StoreState = -iota + // StoreStateOffline means the TiKV store is in the process of being taken + // offline (but is still accessible). + StoreStateOffline + // StoreStateDisconnected means the TiKV store does not respond to PD. + StoreStateDisconnected + // StoreStateDown means the TiKV store does not respond to PD for a long + // time (> 30 minutes). + StoreStateDown + // StoreStateTombstone means the TiKV store is shut down and the data has + // been evacuated. Lightning should never interact with stores in this + // state. + StoreStateTombstone +) + +var jsonToStoreState = map[string]StoreState{ + `"Up"`: StoreStateUp, + `"Offline"`: StoreStateOffline, + `"Disconnected"`: StoreStateDisconnected, + `"Down"`: StoreStateDown, + `"Tombstone"`: StoreStateTombstone, +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (ss *StoreState) UnmarshalJSON(content []byte) error { + if state, ok := jsonToStoreState[string(content)]; ok { + *ss = state + return nil + } + return errors.New("Unknown store state") +} + +// Store contains metadata about a TiKV store. +type Store struct { + Address string + Version string + State StoreState `json:"state_name"` +} + +func withTiKVConnection(ctx context.Context, tls *common.TLS, tikvAddr string, action func(import_sstpb.ImportSSTClient) error) error { + // Connect to the ImportSST service on the given TiKV node. + // The connection is needed for executing `action` and will be tear down + // when this function exits. + conn, err := grpc.DialContext(ctx, tikvAddr, tls.ToGRPCDialOption()) + if err != nil { + return errors.Trace(err) + } + defer conn.Close() + + client := import_sstpb.NewImportSSTClient(conn) + return action(client) +} + +// ForAllStores executes `action` in parallel for all TiKV stores connected to +// a PD server given by the HTTPS client `tls`. +// +// Returns the first non-nil error returned in all `action` calls. If all +// `action` returns nil, this method would return nil as well. +// +// The `minState` argument defines the minimum store state to be included in the +// result (Tombstone < Offline < Down < Disconnected < Up). +func ForAllStores( + ctx context.Context, + tls *common.TLS, + minState StoreState, + action func(c context.Context, store *Store) error, +) error { + // Go through the HTTP interface instead of gRPC so we don't need to keep + // track of the cluster ID. + var stores struct { + Stores []struct { + Store Store + } + } + err := tls.GetJSON(ctx, "/pd/api/v1/stores", &stores) + if err != nil { + return err + } + + eg, c := errgroup.WithContext(ctx) + for _, store := range stores.Stores { + if store.Store.State >= minState { + s := store.Store + eg.Go(func() error { return action(c, &s) }) + } + } + return eg.Wait() +} + +func ignoreUnimplementedError(err error, logger log.Logger) error { + if status.Code(err) == codes.Unimplemented { + logger.Debug("skipping potentially TiFlash store") + return nil + } + return errors.Trace(err) +} + +// SwitchMode changes the TiKV node at the given address to a particular mode. +func SwitchMode(ctx context.Context, tls *common.TLS, tikvAddr string, mode import_sstpb.SwitchMode) error { + task := log.With(zap.Stringer("mode", mode), zap.String("tikv", tikvAddr)).Begin(zap.DebugLevel, "switch mode") + err := withTiKVConnection(ctx, tls, tikvAddr, func(client import_sstpb.ImportSSTClient) error { + _, err := client.SwitchMode(ctx, &import_sstpb.SwitchModeRequest{ + Mode: mode, + }) + return ignoreUnimplementedError(err, task.Logger) + }) + task.End(zap.InfoLevel, err) + return err +} + +// Compact performs a leveled compaction with the given minimum level. +func Compact(ctx context.Context, tls *common.TLS, tikvAddr string, level int32) error { + task := log.With(zap.Int32("level", level), zap.String("tikv", tikvAddr)).Begin(zap.InfoLevel, "compact cluster") + err := withTiKVConnection(ctx, tls, tikvAddr, func(client import_sstpb.ImportSSTClient) error { + _, err := client.Compact(ctx, &import_sstpb.CompactRequest{ + OutputLevel: level, + }) + return ignoreUnimplementedError(err, task.Logger) + }) + task.End(zap.ErrorLevel, err) + return err +} + +var fetchModeRegexp = regexp.MustCompile(`\btikv_config_rocksdb\{cf="default",name="hard_pending_compaction_bytes_limit"\} ([^\n]+)`) + +// FetchMode obtains the import mode status of the TiKV node. +func FetchMode(ctx context.Context, tls *common.TLS, tikvAddr string) (import_sstpb.SwitchMode, error) { + conn, err := grpc.DialContext(ctx, tikvAddr, tls.ToGRPCDialOption()) + if err != nil { + return 0, err + } + defer conn.Close() + + client := debugpb.NewDebugClient(conn) + resp, err := client.GetMetrics(ctx, &debugpb.GetMetricsRequest{All: false}) + if err != nil { + return 0, errors.Trace(err) + } + return FetchModeFromMetrics(resp.Prometheus) +} + +// FetchMode obtains the import mode status from the Prometheus metrics of a TiKV node. +func FetchModeFromMetrics(metrics string) (import_sstpb.SwitchMode, error) { + m := fetchModeRegexp.FindStringSubmatch(metrics) + switch { + case len(m) < 2: + return 0, errors.New("import mode status is not exposed") + case m[1] == "0": + return import_sstpb.SwitchMode_Import, nil + default: + return import_sstpb.SwitchMode_Normal, nil + } +} diff --git a/pkg/lightning/backend/tikv_test.go b/pkg/lightning/backend/tikv_test.go new file mode 100644 index 000000000..a74b42c79 --- /dev/null +++ b/pkg/lightning/backend/tikv_test.go @@ -0,0 +1,148 @@ +package backend_test + +import ( + "context" + "net/http" + "net/http/httptest" + "sort" + "sync" + + . "github.com/pingcap/check" + "github.com/pingcap/kvproto/pkg/import_sstpb" + + kv "github.com/pingcap/br/pkg/lightning/backend" + "github.com/pingcap/br/pkg/lightning/common" +) + +type tikvSuite struct{} + +var _ = Suite(&tikvSuite{}) + +func (s *tikvSuite) TestForAllStores(c *C) { + server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.Write([]byte(` + { + "count": 5, + "stores": [ + { + "store": { + "id": 1, + "address": "127.0.0.1:20160", + "version": "3.0.0-beta.1", + "state_name": "Up" + }, + "status": {} + }, + { + "store": { + "id": 2, + "address": "127.0.0.1:20161", + "version": "3.0.0-rc.1", + "state_name": "Down" + }, + "status": {} + }, + { + "store": { + "id": 3, + "address": "127.0.0.1:20162", + "version": "3.0.0-rc.2", + "state_name": "Disconnected" + }, + "status": {} + }, + { + "store": { + "id": 4, + "address": "127.0.0.1:20163", + "version": "3.0.0", + "state_name": "Tombstone" + }, + "status": {} + }, + { + "store": { + "id": 5, + "address": "127.0.0.1:20164", + "version": "3.0.1", + "state_name": "Offline" + }, + "status": {} + } + ] + } + `)) + })) + defer server.Close() + + ctx := context.Background() + var ( + allStoresLock sync.Mutex + allStores []*kv.Store + ) + tls := common.NewTLSFromMockServer(server) + err := kv.ForAllStores(ctx, tls, kv.StoreStateDown, func(c2 context.Context, store *kv.Store) error { + allStoresLock.Lock() + allStores = append(allStores, store) + allStoresLock.Unlock() + return nil + }) + c.Assert(err, IsNil) + + sort.Slice(allStores, func(i, j int) bool { return allStores[i].Address < allStores[j].Address }) + + c.Assert(allStores, DeepEquals, []*kv.Store{ + { + Address: "127.0.0.1:20160", + Version: "3.0.0-beta.1", + State: kv.StoreStateUp, + }, + { + Address: "127.0.0.1:20161", + Version: "3.0.0-rc.1", + State: kv.StoreStateDown, + }, + { + Address: "127.0.0.1:20162", + Version: "3.0.0-rc.2", + State: kv.StoreStateDisconnected, + }, + { + Address: "127.0.0.1:20164", + Version: "3.0.1", + State: kv.StoreStateOffline, + }, + }) +} + +func (s *tikvSuite) TestFetchModeFromMetrics(c *C) { + testCases := []struct { + metrics string + mode import_sstpb.SwitchMode + isErr bool + }{ + { + metrics: `tikv_config_rocksdb{cf="default",name="hard_pending_compaction_bytes_limit"} 274877906944`, + mode: import_sstpb.SwitchMode_Normal, + }, + { + metrics: `tikv_config_rocksdb{cf="default",name="hard_pending_compaction_bytes_limit"} 0`, + mode: import_sstpb.SwitchMode_Import, + }, + { + metrics: ``, + isErr: true, + }, + } + + for _, tc := range testCases { + comment := Commentf("test case '%s'", tc.metrics) + mode, err := kv.FetchModeFromMetrics(tc.metrics) + if tc.isErr { + c.Assert(err, NotNil, comment) + } else { + c.Assert(err, IsNil, comment) + c.Assert(mode, Equals, tc.mode, comment) + } + } +} diff --git a/pkg/lightning/checkpoints/checkpoints.go b/pkg/lightning/checkpoints/checkpoints.go new file mode 100644 index 000000000..5d0a9935f --- /dev/null +++ b/pkg/lightning/checkpoints/checkpoints.go @@ -0,0 +1,1605 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package checkpoints + +import ( + "context" + "database/sql" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "math" + "os" + "sort" + "strings" + "sync" + + "github.com/joho/sqltocsv" + "github.com/pingcap/errors" + "go.uber.org/zap" + "modernc.org/mathutil" + + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/config" + "github.com/pingcap/br/pkg/lightning/log" + "github.com/pingcap/br/pkg/lightning/mydump" + verify "github.com/pingcap/br/pkg/lightning/verification" +) + +type CheckpointStatus uint8 + +const ( + CheckpointStatusMissing CheckpointStatus = 0 + CheckpointStatusMaxInvalid CheckpointStatus = 25 + CheckpointStatusLoaded CheckpointStatus = 30 + CheckpointStatusAllWritten CheckpointStatus = 60 + CheckpointStatusClosed CheckpointStatus = 90 + CheckpointStatusImported CheckpointStatus = 120 + CheckpointStatusIndexImported CheckpointStatus = 140 + CheckpointStatusAlteredAutoInc CheckpointStatus = 150 + CheckpointStatusChecksumSkipped CheckpointStatus = 170 + CheckpointStatusChecksummed CheckpointStatus = 180 + CheckpointStatusAnalyzeSkipped CheckpointStatus = 200 + CheckpointStatusAnalyzed CheckpointStatus = 210 +) + +const WholeTableEngineID = math.MaxInt32 + +const ( + // the table names to store each kind of checkpoint in the checkpoint database + // remember to increase the version number in case of incompatible change. + CheckpointTableNameTask = "task_v2" + CheckpointTableNameTable = "table_v6" + CheckpointTableNameEngine = "engine_v5" + CheckpointTableNameChunk = "chunk_v5" +) + +const ( + // shared by MySQLCheckpointsDB and GlueCheckpointsDB + CreateDBTemplate = "CREATE DATABASE IF NOT EXISTS %s;" + CreateTaskTableTemplate = ` + CREATE TABLE IF NOT EXISTS %s.%s ( + id tinyint(1) PRIMARY KEY, + task_id bigint NOT NULL, + source_dir varchar(256) NOT NULL, + backend varchar(16) NOT NULL, + importer_addr varchar(256), + tidb_host varchar(128) NOT NULL, + tidb_port int NOT NULL, + pd_addr varchar(128) NOT NULL, + sorted_kv_dir varchar(256) NOT NULL, + lightning_ver varchar(48) NOT NULL + );` + CreateTableTableTemplate = ` + CREATE TABLE IF NOT EXISTS %s.%s ( + task_id bigint NOT NULL, + table_name varchar(261) NOT NULL PRIMARY KEY, + hash binary(32) NOT NULL, + status tinyint unsigned DEFAULT 30, + alloc_base bigint NOT NULL DEFAULT 0, + table_id bigint NOT NULL DEFAULT 0, + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + INDEX(task_id) + );` + CreateEngineTableTemplate = ` + CREATE TABLE IF NOT EXISTS %s.%s ( + table_name varchar(261) NOT NULL, + engine_id int NOT NULL, + status tinyint unsigned DEFAULT 30, + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY(table_name, engine_id DESC) + );` + CreateChunkTableTemplate = ` + CREATE TABLE IF NOT EXISTS %s.%s ( + table_name varchar(261) NOT NULL, + engine_id int unsigned NOT NULL, + path varchar(2048) NOT NULL, + offset bigint NOT NULL, + type int NOT NULL, + compression int NOT NULL, + sort_key varchar(256) NOT NULL, + file_size bigint NOT NULL, + columns text NULL, + should_include_row_id BOOL NOT NULL, + end_offset bigint NOT NULL, + pos bigint NOT NULL, + prev_rowid_max bigint NOT NULL, + rowid_max bigint NOT NULL, + kvc_bytes bigint unsigned NOT NULL DEFAULT 0, + kvc_kvs bigint unsigned NOT NULL DEFAULT 0, + kvc_checksum bigint unsigned NOT NULL DEFAULT 0, + create_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY(table_name, engine_id, path(500), offset) + );` + InitTaskTemplate = ` + REPLACE INTO %s.%s (id, task_id, source_dir, backend, importer_addr, tidb_host, tidb_port, pd_addr, sorted_kv_dir, lightning_ver) + VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?, ?);` + InitTableTemplate = ` + INSERT INTO %s.%s (task_id, table_name, hash, table_id) VALUES (?, ?, ?, ?) + ON DUPLICATE KEY UPDATE task_id = CASE + WHEN hash = VALUES(hash) + THEN VALUES(task_id) + END;` + ReadTaskTemplate = ` + SELECT task_id, source_dir, backend, importer_addr, tidb_host, tidb_port, pd_addr, sorted_kv_dir, lightning_ver FROM %s.%s WHERE id = 1;` + ReadEngineTemplate = ` + SELECT engine_id, status FROM %s.%s WHERE table_name = ? ORDER BY engine_id DESC;` + ReadChunkTemplate = ` + SELECT + engine_id, path, offset, type, compression, sort_key, file_size, columns, + pos, end_offset, prev_rowid_max, rowid_max, + kvc_bytes, kvc_kvs, kvc_checksum, unix_timestamp(create_time) + FROM %s.%s WHERE table_name = ? + ORDER BY engine_id, path, offset;` + ReadTableRemainTemplate = ` + SELECT status, alloc_base, table_id FROM %s.%s WHERE table_name = ?;` + ReplaceEngineTemplate = ` + REPLACE INTO %s.%s (table_name, engine_id, status) VALUES (?, ?, ?);` + ReplaceChunkTemplate = ` + REPLACE INTO %s.%s ( + table_name, engine_id, + path, offset, type, compression, sort_key, file_size, columns, should_include_row_id, + pos, end_offset, prev_rowid_max, rowid_max, + kvc_bytes, kvc_kvs, kvc_checksum, create_time + ) VALUES ( + ?, ?, + ?, ?, ?, ?, ?, ?, ?, FALSE, + ?, ?, ?, ?, + 0, 0, 0, from_unixtime(?) + );` + UpdateChunkTemplate = ` + UPDATE %s.%s SET pos = ?, prev_rowid_max = ?, kvc_bytes = ?, kvc_kvs = ?, kvc_checksum = ?, columns = ? + WHERE (table_name, engine_id, path, offset) = (?, ?, ?, ?);` + UpdateTableRebaseTemplate = ` + UPDATE %s.%s SET alloc_base = GREATEST(?, alloc_base) WHERE table_name = ?;` + UpdateTableStatusTemplate = ` + UPDATE %s.%s SET status = ? WHERE table_name = ?;` + UpdateEngineTemplate = ` + UPDATE %s.%s SET status = ? WHERE (table_name, engine_id) = (?, ?);` + DeleteCheckpointRecordTemplate = "DELETE FROM %s.%s WHERE table_name = ?;" +) + +func IsCheckpointTable(name string) bool { + switch name { + case CheckpointTableNameTask, CheckpointTableNameTable, CheckpointTableNameEngine, CheckpointTableNameChunk: + return true + default: + return false + } +} + +func (status CheckpointStatus) MetricName() string { + switch status { + case CheckpointStatusLoaded: + return "pending" + case CheckpointStatusAllWritten: + return "written" + case CheckpointStatusClosed: + return "closed" + case CheckpointStatusImported: + return "imported" + case CheckpointStatusIndexImported: + return "index_imported" + case CheckpointStatusAlteredAutoInc: + return "altered_auto_inc" + case CheckpointStatusChecksummed, CheckpointStatusChecksumSkipped: + return "checksum" + case CheckpointStatusAnalyzed, CheckpointStatusAnalyzeSkipped: + return "analyzed" + case CheckpointStatusMissing: + return "missing" + default: + return "invalid" + } +} + +type ChunkCheckpointKey struct { + Path string + Offset int64 +} + +func (key *ChunkCheckpointKey) String() string { + return fmt.Sprintf("%s:%d", key.Path, key.Offset) +} + +func (key *ChunkCheckpointKey) less(other *ChunkCheckpointKey) bool { + switch { + case key.Path < other.Path: + return true + case key.Path > other.Path: + return false + default: + return key.Offset < other.Offset + } +} + +type ChunkCheckpoint struct { + Key ChunkCheckpointKey + FileMeta mydump.SourceFileMeta + ColumnPermutation []int + Chunk mydump.Chunk + Checksum verify.KVChecksum + Timestamp int64 +} + +func (ccp *ChunkCheckpoint) DeepCopy() *ChunkCheckpoint { + colPerm := make([]int, 0, len(ccp.ColumnPermutation)) + colPerm = append(colPerm, ccp.ColumnPermutation...) + return &ChunkCheckpoint{ + Key: ccp.Key, + FileMeta: ccp.FileMeta, + ColumnPermutation: colPerm, + Chunk: ccp.Chunk, + Checksum: ccp.Checksum, + Timestamp: ccp.Timestamp, + } +} + +type EngineCheckpoint struct { + Status CheckpointStatus + Chunks []*ChunkCheckpoint // a sorted array +} + +func (engine *EngineCheckpoint) DeepCopy() *EngineCheckpoint { + chunks := make([]*ChunkCheckpoint, 0, len(engine.Chunks)) + for _, chunk := range engine.Chunks { + chunks = append(chunks, chunk.DeepCopy()) + } + return &EngineCheckpoint{ + Status: engine.Status, + Chunks: chunks, + } +} + +type TableCheckpoint struct { + Status CheckpointStatus + AllocBase int64 + Engines map[int32]*EngineCheckpoint + TableID int64 +} + +func (cp *TableCheckpoint) DeepCopy() *TableCheckpoint { + engines := make(map[int32]*EngineCheckpoint, len(cp.Engines)) + for engineID, engine := range cp.Engines { + engines[engineID] = engine.DeepCopy() + } + return &TableCheckpoint{ + Status: cp.Status, + AllocBase: cp.AllocBase, + Engines: engines, + TableID: cp.TableID, + } +} + +func (cp *TableCheckpoint) CountChunks() int { + result := 0 + for _, engine := range cp.Engines { + result += len(engine.Chunks) + } + return result +} + +type chunkCheckpointDiff struct { + pos int64 + rowID int64 + checksum verify.KVChecksum + columnPermutation []int +} + +type engineCheckpointDiff struct { + hasStatus bool + status CheckpointStatus + chunks map[ChunkCheckpointKey]chunkCheckpointDiff +} + +type TableCheckpointDiff struct { + hasStatus bool + hasRebase bool + status CheckpointStatus + allocBase int64 + engines map[int32]engineCheckpointDiff +} + +func NewTableCheckpointDiff() *TableCheckpointDiff { + return &TableCheckpointDiff{ + engines: make(map[int32]engineCheckpointDiff), + } +} + +func (cpd *TableCheckpointDiff) insertEngineCheckpointDiff(engineID int32, newDiff engineCheckpointDiff) { + if oldDiff, ok := cpd.engines[engineID]; ok { + if newDiff.hasStatus { + oldDiff.hasStatus = true + oldDiff.status = newDiff.status + } + for key, chunkDiff := range newDiff.chunks { + oldDiff.chunks[key] = chunkDiff + } + newDiff = oldDiff + } + cpd.engines[engineID] = newDiff +} + +func (cpd *TableCheckpointDiff) String() string { + return fmt.Sprintf( + "{hasStatus:%v, hasRebase:%v, status:%d, allocBase:%d, engines:[%d]}", + cpd.hasStatus, cpd.hasRebase, cpd.status, cpd.allocBase, len(cpd.engines), + ) +} + +// Apply the diff to the existing chunk and engine checkpoints in `cp`. +func (cp *TableCheckpoint) Apply(cpd *TableCheckpointDiff) { + if cpd.hasStatus { + cp.Status = cpd.status + } + if cpd.hasRebase { + cp.AllocBase = cpd.allocBase + } + for engineID, engineDiff := range cpd.engines { + engine := cp.Engines[engineID] + if engine == nil { + continue + } + if engineDiff.hasStatus { + engine.Status = engineDiff.status + } + for key, diff := range engineDiff.chunks { + index := sort.Search(len(engine.Chunks), func(i int) bool { + return !engine.Chunks[i].Key.less(&key) + }) + if index >= len(engine.Chunks) { + continue + } + chunk := engine.Chunks[index] + if chunk.Key != key { + continue + } + chunk.Chunk.Offset = diff.pos + chunk.Chunk.PrevRowIDMax = diff.rowID + chunk.Checksum = diff.checksum + } + } +} + +type TableCheckpointMerger interface { + // MergeInto the table checkpoint diff from a status update or chunk update. + // If there are multiple updates to the same table, only the last one will + // take effect. Therefore, the caller must ensure events for the same table + // are properly ordered by the global time (an old event must be merged + // before the new one). + MergeInto(cpd *TableCheckpointDiff) +} + +type StatusCheckpointMerger struct { + EngineID int32 // WholeTableEngineID == apply to whole table. + Status CheckpointStatus +} + +func (merger *StatusCheckpointMerger) SetInvalid() { + merger.Status /= 10 +} + +func (merger *StatusCheckpointMerger) MergeInto(cpd *TableCheckpointDiff) { + if merger.EngineID == WholeTableEngineID || merger.Status <= CheckpointStatusMaxInvalid { + cpd.status = merger.Status + cpd.hasStatus = true + } + if merger.EngineID != WholeTableEngineID { + cpd.insertEngineCheckpointDiff(merger.EngineID, engineCheckpointDiff{ + hasStatus: true, + status: merger.Status, + chunks: make(map[ChunkCheckpointKey]chunkCheckpointDiff), + }) + } +} + +type ChunkCheckpointMerger struct { + EngineID int32 + Key ChunkCheckpointKey + Checksum verify.KVChecksum + Pos int64 + RowID int64 + ColumnPermutation []int +} + +func (merger *ChunkCheckpointMerger) MergeInto(cpd *TableCheckpointDiff) { + cpd.insertEngineCheckpointDiff(merger.EngineID, engineCheckpointDiff{ + chunks: map[ChunkCheckpointKey]chunkCheckpointDiff{ + merger.Key: { + pos: merger.Pos, + rowID: merger.RowID, + checksum: merger.Checksum, + columnPermutation: merger.ColumnPermutation, + }, + }, + }) +} + +type RebaseCheckpointMerger struct { + AllocBase int64 +} + +func (merger *RebaseCheckpointMerger) MergeInto(cpd *TableCheckpointDiff) { + cpd.hasRebase = true + cpd.allocBase = mathutil.MaxInt64(cpd.allocBase, merger.AllocBase) +} + +type DestroyedTableCheckpoint struct { + TableName string + MinEngineID int32 + MaxEngineID int32 +} + +type TaskCheckpoint struct { + TaskId int64 + SourceDir string + Backend string + ImporterAddr string + TiDBHost string + TiDBPort int + PdAddr string + SortedKVDir string + LightningVer string +} + +type CheckpointsDB interface { + Initialize(ctx context.Context, cfg *config.Config, dbInfo map[string]*TidbDBInfo) error + TaskCheckpoint(ctx context.Context) (*TaskCheckpoint, error) + Get(ctx context.Context, tableName string) (*TableCheckpoint, error) + Close() error + // InsertEngineCheckpoints initializes the checkpoints related to a table. + // It assumes the entire table has not been imported before and will fill in + // default values for the column permutations and checksums. + InsertEngineCheckpoints(ctx context.Context, tableName string, checkpoints map[int32]*EngineCheckpoint) error + Update(checkpointDiffs map[string]*TableCheckpointDiff) + + RemoveCheckpoint(ctx context.Context, tableName string) error + // MoveCheckpoints renames the checkpoint schema to include a suffix + // including the taskID (e.g. `tidb_lightning_checkpoints.1234567890.bak`). + MoveCheckpoints(ctx context.Context, taskID int64) error + // GetLocalStoringTables returns a map containing tables have engine files stored on local disk. + // currently only meaningful for local backend + GetLocalStoringTables(ctx context.Context) (map[string][]int32, error) + IgnoreErrorCheckpoint(ctx context.Context, tableName string) error + DestroyErrorCheckpoint(ctx context.Context, tableName string) ([]DestroyedTableCheckpoint, error) + DumpTables(ctx context.Context, csv io.Writer) error + DumpEngines(ctx context.Context, csv io.Writer) error + DumpChunks(ctx context.Context, csv io.Writer) error +} + +func OpenCheckpointsDB(ctx context.Context, cfg *config.Config) (CheckpointsDB, error) { + if !cfg.Checkpoint.Enable { + return NewNullCheckpointsDB(), nil + } + + switch cfg.Checkpoint.Driver { + case config.CheckpointDriverMySQL: + db, err := sql.Open("mysql", cfg.Checkpoint.DSN) + if err != nil { + return nil, errors.Trace(err) + } + cpdb, err := NewMySQLCheckpointsDB(ctx, db, cfg.Checkpoint.Schema) + if err != nil { + db.Close() + return nil, errors.Trace(err) + } + return cpdb, nil + + case config.CheckpointDriverFile: + return NewFileCheckpointsDB(cfg.Checkpoint.DSN), nil + + default: + return nil, errors.Errorf("Unknown checkpoint driver %s", cfg.Checkpoint.Driver) + } +} + +func IsCheckpointsDBExists(ctx context.Context, cfg *config.Config) (bool, error) { + if !cfg.Checkpoint.Enable { + return false, nil + } + switch cfg.Checkpoint.Driver { + case config.CheckpointDriverMySQL: + db, err := sql.Open("mysql", cfg.Checkpoint.DSN) + if err != nil { + return false, errors.Trace(err) + } + defer db.Close() + checkSQL := "SHOW DATABASES WHERE `DATABASE` = ?" + rows, err := db.QueryContext(ctx, checkSQL, cfg.Checkpoint.Schema) + if err != nil { + return false, errors.Trace(err) + } + defer rows.Close() + return rows.Next(), nil + + case config.CheckpointDriverFile: + _, err := os.Stat(cfg.Checkpoint.DSN) + if err == nil { + return true, err + } else if os.IsNotExist(err) { + return false, nil + } + return false, errors.Trace(err) + + default: + return false, errors.Errorf("Unknown checkpoint driver %s", cfg.Checkpoint.Driver) + } +} + +// NullCheckpointsDB is a checkpoints database with no checkpoints. +type NullCheckpointsDB struct{} + +func NewNullCheckpointsDB() *NullCheckpointsDB { + return &NullCheckpointsDB{} +} + +func (*NullCheckpointsDB) Initialize(context.Context, *config.Config, map[string]*TidbDBInfo) error { + return nil +} + +func (*NullCheckpointsDB) TaskCheckpoint(ctx context.Context) (*TaskCheckpoint, error) { + return nil, nil +} + +func (*NullCheckpointsDB) Close() error { + return nil +} + +func (*NullCheckpointsDB) Get(_ context.Context, _ string) (*TableCheckpoint, error) { + return &TableCheckpoint{ + Status: CheckpointStatusLoaded, + Engines: map[int32]*EngineCheckpoint{}, + }, nil +} + +func (*NullCheckpointsDB) InsertEngineCheckpoints(_ context.Context, _ string, _ map[int32]*EngineCheckpoint) error { + return nil +} + +func (*NullCheckpointsDB) Update(map[string]*TableCheckpointDiff) {} + +type MySQLCheckpointsDB struct { + db *sql.DB + schema string +} + +func NewMySQLCheckpointsDB(ctx context.Context, db *sql.DB, schemaName string) (*MySQLCheckpointsDB, error) { + var escapedSchemaName strings.Builder + common.WriteMySQLIdentifier(&escapedSchemaName, schemaName) + schema := escapedSchemaName.String() + + sql := common.SQLWithRetry{ + DB: db, + Logger: log.With(zap.String("schema", schemaName)), + HideQueryLog: true, + } + err := sql.Exec(ctx, "create checkpoints database", fmt.Sprintf(CreateDBTemplate, schema)) + if err != nil { + return nil, errors.Trace(err) + } + + err = sql.Exec(ctx, "create task checkpoints table", fmt.Sprintf(CreateTaskTableTemplate, schema, CheckpointTableNameTask)) + if err != nil { + return nil, errors.Trace(err) + } + + err = sql.Exec(ctx, "create table checkpoints table", fmt.Sprintf(CreateTableTableTemplate, schema, CheckpointTableNameTable)) + if err != nil { + return nil, errors.Trace(err) + } + + err = sql.Exec(ctx, "create engine checkpoints table", fmt.Sprintf(CreateEngineTableTemplate, schema, CheckpointTableNameEngine)) + if err != nil { + return nil, errors.Trace(err) + } + + err = sql.Exec(ctx, "create chunks checkpoints table", fmt.Sprintf(CreateChunkTableTemplate, schema, CheckpointTableNameChunk)) + if err != nil { + return nil, errors.Trace(err) + } + + return &MySQLCheckpointsDB{ + db: db, + schema: schema, + }, nil +} + +func (cpdb *MySQLCheckpointsDB) Initialize(ctx context.Context, cfg *config.Config, dbInfo map[string]*TidbDBInfo) error { + // We can have at most 65535 placeholders https://stackoverflow.com/q/4922345/ + // Since this step is not performance critical, we just insert the rows one-by-one. + s := common.SQLWithRetry{DB: cpdb.db, Logger: log.L()} + err := s.Transact(ctx, "insert checkpoints", func(c context.Context, tx *sql.Tx) error { + taskStmt, err := tx.PrepareContext(c, fmt.Sprintf(InitTaskTemplate, cpdb.schema, CheckpointTableNameTask)) + if err != nil { + return errors.Trace(err) + } + defer taskStmt.Close() + _, err = taskStmt.ExecContext(ctx, cfg.TaskID, cfg.Mydumper.SourceDir, cfg.TikvImporter.Backend, + cfg.TikvImporter.Addr, cfg.TiDB.Host, cfg.TiDB.Port, cfg.TiDB.PdAddr, cfg.TikvImporter.SortedKVDir, + common.ReleaseVersion) + if err != nil { + return errors.Trace(err) + } + + // If `hash` is not the same but the `table_name` duplicates, + // the CASE expression will return NULL, which can be used to violate + // the NOT NULL requirement of `task_id` column, and caused this INSERT + // statement to fail with an irrecoverable error. + // We do need to capture the error is display a user friendly message + // (multiple nodes cannot import the same table) though. + stmt, err := tx.PrepareContext(c, fmt.Sprintf(InitTableTemplate, cpdb.schema, CheckpointTableNameTable)) + if err != nil { + return errors.Trace(err) + } + defer stmt.Close() + + for _, db := range dbInfo { + for _, table := range db.Tables { + tableName := common.UniqueTable(db.Name, table.Name) + _, err = stmt.ExecContext(c, cfg.TaskID, tableName, 0, table.ID) + if err != nil { + return errors.Trace(err) + } + } + } + + return nil + }) + if err != nil { + return errors.Trace(err) + } + + return nil +} + +func (cpdb *MySQLCheckpointsDB) TaskCheckpoint(ctx context.Context) (*TaskCheckpoint, error) { + s := common.SQLWithRetry{ + DB: cpdb.db, + Logger: log.L(), + } + + taskQuery := fmt.Sprintf(ReadTaskTemplate, cpdb.schema, CheckpointTableNameTask) + taskCp := &TaskCheckpoint{} + err := s.QueryRow(ctx, "fetch task checkpoint", taskQuery, &taskCp.TaskId, &taskCp.SourceDir, &taskCp.Backend, + &taskCp.ImporterAddr, &taskCp.TiDBHost, &taskCp.TiDBPort, &taskCp.PdAddr, &taskCp.SortedKVDir, &taskCp.LightningVer) + if err != nil { + // if task checkpoint is empty, return nil + if errors.Cause(err) == sql.ErrNoRows { + return nil, nil + } + return nil, errors.Trace(err) + } + + return taskCp, nil +} + +func (cpdb *MySQLCheckpointsDB) Close() error { + return errors.Trace(cpdb.db.Close()) +} + +func (cpdb *MySQLCheckpointsDB) Get(ctx context.Context, tableName string) (*TableCheckpoint, error) { + cp := &TableCheckpoint{ + Engines: map[int32]*EngineCheckpoint{}, + } + + s := common.SQLWithRetry{ + DB: cpdb.db, + Logger: log.With(zap.String("table", tableName)), + } + err := s.Transact(ctx, "read checkpoint", func(c context.Context, tx *sql.Tx) error { + // 1. Populate the engines. + + engineQuery := fmt.Sprintf(ReadEngineTemplate, cpdb.schema, CheckpointTableNameEngine) + engineRows, err := tx.QueryContext(c, engineQuery, tableName) + if err != nil { + return errors.Trace(err) + } + defer engineRows.Close() + for engineRows.Next() { + var ( + engineID int32 + status uint8 + ) + if err := engineRows.Scan(&engineID, &status); err != nil { + return errors.Trace(err) + } + cp.Engines[engineID] = &EngineCheckpoint{ + Status: CheckpointStatus(status), + } + } + if err := engineRows.Err(); err != nil { + return errors.Trace(err) + } + + // 2. Populate the chunks. + + chunkQuery := fmt.Sprintf(ReadChunkTemplate, cpdb.schema, CheckpointTableNameChunk) + chunkRows, err := tx.QueryContext(c, chunkQuery, tableName) + if err != nil { + return errors.Trace(err) + } + defer chunkRows.Close() + for chunkRows.Next() { + var ( + value = &ChunkCheckpoint{} + colPerm []byte + engineID int32 + kvcBytes uint64 + kvcKVs uint64 + kvcChecksum uint64 + ) + if err := chunkRows.Scan( + &engineID, &value.Key.Path, &value.Key.Offset, &value.FileMeta.Type, &value.FileMeta.Compression, + &value.FileMeta.SortKey, &value.FileMeta.FileSize, &colPerm, &value.Chunk.Offset, &value.Chunk.EndOffset, + &value.Chunk.PrevRowIDMax, &value.Chunk.RowIDMax, &kvcBytes, &kvcKVs, &kvcChecksum, + &value.Timestamp, + ); err != nil { + return errors.Trace(err) + } + value.FileMeta.Path = value.Key.Path + value.Checksum = verify.MakeKVChecksum(kvcBytes, kvcKVs, kvcChecksum) + if err := json.Unmarshal(colPerm, &value.ColumnPermutation); err != nil { + return errors.Trace(err) + } + cp.Engines[engineID].Chunks = append(cp.Engines[engineID].Chunks, value) + } + if err := chunkRows.Err(); err != nil { + return errors.Trace(err) + } + + // 3. Fill in the remaining table info + + tableQuery := fmt.Sprintf(ReadTableRemainTemplate, cpdb.schema, CheckpointTableNameTable) + tableRow := tx.QueryRowContext(c, tableQuery, tableName) + + var status uint8 + if err := tableRow.Scan(&status, &cp.AllocBase, &cp.TableID); err != nil { + return errors.Trace(err) + } + cp.Status = CheckpointStatus(status) + return nil + }) + if err != nil { + return nil, errors.Trace(err) + } + + return cp, nil +} + +func (cpdb *MySQLCheckpointsDB) InsertEngineCheckpoints(ctx context.Context, tableName string, checkpoints map[int32]*EngineCheckpoint) error { + s := common.SQLWithRetry{ + DB: cpdb.db, + Logger: log.With(zap.String("table", tableName)), + } + err := s.Transact(ctx, "update engine checkpoints", func(c context.Context, tx *sql.Tx) error { + engineStmt, err := tx.PrepareContext(c, fmt.Sprintf(ReplaceEngineTemplate, cpdb.schema, CheckpointTableNameEngine)) + if err != nil { + return errors.Trace(err) + } + defer engineStmt.Close() + + chunkStmt, err := tx.PrepareContext(c, fmt.Sprintf(ReplaceChunkTemplate, cpdb.schema, CheckpointTableNameChunk)) + if err != nil { + return errors.Trace(err) + } + defer chunkStmt.Close() + + for engineID, engine := range checkpoints { + _, err = engineStmt.ExecContext(c, tableName, engineID, engine.Status) + if err != nil { + return errors.Trace(err) + } + for _, value := range engine.Chunks { + columnPerm, err := json.Marshal(value.ColumnPermutation) + if err != nil { + return errors.Trace(err) + } + _, err = chunkStmt.ExecContext( + c, tableName, engineID, + value.Key.Path, value.Key.Offset, value.FileMeta.Type, value.FileMeta.Compression, + value.FileMeta.SortKey, value.FileMeta.FileSize, columnPerm, value.Chunk.Offset, value.Chunk.EndOffset, + value.Chunk.PrevRowIDMax, value.Chunk.RowIDMax, value.Timestamp, + ) + if err != nil { + return errors.Trace(err) + } + } + } + + return nil + }) + if err != nil { + return errors.Trace(err) + } + + return nil +} + +func (cpdb *MySQLCheckpointsDB) Update(checkpointDiffs map[string]*TableCheckpointDiff) { + chunkQuery := fmt.Sprintf(UpdateChunkTemplate, cpdb.schema, CheckpointTableNameChunk) + rebaseQuery := fmt.Sprintf(UpdateTableRebaseTemplate, cpdb.schema, CheckpointTableNameTable) + tableStatusQuery := fmt.Sprintf(UpdateTableStatusTemplate, cpdb.schema, CheckpointTableNameTable) + engineStatusQuery := fmt.Sprintf(UpdateEngineTemplate, cpdb.schema, CheckpointTableNameEngine) + + s := common.SQLWithRetry{DB: cpdb.db, Logger: log.L()} + err := s.Transact(context.Background(), "update checkpoints", func(c context.Context, tx *sql.Tx) error { + chunkStmt, e := tx.PrepareContext(c, chunkQuery) + if e != nil { + return errors.Trace(e) + } + defer chunkStmt.Close() + rebaseStmt, e := tx.PrepareContext(c, rebaseQuery) + if e != nil { + return errors.Trace(e) + } + defer rebaseStmt.Close() + tableStatusStmt, e := tx.PrepareContext(c, tableStatusQuery) + if e != nil { + return errors.Trace(e) + } + defer tableStatusStmt.Close() + engineStatusStmt, e := tx.PrepareContext(c, engineStatusQuery) + if e != nil { + return errors.Trace(e) + } + defer engineStatusStmt.Close() + + for tableName, cpd := range checkpointDiffs { + if cpd.hasStatus { + if _, e := tableStatusStmt.ExecContext(c, cpd.status, tableName); e != nil { + return errors.Trace(e) + } + } + if cpd.hasRebase { + if _, e := rebaseStmt.ExecContext(c, cpd.allocBase, tableName); e != nil { + return errors.Trace(e) + } + } + for engineID, engineDiff := range cpd.engines { + if engineDiff.hasStatus { + if _, e := engineStatusStmt.ExecContext(c, engineDiff.status, tableName, engineID); e != nil { + return errors.Trace(e) + } + } + for key, diff := range engineDiff.chunks { + columnPerm, err := json.Marshal(diff.columnPermutation) + if err != nil { + return errors.Trace(err) + } + if _, e := chunkStmt.ExecContext( + c, + diff.pos, diff.rowID, diff.checksum.SumSize(), diff.checksum.SumKVS(), diff.checksum.Sum(), + columnPerm, tableName, engineID, key.Path, key.Offset, + ); e != nil { + return errors.Trace(e) + } + } + } + } + + return nil + }) + if err != nil { + log.L().Error("save checkpoint failed", zap.Error(err)) + } +} + +type FileCheckpointsDB struct { + lock sync.Mutex // we need to ensure only a thread can access to `checkpoints` at a time + checkpoints CheckpointsModel + path string +} + +func NewFileCheckpointsDB(path string) *FileCheckpointsDB { + cpdb := &FileCheckpointsDB{ + path: path, + checkpoints: CheckpointsModel{ + TaskCheckpoint: &TaskCheckpointModel{}, + Checkpoints: map[string]*TableCheckpointModel{}, + }, + } + // ignore all errors -- file maybe not created yet (and it is fine). + content, err := ioutil.ReadFile(path) + if err == nil { + err2 := cpdb.checkpoints.Unmarshal(content) + if err2 != nil { + log.L().Error("checkpoint file is broken", zap.String("path", path), zap.Error(err2)) + } + // FIXME: patch for empty map may need initialize manually, because currently + // FIXME: a map of zero size -> marshall -> unmarshall -> become nil, see checkpoint_test.go + if cpdb.checkpoints.Checkpoints == nil { + cpdb.checkpoints.Checkpoints = map[string]*TableCheckpointModel{} + } + for _, table := range cpdb.checkpoints.Checkpoints { + if table.Engines == nil { + table.Engines = map[int32]*EngineCheckpointModel{} + } + for _, engine := range table.Engines { + if engine.Chunks == nil { + engine.Chunks = map[string]*ChunkCheckpointModel{} + } + } + } + } else { + log.L().Info("open checkpoint file failed, going to create a new one", + zap.String("path", path), + log.ShortError(err), + ) + } + return cpdb +} + +func (cpdb *FileCheckpointsDB) save() error { + serialized, err := cpdb.checkpoints.Marshal() + if err != nil { + return errors.Trace(err) + } + if err := ioutil.WriteFile(cpdb.path, serialized, 0o644); err != nil { + return errors.Trace(err) + } + return nil +} + +func (cpdb *FileCheckpointsDB) Initialize(ctx context.Context, cfg *config.Config, dbInfo map[string]*TidbDBInfo) error { + cpdb.lock.Lock() + defer cpdb.lock.Unlock() + + cpdb.checkpoints.TaskCheckpoint = &TaskCheckpointModel{ + TaskId: cfg.TaskID, + SourceDir: cfg.Mydumper.SourceDir, + Backend: cfg.TikvImporter.Backend, + ImporterAddr: cfg.TikvImporter.Addr, + TidbHost: cfg.TiDB.Host, + TidbPort: int32(cfg.TiDB.Port), + PdAddr: cfg.TiDB.PdAddr, + SortedKvDir: cfg.TikvImporter.SortedKVDir, + LightningVer: common.ReleaseVersion, + } + + if cpdb.checkpoints.Checkpoints == nil { + cpdb.checkpoints.Checkpoints = make(map[string]*TableCheckpointModel) + } + + for _, db := range dbInfo { + for _, table := range db.Tables { + tableName := common.UniqueTable(db.Name, table.Name) + if _, ok := cpdb.checkpoints.Checkpoints[tableName]; !ok { + cpdb.checkpoints.Checkpoints[tableName] = &TableCheckpointModel{ + Status: uint32(CheckpointStatusLoaded), + Engines: map[int32]*EngineCheckpointModel{}, + TableID: table.ID, + } + } + // TODO check if hash matches + } + } + + return errors.Trace(cpdb.save()) +} + +func (cpdb *FileCheckpointsDB) TaskCheckpoint(_ context.Context) (*TaskCheckpoint, error) { + // this method is always called in lock + cp := cpdb.checkpoints.TaskCheckpoint + if cp == nil || cp.TaskId == 0 { + return nil, nil + } + + return &TaskCheckpoint{ + TaskId: cp.TaskId, + SourceDir: cp.SourceDir, + Backend: cp.Backend, + ImporterAddr: cp.ImporterAddr, + TiDBHost: cp.TidbHost, + TiDBPort: int(cp.TidbPort), + PdAddr: cp.PdAddr, + SortedKVDir: cp.SortedKvDir, + LightningVer: cp.LightningVer, + }, nil +} + +func (cpdb *FileCheckpointsDB) Close() error { + cpdb.lock.Lock() + defer cpdb.lock.Unlock() + + return errors.Trace(cpdb.save()) +} + +func (cpdb *FileCheckpointsDB) Get(_ context.Context, tableName string) (*TableCheckpoint, error) { + cpdb.lock.Lock() + defer cpdb.lock.Unlock() + + tableModel, ok := cpdb.checkpoints.Checkpoints[tableName] + if !ok { + tableModel = &TableCheckpointModel{} + } + + cp := &TableCheckpoint{ + Status: CheckpointStatus(tableModel.Status), + AllocBase: tableModel.AllocBase, + Engines: make(map[int32]*EngineCheckpoint, len(tableModel.Engines)), + TableID: tableModel.TableID, + } + + for engineID, engineModel := range tableModel.Engines { + engine := &EngineCheckpoint{ + Status: CheckpointStatus(engineModel.Status), + Chunks: make([]*ChunkCheckpoint, 0, len(engineModel.Chunks)), + } + + for _, chunkModel := range engineModel.Chunks { + colPerm := make([]int, 0, len(chunkModel.ColumnPermutation)) + for _, c := range chunkModel.ColumnPermutation { + colPerm = append(colPerm, int(c)) + } + engine.Chunks = append(engine.Chunks, &ChunkCheckpoint{ + Key: ChunkCheckpointKey{ + Path: chunkModel.Path, + Offset: chunkModel.Offset, + }, + FileMeta: mydump.SourceFileMeta{ + Path: chunkModel.Path, + Type: mydump.SourceType(chunkModel.Type), + Compression: mydump.Compression(chunkModel.Compression), + SortKey: chunkModel.SortKey, + FileSize: chunkModel.FileSize, + }, + ColumnPermutation: colPerm, + Chunk: mydump.Chunk{ + Offset: chunkModel.Pos, + EndOffset: chunkModel.EndOffset, + PrevRowIDMax: chunkModel.PrevRowidMax, + RowIDMax: chunkModel.RowidMax, + }, + Checksum: verify.MakeKVChecksum(chunkModel.KvcBytes, chunkModel.KvcKvs, chunkModel.KvcChecksum), + Timestamp: chunkModel.Timestamp, + }) + } + + sort.Slice(engine.Chunks, func(i, j int) bool { + return engine.Chunks[i].Key.less(&engine.Chunks[j].Key) + }) + + cp.Engines[engineID] = engine + } + + return cp, nil +} + +func (cpdb *FileCheckpointsDB) InsertEngineCheckpoints(_ context.Context, tableName string, checkpoints map[int32]*EngineCheckpoint) error { + cpdb.lock.Lock() + defer cpdb.lock.Unlock() + + tableModel := cpdb.checkpoints.Checkpoints[tableName] + for engineID, engine := range checkpoints { + engineModel := &EngineCheckpointModel{ + Status: uint32(CheckpointStatusLoaded), + Chunks: make(map[string]*ChunkCheckpointModel), + } + for _, value := range engine.Chunks { + key := value.Key.String() + chunk, ok := engineModel.Chunks[key] + if !ok { + chunk = &ChunkCheckpointModel{ + Path: value.Key.Path, + Offset: value.Key.Offset, + } + engineModel.Chunks[key] = chunk + } + chunk.Type = int32(value.FileMeta.Type) + chunk.Compression = int32(value.FileMeta.Compression) + chunk.SortKey = value.FileMeta.SortKey + chunk.FileSize = value.FileMeta.FileSize + chunk.Pos = value.Chunk.Offset + chunk.EndOffset = value.Chunk.EndOffset + chunk.PrevRowidMax = value.Chunk.PrevRowIDMax + chunk.RowidMax = value.Chunk.RowIDMax + chunk.Timestamp = value.Timestamp + if len(value.ColumnPermutation) > 0 { + chunk.ColumnPermutation = intSlice2Int32Slice(value.ColumnPermutation) + } + + } + tableModel.Engines[engineID] = engineModel + } + + return errors.Trace(cpdb.save()) +} + +func (cpdb *FileCheckpointsDB) Update(checkpointDiffs map[string]*TableCheckpointDiff) { + cpdb.lock.Lock() + defer cpdb.lock.Unlock() + + for tableName, cpd := range checkpointDiffs { + tableModel := cpdb.checkpoints.Checkpoints[tableName] + if cpd.hasStatus { + tableModel.Status = uint32(cpd.status) + } + if cpd.hasRebase { + tableModel.AllocBase = cpd.allocBase + } + for engineID, engineDiff := range cpd.engines { + engineModel := tableModel.Engines[engineID] + if engineDiff.hasStatus { + engineModel.Status = uint32(engineDiff.status) + } + + for key, diff := range engineDiff.chunks { + chunkModel := engineModel.Chunks[key.String()] + chunkModel.Pos = diff.pos + chunkModel.PrevRowidMax = diff.rowID + chunkModel.KvcBytes = diff.checksum.SumSize() + chunkModel.KvcKvs = diff.checksum.SumKVS() + chunkModel.KvcChecksum = diff.checksum.Sum() + chunkModel.ColumnPermutation = intSlice2Int32Slice(diff.columnPermutation) + } + } + } + + if err := cpdb.save(); err != nil { + log.L().Error("save checkpoint failed", zap.Error(err)) + } +} + +// Management functions ---------------------------------------------------------------------------- + +var cannotManageNullDB = errors.New("cannot perform this function while checkpoints is disabled") + +func (*NullCheckpointsDB) RemoveCheckpoint(context.Context, string) error { + return errors.Trace(cannotManageNullDB) +} + +func (*NullCheckpointsDB) MoveCheckpoints(context.Context, int64) error { + return errors.Trace(cannotManageNullDB) +} + +func (*NullCheckpointsDB) GetLocalStoringTables(context.Context) (map[string][]int32, error) { + return nil, nil +} + +func (*NullCheckpointsDB) IgnoreErrorCheckpoint(context.Context, string) error { + return errors.Trace(cannotManageNullDB) +} + +func (*NullCheckpointsDB) DestroyErrorCheckpoint(context.Context, string) ([]DestroyedTableCheckpoint, error) { + return nil, errors.Trace(cannotManageNullDB) +} + +func (*NullCheckpointsDB) DumpTables(context.Context, io.Writer) error { + return errors.Trace(cannotManageNullDB) +} + +func (*NullCheckpointsDB) DumpEngines(context.Context, io.Writer) error { + return errors.Trace(cannotManageNullDB) +} + +func (*NullCheckpointsDB) DumpChunks(context.Context, io.Writer) error { + return errors.Trace(cannotManageNullDB) +} + +func (cpdb *MySQLCheckpointsDB) RemoveCheckpoint(ctx context.Context, tableName string) error { + s := common.SQLWithRetry{ + DB: cpdb.db, + Logger: log.With(zap.String("table", tableName)), + } + + if tableName == "all" { + return s.Exec(ctx, "remove all checkpoints", "DROP SCHEMA "+cpdb.schema) + } + + deleteChunkQuery := fmt.Sprintf(DeleteCheckpointRecordTemplate, cpdb.schema, CheckpointTableNameChunk) + deleteEngineQuery := fmt.Sprintf(DeleteCheckpointRecordTemplate, cpdb.schema, CheckpointTableNameEngine) + deleteTableQuery := fmt.Sprintf(DeleteCheckpointRecordTemplate, cpdb.schema, CheckpointTableNameTable) + + return s.Transact(ctx, "remove checkpoints", func(c context.Context, tx *sql.Tx) error { + if _, e := tx.ExecContext(c, deleteChunkQuery, tableName); e != nil { + return errors.Trace(e) + } + if _, e := tx.ExecContext(c, deleteEngineQuery, tableName); e != nil { + return errors.Trace(e) + } + if _, e := tx.ExecContext(c, deleteTableQuery, tableName); e != nil { + return errors.Trace(e) + } + return nil + }) +} + +func (cpdb *MySQLCheckpointsDB) MoveCheckpoints(ctx context.Context, taskID int64) error { + // The "cpdb.schema" is an escaped schema name of the form "`foo`". + // We use "x[1:len(x)-1]" instead of unescaping it to keep the + // double-backquotes (if any) intact. + newSchema := fmt.Sprintf("`%s.%d.bak`", cpdb.schema[1:len(cpdb.schema)-1], taskID) + s := common.SQLWithRetry{ + DB: cpdb.db, + Logger: log.With(zap.Int64("taskID", taskID)), + } + + createSchemaQuery := "CREATE SCHEMA IF NOT EXISTS " + newSchema + if e := s.Exec(ctx, "create backup checkpoints schema", createSchemaQuery); e != nil { + return e + } + for _, tbl := range []string{ + CheckpointTableNameChunk, CheckpointTableNameEngine, + CheckpointTableNameTable, CheckpointTableNameTask, + } { + query := fmt.Sprintf("RENAME TABLE %[1]s.%[3]s TO %[2]s.%[3]s", cpdb.schema, newSchema, tbl) + if e := s.Exec(ctx, fmt.Sprintf("move %s checkpoints table", tbl), query); e != nil { + return e + } + } + + return nil +} + +func (cpdb *MySQLCheckpointsDB) GetLocalStoringTables(ctx context.Context) (map[string][]int32, error) { + var targetTables map[string][]int32 + + // lightning didn't check CheckpointStatusMaxInvalid before this function is called, so we skip invalid ones + // engines should exist if + // 1. table status is earlier than CheckpointStatusIndexImported, and + // 2. engine status is earlier than CheckpointStatusImported, and + // 3. chunk has been read + query := fmt.Sprintf(` + SELECT DISTINCT t.table_name, c.engine_id + FROM %s.%s t, %s.%s c, %s.%s e + WHERE t.table_name = c.table_name AND t.table_name = e.table_name AND c.engine_id = e.engine_id + AND %d < t.status AND t.status < %d + AND %d < e.status AND e.status < %d + AND c.pos > c.offset;`, + cpdb.schema, CheckpointTableNameTable, cpdb.schema, CheckpointTableNameChunk, cpdb.schema, CheckpointTableNameEngine, + CheckpointStatusMaxInvalid, CheckpointStatusIndexImported, + CheckpointStatusMaxInvalid, CheckpointStatusImported) + + err := common.Retry("get local storing tables", log.L(), func() error { + targetTables = make(map[string][]int32) + rows, err := cpdb.db.QueryContext(ctx, query) + if err != nil { + return errors.Trace(err) + } + defer rows.Close() + for rows.Next() { + var ( + tableName string + engineID int32 + ) + if err := rows.Scan(&tableName, &engineID); err != nil { + return errors.Trace(err) + } + targetTables[tableName] = append(targetTables[tableName], engineID) + } + if err := rows.Err(); err != nil { + return errors.Trace(err) + } + return nil + }) + if err != nil { + return nil, errors.Trace(err) + } + + return targetTables, err +} + +func (cpdb *MySQLCheckpointsDB) IgnoreErrorCheckpoint(ctx context.Context, tableName string) error { + var colName string + if tableName == "all" { + // This will expand to `WHERE 'all' = 'all'` and effectively allowing + // all tables to be included. + colName = "'all'" + } else { + colName = "table_name" + } + + engineQuery := fmt.Sprintf(` + UPDATE %s.%s SET status = %d WHERE %s = ? AND status <= %d; + `, cpdb.schema, CheckpointTableNameEngine, CheckpointStatusLoaded, colName, CheckpointStatusMaxInvalid) + tableQuery := fmt.Sprintf(` + UPDATE %s.%s SET status = %d WHERE %s = ? AND status <= %d; + `, cpdb.schema, CheckpointTableNameTable, CheckpointStatusLoaded, colName, CheckpointStatusMaxInvalid) + + s := common.SQLWithRetry{ + DB: cpdb.db, + Logger: log.With(zap.String("table", tableName)), + } + err := s.Transact(ctx, "ignore error checkpoints", func(c context.Context, tx *sql.Tx) error { + if _, e := tx.ExecContext(c, engineQuery, tableName); e != nil { + return errors.Trace(e) + } + if _, e := tx.ExecContext(c, tableQuery, tableName); e != nil { + return errors.Trace(e) + } + return nil + }) + return errors.Trace(err) +} + +func (cpdb *MySQLCheckpointsDB) DestroyErrorCheckpoint(ctx context.Context, tableName string) ([]DestroyedTableCheckpoint, error) { + var colName, aliasedColName string + + if tableName == "all" { + // These will expand to `WHERE 'all' = 'all'` and effectively allowing + // all tables to be included. + colName = "'all'" + aliasedColName = "'all'" + } else { + colName = "table_name" + aliasedColName = "t.table_name" + } + + selectQuery := fmt.Sprintf(` + SELECT + t.table_name, + COALESCE(MIN(e.engine_id), 0), + COALESCE(MAX(e.engine_id), -1) + FROM %[1]s.%[4]s t + LEFT JOIN %[1]s.%[5]s e ON t.table_name = e.table_name + WHERE %[2]s = ? AND t.status <= %[3]d + GROUP BY t.table_name; + `, cpdb.schema, aliasedColName, CheckpointStatusMaxInvalid, CheckpointTableNameTable, CheckpointTableNameEngine) + deleteChunkQuery := fmt.Sprintf(` + DELETE FROM %[1]s.%[4]s WHERE table_name IN (SELECT table_name FROM %[1]s.%[5]s WHERE %[2]s = ? AND status <= %[3]d) + `, cpdb.schema, colName, CheckpointStatusMaxInvalid, CheckpointTableNameChunk, CheckpointTableNameTable) + deleteEngineQuery := fmt.Sprintf(` + DELETE FROM %[1]s.%[4]s WHERE table_name IN (SELECT table_name FROM %[1]s.%[5]s WHERE %[2]s = ? AND status <= %[3]d) + `, cpdb.schema, colName, CheckpointStatusMaxInvalid, CheckpointTableNameEngine, CheckpointTableNameTable) + deleteTableQuery := fmt.Sprintf(` + DELETE FROM %s.%s WHERE %s = ? AND status <= %d + `, cpdb.schema, CheckpointTableNameTable, colName, CheckpointStatusMaxInvalid) + + var targetTables []DestroyedTableCheckpoint + + s := common.SQLWithRetry{ + DB: cpdb.db, + Logger: log.With(zap.String("table", tableName)), + } + err := s.Transact(ctx, "destroy error checkpoints", func(c context.Context, tx *sql.Tx) error { + // Obtain the list of tables + targetTables = nil + rows, e := tx.QueryContext(c, selectQuery, tableName) + if e != nil { + return errors.Trace(e) + } + defer rows.Close() + for rows.Next() { + var dtc DestroyedTableCheckpoint + if e := rows.Scan(&dtc.TableName, &dtc.MinEngineID, &dtc.MaxEngineID); e != nil { + return errors.Trace(e) + } + targetTables = append(targetTables, dtc) + } + if e := rows.Err(); e != nil { + return errors.Trace(e) + } + + // Delete the checkpoints + if _, e := tx.ExecContext(c, deleteChunkQuery, tableName); e != nil { + return errors.Trace(e) + } + if _, e := tx.ExecContext(c, deleteEngineQuery, tableName); e != nil { + return errors.Trace(e) + } + if _, e := tx.ExecContext(c, deleteTableQuery, tableName); e != nil { + return errors.Trace(e) + } + return nil + }) + if err != nil { + return nil, errors.Trace(err) + } + + return targetTables, nil +} + +func (cpdb *MySQLCheckpointsDB) DumpTables(ctx context.Context, writer io.Writer) error { + rows, err := cpdb.db.QueryContext(ctx, fmt.Sprintf(` + SELECT + task_id, + table_name, + hex(hash) AS hash, + status, + alloc_base, + create_time, + update_time + FROM %s.%s; + `, cpdb.schema, CheckpointTableNameTable)) + if err != nil { + return errors.Trace(err) + } + defer rows.Close() + + return errors.Trace(sqltocsv.Write(writer, rows)) +} + +func (cpdb *MySQLCheckpointsDB) DumpEngines(ctx context.Context, writer io.Writer) error { + rows, err := cpdb.db.QueryContext(ctx, fmt.Sprintf(` + SELECT + table_name, + engine_id, + status, + create_time, + update_time + FROM %s.%s; + `, cpdb.schema, CheckpointTableNameEngine)) + if err != nil { + return errors.Trace(err) + } + defer rows.Close() + + return errors.Trace(sqltocsv.Write(writer, rows)) +} + +func (cpdb *MySQLCheckpointsDB) DumpChunks(ctx context.Context, writer io.Writer) error { + rows, err := cpdb.db.QueryContext(ctx, fmt.Sprintf(` + SELECT + table_name, + path, + offset, + type, + compression, + sort_key, + file_size, + columns, + pos, + end_offset, + prev_rowid_max, + rowid_max, + kvc_bytes, + kvc_kvs, + kvc_checksum, + create_time, + update_time + FROM %s.%s; + `, cpdb.schema, CheckpointTableNameChunk)) + if err != nil { + return errors.Trace(err) + } + defer rows.Close() + + return errors.Trace(sqltocsv.Write(writer, rows)) +} + +func (cpdb *FileCheckpointsDB) RemoveCheckpoint(_ context.Context, tableName string) error { + cpdb.lock.Lock() + defer cpdb.lock.Unlock() + + if tableName == "all" { + cpdb.checkpoints.Reset() + return errors.Trace(os.Remove(cpdb.path)) + } + + delete(cpdb.checkpoints.Checkpoints, tableName) + return errors.Trace(cpdb.save()) +} + +func (cpdb *FileCheckpointsDB) MoveCheckpoints(ctx context.Context, taskID int64) error { + cpdb.lock.Lock() + defer cpdb.lock.Unlock() + + newPath := fmt.Sprintf("%s.%d.bak", cpdb.path, taskID) + return errors.Trace(os.Rename(cpdb.path, newPath)) +} + +func (cpdb *FileCheckpointsDB) GetLocalStoringTables(_ context.Context) (map[string][]int32, error) { + cpdb.lock.Lock() + defer cpdb.lock.Unlock() + + targetTables := make(map[string][]int32) + + for tableName, tableModel := range cpdb.checkpoints.Checkpoints { + if tableModel.Status <= uint32(CheckpointStatusMaxInvalid) || + tableModel.Status >= uint32(CheckpointStatusIndexImported) { + continue + } + for engineID, engineModel := range tableModel.Engines { + if engineModel.Status <= uint32(CheckpointStatusMaxInvalid) || + engineModel.Status >= uint32(CheckpointStatusImported) { + continue + } + + for _, chunkModel := range engineModel.Chunks { + if chunkModel.Pos > chunkModel.Offset { + targetTables[tableName] = append(targetTables[tableName], engineID) + break + } + } + + } + } + + return targetTables, nil +} + +func (cpdb *FileCheckpointsDB) IgnoreErrorCheckpoint(_ context.Context, targetTableName string) error { + cpdb.lock.Lock() + defer cpdb.lock.Unlock() + + for tableName, tableModel := range cpdb.checkpoints.Checkpoints { + if !(targetTableName == "all" || targetTableName == tableName) { + continue + } + if tableModel.Status <= uint32(CheckpointStatusMaxInvalid) { + tableModel.Status = uint32(CheckpointStatusLoaded) + } + for _, engineModel := range tableModel.Engines { + if engineModel.Status <= uint32(CheckpointStatusMaxInvalid) { + engineModel.Status = uint32(CheckpointStatusLoaded) + } + } + } + return errors.Trace(cpdb.save()) +} + +func (cpdb *FileCheckpointsDB) DestroyErrorCheckpoint(_ context.Context, targetTableName string) ([]DestroyedTableCheckpoint, error) { + cpdb.lock.Lock() + defer cpdb.lock.Unlock() + + var targetTables []DestroyedTableCheckpoint + + for tableName, tableModel := range cpdb.checkpoints.Checkpoints { + // Obtain the list of tables + if !(targetTableName == "all" || targetTableName == tableName) { + continue + } + if tableModel.Status <= uint32(CheckpointStatusMaxInvalid) { + var minEngineID, maxEngineID int32 = math.MaxInt32, math.MinInt32 + for engineID := range tableModel.Engines { + if engineID < minEngineID { + minEngineID = engineID + } + if engineID > maxEngineID { + maxEngineID = engineID + } + } + + targetTables = append(targetTables, DestroyedTableCheckpoint{ + TableName: tableName, + MinEngineID: minEngineID, + MaxEngineID: maxEngineID, + }) + } + } + + // Delete the checkpoints + for _, dtcp := range targetTables { + delete(cpdb.checkpoints.Checkpoints, dtcp.TableName) + } + if err := cpdb.save(); err != nil { + return nil, errors.Trace(err) + } + + return targetTables, nil +} + +func (cpdb *FileCheckpointsDB) DumpTables(context.Context, io.Writer) error { + return errors.Errorf("dumping file checkpoint into CSV not unsupported, you may copy %s instead", cpdb.path) +} + +func (cpdb *FileCheckpointsDB) DumpEngines(context.Context, io.Writer) error { + return errors.Errorf("dumping file checkpoint into CSV not unsupported, you may copy %s instead", cpdb.path) +} + +func (cpdb *FileCheckpointsDB) DumpChunks(context.Context, io.Writer) error { + return errors.Errorf("dumping file checkpoint into CSV not unsupported, you may copy %s instead", cpdb.path) +} + +func intSlice2Int32Slice(s []int) []int32 { + res := make([]int32, 0, len(s)) + for _, i := range s { + res = append(res, int32(i)) + } + return res +} diff --git a/pkg/lightning/checkpoints/checkpoints_file_test.go b/pkg/lightning/checkpoints/checkpoints_file_test.go new file mode 100644 index 000000000..e49a9d738 --- /dev/null +++ b/pkg/lightning/checkpoints/checkpoints_file_test.go @@ -0,0 +1,327 @@ +package checkpoints_test + +import ( + "context" + "path/filepath" + "sort" + "testing" + + . "github.com/pingcap/check" + + "github.com/pingcap/br/pkg/lightning/checkpoints" + "github.com/pingcap/br/pkg/lightning/config" + "github.com/pingcap/br/pkg/lightning/mydump" + "github.com/pingcap/br/pkg/lightning/verification" +) + +func Test(t *testing.T) { + TestingT(t) +} + +var _ = Suite(&cpFileSuite{}) + +type cpFileSuite struct { + path string + cpdb *checkpoints.FileCheckpointsDB + cfg *config.Config +} + +func newTestConfig() *config.Config { + cfg := config.NewConfig() + cfg.Mydumper.SourceDir = "/data" + cfg.TaskID = 123 + cfg.TiDB.Port = 4000 + cfg.TiDB.PdAddr = "127.0.0.1:2379" + cfg.TikvImporter.Addr = "127.0.0.1:8287" + cfg.TikvImporter.SortedKVDir = "/tmp/sorted-kv" + return cfg +} + +func (s *cpFileSuite) SetUpTest(c *C) { + dir := c.MkDir() + s.cpdb = checkpoints.NewFileCheckpointsDB(filepath.Join(dir, "cp.pb")) + + ctx := context.Background() + cpdb := s.cpdb + + // 2. initialize with checkpoint data. + cfg := newTestConfig() + err := cpdb.Initialize(ctx, cfg, map[string]*checkpoints.TidbDBInfo{ + "db1": { + Name: "db1", + Tables: map[string]*checkpoints.TidbTableInfo{ + "t1": {Name: "t1"}, + "t2": {Name: "t2"}, + }, + }, + "db2": { + Name: "db2", + Tables: map[string]*checkpoints.TidbTableInfo{ + "t3": {Name: "t3"}, + }, + }, + }) + c.Assert(err, IsNil) + + // 3. set some checkpoints + + err = cpdb.InsertEngineCheckpoints(ctx, "`db1`.`t2`", map[int32]*checkpoints.EngineCheckpoint{ + 0: { + Status: checkpoints.CheckpointStatusLoaded, + Chunks: []*checkpoints.ChunkCheckpoint{{ + Key: checkpoints.ChunkCheckpointKey{ + Path: "/tmp/path/1.sql", + Offset: 0, + }, + FileMeta: mydump.SourceFileMeta{ + Path: "/tmp/path/1.sql", + Type: mydump.SourceTypeSQL, + FileSize: 12345, + }, + Chunk: mydump.Chunk{ + Offset: 12, + EndOffset: 102400, + PrevRowIDMax: 1, + RowIDMax: 5000, + }, + }}, + }, + -1: { + Status: checkpoints.CheckpointStatusLoaded, + Chunks: nil, + }, + }) + c.Assert(err, IsNil) + + err = cpdb.InsertEngineCheckpoints(ctx, "`db2`.`t3`", map[int32]*checkpoints.EngineCheckpoint{ + -1: { + Status: checkpoints.CheckpointStatusLoaded, + Chunks: nil, + }, + }) + c.Assert(err, IsNil) + + // 4. update some checkpoints + + cpd := checkpoints.NewTableCheckpointDiff() + scm := checkpoints.StatusCheckpointMerger{ + EngineID: 0, + Status: checkpoints.CheckpointStatusImported, + } + scm.MergeInto(cpd) + scm = checkpoints.StatusCheckpointMerger{ + EngineID: checkpoints.WholeTableEngineID, + Status: checkpoints.CheckpointStatusAllWritten, + } + scm.MergeInto(cpd) + rcm := checkpoints.RebaseCheckpointMerger{ + AllocBase: 132861, + } + rcm.MergeInto(cpd) + ccm := checkpoints.ChunkCheckpointMerger{ + EngineID: 0, + Key: checkpoints.ChunkCheckpointKey{Path: "/tmp/path/1.sql", Offset: 0}, + Checksum: verification.MakeKVChecksum(4491, 586, 486070148917), + Pos: 55904, + RowID: 681, + } + ccm.MergeInto(cpd) + + cpdb.Update(map[string]*checkpoints.TableCheckpointDiff{"`db1`.`t2`": cpd}) +} + +func (s *cpFileSuite) TearDownTest(c *C) { + c.Assert(s.cpdb.Close(), IsNil) +} + +func (s *cpFileSuite) setInvalidStatus() { + cpd := checkpoints.NewTableCheckpointDiff() + scm := checkpoints.StatusCheckpointMerger{ + EngineID: -1, + Status: checkpoints.CheckpointStatusAllWritten, + } + scm.SetInvalid() + scm.MergeInto(cpd) + + s.cpdb.Update(map[string]*checkpoints.TableCheckpointDiff{ + "`db1`.`t2`": cpd, + "`db2`.`t3`": cpd, + }) +} + +func (s *cpFileSuite) TestGet(c *C) { + ctx := context.Background() + + // 5. get back the checkpoints + + cp, err := s.cpdb.Get(ctx, "`db1`.`t2`") + c.Assert(err, IsNil) + c.Assert(cp, DeepEquals, &checkpoints.TableCheckpoint{ + Status: checkpoints.CheckpointStatusAllWritten, + AllocBase: 132861, + Engines: map[int32]*checkpoints.EngineCheckpoint{ + -1: { + Status: checkpoints.CheckpointStatusLoaded, + Chunks: []*checkpoints.ChunkCheckpoint{}, + }, + 0: { + Status: checkpoints.CheckpointStatusImported, + Chunks: []*checkpoints.ChunkCheckpoint{{ + Key: checkpoints.ChunkCheckpointKey{ + Path: "/tmp/path/1.sql", + Offset: 0, + }, + FileMeta: mydump.SourceFileMeta{ + Path: "/tmp/path/1.sql", + Type: mydump.SourceTypeSQL, + FileSize: 12345, + }, + ColumnPermutation: []int{}, + Chunk: mydump.Chunk{ + Offset: 55904, + EndOffset: 102400, + PrevRowIDMax: 681, + RowIDMax: 5000, + }, + Checksum: verification.MakeKVChecksum(4491, 586, 486070148917), + }}, + }, + }, + }) + + cp, err = s.cpdb.Get(ctx, "`db2`.`t3`") + c.Assert(err, IsNil) + c.Assert(cp, DeepEquals, &checkpoints.TableCheckpoint{ + Status: checkpoints.CheckpointStatusLoaded, + Engines: map[int32]*checkpoints.EngineCheckpoint{ + -1: { + Status: checkpoints.CheckpointStatusLoaded, + Chunks: []*checkpoints.ChunkCheckpoint{}, + }, + }, + }) + + cp, err = s.cpdb.Get(ctx, "`db3`.`not-exists`") + c.Assert(err, IsNil) + c.Assert(cp, DeepEquals, &checkpoints.TableCheckpoint{ + Engines: make(map[int32]*checkpoints.EngineCheckpoint), + }) +} + +func (s *cpFileSuite) TestRemoveAllCheckpoints(c *C) { + ctx := context.Background() + + err := s.cpdb.RemoveCheckpoint(ctx, "all") + c.Assert(err, IsNil) + + cp, err := s.cpdb.Get(ctx, "`db1`.`t2`") + c.Assert(err, IsNil) + c.Assert(cp.Status, Equals, checkpoints.CheckpointStatusMissing) + + cp, err = s.cpdb.Get(ctx, "`db2`.`t3`") + c.Assert(err, IsNil) + c.Assert(cp.Status, Equals, checkpoints.CheckpointStatusMissing) +} + +func (s *cpFileSuite) TestRemoveOneCheckpoint(c *C) { + ctx := context.Background() + + err := s.cpdb.RemoveCheckpoint(ctx, "`db1`.`t2`") + c.Assert(err, IsNil) + + cp, err := s.cpdb.Get(ctx, "`db1`.`t2`") + c.Assert(err, IsNil) + c.Assert(cp.Status, Equals, checkpoints.CheckpointStatusMissing) + + cp, err = s.cpdb.Get(ctx, "`db2`.`t3`") + c.Assert(err, IsNil) + c.Assert(cp.Status, Equals, checkpoints.CheckpointStatusLoaded) +} + +func (s *cpFileSuite) TestIgnoreAllErrorCheckpoints(c *C) { + ctx := context.Background() + + s.setInvalidStatus() + + err := s.cpdb.IgnoreErrorCheckpoint(ctx, "all") + c.Assert(err, IsNil) + + cp, err := s.cpdb.Get(ctx, "`db1`.`t2`") + c.Assert(err, IsNil) + c.Assert(cp.Status, Equals, checkpoints.CheckpointStatusLoaded) + + cp, err = s.cpdb.Get(ctx, "`db2`.`t3`") + c.Assert(err, IsNil) + c.Assert(cp.Status, Equals, checkpoints.CheckpointStatusLoaded) +} + +func (s *cpFileSuite) TestIgnoreOneErrorCheckpoints(c *C) { + ctx := context.Background() + + s.setInvalidStatus() + + err := s.cpdb.IgnoreErrorCheckpoint(ctx, "`db1`.`t2`") + c.Assert(err, IsNil) + + cp, err := s.cpdb.Get(ctx, "`db1`.`t2`") + c.Assert(err, IsNil) + c.Assert(cp.Status, Equals, checkpoints.CheckpointStatusLoaded) + + cp, err = s.cpdb.Get(ctx, "`db2`.`t3`") + c.Assert(err, IsNil) + c.Assert(cp.Status, Equals, checkpoints.CheckpointStatusAllWritten/10) +} + +func (s *cpFileSuite) TestDestroyAllErrorCheckpoints(c *C) { + ctx := context.Background() + + s.setInvalidStatus() + + dtc, err := s.cpdb.DestroyErrorCheckpoint(ctx, "all") + c.Assert(err, IsNil) + sort.Slice(dtc, func(i, j int) bool { return dtc[i].TableName < dtc[j].TableName }) + c.Assert(dtc, DeepEquals, []checkpoints.DestroyedTableCheckpoint{ + { + TableName: "`db1`.`t2`", + MinEngineID: -1, + MaxEngineID: 0, + }, + { + TableName: "`db2`.`t3`", + MinEngineID: -1, + MaxEngineID: -1, + }, + }) + + cp, err := s.cpdb.Get(ctx, "`db1`.`t2`") + c.Assert(err, IsNil) + c.Assert(cp.Status, Equals, checkpoints.CheckpointStatusMissing) + + cp, err = s.cpdb.Get(ctx, "`db2`.`t3`") + c.Assert(err, IsNil) + c.Assert(cp.Status, Equals, checkpoints.CheckpointStatusMissing) +} + +func (s *cpFileSuite) TestDestroyOneErrorCheckpoint(c *C) { + ctx := context.Background() + + s.setInvalidStatus() + + dtc, err := s.cpdb.DestroyErrorCheckpoint(ctx, "`db1`.`t2`") + c.Assert(err, IsNil) + c.Assert(dtc, DeepEquals, []checkpoints.DestroyedTableCheckpoint{ + { + TableName: "`db1`.`t2`", + MinEngineID: -1, + MaxEngineID: 0, + }, + }) + + cp, err := s.cpdb.Get(ctx, "`db1`.`t2`") + c.Assert(err, IsNil) + c.Assert(cp.Status, Equals, checkpoints.CheckpointStatusMissing) + + cp, err = s.cpdb.Get(ctx, "`db2`.`t3`") + c.Assert(err, IsNil) + c.Assert(cp.Status, Equals, checkpoints.CheckpointStatusAllWritten/10) +} diff --git a/pkg/lightning/checkpoints/checkpoints_sql_test.go b/pkg/lightning/checkpoints/checkpoints_sql_test.go new file mode 100644 index 000000000..fdf000182 --- /dev/null +++ b/pkg/lightning/checkpoints/checkpoints_sql_test.go @@ -0,0 +1,493 @@ +package checkpoints_test + +import ( + "context" + "database/sql" + "strings" + "time" + + "github.com/DATA-DOG/go-sqlmock" + . "github.com/pingcap/check" + + "github.com/pingcap/br/pkg/lightning/checkpoints" + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/mydump" + "github.com/pingcap/br/pkg/lightning/verification" +) + +var _ = Suite(&cpSQLSuite{}) + +type cpSQLSuite struct { + db *sql.DB + mock sqlmock.Sqlmock + cpdb *checkpoints.MySQLCheckpointsDB +} + +func (s *cpSQLSuite) SetUpTest(c *C) { + db, mock, err := sqlmock.New() + c.Assert(err, IsNil) + s.db = db + s.mock = mock + + // 1. create the checkpoints database. + s.mock. + ExpectExec("CREATE DATABASE IF NOT EXISTS `mock-schema`"). + WillReturnResult(sqlmock.NewResult(1, 1)) + s.mock. + ExpectExec("CREATE TABLE IF NOT EXISTS `mock-schema`\\.task_v\\d+ .+"). + WillReturnResult(sqlmock.NewResult(2, 1)) + s.mock. + ExpectExec("CREATE TABLE IF NOT EXISTS `mock-schema`\\.table_v\\d+ .+"). + WillReturnResult(sqlmock.NewResult(3, 1)) + s.mock. + ExpectExec("CREATE TABLE IF NOT EXISTS `mock-schema`\\.engine_v\\d+ .+"). + WillReturnResult(sqlmock.NewResult(4, 1)) + s.mock. + ExpectExec("CREATE TABLE IF NOT EXISTS `mock-schema`\\.chunk_v\\d+ .+"). + WillReturnResult(sqlmock.NewResult(5, 1)) + + cpdb, err := checkpoints.NewMySQLCheckpointsDB(context.Background(), s.db, "mock-schema") + c.Assert(err, IsNil) + c.Assert(s.mock.ExpectationsWereMet(), IsNil) + s.cpdb = cpdb +} + +func (s *cpSQLSuite) TearDownTest(c *C) { + s.mock.ExpectClose() + c.Assert(s.cpdb.Close(), IsNil) + c.Assert(s.mock.ExpectationsWereMet(), IsNil) +} + +func (s *cpSQLSuite) TestNormalOperations(c *C) { + ctx := context.Background() + cpdb := s.cpdb + + // 2. initialize with checkpoint data. + + s.mock.ExpectBegin() + initializeStmt := s.mock.ExpectPrepare( + "REPLACE INTO `mock-schema`\\.task_v\\d+") + initializeStmt.ExpectExec(). + WithArgs(123, "/data", "importer", "127.0.0.1:8287", "127.0.0.1", 4000, "127.0.0.1:2379", "/tmp/sorted-kv", common.ReleaseVersion). + WillReturnResult(sqlmock.NewResult(6, 1)) + initializeStmt = s.mock. + ExpectPrepare("INSERT INTO `mock-schema`\\.table_v\\d+") + initializeStmt.ExpectExec(). + WithArgs(123, "`db1`.`t1`", sqlmock.AnyArg(), int64(1)). + WillReturnResult(sqlmock.NewResult(7, 1)) + initializeStmt.ExpectExec(). + WithArgs(123, "`db1`.`t2`", sqlmock.AnyArg(), int64(2)). + WillReturnResult(sqlmock.NewResult(8, 1)) + initializeStmt.ExpectExec(). + WithArgs(123, "`db2`.`t3`", sqlmock.AnyArg(), int64(3)). + WillReturnResult(sqlmock.NewResult(9, 1)) + s.mock.ExpectCommit() + + s.mock.MatchExpectationsInOrder(false) + cfg := newTestConfig() + err := cpdb.Initialize(ctx, cfg, map[string]*checkpoints.TidbDBInfo{ + "db1": { + Name: "db1", + Tables: map[string]*checkpoints.TidbTableInfo{ + "t1": {Name: "t1", ID: 1}, + "t2": {Name: "t2", ID: 2}, + }, + }, + "db2": { + Name: "db2", + Tables: map[string]*checkpoints.TidbTableInfo{ + "t3": {Name: "t3", ID: 3}, + }, + }, + }) + s.mock.MatchExpectationsInOrder(true) + c.Assert(err, IsNil) + c.Assert(s.mock.ExpectationsWereMet(), IsNil) + + // 3. set some checkpoints + + s.mock.ExpectBegin() + insertEngineStmt := s.mock. + ExpectPrepare("REPLACE INTO `mock-schema`\\.engine_v\\d+ .+") + insertEngineStmt. + ExpectExec(). + WithArgs("`db1`.`t2`", 0, 30). + WillReturnResult(sqlmock.NewResult(8, 1)) + insertEngineStmt. + ExpectExec(). + WithArgs("`db1`.`t2`", -1, 30). + WillReturnResult(sqlmock.NewResult(9, 1)) + insertChunkStmt := s.mock. + ExpectPrepare("REPLACE INTO `mock-schema`\\.chunk_v\\d+ .+") + insertChunkStmt. + ExpectExec(). + WithArgs("`db1`.`t2`", 0, "/tmp/path/1.sql", 0, mydump.SourceTypeSQL, 0, "", 123, []byte("null"), 12, 102400, 1, 5000, 1234567890). + WillReturnResult(sqlmock.NewResult(10, 1)) + s.mock.ExpectCommit() + + s.mock.MatchExpectationsInOrder(false) + err = cpdb.InsertEngineCheckpoints(ctx, "`db1`.`t2`", map[int32]*checkpoints.EngineCheckpoint{ + 0: { + Status: checkpoints.CheckpointStatusLoaded, + Chunks: []*checkpoints.ChunkCheckpoint{{ + Key: checkpoints.ChunkCheckpointKey{ + Path: "/tmp/path/1.sql", + Offset: 0, + }, + FileMeta: mydump.SourceFileMeta{ + Path: "/tmp/path/1.sql", + Type: mydump.SourceTypeSQL, + FileSize: 123, + }, + Chunk: mydump.Chunk{ + Offset: 12, + EndOffset: 102400, + PrevRowIDMax: 1, + RowIDMax: 5000, + }, + Timestamp: 1234567890, + }}, + }, + -1: { + Status: checkpoints.CheckpointStatusLoaded, + Chunks: nil, + }, + }) + s.mock.MatchExpectationsInOrder(true) + c.Assert(err, IsNil) + c.Assert(s.mock.ExpectationsWereMet(), IsNil) + + // 4. update some checkpoints + + cpd := checkpoints.NewTableCheckpointDiff() + scm := checkpoints.StatusCheckpointMerger{ + EngineID: 0, + Status: checkpoints.CheckpointStatusImported, + } + scm.MergeInto(cpd) + scm = checkpoints.StatusCheckpointMerger{ + EngineID: checkpoints.WholeTableEngineID, + Status: checkpoints.CheckpointStatusAllWritten, + } + scm.MergeInto(cpd) + rcm := checkpoints.RebaseCheckpointMerger{ + AllocBase: 132861, + } + rcm.MergeInto(cpd) + ccm := checkpoints.ChunkCheckpointMerger{ + EngineID: 0, + Key: checkpoints.ChunkCheckpointKey{Path: "/tmp/path/1.sql", Offset: 0}, + Checksum: verification.MakeKVChecksum(4491, 586, 486070148917), + Pos: 55904, + RowID: 681, + } + ccm.MergeInto(cpd) + + s.mock.ExpectBegin() + s.mock. + ExpectPrepare("UPDATE `mock-schema`\\.chunk_v\\d+ SET pos = .+"). + ExpectExec(). + WithArgs( + 55904, 681, 4491, 586, 486070148917, []byte("null"), + "`db1`.`t2`", 0, "/tmp/path/1.sql", 0, + ). + WillReturnResult(sqlmock.NewResult(11, 1)) + s.mock. + ExpectPrepare("UPDATE `mock-schema`\\.table_v\\d+ SET alloc_base = .+"). + ExpectExec(). + WithArgs(132861, "`db1`.`t2`"). + WillReturnResult(sqlmock.NewResult(12, 1)) + s.mock. + ExpectPrepare("UPDATE `mock-schema`\\.engine_v\\d+ SET status = .+"). + ExpectExec(). + WithArgs(120, "`db1`.`t2`", 0). + WillReturnResult(sqlmock.NewResult(13, 1)) + s.mock. + ExpectPrepare("UPDATE `mock-schema`\\.table_v\\d+ SET status = .+"). + ExpectExec(). + WithArgs(60, "`db1`.`t2`"). + WillReturnResult(sqlmock.NewResult(14, 1)) + s.mock.ExpectCommit() + + s.mock.MatchExpectationsInOrder(false) + cpdb.Update(map[string]*checkpoints.TableCheckpointDiff{"`db1`.`t2`": cpd}) + s.mock.MatchExpectationsInOrder(true) + c.Assert(s.mock.ExpectationsWereMet(), IsNil) + + // 5. get back the checkpoints + + s.mock.ExpectBegin() + s.mock. + ExpectQuery("SELECT .+ FROM `mock-schema`\\.engine_v\\d+"). + WithArgs("`db1`.`t2`"). + WillReturnRows( + sqlmock.NewRows([]string{"engine_id", "status"}). + AddRow(0, 120). + AddRow(-1, 30), + ) + s.mock. + ExpectQuery("SELECT (?s:.+) FROM `mock-schema`\\.chunk_v\\d+"). + WithArgs("`db1`.`t2`"). + WillReturnRows( + sqlmock.NewRows([]string{ + "engine_id", "path", "offset", "type", "compression", "sort_key", "file_size", "columns", + "pos", "end_offset", "prev_rowid_max", "rowid_max", + "kvc_bytes", "kvc_kvs", "kvc_checksum", "unix_timestamp(create_time)", + }). + AddRow( + 0, "/tmp/path/1.sql", 0, mydump.SourceTypeSQL, 0, "", 123, "[]", + 55904, 102400, 681, 5000, + 4491, 586, 486070148917, 1234567894, + ), + ) + s.mock. + ExpectQuery("SELECT .+ FROM `mock-schema`\\.table_v\\d+"). + WithArgs("`db1`.`t2`"). + WillReturnRows( + sqlmock.NewRows([]string{"status", "alloc_base", "table_id"}). + AddRow(60, 132861, int64(2)), + ) + s.mock.ExpectCommit() + + cp, err := cpdb.Get(ctx, "`db1`.`t2`") + c.Assert(err, IsNil) + c.Assert(cp, DeepEquals, &checkpoints.TableCheckpoint{ + Status: checkpoints.CheckpointStatusAllWritten, + AllocBase: 132861, + TableID: int64(2), + Engines: map[int32]*checkpoints.EngineCheckpoint{ + -1: {Status: checkpoints.CheckpointStatusLoaded}, + 0: { + Status: checkpoints.CheckpointStatusImported, + Chunks: []*checkpoints.ChunkCheckpoint{{ + Key: checkpoints.ChunkCheckpointKey{ + Path: "/tmp/path/1.sql", + Offset: 0, + }, + FileMeta: mydump.SourceFileMeta{ + Path: "/tmp/path/1.sql", + Type: mydump.SourceTypeSQL, + FileSize: 123, + }, + ColumnPermutation: []int{}, + Chunk: mydump.Chunk{ + Offset: 55904, + EndOffset: 102400, + PrevRowIDMax: 681, + RowIDMax: 5000, + }, + Checksum: verification.MakeKVChecksum(4491, 586, 486070148917), + Timestamp: 1234567894, + }}, + }, + }, + }) + c.Assert(s.mock.ExpectationsWereMet(), IsNil) +} + +func (s *cpSQLSuite) TestRemoveAllCheckpoints(c *C) { + s.mock.ExpectExec("DROP SCHEMA `mock-schema`").WillReturnResult(sqlmock.NewResult(0, 1)) + + err := s.cpdb.RemoveCheckpoint(context.Background(), "all") + c.Assert(err, IsNil) +} + +func (s *cpSQLSuite) TestRemoveOneCheckpoint(c *C) { + s.mock.ExpectBegin() + s.mock. + ExpectExec("DELETE FROM `mock-schema`\\.chunk_v\\d+ WHERE table_name = \\?"). + WithArgs("`db1`.`t2`"). + WillReturnResult(sqlmock.NewResult(0, 4)) + s.mock. + ExpectExec("DELETE FROM `mock-schema`\\.engine_v\\d+ WHERE table_name = \\?"). + WithArgs("`db1`.`t2`"). + WillReturnResult(sqlmock.NewResult(0, 2)) + s.mock. + ExpectExec("DELETE FROM `mock-schema`\\.table_v\\d+ WHERE table_name = \\?"). + WithArgs("`db1`.`t2`"). + WillReturnResult(sqlmock.NewResult(0, 1)) + s.mock.ExpectCommit() + + err := s.cpdb.RemoveCheckpoint(context.Background(), "`db1`.`t2`") + c.Assert(err, IsNil) +} + +func (s *cpSQLSuite) TestIgnoreAllErrorCheckpoints(c *C) { + s.mock.ExpectBegin() + s.mock. + ExpectExec("UPDATE `mock-schema`\\.engine_v\\d+ SET status = 30 WHERE 'all' = \\? AND status <= 25"). + WithArgs(sqlmock.AnyArg()). + WillReturnResult(sqlmock.NewResult(5, 3)) + s.mock. + ExpectExec("UPDATE `mock-schema`\\.table_v\\d+ SET status = 30 WHERE 'all' = \\? AND status <= 25"). + WithArgs(sqlmock.AnyArg()). + WillReturnResult(sqlmock.NewResult(6, 2)) + s.mock.ExpectCommit() + + err := s.cpdb.IgnoreErrorCheckpoint(context.Background(), "all") + c.Assert(err, IsNil) +} + +func (s *cpSQLSuite) TestIgnoreOneErrorCheckpoint(c *C) { + s.mock.ExpectBegin() + s.mock. + ExpectExec("UPDATE `mock-schema`\\.engine_v\\d+ SET status = 30 WHERE table_name = \\? AND status <= 25"). + WithArgs("`db1`.`t2`"). + WillReturnResult(sqlmock.NewResult(5, 2)) + s.mock. + ExpectExec("UPDATE `mock-schema`\\.table_v\\d+ SET status = 30 WHERE table_name = \\? AND status <= 25"). + WithArgs("`db1`.`t2`"). + WillReturnResult(sqlmock.NewResult(6, 1)) + s.mock.ExpectCommit() + + err := s.cpdb.IgnoreErrorCheckpoint(context.Background(), "`db1`.`t2`") + c.Assert(err, IsNil) +} + +func (s *cpSQLSuite) TestDestroyAllErrorCheckpoints(c *C) { + s.mock.ExpectBegin() + s.mock. + ExpectQuery("SELECT (?s:.+)'all' = \\?"). + WithArgs(sqlmock.AnyArg()). + WillReturnRows( + sqlmock.NewRows([]string{"table_name", "__min__", "__max__"}). + AddRow("`db1`.`t2`", -1, 0), + ) + s.mock. + ExpectExec("DELETE FROM `mock-schema`\\.chunk_v\\d+ WHERE table_name IN .+ 'all' = \\?"). + WithArgs(sqlmock.AnyArg()). + WillReturnResult(sqlmock.NewResult(0, 5)) + s.mock. + ExpectExec("DELETE FROM `mock-schema`\\.engine_v\\d+ WHERE table_name IN .+ 'all' = \\?"). + WithArgs(sqlmock.AnyArg()). + WillReturnResult(sqlmock.NewResult(0, 3)) + s.mock. + ExpectExec("DELETE FROM `mock-schema`\\.table_v\\d+ WHERE 'all' = \\?"). + WithArgs(sqlmock.AnyArg()). + WillReturnResult(sqlmock.NewResult(0, 2)) + s.mock.ExpectCommit() + + dtc, err := s.cpdb.DestroyErrorCheckpoint(context.Background(), "all") + c.Assert(err, IsNil) + c.Assert(dtc, DeepEquals, []checkpoints.DestroyedTableCheckpoint{{ + TableName: "`db1`.`t2`", + MinEngineID: -1, + MaxEngineID: 0, + }}) +} + +func (s *cpSQLSuite) TestDestroyOneErrorCheckpoints(c *C) { + s.mock.ExpectBegin() + s.mock. + ExpectQuery("SELECT (?s:.+)table_name = \\?"). + WithArgs("`db1`.`t2`"). + WillReturnRows( + sqlmock.NewRows([]string{"table_name", "__min__", "__max__"}). + AddRow("`db1`.`t2`", -1, 0), + ) + s.mock. + ExpectExec("DELETE FROM `mock-schema`\\.chunk_v\\d+ WHERE .+table_name = \\?"). + WithArgs("`db1`.`t2`"). + WillReturnResult(sqlmock.NewResult(0, 4)) + s.mock. + ExpectExec("DELETE FROM `mock-schema`\\.engine_v\\d+ WHERE .+table_name = \\?"). + WithArgs("`db1`.`t2`"). + WillReturnResult(sqlmock.NewResult(0, 2)) + s.mock. + ExpectExec("DELETE FROM `mock-schema`\\.table_v\\d+ WHERE table_name = \\?"). + WithArgs("`db1`.`t2`"). + WillReturnResult(sqlmock.NewResult(0, 1)) + s.mock.ExpectCommit() + + dtc, err := s.cpdb.DestroyErrorCheckpoint(context.Background(), "`db1`.`t2`") + c.Assert(err, IsNil) + c.Assert(dtc, DeepEquals, []checkpoints.DestroyedTableCheckpoint{{ + TableName: "`db1`.`t2`", + MinEngineID: -1, + MaxEngineID: 0, + }}) +} + +func (s *cpSQLSuite) TestDump(c *C) { + ctx := context.Background() + t := time.Unix(1555555555, 0).UTC() + + s.mock. + ExpectQuery("SELECT (?s:.+) FROM `mock-schema`\\.chunk_v\\d+"). + WillReturnRows( + sqlmock.NewRows([]string{ + "table_name", "path", "offset", "type", "compression", "sort_key", "file_size", "columns", + "pos", "end_offset", "prev_rowid_max", "rowid_max", + "kvc_bytes", "kvc_kvs", "kvc_checksum", + "create_time", "update_time", + }).AddRow( + "`db1`.`t2`", "/tmp/path/1.sql", 0, mydump.SourceTypeSQL, mydump.CompressionNone, "", 456, "[]", + 55904, 102400, 681, 5000, + 4491, 586, 486070148917, + t, t, + ), + ) + + var csvBuilder strings.Builder + err := s.cpdb.DumpChunks(ctx, &csvBuilder) + c.Assert(err, IsNil) + c.Assert(csvBuilder.String(), Equals, + "table_name,path,offset,type,compression,sort_key,file_size,columns,pos,end_offset,prev_rowid_max,rowid_max,kvc_bytes,kvc_kvs,kvc_checksum,create_time,update_time\n"+ + "`db1`.`t2`,/tmp/path/1.sql,0,3,0,,456,[],55904,102400,681,5000,4491,586,486070148917,2019-04-18 02:45:55 +0000 UTC,2019-04-18 02:45:55 +0000 UTC\n", + ) + + s.mock. + ExpectQuery("SELECT .+ FROM `mock-schema`\\.engine_v\\d+"). + WillReturnRows( + sqlmock.NewRows([]string{"table_name", "engine_id", "status", "create_time", "update_time"}). + AddRow("`db1`.`t2`", -1, 30, t, t). + AddRow("`db1`.`t2`", 0, 120, t, t), + ) + + csvBuilder.Reset() + err = s.cpdb.DumpEngines(ctx, &csvBuilder) + c.Assert(err, IsNil) + c.Assert(csvBuilder.String(), Equals, + "table_name,engine_id,status,create_time,update_time\n"+ + "`db1`.`t2`,-1,30,2019-04-18 02:45:55 +0000 UTC,2019-04-18 02:45:55 +0000 UTC\n"+ + "`db1`.`t2`,0,120,2019-04-18 02:45:55 +0000 UTC,2019-04-18 02:45:55 +0000 UTC\n", + ) + + s.mock. + ExpectQuery("SELECT .+ FROM `mock-schema`\\.table_v\\d+"). + WillReturnRows( + sqlmock.NewRows([]string{"task_id", "table_name", "hash", "status", "alloc_base", "create_time", "update_time"}). + AddRow(1555555555, "`db1`.`t2`", 0, 90, 132861, t, t), + ) + + csvBuilder.Reset() + err = s.cpdb.DumpTables(ctx, &csvBuilder) + c.Assert(err, IsNil) + c.Assert(csvBuilder.String(), Equals, + "task_id,table_name,hash,status,alloc_base,create_time,update_time\n"+ + "1555555555,`db1`.`t2`,0,90,132861,2019-04-18 02:45:55 +0000 UTC,2019-04-18 02:45:55 +0000 UTC\n", + ) +} + +func (s *cpSQLSuite) TestMoveCheckpoints(c *C) { + ctx := context.Background() + + s.mock. + ExpectExec("CREATE SCHEMA IF NOT EXISTS `mock-schema\\.12345678\\.bak`"). + WillReturnResult(sqlmock.NewResult(1, 1)) + s.mock. + ExpectExec("RENAME TABLE `mock-schema`\\.chunk_v\\d+ TO `mock-schema\\.12345678\\.bak`\\.chunk_v\\d+"). + WillReturnResult(sqlmock.NewResult(0, 1)) + s.mock. + ExpectExec("RENAME TABLE `mock-schema`\\.engine_v\\d+ TO `mock-schema\\.12345678\\.bak`\\.engine_v\\d+"). + WillReturnResult(sqlmock.NewResult(0, 1)) + s.mock. + ExpectExec("RENAME TABLE `mock-schema`\\.table_v\\d+ TO `mock-schema\\.12345678\\.bak`\\.table_v\\d+"). + WillReturnResult(sqlmock.NewResult(0, 1)) + s.mock. + ExpectExec("RENAME TABLE `mock-schema`\\.task_v\\d+ TO `mock-schema\\.12345678\\.bak`\\.task_v\\d+"). + WillReturnResult(sqlmock.NewResult(0, 1)) + + err := s.cpdb.MoveCheckpoints(ctx, 12345678) + c.Assert(err, IsNil) +} diff --git a/pkg/lightning/checkpoints/checkpoints_test.go b/pkg/lightning/checkpoints/checkpoints_test.go new file mode 100644 index 000000000..0cd858c73 --- /dev/null +++ b/pkg/lightning/checkpoints/checkpoints_test.go @@ -0,0 +1,306 @@ +package checkpoints + +import ( + "path/filepath" + "testing" + + . "github.com/pingcap/check" + + "github.com/pingcap/br/pkg/lightning/mydump" + "github.com/pingcap/br/pkg/lightning/verification" +) + +func Test(t *testing.T) { + TestingT(t) +} + +var _ = Suite(&checkpointSuite{}) + +type checkpointSuite struct { +} + +func (s *checkpointSuite) TestMergeStatusCheckpoint(c *C) { + cpd := NewTableCheckpointDiff() + + m := StatusCheckpointMerger{EngineID: 0, Status: CheckpointStatusImported} + m.MergeInto(cpd) + + c.Assert(cpd, DeepEquals, &TableCheckpointDiff{ + hasStatus: false, + engines: map[int32]engineCheckpointDiff{ + 0: { + hasStatus: true, + status: CheckpointStatusImported, + chunks: make(map[ChunkCheckpointKey]chunkCheckpointDiff), + }, + }, + }) + + m = StatusCheckpointMerger{EngineID: -1, Status: CheckpointStatusLoaded} + m.MergeInto(cpd) + + c.Assert(cpd, DeepEquals, &TableCheckpointDiff{ + hasStatus: false, + engines: map[int32]engineCheckpointDiff{ + 0: { + hasStatus: true, + status: CheckpointStatusImported, + chunks: make(map[ChunkCheckpointKey]chunkCheckpointDiff), + }, + -1: { + hasStatus: true, + status: CheckpointStatusLoaded, + chunks: make(map[ChunkCheckpointKey]chunkCheckpointDiff), + }, + }, + }) + + m = StatusCheckpointMerger{EngineID: WholeTableEngineID, Status: CheckpointStatusClosed} + m.MergeInto(cpd) + + c.Assert(cpd, DeepEquals, &TableCheckpointDiff{ + hasStatus: true, + status: CheckpointStatusClosed, + engines: map[int32]engineCheckpointDiff{ + 0: { + hasStatus: true, + status: CheckpointStatusImported, + chunks: make(map[ChunkCheckpointKey]chunkCheckpointDiff), + }, + -1: { + hasStatus: true, + status: CheckpointStatusLoaded, + chunks: make(map[ChunkCheckpointKey]chunkCheckpointDiff), + }, + }, + }) + + m = StatusCheckpointMerger{EngineID: -1, Status: CheckpointStatusAllWritten} + m.MergeInto(cpd) + + c.Assert(cpd, DeepEquals, &TableCheckpointDiff{ + hasStatus: true, + status: CheckpointStatusClosed, + engines: map[int32]engineCheckpointDiff{ + 0: { + hasStatus: true, + status: CheckpointStatusImported, + chunks: make(map[ChunkCheckpointKey]chunkCheckpointDiff), + }, + -1: { + hasStatus: true, + status: CheckpointStatusAllWritten, + chunks: make(map[ChunkCheckpointKey]chunkCheckpointDiff), + }, + }, + }) +} + +func (s *checkpointSuite) TestMergeInvalidStatusCheckpoint(c *C) { + cpd := NewTableCheckpointDiff() + + m := StatusCheckpointMerger{EngineID: 0, Status: CheckpointStatusLoaded} + m.MergeInto(cpd) + + m = StatusCheckpointMerger{EngineID: -1, Status: CheckpointStatusAllWritten} + m.SetInvalid() + m.MergeInto(cpd) + + c.Assert(cpd, DeepEquals, &TableCheckpointDiff{ + hasStatus: true, + status: CheckpointStatusAllWritten / 10, + engines: map[int32]engineCheckpointDiff{ + 0: { + hasStatus: true, + status: CheckpointStatusLoaded, + chunks: make(map[ChunkCheckpointKey]chunkCheckpointDiff), + }, + -1: { + hasStatus: true, + status: CheckpointStatusAllWritten / 10, + chunks: make(map[ChunkCheckpointKey]chunkCheckpointDiff), + }, + }, + }) +} + +func (s *checkpointSuite) TestMergeChunkCheckpoint(c *C) { + cpd := NewTableCheckpointDiff() + + key := ChunkCheckpointKey{Path: "/tmp/path/1.sql", Offset: 0} + + m := ChunkCheckpointMerger{ + EngineID: 2, + Key: key, + Checksum: verification.MakeKVChecksum(700, 15, 1234567890), + Pos: 1055, + RowID: 31, + } + m.MergeInto(cpd) + + c.Assert(cpd, DeepEquals, &TableCheckpointDiff{ + engines: map[int32]engineCheckpointDiff{ + 2: { + chunks: map[ChunkCheckpointKey]chunkCheckpointDiff{ + key: { + pos: 1055, + rowID: 31, + checksum: verification.MakeKVChecksum(700, 15, 1234567890), + }, + }, + }, + }, + }) + + m = ChunkCheckpointMerger{ + EngineID: 2, + Key: key, + Checksum: verification.MakeKVChecksum(800, 20, 1357924680), + Pos: 1080, + RowID: 42, + } + m.MergeInto(cpd) + + c.Assert(cpd, DeepEquals, &TableCheckpointDiff{ + engines: map[int32]engineCheckpointDiff{ + 2: { + chunks: map[ChunkCheckpointKey]chunkCheckpointDiff{ + key: { + pos: 1080, + rowID: 42, + checksum: verification.MakeKVChecksum(800, 20, 1357924680), + }, + }, + }, + }, + }) +} + +func (s *checkpointSuite) TestRebaseCheckpoint(c *C) { + cpd := NewTableCheckpointDiff() + + m := RebaseCheckpointMerger{AllocBase: 10000} + m.MergeInto(cpd) + + c.Assert(cpd, DeepEquals, &TableCheckpointDiff{ + hasRebase: true, + allocBase: 10000, + engines: make(map[int32]engineCheckpointDiff), + }) +} + +func (s *checkpointSuite) TestApplyDiff(c *C) { + cp := TableCheckpoint{ + Status: CheckpointStatusLoaded, + AllocBase: 123, + Engines: map[int32]*EngineCheckpoint{ + -1: { + Status: CheckpointStatusLoaded, + }, + 0: { + Status: CheckpointStatusLoaded, + Chunks: []*ChunkCheckpoint{ + { + Key: ChunkCheckpointKey{Path: "/tmp/01.sql"}, + Chunk: mydump.Chunk{ + Offset: 0, + EndOffset: 20000, + PrevRowIDMax: 0, + RowIDMax: 1000, + }, + }, + { + Key: ChunkCheckpointKey{Path: "/tmp/04.sql"}, + Chunk: mydump.Chunk{ + Offset: 0, + EndOffset: 15000, + PrevRowIDMax: 1000, + RowIDMax: 1300, + }, + }, + }, + }, + }, + } + + cpd := NewTableCheckpointDiff() + (&StatusCheckpointMerger{EngineID: -1, Status: CheckpointStatusImported}).MergeInto(cpd) + (&StatusCheckpointMerger{EngineID: WholeTableEngineID, Status: CheckpointStatusAllWritten}).MergeInto(cpd) + (&StatusCheckpointMerger{EngineID: 1234, Status: CheckpointStatusAnalyzeSkipped}).MergeInto(cpd) + (&RebaseCheckpointMerger{AllocBase: 11111}).MergeInto(cpd) + (&ChunkCheckpointMerger{ + EngineID: 0, + Key: ChunkCheckpointKey{Path: "/tmp/01.sql"}, + Checksum: verification.MakeKVChecksum(3333, 4444, 5555), + Pos: 6666, + RowID: 777, + }).MergeInto(cpd) + (&ChunkCheckpointMerger{ + EngineID: 5678, + Key: ChunkCheckpointKey{Path: "/tmp/04.sql"}, + Pos: 9999, + RowID: 888, + }).MergeInto(cpd) + (&ChunkCheckpointMerger{ + EngineID: 0, + Key: ChunkCheckpointKey{Path: "/tmp/03.sql"}, + Pos: 3636, + RowID: 2222, + }).MergeInto(cpd) + (&ChunkCheckpointMerger{ + EngineID: 0, + Key: ChunkCheckpointKey{Path: "/tmp/10.sql"}, + Pos: 4949, + RowID: 444, + }).MergeInto(cpd) + + cp.Apply(cpd) + + c.Assert(cp, DeepEquals, TableCheckpoint{ + Status: CheckpointStatusAllWritten, + AllocBase: 11111, + Engines: map[int32]*EngineCheckpoint{ + -1: { + Status: CheckpointStatusImported, + }, + 0: { + Status: CheckpointStatusLoaded, + Chunks: []*ChunkCheckpoint{ + { + Key: ChunkCheckpointKey{Path: "/tmp/01.sql"}, + Chunk: mydump.Chunk{ + Offset: 6666, + EndOffset: 20000, + PrevRowIDMax: 777, + RowIDMax: 1000, + }, + Checksum: verification.MakeKVChecksum(3333, 4444, 5555), + }, + { + Key: ChunkCheckpointKey{Path: "/tmp/04.sql"}, + Chunk: mydump.Chunk{ + Offset: 0, + EndOffset: 15000, + PrevRowIDMax: 1000, + RowIDMax: 1300, + }, + }, + }, + }, + }, + }) +} + +func (s *checkpointSuite) TestCheckpointMarshallUnmarshall(c *C) { + path := filepath.Join(c.MkDir(), "filecheckpoint") + fileChkp := NewFileCheckpointsDB(path) + fileChkp.checkpoints.Checkpoints["a"] = &TableCheckpointModel{ + Status: uint32(CheckpointStatusLoaded), + Engines: map[int32]*EngineCheckpointModel{}, + } + fileChkp.Close() + + fileChkp2 := NewFileCheckpointsDB(path) + // if not recover empty map explicitly, it will become nil + c.Assert(fileChkp2.checkpoints.Checkpoints["a"].Engines, NotNil) +} diff --git a/pkg/lightning/checkpoints/file_checkpoints.pb.go b/pkg/lightning/checkpoints/file_checkpoints.pb.go new file mode 100644 index 000000000..a70ae492e --- /dev/null +++ b/pkg/lightning/checkpoints/file_checkpoints.pb.go @@ -0,0 +1,2389 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: pkg/lightning/checkpoints/file_checkpoints.proto + +package checkpoints + +import ( + encoding_binary "encoding/binary" + fmt "fmt" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +type CheckpointsModel struct { + // key is table_name + Checkpoints map[string]*TableCheckpointModel `protobuf:"bytes,1,rep,name=checkpoints,proto3" json:"checkpoints,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + TaskCheckpoint *TaskCheckpointModel `protobuf:"bytes,2,opt,name=task_checkpoint,json=taskCheckpoint,proto3" json:"task_checkpoint,omitempty"` +} + +func (m *CheckpointsModel) Reset() { *m = CheckpointsModel{} } +func (m *CheckpointsModel) String() string { return proto.CompactTextString(m) } +func (*CheckpointsModel) ProtoMessage() {} +func (*CheckpointsModel) Descriptor() ([]byte, []int) { + return fileDescriptor_5c42c6f4a8e50df4, []int{0} +} +func (m *CheckpointsModel) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *CheckpointsModel) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_CheckpointsModel.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *CheckpointsModel) XXX_Merge(src proto.Message) { + xxx_messageInfo_CheckpointsModel.Merge(m, src) +} +func (m *CheckpointsModel) XXX_Size() int { + return m.Size() +} +func (m *CheckpointsModel) XXX_DiscardUnknown() { + xxx_messageInfo_CheckpointsModel.DiscardUnknown(m) +} + +var xxx_messageInfo_CheckpointsModel proto.InternalMessageInfo + +type TaskCheckpointModel struct { + TaskId int64 `protobuf:"varint,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` + SourceDir string `protobuf:"bytes,2,opt,name=source_dir,json=sourceDir,proto3" json:"source_dir,omitempty"` + Backend string `protobuf:"bytes,3,opt,name=backend,proto3" json:"backend,omitempty"` + ImporterAddr string `protobuf:"bytes,4,opt,name=importer_addr,json=importerAddr,proto3" json:"importer_addr,omitempty"` + TidbHost string `protobuf:"bytes,5,opt,name=tidb_host,json=tidbHost,proto3" json:"tidb_host,omitempty"` + TidbPort int32 `protobuf:"varint,6,opt,name=tidb_port,json=tidbPort,proto3" json:"tidb_port,omitempty"` + PdAddr string `protobuf:"bytes,7,opt,name=pd_addr,json=pdAddr,proto3" json:"pd_addr,omitempty"` + SortedKvDir string `protobuf:"bytes,8,opt,name=sorted_kv_dir,json=sortedKvDir,proto3" json:"sorted_kv_dir,omitempty"` + LightningVer string `protobuf:"bytes,9,opt,name=lightning_ver,json=lightningVer,proto3" json:"lightning_ver,omitempty"` +} + +func (m *TaskCheckpointModel) Reset() { *m = TaskCheckpointModel{} } +func (m *TaskCheckpointModel) String() string { return proto.CompactTextString(m) } +func (*TaskCheckpointModel) ProtoMessage() {} +func (*TaskCheckpointModel) Descriptor() ([]byte, []int) { + return fileDescriptor_5c42c6f4a8e50df4, []int{1} +} +func (m *TaskCheckpointModel) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TaskCheckpointModel) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TaskCheckpointModel.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TaskCheckpointModel) XXX_Merge(src proto.Message) { + xxx_messageInfo_TaskCheckpointModel.Merge(m, src) +} +func (m *TaskCheckpointModel) XXX_Size() int { + return m.Size() +} +func (m *TaskCheckpointModel) XXX_DiscardUnknown() { + xxx_messageInfo_TaskCheckpointModel.DiscardUnknown(m) +} + +var xxx_messageInfo_TaskCheckpointModel proto.InternalMessageInfo + +type TableCheckpointModel struct { + Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` + Status uint32 `protobuf:"varint,3,opt,name=status,proto3" json:"status,omitempty"` + AllocBase int64 `protobuf:"varint,4,opt,name=alloc_base,json=allocBase,proto3" json:"alloc_base,omitempty"` + Engines map[int32]*EngineCheckpointModel `protobuf:"bytes,8,rep,name=engines,proto3" json:"engines,omitempty" protobuf_key:"zigzag32,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + TableID int64 `protobuf:"varint,9,opt,name=tableID,proto3" json:"tableID,omitempty"` +} + +func (m *TableCheckpointModel) Reset() { *m = TableCheckpointModel{} } +func (m *TableCheckpointModel) String() string { return proto.CompactTextString(m) } +func (*TableCheckpointModel) ProtoMessage() {} +func (*TableCheckpointModel) Descriptor() ([]byte, []int) { + return fileDescriptor_5c42c6f4a8e50df4, []int{2} +} +func (m *TableCheckpointModel) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TableCheckpointModel) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TableCheckpointModel.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TableCheckpointModel) XXX_Merge(src proto.Message) { + xxx_messageInfo_TableCheckpointModel.Merge(m, src) +} +func (m *TableCheckpointModel) XXX_Size() int { + return m.Size() +} +func (m *TableCheckpointModel) XXX_DiscardUnknown() { + xxx_messageInfo_TableCheckpointModel.DiscardUnknown(m) +} + +var xxx_messageInfo_TableCheckpointModel proto.InternalMessageInfo + +type EngineCheckpointModel struct { + Status uint32 `protobuf:"varint,1,opt,name=status,proto3" json:"status,omitempty"` + // key is "$path:$offset" + Chunks map[string]*ChunkCheckpointModel `protobuf:"bytes,2,rep,name=chunks,proto3" json:"chunks,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (m *EngineCheckpointModel) Reset() { *m = EngineCheckpointModel{} } +func (m *EngineCheckpointModel) String() string { return proto.CompactTextString(m) } +func (*EngineCheckpointModel) ProtoMessage() {} +func (*EngineCheckpointModel) Descriptor() ([]byte, []int) { + return fileDescriptor_5c42c6f4a8e50df4, []int{3} +} +func (m *EngineCheckpointModel) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EngineCheckpointModel) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EngineCheckpointModel.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EngineCheckpointModel) XXX_Merge(src proto.Message) { + xxx_messageInfo_EngineCheckpointModel.Merge(m, src) +} +func (m *EngineCheckpointModel) XXX_Size() int { + return m.Size() +} +func (m *EngineCheckpointModel) XXX_DiscardUnknown() { + xxx_messageInfo_EngineCheckpointModel.DiscardUnknown(m) +} + +var xxx_messageInfo_EngineCheckpointModel proto.InternalMessageInfo + +type ChunkCheckpointModel struct { + Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"` + Offset int64 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` + ColumnPermutation []int32 `protobuf:"varint,12,rep,packed,name=column_permutation,json=columnPermutation,proto3" json:"column_permutation,omitempty"` + EndOffset int64 `protobuf:"varint,5,opt,name=end_offset,json=endOffset,proto3" json:"end_offset,omitempty"` + Pos int64 `protobuf:"varint,6,opt,name=pos,proto3" json:"pos,omitempty"` + PrevRowidMax int64 `protobuf:"varint,7,opt,name=prev_rowid_max,json=prevRowidMax,proto3" json:"prev_rowid_max,omitempty"` + RowidMax int64 `protobuf:"varint,8,opt,name=rowid_max,json=rowidMax,proto3" json:"rowid_max,omitempty"` + KvcBytes uint64 `protobuf:"varint,9,opt,name=kvc_bytes,json=kvcBytes,proto3" json:"kvc_bytes,omitempty"` + KvcKvs uint64 `protobuf:"varint,10,opt,name=kvc_kvs,json=kvcKvs,proto3" json:"kvc_kvs,omitempty"` + KvcChecksum uint64 `protobuf:"fixed64,11,opt,name=kvc_checksum,json=kvcChecksum,proto3" json:"kvc_checksum,omitempty"` + Timestamp int64 `protobuf:"fixed64,13,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Type int32 `protobuf:"varint,14,opt,name=type,proto3" json:"type,omitempty"` + Compression int32 `protobuf:"varint,15,opt,name=compression,proto3" json:"compression,omitempty"` + SortKey string `protobuf:"bytes,16,opt,name=sort_key,json=sortKey,proto3" json:"sort_key,omitempty"` + FileSize int64 `protobuf:"varint,17,opt,name=file_size,json=fileSize,proto3" json:"file_size,omitempty"` +} + +func (m *ChunkCheckpointModel) Reset() { *m = ChunkCheckpointModel{} } +func (m *ChunkCheckpointModel) String() string { return proto.CompactTextString(m) } +func (*ChunkCheckpointModel) ProtoMessage() {} +func (*ChunkCheckpointModel) Descriptor() ([]byte, []int) { + return fileDescriptor_5c42c6f4a8e50df4, []int{4} +} +func (m *ChunkCheckpointModel) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ChunkCheckpointModel) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ChunkCheckpointModel.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ChunkCheckpointModel) XXX_Merge(src proto.Message) { + xxx_messageInfo_ChunkCheckpointModel.Merge(m, src) +} +func (m *ChunkCheckpointModel) XXX_Size() int { + return m.Size() +} +func (m *ChunkCheckpointModel) XXX_DiscardUnknown() { + xxx_messageInfo_ChunkCheckpointModel.DiscardUnknown(m) +} + +var xxx_messageInfo_ChunkCheckpointModel proto.InternalMessageInfo + +func init() { + proto.RegisterType((*CheckpointsModel)(nil), "CheckpointsModel") + proto.RegisterMapType((map[string]*TableCheckpointModel)(nil), "CheckpointsModel.CheckpointsEntry") + proto.RegisterType((*TaskCheckpointModel)(nil), "TaskCheckpointModel") + proto.RegisterType((*TableCheckpointModel)(nil), "TableCheckpointModel") + proto.RegisterMapType((map[int32]*EngineCheckpointModel)(nil), "TableCheckpointModel.EnginesEntry") + proto.RegisterType((*EngineCheckpointModel)(nil), "EngineCheckpointModel") + proto.RegisterMapType((map[string]*ChunkCheckpointModel)(nil), "EngineCheckpointModel.ChunksEntry") + proto.RegisterType((*ChunkCheckpointModel)(nil), "ChunkCheckpointModel") +} + +func init() { + proto.RegisterFile("pkg/lightning/checkpoints/file_checkpoints.proto", fileDescriptor_5c42c6f4a8e50df4) +} + +var fileDescriptor_5c42c6f4a8e50df4 = []byte{ + // 823 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x54, 0x4f, 0x6f, 0x23, 0x35, + 0x14, 0xef, 0x64, 0x9a, 0x34, 0x71, 0x92, 0x6e, 0x6a, 0xba, 0x8b, 0x29, 0x10, 0x85, 0x2c, 0x87, + 0x20, 0xd8, 0x54, 0x5a, 0x2e, 0xa8, 0x82, 0x03, 0x6d, 0x57, 0x62, 0x55, 0xad, 0xa8, 0xcc, 0xc2, + 0x81, 0xcb, 0xc8, 0x19, 0xbb, 0x99, 0xd1, 0xfc, 0xf1, 0xc8, 0xf6, 0x0c, 0x9b, 0xfd, 0x0e, 0x48, + 0x7c, 0x0c, 0xbe, 0x04, 0xf7, 0x15, 0xa7, 0x3d, 0x72, 0x84, 0xf6, 0xce, 0x67, 0x40, 0x7e, 0x9e, + 0x36, 0x93, 0x55, 0x54, 0x71, 0x7b, 0xef, 0xf7, 0x7e, 0xef, 0xe7, 0xf7, 0x9e, 0x9f, 0x8d, 0x4e, + 0x8a, 0x64, 0x79, 0x9c, 0xc6, 0xcb, 0xc8, 0xe4, 0x71, 0xde, 0xb4, 0xc2, 0x48, 0x84, 0x49, 0x21, + 0xe3, 0xdc, 0xe8, 0xe3, 0xab, 0x38, 0x15, 0x41, 0x03, 0x98, 0x17, 0x4a, 0x1a, 0x79, 0xf4, 0x64, + 0x19, 0x9b, 0xa8, 0x5c, 0xcc, 0x43, 0x99, 0x1d, 0x2f, 0xe5, 0x52, 0x1e, 0x03, 0xbc, 0x28, 0xaf, + 0xc0, 0x03, 0x07, 0x2c, 0x47, 0x9f, 0xfe, 0xeb, 0xa1, 0xd1, 0xd9, 0x5a, 0xe4, 0x85, 0xe4, 0x22, + 0xc5, 0xe7, 0xa8, 0xdf, 0x10, 0x26, 0xde, 0xc4, 0x9f, 0xf5, 0x9f, 0x4e, 0xe7, 0xef, 0xf2, 0x9a, + 0xc0, 0xb3, 0xdc, 0xa8, 0x15, 0x6d, 0xa6, 0xe1, 0x6f, 0xd0, 0x03, 0xc3, 0x74, 0xd2, 0xa8, 0x91, + 0xb4, 0x26, 0xde, 0xac, 0xff, 0xf4, 0x70, 0xfe, 0x92, 0xe9, 0x64, 0x9d, 0x0c, 0x62, 0x74, 0xdf, + 0x6c, 0x80, 0x47, 0x3f, 0x6e, 0x14, 0x06, 0xfa, 0x78, 0x84, 0xfc, 0x44, 0xac, 0x88, 0x37, 0xf1, + 0x66, 0x3d, 0x6a, 0x4d, 0xfc, 0x39, 0x6a, 0x57, 0x2c, 0x2d, 0x45, 0x2d, 0xfd, 0x70, 0xfe, 0x92, + 0x2d, 0x52, 0xf1, 0xae, 0xb6, 0xe3, 0x9c, 0xb4, 0xbe, 0xf2, 0xa6, 0xbf, 0xb7, 0xd0, 0x7b, 0x5b, + 0x8e, 0xc7, 0xef, 0xa3, 0x3d, 0xa8, 0x36, 0xe6, 0x20, 0xef, 0xd3, 0x8e, 0x75, 0x9f, 0x73, 0xfc, + 0x31, 0x42, 0x5a, 0x96, 0x2a, 0x14, 0x01, 0x8f, 0x15, 0x1c, 0xd3, 0xa3, 0x3d, 0x87, 0x9c, 0xc7, + 0x0a, 0x13, 0xb4, 0xb7, 0x60, 0x61, 0x22, 0x72, 0x4e, 0x7c, 0x88, 0xdd, 0xba, 0xf8, 0x31, 0x1a, + 0xc6, 0x59, 0x21, 0x95, 0x11, 0x2a, 0x60, 0x9c, 0x2b, 0xb2, 0x0b, 0xf1, 0xc1, 0x2d, 0xf8, 0x2d, + 0xe7, 0x0a, 0x7f, 0x88, 0x7a, 0x26, 0xe6, 0x8b, 0x20, 0x92, 0xda, 0x90, 0x36, 0x10, 0xba, 0x16, + 0xf8, 0x4e, 0x6a, 0x73, 0x17, 0xb4, 0x7c, 0xd2, 0x99, 0x78, 0xb3, 0xb6, 0x0b, 0x5e, 0x4a, 0x65, + 0x6c, 0xc1, 0x05, 0x77, 0xc2, 0x7b, 0x90, 0xd7, 0x29, 0x38, 0x48, 0x4e, 0xd1, 0x50, 0xdb, 0x03, + 0x78, 0x90, 0x54, 0x50, 0x73, 0x17, 0xc2, 0x7d, 0x07, 0x5e, 0x54, 0xb6, 0xea, 0xc7, 0x68, 0x78, + 0xb7, 0x55, 0x41, 0x25, 0x14, 0xe9, 0xb9, 0xda, 0xee, 0xc0, 0x9f, 0x84, 0x9a, 0xfe, 0xda, 0x42, + 0x87, 0xdb, 0xc6, 0x89, 0x31, 0xda, 0x8d, 0x98, 0x8e, 0x60, 0x50, 0x03, 0x0a, 0x36, 0x7e, 0x84, + 0x3a, 0xda, 0x30, 0x53, 0x6a, 0x18, 0xc3, 0x90, 0xd6, 0x9e, 0x1d, 0x1f, 0x4b, 0x53, 0x19, 0x06, + 0x0b, 0xa6, 0x05, 0x8c, 0xc0, 0xa7, 0x3d, 0x40, 0x4e, 0x99, 0x16, 0xf8, 0x6b, 0xb4, 0x27, 0xf2, + 0x65, 0x9c, 0x0b, 0x4d, 0xba, 0xf5, 0x9a, 0x6d, 0x3b, 0x72, 0xfe, 0xcc, 0x91, 0xdc, 0x9a, 0xdd, + 0xa6, 0xd8, 0xe1, 0x1b, 0xcb, 0x7e, 0x7e, 0x0e, 0x0d, 0xf8, 0xf4, 0xd6, 0x3d, 0xa2, 0x68, 0xd0, + 0x4c, 0x69, 0x6e, 0xce, 0x81, 0xdb, 0x9c, 0x2f, 0x36, 0x37, 0xe7, 0x51, 0x7d, 0xc4, 0x3d, 0xab, + 0xf3, 0x87, 0x87, 0x1e, 0x6e, 0x25, 0x35, 0x9a, 0xf7, 0x36, 0x9a, 0x3f, 0x41, 0x9d, 0x30, 0x2a, + 0xf3, 0x44, 0x93, 0x56, 0xdd, 0xdc, 0xd6, 0xfc, 0xf9, 0x19, 0x90, 0x5c, 0x73, 0x75, 0xc6, 0xd1, + 0x25, 0xea, 0x37, 0xe0, 0xff, 0xb3, 0xfa, 0x40, 0xbf, 0xa7, 0xfe, 0x3f, 0x7d, 0x74, 0xb8, 0x8d, + 0x63, 0xef, 0xb3, 0x60, 0x26, 0xaa, 0xc5, 0xc1, 0xb6, 0x2d, 0xc9, 0xab, 0x2b, 0x2d, 0xdc, 0xa3, + 0xf5, 0x69, 0xed, 0xe1, 0x27, 0x08, 0x87, 0x32, 0x2d, 0xb3, 0x3c, 0x28, 0x84, 0xca, 0x4a, 0xc3, + 0x4c, 0x2c, 0x73, 0x32, 0x98, 0xf8, 0xb3, 0x36, 0x3d, 0x70, 0x91, 0xcb, 0x75, 0xc0, 0x5e, 0xbf, + 0xc8, 0x79, 0x50, 0x4b, 0xb5, 0xdd, 0xf5, 0x8b, 0x9c, 0x7f, 0xef, 0xd4, 0x46, 0xc8, 0x2f, 0xa4, + 0x86, 0xdd, 0xf6, 0xa9, 0x35, 0xf1, 0xa7, 0x68, 0xbf, 0x50, 0xa2, 0x0a, 0x94, 0xfc, 0x25, 0xe6, + 0x41, 0xc6, 0x5e, 0xc1, 0x76, 0xfb, 0x74, 0x60, 0x51, 0x6a, 0xc1, 0x17, 0xec, 0x95, 0x7d, 0x19, + 0x6b, 0x42, 0x17, 0x08, 0x5d, 0xd5, 0x08, 0x26, 0x55, 0x18, 0x2c, 0x56, 0x46, 0x68, 0xd8, 0x8b, + 0x5d, 0xda, 0x4d, 0xaa, 0xf0, 0xd4, 0xfa, 0xf6, 0xd9, 0xd8, 0x60, 0x52, 0x69, 0x82, 0x20, 0xd4, + 0x49, 0xaa, 0xf0, 0xa2, 0xd2, 0xf8, 0x13, 0x34, 0xb0, 0x01, 0xf8, 0xad, 0x74, 0x99, 0x91, 0xfe, + 0xc4, 0x9b, 0x75, 0x68, 0x3f, 0xa9, 0xc2, 0xb3, 0x1a, 0xc2, 0x1f, 0xd9, 0xf7, 0x98, 0x09, 0x6d, + 0x58, 0x56, 0x90, 0xe1, 0xc4, 0x9b, 0x8d, 0xe8, 0x1a, 0xb0, 0x53, 0x34, 0xab, 0x42, 0x90, 0x7d, + 0x78, 0xa8, 0x60, 0xe3, 0x09, 0xea, 0x87, 0x32, 0x2b, 0x94, 0xd0, 0xda, 0x8e, 0xe9, 0x01, 0x84, + 0x9a, 0x10, 0xfe, 0x00, 0x75, 0xed, 0xc3, 0x0c, 0xec, 0xe5, 0x8e, 0xdc, 0x07, 0x62, 0xfd, 0x0b, + 0xb1, 0xb2, 0x7d, 0xc0, 0x27, 0xaf, 0xe3, 0xd7, 0x82, 0x1c, 0xb8, 0x26, 0x2d, 0xf0, 0x43, 0xfc, + 0x5a, 0x9c, 0x7e, 0xf6, 0xe6, 0x9f, 0xf1, 0xce, 0x9b, 0xeb, 0xb1, 0xf7, 0xf6, 0x7a, 0xec, 0xfd, + 0x7d, 0x3d, 0xf6, 0x7e, 0xbb, 0x19, 0xef, 0xbc, 0xbd, 0x19, 0xef, 0xfc, 0x75, 0x33, 0xde, 0xf9, + 0xb9, 0xf9, 0x11, 0x2f, 0x3a, 0xf0, 0xd5, 0x7f, 0xf9, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc2, + 0xdb, 0x56, 0x3e, 0x57, 0x06, 0x00, 0x00, +} + +func (m *CheckpointsModel) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *CheckpointsModel) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *CheckpointsModel) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.TaskCheckpoint != nil { + { + size, err := m.TaskCheckpoint.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintFileCheckpoints(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Checkpoints) > 0 { + for k := range m.Checkpoints { + v := m.Checkpoints[k] + baseI := i + if v != nil { + { + size, err := v.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintFileCheckpoints(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + i -= len(k) + copy(dAtA[i:], k) + i = encodeVarintFileCheckpoints(dAtA, i, uint64(len(k))) + i-- + dAtA[i] = 0xa + i = encodeVarintFileCheckpoints(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *TaskCheckpointModel) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TaskCheckpointModel) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TaskCheckpointModel) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.LightningVer) > 0 { + i -= len(m.LightningVer) + copy(dAtA[i:], m.LightningVer) + i = encodeVarintFileCheckpoints(dAtA, i, uint64(len(m.LightningVer))) + i-- + dAtA[i] = 0x4a + } + if len(m.SortedKvDir) > 0 { + i -= len(m.SortedKvDir) + copy(dAtA[i:], m.SortedKvDir) + i = encodeVarintFileCheckpoints(dAtA, i, uint64(len(m.SortedKvDir))) + i-- + dAtA[i] = 0x42 + } + if len(m.PdAddr) > 0 { + i -= len(m.PdAddr) + copy(dAtA[i:], m.PdAddr) + i = encodeVarintFileCheckpoints(dAtA, i, uint64(len(m.PdAddr))) + i-- + dAtA[i] = 0x3a + } + if m.TidbPort != 0 { + i = encodeVarintFileCheckpoints(dAtA, i, uint64(m.TidbPort)) + i-- + dAtA[i] = 0x30 + } + if len(m.TidbHost) > 0 { + i -= len(m.TidbHost) + copy(dAtA[i:], m.TidbHost) + i = encodeVarintFileCheckpoints(dAtA, i, uint64(len(m.TidbHost))) + i-- + dAtA[i] = 0x2a + } + if len(m.ImporterAddr) > 0 { + i -= len(m.ImporterAddr) + copy(dAtA[i:], m.ImporterAddr) + i = encodeVarintFileCheckpoints(dAtA, i, uint64(len(m.ImporterAddr))) + i-- + dAtA[i] = 0x22 + } + if len(m.Backend) > 0 { + i -= len(m.Backend) + copy(dAtA[i:], m.Backend) + i = encodeVarintFileCheckpoints(dAtA, i, uint64(len(m.Backend))) + i-- + dAtA[i] = 0x1a + } + if len(m.SourceDir) > 0 { + i -= len(m.SourceDir) + copy(dAtA[i:], m.SourceDir) + i = encodeVarintFileCheckpoints(dAtA, i, uint64(len(m.SourceDir))) + i-- + dAtA[i] = 0x12 + } + if m.TaskId != 0 { + i = encodeVarintFileCheckpoints(dAtA, i, uint64(m.TaskId)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *TableCheckpointModel) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TableCheckpointModel) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TableCheckpointModel) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.TableID != 0 { + i = encodeVarintFileCheckpoints(dAtA, i, uint64(m.TableID)) + i-- + dAtA[i] = 0x48 + } + if len(m.Engines) > 0 { + for k := range m.Engines { + v := m.Engines[k] + baseI := i + if v != nil { + { + size, err := v.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintFileCheckpoints(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + i = encodeVarintFileCheckpoints(dAtA, i, uint64((uint32(k)<<1)^uint32((k>>31)))) + i-- + dAtA[i] = 0x8 + i = encodeVarintFileCheckpoints(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0x42 + } + } + if m.AllocBase != 0 { + i = encodeVarintFileCheckpoints(dAtA, i, uint64(m.AllocBase)) + i-- + dAtA[i] = 0x20 + } + if m.Status != 0 { + i = encodeVarintFileCheckpoints(dAtA, i, uint64(m.Status)) + i-- + dAtA[i] = 0x18 + } + if len(m.Hash) > 0 { + i -= len(m.Hash) + copy(dAtA[i:], m.Hash) + i = encodeVarintFileCheckpoints(dAtA, i, uint64(len(m.Hash))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *EngineCheckpointModel) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EngineCheckpointModel) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EngineCheckpointModel) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Chunks) > 0 { + for k := range m.Chunks { + v := m.Chunks[k] + baseI := i + if v != nil { + { + size, err := v.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintFileCheckpoints(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + i -= len(k) + copy(dAtA[i:], k) + i = encodeVarintFileCheckpoints(dAtA, i, uint64(len(k))) + i-- + dAtA[i] = 0xa + i = encodeVarintFileCheckpoints(dAtA, i, uint64(baseI-i)) + i-- + dAtA[i] = 0x12 + } + } + if m.Status != 0 { + i = encodeVarintFileCheckpoints(dAtA, i, uint64(m.Status)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *ChunkCheckpointModel) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ChunkCheckpointModel) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ChunkCheckpointModel) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.FileSize != 0 { + i = encodeVarintFileCheckpoints(dAtA, i, uint64(m.FileSize)) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x88 + } + if len(m.SortKey) > 0 { + i -= len(m.SortKey) + copy(dAtA[i:], m.SortKey) + i = encodeVarintFileCheckpoints(dAtA, i, uint64(len(m.SortKey))) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x82 + } + if m.Compression != 0 { + i = encodeVarintFileCheckpoints(dAtA, i, uint64(m.Compression)) + i-- + dAtA[i] = 0x78 + } + if m.Type != 0 { + i = encodeVarintFileCheckpoints(dAtA, i, uint64(m.Type)) + i-- + dAtA[i] = 0x70 + } + if m.Timestamp != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.Timestamp)) + i-- + dAtA[i] = 0x69 + } + if len(m.ColumnPermutation) > 0 { + dAtA6 := make([]byte, len(m.ColumnPermutation)*10) + var j5 int + for _, num1 := range m.ColumnPermutation { + num := uint64(num1) + for num >= 1<<7 { + dAtA6[j5] = uint8(uint64(num)&0x7f | 0x80) + num >>= 7 + j5++ + } + dAtA6[j5] = uint8(num) + j5++ + } + i -= j5 + copy(dAtA[i:], dAtA6[:j5]) + i = encodeVarintFileCheckpoints(dAtA, i, uint64(j5)) + i-- + dAtA[i] = 0x62 + } + if m.KvcChecksum != 0 { + i -= 8 + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(m.KvcChecksum)) + i-- + dAtA[i] = 0x59 + } + if m.KvcKvs != 0 { + i = encodeVarintFileCheckpoints(dAtA, i, uint64(m.KvcKvs)) + i-- + dAtA[i] = 0x50 + } + if m.KvcBytes != 0 { + i = encodeVarintFileCheckpoints(dAtA, i, uint64(m.KvcBytes)) + i-- + dAtA[i] = 0x48 + } + if m.RowidMax != 0 { + i = encodeVarintFileCheckpoints(dAtA, i, uint64(m.RowidMax)) + i-- + dAtA[i] = 0x40 + } + if m.PrevRowidMax != 0 { + i = encodeVarintFileCheckpoints(dAtA, i, uint64(m.PrevRowidMax)) + i-- + dAtA[i] = 0x38 + } + if m.Pos != 0 { + i = encodeVarintFileCheckpoints(dAtA, i, uint64(m.Pos)) + i-- + dAtA[i] = 0x30 + } + if m.EndOffset != 0 { + i = encodeVarintFileCheckpoints(dAtA, i, uint64(m.EndOffset)) + i-- + dAtA[i] = 0x28 + } + if m.Offset != 0 { + i = encodeVarintFileCheckpoints(dAtA, i, uint64(m.Offset)) + i-- + dAtA[i] = 0x10 + } + if len(m.Path) > 0 { + i -= len(m.Path) + copy(dAtA[i:], m.Path) + i = encodeVarintFileCheckpoints(dAtA, i, uint64(len(m.Path))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintFileCheckpoints(dAtA []byte, offset int, v uint64) int { + offset -= sovFileCheckpoints(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *CheckpointsModel) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Checkpoints) > 0 { + for k, v := range m.Checkpoints { + _ = k + _ = v + l = 0 + if v != nil { + l = v.Size() + l += 1 + sovFileCheckpoints(uint64(l)) + } + mapEntrySize := 1 + len(k) + sovFileCheckpoints(uint64(len(k))) + l + n += mapEntrySize + 1 + sovFileCheckpoints(uint64(mapEntrySize)) + } + } + if m.TaskCheckpoint != nil { + l = m.TaskCheckpoint.Size() + n += 1 + l + sovFileCheckpoints(uint64(l)) + } + return n +} + +func (m *TaskCheckpointModel) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.TaskId != 0 { + n += 1 + sovFileCheckpoints(uint64(m.TaskId)) + } + l = len(m.SourceDir) + if l > 0 { + n += 1 + l + sovFileCheckpoints(uint64(l)) + } + l = len(m.Backend) + if l > 0 { + n += 1 + l + sovFileCheckpoints(uint64(l)) + } + l = len(m.ImporterAddr) + if l > 0 { + n += 1 + l + sovFileCheckpoints(uint64(l)) + } + l = len(m.TidbHost) + if l > 0 { + n += 1 + l + sovFileCheckpoints(uint64(l)) + } + if m.TidbPort != 0 { + n += 1 + sovFileCheckpoints(uint64(m.TidbPort)) + } + l = len(m.PdAddr) + if l > 0 { + n += 1 + l + sovFileCheckpoints(uint64(l)) + } + l = len(m.SortedKvDir) + if l > 0 { + n += 1 + l + sovFileCheckpoints(uint64(l)) + } + l = len(m.LightningVer) + if l > 0 { + n += 1 + l + sovFileCheckpoints(uint64(l)) + } + return n +} + +func (m *TableCheckpointModel) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Hash) + if l > 0 { + n += 1 + l + sovFileCheckpoints(uint64(l)) + } + if m.Status != 0 { + n += 1 + sovFileCheckpoints(uint64(m.Status)) + } + if m.AllocBase != 0 { + n += 1 + sovFileCheckpoints(uint64(m.AllocBase)) + } + if len(m.Engines) > 0 { + for k, v := range m.Engines { + _ = k + _ = v + l = 0 + if v != nil { + l = v.Size() + l += 1 + sovFileCheckpoints(uint64(l)) + } + mapEntrySize := 1 + sozFileCheckpoints(uint64(k)) + l + n += mapEntrySize + 1 + sovFileCheckpoints(uint64(mapEntrySize)) + } + } + if m.TableID != 0 { + n += 1 + sovFileCheckpoints(uint64(m.TableID)) + } + return n +} + +func (m *EngineCheckpointModel) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Status != 0 { + n += 1 + sovFileCheckpoints(uint64(m.Status)) + } + if len(m.Chunks) > 0 { + for k, v := range m.Chunks { + _ = k + _ = v + l = 0 + if v != nil { + l = v.Size() + l += 1 + sovFileCheckpoints(uint64(l)) + } + mapEntrySize := 1 + len(k) + sovFileCheckpoints(uint64(len(k))) + l + n += mapEntrySize + 1 + sovFileCheckpoints(uint64(mapEntrySize)) + } + } + return n +} + +func (m *ChunkCheckpointModel) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Path) + if l > 0 { + n += 1 + l + sovFileCheckpoints(uint64(l)) + } + if m.Offset != 0 { + n += 1 + sovFileCheckpoints(uint64(m.Offset)) + } + if m.EndOffset != 0 { + n += 1 + sovFileCheckpoints(uint64(m.EndOffset)) + } + if m.Pos != 0 { + n += 1 + sovFileCheckpoints(uint64(m.Pos)) + } + if m.PrevRowidMax != 0 { + n += 1 + sovFileCheckpoints(uint64(m.PrevRowidMax)) + } + if m.RowidMax != 0 { + n += 1 + sovFileCheckpoints(uint64(m.RowidMax)) + } + if m.KvcBytes != 0 { + n += 1 + sovFileCheckpoints(uint64(m.KvcBytes)) + } + if m.KvcKvs != 0 { + n += 1 + sovFileCheckpoints(uint64(m.KvcKvs)) + } + if m.KvcChecksum != 0 { + n += 9 + } + if len(m.ColumnPermutation) > 0 { + l = 0 + for _, e := range m.ColumnPermutation { + l += sovFileCheckpoints(uint64(e)) + } + n += 1 + sovFileCheckpoints(uint64(l)) + l + } + if m.Timestamp != 0 { + n += 9 + } + if m.Type != 0 { + n += 1 + sovFileCheckpoints(uint64(m.Type)) + } + if m.Compression != 0 { + n += 1 + sovFileCheckpoints(uint64(m.Compression)) + } + l = len(m.SortKey) + if l > 0 { + n += 2 + l + sovFileCheckpoints(uint64(l)) + } + if m.FileSize != 0 { + n += 2 + sovFileCheckpoints(uint64(m.FileSize)) + } + return n +} + +func sovFileCheckpoints(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozFileCheckpoints(x uint64) (n int) { + return sovFileCheckpoints(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *CheckpointsModel) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CheckpointsModel: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CheckpointsModel: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Checkpoints", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthFileCheckpoints + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthFileCheckpoints + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Checkpoints == nil { + m.Checkpoints = make(map[string]*TableCheckpointModel) + } + var mapkey string + var mapvalue *TableCheckpointModel + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthFileCheckpoints + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return ErrInvalidLengthFileCheckpoints + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var mapmsglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapmsglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if mapmsglen < 0 { + return ErrInvalidLengthFileCheckpoints + } + postmsgIndex := iNdEx + mapmsglen + if postmsgIndex < 0 { + return ErrInvalidLengthFileCheckpoints + } + if postmsgIndex > l { + return io.ErrUnexpectedEOF + } + mapvalue = &TableCheckpointModel{} + if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil { + return err + } + iNdEx = postmsgIndex + } else { + iNdEx = entryPreIndex + skippy, err := skipFileCheckpoints(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthFileCheckpoints + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.Checkpoints[mapkey] = mapvalue + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TaskCheckpoint", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthFileCheckpoints + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthFileCheckpoints + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.TaskCheckpoint == nil { + m.TaskCheckpoint = &TaskCheckpointModel{} + } + if err := m.TaskCheckpoint.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipFileCheckpoints(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthFileCheckpoints + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthFileCheckpoints + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TaskCheckpointModel) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TaskCheckpointModel: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TaskCheckpointModel: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TaskId", wireType) + } + m.TaskId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TaskId |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SourceDir", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthFileCheckpoints + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthFileCheckpoints + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SourceDir = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Backend", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthFileCheckpoints + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthFileCheckpoints + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Backend = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ImporterAddr", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthFileCheckpoints + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthFileCheckpoints + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ImporterAddr = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TidbHost", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthFileCheckpoints + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthFileCheckpoints + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.TidbHost = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TidbPort", wireType) + } + m.TidbPort = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TidbPort |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PdAddr", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthFileCheckpoints + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthFileCheckpoints + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PdAddr = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SortedKvDir", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthFileCheckpoints + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthFileCheckpoints + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SortedKvDir = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LightningVer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthFileCheckpoints + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthFileCheckpoints + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.LightningVer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipFileCheckpoints(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthFileCheckpoints + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthFileCheckpoints + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TableCheckpointModel) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TableCheckpointModel: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TableCheckpointModel: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Hash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthFileCheckpoints + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthFileCheckpoints + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Hash = append(m.Hash[:0], dAtA[iNdEx:postIndex]...) + if m.Hash == nil { + m.Hash = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + m.Status = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Status |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AllocBase", wireType) + } + m.AllocBase = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.AllocBase |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Engines", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthFileCheckpoints + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthFileCheckpoints + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Engines == nil { + m.Engines = make(map[int32]*EngineCheckpointModel) + } + var mapkey int32 + var mapvalue *EngineCheckpointModel + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var mapkeytemp int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapkeytemp |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + mapkeytemp = int32((uint32(mapkeytemp) >> 1) ^ uint32(((mapkeytemp&1)<<31)>>31)) + mapkey = int32(mapkeytemp) + } else if fieldNum == 2 { + var mapmsglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapmsglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if mapmsglen < 0 { + return ErrInvalidLengthFileCheckpoints + } + postmsgIndex := iNdEx + mapmsglen + if postmsgIndex < 0 { + return ErrInvalidLengthFileCheckpoints + } + if postmsgIndex > l { + return io.ErrUnexpectedEOF + } + mapvalue = &EngineCheckpointModel{} + if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil { + return err + } + iNdEx = postmsgIndex + } else { + iNdEx = entryPreIndex + skippy, err := skipFileCheckpoints(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthFileCheckpoints + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.Engines[mapkey] = mapvalue + iNdEx = postIndex + case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TableID", wireType) + } + m.TableID = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TableID |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipFileCheckpoints(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthFileCheckpoints + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthFileCheckpoints + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EngineCheckpointModel) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EngineCheckpointModel: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EngineCheckpointModel: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) + } + m.Status = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Status |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Chunks", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthFileCheckpoints + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthFileCheckpoints + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Chunks == nil { + m.Chunks = make(map[string]*ChunkCheckpointModel) + } + var mapkey string + var mapvalue *ChunkCheckpointModel + for iNdEx < postIndex { + entryPreIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + if fieldNum == 1 { + var stringLenmapkey uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLenmapkey |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLenmapkey := int(stringLenmapkey) + if intStringLenmapkey < 0 { + return ErrInvalidLengthFileCheckpoints + } + postStringIndexmapkey := iNdEx + intStringLenmapkey + if postStringIndexmapkey < 0 { + return ErrInvalidLengthFileCheckpoints + } + if postStringIndexmapkey > l { + return io.ErrUnexpectedEOF + } + mapkey = string(dAtA[iNdEx:postStringIndexmapkey]) + iNdEx = postStringIndexmapkey + } else if fieldNum == 2 { + var mapmsglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + mapmsglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if mapmsglen < 0 { + return ErrInvalidLengthFileCheckpoints + } + postmsgIndex := iNdEx + mapmsglen + if postmsgIndex < 0 { + return ErrInvalidLengthFileCheckpoints + } + if postmsgIndex > l { + return io.ErrUnexpectedEOF + } + mapvalue = &ChunkCheckpointModel{} + if err := mapvalue.Unmarshal(dAtA[iNdEx:postmsgIndex]); err != nil { + return err + } + iNdEx = postmsgIndex + } else { + iNdEx = entryPreIndex + skippy, err := skipFileCheckpoints(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthFileCheckpoints + } + if (iNdEx + skippy) > postIndex { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + m.Chunks[mapkey] = mapvalue + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipFileCheckpoints(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthFileCheckpoints + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthFileCheckpoints + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ChunkCheckpointModel) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ChunkCheckpointModel: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ChunkCheckpointModel: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Path", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthFileCheckpoints + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthFileCheckpoints + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Path = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Offset", wireType) + } + m.Offset = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Offset |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EndOffset", wireType) + } + m.EndOffset = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.EndOffset |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Pos", wireType) + } + m.Pos = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Pos |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PrevRowidMax", wireType) + } + m.PrevRowidMax = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PrevRowidMax |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RowidMax", wireType) + } + m.RowidMax = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.RowidMax |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field KvcBytes", wireType) + } + m.KvcBytes = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.KvcBytes |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field KvcKvs", wireType) + } + m.KvcKvs = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.KvcKvs |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 11: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field KvcChecksum", wireType) + } + m.KvcChecksum = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.KvcChecksum = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 12: + if wireType == 0 { + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.ColumnPermutation = append(m.ColumnPermutation, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthFileCheckpoints + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthFileCheckpoints + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + var count int + for _, integer := range dAtA[iNdEx:postIndex] { + if integer < 128 { + count++ + } + } + elementCount = count + if elementCount != 0 && len(m.ColumnPermutation) == 0 { + m.ColumnPermutation = make([]int32, 0, elementCount) + } + for iNdEx < postIndex { + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.ColumnPermutation = append(m.ColumnPermutation, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field ColumnPermutation", wireType) + } + case 13: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + m.Timestamp = int64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + case 14: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + m.Type = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Type |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 15: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Compression", wireType) + } + m.Compression = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Compression |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 16: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SortKey", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthFileCheckpoints + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthFileCheckpoints + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SortKey = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 17: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FileSize", wireType) + } + m.FileSize = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.FileSize |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipFileCheckpoints(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthFileCheckpoints + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthFileCheckpoints + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipFileCheckpoints(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowFileCheckpoints + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthFileCheckpoints + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupFileCheckpoints + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthFileCheckpoints + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthFileCheckpoints = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowFileCheckpoints = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupFileCheckpoints = fmt.Errorf("proto: unexpected end of group") +) diff --git a/pkg/lightning/checkpoints/file_checkpoints.proto b/pkg/lightning/checkpoints/file_checkpoints.proto new file mode 100644 index 000000000..82166877f --- /dev/null +++ b/pkg/lightning/checkpoints/file_checkpoints.proto @@ -0,0 +1,69 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; + +option go_package = "checkpoints"; +option (gogoproto.goproto_getters_all) = false; + +message CheckpointsModel { + // key is table_name + map checkpoints = 1; + TaskCheckpointModel task_checkpoint = 2; +} + +message TaskCheckpointModel { + int64 task_id = 1; + string source_dir = 2; + string backend = 3; + string importer_addr = 4; + string tidb_host = 5; + int32 tidb_port = 6; + string pd_addr = 7; + string sorted_kv_dir = 8; + string lightning_ver = 9; +} + +message TableCheckpointModel { + bytes hash = 1; + uint32 status = 3; + int64 alloc_base = 4; + map engines = 8; + int64 tableID = 9; +} + +message EngineCheckpointModel { + uint32 status = 1; + // key is "$path:$offset" + map chunks = 2; +} + +message ChunkCheckpointModel { + string path = 1; + int64 offset = 2; + repeated int32 column_permutation = 12; + int64 end_offset = 5; + int64 pos = 6; + int64 prev_rowid_max = 7; + int64 rowid_max = 8; + uint64 kvc_bytes = 9; + uint64 kvc_kvs = 10; + fixed64 kvc_checksum = 11; + sfixed64 timestamp = 13; + int32 type = 14; + int32 compression = 15; + string sort_key = 16; + int64 file_size = 17; +} diff --git a/pkg/lightning/checkpoints/glue_checkpoint.go b/pkg/lightning/checkpoints/glue_checkpoint.go new file mode 100644 index 000000000..ef9edf7e5 --- /dev/null +++ b/pkg/lightning/checkpoints/glue_checkpoint.go @@ -0,0 +1,797 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package checkpoints + +import ( + "context" + "encoding/json" + "fmt" + "io" + "strings" + + "github.com/pingcap/errors" + "github.com/pingcap/parser/ast" + "github.com/pingcap/tidb/types" + "github.com/pingcap/tidb/util/chunk" + "github.com/pingcap/tidb/util/sqlexec" + "go.uber.org/zap" + + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/config" + "github.com/pingcap/br/pkg/lightning/log" + "github.com/pingcap/br/pkg/lightning/mydump" + verify "github.com/pingcap/br/pkg/lightning/verification" +) + +type Session interface { + Close() + Execute(context.Context, string) ([]sqlexec.RecordSet, error) + CommitTxn(context.Context) error + RollbackTxn(context.Context) + PrepareStmt(sql string) (stmtID uint32, paramCount int, fields []*ast.ResultField, err error) + ExecutePreparedStmt(ctx context.Context, stmtID uint32, param []types.Datum) (sqlexec.RecordSet, error) + DropPreparedStmt(stmtID uint32) error +} + +// GlueCheckpointsDB is almost same with MySQLCheckpointsDB, but it uses TiDB's internal data structure which requires a +// lot to keep same with database/sql. +// TODO: Encapsulate Begin/Commit/Rollback txn, form SQL with args and query/iter/scan TiDB's RecordSet into a interface +// to reuse MySQLCheckpointsDB. +type GlueCheckpointsDB struct { + // getSessionFunc will get a new session from TiDB + getSessionFunc func() (Session, error) + schema string +} + +var _ CheckpointsDB = (*GlueCheckpointsDB)(nil) + +func NewGlueCheckpointsDB(ctx context.Context, se Session, f func() (Session, error), schemaName string) (*GlueCheckpointsDB, error) { + var escapedSchemaName strings.Builder + common.WriteMySQLIdentifier(&escapedSchemaName, schemaName) + schema := escapedSchemaName.String() + logger := log.With(zap.String("schema", schemaName)) + + sql := fmt.Sprintf(CreateDBTemplate, schema) + err := common.Retry("create checkpoints database", logger, func() error { + _, err := se.Execute(ctx, sql) + return err + }) + if err != nil { + return nil, errors.Trace(err) + } + + sql = fmt.Sprintf(CreateTaskTableTemplate, schema, CheckpointTableNameTask) + err = common.Retry("create task checkpoints table", logger, func() error { + _, err := se.Execute(ctx, sql) + return err + }) + if err != nil { + return nil, errors.Trace(err) + } + + sql = fmt.Sprintf(CreateTableTableTemplate, schema, CheckpointTableNameTable) + err = common.Retry("create table checkpoints table", logger, func() error { + _, err := se.Execute(ctx, sql) + return err + }) + if err != nil { + return nil, errors.Trace(err) + } + + sql = fmt.Sprintf(CreateEngineTableTemplate, schema, CheckpointTableNameEngine) + err = common.Retry("create engine checkpoints table", logger, func() error { + _, err := se.Execute(ctx, sql) + return err + }) + if err != nil { + return nil, errors.Trace(err) + } + + sql = fmt.Sprintf(CreateChunkTableTemplate, schema, CheckpointTableNameChunk) + err = common.Retry("create chunks checkpoints table", logger, func() error { + _, err := se.Execute(ctx, sql) + return err + }) + if err != nil { + return nil, errors.Trace(err) + } + + return &GlueCheckpointsDB{ + getSessionFunc: f, + schema: schema, + }, nil +} + +func (g GlueCheckpointsDB) Initialize(ctx context.Context, cfg *config.Config, dbInfo map[string]*TidbDBInfo) error { + logger := log.L() + se, err := g.getSessionFunc() + if err != nil { + return errors.Trace(err) + } + defer se.Close() + + err = Transact(ctx, "insert checkpoints", se, logger, func(c context.Context, s Session) error { + stmtID, _, _, err := s.PrepareStmt(fmt.Sprintf(InitTaskTemplate, g.schema, CheckpointTableNameTask)) + if err != nil { + return errors.Trace(err) + } + defer s.DropPreparedStmt(stmtID) + _, err = s.ExecutePreparedStmt(c, stmtID, []types.Datum{ + types.NewIntDatum(cfg.TaskID), + types.NewStringDatum(cfg.Mydumper.SourceDir), + types.NewStringDatum(cfg.TikvImporter.Backend), + types.NewStringDatum(cfg.TikvImporter.Addr), + types.NewStringDatum(cfg.TiDB.Host), + types.NewIntDatum(int64(cfg.TiDB.Port)), + types.NewStringDatum(cfg.TiDB.PdAddr), + types.NewStringDatum(cfg.TikvImporter.SortedKVDir), + types.NewStringDatum(common.ReleaseVersion), + }) + if err != nil { + return errors.Trace(err) + } + + stmtID2, _, _, err := s.PrepareStmt(fmt.Sprintf(InitTableTemplate, g.schema, CheckpointTableNameTable)) + if err != nil { + return errors.Trace(err) + } + defer s.DropPreparedStmt(stmtID2) + + for _, db := range dbInfo { + for _, table := range db.Tables { + tableName := common.UniqueTable(db.Name, table.Name) + _, err = s.ExecutePreparedStmt(c, stmtID2, []types.Datum{ + types.NewIntDatum(cfg.TaskID), + types.NewStringDatum(tableName), + types.NewIntDatum(0), + types.NewIntDatum(table.ID), + }) + if err != nil { + return errors.Trace(err) + } + } + } + return nil + }) + return errors.Trace(err) +} + +func (g GlueCheckpointsDB) TaskCheckpoint(ctx context.Context) (*TaskCheckpoint, error) { + logger := log.L() + sql := fmt.Sprintf(ReadTaskTemplate, g.schema, CheckpointTableNameTask) + se, err := g.getSessionFunc() + if err != nil { + return nil, errors.Trace(err) + } + defer se.Close() + + var taskCp *TaskCheckpoint + err = common.Retry("fetch task checkpoint", logger, func() error { + rs, err := se.Execute(ctx, sql) + if err != nil { + return errors.Trace(err) + } + r := rs[0] + defer r.Close() + req := r.NewChunk() + err = r.Next(ctx, req) + if err != nil { + return err + } + if req.NumRows() == 0 { + return nil + } + + row := req.GetRow(0) + taskCp = &TaskCheckpoint{} + taskCp.TaskId = row.GetInt64(0) + taskCp.SourceDir = row.GetString(1) + taskCp.Backend = row.GetString(2) + taskCp.ImporterAddr = row.GetString(3) + taskCp.TiDBHost = row.GetString(4) + taskCp.TiDBPort = int(row.GetInt64(5)) + taskCp.PdAddr = row.GetString(6) + taskCp.SortedKVDir = row.GetString(7) + taskCp.LightningVer = row.GetString(8) + return nil + }) + if err != nil { + return nil, errors.Trace(err) + } + return taskCp, nil +} + +func (g GlueCheckpointsDB) Get(ctx context.Context, tableName string) (*TableCheckpoint, error) { + cp := &TableCheckpoint{ + Engines: map[int32]*EngineCheckpoint{}, + } + logger := log.With(zap.String("table", tableName)) + se, err := g.getSessionFunc() + if err != nil { + return nil, errors.Trace(err) + } + defer se.Close() + + tableName = common.InterpolateMySQLString(tableName) + err = Transact(ctx, "read checkpoint", se, logger, func(c context.Context, s Session) error { + // 1. Populate the engines. + sql := fmt.Sprintf(ReadEngineTemplate, g.schema, CheckpointTableNameEngine) + sql = strings.ReplaceAll(sql, "?", tableName) + rs, err := s.Execute(ctx, sql) + if err != nil { + return errors.Trace(err) + } + r := rs[0] + req := r.NewChunk() + it := chunk.NewIterator4Chunk(req) + for { + err = r.Next(ctx, req) + if err != nil { + r.Close() + return err + } + if req.NumRows() == 0 { + break + } + + for row := it.Begin(); row != it.End(); row = it.Next() { + engineID := int32(row.GetInt64(0)) + status := uint8(row.GetUint64(1)) + cp.Engines[engineID] = &EngineCheckpoint{ + Status: CheckpointStatus(status), + } + } + } + r.Close() + + // 2. Populate the chunks. + sql = fmt.Sprintf(ReadChunkTemplate, g.schema, CheckpointTableNameChunk) + sql = strings.ReplaceAll(sql, "?", tableName) + rs, err = s.Execute(ctx, sql) + if err != nil { + return errors.Trace(err) + } + r = rs[0] + req = r.NewChunk() + it = chunk.NewIterator4Chunk(req) + for { + err = r.Next(ctx, req) + if err != nil { + r.Close() + return err + } + if req.NumRows() == 0 { + break + } + + for row := it.Begin(); row != it.End(); row = it.Next() { + value := &ChunkCheckpoint{} + engineID := int32(row.GetInt64(0)) + value.Key.Path = row.GetString(1) + value.Key.Offset = row.GetInt64(2) + value.FileMeta.Type = mydump.SourceType(row.GetInt64(3)) + value.FileMeta.Compression = mydump.Compression(row.GetInt64(4)) + value.FileMeta.SortKey = row.GetString(5) + value.FileMeta.FileSize = row.GetInt64(6) + colPerm := row.GetBytes(7) + value.Chunk.Offset = row.GetInt64(8) + value.Chunk.EndOffset = row.GetInt64(9) + value.Chunk.PrevRowIDMax = row.GetInt64(10) + value.Chunk.RowIDMax = row.GetInt64(11) + kvcBytes := row.GetUint64(12) + kvcKVs := row.GetUint64(13) + kvcChecksum := row.GetUint64(14) + value.Timestamp = row.GetInt64(15) + + value.FileMeta.Path = value.Key.Path + value.Checksum = verify.MakeKVChecksum(kvcBytes, kvcKVs, kvcChecksum) + if err := json.Unmarshal(colPerm, &value.ColumnPermutation); err != nil { + r.Close() + return errors.Trace(err) + } + cp.Engines[engineID].Chunks = append(cp.Engines[engineID].Chunks, value) + } + } + r.Close() + + // 3. Fill in the remaining table info + sql = fmt.Sprintf(ReadTableRemainTemplate, g.schema, CheckpointTableNameTable) + sql = strings.ReplaceAll(sql, "?", tableName) + rs, err = s.Execute(ctx, sql) + if err != nil { + return errors.Trace(err) + } + r = rs[0] + defer r.Close() + req = r.NewChunk() + err = r.Next(ctx, req) + if err != nil { + return err + } + if req.NumRows() == 0 { + return nil + } + + row := req.GetRow(0) + cp.Status = CheckpointStatus(row.GetUint64(0)) + cp.AllocBase = row.GetInt64(1) + cp.TableID = row.GetInt64(2) + return nil + }) + + if err != nil { + return nil, errors.Trace(err) + } + + return cp, nil +} + +func (g GlueCheckpointsDB) Close() error { + return nil +} + +func (g GlueCheckpointsDB) InsertEngineCheckpoints(ctx context.Context, tableName string, checkpointMap map[int32]*EngineCheckpoint) error { + logger := log.With(zap.String("table", tableName)) + se, err := g.getSessionFunc() + if err != nil { + return errors.Trace(err) + } + defer se.Close() + + err = Transact(ctx, "update engine checkpoints", se, logger, func(c context.Context, s Session) error { + engineStmt, _, _, err := s.PrepareStmt(fmt.Sprintf(ReplaceEngineTemplate, g.schema, CheckpointTableNameEngine)) + if err != nil { + return errors.Trace(err) + } + defer s.DropPreparedStmt(engineStmt) + + chunkStmt, _, _, err := s.PrepareStmt(fmt.Sprintf(ReplaceChunkTemplate, g.schema, CheckpointTableNameChunk)) + if err != nil { + return errors.Trace(err) + } + defer s.DropPreparedStmt(chunkStmt) + + for engineID, engine := range checkpointMap { + _, err := s.ExecutePreparedStmt(c, engineStmt, []types.Datum{ + types.NewStringDatum(tableName), + types.NewIntDatum(int64(engineID)), + types.NewUintDatum(uint64(engine.Status)), + }) + if err != nil { + return errors.Trace(err) + } + for _, value := range engine.Chunks { + columnPerm, err := json.Marshal(value.ColumnPermutation) + if err != nil { + return errors.Trace(err) + } + _, err = s.ExecutePreparedStmt(c, chunkStmt, []types.Datum{ + types.NewStringDatum(tableName), + types.NewIntDatum(int64(engineID)), + types.NewStringDatum(value.Key.Path), + types.NewIntDatum(value.Key.Offset), + types.NewIntDatum(int64(value.FileMeta.Type)), + types.NewIntDatum(int64(value.FileMeta.Compression)), + types.NewStringDatum(value.FileMeta.SortKey), + types.NewIntDatum(value.FileMeta.FileSize), + types.NewBytesDatum(columnPerm), + types.NewIntDatum(value.Chunk.Offset), + types.NewIntDatum(value.Chunk.EndOffset), + types.NewIntDatum(value.Chunk.PrevRowIDMax), + types.NewIntDatum(value.Chunk.RowIDMax), + types.NewIntDatum(value.Timestamp), + }) + if err != nil { + return errors.Trace(err) + } + } + } + return nil + }) + return errors.Trace(err) +} + +func (g GlueCheckpointsDB) Update(checkpointDiffs map[string]*TableCheckpointDiff) { + logger := log.L() + se, err := g.getSessionFunc() + if err != nil { + log.L().Error("can't get a session to update GlueCheckpointsDB", zap.Error(errors.Trace(err))) + return + } + defer se.Close() + + chunkQuery := fmt.Sprintf(UpdateChunkTemplate, g.schema, CheckpointTableNameChunk) + rebaseQuery := fmt.Sprintf(UpdateTableRebaseTemplate, g.schema, CheckpointTableNameTable) + tableStatusQuery := fmt.Sprintf(UpdateTableStatusTemplate, g.schema, CheckpointTableNameTable) + engineStatusQuery := fmt.Sprintf(UpdateEngineTemplate, g.schema, CheckpointTableNameEngine) + err = Transact(context.Background(), "update checkpoints", se, logger, func(c context.Context, s Session) error { + chunkStmt, _, _, err := s.PrepareStmt(chunkQuery) + if err != nil { + return errors.Trace(err) + } + defer s.DropPreparedStmt(chunkStmt) + rebaseStmt, _, _, err := s.PrepareStmt(rebaseQuery) + if err != nil { + return errors.Trace(err) + } + defer s.DropPreparedStmt(rebaseStmt) + tableStatusStmt, _, _, err := s.PrepareStmt(tableStatusQuery) + if err != nil { + return errors.Trace(err) + } + defer s.DropPreparedStmt(tableStatusStmt) + engineStatusStmt, _, _, err := s.PrepareStmt(engineStatusQuery) + if err != nil { + return errors.Trace(err) + } + defer s.DropPreparedStmt(engineStatusStmt) + + for tableName, cpd := range checkpointDiffs { + if cpd.hasStatus { + _, err := s.ExecutePreparedStmt(c, tableStatusStmt, []types.Datum{ + types.NewUintDatum(uint64(cpd.status)), + types.NewStringDatum(tableName), + }) + if err != nil { + return errors.Trace(err) + } + } + if cpd.hasRebase { + _, err := s.ExecutePreparedStmt(c, rebaseStmt, []types.Datum{ + types.NewIntDatum(cpd.allocBase), + types.NewStringDatum(tableName), + }) + if err != nil { + return errors.Trace(err) + } + } + for engineID, engineDiff := range cpd.engines { + if engineDiff.hasStatus { + _, err := s.ExecutePreparedStmt(c, engineStatusStmt, []types.Datum{ + types.NewUintDatum(uint64(engineDiff.status)), + types.NewStringDatum(tableName), + types.NewIntDatum(int64(engineID)), + }) + if err != nil { + return errors.Trace(err) + } + } + for key, diff := range engineDiff.chunks { + columnPerm, err := json.Marshal(diff.columnPermutation) + if err != nil { + return errors.Trace(err) + } + _, err = s.ExecutePreparedStmt(c, chunkStmt, []types.Datum{ + types.NewIntDatum(diff.pos), + types.NewIntDatum(diff.rowID), + types.NewUintDatum(diff.checksum.SumSize()), + types.NewUintDatum(diff.checksum.SumKVS()), + types.NewUintDatum(diff.checksum.Sum()), + types.NewBytesDatum(columnPerm), + types.NewStringDatum(tableName), + types.NewIntDatum(int64(engineID)), + types.NewStringDatum(key.Path), + types.NewIntDatum(key.Offset), + }) + if err != nil { + return errors.Trace(err) + } + } + } + } + return nil + }) + if err != nil { + log.L().Error("save checkpoint failed", zap.Error(err)) + } +} + +func (g GlueCheckpointsDB) RemoveCheckpoint(ctx context.Context, tableName string) error { + logger := log.With(zap.String("table", tableName)) + se, err := g.getSessionFunc() + if err != nil { + return errors.Trace(err) + } + defer se.Close() + + if tableName == "all" { + return common.Retry("remove all checkpoints", logger, func() error { + _, err := se.Execute(ctx, "DROP SCHEMA "+g.schema) + return err + }) + } + tableName = common.InterpolateMySQLString(tableName) + deleteChunkQuery := fmt.Sprintf(DeleteCheckpointRecordTemplate, g.schema, CheckpointTableNameChunk) + deleteChunkQuery = strings.ReplaceAll(deleteChunkQuery, "?", tableName) + deleteEngineQuery := fmt.Sprintf(DeleteCheckpointRecordTemplate, g.schema, CheckpointTableNameEngine) + deleteEngineQuery = strings.ReplaceAll(deleteEngineQuery, "?", tableName) + deleteTableQuery := fmt.Sprintf(DeleteCheckpointRecordTemplate, g.schema, CheckpointTableNameTable) + deleteTableQuery = strings.ReplaceAll(deleteTableQuery, "?", tableName) + + return errors.Trace(Transact(ctx, "remove checkpoints", se, logger, func(c context.Context, s Session) error { + if _, e := s.Execute(c, deleteChunkQuery); e != nil { + return e + } + if _, e := s.Execute(c, deleteEngineQuery); e != nil { + return e + } + if _, e := s.Execute(c, deleteTableQuery); e != nil { + return e + } + return nil + })) +} + +func (g GlueCheckpointsDB) MoveCheckpoints(ctx context.Context, taskID int64) error { + newSchema := fmt.Sprintf("`%s.%d.bak`", g.schema[1:len(g.schema)-1], taskID) + logger := log.With(zap.Int64("taskID", taskID)) + se, err := g.getSessionFunc() + if err != nil { + return errors.Trace(err) + } + defer se.Close() + + err = common.Retry("create backup checkpoints schema", logger, func() error { + _, err := se.Execute(ctx, "CREATE SCHEMA IF NOT EXISTS "+newSchema) + return err + }) + if err != nil { + return errors.Trace(err) + } + for _, tbl := range []string{ + CheckpointTableNameChunk, CheckpointTableNameEngine, + CheckpointTableNameTable, CheckpointTableNameTask, + } { + query := fmt.Sprintf("RENAME TABLE %[1]s.%[3]s TO %[2]s.%[3]s", g.schema, newSchema, tbl) + err := common.Retry(fmt.Sprintf("move %s checkpoints table", tbl), logger, func() error { + _, err := se.Execute(ctx, query) + return err + }) + if err != nil { + return errors.Trace(err) + } + } + return nil +} + +func (g GlueCheckpointsDB) GetLocalStoringTables(ctx context.Context) (map[string][]int32, error) { + se, err := g.getSessionFunc() + if err != nil { + return nil, errors.Trace(err) + } + defer se.Close() + + var targetTables map[string][]int32 + + // lightning didn't check CheckpointStatusMaxInvalid before this function is called, so we skip invalid ones + // engines should exist if + // 1. table status is earlier than CheckpointStatusIndexImported, and + // 2. engine status is earlier than CheckpointStatusImported, and + // 3. chunk has been read + query := fmt.Sprintf(` + SELECT DISTINCT t.table_name, c.engine_id + FROM %s.%s t, %s.%s c, %s.%s e + WHERE t.table_name = c.table_name AND t.table_name = e.table_name AND c.engine_id = e.engine_id + AND %d < t.status AND t.status < %d + AND %d < e.status AND e.status < %d + AND c.pos > c.offset;`, + g.schema, CheckpointTableNameTable, g.schema, CheckpointTableNameChunk, g.schema, CheckpointTableNameEngine, + CheckpointStatusMaxInvalid, CheckpointStatusIndexImported, + CheckpointStatusMaxInvalid, CheckpointStatusImported) + + err = common.Retry("get local storing tables", log.L(), func() error { + targetTables = make(map[string][]int32) + rs, err := se.Execute(ctx, query) + if err != nil { + return errors.Trace(err) + } + rows, err := drainFirstRecordSet(ctx, rs) + if err != nil { + return errors.Trace(err) + } + + for _, row := range rows { + tableName := row.GetString(0) + engineID := int32(row.GetInt64(1)) + targetTables[tableName] = append(targetTables[tableName], engineID) + } + return nil + }) + if err != nil { + return nil, errors.Trace(err) + } + + return targetTables, err +} + +func (g GlueCheckpointsDB) IgnoreErrorCheckpoint(ctx context.Context, tableName string) error { + logger := log.With(zap.String("table", tableName)) + se, err := g.getSessionFunc() + if err != nil { + return errors.Trace(err) + } + defer se.Close() + + var colName string + if tableName == "all" { + // This will expand to `WHERE 'all' = 'all'` and effectively allowing + // all tables to be included. + colName = "'all'" + } else { + colName = "table_name" + } + + tableName = common.InterpolateMySQLString(tableName) + + engineQuery := fmt.Sprintf(` + UPDATE %s.%s SET status = %d WHERE %s = %s AND status <= %d; + `, g.schema, CheckpointTableNameEngine, CheckpointStatusLoaded, colName, tableName, CheckpointStatusMaxInvalid) + tableQuery := fmt.Sprintf(` + UPDATE %s.%s SET status = %d WHERE %s = %s AND status <= %d; + `, g.schema, CheckpointTableNameTable, CheckpointStatusLoaded, colName, tableName, CheckpointStatusMaxInvalid) + return errors.Trace(Transact(ctx, "ignore error checkpoints", se, logger, func(c context.Context, s Session) error { + if _, e := s.Execute(c, engineQuery); e != nil { + return e + } + if _, e := s.Execute(c, tableQuery); e != nil { + return e + } + return nil + })) +} + +func (g GlueCheckpointsDB) DestroyErrorCheckpoint(ctx context.Context, tableName string) ([]DestroyedTableCheckpoint, error) { + logger := log.With(zap.String("table", tableName)) + se, err := g.getSessionFunc() + if err != nil { + return nil, errors.Trace(err) + } + defer se.Close() + + var colName, aliasedColName string + + if tableName == "all" { + // These will expand to `WHERE 'all' = 'all'` and effectively allowing + // all tables to be included. + colName = "'all'" + aliasedColName = "'all'" + } else { + colName = "table_name" + aliasedColName = "t.table_name" + } + + tableName = common.InterpolateMySQLString(tableName) + + selectQuery := fmt.Sprintf(` + SELECT + t.table_name, + COALESCE(MIN(e.engine_id), 0), + COALESCE(MAX(e.engine_id), -1) + FROM %[1]s.%[4]s t + LEFT JOIN %[1]s.%[5]s e ON t.table_name = e.table_name + WHERE %[2]s = %[6]s AND t.status <= %[3]d + GROUP BY t.table_name; + `, g.schema, aliasedColName, CheckpointStatusMaxInvalid, CheckpointTableNameTable, CheckpointTableNameEngine, tableName) + deleteChunkQuery := fmt.Sprintf(` + DELETE FROM %[1]s.%[4]s WHERE table_name IN (SELECT table_name FROM %[1]s.%[5]s WHERE %[2]s = %[6]s AND status <= %[3]d) + `, g.schema, colName, CheckpointStatusMaxInvalid, CheckpointTableNameChunk, CheckpointTableNameTable, tableName) + deleteEngineQuery := fmt.Sprintf(` + DELETE FROM %[1]s.%[4]s WHERE table_name IN (SELECT table_name FROM %[1]s.%[5]s WHERE %[2]s = %[6]s AND status <= %[3]d) + `, g.schema, colName, CheckpointStatusMaxInvalid, CheckpointTableNameEngine, CheckpointTableNameTable, tableName) + deleteTableQuery := fmt.Sprintf(` + DELETE FROM %s.%s WHERE %s = %s AND status <= %d + `, g.schema, CheckpointTableNameTable, colName, tableName, CheckpointStatusMaxInvalid) + + var targetTables []DestroyedTableCheckpoint + err = Transact(ctx, "destroy error checkpoints", se, logger, func(c context.Context, s Session) error { + // clean because it's in a retry + targetTables = nil + rs, err := s.Execute(c, selectQuery) + if err != nil { + return errors.Trace(err) + } + r := rs[0] + req := r.NewChunk() + it := chunk.NewIterator4Chunk(req) + for { + err = r.Next(ctx, req) + if err != nil { + r.Close() + return err + } + if req.NumRows() == 0 { + break + } + + for row := it.Begin(); row != it.End(); row = it.Next() { + var dtc DestroyedTableCheckpoint + dtc.TableName = row.GetString(0) + dtc.MinEngineID = int32(row.GetInt64(1)) + dtc.MaxEngineID = int32(row.GetInt64(2)) + targetTables = append(targetTables, dtc) + } + } + r.Close() + + if _, e := s.Execute(c, deleteChunkQuery); e != nil { + return errors.Trace(e) + } + if _, e := s.Execute(c, deleteEngineQuery); e != nil { + return errors.Trace(e) + } + if _, e := s.Execute(c, deleteTableQuery); e != nil { + return errors.Trace(e) + } + return nil + }) + + if err != nil { + return nil, errors.Trace(err) + } + + return targetTables, nil +} + +func (g GlueCheckpointsDB) DumpTables(ctx context.Context, csv io.Writer) error { + return errors.Errorf("dumping glue checkpoint into CSV not unsupported") +} + +func (g GlueCheckpointsDB) DumpEngines(ctx context.Context, csv io.Writer) error { + return errors.Errorf("dumping glue checkpoint into CSV not unsupported") +} + +func (g GlueCheckpointsDB) DumpChunks(ctx context.Context, csv io.Writer) error { + return errors.Errorf("dumping glue checkpoint into CSV not unsupported") +} + +func Transact(ctx context.Context, purpose string, s Session, logger log.Logger, action func(context.Context, Session) error) error { + return common.Retry(purpose, logger, func() error { + _, err := s.Execute(ctx, "BEGIN") + if err != nil { + return errors.Annotate(err, "begin transaction failed") + } + err = action(ctx, s) + if err != nil { + s.RollbackTxn(ctx) + return err + } + err = s.CommitTxn(ctx) + if err != nil { + return errors.Annotate(err, "commit transaction failed") + } + return nil + }) +} + +// TODO: will use drainFirstRecordSet to reduce repeat in GlueCheckpointsDB later +func drainFirstRecordSet(ctx context.Context, rss []sqlexec.RecordSet) ([]chunk.Row, error) { + if len(rss) != 1 { + return nil, errors.New("given result set doesn't have length 1") + } + rs := rss[0] + var rows []chunk.Row + req := rs.NewChunk() + for { + err := rs.Next(ctx, req) + if err != nil || req.NumRows() == 0 { + rs.Close() + return rows, err + } + iter := chunk.NewIterator4Chunk(req) + for r := iter.Begin(); r != iter.End(); r = iter.Next() { + rows = append(rows, r) + } + req = chunk.Renew(req, 1024) + } +} diff --git a/pkg/lightning/checkpoints/tidb.go b/pkg/lightning/checkpoints/tidb.go new file mode 100644 index 000000000..f53cadd42 --- /dev/null +++ b/pkg/lightning/checkpoints/tidb.go @@ -0,0 +1,30 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package checkpoints + +import ( + "github.com/pingcap/parser/model" +) + +type TidbDBInfo struct { + Name string + Tables map[string]*TidbTableInfo +} + +type TidbTableInfo struct { + ID int64 + DB string + Name string + Core *model.TableInfo +} diff --git a/pkg/lightning/common/once_error.go b/pkg/lightning/common/once_error.go new file mode 100644 index 000000000..14c05193e --- /dev/null +++ b/pkg/lightning/common/once_error.go @@ -0,0 +1,46 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "sync" +) + +// OnceError is an error value which will can be assigned once. +// +// The zero value is ready for use. +type OnceError struct { + lock sync.Mutex + err error +} + +// Set assigns an error to this instance, if `e != nil`. +// +// If this method is called multiple times, only the first call is effective. +func (oe *OnceError) Set(e error) { + if e != nil { + oe.lock.Lock() + if oe.err == nil { + oe.err = e + } + oe.lock.Unlock() + } +} + +// Get returns the first error value stored in this instance. +func (oe *OnceError) Get() error { + oe.lock.Lock() + defer oe.lock.Unlock() + return oe.err +} diff --git a/pkg/lightning/common/once_error_test.go b/pkg/lightning/common/once_error_test.go new file mode 100644 index 000000000..f8196c3e4 --- /dev/null +++ b/pkg/lightning/common/once_error_test.go @@ -0,0 +1,59 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package common_test + +import ( + "errors" + "testing" + + . "github.com/pingcap/check" + + "github.com/pingcap/br/pkg/lightning/common" +) + +func TestCommon(t *testing.T) { + TestingT(t) +} + +var _ = Suite(&onceErrorSuite{}) + +type onceErrorSuite struct{} + +func (s *onceErrorSuite) TestOnceError(c *C) { + var err common.OnceError + + c.Assert(err.Get(), IsNil) + + err.Set(nil) + c.Assert(err.Get(), IsNil) + + e := errors.New("1") + err.Set(e) + c.Assert(err.Get(), Equals, e) + + e2 := errors.New("2") + err.Set(e2) + c.Assert(err.Get(), Equals, e) // e, not e2. + + err.Set(nil) + c.Assert(err.Get(), Equals, e) + + ch := make(chan struct{}) + go func() { + err.Set(nil) + ch <- struct{}{} + }() + <-ch + c.Assert(err.Get(), Equals, e) +} diff --git a/pkg/lightning/common/pause.go b/pkg/lightning/common/pause.go new file mode 100644 index 000000000..7c616e6ea --- /dev/null +++ b/pkg/lightning/common/pause.go @@ -0,0 +1,156 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "context" + "runtime" + "sync/atomic" +) + +const ( + // pauseStateRunning indicates the pauser is running (not paused) + pauseStateRunning uint32 = iota + // pauseStatePaused indicates the pauser is paused + pauseStatePaused + // pauseStateLocked indicates the pauser is being held for exclusive access + // of its waiters field, and no other goroutines should be able to + // read/write the map of waiters when the state is Locked (all other + // goroutines trying to access waiters should cooperatively enter a spin + // loop). + pauseStateLocked +) + +// The implementation is based on https://github.com/golang/sync/blob/master/semaphore/semaphore.go + +// Pauser is a type which could allow multiple goroutines to wait on demand, +// similar to a gate or traffic light. +type Pauser struct { + // state has two purposes: (1) records whether we are paused, and (2) acts + // as a spin-lock for the `waiters` map. + state uint32 + waiters map[chan<- struct{}]struct{} +} + +// NewPauser returns an initialized pauser. +func NewPauser() *Pauser { + return &Pauser{ + state: pauseStateRunning, + waiters: make(map[chan<- struct{}]struct{}, 32), + } +} + +// Pause causes all calls to Wait() to block. +func (p *Pauser) Pause() { + // If the state was Paused, we do nothing. + // If the state was Locked, we loop again until the state becomes not Locked. + // If the state was Running, we atomically move into Paused state. + + for { + oldState := atomic.LoadUint32(&p.state) + if oldState == pauseStatePaused || atomic.CompareAndSwapUint32(&p.state, pauseStateRunning, pauseStatePaused) { + return + } + runtime.Gosched() + } +} + +// Resume causes all calls to Wait() to continue. +func (p *Pauser) Resume() { + // If the state was Running, we do nothing. + // If the state was Locked, we loop again until the state becomes not Locked. + // If the state was Paused, we Lock the pauser, clear the waiter map, + // then move into Running state. + + for { + oldState := atomic.LoadUint32(&p.state) + if oldState == pauseStateRunning { + return + } + if atomic.CompareAndSwapUint32(&p.state, pauseStatePaused, pauseStateLocked) { + break + } + runtime.Gosched() + } + + // extract all waiters, then notify them we changed from "Paused" to "Not Paused". + allWaiters := p.waiters + p.waiters = make(map[chan<- struct{}]struct{}, len(allWaiters)) + + atomic.StoreUint32(&p.state, pauseStateRunning) + + for waiter := range allWaiters { + close(waiter) + } +} + +// IsPaused gets whether the current state is paused or not. +func (p *Pauser) IsPaused() bool { + return atomic.LoadUint32(&p.state) != pauseStateRunning +} + +// Wait blocks the current goroutine if the current state is paused, until the +// pauser itself is resumed at least once. +// +// If `ctx` is done, this method will also unblock immediately, and return the +// context error. +func (p *Pauser) Wait(ctx context.Context) error { + // If the state is Running, we return immediately (this path is hot and must + // be taken as soon as possible) + // If the state is Locked, we loop again until the state becomes not Locked. + // If the state is Paused, we Lock the pauser, add a waiter to the map, then + // revert to the original (Paused) state. + + for { + oldState := atomic.LoadUint32(&p.state) + if oldState == pauseStateRunning { + return nil + } + if atomic.CompareAndSwapUint32(&p.state, pauseStatePaused, pauseStateLocked) { + break + } + runtime.Gosched() + } + + waiter := make(chan struct{}) + p.waiters[waiter] = struct{}{} + + atomic.StoreUint32(&p.state, pauseStatePaused) + + select { + case <-ctx.Done(): + err := ctx.Err() + p.cancel(waiter) + return err + case <-waiter: + return nil + } +} + +// cancel removes a waiter from the waiters map +func (p *Pauser) cancel(waiter chan<- struct{}) { + // If the state is Locked, we loop again until the state becomes not Locked. + // Otherwise, we Lock the pauser, remove the waiter from the map, then + // revert to the original state. + + for { + oldState := atomic.LoadUint32(&p.state) + if oldState != pauseStateLocked && atomic.CompareAndSwapUint32(&p.state, oldState, pauseStateLocked) { + delete(p.waiters, waiter) + atomic.StoreUint32(&p.state, oldState) + return + } + runtime.Gosched() + } +} diff --git a/pkg/lightning/common/pause_test.go b/pkg/lightning/common/pause_test.go new file mode 100644 index 000000000..e235693fd --- /dev/null +++ b/pkg/lightning/common/pause_test.go @@ -0,0 +1,181 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package common_test + +import ( + "context" + "sync" + "time" + + . "github.com/pingcap/check" + + "github.com/pingcap/br/pkg/lightning/common" +) + +// unblocksAfter is a checker which ensures the WaitGroup's Wait() method +// returns between the given durations. +var unblocksBetween Checker = &unblocksChecker{ + &CheckerInfo{Name: "unblocksBetween", Params: []string{"waitGroupPtr", "min", "max"}}, +} + +type unblocksChecker struct { + *CheckerInfo +} + +func (checker *unblocksChecker) Check(params []interface{}, names []string) (bool, string) { + wg := params[0].(*sync.WaitGroup) + min := params[1].(time.Duration) + max := params[2].(time.Duration) + + ch := make(chan time.Duration) + go func() { + start := time.Now() + wg.Wait() + ch <- time.Since(start) + }() + select { + case dur := <-ch: + if dur < min { + return false, "WaitGroup unblocked before minimum duration" + } + return true, "" + case <-time.After(max): + return false, "WaitGroup did not unblock after maximum duration" + } +} + +var _ = Suite(&pauseSuite{}) + +type pauseSuite struct{} + +func (s *pauseSuite) TestPause(c *C) { + var wg sync.WaitGroup + p := common.NewPauser() + + // initially these calls should not be blocking. + + wg.Add(10) + for i := 0; i < 10; i++ { + go func() { + defer wg.Done() + err := p.Wait(context.Background()) + c.Assert(err, IsNil) + }() + } + + c.Assert(&wg, unblocksBetween, 0*time.Millisecond, 10*time.Millisecond) + + // after calling Pause(), these should be blocking... + + p.Pause() + + wg.Add(10) + for i := 0; i < 10; i++ { + go func() { + defer wg.Done() + err := p.Wait(context.Background()) + c.Assert(err, IsNil) + }() + } + + // ... until we call Resume() + + go func() { + time.Sleep(500 * time.Millisecond) + p.Resume() + }() + + c.Assert(&wg, unblocksBetween, 500*time.Millisecond, 520*time.Millisecond) + + // if the context is canceled, Wait() should immediately unblock... + + ctx, cancel := context.WithCancel(context.Background()) + + p.Pause() + + wg.Add(10) + for i := 0; i < 10; i++ { + go func() { + defer wg.Done() + err := p.Wait(ctx) + c.Assert(err, Equals, context.Canceled) + }() + } + + cancel() + c.Assert(&wg, unblocksBetween, 0*time.Millisecond, 10*time.Millisecond) + + // canceling the context does not affect the state of the pauser + + wg.Add(1) + go func() { + defer wg.Done() + err := p.Wait(context.Background()) + c.Assert(err, IsNil) + }() + + go func() { + time.Sleep(500 * time.Millisecond) + p.Resume() + }() + + c.Assert(&wg, unblocksBetween, 500*time.Millisecond, 520*time.Millisecond) +} + +// Run `go test github.com/pingcap/br/pkg/lightning/common -check.b -test.v` to get benchmark result. +func (s *pauseSuite) BenchmarkWaitNoOp(c *C) { + p := common.NewPauser() + ctx := context.Background() + for i := 0; i < c.N; i++ { + p.Wait(ctx) + } +} + +func (s *pauseSuite) BenchmarkWaitCtxCanceled(c *C) { + p := common.NewPauser() + p.Pause() + ctx, cancel := context.WithCancel(context.Background()) + cancel() + for i := 0; i < c.N; i++ { + p.Wait(ctx) + } +} + +func (s *pauseSuite) BenchmarkWaitContended(c *C) { + p := common.NewPauser() + + done := make(chan struct{}) + defer close(done) + go func() { + isPaused := false + for { + select { + case <-done: + return + default: + if isPaused { + p.Pause() + } else { + p.Resume() + } + isPaused = !isPaused + } + } + }() + + ctx := context.Background() + for i := 0; i < c.N; i++ { + p.Wait(ctx) + } +} diff --git a/pkg/lightning/common/security.go b/pkg/lightning/common/security.go new file mode 100644 index 000000000..90258c2b5 --- /dev/null +++ b/pkg/lightning/common/security.go @@ -0,0 +1,162 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "context" + "crypto/tls" + "crypto/x509" + "io/ioutil" + "net" + "net/http" + "net/http/httptest" + + "github.com/pingcap/errors" + pd "github.com/tikv/pd/client" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + + "github.com/pingcap/br/pkg/httputil" +) + +// TLS +type TLS struct { + caPath string + certPath string + keyPath string + inner *tls.Config + client *http.Client + url string +} + +// ToTLSConfig constructs a `*tls.Config` from the CA, certification and key +// paths. +// +// If the CA path is empty, returns nil. +func ToTLSConfig(caPath, certPath, keyPath string) (*tls.Config, error) { + if len(caPath) == 0 { + return nil, nil + } + + // Load the client certificates from disk + var certificates []tls.Certificate + if len(certPath) != 0 && len(keyPath) != 0 { + cert, err := tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + return nil, errors.Annotate(err, "could not load client key pair") + } + certificates = []tls.Certificate{cert} + } + + // Create a certificate pool from CA + certPool := x509.NewCertPool() + ca, err := ioutil.ReadFile(caPath) + if err != nil { + return nil, errors.Annotate(err, "could not read ca certificate") + } + + // Append the certificates from the CA + if !certPool.AppendCertsFromPEM(ca) { + return nil, errors.New("failed to append ca certs") + } + + return &tls.Config{ + Certificates: certificates, + RootCAs: certPool, + NextProtos: []string{"h2", "http/1.1"}, // specify `h2` to let Go use HTTP/2. + }, nil +} + +// NewTLS constructs a new HTTP client with TLS configured with the CA, +// certificate and key paths. +// +// If the CA path is empty, returns an instance where TLS is disabled. +func NewTLS(caPath, certPath, keyPath, host string) (*TLS, error) { + if len(caPath) == 0 { + return &TLS{ + inner: nil, + client: &http.Client{}, + url: "http://" + host, + }, nil + } + inner, err := ToTLSConfig(caPath, certPath, keyPath) + if err != nil { + return nil, errors.Trace(err) + } + return &TLS{ + caPath: caPath, + certPath: certPath, + keyPath: keyPath, + inner: inner, + client: httputil.NewClient(inner), + url: "https://" + host, + }, nil +} + +// NewTLSFromMockServer constructs a new TLS instance from the certificates of +// an *httptest.Server. +func NewTLSFromMockServer(server *httptest.Server) *TLS { + return &TLS{ + inner: server.TLS, + client: server.Client(), + url: server.URL, + } +} + +// WithHost creates a new TLS instance with the host replaced. +func (tc *TLS) WithHost(host string) *TLS { + var url string + if tc.inner != nil { + url = "https://" + host + } else { + url = "http://" + host + } + return &TLS{ + inner: tc.inner, + client: tc.client, + url: url, + } +} + +// ToGRPCDialOption constructs a gRPC dial option. +func (tc *TLS) ToGRPCDialOption() grpc.DialOption { + if tc.inner != nil { + return grpc.WithTransportCredentials(credentials.NewTLS(tc.inner)) + } + return grpc.WithInsecure() +} + +// WrapListener places a TLS layer on top of the existing listener. +func (tc *TLS) WrapListener(l net.Listener) net.Listener { + if tc.inner == nil { + return l + } + return tls.NewListener(l, tc.inner) +} + +func (tc *TLS) GetJSON(ctx context.Context, path string, v interface{}) error { + return GetJSON(ctx, tc.client, tc.url+path, v) +} + +func (tc *TLS) ToPDSecurityOption() pd.SecurityOption { + return pd.SecurityOption{ + CAPath: tc.caPath, + CertPath: tc.certPath, + KeyPath: tc.keyPath, + } +} + +func (tc *TLS) TLSConfig() *tls.Config { + return tc.inner +} diff --git a/pkg/lightning/common/security_test.go b/pkg/lightning/common/security_test.go new file mode 100644 index 000000000..e7db1f86d --- /dev/null +++ b/pkg/lightning/common/security_test.go @@ -0,0 +1,99 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package common_test + +import ( + "context" + "io" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "path/filepath" + + . "github.com/pingcap/check" + + "github.com/pingcap/br/pkg/lightning/common" +) + +type securitySuite struct{} + +var _ = Suite(&securitySuite{}) + +func respondPathHandler(w http.ResponseWriter, req *http.Request) { + io.WriteString(w, `{"path":"`) + io.WriteString(w, req.URL.Path) + io.WriteString(w, `"}`) +} + +func (s *securitySuite) TestGetJSONInsecure(c *C) { + mockServer := httptest.NewServer(http.HandlerFunc(respondPathHandler)) + defer mockServer.Close() + + ctx := context.Background() + u, err := url.Parse(mockServer.URL) + c.Assert(err, IsNil) + + tls, err := common.NewTLS("", "", "", u.Host) + c.Assert(err, IsNil) + + var result struct{ Path string } + err = tls.GetJSON(ctx, "/aaa", &result) + c.Assert(err, IsNil) + c.Assert(result.Path, Equals, "/aaa") + err = tls.GetJSON(ctx, "/bbbb", &result) + c.Assert(err, IsNil) + c.Assert(result.Path, Equals, "/bbbb") +} + +func (s *securitySuite) TestGetJSONSecure(c *C) { + mockServer := httptest.NewTLSServer(http.HandlerFunc(respondPathHandler)) + defer mockServer.Close() + + ctx := context.Background() + tls := common.NewTLSFromMockServer(mockServer) + + var result struct{ Path string } + err := tls.GetJSON(ctx, "/ccc", &result) + c.Assert(err, IsNil) + c.Assert(result.Path, Equals, "/ccc") + err = tls.GetJSON(ctx, "/dddd", &result) + c.Assert(err, IsNil) + c.Assert(result.Path, Equals, "/dddd") +} + +func (s *securitySuite) TestInvalidTLS(c *C) { + tempDir := c.MkDir() + + caPath := filepath.Join(tempDir, "ca.pem") + _, err := common.NewTLS(caPath, "", "", "localhost") + c.Assert(err, ErrorMatches, "could not read ca certificate:.*") + + err = ioutil.WriteFile(caPath, []byte("invalid ca content"), 0o644) + c.Assert(err, IsNil) + _, err = common.NewTLS(caPath, "", "", "localhost") + c.Assert(err, ErrorMatches, "failed to append ca certs") + + certPath := filepath.Join(tempDir, "test.pem") + keyPath := filepath.Join(tempDir, "test.key") + _, err = common.NewTLS(caPath, certPath, keyPath, "localhost") + c.Assert(err, ErrorMatches, "could not load client key pair: open.*") + + err = ioutil.WriteFile(certPath, []byte("invalid cert content"), 0o644) + c.Assert(err, IsNil) + err = ioutil.WriteFile(keyPath, []byte("invalid key content"), 0o600) + c.Assert(err, IsNil) + _, err = common.NewTLS(caPath, certPath, keyPath, "localhost") + c.Assert(err, ErrorMatches, "could not load client key pair: tls.*") +} diff --git a/pkg/lightning/common/storage.go b/pkg/lightning/common/storage.go new file mode 100644 index 000000000..e03820319 --- /dev/null +++ b/pkg/lightning/common/storage.go @@ -0,0 +1,23 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +// TODO: Deduplicate this implementation with DM! + +package common + +// StorageSize represents the storage's capacity and available size +// Learn from tidb-binlog source code. +type StorageSize struct { + Capacity uint64 + Available uint64 +} diff --git a/pkg/lightning/common/storage_test.go b/pkg/lightning/common/storage_test.go new file mode 100644 index 000000000..e663384ec --- /dev/null +++ b/pkg/lightning/common/storage_test.go @@ -0,0 +1,34 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package common_test + +import ( + . "github.com/pingcap/check" + + "github.com/pingcap/br/pkg/lightning/common" +) + +var _ = Suite(&testStorageSuite{}) + +type testStorageSuite struct { +} + +func (t *testStorageSuite) TestGetStorageSize(c *C) { + // only ensure we can get storage size. + d := c.MkDir() + size, err := common.GetStorageSize(d) + c.Assert(err, IsNil) + c.Assert(size.Capacity, Greater, uint64(0)) + c.Assert(size.Available, Greater, uint64(0)) +} diff --git a/pkg/lightning/common/storage_unix.go b/pkg/lightning/common/storage_unix.go new file mode 100644 index 000000000..fcae82db7 --- /dev/null +++ b/pkg/lightning/common/storage_unix.go @@ -0,0 +1,57 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !windows + +// TODO: Deduplicate this implementation with DM! + +package common + +import ( + "reflect" + + "golang.org/x/sys/unix" + + "github.com/pingcap/errors" +) + +// GetStorageSize gets storage's capacity and available size +func GetStorageSize(dir string) (size StorageSize, err error) { + var stat unix.Statfs_t + + err = unix.Statfs(dir, &stat) + if err != nil { + return size, errors.Annotatef(err, "cannot get disk capacity at %s", dir) + } + + // When container is run in MacOS, `bsize` obtained by `statfs` syscall is not the fundamental block size, + // but the `iosize` (optimal transfer block size) instead, it's usually 1024 times larger than the `bsize`. + // for example `4096 * 1024`. To get the correct block size, we should use `frsize`. But `frsize` isn't + // guaranteed to be supported everywhere, so we need to check whether it's supported before use it. + // For more details, please refer to: https://github.com/docker/for-mac/issues/2136 + bSize := uint64(stat.Bsize) + field := reflect.ValueOf(&stat).Elem().FieldByName("Frsize") + if field.IsValid() { + if field.Kind() == reflect.Uint64 { + bSize = field.Uint() + } else { + bSize = uint64(field.Int()) + } + } + + // Available blocks * size per block = available space in bytes + size.Available = stat.Bavail * bSize + size.Capacity = stat.Blocks * bSize + + return +} diff --git a/pkg/lightning/common/storage_windows.go b/pkg/lightning/common/storage_windows.go new file mode 100644 index 000000000..7df060d24 --- /dev/null +++ b/pkg/lightning/common/storage_windows.go @@ -0,0 +1,44 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build windows + +// TODO: Deduplicate this implementation with DM! + +package common + +import ( + "syscall" + "unsafe" + + "github.com/pingcap/errors" +) + +var ( + kernel32 = syscall.MustLoadDLL("kernel32.dll") + getDiskFreeSpaceExW = kernel32.MustFindProc("GetDiskFreeSpaceExW") +) + +// GetStorageSize gets storage's capacity and available size +func GetStorageSize(dir string) (size StorageSize, err error) { + r, _, e := getDiskFreeSpaceExW.Call( + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(dir))), + uintptr(unsafe.Pointer(&size.Available)), + uintptr(unsafe.Pointer(&size.Capacity)), + 0, + ) + if r == 0 { + err = errors.Annotatef(e, "cannot get disk capacity at %s", dir) + } + return +} diff --git a/pkg/lightning/common/util.go b/pkg/lightning/common/util.go new file mode 100644 index 000000000..562335e29 --- /dev/null +++ b/pkg/lightning/common/util.go @@ -0,0 +1,370 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "context" + "database/sql" + "encoding/json" + stderrors "errors" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/url" + "os" + "reflect" + "regexp" + "strings" + "syscall" + "time" + + "github.com/go-sql-driver/mysql" + "github.com/pingcap/errors" + "github.com/pingcap/parser/model" + tmysql "github.com/pingcap/tidb/errno" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/pingcap/br/pkg/lightning/log" +) + +const ( + retryTimeout = 3 * time.Second + + defaultMaxRetry = 3 +) + +// MySQLConnectParam records the parameters needed to connect to a MySQL database. +type MySQLConnectParam struct { + Host string + Port int + User string + Password string + SQLMode string + MaxAllowedPacket uint64 + TLS string + Vars map[string]string +} + +func (param *MySQLConnectParam) ToDSN() string { + dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/?charset=utf8mb4&sql_mode='%s'&maxAllowedPacket=%d&tls=%s", + param.User, param.Password, param.Host, param.Port, + param.SQLMode, param.MaxAllowedPacket, param.TLS) + + for k, v := range param.Vars { + dsn += fmt.Sprintf("&%s=%s", k, url.QueryEscape(v)) + } + + return dsn +} + +func (param *MySQLConnectParam) Connect() (*sql.DB, error) { + db, err := sql.Open("mysql", param.ToDSN()) + if err != nil { + return nil, errors.Trace(err) + } + + return db, errors.Trace(db.Ping()) +} + +// IsDirExists checks if dir exists. +func IsDirExists(name string) bool { + f, err := os.Stat(name) + if err != nil { + return false + } + return f != nil && f.IsDir() +} + +// IsEmptyDir checks if dir is empty. +func IsEmptyDir(name string) bool { + entries, err := ioutil.ReadDir(name) + if err != nil { + return false + } + return len(entries) == 0 +} + +// SQLWithRetry constructs a retryable transaction. +type SQLWithRetry struct { + DB *sql.DB + Logger log.Logger + HideQueryLog bool +} + +func (t SQLWithRetry) perform(ctx context.Context, parentLogger log.Logger, purpose string, action func() error) error { + return Retry(purpose, parentLogger, action) +} + +// Retry is shared by SQLWithRetry.perform, implementation of GlueCheckpointsDB and TiDB's glue implementation +func Retry(purpose string, parentLogger log.Logger, action func() error) error { + var err error +outside: + for i := 0; i < defaultMaxRetry; i++ { + logger := parentLogger.With(zap.Int("retryCnt", i)) + + if i > 0 { + logger.Warn(purpose + " retry start") + time.Sleep(retryTimeout) + } + + err = action() + switch { + case err == nil: + return nil + case IsRetryableError(err): + logger.Warn(purpose+" failed but going to try again", log.ShortError(err)) + continue + default: + break outside + } + } + + return errors.Annotatef(err, "%s failed", purpose) +} + +func (t SQLWithRetry) QueryRow(ctx context.Context, purpose string, query string, dest ...interface{}) error { + logger := t.Logger + if !t.HideQueryLog { + logger = logger.With(zap.String("query", query)) + } + return t.perform(ctx, logger, purpose, func() error { + return t.DB.QueryRowContext(ctx, query).Scan(dest...) + }) +} + +// Transact executes an action in a transaction, and retry if the +// action failed with a retryable error. +func (t SQLWithRetry) Transact(ctx context.Context, purpose string, action func(context.Context, *sql.Tx) error) error { + return t.perform(ctx, t.Logger, purpose, func() error { + txn, err := t.DB.BeginTx(ctx, nil) + if err != nil { + return errors.Annotate(err, "begin transaction failed") + } + + err = action(ctx, txn) + if err != nil { + rerr := txn.Rollback() + if rerr != nil { + t.Logger.Error(purpose+" rollback transaction failed", log.ShortError(rerr)) + } + // we should return the exec err, instead of the rollback rerr. + // no need to errors.Trace() it, as the error comes from user code anyway. + return err + } + + err = txn.Commit() + if err != nil { + return errors.Annotate(err, "commit transaction failed") + } + + return nil + }) +} + +// Exec executes a single SQL with optional retry. +func (t SQLWithRetry) Exec(ctx context.Context, purpose string, query string, args ...interface{}) error { + logger := t.Logger + if !t.HideQueryLog { + logger = logger.With(zap.String("query", query), zap.Reflect("args", args)) + } + return t.perform(ctx, logger, purpose, func() error { + _, err := t.DB.ExecContext(ctx, query, args...) + return errors.Trace(err) + }) +} + +// sqlmock uses fmt.Errorf to produce expectation failures, which will cause +// unnecessary retry if not specially handled >:( +var stdFatalErrorsRegexp = regexp.MustCompile( + `^call to (?s:.*) was not expected|arguments do not match:|could not match actual sql`, +) +var stdErrorType = reflect.TypeOf(stderrors.New("")) + +// IsRetryableError returns whether the error is transient (e.g. network +// connection dropped) or irrecoverable (e.g. user pressing Ctrl+C). This +// function returns `false` (irrecoverable) if `err == nil`. +// +// If the error is a multierr, returns true only if all suberrors are retryable. +func IsRetryableError(err error) bool { + for _, singleError := range errors.Errors(err) { + if !isSingleRetryableError(singleError) { + return false + } + } + return true +} + +func isSingleRetryableError(err error) bool { + err = errors.Cause(err) + + switch err { + case nil, context.Canceled, context.DeadlineExceeded, io.EOF: + return false + } + + switch nerr := err.(type) { + case net.Error: + return nerr.Timeout() + case *mysql.MySQLError: + switch nerr.Number { + // ErrLockDeadlock can retry to commit while meet deadlock + case tmysql.ErrUnknown, tmysql.ErrLockDeadlock, tmysql.ErrWriteConflictInTiDB, tmysql.ErrPDServerTimeout, tmysql.ErrTiKVServerTimeout, tmysql.ErrTiKVServerBusy, tmysql.ErrResolveLockTimeout, tmysql.ErrRegionUnavailable: + return true + default: + return false + } + default: + switch status.Code(err) { + case codes.DeadlineExceeded, codes.NotFound, codes.AlreadyExists, codes.PermissionDenied, codes.ResourceExhausted, codes.Aborted, codes.OutOfRange, codes.Unavailable, codes.DataLoss: + return true + case codes.Unknown: + if reflect.TypeOf(err) == stdErrorType { + return !stdFatalErrorsRegexp.MatchString(err.Error()) + } + return true + default: + return false + } + } +} + +// IsContextCanceledError returns whether the error is caused by context +// cancellation. This function should only be used when the code logic is +// affected by whether the error is canceling or not. +// +// This function returns `false` (not a context-canceled error) if `err == nil`. +func IsContextCanceledError(err error) bool { + return log.IsContextCanceledError(err) +} + +// UniqueTable returns an unique table name. +func UniqueTable(schema string, table string) string { + var builder strings.Builder + WriteMySQLIdentifier(&builder, schema) + builder.WriteByte('.') + WriteMySQLIdentifier(&builder, table) + return builder.String() +} + +// Writes a MySQL identifier into the string builder. +// The identifier is always escaped into the form "`foo`". +func WriteMySQLIdentifier(builder *strings.Builder, identifier string) { + builder.Grow(len(identifier) + 2) + builder.WriteByte('`') + + // use a C-style loop instead of range loop to avoid UTF-8 decoding + for i := 0; i < len(identifier); i++ { + b := identifier[i] + if b == '`' { + builder.WriteString("``") + } else { + builder.WriteByte(b) + } + } + + builder.WriteByte('`') +} + +func InterpolateMySQLString(s string) string { + var builder strings.Builder + builder.Grow(len(s) + 2) + builder.WriteByte('\'') + for i := 0; i < len(s); i++ { + b := s[i] + if b == '\'' { + builder.WriteString("''") + } else { + builder.WriteByte(b) + } + } + builder.WriteByte('\'') + return builder.String() +} + +// GetJSON fetches a page and parses it as JSON. The parsed result will be +// stored into the `v`. The variable `v` must be a pointer to a type that can be +// unmarshalled from JSON. +// +// Example: +// +// client := &http.Client{} +// var resp struct { IP string } +// if err := util.GetJSON(client, "http://api.ipify.org/?format=json", &resp); err != nil { +// return errors.Trace(err) +// } +// fmt.Println(resp.IP) +func GetJSON(ctx context.Context, client *http.Client, url string, v interface{}) error { + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return errors.Trace(err) + } + + resp, err := client.Do(req) + if err != nil { + return errors.Trace(err) + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return errors.Trace(err) + } + return errors.Errorf("get %s http status code != 200, message %s", url, string(body)) + } + + return errors.Trace(json.NewDecoder(resp.Body).Decode(v)) +} + +// KillMySelf sends sigint to current process, used in integration test only +// +// Only works on Unix. Signaling on Windows is not supported. +func KillMySelf() error { + proc, err := os.FindProcess(os.Getpid()) + if err == nil { + err = proc.Signal(syscall.SIGINT) + } + return errors.Trace(err) +} + +// KvPair is a pair of key and value. +type KvPair struct { + // Key is the key of the KV pair + Key []byte + // Val is the value of the KV pair + Val []byte +} + +// TableHasAutoRowID return whether table has auto generated row id +func TableHasAutoRowID(info *model.TableInfo) bool { + return !info.PKIsHandle && !info.IsCommonHandle +} + +// StringSliceEqual checks if two string slices are equal. +func StringSliceEqual(a, b []string) bool { + if len(a) != len(b) { + return false + } + for i, v := range a { + if v != b[i] { + return false + } + } + return true +} diff --git a/pkg/lightning/common/util_test.go b/pkg/lightning/common/util_test.go new file mode 100644 index 000000000..3fab74640 --- /dev/null +++ b/pkg/lightning/common/util_test.go @@ -0,0 +1,218 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package common_test + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "net/http/httptest" + "time" + + sqlmock "github.com/DATA-DOG/go-sqlmock" + "github.com/go-sql-driver/mysql" + . "github.com/pingcap/check" + "github.com/pingcap/errors" + tmysql "github.com/pingcap/tidb/errno" + "go.uber.org/multierr" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/log" +) + +type utilSuite struct{} + +var _ = Suite(&utilSuite{}) + +func (s *utilSuite) TestDirNotExist(c *C) { + c.Assert(common.IsDirExists("."), IsTrue) + c.Assert(common.IsDirExists("not-exists"), IsFalse) +} + +func (s *utilSuite) TestGetJSON(c *C) { + type TestPayload struct { + Username string `json:"username"` + Password string `json:"password"` + } + request := TestPayload{ + Username: "lightning", + Password: "lightning-ctl", + } + + ctx := context.Background() + // Mock success response + handle := func(res http.ResponseWriter, req *http.Request) { + res.WriteHeader(http.StatusOK) + err := json.NewEncoder(res).Encode(request) + c.Assert(err, IsNil) + } + testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { + handle(res, req) + })) + defer testServer.Close() + + client := &http.Client{Timeout: time.Second} + + response := TestPayload{} + err := common.GetJSON(ctx, client, "http://not-exists", &response) + c.Assert(err, NotNil) + err = common.GetJSON(ctx, client, testServer.URL, &response) + c.Assert(err, IsNil) + c.Assert(request, DeepEquals, response) + + // Mock `StatusNoContent` response + handle = func(res http.ResponseWriter, req *http.Request) { + res.WriteHeader(http.StatusNoContent) + } + err = common.GetJSON(ctx, client, testServer.URL, &response) + c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, ".*http status code != 200.*") +} + +func (s *utilSuite) TestIsRetryableError(c *C) { + c.Assert(common.IsRetryableError(context.Canceled), IsFalse) + c.Assert(common.IsRetryableError(context.DeadlineExceeded), IsFalse) + c.Assert(common.IsRetryableError(io.EOF), IsFalse) + c.Assert(common.IsRetryableError(&net.AddrError{}), IsFalse) + c.Assert(common.IsRetryableError(&net.DNSError{}), IsFalse) + c.Assert(common.IsRetryableError(&net.DNSError{IsTimeout: true}), IsTrue) + + // MySQL Errors + c.Assert(common.IsRetryableError(&mysql.MySQLError{}), IsFalse) + c.Assert(common.IsRetryableError(&mysql.MySQLError{Number: tmysql.ErrUnknown}), IsTrue) + c.Assert(common.IsRetryableError(&mysql.MySQLError{Number: tmysql.ErrLockDeadlock}), IsTrue) + c.Assert(common.IsRetryableError(&mysql.MySQLError{Number: tmysql.ErrPDServerTimeout}), IsTrue) + c.Assert(common.IsRetryableError(&mysql.MySQLError{Number: tmysql.ErrTiKVServerTimeout}), IsTrue) + c.Assert(common.IsRetryableError(&mysql.MySQLError{Number: tmysql.ErrTiKVServerBusy}), IsTrue) + c.Assert(common.IsRetryableError(&mysql.MySQLError{Number: tmysql.ErrResolveLockTimeout}), IsTrue) + c.Assert(common.IsRetryableError(&mysql.MySQLError{Number: tmysql.ErrRegionUnavailable}), IsTrue) + c.Assert(common.IsRetryableError(&mysql.MySQLError{Number: tmysql.ErrWriteConflictInTiDB}), IsTrue) + + // gRPC Errors + c.Assert(common.IsRetryableError(status.Error(codes.Canceled, "")), IsFalse) + c.Assert(common.IsRetryableError(status.Error(codes.Unknown, "")), IsTrue) + c.Assert(common.IsRetryableError(status.Error(codes.DeadlineExceeded, "")), IsTrue) + c.Assert(common.IsRetryableError(status.Error(codes.NotFound, "")), IsTrue) + c.Assert(common.IsRetryableError(status.Error(codes.AlreadyExists, "")), IsTrue) + c.Assert(common.IsRetryableError(status.Error(codes.PermissionDenied, "")), IsTrue) + c.Assert(common.IsRetryableError(status.Error(codes.ResourceExhausted, "")), IsTrue) + c.Assert(common.IsRetryableError(status.Error(codes.Aborted, "")), IsTrue) + c.Assert(common.IsRetryableError(status.Error(codes.OutOfRange, "")), IsTrue) + c.Assert(common.IsRetryableError(status.Error(codes.Unavailable, "")), IsTrue) + c.Assert(common.IsRetryableError(status.Error(codes.DataLoss, "")), IsTrue) + + // sqlmock errors + c.Assert(common.IsRetryableError(fmt.Errorf("call to database Close was not expected")), IsFalse) + c.Assert(common.IsRetryableError(errors.New("call to database Close was not expected")), IsTrue) + + // multierr + c.Assert(common.IsRetryableError(multierr.Combine(context.Canceled, context.Canceled)), IsFalse) + c.Assert(common.IsRetryableError(multierr.Combine(&net.DNSError{IsTimeout: true}, &net.DNSError{IsTimeout: true})), IsTrue) + c.Assert(common.IsRetryableError(multierr.Combine(context.Canceled, &net.DNSError{IsTimeout: true})), IsFalse) +} + +func (s *utilSuite) TestToDSN(c *C) { + param := common.MySQLConnectParam{ + Host: "127.0.0.1", + Port: 4000, + User: "root", + Password: "123456", + SQLMode: "strict", + MaxAllowedPacket: 1234, + TLS: "cluster", + Vars: map[string]string{ + "tidb_distsql_scan_concurrency": "1", + }, + } + c.Assert(param.ToDSN(), Equals, "root:123456@tcp(127.0.0.1:4000)/?charset=utf8mb4&sql_mode='strict'&maxAllowedPacket=1234&tls=cluster&tidb_distsql_scan_concurrency=1") +} + +func (s *utilSuite) TestIsContextCanceledError(c *C) { + c.Assert(common.IsContextCanceledError(context.Canceled), IsTrue) + c.Assert(common.IsContextCanceledError(io.EOF), IsFalse) +} + +func (s *utilSuite) TestUniqueTable(c *C) { + tableName := common.UniqueTable("test", "t1") + c.Assert(tableName, Equals, "`test`.`t1`") + + tableName = common.UniqueTable("test", "t`1") + c.Assert(tableName, Equals, "`test`.`t``1`") +} + +func (s *utilSuite) TestSQLWithRetry(c *C) { + db, mock, err := sqlmock.New() + c.Assert(err, IsNil) + + sqlWithRetry := &common.SQLWithRetry{ + DB: db, + Logger: log.L(), + } + aValue := new(int) + + // retry defaultMaxRetry times and still failed + for i := 0; i < 3; i++ { + mock.ExpectQuery("select a from test.t1").WillReturnError(errors.New("mock error")) + } + err = sqlWithRetry.QueryRow(context.Background(), "", "select a from test.t1", aValue) + c.Assert(err, ErrorMatches, ".*mock error") + + // meet unretryable error and will return directly + mock.ExpectQuery("select a from test.t1").WillReturnError(context.Canceled) + err = sqlWithRetry.QueryRow(context.Background(), "", "select a from test.t1", aValue) + c.Assert(err, ErrorMatches, ".*context canceled") + + // query success + rows := sqlmock.NewRows([]string{"a"}).AddRow("1") + mock.ExpectQuery("select a from test.t1").WillReturnRows(rows) + + err = sqlWithRetry.QueryRow(context.Background(), "", "select a from test.t1", aValue) + c.Assert(err, IsNil) + c.Assert(*aValue, Equals, 1) + + // test Exec + mock.ExpectExec("delete from").WillReturnError(context.Canceled) + err = sqlWithRetry.Exec(context.Background(), "", "delete from test.t1 where id = ?", 2) + c.Assert(err, ErrorMatches, ".*context canceled") + + mock.ExpectExec("delete from").WillReturnResult(sqlmock.NewResult(0, 1)) + err = sqlWithRetry.Exec(context.Background(), "", "delete from test.t1 where id = ?", 2) + c.Assert(err, IsNil) + + c.Assert(mock.ExpectationsWereMet(), IsNil) +} + +func (s *utilSuite) TestStringSliceEqual(c *C) { + c.Assert(common.StringSliceEqual(nil, nil), IsTrue) + c.Assert(common.StringSliceEqual(nil, []string{}), IsTrue) + c.Assert(common.StringSliceEqual(nil, []string{"a"}), IsFalse) + c.Assert(common.StringSliceEqual([]string{"a"}, nil), IsFalse) + c.Assert(common.StringSliceEqual([]string{"a"}, []string{"a"}), IsTrue) + c.Assert(common.StringSliceEqual([]string{"a"}, []string{"b"}), IsFalse) + c.Assert(common.StringSliceEqual([]string{"a", "b", "c"}, []string{"a", "b", "c"}), IsTrue) + c.Assert(common.StringSliceEqual([]string{"a"}, []string{"a", "b", "c"}), IsFalse) + c.Assert(common.StringSliceEqual([]string{"a", "b", "c"}, []string{"a", "b"}), IsFalse) + c.Assert(common.StringSliceEqual([]string{"a", "x", "y"}, []string{"a", "y", "x"}), IsFalse) +} + +func (s *utilSuite) TestInterpolateMySQLString(c *C) { + c.Assert(common.InterpolateMySQLString("123"), Equals, "'123'") + c.Assert(common.InterpolateMySQLString("1'23"), Equals, "'1''23'") + c.Assert(common.InterpolateMySQLString("1'2''3"), Equals, "'1''2''''3'") +} diff --git a/pkg/lightning/common/version.go b/pkg/lightning/common/version.go new file mode 100644 index 000000000..3017e6a19 --- /dev/null +++ b/pkg/lightning/common/version.go @@ -0,0 +1,99 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "context" + "fmt" + "strings" + + "github.com/coreos/go-semver/semver" + "github.com/pingcap/errors" + "go.uber.org/zap" + + "github.com/pingcap/br/pkg/lightning/log" +) + +// Version information. +var ( + ReleaseVersion = "None" + BuildTS = "None" + GitHash = "None" + GitBranch = "None" + GoVersion = "None" +) + +// GetRawInfo do what its name tells +func GetRawInfo() string { + var info string + info += fmt.Sprintf("Release Version: %s\n", ReleaseVersion) + info += fmt.Sprintf("Git Commit Hash: %s\n", GitHash) + info += fmt.Sprintf("Git Branch: %s\n", GitBranch) + info += fmt.Sprintf("UTC Build Time: %s\n", BuildTS) + info += fmt.Sprintf("Go Version: %s\n", GoVersion) + return info +} + +// PrintInfo prints some information of the app, like git hash, binary build time, etc. +func PrintInfo(app string, callback func()) { + oldLevel := log.SetLevel(zap.InfoLevel) + defer log.SetLevel(oldLevel) + + log.L().Info("Welcome to "+app, + zap.String("Release Version", ReleaseVersion), + zap.String("Git Commit Hash", GitHash), + zap.String("Git Branch", GitBranch), + zap.String("UTC Build Time", BuildTS), + zap.String("Go Version", GoVersion), + ) + + if callback != nil { + callback() + } +} + +// FetchPDVersion get pd version +func FetchPDVersion(ctx context.Context, tls *TLS, pdAddr string) (*semver.Version, error) { + var rawVersion string + err := tls.WithHost(pdAddr).GetJSON(ctx, "/pd/api/v1/config/cluster-version", &rawVersion) + if err != nil { + return nil, errors.Trace(err) + } + + return semver.NewVersion(rawVersion) +} + +func ExtractTiDBVersion(version string) (*semver.Version, error) { + // version format: "5.7.10-TiDB-v2.1.0-rc.1-7-g38c939f" + // ^~~~~~~~~^ we only want this part + // version format: "5.7.10-TiDB-v2.0.4-1-g06a0bf5" + // ^~~~^ + // version format: "5.7.10-TiDB-v2.0.7" + // ^~~~^ + // version format: "5.7.25-TiDB-v3.0.0-beta-211-g09beefbe0-dirty" + // ^~~~~~~~~^ + // The version is generated by `git describe --tags` on the TiDB repository. + versions := strings.Split(strings.TrimSuffix(version, "-dirty"), "-") + end := len(versions) + switch end { + case 3, 4: + case 5, 6: + end -= 2 + default: + return nil, errors.Errorf("not a valid TiDB version: %s", version) + } + rawVersion := strings.Join(versions[2:end], "-") + rawVersion = strings.TrimPrefix(rawVersion, "v") + return semver.NewVersion(rawVersion) +} diff --git a/pkg/lightning/common/version_test.go b/pkg/lightning/common/version_test.go new file mode 100644 index 000000000..8b629fc39 --- /dev/null +++ b/pkg/lightning/common/version_test.go @@ -0,0 +1,95 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package common_test + +import ( + "github.com/coreos/go-semver/semver" + . "github.com/pingcap/check" + + "github.com/pingcap/br/pkg/lightning/common" +) + +func (s *utilSuite) TestVersion(c *C) { + common.ReleaseVersion = "ReleaseVersion" + common.BuildTS = "BuildTS" + common.GitHash = "GitHash" + common.GitBranch = "GitBranch" + common.GoVersion = "GoVersion" + + version := common.GetRawInfo() + c.Assert(version, Equals, `Release Version: ReleaseVersion +Git Commit Hash: GitHash +Git Branch: GitBranch +UTC Build Time: BuildTS +Go Version: GoVersion +`) + common.PrintInfo("test", func() { + common.ReleaseVersion = "None" + common.BuildTS = "None" + common.GitHash = "None" + common.GitBranch = "None" + common.GoVersion = "None" + }) + + version = common.GetRawInfo() + c.Assert(version, Equals, `Release Version: None +Git Commit Hash: None +Git Branch: None +UTC Build Time: None +Go Version: None +`) +} + +func (s *utilSuite) TestExtractTiDBVersion(c *C) { + vers, err := common.ExtractTiDBVersion("5.7.10-TiDB-v2.1.0-rc.1-7-g38c939f") + c.Assert(err, IsNil) + c.Assert(*vers, Equals, *semver.New("2.1.0-rc.1")) + + vers, err = common.ExtractTiDBVersion("5.7.10-TiDB-v2.0.4-1-g06a0bf5") + c.Assert(err, IsNil) + c.Assert(*vers, Equals, *semver.New("2.0.4")) + + vers, err = common.ExtractTiDBVersion("5.7.10-TiDB-v2.0.7") + c.Assert(err, IsNil) + c.Assert(*vers, Equals, *semver.New("2.0.7")) + + vers, err = common.ExtractTiDBVersion("8.0.12-TiDB-v3.0.5-beta.12") + c.Assert(err, IsNil) + c.Assert(*vers, Equals, *semver.New("3.0.5-beta.12")) + + vers, err = common.ExtractTiDBVersion("5.7.25-TiDB-v3.0.0-beta-211-g09beefbe0-dirty") + c.Assert(err, IsNil) + c.Assert(*vers, Equals, *semver.New("3.0.0-beta")) + + vers, err = common.ExtractTiDBVersion("8.0.12-TiDB-v3.0.5-dirty") + c.Assert(err, IsNil) + c.Assert(*vers, Equals, *semver.New("3.0.5")) + + vers, err = common.ExtractTiDBVersion("8.0.12-TiDB-v3.0.5-beta.12-dirty") + c.Assert(err, IsNil) + c.Assert(*vers, Equals, *semver.New("3.0.5-beta.12")) + + vers, err = common.ExtractTiDBVersion("5.7.10-TiDB-v2.1.0-rc.1-7-g38c939f-dirty") + c.Assert(err, IsNil) + c.Assert(*vers, Equals, *semver.New("2.1.0-rc.1")) + + _, err = common.ExtractTiDBVersion("") + c.Assert(err, ErrorMatches, "not a valid TiDB version.*") + + _, err = common.ExtractTiDBVersion("8.0.12") + c.Assert(err, ErrorMatches, "not a valid TiDB version.*") + + _, err = common.ExtractTiDBVersion("not-a-valid-version") + c.Assert(err, NotNil) +} diff --git a/pkg/lightning/config/bytesize.go b/pkg/lightning/config/bytesize.go new file mode 100644 index 000000000..d511c72c1 --- /dev/null +++ b/pkg/lightning/config/bytesize.go @@ -0,0 +1,44 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "encoding/json" + + "github.com/docker/go-units" +) + +// ByteSize is an alias of int64 which accepts human-friendly strings like +// '10G' when read from TOML. +type ByteSize int64 + +// UnmarshalText implements encoding.TextUnmarshaler +func (size *ByteSize) UnmarshalText(b []byte) error { + res, err := units.RAMInBytes(string(b)) + if err != nil { + return err + } + *size = ByteSize(res) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler (for testing) +func (size *ByteSize) UnmarshalJSON(b []byte) error { + var res int64 + if err := json.Unmarshal(b, &res); err != nil { + return err + } + *size = ByteSize(res) + return nil +} diff --git a/pkg/lightning/config/bytesize_test.go b/pkg/lightning/config/bytesize_test.go new file mode 100644 index 000000000..33b709016 --- /dev/null +++ b/pkg/lightning/config/bytesize_test.go @@ -0,0 +1,129 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package config_test + +import ( + "encoding/json" + "strings" + + "github.com/BurntSushi/toml" + . "github.com/pingcap/check" + + "github.com/pingcap/br/pkg/lightning/config" +) + +type byteSizeTestSuite struct{} + +var _ = Suite(&byteSizeTestSuite{}) + +func (s *byteSizeTestSuite) TestByteSizeTOMLDecode(c *C) { + testCases := []struct { + input string + output config.ByteSize + err string + }{ + { + input: "x = 10000", + output: 10000, + }, + { + input: "x = 107_374_182_400", + output: 107_374_182_400, + }, + { + input: "x = '10k'", + output: 10 * 1024, + }, + { + input: "x = '10PiB'", + output: 10 * 1024 * 1024 * 1024 * 1024 * 1024, + }, + { + input: "x = '10 KB'", + output: 10 * 1024, + }, + { + input: "x = '32768'", + output: 32768, + }, + { + input: "x = -1", + err: "invalid size: '-1'", + }, + { + input: "x = 'invalid value'", + err: "invalid size: 'invalid value'", + }, + { + input: "x = true", + err: "invalid size: 'true'", + }, + { + input: "x = 256.0", + output: 256, + }, + { + input: "x = 256.9", + output: 256, + }, + { + input: "x = 10e+9", + output: 10_000_000_000, + }, + { + input: "x = '2.5MB'", + output: 5 * 512 * 1024, + }, + { + input: "x = 2020-01-01T00:00:00Z", + err: "invalid size: '2020-01-01T00:00:00Z'", + }, + { + input: "x = ['100000']", + err: "toml: cannot load TOML value.*", + }, + { + input: "x = { size = '100000' }", + err: "toml: cannot load TOML value.*", + }, + } + + for _, tc := range testCases { + comment := Commentf("input: `%s`", tc.input) + var output struct{ X config.ByteSize } + err := toml.Unmarshal([]byte(tc.input), &output) + if tc.err != "" { + c.Assert(err, ErrorMatches, tc.err, comment) + } else { + c.Assert(err, IsNil, comment) + c.Assert(output.X, Equals, tc.output, comment) + } + } +} + +func (s *byteSizeTestSuite) TestByteSizeTOMLAndJSONEncode(c *C) { + var input struct { + X config.ByteSize `toml:"x" json:"x"` + } + input.X = 1048576 + + var output strings.Builder + err := toml.NewEncoder(&output).Encode(input) + c.Assert(err, IsNil) + c.Assert(output.String(), Equals, "x = 1048576\n") + + js, err := json.Marshal(input) + c.Assert(err, IsNil) + c.Assert(string(js), Equals, `{"x":1048576}`) +} diff --git a/pkg/lightning/config/config.go b/pkg/lightning/config/config.go new file mode 100644 index 000000000..69b64ec2e --- /dev/null +++ b/pkg/lightning/config/config.go @@ -0,0 +1,790 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "context" + "encoding/json" + "fmt" + "net" + "net/url" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" + "time" + + "github.com/BurntSushi/toml" + "github.com/docker/go-units" + gomysql "github.com/go-sql-driver/mysql" + "github.com/pingcap/errors" + "github.com/pingcap/parser/mysql" + filter "github.com/pingcap/tidb-tools/pkg/table-filter" + router "github.com/pingcap/tidb-tools/pkg/table-router" + tidbcfg "github.com/pingcap/tidb/config" + "go.uber.org/zap" + + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/log" +) + +const ( + // ImportMode defines mode of import for tikv. + ImportMode = "import" + // NormalMode defines mode of normal for tikv. + NormalMode = "normal" + + // BackendTiDB is a constant for choosing the "TiDB" backend in the configuration. + BackendTiDB = "tidb" + // BackendImporter is a constant for choosing the "Importer" backend in the configuration. + BackendImporter = "importer" + // BackendLocal is a constant for choosing the "Local" backup in the configuration. + // In this mode, we write & sort kv pairs with local storage and directly write them to tikv. + BackendLocal = "local" + + // CheckpointDriverMySQL is a constant for choosing the "MySQL" checkpoint driver in the configuration. + CheckpointDriverMySQL = "mysql" + // CheckpointDriverFile is a constant for choosing the "File" checkpoint driver in the configuration. + CheckpointDriverFile = "file" + + // ReplaceOnDup indicates using REPLACE INTO to insert data + ReplaceOnDup = "replace" + // IgnoreOnDup indicates using INSERT IGNORE INTO to insert data + IgnoreOnDup = "ignore" + // ErrorOnDup indicates using INSERT INTO to insert data, which would violate PK or UNIQUE constraint + ErrorOnDup = "error" + + defaultDistSQLScanConcurrency = 15 + defaultBuildStatsConcurrency = 20 + defaultIndexSerialScanConcurrency = 20 + defaultChecksumTableConcurrency = 2 +) + +const ( + LocalMemoryTableSize = 512 * units.MiB + + // autoDiskQuotaLocalReservedSize is the estimated size a local-backend + // engine may gain after calling Flush(). This is currently defined by its + // max MemTable size (512 MiB). It is used to compensate for the soft limit + // of the disk quota against the hard limit of the disk free space. + // + // With a maximum of 8 engines, this should contribute 4.0 GiB to the + // reserved size. + autoDiskQuotaLocalReservedSize uint64 = LocalMemoryTableSize + + // autoDiskQuotaLocalReservedSpeed is the estimated size increase per + // millisecond per write thread the local backend may gain on all engines. + // This is used to compute the maximum size overshoot between two disk quota + // checks, if the first one has barely passed. + // + // With cron.check-disk-quota = 1m, region-concurrency = 40, this should + // contribute 2.3 GiB to the reserved size. + autoDiskQuotaLocalReservedSpeed uint64 = 1 * units.KiB +) + +var ( + defaultConfigPaths = []string{"tidb-lightning.toml", "conf/tidb-lightning.toml"} + supportedStorageTypes = []string{"file", "local", "s3", "noop"} + + DefaultFilter = []string{ + "*.*", + "!mysql.*", + "!sys.*", + "!INFORMATION_SCHEMA.*", + "!PERFORMANCE_SCHEMA.*", + "!METRICS_SCHEMA.*", + "!INSPECTION_SCHEMA.*", + } +) + +type DBStore struct { + Host string `toml:"host" json:"host"` + Port int `toml:"port" json:"port"` + User string `toml:"user" json:"user"` + Psw string `toml:"password" json:"-"` + StatusPort int `toml:"status-port" json:"status-port"` + PdAddr string `toml:"pd-addr" json:"pd-addr"` + StrSQLMode string `toml:"sql-mode" json:"sql-mode"` + TLS string `toml:"tls" json:"tls"` + Security *Security `toml:"security" json:"security"` + + SQLMode mysql.SQLMode `toml:"-" json:"-"` + MaxAllowedPacket uint64 `toml:"max-allowed-packet" json:"max-allowed-packet"` + + DistSQLScanConcurrency int `toml:"distsql-scan-concurrency" json:"distsql-scan-concurrency"` + BuildStatsConcurrency int `toml:"build-stats-concurrency" json:"build-stats-concurrency"` + IndexSerialScanConcurrency int `toml:"index-serial-scan-concurrency" json:"index-serial-scan-concurrency"` + ChecksumTableConcurrency int `toml:"checksum-table-concurrency" json:"checksum-table-concurrency"` +} + +type Config struct { + TaskID int64 `toml:"-" json:"id"` + + App Lightning `toml:"lightning" json:"lightning"` + TiDB DBStore `toml:"tidb" json:"tidb"` + + Checkpoint Checkpoint `toml:"checkpoint" json:"checkpoint"` + Mydumper MydumperRuntime `toml:"mydumper" json:"mydumper"` + TikvImporter TikvImporter `toml:"tikv-importer" json:"tikv-importer"` + PostRestore PostRestore `toml:"post-restore" json:"post-restore"` + Cron Cron `toml:"cron" json:"cron"` + Routes []*router.TableRule `toml:"routes" json:"routes"` + Security Security `toml:"security" json:"security"` + + BWList filter.MySQLReplicationRules `toml:"black-white-list" json:"black-white-list"` +} + +func (c *Config) String() string { + bytes, err := json.Marshal(c) + if err != nil { + log.L().Error("marshal config to json error", log.ShortError(err)) + } + return string(bytes) +} + +func (c *Config) ToTLS() (*common.TLS, error) { + hostPort := net.JoinHostPort(c.TiDB.Host, strconv.Itoa(c.TiDB.StatusPort)) + return common.NewTLS(c.Security.CAPath, c.Security.CertPath, c.Security.KeyPath, hostPort) +} + +type Lightning struct { + TableConcurrency int `toml:"table-concurrency" json:"table-concurrency"` + IndexConcurrency int `toml:"index-concurrency" json:"index-concurrency"` + RegionConcurrency int `toml:"region-concurrency" json:"region-concurrency"` + IOConcurrency int `toml:"io-concurrency" json:"io-concurrency"` + CheckRequirements bool `toml:"check-requirements" json:"check-requirements"` +} + +type PostOpLevel int + +const ( + OpLevelOff PostOpLevel = iota + OpLevelOptional + OpLevelRequired +) + +func (t *PostOpLevel) UnmarshalTOML(v interface{}) error { + switch val := v.(type) { + case bool: + if val { + *t = OpLevelRequired + } else { + *t = OpLevelOff + } + case string: + return t.FromStringValue(val) + default: + return errors.Errorf("invalid op level '%v', please choose valid option between ['off', 'optional', 'required']", v) + } + return nil +} + +func (t PostOpLevel) MarshalText() ([]byte, error) { + return []byte(t.String()), nil +} + +// parser command line parameter +func (t *PostOpLevel) FromStringValue(s string) error { + switch strings.ToLower(s) { + case "off", "false": + *t = OpLevelOff + case "required", "true": + *t = OpLevelRequired + case "optional": + *t = OpLevelOptional + default: + return errors.Errorf("invalid op level '%s', please choose valid option between ['off', 'optional', 'required']", s) + } + return nil +} + +func (t *PostOpLevel) MarshalJSON() ([]byte, error) { + return []byte(`"` + t.String() + `"`), nil +} + +func (t *PostOpLevel) UnmarshalJSON(data []byte) error { + return t.FromStringValue(strings.Trim(string(data), `"`)) +} + +func (t PostOpLevel) String() string { + switch t { + case OpLevelOff: + return "off" + case OpLevelOptional: + return "optional" + case OpLevelRequired: + return "required" + default: + panic(fmt.Sprintf("invalid post process type '%d'", t)) + } +} + +// PostRestore has some options which will be executed after kv restored. +type PostRestore struct { + Level1Compact bool `toml:"level-1-compact" json:"level-1-compact"` + Compact bool `toml:"compact" json:"compact"` + Checksum PostOpLevel `toml:"checksum" json:"checksum"` + Analyze PostOpLevel `toml:"analyze" json:"analyze"` + PostProcessAtLast bool `toml:"post-process-at-last" json:"post-process-at-last"` +} + +type CSVConfig struct { + Separator string `toml:"separator" json:"separator"` + Delimiter string `toml:"delimiter" json:"delimiter"` + Header bool `toml:"header" json:"header"` + TrimLastSep bool `toml:"trim-last-separator" json:"trim-last-separator"` + NotNull bool `toml:"not-null" json:"not-null"` + Null string `toml:"null" json:"null"` + BackslashEscape bool `toml:"backslash-escape" json:"backslash-escape"` +} + +type MydumperRuntime struct { + ReadBlockSize ByteSize `toml:"read-block-size" json:"read-block-size"` + BatchSize ByteSize `toml:"batch-size" json:"batch-size"` + BatchImportRatio float64 `toml:"batch-import-ratio" json:"batch-import-ratio"` + SourceDir string `toml:"data-source-dir" json:"data-source-dir"` + NoSchema bool `toml:"no-schema" json:"no-schema"` + CharacterSet string `toml:"character-set" json:"character-set"` + CSV CSVConfig `toml:"csv" json:"csv"` + CaseSensitive bool `toml:"case-sensitive" json:"case-sensitive"` + StrictFormat bool `toml:"strict-format" json:"strict-format"` + MaxRegionSize ByteSize `toml:"max-region-size" json:"max-region-size"` + Filter []string `toml:"filter" json:"filter"` + FileRouters []*FileRouteRule `toml:"files" json:"files"` + DefaultFileRules bool `toml:"default-file-rules" json:"default-file-rules"` +} + +type FileRouteRule struct { + Pattern string `json:"pattern" toml:"pattern" yaml:"pattern"` + Path string `json:"path" toml:"path" yaml:"path"` + Schema string `json:"schema" toml:"schema" yaml:"schema"` + Table string `json:"table" toml:"table" yaml:"table"` + Type string `json:"type" toml:"type" yaml:"type"` + Key string `json:"key" toml:"key" yaml:"key"` + Compression string `json:"compression" toml:"compression" yaml:"compression"` +} + +type TikvImporter struct { + Addr string `toml:"addr" json:"addr"` + Backend string `toml:"backend" json:"backend"` + OnDuplicate string `toml:"on-duplicate" json:"on-duplicate"` + MaxKVPairs int `toml:"max-kv-pairs" json:"max-kv-pairs"` + SendKVPairs int `toml:"send-kv-pairs" json:"send-kv-pairs"` + RegionSplitSize ByteSize `toml:"region-split-size" json:"region-split-size"` + SortedKVDir string `toml:"sorted-kv-dir" json:"sorted-kv-dir"` + DiskQuota ByteSize `toml:"disk-quota" json:"disk-quota"` + RangeConcurrency int `toml:"range-concurrency" json:"range-concurrency"` +} + +type Checkpoint struct { + Enable bool `toml:"enable" json:"enable"` + Schema string `toml:"schema" json:"schema"` + DSN string `toml:"dsn" json:"-"` // DSN may contain password, don't expose this to JSON. + Driver string `toml:"driver" json:"driver"` + KeepAfterSuccess bool `toml:"keep-after-success" json:"keep-after-success"` +} + +type Cron struct { + SwitchMode Duration `toml:"switch-mode" json:"switch-mode"` + LogProgress Duration `toml:"log-progress" json:"log-progress"` + CheckDiskQuota Duration `toml:"check-disk-quota" json:"check-disk-quota"` +} + +type Security struct { + CAPath string `toml:"ca-path" json:"ca-path"` + CertPath string `toml:"cert-path" json:"cert-path"` + KeyPath string `toml:"key-path" json:"key-path"` + // RedactInfoLog indicates that whether enabling redact log + RedactInfoLog bool `toml:"redact-info-log" json:"redact-info-log"` +} + +// RegistersMySQL registers (or deregisters) the TLS config with name "cluster" +// for use in `sql.Open()`. This method is goroutine-safe. +func (sec *Security) RegisterMySQL() error { + if sec == nil { + return nil + } + tlsConfig, err := common.ToTLSConfig(sec.CAPath, sec.CertPath, sec.KeyPath) + switch { + case err != nil: + return err + case tlsConfig != nil: + // error happens only when the key coincides with the built-in names. + _ = gomysql.RegisterTLSConfig("cluster", tlsConfig) + default: + gomysql.DeregisterTLSConfig("cluster") + } + return nil +} + +// A duration which can be deserialized from a TOML string. +// Implemented as https://github.com/BurntSushi/toml#using-the-encodingtextunmarshaler-interface +type Duration struct { + time.Duration +} + +func (d *Duration) UnmarshalText(text []byte) error { + var err error + d.Duration, err = time.ParseDuration(string(text)) + return err +} + +func (d Duration) MarshalText() ([]byte, error) { + return []byte(d.String()), nil +} + +func (d *Duration) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, d.Duration)), nil +} + +func NewConfig() *Config { + return &Config{ + App: Lightning{ + RegionConcurrency: runtime.NumCPU(), + TableConcurrency: 0, + IndexConcurrency: 0, + IOConcurrency: 5, + CheckRequirements: true, + }, + Checkpoint: Checkpoint{ + Enable: true, + }, + TiDB: DBStore{ + Host: "127.0.0.1", + User: "root", + StatusPort: 10080, + StrSQLMode: "ONLY_FULL_GROUP_BY,NO_AUTO_CREATE_USER", + MaxAllowedPacket: defaultMaxAllowedPacket, + BuildStatsConcurrency: defaultBuildStatsConcurrency, + DistSQLScanConcurrency: defaultDistSQLScanConcurrency, + IndexSerialScanConcurrency: defaultIndexSerialScanConcurrency, + ChecksumTableConcurrency: defaultChecksumTableConcurrency, + }, + Cron: Cron{ + SwitchMode: Duration{Duration: 5 * time.Minute}, + LogProgress: Duration{Duration: 5 * time.Minute}, + CheckDiskQuota: Duration{Duration: 1 * time.Minute}, + }, + Mydumper: MydumperRuntime{ + ReadBlockSize: ReadBlockSize, + CSV: CSVConfig{ + Separator: ",", + Delimiter: `"`, + Header: true, + NotNull: false, + Null: `\N`, + BackslashEscape: true, + TrimLastSep: false, + }, + StrictFormat: false, + MaxRegionSize: MaxRegionSize, + Filter: DefaultFilter, + }, + TikvImporter: TikvImporter{ + Backend: BackendImporter, + OnDuplicate: ReplaceOnDup, + MaxKVPairs: 4096, + SendKVPairs: 32768, + RegionSplitSize: SplitRegionSize, + }, + PostRestore: PostRestore{ + Checksum: OpLevelRequired, + Analyze: OpLevelOptional, + PostProcessAtLast: true, + }, + } +} + +// LoadFromGlobal resets the current configuration to the global settings. +func (cfg *Config) LoadFromGlobal(global *GlobalConfig) error { + if err := cfg.LoadFromTOML(global.ConfigFileContent); err != nil { + return err + } + + cfg.TiDB.Host = global.TiDB.Host + cfg.TiDB.Port = global.TiDB.Port + cfg.TiDB.User = global.TiDB.User + cfg.TiDB.Psw = global.TiDB.Psw + cfg.TiDB.StatusPort = global.TiDB.StatusPort + cfg.TiDB.PdAddr = global.TiDB.PdAddr + cfg.Mydumper.SourceDir = global.Mydumper.SourceDir + cfg.Mydumper.NoSchema = global.Mydumper.NoSchema + cfg.Mydumper.Filter = global.Mydumper.Filter + cfg.TikvImporter.Addr = global.TikvImporter.Addr + cfg.TikvImporter.Backend = global.TikvImporter.Backend + cfg.TikvImporter.SortedKVDir = global.TikvImporter.SortedKVDir + cfg.Checkpoint.Enable = global.Checkpoint.Enable + cfg.PostRestore.Checksum = global.PostRestore.Checksum + cfg.PostRestore.Analyze = global.PostRestore.Analyze + cfg.App.CheckRequirements = global.App.CheckRequirements + cfg.Security = global.Security + + return nil +} + +// LoadFromTOML overwrites the current configuration by the TOML data +// If data contains toml items not in Config and GlobalConfig, return an error +// If data contains toml items not in Config, thus won't take effect, warn user +func (cfg *Config) LoadFromTOML(data []byte) error { + // bothUnused saves toml items not belong to Config nor GlobalConfig + var bothUnused []string + // warnItems saves legal toml items but won't effect + var warnItems []string + + dataStr := string(data) + + // Here we load toml into cfg, and rest logic is check unused keys + metaData, err := toml.Decode(dataStr, cfg) + if err != nil { + return errors.Trace(err) + } + + unusedConfigKeys := metaData.Undecoded() + if len(unusedConfigKeys) == 0 { + return nil + } + + // Now we deal with potential both-unused keys of Config and GlobalConfig struct + + metaDataGlobal, err := toml.Decode(dataStr, &GlobalConfig{}) + if err != nil { + return errors.Trace(err) + } + + // Key type returned by metadata.Undecoded doesn't have a equality comparison, + // we convert them to string type instead, and this conversion is identical + unusedGlobalKeys := metaDataGlobal.Undecoded() + unusedGlobalKeyStrs := make(map[string]struct{}) + for _, key := range unusedGlobalKeys { + unusedGlobalKeyStrs[key.String()] = struct{}{} + } + + for _, key := range unusedConfigKeys { + keyStr := key.String() + if _, found := unusedGlobalKeyStrs[keyStr]; found { + bothUnused = append(bothUnused, keyStr) + } else { + warnItems = append(warnItems, keyStr) + } + } + + if len(bothUnused) > 0 { + return errors.Errorf("config file contained unknown configuration options: %s", + strings.Join(bothUnused, ", ")) + } + + // Warn that some legal field of config file won't be overwritten, such as lightning.file + if len(warnItems) > 0 { + log.L().Warn("currently only per-task configuration can be applied, global configuration changes can only be made on startup", + zap.Strings("global config changes", warnItems)) + } + + return nil +} + +// Adjust fixes the invalid or unspecified settings to reasonable valid values. +func (cfg *Config) Adjust(ctx context.Context) error { + // Reject problematic CSV configurations. + csv := &cfg.Mydumper.CSV + if len(csv.Separator) == 0 { + return errors.New("invalid config: `mydumper.csv.separator` must not be empty") + } + + if len(csv.Delimiter) > 0 && (strings.HasPrefix(csv.Separator, csv.Delimiter) || strings.HasPrefix(csv.Delimiter, csv.Separator)) { + return errors.New("invalid config: `mydumper.csv.separator` and `mydumper.csv.delimiter` must not be prefix of each other") + } + + if csv.BackslashEscape { + if csv.Separator == `\` { + return errors.New("invalid config: cannot use '\\' as CSV separator when `mydumper.csv.backslash-escape` is true") + } + if csv.Delimiter == `\` { + return errors.New("invalid config: cannot use '\\' as CSV delimiter when `mydumper.csv.backslash-escape` is true") + } + } + + // adjust file routing + for _, rule := range cfg.Mydumper.FileRouters { + if filepath.IsAbs(rule.Path) { + relPath, err := filepath.Rel(cfg.Mydumper.SourceDir, rule.Path) + if err != nil { + return errors.Trace(err) + } + // ".." means that this path is not in source dir, so we should return an error + if strings.HasPrefix(relPath, "..") { + return errors.Errorf("file route path '%s' is not in source dir '%s'", rule.Path, cfg.Mydumper.SourceDir) + } + rule.Path = relPath + } + } + + // enable default file route rule if no rules are set + if len(cfg.Mydumper.FileRouters) == 0 { + cfg.Mydumper.DefaultFileRules = true + } + + cfg.TikvImporter.Backend = strings.ToLower(cfg.TikvImporter.Backend) + mustHaveInternalConnections := true + switch cfg.TikvImporter.Backend { + case BackendTiDB: + if cfg.App.IndexConcurrency == 0 { + cfg.App.IndexConcurrency = cfg.App.RegionConcurrency + } + if cfg.App.TableConcurrency == 0 { + cfg.App.TableConcurrency = cfg.App.RegionConcurrency + } + mustHaveInternalConnections = false + case BackendImporter, BackendLocal: + if cfg.App.IndexConcurrency == 0 { + cfg.App.IndexConcurrency = 2 + } + if cfg.App.TableConcurrency == 0 { + cfg.App.TableConcurrency = 6 + } + if cfg.TikvImporter.RangeConcurrency == 0 { + cfg.TikvImporter.RangeConcurrency = 16 + } + if cfg.TikvImporter.RegionSplitSize == 0 { + cfg.TikvImporter.RegionSplitSize = SplitRegionSize + } + if cfg.TiDB.DistSQLScanConcurrency == 0 { + cfg.TiDB.DistSQLScanConcurrency = defaultDistSQLScanConcurrency + } + if cfg.TiDB.BuildStatsConcurrency == 0 { + cfg.TiDB.BuildStatsConcurrency = defaultBuildStatsConcurrency + } + if cfg.TiDB.IndexSerialScanConcurrency == 0 { + cfg.TiDB.IndexSerialScanConcurrency = defaultIndexSerialScanConcurrency + } + if cfg.TiDB.ChecksumTableConcurrency == 0 { + cfg.TiDB.ChecksumTableConcurrency = defaultChecksumTableConcurrency + } + default: + return errors.Errorf("invalid config: unsupported `tikv-importer.backend` (%s)", cfg.TikvImporter.Backend) + } + + if cfg.TikvImporter.Backend == BackendLocal { + if len(cfg.TikvImporter.SortedKVDir) == 0 { + return errors.Errorf("tikv-importer.sorted-kv-dir must not be empty!") + } + + storageSizeDir := filepath.Clean(cfg.TikvImporter.SortedKVDir) + sortedKVDirInfo, err := os.Stat(storageSizeDir) + switch { + case os.IsNotExist(err): + // the sorted-kv-dir does not exist, meaning we will create it automatically. + // so we extract the storage size from its parent directory. + storageSizeDir = filepath.Dir(storageSizeDir) + case err == nil: + if !sortedKVDirInfo.IsDir() { + return errors.Errorf("tikv-importer.sorted-kv-dir ('%s') is not a directory", storageSizeDir) + } + default: + return errors.Annotate(err, "invalid tikv-importer.sorted-kv-dir") + } + + if cfg.TikvImporter.DiskQuota == 0 { + enginesCount := uint64(cfg.App.IndexConcurrency + cfg.App.TableConcurrency) + writeAmount := uint64(cfg.App.RegionConcurrency) * uint64(cfg.Cron.CheckDiskQuota.Milliseconds()) + reservedSize := enginesCount*autoDiskQuotaLocalReservedSize + writeAmount*autoDiskQuotaLocalReservedSpeed + + storageSize, err := common.GetStorageSize(storageSizeDir) + if err != nil { + return err + } + if storageSize.Available <= reservedSize { + return errors.Errorf( + "insufficient disk free space on `%s` (only %s, expecting >%s), please use a storage with enough free space, or specify `tikv-importer.disk-quota`", + cfg.TikvImporter.SortedKVDir, + units.BytesSize(float64(storageSize.Available)), + units.BytesSize(float64(reservedSize))) + } + cfg.TikvImporter.DiskQuota = ByteSize(storageSize.Available - reservedSize) + } + } + + if cfg.TikvImporter.Backend == BackendTiDB { + cfg.TikvImporter.OnDuplicate = strings.ToLower(cfg.TikvImporter.OnDuplicate) + switch cfg.TikvImporter.OnDuplicate { + case ReplaceOnDup, IgnoreOnDup, ErrorOnDup: + default: + return errors.Errorf("invalid config: unsupported `tikv-importer.on-duplicate` (%s)", cfg.TikvImporter.OnDuplicate) + } + } + + var err error + cfg.TiDB.SQLMode, err = mysql.GetSQLMode(cfg.TiDB.StrSQLMode) + if err != nil { + return errors.Annotate(err, "invalid config: `mydumper.tidb.sql_mode` must be a valid SQL_MODE") + } + + if cfg.TiDB.Security == nil { + cfg.TiDB.Security = &cfg.Security + } + + switch cfg.TiDB.TLS { + case "": + if len(cfg.TiDB.Security.CAPath) > 0 { + cfg.TiDB.TLS = "cluster" + } else { + cfg.TiDB.TLS = "false" + } + case "cluster": + if len(cfg.Security.CAPath) == 0 { + return errors.New("invalid config: cannot set `tidb.tls` to 'cluster' without a [security] section") + } + case "false", "skip-verify", "preferred": + break + default: + return errors.Errorf("invalid config: unsupported `tidb.tls` config %s", cfg.TiDB.TLS) + } + + // mydumper.filter and black-white-list cannot co-exist. + if cfg.HasLegacyBlackWhiteList() { + log.L().Warn("the config `black-white-list` has been deprecated, please replace with `mydumper.filter`") + if !common.StringSliceEqual(cfg.Mydumper.Filter, DefaultFilter) { + return errors.New("invalid config: `mydumper.filter` and `black-white-list` cannot be simultaneously defined") + } + } + + for _, rule := range cfg.Routes { + if !cfg.Mydumper.CaseSensitive { + rule.ToLower() + } + if err := rule.Valid(); err != nil { + return errors.Trace(err) + } + } + + // automatically determine the TiDB port & PD address from TiDB settings + if mustHaveInternalConnections && (cfg.TiDB.Port <= 0 || len(cfg.TiDB.PdAddr) == 0) { + tls, err := cfg.ToTLS() + if err != nil { + return err + } + + var settings tidbcfg.Config + err = tls.GetJSON(ctx, "/settings", &settings) + if err != nil { + return errors.Annotate(err, "cannot fetch settings from TiDB, please manually fill in `tidb.port` and `tidb.pd-addr`") + } + if cfg.TiDB.Port <= 0 { + cfg.TiDB.Port = int(settings.Port) + } + if len(cfg.TiDB.PdAddr) == 0 { + pdAddrs := strings.Split(settings.Path, ",") + cfg.TiDB.PdAddr = pdAddrs[0] // FIXME support multiple PDs once importer can. + } + } + + if cfg.TiDB.Port <= 0 { + return errors.New("invalid `tidb.port` setting") + } + if mustHaveInternalConnections && len(cfg.TiDB.PdAddr) == 0 { + return errors.New("invalid `tidb.pd-addr` setting") + } + + // handle mydumper + if cfg.Mydumper.BatchSize <= 0 { + // if rows in source files are not sorted by primary key(if primary is number or cluster index enabled), + // the key range in each data engine may have overlap, thus a bigger engine size can somewhat alleviate it. + cfg.Mydumper.BatchSize = defaultBatchSize + } + if cfg.Mydumper.BatchImportRatio < 0.0 || cfg.Mydumper.BatchImportRatio >= 1.0 { + cfg.Mydumper.BatchImportRatio = 0.75 + } + if cfg.Mydumper.ReadBlockSize <= 0 { + cfg.Mydumper.ReadBlockSize = ReadBlockSize + } + if len(cfg.Mydumper.CharacterSet) == 0 { + cfg.Mydumper.CharacterSet = "auto" + } + + if len(cfg.Checkpoint.Schema) == 0 { + cfg.Checkpoint.Schema = "tidb_lightning_checkpoint" + } + if len(cfg.Checkpoint.Driver) == 0 { + cfg.Checkpoint.Driver = CheckpointDriverFile + } + if len(cfg.Checkpoint.DSN) == 0 { + switch cfg.Checkpoint.Driver { + case CheckpointDriverMySQL: + param := common.MySQLConnectParam{ + Host: cfg.TiDB.Host, + Port: cfg.TiDB.Port, + User: cfg.TiDB.User, + Password: cfg.TiDB.Psw, + SQLMode: mysql.DefaultSQLMode, + MaxAllowedPacket: defaultMaxAllowedPacket, + TLS: cfg.TiDB.TLS, + } + cfg.Checkpoint.DSN = param.ToDSN() + case CheckpointDriverFile: + cfg.Checkpoint.DSN = "/tmp/" + cfg.Checkpoint.Schema + ".pb" + } + } + + var u *url.URL + + // An absolute Windows path like "C:\Users\XYZ" would be interpreted as + // an URL with scheme "C" and opaque data "\Users\XYZ". + // Therefore, we only perform URL parsing if we are sure the path is not + // an absolute Windows path. + // Here we use the `filepath.VolumeName` which can identify the "C:" part + // out of the path. On Linux this method always return an empty string. + // On Windows, the drive letter can only be single letters from "A:" to "Z:", + // so this won't mistake "S3:" as a Windows path. + if len(filepath.VolumeName(cfg.Mydumper.SourceDir)) == 0 { + u, err = url.Parse(cfg.Mydumper.SourceDir) + if err != nil { + return errors.Trace(err) + } + } else { + u = &url.URL{} + } + + // convert path and relative path to a valid file url + if u.Scheme == "" { + if !common.IsDirExists(cfg.Mydumper.SourceDir) { + return errors.Errorf("%s: mydumper dir does not exist", cfg.Mydumper.SourceDir) + } + absPath, err := filepath.Abs(cfg.Mydumper.SourceDir) + if err != nil { + return errors.Annotatef(err, "covert data-source-dir '%s' to absolute path failed", cfg.Mydumper.SourceDir) + } + cfg.Mydumper.SourceDir = "file://" + filepath.ToSlash(absPath) + u.Path = absPath + u.Scheme = "file" + } + + found := false + for _, t := range supportedStorageTypes { + if u.Scheme == t { + found = true + break + } + } + if !found { + return errors.Errorf("Unsupported data-source-dir url '%s'", cfg.Mydumper.SourceDir) + } + + return nil +} + +// HasLegacyBlackWhiteList checks whether the deprecated [black-white-list] section +// was defined. +func (cfg *Config) HasLegacyBlackWhiteList() bool { + return len(cfg.BWList.DoTables) != 0 || len(cfg.BWList.DoDBs) != 0 || len(cfg.BWList.IgnoreTables) != 0 || len(cfg.BWList.IgnoreDBs) != 0 +} diff --git a/pkg/lightning/config/config_test.go b/pkg/lightning/config/config_test.go new file mode 100644 index 000000000..dfd2b0a69 --- /dev/null +++ b/pkg/lightning/config/config_test.go @@ -0,0 +1,654 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package config_test + +import ( + "bytes" + "context" + "flag" + "fmt" + "net" + "net/http" + "net/http/httptest" + "net/url" + "path/filepath" + "regexp" + "strconv" + "testing" + "time" + + "github.com/BurntSushi/toml" + . "github.com/pingcap/check" + "github.com/pingcap/parser/mysql" + + "github.com/pingcap/br/pkg/lightning/config" +) + +func Test(t *testing.T) { + TestingT(t) +} + +var _ = Suite(&configTestSuite{}) + +type configTestSuite struct{} + +func startMockServer(c *C, statusCode int, content string) (*httptest.Server, string, int) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(statusCode) + fmt.Fprint(w, content) + })) + + url, err := url.Parse(ts.URL) + c.Assert(err, IsNil) + host, portString, err := net.SplitHostPort(url.Host) + c.Assert(err, IsNil) + port, err := strconv.Atoi(portString) + c.Assert(err, IsNil) + + return ts, host, port +} + +func assignMinimalLegalValue(cfg *config.Config) { + cfg.TiDB.Host = "123.45.67.89" + cfg.TiDB.Port = 4567 + cfg.TiDB.StatusPort = 8901 + cfg.TiDB.PdAddr = "234.56.78.90:12345" + cfg.Mydumper.SourceDir = "file://." + cfg.TikvImporter.DiskQuota = 1 +} + +func (s *configTestSuite) TestAdjustPdAddrAndPort(c *C) { + ts, host, port := startMockServer(c, http.StatusOK, + `{"port":4444,"advertise-address":"","path":"123.45.67.89:1234,56.78.90.12:3456"}`, + ) + defer ts.Close() + + cfg := config.NewConfig() + cfg.TiDB.Host = host + cfg.TiDB.StatusPort = port + cfg.Mydumper.SourceDir = "." + + err := cfg.Adjust(context.Background()) + c.Assert(err, IsNil) + c.Assert(cfg.TiDB.Port, Equals, 4444) + c.Assert(cfg.TiDB.PdAddr, Equals, "123.45.67.89:1234") +} + +func (s *configTestSuite) TestAdjustPdAddrAndPortViaAdvertiseAddr(c *C) { + ts, host, port := startMockServer(c, http.StatusOK, + `{"port":6666,"advertise-address":"121.212.121.212:5555","path":"34.34.34.34:3434"}`, + ) + defer ts.Close() + + cfg := config.NewConfig() + cfg.TiDB.Host = host + cfg.TiDB.StatusPort = port + cfg.Mydumper.SourceDir = "." + + err := cfg.Adjust(context.Background()) + c.Assert(err, IsNil) + c.Assert(cfg.TiDB.Port, Equals, 6666) + c.Assert(cfg.TiDB.PdAddr, Equals, "34.34.34.34:3434") +} + +func (s *configTestSuite) TestAdjustPageNotFound(c *C) { + ts, host, port := startMockServer(c, http.StatusNotFound, "{}") + defer ts.Close() + + cfg := config.NewConfig() + cfg.TiDB.Host = host + cfg.TiDB.StatusPort = port + + err := cfg.Adjust(context.Background()) + c.Assert(err, ErrorMatches, "cannot fetch settings from TiDB.*") +} + +func (s *configTestSuite) TestAdjustConnectRefused(c *C) { + ts, host, port := startMockServer(c, http.StatusOK, "{}") + + cfg := config.NewConfig() + cfg.TiDB.Host = host + cfg.TiDB.StatusPort = port + + ts.Close() // immediately close to ensure connection refused. + + err := cfg.Adjust(context.Background()) + c.Assert(err, ErrorMatches, "cannot fetch settings from TiDB.*") +} + +func (s *configTestSuite) TestAdjustInvalidBackend(c *C) { + cfg := config.NewConfig() + cfg.TikvImporter.Backend = "no_such_backend" + err := cfg.Adjust(context.Background()) + c.Assert(err, ErrorMatches, "invalid config: unsupported `tikv-importer\\.backend` \\(no_such_backend\\)") +} + +func (s *configTestSuite) TestAdjustFileRoutePath(c *C) { + cfg := config.NewConfig() + assignMinimalLegalValue(cfg) + + ctx := context.Background() + tmpDir := c.MkDir() + cfg.Mydumper.SourceDir = tmpDir + invalidPath := filepath.Join(tmpDir, "../test123/1.sql") + rule := &config.FileRouteRule{Path: invalidPath, Type: "sql", Schema: "test", Table: "tbl"} + cfg.Mydumper.FileRouters = []*config.FileRouteRule{rule} + err := cfg.Adjust(ctx) + c.Assert(err, ErrorMatches, fmt.Sprintf("\\Qfile route path '%s' is not in source dir '%s'\\E", invalidPath, tmpDir)) + + relPath := filepath.FromSlash("test_dir/1.sql") + rule.Path = filepath.Join(tmpDir, relPath) + err = cfg.Adjust(ctx) + c.Assert(err, IsNil) + c.Assert(cfg.Mydumper.FileRouters[0].Path, Equals, relPath) +} + +func (s *configTestSuite) TestDecodeError(c *C) { + ts, host, port := startMockServer(c, http.StatusOK, "invalid-string") + defer ts.Close() + + cfg := config.NewConfig() + cfg.TiDB.Host = host + cfg.TiDB.StatusPort = port + + err := cfg.Adjust(context.Background()) + c.Assert(err, ErrorMatches, "cannot fetch settings from TiDB.*") +} + +func (s *configTestSuite) TestInvalidSetting(c *C) { + ts, host, port := startMockServer(c, http.StatusOK, `{"port": 0}`) + defer ts.Close() + + cfg := config.NewConfig() + cfg.TiDB.Host = host + cfg.TiDB.StatusPort = port + + err := cfg.Adjust(context.Background()) + c.Assert(err, ErrorMatches, "invalid `tidb.port` setting") +} + +func (s *configTestSuite) TestInvalidPDAddr(c *C) { + ts, host, port := startMockServer(c, http.StatusOK, `{"port": 1234, "path": ",,"}`) + defer ts.Close() + + cfg := config.NewConfig() + cfg.TiDB.Host = host + cfg.TiDB.StatusPort = port + + err := cfg.Adjust(context.Background()) + c.Assert(err, ErrorMatches, "invalid `tidb.pd-addr` setting") +} + +func (s *configTestSuite) TestAdjustWillNotContactServerIfEverythingIsDefined(c *C) { + cfg := config.NewConfig() + assignMinimalLegalValue(cfg) + + err := cfg.Adjust(context.Background()) + c.Assert(err, IsNil) + c.Assert(cfg.TiDB.Port, Equals, 4567) + c.Assert(cfg.TiDB.PdAddr, Equals, "234.56.78.90:12345") +} + +func (s *configTestSuite) TestAdjustWillBatchImportRatioInvalid(c *C) { + cfg := config.NewConfig() + assignMinimalLegalValue(cfg) + cfg.Mydumper.BatchImportRatio = -1 + err := cfg.Adjust(context.Background()) + c.Assert(err, IsNil) + c.Assert(cfg.Mydumper.BatchImportRatio, Equals, 0.75) +} + +func (s *configTestSuite) TestAdjustSecuritySection(c *C) { + testCases := []struct { + input string + expectedCA string + expectedTLS string + }{ + { + input: ``, + expectedCA: "", + expectedTLS: "false", + }, + { + input: ` + [security] + `, + expectedCA: "", + expectedTLS: "false", + }, + { + input: ` + [security] + ca-path = "/path/to/ca.pem" + `, + expectedCA: "/path/to/ca.pem", + expectedTLS: "cluster", + }, + { + input: ` + [security] + ca-path = "/path/to/ca.pem" + [tidb.security] + `, + expectedCA: "", + expectedTLS: "false", + }, + { + input: ` + [security] + ca-path = "/path/to/ca.pem" + [tidb.security] + ca-path = "/path/to/ca2.pem" + `, + expectedCA: "/path/to/ca2.pem", + expectedTLS: "cluster", + }, + { + input: ` + [security] + [tidb.security] + ca-path = "/path/to/ca2.pem" + `, + expectedCA: "/path/to/ca2.pem", + expectedTLS: "cluster", + }, + { + input: ` + [security] + [tidb] + tls = "skip-verify" + [tidb.security] + `, + expectedCA: "", + expectedTLS: "skip-verify", + }, + } + + for _, tc := range testCases { + comment := Commentf("input = %s", tc.input) + + cfg := config.NewConfig() + assignMinimalLegalValue(cfg) + err := cfg.LoadFromTOML([]byte(tc.input)) + c.Assert(err, IsNil, comment) + + err = cfg.Adjust(context.Background()) + c.Assert(err, IsNil, comment) + c.Assert(cfg.TiDB.Security.CAPath, Equals, tc.expectedCA, comment) + c.Assert(cfg.TiDB.TLS, Equals, tc.expectedTLS, comment) + } +} + +func (s *configTestSuite) TestInvalidCSV(c *C) { + testCases := []struct { + input string + err string + }{ + { + input: ` + [mydumper.csv] + separator = '' + `, + err: "invalid config: `mydumper.csv.separator` must not be empty", + }, + { + input: ` + [mydumper.csv] + separator = 'hello' + delimiter = 'hel' + `, + err: "invalid config: `mydumper.csv.separator` and `mydumper.csv.delimiter` must not be prefix of each other", + }, + { + input: ` + [mydumper.csv] + separator = 'hel' + delimiter = 'hello' + `, + err: "invalid config: `mydumper.csv.separator` and `mydumper.csv.delimiter` must not be prefix of each other", + }, + { + input: ` + [mydumper.csv] + separator = '\' + backslash-escape = false + `, + err: "", + }, + { + input: ` + [mydumper.csv] + separator = ',' + `, + err: "", + }, + { + input: ` + [mydumper.csv] + delimiter = '' + `, + err: "", + }, + { + input: ` + [mydumper.csv] + delimiter = 'hello' + `, + err: "", + }, + { + input: ` + [mydumper.csv] + delimiter = '\' + backslash-escape = false + `, + err: "", + }, + { + input: ` + [mydumper.csv] + separator = '\s' + delimiter = '\d' + `, + err: "", + }, + { + input: ` + [mydumper.csv] + separator = '|' + delimiter = '|' + `, + err: "invalid config: `mydumper.csv.separator` and `mydumper.csv.delimiter` must not be prefix of each other", + }, + { + input: ` + [mydumper.csv] + separator = '\' + backslash-escape = true + `, + err: "invalid config: cannot use '\\' as CSV separator when `mydumper.csv.backslash-escape` is true", + }, + { + input: ` + [mydumper.csv] + delimiter = '\' + backslash-escape = true + `, + err: "invalid config: cannot use '\\' as CSV delimiter when `mydumper.csv.backslash-escape` is true", + }, + { + input: ` + [tidb] + sql-mode = "invalid-sql-mode" + `, + err: "invalid config: `mydumper.tidb.sql_mode` must be a valid SQL_MODE: ERROR 1231 (42000): Variable 'sql_mode' can't be set to the value of 'invalid-sql-mode'", + }, + { + input: ` + [[routes]] + schema-pattern = "" + table-pattern = "shard_table_*" + `, + err: "schema pattern of table route rule should not be empty", + }, + { + input: ` + [[routes]] + schema-pattern = "schema_*" + table-pattern = "" + `, + err: "target schema of table route rule should not be empty", + }, + } + + for _, tc := range testCases { + comment := Commentf("input = %s", tc.input) + + cfg := config.NewConfig() + cfg.Mydumper.SourceDir = "file://." + cfg.TiDB.Port = 4000 + cfg.TiDB.PdAddr = "test.invalid:2379" + err := cfg.LoadFromTOML([]byte(tc.input)) + c.Assert(err, IsNil) + + err = cfg.Adjust(context.Background()) + if tc.err != "" { + c.Assert(err, ErrorMatches, regexp.QuoteMeta(tc.err), comment) + } else { + c.Assert(err, IsNil, comment) + } + } +} + +func (s *configTestSuite) TestInvalidTOML(c *C) { + cfg := &config.Config{} + err := cfg.LoadFromTOML([]byte(` + invalid[mydumper.csv] + delimiter = '\' + backslash-escape = true + `)) + c.Assert(err, ErrorMatches, regexp.QuoteMeta("Near line 0 (last key parsed ''): bare keys cannot contain '['")) +} + +func (s *configTestSuite) TestTOMLUnusedKeys(c *C) { + cfg := &config.Config{} + err := cfg.LoadFromTOML([]byte(` + [lightning] + typo = 123 + `)) + c.Assert(err, ErrorMatches, regexp.QuoteMeta("config file contained unknown configuration options: lightning.typo")) +} + +func (s *configTestSuite) TestDurationUnmarshal(c *C) { + duration := config.Duration{} + err := duration.UnmarshalText([]byte("13m20s")) + c.Assert(err, IsNil) + c.Assert(duration.Duration.Seconds(), Equals, 13*60+20.0) + err = duration.UnmarshalText([]byte("13x20s")) + c.Assert(err, ErrorMatches, "time: unknown unit .?x.? in duration .?13x20s.?") +} + +func (s *configTestSuite) TestDurationMarshalJSON(c *C) { + duration := config.Duration{} + err := duration.UnmarshalText([]byte("13m20s")) + c.Assert(err, IsNil) + c.Assert(duration.Duration.Seconds(), Equals, 13*60+20.0) + result, err := duration.MarshalJSON() + c.Assert(err, IsNil) + c.Assert(string(result), Equals, `"13m20s"`) +} + +func (s *configTestSuite) TestLoadConfig(c *C) { + cfg, err := config.LoadGlobalConfig([]string{"-tidb-port", "sss"}, nil) + c.Assert(err, ErrorMatches, `invalid value "sss" for flag -tidb-port: parse error`) + c.Assert(cfg, IsNil) + + cfg, err = config.LoadGlobalConfig([]string{"-V"}, nil) + c.Assert(err, Equals, flag.ErrHelp) + c.Assert(cfg, IsNil) + + cfg, err = config.LoadGlobalConfig([]string{"-config", "not-exists"}, nil) + c.Assert(err, ErrorMatches, ".*(no such file or directory|The system cannot find the file specified).*") + c.Assert(cfg, IsNil) + + cfg, err = config.LoadGlobalConfig([]string{"--server-mode"}, nil) + c.Assert(err, ErrorMatches, "If server-mode is enabled, the status-addr must be a valid listen address") + c.Assert(cfg, IsNil) + + path, _ := filepath.Abs(".") + cfg, err = config.LoadGlobalConfig([]string{ + "-L", "debug", + "-log-file", "/path/to/file.log", + "-tidb-host", "172.16.30.11", + "-tidb-port", "4001", + "-tidb-user", "guest", + "-tidb-password", "12345", + "-pd-urls", "172.16.30.11:2379,172.16.30.12:2379", + "-d", path, + "-importer", "172.16.30.11:23008", + "-checksum=false", + }, nil) + c.Assert(err, IsNil) + c.Assert(cfg.App.Config.Level, Equals, "debug") + c.Assert(cfg.App.Config.File, Equals, "/path/to/file.log") + c.Assert(cfg.TiDB.Host, Equals, "172.16.30.11") + c.Assert(cfg.TiDB.Port, Equals, 4001) + c.Assert(cfg.TiDB.User, Equals, "guest") + c.Assert(cfg.TiDB.Psw, Equals, "12345") + c.Assert(cfg.TiDB.PdAddr, Equals, "172.16.30.11:2379,172.16.30.12:2379") + c.Assert(cfg.Mydumper.SourceDir, Equals, path) + c.Assert(cfg.TikvImporter.Addr, Equals, "172.16.30.11:23008") + c.Assert(cfg.PostRestore.Checksum, Equals, config.OpLevelOff) + c.Assert(cfg.PostRestore.Analyze, Equals, config.OpLevelOptional) + + taskCfg := config.NewConfig() + err = taskCfg.LoadFromGlobal(cfg) + c.Assert(err, IsNil) + c.Assert(taskCfg.PostRestore.Checksum, Equals, config.OpLevelOff) + c.Assert(taskCfg.PostRestore.Analyze, Equals, config.OpLevelOptional) + + taskCfg.Checkpoint.DSN = "" + taskCfg.Checkpoint.Driver = config.CheckpointDriverMySQL + err = taskCfg.Adjust(context.Background()) + c.Assert(err, IsNil) + c.Assert(taskCfg.Checkpoint.DSN, Equals, "guest:12345@tcp(172.16.30.11:4001)/?charset=utf8mb4&sql_mode='"+mysql.DefaultSQLMode+"'&maxAllowedPacket=67108864&tls=false") + + result := taskCfg.String() + c.Assert(result, Matches, `.*"pd-addr":"172.16.30.11:2379,172.16.30.12:2379".*`) +} + +func (s *configTestSuite) TestDefaultImporterBackendValue(c *C) { + cfg := config.NewConfig() + assignMinimalLegalValue(cfg) + cfg.TikvImporter.Backend = "importer" + err := cfg.Adjust(context.Background()) + c.Assert(err, IsNil) + c.Assert(cfg.App.IndexConcurrency, Equals, 2) + c.Assert(cfg.App.TableConcurrency, Equals, 6) +} + +func (s *configTestSuite) TestDefaultTidbBackendValue(c *C) { + cfg := config.NewConfig() + assignMinimalLegalValue(cfg) + cfg.TikvImporter.Backend = "tidb" + cfg.App.RegionConcurrency = 123 + err := cfg.Adjust(context.Background()) + c.Assert(err, IsNil) + c.Assert(cfg.App.IndexConcurrency, Equals, 123) + c.Assert(cfg.App.TableConcurrency, Equals, 123) +} + +func (s *configTestSuite) TestDefaultCouldBeOverwritten(c *C) { + cfg := config.NewConfig() + assignMinimalLegalValue(cfg) + cfg.TikvImporter.Backend = "importer" + cfg.App.IndexConcurrency = 20 + cfg.App.TableConcurrency = 60 + err := cfg.Adjust(context.Background()) + c.Assert(err, IsNil) + c.Assert(cfg.App.IndexConcurrency, Equals, 20) + c.Assert(cfg.App.TableConcurrency, Equals, 60) +} + +func (s *configTestSuite) TestLoadFromInvalidConfig(c *C) { + taskCfg := config.NewConfig() + err := taskCfg.LoadFromGlobal(&config.GlobalConfig{ + ConfigFileContent: []byte("invalid toml"), + }) + c.Assert(err, ErrorMatches, "Near line 1.*") +} + +func (s *configTestSuite) TestTomlPostRestore(c *C) { + cfg := &config.Config{} + err := cfg.LoadFromTOML([]byte(` + [post-restore] + checksum = "req" + `)) + c.Assert(err, ErrorMatches, regexp.QuoteMeta("invalid op level 'req', please choose valid option between ['off', 'optional', 'required']")) + + err = cfg.LoadFromTOML([]byte(` + [post-restore] + analyze = 123 + `)) + c.Assert(err, ErrorMatches, regexp.QuoteMeta("invalid op level '123', please choose valid option between ['off', 'optional', 'required']")) + + kvMap := map[string]config.PostOpLevel{ + `"off"`: config.OpLevelOff, + `"required"`: config.OpLevelRequired, + `"optional"`: config.OpLevelOptional, + "true": config.OpLevelRequired, + "false": config.OpLevelOff, + } + + var b bytes.Buffer + enc := toml.NewEncoder(&b) + + for k, v := range kvMap { + cfg := &config.Config{} + confStr := fmt.Sprintf("[post-restore]\r\nchecksum= %s\r\n", k) + err := cfg.LoadFromTOML([]byte(confStr)) + c.Assert(err, IsNil) + c.Assert(cfg.PostRestore.Checksum, Equals, v) + + b.Reset() + c.Assert(enc.Encode(cfg.PostRestore), IsNil) + c.Assert(&b, Matches, fmt.Sprintf(`(?s).*checksum = "\Q%s\E".*`, v)) + } + + for k, v := range kvMap { + cfg := &config.Config{} + confStr := fmt.Sprintf("[post-restore]\r\nanalyze= %s\r\n", k) + err := cfg.LoadFromTOML([]byte(confStr)) + c.Assert(err, IsNil) + c.Assert(cfg.PostRestore.Analyze, Equals, v) + + b.Reset() + c.Assert(enc.Encode(cfg.PostRestore), IsNil) + c.Assert(&b, Matches, fmt.Sprintf(`(?s).*analyze = "\Q%s\E".*`, v)) + } +} + +func (s *configTestSuite) TestCronEncodeDecode(c *C) { + cfg := &config.Config{} + cfg.Cron.SwitchMode.Duration = 1 * time.Minute + cfg.Cron.LogProgress.Duration = 2 * time.Minute + cfg.Cron.CheckDiskQuota.Duration = 3 * time.Second + var b bytes.Buffer + c.Assert(toml.NewEncoder(&b).Encode(cfg.Cron), IsNil) + c.Assert(b.String(), Equals, "switch-mode = \"1m0s\"\nlog-progress = \"2m0s\"\ncheck-disk-quota = \"3s\"\n") + + confStr := "[cron]\r\n" + b.String() + cfg2 := &config.Config{} + c.Assert(cfg2.LoadFromTOML([]byte(confStr)), IsNil) + c.Assert(cfg2.Cron, DeepEquals, cfg.Cron) +} + +func (s *configTestSuite) TestAdjustWithLegacyBlackWhiteList(c *C) { + cfg := config.NewConfig() + assignMinimalLegalValue(cfg) + c.Assert(cfg.Mydumper.Filter, DeepEquals, config.DefaultFilter) + c.Assert(cfg.HasLegacyBlackWhiteList(), IsFalse) + + ctx := context.Background() + cfg.Mydumper.Filter = []string{"test.*"} + c.Assert(cfg.Adjust(ctx), IsNil) + c.Assert(cfg.HasLegacyBlackWhiteList(), IsFalse) + + cfg.BWList.DoDBs = []string{"test"} + c.Assert(cfg.Adjust(ctx), ErrorMatches, "invalid config: `mydumper\\.filter` and `black-white-list` cannot be simultaneously defined") + + cfg.Mydumper.Filter = config.DefaultFilter + c.Assert(cfg.Adjust(ctx), IsNil) + c.Assert(cfg.HasLegacyBlackWhiteList(), IsTrue) +} diff --git a/pkg/lightning/config/configlist.go b/pkg/lightning/config/configlist.go new file mode 100644 index 000000000..c78e8903b --- /dev/null +++ b/pkg/lightning/config/configlist.go @@ -0,0 +1,153 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "container/list" + "context" + "sync" + "time" +) + +// ConfigList is a goroutine-safe FIFO list of *Config, which supports removal +// from the middle. The list is not expected to be very long. +type ConfigList struct { + cond *sync.Cond + taskIDMap map[int64]*list.Element + nodes list.List + + // lastID records the largest task ID being Push()'ed to the ConfigList. + // In the rare case where two Push() are executed in the same nanosecond + // (or the not-so-rare case where the clock's precision is lower than CPU + // speed), we'll need to manually force one of the task to use the ID as + // lastID + 1. + lastID int64 +} + +// NewConfigList creates a new ConfigList instance. +func NewConfigList() *ConfigList { + return &ConfigList{ + cond: sync.NewCond(new(sync.Mutex)), + taskIDMap: make(map[int64]*list.Element), + } +} + +// Push adds a configuration to the end of the list. The field `cfg.TaskID` will +// be modified to include a unique ID to identify this task. +func (cl *ConfigList) Push(cfg *Config) { + id := time.Now().UnixNano() + cl.cond.L.Lock() + defer cl.cond.L.Unlock() + if id <= cl.lastID { + id = cl.lastID + 1 + } + cfg.TaskID = id + cl.lastID = id + cl.taskIDMap[id] = cl.nodes.PushBack(cfg) + cl.cond.Broadcast() +} + +// Pop removes a configuration from the front of the list. If the list is empty, +// this method will block until either another goroutines calls Push() or the +// input context expired. +// +// If the context expired, the error field will contain the error from context. +func (cl *ConfigList) Pop(ctx context.Context) (*Config, error) { + res := make(chan *Config) + + go func() { + cl.cond.L.Lock() + defer cl.cond.L.Unlock() + for { + if front := cl.nodes.Front(); front != nil { + cfg := front.Value.(*Config) + delete(cl.taskIDMap, cfg.TaskID) + cl.nodes.Remove(front) + res <- cfg + break + } + cl.cond.Wait() + } + }() + + select { + case cfg := <-res: + return cfg, nil + case <-ctx.Done(): + return nil, ctx.Err() + } +} + +// Remove removes a task from the list given its task ID. Returns true if a task +// is successfully removed, false if the task ID did not exist. +func (cl *ConfigList) Remove(taskID int64) bool { + cl.cond.L.Lock() + defer cl.cond.L.Unlock() + element, ok := cl.taskIDMap[taskID] + if !ok { + return false + } + delete(cl.taskIDMap, taskID) + cl.nodes.Remove(element) + return true +} + +// Get obtains a task from the list given its task ID. If the task ID did not +// exist, the returned bool field will be false. +func (cl *ConfigList) Get(taskID int64) (*Config, bool) { + cl.cond.L.Lock() + defer cl.cond.L.Unlock() + element, ok := cl.taskIDMap[taskID] + if !ok { + return nil, false + } + return element.Value.(*Config), true +} + +// AllIDs returns a list of all task IDs in the list. +func (cl *ConfigList) AllIDs() []int64 { + cl.cond.L.Lock() + defer cl.cond.L.Unlock() + res := make([]int64, 0, len(cl.taskIDMap)) + for element := cl.nodes.Front(); element != nil; element = element.Next() { + res = append(res, element.Value.(*Config).TaskID) + } + return res +} + +// MoveToFront moves a task to the front of the list. Returns true if the task +// is successfully moved (including no-op), false if the task ID did not exist. +func (cl *ConfigList) MoveToFront(taskID int64) bool { + cl.cond.L.Lock() + defer cl.cond.L.Unlock() + element, ok := cl.taskIDMap[taskID] + if !ok { + return false + } + cl.nodes.MoveToFront(element) + return true +} + +// MoveToBack moves a task to the back of the list. Returns true if the task is +// successfully moved (including no-op), false if the task ID did not exist. +func (cl *ConfigList) MoveToBack(taskID int64) bool { + cl.cond.L.Lock() + defer cl.cond.L.Unlock() + element, ok := cl.taskIDMap[taskID] + if !ok { + return false + } + cl.nodes.MoveToBack(element) + return true +} diff --git a/pkg/lightning/config/configlist_test.go b/pkg/lightning/config/configlist_test.go new file mode 100644 index 000000000..bc268c61a --- /dev/null +++ b/pkg/lightning/config/configlist_test.go @@ -0,0 +1,132 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package config_test + +import ( + "context" + "time" + + . "github.com/pingcap/check" + + "github.com/pingcap/br/pkg/lightning/config" +) + +var _ = Suite(&configListTestSuite{}) + +type configListTestSuite struct{} + +func (s *configListTestSuite) TestNormalPushPop(c *C) { + cl := config.NewConfigList() + + cl.Push(&config.Config{TikvImporter: config.TikvImporter{Addr: "1.1.1.1:1111"}}) + cl.Push(&config.Config{TikvImporter: config.TikvImporter{Addr: "2.2.2.2:2222"}}) + + startTime := time.Now() + cfg, err := cl.Pop(context.Background()) // these two should never block. + c.Assert(time.Since(startTime), Less, 100*time.Millisecond) + c.Assert(err, IsNil) + c.Assert(cfg.TikvImporter.Addr, Equals, "1.1.1.1:1111") + + startTime = time.Now() + cfg, err = cl.Pop(context.Background()) + c.Assert(time.Since(startTime), Less, 100*time.Millisecond) + c.Assert(err, IsNil) + c.Assert(cfg.TikvImporter.Addr, Equals, "2.2.2.2:2222") + + go func() { + time.Sleep(400 * time.Millisecond) + cl.Push(&config.Config{TikvImporter: config.TikvImporter{Addr: "3.3.3.3:3333"}}) + }() + + startTime = time.Now() + cfg, err = cl.Pop(context.Background()) // this should block for ≥400ms + c.Assert(time.Since(startTime), GreaterEqual, 400*time.Millisecond) + c.Assert(err, IsNil) + c.Assert(cfg.TikvImporter.Addr, Equals, "3.3.3.3:3333") +} + +func (s *configListTestSuite) TestContextCancel(c *C) { + ctx, cancel := context.WithCancel(context.Background()) + cl := config.NewConfigList() + + go func() { + time.Sleep(400 * time.Millisecond) + cancel() + }() + + startTime := time.Now() + _, err := cl.Pop(ctx) + c.Assert(time.Since(startTime), GreaterEqual, 400*time.Millisecond) + c.Assert(err, Equals, context.Canceled) +} + +func (s *configListTestSuite) TestGetRemove(c *C) { + cl := config.NewConfigList() + + cfg1 := &config.Config{TikvImporter: config.TikvImporter{Addr: "1.1.1.1:1111"}} + cl.Push(cfg1) + cfg2 := &config.Config{TikvImporter: config.TikvImporter{Addr: "2.2.2.2:2222"}} + cl.Push(cfg2) + cfg3 := &config.Config{TikvImporter: config.TikvImporter{Addr: "3.3.3.3:3333"}} + cl.Push(cfg3) + + cfg, ok := cl.Get(cfg2.TaskID) + c.Assert(ok, IsTrue) + c.Assert(cfg, Equals, cfg2) + _, ok = cl.Get(cfg3.TaskID + 1000) + c.Assert(ok, IsFalse) + + ok = cl.Remove(cfg2.TaskID) + c.Assert(ok, IsTrue) + ok = cl.Remove(cfg3.TaskID + 1000) + c.Assert(ok, IsFalse) + _, ok = cl.Get(cfg2.TaskID) + c.Assert(ok, IsFalse) + + var err error + cfg, err = cl.Pop(context.Background()) + c.Assert(err, IsNil) + c.Assert(cfg, Equals, cfg1) + + cfg, err = cl.Pop(context.Background()) + c.Assert(err, IsNil) + c.Assert(cfg, Equals, cfg3) +} + +func (s *configListTestSuite) TestMoveFrontBack(c *C) { + cl := config.NewConfigList() + + cfg1 := &config.Config{TikvImporter: config.TikvImporter{Addr: "1.1.1.1:1111"}} + cl.Push(cfg1) + cfg2 := &config.Config{TikvImporter: config.TikvImporter{Addr: "2.2.2.2:2222"}} + cl.Push(cfg2) + cfg3 := &config.Config{TikvImporter: config.TikvImporter{Addr: "3.3.3.3:3333"}} + cl.Push(cfg3) + + c.Assert(cl.AllIDs(), DeepEquals, []int64{cfg1.TaskID, cfg2.TaskID, cfg3.TaskID}) + + c.Assert(cl.MoveToFront(cfg2.TaskID), IsTrue) + c.Assert(cl.AllIDs(), DeepEquals, []int64{cfg2.TaskID, cfg1.TaskID, cfg3.TaskID}) + c.Assert(cl.MoveToFront(cfg2.TaskID), IsTrue) + c.Assert(cl.AllIDs(), DeepEquals, []int64{cfg2.TaskID, cfg1.TaskID, cfg3.TaskID}) + c.Assert(cl.MoveToFront(123456), IsFalse) + c.Assert(cl.AllIDs(), DeepEquals, []int64{cfg2.TaskID, cfg1.TaskID, cfg3.TaskID}) + + c.Assert(cl.MoveToBack(cfg2.TaskID), IsTrue) + c.Assert(cl.AllIDs(), DeepEquals, []int64{cfg1.TaskID, cfg3.TaskID, cfg2.TaskID}) + c.Assert(cl.MoveToBack(cfg2.TaskID), IsTrue) + c.Assert(cl.AllIDs(), DeepEquals, []int64{cfg1.TaskID, cfg3.TaskID, cfg2.TaskID}) + c.Assert(cl.MoveToBack(123456), IsFalse) + c.Assert(cl.AllIDs(), DeepEquals, []int64{cfg1.TaskID, cfg3.TaskID, cfg2.TaskID}) +} diff --git a/pkg/lightning/config/const.go b/pkg/lightning/config/const.go new file mode 100644 index 000000000..240d37e27 --- /dev/null +++ b/pkg/lightning/config/const.go @@ -0,0 +1,32 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "github.com/docker/go-units" +) + +const ( + // mydumper + ReadBlockSize ByteSize = 64 * units.KiB + MinRegionSize ByteSize = 256 * units.MiB + MaxRegionSize ByteSize = 256 * units.MiB + SplitRegionSize ByteSize = 96 * units.MiB + + BufferSizeScale = 5 + + defaultMaxAllowedPacket = 64 * units.MiB + + defaultBatchSize ByteSize = 100 * units.GiB +) diff --git a/pkg/lightning/config/global.go b/pkg/lightning/config/global.go new file mode 100644 index 000000000..3a24c4f29 --- /dev/null +++ b/pkg/lightning/config/global.go @@ -0,0 +1,284 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "flag" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "time" + + "github.com/BurntSushi/toml" + "github.com/carlmjohnson/flagext" + "github.com/pingcap/errors" + + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/log" +) + +type GlobalLightning struct { + log.Config + StatusAddr string `toml:"status-addr" json:"status-addr"` + ServerMode bool `toml:"server-mode" json:"server-mode"` + CheckRequirements bool `toml:"check-requirements" json:"check-requirements"` + + // The legacy alias for setting "status-addr". The value should always the + // same as StatusAddr, and will not be published in the JSON encoding. + PProfPort int `toml:"pprof-port" json:"-"` +} + +type GlobalTiDB struct { + Host string `toml:"host" json:"host"` + Port int `toml:"port" json:"port"` + User string `toml:"user" json:"user"` + Psw string `toml:"password" json:"-"` + StatusPort int `toml:"status-port" json:"status-port"` + PdAddr string `toml:"pd-addr" json:"pd-addr"` + LogLevel string `toml:"log-level" json:"log-level"` +} + +type GlobalMydumper struct { + SourceDir string `toml:"data-source-dir" json:"data-source-dir"` + NoSchema bool `toml:"no-schema" json:"no-schema"` + Filter []string `toml:"filter" json:"filter"` +} + +type GlobalImporter struct { + Addr string `toml:"addr" json:"addr"` + Backend string `toml:"backend" json:"backend"` + SortedKVDir string `toml:"sorted-kv-dir" json:"sorted-kv-dir"` +} + +type GlobalConfig struct { + App GlobalLightning `toml:"lightning" json:"lightning"` + Checkpoint GlobalCheckpoint `toml:"checkpoint" json:"checkpoint"` + TiDB GlobalTiDB `toml:"tidb" json:"tidb"` + Mydumper GlobalMydumper `toml:"mydumper" json:"mydumper"` + TikvImporter GlobalImporter `toml:"tikv-importer" json:"tikv-importer"` + PostRestore GlobalPostRestore `toml:"post-restore" json:"post-restore"` + Security Security `toml:"security" json:"security"` + + ConfigFileContent []byte +} + +type GlobalCheckpoint struct { + Enable bool `toml:"enable" json:"enable"` +} + +type GlobalPostRestore struct { + Checksum PostOpLevel `toml:"checksum" json:"checksum"` + Analyze PostOpLevel `toml:"analyze" json:"analyze"` +} + +func NewGlobalConfig() *GlobalConfig { + return &GlobalConfig{ + App: GlobalLightning{ + ServerMode: false, + CheckRequirements: true, + }, + Checkpoint: GlobalCheckpoint{ + Enable: true, + }, + TiDB: GlobalTiDB{ + Host: "127.0.0.1", + User: "root", + StatusPort: 10080, + LogLevel: "error", + }, + Mydumper: GlobalMydumper{ + Filter: DefaultFilter, + }, + TikvImporter: GlobalImporter{ + Backend: "importer", + }, + PostRestore: GlobalPostRestore{ + Checksum: OpLevelRequired, + Analyze: OpLevelOptional, + }, + } +} + +// Must should be called after LoadGlobalConfig(). If LoadGlobalConfig() returns +// any error, this function will exit the program with an appropriate exit code. +func Must(cfg *GlobalConfig, err error) *GlobalConfig { + switch errors.Cause(err) { + case nil: + case flag.ErrHelp: + os.Exit(0) + default: + fmt.Println("Failed to parse command flags: ", err) + os.Exit(2) + } + return cfg +} + +func timestampLogFileName() string { + return filepath.Join(os.TempDir(), time.Now().Format("lightning.log.2006-01-02T15.04.05Z0700")) +} + +// LoadGlobalConfig reads the arguments and fills in the GlobalConfig. +func LoadGlobalConfig(args []string, extraFlags func(*flag.FlagSet)) (*GlobalConfig, error) { + cfg := NewGlobalConfig() + fs := flag.NewFlagSet("", flag.ContinueOnError) + + // if both `-c` and `-config` are specified, the last one in the command line will take effect. + // the default value is assigned immediately after the StringVar() call, + // so it is fine to not give any default value for `-c`, to keep the `-h` page clean. + var configFilePath string + fs.StringVar(&configFilePath, "c", "", "(deprecated alias of -config)") + fs.StringVar(&configFilePath, "config", "", "tidb-lightning configuration file") + printVersion := fs.Bool("V", false, "print version of lightning") + + logLevel := flagext.ChoiceVar(fs, "L", "", `log level: info, debug, warn, error, fatal (default info)`, "", "info", "debug", "warn", "warning", "error", "fatal") + logFilePath := fs.String("log-file", "", "log file path") + tidbHost := fs.String("tidb-host", "", "TiDB server host") + tidbPort := fs.Int("tidb-port", 0, "TiDB server port (default 4000)") + tidbUser := fs.String("tidb-user", "", "TiDB user name to connect") + tidbPsw := fs.String("tidb-password", "", "TiDB password to connect") + tidbStatusPort := fs.Int("tidb-status", 0, "TiDB server status port (default 10080)") + pdAddr := fs.String("pd-urls", "", "PD endpoint address") + dataSrcPath := fs.String("d", "", "Directory of the dump to import") + importerAddr := fs.String("importer", "", "address (host:port) to connect to tikv-importer") + backend := flagext.ChoiceVar(fs, "backend", "", `delivery backend: importer, tidb, local (default importer)`, "", "importer", "tidb", "local") + sortedKVDir := fs.String("sorted-kv-dir", "", "path for KV pairs when local backend enabled") + enableCheckpoint := fs.Bool("enable-checkpoint", true, "whether to enable checkpoints") + noSchema := fs.Bool("no-schema", false, "ignore schema files, get schema directly from TiDB instead") + checksum := flagext.ChoiceVar(fs, "checksum", "", "compare checksum after importing.", "", "required", "optional", "off", "true", "false") + analyze := flagext.ChoiceVar(fs, "analyze", "", "analyze table after importing", "", "required", "optional", "off", "true", "false") + checkRequirements := fs.Bool("check-requirements", true, "check cluster version before starting") + tlsCAPath := fs.String("ca", "", "CA certificate path for TLS connection") + tlsCertPath := fs.String("cert", "", "certificate path for TLS connection") + tlsKeyPath := fs.String("key", "", "private key path for TLS connection") + redactInfoLog := fs.Bool("redact-info-log", false, "whether to redact sensitive info in log") + + statusAddr := fs.String("status-addr", "", "the Lightning server address") + serverMode := fs.Bool("server-mode", false, "start Lightning in server mode, wait for multiple tasks instead of starting immediately") + + var filter []string + flagext.StringsVar(fs, &filter, "f", "select tables to import") + + if extraFlags != nil { + extraFlags(fs) + } + + if err := fs.Parse(args); err != nil { + return nil, errors.Trace(err) + } + if *printVersion { + fmt.Println(common.GetRawInfo()) + return nil, flag.ErrHelp + } + + if len(configFilePath) > 0 { + data, err := ioutil.ReadFile(configFilePath) + if err != nil { + return nil, errors.Annotatef(err, "Cannot read config file `%s`", configFilePath) + } + if err = toml.Unmarshal(data, cfg); err != nil { + return nil, errors.Annotatef(err, "Cannot parse config file `%s`", configFilePath) + } + cfg.ConfigFileContent = data + } + + if *logLevel != "" { + cfg.App.Config.Level = *logLevel + } + if *logFilePath != "" { + cfg.App.Config.File = *logFilePath + } + // "-" is a special config for log to stdout + if cfg.App.Config.File == "-" { + cfg.App.Config.File = "" + } else if cfg.App.Config.File == "" { + cfg.App.Config.File = timestampLogFileName() + } + if *tidbHost != "" { + cfg.TiDB.Host = *tidbHost + } + if *tidbPort != 0 { + cfg.TiDB.Port = *tidbPort + } + if *tidbStatusPort != 0 { + cfg.TiDB.StatusPort = *tidbStatusPort + } + if *tidbUser != "" { + cfg.TiDB.User = *tidbUser + } + if *tidbPsw != "" { + cfg.TiDB.Psw = *tidbPsw + } + if *pdAddr != "" { + cfg.TiDB.PdAddr = *pdAddr + } + if *dataSrcPath != "" { + cfg.Mydumper.SourceDir = *dataSrcPath + } + if *importerAddr != "" { + cfg.TikvImporter.Addr = *importerAddr + } + if *serverMode { + cfg.App.ServerMode = true + } + if *statusAddr != "" { + cfg.App.StatusAddr = *statusAddr + } + if *backend != "" { + cfg.TikvImporter.Backend = *backend + } + if *sortedKVDir != "" { + cfg.TikvImporter.SortedKVDir = *sortedKVDir + } + if !*enableCheckpoint { + cfg.Checkpoint.Enable = false + } + if *noSchema { + cfg.Mydumper.NoSchema = true + } + if *checksum != "" { + _ = cfg.PostRestore.Checksum.FromStringValue(*checksum) + } + if *analyze != "" { + _ = cfg.PostRestore.Analyze.FromStringValue(*analyze) + } + if cfg.App.StatusAddr == "" && cfg.App.PProfPort != 0 { + cfg.App.StatusAddr = fmt.Sprintf(":%d", cfg.App.PProfPort) + } + if !*checkRequirements { + cfg.App.CheckRequirements = false + } + if *tlsCAPath != "" { + cfg.Security.CAPath = *tlsCAPath + } + if *tlsCertPath != "" { + cfg.Security.CertPath = *tlsCertPath + } + if *tlsKeyPath != "" { + cfg.Security.KeyPath = *tlsKeyPath + } + if *redactInfoLog { + cfg.Security.RedactInfoLog = *redactInfoLog + } + if len(filter) > 0 { + cfg.Mydumper.Filter = filter + } + + if cfg.App.StatusAddr == "" && cfg.App.ServerMode { + return nil, errors.New("If server-mode is enabled, the status-addr must be a valid listen address") + } + + cfg.App.Config.Adjust() + return cfg, nil +} diff --git a/pkg/lightning/glue/glue.go b/pkg/lightning/glue/glue.go new file mode 100644 index 000000000..cf3a17f5d --- /dev/null +++ b/pkg/lightning/glue/glue.go @@ -0,0 +1,191 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package glue + +import ( + "context" + "database/sql" + "errors" + + "github.com/pingcap/parser" + "github.com/pingcap/parser/ast" + "github.com/pingcap/parser/model" + "github.com/pingcap/parser/mysql" + "github.com/pingcap/tidb/types" + "github.com/pingcap/tidb/util/sqlexec" + + "github.com/pingcap/br/pkg/lightning/checkpoints" + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/config" + "github.com/pingcap/br/pkg/lightning/log" +) + +type Glue interface { + OwnsSQLExecutor() bool + GetSQLExecutor() SQLExecutor + GetDB() (*sql.DB, error) + GetParser() *parser.Parser + GetTables(context.Context, string) ([]*model.TableInfo, error) + GetSession(context.Context) (checkpoints.Session, error) + OpenCheckpointsDB(context.Context, *config.Config) (checkpoints.CheckpointsDB, error) + // Record is used to report some information (key, value) to host TiDB, including progress, stage currently + Record(string, uint64) +} + +type SQLExecutor interface { + // ExecuteWithLog and ObtainStringWithLog should support concurrently call and can't assure different calls goes to + // same underlying connection + ExecuteWithLog(ctx context.Context, query string, purpose string, logger log.Logger) error + ObtainStringWithLog(ctx context.Context, query string, purpose string, logger log.Logger) (string, error) + QueryStringsWithLog(ctx context.Context, query string, purpose string, logger log.Logger) ([][]string, error) + Close() +} + +// sqlConnSession implement checkpoints.Session used only for lighting itself +type sqlConnSession struct { + checkpoints.Session + conn *sql.Conn +} + +func (session *sqlConnSession) Close() { + session.conn.Close() +} + +func (session *sqlConnSession) Execute(ctx context.Context, sql string) ([]sqlexec.RecordSet, error) { + _, err := session.conn.ExecContext(ctx, sql) + return nil, err +} + +func (session *sqlConnSession) CommitTxn(context.Context) error { + return errors.New("sqlConnSession doesn't have a valid CommitTxn implementation") +} + +func (session *sqlConnSession) RollbackTxn(context.Context) {} + +func (session *sqlConnSession) PrepareStmt(sql string) (stmtID uint32, paramCount int, fields []*ast.ResultField, err error) { + return 0, 0, nil, errors.New("sqlConnSession doesn't have a valid PrepareStmt implementation") +} + +func (session *sqlConnSession) ExecutePreparedStmt(ctx context.Context, stmtID uint32, param []types.Datum) (sqlexec.RecordSet, error) { + return nil, errors.New("sqlConnSession doesn't have a valid ExecutePreparedStmt implementation") +} + +func (session *sqlConnSession) DropPreparedStmt(stmtID uint32) error { + return errors.New("sqlConnSession doesn't have a valid DropPreparedStmt implementation") +} + +type ExternalTiDBGlue struct { + db *sql.DB + parser *parser.Parser +} + +func NewExternalTiDBGlue(db *sql.DB, sqlMode mysql.SQLMode) *ExternalTiDBGlue { + p := parser.New() + p.SetSQLMode(sqlMode) + + return &ExternalTiDBGlue{db: db, parser: p} +} + +func (e *ExternalTiDBGlue) GetSQLExecutor() SQLExecutor { + return e +} + +func (e *ExternalTiDBGlue) ExecuteWithLog(ctx context.Context, query string, purpose string, logger log.Logger) error { + sql := common.SQLWithRetry{ + DB: e.db, + Logger: logger, + } + return sql.Exec(ctx, purpose, query) +} + +func (e *ExternalTiDBGlue) ObtainStringWithLog(ctx context.Context, query string, purpose string, logger log.Logger) (string, error) { + var s string + err := common.SQLWithRetry{ + DB: e.db, + Logger: logger, + }.QueryRow(ctx, purpose, query, &s) + return s, err +} + +func (e *ExternalTiDBGlue) QueryStringsWithLog(ctx context.Context, query string, purpose string, logger log.Logger) (result [][]string, finalErr error) { + finalErr = common.SQLWithRetry{ + DB: e.db, + Logger: logger, + }.Transact(ctx, purpose, func(c context.Context, tx *sql.Tx) (txErr error) { + rows, err := tx.QueryContext(c, query) + if err != nil { + return err + } + defer rows.Close() + + colNames, err := rows.Columns() + if err != nil { + return err + } + for rows.Next() { + row := make([]string, len(colNames)) + refs := make([]interface{}, 0, len(row)) + for i := range row { + refs = append(refs, &row[i]) + } + if err := rows.Scan(refs...); err != nil { + return err + } + result = append(result, row) + } + + return rows.Err() + }) + return +} + +func (e *ExternalTiDBGlue) GetDB() (*sql.DB, error) { + return e.db, nil +} + +func (e *ExternalTiDBGlue) GetParser() *parser.Parser { + return e.parser +} + +func (e ExternalTiDBGlue) GetTables(context.Context, string) ([]*model.TableInfo, error) { + return nil, errors.New("ExternalTiDBGlue doesn't have a valid GetTables function") +} + +func (e *ExternalTiDBGlue) GetSession(ctx context.Context) (checkpoints.Session, error) { + conn, err := e.db.Conn(ctx) + if err != nil { + return nil, err + } + return &sqlConnSession{conn: conn}, nil +} + +func (e *ExternalTiDBGlue) OpenCheckpointsDB(ctx context.Context, cfg *config.Config) (checkpoints.CheckpointsDB, error) { + return checkpoints.OpenCheckpointsDB(ctx, cfg) +} + +func (e *ExternalTiDBGlue) OwnsSQLExecutor() bool { + return true +} + +func (e *ExternalTiDBGlue) Close() { + e.db.Close() +} + +func (e *ExternalTiDBGlue) Record(string, uint64) { +} + +const ( + RecordEstimatedChunk = "EstimatedChunk" + RecordFinishedChunk = "FinishedChunk" +) diff --git a/pkg/lightning/lightning.go b/pkg/lightning/lightning.go new file mode 100755 index 000000000..ea5ddea30 --- /dev/null +++ b/pkg/lightning/lightning.go @@ -0,0 +1,701 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package lightning + +import ( + "compress/gzip" + "context" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/http/pprof" + "os" + "sort" + "strconv" + "strings" + "sync" + "time" + + "github.com/pingcap/errors" + "github.com/pingcap/failpoint" + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/shurcooL/httpgzip" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "golang.org/x/net/http/httpproxy" + + "github.com/pingcap/br/pkg/lightning/backend" + "github.com/pingcap/br/pkg/lightning/checkpoints" + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/config" + "github.com/pingcap/br/pkg/lightning/glue" + "github.com/pingcap/br/pkg/lightning/log" + "github.com/pingcap/br/pkg/lightning/mydump" + "github.com/pingcap/br/pkg/lightning/restore" + "github.com/pingcap/br/pkg/lightning/web" + "github.com/pingcap/br/pkg/storage" +) + +type Lightning struct { + globalCfg *config.GlobalConfig + globalTLS *common.TLS + // taskCfgs is the list of task configurations enqueued in the server mode + taskCfgs *config.ConfigList + ctx context.Context + shutdown context.CancelFunc // for whole lightning context + server http.Server + serverAddr net.Addr + serverLock sync.Mutex + + cancelLock sync.Mutex + curTask *config.Config + cancel context.CancelFunc // for per task context, which maybe different from lightning context +} + +func initEnv(cfg *config.GlobalConfig) error { + return log.InitLogger(&cfg.App.Config, cfg.TiDB.LogLevel) +} + +func New(globalCfg *config.GlobalConfig) *Lightning { + if err := initEnv(globalCfg); err != nil { + fmt.Println("Failed to initialize environment:", err) + os.Exit(1) + } + + tls, err := common.NewTLS(globalCfg.Security.CAPath, globalCfg.Security.CertPath, globalCfg.Security.KeyPath, globalCfg.App.StatusAddr) + if err != nil { + log.L().Fatal("failed to load TLS certificates", zap.Error(err)) + } + + log.InitRedact(globalCfg.Security.RedactInfoLog) + + ctx, shutdown := context.WithCancel(context.Background()) + return &Lightning{ + globalCfg: globalCfg, + globalTLS: tls, + ctx: ctx, + shutdown: shutdown, + } +} + +func (l *Lightning) GoServe() error { + handleSigUsr1(func() { + l.serverLock.Lock() + statusAddr := l.globalCfg.App.StatusAddr + shouldStartServer := len(statusAddr) == 0 + if shouldStartServer { + l.globalCfg.App.StatusAddr = ":" + } + l.serverLock.Unlock() + + if shouldStartServer { + // open a random port and start the server if SIGUSR1 is received. + if err := l.goServe(":", os.Stderr); err != nil { + log.L().Warn("failed to start HTTP server", log.ShortError(err)) + } + } else { + // just prints the server address if it is already started. + log.L().Info("already started HTTP server", zap.Stringer("address", l.serverAddr)) + } + }) + + l.serverLock.Lock() + statusAddr := l.globalCfg.App.StatusAddr + l.serverLock.Unlock() + + if len(statusAddr) == 0 { + return nil + } + return l.goServe(statusAddr, ioutil.Discard) +} + +func (l *Lightning) goServe(statusAddr string, realAddrWriter io.Writer) error { + mux := http.NewServeMux() + mux.Handle("/", http.RedirectHandler("/web/", http.StatusFound)) + mux.Handle("/metrics", promhttp.Handler()) + + mux.HandleFunc("/debug/pprof/", pprof.Index) + mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) + mux.HandleFunc("/debug/pprof/profile", pprof.Profile) + mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) + mux.HandleFunc("/debug/pprof/trace", pprof.Trace) + + handleTasks := http.StripPrefix("/tasks", http.HandlerFunc(l.handleTask)) + mux.Handle("/tasks", handleTasks) + mux.Handle("/tasks/", handleTasks) + mux.HandleFunc("/progress/task", handleProgressTask) + mux.HandleFunc("/progress/table", handleProgressTable) + mux.HandleFunc("/pause", handlePause) + mux.HandleFunc("/resume", handleResume) + mux.HandleFunc("/loglevel", handleLogLevel) + + mux.Handle("/web/", http.StripPrefix("/web", httpgzip.FileServer(web.Res, httpgzip.FileServerOptions{ + IndexHTML: true, + ServeError: func(w http.ResponseWriter, req *http.Request, err error) { + if os.IsNotExist(err) && !strings.Contains(req.URL.Path, ".") { + http.Redirect(w, req, "/web/", http.StatusFound) + } else { + httpgzip.NonSpecific(w, req, err) + } + }, + }))) + + listener, err := net.Listen("tcp", statusAddr) + if err != nil { + return err + } + l.serverAddr = listener.Addr() + log.L().Info("starting HTTP server", zap.Stringer("address", l.serverAddr)) + fmt.Fprintln(realAddrWriter, "started HTTP server on", l.serverAddr) + l.server.Handler = mux + listener = l.globalTLS.WrapListener(listener) + + go func() { + err := l.server.Serve(listener) + log.L().Info("stopped HTTP server", log.ShortError(err)) + }() + return nil +} + +// RunOnce is used by binary lightning and host when using lightning as a library. +// - for binary lightning, taskCtx could be context.Background which means taskCtx wouldn't be canceled directly by its +// cancel function, but only by Lightning.Stop or HTTP DELETE using l.cancel. and glue could be nil to let lightning +// use a default glue later. +// - for lightning as a library, taskCtx could be a meaningful context that get canceled outside, and glue could be a +// caller implemented glue. +func (l *Lightning) RunOnce(taskCtx context.Context, taskCfg *config.Config, glue glue.Glue, replaceLogger *zap.Logger) error { + if err := taskCfg.Adjust(taskCtx); err != nil { + return err + } + + taskCfg.TaskID = time.Now().UnixNano() + failpoint.Inject("SetTaskID", func(val failpoint.Value) { + taskCfg.TaskID = int64(val.(int)) + }) + + if replaceLogger != nil { + log.SetAppLogger(replaceLogger) + } + return l.run(taskCtx, taskCfg, glue) +} + +func (l *Lightning) RunServer() error { + l.taskCfgs = config.NewConfigList() + log.L().Info( + "Lightning server is running, post to /tasks to start an import task", + zap.Stringer("address", l.serverAddr), + ) + + for { + task, err := l.taskCfgs.Pop(l.ctx) + if err != nil { + return err + } + err = l.run(context.Background(), task, nil) + if err != nil { + restore.DeliverPauser.Pause() // force pause the progress on error + log.L().Error("tidb lightning encountered error", zap.Error(err)) + } + } +} + +var taskCfgRecorderKey struct{} + +func (l *Lightning) run(taskCtx context.Context, taskCfg *config.Config, g glue.Glue) (err error) { + common.PrintInfo("lightning", func() { + log.L().Info("cfg", zap.Stringer("cfg", taskCfg)) + }) + + logEnvVariables() + + ctx, cancel := context.WithCancel(taskCtx) + l.cancelLock.Lock() + l.cancel = cancel + l.curTask = taskCfg + l.cancelLock.Unlock() + web.BroadcastStartTask() + + defer func() { + cancel() + l.cancelLock.Lock() + l.cancel = nil + l.cancelLock.Unlock() + web.BroadcastEndTask(err) + }() + + failpoint.Inject("SkipRunTask", func() { + if recorder, ok := l.ctx.Value(&taskCfgRecorderKey).(chan *config.Config); ok { + select { + case recorder <- taskCfg: + case <-ctx.Done(): + failpoint.Return(ctx.Err()) + } + } + failpoint.Return(nil) + }) + + if err := taskCfg.TiDB.Security.RegisterMySQL(); err != nil { + return err + } + defer func() { + // deregister TLS config with name "cluster" + if taskCfg.TiDB.Security == nil { + return + } + taskCfg.TiDB.Security.CAPath = "" + taskCfg.TiDB.Security.RegisterMySQL() + }() + + // initiation of default glue should be after RegisterMySQL, which is ready to be called after taskCfg.Adjust + // and also put it here could avoid injecting another two SkipRunTask failpoint to caller + if g == nil { + db, err := restore.DBFromConfig(taskCfg.TiDB) + if err != nil { + return err + } + g = glue.NewExternalTiDBGlue(db, taskCfg.TiDB.SQLMode) + } + + u, err := storage.ParseBackend(taskCfg.Mydumper.SourceDir, &storage.BackendOptions{}) + if err != nil { + return errors.Annotate(err, "parse backend failed") + } + s, err := storage.Create(ctx, u, true) + if err != nil { + return errors.Annotate(err, "create storage failed") + } + + loadTask := log.L().Begin(zap.InfoLevel, "load data source") + var mdl *mydump.MDLoader + mdl, err = mydump.NewMyDumpLoaderWithStore(ctx, taskCfg, s) + loadTask.End(zap.ErrorLevel, err) + if err != nil { + return errors.Trace(err) + } + err = checkSystemRequirement(taskCfg, mdl.GetDatabases()) + if err != nil { + log.L().Error("check system requirements failed", zap.Error(err)) + return errors.Trace(err) + } + // check table schema conflicts + err = checkSchemaConflict(taskCfg, mdl.GetDatabases()) + if err != nil { + log.L().Error("checkpoint schema conflicts with data files", zap.Error(err)) + return errors.Trace(err) + } + + dbMetas := mdl.GetDatabases() + web.BroadcastInitProgress(dbMetas) + + var procedure *restore.RestoreController + procedure, err = restore.NewRestoreController(ctx, dbMetas, taskCfg, s, g) + if err != nil { + log.L().Error("restore failed", log.ShortError(err)) + return errors.Trace(err) + } + defer procedure.Close() + + err = procedure.Run(ctx) + return errors.Trace(err) +} + +func (l *Lightning) Stop() { + l.cancelLock.Lock() + if l.cancel != nil { + l.cancel() + } + l.cancelLock.Unlock() + if err := l.server.Shutdown(l.ctx); err != nil { + log.L().Warn("failed to shutdown HTTP server", log.ShortError(err)) + } + l.shutdown() +} + +// logEnvVariables add related environment variables to log +func logEnvVariables() { + // log http proxy settings, it will be used in gRPC connection by default + proxyCfg := httpproxy.FromEnvironment() + if proxyCfg.HTTPProxy != "" || proxyCfg.HTTPSProxy != "" { + log.L().Info("environment variables", zap.Reflect("httpproxy", proxyCfg)) + } +} + +func writeJSONError(w http.ResponseWriter, code int, prefix string, err error) { + type errorResponse struct { + Error string `json:"error"` + } + + w.WriteHeader(code) + + if err != nil { + prefix += ": " + err.Error() + } + json.NewEncoder(w).Encode(errorResponse{Error: prefix}) +} + +func parseTaskID(req *http.Request) (int64, string, error) { + path := strings.TrimPrefix(req.URL.Path, "/") + taskIDString := path + verb := "" + if i := strings.IndexByte(path, '/'); i >= 0 { + taskIDString = path[:i] + verb = path[i+1:] + } + + taskID, err := strconv.ParseInt(taskIDString, 10, 64) + if err != nil { + return 0, "", err + } + + return taskID, verb, nil +} + +func (l *Lightning) handleTask(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json") + + switch req.Method { + case http.MethodGet: + taskID, _, err := parseTaskID(req) + if e, ok := err.(*strconv.NumError); ok && e.Num == "" { + l.handleGetTask(w) + } else if err == nil { + l.handleGetOneTask(w, req, taskID) + } else { + writeJSONError(w, http.StatusBadRequest, "invalid task ID", err) + } + case http.MethodPost: + l.handlePostTask(w, req) + case http.MethodDelete: + l.handleDeleteOneTask(w, req) + case http.MethodPatch: + l.handlePatchOneTask(w, req) + default: + w.Header().Set("Allow", http.MethodGet+", "+http.MethodPost+", "+http.MethodDelete+", "+http.MethodPatch) + writeJSONError(w, http.StatusMethodNotAllowed, "only GET, POST, DELETE and PATCH are allowed", nil) + } +} + +func (l *Lightning) handleGetTask(w http.ResponseWriter) { + var response struct { + Current *int64 `json:"current"` + QueuedIDs []int64 `json:"queue"` + } + + if l.taskCfgs != nil { + response.QueuedIDs = l.taskCfgs.AllIDs() + } else { + response.QueuedIDs = []int64{} + } + + l.cancelLock.Lock() + if l.cancel != nil && l.curTask != nil { + response.Current = new(int64) + *response.Current = l.curTask.TaskID + } + l.cancelLock.Unlock() + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(response) +} + +func (l *Lightning) handleGetOneTask(w http.ResponseWriter, req *http.Request, taskID int64) { + var task *config.Config + + l.cancelLock.Lock() + if l.curTask != nil && l.curTask.TaskID == taskID { + task = l.curTask + } + l.cancelLock.Unlock() + + if task == nil && l.taskCfgs != nil { + task, _ = l.taskCfgs.Get(taskID) + } + + if task == nil { + writeJSONError(w, http.StatusNotFound, "task ID not found", nil) + return + } + + json, err := json.Marshal(task) + if err != nil { + writeJSONError(w, http.StatusInternalServerError, "unable to serialize task", err) + return + } + + writeBytesCompressed(w, req, json) +} + +func (l *Lightning) handlePostTask(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Cache-Control", "no-store") + + if l.taskCfgs == nil { + // l.taskCfgs is non-nil only if Lightning is started with RunServer(). + // Without the server mode this pointer is default to be nil. + writeJSONError(w, http.StatusNotImplemented, "server-mode not enabled", nil) + return + } + + type taskResponse struct { + ID int64 `json:"id"` + } + + data, err := ioutil.ReadAll(req.Body) + if err != nil { + writeJSONError(w, http.StatusBadRequest, "cannot read request", err) + return + } + log.L().Debug("received task config", zap.ByteString("content", data)) + + cfg := config.NewConfig() + if err = cfg.LoadFromGlobal(l.globalCfg); err != nil { + writeJSONError(w, http.StatusInternalServerError, "cannot restore from global config", err) + return + } + if err = cfg.LoadFromTOML(data); err != nil { + writeJSONError(w, http.StatusBadRequest, "cannot parse task (must be TOML)", err) + return + } + if err = cfg.Adjust(l.ctx); err != nil { + writeJSONError(w, http.StatusBadRequest, "invalid task configuration", err) + return + } + + l.taskCfgs.Push(cfg) + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(taskResponse{ID: cfg.TaskID}) +} + +func (l *Lightning) handleDeleteOneTask(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json") + + taskID, _, err := parseTaskID(req) + if err != nil { + writeJSONError(w, http.StatusBadRequest, "invalid task ID", err) + return + } + + var cancel context.CancelFunc + cancelSuccess := false + + l.cancelLock.Lock() + if l.cancel != nil && l.curTask != nil && l.curTask.TaskID == taskID { + cancel = l.cancel + l.cancel = nil + } + l.cancelLock.Unlock() + + if cancel != nil { + cancel() + cancelSuccess = true + } else if l.taskCfgs != nil { + cancelSuccess = l.taskCfgs.Remove(taskID) + } + + log.L().Info("canceled task", zap.Int64("taskID", taskID), zap.Bool("success", cancelSuccess)) + + if cancelSuccess { + w.WriteHeader(http.StatusOK) + w.Write([]byte("{}")) + } else { + writeJSONError(w, http.StatusNotFound, "task ID not found", nil) + } +} + +func (l *Lightning) handlePatchOneTask(w http.ResponseWriter, req *http.Request) { + if l.taskCfgs == nil { + writeJSONError(w, http.StatusNotImplemented, "server-mode not enabled", nil) + return + } + + taskID, verb, err := parseTaskID(req) + if err != nil { + writeJSONError(w, http.StatusBadRequest, "invalid task ID", err) + return + } + + moveSuccess := false + switch verb { + case "front": + moveSuccess = l.taskCfgs.MoveToFront(taskID) + case "back": + moveSuccess = l.taskCfgs.MoveToBack(taskID) + default: + writeJSONError(w, http.StatusBadRequest, "unknown patch action", nil) + return + } + + if moveSuccess { + w.WriteHeader(http.StatusOK) + w.Write([]byte("{}")) + } else { + writeJSONError(w, http.StatusNotFound, "task ID not found", nil) + } +} + +func writeBytesCompressed(w http.ResponseWriter, req *http.Request, b []byte) { + if !strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") { + w.Write(b) + return + } + + w.Header().Set("Content-Encoding", "gzip") + w.WriteHeader(http.StatusOK) + gw, _ := gzip.NewWriterLevel(w, gzip.BestSpeed) + gw.Write(b) + gw.Close() +} + +func handleProgressTask(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json") + res, err := web.MarshalTaskProgress() + if err == nil { + writeBytesCompressed(w, req, res) + } else { + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(err.Error()) + } +} + +func handleProgressTable(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json") + tableName := req.URL.Query().Get("t") + res, err := web.MarshalTableCheckpoints(tableName) + if err == nil { + writeBytesCompressed(w, req, res) + } else { + if errors.IsNotFound(err) { + w.WriteHeader(http.StatusNotFound) + } else { + w.WriteHeader(http.StatusInternalServerError) + } + json.NewEncoder(w).Encode(err.Error()) + } +} + +func handlePause(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json") + + switch req.Method { + case http.MethodGet: + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, `{"paused":%v}`, restore.DeliverPauser.IsPaused()) + + case http.MethodPut: + w.WriteHeader(http.StatusOK) + restore.DeliverPauser.Pause() + log.L().Info("progress paused") + w.Write([]byte("{}")) + + default: + w.Header().Set("Allow", http.MethodGet+", "+http.MethodPut) + writeJSONError(w, http.StatusMethodNotAllowed, "only GET and PUT are allowed", nil) + } +} + +func handleResume(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json") + + switch req.Method { + case http.MethodPut: + w.WriteHeader(http.StatusOK) + restore.DeliverPauser.Resume() + log.L().Info("progress resumed") + w.Write([]byte("{}")) + + default: + w.Header().Set("Allow", http.MethodPut) + writeJSONError(w, http.StatusMethodNotAllowed, "only PUT is allowed", nil) + } +} + +func handleLogLevel(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json") + + var logLevel struct { + Level zapcore.Level `json:"level"` + } + + switch req.Method { + case http.MethodGet: + logLevel.Level = log.Level() + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(logLevel) + + case http.MethodPut, http.MethodPost: + if err := json.NewDecoder(req.Body).Decode(&logLevel); err != nil { + writeJSONError(w, http.StatusBadRequest, "invalid log level", err) + return + } + oldLevel := log.SetLevel(zapcore.InfoLevel) + log.L().Info("changed log level", zap.Stringer("old", oldLevel), zap.Stringer("new", logLevel.Level)) + log.SetLevel(logLevel.Level) + w.WriteHeader(http.StatusOK) + w.Write([]byte("{}")) + + default: + w.Header().Set("Allow", http.MethodGet+", "+http.MethodPut+", "+http.MethodPost) + writeJSONError(w, http.StatusMethodNotAllowed, "only GET, PUT and POST are allowed", nil) + } +} + +func checkSystemRequirement(cfg *config.Config, dbsMeta []*mydump.MDDatabaseMeta) error { + if !cfg.App.CheckRequirements { + log.L().Info("check-requirement is disabled, skip check system rlimit") + return nil + } + + // in local mode, we need to read&write a lot of L0 sst files, so we need to check system max open files limit + if cfg.TikvImporter.Backend == config.BackendLocal { + // estimate max open files = {top N(TableConcurrency) table sizes} / {MemoryTableSize} + tableTotalSizes := make([]int64, 0) + for _, dbs := range dbsMeta { + for _, tb := range dbs.Tables { + tableTotalSizes = append(tableTotalSizes, tb.TotalSize) + } + } + sort.Slice(tableTotalSizes, func(i, j int) bool { + return tableTotalSizes[i] > tableTotalSizes[j] + }) + topNTotalSize := int64(0) + for i := 0; i < len(tableTotalSizes) && i < cfg.App.TableConcurrency; i++ { + topNTotalSize += tableTotalSizes[i] + } + + estimateMaxFiles := uint64(topNTotalSize/backend.LocalMemoryTableSize) * 2 + if err := backend.VerifyRLimit(estimateMaxFiles); err != nil { + return err + } + } + + return nil +} + +/// checkSchemaConflict return error if checkpoint table scheme is conflict with data files +func checkSchemaConflict(cfg *config.Config, dbsMeta []*mydump.MDDatabaseMeta) error { + if cfg.Checkpoint.Enable && cfg.Checkpoint.Driver == config.CheckpointDriverMySQL { + for _, db := range dbsMeta { + if db.Name == cfg.Checkpoint.Schema { + for _, tb := range db.Tables { + if checkpoints.IsCheckpointTable(tb.Name) { + return errors.Errorf("checkpoint table `%s`.`%s` conflict with data files. Please change the `checkpoint.schema` config or set `checkpoint.driver` to \"file\" instead", db.Name, tb.Name) + } + } + } + } + } + return nil +} diff --git a/pkg/lightning/lightning_test.go b/pkg/lightning/lightning_test.go new file mode 100644 index 000000000..4da5e73ad --- /dev/null +++ b/pkg/lightning/lightning_test.go @@ -0,0 +1,547 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package lightning + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + . "github.com/pingcap/check" + "github.com/pingcap/failpoint" + + "github.com/pingcap/br/pkg/lightning/checkpoints" + "github.com/pingcap/br/pkg/lightning/config" + "github.com/pingcap/br/pkg/lightning/glue" + "github.com/pingcap/br/pkg/lightning/mydump" +) + +type lightningSuite struct{} + +var _ = Suite(&lightningSuite{}) + +func TestLightning(t *testing.T) { + TestingT(t) +} + +func (s *lightningSuite) TestInitEnv(c *C) { + cfg := &config.GlobalConfig{ + App: config.GlobalLightning{StatusAddr: ":45678"}, + } + err := initEnv(cfg) + c.Assert(err, IsNil) + cfg.App.StatusAddr = "" + cfg.App.Config.File = "." + err = initEnv(cfg) + c.Assert(err, ErrorMatches, "can't use directory as log file name") +} + +func (s *lightningSuite) TestRun(c *C) { + globalConfig := config.NewGlobalConfig() + globalConfig.TiDB.Host = "test.invalid" + globalConfig.TiDB.Port = 4000 + globalConfig.TiDB.PdAddr = "test.invalid:2379" + globalConfig.Mydumper.SourceDir = "not-exists" + lightning := New(globalConfig) + cfg := config.NewConfig() + err := cfg.LoadFromGlobal(globalConfig) + c.Assert(err, IsNil) + err = lightning.RunOnce(context.Background(), cfg, nil, nil) + c.Assert(err, ErrorMatches, ".*mydumper dir does not exist") + + path, _ := filepath.Abs(".") + ctx := context.Background() + invalidGlue := glue.NewExternalTiDBGlue(nil, 0) + err = lightning.run(ctx, &config.Config{ + Mydumper: config.MydumperRuntime{ + SourceDir: "file://" + filepath.ToSlash(path), + Filter: []string{"*.*"}, + DefaultFileRules: true, + }, + Checkpoint: config.Checkpoint{ + Enable: true, + Driver: "invalid", + }, + }, invalidGlue) + c.Assert(err, ErrorMatches, "open checkpoint db failed: Unknown checkpoint driver invalid") + + err = lightning.run(ctx, &config.Config{ + Mydumper: config.MydumperRuntime{ + SourceDir: ".", + Filter: []string{"*.*"}, + }, + Checkpoint: config.Checkpoint{ + Enable: true, + Driver: "file", + DSN: "any-file", + }, + }, invalidGlue) + c.Assert(err, NotNil) +} + +var _ = Suite(&lightningServerSuite{}) + +type lightningServerSuite struct { + lightning *Lightning + taskCfgCh chan *config.Config +} + +func (s *lightningServerSuite) SetUpTest(c *C) { + cfg := config.NewGlobalConfig() + cfg.TiDB.Host = "test.invalid" + cfg.TiDB.Port = 4000 + cfg.TiDB.PdAddr = "test.invalid:2379" + cfg.App.ServerMode = true + cfg.App.StatusAddr = "127.0.0.1:0" + cfg.Mydumper.SourceDir = "file://." + + s.lightning = New(cfg) + s.taskCfgCh = make(chan *config.Config) + s.lightning.ctx = context.WithValue(s.lightning.ctx, &taskCfgRecorderKey, s.taskCfgCh) + s.lightning.GoServe() + + failpoint.Enable("github.com/pingcap/br/pkg/lightning/SkipRunTask", "return") +} + +func (s *lightningServerSuite) TearDownTest(c *C) { + failpoint.Disable("github.com/pingcap/br/pkg/lightning/SkipRunTask") + s.lightning.Stop() +} + +func (s *lightningServerSuite) TestRunServer(c *C) { + url := "http://" + s.lightning.serverAddr.String() + "/tasks" + + resp, err := http.Post(url, "application/toml", strings.NewReader("????")) + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusNotImplemented) + var data map[string]string + err = json.NewDecoder(resp.Body).Decode(&data) + c.Assert(err, IsNil) + c.Assert(data, HasKey, "error") + c.Assert(data["error"], Equals, "server-mode not enabled") + resp.Body.Close() + + go s.lightning.RunServer() + time.Sleep(100 * time.Millisecond) + + req, err := http.NewRequest(http.MethodPut, url, nil) + c.Assert(err, IsNil) + resp, err = http.DefaultClient.Do(req) + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusMethodNotAllowed) + c.Assert(resp.Header.Get("Allow"), Matches, ".*"+http.MethodPost+".*") + resp.Body.Close() + + resp, err = http.Post(url, "application/toml", strings.NewReader("????")) + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusBadRequest) + err = json.NewDecoder(resp.Body).Decode(&data) + c.Assert(err, IsNil) + c.Assert(data, HasKey, "error") + c.Assert(data["error"], Matches, "cannot parse task.*") + resp.Body.Close() + + resp, err = http.Post(url, "application/toml", strings.NewReader("[mydumper.csv]\nseparator = 'fooo'\ndelimiter= 'foo'")) + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusBadRequest) + err = json.NewDecoder(resp.Body).Decode(&data) + c.Assert(err, IsNil) + c.Assert(data, HasKey, "error") + c.Assert(data["error"], Matches, "invalid task configuration:.*") + resp.Body.Close() + + for i := 0; i < 20; i++ { + resp, err = http.Post(url, "application/toml", strings.NewReader(fmt.Sprintf(` + [mydumper] + data-source-dir = 'file://demo-path-%d' + [mydumper.csv] + separator = '/' + `, i))) + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusOK) + var result map[string]int + err = json.NewDecoder(resp.Body).Decode(&result) + resp.Body.Close() + c.Assert(err, IsNil) + c.Assert(result, HasKey, "id") + + select { + case taskCfg := <-s.taskCfgCh: + c.Assert(taskCfg.TiDB.Host, Equals, "test.invalid") + c.Assert(taskCfg.Mydumper.SourceDir, Equals, fmt.Sprintf("file://demo-path-%d", i)) + c.Assert(taskCfg.Mydumper.CSV.Separator, Equals, "/") + case <-time.After(500 * time.Millisecond): + c.Fatalf("task is not queued after 500ms (i = %d)", i) + } + } +} + +func (s *lightningServerSuite) TestGetDeleteTask(c *C) { + url := "http://" + s.lightning.serverAddr.String() + "/tasks" + + type getAllResultType struct { + Current int64 + Queue []int64 + } + + getAllTasks := func() (result getAllResultType) { + resp, err := http.Get(url) + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusOK) + err = json.NewDecoder(resp.Body).Decode(&result) + resp.Body.Close() + c.Assert(err, IsNil) + return + } + + postTask := func(i int) int64 { + resp, err := http.Post(url, "application/toml", strings.NewReader(fmt.Sprintf(` + [mydumper] + data-source-dir = 'file://demo-path-%d' + `, i))) + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusOK) + var result struct{ ID int64 } + err = json.NewDecoder(resp.Body).Decode(&result) + resp.Body.Close() + c.Assert(err, IsNil) + return result.ID + } + + go s.lightning.RunServer() + time.Sleep(100 * time.Millisecond) + + // Check `GET /tasks` without any active tasks + + c.Assert(getAllTasks(), DeepEquals, getAllResultType{ + Current: 0, + Queue: []int64{}, + }) + + first := postTask(1) + second := postTask(2) + third := postTask(3) + + c.Assert(first, Not(Equals), 123456) + c.Assert(second, Not(Equals), 123456) + c.Assert(third, Not(Equals), 123456) + + // Check `GET /tasks` returns all tasks currently running + + time.Sleep(100 * time.Millisecond) + c.Assert(getAllTasks(), DeepEquals, getAllResultType{ + Current: first, + Queue: []int64{second, third}, + }) + + // Check `GET /tasks/abcdef` returns error + + resp, err := http.Get(url + "/abcdef") + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusBadRequest) + resp.Body.Close() + + // Check `GET /tasks/123456` returns not found + + resp, err = http.Get(url + "/123456") + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusNotFound) + resp.Body.Close() + + // Check `GET /tasks/1` returns the desired cfg + + var resCfg config.Config + + resp, err = http.Get(fmt.Sprintf("%s/%d", url, second)) + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusOK) + err = json.NewDecoder(resp.Body).Decode(&resCfg) + resp.Body.Close() + c.Assert(err, IsNil) + c.Assert(resCfg.Mydumper.SourceDir, Equals, "file://demo-path-2") + + resp, err = http.Get(fmt.Sprintf("%s/%d", url, first)) + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusOK) + err = json.NewDecoder(resp.Body).Decode(&resCfg) + resp.Body.Close() + c.Assert(err, IsNil) + c.Assert(resCfg.Mydumper.SourceDir, Equals, "file://demo-path-1") + + // Check `DELETE /tasks` returns error. + + req, err := http.NewRequest(http.MethodDelete, url, nil) + c.Assert(err, IsNil) + resp, err = http.DefaultClient.Do(req) + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusBadRequest) + resp.Body.Close() + + // Check `DELETE /tasks/` returns error. + + req.URL.Path = "/tasks/" + resp, err = http.DefaultClient.Do(req) + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusBadRequest) + resp.Body.Close() + + // Check `DELETE /tasks/(not a number)` returns error. + + req.URL.Path = "/tasks/abcdef" + resp, err = http.DefaultClient.Do(req) + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusBadRequest) + resp.Body.Close() + + // Check `DELETE /tasks/123456` returns not found + + req.URL.Path = "/tasks/123456" + resp, err = http.DefaultClient.Do(req) + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusNotFound) + resp.Body.Close() + + // Cancel a queued task, then verify the task list. + + req.URL.Path = fmt.Sprintf("/tasks/%d", second) + resp, err = http.DefaultClient.Do(req) + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusOK) + resp.Body.Close() + + c.Assert(getAllTasks(), DeepEquals, getAllResultType{ + Current: first, + Queue: []int64{third}, + }) + + // Cancel a running task, then verify the task list. + + req.URL.Path = fmt.Sprintf("/tasks/%d", first) + resp, err = http.DefaultClient.Do(req) + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusOK) + resp.Body.Close() + + time.Sleep(100 * time.Millisecond) + c.Assert(getAllTasks(), DeepEquals, getAllResultType{ + Current: third, + Queue: []int64{}, + }) +} + +func (s *lightningServerSuite) TestHTTPAPIOutsideServerMode(c *C) { + s.lightning.globalCfg.App.ServerMode = false + + url := "http://" + s.lightning.serverAddr.String() + "/tasks" + + errCh := make(chan error) + cfg := config.NewConfig() + err := cfg.LoadFromGlobal(s.lightning.globalCfg) + c.Assert(err, IsNil) + go func() { + errCh <- s.lightning.RunOnce(s.lightning.ctx, cfg, nil, nil) + }() + time.Sleep(100 * time.Millisecond) + + var curTask struct { + Current int64 + Queue []int64 + } + + // `GET /tasks` should work fine. + resp, err := http.Get(url) + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusOK) + err = json.NewDecoder(resp.Body).Decode(&curTask) + resp.Body.Close() + c.Assert(err, IsNil) + c.Assert(curTask.Current, Not(Equals), int64(0)) + c.Assert(curTask.Queue, HasLen, 0) + + // `POST /tasks` should return 501 + resp, err = http.Post(url, "application/toml", strings.NewReader("??????")) + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusNotImplemented) + resp.Body.Close() + + // `GET /tasks/(current)` should work fine. + resp, err = http.Get(fmt.Sprintf("%s/%d", url, curTask.Current)) + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusOK) + resp.Body.Close() + + // `GET /tasks/123456` should return 404 + resp, err = http.Get(url + "/123456") + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusNotFound) + resp.Body.Close() + + // `PATCH /tasks/(current)/front` should return 501 + req, err := http.NewRequest(http.MethodPatch, fmt.Sprintf("%s/%d/front", url, curTask.Current), nil) + c.Assert(err, IsNil) + resp, err = http.DefaultClient.Do(req) + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusNotImplemented) + resp.Body.Close() + + // `DELETE /tasks/123456` should return 404 + req.Method = http.MethodDelete + req.URL.Path = "/tasks/123456" + resp, err = http.DefaultClient.Do(req) + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusNotFound) + resp.Body.Close() + + // `DELETE /tasks/(current)` should return 200 + req.URL.Path = fmt.Sprintf("/tasks/%d", curTask.Current) + resp, err = http.DefaultClient.Do(req) + c.Assert(err, IsNil) + c.Assert(resp.StatusCode, Equals, http.StatusOK) + + // ... and the task should be canceled now. + c.Assert(<-errCh, Equals, context.Canceled) +} + +func (s *lightningServerSuite) TestCheckSystemRequirement(c *C) { + if runtime.GOOS == "windows" { + c.Skip("Local-backend is not supported on Windows") + return + } + + cfg := config.NewConfig() + cfg.App.CheckRequirements = true + cfg.App.TableConcurrency = 4 + cfg.TikvImporter.Backend = config.BackendLocal + + dbMetas := []*mydump.MDDatabaseMeta{ + { + Tables: []*mydump.MDTableMeta{ + { + TotalSize: 500 << 20, + }, + { + TotalSize: 150_000 << 20, + }, + }, + }, + { + Tables: []*mydump.MDTableMeta{ + { + TotalSize: 150_800 << 20, + }, + { + TotalSize: 35 << 20, + }, + { + TotalSize: 100_000 << 20, + }, + }, + }, + { + Tables: []*mydump.MDTableMeta{ + { + TotalSize: 240 << 20, + }, + { + TotalSize: 124_000 << 20, + }, + }, + }, + } + + // with max open files 1024, the max table size will be: 65536MB + err := failpoint.Enable("github.com/pingcap/br/pkg/lightning/backend/GetRlimitValue", "return(2049)") + c.Assert(err, IsNil) + err = failpoint.Enable("github.com/pingcap/br/pkg/lightning/backend/SetRlimitError", "return(true)") + c.Assert(err, IsNil) + defer failpoint.Disable("github.com/pingcap/br/pkg/lightning/backend/SetRlimitError") + // with this dbMetas, the estimated fds will be 2050, so should return error + err = checkSystemRequirement(cfg, dbMetas) + c.Assert(err, NotNil) + + // disable check-requirement, should return nil + cfg.App.CheckRequirements = false + err = checkSystemRequirement(cfg, dbMetas) + c.Assert(err, IsNil) + cfg.App.CheckRequirements = true + + err = failpoint.Disable("github.com/pingcap/br/pkg/lightning/backend/GetRlimitValue") + c.Assert(err, IsNil) + + // the min rlimit should be bigger than the default min value (16384) + err = failpoint.Enable("github.com/pingcap/br/pkg/lightning/backend/GetRlimitValue", "return(8200)") + defer failpoint.Disable("github.com/pingcap/br/pkg/lightning/backend/GetRlimitValue") + c.Assert(err, IsNil) + err = checkSystemRequirement(cfg, dbMetas) + c.Assert(err, IsNil) +} + +func (s *lightningServerSuite) TestCheckSchemaConflict(c *C) { + cfg := config.NewConfig() + cfg.Checkpoint.Schema = "cp" + cfg.Checkpoint.Driver = config.CheckpointDriverMySQL + + dbMetas := []*mydump.MDDatabaseMeta{ + { + Name: "test", + Tables: []*mydump.MDTableMeta{ + { + Name: checkpoints.CheckpointTableNameTable, + }, + { + Name: checkpoints.CheckpointTableNameEngine, + }, + }, + }, + { + Name: "cp", + Tables: []*mydump.MDTableMeta{ + { + Name: "test", + }, + }, + }, + } + err := checkSchemaConflict(cfg, dbMetas) + c.Assert(err, IsNil) + + dbMetas = append(dbMetas, &mydump.MDDatabaseMeta{ + Name: "cp", + Tables: []*mydump.MDTableMeta{ + { + Name: checkpoints.CheckpointTableNameChunk, + }, + { + Name: "test123", + }, + }, + }) + err = checkSchemaConflict(cfg, dbMetas) + c.Assert(err, NotNil) + + cfg.Checkpoint.Enable = false + err = checkSchemaConflict(cfg, dbMetas) + c.Assert(err, IsNil) + + cfg.Checkpoint.Enable = true + cfg.Checkpoint.Driver = config.CheckpointDriverFile + err = checkSchemaConflict(cfg, dbMetas) + c.Assert(err, IsNil) +} diff --git a/pkg/lightning/log/log.go b/pkg/lightning/log/log.go new file mode 100644 index 000000000..e3b8f3328 --- /dev/null +++ b/pkg/lightning/log/log.go @@ -0,0 +1,217 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package log + +import ( + "context" + "time" + + "github.com/pingcap/errors" + pclog "github.com/pingcap/log" + "github.com/pingcap/tidb/util/logutil" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +const ( + defaultLogLevel = "info" + defaultLogMaxDays = 7 + defaultLogMaxSize = 512 // MB +) + +// Config serializes log related config in toml/json. +type Config struct { + // Log level. + Level string `toml:"level" json:"level"` + // Log filename, leave empty to disable file log. + File string `toml:"file" json:"file"` + // Max size for a single file, in MB. + FileMaxSize int `toml:"max-size" json:"max-size"` + // Max log keep days, default is never deleting. + FileMaxDays int `toml:"max-days" json:"max-days"` + // Maximum number of old log files to retain. + FileMaxBackups int `toml:"max-backups" json:"max-backups"` +} + +func (cfg *Config) Adjust() { + if len(cfg.Level) == 0 { + cfg.Level = defaultLogLevel + } + if cfg.Level == "warning" { + cfg.Level = "warn" + } + if cfg.FileMaxSize == 0 { + cfg.FileMaxSize = defaultLogMaxSize + } + if cfg.FileMaxDays == 0 { + cfg.FileMaxDays = defaultLogMaxDays + } +} + +// Logger is a simple wrapper around *zap.Logger which provides some extra +// methods to simplify Lightning's log usage. +type Logger struct { + *zap.Logger +} + +// logger for lightning, different from tidb logger. +var ( + appLogger = Logger{zap.NewNop()} + appLevel = zap.NewAtomicLevel() +) + +// InitLogger initializes Lightning's and also the TiDB library's loggers. +func InitLogger(cfg *Config, tidbLoglevel string) error { + logutil.InitLogger(&logutil.LogConfig{Config: pclog.Config{Level: tidbLoglevel}}) + + logCfg := &pclog.Config{ + Level: cfg.Level, + } + if len(cfg.File) > 0 { + logCfg.File = pclog.FileLogConfig{ + Filename: cfg.File, + MaxSize: cfg.FileMaxSize, + MaxDays: cfg.FileMaxDays, + MaxBackups: cfg.FileMaxBackups, + } + } + logger, props, err := pclog.InitLogger(logCfg) + if err != nil { + return err + } + + // Do not log stack traces at all, as we'll get the stack trace from the + // error itself. + appLogger = Logger{logger.WithOptions(zap.AddStacktrace(zap.DPanicLevel))} + appLevel = props.Level + + return nil +} + +// L returns the current logger for Lightning. +func L() Logger { + return appLogger +} + +// SetAppLogger replaces the default logger in this package to given one +func SetAppLogger(l *zap.Logger) { + appLogger = Logger{l.WithOptions(zap.AddStacktrace(zap.DPanicLevel))} +} + +// Level returns the current global log level. +func Level() zapcore.Level { + return appLevel.Level() +} + +// SetLevel modifies the log level of the global logger. Returns the previous +// level. +func SetLevel(level zapcore.Level) zapcore.Level { + oldLevel := appLevel.Level() + appLevel.SetLevel(level) + return oldLevel +} + +// ShortError contructs a field which only records the error message without the +// verbose text (i.e. excludes the stack trace). +// +// In Lightning, all errors are almost always propagated back to `main()` where +// the error stack is written. Including the stack in the middle thus usually +// just repeats known information. You should almost always use `ShortError` +// instead of `zap.Error`, unless the error is no longer propagated upwards. +func ShortError(err error) zap.Field { + if err == nil { + return zap.Skip() + } + return zap.String("error", err.Error()) +} + +// With creates a child logger from the global logger and adds structured +// context to it. +func With(fields ...zap.Field) Logger { + return appLogger.With(fields...) +} + +// IsContextCanceledError returns whether the error is caused by context +// cancellation. +func IsContextCanceledError(err error) bool { + err = errors.Cause(err) + return err == context.Canceled || status.Code(err) == codes.Canceled +} + +// Begin marks the beginning of a task. +func (logger Logger) Begin(level zapcore.Level, name string) *Task { + if ce := logger.WithOptions(zap.AddCallerSkip(1)).Check(level, name+" start"); ce != nil { + ce.Write() + } + return &Task{ + Logger: logger, + level: level, + name: name, + since: time.Now(), + } +} + +// With creates a child logger and adds structured context to it. +func (logger Logger) With(fields ...zap.Field) Logger { + return Logger{logger.Logger.With(fields...)} +} + +// Named adds a new path segment to the logger's name. +func (logger Logger) Named(name string) Logger { + return Logger{logger.Logger.Named(name)} +} + +// Task is a logger for a task spanning a period of time. This structure records +// when the task is started, so the time elapsed for the whole task can be +// logged without book-keeping. +type Task struct { + Logger + level zapcore.Level + name string + since time.Time +} + +// End marks the end of a task. +// +// The `level` is the log level if the task *failed* (i.e. `err != nil`). If the +// task *succeeded* (i.e. `err == nil`), the level from `Begin()` is used +// instead. +// +// The `extraFields` are included in the log only when the task succeeded. +func (task *Task) End(level zapcore.Level, err error, extraFields ...zap.Field) time.Duration { + elapsed := time.Since(task.since) + var verb string + switch { + case err == nil: + level = task.level + verb = " completed" + case IsContextCanceledError(err): + level = zap.DebugLevel + verb = " canceled" + extraFields = nil + default: + verb = " failed" + extraFields = nil + } + if ce := task.WithOptions(zap.AddCallerSkip(1)).Check(level, task.name+verb); ce != nil { + ce.Write(append( + extraFields, + zap.Duration("takeTime", elapsed), + ShortError(err), + )...) + } + return elapsed +} diff --git a/pkg/lightning/log/log_test.go b/pkg/lightning/log/log_test.go new file mode 100644 index 000000000..179ce883e --- /dev/null +++ b/pkg/lightning/log/log_test.go @@ -0,0 +1,51 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package log_test + +import ( + "testing" + + . "github.com/pingcap/check" + "go.uber.org/zap" + + "github.com/pingcap/br/pkg/lightning/log" +) + +func TestLog(t *testing.T) { + TestingT(t) +} + +type logSuite struct{} + +var _ = Suite(&logSuite{}) + +func (s *logSuite) TestConfigAdjust(c *C) { + cfg := &log.Config{} + cfg.Adjust() + c.Assert(cfg.Level, Equals, "info") + + cfg.File = "." + err := log.InitLogger(cfg, "info") + c.Assert(err, ErrorMatches, "can't use directory as log file name") + log.L().Named("xx") +} + +func (s *logSuite) TestTestLogger(c *C) { + logger, buffer := log.MakeTestLogger() + logger.Warn("the message", zap.Int("number", 123456), zap.Ints("array", []int{7, 8, 9})) + c.Assert( + buffer.Stripped(), Equals, + `{"$lvl":"WARN","$msg":"the message","number":123456,"array":[7,8,9]}`, + ) +} diff --git a/pkg/lightning/log/redact.go b/pkg/lightning/log/redact.go new file mode 100644 index 000000000..e75f5cadc --- /dev/null +++ b/pkg/lightning/log/redact.go @@ -0,0 +1,89 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package log + +import ( + "fmt" + + "github.com/pingcap/errors" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +// InitRedact inits the enableRedactLog +func InitRedact(redactLog bool) { + errors.RedactLogEnabled.Store(redactLog) +} + +// NeedRedact returns whether to redact log +func NeedRedact() bool { + return errors.RedactLogEnabled.Load() +} + +// ZapRedactBinary receives zap.Binary and return omitted information if redact log enabled +func ZapRedactBinary(key string, val []byte) zapcore.Field { + if NeedRedact() { + return zap.String(key, "?") + } + return zap.Binary(key, val) +} + +// ZapRedactArray receives zap.Array and return omitted information if redact log enabled +func ZapRedactArray(key string, val zapcore.ArrayMarshaler) zapcore.Field { + if NeedRedact() { + return zap.String(key, "?") + } + return zap.Array(key, val) +} + +// ZapRedactReflect receives zap.Reflect and return omitted information if redact log enabled +func ZapRedactReflect(key string, val interface{}) zapcore.Field { + if NeedRedact() { + return zap.String(key, "?") + } + return zap.Reflect(key, val) +} + +// ZapRedactStringer receives stringer argument and return omitted information in zap.Field if redact log enabled +func ZapRedactStringer(key string, arg fmt.Stringer) zap.Field { + return zap.Stringer(key, RedactStringer(arg)) +} + +// ZapRedactString receives stringer argument and return omitted information in zap.Field if redact log enabled +func ZapRedactString(key string, arg string) zap.Field { + return zap.String(key, RedactString(arg)) +} + +// RedactString receives string argument and return omitted information if redact log enabled +func RedactString(arg string) string { + if NeedRedact() { + return "?" + } + return arg +} + +// RedactStringer receives stringer argument and return omitted information if redact log enabled +func RedactStringer(arg fmt.Stringer) fmt.Stringer { + if NeedRedact() { + return stringer{} + } + return arg +} + +type stringer struct{} + +// String implement fmt.Stringer +func (s stringer) String() string { + return "?" +} diff --git a/pkg/lightning/log/testlogger.go b/pkg/lightning/log/testlogger.go new file mode 100644 index 000000000..c0c8ee2a0 --- /dev/null +++ b/pkg/lightning/log/testlogger.go @@ -0,0 +1,36 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package log + +import ( + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "go.uber.org/zap/zaptest" +) + +// MakeTestLogger creates a Logger instance which produces JSON logs. +func MakeTestLogger() (Logger, *zaptest.Buffer) { + buffer := new(zaptest.Buffer) + logger := zap.New(zapcore.NewCore( + zapcore.NewJSONEncoder(zapcore.EncoderConfig{ + LevelKey: "$lvl", + MessageKey: "$msg", + EncodeLevel: zapcore.CapitalLevelEncoder, + EncodeDuration: zapcore.StringDurationEncoder, + }), + buffer, + zap.DebugLevel, + )) + return Logger{Logger: logger}, buffer +} diff --git a/pkg/lightning/manual/manual.go b/pkg/lightning/manual/manual.go new file mode 100644 index 000000000..fbc78814f --- /dev/null +++ b/pkg/lightning/manual/manual.go @@ -0,0 +1,65 @@ +// Copyright 2020 The LevelDB-Go and Pebble Authors. All rights reserved. Use +// of this source code is governed by a BSD-style license that can be found in +// the LICENSE file. + +package manual + +// #include +import "C" +import "unsafe" + +// The go:linkname directives provides backdoor access to private functions in +// the runtime. Below we're accessing the throw function. + +//go:linkname throw runtime.throw +func throw(s string) + +// TODO(peter): Rather than relying an C malloc/free, we could fork the Go +// runtime page allocator and allocate large chunks of memory using mmap or +// similar. + +const ( + // MaxArrayLen is a safe maximum length for slices on this architecture. + MaxArrayLen = 1<<31 - 1 +) + +// New allocates a slice of size n. The returned slice is from manually managed +// memory and MUST be released by calling Free. Failure to do so will result in +// a memory leak. +func New(n int) []byte { + if n == 0 { + return make([]byte, 0) + } + // We need to be conscious of the Cgo pointer passing rules: + // + // https://golang.org/cmd/cgo/#hdr-Passing_pointers + // + // ... + // Note: the current implementation has a bug. While Go code is permitted + // to write nil or a C pointer (but not a Go pointer) to C memory, the + // current implementation may sometimes cause a runtime error if the + // contents of the C memory appear to be a Go pointer. Therefore, avoid + // passing uninitialized C memory to Go code if the Go code is going to + // store pointer values in it. Zero out the memory in C before passing it + // to Go. + ptr := C.calloc(C.size_t(n), 1) + if ptr == nil { + // NB: throw is like panic, except it guarantees the process will be + // terminated. The call below is exactly what the Go runtime invokes when + // it cannot allocate memory. + throw("out of memory") + } + // Interpret the C pointer as a pointer to a Go array, then slice. + return (*[MaxArrayLen]byte)(unsafe.Pointer(ptr))[:n:n] +} + +// Free frees the specified slice. +func Free(b []byte) { + if cap(b) != 0 { + if len(b) == 0 { + b = b[:cap(b)] + } + ptr := unsafe.Pointer(&b[0]) + C.free(ptr) + } +} diff --git a/pkg/lightning/manual/manual_nocgo.go b/pkg/lightning/manual/manual_nocgo.go new file mode 100644 index 000000000..466d1ee63 --- /dev/null +++ b/pkg/lightning/manual/manual_nocgo.go @@ -0,0 +1,19 @@ +// Copyright 2020 The LevelDB-Go and Pebble Authors. All rights reserved. Use +// of this source code is governed by a BSD-style license that can be found in +// the LICENSE file. + +// +build !cgo + +package manual + +// Provides versions of New and Free when cgo is not available (e.g. cross +// compilation). + +// New allocates a slice of size n. +func New(n int) []byte { + return make([]byte, n) +} + +// Free frees the specified slice. +func Free(b []byte) { +} diff --git a/pkg/lightning/metric/metric.go b/pkg/lightning/metric/metric.go new file mode 100644 index 000000000..b4ffdc9c5 --- /dev/null +++ b/pkg/lightning/metric/metric.go @@ -0,0 +1,258 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package metric + +import ( + "math" + + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" +) + +const ( + // states used for the TableCounter labels + TableStatePending = "pending" + TableStateWritten = "written" + TableStateClosed = "closed" + TableStateImported = "imported" + TableStateAlteredAutoInc = "altered_auto_inc" + TableStateChecksum = "checksum" + TableStateCompleted = "completed" + + // results used for the TableCounter labels + TableResultSuccess = "success" + TableResultFailure = "failure" + + // states used for the ChunkCounter labels + ChunkStateEstimated = "estimated" + ChunkStatePending = "pending" + ChunkStateRunning = "running" + ChunkStateFinished = "finished" + ChunkStateFailed = "failed" + + BlockDeliverKindIndex = "index" + BlockDeliverKindData = "data" +) + +var ( + ImporterEngineCounter = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "lightning", + Name: "importer_engine", + Help: "counting open and closed importer engines", + }, []string{"type"}) + + IdleWorkersGauge = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: "lightning", + Name: "idle_workers", + Help: "counting idle workers", + }, []string{"name"}) + + KvEncoderCounter = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "lightning", + Name: "kv_encoder", + Help: "counting kv open and closed kv encoder", + }, []string{"type"}, + ) + + TableCounter = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "lightning", + Name: "tables", + Help: "count number of tables processed", + }, []string{"state", "result"}) + ProcessedEngineCounter = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "lightning", + Name: "engines", + Help: "count number of engines processed", + }, []string{"state", "result"}) + ChunkCounter = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "lightning", + Name: "chunks", + Help: "count number of chunks processed", + }, []string{"state"}) + BytesCounter = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "lightning", + Name: "bytes", + Help: "count of total bytes", + }, []string{"state"}) + // state can be one of: + // - estimated (an estimation derived from the file size) + // - pending + // - running + // - finished + // - failed + + ImportSecondsHistogram = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Namespace: "lightning", + Name: "import_seconds", + Help: "time needed to import a table", + Buckets: prometheus.ExponentialBuckets(0.125, 2, 6), + }, + ) + ChunkParserReadBlockSecondsHistogram = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Namespace: "lightning", + Name: "chunk_parser_read_block_seconds", + Help: "time needed for chunk parser read a block", + Buckets: prometheus.ExponentialBuckets(0.001, 3.1622776601683795, 10), + }, + ) + ApplyWorkerSecondsHistogram = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "lightning", + Name: "apply_worker_seconds", + Help: "time needed to apply a worker", + Buckets: prometheus.ExponentialBuckets(0.001, 3.1622776601683795, 10), + }, []string{"name"}, + ) + RowReadSecondsHistogram = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Namespace: "lightning", + Name: "row_read_seconds", + Help: "time needed to parse a row", + Buckets: prometheus.ExponentialBuckets(0.001, 3.1622776601683795, 7), + }, + ) + RowReadBytesHistogram = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Namespace: "lightning", + Name: "row_read_bytes", + Help: "number of bytes being read out from data source", + Buckets: prometheus.ExponentialBuckets(1024, 2, 8), + }, + ) + RowEncodeSecondsHistogram = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Namespace: "lightning", + Name: "row_encode_seconds", + Help: "time needed to encode a row", + Buckets: prometheus.ExponentialBuckets(0.001, 3.1622776601683795, 10), + }, + ) + RowKVDeliverSecondsHistogram = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Namespace: "lightning", + Name: "row_kv_deliver_seconds", + Help: "time needed to deliver kvs of a single row", + Buckets: prometheus.ExponentialBuckets(0.001, 3.1622776601683795, 10), + }, + ) + BlockDeliverSecondsHistogram = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Namespace: "lightning", + Name: "block_deliver_seconds", + Help: "time needed to deliver a block", + Buckets: prometheus.ExponentialBuckets(0.001, 3.1622776601683795, 10), + }, + ) + BlockDeliverBytesHistogram = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "lightning", + Name: "block_deliver_bytes", + Help: "number of bytes being sent out to importer", + Buckets: prometheus.ExponentialBuckets(512, 2, 10), + }, []string{"kind"}, + ) + BlockDeliverKVPairsHistogram = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "lightning", + Name: "block_deliver_kv_pairs", + Help: "number of KV pairs being sent out to importer", + Buckets: prometheus.ExponentialBuckets(1, 2, 10), + }, []string{"kind"}, + ) + ChecksumSecondsHistogram = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Namespace: "lightning", + Name: "checksum_seconds", + Help: "time needed to complete the checksum stage", + Buckets: prometheus.ExponentialBuckets(1, 2.2679331552660544, 10), + }, + ) + + LocalStorageUsageBytesGauge = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: "lightning", + Name: "local_storage_usage_bytes", + Help: "disk/memory size currently occupied by intermediate files in local backend", + }, []string{"medium"}, + ) +) + +func init() { + prometheus.MustRegister(IdleWorkersGauge) + prometheus.MustRegister(ImporterEngineCounter) + prometheus.MustRegister(KvEncoderCounter) + prometheus.MustRegister(TableCounter) + prometheus.MustRegister(ProcessedEngineCounter) + prometheus.MustRegister(ChunkCounter) + prometheus.MustRegister(BytesCounter) + prometheus.MustRegister(ImportSecondsHistogram) + prometheus.MustRegister(RowReadSecondsHistogram) + prometheus.MustRegister(RowReadBytesHistogram) + prometheus.MustRegister(RowEncodeSecondsHistogram) + prometheus.MustRegister(RowKVDeliverSecondsHistogram) + prometheus.MustRegister(BlockDeliverSecondsHistogram) + prometheus.MustRegister(BlockDeliverBytesHistogram) + prometheus.MustRegister(BlockDeliverKVPairsHistogram) + prometheus.MustRegister(ChecksumSecondsHistogram) + prometheus.MustRegister(ChunkParserReadBlockSecondsHistogram) + prometheus.MustRegister(ApplyWorkerSecondsHistogram) + prometheus.MustRegister(LocalStorageUsageBytesGauge) +} + +func RecordTableCount(status string, err error) { + var result string + if err != nil { + result = TableResultFailure + } else { + result = TableResultSuccess + } + TableCounter.WithLabelValues(status, result).Inc() +} + +func RecordEngineCount(status string, err error) { + var result string + if err != nil { + result = TableResultFailure + } else { + result = TableResultSuccess + } + ProcessedEngineCounter.WithLabelValues(status, result).Inc() +} + +// ReadCounter reports the current value of the counter. +func ReadCounter(counter prometheus.Counter) float64 { + var metric dto.Metric + if err := counter.Write(&metric); err != nil { + return math.NaN() + } + return metric.Counter.GetValue() +} + +// ReadHistogramSum reports the sum of all observed values in the histogram. +func ReadHistogramSum(histogram prometheus.Histogram) float64 { + var metric dto.Metric + if err := histogram.Write(&metric); err != nil { + return math.NaN() + } + return metric.Histogram.GetSampleSum() +} diff --git a/pkg/lightning/metric/metric_test.go b/pkg/lightning/metric/metric_test.go new file mode 100644 index 000000000..25efda267 --- /dev/null +++ b/pkg/lightning/metric/metric_test.go @@ -0,0 +1,61 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package metric_test + +import ( + "errors" + "testing" + + . "github.com/pingcap/check" + + "github.com/prometheus/client_golang/prometheus" + + "github.com/pingcap/br/pkg/lightning/metric" +) + +type testMetricSuite struct{} + +func (s *testMetricSuite) SetUpSuite(c *C) {} +func (s *testMetricSuite) TearDownSuite(c *C) {} + +var _ = Suite(&testMetricSuite{}) + +func TestMetric(t *testing.T) { + TestingT(t) +} + +func (s *testMetricSuite) TestReadCounter(c *C) { + counter := prometheus.NewCounter(prometheus.CounterOpts{}) + counter.Add(1256.0) + counter.Add(2214.0) + c.Assert(metric.ReadCounter(counter), Equals, 3470.0) +} + +func (s *testMetricSuite) TestReadHistogramSum(c *C) { + histogram := prometheus.NewHistogram(prometheus.HistogramOpts{}) + histogram.Observe(11131.5) + histogram.Observe(15261.0) + c.Assert(metric.ReadHistogramSum(histogram), Equals, 26392.5) +} + +func (s *testMetricSuite) TestRecordEngineCount(c *C) { + metric.RecordEngineCount("table1", nil) + metric.RecordEngineCount("table1", errors.New("mock error")) + successCounter, err := metric.ProcessedEngineCounter.GetMetricWithLabelValues("table1", "success") + c.Assert(err, IsNil) + c.Assert(metric.ReadCounter(successCounter), Equals, 1.0) + failureCount, err := metric.ProcessedEngineCounter.GetMetricWithLabelValues("table1", "failure") + c.Assert(err, IsNil) + c.Assert(metric.ReadCounter(failureCount), Equals, 1.0) +} diff --git a/pkg/lightning/mock/backend.go b/pkg/lightning/mock/backend.go new file mode 100644 index 000000000..8f69935c3 --- /dev/null +++ b/pkg/lightning/mock/backend.go @@ -0,0 +1,456 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/pingcap/br/pkg/lightning/backend (interfaces: AbstractBackend,Encoder,Rows,Row,EngineWriter) + +// $ mockgen -package mock -mock_names 'AbstractBackend=MockBackend' github.com/pingcap/br/pkg/lightning/backend AbstractBackend,Encoder,Rows,Row,EngineWriter + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + gomock "github.com/golang/mock/gomock" + uuid "github.com/google/uuid" + backend "github.com/pingcap/br/pkg/lightning/backend" + log "github.com/pingcap/br/pkg/lightning/log" + verification "github.com/pingcap/br/pkg/lightning/verification" + model "github.com/pingcap/parser/model" + table "github.com/pingcap/tidb/table" + types "github.com/pingcap/tidb/types" + reflect "reflect" + time "time" +) + +// MockBackend is a mock of AbstractBackend interface +type MockBackend struct { + ctrl *gomock.Controller + recorder *MockBackendMockRecorder +} + +// MockBackendMockRecorder is the mock recorder for MockBackend +type MockBackendMockRecorder struct { + mock *MockBackend +} + +// NewMockBackend creates a new mock instance +func NewMockBackend(ctrl *gomock.Controller) *MockBackend { + mock := &MockBackend{ctrl: ctrl} + mock.recorder = &MockBackendMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockBackend) EXPECT() *MockBackendMockRecorder { + return m.recorder +} + +// CheckRequirements mocks base method +func (m *MockBackend) CheckRequirements(arg0 context.Context) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CheckRequirements", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// CheckRequirements indicates an expected call of CheckRequirements +func (mr *MockBackendMockRecorder) CheckRequirements(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckRequirements", reflect.TypeOf((*MockBackend)(nil).CheckRequirements), arg0) +} + +// CleanupEngine mocks base method +func (m *MockBackend) CleanupEngine(arg0 context.Context, arg1 uuid.UUID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CleanupEngine", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// CleanupEngine indicates an expected call of CleanupEngine +func (mr *MockBackendMockRecorder) CleanupEngine(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanupEngine", reflect.TypeOf((*MockBackend)(nil).CleanupEngine), arg0, arg1) +} + +// Close mocks base method +func (m *MockBackend) Close() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Close") +} + +// Close indicates an expected call of Close +func (mr *MockBackendMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockBackend)(nil).Close)) +} + +// CloseEngine mocks base method +func (m *MockBackend) CloseEngine(arg0 context.Context, arg1 uuid.UUID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CloseEngine", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// CloseEngine indicates an expected call of CloseEngine +func (mr *MockBackendMockRecorder) CloseEngine(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseEngine", reflect.TypeOf((*MockBackend)(nil).CloseEngine), arg0, arg1) +} + +// EngineFileSizes mocks base method +func (m *MockBackend) EngineFileSizes() []backend.EngineFileSize { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EngineFileSizes") + ret0, _ := ret[0].([]backend.EngineFileSize) + return ret0 +} + +// EngineFileSizes indicates an expected call of EngineFileSizes +func (mr *MockBackendMockRecorder) EngineFileSizes() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EngineFileSizes", reflect.TypeOf((*MockBackend)(nil).EngineFileSizes)) +} + +// FetchRemoteTableModels mocks base method +func (m *MockBackend) FetchRemoteTableModels(arg0 context.Context, arg1 string) ([]*model.TableInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FetchRemoteTableModels", arg0, arg1) + ret0, _ := ret[0].([]*model.TableInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FetchRemoteTableModels indicates an expected call of FetchRemoteTableModels +func (mr *MockBackendMockRecorder) FetchRemoteTableModels(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchRemoteTableModels", reflect.TypeOf((*MockBackend)(nil).FetchRemoteTableModels), arg0, arg1) +} + +// FlushAllEngines mocks base method +func (m *MockBackend) FlushAllEngines(arg0 context.Context) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FlushAllEngines", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// FlushAllEngines indicates an expected call of FlushAllEngines +func (mr *MockBackendMockRecorder) FlushAllEngines(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FlushAllEngines", reflect.TypeOf((*MockBackend)(nil).FlushAllEngines), arg0) +} + +// FlushEngine mocks base method +func (m *MockBackend) FlushEngine(arg0 context.Context, arg1 uuid.UUID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FlushEngine", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// FlushEngine indicates an expected call of FlushEngine +func (mr *MockBackendMockRecorder) FlushEngine(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FlushEngine", reflect.TypeOf((*MockBackend)(nil).FlushEngine), arg0, arg1) +} + +// ImportEngine mocks base method +func (m *MockBackend) ImportEngine(arg0 context.Context, arg1 uuid.UUID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ImportEngine", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ImportEngine indicates an expected call of ImportEngine +func (mr *MockBackendMockRecorder) ImportEngine(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImportEngine", reflect.TypeOf((*MockBackend)(nil).ImportEngine), arg0, arg1) +} + +// LocalWriter mocks base method +func (m *MockBackend) LocalWriter(arg0 context.Context, arg1 uuid.UUID, arg2 int64) (backend.EngineWriter, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LocalWriter", arg0, arg1, arg2) + ret0, _ := ret[0].(backend.EngineWriter) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LocalWriter indicates an expected call of LocalWriter +func (mr *MockBackendMockRecorder) LocalWriter(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LocalWriter", reflect.TypeOf((*MockBackend)(nil).LocalWriter), arg0, arg1, arg2) +} + +// MakeEmptyRows mocks base method +func (m *MockBackend) MakeEmptyRows() backend.Rows { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MakeEmptyRows") + ret0, _ := ret[0].(backend.Rows) + return ret0 +} + +// MakeEmptyRows indicates an expected call of MakeEmptyRows +func (mr *MockBackendMockRecorder) MakeEmptyRows() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MakeEmptyRows", reflect.TypeOf((*MockBackend)(nil).MakeEmptyRows)) +} + +// NewEncoder mocks base method +func (m *MockBackend) NewEncoder(arg0 table.Table, arg1 *backend.SessionOptions) (backend.Encoder, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewEncoder", arg0, arg1) + ret0, _ := ret[0].(backend.Encoder) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NewEncoder indicates an expected call of NewEncoder +func (mr *MockBackendMockRecorder) NewEncoder(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewEncoder", reflect.TypeOf((*MockBackend)(nil).NewEncoder), arg0, arg1) +} + +// OpenEngine mocks base method +func (m *MockBackend) OpenEngine(arg0 context.Context, arg1 uuid.UUID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OpenEngine", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// OpenEngine indicates an expected call of OpenEngine +func (mr *MockBackendMockRecorder) OpenEngine(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenEngine", reflect.TypeOf((*MockBackend)(nil).OpenEngine), arg0, arg1) +} + +// ResetEngine mocks base method +func (m *MockBackend) ResetEngine(arg0 context.Context, arg1 uuid.UUID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ResetEngine", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// ResetEngine indicates an expected call of ResetEngine +func (mr *MockBackendMockRecorder) ResetEngine(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResetEngine", reflect.TypeOf((*MockBackend)(nil).ResetEngine), arg0, arg1) +} + +// RetryImportDelay mocks base method +func (m *MockBackend) RetryImportDelay() time.Duration { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RetryImportDelay") + ret0, _ := ret[0].(time.Duration) + return ret0 +} + +// RetryImportDelay indicates an expected call of RetryImportDelay +func (mr *MockBackendMockRecorder) RetryImportDelay() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RetryImportDelay", reflect.TypeOf((*MockBackend)(nil).RetryImportDelay)) +} + +// ShouldPostProcess mocks base method +func (m *MockBackend) ShouldPostProcess() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ShouldPostProcess") + ret0, _ := ret[0].(bool) + return ret0 +} + +// ShouldPostProcess indicates an expected call of ShouldPostProcess +func (mr *MockBackendMockRecorder) ShouldPostProcess() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ShouldPostProcess", reflect.TypeOf((*MockBackend)(nil).ShouldPostProcess)) +} + +// MockEncoder is a mock of Encoder interface +type MockEncoder struct { + ctrl *gomock.Controller + recorder *MockEncoderMockRecorder +} + +// MockEncoderMockRecorder is the mock recorder for MockEncoder +type MockEncoderMockRecorder struct { + mock *MockEncoder +} + +// NewMockEncoder creates a new mock instance +func NewMockEncoder(ctrl *gomock.Controller) *MockEncoder { + mock := &MockEncoder{ctrl: ctrl} + mock.recorder = &MockEncoderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockEncoder) EXPECT() *MockEncoderMockRecorder { + return m.recorder +} + +// Close mocks base method +func (m *MockEncoder) Close() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Close") +} + +// Close indicates an expected call of Close +func (mr *MockEncoderMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockEncoder)(nil).Close)) +} + +// Encode mocks base method +func (m *MockEncoder) Encode(arg0 log.Logger, arg1 []types.Datum, arg2 int64, arg3 []int) (backend.Row, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Encode", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(backend.Row) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Encode indicates an expected call of Encode +func (mr *MockEncoderMockRecorder) Encode(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Encode", reflect.TypeOf((*MockEncoder)(nil).Encode), arg0, arg1, arg2, arg3) +} + +// MockRows is a mock of Rows interface +type MockRows struct { + ctrl *gomock.Controller + recorder *MockRowsMockRecorder +} + +// MockRowsMockRecorder is the mock recorder for MockRows +type MockRowsMockRecorder struct { + mock *MockRows +} + +// NewMockRows creates a new mock instance +func NewMockRows(ctrl *gomock.Controller) *MockRows { + mock := &MockRows{ctrl: ctrl} + mock.recorder = &MockRowsMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockRows) EXPECT() *MockRowsMockRecorder { + return m.recorder +} + +// Clear mocks base method +func (m *MockRows) Clear() backend.Rows { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Clear") + ret0, _ := ret[0].(backend.Rows) + return ret0 +} + +// Clear indicates an expected call of Clear +func (mr *MockRowsMockRecorder) Clear() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clear", reflect.TypeOf((*MockRows)(nil).Clear)) +} + +// SplitIntoChunks mocks base method +func (m *MockRows) SplitIntoChunks(arg0 int) []backend.Rows { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SplitIntoChunks", arg0) + ret0, _ := ret[0].([]backend.Rows) + return ret0 +} + +// SplitIntoChunks indicates an expected call of SplitIntoChunks +func (mr *MockRowsMockRecorder) SplitIntoChunks(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SplitIntoChunks", reflect.TypeOf((*MockRows)(nil).SplitIntoChunks), arg0) +} + +// MockRow is a mock of Row interface +type MockRow struct { + ctrl *gomock.Controller + recorder *MockRowMockRecorder +} + +// MockRowMockRecorder is the mock recorder for MockRow +type MockRowMockRecorder struct { + mock *MockRow +} + +// NewMockRow creates a new mock instance +func NewMockRow(ctrl *gomock.Controller) *MockRow { + mock := &MockRow{ctrl: ctrl} + mock.recorder = &MockRowMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockRow) EXPECT() *MockRowMockRecorder { + return m.recorder +} + +// ClassifyAndAppend mocks base method +func (m *MockRow) ClassifyAndAppend(arg0 *backend.Rows, arg1 *verification.KVChecksum, arg2 *backend.Rows, arg3 *verification.KVChecksum) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "ClassifyAndAppend", arg0, arg1, arg2, arg3) +} + +// ClassifyAndAppend indicates an expected call of ClassifyAndAppend +func (mr *MockRowMockRecorder) ClassifyAndAppend(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClassifyAndAppend", reflect.TypeOf((*MockRow)(nil).ClassifyAndAppend), arg0, arg1, arg2, arg3) +} + +// MockEngineWriter is a mock of EngineWriter interface +type MockEngineWriter struct { + ctrl *gomock.Controller + recorder *MockEngineWriterMockRecorder +} + +// MockEngineWriterMockRecorder is the mock recorder for MockEngineWriter +type MockEngineWriterMockRecorder struct { + mock *MockEngineWriter +} + +// NewMockEngineWriter creates a new mock instance +func NewMockEngineWriter(ctrl *gomock.Controller) *MockEngineWriter { + mock := &MockEngineWriter{ctrl: ctrl} + mock.recorder = &MockEngineWriterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockEngineWriter) EXPECT() *MockEngineWriterMockRecorder { + return m.recorder +} + +// AppendRows mocks base method +func (m *MockEngineWriter) AppendRows(arg0 context.Context, arg1 string, arg2 []string, arg3 uint64, arg4 backend.Rows) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AppendRows", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].(error) + return ret0 +} + +// AppendRows indicates an expected call of AppendRows +func (mr *MockEngineWriterMockRecorder) AppendRows(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppendRows", reflect.TypeOf((*MockEngineWriter)(nil).AppendRows), arg0, arg1, arg2, arg3, arg4) +} + +// Close mocks base method +func (m *MockEngineWriter) Close() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Close") + ret0, _ := ret[0].(error) + return ret0 +} + +// Close indicates an expected call of Close +func (mr *MockEngineWriterMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockEngineWriter)(nil).Close)) +} diff --git a/pkg/lightning/mock/glue.go b/pkg/lightning/mock/glue.go new file mode 100644 index 000000000..63ece855e --- /dev/null +++ b/pkg/lightning/mock/glue.go @@ -0,0 +1,234 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/pingcap/br/pkg/lightning/glue/glue.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + sql "database/sql" + gomock "github.com/golang/mock/gomock" + parser "github.com/pingcap/parser" + model "github.com/pingcap/parser/model" + checkpoints "github.com/pingcap/br/pkg/lightning/checkpoints" + config "github.com/pingcap/br/pkg/lightning/config" + glue "github.com/pingcap/br/pkg/lightning/glue" + log "github.com/pingcap/br/pkg/lightning/log" + reflect "reflect" +) + +// MockGlue is a mock of Glue interface +type MockGlue struct { + ctrl *gomock.Controller + recorder *MockGlueMockRecorder +} + +// MockGlueMockRecorder is the mock recorder for MockGlue +type MockGlueMockRecorder struct { + mock *MockGlue +} + +// NewMockGlue creates a new mock instance +func NewMockGlue(ctrl *gomock.Controller) *MockGlue { + mock := &MockGlue{ctrl: ctrl} + mock.recorder = &MockGlueMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockGlue) EXPECT() *MockGlueMockRecorder { + return m.recorder +} + +// OwnsSQLExecutor mocks base method +func (m *MockGlue) OwnsSQLExecutor() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OwnsSQLExecutor") + ret0, _ := ret[0].(bool) + return ret0 +} + +// OwnsSQLExecutor indicates an expected call of OwnsSQLExecutor +func (mr *MockGlueMockRecorder) OwnsSQLExecutor() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OwnsSQLExecutor", reflect.TypeOf((*MockGlue)(nil).OwnsSQLExecutor)) +} + +// GetSQLExecutor mocks base method +func (m *MockGlue) GetSQLExecutor() glue.SQLExecutor { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSQLExecutor") + ret0, _ := ret[0].(glue.SQLExecutor) + return ret0 +} + +// GetSQLExecutor indicates an expected call of GetSQLExecutor +func (mr *MockGlueMockRecorder) GetSQLExecutor() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSQLExecutor", reflect.TypeOf((*MockGlue)(nil).GetSQLExecutor)) +} + +// GetDB mocks base method +func (m *MockGlue) GetDB() (*sql.DB, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetDB") + ret0, _ := ret[0].(*sql.DB) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetDB indicates an expected call of GetDB +func (mr *MockGlueMockRecorder) GetDB() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDB", reflect.TypeOf((*MockGlue)(nil).GetDB)) +} + +// GetParser mocks base method +func (m *MockGlue) GetParser() *parser.Parser { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetParser") + ret0, _ := ret[0].(*parser.Parser) + return ret0 +} + +// GetParser indicates an expected call of GetParser +func (mr *MockGlueMockRecorder) GetParser() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetParser", reflect.TypeOf((*MockGlue)(nil).GetParser)) +} + +// GetTables mocks base method +func (m *MockGlue) GetTables(arg0 context.Context, arg1 string) ([]*model.TableInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetTables", arg0, arg1) + ret0, _ := ret[0].([]*model.TableInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetTables indicates an expected call of GetTables +func (mr *MockGlueMockRecorder) GetTables(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTables", reflect.TypeOf((*MockGlue)(nil).GetTables), arg0, arg1) +} + +// GetSession mocks base method +func (m *MockGlue) GetSession(arg0 context.Context) (checkpoints.Session, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetSession", arg0) + ret0, _ := ret[0].(checkpoints.Session) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetSession indicates an expected call of GetSession +func (mr *MockGlueMockRecorder) GetSession(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSession", reflect.TypeOf((*MockGlue)(nil).GetSession), arg0) +} + +// OpenCheckpointsDB mocks base method +func (m *MockGlue) OpenCheckpointsDB(arg0 context.Context, arg1 *config.Config) (checkpoints.CheckpointsDB, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OpenCheckpointsDB", arg0, arg1) + ret0, _ := ret[0].(checkpoints.CheckpointsDB) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// OpenCheckpointsDB indicates an expected call of OpenCheckpointsDB +func (mr *MockGlueMockRecorder) OpenCheckpointsDB(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenCheckpointsDB", reflect.TypeOf((*MockGlue)(nil).OpenCheckpointsDB), arg0, arg1) +} + +// Record mocks base method +func (m *MockGlue) Record(arg0 string, arg1 uint64) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Record", arg0, arg1) +} + +// Record indicates an expected call of Record +func (mr *MockGlueMockRecorder) Record(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Record", reflect.TypeOf((*MockGlue)(nil).Record), arg0, arg1) +} + +// MockSQLExecutor is a mock of SQLExecutor interface +type MockSQLExecutor struct { + ctrl *gomock.Controller + recorder *MockSQLExecutorMockRecorder +} + +// MockSQLExecutorMockRecorder is the mock recorder for MockSQLExecutor +type MockSQLExecutorMockRecorder struct { + mock *MockSQLExecutor +} + +// NewMockSQLExecutor creates a new mock instance +func NewMockSQLExecutor(ctrl *gomock.Controller) *MockSQLExecutor { + mock := &MockSQLExecutor{ctrl: ctrl} + mock.recorder = &MockSQLExecutorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockSQLExecutor) EXPECT() *MockSQLExecutorMockRecorder { + return m.recorder +} + +// ExecuteWithLog mocks base method +func (m *MockSQLExecutor) ExecuteWithLog(ctx context.Context, query, purpose string, logger log.Logger) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExecuteWithLog", ctx, query, purpose, logger) + ret0, _ := ret[0].(error) + return ret0 +} + +// ExecuteWithLog indicates an expected call of ExecuteWithLog +func (mr *MockSQLExecutorMockRecorder) ExecuteWithLog(ctx, query, purpose, logger interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecuteWithLog", reflect.TypeOf((*MockSQLExecutor)(nil).ExecuteWithLog), ctx, query, purpose, logger) +} + +// ObtainStringWithLog mocks base method +func (m *MockSQLExecutor) ObtainStringWithLog(ctx context.Context, query, purpose string, logger log.Logger) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ObtainStringWithLog", ctx, query, purpose, logger) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ObtainStringWithLog indicates an expected call of ObtainStringWithLog +func (mr *MockSQLExecutorMockRecorder) ObtainStringWithLog(ctx, query, purpose, logger interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ObtainStringWithLog", reflect.TypeOf((*MockSQLExecutor)(nil).ObtainStringWithLog), ctx, query, purpose, logger) +} + +// QueryStringsWithLog mocks base method +func (m *MockSQLExecutor) QueryStringsWithLog(ctx context.Context, query, purpose string, logger log.Logger) ([][]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "QueryStringsWithLog", ctx, query, purpose, logger) + ret0, _ := ret[0].([][]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// QueryStringsWithLog indicates an expected call of QueryStringsWithLog +func (mr *MockSQLExecutorMockRecorder) QueryStringsWithLog(ctx, query, purpose, logger interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryStringsWithLog", reflect.TypeOf((*MockSQLExecutor)(nil).QueryStringsWithLog), ctx, query, purpose, logger) +} + +// Close mocks base method +func (m *MockSQLExecutor) Close() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Close") +} + +// Close indicates an expected call of Close +func (mr *MockSQLExecutorMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockSQLExecutor)(nil).Close)) +} diff --git a/pkg/lightning/mock/glue_checkpoint.go b/pkg/lightning/mock/glue_checkpoint.go new file mode 100644 index 000000000..50bf3fc36 --- /dev/null +++ b/pkg/lightning/mock/glue_checkpoint.go @@ -0,0 +1,136 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/pingcap/br/pkg/lightning/checkpoints/glue_checkpoint.go + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + gomock "github.com/golang/mock/gomock" + ast "github.com/pingcap/parser/ast" + types "github.com/pingcap/tidb/types" + sqlexec "github.com/pingcap/tidb/util/sqlexec" + reflect "reflect" +) + +// MockSession is a mock of Session interface +type MockSession struct { + ctrl *gomock.Controller + recorder *MockSessionMockRecorder +} + +// MockSessionMockRecorder is the mock recorder for MockSession +type MockSessionMockRecorder struct { + mock *MockSession +} + +// NewMockSession creates a new mock instance +func NewMockSession(ctrl *gomock.Controller) *MockSession { + mock := &MockSession{ctrl: ctrl} + mock.recorder = &MockSessionMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockSession) EXPECT() *MockSessionMockRecorder { + return m.recorder +} + +// Close mocks base method +func (m *MockSession) Close() { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Close") +} + +// Close indicates an expected call of Close +func (mr *MockSessionMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockSession)(nil).Close)) +} + +// Execute mocks base method +func (m *MockSession) Execute(arg0 context.Context, arg1 string) ([]sqlexec.RecordSet, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Execute", arg0, arg1) + ret0, _ := ret[0].([]sqlexec.RecordSet) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Execute indicates an expected call of Execute +func (mr *MockSessionMockRecorder) Execute(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Execute", reflect.TypeOf((*MockSession)(nil).Execute), arg0, arg1) +} + +// CommitTxn mocks base method +func (m *MockSession) CommitTxn(arg0 context.Context) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CommitTxn", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// CommitTxn indicates an expected call of CommitTxn +func (mr *MockSessionMockRecorder) CommitTxn(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CommitTxn", reflect.TypeOf((*MockSession)(nil).CommitTxn), arg0) +} + +// RollbackTxn mocks base method +func (m *MockSession) RollbackTxn(arg0 context.Context) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "RollbackTxn", arg0) +} + +// RollbackTxn indicates an expected call of RollbackTxn +func (mr *MockSessionMockRecorder) RollbackTxn(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RollbackTxn", reflect.TypeOf((*MockSession)(nil).RollbackTxn), arg0) +} + +// PrepareStmt mocks base method +func (m *MockSession) PrepareStmt(sql string) (uint32, int, []*ast.ResultField, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PrepareStmt", sql) + ret0, _ := ret[0].(uint32) + ret1, _ := ret[1].(int) + ret2, _ := ret[2].([]*ast.ResultField) + ret3, _ := ret[3].(error) + return ret0, ret1, ret2, ret3 +} + +// PrepareStmt indicates an expected call of PrepareStmt +func (mr *MockSessionMockRecorder) PrepareStmt(sql interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrepareStmt", reflect.TypeOf((*MockSession)(nil).PrepareStmt), sql) +} + +// ExecutePreparedStmt mocks base method +func (m *MockSession) ExecutePreparedStmt(ctx context.Context, stmtID uint32, param []types.Datum) (sqlexec.RecordSet, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ExecutePreparedStmt", ctx, stmtID, param) + ret0, _ := ret[0].(sqlexec.RecordSet) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ExecutePreparedStmt indicates an expected call of ExecutePreparedStmt +func (mr *MockSessionMockRecorder) ExecutePreparedStmt(ctx, stmtID, param interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecutePreparedStmt", reflect.TypeOf((*MockSession)(nil).ExecutePreparedStmt), ctx, stmtID, param) +} + +// DropPreparedStmt mocks base method +func (m *MockSession) DropPreparedStmt(stmtID uint32) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DropPreparedStmt", stmtID) + ret0, _ := ret[0].(error) + return ret0 +} + +// DropPreparedStmt indicates an expected call of DropPreparedStmt +func (mr *MockSessionMockRecorder) DropPreparedStmt(stmtID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DropPreparedStmt", reflect.TypeOf((*MockSession)(nil).DropPreparedStmt), stmtID) +} diff --git a/pkg/lightning/mock/importer.go b/pkg/lightning/mock/importer.go new file mode 100644 index 000000000..a58c0d05b --- /dev/null +++ b/pkg/lightning/mock/importer.go @@ -0,0 +1,377 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/pingcap/kvproto/pkg/import_kvpb (interfaces: ImportKVClient,ImportKV_WriteEngineClient) + +// $ mockgen -package mock github.com/pingcap/kvproto/pkg/import_kvpb ImportKVClient,ImportKV_WriteEngineClient + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + import_kvpb "github.com/pingcap/kvproto/pkg/import_kvpb" + grpc "google.golang.org/grpc" + metadata "google.golang.org/grpc/metadata" +) + +// MockImportKVClient is a mock of ImportKVClient interface +type MockImportKVClient struct { + ctrl *gomock.Controller + recorder *MockImportKVClientMockRecorder +} + +// MockImportKVClientMockRecorder is the mock recorder for MockImportKVClient +type MockImportKVClientMockRecorder struct { + mock *MockImportKVClient +} + +// NewMockImportKVClient creates a new mock instance +func NewMockImportKVClient(ctrl *gomock.Controller) *MockImportKVClient { + mock := &MockImportKVClient{ctrl: ctrl} + mock.recorder = &MockImportKVClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockImportKVClient) EXPECT() *MockImportKVClientMockRecorder { + return m.recorder +} + +// CleanupEngine mocks base method +func (m *MockImportKVClient) CleanupEngine(arg0 context.Context, arg1 *import_kvpb.CleanupEngineRequest, arg2 ...grpc.CallOption) (*import_kvpb.CleanupEngineResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CleanupEngine", varargs...) + ret0, _ := ret[0].(*import_kvpb.CleanupEngineResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CleanupEngine indicates an expected call of CleanupEngine +func (mr *MockImportKVClientMockRecorder) CleanupEngine(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CleanupEngine", reflect.TypeOf((*MockImportKVClient)(nil).CleanupEngine), varargs...) +} + +// CloseEngine mocks base method +func (m *MockImportKVClient) CloseEngine(arg0 context.Context, arg1 *import_kvpb.CloseEngineRequest, arg2 ...grpc.CallOption) (*import_kvpb.CloseEngineResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CloseEngine", varargs...) + ret0, _ := ret[0].(*import_kvpb.CloseEngineResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CloseEngine indicates an expected call of CloseEngine +func (mr *MockImportKVClientMockRecorder) CloseEngine(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseEngine", reflect.TypeOf((*MockImportKVClient)(nil).CloseEngine), varargs...) +} + +// CompactCluster mocks base method +func (m *MockImportKVClient) CompactCluster(arg0 context.Context, arg1 *import_kvpb.CompactClusterRequest, arg2 ...grpc.CallOption) (*import_kvpb.CompactClusterResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CompactCluster", varargs...) + ret0, _ := ret[0].(*import_kvpb.CompactClusterResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CompactCluster indicates an expected call of CompactCluster +func (mr *MockImportKVClientMockRecorder) CompactCluster(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CompactCluster", reflect.TypeOf((*MockImportKVClient)(nil).CompactCluster), varargs...) +} + +// GetMetrics mocks base method +func (m *MockImportKVClient) GetMetrics(arg0 context.Context, arg1 *import_kvpb.GetMetricsRequest, arg2 ...grpc.CallOption) (*import_kvpb.GetMetricsResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetMetrics", varargs...) + ret0, _ := ret[0].(*import_kvpb.GetMetricsResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMetrics indicates an expected call of GetMetrics +func (mr *MockImportKVClientMockRecorder) GetMetrics(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMetrics", reflect.TypeOf((*MockImportKVClient)(nil).GetMetrics), varargs...) +} + +// GetVersion mocks base method +func (m *MockImportKVClient) GetVersion(arg0 context.Context, arg1 *import_kvpb.GetVersionRequest, arg2 ...grpc.CallOption) (*import_kvpb.GetVersionResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "GetVersion", varargs...) + ret0, _ := ret[0].(*import_kvpb.GetVersionResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetVersion indicates an expected call of GetVersion +func (mr *MockImportKVClientMockRecorder) GetVersion(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetVersion", reflect.TypeOf((*MockImportKVClient)(nil).GetVersion), varargs...) +} + +// ImportEngine mocks base method +func (m *MockImportKVClient) ImportEngine(arg0 context.Context, arg1 *import_kvpb.ImportEngineRequest, arg2 ...grpc.CallOption) (*import_kvpb.ImportEngineResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ImportEngine", varargs...) + ret0, _ := ret[0].(*import_kvpb.ImportEngineResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ImportEngine indicates an expected call of ImportEngine +func (mr *MockImportKVClientMockRecorder) ImportEngine(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ImportEngine", reflect.TypeOf((*MockImportKVClient)(nil).ImportEngine), varargs...) +} + +// OpenEngine mocks base method +func (m *MockImportKVClient) OpenEngine(arg0 context.Context, arg1 *import_kvpb.OpenEngineRequest, arg2 ...grpc.CallOption) (*import_kvpb.OpenEngineResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "OpenEngine", varargs...) + ret0, _ := ret[0].(*import_kvpb.OpenEngineResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// OpenEngine indicates an expected call of OpenEngine +func (mr *MockImportKVClientMockRecorder) OpenEngine(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenEngine", reflect.TypeOf((*MockImportKVClient)(nil).OpenEngine), varargs...) +} + +// SwitchMode mocks base method +func (m *MockImportKVClient) SwitchMode(arg0 context.Context, arg1 *import_kvpb.SwitchModeRequest, arg2 ...grpc.CallOption) (*import_kvpb.SwitchModeResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "SwitchMode", varargs...) + ret0, _ := ret[0].(*import_kvpb.SwitchModeResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// SwitchMode indicates an expected call of SwitchMode +func (mr *MockImportKVClientMockRecorder) SwitchMode(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SwitchMode", reflect.TypeOf((*MockImportKVClient)(nil).SwitchMode), varargs...) +} + +// WriteEngine mocks base method +func (m *MockImportKVClient) WriteEngine(arg0 context.Context, arg1 ...grpc.CallOption) (import_kvpb.ImportKV_WriteEngineClient, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "WriteEngine", varargs...) + ret0, _ := ret[0].(import_kvpb.ImportKV_WriteEngineClient) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WriteEngine indicates an expected call of WriteEngine +func (mr *MockImportKVClientMockRecorder) WriteEngine(arg0 interface{}, arg1 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteEngine", reflect.TypeOf((*MockImportKVClient)(nil).WriteEngine), varargs...) +} + +// WriteEngineV3 mocks base method +func (m *MockImportKVClient) WriteEngineV3(arg0 context.Context, arg1 *import_kvpb.WriteEngineV3Request, arg2 ...grpc.CallOption) (*import_kvpb.WriteEngineResponse, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "WriteEngineV3", varargs...) + ret0, _ := ret[0].(*import_kvpb.WriteEngineResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WriteEngineV3 indicates an expected call of WriteEngineV3 +func (mr *MockImportKVClientMockRecorder) WriteEngineV3(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteEngineV3", reflect.TypeOf((*MockImportKVClient)(nil).WriteEngineV3), varargs...) +} + +// MockImportKV_WriteEngineClient is a mock of ImportKV_WriteEngineClient interface +type MockImportKV_WriteEngineClient struct { + ctrl *gomock.Controller + recorder *MockImportKV_WriteEngineClientMockRecorder +} + +// MockImportKV_WriteEngineClientMockRecorder is the mock recorder for MockImportKV_WriteEngineClient +type MockImportKV_WriteEngineClientMockRecorder struct { + mock *MockImportKV_WriteEngineClient +} + +// NewMockImportKV_WriteEngineClient creates a new mock instance +func NewMockImportKV_WriteEngineClient(ctrl *gomock.Controller) *MockImportKV_WriteEngineClient { + mock := &MockImportKV_WriteEngineClient{ctrl: ctrl} + mock.recorder = &MockImportKV_WriteEngineClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockImportKV_WriteEngineClient) EXPECT() *MockImportKV_WriteEngineClientMockRecorder { + return m.recorder +} + +// CloseAndRecv mocks base method +func (m *MockImportKV_WriteEngineClient) CloseAndRecv() (*import_kvpb.WriteEngineResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CloseAndRecv") + ret0, _ := ret[0].(*import_kvpb.WriteEngineResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CloseAndRecv indicates an expected call of CloseAndRecv +func (mr *MockImportKV_WriteEngineClientMockRecorder) CloseAndRecv() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseAndRecv", reflect.TypeOf((*MockImportKV_WriteEngineClient)(nil).CloseAndRecv)) +} + +// CloseSend mocks base method +func (m *MockImportKV_WriteEngineClient) CloseSend() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CloseSend") + ret0, _ := ret[0].(error) + return ret0 +} + +// CloseSend indicates an expected call of CloseSend +func (mr *MockImportKV_WriteEngineClientMockRecorder) CloseSend() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseSend", reflect.TypeOf((*MockImportKV_WriteEngineClient)(nil).CloseSend)) +} + +// Context mocks base method +func (m *MockImportKV_WriteEngineClient) Context() context.Context { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Context") + ret0, _ := ret[0].(context.Context) + return ret0 +} + +// Context indicates an expected call of Context +func (mr *MockImportKV_WriteEngineClientMockRecorder) Context() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Context", reflect.TypeOf((*MockImportKV_WriteEngineClient)(nil).Context)) +} + +// Header mocks base method +func (m *MockImportKV_WriteEngineClient) Header() (metadata.MD, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Header") + ret0, _ := ret[0].(metadata.MD) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Header indicates an expected call of Header +func (mr *MockImportKV_WriteEngineClientMockRecorder) Header() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Header", reflect.TypeOf((*MockImportKV_WriteEngineClient)(nil).Header)) +} + +// RecvMsg mocks base method +func (m *MockImportKV_WriteEngineClient) RecvMsg(arg0 interface{}) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RecvMsg", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// RecvMsg indicates an expected call of RecvMsg +func (mr *MockImportKV_WriteEngineClientMockRecorder) RecvMsg(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RecvMsg", reflect.TypeOf((*MockImportKV_WriteEngineClient)(nil).RecvMsg), arg0) +} + +// Send mocks base method +func (m *MockImportKV_WriteEngineClient) Send(arg0 *import_kvpb.WriteEngineRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Send", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Send indicates an expected call of Send +func (mr *MockImportKV_WriteEngineClientMockRecorder) Send(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Send", reflect.TypeOf((*MockImportKV_WriteEngineClient)(nil).Send), arg0) +} + +// SendMsg mocks base method +func (m *MockImportKV_WriteEngineClient) SendMsg(arg0 interface{}) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendMsg", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendMsg indicates an expected call of SendMsg +func (mr *MockImportKV_WriteEngineClientMockRecorder) SendMsg(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMsg", reflect.TypeOf((*MockImportKV_WriteEngineClient)(nil).SendMsg), arg0) +} + +// Trailer mocks base method +func (m *MockImportKV_WriteEngineClient) Trailer() metadata.MD { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Trailer") + ret0, _ := ret[0].(metadata.MD) + return ret0 +} + +// Trailer indicates an expected call of Trailer +func (mr *MockImportKV_WriteEngineClientMockRecorder) Trailer() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Trailer", reflect.TypeOf((*MockImportKV_WriteEngineClient)(nil).Trailer)) +} diff --git a/pkg/lightning/mock/storage.go b/pkg/lightning/mock/storage.go new file mode 100644 index 000000000..d029ef08b --- /dev/null +++ b/pkg/lightning/mock/storage.go @@ -0,0 +1,137 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/pingcap/br/pkg/storage (interfaces: ExternalStorage) + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + gomock "github.com/golang/mock/gomock" + storage "github.com/pingcap/br/pkg/storage" + reflect "reflect" +) + +// MockExternalStorage is a mock of ExternalStorage interface +type MockExternalStorage struct { + ctrl *gomock.Controller + recorder *MockExternalStorageMockRecorder +} + +// MockExternalStorageMockRecorder is the mock recorder for MockExternalStorage +type MockExternalStorageMockRecorder struct { + mock *MockExternalStorage +} + +// NewMockExternalStorage creates a new mock instance +func NewMockExternalStorage(ctrl *gomock.Controller) *MockExternalStorage { + mock := &MockExternalStorage{ctrl: ctrl} + mock.recorder = &MockExternalStorageMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockExternalStorage) EXPECT() *MockExternalStorageMockRecorder { + return m.recorder +} + +// Create mocks base method +func (m *MockExternalStorage) Create(arg0 context.Context, arg1 string) (storage.ExternalFileWriter, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", arg0, arg1) + ret0, _ := ret[0].(storage.ExternalFileWriter) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Create indicates an expected call of Create +func (mr *MockExternalStorageMockRecorder) Create(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockExternalStorage)(nil).Create), arg0, arg1) +} + +// FileExists mocks base method +func (m *MockExternalStorage) FileExists(arg0 context.Context, arg1 string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "FileExists", arg0, arg1) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// FileExists indicates an expected call of FileExists +func (mr *MockExternalStorageMockRecorder) FileExists(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FileExists", reflect.TypeOf((*MockExternalStorage)(nil).FileExists), arg0, arg1) +} + +// Open mocks base method +func (m *MockExternalStorage) Open(arg0 context.Context, arg1 string) (storage.ExternalFileReader, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Open", arg0, arg1) + ret0, _ := ret[0].(storage.ExternalFileReader) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Open indicates an expected call of Open +func (mr *MockExternalStorageMockRecorder) Open(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Open", reflect.TypeOf((*MockExternalStorage)(nil).Open), arg0, arg1) +} + +// ReadFile mocks base method +func (m *MockExternalStorage) ReadFile(arg0 context.Context, arg1 string) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReadFile", arg0, arg1) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ReadFile indicates an expected call of ReadFile +func (mr *MockExternalStorageMockRecorder) ReadFile(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadFile", reflect.TypeOf((*MockExternalStorage)(nil).ReadFile), arg0, arg1) +} + +// URI mocks base method +func (m *MockExternalStorage) URI() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "URI") + ret0, _ := ret[0].(string) + return ret0 +} + +// URI indicates an expected call of URI +func (mr *MockExternalStorageMockRecorder) URI() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "URI", reflect.TypeOf((*MockExternalStorage)(nil).URI)) +} + +// WalkDir mocks base method +func (m *MockExternalStorage) WalkDir(arg0 context.Context, arg1 *storage.WalkOption, arg2 func(string, int64) error) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WalkDir", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// WalkDir indicates an expected call of WalkDir +func (mr *MockExternalStorageMockRecorder) WalkDir(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalkDir", reflect.TypeOf((*MockExternalStorage)(nil).WalkDir), arg0, arg1, arg2) +} + +// WriteFile mocks base method +func (m *MockExternalStorage) WriteFile(arg0 context.Context, arg1 string, arg2 []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WriteFile", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// WriteFile indicates an expected call of WriteFile +func (mr *MockExternalStorageMockRecorder) WriteFile(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteFile", reflect.TypeOf((*MockExternalStorage)(nil).WriteFile), arg0, arg1, arg2) +} diff --git a/pkg/lightning/mydump/bytes.go b/pkg/lightning/mydump/bytes.go new file mode 100644 index 000000000..d11027520 --- /dev/null +++ b/pkg/lightning/mydump/bytes.go @@ -0,0 +1,39 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package bytes implements functions for the manipulation of byte slices. +// It is analogous to the facilities of the strings package. + +// this is copy from `bytes/bytes.go` + +package mydump + +// byteSet is a 32-byte value, where each bit represents the presence of a +// given byte value in the set. +type byteSet [8]uint32 + +// makeByteSet creates a set of byte value. +func makeByteSet(chars []byte) (as byteSet) { + for i := 0; i < len(chars); i++ { + c := chars[i] + as[c>>5] |= 1 << uint(c&31) + } + return as +} + +// contains reports whether c is inside the set. +func (as *byteSet) contains(c byte) bool { + return (as[c>>5] & (1 << uint(c&31))) != 0 +} + +// IndexAnyByte returns the byte index of the first occurrence in s of any of the byte +// points in chars. It returns -1 if there is no code point in common. +func IndexAnyByte(s []byte, as *byteSet) int { + for i, c := range s { + if as.contains(c) { + return i + } + } + return -1 +} diff --git a/pkg/lightning/mydump/csv/split_large_file.csv b/pkg/lightning/mydump/csv/split_large_file.csv new file mode 100644 index 000000000..7b6512d53 --- /dev/null +++ b/pkg/lightning/mydump/csv/split_large_file.csv @@ -0,0 +1,5 @@ +a,b,c +1,1,2 +2,2,1 +3,2,2 +4,2,2 diff --git a/pkg/lightning/mydump/csv_parser.go b/pkg/lightning/mydump/csv_parser.go new file mode 100644 index 000000000..1e3db9dd3 --- /dev/null +++ b/pkg/lightning/mydump/csv_parser.go @@ -0,0 +1,583 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package mydump + +import ( + "bytes" + "io" + "strings" + "unicode" + + "github.com/pingcap/br/pkg/utils" + + "github.com/pingcap/errors" + "github.com/pingcap/tidb/types" + + "github.com/pingcap/br/pkg/lightning/config" + "github.com/pingcap/br/pkg/lightning/worker" +) + +var ( + errUnterminatedQuotedField = errors.NewNoStackError("syntax error: unterminated quoted field") + errDanglingBackslash = errors.NewNoStackError("syntax error: no character after backslash") + errUnexpectedQuoteField = errors.NewNoStackError("syntax error: cannot have consecutive fields without separator") +) + +// CSVParser is basically a copy of encoding/csv, but special-cased for MySQL-like input. +type CSVParser struct { + blockParser + cfg *config.CSVConfig + escFlavor backslashEscapeFlavor + + comma []byte + quote []byte + quoteIndexFunc func([]byte) int + unquoteIndexFunc func([]byte) int + + // recordBuffer holds the unescaped fields, one after another. + // The fields can be accessed by using the indexes in fieldIndexes. + // E.g., For the row `a,"b","c""d",e`, recordBuffer will contain `abc"de` + // and fieldIndexes will contain the indexes [1, 2, 5, 6]. + recordBuffer []byte + + // fieldIndexes is an index of fields inside recordBuffer. + // The i'th field ends at offset fieldIndexes[i] in recordBuffer. + fieldIndexes []int + + lastRecord []string + + // if set to true, csv parser will treat the first non-empty line as header line + shouldParseHeader bool +} + +func NewCSVParser( + cfg *config.CSVConfig, + reader ReadSeekCloser, + blockBufSize int64, + ioWorkers *worker.Pool, + shouldParseHeader bool, +) *CSVParser { + escFlavor := backslashEscapeFlavorNone + var quoteStopSet []byte + unquoteStopSet := []byte{'\r', '\n', cfg.Separator[0]} + if len(cfg.Delimiter) > 0 { + quoteStopSet = []byte{cfg.Delimiter[0]} + unquoteStopSet = append(unquoteStopSet, cfg.Delimiter[0]) + } + if cfg.BackslashEscape { + escFlavor = backslashEscapeFlavorMySQL + quoteStopSet = append(quoteStopSet, '\\') + unquoteStopSet = append(unquoteStopSet, '\\') + // we need special treatment of the NULL value \N, used by MySQL. + if !cfg.NotNull && cfg.Null == `\N` { + escFlavor = backslashEscapeFlavorMySQLWithNull + } + } + + return &CSVParser{ + blockParser: makeBlockParser(reader, blockBufSize, ioWorkers), + cfg: cfg, + comma: []byte(cfg.Separator), + quote: []byte(cfg.Delimiter), + escFlavor: escFlavor, + quoteIndexFunc: makeBytesIndexFunc(quoteStopSet), + unquoteIndexFunc: makeBytesIndexFunc(unquoteStopSet), + shouldParseHeader: shouldParseHeader, + } +} + +func makeBytesIndexFunc(chars []byte) func([]byte) int { + // chars are guaranteed to be ascii str, so this call will always success + as := makeByteSet(chars) + return func(s []byte) int { + return IndexAnyByte(s, &as) + } +} + +func (parser *CSVParser) unescapeString(input string) (unescaped string, isNull bool) { + if parser.escFlavor == backslashEscapeFlavorMySQLWithNull && input == `\N` { + return input, true + } + unescaped = unescape(input, "", parser.escFlavor) + isNull = parser.escFlavor != backslashEscapeFlavorMySQLWithNull && + !parser.cfg.NotNull && + unescaped == parser.cfg.Null + return +} + +func (parser *CSVParser) readByte() (byte, error) { + if len(parser.buf) == 0 { + if err := parser.readBlock(); err != nil { + return 0, err + } + } + if len(parser.buf) == 0 { + return 0, io.EOF + } + b := parser.buf[0] + parser.buf = parser.buf[1:] + parser.pos++ + return b, nil +} + +func (parser *CSVParser) readBytes(buf []byte) (int, error) { + cnt := 0 + for cnt < len(buf) { + if len(parser.buf) == 0 { + if err := parser.readBlock(); err != nil { + return cnt, err + } + } + if len(parser.buf) == 0 { + parser.pos += int64(cnt) + return cnt, io.EOF + } + readCnt := utils.MinInt(len(buf)-cnt, len(parser.buf)) + copy(buf[cnt:], parser.buf[:readCnt]) + parser.buf = parser.buf[readCnt:] + cnt += readCnt + } + parser.pos += int64(cnt) + return cnt, nil +} + +func (parser *CSVParser) peekBytes(cnt int) ([]byte, error) { + if len(parser.buf) < cnt { + if err := parser.readBlock(); err != nil { + return nil, err + } + } + if len(parser.buf) == 0 { + return nil, io.EOF + } + cnt = utils.MinInt(cnt, len(parser.buf)) + return parser.buf[:cnt], nil +} + +func (parser *CSVParser) skipBytes(n int) { + parser.buf = parser.buf[n:] + parser.pos += int64(n) +} + +// readUntil reads the buffer until any character from the `chars` set is found. +// that character is excluded from the final buffer. +func (parser *CSVParser) readUntil(findIndexFunc func([]byte) int) ([]byte, byte, error) { + index := findIndexFunc(parser.buf) + if index >= 0 { + ret := parser.buf[:index] + parser.buf = parser.buf[index:] + parser.pos += int64(index) + return ret, parser.buf[0], nil + } + + // not found in parser.buf, need allocate and loop. + var buf []byte + for { + buf = append(buf, parser.buf...) + parser.buf = nil + if err := parser.readBlock(); err != nil || len(parser.buf) == 0 { + if err == nil { + err = io.EOF + } + parser.pos += int64(len(buf)) + return buf, 0, errors.Trace(err) + } + index := findIndexFunc(parser.buf) + if index >= 0 { + buf = append(buf, parser.buf[:index]...) + parser.buf = parser.buf[index:] + parser.pos += int64(len(buf)) + return buf, parser.buf[0], nil + } + } +} + +func (parser *CSVParser) readRecord(dst []string) ([]string, error) { + parser.recordBuffer = parser.recordBuffer[:0] + parser.fieldIndexes = parser.fieldIndexes[:0] + + isEmptyLine := true + whitespaceLine := true + + processDefault := func(b byte) error { + if b == '\\' && parser.escFlavor != backslashEscapeFlavorNone { + if err := parser.readByteForBackslashEscape(); err != nil { + return err + } + } else { + parser.recordBuffer = append(parser.recordBuffer, b) + } + return parser.readUnquoteField() + } + + processQuote := func(b byte) error { + return parser.readQuotedField() + } + if len(parser.quote) > 1 { + processQuote = func(b byte) error { + pb, err := parser.peekBytes(len(parser.quote) - 1) + if err != nil && errors.Cause(err) != io.EOF { + return err + } + if bytes.Equal(pb, parser.quote[1:]) { + parser.skipBytes(len(parser.quote) - 1) + return parser.readQuotedField() + } + return processDefault(b) + } + } + + processComma := func(b byte) error { + parser.fieldIndexes = append(parser.fieldIndexes, len(parser.recordBuffer)) + return nil + } + if len(parser.comma) > 1 { + processNotComma := processDefault + if len(parser.quote) > 0 && parser.comma[0] == parser.quote[0] { + processNotComma = processQuote + } + processComma = func(b byte) error { + pb, err := parser.peekBytes(len(parser.comma) - 1) + if err != nil && errors.Cause(err) != io.EOF { + return err + } + if bytes.Equal(pb, parser.comma[1:]) { + parser.skipBytes(len(parser.comma) - 1) + parser.fieldIndexes = append(parser.fieldIndexes, len(parser.recordBuffer)) + return nil + } + return processNotComma(b) + } + } + +outside: + for { + firstByte, err := parser.readByte() + if err != nil { + if isEmptyLine || errors.Cause(err) != io.EOF { + return nil, err + } + // treat EOF as the same as trailing \n. + firstByte = '\n' + } + + switch { + case firstByte == parser.comma[0]: + whitespaceLine = false + if err = processComma(firstByte); err != nil { + return nil, err + } + + case len(parser.quote) > 0 && firstByte == parser.quote[0]: + if err = processQuote(firstByte); err != nil { + return nil, err + } + whitespaceLine = false + case firstByte == '\r', firstByte == '\n': + // new line = end of record (ignore empty lines) + if isEmptyLine { + continue + } + // skip lines only contain whitespaces + if err == nil && whitespaceLine && len(bytes.TrimFunc(parser.recordBuffer, unicode.IsSpace)) == 0 { + parser.recordBuffer = parser.recordBuffer[:0] + continue + } + parser.fieldIndexes = append(parser.fieldIndexes, len(parser.recordBuffer)) + break outside + default: + if err = processDefault(firstByte); err != nil { + return nil, err + } + } + isEmptyLine = false + } + // Create a single string and create slices out of it. + // This pins the memory of the fields together, but allocates once. + str := string(parser.recordBuffer) // Convert to string once to batch allocations + dst = dst[:0] + if cap(dst) < len(parser.fieldIndexes) { + dst = make([]string, len(parser.fieldIndexes)) + } + dst = dst[:len(parser.fieldIndexes)] + var preIdx int + for i, idx := range parser.fieldIndexes { + dst[i] = str[preIdx:idx] + preIdx = idx + } + + // Check or update the expected fields per record. + return dst, nil +} + +func (parser *CSVParser) readByteForBackslashEscape() error { + b, err := parser.readByte() + err = parser.replaceEOF(err, errDanglingBackslash) + if err != nil { + return err + } + parser.recordBuffer = append(parser.recordBuffer, '\\', b) + return nil +} + +func (parser *CSVParser) readQuotedField() error { + processDefault := func() error { + // in all other cases, we've got a syntax error. + parser.logSyntaxError() + return errors.AddStack(errUnexpectedQuoteField) + } + + processComma := func() error { return nil } + if len(parser.comma) > 1 { + processComma = func() error { + b, err := parser.peekBytes(len(parser.comma)) + if err != nil && errors.Cause(err) != io.EOF { + return err + } + if !bytes.Equal(b, parser.comma) { + return processDefault() + } + return nil + } + } + for { + content, terminator, err := parser.readUntil(parser.quoteIndexFunc) + err = parser.replaceEOF(err, errUnterminatedQuotedField) + if err != nil { + return err + } + parser.recordBuffer = append(parser.recordBuffer, content...) + parser.skipBytes(1) + switch { + case len(parser.quote) > 0 && terminator == parser.quote[0]: + if len(parser.quote) > 1 { + b, err := parser.peekBytes(len(parser.quote) - 1) + if err != nil && err != io.EOF { + return err + } + if !bytes.Equal(b, parser.quote[1:]) { + parser.recordBuffer = append(parser.recordBuffer, terminator) + continue + } + parser.skipBytes(len(parser.quote) - 1) + } + // encountered '"' -> continue if we're seeing '""'. + b, err := parser.peekBytes(1) + if err != nil { + if err == io.EOF { + err = nil + } + return err + } + switch b[0] { + case parser.quote[0]: + // consume the double quotation mark and continue + if len(parser.quote) > 1 { + b, err := parser.peekBytes(len(parser.quote)) + if err != nil && err != io.EOF { + return err + } + if !bytes.Equal(b, parser.quote) { + if parser.quote[0] == parser.comma[0] { + return processComma() + } else { + return processDefault() + } + } + } + parser.skipBytes(len(parser.quote)) + parser.recordBuffer = append(parser.recordBuffer, parser.quote...) + case '\r', '\n': + // end the field if the next is a separator + return nil + case parser.comma[0]: + return processComma() + default: + return processDefault() + } + + case terminator == '\\': + if err := parser.readByteForBackslashEscape(); err != nil { + return err + } + } + } +} + +func (parser *CSVParser) readUnquoteField() error { + addByte := func(b byte) { + // read the following byte + parser.recordBuffer = append(parser.recordBuffer, b) + parser.skipBytes(1) + } + parseQuote := func(b byte) error { + r, err := parser.checkBytes(parser.quote) + if err != nil { + return errors.Trace(err) + } + if r { + parser.logSyntaxError() + return errors.AddStack(errUnexpectedQuoteField) + } + addByte(b) + return nil + } + + parserNoComma := func(b byte) error { + addByte(b) + return nil + } + if len(parser.quote) > 0 && parser.comma[0] == parser.quote[0] { + parserNoComma = parseQuote + } + for { + content, terminator, err := parser.readUntil(parser.unquoteIndexFunc) + parser.recordBuffer = append(parser.recordBuffer, content...) + finished := false + if err != nil { + if errors.Cause(err) == io.EOF { + finished = true + err = nil + } + if err != nil { + return err + } + } + + switch { + case terminator == '\r', terminator == '\n', finished: + return nil + case terminator == parser.comma[0]: + r, err := parser.checkBytes(parser.comma) + if err != nil { + return errors.Trace(err) + } + if r { + return nil + } + if err = parserNoComma(terminator); err != nil { + return err + } + case len(parser.quote) > 0 && terminator == parser.quote[0]: + r, err := parser.checkBytes(parser.quote) + if err != nil { + return errors.Trace(err) + } + if r { + parser.logSyntaxError() + return errors.AddStack(errUnexpectedQuoteField) + } + case terminator == '\\': + parser.skipBytes(1) + if err := parser.readByteForBackslashEscape(); err != nil { + return err + } + } + } +} + +func (parser *CSVParser) checkBytes(b []byte) (bool, error) { + if len(b) == 1 { + return true, nil + } + pb, err := parser.peekBytes(len(b)) + if err != nil { + return false, err + } + return bytes.Equal(pb, b), nil +} + +func (parser *CSVParser) replaceEOF(err error, replaced error) error { + if err == nil || errors.Cause(err) != io.EOF { + return err + } + if replaced != nil { + parser.logSyntaxError() + replaced = errors.AddStack(replaced) + } + return replaced +} + +// ReadRow reads a row from the datafile. +func (parser *CSVParser) ReadRow() error { + row := &parser.lastRow + row.RowID++ + + // skip the header first + if parser.shouldParseHeader { + err := parser.ReadColumns() + if err != nil { + return errors.Trace(err) + } + parser.shouldParseHeader = false + } + + records, err := parser.readRecord(parser.lastRecord) + if err != nil { + return errors.Trace(err) + } + parser.lastRecord = records + // remove the last empty value + if parser.cfg.TrimLastSep { + i := len(records) - 1 + if i >= 0 && len(records[i]) == 0 { + records = records[:i] + } + } + + row.Row = parser.acquireDatumSlice() + if cap(row.Row) >= len(records) { + row.Row = row.Row[:len(records)] + } else { + row.Row = make([]types.Datum, len(records)) + } + for i, record := range records { + unescaped, isNull := parser.unescapeString(record) + if isNull { + row.Row[i].SetNull() + } else { + row.Row[i].SetString(unescaped, "utf8mb4_bin") + } + } + + return nil +} + +func (parser *CSVParser) ReadColumns() error { + columns, err := parser.readRecord(nil) + if err != nil { + return errors.Trace(err) + } + parser.columns = make([]string, 0, len(columns)) + for _, colName := range columns { + colName, _ = parser.unescapeString(colName) + parser.columns = append(parser.columns, strings.ToLower(colName)) + } + return nil +} + +var newLineAsciiSet = makeByteSet([]byte{'\r', '\n'}) + +func indexOfNewLine(b []byte) int { + return IndexAnyByte(b, &newLineAsciiSet) +} + +func (parser *CSVParser) ReadUntilTokNewLine() (int64, error) { + _, _, err := parser.readUntil(indexOfNewLine) + if err != nil { + return 0, err + } + parser.skipBytes(1) + return parser.pos, nil +} diff --git a/pkg/lightning/mydump/csv_parser_test.go b/pkg/lightning/mydump/csv_parser_test.go new file mode 100644 index 000000000..112fd1462 --- /dev/null +++ b/pkg/lightning/mydump/csv_parser_test.go @@ -0,0 +1,909 @@ +package mydump_test + +import ( + "context" + "encoding/csv" + "io" + "os" + "path/filepath" + "strings" + + . "github.com/pingcap/check" + "github.com/pingcap/errors" + "github.com/pingcap/tidb/types" + "go.uber.org/zap" + + "github.com/pingcap/br/pkg/lightning/config" + "github.com/pingcap/br/pkg/lightning/log" + "github.com/pingcap/br/pkg/lightning/mydump" + "github.com/pingcap/br/pkg/lightning/worker" +) + +var _ = Suite(&testMydumpCSVParserSuite{}) + +type testMydumpCSVParserSuite struct { + ioWorkers *worker.Pool +} + +func (s *testMydumpCSVParserSuite) SetUpSuite(c *C) { + s.ioWorkers = worker.NewPool(context.Background(), 5, "test_csv") +} +func (s *testMydumpCSVParserSuite) TearDownSuite(c *C) {} + +type assertPosEq struct { + *CheckerInfo +} + +var posEq = &assertPosEq{ + &CheckerInfo{Name: "posEq", Params: []string{"parser", "pos", "rowID"}}, +} + +func (checker *assertPosEq) Check(params []interface{}, names []string) (result bool, error string) { + parser := params[0].(mydump.Parser) + pos, rowID := parser.Pos() + expectedPos := int64(params[1].(int)) + expectedRowID := int64(params[2].(int)) + return pos == expectedPos && rowID == expectedRowID, "" +} + +var nullDatum types.Datum + +type testCase struct { + input string + expected [][]types.Datum +} + +func (s *testMydumpCSVParserSuite) runTestCases(c *C, cfg *config.CSVConfig, blockBufSize int64, cases []testCase) { + for _, tc := range cases { + parser := mydump.NewCSVParser(cfg, mydump.NewStringReader(tc.input), blockBufSize, s.ioWorkers, false) + for i, row := range tc.expected { + comment := Commentf("input = %q, row = %d", tc.input, i+1) + e := parser.ReadRow() + c.Assert(e, IsNil, Commentf("input = %q, row = %d, error = %s", tc.input, i+1, errors.ErrorStack(e))) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{RowID: int64(i) + 1, Row: row}, comment) + } + c.Assert(errors.Cause(parser.ReadRow()), Equals, io.EOF, Commentf("input = %q", tc.input)) + } +} + +func (s *testMydumpCSVParserSuite) runFailingTestCases(c *C, cfg *config.CSVConfig, blockBufSize int64, cases []string) { + for _, tc := range cases { + parser := mydump.NewCSVParser(cfg, mydump.NewStringReader(tc), blockBufSize, s.ioWorkers, false) + e := parser.ReadRow() + c.Assert(e, ErrorMatches, "syntax error.*", Commentf("input = %q / %s", tc, errors.ErrorStack(e))) + } +} + +func tpchDatums() [][]types.Datum { + datums := make([][]types.Datum, 0, 3) + datums = append(datums, []types.Datum{ + types.NewStringDatum("1"), + types.NewStringDatum("goldenrod lavender spring chocolate lace"), + types.NewStringDatum("Manufacturer#1"), + types.NewStringDatum("Brand#13"), + types.NewStringDatum("PROMO BURNISHED COPPER"), + types.NewStringDatum("7"), + types.NewStringDatum("JUMBO PKG"), + types.NewStringDatum("901.00"), + types.NewStringDatum("ly. slyly ironi"), + }) + datums = append(datums, []types.Datum{ + types.NewStringDatum("2"), + types.NewStringDatum("blush thistle blue yellow saddle"), + types.NewStringDatum("Manufacturer#1"), + types.NewStringDatum("Brand#13"), + types.NewStringDatum("LARGE BRUSHED BRASS"), + types.NewStringDatum("1"), + types.NewStringDatum("LG CASE"), + types.NewStringDatum("902.00"), + types.NewStringDatum("lar accounts amo"), + }) + datums = append(datums, []types.Datum{ + types.NewStringDatum("3"), + types.NewStringDatum("spring green yellow purple cornsilk"), + types.NewStringDatum("Manufacturer#4"), + types.NewStringDatum("Brand#42"), + types.NewStringDatum("STANDARD POLISHED BRASS"), + types.NewStringDatum("21"), + types.NewStringDatum("WRAP CASE"), + types.NewStringDatum("903.00"), + types.NewStringDatum("egular deposits hag"), + }) + + return datums +} + +func datumsToString(datums [][]types.Datum, delimitor string, quote string, lastSep bool) string { + var b strings.Builder + doubleQuote := quote + quote + for _, ds := range datums { + for i, d := range ds { + text := d.GetString() + if len(quote) > 0 { + b.WriteString(quote) + b.WriteString(strings.ReplaceAll(text, quote, doubleQuote)) + b.WriteString(quote) + } else { + b.WriteString(text) + } + if lastSep || i < len(ds)-1 { + b.WriteString(delimitor) + } + } + b.WriteString("\r\n") + } + return b.String() +} + +func (s *testMydumpCSVParserSuite) TestTPCH(c *C) { + datums := tpchDatums() + input := datumsToString(datums, "|", "", true) + reader := mydump.NewStringReader(input) + + cfg := config.CSVConfig{ + Separator: "|", + Delimiter: "", + TrimLastSep: true, + } + + parser := mydump.NewCSVParser(&cfg, reader, int64(config.ReadBlockSize), s.ioWorkers, false) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 1, + Row: datums[0], + }) + c.Assert(parser, posEq, 126, 1) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 2, + Row: datums[1], + }) + c.Assert(parser, posEq, 241, 2) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 3, + Row: datums[2], + }) + c.Assert(parser, posEq, 369, 3) + + c.Assert(errors.Cause(parser.ReadRow()), Equals, io.EOF) +} + +func (s *testMydumpCSVParserSuite) TestTPCHMultiBytes(c *C) { + datums := tpchDatums() + sepsAndQuotes := [][2]string{ + {",", ""}, + {"\000", ""}, + {",", ""}, + {"🤔", ""}, + {",", "。"}, + {"||", ""}, + {"|+|", ""}, + {"##", ""}, + {",", "'"}, + {",", `"`}, + {"🤔", `''`}, + {"🤔", `"'`}, + {"🤔", `"'`}, + {"🤔", "🌚"}, // this two emoji have same prefix bytes + {"##", "#-"}, + {"\\s", "\\q"}, + {",", "1"}, + {",", "ac"}, + } + for _, SepAndQuote := range sepsAndQuotes { + inputStr := datumsToString(datums, SepAndQuote[0], SepAndQuote[1], false) + + // extract all index in the middle of '\r\n' from the inputStr. + // they indicate where the parser stops after reading one row. + // should be equals to the number of datums. + var allExpectedParserPos []int + for { + last := 0 + if len(allExpectedParserPos) > 0 { + last = allExpectedParserPos[len(allExpectedParserPos)-1] + } + pos := strings.IndexByte(inputStr[last:], '\r') + if pos < 0 { + break + } + allExpectedParserPos = append(allExpectedParserPos, last+pos+1) + } + c.Assert(allExpectedParserPos, HasLen, len(datums)) + + cfg := config.CSVConfig{ + Separator: SepAndQuote[0], + Delimiter: SepAndQuote[1], + TrimLastSep: false, + } + + reader := mydump.NewStringReader(inputStr) + parser := mydump.NewCSVParser(&cfg, reader, int64(config.ReadBlockSize), s.ioWorkers, false) + + for i, expectedParserPos := range allExpectedParserPos { + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: int64(i + 1), + Row: datums[i], + }) + c.Assert(parser, posEq, expectedParserPos, i+1) + } + + c.Assert(errors.Cause(parser.ReadRow()), Equals, io.EOF) + } +} + +func (s *testMydumpCSVParserSuite) TestRFC4180(c *C) { + cfg := config.CSVConfig{ + Separator: ",", + Delimiter: `"`, + } + + // example 1, trailing new lines + + parser := mydump.NewCSVParser(&cfg, mydump.NewStringReader("aaa,bbb,ccc\nzzz,yyy,xxx\n"), int64(config.ReadBlockSize), s.ioWorkers, false) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 1, + Row: []types.Datum{ + types.NewStringDatum("aaa"), + types.NewStringDatum("bbb"), + types.NewStringDatum("ccc"), + }, + }) + c.Assert(parser, posEq, 12, 1) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 2, + Row: []types.Datum{ + types.NewStringDatum("zzz"), + types.NewStringDatum("yyy"), + types.NewStringDatum("xxx"), + }, + }) + c.Assert(parser, posEq, 24, 2) + + c.Assert(errors.Cause(parser.ReadRow()), Equals, io.EOF) + + // example 2, no trailing new lines + + parser = mydump.NewCSVParser(&cfg, mydump.NewStringReader("aaa,bbb,ccc\nzzz,yyy,xxx"), int64(config.ReadBlockSize), s.ioWorkers, false) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 1, + Row: []types.Datum{ + types.NewStringDatum("aaa"), + types.NewStringDatum("bbb"), + types.NewStringDatum("ccc"), + }, + }) + c.Assert(parser, posEq, 12, 1) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 2, + Row: []types.Datum{ + types.NewStringDatum("zzz"), + types.NewStringDatum("yyy"), + types.NewStringDatum("xxx"), + }, + }) + c.Assert(parser, posEq, 23, 2) + + c.Assert(errors.Cause(parser.ReadRow()), Equals, io.EOF) + + // example 5, quoted fields + + parser = mydump.NewCSVParser(&cfg, mydump.NewStringReader(`"aaa","bbb","ccc"`+"\nzzz,yyy,xxx"), int64(config.ReadBlockSize), s.ioWorkers, false) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 1, + Row: []types.Datum{ + types.NewStringDatum("aaa"), + types.NewStringDatum("bbb"), + types.NewStringDatum("ccc"), + }, + }) + c.Assert(parser, posEq, 18, 1) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 2, + Row: []types.Datum{ + types.NewStringDatum("zzz"), + types.NewStringDatum("yyy"), + types.NewStringDatum("xxx"), + }, + }) + c.Assert(parser, posEq, 29, 2) + + c.Assert(errors.Cause(parser.ReadRow()), Equals, io.EOF) + + // example 6, line breaks within fields + + parser = mydump.NewCSVParser(&cfg, mydump.NewStringReader(`"aaa","b +bb","ccc" +zzz,yyy,xxx`), int64(config.ReadBlockSize), s.ioWorkers, false) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 1, + Row: []types.Datum{ + types.NewStringDatum("aaa"), + types.NewStringDatum("b\nbb"), + types.NewStringDatum("ccc"), + }, + }) + c.Assert(parser, posEq, 19, 1) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 2, + Row: []types.Datum{ + types.NewStringDatum("zzz"), + types.NewStringDatum("yyy"), + types.NewStringDatum("xxx"), + }, + }) + c.Assert(parser, posEq, 30, 2) + + c.Assert(errors.Cause(parser.ReadRow()), Equals, io.EOF) + + // example 7, quote escaping + + parser = mydump.NewCSVParser(&cfg, mydump.NewStringReader(`"aaa","b""bb","ccc"`), int64(config.ReadBlockSize), s.ioWorkers, false) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 1, + Row: []types.Datum{ + types.NewStringDatum("aaa"), + types.NewStringDatum("b\"bb"), + types.NewStringDatum("ccc"), + }, + }) + c.Assert(parser, posEq, 19, 1) + + c.Assert(errors.Cause(parser.ReadRow()), Equals, io.EOF) +} + +func (s *testMydumpCSVParserSuite) TestMySQL(c *C) { + cfg := config.CSVConfig{ + Separator: ",", + Delimiter: `"`, + BackslashEscape: true, + NotNull: false, + Null: `\N`, + } + + parser := mydump.NewCSVParser(&cfg, mydump.NewStringReader(`"\"","\\","\?" +"\ +",\N,\\N`), int64(config.ReadBlockSize), s.ioWorkers, false) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 1, + Row: []types.Datum{ + types.NewStringDatum(`"`), + types.NewStringDatum(`\`), + types.NewStringDatum("?"), + }, + }) + c.Assert(parser, posEq, 15, 1) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 2, + Row: []types.Datum{ + types.NewStringDatum("\n"), + nullDatum, + types.NewStringDatum(`\N`), + }, + }) + c.Assert(parser, posEq, 26, 2) + + c.Assert(errors.Cause(parser.ReadRow()), Equals, io.EOF) +} + +func (s *testMydumpCSVParserSuite) TestSyntaxError(c *C) { + cfg := config.CSVConfig{ + Separator: ",", + Delimiter: `"`, + BackslashEscape: true, + } + + inputs := []string{ + `"???`, + `\`, + `"\`, + `0"`, + `0\`, + "\"\v", + `"""`, + "\"\r", + "\"\x01", + } + + s.runFailingTestCases(c, &cfg, int64(config.ReadBlockSize), inputs) + + cfg.BackslashEscape = false + s.runFailingTestCases(c, &cfg, int64(config.ReadBlockSize), []string{`"\`}) +} + +func (s *testMydumpCSVParserSuite) TestTSV(c *C) { + cfg := config.CSVConfig{ + Separator: "\t", + Delimiter: "", + BackslashEscape: false, + NotNull: false, + Null: "", + Header: true, + } + + parser := mydump.NewCSVParser(&cfg, mydump.NewStringReader(`a b c d e f +0 foo 0000-00-00 +0 foo 0000-00-00 +0 abc def ghi bar 1999-12-31`), int64(config.ReadBlockSize), s.ioWorkers, true) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 1, + Row: []types.Datum{ + types.NewStringDatum("0"), + nullDatum, + nullDatum, + nullDatum, + types.NewStringDatum("foo"), + types.NewStringDatum("0000-00-00"), + }, + }) + c.Assert(parser, posEq, 32, 1) + c.Assert(parser.Columns(), DeepEquals, []string{"a", "b", "c", "d", "e", "f"}) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 2, + Row: []types.Datum{ + types.NewStringDatum("0"), + nullDatum, + nullDatum, + nullDatum, + types.NewStringDatum("foo"), + types.NewStringDatum("0000-00-00"), + }, + }) + c.Assert(parser, posEq, 52, 2) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 3, + Row: []types.Datum{ + types.NewStringDatum("0"), + types.NewStringDatum("abc"), + types.NewStringDatum("def"), + types.NewStringDatum("ghi"), + types.NewStringDatum("bar"), + types.NewStringDatum("1999-12-31"), + }, + }) + c.Assert(parser, posEq, 80, 3) + + c.Assert(errors.Cause(parser.ReadRow()), Equals, io.EOF) +} + +func (s *testMydumpCSVParserSuite) TestCsvWithWhiteSpaceLine(c *C) { + cfg := config.CSVConfig{ + Separator: ",", + Delimiter: `"`, + } + data := " \r\n\r\n0,,abc\r\n \r\n123,1999-12-31,test\r\n" + parser := mydump.NewCSVParser(&cfg, mydump.NewStringReader(data), int64(config.ReadBlockSize), s.ioWorkers, false) + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 1, + Row: []types.Datum{ + types.NewStringDatum("0"), + nullDatum, + types.NewStringDatum("abc"), + }, + }) + + c.Assert(parser, posEq, 12, 1) + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 2, + Row: []types.Datum{ + types.NewStringDatum("123"), + types.NewStringDatum("1999-12-31"), + types.NewStringDatum("test"), + }, + }) + c.Assert(parser.Close(), IsNil) + + cfg.Header = true + data = " \r\na,b,c\r\n0,,abc\r\n" + parser = mydump.NewCSVParser(&cfg, mydump.NewStringReader(data), int64(config.ReadBlockSize), s.ioWorkers, true) + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.Columns(), DeepEquals, []string{"a", "b", "c"}) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 1, + Row: []types.Datum{ + types.NewStringDatum("0"), + nullDatum, + types.NewStringDatum("abc"), + }, + }) + + c.Assert(parser, posEq, 17, 1) + c.Assert(parser.Close(), IsNil) +} + +func (s *testMydumpCSVParserSuite) TestEmpty(c *C) { + cfg := config.CSVConfig{ + Separator: ",", + Delimiter: `"`, + } + + parser := mydump.NewCSVParser(&cfg, mydump.NewStringReader(""), int64(config.ReadBlockSize), s.ioWorkers, false) + c.Assert(errors.Cause(parser.ReadRow()), Equals, io.EOF) + + // Try again with headers. + + cfg.Header = true + + parser = mydump.NewCSVParser(&cfg, mydump.NewStringReader(""), int64(config.ReadBlockSize), s.ioWorkers, true) + c.Assert(errors.Cause(parser.ReadRow()), Equals, io.EOF) + + parser = mydump.NewCSVParser(&cfg, mydump.NewStringReader("h\n"), int64(config.ReadBlockSize), s.ioWorkers, true) + c.Assert(errors.Cause(parser.ReadRow()), Equals, io.EOF) +} + +func (s *testMydumpCSVParserSuite) TestCRLF(c *C) { + cfg := config.CSVConfig{ + Separator: ",", + Delimiter: `"`, + } + parser := mydump.NewCSVParser(&cfg, mydump.NewStringReader("a\rb\r\nc\n\n\n\nd"), int64(config.ReadBlockSize), s.ioWorkers, false) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 1, + Row: []types.Datum{types.NewStringDatum("a")}, + }) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 2, + Row: []types.Datum{types.NewStringDatum("b")}, + }) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 3, + Row: []types.Datum{types.NewStringDatum("c")}, + }) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 4, + Row: []types.Datum{types.NewStringDatum("d")}, + }) + + c.Assert(errors.Cause(parser.ReadRow()), Equals, io.EOF) +} + +func (s *testMydumpCSVParserSuite) TestQuotedSeparator(c *C) { + cfg := config.CSVConfig{ + Separator: ",", + Delimiter: `"`, + } + + parser := mydump.NewCSVParser(&cfg, mydump.NewStringReader(`",",','`), int64(config.ReadBlockSize), s.ioWorkers, false) + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 1, + Row: []types.Datum{ + types.NewStringDatum(","), + types.NewStringDatum("'"), + types.NewStringDatum("'"), + }, + }) + + c.Assert(errors.Cause(parser.ReadRow()), Equals, io.EOF) +} + +func (s *testMydumpCSVParserSuite) TestConsecutiveFields(c *C) { + // Note: the behavior of reading `"xxx"yyy` here is undefined in RFC 4180. + // Python's CSV module returns `xxxyyy`. + // Rust's CSV package returns `xxxyyy`. + // Go's CSV package returns a parse error. + // NPM's CSV package returns a parse error. + // MySQL's LOAD DATA statement returns `"xxx"yyy` as-is. + + cfg := config.CSVConfig{ + Separator: ",", + Delimiter: `"`, + } + + testCases := []string{ + `"x"?`, + "\"\"\x01", + "\"\"\v", + } + + s.runFailingTestCases(c, &cfg, int64(config.ReadBlockSize), testCases) +} + +func (s *testMydumpCSVParserSuite) TestSpecialChars(c *C) { + cfg := config.CSVConfig{Separator: ",", Delimiter: `"`} + testCases := []testCase{ + { + input: "\x00", + expected: [][]types.Datum{{types.NewStringDatum("\x00")}}, + }, + { + input: `0\`, + expected: [][]types.Datum{{types.NewStringDatum(`0\`)}}, + }, + { + input: `\`, + expected: [][]types.Datum{{types.NewStringDatum(`\`)}}, + }, + { + input: "0\v", + expected: [][]types.Datum{{types.NewStringDatum("0\v")}}, + }, + { + input: "0\x00", + expected: [][]types.Datum{{types.NewStringDatum("0\x00")}}, + }, + { + input: "\n\r", + expected: [][]types.Datum{}, + }, + { + input: `"""",0`, + expected: [][]types.Datum{{types.NewStringDatum(`"`), types.NewStringDatum(`0`)}}, + }, + } + + s.runTestCases(c, &cfg, int64(config.ReadBlockSize), testCases) +} + +func (s *testMydumpCSVParserSuite) TestContinuation(c *C) { + cfg := config.CSVConfig{ + Separator: ",", + Delimiter: `"`, + BackslashEscape: true, + TrimLastSep: true, + } + + testCases := []testCase{ + { + input: `"abcdef",\njklm,nop` + "\r\n" + `"""""","\n",a,`, + expected: [][]types.Datum{ + { + types.NewStringDatum("abcdef"), + types.NewStringDatum("\njklm"), + types.NewStringDatum("nop"), + }, + { + types.NewStringDatum(`""`), + types.NewStringDatum("\n"), + types.NewStringDatum("a"), + }, + }, + }, + { + input: `"VzMXdTXsLbiIqTYQlwPSudocNPKVsAqXgnuvupXEzlxkaFpBtHNDyoVEydoEgdnhsygaNHLpMTdEkpkrkNdzVjCbSoXvUqwoVaca"`, + expected: [][]types.Datum{{types.NewStringDatum("VzMXdTXsLbiIqTYQlwPSudocNPKVsAqXgnuvupXEzlxkaFpBtHNDyoVEydoEgdnhsygaNHLpMTdEkpkrkNdzVjCbSoXvUqwoVaca")}}, + }, + } + + s.runTestCases(c, &cfg, 1, testCases) +} + +func (s *testMydumpCSVParserSuite) TestBackslashAsSep(c *C) { + cfg := config.CSVConfig{ + Separator: `\`, + Delimiter: `"`, + } + + testCases := []testCase{ + { + input: `0\`, + expected: [][]types.Datum{{types.NewStringDatum("0"), nullDatum}}, + }, + { + input: `\`, + expected: [][]types.Datum{{nullDatum, nullDatum}}, + }, + } + + s.runTestCases(c, &cfg, 1, testCases) + + failingInputs := []string{ + `"\`, + } + s.runFailingTestCases(c, &cfg, 1, failingInputs) +} + +func (s *testMydumpCSVParserSuite) TestBackslashAsDelim(c *C) { + cfg := config.CSVConfig{ + Separator: ",", + Delimiter: `\`, + } + + testCases := []testCase{ + { + input: `\\`, + expected: [][]types.Datum{{nullDatum}}, + }, + } + s.runTestCases(c, &cfg, 1, testCases) + + failingInputs := []string{ + `"\`, + } + s.runFailingTestCases(c, &cfg, 1, failingInputs) +} + +// errorReader implements the Reader interface which always returns an error. +type errorReader struct{} + +func (*errorReader) Read(p []byte) (int, error) { + return 0, errors.New("fake read error") +} + +func (*errorReader) Seek(offset int64, whence int) (int64, error) { + return 0, errors.New("fake seek error") +} + +func (*errorReader) Close() error { + return errors.New("fake close error") +} + +func (s *testMydumpCSVParserSuite) TestReadError(c *C) { + cfg := config.CSVConfig{ + Separator: ",", + Delimiter: `"`, + } + + parser := mydump.NewCSVParser(&cfg, &errorReader{}, int64(config.ReadBlockSize), s.ioWorkers, false) + c.Assert(parser.ReadRow(), ErrorMatches, "fake read error") +} + +// TestSyntaxErrorLog checks that a syntax error won't dump huge strings into the log. +func (s *testMydumpCSVParserSuite) TestSyntaxErrorLog(c *C) { + cfg := config.CSVConfig{ + Separator: "\t", + Delimiter: "'", + } + + tc := mydump.NewStringReader("x'" + strings.Repeat("y", 50000)) + parser := mydump.NewCSVParser(&cfg, tc, 50000, s.ioWorkers, false) + logger, buffer := log.MakeTestLogger() + parser.SetLogger(logger) + c.Assert(parser.ReadRow(), ErrorMatches, "syntax error.*") + c.Assert(logger.Sync(), IsNil) + + c.Assert( + buffer.Stripped(), Equals, + `{"$lvl":"ERROR","$msg":"syntax error","pos":1,"content":"'`+strings.Repeat("y", 255)+`"}`, + ) +} + +// TestTrimLastSep checks that set `TrimLastSep` to true trim only the last empty filed. +func (s *testMydumpCSVParserSuite) TestTrimLastSep(c *C) { + cfg := config.CSVConfig{ + Separator: ",", + Delimiter: `"`, + TrimLastSep: true, + } + parser := mydump.NewCSVParser( + &cfg, + mydump.NewStringReader("123,456,789,\r\na,b,,\r\n,,,\r\n\"a\",\"\",\"\",\r\n"), + int64(config.ReadBlockSize), + s.ioWorkers, + false, + ) + for i := 0; i < 4; i++ { + c.Assert(parser.ReadRow(), IsNil) + c.Assert(len(parser.LastRow().Row), Equals, 3) + } +} + +// Run `go test github.com/pingcap/br/pkg/lightning/mydump -check.b -check.bmem -test.v` to get benchmark result. +// Please ensure your temporary storage has (c.N / 2) KiB of free space. + +type benchCSVParserSuite struct { + csvPath string + ioWorkers *worker.Pool +} + +var _ = Suite(&benchCSVParserSuite{}) + +func (s *benchCSVParserSuite) setupTest(c *C) { + s.ioWorkers = worker.NewPool(context.Background(), 5, "bench_csv") + + dir := c.MkDir() + s.csvPath = filepath.Join(dir, "input.csv") + file, err := os.Create(s.csvPath) + c.Assert(err, IsNil) + defer func() { + c.Assert(file.Close(), IsNil) + }() + for i := 0; i < c.N; i++ { + _, err = file.WriteString("18,1,1,0.3650,GC,BARBARBAR,rw9AOV1AjoI1,50000.00,-10.00,10.00,1,1,djj3Q2XaIPoYVy1FuF,gc80Q2o82Au3C9xv,PYOolSxG3w,DI,265111111,7586538936787184,2020-02-26 20:06:00.193,OE,YCkSPBVqoJ2V5F8zWs87V5XzbaIY70aWCD4dgcB6bjUzCr5wOJCJ2TYH49J7yWyysbudJIxlTAEWSJahY7hswLtTsqyjEkrlsN8iDMAa9Poj29miJ08tnn2G8mL64IlyywvnRGbLbyGvWDdrOSF42RyUFTWVyqlDWc6Gr5wyMPYgvweKemzFDVD3kro5JsmBmJY08EK54nQoyfo2sScyb34zcM9GFo9ZQTwloINfPYQKXQm32m0XvU7jiNmYpFTFJQjdqA825SEvQqMMefG2WG4jVu9UPdhdUjRsFRd0Gw7YPKByOlcuY0eKxT7sAzMKXx2000RR6dqHNXe47oVYd\n") + c.Assert(err, IsNil) + } + c.ResetTimer() +} + +func (s *benchCSVParserSuite) BenchmarkReadRowUsingMydumpCSVParser(c *C) { + s.setupTest(c) + + file, err := os.Open(s.csvPath) + c.Assert(err, IsNil) + defer func() { + c.Assert(file.Close(), IsNil) + }() + + cfg := config.CSVConfig{Separator: ","} + parser := mydump.NewCSVParser(&cfg, file, 65536, s.ioWorkers, false) + parser.SetLogger(log.Logger{Logger: zap.NewNop()}) + + rowsCount := 0 + for { + err := parser.ReadRow() + if err == nil { + parser.RecycleRow(parser.LastRow()) + rowsCount++ + continue + } + if errors.Cause(err) == io.EOF { + break + } + c.Fatal(err) + } + c.Assert(rowsCount, Equals, c.N) +} + +func (s *benchCSVParserSuite) BenchmarkReadRowUsingEncodingCSV(c *C) { + s.setupTest(c) + + file, err := os.Open(s.csvPath) + c.Assert(err, IsNil) + defer func() { + c.Assert(file.Close(), IsNil) + }() + + csvParser := csv.NewReader(file) + + rowsCount := 0 + var datums []types.Datum + for { + records, err := csvParser.Read() + if err == nil { + // for fair comparison, we need to include the cost of conversion to Datum. + for _, record := range records { + datums = append(datums, types.NewStringDatum(record)) + } + datums = datums[:0] + rowsCount++ + continue + } + if errors.Cause(err) == io.EOF { + break + } + c.Fatal(err) + } + c.Assert(rowsCount, Equals, c.N) +} diff --git a/pkg/lightning/mydump/examples/metadata b/pkg/lightning/mydump/examples/metadata new file mode 100644 index 000000000..ce9e92da1 --- /dev/null +++ b/pkg/lightning/mydump/examples/metadata @@ -0,0 +1,2 @@ +Started dump at: 2017-11-30 20:16:38 +Finished dump at: 2017-11-30 20:16:38 diff --git a/pkg/lightning/mydump/examples/mocker_test-schema-create.sql b/pkg/lightning/mydump/examples/mocker_test-schema-create.sql new file mode 100644 index 000000000..240afe8ec --- /dev/null +++ b/pkg/lightning/mydump/examples/mocker_test-schema-create.sql @@ -0,0 +1 @@ +CREATE DATABASE `mocker_test` /* !40100 DEFAULT CHARACTER SET utf8 */; diff --git a/pkg/lightning/mydump/examples/mocker_test.i-schema.sql b/pkg/lightning/mydump/examples/mocker_test.i-schema.sql new file mode 100644 index 000000000..59a8ef305 --- /dev/null +++ b/pkg/lightning/mydump/examples/mocker_test.i-schema.sql @@ -0,0 +1,6 @@ +/* The characters 'ı' and 'Å¿' have lengths 2 */; +/* but their upper case 'I' and 'S" have length 1 */; +/* Code that relies on indices extracted from ToUpper will fail on this table*/; +CREATE TABLE `ı` ( +`Å¿` varchar(5) NOT NULL +) ENGINE=InnoDB; diff --git a/pkg/lightning/mydump/examples/mocker_test.i.sql b/pkg/lightning/mydump/examples/mocker_test.i.sql new file mode 100644 index 000000000..b7f1b0058 --- /dev/null +++ b/pkg/lightning/mydump/examples/mocker_test.i.sql @@ -0,0 +1 @@ +INSERT INTO `ı` VALUES ('🤪'); diff --git a/pkg/lightning/mydump/examples/mocker_test.report_case_high_risk-schema.sql b/pkg/lightning/mydump/examples/mocker_test.report_case_high_risk-schema.sql new file mode 100644 index 000000000..f01882759 --- /dev/null +++ b/pkg/lightning/mydump/examples/mocker_test.report_case_high_risk-schema.sql @@ -0,0 +1,9 @@ +CREATE TABLE `report_case_high_risk` ( +`id` int(11) NOT NULL AUTO_INCREMENT, +`report_data` varchar(10) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'ͳ��ʱ��', +`caseType` varchar(60) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '��������', +`total_case` int(11) NOT NULL DEFAULT '0' COMMENT '�ܰ���', +`today_new_case` int(11) NOT NULL DEFAULT '0' COMMENT '��������', +PRIMARY KEY(`id`), +KEY `idn_id_report_case_mid_risk` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; \ No newline at end of file diff --git a/pkg/lightning/mydump/examples/mocker_test.report_case_high_risk.sql b/pkg/lightning/mydump/examples/mocker_test.report_case_high_risk.sql new file mode 100644 index 000000000..27c7121e2 --- /dev/null +++ b/pkg/lightning/mydump/examples/mocker_test.report_case_high_risk.sql @@ -0,0 +1 @@ +INSERT INTO `report_case_high_risk` VALUES (2,'4','6',8,10); diff --git a/pkg/lightning/mydump/examples/mocker_test.tbl_autoid-schema.sql b/pkg/lightning/mydump/examples/mocker_test.tbl_autoid-schema.sql new file mode 100644 index 000000000..ca8e1d72d --- /dev/null +++ b/pkg/lightning/mydump/examples/mocker_test.tbl_autoid-schema.sql @@ -0,0 +1,8 @@ +/*!40101 SET NAMES binary*/; +/*!40014 SET FOREIGN_KEY_CHECKS=0*/; + +CREATE TABLE `tbl_autoid` ( + `ID` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `Name` varchar(64) DEFAULT NULL, + PRIMARY KEY (`ID`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; diff --git a/pkg/lightning/mydump/examples/mocker_test.tbl_autoid.sql b/pkg/lightning/mydump/examples/mocker_test.tbl_autoid.sql new file mode 100644 index 000000000..4d98b04b4 --- /dev/null +++ b/pkg/lightning/mydump/examples/mocker_test.tbl_autoid.sql @@ -0,0 +1,10010 @@ +/*!40101 SET NAMES binary*/; +/*!40014 SET FOREIGN_KEY_CHECKS=0*/; +/*!40103 SET TIME_ZONE='+00:00' */; +INSERT INTO `tbl_autoid` VALUES +(1,"0-0-0"), +(2,"0-0-1"),(3,"0-0-2"), +(4,"0-0-3"),(5,"0-0-4"),(6,"0-0-5"), +(7,"0-0-6"),(8,"0-0-7"),(9,"0-0-8"),(10,"0-0-9"); +INSERT INTO `tbl_autoid` VALUES +(11,"0-0-10"), +(12,"0-0-11"), +(13,"0-0-12"), +(14,"0-0-13"), +(15,"0-0-14"), +(16,"0-0-15"), +(17,"0-0-16"), +(18,"0-0-17"), +(19,"0-0-18"), +(20,"0-0-19"), +(21,"0-0-20"), +(22,"0-0-21"), +(23,"0-0-22"), +(24,"0-0-23"), +(25,"0-0-24"), +(26,"0-0-25"), +(27,"0-0-26"), +(28,"0-0-27"), +(29,"0-0-28"), +(30,"3-0-0"), +(31,"0-0-29"), +(32,"0-0-30"), +(33,"0-0-31"), +(34,"3-0-1"), +(35,"0-0-32"), +(36,"3-0-2"), +(37,"0-0-33"), +(38,"3-0-3"), +(39,"0-0-34"), +(40,"3-0-4"), +(41,"0-0-35"), +(42,"3-0-5"), +(43,"0-0-36"), +(44,"3-0-6"), +(45,"3-0-7"), +(46,"0-0-37"); +INSERT INTO `tbl_autoid` VALUES +(47,"3-0-8"), +(48,"0-0-38"), +(49,"3-0-9"), +(50,"0-0-39"), +(51,"3-0-10"), +(52,"0-0-40"), +(53,"3-0-11"), +(54,"0-0-41"), +(55,"3-0-12"), +(56,"0-0-42"), +(57,"0-0-43"), +(58,"0-0-44"), +(59,"3-0-13"), +(60,"0-0-45"), +(61,"3-0-14"), +(62,"0-0-46"), +(63,"3-0-15"), +(64,"0-0-47"), +(65,"3-0-16"), +(66,"3-0-17"), +(67,"0-0-48"), +(68,"3-0-18"), +(69,"3-0-19"), +(70,"0-0-49"), +(71,"3-0-20"), +(72,"3-0-21"), +(73,"4-0-0"), +(74,"3-0-22"), +(75,"3-0-23"), +(76,"3-0-24"), +(77,"3-0-25"), +(78,"3-0-26"), +(79,"3-0-27"), +(80,"4-0-1"), +(81,"3-0-28"), +(82,"3-0-29"), +(83,"4-0-2"), +(84,"3-0-30"), +(85,"3-0-31"), +(86,"4-0-3"), +(87,"3-0-32"); +INSERT INTO `tbl_autoid` VALUES +(88,"4-0-4"), +(89,"4-0-5"), +(90,"3-0-33"), +(91,"4-0-6"), +(92,"3-0-34"), +(93,"3-0-35"), +(94,"4-0-7"), +(95,"3-0-36"), +(96,"3-0-37"), +(97,"4-0-8"), +(98,"3-0-38"), +(99,"3-0-39"), +(100,"3-0-40"), +(101,"3-0-41"), +(102,"3-0-42"), +(103,"3-0-43"), +(104,"4-0-9"), +(105,"3-0-44"), +(106,"4-0-10"), +(107,"3-0-45"), +(108,"3-0-46"), +(109,"4-0-11"), +(110,"3-0-47"), +(111,"4-0-12"), +(112,"3-0-48"), +(113,"3-0-49"), +(114,"4-0-13"), +(115,"4-0-14"), +(116,"2-0-0"), +(117,"4-0-15"), +(118,"4-0-16"), +(119,"4-0-17"), +(120,"2-0-1"), +(121,"4-0-18"), +(122,"2-0-2"), +(123,"4-0-19"), +(124,"2-0-3"); +INSERT INTO `tbl_autoid` VALUES +(125,"4-0-20"), +(126,"2-0-4"), +(127,"4-0-21"), +(128,"2-0-5"), +(129,"4-0-22"), +(130,"2-0-6"), +(131,"2-0-7"), +(132,"4-0-23"), +(133,"2-0-8"), +(134,"4-0-24"), +(135,"2-0-9"), +(136,"4-0-25"), +(137,"2-0-10"), +(138,"2-0-11"), +(139,"4-0-26"), +(140,"2-0-12"), +(141,"2-0-13"), +(142,"4-0-27"), +(143,"2-0-14"), +(144,"4-0-28"), +(145,"2-0-15"), +(146,"2-0-16"), +(147,"4-0-29"), +(148,"2-0-17"), +(149,"4-0-30"), +(150,"2-0-18"), +(151,"4-0-31"), +(152,"2-0-19"), +(153,"2-0-20"), +(154,"4-0-32"), +(155,"2-0-21"), +(156,"2-0-22"), +(157,"1-0-0"), +(158,"2-0-23"), +(159,"4-0-33"), +(160,"2-0-24"), +(161,"2-0-25"), +(162,"4-0-34"), +(163,"1-0-1"), +(164,"2-0-26"), +(165,"1-0-2"), +(166,"2-0-27"), +(167,"4-0-35"), +(168,"1-0-3"), +(169,"2-0-28"), +(170,"1-0-4"), +(171,"2-0-29"), +(172,"4-0-36"), +(173,"1-0-5"), +(174,"2-0-30"), +(175,"1-0-6"), +(176,"4-0-37"), +(177,"2-0-31"), +(178,"2-0-32"), +(179,"4-0-38"), +(180,"4-0-39"), +(181,"2-0-33"), +(182,"1-0-7"), +(183,"2-0-34"), +(184,"1-0-8"), +(185,"4-0-40"), +(186,"2-0-35"), +(187,"1-0-9"), +(188,"2-0-36"), +(189,"4-0-41"), +(190,"2-0-37"), +(191,"1-0-10"), +(192,"2-0-38"), +(193,"1-0-11"), +(194,"4-0-42"), +(195,"2-0-39"), +(196,"1-0-12"), +(197,"2-0-40"), +(198,"1-0-13"), +(199,"4-0-43"), +(200,"2-0-41"), +(201,"1-0-14"), +(202,"2-0-42"), +(203,"4-0-44"), +(204,"1-0-15"), +(205,"2-0-43"), +(206,"1-0-16"), +(207,"2-0-44"), +(208,"4-0-45"), +(209,"1-0-17"), +(210,"2-0-45"), +(211,"1-0-18"); +INSERT INTO `tbl_autoid` VALUES +(212,"4-0-46"), +(213,"2-0-46"), +(214,"1-0-19"), +(215,"2-0-47"), +(216,"1-0-20"), +(217,"4-0-47"), +(218,"2-0-48"), +(219,"1-0-21"), +(220,"2-0-49"), +(221,"1-0-22"), +(222,"4-0-48"), +(223,"1-0-23"), +(224,"1-0-24"), +(225,"4-0-49"), +(226,"1-0-25"), +(227,"1-0-26"), +(228,"1-0-27"), +(229,"1-0-28"), +(230,"1-0-29"), +(231,"1-0-30"), +(232,"1-0-31"), +(233,"1-0-32"), +(234,"1-0-33"), +(235,"1-0-34"), +(236,"1-0-35"), +(237,"1-0-36"), +(238,"1-0-37"), +(239,"1-0-38"), +(240,"1-0-39"), +(241,"1-0-40"), +(242,"1-0-41"), +(243,"1-0-42"), +(244,"1-0-43"), +(245,"1-0-44"), +(246,"1-0-45"), +(247,"1-0-46"), +(248,"1-0-47"), +(249,"1-0-48"), +(250,"1-0-49"), +(251,"3-1-0"), +(252,"3-1-1"); +INSERT INTO `tbl_autoid` VALUES +(253,"3-1-2"), +(254,"3-1-3"), +(255,"3-1-4"), +(256,"3-1-5"), +(257,"3-1-6"), +(258,"3-1-7"), +(259,"3-1-8"), +(260,"3-1-9"), +(261,"3-1-10"), +(262,"3-1-11"), +(263,"3-1-12"), +(264,"3-1-13"), +(265,"3-1-14"), +(266,"3-1-15"), +(267,"3-1-16"), +(268,"3-1-17"), +(269,"3-1-18"), +(270,"3-1-19"), +(271,"3-1-20"), +(272,"3-1-21"), +(273,"3-1-22"), +(274,"3-1-23"), +(275,"3-1-24"), +(276,"3-1-25"), +(277,"3-1-26"), +(278,"3-1-27"), +(279,"3-1-28"), +(280,"3-1-29"), +(281,"3-1-30"), +(282,"3-1-31"), +(283,"3-1-32"), +(284,"3-1-33"), +(285,"3-1-34"), +(286,"3-1-35"), +(287,"3-1-36"), +(288,"3-1-37"), +(289,"3-1-38"), +(290,"3-1-39"), +(291,"3-1-40"), +(292,"3-1-41"), +(293,"3-1-42"), +(294,"3-1-43"), +(295,"3-1-44"), +(296,"3-1-45"), +(297,"3-1-46"), +(298,"3-1-47"), +(299,"3-1-48"), +(300,"3-1-49"), +(301,"2-1-0"), +(302,"2-1-1"), +(303,"2-1-2"), +(304,"2-1-3"), +(305,"2-1-4"), +(306,"2-1-5"), +(307,"2-1-6"), +(308,"2-1-7"), +(309,"2-1-8"), +(310,"2-1-9"), +(311,"2-1-10"), +(312,"2-1-11"), +(313,"2-1-12"), +(314,"2-1-13"), +(315,"2-1-14"), +(316,"2-1-15"), +(317,"2-1-16"), +(318,"2-1-17"), +(319,"2-1-18"), +(320,"2-1-19"), +(321,"2-1-20"), +(322,"2-1-21"), +(323,"2-1-22"), +(324,"2-1-23"), +(325,"2-1-24"), +(326,"2-1-25"), +(327,"2-1-26"), +(328,"2-1-27"), +(329,"2-1-28"), +(330,"2-1-29"), +(331,"2-1-30"), +(332,"2-1-31"), +(333,"2-1-32"), +(334,"2-1-33"), +(335,"2-1-34"), +(336,"2-1-35"), +(337,"2-1-36"), +(338,"2-1-37"), +(339,"2-1-38"), +(340,"2-1-39"), +(341,"2-1-40"), +(342,"2-1-41"), +(343,"2-1-42"), +(344,"2-1-43"), +(345,"2-1-44"), +(346,"2-1-45"), +(347,"2-1-46"), +(348,"2-1-47"), +(349,"2-1-48"), +(350,"2-1-49"), +(351,"1-1-0"), +(352,"1-1-1"), +(353,"1-1-2"), +(354,"1-1-3"), +(355,"1-1-4"), +(356,"1-1-5"), +(357,"1-1-6"), +(358,"1-1-7"); +INSERT INTO `tbl_autoid` VALUES +(359,"1-1-8"), +(360,"1-1-9"), +(361,"1-1-10"), +(362,"1-1-11"), +(363,"1-1-12"), +(364,"1-1-13"), +(365,"1-1-14"), +(366,"1-1-15"), +(367,"1-1-16"), +(368,"1-1-17"), +(369,"1-1-18"), +(370,"1-1-19"), +(371,"1-1-20"), +(372,"1-1-21"), +(373,"1-1-22"), +(374,"1-1-23"), +(375,"1-1-24"), +(376,"1-1-25"), +(377,"1-1-26"), +(378,"1-1-27"), +(379,"1-1-28"), +(380,"1-1-29"), +(381,"1-1-30"), +(382,"1-1-31"), +(383,"1-1-32"), +(384,"1-1-33"), +(385,"1-1-34"), +(386,"1-1-35"), +(387,"1-1-36"), +(388,"1-1-37"), +(389,"1-1-38"), +(390,"1-1-39"), +(391,"1-1-40"), +(392,"1-1-41"), +(393,"1-1-42"), +(394,"1-1-43"), +(395,"1-1-44"), +(396,"1-1-45"), +(397,"1-1-46"), +(398,"1-1-47"), +(399,"1-1-48"), +(400,"1-1-49"), +(401,"4-1-0"), +(402,"4-1-1"), +(403,"4-1-2"), +(404,"4-1-3"), +(405,"4-1-4"), +(406,"4-1-5"), +(407,"4-1-6"), +(408,"4-1-7"), +(409,"4-1-8"), +(410,"4-1-9"), +(411,"4-1-10"), +(412,"4-1-11"), +(413,"4-1-12"), +(414,"4-1-13"), +(415,"4-1-14"), +(416,"4-1-15"), +(417,"4-1-16"), +(418,"4-1-17"), +(419,"4-1-18"), +(420,"4-1-19"), +(421,"4-1-20"), +(422,"4-1-21"), +(423,"4-1-22"), +(424,"4-1-23"), +(425,"4-1-24"), +(426,"4-1-25"), +(427,"4-1-26"), +(428,"4-1-27"), +(429,"4-1-28"), +(430,"4-1-29"), +(431,"4-1-30"), +(432,"4-1-31"), +(433,"4-1-32"), +(434,"4-1-33"), +(435,"4-1-34"), +(436,"4-1-35"), +(437,"4-1-36"), +(438,"4-1-37"), +(439,"4-1-38"), +(440,"4-1-39"), +(441,"4-1-40"), +(442,"4-1-41"), +(443,"4-1-42"), +(444,"4-1-43"), +(445,"4-1-44"), +(446,"4-1-45"), +(447,"4-1-46"), +(448,"4-1-47"), +(449,"4-1-48"), +(450,"4-1-49"), +(451,"0-1-0"), +(452,"0-1-1"), +(453,"0-1-2"), +(454,"0-1-3"), +(455,"0-1-4"), +(456,"0-1-5"), +(457,"0-1-6"), +(458,"0-1-7"), +(459,"0-1-8"), +(460,"0-1-9"), +(461,"0-1-10"), +(462,"0-1-11"), +(463,"0-1-12"), +(464,"0-1-13"), +(465,"0-1-14"), +(466,"0-1-15"), +(467,"0-1-16"), +(468,"0-1-17"), +(469,"0-1-18"), +(470,"0-1-19"), +(471,"0-1-20"), +(472,"0-1-21"), +(473,"0-1-22"), +(474,"0-1-23"), +(475,"0-1-24"), +(476,"0-1-25"), +(477,"0-1-26"), +(478,"0-1-27"), +(479,"0-1-28"), +(480,"0-1-29"), +(481,"0-1-30"), +(482,"0-1-31"), +(483,"0-1-32"), +(484,"0-1-33"), +(485,"0-1-34"), +(486,"0-1-35"), +(487,"0-1-36"), +(488,"0-1-37"), +(489,"0-1-38"), +(490,"0-1-39"), +(491,"0-1-40"), +(492,"0-1-41"), +(493,"0-1-42"), +(494,"0-1-43"), +(495,"0-1-44"), +(496,"0-1-45"), +(497,"0-1-46"); +INSERT INTO `tbl_autoid` VALUES +(498,"0-1-47"), +(499,"0-1-48"), +(500,"0-1-49"), +(501,"1-2-0"), +(502,"1-2-1"), +(503,"1-2-2"), +(504,"1-2-3"), +(505,"1-2-4"), +(506,"1-2-5"), +(507,"1-2-6"), +(508,"1-2-7"), +(509,"1-2-8"), +(510,"1-2-9"), +(511,"1-2-10"), +(512,"1-2-11"), +(513,"1-2-12"), +(514,"1-2-13"), +(515,"1-2-14"), +(516,"1-2-15"), +(517,"1-2-16"), +(518,"1-2-17"), +(519,"1-2-18"), +(520,"1-2-19"), +(521,"1-2-20"), +(522,"1-2-21"), +(523,"1-2-22"), +(524,"1-2-23"), +(525,"1-2-24"), +(526,"1-2-25"), +(527,"1-2-26"), +(528,"1-2-27"), +(529,"1-2-28"), +(530,"1-2-29"), +(531,"1-2-30"), +(532,"1-2-31"), +(533,"1-2-32"), +(534,"1-2-33"), +(535,"1-2-34"), +(536,"1-2-35"), +(537,"1-2-36"), +(538,"1-2-37"), +(539,"1-2-38"), +(540,"1-2-39"), +(541,"1-2-40"), +(542,"1-2-41"), +(543,"1-2-42"), +(544,"1-2-43"), +(545,"1-2-44"), +(546,"1-2-45"), +(547,"1-2-46"), +(548,"1-2-47"), +(549,"1-2-48"), +(550,"1-2-49"), +(551,"3-2-0"), +(552,"3-2-1"), +(553,"3-2-2"), +(554,"3-2-3"), +(555,"3-2-4"), +(556,"3-2-5"), +(557,"3-2-6"), +(558,"3-2-7"), +(559,"3-2-8"), +(560,"4-2-0"), +(561,"3-2-9"), +(562,"2-2-0"), +(563,"3-2-10"), +(564,"4-2-1"), +(565,"2-2-1"), +(566,"3-2-11"), +(567,"4-2-2"), +(568,"2-2-2"), +(569,"3-2-12"), +(570,"4-2-3"), +(571,"2-2-3"), +(572,"3-2-13"), +(573,"4-2-4"), +(574,"2-2-4"), +(575,"3-2-14"), +(576,"4-2-5"), +(577,"2-2-5"), +(578,"3-2-15"), +(579,"4-2-6"), +(580,"2-2-6"), +(581,"3-2-16"), +(582,"4-2-7"), +(583,"2-2-7"), +(584,"3-2-17"), +(585,"4-2-8"), +(586,"2-2-8"), +(587,"3-2-18"), +(588,"4-2-9"), +(589,"2-2-9"), +(590,"3-2-19"), +(591,"4-2-10"), +(592,"2-2-10"), +(593,"3-2-20"), +(594,"4-2-11"), +(595,"2-2-11"), +(596,"3-2-21"), +(597,"4-2-12"); +INSERT INTO `tbl_autoid` VALUES +(598,"2-2-12"), +(599,"3-2-22"), +(600,"2-2-13"), +(601,"3-2-23"), +(602,"2-2-14"), +(603,"3-2-24"), +(604,"2-2-15"), +(605,"3-2-25"), +(606,"2-2-16"), +(607,"4-2-13"), +(608,"3-2-26"), +(609,"2-2-17"), +(610,"4-2-14"), +(611,"3-2-27"), +(612,"2-2-18"), +(613,"4-2-15"), +(614,"3-2-28"), +(615,"2-2-19"), +(616,"4-2-16"), +(617,"3-2-29"), +(618,"2-2-20"), +(619,"4-2-17"), +(620,"3-2-30"), +(621,"2-2-21"), +(622,"4-2-18"), +(623,"3-2-31"), +(624,"2-2-22"), +(625,"4-2-19"), +(626,"2-2-23"), +(627,"3-2-32"), +(628,"4-2-20"), +(629,"2-2-24"), +(630,"3-2-33"), +(631,"4-2-21"), +(632,"2-2-25"), +(633,"3-2-34"), +(634,"4-2-22"), +(635,"2-2-26"), +(636,"3-2-35"), +(637,"4-2-23"), +(638,"2-2-27"), +(639,"3-2-36"), +(640,"4-2-24"), +(641,"2-2-28"), +(642,"3-2-37"), +(643,"4-2-25"), +(644,"2-2-29"), +(645,"4-2-26"), +(646,"3-2-38"), +(647,"2-2-30"), +(648,"4-2-27"), +(649,"3-2-39"), +(650,"4-2-28"), +(651,"2-2-31"), +(652,"3-2-40"), +(653,"4-2-29"), +(654,"2-2-32"), +(655,"3-2-41"), +(656,"4-2-30"), +(657,"2-2-33"), +(658,"3-2-42"), +(659,"4-2-31"), +(660,"2-2-34"), +(661,"3-2-43"), +(662,"4-2-32"), +(663,"2-2-35"), +(664,"3-2-44"), +(665,"4-2-33"), +(666,"2-2-36"), +(667,"3-2-45"), +(668,"4-2-34"), +(669,"2-2-37"), +(670,"3-2-46"), +(671,"4-2-35"), +(672,"2-2-38"), +(673,"3-2-47"), +(674,"4-2-36"), +(675,"2-2-39"), +(676,"3-2-48"), +(677,"4-2-37"), +(678,"3-2-49"), +(679,"4-2-38"), +(680,"4-2-39"), +(681,"4-2-40"), +(682,"4-2-41"), +(683,"4-2-42"), +(684,"4-2-43"), +(685,"4-2-44"), +(686,"4-2-45"), +(687,"4-2-46"), +(688,"4-2-47"), +(689,"4-2-48"), +(690,"4-2-49"), +(691,"2-2-40"), +(692,"2-2-41"), +(693,"2-2-42"), +(694,"2-2-43"), +(695,"2-2-44"); +INSERT INTO `tbl_autoid` VALUES +(696,"2-2-45"), +(697,"2-2-46"), +(698,"2-2-47"), +(699,"2-2-48"), +(700,"2-2-49"), +(701,"0-2-0"), +(702,"0-2-1"), +(703,"0-2-2"), +(704,"0-2-3"), +(705,"0-2-4"), +(706,"0-2-5"), +(707,"0-2-6"), +(708,"0-2-7"), +(709,"0-2-8"), +(710,"0-2-9"), +(711,"0-2-10"), +(712,"0-2-11"), +(713,"0-2-12"), +(714,"0-2-13"), +(715,"0-2-14"), +(716,"0-2-15"), +(717,"0-2-16"), +(718,"0-2-17"), +(719,"0-2-18"), +(720,"0-2-19"), +(721,"0-2-20"), +(722,"0-2-21"); +INSERT INTO `tbl_autoid` VALUES +(723,"0-2-22"), +(724,"0-2-23"), +(725,"0-2-24"), +(726,"0-2-25"), +(727,"0-2-26"), +(728,"0-2-27"), +(729,"0-2-28"), +(730,"0-2-29"), +(731,"0-2-30"), +(732,"0-2-31"), +(733,"0-2-32"), +(734,"0-2-33"), +(735,"0-2-34"), +(736,"0-2-35"), +(737,"0-2-36"), +(738,"0-2-37"), +(739,"0-2-38"), +(740,"0-2-39"), +(741,"0-2-40"), +(742,"0-2-41"), +(743,"0-2-42"), +(744,"0-2-43"), +(745,"0-2-44"), +(746,"0-2-45"), +(747,"0-2-46"), +(748,"0-2-47"), +(749,"0-2-48"), +(750,"0-2-49"), +(751,"0-3-0"), +(752,"0-3-1"), +(753,"0-3-2"), +(754,"0-3-3"), +(755,"0-3-4"), +(756,"0-3-5"), +(757,"0-3-6"), +(758,"0-3-7"), +(759,"0-3-8"), +(760,"0-3-9"), +(761,"0-3-10"), +(762,"0-3-11"), +(763,"0-3-12"), +(764,"0-3-13"), +(765,"0-3-14"), +(766,"0-3-15"), +(767,"0-3-16"), +(768,"0-3-17"), +(769,"0-3-18"), +(770,"0-3-19"), +(771,"0-3-20"), +(772,"0-3-21"), +(773,"0-3-22"), +(774,"0-3-23"), +(775,"0-3-24"), +(776,"0-3-25"), +(777,"0-3-26"), +(778,"0-3-27"), +(779,"0-3-28"), +(780,"0-3-29"), +(781,"0-3-30"), +(782,"0-3-31"), +(783,"0-3-32"), +(784,"0-3-33"), +(785,"0-3-34"), +(786,"0-3-35"), +(787,"0-3-36"), +(788,"0-3-37"), +(789,"0-3-38"), +(790,"0-3-39"), +(791,"0-3-40"), +(792,"0-3-41"), +(793,"0-3-42"), +(794,"0-3-43"), +(795,"0-3-44"), +(796,"0-3-45"), +(797,"0-3-46"), +(798,"0-3-47"), +(799,"0-3-48"), +(800,"0-3-49"), +(801,"1-3-0"), +(802,"1-3-1"), +(803,"1-3-2"), +(804,"1-3-3"), +(805,"1-3-4"), +(806,"1-3-5"), +(807,"1-3-6"), +(808,"1-3-7"), +(809,"1-3-8"), +(810,"1-3-9"), +(811,"1-3-10"), +(812,"1-3-11"), +(813,"1-3-12"), +(814,"1-3-13"), +(815,"1-3-14"), +(816,"1-3-15"), +(817,"1-3-16"), +(818,"1-3-17"), +(819,"1-3-18"), +(820,"1-3-19"), +(821,"1-3-20"), +(822,"1-3-21"), +(823,"1-3-22"), +(824,"1-3-23"), +(825,"1-3-24"), +(826,"1-3-25"), +(827,"1-3-26"), +(828,"1-3-27"), +(829,"1-3-28"), +(830,"1-3-29"), +(831,"1-3-30"), +(832,"1-3-31"), +(833,"1-3-32"), +(834,"1-3-33"), +(835,"1-3-34"), +(836,"1-3-35"), +(837,"1-3-36"), +(838,"1-3-37"), +(839,"1-3-38"), +(840,"1-3-39"), +(841,"1-3-40"), +(842,"1-3-41"), +(843,"1-3-42"), +(844,"1-3-43"), +(845,"1-3-44"), +(846,"1-3-45"), +(847,"1-3-46"), +(848,"1-3-47"), +(849,"1-3-48"), +(850,"1-3-49"), +(851,"2-3-0"), +(852,"2-3-1"), +(853,"2-3-2"), +(854,"2-3-3"), +(855,"2-3-4"), +(856,"3-3-0"), +(857,"2-3-5"), +(858,"2-3-6"), +(859,"3-3-1"), +(860,"2-3-7"), +(861,"3-3-2"), +(862,"2-3-8"), +(863,"2-3-9"), +(864,"2-3-10"), +(865,"3-3-3"), +(866,"2-3-11"), +(867,"3-3-4"), +(868,"2-3-12"), +(869,"2-3-13"), +(870,"3-3-5"), +(871,"2-3-14"), +(872,"3-3-6"), +(873,"2-3-15"), +(874,"3-3-7"), +(875,"2-3-16"), +(876,"3-3-8"), +(877,"2-3-17"), +(878,"3-3-9"), +(879,"2-3-18"), +(880,"3-3-10"), +(881,"2-3-19"), +(882,"3-3-11"), +(883,"2-3-20"), +(884,"3-3-12"), +(885,"2-3-21"), +(886,"3-3-13"), +(887,"2-3-22"), +(888,"3-3-14"), +(889,"2-3-23"), +(890,"3-3-15"), +(891,"2-3-24"), +(892,"3-3-16"), +(893,"2-3-25"), +(894,"3-3-17"), +(895,"2-3-26"), +(896,"3-3-18"), +(897,"2-3-27"), +(898,"3-3-19"), +(899,"2-3-28"), +(900,"3-3-20"), +(901,"2-3-29"), +(902,"3-3-21"), +(903,"2-3-30"), +(904,"3-3-22"), +(905,"2-3-31"), +(906,"3-3-23"), +(907,"2-3-32"), +(908,"3-3-24"), +(909,"2-3-33"), +(910,"3-3-25"), +(911,"2-3-34"), +(912,"3-3-26"), +(913,"2-3-35"), +(914,"3-3-27"), +(915,"2-3-36"), +(916,"3-3-28"), +(917,"2-3-37"), +(918,"2-3-38"), +(919,"2-3-39"), +(920,"2-3-40"), +(921,"2-3-41"), +(922,"2-3-42"), +(923,"2-3-43"), +(924,"2-3-44"), +(925,"2-3-45"), +(926,"2-3-46"), +(927,"2-3-47"), +(928,"2-3-48"), +(929,"2-3-49"), +(930,"3-3-29"), +(931,"3-3-30"), +(932,"3-3-31"), +(933,"3-3-32"), +(934,"3-3-33"), +(935,"3-3-34"), +(936,"3-3-35"), +(937,"3-3-36"), +(938,"3-3-37"), +(939,"3-3-38"), +(940,"3-3-39"), +(941,"3-3-40"), +(942,"3-3-41"), +(943,"3-3-42"), +(944,"3-3-43"), +(945,"3-3-44"), +(946,"3-3-45"), +(947,"3-3-46"), +(948,"3-3-47"), +(949,"3-3-48"), +(950,"3-3-49"), +(951,"4-3-0"), +(952,"4-3-1"), +(953,"4-3-2"), +(954,"4-3-3"), +(955,"4-3-4"), +(956,"4-3-5"), +(957,"4-3-6"), +(958,"4-3-7"), +(959,"4-3-8"), +(960,"4-3-9"), +(961,"4-3-10"), +(962,"4-3-11"), +(963,"4-3-12"), +(964,"4-3-13"), +(965,"4-3-14"), +(966,"4-3-15"), +(967,"4-3-16"), +(968,"4-3-17"), +(969,"4-3-18"), +(970,"4-3-19"), +(971,"4-3-20"), +(972,"4-3-21"), +(973,"4-3-22"), +(974,"4-3-23"), +(975,"4-3-24"), +(976,"4-3-25"), +(977,"4-3-26"), +(978,"4-3-27"), +(979,"4-3-28"), +(980,"4-3-29"), +(981,"4-3-30"), +(982,"4-3-31"), +(983,"4-3-32"), +(984,"4-3-33"), +(985,"4-3-34"), +(986,"4-3-35"), +(987,"4-3-36"), +(988,"4-3-37"), +(989,"4-3-38"), +(990,"4-3-39"), +(991,"4-3-40"), +(992,"4-3-41"), +(993,"4-3-42"), +(994,"4-3-43"), +(995,"4-3-44"), +(996,"4-3-45"), +(997,"4-3-46"), +(998,"4-3-47"), +(999,"4-3-48"), +(1000,"4-3-49"), +(1001,"1-4-0"), +(1002,"1-4-1"), +(1003,"1-4-2"), +(1004,"1-4-3"), +(1005,"1-4-4"), +(1006,"1-4-5"), +(1007,"1-4-6"), +(1008,"1-4-7"), +(1009,"1-4-8"), +(1010,"1-4-9"), +(1011,"1-4-10"), +(1012,"1-4-11"), +(1013,"1-4-12"), +(1014,"1-4-13"), +(1015,"1-4-14"), +(1016,"1-4-15"), +(1017,"1-4-16"), +(1018,"1-4-17"), +(1019,"1-4-18"), +(1020,"1-4-19"), +(1021,"1-4-20"), +(1022,"1-4-21"), +(1023,"1-4-22"), +(1024,"1-4-23"), +(1025,"1-4-24"), +(1026,"1-4-25"), +(1027,"1-4-26"), +(1028,"1-4-27"), +(1029,"1-4-28"), +(1030,"1-4-29"), +(1031,"1-4-30"), +(1032,"1-4-31"), +(1033,"1-4-32"), +(1034,"1-4-33"), +(1035,"1-4-34"), +(1036,"1-4-35"), +(1037,"1-4-36"), +(1038,"1-4-37"), +(1039,"1-4-38"), +(1040,"1-4-39"), +(1041,"1-4-40"), +(1042,"1-4-41"), +(1043,"1-4-42"), +(1044,"1-4-43"), +(1045,"1-4-44"), +(1046,"1-4-45"), +(1047,"1-4-46"), +(1048,"1-4-47"), +(1049,"1-4-48"), +(1050,"1-4-49"), +(1051,"0-4-0"), +(1052,"0-4-1"), +(1053,"0-4-2"), +(1054,"0-4-3"), +(1055,"0-4-4"), +(1056,"0-4-5"), +(1057,"0-4-6"), +(1058,"0-4-7"), +(1059,"0-4-8"), +(1060,"0-4-9"), +(1061,"0-4-10"), +(1062,"0-4-11"), +(1063,"0-4-12"), +(1064,"0-4-13"), +(1065,"0-4-14"), +(1066,"0-4-15"), +(1067,"0-4-16"), +(1068,"0-4-17"), +(1069,"0-4-18"), +(1070,"0-4-19"), +(1071,"0-4-20"), +(1072,"0-4-21"), +(1073,"0-4-22"), +(1074,"0-4-23"), +(1075,"0-4-24"), +(1076,"0-4-25"), +(1077,"0-4-26"), +(1078,"0-4-27"), +(1079,"0-4-28"), +(1080,"0-4-29"), +(1081,"0-4-30"), +(1082,"0-4-31"), +(1083,"0-4-32"), +(1084,"0-4-33"), +(1085,"0-4-34"), +(1086,"0-4-35"), +(1087,"0-4-36"), +(1088,"0-4-37"), +(1089,"0-4-38"), +(1090,"0-4-39"), +(1091,"0-4-40"), +(1092,"0-4-41"), +(1093,"0-4-42"), +(1094,"0-4-43"), +(1095,"0-4-44"), +(1096,"0-4-45"), +(1097,"0-4-46"), +(1098,"0-4-47"), +(1099,"0-4-48"), +(1100,"0-4-49"), +(1101,"2-4-0"), +(1102,"2-4-1"), +(1103,"2-4-2"), +(1104,"2-4-3"), +(1105,"2-4-4"), +(1106,"2-4-5"), +(1107,"2-4-6"), +(1108,"2-4-7"), +(1109,"2-4-8"), +(1110,"2-4-9"), +(1111,"2-4-10"), +(1112,"2-4-11"), +(1113,"2-4-12"), +(1114,"2-4-13"), +(1115,"2-4-14"), +(1116,"2-4-15"), +(1117,"2-4-16"), +(1118,"2-4-17"), +(1119,"2-4-18"), +(1120,"2-4-19"), +(1121,"2-4-20"), +(1122,"2-4-21"), +(1123,"2-4-22"), +(1124,"2-4-23"), +(1125,"2-4-24"), +(1126,"2-4-25"), +(1127,"2-4-26"), +(1128,"2-4-27"), +(1129,"2-4-28"), +(1130,"2-4-29"), +(1131,"2-4-30"), +(1132,"2-4-31"), +(1133,"2-4-32"), +(1134,"2-4-33"), +(1135,"2-4-34"), +(1136,"2-4-35"), +(1137,"2-4-36"), +(1138,"2-4-37"), +(1139,"2-4-38"), +(1140,"2-4-39"), +(1141,"2-4-40"), +(1142,"2-4-41"), +(1143,"2-4-42"), +(1144,"2-4-43"), +(1145,"2-4-44"), +(1146,"2-4-45"), +(1147,"2-4-46"), +(1148,"2-4-47"), +(1149,"2-4-48"), +(1150,"2-4-49"), +(1151,"3-4-0"), +(1152,"3-4-1"), +(1153,"3-4-2"), +(1154,"3-4-3"), +(1155,"3-4-4"), +(1156,"3-4-5"), +(1157,"3-4-6"), +(1158,"3-4-7"), +(1159,"3-4-8"), +(1160,"3-4-9"), +(1161,"3-4-10"), +(1162,"3-4-11"), +(1163,"3-4-12"), +(1164,"3-4-13"), +(1165,"3-4-14"), +(1166,"3-4-15"), +(1167,"3-4-16"), +(1168,"3-4-17"), +(1169,"3-4-18"), +(1170,"3-4-19"), +(1171,"3-4-20"), +(1172,"3-4-21"), +(1173,"3-4-22"), +(1174,"3-4-23"), +(1175,"3-4-24"), +(1176,"3-4-25"), +(1177,"3-4-26"), +(1178,"3-4-27"), +(1179,"3-4-28"), +(1180,"3-4-29"), +(1181,"3-4-30"), +(1182,"3-4-31"), +(1183,"3-4-32"), +(1184,"3-4-33"), +(1185,"3-4-34"), +(1186,"3-4-35"), +(1187,"3-4-36"), +(1188,"3-4-37"), +(1189,"3-4-38"), +(1190,"3-4-39"), +(1191,"3-4-40"), +(1192,"3-4-41"), +(1193,"3-4-42"), +(1194,"3-4-43"), +(1195,"3-4-44"), +(1196,"3-4-45"), +(1197,"3-4-46"), +(1198,"3-4-47"), +(1199,"3-4-48"), +(1200,"3-4-49"), +(1201,"4-4-0"), +(1202,"4-4-1"), +(1203,"4-4-2"), +(1204,"4-4-3"), +(1205,"4-4-4"), +(1206,"4-4-5"), +(1207,"4-4-6"), +(1208,"4-4-7"), +(1209,"4-4-8"), +(1210,"4-4-9"), +(1211,"4-4-10"), +(1212,"4-4-11"), +(1213,"4-4-12"), +(1214,"4-4-13"), +(1215,"4-4-14"), +(1216,"4-4-15"), +(1217,"4-4-16"), +(1218,"4-4-17"), +(1219,"4-4-18"), +(1220,"4-4-19"), +(1221,"4-4-20"), +(1222,"4-4-21"), +(1223,"4-4-22"), +(1224,"4-4-23"), +(1225,"4-4-24"), +(1226,"4-4-25"), +(1227,"4-4-26"), +(1228,"4-4-27"), +(1229,"4-4-28"), +(1230,"4-4-29"), +(1231,"4-4-30"), +(1232,"4-4-31"), +(1233,"4-4-32"), +(1234,"4-4-33"), +(1235,"4-4-34"), +(1236,"4-4-35"), +(1237,"4-4-36"), +(1238,"4-4-37"), +(1239,"4-4-38"), +(1240,"4-4-39"), +(1241,"4-4-40"), +(1242,"4-4-41"), +(1243,"4-4-42"), +(1244,"4-4-43"), +(1245,"4-4-44"), +(1246,"4-4-45"), +(1247,"4-4-46"), +(1248,"4-4-47"), +(1249,"4-4-48"), +(1250,"4-4-49"), +(1251,"1-5-0"), +(1252,"1-5-1"), +(1253,"1-5-2"), +(1254,"1-5-3"), +(1255,"1-5-4"), +(1256,"0-5-0"), +(1257,"1-5-5"), +(1258,"1-5-6"), +(1259,"0-5-1"), +(1260,"1-5-7"), +(1261,"0-5-2"), +(1262,"1-5-8"), +(1263,"0-5-3"), +(1264,"1-5-9"), +(1265,"0-5-4"), +(1266,"1-5-10"), +(1267,"0-5-5"), +(1268,"1-5-11"), +(1269,"0-5-6"), +(1270,"1-5-12"), +(1271,"0-5-7"), +(1272,"1-5-13"), +(1273,"0-5-8"), +(1274,"1-5-14"), +(1275,"0-5-9"), +(1276,"1-5-15"), +(1277,"0-5-10"), +(1278,"1-5-16"), +(1279,"1-5-17"), +(1280,"1-5-18"), +(1281,"1-5-19"), +(1282,"0-5-11"), +(1283,"1-5-20"), +(1284,"0-5-12"), +(1285,"1-5-21"), +(1286,"0-5-13"), +(1287,"1-5-22"), +(1288,"0-5-14"), +(1289,"1-5-23"), +(1290,"0-5-15"), +(1291,"1-5-24"), +(1292,"0-5-16"), +(1293,"1-5-25"), +(1294,"0-5-17"), +(1295,"0-5-18"), +(1296,"1-5-26"), +(1297,"0-5-19"), +(1298,"1-5-27"), +(1299,"0-5-20"), +(1300,"1-5-28"), +(1301,"0-5-21"), +(1302,"1-5-29"), +(1303,"0-5-22"), +(1304,"1-5-30"), +(1305,"0-5-23"), +(1306,"1-5-31"), +(1307,"0-5-24"), +(1308,"1-5-32"), +(1309,"1-5-33"), +(1310,"1-5-34"), +(1311,"0-5-25"), +(1312,"1-5-35"), +(1313,"0-5-26"), +(1314,"0-5-27"), +(1315,"0-5-28"), +(1316,"0-5-29"), +(1317,"1-5-36"), +(1318,"0-5-30"), +(1319,"1-5-37"), +(1320,"0-5-31"), +(1321,"1-5-38"), +(1322,"0-5-32"), +(1323,"1-5-39"), +(1324,"0-5-33"), +(1325,"1-5-40"), +(1326,"0-5-34"), +(1327,"1-5-41"), +(1328,"0-5-35"), +(1329,"1-5-42"), +(1330,"0-5-36"), +(1331,"1-5-43"), +(1332,"0-5-37"), +(1333,"1-5-44"), +(1334,"0-5-38"), +(1335,"1-5-45"), +(1336,"0-5-39"), +(1337,"1-5-46"), +(1338,"0-5-40"), +(1339,"1-5-47"), +(1340,"0-5-41"), +(1341,"1-5-48"), +(1342,"0-5-42"), +(1343,"1-5-49"), +(1344,"0-5-43"), +(1345,"0-5-44"), +(1346,"0-5-45"), +(1347,"0-5-46"), +(1348,"0-5-47"), +(1349,"0-5-48"), +(1350,"0-5-49"), +(1351,"3-5-0"), +(1352,"3-5-1"), +(1353,"3-5-2"), +(1354,"3-5-3"), +(1355,"3-5-4"), +(1356,"3-5-5"), +(1357,"3-5-6"), +(1358,"3-5-7"), +(1359,"3-5-8"), +(1360,"3-5-9"), +(1361,"3-5-10"), +(1362,"3-5-11"), +(1363,"3-5-12"), +(1364,"3-5-13"), +(1365,"3-5-14"), +(1366,"3-5-15"), +(1367,"3-5-16"), +(1368,"3-5-17"), +(1369,"3-5-18"), +(1370,"3-5-19"), +(1371,"3-5-20"), +(1372,"3-5-21"), +(1373,"3-5-22"), +(1374,"3-5-23"), +(1375,"3-5-24"), +(1376,"3-5-25"), +(1377,"3-5-26"), +(1378,"3-5-27"), +(1379,"3-5-28"), +(1380,"3-5-29"), +(1381,"3-5-30"), +(1382,"3-5-31"), +(1383,"3-5-32"), +(1384,"3-5-33"), +(1385,"3-5-34"), +(1386,"3-5-35"), +(1387,"3-5-36"), +(1388,"3-5-37"), +(1389,"3-5-38"), +(1390,"3-5-39"), +(1391,"3-5-40"), +(1392,"3-5-41"), +(1393,"3-5-42"), +(1394,"3-5-43"), +(1395,"3-5-44"), +(1396,"3-5-45"), +(1397,"3-5-46"), +(1398,"3-5-47"), +(1399,"3-5-48"), +(1400,"3-5-49"), +(1401,"2-5-0"), +(1402,"2-5-1"), +(1403,"2-5-2"), +(1404,"2-5-3"), +(1405,"2-5-4"), +(1406,"2-5-5"), +(1407,"2-5-6"), +(1408,"2-5-7"), +(1409,"2-5-8"), +(1410,"2-5-9"), +(1411,"2-5-10"), +(1412,"2-5-11"), +(1413,"2-5-12"), +(1414,"2-5-13"), +(1415,"2-5-14"), +(1416,"2-5-15"), +(1417,"2-5-16"), +(1418,"2-5-17"), +(1419,"2-5-18"), +(1420,"2-5-19"), +(1421,"2-5-20"), +(1422,"2-5-21"), +(1423,"2-5-22"), +(1424,"2-5-23"), +(1425,"2-5-24"), +(1426,"2-5-25"), +(1427,"2-5-26"), +(1428,"2-5-27"), +(1429,"2-5-28"), +(1430,"2-5-29"), +(1431,"2-5-30"), +(1432,"2-5-31"), +(1433,"2-5-32"), +(1434,"2-5-33"), +(1435,"2-5-34"), +(1436,"2-5-35"), +(1437,"2-5-36"), +(1438,"2-5-37"), +(1439,"2-5-38"), +(1440,"2-5-39"), +(1441,"2-5-40"), +(1442,"2-5-41"), +(1443,"2-5-42"), +(1444,"2-5-43"), +(1445,"2-5-44"), +(1446,"2-5-45"), +(1447,"2-5-46"), +(1448,"2-5-47"), +(1449,"2-5-48"), +(1450,"2-5-49"), +(1451,"0-6-0"), +(1452,"0-6-1"), +(1453,"0-6-2"), +(1454,"0-6-3"), +(1455,"0-6-4"), +(1456,"0-6-5"), +(1457,"0-6-6"), +(1458,"0-6-7"), +(1459,"0-6-8"), +(1460,"0-6-9"), +(1461,"0-6-10"), +(1462,"0-6-11"), +(1463,"0-6-12"), +(1464,"0-6-13"), +(1465,"0-6-14"), +(1466,"0-6-15"), +(1467,"0-6-16"), +(1468,"0-6-17"), +(1469,"0-6-18"), +(1470,"0-6-19"), +(1471,"0-6-20"), +(1472,"0-6-21"), +(1473,"0-6-22"), +(1474,"0-6-23"), +(1475,"0-6-24"), +(1476,"0-6-25"), +(1477,"0-6-26"), +(1478,"0-6-27"), +(1479,"0-6-28"), +(1480,"0-6-29"), +(1481,"0-6-30"), +(1482,"0-6-31"), +(1483,"0-6-32"), +(1484,"0-6-33"), +(1485,"0-6-34"), +(1486,"0-6-35"), +(1487,"0-6-36"), +(1488,"0-6-37"), +(1489,"0-6-38"), +(1490,"0-6-39"), +(1491,"0-6-40"), +(1492,"0-6-41"), +(1493,"0-6-42"), +(1494,"0-6-43"), +(1495,"0-6-44"), +(1496,"0-6-45"), +(1497,"0-6-46"), +(1498,"0-6-47"), +(1499,"0-6-48"), +(1500,"0-6-49"), +(1501,"1-6-0"), +(1502,"1-6-1"), +(1503,"1-6-2"), +(1504,"1-6-3"), +(1505,"1-6-4"), +(1506,"1-6-5"), +(1507,"1-6-6"), +(1508,"1-6-7"), +(1509,"1-6-8"), +(1510,"1-6-9"), +(1511,"1-6-10"), +(1512,"1-6-11"), +(1513,"1-6-12"), +(1514,"1-6-13"), +(1515,"1-6-14"), +(1516,"1-6-15"), +(1517,"1-6-16"), +(1518,"1-6-17"), +(1519,"1-6-18"), +(1520,"1-6-19"), +(1521,"1-6-20"), +(1522,"1-6-21"), +(1523,"1-6-22"), +(1524,"1-6-23"), +(1525,"1-6-24"), +(1526,"1-6-25"), +(1527,"1-6-26"), +(1528,"1-6-27"), +(1529,"1-6-28"), +(1530,"1-6-29"), +(1531,"1-6-30"), +(1532,"1-6-31"), +(1533,"1-6-32"), +(1534,"1-6-33"), +(1535,"1-6-34"), +(1536,"1-6-35"), +(1537,"1-6-36"), +(1538,"1-6-37"), +(1539,"1-6-38"), +(1540,"1-6-39"), +(1541,"1-6-40"), +(1542,"1-6-41"), +(1543,"1-6-42"), +(1544,"1-6-43"), +(1545,"1-6-44"), +(1546,"1-6-45"), +(1547,"1-6-46"), +(1548,"1-6-47"), +(1549,"1-6-48"), +(1550,"1-6-49"), +(1551,"3-6-0"), +(1552,"3-6-1"), +(1553,"3-6-2"), +(1554,"3-6-3"), +(1555,"3-6-4"), +(1556,"3-6-5"), +(1557,"3-6-6"), +(1558,"3-6-7"), +(1559,"3-6-8"), +(1560,"3-6-9"), +(1561,"3-6-10"), +(1562,"3-6-11"), +(1563,"3-6-12"), +(1564,"4-5-0"), +(1565,"3-6-13"), +(1566,"3-6-14"), +(1567,"4-5-1"), +(1568,"3-6-15"), +(1569,"4-5-2"), +(1570,"3-6-16"), +(1571,"4-5-3"), +(1572,"3-6-17"), +(1573,"4-5-4"), +(1574,"3-6-18"), +(1575,"4-5-5"), +(1576,"3-6-19"), +(1577,"4-5-6"), +(1578,"3-6-20"), +(1579,"4-5-7"), +(1580,"3-6-21"), +(1581,"4-5-8"), +(1582,"3-6-22"), +(1583,"4-5-9"), +(1584,"3-6-23"), +(1585,"4-5-10"), +(1586,"3-6-24"), +(1587,"3-6-25"), +(1588,"3-6-26"), +(1589,"4-5-11"), +(1590,"3-6-27"), +(1591,"3-6-28"), +(1592,"3-6-29"), +(1593,"4-5-12"), +(1594,"3-6-30"), +(1595,"4-5-13"), +(1596,"3-6-31"), +(1597,"4-5-14"), +(1598,"3-6-32"), +(1599,"4-5-15"), +(1600,"3-6-33"), +(1601,"4-5-16"), +(1602,"3-6-34"), +(1603,"4-5-17"), +(1604,"3-6-35"), +(1605,"4-5-18"), +(1606,"4-5-19"), +(1607,"3-6-36"), +(1608,"4-5-20"), +(1609,"3-6-37"), +(1610,"4-5-21"), +(1611,"3-6-38"), +(1612,"4-5-22"), +(1613,"3-6-39"), +(1614,"4-5-23"), +(1615,"3-6-40"), +(1616,"4-5-24"), +(1617,"3-6-41"), +(1618,"4-5-25"), +(1619,"3-6-42"), +(1620,"4-5-26"), +(1621,"3-6-43"), +(1622,"4-5-27"), +(1623,"3-6-44"), +(1624,"4-5-28"), +(1625,"3-6-45"), +(1626,"4-5-29"), +(1627,"3-6-46"), +(1628,"4-5-30"), +(1629,"4-5-31"), +(1630,"4-5-32"), +(1631,"3-6-47"), +(1632,"4-5-33"), +(1633,"4-5-34"), +(1634,"4-5-35"), +(1635,"4-5-36"), +(1636,"4-5-37"), +(1637,"4-5-38"), +(1638,"4-5-39"), +(1639,"4-5-40"), +(1640,"4-5-41"), +(1641,"4-5-42"), +(1642,"4-5-43"), +(1643,"4-5-44"), +(1644,"4-5-45"), +(1645,"4-5-46"), +(1646,"4-5-47"), +(1647,"4-5-48"), +(1648,"4-5-49"), +(1649,"3-6-48"), +(1650,"3-6-49"), +(1651,"2-6-0"), +(1652,"2-6-1"), +(1653,"2-6-2"), +(1654,"2-6-3"), +(1655,"2-6-4"), +(1656,"2-6-5"), +(1657,"2-6-6"), +(1658,"2-6-7"), +(1659,"2-6-8"), +(1660,"2-6-9"), +(1661,"2-6-10"), +(1662,"2-6-11"), +(1663,"2-6-12"), +(1664,"2-6-13"), +(1665,"2-6-14"), +(1666,"2-6-15"), +(1667,"2-6-16"), +(1668,"2-6-17"), +(1669,"2-6-18"), +(1670,"2-6-19"), +(1671,"2-6-20"), +(1672,"2-6-21"), +(1673,"2-6-22"), +(1674,"2-6-23"), +(1675,"2-6-24"), +(1676,"2-6-25"), +(1677,"2-6-26"), +(1678,"2-6-27"), +(1679,"2-6-28"), +(1680,"2-6-29"), +(1681,"2-6-30"), +(1682,"2-6-31"), +(1683,"2-6-32"), +(1684,"2-6-33"), +(1685,"2-6-34"), +(1686,"2-6-35"), +(1687,"2-6-36"), +(1688,"2-6-37"), +(1689,"2-6-38"), +(1690,"2-6-39"), +(1691,"2-6-40"), +(1692,"2-6-41"), +(1693,"2-6-42"), +(1694,"2-6-43"), +(1695,"2-6-44"), +(1696,"2-6-45"), +(1697,"2-6-46"), +(1698,"2-6-47"), +(1699,"2-6-48"), +(1700,"2-6-49"), +(1701,"0-7-0"), +(1702,"0-7-1"), +(1703,"0-7-2"), +(1704,"0-7-3"), +(1705,"0-7-4"), +(1706,"0-7-5"), +(1707,"0-7-6"), +(1708,"0-7-7"), +(1709,"0-7-8"), +(1710,"0-7-9"), +(1711,"0-7-10"), +(1712,"0-7-11"), +(1713,"0-7-12"), +(1714,"0-7-13"), +(1715,"0-7-14"), +(1716,"0-7-15"), +(1717,"0-7-16"), +(1718,"0-7-17"), +(1719,"0-7-18"), +(1720,"0-7-19"), +(1721,"0-7-20"), +(1722,"0-7-21"), +(1723,"0-7-22"), +(1724,"0-7-23"), +(1725,"0-7-24"), +(1726,"0-7-25"), +(1727,"0-7-26"), +(1728,"0-7-27"), +(1729,"0-7-28"), +(1730,"0-7-29"), +(1731,"0-7-30"), +(1732,"0-7-31"), +(1733,"0-7-32"), +(1734,"0-7-33"), +(1735,"0-7-34"), +(1736,"0-7-35"), +(1737,"0-7-36"), +(1738,"0-7-37"), +(1739,"0-7-38"), +(1740,"0-7-39"), +(1741,"0-7-40"), +(1742,"0-7-41"), +(1743,"0-7-42"), +(1744,"0-7-43"), +(1745,"0-7-44"), +(1746,"0-7-45"), +(1747,"0-7-46"), +(1748,"0-7-47"), +(1749,"0-7-48"), +(1750,"0-7-49"), +(1751,"4-6-0"), +(1752,"4-6-1"), +(1753,"4-6-2"), +(1754,"4-6-3"), +(1755,"4-6-4"), +(1756,"4-6-5"), +(1757,"4-6-6"), +(1758,"4-6-7"), +(1759,"4-6-8"), +(1760,"4-6-9"), +(1761,"4-6-10"), +(1762,"4-6-11"), +(1763,"4-6-12"), +(1764,"4-6-13"), +(1765,"4-6-14"), +(1766,"4-6-15"), +(1767,"4-6-16"), +(1768,"4-6-17"), +(1769,"4-6-18"), +(1770,"4-6-19"), +(1771,"4-6-20"), +(1772,"4-6-21"), +(1773,"4-6-22"), +(1774,"4-6-23"), +(1775,"4-6-24"), +(1776,"4-6-25"), +(1777,"4-6-26"), +(1778,"4-6-27"), +(1779,"4-6-28"), +(1780,"4-6-29"), +(1781,"4-6-30"), +(1782,"4-6-31"), +(1783,"4-6-32"), +(1784,"4-6-33"), +(1785,"4-6-34"), +(1786,"4-6-35"), +(1787,"4-6-36"), +(1788,"4-6-37"), +(1789,"4-6-38"), +(1790,"4-6-39"), +(1791,"4-6-40"), +(1792,"4-6-41"), +(1793,"4-6-42"), +(1794,"4-6-43"), +(1795,"4-6-44"), +(1796,"4-6-45"), +(1797,"4-6-46"), +(1798,"4-6-47"), +(1799,"4-6-48"), +(1800,"4-6-49"), +(1801,"1-7-0"), +(1802,"1-7-1"), +(1803,"1-7-2"), +(1804,"1-7-3"), +(1805,"1-7-4"), +(1806,"1-7-5"), +(1807,"1-7-6"), +(1808,"1-7-7"), +(1809,"1-7-8"), +(1810,"1-7-9"), +(1811,"1-7-10"), +(1812,"1-7-11"), +(1813,"1-7-12"), +(1814,"1-7-13"), +(1815,"1-7-14"), +(1816,"1-7-15"), +(1817,"1-7-16"), +(1818,"1-7-17"), +(1819,"1-7-18"), +(1820,"1-7-19"), +(1821,"1-7-20"), +(1822,"1-7-21"), +(1823,"1-7-22"), +(1824,"1-7-23"), +(1825,"1-7-24"), +(1826,"1-7-25"), +(1827,"1-7-26"), +(1828,"1-7-27"), +(1829,"1-7-28"), +(1830,"1-7-29"), +(1831,"1-7-30"), +(1832,"1-7-31"), +(1833,"1-7-32"), +(1834,"1-7-33"), +(1835,"1-7-34"), +(1836,"1-7-35"), +(1837,"1-7-36"), +(1838,"1-7-37"), +(1839,"1-7-38"), +(1840,"1-7-39"), +(1841,"1-7-40"), +(1842,"1-7-41"), +(1843,"1-7-42"), +(1844,"1-7-43"), +(1845,"1-7-44"), +(1846,"1-7-45"), +(1847,"1-7-46"), +(1848,"1-7-47"), +(1849,"1-7-48"), +(1850,"1-7-49"), +(1851,"3-7-0"), +(1852,"3-7-1"), +(1853,"3-7-2"), +(1854,"3-7-3"), +(1855,"3-7-4"), +(1856,"3-7-5"), +(1857,"3-7-6"), +(1858,"3-7-7"), +(1859,"3-7-8"), +(1860,"3-7-9"), +(1861,"3-7-10"), +(1862,"3-7-11"), +(1863,"3-7-12"), +(1864,"3-7-13"), +(1865,"3-7-14"), +(1866,"3-7-15"), +(1867,"3-7-16"), +(1868,"3-7-17"), +(1869,"3-7-18"), +(1870,"3-7-19"), +(1871,"3-7-20"), +(1872,"3-7-21"), +(1873,"3-7-22"), +(1874,"3-7-23"), +(1875,"3-7-24"), +(1876,"3-7-25"), +(1877,"3-7-26"), +(1878,"3-7-27"), +(1879,"3-7-28"), +(1880,"3-7-29"), +(1881,"3-7-30"), +(1882,"3-7-31"), +(1883,"3-7-32"), +(1884,"3-7-33"), +(1885,"3-7-34"), +(1886,"3-7-35"), +(1887,"3-7-36"), +(1888,"3-7-37"), +(1889,"3-7-38"), +(1890,"3-7-39"), +(1891,"3-7-40"), +(1892,"3-7-41"), +(1893,"3-7-42"), +(1894,"3-7-43"), +(1895,"3-7-44"), +(1896,"3-7-45"), +(1897,"3-7-46"), +(1898,"3-7-47"), +(1899,"3-7-48"), +(1900,"3-7-49"), +(1901,"0-8-0"), +(1902,"0-8-1"), +(1903,"0-8-2"), +(1904,"0-8-3"), +(1905,"0-8-4"), +(1906,"0-8-5"), +(1907,"0-8-6"), +(1908,"0-8-7"), +(1909,"0-8-8"), +(1910,"0-8-9"), +(1911,"0-8-10"), +(1912,"0-8-11"), +(1913,"0-8-12"), +(1914,"0-8-13"), +(1915,"0-8-14"), +(1916,"0-8-15"), +(1917,"0-8-16"), +(1918,"0-8-17"), +(1919,"0-8-18"), +(1920,"0-8-19"), +(1921,"0-8-20"), +(1922,"0-8-21"), +(1923,"0-8-22"), +(1924,"0-8-23"), +(1925,"0-8-24"), +(1926,"0-8-25"), +(1927,"0-8-26"), +(1928,"0-8-27"), +(1929,"0-8-28"), +(1930,"0-8-29"), +(1931,"0-8-30"), +(1932,"0-8-31"), +(1933,"0-8-32"), +(1934,"0-8-33"), +(1935,"0-8-34"), +(1936,"0-8-35"), +(1937,"0-8-36"), +(1938,"0-8-37"), +(1939,"0-8-38"), +(1940,"0-8-39"), +(1941,"0-8-40"), +(1942,"0-8-41"), +(1943,"0-8-42"), +(1944,"0-8-43"), +(1945,"0-8-44"), +(1946,"0-8-45"), +(1947,"0-8-46"), +(1948,"0-8-47"), +(1949,"0-8-48"), +(1950,"0-8-49"), +(1951,"2-7-0"), +(1952,"2-7-1"), +(1953,"2-7-2"), +(1954,"2-7-3"), +(1955,"2-7-4"), +(1956,"2-7-5"), +(1957,"2-7-6"), +(1958,"2-7-7"), +(1959,"2-7-8"), +(1960,"2-7-9"), +(1961,"2-7-10"), +(1962,"2-7-11"), +(1963,"2-7-12"), +(1964,"2-7-13"), +(1965,"2-7-14"), +(1966,"2-7-15"), +(1967,"2-7-16"), +(1968,"2-7-17"), +(1969,"2-7-18"), +(1970,"2-7-19"), +(1971,"2-7-20"), +(1972,"2-7-21"), +(1973,"2-7-22"), +(1974,"2-7-23"), +(1975,"2-7-24"), +(1976,"2-7-25"), +(1977,"2-7-26"), +(1978,"2-7-27"), +(1979,"2-7-28"), +(1980,"2-7-29"), +(1981,"2-7-30"), +(1982,"2-7-31"), +(1983,"2-7-32"), +(1984,"2-7-33"), +(1985,"2-7-34"), +(1986,"2-7-35"), +(1987,"2-7-36"), +(1988,"2-7-37"), +(1989,"2-7-38"), +(1990,"2-7-39"), +(1991,"2-7-40"), +(1992,"2-7-41"), +(1993,"2-7-42"), +(1994,"2-7-43"), +(1995,"2-7-44"), +(1996,"2-7-45"), +(1997,"2-7-46"), +(1998,"2-7-47"), +(1999,"2-7-48"), +(2000,"2-7-49"), +(2001,"4-7-0"), +(2002,"4-7-1"), +(2003,"4-7-2"), +(2004,"4-7-3"), +(2005,"4-7-4"), +(2006,"4-7-5"), +(2007,"4-7-6"), +(2008,"4-7-7"), +(2009,"4-7-8"), +(2010,"4-7-9"), +(2011,"4-7-10"), +(2012,"4-7-11"), +(2013,"4-7-12"), +(2014,"4-7-13"), +(2015,"4-7-14"), +(2016,"4-7-15"), +(2017,"4-7-16"), +(2018,"4-7-17"), +(2019,"4-7-18"), +(2020,"4-7-19"), +(2021,"4-7-20"), +(2022,"4-7-21"), +(2023,"4-7-22"), +(2024,"4-7-23"), +(2025,"4-7-24"), +(2026,"4-7-25"), +(2027,"4-7-26"), +(2028,"4-7-27"), +(2029,"4-7-28"), +(2030,"4-7-29"), +(2031,"4-7-30"), +(2032,"4-7-31"), +(2033,"4-7-32"), +(2034,"4-7-33"), +(2035,"4-7-34"), +(2036,"4-7-35"), +(2037,"4-7-36"), +(2038,"4-7-37"), +(2039,"4-7-38"), +(2040,"4-7-39"), +(2041,"4-7-40"), +(2042,"4-7-41"), +(2043,"4-7-42"), +(2044,"4-7-43"), +(2045,"4-7-44"), +(2046,"4-7-45"), +(2047,"4-7-46"), +(2048,"4-7-47"), +(2049,"4-7-48"), +(2050,"4-7-49"), +(2051,"1-8-0"), +(2052,"1-8-1"), +(2053,"1-8-2"), +(2054,"1-8-3"), +(2055,"1-8-4"), +(2056,"1-8-5"), +(2057,"1-8-6"), +(2058,"1-8-7"), +(2059,"1-8-8"), +(2060,"1-8-9"), +(2061,"1-8-10"), +(2062,"1-8-11"), +(2063,"1-8-12"), +(2064,"1-8-13"), +(2065,"1-8-14"), +(2066,"1-8-15"), +(2067,"1-8-16"), +(2068,"1-8-17"), +(2069,"1-8-18"), +(2070,"1-8-19"), +(2071,"1-8-20"), +(2072,"1-8-21"), +(2073,"1-8-22"), +(2074,"1-8-23"), +(2075,"1-8-24"), +(2076,"1-8-25"), +(2077,"1-8-26"), +(2078,"1-8-27"), +(2079,"1-8-28"), +(2080,"1-8-29"), +(2081,"1-8-30"), +(2082,"1-8-31"), +(2083,"1-8-32"), +(2084,"1-8-33"), +(2085,"1-8-34"), +(2086,"1-8-35"), +(2087,"1-8-36"), +(2088,"1-8-37"), +(2089,"1-8-38"), +(2090,"1-8-39"), +(2091,"1-8-40"), +(2092,"1-8-41"), +(2093,"1-8-42"), +(2094,"1-8-43"), +(2095,"1-8-44"), +(2096,"1-8-45"), +(2097,"1-8-46"), +(2098,"1-8-47"), +(2099,"1-8-48"), +(2100,"1-8-49"), +(2101,"1-9-0"), +(2102,"0-9-0"), +(2103,"0-9-1"), +(2104,"1-9-1"), +(2105,"0-9-2"), +(2106,"1-9-2"), +(2107,"0-9-3"), +(2108,"1-9-3"), +(2109,"0-9-4"), +(2110,"1-9-4"), +(2111,"0-9-5"), +(2112,"2-8-0"), +(2113,"0-9-6"), +(2114,"1-9-5"), +(2115,"0-9-7"), +(2116,"1-9-6"), +(2117,"2-8-1"), +(2118,"0-9-8"), +(2119,"1-9-7"), +(2120,"0-9-9"), +(2121,"2-8-2"), +(2122,"1-9-8"), +(2123,"0-9-10"), +(2124,"2-8-3"), +(2125,"1-9-9"), +(2126,"0-9-11"), +(2127,"2-8-4"), +(2128,"1-9-10"), +(2129,"0-9-12"), +(2130,"2-8-5"), +(2131,"1-9-11"), +(2132,"0-9-13"), +(2133,"3-8-0"), +(2134,"1-9-12"), +(2135,"0-9-14"), +(2136,"2-8-6"), +(2137,"1-9-13"), +(2138,"0-9-15"), +(2139,"2-8-7"), +(2140,"3-8-1"), +(2141,"0-9-16"), +(2142,"1-9-14"), +(2143,"2-8-8"), +(2144,"3-8-2"), +(2145,"0-9-17"), +(2146,"1-9-15"), +(2147,"2-8-9"), +(2148,"0-9-18"), +(2149,"3-8-3"), +(2150,"1-9-16"), +(2151,"0-9-19"), +(2152,"2-8-10"), +(2153,"3-8-4"), +(2154,"1-9-17"), +(2155,"0-9-20"), +(2156,"2-8-11"), +(2157,"3-8-5"), +(2158,"1-9-18"), +(2159,"0-9-21"), +(2160,"2-8-12"), +(2161,"3-8-6"), +(2162,"0-9-22"), +(2163,"2-8-13"), +(2164,"1-9-19"), +(2165,"3-8-7"), +(2166,"0-9-23"), +(2167,"2-8-14"), +(2168,"4-8-0"), +(2169,"1-9-20"), +(2170,"2-8-15"), +(2171,"3-8-8"), +(2172,"4-8-1"), +(2173,"1-9-21"), +(2174,"2-8-16"), +(2175,"4-8-2"), +(2176,"3-8-9"), +(2177,"1-9-22"), +(2178,"2-8-17"), +(2179,"4-8-3"), +(2180,"3-8-10"), +(2181,"1-9-23"), +(2182,"4-8-4"), +(2183,"2-8-18"), +(2184,"3-8-11"), +(2185,"1-9-24"), +(2186,"4-8-5"), +(2187,"2-8-19"), +(2188,"3-8-12"), +(2189,"1-9-25"), +(2190,"4-8-6"), +(2191,"2-8-20"), +(2192,"3-8-13"), +(2193,"1-9-26"), +(2194,"4-8-7"), +(2195,"2-8-21"), +(2196,"3-8-14"), +(2197,"1-9-27"), +(2198,"4-8-8"), +(2199,"3-8-15"), +(2200,"2-8-22"), +(2201,"1-9-28"), +(2202,"4-8-9"), +(2203,"3-8-16"), +(2204,"2-8-23"), +(2205,"1-9-29"), +(2206,"4-8-10"), +(2207,"3-8-17"), +(2208,"2-8-24"), +(2209,"1-9-30"), +(2210,"4-8-11"), +(2211,"3-8-18"), +(2212,"2-8-25"), +(2213,"4-8-12"), +(2214,"1-9-31"), +(2215,"3-8-19"), +(2216,"4-8-13"), +(2217,"2-8-26"), +(2218,"1-9-32"), +(2219,"3-8-20"), +(2220,"4-8-14"), +(2221,"2-8-27"), +(2222,"3-8-21"), +(2223,"4-8-15"), +(2224,"1-9-33"), +(2225,"3-8-22"), +(2226,"4-8-16"), +(2227,"1-9-34"), +(2228,"2-8-28"), +(2229,"3-8-23"), +(2230,"4-8-17"), +(2231,"1-9-35"), +(2232,"2-8-29"), +(2233,"3-8-24"), +(2234,"4-8-18"), +(2235,"1-9-36"), +(2236,"2-8-30"), +(2237,"3-8-25"), +(2238,"1-9-37"), +(2239,"4-8-19"), +(2240,"2-8-31"), +(2241,"1-9-38"), +(2242,"4-8-20"), +(2243,"2-8-32"), +(2244,"0-9-24"), +(2245,"1-9-39"), +(2246,"2-8-33"), +(2247,"1-9-40"), +(2248,"3-8-26"), +(2249,"0-9-25"), +(2250,"2-8-34"), +(2251,"1-9-41"), +(2252,"0-9-26"), +(2253,"2-8-35"), +(2254,"3-8-27"), +(2255,"0-9-27"), +(2256,"2-8-36"), +(2257,"1-9-42"), +(2258,"3-8-28"), +(2259,"0-9-28"), +(2260,"2-8-37"), +(2261,"3-8-29"), +(2262,"1-9-43"), +(2263,"0-9-29"), +(2264,"3-8-30"), +(2265,"1-9-44"), +(2266,"0-9-30"), +(2267,"3-8-31"), +(2268,"4-8-21"), +(2269,"1-9-45"), +(2270,"0-9-31"), +(2271,"3-8-32"), +(2272,"1-9-46"), +(2273,"0-9-32"), +(2274,"3-8-33"), +(2275,"4-8-22"), +(2276,"1-9-47"), +(2277,"0-9-33"), +(2278,"3-8-34"), +(2279,"4-8-23"), +(2280,"1-9-48"), +(2281,"0-9-34"), +(2282,"3-8-35"), +(2283,"4-8-24"), +(2284,"1-9-49"), +(2285,"0-9-35"), +(2286,"3-8-36"), +(2287,"4-8-25"), +(2288,"0-9-36"), +(2289,"3-8-37"), +(2290,"4-8-26"), +(2291,"0-9-37"), +(2292,"3-8-38"), +(2293,"4-8-27"), +(2294,"0-9-38"), +(2295,"3-8-39"), +(2296,"4-8-28"), +(2297,"0-9-39"), +(2298,"3-8-40"), +(2299,"4-8-29"), +(2300,"0-9-40"), +(2301,"3-8-41"), +(2302,"4-8-30"), +(2303,"0-9-41"), +(2304,"3-8-42"), +(2305,"4-8-31"), +(2306,"0-9-42"), +(2307,"3-8-43"), +(2308,"4-8-32"), +(2309,"0-9-43"), +(2310,"3-8-44"), +(2311,"4-8-33"), +(2312,"0-9-44"), +(2313,"3-8-45"), +(2314,"4-8-34"), +(2315,"0-9-45"), +(2316,"3-8-46"), +(2317,"4-8-35"), +(2318,"0-9-46"), +(2319,"3-8-47"), +(2320,"4-8-36"), +(2321,"0-9-47"), +(2322,"3-8-48"), +(2323,"4-8-37"), +(2324,"3-8-49"), +(2325,"0-9-48"), +(2326,"4-8-38"), +(2327,"0-9-49"), +(2328,"4-8-39"), +(2329,"4-8-40"), +(2330,"4-8-41"), +(2331,"4-8-42"), +(2332,"4-8-43"), +(2333,"4-8-44"), +(2334,"4-8-45"), +(2335,"4-8-46"), +(2336,"4-8-47"), +(2337,"4-8-48"), +(2338,"4-8-49"), +(2339,"2-8-38"), +(2340,"2-8-39"), +(2341,"2-8-40"), +(2342,"2-8-41"), +(2343,"2-8-42"), +(2344,"2-8-43"), +(2345,"2-8-44"), +(2346,"2-8-45"), +(2347,"2-8-46"), +(2348,"2-8-47"), +(2349,"2-8-48"), +(2350,"2-8-49"), +(2351,"1-10-0"), +(2352,"1-10-1"), +(2353,"1-10-2"), +(2354,"1-10-3"), +(2355,"1-10-4"), +(2356,"1-10-5"), +(2357,"1-10-6"), +(2358,"1-10-7"), +(2359,"1-10-8"), +(2360,"1-10-9"), +(2361,"1-10-10"), +(2362,"1-10-11"), +(2363,"1-10-12"), +(2364,"1-10-13"), +(2365,"1-10-14"), +(2366,"1-10-15"), +(2367,"1-10-16"), +(2368,"1-10-17"), +(2369,"1-10-18"), +(2370,"1-10-19"), +(2371,"1-10-20"), +(2372,"1-10-21"), +(2373,"1-10-22"), +(2374,"1-10-23"), +(2375,"1-10-24"), +(2376,"1-10-25"), +(2377,"1-10-26"), +(2378,"1-10-27"), +(2379,"1-10-28"), +(2380,"1-10-29"), +(2381,"1-10-30"), +(2382,"1-10-31"), +(2383,"1-10-32"), +(2384,"1-10-33"), +(2385,"1-10-34"), +(2386,"1-10-35"), +(2387,"1-10-36"), +(2388,"1-10-37"), +(2389,"1-10-38"), +(2390,"1-10-39"), +(2391,"1-10-40"), +(2392,"1-10-41"), +(2393,"1-10-42"), +(2394,"1-10-43"), +(2395,"1-10-44"), +(2396,"1-10-45"), +(2397,"1-10-46"), +(2398,"1-10-47"), +(2399,"1-10-48"), +(2400,"1-10-49"), +(2401,"2-9-0"), +(2402,"2-9-1"), +(2403,"2-9-2"), +(2404,"2-9-3"), +(2405,"2-9-4"), +(2406,"2-9-5"), +(2407,"2-9-6"), +(2408,"2-9-7"), +(2409,"2-9-8"), +(2410,"2-9-9"), +(2411,"2-9-10"), +(2412,"2-9-11"), +(2413,"2-9-12"), +(2414,"2-9-13"), +(2415,"2-9-14"), +(2416,"2-9-15"), +(2417,"2-9-16"), +(2418,"2-9-17"), +(2419,"2-9-18"), +(2420,"2-9-19"), +(2421,"2-9-20"), +(2422,"2-9-21"), +(2423,"2-9-22"), +(2424,"2-9-23"), +(2425,"2-9-24"), +(2426,"2-9-25"), +(2427,"2-9-26"), +(2428,"2-9-27"), +(2429,"2-9-28"), +(2430,"2-9-29"), +(2431,"2-9-30"), +(2432,"2-9-31"), +(2433,"2-9-32"), +(2434,"2-9-33"), +(2435,"2-9-34"), +(2436,"2-9-35"), +(2437,"2-9-36"), +(2438,"2-9-37"), +(2439,"2-9-38"), +(2440,"2-9-39"), +(2441,"2-9-40"), +(2442,"2-9-41"), +(2443,"2-9-42"), +(2444,"2-9-43"), +(2445,"2-9-44"), +(2446,"2-9-45"), +(2447,"2-9-46"), +(2448,"2-9-47"), +(2449,"2-9-48"), +(2450,"2-9-49"), +(2451,"3-9-0"), +(2452,"3-9-1"), +(2453,"3-9-2"), +(2454,"3-9-3"), +(2455,"3-9-4"), +(2456,"3-9-5"), +(2457,"3-9-6"), +(2458,"3-9-7"), +(2459,"3-9-8"), +(2460,"3-9-9"), +(2461,"3-9-10"), +(2462,"3-9-11"), +(2463,"3-9-12"), +(2464,"3-9-13"), +(2465,"3-9-14"), +(2466,"3-9-15"), +(2467,"3-9-16"), +(2468,"3-9-17"), +(2469,"3-9-18"), +(2470,"3-9-19"), +(2471,"3-9-20"), +(2472,"3-9-21"), +(2473,"3-9-22"), +(2474,"3-9-23"), +(2475,"3-9-24"), +(2476,"3-9-25"), +(2477,"3-9-26"), +(2478,"3-9-27"), +(2479,"3-9-28"), +(2480,"3-9-29"), +(2481,"3-9-30"), +(2482,"3-9-31"), +(2483,"3-9-32"), +(2484,"3-9-33"), +(2485,"3-9-34"), +(2486,"3-9-35"), +(2487,"3-9-36"), +(2488,"3-9-37"), +(2489,"3-9-38"), +(2490,"3-9-39"), +(2491,"3-9-40"), +(2492,"3-9-41"), +(2493,"3-9-42"), +(2494,"3-9-43"), +(2495,"3-9-44"), +(2496,"3-9-45"), +(2497,"3-9-46"), +(2498,"3-9-47"), +(2499,"3-9-48"), +(2500,"3-9-49"), +(2501,"4-9-0"), +(2502,"4-9-1"), +(2503,"4-9-2"), +(2504,"4-9-3"), +(2505,"4-9-4"), +(2506,"4-9-5"), +(2507,"4-9-6"), +(2508,"4-9-7"), +(2509,"4-9-8"), +(2510,"4-9-9"), +(2511,"4-9-10"), +(2512,"4-9-11"), +(2513,"4-9-12"), +(2514,"4-9-13"), +(2515,"4-9-14"), +(2516,"4-9-15"), +(2517,"4-9-16"), +(2518,"4-9-17"), +(2519,"4-9-18"), +(2520,"4-9-19"), +(2521,"4-9-20"), +(2522,"4-9-21"), +(2523,"4-9-22"), +(2524,"4-9-23"), +(2525,"4-9-24"), +(2526,"4-9-25"), +(2527,"4-9-26"), +(2528,"4-9-27"), +(2529,"4-9-28"), +(2530,"4-9-29"), +(2531,"4-9-30"), +(2532,"4-9-31"), +(2533,"4-9-32"), +(2534,"4-9-33"), +(2535,"4-9-34"), +(2536,"4-9-35"), +(2537,"4-9-36"), +(2538,"4-9-37"), +(2539,"4-9-38"), +(2540,"4-9-39"), +(2541,"4-9-40"), +(2542,"4-9-41"), +(2543,"4-9-42"), +(2544,"4-9-43"), +(2545,"4-9-44"), +(2546,"4-9-45"), +(2547,"4-9-46"), +(2548,"4-9-47"), +(2549,"4-9-48"), +(2550,"4-9-49"), +(2551,"1-11-0"), +(2552,"1-11-1"), +(2553,"1-11-2"), +(2554,"1-11-3"), +(2555,"3-10-0"), +(2556,"0-10-0"), +(2557,"1-11-4"), +(2558,"3-10-1"), +(2559,"1-11-5"), +(2560,"0-10-1"), +(2561,"3-10-2"), +(2562,"1-11-6"), +(2563,"2-10-0"), +(2564,"0-10-2"), +(2565,"3-10-3"), +(2566,"1-11-7"), +(2567,"4-10-0"), +(2568,"3-10-4"), +(2569,"1-11-8"), +(2570,"2-10-1"), +(2571,"4-10-1"), +(2572,"3-10-5"), +(2573,"0-10-3"), +(2574,"1-11-9"), +(2575,"4-10-2"), +(2576,"2-10-2"), +(2577,"3-10-6"), +(2578,"4-10-3"), +(2579,"1-11-10"), +(2580,"0-10-4"), +(2581,"2-10-3"), +(2582,"3-10-7"), +(2583,"1-11-11"), +(2584,"2-10-4"), +(2585,"4-10-4"), +(2586,"0-10-5"), +(2587,"3-10-8"), +(2588,"2-10-5"), +(2589,"1-11-12"), +(2590,"4-10-5"), +(2591,"3-10-9"), +(2592,"2-10-6"), +(2593,"0-10-6"), +(2594,"3-10-10"), +(2595,"4-10-6"), +(2596,"0-10-7"), +(2597,"3-10-11"), +(2598,"4-10-7"), +(2599,"0-10-8"), +(2600,"3-10-12"), +(2601,"4-10-8"), +(2602,"0-10-9"), +(2603,"3-10-13"), +(2604,"4-10-9"), +(2605,"0-10-10"), +(2606,"3-10-14"), +(2607,"4-10-10"), +(2608,"0-10-11"), +(2609,"3-10-15"), +(2610,"4-10-11"), +(2611,"0-10-12"), +(2612,"3-10-16"), +(2613,"4-10-12"), +(2614,"3-10-17"), +(2615,"4-10-13"), +(2616,"3-10-18"), +(2617,"4-10-14"), +(2618,"3-10-19"), +(2619,"0-10-13"), +(2620,"4-10-15"), +(2621,"0-10-14"), +(2622,"4-10-16"), +(2623,"0-10-15"), +(2624,"0-10-16"), +(2625,"4-10-17"), +(2626,"0-10-17"), +(2627,"4-10-18"), +(2628,"0-10-18"), +(2629,"3-10-20"), +(2630,"4-10-19"), +(2631,"0-10-19"), +(2632,"3-10-21"), +(2633,"4-10-20"), +(2634,"0-10-20"), +(2635,"3-10-22"), +(2636,"4-10-21"), +(2637,"0-10-21"), +(2638,"4-10-22"), +(2639,"0-10-22"), +(2640,"4-10-23"), +(2641,"3-10-23"), +(2642,"0-10-23"), +(2643,"4-10-24"), +(2644,"3-10-24"), +(2645,"0-10-24"), +(2646,"4-10-25"), +(2647,"3-10-25"), +(2648,"0-10-25"), +(2649,"4-10-26"), +(2650,"3-10-26"), +(2651,"0-10-26"), +(2652,"4-10-27"), +(2653,"3-10-27"), +(2654,"0-10-27"), +(2655,"4-10-28"), +(2656,"3-10-28"), +(2657,"0-10-28"), +(2658,"4-10-29"), +(2659,"2-10-7"), +(2660,"4-10-30"), +(2661,"0-10-29"), +(2662,"3-10-29"), +(2663,"4-10-31"), +(2664,"2-10-8"), +(2665,"0-10-30"), +(2666,"3-10-30"), +(2667,"4-10-32"), +(2668,"2-10-9"), +(2669,"0-10-31"), +(2670,"3-10-31"), +(2671,"4-10-33"), +(2672,"0-10-32"), +(2673,"2-10-10"), +(2674,"3-10-32"), +(2675,"4-10-34"), +(2676,"0-10-33"), +(2677,"3-10-33"), +(2678,"4-10-35"), +(2679,"0-10-34"), +(2680,"3-10-34"), +(2681,"4-10-36"), +(2682,"0-10-35"), +(2683,"3-10-35"), +(2684,"4-10-37"), +(2685,"0-10-36"), +(2686,"3-10-36"), +(2687,"4-10-38"), +(2688,"0-10-37"), +(2689,"3-10-37"), +(2690,"4-10-39"), +(2691,"0-10-38"), +(2692,"3-10-38"), +(2693,"2-10-11"), +(2694,"4-10-40"), +(2695,"0-10-39"), +(2696,"3-10-39"), +(2697,"2-10-12"), +(2698,"0-10-40"), +(2699,"3-10-40"), +(2700,"2-10-13"), +(2701,"0-10-41"), +(2702,"3-10-41"), +(2703,"4-10-41"), +(2704,"2-10-14"), +(2705,"0-10-42"), +(2706,"3-10-42"), +(2707,"4-10-42"), +(2708,"2-10-15"), +(2709,"3-10-43"), +(2710,"0-10-43"), +(2711,"4-10-43"), +(2712,"3-10-44"), +(2713,"2-10-16"), +(2714,"4-10-44"), +(2715,"3-10-45"), +(2716,"0-10-44"), +(2717,"4-10-45"), +(2718,"3-10-46"), +(2719,"2-10-17"), +(2720,"4-10-46"), +(2721,"0-10-45"), +(2722,"3-10-47"), +(2723,"4-10-47"), +(2724,"2-10-18"), +(2725,"3-10-48"), +(2726,"4-10-48"), +(2727,"0-10-46"), +(2728,"2-10-19"), +(2729,"3-10-49"), +(2730,"4-10-49"), +(2731,"0-10-47"), +(2732,"2-10-20"), +(2733,"0-10-48"), +(2734,"2-10-21"), +(2735,"0-10-49"), +(2736,"2-10-22"), +(2737,"1-11-13"), +(2738,"2-10-23"), +(2739,"2-10-24"), +(2740,"1-11-14"), +(2741,"2-10-25"), +(2742,"1-11-15"), +(2743,"1-11-16"), +(2744,"1-11-17"), +(2745,"1-11-18"), +(2746,"1-11-19"), +(2747,"1-11-20"), +(2748,"1-11-21"), +(2749,"1-11-22"), +(2750,"1-11-23"), +(2751,"1-11-24"), +(2752,"1-11-25"), +(2753,"1-11-26"), +(2754,"1-11-27"), +(2755,"1-11-28"), +(2756,"1-11-29"), +(2757,"1-11-30"), +(2758,"1-11-31"), +(2759,"2-10-26"), +(2760,"1-11-32"), +(2761,"2-10-27"), +(2762,"1-11-33"), +(2763,"2-10-28"), +(2764,"1-11-34"), +(2765,"1-11-35"), +(2766,"1-11-36"), +(2767,"2-10-29"), +(2768,"1-11-37"), +(2769,"2-10-30"), +(2770,"1-11-38"), +(2771,"2-10-31"), +(2772,"1-11-39"), +(2773,"2-10-32"), +(2774,"1-11-40"), +(2775,"2-10-33"), +(2776,"1-11-41"), +(2777,"2-10-34"), +(2778,"2-10-35"), +(2779,"1-11-42"), +(2780,"2-10-36"), +(2781,"1-11-43"), +(2782,"2-10-37"), +(2783,"1-11-44"), +(2784,"2-10-38"), +(2785,"1-11-45"), +(2786,"2-10-39"), +(2787,"1-11-46"), +(2788,"2-10-40"), +(2789,"1-11-47"), +(2790,"2-10-41"), +(2791,"1-11-48"), +(2792,"1-11-49"), +(2793,"2-10-42"), +(2794,"2-10-43"), +(2795,"2-10-44"), +(2796,"2-10-45"), +(2797,"2-10-46"), +(2798,"2-10-47"), +(2799,"2-10-48"), +(2800,"2-10-49"), +(2801,"3-11-0"), +(2802,"3-11-1"), +(2803,"3-11-2"), +(2804,"3-11-3"), +(2805,"3-11-4"), +(2806,"3-11-5"), +(2807,"3-11-6"), +(2808,"3-11-7"), +(2809,"3-11-8"), +(2810,"3-11-9"), +(2811,"3-11-10"), +(2812,"3-11-11"), +(2813,"3-11-12"), +(2814,"3-11-13"), +(2815,"3-11-14"), +(2816,"3-11-15"), +(2817,"3-11-16"), +(2818,"3-11-17"), +(2819,"3-11-18"), +(2820,"3-11-19"), +(2821,"3-11-20"), +(2822,"3-11-21"), +(2823,"3-11-22"), +(2824,"3-11-23"), +(2825,"3-11-24"), +(2826,"3-11-25"), +(2827,"3-11-26"), +(2828,"3-11-27"), +(2829,"3-11-28"), +(2830,"3-11-29"), +(2831,"3-11-30"), +(2832,"3-11-31"), +(2833,"3-11-32"), +(2834,"3-11-33"), +(2835,"3-11-34"), +(2836,"3-11-35"), +(2837,"3-11-36"), +(2838,"3-11-37"), +(2839,"3-11-38"), +(2840,"3-11-39"), +(2841,"3-11-40"), +(2842,"3-11-41"), +(2843,"3-11-42"), +(2844,"3-11-43"), +(2845,"3-11-44"), +(2846,"3-11-45"), +(2847,"3-11-46"), +(2848,"3-11-47"), +(2849,"3-11-48"), +(2850,"3-11-49"), +(2851,"0-11-0"), +(2852,"0-11-1"), +(2853,"0-11-2"), +(2854,"0-11-3"), +(2855,"0-11-4"), +(2856,"0-11-5"), +(2857,"0-11-6"), +(2858,"0-11-7"), +(2859,"0-11-8"), +(2860,"0-11-9"), +(2861,"0-11-10"), +(2862,"0-11-11"), +(2863,"0-11-12"), +(2864,"0-11-13"), +(2865,"0-11-14"), +(2866,"0-11-15"), +(2867,"0-11-16"), +(2868,"0-11-17"), +(2869,"0-11-18"), +(2870,"0-11-19"), +(2871,"0-11-20"), +(2872,"0-11-21"), +(2873,"0-11-22"), +(2874,"0-11-23"), +(2875,"0-11-24"), +(2876,"0-11-25"), +(2877,"0-11-26"), +(2878,"0-11-27"), +(2879,"0-11-28"), +(2880,"0-11-29"), +(2881,"0-11-30"), +(2882,"0-11-31"), +(2883,"0-11-32"), +(2884,"0-11-33"), +(2885,"0-11-34"), +(2886,"0-11-35"), +(2887,"0-11-36"), +(2888,"0-11-37"), +(2889,"0-11-38"), +(2890,"0-11-39"), +(2891,"0-11-40"), +(2892,"0-11-41"), +(2893,"0-11-42"), +(2894,"0-11-43"), +(2895,"0-11-44"), +(2896,"0-11-45"), +(2897,"0-11-46"), +(2898,"0-11-47"), +(2899,"0-11-48"), +(2900,"0-11-49"), +(2901,"4-11-0"), +(2902,"4-11-1"), +(2903,"4-11-2"), +(2904,"4-11-3"), +(2905,"4-11-4"), +(2906,"4-11-5"), +(2907,"4-11-6"), +(2908,"4-11-7"), +(2909,"4-11-8"), +(2910,"4-11-9"), +(2911,"4-11-10"), +(2912,"4-11-11"), +(2913,"4-11-12"), +(2914,"4-11-13"), +(2915,"4-11-14"), +(2916,"4-11-15"), +(2917,"4-11-16"), +(2918,"4-11-17"), +(2919,"4-11-18"), +(2920,"4-11-19"), +(2921,"4-11-20"), +(2922,"4-11-21"), +(2923,"4-11-22"), +(2924,"4-11-23"), +(2925,"4-11-24"), +(2926,"4-11-25"), +(2927,"4-11-26"), +(2928,"4-11-27"), +(2929,"4-11-28"), +(2930,"4-11-29"), +(2931,"4-11-30"), +(2932,"4-11-31"), +(2933,"4-11-32"), +(2934,"4-11-33"), +(2935,"4-11-34"), +(2936,"4-11-35"), +(2937,"4-11-36"), +(2938,"4-11-37"), +(2939,"4-11-38"), +(2940,"4-11-39"), +(2941,"4-11-40"), +(2942,"4-11-41"), +(2943,"4-11-42"), +(2944,"4-11-43"), +(2945,"4-11-44"), +(2946,"4-11-45"), +(2947,"4-11-46"), +(2948,"4-11-47"), +(2949,"4-11-48"), +(2950,"4-11-49"), +(2951,"2-11-0"), +(2952,"2-11-1"), +(2953,"2-11-2"), +(2954,"2-11-3"), +(2955,"2-11-4"), +(2956,"2-11-5"), +(2957,"2-11-6"), +(2958,"2-11-7"), +(2959,"2-11-8"), +(2960,"2-11-9"), +(2961,"2-11-10"), +(2962,"2-11-11"), +(2963,"2-11-12"), +(2964,"2-11-13"), +(2965,"2-11-14"), +(2966,"2-11-15"), +(2967,"2-11-16"), +(2968,"2-11-17"), +(2969,"2-11-18"), +(2970,"2-11-19"), +(2971,"2-11-20"), +(2972,"2-11-21"), +(2973,"2-11-22"), +(2974,"2-11-23"), +(2975,"2-11-24"), +(2976,"2-11-25"), +(2977,"2-11-26"), +(2978,"2-11-27"), +(2979,"2-11-28"), +(2980,"2-11-29"), +(2981,"2-11-30"), +(2982,"2-11-31"), +(2983,"2-11-32"), +(2984,"2-11-33"), +(2985,"2-11-34"), +(2986,"2-11-35"), +(2987,"2-11-36"), +(2988,"2-11-37"), +(2989,"2-11-38"), +(2990,"2-11-39"), +(2991,"2-11-40"), +(2992,"2-11-41"), +(2993,"2-11-42"), +(2994,"2-11-43"), +(2995,"2-11-44"), +(2996,"2-11-45"), +(2997,"2-11-46"), +(2998,"2-11-47"), +(2999,"2-11-48"), +(3000,"2-11-49"), +(3001,"1-12-0"), +(3002,"1-12-1"), +(3003,"1-12-2"), +(3004,"1-12-3"), +(3005,"1-12-4"), +(3006,"1-12-5"), +(3007,"1-12-6"), +(3008,"1-12-7"), +(3009,"1-12-8"), +(3010,"1-12-9"), +(3011,"1-12-10"), +(3012,"1-12-11"), +(3013,"1-12-12"), +(3014,"1-12-13"), +(3015,"1-12-14"), +(3016,"1-12-15"), +(3017,"1-12-16"), +(3018,"1-12-17"), +(3019,"1-12-18"), +(3020,"1-12-19"), +(3021,"1-12-20"), +(3022,"1-12-21"), +(3023,"1-12-22"), +(3024,"1-12-23"), +(3025,"1-12-24"), +(3026,"1-12-25"), +(3027,"1-12-26"), +(3028,"1-12-27"), +(3029,"1-12-28"), +(3030,"1-12-29"), +(3031,"1-12-30"), +(3032,"1-12-31"), +(3033,"1-12-32"), +(3034,"1-12-33"), +(3035,"1-12-34"), +(3036,"1-12-35"), +(3037,"1-12-36"), +(3038,"1-12-37"), +(3039,"1-12-38"), +(3040,"1-12-39"), +(3041,"1-12-40"), +(3042,"1-12-41"), +(3043,"1-12-42"), +(3044,"1-12-43"), +(3045,"1-12-44"), +(3046,"1-12-45"), +(3047,"1-12-46"), +(3048,"1-12-47"), +(3049,"1-12-48"), +(3050,"1-12-49"), +(3051,"2-12-0"), +(3052,"2-12-1"), +(3053,"2-12-2"), +(3054,"2-12-3"), +(3055,"2-12-4"), +(3056,"2-12-5"), +(3057,"2-12-6"), +(3058,"2-12-7"), +(3059,"2-12-8"), +(3060,"2-12-9"), +(3061,"2-12-10"), +(3062,"2-12-11"), +(3063,"2-12-12"), +(3064,"2-12-13"), +(3065,"2-12-14"), +(3066,"2-12-15"), +(3067,"2-12-16"), +(3068,"2-12-17"), +(3069,"2-12-18"), +(3070,"2-12-19"), +(3071,"2-12-20"), +(3072,"2-12-21"), +(3073,"2-12-22"), +(3074,"2-12-23"), +(3075,"2-12-24"), +(3076,"2-12-25"), +(3077,"2-12-26"), +(3078,"2-12-27"), +(3079,"2-12-28"), +(3080,"2-12-29"), +(3081,"2-12-30"), +(3082,"2-12-31"), +(3083,"2-12-32"), +(3084,"2-12-33"), +(3085,"2-12-34"), +(3086,"2-12-35"), +(3087,"2-12-36"), +(3088,"2-12-37"), +(3089,"2-12-38"), +(3090,"2-12-39"), +(3091,"2-12-40"), +(3092,"2-12-41"); +INSERT INTO `tbl_autoid` VALUES +(3093,"2-12-42"), +(3094,"2-12-43"), +(3095,"2-12-44"), +(3096,"2-12-45"), +(3097,"2-12-46"), +(3098,"2-12-47"), +(3099,"2-12-48"), +(3100,"2-12-49"), +(3101,"3-12-0"), +(3102,"3-12-1"), +(3103,"3-12-2"), +(3104,"3-12-3"), +(3105,"3-12-4"), +(3106,"3-12-5"), +(3107,"3-12-6"), +(3108,"3-12-7"), +(3109,"3-12-8"), +(3110,"3-12-9"), +(3111,"3-12-10"), +(3112,"3-12-11"), +(3113,"3-12-12"), +(3114,"3-12-13"), +(3115,"3-12-14"), +(3116,"3-12-15"), +(3117,"3-12-16"), +(3118,"3-12-17"), +(3119,"3-12-18"), +(3120,"3-12-19"), +(3121,"3-12-20"), +(3122,"3-12-21"), +(3123,"3-12-22"), +(3124,"3-12-23"), +(3125,"3-12-24"), +(3126,"3-12-25"), +(3127,"3-12-26"), +(3128,"3-12-27"), +(3129,"3-12-28"), +(3130,"3-12-29"), +(3131,"3-12-30"), +(3132,"3-12-31"), +(3133,"3-12-32"), +(3134,"3-12-33"), +(3135,"3-12-34"), +(3136,"3-12-35"), +(3137,"3-12-36"), +(3138,"3-12-37"), +(3139,"3-12-38"), +(3140,"3-12-39"), +(3141,"3-12-40"), +(3142,"3-12-41"), +(3143,"3-12-42"), +(3144,"3-12-43"), +(3145,"3-12-44"), +(3146,"3-12-45"), +(3147,"3-12-46"), +(3148,"3-12-47"), +(3149,"3-12-48"), +(3150,"3-12-49"), +(3151,"0-12-0"), +(3152,"1-13-0"), +(3153,"0-12-1"), +(3154,"1-13-1"), +(3155,"0-12-2"), +(3156,"1-13-2"), +(3157,"0-12-3"), +(3158,"1-13-3"), +(3159,"0-12-4"), +(3160,"1-13-4"), +(3161,"1-13-5"), +(3162,"0-12-5"), +(3163,"1-13-6"), +(3164,"0-12-6"), +(3165,"1-13-7"), +(3166,"0-12-7"), +(3167,"1-13-8"), +(3168,"0-12-8"), +(3169,"1-13-9"), +(3170,"0-12-9"), +(3171,"1-13-10"), +(3172,"0-12-10"), +(3173,"1-13-11"), +(3174,"0-12-11"), +(3175,"1-13-12"), +(3176,"0-12-12"), +(3177,"1-13-13"), +(3178,"0-12-13"), +(3179,"1-13-14"), +(3180,"0-12-14"), +(3181,"1-13-15"), +(3182,"0-12-15"), +(3183,"1-13-16"), +(3184,"0-12-16"), +(3185,"1-13-17"), +(3186,"1-13-18"), +(3187,"0-12-17"), +(3188,"1-13-19"), +(3189,"0-12-18"), +(3190,"1-13-20"), +(3191,"0-12-19"), +(3192,"1-13-21"), +(3193,"0-12-20"), +(3194,"1-13-22"), +(3195,"0-12-21"), +(3196,"1-13-23"), +(3197,"0-12-22"), +(3198,"1-13-24"), +(3199,"0-12-23"), +(3200,"1-13-25"), +(3201,"0-12-24"), +(3202,"1-13-26"), +(3203,"0-12-25"), +(3204,"1-13-27"), +(3205,"0-12-26"), +(3206,"1-13-28"), +(3207,"0-12-27"), +(3208,"1-13-29"), +(3209,"0-12-28"), +(3210,"1-13-30"), +(3211,"0-12-29"), +(3212,"1-13-31"), +(3213,"1-13-32"), +(3214,"0-12-30"), +(3215,"1-13-33"), +(3216,"0-12-31"), +(3217,"1-13-34"), +(3218,"0-12-32"), +(3219,"1-13-35"), +(3220,"0-12-33"), +(3221,"1-13-36"), +(3222,"0-12-34"), +(3223,"1-13-37"), +(3224,"0-12-35"), +(3225,"1-13-38"), +(3226,"1-13-39"), +(3227,"0-12-36"), +(3228,"1-13-40"), +(3229,"0-12-37"), +(3230,"1-13-41"), +(3231,"0-12-38"), +(3232,"1-13-42"), +(3233,"0-12-39"), +(3234,"1-13-43"), +(3235,"0-12-40"), +(3236,"1-13-44"), +(3237,"0-12-41"), +(3238,"1-13-45"), +(3239,"0-12-42"), +(3240,"1-13-46"), +(3241,"0-12-43"), +(3242,"1-13-47"), +(3243,"0-12-44"), +(3244,"1-13-48"), +(3245,"1-13-49"), +(3246,"0-12-45"), +(3247,"0-12-46"), +(3248,"0-12-47"), +(3249,"0-12-48"), +(3250,"0-12-49"), +(3251,"4-12-0"), +(3252,"4-12-1"), +(3253,"4-12-2"), +(3254,"4-12-3"), +(3255,"4-12-4"), +(3256,"4-12-5"), +(3257,"4-12-6"), +(3258,"4-12-7"), +(3259,"4-12-8"), +(3260,"4-12-9"), +(3261,"4-12-10"), +(3262,"4-12-11"), +(3263,"4-12-12"), +(3264,"4-12-13"), +(3265,"4-12-14"), +(3266,"4-12-15"), +(3267,"4-12-16"), +(3268,"4-12-17"), +(3269,"4-12-18"), +(3270,"4-12-19"), +(3271,"4-12-20"), +(3272,"4-12-21"), +(3273,"4-12-22"), +(3274,"4-12-23"), +(3275,"4-12-24"), +(3276,"4-12-25"), +(3277,"4-12-26"), +(3278,"4-12-27"), +(3279,"4-12-28"), +(3280,"4-12-29"), +(3281,"4-12-30"), +(3282,"4-12-31"), +(3283,"4-12-32"), +(3284,"4-12-33"), +(3285,"4-12-34"), +(3286,"4-12-35"), +(3287,"4-12-36"), +(3288,"4-12-37"), +(3289,"4-12-38"), +(3290,"4-12-39"), +(3291,"4-12-40"), +(3292,"4-12-41"), +(3293,"4-12-42"), +(3294,"4-12-43"), +(3295,"4-12-44"), +(3296,"4-12-45"), +(3297,"4-12-46"), +(3298,"4-12-47"), +(3299,"4-12-48"), +(3300,"4-12-49"), +(3301,"3-13-0"), +(3302,"3-13-1"), +(3303,"3-13-2"), +(3304,"3-13-3"), +(3305,"3-13-4"), +(3306,"3-13-5"), +(3307,"3-13-6"), +(3308,"3-13-7"), +(3309,"3-13-8"), +(3310,"3-13-9"), +(3311,"3-13-10"), +(3312,"3-13-11"), +(3313,"3-13-12"), +(3314,"3-13-13"), +(3315,"3-13-14"), +(3316,"3-13-15"), +(3317,"3-13-16"), +(3318,"3-13-17"), +(3319,"3-13-18"), +(3320,"3-13-19"), +(3321,"3-13-20"), +(3322,"3-13-21"), +(3323,"3-13-22"), +(3324,"3-13-23"), +(3325,"3-13-24"), +(3326,"3-13-25"), +(3327,"3-13-26"), +(3328,"3-13-27"), +(3329,"3-13-28"), +(3330,"3-13-29"), +(3331,"3-13-30"), +(3332,"3-13-31"), +(3333,"3-13-32"), +(3334,"3-13-33"), +(3335,"3-13-34"), +(3336,"3-13-35"), +(3337,"3-13-36"), +(3338,"3-13-37"), +(3339,"3-13-38"), +(3340,"3-13-39"), +(3341,"3-13-40"), +(3342,"3-13-41"), +(3343,"3-13-42"), +(3344,"3-13-43"), +(3345,"3-13-44"), +(3346,"3-13-45"), +(3347,"3-13-46"), +(3348,"3-13-47"), +(3349,"3-13-48"), +(3350,"3-13-49"), +(3351,"2-13-0"), +(3352,"2-13-1"), +(3353,"2-13-2"), +(3354,"2-13-3"), +(3355,"2-13-4"), +(3356,"2-13-5"), +(3357,"2-13-6"), +(3358,"2-13-7"), +(3359,"2-13-8"), +(3360,"2-13-9"), +(3361,"2-13-10"), +(3362,"2-13-11"), +(3363,"2-13-12"), +(3364,"2-13-13"), +(3365,"2-13-14"), +(3366,"2-13-15"), +(3367,"2-13-16"), +(3368,"2-13-17"), +(3369,"2-13-18"), +(3370,"2-13-19"), +(3371,"2-13-20"), +(3372,"2-13-21"), +(3373,"2-13-22"), +(3374,"2-13-23"), +(3375,"2-13-24"), +(3376,"2-13-25"), +(3377,"2-13-26"), +(3378,"2-13-27"), +(3379,"2-13-28"), +(3380,"2-13-29"), +(3381,"2-13-30"), +(3382,"2-13-31"), +(3383,"2-13-32"), +(3384,"2-13-33"), +(3385,"2-13-34"), +(3386,"2-13-35"), +(3387,"2-13-36"), +(3388,"2-13-37"), +(3389,"2-13-38"), +(3390,"2-13-39"), +(3391,"2-13-40"), +(3392,"2-13-41"), +(3393,"2-13-42"), +(3394,"2-13-43"), +(3395,"2-13-44"), +(3396,"2-13-45"), +(3397,"2-13-46"), +(3398,"2-13-47"), +(3399,"2-13-48"), +(3400,"2-13-49"), +(3401,"0-13-0"), +(3402,"0-13-1"), +(3403,"0-13-2"), +(3404,"0-13-3"), +(3405,"0-13-4"), +(3406,"0-13-5"), +(3407,"0-13-6"), +(3408,"0-13-7"), +(3409,"0-13-8"), +(3410,"0-13-9"), +(3411,"0-13-10"), +(3412,"0-13-11"), +(3413,"0-13-12"), +(3414,"0-13-13"), +(3415,"0-13-14"), +(3416,"0-13-15"), +(3417,"0-13-16"), +(3418,"0-13-17"), +(3419,"0-13-18"), +(3420,"0-13-19"), +(3421,"0-13-20"), +(3422,"0-13-21"), +(3423,"0-13-22"), +(3424,"0-13-23"), +(3425,"0-13-24"), +(3426,"0-13-25"), +(3427,"0-13-26"), +(3428,"0-13-27"), +(3429,"0-13-28"), +(3430,"0-13-29"), +(3431,"0-13-30"), +(3432,"0-13-31"), +(3433,"0-13-32"), +(3434,"0-13-33"), +(3435,"0-13-34"), +(3436,"0-13-35"), +(3437,"0-13-36"), +(3438,"0-13-37"), +(3439,"0-13-38"), +(3440,"0-13-39"), +(3441,"0-13-40"), +(3442,"0-13-41"), +(3443,"0-13-42"), +(3444,"0-13-43"), +(3445,"0-13-44"), +(3446,"0-13-45"), +(3447,"0-13-46"), +(3448,"0-13-47"), +(3449,"0-13-48"), +(3450,"0-13-49"), +(3451,"1-14-0"), +(3452,"1-14-1"), +(3453,"1-14-2"), +(3454,"1-14-3"), +(3455,"1-14-4"), +(3456,"1-14-5"), +(3457,"1-14-6"), +(3458,"1-14-7"), +(3459,"1-14-8"), +(3460,"1-14-9"), +(3461,"1-14-10"), +(3462,"1-14-11"), +(3463,"1-14-12"), +(3464,"1-14-13"), +(3465,"1-14-14"), +(3466,"1-14-15"), +(3467,"1-14-16"), +(3468,"1-14-17"), +(3469,"1-14-18"), +(3470,"1-14-19"), +(3471,"1-14-20"), +(3472,"1-14-21"), +(3473,"1-14-22"), +(3474,"1-14-23"), +(3475,"1-14-24"), +(3476,"1-14-25"), +(3477,"1-14-26"), +(3478,"1-14-27"), +(3479,"1-14-28"), +(3480,"1-14-29"), +(3481,"1-14-30"), +(3482,"1-14-31"), +(3483,"1-14-32"), +(3484,"1-14-33"), +(3485,"1-14-34"), +(3486,"1-14-35"), +(3487,"1-14-36"), +(3488,"1-14-37"), +(3489,"1-14-38"), +(3490,"1-14-39"), +(3491,"1-14-40"), +(3492,"1-14-41"), +(3493,"1-14-42"), +(3494,"1-14-43"), +(3495,"1-14-44"), +(3496,"1-14-45"), +(3497,"1-14-46"), +(3498,"1-14-47"), +(3499,"1-14-48"), +(3500,"1-14-49"), +(3501,"4-13-0"), +(3502,"4-13-1"), +(3503,"4-13-2"), +(3504,"4-13-3"), +(3505,"4-13-4"), +(3506,"4-13-5"), +(3507,"4-13-6"), +(3508,"4-13-7"), +(3509,"4-13-8"), +(3510,"4-13-9"), +(3511,"4-13-10"), +(3512,"4-13-11"), +(3513,"4-13-12"), +(3514,"4-13-13"), +(3515,"4-13-14"), +(3516,"4-13-15"), +(3517,"4-13-16"), +(3518,"4-13-17"), +(3519,"4-13-18"), +(3520,"4-13-19"), +(3521,"4-13-20"), +(3522,"4-13-21"), +(3523,"4-13-22"), +(3524,"4-13-23"), +(3525,"4-13-24"), +(3526,"4-13-25"), +(3527,"4-13-26"), +(3528,"4-13-27"), +(3529,"4-13-28"), +(3530,"4-13-29"), +(3531,"4-13-30"), +(3532,"4-13-31"), +(3533,"4-13-32"), +(3534,"4-13-33"), +(3535,"4-13-34"), +(3536,"4-13-35"), +(3537,"4-13-36"), +(3538,"4-13-37"), +(3539,"4-13-38"), +(3540,"4-13-39"), +(3541,"4-13-40"), +(3542,"4-13-41"), +(3543,"4-13-42"), +(3544,"4-13-43"), +(3545,"4-13-44"), +(3546,"4-13-45"), +(3547,"4-13-46"), +(3548,"4-13-47"), +(3549,"4-13-48"), +(3550,"4-13-49"), +(3551,"0-14-0"), +(3552,"0-14-1"), +(3553,"0-14-2"), +(3554,"0-14-3"), +(3555,"0-14-4"), +(3556,"0-14-5"), +(3557,"0-14-6"), +(3558,"0-14-7"), +(3559,"0-14-8"), +(3560,"0-14-9"), +(3561,"0-14-10"), +(3562,"0-14-11"), +(3563,"0-14-12"), +(3564,"0-14-13"), +(3565,"0-14-14"), +(3566,"0-14-15"), +(3567,"0-14-16"), +(3568,"0-14-17"), +(3569,"0-14-18"), +(3570,"0-14-19"), +(3571,"0-14-20"), +(3572,"0-14-21"), +(3573,"0-14-22"), +(3574,"0-14-23"), +(3575,"0-14-24"), +(3576,"0-14-25"), +(3577,"0-14-26"), +(3578,"0-14-27"), +(3579,"0-14-28"), +(3580,"0-14-29"), +(3581,"0-14-30"), +(3582,"0-14-31"), +(3583,"0-14-32"), +(3584,"0-14-33"), +(3585,"0-14-34"), +(3586,"0-14-35"), +(3587,"0-14-36"), +(3588,"0-14-37"), +(3589,"0-14-38"), +(3590,"0-14-39"), +(3591,"0-14-40"), +(3592,"0-14-41"), +(3593,"0-14-42"), +(3594,"0-14-43"), +(3595,"0-14-44"), +(3596,"0-14-45"), +(3597,"0-14-46"), +(3598,"0-14-47"), +(3599,"0-14-48"), +(3600,"0-14-49"), +(3601,"3-14-0"), +(3602,"3-14-1"), +(3603,"3-14-2"), +(3604,"3-14-3"), +(3605,"3-14-4"), +(3606,"3-14-5"), +(3607,"3-14-6"), +(3608,"3-14-7"), +(3609,"3-14-8"), +(3610,"3-14-9"), +(3611,"3-14-10"), +(3612,"3-14-11"), +(3613,"3-14-12"), +(3614,"3-14-13"), +(3615,"3-14-14"), +(3616,"3-14-15"), +(3617,"3-14-16"), +(3618,"3-14-17"), +(3619,"3-14-18"), +(3620,"3-14-19"), +(3621,"3-14-20"), +(3622,"3-14-21"), +(3623,"3-14-22"), +(3624,"3-14-23"), +(3625,"3-14-24"), +(3626,"3-14-25"), +(3627,"3-14-26"), +(3628,"3-14-27"), +(3629,"3-14-28"), +(3630,"3-14-29"), +(3631,"3-14-30"), +(3632,"3-14-31"), +(3633,"3-14-32"), +(3634,"3-14-33"), +(3635,"3-14-34"), +(3636,"3-14-35"), +(3637,"3-14-36"), +(3638,"3-14-37"), +(3639,"3-14-38"), +(3640,"3-14-39"), +(3641,"3-14-40"), +(3642,"3-14-41"), +(3643,"3-14-42"), +(3644,"3-14-43"), +(3645,"3-14-44"), +(3646,"3-14-45"), +(3647,"3-14-46"), +(3648,"3-14-47"), +(3649,"3-14-48"), +(3650,"3-14-49"), +(3651,"2-14-0"), +(3652,"2-14-1"), +(3653,"2-14-2"), +(3654,"2-14-3"), +(3655,"2-14-4"), +(3656,"2-14-5"), +(3657,"2-14-6"), +(3658,"2-14-7"), +(3659,"2-14-8"), +(3660,"2-14-9"), +(3661,"2-14-10"), +(3662,"2-14-11"), +(3663,"2-14-12"), +(3664,"2-14-13"), +(3665,"2-14-14"), +(3666,"2-14-15"), +(3667,"2-14-16"), +(3668,"2-14-17"), +(3669,"2-14-18"), +(3670,"2-14-19"), +(3671,"2-14-20"), +(3672,"2-14-21"), +(3673,"2-14-22"), +(3674,"2-14-23"), +(3675,"2-14-24"), +(3676,"2-14-25"), +(3677,"2-14-26"), +(3678,"2-14-27"), +(3679,"2-14-28"), +(3680,"2-14-29"), +(3681,"2-14-30"), +(3682,"2-14-31"), +(3683,"2-14-32"), +(3684,"2-14-33"), +(3685,"2-14-34"), +(3686,"2-14-35"), +(3687,"2-14-36"), +(3688,"2-14-37"), +(3689,"2-14-38"), +(3690,"2-14-39"), +(3691,"2-14-40"), +(3692,"2-14-41"), +(3693,"2-14-42"), +(3694,"2-14-43"), +(3695,"2-14-44"), +(3696,"2-14-45"), +(3697,"2-14-46"), +(3698,"2-14-47"), +(3699,"2-14-48"), +(3700,"2-14-49"), +(3701,"4-14-0"), +(3702,"4-14-1"), +(3703,"4-14-2"), +(3704,"4-14-3"), +(3705,"4-14-4"), +(3706,"4-14-5"), +(3707,"4-14-6"), +(3708,"4-14-7"), +(3709,"4-14-8"), +(3710,"4-14-9"), +(3711,"4-14-10"), +(3712,"4-14-11"), +(3713,"4-14-12"), +(3714,"4-14-13"), +(3715,"4-14-14"), +(3716,"4-14-15"), +(3717,"4-14-16"), +(3718,"4-14-17"), +(3719,"4-14-18"), +(3720,"4-14-19"), +(3721,"4-14-20"), +(3722,"4-14-21"), +(3723,"4-14-22"), +(3724,"4-14-23"), +(3725,"4-14-24"), +(3726,"4-14-25"), +(3727,"4-14-26"), +(3728,"4-14-27"), +(3729,"4-14-28"), +(3730,"4-14-29"), +(3731,"4-14-30"), +(3732,"4-14-31"), +(3733,"4-14-32"), +(3734,"4-14-33"), +(3735,"4-14-34"), +(3736,"4-14-35"), +(3737,"4-14-36"), +(3738,"4-14-37"), +(3739,"4-14-38"), +(3740,"4-14-39"), +(3741,"4-14-40"), +(3742,"4-14-41"), +(3743,"4-14-42"), +(3744,"4-14-43"), +(3745,"4-14-44"), +(3746,"4-14-45"), +(3747,"4-14-46"), +(3748,"4-14-47"), +(3749,"4-14-48"), +(3750,"4-14-49"), +(3751,"1-15-0"), +(3752,"1-15-1"), +(3753,"1-15-2"), +(3754,"1-15-3"), +(3755,"1-15-4"), +(3756,"1-15-5"), +(3757,"1-15-6"), +(3758,"1-15-7"), +(3759,"1-15-8"), +(3760,"1-15-9"), +(3761,"1-15-10"), +(3762,"1-15-11"), +(3763,"1-15-12"), +(3764,"1-15-13"), +(3765,"1-15-14"), +(3766,"1-15-15"), +(3767,"1-15-16"), +(3768,"1-15-17"), +(3769,"1-15-18"), +(3770,"1-15-19"), +(3771,"1-15-20"), +(3772,"1-15-21"), +(3773,"1-15-22"), +(3774,"1-15-23"), +(3775,"1-15-24"), +(3776,"1-15-25"), +(3777,"1-15-26"), +(3778,"1-15-27"), +(3779,"1-15-28"), +(3780,"1-15-29"), +(3781,"1-15-30"), +(3782,"1-15-31"), +(3783,"1-15-32"), +(3784,"1-15-33"), +(3785,"1-15-34"), +(3786,"1-15-35"), +(3787,"1-15-36"), +(3788,"1-15-37"), +(3789,"1-15-38"), +(3790,"1-15-39"), +(3791,"1-15-40"), +(3792,"1-15-41"), +(3793,"1-15-42"), +(3794,"1-15-43"), +(3795,"1-15-44"), +(3796,"1-15-45"), +(3797,"1-15-46"), +(3798,"1-15-47"), +(3799,"1-15-48"), +(3800,"1-15-49"), +(3801,"0-15-0"), +(3802,"0-15-1"), +(3803,"0-15-2"), +(3804,"0-15-3"), +(3805,"0-15-4"), +(3806,"0-15-5"), +(3807,"0-15-6"), +(3808,"0-15-7"), +(3809,"0-15-8"), +(3810,"0-15-9"), +(3811,"0-15-10"), +(3812,"0-15-11"), +(3813,"0-15-12"), +(3814,"0-15-13"), +(3815,"0-15-14"), +(3816,"0-15-15"), +(3817,"0-15-16"), +(3818,"0-15-17"), +(3819,"0-15-18"), +(3820,"0-15-19"), +(3821,"0-15-20"), +(3822,"0-15-21"), +(3823,"0-15-22"), +(3824,"0-15-23"), +(3825,"0-15-24"), +(3826,"0-15-25"), +(3827,"0-15-26"), +(3828,"0-15-27"), +(3829,"0-15-28"), +(3830,"0-15-29"), +(3831,"0-15-30"), +(3832,"0-15-31"), +(3833,"0-15-32"), +(3834,"0-15-33"), +(3835,"0-15-34"), +(3836,"0-15-35"), +(3837,"0-15-36"), +(3838,"0-15-37"), +(3839,"0-15-38"), +(3840,"0-15-39"), +(3841,"0-15-40"), +(3842,"0-15-41"), +(3843,"0-15-42"), +(3844,"0-15-43"), +(3845,"0-15-44"), +(3846,"0-15-45"), +(3847,"0-15-46"), +(3848,"0-15-47"), +(3849,"0-15-48"), +(3850,"0-15-49"), +(3851,"2-15-0"), +(3852,"2-15-1"), +(3853,"2-15-2"), +(3854,"2-15-3"), +(3855,"2-15-4"), +(3856,"2-15-5"), +(3857,"2-15-6"), +(3858,"2-15-7"), +(3859,"2-15-8"), +(3860,"2-15-9"), +(3861,"2-15-10"), +(3862,"2-15-11"), +(3863,"2-15-12"), +(3864,"2-15-13"), +(3865,"2-15-14"), +(3866,"2-15-15"), +(3867,"2-15-16"), +(3868,"2-15-17"), +(3869,"2-15-18"), +(3870,"2-15-19"), +(3871,"2-15-20"), +(3872,"2-15-21"), +(3873,"2-15-22"), +(3874,"2-15-23"), +(3875,"2-15-24"), +(3876,"2-15-25"), +(3877,"2-15-26"), +(3878,"2-15-27"), +(3879,"2-15-28"), +(3880,"2-15-29"), +(3881,"2-15-30"), +(3882,"2-15-31"), +(3883,"2-15-32"), +(3884,"2-15-33"), +(3885,"2-15-34"), +(3886,"2-15-35"), +(3887,"2-15-36"), +(3888,"2-15-37"), +(3889,"2-15-38"), +(3890,"2-15-39"), +(3891,"2-15-40"), +(3892,"2-15-41"), +(3893,"2-15-42"), +(3894,"2-15-43"), +(3895,"2-15-44"), +(3896,"2-15-45"), +(3897,"2-15-46"), +(3898,"2-15-47"), +(3899,"2-15-48"), +(3900,"2-15-49"), +(3901,"3-15-0"), +(3902,"3-15-1"), +(3903,"3-15-2"), +(3904,"3-15-3"), +(3905,"3-15-4"), +(3906,"3-15-5"), +(3907,"3-15-6"), +(3908,"3-15-7"), +(3909,"3-15-8"), +(3910,"3-15-9"), +(3911,"3-15-10"), +(3912,"3-15-11"), +(3913,"3-15-12"), +(3914,"3-15-13"), +(3915,"3-15-14"), +(3916,"3-15-15"), +(3917,"3-15-16"), +(3918,"3-15-17"), +(3919,"3-15-18"), +(3920,"3-15-19"), +(3921,"3-15-20"), +(3922,"3-15-21"), +(3923,"3-15-22"), +(3924,"3-15-23"), +(3925,"3-15-24"), +(3926,"3-15-25"), +(3927,"3-15-26"), +(3928,"3-15-27"), +(3929,"3-15-28"), +(3930,"3-15-29"), +(3931,"3-15-30"), +(3932,"3-15-31"), +(3933,"3-15-32"), +(3934,"3-15-33"), +(3935,"3-15-34"), +(3936,"3-15-35"), +(3937,"3-15-36"), +(3938,"3-15-37"), +(3939,"3-15-38"), +(3940,"3-15-39"), +(3941,"3-15-40"), +(3942,"3-15-41"), +(3943,"3-15-42"), +(3944,"3-15-43"), +(3945,"3-15-44"), +(3946,"3-15-45"), +(3947,"3-15-46"), +(3948,"3-15-47"), +(3949,"3-15-48"), +(3950,"3-15-49"), +(3951,"4-15-0"), +(3952,"4-15-1"), +(3953,"4-15-2"), +(3954,"4-15-3"), +(3955,"4-15-4"), +(3956,"4-15-5"), +(3957,"4-15-6"), +(3958,"4-15-7"), +(3959,"4-15-8"), +(3960,"4-15-9"), +(3961,"4-15-10"), +(3962,"4-15-11"), +(3963,"4-15-12"), +(3964,"4-15-13"), +(3965,"4-15-14"), +(3966,"4-15-15"), +(3967,"4-15-16"), +(3968,"4-15-17"), +(3969,"4-15-18"), +(3970,"4-15-19"), +(3971,"4-15-20"), +(3972,"4-15-21"), +(3973,"4-15-22"), +(3974,"4-15-23"), +(3975,"4-15-24"), +(3976,"4-15-25"), +(3977,"4-15-26"), +(3978,"4-15-27"), +(3979,"4-15-28"), +(3980,"4-15-29"), +(3981,"4-15-30"), +(3982,"4-15-31"), +(3983,"4-15-32"), +(3984,"4-15-33"), +(3985,"4-15-34"), +(3986,"4-15-35"), +(3987,"4-15-36"), +(3988,"4-15-37"), +(3989,"4-15-38"), +(3990,"4-15-39"), +(3991,"4-15-40"), +(3992,"4-15-41"), +(3993,"4-15-42"), +(3994,"4-15-43"), +(3995,"4-15-44"), +(3996,"4-15-45"), +(3997,"4-15-46"), +(3998,"4-15-47"), +(3999,"4-15-48"), +(4000,"4-15-49"), +(4001,"1-16-0"), +(4002,"1-16-1"), +(4003,"1-16-2"), +(4004,"1-16-3"), +(4005,"1-16-4"), +(4006,"1-16-5"), +(4007,"1-16-6"), +(4008,"1-16-7"), +(4009,"1-16-8"), +(4010,"1-16-9"), +(4011,"1-16-10"), +(4012,"1-16-11"), +(4013,"1-16-12"), +(4014,"1-16-13"), +(4015,"1-16-14"), +(4016,"1-16-15"), +(4017,"1-16-16"), +(4018,"1-16-17"), +(4019,"1-16-18"), +(4020,"1-16-19"), +(4021,"1-16-20"), +(4022,"1-16-21"), +(4023,"1-16-22"), +(4024,"1-16-23"), +(4025,"1-16-24"), +(4026,"1-16-25"), +(4027,"1-16-26"), +(4028,"1-16-27"), +(4029,"1-16-28"), +(4030,"1-16-29"), +(4031,"1-16-30"), +(4032,"1-16-31"), +(4033,"1-16-32"), +(4034,"1-16-33"), +(4035,"1-16-34"), +(4036,"1-16-35"), +(4037,"1-16-36"), +(4038,"1-16-37"), +(4039,"1-16-38"), +(4040,"1-16-39"), +(4041,"1-16-40"), +(4042,"1-16-41"), +(4043,"1-16-42"), +(4044,"1-16-43"), +(4045,"1-16-44"), +(4046,"1-16-45"), +(4047,"1-16-46"), +(4048,"1-16-47"), +(4049,"1-16-48"), +(4050,"1-16-49"), +(4051,"0-16-0"), +(4052,"0-16-1"), +(4053,"0-16-2"), +(4054,"0-16-3"), +(4055,"0-16-4"), +(4056,"0-16-5"), +(4057,"0-16-6"), +(4058,"0-16-7"), +(4059,"0-16-8"), +(4060,"0-16-9"), +(4061,"0-16-10"), +(4062,"0-16-11"), +(4063,"0-16-12"), +(4064,"0-16-13"), +(4065,"0-16-14"), +(4066,"0-16-15"), +(4067,"0-16-16"), +(4068,"0-16-17"), +(4069,"0-16-18"), +(4070,"0-16-19"), +(4071,"0-16-20"), +(4072,"0-16-21"), +(4073,"0-16-22"), +(4074,"0-16-23"), +(4075,"0-16-24"), +(4076,"0-16-25"), +(4077,"0-16-26"), +(4078,"0-16-27"), +(4079,"0-16-28"), +(4080,"0-16-29"), +(4081,"0-16-30"), +(4082,"0-16-31"), +(4083,"0-16-32"), +(4084,"0-16-33"), +(4085,"0-16-34"), +(4086,"0-16-35"), +(4087,"0-16-36"), +(4088,"0-16-37"), +(4089,"0-16-38"), +(4090,"0-16-39"), +(4091,"0-16-40"), +(4092,"0-16-41"), +(4093,"0-16-42"), +(4094,"0-16-43"), +(4095,"0-16-44"), +(4096,"0-16-45"), +(4097,"0-16-46"), +(4098,"0-16-47"), +(4099,"0-16-48"), +(4100,"0-16-49"), +(4101,"4-16-0"), +(4102,"4-16-1"), +(4103,"4-16-2"), +(4104,"4-16-3"), +(4105,"4-16-4"), +(4106,"4-16-5"), +(4107,"4-16-6"), +(4108,"4-16-7"), +(4109,"4-16-8"), +(4110,"4-16-9"), +(4111,"4-16-10"), +(4112,"4-16-11"), +(4113,"4-16-12"), +(4114,"4-16-13"), +(4115,"4-16-14"), +(4116,"4-16-15"), +(4117,"4-16-16"), +(4118,"4-16-17"), +(4119,"4-16-18"), +(4120,"4-16-19"), +(4121,"4-16-20"), +(4122,"4-16-21"), +(4123,"4-16-22"), +(4124,"4-16-23"), +(4125,"4-16-24"), +(4126,"4-16-25"), +(4127,"4-16-26"), +(4128,"4-16-27"), +(4129,"4-16-28"), +(4130,"4-16-29"), +(4131,"4-16-30"), +(4132,"4-16-31"), +(4133,"4-16-32"), +(4134,"4-16-33"), +(4135,"4-16-34"), +(4136,"4-16-35"), +(4137,"4-16-36"), +(4138,"4-16-37"), +(4139,"4-16-38"), +(4140,"4-16-39"), +(4141,"4-16-40"), +(4142,"4-16-41"), +(4143,"4-16-42"), +(4144,"4-16-43"), +(4145,"4-16-44"), +(4146,"4-16-45"), +(4147,"4-16-46"), +(4148,"4-16-47"), +(4149,"4-16-48"), +(4150,"4-16-49"), +(4151,"3-16-0"), +(4152,"3-16-1"), +(4153,"3-16-2"), +(4154,"3-16-3"), +(4155,"3-16-4"), +(4156,"3-16-5"), +(4157,"3-16-6"), +(4158,"3-16-7"), +(4159,"3-16-8"), +(4160,"3-16-9"), +(4161,"3-16-10"), +(4162,"3-16-11"), +(4163,"3-16-12"), +(4164,"3-16-13"), +(4165,"3-16-14"), +(4166,"3-16-15"), +(4167,"3-16-16"), +(4168,"3-16-17"), +(4169,"3-16-18"), +(4170,"3-16-19"), +(4171,"3-16-20"), +(4172,"3-16-21"), +(4173,"3-16-22"), +(4174,"3-16-23"), +(4175,"3-16-24"), +(4176,"3-16-25"), +(4177,"3-16-26"), +(4178,"3-16-27"), +(4179,"3-16-28"), +(4180,"3-16-29"), +(4181,"3-16-30"), +(4182,"3-16-31"), +(4183,"3-16-32"), +(4184,"3-16-33"), +(4185,"3-16-34"), +(4186,"3-16-35"), +(4187,"3-16-36"), +(4188,"3-16-37"), +(4189,"3-16-38"), +(4190,"3-16-39"), +(4191,"3-16-40"), +(4192,"3-16-41"), +(4193,"3-16-42"), +(4194,"3-16-43"), +(4195,"3-16-44"), +(4196,"3-16-45"), +(4197,"3-16-46"), +(4198,"3-16-47"), +(4199,"3-16-48"), +(4200,"3-16-49"), +(4201,"3-17-0"), +(4202,"3-17-1"), +(4203,"3-17-2"), +(4204,"1-17-0"), +(4205,"3-17-3"), +(4206,"3-17-4"), +(4207,"1-17-1"), +(4208,"3-17-5"), +(4209,"3-17-6"), +(4210,"3-17-7"), +(4211,"3-17-8"), +(4212,"3-17-9"), +(4213,"3-17-10"), +(4214,"3-17-11"), +(4215,"1-17-2"), +(4216,"3-17-12"), +(4217,"1-17-3"), +(4218,"3-17-13"), +(4219,"1-17-4"), +(4220,"3-17-14"), +(4221,"0-17-0"), +(4222,"1-17-5"), +(4223,"3-17-15"), +(4224,"1-17-6"), +(4225,"0-17-1"), +(4226,"3-17-16"), +(4227,"1-17-7"), +(4228,"0-17-2"), +(4229,"3-17-17"), +(4230,"4-17-0"), +(4231,"0-17-3"), +(4232,"3-17-18"), +(4233,"0-17-4"), +(4234,"4-17-1"), +(4235,"3-17-19"), +(4236,"0-17-5"), +(4237,"4-17-2"), +(4238,"2-16-0"), +(4239,"0-17-6"), +(4240,"3-17-20"), +(4241,"4-17-3"), +(4242,"0-17-7"), +(4243,"2-16-1"), +(4244,"0-17-8"), +(4245,"2-16-2"), +(4246,"2-16-3"), +(4247,"2-16-4"), +(4248,"2-16-5"), +(4249,"2-16-6"), +(4250,"2-16-7"), +(4251,"2-16-8"), +(4252,"0-17-9"), +(4253,"3-17-21"), +(4254,"3-17-22"), +(4255,"3-17-23"), +(4256,"0-17-10"), +(4257,"3-17-24"), +(4258,"0-17-11"), +(4259,"3-17-25"), +(4260,"0-17-12"), +(4261,"3-17-26"), +(4262,"0-17-13"), +(4263,"3-17-27"), +(4264,"0-17-14"), +(4265,"3-17-28"), +(4266,"0-17-15"), +(4267,"4-17-4"), +(4268,"0-17-16"), +(4269,"4-17-5"), +(4270,"0-17-17"), +(4271,"4-17-6"), +(4272,"0-17-18"), +(4273,"4-17-7"), +(4274,"0-17-19"), +(4275,"4-17-8"), +(4276,"0-17-20"), +(4277,"4-17-9"), +(4278,"4-17-10"), +(4279,"0-17-21"), +(4280,"3-17-29"), +(4281,"3-17-30"), +(4282,"3-17-31"), +(4283,"3-17-32"), +(4284,"3-17-33"), +(4285,"3-17-34"), +(4286,"3-17-35"), +(4287,"3-17-36"), +(4288,"3-17-37"), +(4289,"3-17-38"), +(4290,"3-17-39"), +(4291,"3-17-40"), +(4292,"1-17-8"), +(4293,"1-17-9"), +(4294,"1-17-10"), +(4295,"1-17-11"), +(4296,"1-17-12"), +(4297,"1-17-13"), +(4298,"1-17-14"), +(4299,"0-17-22"), +(4300,"0-17-23"), +(4301,"0-17-24"), +(4302,"0-17-25"), +(4303,"0-17-26"), +(4304,"4-17-11"), +(4305,"4-17-12"), +(4306,"4-17-13"), +(4307,"4-17-14"), +(4308,"4-17-15"), +(4309,"4-17-16"), +(4310,"4-17-17"), +(4311,"4-17-18"), +(4312,"4-17-19"), +(4313,"2-16-9"), +(4314,"4-17-20"), +(4315,"2-16-10"), +(4316,"4-17-21"), +(4317,"3-17-41"), +(4318,"4-17-22"), +(4319,"2-16-11"), +(4320,"4-17-23"), +(4321,"3-17-42"), +(4322,"2-16-12"), +(4323,"4-17-24"), +(4324,"2-16-13"), +(4325,"2-16-14"), +(4326,"4-17-25"), +(4327,"2-16-15"), +(4328,"3-17-43"), +(4329,"1-17-15"), +(4330,"3-17-44"), +(4331,"3-17-45"), +(4332,"3-17-46"), +(4333,"3-17-47"), +(4334,"2-16-16"), +(4335,"3-17-48"), +(4336,"3-17-49"), +(4337,"1-17-16"), +(4338,"1-17-17"), +(4339,"1-17-18"), +(4340,"1-17-19"), +(4341,"1-17-20"), +(4342,"1-17-21"), +(4343,"1-17-22"), +(4344,"1-17-23"), +(4345,"1-17-24"), +(4346,"1-17-25"), +(4347,"1-17-26"), +(4348,"1-17-27"), +(4349,"1-17-28"), +(4350,"1-17-29"), +(4351,"4-17-26"), +(4352,"1-17-30"), +(4353,"1-17-31"), +(4354,"4-17-27"), +(4355,"1-17-32"), +(4356,"4-17-28"), +(4357,"1-17-33"), +(4358,"4-17-29"), +(4359,"4-17-30"), +(4360,"1-17-34"), +(4361,"4-17-31"), +(4362,"1-17-35"), +(4363,"4-17-32"), +(4364,"1-17-36"), +(4365,"0-17-27"), +(4366,"0-17-28"), +(4367,"2-16-17"), +(4368,"2-16-18"), +(4369,"2-16-19"), +(4370,"2-16-20"), +(4371,"2-16-21"), +(4372,"2-16-22"), +(4373,"2-16-23"), +(4374,"2-16-24"), +(4375,"2-16-25"), +(4376,"2-16-26"), +(4377,"2-16-27"), +(4378,"2-16-28"), +(4379,"2-16-29"), +(4380,"2-16-30"), +(4381,"2-16-31"), +(4382,"2-16-32"), +(4383,"2-16-33"), +(4384,"2-16-34"), +(4385,"2-16-35"), +(4386,"2-16-36"), +(4387,"2-16-37"), +(4388,"2-16-38"), +(4389,"2-16-39"), +(4390,"2-16-40"), +(4391,"2-16-41"), +(4392,"4-17-33"), +(4393,"4-17-34"), +(4394,"4-17-35"), +(4395,"4-17-36"), +(4396,"4-17-37"), +(4397,"2-16-42"), +(4398,"4-17-38"), +(4399,"0-17-29"), +(4400,"4-17-39"), +(4401,"1-17-37"), +(4402,"2-16-43"), +(4403,"4-17-40"), +(4404,"2-16-44"), +(4405,"1-17-38"), +(4406,"0-17-30"), +(4407,"2-16-45"), +(4408,"1-17-39"), +(4409,"4-17-41"), +(4410,"2-16-46"), +(4411,"0-17-31"), +(4412,"1-17-40"), +(4413,"2-16-47"), +(4414,"4-17-42"), +(4415,"2-16-48"), +(4416,"1-17-41"), +(4417,"0-17-32"), +(4418,"4-17-43"), +(4419,"2-16-49"), +(4420,"1-17-42"), +(4421,"0-17-33"), +(4422,"4-17-44"), +(4423,"1-17-43"), +(4424,"0-17-34"), +(4425,"4-17-45"), +(4426,"1-17-44"), +(4427,"0-17-35"), +(4428,"4-17-46"), +(4429,"1-17-45"), +(4430,"0-17-36"), +(4431,"1-17-46"), +(4432,"4-17-47"), +(4433,"0-17-37"), +(4434,"1-17-47"), +(4435,"1-17-48"), +(4436,"0-17-38"), +(4437,"1-17-49"), +(4438,"0-17-39"), +(4439,"4-17-48"), +(4440,"0-17-40"), +(4441,"4-17-49"), +(4442,"0-17-41"), +(4443,"0-17-42"), +(4444,"0-17-43"), +(4445,"0-17-44"), +(4446,"0-17-45"), +(4447,"0-17-46"), +(4448,"0-17-47"), +(4449,"0-17-48"), +(4450,"0-17-49"), +(4451,"2-17-0"), +(4452,"2-17-1"), +(4453,"2-17-2"), +(4454,"2-17-3"), +(4455,"2-17-4"), +(4456,"2-17-5"), +(4457,"2-17-6"), +(4458,"2-17-7"), +(4459,"2-17-8"), +(4460,"2-17-9"), +(4461,"2-17-10"), +(4462,"2-17-11"), +(4463,"2-17-12"), +(4464,"2-17-13"), +(4465,"2-17-14"), +(4466,"2-17-15"), +(4467,"2-17-16"), +(4468,"2-17-17"), +(4469,"2-17-18"), +(4470,"2-17-19"), +(4471,"2-17-20"), +(4472,"2-17-21"), +(4473,"2-17-22"), +(4474,"2-17-23"), +(4475,"2-17-24"), +(4476,"2-17-25"), +(4477,"2-17-26"), +(4478,"2-17-27"), +(4479,"2-17-28"), +(4480,"2-17-29"), +(4481,"2-17-30"), +(4482,"2-17-31"), +(4483,"2-17-32"), +(4484,"2-17-33"), +(4485,"2-17-34"), +(4486,"2-17-35"), +(4487,"2-17-36"), +(4488,"2-17-37"), +(4489,"2-17-38"), +(4490,"2-17-39"), +(4491,"2-17-40"), +(4492,"2-17-41"), +(4493,"2-17-42"), +(4494,"2-17-43"), +(4495,"2-17-44"), +(4496,"2-17-45"), +(4497,"2-17-46"), +(4498,"2-17-47"), +(4499,"2-17-48"), +(4500,"2-17-49"), +(4501,"0-18-0"), +(4502,"0-18-1"), +(4503,"0-18-2"), +(4504,"0-18-3"), +(4505,"0-18-4"), +(4506,"0-18-5"), +(4507,"0-18-6"), +(4508,"0-18-7"), +(4509,"0-18-8"), +(4510,"0-18-9"), +(4511,"0-18-10"), +(4512,"0-18-11"), +(4513,"0-18-12"), +(4514,"0-18-13"), +(4515,"0-18-14"), +(4516,"0-18-15"), +(4517,"0-18-16"), +(4518,"0-18-17"), +(4519,"0-18-18"), +(4520,"0-18-19"), +(4521,"0-18-20"), +(4522,"0-18-21"), +(4523,"0-18-22"), +(4524,"0-18-23"), +(4525,"0-18-24"), +(4526,"0-18-25"), +(4527,"0-18-26"), +(4528,"0-18-27"), +(4529,"0-18-28"), +(4530,"0-18-29"), +(4531,"0-18-30"), +(4532,"0-18-31"), +(4533,"0-18-32"), +(4534,"0-18-33"), +(4535,"0-18-34"), +(4536,"0-18-35"), +(4537,"0-18-36"), +(4538,"0-18-37"), +(4539,"0-18-38"), +(4540,"0-18-39"), +(4541,"0-18-40"), +(4542,"0-18-41"), +(4543,"0-18-42"), +(4544,"0-18-43"), +(4545,"0-18-44"), +(4546,"0-18-45"), +(4547,"0-18-46"), +(4548,"0-18-47"), +(4549,"0-18-48"), +(4550,"0-18-49"), +(4551,"3-18-0"), +(4552,"3-18-1"), +(4553,"3-18-2"), +(4554,"3-18-3"), +(4555,"3-18-4"), +(4556,"3-18-5"), +(4557,"3-18-6"), +(4558,"3-18-7"), +(4559,"3-18-8"), +(4560,"3-18-9"), +(4561,"3-18-10"), +(4562,"3-18-11"), +(4563,"3-18-12"), +(4564,"3-18-13"), +(4565,"3-18-14"), +(4566,"3-18-15"), +(4567,"3-18-16"), +(4568,"3-18-17"), +(4569,"3-18-18"), +(4570,"3-18-19"), +(4571,"3-18-20"), +(4572,"3-18-21"), +(4573,"3-18-22"), +(4574,"3-18-23"), +(4575,"3-18-24"), +(4576,"3-18-25"), +(4577,"3-18-26"), +(4578,"3-18-27"), +(4579,"3-18-28"), +(4580,"3-18-29"), +(4581,"3-18-30"), +(4582,"3-18-31"), +(4583,"3-18-32"), +(4584,"3-18-33"), +(4585,"3-18-34"), +(4586,"3-18-35"), +(4587,"3-18-36"), +(4588,"3-18-37"), +(4589,"3-18-38"), +(4590,"3-18-39"), +(4591,"3-18-40"), +(4592,"3-18-41"), +(4593,"3-18-42"), +(4594,"3-18-43"), +(4595,"3-18-44"), +(4596,"3-18-45"), +(4597,"3-18-46"), +(4598,"3-18-47"), +(4599,"3-18-48"), +(4600,"3-18-49"), +(4601,"1-18-0"), +(4602,"1-18-1"), +(4603,"1-18-2"), +(4604,"1-18-3"), +(4605,"1-18-4"), +(4606,"1-18-5"), +(4607,"1-18-6"), +(4608,"1-18-7"), +(4609,"1-18-8"), +(4610,"1-18-9"), +(4611,"1-18-10"), +(4612,"1-18-11"), +(4613,"1-18-12"), +(4614,"1-18-13"), +(4615,"1-18-14"), +(4616,"1-18-15"), +(4617,"1-18-16"), +(4618,"1-18-17"), +(4619,"1-18-18"), +(4620,"1-18-19"), +(4621,"1-18-20"), +(4622,"1-18-21"), +(4623,"1-18-22"), +(4624,"1-18-23"), +(4625,"1-18-24"), +(4626,"1-18-25"), +(4627,"1-18-26"), +(4628,"1-18-27"), +(4629,"1-18-28"), +(4630,"1-18-29"), +(4631,"1-18-30"), +(4632,"1-18-31"), +(4633,"1-18-32"), +(4634,"1-18-33"), +(4635,"1-18-34"), +(4636,"1-18-35"), +(4637,"1-18-36"), +(4638,"1-18-37"), +(4639,"1-18-38"), +(4640,"1-18-39"), +(4641,"1-18-40"), +(4642,"1-18-41"), +(4643,"1-18-42"), +(4644,"1-18-43"), +(4645,"1-18-44"), +(4646,"1-18-45"), +(4647,"1-18-46"), +(4648,"1-18-47"), +(4649,"1-18-48"), +(4650,"1-18-49"), +(4651,"4-18-0"), +(4652,"4-18-1"), +(4653,"4-18-2"), +(4654,"4-18-3"), +(4655,"4-18-4"), +(4656,"4-18-5"), +(4657,"4-18-6"), +(4658,"4-18-7"), +(4659,"4-18-8"), +(4660,"4-18-9"), +(4661,"4-18-10"), +(4662,"4-18-11"), +(4663,"4-18-12"), +(4664,"4-18-13"), +(4665,"4-18-14"), +(4666,"4-18-15"), +(4667,"4-18-16"), +(4668,"4-18-17"), +(4669,"4-18-18"), +(4670,"4-18-19"), +(4671,"4-18-20"), +(4672,"4-18-21"), +(4673,"4-18-22"), +(4674,"4-18-23"), +(4675,"4-18-24"), +(4676,"4-18-25"), +(4677,"4-18-26"), +(4678,"4-18-27"), +(4679,"4-18-28"), +(4680,"4-18-29"), +(4681,"4-18-30"), +(4682,"4-18-31"), +(4683,"4-18-32"), +(4684,"4-18-33"), +(4685,"4-18-34"), +(4686,"4-18-35"), +(4687,"4-18-36"), +(4688,"4-18-37"), +(4689,"4-18-38"), +(4690,"4-18-39"), +(4691,"4-18-40"), +(4692,"4-18-41"), +(4693,"4-18-42"), +(4694,"4-18-43"), +(4695,"4-18-44"), +(4696,"4-18-45"), +(4697,"4-18-46"), +(4698,"4-18-47"), +(4699,"4-18-48"), +(4700,"4-18-49"), +(4701,"0-19-0"), +(4702,"0-19-1"), +(4703,"0-19-2"), +(4704,"0-19-3"), +(4705,"0-19-4"), +(4706,"0-19-5"), +(4707,"0-19-6"), +(4708,"0-19-7"), +(4709,"0-19-8"), +(4710,"0-19-9"), +(4711,"0-19-10"), +(4712,"0-19-11"), +(4713,"0-19-12"), +(4714,"0-19-13"), +(4715,"0-19-14"), +(4716,"0-19-15"), +(4717,"0-19-16"), +(4718,"0-19-17"), +(4719,"0-19-18"), +(4720,"0-19-19"), +(4721,"0-19-20"), +(4722,"0-19-21"), +(4723,"0-19-22"), +(4724,"0-19-23"), +(4725,"0-19-24"), +(4726,"0-19-25"), +(4727,"0-19-26"), +(4728,"0-19-27"), +(4729,"0-19-28"), +(4730,"0-19-29"), +(4731,"0-19-30"), +(4732,"0-19-31"), +(4733,"0-19-32"), +(4734,"0-19-33"), +(4735,"0-19-34"), +(4736,"0-19-35"), +(4737,"0-19-36"), +(4738,"0-19-37"), +(4739,"0-19-38"), +(4740,"0-19-39"), +(4741,"0-19-40"), +(4742,"0-19-41"), +(4743,"0-19-42"), +(4744,"0-19-43"), +(4745,"0-19-44"), +(4746,"0-19-45"), +(4747,"0-19-46"), +(4748,"0-19-47"), +(4749,"0-19-48"), +(4750,"0-19-49"), +(4751,"2-18-0"), +(4752,"2-18-1"), +(4753,"2-18-2"), +(4754,"2-18-3"), +(4755,"2-18-4"), +(4756,"2-18-5"), +(4757,"2-18-6"), +(4758,"2-18-7"), +(4759,"2-18-8"), +(4760,"2-18-9"), +(4761,"2-18-10"), +(4762,"2-18-11"), +(4763,"2-18-12"), +(4764,"2-18-13"), +(4765,"2-18-14"), +(4766,"2-18-15"), +(4767,"2-18-16"), +(4768,"2-18-17"), +(4769,"2-18-18"), +(4770,"2-18-19"), +(4771,"2-18-20"), +(4772,"2-18-21"), +(4773,"2-18-22"), +(4774,"2-18-23"), +(4775,"2-18-24"), +(4776,"2-18-25"), +(4777,"2-18-26"), +(4778,"2-18-27"), +(4779,"2-18-28"), +(4780,"2-18-29"), +(4781,"2-18-30"), +(4782,"2-18-31"), +(4783,"2-18-32"), +(4784,"2-18-33"), +(4785,"2-18-34"), +(4786,"2-18-35"), +(4787,"2-18-36"), +(4788,"2-18-37"), +(4789,"2-18-38"), +(4790,"2-18-39"), +(4791,"2-18-40"), +(4792,"2-18-41"), +(4793,"2-18-42"), +(4794,"2-18-43"), +(4795,"2-18-44"), +(4796,"2-18-45"), +(4797,"2-18-46"), +(4798,"2-18-47"), +(4799,"2-18-48"), +(4800,"2-18-49"), +(4801,"4-19-0"), +(4802,"4-19-1"), +(4803,"4-19-2"), +(4804,"4-19-3"), +(4805,"4-19-4"), +(4806,"4-19-5"), +(4807,"4-19-6"), +(4808,"4-19-7"), +(4809,"4-19-8"), +(4810,"4-19-9"), +(4811,"4-19-10"), +(4812,"4-19-11"), +(4813,"4-19-12"), +(4814,"4-19-13"), +(4815,"4-19-14"), +(4816,"4-19-15"), +(4817,"4-19-16"), +(4818,"4-19-17"), +(4819,"4-19-18"), +(4820,"4-19-19"), +(4821,"4-19-20"), +(4822,"4-19-21"), +(4823,"4-19-22"), +(4824,"4-19-23"), +(4825,"4-19-24"), +(4826,"4-19-25"), +(4827,"4-19-26"), +(4828,"4-19-27"), +(4829,"4-19-28"), +(4830,"4-19-29"), +(4831,"4-19-30"), +(4832,"4-19-31"), +(4833,"4-19-32"), +(4834,"4-19-33"), +(4835,"4-19-34"), +(4836,"4-19-35"), +(4837,"4-19-36"), +(4838,"4-19-37"), +(4839,"4-19-38"), +(4840,"4-19-39"), +(4841,"4-19-40"), +(4842,"4-19-41"), +(4843,"4-19-42"), +(4844,"4-19-43"), +(4845,"4-19-44"), +(4846,"4-19-45"), +(4847,"4-19-46"), +(4848,"4-19-47"), +(4849,"4-19-48"), +(4850,"4-19-49"), +(4851,"3-19-0"), +(4852,"3-19-1"), +(4853,"3-19-2"), +(4854,"3-19-3"), +(4855,"3-19-4"), +(4856,"3-19-5"), +(4857,"3-19-6"), +(4858,"3-19-7"), +(4859,"3-19-8"), +(4860,"3-19-9"), +(4861,"3-19-10"), +(4862,"3-19-11"), +(4863,"3-19-12"), +(4864,"3-19-13"), +(4865,"3-19-14"), +(4866,"3-19-15"), +(4867,"3-19-16"), +(4868,"3-19-17"), +(4869,"3-19-18"), +(4870,"3-19-19"), +(4871,"3-19-20"), +(4872,"3-19-21"), +(4873,"3-19-22"), +(4874,"3-19-23"), +(4875,"3-19-24"), +(4876,"3-19-25"), +(4877,"3-19-26"), +(4878,"3-19-27"), +(4879,"3-19-28"), +(4880,"3-19-29"), +(4881,"3-19-30"), +(4882,"3-19-31"), +(4883,"3-19-32"), +(4884,"3-19-33"), +(4885,"3-19-34"), +(4886,"3-19-35"), +(4887,"3-19-36"), +(4888,"3-19-37"), +(4889,"3-19-38"), +(4890,"3-19-39"), +(4891,"3-19-40"), +(4892,"3-19-41"), +(4893,"3-19-42"), +(4894,"3-19-43"), +(4895,"3-19-44"), +(4896,"3-19-45"), +(4897,"3-19-46"), +(4898,"3-19-47"), +(4899,"3-19-48"), +(4900,"3-19-49"), +(4901,"1-19-0"), +(4902,"1-19-1"), +(4903,"1-19-2"), +(4904,"1-19-3"), +(4905,"1-19-4"), +(4906,"1-19-5"), +(4907,"1-19-6"), +(4908,"1-19-7"), +(4909,"1-19-8"), +(4910,"1-19-9"), +(4911,"1-19-10"), +(4912,"1-19-11"), +(4913,"1-19-12"), +(4914,"1-19-13"), +(4915,"1-19-14"), +(4916,"1-19-15"), +(4917,"1-19-16"), +(4918,"1-19-17"), +(4919,"1-19-18"), +(4920,"1-19-19"), +(4921,"1-19-20"), +(4922,"1-19-21"), +(4923,"1-19-22"), +(4924,"1-19-23"), +(4925,"1-19-24"), +(4926,"1-19-25"), +(4927,"1-19-26"), +(4928,"1-19-27"), +(4929,"1-19-28"), +(4930,"1-19-29"), +(4931,"1-19-30"), +(4932,"1-19-31"), +(4933,"1-19-32"), +(4934,"1-19-33"), +(4935,"1-19-34"), +(4936,"1-19-35"), +(4937,"1-19-36"), +(4938,"1-19-37"), +(4939,"1-19-38"), +(4940,"1-19-39"), +(4941,"1-19-40"), +(4942,"1-19-41"), +(4943,"1-19-42"), +(4944,"1-19-43"), +(4945,"1-19-44"), +(4946,"1-19-45"), +(4947,"1-19-46"), +(4948,"1-19-47"), +(4949,"1-19-48"), +(4950,"1-19-49"), +(4951,"2-19-0"), +(4952,"2-19-1"), +(4953,"2-19-2"), +(4954,"2-19-3"), +(4955,"2-19-4"), +(4956,"2-19-5"), +(4957,"4-20-0"), +(4958,"2-19-6"), +(4959,"4-20-1"), +(4960,"2-19-7"), +(4961,"4-20-2"), +(4962,"2-19-8"), +(4963,"4-20-3"), +(4964,"2-19-9"), +(4965,"4-20-4"), +(4966,"2-19-10"), +(4967,"4-20-5"), +(4968,"2-19-11"), +(4969,"4-20-6"), +(4970,"2-19-12"), +(4971,"4-20-7"), +(4972,"4-20-8"), +(4973,"2-19-13"), +(4974,"4-20-9"), +(4975,"2-19-14"), +(4976,"4-20-10"), +(4977,"2-19-15"), +(4978,"4-20-11"), +(4979,"2-19-16"), +(4980,"4-20-12"), +(4981,"2-19-17"), +(4982,"4-20-13"), +(4983,"2-19-18"), +(4984,"4-20-14"), +(4985,"2-19-19"), +(4986,"4-20-15"), +(4987,"2-19-20"), +(4988,"4-20-16"), +(4989,"2-19-21"), +(4990,"4-20-17"), +(4991,"2-19-22"), +(4992,"4-20-18"), +(4993,"4-20-19"), +(4994,"2-19-23"), +(4995,"4-20-20"), +(4996,"2-19-24"), +(4997,"4-20-21"), +(4998,"2-19-25"), +(4999,"4-20-22"), +(5000,"2-19-26"), +(5001,"4-20-23"), +(5002,"4-20-24"), +(5003,"4-20-25"), +(5004,"4-20-26"), +(5005,"4-20-27"), +(5006,"4-20-28"), +(5007,"4-20-29"), +(5008,"4-20-30"), +(5009,"4-20-31"), +(5010,"4-20-32"), +(5011,"4-20-33"), +(5012,"4-20-34"), +(5013,"4-20-35"), +(5014,"4-20-36"), +(5015,"4-20-37"), +(5016,"4-20-38"), +(5017,"4-20-39"), +(5018,"4-20-40"), +(5019,"4-20-41"), +(5020,"4-20-42"), +(5021,"4-20-43"), +(5022,"4-20-44"), +(5023,"2-19-27"), +(5024,"4-20-45"), +(5025,"4-20-46"), +(5026,"4-20-47"), +(5027,"4-20-48"), +(5028,"2-19-28"), +(5029,"4-20-49"), +(5030,"2-19-29"), +(5031,"2-19-30"), +(5032,"2-19-31"), +(5033,"2-19-32"), +(5034,"2-19-33"), +(5035,"2-19-34"), +(5036,"2-19-35"), +(5037,"2-19-36"), +(5038,"2-19-37"), +(5039,"2-19-38"), +(5040,"2-19-39"), +(5041,"2-19-40"), +(5042,"2-19-41"), +(5043,"2-19-42"), +(5044,"2-19-43"), +(5045,"2-19-44"), +(5046,"2-19-45"), +(5047,"2-19-46"), +(5048,"2-19-47"), +(5049,"2-19-48"), +(5050,"2-19-49"), +(5051,"0-20-0"), +(5052,"0-20-1"), +(5053,"0-20-2"), +(5054,"0-20-3"), +(5055,"0-20-4"), +(5056,"0-20-5"), +(5057,"0-20-6"), +(5058,"0-20-7"), +(5059,"0-20-8"), +(5060,"0-20-9"), +(5061,"0-20-10"), +(5062,"0-20-11"), +(5063,"0-20-12"), +(5064,"0-20-13"), +(5065,"0-20-14"), +(5066,"0-20-15"), +(5067,"0-20-16"), +(5068,"0-20-17"), +(5069,"0-20-18"), +(5070,"0-20-19"), +(5071,"0-20-20"), +(5072,"0-20-21"), +(5073,"0-20-22"), +(5074,"0-20-23"), +(5075,"0-20-24"), +(5076,"0-20-25"), +(5077,"0-20-26"), +(5078,"0-20-27"), +(5079,"0-20-28"), +(5080,"0-20-29"), +(5081,"0-20-30"), +(5082,"0-20-31"), +(5083,"0-20-32"), +(5084,"0-20-33"), +(5085,"0-20-34"), +(5086,"0-20-35"), +(5087,"0-20-36"), +(5088,"0-20-37"), +(5089,"0-20-38"), +(5090,"1-20-0"), +(5091,"1-20-1"), +(5092,"1-20-2"), +(5093,"1-20-3"), +(5094,"1-20-4"), +(5095,"1-20-5"), +(5096,"1-20-6"), +(5097,"1-20-7"), +(5098,"1-20-8"), +(5099,"1-20-9"), +(5100,"1-20-10"), +(5101,"1-20-11"), +(5102,"1-20-12"), +(5103,"1-20-13"), +(5104,"1-20-14"), +(5105,"1-20-15"), +(5106,"1-20-16"), +(5107,"1-20-17"), +(5108,"1-20-18"), +(5109,"1-20-19"), +(5110,"1-20-20"), +(5111,"1-20-21"), +(5112,"1-20-22"), +(5113,"1-20-23"), +(5114,"3-20-0"), +(5115,"1-20-24"), +(5116,"1-20-25"), +(5117,"3-20-1"), +(5118,"1-20-26"), +(5119,"3-20-2"), +(5120,"1-20-27"), +(5121,"3-20-3"), +(5122,"1-20-28"), +(5123,"3-20-4"), +(5124,"1-20-29"), +(5125,"3-20-5"), +(5126,"1-20-30"), +(5127,"3-20-6"), +(5128,"1-20-31"), +(5129,"3-20-7"), +(5130,"1-20-32"), +(5131,"3-20-8"), +(5132,"1-20-33"), +(5133,"3-20-9"), +(5134,"1-20-34"), +(5135,"3-20-10"), +(5136,"1-20-35"), +(5137,"3-20-11"), +(5138,"1-20-36"), +(5139,"3-20-12"), +(5140,"1-20-37"), +(5141,"3-20-13"), +(5142,"1-20-38"), +(5143,"3-20-14"), +(5144,"1-20-39"), +(5145,"3-20-15"), +(5146,"1-20-40"), +(5147,"3-20-16"), +(5148,"1-20-41"), +(5149,"3-20-17"), +(5150,"1-20-42"), +(5151,"3-20-18"), +(5152,"1-20-43"), +(5153,"3-20-19"), +(5154,"1-20-44"), +(5155,"3-20-20"), +(5156,"1-20-45"), +(5157,"3-20-21"), +(5158,"1-20-46"), +(5159,"3-20-22"), +(5160,"1-20-47"), +(5161,"3-20-23"), +(5162,"1-20-48"), +(5163,"3-20-24"), +(5164,"1-20-49"), +(5165,"0-20-39"), +(5166,"3-20-25"), +(5167,"3-20-26"), +(5168,"3-20-27"), +(5169,"3-20-28"), +(5170,"0-20-40"), +(5171,"3-20-29"), +(5172,"0-20-41"), +(5173,"3-20-30"), +(5174,"0-20-42"), +(5175,"3-20-31"), +(5176,"0-20-43"), +(5177,"3-20-32"), +(5178,"0-20-44"), +(5179,"3-20-33"), +(5180,"0-20-45"), +(5181,"3-20-34"), +(5182,"0-20-46"), +(5183,"3-20-35"), +(5184,"0-20-47"), +(5185,"3-20-36"), +(5186,"0-20-48"), +(5187,"3-20-37"), +(5188,"0-20-49"), +(5189,"3-20-38"), +(5190,"3-20-39"), +(5191,"3-20-40"), +(5192,"3-20-41"), +(5193,"3-20-42"), +(5194,"3-20-43"), +(5195,"3-20-44"), +(5196,"3-20-45"), +(5197,"3-20-46"), +(5198,"3-20-47"), +(5199,"3-20-48"), +(5200,"3-20-49"), +(5201,"1-21-0"), +(5202,"1-21-1"), +(5203,"1-21-2"), +(5204,"1-21-3"), +(5205,"1-21-4"), +(5206,"1-21-5"), +(5207,"1-21-6"), +(5208,"1-21-7"), +(5209,"1-21-8"), +(5210,"1-21-9"), +(5211,"1-21-10"), +(5212,"1-21-11"), +(5213,"1-21-12"), +(5214,"1-21-13"), +(5215,"1-21-14"), +(5216,"1-21-15"), +(5217,"1-21-16"), +(5218,"1-21-17"), +(5219,"1-21-18"), +(5220,"1-21-19"), +(5221,"1-21-20"), +(5222,"1-21-21"), +(5223,"1-21-22"), +(5224,"1-21-23"), +(5225,"1-21-24"), +(5226,"1-21-25"), +(5227,"1-21-26"), +(5228,"1-21-27"), +(5229,"1-21-28"), +(5230,"1-21-29"), +(5231,"1-21-30"), +(5232,"1-21-31"), +(5233,"1-21-32"), +(5234,"1-21-33"), +(5235,"1-21-34"), +(5236,"1-21-35"), +(5237,"1-21-36"), +(5238,"1-21-37"), +(5239,"1-21-38"), +(5240,"1-21-39"), +(5241,"1-21-40"), +(5242,"1-21-41"), +(5243,"1-21-42"), +(5244,"1-21-43"), +(5245,"1-21-44"), +(5246,"1-21-45"), +(5247,"1-21-46"), +(5248,"1-21-47"), +(5249,"1-21-48"), +(5250,"1-21-49"), +(5251,"3-21-0"), +(5252,"3-21-1"), +(5253,"3-21-2"), +(5254,"3-21-3"), +(5255,"3-21-4"), +(5256,"3-21-5"), +(5257,"3-21-6"), +(5258,"3-21-7"), +(5259,"3-21-8"), +(5260,"3-21-9"), +(5261,"3-21-10"), +(5262,"3-21-11"), +(5263,"3-21-12"), +(5264,"3-21-13"), +(5265,"3-21-14"), +(5266,"3-21-15"), +(5267,"3-21-16"), +(5268,"3-21-17"), +(5269,"3-21-18"), +(5270,"3-21-19"), +(5271,"3-21-20"), +(5272,"3-21-21"), +(5273,"3-21-22"), +(5274,"3-21-23"), +(5275,"3-21-24"), +(5276,"3-21-25"), +(5277,"3-21-26"), +(5278,"3-21-27"), +(5279,"3-21-28"), +(5280,"3-21-29"), +(5281,"3-21-30"), +(5282,"3-21-31"), +(5283,"3-21-32"), +(5284,"3-21-33"), +(5285,"3-21-34"), +(5286,"3-21-35"), +(5287,"3-21-36"), +(5288,"3-21-37"), +(5289,"3-21-38"), +(5290,"3-21-39"), +(5291,"3-21-40"), +(5292,"3-21-41"), +(5293,"3-21-42"), +(5294,"3-21-43"), +(5295,"3-21-44"), +(5296,"3-21-45"), +(5297,"3-21-46"), +(5298,"3-21-47"), +(5299,"3-21-48"), +(5300,"3-21-49"), +(5301,"2-20-0"), +(5302,"2-20-1"), +(5303,"2-20-2"), +(5304,"2-20-3"), +(5305,"2-20-4"), +(5306,"2-20-5"), +(5307,"2-20-6"), +(5308,"2-20-7"), +(5309,"2-20-8"), +(5310,"2-20-9"), +(5311,"2-20-10"), +(5312,"2-20-11"), +(5313,"2-20-12"), +(5314,"2-20-13"), +(5315,"2-20-14"), +(5316,"2-20-15"), +(5317,"2-20-16"), +(5318,"2-20-17"), +(5319,"2-20-18"), +(5320,"2-20-19"), +(5321,"2-20-20"), +(5322,"2-20-21"), +(5323,"2-20-22"), +(5324,"2-20-23"), +(5325,"2-20-24"), +(5326,"2-20-25"), +(5327,"2-20-26"), +(5328,"2-20-27"), +(5329,"2-20-28"), +(5330,"2-20-29"), +(5331,"2-20-30"), +(5332,"2-20-31"), +(5333,"2-20-32"), +(5334,"2-20-33"), +(5335,"2-20-34"), +(5336,"2-20-35"), +(5337,"2-20-36"), +(5338,"2-20-37"), +(5339,"2-20-38"), +(5340,"2-20-39"), +(5341,"2-20-40"), +(5342,"2-20-41"), +(5343,"2-20-42"), +(5344,"2-20-43"), +(5345,"2-20-44"), +(5346,"2-20-45"), +(5347,"2-20-46"), +(5348,"2-20-47"), +(5349,"2-20-48"), +(5350,"2-20-49"), +(5351,"0-21-0"), +(5352,"0-21-1"), +(5353,"0-21-2"), +(5354,"0-21-3"), +(5355,"0-21-4"), +(5356,"0-21-5"), +(5357,"0-21-6"), +(5358,"0-21-7"), +(5359,"0-21-8"), +(5360,"0-21-9"), +(5361,"0-21-10"), +(5362,"0-21-11"), +(5363,"0-21-12"), +(5364,"0-21-13"), +(5365,"0-21-14"), +(5366,"0-21-15"), +(5367,"0-21-16"), +(5368,"0-21-17"), +(5369,"0-21-18"), +(5370,"0-21-19"), +(5371,"0-21-20"), +(5372,"0-21-21"), +(5373,"0-21-22"), +(5374,"0-21-23"), +(5375,"0-21-24"), +(5376,"0-21-25"), +(5377,"0-21-26"), +(5378,"0-21-27"), +(5379,"0-21-28"), +(5380,"0-21-29"), +(5381,"0-21-30"), +(5382,"0-21-31"), +(5383,"0-21-32"), +(5384,"0-21-33"), +(5385,"0-21-34"), +(5386,"0-21-35"), +(5387,"0-21-36"), +(5388,"0-21-37"), +(5389,"0-21-38"), +(5390,"0-21-39"), +(5391,"0-21-40"), +(5392,"0-21-41"), +(5393,"0-21-42"), +(5394,"0-21-43"), +(5395,"0-21-44"), +(5396,"0-21-45"), +(5397,"0-21-46"), +(5398,"0-21-47"), +(5399,"0-21-48"), +(5400,"0-21-49"), +(5401,"4-21-0"), +(5402,"4-21-1"), +(5403,"4-21-2"), +(5404,"4-21-3"), +(5405,"4-21-4"), +(5406,"4-21-5"), +(5407,"4-21-6"), +(5408,"4-21-7"), +(5409,"4-21-8"), +(5410,"4-21-9"), +(5411,"4-21-10"), +(5412,"4-21-11"), +(5413,"4-21-12"), +(5414,"4-21-13"), +(5415,"4-21-14"), +(5416,"4-21-15"), +(5417,"4-21-16"), +(5418,"4-21-17"), +(5419,"4-21-18"), +(5420,"4-21-19"), +(5421,"4-21-20"), +(5422,"4-21-21"), +(5423,"4-21-22"), +(5424,"4-21-23"), +(5425,"4-21-24"), +(5426,"4-21-25"), +(5427,"4-21-26"), +(5428,"4-21-27"), +(5429,"4-21-28"), +(5430,"4-21-29"), +(5431,"4-21-30"), +(5432,"4-21-31"), +(5433,"4-21-32"), +(5434,"4-21-33"), +(5435,"4-21-34"), +(5436,"4-21-35"), +(5437,"4-21-36"), +(5438,"4-21-37"), +(5439,"4-21-38"), +(5440,"4-21-39"), +(5441,"4-21-40"), +(5442,"4-21-41"), +(5443,"4-21-42"), +(5444,"4-21-43"), +(5445,"4-21-44"), +(5446,"4-21-45"), +(5447,"4-21-46"), +(5448,"4-21-47"), +(5449,"4-21-48"), +(5450,"4-21-49"), +(5451,"2-21-0"), +(5452,"2-21-1"), +(5453,"2-21-2"), +(5454,"2-21-3"), +(5455,"2-21-4"), +(5456,"2-21-5"), +(5457,"2-21-6"), +(5458,"2-21-7"), +(5459,"2-21-8"), +(5460,"2-21-9"), +(5461,"2-21-10"), +(5462,"2-21-11"), +(5463,"2-21-12"), +(5464,"2-21-13"), +(5465,"2-21-14"), +(5466,"2-21-15"), +(5467,"2-21-16"), +(5468,"2-21-17"), +(5469,"2-21-18"), +(5470,"2-21-19"), +(5471,"2-21-20"), +(5472,"2-21-21"), +(5473,"2-21-22"), +(5474,"2-21-23"), +(5475,"2-21-24"), +(5476,"2-21-25"), +(5477,"2-21-26"), +(5478,"2-21-27"), +(5479,"2-21-28"), +(5480,"2-21-29"), +(5481,"2-21-30"), +(5482,"2-21-31"), +(5483,"2-21-32"), +(5484,"2-21-33"), +(5485,"2-21-34"), +(5486,"2-21-35"), +(5487,"2-21-36"), +(5488,"2-21-37"), +(5489,"2-21-38"), +(5490,"2-21-39"), +(5491,"2-21-40"), +(5492,"2-21-41"), +(5493,"2-21-42"), +(5494,"2-21-43"), +(5495,"2-21-44"), +(5496,"2-21-45"), +(5497,"2-21-46"), +(5498,"2-21-47"), +(5499,"2-21-48"), +(5500,"2-21-49"), +(5501,"3-22-0"), +(5502,"3-22-1"), +(5503,"3-22-2"), +(5504,"3-22-3"), +(5505,"3-22-4"), +(5506,"3-22-5"), +(5507,"3-22-6"), +(5508,"3-22-7"), +(5509,"3-22-8"), +(5510,"3-22-9"), +(5511,"3-22-10"), +(5512,"3-22-11"), +(5513,"3-22-12"), +(5514,"3-22-13"), +(5515,"3-22-14"), +(5516,"3-22-15"), +(5517,"3-22-16"), +(5518,"3-22-17"), +(5519,"3-22-18"), +(5520,"3-22-19"), +(5521,"3-22-20"), +(5522,"3-22-21"), +(5523,"3-22-22"), +(5524,"3-22-23"), +(5525,"3-22-24"), +(5526,"3-22-25"), +(5527,"3-22-26"), +(5528,"3-22-27"), +(5529,"3-22-28"), +(5530,"3-22-29"), +(5531,"3-22-30"), +(5532,"3-22-31"), +(5533,"3-22-32"), +(5534,"3-22-33"), +(5535,"3-22-34"), +(5536,"3-22-35"), +(5537,"3-22-36"), +(5538,"3-22-37"), +(5539,"3-22-38"), +(5540,"3-22-39"), +(5541,"3-22-40"), +(5542,"3-22-41"), +(5543,"3-22-42"), +(5544,"3-22-43"), +(5545,"3-22-44"), +(5546,"3-22-45"), +(5547,"3-22-46"), +(5548,"3-22-47"), +(5549,"3-22-48"), +(5550,"3-22-49"), +(5551,"4-22-0"), +(5552,"4-22-1"), +(5553,"4-22-2"), +(5554,"4-22-3"), +(5555,"4-22-4"), +(5556,"4-22-5"), +(5557,"4-22-6"), +(5558,"4-22-7"), +(5559,"4-22-8"), +(5560,"4-22-9"), +(5561,"4-22-10"), +(5562,"4-22-11"), +(5563,"4-22-12"), +(5564,"4-22-13"), +(5565,"4-22-14"), +(5566,"4-22-15"), +(5567,"4-22-16"), +(5568,"4-22-17"), +(5569,"4-22-18"), +(5570,"4-22-19"), +(5571,"4-22-20"), +(5572,"4-22-21"), +(5573,"4-22-22"), +(5574,"4-22-23"), +(5575,"4-22-24"), +(5576,"4-22-25"), +(5577,"4-22-26"), +(5578,"4-22-27"), +(5579,"4-22-28"), +(5580,"4-22-29"), +(5581,"4-22-30"), +(5582,"4-22-31"), +(5583,"4-22-32"), +(5584,"4-22-33"), +(5585,"4-22-34"), +(5586,"4-22-35"), +(5587,"4-22-36"), +(5588,"4-22-37"), +(5589,"4-22-38"), +(5590,"4-22-39"), +(5591,"4-22-40"), +(5592,"4-22-41"), +(5593,"4-22-42"), +(5594,"4-22-43"), +(5595,"4-22-44"), +(5596,"4-22-45"), +(5597,"4-22-46"), +(5598,"4-22-47"), +(5599,"4-22-48"), +(5600,"4-22-49"), +(5601,"1-22-0"), +(5602,"1-22-1"), +(5603,"1-22-2"), +(5604,"1-22-3"), +(5605,"1-22-4"), +(5606,"1-22-5"), +(5607,"1-22-6"), +(5608,"1-22-7"), +(5609,"1-22-8"), +(5610,"1-22-9"), +(5611,"1-22-10"), +(5612,"1-22-11"), +(5613,"1-22-12"), +(5614,"1-22-13"), +(5615,"1-22-14"), +(5616,"1-22-15"), +(5617,"1-22-16"), +(5618,"1-22-17"), +(5619,"1-22-18"), +(5620,"1-22-19"), +(5621,"1-22-20"), +(5622,"1-22-21"), +(5623,"1-22-22"), +(5624,"1-22-23"), +(5625,"1-22-24"), +(5626,"1-22-25"), +(5627,"1-22-26"), +(5628,"1-22-27"), +(5629,"1-22-28"), +(5630,"1-22-29"), +(5631,"1-22-30"), +(5632,"1-22-31"), +(5633,"1-22-32"), +(5634,"1-22-33"), +(5635,"1-22-34"), +(5636,"1-22-35"), +(5637,"1-22-36"), +(5638,"1-22-37"), +(5639,"1-22-38"), +(5640,"1-22-39"), +(5641,"1-22-40"), +(5642,"1-22-41"), +(5643,"1-22-42"), +(5644,"1-22-43"), +(5645,"1-22-44"), +(5646,"1-22-45"), +(5647,"1-22-46"), +(5648,"1-22-47"), +(5649,"1-22-48"), +(5650,"1-22-49"), +(5651,"0-22-0"), +(5652,"0-22-1"), +(5653,"0-22-2"), +(5654,"0-22-3"), +(5655,"0-22-4"), +(5656,"0-22-5"), +(5657,"0-22-6"), +(5658,"0-22-7"), +(5659,"0-22-8"), +(5660,"0-22-9"), +(5661,"0-22-10"), +(5662,"0-22-11"), +(5663,"0-22-12"), +(5664,"0-22-13"), +(5665,"0-22-14"), +(5666,"0-22-15"), +(5667,"0-22-16"), +(5668,"0-22-17"), +(5669,"0-22-18"), +(5670,"0-22-19"), +(5671,"0-22-20"), +(5672,"0-22-21"), +(5673,"0-22-22"), +(5674,"0-22-23"), +(5675,"0-22-24"), +(5676,"0-22-25"), +(5677,"0-22-26"), +(5678,"0-22-27"), +(5679,"0-22-28"), +(5680,"0-22-29"), +(5681,"0-22-30"), +(5682,"0-22-31"), +(5683,"0-22-32"), +(5684,"0-22-33"), +(5685,"0-22-34"), +(5686,"0-22-35"), +(5687,"0-22-36"), +(5688,"0-22-37"), +(5689,"0-22-38"), +(5690,"0-22-39"), +(5691,"0-22-40"), +(5692,"0-22-41"), +(5693,"0-22-42"), +(5694,"0-22-43"), +(5695,"0-22-44"), +(5696,"0-22-45"), +(5697,"0-22-46"), +(5698,"0-22-47"), +(5699,"0-22-48"), +(5700,"0-22-49"), +(5701,"4-23-0"), +(5702,"4-23-1"), +(5703,"2-22-0"), +(5704,"4-23-2"), +(5705,"2-22-1"), +(5706,"4-23-3"), +(5707,"2-22-2"), +(5708,"4-23-4"), +(5709,"2-22-3"), +(5710,"4-23-5"), +(5711,"2-22-4"), +(5712,"4-23-6"), +(5713,"2-22-5"), +(5714,"4-23-7"), +(5715,"2-22-6"), +(5716,"4-23-8"), +(5717,"2-22-7"), +(5718,"4-23-9"), +(5719,"2-22-8"), +(5720,"4-23-10"), +(5721,"2-22-9"), +(5722,"4-23-11"), +(5723,"2-22-10"), +(5724,"4-23-12"), +(5725,"2-22-11"), +(5726,"4-23-13"), +(5727,"2-22-12"), +(5728,"4-23-14"), +(5729,"2-22-13"), +(5730,"4-23-15"), +(5731,"2-22-14"), +(5732,"4-23-16"), +(5733,"2-22-15"), +(5734,"4-23-17"), +(5735,"4-23-18"), +(5736,"2-22-16"), +(5737,"4-23-19"), +(5738,"2-22-17"), +(5739,"4-23-20"), +(5740,"2-22-18"), +(5741,"4-23-21"), +(5742,"2-22-19"), +(5743,"4-23-22"), +(5744,"2-22-20"), +(5745,"4-23-23"), +(5746,"2-22-21"), +(5747,"4-23-24"), +(5748,"2-22-22"), +(5749,"4-23-25"), +(5750,"2-22-23"), +(5751,"4-23-26"), +(5752,"2-22-24"), +(5753,"4-23-27"), +(5754,"2-22-25"), +(5755,"4-23-28"), +(5756,"2-22-26"), +(5757,"4-23-29"), +(5758,"2-22-27"), +(5759,"4-23-30"), +(5760,"2-22-28"), +(5761,"4-23-31"), +(5762,"2-22-29"), +(5763,"4-23-32"), +(5764,"2-22-30"), +(5765,"4-23-33"), +(5766,"2-22-31"), +(5767,"4-23-34"), +(5768,"2-22-32"), +(5769,"4-23-35"), +(5770,"2-22-33"), +(5771,"4-23-36"), +(5772,"2-22-34"), +(5773,"4-23-37"), +(5774,"2-22-35"), +(5775,"4-23-38"), +(5776,"2-22-36"), +(5777,"4-23-39"), +(5778,"2-22-37"), +(5779,"4-23-40"), +(5780,"2-22-38"), +(5781,"4-23-41"), +(5782,"2-22-39"), +(5783,"4-23-42"), +(5784,"2-22-40"), +(5785,"4-23-43"), +(5786,"4-23-44"), +(5787,"2-22-41"), +(5788,"4-23-45"), +(5789,"2-22-42"), +(5790,"4-23-46"), +(5791,"2-22-43"), +(5792,"4-23-47"), +(5793,"2-22-44"), +(5794,"4-23-48"), +(5795,"2-22-45"), +(5796,"4-23-49"), +(5797,"2-22-46"), +(5798,"2-22-47"), +(5799,"2-22-48"), +(5800,"2-22-49"), +(5801,"2-23-0"), +(5802,"2-23-1"), +(5803,"2-23-2"), +(5804,"2-23-3"), +(5805,"2-23-4"), +(5806,"2-23-5"), +(5807,"2-23-6"), +(5808,"2-23-7"), +(5809,"2-23-8"), +(5810,"2-23-9"), +(5811,"2-23-10"), +(5812,"2-23-11"), +(5813,"2-23-12"), +(5814,"2-23-13"), +(5815,"2-23-14"), +(5816,"2-23-15"), +(5817,"2-23-16"), +(5818,"2-23-17"), +(5819,"2-23-18"), +(5820,"2-23-19"), +(5821,"2-23-20"), +(5822,"2-23-21"), +(5823,"2-23-22"), +(5824,"2-23-23"), +(5825,"2-23-24"), +(5826,"2-23-25"), +(5827,"2-23-26"), +(5828,"2-23-27"), +(5829,"2-23-28"), +(5830,"2-23-29"), +(5831,"2-23-30"), +(5832,"2-23-31"), +(5833,"2-23-32"), +(5834,"2-23-33"), +(5835,"2-23-34"), +(5836,"2-23-35"), +(5837,"2-23-36"), +(5838,"2-23-37"), +(5839,"2-23-38"), +(5840,"2-23-39"), +(5841,"2-23-40"), +(5842,"2-23-41"), +(5843,"2-23-42"), +(5844,"2-23-43"), +(5845,"2-23-44"), +(5846,"2-23-45"), +(5847,"2-23-46"), +(5848,"2-23-47"), +(5849,"2-23-48"), +(5850,"2-23-49"), +(5851,"0-23-0"), +(5852,"0-23-1"), +(5853,"0-23-2"), +(5854,"0-23-3"), +(5855,"0-23-4"), +(5856,"0-23-5"), +(5857,"0-23-6"), +(5858,"0-23-7"), +(5859,"0-23-8"), +(5860,"0-23-9"), +(5861,"0-23-10"), +(5862,"3-23-0"), +(5863,"0-23-11"), +(5864,"0-23-12"), +(5865,"0-23-13"), +(5866,"0-23-14"), +(5867,"0-23-15"), +(5868,"0-23-16"), +(5869,"3-23-1"), +(5870,"1-23-0"), +(5871,"3-23-2"), +(5872,"3-23-3"), +(5873,"1-23-1"), +(5874,"3-23-4"), +(5875,"1-23-2"), +(5876,"3-23-5"), +(5877,"1-23-3"), +(5878,"3-23-6"), +(5879,"1-23-4"), +(5880,"3-23-7"), +(5881,"1-23-5"), +(5882,"3-23-8"), +(5883,"1-23-6"), +(5884,"3-23-9"), +(5885,"3-23-10"), +(5886,"4-24-0"), +(5887,"0-23-17"), +(5888,"3-23-11"), +(5889,"0-23-18"), +(5890,"4-24-1"), +(5891,"0-23-19"), +(5892,"4-24-2"), +(5893,"1-23-7"), +(5894,"4-24-3"), +(5895,"1-23-8"), +(5896,"1-23-9"), +(5897,"4-24-4"), +(5898,"1-23-10"), +(5899,"1-23-11"), +(5900,"4-24-5"), +(5901,"0-23-20"), +(5902,"4-24-6"), +(5903,"0-23-21"), +(5904,"3-23-12"), +(5905,"3-23-13"), +(5906,"0-23-22"), +(5907,"4-24-7"), +(5908,"4-24-8"), +(5909,"1-23-12"), +(5910,"1-23-13"), +(5911,"3-23-14"), +(5912,"1-23-14"), +(5913,"3-23-15"), +(5914,"3-23-16"), +(5915,"1-23-15"), +(5916,"3-23-17"), +(5917,"1-23-16"), +(5918,"4-24-9"), +(5919,"1-23-17"), +(5920,"4-24-10"), +(5921,"1-23-18"), +(5922,"4-24-11"), +(5923,"1-23-19"), +(5924,"3-23-18"), +(5925,"4-24-12"), +(5926,"0-23-23"), +(5927,"4-24-13"), +(5928,"3-23-19"), +(5929,"0-23-24"), +(5930,"1-23-20"), +(5931,"3-23-20"), +(5932,"0-23-25"), +(5933,"1-23-21"), +(5934,"3-23-21"), +(5935,"1-23-22"), +(5936,"0-23-26"), +(5937,"1-23-23"), +(5938,"3-23-22"), +(5939,"1-23-24"), +(5940,"0-23-27"), +(5941,"1-23-25"), +(5942,"3-23-23"), +(5943,"0-23-28"), +(5944,"1-23-26"), +(5945,"0-23-29"), +(5946,"3-23-24"), +(5947,"1-23-27"), +(5948,"0-23-30"), +(5949,"1-23-28"), +(5950,"3-23-25"), +(5951,"1-23-29"), +(5952,"3-23-26"), +(5953,"1-23-30"), +(5954,"3-23-27"), +(5955,"1-23-31"), +(5956,"4-24-14"), +(5957,"3-23-28"), +(5958,"4-24-15"), +(5959,"4-24-16"), +(5960,"3-23-29"), +(5961,"1-23-32"), +(5962,"0-23-31"), +(5963,"1-23-33"), +(5964,"1-23-34"), +(5965,"0-23-32"), +(5966,"1-23-35"), +(5967,"0-23-33"), +(5968,"1-23-36"), +(5969,"0-23-34"), +(5970,"1-23-37"), +(5971,"0-23-35"), +(5972,"1-23-38"), +(5973,"0-23-36"), +(5974,"1-23-39"), +(5975,"0-23-37"), +(5976,"4-24-17"), +(5977,"4-24-18"), +(5978,"1-23-40"), +(5979,"0-23-38"), +(5980,"4-24-19"), +(5981,"4-24-20"), +(5982,"0-23-39"), +(5983,"4-24-21"), +(5984,"0-23-40"), +(5985,"0-23-41"), +(5986,"4-24-22"), +(5987,"0-23-42"), +(5988,"4-24-23"), +(5989,"0-23-43"), +(5990,"1-23-41"), +(5991,"0-23-44"), +(5992,"3-23-30"), +(5993,"1-23-42"), +(5994,"0-23-45"), +(5995,"3-23-31"), +(5996,"0-23-46"), +(5997,"3-23-32"), +(5998,"4-24-24"), +(5999,"3-23-33"), +(6000,"0-23-47"), +(6001,"3-23-34"), +(6002,"0-23-48"), +(6003,"3-23-35"), +(6004,"4-24-25"), +(6005,"4-24-26"), +(6006,"4-24-27"), +(6007,"4-24-28"), +(6008,"4-24-29"), +(6009,"4-24-30"), +(6010,"4-24-31"), +(6011,"4-24-32"), +(6012,"4-24-33"), +(6013,"4-24-34"), +(6014,"4-24-35"), +(6015,"4-24-36"), +(6016,"4-24-37"), +(6017,"4-24-38"), +(6018,"4-24-39"), +(6019,"4-24-40"), +(6020,"4-24-41"), +(6021,"1-23-43"), +(6022,"3-23-36"), +(6023,"0-23-49"), +(6024,"1-23-44"), +(6025,"1-23-45"), +(6026,"1-23-46"), +(6027,"1-23-47"), +(6028,"1-23-48"), +(6029,"1-23-49"), +(6030,"4-24-42"), +(6031,"4-24-43"), +(6032,"4-24-44"), +(6033,"4-24-45"), +(6034,"4-24-46"), +(6035,"4-24-47"), +(6036,"4-24-48"), +(6037,"4-24-49"), +(6038,"3-23-37"), +(6039,"3-23-38"), +(6040,"3-23-39"), +(6041,"3-23-40"), +(6042,"3-23-41"), +(6043,"3-23-42"), +(6044,"3-23-43"), +(6045,"3-23-44"), +(6046,"3-23-45"), +(6047,"3-23-46"), +(6048,"3-23-47"), +(6049,"3-23-48"), +(6050,"3-23-49"), +(6051,"2-24-0"), +(6052,"2-24-1"), +(6053,"2-24-2"), +(6054,"2-24-3"), +(6055,"2-24-4"), +(6056,"2-24-5"), +(6057,"2-24-6"), +(6058,"2-24-7"), +(6059,"2-24-8"), +(6060,"2-24-9"), +(6061,"2-24-10"), +(6062,"2-24-11"), +(6063,"2-24-12"), +(6064,"2-24-13"), +(6065,"2-24-14"), +(6066,"2-24-15"), +(6067,"2-24-16"), +(6068,"2-24-17"), +(6069,"2-24-18"), +(6070,"2-24-19"), +(6071,"2-24-20"), +(6072,"2-24-21"), +(6073,"2-24-22"), +(6074,"2-24-23"), +(6075,"2-24-24"), +(6076,"2-24-25"), +(6077,"2-24-26"), +(6078,"2-24-27"), +(6079,"2-24-28"), +(6080,"2-24-29"), +(6081,"2-24-30"), +(6082,"2-24-31"), +(6083,"2-24-32"), +(6084,"2-24-33"), +(6085,"2-24-34"), +(6086,"2-24-35"), +(6087,"2-24-36"), +(6088,"2-24-37"), +(6089,"2-24-38"), +(6090,"2-24-39"), +(6091,"2-24-40"), +(6092,"2-24-41"), +(6093,"2-24-42"), +(6094,"2-24-43"), +(6095,"2-24-44"), +(6096,"2-24-45"), +(6097,"2-24-46"), +(6098,"2-24-47"), +(6099,"2-24-48"), +(6100,"2-24-49"), +(6101,"0-24-0"), +(6102,"0-24-1"), +(6103,"0-24-2"), +(6104,"0-24-3"), +(6105,"0-24-4"), +(6106,"0-24-5"), +(6107,"0-24-6"), +(6108,"0-24-7"), +(6109,"0-24-8"), +(6110,"0-24-9"), +(6111,"0-24-10"), +(6112,"0-24-11"), +(6113,"0-24-12"), +(6114,"0-24-13"), +(6115,"0-24-14"), +(6116,"0-24-15"), +(6117,"0-24-16"), +(6118,"0-24-17"), +(6119,"0-24-18"), +(6120,"0-24-19"), +(6121,"0-24-20"), +(6122,"0-24-21"), +(6123,"0-24-22"), +(6124,"0-24-23"), +(6125,"0-24-24"), +(6126,"0-24-25"), +(6127,"0-24-26"), +(6128,"0-24-27"), +(6129,"0-24-28"), +(6130,"0-24-29"), +(6131,"0-24-30"), +(6132,"0-24-31"), +(6133,"0-24-32"), +(6134,"0-24-33"), +(6135,"0-24-34"), +(6136,"0-24-35"), +(6137,"0-24-36"), +(6138,"0-24-37"), +(6139,"0-24-38"), +(6140,"0-24-39"), +(6141,"0-24-40"), +(6142,"0-24-41"), +(6143,"0-24-42"), +(6144,"0-24-43"), +(6145,"0-24-44"), +(6146,"0-24-45"), +(6147,"0-24-46"), +(6148,"0-24-47"), +(6149,"0-24-48"), +(6150,"0-24-49"), +(6151,"1-24-0"), +(6152,"1-24-1"), +(6153,"1-24-2"), +(6154,"1-24-3"), +(6155,"1-24-4"), +(6156,"1-24-5"), +(6157,"1-24-6"), +(6158,"1-24-7"), +(6159,"1-24-8"), +(6160,"1-24-9"), +(6161,"1-24-10"), +(6162,"1-24-11"), +(6163,"1-24-12"), +(6164,"1-24-13"), +(6165,"1-24-14"), +(6166,"1-24-15"), +(6167,"1-24-16"), +(6168,"1-24-17"), +(6169,"1-24-18"), +(6170,"1-24-19"), +(6171,"1-24-20"), +(6172,"1-24-21"), +(6173,"1-24-22"), +(6174,"1-24-23"), +(6175,"1-24-24"), +(6176,"1-24-25"), +(6177,"1-24-26"), +(6178,"1-24-27"), +(6179,"1-24-28"), +(6180,"1-24-29"), +(6181,"1-24-30"), +(6182,"1-24-31"), +(6183,"1-24-32"), +(6184,"1-24-33"), +(6185,"1-24-34"), +(6186,"1-24-35"), +(6187,"1-24-36"), +(6188,"1-24-37"), +(6189,"1-24-38"), +(6190,"1-24-39"), +(6191,"1-24-40"), +(6192,"1-24-41"), +(6193,"1-24-42"), +(6194,"1-24-43"), +(6195,"1-24-44"), +(6196,"1-24-45"), +(6197,"1-24-46"), +(6198,"1-24-47"), +(6199,"1-24-48"), +(6200,"1-24-49"), +(6201,"4-25-0"), +(6202,"4-25-1"), +(6203,"4-25-2"), +(6204,"4-25-3"), +(6205,"4-25-4"), +(6206,"4-25-5"), +(6207,"4-25-6"), +(6208,"4-25-7"), +(6209,"4-25-8"), +(6210,"4-25-9"), +(6211,"4-25-10"), +(6212,"4-25-11"), +(6213,"4-25-12"), +(6214,"4-25-13"), +(6215,"4-25-14"), +(6216,"4-25-15"), +(6217,"4-25-16"), +(6218,"4-25-17"), +(6219,"4-25-18"), +(6220,"4-25-19"), +(6221,"2-25-0"), +(6222,"4-25-20"), +(6223,"4-25-21"), +(6224,"2-25-1"), +(6225,"4-25-22"), +(6226,"4-25-23"), +(6227,"2-25-2"), +(6228,"4-25-24"), +(6229,"4-25-25"), +(6230,"4-25-26"), +(6231,"4-25-27"), +(6232,"4-25-28"), +(6233,"2-25-3"), +(6234,"4-25-29"), +(6235,"4-25-30"), +(6236,"2-25-4"), +(6237,"4-25-31"), +(6238,"4-25-32"), +(6239,"2-25-5"), +(6240,"4-25-33"), +(6241,"2-25-6"), +(6242,"4-25-34"), +(6243,"2-25-7"), +(6244,"4-25-35"), +(6245,"2-25-8"), +(6246,"4-25-36"), +(6247,"4-25-37"), +(6248,"2-25-9"), +(6249,"4-25-38"), +(6250,"2-25-10"), +(6251,"4-25-39"), +(6252,"2-25-11"), +(6253,"4-25-40"), +(6254,"4-25-41"), +(6255,"2-25-12"), +(6256,"4-25-42"), +(6257,"2-25-13"), +(6258,"4-25-43"), +(6259,"4-25-44"), +(6260,"2-25-14"), +(6261,"4-25-45"), +(6262,"2-25-15"), +(6263,"4-25-46"), +(6264,"2-25-16"), +(6265,"4-25-47"), +(6266,"2-25-17"), +(6267,"4-25-48"), +(6268,"4-25-49"), +(6269,"2-25-18"), +(6270,"2-25-19"), +(6271,"2-25-20"), +(6272,"2-25-21"), +(6273,"2-25-22"), +(6274,"2-25-23"), +(6275,"2-25-24"), +(6276,"2-25-25"), +(6277,"2-25-26"), +(6278,"2-25-27"), +(6279,"2-25-28"), +(6280,"2-25-29"), +(6281,"2-25-30"), +(6282,"2-25-31"), +(6283,"2-25-32"), +(6284,"2-25-33"), +(6285,"2-25-34"), +(6286,"2-25-35"), +(6287,"2-25-36"), +(6288,"2-25-37"), +(6289,"2-25-38"), +(6290,"2-25-39"), +(6291,"2-25-40"), +(6292,"2-25-41"), +(6293,"2-25-42"), +(6294,"2-25-43"), +(6295,"2-25-44"), +(6296,"2-25-45"), +(6297,"2-25-46"), +(6298,"2-25-47"), +(6299,"2-25-48"), +(6300,"2-25-49"), +(6301,"3-24-0"), +(6302,"3-24-1"), +(6303,"3-24-2"), +(6304,"3-24-3"), +(6305,"3-24-4"), +(6306,"3-24-5"), +(6307,"3-24-6"), +(6308,"3-24-7"), +(6309,"3-24-8"), +(6310,"3-24-9"), +(6311,"3-24-10"), +(6312,"3-24-11"), +(6313,"3-24-12"), +(6314,"3-24-13"), +(6315,"3-24-14"), +(6316,"3-24-15"), +(6317,"3-24-16"), +(6318,"3-24-17"), +(6319,"3-24-18"), +(6320,"3-24-19"), +(6321,"3-24-20"), +(6322,"3-24-21"), +(6323,"3-24-22"), +(6324,"3-24-23"), +(6325,"3-24-24"), +(6326,"3-24-25"), +(6327,"3-24-26"), +(6328,"3-24-27"), +(6329,"3-24-28"), +(6330,"3-24-29"), +(6331,"3-24-30"), +(6332,"3-24-31"), +(6333,"3-24-32"), +(6334,"3-24-33"), +(6335,"3-24-34"), +(6336,"3-24-35"), +(6337,"3-24-36"), +(6338,"3-24-37"), +(6339,"3-24-38"), +(6340,"3-24-39"), +(6341,"3-24-40"), +(6342,"3-24-41"), +(6343,"3-24-42"), +(6344,"3-24-43"), +(6345,"3-24-44"), +(6346,"3-24-45"), +(6347,"3-24-46"), +(6348,"3-24-47"), +(6349,"3-24-48"), +(6350,"3-24-49"), +(6351,"0-25-0"), +(6352,"0-25-1"), +(6353,"0-25-2"), +(6354,"0-25-3"), +(6355,"0-25-4"), +(6356,"0-25-5"), +(6357,"0-25-6"), +(6358,"0-25-7"), +(6359,"0-25-8"), +(6360,"0-25-9"), +(6361,"0-25-10"), +(6362,"0-25-11"), +(6363,"0-25-12"), +(6364,"0-25-13"), +(6365,"0-25-14"), +(6366,"0-25-15"), +(6367,"0-25-16"), +(6368,"0-25-17"), +(6369,"0-25-18"), +(6370,"0-25-19"), +(6371,"0-25-20"), +(6372,"0-25-21"), +(6373,"0-25-22"), +(6374,"0-25-23"), +(6375,"0-25-24"), +(6376,"0-25-25"), +(6377,"0-25-26"), +(6378,"0-25-27"), +(6379,"0-25-28"), +(6380,"0-25-29"), +(6381,"0-25-30"), +(6382,"0-25-31"), +(6383,"0-25-32"), +(6384,"0-25-33"), +(6385,"0-25-34"), +(6386,"0-25-35"), +(6387,"0-25-36"), +(6388,"0-25-37"), +(6389,"0-25-38"), +(6390,"0-25-39"), +(6391,"0-25-40"), +(6392,"0-25-41"), +(6393,"0-25-42"), +(6394,"0-25-43"), +(6395,"0-25-44"), +(6396,"0-25-45"), +(6397,"0-25-46"), +(6398,"0-25-47"), +(6399,"0-25-48"), +(6400,"0-25-49"), +(6401,"4-26-0"), +(6402,"4-26-1"), +(6403,"4-26-2"), +(6404,"4-26-3"), +(6405,"4-26-4"), +(6406,"4-26-5"), +(6407,"4-26-6"), +(6408,"4-26-7"), +(6409,"4-26-8"), +(6410,"4-26-9"), +(6411,"4-26-10"), +(6412,"4-26-11"), +(6413,"4-26-12"), +(6414,"4-26-13"), +(6415,"4-26-14"), +(6416,"4-26-15"), +(6417,"4-26-16"), +(6418,"4-26-17"), +(6419,"4-26-18"), +(6420,"4-26-19"), +(6421,"4-26-20"), +(6422,"4-26-21"), +(6423,"4-26-22"), +(6424,"4-26-23"), +(6425,"4-26-24"), +(6426,"4-26-25"), +(6427,"4-26-26"), +(6428,"4-26-27"), +(6429,"4-26-28"), +(6430,"4-26-29"), +(6431,"4-26-30"), +(6432,"4-26-31"), +(6433,"4-26-32"), +(6434,"4-26-33"), +(6435,"4-26-34"), +(6436,"4-26-35"), +(6437,"4-26-36"), +(6438,"4-26-37"), +(6439,"4-26-38"), +(6440,"4-26-39"), +(6441,"4-26-40"), +(6442,"4-26-41"), +(6443,"4-26-42"), +(6444,"4-26-43"), +(6445,"4-26-44"), +(6446,"4-26-45"), +(6447,"4-26-46"), +(6448,"4-26-47"), +(6449,"4-26-48"), +(6450,"4-26-49"), +(6451,"1-25-0"), +(6452,"1-25-1"), +(6453,"1-25-2"), +(6454,"1-25-3"), +(6455,"1-25-4"), +(6456,"1-25-5"), +(6457,"1-25-6"), +(6458,"1-25-7"), +(6459,"1-25-8"), +(6460,"1-25-9"), +(6461,"1-25-10"), +(6462,"1-25-11"), +(6463,"1-25-12"), +(6464,"1-25-13"), +(6465,"1-25-14"), +(6466,"1-25-15"), +(6467,"1-25-16"), +(6468,"1-25-17"), +(6469,"1-25-18"), +(6470,"1-25-19"), +(6471,"1-25-20"), +(6472,"1-25-21"), +(6473,"1-25-22"), +(6474,"1-25-23"), +(6475,"1-25-24"), +(6476,"1-25-25"), +(6477,"1-25-26"), +(6478,"1-25-27"), +(6479,"1-25-28"), +(6480,"1-25-29"), +(6481,"1-25-30"), +(6482,"1-25-31"), +(6483,"1-25-32"), +(6484,"1-25-33"), +(6485,"1-25-34"), +(6486,"1-25-35"), +(6487,"1-25-36"), +(6488,"1-25-37"), +(6489,"1-25-38"), +(6490,"1-25-39"), +(6491,"1-25-40"), +(6492,"1-25-41"), +(6493,"1-25-42"), +(6494,"1-25-43"), +(6495,"1-25-44"), +(6496,"1-25-45"), +(6497,"1-25-46"), +(6498,"1-25-47"), +(6499,"1-25-48"), +(6500,"1-25-49"), +(6501,"2-26-0"), +(6502,"2-26-1"), +(6503,"2-26-2"), +(6504,"2-26-3"), +(6505,"2-26-4"), +(6506,"2-26-5"), +(6507,"2-26-6"), +(6508,"2-26-7"), +(6509,"2-26-8"), +(6510,"2-26-9"), +(6511,"2-26-10"), +(6512,"2-26-11"), +(6513,"2-26-12"), +(6514,"2-26-13"), +(6515,"2-26-14"), +(6516,"2-26-15"), +(6517,"2-26-16"), +(6518,"2-26-17"), +(6519,"2-26-18"), +(6520,"2-26-19"), +(6521,"2-26-20"), +(6522,"2-26-21"), +(6523,"2-26-22"), +(6524,"2-26-23"), +(6525,"2-26-24"), +(6526,"2-26-25"), +(6527,"2-26-26"), +(6528,"2-26-27"), +(6529,"2-26-28"), +(6530,"2-26-29"), +(6531,"2-26-30"), +(6532,"2-26-31"), +(6533,"2-26-32"), +(6534,"2-26-33"), +(6535,"2-26-34"), +(6536,"2-26-35"), +(6537,"2-26-36"), +(6538,"2-26-37"), +(6539,"2-26-38"), +(6540,"2-26-39"), +(6541,"2-26-40"), +(6542,"2-26-41"), +(6543,"2-26-42"), +(6544,"2-26-43"), +(6545,"2-26-44"), +(6546,"2-26-45"), +(6547,"2-26-46"), +(6548,"2-26-47"), +(6549,"2-26-48"), +(6550,"2-26-49"), +(6551,"3-25-0"), +(6552,"3-25-1"), +(6553,"3-25-2"), +(6554,"3-25-3"), +(6555,"3-25-4"), +(6556,"3-25-5"), +(6557,"3-25-6"), +(6558,"3-25-7"), +(6559,"3-25-8"), +(6560,"3-25-9"), +(6561,"3-25-10"), +(6562,"3-25-11"), +(6563,"3-25-12"), +(6564,"3-25-13"), +(6565,"3-25-14"), +(6566,"3-25-15"), +(6567,"3-25-16"), +(6568,"3-25-17"), +(6569,"3-25-18"), +(6570,"3-25-19"), +(6571,"3-25-20"), +(6572,"3-25-21"), +(6573,"3-25-22"), +(6574,"3-25-23"), +(6575,"3-25-24"), +(6576,"3-25-25"), +(6577,"3-25-26"), +(6578,"3-25-27"), +(6579,"3-25-28"), +(6580,"3-25-29"), +(6581,"3-25-30"), +(6582,"3-25-31"), +(6583,"3-25-32"), +(6584,"3-25-33"), +(6585,"3-25-34"), +(6586,"3-25-35"), +(6587,"3-25-36"), +(6588,"3-25-37"), +(6589,"3-25-38"), +(6590,"3-25-39"), +(6591,"3-25-40"), +(6592,"3-25-41"), +(6593,"3-25-42"), +(6594,"3-25-43"), +(6595,"3-25-44"), +(6596,"3-25-45"), +(6597,"3-25-46"), +(6598,"3-25-47"), +(6599,"3-25-48"), +(6600,"3-25-49"), +(6601,"0-26-0"), +(6602,"0-26-1"), +(6603,"0-26-2"), +(6604,"0-26-3"), +(6605,"0-26-4"), +(6606,"0-26-5"), +(6607,"0-26-6"), +(6608,"0-26-7"), +(6609,"0-26-8"), +(6610,"0-26-9"), +(6611,"0-26-10"), +(6612,"0-26-11"), +(6613,"0-26-12"), +(6614,"0-26-13"), +(6615,"0-26-14"), +(6616,"0-26-15"), +(6617,"0-26-16"), +(6618,"0-26-17"), +(6619,"0-26-18"), +(6620,"0-26-19"), +(6621,"0-26-20"), +(6622,"0-26-21"), +(6623,"0-26-22"), +(6624,"0-26-23"), +(6625,"0-26-24"), +(6626,"0-26-25"), +(6627,"0-26-26"), +(6628,"0-26-27"), +(6629,"0-26-28"), +(6630,"0-26-29"), +(6631,"0-26-30"), +(6632,"0-26-31"), +(6633,"0-26-32"), +(6634,"0-26-33"), +(6635,"0-26-34"), +(6636,"0-26-35"), +(6637,"0-26-36"), +(6638,"0-26-37"), +(6639,"0-26-38"), +(6640,"0-26-39"), +(6641,"0-26-40"), +(6642,"0-26-41"), +(6643,"0-26-42"), +(6644,"0-26-43"), +(6645,"0-26-44"), +(6646,"0-26-45"), +(6647,"0-26-46"), +(6648,"0-26-47"), +(6649,"0-26-48"), +(6650,"0-26-49"), +(6651,"4-27-0"), +(6652,"4-27-1"), +(6653,"4-27-2"), +(6654,"4-27-3"), +(6655,"4-27-4"), +(6656,"4-27-5"), +(6657,"4-27-6"), +(6658,"4-27-7"), +(6659,"4-27-8"), +(6660,"4-27-9"), +(6661,"4-27-10"), +(6662,"4-27-11"), +(6663,"4-27-12"), +(6664,"4-27-13"), +(6665,"4-27-14"), +(6666,"4-27-15"), +(6667,"4-27-16"), +(6668,"4-27-17"), +(6669,"4-27-18"), +(6670,"4-27-19"), +(6671,"4-27-20"), +(6672,"4-27-21"), +(6673,"4-27-22"), +(6674,"4-27-23"), +(6675,"4-27-24"), +(6676,"4-27-25"), +(6677,"4-27-26"), +(6678,"4-27-27"), +(6679,"4-27-28"), +(6680,"4-27-29"), +(6681,"4-27-30"), +(6682,"4-27-31"), +(6683,"4-27-32"), +(6684,"4-27-33"), +(6685,"4-27-34"), +(6686,"4-27-35"), +(6687,"4-27-36"), +(6688,"4-27-37"), +(6689,"4-27-38"), +(6690,"4-27-39"), +(6691,"4-27-40"), +(6692,"4-27-41"), +(6693,"4-27-42"), +(6694,"4-27-43"), +(6695,"4-27-44"), +(6696,"4-27-45"), +(6697,"4-27-46"), +(6698,"4-27-47"), +(6699,"4-27-48"), +(6700,"4-27-49"), +(6701,"1-26-0"), +(6702,"1-26-1"), +(6703,"1-26-2"), +(6704,"1-26-3"), +(6705,"1-26-4"), +(6706,"1-26-5"), +(6707,"1-26-6"), +(6708,"1-26-7"), +(6709,"1-26-8"), +(6710,"1-26-9"), +(6711,"1-26-10"), +(6712,"1-26-11"), +(6713,"1-26-12"), +(6714,"1-26-13"), +(6715,"1-26-14"), +(6716,"1-26-15"), +(6717,"1-26-16"), +(6718,"1-26-17"), +(6719,"1-26-18"), +(6720,"1-26-19"), +(6721,"1-26-20"), +(6722,"1-26-21"), +(6723,"1-26-22"), +(6724,"1-26-23"), +(6725,"1-26-24"), +(6726,"1-26-25"), +(6727,"1-26-26"), +(6728,"1-26-27"), +(6729,"1-26-28"), +(6730,"1-26-29"), +(6731,"1-26-30"), +(6732,"1-26-31"), +(6733,"1-26-32"), +(6734,"1-26-33"), +(6735,"1-26-34"), +(6736,"1-26-35"), +(6737,"1-26-36"), +(6738,"1-26-37"), +(6739,"1-26-38"), +(6740,"1-26-39"), +(6741,"1-26-40"), +(6742,"1-26-41"), +(6743,"1-26-42"), +(6744,"1-26-43"), +(6745,"1-26-44"), +(6746,"1-26-45"), +(6747,"1-26-46"), +(6748,"1-26-47"), +(6749,"1-26-48"), +(6750,"1-26-49"), +(6751,"2-27-0"), +(6752,"2-27-1"), +(6753,"2-27-2"), +(6754,"2-27-3"), +(6755,"2-27-4"), +(6756,"2-27-5"), +(6757,"2-27-6"), +(6758,"2-27-7"), +(6759,"2-27-8"), +(6760,"2-27-9"), +(6761,"2-27-10"), +(6762,"2-27-11"), +(6763,"2-27-12"), +(6764,"2-27-13"), +(6765,"2-27-14"), +(6766,"2-27-15"), +(6767,"2-27-16"), +(6768,"2-27-17"), +(6769,"2-27-18"), +(6770,"2-27-19"), +(6771,"2-27-20"), +(6772,"2-27-21"), +(6773,"2-27-22"), +(6774,"2-27-23"), +(6775,"2-27-24"), +(6776,"2-27-25"), +(6777,"2-27-26"), +(6778,"2-27-27"), +(6779,"2-27-28"), +(6780,"2-27-29"), +(6781,"2-27-30"), +(6782,"2-27-31"), +(6783,"2-27-32"), +(6784,"2-27-33"), +(6785,"2-27-34"), +(6786,"2-27-35"), +(6787,"2-27-36"), +(6788,"2-27-37"), +(6789,"2-27-38"), +(6790,"2-27-39"), +(6791,"2-27-40"), +(6792,"2-27-41"), +(6793,"2-27-42"), +(6794,"2-27-43"), +(6795,"2-27-44"), +(6796,"2-27-45"), +(6797,"2-27-46"), +(6798,"2-27-47"), +(6799,"2-27-48"), +(6800,"2-27-49"), +(6801,"3-26-0"), +(6802,"3-26-1"), +(6803,"3-26-2"), +(6804,"3-26-3"), +(6805,"3-26-4"), +(6806,"3-26-5"), +(6807,"3-26-6"), +(6808,"3-26-7"), +(6809,"3-26-8"), +(6810,"3-26-9"), +(6811,"3-26-10"), +(6812,"3-26-11"), +(6813,"3-26-12"), +(6814,"3-26-13"), +(6815,"3-26-14"), +(6816,"3-26-15"), +(6817,"3-26-16"), +(6818,"3-26-17"), +(6819,"3-26-18"), +(6820,"3-26-19"), +(6821,"3-26-20"), +(6822,"3-26-21"), +(6823,"3-26-22"), +(6824,"3-26-23"), +(6825,"3-26-24"), +(6826,"3-26-25"), +(6827,"3-26-26"), +(6828,"3-26-27"), +(6829,"3-26-28"), +(6830,"3-26-29"), +(6831,"3-26-30"), +(6832,"3-26-31"), +(6833,"3-26-32"), +(6834,"3-26-33"), +(6835,"3-26-34"), +(6836,"3-26-35"), +(6837,"3-26-36"), +(6838,"3-26-37"), +(6839,"3-26-38"), +(6840,"3-26-39"), +(6841,"3-26-40"), +(6842,"3-26-41"), +(6843,"3-26-42"), +(6844,"3-26-43"), +(6845,"3-26-44"), +(6846,"3-26-45"), +(6847,"3-26-46"), +(6848,"3-26-47"), +(6849,"3-26-48"), +(6850,"3-26-49"), +(6851,"4-28-0"), +(6852,"4-28-1"), +(6853,"4-28-2"), +(6854,"4-28-3"), +(6855,"4-28-4"), +(6856,"4-28-5"), +(6857,"4-28-6"), +(6858,"4-28-7"), +(6859,"4-28-8"), +(6860,"4-28-9"), +(6861,"4-28-10"), +(6862,"4-28-11"), +(6863,"4-28-12"), +(6864,"4-28-13"), +(6865,"4-28-14"), +(6866,"4-28-15"), +(6867,"4-28-16"), +(6868,"4-28-17"), +(6869,"4-28-18"), +(6870,"4-28-19"), +(6871,"4-28-20"), +(6872,"4-28-21"), +(6873,"4-28-22"), +(6874,"4-28-23"), +(6875,"4-28-24"), +(6876,"4-28-25"), +(6877,"4-28-26"), +(6878,"4-28-27"), +(6879,"4-28-28"), +(6880,"4-28-29"), +(6881,"4-28-30"), +(6882,"4-28-31"), +(6883,"4-28-32"), +(6884,"4-28-33"), +(6885,"4-28-34"), +(6886,"4-28-35"), +(6887,"4-28-36"), +(6888,"4-28-37"), +(6889,"4-28-38"), +(6890,"4-28-39"), +(6891,"4-28-40"), +(6892,"4-28-41"), +(6893,"4-28-42"), +(6894,"4-28-43"), +(6895,"4-28-44"), +(6896,"4-28-45"), +(6897,"4-28-46"), +(6898,"4-28-47"), +(6899,"4-28-48"), +(6900,"4-28-49"), +(6901,"0-27-0"), +(6902,"0-27-1"), +(6903,"0-27-2"), +(6904,"0-27-3"), +(6905,"0-27-4"), +(6906,"0-27-5"), +(6907,"0-27-6"), +(6908,"0-27-7"), +(6909,"0-27-8"), +(6910,"0-27-9"), +(6911,"0-27-10"), +(6912,"0-27-11"), +(6913,"0-27-12"), +(6914,"0-27-13"), +(6915,"0-27-14"), +(6916,"0-27-15"), +(6917,"0-27-16"), +(6918,"0-27-17"), +(6919,"0-27-18"), +(6920,"0-27-19"), +(6921,"0-27-20"), +(6922,"0-27-21"), +(6923,"0-27-22"), +(6924,"0-27-23"), +(6925,"0-27-24"), +(6926,"0-27-25"), +(6927,"0-27-26"), +(6928,"0-27-27"), +(6929,"0-27-28"), +(6930,"0-27-29"), +(6931,"0-27-30"), +(6932,"0-27-31"), +(6933,"0-27-32"), +(6934,"0-27-33"), +(6935,"0-27-34"), +(6936,"0-27-35"), +(6937,"0-27-36"), +(6938,"0-27-37"), +(6939,"0-27-38"), +(6940,"0-27-39"), +(6941,"0-27-40"), +(6942,"0-27-41"), +(6943,"0-27-42"), +(6944,"0-27-43"), +(6945,"0-27-44"), +(6946,"0-27-45"), +(6947,"0-27-46"), +(6948,"0-27-47"), +(6949,"0-27-48"), +(6950,"0-27-49"), +(6951,"1-27-0"), +(6952,"1-27-1"), +(6953,"1-27-2"), +(6954,"1-27-3"), +(6955,"1-27-4"), +(6956,"1-27-5"), +(6957,"1-27-6"), +(6958,"1-27-7"), +(6959,"1-27-8"), +(6960,"1-27-9"), +(6961,"1-27-10"), +(6962,"1-27-11"), +(6963,"1-27-12"), +(6964,"1-27-13"), +(6965,"1-27-14"), +(6966,"1-27-15"), +(6967,"1-27-16"), +(6968,"1-27-17"), +(6969,"1-27-18"), +(6970,"1-27-19"), +(6971,"1-27-20"), +(6972,"1-27-21"), +(6973,"1-27-22"), +(6974,"1-27-23"), +(6975,"1-27-24"), +(6976,"1-27-25"), +(6977,"1-27-26"), +(6978,"1-27-27"), +(6979,"1-27-28"), +(6980,"1-27-29"), +(6981,"1-27-30"), +(6982,"1-27-31"), +(6983,"1-27-32"), +(6984,"1-27-33"), +(6985,"1-27-34"), +(6986,"1-27-35"), +(6987,"1-27-36"), +(6988,"1-27-37"), +(6989,"1-27-38"), +(6990,"1-27-39"), +(6991,"1-27-40"), +(6992,"1-27-41"), +(6993,"1-27-42"), +(6994,"1-27-43"), +(6995,"1-27-44"), +(6996,"1-27-45"), +(6997,"1-27-46"), +(6998,"1-27-47"), +(6999,"1-27-48"), +(7000,"1-27-49"), +(7001,"2-28-0"), +(7002,"2-28-1"), +(7003,"2-28-2"), +(7004,"2-28-3"), +(7005,"2-28-4"), +(7006,"2-28-5"), +(7007,"2-28-6"), +(7008,"2-28-7"), +(7009,"2-28-8"), +(7010,"2-28-9"), +(7011,"2-28-10"), +(7012,"2-28-11"), +(7013,"2-28-12"), +(7014,"2-28-13"), +(7015,"2-28-14"), +(7016,"2-28-15"), +(7017,"2-28-16"), +(7018,"2-28-17"), +(7019,"2-28-18"), +(7020,"2-28-19"), +(7021,"2-28-20"), +(7022,"2-28-21"), +(7023,"2-28-22"), +(7024,"2-28-23"), +(7025,"2-28-24"), +(7026,"2-28-25"), +(7027,"2-28-26"), +(7028,"2-28-27"), +(7029,"2-28-28"), +(7030,"2-28-29"), +(7031,"2-28-30"), +(7032,"2-28-31"), +(7033,"2-28-32"), +(7034,"2-28-33"), +(7035,"2-28-34"), +(7036,"2-28-35"), +(7037,"2-28-36"), +(7038,"2-28-37"), +(7039,"2-28-38"), +(7040,"2-28-39"), +(7041,"2-28-40"), +(7042,"2-28-41"), +(7043,"2-28-42"), +(7044,"2-28-43"), +(7045,"2-28-44"), +(7046,"2-28-45"), +(7047,"2-28-46"), +(7048,"2-28-47"), +(7049,"2-28-48"), +(7050,"2-28-49"), +(7051,"3-27-0"), +(7052,"3-27-1"), +(7053,"3-27-2"), +(7054,"3-27-3"), +(7055,"3-27-4"), +(7056,"3-27-5"), +(7057,"3-27-6"), +(7058,"3-27-7"), +(7059,"3-27-8"), +(7060,"3-27-9"), +(7061,"3-27-10"), +(7062,"3-27-11"), +(7063,"3-27-12"), +(7064,"3-27-13"), +(7065,"3-27-14"), +(7066,"3-27-15"), +(7067,"3-27-16"), +(7068,"3-27-17"), +(7069,"3-27-18"), +(7070,"3-27-19"), +(7071,"3-27-20"), +(7072,"3-27-21"), +(7073,"3-27-22"), +(7074,"3-27-23"), +(7075,"3-27-24"), +(7076,"3-27-25"), +(7077,"3-27-26"), +(7078,"3-27-27"), +(7079,"3-27-28"), +(7080,"3-27-29"), +(7081,"3-27-30"), +(7082,"3-27-31"), +(7083,"3-27-32"), +(7084,"3-27-33"), +(7085,"3-27-34"), +(7086,"3-27-35"), +(7087,"3-27-36"), +(7088,"3-27-37"), +(7089,"3-27-38"), +(7090,"3-27-39"), +(7091,"3-27-40"), +(7092,"3-27-41"), +(7093,"3-27-42"), +(7094,"3-27-43"), +(7095,"3-27-44"), +(7096,"3-27-45"), +(7097,"3-27-46"), +(7098,"3-27-47"), +(7099,"3-27-48"), +(7100,"3-27-49"), +(7101,"4-29-0"), +(7102,"4-29-1"), +(7103,"4-29-2"), +(7104,"4-29-3"), +(7105,"4-29-4"), +(7106,"4-29-5"), +(7107,"4-29-6"), +(7108,"4-29-7"), +(7109,"4-29-8"), +(7110,"4-29-9"), +(7111,"4-29-10"), +(7112,"4-29-11"), +(7113,"4-29-12"), +(7114,"4-29-13"), +(7115,"4-29-14"), +(7116,"4-29-15"), +(7117,"4-29-16"), +(7118,"4-29-17"), +(7119,"4-29-18"), +(7120,"4-29-19"), +(7121,"4-29-20"), +(7122,"4-29-21"), +(7123,"4-29-22"), +(7124,"4-29-23"), +(7125,"4-29-24"), +(7126,"4-29-25"), +(7127,"4-29-26"), +(7128,"4-29-27"), +(7129,"4-29-28"), +(7130,"4-29-29"), +(7131,"4-29-30"), +(7132,"4-29-31"), +(7133,"4-29-32"), +(7134,"4-29-33"), +(7135,"4-29-34"), +(7136,"4-29-35"), +(7137,"4-29-36"), +(7138,"4-29-37"), +(7139,"4-29-38"), +(7140,"4-29-39"), +(7141,"4-29-40"), +(7142,"4-29-41"), +(7143,"4-29-42"), +(7144,"4-29-43"), +(7145,"4-29-44"), +(7146,"4-29-45"), +(7147,"4-29-46"), +(7148,"4-29-47"), +(7149,"4-29-48"), +(7150,"4-29-49"), +(7151,"0-28-0"), +(7152,"0-28-1"), +(7153,"0-28-2"), +(7154,"0-28-3"), +(7155,"0-28-4"), +(7156,"0-28-5"), +(7157,"0-28-6"), +(7158,"0-28-7"), +(7159,"0-28-8"), +(7160,"0-28-9"), +(7161,"0-28-10"), +(7162,"0-28-11"), +(7163,"0-28-12"), +(7164,"0-28-13"), +(7165,"0-28-14"), +(7166,"0-28-15"), +(7167,"0-28-16"), +(7168,"0-28-17"), +(7169,"0-28-18"), +(7170,"0-28-19"), +(7171,"0-28-20"), +(7172,"0-28-21"), +(7173,"0-28-22"), +(7174,"0-28-23"), +(7175,"0-28-24"), +(7176,"0-28-25"), +(7177,"0-28-26"), +(7178,"0-28-27"), +(7179,"0-28-28"), +(7180,"0-28-29"), +(7181,"0-28-30"), +(7182,"0-28-31"), +(7183,"0-28-32"), +(7184,"0-28-33"), +(7185,"0-28-34"), +(7186,"0-28-35"), +(7187,"0-28-36"), +(7188,"0-28-37"), +(7189,"0-28-38"), +(7190,"0-28-39"), +(7191,"0-28-40"), +(7192,"0-28-41"), +(7193,"0-28-42"), +(7194,"0-28-43"), +(7195,"0-28-44"), +(7196,"0-28-45"), +(7197,"0-28-46"), +(7198,"0-28-47"), +(7199,"0-28-48"), +(7200,"0-28-49"), +(7201,"1-28-0"), +(7202,"1-28-1"), +(7203,"1-28-2"), +(7204,"1-28-3"), +(7205,"1-28-4"), +(7206,"1-28-5"), +(7207,"1-28-6"), +(7208,"1-28-7"), +(7209,"1-28-8"), +(7210,"1-28-9"), +(7211,"1-28-10"), +(7212,"1-28-11"), +(7213,"1-28-12"), +(7214,"1-28-13"), +(7215,"1-28-14"), +(7216,"1-28-15"), +(7217,"1-28-16"), +(7218,"1-28-17"), +(7219,"1-28-18"), +(7220,"1-28-19"), +(7221,"1-28-20"), +(7222,"1-28-21"), +(7223,"1-28-22"), +(7224,"1-28-23"), +(7225,"1-28-24"), +(7226,"1-28-25"), +(7227,"1-28-26"), +(7228,"1-28-27"), +(7229,"1-28-28"), +(7230,"1-28-29"), +(7231,"1-28-30"), +(7232,"1-28-31"), +(7233,"1-28-32"), +(7234,"1-28-33"), +(7235,"1-28-34"), +(7236,"1-28-35"), +(7237,"1-28-36"), +(7238,"1-28-37"), +(7239,"1-28-38"), +(7240,"1-28-39"), +(7241,"1-28-40"), +(7242,"1-28-41"), +(7243,"1-28-42"), +(7244,"1-28-43"), +(7245,"1-28-44"), +(7246,"1-28-45"), +(7247,"1-28-46"), +(7248,"1-28-47"), +(7249,"1-28-48"), +(7250,"1-28-49"), +(7251,"2-29-0"), +(7252,"2-29-1"), +(7253,"2-29-2"), +(7254,"2-29-3"), +(7255,"2-29-4"), +(7256,"2-29-5"), +(7257,"2-29-6"), +(7258,"2-29-7"), +(7259,"2-29-8"), +(7260,"2-29-9"), +(7261,"2-29-10"), +(7262,"2-29-11"), +(7263,"2-29-12"), +(7264,"2-29-13"), +(7265,"2-29-14"), +(7266,"2-29-15"), +(7267,"2-29-16"), +(7268,"2-29-17"), +(7269,"2-29-18"), +(7270,"2-29-19"), +(7271,"2-29-20"), +(7272,"2-29-21"), +(7273,"2-29-22"), +(7274,"2-29-23"), +(7275,"2-29-24"), +(7276,"2-29-25"), +(7277,"2-29-26"), +(7278,"2-29-27"), +(7279,"2-29-28"), +(7280,"2-29-29"), +(7281,"2-29-30"), +(7282,"2-29-31"), +(7283,"2-29-32"), +(7284,"2-29-33"), +(7285,"2-29-34"), +(7286,"2-29-35"), +(7287,"2-29-36"), +(7288,"2-29-37"), +(7289,"2-29-38"), +(7290,"2-29-39"), +(7291,"2-29-40"), +(7292,"2-29-41"), +(7293,"2-29-42"), +(7294,"2-29-43"), +(7295,"2-29-44"), +(7296,"2-29-45"), +(7297,"2-29-46"), +(7298,"2-29-47"), +(7299,"2-29-48"), +(7300,"2-29-49"), +(7301,"3-28-0"), +(7302,"3-28-1"), +(7303,"3-28-2"), +(7304,"3-28-3"), +(7305,"3-28-4"), +(7306,"3-28-5"), +(7307,"3-28-6"), +(7308,"3-28-7"), +(7309,"3-28-8"), +(7310,"3-28-9"), +(7311,"3-28-10"), +(7312,"3-28-11"), +(7313,"3-28-12"), +(7314,"3-28-13"), +(7315,"3-28-14"), +(7316,"3-28-15"), +(7317,"3-28-16"), +(7318,"3-28-17"), +(7319,"3-28-18"), +(7320,"3-28-19"), +(7321,"3-28-20"), +(7322,"3-28-21"), +(7323,"3-28-22"), +(7324,"3-28-23"), +(7325,"3-28-24"), +(7326,"3-28-25"), +(7327,"3-28-26"), +(7328,"3-28-27"), +(7329,"3-28-28"), +(7330,"3-28-29"), +(7331,"3-28-30"), +(7332,"3-28-31"), +(7333,"3-28-32"), +(7334,"3-28-33"), +(7335,"3-28-34"), +(7336,"3-28-35"), +(7337,"3-28-36"), +(7338,"3-28-37"), +(7339,"3-28-38"), +(7340,"3-28-39"), +(7341,"3-28-40"), +(7342,"3-28-41"), +(7343,"3-28-42"), +(7344,"3-28-43"), +(7345,"3-28-44"), +(7346,"3-28-45"), +(7347,"3-28-46"), +(7348,"3-28-47"), +(7349,"3-28-48"), +(7350,"3-28-49"), +(7351,"0-29-0"), +(7352,"0-29-1"), +(7353,"0-29-2"), +(7354,"0-29-3"), +(7355,"0-29-4"), +(7356,"0-29-5"), +(7357,"0-29-6"), +(7358,"0-29-7"), +(7359,"0-29-8"), +(7360,"0-29-9"), +(7361,"0-29-10"), +(7362,"0-29-11"), +(7363,"0-29-12"), +(7364,"0-29-13"), +(7365,"0-29-14"), +(7366,"0-29-15"), +(7367,"0-29-16"), +(7368,"0-29-17"), +(7369,"0-29-18"), +(7370,"0-29-19"), +(7371,"0-29-20"), +(7372,"0-29-21"), +(7373,"0-29-22"), +(7374,"0-29-23"), +(7375,"0-29-24"), +(7376,"0-29-25"), +(7377,"0-29-26"), +(7378,"0-29-27"), +(7379,"0-29-28"), +(7380,"0-29-29"), +(7381,"0-29-30"), +(7382,"0-29-31"), +(7383,"0-29-32"), +(7384,"0-29-33"), +(7385,"0-29-34"), +(7386,"0-29-35"), +(7387,"0-29-36"), +(7388,"0-29-37"), +(7389,"0-29-38"), +(7390,"0-29-39"), +(7391,"0-29-40"), +(7392,"0-29-41"), +(7393,"0-29-42"), +(7394,"0-29-43"), +(7395,"0-29-44"), +(7396,"0-29-45"), +(7397,"0-29-46"), +(7398,"0-29-47"), +(7399,"0-29-48"), +(7400,"0-29-49"), +(7401,"4-30-0"), +(7402,"4-30-1"), +(7403,"4-30-2"), +(7404,"4-30-3"), +(7405,"4-30-4"), +(7406,"4-30-5"), +(7407,"4-30-6"), +(7408,"4-30-7"), +(7409,"4-30-8"), +(7410,"4-30-9"), +(7411,"4-30-10"), +(7412,"4-30-11"), +(7413,"4-30-12"), +(7414,"4-30-13"), +(7415,"4-30-14"), +(7416,"4-30-15"), +(7417,"4-30-16"), +(7418,"4-30-17"), +(7419,"4-30-18"), +(7420,"4-30-19"), +(7421,"4-30-20"), +(7422,"4-30-21"), +(7423,"4-30-22"), +(7424,"4-30-23"), +(7425,"4-30-24"), +(7426,"4-30-25"), +(7427,"4-30-26"), +(7428,"4-30-27"), +(7429,"4-30-28"), +(7430,"4-30-29"), +(7431,"4-30-30"), +(7432,"4-30-31"), +(7433,"4-30-32"), +(7434,"4-30-33"), +(7435,"4-30-34"), +(7436,"4-30-35"), +(7437,"4-30-36"), +(7438,"4-30-37"), +(7439,"4-30-38"), +(7440,"4-30-39"), +(7441,"4-30-40"), +(7442,"4-30-41"), +(7443,"4-30-42"), +(7444,"4-30-43"), +(7445,"4-30-44"), +(7446,"4-30-45"), +(7447,"4-30-46"), +(7448,"4-30-47"), +(7449,"4-30-48"), +(7450,"4-30-49"), +(7451,"0-30-0"), +(7452,"0-30-1"), +(7453,"0-30-2"), +(7454,"0-30-3"), +(7455,"0-30-4"), +(7456,"0-30-5"), +(7457,"0-30-6"), +(7458,"0-30-7"), +(7459,"0-30-8"), +(7460,"0-30-9"), +(7461,"0-30-10"), +(7462,"0-30-11"), +(7463,"0-30-12"), +(7464,"0-30-13"), +(7465,"0-30-14"), +(7466,"0-30-15"), +(7467,"0-30-16"), +(7468,"0-30-17"), +(7469,"0-30-18"), +(7470,"4-31-0"), +(7471,"0-30-19"), +(7472,"0-30-20"), +(7473,"0-30-21"), +(7474,"4-31-1"), +(7475,"0-30-22"), +(7476,"0-30-23"), +(7477,"4-31-2"), +(7478,"0-30-24"), +(7479,"0-30-25"), +(7480,"4-31-3"), +(7481,"0-30-26"), +(7482,"4-31-4"), +(7483,"0-30-27"), +(7484,"0-30-28"), +(7485,"4-31-5"), +(7486,"0-30-29"), +(7487,"4-31-6"), +(7488,"0-30-30"), +(7489,"0-30-31"), +(7490,"4-31-7"), +(7491,"0-30-32"), +(7492,"0-30-33"), +(7493,"4-31-8"), +(7494,"0-30-34"), +(7495,"4-31-9"), +(7496,"0-30-35"), +(7497,"0-30-36"), +(7498,"4-31-10"), +(7499,"0-30-37"), +(7500,"4-31-11"), +(7501,"0-30-38"), +(7502,"0-30-39"), +(7503,"4-31-12"), +(7504,"0-30-40"), +(7505,"0-30-41"), +(7506,"4-31-13"), +(7507,"0-30-42"), +(7508,"4-31-14"), +(7509,"0-30-43"), +(7510,"0-30-44"), +(7511,"4-31-15"), +(7512,"0-30-45"), +(7513,"4-31-16"), +(7514,"0-30-46"), +(7515,"0-30-47"), +(7516,"4-31-17"), +(7517,"2-30-0"), +(7518,"0-30-48"), +(7519,"4-31-18"), +(7520,"0-30-49"), +(7521,"4-31-19"), +(7522,"2-30-1"), +(7523,"4-31-20"), +(7524,"2-30-2"), +(7525,"4-31-21"), +(7526,"2-30-3"), +(7527,"4-31-22"), +(7528,"2-30-4"), +(7529,"4-31-23"), +(7530,"2-30-5"), +(7531,"4-31-24"), +(7532,"2-30-6"), +(7533,"4-31-25"), +(7534,"2-30-7"), +(7535,"4-31-26"), +(7536,"2-30-8"), +(7537,"2-30-9"), +(7538,"4-31-27"), +(7539,"2-30-10"), +(7540,"2-30-11"), +(7541,"4-31-28"), +(7542,"2-30-12"), +(7543,"2-30-13"), +(7544,"4-31-29"), +(7545,"2-30-14"), +(7546,"2-30-15"), +(7547,"4-31-30"), +(7548,"2-30-16"), +(7549,"2-30-17"), +(7550,"4-31-31"), +(7551,"4-31-32"), +(7552,"2-30-18"), +(7553,"4-31-33"), +(7554,"2-30-19"), +(7555,"2-30-20"), +(7556,"4-31-34"), +(7557,"2-30-21"), +(7558,"2-30-22"), +(7559,"4-31-35"), +(7560,"2-30-23"), +(7561,"2-30-24"), +(7562,"4-31-36"), +(7563,"2-30-25"), +(7564,"2-30-26"), +(7565,"4-31-37"), +(7566,"2-30-27"), +(7567,"4-31-38"), +(7568,"2-30-28"), +(7569,"2-30-29"), +(7570,"4-31-39"), +(7571,"2-30-30"), +(7572,"4-31-40"), +(7573,"2-30-31"), +(7574,"4-31-41"), +(7575,"2-30-32"), +(7576,"4-31-42"), +(7577,"2-30-33"), +(7578,"4-31-43"), +(7579,"2-30-34"), +(7580,"4-31-44"), +(7581,"2-30-35"), +(7582,"4-31-45"), +(7583,"2-30-36"), +(7584,"4-31-46"), +(7585,"2-30-37"), +(7586,"4-31-47"), +(7587,"2-30-38"), +(7588,"4-31-48"), +(7589,"2-30-39"), +(7590,"4-31-49"), +(7591,"2-30-40"), +(7592,"2-30-41"), +(7593,"2-30-42"), +(7594,"2-30-43"), +(7595,"2-30-44"), +(7596,"2-30-45"), +(7597,"2-30-46"), +(7598,"2-30-47"), +(7599,"2-30-48"), +(7600,"2-30-49"), +(7601,"3-29-0"), +(7602,"1-29-0"), +(7603,"1-29-1"), +(7604,"3-29-1"), +(7605,"1-29-2"), +(7606,"3-29-2"), +(7607,"1-29-3"), +(7608,"3-29-3"), +(7609,"1-29-4"), +(7610,"3-29-4"), +(7611,"1-29-5"), +(7612,"3-29-5"), +(7613,"1-29-6"), +(7614,"3-29-6"), +(7615,"1-29-7"), +(7616,"3-29-7"), +(7617,"1-29-8"), +(7618,"3-29-8"), +(7619,"1-29-9"), +(7620,"3-29-9"), +(7621,"1-29-10"), +(7622,"3-29-10"), +(7623,"1-29-11"), +(7624,"3-29-11"), +(7625,"1-29-12"), +(7626,"3-29-12"), +(7627,"1-29-13"), +(7628,"3-29-13"), +(7629,"1-29-14"), +(7630,"3-29-14"), +(7631,"1-29-15"), +(7632,"3-29-15"), +(7633,"1-29-16"), +(7634,"3-29-16"), +(7635,"1-29-17"), +(7636,"3-29-17"), +(7637,"1-29-18"), +(7638,"3-29-18"), +(7639,"1-29-19"), +(7640,"3-29-19"), +(7641,"1-29-20"), +(7642,"3-29-20"), +(7643,"1-29-21"), +(7644,"3-29-21"), +(7645,"1-29-22"), +(7646,"3-29-22"), +(7647,"1-29-23"), +(7648,"3-29-23"), +(7649,"1-29-24"), +(7650,"3-29-24"), +(7651,"1-29-25"), +(7652,"3-29-25"), +(7653,"1-29-26"), +(7654,"3-29-26"), +(7655,"1-29-27"), +(7656,"3-29-27"), +(7657,"1-29-28"), +(7658,"3-29-28"), +(7659,"1-29-29"), +(7660,"3-29-29"), +(7661,"1-29-30"), +(7662,"3-29-30"), +(7663,"1-29-31"), +(7664,"3-29-31"), +(7665,"1-29-32"), +(7666,"3-29-32"), +(7667,"1-29-33"), +(7668,"3-29-33"), +(7669,"1-29-34"), +(7670,"3-29-34"), +(7671,"1-29-35"), +(7672,"3-29-35"), +(7673,"1-29-36"), +(7674,"3-29-36"), +(7675,"1-29-37"), +(7676,"3-29-37"), +(7677,"1-29-38"), +(7678,"3-29-38"), +(7679,"1-29-39"), +(7680,"3-29-39"), +(7681,"1-29-40"), +(7682,"3-29-40"), +(7683,"1-29-41"), +(7684,"3-29-41"), +(7685,"1-29-42"), +(7686,"3-29-42"), +(7687,"1-29-43"), +(7688,"3-29-43"), +(7689,"1-29-44"), +(7690,"3-29-44"), +(7691,"1-29-45"), +(7692,"3-29-45"), +(7693,"1-29-46"), +(7694,"3-29-46"), +(7695,"1-29-47"), +(7696,"3-29-47"), +(7697,"1-29-48"), +(7698,"3-29-48"), +(7699,"1-29-49"), +(7700,"3-29-49"), +(7701,"0-31-0"), +(7702,"0-31-1"), +(7703,"0-31-2"), +(7704,"0-31-3"), +(7705,"0-31-4"), +(7706,"0-31-5"), +(7707,"0-31-6"), +(7708,"0-31-7"), +(7709,"0-31-8"), +(7710,"0-31-9"), +(7711,"0-31-10"), +(7712,"0-31-11"), +(7713,"0-31-12"), +(7714,"0-31-13"), +(7715,"0-31-14"), +(7716,"0-31-15"), +(7717,"0-31-16"), +(7718,"0-31-17"), +(7719,"0-31-18"), +(7720,"0-31-19"), +(7721,"0-31-20"), +(7722,"0-31-21"), +(7723,"0-31-22"), +(7724,"0-31-23"), +(7725,"0-31-24"), +(7726,"0-31-25"), +(7727,"0-31-26"), +(7728,"0-31-27"), +(7729,"0-31-28"), +(7730,"0-31-29"), +(7731,"0-31-30"), +(7732,"0-31-31"), +(7733,"0-31-32"), +(7734,"0-31-33"), +(7735,"0-31-34"), +(7736,"0-31-35"), +(7737,"0-31-36"), +(7738,"0-31-37"), +(7739,"0-31-38"), +(7740,"0-31-39"), +(7741,"0-31-40"), +(7742,"0-31-41"), +(7743,"0-31-42"), +(7744,"0-31-43"), +(7745,"0-31-44"), +(7746,"0-31-45"), +(7747,"0-31-46"), +(7748,"0-31-47"), +(7749,"0-31-48"), +(7750,"0-31-49"), +(7751,"1-30-0"), +(7752,"1-30-1"), +(7753,"1-30-2"), +(7754,"1-30-3"), +(7755,"1-30-4"), +(7756,"1-30-5"), +(7757,"1-30-6"), +(7758,"1-30-7"), +(7759,"1-30-8"), +(7760,"1-30-9"), +(7761,"1-30-10"), +(7762,"1-30-11"), +(7763,"1-30-12"), +(7764,"1-30-13"), +(7765,"1-30-14"), +(7766,"1-30-15"), +(7767,"1-30-16"), +(7768,"1-30-17"), +(7769,"1-30-18"), +(7770,"1-30-19"), +(7771,"1-30-20"), +(7772,"1-30-21"), +(7773,"1-30-22"), +(7774,"1-30-23"), +(7775,"1-30-24"), +(7776,"1-30-25"), +(7777,"1-30-26"), +(7778,"1-30-27"), +(7779,"1-30-28"), +(7780,"1-30-29"), +(7781,"1-30-30"), +(7782,"1-30-31"), +(7783,"1-30-32"), +(7784,"1-30-33"), +(7785,"1-30-34"), +(7786,"1-30-35"), +(7787,"1-30-36"), +(7788,"1-30-37"), +(7789,"1-30-38"), +(7790,"1-30-39"), +(7791,"1-30-40"), +(7792,"1-30-41"), +(7793,"1-30-42"), +(7794,"1-30-43"), +(7795,"1-30-44"), +(7796,"1-30-45"), +(7797,"1-30-46"), +(7798,"1-30-47"), +(7799,"1-30-48"), +(7800,"1-30-49"), +(7801,"2-31-0"), +(7802,"2-31-1"), +(7803,"2-31-2"), +(7804,"2-31-3"), +(7805,"2-31-4"), +(7806,"2-31-5"), +(7807,"2-31-6"), +(7808,"2-31-7"), +(7809,"2-31-8"), +(7810,"2-31-9"), +(7811,"2-31-10"), +(7812,"2-31-11"), +(7813,"2-31-12"), +(7814,"2-31-13"), +(7815,"2-31-14"), +(7816,"2-31-15"), +(7817,"2-31-16"), +(7818,"2-31-17"), +(7819,"2-31-18"), +(7820,"2-31-19"), +(7821,"2-31-20"), +(7822,"2-31-21"), +(7823,"2-31-22"), +(7824,"2-31-23"), +(7825,"2-31-24"), +(7826,"2-31-25"), +(7827,"2-31-26"), +(7828,"2-31-27"), +(7829,"2-31-28"), +(7830,"2-31-29"), +(7831,"2-31-30"), +(7832,"2-31-31"), +(7833,"2-31-32"), +(7834,"2-31-33"), +(7835,"2-31-34"), +(7836,"2-31-35"), +(7837,"2-31-36"), +(7838,"2-31-37"), +(7839,"2-31-38"), +(7840,"2-31-39"), +(7841,"2-31-40"), +(7842,"2-31-41"), +(7843,"2-31-42"), +(7844,"2-31-43"), +(7845,"2-31-44"), +(7846,"2-31-45"), +(7847,"2-31-46"), +(7848,"2-31-47"), +(7849,"2-31-48"), +(7850,"2-31-49"), +(7851,"3-30-0"), +(7852,"3-30-1"), +(7853,"4-32-0"), +(7854,"3-30-2"), +(7855,"3-30-3"), +(7856,"4-32-1"), +(7857,"3-30-4"), +(7858,"3-30-5"), +(7859,"4-32-2"), +(7860,"3-30-6"), +(7861,"4-32-3"), +(7862,"3-30-7"), +(7863,"4-32-4"), +(7864,"3-30-8"), +(7865,"4-32-5"), +(7866,"3-30-9"), +(7867,"4-32-6"), +(7868,"3-30-10"), +(7869,"4-32-7"), +(7870,"3-30-11"), +(7871,"4-32-8"), +(7872,"3-30-12"), +(7873,"4-32-9"), +(7874,"3-30-13"), +(7875,"4-32-10"), +(7876,"3-30-14"), +(7877,"3-30-15"), +(7878,"3-30-16"), +(7879,"4-32-11"), +(7880,"3-30-17"), +(7881,"4-32-12"), +(7882,"3-30-18"), +(7883,"4-32-13"), +(7884,"3-30-19"), +(7885,"4-32-14"), +(7886,"3-30-20"), +(7887,"4-32-15"), +(7888,"3-30-21"), +(7889,"4-32-16"), +(7890,"3-30-22"), +(7891,"4-32-17"), +(7892,"3-30-23"), +(7893,"4-32-18"), +(7894,"3-30-24"), +(7895,"4-32-19"), +(7896,"3-30-25"), +(7897,"4-32-20"), +(7898,"3-30-26"), +(7899,"4-32-21"), +(7900,"3-30-27"), +(7901,"4-32-22"), +(7902,"3-30-28"), +(7903,"4-32-23"), +(7904,"3-30-29"), +(7905,"4-32-24"), +(7906,"3-30-30"), +(7907,"4-32-25"), +(7908,"3-30-31"), +(7909,"4-32-26"), +(7910,"3-30-32"), +(7911,"4-32-27"), +(7912,"3-30-33"), +(7913,"4-32-28"), +(7914,"3-30-34"), +(7915,"4-32-29"), +(7916,"3-30-35"), +(7917,"4-32-30"), +(7918,"3-30-36"), +(7919,"4-32-31"), +(7920,"3-30-37"), +(7921,"4-32-32"), +(7922,"3-30-38"), +(7923,"4-32-33"), +(7924,"4-32-34"), +(7925,"3-30-39"), +(7926,"4-32-35"), +(7927,"4-32-36"), +(7928,"3-30-40"), +(7929,"4-32-37"), +(7930,"4-32-38"), +(7931,"3-30-41"), +(7932,"4-32-39"), +(7933,"4-32-40"), +(7934,"3-30-42"), +(7935,"4-32-41"), +(7936,"4-32-42"), +(7937,"3-30-43"), +(7938,"4-32-43"), +(7939,"3-30-44"), +(7940,"4-32-44"), +(7941,"3-30-45"), +(7942,"4-32-45"), +(7943,"3-30-46"), +(7944,"4-32-46"), +(7945,"3-30-47"), +(7946,"4-32-47"), +(7947,"3-30-48"), +(7948,"4-32-48"), +(7949,"3-30-49"), +(7950,"4-32-49"), +(7951,"1-31-0"), +(7952,"1-31-1"), +(7953,"1-31-2"), +(7954,"1-31-3"), +(7955,"1-31-4"), +(7956,"1-31-5"), +(7957,"1-31-6"), +(7958,"1-31-7"), +(7959,"1-31-8"), +(7960,"1-31-9"), +(7961,"1-31-10"), +(7962,"1-31-11"), +(7963,"1-31-12"), +(7964,"1-31-13"), +(7965,"1-31-14"), +(7966,"1-31-15"), +(7967,"1-31-16"), +(7968,"1-31-17"), +(7969,"1-31-18"), +(7970,"1-31-19"), +(7971,"1-31-20"), +(7972,"1-31-21"), +(7973,"1-31-22"), +(7974,"1-31-23"), +(7975,"1-31-24"), +(7976,"1-31-25"), +(7977,"1-31-26"), +(7978,"1-31-27"), +(7979,"1-31-28"), +(7980,"1-31-29"), +(7981,"1-31-30"), +(7982,"1-31-31"), +(7983,"1-31-32"), +(7984,"1-31-33"), +(7985,"1-31-34"), +(7986,"1-31-35"), +(7987,"1-31-36"), +(7988,"1-31-37"), +(7989,"1-31-38"), +(7990,"1-31-39"), +(7991,"1-31-40"), +(7992,"1-31-41"), +(7993,"1-31-42"), +(7994,"1-31-43"), +(7995,"1-31-44"), +(7996,"1-31-45"), +(7997,"1-31-46"), +(7998,"1-31-47"), +(7999,"1-31-48"), +(8000,"1-31-49"), +(8001,"0-32-0"), +(8002,"0-32-1"), +(8003,"0-32-2"), +(8004,"0-32-3"), +(8005,"0-32-4"), +(8006,"0-32-5"), +(8007,"0-32-6"), +(8008,"0-32-7"), +(8009,"0-32-8"), +(8010,"0-32-9"), +(8011,"0-32-10"), +(8012,"0-32-11"), +(8013,"0-32-12"), +(8014,"0-32-13"), +(8015,"0-32-14"), +(8016,"0-32-15"), +(8017,"0-32-16"), +(8018,"0-32-17"), +(8019,"0-32-18"), +(8020,"0-32-19"), +(8021,"0-32-20"), +(8022,"0-32-21"), +(8023,"0-32-22"), +(8024,"0-32-23"), +(8025,"0-32-24"), +(8026,"0-32-25"), +(8027,"0-32-26"), +(8028,"0-32-27"), +(8029,"0-32-28"), +(8030,"0-32-29"), +(8031,"0-32-30"), +(8032,"0-32-31"), +(8033,"0-32-32"), +(8034,"0-32-33"), +(8035,"0-32-34"), +(8036,"0-32-35"), +(8037,"0-32-36"), +(8038,"0-32-37"), +(8039,"0-32-38"), +(8040,"0-32-39"), +(8041,"0-32-40"), +(8042,"0-32-41"), +(8043,"0-32-42"), +(8044,"0-32-43"), +(8045,"0-32-44"), +(8046,"0-32-45"), +(8047,"0-32-46"), +(8048,"0-32-47"), +(8049,"0-32-48"), +(8050,"0-32-49"), +(8051,"3-31-0"), +(8052,"4-33-0"), +(8053,"3-31-1"), +(8054,"4-33-1"), +(8055,"3-31-2"), +(8056,"4-33-2"), +(8057,"3-31-3"), +(8058,"4-33-3"), +(8059,"1-32-0"), +(8060,"3-31-4"), +(8061,"4-33-4"), +(8062,"3-31-5"), +(8063,"4-33-5"), +(8064,"1-32-1"), +(8065,"3-31-6"), +(8066,"4-33-6"), +(8067,"1-32-2"), +(8068,"3-31-7"), +(8069,"4-33-7"), +(8070,"1-32-3"), +(8071,"3-31-8"), +(8072,"4-33-8"), +(8073,"1-32-4"), +(8074,"3-31-9"), +(8075,"4-33-9"), +(8076,"1-32-5"), +(8077,"3-31-10"), +(8078,"4-33-10"), +(8079,"1-32-6"), +(8080,"3-31-11"), +(8081,"4-33-11"), +(8082,"1-32-7"), +(8083,"3-31-12"), +(8084,"4-33-12"), +(8085,"1-32-8"), +(8086,"3-31-13"), +(8087,"4-33-13"), +(8088,"1-32-9"), +(8089,"3-31-14"), +(8090,"4-33-14"), +(8091,"3-31-15"), +(8092,"4-33-15"), +(8093,"1-32-10"), +(8094,"3-31-16"), +(8095,"4-33-16"), +(8096,"1-32-11"), +(8097,"3-31-17"), +(8098,"4-33-17"), +(8099,"1-32-12"), +(8100,"3-31-18"), +(8101,"4-33-18"), +(8102,"1-32-13"), +(8103,"3-31-19"), +(8104,"4-33-19"), +(8105,"1-32-14"), +(8106,"4-33-20"), +(8107,"3-31-20"), +(8108,"4-33-21"), +(8109,"3-31-21"), +(8110,"4-33-22"), +(8111,"4-33-23"), +(8112,"4-33-24"), +(8113,"3-31-22"), +(8114,"4-33-25"), +(8115,"1-32-15"), +(8116,"3-31-23"), +(8117,"4-33-26"), +(8118,"1-32-16"), +(8119,"3-31-24"), +(8120,"4-33-27"), +(8121,"1-32-17"), +(8122,"3-31-25"), +(8123,"4-33-28"), +(8124,"1-32-18"), +(8125,"3-31-26"), +(8126,"4-33-29"), +(8127,"1-32-19"), +(8128,"3-31-27"), +(8129,"4-33-30"), +(8130,"1-32-20"), +(8131,"3-31-28"), +(8132,"4-33-31"), +(8133,"1-32-21"), +(8134,"3-31-29"), +(8135,"2-32-0"), +(8136,"4-33-32"), +(8137,"1-32-22"), +(8138,"3-31-30"), +(8139,"0-33-0"), +(8140,"4-33-33"), +(8141,"3-31-31"), +(8142,"1-32-23"), +(8143,"2-32-1"), +(8144,"4-33-34"), +(8145,"1-32-24"), +(8146,"3-31-32"), +(8147,"0-33-1"), +(8148,"4-33-35"), +(8149,"1-32-25"), +(8150,"2-32-2"), +(8151,"3-31-33"), +(8152,"4-33-36"), +(8153,"1-32-26"), +(8154,"0-33-2"), +(8155,"3-31-34"), +(8156,"4-33-37"), +(8157,"2-32-3"), +(8158,"1-32-27"), +(8159,"4-33-38"), +(8160,"3-31-35"), +(8161,"0-33-3"), +(8162,"2-32-4"), +(8163,"3-31-36"), +(8164,"4-33-39"), +(8165,"0-33-4"), +(8166,"1-32-28"), +(8167,"3-31-37"), +(8168,"2-32-5"), +(8169,"4-33-40"), +(8170,"1-32-29"), +(8171,"0-33-5"), +(8172,"3-31-38"), +(8173,"4-33-41"), +(8174,"2-32-6"), +(8175,"1-32-30"), +(8176,"3-31-39"), +(8177,"0-33-6"), +(8178,"1-32-31"), +(8179,"4-33-42"), +(8180,"3-31-40"), +(8181,"2-32-7"), +(8182,"4-33-43"), +(8183,"1-32-32"), +(8184,"3-31-41"), +(8185,"0-33-7"), +(8186,"4-33-44"), +(8187,"2-32-8"), +(8188,"3-31-42"), +(8189,"1-32-33"), +(8190,"4-33-45"), +(8191,"0-33-8"), +(8192,"3-31-43"), +(8193,"2-32-9"), +(8194,"4-33-46"), +(8195,"3-31-44"), +(8196,"1-32-34"), +(8197,"0-33-9"), +(8198,"4-33-47"), +(8199,"2-32-10"), +(8200,"1-32-35"), +(8201,"3-31-45"), +(8202,"0-33-10"), +(8203,"4-33-48"), +(8204,"3-31-46"), +(8205,"2-32-11"), +(8206,"1-32-36"), +(8207,"4-33-49"), +(8208,"0-33-11"), +(8209,"1-32-37"), +(8210,"2-32-12"), +(8211,"1-32-38"), +(8212,"0-33-12"), +(8213,"1-32-39"), +(8214,"2-32-13"), +(8215,"0-33-13"), +(8216,"1-32-40"), +(8217,"1-32-41"), +(8218,"0-33-14"), +(8219,"2-32-14"), +(8220,"1-32-42"), +(8221,"0-33-15"), +(8222,"1-32-43"), +(8223,"2-32-15"), +(8224,"1-32-44"), +(8225,"0-33-16"), +(8226,"2-32-16"), +(8227,"1-32-45"), +(8228,"0-33-17"), +(8229,"1-32-46"), +(8230,"2-32-17"), +(8231,"1-32-47"), +(8232,"0-33-18"), +(8233,"2-32-18"), +(8234,"1-32-48"), +(8235,"1-32-49"), +(8236,"0-33-19"), +(8237,"2-32-19"), +(8238,"0-33-20"), +(8239,"2-32-20"), +(8240,"0-33-21"), +(8241,"2-32-21"), +(8242,"0-33-22"), +(8243,"2-32-22"), +(8244,"0-33-23"), +(8245,"2-32-23"), +(8246,"0-33-24"), +(8247,"2-32-24"), +(8248,"0-33-25"), +(8249,"2-32-25"), +(8250,"0-33-26"), +(8251,"2-32-26"), +(8252,"0-33-27"), +(8253,"2-32-27"), +(8254,"0-33-28"), +(8255,"3-31-47"), +(8256,"2-32-28"), +(8257,"0-33-29"), +(8258,"3-31-48"), +(8259,"3-31-49"), +(8260,"2-32-29"), +(8261,"0-33-30"), +(8262,"2-32-30"), +(8263,"0-33-31"), +(8264,"2-32-31"), +(8265,"0-33-32"), +(8266,"2-32-32"), +(8267,"0-33-33"), +(8268,"2-32-33"), +(8269,"0-33-34"), +(8270,"2-32-34"), +(8271,"0-33-35"), +(8272,"2-32-35"), +(8273,"0-33-36"), +(8274,"2-32-36"), +(8275,"0-33-37"), +(8276,"2-32-37"), +(8277,"0-33-38"), +(8278,"2-32-38"), +(8279,"0-33-39"), +(8280,"2-32-39"), +(8281,"0-33-40"), +(8282,"2-32-40"), +(8283,"0-33-41"), +(8284,"2-32-41"), +(8285,"0-33-42"), +(8286,"2-32-42"), +(8287,"0-33-43"), +(8288,"2-32-43"), +(8289,"0-33-44"), +(8290,"2-32-44"), +(8291,"0-33-45"), +(8292,"2-32-45"), +(8293,"0-33-46"), +(8294,"2-32-46"), +(8295,"0-33-47"), +(8296,"2-32-47"), +(8297,"0-33-48"), +(8298,"2-32-48"), +(8299,"0-33-49"), +(8300,"2-32-49"), +(8301,"0-34-0"), +(8302,"0-34-1"), +(8303,"0-34-2"), +(8304,"0-34-3"), +(8305,"0-34-4"), +(8306,"0-34-5"), +(8307,"0-34-6"), +(8308,"0-34-7"), +(8309,"0-34-8"), +(8310,"0-34-9"), +(8311,"0-34-10"), +(8312,"0-34-11"), +(8313,"0-34-12"), +(8314,"0-34-13"), +(8315,"0-34-14"), +(8316,"0-34-15"), +(8317,"0-34-16"), +(8318,"0-34-17"), +(8319,"0-34-18"), +(8320,"0-34-19"), +(8321,"0-34-20"), +(8322,"0-34-21"), +(8323,"0-34-22"), +(8324,"0-34-23"), +(8325,"0-34-24"), +(8326,"0-34-25"), +(8327,"0-34-26"), +(8328,"0-34-27"), +(8329,"0-34-28"), +(8330,"0-34-29"), +(8331,"0-34-30"), +(8332,"0-34-31"), +(8333,"0-34-32"), +(8334,"0-34-33"), +(8335,"0-34-34"), +(8336,"0-34-35"), +(8337,"0-34-36"), +(8338,"0-34-37"), +(8339,"0-34-38"), +(8340,"0-34-39"), +(8341,"0-34-40"), +(8342,"0-34-41"), +(8343,"0-34-42"), +(8344,"0-34-43"), +(8345,"0-34-44"), +(8346,"0-34-45"), +(8347,"0-34-46"), +(8348,"0-34-47"), +(8349,"0-34-48"), +(8350,"0-34-49"), +(8351,"4-34-0"), +(8352,"4-34-1"), +(8353,"4-34-2"), +(8354,"4-34-3"), +(8355,"4-34-4"), +(8356,"4-34-5"), +(8357,"4-34-6"), +(8358,"4-34-7"), +(8359,"4-34-8"), +(8360,"4-34-9"), +(8361,"4-34-10"), +(8362,"4-34-11"), +(8363,"4-34-12"), +(8364,"4-34-13"), +(8365,"4-34-14"), +(8366,"4-34-15"), +(8367,"4-34-16"), +(8368,"4-34-17"), +(8369,"4-34-18"), +(8370,"4-34-19"), +(8371,"4-34-20"), +(8372,"4-34-21"), +(8373,"4-34-22"), +(8374,"4-34-23"), +(8375,"4-34-24"), +(8376,"4-34-25"), +(8377,"4-34-26"), +(8378,"4-34-27"), +(8379,"4-34-28"), +(8380,"4-34-29"), +(8381,"4-34-30"), +(8382,"4-34-31"), +(8383,"4-34-32"), +(8384,"4-34-33"), +(8385,"4-34-34"), +(8386,"4-34-35"), +(8387,"4-34-36"), +(8388,"4-34-37"), +(8389,"4-34-38"), +(8390,"4-34-39"), +(8391,"4-34-40"), +(8392,"4-34-41"), +(8393,"4-34-42"), +(8394,"4-34-43"), +(8395,"4-34-44"), +(8396,"4-34-45"), +(8397,"4-34-46"), +(8398,"4-34-47"), +(8399,"4-34-48"), +(8400,"4-34-49"), +(8401,"1-33-0"), +(8402,"1-33-1"), +(8403,"1-33-2"), +(8404,"1-33-3"), +(8405,"1-33-4"), +(8406,"1-33-5"), +(8407,"1-33-6"), +(8408,"1-33-7"), +(8409,"1-33-8"), +(8410,"1-33-9"), +(8411,"1-33-10"), +(8412,"1-33-11"), +(8413,"1-33-12"), +(8414,"1-33-13"), +(8415,"1-33-14"), +(8416,"1-33-15"), +(8417,"1-33-16"), +(8418,"1-33-17"), +(8419,"1-33-18"), +(8420,"1-33-19"), +(8421,"1-33-20"), +(8422,"1-33-21"), +(8423,"1-33-22"), +(8424,"1-33-23"), +(8425,"1-33-24"), +(8426,"1-33-25"), +(8427,"1-33-26"), +(8428,"1-33-27"), +(8429,"1-33-28"), +(8430,"1-33-29"), +(8431,"1-33-30"), +(8432,"1-33-31"), +(8433,"1-33-32"), +(8434,"1-33-33"), +(8435,"1-33-34"), +(8436,"1-33-35"), +(8437,"1-33-36"), +(8438,"1-33-37"), +(8439,"1-33-38"), +(8440,"1-33-39"), +(8441,"1-33-40"), +(8442,"1-33-41"), +(8443,"1-33-42"), +(8444,"1-33-43"), +(8445,"1-33-44"), +(8446,"1-33-45"), +(8447,"1-33-46"), +(8448,"1-33-47"), +(8449,"1-33-48"), +(8450,"1-33-49"), +(8451,"3-32-0"), +(8452,"3-32-1"), +(8453,"3-32-2"), +(8454,"3-32-3"), +(8455,"3-32-4"), +(8456,"3-32-5"), +(8457,"3-32-6"), +(8458,"3-32-7"), +(8459,"3-32-8"), +(8460,"3-32-9"), +(8461,"3-32-10"), +(8462,"3-32-11"), +(8463,"3-32-12"), +(8464,"3-32-13"), +(8465,"3-32-14"), +(8466,"3-32-15"), +(8467,"3-32-16"), +(8468,"3-32-17"), +(8469,"3-32-18"), +(8470,"3-32-19"), +(8471,"3-32-20"), +(8472,"3-32-21"), +(8473,"3-32-22"), +(8474,"3-32-23"), +(8475,"3-32-24"), +(8476,"3-32-25"), +(8477,"3-32-26"), +(8478,"3-32-27"), +(8479,"3-32-28"), +(8480,"3-32-29"), +(8481,"3-32-30"), +(8482,"3-32-31"), +(8483,"3-32-32"), +(8484,"3-32-33"), +(8485,"3-32-34"), +(8486,"3-32-35"), +(8487,"3-32-36"), +(8488,"3-32-37"), +(8489,"3-32-38"), +(8490,"3-32-39"), +(8491,"3-32-40"), +(8492,"3-32-41"), +(8493,"3-32-42"), +(8494,"3-32-43"), +(8495,"3-32-44"), +(8496,"3-32-45"), +(8497,"3-32-46"), +(8498,"3-32-47"), +(8499,"3-32-48"), +(8500,"3-32-49"), +(8501,"2-33-0"), +(8502,"2-33-1"), +(8503,"2-33-2"), +(8504,"2-33-3"), +(8505,"2-33-4"), +(8506,"2-33-5"), +(8507,"2-33-6"), +(8508,"2-33-7"), +(8509,"2-33-8"), +(8510,"2-33-9"), +(8511,"2-33-10"), +(8512,"2-33-11"), +(8513,"2-33-12"), +(8514,"2-33-13"), +(8515,"2-33-14"), +(8516,"2-33-15"), +(8517,"2-33-16"), +(8518,"2-33-17"), +(8519,"2-33-18"), +(8520,"2-33-19"), +(8521,"2-33-20"), +(8522,"2-33-21"), +(8523,"2-33-22"), +(8524,"2-33-23"), +(8525,"2-33-24"), +(8526,"2-33-25"), +(8527,"2-33-26"), +(8528,"2-33-27"), +(8529,"2-33-28"), +(8530,"2-33-29"), +(8531,"2-33-30"), +(8532,"2-33-31"), +(8533,"2-33-32"), +(8534,"2-33-33"), +(8535,"2-33-34"), +(8536,"2-33-35"), +(8537,"2-33-36"), +(8538,"2-33-37"), +(8539,"2-33-38"), +(8540,"2-33-39"), +(8541,"2-33-40"), +(8542,"2-33-41"), +(8543,"2-33-42"), +(8544,"2-33-43"), +(8545,"2-33-44"), +(8546,"2-33-45"), +(8547,"2-33-46"), +(8548,"2-33-47"), +(8549,"2-33-48"), +(8550,"2-33-49"), +(8551,"0-35-0"), +(8552,"0-35-1"), +(8553,"0-35-2"), +(8554,"0-35-3"), +(8555,"0-35-4"), +(8556,"0-35-5"), +(8557,"0-35-6"), +(8558,"0-35-7"), +(8559,"0-35-8"), +(8560,"0-35-9"), +(8561,"0-35-10"), +(8562,"0-35-11"), +(8563,"0-35-12"), +(8564,"0-35-13"), +(8565,"0-35-14"), +(8566,"0-35-15"), +(8567,"0-35-16"), +(8568,"0-35-17"), +(8569,"0-35-18"), +(8570,"0-35-19"), +(8571,"0-35-20"), +(8572,"0-35-21"), +(8573,"0-35-22"), +(8574,"0-35-23"), +(8575,"0-35-24"), +(8576,"0-35-25"), +(8577,"0-35-26"), +(8578,"0-35-27"), +(8579,"0-35-28"), +(8580,"0-35-29"), +(8581,"0-35-30"), +(8582,"0-35-31"), +(8583,"0-35-32"), +(8584,"0-35-33"), +(8585,"0-35-34"), +(8586,"0-35-35"), +(8587,"0-35-36"), +(8588,"0-35-37"), +(8589,"0-35-38"), +(8590,"0-35-39"), +(8591,"0-35-40"), +(8592,"0-35-41"), +(8593,"0-35-42"), +(8594,"0-35-43"), +(8595,"0-35-44"), +(8596,"0-35-45"), +(8597,"0-35-46"), +(8598,"0-35-47"), +(8599,"0-35-48"), +(8600,"0-35-49"), +(8601,"4-35-0"), +(8602,"4-35-1"), +(8603,"4-35-2"), +(8604,"4-35-3"), +(8605,"4-35-4"), +(8606,"4-35-5"), +(8607,"4-35-6"), +(8608,"4-35-7"), +(8609,"4-35-8"), +(8610,"4-35-9"), +(8611,"4-35-10"), +(8612,"4-35-11"), +(8613,"4-35-12"), +(8614,"4-35-13"), +(8615,"4-35-14"), +(8616,"4-35-15"), +(8617,"4-35-16"), +(8618,"4-35-17"), +(8619,"4-35-18"), +(8620,"4-35-19"), +(8621,"4-35-20"), +(8622,"4-35-21"), +(8623,"4-35-22"), +(8624,"4-35-23"), +(8625,"4-35-24"), +(8626,"4-35-25"), +(8627,"4-35-26"), +(8628,"4-35-27"), +(8629,"1-34-0"), +(8630,"4-35-28"), +(8631,"4-35-29"), +(8632,"1-34-1"), +(8633,"4-35-30"), +(8634,"1-34-2"), +(8635,"4-35-31"), +(8636,"1-34-3"), +(8637,"4-35-32"), +(8638,"1-34-4"), +(8639,"4-35-33"), +(8640,"1-34-5"), +(8641,"4-35-34"), +(8642,"1-34-6"), +(8643,"4-35-35"), +(8644,"1-34-7"), +(8645,"4-35-36"), +(8646,"1-34-8"), +(8647,"4-35-37"), +(8648,"1-34-9"), +(8649,"4-35-38"), +(8650,"1-34-10"), +(8651,"4-35-39"), +(8652,"1-34-11"), +(8653,"4-35-40"), +(8654,"1-34-12"), +(8655,"4-35-41"), +(8656,"1-34-13"), +(8657,"4-35-42"), +(8658,"1-34-14"), +(8659,"4-35-43"), +(8660,"1-34-15"), +(8661,"4-35-44"), +(8662,"1-34-16"), +(8663,"4-35-45"), +(8664,"1-34-17"), +(8665,"4-35-46"), +(8666,"1-34-18"), +(8667,"4-35-47"), +(8668,"1-34-19"), +(8669,"4-35-48"), +(8670,"1-34-20"), +(8671,"4-35-49"), +(8672,"1-34-21"), +(8673,"1-34-22"), +(8674,"1-34-23"), +(8675,"1-34-24"), +(8676,"1-34-25"), +(8677,"1-34-26"), +(8678,"1-34-27"), +(8679,"1-34-28"), +(8680,"1-34-29"), +(8681,"1-34-30"), +(8682,"1-34-31"), +(8683,"1-34-32"), +(8684,"1-34-33"), +(8685,"1-34-34"), +(8686,"1-34-35"), +(8687,"1-34-36"), +(8688,"1-34-37"), +(8689,"1-34-38"), +(8690,"1-34-39"), +(8691,"1-34-40"), +(8692,"1-34-41"), +(8693,"1-34-42"), +(8694,"1-34-43"), +(8695,"1-34-44"), +(8696,"1-34-45"), +(8697,"1-34-46"), +(8698,"1-34-47"), +(8699,"1-34-48"), +(8700,"1-34-49"), +(8701,"2-34-0"), +(8702,"2-34-1"), +(8703,"2-34-2"), +(8704,"2-34-3"), +(8705,"2-34-4"), +(8706,"2-34-5"), +(8707,"2-34-6"), +(8708,"2-34-7"), +(8709,"2-34-8"), +(8710,"2-34-9"), +(8711,"2-34-10"), +(8712,"2-34-11"), +(8713,"2-34-12"), +(8714,"2-34-13"), +(8715,"2-34-14"), +(8716,"2-34-15"), +(8717,"2-34-16"), +(8718,"2-34-17"), +(8719,"2-34-18"), +(8720,"2-34-19"), +(8721,"2-34-20"), +(8722,"2-34-21"), +(8723,"2-34-22"), +(8724,"2-34-23"), +(8725,"2-34-24"), +(8726,"2-34-25"), +(8727,"2-34-26"), +(8728,"2-34-27"), +(8729,"2-34-28"), +(8730,"2-34-29"), +(8731,"2-34-30"), +(8732,"2-34-31"), +(8733,"2-34-32"), +(8734,"2-34-33"), +(8735,"2-34-34"), +(8736,"2-34-35"), +(8737,"2-34-36"), +(8738,"2-34-37"), +(8739,"2-34-38"), +(8740,"2-34-39"), +(8741,"2-34-40"), +(8742,"2-34-41"), +(8743,"2-34-42"), +(8744,"2-34-43"), +(8745,"2-34-44"), +(8746,"2-34-45"), +(8747,"2-34-46"), +(8748,"2-34-47"), +(8749,"2-34-48"), +(8750,"2-34-49"), +(8751,"3-33-0"), +(8752,"3-33-1"), +(8753,"3-33-2"), +(8754,"3-33-3"), +(8755,"3-33-4"), +(8756,"3-33-5"), +(8757,"3-33-6"), +(8758,"3-33-7"), +(8759,"3-33-8"), +(8760,"3-33-9"), +(8761,"3-33-10"), +(8762,"3-33-11"), +(8763,"3-33-12"), +(8764,"3-33-13"), +(8765,"3-33-14"), +(8766,"3-33-15"), +(8767,"3-33-16"), +(8768,"3-33-17"), +(8769,"3-33-18"), +(8770,"3-33-19"), +(8771,"3-33-20"), +(8772,"3-33-21"), +(8773,"3-33-22"), +(8774,"3-33-23"), +(8775,"3-33-24"), +(8776,"3-33-25"), +(8777,"3-33-26"), +(8778,"3-33-27"), +(8779,"3-33-28"), +(8780,"3-33-29"), +(8781,"3-33-30"), +(8782,"3-33-31"), +(8783,"3-33-32"), +(8784,"3-33-33"), +(8785,"3-33-34"), +(8786,"3-33-35"), +(8787,"3-33-36"), +(8788,"3-33-37"), +(8789,"3-33-38"), +(8790,"3-33-39"), +(8791,"3-33-40"), +(8792,"3-33-41"), +(8793,"3-33-42"), +(8794,"3-33-43"), +(8795,"3-33-44"), +(8796,"3-33-45"), +(8797,"3-33-46"), +(8798,"3-33-47"), +(8799,"3-33-48"), +(8800,"3-33-49"), +(8801,"0-36-0"), +(8802,"0-36-1"), +(8803,"0-36-2"), +(8804,"0-36-3"), +(8805,"0-36-4"), +(8806,"0-36-5"), +(8807,"0-36-6"), +(8808,"0-36-7"), +(8809,"0-36-8"), +(8810,"0-36-9"), +(8811,"0-36-10"), +(8812,"0-36-11"), +(8813,"0-36-12"), +(8814,"0-36-13"), +(8815,"0-36-14"), +(8816,"0-36-15"), +(8817,"0-36-16"), +(8818,"0-36-17"), +(8819,"0-36-18"), +(8820,"0-36-19"), +(8821,"0-36-20"), +(8822,"0-36-21"), +(8823,"0-36-22"), +(8824,"0-36-23"), +(8825,"0-36-24"), +(8826,"0-36-25"), +(8827,"0-36-26"), +(8828,"0-36-27"), +(8829,"0-36-28"), +(8830,"0-36-29"), +(8831,"0-36-30"), +(8832,"0-36-31"), +(8833,"0-36-32"), +(8834,"0-36-33"), +(8835,"0-36-34"), +(8836,"0-36-35"), +(8837,"0-36-36"), +(8838,"0-36-37"), +(8839,"0-36-38"), +(8840,"0-36-39"), +(8841,"0-36-40"), +(8842,"0-36-41"), +(8843,"0-36-42"), +(8844,"0-36-43"), +(8845,"0-36-44"), +(8846,"0-36-45"), +(8847,"0-36-46"), +(8848,"0-36-47"), +(8849,"0-36-48"), +(8850,"0-36-49"), +(8851,"4-36-0"), +(8852,"4-36-1"), +(8853,"4-36-2"), +(8854,"4-36-3"), +(8855,"4-36-4"), +(8856,"4-36-5"), +(8857,"4-36-6"), +(8858,"4-36-7"), +(8859,"4-36-8"), +(8860,"4-36-9"), +(8861,"4-36-10"), +(8862,"4-36-11"), +(8863,"4-36-12"), +(8864,"4-36-13"), +(8865,"2-35-0"), +(8866,"4-36-14"), +(8867,"2-35-1"), +(8868,"2-35-2"), +(8869,"4-36-15"), +(8870,"2-35-3"), +(8871,"4-36-16"), +(8872,"2-35-4"), +(8873,"2-35-5"), +(8874,"4-36-17"), +(8875,"2-35-6"), +(8876,"2-35-7"), +(8877,"4-36-18"), +(8878,"2-35-8"), +(8879,"2-35-9"), +(8880,"4-36-19"), +(8881,"2-35-10"), +(8882,"2-35-11"), +(8883,"4-36-20"), +(8884,"2-35-12"), +(8885,"2-35-13"), +(8886,"4-36-21"), +(8887,"2-35-14"), +(8888,"4-36-22"), +(8889,"2-35-15"), +(8890,"2-35-16"), +(8891,"4-36-23"), +(8892,"2-35-17"), +(8893,"2-35-18"), +(8894,"4-36-24"), +(8895,"2-35-19"), +(8896,"2-35-20"), +(8897,"4-36-25"), +(8898,"2-35-21"), +(8899,"4-36-26"), +(8900,"2-35-22"), +(8901,"2-35-23"), +(8902,"2-35-24"), +(8903,"4-36-27"), +(8904,"2-35-25"), +(8905,"2-35-26"), +(8906,"4-36-28"), +(8907,"2-35-27"), +(8908,"2-35-28"), +(8909,"4-36-29"), +(8910,"2-35-29"), +(8911,"4-36-30"), +(8912,"2-35-30"), +(8913,"1-35-0"), +(8914,"2-35-31"), +(8915,"4-36-31"), +(8916,"2-35-32"), +(8917,"1-35-1"), +(8918,"2-35-33"), +(8919,"1-35-2"), +(8920,"4-36-32"), +(8921,"2-35-34"), +(8922,"1-35-3"), +(8923,"2-35-35"), +(8924,"1-35-4"), +(8925,"4-36-33"), +(8926,"2-35-36"), +(8927,"1-35-5"), +(8928,"2-35-37"), +(8929,"1-35-6"), +(8930,"4-36-34"), +(8931,"2-35-38"), +(8932,"1-35-7"), +(8933,"2-35-39"), +(8934,"1-35-8"), +(8935,"4-36-35"), +(8936,"2-35-40"), +(8937,"1-35-9"), +(8938,"2-35-41"), +(8939,"1-35-10"), +(8940,"4-36-36"), +(8941,"2-35-42"), +(8942,"1-35-11"), +(8943,"2-35-43"), +(8944,"1-35-12"), +(8945,"4-36-37"), +(8946,"2-35-44"), +(8947,"1-35-13"), +(8948,"2-35-45"), +(8949,"1-35-14"), +(8950,"4-36-38"), +(8951,"2-35-46"), +(8952,"1-35-15"), +(8953,"2-35-47"), +(8954,"1-35-16"), +(8955,"4-36-39"), +(8956,"2-35-48"), +(8957,"1-35-17"), +(8958,"2-35-49"), +(8959,"4-36-40"), +(8960,"1-35-18"), +(8961,"1-35-19"), +(8962,"4-36-41"), +(8963,"1-35-20"), +(8964,"1-35-21"), +(8965,"4-36-42"), +(8966,"1-35-22"), +(8967,"1-35-23"), +(8968,"4-36-43"), +(8969,"1-35-24"), +(8970,"1-35-25"), +(8971,"4-36-44"), +(8972,"1-35-26"), +(8973,"1-35-27"), +(8974,"4-36-45"), +(8975,"1-35-28"), +(8976,"1-35-29"), +(8977,"4-36-46"), +(8978,"1-35-30"), +(8979,"1-35-31"), +(8980,"4-36-47"), +(8981,"1-35-32"), +(8982,"1-35-33"), +(8983,"4-36-48"), +(8984,"1-35-34"), +(8985,"1-35-35"), +(8986,"4-36-49"), +(8987,"1-35-36"), +(8988,"1-35-37"), +(8989,"1-35-38"), +(8990,"1-35-39"), +(8991,"1-35-40"), +(8992,"1-35-41"), +(8993,"1-35-42"), +(8994,"1-35-43"), +(8995,"1-35-44"), +(8996,"1-35-45"), +(8997,"1-35-46"), +(8998,"1-35-47"), +(8999,"1-35-48"), +(9000,"1-35-49"), +(9001,"3-34-0"), +(9002,"3-34-1"), +(9003,"3-34-2"), +(9004,"3-34-3"), +(9005,"3-34-4"), +(9006,"3-34-5"), +(9007,"3-34-6"), +(9008,"3-34-7"), +(9009,"3-34-8"), +(9010,"3-34-9"), +(9011,"3-34-10"), +(9012,"3-34-11"), +(9013,"3-34-12"), +(9014,"3-34-13"), +(9015,"3-34-14"), +(9016,"3-34-15"), +(9017,"3-34-16"), +(9018,"3-34-17"), +(9019,"3-34-18"), +(9020,"3-34-19"), +(9021,"3-34-20"), +(9022,"3-34-21"), +(9023,"3-34-22"), +(9024,"3-34-23"), +(9025,"3-34-24"), +(9026,"3-34-25"), +(9027,"3-34-26"), +(9028,"3-34-27"), +(9029,"3-34-28"), +(9030,"3-34-29"), +(9031,"3-34-30"), +(9032,"3-34-31"), +(9033,"3-34-32"), +(9034,"3-34-33"), +(9035,"3-34-34"), +(9036,"3-34-35"), +(9037,"3-34-36"), +(9038,"3-34-37"), +(9039,"3-34-38"), +(9040,"3-34-39"), +(9041,"3-34-40"), +(9042,"3-34-41"), +(9043,"3-34-42"), +(9044,"3-34-43"), +(9045,"3-34-44"), +(9046,"3-34-45"), +(9047,"3-34-46"), +(9048,"3-34-47"), +(9049,"3-34-48"), +(9050,"3-34-49"), +(9051,"0-37-0"), +(9052,"0-37-1"), +(9053,"0-37-2"), +(9054,"0-37-3"), +(9055,"0-37-4"), +(9056,"0-37-5"), +(9057,"0-37-6"), +(9058,"0-37-7"), +(9059,"0-37-8"), +(9060,"0-37-9"), +(9061,"0-37-10"), +(9062,"0-37-11"), +(9063,"0-37-12"), +(9064,"0-37-13"), +(9065,"0-37-14"), +(9066,"0-37-15"), +(9067,"0-37-16"), +(9068,"0-37-17"), +(9069,"0-37-18"), +(9070,"0-37-19"), +(9071,"0-37-20"), +(9072,"0-37-21"), +(9073,"0-37-22"), +(9074,"0-37-23"), +(9075,"0-37-24"), +(9076,"0-37-25"), +(9077,"0-37-26"), +(9078,"0-37-27"), +(9079,"0-37-28"), +(9080,"0-37-29"), +(9081,"0-37-30"), +(9082,"0-37-31"), +(9083,"0-37-32"), +(9084,"0-37-33"), +(9085,"0-37-34"), +(9086,"0-37-35"), +(9087,"0-37-36"), +(9088,"0-37-37"), +(9089,"0-37-38"), +(9090,"0-37-39"), +(9091,"0-37-40"), +(9092,"0-37-41"), +(9093,"0-37-42"), +(9094,"0-37-43"), +(9095,"0-37-44"), +(9096,"0-37-45"), +(9097,"0-37-46"), +(9098,"0-37-47"), +(9099,"0-37-48"), +(9100,"0-37-49"), +(9101,"1-36-0"), +(9102,"1-36-1"), +(9103,"1-36-2"), +(9104,"1-36-3"), +(9105,"1-36-4"), +(9106,"1-36-5"), +(9107,"1-36-6"), +(9108,"1-36-7"), +(9109,"1-36-8"), +(9110,"1-36-9"), +(9111,"1-36-10"), +(9112,"1-36-11"), +(9113,"1-36-12"), +(9114,"1-36-13"), +(9115,"1-36-14"), +(9116,"1-36-15"), +(9117,"1-36-16"), +(9118,"1-36-17"), +(9119,"1-36-18"), +(9120,"1-36-19"), +(9121,"1-36-20"), +(9122,"1-36-21"), +(9123,"1-36-22"), +(9124,"1-36-23"), +(9125,"1-36-24"), +(9126,"1-36-25"), +(9127,"1-36-26"), +(9128,"1-36-27"), +(9129,"1-36-28"), +(9130,"1-36-29"), +(9131,"1-36-30"), +(9132,"1-36-31"), +(9133,"1-36-32"), +(9134,"1-36-33"), +(9135,"1-36-34"), +(9136,"1-36-35"), +(9137,"1-36-36"), +(9138,"1-36-37"), +(9139,"1-36-38"), +(9140,"1-36-39"), +(9141,"1-36-40"), +(9142,"1-36-41"), +(9143,"1-36-42"), +(9144,"1-36-43"), +(9145,"1-36-44"), +(9146,"1-36-45"), +(9147,"1-36-46"), +(9148,"1-36-47"), +(9149,"1-36-48"), +(9150,"1-36-49"), +(9151,"3-35-0"), +(9152,"3-35-1"), +(9153,"3-35-2"), +(9154,"3-35-3"), +(9155,"3-35-4"), +(9156,"3-35-5"), +(9157,"3-35-6"), +(9158,"3-35-7"), +(9159,"3-35-8"), +(9160,"3-35-9"), +(9161,"3-35-10"), +(9162,"3-35-11"), +(9163,"3-35-12"), +(9164,"3-35-13"), +(9165,"3-35-14"), +(9166,"3-35-15"), +(9167,"3-35-16"), +(9168,"3-35-17"), +(9169,"3-35-18"), +(9170,"3-35-19"), +(9171,"3-35-20"), +(9172,"3-35-21"), +(9173,"3-35-22"), +(9174,"3-35-23"), +(9175,"3-35-24"), +(9176,"3-35-25"), +(9177,"3-35-26"), +(9178,"3-35-27"), +(9179,"3-35-28"), +(9180,"3-35-29"), +(9181,"3-35-30"), +(9182,"3-35-31"), +(9183,"3-35-32"), +(9184,"3-35-33"), +(9185,"3-35-34"), +(9186,"3-35-35"), +(9187,"3-35-36"), +(9188,"3-35-37"), +(9189,"3-35-38"), +(9190,"3-35-39"), +(9191,"3-35-40"), +(9192,"3-35-41"), +(9193,"3-35-42"), +(9194,"3-35-43"), +(9195,"3-35-44"), +(9196,"3-35-45"), +(9197,"3-35-46"), +(9198,"3-35-47"), +(9199,"3-35-48"), +(9200,"3-35-49"), +(9201,"4-37-0"), +(9202,"4-37-1"), +(9203,"4-37-2"), +(9204,"4-37-3"), +(9205,"4-37-4"), +(9206,"4-37-5"), +(9207,"4-37-6"), +(9208,"4-37-7"), +(9209,"4-37-8"), +(9210,"4-37-9"), +(9211,"4-37-10"), +(9212,"4-37-11"), +(9213,"4-37-12"), +(9214,"4-37-13"), +(9215,"4-37-14"), +(9216,"4-37-15"), +(9217,"4-37-16"), +(9218,"4-37-17"), +(9219,"4-37-18"), +(9220,"4-37-19"), +(9221,"4-37-20"), +(9222,"4-37-21"), +(9223,"4-37-22"), +(9224,"4-37-23"), +(9225,"4-37-24"), +(9226,"4-37-25"), +(9227,"4-37-26"), +(9228,"4-37-27"), +(9229,"4-37-28"), +(9230,"4-37-29"), +(9231,"4-37-30"), +(9232,"4-37-31"), +(9233,"4-37-32"), +(9234,"4-37-33"), +(9235,"4-37-34"), +(9236,"4-37-35"), +(9237,"4-37-36"), +(9238,"4-37-37"), +(9239,"4-37-38"), +(9240,"4-37-39"), +(9241,"4-37-40"), +(9242,"4-37-41"), +(9243,"4-37-42"), +(9244,"4-37-43"), +(9245,"4-37-44"), +(9246,"4-37-45"), +(9247,"4-37-46"), +(9248,"4-37-47"), +(9249,"4-37-48"), +(9250,"4-37-49"), +(9251,"2-36-0"), +(9252,"2-36-1"), +(9253,"2-36-2"), +(9254,"2-36-3"), +(9255,"2-36-4"), +(9256,"2-36-5"), +(9257,"2-36-6"), +(9258,"2-36-7"), +(9259,"2-36-8"), +(9260,"2-36-9"), +(9261,"2-36-10"), +(9262,"2-36-11"), +(9263,"2-36-12"), +(9264,"2-36-13"), +(9265,"2-36-14"), +(9266,"2-36-15"), +(9267,"2-36-16"), +(9268,"2-36-17"), +(9269,"2-36-18"), +(9270,"2-36-19"), +(9271,"2-36-20"), +(9272,"2-36-21"), +(9273,"2-36-22"), +(9274,"2-36-23"), +(9275,"2-36-24"), +(9276,"2-36-25"), +(9277,"2-36-26"), +(9278,"2-36-27"), +(9279,"2-36-28"), +(9280,"2-36-29"), +(9281,"2-36-30"), +(9282,"2-36-31"), +(9283,"2-36-32"), +(9284,"2-36-33"), +(9285,"2-36-34"), +(9286,"2-36-35"), +(9287,"2-36-36"), +(9288,"2-36-37"), +(9289,"2-36-38"), +(9290,"2-36-39"), +(9291,"2-36-40"), +(9292,"2-36-41"), +(9293,"2-36-42"), +(9294,"2-36-43"), +(9295,"2-36-44"), +(9296,"2-36-45"), +(9297,"2-36-46"), +(9298,"2-36-47"), +(9299,"2-36-48"), +(9300,"2-36-49"), +(9301,"0-38-0"), +(9302,"0-38-1"), +(9303,"0-38-2"), +(9304,"0-38-3"), +(9305,"0-38-4"), +(9306,"0-38-5"), +(9307,"0-38-6"), +(9308,"0-38-7"), +(9309,"0-38-8"), +(9310,"0-38-9"), +(9311,"0-38-10"), +(9312,"0-38-11"), +(9313,"0-38-12"), +(9314,"0-38-13"), +(9315,"0-38-14"), +(9316,"0-38-15"), +(9317,"0-38-16"), +(9318,"0-38-17"), +(9319,"0-38-18"), +(9320,"0-38-19"), +(9321,"0-38-20"), +(9322,"0-38-21"), +(9323,"0-38-22"), +(9324,"0-38-23"), +(9325,"0-38-24"), +(9326,"0-38-25"), +(9327,"0-38-26"), +(9328,"0-38-27"), +(9329,"0-38-28"), +(9330,"0-38-29"), +(9331,"0-38-30"), +(9332,"0-38-31"), +(9333,"0-38-32"), +(9334,"0-38-33"), +(9335,"0-38-34"), +(9336,"0-38-35"), +(9337,"0-38-36"), +(9338,"0-38-37"), +(9339,"0-38-38"), +(9340,"0-38-39"), +(9341,"0-38-40"), +(9342,"0-38-41"), +(9343,"0-38-42"), +(9344,"0-38-43"), +(9345,"0-38-44"), +(9346,"0-38-45"), +(9347,"0-38-46"), +(9348,"0-38-47"), +(9349,"0-38-48"), +(9350,"0-38-49"), +(9351,"1-37-0"), +(9352,"1-37-1"), +(9353,"1-37-2"), +(9354,"3-36-0"), +(9355,"1-37-3"), +(9356,"3-36-1"), +(9357,"1-37-4"), +(9358,"3-36-2"), +(9359,"1-37-5"), +(9360,"3-36-3"), +(9361,"1-37-6"), +(9362,"3-36-4"), +(9363,"1-37-7"), +(9364,"3-36-5"), +(9365,"1-37-8"), +(9366,"3-36-6"), +(9367,"1-37-9"), +(9368,"3-36-7"), +(9369,"1-37-10"), +(9370,"3-36-8"), +(9371,"1-37-11"), +(9372,"3-36-9"), +(9373,"1-37-12"), +(9374,"3-36-10"), +(9375,"1-37-13"), +(9376,"3-36-11"), +(9377,"1-37-14"), +(9378,"3-36-12"), +(9379,"1-37-15"), +(9380,"3-36-13"), +(9381,"1-37-16"), +(9382,"3-36-14"), +(9383,"1-37-17"), +(9384,"3-36-15"), +(9385,"1-37-18"), +(9386,"3-36-16"), +(9387,"1-37-19"), +(9388,"3-36-17"), +(9389,"1-37-20"), +(9390,"3-36-18"), +(9391,"1-37-21"), +(9392,"3-36-19"), +(9393,"1-37-22"), +(9394,"3-36-20"), +(9395,"1-37-23"), +(9396,"3-36-21"), +(9397,"1-37-24"), +(9398,"3-36-22"), +(9399,"1-37-25"), +(9400,"3-36-23"), +(9401,"1-37-26"), +(9402,"3-36-24"), +(9403,"1-37-27"), +(9404,"3-36-25"), +(9405,"1-37-28"), +(9406,"3-36-26"), +(9407,"1-37-29"), +(9408,"3-36-27"), +(9409,"1-37-30"), +(9410,"3-36-28"), +(9411,"1-37-31"), +(9412,"3-36-29"), +(9413,"1-37-32"), +(9414,"3-36-30"), +(9415,"1-37-33"), +(9416,"3-36-31"), +(9417,"1-37-34"), +(9418,"3-36-32"), +(9419,"1-37-35"), +(9420,"3-36-33"), +(9421,"1-37-36"), +(9422,"3-36-34"), +(9423,"1-37-37"), +(9424,"3-36-35"), +(9425,"1-37-38"), +(9426,"3-36-36"), +(9427,"1-37-39"), +(9428,"3-36-37"), +(9429,"1-37-40"), +(9430,"3-36-38"), +(9431,"3-36-39"), +(9432,"1-37-41"), +(9433,"3-36-40"), +(9434,"1-37-42"), +(9435,"3-36-41"), +(9436,"1-37-43"), +(9437,"3-36-42"), +(9438,"1-37-44"), +(9439,"3-36-43"), +(9440,"1-37-45"), +(9441,"3-36-44"), +(9442,"1-37-46"), +(9443,"3-36-45"), +(9444,"1-37-47"), +(9445,"1-37-48"), +(9446,"1-37-49"), +(9447,"3-36-46"), +(9448,"3-36-47"), +(9449,"3-36-48"), +(9450,"3-36-49"), +(9451,"2-37-0"), +(9452,"2-37-1"), +(9453,"2-37-2"), +(9454,"2-37-3"), +(9455,"2-37-4"), +(9456,"2-37-5"), +(9457,"2-37-6"), +(9458,"2-37-7"), +(9459,"2-37-8"), +(9460,"2-37-9"), +(9461,"2-37-10"), +(9462,"2-37-11"), +(9463,"2-37-12"), +(9464,"2-37-13"), +(9465,"2-37-14"), +(9466,"2-37-15"), +(9467,"2-37-16"), +(9468,"2-37-17"), +(9469,"2-37-18"), +(9470,"2-37-19"), +(9471,"2-37-20"), +(9472,"2-37-21"), +(9473,"2-37-22"), +(9474,"2-37-23"), +(9475,"2-37-24"), +(9476,"2-37-25"), +(9477,"2-37-26"), +(9478,"2-37-27"), +(9479,"2-37-28"), +(9480,"2-37-29"), +(9481,"2-37-30"), +(9482,"2-37-31"), +(9483,"2-37-32"), +(9484,"2-37-33"), +(9485,"2-37-34"), +(9486,"2-37-35"), +(9487,"2-37-36"), +(9488,"2-37-37"), +(9489,"2-37-38"), +(9490,"2-37-39"), +(9491,"2-37-40"), +(9492,"2-37-41"), +(9493,"2-37-42"), +(9494,"2-37-43"), +(9495,"2-37-44"), +(9496,"2-37-45"), +(9497,"2-37-46"), +(9498,"2-37-47"), +(9499,"2-37-48"), +(9500,"2-37-49"), +(9501,"0-39-0"), +(9502,"0-39-1"), +(9503,"0-39-2"), +(9504,"0-39-3"), +(9505,"0-39-4"), +(9506,"0-39-5"), +(9507,"0-39-6"), +(9508,"0-39-7"), +(9509,"0-39-8"), +(9510,"0-39-9"), +(9511,"0-39-10"), +(9512,"0-39-11"), +(9513,"0-39-12"), +(9514,"0-39-13"), +(9515,"0-39-14"), +(9516,"0-39-15"), +(9517,"0-39-16"), +(9518,"0-39-17"), +(9519,"0-39-18"), +(9520,"0-39-19"), +(9521,"0-39-20"), +(9522,"0-39-21"), +(9523,"0-39-22"), +(9524,"0-39-23"), +(9525,"0-39-24"), +(9526,"0-39-25"), +(9527,"0-39-26"), +(9528,"0-39-27"), +(9529,"0-39-28"), +(9530,"0-39-29"), +(9531,"0-39-30"), +(9532,"0-39-31"), +(9533,"0-39-32"), +(9534,"0-39-33"), +(9535,"0-39-34"), +(9536,"0-39-35"), +(9537,"0-39-36"), +(9538,"0-39-37"), +(9539,"0-39-38"), +(9540,"0-39-39"), +(9541,"0-39-40"), +(9542,"0-39-41"), +(9543,"0-39-42"), +(9544,"0-39-43"), +(9545,"0-39-44"), +(9546,"0-39-45"), +(9547,"0-39-46"), +(9548,"0-39-47"), +(9549,"0-39-48"), +(9550,"0-39-49"), +(9551,"4-38-0"), +(9552,"4-38-1"), +(9553,"4-38-2"), +(9554,"4-38-3"), +(9555,"4-38-4"), +(9556,"4-38-5"), +(9557,"4-38-6"), +(9558,"4-38-7"), +(9559,"4-38-8"), +(9560,"4-38-9"), +(9561,"4-38-10"), +(9562,"4-38-11"), +(9563,"4-38-12"), +(9564,"4-38-13"), +(9565,"4-38-14"), +(9566,"4-38-15"), +(9567,"4-38-16"), +(9568,"4-38-17"), +(9569,"4-38-18"), +(9570,"4-38-19"), +(9571,"4-38-20"), +(9572,"4-38-21"), +(9573,"4-38-22"), +(9574,"4-38-23"), +(9575,"4-38-24"), +(9576,"4-38-25"), +(9577,"4-38-26"), +(9578,"4-38-27"), +(9579,"4-38-28"), +(9580,"4-38-29"), +(9581,"4-38-30"), +(9582,"4-38-31"), +(9583,"4-38-32"), +(9584,"4-38-33"), +(9585,"4-38-34"), +(9586,"4-38-35"), +(9587,"4-38-36"), +(9588,"4-38-37"), +(9589,"4-38-38"), +(9590,"4-38-39"), +(9591,"4-38-40"), +(9592,"4-38-41"), +(9593,"4-38-42"), +(9594,"4-38-43"), +(9595,"4-38-44"), +(9596,"4-38-45"), +(9597,"4-38-46"), +(9598,"4-38-47"), +(9599,"4-38-48"), +(9600,"4-38-49"), +(9601,"1-38-0"), +(9602,"1-38-1"), +(9603,"1-38-2"), +(9604,"1-38-3"), +(9605,"1-38-4"), +(9606,"1-38-5"), +(9607,"1-38-6"), +(9608,"1-38-7"), +(9609,"1-38-8"), +(9610,"1-38-9"), +(9611,"1-38-10"), +(9612,"1-38-11"), +(9613,"1-38-12"), +(9614,"1-38-13"), +(9615,"1-38-14"), +(9616,"1-38-15"), +(9617,"1-38-16"), +(9618,"1-38-17"), +(9619,"1-38-18"), +(9620,"1-38-19"), +(9621,"1-38-20"), +(9622,"1-38-21"), +(9623,"1-38-22"), +(9624,"1-38-23"), +(9625,"1-38-24"), +(9626,"1-38-25"), +(9627,"1-38-26"), +(9628,"1-38-27"), +(9629,"1-38-28"), +(9630,"1-38-29"), +(9631,"1-38-30"), +(9632,"1-38-31"), +(9633,"1-38-32"), +(9634,"1-38-33"), +(9635,"1-38-34"), +(9636,"1-38-35"), +(9637,"1-38-36"), +(9638,"1-38-37"), +(9639,"1-38-38"), +(9640,"1-38-39"), +(9641,"1-38-40"), +(9642,"1-38-41"), +(9643,"1-38-42"), +(9644,"1-38-43"), +(9645,"1-38-44"), +(9646,"1-38-45"), +(9647,"3-37-0"), +(9648,"1-38-46"), +(9649,"3-37-1"), +(9650,"1-38-47"), +(9651,"3-37-2"), +(9652,"3-37-3"), +(9653,"1-38-48"), +(9654,"3-37-4"), +(9655,"3-37-5"), +(9656,"1-38-49"), +(9657,"3-37-6"), +(9658,"3-37-7"), +(9659,"3-37-8"), +(9660,"3-37-9"), +(9661,"3-37-10"), +(9662,"3-37-11"), +(9663,"3-37-12"), +(9664,"3-37-13"), +(9665,"3-37-14"), +(9666,"3-37-15"), +(9667,"3-37-16"), +(9668,"3-37-17"), +(9669,"3-37-18"), +(9670,"3-37-19"), +(9671,"3-37-20"), +(9672,"3-37-21"), +(9673,"3-37-22"), +(9674,"3-37-23"), +(9675,"3-37-24"), +(9676,"3-37-25"), +(9677,"3-37-26"), +(9678,"3-37-27"), +(9679,"3-37-28"), +(9680,"3-37-29"), +(9681,"3-37-30"), +(9682,"3-37-31"), +(9683,"3-37-32"), +(9684,"3-37-33"), +(9685,"3-37-34"), +(9686,"3-37-35"), +(9687,"3-37-36"), +(9688,"3-37-37"), +(9689,"3-37-38"), +(9690,"3-37-39"), +(9691,"3-37-40"), +(9692,"3-37-41"), +(9693,"3-37-42"), +(9694,"3-37-43"), +(9695,"3-37-44"), +(9696,"3-37-45"), +(9697,"3-37-46"), +(9698,"3-37-47"), +(9699,"3-37-48"), +(9700,"3-37-49"), +(9701,"1-39-0"), +(9702,"1-39-1"), +(9703,"2-38-0"), +(9704,"1-39-2"), +(9705,"1-39-3"), +(9706,"2-38-1"), +(9707,"2-38-2"), +(9708,"1-39-4"), +(9709,"2-38-3"), +(9710,"1-39-5"), +(9711,"1-39-6"), +(9712,"2-38-4"), +(9713,"1-39-7"), +(9714,"2-38-5"), +(9715,"1-39-8"), +(9716,"2-38-6"), +(9717,"1-39-9"), +(9718,"4-39-0"), +(9719,"2-38-7"), +(9720,"1-39-10"), +(9721,"2-38-8"), +(9722,"1-39-11"), +(9723,"4-39-1"), +(9724,"2-38-9"), +(9725,"1-39-12"), +(9726,"4-39-2"), +(9727,"2-38-10"), +(9728,"1-39-13"), +(9729,"2-38-11"), +(9730,"4-39-3"), +(9731,"1-39-14"), +(9732,"2-38-12"), +(9733,"1-39-15"), +(9734,"4-39-4"), +(9735,"2-38-13"), +(9736,"1-39-16"), +(9737,"4-39-5"), +(9738,"2-38-14"), +(9739,"1-39-17"), +(9740,"4-39-6"), +(9741,"1-39-18"), +(9742,"2-38-15"), +(9743,"4-39-7"), +(9744,"1-39-19"), +(9745,"2-38-16"), +(9746,"4-39-8"), +(9747,"1-39-20"), +(9748,"2-38-17"), +(9749,"1-39-21"), +(9750,"4-39-9"), +(9751,"2-38-18"), +(9752,"1-39-22"), +(9753,"4-39-10"), +(9754,"2-38-19"), +(9755,"1-39-23"), +(9756,"4-39-11"), +(9757,"1-39-24"), +(9758,"2-38-20"), +(9759,"4-39-12"), +(9760,"1-39-25"), +(9761,"2-38-21"), +(9762,"4-39-13"), +(9763,"1-39-26"), +(9764,"2-38-22"), +(9765,"4-39-14"), +(9766,"1-39-27"), +(9767,"2-38-23"), +(9768,"4-39-15"), +(9769,"1-39-28"), +(9770,"2-38-24"), +(9771,"4-39-16"), +(9772,"1-39-29"), +(9773,"2-38-25"), +(9774,"4-39-17"), +(9775,"1-39-30"), +(9776,"2-38-26"), +(9777,"4-39-18"), +(9778,"1-39-31"), +(9779,"2-38-27"), +(9780,"4-39-19"), +(9781,"1-39-32"), +(9782,"2-38-28"), +(9783,"4-39-20"), +(9784,"1-39-33"), +(9785,"2-38-29"), +(9786,"4-39-21"), +(9787,"1-39-34"), +(9788,"2-38-30"), +(9789,"4-39-22"), +(9790,"1-39-35"), +(9791,"2-38-31"), +(9792,"4-39-23"), +(9793,"1-39-36"), +(9794,"3-38-0"), +(9795,"4-39-24"), +(9796,"1-39-37"), +(9797,"2-38-32"), +(9798,"4-39-25"), +(9799,"1-39-38"), +(9800,"2-38-33"), +(9801,"3-38-1"), +(9802,"4-39-26"), +(9803,"1-39-39"), +(9804,"2-38-34"), +(9805,"1-39-40"), +(9806,"3-38-2"), +(9807,"2-38-35"), +(9808,"1-39-41"), +(9809,"4-39-27"), +(9810,"2-38-36"), +(9811,"1-39-42"), +(9812,"3-38-3"), +(9813,"2-38-37"), +(9814,"1-39-43"), +(9815,"4-39-28"), +(9816,"2-38-38"), +(9817,"1-39-44"), +(9818,"3-38-4"), +(9819,"4-39-29"), +(9820,"2-38-39"), +(9821,"1-39-45"), +(9822,"4-39-30"), +(9823,"3-38-5"), +(9824,"1-39-46"), +(9825,"2-38-40"), +(9826,"4-39-31"), +(9827,"1-39-47"), +(9828,"2-38-41"), +(9829,"4-39-32"), +(9830,"1-39-48"), +(9831,"3-38-6"), +(9832,"2-38-42"), +(9833,"4-39-33"), +(9834,"1-39-49"), +(9835,"2-38-43"), +(9836,"3-38-7"), +(9837,"4-39-34"), +(9838,"2-38-44"), +(9839,"4-39-35"), +(9840,"3-38-8"), +(9841,"2-38-45"), +(9842,"4-39-36"), +(9843,"2-38-46"), +(9844,"3-38-9"), +(9845,"4-39-37"), +(9846,"2-38-47"), +(9847,"4-39-38"), +(9848,"3-38-10"), +(9849,"2-38-48"), +(9850,"4-39-39"), +(9851,"2-38-49"), +(9852,"3-38-11"), +(9853,"4-39-40"), +(9854,"4-39-41"), +(9855,"3-38-12"), +(9856,"4-39-42"), +(9857,"3-38-13"), +(9858,"4-39-43"), +(9859,"4-39-44"), +(9860,"3-38-14"), +(9861,"4-39-45"), +(9862,"4-39-46"), +(9863,"3-38-15"), +(9864,"4-39-47"), +(9865,"4-39-48"), +(9866,"3-38-16"), +(9867,"4-39-49"), +(9868,"3-38-17"), +(9869,"3-38-18"), +(9870,"3-38-19"), +(9871,"3-38-20"), +(9872,"3-38-21"), +(9873,"3-38-22"), +(9874,"3-38-23"), +(9875,"3-38-24"), +(9876,"3-38-25"), +(9877,"3-38-26"), +(9878,"3-38-27"), +(9879,"3-38-28"), +(9880,"3-38-29"), +(9881,"3-38-30"), +(9882,"3-38-31"), +(9883,"3-38-32"), +(9884,"3-38-33"), +(9885,"3-38-34"), +(9886,"3-38-35"), +(9887,"3-38-36"), +(9888,"3-38-37"), +(9889,"3-38-38"), +(9890,"3-38-39"), +(9891,"3-38-40"), +(9892,"3-38-41"), +(9893,"3-38-42"), +(9894,"3-38-43"), +(9895,"3-38-44"), +(9896,"3-38-45"), +(9897,"3-38-46"), +(9898,"3-38-47"), +(9899,"3-38-48"), +(9900,"3-38-49"), +(9901,"3-39-0"), +(9902,"3-39-1"), +(9903,"3-39-2"), +(9904,"3-39-3"), +(9905,"3-39-4"), +(9906,"3-39-5"), +(9907,"3-39-6"), +(9908,"3-39-7"), +(9909,"3-39-8"), +(9910,"3-39-9"), +(9911,"3-39-10"), +(9912,"3-39-11"), +(9913,"3-39-12"), +(9914,"3-39-13"), +(9915,"3-39-14"), +(9916,"3-39-15"), +(9917,"3-39-16"), +(9918,"3-39-17"), +(9919,"3-39-18"), +(9920,"3-39-19"), +(9921,"3-39-20"), +(9922,"3-39-21"), +(9923,"3-39-22"), +(9924,"3-39-23"), +(9925,"3-39-24"), +(9926,"3-39-25"), +(9927,"3-39-26"), +(9928,"3-39-27"), +(9929,"3-39-28"), +(9930,"3-39-29"), +(9931,"3-39-30"), +(9932,"3-39-31"), +(9933,"3-39-32"), +(9934,"3-39-33"), +(9935,"3-39-34"), +(9936,"3-39-35"), +(9937,"3-39-36"), +(9938,"3-39-37"), +(9939,"3-39-38"), +(9940,"3-39-39"), +(9941,"3-39-40"), +(9942,"3-39-41"), +(9943,"3-39-42"), +(9944,"3-39-43"), +(9945,"3-39-44"), +(9946,"3-39-45"), +(9947,"3-39-46"), +(9948,"3-39-47"), +(9949,"3-39-48"), +(9950,"3-39-49"), +(9951,"2-39-0"), +(9952,"2-39-1"), +(9953,"2-39-2"), +(9954,"2-39-3"), +(9955,"2-39-4"), +(9956,"2-39-5"), +(9957,"2-39-6"), +(9958,"2-39-7"), +(9959,"2-39-8"), +(9960,"2-39-9"), +(9961,"2-39-10"), +(9962,"2-39-11"), +(9963,"2-39-12"), +(9964,"2-39-13"), +(9965,"2-39-14"), +(9966,"2-39-15"), +(9967,"2-39-16"), +(9968,"2-39-17"), +(9969,"2-39-18"), +(9970,"2-39-19"), +(9971,"2-39-20"), +(9972,"2-39-21"), +(9973,"2-39-22"), +(9974,"2-39-23"), +(9975,"2-39-24"), +(9976,"2-39-25"), +(9977,"2-39-26"), +(9978,"2-39-27"), +(9979,"2-39-28"), +(9980,"2-39-29"), +(9981,"2-39-30"), +(9982,"2-39-31"), +(9983,"2-39-32"), +(9984,"2-39-33"), +(9985,"2-39-34"), +(9986,"2-39-35"), +(9987,"2-39-36"), +(9988,"2-39-37"), +(9989,"2-39-38"), +(9990,"2-39-39"), +(9991,"2-39-40"), +(9992,"2-39-41"), +(9993,"2-39-42"), +(9994,"2-39-43"), +(9995,"2-39-44"), +(9996,"2-39-45"), +(9997,"2-39-46"), +(9998,"2-39-47"), +(9999,"2-39-48"), +(10000,"2-39-49"); \ No newline at end of file diff --git a/pkg/lightning/mydump/examples/mocker_test.tbl_multi_index-schema.sql b/pkg/lightning/mydump/examples/mocker_test.tbl_multi_index-schema.sql new file mode 100644 index 000000000..bc7a3c103 --- /dev/null +++ b/pkg/lightning/mydump/examples/mocker_test.tbl_multi_index-schema.sql @@ -0,0 +1,9 @@ +/*!40101 SET NAMES binary*/; +/*!40014 SET FOREIGN_KEY_CHECKS=0*/; + +CREATE TABLE `tbl_multi_index` ( + `Name` varchar(64) DEFAULT NULL, + `Age` int(10) UNSIGNED DEFAULT NULL, + KEY `idx_name` (`Name`), + KEY `idx_age_name` (`Age`,`Name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; diff --git a/pkg/lightning/mydump/examples/mocker_test.tbl_multi_index.sql b/pkg/lightning/mydump/examples/mocker_test.tbl_multi_index.sql new file mode 100644 index 000000000..bed1e4b87 --- /dev/null +++ b/pkg/lightning/mydump/examples/mocker_test.tbl_multi_index.sql @@ -0,0 +1,10010 @@ +/*!40101 SET NAMES binary*/; +/*!40014 SET FOREIGN_KEY_CHECKS=0*/; +/*!40103 SET TIME_ZONE='+00:00' */; +INSERT INTO `tbl_multi_index` VALUES +("0+0+0",0), +("1+0+0",0); +INSERT INTO `tbl_multi_index` VALUES +("4+0+0",0), +("1+0+1",0), +("3+0+0",0), +("0+0+1",0), +("2+0+0",0), +("4+0+1",0), +("0+0+2",0), +("0+0+3",0), +("3+0+1",0), +("2+0+1",0), +("0+0+4",0), +("1+0+2",0), +("3+0+2",0), +("1+0+3",0), +("2+0+2",0), +("3+0+3",0), +("1+0+4",0), +("0+0+5",0), +("4+0+2",0), +("0+0+6",0), +("1+0+5",0), +("0+0+7",0), +("4+0+3",0), +("1+0+6",0), +("3+0+4",0), +("4+0+4",0), +("2+0+3",0), +("2+0+4",0), +("0+0+8",0), +("1+0+7",0), +("2+0+5",0), +("2+0+6",0), +("3+0+5",0), +("1+0+8",0), +("4+0+5",0), +("1+0+9",0), +("1+0+10",0), +("2+0+7",0), +("3+0+6",0), +("4+0+6",0), +("2+0+8",0), +("4+0+7",0), +("1+0+11",0), +("4+0+8",0), +("3+0+7",0), +("2+0+9",0), +("1+0+12",0), +("3+0+8",0), +("0+0+9",0), +("4+0+9",0), +("2+0+10",0), +("1+0+13",0), +("3+0+9",0), +("4+0+10",0), +("0+0+10",0), +("2+0+11",0), +("2+0+12",0), +("0+0+11",0), +("0+0+12",0), +("2+0+13",0), +("4+0+11",0), +("0+0+13",0), +("4+0+12",0), +("3+0+10",0), +("0+0+14",0), +("1+0+14",0), +("4+0+13",0), +("4+0+14",0), +("3+0+11",0), +("1+0+15",0), +("1+0+16",0), +("2+0+14",0), +("2+0+15",0), +("0+0+15",0), +("1+0+17",0), +("3+0+12",0), +("4+0+15",0), +("0+0+16",0), +("2+0+16",0), +("1+0+18",0), +("4+0+16",0), +("1+0+19",0), +("1+0+20",0), +("3+0+13",0), +("3+0+14",0), +("0+0+17",0), +("4+0+17",0), +("0+0+18",0), +("1+0+21",0), +("2+0+17",0), +("2+0+18",0), +("2+0+19",0), +("4+0+18",0), +("3+0+15",0), +("2+0+20",0), +("4+0+19",0), +("4+0+20",0), +("0+0+19",0), +("4+0+21",0), +("1+0+22",0), +("3+0+16",0), +("1+0+23",0), +("0+0+20",0), +("3+0+17",0), +("4+0+22",0), +("3+0+18",0), +("3+0+19",0), +("1+0+24",0), +("0+0+21",0), +("4+0+23",0), +("3+0+20",0), +("3+0+21",0), +("0+0+22",0), +("3+0+22",0), +("3+0+23",0), +("1+0+25",0), +("1+0+26",0), +("0+0+23",0), +("3+0+24",0), +("2+0+21",0), +("3+0+25",0), +("4+0+24",0), +("3+0+26",0), +("1+0+27",0), +("3+0+27",0), +("0+0+24",0), +("2+0+22",0), +("2+0+23",0), +("2+0+24",0), +("4+0+25",0), +("4+0+26",0), +("3+0+28",0), +("3+0+29",0), +("3+0+30",0), +("0+0+25",0), +("0+0+26",0), +("0+0+27",0), +("4+0+27",0), +("1+0+28",0), +("1+0+29",0), +("4+0+28",0), +("0+0+28",0), +("4+0+29",0), +("0+0+29",0), +("1+0+30",0), +("4+0+30",0), +("1+0+31",0), +("1+0+32",0), +("1+0+33",0), +("3+0+31",0), +("0+0+30",0), +("4+0+31",0), +("1+0+34",0), +("0+0+31",0), +("0+0+32",0), +("4+0+32",0), +("1+0+35",0), +("4+0+33",0), +("0+0+33",0), +("1+0+36",0), +("1+0+37",0), +("1+0+38",0), +("3+0+32",0), +("1+0+39",0), +("0+0+34",0), +("1+0+40",0), +("3+0+33",0); +INSERT INTO `tbl_multi_index` VALUES +("1+0+41",0), +("2+0+25",0), +("2+0+26",0), +("4+0+34",0), +("1+0+42",0), +("1+0+43",0), +("3+0+34",0), +("2+0+27",0), +("1+0+44",0), +("3+0+35",0), +("2+0+28",0), +("2+0+29",0), +("0+0+35",0), +("0+0+36",0), +("2+0+30",0), +("1+0+45",0), +("0+0+37",0), +("0+0+38",0), +("2+0+31",0), +("4+0+35",0), +("1+0+46",0), +("2+0+32",0), +("2+0+33",0), +("0+0+39",0), +("0+0+40",0), +("2+0+34",0), +("2+0+35",0), +("4+0+36",0), +("0+0+41",0), +("3+0+36",0), +("0+0+42",0), +("0+0+43",0), +("3+0+37",0), +("3+0+38",0), +("4+0+37",0), +("0+0+44",0), +("3+0+39",0), +("0+0+45",0), +("4+0+38",0), +("2+0+36",0), +("2+0+37",0), +("4+0+39",0), +("1+0+47",0), +("2+0+38",0), +("2+0+39",0), +("4+0+40",0), +("1+0+48",0), +("3+0+40",0), +("3+0+41",0), +("0+0+46",0), +("3+0+42",0), +("3+0+43",0), +("3+0+44",0), +("3+0+45",0), +("0+0+47",0), +("1+0+49",0), +("2+0+40",0), +("3+0+46",0), +("4+0+41",0), +("2+0+41",0), +("4+0+42",0), +("3+0+47",0), +("0+0+48",0), +("1+1+0",0), +("2+0+42",0), +("3+0+48",0), +("2+0+43",0), +("3+0+49",0), +("4+0+43",0), +("4+0+44",0), +("1+1+1",1), +("0+0+49",0), +("2+0+44",0), +("1+1+2",2), +("2+0+45",0), +("1+1+3",3), +("3+1+0",0), +("1+1+4",4), +("4+0+45",0), +("1+1+5",5), +("3+1+1",1), +("0+1+0",0), +("4+0+46",0), +("1+1+6",6), +("1+1+7",7), +("0+1+1",1), +("2+0+46",0), +("0+1+2",2), +("3+1+2",2), +("3+1+3",3), +("4+0+47",0), +("2+0+47",0), +("3+1+4",4), +("2+0+48",0), +("4+0+48",0), +("3+1+5",5); +INSERT INTO `tbl_multi_index` VALUES +("0+1+3",3), +("3+1+6",6), +("4+0+49",0), +("1+1+8",8), +("0+1+4",4), +("1+1+9",9), +("0+1+5",5), +("1+1+10",10), +("1+1+11",11), +("0+1+6",6), +("1+1+12",12), +("2+0+49",0), +("0+1+7",7), +("3+1+7",7), +("0+1+8",8), +("3+1+8",8), +("0+1+9",9), +("0+1+10",10), +("3+1+9",9), +("1+1+13",13), +("3+1+10",10), +("4+1+0",0), +("2+1+0",0), +("3+1+11",11), +("1+1+14",14), +("2+1+1",1), +("3+1+12",12), +("3+1+13",13), +("4+1+1",1), +("0+1+11",11), +("3+1+14",14), +("2+1+2",2), +("0+1+12",12), +("3+1+15",15), +("1+1+15",15), +("2+1+3",3), +("4+1+2",2), +("1+1+16",16), +("0+1+13",13), +("2+1+4",4), +("1+1+17",17), +("4+1+3",3), +("2+1+5",5), +("4+1+4",4), +("0+1+14",14), +("4+1+5",5), +("3+1+16",16), +("3+1+17",17), +("4+1+6",6), +("3+1+18",18), +("1+1+18",18), +("0+1+15",15), +("0+1+16",16), +("0+1+17",17), +("0+1+18",18), +("0+1+19",19), +("2+1+6",6), +("4+1+7",7), +("1+1+19",19), +("2+1+7",7), +("0+1+20",20), +("4+1+8",8), +("4+1+9",9), +("3+1+19",19), +("3+1+20",20), +("0+1+21",21), +("1+1+20",20), +("0+1+22",22), +("1+1+21",21), +("3+1+21",21), +("1+1+22",22), +("1+1+23",23), +("3+1+22",22), +("2+1+8",8), +("2+1+9",9), +("1+1+24",24), +("4+1+10",10), +("0+1+23",23), +("2+1+10",10), +("2+1+11",11), +("2+1+12",12), +("1+1+25",25), +("1+1+26",26), +("1+1+27",27), +("2+1+13",13), +("3+1+23",23), +("2+1+14",14), +("0+1+24",24), +("3+1+24",24), +("1+1+28",28), +("1+1+29",29), +("2+1+15",15), +("2+1+16",16), +("3+1+25",25), +("0+1+25",25), +("1+1+30",30), +("4+1+11",11), +("1+1+31",31), +("3+1+26",26), +("1+1+32",32), +("4+1+12",12), +("0+1+26",26), +("3+1+27",27), +("0+1+27",27), +("1+1+33",33), +("1+1+34",34), +("1+1+35",35), +("1+1+36",36), +("4+1+13",13), +("1+1+37",37), +("3+1+28",28), +("0+1+28",28), +("0+1+29",29), +("2+1+17",17), +("0+1+30",30), +("2+1+18",18), +("3+1+29",29); +INSERT INTO `tbl_multi_index` VALUES +("2+1+19",19), +("4+1+14",14), +("4+1+15",15), +("3+1+30",30), +("0+1+31",31), +("3+1+31",31), +("3+1+32",32), +("3+1+33",33), +("0+1+32",32), +("2+1+20",20), +("0+1+33",33), +("2+1+21",21), +("1+1+38",38), +("1+1+39",39), +("1+1+40",40), +("3+1+34",34), +("2+1+22",22), +("4+1+16",16), +("2+1+23",23), +("3+1+35",35), +("1+1+41",41), +("3+1+36",36), +("3+1+37",37), +("0+1+34",34), +("1+1+42",42), +("0+1+35",35), +("1+1+43",43), +("4+1+17",17), +("4+1+18",18), +("0+1+36",36), +("1+1+44",44), +("2+1+24",24), +("4+1+19",19), +("4+1+20",20), +("1+1+45",45), +("1+1+46",46), +("0+1+37",37), +("3+1+38",38), +("3+1+39",39), +("0+1+38",38), +("3+1+40",40), +("0+1+39",39), +("0+1+40",40), +("0+1+41",41), +("2+1+25",25), +("4+1+21",21), +("3+1+41",41), +("4+1+22",22), +("1+1+47",47), +("3+1+42",42), +("1+1+48",48), +("1+1+49",49), +("2+1+26",26), +("4+1+23",23), +("3+1+43",43), +("3+1+44",44), +("0+1+42",42), +("4+1+24",24), +("2+1+27",27), +("4+1+25",25), +("3+1+45",45), +("2+1+28",28), +("4+1+26",26), +("3+1+46",46), +("0+1+43",43), +("3+1+47",47), +("3+1+48",48), +("3+1+49",49), +("4+1+27",27), +("1+2+0",0), +("1+2+1",2), +("0+1+44",44), +("4+1+28",28), +("2+1+29",29), +("1+2+2",4), +("0+1+45",45), +("1+2+3",6), +("1+2+4",8), +("3+2+0",0), +("2+1+30",30), +("3+2+1",2), +("4+1+29",29), +("0+1+46",46), +("2+1+31",31), +("3+2+2",4), +("3+2+3",6), +("0+1+47",47), +("3+2+4",8), +("0+1+48",48), +("4+1+30",30), +("4+1+31",31), +("0+1+49",49), +("4+1+32",32), +("2+1+32",32), +("4+1+33",33), +("4+1+34",34), +("3+2+5",10), +("4+1+35",35), +("2+1+33",33), +("1+2+5",10), +("2+1+34",34), +("3+2+6",12), +("4+1+36",36), +("3+2+7",14), +("4+1+37",37), +("2+1+35",35), +("0+2+0",0), +("4+1+38",38), +("2+1+36",36), +("4+1+39",39), +("2+1+37",37), +("2+1+38",38), +("0+2+1",2), +("2+1+39",39), +("1+2+6",12), +("2+1+40",40), +("1+2+7",14), +("4+1+40",40), +("3+2+8",16), +("0+2+2",4), +("2+1+41",41), +("4+1+41",41), +("1+2+8",16), +("3+2+9",18), +("0+2+3",6), +("2+1+42",42), +("1+2+9",18), +("1+2+10",20), +("3+2+10",20), +("1+2+11",22), +("0+2+4",8), +("4+1+42",42), +("2+1+43",43), +("3+2+11",22), +("4+1+43",43), +("0+2+5",10), +("1+2+12",24), +("0+2+6",12), +("0+2+7",14), +("1+2+13",26), +("0+2+8",16), +("1+2+14",28), +("1+2+15",30), +("4+1+44",44), +("1+2+16",32), +("4+1+45",45), +("3+2+12",24), +("4+1+46",46), +("2+1+44",44), +("4+1+47",47), +("1+2+17",34), +("4+1+48",48), +("2+1+45",45), +("3+2+13",26), +("0+2+9",18), +("2+1+46",46), +("4+1+49",49), +("0+2+10",20), +("2+1+47",47), +("3+2+14",28), +("0+2+11",22), +("2+1+48",48), +("1+2+18",36), +("2+1+49",49), +("0+2+12",24), +("3+2+15",30), +("0+2+13",26), +("1+2+19",38), +("0+2+14",28), +("1+2+20",40), +("1+2+21",42), +("2+2+0",0), +("1+2+22",44), +("1+2+23",46), +("4+2+0",0), +("0+2+15",30), +("4+2+1",2), +("2+2+1",2), +("3+2+16",32), +("2+2+2",4), +("0+2+16",32), +("0+2+17",34), +("2+2+3",6), +("3+2+17",34), +("3+2+18",36), +("2+2+4",8), +("0+2+18",36), +("2+2+5",10), +("1+2+24",48), +("2+2+6",12), +("3+2+19",38), +("4+2+2",4), +("3+2+20",40), +("2+2+7",14), +("1+2+25",50), +("1+2+26",52), +("2+2+8",16), +("4+2+3",6), +("2+2+9",18), +("3+2+21",42), +("4+2+4",8), +("3+2+22",44), +("0+2+19",38), +("0+2+20",40), +("2+2+10",20), +("1+2+27",54), +("0+2+21",42), +("2+2+11",22), +("2+2+12",24), +("4+2+5",10), +("2+2+13",26), +("2+2+14",28), +("1+2+28",56), +("4+2+6",12), +("2+2+15",30), +("1+2+29",58), +("3+2+23",46), +("0+2+22",44), +("4+2+7",14), +("1+2+30",60), +("2+2+16",32), +("3+2+24",48), +("0+2+23",46), +("4+2+8",16), +("2+2+17",34), +("2+2+18",36), +("0+2+24",48), +("0+2+25",50), +("2+2+19",38), +("2+2+20",40), +("1+2+31",62), +("2+2+21",42), +("0+2+26",52), +("1+2+32",64), +("1+2+33",66), +("2+2+22",44), +("0+2+27",54), +("0+2+28",56), +("1+2+34",68), +("3+2+25",50), +("3+2+26",52), +("2+2+23",46), +("3+2+27",54), +("3+2+28",56), +("2+2+24",48), +("1+2+35",70), +("3+2+29",58), +("2+2+25",50), +("2+2+26",52), +("0+2+29",58), +("2+2+27",54), +("1+2+36",72), +("4+2+9",18), +("4+2+10",20), +("2+2+28",56), +("1+2+37",74), +("0+2+30",60), +("1+2+38",76), +("4+2+11",22), +("0+2+31",62), +("4+2+12",24), +("2+2+29",58), +("3+2+30",60), +("2+2+30",60), +("3+2+31",62), +("1+2+39",78), +("4+2+13",26), +("1+2+40",80), +("2+2+31",62), +("1+2+41",82), +("1+2+42",84), +("1+2+43",86), +("2+2+32",64), +("0+2+32",64), +("0+2+33",66), +("0+2+34",68), +("3+2+32",64), +("4+2+14",28), +("0+2+35",70), +("3+2+33",66), +("3+2+34",68), +("1+2+44",88), +("1+2+45",90), +("4+2+15",30), +("1+2+46",92), +("3+2+35",70), +("1+2+47",94), +("1+2+48",96), +("0+2+36",72), +("3+2+36",72), +("4+2+16",32), +("2+2+33",66), +("3+2+37",74), +("3+2+38",76), +("1+2+49",98), +("4+2+17",34), +("4+2+18",36), +("0+2+37",74), +("3+2+39",78), +("0+2+38",76), +("3+2+40",80), +("4+2+19",38), +("3+2+41",82), +("1+3+0",0), +("1+3+1",3), +("4+2+20",40), +("4+2+21",42), +("4+2+22",44), +("0+2+39",78), +("0+2+40",80), +("0+2+41",82), +("4+2+23",46), +("4+2+24",48), +("2+2+34",68), +("0+2+42",84), +("2+2+35",70), +("0+2+43",86), +("1+3+2",6), +("4+2+25",50), +("4+2+26",52), +("4+2+27",54), +("3+2+42",84), +("4+2+28",56), +("2+2+36",72), +("3+2+43",86), +("1+3+3",9), +("2+2+37",74), +("0+2+44",88), +("4+2+29",58), +("2+2+38",76), +("0+2+45",90), +("4+2+30",60), +("3+2+44",88), +("0+2+46",92), +("2+2+39",78), +("1+3+4",12), +("4+2+31",62), +("4+2+32",64), +("3+2+45",90), +("1+3+5",15), +("0+2+47",94), +("3+2+46",92), +("3+2+47",94), +("1+3+6",18), +("0+2+48",96), +("1+3+7",21), +("0+2+49",98), +("1+3+8",24), +("4+2+33",66), +("2+2+40",80), +("4+2+34",68), +("1+3+9",27), +("2+2+41",82), +("1+3+10",30), +("4+2+35",70), +("1+3+11",33), +("2+2+42",84), +("0+3+0",0), +("4+2+36",72), +("3+2+48",96), +("0+3+1",3), +("0+3+2",6), +("1+3+12",36), +("3+2+49",98), +("4+2+37",74), +("0+3+3",9), +("0+3+4",12), +("4+2+38",76), +("2+2+43",86), +("0+3+5",15), +("4+2+39",78), +("1+3+13",39), +("1+3+14",42), +("0+3+6",18), +("4+2+40",80), +("0+3+7",21), +("4+2+41",82), +("1+3+15",45), +("2+2+44",88), +("0+3+8",24), +("1+3+16",48), +("2+2+45",90), +("0+3+9",27), +("3+3+0",0), +("1+3+17",51), +("3+3+1",3), +("0+3+10",30), +("4+2+42",84), +("0+3+11",33), +("2+2+46",92), +("4+2+43",86), +("1+3+18",54), +("4+2+44",88); +INSERT INTO `tbl_multi_index` VALUES +("4+2+45",90), +("2+2+47",94), +("4+2+46",92), +("1+3+19",57), +("2+2+48",96), +("4+2+47",94), +("2+2+49",98), +("1+3+20",60), +("1+3+21",63), +("1+3+22",66), +("0+3+12",36), +("3+3+2",6), +("0+3+13",39), +("4+2+48",96), +("3+3+3",9), +("4+2+49",98), +("2+3+0",0), +("1+3+23",69), +("0+3+14",42), +("1+3+24",72), +("0+3+15",45), +("0+3+16",48), +("2+3+1",3), +("1+3+25",75), +("0+3+17",51), +("0+3+18",54), +("0+3+19",57), +("1+3+26",78), +("3+3+4",12), +("0+3+20",60), +("2+3+2",6), +("4+3+0",0), +("1+3+27",81), +("4+3+1",3), +("1+3+28",84), +("2+3+3",9), +("4+3+2",6), +("0+3+21",63), +("2+3+4",12), +("4+3+3",9), +("3+3+5",15), +("4+3+4",12), +("3+3+6",18), +("3+3+7",21), +("0+3+22",66), +("1+3+29",87), +("4+3+5",15), +("0+3+23",69), +("3+3+8",24), +("4+3+6",18), +("2+3+5",15), +("2+3+6",18), +("0+3+24",72), +("0+3+25",75), +("3+3+9",27), +("4+3+7",21), +("0+3+26",78), +("1+3+30",90), +("4+3+8",24), +("2+3+7",21), +("4+3+9",27), +("4+3+10",30), +("0+3+27",81), +("1+3+31",93), +("2+3+8",24), +("0+3+28",84), +("1+3+32",96), +("0+3+29",87), +("4+3+11",33), +("0+3+30",90), +("1+3+33",99); +INSERT INTO `tbl_multi_index` VALUES +("2+3+9",27), +("3+3+10",30), +("3+3+11",33), +("1+3+34",102), +("0+3+31",93), +("2+3+10",30), +("1+3+35",105), +("2+3+11",33), +("1+3+36",108), +("0+3+32",96), +("4+3+12",36), +("2+3+12",36), +("2+3+13",39), +("4+3+13",39), +("3+3+12",36), +("0+3+33",99), +("3+3+13",39), +("4+3+14",42), +("1+3+37",111), +("0+3+34",102), +("4+3+15",45), +("1+3+38",114), +("1+3+39",117), +("3+3+14",42), +("4+3+16",48), +("1+3+40",120), +("0+3+35",105), +("0+3+36",108), +("2+3+14",42), +("2+3+15",45), +("0+3+37",111), +("2+3+16",48), +("1+3+41",123), +("0+3+38",114), +("1+3+42",126), +("3+3+15",45), +("2+3+17",51), +("3+3+16",48), +("1+3+43",129), +("0+3+39",117), +("4+3+17",51), +("3+3+17",51), +("4+3+18",54), +("1+3+44",132), +("1+3+45",135), +("0+3+40",120), +("4+3+19",57), +("0+3+41",123), +("3+3+18",54), +("4+3+20",60), +("1+3+46",138), +("1+3+47",141), +("3+3+19",57), +("2+3+18",54), +("4+3+21",63), +("2+3+19",57), +("0+3+42",126), +("2+3+20",60), +("3+3+20",60), +("1+3+48",144), +("4+3+22",66), +("0+3+43",129), +("0+3+44",132), +("2+3+21",63), +("1+3+49",147), +("2+3+22",66), +("4+3+23",69), +("0+3+45",135), +("4+3+24",72), +("0+3+46",138), +("3+3+21",63), +("4+3+25",75), +("3+3+22",66), +("0+3+47",141), +("3+3+23",69), +("2+3+23",69), +("1+4+0",0), +("2+3+24",72), +("3+3+24",72), +("4+3+26",78), +("1+4+1",4), +("1+4+2",8), +("3+3+25",75), +("0+3+48",144), +("2+3+25",75), +("2+3+26",78), +("0+3+49",147), +("4+3+27",81), +("1+4+3",12), +("2+3+27",81), +("2+3+28",84), +("3+3+26",78), +("4+3+28",84), +("0+4+0",0), +("2+3+29",87), +("4+3+29",87), +("3+3+27",81), +("1+4+4",16), +("2+3+30",90), +("1+4+5",20), +("2+3+31",93), +("4+3+30",90), +("3+3+28",84), +("4+3+31",93), +("0+4+1",4), +("2+3+32",96), +("4+3+32",96), +("3+3+29",87), +("1+4+6",24), +("0+4+2",8), +("2+3+33",99), +("4+3+33",99), +("3+3+30",90), +("2+3+34",102), +("0+4+3",12), +("2+3+35",105), +("4+3+34",102), +("3+3+31",93), +("4+3+35",105), +("1+4+7",28), +("2+3+36",108), +("0+4+4",16), +("3+3+32",96), +("2+3+37",111), +("3+3+33",99), +("2+3+38",114), +("4+3+36",108), +("1+4+8",32), +("3+3+34",102), +("0+4+5",20), +("1+4+9",36), +("4+3+37",111), +("0+4+6",24), +("2+3+39",117), +("3+3+35",105), +("2+3+40",120), +("1+4+10",40), +("0+4+7",28), +("3+3+36",108), +("1+4+11",44), +("4+3+38",114), +("0+4+8",32), +("1+4+12",48), +("0+4+9",36), +("4+3+39",117), +("2+3+41",123), +("0+4+10",40), +("1+4+13",52), +("1+4+14",56), +("0+4+11",44), +("1+4+15",60), +("4+3+40",120), +("3+3+37",111), +("4+3+41",123), +("3+3+38",114), +("0+4+12",48), +("1+4+16",64), +("0+4+13",52), +("3+3+39",117), +("1+4+17",68), +("2+3+42",126), +("1+4+18",72), +("0+4+14",56), +("1+4+19",76), +("0+4+15",60), +("1+4+20",80), +("4+3+42",126), +("1+4+21",84), +("0+4+16",64), +("1+4+22",88), +("3+3+40",120), +("3+3+41",123), +("0+4+17",68), +("3+3+42",126), +("4+3+43",129), +("3+3+43",129), +("3+3+44",132), +("1+4+23",92), +("0+4+18",72), +("4+3+44",132), +("3+3+45",135), +("4+3+45",135), +("0+4+19",76), +("4+3+46",138), +("3+3+46",138), +("4+3+47",141), +("2+3+43",129), +("4+3+48",144), +("1+4+24",96), +("0+4+20",80), +("0+4+21",84), +("1+4+25",100), +("2+3+44",132), +("4+3+49",147), +("3+3+47",141), +("3+3+48",144), +("0+4+22",88), +("2+3+45",135), +("3+3+49",147), +("2+3+46",138), +("0+4+23",92), +("2+3+47",141), +("4+4+0",0), +("0+4+24",96), +("1+4+26",104), +("0+4+25",100), +("4+4+1",4), +("0+4+26",104), +("4+4+2",8), +("2+3+48",144), +("0+4+27",108), +("0+4+28",112), +("2+3+49",147), +("1+4+27",108), +("0+4+29",116), +("4+4+3",12), +("0+4+30",120), +("1+4+28",112), +("1+4+29",116), +("3+4+0",0), +("1+4+30",120), +("3+4+1",4), +("0+4+31",124), +("0+4+32",128), +("1+4+31",124), +("2+4+0",0), +("4+4+4",16), +("1+4+32",128), +("4+4+5",20), +("1+4+33",132), +("0+4+33",132), +("3+4+2",8), +("0+4+34",136), +("2+4+1",4), +("4+4+6",24), +("3+4+3",12), +("0+4+35",140), +("2+4+2",8), +("2+4+3",12), +("1+4+34",136), +("2+4+4",16), +("4+4+7",28), +("0+4+36",144), +("1+4+35",140), +("0+4+37",148), +("1+4+36",144), +("4+4+8",32), +("0+4+38",152), +("1+4+37",148), +("2+4+5",20), +("4+4+9",36), +("1+4+38",152), +("3+4+4",16), +("1+4+39",156), +("0+4+39",156), +("1+4+40",160), +("3+4+5",20), +("0+4+40",160), +("4+4+10",40), +("4+4+11",44), +("4+4+12",48), +("1+4+41",164), +("3+4+6",24), +("3+4+7",28), +("2+4+6",24), +("4+4+13",52), +("2+4+7",28), +("4+4+14",56), +("4+4+15",60), +("3+4+8",32), +("2+4+8",32), +("4+4+16",64), +("0+4+41",164), +("0+4+42",168), +("3+4+9",36), +("1+4+42",168), +("0+4+43",172), +("1+4+43",172), +("0+4+44",176), +("1+4+44",176), +("1+4+45",180), +("0+4+45",180), +("4+4+17",68), +("2+4+9",36), +("0+4+46",184), +("0+4+47",188), +("1+4+46",184), +("2+4+10",40), +("0+4+48",192), +("3+4+10",40), +("4+4+18",72), +("0+4+49",196), +("1+4+47",188), +("2+4+11",44), +("3+4+11",44), +("2+4+12",48), +("3+4+12",48), +("1+4+48",192), +("2+4+13",52), +("0+5+0",0), +("3+4+13",52), +("4+4+19",76), +("4+4+20",80), +("1+4+49",196), +("4+4+21",84), +("4+4+22",88), +("3+4+14",56), +("3+4+15",60), +("4+4+23",92), +("2+4+14",56), +("0+5+1",5), +("3+4+16",64), +("3+4+17",68), +("2+4+15",60), +("0+5+2",10), +("4+4+24",96), +("0+5+3",15), +("0+5+4",20), +("0+5+5",25), +("4+4+25",100), +("4+4+26",104), +("2+4+16",64), +("4+4+27",108), +("3+4+18",72), +("2+4+17",68), +("0+5+6",30), +("4+4+28",112), +("4+4+29",116), +("2+4+18",72), +("2+4+19",76), +("3+4+19",76), +("4+4+30",120), +("2+4+20",80), +("0+5+7",35), +("0+5+8",40), +("3+4+20",80), +("0+5+9",45), +("4+4+31",124), +("2+4+21",84), +("2+4+22",88), +("4+4+32",128), +("0+5+10",50), +("0+5+11",55), +("4+4+33",132), +("4+4+34",136), +("2+4+23",92), +("0+5+12",60), +("0+5+13",65), +("0+5+14",70), +("0+5+15",75), +("4+4+35",140), +("2+4+24",96), +("2+4+25",100), +("0+5+16",80), +("0+5+17",85), +("0+5+18",90), +("3+4+21",84), +("3+4+22",88), +("4+4+36",144), +("2+4+26",104), +("3+4+23",92), +("0+5+19",95), +("3+4+24",96), +("4+4+37",148), +("3+4+25",100), +("4+4+38",152), +("2+4+27",108), +("4+4+39",156), +("0+5+20",100), +("2+4+28",112), +("3+4+26",104), +("0+5+21",105), +("4+4+40",160), +("4+4+41",164), +("3+4+27",108), +("3+4+28",112), +("4+4+42",168), +("3+4+29",116), +("0+5+22",110), +("0+5+23",115), +("4+4+43",172), +("2+4+29",116), +("3+4+30",120), +("0+5+24",120), +("3+4+31",124), +("4+4+44",176), +("2+4+30",120), +("4+4+45",180), +("4+4+46",184), +("3+4+32",128), +("4+4+47",188), +("0+5+25",125), +("3+4+33",132), +("2+4+31",124), +("2+4+32",128), +("0+5+26",130), +("4+4+48",192), +("2+4+33",132), +("4+4+49",196), +("0+5+27",135), +("3+4+34",136), +("2+4+34",136), +("3+4+35",140), +("0+5+28",140), +("2+4+35",140), +("0+5+29",145), +("2+4+36",144), +("0+5+30",150), +("3+4+36",144), +("0+5+31",155), +("3+4+37",148), +("0+5+32",160), +("2+4+37",148), +("2+4+38",152), +("2+4+39",156), +("3+4+38",152), +("3+4+39",156), +("3+4+40",160), +("0+5+33",165), +("3+4+41",164), +("2+4+40",160), +("3+4+42",168), +("0+5+34",170), +("0+5+35",175), +("3+4+43",172), +("3+4+44",176), +("2+4+41",164), +("3+4+45",180), +("0+5+36",180), +("2+4+42",168), +("3+4+46",184), +("3+4+47",188), +("3+4+48",192), +("0+5+37",185), +("3+4+49",196), +("2+4+43",172), +("2+4+44",176), +("2+4+45",180), +("2+4+46",184), +("2+4+47",188), +("2+4+48",192), +("0+5+38",190), +("2+4+49",196), +("0+5+39",195), +("0+5+40",200), +("0+5+41",205), +("0+5+42",210), +("0+5+43",215), +("0+5+44",220), +("0+5+45",225), +("0+5+46",230), +("0+5+47",235), +("0+5+48",240), +("0+5+49",245), +("1+5+0",0), +("1+5+1",5), +("1+5+2",10), +("1+5+3",15), +("1+5+4",20), +("1+5+5",25), +("1+5+6",30), +("1+5+7",35), +("1+5+8",40), +("1+5+9",45), +("1+5+10",50), +("1+5+11",55), +("1+5+12",60), +("1+5+13",65), +("1+5+14",70), +("1+5+15",75), +("1+5+16",80), +("1+5+17",85), +("1+5+18",90), +("1+5+19",95), +("1+5+20",100), +("1+5+21",105), +("1+5+22",110), +("1+5+23",115), +("1+5+24",120), +("1+5+25",125), +("1+5+26",130), +("1+5+27",135), +("1+5+28",140), +("1+5+29",145), +("1+5+30",150), +("1+5+31",155), +("1+5+32",160), +("1+5+33",165), +("1+5+34",170), +("1+5+35",175), +("1+5+36",180), +("1+5+37",185), +("1+5+38",190), +("1+5+39",195), +("1+5+40",200), +("1+5+41",205), +("1+5+42",210), +("1+5+43",215), +("1+5+44",220), +("1+5+45",225), +("1+5+46",230), +("1+5+47",235), +("4+5+0",0), +("2+5+0",0), +("1+5+48",240), +("1+5+49",245), +("0+6+0",0), +("3+5+0",0), +("4+5+1",5), +("2+5+1",5), +("0+6+1",6), +("4+5+2",10), +("2+5+2",10), +("4+5+3",15), +("0+6+2",12), +("3+5+1",5), +("4+5+4",20), +("0+6+3",18), +("1+6+0",0), +("3+5+2",10), +("4+5+5",25), +("1+6+1",6), +("2+5+3",15), +("4+5+6",30), +("3+5+3",15), +("0+6+4",24), +("3+5+4",20), +("1+6+2",12), +("1+6+3",18), +("4+5+7",35), +("3+5+5",25), +("2+5+4",20), +("1+6+4",24), +("4+5+8",40), +("0+6+5",30), +("4+5+9",45), +("1+6+5",30), +("2+5+5",25), +("3+5+6",30), +("2+5+6",30), +("1+6+6",36), +("0+6+6",36), +("3+5+7",35), +("4+5+10",50), +("1+6+7",42), +("2+5+7",35), +("1+6+8",48), +("4+5+11",55), +("3+5+8",40), +("2+5+8",40), +("0+6+7",42), +("3+5+9",45), +("0+6+8",48), +("0+6+9",54), +("4+5+12",60), +("2+5+9",45), +("0+6+10",60), +("3+5+10",50), +("0+6+11",66), +("4+5+13",65), +("3+5+11",55), +("3+5+12",60), +("1+6+9",54), +("0+6+12",72), +("4+5+14",70), +("3+5+13",65), +("3+5+14",70), +("4+5+15",75), +("2+5+10",50), +("3+5+15",75), +("0+6+13",78), +("3+5+16",80), +("4+5+16",80), +("0+6+14",84), +("4+5+17",85), +("0+6+15",90), +("1+6+10",60), +("4+5+18",90), +("3+5+17",85), +("2+5+11",55), +("0+6+16",96), +("1+6+11",66), +("1+6+12",72), +("2+5+12",60), +("1+6+13",78), +("3+5+18",90), +("1+6+14",84), +("1+6+15",90), +("4+5+19",95), +("3+5+19",95), +("4+5+20",100), +("1+6+16",96), +("0+6+17",102), +("4+5+21",105), +("3+5+20",100), +("3+5+21",105), +("2+5+13",65), +("4+5+22",110), +("3+5+22",110), +("2+5+14",70), +("1+6+17",102), +("2+5+15",75), +("0+6+18",108), +("1+6+18",108), +("4+5+23",115), +("3+5+23",115), +("2+5+16",80), +("2+5+17",85), +("4+5+24",120), +("3+5+24",120), +("0+6+19",114), +("2+5+18",90), +("1+6+19",114), +("1+6+20",120), +("2+5+19",95), +("2+5+20",100), +("2+5+21",105), +("4+5+25",125), +("0+6+20",120), +("1+6+21",126), +("4+5+26",130), +("2+5+22",110), +("3+5+25",125), +("4+5+27",135), +("4+5+28",140), +("0+6+21",126), +("3+5+26",130), +("2+5+23",115), +("3+5+27",135), +("0+6+22",132), +("2+5+24",120), +("2+5+25",125), +("1+6+22",132), +("3+5+28",140), +("2+5+26",130), +("4+5+29",145), +("1+6+23",138), +("3+5+29",145), +("4+5+30",150), +("4+5+31",155), +("1+6+24",144), +("4+5+32",160), +("3+5+30",150), +("0+6+23",138), +("2+5+27",135), +("1+6+25",150), +("4+5+33",165), +("1+6+26",156), +("3+5+31",155), +("2+5+28",140), +("1+6+27",162), +("2+5+29",145), +("1+6+28",168), +("1+6+29",174), +("3+5+32",160), +("3+5+33",165), +("4+5+34",170), +("0+6+24",144), +("3+5+34",170), +("3+5+35",175), +("4+5+35",175), +("3+5+36",180), +("2+5+30",150), +("0+6+25",150), +("0+6+26",156), +("1+6+30",180), +("0+6+27",162), +("0+6+28",168), +("4+5+36",180), +("4+5+37",185), +("1+6+31",186), +("0+6+29",174), +("0+6+30",180), +("3+5+37",185), +("2+5+31",155), +("0+6+31",186), +("3+5+38",190), +("0+6+32",192), +("1+6+32",192), +("0+6+33",198), +("2+5+32",160), +("1+6+33",198), +("0+6+34",204), +("4+5+38",190), +("4+5+39",195), +("3+5+39",195), +("2+5+33",165), +("2+5+34",170), +("4+5+40",200), +("3+5+40",200), +("2+5+35",175), +("3+5+41",205), +("4+5+41",205), +("1+6+34",204), +("0+6+35",210), +("3+5+42",210), +("2+5+36",180), +("0+6+36",216), +("2+5+37",185), +("2+5+38",190), +("2+5+39",195), +("4+5+42",210), +("2+5+40",200), +("4+5+43",215), +("4+5+44",220), +("4+5+45",225), +("2+5+41",205), +("4+5+46",230), +("1+6+35",210), +("2+5+42",210), +("0+6+37",222), +("1+6+36",216), +("3+5+43",215), +("4+5+47",235), +("1+6+37",222), +("3+5+44",220), +("2+5+43",215), +("1+6+38",228), +("0+6+38",228), +("2+5+44",220), +("0+6+39",234), +("0+6+40",240), +("3+5+45",225), +("1+6+39",234), +("0+6+41",246), +("4+5+48",240), +("4+5+49",245), +("0+6+42",252), +("3+5+46",230), +("2+5+45",225), +("1+6+40",240), +("2+5+46",230), +("3+5+47",235), +("3+5+48",240), +("1+6+41",246), +("2+5+47",235), +("1+6+42",252), +("3+5+49",245), +("2+5+48",240), +("1+6+43",258), +("1+6+44",264), +("2+5+49",245), +("4+6+0",0), +("4+6+1",6), +("0+6+43",258), +("1+6+45",270), +("4+6+2",12), +("1+6+46",276), +("3+6+0",0), +("4+6+3",18), +("0+6+44",264), +("4+6+4",24), +("0+6+45",270), +("4+6+5",30), +("1+6+47",282), +("3+6+1",6), +("0+6+46",276), +("3+6+2",12), +("4+6+6",36), +("0+6+47",282), +("4+6+7",42), +("0+6+48",288), +("2+6+0",0), +("4+6+8",48), +("3+6+3",18), +("0+6+49",294), +("3+6+4",24), +("2+6+1",6), +("3+6+5",30), +("4+6+9",54), +("1+6+48",288), +("4+6+10",60), +("1+6+49",294), +("0+7+0",0), +("4+6+11",66), +("4+6+12",72), +("0+7+1",7), +("3+6+6",36), +("0+7+2",14), +("3+6+7",42), +("2+6+2",12), +("3+6+8",48), +("4+6+13",78), +("3+6+9",54), +("0+7+3",21), +("2+6+3",18), +("3+6+10",60), +("0+7+4",28), +("1+7+0",0), +("3+6+11",66), +("2+6+4",24), +("0+7+5",35), +("2+6+5",30), +("0+7+6",42), +("1+7+1",7), +("0+7+7",49), +("1+7+2",14), +("0+7+8",56), +("3+6+12",72), +("0+7+9",63), +("3+6+13",78), +("0+7+10",70), +("2+6+6",36), +("0+7+11",77), +("2+6+7",42), +("3+6+14",84), +("2+6+8",48), +("1+7+3",21), +("3+6+15",90), +("4+6+14",84), +("3+6+16",96), +("2+6+9",54), +("0+7+12",84), +("2+6+10",60), +("1+7+4",28), +("3+6+17",102), +("2+6+11",66), +("4+6+15",90), +("1+7+5",35), +("4+6+16",96), +("1+7+6",42), +("3+6+18",108), +("1+7+7",49), +("4+6+17",102), +("4+6+18",108), +("2+6+12",72), +("3+6+19",114), +("2+6+13",78), +("1+7+8",56), +("4+6+19",114), +("2+6+14",84), +("3+6+20",120), +("4+6+20",120), +("2+6+15",90), +("1+7+9",63), +("3+6+21",126), +("3+6+22",132), +("4+6+21",126), +("3+6+23",138), +("3+6+24",144), +("4+6+22",132), +("4+6+23",138), +("3+6+25",150), +("0+7+13",91), +("2+6+16",96), +("1+7+10",70), +("0+7+14",98), +("4+6+24",144), +("3+6+26",156), +("0+7+15",105), +("1+7+11",77), +("2+6+17",102), +("4+6+25",150), +("0+7+16",112), +("1+7+12",84), +("4+6+26",156), +("1+7+13",91), +("2+6+18",108), +("1+7+14",98), +("2+6+19",114), +("3+6+27",162), +("4+6+27",162), +("3+6+28",168), +("4+6+28",168), +("1+7+15",105), +("0+7+17",119), +("3+6+29",174), +("2+6+20",120), +("0+7+18",126), +("0+7+19",133), +("2+6+21",126), +("0+7+20",140), +("3+6+30",180), +("0+7+21",147), +("1+7+16",112), +("0+7+22",154), +("1+7+17",119), +("3+6+31",186), +("4+6+29",174), +("1+7+18",126), +("4+6+30",180), +("4+6+31",186), +("2+6+22",132), +("1+7+19",133), +("1+7+20",140), +("2+6+23",138), +("3+6+32",192), +("2+6+24",144), +("2+6+25",150), +("4+6+32",192), +("0+7+23",161), +("3+6+33",198), +("2+6+26",156), +("1+7+21",147), +("1+7+22",154), +("2+6+27",162), +("4+6+33",198), +("4+6+34",204), +("1+7+23",161), +("2+6+28",168), +("0+7+24",168), +("2+6+29",174), +("3+6+34",204), +("2+6+30",180), +("0+7+25",175), +("4+6+35",210), +("0+7+26",182), +("4+6+36",216), +("2+6+31",186), +("0+7+27",189), +("1+7+24",168), +("4+6+37",222), +("3+6+35",210), +("3+6+36",216), +("2+6+32",192), +("1+7+25",175), +("4+6+38",228), +("2+6+33",198), +("0+7+28",196), +("4+6+39",234), +("4+6+40",240), +("1+7+26",182), +("3+6+37",222), +("3+6+38",228), +("4+6+41",246), +("2+6+34",204), +("3+6+39",234), +("2+6+35",210), +("0+7+29",203), +("3+6+40",240), +("0+7+30",210), +("2+6+36",216), +("1+7+27",189), +("4+6+42",252), +("2+6+37",222), +("4+6+43",258), +("1+7+28",196), +("3+6+41",246), +("1+7+29",203), +("2+6+38",228), +("2+6+39",234), +("4+6+44",264), +("0+7+31",217), +("4+6+45",270), +("1+7+30",210), +("3+6+42",252), +("0+7+32",224), +("3+6+43",258), +("3+6+44",264), +("2+6+40",240), +("0+7+33",231), +("1+7+31",217), +("2+6+41",246), +("4+6+46",276), +("1+7+32",224), +("1+7+33",231), +("2+6+42",252), +("3+6+45",270), +("3+6+46",276), +("0+7+34",238), +("3+6+47",282), +("4+6+47",282), +("2+6+43",258), +("1+7+34",238), +("2+6+44",264), +("1+7+35",245), +("4+6+48",288), +("3+6+48",288), +("0+7+35",245), +("4+6+49",294), +("0+7+36",252), +("2+6+45",270), +("0+7+37",259), +("2+6+46",276), +("0+7+38",266), +("1+7+36",252), +("0+7+39",273), +("0+7+40",280), +("3+6+49",294), +("2+6+47",282), +("1+7+37",259), +("4+7+0",0), +("2+6+48",288), +("2+6+49",294), +("0+7+41",287), +("1+7+38",266), +("0+7+42",294), +("4+7+1",7), +("0+7+43",301), +("4+7+2",14), +("1+7+39",273), +("4+7+3",21), +("3+7+0",0), +("1+7+40",280), +("0+7+44",308), +("1+7+41",287), +("1+7+42",294), +("0+7+45",315), +("4+7+4",28), +("1+7+43",301), +("4+7+5",35), +("1+7+44",308), +("1+7+45",315), +("3+7+1",7), +("4+7+6",42), +("2+7+0",0), +("3+7+2",14), +("2+7+1",7), +("0+7+46",322), +("4+7+7",49), +("0+7+47",329), +("3+7+3",21), +("0+7+48",336), +("4+7+8",56), +("1+7+46",322), +("3+7+4",28), +("3+7+5",35), +("2+7+2",14), +("1+7+47",329), +("1+7+48",336), +("2+7+3",21), +("0+7+49",343), +("2+7+4",28), +("2+7+5",35), +("3+7+6",42), +("2+7+6",42), +("3+7+7",49), +("4+7+9",63), +("0+8+0",0), +("4+7+10",70), +("2+7+7",49), +("1+7+49",343), +("0+8+1",8), +("0+8+2",16), +("2+7+8",56), +("0+8+3",24), +("3+7+8",56), +("3+7+9",63), +("0+8+4",32), +("3+7+10",70), +("0+8+5",40), +("4+7+11",77), +("3+7+11",77), +("2+7+9",63), +("4+7+12",84), +("2+7+10",70), +("3+7+12",84), +("0+8+6",48), +("4+7+13",91), +("2+7+11",77), +("0+8+7",56), +("2+7+12",84), +("1+8+0",0), +("0+8+8",64), +("1+8+1",8), +("3+7+13",91), +("2+7+13",91), +("1+8+2",16), +("1+8+3",24), +("1+8+4",32), +("3+7+14",98), +("4+7+14",98), +("3+7+15",105), +("4+7+15",105), +("2+7+14",98), +("4+7+16",112), +("1+8+5",40), +("0+8+9",72), +("3+7+16",112), +("4+7+17",119), +("3+7+17",119), +("1+8+6",48), +("4+7+18",126), +("4+7+19",133), +("3+7+18",126), +("3+7+19",133), +("0+8+10",80), +("4+7+20",140), +("4+7+21",147), +("3+7+20",140), +("1+8+7",56), +("4+7+22",154), +("0+8+11",88), +("4+7+23",161), +("0+8+12",96), +("3+7+21",147), +("4+7+24",168), +("4+7+25",175), +("3+7+22",154), +("1+8+8",64), +("0+8+13",104), +("1+8+9",72), +("4+7+26",182), +("0+8+14",112), +("2+7+15",105), +("0+8+15",120), +("2+7+16",112), +("1+8+10",80), +("4+7+27",189), +("2+7+17",119), +("0+8+16",128), +("4+7+28",196), +("1+8+11",88), +("2+7+18",126), +("0+8+17",136), +("3+7+23",161), +("4+7+29",203), +("0+8+18",144), +("0+8+19",152), +("0+8+20",160), +("4+7+30",210), +("1+8+12",96), +("0+8+21",168), +("0+8+22",176), +("2+7+19",133), +("0+8+23",184), +("3+7+24",168), +("0+8+24",192), +("1+8+13",104), +("1+8+14",112), +("3+7+25",175), +("4+7+31",217), +("4+7+32",224), +("4+7+33",231), +("1+8+15",120), +("3+7+26",182), +("2+7+20",140), +("2+7+21",147), +("2+7+22",154), +("4+7+34",238), +("4+7+35",245), +("3+7+27",189), +("3+7+28",196), +("0+8+25",200), +("0+8+26",208), +("4+7+36",252), +("4+7+37",259), +("4+7+38",266), +("4+7+39",273), +("3+7+29",203), +("1+8+16",128), +("3+7+30",210), +("3+7+31",217), +("1+8+17",136), +("0+8+27",216), +("0+8+28",224), +("3+7+32",224), +("2+7+23",161), +("3+7+33",231), +("3+7+34",238), +("3+7+35",245), +("2+7+24",168), +("4+7+40",280), +("2+7+25",175), +("3+7+36",252), +("3+7+37",259), +("0+8+29",232), +("0+8+30",240), +("0+8+31",248), +("3+7+38",266), +("2+7+26",182), +("0+8+32",256), +("2+7+27",189), +("4+7+41",287), +("4+7+42",294), +("4+7+43",301), +("4+7+44",308), +("1+8+18",144), +("4+7+45",315), +("4+7+46",322), +("3+7+39",273), +("0+8+33",264), +("2+7+28",196), +("2+7+29",203), +("0+8+34",272), +("1+8+19",152), +("3+7+40",280), +("0+8+35",280), +("4+7+47",329), +("4+7+48",336), +("0+8+36",288), +("0+8+37",296), +("2+7+30",210), +("1+8+20",160), +("4+7+49",343), +("2+7+31",217), +("1+8+21",168), +("2+7+32",224), +("1+8+22",176), +("0+8+38",304), +("2+7+33",231), +("4+8+0",0), +("1+8+23",184), +("4+8+1",8), +("3+7+41",287), +("2+7+34",238), +("0+8+39",312), +("3+7+42",294), +("1+8+24",192), +("1+8+25",200), +("3+7+43",301), +("4+8+2",16), +("2+7+35",245), +("3+7+44",308), +("0+8+40",320), +("2+7+36",252), +("2+7+37",259), +("4+8+3",24), +("4+8+4",32), +("4+8+5",40), +("1+8+26",208), +("1+8+27",216), +("2+7+38",266), +("4+8+6",48), +("1+8+28",224), +("4+8+7",56), +("0+8+41",328), +("0+8+42",336), +("4+8+8",64), +("3+7+45",315), +("2+7+39",273), +("4+8+9",72), +("2+7+40",280), +("3+7+46",322), +("3+7+47",329), +("0+8+43",344), +("1+8+29",232), +("0+8+44",352), +("3+7+48",336), +("0+8+45",360), +("0+8+46",368), +("2+7+41",287), +("1+8+30",240), +("1+8+31",248), +("0+8+47",376), +("4+8+10",80), +("3+7+49",343), +("1+8+32",256), +("0+8+48",384), +("1+8+33",264), +("0+8+49",392), +("2+7+42",294), +("1+8+34",272), +("3+8+0",0), +("1+8+35",280), +("3+8+1",8), +("4+8+11",88), +("4+8+12",96), +("0+9+0",0), +("2+7+43",301), +("2+7+44",308), +("1+8+36",288), +("3+8+2",16), +("0+9+1",9), +("3+8+3",24), +("4+8+13",104), +("0+9+2",18), +("1+8+37",296), +("0+9+3",27), +("3+8+4",32), +("1+8+38",304), +("4+8+14",112), +("1+8+39",312), +("1+8+40",320), +("4+8+15",120), +("2+7+45",315), +("4+8+16",128), +("4+8+17",136), +("3+8+5",40), +("0+9+4",36), +("2+7+46",322), +("1+8+41",328), +("2+7+47",329), +("4+8+18",144), +("0+9+5",45), +("2+7+48",336), +("2+7+49",343), +("4+8+19",152), +("3+8+6",48), +("3+8+7",56), +("4+8+20",160), +("4+8+21",168), +("1+8+42",336), +("3+8+8",64), +("1+8+43",344), +("3+8+9",72), +("3+8+10",80), +("1+8+44",352), +("3+8+11",88), +("3+8+12",96), +("1+8+45",360), +("0+9+6",54), +("1+8+46",368), +("3+8+13",104), +("0+9+7",63), +("3+8+14",112), +("3+8+15",120), +("1+8+47",376), +("0+9+8",72), +("4+8+22",176), +("4+8+23",184), +("0+9+9",81), +("1+8+48",384), +("2+8+0",0), +("0+9+10",90), +("1+8+49",392), +("3+8+16",128), +("2+8+1",8), +("0+9+11",99), +("3+8+17",136), +("4+8+24",192), +("4+8+25",200), +("2+8+2",16), +("0+9+12",108), +("4+8+26",208), +("0+9+13",117), +("3+8+18",144), +("2+8+3",24), +("0+9+14",126), +("3+8+19",152), +("2+8+4",32), +("3+8+20",160), +("1+9+0",0), +("3+8+21",168), +("4+8+27",216), +("2+8+5",40), +("0+9+15",135), +("2+8+6",48), +("3+8+22",176), +("4+8+28",224), +("3+8+23",184), +("0+9+16",144), +("4+8+29",232), +("3+8+24",192), +("2+8+7",56), +("4+8+30",240), +("3+8+25",200), +("1+9+1",9), +("4+8+31",248), +("2+8+8",64), +("1+9+2",18), +("4+8+32",256), +("1+9+3",27), +("0+9+17",153), +("4+8+33",264), +("3+8+26",208), +("2+8+9",72), +("0+9+18",162), +("0+9+19",171), +("1+9+4",36), +("1+9+5",45), +("2+8+10",80), +("1+9+6",54), +("4+8+34",272), +("4+8+35",280), +("1+9+7",63), +("4+8+36",288), +("3+8+27",216), +("2+8+11",88), +("1+9+8",72), +("0+9+20",180), +("0+9+21",189), +("1+9+9",81), +("3+8+28",224), +("0+9+22",198), +("2+8+12",96), +("4+8+37",296), +("4+8+38",304), +("2+8+13",104), +("1+9+10",90), +("0+9+23",207), +("4+8+39",312), +("0+9+24",216), +("4+8+40",320), +("1+9+11",99), +("1+9+12",108), +("4+8+41",328), +("1+9+13",117), +("0+9+25",225), +("3+8+29",232), +("2+8+14",112), +("3+8+30",240), +("4+8+42",336), +("3+8+31",248), +("1+9+14",126), +("4+8+43",344), +("0+9+26",234), +("3+8+32",256), +("1+9+15",135), +("4+8+44",352), +("0+9+27",243), +("1+9+16",144), +("1+9+17",153), +("3+8+33",264), +("1+9+18",162), +("2+8+15",120), +("0+9+28",252), +("4+8+45",360), +("1+9+19",171), +("4+8+46",368), +("1+9+20",180), +("2+8+16",128), +("1+9+21",189), +("3+8+34",272), +("1+9+22",198), +("3+8+35",280), +("4+8+47",376), +("3+8+36",288), +("1+9+23",207), +("2+8+17",136), +("1+9+24",216), +("3+8+37",296), +("4+8+48",384), +("1+9+25",225), +("0+9+29",261), +("2+8+18",144), +("1+9+26",234), +("0+9+30",270), +("1+9+27",243), +("1+9+28",252), +("3+8+38",304), +("2+8+19",152), +("1+9+29",261), +("0+9+31",279), +("2+8+20",160), +("1+9+30",270), +("0+9+32",288), +("1+9+31",279), +("3+8+39",312), +("4+8+49",392), +("0+9+33",297), +("1+9+32",288), +("3+8+40",320), +("0+9+34",306), +("3+8+41",328), +("2+8+21",168), +("3+8+42",336), +("0+9+35",315), +("2+8+22",176), +("0+9+36",324), +("2+8+23",184), +("1+9+33",297), +("3+8+43",344), +("0+9+37",333), +("0+9+38",342), +("2+8+24",192), +("0+9+39",351), +("0+9+40",360), +("1+9+34",306), +("1+9+35",315), +("1+9+36",324), +("0+9+41",369), +("1+9+37",333), +("2+8+25",200), +("1+9+38",342), +("3+8+44",352), +("3+8+45",360), +("0+9+42",378), +("2+8+26",208), +("0+9+43",387), +("3+8+46",368), +("2+8+27",216), +("3+8+47",376), +("2+8+28",224), +("0+9+44",396), +("1+9+39",351), +("3+8+48",384), +("2+8+29",232), +("1+9+40",360), +("0+9+45",405), +("1+9+41",369), +("2+8+30",240), +("2+8+31",248), +("3+8+49",392), +("1+9+42",378), +("2+8+32",256), +("0+9+46",414), +("0+9+47",423), +("1+9+43",387), +("0+9+48",432), +("2+8+33",264), +("2+8+34",272), +("1+9+44",396), +("2+8+35",280), +("0+9+49",441), +("1+9+45",405), +("2+8+36",288), +("1+9+46",414), +("1+9+47",423), +("1+9+48",432), +("2+8+37",296), +("2+8+38",304), +("1+9+49",441), +("2+8+39",312), +("2+8+40",320), +("2+8+41",328), +("2+8+42",336), +("2+8+43",344), +("2+8+44",352), +("2+8+45",360), +("2+8+46",368), +("2+8+47",376), +("2+8+48",384), +("2+8+49",392), +("2+9+0",0), +("0+10+0",0), +("3+9+0",0), +("4+9+0",0), +("0+10+1",10), +("1+10+0",0), +("2+9+1",9), +("0+10+2",20), +("4+9+1",9), +("1+10+1",10), +("3+9+1",9), +("2+9+2",18), +("3+9+2",18), +("2+9+3",27), +("1+10+2",20), +("2+9+4",36), +("0+10+3",30), +("0+10+4",40), +("3+9+3",27), +("1+10+3",30), +("2+9+5",45), +("2+9+6",54), +("3+9+4",36), +("0+10+5",50), +("2+9+7",63), +("2+9+8",72), +("0+10+6",60), +("0+10+7",70), +("4+9+2",18), +("0+10+8",80), +("1+10+4",40), +("2+9+9",81), +("0+10+9",90), +("3+9+5",45), +("1+10+5",50), +("2+9+10",90), +("3+9+6",54), +("3+9+7",63), +("1+10+6",60), +("2+9+11",99), +("1+10+7",70), +("3+9+8",72), +("0+10+10",100), +("2+9+12",108), +("3+9+9",81), +("0+10+11",110), +("3+9+10",90), +("1+10+8",80), +("2+9+13",117), +("3+9+11",99), +("1+10+9",90), +("2+9+14",126), +("0+10+12",120), +("4+9+3",27), +("1+10+10",100), +("2+9+15",135), +("0+10+13",130), +("2+9+16",144), +("4+9+4",36), +("4+9+5",45), +("0+10+14",140), +("3+9+12",108), +("2+9+17",153), +("4+9+6",54), +("0+10+15",150), +("3+9+13",117), +("3+9+14",126), +("0+10+16",160), +("0+10+17",170), +("4+9+7",63), +("1+10+11",110), +("2+9+18",162), +("2+9+19",171), +("4+9+8",72), +("2+9+20",180), +("1+10+12",120), +("4+9+9",81), +("3+9+15",135), +("0+10+18",180), +("1+10+13",130), +("3+9+16",144), +("1+10+14",140), +("4+9+10",90), +("0+10+19",190), +("2+9+21",189), +("1+10+15",150), +("1+10+16",160), +("2+9+22",198), +("1+10+17",170), +("3+9+17",153), +("1+10+18",180), +("4+9+11",99), +("4+9+12",108), +("3+9+18",162), +("3+9+19",171), +("3+9+20",180), +("2+9+23",207), +("1+10+19",190), +("1+10+20",200), +("0+10+20",200), +("0+10+21",210), +("3+9+21",189), +("4+9+13",117), +("1+10+21",210), +("2+9+24",216), +("1+10+22",220), +("1+10+23",230), +("4+9+14",126), +("2+9+25",225), +("0+10+22",220), +("1+10+24",240), +("4+9+15",135), +("2+9+26",234), +("1+10+25",250), +("1+10+26",260), +("0+10+23",230), +("1+10+27",270), +("2+9+27",243), +("3+9+22",198), +("0+10+24",240), +("1+10+28",280), +("0+10+25",250), +("4+9+16",144), +("2+9+28",252), +("2+9+29",261), +("2+9+30",270), +("4+9+17",153), +("3+9+23",207), +("2+9+31",279), +("4+9+18",162), +("0+10+26",260), +("2+9+32",288), +("3+9+24",216), +("1+10+29",290), +("2+9+33",297), +("0+10+27",270), +("0+10+28",280), +("1+10+30",300), +("2+9+34",306), +("0+10+29",290), +("1+10+31",310), +("3+9+25",225), +("2+9+35",315), +("3+9+26",234), +("4+9+19",171), +("0+10+30",300), +("2+9+36",324), +("2+9+37",333), +("3+9+27",243), +("0+10+31",310), +("4+9+20",180), +("1+10+32",320), +("4+9+21",189), +("1+10+33",330), +("0+10+32",320), +("4+9+22",198), +("2+9+38",342), +("2+9+39",351), +("2+9+40",360), +("4+9+23",207), +("1+10+34",340), +("2+9+41",369), +("4+9+24",216), +("2+9+42",378), +("0+10+33",330), +("1+10+35",350), +("0+10+34",340), +("0+10+35",350), +("1+10+36",360), +("4+9+25",225), +("2+9+43",387), +("0+10+36",360), +("4+9+26",234), +("3+9+28",252), +("2+9+44",396), +("4+9+27",243), +("3+9+29",261), +("2+9+45",405), +("4+9+28",252), +("0+10+37",370), +("2+9+46",414), +("4+9+29",261), +("0+10+38",380), +("1+10+37",370), +("2+9+47",423), +("4+9+30",270), +("2+9+48",432), +("1+10+38",380), +("4+9+31",279), +("2+9+49",441), +("0+10+39",390), +("4+9+32",288), +("0+10+40",400), +("0+10+41",410), +("1+10+39",390), +("3+9+30",270), +("4+9+33",297), +("3+9+31",279), +("0+10+42",420), +("1+10+40",400), +("2+10+0",0), +("4+9+34",306), +("3+9+32",288), +("0+10+43",430), +("4+9+35",315), +("2+10+1",10), +("1+10+41",410), +("4+9+36",324), +("3+9+33",297), +("2+10+2",20), +("4+9+37",333), +("3+9+34",306), +("1+10+42",420), +("0+10+44",440), +("3+9+35",315), +("2+10+3",30), +("2+10+4",40), +("3+9+36",324), +("2+10+5",50), +("4+9+38",342), +("0+10+45",450), +("1+10+43",430), +("4+9+39",351), +("3+9+37",333), +("4+9+40",360), +("2+10+6",60), +("0+10+46",460), +("3+9+38",342), +("4+9+41",369), +("0+10+47",470), +("2+10+7",70), +("3+9+39",351), +("1+10+44",440), +("2+10+8",80), +("1+10+45",450), +("2+10+9",90), +("1+10+46",460), +("1+10+47",470), +("2+10+10",100), +("4+9+42",378), +("1+10+48",480), +("2+10+11",110), +("1+10+49",490), +("3+9+40",360), +("2+10+12",120), +("4+9+43",387), +("0+10+48",480), +("2+10+13",130), +("1+11+0",0), +("2+10+14",140), +("3+9+41",369), +("3+9+42",378), +("1+11+1",11), +("3+9+43",387), +("4+9+44",396), +("1+11+2",22), +("4+9+45",405), +("4+9+46",414), +("3+9+44",396), +("0+10+49",490), +("3+9+45",405), +("4+9+47",423), +("3+9+46",414), +("3+9+47",423), +("4+9+48",432), +("2+10+15",150), +("3+9+48",432), +("4+9+49",441), +("2+10+16",160), +("1+11+3",33), +("1+11+4",44), +("1+11+5",55), +("3+9+49",441), +("2+10+17",170), +("1+11+6",66), +("2+10+18",180), +("2+10+19",190), +("1+11+7",77), +("0+11+0",0), +("1+11+8",88), +("2+10+20",200), +("2+10+21",210), +("1+11+9",99), +("0+11+1",11), +("0+11+2",22), +("4+10+0",0), +("2+10+22",220), +("3+10+0",0), +("4+10+1",10), +("2+10+23",230), +("0+11+3",33), +("0+11+4",44), +("2+10+24",240), +("4+10+2",20), +("0+11+5",55), +("4+10+3",30), +("0+11+6",66), +("3+10+1",10), +("0+11+7",77), +("3+10+2",20), +("1+11+10",110), +("0+11+8",88), +("4+10+4",40), +("3+10+3",30), +("3+10+4",40), +("2+10+25",250), +("3+10+5",50), +("1+11+11",121), +("3+10+6",60), +("4+10+5",50), +("2+10+26",260), +("0+11+9",99), +("2+10+27",270), +("3+10+7",70), +("4+10+6",60), +("4+10+7",70), +("3+10+8",80), +("3+10+9",90), +("4+10+8",80), +("1+11+12",132), +("0+11+10",110), +("1+11+13",143), +("1+11+14",154), +("1+11+15",165), +("3+10+10",100), +("1+11+16",176), +("2+10+28",280), +("3+10+11",110), +("0+11+11",121), +("4+10+9",90), +("3+10+12",120), +("0+11+12",132), +("4+10+10",100), +("0+11+13",143), +("1+11+17",187), +("0+11+14",154), +("4+10+11",110), +("4+10+12",120), +("2+10+29",290), +("2+10+30",300), +("0+11+15",165), +("1+11+18",198), +("0+11+16",176), +("4+10+13",130), +("2+10+31",310), +("4+10+14",140), +("2+10+32",320), +("0+11+17",187), +("2+10+33",330), +("4+10+15",150), +("2+10+34",340), +("3+10+13",130), +("2+10+35",350), +("4+10+16",160), +("0+11+18",198), +("1+11+19",209), +("2+10+36",360), +("1+11+20",220), +("4+10+17",170), +("2+10+37",370), +("3+10+14",140), +("1+11+21",231), +("2+10+38",380), +("1+11+22",242), +("1+11+23",253), +("0+11+19",209), +("4+10+18",180), +("2+10+39",390), +("0+11+20",220), +("3+10+15",150), +("4+10+19",190), +("0+11+21",231), +("1+11+24",264), +("0+11+22",242), +("3+10+16",160), +("4+10+20",200), +("0+11+23",253), +("1+11+25",275), +("0+11+24",264), +("4+10+21",210), +("3+10+17",170), +("2+10+40",400), +("1+11+26",286), +("0+11+25",275), +("4+10+22",220), +("0+11+26",286), +("2+10+41",410), +("2+10+42",420), +("1+11+27",297), +("1+11+28",308), +("3+10+18",180), +("2+10+43",430), +("0+11+27",297), +("0+11+28",308), +("0+11+29",319), +("3+10+19",190), +("4+10+23",230), +("2+10+44",440), +("3+10+20",200), +("3+10+21",210), +("0+11+30",330), +("1+11+29",319), +("3+10+22",220), +("4+10+24",240), +("2+10+45",450), +("0+11+31",341), +("0+11+32",352), +("0+11+33",363), +("2+10+46",460), +("4+10+25",250), +("0+11+34",374), +("4+10+26",260), +("1+11+30",330), +("2+10+47",470), +("0+11+35",385), +("0+11+36",396), +("4+10+27",270), +("2+10+48",480), +("3+10+23",230), +("0+11+37",407), +("3+10+24",240), +("3+10+25",250), +("0+11+38",418), +("1+11+31",341), +("0+11+39",429), +("1+11+32",352), +("0+11+40",440), +("1+11+33",363), +("1+11+34",374), +("2+10+49",490), +("4+10+28",280), +("0+11+41",451), +("4+10+29",290), +("0+11+42",462), +("4+10+30",300), +("1+11+35",385), +("0+11+43",473), +("0+11+44",484), +("4+10+31",310), +("1+11+36",396), +("4+10+32",320), +("3+10+26",260), +("4+10+33",330), +("4+10+34",340), +("2+11+0",0), +("3+10+27",270), +("2+11+1",11), +("3+10+28",280), +("4+10+35",350), +("1+11+37",407), +("0+11+45",495), +("1+11+38",418), +("3+10+29",290), +("3+10+30",300), +("0+11+46",506), +("3+10+31",310), +("1+11+39",429), +("4+10+36",360), +("4+10+37",370), +("3+10+32",320), +("2+11+2",22), +("0+11+47",517), +("4+10+38",380), +("3+10+33",330), +("4+10+39",390), +("2+11+3",33), +("1+11+40",440), +("4+10+40",400), +("3+10+34",340), +("0+11+48",528), +("0+11+49",539), +("1+11+41",451), +("3+10+35",350), +("3+10+36",360), +("1+11+42",462), +("2+11+4",44), +("3+10+37",370), +("2+11+5",55), +("1+11+43",473), +("2+11+6",66), +("3+10+38",380), +("1+11+44",484), +("0+12+0",0), +("3+10+39",390), +("2+11+7",77), +("0+12+1",12), +("2+11+8",88), +("0+12+2",24), +("0+12+3",36), +("1+11+45",495), +("0+12+4",48), +("4+10+41",410), +("4+10+42",420), +("1+11+46",506), +("4+10+43",430), +("1+11+47",517), +("0+12+5",60), +("1+11+48",528), +("2+11+9",99), +("0+12+6",72), +("2+11+10",110), +("1+11+49",539), +("0+12+7",84), +("0+12+8",96), +("2+11+11",121), +("3+10+40",400), +("2+11+12",132), +("4+10+44",440), +("4+10+45",450), +("3+10+41",410), +("3+10+42",420), +("2+11+13",143), +("0+12+9",108), +("0+12+10",120), +("0+12+11",132), +("3+10+43",430), +("1+12+0",0), +("1+12+1",12), +("1+12+2",24), +("0+12+12",144), +("4+10+46",460), +("4+10+47",470), +("4+10+48",480), +("4+10+49",490), +("3+10+44",440), +("2+11+14",154), +("1+12+3",36), +("2+11+15",165), +("1+12+4",48), +("2+11+16",176), +("0+12+13",156), +("2+11+17",187), +("2+11+18",198), +("4+11+0",0), +("1+12+5",60), +("0+12+14",168), +("3+10+45",450), +("1+12+6",72), +("0+12+15",180), +("4+11+1",11), +("0+12+16",192), +("2+11+19",209), +("4+11+2",22), +("3+10+46",460), +("4+11+3",33), +("3+10+47",470), +("1+12+7",84), +("3+10+48",480), +("0+12+17",204), +("2+11+20",220), +("2+11+21",231), +("2+11+22",242), +("2+11+23",253), +("0+12+18",216), +("2+11+24",264), +("4+11+4",44), +("1+12+8",96), +("3+10+49",490), +("1+12+9",108), +("1+12+10",120), +("1+12+11",132), +("4+11+5",55), +("1+12+12",144), +("1+12+13",156), +("1+12+14",168), +("0+12+19",228), +("1+12+15",180), +("0+12+20",240), +("0+12+21",252), +("2+11+25",275), +("3+11+0",0), +("3+11+1",11), +("1+12+16",192), +("4+11+6",66), +("4+11+7",77), +("2+11+26",286), +("4+11+8",88), +("1+12+17",204), +("1+12+18",216), +("2+11+27",297), +("4+11+9",99), +("2+11+28",308), +("0+12+22",264), +("2+11+29",319), +("1+12+19",228), +("1+12+20",240), +("0+12+23",276), +("4+11+10",110), +("0+12+24",288), +("3+11+2",22), +("1+12+21",252), +("3+11+3",33), +("4+11+11",121), +("2+11+30",330), +("3+11+4",44), +("2+11+31",341), +("0+12+25",300), +("3+11+5",55), +("0+12+26",312), +("1+12+22",264), +("1+12+23",276), +("3+11+6",66), +("1+12+24",288), +("1+12+25",300), +("2+11+32",352), +("1+12+26",312), +("1+12+27",324), +("2+11+33",363), +("2+11+34",374), +("1+12+28",336), +("2+11+35",385), +("4+11+12",132), +("1+12+29",348), +("1+12+30",360), +("2+11+36",396), +("3+11+7",77), +("4+11+13",143), +("4+11+14",154), +("0+12+27",324), +("1+12+31",372), +("0+12+28",336), +("2+11+37",407), +("0+12+29",348), +("0+12+30",360), +("1+12+32",384), +("2+11+38",418), +("0+12+31",372), +("4+11+15",165), +("0+12+32",384), +("3+11+8",88), +("4+11+16",176), +("2+11+39",429), +("3+11+9",99), +("3+11+10",110), +("4+11+17",187), +("1+12+33",396), +("4+11+18",198), +("0+12+33",396), +("0+12+34",408), +("3+11+11",121), +("0+12+35",420), +("1+12+34",408), +("4+11+19",209), +("0+12+36",432), +("1+12+35",420), +("4+11+20",220), +("0+12+37",444), +("4+11+21",231), +("0+12+38",456), +("2+11+40",440), +("4+11+22",242), +("4+11+23",253), +("3+11+12",132), +("1+12+36",432), +("4+11+24",264), +("4+11+25",275), +("0+12+39",468), +("2+11+41",451), +("0+12+40",480), +("1+12+37",444), +("2+11+42",462), +("1+12+38",456), +("2+11+43",473), +("0+12+41",492), +("4+11+26",286), +("0+12+42",504), +("3+11+13",143), +("4+11+27",297), +("0+12+43",516), +("1+12+39",468), +("0+12+44",528), +("2+11+44",484), +("0+12+45",540), +("1+12+40",480), +("1+12+41",492), +("0+12+46",552), +("1+12+42",504), +("4+11+28",308), +("0+12+47",564), +("0+12+48",576), +("1+12+43",516), +("3+11+14",154), +("2+11+45",495), +("4+11+29",319), +("4+11+30",330), +("2+11+46",506), +("1+12+44",528), +("2+11+47",517), +("1+12+45",540), +("2+11+48",528), +("0+12+49",588), +("4+11+31",341), +("3+11+15",165), +("4+11+32",352), +("1+12+46",552), +("3+11+16",176), +("1+12+47",564), +("1+12+48",576), +("1+12+49",588), +("3+11+17",187), +("2+11+49",539), +("3+11+18",198), +("4+11+33",363), +("3+11+19",209), +("4+11+34",374), +("4+11+35",385), +("4+11+36",396), +("4+11+37",407), +("3+11+20",220), +("4+11+38",418), +("3+11+21",231), +("3+11+22",242), +("4+11+39",429), +("3+11+23",253), +("0+13+0",0), +("2+12+0",0), +("3+11+24",264), +("4+11+40",440), +("0+13+1",13), +("2+12+1",12), +("1+13+0",0), +("4+11+41",451), +("3+11+25",275), +("4+11+42",462), +("4+11+43",473), +("1+13+1",13), +("4+11+44",484), +("0+13+2",26), +("4+11+45",495), +("1+13+2",26), +("0+13+3",39), +("1+13+3",39), +("4+11+46",506), +("3+11+26",286), +("0+13+4",52), +("1+13+4",52), +("3+11+27",297), +("1+13+5",65), +("0+13+5",65), +("1+13+6",78), +("0+13+6",78), +("2+12+2",24), +("4+11+47",517), +("1+13+7",91), +("2+12+3",36), +("0+13+7",91), +("1+13+8",104), +("1+13+9",117), +("2+12+4",48), +("1+13+10",130), +("2+12+5",60), +("1+13+11",143), +("2+12+6",72), +("4+11+48",528), +("1+13+12",156), +("0+13+8",104), +("3+11+28",308), +("1+13+13",169), +("2+12+7",84), +("3+11+29",319), +("4+11+49",539), +("0+13+9",117), +("1+13+14",182), +("3+11+30",330), +("0+13+10",130), +("3+11+31",341), +("0+13+11",143), +("3+11+32",352), +("4+12+0",0), +("3+11+33",363), +("0+13+12",156), +("0+13+13",169), +("2+12+8",96), +("4+12+1",12), +("2+12+9",108), +("1+13+15",195), +("4+12+2",24), +("1+13+16",208), +("3+11+34",374), +("1+13+17",221), +("3+11+35",385), +("1+13+18",234), +("4+12+3",36), +("3+11+36",396), +("4+12+4",48), +("3+11+37",407), +("4+12+5",60), +("3+11+38",418), +("2+12+10",120), +("4+12+6",72), +("1+13+19",247), +("3+11+39",429), +("1+13+20",260), +("2+12+11",132), +("2+12+12",144), +("1+13+21",273), +("1+13+22",286), +("0+13+14",182), +("2+12+13",156), +("3+11+40",440), +("1+13+23",299), +("4+12+7",84), +("0+13+15",195), +("1+13+24",312), +("0+13+16",208), +("3+11+41",451), +("3+11+42",462), +("4+12+8",96), +("3+11+43",473), +("2+12+14",168), +("3+11+44",484), +("4+12+9",108), +("0+13+17",221), +("3+11+45",495), +("0+13+18",234), +("3+11+46",506), +("2+12+15",180), +("0+13+19",247), +("2+12+16",192), +("2+12+17",204), +("1+13+25",325), +("2+12+18",216), +("0+13+20",260), +("3+11+47",517), +("4+12+10",120), +("3+11+48",528), +("0+13+21",273), +("3+11+49",539), +("0+13+22",286), +("1+13+26",338), +("0+13+23",299), +("2+12+19",228), +("2+12+20",240), +("4+12+11",132), +("0+13+24",312), +("4+12+12",144), +("0+13+25",325), +("0+13+26",338), +("0+13+27",351), +("4+12+13",156), +("0+13+28",364), +("2+12+21",252), +("0+13+29",377), +("2+12+22",264), +("4+12+14",168), +("0+13+30",390), +("2+12+23",276), +("0+13+31",403), +("1+13+27",351), +("3+12+0",0), +("2+12+24",288), +("1+13+28",364), +("0+13+32",416), +("1+13+29",377), +("3+12+1",12), +("1+13+30",390), +("4+12+15",180), +("2+12+25",300), +("2+12+26",312), +("0+13+33",429), +("2+12+27",324), +("2+12+28",336), +("3+12+2",24), +("0+13+34",442), +("2+12+29",348), +("0+13+35",455), +("2+12+30",360), +("3+12+3",36), +("0+13+36",468), +("3+12+4",48), +("1+13+31",403), +("2+12+31",372), +("0+13+37",481), +("3+12+5",60), +("0+13+38",494), +("2+12+32",384), +("1+13+32",416), +("4+12+16",192), +("2+12+33",396), +("1+13+33",429), +("0+13+39",507), +("0+13+40",520), +("3+12+6",72), +("3+12+7",84), +("2+12+34",408), +("0+13+41",533), +("3+12+8",96), +("0+13+42",546), +("3+12+9",108), +("4+12+17",204), +("2+12+35",420), +("1+13+34",442), +("2+12+36",432), +("0+13+43",559), +("4+12+18",216), +("0+13+44",572), +("3+12+10",120), +("0+13+45",585), +("4+12+19",228), +("0+13+46",598), +("4+12+20",240), +("2+12+37",444), +("1+13+35",455), +("2+12+38",456), +("1+13+36",468), +("2+12+39",468), +("3+12+11",132), +("2+12+40",480), +("1+13+37",481), +("4+12+21",252), +("1+13+38",494), +("4+12+22",264), +("0+13+47",611), +("3+12+12",144), +("4+12+23",276), +("0+13+48",624), +("1+13+39",507), +("4+12+24",288), +("0+13+49",637), +("4+12+25",300), +("2+12+41",492), +("1+13+40",520), +("3+12+13",156), +("2+12+42",504), +("3+12+14",168), +("1+13+41",533), +("1+13+42",546), +("1+13+43",559), +("1+13+44",572), +("1+13+45",585), +("2+12+43",516), +("3+12+15",180), +("4+12+26",312), +("1+13+46",598), +("1+13+47",611), +("4+12+27",324), +("2+12+44",528), +("4+12+28",336), +("4+12+29",348), +("3+12+16",192), +("3+12+17",204), +("1+13+48",624), +("4+12+30",360), +("2+12+45",540), +("1+13+49",637), +("4+12+31",372), +("3+12+18",216), +("4+12+32",384), +("4+12+33",396), +("4+12+34",408), +("3+12+19",228), +("3+12+20",240), +("4+12+35",420), +("3+12+21",252), +("2+12+46",552), +("3+12+22",264), +("4+12+36",432), +("4+12+37",444), +("3+12+23",276), +("3+12+24",288), +("4+12+38",456), +("3+12+25",300), +("3+12+26",312), +("2+12+47",564), +("4+12+39",468), +("4+12+40",480), +("3+12+27",324), +("4+12+41",492), +("4+12+42",504), +("2+12+48",576), +("4+12+43",516), +("3+12+28",336), +("4+12+44",528), +("2+12+49",588), +("4+12+45",540), +("3+12+29",348), +("3+12+30",360), +("3+12+31",372), +("3+12+32",384), +("4+12+46",552), +("3+12+33",396), +("3+12+34",408), +("4+12+47",564), +("3+12+35",420), +("3+12+36",432), +("3+12+37",444), +("4+12+48",576), +("4+12+49",588), +("3+12+38",456), +("3+12+39",468), +("3+12+40",480), +("3+12+41",492), +("3+12+42",504), +("3+12+43",516), +("3+12+44",528), +("3+12+45",540), +("3+12+46",552), +("3+12+47",564), +("3+12+48",576), +("3+12+49",588), +("3+13+0",0), +("1+14+0",0), +("0+14+0",0), +("2+13+0",0), +("4+13+0",0), +("2+13+1",13), +("0+14+1",14), +("1+14+1",14), +("0+14+2",28), +("3+13+1",13), +("2+13+2",26), +("0+14+3",42), +("0+14+4",56), +("4+13+1",13), +("0+14+5",70), +("4+13+2",26), +("1+14+2",28), +("2+13+3",39), +("2+13+4",52), +("4+13+3",39), +("3+13+2",26), +("4+13+4",52), +("4+13+5",65), +("0+14+6",84), +("0+14+7",98), +("1+14+3",42), +("4+13+6",78), +("2+13+5",65), +("1+14+4",56), +("2+13+6",78), +("0+14+8",112), +("1+14+5",70), +("3+13+3",39), +("3+13+4",52), +("0+14+9",126), +("3+13+5",65), +("2+13+7",91), +("2+13+8",104), +("0+14+10",140), +("2+13+9",117), +("2+13+10",130), +("1+14+6",84), +("2+13+11",143), +("3+13+6",78), +("1+14+7",98), +("4+13+7",91), +("2+13+12",156), +("2+13+13",169), +("2+13+14",182), +("3+13+7",91), +("2+13+15",195), +("1+14+8",112), +("0+14+11",154), +("0+14+12",168), +("3+13+8",104), +("2+13+16",208), +("3+13+9",117), +("0+14+13",182), +("3+13+10",130), +("4+13+8",104), +("1+14+9",126), +("3+13+11",143), +("3+13+12",156), +("0+14+14",196), +("1+14+10",140), +("4+13+9",117), +("4+13+10",130), +("2+13+17",221), +("3+13+13",169), +("3+13+14",182), +("0+14+15",210), +("1+14+11",154), +("0+14+16",224), +("1+14+12",168), +("1+14+13",182), +("2+13+18",234), +("4+13+11",143), +("3+13+15",195), +("0+14+17",238), +("1+14+14",196), +("1+14+15",210), +("0+14+18",252), +("0+14+19",266), +("3+13+16",208), +("3+13+17",221), +("2+13+19",247), +("4+13+12",156), +("2+13+20",260), +("2+13+21",273), +("0+14+20",280), +("3+13+18",234), +("2+13+22",286), +("4+13+13",169), +("2+13+23",299), +("3+13+19",247), +("3+13+20",260), +("3+13+21",273), +("3+13+22",286), +("4+13+14",182), +("3+13+23",299), +("0+14+21",294), +("4+13+15",195), +("4+13+16",208), +("3+13+24",312), +("2+13+24",312), +("3+13+25",325), +("2+13+25",325), +("2+13+26",338), +("4+13+17",221), +("4+13+18",234), +("4+13+19",247), +("0+14+22",308), +("0+14+23",322), +("1+14+16",224), +("1+14+17",238), +("3+13+26",338), +("0+14+24",336), +("3+13+27",351), +("0+14+25",350), +("4+13+20",260), +("0+14+26",364), +("1+14+18",252), +("0+14+27",378), +("0+14+28",392), +("3+13+28",364), +("4+13+21",273), +("2+13+27",351), +("3+13+29",377), +("2+13+28",364), +("1+14+19",266), +("3+13+30",390), +("3+13+31",403), +("4+13+22",286), +("0+14+29",406), +("2+13+29",377), +("2+13+30",390), +("2+13+31",403), +("0+14+30",420), +("1+14+20",280), +("2+13+32",416), +("1+14+21",294), +("4+13+23",299), +("0+14+31",434), +("1+14+22",308), +("0+14+32",448), +("0+14+33",462), +("4+13+24",312), +("4+13+25",325), +("2+13+33",429), +("0+14+34",476), +("1+14+23",322), +("4+13+26",338), +("0+14+35",490), +("4+13+27",351), +("2+13+34",442), +("1+14+24",336), +("2+13+35",455), +("0+14+36",504), +("1+14+25",350), +("3+13+32",416), +("1+14+26",364), +("0+14+37",518), +("1+14+27",378), +("3+13+33",429), +("2+13+36",468), +("3+13+34",442), +("3+13+35",455), +("3+13+36",468), +("2+13+37",481), +("3+13+37",481), +("4+13+28",364), +("2+13+38",494), +("2+13+39",507), +("3+13+38",494), +("1+14+28",392), +("2+13+40",520), +("4+13+29",377), +("3+13+39",507), +("2+13+41",533), +("1+14+29",406), +("0+14+38",532), +("4+13+30",390), +("3+13+40",520), +("2+13+42",546), +("4+13+31",403), +("1+14+30",420), +("4+13+32",416), +("2+13+43",559), +("0+14+39",546), +("1+14+31",434), +("3+13+41",533), +("1+14+32",448), +("0+14+40",560), +("1+14+33",462), +("0+14+41",574), +("1+14+34",476), +("2+13+44",572), +("0+14+42",588), +("3+13+42",546), +("1+14+35",490), +("0+14+43",602), +("4+13+33",429), +("1+14+36",504), +("3+13+43",559), +("0+14+44",616), +("2+13+45",585), +("3+13+44",572), +("1+14+37",518), +("4+13+34",442), +("3+13+45",585), +("4+13+35",455), +("0+14+45",630), +("1+14+38",532), +("4+13+36",468), +("2+13+46",598), +("3+13+46",598), +("2+13+47",611), +("4+13+37",481), +("0+14+46",644), +("2+13+48",624), +("4+13+38",494), +("2+13+49",637), +("4+13+39",507), +("0+14+47",658), +("4+13+40",520), +("1+14+39",546), +("0+14+48",672), +("1+14+40",560), +("0+14+49",686), +("1+14+41",574), +("1+14+42",588), +("4+13+41",533), +("2+14+0",0), +("4+13+42",546), +("1+14+43",602), +("3+13+47",611), +("0+15+0",0), +("1+14+44",616), +("2+14+1",14), +("2+14+2",28), +("2+14+3",42), +("4+13+43",559), +("3+13+48",624), +("4+13+44",572), +("4+13+45",585), +("0+15+1",15), +("1+14+45",630), +("4+13+46",598), +("1+14+46",644), +("2+14+4",56), +("1+14+47",658), +("0+15+2",30), +("4+13+47",611), +("0+15+3",45), +("2+14+5",70), +("1+14+48",672), +("2+14+6",84), +("3+13+49",637), +("1+14+49",686), +("0+15+4",60), +("4+13+48",624), +("0+15+5",75), +("4+13+49",637), +("0+15+6",90), +("2+14+7",98), +("2+14+8",112), +("3+14+0",0), +("1+15+0",0), +("4+14+0",0), +("0+15+7",105), +("2+14+9",126), +("3+14+1",14), +("0+15+8",120), +("3+14+2",28), +("0+15+9",135), +("3+14+3",42), +("1+15+1",15), +("3+14+4",56), +("0+15+10",150), +("2+14+10",140), +("4+14+1",14), +("2+14+11",154), +("4+14+2",28), +("3+14+5",70), +("3+14+6",84), +("4+14+3",42), +("0+15+11",165), +("2+14+12",168), +("0+15+12",180), +("1+15+2",30), +("2+14+13",182), +("3+14+7",98), +("1+15+3",45), +("3+14+8",112), +("1+15+4",60), +("3+14+9",126), +("1+15+5",75), +("2+14+14",196), +("0+15+13",195), +("4+14+4",56), +("0+15+14",210), +("1+15+6",90), +("3+14+10",140), +("0+15+15",225), +("4+14+5",70), +("0+15+16",240), +("3+14+11",154), +("0+15+17",255), +("4+14+6",84), +("0+15+18",270), +("0+15+19",285), +("4+14+7",98), +("4+14+8",112), +("3+14+12",168), +("1+15+7",105), +("1+15+8",120), +("0+15+20",300), +("1+15+9",135), +("0+15+21",315), +("2+14+15",210), +("1+15+10",150), +("2+14+16",224), +("3+14+13",182), +("3+14+14",196), +("2+14+17",238), +("1+15+11",165), +("3+14+15",210), +("0+15+22",330), +("3+14+16",224), +("4+14+9",126), +("3+14+17",238), +("4+14+10",140), +("1+15+12",180), +("1+15+13",195), +("4+14+11",154), +("2+14+18",252), +("3+14+18",252), +("2+14+19",266), +("3+14+19",266), +("4+14+12",168), +("1+15+14",210), +("2+14+20",280), +("3+14+20",280), +("0+15+23",345), +("1+15+15",225), +("3+14+21",294), +("0+15+24",360), +("1+15+16",240), +("2+14+21",294), +("3+14+22",308), +("1+15+17",255), +("3+14+23",322), +("0+15+25",375), +("4+14+13",182), +("3+14+24",336), +("1+15+18",270), +("1+15+19",285), +("3+14+25",350), +("2+14+22",308), +("3+14+26",364), +("4+14+14",196), +("3+14+27",378), +("0+15+26",390), +("2+14+23",322), +("3+14+28",392), +("0+15+27",405), +("2+14+24",336), +("1+15+20",300), +("2+14+25",350), +("1+15+21",315), +("0+15+28",420), +("0+15+29",435), +("4+14+15",210), +("2+14+26",364), +("0+15+30",450), +("1+15+22",330), +("4+14+16",224), +("0+15+31",465), +("3+14+29",406), +("4+14+17",238), +("2+14+27",378), +("3+14+30",420), +("0+15+32",480), +("1+15+23",345), +("4+14+18",252), +("4+14+19",266), +("3+14+31",434), +("3+14+32",448), +("0+15+33",495), +("3+14+33",462), +("3+14+34",476), +("0+15+34",510), +("0+15+35",525), +("2+14+28",392), +("4+14+20",280), +("1+15+24",360), +("0+15+36",540), +("2+14+29",406), +("1+15+25",375), +("3+14+35",490), +("1+15+26",390), +("3+14+36",504), +("2+14+30",420), +("0+15+37",555), +("4+14+21",294), +("3+14+37",518), +("2+14+31",434), +("2+14+32",448), +("1+15+27",405), +("4+14+22",308), +("2+14+33",462), +("3+14+38",532), +("1+15+28",420), +("2+14+34",476), +("0+15+38",570), +("1+15+29",435), +("4+14+23",322), +("3+14+39",546), +("4+14+24",336), +("2+14+35",490), +("4+14+25",350), +("2+14+36",504), +("2+14+37",518), +("0+15+39",585), +("4+14+26",364), +("4+14+27",378), +("4+14+28",392), +("3+14+40",560), +("0+15+40",600), +("3+14+41",574), +("2+14+38",532), +("1+15+30",450), +("4+14+29",406), +("4+14+30",420), +("0+15+41",615), +("1+15+31",465), +("1+15+32",480), +("0+15+42",630), +("3+14+42",588), +("3+14+43",602), +("2+14+39",546), +("2+14+40",560), +("2+14+41",574), +("1+15+33",495), +("0+15+43",645), +("0+15+44",660), +("4+14+31",434), +("1+15+34",510), +("3+14+44",616), +("3+14+45",630), +("1+15+35",525), +("1+15+36",540), +("0+15+45",675), +("4+14+32",448), +("3+14+46",644), +("4+14+33",462), +("0+15+46",690), +("4+14+34",476), +("4+14+35",490), +("3+14+47",658), +("3+14+48",672), +("3+14+49",686), +("2+14+42",588), +("0+15+47",705), +("4+14+36",504), +("4+14+37",518), +("1+15+37",555), +("4+14+38",532), +("4+14+39",546), +("3+15+0",0), +("4+14+40",560), +("3+15+1",15), +("2+14+43",602), +("0+15+48",720), +("2+14+44",616), +("3+15+2",30), +("3+15+3",45), +("4+14+41",574), +("3+15+4",60), +("1+15+38",570), +("0+15+49",735), +("4+14+42",588), +("3+15+5",75), +("2+14+45",630), +("3+15+6",90), +("3+15+7",105), +("3+15+8",120), +("4+14+43",602), +("2+14+46",644), +("4+14+44",616), +("2+14+47",658), +("3+15+9",135), +("1+15+39",585), +("2+14+48",672), +("3+15+10",150), +("4+14+45",630), +("0+16+0",0), +("1+15+40",600), +("2+14+49",686), +("1+15+41",615), +("0+16+1",16), +("0+16+2",32), +("0+16+3",48), +("4+14+46",644), +("3+15+11",165), +("4+14+47",658), +("1+15+42",630), +("4+14+48",672), +("4+14+49",686), +("0+16+4",64), +("3+15+12",180), +("1+15+43",645), +("3+15+13",195), +("3+15+14",210), +("1+15+44",660), +("0+16+5",80), +("3+15+15",225), +("0+16+6",96), +("0+16+7",112), +("2+15+0",0), +("0+16+8",128), +("4+15+0",0), +("4+15+1",15), +("1+15+45",675), +("3+15+16",240), +("0+16+9",144), +("4+15+2",30), +("3+15+17",255), +("4+15+3",45), +("3+15+18",270), +("4+15+4",60), +("2+15+1",15), +("1+15+46",690), +("0+16+10",160), +("3+15+19",285), +("0+16+11",176), +("4+15+5",75), +("1+15+47",705), +("3+15+20",300), +("1+15+48",720), +("4+15+6",90), +("4+15+7",105), +("1+15+49",735), +("3+15+21",315), +("3+15+22",330), +("3+15+23",345), +("3+15+24",360), +("0+16+12",192), +("4+15+8",120), +("4+15+9",135), +("3+15+25",375), +("2+15+2",30), +("3+15+26",390), +("0+16+13",208), +("4+15+10",150), +("0+16+14",224), +("3+15+27",405), +("4+15+11",165), +("3+15+28",420), +("0+16+15",240), +("0+16+16",256), +("0+16+17",272), +("2+15+3",45), +("0+16+18",288), +("2+15+4",60), +("0+16+19",304), +("4+15+12",180), +("0+16+20",320), +("4+15+13",195), +("1+16+0",0), +("0+16+21",336), +("4+15+14",210), +("2+15+5",75), +("3+15+29",435), +("1+16+1",16), +("4+15+15",225), +("1+16+2",32), +("4+15+16",240), +("4+15+17",255), +("0+16+22",352), +("4+15+18",270), +("2+15+6",90), +("1+16+3",48), +("4+15+19",285), +("1+16+4",64), +("3+15+30",450), +("2+15+7",105), +("1+16+5",80), +("4+15+20",300), +("4+15+21",315), +("2+15+8",120), +("2+15+9",135), +("3+15+31",465), +("4+15+22",330), +("0+16+23",368), +("3+15+32",480), +("0+16+24",384), +("1+16+6",96), +("4+15+23",345), +("1+16+7",112), +("1+16+8",128), +("1+16+9",144), +("1+16+10",160), +("2+15+10",150), +("2+15+11",165), +("3+15+33",495), +("1+16+11",176), +("4+15+24",360), +("3+15+34",510), +("0+16+25",400), +("1+16+12",192), +("1+16+13",208), +("1+16+14",224), +("1+16+15",240), +("0+16+26",416), +("1+16+16",256), +("0+16+27",432), +("2+15+12",180), +("0+16+28",448), +("4+15+25",375), +("0+16+29",464), +("4+15+26",390), +("0+16+30",480), +("1+16+17",272), +("1+16+18",288), +("0+16+31",496), +("0+16+32",512), +("2+15+13",195), +("2+15+14",210), +("0+16+33",528), +("0+16+34",544), +("0+16+35",560), +("0+16+36",576), +("3+15+35",525), +("2+15+15",225), +("3+15+36",540), +("0+16+37",592), +("4+15+27",405), +("4+15+28",420), +("1+16+19",304), +("0+16+38",608), +("1+16+20",320), +("1+16+21",336), +("2+15+16",240), +("0+16+39",624), +("2+15+17",255), +("2+15+18",270), +("4+15+29",435), +("4+15+30",450), +("4+15+31",465), +("1+16+22",352), +("2+15+19",285), +("1+16+23",368), +("0+16+40",640), +("4+15+32",480), +("0+16+41",656), +("3+15+37",555), +("4+15+33",495), +("1+16+24",384), +("1+16+25",400), +("0+16+42",672), +("3+15+38",570), +("0+16+43",688), +("2+15+20",300), +("4+15+34",510), +("2+15+21",315), +("4+15+35",525), +("0+16+44",704), +("2+15+22",330), +("4+15+36",540), +("3+15+39",585), +("1+16+26",416), +("4+15+37",555), +("3+15+40",600), +("4+15+38",570), +("3+15+41",615), +("1+16+27",432), +("0+16+45",720), +("1+16+28",448), +("0+16+46",736), +("4+15+39",585), +("0+16+47",752), +("1+16+29",464), +("3+15+42",630), +("3+15+43",645), +("1+16+30",480), +("1+16+31",496), +("0+16+48",768), +("4+15+40",600), +("4+15+41",615), +("2+15+23",345), +("0+16+49",784), +("4+15+42",630), +("4+15+43",645), +("2+15+24",360), +("2+15+25",375), +("1+16+32",512), +("2+15+26",390), +("3+15+44",660), +("1+16+33",528), +("3+15+45",675), +("2+15+27",405), +("3+15+46",690), +("4+15+44",660), +("2+15+28",420), +("4+15+45",675), +("0+17+0",0), +("4+15+46",690), +("4+15+47",705), +("3+15+47",705), +("2+15+29",435), +("2+15+30",450), +("4+15+48",720), +("2+15+31",465), +("4+15+49",735), +("2+15+32",480), +("0+17+1",17), +("2+15+33",495), +("1+16+34",544), +("0+17+2",34), +("2+15+34",510), +("0+17+3",51), +("2+15+35",525), +("0+17+4",68), +("1+16+35",560), +("2+15+36",540), +("1+16+36",576), +("4+16+0",0), +("1+16+37",592), +("2+15+37",555), +("0+17+5",85), +("3+15+48",720), +("3+15+49",735), +("2+15+38",570), +("0+17+6",102), +("1+16+38",608), +("1+16+39",624), +("0+17+7",119), +("0+17+8",136), +("3+16+0",0), +("0+17+9",153), +("4+16+1",16), +("4+16+2",32), +("2+15+39",585), +("3+16+1",16), +("0+17+10",170), +("3+16+2",32), +("2+15+40",600), +("4+16+3",48), +("2+15+41",615), +("3+16+3",48), +("2+15+42",630), +("0+17+11",187), +("0+17+12",204), +("1+16+40",640), +("3+16+4",64), +("1+16+41",656), +("3+16+5",80), +("3+16+6",96), +("4+16+4",64), +("3+16+7",112), +("1+16+42",672), +("0+17+13",221), +("2+15+43",645), +("4+16+5",80), +("2+15+44",660), +("3+16+8",128), +("0+17+14",238), +("2+15+45",675), +("3+16+9",144), +("1+16+43",688), +("1+16+44",704), +("2+15+46",690), +("0+17+15",255), +("1+16+45",720), +("0+17+16",272), +("3+16+10",160), +("3+16+11",176), +("0+17+17",289), +("0+17+18",306), +("0+17+19",323), +("2+15+47",705), +("1+16+46",736), +("1+16+47",752), +("0+17+20",340), +("2+15+48",720), +("1+16+48",768), +("1+16+49",784), +("4+16+6",96), +("2+15+49",735), +("0+17+21",357), +("0+17+22",374), +("0+17+23",391), +("4+16+7",112), +("0+17+24",408), +("2+16+0",0), +("4+16+8",128), +("1+17+0",0), +("3+16+12",192), +("3+16+13",208), +("4+16+9",144), +("1+17+1",17), +("0+17+25",425), +("1+17+2",34), +("1+17+3",51), +("3+16+14",224), +("2+16+1",16), +("1+17+4",68), +("2+16+2",32), +("2+16+3",48), +("1+17+5",85), +("2+16+4",64), +("0+17+26",442), +("2+16+5",80), +("1+17+6",102), +("0+17+27",459), +("3+16+15",240), +("4+16+10",160), +("1+17+7",119), +("3+16+16",256), +("3+16+17",272), +("4+16+11",176), +("4+16+12",192), +("2+16+6",96), +("0+17+28",476), +("1+17+8",136), +("1+17+9",153), +("4+16+13",208), +("2+16+7",112), +("0+17+29",493), +("3+16+18",288), +("4+16+14",224), +("2+16+8",128), +("0+17+30",510), +("4+16+15",240), +("0+17+31",527), +("0+17+32",544), +("4+16+16",256), +("2+16+9",144), +("4+16+17",272), +("0+17+33",561), +("1+17+10",170), +("3+16+19",304), +("1+17+11",187), +("4+16+18",288), +("1+17+12",204), +("1+17+13",221), +("0+17+34",578), +("2+16+10",160), +("4+16+19",304), +("2+16+11",176), +("3+16+20",320), +("4+16+20",320), +("1+17+14",238), +("2+16+12",192), +("1+17+15",255), +("3+16+21",336), +("1+17+16",272), +("0+17+35",595), +("4+16+21",336), +("1+17+17",289), +("3+16+22",352), +("4+16+22",352), +("1+17+18",306), +("0+17+36",612), +("1+17+19",323), +("2+16+13",208), +("4+16+23",368), +("2+16+14",224), +("1+17+20",340), +("4+16+24",384), +("0+17+37",629), +("2+16+15",240), +("1+17+21",357), +("4+16+25",400), +("1+17+22",374), +("4+16+26",416), +("1+17+23",391), +("0+17+38",646), +("3+16+23",368), +("2+16+16",256), +("0+17+39",663), +("2+16+17",272), +("4+16+27",432), +("0+17+40",680), +("2+16+18",288), +("0+17+41",697), +("2+16+19",304), +("3+16+24",384), +("2+16+20",320), +("0+17+42",714), +("4+16+28",448), +("2+16+21",336), +("1+17+24",408), +("4+16+29",464), +("1+17+25",425), +("3+16+25",400), +("0+17+43",731), +("3+16+26",416), +("3+16+27",432), +("1+17+26",442), +("3+16+28",448), +("2+16+22",352), +("1+17+27",459), +("0+17+44",748), +("4+16+30",480), +("3+16+29",464), +("1+17+28",476), +("3+16+30",480), +("1+17+29",493), +("3+16+31",496), +("2+16+23",368), +("4+16+31",496), +("1+17+30",510), +("2+16+24",384), +("0+17+45",765), +("0+17+46",782), +("3+16+32",512), +("1+17+31",527), +("3+16+33",528), +("0+17+47",799), +("4+16+32",512), +("1+17+32",544), +("0+17+48",816), +("2+16+25",400), +("1+17+33",561), +("1+17+34",578), +("4+16+33",528), +("1+17+35",595), +("2+16+26",416), +("1+17+36",612), +("1+17+37",629), +("4+16+34",544), +("1+17+38",646), +("4+16+35",560), +("2+16+27",432), +("3+16+34",544), +("4+16+36",576), +("0+17+49",833), +("1+17+39",663), +("4+16+37",592), +("3+16+35",560), +("1+17+40",680), +("3+16+36",576), +("1+17+41",697), +("2+16+28",448), +("2+16+29",464), +("3+16+37",592), +("4+16+38",608), +("3+16+38",608), +("4+16+39",624), +("3+16+39",624), +("1+17+42",714), +("1+17+43",731), +("4+16+40",640), +("2+16+30",480), +("0+18+0",0), +("2+16+31",496), +("0+18+1",18), +("1+17+44",748), +("2+16+32",512), +("1+17+45",765), +("4+16+41",656), +("2+16+33",528), +("1+17+46",782), +("3+16+40",640), +("3+16+41",656), +("4+16+42",672), +("0+18+2",36), +("3+16+42",672), +("0+18+3",54), +("4+16+43",688), +("3+16+43",688), +("0+18+4",72), +("1+17+47",799), +("2+16+34",544), +("3+16+44",704), +("4+16+44",704), +("4+16+45",720), +("3+16+45",720), +("1+17+48",816), +("0+18+5",90), +("1+17+49",833), +("0+18+6",108), +("3+16+46",736), +("2+16+35",560), +("0+18+7",126), +("4+16+46",736), +("3+16+47",752), +("0+18+8",144), +("2+16+36",576), +("3+16+48",768), +("4+16+47",752), +("3+16+49",784), +("0+18+9",162), +("2+16+37",592), +("1+18+0",0), +("0+18+10",180), +("0+18+11",198), +("0+18+12",216), +("4+16+48",768), +("1+18+1",18), +("2+16+38",608), +("1+18+2",36), +("0+18+13",234), +("4+16+49",784), +("0+18+14",252), +("2+16+39",624), +("1+18+3",54), +("0+18+15",270), +("1+18+4",72), +("2+16+40",640), +("0+18+16",288), +("2+16+41",656), +("1+18+5",90), +("2+16+42",672), +("1+18+6",108), +("0+18+17",306), +("3+17+0",0), +("2+16+43",688), +("0+18+18",324), +("1+18+7",126), +("1+18+8",144), +("4+17+0",0), +("0+18+19",342), +("4+17+1",17), +("1+18+9",162), +("3+17+1",17), +("2+16+44",704), +("3+17+2",34), +("0+18+20",360), +("3+17+3",51), +("3+17+4",68), +("3+17+5",85), +("4+17+2",34), +("2+16+45",720), +("3+17+6",102), +("3+17+7",119), +("0+18+21",378), +("2+16+46",736), +("2+16+47",752), +("2+16+48",768), +("4+17+3",51), +("4+17+4",68), +("2+16+49",784), +("0+18+22",396), +("3+17+8",136), +("1+18+10",180), +("1+18+11",198), +("0+18+23",414), +("1+18+12",216), +("3+17+9",153), +("3+17+10",170), +("1+18+13",234), +("1+18+14",252), +("3+17+11",187), +("2+17+0",0), +("0+18+24",432), +("1+18+15",270), +("1+18+16",288), +("4+17+5",85), +("4+17+6",102), +("3+17+12",204), +("4+17+7",119), +("3+17+13",221), +("3+17+14",238), +("0+18+25",450), +("2+17+1",17), +("4+17+8",136), +("0+18+26",468), +("0+18+27",486), +("2+17+2",34), +("3+17+15",255), +("1+18+17",306), +("3+17+16",272), +("0+18+28",504), +("0+18+29",522), +("3+17+17",289), +("4+17+9",153), +("4+17+10",170), +("3+17+18",306), +("2+17+3",51), +("2+17+4",68), +("2+17+5",85), +("1+18+18",324), +("4+17+11",187), +("1+18+19",342), +("0+18+30",540), +("4+17+12",204), +("4+17+13",221), +("3+17+19",323), +("3+17+20",340), +("3+17+21",357), +("2+17+6",102), +("0+18+31",558), +("0+18+32",576), +("1+18+20",360), +("0+18+33",594), +("4+17+14",238), +("0+18+34",612), +("0+18+35",630), +("0+18+36",648), +("1+18+21",378), +("0+18+37",666), +("4+17+15",255), +("1+18+22",396), +("4+17+16",272), +("2+17+7",119), +("3+17+22",374), +("0+18+38",684), +("1+18+23",414), +("3+17+23",391), +("3+17+24",408), +("4+17+17",289), +("0+18+39",702), +("1+18+24",432), +("0+18+40",720), +("4+17+18",306), +("1+18+25",450), +("0+18+41",738), +("2+17+8",136), +("1+18+26",468), +("4+17+19",323), +("3+17+25",425), +("0+18+42",756), +("2+17+9",153), +("3+17+26",442), +("4+17+20",340), +("2+17+10",170), +("2+17+11",187), +("3+17+27",459), +("2+17+12",204), +("4+17+21",357), +("1+18+27",486), +("2+17+13",221), +("4+17+22",374), +("1+18+28",504), +("2+17+14",238), +("0+18+43",774), +("0+18+44",792), +("3+17+28",476), +("0+18+45",810), +("1+18+29",522), +("3+17+29",493), +("0+18+46",828), +("3+17+30",510), +("4+17+23",391), +("4+17+24",408), +("3+17+31",527), +("3+17+32",544), +("1+18+30",540), +("4+17+25",425), +("2+17+15",255), +("0+18+47",846), +("4+17+26",442), +("1+18+31",558), +("4+17+27",459), +("2+17+16",272), +("3+17+33",561), +("1+18+32",576), +("4+17+28",476), +("3+17+34",578), +("1+18+33",594), +("0+18+48",864), +("3+17+35",595), +("0+18+49",882), +("4+17+29",493), +("3+17+36",612), +("3+17+37",629), +("3+17+38",646), +("3+17+39",663), +("4+17+30",510), +("2+17+17",289), +("0+19+0",0), +("1+18+34",612), +("4+17+31",527), +("2+17+18",306), +("2+17+19",323), +("3+17+40",680), +("4+17+32",544), +("1+18+35",630), +("1+18+36",648), +("3+17+41",697), +("1+18+37",666), +("2+17+20",340), +("1+18+38",684), +("2+17+21",357), +("0+19+1",19), +("1+18+39",702), +("1+18+40",720), +("1+18+41",738), +("4+17+33",561), +("4+17+34",578), +("3+17+42",714), +("0+19+2",38), +("2+17+22",374), +("2+17+23",391), +("2+17+24",408), +("4+17+35",595), +("2+17+25",425), +("3+17+43",731), +("4+17+36",612), +("1+18+42",756), +("2+17+26",442), +("4+17+37",629), +("0+19+3",57), +("3+17+44",748), +("0+19+4",76), +("4+17+38",646), +("3+17+45",765), +("4+17+39",663), +("4+17+40",680), +("2+17+27",459), +("2+17+28",476), +("0+19+5",95), +("3+17+46",782), +("0+19+6",114), +("4+17+41",697), +("4+17+42",714), +("1+18+43",774), +("0+19+7",133), +("4+17+43",731), +("3+17+47",799), +("3+17+48",816), +("3+17+49",833), +("1+18+44",792), +("4+17+44",748), +("0+19+8",152), +("2+17+29",493), +("0+19+9",171), +("4+17+45",765), +("4+17+46",782), +("2+17+30",510), +("4+17+47",799), +("0+19+10",190), +("4+17+48",816), +("1+18+45",810), +("3+18+0",0), +("0+19+11",209), +("3+18+1",18), +("2+17+31",527), +("4+17+49",833), +("1+18+46",828), +("1+18+47",846), +("2+17+32",544), +("1+18+48",864), +("3+18+2",36), +("0+19+12",228), +("4+18+0",0), +("4+18+1",18), +("4+18+2",36), +("4+18+3",54), +("4+18+4",72), +("4+18+5",90), +("4+18+6",108), +("4+18+7",126), +("3+18+3",54), +("1+18+49",882), +("4+18+8",144), +("3+18+4",72), +("0+19+13",247), +("4+18+9",162), +("3+18+5",90), +("0+19+14",266), +("4+18+10",180), +("4+18+11",198), +("1+19+0",0), +("2+17+33",561), +("2+17+34",578), +("2+17+35",595), +("1+19+1",19), +("3+18+6",108), +("4+18+12",216), +("4+18+13",234), +("0+19+15",285), +("2+17+36",612), +("3+18+7",126), +("4+18+14",252), +("2+17+37",629), +("0+19+16",304), +("1+19+2",38), +("2+17+38",646), +("4+18+15",270), +("3+18+8",144), +("0+19+17",323), +("2+17+39",663), +("4+18+16",288), +("1+19+3",57), +("1+19+4",76), +("3+18+9",162), +("1+19+5",95), +("1+19+6",114), +("0+19+18",342), +("2+17+40",680), +("0+19+19",361), +("3+18+10",180), +("1+19+7",133), +("2+17+41",697), +("1+19+8",152), +("2+17+42",714), +("4+18+17",306), +("4+18+18",324), +("2+17+43",731), +("3+18+11",198), +("3+18+12",216), +("1+19+9",171), +("0+19+20",380), +("3+18+13",234), +("1+19+10",190), +("1+19+11",209), +("1+19+12",228), +("3+18+14",252), +("1+19+13",247), +("0+19+21",399), +("1+19+14",266), +("2+17+44",748), +("2+17+45",765), +("1+19+15",285), +("2+17+46",782), +("1+19+16",304), +("0+19+22",418), +("4+18+19",342), +("0+19+23",437), +("0+19+24",456), +("3+18+15",270), +("4+18+20",360), +("0+19+25",475), +("2+17+47",799), +("1+19+17",323), +("0+19+26",494), +("1+19+18",342), +("2+17+48",816), +("4+18+21",378), +("4+18+22",396), +("0+19+27",513), +("0+19+28",532), +("4+18+23",414), +("1+19+19",361), +("3+18+16",288), +("0+19+29",551), +("4+18+24",432), +("2+17+49",833), +("0+19+30",570), +("0+19+31",589), +("1+19+20",380), +("4+18+25",450), +("1+19+21",399), +("0+19+32",608), +("3+18+17",306), +("4+18+26",468), +("3+18+18",324), +("1+19+22",418), +("4+18+27",486), +("4+18+28",504), +("0+19+33",627), +("3+18+19",342), +("1+19+23",437), +("2+18+0",0), +("1+19+24",456), +("0+19+34",646), +("3+18+20",360), +("0+19+35",665), +("1+19+25",475), +("2+18+1",18), +("1+19+26",494), +("4+18+29",522), +("3+18+21",378), +("2+18+2",36), +("4+18+30",540), +("2+18+3",54), +("1+19+27",513), +("0+19+36",684), +("2+18+4",72), +("4+18+31",558), +("3+18+22",396), +("3+18+23",414), +("1+19+28",532), +("3+18+24",432), +("4+18+32",576), +("1+19+29",551), +("0+19+37",703), +("2+18+5",90), +("0+19+38",722), +("3+18+25",450), +("0+19+39",741), +("3+18+26",468), +("0+19+40",760), +("4+18+33",594), +("0+19+41",779), +("4+18+34",612), +("0+19+42",798), +("3+18+27",486), +("3+18+28",504), +("2+18+6",108), +("1+19+30",570), +("3+18+29",522), +("2+18+7",126), +("1+19+31",589), +("0+19+43",817), +("2+18+8",144), +("1+19+32",608), +("0+19+44",836), +("1+19+33",627), +("2+18+9",162), +("0+19+45",855), +("1+19+34",646), +("0+19+46",874), +("2+18+10",180), +("3+18+30",540), +("2+18+11",198), +("4+18+35",630), +("2+18+12",216), +("3+18+31",558), +("2+18+13",234), +("1+19+35",665), +("4+18+36",648), +("4+18+37",666), +("0+19+47",893), +("4+18+38",684), +("1+19+36",684), +("0+19+48",912), +("2+18+14",252), +("3+18+32",576), +("1+19+37",703), +("1+19+38",722), +("1+19+39",741), +("2+18+15",270), +("3+18+33",594), +("0+19+49",931), +("1+19+40",760), +("1+19+41",779), +("2+18+16",288), +("3+18+34",612), +("4+18+39",702), +("2+18+17",306), +("3+18+35",630), +("4+18+40",720), +("3+18+36",648), +("2+18+18",324), +("2+18+19",342), +("3+18+37",666), +("4+18+41",738), +("0+20+0",0), +("2+18+20",360), +("2+18+21",378), +("3+18+38",684), +("1+19+42",798), +("2+18+22",396), +("1+19+43",817), +("4+18+42",756), +("2+18+23",414), +("0+20+1",20), +("2+18+24",432), +("3+18+39",702), +("4+18+43",774), +("2+18+25",450), +("3+18+40",720), +("4+18+44",792), +("2+18+26",468), +("1+19+44",836), +("1+19+45",855), +("1+19+46",874), +("4+18+45",810), +("1+19+47",893), +("2+18+27",486), +("1+19+48",912), +("0+20+2",40), +("0+20+3",60), +("0+20+4",80), +("0+20+5",100), +("4+18+46",828), +("3+18+41",738), +("2+18+28",504), +("1+19+49",931), +("4+18+47",846), +("2+18+29",522), +("0+20+6",120), +("2+18+30",540), +("0+20+7",140), +("0+20+8",160), +("4+18+48",864), +("4+18+49",882), +("2+18+31",558), +("3+18+42",756), +("2+18+32",576), +("3+18+43",774), +("0+20+9",180), +("2+18+33",594), +("3+18+44",792), +("1+20+0",0), +("2+18+34",612), +("1+20+1",20), +("2+18+35",630), +("1+20+2",40), +("2+18+36",648), +("3+18+45",810), +("1+20+3",60), +("0+20+10",200), +("3+18+46",828), +("2+18+37",666), +("1+20+4",80), +("0+20+11",220), +("4+19+0",0), +("3+18+47",846), +("3+18+48",864), +("2+18+38",684), +("3+18+49",882), +("1+20+5",100), +("1+20+6",120), +("2+18+39",702), +("1+20+7",140), +("2+18+40",720), +("0+20+12",240), +("1+20+8",160), +("2+18+41",738), +("1+20+9",180), +("2+18+42",756), +("2+18+43",774), +("2+18+44",792), +("4+19+1",19), +("2+18+45",810), +("0+20+13",260), +("1+20+10",200), +("0+20+14",280), +("2+18+46",828), +("0+20+15",300), +("1+20+11",220), +("0+20+16",320), +("4+19+2",38), +("0+20+17",340), +("2+18+47",846), +("2+18+48",864), +("4+19+3",57), +("0+20+18",360), +("4+19+4",76), +("1+20+12",240), +("2+18+49",882), +("0+20+19",380), +("0+20+20",400), +("1+20+13",260), +("4+19+5",95), +("0+20+21",420), +("1+20+14",280), +("0+20+22",440), +("4+19+6",114), +("0+20+23",460), +("4+19+7",133), +("4+19+8",152), +("0+20+24",480), +("4+19+9",171), +("0+20+25",500), +("1+20+15",300), +("0+20+26",520), +("0+20+27",540), +("4+19+10",190), +("4+19+11",209), +("0+20+28",560), +("1+20+16",320), +("4+19+12",228), +("1+20+17",340), +("0+20+29",580), +("0+20+30",600), +("1+20+18",360), +("4+19+13",247), +("1+20+19",380), +("1+20+20",400), +("4+19+14",266), +("1+20+21",420), +("1+20+22",440), +("1+20+23",460), +("4+19+15",285), +("0+20+31",620), +("4+19+16",304), +("0+20+32",640), +("0+20+33",660), +("0+20+34",680), +("0+20+35",700), +("4+19+17",323), +("0+20+36",720), +("4+19+18",342), +("4+19+19",361), +("0+20+37",740), +("4+19+20",380), +("1+20+24",480), +("1+20+25",500), +("1+20+26",520), +("1+20+27",540), +("1+20+28",560), +("4+19+21",399), +("4+19+22",418), +("4+19+23",437), +("0+20+38",760), +("4+19+24",456), +("0+20+39",780), +("1+20+29",580), +("0+20+40",800), +("4+19+25",475), +("1+20+30",600), +("1+20+31",620), +("0+20+41",820), +("1+20+32",640), +("1+20+33",660), +("1+20+34",680), +("1+20+35",700), +("1+20+36",720), +("4+19+26",494), +("1+20+37",740), +("4+19+27",513), +("1+20+38",760), +("4+19+28",532), +("1+20+39",780), +("1+20+40",800), +("1+20+41",820), +("0+20+42",840), +("1+20+42",840), +("0+20+43",860), +("1+20+43",860), +("1+20+44",880), +("0+20+44",880), +("0+20+45",900), +("0+20+46",920), +("0+20+47",940), +("4+19+29",551), +("0+20+48",960), +("4+19+30",570), +("4+19+31",589), +("1+20+45",900), +("1+20+46",920), +("0+20+49",980), +("4+19+32",608), +("4+19+33",627), +("4+19+34",646), +("4+19+35",665), +("1+20+47",940), +("4+19+36",684), +("4+19+37",703), +("1+20+48",960), +("4+19+38",722), +("1+20+49",980), +("4+19+39",741), +("4+19+40",760), +("4+19+41",779), +("4+19+42",798), +("4+19+43",817), +("4+19+44",836), +("4+19+45",855), +("4+19+46",874), +("4+19+47",893), +("4+19+48",912), +("4+19+49",931), +("2+19+0",0), +("4+20+0",0), +("0+21+0",0), +("2+19+1",19), +("3+19+0",0), +("0+21+1",21), +("4+20+1",20), +("1+21+0",0), +("4+20+2",40), +("3+19+1",19), +("1+21+1",21), +("4+20+3",60), +("0+21+2",42), +("4+20+4",80), +("2+19+2",38), +("1+21+2",42), +("4+20+5",100), +("0+21+3",63), +("1+21+3",63), +("0+21+4",84), +("1+21+4",84), +("4+20+6",120), +("2+19+3",57), +("0+21+5",105), +("0+21+6",126), +("1+21+5",105), +("2+19+4",76), +("1+21+6",126), +("4+20+7",140), +("3+19+2",38), +("0+21+7",147), +("0+21+8",168), +("2+19+5",95), +("3+19+3",57), +("4+20+8",160), +("1+21+7",147), +("1+21+8",168), +("0+21+9",189), +("4+20+9",180), +("0+21+10",210), +("4+20+10",200), +("3+19+4",76), +("2+19+6",114), +("0+21+11",231), +("1+21+9",189), +("4+20+11",220), +("3+19+5",95), +("4+20+12",240), +("0+21+12",252), +("1+21+10",210), +("3+19+6",114), +("0+21+13",273), +("4+20+13",260), +("1+21+11",231), +("0+21+14",294), +("4+20+14",280), +("1+21+12",252), +("2+19+7",133), +("0+21+15",315), +("0+21+16",336), +("0+21+17",357), +("4+20+15",300), +("0+21+18",378), +("3+19+7",133), +("4+20+16",320), +("0+21+19",399), +("3+19+8",152), +("2+19+8",152), +("3+19+9",171), +("3+19+10",190), +("4+20+17",340), +("0+21+20",420), +("3+19+11",209), +("0+21+21",441), +("2+19+9",171), +("0+21+22",462), +("2+19+10",190), +("1+21+13",273), +("0+21+23",483), +("1+21+14",294), +("2+19+11",209), +("3+19+12",228), +("2+19+12",228), +("1+21+15",315), +("4+20+18",360), +("4+20+19",380), +("1+21+16",336), +("3+19+13",247), +("2+19+13",247), +("0+21+24",504), +("1+21+17",357), +("4+20+20",400), +("0+21+25",525), +("1+21+18",378), +("3+19+14",266), +("4+20+21",420), +("1+21+19",399), +("2+19+14",266), +("2+19+15",285), +("1+21+20",420), +("2+19+16",304), +("3+19+15",285), +("0+21+26",546), +("3+19+16",304), +("1+21+21",441), +("1+21+22",462), +("0+21+27",567), +("1+21+23",483), +("4+20+22",440), +("3+19+17",323), +("1+21+24",504), +("0+21+28",588), +("2+19+17",323), +("1+21+25",525), +("4+20+23",460), +("1+21+26",546), +("4+20+24",480), +("4+20+25",500), +("2+19+18",342), +("4+20+26",520), +("2+19+19",361), +("3+19+18",342), +("0+21+29",609), +("4+20+27",540), +("3+19+19",361), +("3+19+20",380), +("4+20+28",560), +("0+21+30",630), +("3+19+21",399), +("3+19+22",418), +("2+19+20",380), +("3+19+23",437), +("4+20+29",580), +("0+21+31",651), +("4+20+30",600), +("4+20+31",620), +("3+19+24",456), +("1+21+27",567), +("3+19+25",475), +("0+21+32",672), +("1+21+28",588), +("2+19+21",399), +("1+21+29",609), +("0+21+33",693), +("2+19+22",418), +("4+20+32",640), +("3+19+26",494), +("4+20+33",660), +("0+21+34",714), +("0+21+35",735), +("3+19+27",513), +("3+19+28",532), +("0+21+36",756), +("2+19+23",437), +("0+21+37",777), +("4+20+34",680), +("2+19+24",456), +("2+19+25",475), +("1+21+30",630), +("0+21+38",798), +("3+19+29",551), +("2+19+26",494), +("3+19+30",570), +("1+21+31",651), +("2+19+27",513), +("1+21+32",672), +("2+19+28",532), +("4+20+35",700), +("3+19+31",589), +("4+20+36",720), +("2+19+29",551), +("0+21+39",819), +("4+20+37",740), +("2+19+30",570), +("3+19+32",608), +("3+19+33",627), +("2+19+31",589), +("3+19+34",646), +("3+19+35",665), +("4+20+38",760), +("4+20+39",780), +("1+21+33",693), +("1+21+34",714), +("4+20+40",800), +("4+20+41",820), +("0+21+40",840), +("3+19+36",684), +("4+20+42",840), +("4+20+43",860), +("0+21+41",861), +("2+19+32",608), +("4+20+44",880), +("1+21+35",735), +("0+21+42",882), +("2+19+33",627), +("2+19+34",646), +("4+20+45",900), +("0+21+43",903), +("4+20+46",920), +("0+21+44",924), +("2+19+35",665), +("0+21+45",945), +("1+21+36",756), +("2+19+36",684), +("2+19+37",703), +("3+19+37",703), +("3+19+38",722), +("1+21+37",777), +("0+21+46",966), +("1+21+38",798), +("0+21+47",987), +("0+21+48",1008), +("3+19+39",741), +("2+19+38",722), +("0+21+49",1029), +("2+19+39",741), +("1+21+39",819), +("1+21+40",840), +("4+20+47",940), +("1+21+41",861), +("1+21+42",882), +("3+19+40",760), +("2+19+40",760), +("3+19+41",779), +("1+21+43",903), +("0+22+0",0), +("2+19+41",779), +("3+19+42",798), +("0+22+1",22), +("2+19+42",798), +("1+21+44",924), +("3+19+43",817), +("1+21+45",945), +("2+19+43",817), +("0+22+2",44), +("2+19+44",836), +("1+21+46",966), +("4+20+48",960), +("2+19+45",855), +("2+19+46",874), +("0+22+3",66), +("3+19+44",836), +("2+19+47",893), +("4+20+49",980), +("2+19+48",912), +("1+21+47",987), +("2+19+49",931), +("1+21+48",1008), +("0+22+4",88), +("1+21+49",1029), +("0+22+5",110), +("0+22+6",132), +("4+21+0",0), +("4+21+1",21), +("0+22+7",154), +("0+22+8",176), +("3+19+45",855), +("4+21+2",42), +("3+19+46",874), +("0+22+9",198), +("3+19+47",893), +("0+22+10",220), +("2+20+0",0), +("4+21+3",63), +("2+20+1",20), +("3+19+48",912), +("3+19+49",931), +("2+20+2",40), +("1+22+0",0), +("0+22+11",242), +("2+20+3",60), +("0+22+12",264), +("1+22+1",22), +("0+22+13",286), +("2+20+4",80), +("4+21+4",84), +("3+20+0",0), +("0+22+14",308), +("2+20+5",100), +("1+22+2",44), +("0+22+15",330), +("0+22+16",352), +("2+20+6",120), +("3+20+1",20), +("0+22+17",374), +("1+22+3",66), +("4+21+5",105), +("0+22+18",396), +("0+22+19",418), +("4+21+6",126), +("1+22+4",88), +("4+21+7",147), +("2+20+7",140), +("4+21+8",168), +("4+21+9",189), +("1+22+5",110), +("4+21+10",210), +("1+22+6",132), +("3+20+2",40), +("3+20+3",60), +("2+20+8",160), +("2+20+9",180), +("4+21+11",231), +("4+21+12",252), +("4+21+13",273), +("3+20+4",80), +("3+20+5",100), +("0+22+20",440), +("1+22+7",154), +("3+20+6",120), +("0+22+21",462), +("0+22+22",484), +("4+21+14",294), +("3+20+7",140), +("3+20+8",160), +("3+20+9",180), +("4+21+15",315), +("1+22+8",176), +("2+20+10",200), +("2+20+11",220), +("2+20+12",240), +("2+20+13",260), +("3+20+10",200), +("0+22+23",506), +("0+22+24",528), +("1+22+9",198), +("1+22+10",220), +("0+22+25",550), +("0+22+26",572), +("0+22+27",594), +("2+20+14",280), +("2+20+15",300), +("4+21+16",336), +("3+20+11",220), +("3+20+12",240), +("1+22+11",242), +("3+20+13",260), +("4+21+17",357), +("2+20+16",320), +("4+21+18",378), +("2+20+17",340), +("1+22+12",264), +("3+20+14",280), +("4+21+19",399), +("1+22+13",286), +("3+20+15",300), +("4+21+20",420), +("0+22+28",616), +("0+22+29",638), +("3+20+16",320), +("4+21+21",441), +("3+20+17",340), +("4+21+22",462), +("1+22+14",308), +("3+20+18",360), +("2+20+18",360), +("2+20+19",380), +("4+21+23",483), +("0+22+30",660), +("2+20+20",400), +("0+22+31",682), +("2+20+21",420), +("2+20+22",440), +("2+20+23",460), +("0+22+32",704), +("3+20+19",380), +("1+22+15",330), +("2+20+24",480), +("4+21+24",504), +("2+20+25",500), +("2+20+26",520), +("1+22+16",352), +("0+22+33",726), +("3+20+20",400), +("4+21+25",525), +("0+22+34",748), +("0+22+35",770), +("2+20+27",540), +("0+22+36",792), +("4+21+26",546), +("0+22+37",814), +("2+20+28",560), +("4+21+27",567), +("1+22+17",374), +("3+20+21",420), +("0+22+38",836), +("4+21+28",588), +("0+22+39",858), +("1+22+18",396), +("3+20+22",440), +("2+20+29",580), +("1+22+19",418), +("0+22+40",880), +("2+20+30",600), +("0+22+41",902), +("1+22+20",440), +("1+22+21",462), +("1+22+22",484), +("0+22+42",924), +("3+20+23",460), +("3+20+24",480), +("4+21+29",609), +("2+20+31",620), +("4+21+30",630), +("2+20+32",640), +("2+20+33",660), +("1+22+23",506), +("4+21+31",651), +("2+20+34",680), +("1+22+24",528), +("4+21+32",672), +("1+22+25",550), +("2+20+35",700), +("2+20+36",720), +("2+20+37",740), +("0+22+43",946), +("4+21+33",693), +("3+20+25",500), +("2+20+38",760), +("3+20+26",520), +("3+20+27",540), +("4+21+34",714), +("4+21+35",735), +("4+21+36",756), +("0+22+44",968), +("1+22+26",572), +("4+21+37",777), +("0+22+45",990), +("0+22+46",1012), +("0+22+47",1034), +("4+21+38",798), +("4+21+39",819), +("4+21+40",840), +("4+21+41",861), +("4+21+42",882), +("0+22+48",1056), +("1+22+27",594), +("1+22+28",616), +("3+20+28",560), +("4+21+43",903), +("3+20+29",580), +("2+20+39",780), +("2+20+40",800), +("4+21+44",924), +("3+20+30",600), +("1+22+29",638), +("2+20+41",820), +("2+20+42",840), +("0+22+49",1078), +("1+22+30",660), +("1+22+31",682), +("1+22+32",704), +("2+20+43",860), +("3+20+31",620), +("2+20+44",880), +("2+20+45",900), +("4+21+45",945), +("4+21+46",966), +("2+20+46",920), +("4+21+47",987), +("0+23+0",0), +("4+21+48",1008), +("3+20+32",640), +("2+20+47",940), +("1+22+33",726), +("2+20+48",960), +("2+20+49",980), +("1+22+34",748), +("1+22+35",770), +("1+22+36",792), +("0+23+1",23), +("3+20+33",660), +("0+23+2",46), +("1+22+37",814), +("0+23+3",69), +("4+21+49",1029), +("3+20+34",680), +("2+21+0",0), +("2+21+1",21), +("0+23+4",92), +("1+22+38",836), +("2+21+2",42), +("3+20+35",700), +("1+22+39",858), +("3+20+36",720), +("0+23+5",115), +("2+21+3",63), +("4+22+0",0), +("4+22+1",22), +("3+20+37",740), +("1+22+40",880), +("4+22+2",44), +("3+20+38",760), +("2+21+4",84), +("3+20+39",780), +("1+22+41",902), +("3+20+40",800), +("2+21+5",105), +("0+23+6",138), +("3+20+41",820), +("3+20+42",840), +("3+20+43",860), +("0+23+7",161), +("0+23+8",184), +("4+22+3",66), +("3+20+44",880), +("0+23+9",207), +("4+22+4",88), +("4+22+5",110), +("4+22+6",132), +("0+23+10",230), +("2+21+6",126), +("4+22+7",154), +("4+22+8",176), +("1+22+42",924), +("1+22+43",946), +("4+22+9",198), +("3+20+45",900), +("0+23+11",253), +("1+22+44",968), +("1+22+45",990), +("1+22+46",1012), +("4+22+10",220), +("4+22+11",242), +("4+22+12",264), +("0+23+12",276), +("0+23+13",299), +("3+20+46",920), +("3+20+47",940), +("2+21+7",147), +("3+20+48",960), +("0+23+14",322), +("2+21+8",168), +("1+22+47",1034), +("2+21+9",189), +("4+22+13",286), +("0+23+15",345), +("0+23+16",368), +("3+20+49",980), +("1+22+48",1056), +("2+21+10",210), +("0+23+17",391), +("0+23+18",414), +("0+23+19",437), +("4+22+14",308), +("1+22+49",1078), +("2+21+11",231), +("4+22+15",330), +("2+21+12",252), +("4+22+16",352), +("2+21+13",273), +("1+23+0",0), +("4+22+17",374), +("1+23+1",23), +("2+21+14",294), +("1+23+2",46), +("2+21+15",315), +("4+22+18",396), +("2+21+16",336), +("1+23+3",69), +("3+21+0",0), +("4+22+19",418), +("0+23+20",460), +("0+23+21",483), +("0+23+22",506), +("4+22+20",440), +("2+21+17",357), +("4+22+21",462), +("4+22+22",484), +("1+23+4",92), +("1+23+5",115), +("1+23+6",138), +("0+23+23",529), +("4+22+23",506), +("3+21+1",21), +("0+23+24",552), +("2+21+18",378), +("0+23+25",575), +("2+21+19",399), +("4+22+24",528), +("0+23+26",598), +("1+23+7",161), +("1+23+8",184), +("4+22+25",550), +("0+23+27",621), +("4+22+26",572), +("1+23+9",207), +("3+21+2",42), +("2+21+20",420), +("1+23+10",230), +("1+23+11",253), +("1+23+12",276), +("0+23+28",644), +("1+23+13",299), +("3+21+3",63), +("3+21+4",84), +("1+23+14",322), +("2+21+21",441), +("1+23+15",345), +("0+23+29",667), +("4+22+27",594), +("1+23+16",368), +("4+22+28",616), +("3+21+5",105), +("1+23+17",391), +("1+23+18",414), +("4+22+29",638), +("2+21+22",462), +("2+21+23",483), +("3+21+6",126), +("1+23+19",437), +("0+23+30",690), +("1+23+20",460), +("4+22+30",660), +("4+22+31",682), +("3+21+7",147), +("1+23+21",483), +("1+23+22",506), +("4+22+32",704), +("0+23+31",713), +("2+21+24",504), +("2+21+25",525), +("2+21+26",546), +("1+23+23",529), +("0+23+32",736), +("4+22+33",726), +("4+22+34",748), +("4+22+35",770), +("2+21+27",567), +("1+23+24",552), +("3+21+8",168), +("2+21+28",588), +("0+23+33",759), +("4+22+36",792), +("2+21+29",609), +("1+23+25",575), +("0+23+34",782), +("2+21+30",630), +("4+22+37",814), +("4+22+38",836), +("2+21+31",651), +("0+23+35",805), +("1+23+26",598), +("3+21+9",189), +("4+22+39",858), +("2+21+32",672), +("3+21+10",210), +("2+21+33",693), +("3+21+11",231), +("0+23+36",828), +("1+23+27",621), +("4+22+40",880), +("4+22+41",902), +("3+21+12",252), +("0+23+37",851), +("1+23+28",644), +("2+21+34",714), +("0+23+38",874), +("2+21+35",735), +("2+21+36",756), +("2+21+37",777), +("1+23+29",667), +("3+21+13",273), +("1+23+30",690), +("1+23+31",713), +("2+21+38",798), +("2+21+39",819), +("3+21+14",294), +("4+22+42",924), +("3+21+15",315), +("2+21+40",840), +("4+22+43",946), +("3+21+16",336), +("3+21+17",357), +("0+23+39",897), +("3+21+18",378), +("4+22+44",968), +("1+23+32",736), +("3+21+19",399), +("2+21+41",861), +("4+22+45",990), +("2+21+42",882), +("2+21+43",903), +("4+22+46",1012), +("1+23+33",759), +("3+21+20",420), +("3+21+21",441), +("0+23+40",920), +("1+23+34",782), +("2+21+44",924), +("0+23+41",943), +("0+23+42",966), +("3+21+22",462), +("0+23+43",989), +("1+23+35",805), +("1+23+36",828), +("1+23+37",851), +("1+23+38",874), +("1+23+39",897), +("2+21+45",945), +("0+23+44",1012), +("1+23+40",920), +("1+23+41",943), +("2+21+46",966), +("2+21+47",987), +("2+21+48",1008), +("3+21+23",483), +("1+23+42",966), +("2+21+49",1029), +("0+23+45",1035), +("0+23+46",1058), +("3+21+24",504), +("3+21+25",525), +("1+23+43",989), +("4+22+47",1034), +("0+23+47",1081), +("2+22+0",0), +("0+23+48",1104), +("3+21+26",546), +("1+23+44",1012), +("0+23+49",1127), +("2+22+1",22), +("4+22+48",1056), +("2+22+2",44), +("1+23+45",1035), +("3+21+27",567), +("0+24+0",0), +("4+22+49",1078), +("2+22+3",66), +("2+22+4",88), +("2+22+5",110), +("1+23+46",1058), +("2+22+6",132), +("1+23+47",1081), +("2+22+7",154), +("3+21+28",588), +("4+23+0",0), +("3+21+29",609), +("4+23+1",23), +("0+24+1",24), +("2+22+8",176), +("4+23+2",46), +("4+23+3",69), +("4+23+4",92), +("1+23+48",1104), +("0+24+2",48), +("0+24+3",72), +("4+23+5",115), +("1+23+49",1127), +("4+23+6",138), +("0+24+4",96), +("0+24+5",120), +("4+23+7",161), +("3+21+30",630), +("2+22+9",198), +("3+21+31",651), +("0+24+6",144), +("1+24+0",0), +("1+24+1",24), +("0+24+7",168), +("0+24+8",192), +("1+24+2",48), +("0+24+9",216), +("1+24+3",72), +("3+21+32",672), +("0+24+10",240), +("2+22+10",220), +("2+22+11",242), +("3+21+33",693), +("3+21+34",714), +("3+21+35",735), +("4+23+8",184), +("0+24+11",264), +("0+24+12",288), +("1+24+4",96), +("4+23+9",207), +("0+24+13",312), +("1+24+5",120), +("0+24+14",336), +("3+21+36",756), +("1+24+6",144), +("3+21+37",777), +("4+23+10",230), +("2+22+12",264), +("3+21+38",798), +("1+24+7",168), +("2+22+13",286), +("1+24+8",192), +("1+24+9",216), +("0+24+15",360), +("0+24+16",384), +("1+24+10",240), +("4+23+11",253), +("3+21+39",819), +("4+23+12",276), +("1+24+11",264), +("1+24+12",288), +("1+24+13",312), +("0+24+17",408), +("3+21+40",840), +("3+21+41",861), +("0+24+18",432), +("3+21+42",882), +("0+24+19",456), +("1+24+14",336), +("3+21+43",903), +("1+24+15",360), +("4+23+13",299), +("3+21+44",924), +("0+24+20",480), +("1+24+16",384), +("4+23+14",322), +("4+23+15",345), +("1+24+17",408), +("2+22+14",308), +("0+24+21",504), +("0+24+22",528), +("2+22+15",330), +("3+21+45",945), +("1+24+18",432), +("1+24+19",456), +("1+24+20",480), +("2+22+16",352), +("1+24+21",504), +("4+23+16",368), +("4+23+17",391), +("4+23+18",414), +("2+22+17",374), +("1+24+22",528), +("1+24+23",552), +("1+24+24",576), +("1+24+25",600), +("0+24+23",552), +("1+24+26",624), +("3+21+46",966), +("3+21+47",987), +("4+23+19",437), +("4+23+20",460), +("3+21+48",1008), +("2+22+18",396), +("1+24+27",648), +("1+24+28",672), +("1+24+29",696), +("1+24+30",720), +("1+24+31",744), +("2+22+19",418), +("3+21+49",1029), +("4+23+21",483), +("0+24+24",576), +("0+24+25",600), +("0+24+26",624), +("2+22+20",440), +("0+24+27",648), +("4+23+22",506), +("4+23+23",529), +("2+22+21",462), +("4+23+24",552), +("0+24+28",672), +("3+22+0",0), +("1+24+32",768), +("4+23+25",575), +("0+24+29",696), +("4+23+26",598), +("4+23+27",621), +("1+24+33",792), +("4+23+28",644), +("1+24+34",816), +("2+22+22",484), +("1+24+35",840), +("4+23+29",667), +("2+22+23",506), +("3+22+1",22), +("4+23+30",690), +("0+24+30",720), +("1+24+36",864), +("4+23+31",713), +("2+22+24",528), +("3+22+2",44), +("0+24+31",744), +("4+23+32",736), +("3+22+3",66), +("0+24+32",768), +("2+22+25",550), +("0+24+33",792), +("2+22+26",572), +("4+23+33",759), +("1+24+37",888), +("4+23+34",782), +("1+24+38",912), +("3+22+4",88), +("1+24+39",936), +("2+22+27",594), +("4+23+35",805), +("1+24+40",960), +("0+24+34",816), +("4+23+36",828), +("2+22+28",616), +("4+23+37",851), +("2+22+29",638), +("2+22+30",660), +("3+22+5",110), +("0+24+35",840), +("2+22+31",682), +("1+24+41",984), +("2+22+32",704), +("4+23+38",874), +("1+24+42",1008), +("0+24+36",864), +("1+24+43",1032), +("4+23+39",897), +("3+22+6",132), +("0+24+37",888), +("3+22+7",154), +("3+22+8",176), +("4+23+40",920), +("2+22+33",726), +("4+23+41",943), +("3+22+9",198), +("2+22+34",748), +("3+22+10",220), +("2+22+35",770), +("1+24+44",1056), +("1+24+45",1080), +("2+22+36",792), +("1+24+46",1104), +("3+22+11",242), +("4+23+42",966), +("1+24+47",1128), +("2+22+37",814), +("2+22+38",836), +("0+24+38",912), +("4+23+43",989), +("2+22+39",858), +("1+24+48",1152), +("3+22+12",264), +("1+24+49",1176), +("2+22+40",880), +("4+23+44",1012), +("0+24+39",936), +("0+24+40",960), +("4+23+45",1035), +("3+22+13",286), +("3+22+14",308), +("2+22+41",902), +("4+23+46",1058), +("3+22+15",330), +("4+23+47",1081), +("2+22+42",924), +("3+22+16",352), +("0+24+41",984), +("0+24+42",1008), +("3+22+17",374), +("1+25+0",0), +("4+23+48",1104), +("0+24+43",1032), +("4+23+49",1127), +("3+22+18",396), +("3+22+19",418), +("2+22+43",946), +("0+24+44",1056), +("2+22+44",968), +("0+24+45",1080), +("2+22+45",990), +("4+24+0",0), +("2+22+46",1012), +("0+24+46",1104), +("1+25+1",25), +("3+22+20",440), +("4+24+1",24), +("1+25+2",50), +("2+22+47",1034), +("2+22+48",1056), +("3+22+21",462), +("1+25+3",75), +("3+22+22",484), +("0+24+47",1128), +("4+24+2",48), +("2+22+49",1078), +("4+24+3",72), +("4+24+4",96), +("0+24+48",1152), +("3+22+23",506), +("3+22+24",528), +("0+24+49",1176), +("1+25+4",100), +("4+24+5",120), +("3+22+25",550), +("1+25+5",125), +("2+23+0",0), +("1+25+6",150), +("3+22+26",572), +("3+22+27",594), +("2+23+1",23), +("0+25+0",0), +("3+22+28",616), +("3+22+29",638), +("4+24+6",144), +("3+22+30",660), +("2+23+2",46), +("0+25+1",25), +("0+25+2",50), +("3+22+31",682), +("2+23+3",69), +("2+23+4",92), +("3+22+32",704), +("2+23+5",115), +("1+25+7",175), +("3+22+33",726), +("3+22+34",748), +("2+23+6",138), +("0+25+3",75), +("2+23+7",161), +("3+22+35",770), +("0+25+4",100), +("1+25+8",200), +("1+25+9",225), +("4+24+7",168), +("2+23+8",184), +("3+22+36",792), +("0+25+5",125), +("0+25+6",150), +("2+23+9",207), +("2+23+10",230), +("0+25+7",175), +("0+25+8",200), +("0+25+9",225), +("3+22+37",814), +("4+24+8",192), +("2+23+11",253), +("0+25+10",250), +("1+25+10",250), +("4+24+9",216), +("4+24+10",240), +("2+23+12",276), +("0+25+11",275), +("1+25+11",275), +("3+22+38",836), +("2+23+13",299), +("1+25+12",300), +("4+24+11",264), +("1+25+13",325), +("1+25+14",350), +("4+24+12",288), +("1+25+15",375), +("0+25+12",300), +("3+22+39",858), +("0+25+13",325), +("4+24+13",312), +("4+24+14",336), +("0+25+14",350), +("3+22+40",880), +("0+25+15",375), +("3+22+41",902), +("4+24+15",360), +("1+25+16",400), +("2+23+14",322), +("3+22+42",924), +("4+24+16",384), +("3+22+43",946), +("1+25+17",425), +("3+22+44",968), +("0+25+16",400), +("1+25+18",450), +("2+23+15",345), +("1+25+19",475), +("3+22+45",990), +("0+25+17",425), +("0+25+18",450), +("4+24+17",408), +("4+24+18",432), +("2+23+16",368), +("3+22+46",1012), +("3+22+47",1034), +("3+22+48",1056), +("4+24+19",456), +("3+22+49",1078), +("4+24+20",480), +("1+25+20",500), +("2+23+17",391), +("0+25+19",475), +("4+24+21",504), +("4+24+22",528), +("4+24+23",552), +("1+25+21",525), +("4+24+24",576), +("0+25+20",500), +("0+25+21",525), +("1+25+22",550), +("0+25+22",550), +("2+23+18",414), +("3+23+0",0), +("1+25+23",575), +("2+23+19",437), +("4+24+25",600), +("4+24+26",624), +("0+25+23",575), +("0+25+24",600), +("1+25+24",600), +("3+23+1",23), +("2+23+20",460), +("4+24+27",648), +("0+25+25",625), +("1+25+25",625), +("0+25+26",650), +("3+23+2",46), +("3+23+3",69), +("1+25+26",650), +("0+25+27",675), +("4+24+28",672), +("0+25+28",700), +("3+23+4",92), +("0+25+29",725), +("2+23+21",483), +("2+23+22",506), +("1+25+27",675), +("0+25+30",750), +("4+24+29",696), +("0+25+31",775), +("4+24+30",720), +("0+25+32",800), +("2+23+23",529), +("2+23+24",552), +("0+25+33",825), +("1+25+28",700), +("4+24+31",744), +("4+24+32",768), +("2+23+25",575), +("2+23+26",598), +("3+23+5",115), +("0+25+34",850), +("3+23+6",138), +("0+25+35",875), +("0+25+36",900), +("0+25+37",925), +("4+24+33",792), +("4+24+34",816), +("1+25+29",725), +("4+24+35",840), +("1+25+30",750), +("2+23+27",621), +("3+23+7",161), +("1+25+31",775), +("1+25+32",800), +("4+24+36",864), +("1+25+33",825), +("2+23+28",644), +("1+25+34",850), +("2+23+29",667), +("0+25+38",950), +("4+24+37",888), +("4+24+38",912), +("1+25+35",875), +("1+25+36",900), +("4+24+39",936), +("4+24+40",960), +("1+25+37",925), +("4+24+41",984), +("1+25+38",950), +("2+23+30",690), +("3+23+8",184), +("0+25+39",975), +("3+23+9",207), +("0+25+40",1000), +("3+23+10",230), +("3+23+11",253), +("4+24+42",1008), +("3+23+12",276), +("3+23+13",299), +("2+23+31",713), +("2+23+32",736), +("0+25+41",1025), +("1+25+39",975), +("4+24+43",1032), +("4+24+44",1056), +("1+25+40",1000), +("1+25+41",1025), +("1+25+42",1050), +("2+23+33",759), +("3+23+14",322), +("4+24+45",1080), +("4+24+46",1104), +("0+25+42",1050), +("4+24+47",1128), +("1+25+43",1075), +("2+23+34",782), +("3+23+15",345), +("3+23+16",368), +("1+25+44",1100), +("2+23+35",805), +("3+23+17",391), +("1+25+45",1125), +("2+23+36",828), +("2+23+37",851), +("0+25+43",1075), +("2+23+38",874), +("2+23+39",897), +("0+25+44",1100), +("0+25+45",1125), +("0+25+46",1150), +("1+25+46",1150), +("0+25+47",1175), +("0+25+48",1200), +("4+24+48",1152), +("4+24+49",1176), +("3+23+18",414), +("0+25+49",1225), +("1+25+47",1175), +("3+23+19",437), +("3+23+20",460), +("2+23+40",920), +("2+23+41",943), +("4+25+0",0), +("3+23+21",483), +("4+25+1",25), +("0+26+0",0), +("0+26+1",26), +("3+23+22",506), +("0+26+2",52), +("2+23+42",966), +("1+25+48",1200), +("3+23+23",529), +("0+26+3",78), +("0+26+4",104), +("2+23+43",989), +("1+25+49",1225), +("0+26+5",130), +("3+23+24",552), +("2+23+44",1012), +("0+26+6",156), +("4+25+2",50), +("0+26+7",182), +("1+26+0",0), +("2+23+45",1035), +("4+25+3",75), +("4+25+4",100), +("3+23+25",575), +("0+26+8",208), +("1+26+1",26), +("3+23+26",598), +("2+23+46",1058), +("4+25+5",125), +("4+25+6",150), +("2+23+47",1081), +("0+26+9",234), +("4+25+7",175), +("4+25+8",200), +("3+23+27",621), +("0+26+10",260), +("0+26+11",286), +("3+23+28",644), +("3+23+29",667), +("4+25+9",225), +("1+26+2",52), +("2+23+48",1104), +("2+23+49",1127), +("0+26+12",312), +("3+23+30",690), +("1+26+3",78), +("3+23+31",713), +("0+26+13",338), +("1+26+4",104), +("1+26+5",130), +("0+26+14",364), +("2+24+0",0), +("0+26+15",390), +("2+24+1",24), +("3+23+32",736), +("1+26+6",156), +("4+25+10",250), +("4+25+11",275), +("0+26+16",416), +("4+25+12",300), +("3+23+33",759), +("0+26+17",442), +("1+26+7",182), +("1+26+8",208), +("1+26+9",234), +("3+23+34",782), +("1+26+10",260), +("0+26+18",468), +("3+23+35",805), +("3+23+36",828), +("4+25+13",325), +("0+26+19",494), +("0+26+20",520), +("2+24+2",48), +("0+26+21",546), +("1+26+11",286), +("1+26+12",312), +("0+26+22",572), +("0+26+23",598), +("0+26+24",624), +("0+26+25",650), +("4+25+14",350), +("1+26+13",338), +("3+23+37",851), +("0+26+26",676), +("4+25+15",375), +("0+26+27",702), +("2+24+3",72), +("3+23+38",874), +("0+26+28",728), +("4+25+16",400), +("1+26+14",364), +("4+25+17",425), +("1+26+15",390), +("4+25+18",450), +("0+26+29",754), +("0+26+30",780), +("0+26+31",806), +("1+26+16",416), +("0+26+32",832), +("2+24+4",96), +("0+26+33",858), +("1+26+17",442), +("3+23+39",897), +("1+26+18",468), +("4+25+19",475), +("1+26+19",494), +("0+26+34",884), +("0+26+35",910), +("4+25+20",500), +("4+25+21",525), +("2+24+5",120), +("3+23+40",920), +("3+23+41",943), +("0+26+36",936), +("1+26+20",520), +("3+23+42",966), +("4+25+22",550), +("2+24+6",144), +("0+26+37",962), +("0+26+38",988), +("2+24+7",168), +("1+26+21",546), +("3+23+43",989), +("1+26+22",572), +("3+23+44",1012), +("0+26+39",1014), +("1+26+23",598), +("0+26+40",1040), +("3+23+45",1035), +("4+25+23",575), +("0+26+41",1066), +("2+24+8",192), +("0+26+42",1092), +("2+24+9",216), +("3+23+46",1058), +("2+24+10",240), +("3+23+47",1081), +("2+24+11",264), +("3+23+48",1104), +("1+26+24",624), +("3+23+49",1127), +("1+26+25",650), +("2+24+12",288), +("0+26+43",1118), +("4+25+24",600), +("2+24+13",312), +("0+26+44",1144), +("3+24+0",0), +("2+24+14",336), +("2+24+15",360), +("1+26+26",676), +("4+25+25",625), +("1+26+27",702), +("0+26+45",1170), +("1+26+28",728), +("4+25+26",650), +("1+26+29",754), +("2+24+16",384), +("3+24+1",24), +("2+24+17",408), +("1+26+30",780), +("0+26+46",1196), +("1+26+31",806), +("0+26+47",1222), +("3+24+2",48), +("1+26+32",832), +("1+26+33",858), +("3+24+3",72), +("1+26+34",884), +("0+26+48",1248), +("3+24+4",96), +("2+24+18",432), +("4+25+27",675), +("3+24+5",120), +("1+26+35",910), +("0+26+49",1274), +("3+24+6",144), +("2+24+19",456), +("2+24+20",480), +("1+26+36",936), +("3+24+7",168), +("2+24+21",504), +("1+26+37",962), +("2+24+22",528), +("2+24+23",552), +("3+24+8",192), +("2+24+24",576), +("4+25+28",700), +("1+26+38",988), +("2+24+25",600), +("4+25+29",725), +("0+27+0",0), +("1+26+39",1014), +("1+26+40",1040), +("4+25+30",750), +("1+26+41",1066), +("4+25+31",775), +("2+24+26",624), +("0+27+1",27), +("4+25+32",800), +("2+24+27",648), +("4+25+33",825), +("0+27+2",54), +("4+25+34",850), +("3+24+9",216), +("1+26+42",1092), +("2+24+28",672), +("3+24+10",240), +("4+25+35",875), +("4+25+36",900), +("1+26+43",1118), +("4+25+37",925), +("3+24+11",264), +("4+25+38",950), +("2+24+29",696), +("3+24+12",288), +("1+26+44",1144), +("4+25+39",975), +("3+24+13",312), +("1+26+45",1170), +("4+25+40",1000), +("0+27+3",81), +("4+25+41",1025), +("0+27+4",108), +("3+24+14",336), +("4+25+42",1050), +("0+27+5",135), +("2+24+30",720), +("0+27+6",162), +("4+25+43",1075), +("2+24+31",744), +("3+24+15",360), +("4+25+44",1100), +("3+24+16",384), +("3+24+17",408), +("4+25+45",1125), +("2+24+32",768), +("1+26+46",1196), +("1+26+47",1222), +("3+24+18",432), +("1+26+48",1248), +("4+25+46",1150), +("2+24+33",792), +("2+24+34",816), +("0+27+7",189), +("2+24+35",840), +("2+24+36",864), +("3+24+19",456), +("3+24+20",480), +("4+25+47",1175), +("3+24+21",504), +("3+24+22",528), +("1+26+49",1274), +("2+24+37",888), +("0+27+8",216), +("3+24+23",552), +("2+24+38",912), +("3+24+24",576), +("4+25+48",1200), +("3+24+25",600), +("4+25+49",1225), +("3+24+26",624), +("3+24+27",648), +("1+27+0",0), +("2+24+39",936), +("3+24+28",672), +("3+24+29",696), +("0+27+9",243), +("0+27+10",270), +("1+27+1",27), +("1+27+2",54), +("3+24+30",720), +("2+24+40",960), +("1+27+3",81), +("3+24+31",744), +("3+24+32",768), +("0+27+11",297), +("2+24+41",984), +("3+24+33",792), +("1+27+4",108), +("2+24+42",1008), +("2+24+43",1032), +("0+27+12",324), +("1+27+5",135), +("0+27+13",351), +("2+24+44",1056), +("2+24+45",1080), +("3+24+34",816), +("1+27+6",162), +("2+24+46",1104), +("2+24+47",1128), +("3+24+35",840), +("1+27+7",189), +("2+24+48",1152), +("3+24+36",864), +("0+27+14",378), +("2+24+49",1176), +("1+27+8",216), +("1+27+9",243), +("0+27+15",405), +("0+27+16",432), +("0+27+17",459), +("0+27+18",486), +("1+27+10",270), +("1+27+11",297), +("3+24+37",888), +("1+27+12",324), +("3+24+38",912), +("3+24+39",936), +("1+27+13",351), +("0+27+19",513), +("0+27+20",540), +("1+27+14",378), +("1+27+15",405), +("3+24+40",960), +("3+24+41",984), +("0+27+21",567), +("1+27+16",432), +("0+27+22",594), +("1+27+17",459), +("0+27+23",621), +("1+27+18",486), +("1+27+19",513), +("0+27+24",648), +("3+24+42",1008), +("3+24+43",1032), +("0+27+25",675), +("0+27+26",702), +("3+24+44",1056), +("3+24+45",1080), +("0+27+27",729), +("0+27+28",756), +("0+27+29",783), +("1+27+20",540), +("3+24+46",1104), +("1+27+21",567), +("3+24+47",1128), +("0+27+30",810), +("1+27+22",594), +("1+27+23",621), +("3+24+48",1152), +("0+27+31",837), +("0+27+32",864), +("3+24+49",1176), +("1+27+24",648), +("1+27+25",675), +("0+27+33",891), +("1+27+26",702), +("1+27+27",729), +("0+27+34",918), +("1+27+28",756), +("0+27+35",945), +("1+27+29",783), +("0+27+36",972), +("1+27+30",810), +("0+27+37",999), +("1+27+31",837), +("0+27+38",1026), +("1+27+32",864), +("0+27+39",1053), +("0+27+40",1080), +("1+27+33",891), +("0+27+41",1107), +("1+27+34",918), +("0+27+42",1134), +("0+27+43",1161), +("1+27+35",945), +("1+27+36",972), +("0+27+44",1188), +("1+27+37",999), +("1+27+38",1026), +("1+27+39",1053), +("1+27+40",1080), +("1+27+41",1107), +("1+27+42",1134), +("0+27+45",1215), +("0+27+46",1242), +("1+27+43",1161), +("0+27+47",1269), +("1+27+44",1188), +("0+27+48",1296), +("0+27+49",1323), +("1+27+45",1215), +("1+27+46",1242), +("1+27+47",1269), +("1+27+48",1296), +("1+27+49",1323), +("4+26+0",0), +("2+25+0",0), +("2+25+1",25), +("4+26+1",26), +("4+26+2",52), +("2+25+2",50), +("2+25+3",75), +("4+26+3",78), +("4+26+4",104), +("2+25+4",100), +("4+26+5",130), +("2+25+5",125), +("4+26+6",156), +("2+25+6",150), +("2+25+7",175), +("4+26+7",182), +("2+25+8",200), +("4+26+8",208), +("4+26+9",234), +("4+26+10",260), +("2+25+9",225), +("4+26+11",286), +("4+26+12",312), +("4+26+13",338), +("4+26+14",364), +("4+26+15",390), +("2+25+10",250), +("2+25+11",275), +("2+25+12",300), +("4+26+16",416), +("4+26+17",442), +("4+26+18",468), +("4+26+19",494), +("2+25+13",325), +("2+25+14",350), +("2+25+15",375), +("4+26+20",520), +("4+26+21",546), +("2+25+16",400), +("4+26+22",572), +("2+25+17",425), +("4+26+23",598), +("2+25+18",450), +("4+26+24",624), +("4+26+25",650), +("2+25+19",475), +("2+25+20",500), +("4+26+26",676), +("4+26+27",702), +("4+26+28",728), +("4+26+29",754), +("4+26+30",780), +("2+25+21",525), +("2+25+22",550), +("4+26+31",806), +("4+26+32",832), +("2+25+23",575), +("4+26+33",858), +("4+26+34",884), +("2+25+24",600), +("2+25+25",625), +("4+26+35",910), +("2+25+26",650), +("4+26+36",936), +("4+26+37",962), +("2+25+27",675), +("2+25+28",700), +("2+25+29",725), +("4+26+38",988), +("2+25+30",750), +("2+25+31",775), +("4+26+39",1014), +("2+25+32",800), +("4+26+40",1040), +("4+26+41",1066), +("4+26+42",1092), +("4+26+43",1118), +("4+26+44",1144), +("2+25+33",825), +("4+26+45",1170), +("2+25+34",850), +("4+26+46",1196), +("2+25+35",875), +("4+26+47",1222), +("4+26+48",1248), +("4+26+49",1274), +("2+25+36",900), +("2+25+37",925), +("2+25+38",950), +("2+25+39",975), +("2+25+40",1000), +("2+25+41",1025), +("2+25+42",1050), +("0+28+0",0), +("4+27+0",0), +("3+25+0",0), +("1+28+0",0), +("2+25+43",1075), +("0+28+1",28), +("2+25+44",1100), +("3+25+1",25), +("4+27+1",27), +("3+25+2",50), +("4+27+2",54), +("3+25+3",75), +("1+28+1",28), +("0+28+2",56), +("3+25+4",100), +("2+25+45",1125), +("4+27+3",81), +("2+25+46",1150), +("1+28+2",56), +("4+27+4",108), +("3+25+5",125), +("1+28+3",84), +("0+28+3",84), +("2+25+47",1175), +("4+27+5",135), +("3+25+6",150), +("4+27+6",162), +("4+27+7",189), +("1+28+4",112), +("2+25+48",1200), +("0+28+4",112), +("3+25+7",175), +("2+25+49",1225), +("4+27+8",216), +("1+28+5",140), +("0+28+5",140), +("3+25+8",200), +("0+28+6",168), +("3+25+9",225), +("4+27+9",243), +("0+28+7",196), +("4+27+10",270), +("3+25+10",250), +("0+28+8",224), +("4+27+11",297), +("1+28+6",168), +("0+28+9",252), +("4+27+12",324), +("1+28+7",196), +("3+25+11",275), +("4+27+13",351), +("1+28+8",224), +("3+25+12",300), +("4+27+14",378), +("2+26+0",0), +("4+27+15",405), +("0+28+10",280), +("2+26+1",26), +("0+28+11",308), +("1+28+9",252), +("3+25+13",325), +("1+28+10",280), +("1+28+11",308), +("3+25+14",350), +("0+28+12",336), +("0+28+13",364), +("1+28+12",336), +("1+28+13",364), +("0+28+14",392), +("2+26+2",52), +("4+27+16",432), +("2+26+3",78), +("4+27+17",459), +("0+28+15",420), +("0+28+16",448), +("1+28+14",392), +("4+27+18",486), +("1+28+15",420), +("4+27+19",513), +("0+28+17",476), +("4+27+20",540), +("3+25+15",375), +("2+26+4",104), +("0+28+18",504), +("0+28+19",532), +("3+25+16",400), +("3+25+17",425), +("0+28+20",560), +("4+27+21",567), +("0+28+21",588), +("3+25+18",450), +("1+28+16",448), +("2+26+5",130), +("4+27+22",594), +("0+28+22",616), +("2+26+6",156), +("0+28+23",644), +("1+28+17",476), +("4+27+23",621), +("1+28+18",504), +("4+27+24",648), +("3+25+19",475), +("2+26+7",182), +("4+27+25",675), +("0+28+24",672), +("1+28+19",532), +("3+25+20",500), +("0+28+25",700), +("2+26+8",208), +("3+25+21",525), +("1+28+20",560), +("4+27+26",702), +("2+26+9",234), +("3+25+22",550), +("4+27+27",729), +("0+28+26",728), +("0+28+27",756), +("2+26+10",260), +("2+26+11",286), +("0+28+28",784), +("4+27+28",756), +("1+28+21",588), +("2+26+12",312), +("4+27+29",783), +("2+26+13",338), +("4+27+30",810), +("0+28+29",812), +("2+26+14",364), +("1+28+22",616), +("0+28+30",840), +("0+28+31",868), +("0+28+32",896), +("4+27+31",837), +("0+28+33",924), +("2+26+15",390), +("2+26+16",416), +("3+25+23",575), +("2+26+17",442), +("4+27+32",864), +("1+28+23",644), +("2+26+18",468), +("4+27+33",891), +("1+28+24",672), +("0+28+34",952), +("2+26+19",494), +("3+25+24",600), +("3+25+25",625), +("2+26+20",520), +("4+27+34",918), +("3+25+26",650), +("1+28+25",700), +("0+28+35",980), +("4+27+35",945), +("0+28+36",1008), +("2+26+21",546), +("2+26+22",572), +("1+28+26",728), +("2+26+23",598), +("3+25+27",675), +("1+28+27",756), +("0+28+37",1036), +("2+26+24",624), +("1+28+28",784), +("4+27+36",972), +("3+25+28",700), +("1+28+29",812), +("2+26+25",650), +("3+25+29",725), +("3+25+30",750), +("0+28+38",1064), +("2+26+26",676), +("3+25+31",775), +("2+26+27",702), +("0+28+39",1092), +("2+26+28",728), +("2+26+29",754), +("1+28+30",840), +("4+27+37",999), +("1+28+31",868), +("2+26+30",780), +("3+25+32",800), +("0+28+40",1120), +("3+25+33",825), +("0+28+41",1148), +("0+28+42",1176), +("2+26+31",806), +("3+25+34",850), +("0+28+43",1204), +("3+25+35",875), +("2+26+32",832), +("1+28+32",896), +("1+28+33",924), +("1+28+34",952), +("1+28+35",980), +("1+28+36",1008), +("2+26+33",858), +("1+28+37",1036), +("4+27+38",1026), +("0+28+44",1232), +("2+26+34",884), +("0+28+45",1260), +("2+26+35",910), +("1+28+38",1064), +("4+27+39",1053), +("0+28+46",1288), +("4+27+40",1080), +("0+28+47",1316), +("1+28+39",1092), +("0+28+48",1344), +("3+25+36",900), +("3+25+37",925), +("0+28+49",1372), +("3+25+38",950), +("2+26+36",936), +("3+25+39",975), +("1+28+40",1120), +("3+25+40",1000), +("3+25+41",1025), +("1+28+41",1148), +("4+27+41",1107), +("4+27+42",1134), +("3+25+42",1050), +("1+28+42",1176), +("2+26+37",962), +("2+26+38",988), +("4+27+43",1161), +("1+28+43",1204), +("4+27+44",1188), +("1+28+44",1232), +("3+25+43",1075), +("4+27+45",1215), +("2+26+39",1014), +("4+27+46",1242), +("3+25+44",1100), +("1+28+45",1260), +("0+29+0",0), +("2+26+40",1040), +("1+28+46",1288), +("3+25+45",1125), +("4+27+47",1269), +("2+26+41",1066), +("1+28+47",1316), +("4+27+48",1296), +("0+29+1",29), +("3+25+46",1150), +("4+27+49",1323), +("2+26+42",1092), +("0+29+2",58), +("1+28+48",1344), +("1+28+49",1372), +("3+25+47",1175), +("0+29+3",87), +("4+28+0",0), +("3+25+48",1200), +("3+25+49",1225), +("0+29+4",116), +("0+29+5",145), +("4+28+1",28), +("0+29+6",174), +("0+29+7",203), +("2+26+43",1118), +("4+28+2",56), +("4+28+3",84), +("2+26+44",1144), +("2+26+45",1170), +("2+26+46",1196), +("4+28+4",112), +("0+29+8",232), +("0+29+9",261), +("0+29+10",290), +("4+28+5",140), +("0+29+11",319), +("0+29+12",348), +("2+26+47",1222), +("2+26+48",1248), +("2+26+49",1274), +("4+28+6",168), +("0+29+13",377), +("4+28+7",196), +("0+29+14",406), +("0+29+15",435), +("4+28+8",224), +("4+28+9",252), +("4+28+10",280), +("4+28+11",308), +("0+29+16",464), +("0+29+17",493), +("4+28+12",336), +("4+28+13",364), +("0+29+18",522), +("4+28+14",392), +("4+28+15",420), +("4+28+16",448), +("4+28+17",476), +("4+28+18",504), +("4+28+19",532), +("4+28+20",560), +("0+29+19",551), +("4+28+21",588), +("4+28+22",616), +("0+29+20",580), +("0+29+21",609), +("4+28+23",644), +("0+29+22",638), +("0+29+23",667), +("4+28+24",672), +("4+28+25",700), +("0+29+24",696), +("0+29+25",725), +("4+28+26",728), +("4+28+27",756), +("4+28+28",784), +("0+29+26",754), +("0+29+27",783), +("4+28+29",812), +("0+29+28",812), +("0+29+29",841), +("4+28+30",840), +("0+29+30",870), +("0+29+31",899), +("0+29+32",928), +("0+29+33",957), +("4+28+31",868), +("0+29+34",986), +("0+29+35",1015), +("4+28+32",896), +("0+29+36",1044), +("0+29+37",1073), +("0+29+38",1102), +("0+29+39",1131), +("0+29+40",1160), +("4+28+33",924), +("4+28+34",952), +("0+29+41",1189), +("0+29+42",1218), +("4+28+35",980), +("0+29+43",1247), +("4+28+36",1008), +("0+29+44",1276), +("0+29+45",1305), +("0+29+46",1334), +("0+29+47",1363), +("0+29+48",1392), +("0+29+49",1421), +("4+28+37",1036), +("4+28+38",1064), +("4+28+39",1092), +("4+28+40",1120), +("4+28+41",1148), +("4+28+42",1176), +("4+28+43",1204), +("4+28+44",1232), +("4+28+45",1260), +("4+28+46",1288), +("4+28+47",1316), +("4+28+48",1344), +("4+28+49",1372), +("4+29+0",0), +("1+29+0",0), +("3+26+0",0), +("2+27+0",0), +("0+30+0",0), +("0+30+1",30), +("4+29+1",29), +("3+26+1",26), +("0+30+2",60), +("3+26+2",52), +("3+26+3",78), +("3+26+4",104), +("0+30+3",90), +("3+26+5",130), +("4+29+2",58), +("1+29+1",29), +("3+26+6",156), +("4+29+3",87), +("1+29+2",58), +("2+27+1",27), +("0+30+4",120), +("2+27+2",54), +("0+30+5",150), +("0+30+6",180), +("4+29+4",116), +("4+29+5",145), +("2+27+3",81), +("1+29+3",87), +("0+30+7",210), +("4+29+6",174), +("1+29+4",116), +("2+27+4",108), +("4+29+7",203), +("2+27+5",135), +("3+26+7",182), +("3+26+8",208), +("0+30+8",240), +("3+26+9",234), +("0+30+9",270), +("3+26+10",260), +("3+26+11",286), +("1+29+5",145), +("1+29+6",174), +("0+30+10",300), +("0+30+11",330), +("0+30+12",360), +("1+29+7",203), +("0+30+13",390), +("4+29+8",232), +("2+27+6",162), +("0+30+14",420), +("2+27+7",189), +("4+29+9",261), +("3+26+12",312), +("0+30+15",450), +("0+30+16",480), +("1+29+8",232), +("1+29+9",261), +("3+26+13",338), +("0+30+17",510), +("3+26+14",364), +("4+29+10",290), +("2+27+8",216), +("3+26+15",390), +("2+27+9",243), +("1+29+10",290), +("1+29+11",319), +("2+27+10",270), +("0+30+18",540), +("1+29+12",348), +("3+26+16",416), +("1+29+13",377), +("1+29+14",406), +("3+26+17",442), +("3+26+18",468), +("2+27+11",297), +("2+27+12",324), +("0+30+19",570), +("0+30+20",600), +("2+27+13",351), +("0+30+21",630), +("2+27+14",378), +("2+27+15",405), +("3+26+19",494), +("2+27+16",432), +("2+27+17",459), +("2+27+18",486), +("3+26+20",520), +("4+29+11",319), +("4+29+12",348), +("2+27+19",513), +("4+29+13",377), +("0+30+22",660), +("2+27+20",540), +("4+29+14",406), +("2+27+21",567), +("3+26+21",546), +("0+30+23",690), +("4+29+15",435), +("2+27+22",594), +("0+30+24",720), +("4+29+16",464), +("3+26+22",572), +("0+30+25",750), +("4+29+17",493), +("1+29+15",435), +("3+26+23",598), +("3+26+24",624), +("4+29+18",522), +("2+27+23",621), +("4+29+19",551), +("3+26+25",650), +("2+27+24",648), +("1+29+16",464), +("0+30+26",780), +("1+29+17",493), +("2+27+25",675), +("2+27+26",702), +("2+27+27",729), +("3+26+26",676), +("1+29+18",522), +("2+27+28",756), +("3+26+27",702), +("4+29+20",580), +("2+27+29",783), +("0+30+27",810), +("3+26+28",728), +("4+29+21",609), +("4+29+22",638), +("4+29+23",667), +("1+29+19",551), +("2+27+30",810), +("2+27+31",837), +("1+29+20",580), +("4+29+24",696), +("2+27+32",864), +("3+26+29",754), +("1+29+21",609), +("4+29+25",725), +("4+29+26",754), +("3+26+30",780), +("4+29+27",783), +("3+26+31",806), +("0+30+28",840), +("4+29+28",812), +("0+30+29",870), +("1+29+22",638), +("2+27+33",891), +("1+29+23",667), +("4+29+29",841), +("2+27+34",918), +("0+30+30",900), +("4+29+30",870), +("0+30+31",930), +("4+29+31",899), +("4+29+32",928), +("1+29+24",696), +("0+30+32",960), +("1+29+25",725), +("1+29+26",754), +("2+27+35",945), +("2+27+36",972), +("0+30+33",990), +("2+27+37",999), +("3+26+32",832), +("0+30+34",1020), +("2+27+38",1026), +("0+30+35",1050), +("1+29+27",783), +("1+29+28",812), +("3+26+33",858), +("3+26+34",884), +("0+30+36",1080), +("4+29+33",957), +("4+29+34",986), +("3+26+35",910), +("2+27+39",1053), +("0+30+37",1110), +("0+30+38",1140), +("1+29+29",841), +("4+29+35",1015), +("2+27+40",1080), +("2+27+41",1107), +("4+29+36",1044), +("2+27+42",1134), +("1+29+30",870), +("4+29+37",1073), +("2+27+43",1161), +("2+27+44",1188), +("0+30+39",1170), +("2+27+45",1215), +("1+29+31",899), +("0+30+40",1200), +("1+29+32",928), +("2+27+46",1242), +("0+30+41",1230), +("4+29+38",1102), +("2+27+47",1269), +("1+29+33",957), +("0+30+42",1260), +("3+26+36",936), +("2+27+48",1296), +("0+30+43",1290), +("2+27+49",1323), +("4+29+39",1131), +("4+29+40",1160), +("3+26+37",962), +("0+30+44",1320), +("4+29+41",1189), +("1+29+34",986), +("1+29+35",1015), +("4+29+42",1218), +("2+28+0",0), +("2+28+1",28), +("4+29+43",1247), +("2+28+2",56), +("4+29+44",1276), +("4+29+45",1305), +("2+28+3",84), +("4+29+46",1334), +("3+26+38",988), +("3+26+39",1014), +("1+29+36",1044), +("3+26+40",1040), +("0+30+45",1350), +("3+26+41",1066), +("4+29+47",1363), +("0+30+46",1380), +("4+29+48",1392), +("2+28+4",112), +("4+29+49",1421), +("2+28+5",140), +("2+28+6",168), +("2+28+7",196), +("1+29+37",1073), +("2+28+8",224), +("4+30+0",0), +("1+29+38",1102), +("1+29+39",1131), +("3+26+42",1092), +("3+26+43",1118), +("4+30+1",30), +("4+30+2",60), +("1+29+40",1160), +("1+29+41",1189), +("2+28+9",252), +("0+30+47",1410), +("1+29+42",1218), +("3+26+44",1144), +("2+28+10",280), +("1+29+43",1247), +("4+30+3",90), +("4+30+4",120), +("2+28+11",308), +("2+28+12",336), +("1+29+44",1276), +("1+29+45",1305), +("4+30+5",150), +("2+28+13",364), +("0+30+48",1440), +("2+28+14",392), +("0+30+49",1470), +("4+30+6",180), +("1+29+46",1334), +("4+30+7",210), +("4+30+8",240), +("2+28+15",420), +("3+26+45",1170), +("2+28+16",448), +("0+31+0",0), +("3+26+46",1196), +("4+30+9",270), +("4+30+10",300), +("0+31+1",31), +("3+26+47",1222), +("1+29+47",1363), +("1+29+48",1392), +("1+29+49",1421), +("0+31+2",62), +("0+31+3",93), +("3+26+48",1248), +("3+26+49",1274), +("0+31+4",124), +("0+31+5",155), +("0+31+6",186), +("2+28+17",476), +("0+31+7",217), +("2+28+18",504), +("0+31+8",248), +("2+28+19",532), +("4+30+11",330), +("0+31+9",279), +("2+28+20",560), +("1+30+0",0), +("4+30+12",360), +("2+28+21",588), +("4+30+13",390), +("0+31+10",310), +("3+27+0",0), +("2+28+22",616), +("3+27+1",27), +("0+31+11",341), +("4+30+14",420), +("4+30+15",450), +("1+30+1",30), +("2+28+23",644), +("0+31+12",372), +("2+28+24",672), +("4+30+16",480), +("4+30+17",510), +("3+27+2",54), +("0+31+13",403), +("2+28+25",700), +("1+30+2",60), +("0+31+14",434), +("0+31+15",465), +("1+30+3",90), +("0+31+16",496), +("4+30+18",540), +("0+31+17",527), +("1+30+4",120), +("2+28+26",728), +("2+28+27",756), +("3+27+3",81), +("1+30+5",150), +("2+28+28",784), +("4+30+19",570), +("1+30+6",180), +("0+31+18",558), +("4+30+20",600), +("2+28+29",812), +("0+31+19",589), +("4+30+21",630), +("0+31+20",620), +("1+30+7",210), +("0+31+21",651), +("4+30+22",660), +("0+31+22",682), +("2+28+30",840), +("4+30+23",690), +("0+31+23",713), +("1+30+8",240), +("0+31+24",744), +("4+30+24",720), +("0+31+25",775), +("2+28+31",868), +("2+28+32",896), +("4+30+25",750), +("2+28+33",924), +("0+31+26",806), +("2+28+34",952), +("4+30+26",780), +("0+31+27",837), +("4+30+27",810), +("0+31+28",868), +("2+28+35",980), +("3+27+4",108), +("1+30+9",270), +("3+27+5",135), +("3+27+6",162), +("2+28+36",1008), +("2+28+37",1036), +("2+28+38",1064), +("3+27+7",189), +("4+30+28",840), +("3+27+8",216), +("4+30+29",870), +("1+30+10",300), +("4+30+30",900), +("4+30+31",930), +("3+27+9",243), +("2+28+39",1092), +("4+30+32",960), +("0+31+29",899), +("1+30+11",330), +("1+30+12",360), +("0+31+30",930), +("2+28+40",1120), +("1+30+13",390), +("4+30+33",990), +("3+27+10",270), +("0+31+31",961), +("0+31+32",992), +("3+27+11",297), +("2+28+41",1148), +("3+27+12",324), +("2+28+42",1176), +("3+27+13",351), +("4+30+34",1020), +("0+31+33",1023), +("2+28+43",1204), +("3+27+14",378), +("4+30+35",1050), +("4+30+36",1080), +("2+28+44",1232), +("2+28+45",1260), +("2+28+46",1288), +("1+30+14",420), +("2+28+47",1316), +("2+28+48",1344), +("0+31+34",1054), +("4+30+37",1110), +("4+30+38",1140), +("2+28+49",1372), +("3+27+15",405), +("4+30+39",1170), +("4+30+40",1200), +("3+27+16",432), +("3+27+17",459), +("1+30+15",450), +("1+30+16",480), +("0+31+35",1085), +("0+31+36",1116), +("1+30+17",510), +("4+30+41",1230), +("0+31+37",1147), +("0+31+38",1178), +("0+31+39",1209), +("3+27+18",486), +("4+30+42",1260), +("3+27+19",513), +("3+27+20",540), +("4+30+43",1290), +("4+30+44",1320), +("4+30+45",1350), +("0+31+40",1240), +("0+31+41",1271), +("1+30+18",540), +("0+31+42",1302), +("4+30+46",1380), +("4+30+47",1410), +("1+30+19",570), +("4+30+48",1440), +("1+30+20",600), +("4+30+49",1470), +("2+29+0",0), +("1+30+21",630), +("1+30+22",660), +("3+27+21",567), +("3+27+22",594), +("1+30+23",690), +("1+30+24",720), +("0+31+43",1333), +("0+31+44",1364), +("0+31+45",1395), +("2+29+1",29), +("0+31+46",1426), +("1+30+25",750), +("3+27+23",621), +("4+31+0",0), +("3+27+24",648), +("4+31+1",31), +("4+31+2",62), +("0+31+47",1457), +("1+30+26",780), +("1+30+27",810), +("1+30+28",840), +("1+30+29",870), +("0+31+48",1488), +("4+31+3",93), +("1+30+30",900), +("1+30+31",930), +("3+27+25",675), +("4+31+4",124), +("1+30+32",960), +("0+31+49",1519), +("2+29+2",58), +("2+29+3",87), +("3+27+26",702), +("1+30+33",990), +("4+31+5",155), +("2+29+4",116), +("4+31+6",186), +("3+27+27",729), +("0+32+0",0), +("3+27+28",756), +("4+31+7",217), +("2+29+5",145), +("2+29+6",174), +("2+29+7",203), +("2+29+8",232), +("2+29+9",261), +("0+32+1",32), +("0+32+2",64), +("0+32+3",96), +("2+29+10",290), +("1+30+34",1020), +("0+32+4",128), +("0+32+5",160), +("3+27+29",783), +("4+31+8",248), +("4+31+9",279), +("3+27+30",810), +("4+31+10",310), +("1+30+35",1050), +("3+27+31",837), +("3+27+32",864), +("4+31+11",341), +("1+30+36",1080), +("1+30+37",1110), +("0+32+6",192), +("1+30+38",1140), +("1+30+39",1170), +("4+31+12",372), +("0+32+7",224), +("3+27+33",891), +("1+30+40",1200), +("2+29+11",319), +("3+27+34",918), +("4+31+13",403), +("4+31+14",434), +("4+31+15",465), +("1+30+41",1230), +("0+32+8",256), +("2+29+12",348), +("3+27+35",945), +("4+31+16",496), +("0+32+9",288), +("3+27+36",972), +("4+31+17",527), +("3+27+37",999), +("2+29+13",377), +("2+29+14",406), +("2+29+15",435), +("4+31+18",558), +("4+31+19",589), +("0+32+10",320), +("2+29+16",464), +("4+31+20",620), +("1+30+42",1260), +("1+30+43",1290), +("2+29+17",493), +("2+29+18",522), +("3+27+38",1026), +("0+32+11",352), +("0+32+12",384), +("1+30+44",1320), +("3+27+39",1053), +("0+32+13",416), +("4+31+21",651), +("3+27+40",1080), +("2+29+19",551), +("2+29+20",580), +("4+31+22",682), +("1+30+45",1350), +("3+27+41",1107), +("0+32+14",448), +("1+30+46",1380), +("1+30+47",1410), +("1+30+48",1440), +("3+27+42",1134), +("4+31+23",713), +("0+32+15",480), +("1+30+49",1470), +("0+32+16",512), +("3+27+43",1161), +("3+27+44",1188), +("2+29+21",609), +("2+29+22",638), +("3+27+45",1215), +("4+31+24",744), +("0+32+17",544), +("0+32+18",576), +("3+27+46",1242), +("3+27+47",1269), +("2+29+23",667), +("1+31+0",0), +("1+31+1",31), +("3+27+48",1296), +("3+27+49",1323), +("4+31+25",775), +("0+32+19",608), +("4+31+26",806), +("4+31+27",837), +("2+29+24",696), +("4+31+28",868), +("0+32+20",640), +("2+29+25",725), +("3+28+0",0), +("2+29+26",754), +("0+32+21",672), +("2+29+27",783), +("3+28+1",28), +("1+31+2",62), +("1+31+3",93), +("1+31+4",124), +("4+31+29",899), +("1+31+5",155), +("0+32+22",704), +("1+31+6",186), +("4+31+30",930), +("4+31+31",961), +("1+31+7",217), +("2+29+28",812), +("3+28+2",56), +("2+29+29",841), +("3+28+3",84), +("4+31+32",992), +("4+31+33",1023), +("0+32+23",736), +("4+31+34",1054), +("1+31+8",248), +("1+31+9",279), +("3+28+4",112), +("4+31+35",1085), +("2+29+30",870), +("4+31+36",1116), +("1+31+10",310), +("1+31+11",341), +("0+32+24",768), +("2+29+31",899), +("3+28+5",140), +("3+28+6",168), +("0+32+25",800), +("0+32+26",832), +("1+31+12",372), +("4+31+37",1147), +("4+31+38",1178), +("3+28+7",196), +("1+31+13",403), +("2+29+32",928), +("1+31+14",434), +("3+28+8",224), +("2+29+33",957), +("4+31+39",1209), +("1+31+15",465), +("3+28+9",252), +("2+29+34",986), +("4+31+40",1240), +("4+31+41",1271), +("3+28+10",280), +("3+28+11",308), +("2+29+35",1015), +("3+28+12",336), +("3+28+13",364), +("3+28+14",392), +("0+32+27",864), +("4+31+42",1302), +("0+32+28",896), +("0+32+29",928), +("2+29+36",1044), +("0+32+30",960), +("4+31+43",1333), +("4+31+44",1364), +("3+28+15",420), +("3+28+16",448), +("3+28+17",476), +("4+31+45",1395), +("4+31+46",1426), +("0+32+31",992), +("4+31+47",1457), +("3+28+18",504), +("3+28+19",532), +("1+31+16",496), +("1+31+17",527), +("4+31+48",1488), +("2+29+37",1073), +("3+28+20",560), +("0+32+32",1024), +("1+31+18",558), +("1+31+19",589), +("0+32+33",1056), +("0+32+34",1088), +("4+31+49",1519), +("0+32+35",1120), +("0+32+36",1152), +("1+31+20",620), +("3+28+21",588), +("1+31+21",651), +("3+28+22",616), +("0+32+37",1184), +("4+32+0",0), +("4+32+1",32), +("2+29+38",1102), +("0+32+38",1216), +("1+31+22",682), +("4+32+2",64), +("0+32+39",1248), +("0+32+40",1280), +("3+28+23",644), +("2+29+39",1131), +("2+29+40",1160), +("2+29+41",1189), +("0+32+41",1312), +("0+32+42",1344), +("0+32+43",1376), +("3+28+24",672), +("0+32+44",1408), +("2+29+42",1218), +("0+32+45",1440), +("3+28+25",700), +("4+32+3",96), +("3+28+26",728), +("3+28+27",756), +("4+32+4",128), +("4+32+5",160), +("4+32+6",192), +("3+28+28",784), +("3+28+29",812), +("2+29+43",1247), +("2+29+44",1276), +("4+32+7",224), +("1+31+23",713), +("3+28+30",840), +("2+29+45",1305), +("2+29+46",1334), +("3+28+31",868), +("3+28+32",896), +("3+28+33",924), +("2+29+47",1363), +("2+29+48",1392), +("2+29+49",1421), +("4+32+8",256), +("4+32+9",288), +("3+28+34",952), +("3+28+35",980), +("1+31+24",744), +("3+28+36",1008), +("1+31+25",775), +("3+28+37",1036), +("1+31+26",806), +("1+31+27",837), +("4+32+10",320), +("3+28+38",1064), +("1+31+28",868), +("0+32+46",1472), +("2+30+0",0), +("0+32+47",1504), +("0+32+48",1536), +("0+32+49",1568), +("1+31+29",899), +("3+28+39",1092), +("1+31+30",930), +("2+30+1",30), +("1+31+31",961), +("1+31+32",992), +("1+31+33",1023), +("3+28+40",1120), +("4+32+11",352), +("0+33+0",0), +("2+30+2",60), +("0+33+1",33), +("4+32+12",384), +("0+33+2",66), +("0+33+3",99), +("1+31+34",1054), +("2+30+3",90), +("1+31+35",1085), +("2+30+4",120), +("1+31+36",1116), +("0+33+4",132), +("3+28+41",1148), +("4+32+13",416), +("1+31+37",1147), +("1+31+38",1178), +("1+31+39",1209), +("1+31+40",1240), +("2+30+5",150), +("1+31+41",1271), +("1+31+42",1302), +("3+28+42",1176), +("1+31+43",1333), +("1+31+44",1364), +("0+33+5",165), +("2+30+6",180), +("4+32+14",448), +("3+28+43",1204), +("1+31+45",1395), +("2+30+7",210), +("3+28+44",1232), +("4+32+15",480), +("4+32+16",512), +("1+31+46",1426), +("2+30+8",240), +("4+32+17",544), +("1+31+47",1457), +("1+31+48",1488), +("3+28+45",1260), +("3+28+46",1288), +("0+33+6",198), +("3+28+47",1316), +("2+30+9",270), +("0+33+7",231), +("0+33+8",264), +("3+28+48",1344), +("1+31+49",1519), +("4+32+18",576), +("2+30+10",300), +("4+32+19",608), +("3+28+49",1372), +("2+30+11",330), +("2+30+12",360), +("4+32+20",640), +("0+33+9",297), +("0+33+10",330), +("4+32+21",672), +("4+32+22",704), +("2+30+13",390), +("4+32+23",736), +("0+33+11",363), +("2+30+14",420), +("4+32+24",768), +("2+30+15",450), +("1+32+0",0), +("0+33+12",396), +("1+32+1",32), +("4+32+25",800), +("2+30+16",480), +("3+29+0",0), +("1+32+2",64), +("0+33+13",429), +("0+33+14",462), +("1+32+3",96), +("3+29+1",29), +("3+29+2",58), +("2+30+17",510), +("2+30+18",540), +("1+32+4",128), +("3+29+3",87), +("3+29+4",116), +("3+29+5",145), +("3+29+6",174), +("3+29+7",203), +("1+32+5",160), +("0+33+15",495), +("2+30+19",570), +("4+32+26",832), +("0+33+16",528), +("2+30+20",600), +("4+32+27",864), +("3+29+8",232), +("0+33+17",561), +("1+32+6",192), +("0+33+18",594), +("3+29+9",261), +("3+29+10",290), +("1+32+7",224), +("0+33+19",627), +("2+30+21",630), +("4+32+28",896), +("2+30+22",660), +("3+29+11",319), +("2+30+23",690), +("3+29+12",348), +("2+30+24",720), +("4+32+29",928), +("2+30+25",750), +("1+32+8",256), +("2+30+26",780), +("3+29+13",377), +("0+33+20",660), +("4+32+30",960), +("2+30+27",810), +("0+33+21",693), +("1+32+9",288), +("2+30+28",840), +("4+32+31",992), +("0+33+22",726), +("2+30+29",870), +("4+32+32",1024), +("0+33+23",759), +("4+32+33",1056), +("4+32+34",1088), +("2+30+30",900), +("4+32+35",1120), +("0+33+24",792), +("4+32+36",1152), +("3+29+14",406), +("0+33+25",825), +("4+32+37",1184), +("1+32+10",320), +("0+33+26",858), +("4+32+38",1216), +("3+29+15",435), +("3+29+16",464), +("1+32+11",352), +("4+32+39",1248), +("1+32+12",384), +("1+32+13",416), +("1+32+14",448), +("3+29+17",493), +("3+29+18",522), +("4+32+40",1280), +("4+32+41",1312), +("3+29+19",551), +("3+29+20",580), +("2+30+31",930), +("2+30+32",960), +("3+29+21",609), +("3+29+22",638), +("3+29+23",667), +("0+33+27",891), +("3+29+24",696), +("0+33+28",924), +("1+32+15",480), +("2+30+33",990), +("3+29+25",725), +("0+33+29",957), +("3+29+26",754), +("0+33+30",990), +("3+29+27",783), +("3+29+28",812), +("1+32+16",512), +("4+32+42",1344), +("0+33+31",1023), +("4+32+43",1376), +("2+30+34",1020), +("0+33+32",1056), +("3+29+29",841), +("0+33+33",1089), +("2+30+35",1050), +("2+30+36",1080), +("4+32+44",1408), +("2+30+37",1110), +("1+32+17",544), +("4+32+45",1440), +("0+33+34",1122), +("2+30+38",1140), +("0+33+35",1155), +("3+29+30",870), +("4+32+46",1472), +("2+30+39",1170), +("0+33+36",1188), +("4+32+47",1504), +("2+30+40",1200), +("4+32+48",1536), +("2+30+41",1230), +("1+32+18",576), +("0+33+37",1221), +("4+32+49",1568), +("0+33+38",1254), +("1+32+19",608), +("0+33+39",1287), +("1+32+20",640), +("1+32+21",672), +("2+30+42",1260), +("2+30+43",1290), +("1+32+22",704), +("2+30+44",1320), +("3+29+31",899), +("1+32+23",736), +("3+29+32",928), +("3+29+33",957), +("1+32+24",768), +("0+33+40",1320), +("1+32+25",800), +("0+33+41",1353), +("2+30+45",1350), +("3+29+34",986), +("4+33+0",0), +("1+32+26",832), +("1+32+27",864), +("3+29+35",1015), +("0+33+42",1386), +("3+29+36",1044), +("0+33+43",1419), +("2+30+46",1380), +("0+33+44",1452), +("1+32+28",896), +("4+33+1",33), +("0+33+45",1485), +("1+32+29",928), +("2+30+47",1410), +("0+33+46",1518), +("1+32+30",960), +("0+33+47",1551), +("2+30+48",1440), +("0+33+48",1584), +("2+30+49",1470), +("0+33+49",1617), +("4+33+2",66), +("3+29+37",1073), +("1+32+31",992), +("3+29+38",1102), +("3+29+39",1131), +("4+33+3",99), +("1+32+32",1024), +("1+32+33",1056), +("4+33+4",132), +("4+33+5",165), +("1+32+34",1088), +("0+34+0",0), +("1+32+35",1120), +("4+33+6",198), +("0+34+1",34), +("1+32+36",1152), +("3+29+40",1160), +("1+32+37",1184), +("1+32+38",1216), +("1+32+39",1248), +("3+29+41",1189), +("0+34+2",68), +("4+33+7",231), +("1+32+40",1280), +("2+31+0",0), +("0+34+3",102), +("1+32+41",1312), +("4+33+8",264), +("2+31+1",31), +("3+29+42",1218), +("4+33+9",297), +("0+34+4",136), +("4+33+10",330), +("0+34+5",170), +("2+31+2",62), +("0+34+6",204), +("4+33+11",363), +("2+31+3",93), +("1+32+42",1344), +("0+34+7",238), +("2+31+4",124), +("1+32+43",1376), +("2+31+5",155), +("3+29+43",1247), +("4+33+12",396), +("2+31+6",186), +("2+31+7",217), +("0+34+8",272), +("1+32+44",1408), +("4+33+13",429), +("3+29+44",1276), +("2+31+8",248), +("4+33+14",462), +("4+33+15",495), +("3+29+45",1305), +("0+34+9",306), +("4+33+16",528), +("4+33+17",561), +("3+29+46",1334), +("1+32+45",1440), +("0+34+10",340), +("1+32+46",1472), +("0+34+11",374), +("1+32+47",1504), +("0+34+12",408), +("3+29+47",1363), +("0+34+13",442), +("1+32+48",1536), +("3+29+48",1392), +("0+34+14",476), +("4+33+18",594), +("3+29+49",1421), +("4+33+19",627), +("1+32+49",1568), +("4+33+20",660), +("2+31+9",279), +("4+33+21",693), +("2+31+10",310), +("4+33+22",726), +("2+31+11",341), +("0+34+15",510), +("2+31+12",372), +("0+34+16",544), +("2+31+13",403), +("4+33+23",759), +("4+33+24",792), +("3+30+0",0), +("4+33+25",825), +("0+34+17",578), +("3+30+1",30), +("2+31+14",434), +("4+33+26",858), +("3+30+2",60), +("4+33+27",891), +("4+33+28",924), +("3+30+3",90), +("1+33+0",0), +("0+34+18",612), +("3+30+4",120), +("1+33+1",33), +("1+33+2",66), +("4+33+29",957), +("4+33+30",990), +("4+33+31",1023), +("4+33+32",1056), +("1+33+3",99), +("1+33+4",132), +("4+33+33",1089), +("1+33+5",165), +("3+30+5",150), +("1+33+6",198), +("1+33+7",231), +("1+33+8",264), +("2+31+15",465), +("2+31+16",496), +("3+30+6",180), +("2+31+17",527), +("2+31+18",558), +("2+31+19",589), +("1+33+9",297), +("1+33+10",330), +("3+30+7",210), +("3+30+8",240), +("0+34+19",646), +("1+33+11",363), +("4+33+34",1122), +("0+34+20",680), +("1+33+12",396), +("1+33+13",429), +("2+31+20",620), +("3+30+9",270), +("2+31+21",651), +("1+33+14",462), +("1+33+15",495), +("3+30+10",300), +("3+30+11",330), +("0+34+21",714), +("1+33+16",528), +("0+34+22",748), +("1+33+17",561), +("2+31+22",682), +("3+30+12",360), +("0+34+23",782), +("2+31+23",713), +("3+30+13",390), +("0+34+24",816), +("4+33+35",1155), +("2+31+24",744), +("4+33+36",1188), +("4+33+37",1221), +("4+33+38",1254), +("1+33+18",594), +("1+33+19",627), +("1+33+20",660), +("1+33+21",693), +("0+34+25",850), +("0+34+26",884), +("1+33+22",726), +("4+33+39",1287), +("4+33+40",1320), +("3+30+14",420), +("2+31+25",775), +("0+34+27",918), +("0+34+28",952), +("3+30+15",450), +("4+33+41",1353), +("3+30+16",480), +("3+30+17",510), +("2+31+26",806), +("0+34+29",986), +("1+33+23",759), +("1+33+24",792), +("4+33+42",1386), +("1+33+25",825), +("1+33+26",858), +("0+34+30",1020), +("0+34+31",1054), +("2+31+27",837), +("2+31+28",868), +("1+33+27",891), +("3+30+18",540), +("4+33+43",1419), +("1+33+28",924), +("1+33+29",957), +("2+31+29",899), +("3+30+19",570), +("3+30+20",600), +("3+30+21",630), +("0+34+32",1088), +("0+34+33",1122), +("0+34+34",1156), +("1+33+30",990), +("4+33+44",1452), +("4+33+45",1485), +("0+34+35",1190), +("2+31+30",930), +("1+33+31",1023), +("0+34+36",1224), +("1+33+32",1056), +("1+33+33",1089), +("3+30+22",660), +("2+31+31",961), +("2+31+32",992), +("4+33+46",1518), +("2+31+33",1023), +("3+30+23",690), +("1+33+34",1122), +("1+33+35",1155), +("3+30+24",720), +("3+30+25",750), +("1+33+36",1188), +("1+33+37",1221), +("2+31+34",1054), +("0+34+37",1258), +("0+34+38",1292), +("4+33+47",1551), +("3+30+26",780), +("1+33+38",1254), +("2+31+35",1085), +("1+33+39",1287), +("0+34+39",1326), +("2+31+36",1116), +("4+33+48",1584), +("2+31+37",1147), +("4+33+49",1617), +("0+34+40",1360), +("1+33+40",1320), +("0+34+41",1394), +("1+33+41",1353), +("0+34+42",1428), +("2+31+38",1178), +("0+34+43",1462), +("4+34+0",0), +("2+31+39",1209), +("0+34+44",1496), +("4+34+1",34), +("2+31+40",1240), +("4+34+2",68), +("2+31+41",1271), +("1+33+42",1386), +("3+30+27",810), +("0+34+45",1530), +("1+33+43",1419), +("4+34+3",102), +("3+30+28",840), +("4+34+4",136), +("3+30+29",870), +("4+34+5",170), +("3+30+30",900), +("1+33+44",1452), +("2+31+42",1302), +("0+34+46",1564), +("4+34+6",204), +("0+34+47",1598), +("0+34+48",1632), +("2+31+43",1333), +("3+30+31",930), +("0+34+49",1666), +("3+30+32",960), +("2+31+44",1364), +("2+31+45",1395), +("1+33+45",1485), +("4+34+7",238), +("1+33+46",1518), +("0+35+0",0), +("4+34+8",272), +("1+33+47",1551), +("2+31+46",1426), +("0+35+1",35), +("3+30+33",990), +("4+34+9",306), +("1+33+48",1584), +("3+30+34",1020), +("2+31+47",1457), +("1+33+49",1617), +("4+34+10",340), +("0+35+2",70), +("2+31+48",1488), +("4+34+11",374), +("2+31+49",1519), +("0+35+3",105), +("1+34+0",0), +("4+34+12",408), +("4+34+13",442), +("3+30+35",1050), +("4+34+14",476), +("0+35+4",140), +("4+34+15",510), +("0+35+5",175), +("0+35+6",210), +("3+30+36",1080), +("4+34+16",544), +("4+34+17",578), +("1+34+1",34), +("3+30+37",1110), +("1+34+2",68), +("0+35+7",245), +("0+35+8",280), +("1+34+3",102), +("0+35+9",315), +("1+34+4",136), +("4+34+18",612), +("0+35+10",350), +("3+30+38",1140), +("4+34+19",646), +("2+32+0",0), +("3+30+39",1170), +("3+30+40",1200), +("1+34+5",170), +("3+30+41",1230), +("4+34+20",680), +("2+32+1",32), +("3+30+42",1260), +("4+34+21",714), +("1+34+6",204), +("4+34+22",748), +("3+30+43",1290), +("1+34+7",238), +("0+35+11",385), +("3+30+44",1320), +("4+34+23",782), +("4+34+24",816), +("0+35+12",420), +("1+34+8",272), +("2+32+2",64), +("3+30+45",1350), +("0+35+13",455), +("2+32+3",96), +("0+35+14",490), +("2+32+4",128), +("2+32+5",160), +("3+30+46",1380), +("2+32+6",192), +("0+35+15",525), +("2+32+7",224), +("1+34+9",306), +("0+35+16",560), +("1+34+10",340), +("2+32+8",256), +("2+32+9",288), +("2+32+10",320), +("1+34+11",374), +("1+34+12",408), +("0+35+17",595), +("3+30+47",1410), +("0+35+18",630), +("2+32+11",352), +("2+32+12",384), +("2+32+13",416), +("4+34+25",850), +("4+34+26",884), +("1+34+13",442), +("2+32+14",448), +("4+34+27",918), +("1+34+14",476), +("4+34+28",952), +("2+32+15",480), +("0+35+19",665), +("4+34+29",986), +("4+34+30",1020), +("2+32+16",512), +("4+34+31",1054), +("3+30+48",1440), +("2+32+17",544), +("0+35+20",700), +("4+34+32",1088), +("2+32+18",576), +("4+34+33",1122), +("3+30+49",1470), +("2+32+19",608), +("4+34+34",1156), +("0+35+21",735), +("4+34+35",1190), +("0+35+22",770), +("2+32+20",640), +("0+35+23",805), +("2+32+21",672), +("4+34+36",1224), +("0+35+24",840), +("4+34+37",1258), +("0+35+25",875), +("4+34+38",1292), +("3+31+0",0), +("0+35+26",910), +("2+32+22",704), +("4+34+39",1326), +("3+31+1",31), +("0+35+27",945), +("2+32+23",736), +("4+34+40",1360), +("0+35+28",980), +("4+34+41",1394), +("1+34+15",510), +("3+31+2",62), +("1+34+16",544), +("3+31+3",93), +("0+35+29",1015), +("1+34+17",578), +("4+34+42",1428), +("2+32+24",768), +("1+34+18",612), +("4+34+43",1462), +("3+31+4",124), +("3+31+5",155), +("3+31+6",186), +("0+35+30",1050), +("3+31+7",217), +("2+32+25",800), +("3+31+8",248), +("0+35+31",1085), +("0+35+32",1120), +("3+31+9",279), +("1+34+19",646), +("0+35+33",1155), +("1+34+20",680), +("0+35+34",1190), +("3+31+10",310), +("1+34+21",714), +("3+31+11",341), +("4+34+44",1496), +("1+34+22",748), +("3+31+12",372), +("3+31+13",403), +("1+34+23",782), +("4+34+45",1530), +("0+35+35",1225), +("0+35+36",1260), +("1+34+24",816), +("3+31+14",434), +("3+31+15",465), +("2+32+26",832), +("2+32+27",864), +("0+35+37",1295), +("3+31+16",496), +("3+31+17",527), +("1+34+25",850), +("2+32+28",896), +("2+32+29",928), +("4+34+46",1564), +("3+31+18",558), +("0+35+38",1330), +("0+35+39",1365), +("4+34+47",1598), +("2+32+30",960), +("4+34+48",1632), +("3+31+19",589), +("4+34+49",1666), +("2+32+31",992), +("1+34+26",884), +("2+32+32",1024), +("3+31+20",620), +("2+32+33",1056), +("1+34+27",918), +("2+32+34",1088), +("0+35+40",1400), +("1+34+28",952), +("1+34+29",986), +("1+34+30",1020), +("0+35+41",1435), +("3+31+21",651), +("1+34+31",1054), +("0+35+42",1470), +("4+35+0",0), +("2+32+35",1120), +("1+34+32",1088), +("0+35+43",1505), +("3+31+22",682), +("1+34+33",1122), +("4+35+1",35), +("2+32+36",1152), +("3+31+23",713), +("2+32+37",1184), +("4+35+2",70), +("1+34+34",1156), +("0+35+44",1540), +("3+31+24",744), +("1+34+35",1190), +("1+34+36",1224), +("0+35+45",1575), +("2+32+38",1216), +("4+35+3",105), +("0+35+46",1610), +("0+35+47",1645), +("3+31+25",775), +("2+32+39",1248), +("4+35+4",140), +("0+35+48",1680), +("4+35+5",175), +("4+35+6",210), +("4+35+7",245), +("2+32+40",1280), +("1+34+37",1258), +("3+31+26",806), +("3+31+27",837), +("2+32+41",1312), +("2+32+42",1344), +("1+34+38",1292), +("1+34+39",1326), +("1+34+40",1360), +("4+35+8",280), +("4+35+9",315), +("3+31+28",868), +("0+35+49",1715), +("4+35+10",350), +("3+31+29",899), +("2+32+43",1376), +("3+31+30",930), +("1+34+41",1394), +("2+32+44",1408), +("3+31+31",961), +("4+35+11",385), +("0+36+0",0), +("2+32+45",1440), +("3+31+32",992), +("3+31+33",1023), +("4+35+12",420), +("1+34+42",1428), +("4+35+13",455), +("4+35+14",490), +("3+31+34",1054), +("2+32+46",1472), +("4+35+15",525), +("0+36+1",36), +("1+34+43",1462), +("3+31+35",1085), +("0+36+2",72), +("3+31+36",1116), +("3+31+37",1147), +("2+32+47",1504), +("2+32+48",1536), +("1+34+44",1496), +("0+36+3",108), +("4+35+16",560), +("2+32+49",1568), +("1+34+45",1530), +("1+34+46",1564), +("4+35+17",595), +("3+31+38",1178), +("1+34+47",1598), +("3+31+39",1209), +("2+33+0",0), +("1+34+48",1632), +("4+35+18",630), +("0+36+4",144), +("4+35+19",665), +("1+34+49",1666), +("3+31+40",1240), +("0+36+5",180), +("4+35+20",700), +("0+36+6",216), +("3+31+41",1271), +("0+36+7",252), +("2+33+1",33), +("2+33+2",66), +("3+31+42",1302), +("3+31+43",1333), +("0+36+8",288), +("0+36+9",324), +("3+31+44",1364), +("4+35+21",735), +("0+36+10",360), +("2+33+3",99), +("4+35+22",770), +("4+35+23",805), +("4+35+24",840), +("4+35+25",875), +("3+31+45",1395), +("0+36+11",396), +("2+33+4",132), +("2+33+5",165), +("0+36+12",432), +("2+33+6",198), +("3+31+46",1426), +("2+33+7",231), +("3+31+47",1457), +("4+35+26",910), +("2+33+8",264), +("4+35+27",945), +("2+33+9",297), +("2+33+10",330), +("4+35+28",980), +("2+33+11",363), +("3+31+48",1488), +("0+36+13",468), +("4+35+29",1015), +("3+31+49",1519), +("4+35+30",1050), +("4+35+31",1085), +("0+36+14",504), +("2+33+12",396), +("4+35+32",1120), +("0+36+15",540), +("2+33+13",429), +("2+33+14",462), +("0+36+16",576), +("0+36+17",612), +("0+36+18",648), +("4+35+33",1155), +("0+36+19",684), +("2+33+15",495), +("4+35+34",1190), +("2+33+16",528), +("2+33+17",561), +("4+35+35",1225), +("4+35+36",1260), +("0+36+20",720), +("4+35+37",1295), +("0+36+21",756), +("0+36+22",792), +("2+33+18",594), +("2+33+19",627), +("2+33+20",660), +("2+33+21",693), +("4+35+38",1330), +("4+35+39",1365), +("4+35+40",1400), +("0+36+23",828), +("4+35+41",1435), +("2+33+22",726), +("2+33+23",759), +("0+36+24",864), +("2+33+24",792), +("4+35+42",1470), +("4+35+43",1505), +("0+36+25",900), +("2+33+25",825), +("2+33+26",858), +("0+36+26",936), +("0+36+27",972), +("4+35+44",1540), +("4+35+45",1575), +("4+35+46",1610), +("2+33+27",891), +("4+35+47",1645), +("0+36+28",1008), +("2+33+28",924), +("4+35+48",1680), +("4+35+49",1715), +("2+33+29",957), +("2+33+30",990), +("2+33+31",1023), +("0+36+29",1044), +("0+36+30",1080), +("0+36+31",1116), +("0+36+32",1152), +("0+36+33",1188), +("2+33+32",1056), +("2+33+33",1089), +("0+36+34",1224), +("0+36+35",1260), +("2+33+34",1122), +("2+33+35",1155), +("2+33+36",1188), +("2+33+37",1221), +("0+36+36",1296), +("0+36+37",1332), +("2+33+38",1254), +("0+36+38",1368), +("2+33+39",1287), +("0+36+39",1404), +("2+33+40",1320), +("2+33+41",1353), +("2+33+42",1386), +("0+36+40",1440), +("2+33+43",1419), +("2+33+44",1452), +("2+33+45",1485), +("2+33+46",1518), +("2+33+47",1551), +("2+33+48",1584), +("2+33+49",1617), +("0+36+41",1476), +("0+36+42",1512), +("0+36+43",1548), +("0+36+44",1584), +("0+36+45",1620), +("0+36+46",1656), +("0+36+47",1692), +("0+36+48",1728), +("0+36+49",1764), +("0+37+0",0), +("3+32+0",0), +("4+36+0",0), +("2+34+0",0), +("1+35+0",0), +("1+35+1",35), +("2+34+1",34), +("0+37+1",37), +("2+34+2",68), +("4+36+1",36), +("3+32+1",32), +("4+36+2",72), +("0+37+2",74), +("4+36+3",108), +("1+35+2",70), +("4+36+4",144), +("1+35+3",105), +("4+36+5",180), +("3+32+2",64), +("1+35+4",140), +("4+36+6",216), +("0+37+3",111), +("1+35+5",175), +("4+36+7",252), +("0+37+4",148), +("2+34+3",102), +("1+35+6",210), +("3+32+3",96), +("1+35+7",245), +("0+37+5",185), +("1+35+8",280), +("0+37+6",222), +("3+32+4",128), +("4+36+8",288), +("4+36+9",324), +("0+37+7",259), +("4+36+10",360), +("3+32+5",160), +("1+35+9",315), +("4+36+11",396), +("0+37+8",296), +("2+34+4",136), +("4+36+12",432), +("0+37+9",333), +("1+35+10",350), +("4+36+13",468), +("3+32+6",192), +("1+35+11",385), +("3+32+7",224), +("1+35+12",420), +("0+37+10",370), +("0+37+11",407), +("2+34+5",170), +("3+32+8",256), +("1+35+13",455), +("0+37+12",444), +("1+35+14",490), +("4+36+14",504), +("0+37+13",481), +("1+35+15",525), +("2+34+6",204), +("0+37+14",518), +("0+37+15",555), +("0+37+16",592), +("3+32+9",288), +("4+36+15",540), +("2+34+7",238), +("3+32+10",320), +("3+32+11",352), +("0+37+17",629), +("2+34+8",272), +("1+35+16",560), +("3+32+12",384), +("1+35+17",595), +("3+32+13",416), +("0+37+18",666), +("3+32+14",448), +("2+34+9",306), +("3+32+15",480), +("0+37+19",703), +("2+34+10",340), +("4+36+16",576), +("0+37+20",740), +("1+35+18",630), +("0+37+21",777), +("1+35+19",665), +("2+34+11",374), +("4+36+17",612), +("1+35+20",700), +("3+32+16",512), +("1+35+21",735), +("4+36+18",648), +("3+32+17",544), +("1+35+22",770), +("3+32+18",576), +("0+37+22",814), +("2+34+12",408), +("0+37+23",851), +("1+35+23",805), +("1+35+24",840), +("3+32+19",608), +("2+34+13",442), +("3+32+20",640), +("0+37+24",888), +("1+35+25",875), +("4+36+19",684), +("3+32+21",672), +("2+34+14",476), +("3+32+22",704), +("4+36+20",720), +("3+32+23",736), +("1+35+26",910), +("1+35+27",945), +("3+32+24",768), +("4+36+21",756), +("0+37+25",925), +("4+36+22",792), +("0+37+26",962), +("3+32+25",800), +("2+34+15",510), +("1+35+28",980), +("3+32+26",832), +("4+36+23",828), +("4+36+24",864), +("0+37+27",999), +("3+32+27",864), +("2+34+16",544), +("4+36+25",900), +("1+35+29",1015), +("0+37+28",1036), +("2+34+17",578), +("3+32+28",896), +("1+35+30",1050), +("4+36+26",936), +("0+37+29",1073), +("2+34+18",612), +("1+35+31",1085), +("3+32+29",928), +("4+36+27",972), +("4+36+28",1008), +("1+35+32",1120), +("2+34+19",646), +("4+36+29",1044), +("0+37+30",1110), +("1+35+33",1155), +("2+34+20",680), +("0+37+31",1147), +("4+36+30",1080), +("4+36+31",1116), +("3+32+30",960), +("1+35+34",1190), +("3+32+31",992), +("0+37+32",1184), +("2+34+21",714), +("0+37+33",1221), +("1+35+35",1225), +("3+32+32",1024), +("3+32+33",1056), +("1+35+36",1260), +("3+32+34",1088), +("1+35+37",1295), +("0+37+34",1258), +("4+36+32",1152), +("3+32+35",1120), +("3+32+36",1152), +("1+35+38",1330), +("4+36+33",1188), +("1+35+39",1365), +("0+37+35",1295), +("3+32+37",1184), +("0+37+36",1332), +("1+35+40",1400), +("2+34+22",748), +("3+32+38",1216), +("4+36+34",1224), +("3+32+39",1248), +("0+37+37",1369), +("2+34+23",782), +("0+37+38",1406), +("4+36+35",1260), +("0+37+39",1443), +("2+34+24",816), +("4+36+36",1296), +("1+35+41",1435), +("3+32+40",1280), +("4+36+37",1332), +("0+37+40",1480), +("3+32+41",1312), +("4+36+38",1368), +("0+37+41",1517), +("2+34+25",850), +("4+36+39",1404), +("2+34+26",884), +("1+35+42",1470), +("2+34+27",918), +("4+36+40",1440), +("1+35+43",1505), +("4+36+41",1476), +("4+36+42",1512), +("1+35+44",1540), +("4+36+43",1548), +("0+37+42",1554), +("2+34+28",952), +("0+37+43",1591), +("1+35+45",1575), +("3+32+42",1344), +("2+34+29",986), +("4+36+44",1584), +("3+32+43",1376), +("4+36+45",1620), +("0+37+44",1628), +("3+32+44",1408), +("4+36+46",1656), +("1+35+46",1610), +("3+32+45",1440), +("0+37+45",1665), +("4+36+47",1692), +("1+35+47",1645), +("4+36+48",1728), +("2+34+30",1020), +("1+35+48",1680), +("4+36+49",1764), +("0+37+46",1702), +("3+32+46",1472), +("2+34+31",1054), +("0+37+47",1739), +("1+35+49",1715), +("2+34+32",1088), +("3+32+47",1504), +("2+34+33",1122), +("3+32+48",1536), +("4+37+0",0), +("4+37+1",37), +("3+32+49",1568), +("4+37+2",74), +("4+37+3",111), +("0+37+48",1776), +("2+34+34",1156), +("0+37+49",1813), +("2+34+35",1190), +("2+34+36",1224), +("4+37+4",148), +("2+34+37",1258), +("4+37+5",185), +("2+34+38",1292), +("4+37+6",222), +("2+34+39",1326), +("4+37+7",259), +("1+36+0",0), +("3+33+0",0), +("0+38+0",0), +("4+37+8",296), +("4+37+9",333), +("2+34+40",1360), +("3+33+1",33), +("1+36+1",36), +("3+33+2",66), +("2+34+41",1394), +("0+38+1",38), +("1+36+2",72), +("3+33+3",99), +("1+36+3",108), +("2+34+42",1428), +("3+33+4",132), +("4+37+10",370), +("1+36+4",144), +("4+37+11",407), +("1+36+5",180), +("3+33+5",165), +("2+34+43",1462), +("0+38+2",76), +("0+38+3",114), +("1+36+6",216), +("3+33+6",198), +("1+36+7",252), +("0+38+4",152), +("3+33+7",231), +("3+33+8",264), +("1+36+8",288), +("2+34+44",1496), +("4+37+12",444), +("4+37+13",481), +("4+37+14",518), +("1+36+9",324), +("4+37+15",555), +("2+34+45",1530), +("4+37+16",592), +("0+38+5",190), +("1+36+10",360), +("3+33+9",297), +("3+33+10",330), +("2+34+46",1564), +("4+37+17",629), +("2+34+47",1598), +("3+33+11",363), +("4+37+18",666), +("0+38+6",228), +("4+37+19",703), +("1+36+11",396), +("4+37+20",740), +("1+36+12",432), +("2+34+48",1632), +("4+37+21",777), +("0+38+7",266), +("1+36+13",468), +("3+33+12",396), +("1+36+14",504), +("0+38+8",304), +("3+33+13",429), +("0+38+9",342), +("4+37+22",814), +("4+37+23",851), +("1+36+15",540), +("2+34+49",1666), +("0+38+10",380), +("1+36+16",576), +("0+38+11",418), +("3+33+14",462), +("1+36+17",612), +("0+38+12",456), +("1+36+18",648), +("0+38+13",494), +("4+37+24",888), +("0+38+14",532), +("4+37+25",925), +("0+38+15",570), +("1+36+19",684), +("3+33+15",495), +("0+38+16",608), +("1+36+20",720), +("3+33+16",528), +("0+38+17",646), +("1+36+21",756), +("0+38+18",684), +("4+37+26",962), +("1+36+22",792), +("4+37+27",999), +("2+35+0",0), +("4+37+28",1036), +("0+38+19",722), +("3+33+17",561), +("1+36+23",828), +("1+36+24",864), +("1+36+25",900), +("1+36+26",936), +("3+33+18",594), +("1+36+27",972), +("0+38+20",760), +("4+37+29",1073), +("4+37+30",1110), +("2+35+1",35), +("3+33+19",627), +("0+38+21",798), +("4+37+31",1147), +("3+33+20",660), +("3+33+21",693), +("0+38+22",836), +("3+33+22",726), +("1+36+28",1008), +("4+37+32",1184), +("4+37+33",1221), +("4+37+34",1258), +("0+38+23",874), +("2+35+2",70), +("1+36+29",1044), +("2+35+3",105), +("0+38+24",912), +("2+35+4",140), +("1+36+30",1080), +("3+33+23",759), +("4+37+35",1295), +("1+36+31",1116), +("0+38+25",950), +("3+33+24",792), +("0+38+26",988), +("4+37+36",1332), +("3+33+25",825), +("4+37+37",1369), +("4+37+38",1406), +("2+35+5",175), +("3+33+26",858), +("4+37+39",1443), +("1+36+32",1152), +("4+37+40",1480), +("0+38+27",1026), +("3+33+27",891), +("1+36+33",1188), +("0+38+28",1064), +("3+33+28",924), +("0+38+29",1102), +("1+36+34",1224), +("2+35+6",210), +("2+35+7",245), +("0+38+30",1140), +("4+37+41",1517), +("0+38+31",1178), +("2+35+8",280), +("4+37+42",1554), +("0+38+32",1216), +("4+37+43",1591), +("1+36+35",1260), +("0+38+33",1254), +("2+35+9",315), +("1+36+36",1296), +("1+36+37",1332), +("2+35+10",350), +("3+33+29",957), +("4+37+44",1628), +("0+38+34",1292), +("3+33+30",990), +("1+36+38",1368), +("4+37+45",1665), +("0+38+35",1330), +("0+38+36",1368), +("4+37+46",1702), +("4+37+47",1739), +("0+38+37",1406), +("3+33+31",1023), +("1+36+39",1404), +("1+36+40",1440), +("2+35+11",385), +("3+33+32",1056), +("4+37+48",1776), +("1+36+41",1476), +("4+37+49",1813), +("1+36+42",1512), +("0+38+38",1444), +("0+38+39",1482), +("1+36+43",1548), +("3+33+33",1089), +("1+36+44",1584), +("1+36+45",1620), +("0+38+40",1520), +("1+36+46",1656), +("1+36+47",1692), +("2+35+12",420), +("2+35+13",455), +("3+33+34",1122), +("0+38+41",1558), +("0+38+42",1596), +("3+33+35",1155), +("3+33+36",1188), +("1+36+48",1728), +("1+36+49",1764), +("3+33+37",1221), +("2+35+14",490), +("3+33+38",1254), +("2+35+15",525), +("4+38+0",0), +("2+35+16",560), +("3+33+39",1287), +("4+38+1",38), +("0+38+43",1634), +("3+33+40",1320), +("0+38+44",1672), +("3+33+41",1353), +("1+37+0",0), +("3+33+42",1386), +("2+35+17",595), +("3+33+43",1419), +("0+38+45",1710), +("4+38+2",76), +("1+37+1",37), +("0+38+46",1748), +("2+35+18",630), +("2+35+19",665), +("0+38+47",1786), +("3+33+44",1452), +("0+38+48",1824), +("0+38+49",1862), +("2+35+20",700), +("3+33+45",1485), +("1+37+2",74), +("3+33+46",1518), +("4+38+3",114), +("2+35+21",735), +("1+37+3",111), +("2+35+22",770), +("2+35+23",805), +("4+38+4",152), +("3+33+47",1551), +("4+38+5",190), +("4+38+6",228), +("3+33+48",1584), +("1+37+4",148), +("1+37+5",185), +("4+38+7",266), +("3+33+49",1617), +("2+35+24",840), +("4+38+8",304), +("1+37+6",222), +("4+38+9",342), +("2+35+25",875), +("1+37+7",259), +("1+37+8",296), +("4+38+10",380), +("3+34+0",0), +("1+37+9",333), +("0+39+0",0), +("2+35+26",910), +("4+38+11",418), +("1+37+10",370), +("2+35+27",945), +("4+38+12",456), +("1+37+11",407), +("3+34+1",34), +("2+35+28",980), +("0+39+1",39), +("4+38+13",494), +("2+35+29",1015), +("3+34+2",68), +("1+37+12",444), +("4+38+14",532), +("4+38+15",570), +("2+35+30",1050), +("3+34+3",102), +("2+35+31",1085), +("1+37+13",481), +("2+35+32",1120), +("4+38+16",608), +("2+35+33",1155), +("2+35+34",1190), +("2+35+35",1225), +("2+35+36",1260), +("1+37+14",518), +("4+38+17",646), +("0+39+2",78), +("2+35+37",1295), +("1+37+15",555), +("3+34+4",136), +("2+35+38",1330), +("2+35+39",1365), +("4+38+18",684), +("2+35+40",1400), +("1+37+16",592), +("0+39+3",117), +("4+38+19",722), +("1+37+17",629), +("3+34+5",170), +("1+37+18",666), +("4+38+20",760), +("4+38+21",798), +("2+35+41",1435), +("1+37+19",703), +("3+34+6",204), +("0+39+4",156), +("0+39+5",195), +("4+38+22",836), +("1+37+20",740), +("2+35+42",1470), +("3+34+7",238), +("0+39+6",234), +("2+35+43",1505), +("1+37+21",777), +("3+34+8",272), +("3+34+9",306), +("4+38+23",874), +("4+38+24",912), +("1+37+22",814), +("0+39+7",273), +("0+39+8",312), +("2+35+44",1540), +("1+37+23",851), +("1+37+24",888), +("2+35+45",1575), +("1+37+25",925), +("3+34+10",340), +("1+37+26",962), +("4+38+25",950), +("3+34+11",374), +("3+34+12",408), +("3+34+13",442), +("4+38+26",988), +("1+37+27",999), +("0+39+9",351), +("0+39+10",390), +("1+37+28",1036), +("3+34+14",476), +("4+38+27",1026), +("2+35+46",1610), +("3+34+15",510), +("1+37+29",1073), +("0+39+11",429), +("3+34+16",544), +("3+34+17",578), +("0+39+12",468), +("0+39+13",507), +("1+37+30",1110), +("1+37+31",1147), +("0+39+14",546), +("4+38+28",1064), +("1+37+32",1184), +("2+35+47",1645), +("0+39+15",585), +("0+39+16",624), +("3+34+18",612), +("4+38+29",1102), +("3+34+19",646), +("0+39+17",663), +("0+39+18",702), +("0+39+19",741), +("3+34+20",680), +("0+39+20",780), +("0+39+21",819), +("1+37+33",1221), +("2+35+48",1680), +("4+38+30",1140), +("4+38+31",1178), +("0+39+22",858), +("2+35+49",1715), +("3+34+21",714), +("1+37+34",1258), +("4+38+32",1216), +("3+34+22",748), +("0+39+23",897), +("3+34+23",782), +("1+37+35",1295), +("0+39+24",936), +("3+34+24",816), +("0+39+25",975), +("4+38+33",1254), +("2+36+0",0), +("1+37+36",1332), +("4+38+34",1292), +("2+36+1",36), +("0+39+26",1014), +("1+37+37",1369), +("0+39+27",1053), +("0+39+28",1092), +("3+34+25",850), +("2+36+2",72), +("0+39+29",1131), +("0+39+30",1170), +("4+38+35",1330), +("4+38+36",1368), +("1+37+38",1406), +("4+38+37",1406), +("2+36+3",108), +("1+37+39",1443), +("4+38+38",1444), +("2+36+4",144), +("3+34+26",884), +("0+39+31",1209), +("1+37+40",1480), +("0+39+32",1248), +("4+38+39",1482), +("4+38+40",1520), +("3+34+27",918), +("0+39+33",1287), +("0+39+34",1326), +("0+39+35",1365), +("1+37+41",1517), +("3+34+28",952), +("3+34+29",986), +("0+39+36",1404), +("0+39+37",1443), +("3+34+30",1020), +("3+34+31",1054), +("4+38+41",1558), +("3+34+32",1088), +("2+36+5",180), +("3+34+33",1122), +("2+36+6",216), +("2+36+7",252), +("3+34+34",1156), +("4+38+42",1596), +("0+39+38",1482), +("4+38+43",1634), +("1+37+42",1554), +("1+37+43",1591), +("1+37+44",1628), +("3+34+35",1190), +("0+39+39",1521), +("1+37+45",1665), +("0+39+40",1560), +("2+36+8",288), +("2+36+9",324), +("3+34+36",1224), +("0+39+41",1599), +("4+38+44",1672), +("3+34+37",1258), +("0+39+42",1638), +("3+34+38",1292), +("3+34+39",1326), +("2+36+10",360), +("2+36+11",396), +("2+36+12",432), +("2+36+13",468), +("4+38+45",1710), +("0+39+43",1677), +("0+39+44",1716), +("2+36+14",504), +("4+38+46",1748), +("0+39+45",1755), +("4+38+47",1786), +("2+36+15",540), +("1+37+46",1702), +("1+37+47",1739), +("0+39+46",1794), +("4+38+48",1824), +("3+34+40",1360), +("4+38+49",1862), +("3+34+41",1394), +("0+39+47",1833), +("1+37+48",1776), +("0+39+48",1872), +("2+36+16",576), +("4+39+0",0), +("0+39+49",1911), +("3+34+42",1428), +("1+37+49",1813), +("3+34+43",1462), +("3+34+44",1496), +("2+36+17",612), +("3+34+45",1530), +("4+39+1",39), +("2+36+18",648), +("4+39+2",78), +("4+39+3",117), +("3+34+46",1564), +("4+39+4",156), +("2+36+19",684), +("2+36+20",720), +("4+39+5",195), +("4+39+6",234), +("3+34+47",1598), +("2+36+21",756), +("2+36+22",792), +("3+34+48",1632), +("4+39+7",273), +("3+34+49",1666), +("1+38+0",0), +("4+39+8",312), +("2+36+23",828), +("1+38+1",38), +("1+38+2",76), +("1+38+3",114), +("1+38+4",152), +("2+36+24",864), +("3+35+0",0), +("2+36+25",900), +("3+35+1",35), +("2+36+26",936), +("3+35+2",70), +("3+35+3",105), +("3+35+4",140), +("4+39+9",351), +("4+39+10",390), +("2+36+27",972), +("2+36+28",1008), +("1+38+5",190), +("2+36+29",1044), +("4+39+11",429), +("3+35+5",175), +("1+38+6",228), +("1+38+7",266), +("3+35+6",210), +("4+39+12",468), +("2+36+30",1080), +("4+39+13",507), +("2+36+31",1116), +("1+38+8",304), +("2+36+32",1152), +("2+36+33",1188), +("3+35+7",245), +("2+36+34",1224), +("2+36+35",1260), +("1+38+9",342), +("2+36+36",1296), +("1+38+10",380), +("3+35+8",280), +("2+36+37",1332), +("4+39+14",546), +("4+39+15",585), +("2+36+38",1368), +("4+39+16",624), +("2+36+39",1404), +("4+39+17",663), +("4+39+18",702), +("4+39+19",741), +("3+35+9",315), +("2+36+40",1440), +("3+35+10",350), +("3+35+11",385), +("1+38+11",418), +("1+38+12",456), +("2+36+41",1476), +("2+36+42",1512), +("1+38+13",494), +("4+39+20",780), +("2+36+43",1548), +("2+36+44",1584), +("1+38+14",532), +("1+38+15",570), +("2+36+45",1620), +("2+36+46",1656), +("2+36+47",1692), +("3+35+12",420), +("4+39+21",819), +("4+39+22",858), +("1+38+16",608), +("3+35+13",455), +("3+35+14",490), +("3+35+15",525), +("4+39+23",897), +("2+36+48",1728), +("1+38+17",646), +("3+35+16",560), +("3+35+17",595), +("1+38+18",684), +("4+39+24",936), +("1+38+19",722), +("2+36+49",1764), +("3+35+18",630), +("4+39+25",975), +("4+39+26",1014), +("1+38+20",760), +("1+38+21",798), +("1+38+22",836), +("3+35+19",665), +("4+39+27",1053), +("3+35+20",700), +("4+39+28",1092), +("1+38+23",874), +("4+39+29",1131), +("2+37+0",0), +("3+35+21",735), +("1+38+24",912), +("1+38+25",950), +("2+37+1",37), +("3+35+22",770), +("3+35+23",805), +("3+35+24",840), +("3+35+25",875), +("1+38+26",988), +("2+37+2",74), +("2+37+3",111), +("2+37+4",148), +("4+39+30",1170), +("2+37+5",185), +("4+39+31",1209), +("3+35+26",910), +("1+38+27",1026), +("2+37+6",222), +("3+35+27",945), +("3+35+28",980), +("2+37+7",259), +("3+35+29",1015), +("2+37+8",296), +("2+37+9",333), +("4+39+32",1248), +("1+38+28",1064), +("4+39+33",1287), +("3+35+30",1050), +("1+38+29",1102), +("3+35+31",1085), +("4+39+34",1326), +("1+38+30",1140), +("2+37+10",370), +("1+38+31",1178), +("4+39+35",1365), +("3+35+32",1120), +("3+35+33",1155), +("2+37+11",407), +("2+37+12",444), +("2+37+13",481), +("3+35+34",1190), +("3+35+35",1225), +("4+39+36",1404), +("4+39+37",1443), +("4+39+38",1482), +("3+35+36",1260), +("1+38+32",1216), +("1+38+33",1254), +("1+38+34",1292), +("2+37+14",518), +("2+37+15",555), +("1+38+35",1330), +("1+38+36",1368), +("1+38+37",1406), +("3+35+37",1295), +("3+35+38",1330), +("1+38+38",1444), +("3+35+39",1365), +("2+37+16",592), +("2+37+17",629), +("3+35+40",1400), +("3+35+41",1435), +("2+37+18",666), +("4+39+39",1521), +("4+39+40",1560), +("4+39+41",1599), +("1+38+39",1482), +("1+38+40",1520), +("3+35+42",1470), +("4+39+42",1638), +("2+37+19",703), +("4+39+43",1677), +("1+38+41",1558), +("3+35+43",1505), +("4+39+44",1716), +("2+37+20",740), +("1+38+42",1596), +("3+35+44",1540), +("3+35+45",1575), +("2+37+21",777), +("3+35+46",1610), +("3+35+47",1645), +("1+38+43",1634), +("4+39+45",1755), +("2+37+22",814), +("2+37+23",851), +("2+37+24",888), +("4+39+46",1794), +("4+39+47",1833), +("1+38+44",1672), +("4+39+48",1872), +("1+38+45",1710), +("3+35+48",1680), +("3+35+49",1715), +("4+39+49",1911), +("2+37+25",925), +("2+37+26",962), +("1+38+46",1748), +("2+37+27",999), +("1+38+47",1786), +("3+36+0",0), +("3+36+1",36), +("2+37+28",1036), +("3+36+2",72), +("3+36+3",108), +("1+38+48",1824), +("3+36+4",144), +("3+36+5",180), +("2+37+29",1073), +("3+36+6",216), +("1+38+49",1862), +("3+36+7",252), +("3+36+8",288), +("3+36+9",324), +("3+36+10",360), +("2+37+30",1110), +("2+37+31",1147), +("3+36+11",396), +("3+36+12",432), +("1+39+0",0), +("1+39+1",39), +("2+37+32",1184), +("1+39+2",78), +("1+39+3",117), +("3+36+13",468), +("2+37+33",1221), +("3+36+14",504), +("2+37+34",1258), +("3+36+15",540), +("1+39+4",156), +("1+39+5",195), +("3+36+16",576), +("3+36+17",612), +("3+36+18",648), +("2+37+35",1295), +("3+36+19",684), +("2+37+36",1332), +("1+39+6",234), +("3+36+20",720), +("2+37+37",1369), +("1+39+7",273), +("2+37+38",1406), +("1+39+8",312), +("1+39+9",351), +("1+39+10",390), +("2+37+39",1443), +("2+37+40",1480), +("3+36+21",756), +("3+36+22",792), +("3+36+23",828), +("2+37+41",1517), +("1+39+11",429), +("1+39+12",468), +("1+39+13",507), +("2+37+42",1554), +("3+36+24",864), +("2+37+43",1591), +("2+37+44",1628), +("3+36+25",900), +("3+36+26",936), +("3+36+27",972), +("1+39+14",546), +("3+36+28",1008), +("2+37+45",1665), +("2+37+46",1702), +("3+36+29",1044), +("2+37+47",1739), +("3+36+30",1080), +("1+39+15",585), +("1+39+16",624), +("1+39+17",663), +("1+39+18",702), +("1+39+19",741), +("1+39+20",780), +("3+36+31",1116), +("1+39+21",819), +("2+37+48",1776), +("3+36+32",1152), +("2+37+49",1813), +("3+36+33",1188), +("3+36+34",1224), +("3+36+35",1260), +("3+36+36",1296), +("1+39+22",858), +("3+36+37",1332), +("1+39+23",897), +("2+38+0",0), +("3+36+38",1368), +("2+38+1",38), +("3+36+39",1404), +("2+38+2",76), +("3+36+40",1440), +("1+39+24",936), +("1+39+25",975), +("1+39+26",1014), +("3+36+41",1476), +("1+39+27",1053), +("3+36+42",1512), +("3+36+43",1548), +("2+38+3",114), +("1+39+28",1092), +("1+39+29",1131), +("2+38+4",152), +("2+38+5",190), +("1+39+30",1170), +("3+36+44",1584), +("2+38+6",228), +("2+38+7",266), +("2+38+8",304), +("3+36+45",1620), +("2+38+9",342), +("3+36+46",1656), +("2+38+10",380), +("3+36+47",1692), +("2+38+11",418), +("1+39+31",1209), +("1+39+32",1248), +("2+38+12",456), +("1+39+33",1287), +("1+39+34",1326), +("2+38+13",494), +("1+39+35",1365), +("2+38+14",532), +("1+39+36",1404), +("1+39+37",1443), +("3+36+48",1728), +("2+38+15",570), +("2+38+16",608), +("1+39+38",1482), +("1+39+39",1521), +("3+36+49",1764), +("2+38+17",646), +("2+38+18",684), +("1+39+40",1560), +("2+38+19",722), +("1+39+41",1599), +("2+38+20",760), +("3+37+0",0), +("2+38+21",798), +("1+39+42",1638), +("1+39+43",1677), +("2+38+22",836), +("1+39+44",1716), +("1+39+45",1755), +("1+39+46",1794), +("3+37+1",37), +("3+37+2",74), +("1+39+47",1833), +("1+39+48",1872), +("2+38+23",874), +("2+38+24",912), +("1+39+49",1911), +("2+38+25",950), +("3+37+3",111), +("2+38+26",988), +("2+38+27",1026), +("2+38+28",1064), +("3+37+4",148), +("2+38+29",1102), +("3+37+5",185), +("2+38+30",1140), +("2+38+31",1178), +("2+38+32",1216), +("2+38+33",1254), +("2+38+34",1292), +("2+38+35",1330), +("2+38+36",1368), +("2+38+37",1406), +("2+38+38",1444), +("2+38+39",1482), +("2+38+40",1520), +("2+38+41",1558), +("2+38+42",1596), +("3+37+6",222), +("3+37+7",259), +("2+38+43",1634), +("3+37+8",296), +("2+38+44",1672), +("2+38+45",1710), +("3+37+9",333), +("2+38+46",1748), +("3+37+10",370), +("2+38+47",1786), +("2+38+48",1824), +("2+38+49",1862), +("3+37+11",407), +("3+37+12",444), +("3+37+13",481), +("3+37+14",518), +("3+37+15",555), +("3+37+16",592), +("3+37+17",629), +("2+39+0",0), +("3+37+18",666), +("2+39+1",39), +("3+37+19",703), +("3+37+20",740), +("3+37+21",777), +("2+39+2",78), +("3+37+22",814), +("3+37+23",851), +("3+37+24",888), +("2+39+3",117), +("2+39+4",156), +("2+39+5",195), +("2+39+6",234), +("2+39+7",273), +("2+39+8",312), +("2+39+9",351), +("3+37+25",925), +("2+39+10",390), +("3+37+26",962), +("2+39+11",429), +("3+37+27",999), +("2+39+12",468), +("3+37+28",1036), +("2+39+13",507), +("3+37+29",1073), +("2+39+14",546), +("3+37+30",1110), +("2+39+15",585), +("3+37+31",1147), +("2+39+16",624), +("3+37+32",1184), +("2+39+17",663), +("3+37+33",1221), +("2+39+18",702), +("3+37+34",1258), +("2+39+19",741), +("3+37+35",1295), +("2+39+20",780), +("3+37+36",1332), +("2+39+21",819), +("3+37+37",1369), +("2+39+22",858), +("3+37+38",1406), +("2+39+23",897), +("3+37+39",1443), +("2+39+24",936), +("3+37+40",1480), +("2+39+25",975), +("3+37+41",1517), +("2+39+26",1014), +("3+37+42",1554), +("2+39+27",1053), +("3+37+43",1591), +("2+39+28",1092), +("3+37+44",1628), +("2+39+29",1131), +("3+37+45",1665), +("2+39+30",1170), +("3+37+46",1702), +("2+39+31",1209), +("3+37+47",1739), +("2+39+32",1248), +("3+37+48",1776), +("2+39+33",1287), +("3+37+49",1813), +("2+39+34",1326), +("2+39+35",1365), +("2+39+36",1404), +("2+39+37",1443), +("2+39+38",1482), +("2+39+39",1521), +("3+38+0",0), +("2+39+40",1560), +("2+39+41",1599), +("3+38+1",38), +("2+39+42",1638), +("3+38+2",76), +("2+39+43",1677), +("2+39+44",1716), +("2+39+45",1755), +("3+38+3",114), +("2+39+46",1794), +("3+38+4",152), +("2+39+47",1833), +("3+38+5",190), +("2+39+48",1872), +("2+39+49",1911), +("3+38+6",228), +("3+38+7",266), +("3+38+8",304), +("3+38+9",342), +("3+38+10",380), +("3+38+11",418), +("3+38+12",456), +("3+38+13",494), +("3+38+14",532), +("3+38+15",570), +("3+38+16",608), +("3+38+17",646), +("3+38+18",684), +("3+38+19",722), +("3+38+20",760), +("3+38+21",798), +("3+38+22",836), +("3+38+23",874), +("3+38+24",912), +("3+38+25",950), +("3+38+26",988), +("3+38+27",1026), +("3+38+28",1064), +("3+38+29",1102), +("3+38+30",1140), +("3+38+31",1178), +("3+38+32",1216), +("3+38+33",1254), +("3+38+34",1292), +("3+38+35",1330), +("3+38+36",1368), +("3+38+37",1406), +("3+38+38",1444), +("3+38+39",1482), +("3+38+40",1520), +("3+38+41",1558), +("3+38+42",1596), +("3+38+43",1634), +("3+38+44",1672), +("3+38+45",1710), +("3+38+46",1748), +("3+38+47",1786), +("3+38+48",1824), +("3+38+49",1862), +("3+39+0",0), +("3+39+1",39), +("3+39+2",78), +("3+39+3",117), +("3+39+4",156), +("3+39+5",195), +("3+39+6",234), +("3+39+7",273), +("3+39+8",312), +("3+39+9",351), +("3+39+10",390), +("3+39+11",429), +("3+39+12",468), +("3+39+13",507), +("3+39+14",546), +("3+39+15",585), +("3+39+16",624), +("3+39+17",663), +("3+39+18",702), +("3+39+19",741), +("3+39+20",780), +("3+39+21",819), +("3+39+22",858), +("3+39+23",897), +("3+39+24",936), +("3+39+25",975), +("3+39+26",1014), +("3+39+27",1053), +("3+39+28",1092), +("3+39+29",1131), +("3+39+30",1170), +("3+39+31",1209), +("3+39+32",1248), +("3+39+33",1287), +("3+39+34",1326), +("3+39+35",1365), +("3+39+36",1404), +("3+39+37",1443), +("3+39+38",1482), +("3+39+39",1521), +("3+39+40",1560), +("3+39+41",1599), +("3+39+42",1638), +("3+39+43",1677), +("3+39+44",1716), +("3+39+45",1755), +("3+39+46",1794), +("3+39+47",1833), +("3+39+48",1872), +("3+39+49",1911); \ No newline at end of file diff --git a/pkg/lightning/mydump/loader.go b/pkg/lightning/mydump/loader.go new file mode 100644 index 000000000..182e892be --- /dev/null +++ b/pkg/lightning/mydump/loader.go @@ -0,0 +1,474 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package mydump + +import ( + "context" + "path/filepath" + "sort" + + "github.com/pingcap/errors" + filter "github.com/pingcap/tidb-tools/pkg/table-filter" + router "github.com/pingcap/tidb-tools/pkg/table-router" + "go.uber.org/zap" + + "github.com/pingcap/br/pkg/lightning/config" + "github.com/pingcap/br/pkg/lightning/log" + "github.com/pingcap/br/pkg/storage" +) + +type MDDatabaseMeta struct { + Name string + SchemaFile string + Tables []*MDTableMeta + Views []*MDTableMeta + charSet string +} + +type MDTableMeta struct { + DB string + Name string + SchemaFile FileInfo + DataFiles []FileInfo + charSet string + TotalSize int64 +} + +type SourceFileMeta struct { + Path string + Type SourceType + Compression Compression + SortKey string + FileSize int64 +} + +func (m *MDTableMeta) GetSchema(ctx context.Context, store storage.ExternalStorage) string { + schema, err := ExportStatement(ctx, store, m.SchemaFile, m.charSet) + if err != nil { + log.L().Error("failed to extract table schema", + zap.String("Path", m.SchemaFile.FileMeta.Path), + log.ShortError(err), + ) + return "" + } + return string(schema) +} + +/* + Mydumper File Loader +*/ +type MDLoader struct { + store storage.ExternalStorage + noSchema bool + dbs []*MDDatabaseMeta + filter filter.Filter + router *router.Table + fileRouter FileRouter + charSet string +} + +type mdLoaderSetup struct { + loader *MDLoader + dbSchemas []FileInfo + tableSchemas []FileInfo + viewSchemas []FileInfo + tableDatas []FileInfo + dbIndexMap map[string]int + tableIndexMap map[filter.Table]int +} + +func NewMyDumpLoader(ctx context.Context, cfg *config.Config) (*MDLoader, error) { + u, err := storage.ParseBackend(cfg.Mydumper.SourceDir, nil) + if err != nil { + return nil, err + } + s, err := storage.Create(ctx, u, true) + if err != nil { + return nil, err + } + + return NewMyDumpLoaderWithStore(ctx, cfg, s) +} + +func NewMyDumpLoaderWithStore(ctx context.Context, cfg *config.Config, store storage.ExternalStorage) (*MDLoader, error) { + var r *router.Table + var err error + + if len(cfg.Routes) > 0 && len(cfg.Mydumper.FileRouters) > 0 { + return nil, errors.New("table route is deprecated, can't config both [routes] and [mydumper.files]") + } + + if len(cfg.Routes) > 0 { + r, err = router.NewTableRouter(cfg.Mydumper.CaseSensitive, cfg.Routes) + if err != nil { + return nil, errors.Trace(err) + } + } + + // use the legacy black-white-list if defined. otherwise use the new filter. + var f filter.Filter + if cfg.HasLegacyBlackWhiteList() { + f, err = filter.ParseMySQLReplicationRules(&cfg.BWList) + } else { + f, err = filter.Parse(cfg.Mydumper.Filter) + } + if err != nil { + return nil, errors.Annotate(err, "parse filter failed") + } + if !cfg.Mydumper.CaseSensitive { + f = filter.CaseInsensitive(f) + } + + fileRouteRules := cfg.Mydumper.FileRouters + if cfg.Mydumper.DefaultFileRules { + fileRouteRules = append(fileRouteRules, defaultFileRouteRules...) + } + + fileRouter, err := NewFileRouter(fileRouteRules) + if err != nil { + return nil, errors.Annotate(err, "parser file routing rule failed") + } + + mdl := &MDLoader{ + store: store, + noSchema: cfg.Mydumper.NoSchema, + filter: f, + router: r, + charSet: cfg.Mydumper.CharacterSet, + fileRouter: fileRouter, + } + + setup := mdLoaderSetup{ + loader: mdl, + dbIndexMap: make(map[string]int), + tableIndexMap: make(map[filter.Table]int), + } + + if err := setup.setup(ctx, mdl.store); err != nil { + return nil, errors.Trace(err) + } + + return mdl, nil +} + +type fileType int + +const ( + fileTypeDatabaseSchema fileType = iota + fileTypeTableSchema + fileTypeTableData +) + +func (ftype fileType) String() string { + switch ftype { + case fileTypeDatabaseSchema: + return "database schema" + case fileTypeTableSchema: + return "table schema" + case fileTypeTableData: + return "table data" + default: + return "(unknown)" + } +} + +type FileInfo struct { + TableName filter.Table + FileMeta SourceFileMeta +} + +// setup the `s.loader.dbs` slice by scanning all *.sql files inside `dir`. +// +// The database and tables are inserted in a consistent order, so creating an +// MDLoader twice with the same data source is going to produce the same array, +// even after killing Lightning. +// +// This is achieved by using `filepath.Walk` internally which guarantees the +// files are visited in lexicographical order (note that this does not mean the +// databases and tables in the end are ordered lexicographically since they may +// be stored in different subdirectories). +// +// Will sort tables by table size, this means that the big table is imported +// at the latest, which to avoid large table take a long time to import and block +// small table to release index worker. +func (s *mdLoaderSetup) setup(ctx context.Context, store storage.ExternalStorage) error { + /* + Mydumper file names format + db —— {db}-schema-create.sql + table —— {db}.{table}-schema.sql + sql —— {db}.{table}.{part}.sql / {db}.{table}.sql + */ + if err := s.listFiles(ctx, store); err != nil { + return errors.Annotate(err, "list file failed") + } + if err := s.route(); err != nil { + return errors.Trace(err) + } + + if !s.loader.noSchema { + // setup database schema + if len(s.dbSchemas) == 0 { + return errors.New("no schema create sql files found. Please either set `mydumper.no-schema` to true or add schema sql file for each database.") + } + for _, fileInfo := range s.dbSchemas { + if _, dbExists := s.insertDB(fileInfo.TableName.Schema, fileInfo.FileMeta.Path); dbExists && s.loader.router == nil { + return errors.Errorf("invalid database schema file, duplicated item - %s", fileInfo.FileMeta.Path) + } + } + + // setup table schema + for _, fileInfo := range s.tableSchemas { + _, dbExists, tableExists := s.insertTable(fileInfo) + if !dbExists { + return errors.Errorf("invalid table schema file, cannot find db '%s' - %s", fileInfo.TableName.Schema, fileInfo.FileMeta.Path) + } else if tableExists && s.loader.router == nil { + return errors.Errorf("invalid table schema file, duplicated item - %s", fileInfo.FileMeta.Path) + } + } + + // setup view schema + for _, fileInfo := range s.viewSchemas { + dbExists, tableExists := s.insertView(fileInfo) + if !dbExists { + return errors.Errorf("invalid table schema file, cannot find db '%s' - %s", fileInfo.TableName.Schema, fileInfo.FileMeta.Path) + } else if !tableExists { + // remove the last `-view.sql` from path as the relate table schema file path + return errors.Errorf("invalid view schema file, miss host table schema for view '%s'", fileInfo.TableName.Name) + } + } + } + + // Sql file for restore data + for _, fileInfo := range s.tableDatas { + // set a dummy `FileInfo` here without file meta because we needn't restore the table schema + tableMeta, dbExists, tableExists := s.insertTable(FileInfo{TableName: fileInfo.TableName}) + if !s.loader.noSchema { + if !dbExists { + return errors.Errorf("invalid data file, miss host db '%s' - %s", fileInfo.TableName.Schema, fileInfo.FileMeta.Path) + } else if !tableExists { + return errors.Errorf("invalid data file, miss host table '%s' - %s", fileInfo.TableName.Name, fileInfo.FileMeta.Path) + } + } + tableMeta.DataFiles = append(tableMeta.DataFiles, fileInfo) + tableMeta.TotalSize += fileInfo.FileMeta.FileSize + } + + for _, dbMeta := range s.loader.dbs { + // Put the small table in the front of the slice which can avoid large table + // take a long time to import and block small table to release index worker. + sort.SliceStable(dbMeta.Tables, func(i, j int) bool { + return dbMeta.Tables[i].TotalSize < dbMeta.Tables[j].TotalSize + }) + + // sort each table source files by sort-key + for _, tbMeta := range dbMeta.Tables { + dataFiles := tbMeta.DataFiles + sort.SliceStable(dataFiles, func(i, j int) bool { + return dataFiles[i].FileMeta.SortKey < dataFiles[j].FileMeta.SortKey + }) + } + } + + return nil +} + +func (s *mdLoaderSetup) listFiles(ctx context.Context, store storage.ExternalStorage) error { + // `filepath.Walk` yields the paths in a deterministic (lexicographical) order, + // meaning the file and chunk orders will be the same everytime it is called + // (as long as the source is immutable). + err := store.WalkDir(ctx, &storage.WalkOption{}, func(path string, size int64) error { + logger := log.With(zap.String("path", path)) + + res, err := s.loader.fileRouter.Route(filepath.ToSlash(path)) + if err != nil { + return errors.Annotatef(err, "apply file routing on file '%s' failed", path) + } + if res == nil { + logger.Debug("[loader] file is filtered by file router") + return nil + } + + info := FileInfo{ + TableName: filter.Table{Schema: res.Schema, Name: res.Name}, + FileMeta: SourceFileMeta{Path: path, Type: res.Type, Compression: res.Compression, SortKey: res.Key, FileSize: size}, + } + + if s.loader.shouldSkip(&info.TableName) { + logger.Debug("[filter] ignoring table file") + + return nil + } + + switch res.Type { + case SourceTypeSchemaSchema: + s.dbSchemas = append(s.dbSchemas, info) + case SourceTypeTableSchema: + s.tableSchemas = append(s.tableSchemas, info) + case SourceTypeViewSchema: + s.viewSchemas = append(s.viewSchemas, info) + case SourceTypeSQL, SourceTypeCSV, SourceTypeParquet: + s.tableDatas = append(s.tableDatas, info) + } + + logger.Debug("file route result", zap.String("schema", res.Schema), + zap.String("table", res.Name), zap.Stringer("type", res.Type)) + + return nil + }) + + return errors.Trace(err) +} + +func (l *MDLoader) shouldSkip(table *filter.Table) bool { + if len(table.Name) == 0 { + return !l.filter.MatchSchema(table.Schema) + } + return !l.filter.MatchTable(table.Schema, table.Name) +} + +func (s *mdLoaderSetup) route() error { + r := s.loader.router + if r == nil { + return nil + } + + type dbInfo struct { + fileMeta SourceFileMeta + count int + } + + knownDBNames := make(map[string]dbInfo) + for _, info := range s.dbSchemas { + knownDBNames[info.TableName.Schema] = dbInfo{ + fileMeta: info.FileMeta, + count: 1, + } + } + for _, info := range s.tableSchemas { + dbInfo := knownDBNames[info.TableName.Schema] + dbInfo.count++ + knownDBNames[info.TableName.Schema] = dbInfo + } + for _, info := range s.viewSchemas { + dbInfo := knownDBNames[info.TableName.Schema] + dbInfo.count++ + } + + run := func(arr []FileInfo) error { + for i, info := range arr { + dbName, tableName, err := r.Route(info.TableName.Schema, info.TableName.Name) + if err != nil { + return errors.Trace(err) + } + if dbName != info.TableName.Schema { + oldInfo := knownDBNames[info.TableName.Schema] + oldInfo.count-- + knownDBNames[info.TableName.Schema] = oldInfo + + newInfo, ok := knownDBNames[dbName] + newInfo.count++ + if !ok { + newInfo.fileMeta = oldInfo.fileMeta + s.dbSchemas = append(s.dbSchemas, FileInfo{ + TableName: filter.Table{Schema: dbName}, + FileMeta: oldInfo.fileMeta, + }) + } + knownDBNames[dbName] = newInfo + } + arr[i].TableName = filter.Table{Schema: dbName, Name: tableName} + } + return nil + } + + if err := run(s.tableSchemas); err != nil { + return errors.Trace(err) + } + if err := run(s.viewSchemas); err != nil { + return errors.Trace(err) + } + if err := run(s.tableDatas); err != nil { + return errors.Trace(err) + } + + // remove all schemas which has been entirely routed away + // https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating + remainingSchemas := s.dbSchemas[:0] + for _, info := range s.dbSchemas { + if knownDBNames[info.TableName.Schema].count > 0 { + remainingSchemas = append(remainingSchemas, info) + } + } + s.dbSchemas = remainingSchemas + + return nil +} + +func (s *mdLoaderSetup) insertDB(dbName string, path string) (*MDDatabaseMeta, bool) { + dbIndex, ok := s.dbIndexMap[dbName] + if ok { + return s.loader.dbs[dbIndex], true + } else { + s.dbIndexMap[dbName] = len(s.loader.dbs) + ptr := &MDDatabaseMeta{ + Name: dbName, + SchemaFile: path, + charSet: s.loader.charSet, + } + s.loader.dbs = append(s.loader.dbs, ptr) + return ptr, false + } +} + +func (s *mdLoaderSetup) insertTable(fileInfo FileInfo) (*MDTableMeta, bool, bool) { + dbMeta, dbExists := s.insertDB(fileInfo.TableName.Schema, "") + tableIndex, ok := s.tableIndexMap[fileInfo.TableName] + if ok { + return dbMeta.Tables[tableIndex], dbExists, true + } else { + s.tableIndexMap[fileInfo.TableName] = len(dbMeta.Tables) + ptr := &MDTableMeta{ + DB: fileInfo.TableName.Schema, + Name: fileInfo.TableName.Name, + SchemaFile: fileInfo, + DataFiles: make([]FileInfo, 0, 16), + charSet: s.loader.charSet, + } + dbMeta.Tables = append(dbMeta.Tables, ptr) + return ptr, dbExists, false + } +} + +func (s *mdLoaderSetup) insertView(fileInfo FileInfo) (bool, bool) { + dbMeta, dbExists := s.insertDB(fileInfo.TableName.Schema, "") + _, ok := s.tableIndexMap[fileInfo.TableName] + if ok { + meta := &MDTableMeta{ + DB: fileInfo.TableName.Schema, + Name: fileInfo.TableName.Name, + SchemaFile: fileInfo, + charSet: s.loader.charSet, + } + dbMeta.Views = append(dbMeta.Views, meta) + } + return dbExists, ok +} + +func (l *MDLoader) GetDatabases() []*MDDatabaseMeta { + return l.dbs +} + +func (l *MDLoader) GetStore() storage.ExternalStorage { + return l.store +} diff --git a/pkg/lightning/mydump/loader_test.go b/pkg/lightning/mydump/loader_test.go new file mode 100644 index 000000000..816e904d8 --- /dev/null +++ b/pkg/lightning/mydump/loader_test.go @@ -0,0 +1,555 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package mydump_test + +import ( + "context" + "io/ioutil" + "os" + "path/filepath" + "testing" + + . "github.com/pingcap/check" + filter "github.com/pingcap/tidb-tools/pkg/table-filter" + router "github.com/pingcap/tidb-tools/pkg/table-router" + + "github.com/pingcap/br/pkg/lightning/config" + md "github.com/pingcap/br/pkg/lightning/mydump" +) + +var _ = Suite(&testMydumpLoaderSuite{}) + +func TestMydumps(t *testing.T) { + TestingT(t) +} + +type testMydumpLoaderSuite struct { + cfg *config.Config + sourceDir string +} + +func (s *testMydumpLoaderSuite) SetUpSuite(c *C) {} +func (s *testMydumpLoaderSuite) TearDownSuite(c *C) {} + +func newConfigWithSourceDir(sourceDir string) *config.Config { + path, _ := filepath.Abs(sourceDir) + return &config.Config{ + Mydumper: config.MydumperRuntime{ + SourceDir: "file://" + filepath.ToSlash(path), + Filter: []string{"*.*"}, + DefaultFileRules: true, + }, + } +} + +func (s *testMydumpLoaderSuite) SetUpTest(c *C) { + s.sourceDir = c.MkDir() + s.cfg = newConfigWithSourceDir(s.sourceDir) +} + +func (s *testMydumpLoaderSuite) touch(c *C, filename ...string) string { + components := make([]string, len(filename)+1) + components = append(components, s.sourceDir) + components = append(components, filename...) + path := filepath.Join(components...) + err := ioutil.WriteFile(path, nil, 0o644) + c.Assert(err, IsNil) + return path +} + +func (s *testMydumpLoaderSuite) mkdir(c *C, dirname string) { + path := filepath.Join(s.sourceDir, dirname) + err := os.Mkdir(path, 0o755) + c.Assert(err, IsNil) +} + +func (s *testMydumpLoaderSuite) TestLoader(c *C) { + ctx := context.Background() + cfg := newConfigWithSourceDir("./not-exists") + _, err := md.NewMyDumpLoader(ctx, cfg) + c.Assert(err, NotNil) + + cfg = newConfigWithSourceDir("./examples") + mdl, err := md.NewMyDumpLoader(ctx, cfg) + c.Assert(err, IsNil) + + dbMetas := mdl.GetDatabases() + c.Assert(len(dbMetas), Equals, 1) + dbMeta := dbMetas[0] + c.Assert(dbMeta.Name, Equals, "mocker_test") + c.Assert(len(dbMeta.Tables), Equals, 4) + + expected := []struct { + name string + dataFiles int + }{ + {name: "i", dataFiles: 1}, + {name: "report_case_high_risk", dataFiles: 1}, + {name: "tbl_multi_index", dataFiles: 1}, + {name: "tbl_autoid", dataFiles: 1}, + } + + for i, table := range expected { + c.Assert(dbMeta.Tables[i].Name, Equals, table.name) + c.Assert(len(dbMeta.Tables[i].DataFiles), Equals, table.dataFiles) + } +} + +func (s *testMydumpLoaderSuite) TestEmptyDB(c *C) { + _, err := md.NewMyDumpLoader(context.Background(), s.cfg) + c.Assert(err, ErrorMatches, "no schema create sql files found. Please either set `mydumper.no-schema` to true or add schema sql file for each database.") +} + +func (s *testMydumpLoaderSuite) TestDuplicatedDB(c *C) { + /* + Path/ + a/ + db-schema-create.sql + b/ + db-schema-create.sql + */ + s.mkdir(c, "a") + s.touch(c, "a", "db-schema-create.sql") + s.mkdir(c, "b") + s.touch(c, "b", "db-schema-create.sql") + + _, err := md.NewMyDumpLoader(context.Background(), s.cfg) + c.Assert(err, ErrorMatches, `invalid database schema file, duplicated item - .*[/\\]db-schema-create\.sql`) +} + +func (s *testMydumpLoaderSuite) TestTableNoHostDB(c *C) { + /* + Path/ + notdb-schema-create.sql + db.tbl-schema.sql + */ + + dir := s.sourceDir + err := ioutil.WriteFile(filepath.Join(dir, "notdb-schema-create.sql"), nil, 0o644) + c.Assert(err, IsNil) + err = ioutil.WriteFile(filepath.Join(dir, "db.tbl-schema.sql"), nil, 0o644) + c.Assert(err, IsNil) + + _, err = md.NewMyDumpLoader(context.Background(), s.cfg) + c.Assert(err, ErrorMatches, `invalid table schema file, cannot find db 'db' - .*db\.tbl-schema\.sql`) +} + +func (s *testMydumpLoaderSuite) TestDuplicatedTable(c *C) { + /* + Path/ + db-schema-create.sql + a/ + db.tbl-schema.sql + b/ + db.tbl-schema.sql + */ + + s.touch(c, "db-schema-create.sql") + s.mkdir(c, "a") + s.touch(c, "a", "db.tbl-schema.sql") + s.mkdir(c, "b") + s.touch(c, "b", "db.tbl-schema.sql") + + _, err := md.NewMyDumpLoader(context.Background(), s.cfg) + c.Assert(err, ErrorMatches, `invalid table schema file, duplicated item - .*db\.tbl-schema\.sql`) +} + +func (s *testMydumpLoaderSuite) TestDataNoHostDB(c *C) { + /* + Path/ + notdb-schema-create.sql + db.tbl.sql + */ + + s.touch(c, "notdb-schema-create.sql") + s.touch(c, "db.tbl.sql") + + _, err := md.NewMyDumpLoader(context.Background(), s.cfg) + c.Assert(err, ErrorMatches, `invalid data file, miss host db 'db' - .*[/\\]?db\.tbl\.sql`) +} + +func (s *testMydumpLoaderSuite) TestDataNoHostTable(c *C) { + /* + Path/ + db-schema-create.sql + db.tbl.sql + */ + + s.touch(c, "db-schema-create.sql") + s.touch(c, "db.tbl.sql") + + _, err := md.NewMyDumpLoader(context.Background(), s.cfg) + c.Assert(err, ErrorMatches, `invalid data file, miss host table 'tbl' - .*[/\\]?db\.tbl\.sql`) +} + +func (s *testMydumpLoaderSuite) TestViewNoHostDB(c *C) { + /* + Path/ + notdb-schema-create.sql + db.tbl-schema-view.sql + */ + s.touch(c, "notdb-schema-create.sql") + s.touch(c, "db.tbl-schema-view.sql") + + _, err := md.NewMyDumpLoader(context.Background(), s.cfg) + c.Assert(err, ErrorMatches, `invalid table schema file, cannot find db 'db' - .*[/\\]?db\.tbl-schema-view\.sql`) +} + +func (s *testMydumpLoaderSuite) TestViewNoHostTable(c *C) { + /* + Path/ + db-schema-create.sql + db.tbl-schema-view.sql + */ + + s.touch(c, "db-schema-create.sql") + s.touch(c, "db.tbl-schema-view.sql") + + _, err := md.NewMyDumpLoader(context.Background(), s.cfg) + c.Assert(err, ErrorMatches, `invalid view schema file, miss host table schema for view 'tbl'`) +} + +func (s *testMydumpLoaderSuite) TestDataWithoutSchema(c *C) { + dir := s.sourceDir + p := filepath.Join(dir, "db.tbl.sql") + err := ioutil.WriteFile(p, nil, 0o644) + c.Assert(err, IsNil) + + s.cfg.Mydumper.NoSchema = true + + mdl, err := md.NewMyDumpLoader(context.Background(), s.cfg) + c.Assert(err, IsNil) + c.Assert(mdl.GetDatabases(), DeepEquals, []*md.MDDatabaseMeta{{ + Name: "db", + SchemaFile: "", + Tables: []*md.MDTableMeta{{ + DB: "db", + Name: "tbl", + SchemaFile: md.FileInfo{TableName: filter.Table{Schema: "db", Name: "tbl"}}, + DataFiles: []md.FileInfo{{TableName: filter.Table{Schema: "db", Name: "tbl"}, FileMeta: md.SourceFileMeta{Path: "db.tbl.sql", Type: md.SourceTypeSQL}}}, + }}, + }}) +} + +func (s *testMydumpLoaderSuite) TestTablesWithDots(c *C) { + s.touch(c, "db-schema-create.sql") + s.touch(c, "db.tbl.with.dots-schema.sql") + s.touch(c, "db.tbl.with.dots.0001.sql") + s.touch(c, "db.0002-schema.sql") + s.touch(c, "db.0002.sql") + + // insert some tables with file name structures which we're going to ignore. + s.touch(c, "db.v-schema-trigger.sql") + s.touch(c, "db.v-schema-post.sql") + s.touch(c, "db.sql") + s.touch(c, "db-schema.sql") + + mdl, err := md.NewMyDumpLoader(context.Background(), s.cfg) + c.Assert(err, IsNil) + c.Assert(mdl.GetDatabases(), DeepEquals, []*md.MDDatabaseMeta{{ + Name: "db", + SchemaFile: "db-schema-create.sql", + Tables: []*md.MDTableMeta{ + { + DB: "db", + Name: "0002", + SchemaFile: md.FileInfo{TableName: filter.Table{Schema: "db", Name: "0002"}, FileMeta: md.SourceFileMeta{Path: "db.0002-schema.sql", Type: md.SourceTypeTableSchema}}, + DataFiles: []md.FileInfo{{TableName: filter.Table{Schema: "db", Name: "0002"}, FileMeta: md.SourceFileMeta{Path: "db.0002.sql", Type: md.SourceTypeSQL}}}, + }, + { + DB: "db", + Name: "tbl.with.dots", + SchemaFile: md.FileInfo{TableName: filter.Table{Schema: "db", Name: "tbl.with.dots"}, FileMeta: md.SourceFileMeta{Path: "db.tbl.with.dots-schema.sql", Type: md.SourceTypeTableSchema}}, + DataFiles: []md.FileInfo{{TableName: filter.Table{Schema: "db", Name: "tbl.with.dots"}, FileMeta: md.SourceFileMeta{Path: "db.tbl.with.dots.0001.sql", Type: md.SourceTypeSQL, SortKey: "0001"}}}, + }, + }, + }}) +} + +func (s *testMydumpLoaderSuite) TestRouter(c *C) { + s.cfg.Routes = []*router.TableRule{ + { + SchemaPattern: "a*", + TablePattern: "t*", + TargetSchema: "b", + TargetTable: "u", + }, + { + SchemaPattern: "c*", + TargetSchema: "c", + }, + { + SchemaPattern: "e*", + TablePattern: "f*", + TargetSchema: "v", + TargetTable: "vv", + }, + } + + /* + Path/ + a0-schema-create.sql + a0.t0-schema.sql + a0.t0.1.sql + a0.t1-schema.sql + a0.t1.1.sql + a1-schema-create.sql + a1.s1-schema.sql + a1.s1.1.schema.sql + a1.t2-schema.sql + a1.t2.1.sql + a1.v1-schema.sql + a1.v1-schema-view.sql + c0-schema-create.sql + c0.t3-schema.sql + c0.t3.1.sql + d0-schema-create.sql + e0-schema-create.sql + e0.f0-schema.sql + e0.f0-schema-view.sql + */ + + s.touch(c, "a0-schema-create.sql") + s.touch(c, "a0.t0-schema.sql") + s.touch(c, "a0.t0.1.sql") + s.touch(c, "a0.t1-schema.sql") + s.touch(c, "a0.t1.1.sql") + + s.touch(c, "a1-schema-create.sql") + s.touch(c, "a1.s1-schema.sql") + s.touch(c, "a1.s1.1.sql") + s.touch(c, "a1.t2-schema.sql") + s.touch(c, "a1.t2.1.sql") + s.touch(c, "a1.v1-schema.sql") + s.touch(c, "a1.v1-schema-view.sql") + + s.touch(c, "c0-schema-create.sql") + s.touch(c, "c0.t3-schema.sql") + s.touch(c, "c0.t3.1.sql") + + s.touch(c, "d0-schema-create.sql") + + s.touch(c, "e0-schema-create.sql") + s.touch(c, "e0.f0-schema.sql") + s.touch(c, "e0.f0-schema-view.sql") + + mdl, err := md.NewMyDumpLoader(context.Background(), s.cfg) + c.Assert(err, IsNil) + c.Assert(mdl.GetDatabases(), DeepEquals, []*md.MDDatabaseMeta{ + { + Name: "a1", + SchemaFile: "a1-schema-create.sql", + Tables: []*md.MDTableMeta{ + { + DB: "a1", + Name: "s1", + SchemaFile: md.FileInfo{TableName: filter.Table{Schema: "a1", Name: "s1"}, FileMeta: md.SourceFileMeta{Path: "a1.s1-schema.sql", Type: md.SourceTypeTableSchema}}, + DataFiles: []md.FileInfo{{TableName: filter.Table{Schema: "a1", Name: "s1"}, FileMeta: md.SourceFileMeta{Path: "a1.s1.1.sql", Type: md.SourceTypeSQL, SortKey: "1"}}}, + }, + { + DB: "a1", + Name: "v1", + SchemaFile: md.FileInfo{TableName: filter.Table{Schema: "a1", Name: "v1"}, FileMeta: md.SourceFileMeta{Path: "a1.v1-schema.sql", Type: md.SourceTypeTableSchema}}, + DataFiles: []md.FileInfo{}, + }, + }, + Views: []*md.MDTableMeta{ + { + DB: "a1", + Name: "v1", + SchemaFile: md.FileInfo{TableName: filter.Table{Schema: "a1", Name: "v1"}, FileMeta: md.SourceFileMeta{Path: "a1.v1-schema-view.sql", Type: md.SourceTypeViewSchema}}, + }, + }, + }, + { + Name: "d0", + SchemaFile: "d0-schema-create.sql", + }, + { + Name: "b", + SchemaFile: "a0-schema-create.sql", + Tables: []*md.MDTableMeta{ + { + DB: "b", + Name: "u", + SchemaFile: md.FileInfo{TableName: filter.Table{Schema: "b", Name: "u"}, FileMeta: md.SourceFileMeta{Path: "a0.t0-schema.sql", Type: md.SourceTypeTableSchema}}, + DataFiles: []md.FileInfo{ + {TableName: filter.Table{Schema: "b", Name: "u"}, FileMeta: md.SourceFileMeta{Path: "a0.t0.1.sql", Type: md.SourceTypeSQL, SortKey: "1"}}, + {TableName: filter.Table{Schema: "b", Name: "u"}, FileMeta: md.SourceFileMeta{Path: "a0.t1.1.sql", Type: md.SourceTypeSQL, SortKey: "1"}}, + {TableName: filter.Table{Schema: "b", Name: "u"}, FileMeta: md.SourceFileMeta{Path: "a1.t2.1.sql", Type: md.SourceTypeSQL, SortKey: "1"}}, + }, + }, + }, + }, + { + Name: "c", + SchemaFile: "c0-schema-create.sql", + Tables: []*md.MDTableMeta{ + { + DB: "c", + Name: "t3", + SchemaFile: md.FileInfo{TableName: filter.Table{Schema: "c", Name: "t3"}, FileMeta: md.SourceFileMeta{Path: "c0.t3-schema.sql", Type: md.SourceTypeTableSchema}}, + DataFiles: []md.FileInfo{{TableName: filter.Table{Schema: "c", Name: "t3"}, FileMeta: md.SourceFileMeta{Path: "c0.t3.1.sql", Type: md.SourceTypeSQL, SortKey: "1"}}}, + }, + }, + }, + { + Name: "v", + SchemaFile: "e0-schema-create.sql", + Tables: []*md.MDTableMeta{ + { + DB: "v", + Name: "vv", + SchemaFile: md.FileInfo{TableName: filter.Table{Schema: "v", Name: "vv"}, FileMeta: md.SourceFileMeta{Path: "e0.f0-schema.sql", Type: md.SourceTypeTableSchema}}, + DataFiles: []md.FileInfo{}, + }, + }, + Views: []*md.MDTableMeta{ + { + DB: "v", + Name: "vv", + SchemaFile: md.FileInfo{TableName: filter.Table{Schema: "v", Name: "vv"}, FileMeta: md.SourceFileMeta{Path: "e0.f0-schema-view.sql", Type: md.SourceTypeViewSchema}}, + }, + }, + }, + }) +} + +func (s *testMydumpLoaderSuite) TestBadRouterRule(c *C) { + s.cfg.Routes = []*router.TableRule{{ + SchemaPattern: "a*b", + TargetSchema: "ab", + }} + + s.touch(c, "a1b-schema-create.sql") + + _, err := md.NewMyDumpLoader(context.Background(), s.cfg) + c.Assert(err, ErrorMatches, `.*pattern a\*b not valid`) +} + +func (s *testMydumpLoaderSuite) TestFileRouting(c *C) { + s.cfg.Mydumper.DefaultFileRules = false + s.cfg.Mydumper.FileRouters = []*config.FileRouteRule{ + { + Pattern: `(?i)^(?:[^./]*/)*([a-z0-9_]+)/schema\.sql$`, + Schema: "$1", + Type: "schema-schema", + }, + { + Pattern: `(?i)^(?:[^./]*/)*([a-z0-9]+)/([a-z0-9_]+)-table\.sql$`, + Schema: "$1", + Table: "$2", + Type: "table-schema", + }, + { + Pattern: `(?i)^(?:[^./]*/)*([a-z0-9]+)/([a-z0-9_]+)-view\.sql$`, + Schema: "$1", + Table: "$2", + Type: "view-schema", + }, + { + Pattern: `(?i)^(?:[^./]*/)*([a-z][a-z0-9_]*)/([a-z]+)[0-9]*(?:\.([0-9]+))?\.(sql|csv)$`, + Schema: "$1", + Table: "$2", + Type: "$4", + }, + { + Pattern: `^(?:[^./]*/)*([a-z]+)(?:\.([0-9]+))?\.(sql|csv)$`, + Schema: "d2", + Table: "$1", + Type: "$3", + }, + } + + s.mkdir(c, "d1") + s.mkdir(c, "d2") + s.touch(c, "d1/schema.sql") + s.touch(c, "d1/test-table.sql") + s.touch(c, "d1/test0.sql") + s.touch(c, "d1/test1.sql") + s.touch(c, "d1/test2.001.sql") + s.touch(c, "d1/v1-table.sql") + s.touch(c, "d1/v1-view.sql") + _ = s.touch(c, "d1/t1-schema-create.sql") + s.touch(c, "d2/schema.sql") + s.touch(c, "d2/abc-table.sql") + s.touch(c, "abc.1.sql") + + mdl, err := md.NewMyDumpLoader(context.Background(), s.cfg) + c.Assert(err, IsNil) + c.Assert(mdl.GetDatabases(), DeepEquals, []*md.MDDatabaseMeta{ + { + Name: "d1", + SchemaFile: filepath.FromSlash("d1/schema.sql"), + Tables: []*md.MDTableMeta{ + { + DB: "d1", + Name: "test", + SchemaFile: md.FileInfo{ + TableName: filter.Table{Schema: "d1", Name: "test"}, + FileMeta: md.SourceFileMeta{Path: filepath.FromSlash("d1/test-table.sql"), Type: md.SourceTypeTableSchema}, + }, + DataFiles: []md.FileInfo{ + { + TableName: filter.Table{Schema: "d1", Name: "test"}, + FileMeta: md.SourceFileMeta{Path: filepath.FromSlash("d1/test0.sql"), Type: md.SourceTypeSQL}, + }, + { + TableName: filter.Table{Schema: "d1", Name: "test"}, + FileMeta: md.SourceFileMeta{Path: filepath.FromSlash("d1/test1.sql"), Type: md.SourceTypeSQL}, + }, + { + TableName: filter.Table{Schema: "d1", Name: "test"}, + FileMeta: md.SourceFileMeta{Path: filepath.FromSlash("d1/test2.001.sql"), Type: md.SourceTypeSQL}, + }, + }, + }, + { + DB: "d1", + Name: "v1", + SchemaFile: md.FileInfo{ + TableName: filter.Table{Schema: "d1", Name: "v1"}, + FileMeta: md.SourceFileMeta{Path: filepath.FromSlash("d1/v1-table.sql"), Type: md.SourceTypeTableSchema}, + }, + DataFiles: []md.FileInfo{}, + }, + }, + Views: []*md.MDTableMeta{ + { + DB: "d1", + Name: "v1", + SchemaFile: md.FileInfo{ + TableName: filter.Table{Schema: "d1", Name: "v1"}, + FileMeta: md.SourceFileMeta{Path: filepath.FromSlash("d1/v1-view.sql"), Type: md.SourceTypeViewSchema}, + }, + }, + }, + }, + { + Name: "d2", + SchemaFile: filepath.FromSlash("d2/schema.sql"), + Tables: []*md.MDTableMeta{ + { + DB: "d2", + Name: "abc", + SchemaFile: md.FileInfo{ + TableName: filter.Table{Schema: "d2", Name: "abc"}, + FileMeta: md.SourceFileMeta{Path: filepath.FromSlash("d2/abc-table.sql"), Type: md.SourceTypeTableSchema}, + }, + DataFiles: []md.FileInfo{{TableName: filter.Table{Schema: "d2", Name: "abc"}, FileMeta: md.SourceFileMeta{Path: "abc.1.sql", Type: md.SourceTypeSQL}}}, + }, + }, + }, + }) +} diff --git a/pkg/lightning/mydump/parquet_parser.go b/pkg/lightning/mydump/parquet_parser.go new file mode 100644 index 000000000..3a40f60cb --- /dev/null +++ b/pkg/lightning/mydump/parquet_parser.go @@ -0,0 +1,370 @@ +package mydump + +import ( + "bytes" + "context" + "fmt" + "io" + "reflect" + "strings" + "time" + + "github.com/pingcap/errors" + "github.com/pingcap/tidb/types" + "github.com/xitongsys/parquet-go/parquet" + preader "github.com/xitongsys/parquet-go/reader" + "github.com/xitongsys/parquet-go/source" + "go.uber.org/zap" + + "github.com/pingcap/br/pkg/lightning/log" + "github.com/pingcap/br/pkg/storage" +) + +const ( + batchReadRowSize = 32 + + // if a parquet if small than this threshold, parquet will load the whole file in a byte slice to + // optimize the read performance + smallParquetFileThreshold = 256 * 1024 * 1024 +) + +type ParquetParser struct { + Reader *preader.ParquetReader + columns []string + columnMetas []*parquet.SchemaElement + rows []interface{} + readRows int64 + curStart int64 + curIndex int + lastRow Row + logger log.Logger +} + +// readerWrapper is a used for implement `source.ParquetFile` +type readerWrapper struct { + ReadSeekCloser + store storage.ExternalStorage + ctx context.Context + // current file path + path string +} + +func (r *readerWrapper) Write(p []byte) (n int, err error) { + return 0, errors.New("unsupported operation") +} + +func (r *readerWrapper) Open(name string) (source.ParquetFile, error) { + if len(name) == 0 { + name = r.path + } + reader, err := r.store.Open(r.ctx, name) + if err != nil { + return nil, errors.Trace(err) + } + return &readerWrapper{ + ReadSeekCloser: reader, + store: r.store, + ctx: r.ctx, + path: name, + }, nil +} + +func (r *readerWrapper) Create(name string) (source.ParquetFile, error) { + return nil, errors.New("unsupported operation") +} + +// bytesReaderWrapper is a wrapper of bytes.Reader used for implement `source.ParquetFile` +type bytesReaderWrapper struct { + *bytes.Reader + rawBytes []byte + // current file path + path string +} + +func (r *bytesReaderWrapper) Close() error { + return nil +} + +func (r *bytesReaderWrapper) Create(name string) (source.ParquetFile, error) { + return nil, errors.New("unsupported operation") +} + +func (r *bytesReaderWrapper) Write(p []byte) (n int, err error) { + return 0, errors.New("unsupported operation") +} + +func (r *bytesReaderWrapper) Open(name string) (source.ParquetFile, error) { + if len(name) > 0 && name != r.path { + panic(fmt.Sprintf("Open with a different name is not supported! current: '%s', new: '%s'", r.path, name)) + } + return &bytesReaderWrapper{ + Reader: bytes.NewReader(r.rawBytes), + rawBytes: r.rawBytes, + path: r.path, + }, nil +} + +func OpenParquetReader( + ctx context.Context, + store storage.ExternalStorage, + path string, + size int64, +) (source.ParquetFile, error) { + if size <= smallParquetFileThreshold { + fileBytes, err := store.ReadFile(ctx, path) + if err != nil { + return nil, err + } + return &bytesReaderWrapper{ + Reader: bytes.NewReader(fileBytes), + rawBytes: fileBytes, + path: path, + }, nil + } + + r, err := store.Open(ctx, path) + if err != nil { + return nil, err + } + return &readerWrapper{ + ReadSeekCloser: r, + store: store, + ctx: ctx, + path: path, + }, nil +} + +// a special func to fetch parquet file row count fast. +func ReadParquetFileRowCount( + ctx context.Context, + store storage.ExternalStorage, + r storage.ReadSeekCloser, + path string, +) (int64, error) { + wrapper := &readerWrapper{ + ReadSeekCloser: r, + store: store, + ctx: ctx, + path: path, + } + var err error + res := new(preader.ParquetReader) + res.NP = 1 + res.PFile = wrapper + if err = res.ReadFooter(); err != nil { + return 0, err + } + numRows := res.Footer.NumRows + if err = wrapper.Close(); err != nil { + return 0, err + } + return numRows, nil +} + +func NewParquetParser( + ctx context.Context, + store storage.ExternalStorage, + r storage.ReadSeekCloser, + path string, +) (*ParquetParser, error) { + // check to avoid wrapping twice + wrapper, ok := r.(source.ParquetFile) + if !ok { + wrapper = &readerWrapper{ + ReadSeekCloser: r, + store: store, + ctx: ctx, + path: path, + } + } + + // FIXME: need to bench what the best value for the concurrent reader number + reader, err := preader.NewParquetReader(wrapper, nil, 2) + if err != nil { + return nil, errors.Trace(err) + } + + columns := make([]string, 0, len(reader.Footer.Schema)-1) + columnMetas := make([]*parquet.SchemaElement, 0, len(reader.Footer.Schema)-1) + for _, c := range reader.SchemaHandler.SchemaElements { + if c.GetNumChildren() == 0 { + // NOTE: the SchemaElement.Name is capitalized, SchemaHandler.Infos.ExName is the raw column name + // though in this context, there is no difference between these two fields + columns = append(columns, strings.ToLower(c.Name)) + columnMetas = append(columnMetas, c) + } + } + + return &ParquetParser{ + Reader: reader, + columns: columns, + columnMetas: columnMetas, + logger: log.L(), + }, nil +} + +// Pos returns the currently row number of the parquet file +func (pp *ParquetParser) Pos() (pos int64, rowID int64) { + return pp.curStart + int64(pp.curIndex), pp.lastRow.RowID +} + +func (pp *ParquetParser) SetPos(pos int64, rowID int64) error { + if pos < pp.curStart { + panic("don't support seek back yet") + } + pp.lastRow.RowID = rowID + + if pos < pp.curStart+int64(len(pp.rows)) { + pp.curIndex = int(pos - pp.curStart) + pp.readRows = pos + return nil + } + + if pos > pp.curStart+int64(len(pp.rows)) { + if err := pp.Reader.SkipRows(pos - pp.curStart - int64(len(pp.rows))); err != nil { + return errors.Trace(err) + } + } + pp.curStart = pos + pp.readRows = pos + pp.curIndex = 0 + if len(pp.rows) > 0 { + pp.rows = pp.rows[:0] + } + + return nil +} + +func (pp *ParquetParser) Close() error { + pp.Reader.ReadStop() + return pp.Reader.PFile.Close() +} + +func (pp *ParquetParser) ReadRow() error { + pp.lastRow.RowID++ + if pp.curIndex >= len(pp.rows) { + if pp.readRows >= pp.Reader.GetNumRows() { + return io.EOF + } + count := batchReadRowSize + if pp.Reader.GetNumRows()-pp.readRows < int64(count) { + count = int(pp.Reader.GetNumRows() - pp.readRows) + } + + var err error + pp.rows, err = pp.Reader.ReadByNumber(count) + if err != nil { + return errors.Trace(err) + } + pp.curStart = pp.readRows + pp.readRows += int64(len(pp.rows)) + pp.curIndex = 0 + } + + row := pp.rows[pp.curIndex] + pp.curIndex++ + + v := reflect.ValueOf(row) + length := v.NumField() + if cap(pp.lastRow.Row) < length { + pp.lastRow.Row = make([]types.Datum, length) + } else { + pp.lastRow.Row = pp.lastRow.Row[:length] + } + for i := 0; i < length; i++ { + setDatumValue(&pp.lastRow.Row[i], v.Field(i), pp.columnMetas[i]) + } + return nil +} + +// convert a parquet value to Datum +// +// See: https://github.com/apache/parquet-format/blob/master/LogicalTypes.md +func setDatumValue(d *types.Datum, v reflect.Value, meta *parquet.SchemaElement) { + switch v.Kind() { + case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + d.SetUint64(v.Uint()) + case reflect.Int8, reflect.Int16: + d.SetInt64(v.Int()) + case reflect.Int32, reflect.Int64: + setDatumByInt(d, v.Int(), meta) + case reflect.String: + d.SetString(v.String(), "") + case reflect.Float32, reflect.Float64: + d.SetFloat64(v.Float()) + case reflect.Ptr: + if v.IsNil() { + d.SetNull() + } else { + setDatumValue(d, v.Elem(), meta) + } + default: + log.L().Fatal("unknown value", zap.Stringer("kind", v.Kind()), + zap.String("type", v.Type().Name()), zap.Reflect("value", v.Interface())) + } +} + +// when the value type is int32/int64, convert to value to target logical type in tidb +func setDatumByInt(d *types.Datum, v int64, meta *parquet.SchemaElement) { + if meta.ConvertedType == nil { + d.SetInt64(v) + return + } + switch *meta.ConvertedType { + // decimal + case parquet.ConvertedType_DECIMAL: + minLen := *meta.Scale + 1 + if v < 0 { + minLen++ + } + val := fmt.Sprintf("%0*d", minLen, v) + dotIndex := len(val) - int(*meta.Scale) + d.SetString(val[:dotIndex]+"."+val[dotIndex:], "") + case parquet.ConvertedType_DATE: + dateStr := time.Unix(v*86400, 0).Format("2006-01-02") + d.SetString(dateStr, "") + // convert all timestamp types (datetime/timestamp) to string + case parquet.ConvertedType_TIMESTAMP_MICROS: + dateStr := time.Unix(v/1e6, (v%1e6)*1e3).Format("2006-01-02 15:04:05.999") + d.SetString(dateStr, "") + case parquet.ConvertedType_TIMESTAMP_MILLIS: + dateStr := time.Unix(v/1e3, (v%1e3)*1e6).Format("2006-01-02 15:04:05.999") + d.SetString(dateStr, "") + // covert time types to string + case parquet.ConvertedType_TIME_MILLIS, parquet.ConvertedType_TIME_MICROS: + if *meta.ConvertedType == parquet.ConvertedType_TIME_MICROS { + v /= 1e3 + } + millis := v % 1e3 + v /= 1e3 + sec := v % 60 + v /= 60 + min := v % 60 + v /= 60 + d.SetString(fmt.Sprintf("%d:%d:%d.%3d", v, min, sec, millis), "") + default: + d.SetInt64(v) + } +} + +func (pp *ParquetParser) LastRow() Row { + return pp.lastRow +} + +func (pp *ParquetParser) RecycleRow(row Row) { +} + +// Columns returns the _lower-case_ column names corresponding to values in +// the LastRow. +func (pp *ParquetParser) Columns() []string { + return pp.columns +} + +// SetColumns set restored column names to parser +func (pp *ParquetParser) SetColumns(cols []string) { + // just do nothing +} + +func (pp *ParquetParser) SetLogger(l log.Logger) { + pp.logger = l +} diff --git a/pkg/lightning/mydump/parquet_parser_test.go b/pkg/lightning/mydump/parquet_parser_test.go new file mode 100644 index 000000000..d86136a65 --- /dev/null +++ b/pkg/lightning/mydump/parquet_parser_test.go @@ -0,0 +1,217 @@ +package mydump + +import ( + "context" + "io" + "path/filepath" + "strconv" + "time" + + . "github.com/pingcap/check" + "github.com/pingcap/tidb/types" + "github.com/xitongsys/parquet-go-source/local" + writer2 "github.com/xitongsys/parquet-go/writer" + + "github.com/pingcap/br/pkg/storage" +) + +type testParquetParserSuite struct{} + +var _ = Suite(testParquetParserSuite{}) + +func (s testParquetParserSuite) TestParquetParser(c *C) { + type Test struct { + S string `parquet:"name=sS, type=UTF8, encoding=PLAIN_DICTIONARY"` + A int32 `parquet:"name=a_A, type=INT32"` + } + + dir := c.MkDir() + // prepare data + name := "test123.parquet" + testPath := filepath.Join(dir, name) + pf, err := local.NewLocalFileWriter(testPath) + c.Assert(err, IsNil) + test := &Test{} + writer, err := writer2.NewParquetWriter(pf, test, 2) + c.Assert(err, IsNil) + + for i := 0; i < 100; i++ { + test.A = int32(i) + test.S = strconv.Itoa(i) + c.Assert(writer.Write(test), IsNil) + } + + c.Assert(writer.WriteStop(), IsNil) + c.Assert(pf.Close(), IsNil) + + store, err := storage.NewLocalStorage(dir) + c.Assert(err, IsNil) + r, err := store.Open(context.TODO(), name) + c.Assert(err, IsNil) + reader, err := NewParquetParser(context.TODO(), store, r, name) + c.Assert(err, IsNil) + defer reader.Close() + + c.Assert(reader.Columns(), DeepEquals, []string{"ss", "a_a"}) + + verifyRow := func(i int) { + c.Assert(reader.lastRow.RowID, Equals, int64(i+1)) + c.Assert(len(reader.lastRow.Row), Equals, 2) + c.Assert(reader.lastRow.Row[0], DeepEquals, types.NewCollationStringDatum(strconv.Itoa(i), "", 0)) + c.Assert(reader.lastRow.Row[1], DeepEquals, types.NewIntDatum(int64(i))) + } + + // test read some rows + for i := 0; i < 10; i++ { + c.Assert(reader.ReadRow(), IsNil) + verifyRow(i) + } + + // test set pos to pos < curpos + batchReadRowSize + c.Assert(reader.SetPos(15, 15), IsNil) + c.Assert(reader.ReadRow(), IsNil) + verifyRow(15) + + // test set pos to pos > curpos + batchReadRowSize + c.Assert(reader.SetPos(80, 80), IsNil) + for i := 80; i < 100; i++ { + c.Assert(reader.ReadRow(), IsNil) + verifyRow(i) + } + + c.Assert(reader.ReadRow(), Equals, io.EOF) +} + +func (s testParquetParserSuite) TestParquetVariousTypes(c *C) { + // those deprecated TIME/TIMESTAMP types depend on the local timezone! + prevTZ := time.Local + time.Local = time.FixedZone("UTC+8", 8*60*60) + defer func() { + time.Local = prevTZ + }() + + type Test struct { + Date int32 `parquet:"name=date, type=DATE"` + TimeMillis int32 `parquet:"name=timemillis, type=TIME_MILLIS"` + TimeMicros int64 `parquet:"name=timemicros, type=TIME_MICROS"` + TimestampMillis int64 `parquet:"name=timestampmillis, type=TIMESTAMP_MILLIS"` + TimestampMicros int64 `parquet:"name=timestampmicros, type=TIMESTAMP_MICROS"` + + Decimal1 int32 `parquet:"name=decimal1, type=DECIMAL, scale=2, precision=9, basetype=INT32"` + Decimal2 int32 `parquet:"name=decimal2, type=DECIMAL, scale=4, precision=4, basetype=INT32"` + Decimal3 int64 `parquet:"name=decimal3, type=DECIMAL, scale=2, precision=18, basetype=INT64"` + Decimal4 string `parquet:"name=decimal4, type=DECIMAL, scale=2, precision=10, basetype=FIXED_LEN_BYTE_ARRAY, length=12"` + Decimal5 string `parquet:"name=decimal5, type=DECIMAL, scale=2, precision=20, basetype=BYTE_ARRAY"` + Decimal6 int32 `parquet:"name=decimal6, type=DECIMAL, scale=4, precision=4, basetype=INT32"` + } + + dir := c.MkDir() + // prepare data + name := "test123.parquet" + testPath := filepath.Join(dir, name) + pf, err := local.NewLocalFileWriter(testPath) + c.Assert(err, IsNil) + test := &Test{} + writer, err := writer2.NewParquetWriter(pf, test, 2) + c.Assert(err, IsNil) + + v := &Test{ + Date: 18564, // 2020-10-29 + TimeMillis: 62775123, // 17:26:15.123 (note all time are in UTC+8!) + TimeMicros: 62775123000, // 17:26:15.123 + TimestampMillis: 1603963672356, // 2020-10-29T17:27:52.356 + TimestampMicros: 1603963672356956, // 2020-10-29T17:27:52.356956 + Decimal1: -12345678, // -123456.78 + Decimal2: 456, // 0.0456 + Decimal3: 123456789012345678, // 1234567890123456.78 + Decimal4: "-12345678.09", + Decimal5: "-1234567890123456.78", + Decimal6: -1, // -0.0001 + } + c.Assert(writer.Write(v), IsNil) + c.Assert(writer.WriteStop(), IsNil) + c.Assert(pf.Close(), IsNil) + + store, err := storage.NewLocalStorage(dir) + c.Assert(err, IsNil) + r, err := store.Open(context.TODO(), name) + c.Assert(err, IsNil) + reader, err := NewParquetParser(context.TODO(), store, r, name) + c.Assert(err, IsNil) + defer reader.Close() + + c.Assert(len(reader.columns), Equals, 11) + + c.Assert(reader.ReadRow(), IsNil) + c.Assert(reader.lastRow.Row, DeepEquals, []types.Datum{ + types.NewCollationStringDatum("2020-10-29", "", 0), + types.NewCollationStringDatum("17:26:15.123", "", 0), + types.NewCollationStringDatum("17:26:15.123", "", 0), + types.NewCollationStringDatum("2020-10-29 17:27:52.356", "", 0), + types.NewCollationStringDatum("2020-10-29 17:27:52.356", "", 0), + types.NewCollationStringDatum("-123456.78", "", 0), + types.NewCollationStringDatum("0.0456", "", 0), + types.NewCollationStringDatum("1234567890123456.78", "", 0), + types.NewCollationStringDatum("-12345678.09", "", 0), + types.NewCollationStringDatum("-1234567890123456.78", "", 0), + types.NewCollationStringDatum("-0.0001", "", 0), + }) + + type TestDecimal struct { + Decimal1 int32 `parquet:"name=decimal1, type=DECIMAL, scale=3, precision=5, basetype=INT32"` + DecimalRef *int32 `parquet:"name=decimal2, type=DECIMAL, scale=3, precision=5, basetype=INT32"` + } + + cases := [][]interface{}{ + {int32(0), "0.000"}, + {int32(1000), "1.000"}, + {int32(-1000), "-1.000"}, + {int32(999), "0.999"}, + {int32(-999), "-0.999"}, + {int32(1), "0.001"}, + {int32(-1), "-0.001"}, + } + + fileName := "test.02.parquet" + testPath = filepath.Join(dir, fileName) + pf, err = local.NewLocalFileWriter(testPath) + td := &TestDecimal{} + c.Assert(err, IsNil) + writer, err = writer2.NewParquetWriter(pf, td, 2) + c.Assert(err, IsNil) + for i, testCase := range cases { + val := testCase[0].(int32) + td.Decimal1 = val + if i%2 == 0 { + td.DecimalRef = &val + } else { + td.DecimalRef = nil + } + c.Assert(writer.Write(td), IsNil) + } + c.Assert(writer.WriteStop(), IsNil) + c.Assert(pf.Close(), IsNil) + + r, err = store.Open(context.TODO(), fileName) + c.Assert(err, IsNil) + reader, err = NewParquetParser(context.TODO(), store, r, fileName) + c.Assert(err, IsNil) + defer reader.Close() + + for i, testCase := range cases { + c.Assert(reader.ReadRow(), IsNil) + vals := []types.Datum{types.NewCollationStringDatum(testCase[1].(string), "", 0)} + if i%2 == 0 { + vals = append(vals, vals[0]) + } else { + vals = append(vals, types.Datum{}) + } + // because we always reuse the datums in reader.lastRow.Row, so we can't directly + // compare will `DeepEqual` here + c.Assert(len(reader.lastRow.Row), Equals, len(vals)) + for i, val := range vals { + c.Assert(reader.lastRow.Row[i].Kind(), Equals, val.Kind()) + c.Assert(reader.lastRow.Row[i].GetValue(), Equals, val.GetValue()) + } + } +} diff --git a/pkg/lightning/mydump/parser.go b/pkg/lightning/mydump/parser.go new file mode 100644 index 000000000..e08d3e3ac --- /dev/null +++ b/pkg/lightning/mydump/parser.go @@ -0,0 +1,571 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package mydump + +import ( + "bytes" + "fmt" + "io" + "regexp" + "strconv" + "strings" + "sync" + "time" + + "github.com/pingcap/errors" + "github.com/pingcap/parser/mysql" + "github.com/pingcap/tidb/types" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/pingcap/br/pkg/lightning/config" + "github.com/pingcap/br/pkg/lightning/log" + "github.com/pingcap/br/pkg/lightning/metric" + "github.com/pingcap/br/pkg/lightning/worker" +) + +type blockParser struct { + // states for the lexer + reader PooledReader + buf []byte + blockBuf []byte + isLastChunk bool + + // The list of column names of the last INSERT statement. + columns []string + + rowPool *sync.Pool + lastRow Row + // Current file offset. + pos int64 + + // cache + remainBuf *bytes.Buffer + appendBuf *bytes.Buffer + + // the Logger associated with this parser for reporting failure + Logger log.Logger +} + +func makeBlockParser(reader ReadSeekCloser, blockBufSize int64, ioWorkers *worker.Pool) blockParser { + return blockParser{ + reader: MakePooledReader(reader, ioWorkers), + blockBuf: make([]byte, blockBufSize*config.BufferSizeScale), + remainBuf: &bytes.Buffer{}, + appendBuf: &bytes.Buffer{}, + Logger: log.L(), + rowPool: &sync.Pool{ + New: func() interface{} { + return make([]types.Datum, 0, 16) + }, + }, + } +} + +// ChunkParser is a parser of the data files (the file containing only INSERT +// statements). +type ChunkParser struct { + blockParser + + escFlavor backslashEscapeFlavor +} + +// Chunk represents a portion of the data file. +type Chunk struct { + Offset int64 + EndOffset int64 + PrevRowIDMax int64 + RowIDMax int64 + Columns []string +} + +// Row is the content of a row. +type Row struct { + RowID int64 + Row []types.Datum +} + +// MarshalLogArray implements the zapcore.ArrayMarshaler interface +func (row Row) MarshalLogArray(encoder zapcore.ArrayEncoder) error { + for _, r := range row.Row { + encoder.AppendString(r.String()) + } + return nil +} + +type backslashEscapeFlavor uint8 + +const ( + backslashEscapeFlavorNone backslashEscapeFlavor = iota + backslashEscapeFlavorMySQL + backslashEscapeFlavorMySQLWithNull +) + +type Parser interface { + Pos() (pos int64, rowID int64) + SetPos(pos int64, rowID int64) error + Close() error + ReadRow() error + LastRow() Row + RecycleRow(row Row) + + // Columns returns the _lower-case_ column names corresponding to values in + // the LastRow. + Columns() []string + // SetColumns set restored column names to parser + SetColumns([]string) + + SetLogger(log.Logger) +} + +// NewChunkParser creates a new parser which can read chunks out of a file. +func NewChunkParser( + sqlMode mysql.SQLMode, + reader ReadSeekCloser, + blockBufSize int64, + ioWorkers *worker.Pool, +) *ChunkParser { + escFlavor := backslashEscapeFlavorMySQL + if sqlMode.HasNoBackslashEscapesMode() { + escFlavor = backslashEscapeFlavorNone + } + + return &ChunkParser{ + blockParser: makeBlockParser(reader, blockBufSize, ioWorkers), + escFlavor: escFlavor, + } +} + +// SetPos changes the reported position and row ID. +func (parser *blockParser) SetPos(pos int64, rowID int64) error { + p, err := parser.reader.Seek(pos, io.SeekStart) + if err != nil { + return errors.Trace(err) + } + if p != pos { + return errors.Errorf("set pos failed, required position: %d, got: %d", pos, p) + } + parser.pos = pos + parser.lastRow.RowID = rowID + return nil +} + +// Pos returns the current file offset. +func (parser *blockParser) Pos() (int64, int64) { + return parser.pos, parser.lastRow.RowID +} + +func (parser *blockParser) Close() error { + return parser.reader.Close() +} + +func (parser *blockParser) Columns() []string { + return parser.columns +} + +func (parser *blockParser) SetColumns(columns []string) { + parser.columns = columns +} + +func (parser *blockParser) logSyntaxError() { + content := parser.buf + if len(content) > 256 { + content = content[:256] + } + parser.Logger.Error("syntax error", + zap.Int64("pos", parser.pos), + zap.ByteString("content", content), + ) +} + +func (parser *blockParser) SetLogger(logger log.Logger) { + parser.Logger = logger +} + +type token byte + +const ( + tokNil token = iota + tokRowBegin + tokRowEnd + tokValues + tokNull + tokTrue + tokFalse + tokHexString + tokBinString + tokInteger + tokSingleQuoted + tokDoubleQuoted + tokBackQuoted + tokUnquoted +) + +var tokenDescriptions = [...]string{ + tokNil: "", + tokRowBegin: "RowBegin", + tokRowEnd: "RowEnd", + tokValues: "Values", + tokNull: "Null", + tokTrue: "True", + tokFalse: "False", + tokHexString: "HexString", + tokBinString: "BinString", + tokInteger: "Integer", + tokSingleQuoted: "SingleQuoted", + tokDoubleQuoted: "DoubleQuoted", + tokBackQuoted: "BackQuoted", + tokUnquoted: "Unquoted", +} + +// String implements the fmt.Stringer interface +// +// Mainly used for debugging a token. +func (tok token) String() string { + t := int(tok) + if t >= 0 && t < len(tokenDescriptions) { + if description := tokenDescriptions[t]; description != "" { + return description + } + } + return fmt.Sprintf("", t) +} + +func (parser *blockParser) readBlock() error { + startTime := time.Now() + + n, err := parser.reader.ReadFull(parser.blockBuf) + + switch err { + case io.ErrUnexpectedEOF, io.EOF: + parser.isLastChunk = true + fallthrough + case nil: + // `parser.buf` reference to `appendBuf.Bytes`, so should use remainBuf to + // hold the `parser.buf` rest data to prevent slice overlap + parser.remainBuf.Reset() + parser.remainBuf.Write(parser.buf) + parser.appendBuf.Reset() + parser.appendBuf.Write(parser.remainBuf.Bytes()) + parser.appendBuf.Write(parser.blockBuf[:n]) + parser.buf = parser.appendBuf.Bytes() + metric.ChunkParserReadBlockSecondsHistogram.Observe(time.Since(startTime).Seconds()) + return nil + default: + return errors.Trace(err) + } +} + +var unescapeRegexp = regexp.MustCompile(`(?s)\\.`) + +func unescape( + input string, + delim string, + escFlavor backslashEscapeFlavor, +) string { + if len(delim) > 0 { + delim2 := delim + delim + if strings.Index(input, delim2) != -1 { + input = strings.Replace(input, delim2, delim, -1) + } + } + if escFlavor != backslashEscapeFlavorNone && strings.IndexByte(input, '\\') != -1 { + input = unescapeRegexp.ReplaceAllStringFunc(input, func(substr string) string { + switch substr[1] { + case '0': + return "\x00" + case 'b': + return "\b" + case 'n': + return "\n" + case 'r': + return "\r" + case 't': + return "\t" + case 'Z': + return "\x1a" + default: + return substr[1:] + } + }) + } + return input +} + +func (parser *ChunkParser) unescapeString(input string) string { + if len(input) >= 2 { + switch input[0] { + case '\'', '"': + return unescape(input[1:len(input)-1], input[:1], parser.escFlavor) + case '`': + return unescape(input[1:len(input)-1], "`", backslashEscapeFlavorNone) + } + } + return input +} + +// ReadRow reads a row from the datafile. +func (parser *ChunkParser) ReadRow() error { + // This parser will recognize contents like: + // + // `tableName` (...) VALUES (...) (...) (...) + // + // Keywords like INSERT, INTO and separators like ',' and ';' are treated + // like comments and ignored. Therefore, this parser will accept some + // nonsense input. The advantage is the parser becomes extremely simple, + // suitable for us where we just want to quickly and accurately split the + // file apart, not to validate the content. + + type state byte + + const ( + // the state after "INSERT INTO" before the column names or "VALUES" + stateTableName state = iota + + // the state while reading the column names + stateColumns + + // the state after reading "VALUES" + stateValues + + // the state while reading row values + stateRow + ) + + // Dry-run sample of the state machine, first row: + // + // Input Token State + // ~~~~~ ~~~~~ ~~~~~ + // + // stateValues + // INSERT + // INTO + // `tableName` tokBackQuoted + // stateTableName (reset columns) + // ( tokRowBegin + // stateColumns + // `a` tokBackQuoted + // stateColumns (append column) + // , + // `b` tokBackQuoted + // stateColumns (append column) + // ) tokRowEnd + // stateValues + // VALUES + // stateValues (no-op) + // ( tokRowBegin + // stateRow (reset row) + // 1 tokInteger + // stateRow (append value) + // , + // 2 tokInteger + // stateRow (append value) + // ) tokRowEnd + // return + // + // + // Second row: + // + // Input Token State + // ~~~~~ ~~~~~ ~~~~~ + // + // stateValues + // , + // ( tokRowBegin + // stateRow (reset row) + // 3 tokInteger + // stateRow (append value) + // ) tokRowEnd + // return + // + // Third row: + // + // Input Token State + // ~~~~~ ~~~~~ ~~~~~ + // + // ; + // INSERT + // INTO + // `database` tokBackQuoted + // stateTableName (reset columns) + // . + // `tableName` tokBackQuoted + // stateTableName (no-op) + // VALUES + // stateValues + // ( tokRowBegin + // stateRow (reset row) + // 4 tokInteger + // stateRow (append value) + // ) tokRowEnd + // return + + row := &parser.lastRow + st := stateValues + + for { + tok, content, err := parser.lex() + if err != nil { + if err == io.EOF && st != stateValues { + return errors.Errorf("syntax error: premature EOF at offset %d", parser.pos) + } + return errors.Trace(err) + } + switch st { + case stateTableName: + switch tok { + case tokRowBegin: + st = stateColumns + case tokValues: + st = stateValues + case tokUnquoted, tokDoubleQuoted, tokBackQuoted: + default: + return errors.Errorf( + "syntax error: unexpected %s (%s) at offset %d, expecting %s", + tok, content, parser.pos, "table name", + ) + } + case stateColumns: + switch tok { + case tokRowEnd: + st = stateValues + case tokUnquoted, tokDoubleQuoted, tokBackQuoted: + columnName := strings.ToLower(parser.unescapeString(string(content))) + parser.columns = append(parser.columns, columnName) + default: + return errors.Errorf( + "syntax error: unexpected %s (%s) at offset %d, expecting %s", + tok, content, parser.pos, "column list", + ) + } + case stateValues: + switch tok { + case tokRowBegin: + row.RowID++ + row.Row = parser.acquireDatumSlice() + st = stateRow + case tokUnquoted, tokDoubleQuoted, tokBackQuoted: + parser.columns = nil + st = stateTableName + case tokValues: + default: + return errors.Errorf( + "syntax error: unexpected %s (%s) at offset %d, expecting %s", + tok, content, parser.pos, "start of row", + ) + } + case stateRow: + var value types.Datum + switch tok { + case tokRowEnd: + return nil + case tokNull: + value.SetNull() + case tokTrue: + value.SetInt64(1) + case tokFalse: + value.SetInt64(0) + case tokInteger: + c := string(content) + if strings.HasPrefix(c, "-") { + i, err := strconv.ParseInt(c, 10, 64) + if err == nil { + value.SetInt64(i) + break + } + } else { + u, err := strconv.ParseUint(c, 10, 64) + if err == nil { + value.SetUint64(u) + break + } + } + // if the integer is too long, fallback to treating it as a + // string (all types that treats integer specially like BIT + // can't handle integers more than 64 bits anyway) + fallthrough + case tokUnquoted, tokSingleQuoted, tokDoubleQuoted: + value.SetString(parser.unescapeString(string(content)), "utf8mb4_bin") + case tokHexString: + hexLit, err := types.ParseHexStr(string(content)) + if err != nil { + return err + } + value.SetBinaryLiteral(hexLit) + case tokBinString: + binLit, err := types.ParseBitStr(string(content)) + if err != nil { + return err + } + value.SetBinaryLiteral(binLit) + default: + return errors.Errorf( + "syntax error: unexpected %s (%s) at offset %d, expecting %s", + tok, content, parser.pos, "data literal", + ) + } + row.Row = append(row.Row, value) + } + } +} + +// LastRow is the copy of the row parsed by the last call to ReadRow(). +func (parser *blockParser) LastRow() Row { + return parser.lastRow +} + +// RecycleRow places the row object back into the allocation pool. +func (parser *blockParser) RecycleRow(row Row) { + parser.rowPool.Put(row.Row[:0]) +} + +// acquireDatumSlice allocates an empty []types.Datum +func (parser *blockParser) acquireDatumSlice() []types.Datum { + return parser.rowPool.Get().([]types.Datum) +} + +// ReadChunks parses the entire file and splits it into continuous chunks of +// size >= minSize. +func ReadChunks(parser Parser, minSize int64) ([]Chunk, error) { + var chunks []Chunk + + pos, lastRowID := parser.Pos() + cur := Chunk{ + Offset: pos, + EndOffset: pos, + PrevRowIDMax: lastRowID, + RowIDMax: lastRowID, + } + + for { + switch err := parser.ReadRow(); errors.Cause(err) { + case nil: + cur.EndOffset, cur.RowIDMax = parser.Pos() + if cur.EndOffset-cur.Offset >= minSize { + chunks = append(chunks, cur) + cur.Offset = cur.EndOffset + cur.PrevRowIDMax = cur.RowIDMax + } + + case io.EOF: + if cur.Offset < cur.EndOffset { + chunks = append(chunks, cur) + } + return chunks, nil + + default: + return nil, errors.Trace(err) + } + } +} diff --git a/pkg/lightning/mydump/parser.rl b/pkg/lightning/mydump/parser.rl new file mode 100644 index 000000000..1ad090dd5 --- /dev/null +++ b/pkg/lightning/mydump/parser.rl @@ -0,0 +1,187 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +// Please edit `parser.rl` if you want to modify this file. To generate +// `parser_generated.go`, please execute +// +// ```sh +// make data_parsers +// ``` + +package mydump + +import ( + "io" + + "github.com/pingcap/errors" +) + +%%{ +#` + +# This is a ragel parser to quickly scan through a data source file consisting +# of INSERT statements only. You may find detailed syntax explanation on its +# website . + +machine chunk_parser; + +# We treat all unimportant patterns as "comments". This include: +# - Real SQL comments `/* ... */` and `-- ...` +# - Whitespace +# - Separators `,` and `;` +# - The keyword `INTO` (suffix `i` means case-insensitive). +# - The parts of the function `CONVERT(` and `USING UTF8MB4)` +# (to strip the unnecessary detail from mydumper JSON output) +block_comment = '/*' any* :>> '*/'; +line_comment = /--[^\r\n]*/; +comment = + block_comment | + line_comment | + space | + [,;] | + 'convert('i | + 'using utf8mb4)'i; + +# The patterns parse quoted strings. +bs = '\\' when { parser.escFlavor != backslashEscapeFlavorNone }; + +single_quoted = "'" (^"'" | bs any | "''")** "'"; +double_quoted = '"' (^'"' | bs any | '""')** '"'; +back_quoted = '`' (^'`' | '``')* '`'; +unquoted = ^([,;()'"`/*] | space)+; + +integer = '-'? [0-9]+; +hex_string = '0x' [0-9a-fA-F]+ | "x'"i [0-9a-fA-F]* "'"; +bin_string = '0b' [01]+ | "b'"i [01]* "'"; + +main := |* + comment; + + '(' => { + consumedToken = tokRowBegin + fbreak; + }; + + ')' => { + consumedToken = tokRowEnd + fbreak; + }; + + 'values'i => { + consumedToken = tokValues + fbreak; + }; + + 'null'i => { + consumedToken = tokNull + fbreak; + }; + + 'true'i => { + consumedToken = tokTrue + fbreak; + }; + + 'false'i => { + consumedToken = tokFalse + fbreak; + }; + + integer => { + consumedToken = tokInteger + fbreak; + }; + + hex_string => { + consumedToken = tokHexString + fbreak; + }; + + bin_string => { + consumedToken = tokBinString + fbreak; + }; + + single_quoted => { + consumedToken = tokSingleQuoted + fbreak; + }; + + double_quoted => { + consumedToken = tokDoubleQuoted + fbreak; + }; + + back_quoted => { + consumedToken = tokBackQuoted + fbreak; + }; + + unquoted => { + consumedToken = tokUnquoted + fbreak; + }; +*|; + +#` +}%% + +%% write data; + +func (parser *ChunkParser) lex() (token, []byte, error) { + var cs, ts, te, act, p int + %% write init; + + for { + data := parser.buf + consumedToken := tokNil + pe := len(data) + eof := -1 + if parser.isLastChunk { + eof = pe + } + + %% write exec; + + if cs == %%{ write error; }%% { + parser.logSyntaxError() + return tokNil, nil, errors.New("syntax error") + } + + if consumedToken != tokNil { + result := data[ts:te] + parser.buf = data[te:] + parser.pos += int64(te) + return consumedToken, result, nil + } + + if parser.isLastChunk { + if te == eof { + return tokNil, nil, io.EOF + } else { + return tokNil, nil, errors.New("syntax error: unexpected EOF") + } + } + + parser.buf = parser.buf[ts:] + parser.pos += int64(ts) + p -= ts + te -= ts + ts = 0 + if err := parser.readBlock(); err != nil { + return tokNil, nil, errors.Trace(err) + } + } + + return tokNil, nil, nil +} diff --git a/pkg/lightning/mydump/parser_generated.go b/pkg/lightning/mydump/parser_generated.go new file mode 100644 index 000000000..a53af0eb9 --- /dev/null +++ b/pkg/lightning/mydump/parser_generated.go @@ -0,0 +1,2515 @@ +// Code generated by ragel DO NOT EDIT. + +//.... lightning/mydump/parser.rl:1 +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +// Please edit `parser.rl` if you want to modify this file. To generate +// `parser_generated.go`, please execute +// +// ```sh +// make data_parsers +// ``` + +package mydump + +import ( + "io" + + "github.com/pingcap/errors" +) + +//.... lightning/mydump/parser.rl:137 + +//.... tmp_parser.go:37 +const ( + chunk_parser_start int = 21 + chunk_parser_first_final int = 21 + chunk_parser_error int = 0 +) + +const chunk_parser_en_main int = 21 + +//.... lightning/mydump/parser.rl:140 + +func (parser *ChunkParser) lex() (token, []byte, error) { + var cs, ts, te, act, p int + + //.... tmp_parser.go:50 + { + cs = chunk_parser_start + ts = 0 + te = 0 + act = 0 + } + + //.... lightning/mydump/parser.rl:144 + + for { + data := parser.buf + consumedToken := tokNil + pe := len(data) + eof := -1 + if parser.isLastChunk { + eof = pe + } + + //.... tmp_parser.go:70 + { + var _widec int16 + if p == pe { + goto _test_eof + } + switch cs { + case 21: + goto st_case_21 + case 22: + goto st_case_22 + case 1: + goto st_case_1 + case 2: + goto st_case_2 + case 23: + goto st_case_23 + case 3: + goto st_case_3 + case 0: + goto st_case_0 + case 4: + goto st_case_4 + case 5: + goto st_case_5 + case 24: + goto st_case_24 + case 6: + goto st_case_6 + case 25: + goto st_case_25 + case 26: + goto st_case_26 + case 27: + goto st_case_27 + case 28: + goto st_case_28 + case 7: + goto st_case_7 + case 8: + goto st_case_8 + case 9: + goto st_case_9 + case 29: + goto st_case_29 + case 30: + goto st_case_30 + case 31: + goto st_case_31 + case 32: + goto st_case_32 + case 10: + goto st_case_10 + case 33: + goto st_case_33 + case 34: + goto st_case_34 + case 35: + goto st_case_35 + case 36: + goto st_case_36 + case 37: + goto st_case_37 + case 38: + goto st_case_38 + case 39: + goto st_case_39 + case 40: + goto st_case_40 + case 41: + goto st_case_41 + case 42: + goto st_case_42 + case 43: + goto st_case_43 + case 44: + goto st_case_44 + case 45: + goto st_case_45 + case 46: + goto st_case_46 + case 47: + goto st_case_47 + case 48: + goto st_case_48 + case 49: + goto st_case_49 + case 50: + goto st_case_50 + case 51: + goto st_case_51 + case 52: + goto st_case_52 + case 53: + goto st_case_53 + case 54: + goto st_case_54 + case 11: + goto st_case_11 + case 12: + goto st_case_12 + case 13: + goto st_case_13 + case 14: + goto st_case_14 + case 15: + goto st_case_15 + case 16: + goto st_case_16 + case 17: + goto st_case_17 + case 18: + goto st_case_18 + case 55: + goto st_case_55 + case 56: + goto st_case_56 + case 57: + goto st_case_57 + case 58: + goto st_case_58 + case 59: + goto st_case_59 + case 60: + goto st_case_60 + case 19: + goto st_case_19 + case 20: + goto st_case_20 + case 61: + goto st_case_61 + } + goto st_out + tr4: + //.... NONE:1 + switch act { + case 0: + { + { + goto st0 + } + } + case 4: + { + p = (te) - 1 + + consumedToken = tokValues + { + p++ + cs = 21 + goto _out + } + } + case 5: + { + p = (te) - 1 + + consumedToken = tokNull + { + p++ + cs = 21 + goto _out + } + } + case 6: + { + p = (te) - 1 + + consumedToken = tokTrue + { + p++ + cs = 21 + goto _out + } + } + case 7: + { + p = (te) - 1 + + consumedToken = tokFalse + { + p++ + cs = 21 + goto _out + } + } + case 9: + { + p = (te) - 1 + + consumedToken = tokHexString + { + p++ + cs = 21 + goto _out + } + } + case 10: + { + p = (te) - 1 + + consumedToken = tokBinString + { + p++ + cs = 21 + goto _out + } + } + case 11: + { + p = (te) - 1 + + consumedToken = tokSingleQuoted + { + p++ + cs = 21 + goto _out + } + } + case 12: + { + p = (te) - 1 + + consumedToken = tokDoubleQuoted + { + p++ + cs = 21 + goto _out + } + } + case 13: + { + p = (te) - 1 + + consumedToken = tokBackQuoted + { + p++ + cs = 21 + goto _out + } + } + case 14: + { + p = (te) - 1 + + consumedToken = tokUnquoted + { + p++ + cs = 21 + goto _out + } + } + } + + goto st21 + tr10: + //.... lightning/mydump/parser.rl:68 + te = p + 1 + + goto st21 + tr11: + //.... lightning/mydump/parser.rl:130 + p = (te) - 1 + { + consumedToken = tokUnquoted + { + p++ + cs = 21 + goto _out + } + } + goto st21 + tr12: + //.... lightning/mydump/parser.rl:110 + te = p + 1 + { + consumedToken = tokBinString + { + p++ + cs = 21 + goto _out + } + } + goto st21 + tr21: + //.... lightning/mydump/parser.rl:105 + te = p + 1 + { + consumedToken = tokHexString + { + p++ + cs = 21 + goto _out + } + } + goto st21 + tr28: + //.... lightning/mydump/parser.rl:70 + te = p + 1 + { + consumedToken = tokRowBegin + { + p++ + cs = 21 + goto _out + } + } + goto st21 + tr29: + //.... lightning/mydump/parser.rl:75 + te = p + 1 + { + consumedToken = tokRowEnd + { + p++ + cs = 21 + goto _out + } + } + goto st21 + tr42: + //.... lightning/mydump/parser.rl:120 + te = p + p-- + { + consumedToken = tokDoubleQuoted + { + p++ + cs = 21 + goto _out + } + } + goto st21 + tr43: + //.... lightning/mydump/parser.rl:115 + te = p + p-- + { + consumedToken = tokSingleQuoted + { + p++ + cs = 21 + goto _out + } + } + goto st21 + tr44: + //.... lightning/mydump/parser.rl:130 + te = p + p-- + { + consumedToken = tokUnquoted + { + p++ + cs = 21 + goto _out + } + } + goto st21 + tr46: + //.... lightning/mydump/parser.rl:68 + te = p + p-- + + goto st21 + tr48: + //.... lightning/mydump/parser.rl:100 + te = p + p-- + { + consumedToken = tokInteger + { + p++ + cs = 21 + goto _out + } + } + goto st21 + tr79: + //.... lightning/mydump/parser.rl:125 + te = p + p-- + { + consumedToken = tokBackQuoted + { + p++ + cs = 21 + goto _out + } + } + goto st21 + st21: + //.... NONE:1 + ts = 0 + + //.... NONE:1 + act = 0 + + if p++; p == pe { + goto _test_eof21 + } + st_case_21: + //.... NONE:1 + ts = p + + //.... tmp_parser.go:381 + switch data[p] { + case 32: + goto tr10 + case 34: + goto st1 + case 39: + goto st4 + case 40: + goto tr28 + case 41: + goto tr29 + case 42: + goto st0 + case 44: + goto tr10 + case 45: + goto st25 + case 47: + goto st7 + case 48: + goto st29 + case 59: + goto tr10 + case 66: + goto tr34 + case 67: + goto st33 + case 70: + goto st40 + case 78: + goto st44 + case 84: + goto st47 + case 85: + goto st50 + case 86: + goto st55 + case 88: + goto tr41 + case 96: + goto st20 + case 98: + goto tr34 + case 99: + goto st33 + case 102: + goto st40 + case 110: + goto st44 + case 116: + goto st47 + case 117: + goto st50 + case 118: + goto st55 + case 120: + goto tr41 + } + switch { + case data[p] > 13: + if 49 <= data[p] && data[p] <= 57 { + goto st28 + } + case data[p] >= 9: + goto tr10 + } + goto tr25 + tr25: + //.... NONE:1 + te = p + 1 + + //.... lightning/mydump/parser.rl:130 + act = 14 + goto st22 + tr62: + //.... NONE:1 + te = p + 1 + + //.... lightning/mydump/parser.rl:95 + act = 7 + goto st22 + tr65: + //.... NONE:1 + te = p + 1 + + //.... lightning/mydump/parser.rl:85 + act = 5 + goto st22 + tr68: + //.... NONE:1 + te = p + 1 + + //.... lightning/mydump/parser.rl:90 + act = 6 + goto st22 + tr78: + //.... NONE:1 + te = p + 1 + + //.... lightning/mydump/parser.rl:80 + act = 4 + goto st22 + st22: + if p++; p == pe { + goto _test_eof22 + } + st_case_22: + //.... tmp_parser.go:489 + switch data[p] { + case 32: + goto tr4 + case 34: + goto tr4 + case 44: + goto tr4 + case 47: + goto tr4 + case 59: + goto tr4 + case 96: + goto tr4 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr4 + } + case data[p] >= 9: + goto tr4 + } + goto tr25 + st1: + if p++; p == pe { + goto _test_eof1 + } + st_case_1: + _widec = int16(data[p]) + if 92 <= data[p] && data[p] <= 92 { + _widec = 256 + (int16(data[p]) - 0) + if parser.escFlavor != backslashEscapeFlavorNone { + _widec += 256 + } + } + switch _widec { + case 34: + goto tr1 + case 348: + goto st2 + case 604: + goto st3 + } + switch { + case _widec > 91: + if 93 <= _widec { + goto st2 + } + default: + goto st2 + } + goto st0 + st2: + if p++; p == pe { + goto _test_eof2 + } + st_case_2: + _widec = int16(data[p]) + if 92 <= data[p] && data[p] <= 92 { + _widec = 256 + (int16(data[p]) - 0) + if parser.escFlavor != backslashEscapeFlavorNone { + _widec += 256 + } + } + switch _widec { + case 34: + goto tr1 + case 348: + goto st2 + case 604: + goto st3 + } + switch { + case _widec > 91: + if 93 <= _widec { + goto st2 + } + default: + goto st2 + } + goto tr4 + tr1: + //.... NONE:1 + te = p + 1 + + //.... lightning/mydump/parser.rl:120 + act = 12 + goto st23 + st23: + if p++; p == pe { + goto _test_eof23 + } + st_case_23: + //.... tmp_parser.go:583 + if data[p] == 34 { + goto st2 + } + goto tr42 + st3: + if p++; p == pe { + goto _test_eof3 + } + st_case_3: + _widec = int16(data[p]) + if 92 <= data[p] && data[p] <= 92 { + _widec = 256 + (int16(data[p]) - 0) + if parser.escFlavor != backslashEscapeFlavorNone { + _widec += 256 + } + } + switch _widec { + case 348: + goto st2 + case 604: + goto st2 + } + switch { + case _widec > 91: + if 93 <= _widec { + goto st2 + } + default: + goto st2 + } + goto tr4 + st_case_0: + st0: + cs = 0 + goto _out + st4: + if p++; p == pe { + goto _test_eof4 + } + st_case_4: + _widec = int16(data[p]) + if 92 <= data[p] && data[p] <= 92 { + _widec = 256 + (int16(data[p]) - 0) + if parser.escFlavor != backslashEscapeFlavorNone { + _widec += 256 + } + } + switch _widec { + case 39: + goto tr6 + case 348: + goto st5 + case 604: + goto st6 + } + switch { + case _widec > 91: + if 93 <= _widec { + goto st5 + } + default: + goto st5 + } + goto st0 + st5: + if p++; p == pe { + goto _test_eof5 + } + st_case_5: + _widec = int16(data[p]) + if 92 <= data[p] && data[p] <= 92 { + _widec = 256 + (int16(data[p]) - 0) + if parser.escFlavor != backslashEscapeFlavorNone { + _widec += 256 + } + } + switch _widec { + case 39: + goto tr6 + case 348: + goto st5 + case 604: + goto st6 + } + switch { + case _widec > 91: + if 93 <= _widec { + goto st5 + } + default: + goto st5 + } + goto tr4 + tr6: + //.... NONE:1 + te = p + 1 + + //.... lightning/mydump/parser.rl:115 + act = 11 + goto st24 + st24: + if p++; p == pe { + goto _test_eof24 + } + st_case_24: + //.... tmp_parser.go:689 + if data[p] == 39 { + goto st5 + } + goto tr43 + st6: + if p++; p == pe { + goto _test_eof6 + } + st_case_6: + _widec = int16(data[p]) + if 92 <= data[p] && data[p] <= 92 { + _widec = 256 + (int16(data[p]) - 0) + if parser.escFlavor != backslashEscapeFlavorNone { + _widec += 256 + } + } + switch _widec { + case 348: + goto st5 + case 604: + goto st5 + } + switch { + case _widec > 91: + if 93 <= _widec { + goto st5 + } + default: + goto st5 + } + goto tr4 + st25: + if p++; p == pe { + goto _test_eof25 + } + st_case_25: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 45: + goto st26 + case 47: + goto tr44 + case 59: + goto tr44 + case 96: + goto tr44 + } + switch { + case data[p] < 39: + if 9 <= data[p] && data[p] <= 13 { + goto tr44 + } + case data[p] > 42: + if 48 <= data[p] && data[p] <= 57 { + goto st28 + } + default: + goto tr44 + } + goto tr25 + st26: + if p++; p == pe { + goto _test_eof26 + } + st_case_26: + switch data[p] { + case 10: + goto tr46 + case 13: + goto tr46 + case 32: + goto st27 + case 34: + goto st27 + case 44: + goto st27 + case 47: + goto st27 + case 59: + goto st27 + case 96: + goto st27 + } + switch { + case data[p] > 12: + if 39 <= data[p] && data[p] <= 42 { + goto st27 + } + case data[p] >= 9: + goto st27 + } + goto st26 + st27: + if p++; p == pe { + goto _test_eof27 + } + st_case_27: + switch data[p] { + case 10: + goto tr46 + case 13: + goto tr46 + } + goto st27 + st28: + if p++; p == pe { + goto _test_eof28 + } + st_case_28: + switch data[p] { + case 32: + goto tr48 + case 34: + goto tr48 + case 44: + goto tr48 + case 47: + goto tr48 + case 59: + goto tr48 + case 96: + goto tr48 + } + switch { + case data[p] < 39: + if 9 <= data[p] && data[p] <= 13 { + goto tr48 + } + case data[p] > 42: + if 48 <= data[p] && data[p] <= 57 { + goto st28 + } + default: + goto tr48 + } + goto tr25 + st7: + if p++; p == pe { + goto _test_eof7 + } + st_case_7: + if data[p] == 42 { + goto st8 + } + goto st0 + st8: + if p++; p == pe { + goto _test_eof8 + } + st_case_8: + if data[p] == 42 { + goto st9 + } + goto st8 + st9: + if p++; p == pe { + goto _test_eof9 + } + st_case_9: + switch data[p] { + case 42: + goto st9 + case 47: + goto tr10 + } + goto st8 + st29: + if p++; p == pe { + goto _test_eof29 + } + st_case_29: + switch data[p] { + case 32: + goto tr48 + case 34: + goto tr48 + case 44: + goto tr48 + case 47: + goto tr48 + case 59: + goto tr48 + case 96: + goto tr48 + case 98: + goto tr49 + case 120: + goto tr50 + } + switch { + case data[p] < 39: + if 9 <= data[p] && data[p] <= 13 { + goto tr48 + } + case data[p] > 42: + if 48 <= data[p] && data[p] <= 57 { + goto st28 + } + default: + goto tr48 + } + goto tr25 + tr49: + //.... NONE:1 + te = p + 1 + + //.... lightning/mydump/parser.rl:130 + act = 14 + goto st30 + tr51: + //.... NONE:1 + te = p + 1 + + //.... lightning/mydump/parser.rl:110 + act = 10 + goto st30 + st30: + if p++; p == pe { + goto _test_eof30 + } + st_case_30: + //.... tmp_parser.go:916 + switch data[p] { + case 32: + goto tr4 + case 34: + goto tr4 + case 44: + goto tr4 + case 47: + goto tr4 + case 59: + goto tr4 + case 96: + goto tr4 + } + switch { + case data[p] < 39: + if 9 <= data[p] && data[p] <= 13 { + goto tr4 + } + case data[p] > 42: + if 48 <= data[p] && data[p] <= 49 { + goto tr51 + } + default: + goto tr4 + } + goto tr25 + tr50: + //.... NONE:1 + te = p + 1 + + //.... lightning/mydump/parser.rl:130 + act = 14 + goto st31 + tr52: + //.... NONE:1 + te = p + 1 + + //.... lightning/mydump/parser.rl:105 + act = 9 + goto st31 + st31: + if p++; p == pe { + goto _test_eof31 + } + st_case_31: + //.... tmp_parser.go:963 + switch data[p] { + case 32: + goto tr4 + case 34: + goto tr4 + case 44: + goto tr4 + case 47: + goto tr4 + case 59: + goto tr4 + case 96: + goto tr4 + } + switch { + case data[p] < 48: + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr4 + } + case data[p] >= 9: + goto tr4 + } + case data[p] > 57: + switch { + case data[p] > 70: + if 97 <= data[p] && data[p] <= 102 { + goto tr52 + } + case data[p] >= 65: + goto tr52 + } + default: + goto tr52 + } + goto tr25 + tr34: + //.... NONE:1 + te = p + 1 + + goto st32 + st32: + if p++; p == pe { + goto _test_eof32 + } + st_case_32: + //.... tmp_parser.go:1011 + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 39: + goto st10 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 96: + goto tr44 + } + switch { + case data[p] > 13: + if 40 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st10: + if p++; p == pe { + goto _test_eof10 + } + st_case_10: + if data[p] == 39 { + goto tr12 + } + if 48 <= data[p] && data[p] <= 49 { + goto st10 + } + goto tr11 + st33: + if p++; p == pe { + goto _test_eof33 + } + st_case_33: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 79: + goto st34 + case 96: + goto tr44 + case 111: + goto st34 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st34: + if p++; p == pe { + goto _test_eof34 + } + st_case_34: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 78: + goto st35 + case 96: + goto tr44 + case 110: + goto st35 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st35: + if p++; p == pe { + goto _test_eof35 + } + st_case_35: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 86: + goto st36 + case 96: + goto tr44 + case 118: + goto st36 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st36: + if p++; p == pe { + goto _test_eof36 + } + st_case_36: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 69: + goto st37 + case 96: + goto tr44 + case 101: + goto st37 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st37: + if p++; p == pe { + goto _test_eof37 + } + st_case_37: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 82: + goto st38 + case 96: + goto tr44 + case 114: + goto st38 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st38: + if p++; p == pe { + goto _test_eof38 + } + st_case_38: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 84: + goto st39 + case 96: + goto tr44 + case 116: + goto st39 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st39: + if p++; p == pe { + goto _test_eof39 + } + st_case_39: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 40: + goto tr10 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 96: + goto tr44 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st40: + if p++; p == pe { + goto _test_eof40 + } + st_case_40: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 65: + goto st41 + case 96: + goto tr44 + case 97: + goto st41 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st41: + if p++; p == pe { + goto _test_eof41 + } + st_case_41: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 76: + goto st42 + case 96: + goto tr44 + case 108: + goto st42 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st42: + if p++; p == pe { + goto _test_eof42 + } + st_case_42: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 83: + goto st43 + case 96: + goto tr44 + case 115: + goto st43 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st43: + if p++; p == pe { + goto _test_eof43 + } + st_case_43: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 69: + goto tr62 + case 96: + goto tr44 + case 101: + goto tr62 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st44: + if p++; p == pe { + goto _test_eof44 + } + st_case_44: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 85: + goto st45 + case 96: + goto tr44 + case 117: + goto st45 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st45: + if p++; p == pe { + goto _test_eof45 + } + st_case_45: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 76: + goto st46 + case 96: + goto tr44 + case 108: + goto st46 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st46: + if p++; p == pe { + goto _test_eof46 + } + st_case_46: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 76: + goto tr65 + case 96: + goto tr44 + case 108: + goto tr65 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st47: + if p++; p == pe { + goto _test_eof47 + } + st_case_47: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 82: + goto st48 + case 96: + goto tr44 + case 114: + goto st48 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st48: + if p++; p == pe { + goto _test_eof48 + } + st_case_48: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 85: + goto st49 + case 96: + goto tr44 + case 117: + goto st49 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st49: + if p++; p == pe { + goto _test_eof49 + } + st_case_49: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 69: + goto tr68 + case 96: + goto tr44 + case 101: + goto tr68 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st50: + if p++; p == pe { + goto _test_eof50 + } + st_case_50: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 83: + goto st51 + case 96: + goto tr44 + case 115: + goto st51 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st51: + if p++; p == pe { + goto _test_eof51 + } + st_case_51: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 73: + goto st52 + case 96: + goto tr44 + case 105: + goto st52 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st52: + if p++; p == pe { + goto _test_eof52 + } + st_case_52: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 78: + goto st53 + case 96: + goto tr44 + case 110: + goto st53 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st53: + if p++; p == pe { + goto _test_eof53 + } + st_case_53: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 71: + goto tr72 + case 96: + goto tr44 + case 103: + goto tr72 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + tr72: + //.... NONE:1 + te = p + 1 + + goto st54 + st54: + if p++; p == pe { + goto _test_eof54 + } + st_case_54: + //.... tmp_parser.go:1729 + switch data[p] { + case 32: + goto st11 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 96: + goto tr44 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st11: + if p++; p == pe { + goto _test_eof11 + } + st_case_11: + switch data[p] { + case 85: + goto st12 + case 117: + goto st12 + } + goto tr11 + st12: + if p++; p == pe { + goto _test_eof12 + } + st_case_12: + switch data[p] { + case 84: + goto st13 + case 116: + goto st13 + } + goto tr11 + st13: + if p++; p == pe { + goto _test_eof13 + } + st_case_13: + switch data[p] { + case 70: + goto st14 + case 102: + goto st14 + } + goto tr11 + st14: + if p++; p == pe { + goto _test_eof14 + } + st_case_14: + if data[p] == 56 { + goto st15 + } + goto tr11 + st15: + if p++; p == pe { + goto _test_eof15 + } + st_case_15: + switch data[p] { + case 77: + goto st16 + case 109: + goto st16 + } + goto tr11 + st16: + if p++; p == pe { + goto _test_eof16 + } + st_case_16: + switch data[p] { + case 66: + goto st17 + case 98: + goto st17 + } + goto tr11 + st17: + if p++; p == pe { + goto _test_eof17 + } + st_case_17: + if data[p] == 52 { + goto st18 + } + goto tr11 + st18: + if p++; p == pe { + goto _test_eof18 + } + st_case_18: + if data[p] == 41 { + goto tr10 + } + goto tr11 + st55: + if p++; p == pe { + goto _test_eof55 + } + st_case_55: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 65: + goto st56 + case 96: + goto tr44 + case 97: + goto st56 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st56: + if p++; p == pe { + goto _test_eof56 + } + st_case_56: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 76: + goto st57 + case 96: + goto tr44 + case 108: + goto st57 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st57: + if p++; p == pe { + goto _test_eof57 + } + st_case_57: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 85: + goto st58 + case 96: + goto tr44 + case 117: + goto st58 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st58: + if p++; p == pe { + goto _test_eof58 + } + st_case_58: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 69: + goto st59 + case 96: + goto tr44 + case 101: + goto st59 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st59: + if p++; p == pe { + goto _test_eof59 + } + st_case_59: + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 83: + goto tr78 + case 96: + goto tr44 + case 115: + goto tr78 + } + switch { + case data[p] > 13: + if 39 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + tr41: + //.... NONE:1 + te = p + 1 + + goto st60 + st60: + if p++; p == pe { + goto _test_eof60 + } + st_case_60: + //.... tmp_parser.go:2010 + switch data[p] { + case 32: + goto tr44 + case 34: + goto tr44 + case 39: + goto st19 + case 44: + goto tr44 + case 47: + goto tr44 + case 59: + goto tr44 + case 96: + goto tr44 + } + switch { + case data[p] > 13: + if 40 <= data[p] && data[p] <= 42 { + goto tr44 + } + case data[p] >= 9: + goto tr44 + } + goto tr25 + st19: + if p++; p == pe { + goto _test_eof19 + } + st_case_19: + if data[p] == 39 { + goto tr21 + } + switch { + case data[p] < 65: + if 48 <= data[p] && data[p] <= 57 { + goto st19 + } + case data[p] > 70: + if 97 <= data[p] && data[p] <= 102 { + goto st19 + } + default: + goto st19 + } + goto tr11 + st20: + if p++; p == pe { + goto _test_eof20 + } + st_case_20: + if data[p] == 96 { + goto tr24 + } + goto st20 + tr24: + //.... NONE:1 + te = p + 1 + + //.... lightning/mydump/parser.rl:125 + act = 13 + goto st61 + st61: + if p++; p == pe { + goto _test_eof61 + } + st_case_61: + //.... tmp_parser.go:2078 + if data[p] == 96 { + goto st20 + } + goto tr79 + st_out: + _test_eof21: + cs = 21 + goto _test_eof + _test_eof22: + cs = 22 + goto _test_eof + _test_eof1: + cs = 1 + goto _test_eof + _test_eof2: + cs = 2 + goto _test_eof + _test_eof23: + cs = 23 + goto _test_eof + _test_eof3: + cs = 3 + goto _test_eof + _test_eof4: + cs = 4 + goto _test_eof + _test_eof5: + cs = 5 + goto _test_eof + _test_eof24: + cs = 24 + goto _test_eof + _test_eof6: + cs = 6 + goto _test_eof + _test_eof25: + cs = 25 + goto _test_eof + _test_eof26: + cs = 26 + goto _test_eof + _test_eof27: + cs = 27 + goto _test_eof + _test_eof28: + cs = 28 + goto _test_eof + _test_eof7: + cs = 7 + goto _test_eof + _test_eof8: + cs = 8 + goto _test_eof + _test_eof9: + cs = 9 + goto _test_eof + _test_eof29: + cs = 29 + goto _test_eof + _test_eof30: + cs = 30 + goto _test_eof + _test_eof31: + cs = 31 + goto _test_eof + _test_eof32: + cs = 32 + goto _test_eof + _test_eof10: + cs = 10 + goto _test_eof + _test_eof33: + cs = 33 + goto _test_eof + _test_eof34: + cs = 34 + goto _test_eof + _test_eof35: + cs = 35 + goto _test_eof + _test_eof36: + cs = 36 + goto _test_eof + _test_eof37: + cs = 37 + goto _test_eof + _test_eof38: + cs = 38 + goto _test_eof + _test_eof39: + cs = 39 + goto _test_eof + _test_eof40: + cs = 40 + goto _test_eof + _test_eof41: + cs = 41 + goto _test_eof + _test_eof42: + cs = 42 + goto _test_eof + _test_eof43: + cs = 43 + goto _test_eof + _test_eof44: + cs = 44 + goto _test_eof + _test_eof45: + cs = 45 + goto _test_eof + _test_eof46: + cs = 46 + goto _test_eof + _test_eof47: + cs = 47 + goto _test_eof + _test_eof48: + cs = 48 + goto _test_eof + _test_eof49: + cs = 49 + goto _test_eof + _test_eof50: + cs = 50 + goto _test_eof + _test_eof51: + cs = 51 + goto _test_eof + _test_eof52: + cs = 52 + goto _test_eof + _test_eof53: + cs = 53 + goto _test_eof + _test_eof54: + cs = 54 + goto _test_eof + _test_eof11: + cs = 11 + goto _test_eof + _test_eof12: + cs = 12 + goto _test_eof + _test_eof13: + cs = 13 + goto _test_eof + _test_eof14: + cs = 14 + goto _test_eof + _test_eof15: + cs = 15 + goto _test_eof + _test_eof16: + cs = 16 + goto _test_eof + _test_eof17: + cs = 17 + goto _test_eof + _test_eof18: + cs = 18 + goto _test_eof + _test_eof55: + cs = 55 + goto _test_eof + _test_eof56: + cs = 56 + goto _test_eof + _test_eof57: + cs = 57 + goto _test_eof + _test_eof58: + cs = 58 + goto _test_eof + _test_eof59: + cs = 59 + goto _test_eof + _test_eof60: + cs = 60 + goto _test_eof + _test_eof19: + cs = 19 + goto _test_eof + _test_eof20: + cs = 20 + goto _test_eof + _test_eof61: + cs = 61 + goto _test_eof + + _test_eof: + { + } + if p == eof { + switch cs { + case 22: + goto tr4 + case 2: + goto tr4 + case 23: + goto tr42 + case 3: + goto tr4 + case 5: + goto tr4 + case 24: + goto tr43 + case 6: + goto tr4 + case 25: + goto tr44 + case 26: + goto tr46 + case 27: + goto tr46 + case 28: + goto tr48 + case 29: + goto tr48 + case 30: + goto tr4 + case 31: + goto tr4 + case 32: + goto tr44 + case 10: + goto tr11 + case 33: + goto tr44 + case 34: + goto tr44 + case 35: + goto tr44 + case 36: + goto tr44 + case 37: + goto tr44 + case 38: + goto tr44 + case 39: + goto tr44 + case 40: + goto tr44 + case 41: + goto tr44 + case 42: + goto tr44 + case 43: + goto tr44 + case 44: + goto tr44 + case 45: + goto tr44 + case 46: + goto tr44 + case 47: + goto tr44 + case 48: + goto tr44 + case 49: + goto tr44 + case 50: + goto tr44 + case 51: + goto tr44 + case 52: + goto tr44 + case 53: + goto tr44 + case 54: + goto tr44 + case 11: + goto tr11 + case 12: + goto tr11 + case 13: + goto tr11 + case 14: + goto tr11 + case 15: + goto tr11 + case 16: + goto tr11 + case 17: + goto tr11 + case 18: + goto tr11 + case 55: + goto tr44 + case 56: + goto tr44 + case 57: + goto tr44 + case 58: + goto tr44 + case 59: + goto tr44 + case 60: + goto tr44 + case 19: + goto tr11 + case 20: + goto tr4 + case 61: + goto tr79 + } + } + + _out: + { + } + } + + //.... lightning/mydump/parser.rl:155 + + if cs == 0 { + parser.logSyntaxError() + return tokNil, nil, errors.New("syntax error") + } + + if consumedToken != tokNil { + result := data[ts:te] + parser.buf = data[te:] + parser.pos += int64(te) + return consumedToken, result, nil + } + + if parser.isLastChunk { + if te == eof { + return tokNil, nil, io.EOF + } else { + return tokNil, nil, errors.New("syntax error: unexpected EOF") + } + } + + parser.buf = parser.buf[ts:] + parser.pos += int64(ts) + p -= ts + te -= ts + ts = 0 + if err := parser.readBlock(); err != nil { + return tokNil, nil, errors.Trace(err) + } + } + + return tokNil, nil, nil +} diff --git a/pkg/lightning/mydump/parser_test.go b/pkg/lightning/mydump/parser_test.go new file mode 100644 index 000000000..53ee5e119 --- /dev/null +++ b/pkg/lightning/mydump/parser_test.go @@ -0,0 +1,884 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package mydump_test + +import ( + "context" + "io" + + . "github.com/pingcap/check" + "github.com/pingcap/errors" + "github.com/pingcap/parser/mysql" + "github.com/pingcap/tidb/types" + + "github.com/pingcap/br/pkg/lightning/config" + "github.com/pingcap/br/pkg/lightning/mydump" + "github.com/pingcap/br/pkg/lightning/worker" +) + +var _ = Suite(&testMydumpParserSuite{}) + +type testMydumpParserSuite struct { + ioWorkers *worker.Pool +} + +func (s *testMydumpParserSuite) SetUpSuite(c *C) { + s.ioWorkers = worker.NewPool(context.Background(), 5, "test_sql") +} +func (s *testMydumpParserSuite) TearDownSuite(c *C) {} + +func (s *testMydumpParserSuite) runTestCases(c *C, mode mysql.SQLMode, blockBufSize int64, cases []testCase) { + for _, tc := range cases { + parser := mydump.NewChunkParser(mode, mydump.NewStringReader(tc.input), blockBufSize, s.ioWorkers) + for i, row := range tc.expected { + e := parser.ReadRow() + comment := Commentf("input = %q, row = %d, err = %s", tc.input, i+1, errors.ErrorStack(e)) + c.Assert(e, IsNil, comment) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{RowID: int64(i) + 1, Row: row}, comment) + } + c.Assert(errors.Cause(parser.ReadRow()), Equals, io.EOF, Commentf("input = %q", tc.input)) + } +} + +func (s *testMydumpParserSuite) runFailingTestCases(c *C, mode mysql.SQLMode, blockBufSize int64, cases []string) { + for _, tc := range cases { + parser := mydump.NewChunkParser(mode, mydump.NewStringReader(tc), blockBufSize, s.ioWorkers) + c.Assert(parser.ReadRow(), ErrorMatches, "syntax error.*", Commentf("input = %q", tc)) + } +} + +func (s *testMydumpParserSuite) TestReadRow(c *C) { + reader := mydump.NewStringReader( + "/* whatever pragmas */;" + + "INSERT INTO `namespaced`.`table` (columns, more, columns) VALUES (1,-2, 3),\n(4,5., 6);" + + "INSERT `namespaced`.`table` (x,y,z) VALUES (7,8,9);" + + "insert another_table values (10,11e1,12, '(13)', '(', 14, ')');", + ) + + parser := mydump.NewChunkParser(mysql.ModeNone, reader, int64(config.ReadBlockSize), s.ioWorkers) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 1, + Row: []types.Datum{ + types.NewUintDatum(1), + types.NewIntDatum(-2), + types.NewUintDatum(3), + }, + }) + c.Assert(parser.Columns(), DeepEquals, []string{"columns", "more", "columns"}) + offset, rowID := parser.Pos() + c.Assert(offset, Equals, int64(97)) + c.Assert(rowID, Equals, int64(1)) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 2, + Row: []types.Datum{ + types.NewUintDatum(4), + types.NewStringDatum("5."), + types.NewUintDatum(6), + }, + }) + c.Assert(parser.Columns(), DeepEquals, []string{"columns", "more", "columns"}) + offset, rowID = parser.Pos() + c.Assert(offset, Equals, int64(108)) + c.Assert(rowID, Equals, int64(2)) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 3, + Row: []types.Datum{ + types.NewUintDatum(7), + types.NewUintDatum(8), + types.NewUintDatum(9), + }, + }) + c.Assert(parser.Columns(), DeepEquals, []string{"x", "y", "z"}) + offset, rowID = parser.Pos() + c.Assert(offset, Equals, int64(159)) + c.Assert(rowID, Equals, int64(3)) + + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.LastRow(), DeepEquals, mydump.Row{ + RowID: 4, + Row: []types.Datum{ + types.NewUintDatum(10), + types.NewStringDatum("11e1"), + types.NewUintDatum(12), + types.NewStringDatum("(13)"), + types.NewStringDatum("("), + types.NewUintDatum(14), + types.NewStringDatum(")"), + }, + }) + c.Assert(parser.Columns(), IsNil) + offset, rowID = parser.Pos() + c.Assert(offset, Equals, int64(222)) + c.Assert(rowID, Equals, int64(4)) + + c.Assert(errors.Cause(parser.ReadRow()), Equals, io.EOF) +} + +func (s *testMydumpParserSuite) TestReadChunks(c *C) { + reader := mydump.NewStringReader(` + INSERT foo VALUES (1,2,3,4),(5,6,7,8),(9,10,11,12); + INSERT foo VALUES (13,14,15,16),(17,18,19,20),(21,22,23,24),(25,26,27,28); + INSERT foo VALUES (29,30,31,32),(33,34,35,36); + `) + + parser := mydump.NewChunkParser(mysql.ModeNone, reader, int64(config.ReadBlockSize), s.ioWorkers) + + chunks, err := mydump.ReadChunks(parser, 32) + c.Assert(err, IsNil) + c.Assert(chunks, DeepEquals, []mydump.Chunk{ + { + Offset: 0, + EndOffset: 40, + PrevRowIDMax: 0, + RowIDMax: 2, + }, + { + Offset: 40, + EndOffset: 88, + PrevRowIDMax: 2, + RowIDMax: 4, + }, + { + Offset: 88, + EndOffset: 130, + PrevRowIDMax: 4, + RowIDMax: 7, + }, + { + Offset: 130, + EndOffset: 165, + PrevRowIDMax: 7, + RowIDMax: 8, + }, + { + Offset: 165, + EndOffset: 179, + PrevRowIDMax: 8, + RowIDMax: 9, + }, + }) +} + +func (s *testMydumpParserSuite) TestNestedRow(c *C) { + reader := mydump.NewStringReader(` + INSERT INTO exam_detail VALUES + ("123",CONVERT("{}" USING UTF8MB4)), + ("456",CONVERT("{\"a\":4}" USING UTF8MB4)), + ("789",CONVERT("[]" USING UTF8MB4)); + `) + + parser := mydump.NewChunkParser(mysql.ModeNone, reader, int64(config.ReadBlockSize), s.ioWorkers) + chunks, err := mydump.ReadChunks(parser, 96) + + c.Assert(err, IsNil) + c.Assert(chunks, DeepEquals, []mydump.Chunk{ + { + Offset: 0, + EndOffset: 117, + PrevRowIDMax: 0, + RowIDMax: 2, + }, + { + Offset: 117, + EndOffset: 156, + PrevRowIDMax: 2, + RowIDMax: 3, + }, + }) +} + +func (s *testMydumpParserSuite) TestVariousSyntax(c *C) { + testCases := []testCase{ + { + input: "INSERT INTO foobar VALUES (1, 2);", + expected: [][]types.Datum{{types.NewUintDatum(1), types.NewUintDatum(2)}}, + }, + { + input: "INSERT INTO `foobar` VALUES (3, 4);", + expected: [][]types.Datum{{types.NewUintDatum(3), types.NewUintDatum(4)}}, + }, + { + input: `INSERT INTO "foobar" VALUES (5, 6);`, + expected: [][]types.Datum{{types.NewUintDatum(5), types.NewUintDatum(6)}}, + }, + { + input: `(7, -8, Null, '9'), (b'10', 0b11, 0x12, x'13'), ("14", True, False, 0)`, + expected: [][]types.Datum{ + { + types.NewUintDatum(7), + types.NewIntDatum(-8), + nullDatum, + types.NewStringDatum("9"), + }, + { + types.NewBinaryLiteralDatum(types.BinaryLiteral([]byte{2})), + types.NewBinaryLiteralDatum(types.BinaryLiteral([]byte{3})), + types.NewBinaryLiteralDatum(types.BinaryLiteral([]byte{0x12})), + types.NewBinaryLiteralDatum(types.BinaryLiteral([]byte{0x13})), + }, + { + types.NewStringDatum("14"), + types.NewIntDatum(1), + types.NewIntDatum(0), + types.NewUintDatum(0), + }, + }, + }, + { + input: ` + (.15, 1.6, 17.), + (.18e1, 1.9e1, 20.e1), (.21e-1, 2.2e-1, 23.e-1), (.24e+1, 2.5e+1, 26.e+1), + (-.27, -2.8, -29.), + (-.30e1, -3.1e1, -32.e1), (-.33e-1, -3.4e-1, -35.e-1), (-.36e+1, -3.7e+1, -38.e+1), + (1e39, 1e-40, 1e+41), + (.42E1, 4.3E1, 44.E1), (.45E-1, 4.6E-1, 47.E-1), (.48E+1, 4.9E+1, 50.E+1), + (-.51E1, -5.2E1, -53.E1), (-.54E-1, -5.5E-1, -56.E-1), (-.57E+1, -5.8E+1, -59.E+1), + (1E60, 1E-61, 1E+62), + (6.33333333333333333333333333333333333333333333, -6.44444444444444444444444444444444444444444444, -0.0), + (65555555555555555555555555555555555555555555.5, -66666666666666666666666666666666666666666666.6, 0.0) + `, + expected: [][]types.Datum{ + {types.NewStringDatum(".15"), types.NewStringDatum("1.6"), types.NewStringDatum("17.")}, + {types.NewStringDatum(".18e1"), types.NewStringDatum("1.9e1"), types.NewStringDatum("20.e1")}, + {types.NewStringDatum(".21e-1"), types.NewStringDatum("2.2e-1"), types.NewStringDatum("23.e-1")}, + {types.NewStringDatum(".24e+1"), types.NewStringDatum("2.5e+1"), types.NewStringDatum("26.e+1")}, + {types.NewStringDatum("-.27"), types.NewStringDatum("-2.8"), types.NewStringDatum("-29.")}, + {types.NewStringDatum("-.30e1"), types.NewStringDatum("-3.1e1"), types.NewStringDatum("-32.e1")}, + {types.NewStringDatum("-.33e-1"), types.NewStringDatum("-3.4e-1"), types.NewStringDatum("-35.e-1")}, + {types.NewStringDatum("-.36e+1"), types.NewStringDatum("-3.7e+1"), types.NewStringDatum("-38.e+1")}, + {types.NewStringDatum("1e39"), types.NewStringDatum("1e-40"), types.NewStringDatum("1e+41")}, + {types.NewStringDatum(".42E1"), types.NewStringDatum("4.3E1"), types.NewStringDatum("44.E1")}, + {types.NewStringDatum(".45E-1"), types.NewStringDatum("4.6E-1"), types.NewStringDatum("47.E-1")}, + {types.NewStringDatum(".48E+1"), types.NewStringDatum("4.9E+1"), types.NewStringDatum("50.E+1")}, + {types.NewStringDatum("-.51E1"), types.NewStringDatum("-5.2E1"), types.NewStringDatum("-53.E1")}, + {types.NewStringDatum("-.54E-1"), types.NewStringDatum("-5.5E-1"), types.NewStringDatum("-56.E-1")}, + {types.NewStringDatum("-.57E+1"), types.NewStringDatum("-5.8E+1"), types.NewStringDatum("-59.E+1")}, + {types.NewStringDatum("1E60"), types.NewStringDatum("1E-61"), types.NewStringDatum("1E+62")}, + { + types.NewStringDatum("6.33333333333333333333333333333333333333333333"), + types.NewStringDatum("-6.44444444444444444444444444444444444444444444"), + types.NewStringDatum("-0.0"), + }, + { + types.NewStringDatum("65555555555555555555555555555555555555555555.5"), + types.NewStringDatum("-66666666666666666666666666666666666666666666.6"), + types.NewStringDatum("0.0"), + }, + }, + }, + { + input: ` + (0x123456ABCDEFabcdef, 0xABCDEF123456, 0xabcdef123456, 0x123), + (x'123456ABCDEFabcdef', x'ABCDEF123456', x'abcdef123456', x''), + (X'123456ABCDEFabcdef', X'ABCDEF123456', X'abcdef123456', X''), + ( + 0b101010101010101010101010101010101010101010101010101010101010101010, + b'010101010101010101010101010101010101010101010101010101010101010101', + B'110011001100110011001100110011001100110011001100110011001100', + b'', + B'' + ) + `, + expected: [][]types.Datum{ + { + types.NewBinaryLiteralDatum(types.BinaryLiteral([]byte{0x12, 0x34, 0x56, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef})), + types.NewBinaryLiteralDatum(types.BinaryLiteral([]byte{0xab, 0xcd, 0xef, 0x12, 0x34, 0x56})), + types.NewBinaryLiteralDatum(types.BinaryLiteral([]byte{0xab, 0xcd, 0xef, 0x12, 0x34, 0x56})), + types.NewBinaryLiteralDatum(types.BinaryLiteral([]byte{0x01, 0x23})), + }, + { + types.NewBinaryLiteralDatum(types.BinaryLiteral([]byte{0x12, 0x34, 0x56, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef})), + types.NewBinaryLiteralDatum(types.BinaryLiteral([]byte{0xab, 0xcd, 0xef, 0x12, 0x34, 0x56})), + types.NewBinaryLiteralDatum(types.BinaryLiteral([]byte{0xab, 0xcd, 0xef, 0x12, 0x34, 0x56})), + types.NewBinaryLiteralDatum(types.BinaryLiteral([]byte{})), + }, + { + types.NewBinaryLiteralDatum(types.BinaryLiteral([]byte{0x12, 0x34, 0x56, 0xab, 0xcd, 0xef, 0xab, 0xcd, 0xef})), + types.NewBinaryLiteralDatum(types.BinaryLiteral([]byte{0xab, 0xcd, 0xef, 0x12, 0x34, 0x56})), + types.NewBinaryLiteralDatum(types.BinaryLiteral([]byte{0xab, 0xcd, 0xef, 0x12, 0x34, 0x56})), + types.NewBinaryLiteralDatum(types.BinaryLiteral([]byte{})), + }, + { + types.NewBinaryLiteralDatum(types.BinaryLiteral([]byte{0x02, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa})), + types.NewBinaryLiteralDatum(types.BinaryLiteral([]byte{0x01, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55})), + types.NewBinaryLiteralDatum(types.BinaryLiteral([]byte{0x0c, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc})), + types.NewBinaryLiteralDatum(types.BinaryLiteral([]byte{})), + types.NewBinaryLiteralDatum(types.BinaryLiteral([]byte{})), + }, + }, + }, + { + input: "/* comment */; -- comment", + expected: [][]types.Datum{}, + }, + { + input: ` + -- comment /* ... + insert into xxx -- comment + values -- comment + (true, false), -- comment + (null, 00000); -- comment ... */ + `, + expected: [][]types.Datum{ + {types.NewIntDatum(1), types.NewIntDatum(0)}, + {nullDatum, types.NewUintDatum(0)}, + }, + }, + { + input: `('\0\b\n\r\t\Z\'\a')`, + expected: [][]types.Datum{{types.NewStringDatum("\x00\b\n\r\t\x1a'a")}}, + }, + { + input: `(CONVERT("[1,2,3]" USING UTF8MB4))`, + expected: [][]types.Datum{{types.NewStringDatum("[1,2,3]")}}, + }, + } + + s.runTestCases(c, mysql.ModeNone, int64(config.ReadBlockSize), testCases) +} + +func (s *testMydumpParserSuite) TestContinuation(c *C) { + testCases := []testCase{ + { + input: ` + ('FUZNtcGYegeXwnMRKtYnXtFhgnAMTzQHEBUTBehAFBQdPsnjHhRwRZhZLtEBsIDUFduzftskgxkYkPmEgvoirfIZRsARXjsdKwOc') + `, + expected: [][]types.Datum{ + {types.NewStringDatum("FUZNtcGYegeXwnMRKtYnXtFhgnAMTzQHEBUTBehAFBQdPsnjHhRwRZhZLtEBsIDUFduzftskgxkYkPmEgvoirfIZRsARXjsdKwOc")}, + }, + }, + { + input: "INSERT INTO `report_case_high_risk` VALUES (2,'4','6',8,10);", + expected: [][]types.Datum{ + {types.NewUintDatum(2), types.NewStringDatum("4"), types.NewStringDatum("6"), types.NewUintDatum(8), types.NewUintDatum(10)}, + }, + }, + } + + s.runTestCases(c, mysql.ModeNone, 1, testCases) +} + +func (s *testMydumpParserSuite) TestPseudoKeywords(c *C) { + reader := mydump.NewStringReader(` + INSERT INTO t ( + c, C, + co, CO, + con, CON, + conv, CONV, + conve, CONVE, + conver, CONVER, + convert, CONVERT, + u, U, + us, US, + usi, USI, + usin, USIN, + ut, UT, + utf, UTF, + utf8, UTF8, + utf8m, UTF8M, + utf8mb, UTF8MB, + utf8mb4, UTF8MB4, + t, T, + tr, TR, + tru, TRU, + f, F, + fa, FA, + fal, FAL, + fals, FALS, + n, N, + nu, NU, + nul, NUL, + v, V, + va, VA, + val, VAL, + valu, VALU, + value, VALUE, + i, I, + ins, INS, + inse, INSE, + inser, INSER, + ) VALUES (); + `) + + parser := mydump.NewChunkParser(mysql.ModeNone, reader, int64(config.ReadBlockSize), s.ioWorkers) + c.Assert(parser.ReadRow(), IsNil) + c.Assert(parser.Columns(), DeepEquals, []string{ + "c", "c", + "co", "co", + "con", "con", + "conv", "conv", + "conve", "conve", + "conver", "conver", + "convert", "convert", + "u", "u", + "us", "us", + "usi", "usi", + "usin", "usin", + "ut", "ut", + "utf", "utf", + "utf8", "utf8", + "utf8m", "utf8m", + "utf8mb", "utf8mb", + "utf8mb4", "utf8mb4", + "t", "t", + "tr", "tr", + "tru", "tru", + "f", "f", + "fa", "fa", + "fal", "fal", + "fals", "fals", + "n", "n", + "nu", "nu", + "nul", "nul", + "v", "v", + "va", "va", + "val", "val", + "valu", "valu", + "value", "value", + "i", "i", + "ins", "ins", + "inse", "inse", + "inser", "inser", + }) +} + +func (s *testMydumpParserSuite) TestSyntaxError(c *C) { + inputs := []string{ + "('xxx)", + `("xxx)`, + "(`xxx)", + "(/* xxx)", + `('\')`, + `("\")`, + `('\)`, + `("\)`, + "(", + "(1", + "(1,", + "(values)", + "insert into e (f", + "insert into e (3) values (4)", + "insert into e ('3') values (4)", + "insert into e (0x3) values (4)", + "insert into e (x'3') values (4)", + "insert into e (b'3') values (4)", + "3", + "(`values`)", + "/* ...", + } + + s.runFailingTestCases(c, mysql.ModeNone, int64(config.ReadBlockSize), inputs) +} + +// Various syntax error cases collected via fuzzing. +// These cover most of the tokenizer branches. + +func (s *testMydumpParserSuite) TestMoreSyntaxError(c *C) { + inputs := []string{ + " usin0", + "- ", + "-,", + "-;", + "-", + "-(", + "-/", + "-\"", + "-`", + ", '0\\0", + ",/*000", + "; con0", + ";CONV0", + ";using UTF0", + "''", + "'", + "'\\", + "'\\\\", + "'0''00", + "'0'", + "'0\\", + "(''''0", + "(''0'0", + "(fals0", + "(x'000", + "*", + "/", + "/**", + "/***", + "/**0", + "/*00*0", + "/0", + "\"", + "\"\"", + "\"\"\"0\\0", + "\"\\", + "\"\\\\", + "\"0\"", + "\"0\"\x00\"0", + "\"0\\", + "\"0000\"\"\"\"\"0", + "\"00000", + "\x00;", + "\xd9/", + "\xde0 b'0", + "\xed00000", + "``", + "`````0", + "0 ", + "0-\"", + "0,", + "0;", + "0", + "0/", + "0\"", + "0\x00 CONVERT0", + "0\x00\"\"C0", + "0\x03\fFa0", + "0`", + "00 ", + "00/", + "00\"", + "00`", + "000\xf1/0", + "00a t0", + "00b b0", + "00d f0", + "00e u0", + "00l 00", + "00l v0", + "00n -0", + "00n Using UTF8M0", + "00t using 0", + "00t x0", + "0a VA0", + "0b ", + "0b;", + "0b'", + "0b", + "0b/", + "0b\"", + "0b`", + "0b0000", + "0by", + "0O,CO0", + "0r tr0", + "0s us0", + "0T``CONVER0", + "0u\vnu0", + "0x ", + "0x;", + "0x", + "0x/", + "0x\"", + "0x\n", + "0x\x00", + "0x`", + "0x0000", + "6;", + "a`00`0", + "b ", + "b,", + "B;", + "b'", + "b", + "b/", + "b\"", + "B\f", + "b`", + "b0", + "C ", + "C;", + "C", + "C/", + "C\"", + "C\n", + "C`", + "C0", + "CO ", + "CO;", + "CO", + "CO/", + "CO\"", + "CO\v", + "CO`", + "CON ", + "CON;", + "CON", + "CON/", + "CON\"", + "CON\v", + "CON`", + "CON0", + "CONV ", + "CONV;", + "CONv", + "CONV/", + "CONV\"", + "CONV\v", + "CONV`", + "CONV0", + "CONVE ", + "CONVE;", + "CONVE", + "CONVE/", + "CONVE\"", + "CONVE\v", + "CONVE`", + "CONVE0", + "CONVER ", + "CONVER;", + "CONVER", + "CONVER/", + "CONVER\"", + "CONVER\n", + "CONVER`", + "CONVER0", + "CONVERT ", + "CONVERT;", + "CONVERT", + "CONVERT/", + "CONVERT\"", + "CONVERT\n", + "CONVERT`", + "CONVERT0", + "e tru0", + "f ", + "f;", + "F/", + "f\"", + "F\f", + "f`", + "fa ", + "Fa;", + "fa", + "FA/", + "FA\"", + "Fa\f", + "FA`", + "fa0", + "fal ", + "fal;", + "Fal", + "fal/", + "FAL\"", + "FAL\n", + "FAl`", + "fal0", + "fals ", + "fals;", + "fals", + "fals/", + "fals\"", + "fals\n", + "fals`", + "FALS0", + "FALSE", + "g Using UT0", + "N ", + "N NUL0", + "n;", + "N", + "N/", + "n\"", + "n\v", + "n`", + "N0", + "n00\vn0", + "nu ", + "nu;", + "nu", + "nu/", + "nU\"", + "nu\v", + "nu`", + "nu0", + "NUL ", + "nuL;", + "nul", + "NuL/", + "NUL\"", + "NUL\f", + "nul`", + "nul0", + "NULL", + "O", + "R FAL0", + "t n(50", + "t usi0", + "t using UTF80", + "t using UTF8M", + "t;", + "t", + "t/", + "t\"", + "t\f", + "t`", + "t0 USING U0", + "t0", + "tr ", + "Tr;", + "tr", + "tr/", + "tr\"", + "tr\f", + "tr`", + "tr0", + "tru ", + "trU;", + "TRU", + "TrU/", + "tru\"", + "tru\f", + "tru`", + "TRU0", + "TRUE", + "u ", + "u;", + "u", + "u/", + "U\"", + "u\t", + "u`", + "us ", + "us;", + "us", + "us/", + "US\"", + "us\t", + "us`", + "us0", + "usi ", + "usi;", + "usi", + "usi/", + "usi\"", + "usi\t", + "usi`", + "usi0", + "usin ", + "usiN;", + "USIN", + "usin/", + "usin\"", + "usin\t", + "usin`", + "USIN0", + "using ", + "using 0", + "using u", + "USING U", + "USING U0", + "USING Ut", + "using UT0", + "using utF", + "using UTf", + "using utF0", + "using utf8", + "using UTF80", + "using UTF8m", + "Using UTF8M0", + "Using UTF8MB", + "Using utf8mb", + "using,", + "using;", + "using", + "USING", + "using/", + "using\"", + "using\v", + "using`", + "using0", + "v ", + "v;", + "v", + "V/", + "V\"", + "v\v", + "v`", + "v0", + "va ", + "va;", + "va", + "va/", + "Va\"", + "va\v", + "va`", + "va0", + "val ", + "val;", + "val", + "val/", + "Val\"", + "VAL\v", + "val`", + "val0", + "valu ", + "valu;", + "valu", + "valu/", + "valu\"", + "VALU\t", + "VALU`", + "valu0", + "value ", + "value;", + "value", + "Value/", + "Value\"", + "VALUE\v", + "value`", + "value0", + "x ", + "x val0", + "x;", + "x'", + "x'x", + "x", + "x/", + "x\"", + "X\r", + "x`", + "x00`0`Valu0", + } + + s.runFailingTestCases(c, mysql.ModeNone, 1, inputs) + s.runFailingTestCases(c, mysql.ModeNoBackslashEscapes, 1, inputs) +} + +func (s *testMydumpParserSuite) TestMoreEmptyFiles(c *C) { + testCases := []testCase{ + {input: ""}, + {input: "--\t"}, + {input: "--\""}, + {input: "-- 000"}, + {input: "--;"}, + {input: "--,"}, + {input: "--0"}, + {input: "--`"}, + {input: "--"}, + {input: "--0000"}, + {input: "--\n"}, + {input: "--/"}, + {input: "--\"\r"}, + {input: "--\r"}, + } + + s.runTestCases(c, mysql.ModeNone, 1, testCases) + s.runTestCases(c, mysql.ModeNoBackslashEscapes, 1, testCases) +} diff --git a/pkg/lightning/mydump/reader.go b/pkg/lightning/mydump/reader.go new file mode 100644 index 000000000..30464eb7b --- /dev/null +++ b/pkg/lightning/mydump/reader.go @@ -0,0 +1,178 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package mydump + +import ( + "bufio" + "bytes" + "context" + "io" + "strings" + "unicode/utf8" + + "github.com/pingcap/errors" + "go.uber.org/zap" + "golang.org/x/text/encoding/simplifiedchinese" + + "github.com/pingcap/br/pkg/lightning/log" + "github.com/pingcap/br/pkg/lightning/worker" + "github.com/pingcap/br/pkg/storage" +) + +var ( + ErrInsertStatementNotFound = errors.New("insert statement not found") + errInvalidSchemaEncoding = errors.New("invalid schema encoding") +) + +func decodeCharacterSet(data []byte, characterSet string) ([]byte, error) { + switch characterSet { + case "binary": + // do nothing + case "auto", "utf8mb4": + if utf8.Valid(data) { + break + } + if characterSet == "utf8mb4" { + return nil, errInvalidSchemaEncoding + } + // try gb18030 next if the encoding is "auto" + // if we support too many encodings, consider switching strategy to + // perform `chardet` first. + fallthrough + case "gb18030": + decoded, err := simplifiedchinese.GB18030.NewDecoder().Bytes(data) + if err != nil { + return nil, errors.Trace(err) + } + // check for U+FFFD to see if decoding contains errors. + // https://groups.google.com/d/msg/golang-nuts/pENT3i4zJYk/v2X3yyiICwAJ + if bytes.ContainsRune(decoded, '\ufffd') { + return nil, errInvalidSchemaEncoding + } + data = decoded + default: + return nil, errors.Errorf("Unsupported encoding %s", characterSet) + } + return data, nil +} + +func ExportStatement(ctx context.Context, store storage.ExternalStorage, sqlFile FileInfo, characterSet string) ([]byte, error) { + fd, err := store.Open(ctx, sqlFile.FileMeta.Path) + if err != nil { + return nil, errors.Trace(err) + } + defer fd.Close() + + br := bufio.NewReader(fd) + + data := make([]byte, 0, sqlFile.FileMeta.FileSize+1) + buffer := make([]byte, 0, sqlFile.FileMeta.FileSize+1) + for { + line, err := br.ReadBytes('\n') + if errors.Cause(err) == io.EOF { + if len(line) == 0 { // it will return EOF if there is no trailing new line. + break + } + } else if err != nil { + return nil, errors.Trace(err) + } + + line = bytes.TrimSpace(line) + if len(line) == 0 { + continue + } + + buffer = append(buffer, line...) + if buffer[len(buffer)-1] == ';' { + statement := string(buffer) + if !(strings.HasPrefix(statement, "/*") && strings.HasSuffix(statement, "*/;")) { + data = append(data, buffer...) + } + buffer = buffer[:0] + } else { + buffer = append(buffer, '\n') + } + } + + data, err = decodeCharacterSet(data, characterSet) + if err != nil { + log.L().Error("cannot decode input file, please convert to target encoding manually", + zap.String("encoding", characterSet), + zap.String("Path", sqlFile.FileMeta.Path), + ) + return nil, errors.Annotatef(err, "failed to decode %s as %s", sqlFile.FileMeta.Path, characterSet) + } + return data, nil +} + +// ReadSeekCloser = Reader + Seeker + Closer +type ReadSeekCloser interface { + io.Reader + io.Seeker + io.Closer +} + +// StringReader is a wrapper around *strings.Reader with an additional Close() method +type StringReader struct{ *strings.Reader } + +// NewStringReader constructs a new StringReader +func NewStringReader(s string) StringReader { + return StringReader{Reader: strings.NewReader(s)} +} + +// Close implements io.Closer +func (sr StringReader) Close() error { + return nil +} + +// PooledReader is a throttled reader wrapper, where Read() calls have an upper limit of concurrency +// imposed by the given worker pool. +type PooledReader struct { + reader ReadSeekCloser + ioWorkers *worker.Pool +} + +// MakePooledReader constructs a new PooledReader. +func MakePooledReader(reader ReadSeekCloser, ioWorkers *worker.Pool) PooledReader { + return PooledReader{ + reader: reader, + ioWorkers: ioWorkers, + } +} + +// Read implements io.Reader +func (pr PooledReader) Read(p []byte) (n int, err error) { + w := pr.ioWorkers.Apply() + defer pr.ioWorkers.Recycle(w) + return pr.reader.Read(p) +} + +// Seek implements io.Seeker +func (pr PooledReader) Seek(offset int64, whence int) (int64, error) { + w := pr.ioWorkers.Apply() + defer pr.ioWorkers.Recycle(w) + return pr.reader.Seek(offset, whence) +} + +// Close implements io.Closer +func (pr PooledReader) Close() error { + return pr.reader.Close() +} + +// ReadFull is same as `io.ReadFull(pr)` with less worker recycling +func (pr PooledReader) ReadFull(buf []byte) (n int, err error) { + w := pr.ioWorkers.Apply() + defer pr.ioWorkers.Recycle(w) + return io.ReadFull(pr.reader, buf) +} diff --git a/pkg/lightning/mydump/reader_test.go b/pkg/lightning/mydump/reader_test.go new file mode 100644 index 000000000..7785c86b1 --- /dev/null +++ b/pkg/lightning/mydump/reader_test.go @@ -0,0 +1,197 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package mydump_test + +import ( + "context" + "errors" + "io/ioutil" + "os" + + "github.com/golang/mock/gomock" + . "github.com/pingcap/check" + + "github.com/pingcap/br/pkg/lightning/mock" + . "github.com/pingcap/br/pkg/lightning/mydump" + "github.com/pingcap/br/pkg/storage" +) + +////////////////////////////////////////////////////////// + +var _ = Suite(&testMydumpReaderSuite{}) + +type testMydumpReaderSuite struct{} + +func (s *testMydumpReaderSuite) SetUpSuite(c *C) {} +func (s *testMydumpReaderSuite) TearDownSuite(c *C) {} + +func (s *testMydumpReaderSuite) TestExportStatementNoTrailingNewLine(c *C) { + dir := c.MkDir() + file, err := ioutil.TempFile(dir, "tidb_lightning_test_reader") + c.Assert(err, IsNil) + defer os.Remove(file.Name()) + + store, err := storage.NewLocalStorage(dir) + c.Assert(err, IsNil) + + _, err = file.Write([]byte("CREATE DATABASE whatever;")) + c.Assert(err, IsNil) + stat, err := file.Stat() + c.Assert(err, IsNil) + err = file.Close() + c.Assert(err, IsNil) + + f := FileInfo{FileMeta: SourceFileMeta{Path: stat.Name(), FileSize: stat.Size()}} + data, err := ExportStatement(context.TODO(), store, f, "auto") + c.Assert(err, IsNil) + c.Assert(data, DeepEquals, []byte("CREATE DATABASE whatever;")) +} + +func (s *testMydumpReaderSuite) TestExportStatementWithComment(c *C) { + dir := c.MkDir() + file, err := ioutil.TempFile(dir, "tidb_lightning_test_reader") + c.Assert(err, IsNil) + defer os.Remove(file.Name()) + + _, err = file.Write([]byte(` + /* whatever blabla + multiple lines comment + multiple lines comment + multiple lines comment + multiple lines comment + multiple lines comment + */; + CREATE DATABASE whatever; +`)) + c.Assert(err, IsNil) + stat, err := file.Stat() + c.Assert(err, IsNil) + err = file.Close() + c.Assert(err, IsNil) + + store, err := storage.NewLocalStorage(dir) + c.Assert(err, IsNil) + + f := FileInfo{FileMeta: SourceFileMeta{Path: stat.Name(), FileSize: stat.Size()}} + data, err := ExportStatement(context.TODO(), store, f, "auto") + c.Assert(err, IsNil) + c.Assert(data, DeepEquals, []byte("CREATE DATABASE whatever;")) +} + +func (s *testMydumpReaderSuite) TestExportStatementWithCommentNoTrailingNewLine(c *C) { + dir := c.MkDir() + file, err := ioutil.TempFile(dir, "tidb_lightning_test_reader") + c.Assert(err, IsNil) + defer os.Remove(file.Name()) + + _, err = file.Write([]byte(` + /* whatever blabla + multiple lines comment + multiple lines comment + multiple lines comment + multiple lines comment + multiple lines comment + */; + CREATE DATABASE whatever;`)) + c.Assert(err, IsNil) + stat, err := file.Stat() + c.Assert(err, IsNil) + err = file.Close() + c.Assert(err, IsNil) + + store, err := storage.NewLocalStorage(dir) + c.Assert(err, IsNil) + f := FileInfo{FileMeta: SourceFileMeta{Path: stat.Name(), FileSize: stat.Size()}} + data, err := ExportStatement(context.TODO(), store, f, "auto") + c.Assert(err, IsNil) + c.Assert(data, DeepEquals, []byte("CREATE DATABASE whatever;")) +} + +func (s *testMydumpReaderSuite) TestExportStatementGBK(c *C) { + dir := c.MkDir() + file, err := ioutil.TempFile(dir, "tidb_lightning_test_reader") + c.Assert(err, IsNil) + defer os.Remove(file.Name()) + + _, err = file.Write([]byte("CREATE TABLE a (b int(11) COMMENT '")) + c.Assert(err, IsNil) + // "D7 DC B0 B8 C0 FD" is the GBK encoding of "总案例". + _, err = file.Write([]byte{0xD7, 0xDC, 0xB0, 0xB8, 0xC0, 0xFD}) + c.Assert(err, IsNil) + _, err = file.Write([]byte("');\n")) + c.Assert(err, IsNil) + stat, err := file.Stat() + c.Assert(err, IsNil) + err = file.Close() + c.Assert(err, IsNil) + + store, err := storage.NewLocalStorage(dir) + c.Assert(err, IsNil) + f := FileInfo{FileMeta: SourceFileMeta{Path: stat.Name(), FileSize: stat.Size()}} + data, err := ExportStatement(context.TODO(), store, f, "auto") + c.Assert(err, IsNil) + c.Assert(data, DeepEquals, []byte("CREATE TABLE a (b int(11) COMMENT '总案例');")) +} + +func (s *testMydumpReaderSuite) TestExportStatementGibberishError(c *C) { + dir := c.MkDir() + file, err := ioutil.TempFile(dir, "tidb_lightning_test_reader") + c.Assert(err, IsNil) + defer os.Remove(file.Name()) + + _, err = file.Write([]byte("\x9e\x02\xdc\xfbZ/=n\xf3\xf2N8\xc1\xf2\xe9\xaa\xd0\x85\xc5}\x97\x07\xae6\x97\x99\x9c\x08\xcb\xe8;")) + c.Assert(err, IsNil) + stat, err := file.Stat() + c.Assert(err, IsNil) + err = file.Close() + c.Assert(err, IsNil) + + store, err := storage.NewLocalStorage(dir) + c.Assert(err, IsNil) + + f := FileInfo{FileMeta: SourceFileMeta{Path: stat.Name(), FileSize: stat.Size()}} + data, err := ExportStatement(context.TODO(), store, f, "auto") + c.Assert(data, HasLen, 0) + c.Assert(err, ErrorMatches, `failed to decode \w* as auto: invalid schema encoding`) +} + +type AlwaysErrorReadSeekCloser struct{} + +func (AlwaysErrorReadSeekCloser) Read([]byte) (int, error) { + return 0, errors.New("read error!") +} + +func (AlwaysErrorReadSeekCloser) Seek(int64, int) (int64, error) { + return 0, errors.New("seek error!") +} + +func (AlwaysErrorReadSeekCloser) Close() error { + return nil +} + +func (s *testMydumpReaderSuite) TestExportStatementHandleNonEOFError(c *C) { + controller := gomock.NewController(c) + defer controller.Finish() + + ctx := context.TODO() + + mockStorage := mock.NewMockExternalStorage(controller) + mockStorage.EXPECT(). + Open(ctx, "no-perm-file"). + Return(AlwaysErrorReadSeekCloser{}, nil) + + f := FileInfo{FileMeta: SourceFileMeta{Path: "no-perm-file", FileSize: 1}} + _, err := ExportStatement(ctx, mockStorage, f, "auto") + c.Assert(err, ErrorMatches, "read error!") +} diff --git a/pkg/lightning/mydump/region.go b/pkg/lightning/mydump/region.go new file mode 100644 index 000000000..9d7f1fb51 --- /dev/null +++ b/pkg/lightning/mydump/region.go @@ -0,0 +1,397 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package mydump + +import ( + "context" + "math" + "sync" + "time" + + "github.com/pingcap/br/pkg/utils" + + "github.com/pingcap/errors" + "go.uber.org/zap" + + "github.com/pingcap/br/pkg/lightning/config" + "github.com/pingcap/br/pkg/lightning/log" + "github.com/pingcap/br/pkg/lightning/worker" + "github.com/pingcap/br/pkg/storage" +) + +const tableRegionSizeWarningThreshold int64 = 1024 * 1024 * 1024 + +type TableRegion struct { + EngineID int32 + + DB string + Table string + FileMeta SourceFileMeta + + Chunk Chunk +} + +func (reg *TableRegion) RowIDMin() int64 { + return reg.Chunk.PrevRowIDMax + 1 +} + +func (reg *TableRegion) Rows() int64 { + return reg.Chunk.RowIDMax - reg.Chunk.PrevRowIDMax +} + +func (reg *TableRegion) Offset() int64 { + return reg.Chunk.Offset +} + +func (reg *TableRegion) Size() int64 { + return reg.Chunk.EndOffset - reg.Chunk.Offset +} + +//////////////////////////////////////////////////////////////// + +func AllocateEngineIDs( + filesRegions []*TableRegion, + dataFileSizes []float64, + batchSize float64, + batchImportRatio float64, + tableConcurrency float64, +) { + totalDataFileSize := 0.0 + for _, dataFileSize := range dataFileSizes { + totalDataFileSize += dataFileSize + } + + // No need to batch if the size is too small :) + if totalDataFileSize <= batchSize { + return + } + + curEngineID := int32(0) + curEngineSize := 0.0 + curBatchSize := batchSize + + // import() step will not be concurrent. + // If multiple Batch end times are close, it will result in multiple + // Batch import serials. We need use a non-uniform batch size to create a pipeline effect. + // Here we calculate the total number of engines, which is needed to compute the scale up + // + // Total/B1 = 1/(1-R) * (N - 1/beta(N, R)) + // ≲ N/(1-R) + // + // We use a simple brute force search since the search space is extremely small. + ratio := totalDataFileSize * (1 - batchImportRatio) / batchSize + n := math.Ceil(ratio) + logGammaNPlusR, _ := math.Lgamma(n + batchImportRatio) + logGammaN, _ := math.Lgamma(n) + logGammaR, _ := math.Lgamma(batchImportRatio) + invBetaNR := math.Exp(logGammaNPlusR - logGammaN - logGammaR) // 1/B(N, R) = Γ(N+R)/Γ(N)Γ(R) + for { + if n <= 0 || n > tableConcurrency { + n = tableConcurrency + break + } + realRatio := n - invBetaNR + if realRatio >= ratio { + // we don't have enough engines. reduce the batch size to keep the pipeline smooth. + curBatchSize = totalDataFileSize * (1 - batchImportRatio) / realRatio + break + } + invBetaNR *= 1 + batchImportRatio/n // Γ(X+1) = X * Γ(X) + n += 1.0 + } + + for i, dataFileSize := range dataFileSizes { + filesRegions[i].EngineID = curEngineID + curEngineSize += dataFileSize + + if curEngineSize >= curBatchSize { + curEngineSize = 0 + curEngineID++ + + i := float64(curEngineID) + // calculate the non-uniform batch size + if i >= n { + curBatchSize = batchSize + } else { + // B_(i+1) = B_i * (I/W/(N-i) + 1) + curBatchSize *= batchImportRatio/(n-i) + 1.0 + } + } + } +} + +func MakeTableRegions( + ctx context.Context, + meta *MDTableMeta, + columns int, + cfg *config.Config, + ioWorkers *worker.Pool, + store storage.ExternalStorage, +) ([]*TableRegion, error) { + // Split files into regions + type fileRegionRes struct { + info FileInfo + regions []*TableRegion + sizes []float64 + err error + } + + start := time.Now() + + execCtx, cancel := context.WithCancel(ctx) + defer cancel() + + concurrency := utils.MaxInt(cfg.App.RegionConcurrency, 2) + fileChan := make(chan FileInfo, concurrency) + resultChan := make(chan fileRegionRes, concurrency) + var wg sync.WaitGroup + for i := 0; i < concurrency; i++ { + wg.Add(1) + go func() { + for info := range fileChan { + regions, sizes, err := makeSourceFileRegion(execCtx, meta, info, columns, cfg, ioWorkers, store) + select { + case resultChan <- fileRegionRes{info: info, regions: regions, sizes: sizes, err: err}: + case <-ctx.Done(): + break + } + if err != nil { + break + } + } + wg.Done() + }() + } + + go func() { + wg.Wait() + close(resultChan) + }() + + errChan := make(chan error, 1) + fileRegionsMap := make(map[string]fileRegionRes, len(meta.DataFiles)) + go func() { + for res := range resultChan { + if res.err != nil { + errChan <- res.err + return + } + fileRegionsMap[res.info.FileMeta.Path] = res + } + errChan <- nil + }() + + for _, dataFile := range meta.DataFiles { + select { + case fileChan <- dataFile: + case <-ctx.Done(): + close(fileChan) + return nil, ctx.Err() + case err := <-errChan: + return nil, err + } + } + close(fileChan) + err := <-errChan + if err != nil { + return nil, err + } + + filesRegions := make([]*TableRegion, 0, len(meta.DataFiles)) + dataFileSizes := make([]float64, 0, len(meta.DataFiles)) + prevRowIDMax := int64(0) + for _, dataFile := range meta.DataFiles { + fileRegionsRes := fileRegionsMap[dataFile.FileMeta.Path] + var delta int64 + if len(fileRegionsRes.regions) > 0 { + delta = prevRowIDMax - fileRegionsRes.regions[0].Chunk.PrevRowIDMax + } + + for _, region := range fileRegionsRes.regions { + region.Chunk.PrevRowIDMax += delta + region.Chunk.RowIDMax += delta + } + filesRegions = append(filesRegions, fileRegionsRes.regions...) + dataFileSizes = append(dataFileSizes, fileRegionsRes.sizes...) + prevRowIDMax = fileRegionsRes.regions[len(fileRegionsRes.regions)-1].Chunk.RowIDMax + } + + log.L().Info("makeTableRegions", zap.Int("filesCount", len(meta.DataFiles)), + zap.Int64("maxRegionSize", int64(cfg.Mydumper.MaxRegionSize)), + zap.Int("RegionsCount", len(filesRegions)), + zap.Duration("cost", time.Since(start))) + + AllocateEngineIDs(filesRegions, dataFileSizes, float64(cfg.Mydumper.BatchSize), cfg.Mydumper.BatchImportRatio, float64(cfg.App.TableConcurrency)) + return filesRegions, nil +} + +func makeSourceFileRegion( + ctx context.Context, + meta *MDTableMeta, + fi FileInfo, + columns int, + cfg *config.Config, + ioWorkers *worker.Pool, + store storage.ExternalStorage, +) ([]*TableRegion, []float64, error) { + if fi.FileMeta.Type == SourceTypeParquet { + _, region, err := makeParquetFileRegion(ctx, store, meta, fi, 0) + if err != nil { + return nil, nil, err + } + return []*TableRegion{region}, []float64{float64(fi.FileMeta.FileSize)}, nil + } + + dataFileSize := fi.FileMeta.FileSize + divisor := int64(columns) + isCsvFile := fi.FileMeta.Type == SourceTypeCSV + if !isCsvFile { + divisor += 2 + } + // If a csv file is overlarge, we need to split it into multiple regions. + // Note: We can only split a csv file whose format is strict. + if isCsvFile && dataFileSize > int64(cfg.Mydumper.MaxRegionSize) && cfg.Mydumper.StrictFormat { + + _, regions, subFileSizes, err := SplitLargeFile(ctx, meta, cfg, fi, divisor, 0, ioWorkers, store) + return regions, subFileSizes, err + } + + tableRegion := &TableRegion{ + DB: meta.DB, + Table: meta.Name, + FileMeta: fi.FileMeta, + Chunk: Chunk{ + Offset: 0, + EndOffset: fi.FileMeta.FileSize, + PrevRowIDMax: 0, + RowIDMax: fi.FileMeta.FileSize / divisor, + }, + } + + if tableRegion.Size() > tableRegionSizeWarningThreshold { + log.L().Warn( + "file is too big to be processed efficiently; we suggest splitting it at 256 MB each", + zap.String("file", fi.FileMeta.Path), + zap.Int64("size", dataFileSize)) + } + return []*TableRegion{tableRegion}, []float64{float64(fi.FileMeta.FileSize)}, nil +} + +// because parquet files can't seek efficiently, there is no benefit in split. +// parquet file are column orient, so the offset is read line number +func makeParquetFileRegion( + ctx context.Context, + store storage.ExternalStorage, + meta *MDTableMeta, + dataFile FileInfo, + prevRowIdxMax int64, +) (int64, *TableRegion, error) { + r, err := store.Open(ctx, dataFile.FileMeta.Path) + if err != nil { + return prevRowIdxMax, nil, errors.Trace(err) + } + numberRows, err := ReadParquetFileRowCount(ctx, store, r, dataFile.FileMeta.Path) + rowIDMax := prevRowIdxMax + numberRows + region := &TableRegion{ + DB: meta.DB, + Table: meta.Name, + FileMeta: dataFile.FileMeta, + Chunk: Chunk{ + Offset: 0, + EndOffset: numberRows, + PrevRowIDMax: prevRowIdxMax, + RowIDMax: rowIDMax, + }, + } + return rowIDMax, region, nil +} + +// SplitLargeFile splits a large csv file into multiple regions, the size of +// each regions is specified by `config.MaxRegionSize`. +// Note: We split the file coarsely, thus the format of csv file is needed to be +// strict. +// e.g. +// - CSV file with header is invalid +// - a complete tuple split into multiple lines is invalid +func SplitLargeFile( + ctx context.Context, + meta *MDTableMeta, + cfg *config.Config, + dataFile FileInfo, + divisor int64, + prevRowIdxMax int64, + ioWorker *worker.Pool, + store storage.ExternalStorage, +) (prevRowIdMax int64, regions []*TableRegion, dataFileSizes []float64, err error) { + maxRegionSize := int64(cfg.Mydumper.MaxRegionSize) + dataFileSizes = make([]float64, 0, dataFile.FileMeta.FileSize/maxRegionSize+1) + startOffset, endOffset := int64(0), maxRegionSize + var columns []string + if cfg.Mydumper.CSV.Header { + r, err := store.Open(ctx, dataFile.FileMeta.Path) + if err != nil { + return 0, nil, nil, err + } + parser := NewCSVParser(&cfg.Mydumper.CSV, r, int64(cfg.Mydumper.ReadBlockSize), ioWorker, true) + if err = parser.ReadColumns(); err != nil { + return 0, nil, nil, err + } + columns = parser.Columns() + startOffset, _ = parser.Pos() + endOffset = startOffset + maxRegionSize + } + for { + curRowsCnt := (endOffset - startOffset) / divisor + rowIDMax := prevRowIdxMax + curRowsCnt + if endOffset != dataFile.FileMeta.FileSize { + r, err := store.Open(ctx, dataFile.FileMeta.Path) + if err != nil { + return 0, nil, nil, err + } + parser := NewCSVParser(&cfg.Mydumper.CSV, r, int64(cfg.Mydumper.ReadBlockSize), ioWorker, false) + if err = parser.SetPos(endOffset, prevRowIdMax); err != nil { + return 0, nil, nil, err + } + pos, err := parser.ReadUntilTokNewLine() + if err != nil { + return 0, nil, nil, err + } + endOffset = pos + parser.Close() + } + regions = append(regions, + &TableRegion{ + DB: meta.DB, + Table: meta.Name, + FileMeta: dataFile.FileMeta, + Chunk: Chunk{ + Offset: startOffset, + EndOffset: endOffset, + PrevRowIDMax: prevRowIdxMax, + RowIDMax: rowIDMax, + Columns: columns, + }, + }) + dataFileSizes = append(dataFileSizes, float64(endOffset-startOffset)) + prevRowIdxMax = rowIDMax + if endOffset == dataFile.FileMeta.FileSize { + break + } + startOffset = endOffset + if endOffset += maxRegionSize; endOffset > dataFile.FileMeta.FileSize { + endOffset = dataFile.FileMeta.FileSize + } + } + return prevRowIdxMax, regions, dataFileSizes, nil +} diff --git a/pkg/lightning/mydump/region_test.go b/pkg/lightning/mydump/region_test.go new file mode 100644 index 000000000..4c0062721 --- /dev/null +++ b/pkg/lightning/mydump/region_test.go @@ -0,0 +1,248 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package mydump_test + +import ( + "context" + "log" + "os" + + "github.com/pingcap/br/pkg/storage" + + . "github.com/pingcap/check" + + "github.com/pingcap/br/pkg/lightning/config" + . "github.com/pingcap/br/pkg/lightning/mydump" + "github.com/pingcap/br/pkg/lightning/worker" +) + +var _ = Suite(&testMydumpRegionSuite{}) + +type testMydumpRegionSuite struct{} + +func (s *testMydumpRegionSuite) SetUpSuite(c *C) {} +func (s *testMydumpRegionSuite) TearDownSuite(c *C) {} + +// var expectedTuplesCount = map[string]int64{ +// "i": 1, +// "report_case_high_risk": 1, +// "tbl_autoid": 10000, +// "tbl_multi_index": 10000, +// } + +func getFileSize(file string) (int64, error) { + fd, err := os.Open(file) + if err != nil { + return -1, err + } + defer fd.Close() + + fstat, err := fd.Stat() + if err != nil { + return -1, err + } + + return fstat.Size(), nil +} + +/* + TODO : test with specified 'regionBlockSize' ... +*/ +func (s *testMydumpRegionSuite) TestTableRegion(c *C) { + cfg := newConfigWithSourceDir("./examples") + loader, _ := NewMyDumpLoader(context.Background(), cfg) + dbMeta := loader.GetDatabases()[0] + + ioWorkers := worker.NewPool(context.Background(), 1, "io") + for _, meta := range dbMeta.Tables { + regions, err := MakeTableRegions(context.Background(), meta, 1, cfg, ioWorkers, loader.GetStore()) + c.Assert(err, IsNil) + + // check - region-size vs file-size + var tolFileSize int64 = 0 + for _, file := range meta.DataFiles { + tolFileSize += file.FileMeta.FileSize + } + var tolRegionSize int64 = 0 + for _, region := range regions { + tolRegionSize += region.Size() + } + c.Assert(tolRegionSize, Equals, tolFileSize) + + // // check - rows num + // var tolRows int64 = 0 + // for _, region := range regions { + // tolRows += region.Rows() + // } + // c.Assert(tolRows, Equals, expectedTuplesCount[table]) + + // check - range + regionNum := len(regions) + preReg := regions[0] + for i := 1; i < regionNum; i++ { + reg := regions[i] + if preReg.FileMeta.Path == reg.FileMeta.Path { + c.Assert(reg.Offset(), Equals, preReg.Offset()+preReg.Size()) + c.Assert(reg.RowIDMin(), Equals, preReg.RowIDMin()+preReg.Rows()) + } else { + c.Assert(reg.Offset, Equals, 0) + c.Assert(reg.RowIDMin(), Equals, 1) + } + preReg = reg + } + } +} + +func (s *testMydumpRegionSuite) TestAllocateEngineIDs(c *C) { + dataFileSizes := make([]float64, 700) + for i := range dataFileSizes { + dataFileSizes[i] = 1.0 + } + filesRegions := make([]*TableRegion, 0, len(dataFileSizes)) + for range dataFileSizes { + filesRegions = append(filesRegions, new(TableRegion)) + } + + checkEngineSizes := func(what string, expected map[int32]int) { + actual := make(map[int32]int) + for _, region := range filesRegions { + actual[region.EngineID]++ + } + c.Assert(actual, DeepEquals, expected, Commentf("%s", what)) + } + + // Batch size > Total size => Everything in the zero batch. + AllocateEngineIDs(filesRegions, dataFileSizes, 1000, 0.5, 1000) + checkEngineSizes("no batching", map[int32]int{ + 0: 700, + }) + + // Allocate 3 engines. + AllocateEngineIDs(filesRegions, dataFileSizes, 200, 0.5, 1000) + checkEngineSizes("batch size = 200", map[int32]int{ + 0: 170, + 1: 213, + 2: 317, + }) + + // Allocate 3 engines with an alternative ratio + AllocateEngineIDs(filesRegions, dataFileSizes, 200, 0.6, 1000) + checkEngineSizes("batch size = 200, ratio = 0.6", map[int32]int{ + 0: 160, + 1: 208, + 2: 332, + }) + + // Allocate 5 engines. + AllocateEngineIDs(filesRegions, dataFileSizes, 100, 0.5, 1000) + checkEngineSizes("batch size = 100", map[int32]int{ + 0: 93, + 1: 105, + 2: 122, + 3: 153, + 4: 227, + }) + + // Number of engines > table concurrency + AllocateEngineIDs(filesRegions, dataFileSizes, 50, 0.5, 4) + checkEngineSizes("batch size = 50, limit table conc = 4", map[int32]int{ + 0: 50, + 1: 59, + 2: 73, + 3: 110, + 4: 50, + 5: 50, + 6: 50, + 7: 50, + 8: 50, + 9: 50, + 10: 50, + 11: 50, + 12: 8, + }) + + // Zero ratio = Uniform + AllocateEngineIDs(filesRegions, dataFileSizes, 100, 0.0, 1000) + checkEngineSizes("batch size = 100, ratio = 0", map[int32]int{ + 0: 100, + 1: 100, + 2: 100, + 3: 100, + 4: 100, + 5: 100, + 6: 100, + }) +} + +func (s *testMydumpRegionSuite) TestSplitLargeFile(c *C) { + meta := &MDTableMeta{ + DB: "csv", + Name: "large_csv_file", + } + cfg := &config.Config{ + Mydumper: config.MydumperRuntime{ + ReadBlockSize: config.ReadBlockSize, + CSV: config.CSVConfig{ + Separator: ",", + Delimiter: "", + Header: true, + TrimLastSep: false, + NotNull: false, + Null: "NULL", + BackslashEscape: true, + }, + StrictFormat: true, + Filter: []string{"*.*"}, + }, + } + filePath := "./csv/split_large_file.csv" + dataFileInfo, err := os.Stat(filePath) + if err != nil { + log.Fatal(err) + } + fileSize := dataFileInfo.Size() + fileInfo := FileInfo{FileMeta: SourceFileMeta{Path: filePath, Type: SourceTypeCSV, FileSize: fileSize}} + colCnt := int64(3) + columns := []string{"a", "b", "c"} + for _, tc := range []struct { + maxRegionSize config.ByteSize + chkCnt int + offsets [][]int64 + }{ + {1, 4, [][]int64{{6, 12}, {12, 18}, {18, 24}, {24, 30}}}, + {6, 2, [][]int64{{6, 18}, {18, 30}}}, + {8, 2, [][]int64{{6, 18}, {18, 30}}}, + {12, 2, [][]int64{{6, 24}, {24, 30}}}, + {13, 2, [][]int64{{6, 24}, {24, 30}}}, + {18, 1, [][]int64{{6, 30}}}, + {19, 1, [][]int64{{6, 30}}}, + } { + cfg.Mydumper.MaxRegionSize = tc.maxRegionSize + prevRowIdxMax := int64(0) + ioWorker := worker.NewPool(context.Background(), 4, "io") + + store, err := storage.NewLocalStorage(".") + c.Assert(err, IsNil) + + _, regions, _, err := SplitLargeFile(context.Background(), meta, cfg, fileInfo, colCnt, prevRowIdxMax, ioWorker, store) + c.Assert(err, IsNil) + c.Assert(len(regions), Equals, tc.chkCnt) + for i := range tc.offsets { + c.Assert(regions[i].Chunk.Offset, Equals, tc.offsets[i][0]) + c.Assert(regions[i].Chunk.EndOffset, Equals, tc.offsets[i][1]) + c.Assert(len(regions[i].Chunk.Columns), Equals, len(columns)) + c.Assert(regions[i].Chunk.Columns, DeepEquals, columns) + } + } +} diff --git a/pkg/lightning/mydump/router.go b/pkg/lightning/mydump/router.go new file mode 100644 index 000000000..10b87e0b1 --- /dev/null +++ b/pkg/lightning/mydump/router.go @@ -0,0 +1,339 @@ +package mydump + +import ( + "regexp" + "strconv" + "strings" + + "github.com/tikv/pd/pkg/slice" + + "github.com/pingcap/errors" + "github.com/pingcap/tidb-tools/pkg/filter" + + "github.com/pingcap/br/pkg/lightning/config" +) + +type SourceType int + +const ( + SourceTypeIgnore SourceType = iota + SourceTypeSchemaSchema + SourceTypeTableSchema + SourceTypeSQL + SourceTypeCSV + SourceTypeParquet + SourceTypeViewSchema +) + +const ( + SchemaSchema = "schema-schema" + TableSchema = "table-schema" + ViewSchema = "view-schema" + TypeSQL = "sql" + TypeCSV = "csv" + TypeParquet = "parquet" + TypeIgnore = "ignore" +) + +type Compression int + +const ( + CompressionNone Compression = iota + CompressionGZ + CompressionLZ4 + CompressionZStd + CompressionXZ +) + +func parseSourceType(t string) (SourceType, error) { + switch strings.ToLower(strings.TrimSpace(t)) { + case SchemaSchema: + return SourceTypeSchemaSchema, nil + case TableSchema: + return SourceTypeTableSchema, nil + case TypeSQL: + return SourceTypeSQL, nil + case TypeCSV: + return SourceTypeCSV, nil + case TypeParquet: + return SourceTypeParquet, nil + case TypeIgnore: + return SourceTypeIgnore, nil + case ViewSchema: + return SourceTypeViewSchema, nil + default: + return SourceTypeIgnore, errors.Errorf("unknown source type '%s'", t) + } +} + +func (s SourceType) String() string { + switch s { + case SourceTypeSchemaSchema: + return SchemaSchema + case SourceTypeTableSchema: + return TableSchema + case SourceTypeCSV: + return TypeCSV + case SourceTypeSQL: + return TypeSQL + case SourceTypeParquet: + return TypeParquet + case SourceTypeViewSchema: + return ViewSchema + default: + return TypeIgnore + } +} + +func parseCompressionType(t string) (Compression, error) { + switch strings.ToLower(strings.TrimSpace(t)) { + case "gz": + return CompressionGZ, nil + case "lz4": + return CompressionLZ4, nil + case "zstd": + return CompressionZStd, nil + case "xz": + return CompressionXZ, nil + case "": + return CompressionNone, nil + default: + return CompressionNone, errors.Errorf("invalid compression type '%s'", t) + } +} + +var expandVariablePattern = regexp.MustCompile(`\$(?:\$|[\pL\p{Nd}_]+|\{[\pL\p{Nd}_]+\})`) + +var defaultFileRouteRules = []*config.FileRouteRule{ + // ignore *-schema-trigger.sql, *-schema-post.sql files + {Pattern: `(?i).*(-schema-trigger|-schema-post)\.sql$`, Type: "ignore"}, + // db schema create file pattern, matches files like '{schema}-schema-create.sql' + {Pattern: `(?i)^(?:[^/]*/)*([^/.]+)-schema-create\.sql$`, Schema: "$1", Table: "", Type: SchemaSchema}, + // table schema create file pattern, matches files like '{schema}.{table}-schema.sql' + {Pattern: `(?i)^(?:[^/]*/)*([^/.]+)\.(.*?)-schema\.sql$`, Schema: "$1", Table: "$2", Type: TableSchema}, + // view schema create file pattern, matches files like '{schema}.{table}-schema-view.sql' + {Pattern: `(?i)^(?:[^/]*/)*([^/.]+)\.(.*?)-schema-view\.sql$`, Schema: "$1", Table: "$2", Type: ViewSchema}, + // source file pattern, matches files like '{schema}.{table}.0001.{sql|csv}' + {Pattern: `(?i)^(?:[^/]*/)*([^/.]+)\.(.*?)(?:\.([0-9]+))?\.(sql|csv|parquet)$`, Schema: "$1", Table: "$2", Type: "$4", Key: "$3"}, +} + +// // RouteRule is a rule to route file path to target schema/table +type FileRouter interface { + // Route apply rule to path. Return nil if path doesn't math route rule; + // return error if path match route rule but the captured value for field is invalid + Route(path string) (*RouteResult, error) +} + +// chainRouters aggregates multi `FileRouter` as a router +type chainRouters []FileRouter + +func (c chainRouters) Route(path string) (*RouteResult, error) { + for _, r := range c { + res, err := r.Route(path) + if err != nil { + return nil, err + } + if res != nil { + return res, nil + } + } + return nil, nil +} + +func NewFileRouter(cfg []*config.FileRouteRule) (FileRouter, error) { + res := make([]FileRouter, 0, len(cfg)) + p := regexRouterParser{} + for _, c := range cfg { + rule, err := p.Parse(c) + if err != nil { + return nil, err + } + res = append(res, rule) + } + return chainRouters(res), nil +} + +// `RegexRouter` is a `FileRouter` implement that apply specific regex pattern to filepath. +// if regex pattern match, then each extractors with capture the matched regexp pattern and +// set value to target field in `RouteResult` +type RegexRouter struct { + pattern *regexp.Regexp + extractors []patExpander +} + +func (r *RegexRouter) Route(path string) (*RouteResult, error) { + indexes := r.pattern.FindStringSubmatchIndex(path) + if len(indexes) == 0 { + return nil, nil + } + result := &RouteResult{} + for _, e := range r.extractors { + err := e.Expand(r.pattern, path, indexes, result) + if err != nil { + return nil, err + } + } + return result, nil +} + +type regexRouterParser struct{} + +func (p regexRouterParser) Parse(r *config.FileRouteRule) (*RegexRouter, error) { + rule := &RegexRouter{} + if r.Path == "" && r.Pattern == "" { + return nil, errors.New("`path` and `pattern` must not be both empty in [[mydumper.files]]") + } + if r.Path != "" && r.Pattern != "" { + return nil, errors.New("can't set both `path` and `pattern` field in [[mydumper.files]]") + } + if r.Path != "" { + // convert constant string as a regexp pattern + r.Pattern = regexp.QuoteMeta(r.Path) + // escape all '$' by '$$' in match templates + quoteTmplFn := func(t string) string { return strings.ReplaceAll(t, "$", "$$") } + r.Table = quoteTmplFn(r.Table) + r.Schema = quoteTmplFn(r.Schema) + r.Type = quoteTmplFn(r.Type) + r.Compression = quoteTmplFn(r.Compression) + r.Key = quoteTmplFn(r.Key) + + } + pattern, err := regexp.Compile(r.Pattern) + if err != nil { + return nil, errors.Trace(err) + } + rule.pattern = pattern + + err = p.parseFieldExtractor(rule, "type", r.Type, func(result *RouteResult, value string) error { + ty, err := parseSourceType(value) + if err != nil { + return err + } + result.Type = ty + return nil + }) + if err != nil { + return nil, err + } + // ignore pattern needn't parse other fields + if r.Type == TypeIgnore { + return rule, nil + } + + err = p.parseFieldExtractor(rule, "schema", r.Schema, func(result *RouteResult, value string) error { + result.Schema = value + return nil + }) + if err != nil { + return nil, err + } + + // special case: when the pattern is for db schema, should not parse table name + if r.Type != SchemaSchema { + err = p.parseFieldExtractor(rule, "table", r.Table, func(result *RouteResult, value string) error { + result.Name = value + return nil + }) + if err != nil { + return nil, err + } + } + + if len(r.Key) > 0 { + err = p.parseFieldExtractor(rule, "key", r.Key, func(result *RouteResult, value string) error { + result.Key = value + return nil + }) + if err != nil { + return nil, err + } + } + + if len(r.Compression) > 0 { + err = p.parseFieldExtractor(rule, "compression", r.Compression, func(result *RouteResult, value string) error { + // TODO: should support restore compressed source files + compression, err := parseCompressionType(value) + if err != nil { + return err + } + if compression != CompressionNone { + return errors.New("Currently we don't support restore compressed source file yet") + } + result.Compression = compression + return nil + }) + if err != nil { + return nil, err + } + } + + return rule, nil +} + +// parse each field extractor in `p.r` and set them to p.rule +func (p regexRouterParser) parseFieldExtractor( + rule *RegexRouter, + field, + fieldPattern string, + applyFn func(result *RouteResult, value string) error, +) error { + // pattern is empty, return default rule + if len(fieldPattern) == 0 { + return errors.Errorf("field '%s' match pattern can't be empty", field) + } + + // check and parse regexp template + if err := p.checkSubPatterns(rule.pattern, fieldPattern); err != nil { + return errors.Trace(err) + } + rule.extractors = append(rule.extractors, patExpander{ + template: fieldPattern, + applyFn: applyFn, + }) + return nil +} + +func (p regexRouterParser) checkSubPatterns(pat *regexp.Regexp, t string) error { + subPats := expandVariablePattern.FindAllString(t, -1) + for _, subVar := range subPats { + var tmplName string + switch { + case subVar == "$$": + continue + case strings.HasPrefix(subVar, "${"): + tmplName = subVar[2 : len(subVar)-1] + default: + tmplName = subVar[1:] + } + if number, err := strconv.Atoi(tmplName); err == nil { + if number > pat.NumSubexp() { + return errors.Errorf("sub pattern capture '%s' out of range", subVar) + } + } else if !slice.AnyOf(pat.SubexpNames(), func(i int) bool { + // FIXME: we should use re.SubexpIndex here, but not supported in go1.13 yet + return pat.SubexpNames()[i] == tmplName + }) { + return errors.Errorf("invalid named capture '%s'", subVar) + } + } + + return nil +} + +// patExpander extract string by expanding template with the regexp pattern +type patExpander struct { + template string + applyFn func(result *RouteResult, value string) error +} + +func (p *patExpander) Expand(pattern *regexp.Regexp, path string, matchIndex []int, result *RouteResult) error { + value := pattern.ExpandString([]byte{}, p.template, path, matchIndex) + return p.applyFn(result, string(value)) +} + +type RouteResult struct { + filter.Table + Key string + Compression Compression + Type SourceType +} diff --git a/pkg/lightning/mydump/router_test.go b/pkg/lightning/mydump/router_test.go new file mode 100644 index 000000000..e308bd842 --- /dev/null +++ b/pkg/lightning/mydump/router_test.go @@ -0,0 +1,256 @@ +package mydump + +import ( + "strings" + + . "github.com/pingcap/check" + "github.com/pingcap/tidb-tools/pkg/filter" + + "github.com/pingcap/br/pkg/lightning/config" +) + +var _ = Suite(&testFileRouterSuite{}) + +type testFileRouterSuite struct{} + +func (t *testFileRouterSuite) TestRouteParser(c *C) { + // valid rules + rules := []*config.FileRouteRule{ + {Pattern: `^(?:[^/]*/)*([^/.]+)\.([^./]+)(?:\.[0-9]+)?\.(csv|sql)`, Schema: "$1", Table: "$2", Type: "$3"}, + {Pattern: `^.+\.(csv|sql)`, Schema: "test", Table: "t", Type: "$1"}, + {Pattern: `^(?:[^/]*/)*(?P[^/.]+)\.(?P[^./]+)(?:\.(?P[0-9]+))?\.(?Pcsv|sql)(?:\.(?P[A-Za-z0-9]+))?$`, Schema: "$schema", Table: "$table", Type: "$type", Key: "$key", Compression: "$cp"}, + {Pattern: `^(?:[^/]*/)*(?P[^/.]+)\.(?P
[^./]+)(?:\.([0-9]+))?\.(csv|sql)(?:\.(?P[A-Za-z0-9]+))?$`, Schema: "${schema}s", Table: "$table", Type: "${3}_0", Key: "$4", Compression: "$cp"}, + {Pattern: `^(?:[^/]*/)*([^/.]+)\.(?P
[^./]+)(?:\.([0-9]+))?\.(csv|sql)(?:\.(?P[A-Za-z0-9]+))?$`, Schema: "${1}s", Table: "$table", Type: "${3}_0", Key: "$4", Compression: "$cp"}, + {Pattern: `^(?:[^/]*/)*([^/.]+)\.([^./]+)(?:\.[0-9]+)?\.(csv|sql)`, Schema: "$1-schema", Table: "$1-table", Type: "$2"}, + } + for _, r := range rules { + _, err := NewFileRouter([]*config.FileRouteRule{r}) + c.Assert(err, IsNil) + } + + // invalid rules + invalidRules := []*config.FileRouteRule{ + {Pattern: `^(?:[^/]*/)*(?P\.(?P
[^./]+).*$`, Schema: "$test", Table: "$table"}, + {Pattern: `^(?:[^/]*/)*(?P[^/.]+)\.(?P
[^./]+).*$`, Schema: "$schemas", Table: "$table"}, + {Pattern: `^(?:[^/]*/)*([^/.]+)\.([^./]+)(?:\.[0-9]+)?\.(csv|sql)`, Schema: "$1", Table: "$2", Type: "$3", Key: "$4"}, + } + for _, r := range invalidRules { + _, err := NewFileRouter([]*config.FileRouteRule{r}) + c.Assert(err, NotNil) + } +} + +func (t *testFileRouterSuite) TestInvalidRouteRule(c *C) { + rule := &config.FileRouteRule{} + rules := []*config.FileRouteRule{rule} + _, err := NewFileRouter(rules) + c.Assert(err, ErrorMatches, "`path` and `pattern` must not be both empty in \\[\\[mydumper.files\\]\\]") + + rule.Pattern = `^(?:[^/]*/)*([^/.]+)\.(?P
[^./]+)(?:\.(?P[0-9]+))?\.(?Pcsv|sql)(?:\.(?P[A-Za-z0-9]+))?$` + _, err = NewFileRouter(rules) + c.Assert(err, ErrorMatches, "field 'type' match pattern can't be empty") + + rule.Type = "$type" + _, err = NewFileRouter(rules) + c.Assert(err, ErrorMatches, "field 'schema' match pattern can't be empty") + + rule.Schema = "$schema" + _, err = NewFileRouter(rules) + c.Assert(err, ErrorMatches, "invalid named capture '\\$schema'") + + rule.Schema = "$1" + _, err = NewFileRouter(rules) + c.Assert(err, ErrorMatches, "field 'table' match pattern can't be empty") + + rule.Table = "$table" + _, err = NewFileRouter(rules) + c.Assert(err, IsNil) + + rule.Path = "/tmp/1.sql" + _, err = NewFileRouter(rules) + c.Assert(err, ErrorMatches, "can't set both `path` and `pattern` field in \\[\\[mydumper.files\\]\\]") +} + +func (t *testFileRouterSuite) TestSingleRouteRule(c *C) { + rules := []*config.FileRouteRule{ + {Pattern: `^(?:[^/]*/)*([^/.]+)\.(?P
[^./]+)(?:\.(?P[0-9]+))?\.(?Pcsv|sql)(?:\.(?P[A-Za-z0-9]+))?$`, Schema: "$1", Table: "$table", Type: "$type", Key: "$key", Compression: "$cp"}, + } + + r, err := NewFileRouter(rules) + c.Assert(err, IsNil) + + inputOutputMap := map[string][]string{ + "my_schema.my_table.sql": {"my_schema", "my_table", "", "", "sql"}, + "/test/123/my_schema.my_table.sql": {"my_schema", "my_table", "", "", "sql"}, + "my_dir/my_schema.my_table.csv": {"my_schema", "my_table", "", "", "csv"}, + "my_schema.my_table.0001.sql": {"my_schema", "my_table", "0001", "", "sql"}, + } + for path, fields := range inputOutputMap { + res, err := r.Route(path) + c.Assert(err, IsNil) + compress, e := parseCompressionType(fields[3]) + c.Assert(e, IsNil) + ty, e := parseSourceType(fields[4]) + c.Assert(e, IsNil) + exp := &RouteResult{filter.Table{Schema: fields[0], Name: fields[1]}, fields[2], compress, ty} + c.Assert(res, DeepEquals, exp) + } + + notMatchPaths := []string{ + "my_table.sql", + "/schema/table.sql", + "my_schema.my_table.txt", + "my_schema.my_table.001.txt", + "my_schema.my_table.0001-002.sql", + } + for _, p := range notMatchPaths { + res, err := r.Route(p) + c.Assert(res, IsNil) + c.Assert(err, IsNil) + } + + rule := &config.FileRouteRule{Pattern: `^(?:[^/]*/)*([^/.]+)\.(?P
[^./]+)(?:\.(?P[0-9]+))?\.(?P\w+)(?:\.(?P[A-Za-z0-9]+))?$`, Schema: "$1", Table: "$table", Type: "$type", Key: "$key", Compression: "$cp"} + r, err = NewFileRouter([]*config.FileRouteRule{rule}) + c.Assert(err, IsNil) + c.Assert(r, NotNil) + invalidMatchPaths := []string{ + "my_schema.my_table.sql.gz", + "my_schema.my_table.sql.rar", + "my_schema.my_table.txt", + } + for _, p := range invalidMatchPaths { + res, err := r.Route(p) + c.Assert(res, IsNil) + c.Assert(err, NotNil) + } +} + +func (t *testFileRouterSuite) TestMultiRouteRule(c *C) { + // multi rule don't intersect with each other + rules := []*config.FileRouteRule{ + {Pattern: `(?:[^/]*/)*([^/.]+)-schema-create\.sql`, Schema: "$1", Type: SchemaSchema}, + {Pattern: `(?:[^/]*/)*([^/.]+)\.([^/.]+)-schema\.sql$`, Schema: "$1", Table: "$2", Type: TableSchema}, + {Pattern: `(?:[^/]*/)*([^/.]+)\.([^/.]+)-schema-view\.sql$`, Schema: "$1", Table: "$2", Type: ViewSchema}, + {Pattern: `^(?:[^/]*/)*(?P[^/.]+)\.(?P
[^./]+)(?:\.(?P[0-9]+))?\.(?Pcsv|sql)(?:\.(?P[A-Za-z0-9]+))?$`, Schema: "$schema", Table: "$table", Type: "$type", Key: "$key", Compression: "$cp"}, + } + + r, err := NewFileRouter(rules) + c.Assert(err, IsNil) + + inputOutputMap := map[string][]string{ + "test-schema-create.sql": {"test", "", "", "", SchemaSchema}, + "test.t-schema.sql": {"test", "t", "", "", TableSchema}, + "test.v1-schema-view.sql": {"test", "v1", "", "", ViewSchema}, + "my_schema.my_table.sql": {"my_schema", "my_table", "", "", "sql"}, + "/test/123/my_schema.my_table.sql": {"my_schema", "my_table", "", "", "sql"}, + "my_dir/my_schema.my_table.csv": {"my_schema", "my_table", "", "", "csv"}, + "my_schema.my_table.0001.sql": {"my_schema", "my_table", "0001", "", "sql"}, + //"my_schema.my_table.0001.sql.gz": {"my_schema", "my_table", "0001", "gz", "sql"}, + } + for path, fields := range inputOutputMap { + res, err := r.Route(path) + c.Assert(err, IsNil) + if len(fields) == 0 { + c.Assert(res, IsNil) + } else { + compress, e := parseCompressionType(fields[3]) + c.Assert(e, IsNil) + ty, e := parseSourceType(fields[4]) + c.Assert(e, IsNil) + exp := &RouteResult{filter.Table{Schema: fields[0], Name: fields[1]}, fields[2], compress, ty} + c.Assert(res, DeepEquals, exp) + } + } + + // multi rule don't intersect with each other + // add another rule that math same pattern with the third rule, the result should be no different + p := &config.FileRouteRule{Pattern: `^(?P[^/.]+)\.(?P
[^./]+)(?:\.(?P[0-9]+))?\.(?Pcsv|sql)(?:\.(?P[A-Za-z0-9]+))?$`, Schema: "test_schema", Table: "test_table", Type: "$type", Key: "$key", Compression: "$cp"} + rules = append(rules, p) + r, err = NewFileRouter(rules) + c.Assert(err, IsNil) + for path, fields := range inputOutputMap { + res, err := r.Route(path) + c.Assert(err, IsNil) + if len(fields) == 0 { + c.Assert(res, IsNil) + } else { + compress, e := parseCompressionType(fields[3]) + c.Assert(e, IsNil) + ty, e := parseSourceType(fields[4]) + c.Assert(e, IsNil) + exp := &RouteResult{filter.Table{Schema: fields[0], Name: fields[1]}, fields[2], compress, ty} + c.Assert(res, DeepEquals, exp) + } + } +} + +func (t *testFileRouterSuite) TestRouteExpanding(c *C) { + rule := &config.FileRouteRule{ + Pattern: `^(?:[^/]*/)*(?P[^/.]+)\.(?P[^./]+)(?:\.(?P[0-9]+))?\.(?Pcsv|sql)(?:\.(?P[A-Za-z0-9]+))?$`, + Schema: "$schema", + Type: "$type", + Key: "$key", + Compression: "$cp", + } + path := "db.table.001.sql" + tablePatternResMap := map[string]string{ + "$schema": "db", + "$table_name": "table", + "$schema.$table_name": "db.table", + "${1}": "db", + "${1}_$table_name": "db_table", + "${2}.schema": "table.schema", + "$${2}": "${2}", + "$$table_name": "$table_name", + "$table_name-123": "table-123", + "$$12$1$schema": "$12dbdb", + "${table_name}$$2": "table$2", + "${table_name}$$": "table$", + "{1}$$": "{1}$", + "my_table": "my_table", + } + + for pat, value := range tablePatternResMap { + rule.Table = pat + router, err := NewFileRouter([]*config.FileRouteRule{rule}) + c.Assert(err, IsNil) + res, err := router.Route(path) + c.Assert(err, IsNil) + c.Assert(res, NotNil) + c.Assert(res.Name, Equals, value) + } + + invalidPatterns := []string{"$1_$schema", "$schema_$table_name", "$6"} + for _, pat := range invalidPatterns { + rule.Table = pat + _, err := NewFileRouter([]*config.FileRouteRule{rule}) + c.Assert(err, NotNil) + } +} + +func (t *testFileRouterSuite) TestRouteWithPath(c *C) { + fileName := "myschema.(my_table$1).000.sql" + rule := &config.FileRouteRule{ + Path: fileName, + Schema: "schema", + Table: "my_table$1", + Type: "sql", + Key: "$key", + } + r := *rule + router, err := NewFileRouter([]*config.FileRouteRule{&r}) + c.Assert(err, IsNil) + res, err := router.Route(fileName) + c.Assert(err, IsNil) + c.Assert(res, NotNil) + c.Assert(res.Schema, Equals, rule.Schema) + c.Assert(res.Name, Equals, rule.Table) + ty, _ := parseSourceType(rule.Type) + c.Assert(res.Type, Equals, ty) + c.Assert(res.Key, Equals, rule.Key) + + // replace all '.' by '-', if with plain regex pattern, will still match + res, err = router.Route(strings.ReplaceAll(fileName, ".", "-")) + c.Assert(err, IsNil) + c.Assert(res, IsNil) +} diff --git a/pkg/lightning/restore/checksum.go b/pkg/lightning/restore/checksum.go new file mode 100644 index 000000000..369b20a18 --- /dev/null +++ b/pkg/lightning/restore/checksum.go @@ -0,0 +1,458 @@ +package restore + +import ( + "container/heap" + "context" + "database/sql" + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/google/uuid" + "github.com/pingcap/errors" + "github.com/pingcap/failpoint" + tidbcfg "github.com/pingcap/tidb/config" + "github.com/pingcap/tidb/kv" + "github.com/pingcap/tidb/store/tikv" + "github.com/pingcap/tidb/store/tikv/oracle" + "github.com/pingcap/tipb/go-tipb" + pd "github.com/tikv/pd/client" + "go.uber.org/zap" + + "github.com/pingcap/br/pkg/checksum" + . "github.com/pingcap/br/pkg/lightning/checkpoints" + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/config" + "github.com/pingcap/br/pkg/lightning/log" + "github.com/pingcap/br/pkg/lightning/metric" + "github.com/pingcap/br/pkg/utils" +) + +const ( + preUpdateServiceSafePointFactor = 3 + + maxErrorRetryCount = 3 +) + +var ( + serviceSafePointTTL int64 = 10 * 60 // 10 min in seconds + + minDistSQLScanConcurrency = 4 +) + +// RemoteChecksum represents a checksum result got from tidb. +type RemoteChecksum struct { + Schema string + Table string + Checksum uint64 + TotalKVs uint64 + TotalBytes uint64 +} + +type ChecksumManager interface { + Checksum(ctx context.Context, tableInfo *TidbTableInfo) (*RemoteChecksum, error) +} + +func newChecksumManager(ctx context.Context, rc *RestoreController) (ChecksumManager, error) { + // if we don't need checksum, just return nil + if rc.cfg.TikvImporter.Backend == config.BackendTiDB || rc.cfg.PostRestore.Checksum == config.OpLevelOff { + return nil, nil + } + + pdAddr := rc.cfg.TiDB.PdAddr + pdVersion, err := common.FetchPDVersion(ctx, rc.tls, pdAddr) + if err != nil { + return nil, errors.Trace(err) + } + + // for v4.0.0 or upper, we can use the gc ttl api + var manager ChecksumManager + if pdVersion.Major >= 4 { + tlsOpt := rc.tls.ToPDSecurityOption() + pdCli, err := pd.NewClientWithContext(ctx, []string{pdAddr}, tlsOpt) + if err != nil { + return nil, errors.Trace(err) + } + + // TODO: make tikv.Driver{}.Open use arguments instead of global variables + if tlsOpt.CAPath != "" { + conf := tidbcfg.GetGlobalConfig() + conf.Security.ClusterSSLCA = tlsOpt.CAPath + conf.Security.ClusterSSLCert = tlsOpt.CertPath + conf.Security.ClusterSSLKey = tlsOpt.KeyPath + tidbcfg.StoreGlobalConfig(conf) + } + store, err := tikv.Driver{}.Open(fmt.Sprintf("tikv://%s?disableGC=true", pdAddr)) + if err != nil { + return nil, errors.Trace(err) + } + + manager = newTiKVChecksumManager(store.(tikv.Storage).GetClient(), pdCli, uint(rc.cfg.TiDB.DistSQLScanConcurrency)) + } else { + db, err := rc.tidbGlue.GetDB() + if err != nil { + return nil, errors.Trace(err) + } + manager = newTiDBChecksumExecutor(db) + } + + return manager, nil +} + +// fetch checksum for tidb sql client +type tidbChecksumExecutor struct { + db *sql.DB + manager *gcLifeTimeManager +} + +func newTiDBChecksumExecutor(db *sql.DB) *tidbChecksumExecutor { + return &tidbChecksumExecutor{ + db: db, + manager: newGCLifeTimeManager(), + } +} + +func (e *tidbChecksumExecutor) Checksum(ctx context.Context, tableInfo *TidbTableInfo) (*RemoteChecksum, error) { + var err error + if err = e.manager.addOneJob(ctx, e.db); err != nil { + return nil, err + } + + // set it back finally + defer e.manager.removeOneJob(ctx, e.db) + + tableName := common.UniqueTable(tableInfo.DB, tableInfo.Name) + + task := log.With(zap.String("table", tableName)).Begin(zap.InfoLevel, "remote checksum") + + // ADMIN CHECKSUM TABLE
,
example. + // mysql> admin checksum table test.t; + // +---------+------------+---------------------+-----------+-------------+ + // | Db_name | Table_name | Checksum_crc64_xor | Total_kvs | Total_bytes | + // +---------+------------+---------------------+-----------+-------------+ + // | test | t | 8520875019404689597 | 7296873 | 357601387 | + // +---------+------------+---------------------+-----------+-------------+ + + cs := RemoteChecksum{} + err = common.SQLWithRetry{DB: e.db, Logger: task.Logger}.QueryRow(ctx, "compute remote checksum", + "ADMIN CHECKSUM TABLE "+tableName, &cs.Schema, &cs.Table, &cs.Checksum, &cs.TotalKVs, &cs.TotalBytes, + ) + dur := task.End(zap.ErrorLevel, err) + metric.ChecksumSecondsHistogram.Observe(dur.Seconds()) + if err != nil { + return nil, errors.Trace(err) + } + return &cs, nil +} + +// DoChecksum do checksum for tables. +// table should be in .
, format. e.g. foo.bar +func DoChecksum(ctx context.Context, table *TidbTableInfo) (*RemoteChecksum, error) { + var err error + manager, ok := ctx.Value(&checksumManagerKey).(ChecksumManager) + if !ok { + return nil, errors.New("No gcLifeTimeManager found in context, check context initialization") + } + + task := log.With(zap.String("table", table.Name)).Begin(zap.InfoLevel, "remote checksum") + + cs, err := manager.Checksum(ctx, table) + dur := task.End(zap.ErrorLevel, err) + metric.ChecksumSecondsHistogram.Observe(dur.Seconds()) + + return cs, err +} + +type gcLifeTimeManager struct { + runningJobsLock sync.Mutex + runningJobs int + oriGCLifeTime string +} + +func newGCLifeTimeManager() *gcLifeTimeManager { + // Default values of three member are enough to initialize this struct + return &gcLifeTimeManager{} +} + +// Pre- and post-condition: +// if m.runningJobs == 0, GC life time has not been increased. +// if m.runningJobs > 0, GC life time has been increased. +// m.runningJobs won't be negative(overflow) since index concurrency is relatively small +func (m *gcLifeTimeManager) addOneJob(ctx context.Context, db *sql.DB) error { + m.runningJobsLock.Lock() + defer m.runningJobsLock.Unlock() + + if m.runningJobs == 0 { + oriGCLifeTime, err := ObtainGCLifeTime(ctx, db) + if err != nil { + return err + } + m.oriGCLifeTime = oriGCLifeTime + err = increaseGCLifeTime(ctx, m, db) + if err != nil { + return err + } + } + m.runningJobs += 1 + return nil +} + +// Pre- and post-condition: +// if m.runningJobs == 0, GC life time has been tried to recovered. If this try fails, a warning will be printed. +// if m.runningJobs > 0, GC life time has not been recovered. +// m.runningJobs won't minus to negative since removeOneJob follows a successful addOneJob. +func (m *gcLifeTimeManager) removeOneJob(ctx context.Context, db *sql.DB) { + m.runningJobsLock.Lock() + defer m.runningJobsLock.Unlock() + + m.runningJobs -= 1 + if m.runningJobs == 0 { + err := UpdateGCLifeTime(ctx, db, m.oriGCLifeTime) + if err != nil { + query := fmt.Sprintf( + "UPDATE mysql.tidb SET VARIABLE_VALUE = '%s' WHERE VARIABLE_NAME = 'tikv_gc_life_time'", + m.oriGCLifeTime, + ) + log.L().Warn("revert GC lifetime failed, please reset the GC lifetime manually after Lightning completed", + zap.String("query", query), + log.ShortError(err), + ) + } + } +} + +func increaseGCLifeTime(ctx context.Context, manager *gcLifeTimeManager, db *sql.DB) (err error) { + // checksum command usually takes a long time to execute, + // so here need to increase the gcLifeTime for single transaction. + var increaseGCLifeTime bool + if manager.oriGCLifeTime != "" { + ori, err := time.ParseDuration(manager.oriGCLifeTime) + if err != nil { + return errors.Trace(err) + } + if ori < defaultGCLifeTime { + increaseGCLifeTime = true + } + } else { + increaseGCLifeTime = true + } + + if increaseGCLifeTime { + err = UpdateGCLifeTime(ctx, db, defaultGCLifeTime.String()) + if err != nil { + return err + } + } + + failpoint.Inject("IncreaseGCUpdateDuration", nil) + + return nil +} + +type tikvChecksumManager struct { + client kv.Client + manager gcTTLManager + distSQLScanConcurrency uint +} + +// newTiKVChecksumManager return a new tikv checksum manager +func newTiKVChecksumManager(client kv.Client, pdClient pd.Client, distSQLScanConcurrency uint) *tikvChecksumManager { + return &tikvChecksumManager{ + client: client, + manager: newGCTTLManager(pdClient), + distSQLScanConcurrency: distSQLScanConcurrency, + } +} + +func (e *tikvChecksumManager) checksumDB(ctx context.Context, tableInfo *TidbTableInfo) (*RemoteChecksum, error) { + executor, err := checksum.NewExecutorBuilder(tableInfo.Core, oracle.ComposeTS(time.Now().Unix()*1000, 0)). + SetConcurrency(e.distSQLScanConcurrency). + Build() + if err != nil { + return nil, errors.Trace(err) + } + + distSQLScanConcurrency := int(e.distSQLScanConcurrency) + for i := 0; i < maxErrorRetryCount; i++ { + _ = executor.Each(func(request *kv.Request) error { + request.Concurrency = distSQLScanConcurrency + return nil + }) + var execRes *tipb.ChecksumResponse + execRes, err = executor.Execute(ctx, e.client, func() {}) + if err == nil { + return &RemoteChecksum{ + Schema: tableInfo.DB, + Table: tableInfo.Name, + Checksum: execRes.Checksum, + TotalBytes: execRes.TotalBytes, + TotalKVs: execRes.TotalKvs, + }, nil + } + + log.L().Warn("remote checksum failed", zap.String("db", tableInfo.DB), + zap.String("table", tableInfo.Name), zap.Error(err), + zap.Int("concurrency", distSQLScanConcurrency), zap.Int("retry", i)) + + // do not retry context.Canceled error + if !common.IsRetryableError(err) { + break + } + if distSQLScanConcurrency > minDistSQLScanConcurrency { + distSQLScanConcurrency = utils.MaxInt(distSQLScanConcurrency/2, minDistSQLScanConcurrency) + } + } + + return nil, err +} + +func (e *tikvChecksumManager) Checksum(ctx context.Context, tableInfo *TidbTableInfo) (*RemoteChecksum, error) { + tbl := common.UniqueTable(tableInfo.DB, tableInfo.Name) + err := e.manager.addOneJob(ctx, tbl, oracle.ComposeTS(time.Now().Unix()*1000, 0)) + if err != nil { + return nil, errors.Trace(err) + } + + return e.checksumDB(ctx, tableInfo) +} + +type tableChecksumTS struct { + table string + gcSafeTS uint64 +} + +// following function are for implement `heap.Interface` + +func (m *gcTTLManager) Len() int { + return len(m.tableGCSafeTS) +} + +func (m *gcTTLManager) Less(i, j int) bool { + return m.tableGCSafeTS[i].gcSafeTS < m.tableGCSafeTS[j].gcSafeTS +} + +func (m *gcTTLManager) Swap(i, j int) { + m.tableGCSafeTS[i], m.tableGCSafeTS[j] = m.tableGCSafeTS[j], m.tableGCSafeTS[i] +} + +func (m *gcTTLManager) Push(x interface{}) { + m.tableGCSafeTS = append(m.tableGCSafeTS, x.(*tableChecksumTS)) +} + +func (m *gcTTLManager) Pop() interface{} { + i := m.tableGCSafeTS[len(m.tableGCSafeTS)-1] + m.tableGCSafeTS = m.tableGCSafeTS[:len(m.tableGCSafeTS)-1] + return i +} + +type gcTTLManager struct { + lock sync.Mutex + pdClient pd.Client + // tableGCSafeTS is a binary heap that stored active checksum jobs GC safe point ts + tableGCSafeTS []*tableChecksumTS + currentTs uint64 + serviceID string + // 0 for not start, otherwise started + started uint32 +} + +func newGCTTLManager(pdClient pd.Client) gcTTLManager { + return gcTTLManager{ + pdClient: pdClient, + serviceID: fmt.Sprintf("lightning-%s", uuid.New()), + } +} + +func (m *gcTTLManager) addOneJob(ctx context.Context, table string, ts uint64) error { + // start gc ttl loop if not started yet. + if atomic.CompareAndSwapUint32(&m.started, 0, 1) { + m.start(ctx) + } + m.lock.Lock() + defer m.lock.Unlock() + var curTs uint64 + if len(m.tableGCSafeTS) > 0 { + curTs = m.tableGCSafeTS[0].gcSafeTS + } + m.Push(&tableChecksumTS{table: table, gcSafeTS: ts}) + heap.Fix(m, len(m.tableGCSafeTS)-1) + m.currentTs = m.tableGCSafeTS[0].gcSafeTS + if curTs == 0 || m.currentTs < curTs { + return m.doUpdateGCTTL(ctx, m.currentTs) + } + return nil +} + +func (m *gcTTLManager) removeOneJob(table string) { + m.lock.Lock() + defer m.lock.Unlock() + idx := -1 + for i := 0; i < len(m.tableGCSafeTS); i++ { + if m.tableGCSafeTS[i].table == table { + idx = i + break + } + } + + if idx >= 0 { + l := len(m.tableGCSafeTS) + m.tableGCSafeTS[idx] = m.tableGCSafeTS[l-1] + m.tableGCSafeTS = m.tableGCSafeTS[:l-1] + if l > 1 && idx < l-1 { + heap.Fix(m, idx) + } + } + + var newTs uint64 + if len(m.tableGCSafeTS) > 0 { + newTs = m.tableGCSafeTS[0].gcSafeTS + } + m.currentTs = newTs +} + +func (m *gcTTLManager) updateGCTTL(ctx context.Context) error { + m.lock.Lock() + currentTs := m.currentTs + m.lock.Unlock() + return m.doUpdateGCTTL(ctx, currentTs) +} + +func (m *gcTTLManager) doUpdateGCTTL(ctx context.Context, ts uint64) error { + log.L().Debug("update PD safePoint limit with TTL", + zap.Uint64("currnet_ts", ts)) + var err error + if ts > 0 { + _, err = m.pdClient.UpdateServiceGCSafePoint(ctx, + m.serviceID, serviceSafePointTTL, ts) + } + return err +} + +func (m *gcTTLManager) start(ctx context.Context) { + // It would be OK since TTL won't be zero, so gapTime should > `0. + updateGapTime := time.Duration(serviceSafePointTTL) * time.Second / preUpdateServiceSafePointFactor + + updateTick := time.NewTicker(updateGapTime) + + updateGCTTL := func() { + if err := m.updateGCTTL(ctx); err != nil { + log.L().Warn("failed to update service safe point, checksum may fail if gc triggered", zap.Error(err)) + } + } + + // trigger a service gc ttl at start + updateGCTTL() + go func() { + defer updateTick.Stop() + for { + select { + case <-ctx.Done(): + log.L().Info("service safe point keeper exited") + return + case <-updateTick.C: + updateGCTTL() + } + } + }() +} diff --git a/pkg/lightning/restore/checksum_test.go b/pkg/lightning/restore/checksum_test.go new file mode 100644 index 000000000..02cfdfb39 --- /dev/null +++ b/pkg/lightning/restore/checksum_test.go @@ -0,0 +1,406 @@ +package restore + +import ( + "context" + "database/sql" + "fmt" + "sort" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/pingcap/tidb/util/memory" + + "github.com/pingcap/parser" + "github.com/pingcap/parser/ast" + "github.com/pingcap/tidb/ddl" + "github.com/pingcap/tidb/store/tikv/oracle" + tmock "github.com/pingcap/tidb/util/mock" + + "github.com/pingcap/tidb/kv" + "github.com/pingcap/tipb/go-tipb" + + pd "github.com/tikv/pd/client" + + "github.com/DATA-DOG/go-sqlmock" + . "github.com/pingcap/check" + "github.com/pingcap/errors" + + . "github.com/pingcap/br/pkg/lightning/checkpoints" +) + +var _ = Suite(&checksumSuite{}) + +type checksumSuite struct{} + +func MockDoChecksumCtx(db *sql.DB) context.Context { + ctx := context.Background() + manager := newTiDBChecksumExecutor(db) + return context.WithValue(ctx, &checksumManagerKey, manager) +} + +func (s *checksumSuite) TestDoChecksum(c *C) { + db, mock, err := sqlmock.New() + c.Assert(err, IsNil) + + mock.ExpectQuery("\\QSELECT VARIABLE_VALUE FROM mysql.tidb WHERE VARIABLE_NAME = 'tikv_gc_life_time'\\E"). + WillReturnRows(sqlmock.NewRows([]string{"VARIABLE_VALUE"}).AddRow("10m")) + mock.ExpectExec("\\QUPDATE mysql.tidb SET VARIABLE_VALUE = ? WHERE VARIABLE_NAME = 'tikv_gc_life_time'\\E"). + WithArgs("100h0m0s"). + WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectQuery("\\QADMIN CHECKSUM TABLE `test`.`t`\\E"). + WillReturnRows( + sqlmock.NewRows([]string{"Db_name", "Table_name", "Checksum_crc64_xor", "Total_kvs", "Total_bytes"}). + AddRow("test", "t", 8520875019404689597, 7296873, 357601387), + ) + mock.ExpectExec("\\QUPDATE mysql.tidb SET VARIABLE_VALUE = ? WHERE VARIABLE_NAME = 'tikv_gc_life_time'\\E"). + WithArgs("10m"). + WillReturnResult(sqlmock.NewResult(2, 1)) + mock.ExpectClose() + + ctx := MockDoChecksumCtx(db) + checksum, err := DoChecksum(ctx, &TidbTableInfo{DB: "test", Name: "t"}) + c.Assert(err, IsNil) + c.Assert(*checksum, DeepEquals, RemoteChecksum{ + Schema: "test", + Table: "t", + Checksum: 8520875019404689597, + TotalKVs: 7296873, + TotalBytes: 357601387, + }) + + c.Assert(db.Close(), IsNil) + c.Assert(mock.ExpectationsWereMet(), IsNil) +} + +func (s *checksumSuite) TestDoChecksumParallel(c *C) { + db, mock, err := sqlmock.New() + c.Assert(err, IsNil) + + mock.ExpectQuery("\\QSELECT VARIABLE_VALUE FROM mysql.tidb WHERE VARIABLE_NAME = 'tikv_gc_life_time'\\E"). + WillReturnRows(sqlmock.NewRows([]string{"VARIABLE_VALUE"}).AddRow("10m")) + mock.ExpectExec("\\QUPDATE mysql.tidb SET VARIABLE_VALUE = ? WHERE VARIABLE_NAME = 'tikv_gc_life_time'\\E"). + WithArgs("100h0m0s"). + WillReturnResult(sqlmock.NewResult(1, 1)) + for i := 0; i < 5; i++ { + mock.ExpectQuery("\\QADMIN CHECKSUM TABLE `test`.`t`\\E"). + WillDelayFor(100 * time.Millisecond). + WillReturnRows( + sqlmock.NewRows([]string{"Db_name", "Table_name", "Checksum_crc64_xor", "Total_kvs", "Total_bytes"}). + AddRow("test", "t", 8520875019404689597, 7296873, 357601387), + ) + } + mock.ExpectExec("\\QUPDATE mysql.tidb SET VARIABLE_VALUE = ? WHERE VARIABLE_NAME = 'tikv_gc_life_time'\\E"). + WithArgs("10m"). + WillReturnResult(sqlmock.NewResult(2, 1)) + mock.ExpectClose() + + ctx := MockDoChecksumCtx(db) + + // db.Close() will close all connections from its idle pool, set it 1 to expect one close + db.SetMaxIdleConns(1) + var wg sync.WaitGroup + wg.Add(5) + for i := 0; i < 5; i++ { + go func() { + defer wg.Done() + checksum, err := DoChecksum(ctx, &TidbTableInfo{DB: "test", Name: "t"}) + c.Assert(err, IsNil) + c.Assert(*checksum, DeepEquals, RemoteChecksum{ + Schema: "test", + Table: "t", + Checksum: 8520875019404689597, + TotalKVs: 7296873, + TotalBytes: 357601387, + }) + }() + } + wg.Wait() + + c.Assert(db.Close(), IsNil) + c.Assert(mock.ExpectationsWereMet(), IsNil) +} + +func (s *checksumSuite) TestIncreaseGCLifeTimeFail(c *C) { + db, mock, err := sqlmock.New() + c.Assert(err, IsNil) + + for i := 0; i < 5; i++ { + mock.ExpectQuery("\\QSELECT VARIABLE_VALUE FROM mysql.tidb WHERE VARIABLE_NAME = 'tikv_gc_life_time'\\E"). + WillReturnRows(sqlmock.NewRows([]string{"VARIABLE_VALUE"}).AddRow("10m")) + mock.ExpectExec("\\QUPDATE mysql.tidb SET VARIABLE_VALUE = ? WHERE VARIABLE_NAME = 'tikv_gc_life_time'\\E"). + WithArgs("100h0m0s"). + WillReturnError(errors.Annotate(context.Canceled, "update gc error")) + } + // This recover GC Life Time SQL should not be executed in DoChecksum + mock.ExpectExec("\\QUPDATE mysql.tidb SET VARIABLE_VALUE = ? WHERE VARIABLE_NAME = 'tikv_gc_life_time'\\E"). + WithArgs("10m"). + WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectClose() + + ctx := MockDoChecksumCtx(db) + var wg sync.WaitGroup + wg.Add(5) + for i := 0; i < 5; i++ { + go func() { + _, err = DoChecksum(ctx, &TidbTableInfo{DB: "test", Name: "t"}) + c.Assert(err, ErrorMatches, "update GC lifetime failed: update gc error: context canceled") + wg.Done() + }() + } + wg.Wait() + + _, err = db.Exec("\\QUPDATE mysql.tidb SET VARIABLE_VALUE = ? WHERE VARIABLE_NAME = 'tikv_gc_life_time'\\E", "10m") + c.Assert(err, IsNil) + + c.Assert(db.Close(), IsNil) + c.Assert(mock.ExpectationsWereMet(), IsNil) +} + +func (s *checksumSuite) TestDoChecksumWithTikv(c *C) { + // set up mock tikv checksum manager + pdClient := &testPDClient{} + resp := tipb.ChecksumResponse{Checksum: 123, TotalKvs: 10, TotalBytes: 1000} + kvClient := &mockChecksumKVClient{checksum: resp, respDur: time.Second * 5} + + // mock a table info + p := parser.New() + se := tmock.NewContext() + node, err := p.ParseOneStmt("CREATE TABLE `t1` (`c1` varchar(5) NOT NULL)", "utf8mb4", "utf8mb4_bin") + c.Assert(err, IsNil) + tableInfo, err := ddl.MockTableInfo(se, node.(*ast.CreateTableStmt), 999) + c.Assert(err, IsNil) + + for i := 0; i <= maxErrorRetryCount; i++ { + kvClient.maxErrCount = i + kvClient.curErrCount = 0 + checksumExec := &tikvChecksumManager{manager: newGCTTLManager(pdClient), client: kvClient} + startTs := oracle.ComposeTS(time.Now().Unix()*1000, 0) + ctx := context.WithValue(context.Background(), &checksumManagerKey, checksumExec) + _, err = DoChecksum(ctx, &TidbTableInfo{DB: "test", Name: "t", Core: tableInfo}) + // with max error retry < maxErrorRetryCount, the checksum can success + if i >= maxErrorRetryCount { + c.Assert(err, ErrorMatches, "tikv timeout") + continue + } else { + c.Assert(err, IsNil) + } + + // after checksum, safepint should be small than start ts + ts := pdClient.currentSafePoint() + // 1ms for the schedule deviation + c.Assert(ts <= startTs+1, IsTrue) + c.Assert(atomic.LoadUint32(&checksumExec.manager.started) > 0, IsTrue) + } +} + +func (s *checksumSuite) TestDoChecksumWithTikvErrRetry(c *C) { +} + +func (s *checksumSuite) TestDoChecksumWithErrorAndLongOriginalLifetime(c *C) { + db, mock, err := sqlmock.New() + c.Assert(err, IsNil) + + mock.ExpectQuery("\\QSELECT VARIABLE_VALUE FROM mysql.tidb WHERE VARIABLE_NAME = 'tikv_gc_life_time'\\E"). + WillReturnRows(sqlmock.NewRows([]string{"VARIABLE_VALUE"}).AddRow("300h")) + mock.ExpectQuery("\\QADMIN CHECKSUM TABLE `test`.`t`\\E"). + WillReturnError(errors.Annotate(context.Canceled, "mock syntax error")) + mock.ExpectExec("\\QUPDATE mysql.tidb SET VARIABLE_VALUE = ? WHERE VARIABLE_NAME = 'tikv_gc_life_time'\\E"). + WithArgs("300h"). + WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectClose() + + ctx := MockDoChecksumCtx(db) + _, err = DoChecksum(ctx, &TidbTableInfo{DB: "test", Name: "t"}) + c.Assert(err, ErrorMatches, "compute remote checksum failed: mock syntax error.*") + + c.Assert(db.Close(), IsNil) + c.Assert(mock.ExpectationsWereMet(), IsNil) +} + +type safePointTTL struct { + safePoint uint64 + // ttl is the last timestamp this safe point is valid + ttl int64 +} + +type testPDClient struct { + sync.Mutex + pd.Client + count int32 + gcSafePoint []safePointTTL +} + +func (c *testPDClient) currentSafePoint() uint64 { + ts := time.Now().Unix() + c.Lock() + defer c.Unlock() + for _, s := range c.gcSafePoint { + if s.ttl > ts { + return s.safePoint + } + } + return 0 +} + +func (c *testPDClient) UpdateServiceGCSafePoint(ctx context.Context, serviceID string, ttl int64, safePoint uint64) (uint64, error) { + if !strings.HasPrefix(serviceID, "lightning") { + panic("service ID must start with 'lightning'") + } + atomic.AddInt32(&c.count, 1) + c.Lock() + idx := sort.Search(len(c.gcSafePoint), func(i int) bool { + return c.gcSafePoint[i].safePoint >= safePoint + }) + sp := c.gcSafePoint + ttlEnd := time.Now().Unix() + ttl + spTTL := safePointTTL{safePoint: safePoint, ttl: ttlEnd} + switch { + case idx >= len(sp): + c.gcSafePoint = append(c.gcSafePoint, spTTL) + case sp[idx].safePoint == safePoint: + if ttlEnd > sp[idx].ttl { + sp[idx].ttl = ttlEnd + } + default: + c.gcSafePoint = append(append(sp[:idx], spTTL), sp[idx:]...) + } + c.Unlock() + return c.currentSafePoint(), nil +} + +func (s *checksumSuite) TestGcTTLManagerSingle(c *C) { + pdClient := &testPDClient{} + manager := newGCTTLManager(pdClient) + c.Assert(manager.serviceID, Not(Equals), "") + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + oldTTL := serviceSafePointTTL + // set serviceSafePointTTL to 3 second, so lightning will update it in each 1 seconds. + serviceSafePointTTL = 3 + defer func() { + serviceSafePointTTL = oldTTL + }() + + err := manager.addOneJob(ctx, "test", uint64(time.Now().Unix())) + c.Assert(err, IsNil) + + time.Sleep(6*time.Second + 10*time.Millisecond) + + // after 6 seconds, must at least update 5 times + val := atomic.LoadInt32(&pdClient.count) + c.Assert(val, GreaterEqual, int32(5)) + + // after remove the job, there are no job remain, gc ttl needn't to be updated + manager.removeOneJob("test") + time.Sleep(10 * time.Millisecond) + val = atomic.LoadInt32(&pdClient.count) + time.Sleep(3*time.Second + 10*time.Millisecond) + c.Assert(atomic.LoadInt32(&pdClient.count), Equals, val) +} + +func (s *checksumSuite) TestGcTTLManagerMulti(c *C) { + manager := newGCTTLManager(&testPDClient{}) + ctx := context.Background() + + for i := uint64(1); i <= 5; i++ { + err := manager.addOneJob(ctx, fmt.Sprintf("test%d", i), i) + c.Assert(err, IsNil) + c.Assert(manager.currentTs, Equals, uint64(1)) + } + + manager.removeOneJob("test2") + c.Assert(manager.currentTs, Equals, uint64(1)) + + manager.removeOneJob("test1") + c.Assert(manager.currentTs, Equals, uint64(3)) + + manager.removeOneJob("test3") + c.Assert(manager.currentTs, Equals, uint64(4)) + + manager.removeOneJob("test4") + c.Assert(manager.currentTs, Equals, uint64(5)) + + manager.removeOneJob("test5") + c.Assert(manager.currentTs, Equals, uint64(0)) +} + +func (s *checksumSuite) TestPdServiceID(c *C) { + pdCli := &testPDClient{} + gcTTLManager1 := newGCTTLManager(pdCli) + c.Assert(gcTTLManager1.serviceID, Matches, "lightning-.*") + gcTTLManager2 := newGCTTLManager(pdCli) + c.Assert(gcTTLManager2.serviceID, Matches, "lightning-.*") + + c.Assert(gcTTLManager1.serviceID != gcTTLManager2.serviceID, IsTrue) +} + +type mockResponse struct { + finished bool + data []byte +} + +func (r *mockResponse) Next(ctx context.Context) (resultSubset kv.ResultSubset, err error) { + if r.finished { + return nil, nil + } + r.finished = true + return &mockResultSubset{data: r.data}, nil +} + +func (r *mockResponse) Close() error { + return nil +} + +type mockErrorResponse struct { + err string +} + +func (r *mockErrorResponse) Next(ctx context.Context) (resultSubset kv.ResultSubset, err error) { + return nil, errors.New(r.err) +} + +func (r *mockErrorResponse) Close() error { + return nil +} + +type mockResultSubset struct { + data []byte +} + +func (r *mockResultSubset) GetData() []byte { + return r.data +} + +func (r *mockResultSubset) GetStartKey() kv.Key { + return []byte{} +} + +func (r *mockResultSubset) MemSize() int64 { + return 0 +} + +func (r *mockResultSubset) RespTime() time.Duration { + return time.Millisecond +} + +type mockChecksumKVClient struct { + kv.Client + checksum tipb.ChecksumResponse + respDur time.Duration + // return error count before return success + maxErrCount int + curErrCount int +} + +// a mock client for checksum request +func (c *mockChecksumKVClient) Send(ctx context.Context, req *kv.Request, vars *kv.Variables, sessionMemTracker *memory.Tracker, enabledRateLimitAction bool) kv.Response { + if c.curErrCount < c.maxErrCount { + c.curErrCount++ + return &mockErrorResponse{err: "tikv timeout"} + } + data, _ := c.checksum.Marshal() + time.Sleep(c.respDur) + return &mockResponse{data: data} +} diff --git a/pkg/lightning/restore/const.go b/pkg/lightning/restore/const.go new file mode 100644 index 000000000..082940c7e --- /dev/null +++ b/pkg/lightning/restore/const.go @@ -0,0 +1,18 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package restore + +const ( + defReadBlockSize int64 = 1024 * 128 // TODO ... config +) diff --git a/pkg/lightning/restore/restore.go b/pkg/lightning/restore/restore.go new file mode 100644 index 000000000..3cec18729 --- /dev/null +++ b/pkg/lightning/restore/restore.go @@ -0,0 +1,2537 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package restore + +import ( + "context" + "fmt" + "io" + "math" + "os" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/pingcap/errors" + "github.com/pingcap/failpoint" + sstpb "github.com/pingcap/kvproto/pkg/import_sstpb" + "github.com/pingcap/parser/model" + "github.com/pingcap/tidb/meta/autoid" + "github.com/pingcap/tidb/table" + "github.com/pingcap/tidb/table/tables" + "github.com/pingcap/tidb/util/collate" + "go.uber.org/multierr" + "go.uber.org/zap" + "modernc.org/mathutil" + + kv "github.com/pingcap/br/pkg/lightning/backend" + . "github.com/pingcap/br/pkg/lightning/checkpoints" + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/config" + "github.com/pingcap/br/pkg/lightning/glue" + "github.com/pingcap/br/pkg/lightning/log" + "github.com/pingcap/br/pkg/lightning/metric" + "github.com/pingcap/br/pkg/lightning/mydump" + verify "github.com/pingcap/br/pkg/lightning/verification" + "github.com/pingcap/br/pkg/lightning/web" + "github.com/pingcap/br/pkg/lightning/worker" + "github.com/pingcap/br/pkg/pdutil" + "github.com/pingcap/br/pkg/storage" + "github.com/pingcap/br/pkg/utils" +) + +const ( + FullLevelCompact = -1 + Level1Compact = 1 +) + +const ( + defaultGCLifeTime = 100 * time.Hour +) + +const ( + indexEngineID = -1 +) + +const ( + compactStateIdle int32 = iota + compactStateDoing +) + +// DeliverPauser is a shared pauser to pause progress to (*chunkRestore).encodeLoop +var DeliverPauser = common.NewPauser() + +func init() { + // used in integration tests + failpoint.Inject("SetMinDeliverBytes", func(v failpoint.Value) { + minDeliverBytes = uint64(v.(int)) + }) +} + +type saveCp struct { + tableName string + merger TableCheckpointMerger +} + +type errorSummary struct { + status CheckpointStatus + err error +} + +type errorSummaries struct { + sync.Mutex + logger log.Logger + summary map[string]errorSummary +} + +// makeErrorSummaries returns an initialized errorSummaries instance +func makeErrorSummaries(logger log.Logger) errorSummaries { + return errorSummaries{ + logger: logger, + summary: make(map[string]errorSummary), + } +} + +func (es *errorSummaries) emitLog() { + es.Lock() + defer es.Unlock() + + if errorCount := len(es.summary); errorCount > 0 { + logger := es.logger + logger.Error("tables failed to be imported", zap.Int("count", errorCount)) + for tableName, errorSummary := range es.summary { + logger.Error("-", + zap.String("table", tableName), + zap.String("status", errorSummary.status.MetricName()), + log.ShortError(errorSummary.err), + ) + } + } +} + +func (es *errorSummaries) record(tableName string, err error, status CheckpointStatus) { + es.Lock() + defer es.Unlock() + es.summary[tableName] = errorSummary{status: status, err: err} +} + +const ( + diskQuotaStateIdle int32 = iota + diskQuotaStateChecking + diskQuotaStateImporting +) + +type RestoreController struct { + cfg *config.Config + dbMetas []*mydump.MDDatabaseMeta + dbInfos map[string]*TidbDBInfo + tableWorkers *worker.Pool + indexWorkers *worker.Pool + regionWorkers *worker.Pool + ioWorkers *worker.Pool + checksumWorks *worker.Pool + pauser *common.Pauser + backend kv.Backend + tidbGlue glue.Glue + postProcessLock sync.Mutex // a simple way to ensure post-processing is not concurrent without using complicated goroutines + alterTableLock sync.Mutex + compactState int32 + sysVars map[string]string + tls *common.TLS + + errorSummaries errorSummaries + + checkpointsDB CheckpointsDB + saveCpCh chan saveCp + checkpointsWg sync.WaitGroup + + closedEngineLimit *worker.Pool + store storage.ExternalStorage + checksumManager ChecksumManager + + diskQuotaLock sync.RWMutex + diskQuotaState int32 +} + +func NewRestoreController( + ctx context.Context, + dbMetas []*mydump.MDDatabaseMeta, + cfg *config.Config, + s storage.ExternalStorage, + g glue.Glue, +) (*RestoreController, error) { + return NewRestoreControllerWithPauser(ctx, dbMetas, cfg, s, DeliverPauser, g) +} + +func NewRestoreControllerWithPauser( + ctx context.Context, + dbMetas []*mydump.MDDatabaseMeta, + cfg *config.Config, + s storage.ExternalStorage, + pauser *common.Pauser, + g glue.Glue, +) (*RestoreController, error) { + tls, err := cfg.ToTLS() + if err != nil { + return nil, err + } + + cpdb, err := g.OpenCheckpointsDB(ctx, cfg) + if err != nil { + return nil, errors.Annotate(err, "open checkpoint db failed") + } + + taskCp, err := cpdb.TaskCheckpoint(ctx) + if err != nil { + return nil, errors.Annotate(err, "get task checkpoint failed") + } + if err := verifyCheckpoint(cfg, taskCp); err != nil { + return nil, errors.Trace(err) + } + + var backend kv.Backend + switch cfg.TikvImporter.Backend { + case config.BackendImporter: + var err error + backend, err = kv.NewImporter(ctx, tls, cfg.TikvImporter.Addr, cfg.TiDB.PdAddr) + if err != nil { + return nil, errors.Annotate(err, "open importer backend failed") + } + case config.BackendTiDB: + db, err := DBFromConfig(cfg.TiDB) + if err != nil { + return nil, errors.Annotate(err, "open tidb backend failed") + } + backend = kv.NewTiDBBackend(db, cfg.TikvImporter.OnDuplicate) + case config.BackendLocal: + var rLimit uint64 + rLimit, err = kv.GetSystemRLimit() + if err != nil { + return nil, err + } + maxOpenFiles := int(rLimit / uint64(cfg.App.TableConcurrency)) + // check overflow + if maxOpenFiles < 0 { + maxOpenFiles = math.MaxInt32 + } + + backend, err = kv.NewLocalBackend(ctx, tls, cfg.TiDB.PdAddr, int64(cfg.TikvImporter.RegionSplitSize), + cfg.TikvImporter.SortedKVDir, cfg.TikvImporter.RangeConcurrency, cfg.TikvImporter.SendKVPairs, + cfg.Checkpoint.Enable, g, maxOpenFiles) + if err != nil { + return nil, errors.Annotate(err, "build local backend failed") + } + err = verifyLocalFile(ctx, cpdb, cfg.TikvImporter.SortedKVDir) + if err != nil { + return nil, err + } + default: + return nil, errors.New("unknown backend: " + cfg.TikvImporter.Backend) + } + + rc := &RestoreController{ + cfg: cfg, + dbMetas: dbMetas, + tableWorkers: worker.NewPool(ctx, cfg.App.TableConcurrency, "table"), + indexWorkers: worker.NewPool(ctx, cfg.App.IndexConcurrency, "index"), + regionWorkers: worker.NewPool(ctx, cfg.App.RegionConcurrency, "region"), + ioWorkers: worker.NewPool(ctx, cfg.App.IOConcurrency, "io"), + checksumWorks: worker.NewPool(ctx, cfg.TiDB.ChecksumTableConcurrency, "checksum"), + pauser: pauser, + backend: backend, + tidbGlue: g, + sysVars: defaultImportantVariables, + tls: tls, + + errorSummaries: makeErrorSummaries(log.L()), + checkpointsDB: cpdb, + saveCpCh: make(chan saveCp), + closedEngineLimit: worker.NewPool(ctx, cfg.App.TableConcurrency*2, "closed-engine"), + + store: s, + } + + return rc, nil +} + +func (rc *RestoreController) Close() { + rc.backend.Close() + rc.tidbGlue.GetSQLExecutor().Close() +} + +func (rc *RestoreController) Run(ctx context.Context) error { + opts := []func(context.Context) error{ + rc.checkRequirements, + rc.setGlobalVariables, + rc.restoreSchema, + rc.restoreTables, + rc.fullCompact, + rc.switchToNormalMode, + rc.cleanCheckpoints, + } + + task := log.L().Begin(zap.InfoLevel, "the whole procedure") + + var err error + finished := false +outside: + for i, process := range opts { + err = process(ctx) + if i == len(opts)-1 { + finished = true + } + logger := task.With(zap.Int("step", i), log.ShortError(err)) + + switch { + case err == nil: + case log.IsContextCanceledError(err): + logger.Info("task canceled") + err = nil + break outside + default: + logger.Error("run failed") + fmt.Fprintf(os.Stderr, "Error: %s\n", err) + break outside // ps : not continue + } + } + + // if process is cancelled, should make sure checkpoints are written to db. + if !finished { + rc.waitCheckpointFinish() + } + + task.End(zap.ErrorLevel, err) + rc.errorSummaries.emitLog() + + return errors.Trace(err) +} + +type schemaStmtType int + +func (stmtType schemaStmtType) String() string { + switch stmtType { + case schemaCreateDatabase: + return "restore database schema" + case schemaCreateTable: + return "restore table schema" + case schemaCreateView: + return "restore view schema" + } + return "unknown statement of schema" +} + +const ( + schemaCreateDatabase schemaStmtType = iota + schemaCreateTable + schemaCreateView +) + +type schemaJob struct { + dbName string + tblName string // empty for create db jobs + stmtType schemaStmtType + stmts []*schemaStmt +} + +type schemaStmt struct { + sql string +} + +type restoreSchemaWorker struct { + ctx context.Context + quit context.CancelFunc + jobCh chan *schemaJob + errCh chan error + wg sync.WaitGroup + glue glue.Glue + store storage.ExternalStorage +} + +func (worker *restoreSchemaWorker) makeJobs(dbMetas []*mydump.MDDatabaseMeta) error { + defer func() { + close(worker.jobCh) + worker.quit() + }() + var err error + // 1. restore databases, execute statements concurrency + for _, dbMeta := range dbMetas { + restoreSchemaJob := &schemaJob{ + dbName: dbMeta.Name, + stmtType: schemaCreateDatabase, + stmts: make([]*schemaStmt, 0, 1), + } + restoreSchemaJob.stmts = append(restoreSchemaJob.stmts, &schemaStmt{ + sql: createDatabaseIfNotExistStmt(dbMeta.Name), + }) + err = worker.appendJob(restoreSchemaJob) + if err != nil { + return err + } + } + err = worker.wait() + if err != nil { + return err + } + // 2. restore tables, execute statements concurrency + for _, dbMeta := range dbMetas { + for _, tblMeta := range dbMeta.Tables { + sql := tblMeta.GetSchema(worker.ctx, worker.store) + if sql != "" { + stmts, err := createTableIfNotExistsStmt(worker.glue.GetParser(), sql, dbMeta.Name, tblMeta.Name) + if err != nil { + return err + } + restoreSchemaJob := &schemaJob{ + dbName: dbMeta.Name, + tblName: tblMeta.Name, + stmtType: schemaCreateTable, + stmts: make([]*schemaStmt, 0, len(stmts)), + } + for _, sql := range stmts { + restoreSchemaJob.stmts = append(restoreSchemaJob.stmts, &schemaStmt{ + sql: sql, + }) + } + err = worker.appendJob(restoreSchemaJob) + if err != nil { + return err + } + } + } + } + err = worker.wait() + if err != nil { + return err + } + // 3. restore views. Since views can cross database we must restore views after all table schemas are restored. + for _, dbMeta := range dbMetas { + for _, viewMeta := range dbMeta.Views { + sql := viewMeta.GetSchema(worker.ctx, worker.store) + if sql != "" { + stmts, err := createTableIfNotExistsStmt(worker.glue.GetParser(), sql, dbMeta.Name, viewMeta.Name) + if err != nil { + return err + } + restoreSchemaJob := &schemaJob{ + dbName: dbMeta.Name, + tblName: viewMeta.Name, + stmtType: schemaCreateView, + stmts: make([]*schemaStmt, 0, len(stmts)), + } + for _, sql := range stmts { + restoreSchemaJob.stmts = append(restoreSchemaJob.stmts, &schemaStmt{ + sql: sql, + }) + } + err = worker.appendJob(restoreSchemaJob) + if err != nil { + return err + } + // we don't support restore views concurrency, cauz it maybe will raise a error + err = worker.wait() + if err != nil { + return err + } + } + } + } + return nil +} + +func (worker *restoreSchemaWorker) doJob() { + var session Session + defer func() { + if session != nil { + session.Close() + } + }() +loop: + for { + select { + case <-worker.ctx.Done(): + // don't `return` or throw `worker.ctx.Err()`here, + // if we `return`, we can't mark cancelled jobs as done, + // if we `throw(worker.ctx.Err())`, it will be blocked to death + break loop + case job := <-worker.jobCh: + if job == nil { + // successful exit + return + } + var err error + if session == nil { + session, err = worker.glue.GetSession(worker.ctx) + if err != nil { + worker.wg.Done() + worker.throw(err) + // don't return + break loop + } + } + logger := log.With(zap.String("db", job.dbName), zap.String("table", job.tblName)) + for _, stmt := range job.stmts { + task := logger.Begin(zap.DebugLevel, fmt.Sprintf("execute SQL: %s", stmt.sql)) + _, err = session.Execute(worker.ctx, stmt.sql) + task.End(zap.ErrorLevel, err) + if err != nil { + err = errors.Annotatef(err, "%s %s failed", job.stmtType.String(), common.UniqueTable(job.dbName, job.tblName)) + worker.wg.Done() + worker.throw(err) + // don't return + break loop + } + } + worker.wg.Done() + } + } + // mark the cancelled job as `Done`, a little tricky, + // cauz we need make sure `worker.wg.Wait()` wouldn't blocked forever + for range worker.jobCh { + worker.wg.Done() + } +} + +func (worker *restoreSchemaWorker) wait() error { + // avoid to `worker.wg.Wait()` blocked forever when all `doJob`'s goroutine exited. + // don't worry about goroutine below, it never become a zombie, + // cauz we have mechanism to clean cancelled jobs in `worker.jobCh`. + // means whole jobs has been send to `worker.jobCh` would be done. + waitCh := make(chan struct{}) + go func() { + worker.wg.Wait() + close(waitCh) + }() + select { + case err := <-worker.errCh: + return err + case <-worker.ctx.Done(): + return worker.ctx.Err() + case <-waitCh: + return nil + } +} + +func (worker *restoreSchemaWorker) throw(err error) { + select { + case <-worker.ctx.Done(): + // don't throw `worker.ctx.Err()` again, it will be blocked to death. + return + case worker.errCh <- err: + worker.quit() + } +} + +func (worker *restoreSchemaWorker) appendJob(job *schemaJob) error { + worker.wg.Add(1) + select { + case err := <-worker.errCh: + // cancel the job + worker.wg.Done() + return err + case <-worker.ctx.Done(): + // cancel the job + worker.wg.Done() + return worker.ctx.Err() + case worker.jobCh <- job: + return nil + } +} + +func (rc *RestoreController) restoreSchema(ctx context.Context) error { + if !rc.cfg.Mydumper.NoSchema { + logTask := log.L().Begin(zap.InfoLevel, "restore all schema") + concurrency := utils.MinInt(rc.cfg.App.RegionConcurrency, 8) + childCtx, cancel := context.WithCancel(ctx) + worker := restoreSchemaWorker{ + ctx: childCtx, + quit: cancel, + jobCh: make(chan *schemaJob, concurrency), + errCh: make(chan error), + glue: rc.tidbGlue, + store: rc.store, + } + for i := 0; i < concurrency; i++ { + go worker.doJob() + } + err := worker.makeJobs(rc.dbMetas) + logTask.End(zap.ErrorLevel, err) + if err != nil { + return err + } + } + getTableFunc := rc.backend.FetchRemoteTableModels + if !rc.tidbGlue.OwnsSQLExecutor() { + getTableFunc = rc.tidbGlue.GetTables + } + dbInfos, err := LoadSchemaInfo(ctx, rc.dbMetas, getTableFunc) + if err != nil { + return errors.Trace(err) + } + rc.dbInfos = dbInfos + + // Load new checkpoints + err = rc.checkpointsDB.Initialize(ctx, rc.cfg, dbInfos) + if err != nil { + return errors.Trace(err) + } + failpoint.Inject("InitializeCheckpointExit", func() { + log.L().Warn("exit triggered", zap.String("failpoint", "InitializeCheckpointExit")) + os.Exit(0) + }) + + go rc.listenCheckpointUpdates() + + rc.sysVars = ObtainImportantVariables(ctx, rc.tidbGlue.GetSQLExecutor()) + + // Estimate the number of chunks for progress reporting + err = rc.estimateChunkCountIntoMetrics(ctx) + return err +} + +// verifyCheckpoint check whether previous task checkpoint is compatible with task config +func verifyCheckpoint(cfg *config.Config, taskCp *TaskCheckpoint) error { + if taskCp == nil { + return nil + } + // always check the backend value even with 'check-requirements = false' + retryUsage := "destroy all checkpoints" + if cfg.Checkpoint.Driver == config.CheckpointDriverFile { + retryUsage = fmt.Sprintf("delete the file '%s'", cfg.Checkpoint.DSN) + } + retryUsage += " and remove all restored tables and try again" + + if cfg.TikvImporter.Backend != taskCp.Backend { + return errors.Errorf("config 'tikv-importer.backend' value '%s' different from checkpoint value '%s', please %s", cfg.TikvImporter.Backend, taskCp.Backend, retryUsage) + } + + if cfg.App.CheckRequirements { + if common.ReleaseVersion != taskCp.LightningVer { + var displayVer string + if len(taskCp.LightningVer) != 0 { + displayVer = fmt.Sprintf("at '%s'", taskCp.LightningVer) + } else { + displayVer = "before v4.0.6/v3.0.19" + } + return errors.Errorf("lightning version is '%s', but checkpoint was created %s, please %s", common.ReleaseVersion, displayVer, retryUsage) + } + + errorFmt := "config '%s' value '%s' different from checkpoint value '%s'. You may set 'check-requirements = false' to skip this check or " + retryUsage + if cfg.Mydumper.SourceDir != taskCp.SourceDir { + return errors.Errorf(errorFmt, "mydumper.data-source-dir", cfg.Mydumper.SourceDir, taskCp.SourceDir) + } + + if cfg.TikvImporter.Backend == config.BackendLocal && cfg.TikvImporter.SortedKVDir != taskCp.SortedKVDir { + return errors.Errorf(errorFmt, "mydumper.sorted-kv-dir", cfg.TikvImporter.SortedKVDir, taskCp.SortedKVDir) + } + + if cfg.TikvImporter.Backend == config.BackendImporter && cfg.TikvImporter.Addr != taskCp.ImporterAddr { + return errors.Errorf(errorFmt, "tikv-importer.addr", cfg.TikvImporter.Backend, taskCp.Backend) + } + + if cfg.TiDB.Host != taskCp.TiDBHost { + return errors.Errorf(errorFmt, "tidb.host", cfg.TiDB.Host, taskCp.TiDBHost) + } + + if cfg.TiDB.Port != taskCp.TiDBPort { + return errors.Errorf(errorFmt, "tidb.port", cfg.TiDB.Port, taskCp.TiDBPort) + } + + if cfg.TiDB.PdAddr != taskCp.PdAddr { + return errors.Errorf(errorFmt, "tidb.pd-addr", cfg.TiDB.PdAddr, taskCp.PdAddr) + } + } + + return nil +} + +// for local backend, we should check if local SST exists in disk, otherwise we'll lost data +func verifyLocalFile(ctx context.Context, cpdb CheckpointsDB, dir string) error { + targetTables, err := cpdb.GetLocalStoringTables(ctx) + if err != nil { + return errors.Trace(err) + } + for tableName, engineIDs := range targetTables { + for _, engineID := range engineIDs { + _, eID := kv.MakeUUID(tableName, engineID) + file := kv.LocalFile{Uuid: eID} + err := file.Exist(dir) + if err != nil { + log.L().Error("can't find local file", + zap.String("table name", tableName), + zap.Int32("engine ID", engineID)) + return errors.Trace(err) + } + } + } + return nil +} + +func (rc *RestoreController) estimateChunkCountIntoMetrics(ctx context.Context) error { + estimatedChunkCount := 0.0 + estimatedEngineCnt := int64(0) + batchSize := int64(rc.cfg.Mydumper.BatchSize) + for _, dbMeta := range rc.dbMetas { + for _, tableMeta := range dbMeta.Tables { + tableName := common.UniqueTable(dbMeta.Name, tableMeta.Name) + dbCp, err := rc.checkpointsDB.Get(ctx, tableName) + if err != nil { + return errors.Trace(err) + } + + fileChunks := make(map[string]float64) + for engineId, eCp := range dbCp.Engines { + if eCp.Status < CheckpointStatusImported { + estimatedEngineCnt++ + } + if engineId == indexEngineID { + continue + } + for _, c := range eCp.Chunks { + if _, ok := fileChunks[c.Key.Path]; !ok { + fileChunks[c.Key.Path] = 0.0 + } + remainChunkCnt := float64(c.Chunk.EndOffset-c.Chunk.Offset) / float64(c.Chunk.EndOffset-c.Key.Offset) + fileChunks[c.Key.Path] += remainChunkCnt + } + } + // estimate engines count if engine cp is empty + if len(dbCp.Engines) == 0 { + estimatedEngineCnt += ((tableMeta.TotalSize + batchSize - 1) / batchSize) + 1 + } + for _, fileMeta := range tableMeta.DataFiles { + if cnt, ok := fileChunks[fileMeta.FileMeta.Path]; ok { + estimatedChunkCount += cnt + continue + } + if fileMeta.FileMeta.Type == mydump.SourceTypeCSV { + cfg := rc.cfg.Mydumper + if fileMeta.FileMeta.FileSize > int64(cfg.MaxRegionSize) && cfg.StrictFormat && !cfg.CSV.Header { + estimatedChunkCount += math.Round(float64(fileMeta.FileMeta.FileSize) / float64(cfg.MaxRegionSize)) + } else { + estimatedChunkCount += 1 + } + } else { + estimatedChunkCount += 1 + } + } + } + } + metric.ChunkCounter.WithLabelValues(metric.ChunkStateEstimated).Add(estimatedChunkCount) + metric.ProcessedEngineCounter.WithLabelValues(metric.ChunkStateEstimated, metric.TableResultSuccess). + Add(float64(estimatedEngineCnt)) + rc.tidbGlue.Record(glue.RecordEstimatedChunk, uint64(estimatedChunkCount)) + return nil +} + +func (rc *RestoreController) saveStatusCheckpoint(tableName string, engineID int32, err error, statusIfSucceed CheckpointStatus) { + merger := &StatusCheckpointMerger{Status: statusIfSucceed, EngineID: engineID} + + log.L().Debug("update checkpoint", zap.String("table", tableName), zap.Int32("engine_id", engineID), + zap.Uint8("new_status", uint8(statusIfSucceed)), zap.Error(err)) + + switch { + case err == nil: + break + case !common.IsContextCanceledError(err): + merger.SetInvalid() + rc.errorSummaries.record(tableName, err, statusIfSucceed) + default: + return + } + + if engineID == WholeTableEngineID { + metric.RecordTableCount(statusIfSucceed.MetricName(), err) + } else { + metric.RecordEngineCount(statusIfSucceed.MetricName(), err) + } + + rc.saveCpCh <- saveCp{tableName: tableName, merger: merger} +} + +// listenCheckpointUpdates will combine several checkpoints together to reduce database load. +func (rc *RestoreController) listenCheckpointUpdates() { + rc.checkpointsWg.Add(1) + + var lock sync.Mutex + coalesed := make(map[string]*TableCheckpointDiff) + + hasCheckpoint := make(chan struct{}, 1) + defer close(hasCheckpoint) + + go func() { + for range hasCheckpoint { + lock.Lock() + cpd := coalesed + coalesed = make(map[string]*TableCheckpointDiff) + lock.Unlock() + + if len(cpd) > 0 { + rc.checkpointsDB.Update(cpd) + web.BroadcastCheckpointDiff(cpd) + } + rc.checkpointsWg.Done() + } + }() + + for scp := range rc.saveCpCh { + lock.Lock() + cpd, ok := coalesed[scp.tableName] + if !ok { + cpd = NewTableCheckpointDiff() + coalesed[scp.tableName] = cpd + } + scp.merger.MergeInto(cpd) + + if len(hasCheckpoint) == 0 { + rc.checkpointsWg.Add(1) + hasCheckpoint <- struct{}{} + } + + lock.Unlock() + + failpoint.Inject("FailIfImportedChunk", func(val failpoint.Value) { + if merger, ok := scp.merger.(*ChunkCheckpointMerger); ok && merger.Checksum.SumKVS() >= uint64(val.(int)) { + rc.checkpointsWg.Done() + rc.checkpointsWg.Wait() + panic("forcing failure due to FailIfImportedChunk") + } + }) + + failpoint.Inject("FailIfStatusBecomes", func(val failpoint.Value) { + if merger, ok := scp.merger.(*StatusCheckpointMerger); ok && merger.EngineID >= 0 && int(merger.Status) == val.(int) { + rc.checkpointsWg.Done() + rc.checkpointsWg.Wait() + panic("forcing failure due to FailIfStatusBecomes") + } + }) + + failpoint.Inject("FailIfIndexEngineImported", func(val failpoint.Value) { + if merger, ok := scp.merger.(*StatusCheckpointMerger); ok && + merger.EngineID == WholeTableEngineID && + merger.Status == CheckpointStatusIndexImported && val.(int) > 0 { + rc.checkpointsWg.Done() + rc.checkpointsWg.Wait() + panic("forcing failure due to FailIfIndexEngineImported") + } + }) + + failpoint.Inject("KillIfImportedChunk", func(val failpoint.Value) { + if merger, ok := scp.merger.(*ChunkCheckpointMerger); ok && merger.Checksum.SumKVS() >= uint64(val.(int)) { + common.KillMySelf() + } + }) + } + rc.checkpointsWg.Done() +} + +func (rc *RestoreController) runPeriodicActions(ctx context.Context, stop <-chan struct{}) { + // a nil channel blocks forever. + // if the cron duration is zero we use the nil channel to skip the action. + var logProgressChan <-chan time.Time + if rc.cfg.Cron.LogProgress.Duration > 0 { + logProgressTicker := time.NewTicker(rc.cfg.Cron.LogProgress.Duration) + defer logProgressTicker.Stop() + logProgressChan = logProgressTicker.C + } + + glueProgressTicker := time.NewTicker(3 * time.Second) + defer glueProgressTicker.Stop() + + var switchModeChan <-chan time.Time + // tidb backend don't need to switch tikv to import mode + if rc.cfg.TikvImporter.Backend != config.BackendTiDB && rc.cfg.Cron.SwitchMode.Duration > 0 { + switchModeTicker := time.NewTicker(rc.cfg.Cron.SwitchMode.Duration) + defer switchModeTicker.Stop() + switchModeChan = switchModeTicker.C + + rc.switchToImportMode(ctx) + } + + var checkQuotaChan <-chan time.Time + // only local storage has disk quota concern. + if rc.cfg.TikvImporter.Backend == config.BackendLocal && rc.cfg.Cron.CheckDiskQuota.Duration > 0 { + checkQuotaTicker := time.NewTicker(rc.cfg.Cron.CheckDiskQuota.Duration) + defer checkQuotaTicker.Stop() + checkQuotaChan = checkQuotaTicker.C + } + + start := time.Now() + for { + select { + case <-ctx.Done(): + log.L().Warn("stopping periodic actions", log.ShortError(ctx.Err())) + return + case <-stop: + log.L().Info("everything imported, stopping periodic actions") + return + + case <-switchModeChan: + // periodically switch to import mode, as requested by TiKV 3.0 + rc.switchToImportMode(ctx) + + case <-logProgressChan: + // log the current progress periodically, so OPS will know that we're still working + nanoseconds := float64(time.Since(start).Nanoseconds()) + // the estimated chunk is not accurate(likely under estimated), but the actual count is not accurate + // before the last table start, so use the bigger of the two should be a workaround + estimated := metric.ReadCounter(metric.ChunkCounter.WithLabelValues(metric.ChunkStateEstimated)) + pending := metric.ReadCounter(metric.ChunkCounter.WithLabelValues(metric.ChunkStatePending)) + if estimated < pending { + estimated = pending + } + finished := metric.ReadCounter(metric.ChunkCounter.WithLabelValues(metric.ChunkStateFinished)) + totalTables := metric.ReadCounter(metric.TableCounter.WithLabelValues(metric.TableStatePending, metric.TableResultSuccess)) + completedTables := metric.ReadCounter(metric.TableCounter.WithLabelValues(metric.TableStateCompleted, metric.TableResultSuccess)) + bytesRead := metric.ReadHistogramSum(metric.RowReadBytesHistogram) + engineEstimated := metric.ReadCounter(metric.ProcessedEngineCounter.WithLabelValues(metric.ChunkStateEstimated, metric.TableResultSuccess)) + enginePending := metric.ReadCounter(metric.ProcessedEngineCounter.WithLabelValues(metric.ChunkStatePending, metric.TableResultSuccess)) + if engineEstimated < enginePending { + engineEstimated = enginePending + } + engineFinished := metric.ReadCounter(metric.ProcessedEngineCounter.WithLabelValues(metric.TableStateImported, metric.TableResultSuccess)) + bytesWritten := metric.ReadCounter(metric.BytesCounter.WithLabelValues(metric.TableStateWritten)) + bytesImported := metric.ReadCounter(metric.BytesCounter.WithLabelValues(metric.TableStateImported)) + + var state string + var remaining zap.Field + if finished >= estimated { + if engineFinished < engineEstimated { + state = "importing" + } else { + state = "post-processing" + } + remaining = zap.Skip() + } else if finished > 0 { + state = "writing" + } else { + state = "preparing" + } + + // since we can't accurately estimate the extra time cost by import after all writing are finished, + // so here we use estimatedWritingProgress * 0.8 + estimatedImportingProgress * 0.2 as the total + // progress. + remaining = zap.Skip() + totalPercent := 0.0 + if finished > 0 { + writePercent := math.Min(finished/estimated, 1.0) + importPercent := 1.0 + if bytesWritten > 0 { + totalBytes := bytesWritten / writePercent + importPercent = math.Min(bytesImported/totalBytes, 1.0) + } + totalPercent = writePercent*0.8 + importPercent*0.2 + if totalPercent < 1.0 { + remainNanoseconds := (1.0 - totalPercent) / totalPercent * nanoseconds + remaining = zap.Duration("remaining", time.Duration(remainNanoseconds).Round(time.Second)) + } + } + + formatPercent := func(finish, estimate float64) string { + speed := "" + if estimated > 0 { + speed = fmt.Sprintf(" (%.1f%%)", finish/estimate*100) + } + return speed + } + + // avoid output bytes speed if there are no unfinished chunks + chunkSpeed := zap.Skip() + if bytesRead > 0 { + chunkSpeed = zap.Float64("speed(MiB/s)", bytesRead/(1048576e-9*nanoseconds)) + } + + // Note: a speed of 28 MiB/s roughly corresponds to 100 GiB/hour. + log.L().Info("progress", + zap.String("total", fmt.Sprintf("%.1f%%", totalPercent*100)), + // zap.String("files", fmt.Sprintf("%.0f/%.0f (%.1f%%)", finished, estimated, finished/estimated*100)), + zap.String("tables", fmt.Sprintf("%.0f/%.0f%s", completedTables, totalTables, formatPercent(completedTables, totalTables))), + zap.String("chunks", fmt.Sprintf("%.0f/%.0f%s", finished, estimated, formatPercent(finished, estimated))), + zap.String("engines", fmt.Sprintf("%.f/%.f%s", engineFinished, engineEstimated, formatPercent(engineFinished, engineEstimated))), + chunkSpeed, + zap.String("state", state), + remaining, + ) + + case <-checkQuotaChan: + // verify the total space occupied by sorted-kv-dir is below the quota, + // otherwise we perform an emergency import. + rc.enforceDiskQuota(ctx) + + case <-glueProgressTicker.C: + finished := metric.ReadCounter(metric.ChunkCounter.WithLabelValues(metric.ChunkStateFinished)) + rc.tidbGlue.Record(glue.RecordFinishedChunk, uint64(finished)) + } + } +} + +var checksumManagerKey struct{} + +func (rc *RestoreController) restoreTables(ctx context.Context) error { + logTask := log.L().Begin(zap.InfoLevel, "restore all tables data") + + // for local backend, we should disable some pd scheduler and change some settings, to + // make split region and ingest sst more stable + // because importer backend is mostly use for v3.x cluster which doesn't support these api, + // so we also don't do this for import backend + if rc.cfg.TikvImporter.Backend == config.BackendLocal { + // disable some pd schedulers + pdController, err := pdutil.NewPdController(ctx, rc.cfg.TiDB.PdAddr, + rc.tls.TLSConfig(), rc.tls.ToPDSecurityOption()) + if err != nil { + return errors.Trace(err) + } + logTask.Info("removing PD leader®ion schedulers") + restoreFn, e := pdController.RemoveSchedulers(ctx) + defer func() { + // use context.Background to make sure this restore function can still be executed even if ctx is canceled + if restoreE := restoreFn(context.Background()); restoreE != nil { + logTask.Warn("failed to restore removed schedulers, you may need to restore them manually", zap.Error(restoreE)) + return + } + logTask.Info("add back PD leader®ion schedulers") + }() + if e != nil { + return errors.Trace(err) + } + } + + type task struct { + tr *TableRestore + cp *TableCheckpoint + } + + totalTables := 0 + for _, dbMeta := range rc.dbMetas { + totalTables += len(dbMeta.Tables) + } + postProcessTaskChan := make(chan task, totalTables) + + var wg sync.WaitGroup + var restoreErr common.OnceError + + stopPeriodicActions := make(chan struct{}) + go rc.runPeriodicActions(ctx, stopPeriodicActions) + defer close(stopPeriodicActions) + + taskCh := make(chan task, rc.cfg.App.IndexConcurrency) + defer close(taskCh) + + manager, err := newChecksumManager(ctx, rc) + if err != nil { + return errors.Trace(err) + } + ctx2 := context.WithValue(ctx, &checksumManagerKey, manager) + for i := 0; i < rc.cfg.App.IndexConcurrency; i++ { + go func() { + for task := range taskCh { + tableLogTask := task.tr.logger.Begin(zap.InfoLevel, "restore table") + web.BroadcastTableCheckpoint(task.tr.tableName, task.cp) + needPostProcess, err := task.tr.restoreTable(ctx2, rc, task.cp) + err = errors.Annotatef(err, "restore table %s failed", task.tr.tableName) + tableLogTask.End(zap.ErrorLevel, err) + web.BroadcastError(task.tr.tableName, err) + metric.RecordTableCount("completed", err) + restoreErr.Set(err) + if needPostProcess { + postProcessTaskChan <- task + } + wg.Done() + } + }() + } + + // first collect all tables where the checkpoint is invalid + allInvalidCheckpoints := make(map[string]CheckpointStatus) + // collect all tables whose checkpoint's tableID can't match current tableID + allDirtyCheckpoints := make(map[string]struct{}) + for _, dbMeta := range rc.dbMetas { + dbInfo, ok := rc.dbInfos[dbMeta.Name] + if !ok { + return errors.Errorf("database %s not found in rc.dbInfos", dbMeta.Name) + } + for _, tableMeta := range dbMeta.Tables { + tableInfo, ok := dbInfo.Tables[tableMeta.Name] + if !ok { + return errors.Errorf("table info %s.%s not found", dbMeta.Name, tableMeta.Name) + } + + tableName := common.UniqueTable(dbInfo.Name, tableInfo.Name) + cp, err := rc.checkpointsDB.Get(ctx, tableName) + if err != nil { + return errors.Trace(err) + } + if cp.Status <= CheckpointStatusMaxInvalid { + allInvalidCheckpoints[tableName] = cp.Status + } else if cp.TableID > 0 && cp.TableID != tableInfo.ID { + allDirtyCheckpoints[tableName] = struct{}{} + } + } + } + + if len(allInvalidCheckpoints) != 0 { + logger := log.L() + logger.Error( + "TiDB Lightning has failed last time. To prevent data loss, this run will stop now. Please resolve errors first", + zap.Int("count", len(allInvalidCheckpoints)), + ) + + for tableName, status := range allInvalidCheckpoints { + failedStep := status * 10 + var action strings.Builder + action.WriteString("./tidb-lightning-ctl --checkpoint-error-") + switch failedStep { + case CheckpointStatusAlteredAutoInc, CheckpointStatusAnalyzed: + action.WriteString("ignore") + default: + action.WriteString("destroy") + } + action.WriteString("='") + action.WriteString(tableName) + action.WriteString("' --config=...") + + logger.Info("-", + zap.String("table", tableName), + zap.Uint8("status", uint8(status)), + zap.String("failedStep", failedStep.MetricName()), + zap.Stringer("recommendedAction", &action), + ) + } + + logger.Info("You may also run `./tidb-lightning-ctl --checkpoint-error-destroy=all --config=...` to start from scratch") + logger.Info("For details of this failure, read the log file from the PREVIOUS run") + + return errors.New("TiDB Lightning has failed last time; please resolve these errors first") + } + if len(allDirtyCheckpoints) > 0 { + logger := log.L() + logger.Error( + "TiDB Lightning has detected tables with illegal checkpoints. To prevent data mismatch, this run will stop now. Please remove these checkpoints first", + zap.Int("count", len(allDirtyCheckpoints)), + ) + + for tableName := range allDirtyCheckpoints { + logger.Info("-", + zap.String("table", tableName), + zap.String("recommendedAction", "./tidb-lightning-ctl --checkpoint-remove='"+tableName+"' --config=..."), + ) + } + + logger.Info("You may also run `./tidb-lightning-ctl --checkpoint-remove=all --config=...` to start from scratch") + + return errors.New("TiDB Lightning has detected tables with illegal checkpoints; please remove these checkpoints first") + } + + for _, dbMeta := range rc.dbMetas { + dbInfo := rc.dbInfos[dbMeta.Name] + for _, tableMeta := range dbMeta.Tables { + tableInfo := dbInfo.Tables[tableMeta.Name] + tableName := common.UniqueTable(dbInfo.Name, tableInfo.Name) + cp, err := rc.checkpointsDB.Get(ctx, tableName) + if err != nil { + return errors.Trace(err) + } + tr, err := NewTableRestore(tableName, tableMeta, dbInfo, tableInfo, cp) + if err != nil { + return errors.Trace(err) + } + + wg.Add(1) + select { + case taskCh <- task{tr: tr, cp: cp}: + case <-ctx.Done(): + return ctx.Err() + } + } + } + + wg.Wait() + // if context is done, should return directly + select { + case <-ctx.Done(): + err = restoreErr.Get() + if err == nil { + err = ctx.Err() + } + logTask.End(zap.ErrorLevel, err) + return err + default: + } + + close(postProcessTaskChan) + // otherwise, we should run all tasks in the post-process task chan + for i := 0; i < rc.cfg.App.TableConcurrency; i++ { + wg.Add(1) + go func() { + for task := range postProcessTaskChan { + // force all the remain post-process tasks to be executed + _, err := task.tr.postProcess(ctx2, rc, task.cp, true) + restoreErr.Set(err) + } + wg.Done() + }() + } + wg.Wait() + + err = restoreErr.Get() + logTask.End(zap.ErrorLevel, err) + return err +} + +func (t *TableRestore) restoreTable( + ctx context.Context, + rc *RestoreController, + cp *TableCheckpoint, +) (bool, error) { + // 1. Load the table info. + + select { + case <-ctx.Done(): + return false, ctx.Err() + default: + } + + // no need to do anything if the chunks are already populated + if len(cp.Engines) > 0 { + t.logger.Info("reusing engines and files info from checkpoint", + zap.Int("enginesCnt", len(cp.Engines)), + zap.Int("filesCnt", cp.CountChunks()), + ) + } else if cp.Status < CheckpointStatusAllWritten { + if err := t.populateChunks(ctx, rc, cp); err != nil { + return false, errors.Trace(err) + } + if err := rc.checkpointsDB.InsertEngineCheckpoints(ctx, t.tableName, cp.Engines); err != nil { + return false, errors.Trace(err) + } + web.BroadcastTableCheckpoint(t.tableName, cp) + + // rebase the allocator so it exceeds the number of rows. + if t.tableInfo.Core.PKIsHandle && t.tableInfo.Core.ContainsAutoRandomBits() { + cp.AllocBase = mathutil.MaxInt64(cp.AllocBase, t.tableInfo.Core.AutoRandID) + t.alloc.Get(autoid.AutoRandomType).Rebase(t.tableInfo.ID, cp.AllocBase, false) + } else { + cp.AllocBase = mathutil.MaxInt64(cp.AllocBase, t.tableInfo.Core.AutoIncID) + t.alloc.Get(autoid.RowIDAllocType).Rebase(t.tableInfo.ID, cp.AllocBase, false) + } + rc.saveCpCh <- saveCp{ + tableName: t.tableName, + merger: &RebaseCheckpointMerger{ + AllocBase: cp.AllocBase, + }, + } + } + + // 2. Restore engines (if still needed) + err := t.restoreEngines(ctx, rc, cp) + if err != nil { + return false, errors.Trace(err) + } + + // 3. Post-process. With the last parameter set to false, we can allow delay analyze execute latter + return t.postProcess(ctx, rc, cp, false /* force-analyze */) +} + +func (t *TableRestore) restoreEngines(ctx context.Context, rc *RestoreController, cp *TableCheckpoint) error { + indexEngineCp := cp.Engines[indexEngineID] + if indexEngineCp == nil { + return errors.Errorf("table %v index engine checkpoint not found", t.tableName) + } + + // The table checkpoint status set to `CheckpointStatusIndexImported` only if + // both all data engines and the index engine had been imported to TiKV. + // But persist index engine checkpoint status and table checkpoint status are + // not an atomic operation, so `cp.Status < CheckpointStatusIndexImported` + // but `indexEngineCp.Status == CheckpointStatusImported` could happen + // when kill lightning after saving index engine checkpoint status before saving + // table checkpoint status. + var closedIndexEngine *kv.ClosedEngine + if indexEngineCp.Status < CheckpointStatusImported && cp.Status < CheckpointStatusIndexImported { + indexWorker := rc.indexWorkers.Apply() + defer rc.indexWorkers.Recycle(indexWorker) + + indexEngine, err := rc.backend.OpenEngine(ctx, t.tableName, indexEngineID) + if err != nil { + return errors.Trace(err) + } + + // The table checkpoint status less than `CheckpointStatusIndexImported` implies + // that index engine checkpoint status less than `CheckpointStatusImported`. + // So the index engine must be found in above process + if indexEngine == nil { + return errors.Errorf("table checkpoint status %v incompitable with index engine checkpoint status %v", + cp.Status, indexEngineCp.Status) + } + + logTask := t.logger.Begin(zap.InfoLevel, "import whole table") + var wg sync.WaitGroup + var engineErr common.OnceError + + for engineID, engine := range cp.Engines { + select { + case <-ctx.Done(): + // Set engineErr and break this for loop to wait all the sub-routines done before return. + // Directly return may cause panic because caller will close the pebble db but some sub routines + // are still reading from or writing to the pebble db. + engineErr.Set(ctx.Err()) + default: + } + if engineErr.Get() != nil { + break + } + + // Should skip index engine + if engineID < 0 { + continue + } + + if engine.Status < CheckpointStatusImported { + wg.Add(1) + + // Note: We still need tableWorkers to control the concurrency of tables. + // In the future, we will investigate more about + // the difference between restoring tables concurrently and restoring tables one by one. + restoreWorker := rc.tableWorkers.Apply() + + go func(w *worker.Worker, eid int32, ecp *EngineCheckpoint) { + defer wg.Done() + + engineLogTask := t.logger.With(zap.Int32("engineNumber", eid)).Begin(zap.InfoLevel, "restore engine") + dataClosedEngine, err := t.restoreEngine(ctx, rc, indexEngine, eid, ecp) + engineLogTask.End(zap.ErrorLevel, err) + rc.tableWorkers.Recycle(w) + if err != nil { + engineErr.Set(err) + return + } + + failpoint.Inject("FailBeforeDataEngineImported", func() { + panic("forcing failure due to FailBeforeDataEngineImported") + }) + + dataWorker := rc.closedEngineLimit.Apply() + defer rc.closedEngineLimit.Recycle(dataWorker) + if err := t.importEngine(ctx, dataClosedEngine, rc, eid, ecp); err != nil { + engineErr.Set(err) + } + }(restoreWorker, engineID, engine) + } + } + + wg.Wait() + + err = engineErr.Get() + logTask.End(zap.ErrorLevel, err) + if err != nil { + return errors.Trace(err) + } + + // If index engine file has been closed but not imported only if context cancel occurred + // when `importKV()` execution, so `UnsafeCloseEngine` and continue import it. + if indexEngineCp.Status == CheckpointStatusClosed { + closedIndexEngine, err = rc.backend.UnsafeCloseEngine(ctx, t.tableName, indexEngineID) + } else { + closedIndexEngine, err = indexEngine.Close(ctx) + rc.saveStatusCheckpoint(t.tableName, indexEngineID, err, CheckpointStatusClosed) + } + if err != nil { + return errors.Trace(err) + } + } + + if cp.Status < CheckpointStatusIndexImported { + var err error + if indexEngineCp.Status < CheckpointStatusImported { + // the lock ensures the import() step will not be concurrent. + if !rc.isLocalBackend() { + rc.postProcessLock.Lock() + } + err = t.importKV(ctx, closedIndexEngine, rc, indexEngineID) + rc.saveStatusCheckpoint(t.tableName, indexEngineID, err, CheckpointStatusImported) + if !rc.isLocalBackend() { + rc.postProcessLock.Unlock() + } + } + + failpoint.Inject("FailBeforeIndexEngineImported", func() { + panic("forcing failure due to FailBeforeIndexEngineImported") + }) + + rc.saveStatusCheckpoint(t.tableName, WholeTableEngineID, err, CheckpointStatusIndexImported) + if err != nil { + return errors.Trace(err) + } + } + return nil +} + +func (t *TableRestore) restoreEngine( + ctx context.Context, + rc *RestoreController, + indexEngine *kv.OpenedEngine, + engineID int32, + cp *EngineCheckpoint, +) (*kv.ClosedEngine, error) { + if cp.Status >= CheckpointStatusClosed { + closedEngine, err := rc.backend.UnsafeCloseEngine(ctx, t.tableName, engineID) + // If any error occurred, recycle worker immediately + if err != nil { + return closedEngine, errors.Trace(err) + } + return closedEngine, nil + } + + // In Local backend, the local writer will produce an SST file for batch + // ingest into the local DB every 1000 KV pairs or up to 512 MiB. + // There are (region-concurrency) data writers, and (index-concurrency) index writers. + // Thus, the disk size occupied by these writers are up to + // (region-concurrency + index-concurrency) * 512 MiB. + // This number should not exceed the disk quota. + // Therefore, we need to reduce that "512 MiB" to respect the disk quota: + localWriterMaxCacheSize := int64(rc.cfg.TikvImporter.DiskQuota) // int64(rc.cfg.App.IndexConcurrency+rc.cfg.App.RegionConcurrency) + if localWriterMaxCacheSize > config.LocalMemoryTableSize { + localWriterMaxCacheSize = config.LocalMemoryTableSize + } + + indexWriter, err := indexEngine.LocalWriter(ctx, localWriterMaxCacheSize) + if err != nil { + return nil, errors.Trace(err) + } + + logTask := t.logger.With(zap.Int32("engineNumber", engineID)).Begin(zap.InfoLevel, "encode kv data and write") + + dataEngine, err := rc.backend.OpenEngine(ctx, t.tableName, engineID) + if err != nil { + return nil, errors.Trace(err) + } + + var wg sync.WaitGroup + var chunkErr common.OnceError + + // Restore table data + for chunkIndex, chunk := range cp.Chunks { + if chunk.Chunk.Offset >= chunk.Chunk.EndOffset { + continue + } + + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + if chunkErr.Get() != nil { + break + } + + // Flows : + // 1. read mydump file + // 2. sql -> kvs + // 3. load kvs data (into kv deliver server) + // 4. flush kvs data (into tikv node) + cr, err := newChunkRestore(ctx, chunkIndex, rc.cfg, chunk, rc.ioWorkers, rc.store, t.tableInfo) + if err != nil { + return nil, errors.Trace(err) + } + var remainChunkCnt float64 + if chunk.Chunk.Offset < chunk.Chunk.EndOffset { + remainChunkCnt = float64(chunk.Chunk.EndOffset-chunk.Chunk.Offset) / float64(chunk.Chunk.EndOffset-chunk.Key.Offset) + metric.ChunkCounter.WithLabelValues(metric.ChunkStatePending).Add(remainChunkCnt) + } + + restoreWorker := rc.regionWorkers.Apply() + wg.Add(1) + dataWriter, err := dataEngine.LocalWriter(ctx, localWriterMaxCacheSize) + if err != nil { + return nil, errors.Trace(err) + } + go func(w *worker.Worker, cr *chunkRestore) { + // Restore a chunk. + defer func() { + cr.close() + wg.Done() + rc.regionWorkers.Recycle(w) + }() + metric.ChunkCounter.WithLabelValues(metric.ChunkStateRunning).Add(remainChunkCnt) + err := cr.restore(ctx, t, engineID, dataWriter, indexWriter, rc) + if err == nil { + err = dataWriter.Close() + } + if err == nil { + metric.ChunkCounter.WithLabelValues(metric.ChunkStateFinished).Add(remainChunkCnt) + metric.BytesCounter.WithLabelValues(metric.TableStateWritten).Add(float64(cr.chunk.Checksum.SumSize())) + } else { + metric.ChunkCounter.WithLabelValues(metric.ChunkStateFailed).Add(remainChunkCnt) + chunkErr.Set(err) + } + }(restoreWorker, cr) + } + + wg.Wait() + if err := indexWriter.Close(); err != nil { + return nil, errors.Trace(err) + } + + // Report some statistics into the log for debugging. + totalKVSize := uint64(0) + totalSQLSize := int64(0) + for _, chunk := range cp.Chunks { + totalKVSize += chunk.Checksum.SumSize() + totalSQLSize += chunk.Chunk.EndOffset - chunk.Chunk.Offset + } + + err = chunkErr.Get() + logTask.End(zap.ErrorLevel, err, + zap.Int64("read", totalSQLSize), + zap.Uint64("written", totalKVSize), + ) + + flushAndSaveAllChunks := func() error { + if err = indexEngine.Flush(ctx); err != nil { + return errors.Trace(err) + } + // Currently we write all the checkpoints after data&index engine are flushed. + for _, chunk := range cp.Chunks { + saveCheckpoint(rc, t, engineID, chunk) + } + return nil + } + + // in local mode, this check-point make no sense, because we don't do flush now, + // so there may be data lose if exit at here. So we don't write this checkpoint + // here like other mode. + if !rc.isLocalBackend() { + rc.saveStatusCheckpoint(t.tableName, engineID, err, CheckpointStatusAllWritten) + } + if err != nil { + // if process is canceled, we should flush all chunk checkpoints for local backend + if rc.isLocalBackend() && common.IsContextCanceledError(err) { + // ctx is canceled, so to avoid Close engine failed, we use `context.Background()` here + if _, err2 := dataEngine.Close(context.Background()); err2 != nil { + log.L().Warn("flush all chunk checkpoints failed before manually exits", zap.Error(err2)) + return nil, errors.Trace(err) + } + if err2 := flushAndSaveAllChunks(); err2 != nil { + log.L().Warn("flush all chunk checkpoints failed before manually exits", zap.Error(err2)) + } + } + return nil, errors.Trace(err) + } + + closedDataEngine, err := dataEngine.Close(ctx) + // For local backend, if checkpoint is enabled, we must flush index engine to avoid data loss. + // this flush action impact up to 10% of the performance, so we only do it if necessary. + if err == nil && rc.cfg.Checkpoint.Enable && rc.isLocalBackend() { + if err = flushAndSaveAllChunks(); err != nil { + return nil, errors.Trace(err) + } + + // Currently we write all the checkpoints after data&index engine are flushed. + for _, chunk := range cp.Chunks { + saveCheckpoint(rc, t, engineID, chunk) + } + } + rc.saveStatusCheckpoint(t.tableName, engineID, err, CheckpointStatusClosed) + if err != nil { + // If any error occurred, recycle worker immediately + return nil, errors.Trace(err) + } + return closedDataEngine, nil +} + +func (t *TableRestore) importEngine( + ctx context.Context, + closedEngine *kv.ClosedEngine, + rc *RestoreController, + engineID int32, + cp *EngineCheckpoint, +) error { + if cp.Status >= CheckpointStatusImported { + return nil + } + + // 1. close engine, then calling import + // FIXME: flush is an asynchronous operation, what if flush failed? + + // the lock ensures the import() step will not be concurrent. + if !rc.isLocalBackend() { + rc.postProcessLock.Lock() + } + err := t.importKV(ctx, closedEngine, rc, engineID) + rc.saveStatusCheckpoint(t.tableName, engineID, err, CheckpointStatusImported) + if !rc.isLocalBackend() { + rc.postProcessLock.Unlock() + } + if err != nil { + return errors.Trace(err) + } + + // 2. perform a level-1 compact if idling. + if rc.cfg.PostRestore.Level1Compact && + atomic.CompareAndSwapInt32(&rc.compactState, compactStateIdle, compactStateDoing) { + go func() { + // we ignore level-1 compact failure since it is not fatal. + // no need log the error, it is done in (*Importer).Compact already. + _ = rc.doCompact(ctx, Level1Compact) + atomic.StoreInt32(&rc.compactState, compactStateIdle) + }() + } + + return nil +} + +// postProcess execute rebase-auto-id/checksum/analyze according to the task config. +// +// if the parameter forcePostProcess to true, postProcess force run checksum and analyze even if the +// post-process-at-last config is true. And if this two phases are skipped, the first return value will be true. +func (t *TableRestore) postProcess( + ctx context.Context, + rc *RestoreController, + cp *TableCheckpoint, + forcePostProcess bool, +) (bool, error) { + // there are no data in this table, no need to do post process + // this is important for tables that are just the dump table of views + // because at this stage, the table was already deleted and replaced by the related view + if len(cp.Engines) == 1 { + return false, nil + } + + // 3. alter table set auto_increment + if cp.Status < CheckpointStatusAlteredAutoInc { + rc.alterTableLock.Lock() + tblInfo := t.tableInfo.Core + var err error + if tblInfo.PKIsHandle && tblInfo.ContainsAutoRandomBits() { + err = AlterAutoRandom(ctx, rc.tidbGlue.GetSQLExecutor(), t.tableName, t.alloc.Get(autoid.AutoRandomType).Base()+1) + } else if common.TableHasAutoRowID(tblInfo) || tblInfo.GetAutoIncrementColInfo() != nil { + // only alter auto increment id iff table contains auto-increment column or generated handle + err = AlterAutoIncrement(ctx, rc.tidbGlue.GetSQLExecutor(), t.tableName, t.alloc.Get(autoid.RowIDAllocType).Base()+1) + } + rc.alterTableLock.Unlock() + rc.saveStatusCheckpoint(t.tableName, WholeTableEngineID, err, CheckpointStatusAlteredAutoInc) + if err != nil { + return false, err + } + cp.Status = CheckpointStatusAlteredAutoInc + } + + // tidb backend don't need checksum & analyze + if !rc.backend.ShouldPostProcess() { + t.logger.Debug("skip checksum & analyze, not supported by this backend") + rc.saveStatusCheckpoint(t.tableName, WholeTableEngineID, nil, CheckpointStatusAnalyzeSkipped) + return false, nil + } + + w := rc.checksumWorks.Apply() + defer rc.checksumWorks.Recycle(w) + + finished := true + if cp.Status < CheckpointStatusChecksummed { + if rc.cfg.PostRestore.Checksum == config.OpLevelOff { + t.logger.Info("skip checksum") + rc.saveStatusCheckpoint(t.tableName, WholeTableEngineID, nil, CheckpointStatusChecksumSkipped) + } else { + if forcePostProcess || !rc.cfg.PostRestore.PostProcessAtLast { + // 4. do table checksum + var localChecksum verify.KVChecksum + for _, engine := range cp.Engines { + for _, chunk := range engine.Chunks { + localChecksum.Add(&chunk.Checksum) + } + } + t.logger.Info("local checksum", zap.Object("checksum", &localChecksum)) + err := t.compareChecksum(ctx, localChecksum) + // with post restore level 'optional', we will skip checksum error + if rc.cfg.PostRestore.Checksum == config.OpLevelOptional { + if err != nil { + t.logger.Warn("compare checksum failed, will skip this error and go on", log.ShortError(err)) + err = nil + } + } + rc.saveStatusCheckpoint(t.tableName, WholeTableEngineID, err, CheckpointStatusChecksummed) + if err != nil { + return false, errors.Trace(err) + } + cp.Status = CheckpointStatusChecksummed + } else { + finished = false + } + } + } + if !finished { + return !finished, nil + } + + // 5. do table analyze + if cp.Status < CheckpointStatusAnalyzed { + if rc.cfg.PostRestore.Analyze == config.OpLevelOff { + t.logger.Info("skip analyze") + rc.saveStatusCheckpoint(t.tableName, WholeTableEngineID, nil, CheckpointStatusAnalyzeSkipped) + cp.Status = CheckpointStatusAnalyzed + } else if forcePostProcess || !rc.cfg.PostRestore.PostProcessAtLast { + err := t.analyzeTable(ctx, rc.tidbGlue.GetSQLExecutor()) + // witch post restore level 'optional', we will skip analyze error + if rc.cfg.PostRestore.Analyze == config.OpLevelOptional { + if err != nil { + t.logger.Warn("analyze table failed, will skip this error and go on", log.ShortError(err)) + err = nil + } + } + rc.saveStatusCheckpoint(t.tableName, WholeTableEngineID, err, CheckpointStatusAnalyzed) + if err != nil { + return false, errors.Trace(err) + } + cp.Status = CheckpointStatusAnalyzed + } else { + finished = false + } + } + + return !finished, nil +} + +// do full compaction for the whole data. +func (rc *RestoreController) fullCompact(ctx context.Context) error { + if !rc.cfg.PostRestore.Compact { + log.L().Info("skip full compaction") + return nil + } + + // wait until any existing level-1 compact to complete first. + task := log.L().Begin(zap.InfoLevel, "wait for completion of existing level 1 compaction") + for !atomic.CompareAndSwapInt32(&rc.compactState, compactStateIdle, compactStateDoing) { + time.Sleep(100 * time.Millisecond) + } + task.End(zap.ErrorLevel, nil) + + return errors.Trace(rc.doCompact(ctx, FullLevelCompact)) +} + +func (rc *RestoreController) doCompact(ctx context.Context, level int32) error { + tls := rc.tls.WithHost(rc.cfg.TiDB.PdAddr) + return kv.ForAllStores( + ctx, + tls, + kv.StoreStateDisconnected, + func(c context.Context, store *kv.Store) error { + return kv.Compact(c, tls, store.Address, level) + }, + ) +} + +func (rc *RestoreController) switchToImportMode(ctx context.Context) { + rc.switchTiKVMode(ctx, sstpb.SwitchMode_Import) +} + +func (rc *RestoreController) switchToNormalMode(ctx context.Context) error { + rc.switchTiKVMode(ctx, sstpb.SwitchMode_Normal) + return nil +} + +func (rc *RestoreController) switchTiKVMode(ctx context.Context, mode sstpb.SwitchMode) { + // It is fine if we miss some stores which did not switch to Import mode, + // since we're running it periodically, so we exclude disconnected stores. + // But it is essential all stores be switched back to Normal mode to allow + // normal operation. + var minState kv.StoreState + if mode == sstpb.SwitchMode_Import { + minState = kv.StoreStateOffline + } else { + minState = kv.StoreStateDisconnected + } + tls := rc.tls.WithHost(rc.cfg.TiDB.PdAddr) + // we ignore switch mode failure since it is not fatal. + // no need log the error, it is done in kv.SwitchMode already. + _ = kv.ForAllStores( + ctx, + tls, + minState, + func(c context.Context, store *kv.Store) error { + return kv.SwitchMode(c, tls, store.Address, mode) + }, + ) +} + +func (rc *RestoreController) enforceDiskQuota(ctx context.Context) { + if !atomic.CompareAndSwapInt32(&rc.diskQuotaState, diskQuotaStateIdle, diskQuotaStateChecking) { + // do not run multiple the disk quota check / import simultaneously. + // (we execute the lock check in background to avoid blocking the cron thread) + return + } + + go func() { + // locker is assigned when we detect the disk quota is exceeded. + // before the disk quota is confirmed exceeded, we keep the diskQuotaLock + // unlocked to avoid periodically interrupting the writer threads. + var locker sync.Locker + defer func() { + atomic.StoreInt32(&rc.diskQuotaState, diskQuotaStateIdle) + if locker != nil { + locker.Unlock() + } + }() + + isRetrying := false + + for { + // sleep for a cycle if we are retrying because there is nothing new to import. + if isRetrying { + select { + case <-ctx.Done(): + return + case <-time.After(rc.cfg.Cron.CheckDiskQuota.Duration): + } + } else { + isRetrying = true + } + + quota := int64(rc.cfg.TikvImporter.DiskQuota) + largeEngines, inProgressLargeEngines, totalDiskSize, totalMemSize := rc.backend.CheckDiskQuota(quota) + metric.LocalStorageUsageBytesGauge.WithLabelValues("disk").Set(float64(totalDiskSize)) + metric.LocalStorageUsageBytesGauge.WithLabelValues("mem").Set(float64(totalMemSize)) + + logger := log.With( + zap.Int64("diskSize", totalDiskSize), + zap.Int64("memSize", totalMemSize), + zap.Int64("quota", quota), + zap.Int("largeEnginesCount", len(largeEngines)), + zap.Int("inProgressLargeEnginesCount", inProgressLargeEngines)) + + if len(largeEngines) == 0 && inProgressLargeEngines == 0 { + logger.Debug("disk quota respected") + return + } + + if locker == nil { + // blocks all writers when we detected disk quota being exceeded. + rc.diskQuotaLock.Lock() + locker = &rc.diskQuotaLock + } + + logger.Warn("disk quota exceeded") + if len(largeEngines) == 0 { + logger.Warn("all large engines are already importing, keep blocking all writes") + continue + } + + // flush all engines so that checkpoints can be updated. + if err := rc.backend.FlushAll(ctx); err != nil { + logger.Error("flush engine for disk quota failed, check again later", log.ShortError(err)) + return + } + + // at this point, all engines are synchronized on disk. + // we then import every large engines one by one and complete. + // if any engine failed to import, we just try again next time, since the data are still intact. + atomic.StoreInt32(&rc.diskQuotaState, diskQuotaStateImporting) + task := logger.Begin(zap.WarnLevel, "importing large engines for disk quota") + var importErr error + for _, engine := range largeEngines { + if err := rc.backend.UnsafeImportAndReset(ctx, engine); err != nil { + importErr = multierr.Append(importErr, err) + } + } + task.End(zap.ErrorLevel, importErr) + return + } + }() +} + +func (rc *RestoreController) checkRequirements(ctx context.Context) error { + // skip requirement check if explicitly turned off + if !rc.cfg.App.CheckRequirements { + return nil + } + return rc.backend.CheckRequirements(ctx) +} + +func (rc *RestoreController) setGlobalVariables(ctx context.Context) error { + // set new collation flag base on tidb config + enabled := ObtainNewCollationEnabled(ctx, rc.tidbGlue.GetSQLExecutor()) + // we should enable/disable new collation here since in server mode, tidb config + // may be different in different tasks + collate.SetNewCollationEnabledForTest(enabled) + return nil +} + +func (rc *RestoreController) waitCheckpointFinish() { + // wait checkpoint process finish so that we can do cleanup safely + close(rc.saveCpCh) + rc.checkpointsWg.Wait() +} + +func (rc *RestoreController) cleanCheckpoints(ctx context.Context) error { + rc.waitCheckpointFinish() + + if !rc.cfg.Checkpoint.Enable { + return nil + } + + logger := log.With( + zap.Bool("keepAfterSuccess", rc.cfg.Checkpoint.KeepAfterSuccess), + zap.Int64("taskID", rc.cfg.TaskID), + ) + + task := logger.Begin(zap.InfoLevel, "clean checkpoints") + var err error + if rc.cfg.Checkpoint.KeepAfterSuccess { + err = rc.checkpointsDB.MoveCheckpoints(ctx, rc.cfg.TaskID) + } else { + err = rc.checkpointsDB.RemoveCheckpoint(ctx, "all") + } + task.End(zap.ErrorLevel, err) + return errors.Annotate(err, "clean checkpoints") +} + +func (rc *RestoreController) isLocalBackend() bool { + return rc.cfg.TikvImporter.Backend == "local" +} + +type chunkRestore struct { + parser mydump.Parser + index int + chunk *ChunkCheckpoint +} + +func newChunkRestore( + ctx context.Context, + index int, + cfg *config.Config, + chunk *ChunkCheckpoint, + ioWorkers *worker.Pool, + store storage.ExternalStorage, + tableInfo *TidbTableInfo, +) (*chunkRestore, error) { + blockBufSize := int64(cfg.Mydumper.ReadBlockSize) + + var reader storage.ReadSeekCloser + var err error + if chunk.FileMeta.Type == mydump.SourceTypeParquet { + reader, err = mydump.OpenParquetReader(ctx, store, chunk.FileMeta.Path, chunk.FileMeta.FileSize) + } else { + reader, err = store.Open(ctx, chunk.FileMeta.Path) + } + if err != nil { + return nil, errors.Trace(err) + } + + var parser mydump.Parser + switch chunk.FileMeta.Type { + case mydump.SourceTypeCSV: + hasHeader := cfg.Mydumper.CSV.Header && chunk.Chunk.Offset == 0 + parser = mydump.NewCSVParser(&cfg.Mydumper.CSV, reader, blockBufSize, ioWorkers, hasHeader) + case mydump.SourceTypeSQL: + parser = mydump.NewChunkParser(cfg.TiDB.SQLMode, reader, blockBufSize, ioWorkers) + case mydump.SourceTypeParquet: + parser, err = mydump.NewParquetParser(ctx, store, reader, chunk.FileMeta.Path) + if err != nil { + return nil, errors.Trace(err) + } + default: + panic(fmt.Sprintf("file '%s' with unknown source type '%s'", chunk.Key.Path, chunk.FileMeta.Type.String())) + } + + if err = parser.SetPos(chunk.Chunk.Offset, chunk.Chunk.PrevRowIDMax); err != nil { + return nil, errors.Trace(err) + } + if len(chunk.ColumnPermutation) > 0 { + parser.SetColumns(getColumnNames(tableInfo.Core, chunk.ColumnPermutation)) + } + + return &chunkRestore{ + parser: parser, + index: index, + chunk: chunk, + }, nil +} + +func (cr *chunkRestore) close() { + cr.parser.Close() +} + +type TableRestore struct { + // The unique table name in the form "`db`.`tbl`". + tableName string + dbInfo *TidbDBInfo + tableInfo *TidbTableInfo + tableMeta *mydump.MDTableMeta + encTable table.Table + alloc autoid.Allocators + logger log.Logger +} + +func NewTableRestore( + tableName string, + tableMeta *mydump.MDTableMeta, + dbInfo *TidbDBInfo, + tableInfo *TidbTableInfo, + cp *TableCheckpoint, +) (*TableRestore, error) { + idAlloc := kv.NewPanickingAllocators(cp.AllocBase) + tbl, err := tables.TableFromMeta(idAlloc, tableInfo.Core) + if err != nil { + return nil, errors.Annotatef(err, "failed to tables.TableFromMeta %s", tableName) + } + + return &TableRestore{ + tableName: tableName, + dbInfo: dbInfo, + tableInfo: tableInfo, + tableMeta: tableMeta, + encTable: tbl, + alloc: idAlloc, + logger: log.With(zap.String("table", tableName)), + }, nil +} + +func (tr *TableRestore) Close() { + tr.encTable = nil + tr.logger.Info("restore done") +} + +func (t *TableRestore) populateChunks(ctx context.Context, rc *RestoreController, cp *TableCheckpoint) error { + task := t.logger.Begin(zap.InfoLevel, "load engines and files") + chunks, err := mydump.MakeTableRegions(ctx, t.tableMeta, len(t.tableInfo.Core.Columns), rc.cfg, rc.ioWorkers, rc.store) + if err == nil { + timestamp := time.Now().Unix() + failpoint.Inject("PopulateChunkTimestamp", func(v failpoint.Value) { + timestamp = int64(v.(int)) + }) + for _, chunk := range chunks { + engine, found := cp.Engines[chunk.EngineID] + if !found { + engine = &EngineCheckpoint{ + Status: CheckpointStatusLoaded, + } + cp.Engines[chunk.EngineID] = engine + } + ccp := &ChunkCheckpoint{ + Key: ChunkCheckpointKey{ + Path: chunk.FileMeta.Path, + Offset: chunk.Chunk.Offset, + }, + FileMeta: chunk.FileMeta, + ColumnPermutation: nil, + Chunk: chunk.Chunk, + Timestamp: timestamp, + } + if len(chunk.Chunk.Columns) > 0 { + perms, err := t.parseColumnPermutations(chunk.Chunk.Columns) + if err != nil { + return errors.Trace(err) + } + ccp.ColumnPermutation = perms + } + engine.Chunks = append(engine.Chunks, ccp) + } + + // Add index engine checkpoint + cp.Engines[indexEngineID] = &EngineCheckpoint{Status: CheckpointStatusLoaded} + } + task.End(zap.ErrorLevel, err, + zap.Int("enginesCnt", len(cp.Engines)), + zap.Int("filesCnt", len(chunks)), + ) + return err +} + +// initializeColumns computes the "column permutation" for an INSERT INTO +// statement. Suppose a table has columns (a, b, c, d) in canonical order, and +// we execute `INSERT INTO (d, b, a) VALUES ...`, we will need to remap the +// columns as: +// +// - column `a` is at position 2 +// - column `b` is at position 1 +// - column `c` is missing +// - column `d` is at position 0 +// +// The column permutation of (d, b, a) is set to be [2, 1, -1, 0]. +// +// The argument `columns` _must_ be in lower case. +func (t *TableRestore) initializeColumns(columns []string, ccp *ChunkCheckpoint) error { + var colPerm []int + if len(columns) == 0 { + colPerm = make([]int, 0, len(t.tableInfo.Core.Columns)+1) + shouldIncludeRowID := common.TableHasAutoRowID(t.tableInfo.Core) + + // no provided columns, so use identity permutation. + for i := range t.tableInfo.Core.Columns { + colPerm = append(colPerm, i) + } + if shouldIncludeRowID { + colPerm = append(colPerm, -1) + } + } else { + var err error + colPerm, err = t.parseColumnPermutations(columns) + if err != nil { + return errors.Trace(err) + } + } + + ccp.ColumnPermutation = colPerm + return nil +} + +func (t *TableRestore) parseColumnPermutations(columns []string) ([]int, error) { + colPerm := make([]int, 0, len(t.tableInfo.Core.Columns)+1) + + columnMap := make(map[string]int) + for i, column := range columns { + columnMap[column] = i + } + + tableColumnMap := make(map[string]int) + for i, col := range t.tableInfo.Core.Columns { + tableColumnMap[col.Name.L] = i + } + + // check if there are some unknown columns + var unknownCols []string + for _, c := range columns { + if _, ok := tableColumnMap[c]; !ok && c != model.ExtraHandleName.L { + unknownCols = append(unknownCols, c) + } + } + if len(unknownCols) > 0 { + return colPerm, errors.Errorf("unknown columns in header %s", unknownCols) + } + + for _, colInfo := range t.tableInfo.Core.Columns { + if i, ok := columnMap[colInfo.Name.L]; ok { + colPerm = append(colPerm, i) + } else { + if len(colInfo.GeneratedExprString) == 0 { + t.logger.Warn("column missing from data file, going to fill with default value", + zap.String("colName", colInfo.Name.O), + zap.Stringer("colType", &colInfo.FieldType), + ) + } + colPerm = append(colPerm, -1) + } + } + if i, ok := columnMap[model.ExtraHandleName.L]; ok { + colPerm = append(colPerm, i) + } else if common.TableHasAutoRowID(t.tableInfo.Core) { + colPerm = append(colPerm, -1) + } + + return colPerm, nil +} + +func getColumnNames(tableInfo *model.TableInfo, permutation []int) []string { + colIndexes := make([]int, 0, len(permutation)) + for i := 0; i < len(permutation); i++ { + colIndexes = append(colIndexes, -1) + } + colCnt := 0 + for i, p := range permutation { + if p >= 0 { + colIndexes[p] = i + colCnt++ + } + } + + names := make([]string, 0, colCnt) + for _, idx := range colIndexes { + // skip columns with index -1 + if idx >= 0 { + // original fiels contains _tidb_rowid field + if idx == len(tableInfo.Columns) { + names = append(names, model.ExtraHandleName.O) + } else { + names = append(names, tableInfo.Columns[idx].Name.O) + } + } + } + return names +} + +func (tr *TableRestore) importKV( + ctx context.Context, + closedEngine *kv.ClosedEngine, + rc *RestoreController, + engineID int32, +) error { + task := closedEngine.Logger().Begin(zap.InfoLevel, "import and cleanup engine") + + err := closedEngine.Import(ctx) + rc.saveStatusCheckpoint(tr.tableName, engineID, err, CheckpointStatusImported) + if err == nil { + closedEngine.Cleanup(ctx) + } + + dur := task.End(zap.ErrorLevel, err) + + if err != nil { + return errors.Trace(err) + } + + metric.ImportSecondsHistogram.Observe(dur.Seconds()) + + failpoint.Inject("SlowDownImport", func() {}) + + return nil +} + +// do checksum for each table. +func (tr *TableRestore) compareChecksum(ctx context.Context, localChecksum verify.KVChecksum) error { + remoteChecksum, err := DoChecksum(ctx, tr.tableInfo) + if err != nil { + return errors.Trace(err) + } + + if remoteChecksum.Checksum != localChecksum.Sum() || + remoteChecksum.TotalKVs != localChecksum.SumKVS() || + remoteChecksum.TotalBytes != localChecksum.SumSize() { + return errors.Errorf("checksum mismatched remote vs local => (checksum: %d vs %d) (total_kvs: %d vs %d) (total_bytes:%d vs %d)", + remoteChecksum.Checksum, localChecksum.Sum(), + remoteChecksum.TotalKVs, localChecksum.SumKVS(), + remoteChecksum.TotalBytes, localChecksum.SumSize(), + ) + } + + tr.logger.Info("checksum pass", zap.Object("local", &localChecksum)) + return nil +} + +func (tr *TableRestore) analyzeTable(ctx context.Context, g glue.SQLExecutor) error { + task := tr.logger.Begin(zap.InfoLevel, "analyze") + err := g.ExecuteWithLog(ctx, "ANALYZE TABLE "+tr.tableName, "analyze table", tr.logger) + task.End(zap.ErrorLevel, err) + return err +} + +//////////////////////////////////////////////////////////////// + +var ( + maxKVQueueSize = 128 // Cache at most this number of rows before blocking the encode loop + minDeliverBytes uint64 = 65536 // 64 KB. batch at least this amount of bytes to reduce number of messages +) + +type deliveredKVs struct { + kvs kv.Row // if kvs is nil, this indicated we've got the last message. + columns []string + offset int64 + rowID int64 +} + +type deliverResult struct { + totalDur time.Duration + err error +} + +func (cr *chunkRestore) deliverLoop( + ctx context.Context, + kvsCh <-chan []deliveredKVs, + t *TableRestore, + engineID int32, + dataEngine, indexEngine *kv.LocalEngineWriter, + rc *RestoreController, +) (deliverTotalDur time.Duration, err error) { + var channelClosed bool + + deliverLogger := t.logger.With( + zap.Int32("engineNumber", engineID), + zap.Int("fileIndex", cr.index), + zap.Stringer("path", &cr.chunk.Key), + zap.String("task", "deliver"), + ) + + for !channelClosed { + var dataChecksum, indexChecksum verify.KVChecksum + var columns []string + var kvPacket []deliveredKVs + // init these two field as checkpoint current value, so even if there are no kv pairs delivered, + // chunk checkpoint should stay the same + offset := cr.chunk.Chunk.Offset + rowID := cr.chunk.Chunk.PrevRowIDMax + // Fetch enough KV pairs from the source. + dataKVs := rc.backend.MakeEmptyRows() + indexKVs := rc.backend.MakeEmptyRows() + + populate: + for dataChecksum.SumSize() < minDeliverBytes { + select { + case kvPacket = <-kvsCh: + if len(kvPacket) == 0 { + channelClosed = true + break populate + } + for _, p := range kvPacket { + p.kvs.ClassifyAndAppend(&dataKVs, &dataChecksum, &indexKVs, &indexChecksum) + columns = p.columns + offset = p.offset + rowID = p.rowID + } + case <-ctx.Done(): + err = ctx.Err() + return + } + } + + // we are allowed to save checkpoint when the disk quota state moved to "importing" + // since all engines are flushed. + if atomic.LoadInt32(&rc.diskQuotaState) == diskQuotaStateImporting { + saveCheckpoint(rc, t, engineID, cr.chunk) + } + + func() { + rc.diskQuotaLock.RLock() + defer rc.diskQuotaLock.RUnlock() + + // Write KVs into the engine + start := time.Now() + + if err = dataEngine.WriteRows(ctx, columns, dataKVs); err != nil { + deliverLogger.Error("write to data engine failed", log.ShortError(err)) + return + } + if err = indexEngine.WriteRows(ctx, columns, indexKVs); err != nil { + deliverLogger.Error("write to index engine failed", log.ShortError(err)) + return + } + + deliverDur := time.Since(start) + deliverTotalDur += deliverDur + metric.BlockDeliverSecondsHistogram.Observe(deliverDur.Seconds()) + metric.BlockDeliverBytesHistogram.WithLabelValues(metric.BlockDeliverKindData).Observe(float64(dataChecksum.SumSize())) + metric.BlockDeliverBytesHistogram.WithLabelValues(metric.BlockDeliverKindIndex).Observe(float64(indexChecksum.SumSize())) + metric.BlockDeliverKVPairsHistogram.WithLabelValues(metric.BlockDeliverKindData).Observe(float64(dataChecksum.SumKVS())) + metric.BlockDeliverKVPairsHistogram.WithLabelValues(metric.BlockDeliverKindIndex).Observe(float64(indexChecksum.SumKVS())) + }() + + // Update the table, and save a checkpoint. + // (the write to the importer is effective immediately, thus update these here) + // No need to apply a lock since this is the only thread updating `cr.chunk.**`. + // In local mode, we should write these checkpoint after engine flushed. + cr.chunk.Checksum.Add(&dataChecksum) + cr.chunk.Checksum.Add(&indexChecksum) + cr.chunk.Chunk.Offset = offset + cr.chunk.Chunk.PrevRowIDMax = rowID + if !rc.isLocalBackend() && (dataChecksum.SumKVS() != 0 || indexChecksum.SumKVS() != 0) { + // No need to save checkpoint if nothing was delivered. + saveCheckpoint(rc, t, engineID, cr.chunk) + } + failpoint.Inject("SlowDownWriteRows", func() { + deliverLogger.Warn("Slowed down write rows") + }) + failpoint.Inject("FailAfterWriteRows", nil) + // TODO: for local backend, we may save checkpoint more frequently, e.g. after writen + // 10GB kv pairs to data engine, we can do a flush for both data & index engine, then we + // can safely update current checkpoint. + + failpoint.Inject("LocalBackendSaveCheckpoint", func() { + if !rc.isLocalBackend() && (dataChecksum.SumKVS() != 0 || indexChecksum.SumKVS() != 0) { + // No need to save checkpoint if nothing was delivered. + saveCheckpoint(rc, t, engineID, cr.chunk) + } + }) + } + + return +} + +func saveCheckpoint(rc *RestoreController, t *TableRestore, engineID int32, chunk *ChunkCheckpoint) { + // We need to update the AllocBase every time we've finished a file. + // The AllocBase is determined by the maximum of the "handle" (_tidb_rowid + // or integer primary key), which can only be obtained by reading all data. + + var base int64 + if t.tableInfo.Core.PKIsHandle && t.tableInfo.Core.ContainsAutoRandomBits() { + base = t.alloc.Get(autoid.AutoRandomType).Base() + 1 + } else { + base = t.alloc.Get(autoid.RowIDAllocType).Base() + 1 + } + rc.saveCpCh <- saveCp{ + tableName: t.tableName, + merger: &RebaseCheckpointMerger{ + AllocBase: base, + }, + } + rc.saveCpCh <- saveCp{ + tableName: t.tableName, + merger: &ChunkCheckpointMerger{ + EngineID: engineID, + Key: chunk.Key, + Checksum: chunk.Checksum, + Pos: chunk.Chunk.Offset, + RowID: chunk.Chunk.PrevRowIDMax, + ColumnPermutation: chunk.ColumnPermutation, + }, + } +} + +func (cr *chunkRestore) encodeLoop( + ctx context.Context, + kvsCh chan<- []deliveredKVs, + t *TableRestore, + logger log.Logger, + kvEncoder kv.Encoder, + deliverCompleteCh <-chan deliverResult, + rc *RestoreController, +) (readTotalDur time.Duration, encodeTotalDur time.Duration, err error) { + send := func(kvs []deliveredKVs) error { + select { + case kvsCh <- kvs: + return nil + case <-ctx.Done(): + return ctx.Err() + case deliverResult, ok := <-deliverCompleteCh: + if deliverResult.err == nil && !ok { + deliverResult.err = ctx.Err() + } + if deliverResult.err == nil { + deliverResult.err = errors.New("unexpected premature fulfillment") + logger.DPanic("unexpected: deliverCompleteCh prematurely fulfilled with no error", zap.Bool("chIsOpen", ok)) + } + return errors.Trace(deliverResult.err) + } + } + + pauser, maxKvPairsCnt := rc.pauser, rc.cfg.TikvImporter.MaxKVPairs + initializedColumns, reachEOF := false, false + for !reachEOF { + if err = pauser.Wait(ctx); err != nil { + return + } + offset, _ := cr.parser.Pos() + if offset >= cr.chunk.Chunk.EndOffset { + break + } + + var readDur, encodeDur time.Duration + canDeliver := false + kvPacket := make([]deliveredKVs, 0, maxKvPairsCnt) + var newOffset, rowID int64 + outLoop: + for !canDeliver { + readDurStart := time.Now() + err = cr.parser.ReadRow() + columnNames := cr.parser.Columns() + newOffset, rowID = cr.parser.Pos() + switch errors.Cause(err) { + case nil: + if !initializedColumns { + if len(cr.chunk.ColumnPermutation) == 0 { + if err = t.initializeColumns(columnNames, cr.chunk); err != nil { + return + } + } + initializedColumns = true + } + case io.EOF: + reachEOF = true + break outLoop + default: + err = errors.Annotatef(err, "in file %s at offset %d", &cr.chunk.Key, newOffset) + return + } + readDur += time.Since(readDurStart) + encodeDurStart := time.Now() + lastRow := cr.parser.LastRow() + // sql -> kv + kvs, encodeErr := kvEncoder.Encode(logger, lastRow.Row, lastRow.RowID, cr.chunk.ColumnPermutation) + encodeDur += time.Since(encodeDurStart) + cr.parser.RecycleRow(lastRow) + if encodeErr != nil { + err = errors.Annotatef(encodeErr, "in file %s at offset %d", &cr.chunk.Key, newOffset) + return + } + kvPacket = append(kvPacket, deliveredKVs{kvs: kvs, columns: columnNames, offset: newOffset, rowID: rowID}) + if len(kvPacket) >= maxKvPairsCnt || newOffset == cr.chunk.Chunk.EndOffset { + canDeliver = true + } + } + encodeTotalDur += encodeDur + metric.RowEncodeSecondsHistogram.Observe(encodeDur.Seconds()) + readTotalDur += readDur + metric.RowReadSecondsHistogram.Observe(readDur.Seconds()) + metric.RowReadBytesHistogram.Observe(float64(newOffset - offset)) + + if len(kvPacket) != 0 { + deliverKvStart := time.Now() + if err = send(kvPacket); err != nil { + return + } + metric.RowKVDeliverSecondsHistogram.Observe(time.Since(deliverKvStart).Seconds()) + } + } + + err = send([]deliveredKVs{}) + return +} + +func (cr *chunkRestore) restore( + ctx context.Context, + t *TableRestore, + engineID int32, + dataEngine, indexEngine *kv.LocalEngineWriter, + rc *RestoreController, +) error { + // Create the encoder. + kvEncoder, err := rc.backend.NewEncoder(t.encTable, &kv.SessionOptions{ + SQLMode: rc.cfg.TiDB.SQLMode, + Timestamp: cr.chunk.Timestamp, + SysVars: rc.sysVars, + // use chunk.PrevRowIDMax as the auto random seed, so it can stay the same value after recover from checkpoint. + AutoRandomSeed: cr.chunk.Chunk.PrevRowIDMax, + }) + if err != nil { + return err + } + + kvsCh := make(chan []deliveredKVs, maxKVQueueSize) + deliverCompleteCh := make(chan deliverResult) + + defer func() { + kvEncoder.Close() + kvEncoder = nil + close(kvsCh) + }() + + go func() { + defer close(deliverCompleteCh) + dur, err := cr.deliverLoop(ctx, kvsCh, t, engineID, dataEngine, indexEngine, rc) + select { + case <-ctx.Done(): + case deliverCompleteCh <- deliverResult{dur, err}: + } + }() + + logTask := t.logger.With( + zap.Int32("engineNumber", engineID), + zap.Int("fileIndex", cr.index), + zap.Stringer("path", &cr.chunk.Key), + ).Begin(zap.InfoLevel, "restore file") + + readTotalDur, encodeTotalDur, err := cr.encodeLoop(ctx, kvsCh, t, logTask.Logger, kvEncoder, deliverCompleteCh, rc) + if err != nil { + return err + } + + select { + case deliverResult := <-deliverCompleteCh: + logTask.End(zap.ErrorLevel, deliverResult.err, + zap.Duration("readDur", readTotalDur), + zap.Duration("encodeDur", encodeTotalDur), + zap.Duration("deliverDur", deliverResult.totalDur), + zap.Object("checksum", &cr.chunk.Checksum), + ) + return errors.Trace(deliverResult.err) + case <-ctx.Done(): + return ctx.Err() + } +} diff --git a/pkg/lightning/restore/restore_test.go b/pkg/lightning/restore/restore_test.go new file mode 100644 index 000000000..c32760827 --- /dev/null +++ b/pkg/lightning/restore/restore_test.go @@ -0,0 +1,1288 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package restore + +import ( + "context" + "fmt" + "io/ioutil" + "path/filepath" + "sort" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/golang/mock/gomock" + "github.com/google/uuid" + . "github.com/pingcap/check" + "github.com/pingcap/errors" + "github.com/pingcap/failpoint" + "github.com/pingcap/kvproto/pkg/import_kvpb" + "github.com/pingcap/parser" + "github.com/pingcap/parser/ast" + "github.com/pingcap/parser/model" + "github.com/pingcap/parser/mysql" + filter "github.com/pingcap/tidb-tools/pkg/table-filter" + "github.com/pingcap/tidb/ddl" + tmock "github.com/pingcap/tidb/util/mock" + + kv "github.com/pingcap/br/pkg/lightning/backend" + "github.com/pingcap/br/pkg/lightning/checkpoints" + . "github.com/pingcap/br/pkg/lightning/checkpoints" + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/config" + "github.com/pingcap/br/pkg/lightning/glue" + "github.com/pingcap/br/pkg/lightning/log" + "github.com/pingcap/br/pkg/lightning/mock" + "github.com/pingcap/br/pkg/lightning/mydump" + "github.com/pingcap/br/pkg/lightning/verification" + "github.com/pingcap/br/pkg/lightning/worker" + "github.com/pingcap/br/pkg/storage" +) + +var _ = Suite(&restoreSuite{}) + +type restoreSuite struct{} + +func (s *restoreSuite) TestNewTableRestore(c *C) { + testCases := []struct { + name string + createStmt string + }{ + {"t1", "CREATE TABLE `t1` (`c1` varchar(5) NOT NULL)"}, + // {"t2", "CREATE TABLE `t2` (`c1` varchar(30000) NOT NULL)"}, // no longer able to create this kind of table. + {"t3", "CREATE TABLE `t3-a` (`c1-a` varchar(5) NOT NULL)"}, + } + + p := parser.New() + se := tmock.NewContext() + + dbInfo := &TidbDBInfo{Name: "mockdb", Tables: map[string]*TidbTableInfo{}} + for i, tc := range testCases { + node, err := p.ParseOneStmt(tc.createStmt, "utf8mb4", "utf8mb4_bin") + c.Assert(err, IsNil) + tableInfo, err := ddl.MockTableInfo(se, node.(*ast.CreateTableStmt), int64(i+1)) + c.Assert(err, IsNil) + tableInfo.State = model.StatePublic + + dbInfo.Tables[tc.name] = &TidbTableInfo{ + Name: tc.name, + Core: tableInfo, + } + } + + for _, tc := range testCases { + tableInfo := dbInfo.Tables[tc.name] + tableName := common.UniqueTable("mockdb", tableInfo.Name) + tr, err := NewTableRestore(tableName, nil, dbInfo, tableInfo, &TableCheckpoint{}) + c.Assert(tr, NotNil) + c.Assert(err, IsNil) + } +} + +func (s *restoreSuite) TestNewTableRestoreFailure(c *C) { + tableInfo := &TidbTableInfo{ + Name: "failure", + Core: &model.TableInfo{}, + } + dbInfo := &TidbDBInfo{Name: "mockdb", Tables: map[string]*TidbTableInfo{ + "failure": tableInfo, + }} + tableName := common.UniqueTable("mockdb", "failure") + + _, err := NewTableRestore(tableName, nil, dbInfo, tableInfo, &TableCheckpoint{}) + c.Assert(err, ErrorMatches, `failed to tables\.TableFromMeta.*`) +} + +func (s *restoreSuite) TestErrorSummaries(c *C) { + logger, buffer := log.MakeTestLogger() + + es := makeErrorSummaries(logger) + es.record("first", errors.New("a1 error"), CheckpointStatusAnalyzed) + es.record("second", errors.New("b2 error"), CheckpointStatusAllWritten) + es.emitLog() + + lines := buffer.Lines() + sort.Strings(lines[1:]) + c.Assert(lines, DeepEquals, []string{ + `{"$lvl":"ERROR","$msg":"tables failed to be imported","count":2}`, + `{"$lvl":"ERROR","$msg":"-","table":"first","status":"analyzed","error":"a1 error"}`, + `{"$lvl":"ERROR","$msg":"-","table":"second","status":"written","error":"b2 error"}`, + }) +} + +func (s *restoreSuite) TestVerifyCheckpoint(c *C) { + dir := c.MkDir() + cpdb := checkpoints.NewFileCheckpointsDB(filepath.Join(dir, "cp.pb")) + defer cpdb.Close() + ctx := context.Background() + + actualReleaseVersion := common.ReleaseVersion + defer func() { + common.ReleaseVersion = actualReleaseVersion + }() + + taskCp, err := cpdb.TaskCheckpoint(ctx) + c.Assert(err, IsNil) + c.Assert(taskCp, IsNil) + + newCfg := func() *config.Config { + cfg := config.NewConfig() + cfg.Mydumper.SourceDir = "/data" + cfg.TaskID = 123 + cfg.TiDB.Port = 4000 + cfg.TiDB.PdAddr = "127.0.0.1:2379" + cfg.TikvImporter.Addr = "127.0.0.1:8287" + cfg.TikvImporter.SortedKVDir = "/tmp/sorted-kv" + + return cfg + } + + err = cpdb.Initialize(ctx, newCfg(), map[string]*checkpoints.TidbDBInfo{}) + c.Assert(err, IsNil) + + adjustFuncs := map[string]func(cfg *config.Config){ + "tikv-importer.backend": func(cfg *config.Config) { + cfg.TikvImporter.Backend = "local" + }, + "tikv-importer.addr": func(cfg *config.Config) { + cfg.TikvImporter.Addr = "128.0.0.1:8287" + }, + "mydumper.data-source-dir": func(cfg *config.Config) { + cfg.Mydumper.SourceDir = "/tmp/test" + }, + "tidb.host": func(cfg *config.Config) { + cfg.TiDB.Host = "192.168.0.1" + }, + "tidb.port": func(cfg *config.Config) { + cfg.TiDB.Port = 5000 + }, + "tidb.pd-addr": func(cfg *config.Config) { + cfg.TiDB.PdAddr = "127.0.0.1:3379" + }, + "version": func(cfg *config.Config) { + common.ReleaseVersion = "some newer version" + }, + } + + // default mode, will return error + taskCp, err = cpdb.TaskCheckpoint(ctx) + c.Assert(err, IsNil) + for conf, fn := range adjustFuncs { + cfg := newCfg() + fn(cfg) + err := verifyCheckpoint(cfg, taskCp) + if conf == "version" { + common.ReleaseVersion = actualReleaseVersion + c.Assert(err, ErrorMatches, "lightning version is 'some newer version', but checkpoint was created at '"+actualReleaseVersion+"'.*") + } else { + c.Assert(err, ErrorMatches, fmt.Sprintf("config '%s' value '.*' different from checkpoint value .*", conf)) + } + } + + for conf, fn := range adjustFuncs { + if conf == "tikv-importer.backend" { + continue + } + cfg := newCfg() + cfg.App.CheckRequirements = false + fn(cfg) + err := cpdb.Initialize(context.Background(), cfg, map[string]*checkpoints.TidbDBInfo{}) + c.Assert(err, IsNil) + } +} + +var _ = Suite(&tableRestoreSuite{}) + +type tableRestoreSuiteBase struct { + tr *TableRestore + cfg *config.Config + + tableInfo *TidbTableInfo + dbInfo *TidbDBInfo + tableMeta *mydump.MDTableMeta + + store storage.ExternalStorage +} + +type tableRestoreSuite struct { + tableRestoreSuiteBase +} + +func (s *tableRestoreSuiteBase) SetUpSuite(c *C) { + // Produce a mock table info + + p := parser.New() + p.SetSQLMode(mysql.ModeANSIQuotes) + se := tmock.NewContext() + node, err := p.ParseOneStmt(` + CREATE TABLE "table" ( + a INT, + b INT, + c INT, + KEY (b) + ) + `, "", "") + c.Assert(err, IsNil) + core, err := ddl.MockTableInfo(se, node.(*ast.CreateTableStmt), 0xabcdef) + c.Assert(err, IsNil) + core.State = model.StatePublic + + s.tableInfo = &TidbTableInfo{Name: "table", DB: "db", Core: core} + s.dbInfo = &TidbDBInfo{ + Name: "db", + Tables: map[string]*TidbTableInfo{"table": s.tableInfo}, + } + + // Write some sample SQL dump + + fakeDataDir := c.MkDir() + + store, err := storage.NewLocalStorage(fakeDataDir) + c.Assert(err, IsNil) + s.store = store + + fakeDataFilesCount := 6 + fakeDataFilesContent := []byte("INSERT INTO `table` VALUES (1, 2, 3);") + c.Assert(len(fakeDataFilesContent), Equals, 37) + fakeDataFiles := make([]mydump.FileInfo, 0, fakeDataFilesCount) + for i := 1; i <= fakeDataFilesCount; i++ { + fakeFileName := fmt.Sprintf("db.table.%d.sql", i) + fakeDataPath := filepath.Join(fakeDataDir, fakeFileName) + err = ioutil.WriteFile(fakeDataPath, fakeDataFilesContent, 0o644) + c.Assert(err, IsNil) + fakeDataFiles = append(fakeDataFiles, mydump.FileInfo{TableName: filter.Table{"db", "table"}, FileMeta: mydump.SourceFileMeta{Path: fakeFileName, Type: mydump.SourceTypeSQL, SortKey: fmt.Sprintf("%d", i), FileSize: 37}}) + } + + fakeCsvContent := []byte("1,2,3\r\n4,5,6\r\n") + csvName := "db.table.99.csv" + err = ioutil.WriteFile(filepath.Join(fakeDataDir, csvName), fakeCsvContent, 0o644) + c.Assert(err, IsNil) + fakeDataFiles = append(fakeDataFiles, mydump.FileInfo{TableName: filter.Table{"db", "table"}, FileMeta: mydump.SourceFileMeta{Path: csvName, Type: mydump.SourceTypeCSV, SortKey: "99", FileSize: 14}}) + + s.tableMeta = &mydump.MDTableMeta{ + DB: "db", + Name: "table", + TotalSize: 222, + SchemaFile: mydump.FileInfo{TableName: filter.Table{Schema: "db", Name: "table"}, FileMeta: mydump.SourceFileMeta{Path: "db.table-schema.sql", Type: mydump.SourceTypeTableSchema}}, + DataFiles: fakeDataFiles, + } +} + +func (s *tableRestoreSuiteBase) SetUpTest(c *C) { + // Collect into the test TableRestore structure + var err error + s.tr, err = NewTableRestore("`db`.`table`", s.tableMeta, s.dbInfo, s.tableInfo, &TableCheckpoint{}) + c.Assert(err, IsNil) + + s.cfg = config.NewConfig() + s.cfg.Mydumper.BatchSize = 111 + s.cfg.App.TableConcurrency = 2 +} + +func (s *tableRestoreSuite) TestPopulateChunks(c *C) { + failpoint.Enable("github.com/pingcap/br/pkg/lightning/restore/PopulateChunkTimestamp", "return(1234567897)") + defer failpoint.Disable("github.com/pingcap/br/pkg/lightning/restore/PopulateChunkTimestamp") + + cp := &TableCheckpoint{ + Engines: make(map[int32]*EngineCheckpoint), + } + + rc := &RestoreController{cfg: s.cfg, ioWorkers: worker.NewPool(context.Background(), 1, "io"), store: s.store} + err := s.tr.populateChunks(context.Background(), rc, cp) + c.Assert(err, IsNil) + c.Assert(cp.Engines, DeepEquals, map[int32]*EngineCheckpoint{ + -1: { + Status: CheckpointStatusLoaded, + }, + 0: { + Status: CheckpointStatusLoaded, + Chunks: []*ChunkCheckpoint{ + { + Key: ChunkCheckpointKey{Path: s.tr.tableMeta.DataFiles[0].FileMeta.Path, Offset: 0}, + FileMeta: s.tr.tableMeta.DataFiles[0].FileMeta, + Chunk: mydump.Chunk{ + Offset: 0, + EndOffset: 37, + PrevRowIDMax: 0, + RowIDMax: 7, // 37 bytes with 3 columns can store at most 7 rows. + }, + Timestamp: 1234567897, + }, + { + Key: ChunkCheckpointKey{Path: s.tr.tableMeta.DataFiles[1].FileMeta.Path, Offset: 0}, + FileMeta: s.tr.tableMeta.DataFiles[1].FileMeta, + Chunk: mydump.Chunk{ + Offset: 0, + EndOffset: 37, + PrevRowIDMax: 7, + RowIDMax: 14, + }, + Timestamp: 1234567897, + }, + { + Key: ChunkCheckpointKey{Path: s.tr.tableMeta.DataFiles[2].FileMeta.Path, Offset: 0}, + FileMeta: s.tr.tableMeta.DataFiles[2].FileMeta, + Chunk: mydump.Chunk{ + Offset: 0, + EndOffset: 37, + PrevRowIDMax: 14, + RowIDMax: 21, + }, + Timestamp: 1234567897, + }, + }, + }, + 1: { + Status: CheckpointStatusLoaded, + Chunks: []*ChunkCheckpoint{ + { + Key: ChunkCheckpointKey{Path: s.tr.tableMeta.DataFiles[3].FileMeta.Path, Offset: 0}, + FileMeta: s.tr.tableMeta.DataFiles[3].FileMeta, + Chunk: mydump.Chunk{ + Offset: 0, + EndOffset: 37, + PrevRowIDMax: 21, + RowIDMax: 28, + }, + Timestamp: 1234567897, + }, + { + Key: ChunkCheckpointKey{Path: s.tr.tableMeta.DataFiles[4].FileMeta.Path, Offset: 0}, + FileMeta: s.tr.tableMeta.DataFiles[4].FileMeta, + Chunk: mydump.Chunk{ + Offset: 0, + EndOffset: 37, + PrevRowIDMax: 28, + RowIDMax: 35, + }, + Timestamp: 1234567897, + }, + { + Key: ChunkCheckpointKey{Path: s.tr.tableMeta.DataFiles[5].FileMeta.Path, Offset: 0}, + FileMeta: s.tr.tableMeta.DataFiles[5].FileMeta, + Chunk: mydump.Chunk{ + Offset: 0, + EndOffset: 37, + PrevRowIDMax: 35, + RowIDMax: 42, + }, + Timestamp: 1234567897, + }, + }, + }, + 2: { + Status: CheckpointStatusLoaded, + Chunks: []*ChunkCheckpoint{ + { + Key: ChunkCheckpointKey{Path: s.tr.tableMeta.DataFiles[6].FileMeta.Path, Offset: 0}, + FileMeta: s.tr.tableMeta.DataFiles[6].FileMeta, + Chunk: mydump.Chunk{ + Offset: 0, + EndOffset: 14, + PrevRowIDMax: 42, + RowIDMax: 46, + }, + Timestamp: 1234567897, + }, + }, + }, + }) + + // set csv header to true, this will cause check columns fail + s.cfg.Mydumper.CSV.Header = true + s.cfg.Mydumper.StrictFormat = true + regionSize := s.cfg.Mydumper.MaxRegionSize + s.cfg.Mydumper.MaxRegionSize = 5 + err = s.tr.populateChunks(context.Background(), rc, cp) + c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, `.*unknown columns in header \[1 2 3\]`) + s.cfg.Mydumper.MaxRegionSize = regionSize + s.cfg.Mydumper.CSV.Header = false +} + +func (s *tableRestoreSuite) TestPopulateChunksCSVHeader(c *C) { + fakeDataDir := c.MkDir() + store, err := storage.NewLocalStorage(fakeDataDir) + c.Assert(err, IsNil) + + fakeDataFiles := make([]mydump.FileInfo, 0) + + fakeCsvContents := []string{ + // small full header + "a,b,c\r\n1,2,3\r\n", + // small partial header + "b,c\r\n2,3\r\n", + // big full header + "a,b,c\r\n90000,80000,700000\r\n1000,2000,3000\r\n11,22,33\r\n3,4,5\r\n", + // big full header unordered + "c,a,b\r\n,1000,2000,3000\r\n11,22,33\r\n1000,2000,404\r\n3,4,5\r\n90000,80000,700000\r\n7999999,89999999,9999999\r\n", + // big partial header + "b,c\r\n2000001,30000001\r\n35231616,462424626\r\n62432,434898934\r\n", + } + total := 0 + for i, s := range fakeCsvContents { + csvName := fmt.Sprintf("db.table.%02d.csv", i) + err := ioutil.WriteFile(filepath.Join(fakeDataDir, csvName), []byte(s), 0o644) + c.Assert(err, IsNil) + fakeDataFiles = append(fakeDataFiles, mydump.FileInfo{ + TableName: filter.Table{"db", "table"}, + FileMeta: mydump.SourceFileMeta{Path: csvName, Type: mydump.SourceTypeCSV, SortKey: fmt.Sprintf("%02d", i), FileSize: int64(len(s))}, + }) + total += len(s) + } + tableMeta := &mydump.MDTableMeta{ + DB: "db", + Name: "table", + TotalSize: int64(total), + SchemaFile: mydump.FileInfo{TableName: filter.Table{Schema: "db", Name: "table"}, FileMeta: mydump.SourceFileMeta{Path: "db.table-schema.sql", Type: mydump.SourceTypeTableSchema}}, + DataFiles: fakeDataFiles, + } + + failpoint.Enable("github.com/pingcap/br/pkg/lightning/restore/PopulateChunkTimestamp", "return(1234567897)") + defer failpoint.Disable("github.com/pingcap/br/pkg/lightning/restore/PopulateChunkTimestamp") + + cp := &TableCheckpoint{ + Engines: make(map[int32]*EngineCheckpoint), + } + + cfg := config.NewConfig() + cfg.Mydumper.BatchSize = 100 + cfg.Mydumper.MaxRegionSize = 40 + + cfg.Mydumper.CSV.Header = true + cfg.Mydumper.StrictFormat = true + rc := &RestoreController{cfg: cfg, ioWorkers: worker.NewPool(context.Background(), 1, "io"), store: store} + + tr, err := NewTableRestore("`db`.`table`", tableMeta, s.dbInfo, s.tableInfo, &TableCheckpoint{}) + c.Assert(err, IsNil) + c.Assert(tr.populateChunks(context.Background(), rc, cp), IsNil) + + c.Assert(cp.Engines, DeepEquals, map[int32]*EngineCheckpoint{ + -1: { + Status: CheckpointStatusLoaded, + }, + 0: { + Status: CheckpointStatusLoaded, + Chunks: []*ChunkCheckpoint{ + { + Key: ChunkCheckpointKey{Path: tableMeta.DataFiles[0].FileMeta.Path, Offset: 0}, + FileMeta: tableMeta.DataFiles[0].FileMeta, + Chunk: mydump.Chunk{ + Offset: 0, + EndOffset: 14, + PrevRowIDMax: 0, + RowIDMax: 4, // 37 bytes with 3 columns can store at most 7 rows. + }, + Timestamp: 1234567897, + }, + { + Key: ChunkCheckpointKey{Path: tableMeta.DataFiles[1].FileMeta.Path, Offset: 0}, + FileMeta: tableMeta.DataFiles[1].FileMeta, + Chunk: mydump.Chunk{ + Offset: 0, + EndOffset: 10, + PrevRowIDMax: 4, + RowIDMax: 7, + }, + Timestamp: 1234567897, + }, + { + Key: ChunkCheckpointKey{Path: tableMeta.DataFiles[2].FileMeta.Path, Offset: 6}, + FileMeta: tableMeta.DataFiles[2].FileMeta, + ColumnPermutation: []int{0, 1, 2, -1}, + Chunk: mydump.Chunk{ + Offset: 6, + EndOffset: 52, + PrevRowIDMax: 7, + RowIDMax: 20, + Columns: []string{"a", "b", "c"}, + }, + + Timestamp: 1234567897, + }, + { + Key: ChunkCheckpointKey{Path: tableMeta.DataFiles[2].FileMeta.Path, Offset: 52}, + FileMeta: tableMeta.DataFiles[2].FileMeta, + ColumnPermutation: []int{0, 1, 2, -1}, + Chunk: mydump.Chunk{ + Offset: 52, + EndOffset: 60, + PrevRowIDMax: 20, + RowIDMax: 22, + Columns: []string{"a", "b", "c"}, + }, + Timestamp: 1234567897, + }, + { + Key: ChunkCheckpointKey{Path: tableMeta.DataFiles[3].FileMeta.Path, Offset: 6}, + FileMeta: tableMeta.DataFiles[3].FileMeta, + ColumnPermutation: []int{1, 2, 0, -1}, + Chunk: mydump.Chunk{ + Offset: 6, + EndOffset: 48, + PrevRowIDMax: 22, + RowIDMax: 35, + Columns: []string{"c", "a", "b"}, + }, + Timestamp: 1234567897, + }, + }, + }, + 1: { + Status: CheckpointStatusLoaded, + Chunks: []*ChunkCheckpoint{ + { + Key: ChunkCheckpointKey{Path: tableMeta.DataFiles[3].FileMeta.Path, Offset: 48}, + FileMeta: tableMeta.DataFiles[3].FileMeta, + ColumnPermutation: []int{1, 2, 0, -1}, + Chunk: mydump.Chunk{ + Offset: 48, + EndOffset: 101, + PrevRowIDMax: 35, + RowIDMax: 48, + Columns: []string{"c", "a", "b"}, + }, + Timestamp: 1234567897, + }, + { + Key: ChunkCheckpointKey{Path: tableMeta.DataFiles[3].FileMeta.Path, Offset: 101}, + FileMeta: tableMeta.DataFiles[3].FileMeta, + ColumnPermutation: []int{1, 2, 0, -1}, + Chunk: mydump.Chunk{ + Offset: 101, + EndOffset: 102, + PrevRowIDMax: 48, + RowIDMax: 48, + Columns: []string{"c", "a", "b"}, + }, + Timestamp: 1234567897, + }, + { + Key: ChunkCheckpointKey{Path: tableMeta.DataFiles[4].FileMeta.Path, Offset: 4}, + FileMeta: tableMeta.DataFiles[4].FileMeta, + ColumnPermutation: []int{-1, 0, 1, -1}, + Chunk: mydump.Chunk{ + Offset: 4, + EndOffset: 59, + PrevRowIDMax: 48, + RowIDMax: 61, + Columns: []string{"b", "c"}, + }, + Timestamp: 1234567897, + }, + }, + }, + 2: { + Status: CheckpointStatusLoaded, + Chunks: []*ChunkCheckpoint{ + { + Key: ChunkCheckpointKey{Path: tableMeta.DataFiles[4].FileMeta.Path, Offset: 59}, + FileMeta: tableMeta.DataFiles[4].FileMeta, + ColumnPermutation: []int{-1, 0, 1, -1}, + Chunk: mydump.Chunk{ + Offset: 59, + EndOffset: 60, + PrevRowIDMax: 61, + RowIDMax: 61, + Columns: []string{"b", "c"}, + }, + Timestamp: 1234567897, + }, + }, + }, + }) +} + +func (s *tableRestoreSuite) TestGetColumnsNames(c *C) { + c.Assert(getColumnNames(s.tableInfo.Core, []int{0, 1, 2, -1}), DeepEquals, []string{"a", "b", "c"}) + c.Assert(getColumnNames(s.tableInfo.Core, []int{1, 0, 2, -1}), DeepEquals, []string{"b", "a", "c"}) + c.Assert(getColumnNames(s.tableInfo.Core, []int{-1, 0, 1, -1}), DeepEquals, []string{"b", "c"}) + c.Assert(getColumnNames(s.tableInfo.Core, []int{0, 1, -1, -1}), DeepEquals, []string{"a", "b"}) + c.Assert(getColumnNames(s.tableInfo.Core, []int{1, -1, 0, -1}), DeepEquals, []string{"c", "a"}) + c.Assert(getColumnNames(s.tableInfo.Core, []int{-1, 0, -1, -1}), DeepEquals, []string{"b"}) + c.Assert(getColumnNames(s.tableInfo.Core, []int{1, 2, 3, 0}), DeepEquals, []string{"_tidb_rowid", "a", "b", "c"}) + c.Assert(getColumnNames(s.tableInfo.Core, []int{1, 0, 2, 3}), DeepEquals, []string{"b", "a", "c", "_tidb_rowid"}) + c.Assert(getColumnNames(s.tableInfo.Core, []int{-1, 0, 2, 1}), DeepEquals, []string{"b", "_tidb_rowid", "c"}) + c.Assert(getColumnNames(s.tableInfo.Core, []int{2, -1, 0, 1}), DeepEquals, []string{"c", "_tidb_rowid", "a"}) + c.Assert(getColumnNames(s.tableInfo.Core, []int{-1, 1, -1, 0}), DeepEquals, []string{"_tidb_rowid", "b"}) +} + +func (s *tableRestoreSuite) TestInitializeColumns(c *C) { + ccp := &ChunkCheckpoint{} + c.Assert(s.tr.initializeColumns(nil, ccp), IsNil) + c.Assert(ccp.ColumnPermutation, DeepEquals, []int{0, 1, 2, -1}) + + ccp.ColumnPermutation = nil + c.Assert(s.tr.initializeColumns([]string{"b", "c", "a"}, ccp), IsNil) + c.Assert(ccp.ColumnPermutation, DeepEquals, []int{2, 0, 1, -1}) + + ccp.ColumnPermutation = nil + c.Assert(s.tr.initializeColumns([]string{"b"}, ccp), IsNil) + c.Assert(ccp.ColumnPermutation, DeepEquals, []int{-1, 0, -1, -1}) + + ccp.ColumnPermutation = nil + c.Assert(s.tr.initializeColumns([]string{"_tidb_rowid", "b", "a", "c"}, ccp), IsNil) + c.Assert(ccp.ColumnPermutation, DeepEquals, []int{2, 1, 3, 0}) + + ccp.ColumnPermutation = nil + err := s.tr.initializeColumns([]string{"_tidb_rowid", "b", "a", "c", "d"}, ccp) + c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, `unknown columns in header \[d\]`) + + ccp.ColumnPermutation = nil + err = s.tr.initializeColumns([]string{"e", "b", "c", "d"}, ccp) + c.Assert(err, NotNil) + c.Assert(err, ErrorMatches, `unknown columns in header \[e d\]`) +} + +func (s *tableRestoreSuite) TestCompareChecksumSuccess(c *C) { + db, mock, err := sqlmock.New() + c.Assert(err, IsNil) + + mock.ExpectQuery("SELECT.*tikv_gc_life_time.*"). + WillReturnRows(sqlmock.NewRows([]string{"VARIABLE_VALUE"}).AddRow("10m")) + mock.ExpectExec("UPDATE.*tikv_gc_life_time.*"). + WithArgs("100h0m0s"). + WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectQuery("ADMIN CHECKSUM.*"). + WillReturnRows( + sqlmock.NewRows([]string{"Db_name", "Table_name", "Checksum_crc64_xor", "Total_kvs", "Total_bytes"}). + AddRow("db", "table", 1234567890, 12345, 1234567), + ) + mock.ExpectExec("UPDATE.*tikv_gc_life_time.*"). + WithArgs("10m"). + WillReturnResult(sqlmock.NewResult(2, 1)) + mock.ExpectClose() + + ctx := MockDoChecksumCtx(db) + err = s.tr.compareChecksum(ctx, verification.MakeKVChecksum(1234567, 12345, 1234567890)) + c.Assert(err, IsNil) + + c.Assert(db.Close(), IsNil) + c.Assert(mock.ExpectationsWereMet(), IsNil) +} + +func (s *tableRestoreSuite) TestCompareChecksumFailure(c *C) { + db, mock, err := sqlmock.New() + c.Assert(err, IsNil) + + mock.ExpectQuery("SELECT.*tikv_gc_life_time.*"). + WillReturnRows(sqlmock.NewRows([]string{"VARIABLE_VALUE"}).AddRow("10m")) + mock.ExpectExec("UPDATE.*tikv_gc_life_time.*"). + WithArgs("100h0m0s"). + WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectQuery("ADMIN CHECKSUM TABLE `db`\\.`table`"). + WillReturnRows( + sqlmock.NewRows([]string{"Db_name", "Table_name", "Checksum_crc64_xor", "Total_kvs", "Total_bytes"}). + AddRow("db", "table", 1234567890, 12345, 1234567), + ) + mock.ExpectExec("UPDATE.*tikv_gc_life_time.*"). + WithArgs("10m"). + WillReturnResult(sqlmock.NewResult(2, 1)) + mock.ExpectClose() + + ctx := MockDoChecksumCtx(db) + err = s.tr.compareChecksum(ctx, verification.MakeKVChecksum(9876543, 54321, 1357924680)) + c.Assert(err, ErrorMatches, "checksum mismatched.*") + + c.Assert(db.Close(), IsNil) + c.Assert(mock.ExpectationsWereMet(), IsNil) +} + +func (s *tableRestoreSuite) TestAnalyzeTable(c *C) { + db, mock, err := sqlmock.New() + c.Assert(err, IsNil) + + mock.ExpectExec("ANALYZE TABLE `db`\\.`table`"). + WillReturnResult(sqlmock.NewResult(1, 1)) + mock.ExpectClose() + + ctx := context.Background() + defaultSQLMode, err := mysql.GetSQLMode(mysql.DefaultSQLMode) + c.Assert(err, IsNil) + g := glue.NewExternalTiDBGlue(db, defaultSQLMode) + err = s.tr.analyzeTable(ctx, g) + c.Assert(err, IsNil) + + c.Assert(db.Close(), IsNil) + c.Assert(mock.ExpectationsWereMet(), IsNil) +} + +func (s *tableRestoreSuite) TestImportKVSuccess(c *C) { + controller := gomock.NewController(c) + defer controller.Finish() + mockBackend := mock.NewMockBackend(controller) + importer := kv.MakeBackend(mockBackend) + chptCh := make(chan saveCp) + defer close(chptCh) + rc := &RestoreController{saveCpCh: chptCh} + go func() { + for range chptCh { + } + }() + + ctx := context.Background() + engineUUID := uuid.New() + + mockBackend.EXPECT(). + CloseEngine(ctx, engineUUID). + Return(nil) + mockBackend.EXPECT(). + ImportEngine(ctx, engineUUID). + Return(nil) + mockBackend.EXPECT(). + CleanupEngine(ctx, engineUUID). + Return(nil) + + closedEngine, err := importer.UnsafeCloseEngineWithUUID(ctx, "tag", engineUUID) + c.Assert(err, IsNil) + err = s.tr.importKV(ctx, closedEngine, rc, 1) + c.Assert(err, IsNil) +} + +func (s *tableRestoreSuite) TestImportKVFailure(c *C) { + controller := gomock.NewController(c) + defer controller.Finish() + mockBackend := mock.NewMockBackend(controller) + importer := kv.MakeBackend(mockBackend) + chptCh := make(chan saveCp) + defer close(chptCh) + rc := &RestoreController{saveCpCh: chptCh} + go func() { + for range chptCh { + } + }() + + ctx := context.Background() + engineUUID := uuid.New() + + mockBackend.EXPECT(). + CloseEngine(ctx, engineUUID). + Return(nil) + mockBackend.EXPECT(). + ImportEngine(ctx, engineUUID). + Return(errors.Annotate(context.Canceled, "fake import error")) + + closedEngine, err := importer.UnsafeCloseEngineWithUUID(ctx, "tag", engineUUID) + c.Assert(err, IsNil) + err = s.tr.importKV(ctx, closedEngine, rc, 1) + c.Assert(err, ErrorMatches, "fake import error.*") +} + +var _ = Suite(&chunkRestoreSuite{}) + +type chunkRestoreSuite struct { + tableRestoreSuiteBase + cr *chunkRestore +} + +func (s *chunkRestoreSuite) SetUpTest(c *C) { + s.tableRestoreSuiteBase.SetUpTest(c) + + ctx := context.Background() + w := worker.NewPool(ctx, 5, "io") + + chunk := ChunkCheckpoint{ + Key: ChunkCheckpointKey{Path: s.tr.tableMeta.DataFiles[1].FileMeta.Path, Offset: 0}, + FileMeta: s.tr.tableMeta.DataFiles[1].FileMeta, + Chunk: mydump.Chunk{ + Offset: 0, + EndOffset: 37, + PrevRowIDMax: 18, + RowIDMax: 36, + }, + } + + var err error + s.cr, err = newChunkRestore(context.Background(), 1, s.cfg, &chunk, w, s.store, nil) + c.Assert(err, IsNil) +} + +func (s *chunkRestoreSuite) TearDownTest(c *C) { + s.cr.close() +} + +func (s *chunkRestoreSuite) TestDeliverLoopCancel(c *C) { + rc := &RestoreController{backend: kv.NewMockImporter(nil, "")} + + ctx, cancel := context.WithCancel(context.Background()) + kvsCh := make(chan []deliveredKVs) + go cancel() + _, err := s.cr.deliverLoop(ctx, kvsCh, s.tr, 0, nil, nil, rc) + c.Assert(errors.Cause(err), Equals, context.Canceled) +} + +func (s *chunkRestoreSuite) TestDeliverLoopEmptyData(c *C) { + ctx := context.Background() + + // Open two mock engines. + + controller := gomock.NewController(c) + defer controller.Finish() + mockBackend := mock.NewMockBackend(controller) + importer := kv.MakeBackend(mockBackend) + + mockBackend.EXPECT().OpenEngine(ctx, gomock.Any()).Return(nil).Times(2) + mockBackend.EXPECT().MakeEmptyRows().Return(kv.MakeRowsFromKvPairs(nil)).AnyTimes() + mockWriter := mock.NewMockEngineWriter(controller) + mockBackend.EXPECT().LocalWriter(ctx, gomock.Any(), int64(2048)).Return(mockWriter, nil).AnyTimes() + mockWriter.EXPECT(). + AppendRows(ctx, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil).AnyTimes() + + dataEngine, err := importer.OpenEngine(ctx, s.tr.tableName, 0) + c.Assert(err, IsNil) + dataWriter, err := dataEngine.LocalWriter(ctx, 2048) + c.Assert(err, IsNil) + indexEngine, err := importer.OpenEngine(ctx, s.tr.tableName, -1) + c.Assert(err, IsNil) + indexWriter, err := indexEngine.LocalWriter(ctx, 2048) + c.Assert(err, IsNil) + + // Deliver nothing. + + cfg := &config.Config{} + rc := &RestoreController{cfg: cfg, backend: importer} + + kvsCh := make(chan []deliveredKVs, 1) + kvsCh <- []deliveredKVs{} + _, err = s.cr.deliverLoop(ctx, kvsCh, s.tr, 0, dataWriter, indexWriter, rc) + c.Assert(err, IsNil) +} + +func (s *chunkRestoreSuite) TestDeliverLoop(c *C) { + ctx := context.Background() + kvsCh := make(chan []deliveredKVs) + mockCols := []string{"c1", "c2"} + + // Open two mock engines. + + controller := gomock.NewController(c) + defer controller.Finish() + mockBackend := mock.NewMockBackend(controller) + importer := kv.MakeBackend(mockBackend) + + mockBackend.EXPECT().OpenEngine(ctx, gomock.Any()).Return(nil).Times(2) + mockBackend.EXPECT().MakeEmptyRows().Return(kv.MakeRowsFromKvPairs(nil)).AnyTimes() + mockWriter := mock.NewMockEngineWriter(controller) + mockBackend.EXPECT().LocalWriter(ctx, gomock.Any(), int64(2048)).Return(mockWriter, nil).AnyTimes() + + dataEngine, err := importer.OpenEngine(ctx, s.tr.tableName, 0) + c.Assert(err, IsNil) + indexEngine, err := importer.OpenEngine(ctx, s.tr.tableName, -1) + c.Assert(err, IsNil) + + dataWriter, err := dataEngine.LocalWriter(ctx, 2048) + c.Assert(err, IsNil) + indexWriter, err := indexEngine.LocalWriter(ctx, 2048) + c.Assert(err, IsNil) + + // Set up the expected API calls to the data engine... + + mockWriter.EXPECT(). + AppendRows(ctx, s.tr.tableName, mockCols, gomock.Any(), kv.MakeRowsFromKvPairs([]common.KvPair{ + { + Key: []byte("txxxxxxxx_ryyyyyyyy"), + Val: []byte("value1"), + }, + { + Key: []byte("txxxxxxxx_rwwwwwwww"), + Val: []byte("value2"), + }, + })). + Return(nil) + + // ... and the index engine. + // + // Note: This test assumes data engine is written before the index engine. + + mockWriter.EXPECT(). + AppendRows(ctx, s.tr.tableName, mockCols, gomock.Any(), kv.MakeRowsFromKvPairs([]common.KvPair{ + { + Key: []byte("txxxxxxxx_izzzzzzzz"), + Val: []byte("index1"), + }, + })). + Return(nil) + + // Now actually start the delivery loop. + + saveCpCh := make(chan saveCp, 2) + go func() { + kvsCh <- []deliveredKVs{ + { + kvs: kv.MakeRowFromKvPairs([]common.KvPair{ + { + Key: []byte("txxxxxxxx_ryyyyyyyy"), + Val: []byte("value1"), + }, + { + Key: []byte("txxxxxxxx_rwwwwwwww"), + Val: []byte("value2"), + }, + { + Key: []byte("txxxxxxxx_izzzzzzzz"), + Val: []byte("index1"), + }, + }), + columns: mockCols, + offset: 12, + rowID: 76, + }, + } + kvsCh <- []deliveredKVs{} + close(kvsCh) + }() + + cfg := &config.Config{} + rc := &RestoreController{cfg: cfg, saveCpCh: saveCpCh, backend: importer} + + _, err = s.cr.deliverLoop(ctx, kvsCh, s.tr, 0, dataWriter, indexWriter, rc) + c.Assert(err, IsNil) + c.Assert(saveCpCh, HasLen, 2) + c.Assert(s.cr.chunk.Chunk.Offset, Equals, int64(12)) + c.Assert(s.cr.chunk.Chunk.PrevRowIDMax, Equals, int64(76)) + c.Assert(s.cr.chunk.Checksum.SumKVS(), Equals, uint64(3)) +} + +func (s *chunkRestoreSuite) TestEncodeLoop(c *C) { + ctx := context.Background() + kvsCh := make(chan []deliveredKVs, 2) + deliverCompleteCh := make(chan deliverResult) + kvEncoder, err := kv.NewTableKVEncoder(s.tr.encTable, &kv.SessionOptions{ + SQLMode: s.cfg.TiDB.SQLMode, + Timestamp: 1234567895, + }) + c.Assert(err, IsNil) + cfg := config.NewConfig() + rc := &RestoreController{pauser: DeliverPauser, cfg: cfg} + _, _, err = s.cr.encodeLoop(ctx, kvsCh, s.tr, s.tr.logger, kvEncoder, deliverCompleteCh, rc) + c.Assert(err, IsNil) + c.Assert(kvsCh, HasLen, 2) + + kvs := <-kvsCh + c.Assert(kvs, HasLen, 1) + c.Assert(kvs[0].kvs, HasLen, 2) + c.Assert(kvs[0].rowID, Equals, int64(19)) + c.Assert(kvs[0].offset, Equals, int64(36)) + + kvs = <-kvsCh + c.Assert(len(kvs), Equals, 0) +} + +func (s *chunkRestoreSuite) TestEncodeLoopCanceled(c *C) { + ctx, cancel := context.WithCancel(context.Background()) + kvsCh := make(chan []deliveredKVs) + deliverCompleteCh := make(chan deliverResult) + kvEncoder, err := kv.NewTableKVEncoder(s.tr.encTable, &kv.SessionOptions{ + SQLMode: s.cfg.TiDB.SQLMode, + Timestamp: 1234567896, + }) + c.Assert(err, IsNil) + + go cancel() + cfg := config.NewConfig() + rc := &RestoreController{pauser: DeliverPauser, cfg: cfg} + _, _, err = s.cr.encodeLoop(ctx, kvsCh, s.tr, s.tr.logger, kvEncoder, deliverCompleteCh, rc) + c.Assert(errors.Cause(err), Equals, context.Canceled) + c.Assert(kvsCh, HasLen, 0) +} + +func (s *chunkRestoreSuite) TestEncodeLoopForcedError(c *C) { + ctx := context.Background() + kvsCh := make(chan []deliveredKVs, 2) + deliverCompleteCh := make(chan deliverResult) + kvEncoder, err := kv.NewTableKVEncoder(s.tr.encTable, &kv.SessionOptions{ + SQLMode: s.cfg.TiDB.SQLMode, + Timestamp: 1234567897, + }) + c.Assert(err, IsNil) + + // close the chunk so reading it will result in the "file already closed" error. + s.cr.parser.Close() + + cfg := config.NewConfig() + rc := &RestoreController{pauser: DeliverPauser, cfg: cfg} + _, _, err = s.cr.encodeLoop(ctx, kvsCh, s.tr, s.tr.logger, kvEncoder, deliverCompleteCh, rc) + c.Assert(err, ErrorMatches, `in file .*[/\\]?db\.table\.2\.sql:0 at offset 0:.*file already closed`) + c.Assert(kvsCh, HasLen, 0) +} + +func (s *chunkRestoreSuite) TestEncodeLoopDeliverErrored(c *C) { + ctx := context.Background() + kvsCh := make(chan []deliveredKVs) + deliverCompleteCh := make(chan deliverResult) + kvEncoder, err := kv.NewTableKVEncoder(s.tr.encTable, &kv.SessionOptions{ + SQLMode: s.cfg.TiDB.SQLMode, + Timestamp: 1234567898, + }) + c.Assert(err, IsNil) + + go func() { + deliverCompleteCh <- deliverResult{ + err: errors.New("fake deliver error"), + } + }() + cfg := config.NewConfig() + rc := &RestoreController{pauser: DeliverPauser, cfg: cfg} + _, _, err = s.cr.encodeLoop(ctx, kvsCh, s.tr, s.tr.logger, kvEncoder, deliverCompleteCh, rc) + c.Assert(err, ErrorMatches, "fake deliver error") + c.Assert(kvsCh, HasLen, 0) +} + +func (s *chunkRestoreSuite) TestEncodeLoopColumnsMismatch(c *C) { + dir := c.MkDir() + fileName := "db.table.000.csv" + err := ioutil.WriteFile(filepath.Join(dir, fileName), []byte("1,2,3,4\r\n4,5,6,7\r\n"), 0o644) + c.Assert(err, IsNil) + + store, err := storage.NewLocalStorage(dir) + c.Assert(err, IsNil) + + ctx := context.Background() + cfg := config.NewConfig() + rc := &RestoreController{pauser: DeliverPauser, cfg: cfg} + + reader, err := store.Open(ctx, fileName) + c.Assert(err, IsNil) + w := worker.NewPool(ctx, 5, "io") + p := mydump.NewCSVParser(&cfg.Mydumper.CSV, reader, 111, w, false) + + err = s.cr.parser.Close() + c.Assert(err, IsNil) + s.cr.parser = p + + kvsCh := make(chan []deliveredKVs, 2) + deliverCompleteCh := make(chan deliverResult) + kvEncoder, err := kv.NewTiDBBackend(nil, config.ReplaceOnDup).NewEncoder( + s.tr.encTable, + &kv.SessionOptions{ + SQLMode: s.cfg.TiDB.SQLMode, + Timestamp: 1234567895, + }) + c.Assert(err, IsNil) + + _, _, err = s.cr.encodeLoop(ctx, kvsCh, s.tr, s.tr.logger, kvEncoder, deliverCompleteCh, rc) + c.Assert(err, ErrorMatches, "in file db.table.2.sql:0 at offset 8: column count mismatch, expected 3, got 4") + c.Assert(kvsCh, HasLen, 0) +} + +func (s *chunkRestoreSuite) TestRestore(c *C) { + ctx := context.Background() + + // Open two mock engines + + controller := gomock.NewController(c) + defer controller.Finish() + mockClient := mock.NewMockImportKVClient(controller) + mockDataWriter := mock.NewMockImportKV_WriteEngineClient(controller) + mockIndexWriter := mock.NewMockImportKV_WriteEngineClient(controller) + importer := kv.NewMockImporter(mockClient, "127.0.0.1:2379") + + mockClient.EXPECT().OpenEngine(ctx, gomock.Any()).Return(nil, nil) + mockClient.EXPECT().OpenEngine(ctx, gomock.Any()).Return(nil, nil) + + dataEngine, err := importer.OpenEngine(ctx, s.tr.tableName, 0) + c.Assert(err, IsNil) + indexEngine, err := importer.OpenEngine(ctx, s.tr.tableName, -1) + c.Assert(err, IsNil) + dataWriter, err := dataEngine.LocalWriter(ctx, 2048) + c.Assert(err, IsNil) + indexWriter, err := indexEngine.LocalWriter(ctx, 2048) + c.Assert(err, IsNil) + + // Expected API sequence + // (we don't care about the actual content, this would be checked in the integrated tests) + + mockClient.EXPECT().WriteEngine(ctx).Return(mockDataWriter, nil) + mockDataWriter.EXPECT().Send(gomock.Any()).Return(nil) + mockDataWriter.EXPECT().Send(gomock.Any()).DoAndReturn(func(req *import_kvpb.WriteEngineRequest) error { + c.Assert(req.GetBatch().GetMutations(), HasLen, 1) + return nil + }) + mockDataWriter.EXPECT().CloseAndRecv().Return(nil, nil) + + mockClient.EXPECT().WriteEngine(ctx).Return(mockIndexWriter, nil) + mockIndexWriter.EXPECT().Send(gomock.Any()).Return(nil) + mockIndexWriter.EXPECT().Send(gomock.Any()).DoAndReturn(func(req *import_kvpb.WriteEngineRequest) error { + c.Assert(req.GetBatch().GetMutations(), HasLen, 1) + return nil + }) + mockIndexWriter.EXPECT().CloseAndRecv().Return(nil, nil) + + // Now actually start the restore loop. + + saveCpCh := make(chan saveCp, 2) + err = s.cr.restore(ctx, s.tr, 0, dataWriter, indexWriter, &RestoreController{ + cfg: s.cfg, + saveCpCh: saveCpCh, + backend: importer, + pauser: DeliverPauser, + }) + c.Assert(err, IsNil) + c.Assert(saveCpCh, HasLen, 2) +} + +var _ = Suite(&restoreSchemaSuite{}) + +type restoreSchemaSuite struct { + ctx context.Context + rc *RestoreController + controller *gomock.Controller +} + +func (s *restoreSchemaSuite) SetUpSuite(c *C) { + ctx := context.Background() + fakeDataDir := c.MkDir() + store, err := storage.NewLocalStorage(fakeDataDir) + c.Assert(err, IsNil) + // restore database schema file + fakeDBName := "fakedb" + // please follow the `mydump.defaultFileRouteRules`, matches files like '{schema}-schema-create.sql' + fakeFileName := fmt.Sprintf("%s-schema-create.sql", fakeDBName) + err = store.WriteFile(ctx, fakeFileName, []byte(fmt.Sprintf("CREATE DATABASE %s;", fakeDBName))) + c.Assert(err, IsNil) + // restore table schema files + fakeTableFilesCount := 8 + for i := 1; i <= fakeTableFilesCount; i++ { + fakeTableName := fmt.Sprintf("tbl%d", i) + // please follow the `mydump.defaultFileRouteRules`, matches files like '{schema}.{table}-schema.sql' + fakeFileName := fmt.Sprintf("%s.%s-schema.sql", fakeDBName, fakeTableName) + fakeFileContent := []byte(fmt.Sprintf("CREATE TABLE %s(i TINYINT);", fakeTableName)) + err = store.WriteFile(ctx, fakeFileName, fakeFileContent) + c.Assert(err, IsNil) + } + // restore view schema files + fakeViewFilesCount := 8 + for i := 1; i <= fakeViewFilesCount; i++ { + fakeViewName := fmt.Sprintf("tbl%d", i) + // please follow the `mydump.defaultFileRouteRules`, matches files like '{schema}.{table}-schema-view.sql' + fakeFileName := fmt.Sprintf("%s.%s-schema-view.sql", fakeDBName, fakeViewName) + fakeFileContent := []byte(fmt.Sprintf("CREATE ALGORITHM=UNDEFINED VIEW `%s` (`i`) AS SELECT `i` FROM `%s`.`%s`;", fakeViewName, fakeDBName, fmt.Sprintf("tbl%d", i))) + err = store.WriteFile(ctx, fakeFileName, fakeFileContent) + c.Assert(err, IsNil) + } + config := config.NewConfig() + config.Mydumper.NoSchema = false + config.Mydumper.DefaultFileRules = true + config.Mydumper.CharacterSet = "utf8mb4" + config.App.RegionConcurrency = 8 + mydumpLoader, err := mydump.NewMyDumpLoaderWithStore(ctx, config, store) + c.Assert(err, IsNil) + s.rc = &RestoreController{ + cfg: config, + store: store, + dbMetas: mydumpLoader.GetDatabases(), + checkpointsDB: &checkpoints.NullCheckpointsDB{}, + } +} + +func (s *restoreSchemaSuite) SetUpTest(c *C) { + s.controller, s.ctx = gomock.WithContext(context.Background(), c) + mockBackend := mock.NewMockBackend(s.controller) + // We don't care the execute results of those + mockBackend.EXPECT(). + FetchRemoteTableModels(gomock.Any(), gomock.Any()). + AnyTimes(). + Return(make([]*model.TableInfo, 0), nil) + s.rc.backend = kv.MakeBackend(mockBackend) + mockSQLExecutor := mock.NewMockSQLExecutor(s.controller) + mockSQLExecutor.EXPECT(). + ExecuteWithLog(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + AnyTimes(). + Return(nil) + mockSession := mock.NewMockSession(s.controller) + mockSession.EXPECT(). + Close(). + AnyTimes(). + Return() + mockSession.EXPECT(). + Execute(gomock.Any(), gomock.Any()). + AnyTimes(). + Return(nil, nil) + mockTiDBGlue := mock.NewMockGlue(s.controller) + mockTiDBGlue.EXPECT(). + GetSQLExecutor(). + AnyTimes(). + Return(mockSQLExecutor) + mockTiDBGlue.EXPECT(). + GetSession(gomock.Any()). + AnyTimes(). + Return(mockSession, nil) + mockTiDBGlue.EXPECT(). + OwnsSQLExecutor(). + AnyTimes(). + Return(true) + parser := parser.New() + mockTiDBGlue.EXPECT(). + GetParser(). + AnyTimes(). + Return(parser) + s.rc.tidbGlue = mockTiDBGlue +} + +func (s *restoreSchemaSuite) TearDownTest(c *C) { + s.rc.Close() + s.controller.Finish() +} + +func (s *restoreSchemaSuite) TestRestoreSchemaSuccessful(c *C) { + err := s.rc.restoreSchema(s.ctx) + c.Assert(err, IsNil) +} + +func (s *restoreSchemaSuite) TestRestoreSchemaFailed(c *C) { + injectErr := errors.New("Somthing wrong") + mockSession := mock.NewMockSession(s.controller) + mockSession.EXPECT(). + Close(). + AnyTimes(). + Return() + mockSession.EXPECT(). + Execute(gomock.Any(), gomock.Any()). + AnyTimes(). + Return(nil, injectErr) + mockTiDBGlue := mock.NewMockGlue(s.controller) + mockTiDBGlue.EXPECT(). + GetSession(gomock.Any()). + AnyTimes(). + Return(mockSession, nil) + s.rc.tidbGlue = mockTiDBGlue + err := s.rc.restoreSchema(s.ctx) + c.Assert(err, NotNil) + c.Assert(errors.ErrorEqual(err, injectErr), IsTrue) +} + +func (s *restoreSchemaSuite) TestRestoreSchemaContextCancel(c *C) { + childCtx, cancel := context.WithCancel(s.ctx) + mockSession := mock.NewMockSession(s.controller) + mockSession.EXPECT(). + Close(). + AnyTimes(). + Return() + mockSession.EXPECT(). + Execute(gomock.Any(), gomock.Any()). + AnyTimes(). + Do(func(context.Context, string) { cancel() }). + Return(nil, nil) + mockTiDBGlue := mock.NewMockGlue(s.controller) + mockTiDBGlue.EXPECT(). + GetSession(gomock.Any()). + AnyTimes(). + Return(mockSession, nil) + s.rc.tidbGlue = mockTiDBGlue + err := s.rc.restoreSchema(childCtx) + cancel() + c.Assert(err, NotNil) + c.Assert(err, Equals, childCtx.Err()) +} diff --git a/pkg/lightning/restore/tidb.go b/pkg/lightning/restore/tidb.go new file mode 100644 index 000000000..a672e7742 --- /dev/null +++ b/pkg/lightning/restore/tidb.go @@ -0,0 +1,380 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package restore + +import ( + "context" + "database/sql" + "fmt" + "strconv" + "strings" + + tmysql "github.com/go-sql-driver/mysql" + "github.com/pingcap/errors" + "github.com/pingcap/parser" + "github.com/pingcap/parser/ast" + "github.com/pingcap/parser/format" + "github.com/pingcap/parser/model" + "github.com/pingcap/parser/mysql" + "github.com/pingcap/parser/terror" + + "github.com/pingcap/br/pkg/lightning/glue" + + . "github.com/pingcap/br/pkg/lightning/checkpoints" + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/config" + "github.com/pingcap/br/pkg/lightning/log" + "github.com/pingcap/br/pkg/lightning/metric" + "github.com/pingcap/br/pkg/lightning/mydump" + + "go.uber.org/zap" +) + +// defaultImportantVariables is used in ObtainImportantVariables to retrieve the system +// variables from downstream which may affect KV encode result. The values record the default +// values if missing. +var defaultImportantVariables = map[string]string{ + "tidb_row_format_version": "1", + "max_allowed_packet": "67108864", + "div_precision_increment": "4", + "time_zone": "SYSTEM", + "lc_time_names": "en_US", + "default_week_format": "0", + "block_encryption_mode": "aes-128-ecb", + "group_concat_max_len": "1024", +} + +type TiDBManager struct { + db *sql.DB + parser *parser.Parser +} + +// getSQLErrCode returns error code if err is a mysql error +func getSQLErrCode(err error) (terror.ErrCode, bool) { + mysqlErr, ok := errors.Cause(err).(*tmysql.MySQLError) + if !ok { + return -1, false + } + + return terror.ErrCode(mysqlErr.Number), true +} + +func isUnknownSystemVariableErr(err error) bool { + code, ok := getSQLErrCode(err) + if !ok { + return strings.Contains(err.Error(), "Unknown system variable") + } + return code == mysql.ErrUnknownSystemVariable +} + +func DBFromConfig(dsn config.DBStore) (*sql.DB, error) { + param := common.MySQLConnectParam{ + Host: dsn.Host, + Port: dsn.Port, + User: dsn.User, + Password: dsn.Psw, + SQLMode: dsn.StrSQLMode, + MaxAllowedPacket: dsn.MaxAllowedPacket, + TLS: dsn.TLS, + Vars: map[string]string{ + "tidb_build_stats_concurrency": strconv.Itoa(dsn.BuildStatsConcurrency), + "tidb_distsql_scan_concurrency": strconv.Itoa(dsn.DistSQLScanConcurrency), + "tidb_index_serial_scan_concurrency": strconv.Itoa(dsn.IndexSerialScanConcurrency), + "tidb_checksum_table_concurrency": strconv.Itoa(dsn.ChecksumTableConcurrency), + + // after https://github.com/pingcap/tidb/pull/17102 merge, + // we need set session to true for insert auto_random value in TiDB Backend + "allow_auto_random_explicit_insert": "1", + // allow use _tidb_rowid in sql statement + "tidb_opt_write_row_id": "1", + }, + } + db, err := param.Connect() + if err != nil { + if isUnknownSystemVariableErr(err) { + // not support allow_auto_random_explicit_insert, retry connect + delete(param.Vars, "allow_auto_random_explicit_insert") + db, err = param.Connect() + if err != nil { + return nil, errors.Trace(err) + } + } else { + return nil, errors.Trace(err) + } + } + return db, nil +} + +func NewTiDBManager(dsn config.DBStore, tls *common.TLS) (*TiDBManager, error) { + db, err := DBFromConfig(dsn) + if err != nil { + return nil, errors.Trace(err) + } + + return NewTiDBManagerWithDB(db, dsn.SQLMode), nil +} + +// NewTiDBManagerWithDB creates a new TiDB manager with an existing database +// connection. +func NewTiDBManagerWithDB(db *sql.DB, sqlMode mysql.SQLMode) *TiDBManager { + parser := parser.New() + parser.SetSQLMode(sqlMode) + + return &TiDBManager{ + db: db, + parser: parser, + } +} + +func (timgr *TiDBManager) Close() { + timgr.db.Close() +} + +func InitSchema(ctx context.Context, g glue.Glue, database string, tablesSchema map[string]string) error { + logger := log.With(zap.String("db", database)) + sqlExecutor := g.GetSQLExecutor() + + var createDatabase strings.Builder + createDatabase.WriteString("CREATE DATABASE IF NOT EXISTS ") + common.WriteMySQLIdentifier(&createDatabase, database) + err := sqlExecutor.ExecuteWithLog(ctx, createDatabase.String(), "create database", logger) + if err != nil { + return errors.Trace(err) + } + + task := logger.Begin(zap.InfoLevel, "create tables") + var sqlCreateStmts []string +loopCreate: + for tbl, sqlCreateTable := range tablesSchema { + task.Debug("create table", zap.String("schema", sqlCreateTable)) + + sqlCreateStmts, err = createTableIfNotExistsStmt(g.GetParser(), sqlCreateTable, database, tbl) + if err != nil { + break + } + + // TODO: maybe we should put these createStems into a transaction + for _, s := range sqlCreateStmts { + err = sqlExecutor.ExecuteWithLog( + ctx, + s, + "create table", + logger.With(zap.String("table", common.UniqueTable(database, tbl))), + ) + if err != nil { + break loopCreate + } + } + } + task.End(zap.ErrorLevel, err) + + return errors.Trace(err) +} + +func createDatabaseIfNotExistStmt(dbName string) string { + var createDatabase strings.Builder + createDatabase.WriteString("CREATE DATABASE IF NOT EXISTS ") + common.WriteMySQLIdentifier(&createDatabase, dbName) + return createDatabase.String() +} + +func createTableIfNotExistsStmt(p *parser.Parser, createTable, dbName, tblName string) ([]string, error) { + stmts, _, err := p.Parse(createTable, "", "") + if err != nil { + return []string{}, err + } + + var res strings.Builder + ctx := format.NewRestoreCtx(format.DefaultRestoreFlags, &res) + + retStmts := make([]string, 0, len(stmts)) + for _, stmt := range stmts { + switch node := stmt.(type) { + case *ast.CreateTableStmt: + node.Table.Schema = model.NewCIStr(dbName) + node.Table.Name = model.NewCIStr(tblName) + node.IfNotExists = true + case *ast.CreateViewStmt: + node.ViewName.Schema = model.NewCIStr(dbName) + node.ViewName.Name = model.NewCIStr(tblName) + case *ast.DropTableStmt: + node.Tables[0].Schema = model.NewCIStr(dbName) + node.Tables[0].Name = model.NewCIStr(tblName) + node.IfExists = true + } + if err := stmt.Restore(ctx); err != nil { + return []string{}, err + } + ctx.WritePlain(";") + retStmts = append(retStmts, res.String()) + res.Reset() + } + + return retStmts, nil +} + +func (timgr *TiDBManager) DropTable(ctx context.Context, tableName string) error { + sql := common.SQLWithRetry{ + DB: timgr.db, + Logger: log.With(zap.String("table", tableName)), + } + return sql.Exec(ctx, "drop table", "DROP TABLE "+tableName) +} + +func LoadSchemaInfo( + ctx context.Context, + schemas []*mydump.MDDatabaseMeta, + getTables func(context.Context, string) ([]*model.TableInfo, error), +) (map[string]*TidbDBInfo, error) { + result := make(map[string]*TidbDBInfo, len(schemas)) + for _, schema := range schemas { + tables, err := getTables(ctx, schema.Name) + if err != nil { + return nil, err + } + + dbInfo := &TidbDBInfo{ + Name: schema.Name, + Tables: make(map[string]*TidbTableInfo), + } + + for _, tbl := range tables { + tableName := tbl.Name.String() + if tbl.State != model.StatePublic { + err := errors.Errorf("table [%s.%s] state is not public", schema.Name, tableName) + metric.RecordTableCount(metric.TableStatePending, err) + return nil, err + } + metric.RecordTableCount(metric.TableStatePending, err) + if err != nil { + return nil, errors.Trace(err) + } + tableInfo := &TidbTableInfo{ + ID: tbl.ID, + DB: schema.Name, + Name: tableName, + Core: tbl, + } + dbInfo.Tables[tableName] = tableInfo + } + + result[schema.Name] = dbInfo + } + return result, nil +} + +func ObtainGCLifeTime(ctx context.Context, db *sql.DB) (string, error) { + var gcLifeTime string + err := common.SQLWithRetry{DB: db, Logger: log.L()}.QueryRow( + ctx, + "obtain GC lifetime", + "SELECT VARIABLE_VALUE FROM mysql.tidb WHERE VARIABLE_NAME = 'tikv_gc_life_time'", + &gcLifeTime, + ) + return gcLifeTime, err +} + +func UpdateGCLifeTime(ctx context.Context, db *sql.DB, gcLifeTime string) error { + sql := common.SQLWithRetry{ + DB: db, + Logger: log.With(zap.String("gcLifeTime", gcLifeTime)), + } + return sql.Exec(ctx, "update GC lifetime", + "UPDATE mysql.tidb SET VARIABLE_VALUE = ? WHERE VARIABLE_NAME = 'tikv_gc_life_time'", + gcLifeTime, + ) +} + +func ObtainImportantVariables(ctx context.Context, g glue.SQLExecutor) map[string]string { + var query strings.Builder + query.WriteString("SHOW VARIABLES WHERE Variable_name IN ('") + first := true + for k := range defaultImportantVariables { + if first { + first = false + } else { + query.WriteString("','") + } + query.WriteString(k) + } + query.WriteString("')") + kvs, err := g.QueryStringsWithLog(ctx, query.String(), "obtain system variables", log.L()) + if err != nil { + // error is not fatal + log.L().Warn("obtain system variables failed, use default variables instead", log.ShortError(err)) + } + + // convert result into a map. fill in any missing variables with default values. + result := make(map[string]string, len(defaultImportantVariables)) + for _, kv := range kvs { + result[kv[0]] = kv[1] + } + for k, defV := range defaultImportantVariables { + if _, ok := result[k]; !ok { + result[k] = defV + } + } + + return result +} + +func ObtainNewCollationEnabled(ctx context.Context, g glue.SQLExecutor) bool { + newCollationEnabled := false + newCollationVal, err := g.ObtainStringWithLog( + ctx, + "SELECT variable_value FROM mysql.tidb WHERE variable_name = 'new_collation_enabled'", + "obtain new collation enabled", + log.L(), + ) + if err == nil && newCollationVal == "True" { + newCollationEnabled = true + } + + return newCollationEnabled +} + +// AlterAutoIncrement rebase the table auto increment id +// +// NOTE: since tidb can make sure the auto id is always be rebase even if the `incr` value is smaller +// the the auto incremanet base in tidb side, we needn't fetch currently auto increment value here. +// See: https://github.com/pingcap/tidb/blob/64698ef9a3358bfd0fdc323996bb7928a56cadca/ddl/ddl_api.go#L2528-L2533 +func AlterAutoIncrement(ctx context.Context, g glue.SQLExecutor, tableName string, incr int64) error { + logger := log.With(zap.String("table", tableName), zap.Int64("auto_increment", incr)) + query := fmt.Sprintf("ALTER TABLE %s AUTO_INCREMENT=%d", tableName, incr) + task := logger.Begin(zap.InfoLevel, "alter table auto_increment") + err := g.ExecuteWithLog(ctx, query, "alter table auto_increment", logger) + task.End(zap.ErrorLevel, err) + if err != nil { + task.Error( + "alter table auto_increment failed, please perform the query manually (this is needed no matter the table has an auto-increment column or not)", + zap.String("query", query), + ) + } + return errors.Annotatef(err, "%s", query) +} + +func AlterAutoRandom(ctx context.Context, g glue.SQLExecutor, tableName string, randomBase int64) error { + logger := log.With(zap.String("table", tableName), zap.Int64("auto_random", randomBase)) + query := fmt.Sprintf("ALTER TABLE %s AUTO_RANDOM_BASE=%d", tableName, randomBase) + task := logger.Begin(zap.InfoLevel, "alter table auto_random") + err := g.ExecuteWithLog(ctx, query, "alter table auto_random_base", logger) + task.End(zap.ErrorLevel, err) + if err != nil { + task.Error( + "alter table auto_random_base failed, please perform the query manually (this is needed no matter the table has an auto-random column or not)", + zap.String("query", query), + ) + } + return errors.Annotatef(err, "%s", query) +} diff --git a/pkg/lightning/restore/tidb_test.go b/pkg/lightning/restore/tidb_test.go new file mode 100644 index 000000000..fab65d679 --- /dev/null +++ b/pkg/lightning/restore/tidb_test.go @@ -0,0 +1,476 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package restore + +import ( + "context" + "net/http" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/go-sql-driver/mysql" + . "github.com/pingcap/check" + "github.com/pingcap/errors" + "github.com/pingcap/parser/ast" + "github.com/pingcap/parser/model" + tmysql "github.com/pingcap/parser/mysql" + "github.com/pingcap/tidb/ddl" + "github.com/pingcap/tidb/util/mock" + + "github.com/pingcap/br/pkg/lightning/glue" + + "github.com/pingcap/br/pkg/lightning/checkpoints" + "github.com/pingcap/br/pkg/lightning/mydump" +) + +var _ = Suite(&tidbSuite{}) + +type tidbSuite struct { + mockDB sqlmock.Sqlmock + handler http.Handler + timgr *TiDBManager + tiGlue glue.Glue +} + +func TestTiDB(t *testing.T) { + TestingT(t) +} + +func (s *tidbSuite) SetUpTest(c *C) { + db, mock, err := sqlmock.New() + c.Assert(err, IsNil) + + s.mockDB = mock + defaultSQLMode, err := tmysql.GetSQLMode(tmysql.DefaultSQLMode) + c.Assert(err, IsNil) + + s.timgr = NewTiDBManagerWithDB(db, defaultSQLMode) + s.tiGlue = glue.NewExternalTiDBGlue(db, defaultSQLMode) +} + +func (s *tidbSuite) TearDownTest(c *C) { + s.timgr.Close() + c.Assert(s.mockDB.ExpectationsWereMet(), IsNil) +} + +func (s *tidbSuite) TestCreateTableIfNotExistsStmt(c *C) { + dbName := "testdb" + createTableIfNotExistsStmt := func(createTable, tableName string) []string { + res, err := createTableIfNotExistsStmt(s.tiGlue.GetParser(), createTable, dbName, tableName) + c.Assert(err, IsNil) + return res + } + + c.Assert( + createTableIfNotExistsStmt("CREATE TABLE `foo`(`bar` TINYINT(1));", "foo"), + DeepEquals, + []string{"CREATE TABLE IF NOT EXISTS `testdb`.`foo` (`bar` TINYINT(1));"}, + ) + + c.Assert( + createTableIfNotExistsStmt("CREATE TABLE IF NOT EXISTS `foo`(`bar` TINYINT(1));", "foo"), + DeepEquals, + []string{"CREATE TABLE IF NOT EXISTS `testdb`.`foo` (`bar` TINYINT(1));"}, + ) + + // case insensitive + c.Assert( + createTableIfNotExistsStmt("/* cOmmEnt */ creAte tablE `fOo`(`bar` TinyinT(1));", "fOo"), + DeepEquals, + []string{"CREATE TABLE IF NOT EXISTS `testdb`.`fOo` (`bar` TINYINT(1));"}, + ) + + c.Assert( + createTableIfNotExistsStmt("/* coMMenT */ crEatE tAble If not EXISts `FoO`(`bAR` tiNyInT(1));", "FoO"), + DeepEquals, + []string{"CREATE TABLE IF NOT EXISTS `testdb`.`FoO` (`bAR` TINYINT(1));"}, + ) + + // only one "CREATE TABLE" is replaced + c.Assert( + createTableIfNotExistsStmt("CREATE TABLE `foo`(`bar` INT(1) COMMENT 'CREATE TABLE');", "foo"), + DeepEquals, + []string{"CREATE TABLE IF NOT EXISTS `testdb`.`foo` (`bar` INT(1) COMMENT 'CREATE TABLE');"}, + ) + + // upper case becomes shorter + c.Assert( + createTableIfNotExistsStmt("CREATE TABLE `Å¿`(`ı` TINYINT(1));", "Å¿"), + DeepEquals, + []string{"CREATE TABLE IF NOT EXISTS `testdb`.`Å¿` (`ı` TINYINT(1));"}, + ) + + // upper case becomes longer + c.Assert( + createTableIfNotExistsStmt("CREATE TABLE `É‘`(`È¿` TINYINT(1));", "É‘"), + DeepEquals, + []string{"CREATE TABLE IF NOT EXISTS `testdb`.`É‘` (`È¿` TINYINT(1));"}, + ) + + // non-utf-8 + c.Assert( + createTableIfNotExistsStmt("CREATE TABLE `\xcc\xcc\xcc`(`\xdd\xdd\xdd` TINYINT(1));", "\xcc\xcc\xcc"), + DeepEquals, + []string{"CREATE TABLE IF NOT EXISTS `testdb`.`\xcc\xcc\xcc` (`ÃÃÃ` TINYINT(1));"}, + ) + + // renaming a table + c.Assert( + createTableIfNotExistsStmt("create table foo(x int);", "ba`r"), + DeepEquals, + []string{"CREATE TABLE IF NOT EXISTS `testdb`.`ba``r` (`x` INT);"}, + ) + + // conditional comments + c.Assert( + createTableIfNotExistsStmt(` + /*!40101 SET NAMES binary*/; + /*!40014 SET FOREIGN_KEY_CHECKS=0*/; + CREATE TABLE x.y (z double) ENGINE=InnoDB AUTO_INCREMENT=8343230 DEFAULT CHARSET=utf8; + `, "m"), + DeepEquals, + []string{ + "SET NAMES 'binary';", + "SET @@SESSION.`FOREIGN_KEY_CHECKS`=0;", + "CREATE TABLE IF NOT EXISTS `testdb`.`m` (`z` DOUBLE) ENGINE = InnoDB AUTO_INCREMENT = 8343230 DEFAULT CHARACTER SET = UTF8;", + }, + ) + + // create view + c.Assert( + createTableIfNotExistsStmt(` + /*!40101 SET NAMES binary*/; + DROP TABLE IF EXISTS v2; + DROP VIEW IF EXISTS v2; + SET @PREV_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT; + SET @PREV_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS; + SET @PREV_COLLATION_CONNECTION=@@COLLATION_CONNECTION; + SET character_set_client = utf8; + SET character_set_results = utf8; + SET collation_connection = utf8_general_ci; + CREATE ALGORITHM=UNDEFINED DEFINER=root@192.168.198.178 SQL SECURITY DEFINER VIEW v2 (s) AS SELECT s FROM db1.v1 WHERE i<2; + SET character_set_client = @PREV_CHARACTER_SET_CLIENT; + SET character_set_results = @PREV_CHARACTER_SET_RESULTS; + SET collation_connection = @PREV_COLLATION_CONNECTION; + `, "m"), + DeepEquals, + []string{ + "SET NAMES 'binary';", + "DROP TABLE IF EXISTS `testdb`.`m`;", + "DROP VIEW IF EXISTS `testdb`.`m`;", + "SET @`PREV_CHARACTER_SET_CLIENT`=@@`character_set_client`;", + "SET @`PREV_CHARACTER_SET_RESULTS`=@@`character_set_results`;", + "SET @`PREV_COLLATION_CONNECTION`=@@`collation_connection`;", + "SET @@SESSION.`character_set_client`=`utf8`;", + "SET @@SESSION.`character_set_results`=`utf8`;", + "SET @@SESSION.`collation_connection`=`utf8_general_ci`;", + "CREATE ALGORITHM = UNDEFINED DEFINER = `root`@`192.168.198.178` SQL SECURITY DEFINER VIEW `testdb`.`m` (`s`) AS SELECT `s` FROM `db1`.`v1` WHERE `i`<2;", + "SET @@SESSION.`character_set_client`=@`PREV_CHARACTER_SET_CLIENT`;", + "SET @@SESSION.`character_set_results`=@`PREV_CHARACTER_SET_RESULTS`;", + "SET @@SESSION.`collation_connection`=@`PREV_COLLATION_CONNECTION`;", + }, + ) +} + +func (s *tidbSuite) TestInitSchema(c *C) { + ctx := context.Background() + + s.mockDB. + ExpectExec("CREATE DATABASE IF NOT EXISTS `db`"). + WillReturnResult(sqlmock.NewResult(1, 1)) + s.mockDB. + ExpectExec("\\QCREATE TABLE IF NOT EXISTS `db`.`t1` (`a` INT PRIMARY KEY,`b` VARCHAR(200));\\E"). + WillReturnResult(sqlmock.NewResult(2, 1)) + s.mockDB. + ExpectExec("\\QSET @@SESSION.`FOREIGN_KEY_CHECKS`=0;\\E"). + WillReturnResult(sqlmock.NewResult(0, 0)) + s.mockDB. + ExpectExec("\\QCREATE TABLE IF NOT EXISTS `db`.`t2` (`xx` TEXT) AUTO_INCREMENT = 11203;\\E"). + WillReturnResult(sqlmock.NewResult(2, 1)) + s.mockDB. + ExpectClose() + + s.mockDB.MatchExpectationsInOrder(false) // maps are unordered. + err := InitSchema(ctx, s.tiGlue, "db", map[string]string{ + "t1": "create table t1 (a int primary key, b varchar(200));", + "t2": "/*!40014 SET FOREIGN_KEY_CHECKS=0*/;CREATE TABLE `db`.`t2` (xx TEXT) AUTO_INCREMENT=11203;", + }) + s.mockDB.MatchExpectationsInOrder(true) + c.Assert(err, IsNil) +} + +func (s *tidbSuite) TestInitSchemaSyntaxError(c *C) { + ctx := context.Background() + + s.mockDB. + ExpectExec("CREATE DATABASE IF NOT EXISTS `db`"). + WillReturnResult(sqlmock.NewResult(1, 1)) + s.mockDB. + ExpectClose() + + err := InitSchema(ctx, s.tiGlue, "db", map[string]string{ + "t1": "create table `t1` with invalid syntax;", + }) + c.Assert(err, NotNil) +} + +func (s *tidbSuite) TestInitSchemaErrorLost(c *C) { + ctx := context.Background() + + s.mockDB. + ExpectExec("CREATE DATABASE IF NOT EXISTS `db`"). + WillReturnResult(sqlmock.NewResult(1, 1)) + + s.mockDB. + ExpectExec("CREATE TABLE IF NOT EXISTS.*"). + WillReturnError(&mysql.MySQLError{ + Number: tmysql.ErrTooBigFieldlength, + Message: "Column length too big", + }) + + s.mockDB. + ExpectClose() + + err := InitSchema(ctx, s.tiGlue, "db", map[string]string{ + "t1": "create table `t1` (a int);", + "t2": "create table t2 (a int primary key, b varchar(200));", + }) + c.Assert(err, ErrorMatches, ".*Column length too big.*") +} + +func (s *tidbSuite) TestInitSchemaUnsupportedSchemaError(c *C) { + ctx := context.Background() + + s.mockDB. + ExpectExec("CREATE DATABASE IF NOT EXISTS `db`"). + WillReturnResult(sqlmock.NewResult(1, 1)) + s.mockDB. + ExpectExec("CREATE TABLE IF NOT EXISTS `db`.`t1`.*"). + WillReturnError(&mysql.MySQLError{ + Number: tmysql.ErrTooBigFieldlength, + Message: "Column length too big", + }) + s.mockDB. + ExpectClose() + + err := InitSchema(ctx, s.tiGlue, "db", map[string]string{ + "t1": "create table `t1` (a VARCHAR(999999999));", + }) + c.Assert(err, ErrorMatches, ".*Column length too big.*") +} + +func (s *tidbSuite) TestDropTable(c *C) { + ctx := context.Background() + + s.mockDB. + ExpectExec("DROP TABLE `db`.`table`"). + WillReturnResult(sqlmock.NewResult(1, 1)) + s.mockDB. + ExpectClose() + + err := s.timgr.DropTable(ctx, "`db`.`table`") + c.Assert(err, IsNil) +} + +func (s *tidbSuite) TestLoadSchemaInfo(c *C) { + ctx := context.Background() + + // Prepare the mock reply. + nodes, _, err := s.timgr.parser.Parse( + "CREATE TABLE `t1` (`a` INT PRIMARY KEY);"+ + "CREATE TABLE `t2` (`b` VARCHAR(20), `c` BOOL, KEY (`b`, `c`))", + "", "") + c.Assert(err, IsNil) + tableInfos := make([]*model.TableInfo, 0, len(nodes)) + sctx := mock.NewContext() + for i, node := range nodes { + c.Assert(node, FitsTypeOf, &ast.CreateTableStmt{}) + info, err := ddl.MockTableInfo(sctx, node.(*ast.CreateTableStmt), int64(i+100)) + c.Assert(err, IsNil) + info.State = model.StatePublic + tableInfos = append(tableInfos, info) + } + + loaded, err := LoadSchemaInfo(ctx, []*mydump.MDDatabaseMeta{{Name: "db"}}, func(ctx context.Context, schema string) ([]*model.TableInfo, error) { + c.Assert(schema, Equals, "db") + return tableInfos, nil + }) + c.Assert(err, IsNil) + c.Assert(loaded, DeepEquals, map[string]*checkpoints.TidbDBInfo{ + "db": { + Name: "db", + Tables: map[string]*checkpoints.TidbTableInfo{ + "t1": { + ID: 100, + DB: "db", + Name: "t1", + Core: tableInfos[0], + }, + "t2": { + ID: 101, + DB: "db", + Name: "t2", + Core: tableInfos[1], + }, + }, + }, + }) +} + +func (s *tidbSuite) TestLoadSchemaInfoMissing(c *C) { + ctx := context.Background() + + _, err := LoadSchemaInfo(ctx, []*mydump.MDDatabaseMeta{{Name: "asdjalsjdlas"}}, func(ctx context.Context, schema string) ([]*model.TableInfo, error) { + return nil, errors.Errorf("[schema:1049]Unknown database '%s'", schema) + }) + c.Assert(err, ErrorMatches, ".*Unknown database.*") +} + +func (s *tidbSuite) TestGetGCLifetime(c *C) { + ctx := context.Background() + + s.mockDB. + ExpectQuery("\\QSELECT VARIABLE_VALUE FROM mysql.tidb WHERE VARIABLE_NAME = 'tikv_gc_life_time'\\E"). + WillReturnRows(sqlmock.NewRows([]string{"VARIABLE_VALUE"}).AddRow("10m")) + s.mockDB. + ExpectClose() + + res, err := ObtainGCLifeTime(ctx, s.timgr.db) + c.Assert(err, IsNil) + c.Assert(res, Equals, "10m") +} + +func (s *tidbSuite) TestSetGCLifetime(c *C) { + ctx := context.Background() + + s.mockDB. + ExpectExec("\\QUPDATE mysql.tidb SET VARIABLE_VALUE = ? WHERE VARIABLE_NAME = 'tikv_gc_life_time'\\E"). + WithArgs("12m"). + WillReturnResult(sqlmock.NewResult(1, 1)) + s.mockDB. + ExpectClose() + + err := UpdateGCLifeTime(ctx, s.timgr.db, "12m") + c.Assert(err, IsNil) +} + +func (s *tidbSuite) TestAlterAutoInc(c *C) { + ctx := context.Background() + + s.mockDB. + ExpectExec("\\QALTER TABLE `db`.`table` AUTO_INCREMENT=12345\\E"). + WillReturnResult(sqlmock.NewResult(1, 1)) + s.mockDB. + ExpectClose() + + err := AlterAutoIncrement(ctx, s.tiGlue.GetSQLExecutor(), "`db`.`table`", 12345) + c.Assert(err, IsNil) +} + +func (s *tidbSuite) TestAlterAutoRandom(c *C) { + ctx := context.Background() + + s.mockDB. + ExpectExec("\\QALTER TABLE `db`.`table` AUTO_RANDOM_BASE=12345\\E"). + WillReturnResult(sqlmock.NewResult(1, 1)) + s.mockDB. + ExpectClose() + + err := AlterAutoRandom(ctx, s.tiGlue.GetSQLExecutor(), "`db`.`table`", 12345) + c.Assert(err, IsNil) +} + +func (s *tidbSuite) TestObtainRowFormatVersionSucceed(c *C) { + ctx := context.Background() + + s.mockDB. + ExpectBegin() + s.mockDB. + ExpectQuery(`SHOW VARIABLES WHERE Variable_name IN \(.*'tidb_row_format_version'.*\)`). + WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}). + AddRow("tidb_row_format_version", "2"). + AddRow("max_allowed_packet", "1073741824"). + AddRow("div_precision_increment", "10"). + AddRow("time_zone", "-08:00"). + AddRow("lc_time_names", "ja_JP"). + AddRow("default_week_format", "1"). + AddRow("block_encryption_mode", "aes-256-cbc"). + AddRow("group_concat_max_len", "1073741824")) + s.mockDB. + ExpectCommit() + s.mockDB. + ExpectClose() + + sysVars := ObtainImportantVariables(ctx, s.tiGlue.GetSQLExecutor()) + c.Assert(sysVars, DeepEquals, map[string]string{ + "tidb_row_format_version": "2", + "max_allowed_packet": "1073741824", + "div_precision_increment": "10", + "time_zone": "-08:00", + "lc_time_names": "ja_JP", + "default_week_format": "1", + "block_encryption_mode": "aes-256-cbc", + "group_concat_max_len": "1073741824", + }) +} + +func (s *tidbSuite) TestObtainRowFormatVersionFailure(c *C) { + ctx := context.Background() + + s.mockDB. + ExpectBegin() + s.mockDB. + ExpectQuery(`SHOW VARIABLES WHERE Variable_name IN \(.*'tidb_row_format_version'.*\)`). + WillReturnRows(sqlmock.NewRows([]string{"Variable_name", "Value"}).AddRow("time_zone", "+00:00")) + s.mockDB. + ExpectCommit() + s.mockDB. + ExpectClose() + + sysVars := ObtainImportantVariables(ctx, s.tiGlue.GetSQLExecutor()) + c.Assert(sysVars, DeepEquals, map[string]string{ + "tidb_row_format_version": "1", + "max_allowed_packet": "67108864", + "div_precision_increment": "4", + "time_zone": "+00:00", + "lc_time_names": "en_US", + "default_week_format": "0", + "block_encryption_mode": "aes-128-ecb", + "group_concat_max_len": "1024", + }) +} + +func (s *tidbSuite) TestObtainNewCollationEnabled(c *C) { + ctx := context.Background() + + s.mockDB. + ExpectQuery("\\QSELECT variable_value FROM mysql.tidb WHERE variable_name = 'new_collation_enabled'\\E") + version := ObtainNewCollationEnabled(ctx, s.tiGlue.GetSQLExecutor()) + c.Assert(version, Equals, false) + + kvMap := map[string]bool{ + "True": true, + "False": false, + } + for k, v := range kvMap { + s.mockDB. + ExpectQuery("\\QSELECT variable_value FROM mysql.tidb WHERE variable_name = 'new_collation_enabled'\\E"). + WillReturnRows(sqlmock.NewRows([]string{"variable_value"}).AddRow(k)) + + version := ObtainNewCollationEnabled(ctx, s.tiGlue.GetSQLExecutor()) + c.Assert(version, Equals, v) + } + s.mockDB. + ExpectClose() +} diff --git a/pkg/lightning/sigusr1_other.go b/pkg/lightning/sigusr1_other.go new file mode 100644 index 000000000..1bb579c31 --- /dev/null +++ b/pkg/lightning/sigusr1_other.go @@ -0,0 +1,20 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build !linux,!darwin,!freebsd,!unix + +package lightning + +// handleSigUSR1 does nothing outside of Unix, since SIGUSR1 does not exist. +func handleSigUsr1(handler func()) { +} diff --git a/pkg/lightning/sigusr1_unix.go b/pkg/lightning/sigusr1_unix.go new file mode 100644 index 000000000..087f07d23 --- /dev/null +++ b/pkg/lightning/sigusr1_unix.go @@ -0,0 +1,38 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build linux darwin freebsd unix + +package lightning + +import ( + "os" + "os/signal" + "syscall" + + "go.uber.org/zap" + + "github.com/pingcap/br/pkg/lightning/log" +) + +// handleSigUsr1 listens for the SIGUSR1 signal and executes `handler()` every time it is received. +func handleSigUsr1(handler func()) { + ch := make(chan os.Signal, 1) + signal.Notify(ch, syscall.SIGUSR1) + go func() { + for sig := range ch { + log.L().Debug("received signal", zap.Stringer("signal", sig)) + handler() + } + }() +} diff --git a/pkg/lightning/verification/checksum.go b/pkg/lightning/verification/checksum.go new file mode 100644 index 000000000..db008af2f --- /dev/null +++ b/pkg/lightning/verification/checksum.go @@ -0,0 +1,107 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package verification + +import ( + "fmt" + "hash/crc64" + + "go.uber.org/zap/zapcore" + + "github.com/pingcap/br/pkg/lightning/common" +) + +var ecmaTable = crc64.MakeTable(crc64.ECMA) + +type KVChecksum struct { + bytes uint64 + kvs uint64 + checksum uint64 +} + +func NewKVChecksum(checksum uint64) *KVChecksum { + return &KVChecksum{ + checksum: checksum, + } +} + +func MakeKVChecksum(bytes uint64, kvs uint64, checksum uint64) KVChecksum { + return KVChecksum{ + bytes: bytes, + kvs: kvs, + checksum: checksum, + } +} + +func (c *KVChecksum) UpdateOne(kv common.KvPair) { + sum := crc64.Update(0, ecmaTable, kv.Key) + sum = crc64.Update(sum, ecmaTable, kv.Val) + + c.bytes += uint64(len(kv.Key) + len(kv.Val)) + c.kvs++ + c.checksum ^= sum +} + +func (c *KVChecksum) Update(kvs []common.KvPair) { + var ( + checksum uint64 + sum uint64 + kvNum int + bytes int + ) + + for _, pair := range kvs { + sum = crc64.Update(0, ecmaTable, pair.Key) + sum = crc64.Update(sum, ecmaTable, pair.Val) + checksum ^= sum + kvNum++ + bytes += (len(pair.Key) + len(pair.Val)) + } + + c.bytes += uint64(bytes) + c.kvs += uint64(kvNum) + c.checksum ^= checksum +} + +func (c *KVChecksum) Add(other *KVChecksum) { + c.bytes += other.bytes + c.kvs += other.kvs + c.checksum ^= other.checksum +} + +func (c *KVChecksum) Sum() uint64 { + return c.checksum +} + +func (c *KVChecksum) SumSize() uint64 { + return c.bytes +} + +func (c *KVChecksum) SumKVS() uint64 { + return c.kvs +} + +// MarshalLogObject implements the zapcore.ObjectMarshaler interface. +func (c *KVChecksum) MarshalLogObject(encoder zapcore.ObjectEncoder) error { + encoder.AddUint64("cksum", c.checksum) + encoder.AddUint64("size", c.bytes) + encoder.AddUint64("kvs", c.kvs) + return nil +} + +// MarshalJSON implements the json.Marshaler interface. +func (c KVChecksum) MarshalJSON() ([]byte, error) { + result := fmt.Sprintf(`{"checksum":%d,"size":%d,"kvs":%d}`, c.checksum, c.bytes, c.kvs) + return []byte(result), nil +} diff --git a/pkg/lightning/verification/checksum_test.go b/pkg/lightning/verification/checksum_test.go new file mode 100644 index 000000000..c7b17039a --- /dev/null +++ b/pkg/lightning/verification/checksum_test.go @@ -0,0 +1,92 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package verification_test + +import ( + "encoding/json" + "testing" + + . "github.com/pingcap/check" + + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/verification" +) + +type testKVChcksumSuite struct{} + +func (s *testKVChcksumSuite) SetUpSuite(c *C) {} +func (s *testKVChcksumSuite) TearDownSuite(c *C) {} + +var _ = Suite(&testKVChcksumSuite{}) + +func TestKVChcksum(t *testing.T) { + TestingT(t) +} + +func uint64NotEqual(a uint64, b uint64) bool { return a != b } + +func (s *testKVChcksumSuite) TestChcksum(c *C) { + checksum := verification.NewKVChecksum(0) + c.Assert(checksum.Sum(), Equals, uint64(0)) + + // checksum on nothing + checksum.Update([]common.KvPair{}) + c.Assert(checksum.Sum(), Equals, uint64(0)) + + checksum.Update(nil) + c.Assert(checksum.Sum(), Equals, uint64(0)) + + // checksum on real data + excpectChecksum := uint64(4850203904608948940) + + kvs := []common.KvPair{ + { + Key: []byte("Cop"), + Val: []byte("PingCAP"), + }, + { + Key: []byte("Introduction"), + Val: []byte("Inspired by Google Spanner/F1, PingCAP develops TiDB."), + }, + } + + checksum.Update(kvs) + + var kvBytes uint64 + for _, kv := range kvs { + kvBytes += uint64(len(kv.Key) + len(kv.Val)) + } + c.Assert(checksum.SumSize(), Equals, kvBytes) + c.Assert(checksum.SumKVS(), Equals, uint64(len(kvs))) + c.Assert(checksum.Sum(), Equals, excpectChecksum) + + // recompute on same key-value + checksum.Update(kvs) + c.Assert(checksum.SumSize(), Equals, kvBytes<<1) + c.Assert(checksum.SumKVS(), Equals, uint64(len(kvs))<<1) + c.Assert(uint64NotEqual(checksum.Sum(), excpectChecksum), IsTrue) +} + +func (s *testKVChcksumSuite) TestChecksumJSON(c *C) { + testStruct := &struct { + Checksum verification.KVChecksum + }{ + Checksum: verification.MakeKVChecksum(123, 456, 7890), + } + + res, err := json.Marshal(testStruct) + + c.Assert(err, IsNil) + c.Assert(res, BytesEquals, []byte(`{"Checksum":{"checksum":7890,"size":123,"kvs":456}}`)) +} diff --git a/pkg/lightning/web/progress.go b/pkg/lightning/web/progress.go new file mode 100644 index 000000000..099594dda --- /dev/null +++ b/pkg/lightning/web/progress.go @@ -0,0 +1,187 @@ +package web + +import ( + "encoding/json" + "sync" + + "github.com/pingcap/errors" + + "github.com/pingcap/br/pkg/lightning/checkpoints" + "github.com/pingcap/br/pkg/lightning/common" + "github.com/pingcap/br/pkg/lightning/mydump" +) + +// checkpointsMap is a concurrent map (table name → checkpoints). +// +// Implementation note: Currently the checkpointsMap is only written from a +// single goroutine inside (*RestoreController).listenCheckpointUpdates(), so +// all writes are going to be single threaded. Writing to checkpoint is not +// considered performance critical. The map can be read from any HTTP connection +// goroutine. Therefore, we simply implement the concurrent map using a single +// RWMutex. We may switch to more complicated data structure if contention is +// shown to be a problem. +// +// Do not implement this using a sync.Map, its mutex can't protect the content +// of a pointer. +type checkpointsMap struct { + mu sync.RWMutex + checkpoints map[string]*checkpoints.TableCheckpoint +} + +func makeCheckpointsMap() (res checkpointsMap) { + res.checkpoints = make(map[string]*checkpoints.TableCheckpoint) + return +} + +func (cpm *checkpointsMap) clear() { + cpm.mu.Lock() + cpm.checkpoints = make(map[string]*checkpoints.TableCheckpoint) + cpm.mu.Unlock() +} + +func (cpm *checkpointsMap) insert(key string, cp *checkpoints.TableCheckpoint) { + cpm.mu.Lock() + cpm.checkpoints[key] = cp + cpm.mu.Unlock() +} + +type totalWritten struct { + key string + totalWritten int64 +} + +func (cpm *checkpointsMap) update(diffs map[string]*checkpoints.TableCheckpointDiff) []totalWritten { + totalWrittens := make([]totalWritten, 0, len(diffs)) + + cpm.mu.Lock() + defer cpm.mu.Unlock() + + for key, diff := range diffs { + cp := cpm.checkpoints[key] + cp.Apply(diff) + + tw := int64(0) + for _, engine := range cp.Engines { + for _, chunk := range engine.Chunks { + if engine.Status >= checkpoints.CheckpointStatusAllWritten { + tw += chunk.Chunk.EndOffset - chunk.Key.Offset + } else { + tw += chunk.Chunk.Offset - chunk.Key.Offset + } + } + } + totalWrittens = append(totalWrittens, totalWritten{key: key, totalWritten: tw}) + } + return totalWrittens +} + +func (cpm *checkpointsMap) marshal(key string) ([]byte, error) { + cpm.mu.RLock() + defer cpm.mu.RUnlock() + + if cp, ok := cpm.checkpoints[key]; ok { + return json.Marshal(cp) + } + return nil, errors.NotFoundf("table %s", key) +} + +type taskStatus uint8 + +const ( + taskStatusNotStarted taskStatus = 0 + taskStatusRunning = 1 + taskStatusCompleted = 2 +) + +type tableInfo struct { + TotalWritten int64 `json:"w"` + TotalSize int64 `json:"z"` + Status taskStatus `json:"s"` + Message string `json:"m,omitempty"` +} + +type taskProgress struct { + mu sync.RWMutex + Tables map[string]*tableInfo `json:"t"` + Status taskStatus `json:"s"` + Message string `json:"m,omitempty"` + + // The contents have their own mutex for protection + checkpoints checkpointsMap +} + +var currentProgress = taskProgress{ + checkpoints: makeCheckpointsMap(), +} + +func BroadcastStartTask() { + currentProgress.mu.Lock() + currentProgress.Status = taskStatusRunning + currentProgress.mu.Unlock() + + currentProgress.checkpoints.clear() +} + +func BroadcastEndTask(err error) { + errString := errors.ErrorStack(err) + + currentProgress.mu.Lock() + currentProgress.Status = taskStatusCompleted + currentProgress.Message = errString + currentProgress.mu.Unlock() +} + +func BroadcastInitProgress(databases []*mydump.MDDatabaseMeta) { + tables := make(map[string]*tableInfo, len(databases)) + + for _, db := range databases { + for _, tbl := range db.Tables { + name := common.UniqueTable(db.Name, tbl.Name) + tables[name] = &tableInfo{TotalSize: tbl.TotalSize} + } + } + + currentProgress.mu.Lock() + currentProgress.Tables = tables + currentProgress.mu.Unlock() +} + +func BroadcastTableCheckpoint(tableName string, cp *checkpoints.TableCheckpoint) { + currentProgress.mu.Lock() + currentProgress.Tables[tableName].Status = taskStatusRunning + currentProgress.mu.Unlock() + + // create a deep copy to avoid false sharing + currentProgress.checkpoints.insert(tableName, cp.DeepCopy()) +} + +func BroadcastCheckpointDiff(diffs map[string]*checkpoints.TableCheckpointDiff) { + totalWrittens := currentProgress.checkpoints.update(diffs) + + currentProgress.mu.Lock() + for _, tw := range totalWrittens { + currentProgress.Tables[tw.key].TotalWritten = tw.totalWritten + } + currentProgress.mu.Unlock() +} + +func BroadcastError(tableName string, err error) { + errString := errors.ErrorStack(err) + + currentProgress.mu.Lock() + if tbl := currentProgress.Tables[tableName]; tbl != nil { + tbl.Status = taskStatusCompleted + tbl.Message = errString + } + currentProgress.mu.Unlock() +} + +func MarshalTaskProgress() ([]byte, error) { + currentProgress.mu.RLock() + defer currentProgress.mu.RUnlock() + return json.Marshal(¤tProgress) +} + +func MarshalTableCheckpoints(tableName string) ([]byte, error) { + return currentProgress.checkpoints.marshal(tableName) +} diff --git a/pkg/lightning/web/res.go b/pkg/lightning/web/res.go new file mode 100644 index 000000000..92a6a905c --- /dev/null +++ b/pkg/lightning/web/res.go @@ -0,0 +1,22 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build dev + +package web + +import ( + "net/http" +) + +var Res http.FileSystem = http.Dir("web/dist") diff --git a/pkg/lightning/web/res_vfsdata.go b/pkg/lightning/web/res_vfsdata.go new file mode 100644 index 000000000..f0e1b4ea5 --- /dev/null +++ b/pkg/lightning/web/res_vfsdata.go @@ -0,0 +1,194 @@ +// Code generated by vfsgen; DO NOT EDIT. + +// +build !dev + +package web + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + pathpkg "path" + "time" +) + +// Res statically implements the virtual filesystem provided to vfsgen. +var Res = func() http.FileSystem { + fs := vfsgenÛ°FS{ + "/": &vfsgenÛ°DirInfo{ + name: "/", + modTime: time.Date(2020, 4, 6, 23, 45, 49, 105645100, time.UTC), + }, + "/index.html": &vfsgenÛ°CompressedFileInfo{ + name: "index.html", + modTime: time.Date(2020, 8, 24, 10, 15, 59, 106307700, time.UTC), + uncompressedSize: 355, + + compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5c\x90\xc1\x4e\x33\x31\x0c\x84\x5f\x25\x7f\xce\xdd\x46\xff\x8d\x83\x93\x03\x42\x1c\x10\x37\xca\x03\xb8\x89\xbb\x31\x6c\x9c\x25\x71\xb7\xf4\xed\x51\xbb\x05\x24\x2e\x96\x66\x46\x9f\x35\x1a\xf8\x97\x6a\xd4\xf3\x4c\x26\x6b\x99\x02\x5c\xae\x99\x50\x46\x6f\x49\x6c\x80\x4c\x98\x02\x14\x52\x34\x31\x63\xeb\xa4\xde\xbe\xee\x1e\x87\x3b\x7b\x73\x05\x0b\x79\xbb\x30\x9d\xe6\xda\xd4\x9a\x58\x45\x49\xd4\xdb\xc2\xc2\xe5\x58\x86\x1e\x71\x22\xff\x7f\xc3\xc2\xca\x38\xfd\xe8\x13\x27\xcd\x3e\xd1\xc2\x91\x86\xab\xd8\xf4\xdc\x58\xde\x07\xad\xc3\x81\xd5\x4b\xb5\x2e\x80\xb2\x4e\x14\x76\xfc\x70\x6f\x9e\x79\xcc\x2a\x2c\x23\xb8\xd5\x05\xb7\xd6\xdb\xd7\x74\x0e\x20\xb5\xc7\xc6\xb3\x06\xe8\xda\xaa\x8c\x7f\x20\x73\xa2\xbd\x61\x51\x6a\x07\x8c\x64\x1a\x7d\x1c\xb9\x51\x37\x4f\xb8\xe0\xcb\x15\xdc\x82\xbb\x91\xe0\x7e\x9f\x25\x5e\x0c\x27\x6f\x71\x9e\x6d\x00\x97\x78\x09\xb0\x66\xa6\xb7\xe8\x2d\x4b\xa2\xcf\xed\x5b\xbf\x84\xdf\x8c\x5b\x1b\xb9\xeb\xa6\x5f\x01\x00\x00\xff\xff\xe8\x66\x63\x62\x63\x01\x00\x00"), + }, + "/index.js": &vfsgenÛ°CompressedFileInfo{ + name: "index.js", + modTime: time.Date(2020, 8, 24, 10, 15, 59, 107309100, time.UTC), + uncompressedSize: 426146, + + compressedContent: []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd4\xfd\x7b\x7b\xdb\x36\xb2\x38\x00\xff\x7f\x3e\x85\x8d\xd3\xa3\x12\xd1\x58\x26\x65\xcb\x17\xaa\xa8\x9a\xa4\xe9\x36\xbb\x49\xd3\x4d\xd2\x76\xbb\x8a\x36\x3f\x4a\x84\x2c\xc6\x14\xa8\x92\x90\x2f\x91\xf8\x7e\xf6\xf7\xc1\x95\x20\x45\x39\xee\xd9\x3d\xef\xf3\xbc\x9b\xad\x4c\xdc\x06\xb7\xc1\x60\x66\x30\x18\x1c\xce\xd7\x6c\xc6\x93\x8c\x79\x14\x6f\x6e\xa2\xfc\x80\x93\x4d\x39\x34\x91\x07\xcc\xcb\xf1\x26\x99\x7b\x7c\x9c\x4f\x70\x4e\xf9\x3a\x67\x07\xe2\xbb\x47\xef\x56\x59\xce\x8b\xa1\x28\x92\x11\x11\x45\x36\x49\x98\x43\x1a\x1e\x06\xa0\x13\xc3\x4d\x59\x0e\x75\x21\x2a\x0a\xcd\xa2\x34\xf5\x32\x53\x16\x32\xa8\xbe\x19\x86\xac\x97\x92\x43\xbf\x8a\x2b\x59\x6f\x49\x28\xb0\xde\x8c\x70\x60\xbd\x98\x54\x4d\x05\x0e\x39\xde\xb0\x5e\x26\x3e\xf1\x76\xfb\x66\xfa\x89\xce\x78\x2f\xa6\xf3\x84\xd1\x9f\xf3\x6c\x45\x73\x7e\x2f\xb3\x6d\x28\x5b\x2f\x69\x1e\x4d\x53\x1a\x1e\xfa\x70\x45\x79\x98\x97\xb8\x04\xd6\xcb\x89\xdb\x75\xb4\x66\xaa\x74\x8c\x0e\x09\xbf\x5f\xd1\x6c\x7e\xf0\xee\x7e\x39\xcd\xd2\x4e\x47\xfd\xed\xf1\xec\x1d\xcf\x13\x76\xf5\x3e\xba\xea\x74\xf6\xd5\xb8\x9b\x17\x36\x37\x51\xba\xa6\x21\x7a\x9d\xc5\xeb\x94\xa2\x12\xc3\xbe\xc2\xe8\xe3\x47\x5a\xe8\x6c\xa6\xd8\xa1\xaf\x9a\xcb\x6b\xdd\x97\x93\x12\x74\x78\xa7\xe3\x51\x22\x3a\x80\xe1\xa2\xc3\xcd\x0c\xd1\x61\x32\xf7\x4e\x45\x2a\xca\x64\x55\x88\x98\x3e\xd1\x4e\x47\xfc\xbf\x57\xd5\x54\x15\x12\x73\x99\x13\xdd\xb8\x59\x4e\x23\x4e\x3d\xb6\x4e\x53\x2c\xc0\xb1\x5e\xee\xe5\xfb\x9a\x9e\x03\x8a\xe9\x3c\x5a\xa7\x1c\x35\x47\x5c\xf5\x82\x96\x18\xfa\xb2\x41\x85\x1c\x97\x6a\x90\x29\x9e\x67\xb9\x27\xd1\xe8\x20\x61\x07\x14\xb3\x5e\xec\xe5\x90\x81\xed\x2e\xc7\x1b\x8b\x44\x7c\x52\xf6\xa6\x09\x8b\x65\xbb\x20\xc3\xd8\xe0\x57\x2e\xc6\x88\x91\x5d\x6c\x6e\xf4\x76\x64\x73\x54\x50\x7b\xba\xed\x65\xd8\x92\x68\x31\x58\xb4\x8b\x03\x8a\x10\x70\x0c\x5c\x54\x97\x35\xa6\x44\x67\xd4\x43\xb4\xca\x33\x9e\x89\x4e\xf6\x16\x51\xf1\xe6\x96\x99\xc1\x52\xab\x40\x14\x10\x30\x56\x04\x21\x60\x1e\xeb\x15\xe4\xf2\x12\x97\xde\xb8\x86\xe4\x4c\x20\x66\x41\x0f\xc4\xa0\xcd\x38\x1a\x52\xb3\x36\x08\xf3\x2e\x2e\x70\x09\x0f\xe6\xb6\xab\x38\xb7\xfd\xf1\xec\xfc\x46\x45\x91\x5c\xb1\xed\xd6\x1d\x31\x33\x13\x9c\x04\x43\xfe\x4d\x94\x5f\xad\x97\x94\xf1\xa2\x97\x52\x76\xc5\x17\x43\xde\xed\xaa\x51\x65\xc4\xa6\x8d\xf9\x64\x68\x8a\xe5\x62\x02\x19\x7e\x54\xff\x19\xe4\x58\x20\xaf\x20\x1c\x4c\x10\x97\xd2\x0e\x38\xee\x45\xab\x55\x7a\xef\xf1\x45\x52\x80\xad\x08\x97\xd5\x04\x78\xbb\xf3\x94\x97\xf8\x4b\xc3\xf1\x60\xf9\xac\xc4\x58\x2f\x00\xe6\x05\x27\xb8\x1a\xbc\xcc\xae\x37\x81\x73\x84\x98\x05\xb3\x29\x65\x7e\x06\x19\x24\x7a\x50\xbd\xbc\x17\x61\x99\x5d\xac\x18\x3d\x0e\x57\x94\x3b\xbd\x57\xf4\xa1\x50\xe3\x18\x91\x87\xf2\x78\x14\xcb\xa1\xcd\x88\x3f\xcc\xbe\x89\xcc\x2c\x64\xdd\x2e\x66\x24\x1a\x67\x13\xe0\xbd\x84\xc5\xf4\xee\xcd\xdc\x63\xf8\x5b\xe2\x5b\x3a\x58\x0d\xfd\x4a\x83\x7c\x59\xbc\xb0\xab\xd2\x20\x20\x13\x13\x90\x8c\xd9\x84\xd0\x31\xab\x26\x20\x29\x1f\x8f\x56\x66\x95\x01\x83\x8c\x20\x24\x7a\x6d\x16\x78\x45\x71\xb6\x5b\xc4\xd6\xcb\x29\xcd\x9d\x38\x9c\x75\x09\x1d\xd2\xb4\xa0\x07\xa2\xcc\x0e\x95\xc2\xc9\xdc\x7b\x9a\xe7\xd1\x7d\x2f\x29\xe4\x5f\x41\xe2\xc4\x60\x70\xe2\x0f\xf9\x37\xd4\x45\x49\x41\x15\x3a\x1d\x8f\x91\xdc\x13\x9f\x58\x74\x2b\x13\xff\x75\x09\x3a\x40\x18\xb2\x2e\x61\x58\xd5\x25\x21\x28\x32\xa3\x4b\x35\x32\x72\x4b\x50\xb2\x92\xf7\x22\xe2\x20\x8a\xc1\x72\x39\x24\xc4\x57\x1d\x66\xbb\xab\x04\x7b\xd4\x59\x1e\xac\xdb\x9d\x88\x06\x71\xd1\xba\xb6\xa6\xb9\x35\x7e\x61\xe0\x2d\x7a\x62\xc8\x08\xf3\xfa\x18\x12\xc2\x3c\x1f\x43\x44\x58\x8f\x79\x09\x86\x94\x78\xcc\x1b\x60\x60\xde\x49\x1f\x63\x28\x64\x7c\x8a\x61\x46\x90\x81\x5c\x0d\xb2\xd9\xde\x50\x21\x3f\x9a\x09\xbd\x84\xd3\x3c\xe2\x59\x3e\x72\x49\x84\xe1\x01\xf4\x3c\x39\x24\xb3\x4a\xa4\x9d\xce\x03\xd5\xd1\xde\x2c\x63\x05\xcf\xd7\x33\x9e\xe5\x84\x10\x1b\x7f\x68\xbe\x2b\xfc\x1d\x99\xb6\x85\xb6\x42\x58\x93\x0a\x5d\x88\xe7\xec\xdb\xb6\xa6\xdb\x84\xc5\xd9\xed\xc8\x49\x0a\x67\x9e\x8a\x14\x33\xf0\xa5\xe2\x71\x36\x93\x93\xd7\x00\x60\xa2\x05\x88\x4b\x42\x88\x09\xf7\x58\x16\xd3\xf7\xf7\x2b\x0a\xb1\x18\xfd\xe0\x42\x0c\x7f\x7f\x80\x31\xcc\xc5\x5c\x05\x18\x56\x62\xb2\xce\x31\x2c\x14\x69\x81\x25\xd9\x94\xee\x20\x54\x2b\xea\x46\x8c\x62\x45\x69\xb6\x5b\xd3\x56\x67\xbb\x74\xf7\xf8\x9d\x25\x62\xb7\xb4\x65\xb4\xf2\x6e\x24\x19\xaa\x8d\xf7\x21\x21\xcb\xfa\x86\xaf\xf8\x3d\x8d\xdb\x4c\xad\x0d\x2e\x88\xc2\x8d\x27\xc9\x82\xc1\x4e\x5e\xda\x56\x4e\x0d\x76\xde\x64\x49\x7c\xe0\x13\x22\x36\x59\x8f\x12\xb4\x66\x2c\x5a\xd2\x18\x59\x4a\xda\xfb\x54\x14\x90\x91\x1b\x8f\x0b\x5c\xcd\x7b\xab\x74\x7d\x95\xb0\xa2\x97\xb1\xe7\x92\xbd\x78\xbb\x4e\xa9\x47\x21\x03\x66\xeb\x49\xb6\x5b\x8f\x8e\xfd\x09\x48\xce\xa3\x14\x80\xae\x1a\xfb\xac\x6d\xad\xd8\x3b\x73\xe2\x0f\x73\x4b\x10\x3a\x1d\x74\x98\x2c\xc5\x16\x19\x31\x31\x6a\x44\x6c\x30\xc3\x5c\x90\x4c\x41\x22\xc4\x72\x03\xd6\xd5\xb1\x7a\x5f\x77\xd8\xdd\x7b\x4b\xeb\x6d\xd7\xb8\x5c\xbc\x87\x01\x86\xc3\xbd\xa3\x3d\x34\xad\x69\x99\x93\xb1\x3f\xc1\x96\xc5\xf9\x93\x8d\x45\x20\x88\x04\xeb\x92\x2b\xb9\x51\x82\xa0\x19\x8a\x90\x31\x11\x05\x32\xdd\x4e\xd0\x76\xbb\x03\xce\xd4\x74\x14\x4c\xb6\x5b\x09\xf1\xc0\xc9\x82\x81\x55\x93\x7a\xb7\x77\x6c\xb9\x6a\x92\x28\x7c\x80\xec\xa8\x75\x69\x55\xf6\x76\x07\x21\x64\x07\xc8\xa6\x34\x98\xa0\x86\xe6\xd0\x72\xa8\xb9\x16\x1c\x98\xdc\xc1\x18\x87\x84\xd8\xb2\xd9\xc8\x0f\x33\x88\x08\xef\xcd\xa3\x34\x9d\x46\xb3\xeb\x42\xe2\x71\xa7\x93\x74\xbb\x10\xed\x6e\x0d\x51\x35\xc0\x29\xf1\x87\x69\xb5\x57\xa6\x86\x63\x29\x48\x34\x4e\x2b\x4e\x65\x26\xf0\x5c\x6f\xc2\x6b\x52\x8c\x67\x93\xa1\x40\xb7\x43\xb2\xee\x74\xbc\x5c\xfc\xd7\x25\xe8\x03\x43\x18\xc4\x07\xea\xde\x79\xb3\x2e\x0a\x0f\x50\xf7\xde\x5b\xe3\x2e\x1a\x22\x48\x30\x2e\x4b\xbb\xa7\x08\x38\xb1\x80\x19\x29\x98\x73\x12\x8d\x63\x03\x73\xbe\x0f\x66\x6c\x60\xce\x2b\x98\x06\xda\x4a\x40\xe3\x0a\xda\x82\xf0\xf1\xca\x40\x13\x58\x63\xc7\x45\xcc\xf2\x6a\x1f\xf8\x95\x01\xbf\xa8\xc0\x1b\x2e\x70\xbb\x65\xbd\x28\x4d\xb3\xdb\x17\xcb\x15\xbf\xc7\x9d\x0e\x1d\x29\x20\x12\x46\x37\xef\x2a\x50\x77\x1e\xed\xa2\x83\x0d\xea\xe6\x70\x74\x94\xe0\xee\x9d\x87\x4a\x09\x28\xcc\xe5\xca\x7c\x41\x8e\xbd\xf1\xf8\xc3\xa4\xf7\xdf\x4f\xbe\xfa\xf6\x9b\xee\xff\x87\x6c\xff\x15\x7a\x18\xd0\xd7\xff\xef\x43\x31\xc1\xc7\x57\x70\x4d\xda\x04\xab\xe7\xef\xde\x75\x3a\xcf\xdf\xbd\xeb\xd1\x62\x16\xad\x28\xbc\x21\x2d\xbb\xc8\xf5\xe8\xda\xa3\x38\xa4\xbd\x9c\xae\xd2\x68\x46\xbd\x17\x80\x3e\x7c\xf8\x2a\x40\xb8\x84\xe7\xb5\x8d\xd9\xa0\x21\x35\x68\x28\x38\xc7\x9e\xa8\x8b\xa0\x82\xdf\x0b\x61\x4a\xc6\x5c\xd3\x7b\x8d\x65\x2a\x9c\x14\x3f\xe7\xd9\x8c\x16\x05\x8d\xc9\x61\xa0\xe2\x64\xfe\x5a\xae\x9c\xb2\x98\xe6\x34\x6f\x89\x14\xdc\x54\x2d\x3a\x5b\x89\x86\x14\x3a\xce\x52\xc1\x62\x41\x29\x17\x7b\x76\xef\xad\x06\x36\xb4\x0d\xa2\xf5\x92\xcc\x6d\x06\x87\x7c\x54\x6f\x43\x6e\x3f\x43\xc1\x47\xd4\x13\x19\xbd\x3d\xc8\x2a\x46\xba\xc1\x06\x92\x26\x6f\x51\xa3\x72\x56\xb8\xb7\xb5\x8f\xe9\x44\x77\xe0\xf0\x90\x75\x3a\xac\x37\xcf\xf2\x99\xdc\x75\x0e\xf3\x4e\xa7\x96\xaf\x09\xc0\x68\x05\x86\xac\xd3\x39\x0c\x04\x39\x10\x2d\x10\x23\xbd\xdd\x7a\x19\x71\x3b\x2c\x76\x08\x77\x5b\x58\x44\xec\x8a\xfe\x2a\x64\x46\x8f\x83\x1a\x1b\xcd\x98\x27\x44\xed\x8a\xd9\x76\x2b\x61\x0a\x12\x41\xe5\x2a\xb1\x4d\x11\x8d\x4b\x3a\x9d\xc3\xa8\xd3\x39\xcc\x77\x1a\x94\x92\xa4\xd3\x89\x44\x9e\x74\x14\xd3\x94\x72\x5a\xef\x6d\x58\xef\x53\xd6\x9c\x69\xdd\x69\x33\xdc\x06\x7e\x5a\x9f\xa2\x5e\x4e\x97\xd9\x4d\x25\x1a\x37\x80\x00\xc5\x61\x3d\x7f\x41\xf9\xfe\xcc\x90\x61\xb0\x1d\x28\xea\x23\x27\xb1\xca\xd0\xe3\xa2\xd3\x29\x7a\x11\xe7\xd1\x6c\x41\x63\x59\xa4\x04\x5a\x7a\x18\xde\xd5\xd6\x96\x5d\x2b\xdc\x13\x4c\x7b\xae\x08\x4c\x36\xf4\x32\xa2\xe5\x02\x29\x75\xa9\xb4\xed\x56\x0e\x7f\xaf\xa0\x29\x15\xcc\xc3\x7b\x7a\xc7\x0d\xba\x67\xbd\x24\xae\xbe\x77\x96\x83\x9e\xb1\xdc\x96\x85\x48\x04\x66\xd9\x8a\xc6\x90\x8a\x4f\xb9\x24\x0a\x92\xf7\xae\x28\x13\x7c\x26\x7d\x19\x5b\x16\x60\x94\xd5\xeb\x4c\xc2\xc3\xe0\x90\x90\x48\x70\xce\xa2\xde\x42\xcb\x56\xde\x4a\xc8\x5b\xee\x77\x86\x31\xa4\x18\x1a\xe5\x51\x0f\x75\xdf\xc8\xa2\x18\x43\x56\xea\x02\x73\x51\x80\x03\xc5\x7a\xfb\x76\xe4\xa6\x4a\xe6\x97\xa2\xe8\xfb\xac\x45\xb1\x50\x9b\x45\x81\x55\xdc\x08\xc7\x8a\xfc\x64\x7f\x7d\xf7\xe6\x27\x0f\x37\xa5\x63\x5e\x9b\x70\x0a\x39\xd4\xc4\x5f\x35\x73\x4c\x17\x77\xc9\x9c\x94\x3d\x5c\x66\x8d\xd7\x91\xbf\x56\xbb\xc2\x62\x3e\x19\xee\x30\x90\x6c\x24\xe2\x09\x0b\xeb\x7b\x28\x53\xd2\x38\x9f\x10\xf1\xed\x48\xe3\xb2\x2d\x4a\x99\xb5\x6f\x10\x6a\x08\x09\x8c\x1c\x1e\xf2\x4e\x87\xdb\xe8\x34\x61\xd7\x23\x57\x40\xde\x94\x40\x61\x53\x6d\x3f\x52\xc7\x15\xda\x41\xbf\x55\xcb\xc0\x9d\x41\x87\x28\x02\xc3\xa5\xd6\x42\x79\xb1\x9a\xc1\xf1\xe6\x9a\xde\x87\xc8\x14\x40\x50\x50\x5e\x93\x4b\x04\xf7\x70\x48\xc8\x0e\x58\xbd\x53\xd4\x70\xc5\x70\xc5\xb5\xe9\x05\x46\x1a\x4b\x53\xaa\xc4\x3a\x1d\xae\xe6\xf3\x9d\x86\xe0\x31\xa0\x62\xd9\x98\x3d\x4b\xf2\xb7\x8a\xa4\xe3\xb2\x94\xda\xc7\x5d\xd5\xc3\x4e\x1b\xca\x72\x82\x81\x97\xde\x73\x0c\x9f\xc8\xc6\xe5\x95\xc3\x26\x0d\x57\x20\xd0\x77\x42\x98\x11\xac\xa6\xd8\xd7\x57\x51\x4e\x19\xef\x74\xd0\x35\xbd\x9f\xe7\xd1\x92\x16\x48\x91\x60\x19\x2f\xf7\xc5\x91\x20\xa5\xa1\xd8\x2f\xde\x69\x48\x65\x09\xef\xc9\x46\x31\x63\x61\x00\xb3\x45\x92\xc6\x39\x65\x62\x6a\xe0\x67\x72\xfc\x9d\x37\xfe\x70\x7b\x34\xe9\xe2\x63\x78\xfb\xe8\xbd\x77\x96\xb1\x38\x11\xa9\x51\xaa\x77\xe0\x88\xd7\xb6\xcb\xe6\x86\xfc\xc7\x9a\xe6\xf5\x98\x7c\x9d\xd2\xe2\x81\x2d\x76\xff\x46\xbe\x67\x87\x76\xb6\x5c\x55\x1b\xeb\x09\x99\x45\xef\x58\x42\x6e\xe2\xb3\x85\xf7\x73\xb5\x6c\x13\xbb\xca\x22\x4e\xb2\x51\x36\x0e\x26\x21\x5a\xb3\x6b\x96\xdd\x32\xd4\xba\x77\xab\x36\x8b\xc1\xfd\xa7\xd7\x40\x7b\x06\x1b\x35\x0b\x72\x0f\x28\x31\x06\x8e\xab\x32\xbd\x28\x8e\xbd\x04\xf8\x38\x99\xe0\xa1\x13\xad\xf7\x4e\x4f\x49\x43\x9c\xd0\x5d\x42\x25\x55\x48\x02\x43\xda\x38\x29\x07\xd4\x15\xe5\x1e\xc5\x65\xa5\x39\xfa\x42\x7e\xa3\x5f\x52\x65\xa2\x38\x6e\xd4\xa1\xd9\x7e\xc9\x26\x34\x3a\xa2\xd2\xac\x7e\x76\xe4\x3d\xb0\xed\xeb\xe9\x93\xcb\x25\xc7\x90\xe3\x50\x20\xa8\xa8\xb2\x95\xf6\xb8\x9c\x8b\x12\x3d\xdf\x63\xd0\x32\xb3\x16\x28\x44\xac\xfe\x24\xef\xf5\x47\x95\xc7\xa0\xb7\xcc\x65\x02\xe4\xbd\xfd\xc4\x20\x59\x8c\x2a\xad\xc6\x1e\x49\xc4\x11\x7c\x71\x89\x6a\xc4\x42\x76\xdd\x34\xd8\xa3\x95\x64\x36\xaa\x17\x13\x1c\x36\x17\x1c\x76\x89\x42\x84\xf4\x3e\xfd\x13\x39\xfe\x6e\x49\xe3\x24\xda\x7e\x57\xac\x57\x52\xc9\xfb\xa1\xe8\x1e\xc3\xc7\x47\x51\x80\x83\x9f\x7a\x9c\x16\x62\x6e\x47\x02\xf1\xde\xea\x54\x35\x8c\x25\xbc\xde\xb7\xb4\x9f\x92\xe3\xef\x2c\x99\xf8\x50\x74\xab\x65\xfe\xf2\xd1\xcb\xbc\x22\x33\x76\x91\xa3\xef\x9a\x91\xcd\xa5\x2e\x16\x5d\x7d\x0d\xc7\xff\x57\x0b\xbf\xb1\xb6\x9f\xe2\x61\xd6\xe9\x88\x75\x3c\xaa\x5a\x22\x97\x75\x15\x44\x2c\x13\x7f\x9d\xb6\xdb\xfe\x76\xd1\x11\xea\xda\x9c\x0d\x02\x60\x18\x55\xc3\xe9\x44\x96\xf9\x4f\x09\x73\x39\x1d\x43\x5d\x0a\x4b\x5d\x12\xd1\x03\x42\x48\x52\xb5\x2a\x7c\xe3\x69\x7e\x2c\xc2\xf8\x3f\x41\x5b\x0a\xe0\xe3\x62\x02\x5f\x2a\xd8\x4e\x7b\x5a\xc4\x8a\x47\x2e\xce\xd7\x0f\x2d\xce\xd7\x8f\x5c\x9c\xaf\x1f\xb9\x38\x23\xde\x45\x07\x7a\x86\x92\xf8\xcf\xac\x52\xa9\xe8\x41\x76\x71\x22\x0c\x7b\x00\xca\x0c\xa5\x59\xb8\x9f\x1b\x4b\xe8\x18\x5e\x91\xe3\x0f\x5f\xd9\x95\x74\x05\xdf\xb7\x9e\x13\xed\x2a\xca\x47\x95\xac\xfb\xca\x39\xa3\xa0\xce\x32\x97\x0a\x3a\x3e\xe2\x63\x36\x09\x69\x89\x71\x48\x4b\xf8\x61\x0f\x3d\x16\xdc\x1c\x64\xe4\x7b\x2f\x17\x74\x38\x3b\x24\x24\x37\x3c\x5e\x86\x4b\x78\xf6\x38\xd6\x62\xa7\x95\x9d\xce\xe7\x1a\xad\x79\x59\xa3\x35\x60\x89\xf9\x3b\xc1\xad\xed\x87\x2a\xc4\x72\x21\x37\xca\x45\xd5\xe9\xb0\x91\x87\x22\x96\x2c\x23\x91\xf9\x48\x2e\xbe\x44\x6a\x92\x7f\xf0\x28\x34\x53\x80\xf5\xec\x88\x63\x27\xb5\xb5\x48\x23\x37\x95\x63\x56\x93\x34\x77\x1a\x69\xe4\xf6\xba\xa4\x25\x25\xdf\x4a\xe7\x57\xdc\x26\x82\x9c\x70\xbc\x99\x45\x05\x75\xea\x0b\xeb\x61\xd5\xe4\x50\x97\xfb\x5e\xb0\xfc\x4e\x7b\x86\xfa\xec\x31\xb4\x0c\x77\x59\xc2\x6f\xfb\x64\x36\xe7\x88\x0e\x9c\x43\x38\xad\xee\x02\xa5\x02\x30\x0c\x3d\xa8\x83\x23\x26\x4f\x8c\xf2\x71\x36\x71\x8e\x25\x32\xa3\xfe\xf4\xb8\x16\xfc\xf4\xa1\x1b\x85\xb1\xc0\xf4\x49\x6f\x96\xb1\x59\xc4\xbd\x1c\x5b\x31\xb0\x85\xa3\x2a\x6b\x47\x9e\x95\x44\x05\xfc\x4b\x54\xe2\xff\x5c\x7c\xb8\xa6\xf7\x4d\xa9\x41\xb1\xd4\x5f\x3d\x6e\x43\x7d\x24\x1f\x4d\x6f\x0f\x7e\x6b\x6c\xb6\xbf\x3c\x7a\xe7\x9c\x67\x8c\x1f\xcd\xa3\x19\x75\x77\xce\x66\x64\x73\xe7\xdc\xd5\x50\xfd\xe7\x58\x62\xa3\x76\xaa\xef\x6a\x7f\x86\xf4\xd7\xa5\x4a\x47\x3a\x75\x4f\x97\x11\x02\x46\xfc\x21\xfb\xa6\x4a\x37\x1a\x5b\xd6\xed\x62\xde\x25\x7a\x12\x23\x57\xf2\x1b\xb3\x09\xae\x05\xbb\xc1\x44\xd0\x6c\xad\xf7\xac\x0e\x2a\xea\x88\x50\x83\x01\x82\x91\x95\x64\xfb\x47\x72\x5c\x0d\xf5\x31\xfc\xfa\x38\xac\xf8\xb1\x46\xfa\x7e\x69\xcc\xfc\x1f\x8f\x9e\xf9\x9b\x84\xde\x0a\x1e\xcf\x9d\xf8\x46\xdc\xff\x1f\xcd\xfb\x83\x2b\xcf\x8e\xf8\xdf\x1e\x29\xcb\xda\x71\x20\xea\x08\xec\xbb\xa3\x65\x71\x54\x8b\x94\x83\xff\x47\x63\xf0\xff\xfe\x78\x9d\x70\xb2\x5c\xed\x55\x0a\x4b\x7b\x95\xff\xa3\x61\x56\xb0\xff\xc3\xcb\x4b\x02\x7d\x60\x79\xc9\xf4\xc6\xf2\x32\x0d\xab\x38\x1b\x99\x6b\xcc\x26\xf2\xa4\xc0\x8d\xf9\xc2\x22\xdb\x03\x49\x80\xd1\xd3\xfe\x17\xb2\x41\xdf\xcd\x16\x51\x5e\x50\x8e\xc2\x43\x1f\xd0\x77\xea\x08\x4a\x07\xc4\xf6\x58\xac\x04\xbd\x93\x12\xc9\xef\x64\xfc\x09\x3e\xc2\x33\xf8\x0a\x7e\x85\xbf\xc1\xa3\x56\xa5\x54\x03\xff\x45\x62\xc5\xdf\xeb\x58\x31\x81\x7f\x90\x8d\xe6\x61\x25\xf8\xbf\x92\x8d\xd4\x63\x8b\xba\xdd\xf8\x7f\xee\xc3\x1e\x8d\x39\xcb\x68\x45\x36\xa5\x9e\xe8\xe8\xd6\x7e\x4b\x01\x99\x8c\x27\x2a\x34\xcb\xd6\x8c\xd3\x9c\x3c\x80\x39\xb3\x34\x2a\x8a\x86\x68\x63\x37\x98\x56\xbc\xa3\xf5\x82\xd4\x7c\x35\xcb\xd2\xea\xfb\x21\x45\x41\x14\xc7\xad\x0c\x63\x5d\x2f\x0f\x09\xc9\xf4\x4e\x07\x11\xc9\xac\x14\x93\xc9\x23\xdd\x82\x64\xf6\x28\x03\x66\x24\x73\x44\x1b\x58\x8b\xdc\x4a\xfc\x89\x6b\x76\x31\x1b\xdd\xee\xd0\xed\x0e\x68\xd1\x23\x01\x59\x43\x18\xc1\xa7\xa2\x08\x53\x30\xd0\xc3\x02\x2a\xd8\xe1\x0c\x14\xe4\x70\x0d\x52\x40\xa2\x60\xbb\x1c\xd6\x47\x03\x8c\x42\x2d\x54\x43\x5a\x02\xc3\x30\x27\x74\x58\x1d\x19\xe4\xd1\x6d\xa7\xe3\xcd\x89\x10\xe9\x62\x8d\xbc\x7a\x02\xbb\x5d\x6c\xa7\x7a\x3c\x9f\x10\x0e\x73\x5b\x4c\x37\xbc\xd3\xf1\x62\xab\xb5\xd3\x1a\x66\x37\x7d\x3c\x9f\xe8\x83\x8b\x15\x99\x7a\x73\xe0\x10\xcb\xe3\xf8\xc3\x95\x61\x21\x05\x86\x6a\x51\x8b\x5e\x25\x05\xa7\xb9\xb7\x52\x25\x16\xd5\x61\x68\xac\x30\x6c\x54\x21\x9b\x5e\xca\xa1\x4e\x19\xba\x0b\x51\x65\x28\x56\x69\x32\xa3\xde\x02\x7c\x58\x61\x58\x95\x20\xb5\x43\x7b\x35\x3d\xcb\x68\x35\xa6\x13\x91\x4b\x1d\x5d\xd4\x32\xca\x1c\x6b\x66\x5b\x48\x31\xb8\x67\x27\x62\x7c\x24\xde\x4d\x60\xb7\x01\x4e\x4c\xa5\x47\x82\xe0\x51\xea\xa7\x66\xb1\x52\xb1\x94\x62\xbd\xee\x2a\xc5\xf7\x69\x96\x86\x6e\x9b\x64\x93\x7c\xdc\x9b\x67\xf9\x8b\x68\xb6\xf0\x68\x5d\xef\x04\xaa\x0e\xd3\xd1\xdd\x41\x90\xc3\x24\xbb\x4a\x28\x08\x2c\x2a\x78\xc4\x66\xd2\xb2\x45\xeb\xb6\x54\x0e\x83\x13\x32\x5b\x2f\x89\xcd\x31\x9d\xc1\x0b\x03\x43\x9e\x46\x84\x35\x40\x2f\xf5\x11\x93\xc5\x62\x53\xd6\x46\x8c\xa9\xd4\x0c\xe8\xe2\xa2\xc1\xd5\xdc\xd4\x9a\xec\x4e\x52\xd5\xf2\x9d\x76\xef\x66\xb3\xcd\xaf\xcd\x73\xbd\xf1\xbb\xcd\x76\xf3\xee\x34\x56\x36\x73\x15\x47\x9c\xee\x4c\x9d\x20\x3f\x6d\x56\x63\x5e\x53\xb6\xf9\x86\xf8\x23\xb5\x28\xc2\x4a\x8a\xf1\x27\x78\xe4\x5a\x5b\x3d\x9c\x17\x78\x4b\xce\x60\x37\x67\x30\x69\x91\xad\xbe\x21\xfd\xdd\x9c\xfd\x09\x0e\xbd\x36\xa8\xed\xf5\xb7\x41\x6d\xaf\x9f\xca\xb3\x4e\x21\xac\xaa\xf5\x27\x07\xef\x0d\xa3\x0e\x9e\x4d\x94\x7a\xb5\x66\x82\xa0\xed\x35\x9a\xa4\x42\xda\x6f\xb4\x01\x92\x99\xc6\xb9\x02\x55\xcd\xd2\x1b\xe6\x4c\x94\x39\x26\x34\x04\x49\x59\x09\xfc\x03\x0f\xf7\xec\x19\xce\xf2\xab\x36\x0e\x79\x3c\xa6\x54\x2f\x2e\xe2\x50\xac\x23\x75\xc5\xd2\x2a\x54\x76\x69\xa3\x0e\x6c\x39\x14\x24\x75\xce\x76\x7b\x19\xfb\xc5\xe4\xe4\x10\x41\x8e\x21\x37\x64\xa1\xd3\x11\xff\x3f\x24\xa6\x40\xc5\x11\x49\x6b\x8f\xa4\x57\xd7\x4c\x78\x3a\x1b\xa4\x10\x61\x48\xdd\x33\xb4\xb5\x01\x31\x9e\x4d\x86\xeb\x43\x22\x0d\x44\x3a\x1d\x69\x97\xb6\xf2\x66\xb0\x86\xbf\x56\x86\x1a\xb1\x63\x4a\x32\xb7\x05\xe3\x09\xac\x48\x61\x6c\x40\x08\x99\x77\x3a\x73\x65\xab\xa1\xa1\xc4\x52\xdd\x25\x00\x09\x81\x7f\x8f\x9e\xbb\xc1\xd2\xb5\x48\xcb\xf6\x70\xde\x95\x96\xb5\xe0\xbf\x8b\x07\x99\x31\x87\x49\x88\x33\xff\xd9\xa4\xa6\x0e\xf3\x92\xed\x56\xda\xe6\x72\x87\xef\x03\xde\x25\x49\x75\x48\xa9\x59\x3b\x4a\xf7\xf3\xdc\x8e\x29\x91\xdd\x3a\xdb\xf8\xa1\x98\xae\xd2\xec\x9e\xd6\xb5\xbf\xe6\x20\xfb\x0b\x2a\xe1\x56\xd3\x8c\xc7\x33\x58\x7f\xac\x69\x83\xd5\xb7\xf5\x1a\x9e\xde\xb6\xce\x44\x18\xe8\x86\xff\xab\x20\x9b\x18\xd3\xc9\x86\xca\x82\xc3\x46\x71\x38\x52\xa1\xeb\x28\x5c\xa1\x95\x2d\xda\xc7\xd7\x94\x18\xb8\x65\xbd\x5a\x2d\x40\xaa\x64\x99\xd8\xa6\x39\x76\xdb\x69\x89\x4c\xa5\x26\x66\xa0\x2c\xff\x5a\xd5\xc0\x0f\x71\x96\x72\xf4\xc8\x9e\x13\x4e\x33\xb6\xdb\x6d\xbd\xd1\x0d\xb3\x0a\x9d\xcf\xc3\xcd\x19\x69\xe0\x8b\xd2\x4a\xe9\xa0\xa7\x7b\x29\x56\x52\x4c\x1f\xd3\x8a\xd1\xc3\x8d\x50\x40\x76\x1b\xa1\xb0\x40\x99\x70\x3c\xf2\x44\x4c\x62\xd9\xb0\x06\xa7\xd3\x39\xb4\x73\xa7\x90\x70\x3c\xa9\x11\xd4\x7d\xa7\x68\xd9\xa3\x4f\xd1\xb2\x46\xd3\x47\xb5\xc1\x1b\x79\xf9\x28\xef\xad\xd6\xc5\xc2\xcb\xc4\x16\xa6\x88\x41\x41\x73\x5e\x2b\x2c\xdb\x56\x6b\xa8\xe5\x9e\x1a\x45\xd4\xb0\xec\x2e\x2b\x8c\x21\xc3\x61\xa6\xab\x70\x57\x53\x86\xab\x43\xbe\x0a\xce\x2e\xdb\xb5\x6f\x86\x9c\xe6\xd6\x0e\x27\x8b\x86\xb2\x5d\x19\x45\x8c\x1b\xb7\x14\x68\x4d\xe6\xd1\x45\xbd\x1c\xa4\x99\x25\x97\x27\x44\xcc\x8c\x4f\x69\x0d\x45\xe1\xcf\x1f\xb3\x2a\xc6\x68\xa7\x88\xa6\xea\x3b\x76\xef\x23\x1a\xee\x80\xd1\x93\x2f\xd5\xa2\x9e\x93\xaa\xb8\x75\x8f\x63\x38\xf4\x1a\xe8\xc5\x1f\x30\x5b\xd2\x0b\xc7\x41\x75\xd3\x42\xcf\x2d\x86\xff\xd7\xa7\xc4\x6a\x96\xf7\x2d\xc0\xfd\x2b\x4e\x2d\xe4\x26\xdd\xf5\xed\xc2\xde\xc3\x42\x1a\x45\x36\x75\x96\x0e\xd6\x99\xad\x52\xbb\xba\x46\xd2\x00\x57\xe3\x75\xea\x5a\x05\xa7\x83\x15\xdf\xa4\xb2\x58\x20\x0f\xa9\xc3\x9c\xf2\xce\xfe\xaa\xb7\x4e\xbe\x6f\xeb\xd4\x68\xaf\x57\x35\xd9\x24\x42\x1e\x65\x51\x1a\x8e\x27\x40\xef\xec\xb7\x51\x45\x48\xde\xdf\x1a\x4d\x3c\x44\x9d\x5d\x45\xca\x4e\x97\x5b\x38\x48\x03\xba\x56\xd0\xe5\x28\xdd\x15\xd4\x9a\x79\x9c\x4f\x0c\xfd\x4a\xe6\x5e\x86\xed\x3d\x07\x47\x00\x16\x83\x58\xa3\x5c\x4d\x8d\xd7\x21\x75\x95\x6d\x2e\x4f\x44\x77\x4e\x0e\xac\xce\xcb\x69\x8e\x03\xba\xa6\x04\xdb\x9f\x6d\xcc\x26\xea\x0e\x0f\x55\xcc\x9c\xc6\xd4\x06\xfb\x48\x8d\x7a\x13\x38\x06\x5a\x57\x08\xfa\x65\xad\x5b\xb2\xc0\x9f\x1c\x72\xb7\x68\x8d\x8d\x37\xba\xda\x07\xf2\x8b\x71\xd7\xf9\x2c\x67\x5f\xe5\x10\x63\xb5\x87\xcb\xf4\x87\x7c\x6f\x43\x44\x31\xf7\xd6\xcd\x43\xf9\xc6\x7c\xa2\x69\x81\xe1\xd7\x1b\xbd\x17\x12\x85\xbd\xf6\x57\x31\xab\x0e\x38\x55\xcc\x65\x5c\xdb\x73\x8c\xb3\x89\x05\xa9\x2a\x74\x0e\xf6\x1e\x18\x73\x0a\xed\xf5\x3a\xa5\xdd\xca\xf3\x5d\x34\xaf\x32\x8a\x36\xe4\x75\x6b\x17\x49\x60\x0a\xda\xdc\x8b\xea\x97\x0a\x36\x72\xa7\x0c\x91\x59\xd6\xa8\xb4\xa6\x88\x0e\x09\x18\x73\xb5\xa3\x4e\x86\x47\xca\x94\xb6\xa2\xb6\x9d\x8e\xa7\x77\x29\x8a\x1b\xf4\x60\x6c\x4f\xf0\x5c\x58\x3d\x53\x15\xd4\x62\x0d\x8d\xc1\xbd\x9c\xc6\xeb\x19\xf5\xbc\x3d\x17\x2e\x94\x51\xba\xba\x25\xd2\xe9\x88\x95\xa2\xaa\xe7\xee\x4d\x11\x5a\xe2\x86\xda\x76\x3c\x81\xda\x0a\xab\x45\xa8\x73\xe2\x5a\x8c\xe4\x93\x65\x8c\x7b\x4a\x2b\x23\xd4\xac\x0b\x02\x88\x0d\x29\x65\x54\xb0\xb5\xde\x83\xe4\xd4\x19\x97\xc7\xeb\x46\x1b\x96\x9e\x0a\x02\x30\x87\xf0\x28\x0d\x5c\x32\xf7\xe4\xd4\x70\x67\x6a\x70\x32\xf7\xe4\x4c\x6b\x2c\xda\x6e\xd9\xb7\x8e\xd0\x85\xb9\x99\xb7\x56\x09\xde\x5d\xf1\xfa\xf6\x75\xbd\xce\x6f\xad\xd1\x83\xc4\x29\x6e\x54\x6e\x39\xf8\x56\x91\x55\xb8\xeb\xbc\x65\x1c\xda\x15\x7e\x2d\x5d\x76\xba\x35\xac\x27\x18\x45\x9f\x56\xea\x7d\x41\x88\xad\xcc\x42\x46\x9b\x32\xa4\x42\xa2\xad\x4c\x96\xcd\xc5\x54\x6f\xa1\x8d\x3a\x91\x49\x42\x13\x2c\x2f\xdb\x41\x42\xfc\x61\xd2\x58\xb1\x7a\xa0\x12\xb3\x1f\x45\xf5\xa6\x8f\x13\x73\x6f\x82\x75\x3a\x91\xad\xed\x90\x10\xb6\xdd\xda\xab\x78\x52\xc2\xcd\xba\x24\xaa\x36\xea\xbc\xb2\x81\xcd\xea\xf6\xa6\xd4\xd8\x9b\xca\x51\x41\x7b\x8c\x3a\xe5\xdc\xb7\xb4\x74\xe4\x87\xf5\x06\xb6\x65\x3a\x0a\x1a\xd3\x2d\x2d\x42\x05\xb2\x63\xc8\x69\xeb\x95\x0a\x75\xbd\xad\xd3\x51\x7f\x7b\xaf\x23\xbe\x20\x44\xfc\x8e\x54\x4c\xd8\x56\xa8\xa0\xe9\xbc\xd3\x11\xbf\x6e\x01\x11\x0e\x7f\x30\x3d\x42\x0e\x37\x83\xb0\x87\x21\xa3\x04\xf5\xe7\x41\x34\x9b\x9d\xcd\x4e\xa2\x33\xff\x6c\xea\x5f\xf4\xe9\x80\xd2\xf9\x80\x0e\x4e\x4f\x83\xd3\xf9\x7c\x8a\xb4\xda\x23\xa7\xe3\x8c\x4e\x3a\x1d\x4f\x7d\x10\x5f\x9b\xf2\x53\x9d\xd2\xed\x42\xd4\x40\xbf\xba\xe9\x90\xb9\x41\x24\xf6\x26\xdd\x10\x9b\x5b\xee\x22\xbc\x4b\x02\x2d\x39\x49\x0c\x41\xf6\x86\x92\x54\x54\xd9\x61\x94\x72\xf5\x4f\xd1\x92\xfe\x9c\xd3\x79\x72\xd7\xe9\x78\x09\xd9\x9b\xaa\x6c\x91\x0e\x9d\x0c\x42\xd0\x92\xaa\xdc\x8c\x18\x04\x69\xa4\x61\x2c\xd8\x80\x65\xc2\x92\xf9\xfd\x08\xa1\xae\x97\x6c\xb7\x68\x86\x70\x37\xa1\xdd\xac\xcb\xc3\xa4\xcb\xd4\x39\xd9\x11\x12\x51\x5e\x36\x12\x5f\x59\x88\x10\x56\x26\x65\x65\x09\x6d\x42\xc2\x4e\xb7\x2b\xfe\x52\x48\xd2\x84\x4a\xd9\xb7\x74\x2e\xb1\x15\x5a\xff\xc2\xf3\xfb\xea\x8a\x7b\xc4\x79\x9e\x4c\xd7\x9c\x4a\x72\xfb\x3a\x5a\x8d\x5a\xe2\xa4\xc0\xc1\x71\xa8\x79\x1b\x11\x34\x96\xea\xfa\x4a\x06\x2e\x67\xd2\x88\xce\x72\xb9\x08\x95\xd5\x3d\xb0\x59\x75\xda\x9a\xdf\x1b\x2b\x9a\xdd\x3b\x71\xd2\xd6\x3c\x27\xf7\x1e\x83\x43\x1f\x83\x7b\x6b\x4d\x6c\x70\x63\x56\x2d\x03\xe7\x46\xa3\x6a\x52\xed\xb6\x04\xe4\x80\xdc\xeb\x6c\x87\xfe\xf0\xb1\x3d\x2d\x44\x4f\x21\xaf\xfa\xda\x00\xbc\xd3\xd1\xc3\x40\xd3\x83\x43\xbf\xea\xf0\xda\x19\xea\xc7\xd6\xac\xe4\x2d\x77\x98\x9b\x37\x46\x9c\xba\x9d\xc1\x8d\x69\xcd\xcb\x00\xad\x9b\xad\x73\x68\x44\x10\xc2\xe5\x4e\x37\xa7\x24\x75\x37\x74\x0b\xc0\xde\x5f\x95\x76\xa9\xd6\x82\x1d\x2d\x68\x14\x23\x5c\x62\xe7\x0a\xfc\x8a\x56\x3b\x03\xa3\x96\x5a\x29\xad\xae\x9a\xab\x6f\x7d\x23\x6a\xef\xbb\xab\x29\x98\xf3\x1a\x0b\x6e\xcd\xd4\xd8\x44\x40\xca\x1d\xf9\x35\x6f\xec\x76\x7a\x67\xad\x27\x08\xf9\x3f\xc9\xd8\xcf\x59\xc2\xb8\xde\x7d\xdd\x28\x7b\xd5\xb0\x26\x6e\xa8\x0b\x1f\xca\x78\x9f\x35\xef\xf2\x18\x3b\xc8\x2a\xa1\x47\x53\x2a\x07\x49\xa5\xfc\x94\xc5\x14\x58\x16\xd3\x96\x2c\xa5\x80\xea\xed\xef\x7f\x75\x17\x73\xc8\xbe\x15\xa3\x71\x74\xf4\xd8\x21\xf8\xdf\xf7\x14\xff\xa7\x7b\xd9\x63\xf4\x8e\xbf\x4b\xa6\x69\xc2\xae\xca\x52\x37\xbf\xd1\x1e\xd9\x95\xca\xd5\x87\xd5\x6e\x98\xdb\x46\x7b\xd8\x83\x39\x15\xcc\x9c\x12\xe2\x94\x6d\xa7\x68\x48\xd1\x82\x32\x6e\xb2\x1e\xba\x0b\x42\x48\x6e\xaf\x62\x8b\xe1\x13\xdf\x8a\x8f\xe7\x79\xb2\xf4\x30\xa9\x5c\x37\x34\x47\x2a\xaf\x09\xa8\x66\x7c\xb2\x9d\xe1\xc8\xea\xbd\xb7\x84\x41\x9e\xd9\xee\xac\x33\x75\x36\xb9\x67\x99\x7d\xbd\xa4\x3c\x1a\x1b\xf7\x0c\x04\xcd\x8a\xd5\x11\xcb\xd8\x8c\xa2\xc9\xd7\x15\x27\x3d\x92\x34\xf8\xa9\xa1\x21\x1e\x9a\x65\x8c\x53\x41\xeb\x94\xee\x0c\x63\x58\xee\x53\x3e\xd2\xde\x4c\x71\xdb\x76\x04\x2b\xbb\x5c\x26\xd8\x51\xa9\xe4\x67\x24\xc7\x43\x41\xbe\x92\xb9\x87\x2a\x9d\x1a\x52\x3e\x12\x5c\x2d\x5b\x75\xdc\x24\xb2\x46\xab\x15\x65\x71\x95\x75\x23\x75\x2d\x3a\x4a\x90\xb0\xbd\xf4\xf3\xa0\x6a\xd8\x98\x4d\x4a\xb8\xd9\x7b\x88\xa0\x79\xd6\xe6\x2e\x44\x0a\x63\xb5\x54\x25\x90\x19\x35\x02\x90\x4b\x4b\xc9\xba\xca\x6a\x86\x9e\xc4\x3a\x4e\x63\x74\xdd\xc8\x4a\x4a\xc7\x6e\xcc\x22\x2a\x5e\xca\x41\xa0\x5a\xbd\x78\x18\x00\xed\x74\x18\x55\xea\x59\xec\x96\xab\xdd\x1d\x92\x51\xa3\xea\xd3\xac\xe7\x70\x53\x4a\xce\x57\x1a\xfb\x43\x2e\xbf\x78\x04\x19\xe1\xa6\x45\xc3\x5a\xf3\x32\xc7\x47\xcb\x0e\x52\x29\x2f\x3d\x2f\x54\x56\x4f\x9b\xd5\x56\xf8\xd3\xe3\xf4\x8e\x3f\x57\x38\x23\xd9\x5c\xad\x7c\x72\xe0\x8b\xa1\x71\x10\x2c\x8e\x78\x74\xf4\xa9\x28\x10\x20\x84\x81\x69\xdd\x47\x7b\x5e\xd9\x03\x04\x0c\x43\xfe\x60\x3e\x09\x53\x74\x12\x41\x6e\xee\x72\x2e\xa8\x87\x87\xc9\x83\xc5\xd4\x72\x80\xe4\x4f\x9e\x39\xc8\x7b\xe6\x2e\xd4\x6a\x15\x9b\x6b\xab\x62\x3a\xf0\xe6\xb0\x4d\x51\xdc\xa4\xad\x90\x93\x95\xc0\x67\x69\xba\x11\x28\x1b\xea\x5c\x83\xc4\xe6\x43\x97\x79\x46\xe7\x59\x4e\xa5\x81\xaf\x20\x17\xd5\x6a\x61\x9d\xce\x8e\x0b\x14\x66\x89\x95\xa1\x8a\xcc\xb1\xb9\x11\xad\x15\xc3\x93\x34\x41\xd7\x48\x10\xd6\x37\xd0\xa9\x87\xf5\xea\x7b\x2e\xe8\xa2\x47\x71\xe9\xb9\x43\x00\xbb\x58\xa8\xe6\x81\x92\x67\x59\x96\xd2\x88\x79\x55\x0e\x77\x90\xac\x1e\x56\x0b\x7e\xcd\xc5\x20\x9d\x49\x79\x7b\xd7\x49\xfd\x84\xa6\x6c\x3f\x9d\x71\x4c\x38\x76\x67\x6c\x28\xdd\x37\xa9\x45\xad\xfa\xe6\x66\xdc\xa3\x6e\x76\x20\xaa\xd3\x67\x29\x4b\xd4\x0d\x8a\x1b\xe7\x17\x85\x47\xb5\xe6\x38\xac\xb5\xa4\xb9\x7e\xba\x95\x69\x9e\x87\xd5\x2d\x81\xfa\x01\x46\xf3\xf8\xa1\xc9\xff\xd4\xce\x63\xad\x22\xd2\x3d\xcc\xd0\xa7\xb2\x6c\x02\x0c\x38\xde\x7b\x3c\xb2\x7b\x87\x5b\xf9\x5c\xa8\x2f\x28\x89\xe9\x60\xfa\xb6\xa9\x74\x5f\xcc\xa8\xcd\x6b\xb7\xf4\x0e\x09\xa1\xda\x20\xdf\xb1\x7a\xb6\xb1\xdb\xad\x5c\x02\x5e\x46\x96\xf2\xac\xbe\x1a\x8b\x4d\x75\xab\x28\x28\x31\x70\x2c\x5d\xdd\xec\x0c\x72\xae\x1a\x02\x19\x86\x0c\x97\xd2\x39\x89\x7b\x50\xe1\x86\x34\x12\x48\xb6\x59\xe9\x2c\x49\x5b\xdf\x2c\xa5\x73\xae\x66\x2a\x0a\xe3\xce\x94\x5c\xbd\x09\x36\x3b\xd1\x50\x69\x0a\x64\x27\x12\xb0\x1e\x78\xec\x25\xe3\x3d\x08\xed\x83\x5b\x0d\x89\x20\xfa\xf2\x11\xcf\x6e\x93\xcd\x41\xbe\xa3\x54\x51\xd5\x1f\x89\xea\xc5\x24\xba\x20\x3d\x26\xc4\x9a\xbd\x67\x31\x15\xf7\xb4\x5b\x91\xdd\x66\x2d\x53\xe5\xa0\x9d\x18\x7b\x31\xa2\x63\x66\xfd\xfa\x31\xdb\x0e\xa5\x1a\xb2\x37\x58\x5b\x4f\xd4\xbe\xd4\x87\xdd\xe6\xd4\x3b\xd5\xc4\x7b\x81\xce\xd8\x39\x61\x2b\xf6\x1d\x20\xb5\xf7\x51\xeb\x02\xa7\x94\xf8\x70\xf5\x25\x8e\x22\x89\xc9\x94\x76\xbb\xda\xce\x95\xe6\x45\x92\x31\x82\x02\xbf\x77\xda\xf3\x51\x4d\x35\xaa\x4e\xd4\x1b\xd7\xbc\x36\x49\x1c\x6e\x94\xa4\x2f\xd0\x1d\xd4\x26\xfc\x97\xca\x4c\x31\xa2\x95\xfd\xe2\x7a\x74\x43\x25\xb3\x06\x1a\x64\x75\x78\x53\x19\x36\x92\x88\x7a\x0e\xc4\xea\xf6\xa9\xd2\xc9\xff\xbe\xa3\x78\x37\x9a\xdb\x75\x41\xbd\xdf\xc7\x7c\x02\x46\x97\x6c\x54\xb9\xa8\xd4\x24\xbb\xa0\x7c\xbd\x12\x3b\xc2\x03\x9b\xa8\xcc\xd3\x76\x84\xb5\xab\x8d\x01\xda\x6b\x76\xd7\xcc\xb6\x55\xa6\x34\xd2\xc9\x6e\x11\x5c\xb3\x8c\xb3\x82\x4e\xbc\x63\x34\x51\x4f\x06\x65\x34\x07\xde\x2e\xc4\xed\x56\xa6\x19\x90\xce\xd0\x3e\xd8\xb6\x66\x03\xb0\x51\xfd\x34\x65\x9a\x9d\xb6\xd6\x85\xb2\x66\x76\x0c\xc8\x60\x80\xbe\x94\x54\x2f\x6e\x12\x09\xb5\x9f\x62\x4c\xf4\xb4\xea\x2d\x78\x5d\x50\xd7\x15\x9e\x4d\xae\x8e\x1f\x55\x77\x1c\x1a\xf9\xf0\x79\x43\x75\xbe\xa0\x35\xd7\x86\x2b\xa9\xbc\x03\xc8\x3d\x44\xee\x26\x7a\x23\x1a\xf9\xa1\xf9\xec\x06\xc6\x48\x41\xac\x0a\x2a\xd8\x91\x5d\x23\x97\x4f\x85\x32\x59\x71\xcd\x76\xb9\x33\x1f\xfa\xf8\xd9\x31\x18\xae\x8f\x5c\xf8\xc0\x30\x57\xab\xaa\x75\x30\x41\xb6\x32\x64\x65\xe5\x16\xb2\xb6\x58\xea\x07\x0c\x9e\xd8\x84\x2a\x25\x78\xeb\x20\xd6\x7c\x44\x6a\x8b\x10\x66\xf8\x11\xc3\xfc\x57\x13\xf1\xc5\x2d\xda\xce\x03\xec\x7a\x4a\x82\x16\x2f\x78\x6e\x2f\xaa\x2a\x34\x3c\x79\xf4\xa8\x27\x64\xf7\x4a\xa7\xb6\x8f\xb6\xd3\xf1\xf0\xd0\x95\x78\x98\xd5\x26\xc9\xcb\x76\xd6\xd0\x95\xb3\x7c\x33\x63\xa8\x24\x73\x56\x46\x51\x22\xc5\x32\x0e\x32\xcd\x35\x90\x32\xcc\xbf\x72\x20\x96\x55\x3e\xbf\x34\xc2\xb7\x9b\xb2\x24\x18\x92\x9d\xf3\x34\xc7\x1f\x9f\x6c\x5d\x8b\x71\x27\x30\xe7\x3a\x1c\xc7\x75\x6f\x56\xe3\xdc\xbd\x0e\xe7\x78\x03\xb3\x16\x2e\x95\x28\xcf\x85\x74\xeb\x52\x5c\x8e\x4b\x6b\x70\x24\xb6\x1d\xd9\xad\xfb\x76\xc5\xbc\xf1\x75\x54\x89\x00\x09\x13\x91\x70\xd7\x6a\x44\x22\x5a\x7c\xa5\x4c\x5a\x6e\x29\xf1\xee\xa4\x4e\xc4\x0b\x4e\x2f\x30\x86\x17\x94\x6c\xea\xae\x2d\xc0\x75\xe2\x42\xa8\xd6\xdf\x0e\x33\xe9\x66\x47\x80\x7a\x1d\xad\xa4\x62\x90\x7b\x62\xbc\x95\x63\x14\xee\xa9\xc3\xd2\xab\x1d\x50\x95\xea\x40\x03\xb2\x97\xf7\x73\x19\xc3\xb0\xb5\xa6\x57\x9b\xf9\x4e\x79\x53\xd2\x68\x39\xa5\x63\x89\x6b\x4a\x98\x17\x0c\x02\x0c\x6f\xe8\x43\x8e\x08\xb5\xf7\xbf\x79\x96\x8f\xaa\x4f\x0f\x2d\xd7\x49\x8f\xd1\x82\xd3\x18\xe1\x10\x7d\xfc\xf8\xfe\xc7\x17\xaf\x5f\x7c\xfc\xe9\xc5\xbb\xf7\x2f\xbe\xff\xf8\x11\xc1\x73\x4a\xc6\x68\xb6\xa0\xb3\x6b\x1a\x23\x40\x71\x52\x08\xf6\x4c\x7c\xd2\x3c\xcf\x72\x04\x68\x9e\xcd\xd6\x85\x8c\x91\x5f\xbf\x26\x45\x32\x4d\x29\x02\x94\xd3\x3f\xd6\x49\xae\xf2\xde\xad\x22\x16\xcb\x4f\xa5\x47\xa5\x31\x9a\x0c\xff\xeb\xf8\xc9\x93\xff\x3a\x78\x72\xf0\xf4\x60\x4a\x39\xa7\xf9\x41\x34\x2d\x78\x1e\x29\x86\x22\xbb\xa1\xb9\x98\xca\xde\x7f\x1d\xc8\x4c\xdf\xcd\xb2\xd5\x7d\x9e\x5c\x2d\xf8\xc1\x9b\x94\x5e\x1d\xbc\x2c\x32\x46\xd9\x81\xf7\x2e\xcd\xa6\x59\x5c\x5c\x67\x09\x3e\x38\x36\xb1\x7d\x3f\x38\x3d\x5a\xe5\xb4\xa0\x8c\xcb\xc2\xb7\x74\x5a\x24\x9c\x1e\x2c\x38\x5f\x15\xe1\xf1\xf1\x55\xc2\x17\xeb\x69\x6f\x96\x2d\x8f\x67\x45\x91\xb0\x4f\xc5\xf1\xa7\xa2\x90\x59\xd3\x64\x46\x59\x41\x0f\x5e\xbf\x7c\xff\x5f\x07\x4f\x8e\xc5\xa4\xbd\xa3\xe4\xfb\x88\xd3\x1e\xcb\x6e\x3d\x0c\x9f\xc4\x38\x33\xa9\x2d\x29\x50\xf7\x1d\x85\xf7\x32\x42\xd2\x38\xd4\x3d\xe8\x76\xdf\x29\x2e\xf9\x67\xba\xcb\x65\x3d\x7c\xe3\x26\x99\x7b\xd5\x0c\x5a\x24\xe7\xb5\x3b\x0d\x0a\x89\xc4\x22\x17\xa4\xa8\xc2\xa2\xf1\x7b\x3a\x21\x1c\xf2\x07\x2f\x09\xcb\x2a\x3e\xa9\x5b\x1a\xdb\xed\x7b\xf5\xd1\xf4\xb4\xe7\x78\xc1\xa9\x9b\x73\x49\x0f\x7a\x2d\x28\x96\x75\x3a\xc6\xd6\x5d\x9a\x78\xc9\xf5\x5f\x99\x76\xf1\xf1\x27\x3a\x21\x0c\xe4\xcd\x60\x7d\xc4\xbc\x6f\x95\x71\x21\xa6\x8b\xae\x08\xf9\xdc\xcb\xb4\x3d\x46\xe2\x51\xbc\xdd\x1a\x12\x17\x91\x4c\x40\x14\x62\x47\x54\xf9\xa6\x53\x0e\xe2\x32\x65\x00\x9c\x42\x34\x4e\x27\x62\x0f\xc9\x71\x59\x96\xf0\x96\x12\xf4\xdd\x55\x9a\x4d\xa3\x14\xc1\x4f\x0f\x18\xd6\xba\x76\x0c\x99\x35\xad\x55\x37\xdb\x4c\x79\x6d\x0e\x46\xde\xd2\x3f\xe3\x4d\xe1\x31\xfe\xd1\xf6\xbb\x2c\xfb\xf7\x5c\x13\x64\xc0\xc7\xd9\xff\x0f\xdc\x9e\xfc\x1b\x2e\x4c\x6a\x1b\xe7\xc3\xfe\x4b\xfe\xb7\xa6\x73\xbb\x07\xe8\x0f\x5a\x93\x19\x0b\x88\x8f\x5f\x40\x97\x47\x60\x48\x1b\x3e\x88\xda\x1e\x85\x10\x7b\xae\x3b\xd6\xdd\x62\x88\xfd\xa9\x58\x0b\xf2\xe9\x19\x44\x3f\x40\x7a\xab\x76\xe6\x5d\x39\x0d\x75\xb9\x9d\x0c\xf8\x17\x5d\x56\xfc\xd9\xab\xaa\xb6\xba\x91\xfd\x72\x8d\xf4\x2a\xb7\x2c\xaf\x29\x39\xfe\x50\x3c\x81\x0f\xc5\x93\xe3\xab\xea\x44\xec\x69\xd3\xc6\x5d\x74\x6e\x95\x26\xdc\x7b\x2d\x56\x34\x41\xc8\xdc\xbe\xaf\x59\x11\x75\x89\x72\x26\xc1\xa4\xd1\xbd\x3c\x96\x00\x36\xce\xd4\x1d\xcb\x5c\x3b\xfc\xac\x8c\x88\x04\xe4\x97\xff\x1b\x12\x7d\x48\x6b\x04\x59\x0b\xfb\x6f\xab\x58\x7a\x7b\xf0\x13\x75\x8c\xf3\x2a\x0f\x52\x9d\x4e\x35\x3b\xd2\xcb\x86\x9e\x33\x1f\x76\x67\xcd\x05\xf7\xd1\x82\x33\xde\x13\xd4\x0c\xb9\xc7\xf1\x06\xfb\xe4\x59\x8d\x52\x28\x55\xc7\x5c\xf6\xba\xbd\x9b\xa9\x9e\x28\xcb\x28\xab\x27\x75\x0f\x50\xf9\x65\x55\x66\x51\x2a\x46\x25\x1a\x55\x3f\x55\xe2\x5c\xd9\x30\x3f\x72\x11\xa2\x72\x45\x61\x34\x5f\xde\xae\x16\xc7\x2a\x0f\xa5\xf5\x8f\xb2\xac\xcb\x08\x1b\xb1\xf1\x5b\x3a\x09\xcd\x18\x67\x15\x3e\x48\xaf\x51\x19\x36\x5a\x0a\x63\x60\x9c\x40\x36\x4e\x76\x1c\xb0\x70\xd8\xd8\xdb\x8a\x4f\x45\xa6\xea\x28\x57\x9e\xc3\xea\x8d\x4b\x56\x56\x96\x62\xcb\x78\x54\x0b\x87\xb5\x5d\x82\x61\x3b\xcd\x99\x9c\xe6\xac\x9a\xd9\xb7\xe6\x6c\x12\x2b\x2c\xd1\x77\x44\x9e\x52\xcf\x66\xaa\xb2\xb8\xad\x1b\xee\x76\x50\xa0\xf6\x43\x1d\x4c\x04\xf9\xb7\x3d\xca\x54\x87\xe4\x0e\xf8\xd9\x5d\x6a\xf0\x8a\x92\xe3\xce\xf1\x15\x7c\x4f\xeb\xde\x54\x24\x7a\xfd\xf0\xe0\xc5\x93\x56\x43\x11\x87\x45\xd6\x64\x7a\xbb\x95\x7e\x26\xaa\x08\xc7\xca\x3e\x23\x59\xe5\xf7\x30\xcc\x9d\x63\x78\xde\x5c\xf8\x5c\x2f\xfc\xcf\x72\xe1\x53\x27\xe4\x18\x2d\x31\xd7\x50\xc9\x94\x8d\x08\x13\xd8\xa0\x7c\xd6\xe6\x2d\x3e\x6b\xf3\x71\x3a\x19\x1a\x23\x25\xd0\x0e\xc3\xa5\x8a\xad\xda\x38\x50\x07\xe1\x51\x51\x39\x8e\xa1\x10\xe1\x30\x92\x54\xa6\xa8\x2c\x98\x6c\xf3\x5d\x2a\x61\x6d\xc8\x76\xa9\xab\x96\xa4\x8d\xdc\x5f\x62\x4b\xc2\xcd\x8a\x14\x1c\x79\xc2\xae\x5e\xd1\x1b\x9a\x0e\x33\xd7\x7b\x6f\x10\x66\xdd\x40\x4b\x7a\x4d\xbf\x1d\x16\x4b\x37\x6e\xf9\x30\xd3\xa2\xbb\x6b\x41\x27\x6b\x35\xb6\x0a\x0a\x5f\x12\xe5\x73\x29\x29\x2d\x19\x6c\x67\x23\x33\x48\x20\x52\xbc\xaa\x5a\xdd\x87\x84\x24\x8a\x78\x98\x11\x51\x1e\x41\xa1\x80\x19\x49\x60\x4d\x66\x0d\x4a\x33\xac\x5d\xef\xca\xcc\xf5\x2e\x39\xf6\x71\x6d\xec\x61\x45\xd4\xaa\x8a\xc7\xbe\x64\xf8\xe6\xdb\xed\x4a\xd6\x9d\x12\x26\x2f\x8b\xa5\x18\xe6\xd6\x91\xaf\x17\xc3\xcc\x59\x3f\x42\x46\x2e\x08\xf5\xd6\xd2\xc5\xd3\x82\x2c\xec\x44\x7e\x4f\xa1\xc0\xb0\xb6\x2b\x6b\x01\xd9\x38\xde\x59\x59\xa9\xb3\xb2\x16\x62\x1f\x94\x07\x42\xab\x4e\xa7\x2a\x18\x0b\x2e\x3c\xc5\x36\x3c\x93\x0e\x1a\x24\xb0\xaa\x6c\xd5\xa6\xd2\xd2\x1b\x91\xa5\xac\x50\xa8\x2c\xe1\x19\x25\xc7\xe3\xa7\x47\xff\x9c\x1c\x5f\xc1\x6f\x94\x1c\xff\x6b\x59\x1c\x1d\xc3\x57\xb4\xf6\x64\xcc\x2f\xd4\xb1\x0e\x3a\x52\x27\x26\xaf\xb2\x5b\x9a\x3f\x8f\x0a\xaa\x99\xb9\x1f\x77\xcc\xbc\xbf\x6a\x3e\x13\xe1\xf8\xd7\xfe\xca\xfa\xa2\x15\x74\xce\x0c\xd0\x33\x0a\xbf\x54\xde\x9b\x64\x1e\xf2\x1b\x55\xfe\x39\x38\x96\xd6\x55\x3c\xe4\x4e\xc3\x7e\xa5\xf5\x37\x6e\xea\x3e\xcf\x37\x7c\xec\xd7\xcc\x6b\xd1\xd1\x11\xc2\x23\x16\xfe\x28\xa4\x58\xf5\x46\x42\xc5\x65\x58\x0f\xcc\x9d\x4e\xd3\xe1\x77\x95\x86\x47\x8e\x0b\x6b\xe2\x24\x48\xef\xec\xbf\x52\x1c\xba\xe9\xa2\x79\x4e\x59\x0c\xca\x7e\xe7\x8f\xf6\xfd\x7f\x8f\xfc\xd4\xe2\x24\xa2\xee\x1b\xa2\xf5\xf5\x04\x59\xf7\x98\xbb\x26\xbc\xfa\xe3\x57\xa5\x8a\x78\xd8\x5d\x92\x35\x75\xad\x0d\x5d\xf3\x5d\x99\x1f\xa9\xa3\x51\xe0\x62\x77\x1f\xd1\xd0\x63\x4a\x1a\xca\x41\x6f\xd5\x72\x27\xf8\x1b\x25\xf7\x54\x6a\x4d\x46\x42\xbc\x5e\xdd\x85\x68\x75\x87\xe0\xef\xf5\xe8\x65\x11\xa2\x65\x81\xe0\x2f\x8d\xdc\x34\x9f\x09\xce\x10\xfd\x8f\xf3\x54\xc5\xef\xce\xdc\x1f\x7b\x47\xe3\xe8\xe8\xb3\xf4\x4f\xcd\x5a\x95\x7d\xe3\x60\xd2\xe3\xd9\x2f\xab\x95\x45\x5a\xc8\x5d\x94\xd1\x2f\xd5\x48\x9f\x4a\x54\xec\x77\xf9\x38\xb3\x78\x29\x86\x44\x45\xd7\x19\xb9\x7f\x50\xf2\x3b\xf5\x36\x8e\x63\xa8\x98\xa6\xd1\x3d\x0a\xff\x5e\x73\x70\x15\xaf\x73\xed\x46\x4a\xc4\x0b\x5c\xb8\xca\xb3\x35\x8b\x8f\x56\x59\x91\xa8\x84\xbf\xb5\x27\x1c\xdd\xed\x4f\xba\xdf\x49\x2a\x92\xcf\x54\x46\x4e\xb3\x3c\xa6\xb9\x4a\x96\x9f\x47\xd3\x8c\xf3\x6c\x89\x76\xa3\x8e\x52\x3a\xe7\x47\x79\x14\x27\xeb\xa2\x2d\x59\xea\x3e\x1e\x48\xbf\x4d\x62\xbe\xa8\x25\x08\x80\x3b\x11\x2d\xf9\x5a\x60\xca\xca\x76\x63\x5a\x0a\xf3\x6c\xd5\x0c\xef\xed\x89\x48\xdb\xdb\x0d\x91\xb8\x0b\xbe\x8a\x59\x46\xf9\x55\xc2\x64\x9a\xfa\xac\x0d\xa5\x8e\xaa\xba\xac\x23\x9c\x8e\xe8\x18\xd3\xe0\x55\x14\xc7\x09\xbb\x92\x49\xfa\xbb\x06\xd1\xc4\x55\x20\x4d\x8c\x03\xd3\x44\xd9\x51\x58\x46\xc5\xf5\x0e\xd2\xd4\x23\xef\x9d\x48\x8b\x28\x0b\x2a\x80\x8a\x2f\xd9\x63\x95\x23\x61\x47\x2a\xde\x94\xb8\xab\x87\x13\xe6\x8e\x98\x48\xae\x82\xaa\x2b\xe2\x4b\x74\x40\xfc\xe5\xd9\x4a\xfc\xc9\x4d\x45\x68\x9a\xdd\x1d\x15\x8b\x28\xce\x6e\x15\x00\x4e\xef\x78\x2d\x62\x96\xa5\xeb\x25\x3b\xba\x8a\x56\xb5\xb0\x10\xe8\x76\x22\xdc\x96\xe8\x68\x27\x46\x3a\x83\xb2\x9d\xad\x82\x62\x99\xf2\x48\x45\xa6\x52\x09\x78\x54\xac\xa2\x59\xc2\xae\x9c\x26\x29\x1f\x86\x6e\x1b\x79\x9e\x5d\xd3\x9d\x08\xb7\xc2\xdb\x2c\x8f\x6b\xa0\x96\x99\x18\x7c\x35\x50\xf2\xf3\x28\x9b\xcf\xa5\xd7\x9c\xbf\x51\xc8\xd6\x3c\x4d\x18\x95\xa9\xfa\xdb\x4d\xb6\x71\x55\x05\x2b\x9a\x17\x2b\x3a\xe3\xc9\x8d\x2a\xe5\x84\x8f\xb2\x3c\x11\x88\x76\x87\xc2\xbf\xb4\xa7\xdc\xab\x14\x9e\x47\xac\x98\x67\xf9\x52\xc7\xb7\xc7\x1a\x38\x3b\xf1\x7b\xa0\x1c\x7d\x76\xe2\x93\x26\x2d\x74\x63\x6b\xc4\xf0\x86\xe6\x3c\x99\x45\xe9\x51\x94\x26\x57\x9a\x0e\xce\x53\x7a\x77\x34\x8d\x8a\x44\xaf\xd4\x62\x11\xad\xe8\x91\x5a\x47\x32\x46\x4c\xa1\xf8\x7b\x95\x27\xb1\xcc\x21\x3e\x2a\x7c\x91\xa1\x3c\xbb\x6d\xc4\x34\xd1\x4a\x46\x72\xba\x5c\xa5\x11\xa7\x22\x7f\xd1\x16\xaf\x4a\xb9\x49\xd1\x9a\x67\xcd\xec\x32\xae\x96\xb5\xc2\x72\x4b\xc3\xab\x98\xfb\x9d\x98\x69\xba\xce\x77\x22\x8b\x55\x4e\xa3\xd8\xc1\x5d\x89\x0d\xee\x5a\x74\xd6\x8e\xa9\xc6\x8d\xba\xdf\x8d\x32\x15\x95\x8e\x2d\xf0\x5f\xa9\xab\x67\xa8\xdc\xfe\xef\x9a\x79\xf3\xe6\xbb\x27\xb5\x6b\x2d\xf2\xc9\x40\x05\x4c\x2a\x64\xf1\xfe\xd7\xa0\xb8\x14\x5d\xab\x47\x2f\xa4\x25\x67\x5d\x05\x8a\xb9\xd8\x8b\xff\x4a\xb5\x1e\x71\xc7\x33\x46\x62\x72\x25\xaa\x52\x65\x86\x2f\x5d\x2d\xd7\xaa\x6e\x5a\x69\x71\x73\xab\x84\x8d\xe9\x64\xbb\xfd\x87\xe4\x44\x75\x97\xa3\x51\x8b\xa2\x39\x1a\x45\x1e\xc7\x8e\x8a\x2e\x44\xa8\xcb\xbb\x51\xc8\x5d\xb5\x9d\xf5\xa3\x20\x80\xff\xf3\x91\x37\x20\x24\x0f\x33\xfc\x12\x07\x68\x94\xf4\x56\xf0\x61\x35\xc1\x87\x36\x15\xe7\x54\xcf\x43\x75\xf7\xd9\xf2\x80\xfb\xd9\xbe\xea\x92\xec\x5f\xa9\xc7\xe4\x41\x64\xe9\x5e\x41\xa0\x5a\x4a\xd6\x0f\x19\xf1\xed\x96\x7f\x6b\x15\x08\xf2\x0c\xd4\x86\x86\x8e\x85\x54\xcd\x81\x25\xc7\x43\xf6\x0d\x97\xc6\x2a\xb9\x79\xa4\xac\xe2\xa8\x2a\x79\x9c\x3b\x9c\xdb\x97\x18\x61\xd3\x35\xa9\x0d\x96\x1a\xfb\x46\x89\x47\x3c\x03\x69\x9e\xc5\x12\xc3\xa7\xa5\xb1\x0a\xb2\xaa\x71\x9e\x67\xcb\x96\x1a\xf4\xe1\x06\x35\x13\xd4\x70\xac\xea\xb6\xce\x9c\xec\x32\xb2\xf3\xa4\x9b\x41\x23\xfd\x8c\x1b\xd6\x9e\x8d\x2e\xe0\x28\x30\x73\x87\xde\xd8\x17\xae\x58\xe3\xc1\x2d\x79\xda\x5c\x8b\x91\x32\x35\x06\xf4\x3a\x5a\x21\x65\x1a\x8c\xde\x51\x55\x76\x54\xeb\x4e\x88\x9e\x9a\x03\x53\x9d\xf1\xf8\x5f\xde\x28\xfc\x25\xd9\xbe\xc4\x8c\x7b\xa3\xf0\x62\x1b\x9c\x6d\x4f\xfa\xd8\x1b\x85\xcf\xd3\x68\xb9\xa2\x31\x56\x10\xbe\x3a\x56\x62\x19\xc3\x23\xdd\x39\x73\x8e\xd8\x18\x22\xbc\xe1\x8b\x3c\xbb\x95\x0a\xc5\xf7\xf7\x2b\xfa\x22\xcf\xb3\xdc\x43\x2f\xd9\x4d\x94\x26\xf1\x41\xc4\x05\xa9\xe5\x07\x3c\x3b\x50\xe4\xee\x80\x65\xec\x48\x4e\xc8\x34\xad\x5c\x12\xf5\x3e\xb0\x97\xec\x40\x32\x67\x22\xeb\x94\x1e\x98\x2c\x20\x0b\x44\xa2\x4d\x07\x8a\xca\x14\x07\xcb\x75\xc1\x0f\x16\xd1\x0d\x3d\x88\x0e\xc6\x8d\x39\x9e\x78\xf8\x60\x49\xf9\x22\x8b\x7b\x08\x97\x5a\x78\x65\xd2\x1d\x4b\x2e\x7f\x33\xf9\x9b\xc8\xdf\x88\x93\x75\xa7\x83\x32\xc6\xb3\xf5\x6c\x51\xf0\x28\xe7\x28\x71\xee\x59\x98\x0f\x6d\xad\x2b\x08\xe6\x5a\x91\x97\x94\x93\xcd\xeb\xec\x73\x88\x8e\x96\xd9\xe7\x23\x04\x42\xb2\x39\x5a\x16\x47\x08\xde\x84\xe8\x28\x3b\x42\xf0\x1b\x9d\x5e\x27\x3c\x44\x47\xb7\xf2\xe3\x08\x95\x50\xf0\xbd\x66\xc0\x2b\x84\x1b\x2a\xc0\x99\x7c\x2c\x22\x95\x94\x74\xc6\xbb\xe8\xbd\xd9\x91\x45\x0b\x0b\x8e\x37\x8c\x93\x19\x17\x9d\x4a\xf9\x78\xc6\x27\xc3\x69\x4e\xa3\xeb\x12\xa9\x7a\xe5\x74\xf3\x4e\x07\x2d\x8b\x1f\xef\x57\x0b\xca\x0a\x55\x4c\x20\x13\x27\x52\x08\x93\x25\x7b\xcb\x42\x8e\x05\x8d\xaf\x28\xc2\xd0\x2c\x7d\x14\xad\x56\x29\x3d\xe2\x79\x94\xa4\x82\x23\x15\x0c\x90\x05\x24\x46\x52\xa6\x23\x35\xca\x6b\x4e\x18\x87\x98\x93\x9c\xc3\x9c\x93\x8c\xc3\x8a\x93\x84\xc3\x82\x93\x88\xcb\xb5\xb1\xe4\x64\xc3\x32\x79\x63\x2b\x4d\xc3\xb1\x34\x49\x8f\xf2\x48\xda\xd0\x83\xf6\xa1\x4e\x63\xa3\x4f\x68\x79\xa8\xce\x2d\xa1\xe9\xad\xe8\x0b\x21\x64\xcd\x47\xd5\x50\x77\x69\x18\xf3\x2e\xc5\x65\x09\x37\x8d\x2a\x67\x59\x9a\xe5\x47\x51\xfc\x69\x5d\xf0\xc7\x56\x5a\x2b\x63\xaa\xad\x46\x6a\xcd\x47\x31\xef\xa2\x55\x9e\x30\x55\xb7\xac\x78\xca\xc9\xf1\xf8\xe8\x43\x31\xe9\x7a\x3d\x3c\x72\x4f\x22\xae\x78\x4d\xe9\xc9\x47\xbc\x2e\xba\x86\x08\x55\xc4\xf2\x9e\xd7\x6c\x5a\x8c\xc8\x3a\xe5\x70\xc5\xb1\xf3\x16\x98\x9b\xed\x9e\x7b\x52\x8d\xa3\xa6\xe5\x96\xc3\x8b\xc6\x20\x08\x29\xe1\x0b\x9d\xd7\x94\xef\xf0\xf8\x5f\x22\xf3\xb1\x71\xa2\x5a\x19\x64\x0a\x82\xe8\x8e\x81\xcc\x2e\x6a\x96\x22\x48\xb2\x8c\x04\x42\xd5\x4f\x82\xc5\xf2\xe1\xdd\xbb\x07\xf2\x88\x59\xab\x1e\x32\x29\xe1\xba\xd1\x70\xc9\xea\x64\x79\x42\x19\x57\x5c\xe6\x23\x67\x70\xa7\x9c\x99\x45\x85\xbf\x87\x84\xcc\xf9\x76\xbb\xe0\xa3\x0a\x71\xde\x34\xab\xb6\xeb\xef\x4b\x03\xe7\x78\x46\xad\x0a\xe9\x0a\x59\xcf\x46\x39\x75\x3d\x6f\xab\x2b\x79\x44\x07\x77\x2b\x4b\xdc\xee\xe9\xda\x64\x9c\x53\xdd\xbb\x46\x75\xb7\x79\xc2\xc5\xea\x5e\x66\xf1\xa3\x17\x62\xad\x4c\xdb\x9a\xd8\x6e\xed\xca\xec\x74\x14\x7d\x39\x24\x64\x25\xd7\x8a\x5e\x22\x9f\x1a\xed\x58\x17\x42\x52\x93\x5a\xcd\xc7\x36\xc3\x2d\x62\x5a\xf1\x3a\xfb\xbc\xd3\x84\xed\x56\x4f\x35\x11\x53\xed\xb4\xe1\x3d\x27\x9b\x2f\xad\x03\xe3\xcd\xe5\xf8\x5f\x92\xc6\x1e\xd9\xd5\xb0\x43\x07\x74\xe8\xb9\x14\x15\x50\x57\xae\xca\x44\x3a\xaa\x17\x14\x42\x8b\x28\x82\x44\x54\x8d\x14\x30\x56\x62\x21\xb8\xb9\x65\x8c\x5c\xc2\xa2\x8d\x3f\x3f\xa2\x8d\x6a\xad\x7a\x4a\xc1\xb1\x55\xd2\xd4\x56\x2b\x12\xf0\x51\xc2\x84\x6c\xb1\x67\x15\xdb\xa6\x34\x6d\x36\x2a\x72\x83\x34\x04\x79\xc7\xc4\xb0\x74\x6a\x2d\xb3\xaa\x83\xac\x2c\xe1\xed\xa3\xc7\x53\x91\x36\x55\x58\xac\xf5\x9f\x1e\x51\x52\xb5\xeb\xce\xf1\xb1\x83\x8e\xaa\x87\x74\xaa\x6f\xb1\x75\xa9\xef\x60\x32\xa2\xe1\x9a\x77\xb5\x1b\x7e\x39\xf1\x66\xce\x0e\x35\x72\xea\xa0\xce\x23\xca\x56\x1b\x49\x59\xc2\xc7\x2f\x34\xac\x72\x91\x3f\xcb\xb3\x34\x3d\x2a\x98\xe2\xcb\xcc\x41\xac\x10\x20\x7c\x08\x02\x5c\xdf\xad\x50\xb7\xc2\xc2\xd7\x8f\xad\x22\xbb\xa1\xb9\xae\x66\x4a\x17\xd1\x4d\x92\xe5\xbb\x1b\xa1\xc0\x35\x9d\x69\xb6\x88\x12\x26\xb5\x15\xb2\x9e\xa7\x9c\x6c\x94\x00\x7e\x95\x67\xb7\x28\x54\xdf\x4a\x89\x74\x23\x66\x57\x86\x8b\x45\x9e\xb0\x6b\x93\xca\xe8\x55\xe4\xa6\x6a\xd1\x5d\x17\xcd\xe9\x9c\xe6\x39\xd5\xda\x49\x40\x62\x8f\x4c\xe6\xf7\x47\xe6\xda\x9b\xc9\x17\xcd\xae\x11\x28\xbd\xa5\x8a\x91\xdf\x08\x90\xd4\x0c\x08\x8e\x70\x69\x81\x2a\x65\x81\x49\x6a\x40\x92\x42\xb2\x04\x57\xc2\xcb\x47\xa3\xcc\x53\x5e\x89\x81\xd2\xff\x9e\xb7\x8b\xbd\x62\x80\x3e\x73\xb2\x11\xd5\x84\x52\x50\x17\x5f\xa6\xdb\x7a\xc0\x9a\xd1\x71\x92\x53\x25\x50\x86\x63\x99\xa8\x76\x19\xa4\x24\xfd\x2a\x75\x62\x3a\xaf\xf2\xc4\x09\x8b\x52\x01\x73\xbd\xda\x19\x03\x91\xc3\x0c\x81\xac\x63\x9e\x8a\xaa\xbf\x00\xbe\x65\xe4\x45\x16\x3d\x52\xaf\xb8\x11\x4b\xae\xe9\x7d\xe1\x7d\xe6\x18\xbe\x6f\x35\x9b\x95\x3b\x30\xfc\xc0\xc9\x78\xc9\xe1\x46\xb0\x0e\x70\xcd\xe1\x0d\x87\xe7\x1c\xde\x71\xf8\xc4\xe1\x3d\x87\x9f\x39\xbc\xe5\xf0\x13\x87\x8f\x1c\x5e\x73\x78\xc9\xe1\x0b\xd3\xe0\x3e\xd1\xb0\x5c\xa7\x3c\x59\x29\xf7\x90\xaf\xdc\xe3\xc3\x6f\x8f\x02\x73\xe8\xfb\x59\x4e\x97\xa0\x69\x75\x81\x30\xb3\x62\x9b\x9a\xbe\xac\x9a\xbe\xac\xf6\xda\xc3\x61\x50\x3d\x07\x25\x8f\x73\x33\xf7\x38\x57\xe4\xd4\x08\x90\x8d\xfd\x89\x62\x41\xaa\x92\xe6\x00\x4d\x1e\xfa\x7c\xcf\x71\x75\x6d\xb4\x9c\xc0\x33\x4e\x7e\xe0\xbd\x79\x92\x72\x9a\x7b\x5e\xab\xe1\xf1\xce\x58\x94\x18\x4b\x58\x8f\xcf\x0e\xbf\x7d\xb9\x1a\xbb\x75\x0a\xf0\xed\x6e\x60\x2a\x1b\x9b\x75\xb1\xb0\xae\xb5\x38\xf7\x78\x55\x1a\x63\xa0\x25\x86\xf1\x04\xc3\x57\xf2\x98\x4d\x49\x39\xb7\x0f\x0a\x2b\x72\x87\xf8\x85\x13\xed\xbc\xe2\x8a\xf2\xe7\xd9\x72\xb5\xe6\x34\x56\xbe\x96\xf6\x89\x50\x72\xff\x30\x33\xf3\xa3\x14\x71\x7e\xe1\x38\x29\x7e\x8a\x7e\xf2\x7e\xe4\x78\xbb\xf5\xbe\xe2\xe3\x5f\xf8\xf8\x47\x3e\x99\x10\xf5\x17\x0f\x7f\xe3\x2d\x26\xbe\x0e\xca\xaa\xc3\xcf\xaf\x04\xce\x94\xd8\x61\x8a\x7f\xe5\xfb\x1e\x07\xde\x94\x18\x0e\x6f\xeb\xac\xa9\xba\x45\x20\xa1\xd8\x63\x4c\x89\x86\x2e\x63\x75\x28\x69\xad\xc3\xd7\x1d\x4a\x8f\xf7\x9e\xc8\xa8\xde\xb6\xbc\xd5\x7e\x9c\x6a\x6a\x92\x21\xfb\xe6\x19\xb7\x6f\x08\x7b\x12\x2e\x79\xc6\x95\xdf\xaa\x5b\xeb\xf9\x09\xc3\xa1\xaa\x5f\xea\x50\xe4\x25\x5f\x93\x28\x0a\x20\xb4\xff\x8a\xae\xea\xbd\x3c\x7e\xe4\xf0\x37\xd1\x43\xf8\x3b\x27\x9b\xaa\xe9\x61\x50\xd3\xd3\x9a\x7b\xcc\x48\xc4\x9b\xfd\xce\xe9\x68\x7b\xb4\x5b\xac\x84\xbf\x70\x72\xec\xfd\xeb\x43\xf1\x44\x9b\x7a\x6c\xe1\xc0\xab\x42\xde\xe8\x70\xfc\x2f\x0f\x4f\x9e\x7c\xc0\xd8\x15\x7f\x7e\xe7\xae\x65\xea\x4d\x24\x37\x30\x33\x15\x32\x2c\xb9\x92\x28\x4d\x6b\x09\x22\xec\x26\x18\x53\x08\x04\x07\x32\x49\x9b\x03\x8e\x7e\x95\xfe\x30\x10\x1c\xa0\xee\xaf\x82\xbe\x5b\x05\x94\x98\xa5\xed\x96\x39\xf8\xf1\x0f\x5e\xbb\x1e\x25\x09\xc8\x1f\x82\x55\x34\x24\xd4\xbd\x80\xce\x5d\xa7\xc7\xd5\xed\x8b\xed\xf6\x50\x61\xef\x2a\xca\x0b\xfa\x92\x71\x8f\x41\xe0\xe3\xca\xd0\x4a\x37\x8c\x76\x59\x85\x64\x7f\xe3\xe3\xdc\x22\x99\x0c\xc8\xb9\xfe\xc3\x99\x6b\xd6\x9c\x6a\x95\x8f\x1c\x06\x70\x18\x94\xc9\xdc\xfb\xbb\x44\x14\x46\x98\x65\xd2\xfe\xc2\xe1\x77\xee\x68\x46\x45\xf3\x1d\x90\x82\x45\x38\x5a\x16\x6a\xf7\x22\x84\x78\x8c\xc8\x5d\xaf\xd3\xf1\xdc\x8a\x6d\x9e\x69\x76\x87\x30\xd4\xda\x04\x4d\x98\x18\xef\x36\xce\x0c\x78\x0d\x28\x02\x95\x83\xa9\xbf\xe5\x5a\xd6\xfa\x10\x85\x51\x24\xe6\xaf\x7c\x8f\xd9\xd0\xae\x8f\x2b\x63\x10\xaa\xdd\x0b\xd4\xb5\xcf\xac\xd3\xa9\xef\x24\x39\x56\x6f\xa6\xe7\x92\x30\x53\xc7\x87\x71\x26\xba\x91\x10\x85\x3c\x49\xa7\x93\xe8\xfb\x71\x19\x39\xf4\x8d\xad\xf0\x61\x00\x29\xf9\x07\xf7\x12\x90\x90\x86\x69\xa7\x93\xea\xd7\x9a\x22\x91\x0d\xbc\x6c\xbb\x8d\xc4\xd0\x66\x95\xa7\xed\x31\x9b\x00\x1f\x27\xdb\x2d\x9b\x90\x74\xbb\xcd\x71\x59\x69\x95\x9b\x3a\xe2\x1d\xa3\x37\xd1\xa3\xda\xab\x36\xca\xf6\xcd\x1a\x93\x0d\xb9\x7c\x92\xe4\x48\xcf\xac\x08\xe1\x71\x30\x71\x44\xa0\x11\x0b\xd1\x77\x92\xe9\x74\x00\x75\x99\x31\x17\x0b\x7c\xad\x2b\xdb\x6f\xeb\xbd\xfb\x1c\x94\x55\x56\x8f\x78\x28\x2f\x59\x3c\x60\x77\x60\x11\xf9\x1f\xdc\xe3\x20\x95\xbb\xdb\x2d\x2d\xcb\x52\x8e\xe9\x3f\xf9\xee\x7d\xe0\x3d\x7b\x99\x22\xa3\x8e\xa7\xae\x11\xfd\x96\x8f\x82\xf0\x28\x08\xad\xc7\x0e\x93\x54\x7e\x49\xfb\xce\xbf\xa0\x7d\xaf\xec\x8a\x72\x41\x53\xb3\x1a\x1b\xc5\x71\xaf\xc8\x72\x21\x8d\x40\x0b\x97\x91\x8f\xb3\x71\x32\x99\x10\xae\xfe\x56\x9a\xf0\x9a\xde\xdd\xb1\x11\xb1\xd7\x0d\x7f\xa6\x1e\x86\x97\xe2\xe7\x07\xf1\xf3\x87\xf8\xf9\xa7\xf8\x71\x94\xdd\xa4\xee\x47\x4a\xbd\x0f\xfa\x57\x2e\x72\x72\x0f\x4f\x94\xa7\x0f\xce\xc8\x1d\xf5\x44\x1d\x18\x18\x23\x1b\x7d\xd3\x42\xdf\xa6\x13\xbb\xc2\x61\x60\xaf\x7d\x3d\x37\x2e\x95\xc2\x9d\x99\x68\x5e\xd4\xf9\xd6\xef\x74\xd4\x96\x7a\x48\x88\xeb\x31\x7d\xe4\x06\xc2\x4d\x09\x9c\xd0\x9e\xa9\x55\x5a\x89\x02\x23\xb6\x24\xef\x74\xb8\xb4\xc0\x5b\xe5\x59\xbc\x96\x55\x2a\x77\x4e\xe0\x98\xa7\xe5\x23\xf4\xa9\x28\x50\x98\x43\x22\x44\x2a\x2a\x1f\xe9\xb3\xa9\xc9\x08\xa1\x30\x81\x94\x48\xd2\x14\x89\x10\x42\xc6\x91\x5e\x04\xe8\x08\x61\x28\x88\x0f\xb3\x16\x03\xed\xa2\x4b\x82\x72\xc7\x5b\x93\xdd\x12\x72\x52\x3d\x69\x25\x5f\x1c\x55\x4e\x52\x64\x9b\x2a\xc3\x99\xd7\xeb\x04\xe1\x4e\xe7\xb0\xfe\xfc\x55\xa7\x73\xa8\xf0\x4a\xda\x9f\x3d\xa7\x15\x9b\x2b\x30\xc7\x90\x4d\x51\xf8\xc8\xb6\x56\x25\x69\x33\xbc\xaa\x13\x29\xb6\x2f\x7b\xc9\xee\x34\xb2\x5b\xbb\x7a\x53\x3d\x5f\xd0\x25\x1d\xbf\x11\x04\xdf\x8e\x89\x29\x93\xd4\x20\xcc\x3c\x8c\x43\x63\x9a\xd7\x56\x61\x56\xcb\x5a\x96\x1e\x56\xb7\xd3\x98\x7a\xfa\xa3\x78\x1e\xcd\x16\xfa\xaa\xac\x8a\x78\x1d\xb1\xe8\x8a\xe6\xa1\xb9\xc8\xa4\x62\xdf\x6a\xef\x43\xda\x7b\x70\xce\x48\xd4\x8b\x34\xc9\x97\x57\xf5\x85\x20\xa6\xcd\x90\x33\x46\x8e\x02\x7a\x59\x2d\x90\xc8\x99\xb0\x8c\x89\x19\x63\x5e\xff\x44\x65\x4e\x19\x61\x5e\x70\x7a\xe9\x1c\x76\x16\xae\xb3\xde\xdd\xc3\x3e\x73\x0f\x61\xa3\x6a\x0f\x1d\x8b\x53\x6d\x32\x98\x28\xdf\x2a\x84\x8f\xa8\xc7\x70\x48\xab\x7d\x58\x1d\x74\x50\xb1\xf9\x1e\x66\xdb\xed\x21\xeb\x09\x69\x3c\x4f\x62\x5a\xd4\x43\xe3\xcc\x6e\xee\x89\xde\x30\xea\xa9\x90\x36\xcd\x2c\x13\x3b\x91\x2e\x7d\x89\x70\x3b\xe3\x9b\x8a\x3d\x55\x43\x48\x99\x00\x21\x62\x20\x12\x3b\xb2\x10\x1e\xd2\x12\x2a\xd7\x25\x8a\x12\xcc\x58\xcd\xe4\x6f\xbd\x73\x7f\xac\xe0\x11\x97\x28\xae\x6d\x91\x8b\x37\x1a\x9f\x76\x68\x86\x25\x8d\xdb\xed\xa6\x1c\xe6\xbd\x99\xc0\x82\xe7\xf6\x72\x61\x3d\x82\x6c\xe4\xeb\x42\x0a\x49\xd2\xa8\x90\xfe\x5f\xaa\xd0\x5f\xdf\xbd\x13\x4d\x34\xc6\xaa\x15\xf7\x90\x9b\x2b\x8a\x62\x5b\xad\x41\xec\xe9\x72\xd2\x59\x5c\x5b\x02\xb1\x65\x41\xee\xd9\xc0\xdb\x61\x88\x96\xb4\x03\x11\x29\x84\xeb\xd2\xd9\x6e\x1e\xf5\x2a\x94\x9e\x80\x5b\x2a\xe7\x70\x1a\x15\x26\x3d\x6c\x6f\x17\x30\x7a\x6b\x72\x70\x10\xf2\x52\xc6\xa4\x57\x27\x31\x65\x6d\x35\x38\x9e\xc4\x6a\xca\x0c\x3d\x59\x90\x09\x26\x40\xac\x76\x45\x19\xdd\x59\x83\xc8\xc6\xc8\x7b\x07\x59\x0e\x29\xa1\x96\x8c\x1d\x26\x2d\xd3\xaa\x4d\x98\x5f\xa8\x1b\x81\x49\xaf\xb6\xa4\x21\x82\xcc\x18\xbf\x6e\x72\x3a\x2f\x42\x1f\x44\x2b\x92\x99\xf2\xfd\x29\x67\x34\xbe\x67\xd1\x32\x99\xc9\x2d\xb6\xd0\xab\xfd\x85\xba\xcc\xd8\x02\x0e\x0a\xcd\xe4\xcd\x6a\xab\x61\xdf\xf7\xa6\x84\xa8\x72\x81\x9f\x60\xd8\x94\xb0\x91\xdd\x0f\x33\x98\xa7\xc9\x2a\x44\x53\xe5\x1a\xa5\x5a\xee\x49\x4f\x24\x8c\xd4\x9f\x10\xe5\x5c\x8a\x0e\x59\xcf\x2a\x4e\x4a\x3c\x9c\xb9\xd7\x66\x67\xbd\x82\xe6\x37\x34\xff\x4b\x73\x4f\xdc\x6e\xab\x7c\x36\x52\xb6\x7e\x4d\x4c\xdf\xde\x3a\x5e\xd6\xc4\x2e\x51\xf4\xc4\x40\xa9\x71\x8d\x87\x26\x97\x24\x9a\x9d\x8e\x17\x37\x47\x5a\x26\xc8\x71\x56\xc3\x32\x27\x86\x48\x7a\x19\xa4\x78\x18\x6f\xb7\x9e\x17\x93\xc4\xb9\x6d\x53\x5d\x81\xf6\xe6\x75\x43\x63\xb1\x09\x29\x57\x07\x18\xe3\xca\x43\x7f\xa3\x11\x8d\xc9\xb1\x2d\x80\x18\x63\x58\x6b\x9b\x64\x2f\xc6\x50\xf4\x9c\xc9\x26\x31\x14\xbd\xda\x5c\x93\x3a\x67\xae\xd0\x54\x5e\xb2\xa8\xd9\x2e\x70\x7b\x23\x6f\x9c\x4f\x20\xb1\x57\xfd\x86\xb5\x3b\x8a\x62\x57\xc7\x6c\xbb\xd5\x97\xac\xd5\xc5\xbf\x16\x5b\x13\x92\x74\x3a\x4a\x9e\x22\x59\xa7\xb3\xab\x29\xd2\x76\x20\xd4\xcb\xf0\x30\xea\x74\xbc\x06\xc8\xa8\xe2\xbf\x59\xe9\xcd\xa5\x73\x93\x46\xb7\x14\x88\xd5\xbe\x21\x6f\xe4\x6e\x9b\x00\x5f\x4e\xc0\x70\x65\x9e\x4b\xe1\x18\x56\xd5\x6c\x30\x0b\x40\x0e\xeb\x0a\x98\xbd\x98\xbd\x9f\xb8\xd4\xa6\xc2\x52\x3a\x87\xb6\xac\x4c\x64\xe9\xcc\xe1\x4a\x1b\x9a\x57\x35\xb4\xc2\x19\x2a\x9c\x15\xdb\x6c\x45\x7d\xe6\x6d\xd4\x67\x58\x6f\x7c\xa7\x53\x0f\x57\x1d\xae\xe0\xac\x6a\xb7\x5f\x14\x0d\x63\x96\x86\xe5\x3b\x34\x2c\x6b\xd2\x30\xa5\xe2\xdb\x47\xba\x12\xb3\xa0\xf2\x06\xad\xc9\xe4\x95\x29\xd9\xb1\x23\x62\x5c\xc9\xe4\xcd\x45\x2b\xb9\x49\x99\xa9\xd3\xf1\x5e\x50\x73\x1f\xba\x0d\x18\xe4\x12\x21\x9a\x6e\x08\xc4\x3a\xaa\x86\x14\x43\xd4\xe9\x44\xc6\xf3\x40\x3d\x09\x03\x6f\x8c\x9e\xb7\x07\x64\x3d\x5f\x1d\x66\x23\x0d\x3b\x53\xb6\x70\xa7\x0c\x72\xc9\x70\xad\x0b\xfa\x96\xce\xbd\xb1\x74\xce\xab\x23\x5e\xd3\x65\xd6\xe2\x6a\x52\x6c\xc9\xd2\xb0\xa8\x37\x5b\xe7\x39\x65\x5c\xad\x31\xcf\x86\x49\x26\x66\x4e\x88\x15\x1a\xd0\x8b\xf9\x5c\xe0\x6b\x8b\xd7\x4a\x27\x86\x75\x3a\xcc\xc3\x02\xb6\x60\x91\xaa\xd6\x2e\x1d\xc4\xd8\x11\x35\x82\x56\x51\x23\x70\x45\x8d\x60\x62\x5c\xb4\xa9\xdb\x26\x84\x37\xfd\xc3\x42\x4a\x78\xcf\x6e\xb9\x50\x10\xde\xd3\xcf\xf8\xbe\x97\xc8\x37\xab\x24\x8a\x62\x34\x63\x61\x01\x6b\xb3\x04\x33\xe3\xdd\x58\x3d\x64\x8c\x1a\x90\x11\x20\x0b\x17\x01\x72\xa1\xa2\x09\x86\x98\x48\x8e\x14\xe6\x84\x6d\xb7\x89\x10\xc7\xa3\x6b\x35\xbf\x05\x1a\xc6\x8e\x83\x1a\x79\x9b\x46\x30\xbb\xea\x9d\x3c\x06\x4b\xca\xa3\x70\x0e\x8d\xea\xc2\x79\xa9\x9f\xa8\xfb\x8f\x0a\x69\xba\xaf\xd7\x92\xdc\xe0\xed\x76\x06\xd9\xde\x4d\x59\x6e\xc4\x72\xd2\x0d\xf3\x9e\x33\xb1\x5d\x08\x21\xd8\xc1\x33\x21\x77\xb9\xc1\xe1\x82\xed\x38\x5b\xcc\xc9\x46\xf7\x56\x52\x03\xd1\x96\xda\x82\x0f\x63\xa8\x91\x84\x30\x03\xb5\xe3\x73\x2b\xb7\xc5\x4c\x5d\x45\x28\x2c\x6a\x1e\x06\x90\xd8\x40\x0e\x4e\x95\x2b\xe6\xe5\x0a\xfd\x38\xc4\x93\x87\x70\xd7\x42\xeb\x74\xe6\xcc\xb3\xe0\x1a\x15\xf9\xa5\xdd\xaa\xd7\xb5\x5c\x96\x2c\xa7\x96\xaf\x9f\xdb\x16\xaf\x24\x43\x7e\x23\x65\x97\x81\x8f\x61\xca\x5a\xbc\x50\xfd\xaf\x97\xc1\xae\xd3\x65\xfb\x80\x54\x1d\xe5\xc5\x8a\xb8\x4d\xf8\xa2\xbe\x00\x0e\x09\x49\x3b\x9d\x14\xd6\x66\x31\xc5\x3b\xeb\xa0\x86\xe2\x80\x2c\x0c\x04\x6a\x89\x4c\xec\x98\xc0\x8a\x88\xa5\x5d\xdf\x18\xdd\xe2\x61\xe2\xf2\xc1\x0a\xf3\xd7\xdb\x2d\x13\x24\x7e\x95\x46\xf7\x02\xef\x5b\x56\x80\x64\x4f\x16\x12\xbb\xe6\x59\x7e\x1b\xe5\xb1\xc0\xb0\xc6\xd9\x87\x9d\x04\x73\x71\x4c\xba\xfc\x61\x34\x7f\x4b\xe7\x8d\x5e\x51\x18\x23\x9d\x19\x01\x32\x99\xc4\xf2\x9d\x93\xd5\x43\x0c\x29\x33\x43\x2a\x24\x06\xf9\x30\x93\x68\x57\x6c\x3d\xb5\x35\x2d\x06\xd7\xdb\xed\x0c\x77\x3a\x5e\xba\xb3\xe0\x12\xb1\x55\x7b\x0b\x13\x7f\x23\xa5\x3a\xcd\xde\xa6\x7a\x60\x60\x25\x6a\x09\x63\x21\x2f\xcc\x3a\x9d\xc3\x85\xda\x39\x45\x39\xf5\x45\x52\x4d\x90\xeb\xea\x54\x56\x9f\x80\x9c\xce\xc3\x62\xbb\xe5\xf6\x19\xab\x79\x09\x0b\x8c\x1d\x1f\x3c\x85\x87\xbd\x85\xd8\xe7\x16\x65\x09\x57\x02\x51\x4f\xce\xf0\x90\xf7\xa2\x76\x95\xdc\xf4\xe1\x39\xbe\x62\xbd\xa8\x04\xb9\x3f\xc1\xae\xf7\x11\x7a\xb7\xca\x72\x5e\x10\xe6\x5d\x9e\x62\x0f\xef\x66\x41\xeb\x82\x1e\x88\x81\x9c\x71\x24\x98\x0e\x8f\x03\x8a\x10\xb4\xec\x34\x99\x59\x8e\x39\x61\xde\xd9\xb9\xa3\x15\xc8\x1a\x26\x9c\x87\x95\x09\x67\x65\xbe\xa8\x4c\x17\xdd\x7e\x9c\x63\xc7\x31\xe7\x6c\x11\xe5\x4f\xb9\xe7\xe3\xba\xb5\x56\x97\x6a\x73\xce\xa0\xad\x7f\x6d\x8d\x8f\x5b\x1b\x2f\x5d\xc3\xea\x1c\xd3\xd6\x1c\x33\x27\xc7\xac\x35\xc7\xda\xc9\xd1\x3e\x44\xb1\x93\x83\xb6\xe6\x98\x3f\x3c\x88\xff\x16\x75\xf2\x5b\x1e\x54\xfc\xb6\xdf\x5a\xb4\xef\x16\xed\x4f\x42\xab\x18\x78\x1d\xf1\x45\x6f\x99\x30\x4f\x7d\x44\x77\xea\xf9\x7a\xf7\x5c\x27\xd1\xb3\x4d\x1b\x76\xd4\x02\x01\xfe\x5b\xe9\xce\xed\x64\x5a\x45\x4d\x4d\xc1\x42\xab\x5b\xf4\x81\x31\xe6\x16\x28\xf2\x96\x5e\xbd\xb8\x5b\x79\xa8\xb7\x09\xc0\xd1\xdb\xe9\x9e\x90\xb3\x51\x3f\x0c\x00\x95\x08\x03\xba\x42\x58\x32\xb7\x4b\xa9\x3e\xaa\x8c\xb4\x59\xa7\x23\x6f\xbf\x8f\xfd\x49\x75\x20\xc8\x08\xdb\x7f\x40\xdc\xa5\x25\x16\xb3\x36\x42\xf9\xd5\xd4\xd6\x7a\x2a\xaf\x3e\x6a\x05\x38\x8a\x50\x88\x10\x20\xaf\x52\xee\xed\x00\x74\x4d\x0e\xbf\x39\x19\xd9\xf3\x2a\x0a\xc1\x19\x0e\xe5\x68\xca\x7b\x66\x5e\x3d\xe5\xb8\x3f\x18\x3c\x09\xe8\x09\x3e\x0e\xe8\x49\x89\x71\xef\x53\x96\x30\x4f\xdd\x6d\x46\x18\x49\x6b\x45\x8f\x62\x6c\x6f\x7b\x5a\xcd\xa8\xa7\x47\xc0\xb5\x82\x51\x6e\x59\xe5\xdb\x1b\x63\xd9\x1d\x10\xbf\x11\x02\xb4\x28\x52\xf5\x1b\xa1\x89\x85\xc1\xf0\x83\xeb\xf3\x04\x4c\xc5\x59\xad\x1e\xde\x0d\xa0\x72\xde\x8d\xf5\x05\x6f\x04\xd6\x76\x69\x23\x10\x23\x64\x20\xd5\x2c\x45\x98\x91\x6c\xef\xf0\xcb\xd1\xf8\x21\xcd\x22\x69\x81\x5e\x63\xb2\x23\x57\x9c\x11\x10\x65\x77\x15\x4c\xd7\xbb\xa2\x73\xcd\x52\x74\x19\x8f\x5a\xa6\xfb\x81\xd9\xf1\x71\x28\x30\x20\x6c\xc2\x12\x23\x26\x5d\x2b\x8c\x83\x89\xa3\x2f\x16\x41\x40\xff\x23\x06\x7f\xdc\xaf\x27\xf4\x55\x02\x86\x2a\x92\x37\x90\xa6\x31\xbb\x55\x67\x53\x57\x06\x2c\xa4\x17\x1c\x52\x54\x78\x5d\x2d\x47\x06\x39\xee\xf6\xfc\x01\x3e\xf6\xec\x5a\xb5\x71\x15\xbc\xc2\xd1\xd3\x8a\x9e\x10\x42\x3c\xe5\x94\x07\xab\x13\xa5\xa4\xc5\xab\x83\xcd\xa1\x06\x59\xb0\xfc\x63\x7f\x02\x39\xe1\xe3\x60\x72\x1c\xf8\x3e\x64\x84\x8f\xfb\xea\x33\x25\xf9\x13\xdb\x82\x0c\x82\xa3\x4c\xb0\xa4\xff\x31\x56\xcb\xa3\x5d\x76\x7c\xe2\xe3\xff\x09\xfa\xd6\x62\xe4\x28\x7d\x62\xc7\xc1\x56\xcd\x8f\x4e\xe0\xf2\x88\x43\x80\xe1\x28\xc0\x25\xcc\x88\xc2\xfc\x35\x19\x3b\xab\x4e\x2c\xb3\x42\x10\x24\xd8\x89\xbc\x68\x8b\x3c\xc5\xd8\x1c\x29\xa9\x55\xe3\xfa\xc3\x98\x75\x89\xd8\x00\xd6\xe6\xad\x9f\x93\x89\x60\x0c\x3c\x85\xf6\x33\x83\xf6\xeb\x52\x5e\x78\x30\xa3\x19\x36\x70\xf7\x80\x13\xbe\x6f\x59\x78\xf4\x98\xf4\x07\x03\xfc\x0d\xe9\xf9\x27\x97\xfd\x8b\x11\x3d\x0e\xfa\xbd\xcb\xbe\xa2\x23\xab\xec\xd6\xf3\xa8\x98\xf0\x01\x3e\x0e\xc4\x1f\xe8\xf7\x4e\xa5\x6a\xfa\x27\x79\x5d\xc7\xf3\x7a\xfd\xa0\x7f\xf6\x44\xcc\x5e\xb7\x77\x1e\x0c\xfa\x4f\xc4\x0c\x76\x7b\xfe\x79\x5f\x7c\xf6\x27\x62\x97\xfd\x21\xb9\xa3\xb1\x77\xe2\x9a\x74\xcc\xfe\xed\x69\xeb\x05\x83\x8a\xcd\xa1\xf8\xdb\xde\x60\x14\xab\x0b\x0e\x73\xf9\xc7\x79\x2a\xa2\x7e\xee\x28\x31\x0f\x38\xc9\x3c\x8e\x25\xd5\xaa\xf9\xde\x15\x28\xec\x78\xdd\xd5\x7b\x8f\x9c\x06\x0c\x66\x60\xc7\x27\x13\xc2\x41\x90\x0d\x47\xb5\x5b\xdd\x32\xa9\xd5\x20\x57\xbb\x82\xd2\x58\xf2\xd8\x82\xeb\x4f\x9e\x90\xe0\x88\x5b\xdd\x58\x6b\x19\x49\x72\x70\xdd\x18\xe5\x64\xc8\xba\x24\xa8\x00\x31\x0d\xc8\xdc\x93\xaa\xb5\x70\xfe\x6f\xb5\xb0\x4b\xbc\xc0\xf7\x8f\x9c\x18\xfc\xe4\x3f\xd2\xe2\x2e\x11\x2b\xe1\xc8\x89\x11\x80\xdd\x1e\x94\xf0\x30\x3f\x76\xe8\x68\x2d\xf7\xde\x25\xfa\xf8\xf1\xed\x8b\xa7\xcf\xdf\x7f\xfc\xfe\xc5\xaf\xef\xdf\xbc\x79\xf5\xee\xe3\x5f\x5e\xbd\x79\xf6\xf4\xd5\xc7\x1f\xdf\xbc\xf9\xdb\xc7\x8f\x9d\x4e\xcb\x21\xd3\xc3\x45\x7a\xd2\xf1\xdd\xf7\xcf\x5f\xe0\x8d\x2f\xcf\x9a\x1e\x99\x5d\xf4\xc8\x9e\x45\xcd\x32\x56\x64\x29\xed\x49\x7f\x79\xb2\xaf\xf2\x75\x5f\x87\x9b\xbe\xb8\xfc\x12\x2f\x6d\x7b\x9f\x37\x9f\x51\x94\xf7\xcc\x46\xfa\x24\x4a\x8d\x48\xe5\xe4\x01\x38\x98\x33\x1d\xa0\x6c\xbd\x54\xf7\x74\xc2\x43\x1f\x66\x19\x9b\x27\x57\x6b\x1b\xbe\xcd\x13\xae\xbf\x4b\x1c\x4a\xb7\x05\x0c\x68\xf9\x20\x83\x9a\xcb\x57\xc2\xfe\x44\xbb\xa5\xa7\x47\x73\xd5\xca\x9c\xce\x6b\xb5\xb9\x73\x09\x4b\xfa\x6a\xb5\x37\xb2\x9c\xcb\x54\xf2\xcd\xf7\x8f\x1f\x65\xda\xc7\x8f\x84\xff\xdb\xcd\x7b\x90\x87\x4f\x5c\xee\xba\x8f\x21\x23\x1b\x1a\x89\x9d\xfe\xcd\x9a\x87\x68\xb6\x9e\x26\xb3\xa3\x29\xfd\x9c\xd0\xdc\xf3\x7b\xa7\x70\xe0\xc3\x81\xdf\xeb\xc3\x41\x80\x11\x88\x8c\x6d\xd9\xfc\xdd\x6c\x2f\xd9\x1e\x60\x81\xca\x53\x2c\xa2\x7c\xb5\xb7\xbe\x33\x99\xa9\x84\x84\x6c\x8a\x45\x96\x73\x5a\xf0\x30\x18\xf8\xa0\x02\x79\xd8\xf7\xf5\x77\xd8\x1f\xc8\x53\x20\x16\x47\x79\x1c\x9e\xf8\x02\x05\x96\xab\x94\xde\x85\x27\xe7\x03\xa0\x8c\x53\xc1\x8e\xbd\x9b\xe5\x94\xb2\xb0\xdf\x1f\x40\x4a\xa3\x9b\x2a\x26\xb8\x1c\x38\x47\x92\x91\xfb\x44\x91\x61\x48\x9c\x6d\x8f\x62\x40\xcb\x02\xe1\x52\xc8\xa1\x62\xd4\x12\x76\x15\x66\x60\x2e\x1c\x87\x09\x34\xcf\x74\xff\x4d\xc5\xd8\x58\x9a\x9a\xb5\x3d\x42\xff\xe7\x74\x92\xa6\x85\x90\x56\x4a\x46\x36\x92\x0a\x61\x35\x70\x4c\xea\x23\x55\x8f\xea\x9a\xc8\xac\x57\x61\x47\x21\x75\x32\xf2\xd6\x35\xc4\x55\xa6\xf5\xc8\x0f\xd7\x43\x97\x2b\x96\x3a\x1a\x73\x0d\x1b\x90\x82\x2b\x55\x93\x69\x74\x8f\xac\xcb\x90\x9d\xbb\x96\x23\x1a\x8e\xe9\x64\xaf\xa5\x6a\x35\x29\x14\xd0\x41\xc5\x33\xee\x28\x39\xd2\x51\x1a\x46\x5e\x8a\x6b\xb9\x66\x0f\x97\x89\x47\x71\x18\x79\xb1\xd4\x42\x18\x1e\x14\x29\x8f\xa7\x4f\xd7\x3c\xfb\x51\x5e\x8a\xfe\xde\x4c\x75\xc3\x30\xaa\xf2\x9d\xe6\x1b\x29\xe4\xf8\xe4\xac\x26\x30\x2a\x14\x0a\xfc\x27\xde\x69\x37\x18\x3c\xb1\x5c\x0a\x87\x5e\x7f\x80\xbb\xfc\x78\xa0\x3c\x5a\xfd\x29\xc2\xa3\xed\xf9\x2a\x23\xc1\x8d\xd2\xcd\x32\xc8\xc5\xc2\x16\x0b\xc8\x3d\xcd\xd7\x26\xa1\xea\x66\x75\xe2\xde\xac\x66\x24\x91\x37\x79\x1d\xb9\xe7\x5b\xe2\x6f\xb7\x5e\x66\xae\xd1\x56\x9e\xae\xfe\x2c\x7d\x72\x75\x2c\x6d\xd2\x65\xa7\x23\xc8\x1f\x2d\x5e\x67\xf1\x3a\xa5\x23\x1a\x1a\xe5\x4d\x48\xbf\x38\x20\x86\x90\x05\xa7\x78\xd8\xbe\x5b\x70\x40\x15\x70\x64\x36\x0e\xb1\x1f\x80\x55\x47\xb6\x3a\xab\x4f\x4c\x6a\x6f\x49\x97\x99\x57\x05\x5b\x15\x7e\xee\xee\x55\x65\xad\x6b\xc1\x22\x13\x0f\x9e\x0f\x99\x09\x68\x7d\x18\x2b\xe5\xa3\xb0\x02\xfd\xf0\xd0\xaf\x5c\x0b\x2f\xd7\xc9\x4f\xd1\x92\x92\xa8\x6a\x8e\x8a\x01\x56\x6a\xc1\x33\xf7\x98\x77\x79\x8e\x31\x24\xf2\x53\x70\xf0\x91\xfc\x1a\x04\x8f\xdd\x2b\xda\xe7\xb2\xb6\x57\x9c\x9c\x58\x4f\xde\xde\xc9\xa9\xa3\x95\x49\x6a\x5c\xaa\x4b\x07\x5a\xaf\x32\x3f\x74\x25\x7a\xbb\x45\x63\x75\xba\x7a\x60\xef\x0d\x4f\x84\x5c\xf1\xc5\x1b\xcd\xd5\xa3\xdd\x90\x93\x43\x21\x85\x49\x1b\x4a\x45\xa4\x24\x93\x63\x3d\xa8\x41\x4a\x68\xcb\xa5\xdd\xe1\xa1\x97\x13\x2f\x22\xa9\x7c\x2d\xc5\xc3\xb8\x17\x67\xcc\x7d\x2f\x35\x52\x7c\x1e\x86\x43\xbe\xdd\x1a\xc5\x87\x10\x88\xf1\x30\x97\x96\x99\x15\x73\x94\x89\x26\x24\x84\x96\xf3\x84\x45\x69\x7a\xaf\xde\xd5\xdb\x6e\xd5\x62\x4d\x7b\x6a\xac\xb6\x5b\xf3\xe5\x61\x9b\x53\xfa\x04\x54\x2a\x87\xc4\x39\x1e\x2e\xe5\xf8\x6d\xb7\xae\xd2\xf8\xb1\xea\xbe\x47\xe8\x2a\x4f\x82\xa6\x9a\x6d\xc7\x28\xed\xcb\x6e\xa2\x6a\xce\xb3\xab\xcb\xfa\x0d\xe7\x51\x4c\x70\x61\x82\x15\x1b\xba\x9d\xab\xba\xb6\x8b\x39\xff\x99\x1b\xde\xea\x32\xf6\x17\x46\x6c\x57\xc3\x2c\x78\xf8\x2f\x70\xf1\x35\x15\xf2\xd9\x9f\x61\x1e\xad\x30\x9b\x3f\xe4\xe3\x1a\x15\xf2\xa3\x99\x60\x91\x77\xd4\x42\x55\x8d\x76\xb9\x6c\xb9\xc3\x25\x48\xee\x03\xd5\xd5\x99\x53\x42\x6c\xfc\xa1\xf9\xae\xd6\xe1\xc8\xb4\x2d\xb4\x15\x8a\xc9\x7b\xcc\x16\xf1\xe7\x76\xba\xba\x2c\xc6\xdb\x1e\x78\x93\xbe\x1e\x7a\x95\x40\x40\xdc\xc0\x76\x7b\x18\x40\xde\x73\x05\x04\xb1\x46\x91\x5c\xd2\x28\x31\x4f\x7f\x1a\x81\x41\xda\x5d\xed\x13\x3f\x72\xe9\x0d\x2f\x77\x15\x71\x59\x53\x80\xe9\x74\x72\xcf\xe1\xf7\x05\x6d\x67\x32\x0e\x18\xfe\x92\x04\x92\xb5\xb2\xf8\x66\xa9\x5e\xf6\x71\x4f\x31\x2f\xc9\xfc\x1e\x04\x35\xbe\x3c\xc1\xc3\x87\xb6\xd8\x8d\xd4\xe5\x85\x62\x59\x83\x2d\x19\xe6\x65\x59\x89\x6b\x3d\x99\x85\x64\xae\x08\x57\xd5\x42\xf2\x3f\x85\xd3\xee\x6d\x17\xf7\x78\xe3\x2d\x9d\xd3\x9c\xb2\x99\x59\xc0\x7c\x91\x14\x07\x8b\xa8\x60\x5f\xf3\x83\x29\xa5\xec\x20\x61\x09\x4f\xa2\x34\x29\x68\x7c\x70\x74\x50\xac\x57\x34\xf7\x70\x2d\x87\xa0\xf6\x34\x76\x5e\x2b\x7b\x14\xa6\xfd\x09\x5c\xfb\xbf\xdc\xa2\x74\xa3\xea\xfe\x3e\xfe\x5d\x61\xcf\x8a\x72\x4a\x96\x93\x6f\x2a\xe9\x53\x71\x41\x2b\x0b\x2d\x16\x9b\x70\x23\x28\x93\xdd\x13\x44\x19\xae\x8e\x3e\x45\xf0\x8a\xf2\xef\x1b\x39\x64\x54\x9e\xdc\xd0\xf8\x1d\x8f\x38\xfd\x21\xcf\x96\x72\x4e\xf7\xa4\xd9\x72\xcb\xe4\x2e\x61\xf2\x6b\x95\x67\x2b\xdb\x00\xae\x1a\x26\xe5\x3d\xa6\x6b\x55\x0b\x5c\x67\x55\x23\x2a\xdb\x2e\x10\x20\xb7\x5f\x32\xce\x8a\x3c\x2a\x90\xf0\x7b\x09\x2c\x22\x9b\xaf\xbe\x52\x64\x49\xf5\x7a\xb9\x8a\x72\xfa\x88\x0e\xb7\xb7\x2d\xad\x99\xaf\x16\x0e\x39\xcd\x7b\x49\x21\xcd\x58\x28\x1e\x45\x61\x3a\xa6\x3d\x53\xef\x64\xbb\xcd\xca\x74\x9c\xf7\x7e\xb0\x3c\xe3\xa4\xde\x2a\xf5\x36\xd4\x9f\x6c\x54\x09\x02\xa6\xa8\x72\x42\xa2\x9a\x01\x63\x83\x58\x59\x1b\x92\xde\x15\xe5\x8e\xa3\x4c\x01\xb4\xb0\x47\xd0\x8d\x44\x85\xfd\x05\xcc\xdb\x93\xbf\xa7\xc5\x2c\x4f\x56\x3c\xcb\x61\xe5\xe4\xf8\xd9\xcc\xd2\x9b\x39\x2c\x76\x16\x43\x0b\x7d\x3a\x90\x6e\x0f\x21\x6f\x3f\x16\x55\xb6\xeb\x0b\x63\xae\xb7\xf2\x18\x1e\x66\x9d\x4e\x76\x48\xc8\xa2\xd3\x11\x45\x33\x41\x81\x95\x25\xd5\x5a\xa4\xc6\xf2\xea\x4b\x64\x04\xbc\xd8\x63\x82\x93\xb6\x8e\xf4\xe5\x51\x01\x2c\x49\xe1\x31\x0c\x37\xc4\x1f\xde\x7c\x13\x99\x5d\xa4\xdb\xbd\x51\x15\x4d\x49\x34\xbe\x51\xd7\x3e\xbd\x64\x3c\x9d\x6c\xb7\x79\xa7\x93\xcb\x8f\x65\xa7\xb3\x94\x1f\x69\xa7\x93\x8e\xa7\x13\xcd\x71\x5e\x91\xb9\xc7\x60\xaa\xae\xca\xcd\x3c\x0e\x53\xb8\xaa\xbd\xc5\xeb\x5c\xb1\xf9\xb3\x44\x74\xaf\x2b\xa2\xff\xa4\x02\xeb\xf1\xcc\x55\x4c\x15\x53\xb0\xce\xe9\x5e\x0e\xeb\xdf\x6d\x98\xd4\xac\x2c\xb3\x69\x92\xd2\x77\x9c\xae\x56\x34\x0f\x03\x7a\x02\xc5\x8a\xd2\xf8\xfb\x24\x4a\xc3\xc0\x1f\xf8\x10\xad\x56\xcf\xa2\x3c\x0c\x02\xdf\x87\x38\x8f\x6e\x45\xae\xbe\xef\xc3\x32\x8b\x45\x96\x13\xdf\x87\x82\x45\xb3\xeb\xa9\xc8\x74\xea\xfb\xc0\xb3\x2c\xe5\xc9\x2a\x0c\x06\xbe\xff\x68\x91\xd2\xc7\x72\x77\xed\x0b\xb9\x4a\x19\xf9\x43\x24\x3e\x30\xa4\x64\x8c\xee\x0a\x04\xa8\x58\x22\x40\xcb\x18\x01\x4a\xaf\x10\xa0\xbb\x14\x4d\x86\x6d\xe7\x4b\xb4\x3a\x2e\xaa\xee\x81\x8e\x36\x77\xd2\x86\x79\x19\x9e\x89\xb6\xc7\xe1\xe5\x99\x0f\xe9\x55\x18\xf4\x2f\x7c\xb8\x4b\xc3\xe0\xb2\xef\x97\xa1\xba\x9e\xb2\x66\x09\xb7\xe2\x8c\xba\x92\xb2\xba\x43\x61\x2e\x6d\x4a\x0a\x4e\x57\x75\xad\xcd\x60\xc7\x7c\x8c\xc2\x58\xf1\x3a\xa2\xd9\x02\x9a\x68\x3d\xa7\x2b\x34\x71\x18\xff\xd8\x39\x11\xdb\x79\x7b\x72\x4c\x27\x23\xf1\x13\x9a\x1b\x0c\xe8\x3b\xf9\x98\xe7\x81\x67\x1d\x36\x86\xd5\xa1\x1e\x76\x2e\x7c\xd4\x4e\xf1\xe6\xee\xf5\x96\xd4\xea\x1b\x9c\x87\x63\x88\x90\x90\xcc\xd1\xe9\x28\x96\x5e\xa4\x1e\xa8\xea\xa1\x96\xd6\x5a\x71\x10\xb1\xf8\x00\xe1\x2e\xf2\xac\x4b\xc9\x0a\x8a\x3a\x07\xc8\xdb\x1e\xdd\x1c\xa7\xe3\xbc\x1b\x4c\x04\x4c\xfd\x15\x72\x7c\x34\x3b\x0e\x7c\xbf\xd9\xcb\xba\x0c\x1c\x49\x4b\x94\x6b\x7a\x5f\x84\xa9\x39\xf1\x62\xb0\x5e\x85\x31\xc4\xd9\x6d\x5d\x89\xa4\x46\x3d\xad\xf9\xb2\x86\x9c\x88\x2a\x79\x75\x73\x8b\x3b\x63\x33\x8a\x3d\x81\x84\xee\xe0\xb4\x74\x6b\xa7\x3b\x79\xa7\xc3\xbf\xf5\x47\x79\x48\xf7\xf4\x01\xa6\x94\xdf\x52\xca\xc2\x39\x64\x2c\x6d\xf3\x01\x21\xe7\x50\xbe\x36\x24\x2b\x6b\xc9\x20\x46\xbf\x2c\x61\x5d\x3f\x37\xab\xf8\xd8\x6c\xd8\x36\x52\x57\x6b\xce\x69\x5e\x34\xd5\xa7\x2d\x16\x1b\x8f\xb5\x2b\x6c\xad\x46\xfb\x24\x79\x45\xe7\x3c\xe4\x62\x79\xeb\x88\xb7\xd2\x83\xa8\x88\x29\x81\x35\x9d\x66\xd3\xde\x7a\xe5\x89\xf5\x6e\xc4\x82\x56\x60\x27\x3b\xc0\x4e\x04\xb0\x71\x55\x7a\x82\xe5\x8b\x85\x59\x96\x0a\xe2\xe4\x65\x64\xb3\x4c\x98\x52\x2e\x86\x83\xb3\xb2\x56\x6d\xe6\x9c\x92\x2b\x08\x62\xbe\x01\x49\x44\xf6\x1c\x67\x42\xe1\x41\x1a\xb1\xb8\x98\x45\x2b\x8a\x11\x06\x07\xe4\xe9\x45\x89\x1b\x30\x9d\x9e\x38\x19\xcf\x4e\x4b\xf9\xbe\x26\x30\xed\x4a\x4b\xd9\xde\x40\x4c\x36\xd3\x34\x9a\x5d\x87\xe8\xbf\x7d\xdf\x47\x70\xbb\x48\x38\x0d\xd1\x7f\xcf\xe7\x73\x54\xc2\x9c\x6c\x06\xbe\x08\x45\xe2\x1f\x82\xc0\x97\xa1\x81\xf8\x87\xa0\x2f\x43\x54\xfe\x0f\xc1\x89\x0a\xf9\xe2\x1f\x82\x53\x19\x9a\xc6\xe2\x1f\x82\x81\x0c\x5d\x52\xf1\x0f\xc1\x99\x0c\x9d\x0f\xc4\x3f\x04\xe7\x32\x74\x16\x88\x7f\x08\x2e\x64\xe8\xb4\x2f\xfe\x21\xb8\x94\xa1\x7e\x20\xfe\x21\x78\xaa\xaa\x8f\x07\xe2\x1f\x82\xa7\xaa\xfe\x48\xfe\x0f\xc1\x53\x55\xe5\x89\x2f\xfe\x21\x78\x5a\x83\x5b\xc2\x4a\x77\x85\x5e\xd0\x68\x7e\x66\xba\x32\x1b\xcc\x22\x7a\x69\xba\x72\x39\x8f\x2e\xe2\xc8\x74\xe5\xfc\xf2\xe2\x6c\x36\x35\x5d\x19\xcc\xce\xa6\x33\xdf\x74\xe5\x64\x3e\x08\xa6\x03\xd3\x95\x93\xcb\xd3\xcb\x68\x6a\xba\x72\xe2\x9f\xcc\x2f\xe7\xa6\x2b\xfd\x8b\x93\xc1\xe5\x89\xe9\x4a\x10\xf5\x4f\xce\xa9\xed\xca\xc5\xec\x92\xce\xe7\xb6\x2b\x83\x93\xb3\x78\x4e\xab\xae\xc4\x83\x48\x06\x0d\xdc\xd3\xf9\x9c\xa2\x12\x16\x66\x56\x66\xf4\x94\xce\xec\xac\x5c\x4c\xa7\xb1\x6f\xba\x32\x3f\xbd\x98\x4f\x03\xd3\x95\xb9\x7f\xd6\xbf\xec\x9b\xae\xd0\xd9\xa9\x7f\x1e\x99\xae\xd0\xcb\x80\x9e\x9d\x98\xae\xc4\x17\xc1\xf4\xcc\x37\x5d\x99\xf5\x83\x8b\xc1\xd4\x74\x25\x8a\x83\xd3\xc1\xb9\xe9\xca\xc5\x85\x4f\x4f\xe7\xb6\x2b\xf3\xf9\x85\x2f\xc6\x40\x77\x65\x3e\x3f\xf5\x2f\x02\xdb\x95\xf9\xc0\xf7\x45\x51\xdd\x95\xd9\x20\x08\xce\xfa\xa8\x84\x25\x61\xde\xc9\xb9\x60\xd8\x74\x97\xe6\xf3\x13\x81\x3e\x06\x26\xf5\xa7\x7d\xdb\xa5\xf9\x6c\x76\xe1\xdb\x2e\xcd\xa7\xe7\xa7\xb1\xe9\xd2\x7c\x1e\x9d\xf7\xcf\x4c\x97\xe6\xf3\xcb\x0b\x81\xcc\xaa\x4b\xf3\xe9\xc5\xcc\xb7\x5d\x9a\x0f\xce\x65\x48\x75\x89\xce\xcf\x64\x48\x75\x89\x9e\x0d\x02\x11\xb2\x5d\x8a\x03\x51\xa1\xed\x52\x34\x3d\xf5\xab\x2e\xcd\x2f\x55\x66\x0d\x77\x7e\x16\xfb\x3e\x2a\x61\x6a\x10\xed\x64\xde\x9f\xc7\xa6\x2b\xd3\x69\x4c\xe7\x53\x8b\x68\xfe\x2c\x9a\x5f\x9a\xae\x9c\x9d\x4e\x07\x02\x25\x4f\x35\xee\x47\x72\x75\x0d\x34\xee\x5f\x9e\xcd\xed\xec\x04\xf4\xe2\x82\xda\x35\x13\x5c\x9e\x9f\xc5\x7d\xd3\x95\x60\x70\x36\x98\xd9\xae\xf8\xf1\xe9\x79\x54\xad\x99\x8b\xfe\x34\x70\x10\xed\xf4\xf4\x22\x92\x41\x55\x65\xff\xf2\xfc\x52\x06\xcf\x75\xf0\xac\x2f\x97\xff\x95\x98\x9d\x0b\x0c\xf7\x84\x79\xe7\x18\xee\xc8\x46\xc8\x95\xe1\x66\x95\x27\xcb\x28\xbf\x0f\xa5\x65\x95\xa7\x4f\x07\xe1\xc0\xef\x5d\x9c\x63\x04\x05\x9d\x65\x2c\x6e\x4d\x1f\x9c\x62\x04\xe6\x31\xb6\xdd\xe4\x93\x0b\x8c\x60\x91\x30\xde\x9e\x54\x42\x9c\xdc\x24\xd2\xc7\x4d\x33\x39\xe8\x63\x04\x95\x27\xf3\x70\xb3\x8a\x04\x6f\x19\xf7\x24\x3d\x33\xb2\x57\x38\x1f\x0f\xfc\x49\x09\xea\xad\xb6\x70\x13\x29\x47\xc8\xed\xcd\x5c\x64\x37\x6d\x35\xf9\x36\xed\xcd\x2a\x9a\x09\x59\xb4\xe7\x9f\x82\x79\x1e\xae\x25\xff\x85\x1c\x12\x95\x5c\x15\xb9\x78\x60\x18\xfa\x67\xce\x28\x3d\xab\x3a\xd5\xde\x6b\x93\xd1\xc2\x3e\xb9\x00\xf9\xa4\xdd\x9e\xfc\x32\xcd\x66\x0e\xfa\x72\x34\x6e\x22\xb7\x75\x41\xbf\x2c\xe1\xb6\x39\xdb\x66\x30\x9b\xf3\xdb\x1f\x0c\xe0\xa0\xfa\xf1\x7b\xe7\xbb\x93\xbc\x93\x67\x50\x9f\xe9\xd6\xf4\x64\x96\xb1\x07\xd2\x9b\xe8\xb0\x93\x67\x0f\x52\xcc\xc7\x17\xbe\x3f\xb1\x38\x61\xb7\x8c\x1d\xbc\x30\x1d\x76\x31\x61\xa7\x12\x39\xbf\x0d\x7c\xb8\x68\xe2\xc3\x6e\xd3\xce\xda\xb0\x22\x38\xfb\xe2\xb8\x9d\x3c\x84\x1a\xed\x23\xf0\x25\x04\x69\x2f\xf5\x08\x34\xe9\x9f\xba\x7e\x06\x5e\x34\x1f\xc5\xcb\x7b\xa9\x60\x3d\xb6\xdb\x1c\x12\x92\xf7\xe2\x28\xbf\xde\x6e\x83\xde\xe0\x49\x3e\xa4\x63\x3e\x91\x26\x4c\x8d\xf7\x3d\x18\x1e\x51\x75\x7c\xc0\x26\x21\x92\xc5\xa5\x8b\x96\x11\x55\xb0\x8c\xc8\x73\xdf\xa3\xd8\xa3\xbd\x65\x94\x30\xc8\x70\x88\x04\x6c\xa4\x7d\xef\x50\x59\x53\x95\x33\xb2\x39\x13\xec\x1a\x7a\x5d\xbb\xe2\x9b\xc6\xf1\x86\xfc\x26\xeb\x0c\x57\xe3\x13\x81\x2f\x02\x44\xb8\x1a\x0f\x24\xee\x44\xf9\x75\xb8\x1a\x9f\xfb\xfe\xc4\x48\x70\x76\x55\xb8\x87\xf0\xb9\x81\xb1\xe8\x3d\x95\x02\xac\x80\xb1\xe8\x09\xb2\xab\x60\x2c\x7a\x82\xe6\x96\x5a\xd0\x93\xb6\x36\x75\x49\x4f\x97\x5f\xf6\x22\xa7\x15\x22\x54\xb5\x43\x84\x54\x4b\x0a\xb8\x26\xb4\x77\x1b\xe5\x2c\x61\x57\xf0\xa6\x82\x73\x6d\xe0\xdc\x38\x50\x6e\x1c\x18\x37\x1a\xc2\x35\x3c\x97\x06\xb6\xf3\x0c\xde\x55\xc5\x9f\x9b\xe2\x53\xa7\xf8\xd4\x29\x3e\xd5\xc5\x9f\xc3\x27\x69\x26\x3b\x9b\xd1\xa2\x80\xf7\x15\x84\x4f\x06\xc2\x55\xad\x23\x57\xb5\x8e\x5c\xd9\x8e\x7c\x82\x9f\x8d\xc1\xeb\xdb\x0a\xc8\xcf\x23\x8d\x12\xe1\xcf\xf0\x93\x72\x58\xcc\xf3\xa8\xe0\xef\x17\x39\x2d\x16\x59\x1a\xc3\xc7\x2a\xf3\x4f\xa3\x93\xf0\x27\x78\x2d\x9f\x7c\x67\x51\xfa\x46\xba\xc5\x87\xa7\x55\x86\xd7\xa3\x5e\x3f\x7c\x0d\x2f\x77\xc4\x68\x8d\x0b\xf2\xd1\x4f\x3d\xa7\xce\xbb\xa1\x7a\x74\xe5\x2d\x8e\x79\x26\x32\xa9\xde\x22\x40\xa2\xbd\x08\xd0\x4e\xb3\x44\x52\xd5\x86\x9a\x44\xfe\xd9\x11\xaa\x2c\xc6\xc6\xa2\x1d\xb7\x3d\x41\x7d\x0d\x62\xe2\x6f\xc9\xc7\x51\x3d\x2a\xbc\xab\x05\x25\x57\xff\xea\x3f\x67\x7c\x3a\xf0\xff\x0d\x5b\xfa\x13\xdf\x87\x7c\xb7\xf0\x49\x6b\xe1\x13\xb7\xf0\xc9\x24\x3c\xf7\x7d\xa5\xa1\xb3\x17\xe0\x23\x23\xa4\x49\x2f\x67\x09\xeb\x74\x04\x95\x90\x6b\x5d\x04\xe5\x69\x23\x86\x43\x15\xda\x67\xcf\xbd\x16\x40\x4e\x81\x63\xdc\xea\x67\xe9\x11\x85\x07\xf0\xd7\x77\x6f\x7e\xaa\x8e\x4d\x74\xf5\xb8\xba\xc3\x21\x68\xa0\xc6\x51\x60\xf0\x14\x83\x8c\x90\xd4\x09\x72\x11\x76\x90\x96\xde\x71\x49\x02\xdd\x08\xf2\xd9\xc2\x04\x5a\xc2\xf7\x64\x23\x17\xc6\x2d\xa8\xc5\x73\xd7\x14\x78\x13\xe7\xc6\x90\x1a\xa4\x59\xb6\x5c\x66\x2c\x8c\x95\x5e\xfb\x2d\x18\x64\x79\xe5\x31\xec\xec\xdc\xaf\xbc\x14\x90\x20\x43\x08\x90\xa0\x4c\xe2\xcf\xb9\xef\x23\x0c\x12\xcf\xc3\x57\xde\x0c\x83\x46\xf5\xf0\x95\xf7\x06\x83\xc0\xf6\xf0\x95\xf7\x0e\x83\x46\xf8\xf0\x95\xf7\x1e\xc3\x55\x4e\xef\xc3\x39\xec\xe0\x7c\xf8\x11\xa4\xcb\xb6\xaa\x6f\xe1\x67\x88\xd6\x57\x62\x96\x9f\x67\xa9\xa8\x02\x9c\x45\x11\x3e\x2d\xe1\xfb\xf1\xdb\x09\x86\x97\x0e\x89\x7e\xe3\xac\x0e\xd7\xc8\x86\x0e\x9e\x50\x7c\x1c\xd0\x81\x44\xfa\xe7\x8a\x51\xb1\xee\xab\x43\xb4\x5e\xad\x68\x3e\x8b\x0a\x8a\x9c\xfd\xe9\x9d\x6b\xfc\xd1\x72\x3c\xca\x47\xdc\xa3\x58\x52\x73\xd6\x9b\x67\x8c\xff\x10\x2d\x93\xb4\x41\xce\xbf\x46\x6f\xb3\x69\xc6\x33\x04\x07\xe8\x47\x9a\xde\x50\x9e\xcc\x22\x11\x78\x9a\x27\x51\x8a\xe0\xa0\x88\x58\x71\x54\xd0\x3c\x99\x7f\x2d\x89\xba\x02\xf5\x2e\xf9\xdc\xb8\x01\x1a\x9c\x4a\x15\x9e\x4a\xfe\x4d\xca\xe8\xaf\xc4\x4f\xdd\xf0\x4a\xc8\x07\x6b\x98\xd7\xb2\xbd\xa5\x57\xeb\x34\xca\x61\x55\x65\x9c\x8f\x04\x1f\x3f\x87\x45\x2d\xe3\x6b\x1a\x27\xeb\x25\x2c\xab\x7c\x8b\x91\x10\x2a\x16\x70\x53\xcb\xf7\x4c\x50\xce\x69\x95\xeb\x66\x24\xc4\x80\x1b\xc1\xfd\xf7\x16\x7c\x99\xfe\x60\x3a\x70\x5f\xe5\xb9\x1a\x05\x67\xe1\x15\xdc\x11\xd6\x8b\xd2\xf4\xd7\x28\x4f\x22\xc6\x0b\xb8\x25\xac\xb7\xba\x7b\x9f\xbd\xa5\x4b\x78\x51\x23\xac\x0c\xc6\xa8\x1a\x54\xa4\xde\xa3\x78\xa7\x3c\x7a\x36\x86\xa0\x16\xa3\x7b\x5b\x8b\x53\x1d\xab\x45\x3d\x53\x64\xd6\x6d\xae\xf4\x7a\x69\x9b\x86\x00\xe9\x86\x99\xdb\x83\xd7\x64\x76\x1c\x9c\xc2\x3b\x72\x5b\xf7\xf8\xbf\x63\x8b\x76\x7c\xff\xe4\x1a\x50\x4e\x97\x08\x97\xf0\xa9\xf1\x4a\x2c\xe4\x90\x35\x29\xb8\x5a\x89\x55\x6f\xc3\x14\xaa\x96\x86\x14\x4c\xd7\xc3\x77\x1e\xc7\x90\x26\x8c\x6a\x25\x0d\x2b\xe1\xcf\x20\x18\x21\x24\x1d\x6d\xd4\xfb\x33\xef\xd4\x9b\x31\x8e\x6b\xa2\x37\x5e\x7e\xcc\x31\x20\xd9\xee\x70\x53\x42\x06\x77\xb8\x84\xf7\x64\xb3\x08\xc2\x4f\x5e\x0c\x97\x67\x10\xf4\x82\xb3\x73\x38\x0a\x7a\x03\x0c\x8b\xbe\x8c\x3d\xf3\x21\xe8\xf5\xe1\x48\x46\x9d\x84\x9f\xbc\x15\x9c\x5e\xe8\x8c\x3e\x86\xc5\xa9\x8c\x3a\x39\x15\xb9\x4e\x06\xd2\xb8\x0d\x16\x03\x19\xd9\x17\x91\x27\x27\xa7\x32\xdf\x59\xf8\xc9\x5b\x42\x5f\x40\x3b\x83\x5e\x30\x10\x74\x63\xca\x13\x9e\xd2\x40\x66\x0e\x44\xed\xe7\x83\x7a\x52\x5f\x16\x0a\x04\x9c\xc1\x39\xf4\x02\x0c\xd3\x2c\xbe\x77\x0a\xe8\xfc\x22\xb6\xaf\x62\x45\xde\xd3\x13\x1d\xbd\xe6\x3c\x63\x15\x0c\x01\xfe\x14\x9e\x63\x98\x45\xf2\x76\xaf\x2a\xd1\x17\x4d\x3a\x83\xde\x29\x06\xc1\xb6\xcb\x17\x74\x74\x42\x5f\x24\x04\xf0\x1c\x7f\x99\xd0\xba\xa8\x16\xde\x83\x46\xae\xf0\x1d\x28\x86\xfc\x0d\xec\xcc\xbf\xcc\x39\x83\x06\xb6\x87\x31\xec\x60\x7b\xb8\x82\x26\xb6\x87\x4b\xa8\x63\x7b\x38\x2d\xe1\x3d\x86\x17\xb0\x99\xa5\x19\xa3\xe1\x61\x50\x3a\x94\xf3\x93\x3d\x95\x19\x3b\xce\xaa\x1a\xfb\xf1\x37\xc4\x1f\xa9\x15\x1d\xba\x7a\x55\xb1\x52\x1c\x4b\xca\xdd\x52\xc1\x6e\xa9\xe0\xcb\xa5\xfa\xbb\xa5\xfa\x5f\x2e\x75\xb2\x5b\xea\x44\x95\xd2\xc2\xad\xf8\x57\x01\xe8\xf5\xa5\x92\x1b\x1e\xea\xf4\xe9\x2e\xc8\xd3\x2f\x37\x64\xb0\x5b\x6a\xf0\xe5\x52\x67\xbb\xa5\xce\xbe\x5c\xea\x7c\xb7\xd4\xf9\x43\x9d\x0e\x4e\xbf\xdc\xeb\x8b\x5d\x98\x17\x5f\x6e\xc9\xe5\x6e\xa9\xcb\x47\x20\x48\x0b\x5e\x05\x8f\x41\xac\x36\xcc\x0a\x1e\xec\xb9\x9a\xef\x89\x63\xd1\x2b\x68\xfb\x7b\x32\x46\x2c\x63\x14\xc1\x27\xcf\x87\x3e\x04\x70\x14\x80\x0f\x01\x04\x12\x40\x00\x27\x82\x46\x89\xb4\x13\x91\xd6\x07\x91\xa9\xaf\xd3\x06\x55\xda\x89\x4a\x3b\x81\x53\x9d\x76\x61\xd2\xfa\x70\xaa\x60\x9e\x8a\xfc\x0a\xb6\x5f\x15\x1c\xa8\xc4\x81\x28\xa0\x12\x4f\x77\x12\xcf\x64\x09\x95\x6a\xe1\x0a\x70\xb2\xce\x73\x91\x1a\xc8\x86\x09\xca\xa7\x52\x07\x22\xf5\x04\x7c\xb8\x30\xa9\x27\x02\x72\xdf\xa4\x9e\xa9\xd4\x4b\x49\xe4\x54\xea\x99\x49\x3d\x33\xa9\xa2\xe4\x29\xa8\xb6\x07\x17\x70\x62\x92\xcf\xe1\x48\x74\x33\x08\x20\x18\xe8\xe4\xbe\x6f\x92\xcf\xe1\x42\x27\xf7\x21\x38\x97\x63\x35\x80\x7e\x1f\x4e\x9b\xc9\x27\x10\x5c\x9a\xe4\xd3\x2a\xf9\x52\x27\x9f\x42\x3f\x30\xc9\x67\x26\xf9\x42\x24\x8b\x61\x0c\x24\xcc\xbe\x1c\x9c\xfe\x05\x0c\x4c\x72\xe0\xeb\xf4\x33\x01\x54\xa5\x9f\xf8\x4e\x7a\xa0\xd3\xcf\x05\x54\x9d\xde\x37\xe9\x97\x55\xfa\x85\x00\xab\x86\xf7\xe4\x14\xce\x6c\x7a\x1f\x8e\xce\x44\xfa\x25\xf4\x2f\x4d\xfa\x99\x49\x0f\x64\xb7\x64\x06\x31\x20\x12\x7b\xe0\x02\x4e\x2e\xe0\x7c\x37\x43\x00\x27\x27\x3a\xc3\xa9\xef\x66\x38\xd5\x19\xfa\x70\x32\x30\x19\xfa\x36\x83\xc4\x90\xa3\x73\x91\xe1\x44\x54\xad\xa6\xf1\xf4\x14\x2e\xaa\x0c\x03\x9d\xe1\x54\x54\xad\x33\x9c\xc1\x05\x9e\xc0\xcf\x64\xa3\xfc\xf2\xbf\x95\x0f\x14\x86\xa7\x25\xbc\x25\xcc\x0b\xce\x30\xfc\x44\x3c\xe6\x05\xe7\x18\xa4\x53\x3b\x3c\x64\xde\x40\xf1\x3f\x1f\xdb\xaf\xda\xf3\x91\xbb\xe5\xc9\xbb\x38\xd5\x06\x13\xd2\x12\x5e\x93\xc7\x9c\x6b\xc3\x53\xa2\x4e\x46\xf7\x9f\xa4\xc3\x7a\xd5\xe6\x80\xfe\x81\xa3\xe0\xd7\x63\x2a\x89\x01\x46\x58\x3b\xf5\x7c\x09\x9f\xe1\x15\xd9\x2c\x43\xfd\x4c\x22\x82\x55\x68\x5e\x37\x44\x52\x80\xe2\x21\x7a\x9f\xad\x10\xe4\x21\x7a\xab\xb8\xcb\x69\x88\x9e\xa9\x07\x13\x21\x0d\xd1\x2b\x3a\xe7\x08\xee\xc2\xb1\xfe\xd2\xb9\x26\x70\x1f\x8e\x55\x49\x93\x7b\x52\xc2\x0f\x64\xa3\xea\xf9\x47\x88\x96\x77\x48\x3f\xee\xf8\x7b\x88\x96\xf7\xc8\x9c\x0c\xfe\x43\x3d\x4c\xaa\x43\xbf\x87\x68\x75\x8f\x4a\x78\x46\xbc\x97\xcd\xf7\x6d\xed\xb5\xee\xbe\xba\x64\xf0\x43\xe5\x18\x7a\x4c\x27\x43\x4a\x7e\x30\x1e\x97\xb9\x7d\x43\x1a\xc9\x2b\xcf\x7a\x8a\xde\xaa\xcb\x18\x7d\x2c\x4f\x92\xfd\x09\x64\x44\xde\xcd\x4d\xc8\xab\x71\x3e\x81\x88\x7c\x3f\xce\x26\xdb\x2d\x42\xc3\x9a\x69\x9a\xb1\x38\x89\xf0\x28\xda\x7b\x2b\x39\x91\x97\xc2\xc3\x71\xd2\x8d\x26\x25\x7c\x26\x9b\x12\x5a\x72\x59\x81\xe0\xb3\xf2\xff\x2b\xfe\x90\x97\x1e\xc5\x18\x3e\x4b\x67\xd9\xf0\x1b\x19\x23\x39\xf7\x62\x6c\x97\x82\x91\x5f\x4e\xc5\x4f\x2a\x7e\xee\xc4\x8f\x90\x06\xc4\x30\xaf\x44\x8e\x95\xc8\xb1\x12\x39\x56\xa9\xe4\xd9\xc5\x8f\xc8\x61\xe6\x57\x7f\xa8\x99\x51\xdf\x7a\x5e\x75\xc8\xcc\xad\x0e\xea\x59\xd5\xd3\x66\xbf\x7e\x47\xf6\x15\xcc\xea\x4b\x01\x75\x4f\x78\xab\xa0\x05\xeb\x9c\x08\x57\xa1\x7f\x54\x9f\xbf\xbb\x36\x22\x5f\xd5\xfc\x51\x29\x1e\x7d\xbb\xbd\x30\x16\x16\x3b\x4f\xcb\xb5\x9a\xfa\x3e\xa1\x65\xd8\x7c\x46\xaf\x35\xa3\x18\xf1\xb0\x5d\xb4\x75\x0f\xdd\x1d\xbb\xd6\x5f\xf6\xbc\x8e\x5e\x7b\x10\x68\xc7\xef\x7b\xee\x3c\x8b\x9b\x4f\xc8\xce\x15\x80\x9d\xcb\x37\xce\x93\x80\xda\xc5\x97\xba\x77\x15\x4d\x0b\x8f\x57\xea\x13\xfe\x2d\xf1\x47\x2c\xdc\x35\xd1\x18\x1d\xb1\x10\x55\x7e\x4c\x19\x2e\xe5\x85\x0c\xe9\x56\x7e\x53\xba\x86\xba\x3f\x56\x03\xfe\x95\xa7\x1d\x7f\xb5\x7a\xbe\xa4\xcd\x6b\x48\xc6\x9b\x2a\x21\xe4\x37\xd7\x3d\x80\x31\x79\x58\xa7\xa9\xb6\x1d\xfa\xc5\x7b\xe6\x31\x0c\x1c\x43\x56\x7b\x81\xae\x69\x76\xd4\xf6\xfa\xa1\xf5\x86\x29\x5b\xd6\x93\xcf\xbd\xac\xb2\x84\xf1\x62\xbb\x7d\x5a\xb9\x5a\x6d\x19\xf4\x0c\x12\x77\xd8\x7b\xeb\x95\x27\x0d\x95\x8b\x71\x32\xc1\x13\xc2\x3c\x2e\x3e\xec\x90\xd4\x1d\xd9\x69\x82\xf1\x93\x24\x18\xb8\x7a\xf9\xfe\xa1\x56\x34\xfc\x10\x7f\x01\x0f\x32\xd9\x20\xdd\x90\xdc\x69\x88\x19\x3f\x8f\xe3\x52\x76\x23\xc7\xce\x73\x02\x1f\x65\x9e\x1f\x7b\xd6\xfc\x51\x10\x9a\x1f\xf5\xb3\x04\xd2\x56\x92\xfc\xe6\x3c\xd7\xfd\xef\xde\xb8\xbb\x50\xfe\x47\x97\xeb\xa4\xfe\x14\x8d\xc0\x96\x8d\x5e\x9f\xa1\x68\x39\xab\x79\x05\xd7\x46\x86\xbb\xd5\xd6\xaf\x44\x08\x3a\xec\x0f\xf3\x6f\xa8\xba\x73\x25\x7d\x00\xda\xda\x73\x8b\x27\xbe\xeb\x58\x83\x7b\x01\x0e\x83\x7a\x8c\xa0\xe5\x38\x6c\x71\xd8\xf1\xf0\x1b\x81\x7a\x6d\xb9\x6f\xd7\xec\xae\x24\xc7\x5b\x83\x24\xaf\x58\x7a\x07\xd5\xfc\xf3\x01\x6a\x4a\xbe\x4d\xeb\x78\x66\xac\xcb\x36\x57\xb4\x46\x54\xac\xed\xa2\x18\x3c\x31\xc0\xe4\xd0\x07\xa6\xde\x0b\x10\xac\x48\x1f\xc3\xdf\x08\xf3\x4e\x34\xf7\xf1\xf7\xc7\x8d\xef\x9f\x73\x03\xed\xe0\x71\xe3\x24\xc7\x9c\xd2\x28\x03\x65\xd7\xbd\x73\x2e\x12\x95\xef\xd2\x55\x24\x75\x29\xb0\xae\x52\x53\x91\x9a\x42\x5c\x51\x6f\x98\xab\x93\x89\xec\x2a\x8f\x56\x8b\xfb\xba\x1a\x6e\x53\x86\x95\x75\x6c\x75\xa2\xe0\xb4\x4b\x6c\x40\xb2\x0d\x72\xc3\x90\xf5\x09\xee\x48\xbf\xf8\x2b\xcf\x10\x34\x64\x34\xc1\xb0\x24\xd7\xde\x1a\xc3\x8d\xb2\x6a\x9d\x92\x5f\xbd\x18\xc3\x15\x71\x39\xb4\x8d\x03\x3c\xbc\x01\xeb\x77\x34\x44\x29\xcf\x91\xb1\xc8\x9e\x79\x37\x30\x85\x48\x69\x3c\xa4\x93\x60\x31\x66\xba\x01\xe1\x52\xfb\x7d\xda\x94\xa0\x5e\x79\x2d\xc2\xf7\x50\xb5\x24\x7c\xe7\x2d\x61\x85\xc1\xac\x8f\x29\xc8\x17\x75\xc3\x9f\xa1\x7a\xb7\xa1\x08\xff\xe8\x45\xf0\xf9\xa5\x74\x32\xf7\xb7\x5e\x54\xc2\x02\xc3\xfd\xee\x7a\xb9\x73\xd6\xcb\xfd\xb7\xc1\xe8\xfe\x28\x08\x7d\x0c\xb7\x24\x18\xde\x7e\x73\x3f\xbc\xed\x76\xf1\xdd\xf8\xf6\x28\x70\x57\xce\xad\x5d\x39\x57\xe4\xee\xe1\x77\x48\x1a\xbc\x2b\x2e\x31\x5c\xe1\xd2\x53\xae\xa5\xfe\xfe\x18\xe3\x54\x6d\x86\x42\xa7\x94\x56\x66\x28\xb3\x38\xb6\x66\x28\x74\x7e\x19\x5d\x5a\x23\x21\x3a\x38\x3f\x39\x3f\xb1\x96\x35\xf3\xc1\xc9\xc0\x1a\x09\xcd\x4f\x4f\x4f\x4e\xce\x8c\xed\x06\x1d\x9c\x5c\x9e\x58\xdb\x8d\xf8\xa4\x3f\xef\x5b\x23\xa1\xd9\x59\xff\xa2\x7f\x61\x6c\x37\xa6\xe7\xc1\x2c\x98\xb9\x96\x35\x51\xcd\x0c\x65\xd0\x1f\xf4\x1d\x33\x94\xe0\xfc\xf4\xd4\xda\x6e\xc4\x03\xdf\xf7\x7d\xf4\x98\x7b\x50\xc6\x1e\x6a\x3e\xa0\x97\xd6\x1e\xea\x82\x9e\xcd\xac\x3d\x54\x34\x88\xcf\xa2\x73\xd3\xd5\x8b\x60\x76\x7e\x71\x6a\xba\x7a\x76\x36\x9d\x9e\x59\x23\xa2\xd3\x59\x34\x1f\x58\x8b\x9b\xd3\x93\xc8\x3f\x3d\xb7\xf6\x50\x17\x17\xf4\x64\x66\xed\xa1\xe8\x79\x7c\x62\x4d\xbb\x82\xe9\x80\xf6\x2b\x8b\x9b\xe9\xe5\xfc\x6c\x16\xd9\xae\x9e\x5d\xce\xfd\xa8\xb2\x87\xf2\x7d\x7a\x76\x7e\x66\xbb\xea\xfb\xb3\x8b\xc1\xc9\x17\xbb\xea\xdc\xe1\xc4\x9b\x43\x37\xa0\xc8\xe5\xf1\xbf\xbc\xe9\xf6\x19\xfe\xea\x18\x72\xb2\x49\xe8\x2c\xdc\x4c\x13\x2e\xe4\x16\xc1\x84\xfe\x2d\x11\xbf\xaf\xe5\xef\x5f\xe4\xef\x7b\xf9\xfb\xb3\xfc\x7d\x21\x7f\xff\x29\x7f\x7f\x4f\xa6\x68\x02\xd3\x7b\x4e\x45\xd9\x67\xb2\xec\x33\x59\xf6\x99\x2c\xfb\x4c\x96\x7d\x26\xcb\x3e\x93\x65\x9f\xc9\xb2\xcf\x64\xd9\x67\x42\xba\xf8\x44\xe3\x66\xf5\xb2\x76\x59\xb9\xac\x5b\x56\x2d\x6b\x96\x15\xcb\x7a\x77\xaa\x95\xb5\xca\x4a\x65\x9d\xb2\x4a\x59\xa3\xac\x50\xd6\x27\xaa\x2b\x21\x53\xfd\x1d\x23\x04\xe8\x3a\x99\x26\x82\x36\x51\xf9\xe7\x4a\x85\xb8\x0a\xad\xd4\x1f\x7a\x27\xff\x7c\x56\xa1\xfb\x6c\x9a\xa0\x89\x6e\xb3\x06\x91\x66\x12\xc4\x55\x24\x41\xc8\x3f\x9c\xe6\x91\x04\xc1\x23\x09\x22\x92\x10\xb8\x0c\xdd\x67\xe2\xef\xa4\xac\x5d\x35\xd5\xdc\x1b\x24\x10\x41\x0a\x05\xcc\x60\x0d\x31\xcc\x61\x05\x0b\x58\x82\xa0\x62\x57\x70\x4f\x82\x6f\x9a\x94\xe5\xb1\xf7\xe5\xef\xc8\x78\x02\xb7\xc4\x87\x17\x9a\x70\xc3\xb5\xb9\x45\x9a\xcc\x3d\xf5\xd2\x0a\x75\x9d\x42\xb5\x18\xcd\xeb\x5d\xd5\xf2\x95\x62\xaf\x23\x84\xdc\xf7\xc4\xd4\xc1\xc2\x84\xd6\x2c\xb9\x03\x2e\xa2\xa3\x82\x6e\xb7\x7d\xbb\x57\x1c\x8a\x54\xa9\x27\x1e\xe9\xbf\xe1\x62\x14\x84\x7d\xd7\x47\xe2\x7d\x2f\xcd\x66\x51\x4a\x47\xe6\x23\x44\x08\xd6\x36\x5a\xbb\xab\xdc\x6e\x37\xa5\x3d\xe1\x91\xa5\x0a\xba\x8a\xd4\xad\x41\xe7\x5b\x94\xbd\xa9\xe5\x5a\x45\x33\x2a\xb3\xc8\x8f\x70\x21\x9f\x35\x38\x40\x70\x25\x12\xd5\x05\x11\x09\x7b\x4a\xfa\xca\xae\xe2\xde\x7a\x13\xd8\x6e\x91\x9c\x75\x04\x73\x72\xdf\xcb\xd6\x7c\x25\x5f\x3f\xd4\x7c\x09\xa4\xa6\xfb\xf3\x75\x9a\xce\xb3\x7c\x09\x85\x13\x28\xec\x6d\x82\x6c\xae\xb6\x81\x91\x93\x18\x8e\x27\x76\x5e\x64\x3b\xe9\x9d\x72\xdd\x38\xaa\x3e\xc3\xa3\x00\x22\xd2\xff\x86\x8f\x02\x7a\x12\x06\x7e\xff\x14\xbc\x98\x78\xd7\x44\x3b\xfe\xa1\x18\x7f\xe3\xe3\x4e\xc7\xbb\x26\x47\xd7\x18\x14\x47\xff\x62\xbb\x55\x53\xfb\x02\x8b\xa4\x17\x44\x8a\x1f\xf3\x34\xcb\x72\x25\x89\xa4\xd9\x95\x77\x8d\x8f\xed\x77\x84\x05\x18\x99\xd5\xc7\x70\xf1\xcd\x0b\xf9\x79\x81\x05\x0e\x6b\x57\xac\x72\xc7\x7f\x11\x7a\xca\xc0\xc2\xbb\x1b\xfb\x13\xe2\xc3\xdd\x38\x98\x10\x39\x9c\xf9\x78\x3a\x19\x27\x23\x24\x90\x02\x85\x48\xae\x51\x34\x19\xbf\x98\xe0\xd0\xbb\x25\xd7\xc7\x9e\x1c\xd9\x91\xf5\x14\xd0\x87\xc0\x7f\xf2\x02\x57\x0e\x8e\x02\x7a\x02\x2f\x30\x86\xa4\xd3\x89\xbe\x21\xde\xed\x13\x72\x81\x3b\x9d\x17\xdf\x5c\x74\x3a\xde\xed\x31\x89\xe0\x45\xb7\x8b\x41\xd6\xab\xfb\x7e\x6b\x7d\x1a\xf9\xdf\xbc\x18\xad\x42\x1f\xeb\x74\x42\x22\x5d\xd2\x72\x2b\xd5\x88\x76\x3a\xaa\xf1\x81\x01\x18\x4c\x48\xa0\x9f\xb2\x92\x83\x37\x4a\x46\xe8\x7a\x8a\x42\x74\xfd\xec\x81\x6e\xc1\x42\x02\x0a\x26\x44\xe3\x07\x21\x64\x3a\x12\x11\x95\x33\xbc\x50\xb4\x4b\x46\x99\x37\x85\x8e\x9f\x7d\x75\x0c\x08\xe1\x50\xc4\x02\x53\x0f\x48\x8a\x6f\x6c\x9a\xe5\x4c\x95\x08\xeb\x06\x22\x84\x31\x86\xd8\x64\x3a\x72\x92\xae\xc6\xe2\xcf\x64\xbb\x95\x20\x25\x36\xce\x46\x32\x97\xf8\xe9\xf1\xec\x95\x5c\x42\xf6\x35\x76\xff\x9b\x99\xe1\xbd\xf7\xe6\x9a\xc1\x5a\x64\x5c\x56\x1e\xf5\xdc\xac\x06\x54\xf5\x9c\x65\x0f\xc1\x12\x63\x40\xf2\x5d\x69\x85\x2a\x77\xa1\x97\x9a\x11\x2a\xc6\x2f\x26\x23\xf1\x13\x66\x62\x38\x5f\x4c\xba\x9e\x1a\x52\x3d\xa2\x08\x77\x3d\x31\xf4\x02\xbe\x5c\x9a\x85\x74\xaa\x56\x09\x74\xf3\x91\xf6\x74\x20\x72\x80\x5a\xb1\x6a\x08\xed\x3a\x79\x51\x86\x77\x8a\xaf\xbf\xc1\x18\x97\x49\x6f\x15\xe5\x3c\x89\xd2\x36\x27\x0d\xee\xb6\x68\xfd\x15\x72\x90\x2e\x8e\xaa\xab\x5d\x49\xd9\xea\xc0\x48\xbd\x0f\xd3\xe9\xa8\xbf\xb8\xc4\xea\x46\x24\x5f\x24\x05\x30\x6f\x70\xf2\xc5\x5b\x41\x3b\x7e\xd8\x72\xc2\x3c\x75\x3d\xa7\xc7\xbc\x5c\x5f\xd0\x09\xd4\xfd\x9c\x01\x86\x54\xc6\x47\x18\x0a\xd2\xd6\x9e\x2b\xf9\x0c\xcc\xfb\x45\x52\x8c\xaa\xcf\x70\x7f\xcb\x47\xea\x4f\x68\xe9\x8e\x74\x52\xe1\x6c\x4b\x8e\x4f\xb0\xf1\xa4\x7a\xef\xa7\xa6\xbd\xe4\xca\x89\x00\xc5\x25\x64\xf3\x79\x3d\x89\xb4\xbc\xb3\xe7\x68\x5c\x45\x8d\xf2\xea\xd4\x3e\x91\x0a\x0a\x37\x41\x9a\xd8\x51\xc2\xa0\xed\xd1\x3a\x07\xac\x14\xd0\xa5\x3f\x41\x6d\xf1\x9f\x35\xdf\x44\x69\x7a\x6f\x50\xae\x45\x32\x88\x60\x4d\xd0\xc7\x8f\x2a\xef\x51\x4e\xa3\x19\x3f\xd2\x97\x4a\x8f\x50\xd7\xf3\x8a\x71\x24\xd2\xd5\xe0\x7e\x5c\xb3\xe4\x8f\x35\xfd\x98\xc4\x1f\x3f\xa2\x09\x11\x89\x93\xed\xd6\xc7\xdd\x00\x77\xd1\xc7\x8f\x08\x43\x5c\xc3\x38\x3b\xaa\xe6\xfe\x87\x71\xa9\xe5\x09\x11\x4e\xbd\x20\x28\x51\xc7\xee\xdc\x78\xbb\x15\x11\xb8\x47\x97\x09\xe7\x34\x27\x33\xe5\x77\x69\x55\x18\x8f\x0d\xbc\x74\x19\x7f\x06\xd4\x7a\x3e\x70\xae\x28\xda\xdb\x9c\x57\x94\x3f\x77\xae\xd1\xee\xf8\xb9\x36\xed\xa1\x64\x53\xe2\xf1\x7a\x42\x44\xed\xa6\x72\xa0\xa5\xbc\x67\xae\x7d\x0a\xff\x96\xa4\xe9\x5b\x3a\xa3\xc9\x0d\x55\xda\x8a\x86\xb8\x2e\x8b\x3a\x6d\x95\xce\xc8\x54\xab\xad\xeb\xf4\x66\x1e\xe9\x96\x5e\x7e\x0d\x3d\x2f\x21\x39\x26\x84\x78\x11\xc9\xf0\x48\x20\x67\xb2\xdd\x06\xc7\x09\x21\xc1\x71\x14\x26\x87\x44\x6c\x0e\x87\x24\xc2\x23\x46\xfc\xd0\xdb\x6b\xd9\x92\x43\x86\xc3\xc0\x3f\x3f\x39\x3f\x0d\x2e\xfa\x27\x20\x20\x79\x6c\x4b\xc4\x4e\xe9\x76\x4f\xbe\xd6\xa0\x6b\x07\x86\xd5\x71\x5a\x02\x91\xe8\xb4\xba\xcb\xda\xf2\xcc\x91\xd3\x01\x79\x3f\x39\xa7\xac\x04\x56\x7a\x79\xe5\x10\x1d\x0f\xe3\xde\xce\xdd\x65\xe2\xa9\xe7\x12\xc4\x18\xa7\xbd\xa8\xa7\xc8\x5b\x2f\x29\xde\xd2\x3f\xd6\x49\x4e\x63\x60\xc6\xd3\xb3\x8b\xe0\x3b\x18\xe4\xcc\x18\x7f\x18\x83\xa4\x13\x70\xf3\x62\x8c\xf4\xe5\x2f\x9f\x11\xf3\xb0\x20\x73\x19\xfb\x45\x3e\x29\x40\xea\x5e\x5f\xfc\x43\xe2\x79\xfe\x96\xf6\xb2\xa9\x7c\xba\x23\x7e\x96\xf0\x02\x77\x18\xee\x74\xe4\xe3\x16\xf2\x1a\xb4\xd7\x06\x53\x40\x6d\x60\x26\x7f\x10\x33\x1f\x87\x56\x46\x9f\xec\xb6\x67\x28\xe7\xc0\x8d\x21\xca\x19\x09\x1f\x55\xb3\x1e\xf2\x1a\xee\x7e\x9f\xc4\xaf\xb3\x35\xab\x2d\x00\x09\x46\xaf\xf5\xf1\x7a\xa2\x91\xa3\x8a\xe8\x89\x51\x91\x55\xe9\xc1\x52\xfd\xa1\x2e\x12\x3f\xaa\x5d\xd4\x6d\xd7\xee\x9a\xfa\x85\x2d\xff\x7c\xd3\xe6\xf3\x46\xdb\x04\x58\x33\x1f\xfb\xf0\xb6\x02\x30\x6a\x02\xbc\xa2\xdc\xc3\xaa\x71\xfb\x70\xdf\xab\xf5\xdc\x60\x3f\xec\x7a\xbe\x1a\xfb\x93\x90\x62\xd5\x3e\x89\x85\x9a\x0a\xa8\xe1\xdb\x5d\x2e\x66\x63\xee\xcd\xea\x0b\x26\xdb\x5d\x30\x90\x61\xd8\xfc\x9c\x67\xca\x18\x3e\x86\xe7\x19\x2b\xd6\x4b\x9a\x87\xf3\xb2\x94\x92\xfe\xfa\x51\x5b\xb2\x75\x40\x71\xe1\xba\x9b\x58\xed\x38\x91\x48\x9c\x18\x31\x67\x49\xba\xe7\x21\xbd\xd4\x53\xae\x83\x80\x63\xd7\x15\x05\xcf\xae\x29\x2b\xde\x67\x3f\xe8\x32\x24\x6d\x49\x54\x7e\x90\xc9\xdc\xb8\x24\xaa\x5c\x23\x8f\x91\xf7\xe1\xc3\x87\x0f\x3d\x8c\x00\x79\xe3\x0f\x1f\x8e\x7b\x13\x3c\xf2\x46\xa1\x37\x0a\x3f\x7c\x08\xbd\x0f\x1f\x6e\xbb\x58\x7e\x7b\x9e\xfc\xf3\xe1\x43\x6f\x3b\xfe\x97\xf8\xeb\xe1\x09\xee\xe2\x0f\x1f\x30\x1e\x6d\x1f\x4c\xf6\xc6\xdd\x27\xa3\x09\x1e\x6d\xbd\x0f\x1f\x9e\x60\x8c\x8c\x79\xc1\x56\xbb\x5d\xde\xf1\x8d\x64\xbd\x97\x40\x2e\xc4\xcb\x84\xf8\x10\x11\x5f\xbe\x19\x07\x6b\xf9\x08\x5d\x2f\xa6\x69\xb2\x4c\x38\xcd\xb7\x5b\x74\x8c\x86\xea\x59\x19\x8f\x91\xac\x47\xef\xe8\x4c\xfa\x37\xd6\xef\xf8\xa8\xa3\xbc\xb9\x3a\xca\x5b\x11\xa6\x0e\x23\x84\x74\x9a\x76\x89\xf1\x05\x1e\xc1\x4a\xb0\x43\xab\x6e\x6c\x34\x6b\x73\x9c\x76\xc9\x7c\x1c\x4c\xaa\x87\x26\x17\x84\x8e\xa3\x09\x2c\x89\x74\xcc\x7b\x43\xd8\xf8\x64\x02\x53\xc2\xc6\xa7\x13\xb8\x22\x6c\x3c\x98\xc0\x3d\x61\xe3\xb3\x09\xdc\x11\x36\x3e\x9f\x0c\x53\xe9\x24\x45\x32\x32\x29\x96\xcd\x57\xf8\x79\x4b\x54\x7b\x97\xe6\x3d\x9c\x45\xa7\xb3\x38\x24\x64\x09\x2f\x08\xea\x0a\x7e\xf4\x7e\xbb\x45\x4f\xe4\x07\x5c\x13\x34\x6a\x44\xbd\x91\x2d\xd8\x6e\xd7\xf0\x9c\x4c\xb7\xdb\xab\xa1\xae\x44\x79\x83\xb8\xd9\x6e\x93\x6e\x17\x56\xca\xf9\xfe\x72\xbb\x45\x08\xec\x70\x85\x6f\xf4\x4b\x63\x51\x1a\x5e\x43\x4e\x57\x34\xe2\xe1\x0b\xd0\xbc\x6c\x78\x0b\x51\xc1\x69\x9e\x14\xd7\xe1\xe1\xe1\x1d\xac\x22\xb1\x89\xb1\xf0\xf9\x68\xe6\x3d\xc7\xe1\xdd\x08\xf5\x9e\xa0\x10\x8d\xff\x85\xba\x85\xf7\x06\x77\xd1\xa4\x3b\x42\x65\xf5\x2e\x4f\x64\x7d\x17\x75\x3a\x7a\x78\x95\x53\xee\x08\x63\x48\x3b\x9d\x6a\x30\xf2\x86\x57\x66\xc3\x9c\xb1\x59\x16\xd3\x5f\xde\xbe\xf4\x68\xc5\xfd\x1f\x8f\x3f\x1c\x8f\xfe\x7b\x72\x7c\x05\x6d\x5e\xee\xfe\x07\x75\x95\x7b\xf0\xe7\x59\x4c\x8d\xbf\x77\x2d\x41\x04\x67\x0d\xe7\xef\xb5\xe7\x86\xd3\xa6\xaf\x9c\x16\x77\x4c\x90\x11\x7f\x98\x55\x2e\x99\xb2\x6e\x17\x57\x72\x83\x39\x33\x18\x67\x13\xe9\xd7\x39\x9b\xd4\xbc\x8e\xff\xcb\x1b\x85\xa8\x2b\x52\x7b\x7a\x24\xbb\x08\x7f\x85\x20\xf6\xb8\x63\xe5\xec\x6c\x8f\x59\xd5\x9a\x44\x20\x7b\x4a\xe4\x9b\x6d\x50\x10\x2f\x13\x1f\xb8\xb7\xca\x29\xe7\xf7\xa3\x28\xb4\x23\x55\x3d\x92\x32\x23\xfe\x70\x56\x35\x75\x66\x9c\xfd\xac\x09\x1d\xcf\x26\xad\x66\xda\x6b\xbd\x48\x60\x4e\xd2\xf1\x5a\x3e\x20\x31\x19\x5a\x37\x79\x73\xc9\x73\xad\x7b\x06\x63\xf0\x66\x6d\xa4\x9e\x4e\xc7\x4b\xba\x64\xdd\x53\x58\x86\x87\x82\xac\x26\x6c\x4d\xcb\x36\x4d\xcf\xd7\x2f\xee\x56\xf2\x6a\xce\x01\xfa\xba\xab\x6a\xe9\x7e\x8d\x0e\x78\x76\x30\xa5\x07\x5a\x8a\xf8\x5a\x9e\xb1\xe5\xde\x1c\xab\xd3\xfb\x75\x4f\x21\x67\xab\xea\x68\x3f\x40\x96\xf1\x03\x55\x10\x0e\xa6\x6b\xf1\x2d\x77\xfd\xf8\xe0\xff\x7d\xdd\x6d\x98\x9b\xcf\x71\x17\xfd\x3f\x84\xcd\xe3\x5f\x73\x33\xe5\x8d\x3e\x9b\x9e\x0d\xff\x7c\x43\xa6\xf4\x80\x2e\x57\xfc\xfe\x6b\x5c\x9a\x69\x5d\x91\xff\x2f\x7b\xff\xba\xde\xb6\x6e\x2d\x8c\xc2\xff\x7b\x15\x14\xbf\x4c\x2d\x20\x04\x69\x52\x3e\x53\x86\xf5\x66\x3a\x76\xa7\x57\x73\xf0\xb2\x9d\x39\xdb\x2a\xaa\x1f\x9a\x84\x24\x26\x14\xa9\x92\xa0\x6c\xc7\xd2\x7b\x2d\xdf\xb5\xec\x2b\xdb\x0f\x06\x00\x1e\x24\x39\x49\xd7\xda\x3f\xf6\xdb\x67\x37\xb3\x16\x88\xf3\x71\x60\x8c\x81\x71\x70\xfb\xf3\x13\xdd\x56\x7f\x2e\xd6\x28\x1e\xa3\x88\x16\x68\x3c\x9c\x8f\x30\xe9\xa4\xc3\x70\x24\xc9\xf6\x68\x3b\xdf\xac\x6e\x33\x48\x92\x8d\x76\xc1\x7c\xbd\x8c\xd5\x7b\xee\x3f\xcc\x1f\xce\x45\xa4\xe7\xc2\xa2\x30\x19\xf3\x81\x5e\x5a\xbf\xac\x61\x2d\xb6\xa2\x15\x78\xa5\x92\x5d\x2e\x1d\x0d\x2c\x06\xf5\xb9\x1d\x37\xcf\xed\xff\x73\xa7\xd6\x2f\xd0\xf8\x5f\x9b\x9c\xff\xce\xc4\x98\xff\x61\x45\xd6\x7f\x98\xff\x01\x13\xa1\x67\x40\x0f\xda\x10\x71\xab\xca\xd6\xdf\x6a\xab\x71\x1b\x56\x8f\x1e\x0d\x1d\xeb\xf5\x80\xfe\xa3\xe3\xbf\x7a\x5e\x21\x3c\xfc\x3c\x5a\x7e\xde\xf9\xfc\x79\x84\x77\x26\xc4\xfc\xfc\xf9\x95\x67\xae\xdb\xc9\xde\x56\x07\xed\xf8\xaf\x3e\xef\x88\xcb\x74\x5b\xb1\x35\x93\xd7\xf0\xba\x4c\x39\x61\xab\xb6\x91\x8a\xa6\xed\xc8\x82\xc1\x1b\xd3\x82\x01\x23\x24\x36\xd7\x6c\x4d\x80\x55\x2e\x04\x9e\xdd\x53\x01\x7d\x52\x22\xa8\xf4\xda\x32\x4c\x26\x6e\xd9\x54\x02\x23\xc9\x70\x20\x31\xed\x78\xe0\x87\x97\xa5\x11\x09\x24\xdc\x72\xfb\x49\x0d\x89\x12\x0d\x89\x42\xca\x86\x49\x0b\x12\x55\x30\x34\xc4\x81\x45\x0b\x14\x36\x1c\x3b\x8f\xc5\xb7\x86\x30\x64\x4e\x4d\x00\xa7\x61\x03\x96\x9a\x7d\xc5\x27\x08\x31\x09\x15\xc0\xe8\x76\xd1\xdc\x52\x79\xc7\xd6\xdc\x32\xf1\x6b\x13\x93\xc0\xa2\x73\x1a\x56\xa7\x7a\x10\x6a\x40\x36\x18\x5b\x26\x32\x21\xdf\xc0\xf4\x75\x31\x1d\x25\xe2\xea\x2f\x53\x92\xfe\x53\x5a\xa0\x74\x0d\x03\xc1\x64\x46\x03\x85\x4c\xd8\x53\x0d\x4a\x28\xa5\xd3\xca\x10\xe8\x72\x89\x02\x8a\x66\x03\x9d\xcd\x25\x75\x46\x3f\xc0\x96\x6c\x7b\x2a\x7e\xe9\x2b\xd1\x32\xf4\x3a\x1e\x98\xaf\x4c\x3f\xeb\x76\x67\xb0\x5e\x68\x40\x21\xcf\xf2\x15\x36\x49\x89\x5a\xb7\x8d\x69\x05\x04\xec\xf8\xb4\xac\xa2\xcf\xd7\x6c\xad\x6d\x59\x5c\xa2\x56\x94\xb0\x26\x3b\x59\xd6\x3b\xd8\xc2\xd3\x10\xf7\x7a\x56\xe6\xa1\x76\x96\xb1\xf3\x19\xa1\x41\xe7\xf3\x00\xef\x4c\x00\x9c\xa6\xb8\xf6\xc1\xec\xf6\xf3\x93\xb4\x69\x3b\x95\x37\x71\x95\x5c\xa3\x29\xd2\x1d\x65\x85\xa5\xc0\x67\x85\xa8\x74\x3c\x8d\xa9\x74\xbc\x0a\x55\xe9\x78\x0d\x5c\xc5\xab\x50\x15\x70\x62\x59\xdd\xad\xf2\x7c\x48\x6b\x90\x7e\x2e\x28\x87\x75\x34\xbd\xee\xe9\x70\xb4\xed\xb2\x57\x28\xcb\x1c\x89\x6b\x1c\x8a\xa8\xb1\x37\xda\x68\xae\x02\x2c\x62\x5e\x63\xb8\x62\xdb\xd4\xab\xa2\x5a\xf5\xd7\x7b\x51\x59\x0a\x89\xa5\xc9\x3b\x11\xa9\x33\x4b\x93\x8a\x3f\x34\xcb\xf6\xa7\x9d\xd7\x7f\x92\x88\x89\x1d\x14\x45\x3c\x49\xff\x84\x42\x6c\xdc\xc4\x69\x94\x33\xe3\x26\xcb\xa7\x65\xf1\xa7\xff\x25\x36\x5e\x5a\x30\xe3\xfd\xe5\xed\x9f\x5e\xef\xc8\x71\x7f\xd7\xaa\x55\xb6\x69\xa8\xad\xad\x64\x5a\xdb\xcc\xad\x73\xcc\x55\xda\x65\x71\x5e\x59\x14\x5c\xb3\x1b\xdd\x30\xc5\xbb\xf5\x6d\x48\xd5\x29\x47\x62\x84\x41\xaa\xee\x53\x69\xd1\xce\x78\x88\xf9\x14\x64\x88\x8c\x2c\x37\x6a\x4e\xe4\x9a\x68\x12\x62\x78\xb5\xc5\xd2\x1f\x06\xfb\xa2\x02\xc9\x68\xb5\xa2\x04\x3f\x3a\x9e\x22\xc1\x45\x8f\xd4\x8d\x64\x06\xf7\xa1\xc4\x14\xd8\x70\x7f\x44\xcd\x88\x99\xc4\xdc\x6f\x98\xb1\xdb\x62\x2f\x0c\x31\x3c\x74\x47\x75\xa5\xb5\xe5\x4f\x30\x33\xed\xf6\xd3\x13\xcf\x05\x8b\x8c\x7c\x68\xde\x99\x96\xb2\x7e\x37\xce\xb3\xd9\x99\xba\x17\x51\x8a\x47\x34\x05\x70\xe9\x7a\xbd\xdd\xbd\xfd\x83\xc3\xa3\x63\xb3\xf3\xdd\x56\xf9\x8b\xf6\xa0\xa5\x40\x5b\x25\x9c\x62\xe2\xf6\x80\x73\x5a\x19\x80\x11\xc3\x8d\xd8\x78\x32\x8d\xbf\x7c\x4d\x66\x69\x36\xff\x67\x5e\x70\xb3\x16\x99\xdc\xee\xe4\x38\x1f\xb2\x11\x70\x5e\xc9\xf6\xf2\xf5\x6c\x81\x0c\x54\x6b\xee\xd1\xf3\x8a\xe4\x8d\xae\xd5\x26\xc1\x74\x1f\x57\x2b\x84\x07\xad\x32\x6b\xce\xea\x6b\xa2\x31\x21\x05\x15\x9b\x8c\x84\xd4\xeb\x87\x1b\x4f\x9b\x12\x31\xd6\xd9\x4b\x23\x4e\x8d\x4a\x04\xb4\x7e\xde\x0c\x47\x18\x67\x92\xcc\x4f\x49\x89\xbb\x5d\x54\x0c\xcb\x11\x4d\x87\xe5\x08\x36\x42\x8e\x9f\x13\x9a\xa3\xb4\xbe\x19\x23\xea\xf6\xa3\x13\x6d\x6f\xa8\x1f\x59\x16\x8e\x75\x05\xc9\x30\x1a\xc9\x3a\x44\x68\x04\x86\x8a\xa2\xd1\xa8\xa6\x9c\x8a\x2d\xef\xee\xb0\x2a\xb5\xd3\x03\x94\x6d\x31\xbd\x15\x93\x80\xee\xfc\xc3\x16\x14\xfb\xe7\xc8\x12\x7f\x9d\xcf\xd1\x6b\x41\x91\x3b\x9f\x23\xa0\xdc\xd9\xd0\xb2\x47\x03\xf1\x31\x78\xb5\x13\x93\x44\x3e\x00\x85\x2c\x16\xd3\x54\x3f\x06\x91\x90\x9a\xc3\x5f\xe3\x89\x7c\xfa\x92\xea\x9a\x23\x43\xd0\xdb\xa1\x65\xaa\xc8\x79\x2e\xc0\x73\xbc\x60\xc6\x34\x28\x8c\x59\x96\x33\x83\x4f\x83\xd4\xf0\xf6\x0d\xb1\x20\xf1\x38\x0e\x83\x94\x1b\x51\x3c\x89\x79\xe1\x1b\x26\x89\xa8\xc7\xbc\x3d\x32\xa6\x43\x90\xe7\xf7\x5c\x97\x78\x6c\x97\x78\x6c\x8f\x78\x6c\x9f\x78\xec\x80\x78\xec\x90\x78\xec\x88\x78\xec\x98\x78\x4c\x64\x62\x9e\x27\xfe\xf4\xc4\x9f\x5d\x41\xb2\xb7\xdc\x8b\x4f\x6b\x96\x9d\xbb\xac\x78\x7d\xec\xd4\x5d\x2e\x19\x3c\x00\x72\x9f\xdb\x5e\xc3\x55\x25\x30\xc9\xf5\xd9\x23\x29\xc9\xa9\x07\x1c\x61\x45\xe7\xc7\x94\x0d\xdd\x91\x65\x9a\xfd\xfc\x24\xeb\xcb\xac\x29\xf5\xf6\x6c\xc4\x29\x1b\xe6\x96\x25\xd2\x70\x65\x40\xd5\xb6\xfb\x9c\x9a\xae\x69\x71\xc0\x11\x39\x20\xf6\x19\xad\xac\x83\xef\x1d\x81\x57\xd2\x06\x66\x6b\xdb\x19\xee\xd7\x6f\xdb\xd5\xe5\x9f\x59\xde\x72\xe9\x35\x2e\xea\x45\xcb\x03\x28\xf4\x31\x94\xae\x31\xa5\x73\x68\x70\x00\x58\x48\xcd\x76\x12\x52\xee\x48\xcf\xd0\xc1\x72\xd9\x49\x5a\x62\x95\x02\xb6\x82\x87\xdd\x4c\xfa\xda\x89\xbb\xdd\x4e\x2c\x82\xe9\x72\x99\x57\x39\x07\xf9\xc0\xf5\xed\xc4\x0f\x44\xfe\xa0\x43\xab\x3a\x02\x59\x41\x70\xe2\x92\x9c\x16\x94\x86\x04\xbc\xa7\x57\x22\x7e\xa2\x5c\x27\xfb\x47\x3a\xf0\x7c\xdb\x93\x1e\x5e\x75\x52\x71\x1a\xea\x78\x31\x2f\x09\x45\x05\xcd\x34\x8e\x73\x82\xc2\x6a\x9e\xf0\xa0\xf0\xc5\xb0\xdc\x7e\x70\x92\xf4\x03\x71\x54\xc6\x28\x1b\x06\xa3\x0e\x8d\x87\x41\xe5\x8d\x5d\xc4\x9c\x8a\x08\x5d\xab\x6e\x87\xd2\x70\xe0\xfa\x75\x73\xf5\x2c\xde\xd7\x76\x13\x04\xb8\x3e\xe1\xcb\x25\x3b\x4d\x97\x4b\xd6\xa1\xe0\x05\x49\x5d\x37\xf2\x9a\x09\x2d\x24\xd0\x37\x6d\x99\xd4\xc4\xd6\xa6\x05\x30\x36\xd0\x95\x0c\x4c\x23\x2b\xb9\x91\x8d\x8d\x3c\x48\x27\xcc\x37\x4c\xdf\x04\x3a\x2f\x48\x8d\x38\xe5\x6c\xc2\xf2\x46\x5c\xe3\xb4\xc8\x2a\x7d\xc3\xc4\x0a\xca\x8b\x7e\xd4\x5d\x9e\x34\xb9\xd0\x61\x65\xcb\x4d\x0f\x76\x8a\x98\xc3\x76\xbc\x3d\x0c\x2f\xc8\xcc\x09\x87\x7c\xf4\x4b\xaf\x43\xdd\xba\x86\xa7\x26\x19\x50\x0b\xc2\x7b\x83\x86\xe3\x34\xcb\x74\xcc\x86\xef\x3b\x9f\x61\x0b\xf1\x13\x77\x60\x32\xd3\x37\x99\x65\x62\x8b\xd7\x15\x3e\xb6\x60\x10\x01\x87\xcf\xfc\xc4\x95\xc7\x23\xa3\xa9\xa8\xac\x6f\x59\xbc\x9f\x59\x34\xc5\x7d\x46\x33\x8b\xad\xb4\x8f\x18\xcb\xe2\xa7\x28\xaf\x8e\x18\xae\x4a\x11\x6e\xd3\xbc\x6f\xdb\x55\x31\x8b\x66\xb2\x14\x3f\xc9\xbb\x5d\xc4\x2a\x7e\x9c\x4b\x78\xbb\xc3\x0d\xb1\x66\xb6\x42\xf1\x36\xff\xd5\xe2\xcc\x90\x98\x3c\x90\x73\xf2\x95\x7c\x24\x67\xe4\x86\x5e\x34\x3c\x8d\x3c\x37\xcc\x34\xfb\x17\x44\x53\x9b\x12\xd5\x04\xe6\xf1\xc7\xb1\x72\x81\xfe\x05\xee\xfe\x0b\xe4\x61\x72\x4b\x7b\x2e\xb9\xa2\x7b\xe4\x9a\xda\x87\xe4\x03\xed\x79\xe4\x8e\xda\x02\x68\xbd\xa7\xe2\xef\x1b\xda\xf1\xc8\x25\xf5\xc8\x37\xea\x92\x77\xf4\x59\xa1\xb3\xa6\x49\x26\x79\x56\xce\x41\x49\x71\xb7\xd6\x14\xff\x73\x15\xe9\xaa\x0c\xb5\x4c\x09\x31\x49\xc4\xc2\x78\x16\x24\x8d\x48\xc7\x24\xe3\x5c\x1a\x52\x69\x96\x6d\xc7\xd5\xd9\xff\xaf\xff\xbf\x49\x8a\x72\x2c\xbb\xb0\x22\x6f\x69\x03\x71\xd8\x76\x29\x97\x8b\x87\xc7\xa7\x6f\x0d\x0b\x97\x17\x4d\x20\x14\x93\x84\x84\xb5\xb8\x10\xb0\xe6\xa5\x2d\x81\x59\x93\x50\xb8\xa8\xa5\xba\x61\xda\x98\xf2\x1a\xa7\x5e\x4c\xe4\x41\xec\x76\x41\x72\x80\x39\x77\x71\x51\x5d\x33\xba\xdc\xcc\x01\xc3\x8c\x64\x91\xc5\x11\xea\x30\x27\x5c\x2e\x99\xc3\x4e\xdf\x0f\x66\x4e\x48\x67\x0e\x03\x7e\xa9\xcf\x1c\x76\x72\x07\x51\x43\x11\xe7\x8e\x7c\x24\x7e\x05\x38\x14\x91\xe2\xec\xc8\xcd\x82\xa5\x51\x02\x34\xde\xb4\xcb\xc8\x70\xb7\xeb\xbe\x66\x94\xba\xd0\x2d\xd1\xb0\xb7\xc3\x4e\xdc\x01\x62\xd4\x66\xc4\xf6\xb0\xef\x11\x71\x91\xfc\xef\xff\xad\xee\x8d\x90\xba\x24\xa2\xac\x1f\x9d\x52\xcf\xed\x47\x3b\xd4\x73\x89\xc0\x20\xfa\x0d\x9d\x0d\x14\xae\xf7\x15\x7a\x16\x42\xbf\x86\x6c\x84\xf1\x6a\x4a\xab\xa3\x5f\xb1\x50\x3a\x81\xe4\x65\x34\xd2\xaa\xa9\xcc\xd0\x8c\x4c\xc9\x18\xf7\x45\x17\xf7\xf6\x29\x9d\xb6\xd9\x25\x03\x34\xa5\xd3\xea\x34\xcb\x8e\xaf\x50\x48\xa7\xb5\x5b\x26\xc7\xc4\xf8\xd4\xf6\xc0\x4d\xe9\xb4\x25\x4b\x21\x90\x3f\x82\x22\x51\x01\x0b\x72\x41\xc1\xb1\x9d\x18\xe3\x53\x77\x80\x42\x10\xd3\x09\x69\x84\x49\x68\x51\x4b\x37\x11\x59\x1e\x26\xd0\x62\xc3\x39\x60\x84\xb1\xaf\xf3\x57\xa4\x6c\x35\xba\x7b\xc4\x49\x8f\xbc\xd5\x17\xad\xf9\x6b\x50\x30\x13\x13\xcf\x15\x7b\x42\x8d\xf2\x13\x9a\xa9\xb3\xc6\x30\xb9\xb5\x66\x0e\xb3\x3c\x72\x05\xab\xd7\x98\x14\xb2\x75\x21\x45\x1b\xee\x6b\xd6\xa1\xee\xda\x9c\xa9\xed\xd7\x58\xdb\xcd\xa9\x22\x17\xce\xdb\xf3\x5f\x3f\xfd\xb9\xdb\xad\x67\x66\xe7\x1f\xee\x67\xc7\x7d\xbd\xfc\xec\x80\x08\x4d\x05\x45\xf7\x5b\x77\x46\x69\xa9\x15\x34\xf4\xca\xfc\xcc\xd2\xf4\x25\x5e\xf1\xb6\x01\xdf\x48\x48\x23\xea\x92\x79\x35\x75\xfd\xe8\x64\x2e\xd1\xc6\x31\x4a\xab\x65\x8c\x55\xf5\x6f\x80\xe7\x75\x22\xf7\xad\xe9\x98\x94\xc6\x92\x75\x78\x1a\xe2\xe7\x90\xce\x6b\x16\x6c\x05\x88\x3b\x09\xac\x3d\x9d\xb6\xd9\x69\x6a\x43\xf0\xec\x5d\xf6\xa0\xe3\xf0\x72\xa9\x32\x36\x22\xab\x8c\x8d\xc2\x18\x0b\xc4\xb7\x23\xce\x84\xed\x49\x96\xb3\x6e\xb7\xb1\x0a\x8d\xa5\x03\x66\x84\x80\x92\x28\xa4\x68\x4a\x73\x34\x25\x5c\x20\x83\x33\xa7\xc0\x78\x63\xb3\x0e\xb6\x6c\x55\xbf\xde\x5d\x80\x6e\x09\x44\x1b\xf0\xac\xd6\xbc\x47\x18\xe6\x0e\x26\xba\x31\xa7\x9b\x19\x6d\x7b\x2e\x10\x32\xd8\x62\xd5\xf6\x26\x96\x35\x97\x9c\xe7\xb9\x4d\x23\x32\xee\x76\xab\x0d\x32\x3f\xf5\xf6\xc5\xe5\x74\x7a\xec\xba\x87\xde\xf1\x71\x6f\x7f\xef\x70\xcf\x3d\x3e\xf6\x1a\x48\xc5\xda\x0e\x99\x39\xc5\x6b\x26\x61\x50\x48\x43\x3b\xb2\x3d\x7c\xfa\x1e\x37\x01\x44\xe5\x50\x2d\x3c\xb9\xc3\x0d\x90\xd6\xd7\xa7\xa7\x01\x3f\x46\x24\xa2\x28\xb4\x3c\xfc\x8b\xb7\x47\xe4\x79\x8b\x2c\xea\xed\x61\x12\x9d\xcc\x25\x90\x8a\xba\xdd\x99\x13\x4a\x56\x45\x75\x66\xe1\x80\x92\xb9\x4d\xbd\x3d\xd8\x5a\x78\x33\x4b\x44\x64\x4d\xb8\x1f\x01\xce\xdb\x98\x11\xac\x0f\x80\xdc\x4e\x91\x4d\xe7\x30\xb7\xfd\xc8\xb6\xfb\x53\x4b\x20\xc1\x02\x38\x55\x35\xe2\x95\x3e\x15\xd5\x60\x6a\x7c\xe2\xd7\x35\x6b\x56\x5a\x1e\xb5\xbe\x26\xd2\x41\x4a\xaf\xfc\x7b\x94\x12\x97\x1c\x81\xf5\x99\xb0\xd2\xc4\x68\x88\x84\x89\x02\x80\x19\x0b\x7c\x36\x00\xd8\xaf\xaf\x99\x82\xce\x90\x28\x45\x0a\xea\x51\x9a\x2f\x97\x3d\xb0\x50\x8b\x82\x13\x7a\xbd\x5c\x06\xa7\xf4\x03\x1e\x3c\xa1\x82\x04\xd8\x7f\x14\x3f\x04\x46\xa0\xd7\x21\xa6\x88\xd1\x4f\xa8\x02\x46\x02\xfb\xc1\x0e\x23\x80\xc3\xca\x8a\xf5\x84\x90\x56\xf5\xfc\x84\xc6\xcb\x65\x7c\x42\xaf\x15\xa2\xd3\x4f\x4e\x78\xbf\x80\x09\x22\x89\xd8\x91\x05\x15\xcd\xc6\xb8\x3a\x97\xdc\xa6\x01\x29\xa8\xe8\x45\x0c\xbd\x20\xb1\xe5\x9d\x26\x52\x63\xca\xe6\xa7\x2e\xf0\xcf\x44\x15\x8e\xd9\xe7\xb6\xad\x6a\xc3\xfd\xaa\x06\xc4\x2d\x1a\xdb\x09\x56\x59\x63\xcb\x03\x37\xe0\xaa\x0c\x6e\x15\xaa\x66\xb1\x10\x5b\x27\x1b\x98\xb6\x69\x15\x7e\x51\x2f\xce\x1f\x9b\xaf\xae\x82\x44\x52\x53\x31\x74\x47\xb8\x9f\xd7\x4c\xb1\x5c\x3d\x59\x74\x50\x5a\x65\xc9\x47\x18\x3b\x05\x7e\xce\x68\xda\x07\x05\x8a\x15\x97\x94\x6f\x46\x52\x01\x45\x32\x9a\x56\x9a\x4b\xd9\xaa\xa9\xce\xb7\xce\x84\x13\x0d\x57\xee\x2a\x3a\x7c\x68\xdb\xd9\xa8\xcf\x9d\x79\x36\x47\xca\x3c\x79\x06\x2e\x44\xfb\x19\x5c\xc7\x19\x5c\xc7\x79\x7d\x1d\xa3\x94\xe6\x96\xb7\xf7\x3a\x85\x23\x27\xf0\x5f\xb1\x47\xe4\x9d\x9c\x9e\xdc\x41\xc4\x90\x29\xec\x01\x52\x88\x88\xe2\xb8\xc9\x33\xff\xb4\x6d\xb7\x86\xa4\x54\x68\x90\xa0\xcb\x16\x74\x0c\x77\x0c\x7e\x66\xbe\x42\x6f\x05\x34\x9c\x89\xae\x85\xd0\xb5\x10\xba\x96\x89\xae\x89\x05\x8b\x29\xb7\x33\x01\xbe\x63\x71\xe0\x48\x40\x39\x99\x52\x54\xd2\xd9\x70\x4e\xdd\x11\xde\x59\x0c\x33\x3b\xb0\xbd\xd1\x2f\x9e\xbb\x74\xab\x5d\x89\xe6\x34\x41\x62\x79\xb1\x20\x00\xf0\x29\x9d\x35\x5f\xa9\x3a\x39\x86\xd9\x36\x98\x3c\x99\xb3\x4a\xf5\x7f\xde\x9f\xc9\x73\xe9\x62\xdc\x2f\xe9\x94\xba\x44\x74\x30\xa0\x28\xfe\x45\x9c\x77\xdb\xdb\xb3\x3c\x79\x53\x8b\x92\x25\x15\x5d\x9f\x8f\x44\xa6\xcd\xee\x4f\x29\x6a\x15\x14\xc3\x18\xb8\x7e\xd9\xee\x33\x3c\xdf\x89\x73\xc1\x4f\x5c\xe9\xba\xa7\x23\xea\xb4\xbc\xd1\x72\x89\x82\x13\x77\x50\xfa\xe5\x2f\xba\x04\xa8\xd8\x9e\xec\x0d\xd0\x54\x90\xa4\xdd\x2e\x72\x29\x4d\x97\xcb\x94\x52\x04\xfb\x74\xb0\xeb\xf7\x30\xf6\xa7\xa7\xfb\xcb\xe5\x3e\xd8\xb0\x47\x7b\x90\x23\x5f\x2e\x0f\x28\x4d\xbb\x5d\x14\x9f\xba\x83\xe0\xd4\x1d\xa8\x6e\x8c\x7c\xd7\x9f\x0d\xe7\xa2\xee\x5f\x3c\xb7\xeb\x35\x2b\x3b\xf2\x0f\x31\x26\xfc\xc4\x5b\x2e\x3b\xb3\x9a\x29\x67\xe8\xf9\xa2\x2e\xc9\x07\xe2\x54\x32\xc0\x3f\x44\x16\xba\x18\x22\x6f\xcf\xe6\xbf\x78\x7b\x02\xec\x8e\x88\xd8\x2b\x36\x5f\x2e\x5d\xec\x43\x3a\xec\x21\xc2\xd4\xeb\x61\x3c\x40\x55\x65\x73\x12\x52\x8f\xcc\x6d\x1b\xfb\x8d\x48\x4b\x6c\x8f\xc5\xd0\xdb\xb3\xe3\x11\x11\x73\x4d\x45\xe7\x0b\xa4\xbb\xff\xcb\x42\xd0\xbd\xaf\x43\xdf\xc5\x24\x87\x43\xdd\xef\x4b\x14\x87\x52\x05\xe4\x63\x58\x41\xd8\x62\x01\xac\x51\x00\x6b\x14\xeb\x5b\x4f\xa6\x59\x34\x04\xc6\x57\x33\x0b\xe0\xab\x71\x87\x86\x60\xe4\x8b\x59\x96\x1c\x23\x8d\xba\x5d\x04\x21\x0f\x63\x75\x70\xc5\xa6\x1e\xce\xa1\x16\xf1\xdb\xa1\x91\xdc\x63\x7d\x31\xb9\xf6\x88\xba\xa2\xf2\x95\xec\x8e\x1e\x5d\x5f\x60\xf6\xb3\xa1\x6d\xc7\x23\xb1\xef\xe4\x59\x5d\x49\x0c\xbe\x75\x02\x01\x83\x87\x3e\x54\xa7\xb0\x82\x0b\x6c\x9b\xd6\x29\x38\x68\xae\xb8\x3c\x12\xd2\xd3\x74\xd0\xbc\x11\x7c\xc4\x35\xe0\xe7\x34\x05\x58\x9f\x9e\xd2\x0f\x83\x27\x10\x37\xf3\x1f\xc1\x6d\x02\x00\x59\xb9\x19\x04\xf8\xe3\x3e\xaf\xda\xbd\x70\x40\x21\x9f\x32\x72\xe1\x5c\x7f\xfc\xf4\xe1\xed\xdd\xa7\x2b\xea\x56\x1f\x6f\x3f\xfe\xf1\x81\x7a\xd5\xe7\xd9\xf9\xe5\x3b\xda\xab\x3e\x2f\xde\x7d\xfc\x78\x4d\x77\xab\xef\xdf\xde\xbc\xbb\x10\xe5\xf7\xda\x31\x50\xc9\x7e\x3b\xee\xfc\xf7\xf3\x0f\xf4\xa0\x1d\x07\xb5\x1f\xb6\xe3\x64\x13\x47\xe4\xc2\x39\xff\x74\xf6\xee\xf2\x2d\x3d\x26\x17\xca\x31\x0f\xbd\x70\x0a\xc6\x37\x85\xd9\x48\xaa\xef\xd5\x0e\x55\x6a\x8e\x4a\x64\x61\xc3\x8f\xbe\xe6\x83\x28\x86\xbb\xc1\xd4\xf3\xa9\x6f\x98\x96\xc4\x60\x36\xac\x4a\x72\x6a\xbe\x3d\x3f\xbb\x7c\xff\xe6\xdd\xdd\xd5\xbb\x37\x67\xe7\x37\xe0\xd4\xfa\x1e\x49\xc3\x71\xc4\x25\x73\x81\xdc\xde\xd2\x54\xcc\xf8\x66\x59\x18\xda\xe5\x87\x3f\xdf\xbd\xff\xf8\xf6\x7c\xbd\xe8\x91\x28\x7a\xf5\x52\xd1\xf3\xbf\x5e\x7d\xfc\x70\xfe\xe1\xf6\xf2\xcd\xbb\xbb\x37\xb7\x50\x56\x15\xc5\xdd\x6e\x2a\x36\xde\x40\x54\x26\x50\x03\x7b\x4e\x00\xc7\xbe\x97\xee\xb5\x55\xa7\xae\xa5\x30\xd0\x07\x10\x06\xc2\xbe\xc8\x2c\x72\xaa\x34\x1b\x7d\xa0\xe9\x89\x3b\xb0\x53\x3f\xc5\xf8\x85\xde\xbf\xf9\xf0\xe7\x73\x13\x0b\x14\x7d\xad\x69\x5c\xb7\x6c\x7b\x8d\xa6\x3d\x59\xfd\x9d\x6c\xfa\x3d\x34\xdd\xaf\x69\xa4\xba\x03\x9d\x74\x6d\x4d\xb8\x65\x36\x5e\x3e\xbe\xb1\x3c\x13\xab\x92\xe2\xfe\x1d\xb5\xd1\xfb\xba\xab\xab\x17\x56\xe9\xec\xfa\x6f\x57\xb7\x1f\x4d\x89\xd6\xea\xde\x76\x28\xed\x6c\x6d\x49\x34\xc3\xf3\x92\x19\x59\x6e\x8c\x83\xa4\x60\xaa\x31\x78\xcf\x5b\x73\xb6\x5b\xbf\xda\xe6\x4f\x73\x9e\x2d\x97\x9d\xb5\x80\x33\x61\xfc\x3a\x48\xa3\x6c\x06\xc2\x82\x45\xb7\xab\x13\x72\x88\xfd\xf5\x89\xb3\x42\x75\xe2\x0d\xed\xa4\xa4\xda\x87\x32\x9b\x51\xa6\xc1\x22\x88\x93\xe0\x3e\x61\x26\xee\xbf\xa1\xa9\x44\x7d\x44\xe0\x85\xd1\xbe\xff\xf8\xf6\xd3\xbb\x8f\x5b\x77\xd5\xb1\x98\xde\xcb\x97\x76\xd5\xd5\xc7\x3f\xee\xae\xae\xcf\xcf\x2e\x6f\x2e\x3f\x7e\xd8\xba\x97\xbf\xbd\x54\xf4\xe2\xe3\xf5\x7b\xb1\x11\xb7\x9e\x31\x3d\xe3\x2f\xcc\x75\x90\x1a\xb2\x80\x9a\xe7\x77\x2f\x0f\xed\xcd\xbb\xab\xdf\xde\xfc\x7a\x5e\x35\xb4\x26\xc5\xa3\x1b\x5a\x2e\x77\xfe\xe1\xbc\x5a\x0e\x2d\xdb\xf9\x5c\x8c\x96\xc8\xc1\xce\xeb\xcf\xde\x8e\xe4\x31\xa4\xdb\xfa\x11\x4b\x0d\x2c\xd5\x83\xb7\x34\xd5\xef\x11\xcf\xed\x13\xee\xdf\x92\xd6\xb1\xf5\xaf\x48\xfb\x2c\xfa\xc3\x6b\xf2\x61\x44\xe0\x74\xf8\xc3\x3b\xf2\x7e\x44\xe4\xee\xf3\xdf\x90\xc6\xc2\xf8\x97\xa4\x35\xdb\xfe\x37\x22\xa7\xd0\x7f\x47\xf4\x20\xfd\xb7\xab\x15\xb9\x70\x1a\x5c\xa2\x75\x21\xf0\x0e\x5b\x2e\x3b\xa0\x58\xb0\x8d\x99\xd4\x91\x3c\x69\x45\xbe\xe9\x48\xe5\x68\x14\xf8\xff\x02\x7f\xcb\x80\x74\x88\x29\x73\x8a\x3e\xf3\xc5\xa4\xd6\x6e\xaa\xf2\xe0\x69\x64\x52\xfa\xbc\x5a\xf3\x49\x95\xab\x93\xe4\x51\x10\x1b\x07\x4d\xa8\x18\x77\xbb\xd9\x29\xb5\xe7\xdd\x6e\x76\x42\xc5\x5f\x2a\x48\xc2\x0c\xeb\xbb\x9b\xe6\x02\xe1\xd0\x1f\x99\x54\x01\xca\x35\x16\x57\xf5\x4e\xe1\x71\x2b\xc0\xed\x29\xca\x24\xb5\x87\x4f\x3c\x41\x61\x48\x62\x4f\x5d\x78\x50\x9f\x46\x2b\xa8\xc2\xdb\x39\x75\xfb\xfc\x24\x6f\x3a\x0e\x94\x67\x3e\x17\x1b\x03\x10\xb2\x53\x1a\x2d\x97\x29\x10\xac\x29\xae\xf0\x46\xd1\xaf\x0e\xa5\xd5\xe3\xa7\xbb\xaa\xd9\x07\xea\xba\xcd\xa5\x94\xa2\xec\xbd\x8e\x8c\x97\xcb\xf6\x34\xd4\x43\x59\xbb\x58\xb4\x96\x5f\xb5\x4e\xf2\x6a\x11\x6b\x3c\x0b\x1e\xe3\x59\x39\xa3\x10\xda\x22\x50\xfc\x47\xfd\x1e\x47\x6e\x9c\x84\xcb\x42\x71\xaa\x0b\xc5\xe9\x0f\x0b\x4d\x64\x21\x09\x75\x28\x8a\xe9\x1a\x11\xdf\x93\xae\x65\x65\x3a\xc2\xdd\x9e\x7b\x7c\xe8\xed\x7b\x83\xcd\x7a\x0b\xf4\xfd\xa2\x78\xb5\x45\xd9\xe4\x68\xf7\xe8\xe8\xc0\x3d\x7a\x8d\x2a\xe9\xec\xbd\x76\xa9\xa5\x8b\x2d\xa4\x73\xad\xa7\xb4\x6d\xc4\x54\x1b\x58\xa9\x94\xb8\x24\xa2\xc3\x11\x99\x2a\x2a\xec\x4b\x83\xe7\xca\x06\x8c\xde\xfa\xf7\x88\x09\x08\x86\x49\x46\x13\x04\x2f\x08\xe4\x8d\xd8\x16\xdb\x41\xb4\xde\x48\xdb\x53\x81\x30\xfe\x14\xa7\x7c\xb7\x27\x25\x24\xb3\xd7\xb4\x27\x68\x8b\x93\xac\x8f\x51\x40\xbd\x5d\xcf\x3d\xec\xbd\xe6\xc3\x72\x64\x21\x3e\x2c\x2d\x6f\x74\x7a\x7a\xea\x79\x82\x66\x39\x66\xde\xfe\x00\xa5\x3f\x5b\x73\x4f\xe0\xea\xf2\xad\xd5\x1d\x11\x59\x97\xbe\xb0\x23\xe5\x5b\xf4\x17\x8f\x89\xe1\x94\x16\xed\x09\xfa\x26\xdb\xe9\xd5\x4c\xd5\xef\x5d\x34\xde\x0f\x2e\x9a\xd6\x14\x34\x6a\x10\xc3\x3d\xac\x47\xdb\x3b\xf2\xf6\x0e\xf7\x8e\x0f\x0f\x0e\x3d\xf7\x60\xff\xe0\x35\xda\xf5\xba\xa2\xcb\xd8\xf2\xdc\xe3\x63\xf0\xc6\x70\x78\x78\x78\xf0\x5a\x76\xde\xda\xeb\x1d\xef\x1d\x1f\x1c\xf6\x8e\x65\x4c\x6f\x64\x79\x07\x87\x87\x87\x3d\x4f\x7e\xef\xaa\x29\xdb\x1b\x9d\x9c\x78\x07\x58\x7e\xec\x8f\x4e\x4e\x8e\xb0\x25\x82\x07\x23\x3d\x89\x5b\x3a\x76\x88\x9d\x30\x9b\x0b\x64\xb7\xdc\x3a\x3f\x87\x72\x7e\x0e\x05\x64\xe9\xbc\x91\x84\x85\x1e\x46\x8c\x30\x3e\x11\x15\x77\xbb\x28\x1a\x96\x96\x35\xa2\xaa\x24\x4c\x04\x13\x14\x1f\x41\x19\x8d\x86\xb6\x5d\x0a\x44\x87\x81\xd3\xb0\xb1\xa0\x62\xd8\x88\x44\x62\x91\x0a\x94\xed\x04\xf8\x75\x80\x81\x08\x10\x51\xfd\x48\x92\x00\xa4\xb4\x6d\xd8\x93\xe5\x89\x8b\x23\x3a\xcc\x2b\xf6\x96\xf4\xaa\x6c\x7b\xaa\x88\x2b\x8a\x14\x73\xc5\xb3\xf2\x30\xc9\x6d\xaa\xfb\x50\x02\xcd\x13\xad\xd3\x3c\xa5\x20\x68\xca\x13\x6f\xaf\xdb\x45\x90\xdb\x2e\x2b\x84\x7e\xea\x30\x9a\x93\xa9\x13\xd2\x88\x4c\x57\x98\x5c\x38\x45\x39\xdb\x6e\x1f\xc3\x6b\xfa\x0a\x27\x9a\x9f\xc1\x81\xe5\xc1\x6a\xb7\x99\x38\xa5\xa9\x33\x4f\xca\x02\xf1\x21\xb3\xac\xda\xb9\x73\xba\x22\x2d\x25\x86\xc6\xab\x54\xc5\x41\xa8\x04\xe7\x48\x4c\x02\x2a\xb6\x74\x42\x5d\x78\xc5\xd5\xd2\x71\x27\x45\x5f\x13\x78\x95\x8f\xb5\xd8\xb6\xfb\xc1\x30\x1e\xbd\xa6\x5c\x11\x77\x40\xda\xe5\xb5\xc7\x23\xcd\x14\x4e\x2c\x0b\x6b\x31\xe5\xa0\x29\xb9\x14\x0c\xb3\xd1\x69\x0a\xfc\x7f\x09\x15\x82\x61\x66\x79\x23\xb1\x84\x10\xa0\x2e\x26\x32\x64\x89\xa4\xd1\x4e\xba\x74\x45\xc4\xe8\x97\x06\xb7\x26\x70\x72\xb6\x60\x39\x48\x61\x6e\xca\x2b\xcb\x17\xb7\x40\xc2\xa6\x75\xf5\xf2\x05\xe5\x2d\xf6\x2e\xb9\xa7\xb7\x64\x42\xaf\xa4\xc0\xc4\x29\x75\xbb\x5d\x54\xd2\x6f\xf0\x72\xc6\x29\x5f\xe7\xfa\x92\x31\x45\x1a\xb6\xe5\x18\x4b\xef\xdf\xfa\x71\x74\x21\x90\xb2\x12\x16\x99\xa1\x47\x34\x43\x63\x41\xff\x8d\x1d\x26\x29\x3d\xcf\x25\x19\x69\xca\xcc\x60\x22\x76\xc5\xb4\x7a\x5d\xc5\x24\xa4\x25\x45\x73\xca\xd4\x28\x82\x01\x4a\xe8\xdb\x76\x19\x1f\x25\xcd\xf7\x33\x93\xbc\xc5\x15\x0f\x50\x6c\xdd\x39\x1c\x8b\xfe\x5c\x53\xbc\xe2\x84\xcd\x1b\xec\x85\xa4\x7e\x7c\x15\x69\x0b\x81\xaf\xdb\xa1\x2f\xfa\x4a\xe7\xa2\xb3\x34\x24\x63\xa7\xa0\x31\x99\x53\x34\xa6\x29\x1a\x93\x29\x28\xe4\x67\x18\x3b\x21\x89\xe8\xd8\xc9\x49\x48\xc7\x0e\xc3\x64\x41\xe7\xc3\x82\x86\xd6\xbd\xe5\x8d\x08\x00\x3c\x12\x89\x0b\xbd\xa8\x99\x2d\xf3\x61\x21\x12\x23\x3a\x39\xd9\x1b\x28\xea\x6f\xb1\x5c\x46\x8a\xbd\x32\x59\x2e\x27\x94\xa2\x71\x83\xbd\xb2\x38\x2d\x97\xcb\x05\xa5\xa5\x64\xaf\x4c\x96\xcb\x08\xd8\x2b\x93\x6e\xd7\xeb\xce\x87\x85\xed\x8d\x9a\x85\x24\x1b\xa5\x00\x36\x0a\x8c\x93\xd3\x68\xf0\x88\xaa\x61\x7a\x98\xd8\xf7\xa4\x31\x6a\xec\x37\xa7\x40\x03\xe8\xb9\x46\x5e\x0a\x12\x01\x3c\xb2\xed\xac\x6f\x59\x62\x36\x8b\xd1\x69\xd6\xc7\xf3\x61\x31\x12\x47\x64\xb9\x44\x96\x15\x92\x39\x05\x9d\x67\x69\x65\x67\x8e\x35\x5c\x98\x57\x5c\x42\xb5\x0e\x4a\x12\x07\x76\x93\x69\xf6\x17\x27\xb4\xec\x73\x8b\x56\x5d\x98\x0f\x17\xe2\xec\xe2\x3e\xa7\x8f\x88\x93\xb0\xd5\xd3\x86\xdb\x41\xb4\x6e\xae\x68\xed\x50\x57\x8f\xe2\x92\x89\xdd\x3a\xcc\x24\xa4\xfc\x17\x8f\x1d\x92\x92\xf2\x1d\x8f\x1d\x2e\x5d\x09\x49\xab\x87\x6d\xdc\x2f\x6c\xbb\x8f\x13\x50\x3d\x0a\x5f\xa3\x98\xb2\x61\x31\x12\x45\xb0\x85\x72\x5a\xbe\x8e\x2d\x14\x40\x9c\x2c\x8e\x5f\x87\x58\xa4\xbe\xf6\xd8\xa1\x95\x60\x71\x46\x45\x46\x95\x68\x95\xaf\x03\x22\x32\xd3\xec\x97\x54\x03\xa5\x04\x5e\xd2\x87\x49\x35\x67\x0c\xb7\xb8\x9b\x7c\x83\xbb\x09\x88\x43\x87\xe6\x38\xa6\xe9\x69\x2e\x25\x2d\x00\x09\x54\xc2\x2d\x00\x5b\x52\x00\x2a\x20\x02\x97\x8d\x3a\x94\x0f\x33\x81\xd6\x52\xf1\x75\x2a\x3e\x64\x31\xc9\x44\xaa\x84\xa4\x1b\xba\x85\x9b\x00\x91\xba\x20\x4f\x83\xd9\x30\x1d\xd9\x34\x53\xe6\xbf\x4e\xf8\x30\x15\x95\xb9\x44\x7c\xd1\xec\x75\x6e\x41\x06\x70\x58\x0c\xd7\x57\x87\x0d\xdd\x51\xb7\x5b\x8b\x39\xf4\x59\xf3\xfa\xc0\xfd\x0d\x48\x55\x2d\x96\x12\x43\x26\x25\xc0\xa8\xca\x00\x06\x79\x6c\x8a\x0d\x90\x2f\xe4\x96\xe6\x4e\x41\x69\xe6\x14\x30\x2c\x72\x45\x73\x27\x24\xd7\x34\x73\x42\xf9\x00\x7e\xd5\xed\x5e\x41\x2f\xae\xbb\xdd\x6b\x71\x16\xda\x4f\xe0\xb9\x53\x74\xbb\x99\xf8\x83\xae\x06\x9d\xeb\xe5\x52\x64\xee\x50\x91\xd3\xbf\xc6\x83\xab\x6e\xd7\xa5\x54\xc4\x2d\x97\x9d\xeb\x81\xfb\xfa\xd6\xbf\xdd\x71\xfd\x0f\xc1\x07\xb9\x89\x9f\x28\x9a\x28\xd0\x77\x2b\x60\x81\xc0\xf3\x6e\x69\x6c\xa1\x92\xe6\x0e\xb3\x33\x07\xfc\xdd\x25\xcb\x25\x4a\x68\x44\x4a\x3a\x45\xb9\x14\x11\xb1\xa7\x28\x93\x21\x72\x4b\x6f\x77\xbc\xbd\xa5\x2b\x00\xa9\xdb\xbf\x1e\x8e\x47\x94\xa2\xab\xe1\x18\xb4\x89\xfb\x63\xc5\x97\x16\xf1\xa7\x55\x74\xb7\x5b\xda\x36\xb9\x3d\x71\xf1\x93\x44\x24\x3c\x4c\xe6\xb4\xe3\xd6\xd7\xf6\x47\x7a\xa5\xb7\xfa\x0d\xbd\xae\x94\xa5\xa8\x4b\x6e\x2d\xda\x23\x68\x46\x0b\x94\xec\x20\x31\x50\xcb\xc3\x18\x9f\x8a\xeb\xe7\x9a\x32\x74\x4d\x66\x24\xc1\xe4\x8a\x32\x74\x25\x83\x8d\xf2\x75\xad\x98\x7c\xa5\x37\xe4\x81\xa2\x47\x7a\x55\x3d\x63\xdd\xd4\x60\xf7\xe1\xe4\xa6\xff\x38\x7c\x10\xa8\x8a\x8b\xfb\x5f\xe8\xb5\x3e\x57\xe4\x8b\xb8\x5b\xf5\x96\xff\x82\xc9\x19\xcc\x36\xb9\x1e\x7a\xa3\x53\x9a\xec\xf4\xba\xdd\x33\xcb\xea\x47\x19\x3c\xae\x51\x97\xa0\x90\x72\x74\x4d\x1e\xc9\x0d\x79\xa8\xde\x55\xcf\xc1\x44\x00\xb9\xe9\xd0\x07\xb0\x50\x71\xfe\x3a\xb1\xc0\xc4\x80\x98\x1d\x2c\x87\x77\xbe\x73\x26\xc6\x05\xa0\x6b\x76\x0a\xcf\x2e\x33\x9a\xd8\x9e\xb8\xe0\xd0\xa2\x1a\x6a\xf5\x5a\xf4\x40\x1f\x75\xef\x3d\x4a\x39\x5a\x90\x47\x72\x4f\x1e\x70\x1f\xcf\x6c\x9b\xa4\x68\x41\x6e\x4e\xee\x07\x5f\xfc\x6b\x72\x2f\xa6\xe5\x9e\x2e\x6a\x60\xa2\xce\xa1\x4b\xe9\x0c\x5e\xd9\x67\x54\x37\x53\x0d\xbc\x9a\x9a\x78\x8c\xee\x4f\x44\xb7\x17\xcd\x99\x58\x60\x4c\x52\xf4\x48\x16\xe4\x41\xd4\x5e\x77\x86\x08\xc2\x2d\x94\x08\x61\x3d\x11\x27\x5e\x1f\xcf\x2c\x0b\x8a\xdc\x9c\x3c\x40\xb7\xd6\x0a\xae\x74\x97\x80\x9d\x2c\x32\x3f\x52\xc0\x98\x9e\x86\x63\xb1\x2e\x33\xb0\x91\x31\x50\xab\x74\x35\xfc\x2a\x26\xcf\x47\x8f\x74\x28\xc2\x23\xf2\x40\x3d\xbc\x7a\x98\xc6\x09\x43\xe8\xab\x65\x9d\x7c\xd4\x17\x18\xd8\x9c\xe8\x76\x6f\x05\xda\x38\xa7\x75\x1c\x79\x82\x83\xf2\xd4\x3c\xe4\x02\x9d\x4d\x28\x8d\x24\x38\x19\x53\x8f\xdc\x52\x91\xad\x7f\x0b\x58\xe2\x2d\x60\x89\xb0\xc9\x3f\xa1\x09\x89\x2d\x34\x71\x18\x1d\x5b\xde\xde\xeb\xd2\xf6\xc4\xe1\x09\xc8\x5c\x3d\xc8\x89\x94\x92\x4c\x9c\x9c\x5a\xf3\xca\x32\x16\x5c\x03\x0f\x74\xe7\x1f\xc8\x1e\x60\x17\x0d\x1f\xef\xb3\x11\x46\x03\xfa\xf9\x61\xf8\xf9\xc1\x19\xbd\x7e\x85\x77\x62\x72\x2e\xd2\x87\xff\x70\x46\x16\xfe\xec\xbc\xda\x21\x5f\xe9\xce\x3f\x3e\x3b\x2a\xe6\xd5\x0e\xf9\x28\x45\x33\x2f\xd3\x71\x9c\xc6\xfc\x69\x29\xce\xf7\xab\x1d\x72\x26\xb2\x15\xaf\x3f\x5b\x68\x40\xa1\x36\xbc\xfc\xc7\xe7\xc2\x5a\x7e\x2e\xac\x57\x3b\x13\x92\x6d\xd8\xc4\xaf\x00\x35\x4d\x07\xdc\xaf\x31\xa5\x33\x81\x27\x89\x75\xff\x28\x79\x3c\x31\xc6\x4c\xe0\x13\x60\x9a\x25\xc6\x03\xe0\xb9\xc7\x02\xed\xf0\x7c\xaf\xba\x7f\x3b\xf0\x7a\x42\xe3\xaa\x9a\x07\x82\x5e\x90\x47\xcf\xa8\xf9\x68\x52\xd0\xf8\x58\x13\x17\x18\x78\x07\xbe\x79\x6f\x52\x9a\x0e\x7a\xfe\x11\xc9\xbb\xdd\xbc\x43\xb3\x01\xf3\xf9\x0a\x63\xf1\x89\x32\x9a\x93\x66\x2b\xe7\xc4\x7c\xe5\x99\xb5\x76\xd0\x57\x62\xba\x8e\x88\xc1\x84\x77\x6a\x06\x82\x02\xa0\x31\xc9\x60\x64\x9a\x8d\xb3\xc6\x54\xf8\x90\x71\x23\x30\x2d\x94\x0f\x4c\xe3\x3e\x28\x98\x61\x5a\xb9\x6f\x9a\xd8\x32\x6b\x61\x3b\x8b\x63\x71\x31\xc0\x46\x5a\x35\x9f\x21\x56\xe4\xc6\x09\xee\x8b\x2c\x29\x39\x93\xba\xcc\xf0\xbd\x61\xb8\x40\x93\x03\xd3\xb8\x58\x7f\x45\x45\xa2\x62\x0f\xbc\x8a\xdc\x38\xca\x55\x76\x74\x9b\x6d\x57\xdc\x5d\x28\x45\xe1\x4a\x38\x0a\x8b\x52\x4a\xd6\xeb\x4a\x4c\x46\x41\x6f\x9c\x68\x4e\xb7\x99\x8f\x80\xab\xab\x92\xbc\xd2\x3c\x7d\x55\x73\x45\xe1\x6b\xe5\x74\x0e\x6f\xe8\x5c\xbe\xa1\xeb\xd7\xec\x18\x13\x66\xc5\xf0\xb4\x25\x25\x63\x3a\x28\xa5\xb1\x13\xe2\x75\xa9\xd1\x9c\x7a\x7b\xaf\x05\x62\x92\x56\x42\x88\xe2\x46\x91\x06\x0d\xe0\x01\x12\x4c\xe2\x66\x23\x09\x35\xb2\x5f\x3c\x97\xd2\xea\x11\x56\x1c\x5b\x2d\x22\x0a\x73\x94\x0b\x0a\x23\x87\xb1\x82\x67\xb1\xe8\xd7\x27\x0a\xe1\xed\xd3\x94\xae\x4f\x13\xb9\x25\x57\xb8\x51\xfc\x36\xbb\x94\x52\x96\x50\x4f\xfc\x2f\x54\x04\x10\x83\xdc\x54\x26\x7a\xe2\x80\xab\xde\xcc\xb3\x87\x97\xa7\x9d\x04\x9a\x9c\xa9\x16\x00\xb1\x4a\x5e\x09\x3b\x61\xb7\xdb\x61\x4e\x5c\xa8\x6e\xa1\x75\x16\xab\x79\xae\xda\xdb\x94\x12\xb5\x7e\x03\x1d\xe5\x6a\x49\x79\xb7\x8b\xb8\xde\x70\x18\x4b\xd1\x86\x53\x6f\x8f\x74\xe6\x4e\x28\xf0\x6d\x90\x78\x00\xa6\x9b\x0c\x76\xbb\x9d\xb9\xc3\x80\x9d\x38\xaf\xc8\x9a\xe5\x52\x0a\xcf\x75\xa4\x80\x84\x5e\xdf\xb1\xaa\xb8\x32\x85\x64\xfd\x86\xe6\x98\x04\x83\x9e\x3d\x41\x0c\xfb\xb2\x33\x98\xf0\xc1\xd8\x99\x65\x11\xe2\xd8\x87\xd7\xed\x92\xc2\x76\x57\x66\x68\xb9\x13\x0e\x3a\x1c\x2a\xf6\x3b\xdc\x29\xda\x07\x16\x70\x17\x94\xd3\x4e\xd9\xed\xce\x9b\x73\xd2\xed\xf2\xd6\x14\x75\xbb\x68\x4e\xe7\xaa\x9d\x5a\x5a\x4c\x0c\xf7\x58\xa4\x39\x20\xcb\x3d\x77\xd8\x89\xed\x2d\x97\xf0\xfc\xe9\xb0\x81\x1c\xf4\xa9\xb7\x5c\x06\xa2\xfe\x10\x6e\xf5\xde\x1e\x3b\xf4\x65\xca\xc9\x11\xf3\x76\xab\x44\x77\x74\x42\x8f\xc5\xff\x0e\xf7\xd9\x61\x2d\x53\x17\xd3\xb9\x3c\xbe\x62\xd4\x03\xdb\xf5\x5d\x22\x9a\x03\x12\x39\xa6\xde\x4e\x8c\xd5\xa6\x29\x07\xde\x4e\xec\xc7\xb8\xff\x0d\x52\x12\xf4\x6d\xc7\xdb\xb3\x7a\x58\xea\x71\x06\x83\x4a\xf4\xc1\xd9\xc7\xa4\xac\xc1\x41\x44\x41\x86\xcf\x8f\x28\xca\x68\x65\x92\x57\x4d\xef\x2f\x3d\x32\xae\x79\x75\xf2\x71\x37\x52\xa2\x14\x63\x3a\x76\x78\x3c\x63\x85\x20\x70\x9c\x50\x3d\xb9\xc6\x83\x71\xb5\xb4\xa7\x71\xb7\x8b\xea\x4f\x1a\x63\x5f\x40\x59\x51\x50\x4f\x65\x3c\x46\x59\xc5\x69\x46\x19\xb0\x66\x7a\x58\xf1\x7a\xfb\x11\xcd\x7e\xe9\x55\xbc\xdd\x4f\x40\x93\xc8\x26\xe1\x71\x43\x40\x07\x0f\x02\xa7\xde\x1e\x96\x03\xa9\xae\x0c\x55\xa1\x1a\x48\xb3\x3e\xb1\x94\xba\xe3\x24\x16\xcb\x04\x2b\xd0\xec\xf4\x7c\xa3\xd3\xcd\xf5\xaf\x84\xca\xc7\x3e\x2a\x61\x40\x5f\xc4\x79\x47\xe3\xf6\x7e\x8c\x07\x9f\xd0\x98\x7c\x23\x57\x44\x9a\x15\xc2\xfe\x18\x4e\xb4\x3a\x51\x6b\x26\x29\x2a\x79\xea\x2d\x10\xbc\xe6\x90\x5e\x29\x0e\x29\x80\x4a\x4e\x38\xcc\x01\x93\xd5\x16\xe7\xff\x2c\x83\xe4\x36\xa3\x37\x0e\xfb\xe7\x76\x30\x03\xce\x7d\xb6\x82\xf6\xb8\xb8\x10\x17\xfe\x16\x1b\x19\x9d\x8e\x34\x8a\x21\x73\xfd\x19\x2c\x02\xe5\xb7\xd3\x20\xa5\x37\xce\x84\xff\xec\xf5\x71\xea\x6e\x94\xff\x98\xd7\x1d\x9e\xf0\x17\x2c\x48\x78\x62\x19\xf9\x96\x4e\xe3\xe5\x12\x0c\x94\xc9\x6a\xd5\x59\x7d\xb9\xf7\xdd\x6e\xf3\x52\x38\x95\x91\xfa\xca\xe8\xc9\x4a\xde\xb1\xa2\x50\x03\x4b\x7e\x7a\x60\x27\x6e\xbb\x70\x73\x54\xc9\x0b\xa3\xb2\x7f\x72\x54\x1f\x82\x0f\x5b\x46\x24\x0d\x88\xa8\x1c\x6c\x12\xf0\x78\xf1\xa2\x69\x93\x42\x77\xef\x2a\x93\x1a\xb4\x2f\x66\xd4\x0b\xf4\x77\x96\x67\xdf\x9b\x46\x97\x4a\x73\x27\x02\x62\x89\x12\xb3\x38\x2d\x8b\xef\xdd\x47\x90\x9d\x24\x34\x70\xe0\x36\xe2\xb4\xbe\x8f\xc4\x88\x9d\x82\x74\x92\xe5\xb2\xc3\xb7\xc0\x66\x81\x30\x77\x6a\xc9\x5b\x01\xb1\x6c\x4e\x02\xc9\xd7\x54\x86\x53\x0a\x1a\xc0\x9a\x12\x40\x96\x44\xa0\xa4\x81\x13\x82\x91\x5e\x49\xf8\x16\xcb\x65\x27\x54\x1a\xfa\xcb\x65\x67\xac\xab\x2b\x07\x48\xd5\xc8\xb0\x2f\x5b\x1d\x0f\x02\x5f\xb7\xdc\x29\x25\xad\x3b\x6e\xde\x4a\x82\x4a\xd8\x28\x55\x82\x35\x62\x7f\x97\xd2\x2b\x80\xd0\x00\xd6\x0a\x3a\x45\x05\x26\x21\x05\x4d\xdf\x92\x96\x15\xd5\x97\xd0\xc2\x0e\x25\x11\x20\x00\xf5\x89\x8b\x07\x28\xa1\x76\x42\x32\x5a\x62\x1f\x85\xb4\x10\xf8\x34\x26\x59\xcd\xc7\x24\x9c\x26\x20\x20\x97\xd5\x22\x52\x8d\xe4\x55\xc5\xf9\xc8\x29\x8a\x29\x4a\x68\x59\x6b\xb0\xf0\xda\x4c\x00\x1e\x24\x3e\x27\x09\x55\x4f\x6f\xfa\xcd\xad\x1c\xf2\x51\x87\x8e\x87\x1c\xb8\x23\xe2\xeb\x44\x7c\xd4\xd2\x35\x31\xe0\xc6\x25\x29\xe9\x98\x8c\x69\x46\x60\x02\x98\x53\x60\x22\x96\x33\xaf\x1b\xb0\x51\x5a\x37\xad\x25\xfd\xa0\xe3\xe5\x30\x55\x04\xb0\x7c\xaf\x88\x6c\xaf\x9f\x9f\x26\xf2\x4a\x29\x87\xb6\x9d\x8b\x46\xf3\x91\xd6\x66\xca\xfb\x69\xb7\xdb\x11\x09\xe9\x48\x14\x1e\x51\x8e\xfb\xb6\x2d\x42\xa4\x1c\xe6\x23\x8b\x46\x2b\xf1\x6b\x53\x51\x0a\xee\xb8\xbe\x4b\xa9\x58\x8a\x7e\xd9\xe2\xcc\xdb\x76\x58\xeb\xbd\x8a\x95\x7a\x85\x18\x29\x49\x88\x7d\x58\x48\xb9\x6a\x9e\xef\x91\x86\x84\x8f\x02\xa9\xb3\x2c\x2a\x13\x71\x90\x67\x59\xb4\x65\x8b\x37\xf0\x5c\xbd\x43\x1b\x3b\x9b\x74\x62\x85\xd7\x14\xcb\x25\x53\x88\x97\x38\x35\x83\x7a\x87\xfb\x12\xf7\x89\x21\x35\x6e\xa6\xc6\xd8\x47\xc7\x94\x5e\x0e\x40\x74\xb4\x80\x29\xf7\x48\x4e\x53\x14\x13\x71\x01\xec\x82\x6c\x10\xcd\x48\xee\x14\xaf\x69\x86\xfd\x3a\xe9\x12\x13\xc4\x68\x2c\x4f\x26\xca\xd5\x5d\x27\x6e\x41\x8d\x90\x75\xe8\x25\x38\x1e\x2c\x68\x2c\x16\x51\x0d\xb6\x4c\x78\x3c\x4f\x62\x85\x65\x42\xa9\xef\xe2\x99\x9a\x79\xde\x60\x4b\xc9\xb3\xfe\x44\x27\x4e\x48\x1e\xd7\x0f\xba\x62\x43\x3d\x75\xbb\x8f\xdd\xee\x13\xe0\x83\x8f\x0d\x36\x54\x67\x22\x26\x4a\x4e\xd7\x53\xb7\xdb\x91\x39\x3a\x8f\xcb\xe5\xa3\xf8\x91\x5f\x4f\x95\x70\x96\x26\x93\x60\x15\x5f\xd3\x89\x53\x10\x51\xf3\x40\x0a\x6a\xb9\x52\xfa\xcd\xc5\x7e\x93\x8a\xc2\x44\x4a\x1d\xe6\x74\x2a\x88\x6b\x71\x13\x58\x95\xaa\x12\xa9\xea\x41\x05\x7d\xaa\xcf\xcf\xb8\xe2\x22\x60\x60\x54\x3c\x91\x27\xfa\x48\x1e\xe9\x82\x64\xb4\x20\x85\x3c\x12\x82\xca\x28\xac\x31\x59\xd0\xe1\xa8\x9f\xd9\x76\x7f\x51\x1f\x54\xd1\xe2\x3d\x8d\x40\x6d\x30\xa3\xe3\xbe\x6d\x67\xa7\xd4\xad\xf4\xf6\x5c\x32\xa7\x8f\xc3\x0c\x18\xaa\x64\x06\x41\xc9\x2a\x25\x31\xcd\x2c\x14\xd0\x02\xf7\xe3\xd3\xac\x8f\x53\x8a\x50\x48\xe7\xaf\x51\x48\x9f\x86\xb6\x1d\x68\x16\x6c\x42\x67\xaf\x43\x0b\x95\xf4\x69\x18\x54\x2c\xd8\x79\xcd\x82\x5d\x0c\xe3\x91\x95\xe2\x9d\x7b\xe0\xc3\x26\x9a\x0f\x3b\x7b\x5d\x92\xc5\x30\xb6\xed\x11\x0d\x7f\xb9\xef\x8b\x5c\x34\xad\x6c\xf5\x0f\x2c\x2b\xf7\x17\xad\x83\x24\xce\xcd\x82\xe4\xb0\x59\x52\x71\xef\xb0\xe8\x67\xe9\x4f\x80\x16\x92\x67\x22\xe9\x4f\x01\xc1\xb7\x6f\x2e\xb9\x85\x32\x9a\xbf\x7c\x5d\x64\x2f\x5f\x17\xd9\xe6\x75\x91\xab\x83\xa0\xee\x8b\x98\x4a\xbe\xa3\x24\x57\x44\x20\x01\x46\x69\x51\xdd\x17\xf1\x72\xd9\x09\xe4\x7d\x21\x2e\xa6\x35\xa2\x21\xdb\x91\x4f\x23\x9d\x44\x5e\x0f\x45\xe3\x7a\x00\xb3\xf4\x4c\xdd\x0a\x22\x7d\x90\xfb\xee\xeb\x0c\xee\x83\x98\x4e\x05\x55\x1b\xd0\x29\x0a\xc4\x1d\x90\x54\xf7\x41\x46\x63\x3b\x50\x0a\x67\xa7\xee\x00\x05\x34\x26\x29\x2d\xb0\x8f\x32\x6a\x67\x24\xa5\x09\x26\x69\x0d\xec\x61\x7b\xa5\xf5\xf6\x6a\x24\x01\x18\x44\x19\x4d\x6a\x78\xcc\x69\x51\x6d\x65\x41\xe0\xa6\xb0\x67\x13\x92\xd0\x94\x70\xb9\x6f\xdd\x3e\xef\xe3\x8c\xa2\x64\x68\xdb\x7c\x44\x93\x21\x1f\x59\x85\xf8\x93\xe1\x9d\x68\xe9\x12\x11\x41\x23\x4a\x21\x65\xe0\xfa\xe2\xe7\x97\xa8\xb2\x11\xd1\xed\xa2\x84\x0e\xb3\x8a\x7b\x97\x60\x62\x59\x81\xdc\x2e\x09\x09\x60\xbb\xcc\x73\x16\xc6\x45\x9c\x09\xd4\xaa\xd8\x06\x4a\x5f\x60\x1a\x74\xbb\x0c\x84\xbf\x5a\xdc\x03\xef\xa7\xb8\x07\x3f\xc5\x37\x68\xb1\x0d\x2c\x4f\xb3\x0a\x9e\x5f\xe2\x15\x28\xa5\xc0\xad\xc2\xdc\xab\xda\x62\x09\x30\x2e\x4e\x73\xe0\x27\x40\x58\xf1\x14\x8a\x69\x3c\x96\x54\xfc\x16\x55\x77\x31\x34\x7b\x5d\x5f\x83\xac\x47\x60\x02\xd8\x97\x84\xe9\xa6\xc7\xa4\x5c\xca\x8d\x53\xfc\xb3\x0c\x72\x76\x9d\x65\x5c\x4c\xf1\x3f\xf3\x4d\x9b\x96\x84\x6f\x62\x64\x62\xdb\x07\x4e\x41\x42\x81\x44\x91\x92\xde\x5a\x7b\x24\x52\xc7\xcd\x74\x9d\x7d\xc9\xaa\xf3\x3a\x94\x0a\xd0\x2c\x4e\x43\xd2\xd8\xee\x32\x9f\xc0\xaf\x24\x83\x49\x64\x80\xf4\xc1\x87\xe0\x83\x9f\x0c\x02\xdf\x53\x87\xc5\xa5\x14\x29\xcd\x6d\xd1\x37\x41\x5e\x06\x80\xec\x16\x94\x7a\x3b\xee\x00\x21\x90\x88\xad\x39\xd0\x56\x88\x7f\xe9\x51\x78\x6f\xe5\x52\x27\x80\xb4\xca\x73\x89\x59\x81\x8a\xc9\x4e\x0f\xdb\x28\x3c\x71\x97\xcb\xf0\x17\xe9\x08\x49\xc2\x20\xaa\x2a\x87\x59\x0a\x7d\x38\x0a\x3c\x3b\xaf\xf8\x29\x09\x12\xe0\x44\xeb\x36\xd5\xef\xbe\xcc\x04\xcb\xa5\x21\x86\x4b\x15\xea\x2a\x2c\xd3\xc4\x24\x97\x1c\x0a\x38\x64\x05\x45\xa1\x80\x24\xd8\x2a\xf1\xc9\x6e\xb7\x8b\x0a\x81\xdb\xf4\x31\x9c\xf4\x9c\xe4\x34\x52\x6b\x14\x4b\x6c\x35\x45\x01\x89\x49\x49\x3c\x8c\x31\x99\x21\xb1\x25\xab\xb6\x0b\x2c\xc9\x81\x19\xca\x41\x65\xa3\x8e\x07\x28\x94\x3b\xec\x24\xec\x76\x6d\xbb\x20\xe6\xf1\xf1\xf1\xb1\xd9\x11\x99\xb9\xca\x56\xd8\xbb\xa4\xb0\x3c\x60\x52\x64\xcb\xa5\xb9\x27\x73\x70\x8c\x9f\x2d\xde\xed\x22\x8b\x57\x6a\x5d\xcb\xa5\xb9\x2f\x92\x1a\x6f\x88\xcb\x25\xfa\x84\x72\x92\x3b\xcc\xba\xb5\x7a\x40\x4d\xd3\x8e\x46\x19\x72\xec\xb0\x7f\x8a\x85\xaa\x31\xc1\x8e\x38\xee\x9f\x50\x4c\x62\x55\xc2\xc5\x24\xd6\x23\xd5\xd9\xf1\x73\x4e\x63\x55\xa6\xb4\xe8\x1e\x29\xc4\x9f\x8c\x7a\xab\x4a\x85\x4e\x37\xe9\x91\x2b\x85\x7e\xb4\x56\xe6\x05\x26\x99\x06\x0a\xa8\x62\x22\x32\xcb\xc2\xe4\x57\x49\x46\x89\x1d\xee\xa9\xba\xc0\x04\xf6\xcf\xd7\x42\x99\x25\x89\x43\x71\x54\xeb\xea\x74\x65\x59\x3e\x0b\x38\xdd\x6a\xeb\x80\x64\x2d\x98\x45\x69\x8a\xab\x06\x78\xb7\xbb\x61\x77\x8d\x0f\x50\x4a\x39\xe1\x12\x1f\xf1\xd9\x96\x2c\x4c\x64\x61\x84\x51\x9d\x29\xa5\xef\x2a\xed\x8a\x0d\xa9\xe8\x75\xb9\xd8\x4a\x33\x7c\xab\xc4\x26\x40\xbe\xac\xb2\x11\x0e\x78\x6a\xe6\x84\x72\x30\x02\x34\xe4\xda\x64\x86\x63\x8a\x5b\xca\x4a\x9d\x4a\x29\x98\x14\xe2\x73\x53\x2f\x18\x9c\x18\xb7\x35\x83\xa5\x01\x3f\x1a\x48\x33\x86\x81\x34\x63\x98\x01\x63\x6e\x4a\xe7\x83\xa8\x56\xe9\x8e\xc8\xac\x56\x7c\x13\x64\x93\xe4\x5e\x09\x22\x89\x14\x34\x26\x33\x9b\xc6\x98\x24\xa7\x6e\xb7\x3b\x3b\x75\xb5\xcc\xc9\xec\x17\x01\x6a\x48\x54\x29\x77\x22\x97\xc4\xb8\x1f\x9f\xcc\xfa\xb1\x45\x13\x1c\x59\xb4\xb4\xaa\xb4\x98\x24\xb8\x5f\x9c\x2a\x25\xb4\xb2\xd2\x24\x8b\x31\x26\x73\x11\x49\x4d\xdb\xb4\x22\xbc\xca\xe9\x78\x10\x59\x60\x28\xa9\xad\xd4\x2c\xc6\x83\x2d\x71\xea\xad\xd4\xd9\x50\x6e\xc6\x83\x71\xf5\x44\xd0\xb4\xab\xf3\xf9\x73\xf4\x6c\x5a\x85\x65\xae\x3e\x7f\xfe\xd5\x04\xbb\x91\xc4\x7c\xd5\x35\x45\x0b\xdb\xb5\xa1\xa1\x1d\xec\x8f\xb1\x1f\xad\xb4\x72\x90\xb2\x26\x25\xbb\x90\x8b\xb2\x52\x5b\x1a\x22\xd4\x06\x55\x95\x6d\x11\xab\x6f\x22\xeb\x25\x89\xa4\x94\x0b\xb9\x97\x57\xc0\x84\xde\x4b\x9c\xa7\x3e\x13\x1d\x54\x34\xb8\xc5\x2d\x9e\x28\x2a\x04\xad\x02\x97\x81\x53\x08\xd8\xed\x24\x1c\x7d\xc1\x1b\x1c\xe4\x6a\x03\x9a\x16\x2a\x9a\x55\x0c\xcc\x0d\x5b\x04\x1b\x4c\x66\x6c\xfd\x86\x0a\x25\x98\x32\x69\x5f\x34\xf7\x9a\x76\xd4\x0c\x49\x12\xd1\xbc\xfe\xc8\x68\x59\x7f\x2c\xe8\x0c\x4d\x04\xb6\xc5\x1d\x56\x3d\x4e\xda\xf7\x0e\xb3\x3d\x22\xf9\xc1\x74\x3c\x44\x09\x0d\x40\x62\xd5\x1d\x78\x7b\x56\xe2\x27\x23\x01\xfd\x98\x18\x59\xfd\x3e\x82\x38\x56\x5a\x3b\xdc\x8f\x7c\x71\x6d\xbe\x27\xef\xc5\xbd\x42\xf4\x44\x2d\x30\x29\x65\x95\x6e\x7f\x4e\x53\x54\x10\x0e\xdc\x7b\xe2\x75\x04\x35\x9e\xcb\x0b\x40\xf3\x1c\x33\xa0\xc3\xea\xea\x19\xee\xe3\x1c\x24\x09\x62\x12\xd1\xb2\x9d\x39\xa6\x11\xc6\xa4\xa4\x31\x11\x97\x97\xc4\x63\xeb\x34\x8e\xc5\xad\x18\xd7\x3e\x28\x52\xc4\x34\xd5\x87\x49\x26\x3b\x51\xea\x3a\x35\x98\x16\x35\xe6\xba\x57\x71\xdd\x2b\x52\x3a\x05\x8d\x9c\x82\xde\x3b\x05\x99\xd2\x14\x45\x24\x23\xc1\x6b\xda\x23\x57\x58\xd5\x7a\x8f\x81\x39\xdc\xea\x7f\x8a\x4a\x92\x93\x60\x33\x13\x3e\xf1\x06\xc3\x88\x64\x23\x7f\x58\x92\x7c\x44\xde\xd3\x84\x4c\xe5\x7e\x5d\x97\x91\xd6\xb0\xd9\xfa\x4d\x52\x0e\x32\xd7\x55\x85\x34\x7e\x1f\x8e\x57\x38\x61\xe3\x1e\xe8\xa9\x3a\xa4\x00\xf2\x36\x75\x13\x4d\x67\xa4\x4e\x41\x62\x9a\x6e\x68\xec\xc4\x83\x6c\x80\x38\x35\xf5\x4b\xa8\x49\x32\x40\x72\x38\x00\x0b\x8e\xb1\xcf\xa9\xf9\x21\xf8\x60\xfa\x95\x00\x2b\xa7\x31\xe8\xf2\xc4\x52\x97\x67\x86\x52\x27\xc4\x24\xc6\xfe\x63\x15\x06\x59\x2f\x1f\x5c\x2b\x88\xfc\x22\x01\xa5\x95\x0e\x67\x8a\xc9\xad\x15\x83\x3a\x39\x16\xd9\x53\x25\x1c\xe6\xc3\x55\xb5\x45\x31\x9d\xd3\x1c\xad\x55\x4e\x3c\x97\x30\x92\x91\x8e\x8b\xb1\xec\x72\xaa\x1e\x5f\xea\xae\x13\xe0\x2f\x2a\x03\x12\x82\xd6\xcf\xfe\xf3\xe6\xe3\x16\x56\xa3\xd1\x58\x8f\xa6\xd8\x3a\x38\xf6\xd2\x6f\x40\xa0\xd0\x83\x38\x26\x17\x2b\x81\x50\x45\x6c\x1c\x94\x09\xa7\xb1\x53\x67\x8f\x49\xe5\x73\x02\x6d\xb3\x1e\x1e\xaf\x94\x71\x62\x92\x12\x4e\x18\xa0\x25\xb5\x11\xac\x1c\xaf\xd0\x8f\xfc\x07\xa4\x4e\x8e\x6a\xc3\xda\x68\xbf\x27\xe8\xa2\x08\x71\x62\xaa\xfe\x98\x8d\x07\xe3\xda\xae\x9c\x13\xac\x7e\xe8\x9a\x40\xd7\xe9\xc1\x4b\x22\xea\x49\x5f\x04\xae\x00\x2e\x08\x7c\x11\xa4\x68\x17\x83\x47\x02\xb4\x27\xce\x63\x8a\x0e\x04\x72\x1a\x3b\xe3\x2c\x7f\x08\xf2\xe8\x9a\x8d\xd7\x5d\x55\x69\x8b\x74\x95\xb1\xe8\x44\x7c\x24\x41\x51\xb0\x02\xf8\x98\x10\xfe\x10\xcc\xc4\xdd\xca\x9c\x30\x4b\xb2\x9c\x44\xb5\x9b\xb1\x72\x60\xc6\xe9\x94\xe5\x31\x37\xfd\x52\x72\x3a\x2b\x8b\xaa\x2d\x6f\x64\x66\xb1\x98\x98\xe0\x90\x8c\x39\xda\x01\x79\xe5\x2c\x86\x52\x3a\x1d\x54\xf3\xe3\x4f\xc9\x82\x32\x70\xf7\x7f\x06\xed\xdd\xc3\x63\x0b\x4f\xd8\x9b\x30\x64\x85\xb8\x2c\x98\xb3\x88\xd9\xc3\xaf\xd9\x23\x79\xaa\xab\x98\x0c\x4c\xd7\x70\x8d\xde\x9e\xd1\xdb\x33\xfd\x09\x79\xdc\xf0\x7d\xa6\x47\x69\x12\x53\x8d\x51\x87\xc4\x08\x45\x58\xb4\x07\xbf\x6a\x10\xca\xb1\xbf\xf2\xe1\x5f\x75\xc9\x24\x66\xa3\x43\x26\x31\x55\x77\xcc\x51\xc3\x56\x91\x74\x59\x70\x9e\x30\x71\x05\xa1\x39\x51\xbd\xc9\xc1\x53\x5a\xd5\xaa\xdf\xf4\xe4\x9e\x38\x79\x96\x71\x12\x92\x6a\x56\x3b\xa0\x67\x98\x0c\x55\xd7\x2a\x87\xfa\xb2\x50\x21\x0a\x45\x18\x8f\xea\xdd\xd5\x01\x69\x97\x64\x58\xf7\x7b\x5b\x99\x19\xc6\x23\x4c\xc6\x59\x58\x16\xc1\x7d\xc2\x7c\x13\x94\x96\x4c\xa2\xc6\xe1\x3f\x11\x68\xcf\x5f\x10\x33\xc8\xe3\xc0\x9e\xc6\x51\xc4\x52\xd3\xef\xdc\x2f\x97\xca\x55\x51\x9e\x25\xcc\xbf\x1f\x98\xf1\x6c\x62\xfa\x3a\x8e\x8d\x7d\xbe\x22\x8f\x98\xa4\xe4\x7e\xb0\x3e\x05\x72\xce\x4c\x38\xb0\xe4\x1e\x83\xbd\x18\xbc\xc2\xb8\x1f\x3a\xb3\x32\x16\x93\x41\xcd\x9b\xc5\xe4\x32\xcc\x52\x93\x70\x27\xd0\xeb\x97\x88\x1e\x6f\xb1\xc9\xf6\x2c\x26\xcb\x7f\x2e\x0b\x96\xdf\xb0\x44\xa0\x96\xca\xd1\xb6\x72\xd5\xeb\xb1\x99\x49\xa6\xe0\xa5\x5e\x7d\x44\x71\x31\x4f\x82\x27\xdf\x8c\xd3\x24\x4e\x99\x7d\x9f\x64\xe1\x57\x93\x8c\xe3\x24\xf1\xcd\xb0\xcc\x73\x96\x72\xb5\xc0\xe3\x84\x3d\xde\x4c\xf3\x38\xfd\xea\xbb\xb5\xcb\xfc\xa6\xbb\x3d\x47\xf9\xd9\x47\xbd\x3d\xdc\xf0\x3e\x27\xf2\xd4\xae\xe8\xd4\x14\x20\x53\xb4\x61\x92\xe7\xa8\xcc\x83\x2d\xb9\x74\xb4\x53\x4c\xb3\x9c\xb3\x7c\x85\x57\x72\x05\xae\xf2\x78\x16\xe4\x4f\xfe\xb3\x5c\x8f\xca\x31\xa0\x33\x97\x09\xce\x2c\x88\x53\x95\xf7\x46\x23\xc4\x9b\xb9\x2b\x5c\xb9\x99\xff\x0d\xcc\xe7\x66\x66\x89\xc2\xc1\xcf\x82\xa9\xbc\x80\x53\x6d\x66\x65\x22\xba\x59\xe7\xdb\x18\x36\x54\xf4\x62\xad\x91\xca\xb0\xaa\x26\xf5\x52\x6e\x75\xff\xb9\x9a\xe5\x6a\xf7\xd7\x99\x6e\x66\x41\x92\x34\xb2\x6c\x5f\x08\xd0\xfa\x50\x59\xde\x05\xf9\x84\xfd\xa8\xc4\xee\x3e\x5e\xad\x56\x98\x48\x83\x9a\xe6\xfb\x32\xd6\x1b\x70\x85\x51\xb8\x06\x90\x15\xb8\xec\x6f\x53\x9c\x11\xb7\x13\xf8\xf6\xcb\x9f\x9e\xc1\x20\x28\xdc\xa3\x3a\x9b\xd9\xc8\x65\x62\xd4\xb0\xd0\xb7\x41\x73\x69\x07\x36\x28\xa5\xda\x87\x4d\x7d\x01\xa5\x3f\x7b\x43\xec\xe1\xfe\x76\x9f\x99\x9c\x98\x77\x77\xac\x78\x9f\x45\xa5\x38\x8a\xca\x53\x44\xc7\x5d\x61\xc2\xab\x1b\x53\xf9\x20\x93\x72\xa6\x39\x12\x77\x8c\xb8\x6b\x90\x4b\xc4\x87\xb7\x5f\x5f\xae\x18\x65\x3a\xb8\x7e\xd8\xe7\x01\x9f\x8a\x0d\xef\x9b\xef\xbd\x9e\x71\x14\x7a\x8e\x27\xa0\xb3\xed\x1c\x1b\x3d\xbb\x57\xd8\xce\xb1\xdd\x93\xff\x0c\x11\x34\xc4\x8f\x21\x7e\x7a\xdf\x66\xae\xd1\x0b\x6d\x28\x50\xa5\x16\x3a\xb5\xaa\xa2\xaa\x41\xe4\x3f\xf8\x57\xf2\x9b\x2b\x4c\xcc\xf7\x59\xce\x7e\x67\x39\x37\x71\xbf\x1e\x79\xfc\x7f\xec\x04\xf7\xce\x0e\x9c\xbd\x43\x18\xb0\x0a\x78\xbd\x62\x4f\x84\x3c\xb7\xfa\xcf\x56\x11\xb6\xe7\xde\x78\x87\xce\xfe\x2e\x64\x13\x33\xbe\x6f\x78\xbb\xce\xfe\xf1\x3b\x6f\xdf\xd9\x3f\x36\xbc\x43\x11\xed\xed\x3a\x7b\x9e\x71\x24\xfe\x78\x87\xc6\xa1\xa1\xd2\x5c\xf8\xdb\x33\x0e\x65\x12\xfc\x91\xf9\x65\x0a\xe4\x3a\x14\x45\x64\x51\xa8\x45\x24\xab\x1a\xe4\xf4\x9f\x05\x69\xc8\x92\x7f\x87\xc9\xdf\x33\xbc\x5e\xe2\xc1\x20\x9d\x3d\xef\x9d\xe7\x19\x87\xce\xd1\xee\xef\x3d\x77\xda\xfb\x5d\x84\x92\x7d\x67\xff\xc8\x10\x23\x7f\xd7\x73\x45\x5e\xfb\x48\xfc\x33\x8e\xe4\x44\xbc\xc9\xf3\xec\xe1\xd3\x5c\x20\x68\xff\x0e\xb3\xa1\x86\x28\xa6\xc2\x96\xf3\xb1\x6b\x78\x07\x8e\x77\xf8\xfb\xde\xd4\xee\x2d\xbc\x9e\xe3\x1d\x26\xb6\x98\x12\x1b\xa6\x04\x66\xef\xc8\x38\x32\x8e\xec\xe6\x84\xbc\xcd\x1e\xd2\x7f\x97\x29\xf1\x8c\x9e\x37\xed\xf5\xde\x89\x93\x66\x88\x8f\x6f\x33\xaf\x67\xef\x8a\xe9\xb0\x7b\xd3\xde\x42\x00\x30\x1b\x66\xc7\xde\x9b\xf6\x16\x7b\x72\x1a\xfe\x08\xf2\x34\x4e\x27\xff\x16\x13\xe0\x1a\x07\xef\x8e\x00\x2c\x28\x88\x00\x00\x26\xb1\xf7\xc4\xc9\xd8\x03\xb8\xe3\x1a\xde\x51\x72\x60\x1f\x28\xe8\x30\x65\x8b\x3c\x53\xde\xf4\xff\x1d\x66\xc0\x33\x0e\xc5\x4a\x4f\xe5\x6d\x25\x96\xf9\x00\xc2\x9e\x7d\x2c\x00\xf7\x91\x06\xdc\x47\x15\xe0\x3e\x5a\x07\xdc\x47\x0d\xc0\xdd\xab\x00\xb7\x98\xb7\x50\x24\x8b\xcb\xef\xc8\x16\x40\x5c\x40\x97\x42\x06\x0c\x00\x34\x86\xf8\x80\x23\x26\x03\x35\xf0\xb9\x4c\xc7\xd9\xc7\x92\x27\xca\x00\xf4\xff\xf1\xf3\x7c\x64\xec\x2f\xbc\xbd\xc4\xf3\xec\x43\x39\xc0\xab\x24\x78\x02\x80\xf2\xef\x30\xba\x03\xc3\x3b\x9e\xee\xfd\xbe\xff\xdb\xc1\xc2\xdb\xfb\x36\x3b\xb2\xbd\xbd\x85\xb7\x27\x62\xa6\xb6\x82\x1a\x57\x41\x59\xb0\x7f\x87\xb1\x7a\x87\xce\xc1\xbe\x71\xe0\xec\xee\x9f\x79\x07\x4e\xcf\xd8\x73\x8e\x0d\x6f\xcf\xe9\x79\x86\xb8\x31\x8c\x3d\xd8\xf3\x3d\xc3\xb5\x0f\x9d\xe3\x63\xb1\xc1\x8f\x64\x08\x76\xfe\xa1\x71\x64\xc8\xaf\x70\xd7\x39\xdc\x35\x5c\xe3\xc0\x39\xda\xb3\x7b\xce\xfe\xbe\x71\xe8\x1c\xee\xda\xe2\xf0\x39\xee\x51\x68\x3b\x47\x3d\xa3\xe7\xec\xee\xda\xbb\x8e\xbb\x67\xec\xd9\xfb\xa2\xd5\x3d\x7b\xd7\xd9\x15\xe7\xe9\xc0\xee\x39\x07\xc7\xf6\x81\x7d\x50\xc8\x80\x71\x60\x1f\x84\x9e\x73\x70\x60\xb8\xc6\xae\xe3\xed\x39\x07\xc7\xc6\x9e\xd3\xeb\x19\x9e\x73\x78\x04\x57\x9d\x37\x3d\xfc\x7d\x2f\xb1\x7b\xce\xee\xbe\xa8\x78\x5f\x2e\xcb\x35\x1b\xe7\xac\x98\xfe\x5b\x2c\xcc\xb1\xe1\xed\x4e\xed\x03\x00\x60\x0b\xfb\xe0\xb7\x7d\x71\x8b\x1d\xfc\xbe\x0f\x30\xed\x60\xa1\xd0\xeb\x37\xd1\xbf\x05\x40\xf1\x8e\xc5\x42\x7a\xae\xe3\xee\x9d\x79\x47\xce\xc1\xa1\x71\xa0\x90\xdc\x83\x3d\xb5\x13\x8d\x63\xc7\x13\xbb\xf2\xc0\x39\x30\x20\x7a\x5f\x14\x39\x12\xfb\xa9\xe7\xec\xee\x19\x47\xce\xae\xd8\x2e\x9e\xeb\x1c\x0b\x0a\xc8\xdb\x0b\xc5\xde\xd9\xf5\x0c\xb1\xa5\x8c\x03\xf1\x6f\xea\xed\x86\x3d\xe7\x50\x64\xdb\xb7\x7b\x4e\x6f\xcf\xd8\xb7\xf7\x05\x31\xe3\x1c\x88\x5d\xeb\xee\xdb\x7b\xce\xe1\x91\xbd\xe7\x1c\x88\xd0\xf1\xc1\xb7\xf7\xde\x9e\xe1\xed\x2e\xf6\xa6\xf6\xde\xc2\xde\xfb\xed\x30\x11\xf9\xf7\x8d\xfd\xa9\xbd\xab\x2e\xd0\x24\x2b\xa3\x4f\xf3\x24\x0b\xfe\x4d\x96\x41\x5c\x8e\xde\x3b\x71\xf7\x1d\x1b\xfb\x35\xb5\x21\x62\xc5\xc0\x65\xa0\xa2\x4d\xf6\x0d\x99\x53\xc6\x1e\xd7\xd4\x8c\x8c\xf6\xe4\x7f\x32\xac\xe8\x93\x6a\xde\xfe\x45\xf8\x59\x69\x69\xe6\x2d\x63\xe9\xd4\x9c\x72\x3e\x2f\xfc\x9d\x9d\x59\xc0\x59\x1e\x07\x89\x5d\xc6\x4e\x98\xcd\x76\xe6\x79\x16\x95\x50\xc4\x06\xf6\xc9\xce\x20\xcc\x22\x46\x4d\x8b\x91\x94\x7a\xfd\x74\xd3\xe6\x7e\x6a\x51\x0f\x73\x8b\x9a\xdd\x20\x9f\x14\xc3\x91\xc8\xbb\xe1\xc1\xaa\x61\x7e\x3f\xad\xd8\x90\xe6\xfb\x38\x8d\xc7\x31\x8b\x8c\xf7\xba\x17\x9f\x2e\x0d\x68\xd6\xf8\xff\x99\x16\xb3\xcc\xbe\xb1\x88\x8b\x98\x1b\x26\xd8\xb4\x19\x67\xb9\xc1\xa7\xcc\x18\x97\x49\x62\xcc\x58\x51\x04\x13\xe6\x98\x2b\xc5\xc1\x0e\xb6\xf3\xae\x81\x73\xbd\xed\x7f\x3f\x72\xb9\xf1\xda\xa8\x7c\x6a\x5c\xb3\x20\xe4\xc6\x42\x50\x0a\xbb\x8e\xf7\x27\xe3\xb5\x01\xae\x5e\x9d\x7a\xb2\x9c\x59\x9c\x3a\x5f\x8a\x3f\x19\xaf\x45\xea\x59\x36\x7f\xca\x05\x76\x68\xa0\x10\x1b\x17\x41\xc8\xee\xb3\xec\x2b\x31\x2e\xd3\xd0\x31\x82\x34\x32\x62\x5e\x18\xc1\x78\x1c\x27\x71\xc0\x59\xe1\xa8\x62\xb7\xd3\xb8\x30\xa4\x0b\x12\x43\x4c\xa0\x11\x17\x86\xea\x42\x04\xae\x30\xe4\xf0\xdf\x5f\xde\xea\x68\x63\x9c\x95\xa2\xba\x54\x24\x88\x2a\xde\x5d\x9e\x9d\x7f\xb8\x39\x37\xc6\x71\xc2\x54\xb4\x91\x67\x19\x37\xa2\x38\x67\x21\xcf\xf2\x27\x23\x1b\x03\xa7\x47\x37\xc4\x73\xc6\x44\x07\x76\xf4\xe1\xda\x3b\xc6\x24\xdb\xe6\x1d\x55\x3a\x0e\xe9\x76\xe5\xaf\x33\xce\x72\x12\xd3\x6c\x50\x7f\x22\x53\x4e\x0b\x93\x67\xc4\xc4\xfe\x81\xeb\xb9\xbb\x24\xd8\x9a\x6b\x9e\xe5\x3c\x48\x54\xa6\x03\x92\x6c\xcd\x34\xce\x83\x49\xa3\xae\x43\x52\x6c\xcd\x26\x97\xed\x6e\x96\x45\x4c\xe5\x3c\x22\xe1\xf6\x56\xf3\x4c\xcc\x4d\x2e\xb3\x81\x18\xf4\x0b\xd9\xc0\x49\xa4\xaa\xed\x98\x44\x5b\xb3\x29\x97\x93\xaa\x32\x97\x8c\xb7\x0f\x42\xbe\x70\xdc\xe5\x6c\xac\x72\xf6\xc8\x7c\xfb\x38\xca\x62\x2e\xd6\x55\x65\xdb\x25\xd3\xad\xd9\x66\x6c\x96\xa9\x2c\xfb\x64\xb6\x35\x4b\x12\x7c\x7b\x52\x59\x0e\xc8\xe2\x27\x96\x33\xe6\x0c\xde\xd6\xfb\x4d\x7b\xfb\x5b\x41\x06\x34\xf0\xa5\x70\xb2\x7c\xb2\x13\x65\x61\xb1\x03\x27\xd6\x8e\x98\xd8\xb3\x39\x3c\x8d\x0c\xe2\x74\x11\xe4\x71\x90\xf2\xef\x43\x0e\xeb\x7f\x0a\x38\xe4\xc9\xfc\xd7\x40\x06\x38\x96\x29\x18\xc4\xa7\x59\x6a\xcf\x74\x65\x11\x5b\x18\x2c\x5d\xc4\x79\x96\xc2\xc3\xbc\x28\x0c\x05\xa1\xfe\x02\x4e\x6e\x10\x45\xb1\xf4\x5d\x64\x4c\x59\x32\x1f\x97\x89\xf1\x20\x69\xe1\xc2\x31\xc1\x87\xd4\x84\x3e\xc7\x05\xb8\x8b\x65\xd1\xa6\xdd\xa0\x8e\xb7\x22\x2c\xfd\x67\xc9\x4a\x76\x91\xe5\x21\x93\xfe\x57\x9b\xf9\xaa\xf4\x6b\x29\x2b\x01\x5e\x73\xb7\x66\xb8\x51\x2e\x75\x5b\x89\x2b\xf2\x44\x9b\x2e\xb9\x2b\x7b\xff\xb5\xf7\x55\xca\x48\xd3\x81\x2b\xe5\xf2\x33\x67\xe3\x82\x3e\xc9\x70\x09\xdd\xca\x69\xba\x5c\x4e\x6a\x55\xff\x07\xd1\x40\xf5\x75\xfe\x3f\xaf\xf9\xb1\xe1\x5a\x28\x2e\x60\x31\xab\x95\xa7\xcf\x2b\xd2\x4c\xd7\x0e\x84\xd7\x9e\xb9\xb7\x9a\x40\xec\x76\xeb\x0d\xdf\x8c\xd5\x4a\x96\x4d\x51\x8c\x7b\x74\xb4\x8f\x71\xbf\xd9\x39\x67\x6d\x82\xeb\x07\x73\x53\xf7\xc2\xc4\xed\xde\x8d\xeb\xc5\x6c\x3d\xa3\x6f\xab\xb6\xb1\xf0\xba\x66\xb3\x51\x5e\x54\xfd\xd0\x70\x6f\xd0\x68\x06\x70\x96\xaf\xf4\xbc\x91\x9a\xb2\x07\xe3\xa1\xff\xd5\x69\xf8\x40\xa0\xe7\x24\x47\x5f\x9b\xdd\xc3\xe4\xab\x13\x17\x57\x65\xce\xd6\xe6\x58\x59\x4f\xfb\x48\x9f\xd5\x03\x93\xf2\x93\x70\xf6\x43\xe7\x50\x37\xf4\xf9\x2b\x7b\xf2\x3b\xf2\x5d\xad\xe3\x92\xbb\xbb\x82\x25\x3a\x04\x77\x8b\xc0\xbb\xea\x8d\xf8\x65\x5d\x20\xec\x79\x45\x02\x10\xdd\x22\x09\xad\xbc\x9b\xc8\xe7\x72\x10\x1e\xcc\xc5\x05\x56\xb9\x9a\xe7\x62\x17\x81\x10\x2d\x84\x30\x69\xa4\x7c\x65\x4f\x60\xbe\xc8\x34\x2d\xf8\xc0\x84\xe3\x33\xfd\x56\x9e\xe3\x6e\xb7\x73\xb3\x6e\x7b\x0f\x4c\xd3\x66\xc3\x7c\x44\xf9\x30\x1f\x55\x3a\x32\x6b\x20\xca\xee\x81\x60\x27\xa5\xb4\xc0\x59\xf5\x0e\x4d\xd3\x4a\xce\xcc\x3b\x29\x6a\x18\x19\x52\x69\xe6\xaa\xc0\xa4\xa4\x6e\xbf\x3c\x29\xfa\xa5\x65\xe1\x70\x58\x8e\xea\x9a\xc1\x4a\x54\xbf\x51\x59\xb8\x92\x4e\x14\x98\x46\xea\xc0\x8f\x75\x3d\x03\x05\x5d\x4b\xa9\xde\x94\x45\xf7\xf5\x28\x0a\x18\x85\x7a\x7a\x7c\xf5\x4a\x6e\x79\x3f\x26\x22\xe0\x33\x22\x96\x2a\x80\x95\x4a\x08\x9c\x54\x3f\x23\x77\xd9\x43\xca\x72\xff\xa3\xa3\x96\xbe\xe1\xf7\xf0\xb6\xe1\xcf\x71\x43\xee\x4e\x1f\x23\x90\x3b\x76\x74\x5b\x94\xd2\x18\xe0\xdf\x15\xdd\xf9\xbc\x63\xed\x4c\xc8\x35\x1d\x8e\xea\xe5\xff\xd0\xf2\xde\x72\x5d\x19\x3b\x96\x58\xf8\xb5\xb4\xb4\x53\x49\x4b\x3b\x39\x2b\x04\x7e\xcb\x48\x26\x16\xf4\x0a\x64\xbb\x28\x27\x99\x23\x2a\xa4\x29\xc9\x2a\x18\x93\x43\xb8\x4c\x39\x75\x49\xa6\x8d\x1c\xca\xe2\x72\xe0\xb2\xb0\xcf\x01\xe3\xf3\x53\xa2\x0a\xfa\x39\x81\x62\xbe\xdb\x18\xf8\x9d\x18\x38\xd3\xad\x4b\x41\xfc\x46\x07\x54\x84\xec\x83\x0c\xeb\x6e\x54\x9f\xb2\x27\x9e\x7b\x7a\x5d\x39\xcc\xbd\x96\x62\xe8\xac\xe1\x19\xe6\xfd\x9a\x3e\xbe\x16\x8e\x71\xfd\xa6\xff\x13\x69\x13\x4e\x19\x61\xaa\x64\x23\xfb\x0d\x7b\x9d\x1d\xb0\x0e\x6e\xde\x67\x59\xc2\x82\x14\x3e\x97\x4b\xa4\x44\x22\xd5\xae\x96\x76\x13\x95\xc0\x0e\xc7\x85\x36\xfb\x61\x14\x0f\x31\x0f\xa7\x28\xc1\xcf\x61\x50\x30\x6d\x7c\xd2\x87\x0f\xe5\x95\xc1\x87\xdc\x52\x2f\x14\xe2\xd5\x76\xf0\x55\x59\x5e\x6d\x00\x59\x89\x11\x43\x71\x23\x80\x82\x2b\x50\xf1\xaa\xdc\x0b\xa1\x4c\x80\x50\x13\x0c\xfd\x9a\x8e\x69\xbd\x41\x9c\xb8\xd8\x4f\x31\x81\x1e\x16\xd4\x25\x29\xad\xd3\xfd\xd4\x32\x7d\x73\xcd\x45\x38\xc7\xb8\x3e\x6f\x6e\x3f\xac\x6d\x79\x35\xbc\xe2\xa6\xd6\x1b\x01\x28\x86\xe1\x88\x84\xb8\x5f\x58\x94\xa1\x84\x94\x30\x97\xeb\x36\x10\xf9\x72\xb9\x71\x7f\xf0\x41\x29\x95\x66\xca\x2d\x88\x13\x2a\xe9\xa2\xdb\xe5\xc3\xc5\x68\xb9\xe4\x43\xf3\x7f\xfd\x2f\x8d\x39\x99\x23\x3c\x28\xa5\xd3\x99\x2d\xe8\x56\x89\xa5\x54\x5e\xa9\xe0\x12\x06\x0f\x0b\x1d\x80\x67\x29\x7b\xe4\x20\x14\x94\xa5\xac\x8f\x65\x77\x69\x22\x25\x8f\x88\x1a\x0d\x58\x7a\x86\x11\x6c\x08\xb9\x52\x4a\x13\x75\xa5\xe5\x00\x03\x89\xbe\xd9\x76\x3d\x52\x59\xbf\x94\x00\x7d\x24\x72\xe7\x03\x55\x54\x7a\xdb\xfb\xca\x9e\x0a\xe3\xd9\xb4\x9a\x3e\xdc\xb8\xf6\xd8\x46\x0c\x13\x5b\xe6\xca\xf4\x73\xf0\x2d\x52\xf9\x69\x5a\x21\x46\x4c\x53\xba\x31\xac\xb6\xec\x9b\xa6\xe8\xd9\xf7\x21\x87\xba\x8a\x45\x6b\x83\x4d\xed\xde\x67\x93\x9a\xbe\x49\x5d\x93\x98\xbe\x08\xf4\xcc\xca\x83\xdd\x2b\xd3\x42\xa6\x69\xb5\xbc\x48\x53\xff\x05\x6f\xb4\xda\x33\xde\x0a\x41\x4b\xd8\xe7\xb5\x7d\xe9\xdd\x83\x46\xcf\x2f\x65\xcf\xe5\xd9\x96\x4b\x54\x9d\x6d\xc2\xf5\xc1\xb6\xac\x46\x91\x6f\x6d\x7f\xf0\x1a\x6a\x80\x01\xd2\x0a\x64\xf4\x19\xfd\x71\x9d\x9b\x5e\xf0\xdf\x21\x46\x72\x92\x6e\x1d\x12\x5b\x61\x29\x9e\x22\x45\x51\x05\xac\x06\xbb\x50\xdb\xc4\xff\x36\xaf\x02\x47\xfc\xc0\x7d\xc0\xe1\x3e\x10\xdd\x1e\xab\x3b\x41\xfa\x77\x2c\xf4\xcd\xc0\x1c\x19\x58\x89\xb5\xce\x2c\xd4\x81\x71\x2d\x97\xe0\xa6\xfd\x2b\x7b\x02\xa7\x3f\x62\xfd\x4c\xd3\x87\x25\x81\x29\xae\x96\xe5\x8a\x98\xaf\xba\x3b\x62\xf7\x88\x3f\x29\xc6\x24\xd7\x70\xb0\x31\x8b\xef\xf4\xbd\xa0\xe1\x5c\x4c\x4d\xed\xf5\x5d\x9a\x33\x11\x55\xa7\x2f\x54\x4b\x04\x1c\xfd\x46\x38\xfd\x80\x38\x89\xa1\x12\x4c\xee\x10\xc7\x70\x15\xbd\x5d\x43\x6b\x9a\xce\x90\xb4\x26\xd6\x5b\x7d\xfd\x35\x60\xe4\x3a\x82\xb8\xdb\xf3\x9a\x4e\xaa\x44\xc1\x5f\xe9\xb3\x44\xa5\x64\xe1\xb7\x71\x31\x0f\x78\x38\x65\xb9\xff\x96\x34\x13\x7e\x15\xb1\x67\x60\xd3\xdb\x7f\xd6\xe4\x9d\x42\xb2\x9a\xf9\x3e\xca\xbb\x98\x5c\x16\x37\xd9\x8c\x5d\x33\x41\xed\x33\x10\x82\x49\x27\x7e\x35\x0a\x41\x3d\x28\x17\x89\xf9\xaa\xcf\x9d\x33\x8d\x44\x3c\xcf\x82\xf9\x86\xeb\xd1\x86\x27\x4e\xdd\xf7\xbe\x76\x88\xaa\x47\xa3\x36\x9a\x00\x5a\xa2\x0c\xc9\x57\x44\xb9\x80\xfc\xa9\xea\xc4\xf4\x5f\xc2\xf4\x43\x15\x55\x3d\x6a\x11\xd4\x0d\xbb\x65\x13\x8b\x82\x5b\xd8\x35\xd2\xc7\x2b\x91\xc2\x57\x84\x67\x70\x20\xfc\x4d\xf8\xd0\x1e\x00\x97\x0d\xbf\x74\x58\x08\x5f\x91\x2c\x4d\xda\xf5\xc4\x63\xd4\xb9\x5d\xf7\x1a\x77\x8f\xbc\xbd\xdd\xe6\x52\xaf\x08\x77\x6a\x6c\xf9\x91\x70\xe7\x42\x71\x24\x68\x42\xb8\x73\xa5\xb8\x09\x34\x14\x1f\x65\xce\xea\xbc\xe7\x84\x3b\x37\xc0\x96\x78\x9f\x45\x8c\x16\xe2\x53\x2d\x3f\x9d\x13\xee\xdc\xdd\xdd\x9c\x9f\x5d\x9f\xdf\xde\x5d\x7e\xb8\x3d\xbf\xfe\xf0\xe6\xdd\xcd\xdd\xdb\x8f\x77\x1f\x3e\xde\xde\x7d\xba\x39\xbf\xfb\x78\x7d\xf7\xb7\x8f\x9f\xee\xfe\xb8\x7c\xf7\xee\xee\xd7\xf3\xbb\x8b\xcb\xeb\xf3\xb7\xf4\x57\xc2\xa5\x45\x7b\xc5\x84\xdc\x50\xb5\xd8\x70\xbd\xaa\x47\xd5\x3b\x38\x24\x0c\xe3\x8a\xe5\x29\x68\x48\x79\xd6\xa5\x3d\x92\xaf\xec\x09\x84\x2b\x05\x28\x10\xf8\xa6\x3c\xf7\x4d\x84\x5c\xd4\xfd\x12\x2e\x4e\x0a\x5a\x21\x91\x3f\xc0\xcb\x25\xf8\x11\x78\x23\xd0\x14\x6d\xbc\x56\xba\x91\xde\x4c\x91\x66\x01\x81\x9d\x55\xa3\xf5\xe5\x56\xb4\xbe\x94\x68\x7d\x39\xaa\x25\x2f\xf9\xb0\x1c\x75\xbb\x55\xaf\xc2\x81\xc0\xc7\x7d\xb0\xe9\xba\x92\xd8\xc2\xcb\x58\x7f\xf9\x12\xd6\x5f\xe2\x67\x8d\xed\x97\xeb\x7e\x42\x4b\xf0\x56\x14\x0e\xa3\x26\xd6\x1f\x6d\x60\xfd\x3f\x06\xd0\x2f\x20\xec\x05\xec\x4a\xc9\x8e\x3e\x53\xd8\xe7\x76\x2b\x0c\xd5\x14\x48\x13\x34\xa0\xda\x8b\x18\xad\xdb\x8c\xc8\x5d\x18\x24\x61\x99\x88\xaa\xa6\x41\x3a\x61\xd1\xaf\x31\x2f\x7c\x4e\xee\xd4\x7a\x82\xf9\x0d\x9f\xb5\xbf\x7b\x22\x82\x4f\x73\x16\x44\x67\x12\x85\x26\x57\x8a\x61\x26\xf1\x9f\xb3\x2c\x2d\xca\x99\xfa\x5a\x61\x47\xa7\x36\x9a\x2e\xc9\x9d\x46\xc4\x99\xd8\x8d\xba\x08\x65\xf5\xe8\xf4\x3e\xff\x52\xc5\x5c\x04\xc0\xc4\xdc\x62\x0f\xe4\x8b\x73\x1f\xa7\x91\x04\x43\xb5\x07\x67\x0e\xb3\x49\x99\x00\x01\xba\x8e\x6b\x36\xde\x94\x91\x6b\x5f\x14\x22\x73\x2d\x97\xbc\x45\x0d\xb1\x1e\xc7\x98\xe4\x00\xac\x7d\x09\x2c\xe2\xe2\xf7\x20\x89\x23\xdd\xf3\x5b\x22\x59\x71\xdf\xad\x62\x46\xee\xc0\x75\x21\x23\x77\x05\x0f\x78\x59\xf8\xb6\x47\xee\x14\x09\x53\xf5\x67\xc6\x66\xdb\xad\x50\xd5\x15\x4d\x35\xb9\xa7\x14\x0f\xfc\x7a\x07\x48\x8b\x61\x1c\x6a\x2a\x0b\x76\x16\x24\xc9\x7d\x10\x7e\xdd\xbe\x71\x2e\x10\x6e\x66\x92\x9e\xb6\x55\xc1\xef\x6d\x38\x5d\x4e\xe6\x69\x16\x7b\xcb\xee\xcb\xc9\x9a\x29\x17\xfc\xac\x12\xcf\xc7\x63\x16\x7e\xbf\x4a\x99\xa5\x59\xe3\xe5\x6c\x2e\xd0\xee\x78\xc1\x7e\x0b\xd2\x28\x61\x1b\xc0\xb0\x5d\xc1\x7a\x76\xed\x81\x5b\x56\xf6\x2e\x78\xca\x4a\xfe\x13\xfd\x68\x66\x6c\xf6\xe6\xfd\x4b\x8b\xa3\x0b\x8a\x0c\xcd\x02\xd7\x2c\x2a\xc3\x96\x01\xfc\x6d\xbd\x56\xb9\xda\x9d\xdd\xbe\x23\xeb\x22\x63\x41\x68\xca\xac\xeb\x0c\xb3\xf5\xcc\x92\xbb\x25\xb3\x2f\x58\x0e\x5a\x24\xa6\x7a\xe6\x30\x7f\xf0\xb4\xf4\x53\x4f\x24\x76\x94\xcd\xfe\x3d\x9f\x49\x5c\xa9\xe7\xb0\x77\x2c\x15\x1d\x8e\x5d\xbc\xe6\x07\xfd\xff\xe3\x9c\xff\x2b\x9c\x73\xe9\xf3\xaa\x89\xb5\x04\xa8\xd7\x3b\xc4\x8d\x59\x4d\x6a\x4a\x41\xeb\xea\x29\xa3\xaf\xf2\x26\x6e\xf2\x8a\xc5\xc2\x4b\x4c\xa1\x36\x02\xbe\x2b\x05\xa0\xb9\x13\xcc\xe7\xc9\x13\x4a\x49\xd8\x10\x75\x06\x3e\x6d\x96\xca\xa6\x19\x5e\xad\x34\xeb\x84\x28\x4e\x4f\x29\xc2\x91\x0c\x8f\xe9\xb3\xca\xda\x42\x29\x0b\xda\x71\x49\x48\xd9\xaa\x41\x68\xcc\xd7\x3a\x5d\x92\x08\x32\x56\xf5\x26\xaa\x3b\x63\x52\xf5\x54\xe2\x25\x53\x99\x3e\x93\x3f\x0b\xc9\x22\x6d\x3e\xce\xb4\x89\x4f\x31\xee\xe5\xd2\x2c\xd3\xaf\x69\xf6\x90\xda\x6c\xc1\x52\x6e\xf6\x99\xc6\xcb\x6e\x83\x7c\xc2\x38\x5d\xa0\x14\x93\x75\x23\x94\xd0\xb5\x84\x8c\x89\xf4\x3d\x36\x57\x3d\x02\xd6\x74\xdd\x29\x31\xdb\x60\x27\x68\x6d\x95\xbc\xe3\x23\x85\x59\xce\x68\xd8\x6f\x4d\xd9\x72\x89\x4a\xe9\x50\x72\x86\x57\x2b\x94\x13\xae\xb0\x43\xc2\x04\x2a\xd8\xee\x1a\x5c\x74\xf2\x01\x05\x4a\xaf\x3f\x63\x40\xeb\x93\x8a\xf9\x03\xe7\xf6\x49\xdf\xfe\x4f\x43\x36\x22\x29\x9d\xd4\x46\xd4\x95\x59\x03\xdb\x3b\x59\xf3\x8f\x12\xa0\xe3\x03\xa2\x2c\xf4\x75\xce\xc5\xd9\x80\x61\x71\x87\x3d\xf2\x3c\x08\xf9\xf9\x02\x86\xbb\x56\x44\xe2\xcf\x95\x37\x3b\xd1\xf8\x39\x98\xdb\x21\x29\xe5\x0e\xcc\xf6\xed\xd3\x9c\x15\x9a\xa1\xa9\xc6\x19\xd3\x74\x98\x8f\x48\x42\x39\x29\x28\x20\xd4\x5f\xd7\x51\xd6\x62\xa3\x7b\xc7\xa4\xc0\xb8\xff\x75\x58\x8c\x68\xdc\x97\x1b\x3c\x76\xe6\xd3\xa0\x60\xd1\x35\x9b\xc4\x05\x97\xea\x18\x1f\x82\x19\x03\x8d\x6a\x65\x25\x29\x13\x9d\x0a\x71\xb8\xde\x40\x86\xbb\xdd\x07\x14\x0e\xb3\x11\x1c\x99\x7e\x46\x3b\xae\xe2\x82\x39\xf9\x5a\x7d\x03\xf4\x80\x36\x63\xa1\x1c\x11\xe5\xb0\x9f\x29\x76\x62\x27\x5b\xef\xf6\x11\xc9\xc5\x24\xad\x56\xab\xe6\x8b\x51\x4d\x9a\x7c\x1c\xb2\xd1\xfa\xde\x71\x5d\x98\x57\x91\x44\x39\x39\x83\x9f\xc6\x6c\x0e\xd3\x91\x13\xb1\xb9\x40\xb0\xd2\x30\x66\x05\xec\x8f\x73\x3a\x1c\x91\xaf\xf4\x79\x45\x3e\x8a\x3f\x67\xad\x7d\x72\xd3\xd4\x5d\xec\x48\xe7\xac\x5c\x4c\x0c\xc3\x5b\x5d\xf0\xe0\xea\x04\x0d\xf9\x08\xc6\xf5\xb4\x99\x67\xb9\x7c\x02\xdb\x4f\x54\x32\xaf\xc5\xc7\xc6\x40\x7a\x84\x63\xdc\x17\x49\x34\x17\x4d\xbb\xab\x55\xda\xed\x3e\x22\x79\x98\xbf\xd0\xce\x56\x2f\x4b\x52\x8b\x42\xab\x2d\x51\xaa\xd4\x2a\x9c\x28\x0b\xe1\xd8\xbd\x9c\xd2\x46\x91\xc1\x2b\x97\x38\x35\x57\xf2\xe7\x7a\x0d\x5c\x7c\x50\x24\x2f\xa3\x33\x41\xf3\xc2\xab\xd9\xe6\x0b\xd9\xed\x3a\x00\x3e\x72\xd5\xd1\xe6\x94\x39\x02\x37\x65\x1f\xb2\x88\xf5\x25\x3d\x31\x45\xa2\x59\xd4\x48\x50\xe4\x1d\x18\xcb\x5b\xe3\xa9\x5f\x0d\xae\x07\x15\x1b\xdc\xbf\xa6\x43\x36\xf2\xaf\x28\x6b\x32\xc4\xa1\x57\x57\x9a\x2f\x73\x45\x38\xbd\x06\x3b\xd6\x54\x8d\xe9\x03\x38\xef\xc4\xd2\xea\xba\xdb\xf4\xe2\xc0\x2c\x0b\x7f\x40\x5c\xec\xaf\xd5\x0b\xcc\x48\xf0\x26\xbe\xc6\xee\xd3\x5c\xa7\x3a\x87\x8a\x69\xf2\xf8\xf0\x33\x2c\xe0\x3b\xfa\x86\xbc\x15\xb0\xed\x02\x76\x95\x4e\xff\x15\xe1\x67\xc5\x34\xba\xaa\x9c\xe9\x5c\x2f\x97\xe8\x1b\xc2\xe4\x3d\x6a\x32\xba\xfe\x68\x1c\x85\x0b\xdc\x6c\x13\xf7\x2f\x68\xc7\x85\xcb\xa9\xcd\xd4\xc0\xab\x71\x9c\x06\x49\xf2\xf4\x2c\x5a\x25\xbf\x22\x75\x2f\xbd\xa2\x3b\xff\x18\xfa\x6f\xec\xbf\xdf\x05\xf6\xb7\xcf\xa5\xeb\x9e\xb9\xb6\xf8\x79\x7b\x00\x7f\x8f\xe0\xe3\x02\x3e\x2e\xe0\xa3\x77\x71\xf1\xb9\x74\x77\x0f\x21\xdb\xee\xe1\x5b\xf8\x7b\x61\x7f\x2e\xbd\x0b\x91\xd2\x73\xdd\x33\x1b\x7e\xde\x8a\xbf\x90\xad\xe7\x1d\x89\x94\x33\x17\x3e\x2e\xce\x2f\x3e\x97\xbb\xae\xeb\xd9\x9f\xcb\xb7\x87\xa2\xcc\xc5\x31\xa4\x5c\xbc\x3d\x13\x1f\x6f\x2f\xe0\xe3\xe2\xe2\xed\xe8\xff\xad\x1d\xfb\x6c\x3b\xae\x7d\x2c\x9a\xfe\xf5\x50\x34\xe3\xca\x36\x0f\xa0\x99\xdd\x0b\x68\x66\xcf\x1d\xbd\x7e\xb5\x43\x3e\xfd\xf0\xf5\xf3\x37\x01\x79\x7e\x6f\x41\x9e\x7f\x36\xaf\x53\x85\x50\x04\x61\xc8\xe6\xbc\xf8\x55\xbe\xce\x14\xb4\x27\x1f\x1c\x76\xe5\xcf\x9e\xf8\x91\xaf\xe2\x01\xe7\x79\x7c\x5f\x72\x06\x8a\x86\xf9\x96\xc8\x62\x1e\x84\x8c\x66\x32\x65\x56\x16\xfc\x53\x51\x89\xbd\xd1\x94\x54\x6f\xf0\xe2\x1b\x2a\x51\x4f\xf1\x40\xff\xaa\x56\x8a\x20\x8d\x79\xfc\x8d\x7d\xba\x7e\xa7\x1e\xeb\xfe\x22\x86\x50\xa9\x9e\x1a\x51\x90\x4e\x58\x9e\x95\x45\xf2\x74\xc3\xf8\x65\x9a\xb2\xfc\xb7\xdb\xf7\xef\x0c\xc5\x8d\x01\x02\x4e\x7f\x9c\x4d\x59\xf8\x95\x09\xac\x5a\xe7\x2a\xca\xf9\x3c\x67\x45\x01\xf4\x5f\xca\xcf\xa3\x98\x07\xf7\x09\x53\xba\x02\x55\xf2\x6f\x4f\x91\xbc\x5a\xaa\x04\xfe\x94\x30\x53\x5b\xd4\x30\x4c\xec\x28\x16\x64\x5b\xaf\xf2\x2f\xe2\x76\x48\xd9\x03\xcc\xb4\x4b\x3a\x1e\x91\x1e\x82\x49\xc7\xc3\x2b\x8c\xc9\x70\x68\xca\xf9\x3e\x9b\x06\x79\xc1\xb8\x49\xd4\xb7\x1d\xaa\x88\x11\x19\xb6\x35\x6a\x45\x18\x62\x05\xea\x7d\x01\xaa\xb3\xe3\x2c\x57\x31\x7c\x7e\xfe\xcf\x32\x5e\x80\x6a\x2d\x9f\xdb\x0c\x3e\x46\xa3\xed\x9d\x53\x40\x72\xe8\x8e\xfa\x7f\x11\x77\x80\xec\x27\x27\x1e\xf4\x73\xe8\x8d\xda\x5d\x35\xc3\xf6\x1c\x99\xc4\x8c\xf2\x60\x32\x51\xe1\x62\xce\x92\x04\x26\xd8\x24\x26\x3c\x03\x99\x2f\xb4\xdb\x9a\x94\x1e\x34\xd6\xb6\xe7\xbd\xd6\x6e\x50\xf2\xec\x5a\x1a\xc2\x32\x89\xc9\x1e\x39\xcb\xd3\x20\xb9\x66\x92\xcc\x29\xae\xc5\x28\x73\x16\xc1\x44\x28\xfd\x5b\x93\x98\x62\xdd\x58\xbe\x60\x6f\x92\xf9\x34\xf8\x17\xba\xd2\x6a\xdc\x0c\x92\x24\x7b\xb8\x28\x93\xe4\x26\xcc\x19\x4b\x8d\xa0\x78\x4a\x43\x43\xf4\xe8\x42\xb4\x05\xa1\xab\x24\x78\x32\xc4\xe4\xe4\x59\x52\xe8\xad\x26\x7e\x59\x6e\x68\xed\x4c\x1d\xb8\x8a\x43\x5e\xe6\xec\x32\x55\x01\x41\x76\xcc\x3e\x64\xc0\x80\x09\x38\x33\xa4\x5e\xb0\x91\x64\xd9\xdc\x48\x33\x29\x13\x6a\xa4\x75\x7a\x36\x67\xa9\x31\x4f\x82\xa7\xe2\x12\x34\x6e\x05\xc9\x1a\x7d\x4c\x93\x27\x23\x57\xd3\x60\x28\x9b\x61\x91\x51\x84\xd9\x5c\xfc\xb0\x60\x96\xb0\xa2\x30\x62\xce\x66\x37\x22\xee\x5f\xdc\xb7\xbb\x3f\x5e\xa2\x50\x1e\x2c\x93\x98\xca\xd6\x20\x83\x20\x87\xa8\x02\x74\x89\x59\xf4\x53\x8b\xb0\x4b\x3a\xee\xfa\x21\x31\xc3\x60\x2e\x26\x4b\xec\xb8\xec\x21\x05\x19\xdc\x9f\xa9\x6b\x6f\xcb\x81\x33\xc3\x2c\x29\x4c\x62\xe6\xd9\x83\xf8\x29\xa4\x22\x7a\x31\x0f\xd2\x9f\xaa\xf2\x60\x5b\x95\x79\xf6\x70\x23\x2a\x20\x66\xc1\x83\x9c\xff\x54\x45\xfb\x3f\x98\x54\x40\x61\xfe\x8b\xee\x0c\x3f\xdb\xfe\x08\x0d\x03\xfb\xdb\x08\xef\x4c\x6a\xd0\xfd\xe7\xe6\x83\xc1\xd0\x1b\xb5\xfd\xe2\xaf\x00\x8c\xa4\xdc\x96\xda\xda\x46\x90\xc4\x13\x20\x73\xed\xfb\xa0\x60\xb0\x71\x82\x3c\xb8\x8f\x43\x5b\xec\x3f\x43\x47\xda\x60\xc6\xcc\x08\x83\xb9\x2e\x18\x26\xf1\xdc\x9e\x07\x7c\x2a\x43\xb9\xd8\x8e\xa0\x88\x6c\xc7\x29\x67\xf9\x3c\x4b\x00\x2c\x6e\x8b\xb3\xc7\x71\xc2\x59\x5e\xa8\x34\x25\xc5\xa8\xbe\x24\x03\x52\x40\xd2\x28\x9b\xc5\x69\xd0\xec\x19\x4b\xc5\x49\xb1\xef\x83\xf0\xeb\x24\x07\x2e\xc8\x38\x4e\x12\x3b\x9b\x07\x61\xcc\x9f\xe4\x07\x74\x64\x9c\x64\x59\x64\x43\x85\x2a\x5c\xe5\xc9\x52\x6e\x8f\x83\x59\x9c\xa8\xb0\x58\xe7\x3a\x64\x07\xd1\x97\xb2\xe0\x2a\x82\xe7\x8c\x87\x53\xfd\xf1\x94\xa8\x8c\x8a\xa7\x21\x3f\x1e\xe4\x74\x4c\x92\xa7\xf9\xd4\x4e\x83\x19\x53\xc1\x2c\x8f\x59\xca\xe5\x78\xa7\x59\x1e\x7f\xcb\x52\x1e\x24\x5b\x12\x17\x2c\xe7\x71\x18\x24\x06\xe4\xb2\x83\x68\x61\x3f\xaa\x70\x96\xc7\x93\x38\xb5\x1f\x8d\x78\x16\x4c\x58\x63\x6a\x40\xcf\x3b\xb7\xc5\x35\x0a\x9f\xa2\x0b\x71\x3a\x51\x23\x9e\x05\xf9\x57\x96\xdb\x2c\x8d\x74\x70\x16\x57\x41\xd8\x88\x46\xb6\x60\x39\xac\xeb\x3c\x93\x8a\xf1\x75\x0c\x9f\xc6\xe1\xd7\x54\x80\x85\x79\x10\xa7\xdc\xce\xf2\x88\xe5\xc6\x3c\x48\xb3\x82\xd9\x9e\x31\xcf\x60\x2d\x25\x4d\x5e\x18\x55\x9f\x60\x89\x53\x6e\x14\xd3\x60\xde\xec\x6a\xc1\xb3\xb9\xea\x17\x04\xf5\x42\x14\x3c\x8f\xbf\x32\x81\xa2\x97\x93\x69\xdd\x8d\x76\x74\xdd\x97\x82\xe7\xd9\x57\x66\x47\x41\x31\x0d\xf2\x3c\x78\x6a\x46\x64\xe3\x71\xc1\xb8\x8e\x11\x83\x08\x83\x79\xf3\xf3\x4b\x16\xa7\xfa\x7b\x16\x73\x31\xd0\x59\x5c\x15\x68\xf4\x48\x7c\x82\x49\x03\x83\xb3\x47\x6e\x07\x69\x38\xcd\x72\x19\x8e\x58\x98\xc9\x9b\x5e\x7e\xd7\x23\x04\x2e\x5d\x7b\x32\xeb\xa8\x7a\x04\x65\x1a\x87\x59\xc4\xec\xfb\x38\x8a\xab\x0f\xb0\x8a\x24\xbe\x78\x61\xcf\xc5\xac\xce\x8c\x85\x1d\x88\x8b\xe9\x9e\xf1\x38\x34\x16\xf6\x34\x48\x27\xa2\x95\x85\x1d\x47\x4c\x2a\xd5\x43\xfc\x2c\xe0\x53\x36\x0b\xe4\xd6\x59\x00\x9f\xcf\x66\xc0\xca\x35\xc4\x8e\x82\x7d\xf4\x24\x83\xd5\x36\x6a\x7e\x3d\x19\x0f\x59\x1e\x55\x5b\xe8\x21\x8f\x61\x07\xcd\xb2\x88\x19\x8f\xb3\x24\x2d\xfc\xc7\x24\x4e\xbf\x1a\x8f\xea\xc0\xff\xf8\x6a\xd0\xa4\x95\x7e\xdb\xfe\x2f\xf2\x67\xbc\x0d\x7f\x68\xdf\xa3\xd0\x8a\x1f\x84\xbc\x14\xb7\x98\xfa\xca\xc3\x3c\x4b\xf4\x57\x23\x58\x4c\xb3\x07\x15\x04\x0b\x17\x3a\xfc\xf4\x33\x57\xd7\x4f\xf7\x0f\x50\x24\x7f\x67\xe7\xe1\xe1\xc1\x79\xd8\x05\x06\xa7\x77\x7c\x7c\xbc\x03\x8d\x99\x35\x74\x7f\x9c\x25\xbe\x80\x4d\x26\x81\x60\x12\xa4\x13\x15\x04\xfc\xf6\x25\x68\xff\x3f\xe9\xc7\x5f\xdf\xbf\x13\x7d\x39\xda\x49\x35\x16\xdd\xe8\x0f\x0f\xee\x2f\xd3\x88\x3d\x0a\x64\x30\xcf\x8a\xe2\x23\xac\xf4\x4f\x5d\x3a\xde\x8f\x6e\xf2\xbf\x38\x30\xfa\xdf\x72\x36\x56\x85\xcc\x2a\xc2\x94\xc5\xd5\x52\x4e\x21\xe6\x47\x73\xe8\x8a\x0e\x17\x79\x28\x72\xca\x02\xd2\x6a\x85\x44\x5a\x67\xd2\x60\xc6\xff\xb4\xeb\x6e\x75\x5f\xfe\x8d\xe6\xff\x8d\x47\xea\xfa\x52\xfd\xeb\x9a\x8f\x1b\xfa\x97\x4d\xc6\xca\x40\xac\x21\x3c\x71\xf5\xd5\x4b\x33\xcd\x06\x20\xda\x08\x74\x8b\xdf\xc9\xbb\x5d\xd4\xab\x08\xfd\x6e\x17\x99\x99\x09\x6f\xba\xee\x68\xb9\x34\x3f\xea\x30\x16\x29\xa9\xfc\xf2\x44\xca\x07\x1d\x06\xf3\x99\x68\x9d\xe3\xd9\x78\x2a\xe7\xcb\xe5\x8b\xa9\x1d\x4a\x53\x30\xe5\x4e\x53\xe8\x4f\xed\x87\x58\x4b\xbf\x29\x79\x31\x25\x40\x57\x71\x54\xa4\x08\x5d\x01\x92\xf5\xa6\x5f\xf9\xcf\x85\x58\x2d\xaa\xa7\xa3\x73\xe5\xc5\x51\x34\x36\xe8\xa4\xeb\x34\xa3\x6f\x46\x01\x0f\x6c\xb3\x43\xa9\x74\xec\xd0\x5c\xb4\xca\x06\xe6\x3e\xc6\xdd\xae\xb4\x86\x23\x72\x32\xdc\x57\xa8\xb3\x5f\x89\x9d\xaf\xaa\xe1\xd5\x0e\x7d\xe3\x31\x6a\x3b\x57\xd6\x1d\xc1\x6a\x80\x6a\xdc\x52\xb4\x6f\x57\x57\xc6\x61\x24\xc6\x5e\x55\xb9\x98\x6c\x19\xb7\xef\x6b\x13\x43\xe0\xaa\x88\x63\x19\x7d\xb0\x1e\xbd\x5c\x7a\xa7\x7c\x55\x75\x0e\x58\x2f\x19\xc8\xea\xa2\x54\xbd\x54\xe7\xd2\x12\xb2\xd8\x0d\x83\xcd\x27\xac\x4e\xe7\x93\x64\xf0\xff\x4e\x18\x5e\x2e\xf5\xd7\x6f\x04\xa4\xa1\x5e\x49\xb7\x49\x0c\x0f\x7e\x17\x1b\xbf\xe3\xfa\xe8\x37\x19\x10\xa7\x13\xaf\x10\xc7\xb5\xc7\xe3\x74\x20\x20\xcb\x2c\x5b\xb0\x37\x9a\xe2\x46\x1c\xfb\x20\x72\xde\x88\x21\x20\x87\x84\xfd\x6c\x9d\xfc\x1e\xb0\x61\xd6\xa2\xbd\x47\xb4\xaa\x79\xb7\xa3\x37\x73\xb7\x6b\x9a\x7e\xea\x23\x4e\xb3\x36\x65\x4f\xf2\xf5\x18\x80\x52\xe4\xfb\xbd\x43\x29\xdd\x95\xbe\x3b\x64\xf5\x58\xf2\x13\xb2\x6e\xb7\xe3\x4a\x01\x4a\xd3\x17\x1d\x26\xf9\xa0\x3d\x90\x0f\x37\xc0\x88\x4f\xb7\x0c\x30\xc5\x18\xe3\xd5\xdf\xd6\xcf\xa9\xb9\x5d\xd2\xc9\x14\x27\xeb\x6f\xce\xf6\xc4\x35\xc9\x2b\x4c\xbe\x5f\x6b\x43\x4c\x6a\x4b\xb5\x8d\x54\xba\x26\x44\x25\x61\xd5\x7f\xd2\x9d\x7f\x20\xe7\x35\x1e\x7e\xfe\xfc\x79\x67\xb4\x43\xfe\xfe\x93\xfa\x4b\x8c\xd1\xbf\xff\x84\x02\x13\xdf\x9e\x6d\x4d\x83\x29\xdd\x9e\x6b\x43\x85\x29\xdf\x9e\x6f\x9b\x0e\x53\xf6\x42\xc3\xeb\x4a\x4c\xf1\x8b\xf9\xda\x5a\x4c\xc1\xf6\x7c\xeb\x6a\x4c\xc9\x8b\xd9\xd4\xa2\x36\x7a\xe9\x79\xa4\x78\x61\xe0\x5b\xd4\x9e\xc2\x17\xc6\xbe\xae\xf7\x54\x7e\x3f\xdf\x5d\x12\x17\xaa\xbb\x3d\x97\x44\xdb\x33\xb7\xb4\xa4\xc6\xdb\xf3\xb4\xd4\xa4\xe6\xdb\xf3\x48\x3b\x5c\xb2\x31\x8f\x4c\xd9\x7f\x4b\x99\x6a\xc6\x1a\x34\xa6\x16\x33\xdc\x22\x7a\xcc\xa4\xa0\xc6\x16\xb1\x63\x46\xa7\xac\xdb\x65\xc3\x29\x1b\x2d\x97\x6c\x5d\xf0\x58\x1d\x87\xaa\xbd\x05\x53\x1c\xff\x35\x91\x3d\xad\x68\xb1\x65\x0c\xb5\x58\x9f\xa3\x4c\x91\x09\x58\xb4\x5c\x32\x27\x85\xdf\xaa\xa8\x92\x13\xdf\x52\x50\xdf\x8b\xfa\xc6\x48\x99\x82\xfb\xa6\x16\x9b\x33\xe5\x7d\xc0\xab\x84\x2b\x79\x88\x64\x74\x56\x47\xeb\x2d\x2e\x13\xf2\x2a\xa1\x96\xa9\x53\x49\x61\x9d\xa4\x77\x91\x4c\x28\x37\x12\xde\x89\x5d\xb3\x6a\x49\x4e\x57\x43\xd0\x3d\x5f\x97\x67\x0f\xaa\x5a\x94\x58\x4b\x25\xa9\xa4\x9a\x89\x37\x32\x68\x69\x27\x95\xa1\x60\x7e\x8d\xbf\x0a\x2a\xa7\x12\x4e\xa2\xbc\x3d\xd3\x5c\xcd\xb4\x69\x92\xb5\x35\x40\xa6\xb8\xd6\xf9\xc0\xbc\xa8\xad\x25\xc2\x3b\x3d\x36\xfd\x46\x9c\xa9\xae\xdb\x48\xf7\x09\xf6\x81\xbc\x1f\x64\xca\xbc\x9d\x22\x3b\xa4\xd2\xc6\xcc\x87\x27\x22\x0f\x04\x7b\x95\x2c\xd2\x80\x39\x4d\x51\x24\xdc\x28\x5d\xb9\x78\x6a\x6f\xbd\x7b\x56\xa3\xec\xa6\xd9\x8f\xb2\x67\xe6\x57\xb3\xcb\x83\x49\x85\x4d\x28\x24\x42\x21\x08\xf0\x73\x28\x7f\x3c\x57\xfe\x1e\xfb\xd2\x06\xa4\x69\x4a\x15\x04\x83\x55\x78\x8d\x7e\xf7\xbe\x8b\xd8\x7d\x39\x01\xf1\x59\x90\xbc\x96\xdf\x37\xc0\xce\x24\x31\x6d\x8c\x5f\xe2\x15\xe0\xc1\x30\x15\xf1\xb9\x8c\xc7\x24\xa7\x31\x89\xa9\x69\x92\x6c\x10\x53\xd3\x40\x01\x37\x4c\x2b\x73\xc4\xfe\x13\xb3\x5f\x51\x1d\xff\x49\xc0\x0f\xa1\x2f\x12\x05\xa5\x2a\xcd\x76\xc2\x1a\x48\x49\x65\xd3\x40\xf2\x55\x2e\x32\xee\x9f\x0c\xd3\x4a\x45\x1a\x26\x29\x35\x3f\xa7\x86\x61\x18\x71\x6a\x98\x16\xca\x97\x4b\xf3\x93\x7c\xa6\x37\xb1\x15\xaf\xb8\x45\x53\xc2\x60\x77\x88\xd9\x54\x0e\x34\x1b\x32\x6c\xf5\xd4\x4e\x60\x6a\xdb\xc8\xa7\x3a\x6b\x35\x5e\xd9\x52\xdf\x68\xe9\x6c\x6c\x6a\x79\xd4\x4f\x92\x7e\x75\x86\xdb\x98\xa3\x69\x36\x9e\xd4\x9e\x58\x93\x1a\x03\x8d\x34\x65\x7f\x59\xf4\x3f\xcd\x22\x98\x30\x81\x8a\xc6\xe9\xbc\x04\xcd\x84\x35\x8c\x55\x20\xea\xc0\xe0\xbc\xcf\x1e\x4d\xa5\x7e\x91\x07\x51\x2c\xd1\xfa\xc6\x73\xd9\x23\x93\xaa\x38\x77\xc0\xfe\xbe\xcd\x83\xf0\x2b\xcb\xc1\xe0\x69\x2b\x66\x8b\x24\x20\x74\x72\x50\xb1\x51\x7d\xc5\x40\x27\xa9\x7e\xd5\x99\x30\xde\x40\x44\xde\xb2\x22\xcc\xe3\x39\xcf\x72\x29\x9d\xaf\xb5\xe8\xea\xc7\x1f\xc2\xc5\x1e\x31\x4d\xab\x7a\x23\xde\xf2\x8e\xdc\x10\x31\x4d\x9b\xea\x87\x15\x88\x49\x45\xbb\x2f\xa4\x14\x8c\x6b\x2a\x09\xb2\x81\xa9\xdd\x82\x71\xbd\x05\xb6\x1b\x3b\x10\xf8\xfc\x73\x08\x68\x51\x99\x83\xa5\xcc\x8e\x4b\x26\x8c\x6f\x6a\x9d\x1a\x99\x12\x9b\x95\x66\x6a\x0b\xb6\x26\x93\x0d\x83\x23\x71\x9d\x89\x30\xbc\x5a\x61\xf2\x9d\x76\x59\x2a\x60\x20\xb4\x9a\x3a\xf5\xc7\x0a\x93\xe7\x09\x53\x62\xa3\xdb\xd4\xf1\x45\xe3\x6b\xa9\x55\x0f\x56\xa4\xe0\xd9\x1c\x56\x36\x4e\x27\xcd\xe2\xeb\xab\x0e\x27\x39\x62\x09\xe3\xcc\x10\xab\xb2\x5a\x09\xfa\xa6\xf9\xdc\xfa\xa0\x2f\xc0\x4e\x83\x6c\xd3\x3b\xb7\x55\x19\xac\x28\xaf\x89\x22\x09\x72\x60\x9b\x40\x47\x91\x5c\xfd\x7e\xed\xb8\x02\xe5\x6a\x93\x31\x47\xed\xb2\x81\xc9\xf3\x92\x99\xda\x54\xa9\xcf\xa4\xf2\x0e\x88\xdb\xe6\x58\xee\x09\xc4\x1d\x3d\x76\xc4\x30\x90\xda\xb5\x2e\x2d\x6b\x9a\xbc\xe5\xba\xda\x4a\x1f\x0e\x3d\xaf\xc4\xa4\xb7\x9f\xde\xb4\x61\xd3\xe6\xeb\x9c\x8e\x5b\x34\x3f\x54\x6d\x4a\x69\x24\x1d\xa4\x3e\x73\xee\x1e\xf2\x60\x3e\x67\x39\x88\x00\x3a\x71\x1a\xf3\x38\x48\x54\xc5\xab\x46\xcf\xbe\xb6\x7a\xa6\x68\x66\xa7\xd9\xa4\xa0\x34\xda\x31\x24\x57\x3e\x7e\xab\x81\x0c\xaa\x50\x9d\x55\x35\xd6\x4f\xe9\x84\x69\x39\x73\x39\x6d\x03\xf5\xeb\x83\xff\xc2\x56\x4f\xe9\x73\xbb\xab\x7e\x4e\x54\x84\x1c\xbe\x54\xef\xcb\xb3\x24\x61\x91\xdf\x86\x34\x5a\x44\xaa\x06\x37\x10\x33\x58\xeb\xaa\xdf\xea\x4a\x03\xf8\x7d\x54\x33\x21\xd3\xc1\xb7\x84\x2a\x82\xbb\xdd\xbf\x22\x46\xea\x97\x1b\x0e\x3c\xa0\xaa\xe4\x99\x2a\xa9\x6a\x50\x3b\x6c\xc2\x10\xd7\xdb\x24\x57\x9d\xa9\x69\xf0\x14\x6b\x18\x0e\x0a\x5b\xe0\xa8\x51\x00\x16\x09\x53\xa1\x98\x40\xcd\x20\x20\x72\x83\xab\x4a\xf8\xa2\x40\xa7\xfa\x55\x1a\x7c\xaf\x27\xd7\x4a\x64\x45\x79\x3f\x8b\x01\x54\x8b\x8b\x29\x67\x05\x93\x1f\xb8\x29\x45\xbe\x49\x82\x2a\xa8\x8a\xfb\x7c\x83\xb0\x53\x29\x83\x2f\x30\x5c\x29\x12\x92\x62\x7f\x33\x63\x73\xcf\x98\xb8\xdb\x6d\x16\x80\xd9\x69\x66\xc0\x95\x37\x1b\x3d\xed\x5a\x89\x6c\x7d\x47\xc1\x58\xdb\x51\xb4\xd3\x59\xcf\xd5\x58\x9f\x1b\xd6\x90\xcd\x78\x71\x3c\x02\x3f\xfb\xfe\x08\xb4\x2c\x51\xbd\x96\x9d\x6a\x7e\x3b\x94\xe6\xdd\xae\x9a\xdf\x0e\x4c\x76\x43\x53\x02\x9a\xa8\xd4\xe3\xf4\xb6\x50\x4b\xd0\xe7\x00\x1d\xb7\x1f\x59\x79\xe0\xd2\xe5\x92\x37\x37\x46\xb5\xd8\x5c\x9c\xa0\x66\x2f\x29\x5f\x01\x2e\x89\x52\x2a\x11\x7b\xb9\x73\x52\x30\x70\x6c\x36\x72\xd7\x33\xf7\x7d\x60\x41\xa0\xba\xb4\xae\xa5\xa9\x10\xf8\xa5\x9a\xd9\xc6\x6e\xe6\xdd\x2e\x73\x40\x97\xe1\xad\x16\x66\x92\xe6\x7c\xb5\xd4\x3c\x90\x46\xda\x51\xc8\x60\xad\xff\x3f\x98\x0a\xbf\x9d\xbd\xb9\xfd\xd7\xab\x49\x9b\xf7\xc5\x2d\x6b\x8b\x0b\xd1\x0c\x3d\x6b\xb9\x08\x05\x47\x57\x02\x11\x40\x7c\x0b\xca\x51\xdf\x0e\x79\xa5\x93\x55\xf1\x61\x59\x5b\x31\xa9\xd6\xdc\xe3\x16\x65\x58\xea\x27\x21\x5e\xa9\x86\x60\xb9\x20\x95\xa6\x88\x58\xc1\xba\x9b\x57\xac\xc5\xa5\x14\x68\x57\x36\x17\x29\x85\xe8\x3c\x07\x89\x14\x25\xa4\x98\x51\xb7\x9f\x9d\x68\xcf\x4d\xfd\xcc\xb2\x30\x1f\x9a\xaf\x4c\x2b\x1d\x66\xa3\x11\xed\xb8\x7d\xe5\x59\xad\x9f\x9e\xb0\xa6\x64\x71\x46\x37\x37\xfa\x2b\x81\xfd\xa4\x23\x0d\xaf\x20\xac\xdf\xa2\x3b\xc0\x76\x42\xad\x38\xf0\x99\xd5\xed\xe6\x3a\x5e\xcd\xfd\x8d\x4e\x16\xb7\x1f\xf8\xa0\x95\x7d\x30\x4d\x4b\x5c\x02\x58\x79\x7c\x21\xb2\xef\xac\xd9\x77\x18\xee\x30\x53\x5d\x10\xd0\xb0\x22\x3b\x45\x6c\x51\xd7\x0c\x42\xa6\x48\x36\x9d\x6d\x6d\x1a\xf7\xf5\x49\x13\xa4\xb4\xc8\xa3\x44\x0b\x40\x77\x5a\xc4\xe0\x95\xce\xa1\x2e\xee\x46\xbf\xeb\xd5\xb8\x66\x95\x11\x8a\x0a\x14\x6d\x17\xa1\x59\x17\x96\x6c\x28\x14\xea\xcb\xbd\x75\x65\x6f\xbb\xd3\xab\xed\xf8\x83\x23\xd0\xbc\xbe\x3f\xac\x21\x16\xd0\x48\xd3\x0d\x0f\xf4\x9d\xd6\xdb\x8f\xf0\xb5\xeb\x9d\xe8\xfb\xa8\x31\xca\xf5\xd1\xf4\xa4\x38\x6d\x5b\x8d\x35\xc5\xca\x05\xb2\x77\x5a\xf9\x0f\xdb\x90\x75\xdd\xc5\x40\x98\x0d\xdd\xd1\x8a\xd3\x74\xa5\x20\xbc\xf4\xc2\x00\xa4\x13\x5f\xbd\x84\x01\xc8\xb9\x81\x6d\xd3\x94\x36\x6c\x0d\xb8\x7d\xc7\x6e\xde\x29\x0d\x45\x53\xb9\x0b\x53\x81\xac\x31\x0d\x8f\x2b\x30\x9a\xd6\x97\x4f\xb3\x7c\xc3\x6a\x83\x86\x36\x5b\x40\x4d\xaa\xae\xae\x0e\xcd\xb7\xc2\xa1\xbc\x69\x17\xa0\x4d\x61\xb1\x47\xae\xa4\xa9\xfa\x12\xbe\xbf\xbc\xea\x02\x37\x90\xfb\xb5\xb1\x73\xeb\x6b\x00\xc4\xbd\xde\x30\xfa\xe2\x2b\xd3\x94\xcf\x12\x93\x5c\x6e\xcd\xd1\x73\x5d\x77\xa7\x58\x4c\x1a\x06\xc5\xbe\x35\x49\x51\x4d\x83\x82\x1f\x05\x45\x37\x7e\xaf\x16\xc8\x3c\x0b\xf8\xf4\x3b\xb9\xe1\xcd\xee\x7d\xc0\xa7\xf0\xe7\xfd\x3b\x73\x9d\x30\xfd\xee\x40\x1a\x3b\xe2\x1d\xdb\x70\x88\x22\x79\x6f\xdf\xad\x00\x5c\x8f\x7c\x03\x56\xfb\xf7\x86\x42\x41\x99\xdd\x1c\x67\x39\x8b\x27\xe9\xc7\x4a\x21\x9f\x0f\xbe\x5f\xbd\x2f\xd5\x88\xdf\x32\x72\xb1\x4d\x41\xa7\x65\xe1\x41\x91\x89\xef\x6f\xde\xcc\xe7\xdd\x2e\xfc\x38\xec\x91\x85\x9f\xd2\x22\x18\xb3\x77\x59\x18\x24\xda\x98\x7b\xfd\x50\x52\x49\xb7\x7e\x37\x3f\xda\xa2\x83\x2b\x5f\x02\x56\x18\xaf\x7c\xb6\x5a\xf7\xc9\x01\x42\xd4\xd5\x4b\xea\xa7\xeb\xcb\x0e\xa5\x97\x62\x32\x2b\x31\x40\x13\x84\xad\xc5\xde\x54\x11\x94\xf7\x2b\x48\x8f\xde\x32\xfa\x96\x2d\x97\xdb\xa5\x98\x91\x19\xc5\x0b\x13\xe3\x46\x59\xf3\xa4\x58\x4c\x4e\x4d\x8b\x6b\x3f\x2c\x08\xd7\x1a\xfc\xd8\x32\x4f\x76\x20\x9d\x70\xfa\x96\x39\xe3\x38\x2f\x38\xdc\xbf\xfd\xd6\x07\xd6\xe8\x2b\x7c\xa2\x66\x9a\x94\xe9\xef\xf3\xb5\xec\xe2\x8c\xa5\x91\xcc\xde\x4c\x13\x54\x71\x43\x25\xe5\xd7\x1a\xfe\x37\xfc\x95\x34\xaa\x12\x20\xb3\xdb\x4d\xe1\xe8\x26\x81\x8a\xed\x76\x77\xe1\x49\x31\xcd\x22\x76\x5b\x3f\x2b\x02\xa6\x8d\x64\xf4\xef\xfa\xd4\xb6\x60\x00\x6d\xb0\x82\xfe\x68\xc1\xb9\xe7\x55\xe5\x97\x67\xb8\xc6\x71\x19\x09\x74\xb4\xfd\xd2\x3b\x34\xff\x60\xf7\x5f\x63\x6e\x5a\x6c\x44\xcd\x07\x15\xe6\x22\xe1\x7d\xf6\x4d\xc6\xce\x44\x80\x93\x54\x0a\x1c\x33\xfa\x1c\xa4\xf1\x0c\x84\x39\x58\x1a\xf9\x7f\x30\x64\xbe\xd1\x11\x26\xa9\xc3\xe7\x69\x64\x62\x52\xe5\x95\x2c\xea\x38\x4b\x5f\x2e\x71\xa9\xb3\x34\xcb\x81\xc8\xcd\xcb\x65\x6e\x40\x34\xac\xe9\xd8\x42\x77\xea\xb6\x8a\x31\x49\xe3\x03\xba\xb5\x22\x9f\x18\x7d\x5e\x91\xdf\x58\x4b\x70\xf7\x77\xcd\x29\xf8\xc4\x86\xac\xf2\xda\x08\x1f\x80\xc2\xbf\x6a\x46\x4b\x0d\x7a\x4e\x52\x0a\xd1\xb5\x7e\x41\x8a\xc5\x5a\x6f\xe3\x0b\x41\xf2\x6f\xac\x55\x31\x4d\x87\x7c\x54\xab\x99\x7f\xe9\x76\xd1\x6f\x8c\x7e\xf7\x54\x38\x20\x4e\xd5\x9c\x6a\x50\xf1\x89\xd3\x4a\x89\x00\x29\x86\xc8\x2b\xe6\x34\x17\xab\xfe\x20\x5b\x32\x54\x2b\xf4\xfd\x6c\xb0\x20\xf5\x27\x6e\x4d\xee\x7a\x47\xea\xf2\xad\x05\x6a\x7c\x49\x1a\xf8\x9f\x8c\xfe\xce\x90\xd9\xec\xac\x89\xc9\x5f\xd6\x62\xe3\xc6\x06\xf9\xaf\xb5\xb4\x42\x6d\x84\x3f\xcb\xf8\x56\x73\x26\x26\x7f\x63\xd4\x0c\xee\xb3\x9c\x1b\x61\x90\xce\x41\xb4\x54\xfe\x2a\x69\x29\x43\xbb\x36\x09\x41\xf9\xd9\x60\xb3\x39\x8f\x59\x64\xb0\x34\xcc\x9f\xe6\x1c\x42\x91\xf8\x0b\x6a\x72\x49\x16\x44\x2c\x8a\x02\x1e\xa8\xe0\x8c\xf1\xa0\xfa\x94\x52\x62\xf3\xa0\x2c\x18\x08\x95\xc2\x9f\x38\x9d\x18\xf3\x3c\x9b\xe4\xac\x28\x8c\x3c\xe0\x4c\x35\x54\x30\xf6\x15\xc4\x49\xd9\x57\x29\xf1\x15\x24\x89\xf8\x86\x87\x8a\xc8\xe0\xf1\x8c\x49\xa3\x61\xc6\x22\x4b\xca\x99\x2e\xf6\x10\x80\xc8\x51\x53\x70\x87\xfc\x15\x6c\x80\x6d\x7b\xd0\xf9\x83\x05\x5f\xdf\x07\xf3\x81\xfa\xf5\xdf\x07\xf3\x06\xec\xfa\xcf\x06\xa6\xf1\x57\xe6\x4c\x18\x6f\xf0\x9c\xd7\x55\xc7\xd9\x83\xf1\x3e\x98\x93\xbf\xc2\x93\xb1\x74\x31\x4c\x1a\xc0\xe8\xef\x4d\xac\x85\xa4\x14\x30\x4c\xe6\x04\x09\x88\x17\x73\xa6\x1c\x9c\x2b\x0e\x77\x1f\x73\x5a\x85\xe1\x6a\x60\x94\xf7\xa3\xec\xd9\xed\x50\xe4\xb9\xbd\x03\xd1\x24\xc3\x8e\x94\xcd\xba\x0d\x26\x52\x2a\x40\x17\xc1\x84\x55\xe1\x8a\x59\xae\x9f\x22\x76\x25\x5f\x27\x98\x0c\xd2\xb5\x17\x31\x18\x9f\x38\xe6\xde\xae\x64\x4b\x07\x93\x1a\xd1\x9a\xb1\x59\x16\x7f\x63\x11\xa0\x55\x4d\x6b\x49\xb5\x70\x06\xd0\x5a\xf5\x88\x60\x5e\xd6\x0a\x56\x78\x5e\xed\x5d\x59\xe0\x8b\x53\x10\x78\x67\xd1\xf6\xe7\x12\xae\xfb\x05\x93\xd8\x59\x37\xde\x10\x20\xef\xe8\xa8\x49\xa8\xa6\x3a\x7f\x07\xb1\x2d\xa4\x68\xa3\x8f\x8a\xb1\x59\xbf\x02\x52\xc4\x29\xb4\xb2\x8e\x86\x43\x13\xd5\x7b\x83\xe8\x83\x7c\x86\x64\x2b\x4d\x4c\xa6\x94\x91\x9c\xf2\x7e\xbf\xe6\x53\xab\x05\xac\x6b\xcf\xb0\xb4\x13\x25\xad\xb8\x64\xed\x8e\xa8\x3c\x71\x53\xb8\x06\x9c\x6c\xaa\x45\xc5\xcf\x29\xcd\xfb\x61\x96\xf2\x38\x2d\xd9\xaa\x72\xa5\xaa\x6c\x26\x88\xa2\x32\xa4\x9d\x5b\xaa\x84\x7e\x2c\x9d\xe3\xc7\x4d\x8a\x90\x73\x94\x61\x02\xed\xc6\x4d\x5e\x96\x8c\xe7\xfd\x98\xc6\x4e\x11\xdf\x27\x71\x3a\x59\x6d\x9b\x6c\x00\xe5\x8a\x21\x4c\x69\xae\xfb\x98\xd2\x8c\xe4\x34\xae\xd1\x19\x69\x97\xab\xe3\x91\xa2\xea\x4e\x21\xbb\x53\x40\x77\x9e\x13\xf0\x01\xa7\x8a\x55\x63\x2a\xa0\x4f\x32\x31\xa7\x19\x49\xab\xc4\x82\x16\x55\xc7\xc0\x7b\xb6\x1c\x6d\xa1\x07\xbf\xbd\xfa\x58\xd4\xf2\x52\xf5\xb1\x68\xff\xc5\xea\xd7\x47\x7f\x8c\xf1\x4a\x0e\xbf\x5a\x3d\xd0\x7b\x5b\xd7\xf9\x74\xe5\x2c\xed\x76\x40\x36\x2a\x98\x7c\x6f\x43\xa5\xb5\x86\x98\xd6\xf9\x94\x72\x2d\x3e\x07\xa6\x7d\xeb\x6d\xba\x56\x9b\x66\x7d\x39\xd6\x7d\x7d\xa4\x97\xcb\x03\x1d\xac\x16\x14\x9c\x9f\xab\x8d\xa1\x7e\xd5\x6a\x81\xfb\x58\x15\x25\x57\x0c\xfc\xa4\x8b\xf3\x25\xb7\x29\xc0\xa4\x0e\xd7\x13\xd2\xd7\x7a\xa0\xb2\xf8\x72\xa9\x43\x74\xfd\xf9\xbc\x86\x5e\xab\xaa\x74\xd5\xa8\x0a\x40\xdb\x7a\xaa\xb7\x1f\xfc\x9c\xb7\x98\x07\x74\x83\xac\xde\x75\x1b\x73\xa8\x9d\x27\xfa\x1b\xb6\xa2\xd6\xed\xa3\x0d\x10\x03\x45\x3b\xa5\xc6\x0b\xcf\x57\x0c\xfb\x2a\x16\xc9\xaf\x8d\x42\x43\x56\xb9\x26\xe7\xd8\x1f\x32\xc2\x47\x75\x57\x33\xae\xd9\x78\x1b\xad\xb3\x8a\xcf\x25\x65\x8a\xba\x5d\x2e\x9f\x91\x52\xc2\x24\xd1\x19\xf3\x35\x65\xc4\x40\x43\xb0\x06\xd0\xba\x8b\x94\xe8\xd0\xbb\xb8\xe0\x2c\x65\x79\x21\xae\x91\x3a\xfa\x52\x5c\xf5\x69\x28\x15\x5e\x5f\xb4\x08\x97\x53\xb7\x9f\x37\xc4\x05\x3b\xcc\x89\x0b\x81\x8c\x05\x13\x85\x37\x66\xf3\x39\x8b\x10\xee\xe7\x96\x85\x41\x87\x7a\x98\x8f\x48\x0a\xc6\x13\x81\x23\xce\xa5\xa3\x4c\xd0\xca\xdb\xd6\x2b\x6d\xe7\x6f\xb3\x63\x3a\x25\x2e\xae\x58\x5e\x40\x7e\x8e\xf0\x72\xd9\x7e\x54\xcc\x59\xc2\x04\x16\xce\x9a\x3c\x8b\x84\x37\x84\x35\xa4\x79\x34\x14\x73\x9a\x73\x14\x83\x9f\x47\xc2\x68\xcc\x49\xac\x8d\x0c\x42\x56\x58\x92\x80\x63\x12\x6f\x70\x63\xf6\x25\x37\xa6\x54\xf1\x8c\x46\x2d\xed\xf5\xe6\x9b\x06\xc8\xc8\x3d\x37\xde\x70\x39\x28\x63\x8b\x5e\x17\x79\xa8\x10\xd0\xe5\x52\x39\xfb\x72\xc2\x2c\xcf\x59\x31\xcf\xd2\x28\x4e\x27\x9f\x0a\x8d\xa1\x82\xf9\x31\xf6\x52\x2a\x26\xf2\xc2\xd5\xe4\xce\x80\x39\xf3\x40\x00\x01\x01\x10\xfc\x06\x6f\x33\xac\x6e\xb6\x2f\xeb\x4f\x76\x88\x51\x33\x4b\x4d\x8b\xe1\x38\x35\x34\x9a\x5c\x5d\x58\xcb\x25\x42\xfc\xfb\xd8\x33\x6e\xcb\xba\x31\xa2\xbc\x9e\xf5\xc1\x3d\xe8\x16\xe4\x09\xb4\x49\x09\x97\x26\x82\x78\xcb\x94\x65\xc4\xe5\xd3\x34\xcf\xe6\xef\xd8\x82\x25\xb7\x60\x87\x55\xae\x7e\x0a\x36\x46\x00\x23\xd6\x51\x72\x4a\xc5\x3e\xd1\x31\xb0\x5f\x78\x96\x6b\x7b\x0e\xd2\x5e\x64\x59\xef\xdb\x92\x6f\x5a\x8c\x1c\xf3\x16\xe7\xb7\xca\xad\x2f\x64\x51\xa6\x6d\x43\xb3\xd5\x3f\x46\x32\xa9\x58\x7d\xf3\x54\x70\x36\xbb\x48\x82\x49\x01\xb6\x33\x9b\x3d\xe6\x24\x6b\x75\xb7\x36\xa8\xd9\xac\xcb\x67\x64\xbd\x26\x3f\x27\x8d\x7a\x7c\x4e\xea\x5a\xfc\x94\x54\xe3\xf5\x87\xa3\xc6\xe6\x9b\xf3\x16\xaf\xab\x2a\x41\x52\x89\x0e\x8a\x9d\x90\x8a\x89\xae\xe7\x0b\x66\x25\xd5\x9e\xd6\x95\x2d\x0e\xb1\xd9\xc5\x16\xcb\xe5\xcd\x40\xf3\xe6\x7d\x93\xa5\x3c\x88\x53\x96\x5f\xa6\xe3\xac\xbe\xb8\xfb\x79\x85\x8b\xe6\xd5\xfd\xde\xcf\xe9\x6e\x47\x55\x23\xf1\x9f\x17\x6b\x52\xf6\x2a\xe4\x45\xb2\xdf\x01\xcc\x4a\xde\x83\xdd\xee\x81\xe2\x2b\x6f\x76\x9b\xa4\xf4\x4b\x8a\x72\xac\xb0\xd6\x14\xb7\x38\xf0\xeb\xbb\x02\x78\xf1\xcf\x80\xd1\xe9\x94\x61\x3a\x52\x36\xbc\xc4\xc1\x6d\x2e\x1d\xee\x83\x11\x88\xc6\x2a\x29\x34\xac\x95\x0b\x8c\x7d\xad\xaf\x5d\x5f\x3d\x2d\xa2\x60\x49\x0f\xf6\x6a\x23\x07\xd2\x2a\x2f\x29\xa8\xdb\x2f\x4e\xce\x75\xa7\x0a\x6d\x65\x33\xa4\xe7\xc3\x62\xd4\x0f\xbb\x5d\x14\xd2\xb0\x6d\x3d\x01\x24\x4c\x63\x92\x91\x00\x5e\x33\x12\x01\xca\x12\x12\x62\xbc\x4a\x38\x4a\x9a\xa0\x6f\xca\x1b\x4f\x6f\x1d\xa0\x9f\x41\x41\x7d\x9d\xb1\x18\xe6\x59\x92\x98\xfe\x7f\x71\xc4\x89\xfe\x22\x1d\x17\x37\x4d\x93\x82\xc6\xa2\x12\x6c\xb9\x4f\xca\x5c\x67\x97\xf1\x20\x40\x2f\x23\x20\x11\xbe\x41\xca\x02\xa9\x08\x29\x76\xac\xa2\x54\x21\x88\x6b\x36\x12\x4a\xd7\x70\xb2\x95\x10\x1c\x32\xf8\x00\xb8\xba\x5d\xa8\x9c\xad\xf7\x2a\x4e\x17\x41\x12\x47\x5a\xfa\x46\xbe\x06\xca\x0f\xf9\x18\xe8\xcb\xcc\x9a\xbb\x69\x7b\x94\xd2\xbf\xb1\x86\x25\x8b\x6e\xf7\x2f\xca\xd8\x50\xaa\xc8\x2f\x69\x1f\x0f\xf6\xff\x8c\x93\x05\x27\xf7\x9c\x4c\xb8\x80\xf1\x4f\x02\x54\x11\x6d\x8f\xf6\x41\xfd\x9e\xab\xdf\xaf\x35\x29\xf7\xb1\x0e\x9e\x41\x99\x1b\x4e\xcd\x59\x56\x16\x2c\xca\x1e\x52\x03\x42\xe5\xdc\xe0\x59\x19\x4e\xe5\x98\x65\x18\xc8\x53\x11\x90\xf4\x6e\x50\x3e\x86\x49\x1c\x7e\x35\xa2\xfb\x44\x06\x94\xb6\x93\x2a\xa3\xbe\xa0\x4e\x15\x2e\xe7\x46\x94\x07\x13\x51\x91\xf8\x95\xf5\x44\x79\x36\x37\xc0\xb3\x6d\x45\xbf\x37\x3f\x65\xa6\xaf\xec\x09\x2a\xfa\xca\x9e\x40\x93\x58\x04\xca\xb9\x01\xb2\x46\xa0\x62\x74\x09\x21\x58\x15\x43\x75\x20\xcc\xe6\x4f\x46\x58\x0a\xca\xbc\xe0\xcc\x90\x7d\x54\xe4\xb4\x92\x7b\x9d\xb1\xb4\x34\x60\x2d\x0c\xb5\x3c\x4d\xfa\xfa\x8b\xb8\x1e\x40\x3f\x55\xec\x12\xd5\x75\xce\x64\x28\x61\xc1\x82\xc9\xc9\xca\x16\x2c\x57\x21\xd1\x9a\x1c\x2b\x44\xea\x70\xc9\x8d\x49\xc6\xab\xf9\x01\x35\x4c\x23\xc9\x8a\xb5\xa8\x66\xeb\xf5\xa5\x73\xcb\x37\x4d\x16\x3c\x83\x68\x2a\x8b\x3e\xa6\x3e\x23\x2d\x08\xcd\x37\x21\xf4\x6e\x6f\x99\xb6\x80\x74\x46\x2a\x90\xe6\xe7\x8d\x23\x79\xa5\xf0\xd2\xb5\x23\xb8\x79\xba\xd4\x2e\x6b\x6e\xf6\x6a\x76\x54\xc6\x6a\x8e\x4c\xff\x61\x33\x77\x35\x6f\x2a\xb7\x9e\x3d\xd3\x3f\xdf\xcc\xdc\x98\x51\x95\xbd\x9e\x57\xd3\xff\x2a\x08\xed\x84\x71\x86\xc4\x35\x08\xf1\x97\x51\xeb\x1c\x6e\x4c\xbd\xaa\x65\xcb\x02\xf8\x1f\xb7\xd6\xd6\x7c\xf8\xe3\x6d\x6d\xff\x0d\xc1\xde\x16\xd8\xed\x50\x1a\x0f\x10\xa3\xb7\x1c\xd5\x65\x48\xe3\x99\x46\xd3\xc3\x9c\xde\xa6\x02\x9f\xed\x76\x17\x02\x0d\xd7\x78\xfb\xfa\x5a\x2e\x69\x4e\x9a\xa8\xc1\x87\xc6\x45\xfa\x25\x45\xfa\x2e\xc5\x0d\x8d\x8e\x8a\x53\xfc\x77\x86\x5a\x09\xc0\xb7\x04\x96\x48\x75\x85\xb5\x48\x74\x4e\x19\x47\x69\x4d\xa4\x31\xa7\xda\x74\x54\x99\xf3\x89\x9d\x52\x1c\xd0\xfb\x84\xdd\xe5\x65\xfa\x47\xcc\xa7\x57\x79\x9c\xe5\x31\x7f\x42\xe0\x0a\x18\x82\x2d\x6b\x9f\xf7\xa2\xca\x15\xae\x8d\x31\xef\x4a\x5e\x4b\x93\x52\x54\x5c\x93\x16\x73\xbc\xd9\xb8\xe4\xa1\xc3\x55\xfd\xd2\x2d\xad\x9c\x38\x37\x4b\xb5\x49\xb0\xbb\x75\xc4\xbb\xce\xb9\x8e\x81\xfe\xa7\xb8\x70\x9b\x67\x8c\x6c\xae\x0b\x69\xb4\x4f\xd6\xae\xe7\x2d\x4b\x01\x6b\xdd\x24\xf0\xe4\xf3\xe1\x42\x4c\x0e\x69\xcf\x73\xc7\xd3\xba\x32\x6e\xe3\xad\xb0\xba\x3b\xef\xe4\x05\x94\x56\xbb\xb6\x69\x93\x99\x23\xc9\x44\x80\x0b\xa2\xef\x9e\x3c\x55\x76\x53\xb4\xad\x95\x27\x3e\x74\x47\xfd\xed\xd3\xf0\x5c\xb3\xbf\x6e\xd3\xe6\x02\x88\x5d\x3a\x03\x9e\x61\x8d\x8c\xfd\xcf\xa7\x49\xcf\xd1\xa0\x35\x7e\xff\x89\x3b\xa0\x4e\x8d\xaa\x77\xf9\x47\xde\xed\xde\x71\xf4\x08\x8a\x3c\x8f\xda\xba\xa5\x4a\x7c\x90\x89\x0f\x90\xf8\xb0\x96\x78\x2e\x13\xcf\x21\xf1\x5c\x27\x7e\xe5\x15\x21\xfb\x9e\x63\xf2\xb1\xf5\xd9\x30\x1c\xc3\xb5\xa1\xe8\xba\x7b\xfa\xa9\xb5\xbd\xcb\xc8\x44\xd0\x25\x62\xd2\x5d\xd2\x38\x21\x45\x38\x65\x51\x99\xd4\xc6\x15\x1b\x69\x1f\xb2\x7c\x16\x24\xfa\xf0\x90\x37\x1c\x37\x19\x7f\xdf\x60\xb3\xd6\x7c\x43\x54\xbf\x69\x5e\x02\xe6\x01\x1c\x99\xc6\xfa\xe2\xe7\x4b\x8e\x60\x6d\x09\xab\xd1\x3a\x69\x52\xae\xde\x04\xa9\x46\xe8\x72\xb1\x0f\xd2\x51\x3f\x6f\x0d\x0d\xc4\x1d\xd7\x86\x26\x20\xa1\x40\x5c\xeb\x95\xb8\x14\x2b\x41\x58\x6b\x01\x2e\xc5\x02\x34\xe2\xce\x65\xdc\x39\xc4\x35\xa6\xbb\x3d\xdb\x5c\x60\xc9\x02\x21\x3e\x6b\xf5\x10\xe5\xf4\x4c\xf4\x0e\xff\xb8\x73\x92\xb1\xe3\xd6\x15\x54\x86\x79\x50\x2a\x2a\x71\x9b\x95\xf4\xf1\x07\x38\x6e\x5a\xd9\xaa\x4e\xe9\x76\xcf\xea\x4d\x07\x26\x80\xc0\x25\xc9\xdb\x1a\x6f\xba\xa8\x83\xbf\x72\x3a\x94\x2f\x0d\x26\xd1\xbf\xff\x64\xa4\x7e\xa8\x38\x4f\x23\x93\xfc\xa5\x19\x53\xbf\x7b\x91\xff\x6a\xc6\xcb\xb7\x2d\x62\xaa\x97\x0a\x19\xba\xaa\x42\x8d\xd7\x8b\x3a\xe9\xb6\x8a\x68\x3f\x68\x34\x22\xce\x74\x84\x7a\xe3\x68\x87\xf4\x7b\xc7\x46\x38\x6a\xff\xe6\x39\x18\x49\xd1\xbf\x9b\x77\x2a\xc4\x5d\xc9\xb8\xb3\x2a\x0e\xac\x4c\xb4\x7e\xe4\x3b\x4a\xf5\xf1\xb6\xf9\xa1\x9f\x55\xaa\x88\xf7\xed\x88\x42\xcd\x90\x08\xdf\x54\xe1\x8d\x5b\x5c\x46\x6e\xf4\x45\x3d\xcb\xb4\x43\xea\x89\xa6\x1d\x54\xaf\x33\xed\x90\x7c\xa9\x69\x87\xe4\xab\x4d\x2b\x54\xbf\xe0\xa8\x0f\xe5\xdb\x85\xfc\x99\x91\xc6\x33\x15\x6c\x0b\x53\x3f\xeb\xd4\xa1\x06\xb7\xe1\x0f\x05\x71\xea\xf3\xbb\x26\xb0\x45\x7b\xb5\x0d\xb3\x74\x44\x32\xf1\x63\x79\x23\x12\x4b\x96\x09\xca\x86\xee\x9a\x45\x0b\x2b\x53\x8a\xa3\x1e\xc6\xfd\x98\x3e\xbf\x60\x5b\xce\x7f\xbe\x2f\xef\xc1\x5f\x7e\x4c\xd4\xa4\x46\x7e\x6c\x99\x7a\x2e\x57\xa4\x69\x9b\xcd\x1f\xe6\x23\x89\x75\x6a\x00\xe6\xf3\x15\xb9\x00\xa1\x67\x41\x0f\x62\xf2\x56\x87\x63\x4c\xde\xf1\x61\x36\xa2\xf1\x6a\xf5\x87\x22\xbf\x24\x76\xad\x91\x76\xf5\x23\x91\x75\xf5\x17\x90\x7a\xf9\xb7\x81\xb7\xab\xf0\x7b\x19\x16\xb8\xbe\x46\xf8\xc5\xff\x2b\xf2\x24\x28\x1f\xcf\xda\x74\x4a\x94\x95\xf7\x09\x53\x91\x0d\x7a\xe4\x7c\x8d\x2e\x09\x26\x37\x35\x85\x02\x7f\x24\x35\x20\xff\x4a\xda\x43\xff\x05\x4a\xaf\xfa\x6d\x90\x2b\x6f\xd7\xc9\x96\xab\x06\xfd\xf2\x95\x3d\x7d\x9a\x1b\x6b\xb4\xd7\xdb\x26\x15\x06\xbf\x9f\xe6\x8a\x86\xd1\x7f\xe1\xa5\xb1\xfd\xde\xb8\x9d\xfa\x3a\x7b\x91\x16\x7b\xdb\xa6\xcb\x54\xe8\xd3\xbc\xf9\x56\x99\x57\xc6\xa1\x15\x91\xa4\x48\xa5\xfa\x09\xf3\xff\xe6\xee\x4f\x98\xdb\xc6\xb5\xfc\x61\xf8\xab\x58\xaa\x14\x0b\x18\xc3\xba\x92\xed\x2c\xa6\x82\xa8\x12\x27\xe9\xa4\x3b\xe9\xa4\xb3\x74\x16\x8f\x2b\x45\x4b\xb0\x8d\x0e\x05\xb8\x41\xca\x8e\x63\xe9\xbb\xbf\x85\x83\x9d\xa4\x1c\xa7\xef\xdc\xf9\xcf\xfb\x54\xb9\x2c\x12\x04\x41\xe0\x60\x3b\x38\xcb\xef\x80\xe6\xf2\x08\xf0\x21\xcc\x4f\xeb\xd4\xb8\xdf\x75\x82\x7c\x92\x1e\x25\xe1\xd2\xd0\x3a\x51\x7a\x9a\x1b\xbb\x7e\xc5\x27\xb3\x21\x26\x7a\xf4\xe8\x2e\xda\xf0\xff\xc2\xd9\xec\x89\xbf\x62\xdf\xb8\xe9\xc9\x27\xee\xc2\x9c\xdb\xf4\xd5\x0b\x7f\x05\x67\x35\x7d\xf1\xca\x9f\xe4\xe6\xd2\x9d\xee\x5e\xfa\x2b\x7d\x8c\x83\x8b\x57\xee\x22\x9c\xfc\x5e\x45\xc7\x3d\x78\xd7\x5e\xbf\x8c\xae\xa3\xd3\xe1\xab\xee\x83\x22\x94\x62\x64\x1c\xee\xa7\x96\x27\x27\x25\x0b\x3f\x8b\xe9\x29\x94\x0f\x57\x50\xfa\xc5\x29\x63\xa5\xf9\x9f\x10\x69\x04\x44\x7a\x54\x93\xed\xc0\x02\xdc\xaa\x69\xdf\x2b\xa2\x4b\x36\x8d\x34\xe0\xd1\x29\xba\x79\xfa\x5e\x7f\x3a\xb7\x0b\x5d\xfc\xd9\xf7\x35\x1d\x8e\xdf\xd7\xf7\x6f\xf9\x5d\xfc\x7d\xbd\xb9\x89\xed\x72\x70\xab\x3e\x78\x5f\x1f\x92\xa1\x31\x03\x78\x56\xd3\x88\x09\x7a\x5f\x31\xf5\x48\x6f\xc2\x5c\x9c\x78\x56\xe8\xcf\x24\x4b\xe3\x94\x41\xfe\x86\x40\x56\x7e\xc5\xb4\xd2\x92\x2b\x27\x8f\x89\x4d\xf1\xff\xa8\x53\xe0\xd4\xa7\xe0\xe7\xa1\xf9\x70\x7b\xdc\xf5\x9a\x6f\x35\xd9\xce\x95\xf5\x55\x1b\xe6\x8a\xfe\x52\x47\x08\xe3\x35\x19\x11\x16\x1f\x2f\x37\x46\xb9\xa2\x9f\xd6\x66\xf1\xa6\x6b\xf4\x63\x3b\xcf\x4a\x4c\xd8\xa0\x98\xcd\x80\x05\x76\x9a\x06\x54\x13\x45\x7a\x43\x9c\xaf\x7b\x14\xb7\xea\x97\x48\x36\xfc\x78\xb9\xfc\x8e\x1c\xd4\xff\xc7\x9a\x70\xfa\x78\xfc\xd8\xe1\x0b\x3e\x47\x92\xb8\x9c\x1e\x5c\x10\x3d\xa6\x1c\x2f\x97\x00\x2f\xe8\x8b\xfc\x14\x15\xf9\x67\x8d\x9e\xd5\x24\xa9\xb9\xc7\x12\x08\x6f\x7c\x4c\x05\xd4\x7f\xd7\x38\x65\x47\xb3\x6c\x6b\x74\xff\x6d\x1d\x09\xb6\x30\x1c\x89\x93\xf2\xc8\xa5\x17\x7d\x1b\x69\xad\x69\xc7\xaf\xa1\xec\x44\xe1\x0c\xb2\x0a\x15\xbc\x19\xba\xbf\x20\xd7\x16\xaf\xdf\xe9\xb5\xf1\x70\x83\x8f\x5d\x2a\xfc\xb0\xec\xf6\xb7\x9a\xbe\x31\x0c\xaf\x7f\x81\x38\x78\x87\x48\x02\x62\x73\x5f\x40\xee\x8b\xce\xdc\x91\x04\xc4\xe6\x7e\x02\xb9\x9f\x74\xe6\x4e\x44\x20\x4e\xcb\xee\xe5\x13\xee\x24\xf9\xd5\xcc\x31\x4e\xde\xd4\xe8\xab\x19\xde\xba\x7b\x63\x32\x43\x98\x14\x57\x6a\x87\x60\xc4\x81\x35\xc4\xc5\x6b\x26\xdd\x97\xfb\xea\xda\x72\x23\x5c\x87\x40\x7a\x7c\x65\x3b\x8b\x30\x0a\xba\x0c\x17\x76\xc4\xc0\x32\x7f\x40\x67\x70\x84\x71\x83\x12\x94\x2c\x09\x96\xec\xaf\x75\x17\x40\x07\x02\x99\xba\xa0\x55\x8d\x14\xc6\xd8\x69\x43\x3e\x83\x78\x3d\x1e\x2a\xe6\x74\x10\x06\x95\x6e\x5c\x5d\x9c\x8c\x9d\xf8\x83\x37\x4a\x65\x35\x92\x91\x6e\xda\xba\x8f\x26\x12\x0b\xf3\x8a\x5c\x2f\xb4\xd8\x31\x50\x26\xc5\xc9\x44\x5e\x2b\xa3\x48\x0a\x97\xd6\x8a\xd8\x9f\xb5\x02\xbd\xae\x27\x56\xac\x65\xd6\x2d\xfc\x5c\x47\x56\x7b\xfe\xb8\x61\x22\x36\xf4\x86\xe4\x08\x60\xaa\x9e\xcf\x8b\x13\xbd\x95\x55\xac\x99\xf8\x56\xb3\x88\x8d\xb4\x0f\x7c\x56\x9f\x9a\xb4\x6f\x4f\x4b\xf6\x2d\xba\xfc\x45\xc9\xc5\x99\xbd\x7f\xa5\x66\xba\x66\x3e\x69\xaa\xb7\xf0\xf0\x65\x73\x5b\xe9\xcb\x63\x5b\xc8\xb1\x29\xe1\xc2\x5d\xbf\x86\xad\xe5\x9c\xb9\xfb\xb7\xa7\x8a\x8b\xaf\xee\xee\x77\x76\x52\xc4\x4f\x5f\xe9\x0a\x82\xf7\xa3\xe2\xb3\x87\x8a\x15\xee\xfa\x8d\x29\xd1\x5e\x3e\x11\xb3\xe8\xee\xed\x59\x21\xe2\x5b\x30\x39\xb4\xf7\xfb\x50\xc3\xf4\x2e\x7a\xdb\x24\xc4\x05\xd8\x14\x57\xc6\xb1\x14\xf5\x07\x00\x81\xd2\x77\x25\x17\x6c\xbf\x2c\xe6\x67\xee\xe6\x99\x7f\x64\x91\xb4\xe0\xd2\x35\x42\xaa\xb3\xd3\xc2\x90\xa7\x2e\x8e\xde\xf2\xef\xd0\xce\x0b\x3e\x93\x17\x90\xf8\x1d\x20\x8b\xe0\x4a\xca\x39\x7c\x8e\x97\xe5\xab\x50\x12\xe0\xb7\x45\xf7\x55\x2d\xcf\x92\x5b\x25\xbf\xb2\xc7\x0e\x17\x2c\x4d\x32\xc8\x60\x21\xed\xa5\x07\xff\x0a\x69\xad\xb2\xdc\xb0\x58\x11\x26\xa8\xb7\x2e\x25\xfd\xb9\x3e\x46\xbd\x94\xdf\xfb\xa4\xff\x2a\x3e\xd3\xd4\xcd\x78\x07\x1e\x8b\xc7\xbb\x31\x07\x45\xec\x72\xd9\xb7\x16\xd5\xfd\x5c\x2c\x97\xce\x91\xa8\x17\x65\x18\x1a\x4f\xe2\xcf\x2d\xbf\x15\x86\xb3\xec\x73\x7d\xc0\x0e\x27\xa8\xdf\xdf\xac\xf1\xa0\x56\x7c\x8e\x70\x5e\x6f\xf6\xcf\xbe\xf5\x23\xa3\x2a\xd1\x38\x64\x01\x64\x34\x80\x1e\x5f\x96\xfa\x49\x97\x63\x98\xf0\x0e\x5f\x06\x22\xc8\x6d\x3b\xfd\xad\xad\x3e\x26\x92\xd6\x02\x09\x52\xeb\x33\x99\xc2\xe3\xfe\x71\x29\x0b\x30\x0d\x37\x73\xbb\x3f\xad\xaa\xa7\x90\x84\x1d\x44\x4b\x28\x98\x48\x9c\xeb\xc3\x1c\x95\xab\x55\x1c\x95\xed\x73\xbd\x06\xa8\x2b\x72\x37\x0a\xe9\xe0\x10\x54\x6f\xb2\xc1\xf4\xb4\x50\x0f\x6b\x34\xc4\x8d\x63\x20\x1b\x54\x8b\x23\xe3\x0f\x8e\x46\x98\x7c\xae\x0f\xea\x43\xfa\xd9\x85\x4c\xb3\x38\x50\x4a\x50\x89\xae\xf4\x59\x8b\xd7\x6c\x0e\x9d\x7c\x55\xd8\x29\x76\x54\x54\x66\x85\x50\x76\x5a\xeb\x1f\x36\x3f\x62\x30\x53\x4e\x21\x95\xcf\x4f\xe0\x47\x33\x95\xfa\xe2\x2b\xbb\x3c\x61\xc2\xce\x04\x98\xd1\xfa\xbc\xaf\x7f\xcf\x0a\x55\xc0\x70\xf6\xe1\x43\x49\xad\x8a\x29\xe4\xb9\x80\x4f\xac\x22\x85\x48\x64\x91\x6e\xfe\x2b\x71\xc0\x0e\xbd\x30\x3d\x78\xb4\x98\x2d\xea\x3a\xdf\x9c\xa6\xf1\xd2\xce\x5d\xc2\x4c\xb4\xbb\x1b\x38\xf6\xc4\xce\x3f\xde\x87\x2b\x2d\xf0\xce\xd0\x14\xd5\x0e\x34\xb8\xae\xd4\xe5\xb2\x87\xfa\x5f\xbe\x80\xab\x00\x17\xeb\xf3\x35\xab\x7e\x67\x64\xad\xd7\x6c\x85\x60\xf8\x66\x59\xc7\x87\xe1\x49\xf3\xed\x6d\x68\x75\xb4\xe7\x16\x81\xc8\x5b\x06\x16\x22\x0c\xf2\xbe\xdb\x1c\x5b\x80\x20\xf5\x80\x57\x0d\x28\x90\x7e\x21\x84\xb4\xf0\x8d\xdf\xe6\x5e\x7f\x1a\xa3\x59\xda\x34\x0b\x37\x39\x6d\xdd\x6f\x55\x6a\xda\x4a\x5b\x28\xde\x4a\x3b\x96\x6a\x5e\xd4\xad\x64\x51\xcc\x5d\x99\x73\x5e\x55\x5c\x9c\x6c\x01\xb4\xa4\x47\xe2\x1a\x35\x41\xb3\x86\x46\xc5\x5a\x0a\xfa\x90\x85\x81\x57\x89\xd8\xf2\xfe\x57\x86\x18\xdd\x4b\x0c\x5c\x96\xcb\xd1\xa8\x61\xf1\x92\x37\x1c\x1e\xf1\xb8\xa6\xfb\x07\xf5\xe1\xb8\xdb\x72\x09\xcc\x93\x4e\x6b\x04\xd6\x49\x2c\x09\xb1\x38\x15\x49\x10\xe6\x85\x0b\x66\xe6\xcf\x2d\x88\x81\xf7\x64\x97\x07\x89\xb3\x90\x99\xb8\x0b\xeb\x59\xd6\x30\xc1\x8b\x80\xc8\x59\xea\x99\xb9\x5c\xb2\xc1\x91\x9c\x5d\xda\xb8\x19\x91\xbb\xa4\x49\x0e\xf5\x9a\x09\x17\x03\x65\x0c\x5e\x52\x89\x7b\x45\xea\x23\xe1\xad\xe0\x83\xa9\x4b\x4c\x61\xa2\x28\x14\x36\x76\xc0\xef\xca\x58\xea\x19\x8b\x0f\xef\x43\x61\xfc\xd8\xd8\xa6\x8a\x1d\x26\x2c\x39\x09\xbb\x0f\xda\xa5\x07\xde\x2e\xf7\x4a\xbf\x97\x2b\x62\x77\xbb\x7a\x8b\xad\xc6\x8c\x8a\x15\xcb\xad\x79\x88\xf9\x86\x82\xd0\x9c\x6f\x8d\x19\x1f\xbe\x52\x34\x49\x70\x98\x24\x2b\x9d\x1e\x4c\x9a\x56\xca\x46\x7f\x58\x41\xcd\x55\x3c\x9f\xce\x04\x0a\x9b\x0c\xb3\x98\xfe\xa4\xa6\x0b\x81\xf0\xb8\xde\xe0\xd6\x98\x4c\x1e\x6f\xb0\x81\x9e\xdf\xcf\x9f\xaa\x62\xee\xa8\x3f\xc6\x57\xba\x6b\x2c\x24\x4a\x7b\xd2\x59\x4c\xe9\x0f\x26\x50\x40\x29\xa7\xc6\xae\xff\x54\xb1\xe3\x10\xe8\x44\xd0\xde\x68\x65\x2c\x6b\xcc\xd1\x14\x3e\x8e\x18\x6d\xbc\x8f\x7d\xa4\x01\xcf\x58\x46\x06\xdf\xa7\xb1\xc5\xb1\xee\x5f\x87\xfd\x11\x5f\xa7\x6e\x27\xde\x5a\x2b\xcb\x50\x80\x07\x81\x3b\x00\x9f\x32\x46\xd9\xc6\x45\xbf\x62\x85\x9a\x9e\x26\x49\x35\x2b\x93\xfb\x85\x4a\xef\xcf\x8a\xaa\xba\x90\x6a\x16\x12\x31\xbc\xf5\xad\xd6\x9b\x95\x83\x1a\x01\x94\x06\xc8\xd1\x00\xe0\x36\x22\xfe\xb9\x55\xdd\x9c\x8b\x66\xb8\x15\xd1\xa9\x13\x3f\x5a\xd4\xb5\x87\xfb\x33\x6d\xb2\x66\x1e\x20\x56\xb1\x37\xbe\x12\x6e\x5d\xe9\xd5\x03\x8f\x7a\x1d\xce\x4a\x01\xe5\x25\x89\x20\x95\xb4\x01\x20\xa3\xce\xac\xa5\x1a\xdc\x09\x69\xe0\x4b\xdc\x7d\xc7\xb0\xf0\xfb\x60\xf0\xc1\xee\x7a\xd8\xc4\x44\x5a\xbb\xe9\x44\x7e\xea\x3f\xc8\xb1\x36\xc3\xc0\xec\x6d\x40\xf4\x4b\xd1\x65\x7f\x57\xb1\xfa\x1d\x9f\x33\xb9\xa8\x27\xe1\xd2\x79\xc2\x7e\xeb\x7c\x65\x5a\xb2\x42\xb9\x97\xe2\x1b\xfb\x5a\xe8\xce\x8b\xb0\x3a\x59\x77\x6c\x88\xd3\x9a\x4c\x76\x67\x97\xe6\x56\x18\x17\x13\xd0\xe1\xf7\x9b\xe9\xb3\x6a\xaf\x5e\x4f\x0c\x47\x46\xd9\xe0\x4c\xb1\x73\x2e\x17\x95\x5b\x2f\x82\x6d\xf3\x70\xcc\xcc\x0a\x73\x2f\xd9\x28\x82\x0f\xd9\xac\xa8\x0b\x60\x19\x6e\x01\xcb\xb8\x5c\xf6\x6f\xf5\xfc\xd5\xa4\x4f\x9d\xeb\x2e\x70\xbe\xc1\x39\xa9\xde\xda\x82\x43\x64\xff\x5f\xb7\x2c\xab\x59\x6f\x6e\xae\x3a\xea\xd2\x3a\x2c\x7e\x15\xf4\x65\x51\x9f\x0e\x54\x21\x66\x72\x1e\x7b\xd9\xed\xdc\x71\x38\x93\xdb\x98\xbc\x12\xb4\xff\xe5\x0b\x00\xa3\x3d\x17\x06\x3c\xde\x59\xc0\xde\xea\x6f\x7e\x15\x64\x3f\x64\x00\xa1\x95\x09\xb7\xa6\x2a\xf3\xf4\x6d\x78\xba\xef\x4e\xc1\xe6\xc5\x28\x50\x7c\x02\xa8\xff\x4a\x80\x66\x3a\x38\x54\x44\x8a\xcc\xd8\x90\x74\x2c\x0c\x41\x6b\x2a\x0e\xde\x8a\xc3\xe5\x52\xe8\x57\xbd\x7b\xb3\xb7\x66\xf7\x66\x17\x66\xe8\x3b\xe6\x50\x93\xca\x5d\x59\x73\x72\xb3\xdf\x40\x6f\x3a\xb5\xb4\xed\x34\x61\xea\x15\x64\x03\x36\x5b\x58\x28\x85\xde\x86\x05\x4e\x76\x85\x2e\x1b\xf0\x77\x31\xa2\xa5\xde\xb9\x75\xb9\xcb\x25\xd3\x4d\xc0\xcb\xe5\xed\x9e\xf5\x54\x31\x56\x85\xf6\x72\xb4\x13\xae\xfd\xa5\x77\xd8\xf0\x66\x3c\x8e\x33\xb8\xed\xdc\x5d\x8c\xfd\x3c\x8b\xed\xe7\x93\xa8\x2e\xa9\xf1\xf9\x4e\x2c\xdb\x7b\x93\xc4\x54\x3d\xd8\x07\x02\x27\x2d\xf9\x1d\x72\xcc\xe4\x55\x1b\xc9\x2a\xcb\x7c\x3b\x42\x68\xd5\xe6\xfb\x5f\x12\xae\x2a\xae\x97\xd9\xac\x62\x06\xc5\x30\x4c\xa7\x56\xc2\xd3\x53\xc9\x43\x41\x95\x66\xac\x3c\xe6\x98\x13\xe1\x49\x01\xba\x1e\x07\x89\x65\xee\xf6\x13\x53\x20\x29\x1e\x07\xa5\x50\x57\x5a\x33\xfb\x4b\xa7\xa9\x69\xa7\x74\x66\x7d\x29\xcf\x3b\x52\x3a\xb3\xbe\x3f\x6b\xde\x77\x66\x7b\x62\x44\x8d\x48\xd1\x9e\xf2\x28\x06\x78\xb9\xd4\x09\xc8\xed\x4f\xd4\x61\xc7\xda\x5d\x31\x80\x73\xc1\x5e\x5b\x5a\x9f\x65\xd6\xd8\x30\x8d\xe5\x79\x4f\xb5\xb0\xd7\x98\xe5\x21\x5a\xa8\x82\x09\xf2\x95\x67\x3b\x9b\xc7\xa1\xed\x9d\x11\xa9\x89\x7f\x18\x4c\x60\x22\xd3\x16\x2f\x20\x40\x35\x85\xa1\x21\x06\xce\xe2\xde\x00\x81\xae\x0b\x88\x75\x50\x1f\x82\xcd\xa9\xe8\x32\xde\x57\x75\x67\x3a\xa9\x31\x11\x5d\x26\xfd\x69\x7e\x9f\x4e\x12\xa4\xa9\x87\x6e\xa2\x19\x38\x80\x1b\xd5\x32\x0e\x0f\xc8\x06\x5f\x12\xdb\xe7\x83\xc3\x71\x3d\xc6\xc2\x7b\x6b\xd4\xf4\x77\x30\x14\x32\xf1\xe3\x3d\x9e\xc7\xf0\x7e\xbd\xb5\x35\xc6\x2f\x05\x12\x07\xf5\x21\x71\x71\x1d\x66\x7d\x67\xec\xa1\x77\x99\x3a\xe0\x7f\xd4\x9b\x9b\x21\xb3\x55\xec\xf6\x49\xe2\x87\xf0\xdc\x53\x9d\x65\x99\x00\x7b\xa2\xb4\x35\xcd\xc0\x60\xe0\x04\xd7\xdd\x3d\xcd\xac\xff\xcb\x9d\xf2\xdd\x6c\xc2\x1d\x3d\xd2\x6e\x83\x6e\x76\xda\x07\xc6\xcd\x22\x82\x10\x80\xd2\xc0\xe5\xe2\xa1\xb0\xb1\xc5\x2d\xab\xf8\xd4\xfe\x3e\x6a\xb2\x8c\x1f\x84\x89\x68\xf5\xc8\xaf\x5d\x8f\x04\xac\x5c\x40\x62\xfa\x54\x1f\x6c\x5c\xe7\x10\x49\x2d\xca\x10\x17\x1b\x8f\xc5\xe4\xb1\xb0\x00\x58\x8f\x45\x7c\x9c\x21\x9c\x4a\xd7\x9d\x21\x0c\x96\xca\x32\x71\xc0\x0e\x29\xa5\xf2\x80\x1d\x42\x30\x2c\xf8\x4e\x41\xd5\x16\xb3\x23\x61\x34\xae\xef\xd3\x42\x67\x54\x5b\xb5\xc9\xca\xb7\xea\x43\x18\x14\x63\x5f\x3d\xea\xec\x02\x18\x19\xdd\xaf\x27\xa3\x2d\x7f\x40\x0c\xa4\xb8\x15\x90\x02\x62\x5b\xb4\xf7\x51\x72\xc4\xca\x3e\x8b\x40\xb5\x3d\x2e\x8d\x89\xb8\xc9\xab\x46\xcf\xb8\x88\x45\x51\x4f\xb8\xc0\x45\x89\x57\x05\x61\x14\x12\x63\xb7\x1a\xe0\x45\xf4\x29\x1f\xb7\xa0\xf9\xa4\x1e\x78\x0e\xd5\x65\xa2\xdf\x3c\x90\x87\xb4\x46\x02\xe7\x7d\xf3\xa5\x3e\x40\x4c\x9b\x68\x49\x26\x16\xa1\xca\x5d\x3e\xa1\xdf\xf2\x47\x17\x9d\x85\x57\x8f\x5d\x6c\x6a\xb0\x76\x60\x33\xea\x20\xbb\x42\xd4\x6a\xfb\x64\xd2\x4e\xca\x01\x2d\xdb\x79\x17\x1a\x14\x92\xc9\x2d\x91\xbf\xb7\x31\x9c\xba\xfc\x95\xa8\x7d\x18\xe8\xfa\x67\x0a\x56\x0e\x6f\x1a\xdb\x0b\x29\xcb\x86\x63\x48\xe3\x61\xe2\x24\x62\x08\x59\x94\x65\xac\x23\x93\x9e\x57\x61\x17\x90\x03\x05\x95\xa1\xab\xc0\xdf\x22\x78\xa1\xc6\xc7\x56\x40\x18\x6c\xae\xf7\x77\xf7\x30\x1e\xb3\xc1\x8c\xb9\xfe\x42\x98\x8c\x86\x0f\x3a\x6b\x9d\x65\xcd\xfa\x36\x1d\x61\x7e\x33\x53\x3b\x64\xa1\x07\x87\x04\x1c\x98\xf5\x0d\x9b\xd1\x3f\x05\x61\xce\xd1\x8a\xfe\x2d\x56\x12\x3d\x13\x11\x9a\xe3\xd5\x99\xe9\x0b\xdb\x8d\x31\xcc\xa0\x19\x95\xcd\xee\xb5\x98\x80\x76\xd8\x45\x63\x71\x6c\x20\x62\xd2\xf2\x26\xcd\x04\x84\x73\x17\x96\x33\xc2\xf9\x8d\x07\x00\x94\x12\xdd\xd3\xde\x08\x93\x75\x83\xed\x96\xc0\x06\x27\x31\x1a\x27\x71\x1b\xae\xad\x6a\xe3\xbd\x49\x2b\x65\x4d\x65\x8d\x2d\xc7\x23\xd8\x35\x0c\x3c\x55\x94\x40\x7b\x43\x7c\xcd\xe0\x85\x0a\x9f\x19\x9f\xb8\x16\xb1\x63\x6f\x39\x7a\x4b\xac\x48\x9c\xa0\x27\x45\x18\x34\xad\x46\x92\xfa\x9a\x85\xc0\x2c\x90\x26\x68\x3c\x4c\x66\x8b\xca\x3e\x6e\x2d\x28\xed\x45\xa7\x63\x6d\x32\xaa\xca\xb5\x6d\x5c\xd7\x59\x6e\x56\x77\x6c\x63\x69\x7a\xea\x60\xb8\x5a\x61\xf2\x4c\x84\xc6\xd0\x2b\x08\x87\x60\x2a\x01\x15\x35\xd7\x49\x04\xd5\x0e\xb8\x4d\x28\xcb\x1a\x65\x69\x46\xc4\xbc\x65\x36\xff\xca\x16\x01\x1d\x69\x70\x3c\xa1\x78\x3e\x67\x6f\xeb\x62\x7e\x96\xb7\x51\x69\x36\xd8\xc0\x3f\x5e\x2e\x1f\x17\xb5\x3e\xb9\x5e\x20\xbc\x22\xad\x55\x0e\xca\xe2\xd5\x3b\xb5\xa8\xdc\xed\x4a\x37\x89\x7d\xab\x99\x98\x25\x7e\xeb\x91\x45\x6b\x2c\xd9\x8c\x51\x43\xbb\x23\xd2\x5a\xff\x2f\x9d\x3a\x8e\x62\xf5\xd1\x08\xba\xd5\x3a\x41\xc1\x42\x16\xf0\xb6\x38\x89\xd6\x03\x1c\xdf\xd0\xe4\x51\x3c\xb2\xa8\x20\x71\x87\x00\x68\x57\x34\xdc\xc0\xce\xd5\xb5\x4e\xd9\x0b\xf2\x9b\x00\xdb\xd2\x95\xbe\x78\x66\x41\x2f\xfe\x10\xd4\xd3\x01\x5d\xe9\x73\xbe\x83\xc8\xff\x65\xed\x93\x4f\x82\x1e\xec\x91\xd1\x0e\xd9\xbe\x4b\x76\xb6\x0f\xc9\x47\x41\xff\xca\xb2\xfe\x7e\x30\xc1\x69\x60\x70\x90\x5f\x2d\x3b\xa2\xb3\x39\x41\x1e\x80\x66\x47\xfe\x93\x59\x86\x7e\x15\xc1\x6d\x32\xce\x66\xaa\xfa\xd9\x7c\xe6\x1d\xfb\x56\x37\xca\xcf\xb2\xde\xaf\x82\x30\xa5\x9f\xa3\xde\x47\xb1\x5c\xfe\x2a\xb2\xec\xde\x7d\xfd\x7f\x34\x7a\x40\x7f\x15\x98\xd4\x8a\x1a\x09\xc2\xe0\x58\xc9\xf9\xfe\x69\xa1\xf6\xe5\x8c\xa1\x9d\x6d\x4c\x84\xa2\x57\x47\xec\x58\x2a\x06\xb6\x46\xf9\x8f\x2d\x10\xfb\x52\x3c\x0a\x2f\xf4\x83\x3d\x62\xfa\x60\x9d\x69\x62\x3f\x35\x5e\xea\x93\xbe\x33\xc6\xeb\x93\xbe\xb7\x79\xea\x93\x3e\xd8\xd6\xf5\x0f\x57\x24\x7a\xe1\x89\x98\xdd\xac\x86\xfb\xc9\x3b\x69\x25\xd3\x67\x6b\xea\x69\x2c\x21\x1b\x86\x56\x6b\xbc\x9e\xbc\xbd\x60\x6c\x77\x95\xd4\xdb\x68\xa3\x7f\xb6\xe6\xd6\xa2\x76\x4d\xdd\xe1\xe9\x4d\x6b\x7f\xad\xd7\xd6\x8f\xeb\x6f\x4c\x66\x7f\xba\x01\xce\xd2\x76\x4d\x0b\xcc\xe3\x9b\x36\xc1\xa2\xb0\xfc\x54\x1b\x56\x44\xa9\x24\x4a\xac\x54\x9d\xd2\x63\x28\xc3\x09\x85\xb7\x46\x3d\x4a\x3f\x05\x25\x32\x68\x7c\xf7\x61\x2a\xba\xcc\xf0\x21\x67\x9b\xb3\xbd\xbd\x07\x52\x2c\x9b\xcb\x67\x32\x83\x3a\xf2\xa7\x9a\x05\xf1\x84\x71\xdb\xf2\x40\xc7\xed\x10\x31\xbe\xc6\x5c\x45\x58\x64\x0d\xc1\x30\x88\x12\x66\xac\x2e\x78\x89\xf5\xea\x52\xd4\x05\x60\x7e\x4d\x8c\xd4\x32\xf7\x22\xc5\x02\xa8\x00\x1a\x34\x45\xaf\x42\x54\xe7\x5c\x28\x92\xb8\x8b\xe6\x4d\x0b\x2c\xcb\xba\x8e\xf9\x31\xfa\x28\x30\xcb\x9b\x94\x6b\x8e\x31\x67\x0d\x25\xd4\xa0\x39\xfe\xbd\xf0\xa2\xf9\xa2\x5e\x06\xf2\xe6\x2b\x4f\xc4\x6c\xed\x0b\xd6\xca\xb1\xf5\x8e\x19\x50\x5e\x19\xc4\x9d\xf6\x07\x8c\x79\x0a\x35\x81\xde\x07\x80\xde\x8e\xaf\xe1\xdc\xf7\xac\xf1\x67\xd8\xde\xde\x83\xf3\x81\xed\xd7\x8e\xb7\xa0\x59\x9e\x83\xe7\x13\xc4\x54\x96\xf5\xbf\x4a\x80\x61\x05\xb5\x0f\xf0\x68\x85\x5a\x2e\xb9\x4e\x6a\xbf\x3c\x01\xb0\x94\x66\x55\xb2\xac\x50\x59\x86\x24\xd5\x27\x57\x9c\xa3\xa7\x22\x1c\x4a\xd1\x63\x41\x15\x5e\x7b\x2e\xd5\x3d\x3d\xc4\x98\x70\xfa\x87\x08\x8c\x38\xe2\xfe\x44\x31\xe1\x30\x36\xa8\xcc\x9d\x81\x95\xa4\x5c\x21\x03\x76\xea\x9e\x61\xf2\x42\x20\x8e\x89\xa4\x1c\xe7\xd2\xb0\x5c\x88\xd1\xcf\x11\xaa\x5d\xd7\x34\x6a\xf6\xa9\x23\x8c\x72\xd1\x87\xa2\x69\xe1\xec\xb2\xb6\x61\xee\x5c\x9c\xf2\xe9\xa9\x11\x9c\x22\x3d\x65\x87\xa4\xb6\xb1\x10\xa2\x3d\x21\xf7\xe8\x08\x35\xd4\x13\x53\x4a\x6b\x95\x65\x4a\x59\x91\x6b\x63\x1e\x19\xa6\x6b\x05\x7d\x9e\xb7\xc0\xf3\x0a\x27\xac\x6c\x56\xdb\xc8\xdf\x7a\x1f\x45\x96\xd9\xe5\x62\x82\x18\x74\x05\x79\x24\xe8\x53\x41\x9d\xc0\x01\x66\x15\x61\xd8\x18\x8e\x35\x68\x61\xb6\xaf\xb8\x26\x4d\x0a\xc0\x91\xad\x1e\x4c\x6b\x55\xfe\xc6\x2e\x97\x4b\x10\x8d\xdb\xab\x39\xab\x8b\xdf\xd8\x25\x20\x1d\xdb\x0c\x59\xe6\x32\x58\x70\xe4\xe9\xa9\x1e\x25\xa3\xfb\xe6\xca\x1d\x35\x3d\xda\x91\x4e\x34\x98\x2c\x40\x5c\xf7\xa0\x8b\x11\x70\x59\x56\xad\xda\xae\xe9\xd1\x68\x9c\xd7\x76\x9c\x9b\x2e\x30\x1d\x73\x4d\x3f\xe0\x09\x42\x35\xfd\x25\x1e\x9b\x42\x0d\x22\x0e\xc4\xd9\x28\x9a\x91\xc8\xf4\x40\xac\x31\xce\xad\xdb\xb4\xb3\x21\x9c\x18\xb6\x1b\x8c\x91\x64\x7e\x20\x49\x7d\xb8\x5a\x91\x4a\xd1\x2b\x30\x64\xc8\x7b\x43\x02\x3b\x96\xfd\xd5\xbc\xb2\xbe\xee\xbb\x9b\x2d\xa8\x74\xdf\x18\xc9\x14\x1c\xac\x65\xe6\x52\x18\x8b\x3a\xa3\x9b\x33\x76\x30\x46\x95\xa9\xaf\x21\xda\x21\xd8\xc4\x80\x4a\x14\x6c\x62\x58\x69\x7e\xbe\x81\x3d\x8d\xfb\xca\x42\x41\xf2\x05\x63\x5f\xf3\xde\x30\xc2\xb7\x9b\xaa\x7f\xaa\xab\x8d\xd4\xb4\x93\x5e\xaf\x52\x07\x46\x84\x7c\x98\xa7\x7a\x55\x83\x91\xa1\xa9\x00\x36\xe4\x37\xdc\xb0\xad\x3f\x52\xb2\x4b\x43\xda\xf5\x5b\xb3\xf5\xdc\x06\xb7\x8d\xd8\x05\x23\xda\xa1\x17\x67\x4d\xb3\xf6\x74\x7b\x8e\x90\x3c\x54\x6a\x87\x86\x98\xe6\xc0\xc3\x20\x59\xa8\x81\x29\xc0\x08\x47\x30\x36\xe1\xb4\xfb\xce\xbb\xea\x8b\x66\xef\x41\x38\x48\x0c\x9e\xe7\xb1\x0d\x4d\x70\xa6\x1a\xd2\xc0\x53\xe8\x04\x00\x7a\x09\xbb\xec\x5c\x59\x21\xca\x05\x43\xa0\xae\x09\x5e\xb6\x51\x24\x1f\xe5\x17\x0f\xf7\xdd\x18\x91\xc8\x50\xff\x28\x65\x38\x4e\x14\xc2\x57\xc7\x7a\x35\x3f\x56\xb0\x55\x4f\x4f\x61\xa7\x45\x7d\x29\x5c\xa0\x32\xd7\x86\x4b\x85\x75\x75\x6d\xcd\xa3\xda\x5d\xba\xda\xd9\x5d\x00\x74\x47\x71\x98\xb3\x2c\x9b\x2b\x74\xa6\x30\x06\x20\xea\x99\xbe\x26\x8c\x00\xa4\x0c\x26\x8f\x31\x34\xd6\xd8\xdb\x7a\xfb\xf3\x87\xe8\x54\xc5\x86\xab\x8f\x7d\x5c\xfb\x28\xf2\x87\xef\x14\x6b\x76\x0d\x50\xac\x48\xb7\x09\xe8\x4a\xd0\xb1\xa2\x35\x1e\x14\xf5\x8f\xda\x85\xcd\xa0\xb1\x3b\xab\x2e\x20\x8a\x0f\xe1\x5b\xd7\x1c\x2b\x56\x13\x62\xb8\xb3\x70\xe3\xf7\x68\x47\x79\xd3\xf8\x48\xef\x1b\x77\x14\x68\x90\x1a\xb9\x63\x3f\xd2\xaf\x51\xe6\x44\x1d\xd3\xd1\xc5\xe6\x55\x7d\xd2\x3a\x52\x74\x5a\xbb\x17\xf4\xd6\xd9\xeb\x3c\xbd\x2d\x97\x7b\xf7\xbb\x8f\x75\xe6\x5c\xf7\x2a\x65\xc7\x16\x8a\x7c\xe1\x15\xac\x83\x40\xcd\xb7\x8b\xb3\x33\xa9\xf4\xb1\xfd\xe8\x86\x8c\x1a\xad\x27\xaf\xf5\x8a\x99\xdb\xd3\x27\xa7\x32\x5a\x65\xe4\xba\x55\x26\x50\x5f\xb7\x97\xc7\x9a\x29\x9e\x65\x7d\x30\x0a\xf3\xc1\x20\xb1\x91\x69\x9f\x2b\x6f\xb8\x3f\x55\x48\xc2\xe0\x3b\x52\xb8\xa0\x5f\xcd\x83\xab\x82\x5e\x28\xc3\x74\xd2\x6f\x0a\x98\x30\x14\x57\x27\x09\x4e\xc3\xaf\x0f\x4e\x23\x5b\x21\x23\x6c\x55\x34\x7b\x45\x9f\x18\x5f\x84\x02\x6e\x0a\x03\x8a\xe8\xba\x6c\xa6\x50\x61\xbc\x15\xca\x2c\x2b\x11\x23\x92\xd4\x98\xc4\xa3\x11\x31\x2a\x53\x34\x67\xac\xd7\xe3\x10\xbd\x22\xcb\x22\xc8\x7c\x17\x42\xf0\x2f\x86\x24\x71\xe9\x44\x5a\x68\xeb\xd5\x8a\xec\xab\x58\x76\x70\xce\xd9\x85\x11\xc1\x18\x3e\xdd\xc9\x11\xde\x2a\x7a\xf5\xb0\xac\xf3\xbe\xd9\xd2\xfb\x64\xdf\x7c\x2e\xef\xdb\xed\xbe\x4f\x5e\xb2\xba\xc8\xfb\x96\x13\xe8\x93\xb7\xa7\xfc\xb8\xce\xfb\xe0\x07\xab\x13\xa2\xc5\xf3\xaf\x68\x5f\x69\x89\x1b\x3d\x43\x70\xc2\xf4\xc8\xe3\xc7\xdc\xb6\x72\xd2\x4e\x42\x0c\xe7\xbd\x1e\x62\xf4\xad\x3a\x60\x87\x38\xcb\x7a\x3d\xb0\x41\x0d\x5a\x72\x15\x64\x41\x7f\x29\x58\xf0\x5e\x2b\x3a\x24\x6f\xf4\xbf\xdf\x81\x1f\xfa\x02\xff\x5f\x2a\xba\xaf\x3c\x15\x2a\x08\x33\xff\xd1\x10\xc2\xdc\x7c\xb2\x42\xaf\x92\x33\x51\x7f\x8c\x6f\xec\x93\xb3\xe2\x84\x7d\x0c\x97\x2e\xbf\xa1\x8e\x2d\xc9\xd2\xc2\xdc\x19\x4a\x9a\x6b\x4b\x35\x73\xd3\x6c\x65\xfe\x4e\x11\xa3\x8f\x75\x52\x38\x7d\x6d\xa5\x70\x8a\x95\x45\xcd\x66\x4d\x41\x5e\x2c\x79\x4b\xb2\x40\x84\x07\xcd\x4d\x45\x21\x13\x22\xc4\xae\x89\x9e\x69\xf6\x3a\x4f\x32\xe2\x15\x99\xcb\x73\xb8\xfc\x98\x7c\x47\xcf\x45\xff\xc4\x60\x38\xfb\x4f\xfb\x74\x8b\x85\xf0\xda\x87\x30\x7b\xad\xf4\x77\x0d\x9d\xc9\xef\x6a\xd2\xf7\x6e\x73\xc1\x0e\x6b\xe2\x73\x6c\xd5\xf9\x30\x47\xbf\x03\xa7\x3d\x8c\x6a\xf2\x69\x6d\x4d\x3e\xad\xa9\xc9\x27\x5b\x93\x37\xbe\x26\x6f\x42\x4d\x3e\x91\x2f\x3f\xa8\xc9\x27\x53\x93\x2f\xae\x26\x2b\x4c\x1e\x2a\xfa\x32\x0c\x1d\xef\xf5\x62\xba\x07\x02\x5c\x9b\x4b\x13\xda\xd9\x8e\x10\xcd\x52\x2f\x94\x17\xcc\x8a\x13\x26\x6a\x5e\x94\xaf\xd3\x74\x5e\xba\xa1\xa6\x2f\xed\x90\xaa\x2f\x78\xe5\x8a\x31\x1f\x7b\xe7\x45\xbc\xbc\x7a\xad\xf8\xbc\x50\x97\x6e\xde\x3e\x57\xf4\x6a\xee\xd5\xfd\xf9\x55\x53\x87\x99\xa7\xe6\x00\x4d\xe1\x97\xc7\x55\x21\x91\xc3\xd1\xa1\xa6\xff\xa2\x62\xe0\x00\x79\x4d\x91\xf0\xfc\xc6\x45\xda\xb6\x5c\x53\xcf\xd7\x51\x8e\x56\xb1\x11\xa4\x0b\x49\xdc\x9d\x42\xd1\xd7\xd4\xf7\x75\x94\xe3\x67\x8a\x5e\x91\xef\xe9\x8e\xf8\xfc\x47\xfb\x1e\x91\xd8\x3a\x11\x45\xcd\xb7\x3b\x78\x5c\xb6\x4e\x22\x05\x0d\xe4\x6a\xe4\xb1\x29\x00\x54\x0a\x91\x86\xd1\xce\x76\x26\x8d\xaa\xbc\x31\xe1\x45\x32\x8d\x97\xcb\x5e\x91\x65\x3d\x9e\x58\x60\x20\x4e\xd5\xc0\x6c\xc1\xe0\xc0\xa8\x72\x48\x49\x2d\x8f\xf5\x51\xdc\x06\x20\xe0\xec\x62\xb9\xe4\xd6\x48\xc9\x98\x7d\xba\x1d\xbc\xc0\x13\x54\xd0\x9a\x04\x18\x18\xb0\x3d\x68\xd5\xc9\x2f\x33\x78\xf2\x17\x70\x00\xc0\x44\x66\x19\xaa\x7b\xd4\x60\xcd\x18\x2b\xa6\x3a\x58\x31\xd5\x16\x27\x0d\x59\xe8\x0d\x9c\x17\xd4\x19\x90\x14\xb1\x39\x9b\x4b\x6c\x51\x2f\x25\x39\x36\x9b\xfd\x4b\x45\x2a\xfa\x5c\x0d\xc2\xa0\x26\x53\x7f\x0f\xa3\x8d\x2c\x6c\x47\xf4\x81\x53\x88\x3b\xc1\x80\x2e\x26\x5d\xd7\x33\x51\x68\x4a\xfa\xd0\x16\x1c\x8f\x3f\x53\x74\x3c\xd8\x75\xe1\xf6\xbe\x0f\x7c\x01\xb3\xc1\xb2\x8a\x09\xcf\x5f\x0b\x54\x60\xc2\x5d\xf8\x2c\x93\x52\x63\x82\x2a\x5a\x46\xc7\x8d\x8a\x14\xf6\x18\x0a\xc7\x8c\xc5\x66\xdf\x60\x27\x11\xaf\x98\x66\xa4\x4a\xfb\x80\x72\x82\x44\x52\xc8\xd4\x9f\x65\x5d\x21\xc6\x1d\x91\x08\x57\x08\x27\x8d\x8e\xa4\x8c\x2c\x68\x4d\x90\xa2\x05\xce\xb2\x05\xb6\x56\xd0\x53\xba\x20\x05\x1d\x12\x46\x4b\xaa\xc6\x6c\xcc\x28\x18\x79\xe1\x62\x73\xd3\x99\x1f\x90\x9a\x4e\xc7\xf5\xd8\x9a\xa8\x60\x66\x9f\x8c\x87\xf7\x8b\x2d\x36\xc6\xa5\x4e\x2f\x31\x29\xb6\xb6\x5c\x3a\xdb\x2a\xc6\x78\xaa\xd3\xa7\x98\x30\x97\xae\x33\xc0\xca\xaf\x0f\xd7\xd3\xe5\x12\x7e\x22\xb4\x65\x27\x93\x73\x05\xda\x02\x56\x65\xe4\x37\x57\xda\x23\x17\xd4\xbc\x24\x25\x3d\x38\x1c\xab\x2c\x53\x3d\x4a\xa7\x21\xd8\x33\x2a\xa8\x8a\x0a\x5e\x2e\x0b\xfd\x1c\x8f\xb1\xd5\x34\x2b\x4c\x94\x2e\x5c\x19\x23\x1a\xa5\x4b\x59\x64\xd9\xa2\x55\xca\xa2\xb3\x14\x65\x4a\x59\x60\xb2\xd0\xa5\x2c\x4c\x29\x0b\x3a\x1c\x2f\xee\x3b\x15\xf7\x78\xb1\xb9\x89\x9f\x0b\x54\x1e\x2c\x62\x53\x9c\xca\xe5\x55\xc1\xc4\x67\xa1\xe9\xf2\x5c\x20\x05\x59\x83\x89\x8f\xf0\xf2\x3f\xbd\x66\xdc\xd9\xcd\x24\x9e\x1c\x54\x87\xf9\x41\x45\xc4\xe1\x6a\x05\xbb\xe3\x0b\xd5\x65\x24\x6b\xdd\x89\x78\x35\xf1\x57\x0d\x29\x95\xdb\x6c\xad\x75\xf6\xd0\x4c\x84\xd1\xbf\x18\xa5\xa3\x7f\xe9\xf9\xcc\x20\x7c\x51\xdd\xa3\xf5\x8a\x3c\x56\x2e\xaa\x62\xd0\x9a\xa5\x06\x18\x81\x61\x7c\x1a\xce\x3c\x2f\xcc\x65\x12\x89\xbc\x1d\x0b\xd7\x07\x00\xef\x8a\x94\x5b\xfb\xa7\x75\x8a\xd5\xe4\xe3\x3c\x82\xcb\x14\x04\x7e\x89\x13\x2c\x16\x93\xa5\x31\xe0\x3d\x26\x42\x2c\x7d\x8e\x86\x8e\x1f\x8e\x55\xb0\x9e\x52\x9b\x9b\xfa\xa4\xd1\x7b\xac\x6c\x90\x44\x03\x1a\xbb\x5c\xf6\x74\x53\x0e\xf4\xcd\x21\xa9\xcd\x2f\x0e\x25\x79\x43\x19\x5d\xb1\x47\xa0\x2e\xbb\x46\x1d\x37\x1a\x3d\xe8\x56\xc7\x91\x0f\x8a\x5e\x99\x93\xd2\xcd\xa4\x2b\x26\xd8\x51\x2a\x5d\x31\x69\x3f\x50\x7c\x04\x60\x0d\x87\x89\x61\x84\x2c\xa9\x78\xa5\x8d\x13\x78\xad\xc0\x85\xdc\xb2\x62\x91\xf7\xf6\xf7\x99\xfd\xfd\x33\x15\x5b\xfc\xad\xd2\x60\x45\x7e\x2b\xab\x27\x81\x1e\xf9\x1e\x80\x40\x7b\xdf\x9a\x3a\xaf\xd3\x1d\xce\xcd\x8c\x3f\x7d\x00\xf9\x5b\x6a\xb9\xbc\xa5\xd7\x81\x85\x40\x02\x5b\x81\x6f\x38\xf7\x1b\x8d\x17\x17\x48\xd0\x5b\x0a\x67\xd9\xa9\xc9\x45\xaf\x4c\x14\x0a\x31\x48\x73\x12\x26\x66\x71\xe2\x13\x31\x5b\xe5\x82\x5e\x15\x62\x7a\x2a\x15\x80\xda\x22\x41\x91\x48\x6b\x95\x65\x8d\x84\x74\x17\x76\x08\xbb\x27\xcc\x06\xaa\x02\x3d\x3e\x1e\x84\x42\x89\xb9\x7c\x65\x3c\x56\xc4\x20\xbe\x25\xd0\x49\xf0\x69\x31\xf0\xd7\x26\xd5\xbf\x10\xdd\xad\xc8\x33\x95\x65\x4f\x15\x7a\xa6\x88\x27\xc8\x33\x90\xad\x34\x24\x60\x1f\x94\x6d\x28\x79\xaf\x08\x4c\x59\x2b\x02\xb3\xe7\x76\x8f\x6f\x4b\x6f\x29\x27\x09\xc3\x46\x33\xff\x5b\xca\x53\x7d\xf8\x31\x4f\x45\xb8\x35\x29\xe2\xb4\x87\x24\xe5\xcb\x25\x4a\xf9\x99\x74\x10\x04\xef\x9f\x89\xca\x9b\x6c\x0e\xc6\xf8\x8a\xe5\x57\x92\xfe\xca\x90\xd4\x7b\xef\xfe\xc0\x4d\x01\x6f\x41\x5e\xd0\xe1\xb8\xb8\xcf\xdd\x1c\x2f\xec\x1c\x97\x80\x42\xca\x0f\x8a\x43\x8c\xaf\xa4\x1e\xa1\x4e\xb7\x23\x69\x6f\xb8\xe2\xb4\x07\xf8\xaf\x29\xef\x65\x05\xf1\x2d\x51\x48\x03\x3e\xd1\x08\x2d\x22\x4f\x14\xd9\xf2\x44\xc9\x32\x74\x4b\x51\xa9\x27\x4b\xed\x66\x4a\x82\x64\x68\xd4\x78\xcf\x14\x7d\xaf\xa8\x9d\x59\x2d\x54\x45\xa3\xf5\xd3\xf3\x6b\x98\xa0\x97\x86\x09\x1e\xeb\x08\x17\x67\x11\x5e\x63\x2c\x72\x87\x09\x4a\xfe\x56\x08\x64\x19\x91\x6b\x4b\x98\xe9\x39\x3f\x46\x8f\x1c\x14\x6e\xaa\xaa\x6c\x6b\x39\x37\x6c\x51\x89\x35\xfc\x8a\xfc\x91\x48\x2f\xbc\xcf\x3a\xf0\xf5\xc6\x18\xb3\x2c\xce\x2a\x36\x7b\xc7\x5d\xc2\x59\xc5\x16\x33\x7f\xc4\x75\x06\x14\x49\x31\xd3\x92\x9f\x1d\xc9\x42\x01\xb8\x54\xc7\x99\xba\x9f\x64\x70\x2a\xcc\xf4\x2d\xd3\x89\x69\xa2\x3e\x2d\x7e\x4a\x04\x0d\xe9\xe9\xdd\x54\x26\xac\x6a\x1f\x63\xf9\xbb\xd7\xd7\xba\x3a\x58\x6d\x88\xf9\xbc\xf3\xdf\x1b\xb8\x64\x9c\x65\xa3\x1d\x07\x30\x47\x47\x3b\x38\x67\xb4\x26\xa3\xa1\x97\x21\x8d\x76\x30\xd9\xd9\xbe\x0f\x3b\x34\xc0\x6c\x4f\x58\x6e\x76\x9b\x5f\x15\xbd\x7a\x52\x4d\xf3\xfe\x93\x6a\x5a\x9c\xb1\x3e\x79\x7b\x56\x4c\xd9\x51\xa1\xf2\xfe\x46\x9f\xbc\x60\xc7\x75\xde\x7f\xa8\x94\xbc\xd0\x97\x7d\xf2\xfe\xcc\xde\xbe\x3f\xeb\x93\x37\x70\xc6\x35\xf7\x70\xdd\x27\x8f\xe5\x85\xb0\x29\x60\xf0\x4e\x1e\xb3\x32\xef\x3f\x06\xbc\xc1\x3e\xf9\xc0\x45\xde\x7f\xf5\xb6\x4f\x5e\x32\xb1\xc8\x5d\x24\x74\x7d\xd3\x27\x0f\xcf\xce\xaa\x46\xd2\x5b\x40\xb5\xc9\xfb\xe6\xf7\x85\x9c\x7e\xed\x93\x97\xf2\xfb\x6b\xc5\x05\xcc\x81\xdf\xd8\x65\xde\x7f\x2f\xf8\x4c\x9f\xa7\x8f\x39\x9b\xf5\x57\xe4\xb3\xa2\x57\xf7\xf2\xfe\xa3\x62\xfa\x15\x62\x4f\xf5\xc9\x5e\xde\x7f\x57\x1c\xf5\xc9\x68\x3b\xef\xef\x97\xac\x50\x7d\x32\xda\xc9\xfb\xf6\x64\x39\xba\x93\xf7\x41\x7a\xd5\x27\xa3\xbb\xe6\xfb\x4a\x96\x7d\x32\xba\x97\xf7\x1f\x96\x3a\x75\x2f\xef\xbf\x2e\xf4\x71\x80\x6c\x0f\xf3\xfe\x7e\x71\x56\x99\x9a\x6c\xdf\x0d\x44\xdb\xd9\x06\x72\xed\xec\xe8\xbc\x27\x4c\x13\x67\x67\xd7\x5c\x1b\x32\xec\xdc\xd6\x5f\x9c\xf5\xc9\xce\x9d\xbc\xff\x4c\xce\xf5\x3b\x77\x13\xca\xee\xdc\x8b\x28\xbb\xb3\x97\x92\x75\x77\x98\x10\x75\xf7\x76\xde\x7f\x2e\x2a\xa6\xf4\xa3\x3b\x81\xbe\x23\xdd\xc6\xa7\x23\x7d\xb1\x93\xf7\x9f\x6e\xeb\x8b\xdd\xbc\xff\x74\x47\x5f\xdc\xce\xfb\x4f\x77\xf5\xc5\x9d\xbc\xff\xf4\xb6\xbe\xb8\x9b\xf7\x9f\xde\xd1\x17\xf7\xf2\xfe\xd3\xbb\xfa\x62\x2f\xef\x3f\xbd\xa7\x49\x35\xcc\xfb\x4f\xf7\xf4\xc5\x48\x17\x38\xd4\x57\x50\xb4\x2e\x7b\x5b\x97\x3d\xd2\x85\xef\xee\xe6\xfd\xdf\x17\x73\x43\x8f\x91\xae\x55\xdc\x55\xdb\xdb\xbb\x79\xff\x25\xab\x8b\xfe\x8a\x30\x19\xcf\x84\xaf\xec\xb2\x29\xcb\x81\x21\xef\xc6\xff\xaf\xea\x00\xee\x0f\x97\x4b\xf8\x05\xbe\x2f\xe9\xe8\x24\x78\x8a\x5d\x26\x82\xfa\x32\x88\x72\x0c\x64\x29\xa3\x30\xbb\xf0\xc4\xf6\x7a\xde\xa5\x68\x64\x0d\x25\xbb\x93\xea\x06\x09\xbf\x29\xf2\xb3\xad\x9c\x7e\xe9\x10\x82\xb4\x47\xf5\xca\xfb\xfd\x15\x71\x3e\x95\xff\x5c\x2a\xa8\xd8\x19\x2b\xac\xdc\xc7\xa8\x30\xd7\x4b\x0b\xdd\x22\xd0\xb5\x76\x75\x50\x04\x28\x91\x0f\x57\xc4\x36\x61\xcd\x6b\x3f\x24\x83\xa7\x81\x2e\x0b\x54\xb4\x3f\x55\x81\x9f\xfe\xc6\x0a\x93\x5a\xc6\xc2\xb7\x59\x51\x17\x10\x5d\xea\x98\x29\xb7\xb4\x8b\x64\x98\x19\x1c\xb1\x2a\x36\x88\x7c\x17\x27\x99\xad\x69\x96\xa4\xad\xed\x92\x6b\xba\xb1\xa3\x57\x56\x98\x28\x19\x6f\x33\xb1\x46\xec\x67\x36\x2b\x99\xb6\x99\x95\x75\xf1\xb1\x8b\xd0\xe6\x89\xb7\xb0\x31\xf9\xfa\x00\xf4\xf5\x38\x7a\xb4\xc5\x06\x51\x9a\xee\x3a\xc8\xfa\x69\x6d\x91\x9f\x92\x22\x3f\xc5\x45\x7e\xea\x28\x32\xc9\xd0\xf1\xdc\x7f\xf1\xb3\x53\x3c\x94\x75\xf1\x12\x38\x52\xd3\x5e\x2e\x13\x76\xf0\x45\x7d\x43\xd5\xd2\xe3\xda\xc5\xa6\x02\xde\xac\x8b\xe9\x8a\x0c\xaa\x82\x95\x83\xde\x23\x3f\x82\x89\x49\x97\x35\x44\x9b\x41\x61\x94\xc9\x36\x97\x15\xf3\x6e\x8c\x7e\x52\x09\x33\x65\xdc\xc4\xf8\x31\xda\x36\xd0\xa1\x20\xd1\x6f\x7f\xce\x81\x10\x3a\x1e\xcb\x22\x10\xae\x31\xd2\x0a\x72\xeb\x2e\x06\x2d\x40\x64\x77\x22\x68\x27\xfc\x1d\xa3\x2f\x55\x13\x9c\xbb\xc9\xe7\xad\x45\xed\x66\xdf\x3c\x4c\x7d\x04\xe2\xed\xef\xa3\x6f\x7a\xb0\x44\x7f\x2f\x81\x9c\x75\x42\xce\x08\x12\xd0\xb9\x45\x5b\x30\xc0\xf8\x36\x6a\x77\x40\x06\xd4\x85\x89\xb8\xb0\x8d\xbf\x19\xe4\xd9\xf8\xcd\xfe\xfe\xc1\x72\x46\xff\x88\x1b\xbb\xf1\x8b\x4e\x52\x49\x15\x5c\x10\x01\x46\xf7\x13\xba\x18\xd0\xbc\x9c\x51\x29\x53\x5e\xf9\xec\xd2\x91\xd5\x13\xdc\x1a\xd8\x30\xfa\x8b\xfa\xa7\xe0\xe2\x31\x94\x57\x42\x90\x08\x9f\x31\x4d\x89\xa8\x12\x63\x9e\x5f\x0f\x8b\x6e\xc6\xf4\x43\xd5\x40\xa0\xd3\xe7\x3b\xc7\x7d\xbf\x10\x10\xc3\x2c\x9c\xf6\xa4\x93\x1d\x92\x7a\xb5\xd2\xf3\xad\x15\xd7\x69\x38\xc2\x78\x7c\x42\x4d\x04\x9e\x20\xf2\x01\xb7\x21\x23\x19\xe9\xbf\x31\x31\x60\x98\x82\x89\xfd\xba\x5c\x9c\x70\xb1\xf1\x96\xcf\xcf\x4a\x16\xa7\x3c\xf1\xc2\xd4\x38\xd5\x98\x80\x24\x6f\xc2\x79\x23\x4e\x89\xec\x7d\xa3\xe4\x58\xe4\x80\xc9\x37\x84\xc9\x29\x7d\x23\xc8\x9c\xbe\x13\xe4\x9c\xbe\x16\xe4\x2d\xba\x6a\xd5\x22\xe7\x92\x74\x56\x24\xff\xae\x48\xab\x2e\xf9\x2b\x45\x5a\xd5\xc9\x7f\x53\xa4\xbb\x46\x79\xa9\x56\xd6\x15\x4b\xd2\x83\x43\x52\x4a\xba\x15\x49\x3b\x2a\xa9\x17\xae\xe1\x83\x52\x82\x36\xcf\x45\xc6\x2a\xe4\x41\x29\x0f\x89\xf9\xa1\x96\x51\xd8\xda\x8a\x21\x40\xa4\x11\x93\x94\x72\x73\xd3\xe5\xf3\xef\x93\x50\x92\x35\xcc\x91\xf4\x6a\x45\x8e\x25\xbd\xb2\xe9\xf9\x42\xae\xc8\x59\x74\xdf\x1b\xad\xc8\xa9\xa4\x0b\x19\xea\x36\x97\xa9\x37\xae\xb3\x86\xd7\x8b\x0b\xac\xdf\xa9\x57\xee\x42\x5a\x9f\xdc\x86\xdf\xae\xca\x32\x35\x68\xb8\x8b\xbf\xb4\x11\xf3\xde\x8b\x79\x51\x7d\x65\x26\xc8\xaa\x3d\x1d\xc4\xd2\xff\xb5\x2f\xbe\x6c\xbd\x66\xc0\x0e\x09\x77\xd1\xcf\xa5\x0d\xc8\x09\x8e\x5e\x07\xd2\x07\xda\x54\x59\x86\x10\x8b\x2b\x89\x7f\xaa\x76\x9a\xb8\x37\xae\x14\xe5\x98\xf0\xc8\x8c\x47\x46\xda\x5c\xa3\x55\xb1\x27\xbd\xf0\x0a\x50\x36\xea\xe8\x23\x89\xf0\x55\x25\xd1\x99\xc4\xa4\x92\xe8\x58\x46\xcf\x4e\x64\x14\x38\xe5\x58\xba\x4e\xef\x51\xba\x90\xcd\x49\x7b\xe7\x1e\xc6\xe3\xa9\x2e\x80\xd4\x98\x4c\x75\x81\x09\xa6\xcc\xa5\x4c\x41\x31\x1b\xbd\xc8\x9c\xb3\x7c\x5c\x4d\xd2\xe1\xe8\xab\xf4\x52\x12\xd3\x20\x78\xc8\x3b\x91\x0b\xd7\x5d\xa3\x68\x2b\x27\x02\x43\x8e\x1e\x82\xe7\xac\x85\xc8\x34\xbc\x47\xce\x8d\x32\xa9\xff\xde\x7a\x27\x11\xde\x08\xa1\x2e\xc8\xd5\x2a\xf1\x51\xfb\x16\x93\x9c\xd1\x46\xcf\x67\xd9\xfa\xce\x64\xea\x24\xed\xcc\xe5\x72\x21\xf5\x1c\x09\x84\x26\x86\x9e\xcc\xd1\xf3\xcc\x3f\x01\x04\xc4\x60\x67\x74\x3d\x6d\x7b\xad\xd0\x7a\x77\xf6\x30\x1e\x8b\x09\x62\xd4\x76\xcb\xa9\xc4\x64\xfd\x64\x68\x55\x95\x32\x92\x0c\x19\x5f\x51\x9c\xdb\x74\x3f\x00\x74\x8d\x9e\xc8\xeb\x90\x55\xbf\x26\x4f\x9b\xd8\xf5\xe4\x55\xf2\xd8\x6c\x66\xfe\xe1\x7e\x5a\x32\xfb\x7b\xc1\xaa\xfa\x75\xc1\x45\x4d\xde\x26\x8f\x84\xbc\x20\x7f\x25\x29\x7a\x70\x18\x62\xba\x9a\x40\x3c\x01\xf2\x2e\xc9\xf5\x7c\x3e\x67\x33\x5e\xd4\xcc\x57\xf7\xb5\xfc\x21\x92\xec\x9b\x24\x4b\x03\x71\xff\xf7\xe4\xe1\x0b\x79\xe1\x9f\x7c\x49\xbf\x3c\x2b\xc3\x47\x5f\xc2\x02\xfb\x30\xa5\xd4\xa9\x5c\x94\xb3\x4f\x9c\x95\x33\xf2\x5c\x5a\x8b\xf0\x1e\xa5\xfb\x72\xb2\x2f\x63\x47\xad\x15\xf9\x6e\x2d\x9d\x5f\xd8\xdf\xc7\x92\xf6\x46\xe4\xa9\xa4\x6f\x25\xc2\xe4\x91\xa4\x23\xb6\xfb\xe0\xa9\x9c\xbc\x95\x1d\x0e\x5e\x3a\xcf\xd6\x53\x19\x59\xe5\x7c\x80\x35\xc3\xf0\xc5\x7f\x49\x84\x2d\x82\xed\x3b\xe9\x24\x70\x7b\x7b\x86\x41\x7a\x1d\x52\xee\x99\x94\x37\x21\xe5\xae\x49\xf9\x3d\xa4\xdc\x31\x29\x5f\x42\xca\x6d\xcf\x53\x34\x91\x23\xb6\x13\x0c\xb1\x5b\xb2\x1d\x29\x7e\x63\x6f\xcf\x95\xf3\x4e\x9a\x92\xf7\xee\xb9\x94\xd7\x2e\xe5\xae\x4b\x79\xe3\x52\xee\xb8\x94\xdf\x5d\xca\x6d\x97\xf2\x45\xde\xb0\x3e\xef\x65\x43\xcf\x05\x15\x24\x4f\x4c\x72\xe4\xbb\x2c\x1b\x20\x85\x2e\xe7\x57\xf7\x20\xf2\xc7\x6d\xae\xee\x94\xd2\xef\x72\x82\xbe\x4b\x7a\xc0\x0e\x75\xdf\x7e\x95\xe8\x9d\x24\xbf\x49\x8c\xf3\xef\xd2\x39\xb5\x92\x97\x32\x72\xa9\x95\x28\x86\x20\x7d\x21\x9d\x2f\xe7\x0b\x39\x76\x83\xe3\x95\xfe\xce\xea\x37\x19\x5b\x32\xfe\x66\xdf\xeb\x3d\x96\x1e\xc3\xe4\xbb\xc4\x57\x7a\x20\x39\xcf\x55\x63\x7d\x69\x64\x30\xdf\xe5\xf8\xbd\x44\x7b\x7b\x49\x44\x16\x83\xd4\x15\x00\xc8\x98\x8b\x0a\x21\x68\x7d\xc0\x0e\xc7\x33\x79\x25\xa8\x40\xbd\xa1\x0f\x5e\xe6\xc2\xc7\xac\x56\x18\xbb\x41\x1c\x70\xc1\x4c\x17\xf8\xda\x64\x99\x26\xc5\x77\xef\x5f\xbe\x39\xc2\x40\xc7\x77\x92\xfc\x2d\x21\x94\xb0\x33\x08\xd5\xa3\x3f\xb6\x06\xfd\xa3\xd9\x0b\xa3\xe1\xdd\x9d\xbb\xbb\xa3\x7b\xdb\xa3\x2d\x34\xda\x44\x28\xba\x67\x9b\xf5\xbf\x46\x43\xfc\x2f\x24\xfe\x45\x47\x43\xbc\x1c\x62\xfc\x5f\x11\xa8\xc4\x2f\x32\xc4\xb0\x07\x60\x00\xe7\xb4\x28\xcf\x2a\x9c\x20\x3f\xd6\xc6\xbf\xaf\xc6\xc4\xf8\xba\x44\xf9\x42\x64\xe4\x03\x71\x98\x65\x48\xff\x00\xf0\x7e\xf0\xa9\x86\x75\xf5\x53\xc4\x60\x19\xef\xc7\x8f\xb6\x0f\x7f\xb5\xbf\x9f\x65\xc3\x46\x98\x71\x84\xaf\x3e\x4b\xfa\xab\xa4\x36\x6f\x14\x25\x98\x07\x21\xf2\x27\xbf\xd5\x8c\x2b\x89\x3e\x49\x4c\x2c\x83\xf6\xc5\x72\x68\x83\x2f\xf6\xb9\x0d\x23\x1f\x39\x55\xf2\x80\x73\x19\x83\xda\x38\x3e\x2f\x09\xd9\x6b\x99\x93\x27\xdf\xce\xb8\xd1\x33\xbe\xe3\x73\x76\xbf\xc6\x9d\xe9\xc1\x56\x04\x80\x23\x3a\xdf\x04\xb3\x96\xce\x77\xb1\x8f\xd0\xda\x43\x3f\x2c\x06\x5b\x65\xc3\x9a\xb2\x56\x11\x02\x4d\x14\x6d\xd5\xb6\xfc\xa3\xa4\x8c\x18\x22\x7b\xdb\x7e\x17\xdd\x39\xd6\x88\x62\x3f\x97\x1c\x58\x9d\xa1\x2d\x78\x3f\xb3\xe4\x93\x0f\x40\x3c\xff\xa6\x00\x3f\xe8\x34\x77\xd3\xa6\x5a\x72\x3f\x04\x3f\x4b\x63\x74\xd2\x1b\x99\x20\x50\x43\x90\x7c\xf2\x63\xd4\x46\xe9\xca\x32\x3f\xc8\x77\x4c\x70\x41\xf4\x59\x37\xa3\xa6\x21\x1d\x93\x9a\x5e\xd9\xfe\xcf\x19\x91\x47\x15\x53\xe7\x6c\xf6\x88\xd7\x55\x5e\x13\xa1\x53\xcd\x30\xb4\xab\xd3\xaf\x32\x0e\x0c\xfd\xb1\xc9\x33\xee\x0c\x35\xcf\xf8\xab\xa4\x35\xf9\x28\x13\xc2\xd0\xab\xb4\xf5\xf9\x90\xc4\x2d\xce\x6b\xa2\xdc\x01\xd0\x48\xde\x56\xc6\x3c\xe3\x57\x4d\x73\x80\xd8\xa2\x5e\x45\xcb\xd2\x91\x6a\xc2\xc5\xf2\x44\x27\x5c\x70\xe3\xed\x6f\xbc\xaf\xfe\x58\xb0\x05\xa3\x57\x47\x45\xc5\x8c\x40\xae\x11\x81\x1b\x10\x49\x21\x93\x13\xe7\x15\x8a\xcd\xf2\x2b\x5d\x7d\x2e\x4e\x2c\x09\x4c\x60\x71\x57\x3b\xff\xa9\xd2\x76\x8e\x1e\x0a\xd1\xe7\x48\x9d\x7c\xdc\x68\x5e\xea\xf5\x15\xf2\xd7\x51\x65\x4c\xaa\x29\xce\xd6\x89\x0d\xcc\x85\xaf\x0d\xb3\x01\xcf\xab\x55\x34\x60\x2a\x1e\x6f\x56\x88\xb5\xa8\xcf\x88\x09\x3a\x52\x31\xe3\xa9\x0e\xb1\x37\x4f\xf2\x21\x39\x2b\x2e\x4b\x59\xcc\x9c\x97\xb7\x61\xca\xcc\x5d\x18\x0d\xd8\x74\x48\x1c\x84\x95\x27\x51\x89\xdd\xd4\x88\x5a\x8b\xdd\x72\x61\x18\x6a\x68\x04\x1e\x58\x0a\x8f\xed\x80\x12\x93\xda\xf6\x75\x8e\xec\x95\x80\x1f\x22\x6c\xba\x9e\x2a\xf6\x25\x5a\x47\xbd\xb0\xe0\xe9\xd1\x33\x2c\x49\x61\x61\x28\x39\x84\x17\x76\x63\x19\x89\x76\x25\x03\xc1\x21\x20\xb2\xbf\xa3\xae\x5e\xc4\x5d\xe0\xb5\x35\x8c\xe0\x2c\x79\x2a\x96\xe4\xe9\xd7\xc6\x66\xd0\x1a\x9d\x32\x8f\xfa\xba\xa4\xdc\x52\xc8\x13\x28\x10\xb6\x8c\xa9\x5c\x98\x82\x2b\x5a\xc0\xd7\xc7\xe6\x87\x96\xa6\x4a\xe6\x87\x56\xab\x82\x96\xa4\x59\x64\xba\x90\x55\xcd\x30\xf5\xe1\x41\x95\x50\x28\xcb\x50\x15\xd1\xa5\x8c\x91\x63\x75\x75\x7c\x55\x4c\xb4\x4f\x1e\x0d\xec\x05\x1d\xba\x68\xc2\xc7\xd6\xe1\xc5\x9b\xf0\xd9\x02\xc2\x8e\x7a\x4a\x2b\x1b\xc9\x1b\x95\xf4\xb4\xb1\x76\xe2\xfb\x96\xa2\xf3\xd6\xc8\x6e\x66\x6d\x0e\xf4\xd3\x41\x9a\x00\xe3\xfe\x74\x50\x17\x27\x7e\xec\x9f\x0e\xec\x55\x98\x00\xa7\x03\x77\x19\x4d\x03\x37\x6a\xcf\x26\xe8\x98\x9e\xd1\x39\x99\xd1\x29\xce\xcf\xe8\x99\x21\xfb\x9c\x94\x0f\x16\x59\x86\x16\xb4\x34\x21\xeb\x5c\x50\xb4\xb3\x2c\x43\x3e\x57\xb3\xfe\x61\x6d\xfe\x0f\xd6\x1c\x13\x5e\xa1\x92\x34\x4b\xc4\x63\x96\x03\x59\xcf\x29\x23\x47\xf4\xd4\x49\xca\x4b\x5a\x93\x39\x15\xe4\x08\x6c\x30\xaf\x6c\xe4\x0a\xbd\xe9\xb4\x2c\xc5\xd0\x39\x3d\x72\x95\xd0\xa7\x08\x7a\x6e\xe4\x7b\x73\x32\x25\xce\x1e\x61\x83\xad\xa6\xf4\x3c\xf1\x98\xdd\xd8\xc9\xcf\xed\x42\xf6\xae\x38\xa1\x5b\xbb\xc3\xbd\xbb\x59\x94\xb2\xbc\xb3\x3b\xb6\x31\x35\xfc\xf6\x83\xca\x0e\x4b\xb5\xe4\xfb\x93\xe4\xe3\xf9\x39\xf6\x16\x81\x53\xc3\xaa\xc5\x75\x32\xe5\x6f\xe7\x7a\x52\x0e\x57\x2e\x60\x5c\x20\x9f\xd9\xc2\x7d\x85\xe8\xce\xb6\x5f\x48\xf4\x7c\xb5\xab\x30\x9e\xf8\x4b\x7a\x70\x7a\x98\x5b\xfb\xc0\xd3\x30\x4d\xf4\x0b\xa7\xf4\x14\x7a\x1f\x2f\x97\xa7\x54\x8f\xfa\x68\x53\x45\xed\xd9\xef\x58\x97\x53\xda\x3d\xbf\x49\xb4\x78\xd0\xb5\x73\xdd\xb4\x09\xc6\xeb\x8c\x4e\x73\x3b\xfe\x8e\x49\x34\x45\xe9\x2c\x29\xea\x8c\x14\x15\x5a\xe8\x25\x37\x1d\xa4\x74\x41\x1a\x3b\x28\x9d\x46\x6b\xf1\x31\x8f\x84\x4c\x7a\xe9\xb4\x04\x21\xfe\x2a\x59\x7b\x18\x0e\xb8\x63\x2c\xc6\x1d\xf3\xf1\xa9\xea\x43\x22\xa9\xf2\x3d\x11\xad\x17\xd2\x02\xee\xba\x47\xa6\x60\x45\x25\x91\x54\x74\x0a\x9b\x9a\x82\x93\xbd\x11\x51\x18\x8f\xad\x81\x9e\xc4\x2b\x83\xde\x7c\xc6\xe9\xa7\xc1\x1b\x40\x9c\x34\xac\xc6\xa3\x00\xeb\x42\x4e\x39\x45\x82\x5d\x6c\xa8\x01\x20\x04\x08\x26\x6a\x3c\x50\xec\xb8\x8a\x44\xa1\xd1\xd2\x2f\xac\x05\x2f\xd2\x67\x21\x45\x6a\xda\x20\x1e\xc6\x93\x3a\x37\x47\x07\x02\x31\x1c\x53\xd2\x0a\x02\x86\x1b\x8d\x3e\x80\xe1\x18\x2d\xcc\x51\x27\x5a\x19\xcd\x39\xa7\x57\xbc\x7a\x29\x17\x00\xad\xd2\x56\xe8\x81\xf3\x0c\x1b\xa4\x12\xa2\xa7\xfc\x88\x29\x9c\x65\x9f\x19\x62\x58\x7f\x76\x45\x98\xf8\x5b\x7f\xe0\x2d\xab\x0d\xaf\x92\xaa\xdc\x0c\xe3\xd3\x51\x88\x95\xed\xfe\x5d\x22\x4c\x24\x3d\xe3\x7e\xad\x19\x23\x49\x2b\x8e\x14\xfd\xad\x44\x8a\x30\x22\x31\x91\x18\xbb\x49\xeb\x4f\x04\x22\xcb\x90\x8c\x3a\x16\x13\x60\x32\x24\x26\x7f\x94\x10\x11\xc4\xd7\xec\x0d\x3b\x2b\x8b\x29\xfb\x8f\xd6\xae\x2e\x4e\xe8\x88\xc8\x7f\xa3\x96\x4f\xa5\x9a\xda\x78\x6e\x0d\xd3\xd9\xeb\xaa\x28\x4c\x15\x55\x5a\x45\xa5\xab\x28\x74\x15\x05\x81\xe0\x28\xca\x56\x71\xdb\x56\xab\x86\x10\x87\xbe\x5a\xb5\xad\x96\xb2\xd5\x12\x89\x3f\xeb\x11\x8f\x8d\xe9\x48\xe1\x35\xbe\xed\xd5\xb5\x21\x06\x37\x02\x2b\x3f\x09\x4c\xeb\x26\xac\x3b\x1d\x29\x28\x3c\xef\x45\x56\xbf\xcb\x65\x7c\x37\xe0\xd5\xeb\x85\x62\x66\xda\xb9\x97\x97\x4b\xd4\x7b\x6a\xec\xbf\x96\x4b\x7d\x25\x09\x8f\x31\xfb\x4e\x78\x2a\x2a\xed\x8d\x88\xa4\x0b\x49\xb8\xc3\xa6\x36\xa2\xe7\x71\x37\x2c\xc5\x06\xf7\x27\x36\x3e\xe1\x54\x72\xc4\x71\x8e\x24\x3d\x97\xa8\xc6\x93\x53\x99\x47\xb2\x5b\x4e\x91\xb1\x9b\xeb\xe9\x8b\xa4\xf4\x0a\xe3\x09\x68\x3f\x24\xce\x17\x52\x9f\xaa\x00\x3c\x08\x09\xc2\x3b\x26\xb4\xc3\x92\x05\x4a\x66\x99\x97\xf3\xd9\x94\x89\xfd\xb5\x86\x0a\x76\x96\x2b\x7a\xce\x49\x44\x7d\xe0\x45\x3b\x86\x0d\x65\xe4\xdf\xd5\x58\xc8\x9f\xd5\x58\x44\xb2\x82\xcb\x68\xe1\x63\xae\x45\xa4\xc3\xa4\xbc\x36\x40\x12\x02\x9c\x48\xca\xf2\x0d\x9b\x32\x7e\xce\x40\x5c\x92\x65\xd7\x3c\x84\x91\xd0\x5d\xe0\xfb\xdf\xdf\x3e\x7c\xfa\xe4\xcb\xb5\xe5\xfe\x28\x8f\x29\xde\xd6\xdb\x9c\xb5\xcf\xf9\xa0\x63\xa5\x41\xb5\xcb\x45\x1a\xc7\xf5\x6f\xbc\x69\x8d\x10\x8b\xef\x25\x78\x43\x57\x54\x10\x1b\xdd\xa7\xb9\x1d\x10\x09\x7b\x09\x3d\xe5\x04\x8e\xb1\x16\x8d\x2a\x1d\xcd\xd7\x8e\x63\xe9\xb2\xba\xf1\xcc\x3b\xc7\x73\xc8\x06\x43\x97\x63\x4c\xe0\xc4\x22\x88\x04\xf8\x8d\x35\xd5\xeb\x58\x16\x74\xed\x4e\x58\xfd\x98\x29\x7e\x6e\xb3\x3d\x55\x72\x6e\x64\x5f\x59\x86\xec\x6e\xc8\xf5\xf6\xb6\xa6\xd8\x35\x5d\xba\xae\xd4\xe5\xb2\x23\xbb\x04\x43\x68\x51\x9c\x55\xa7\xb2\x36\xda\x55\xb3\xf4\xc4\xb9\x7b\x21\x77\xd7\x58\x80\xfd\xb2\x13\x90\x56\x0e\xda\x19\x97\x4b\x54\x53\xb9\x7e\x90\x77\xbd\x93\x65\x5d\xa9\xa8\x93\x00\xd7\xd6\xf1\x9a\x87\x08\x93\x5a\xb3\x47\x7e\x89\xe9\x1e\xc2\xd2\xf5\x86\x19\xc2\x37\xe9\xfe\x35\xd5\xf4\x55\x78\xcc\x67\xb6\x7a\x29\xbb\xbc\x6b\x98\x92\x0b\x6e\xad\x00\x78\x05\xbf\x61\x17\x7a\x12\x73\x8d\x41\x88\x20\xf4\x64\xc0\x9d\x1d\xd2\x15\xa2\xc4\x86\x74\x18\x7c\x01\xbb\x6e\x8b\xbb\x9d\xdc\x8e\x7a\x36\x02\x78\x4b\x76\xb5\xe7\x62\xd7\xd0\x28\x36\xf8\xaa\x53\xdd\xb6\x7b\x97\x38\xb7\x78\x49\xfb\xfd\x4d\xd6\x08\xc8\x5d\x47\x68\xf7\x8a\x1d\xc7\xd5\x8f\x06\x37\x3c\x81\x9f\xc1\x17\x83\xc0\xff\x86\x1d\x1b\xc8\x10\x9d\x98\x23\x54\x27\x98\x77\x46\x90\xab\x0c\xab\x59\x53\x4a\x4f\x39\x38\xc4\x99\x14\x7a\xb5\xf2\xb2\x0d\x36\x31\x31\xbd\x37\xea\x03\x79\x98\x43\xd4\x54\xb6\xc2\xf1\x57\x24\xa9\xe1\x48\xe2\xa0\xff\x23\x0a\x36\x70\x38\xef\xed\x9a\x38\x34\x3d\x4f\xc6\x46\x86\xbd\x21\x58\xef\xb7\x41\x28\xbe\x06\xa9\x50\xc0\xfe\xe8\xf9\x48\x0b\x0d\xfa\x8f\x48\xff\xc0\x74\xa7\x75\x37\x3a\xec\x53\xda\xf6\x13\x72\x08\xf3\xd6\xa3\x06\x4f\xec\x18\xd8\xb8\xe0\xf5\xe9\xc6\x57\x76\x59\x6d\x5c\xf5\x37\x53\xdf\x9d\xc1\x5f\x92\x0b\xd4\x27\x1b\x7d\xbc\xd9\x5f\xf5\xf3\xda\x44\xac\xf1\x15\x7d\xc5\x9b\x41\xb2\xdd\xe9\xc5\x71\x15\xf5\xa0\x2c\xaa\xfa\x09\x8c\x67\x27\x52\x52\x13\x1b\xea\xc3\x24\x53\x41\xe2\x5c\x54\xe0\xbc\x36\x62\x5d\x9b\x90\x3e\xb5\x02\x23\x77\x0b\x07\xa2\xe8\x08\x7c\x6f\x15\x83\x1a\x3a\xc8\xd6\x5e\x0a\x9e\x1d\x0b\xe4\xd5\x18\xd7\x66\xf3\x52\x54\x0d\x2a\x1b\x3c\xa0\x13\x3f\x5e\x05\x71\x3e\x73\xc1\xa8\x7d\x0c\xf5\x31\xf6\x23\xf7\x2b\xbb\x34\x31\xa6\x0c\xae\x18\xa9\x71\xee\x6e\x01\x70\x8c\x00\xce\x74\xdd\xfc\x58\x34\x02\x64\x43\x0e\xf9\xb6\x32\x1e\x5c\xe6\x7d\x3a\xd4\x0c\x8d\x79\xd9\x50\x20\x7a\x95\x23\xbb\x85\x7a\x50\x00\xf3\x8e\x22\x06\xbf\x47\x2f\x0f\x2a\xc6\xe8\xc7\x13\xa4\x5b\x0e\xb9\xf0\x7d\x31\x41\x75\x44\xce\x6d\x22\x70\xae\xf2\x76\x5a\xa4\xe9\x29\xe3\xd0\x34\x3e\xfc\x77\xf4\x05\x90\xe0\x46\xef\x27\x8c\x4f\xc0\xbb\x6d\x68\xf4\xea\xe5\xd2\x3b\xb0\x02\xa2\xd0\x6b\xcd\x64\xb0\xc1\x5c\xce\x18\x30\xee\x26\x3b\xd5\x84\x81\x29\x2f\x91\x41\x8d\x89\xd2\x23\x49\x6b\xe7\x67\xcc\x92\x53\x0f\x98\xb1\x16\xd5\x2c\x02\x18\x17\x82\xd9\x2c\x42\xca\x94\x69\x38\x0f\x28\xf9\x98\xfa\xf5\x96\xa8\xf0\x25\xa5\x6b\xa0\xe8\x5f\x15\x32\xef\x12\x80\x14\x73\x6f\x5a\x88\xeb\xb8\xe6\x6b\xcb\x89\x44\xb3\xd7\x10\x66\xd7\x11\x66\xb9\xac\xd7\xc5\x3a\x34\xc1\x15\xe2\x94\x34\x33\x58\x67\xe9\x56\xc3\xd9\x18\x72\xa7\x49\x40\xf4\x37\x37\x21\x7a\x14\xd8\xe4\xe0\x70\x5d\x0f\xcc\xfc\x69\x89\x77\xb4\xe8\x6e\xd2\xd5\xef\xe2\xaf\x6a\x0e\xeb\xe7\x3a\xfb\x38\xda\x12\xdb\x31\x5a\xba\x42\xb3\xd8\xe5\xc1\x8c\xb2\x7e\x7f\xb3\x76\x5f\x4f\xbf\x11\x7b\x4e\xc6\x1a\x23\x37\x96\x42\x28\xd3\xc1\xad\x5b\xe6\xb1\x95\xfb\x31\xe6\xa0\xcd\x84\x1e\x26\xb5\x19\x26\x76\x7d\xe8\x18\x26\x22\x1e\x26\xe6\x40\x83\x89\x08\x55\x11\x46\xee\x56\xfb\x62\xa1\xab\xd6\x54\x5b\x6f\x58\x17\x1c\x2c\x7a\xe6\x0c\x79\x4f\x50\x43\xe8\xf0\x8e\xe1\x66\x92\xf6\xda\xad\xa8\x3b\x9e\xc6\x59\x93\x5f\x77\x64\x98\x40\xbb\x72\x27\xa4\x6e\x75\x41\x57\x74\x9c\xc4\x94\x57\xf3\x60\xc6\x51\xce\x2c\x0f\xfd\xfe\xa6\x8b\x46\xdb\x22\x7f\x14\x4b\xc4\x93\x5f\xac\x25\xff\x06\xcc\x4c\x60\x16\xcc\x54\xd5\xf3\x9d\x4d\xec\xe0\x34\x1d\xe1\x87\x33\x40\x0f\xe4\x61\xe9\xc8\xbd\x99\x71\x20\x7c\x54\xe0\x22\xcd\x68\xa9\x2e\x0c\xd5\x53\xd3\xe8\xd0\xc0\x30\x2d\x80\xfa\x86\xe2\x62\x0d\xc5\x4f\x63\x48\x84\x2e\xca\xaa\x0e\xca\xfa\x88\x1d\xd0\xd7\xc6\x04\x15\x6a\x05\xc3\xaa\xdf\xdf\xd4\xa5\x75\x92\x56\x79\xd2\x2a\x4f\x5a\xb5\x9e\xb4\xbe\x6c\x33\xab\x15\x6c\x84\x22\x87\x5f\xf7\x39\x95\x10\xbd\x26\x8c\xa8\x26\xd1\x25\x31\x6f\xe4\x53\xf3\x5c\xd7\xae\x41\xf3\x45\xdc\x92\xeb\xbe\x46\x24\xb6\xdd\xa0\x4c\x37\xa8\x80\x1a\xd4\x45\x0d\x45\x64\xe8\x87\xba\xe9\x3b\x17\xc4\x94\x48\x92\x82\x94\x24\x0a\x2d\x31\x35\x3b\xf1\xc2\xfc\xcc\x68\x41\xe6\xb4\xa0\x43\x72\x6e\x74\x35\x96\x8e\xb3\x2c\x9b\x07\xd7\xf4\xf9\xe6\x26\xbe\x9a\x99\xad\xf7\xc1\x7c\x82\xce\xe9\xcc\x6a\x7a\x70\x7e\x4e\x67\x9e\x41\x00\xd0\x31\x7a\x86\x24\x99\x91\xf2\x60\x7e\x48\xaa\x38\xda\xee\x11\xbe\xb2\x57\xb3\x2c\x43\x33\x7a\x6e\x65\xf3\x2b\x96\x65\x33\xbf\x1f\x1f\xc5\xfb\x71\xad\x8b\xc2\xa4\xa0\x1c\x1d\x91\x82\xcc\x3d\x03\xbc\x98\x4c\xe9\x51\xbe\xf0\xdc\xc5\x11\x59\xd0\x23\x32\xa3\xe7\x9a\x8a\x73\x4a\x69\xd9\x00\x21\x14\xa6\xa4\x69\x54\x9f\x99\xb5\x78\x68\x34\xd4\x31\x1f\x33\x7a\x8c\xa4\x6b\x86\x01\x77\xe2\x68\xd6\xaa\xc5\x2c\xaa\xc5\x8c\x2c\xe8\xcc\x9b\x7c\x4c\x57\xba\xf8\x19\x55\xf0\xe9\x75\x9f\x39\xa7\xa7\x68\x46\x24\x99\xc7\x9f\x62\x7e\x40\x9f\xc7\xe4\x98\x0d\x0c\xdb\xef\x5a\x70\x0e\x63\x69\x9e\xc3\xaf\x21\xd3\x79\xab\x82\xe7\x51\x05\xcf\xc9\x42\x93\x3d\x30\x41\xb3\xee\xc8\xa1\x8e\x27\x43\x92\x30\xbc\xc2\x98\x4c\x23\x9b\x55\x4d\x15\x52\x91\xa9\x59\x4d\x17\x74\xce\x90\xe9\xe7\x8e\x33\xdc\xa2\x79\xb2\xba\x3d\xc4\xd1\x98\x40\x15\x5d\x18\x46\xbf\xc2\x2d\xf3\xce\xdb\x23\x1c\xa2\xd3\xcf\xa8\x1d\xb1\x73\x5a\x92\x73\x5a\xd2\x21\x39\x32\x09\x27\xb4\x02\x4e\x1b\xf9\x80\x4c\xf3\x2c\xeb\x9d\x0c\x66\x52\xb0\xf1\xf9\xe6\x66\x94\x01\x5f\xcd\xed\x20\x3e\x9f\xa0\x23\x3a\x27\x73\x3b\x88\x8f\xe8\x3c\x19\xc4\x97\x30\x88\xe7\xe4\xc4\x60\x69\x91\x69\x3c\x8e\x2f\xfd\x38\x9e\x67\x19\x9a\xd3\xa3\x68\x1c\xcf\xfd\x38\xbe\x6c\x8e\xe3\x39\x26\x25\xe5\xe8\x92\x94\xe4\xdc\x77\xd0\x6c\xb2\xa0\x97\xb9\x9f\x41\xf4\x92\xcc\xe8\x25\x99\xd3\x23\x3d\x8e\x4d\x1b\xe2\x11\x3c\xc7\x64\x11\xd5\x64\x6e\x47\xf0\x9a\xd6\xba\x21\x76\x02\x23\x39\xb4\x45\x8f\x30\x5d\x95\x93\x56\x55\x4e\xa2\xaa\x9c\x90\x19\x0d\xa1\x99\x16\x30\x98\xe7\x30\x98\xe7\xf8\xc7\x5f\x3c\x45\x73\x22\xc9\x79\xe3\xab\x61\x5c\x9f\xc4\xe4\x99\x37\xc6\xf5\x09\x8c\xeb\xf3\xfc\xc4\x8c\xeb\x9f\xad\x2b\x94\x78\xb3\x71\xbd\x70\xeb\x66\x24\xb2\x57\x84\xeb\x05\xd3\x2c\x96\xd7\x09\xe2\xb2\x8c\x87\x5d\xc2\x77\x3c\x07\x17\xc6\x29\x60\xf4\xf2\xc6\x9e\x61\x44\x0a\x8b\x6b\x4b\xd5\xfd\xbb\xc0\x76\x17\xe3\x1d\xbb\x98\x05\x62\x59\x98\x2f\x91\x29\x55\x6e\xe0\x4f\x8d\x46\x7d\x6a\x77\xfa\x85\xdf\x0c\xa7\x91\x66\xf7\x6e\xce\x8f\x51\x54\x71\x7c\xa5\x1b\x3d\x75\xb4\xc4\x04\x8e\x13\x53\xd2\xaa\x7b\xc4\x70\x31\xea\xe3\x3f\xad\x52\x4f\x12\xf8\x7c\x7a\x4c\x31\xdf\xfa\xc1\x67\x62\x36\x72\x4a\x78\x72\xda\x88\xbf\xb6\x82\x52\xdc\x8c\xab\xe1\x86\x4c\xa9\x2f\x77\x15\xb5\x0c\x4e\x46\xef\x2a\xd4\x6c\x89\x63\x24\x2b\x02\x14\x6c\x34\x4c\xf3\xed\x95\xe6\x7d\x4d\x49\x26\x8f\xab\x66\xc2\xfb\x26\x95\xd6\xec\x3f\xe0\xf0\xf8\x92\x2a\xbf\x29\x97\x88\x05\xd6\xc0\xe3\xe8\x98\x81\x12\x8e\xf7\x46\xd5\x69\xba\x6e\x0a\x77\xbb\xc0\x2f\x00\x46\x92\x5a\x77\x7c\x02\x02\x27\x29\x69\xe6\xf4\xac\x04\xb9\xd3\x24\xd3\x31\xaa\xd1\x31\x8a\xf0\xb5\xa7\xa5\xb8\x3f\xe0\xdd\xb8\x3b\x52\x01\xc5\x0a\x29\xcd\xef\xf3\x94\x64\xa1\x9c\x98\x42\xab\x2e\x46\x91\x77\x30\x8a\x1e\x63\x81\xd3\x7e\x7f\x93\x3b\x45\xb3\xca\xb2\x3b\x8e\x60\x13\xb4\xa6\x51\xed\xce\x36\x2d\xd0\x19\x5e\x5f\x53\x51\x4c\x4a\xeb\x6d\x78\xc1\x11\xf7\xcc\xd9\xdc\xaf\x17\xfa\xd1\x9c\x45\x8f\xce\x93\x47\x8b\x2c\x03\x8e\x99\x63\xe2\x8d\x45\x79\x96\xf5\xa6\x6e\xa6\xb3\xc4\xf8\xc2\x1a\x43\x98\x1d\xd1\x45\x69\x23\x61\x6b\xdc\x26\x26\x84\x55\x59\x80\x8b\xe9\x72\xc9\x06\x02\x7e\xfb\x5e\xb1\xd6\x0f\x32\x3b\xd3\x44\xa3\xf9\xde\xe7\xf4\x15\x47\xbd\x21\x26\x6f\xcd\xd5\x08\x93\xbf\x38\xbd\x5a\x91\x77\x3c\x18\xa7\xfe\xc5\x57\xe4\x75\xe3\xfe\x4d\x7a\x1f\x44\xbb\xbf\x73\xe7\xd5\x4d\x29\xfd\x8b\x37\xf7\xf1\xbb\xbb\xc1\x27\x23\x12\x00\x7d\xe1\x09\x70\xf7\x54\xa2\x37\xdc\x7a\xa2\xbc\xe6\xd6\x85\xe2\x1d\x27\x7f\x71\x4c\x58\x04\xed\xe2\x4c\xc5\x0d\x89\x46\xa3\x1c\x40\xc8\x02\x12\x8c\x47\x1e\xab\x81\x22\x80\x0a\xf0\xfe\xcd\xf3\xfc\x85\xd9\x5d\x48\xbf\x8f\x1b\xce\x6f\x35\x7d\xc1\x50\x4d\x11\xa3\x10\x77\x73\x52\x47\x01\x1a\xf3\x1a\x27\xc5\x58\xf6\x9b\x99\xc0\x85\x00\x1d\xba\xaa\x74\x3d\x5d\x75\xe3\xe3\xfd\x4b\x6e\x3c\x75\xf4\xd3\x4a\xb7\x0a\x7e\xde\xf0\x38\x4c\x1c\x90\xee\x77\x8e\xde\x70\xef\x29\x62\x51\x0c\x7f\xe7\xe8\x5d\x48\x24\x02\x6a\x69\x8d\x7a\xf1\xb8\x36\x36\x6f\xa8\x41\x2d\x11\x8b\x3d\x9f\x43\xe1\xaf\x7d\x21\xd6\x4a\x31\xa9\x91\x11\xdb\x7f\x8f\xba\x36\x46\x89\x7e\xc1\x5d\x48\x54\x8b\x54\x11\x89\x11\x41\xd8\x0e\x30\x14\x66\xe4\x3a\x7c\x9e\x44\x9d\x10\xd9\x72\x88\x08\x28\x4b\x40\x58\xac\xd3\xcb\x99\x2a\x6a\x88\x4c\xe8\xc2\x97\x02\xd4\x76\x88\x6b\x6a\xb0\xd5\x83\xe7\xbf\x03\x79\x1d\xed\x51\x8f\x1e\x17\xa9\x57\xdd\xa7\x41\x85\x34\x50\xec\x9c\x15\xe5\x2b\x35\xb3\x9a\x81\x61\x0f\x40\xb1\x22\x11\x5f\x47\xc9\x22\x09\x04\x8a\xaf\xec\x85\x5b\x0b\x6a\x10\x88\x42\xd2\x58\x2f\xba\x5c\x2c\x40\x85\x00\xb4\xb5\x66\x3c\x5e\x6a\x0b\x75\x74\x6c\x65\x6c\x03\x54\xdb\xe2\x96\x4b\x77\x15\x23\xfb\xc2\x39\xac\xf6\x99\x56\xbe\x0c\x5f\x09\x7b\x11\x0b\x67\xbb\xcf\x7f\x8f\x13\x43\xd1\x2b\x6f\x83\x9b\x33\x02\xdb\x59\x5e\x9b\x45\xe1\x69\xd3\x1c\xe6\xb1\x8d\x40\xc4\x14\x79\x74\x9d\xa9\xcc\x07\x4e\x87\xe4\x16\xb7\x40\x4e\xf6\xf7\x99\xfd\xfd\x33\x35\xda\xfd\x9b\x23\xe7\x02\xe0\x95\x02\xdb\xa3\x78\xc0\xfe\x96\x9a\x99\xa6\x28\x5e\x21\xc2\xeb\x70\x2c\xbc\x4f\x42\x96\x89\x60\x55\x24\x2c\x56\x8f\x41\xdf\x02\xe8\xad\x4e\xdc\xad\xe0\x47\xc0\x5b\x38\x43\x1f\x38\xe5\xba\x45\x35\xa9\x3b\xf4\xfa\x0d\xb3\x5f\x9b\xd4\x30\x9f\x1a\x92\xa7\x61\xce\x09\x0f\x56\xe6\xae\xd2\x62\x27\x47\x45\x7e\x52\x10\x06\xb6\x43\x12\xb7\x4b\xa3\xf4\x83\xae\x19\x1d\x8e\x67\xd2\xa0\xed\xb7\x3e\xd7\x43\xdb\xb7\x1f\xf0\xe6\x01\x6a\x07\xdc\x72\xf9\x26\x1d\xe9\x1e\x71\x9d\xd3\xd1\x80\xa8\xb6\x97\xa1\x26\xd6\xa5\xa3\xbb\x3e\x7a\xcc\x47\xaf\x9d\x17\xa4\x76\x52\xb5\xf7\x81\x97\x7d\xcf\x8d\x21\x1b\x8c\x12\x53\x87\x5b\xf1\xd8\x20\x75\xab\xca\xc3\xce\xbd\xe2\x17\xee\x63\x98\x5d\x25\xd4\xb3\xa8\xb9\xde\xd0\xda\xdf\x46\xe6\xde\x7f\x87\xcb\xc8\xb8\x33\x95\xec\x3e\xe3\x93\x5b\xbc\xd1\xdf\xcf\x38\x65\xf9\x33\x4e\x9f\x71\x6b\x0f\x4d\x9e\x45\xae\x9b\x9f\x38\x8a\x07\xea\x7b\xee\x2a\x78\x8b\x47\xd6\xc9\xde\x1a\x84\x4d\x1a\xfd\x6e\xaa\x01\xcb\x0e\x73\x84\xb2\x2b\xff\xfa\x3a\xe5\xb6\x2e\xd1\xb2\x5a\xe3\x67\x7a\xb0\xbe\xe7\x94\x79\x77\x09\x37\xd2\x5a\x2a\x38\x4d\xdd\x16\x09\x91\x7e\x17\x77\x98\xcc\x9b\xc7\xef\x79\xa7\xf1\xfa\xfb\xd8\xa2\xf9\x6f\x97\x04\x17\x1d\x8e\x05\x37\xa2\xaf\x5b\xc3\x62\x32\x7f\x4c\x56\xb0\x2e\x35\xeb\xa4\x46\x0c\xe7\x91\xa6\xe6\xd7\xd8\x2b\x86\x23\xbd\x75\xd6\xa6\x62\xd1\xd1\xb9\x19\x7e\x76\x67\xa4\x27\x8b\x00\x15\xde\x1b\xa6\xd7\x48\x36\x7b\xc3\x66\x8b\x29\x53\x94\x59\xb5\xf1\x7b\x0e\x76\x8a\xa1\xd9\x9c\x8a\x0e\x0b\x6e\x1e\xab\xb8\xad\xbf\x56\x49\x8d\x97\xc3\xd8\x3a\x3b\xd8\x89\x61\x5b\x5e\xae\xa2\x52\xa9\x34\xf1\xc6\x62\xd3\xce\xa4\x3c\x69\xcb\x02\x3e\xdb\x77\xcd\xd8\x18\x8a\x97\xd4\x4e\xb0\x29\x95\x7a\xc1\x30\xe7\xcd\x69\x63\x12\x03\x47\x7a\xff\x83\x1d\xb3\xb3\x96\x95\x72\x33\x7f\xd3\x56\x79\xda\xb4\x55\x2e\x80\xf4\xf9\x74\x60\x2e\x08\x2b\x4e\x98\xb2\xf4\xd3\xa5\x45\xb7\xe6\x99\x19\x5b\xf6\x89\x19\x5b\x6d\xcb\xeb\x6a\x82\x4a\x5a\xd1\x19\xe1\x9a\x53\xaf\xac\x9c\x81\xce\xc8\xe2\xc1\x2d\xde\x36\x98\x6c\xa5\xd1\x85\xb1\x70\x35\xf6\xd9\x4e\xa0\x5d\x69\x1e\xc8\x95\x75\x73\xfb\xec\xff\x70\x9b\xc1\x66\x7b\x41\x9a\x5f\xd1\xa7\xa9\xb4\x2c\xe0\x51\xe3\x42\x72\x86\x14\x71\xb5\xc0\x63\x7d\x18\xd6\xc5\xa6\x2e\x79\xd3\x2c\x9b\xc2\xf0\x09\xb4\xe5\x54\xe5\x96\x0a\x25\x79\xa1\x90\x6a\xee\x78\x78\xb9\x74\x0e\x4c\xcd\xbd\x50\xe7\x0d\x16\xa9\xdc\xde\x99\xf1\x5b\x91\x74\x1a\xd9\x37\xec\xec\x3e\x68\x14\x15\x05\x09\x8e\xc0\xdd\x3f\xff\xa7\xe6\x70\xf8\x1a\x91\x61\xf6\x82\x35\xe1\x3a\xc6\x55\xe2\xab\x74\x3a\xda\x40\x02\x6e\x1e\xc2\xbe\x4c\x19\xe2\xa4\x74\x9d\x40\x4a\x6b\xc3\x6d\x3b\xc1\x14\x33\x7e\xa1\x10\xff\x09\x22\x73\xe2\xf9\x45\x4f\x5d\xd0\x51\x47\x94\xc7\x9d\xc4\xe6\x8e\xd8\x9c\xa8\x88\xaa\xac\x08\x54\xd5\x5b\xea\x78\xed\xaa\x6a\x70\xe4\x18\xc2\xed\x5a\xc5\x9f\xd7\x47\x63\x30\x01\xff\xdb\x78\x3c\xc5\x1e\x55\xc4\x11\xda\xe2\x5a\xb5\x3b\x25\xff\xc8\x49\xab\xee\x39\x5b\x61\xdf\x47\x74\x5e\x0c\x8e\xb8\x98\x99\xa3\xdb\x2d\x38\xe8\xb4\x06\x50\x1c\x14\xa0\x2e\x5a\xda\x68\x46\xaf\xea\xe2\x24\x67\x64\xaa\x98\x2e\xbf\x36\xd1\x43\xe5\x65\x2e\xc8\x8c\x9d\x55\xb9\xea\xd8\xaf\x50\xad\xf7\xf1\xd8\x31\x66\x82\x6a\x7a\x15\x0c\x3c\x6c\xf6\x34\x13\x30\x8d\x91\x11\x08\xb3\x1b\x1b\xce\xc3\xc1\x27\xce\xa0\x0f\xa9\x1d\xd9\x73\xa4\x1a\x5e\x47\x8c\xd8\x67\xaa\xf1\x01\x1c\x9b\x54\x88\x22\xf8\x80\xeb\x49\x93\xd2\x29\xb2\x0f\x29\x9a\x0a\x51\x18\x0c\xb7\x78\x6c\xd0\xc5\x88\x6c\x76\x7c\x81\x46\x4b\xfd\x9a\x39\x71\x05\x39\x86\x8d\x79\x15\x1b\x05\xc8\xd6\x27\x3e\xc1\x78\xa3\xad\x97\xac\xf9\xa3\x49\x8f\x66\x9d\x63\xa8\x0a\xcd\x1c\xb5\xa6\x26\xa7\xc5\xc0\x76\x63\x24\xfe\xf9\x8d\x23\x45\xf4\x83\xb3\x2a\x88\x61\x74\xc1\x75\x01\xd6\x26\x9c\x28\xbc\xba\x71\x33\x79\x62\xe6\xc0\x8b\xc4\x41\x5c\x15\xe8\xf6\xe8\x0e\xd9\x25\xa9\x46\xbf\x48\x73\xc9\xee\x5c\x65\x2b\xd7\x2e\xd9\x6e\xe4\xa9\x8a\x1f\x32\x3e\x66\x86\x12\xcd\xff\x90\xc8\x6b\xdb\xa8\x1a\xf1\x0a\xdb\xb0\x6a\x21\xa3\xe7\xd7\x59\x92\x3f\x39\xab\xac\xb0\x0d\xef\x1d\xd9\xa4\x14\x0d\x6f\x6b\x61\xb9\x5a\x31\x01\xcb\x8d\x69\x51\xa3\x03\x76\x68\xbe\x47\x6c\x6b\xaa\x78\xe6\xd6\xe0\x97\x17\x1b\x8d\x14\x49\x58\xd7\x59\x4a\x90\x5f\x5a\x83\x97\x1e\xb0\x30\xde\x6a\x1b\xe0\xeb\x30\x1e\xfc\xc7\x45\xec\x21\x08\xa3\xad\xa6\xad\x57\xfc\x2e\x90\x8e\xa8\x54\xe3\x1d\xd4\xc9\x35\x8c\xa9\x9a\xa8\x83\xd1\x21\x9e\xa8\x83\xe1\x61\x8e\x44\xbb\x66\xba\x26\x51\xe3\xce\xfe\xb7\xaa\x62\xba\xf5\xc7\x15\x3a\x2d\x52\x1b\xfa\x0f\x12\x61\x70\xf7\xbf\xf7\x40\x4d\xf6\xee\xe5\x2a\xf1\xfa\x67\xe0\xd0\x8f\x31\xd1\x39\xee\xde\x57\x93\xbd\xbb\x8d\x1c\xa6\x94\x47\x91\xcb\x42\x74\xdd\xd1\xd2\x5a\x5d\xea\x52\x47\x98\x08\x14\xc2\x3c\xc5\xef\xa8\xd5\x2a\x16\x04\xcc\x1b\x15\xee\xf0\xe3\x90\x2d\xd6\x2d\xf6\xe9\x68\xf0\x6e\xd2\x31\x6b\x0d\x2e\xcd\x08\xf3\x02\x27\xd5\x3c\x24\x3a\xd3\xec\x06\x93\x4f\x8d\x15\x36\xac\xc8\x32\x47\x9d\x3c\x3d\x1c\xe5\x1d\xd7\x20\x09\x8f\x1d\x30\x89\x3e\x45\xdf\xe2\x46\x24\x60\x35\x49\x90\x82\xf5\xb1\x78\x48\x64\x93\x8f\xfd\xc0\x49\x9b\xb9\xfd\xc0\xfd\x81\x6f\x8d\x07\x91\xab\xeb\x72\x09\xe2\xe6\xa6\x97\xa5\x1f\x5b\x60\xe0\xdd\xb1\x41\x63\xec\x00\x22\x8a\x46\x06\xb3\xf3\x96\x94\xa3\x82\x08\x10\x70\xcb\x94\x49\xe5\x44\x46\x2c\xaa\x61\x30\x4b\xe2\x65\x79\x16\x13\x82\xe1\xab\x95\x73\xa4\x31\x4e\x4d\x05\xbd\x52\xac\x70\xce\x07\xb9\xe4\x64\x51\x79\x68\x99\xfc\x6f\x73\x6b\x1f\x9a\x3b\xbb\x1f\x9b\x9b\xe7\xf3\x33\xa6\x20\xe2\xd0\xb3\x42\xcc\x4a\x66\x93\x5f\x14\x97\x72\x51\x27\x39\x5f\xb2\xb9\xb4\x97\x6e\x34\xb8\xbb\x63\x7b\x65\xc6\x84\xb9\x7e\xcc\x8e\x16\x27\xe0\x8f\xee\xb3\x39\x59\x9a\xcb\x70\xcc\x94\x62\xb3\x38\x0f\xe0\x55\x42\xc4\xc2\xfc\x6f\xbe\x22\x47\xd7\x37\x6e\x56\xc4\x8d\x93\x71\xe3\x78\xd1\xd9\xb8\xa6\x33\xd4\x4d\x96\x67\x75\xcd\xf2\xdc\x22\x55\xea\xc8\x14\xb6\x3e\xbf\x5d\x79\x4a\xa6\x39\xcd\xca\x17\xb1\x98\x1b\x1d\x0b\x20\xb9\x76\xf9\x5a\xc5\x3d\xd3\x6c\xa8\x59\x15\xba\xca\xef\x51\xdd\x6c\x54\xe3\xbc\x26\xaa\x79\x68\x89\x38\xd7\xda\x70\xae\xea\x1f\x72\xae\xac\x83\x71\xad\x7f\xc4\xb8\xaa\x36\xe3\xea\x06\x5c\x87\x92\x9a\x05\x91\x3c\x5b\x91\x8e\x2d\xd1\x90\xc8\x32\xcd\x45\x63\x90\x2e\x8a\x74\x90\x3e\xee\x18\xa4\x5d\x7d\x06\x47\x04\xa2\xa8\x38\x18\x1e\xea\x13\xd2\xc1\xc8\x03\xa5\xf1\x02\x35\x37\x01\xf1\x93\x9b\x00\x00\xd5\x74\xad\xff\x62\xb5\xc2\x04\x7a\x1e\x13\xb5\x6a\xcc\x9d\xb6\x8d\x3c\x2b\xcc\x6e\x42\xeb\x83\xe1\x61\x18\x02\xf5\xc1\xe8\x90\x1c\xcc\x0a\x74\xda\x1a\xdd\x07\x35\x61\x87\x18\xe2\x60\x90\x93\xeb\xe7\xe1\xf1\xfa\x79\x58\x74\xcf\xc3\x69\xd1\x9a\x39\x65\xe1\xa7\xc6\x59\x11\x0f\xe5\x5f\xfd\x22\x23\x8a\xd0\x7f\x6d\x10\xa7\x5f\x39\xfa\xc8\xcd\x04\xfb\x1f\xe8\x56\x53\x5a\x77\xbf\x16\xff\x77\xfa\xd5\x56\xb3\xbb\x5f\x8f\x7f\xd4\xaf\x97\xff\x2f\xfb\xf5\xf3\x0d\xfb\xf5\xf3\xff\x68\xbf\x7e\xfe\xff\x8f\x7e\xfd\xfc\x6f\xf5\xeb\x37\x13\x0a\x8a\x5c\xd8\xdf\x27\x45\xa2\x57\xfa\x9a\xb0\xdb\xaf\x2a\x74\xdb\x58\x69\xc0\xbf\x21\x1e\x8b\xc4\x1e\xa5\xff\xf8\xc9\x8b\x27\xef\x9e\x3c\x86\xd0\x47\xcd\x84\xd8\xfb\x32\x36\x62\x8e\x1d\x37\xbc\x5f\x7b\x74\x10\x9f\xa0\xf8\x2e\x75\x1a\x61\x0d\xa7\x11\x96\x38\x8d\xa4\x4f\x23\x97\x95\x22\x0d\x6b\x1d\x59\x07\xdc\xce\x63\x00\xce\xc6\x81\x01\xd5\x14\xa0\x83\xbc\xbe\xdc\xc4\xe3\x8a\xe2\x59\xfa\xa7\xed\xd8\x9b\xb6\xeb\xc1\x52\x2b\xa1\x45\x6f\x68\xed\x57\xee\xe4\xad\xcf\xf5\x6d\x0c\x58\xb3\x81\x5a\xef\xbd\x9d\xa4\x0e\x3f\x2e\x77\xb4\x93\x5f\x13\x78\x7e\xbf\xb0\x06\x06\x4f\x0a\x37\xa2\x2e\x0a\x08\x28\xed\xd5\xce\xe0\xc1\x64\x89\x66\xe3\x85\xd4\xf4\x42\x20\x23\x40\x79\x6b\xcd\x40\xf0\x72\xe9\x33\x79\xb4\xa1\x08\x97\x62\x34\xdc\xbe\x9d\xc5\x7e\x6d\xdb\x66\xb0\xc1\xd9\x13\x7d\x2b\x28\xc3\xe3\xaf\x05\xfa\xa6\x99\xde\x95\xbe\xd5\x63\xf2\x42\x20\xeb\x08\x04\x7e\xb3\x56\xd8\x7c\xb3\x72\x75\x19\x51\x43\xdf\x16\x4e\xdf\x1e\xe0\xaa\x3c\x22\x57\x96\xdd\x36\xce\x55\xc5\x49\x96\xed\x84\xcb\x91\xbf\x1e\xe3\xe8\x35\x28\xda\x97\xfc\x97\x23\x21\xeb\x51\xfa\xad\x08\x4a\x51\x4d\xaa\x27\x85\x0f\xc9\x0d\xdb\xbf\xae\xdb\x90\x58\xec\x9a\xda\x0d\x34\x7e\x8c\x7c\x05\x96\xcb\xfe\x29\x2b\x0c\x30\x7f\x96\xf5\x8f\xe4\xec\xd2\x5e\xf7\x4e\x84\x31\xd5\x8f\x75\xf1\xd8\x62\x40\x5c\x14\xe3\x7a\x8c\xed\x84\x25\xb5\x21\x5d\xdc\x41\xfa\x23\xa6\x0e\x26\x70\x85\x19\xf6\xd0\x9d\x5e\x95\x86\xda\x4e\x8b\x00\x58\xee\xcc\x09\x4c\xb4\xb9\x96\x6c\xf8\x2e\x06\xfc\x13\x47\xdc\xe8\xab\xa4\xa6\xc3\x31\x33\x1a\x7a\x30\x01\x89\x8c\x4d\xdc\x54\x83\x58\xe2\xfc\x18\xf5\xff\x75\x0b\x2c\x13\xb0\x3b\x76\xd5\xf8\xca\x8c\x01\x96\x36\xc4\x19\x48\xd5\x5b\x5b\x30\x20\xfa\xb7\xfa\xc6\x08\xa2\x7f\xab\xe7\xaf\x26\x70\xb5\x5c\xd6\x9b\x9b\xab\x46\xa5\x56\x76\xb5\xb3\x40\x59\x17\x05\xfd\x56\x4c\xe0\x33\xc1\xb6\x2b\xfe\xa0\xb1\xf9\x6f\xab\xb8\xdf\x15\x08\x6a\xf8\x2d\x5e\x3c\x4d\xb4\xd4\xa2\xa1\xd8\x7f\x75\x21\x98\x22\x6f\xd2\xd5\xf5\xf7\x48\x92\x67\x2d\x1f\xbc\x4e\x7b\xf2\x16\xbc\xb8\x60\xb1\x25\x0a\xe7\xfb\x1c\xba\x1e\x32\x41\x4a\x64\xed\x53\xc4\x2e\x01\x02\xfc\x3e\xf5\x4e\xe7\x8f\xd8\x8a\x1d\x7b\x44\x5e\x5d\x8c\xd4\xbb\x5a\xa4\x9f\xe7\x3a\x25\x28\xd3\xdf\x14\x89\x03\xd8\x92\x8e\x88\xad\xa9\xd5\xa2\x1b\xfb\x8d\xbc\x81\xc9\xd5\xc4\xf0\xf2\x05\x64\x74\xeb\xf6\xe8\x6e\x0b\x0a\xe5\x3e\x95\x1d\xd8\x6e\x74\x88\xc9\x1f\xe6\x6b\x32\x16\x5b\xbc\x2c\x5a\xe6\x04\x5e\x15\x6b\x8f\xd1\x22\x5e\xb4\x3b\x0c\xa6\x8b\xe5\x72\xbf\x42\x05\x5e\x2e\xfd\x79\xa6\x48\xb0\x06\xbd\xd0\x40\x80\x53\x6e\xa1\x58\x94\x55\x24\x59\x27\x08\xb1\xd8\xd7\xcb\x38\x0e\x58\x2b\x00\xb0\x72\xe3\xd6\x82\x11\xc8\x4f\x58\x64\xe5\x62\x7b\x9a\x01\x05\x01\x1d\xe3\x36\x31\xce\x40\xb4\x20\x0f\x4d\x3b\x0b\xdb\x4e\x6f\x67\x56\x38\xe8\x62\x22\xef\xf3\x2c\x43\x92\x16\xe9\x3a\x40\x90\xf0\xb3\x58\x84\x16\xe0\x89\xc8\x9f\x2a\x8c\x24\x51\x80\xc0\xab\xc0\x4d\x16\x2a\x85\x27\x96\xd0\x1c\xe7\xcd\x0e\x37\x0e\x87\x85\x77\x55\xbb\xa6\x15\x91\xc5\x55\xa3\x8f\xd2\xfd\x8c\x41\x00\xac\xc6\xea\xd5\xae\x95\x45\xf6\x1b\xe9\x76\xe2\x49\xdb\x46\xa2\x39\x60\x88\x6f\x04\xce\xbf\x17\x91\x83\x59\x64\xac\x55\xa4\x61\xce\xf4\x7c\xf0\xa3\x27\xb8\x0e\xf9\xee\x67\xb6\x4a\xe0\x4f\x94\x7a\x2e\x2e\xe9\x68\xfb\x5e\x54\xf4\xf7\xa2\x1d\xa1\xf4\x5c\x22\xd1\x00\x0c\xf0\xe7\x3c\x3a\x97\xa8\x06\x1b\x5c\x3b\x11\xc5\x3f\x9a\x88\xe2\xff\xdd\x44\x7c\x51\x34\x1c\x90\xa0\xb9\xae\xed\xbd\xe1\xf8\x9b\x44\xb5\xdd\xa8\xc1\x48\x89\x1f\x23\xdf\x58\x6f\xb9\xe5\xd1\x35\x02\xc9\x51\x24\xc5\x73\xf6\x34\xad\x84\x88\x18\xdb\x98\x9c\x38\x4f\x57\xf2\xcd\x5e\x99\xb5\xad\x37\x1c\xc7\x46\x67\xf1\x22\x11\x7d\x9b\x94\x4d\xbb\xb6\x71\x61\x11\x26\xca\xb1\xc3\x86\xb3\x38\x0f\x64\x6a\x3d\x29\xd7\xa2\x48\x4c\xfd\x40\x9a\x4e\xa6\x54\x72\x34\xc5\xf9\xd4\x74\xf7\xb4\x6b\x48\x38\x7b\x75\xb1\x16\xa8\x81\xcc\xba\xe2\x49\x2e\x3a\xf1\x1b\x8a\x75\xf8\x0d\xe3\x59\x27\x82\x43\x71\x03\xc4\x8f\xce\xd7\xd6\xe6\x5f\x2e\x41\x15\xab\x96\xcb\x0a\x42\x73\x66\xd9\xa5\xee\x13\xbd\x90\x4d\x31\x09\x78\x7d\xc7\x2d\x75\x70\x61\xe1\x12\x8e\xc9\x0c\xa4\xf0\xa4\x00\x31\x73\x33\x1f\xb1\xc5\xeb\x69\x59\x2d\x97\x01\xff\x7b\xb9\xe4\x7c\xd2\x01\xa8\xb6\xb1\x30\xf8\x19\x7a\x60\x2c\xf4\x20\x69\x15\x89\x09\x2a\x29\xe7\xcb\xe5\x91\xc9\xa5\xd7\xf0\x63\xf0\xbf\xc1\x13\xf4\x33\x84\x5b\x0f\x7d\x51\x74\x43\x5f\x74\x76\x61\x57\x91\x5d\xa9\xdd\x80\x17\xd7\xd6\xec\x9a\x87\x08\xaf\x29\xae\x0b\x98\xa2\x4e\x81\x29\x70\xfe\x83\x96\xac\x7f\x95\x34\xe6\x1e\x6d\xd9\x48\xd0\x0a\x13\x37\x21\x15\x71\xa3\xa4\x22\x7e\x52\xd2\x29\x51\xb4\xfc\x77\xea\xa0\x68\x6f\x64\xd7\xaa\xc6\xd2\x60\x6c\xa3\xda\x4b\x84\xaf\x51\xed\x9c\x21\x12\xd7\xf1\x49\x99\xff\x22\x9d\x6b\x6f\xa9\x87\x5c\x58\x42\x9a\x4b\x06\x6a\xac\x29\xf8\x67\x57\x10\x82\xba\x56\x08\x74\xdd\x9a\x82\x7f\x6e\xf5\xc0\xff\x77\x16\x8f\x8e\x05\x21\x0c\x89\x78\xe1\x68\x2d\x30\x24\x2a\xfa\xf8\x1f\x2d\x1c\xad\x22\x31\x41\x8b\xe6\xc2\x51\x91\xe3\x9f\x5f\x38\x0c\x99\x6f\x42\x2e\x07\xc7\x73\x83\xa5\xc3\x15\xda\x99\x8c\x14\xd4\xf3\xe6\x2b\x48\x28\x6d\xfd\x53\x5b\xe8\x0d\x16\x12\x57\x5a\x6b\x2a\xfe\xcc\xb0\x6c\xbe\xbe\x7d\xfb\x4e\xba\x14\x75\x11\xd1\x7f\xdc\x44\xbb\x6e\x30\xa2\x59\x56\xb5\x0d\x88\x97\xcb\x6b\xaa\xd9\xfb\x61\x35\xff\xf9\x87\x74\x83\x6e\xb0\x40\x1e\x77\x2d\x90\xc7\x8d\x05\x72\xf1\xff\x3d\xca\xc0\xb2\xed\x78\xea\xc7\x45\xc2\x3f\x47\x16\xfa\x8d\xe3\x88\x3d\x06\x58\xd4\xe0\x0e\x97\x05\x13\x1f\x24\xcb\x7a\x5e\x66\x23\xb3\xec\x02\x60\x26\x48\x6f\x84\xc3\x49\x63\xac\x92\xcd\xe2\x75\x11\x42\x00\x59\x03\xb6\xa2\x73\x4a\x77\xae\xcb\x20\x46\xb1\x16\x3b\xf6\xe4\x1e\xe9\xde\x52\xf6\x3f\x70\xcb\x70\x34\x30\x47\xb0\x54\x34\xa0\xf9\x64\xee\x0f\x07\xe6\xa1\xd1\xb5\xc1\x11\xc9\x1e\x20\x6c\x9e\xa6\x12\xcf\x20\x5a\x45\xad\x1e\xfa\x92\x02\x5d\x9f\x16\x71\x7c\xd5\x00\xcf\xe6\x75\xf2\x56\x0f\x30\x39\x91\x68\x48\x9a\xa9\xad\x04\xe3\x12\x62\xe3\xd7\xe4\xfe\x32\xcb\xec\xeb\x6e\xfb\xd4\x3d\x60\x1c\x9b\x52\x9f\x3c\xe3\x66\xf3\xa8\x20\x1f\x0a\x72\xab\x20\xef\x0b\x7a\xd5\x10\x58\x11\xc5\x6a\x75\x69\xb0\xd3\x23\x0f\x9c\x67\xa9\x25\x04\x91\xd4\x1d\xdd\x83\x59\x82\xdd\xf3\xe9\x77\x6f\x90\x4f\x4a\x7b\xa8\x41\xaa\x6b\x0c\xe1\xe5\xd2\x3e\xd8\xce\x0a\x1c\x0c\x05\x58\x38\x5d\x36\x21\x4c\x89\x9a\xa0\x92\xf6\x86\x8d\xa3\xda\x9d\xdb\xce\xca\x88\x06\x0f\xd8\xd6\xec\x08\x1e\x6f\x83\x63\xab\x8d\x59\x2e\x7b\x26\xc1\x47\x47\x29\x74\xae\x77\xa7\xbc\x7a\xea\xb3\xa0\x62\x49\x47\xe0\xda\xf4\x9d\x93\x51\x56\x84\x73\xa7\x3d\xd4\x59\xb1\x47\x28\x36\xcb\xf6\x0b\xa4\x59\x22\xc8\x50\x46\x4f\x08\xe2\x00\xd7\x02\x26\x4a\x64\xd8\xc0\x10\xa9\xc9\x90\x6a\x6a\x18\xe2\x1a\x71\x65\x84\xe4\x98\x7a\x6c\x38\x9f\x20\xf8\x9f\xdb\x3b\xe7\x2f\x19\x7c\xa4\xd8\x18\x7b\x59\x04\x07\x97\xb1\x14\x48\x09\x09\x5d\x21\x5d\x9b\x26\xa2\x49\x4d\x78\xc0\x4d\x6a\xcd\x80\xf7\x85\x9f\x38\x9c\xf8\xb0\x6d\x92\x06\x87\xcd\x75\xae\x2b\xe6\xa5\x20\xb4\x83\x78\x3f\xc1\xe4\xae\xd9\xe9\x9a\x84\x32\x0a\x48\x85\x5d\xa5\x2c\x79\x79\x42\x5e\x61\xd0\x9f\x52\x4d\xc0\x5a\x0a\x83\x0b\xfa\xcf\xd0\x17\x43\x05\xcd\xa5\xee\x1d\x0b\xe6\x43\x3d\x64\x45\x39\xc6\xa5\xfb\x98\x00\x03\xdc\x06\xb9\xa5\xae\xa0\x8c\x5d\x40\x6b\x22\x3c\x9d\x25\xe9\x8e\x1c\x31\xbc\x8e\xfe\x82\x48\xef\x78\x95\x2e\x71\x51\x67\x88\xf6\x22\x96\xf4\x87\x58\xf1\xe3\x40\xe4\x7f\x30\x74\xfd\xc8\x23\x89\xa0\xc2\x0d\x3d\xfc\x7f\x78\x6c\x8b\x64\xdb\x74\xdf\xf9\x99\x2e\x88\xa6\xc0\xb5\x54\x36\xbd\x93\xf6\x4b\x14\xaa\xc7\x4a\xdf\x5a\x62\xa7\xae\xd0\x1e\xd4\xee\xcf\xeb\x02\x11\x88\xae\x42\x9a\x89\xb4\xc6\x44\x70\xdf\x4d\x89\xed\xe7\xdf\x0d\xa6\xc0\x30\x03\x8d\xe9\xe9\x0c\xf9\x8b\xa6\x67\x11\xbd\xe2\xd5\xa3\x62\xfa\xf5\xa2\x50\xb3\x0a\x22\x6f\xe8\xed\xda\x1b\xb7\xf8\xdb\xb7\x75\xa1\x6a\x1b\xac\xa3\x2c\xaa\x3a\x57\xa4\x2e\x78\x99\x0b\xf8\x09\x7d\x90\x0f\x21\x01\xc2\xb6\x4a\x12\x99\x40\xf3\x55\x8e\x8a\x41\xf4\x31\x5a\x93\x62\xe0\xcb\x37\xf4\x8f\x12\xfc\x07\xe9\x90\x14\xa0\xf7\x04\x8e\x50\x17\x4e\x85\xbd\x08\x9f\x85\x4c\xee\xc3\x54\xda\x37\xac\xa6\x34\x96\x9c\xfe\xd6\x30\x16\x6c\xec\x8b\x12\xf0\x13\xbd\x2b\x28\xe1\xe0\x21\xce\x01\xf1\xc9\xa9\x0c\x92\xc9\x6a\xf6\x45\xa4\xa2\xfd\x14\x63\xac\xe8\x28\x53\xcb\xed\x54\xbc\x77\x67\x37\xf1\xb9\x32\x53\xcf\x6e\xb8\x2c\xde\x70\x59\x6e\x66\x9d\xf3\x1d\x0d\xd3\xc8\xbb\xd1\x1a\x5d\x57\xf7\x52\x9c\x65\x30\x40\x05\x1e\x27\x1e\xb0\xe6\x95\xe6\x23\x5f\x84\x75\x61\x65\xa9\x0b\x2b\x23\x7e\xad\x49\x5c\x58\x41\x4c\xe0\x11\xf4\x63\x27\x56\xd6\xe9\xc4\xca\xbc\x13\x2b\x0b\x4e\xac\xa1\x80\x28\xde\x0e\x6b\x3a\xb0\xfa\x41\x1f\x2d\x1c\x2b\x95\xd1\x91\xae\x87\xd9\xed\x55\x73\xd5\xea\x9a\xdc\xa6\xc9\x56\x45\x2e\x6d\xbc\xdf\x63\xa9\x60\x30\xf6\x81\xe4\xc2\x91\x9c\xc8\x04\xe0\x48\x78\x1c\x44\x00\x21\x8d\x63\x63\xd8\xf6\x81\xef\x33\x28\x31\x04\x26\x82\xfa\x9d\x62\x1c\x6c\xf9\x25\x9e\x20\xe9\x3f\x10\x6b\xc7\x00\x64\xda\xbf\x12\x6d\x33\xf0\x90\xfc\x5d\x20\xcd\x2b\xc2\xf2\xc8\x13\x93\x7e\x9c\x44\x1b\x76\x13\xcb\xb5\xc5\x6c\x01\x9d\x5f\x74\x0d\x93\xe3\x06\xc2\xaa\x5c\xdb\xb8\xa0\xd0\x93\x0e\xc1\x86\x4a\x5f\x67\x19\x2f\xd3\x80\xb8\xcf\x56\xa6\xde\x43\xbb\xa8\x5f\x57\xf5\x5a\x9e\xb0\xfa\x94\xa9\x7e\xee\xda\x1a\x6c\x32\xac\xff\x40\xd7\xbb\xde\x27\xbf\xa3\xb7\xc3\x32\xdf\xe0\xf5\xff\xf0\x0b\x40\xb4\xff\xd5\x69\x7c\xa2\x46\x1c\xa7\xb1\x5b\x2a\xda\x7e\x71\x43\x63\x76\x5d\x54\x48\xf9\x73\x45\x23\xd8\x54\x1a\xce\x39\x99\xfc\xf6\x85\x68\xfe\x35\x51\x8d\x76\x02\x00\x52\xec\x68\x6e\x3a\x58\xb3\x50\xbe\x7b\x1b\xac\x54\xc4\x75\x78\xdb\x14\x0f\x56\x1a\xcd\xd1\x68\x52\x81\xbe\xcd\xf7\xe3\xf5\xfc\xd9\x38\x1d\xa4\x6b\xa9\xfd\x4b\xdb\x32\xc5\xac\xd1\x76\xfe\x9d\xf2\xd9\x8c\x89\x7e\x0e\x76\x03\x7a\x9d\x0d\x8e\xdb\xf1\x38\x8d\x81\x48\x63\x04\x4e\x41\x1b\xa8\xa3\xc2\xc5\x0e\x62\x76\xa7\xd0\x87\xd0\xb4\xae\x69\x80\xe6\xd2\x44\x5a\xef\xe7\xa2\x59\x03\xb5\x66\x09\x10\x49\x0d\xd4\x9a\x09\xaf\x26\x75\xe4\xc4\xad\x0b\x4e\xaa\x64\xae\x93\x7a\xe5\x2a\x25\x69\xe4\x3d\x7c\xed\x9e\x35\xf6\xe8\x8c\xc1\xea\x67\xdb\x02\x5e\xdc\xb1\xbf\xb7\x1d\x46\x88\xc3\xc1\xb0\xe0\x3e\xf0\x73\xcf\xa6\xda\x97\x1c\x58\xc6\x6e\x6c\xbd\x63\x0d\x6f\x72\xef\x27\x63\x25\xc2\x38\xcb\x8e\x24\x32\x47\x2d\x17\xbc\xc5\xe6\x79\xc9\x11\x6e\xc4\x11\x85\x05\x36\xc2\xa1\x4f\x8f\xcd\x26\xb6\x9b\x8b\x7a\xd6\x3c\x64\x37\x13\xec\xe2\x98\x9e\x27\xc3\x4c\x5a\x2e\x7b\x7f\x15\x00\x0e\xd9\x12\xeb\x84\xba\xde\xce\x9f\x73\x04\x70\x19\x1d\xa0\x1a\xdc\x0a\xc4\xd3\x29\x6b\xae\xe2\x56\x7c\xb0\x32\x14\xa2\x4c\xc0\x0e\xa3\x66\x75\x9a\xdf\x96\xa6\x35\x84\xa9\x53\x29\xca\x43\x28\xb1\x19\xce\xf5\x4e\x70\x73\x17\xd6\xc1\x97\x35\x11\x3f\xa0\xb1\xf8\x2a\x15\xe3\xf8\x26\x18\x11\x4e\x53\x37\xe8\xc0\x0f\x0f\x5e\x89\x43\x5a\x13\x75\xb0\x2f\x0e\x69\xa9\x79\x48\x98\x1c\xfc\x58\x15\x73\x17\x35\xdc\x4a\xfa\xcd\x0d\x9b\x1f\xe9\x29\xf3\x5b\x8d\xfa\xa5\x2c\x66\x7d\x0f\xea\x63\x66\xd5\x39\x9f\x31\x69\xb3\x16\x8b\x19\x97\x7d\xcb\xd0\x0c\xc7\xec\xfe\x27\x16\x07\x88\xfc\xad\x46\x9f\xd8\x01\x3b\x6c\x94\x50\xc9\x85\x9a\x32\xf3\x05\xa6\xe9\xd0\xfc\x04\x9f\xbb\x38\xf4\x7c\x5e\x9c\xf8\x58\xe9\x5c\x7c\x6d\xbc\x44\xd6\x55\xf2\x58\xaa\xb9\xc9\xab\x58\xc5\x6a\x9f\xb7\x5a\x1c\xcd\x79\xdd\xcc\x3d\x63\x7a\xbe\x56\xe6\x85\x5a\x9e\x9c\x94\xac\x55\x25\x71\xb6\xa8\xfb\xf9\x57\x86\x14\x29\x4d\x59\x5c\x9c\x17\x25\x87\x4f\x93\x4a\x20\x41\xfa\x52\x98\x90\xe0\xfd\xb4\xb9\x10\x11\xbc\x9f\xab\xc1\x97\x0b\x55\x9c\x9d\x39\x47\x88\xab\x8b\xa2\x7a\xb9\x28\x6b\x7e\x56\xb2\xbc\xd7\x2b\x07\x73\x7b\xb3\xfa\xa9\xe2\x3d\xdc\x76\xfe\xfb\x0d\x2b\xb7\x72\x8b\x60\xb5\xc1\xc5\x86\x14\x88\xeb\xb7\xec\x21\xa9\xc4\xfa\xc8\x39\x38\x2d\xaa\x57\x17\x42\x8f\x24\xa6\xea\x4b\x54\x61\x87\x82\x56\x1e\x54\x87\xe3\xbe\x63\x92\xfb\xe0\x3e\xdc\x82\x6a\x9a\x4e\xd4\x40\xd7\x0b\x66\x33\x04\x9d\x9e\x82\x17\xe9\x41\x78\x91\x4c\x0f\x71\xde\xc2\x73\x9a\x66\x59\xf3\xcd\x7e\x7f\xb3\xfd\xb2\x4e\x3c\xc4\xf9\xab\x76\x3d\xdd\x24\x9e\x66\x19\xb4\xbb\xc2\x2b\x07\xa0\xe6\x46\xbe\xe9\xca\x6f\x4c\xef\xea\x6f\x81\x66\x60\x7d\xd8\x49\x53\x93\xeb\xa5\xfe\xdf\xd5\xa7\x66\xf6\x9c\x81\xd8\x34\x4f\xf9\x96\x0e\x05\x41\x39\x90\x62\xbf\xe4\x10\x61\x4a\x0d\xa4\x98\xea\x6b\x3a\x15\x78\x05\xd0\xb5\x29\x1c\x47\x14\x2e\xb3\xb9\xc8\x99\x18\x63\xb6\x59\x15\xdd\x03\x7c\x9c\x60\x61\x99\x8b\x01\xa0\xb7\x3f\xb6\x38\x48\xe0\x63\x54\x0a\x20\xe2\x77\xc0\xa5\xb2\x29\x93\x7e\x35\x55\xfc\xac\xee\x83\x17\x13\x42\x8c\x56\x03\xe3\x1a\x6b\xa1\x93\x50\x7f\xc6\xcf\xfb\x00\xe2\x2d\x98\x7a\xf6\xee\xe5\x0b\xda\xbf\x6f\xde\x79\x70\xff\xbf\xff\x65\xaf\xfa\xc4\x70\xf8\x73\x79\xce\xc0\xee\x11\xb1\xd8\x08\x12\xe7\x6d\xcc\xd7\x01\xaf\x26\xed\xaf\x71\x72\xc5\xab\x5c\x3f\x5c\xe1\xbc\xa3\x36\x1c\x13\x47\x79\x83\x99\x85\x2a\xca\x88\xf2\x13\x67\x52\xf9\x4b\xda\x1b\xc2\x96\xfb\x1d\x60\x8e\xe0\x82\x9a\x7b\x8c\x71\xde\x2a\xfa\xf7\xb7\x06\x90\x8b\xd9\xd5\x92\xc1\x6a\xa9\xc8\x23\x67\x9f\x18\xd9\xaa\x32\x52\xd1\x42\x4f\x1a\x85\xff\xc9\x6a\xca\x30\xe0\xf4\xfd\x78\x45\x9d\xd2\xe1\x78\x1a\xad\xa8\x53\xbf\xa2\x4e\x0f\x09\xc3\xe3\x46\x29\x1d\xab\x6a\xfb\x53\x37\x5c\x59\x59\xbc\xb2\xb6\x4b\x69\xad\xae\x2c\x5d\x5d\xdb\x6f\x74\xae\xb0\x1d\xd5\xf3\xab\x2c\x00\xb1\x4d\xe9\x13\x7b\x95\x2c\x69\xec\xfa\x05\xd1\x4d\xc7\x29\x7d\xc7\x22\x48\xba\x74\xe2\xb2\xeb\x17\x63\x15\x2d\xc6\x36\x82\x9c\x22\x57\x80\x5f\xe9\x7c\x62\x7f\xae\x52\xc9\x2a\x6d\x1b\xf7\xe6\xe7\x1a\xe7\x16\x96\x29\x55\x2b\x58\xb4\xa7\xce\x26\x67\x0a\x6c\x6d\xa9\x57\xf3\x85\x5e\xbe\x17\xcd\x65\xb1\xc4\x0e\xde\x63\x71\x50\x1e\x8e\xfb\x55\x7d\x59\x32\x3d\x87\xca\x89\x10\x88\x91\x19\xce\xfb\x33\xfd\x31\x25\x17\x55\x79\xf9\x96\xd5\xcf\xdd\x84\xb7\xb9\x4c\x2c\xa4\x19\x9d\x4d\x66\x83\x2f\x5f\x4e\xeb\x79\x69\xe9\x80\xb3\xec\x29\xb3\x45\xc4\xbb\x42\xd9\xde\x15\x66\x93\x34\x38\x04\x5f\x2e\xfb\xfa\x77\x86\xb3\xec\x91\x2b\xa3\xb9\x23\xcc\xec\xb3\x7e\x7f\x53\x3f\xae\x16\x67\x67\x8a\x55\x95\xdd\x1f\x9e\xcc\x38\xa8\x0c\x3e\x14\x4a\x98\xd8\x16\xb4\xcc\x32\x9f\xeb\x19\xa8\x55\xb8\x14\x8d\xe7\xc5\xa2\x96\x4f\xe5\x74\x51\xd9\x04\xd4\xda\x48\x4a\x6c\xdb\x3c\xb3\xfb\x48\xe9\xb4\x1b\xb3\x2c\xfb\x88\x18\x29\xc9\x8c\x54\xf8\x9a\xcd\x85\xc1\xe6\xc2\x88\x22\xbd\xd1\xfa\xcd\x85\xc1\xe6\xc2\x3a\x07\xb0\xf9\x9e\x32\xb0\xa9\x59\x06\x01\x19\x1e\xd6\xb5\xe2\x47\x8b\x9a\xa1\x3e\x24\xc3\x56\x78\xc2\x90\xcd\x85\xd7\x0c\xf6\xb0\x2c\x46\x43\xdb\xee\x30\x48\xb8\x6f\xe0\xc9\x6b\x5d\xe3\x34\x8f\xae\xbe\xaf\x8a\x1d\x83\x7f\x9a\x1a\xb5\x73\xa7\x39\xa2\x6d\xf5\x9a\x5d\x71\x1a\xed\x8a\x2c\xd9\x15\x8f\xcc\x2a\xdb\xb1\x01\x8a\x24\xe4\x4a\x87\xc5\x63\xeb\x74\x73\x27\x37\x61\xbd\xdb\x2c\xfe\xad\x02\x0d\x49\xcb\x4e\x5d\xaf\x1b\x8e\x99\x6f\x45\x4e\x51\x51\xcc\x88\xeb\x39\xfb\xe6\xe1\x83\x74\x71\xf6\x93\xf4\xe8\x44\x54\xcb\x8a\x47\xd8\x7d\xc9\x6c\xf3\x40\x5c\x23\x9b\xe8\xb0\x70\x42\x82\xa2\x1f\xb1\x04\xd8\x6e\x7f\xef\xd8\x37\x00\x37\x44\x0a\x63\xfb\x89\x78\xab\x13\x1d\x84\x1c\xf9\x33\x60\x25\xd1\x77\xf0\x38\x6a\x1a\xb0\x74\xe8\x19\x3b\x2c\x63\x05\x04\x46\xf0\x56\xc0\x0a\xd4\xe4\x21\xac\xcd\x5a\x30\xbf\xa0\xe0\x03\xe2\xe5\x3e\x54\x1b\x45\xbc\x1d\xc3\x48\xf8\x83\x7a\x70\xb2\x86\x7c\x46\x12\xea\x50\x41\xa3\x38\xb8\xfa\x28\x15\xf9\xe5\x40\xd5\x63\x3f\x1d\x6e\xfd\xba\xed\x6d\x09\x06\xad\x6b\xa3\xbf\x34\x72\x9b\x73\x2d\x8f\xbd\x88\x30\x26\x22\xcb\x7a\xca\x88\x8b\x63\xb5\x58\xb0\xf9\xed\x75\x11\xe2\x3a\x7d\xa9\x2e\x6a\x94\x45\x62\xeb\xc9\x5f\xba\xa8\x6f\x7a\xb1\xfb\xab\xa4\x17\xba\xd6\x7f\xe9\xe6\xea\x14\xb8\xb8\x28\x97\x4b\xfd\xe8\x49\x09\x72\x6f\xfa\xa5\xf4\xa7\xee\x57\xfa\xad\x97\x15\x7a\x55\x92\xb7\x25\x26\x0f\xe1\xea\x4b\x89\x31\xd6\xa7\xfe\xe5\xb2\x6b\x8a\x5a\x4c\x7a\x18\x32\xbb\x89\xd4\x20\x1a\x4a\x43\xf7\xa0\x36\x27\xf5\xf0\xe4\xee\x0d\x84\x11\xa3\x3d\x3d\xa7\xed\x28\x74\xa2\xd8\xd6\x68\xc4\x4d\x09\x1d\xef\xd2\x83\x47\x11\x5e\x55\x50\x53\x18\xff\x21\x8e\x7f\x29\x90\x59\xc6\x9d\x60\xdd\x92\x2e\x36\xc1\xee\x92\xf5\xc3\xb6\xdc\x94\xf4\x97\x4d\xb1\xec\x0b\xae\x37\x68\x23\xf7\x6b\xe8\x14\x88\xfb\x32\x89\x87\x6e\x23\x4a\x73\x7a\x70\xe0\xa4\xab\x27\x00\x0d\x37\x0c\x4b\x78\x2b\x1e\xb3\x66\x5c\x26\x23\x37\xce\x0f\x93\x3c\x69\x85\x1a\xe3\x92\x0a\x82\x38\x55\x38\xb6\x03\xd8\x6e\x8f\x77\xc2\x5b\x9f\x22\x3c\x71\x76\x73\xf2\x60\x0a\xf4\xe0\x49\xa4\x9f\x75\x8a\xc0\x16\x08\x42\xe9\x75\x93\xf6\x0b\xa9\x39\x52\x23\x31\xd2\x0b\xf2\x36\x0e\x22\x4f\x45\xc6\x56\x8e\xdf\x5d\x15\xd6\x95\xda\xae\x5e\xcb\x11\xc0\x6b\x52\xbd\x66\x35\xad\x70\x73\x43\x6a\xd6\xbd\x19\xad\x8f\x5f\x63\x55\x5f\x36\xa4\xde\x5d\x0d\xf4\x8c\x5e\xde\x84\xd3\x28\x9b\x55\x4f\x02\xfa\x97\x83\xf8\x36\x8e\xee\x5f\x0e\xc2\xcd\xaa\x33\x5a\x95\xb3\xe7\x08\x0b\xd5\x72\x3b\x98\xf0\x44\x9a\x7b\xe3\xfb\x04\x92\x35\x8e\x3b\x67\x8f\xc1\xc3\x4c\x26\x0f\x20\x68\x08\xef\x64\xf2\x83\x89\x23\x5a\x13\xc7\x4c\xbe\x61\x34\x83\xf4\x29\x26\xcb\x9c\x60\xdb\x27\xbd\x94\x33\xbd\x48\x97\xd1\xc0\x6d\x39\x47\xae\x9d\x5b\xa6\x26\xad\x3d\x42\x78\x50\xc8\xed\xff\x7a\x24\x11\xde\x52\x1d\xea\xd3\x07\xaa\xa1\x2a\xcd\xb2\xd1\xfd\x96\xc4\xc0\x91\xc2\x2f\x26\xad\xbd\xb8\x53\xb9\x41\xc5\xd6\x08\x8f\x55\xac\xd6\x9d\xa0\x20\xd6\x6e\xaa\x9e\xf4\x0c\x09\xc4\x36\x2d\xc4\x93\x20\x9e\x2f\x73\x9f\x93\x98\xa7\xb4\x4c\xf8\x8b\x9e\x23\xe7\x04\xdc\xe9\xda\x4d\x43\xcd\x24\xaa\x09\xb3\x79\x7b\xa8\xbb\xc8\xe6\x27\x2a\x56\x3b\x13\x65\xc5\xf2\x5e\x0d\x12\x53\x3e\xe9\x13\xd2\x45\x5f\xf8\x40\x43\x67\x47\xea\xd8\xbc\xca\x8c\x60\x3e\x19\x65\xf5\x72\x3b\x1f\x65\x7a\x1b\xb3\xc1\x74\x9a\xea\x9e\x3b\xc4\x48\xf1\x23\x85\xf5\x47\xb0\x4d\xeb\xc4\xfd\x3e\x97\x88\xc5\x1b\x9f\xf7\xc0\xf4\x1d\xeb\x26\xd1\xee\x70\xef\x4e\x06\x8e\xc7\xcd\xb8\xea\xf5\xf2\xce\x2e\x61\x71\x10\xa0\x1d\xbd\x65\x76\x08\xef\xed\xee\x85\x92\x0f\xb4\x02\x71\x6c\xdf\xbb\x1d\x81\xb2\xae\xf9\x9c\x93\xbe\xdb\x6c\x00\x3d\x4d\xae\xe1\x1f\xa1\xfa\x8d\x0f\xdf\xac\x31\xa3\xbd\x46\x51\xe1\xd1\x8d\x58\x0e\x57\xaf\xd4\xdf\x78\xa3\xa1\xa0\xf9\x35\x41\xa4\xb2\x42\x00\x46\x8c\xbc\x25\xaf\x49\x55\x17\xd3\xaf\xf9\x11\x43\x35\x5e\xad\x1e\x15\x34\x75\xce\x0f\x2a\xaf\xc6\x06\x2a\x0c\x1b\x70\x9b\xda\x20\x95\xcb\xe5\x1d\x77\x89\xd9\xa0\x38\xd3\xab\xb3\x91\xa6\x45\xce\xe8\x81\x03\xd9\x75\xc1\x2d\x83\x43\x98\xd3\x1f\x8a\x54\xcb\x2f\x40\x83\xd5\xd6\xf2\x47\x4a\xfa\x44\xc7\x2f\x3a\x75\xfc\xc2\xeb\xf8\x45\xac\xe3\x77\x8e\xc9\xfe\xf9\x4a\x34\x55\xfc\xee\x49\xac\x48\x5b\xad\xc8\x87\x94\x52\x2e\xcc\x5a\xd3\xc0\xc5\x28\x33\xf8\x31\x2a\x4c\x4c\x27\x50\x76\x90\x8a\x4c\xe3\x13\x93\x53\x76\x34\x0e\x57\x76\x33\x17\xe9\xd9\xbc\xa0\x4f\x18\x9a\x92\x42\x6f\x48\x70\xa5\x74\xce\x83\xc3\xae\x23\x78\x41\xdf\xf9\xbc\xef\xd6\xe4\x75\xa7\xec\xc2\xc8\x89\x8a\x96\x9c\x48\xad\x13\x20\x35\x4b\x0a\x92\x81\x82\xbe\xf1\xdf\x7d\xd3\xfe\x6e\xfb\x34\x1d\xd9\x30\xfb\xd3\x74\xc7\x59\x5b\x45\x67\xed\x69\x72\xd6\xf6\x12\x24\x69\x62\x50\x12\x6b\x62\x50\x00\x8c\xb6\x6a\xcb\x47\xb2\xac\xe8\x4a\x34\x83\xb1\x38\x28\x0f\xb1\x39\x39\x3b\x49\x13\x30\xc3\xa0\x71\x98\x9a\xc7\xd3\x2e\xe1\xbd\x3e\x4e\x20\x61\x02\x9c\x1e\x54\x87\xb4\xdf\x37\x43\x7e\xad\x78\xca\x4a\x74\xbc\xe8\xa9\x21\x01\xfa\x5f\x90\x13\xb1\xe5\x12\xe9\xbe\x01\x91\x35\x83\x08\x19\x83\xb3\x45\x75\x8a\x4a\x6b\xef\x16\xa4\x73\xca\x05\x4a\x52\x07\xe5\xa1\x1e\xd4\x53\x7b\x74\x2d\x26\x9a\x24\x76\x64\x90\x4e\x6a\x2f\xac\x16\xc5\xbc\xb0\x70\x47\x8f\x29\x6e\xd2\x59\x17\x6b\xd6\x1d\x43\x6d\xdc\xeb\xa0\xf4\x72\xb9\xc8\xb2\x96\x9c\x50\xa7\x77\xf5\x80\x2f\x6c\x81\x3b\xde\xc9\xb2\xe9\x41\x75\xd8\xa3\x74\x71\x50\x1d\xb6\xbb\x50\xa7\x3a\xd8\x5d\x4d\x2a\x47\x2e\xc2\x3c\x99\xf4\x11\x98\x2e\xae\xef\x68\x60\x4f\xd1\x82\x2e\x26\x8b\x54\x02\x49\xa6\x74\x3a\x99\x36\xd2\x2c\x95\x0c\xd4\xed\xc2\x40\x98\x26\x5d\xb3\xc0\x2d\x81\xe5\x94\x52\x4d\xd7\x96\xb4\x67\x91\x65\x4e\x2c\xd9\x8b\xdc\x1a\x5b\x45\xf6\xfb\x9b\x8b\xff\x29\x11\x65\xe7\x48\x43\xbe\x55\x95\x53\xda\x2d\x97\xb6\xd6\x86\xa6\x1d\x63\x70\x81\xf1\x4a\xb4\x08\x60\x07\x0c\x11\x98\x70\xca\x48\xf3\x0c\xd9\x25\x73\x5b\x91\x5b\xed\x25\x1b\x5f\x89\x6e\x41\x94\x41\xcc\xfb\x5c\x74\xf9\x84\x7e\x60\xc5\xd7\xb7\xac\x9e\xd8\xdf\xfc\x2d\xab\x83\x99\x3b\x2b\x53\xef\x67\xb3\xcf\x12\xab\xfb\x9e\x7e\xf5\x36\x18\x61\xe3\x03\x4b\x8d\x23\x08\x58\x18\x69\xcd\xce\x99\x75\x77\x37\x46\x24\x26\xf8\x54\x38\xaf\x8f\xa8\x87\xcd\x38\x67\xc8\x07\xd3\x50\x97\x57\x53\x29\x2a\x59\xb2\x01\x28\x43\xf4\xae\xee\x11\xea\x2a\x06\x5c\xa1\x5c\xd4\x09\x9e\x8e\x8d\xcd\xb2\xc2\x38\x62\x1b\xea\x32\x76\x3b\x50\xec\x38\x41\x6a\x4f\x62\xa4\x45\x41\x3d\xf5\xf7\x1d\x6a\xa7\xf9\x6c\x8d\xaf\x4e\x4c\x2c\x5d\x33\x87\x1a\x18\x9d\x01\xf2\xb5\x4c\x8c\x71\x62\x83\x91\x86\x65\x88\x33\x18\xd9\xde\xb6\x1c\x8f\xb3\xff\xe0\xc7\x68\xfb\xf6\x9d\x58\x2e\x12\x0c\x2f\x02\xfe\x45\x43\x4c\xda\x32\x46\xad\x0d\x0e\x70\x64\x09\xb2\xc6\xab\x06\x35\xa3\xd8\x1a\x41\xcf\x44\x44\x3e\x89\x02\xc3\xd6\x37\xf8\xf2\x45\xb1\x62\x5a\x3f\x17\x70\xbe\x2a\xbb\x8a\xa3\xf5\x2a\x6e\xce\x4e\x6e\xf9\x50\x2b\xfe\xb5\x6c\x61\x2a\x5e\x6a\xf2\xe8\x77\x76\x62\xee\x5c\x95\x49\x10\x0a\x73\x8e\x8b\x4f\x74\x09\x2a\x70\x7c\x98\xb0\xc0\x23\x6e\x10\xd3\x3a\xc0\x43\x1f\x23\x64\xf8\x36\x86\x83\xa3\x37\xa0\x51\x1b\x10\xdb\xb1\xbf\xa2\x09\xba\xae\x99\x66\x0a\xe1\x95\xb0\x90\xc0\x0e\xd6\x1b\x06\x54\x34\xf2\xe4\xff\x6a\xb5\x8d\x20\x39\xaa\xf5\x4d\xaa\xc8\x4b\x67\xf4\xe4\xc3\x9f\xde\x7c\xb8\x1a\x20\x5f\x59\xa2\x1d\x22\x70\x34\x76\x59\x1c\xcd\x9d\xec\x66\x91\x45\x39\x8e\xe3\x88\xb0\xb6\xaf\x2d\xb2\xc2\x7e\xd7\xa8\xce\xe8\xca\x0d\xf9\xab\x1e\xa5\xc2\x05\xc5\x4d\x31\x6e\xc6\xac\xc3\x5b\xad\x8d\xaa\x7e\xb3\x71\xed\xcf\xc8\x80\x3c\x14\xf5\x68\x43\x9c\x71\xcc\x91\x00\xc4\x2c\x1c\x1d\xf5\xd6\x65\xb7\x41\x9a\xbc\xe4\x2d\x3a\x3f\xf8\x1e\x31\xa7\x88\x18\x6d\x8a\xb9\x4c\x11\xeb\x1d\xf8\xd7\x8d\x51\x57\x86\x95\xaf\x57\x32\x3d\x6f\x87\x10\xac\x71\xaf\xf9\x36\x52\x80\x10\x4a\x3a\x31\xcb\x8e\x44\x88\x56\x9d\x52\x3c\xcb\xd8\xe0\x58\x73\x6a\x08\x7b\x7c\xaa\x64\xc6\x37\xd6\xba\x9d\xbc\x49\x54\xe8\xe7\x86\xa9\x35\xd8\x4c\x06\xac\xd6\x68\xbf\x11\xcd\xcc\xcd\x87\xc1\xa7\x2b\x7a\xf2\xbd\xd6\x7b\x14\x76\x55\x1c\xed\xf9\xe5\xc8\x8c\x70\x3b\xf2\xb7\x47\x37\x5a\x9e\x8a\xe6\x24\xea\xd8\x51\x9e\x54\x59\xf6\xa4\x42\x60\xc2\xb0\x7e\x86\xed\xb6\x66\x5a\x22\x62\xab\x1b\x23\x4d\x44\xe8\x4b\x91\x1c\xcb\xad\x09\x06\x8d\x68\x6c\x30\x83\xc5\x64\xef\x6e\x2e\x5a\x98\xc1\x8c\x2a\x17\x93\x02\x30\x95\xec\xda\x17\xbb\x72\x09\x87\x17\x6e\x00\xe8\x04\x8a\x36\xe2\x93\xca\xc4\x6d\xf4\x50\x49\x76\xa1\x01\xad\x18\xd6\x1b\x71\x32\x2c\xeb\x52\x53\xa0\xc3\xf7\xbc\x61\xb2\x98\xba\x0a\x8b\xb9\x71\xc3\x4f\x0f\xf4\xb0\x43\x5b\x2f\xd6\xe6\x66\x68\x0b\x6b\x89\x66\xeb\xce\x92\x51\xc7\x16\xbf\x82\x20\xe1\xf1\xa4\xba\x6d\x6a\x1f\x27\xed\xe6\x0b\xd7\xf5\xd1\x9a\x5a\x26\x0c\x47\x14\x78\xc6\x1f\xc4\x4d\xc0\xc2\x48\x46\xde\x74\x17\xb1\x89\x2d\x71\x38\x6b\x4b\x8b\x49\x0b\xfe\x84\xb5\xa5\xfc\xac\x25\xe5\x4f\x6d\x8e\x9b\xb5\x48\x12\x23\x35\x64\xb4\x4a\xd5\x59\x56\x6a\x7a\x44\xf0\xe8\x65\x04\x95\x7a\x9b\x7a\x58\xb2\x9d\x70\xb9\xeb\x2e\x23\x24\x73\x78\xcb\xa2\x80\x05\x2e\x2d\x86\x59\xb3\x91\xcd\xaa\x12\xd5\x61\x4f\x0c\x68\x5e\x51\x58\xae\xc6\x2c\x1d\x06\xbb\x80\x3a\x59\xdd\x44\x0b\xbb\x4f\xd1\xde\x28\xee\xda\x1d\xb7\x6a\xd5\xb4\xe1\x37\x6a\x70\x62\x1a\xb6\xf1\x8d\x0f\x8f\x30\x5e\x8d\xee\xa4\x6b\x26\x7a\xc4\x50\x4d\xfa\x7d\x1c\x7b\x58\x65\x74\x6b\x74\x17\x8f\x59\x5e\x5b\x77\x02\x36\x1e\xdb\x08\xb2\x37\x94\x02\x55\x25\x72\x37\x18\x50\xbc\xbc\xf1\xf5\x06\x5b\x45\xe2\x20\xe3\xa0\x77\x03\x91\xd0\xf8\x76\x90\x68\xdd\x09\x97\xa3\x7b\xee\xda\x54\x64\x3b\xd9\xd6\x9d\x3c\x6b\xa3\x8e\x83\x81\x38\x23\xe1\x5d\x2f\x50\x8b\xf2\xad\x97\x8e\xad\x00\x5c\x2e\xfd\x80\x01\x28\x6b\xec\x76\x1b\x6c\xb5\x52\x93\x70\x62\x41\x49\x78\x03\x58\x68\x09\xa7\x7a\x28\x4a\x23\xd5\x93\xa0\x77\xc4\x35\xe5\x93\x68\xbd\xc9\xeb\x38\x38\xa7\xa8\xea\x42\x4c\x19\x11\x93\x7b\x20\xfc\xf6\xca\x7b\x15\x05\x21\xd4\xd9\x98\xb2\xec\x01\x2c\x15\xb9\xea\x48\x43\x8d\x22\x40\x1e\x1f\x4a\xc1\xcd\x57\x20\xf8\x25\x55\x38\x11\x3b\xd6\x41\xe9\xa8\xa8\x1a\x18\x46\xe5\x8d\x94\xa0\xe9\x81\x61\x89\xa3\x90\x7b\x2a\x42\x23\xb3\x72\xa5\xe5\x12\x89\x58\xc6\x94\x0a\x2f\x65\xd8\x47\x7c\x20\x3d\xeb\x91\xe8\x50\x92\x9a\xb6\xff\x66\x4e\x76\x3d\x5e\x21\x46\xc0\x9e\xe0\x3f\xd6\x29\x5d\x64\x56\x0d\x7a\xfd\xe7\xdb\x17\x85\x4d\xf0\x9b\xbf\x77\x69\x20\x92\x70\x6a\x1d\xbe\xcd\x64\xe9\x95\xf8\xaa\xa4\xdc\xad\x6b\xc6\xf5\x6c\x9c\xcc\xe8\xb2\x65\xb6\x32\xc4\xd8\x1b\x8f\xd3\x32\x06\x99\x49\x56\x2f\xe9\x57\xae\x0d\xd6\x58\xbb\xf4\x70\x49\xd7\x2e\xe9\xd7\xae\x0d\x06\x6a\x3d\xbb\x3a\x94\xb4\x37\x5c\x59\x29\x38\x0f\x52\x70\x6e\xbe\x64\xea\x6b\xa0\xad\x18\x99\x52\x4e\x16\x54\x90\x19\x9d\x8e\xc7\x9a\xa1\x2f\x4a\x54\x91\x19\x59\xf8\x71\x6a\x7d\x55\xb3\x6c\x17\x6e\x74\x19\xb3\x74\xb6\xcf\xc8\xcc\xe5\xf2\xd6\x3d\x33\x08\xb2\xdb\xe5\xed\x36\xeb\x5c\x03\x67\x7e\x0d\x9c\x05\x49\x78\x28\x60\xe6\x33\xac\x66\xcd\x75\xcf\x3d\x81\x5a\xb8\xae\x95\x13\x54\x51\xa5\x9b\x17\xd1\x5a\x4f\xe0\x2a\x4c\xe0\x2a\x5e\x03\x62\x4b\xdb\x29\xce\xab\x46\x02\x06\x5c\x86\x90\x12\x15\x8b\x7d\x70\xcb\xdd\x40\xe5\x28\x5c\x99\xd3\x20\xa8\xb8\x2e\x1d\x5d\xe9\x74\xd7\xc1\xef\x97\xbb\x97\x83\x8a\xc1\x7f\x0b\xf8\x54\xae\x8f\xf2\xcd\xef\xdc\xa4\x18\xb0\xd8\xe8\xd6\x54\xf0\xce\xfe\xe1\xbe\x7f\x78\x87\xa6\x42\x37\x1c\xdc\xd5\xed\xde\x65\x76\x19\x54\x02\xb2\x12\x6f\x76\x98\xcb\x06\x15\xf3\xba\x8b\x10\xac\xe4\xe6\x32\x97\x36\x8b\x1d\x1f\x66\x95\x3e\xcc\xd6\x38\x75\xc4\x19\x27\x18\xbf\xb1\xba\xc3\x77\x59\xe4\x35\x94\x72\xa2\xb2\x2b\xda\xa0\x39\xb6\xaa\x31\x8b\x3d\x47\x78\xca\xde\x03\x96\x6e\x9b\x03\x14\x3e\xa0\x1d\x6c\xe8\xd6\x3c\xda\xaa\x54\x8c\x8d\x52\x5f\x15\x33\x2e\xad\x52\xfc\xf2\xcc\xdb\xda\x29\x08\x9b\x9b\x65\xaf\x98\x51\x2c\x14\x9a\x91\x96\x7a\x79\x2b\x6c\xc0\x65\x49\x87\x63\x79\x9f\x3b\x43\x67\xb9\x49\xb7\x5d\xac\x3c\x7e\x20\x0f\x49\xa5\x7f\x36\x47\x2d\xc3\x55\x41\xaa\x1f\x19\xae\x3e\x65\x36\x57\x2a\xea\x7d\x64\x93\x3f\x22\x41\x4a\x52\xe9\x95\xd5\x29\x61\x53\x65\xd1\xbe\xa9\x74\xb7\x96\xe6\x4b\xfb\xa1\x53\x06\x69\xbe\x2f\xb1\x30\x1e\x44\x06\xc6\xe4\x9a\x67\x9d\x16\x9a\x2c\xb1\xd0\x14\xa9\xcd\x25\x03\x0b\xcd\xba\x47\x93\x57\xbd\xa6\x20\x35\xc9\xec\x78\xbf\x6d\xb3\x99\x77\x65\xf2\x46\xf6\x07\x87\x79\xbf\xaf\xbf\x89\x57\xab\xe4\x48\x7f\x27\xbf\x99\xc7\xd3\x76\xd0\x21\xc3\xe9\x3b\xde\x71\xbd\x79\x63\x73\x44\xe3\x86\x03\x1a\xbc\x09\x3b\x6b\x74\x7e\xb3\x47\x6e\x90\x46\xdb\x6b\xda\x1b\x91\xef\x35\x6a\x82\xb0\xf8\x23\x78\x4b\x2e\x00\x9a\xd1\x3a\x60\x3f\xa6\xa0\x08\x9a\x5f\xcf\x91\x02\xc3\x12\x6f\x01\xf1\xb0\x04\x3b\x81\x20\x7c\xf6\x4e\xde\x62\x1c\xb4\xbc\x46\xbd\xcf\x63\x0c\x1a\xa2\x26\x1d\x87\x52\x58\x6b\xf4\x40\xc7\x83\x8a\xd5\x4e\x01\x30\xe1\xf1\x1d\xea\xdb\x98\xdf\x7d\xd2\x17\x52\xb0\x3e\xe9\xf3\xf9\x99\x54\x75\x21\xea\x3e\xce\xb9\x0b\x09\x4e\xcd\xd3\x1c\xa5\xdf\x75\xcb\x03\x92\xcd\x33\xac\xfd\x72\x96\xc9\xa6\x02\xc2\x7f\x11\x4f\xa4\x2b\x3e\xb7\xd6\x52\xf0\x92\xff\x66\x2d\xa2\xea\xc9\x88\xef\xbb\xe3\xc9\xc0\x3a\xfb\x5c\x4d\xfa\x60\xad\x9c\xe8\x7e\xdd\x3e\x1d\x5c\xe3\x23\x77\xc0\x86\xc0\x46\x74\xc2\xce\xc4\x01\xa6\xaf\x3a\x8c\x3f\x63\x7f\x78\x9e\xea\xc8\xff\x81\x03\xbd\xf3\xa0\x17\x1d\x3b\xd6\x4f\xf8\xcf\x8b\x7f\xc3\x7f\x3e\xda\x59\x8e\x41\x78\xd0\x30\x97\x68\x3d\xb9\x99\x3c\xfc\x38\x91\x30\x34\x36\x0d\xaf\xda\xb8\x6a\x0b\x10\x3c\x46\x46\xd8\xc2\xdc\x59\x0d\xc4\x65\xc9\x61\x9f\x5d\x6c\x7c\x2e\x30\xa9\x07\xc7\x52\x3d\x29\xa6\xa7\x91\xaa\xa5\x76\xdb\xdd\xb7\x2a\x8a\x12\x00\x28\x59\x42\x0f\x57\xe3\x12\x2a\x06\xc5\x6c\x66\x45\x5e\xa7\x4c\x20\x45\x14\x06\x99\x10\x20\x2f\x9d\x95\xeb\x54\x52\x2f\x8b\xb3\x89\xfd\xcd\x5f\x16\x67\x41\x25\x75\xea\x79\x6d\x24\x68\xc5\x91\x07\x32\xa9\x8b\x13\xba\x43\xc4\xe0\xac\xb8\x2c\x65\x31\xa3\x57\x56\x6e\x1c\xc5\x8c\x52\x4e\xf5\xe4\x9d\x3c\x07\x53\x6b\xa5\x4b\x23\x99\xd8\xf7\x72\xb9\x44\xdf\x01\x56\xe9\x45\x49\x15\x26\x56\x0d\xb6\x22\x51\x24\x80\xf9\xb5\xf5\x18\x3b\xe9\x9b\x6e\xd2\x5a\xe4\xae\x71\xb7\xe6\x29\x3a\x28\x99\xda\x86\x46\xb5\x03\x57\xd8\xba\x11\x85\xa4\x25\x6a\xb2\xba\x34\xe2\x10\xf0\x4e\x4b\x04\x9e\x48\xc8\xf7\x8b\x7a\x7a\x6a\x5c\x84\x3b\x88\xd3\x61\xe6\xa0\x82\x79\xf4\xe3\x72\xf2\xb8\x84\x61\xf3\x96\xd5\xe8\xa0\x3e\xe5\xd5\x21\xce\x1f\x97\x66\x14\x9c\xf2\xca\x13\xd3\x61\xb5\x38\x4d\xa2\x7e\xd8\xae\x06\x72\xba\xc2\x2b\xff\xe8\x2d\x98\xf8\xb8\xd5\x7d\x22\xf2\x7e\x7f\x85\x57\x98\x08\x13\xa9\xaa\x24\x47\x25\x7d\x59\xd4\xa7\x83\x29\xe3\x25\x39\x29\xd7\x87\x50\xbf\x6c\x3e\x33\x48\xeb\xdf\x4a\x3a\x24\x17\x25\xdd\x21\x4f\x4a\xba\x4b\xbe\xea\xdb\x57\xc6\x9b\x9c\xec\xdb\xdf\xb7\x3a\xf1\xaf\x92\x7e\x2b\xc9\x3b\x9b\xf4\xba\xa4\x51\x94\xda\x37\xc9\xdd\xef\x36\xcf\x17\xfd\xda\x4b\xcd\xdf\xea\x7d\x6a\x48\x9e\xdb\xf4\xef\x90\xf4\xc2\xde\x3d\xb6\xbf\x4f\x21\xf5\x91\xbd\xfb\x50\xd2\xbd\x21\xb9\x65\xef\xde\xeb\xd7\x9f\xd9\x9b\x3f\x4b\x3a\x8c\x02\xba\x97\x61\x74\x0c\x7b\x14\xed\xde\xcb\xbe\x96\x78\xe2\xab\x33\xda\x42\x7a\x87\xfc\xd7\x68\xb8\x1c\xe2\x7c\xd8\xa3\xf4\xcf\x72\xf2\x67\x99\xff\x19\x55\x39\xc9\x13\x61\xba\xf8\x41\x6f\x60\xf6\xd1\x76\x06\xdb\xbe\xc1\x02\x71\x76\x96\xa1\xe1\xe3\x28\xb0\x9d\x7d\x63\x37\x0b\x41\x1e\xf6\xf6\xc0\x7f\x3f\xe4\x0f\x71\x7e\xb7\x2d\xe6\x03\x1a\xdd\xd1\xb5\xf7\x81\x10\x62\x5c\x07\x81\x19\xfd\x43\x22\x46\x86\x4b\x31\xa8\x8d\x2e\xf8\x65\xb5\x5c\xde\x66\x3b\x64\xfb\xf6\x10\x27\x38\x24\xca\x9e\x05\xf6\xf6\x72\x16\x75\x4d\x2c\xef\xdb\xbb\x97\xdb\x02\x47\xb7\x87\x64\x34\x4c\xfc\x50\x37\xf6\xac\x62\x60\xef\x8e\xcb\xe5\xbf\x13\xe7\xba\x9d\x33\xba\x7d\xad\x68\x70\x67\xfb\x0e\x6e\xda\x5a\xbe\x2a\xb3\x4c\xef\x53\x6f\xcb\x2c\xdb\xda\x62\x71\x04\xc5\x3f\x82\x06\xf1\xf6\xf0\xfe\x7b\x27\x23\x48\x06\x80\xdf\x23\xc0\x26\x30\xd1\x17\xfc\x62\xa7\x9b\x13\x9a\x42\x5f\x84\xf6\x43\x68\x19\x4d\x67\x18\x24\x59\x06\x5d\x64\x06\xcc\x67\xbd\xc3\xe4\xe8\xa3\xfe\x21\x43\x4a\xe9\xd7\x32\xcb\xfe\xd6\xbc\x55\xee\xd3\xd0\xae\xce\xba\x5c\xee\xdd\x33\x4a\x95\xbd\x3d\x83\x86\xee\xd6\x83\x5b\xe5\xe4\x96\x59\x0f\x5e\x16\x67\xe8\x00\x42\xd3\x1c\xe2\x1c\x79\xe8\x39\x24\xe8\xad\x52\x2f\x90\x88\x61\xbc\x5c\x8a\x07\x35\xce\xb2\x5b\xa5\x66\xaf\x4c\xbd\xa3\x13\x9e\x6d\xca\xff\x2e\x1a\x94\x5f\xc9\xed\xde\x6e\xf1\x6a\x02\xc3\xa0\xb2\x2c\xe0\x04\xc9\x64\xed\x85\xf1\xe7\x59\x0e\x63\xa8\x0f\xac\x06\x55\x91\x26\x4b\x75\x82\x99\xd4\x60\x62\xdb\x65\x09\x5c\xc7\x86\x15\x9d\xe8\x68\xb6\x2d\xd7\xbc\x0c\x07\x43\xd3\x22\x53\x7d\x65\x8e\xcb\xd2\x01\x39\x06\x01\xeb\x4a\xf9\xac\x8d\x41\x2b\xb3\x0c\xbd\xd2\x45\xe9\x0b\xd8\xf0\x09\x38\xb6\x3c\x29\xb3\xec\x65\x85\x24\x79\x5b\x62\xf0\x50\x91\xba\x23\x89\x8c\x70\x36\x12\xe6\x05\xf4\x12\xba\x9a\x6c\x16\xa3\xbd\x38\x31\x81\x91\x27\xf7\xbe\x80\x56\x86\x5a\xcd\xc6\x6b\xa3\xb9\x80\x80\x89\x21\x9f\xeb\x70\x5d\xe2\x6b\x2e\x4e\x6c\x81\xf6\xf9\xf6\x03\x40\x15\x7a\xe0\x03\x8d\xfc\x26\xe4\x85\xb0\x25\xbd\x60\xe7\xac\xc4\x13\x91\x6b\x7e\xbb\x86\xc3\xfa\x30\x8f\xa6\x21\x0c\xf9\x2b\x5b\xb5\x56\x9d\x31\xf3\x9b\x65\x83\xe0\xd1\x2e\x10\xf2\xbc\x56\x5c\x2a\x5e\x5f\xd2\xbd\xbd\x28\x15\x58\xae\x3f\x25\xfa\x5c\xc6\x0c\x15\x8e\xd4\xe1\x35\x05\xca\x11\xdd\xc6\xf8\xad\xb1\x8f\x7a\x12\x69\x3e\x1b\x05\x3b\x8d\x53\x77\x35\x87\x9d\xb5\x1b\x26\xaa\x78\xbd\xb1\xe8\x2f\x35\x56\x0f\x45\xf7\xf6\xf2\x91\xbe\x5c\x2e\xb7\x5d\xca\xed\x5c\xd1\xe1\x03\x8a\x14\x1d\x0d\xff\x0b\x45\xfb\x4a\x8d\xb7\x1a\x29\x0a\xe3\xc9\xde\x5e\xbe\x7d\x7b\xf8\x80\x42\xdc\xd3\xdb\xee\xf2\x6e\xbe\x77\x3b\x9c\xe3\x2c\x53\xd4\xae\xa7\xae\xd3\xda\x86\x81\x96\x4a\x3e\xa0\xca\x9b\xc7\xf6\x28\x7d\x29\xb3\xec\x95\x44\x02\xaf\xd6\xbe\x57\x77\x11\x44\x91\x9a\x36\x5a\xdf\xd1\x5f\xf9\x33\x89\x14\xf9\x35\x4d\x25\x57\x76\x87\xca\xdb\x04\xd1\x5b\xed\x0a\x37\x47\x42\xbd\x8a\x0d\x9f\xc3\x2e\xa0\x77\x7b\xe2\x27\xc7\x73\x33\x2d\x74\xdf\x60\x62\xd6\xe5\x88\xcb\x87\xe1\xe2\x26\x94\xd9\xb6\xeb\xc6\xd8\x21\x81\x47\x68\x06\xe7\xd9\xbe\x6b\x76\x93\x79\x85\x0c\x4c\x81\xde\xa6\x04\x6c\x53\xcb\xa5\xa8\x00\xdf\xcc\xf5\xcf\x7e\xe9\xce\x03\x5f\xcb\xf1\xd7\x72\x49\x47\x77\x3c\xc0\x8f\xa4\xb2\x42\x78\x3c\x06\x8b\xaa\x4a\x5f\x9a\x95\xc5\x6b\x5c\x95\xd5\xb8\xea\x8e\xe4\x08\x6b\x9e\x4b\x91\x93\xd2\x5b\x57\x49\xa2\xc7\xd7\x5f\x6e\xef\xab\xe9\xbb\x92\xb8\xef\xbf\xb4\xbf\xa6\xed\xb1\xee\x69\xbf\x74\xf6\x13\x12\x96\x0d\xc1\xab\x53\x36\xfb\x20\xd5\x57\x4d\x02\x53\x74\x1c\xfd\xd5\x67\x69\xba\x5e\x10\x45\xff\x2a\x3d\x0f\xe8\x38\x89\x6f\x65\xee\x54\xda\x29\xd9\x76\x6f\x3b\x89\xc6\x76\x0e\xdd\xb3\x7d\x5f\x4c\xb6\xf3\x54\x95\x7c\x51\x82\xf5\xbf\x23\xa2\x71\x97\x33\xeb\xca\x5b\x08\xe7\x36\xb3\x2b\x0b\xcc\xe4\xce\xd5\x8a\xce\x2a\x24\x31\x26\xc9\x88\x7c\x5d\x66\xd9\x68\x78\x1f\x49\xfa\xb0\xdc\xbc\x3d\x1c\xc2\xe0\x32\x26\x26\x2f\x4b\x17\x6a\xa3\xb5\x42\xda\xf5\x83\x2f\x97\xfc\x81\x1e\x27\xcd\x0c\x54\x38\x7a\xdb\xae\x5b\xd9\x31\x85\xb8\x19\x63\x38\xcb\x78\x2f\x9c\xb3\x03\x60\x97\x32\x03\xaf\x55\xa0\x72\xd8\x66\x8e\x73\x33\xc1\xfd\xe8\xa5\x40\xc7\x55\x3a\x73\xa4\xfb\xe8\x71\x95\x7a\x67\x6f\x3c\xf9\x9f\x23\xe2\xcb\x32\xcb\x80\x06\x30\x58\xd2\xca\xe2\xe5\x52\x6a\xb2\xdc\x80\x2e\x8e\x2c\xd2\x93\x45\xfe\x23\xb2\x24\xcb\x6c\x8f\xd2\x37\xe5\xa4\xb5\x94\xbe\x29\xcd\xd2\x91\x37\x06\xc0\x44\xd1\x61\xde\x5e\x79\x5f\x97\x78\x4b\xb3\xac\xc3\x07\x48\xe9\x0a\xc2\xc0\xd8\x02\x7f\x51\x45\x87\x80\x19\xd5\x78\x43\xe0\x2d\x89\xef\xeb\xdc\xa3\xed\xe1\x03\x35\x19\x6d\x0f\xf3\xdd\x7b\xfa\x6a\xf7\xde\x30\x1f\x0d\xe1\x52\xff\xe4\xa3\x3d\x93\x61\x6f\x7b\x98\xef\xb0\x9d\x07\x6a\xb2\xc3\x76\xf2\xdd\x1d\x48\xd5\x3f\xf9\x68\xef\xce\xf0\xbf\x8e\x4a\xa4\xfe\xa5\xaf\xc2\x87\x05\x0c\xe0\xfb\x4a\xd3\xe2\x87\x23\x41\xad\x1d\x09\xb7\xf3\x26\xc5\x5e\x07\x9f\xd9\xdf\x4b\x7c\xc5\xe9\xeb\xd2\xa2\x33\xfd\x0e\xec\x9b\xd9\x9d\x86\xcb\x72\x70\xb4\xa8\x2e\x5f\x72\xf1\x78\x61\xe6\xfc\xcb\x0a\x1b\x0a\x4a\xff\xf4\x31\x2b\x8b\xcb\x97\x15\x51\x7a\xbc\x83\x03\x18\x6a\x90\x8a\xe3\x2d\xa4\x73\xa7\xc7\x10\x8c\xf1\x7d\x2a\x27\xc3\x5c\x6e\xaa\x2d\xee\x1a\xea\xc6\xeb\xcf\x34\x38\x69\xf1\xba\x73\xc5\x1e\x86\x25\xd4\xac\x84\x8d\xdd\x24\xe2\xa7\x1a\x3b\xd3\x2a\x66\xeb\xc2\x8e\xf3\xf9\x47\x3c\x5a\x4d\x81\x4b\x9b\xd4\xd1\xf8\xfb\xd9\xcd\xa4\x8e\x37\x93\xba\xb5\x99\x88\x8e\xcd\x44\xc5\x9b\x49\x79\xb3\xcd\x44\xc4\x9b\x89\x4a\x37\x13\xe1\x37\x93\xda\x6e\x26\xb5\xdf\x48\xa3\xa3\xd4\x7e\x53\x39\xbb\x7d\x67\xa4\x59\xb3\x7f\x63\x5f\xa9\xfd\x96\x02\x9d\x6b\x3e\xda\xdd\x19\xac\x8a\x0d\xb1\x1d\x51\xc0\x16\xcb\xc9\x81\xc0\x0e\xc7\xc6\x04\x85\x75\x4c\xb7\x1b\x9b\x13\x5b\x6c\x0a\xdd\x2e\x29\xa3\x5b\xdb\x44\x17\x78\xef\x9f\x14\x68\x49\x77\xd5\xa0\x84\x37\x43\xea\x6c\xfa\xd0\xb3\xea\xc9\x14\xd0\xf4\xde\x1a\x79\xfe\x35\x9d\x1e\x5b\x23\xf2\x4d\xc4\x76\xe5\xfb\xc6\x8f\x45\xd0\xfd\xb2\x61\x2d\x24\xc6\xde\x26\xd6\xab\xcf\x13\x87\x3e\x2b\x71\x57\x56\x3d\x66\x8e\x4a\xfb\x21\x9e\x4a\xe5\x1c\xfd\x12\x73\xa0\xb6\xaf\x5e\xba\x04\x3d\xe7\x29\x34\xd5\xc6\x2e\xbc\x92\x98\x65\x5a\xc5\xfc\x68\x2f\x37\x7e\x72\xc9\xc3\x61\x5e\xeb\x22\x62\x9b\x9d\x57\x25\x65\x64\xbf\x04\x94\x48\xef\xe8\x08\xa2\x46\xf2\xb6\xa4\x75\x43\x08\xf5\xa6\xa4\xaf\x7f\x20\x79\x8a\x4c\xaa\xab\xe0\x16\x37\x1e\x1b\x6b\x3a\x37\x69\x9e\x7a\xd7\x2d\x7a\x5e\x90\x3f\x39\x0e\xbe\x73\xb7\x78\x07\xe2\x71\x4a\x74\x13\xbe\xdb\x9f\x7a\xe1\x28\xeb\x82\xee\x9b\xba\x7b\xa3\x64\x7e\x8c\x3e\x70\x3a\x24\xcf\x38\x7d\xcf\xe9\x2d\x6e\x25\x5a\x3c\xc2\x9f\xd8\x2f\x3d\x6e\x84\xef\x69\xb7\x96\xfd\x55\xd2\x91\x6e\x7d\xed\x44\x73\x63\x96\xbb\x83\x02\xe1\x21\x3f\x29\xe8\x7e\x49\x4a\x13\x91\xb4\xa6\x6f\x4b\x52\x24\x18\xd7\xc3\xdd\x7b\xa4\x48\x8c\xe6\x8a\x4e\xaf\x78\xeb\x31\xd2\x8c\xe0\x55\x76\xca\x55\x4b\x90\x79\x1b\xaa\x54\xb4\x1c\x7b\xa1\x59\x61\x25\x66\x16\xc7\xad\x88\x24\x14\xd3\x09\x2a\x12\x99\xfd\x34\x71\x1e\x2f\x1a\x56\x82\xd3\x56\x5c\x9b\x86\x04\x63\xda\x48\xc0\x79\xa3\x7c\x61\xe0\xa0\xdb\xc6\x87\x26\x38\xc2\x82\xb6\xe0\x2b\xc8\x8c\x72\x67\x30\x0a\x9e\x14\xc7\x14\xf4\x41\xd6\x3e\x19\x04\xfb\x74\xd6\x18\x24\x61\x25\x3d\xc3\xc7\x4e\x21\x7e\x16\x69\x83\xc2\x49\xf3\x34\x7a\xd9\xa8\x9d\x8e\x43\x18\xf9\xd3\x08\x73\x04\xf5\x4c\xca\x75\xe0\x1b\xbd\x05\x06\x66\xf5\xd8\x54\x6c\x4e\x67\x5d\xca\x12\x4a\xe9\xdc\x64\x38\x77\xd2\xeb\xf1\x39\x08\xad\x2b\x4c\x92\x37\xe8\xb9\xb1\xa5\x98\xdb\xa7\xa1\x53\x67\xae\x53\xf9\x31\x9a\x35\x5c\xbe\x8b\xc4\xdc\x6f\x7b\xef\xde\x08\x36\xa1\xc2\xa8\x21\x7d\x1d\xa2\x81\x80\x0b\x13\xdf\xf1\x6e\xa0\xcb\x11\xad\x78\xc4\xe3\x58\x54\x8f\x23\xc8\xb7\x4d\xa6\x1c\x15\xe4\x08\xaf\x5a\x23\xa0\x29\xec\x04\xab\x1f\xeb\xce\x50\xd8\xa8\x23\x27\x54\x0e\xce\xb8\x38\xd9\x2f\xa6\xa7\x31\x51\x4e\x26\x28\x79\x04\xc4\x39\xd3\x13\xc9\x52\x89\x9c\x80\xa8\xae\x22\x25\xc6\x79\x90\xea\x95\xf4\x04\x84\x7a\x15\xb6\x21\x05\xda\xb9\x49\x0f\x90\x0d\x51\x81\xf1\x95\xd1\x0f\x14\x46\xd8\x76\x49\x2f\x63\x2e\x48\x92\x8a\x14\x78\x5c\x19\xfd\xd1\x25\xb9\xc4\xab\x84\xba\xbb\xc3\xbd\x3b\x64\xd6\x12\xdc\xf9\xb6\x46\x36\x40\xd6\x01\xc2\x1a\x28\xe1\x71\x49\xcd\x66\x8e\xce\x19\x2a\x8c\xd3\xd1\x72\xd9\x7f\xb8\x01\x3a\x81\x0d\xaf\x71\xe8\xe3\xcd\xfe\x46\xe5\xce\x13\x1b\x50\xca\x86\xf7\x4f\x27\x1b\x47\x8b\x7a\x43\xc8\x0d\x37\x2e\x37\xde\x3f\xdf\xb8\x28\xaa\x8d\xea\x8c\x4d\xf9\x31\x67\xb3\xc1\x7f\x8b\xff\x16\x0f\x67\xb3\x8d\x62\xe3\xfe\x5b\x1b\xaa\xdb\xe7\xa6\x83\xc1\xe0\x41\xf8\xd6\xc6\x29\x3f\x39\x65\x6a\x83\x8b\x8d\xfa\x94\x6d\xd4\x8a\xb1\x8d\x5a\x6e\x9c\x29\x79\xce\x67\x6c\xa3\xd8\x28\x65\xa1\x97\xd1\x0d\x2e\x66\x7c\x5a\xd4\x52\x6d\x48\xb5\x71\x56\x16\x53\x76\x2a\xcb\x19\x53\x3a\xb7\xd5\xf7\x0e\xfa\x9b\x47\x4c\xd3\x77\x75\xbb\xa7\x19\x1e\x03\x37\xb3\x8d\x49\x49\x7f\x2d\x50\x49\x0a\x3f\x93\xed\x1e\x19\x3b\x18\xec\xe4\x15\x2d\xc9\x4d\x28\x4d\x16\x1c\xcd\xc8\x69\x89\x66\x60\xbc\x81\x53\x43\xb4\x51\xee\x42\x22\x7e\xd3\x0b\xc4\xe5\x19\x23\x17\x74\x96\xda\xcf\xe8\xf9\x73\x67\x37\x8b\x3e\xa6\x47\x4d\xc7\x82\xfa\x6d\xad\x2a\xcd\x5b\x3c\x5e\x74\xae\xc4\x17\xdd\x1a\x2e\xa7\xb4\x5a\x2e\x7b\x8f\xcd\x68\xbc\xc0\xfa\xb0\x7c\xf3\x66\xcf\x5b\xcd\x5e\x5d\x33\xe2\x56\xfb\x25\x5d\x54\x68\xbf\x8c\xcc\xe7\x6b\xca\x82\xbe\xda\x31\xfb\xde\x7d\xa8\xf2\x36\xfa\x81\x83\x75\xf2\xcc\x88\xa7\x3d\x2f\x22\xb0\xa6\x22\x96\x5b\x72\xc7\x9e\xdd\xd7\x07\xa2\xed\xfb\x2c\xcb\xd0\xeb\x92\x32\x1c\x19\x71\xb3\xfb\x6f\xfc\xb3\x37\x9a\xdf\xf8\xbd\xa4\xb1\x7d\x63\x51\x81\x7d\xf6\x83\x2f\x7a\x08\x7d\x29\x93\xc0\xad\x9a\x03\xbf\x8a\x65\xdc\xfb\xe5\x18\xef\x97\x74\x6a\x9a\x19\x2c\xc3\xdb\xf9\xb2\xac\xf7\x50\xf3\x58\x5d\xd9\xa7\x55\x38\x7d\x9c\x97\x71\x7c\x4f\xc2\xc8\xdb\x32\x02\x3e\x68\x02\xb5\x24\x51\x06\x84\xf7\x5e\x41\xb5\xa6\x3c\xc3\x98\x5c\x96\x89\xd7\x1e\xa9\x23\x43\x4e\xf8\xea\x7e\x49\x99\xdb\xdf\x6a\xcd\x42\x84\xdd\x19\xcc\x09\x02\x53\x01\x0b\xff\x70\xf7\x5e\xb6\x5f\x26\x76\xca\xc0\x63\x7c\x2a\x90\xe6\x4a\x00\xa6\x69\x64\xcf\x39\x23\x68\x78\x97\x64\x3e\x46\x27\x18\x12\xe5\x73\xc5\xaa\x03\xc3\xd7\xa8\x16\x78\x0d\xed\x54\x14\x8c\xe5\x03\xa3\xd1\x97\x98\x70\x7b\xc9\x13\xe4\x97\x55\x77\x55\x4c\xe8\x13\x6f\x43\xe0\xa5\xeb\x22\xe0\x2b\xb9\x76\xb3\x74\xca\x7a\x1b\x8a\x88\x91\x02\x4e\x3e\x66\xac\xf6\xcb\x04\xd2\x2b\x70\xf3\x29\x26\x92\x37\xf7\x48\x52\xd7\x05\xf4\x6f\x16\x9a\x78\x3b\x24\x45\x63\x32\xba\x1f\x77\x57\xf7\xa7\x26\xeb\x3f\x94\x37\x9b\xd3\xfc\x18\xc6\xab\x46\xf8\x09\x54\xd3\x8f\x85\x1e\xe0\x41\x57\x91\xe0\x34\x0d\x77\xef\x92\x98\xbe\x0d\x8a\x75\xb9\x6e\x34\x98\xd7\x38\x60\x10\x82\x16\x3b\x83\x9a\xd0\x7f\x7a\x5c\xa7\x2b\xd2\x7e\x98\x48\x09\x24\xd9\x6d\x0b\x79\x13\x8c\x2b\xab\x58\x18\xd0\x40\xe7\x77\x1f\x78\x80\xd6\x00\x30\xe1\x49\x1d\x2f\x49\xc7\x51\x61\xa0\x8e\xb4\x05\xbc\x97\x68\x6f\x8f\x9c\x35\xcd\x49\x9a\x75\x39\xb3\x2b\xda\x4c\x5e\xcd\x2b\x84\xd3\x16\x3d\x2a\x9d\xb8\xfb\x3a\x31\x84\x3b\x7c\xc6\x27\x56\xf0\x9e\xed\x3e\xae\xc6\xfe\x0c\x4d\x04\xb3\x9f\x3b\xf6\x82\x8c\xd2\x9f\xe3\x9a\xd6\x78\x77\x21\x18\xfe\xff\x80\xda\x86\xac\x13\x76\x9a\x73\xb7\xa4\xb3\x0a\x09\xec\xaa\x9f\xaa\xd3\xa8\x24\xea\x7e\x97\x10\x75\x62\xf3\x26\x89\x5d\x19\xd7\x69\xd7\xe8\x30\x87\x92\xdb\xa5\x84\x11\x9f\x16\xa4\xb6\x46\x38\xd4\x26\x88\x49\xfd\x42\x10\x49\x4e\x87\x51\xce\x48\x52\x15\xd6\x8c\x90\xa6\xf3\x3a\x09\x14\xda\x2f\xe9\xab\xc8\xaa\x43\x2f\x0f\x91\xd3\xc9\xc4\x1d\x6e\xe3\xb5\x01\x89\x35\x8b\x03\xa8\x87\x93\x85\x28\x97\x54\xe4\x8d\x44\xb7\xe2\xf9\xa8\xdf\x56\x9e\xb3\xb3\xdd\xda\x9b\xe6\x82\xfe\xed\xe2\x02\x9e\x09\xa3\x7e\x3b\x15\x80\x21\x0b\x48\x13\x60\xe1\xca\xa5\x00\x90\xa4\x3e\x17\x1b\x25\x36\x87\xcd\xab\x4a\xa7\xe4\xe5\x20\xcd\x42\x98\x98\xc5\x89\x4f\xc4\x6c\x65\xd4\xd4\xf6\xd8\x3c\xa5\xa8\xd2\x7f\x65\x03\xa3\x32\xcb\x2a\x6f\x9a\xca\xd9\xc5\x72\x79\xc1\xc5\x4c\x5e\x18\x7f\x72\x57\x9a\xce\x14\xdf\x9b\xfa\x4e\x01\x8f\x8f\x4e\x07\xaa\x10\x27\x6c\x5f\x2e\x44\x8d\xaf\x2a\x3a\x1d\x14\x62\x7a\x2a\x15\x30\x81\x16\x3b\xd7\x26\xbd\x3a\x3e\xae\x58\x4d\x66\x74\x6a\xfc\x48\x21\xcb\xd4\xdd\x99\xa7\x20\xaa\x0a\x56\xf9\x64\xe6\x2f\x23\x88\x80\xd4\x13\xca\x84\x90\x1e\x92\x33\xba\x35\x22\xa7\xfa\xdf\x9c\x0e\xc9\x39\x1d\x92\x23\x5a\x12\x1b\xb3\xa2\xf6\x7e\x19\x6e\x3f\xbe\x1c\x1f\x99\xb0\xd1\x43\x03\x5f\xb1\xd3\xa3\xf4\xc8\x7f\x6d\xb9\x44\x67\xf4\x78\x73\x81\x89\xce\x35\x33\xb9\xa6\x1d\xb9\x4e\xe9\xf1\xe6\x14\x13\x7d\x50\x0e\xe9\x59\x86\x8e\x37\xed\x3d\x98\x5d\x5a\xbb\xeb\xe0\xee\x73\x49\x8f\x62\x00\xef\x31\x3e\xa1\x47\xe4\x88\x5e\x8e\x23\xf7\x91\x23\xf0\x1d\x31\xcd\x04\x01\xc7\x09\xa5\xb4\xca\xb2\xcd\xcd\x39\x35\x88\x1b\x67\xf4\x18\x13\x9d\x3a\xd3\xa9\xe7\xd4\xc0\x97\x9c\xea\xd4\xf8\x43\x7a\x28\xbf\x75\xfb\x86\x11\x4c\x9d\x50\x74\x44\x4f\x70\xe4\xf3\xb0\x3a\xa2\x97\xab\x8a\x6e\x69\x9e\xe6\x6c\xb9\x84\xdf\x53\x0b\x56\x67\x46\xdd\x19\x0c\xb3\x53\xeb\x75\x60\x7a\x61\x55\x69\x12\xda\x0c\x43\xc8\x30\x4c\x32\x8c\xcf\x05\xbd\x2a\xa6\x35\x3f\x77\x40\xe0\x8f\x59\xad\xcf\x9c\x36\x10\x23\xf4\x3d\x9b\xe9\x67\x79\x49\xfc\x08\x7e\xa3\x07\x55\x5e\xad\xc8\xdf\x35\xed\x8d\xc8\xf3\x92\x4a\xcd\xbb\xe9\xd1\x71\x5a\xc5\x6e\xaa\x61\x0d\x7f\xde\x14\xe5\xee\xec\x0c\x31\x1e\x9f\x54\xe8\x39\x08\xdd\x9f\x97\xf4\x79\x19\x4d\xeb\x55\xba\xc5\x3c\x2f\xf1\x38\xfe\x8c\x01\x96\x64\xa4\x0a\x61\x5f\x9e\x97\x96\x5b\xfb\xa6\x4b\x0a\x20\x61\xfc\x18\x8d\xee\x64\xdf\x00\x46\xf9\x79\xec\xce\xd3\xef\x63\x32\xda\xbe\x97\x7d\x33\xaf\x5d\xe8\xd7\x12\xae\xd3\x9d\x6e\xcc\xf3\x27\xf4\x02\x50\x2c\x6c\xea\x93\xee\xd3\xd2\x93\xc9\x13\x83\x5e\x91\x3f\x49\x56\x15\xbc\x72\x06\xf6\xa3\xe1\x0e\x7c\xd3\x2a\x2b\xa7\x25\xfa\xff\x71\xf7\xee\xdd\x6d\xdb\xd8\xa2\xf8\xff\xe7\x53\x48\x3c\xbd\x2c\x30\xde\x56\x24\xe7\xd1\x96\x2a\xa2\xe3\x24\xce\xd4\xd3\x38\xce\xc4\x49\x3b\x1d\x45\x3f\x5f\x5a\x82\x2c\xd6\x14\xa1\x92\x90\x1f\x91\xf8\xdd\x7f\x0b\x1b\x0f\x82\x14\xed\xa4\x33\xe7\xde\xb3\xd6\x5d\xcb\xcb\x22\x41\xbc\xb1\xb1\xb1\xf7\xc6\x7e\x1c\xa7\x6a\xfc\x35\x69\x45\x4d\x47\xea\xd9\x7d\x99\x60\xa6\xd2\x3d\x32\x5d\x4d\x53\x4d\xb6\x79\xf0\x24\x6a\x14\x1a\xf4\x0f\x9e\x36\xf2\x7c\xdf\x96\xe7\x4b\x75\x3f\x89\xbe\x90\xe1\xfb\x68\x9d\x92\x14\xd6\xec\x38\x85\x82\x42\x9a\x92\x35\x2d\x77\x97\xf9\xff\x0c\xac\x24\x73\x72\xc4\xae\x33\xb8\x41\x0c\x0e\xb7\xec\xa8\xe7\xc1\x32\x14\xec\xa8\x57\x87\x66\xb8\xe9\x32\x76\x1b\x2a\x38\xb9\xad\xa3\xe1\xca\xfe\x59\xdb\xd4\x59\x3d\xbf\x2e\xe9\xca\xed\xb6\x9b\xa1\xab\x18\xa6\x35\xb0\x54\xca\x63\xf4\x54\x6b\x91\x0d\x52\xe8\x5a\xdd\xc7\xf3\x47\x8c\x15\xf9\x06\x89\x51\x60\xd4\xf3\x0b\x75\xa0\xc8\x91\x53\xd7\x57\x34\x43\xd4\xed\x6a\x03\xea\x38\xe7\xb6\x57\xef\x44\x91\x68\xf4\xdf\xed\x2a\xf8\xbe\x37\x83\x36\xb8\x2f\x49\x63\x54\xbd\x99\x79\x30\xfb\x1e\x6e\xa9\x8b\x19\x55\x84\xe1\x22\x23\xb7\xaa\xeb\x37\xac\xe8\x21\xfa\x80\x4a\xce\x74\xc4\x8a\x1e\xcf\x66\xea\xf3\x11\xbb\x71\x21\x09\xfc\x13\xf1\x76\x44\x6e\x1b\xa7\x20\xbb\x81\xdb\xda\x19\xa8\x15\x46\x97\x49\x46\x8e\xe0\x56\x6b\x9c\x5a\x44\x4c\x23\x72\xc4\xc8\x0d\x6b\xf4\x79\xbb\x9d\x55\x47\xe3\xcd\xd7\x1c\x8d\xe4\x88\x1d\x35\x4e\x47\x58\xb3\x5b\x3f\xc2\x86\x69\x13\xd2\xaa\x3f\x76\xc8\x6b\x0a\x05\x73\xe3\xc6\x41\x8f\xd2\xc8\xcb\xc6\x33\xb4\xbe\xeb\x1e\xf5\xf8\xad\xe4\xd9\x2c\x0c\xd3\xe7\x45\x18\x92\x35\x2b\xa0\x60\x29\xa4\x6c\xad\xda\x9b\x67\xe4\x16\x52\x0a\x33\xfd\x54\x50\x58\x87\xe1\x2c\x0c\x89\xe2\x4c\x8f\xbc\x23\x7a\xbb\x3d\xf2\x0e\x68\x75\xf2\x21\xc8\x54\xc9\xfa\x18\xc6\x0f\x02\x1f\xd5\x27\x77\x5c\xa3\xd1\x9f\x2d\xe0\x1d\xdb\x98\xae\xf3\xab\x45\x23\x37\xec\xc6\x78\x2d\x41\xd0\x27\x14\xed\x2c\x70\x95\x88\x6e\x11\x6c\xfd\x14\x8e\x8c\x2d\xdd\x61\x9a\x62\xee\x82\x50\x48\x9f\x17\x23\x72\xd4\x8b\x67\x33\x5d\xc1\x8d\xca\xa6\xa7\x80\xe8\x1e\x80\x6b\x90\x46\xe4\x46\x55\x7f\xd4\xf2\x0d\x6a\x75\xa0\x3b\xe5\x1b\x36\x9e\xe0\x31\x7b\xc4\x6e\x87\x6a\xf5\xaa\x4d\x32\xa4\xea\xd4\x3b\xf2\xce\xf1\x1b\xed\xcb\xc9\x69\x9d\x1f\x41\xca\xe7\x32\x3a\xea\x15\xd3\x5c\xa4\xe9\x1b\x3e\x97\x20\xc5\xca\x25\x7c\x10\xab\x52\x3b\xf3\x6a\x95\x73\xe1\x94\xa9\xfd\x6f\x1c\x69\xc0\x2d\xeb\x0f\x6f\x7f\xbc\xb1\x76\x59\xb7\x7b\x7b\x54\x81\xfc\xf8\x76\x42\xad\x87\x14\xaf\x29\x76\xd4\x53\xcd\xab\xa9\xa8\x7d\xfb\x20\x56\xec\xa8\x27\xc5\xaa\x54\xc7\x65\x77\x99\xc1\x75\xc6\x96\x95\x3b\x02\x7b\x4a\x40\xf3\x80\xbb\xad\x82\x8c\xba\xc3\xed\x6a\xe7\x70\x7b\xfc\x2c\xbc\x0a\xc3\x24\x25\xb7\xd0\x44\xc7\x78\xbc\x5d\xd1\xcd\x8d\x81\x62\x24\xf9\x4e\x55\x0d\x75\x77\x4c\xa7\xba\xee\x97\xcc\x3f\x1f\xed\x25\xde\x71\xdd\xec\xf5\x86\xbd\x6c\xdc\x88\xdf\xb0\x97\x65\xcb\x7c\x9e\x8e\x4e\xc9\x0d\x8d\x4e\xdd\x00\x6f\xca\xff\x6b\xe8\xdf\xea\x78\x1f\x0b\x7d\x17\x9d\x68\xa2\xc7\x9b\x6c\x35\xfa\xd7\x29\x75\xea\xde\x1c\x7e\x4d\x99\xac\xb4\x47\x71\x2d\xbc\xc9\x97\xf5\x36\xa1\xf6\x66\x1a\x33\x37\x5d\x88\x24\x5b\x95\x26\xc3\x90\x18\x3d\xf3\x86\x02\x91\x1c\x29\xd6\xe8\xa7\x74\xf4\x31\xdd\xdb\x8b\x88\xd5\x33\xe6\x34\xc2\xc7\x36\xe1\x6a\x11\x86\x37\x85\xef\x1a\x13\x72\x7b\x85\xfe\xd9\x4e\xa0\x56\x71\xe7\xec\x4d\xea\xf4\xdc\x9d\xe4\xc0\xe9\x1f\x6f\xb7\x7f\x58\xb7\xe2\x15\xbf\xbf\x68\x4a\x0e\x1d\x00\xf2\x3a\x00\xa2\xcb\xf6\xa7\xcf\x42\x74\x8b\xb2\x4b\x10\xa0\xb4\xee\xe9\xe0\x20\xe4\x74\xbb\x7d\x9d\x6e\xb7\xe4\x35\xda\x78\xfc\x24\xc8\x0f\xdf\xd5\x9c\xa2\x98\x6e\xa1\xa6\x02\xf6\x45\x61\x83\xdd\xa5\xae\x6c\x41\x54\x0f\x93\x39\xf9\x41\xd1\xfe\xbf\xa6\xb6\x73\x3f\x7c\xf7\xe3\xaf\xe9\xe8\x87\xef\xa2\x5f\x53\x3b\x52\xad\xc4\xff\x51\x10\x0e\xd7\x85\x7f\x8b\x7e\x6d\xaa\x30\x70\xf7\x22\x35\x72\x86\xee\x60\xa8\x2b\x7b\x81\xd2\x06\x6b\x0c\x70\xbf\x7c\xe3\xf1\x80\x5a\xf7\xb4\x57\x3a\xc4\x9e\x61\x2d\xb9\xa7\x9c\xe0\x31\xa3\x7e\xcc\x53\xb5\xd9\x8d\x68\xc4\x0a\x51\xd4\x7c\xd5\x7c\x30\xfc\x39\x1f\x51\x29\x79\x0a\x19\x05\xa1\x7f\xcb\x4a\x49\xc3\x33\xb8\xba\x6f\x87\xa1\x12\x47\x66\x84\x09\x06\xd4\xf9\x0e\xa4\x73\xe6\xd4\x92\xaf\x52\x26\x01\x01\xa8\xdb\xaf\x66\xf6\xa2\xb0\x36\x0b\x53\x8c\x00\xcf\xb4\x05\x11\xfb\x5b\x4c\x32\x90\x3e\xf0\x57\x9a\x05\x56\x6f\xde\xff\x16\x86\x5a\x3f\xc3\x55\x6c\xba\xa8\x86\x52\x29\x80\x63\x6b\x68\x04\xe5\xb6\xaf\x15\x36\xed\xaa\x28\x98\x92\x66\x2e\x2f\x0a\x92\xe9\xa2\x95\x0a\x58\xf5\xd5\xde\xac\xd7\x2e\x4e\x5a\x36\x63\xf6\xb0\xc9\xd1\x76\xdb\xea\x89\xf4\x2b\x6f\x47\x72\x4a\x71\x1a\x33\xe0\x6c\x99\xe2\xcf\xdf\x62\x22\x15\x26\x6c\x9d\xc6\x4c\x4d\x63\xb6\x3b\x8d\x95\x1a\xa1\xa7\xe9\x50\xcd\xec\x5d\x51\x0f\xc3\xc8\xbd\xbb\xc9\x4a\xa7\x20\xef\xcd\x78\xca\x25\x27\x92\xc2\xa9\x89\xcd\x70\xa6\x63\x52\x1a\x35\xf4\xed\x16\x1f\x6e\x50\x25\xb2\xa1\x23\x89\x5a\x5c\x87\xe9\x8f\x4f\xfb\xfd\x11\xaa\xb0\x9c\xa5\x34\x3a\x51\x08\x21\x3a\xd7\xfa\x59\x61\xa8\x55\xf9\xe4\xae\x3e\x60\x18\xca\x1f\xd1\x7f\xe6\xae\x3a\x20\x02\x89\x6f\xa8\x77\x5b\x53\xb8\x69\x9a\xdd\x59\x2d\xfe\x6a\x28\x06\x65\xf7\x35\x2d\xff\x73\x4a\xb4\x72\x2f\x70\x63\x60\xd6\x84\x51\x59\x81\xe6\x75\xda\xf0\x09\x59\x59\xa4\xef\x06\x54\xcd\x6a\xce\x04\x45\x33\xd6\x25\xca\x06\x6b\x77\x2b\x5d\xf4\xd6\xb1\x12\x4e\x7a\xf9\x3e\x66\xdd\xbe\x33\x09\xcd\x7f\xac\x9c\x5f\xa9\x2f\x83\x9a\x97\xab\xc7\xd1\xeb\x58\x8d\xee\x43\x4c\x76\xf5\xf6\x0e\x31\xe2\xc4\x13\x13\x6d\x23\x0c\x8d\x06\x90\xe8\x69\xdf\xee\x9e\xcc\xfc\x6b\x3c\xa5\x6b\xbd\x91\xba\x2f\x34\x3f\x72\xc5\xad\xd8\x71\xe1\x74\x9e\x90\x5a\xb0\x95\x86\x81\x72\x43\x47\x67\xc7\xc2\xdf\xd8\xa8\x09\x63\xca\xdf\x3b\x37\xb1\x35\x61\x2a\xc8\x6f\x02\x44\xef\xdc\x4c\x19\x8a\x78\x68\x33\xa1\x16\x77\xcb\x9a\x3d\xdb\xa0\x3a\xf5\xf8\x19\xd5\x81\xa9\xa3\x6b\x78\x51\xe1\x1b\x22\xf7\x30\xcc\x9f\xb3\x6c\xf4\x93\x8d\x68\x1a\x91\x5d\xff\xfe\x15\x2c\x49\xe6\xa2\xf5\xd2\x91\xf3\x74\xa2\x5d\x10\x0e\x5b\x0a\xd6\x3a\x8c\xa1\x3f\xf2\xf6\xd5\x78\xce\x32\x68\x8b\xc6\x81\x10\x63\x87\xe3\x42\x85\x0f\x1b\xae\xf2\xfd\x4b\x0d\xb1\x33\x19\x61\x48\x44\x33\xae\xb9\xa8\x42\xbf\x52\xe3\x0f\xde\x1f\x6f\x37\xf7\xe5\xf7\xf6\xe0\x70\x63\x2f\x11\x70\x8d\x58\x0a\x9f\xab\x98\xaf\x7c\x37\xfa\xbd\x1f\x06\xd6\x4c\x81\x76\x88\xe7\x5f\xe4\x34\x3c\x73\xc9\xdd\x04\xff\x2a\x87\x02\xdf\x8d\x94\xbe\x14\x44\xc2\xbc\xda\x78\x90\x27\xc8\xfd\x83\x60\x7f\x4f\xf4\x45\x89\x84\x1c\x38\x08\x95\x58\x0f\xc0\x04\x3b\xba\x4a\x95\x33\x1c\xd1\x7a\x57\x6e\xe7\xd4\x3a\x14\x62\x8c\x89\xde\x37\xdf\xe8\xaf\x26\xb6\x03\xaa\xa6\xec\xc4\xff\x37\xe3\xd9\xd1\x2e\xba\x16\xea\xe4\x30\xc2\xee\x6e\x7f\x88\x3b\xd0\xb8\x24\x51\x73\xdc\x56\x8f\xea\x9e\xde\x90\x9e\x63\x23\x93\x32\x32\xbf\x5a\x30\x18\x2b\xec\x61\x64\xe4\x79\xdb\xc1\xa7\x11\x5a\x9b\x7e\x56\x18\x2e\xd5\x4c\xe6\xa0\xf5\xcc\x4d\xcf\x73\x76\x9d\xd4\x42\x2f\xe1\x16\xae\xf9\x86\x7c\x9d\x5c\xf0\x9c\x49\xb8\xd5\xc5\x51\x9d\x57\xb2\x57\x71\xb5\x18\xdd\x3e\xba\x59\xb1\x7e\x62\xd5\x84\xf5\xe1\xad\xcb\xa0\x57\xca\xf9\xa1\x77\x77\x74\x53\x13\x4d\x98\x47\x6a\xa2\x15\xcc\x7b\xee\x2f\xff\x9b\x01\xab\x3a\x2b\x70\x55\xf7\xb5\x17\xde\x73\x35\xee\x75\x41\x37\xee\xd1\xdc\x1a\xa9\x73\xf0\x7c\x2a\x45\x3e\x94\x4c\x12\x74\x04\x9b\xf3\x62\x9d\x4a\x0c\x54\x85\xda\x39\x35\xeb\xf0\xbe\x5f\x5d\xa8\x0d\x45\x0d\x9f\x06\x55\xdd\x03\xbf\x1e\x5a\x52\x78\xb0\x8e\xaa\xdc\x41\xa3\x1c\x2d\x4b\x22\x28\x0c\x10\x4a\xec\x18\x34\x65\x29\x6c\xc6\xa1\x33\x07\x71\x49\xa0\xb7\x2c\x13\x3a\x84\x6f\x7c\xc9\x1a\xb3\xd2\x02\x36\x0e\x0d\xbf\x2c\x08\xa7\xa3\x41\xd4\xaf\xce\x53\x5d\x08\x2f\x42\xdd\x96\x61\x8c\x15\xae\xcc\x60\x30\x34\x3e\x09\x66\x55\xda\x13\x8b\x89\x0e\x70\x10\x9c\xfd\x55\xa0\x4f\x45\x1b\xd3\xb2\xd3\x8f\x24\xfb\xec\x01\x4f\x65\x71\x50\xe9\xd9\x48\xf6\xe6\xe1\x1c\x2a\xcb\xf9\xc3\x59\x9e\x44\x92\x9d\x78\x59\x54\x3f\x34\x4e\xe3\x14\xc3\x2e\x57\x2e\xf7\xea\x44\x7b\xff\x19\x08\x08\x82\xca\xb0\xd5\x80\xb2\x0b\xf1\xe0\x90\x63\x93\xd2\x80\xcf\x1a\xfb\xe6\xd0\x80\x76\xb4\x0d\x16\xd1\x5f\x05\xc9\x41\x50\xcf\xbd\xec\x17\x6b\x7c\xf3\xe7\x6a\xc4\x03\x57\x93\x27\x79\xdd\x01\x8f\x53\xaa\x71\x0a\xa8\xf9\x4e\x14\x8e\x03\xad\xd6\xde\x0c\x16\x5e\x79\xfd\x69\x39\xb9\x46\xa2\xe7\x7b\x27\x80\x34\xd1\x6a\xe7\x33\x8d\x4e\xb4\xb6\x29\x85\xdd\x00\x5a\xb6\x98\x82\x29\x41\x15\x31\x05\xde\xd1\xed\x68\x31\x6c\xb2\xa2\x66\x8c\x8a\x25\x0a\x51\x63\x76\x93\x91\x7b\x49\x1d\xff\xaa\x09\x6e\x63\x26\x41\xb0\x23\x45\xe6\x51\x10\x46\xd9\xfa\x0c\xcf\x1e\x34\x84\xd2\xa7\x8d\xf1\x61\x39\xcc\x86\x34\xf3\x03\x85\x3c\xf6\x19\xc7\xed\xa0\x7f\xf0\xa4\xe6\x62\x10\x11\xe3\x5b\xbb\x52\x99\x21\x0d\x1d\x4a\x6c\xc0\x91\x73\x51\x7b\xe8\xa2\x93\x69\x8a\xff\xa5\x5b\xb7\x76\x58\x48\xee\x77\xbe\xa4\xa7\x9e\x89\x9e\xf5\x47\x04\x97\x19\x42\xc6\xc8\x86\xb3\x77\x4e\x13\xf0\xc3\xae\x27\xf6\xc1\x33\x0a\xc7\x26\x2c\xed\xbd\xe4\x6b\x4b\xb8\xbd\xfb\x08\x57\x69\xa3\x5b\x99\x79\x49\x6b\xa7\x04\x05\x69\x2f\x61\x3c\x22\xc6\x9f\x86\xac\x25\xb2\x8b\xa3\x00\x1b\xc1\x58\xbe\x40\xf3\xc2\x0e\x40\x3b\x05\x33\xbb\xe4\x2f\x6b\x90\x10\xf9\x6b\x69\xcf\x35\x8b\x78\xbe\xb8\x65\xcf\xff\xdc\x96\x75\x21\xe9\x4c\xa3\xcd\xae\x36\x7a\xf0\x7d\xc3\xf1\x70\x7b\xb1\x0a\x0e\x76\x46\xd0\x57\x07\x72\xbe\x43\xdf\xef\x0c\x63\x27\xf4\x3b\x24\x4c\x18\x2f\x1e\xfa\x62\xbe\x51\x05\x5e\x90\x23\x97\x50\x34\xb9\x84\x46\x02\x4b\x9c\x02\x3a\x4d\xe6\x78\x45\xaf\xd9\x8e\xbe\xf6\xa2\xf6\x26\x27\x05\x24\x74\xd4\x8f\xfa\xdb\x56\xaf\x22\xbd\xf3\x69\x9c\x4e\xd7\x69\x2c\xb9\x8e\x66\x3b\x7b\x91\xc8\x62\x74\x4f\x3a\x56\x16\xf9\x9c\x3b\x9e\x6c\xa9\x9b\x24\x24\x0b\xed\x4b\x18\x76\x3d\xc6\x70\xe3\xe3\x24\xa7\x16\xe9\x04\x22\x16\x2f\x16\x0e\xb0\x31\x2e\xb4\xf1\xa2\x23\xa9\x65\x8c\x8b\xa1\x55\x90\x2f\x6a\x3e\x6f\x3d\xee\x75\x4a\x37\x29\x33\x0b\xe7\x2c\x84\xd6\x6c\x5a\x0b\x83\x66\xeb\x5b\x6b\x91\xcb\xba\x67\xe6\x5e\x1b\xef\x2b\xee\x64\xdd\x13\x17\x05\xcf\xaf\xf5\xd0\xc3\x84\xd2\xcd\x00\xaf\x59\xb4\x43\x3a\xb2\xde\xf1\xf0\x82\xfa\xd6\x05\xac\xa9\x5a\xa7\x86\x27\x81\x0c\x87\xd3\x8c\xb0\x59\x31\x5b\x6b\x56\x78\x7a\xde\x61\xb8\x6e\xab\xa0\x99\x88\x15\x24\x6e\x9a\x14\x88\x36\x55\xfb\xb1\x5c\x33\x91\x39\x59\xcb\x9a\xad\xb5\xc9\x85\x5e\x89\x94\x0d\xfa\xd5\x18\x0b\x4d\x06\x55\x7e\xff\x15\xfe\xb3\x13\x5b\xcd\x77\x4a\xad\xee\x23\x2b\x2a\x09\x57\xca\x8a\x46\x40\x45\xed\xdc\x7d\x93\x7a\x0a\x18\x3e\x2f\x57\x30\x4f\x3f\x6d\xe3\x96\xde\xe9\x55\xaa\x0a\x75\x21\xcf\x3f\x65\xc1\xd2\xd2\xec\x5b\x51\xdf\xaa\xf7\x9c\x19\xce\xc5\x92\x65\xcc\xb5\xfd\x5e\x7d\xcb\xd2\xaa\x2a\xcb\x55\xe5\x2c\x57\x24\x62\x42\x04\x24\x95\x6d\x81\x0f\x21\x74\x87\xcb\xba\x1f\xf9\x39\x84\x9b\x20\x55\x67\xbb\xd2\xe8\x05\x9c\x98\x91\x81\xce\xa6\x33\x25\x86\xe8\xb2\x72\x55\x7b\x06\x5a\xec\xd5\x52\x93\x5f\xe0\xbb\x2f\xe3\xdd\x2f\xa0\xdb\x7f\x87\xdf\xb0\x5c\x22\x72\x7f\x23\xc2\x59\xb7\x0f\xc8\xf9\xd1\x08\x3d\xc1\xd9\xe9\xbe\xd4\x44\x8f\xa0\x96\x9b\x42\xde\xa8\xc1\x4b\xf1\x6a\x54\x6e\x59\x9d\xd8\xe0\x81\xe8\x6e\x88\x75\x6f\x8c\x1f\xeb\x23\xa3\x69\xe2\x64\x73\x57\x45\x15\x3b\x05\x1d\x10\xa9\x1e\x73\xc0\xc7\x2b\x7e\xc7\x32\xfd\xe8\xa2\xec\xa1\x8f\x22\x3c\xfb\xf0\xd1\x62\x2c\xcc\xe3\xb8\x45\x5d\x91\x9a\x4c\x7c\xf2\x67\x57\x8f\x48\xa5\x26\xd9\x8c\xdf\xb2\x3e\x98\x7a\xe6\xde\xa7\x9a\x53\x6e\xa9\xd3\x6a\x2e\xbf\x31\xa5\xce\x31\x63\x92\xcf\x77\xd7\xf2\x78\xae\xbc\x75\xba\xea\x66\xae\x9f\x2b\x82\xcd\x74\xc6\x0f\xc9\xa7\xde\x6b\xd1\x74\x55\x42\x53\x02\x5f\xcd\x4a\xd3\xbe\x11\xeb\xdf\x11\x9b\xa8\xd4\x3a\xe8\x54\xc2\xd2\x53\x6f\x41\x2c\x8d\xc3\x6f\xfc\x75\xaa\xf2\x22\xf7\x55\x29\x40\x20\xcf\xb5\xca\x85\x14\xc6\x90\xa3\xcb\x7b\x49\xa1\xdd\x3b\x59\xe9\xb6\x57\xfa\xac\x21\x96\xad\xb4\x6d\xea\xb4\x55\x36\x22\x24\x63\xaa\x5f\xe8\xb5\x58\xb1\xae\x57\xfc\x0e\xb8\x31\xf6\xa9\x2d\x2f\xaf\x33\xeb\x1a\x08\xb8\x0d\x92\x50\x41\x08\xaf\xb9\x21\xaf\xe6\x82\xd7\x1d\xb9\xd3\x88\x64\x4d\x68\xc8\x6a\x0b\x96\xed\x2c\x46\xb6\xeb\xf8\x3d\x6b\x6a\x0f\x53\x68\xf7\x0d\xd3\x1e\x3c\x75\xc7\x27\xce\x4e\xf0\xd4\xac\x11\x3c\x35\xfb\x42\xf0\xd4\xec\x0b\xc1\x53\xb3\x07\x82\xa7\xca\x66\xf0\xd4\xac\x35\x78\xaa\x6c\x0f\x9e\xda\x24\xc4\xeb\xc1\x53\xe5\xbd\xc1\x53\xa5\x1f\x3c\xd5\x0b\x41\xe9\xdc\xfc\x41\x66\x76\x34\xd7\xbf\x90\xe1\xa6\xc6\x58\x43\xbe\xd7\xb8\xdf\x1d\x20\x2b\x4c\x6f\x1d\xab\x1e\x68\x06\x92\xb7\x5d\xae\x72\x8a\x70\x8e\x56\x54\x5e\xd0\xe3\x9d\x80\xfe\x9c\xa6\xec\xa9\x55\xe5\xac\xfb\x4f\xed\x64\xdc\xa2\xcc\x0f\x85\xf5\x0b\xa4\x0e\x3c\x01\x89\x73\x74\x9b\xf2\x28\x65\xdf\x83\xd8\xb2\xef\x7c\xe1\x6e\xee\x92\x6b\xae\xf1\x85\xad\x91\x70\xb5\x37\x06\x07\x90\x81\x84\xef\xb7\xa2\xb1\x23\x84\x02\x69\xe9\x9e\x1a\x90\x94\xd8\x00\x95\xd3\x46\x75\x8f\xb1\x3a\x55\x19\x96\x9d\x62\x59\xaf\xda\xe9\x43\x95\xad\x1b\x95\xfd\xe0\x2a\xf3\xab\x58\xdf\x53\x85\xd5\x24\x50\x73\xdc\x94\x9c\x72\x2f\x04\x93\x8b\x13\x5a\x49\x46\xb1\xf5\x44\x4d\xd8\xa0\x5f\x97\xab\xc4\x2a\xf1\x87\x7a\x5a\x81\x19\x1b\x6e\xbb\x67\x98\xf8\xa4\x9e\x38\xc7\xc4\x67\x90\xd7\xd4\x59\xf5\xb7\x95\xfa\x76\x70\x70\x8f\x58\x66\xf0\xb8\x6f\xd8\x36\x3e\xe2\x91\x1d\x85\x2f\xa7\x21\x52\xcd\x51\xda\x3a\x45\xdc\x0a\xc7\xf2\xdd\x20\xb5\x89\x6f\x36\xf3\x61\x07\x6b\xeb\xa9\xff\x0e\x38\xe4\x20\x55\xb5\x4d\x8f\x2a\x9e\x39\xc0\x3b\x77\xdb\xe7\x97\x7d\x66\x2e\xbc\xbe\x58\xfc\x7d\xb3\x38\x8e\xe8\x89\xa3\x5d\x1c\xb8\x8f\xaa\xc7\x68\x3c\x31\x78\xbc\xb5\x76\x5f\xf6\xbb\xa9\xb1\xc5\x51\xd3\x03\xb7\x41\xcf\x2f\x6d\xc5\xd8\xe5\x64\xb9\xd2\xd3\x88\x95\x46\xbc\x57\x4f\x28\xfd\xb9\x7b\xeb\xba\xef\x08\x10\x73\xe6\xd7\x8d\x94\xb4\x7b\x44\xaf\x69\x4b\xa7\x54\xb6\x99\x3e\xf5\x60\x3b\xe4\x95\xbe\xd7\x26\xa1\xf6\xb5\xb2\x62\xd0\xfd\x69\x9a\xe0\xd7\x1a\x31\x7c\x94\xeb\x9c\x7a\xa9\xca\x5a\xaf\xbf\x86\x8a\xda\x35\x6b\xa8\x25\xfb\xe6\x0b\x15\x21\xe2\x29\xed\xbb\x44\xef\xbe\xd5\x11\x24\xbb\xc6\x06\x2e\x7b\xdd\xa4\xa0\xa2\x69\xee\x4b\xf7\x6d\x21\x3c\x3d\x82\xf3\x06\xc1\xb0\x5b\x87\xb3\x49\x6b\x33\x8a\xc0\x38\xfe\x59\x18\x66\xcf\xb5\xc5\x1d\xf3\x60\xe0\xe4\x8b\x55\x43\xab\x5b\x9b\x61\xf6\xa3\xbc\xcf\x74\x42\x52\x20\xf9\x73\xb9\xdd\xf6\xd1\x6a\xc5\x99\x41\xec\xe4\x92\x5f\x6d\x5e\x21\xbf\xde\xbc\xa2\x1a\xdb\xa1\x19\x9b\x7c\xbe\xab\x84\x54\xf5\xdd\x9f\x73\xe9\xdb\xe7\xec\xcc\xb1\x99\x46\x22\x9f\xb3\xec\xbf\xc3\x22\x45\x3e\x6f\x2b\x70\xdf\x74\xed\x0d\x28\xa8\x91\xb4\xd6\xf6\x80\x93\x21\xe9\x6b\x04\x1c\x37\x96\xbb\xe9\x5b\x05\xb5\xb6\x32\xeb\x0c\xb1\x65\x7e\x7d\x3a\xf6\xb3\x87\x7b\xed\x45\xbe\x75\x13\x91\x68\xcd\x81\x94\xad\x92\x5e\x61\x0c\x8f\x87\x09\xfb\x39\x25\x09\x08\x48\xe9\x90\xe3\x55\x33\xdd\x48\xbc\x6d\xfa\x27\xc7\x68\x59\x2d\x37\x5d\x54\x7b\x75\x1c\xd8\xf0\x2f\x3b\x86\x4b\x7d\xa3\x74\x54\xb0\xcc\xb3\x27\x2e\x1a\xf6\xc4\x45\x43\xda\xc8\x6f\xad\x99\xb6\xf4\x42\xc7\x5d\x0b\xa2\xf9\x27\x4a\x37\xf5\x32\x8d\x00\x6d\x27\x86\x6a\x3c\xe1\xf9\x25\xd7\x81\x3f\x5e\xd6\x6b\x2d\x4b\x55\x41\x9b\x2d\x6e\x41\x87\xcd\x31\x0c\xb4\xf7\x9c\x86\xce\xcd\xd4\x44\x9c\xd3\xe1\xb0\xc8\x14\xa3\xd0\xdc\x09\x92\xc1\x14\x0a\x4f\xba\x95\xb1\xc2\xc4\x55\x65\x6b\xd1\xe0\x21\xa4\x1d\xed\x48\x56\x68\x32\x92\x4d\x34\x9a\x01\x91\xac\x48\x30\x9a\x28\xdd\xf5\x63\xcc\x4b\x2f\x06\x8c\xbb\x9a\xcd\x35\xa5\x9b\x6b\xd9\xb4\xf3\xd8\x9b\x53\x98\x26\xe8\x78\x11\xfe\x9e\x12\x45\x6b\x42\x52\x01\xcd\x9b\xc2\xdc\x6e\x69\xae\xc9\xca\xed\x8c\x34\xce\xb7\x70\x73\x64\xce\x6e\x00\xba\x46\xa4\x6e\xbe\x13\x6c\xae\x6a\xef\x95\x81\xf8\xcc\x0b\x56\xd6\xbc\xcb\x77\xe7\x75\xe5\x12\x22\x0c\x51\x6b\x2a\xbf\xf3\xbc\x7c\xba\xf7\xda\x2e\x78\x6d\x1a\x30\x0d\x01\xb6\xe0\xcb\xd8\xcc\x87\xaa\xc4\x8b\x86\x8e\x91\x62\x33\xed\x59\x6c\xae\x0c\xb2\x30\xec\xe2\x56\x74\x17\x28\x20\x14\x6d\xf1\xd8\x5c\xd0\xa8\x7f\xda\xe1\xe1\x77\xda\xff\xe1\xe8\x71\xd4\xa7\xc3\xdc\x73\x5b\x27\x3c\x4a\x22\x87\x38\xc1\xdb\xbd\xf1\x59\x36\x61\x79\xe5\xcc\x45\xdb\x47\xed\x04\x37\xd3\xe8\xe1\x6f\x9c\x48\x3a\x3c\x6b\xf3\xc0\xcd\xe9\x66\x21\xad\x08\x84\x52\xf8\xfd\x2b\x32\x95\xa4\x0f\x3f\xe0\xa5\x6a\x65\x00\x10\xf1\x86\xb1\x97\x3e\x7f\xcf\x13\xb3\xcf\xde\x0b\x21\x59\x5e\x4d\xdd\xaf\x75\xc6\x9b\x6b\xc4\xc0\x3d\x65\xe8\x1f\x1a\xef\x83\x66\x06\xf2\x7d\x2d\x61\xbb\x0d\x3a\xb8\xb5\xf7\x31\x18\xdb\xfe\x4a\x24\x99\xdc\xb7\xa2\xb7\x4e\xe0\x32\x6b\xd1\xb8\xb7\x8c\xdf\x78\x0c\x95\xd5\x30\xc8\xda\xe2\x23\xe9\x00\x43\x26\xa4\x79\x52\x1f\xdc\x3d\xaa\x74\xc2\xf2\x67\x62\x28\xd8\x4e\xcc\xbc\x37\x05\x89\xe9\x30\xc5\x2d\x47\x38\x2d\xcb\xcf\x05\x91\x10\x03\x07\x51\xd9\xff\xde\xd3\x99\x46\x5c\xfa\x64\x4e\xe4\x76\x4b\x24\xeb\x92\x2e\x91\x8c\x8f\x9a\x2b\xd4\xb4\x90\xb0\x26\xc8\x88\xf3\xb4\x96\x8e\x5e\x04\xe9\x4d\x69\x57\xf6\x16\x71\x71\x28\x65\x9e\x5c\xac\x25\x27\xc1\x2c\x96\xf1\x3e\x76\x26\x17\x42\x06\x94\x52\xe8\xca\xca\xb1\xcf\xd0\x9e\x45\x58\xeb\x90\xd6\x83\xdb\x64\xce\x4c\x57\xed\x14\xdc\x3d\x7d\x90\xa3\x8d\xd9\x1a\x51\xb7\x5f\x1a\xff\x1f\xb4\x34\xb1\x45\x9a\xd3\xdc\xc6\xde\x0a\xeb\x1d\xe7\x81\x39\x2e\xaa\x39\x96\x45\x2d\xd8\xaf\x3f\xe5\xd5\xd5\x33\x96\xaa\x40\xe4\x63\x63\xa7\x3f\xfe\x31\xce\x2f\x71\x36\x0b\xa3\x4d\xef\x69\x98\xb8\x4f\xe3\xc7\x93\x91\xff\x82\xb3\x6c\xa6\x60\x63\x79\xbe\x48\x72\xb8\xe2\x3a\xf2\x82\x43\xc5\x41\xb0\x97\x83\xe3\x34\x38\xd4\x19\x08\xd9\xe4\x0e\x7c\xc5\xc7\x9f\x6a\x94\xc1\xc1\xd7\x75\xf4\xc0\xef\xe8\x81\xe9\xa8\xc2\xed\xbf\x16\x44\x36\xf5\x83\x0f\xfa\xfd\x2a\xe2\x87\x9d\x19\x7d\xdd\x5c\xbe\x28\x2a\xf9\x99\xd1\x01\xaa\x69\x43\x68\x52\x63\x07\x35\x54\xd8\x90\x96\x50\xab\x63\xad\x63\x2b\xee\xae\x6b\x4b\x25\xe8\x96\xd4\x9f\xa9\xe1\xe7\xc2\xda\x80\xe3\x4f\x2d\xc8\x33\xa2\x50\xa3\x9a\x5d\xc2\x52\x36\xb5\x36\xaa\xf8\x14\xd6\xd0\xfc\xef\x82\x20\x31\xe4\x5c\x7b\x1b\xbf\xda\xf0\xda\x46\x7b\x84\xeb\x7a\x35\x7e\x8c\x0b\x82\xb9\x1f\x9b\xdc\x8f\x55\xa3\x17\x5f\xd1\x28\x7a\xcc\xd5\x0d\xa1\x26\x67\xa5\xc4\x59\xb5\xfa\x61\x47\x67\xd3\xaa\x9c\xd5\xa3\xdf\x24\x73\xf2\x92\x13\xa3\x69\x94\x61\x1c\x1f\xa8\xc2\xfc\x64\xb5\x30\x3f\xc6\xa9\x57\xc6\x74\xec\x80\xca\x6a\x45\xd1\x77\xde\xbb\xce\x84\xee\xba\xf2\x3b\x6d\x98\x24\xf2\xc3\x34\x25\xba\xd1\xb1\x6a\x84\x05\x7b\x7f\x3b\x3b\x7d\xdb\xd3\x02\xa7\x64\x7e\x47\x82\x60\x4f\xd2\xbd\x6f\x27\x63\x14\x0e\x98\x3e\x4c\xbe\x55\xfd\xea\x0f\xe5\x8f\x99\xb5\x51\x91\x7b\x7b\xee\x74\x1d\x4b\x8c\xb1\x9f\xeb\xdb\x84\x5c\x1d\x54\x4b\xed\x29\x42\xe4\x4b\x4b\xb9\xbe\xcf\x48\x8e\xca\x14\x5d\xd1\x00\xdb\x1f\x14\xd4\xde\x70\x92\x53\x78\xc9\xf1\x6e\xa2\x2c\xcb\xfb\xe2\x00\xf1\x7a\xd4\x4e\x17\x07\x48\xcf\x0d\x86\x81\xd4\x01\x7c\xc2\xf0\x9d\xca\xdd\xed\x66\x55\x70\x1d\x09\xdd\x81\x5a\x96\x43\xc6\x0b\x38\xde\x89\xb1\x0e\x4d\xa3\xed\x27\xbe\xcf\xbc\x8f\x82\xfc\xf0\x3d\x70\xcf\x81\x81\x2d\xb4\xeb\x4a\x2f\x71\xae\xf4\xe0\xb3\xbf\x43\xd0\xab\xfa\x0f\xda\x1b\xbb\x0f\xf3\xd5\xf5\xd9\x37\xce\xc2\xe0\x9b\x74\x68\x03\x02\xf0\xb6\xb3\x5f\xe1\x92\xe3\x42\x2b\x68\xff\x03\x23\x75\x52\x0a\xba\x51\x42\x61\x59\x10\x05\xc8\x6f\x58\x1b\xdd\x61\x86\x77\xf0\x27\x5c\x02\x22\x07\xf0\x4b\x01\x7f\x14\xf0\x73\xc1\x36\x47\xd7\x0a\x23\x45\xe3\x0f\x19\xbc\xcb\xe0\x7d\x06\x67\x70\x05\x6f\xb2\x9a\xda\x99\x50\xa4\xc9\xe7\x8c\x96\x70\x0e\x27\xf0\x0f\x09\xa9\x84\x65\x01\x1b\x43\x1b\x45\xdd\x41\x39\x29\x87\x7f\x14\x8c\xfc\x52\xb0\xcd\x3c\xc9\x66\xc8\x8f\xbc\xb8\xfb\x49\x14\xf2\xd8\x44\x04\x8c\x7e\xcf\xe0\x62\x9d\xcd\x52\x3c\xf8\xa2\x3e\x5c\xf3\xbc\x50\xa8\x35\x18\x3c\xeb\x0d\x1e\xf7\x06\x01\x68\x6c\xc6\xf3\x77\xf1\xf4\x2a\xbe\xe4\x6f\xe3\x25\x8f\x02\x4d\x71\xcc\xc4\x32\x28\x69\xef\x9e\xba\x9b\x4a\x72\xc1\x3a\x9b\xf1\x79\x92\xf1\x59\x75\x86\x9d\x9f\xbf\x3f\x3a\x7c\xf9\xe1\xfc\xd5\xd1\x2f\x1f\x4e\x4f\xdf\x9c\x9d\xff\xf5\xcd\xe9\x8b\xc3\x37\xe7\x3f\x9d\x9e\xfe\x7c\x7e\x5e\x37\xe4\x90\xec\xe1\xdc\x3a\xe4\x57\x52\xbc\x4a\x0a\x45\xfa\xcc\xf0\x18\x2f\xd6\xab\x95\xc8\x65\xa1\x79\x31\x53\x5f\x7f\x58\x99\x6b\xc8\x5e\x92\xfd\xce\xa7\x92\x70\x3a\xbc\x29\x6a\x78\x49\x87\xc8\x15\xd9\x4b\xb1\x5c\x26\x12\x6b\x50\xf8\x16\x4d\x0d\x8c\x13\xb0\x67\x4f\x98\x51\xd0\xb5\xf6\x21\x9e\xa2\x6e\x65\x15\x55\x96\x70\xf4\xc5\xba\x6d\x38\xdd\x0c\x78\xad\xa8\xff\x4c\x04\xd9\x94\xf0\x4b\x01\x1b\x71\xcd\xf3\x3c\x99\xf1\x9f\x84\xb8\x3a\xab\x34\x3a\x6d\xb2\xa7\xa6\x53\x70\x69\x5d\x67\x69\x59\x54\x6e\xd2\xa7\x0b\x3e\x5b\xa7\x26\x3c\xb7\x4e\x9b\x36\x23\x7d\xbc\xe7\xf3\xe8\xde\x28\x20\x6a\xe1\xfd\x15\x7f\x71\x87\x03\x89\xfc\x81\xd6\xd9\x39\xc2\x59\x86\xf1\x0a\xf4\x79\xef\xdd\xe6\x94\x70\x1f\x88\xb6\xd4\xf6\x47\x31\xfa\x43\x51\xd2\x3a\x40\xcd\x4e\x3f\x8a\xd7\x42\x75\x3c\xe7\xc5\xa2\x3e\xd4\xf6\x44\x21\xa4\x9b\x29\x93\xa3\x36\x51\x97\xdc\x8e\x5d\x0f\xcf\x1c\x9e\x20\x7b\xe7\xe7\x67\x47\x2f\xdf\x1f\x7d\x38\x3f\x7e\xfb\xe1\xe8\xfd\xdb\xc3\x37\x67\xe7\xaf\x4e\xcf\xdf\x9e\x7e\x38\xff\x78\x76\x74\x7e\xfa\xfe\xfc\xb7\xd3\x8f\xe7\xbf\x1e\xbf\x79\x73\xfe\xe2\xe8\xfc\xf5\xf1\xfb\xa3\x57\xec\xe7\x02\xa4\x31\xdb\x7c\x27\x72\x19\xa7\xec\x27\x95\xa2\x86\xf0\xea\xf4\x04\xb9\x9d\xc6\xbe\x31\x52\xe8\xa6\xf7\x94\x41\x8d\xd0\xb5\x5f\x79\xa5\x6f\xba\x2b\x8b\xa8\xe2\x52\x6b\x1d\x83\x7b\x74\x34\x0d\x01\xd3\x94\x54\x7c\xff\x3d\x6d\x72\xfe\x07\xcf\xbe\x87\x53\x94\xf4\xf7\xae\xf8\x5d\xa1\xcd\x37\x9c\x28\xaf\xb6\xe6\xb2\x75\xcd\x65\x6f\x9e\xae\x8b\xc5\xd9\x5d\x36\xdd\x25\xec\xef\xb5\xd4\x1a\x7c\x5f\x79\xa2\x69\x73\xe8\xaa\xbd\xe1\xd4\xce\x12\xef\x14\x41\xa7\xb6\xe6\xf8\xa8\xe2\xab\x35\x29\x89\xaf\xa2\x02\xbf\x71\x74\x96\x84\x6e\x5f\x11\x82\x20\x77\xc9\xbf\x7f\xb5\xc2\x81\xa9\xd0\xd0\x82\xee\x2a\xf6\x50\xb6\x81\x49\x17\x79\xcb\x46\xe5\x4f\xaa\xba\xbb\x5d\xde\xc6\x4f\x85\x21\x69\x70\x06\xb6\x0f\xa6\x23\xdd\x41\x8d\x7e\x6c\xad\xc4\x9c\xa3\x35\xd2\x52\xb1\x48\x7d\xd3\x7f\xa3\x07\x72\x81\x78\x63\xa6\x11\x4e\xa1\xa8\x04\xef\x63\x6d\x4f\xd4\x61\xc1\xaa\xe3\x69\x9a\xfb\xdf\xa0\xed\xeb\xdd\xd1\x0b\x75\xb6\xbe\x90\x39\xe7\xc7\x99\x14\xed\x4c\xa6\x16\x10\x9a\x19\xce\xda\x97\xaf\xda\xa6\xdb\xad\xdb\x61\xad\x7b\xb0\x69\xb2\xf7\x7d\x6d\xf1\x75\x73\xdd\x01\xe4\xd8\x55\x73\x1a\x33\x77\x1a\x97\xd0\x04\xab\x60\x5d\xf0\x8e\x22\x30\xa7\x32\x18\xe2\xb5\x9c\x3a\xee\x58\x46\x7e\x18\xd0\x2f\xe4\xfe\x8f\x47\x7f\xf9\x4b\xe7\xbf\xd2\x64\x8a\x5e\x15\xb5\xe7\xc6\xeb\x7e\x6f\xf0\x43\x6f\xf0\x1f\x9d\xbf\x74\x2c\x7e\xcc\x15\x5b\x32\x5b\x63\x3d\xbd\x65\x92\xf5\x7e\x2f\xfe\xa3\xf3\x17\x95\xe3\xa5\x58\xdd\xe5\xc9\xe5\x42\x76\xc8\x94\x76\x5e\xc7\x53\x7e\x21\xc4\x15\x74\x8e\xb3\x69\xaf\x13\x67\xb3\x4e\x22\x8b\x4e\x3c\x9f\x27\x69\xa2\x16\xbc\x67\x8a\x7d\x58\x24\x45\xa7\x10\xeb\x7c\xca\x3b\x53\x31\xe3\x9d\xa4\xe8\x98\x5e\xcc\x3a\x8a\x48\xc8\xd1\x81\xe3\xc9\xf1\x07\x9b\xdc\x99\x8b\xb5\xaa\x0e\x3d\x3b\xaa\x2a\xde\x1c\xbf\x3c\x7a\x7b\x76\xd4\x99\x27\x29\xb7\x0e\x1f\x15\x67\xde\x99\x25\x39\x52\xe2\x77\x1d\x31\xef\x48\xaf\x21\xb5\xc6\xaa\x03\x8f\x5c\x8c\x61\x88\x01\x11\x69\x1b\x5d\xa2\xfd\x10\xf8\x16\x7f\x2e\x64\xd7\x09\x2f\x8a\xf8\x12\xf5\xfd\x32\x9e\x5a\x1e\x5c\x9f\x9e\xfa\x67\xcd\x5a\x69\xcf\x82\x5a\xaa\x83\x33\x0f\x0a\x33\x71\x43\xe8\xb0\x20\xdd\xbe\x22\x35\x8d\xc7\x97\xca\xc5\x21\x42\x4b\xc1\xe5\x07\x7d\x9b\x44\xd6\xd0\xa7\xc0\xcb\x12\x66\xec\x55\x2c\xd5\x01\xa0\x8a\xd7\xeb\x6b\x89\x75\x56\xe5\xdd\x9f\x95\x50\x67\x4e\x6d\xff\x46\x5e\x33\x39\xa8\xee\x44\xa4\x60\x1c\x1a\xad\xd3\x12\x44\x63\x83\x4e\x99\x97\x47\xc7\x7b\x4b\xfc\x4e\x4c\x53\x1e\xe7\xf6\xfb\x94\x96\x10\xef\x76\xb1\x3b\x28\x51\x07\xd4\x0d\x64\x2e\xf2\x29\x7f\x9d\xc7\x4b\xfe\xbe\x86\xa0\x15\x1d\xe4\x1c\xbb\xce\x99\x5e\xaa\xde\x8a\xe7\x8a\x1f\x42\xc2\x73\x65\x13\xd5\xa8\x61\x61\xdf\xaa\x3e\xc2\xd2\xa6\xf9\x3d\x6b\x00\x83\x5b\xf1\xa9\xc8\x0a\x91\x72\xeb\xe4\xd6\x96\x54\x4d\xa5\x87\x59\xb2\x44\x61\x04\x76\x74\xd8\x02\x2f\x26\x7b\xce\xff\x58\xf3\x42\xd6\xf3\x87\xa1\xa9\xbb\xc7\x11\x1f\x04\xb8\x35\x2e\x72\x71\x53\xf0\xbc\x33\x13\xbc\xc8\xbe\x95\x1d\x43\xc3\x76\x5a\xab\xe8\x75\x4e\xe2\x2b\xde\x29\xd6\x39\xef\xc8\x45\x2c\x3b\x77\x62\x8d\x9e\x4e\x3b\x71\x67\x25\xd2\xbb\x79\x92\xa6\x6a\x87\x68\x0f\xa7\xa6\xea\xa2\xd7\x59\x48\xb9\x2a\xa2\x47\x8f\xe6\x17\xbd\x25\x7f\xa4\xe9\x79\x9b\xbf\x08\x28\xb4\x8c\xe4\xfa\xcf\x75\xb7\x6d\x82\xfe\x0f\xf5\xb6\x6c\x53\x39\x98\xb7\x9a\x68\xcd\xd5\x2e\xa0\x5f\xdc\x2f\x73\xbd\x59\x4a\xdf\x85\xf0\xea\x6b\x37\x9b\xc9\xb8\x7f\xa1\xa3\x0a\x5e\xb2\xee\x00\xee\x34\x6e\xb8\x65\xfb\x03\xb8\x61\x4f\xe1\x88\xf5\x87\x2d\xbb\xa0\xd3\xc4\x0c\xcf\xd9\x91\xda\x17\x3e\xf4\xc3\x57\x6c\x12\x4e\x37\xfd\xe7\x7c\xbb\x1d\x1c\x3c\xfd\x91\x8f\x1a\xcb\x56\x2f\xd4\x91\xf1\x15\x2f\x70\x01\x8a\x44\x26\xd7\x0a\xa1\xca\xce\x05\x97\x37\x9c\x67\x9d\x3e\x22\xf2\xc1\xc1\x53\xe8\xa8\x62\x49\x76\xd9\x99\xab\x92\x8a\x68\x2a\xac\xcb\x5d\xb9\x88\x33\x95\xa7\x33\x5f\x15\x0a\x9b\x67\x42\x76\xd6\x99\x01\x04\x3e\x0b\x68\x74\xc3\xfa\x3f\xf2\x11\x3a\x5d\x99\xa7\x42\xe4\x64\xc0\x1f\x3f\xe2\x34\x7a\xaa\x59\xdf\x2b\x1d\xe8\xac\x86\x5a\xe1\x94\x5d\xf5\x54\x05\x07\x43\xfd\x3b\xe8\x89\x6c\xa9\xb3\xb4\x63\xd8\x3b\x7a\x1f\x7a\x3d\x62\x7c\xef\x06\x49\xc4\x3b\x8d\x68\x47\xa7\xbd\x95\x28\xa4\x69\xd1\xf8\x7a\x22\xde\x4a\xd1\x26\x0e\x6e\x29\x00\x56\x35\x5b\x95\x6b\x62\xd5\x3b\xc6\xe1\x72\xbb\x55\x75\xf6\xa1\xa5\x70\x13\x8d\x66\x74\x73\xcb\x16\x35\x4a\x8c\x93\xe6\x40\x30\x5a\x63\x13\xbb\x2e\xc9\x2d\x45\xc0\xf2\xc4\xa1\x2f\x1b\xf7\xa4\x5a\xac\xc4\xb5\xa7\x15\x49\xbd\x18\xfa\x46\xca\xb4\x3f\x78\xfe\xfc\xf9\x00\x04\xe3\xe3\x1c\xe5\x4d\x5d\x52\xd9\x0e\x86\x61\xff\xc7\x0f\x3a\x16\x59\x15\xce\x75\x9c\x4f\x98\x04\x3e\xce\x26\x4c\x40\xc6\x72\xaf\xf5\x33\x8f\x57\xab\x3c\x0e\x71\xc6\xc7\xfd\x89\x65\x03\x3c\xcd\x2e\xcf\x41\xe6\xb8\x3f\xa9\xb8\x14\x1d\x87\xd5\x8e\x61\x25\x56\xda\x2b\x5e\x66\xe2\xb3\x8e\xfb\x13\x96\x0d\xab\xe8\xfa\x39\xeb\x83\xa8\x06\x9b\xff\x28\x86\x56\xe2\x74\xf0\x17\x92\xef\x0d\xe8\xfe\x00\x62\xc6\xc7\xc9\x04\x52\x96\xec\x0d\xa0\x60\x7c\x9c\xd6\x1b\x8c\xc3\xb0\xff\xfc\x03\x89\x21\xa3\xd4\x25\x16\x3a\xb1\x80\x98\x8e\x08\x0e\xbc\x00\x55\x12\xe3\x1b\x29\xc0\xc1\xb4\x18\x54\xd5\x98\x96\x54\x06\x45\xde\x3c\xba\x6a\xb2\xe6\x3c\xfa\xd5\xb9\x70\xd4\x1d\xd9\x1e\xd1\xe2\x43\xc3\x2a\x5e\xe4\xf2\x38\x9b\xf1\xdb\x7d\x59\x3d\x7b\xfe\x41\x30\xd2\x27\xef\x25\xb3\x7d\xd9\x4b\x66\x88\x91\xde\xb1\xf1\x04\xde\xab\x7f\x6f\xd9\x00\xce\x35\x6a\x3a\x61\x8f\xe1\x10\x1d\xc1\x61\xb0\x69\xd6\x1d\x54\xda\xbe\x6f\x88\xe7\x65\x57\xb2\x33\xf2\xde\x99\x1a\xc8\x5a\x0c\xe0\xea\x1e\x95\xfe\xae\x32\x55\xb3\x20\xb5\x3b\x26\xbc\x8f\x64\xdc\xfa\xc8\x53\x99\xc0\xeb\xf7\x8e\xbd\x3d\xbc\x24\xef\x14\x41\xa1\x1b\xf5\x2f\x46\x0d\xdf\xf3\x19\xe3\x7f\x12\x4e\xa1\x7b\x4c\x2b\x5c\x70\x46\xde\x51\x7a\xac\xf6\x5e\x4e\x5e\xd7\xe2\xc0\xd5\x3a\x1f\x86\x82\xbc\x02\xaf\x6f\xfb\xdc\x6f\xe4\xb5\xde\x98\x7a\x46\xc2\x50\x37\x96\x10\x4a\xd5\x4c\xf5\x4d\x80\xc3\x93\xa1\x75\xf5\xf3\x86\x64\x14\xce\xb1\x6d\xdb\xc2\x79\x18\x92\x2e\x39\x6f\x8c\xea\x79\x46\xb7\x5b\x1e\x86\xdd\x98\x50\x3a\xb4\x97\x65\xe7\x6e\xf2\x6a\x76\x03\x9b\x2a\xdd\xae\xd4\x79\x6f\x65\x74\x7e\x50\x49\xc2\x28\x10\xa4\x3b\xed\xfc\xc8\x32\x3a\xcc\x76\xf0\x61\xdb\x75\x52\x31\xf2\x9a\x29\xa2\x73\xa6\xe7\x30\x0c\x7f\x27\xef\x28\xbc\x21\xd6\x20\x57\xbd\x0e\xf5\x18\x3d\xbb\x84\x73\xaa\x6f\xfc\xad\x57\x03\x6d\x4c\xe2\x4f\xf5\x5a\x4f\xf5\xda\x9b\xea\x8c\xc2\x54\x61\x4e\x03\xab\x53\xc7\xa2\x3b\x88\xcc\x11\x22\xbd\x05\x79\x81\xce\x29\xeb\x3a\x9a\xd6\x46\x6a\x7f\x60\x43\x87\x59\x93\xd0\xa7\xfd\x86\x09\x9c\xe7\xd0\xbf\x6e\xd2\x35\xe0\x4f\x86\x8d\x5b\xf9\xa7\xfc\xb1\x3e\xbf\x7f\x65\xa9\x7f\xe6\x1f\xcf\x52\xee\x34\xae\x9e\xfa\x87\xf1\xf1\x72\xc9\x67\x8a\xd5\x71\x9f\x07\xfe\xe7\x37\xe2\xc6\x7d\x78\xe2\x7f\x78\xab\x08\xd7\xd4\x7d\x7b\xec\x7f\x7b\x97\x0b\xc5\x3f\x59\x23\x7d\xef\xcb\xc7\x82\xe7\x2f\x52\x31\xbd\x42\x1d\x67\x53\xf6\xa0\xc6\x52\x23\x05\xf6\x72\x27\x14\x31\xc7\x08\xe0\x3e\x48\xd5\x68\x0a\xeb\x52\xfd\xe8\x96\x4f\xd7\xaa\x80\x7f\xd8\x1c\x6f\xb7\x87\xdb\x2d\x71\x3b\xab\xce\x52\x57\xa2\xb2\x77\x3e\x7c\xb6\xd0\x39\x27\xcd\x72\xaf\xf1\x56\xd6\x57\x74\xdb\x2d\x84\x40\xe7\x97\xcb\xf8\x6d\xfd\x2e\xc9\x80\xc6\x89\x03\x0d\xeb\x9f\x51\x2b\xd2\x68\x0c\xf0\xb8\x19\x0c\x8a\x9d\x94\x1a\x9b\x9e\x0c\x4f\x98\xac\xcb\xed\x2b\xc1\xd1\x09\xcb\xca\x5a\xeb\xab\x78\x5d\xb4\xcf\x52\x43\xce\x80\x74\xfb\xbb\x38\xc9\x24\xfb\xb5\xf6\x65\x9d\xfd\x9a\xc8\x85\x5b\xbd\x3a\x57\xb5\x03\xe6\xf5\xb1\x18\x00\x36\x00\x5e\x1f\x11\x67\x8f\xbd\x11\x71\x7f\x44\xf2\xa1\x11\x59\x9e\xbf\x05\x66\x20\x83\xd8\x22\xaa\x1d\xfa\xaa\x8d\xfe\x8e\x9d\x62\x4a\x6c\xb9\xe4\xb8\x37\xe3\x69\x7c\x37\x2c\x58\x90\xad\x97\x17\x3c\xf7\xd0\x8f\xa2\x31\x8a\x51\xba\x57\x44\x29\xc4\xbb\xdf\x63\xab\x4f\x39\x72\x4f\x91\x42\x06\x1a\x29\xc5\x4c\x3d\x43\xc1\xd2\x4a\x93\x70\x93\xcc\xa2\xb7\x7b\x7b\x60\x01\x3d\xca\xa0\x86\x34\x23\x0e\x0e\x15\x45\x05\x34\xf4\xdd\x63\x56\xec\xc5\xe0\xce\xa5\x68\x7f\x50\x42\xf1\x3c\x1d\x11\xef\xc0\x65\x05\xbc\x24\xef\x81\x3b\xa3\x5a\x8d\x30\x31\x1a\x31\x79\x4f\xd5\x79\x31\x4a\x08\x8d\x3e\xab\xcd\xa2\x90\x5f\xb1\x9f\x52\xaa\xc8\x05\xaf\x8e\x18\x4f\x38\x4e\x61\x67\x67\x29\xfa\xb2\xb6\x38\x0b\xb1\x4e\x67\xbf\x25\x3c\x9d\xb5\xdc\x0b\x37\x97\x44\x1d\x89\x46\x46\x8a\xe7\x91\x25\x25\xf4\x99\x54\x9d\x4e\x59\xe5\x4a\xc7\xfa\x6b\x9e\xba\x30\x31\x59\xed\xd0\x6e\x09\xfd\xdb\x3c\x70\xb6\xdb\x98\xd4\xb7\xe8\x4d\x1e\xaf\x5a\x51\x90\xde\x8b\x27\xb6\x63\x8d\x11\xb5\xed\xc5\x5e\xbc\x5a\xa5\x77\x18\x0c\x1d\x9c\x6c\xaf\x01\xcd\xe5\xae\xd0\xcb\x50\xb8\xe4\x69\x9f\x22\x7d\x68\x64\x64\xc3\xae\xd7\x64\x4d\x30\x86\x33\x8a\x92\xb8\x84\x3d\x1a\x7f\xfa\xf4\x29\xf8\x74\xdb\xef\xef\x7f\xba\x1d\xcc\x3f\xdd\x7e\x37\xdf\xff\x74\xfb\xc3\xfc\xd3\xba\xdf\x8f\x67\x9f\xd6\xfd\x67\xea\x8b\xfa\x79\xf2\x69\xdd\xff\xae\x3f\xff\xb4\x1e\x7c\x77\xf1\x04\xff\x3f\xfd\xb4\x3e\xe8\xf7\xa7\xfb\xf8\x33\x57\xff\x0f\xbe\xc7\x97\x03\x7c\x79\xd6\xc7\x97\x67\xf3\x4f\xeb\x39\x9f\xab\xff\xf3\xb9\x4a\x9a\xcf\xe7\xf3\xc9\xa3\x4b\x88\xd9\x26\xf8\x74\x11\x44\xc1\xa7\x4f\x17\x01\x04\x9f\x24\x3e\x4a\xf5\x98\xe1\x63\xa6\x1e\xe7\xf8\x38\x57\x8f\x39\x3e\xe6\x01\x7c\x1b\x7c\x1b\x7d\xfb\xe9\x53\xf0\x2d\x04\x9f\x3e\x61\xe2\xa7\x4f\x41\x59\x91\x73\xa9\x47\x9f\x27\xa8\xb8\x72\x6c\x8c\xaa\x92\x9e\xe4\x85\x24\x9c\x8e\xbe\x0d\xbe\xdd\xe3\xbd\x9c\x63\x80\x14\x92\x00\xd9\x5d\xbb\x78\xcc\x27\x66\xf9\x76\x8c\x2a\xe4\x48\xaa\x76\xd7\xc1\x1e\x09\xfa\xfd\x7e\x3f\xd8\xe3\xbd\xe9\x22\xce\x5f\x8a\x19\x3f\x94\xa4\x4f\x7b\x52\x9c\x61\x19\x32\x78\x46\x69\xaf\x48\x93\x29\x27\xfb\x4f\x68\x49\xe9\x1e\xf6\x5f\xb5\xaf\x9e\xca\x16\x51\x84\xa8\xee\xd4\xd1\x95\x8d\x7b\xab\x20\x0c\x25\x7c\x06\xf1\x68\xbf\x11\x41\x00\x52\xfd\xdb\xc5\x2d\xa8\xc9\x53\xb0\xfe\xb0\xf8\x31\x1e\x16\x7b\x6c\x40\xe5\x1e\x0b\x3a\x01\xd2\x32\x3b\x43\x8b\xd1\x23\x47\xac\x79\x11\x96\x40\xe2\x0b\x1a\x5c\x17\x93\x30\x74\x38\xb1\x4a\xdc\x6e\x6d\xeb\x55\x9a\xf3\x97\x6a\xa2\xea\x71\x2b\x1e\x0e\xea\xda\x03\x01\x6d\xee\x95\x8e\x20\xd5\x20\x61\x0a\x6b\x98\xc1\x1c\x56\x8c\xc3\x82\xc5\x8a\xcd\x59\x1a\x7d\xbc\x45\x18\x92\x45\x27\x31\xf7\x63\x62\xde\xc9\xb7\xdb\xbc\x97\x14\x2f\x92\xcb\xb7\xd8\x1d\xb2\xa0\xd4\x6a\x2c\x2e\x5a\xa2\x74\x2d\x5a\x85\x29\x8b\x9e\x14\xaa\x8f\xe8\x10\xdb\xbe\x90\x84\xb6\x12\x98\x19\xe6\xd2\xd8\x85\xc4\x90\xc0\x82\x52\xb0\x15\x19\x85\x0d\x33\xd5\x96\x04\x5b\x8e\x16\x51\x4a\x16\xda\xce\xc6\x4e\x9c\x33\x0a\x2d\x5e\x27\x59\x22\x39\x59\xd0\x91\x81\xa4\x05\x8d\x02\x35\xe0\x40\x17\xb8\x10\x22\xe5\x71\x16\x44\xa6\x78\x9a\x9a\xc7\x8b\xe4\x32\xc9\xa4\xab\xc9\x95\xd6\xc5\xcc\xd8\x23\xc5\xb6\x2c\xcc\xc5\x9a\xa9\x56\xc1\xd1\x1e\x93\x30\x57\xbc\x53\x30\xd6\x39\x3b\x87\x79\x1e\xdf\x4d\x02\xc6\x98\xb9\x0c\xab\xf4\x78\x2c\x90\x1b\xd4\xb5\xa0\x9a\x8d\x9a\xb1\x85\xf5\x58\xab\xc1\x6e\xa6\xc1\x6e\x3e\x2e\x26\x4c\x90\x02\x16\x14\x21\x45\xb5\x69\x3a\xb9\x66\x8a\x89\x9e\x9b\x62\xa3\x60\x3c\x09\x22\x3e\x0a\xc6\x9f\xb2\x60\x8f\xef\xcd\x7b\xbf\x8b\x24\x23\x01\xe0\x2b\xdd\x53\x28\x62\x6f\xb5\x17\x4c\x82\x28\x18\x07\xd5\xe7\x80\xaa\x34\xe0\x6c\x05\x6b\x24\xdf\x5b\xd6\x3a\xa3\xba\x8b\x59\x6b\x17\x77\xb6\x43\x36\x2e\x26\xe8\x2d\x57\x90\x29\x53\x2f\x6a\x61\xc3\x70\xae\x65\x0d\x29\x99\xd2\x3d\xc2\x47\x41\xd4\x09\xa2\x20\x0a\xe8\xde\xda\x98\x62\xf9\x17\x87\x0b\xda\xa2\x83\xe1\x78\x5c\x41\x24\x2c\xe8\x30\xf3\x2a\x95\x8d\x4a\x51\x43\xf3\xbe\xa9\xda\x94\x38\x55\x9b\x87\xa7\xaa\x0c\xa2\x60\xd3\x98\xaa\xd2\x4e\x55\x49\x82\x00\x36\x41\x10\x89\x92\x96\xb4\x24\x2d\x77\x2b\xe6\x98\x41\x3f\x12\x22\x2b\x64\x47\xb0\x47\x64\x14\x9d\x6f\x3f\xa9\xb3\xe2\xe9\xf8\xf5\x7c\x42\x5b\xde\x57\xfa\xfd\xbb\xbe\x7a\xc9\xcd\xcb\x81\x7a\x11\xfa\xe5\x99\xcd\x29\xcd\xc7\x27\x6d\x1f\x1f\x6c\xe6\x91\x3a\xc8\xc8\x28\x9a\x9a\x42\x8f\xdb\x6a\xc8\xec\xfb\x11\xc7\xf7\xc2\x34\xf7\x78\xa7\xed\x5a\x2f\xd7\xe6\xe5\x29\x6d\x36\xf0\x60\x7f\xab\x3a\x1e\x79\xd7\x56\xfe\x19\xb3\x73\x24\x4b\xb6\xd1\xaf\x51\x77\x00\x85\x14\x39\x3f\x2c\xf4\xee\x52\x09\x71\x7a\x13\xdf\x15\xef\xe2\xbc\xe0\x87\x0a\xaf\xa9\xb4\x75\xc1\xdf\xc6\x32\xb9\xe6\x2f\x92\xcb\x63\xd4\x89\x01\xdc\x9a\x87\xd8\x48\x14\xa0\x3c\x34\x00\x5c\xae\x7c\x8d\x1a\x5c\xb5\x2f\x65\xd3\x3b\x51\x57\xdf\xeb\xe9\x6e\xa0\xda\xb8\x7e\x44\x37\x2f\xee\xa3\xd7\x33\x93\xc7\x4b\xc1\xac\xb2\xd7\xec\x2e\x33\xa5\x9b\xe9\x61\xb8\x9b\xa6\xc8\xab\xfa\xc8\x6c\xe9\x46\xb2\x2a\xdc\x48\x02\x27\x8d\x42\xa5\xc1\xfa\xb8\xb5\x4a\x80\x1e\x7b\x7b\x8e\x30\x0c\x92\xcb\x4c\xe4\xfc\xfe\xef\xab\x9c\xa3\xb9\xff\x3d\x39\x76\x4e\xb9\x6f\x8f\xb3\xa9\xc8\x73\x85\x48\x51\xb7\xac\x33\x17\x79\x67\xa7\x58\x47\xac\xd4\x0f\x74\x96\xeb\x42\x76\x2e\x78\xc7\x2e\x5e\xc7\x76\xa8\x23\xf2\x8e\xbb\xbd\xc1\xc8\x72\xab\xb8\x28\xf8\xac\xa3\xe8\x98\xdd\x6e\x0c\xe5\x6e\x62\x5b\x7f\x4b\x5f\x3a\x68\xcc\xa4\xef\x99\x2d\xef\x5b\x63\x9e\xea\x5f\xea\x33\xe4\xd7\xf8\x55\x73\xe3\x15\xf8\x37\x67\xc5\x6f\x7a\x28\xfd\xd7\x7a\xbf\xb4\x1c\x24\x86\x14\x90\xca\x60\x1b\x43\xa0\x39\xf2\x32\x80\xe0\x51\x10\x05\x8f\x02\xb8\x88\x14\xcd\x0a\xf3\x48\x51\xa6\x90\x45\x0a\xbb\x42\x1e\x29\xe2\x14\x14\x45\x28\x83\x12\x66\x75\x55\x28\x35\xe6\x4d\x86\x7a\x65\x67\x77\x99\x8c\x6f\x8f\xf4\x18\x8c\xac\x3f\xe2\x10\xcb\x28\x06\x34\x34\x2e\x14\x7d\xcf\x5a\x54\x82\x78\x18\x72\x1d\x61\x74\x46\x82\xa3\xdb\x15\x9f\x4a\x35\x48\x85\xf0\x83\x6f\x91\xf2\xe1\xf1\xac\x23\xe6\x2a\x29\xdd\x0b\xbe\x0d\x28\x68\xf7\x27\x71\x7e\x28\x49\x4c\x21\xde\x63\x03\x48\x4b\x58\xed\xf0\x56\x20\x58\x10\x68\x7f\xe3\xfb\xea\x90\x4f\x15\xc9\xc9\x82\xfd\x00\xe6\x2a\x85\xd2\x61\xfa\x9c\x05\xfd\x20\x0c\xd3\x1f\x59\xf0\x43\x30\xa4\x62\x8f\xa5\x30\x37\x7c\x71\x0f\xcb\xe0\x99\x2a\xf6\x58\xd0\x0b\x86\x73\x42\xc3\xb0\xad\x0c\xe6\xe7\x98\x7f\xbb\x0d\x8e\x6a\x05\xb1\x42\x08\xf6\x03\x13\x47\x75\x0f\x1f\xb6\x5b\xf7\xed\xe1\x6e\x70\xb6\x27\xc0\x91\x4c\x9c\xd2\x9a\x62\x56\xae\x03\x8a\x2b\x16\x89\x82\x30\x07\xe7\xf3\xc1\xd3\x51\x03\x79\x8d\x44\xb4\x83\x7b\x46\xfa\x87\x08\x1a\x29\xc0\xcd\xd5\xc3\x2e\x7e\x1b\xdd\x5b\x8e\xdb\x72\x9c\x46\x7c\x38\x23\xc1\x8b\x78\xd6\x31\xb4\x1e\x2d\x61\xb1\xbb\x1e\xa8\x92\xa1\xd6\x24\x99\x93\x6f\x83\x6f\xdd\x2c\x69\x55\xd8\x58\xcd\xaf\x16\x4a\xdb\x8f\x66\xac\xf1\xfe\xe0\xb9\x50\x23\xdd\x63\x45\xaf\x58\x5f\x68\x4a\x86\x08\x88\xf7\x07\x94\xe2\xfc\xa2\x8e\x94\x02\x68\x2c\xa7\xea\xf8\x8a\x42\xc1\xda\x64\xd7\x4a\xc1\x7d\xa3\xcd\xfb\x24\x0c\x09\x67\x2b\x35\x05\x6a\x9c\x2a\xe7\xe0\x19\xad\x2d\xc2\x50\x2a\x92\x2a\x63\x83\x67\x7f\xc9\xf6\xf8\x30\xdf\x63\x86\x5c\x9c\xe7\x62\xf9\xd2\xb0\x4b\x56\xf4\xba\xf1\x8c\xd7\x1d\xf7\xb0\x1e\xa7\x13\x23\x4b\xcf\xf7\x98\x7a\x2b\x05\x8b\xcb\xd2\xcc\xa3\xc9\x4e\x4b\x58\xfa\xf3\x88\x97\x3f\xa9\x86\x94\x4e\x30\xa4\x73\x42\x4b\x4b\x3d\x4d\xfd\x8c\x86\x25\x58\x12\x0a\xa9\xa1\xd1\x37\x8e\x6e\xde\x59\x98\xcc\x12\xc0\x5a\x99\x47\x5f\x74\xe1\x8c\x6e\xaa\x09\xc5\x37\x0a\xaa\xca\xa0\x0c\xfc\xf5\x99\x93\xa0\x0c\x28\x64\x3a\x40\x8f\xf1\x87\xc3\xd9\x82\xe8\xdc\x73\xa2\x48\x3d\x7d\xcc\x4a\x77\x06\x9b\x16\x17\x71\x71\x7a\x93\xbd\xcb\xc5\x8a\xe7\xf2\x4e\x33\x19\x19\x70\xaa\xf0\xc1\xb7\xaf\xd6\xab\x34\x99\xc6\x92\x77\xae\xf8\x5d\xc7\x32\x96\xa6\x2a\xe1\x58\x5e\x83\x3c\xb1\x7a\x0f\xfb\x8d\x66\x24\xd0\xad\x74\x6c\x38\x0f\x85\x87\x2f\xd0\x45\x59\xc7\xd1\xfa\xea\x09\x1b\x0f\x68\x64\xb1\xef\x4e\x55\x53\x42\xa3\x6c\xcc\x27\x4c\x3d\x60\xf3\x49\x6b\xf3\x3b\xc7\xd0\xc3\x9d\xf0\xb2\xdf\xdb\x8d\xdd\x2a\x6b\x9d\xb1\x0f\x0f\x2f\x0c\xd2\xc5\x2a\x8b\x83\x30\xc3\x3d\x28\x9a\x58\xb3\x4f\xe3\x7b\xe1\x83\x8d\xf1\xae\x2d\x18\xd7\x80\x61\x6c\x81\x61\xd2\x6c\x73\x12\x50\xe0\x75\x60\xd0\xf4\xff\x94\xd0\x2f\x94\xd9\xed\x67\xac\x98\xb4\xaa\x9b\xea\x04\x33\xa5\x16\xb6\xe7\xfb\xae\xe7\x2b\x42\x9b\xd7\x01\x75\xe4\x3a\x5a\x11\x1a\xed\xee\x13\xbb\x47\x2a\xde\x72\x4e\x02\x19\x20\xec\xe6\xfa\x67\xad\x7f\x38\x42\xb2\x6e\x77\xee\xe7\x9e\xeb\xef\xb1\xfe\x49\xf5\x4f\xe1\x15\x1a\x18\x6e\xd8\x2f\x94\xd5\xea\x4e\xab\x1f\x14\xee\xcf\x48\xf0\x31\xe3\xde\x99\xa8\x0f\xc0\x12\x55\x20\x5b\x34\xd8\x2d\x26\x28\x18\xdf\x0b\x02\x88\x59\x1f\x52\x85\x27\x20\x73\x00\xa2\xcf\x59\x7d\x5c\x77\x34\xdc\xb6\x32\xfd\x72\xe4\x64\x15\x9c\x54\x96\xb6\x90\xb0\x6c\x9c\x5b\xe1\x51\x27\x69\xe1\x42\x13\xb7\xb5\x91\x43\x4c\xee\xe3\x10\x2d\x69\x46\x04\xe3\x24\x01\x49\xe9\x28\x19\xcb\x09\x13\x91\x76\x0c\xde\x51\x6f\x5a\x5d\xd7\x20\x85\x1c\x12\x5a\x12\xc5\xcc\x65\x25\x04\x01\x8d\xb2\x36\xa9\xe1\x0e\xfb\xa1\xce\xc7\x1f\x9e\xd2\xa1\x27\x7c\xa1\x1b\x2f\x5a\xac\x7a\x4b\x7a\x8a\xb4\x93\xbf\xc6\x79\xe6\x7c\x0b\x08\x68\xe1\x6f\xe8\xc6\x9b\x17\x7d\xa2\x19\x81\x95\x3a\x72\xba\x8c\xe5\x56\xd6\xee\x89\x83\x5e\xc6\x69\x9a\x64\x97\x1d\x85\xe5\x30\xd4\xbe\x22\x0a\x93\x59\x2c\x45\x5e\x18\xcd\xb7\xf4\xce\xea\x5f\x38\xed\x8b\xce\xc5\x1d\xea\xc7\xfd\x6f\x85\x18\xf6\xd5\xec\x16\xff\xbb\xb3\xd2\xda\xfc\xbd\xce\xc7\x82\x57\xf5\xf5\xa6\x0b\x3e\xbd\x72\xaf\x84\x76\xa4\xe8\xa8\x59\x53\x15\x2c\x7b\x9d\xf7\x8a\x86\x5a\x8a\x9c\x77\x62\x89\x3a\x39\x4e\x25\x67\x5d\xf0\x7d\x2c\xbc\x5f\xb5\x12\x58\x75\xe1\xb4\xa7\xad\x61\x8e\xb3\xeb\x38\x4f\xe2\x4c\x76\x7e\x49\x44\x8a\x32\xe3\x00\x52\xef\x76\x4f\x56\x57\x3d\xbc\xd4\x3e\x88\xfe\x58\x27\x39\x9f\x31\x6e\x44\xd9\x1b\xdc\xc6\x11\x87\x0b\x21\xd2\x48\xdb\x18\x44\x1c\x34\xb9\x10\x71\xd0\x50\x84\xa2\x7d\x64\x0c\x39\x14\x77\xcb\x0b\xcc\x1a\x67\xaa\x1c\x96\x3f\x9d\x47\x12\x9c\x71\x30\x78\x9e\x3b\x22\x0e\x56\x4a\x86\x99\x32\x31\xe3\xae\x5a\x4c\x11\x19\xaf\x7e\xb1\x88\x84\x62\x11\xe3\x2f\xbf\x8d\xa7\x32\x92\x50\x9f\xc6\x28\x81\x1d\xa8\x88\x84\x3b\x6f\xb3\x9e\xcb\xc9\x32\xc8\xbe\x04\x8a\x15\x30\x05\x46\xf9\xdc\xa8\x9c\xbf\x3b\x3c\x3b\x3b\xff\xf0\xd3\xf1\x59\xab\xe2\xf9\x97\x54\x47\x5b\x95\x41\x07\xcf\x7a\x83\x83\x5e\xff\x3f\x3a\x7f\x31\x36\xa6\x49\xf1\xff\x96\x32\xa8\xc1\x30\x9a\x3d\xb2\xc4\x03\x91\x10\x9c\x9f\xf3\xe2\x44\xcc\xd6\x29\x0f\x60\x83\xac\x57\xd4\xed\x97\xd4\x20\x82\x16\x44\x77\x86\x60\x16\x86\xfa\x57\xe1\x2a\x10\x2c\x1f\x55\xaf\x44\x1b\xcd\x58\x37\x31\x01\x8d\x9e\xf5\x07\xfd\xc7\x90\xb4\xe6\x5a\xa1\x1e\xb4\xc9\xf4\x0c\xe2\xd6\x4c\xf3\x3c\xbe\xf4\xea\xfa\x0e\xd2\xd6\x6c\x7a\x89\xcf\x97\x62\xc6\x4d\xce\xef\xa1\x68\x6f\x15\x2f\x9b\x15\xdd\xad\xb2\x0d\x9e\xc0\xf4\xbe\x6c\xd7\xc9\xcc\x66\xeb\xff\x00\xeb\xd6\x6c\xc6\x5e\xdf\x54\xd6\x87\x59\x6b\xae\xb8\xb8\xcb\xa6\x5e\xe7\x06\x03\x98\xdf\x57\x9d\x31\x4f\xa9\xe7\x5e\xb5\xcf\x8d\xc8\x6f\xe2\x7c\x76\x9e\xf3\xb9\xc9\x79\x00\x8b\xf6\xe9\x31\xc6\x31\x26\xdb\x63\x58\x3e\x98\xed\x3c\x4d\x0a\x33\xa6\x83\x3e\x5c\xb7\xe6\x5d\xf2\xa5\x30\xd5\x3d\x85\x8b\xd6\x2c\x69\xfc\xf9\xce\x64\x79\x06\x97\xed\x43\x58\x67\xb3\x18\xad\x5d\x0d\x20\x0c\xbe\x83\xbb\xd6\x9c\xce\xc5\x96\xc9\xf7\x3d\xdc\xb6\x8f\x61\x2a\x56\x76\x9c\x3f\x54\x07\xd9\x8d\xb5\xd0\x7a\xc8\x6b\x93\x0b\x61\x6c\x2d\x78\x87\x75\x63\xcb\x8e\x70\xbe\xb3\x8c\xe3\x36\x93\x3e\xd3\x57\xd4\x73\xfd\x13\xeb\x9f\x42\xff\xa4\xfa\x67\xe1\x3c\x21\x38\xaa\xab\xaa\x2b\x0c\x77\x3c\x45\xad\x75\xb1\x95\xfe\xb9\xd0\x3f\xd7\xfa\x67\xba\x5b\x97\x53\xa7\x2a\xb5\x9f\x29\x2f\xa1\x3a\x7b\x8e\x3c\x61\x82\x9a\x10\xc6\xd8\xbc\xd4\x5e\x9c\x4e\xe7\xec\x06\x64\xef\x50\x41\xea\x89\x98\x71\x36\x03\xd9\x7b\xe9\xc0\x11\x93\xe6\x3a\xc9\x84\xa4\x2b\xd6\x4b\x9e\xb3\x75\x95\xf6\xce\x6c\x19\x36\x05\xd9\x33\xa6\xe8\x4c\x80\xec\xbd\xd6\x70\xfa\x9e\xcf\xd9\x4a\xbd\x9a\x2d\xcd\x62\x90\xbd\x37\xf1\xe7\x3b\x76\x01\xb2\x77\xc2\x97\x82\x5d\x83\xec\x19\xf3\x88\x44\x3d\x9a\xbd\xca\x0a\x90\xbd\x33\xdc\xe2\xd8\x91\x54\xbd\x1a\x58\x65\x0b\x90\xbd\xa4\xf8\x45\x51\x09\x47\x9e\x7b\xaa\x5d\xf9\xc9\xae\x53\xb4\xd6\x70\x37\x7c\xbb\xe5\x8c\xb1\x58\xff\xcc\xf5\x4f\xa1\x7f\x52\xfd\xb3\xd0\x3f\xcb\xed\xf6\x21\x70\x42\x87\x14\x76\x55\x19\x63\x17\xdb\x6d\xed\xfd\xba\xf1\x3e\x6d\xbc\xaf\x1b\xef\xab\xc6\xfb\x65\xe3\xfd\xae\xf1\x7e\x8b\x97\xd4\x49\x51\xad\x69\x8b\x4c\x49\x81\xc4\x76\x6b\x80\x61\xa6\x0b\x34\x96\xfd\xc8\x26\xd6\x16\xbe\xa5\x2e\x53\xcb\xba\xf4\x0b\x38\xa8\xb8\xbf\xc0\x54\x17\xb0\x20\xd3\xb2\x72\x0f\xce\x72\x6d\xd0\x42\xd7\xe5\x81\xdc\xfd\xed\xae\x4c\x5e\x0b\x8f\xf7\xe7\x8c\x75\x4e\x04\xd6\xfb\x73\x5d\xe8\x5c\x08\xc9\xf7\xe7\xba\xd6\xb9\x76\xac\x80\x9a\xf9\x12\x93\xcf\xee\x81\xfb\x73\x16\x3a\xa7\xb7\x41\xee\xcf\x9b\x9a\xbc\x76\xf7\xdc\x9f\x73\xb1\xc3\x38\x39\x3c\xe2\xa9\x2b\x79\xe6\x38\x56\x30\x12\x17\x45\x72\x99\x6d\xb7\x7e\xd5\x95\x5e\xe5\x60\x28\x77\x8c\x9b\x2a\x5b\xf0\xcc\xb3\x6e\x92\x13\xe7\xb3\x39\x57\x34\x4f\x46\x77\xae\x1e\xdb\x25\x22\xe8\xed\x06\x75\x4e\x15\x03\x56\x59\xeb\x95\x90\xdd\xa3\x5e\xe1\x8d\xa2\x39\xe8\xea\x13\x5e\x81\xaa\xfd\xa4\x7e\xeb\xc3\x33\x60\xba\x73\x59\x7a\xff\x5d\xa9\xf3\x59\xf1\x30\xc9\x9a\xf5\x72\x1b\xbe\x44\xf1\x66\x03\x0a\x82\x65\xa4\x4f\x15\x73\xd9\xcb\x88\xa0\x10\x33\x92\x91\xa7\x14\x32\xf2\x84\x52\x48\xd9\xe6\x57\x7e\x71\x95\xc8\xd7\x22\x93\x67\x4b\x21\xe4\x42\xb1\x08\x41\x9c\xc9\x24\x4e\x93\xb8\xe0\xb3\x00\x4e\xc4\xe7\xd3\xe2\xb6\x91\xe3\x32\x8f\xef\x8a\x69\xac\x88\xc1\x0b\x71\x7b\x96\x7c\xc6\xd4\x0b\x91\xcf\x78\xbe\x7f\x21\x6e\x83\x12\x8a\x36\x68\xd1\x43\x24\x79\x2f\xa6\x64\x33\x15\xa9\xc8\x23\xde\x5b\xc5\x29\x97\x92\x63\xf4\xd2\xde\x2a\x4f\x96\x71\x7e\x57\x6a\x5f\x8f\xe2\x32\x8f\x57\x8b\xbb\xde\x85\x98\xdd\x1d\xc0\xe6\x22\x9e\x5e\x5d\xe6\x8a\xda\x7d\xd9\x28\x5b\x7d\x71\x71\x41\x82\xff\x42\xe5\xc3\xce\x2a\xc7\xab\xed\x07\x0a\x4f\xc5\x72\x29\xb2\xde\xcd\x22\x91\xbc\x2c\x8d\xa3\xdf\xa9\x59\x0f\x12\xab\xce\x92\xdd\xc1\x6c\x82\xff\xba\x4c\xc5\x45\x9c\x06\xd1\x66\x21\x97\x69\x94\x42\xf0\x17\xe8\xfc\x25\x8a\x2e\xf8\x5c\xe4\x1c\x1f\xe3\xb9\xe4\xb9\x6a\xbc\x9a\xa5\x24\x5b\xf0\x3c\x91\x41\x09\xea\x8c\x11\xd9\x25\x74\x2e\x82\x68\x33\x17\x99\xfc\x95\x2b\x7e\x21\xaa\x0d\xbd\x4a\x7f\x21\xd2\x59\x09\x6a\x2a\xa2\xda\x3c\x2e\xe3\xfc\x32\xc9\xa2\x7e\x09\x05\xe1\x14\x36\x41\x18\x45\x6a\xb0\xb3\x5c\xac\x1e\x1c\xf7\xee\xa4\xa9\xd1\x97\x25\x05\x73\xfb\x70\xb2\x4e\x5e\x16\xc5\x8b\xb8\xe0\x69\x92\xf1\xa0\x6c\xcc\x83\x25\x80\x2a\x07\xda\x95\x2f\x29\xe3\x35\x55\x0e\x2b\x97\x4e\x69\x5c\x14\xbc\x00\x61\x84\xa1\x06\x83\x13\xe1\xce\x78\xe7\x3d\xc4\x18\xb3\xae\x59\x46\x7e\xa0\x30\x43\xe8\x5d\x53\x98\x2b\xa0\x1e\x78\x02\x87\x95\xb7\x9d\x1e\x05\x4c\x7b\x68\xc4\xfb\x0b\xdf\x6d\xdc\xc2\x20\x24\x17\x3f\x4f\x42\xce\xb2\xbd\x41\x53\x37\x3e\xdb\x63\x03\xc8\xf7\xd8\x80\xa2\x0e\x3f\xea\xfd\x1b\x2d\x7b\xbc\xee\x59\x36\x9d\x1b\xb8\xb1\xa2\xfe\x4b\x10\x18\xf5\x32\xc8\xf5\x41\x53\xac\xd2\x44\x92\xe0\x51\x40\xb7\xdb\xf1\x04\x84\xca\x27\x9b\xa9\x89\xca\xab\xc6\x01\xb1\xfa\xbe\x22\x52\xed\xcb\x64\xbb\x8d\xf1\x92\x42\x7f\x1b\x09\x96\x47\xb9\x33\xee\x24\x42\xf7\x0a\x04\x13\xc8\x05\xc4\x92\xe4\x94\x42\xd7\x5e\x54\x50\x37\x27\xaa\x12\x97\x6a\xdd\xe3\x8c\x6d\xca\xfe\x60\x32\xcc\x98\xbe\x90\x29\xb6\xdb\xa0\xe7\x9e\xf0\xd7\x3a\x21\xeb\x0e\x1c\x6e\x9d\xb2\x3e\xac\x99\x2d\x3f\x5c\x3f\x67\xfd\xe1\x7a\x7f\x5f\x57\x3d\x63\x62\xbc\x9e\x0c\x75\x7d\xb3\xd1\x82\x08\x58\xd3\xc8\xd4\x3a\x1b\x11\x9d\x00\xd3\xbd\x3d\x1a\x61\xcc\x78\xf3\xbe\xbf\x8f\x9a\xd3\x5d\x7d\x69\x31\x9c\xee\xef\x0f\xa7\x54\xf4\xd6\x59\xb1\x48\xe6\x92\xa8\x0a\xe8\xb0\x9b\x9a\x6e\x89\x71\x7f\xb2\xdd\xaa\xff\x6a\x72\xd4\x2f\xdd\x6e\xbd\xdc\x66\x19\xe6\x4c\x18\xc5\x85\x47\x95\x9e\x50\x16\x86\xc1\xa3\xa0\xcb\xd8\xdc\xdc\x5c\x90\xfd\x01\xd5\x81\xf3\x55\x36\x98\x7b\x3a\x61\xd7\xfe\x65\x9a\xf6\xf2\x71\x3a\x1f\xb9\x27\x42\xa3\x1d\x84\x6d\x3e\x39\x7c\xad\xcd\xa4\x3c\xc1\x96\x35\x7c\xc6\xc0\xda\x95\xf7\x05\x67\x2e\x20\x6d\x70\x98\xac\x72\xf5\x90\xcc\x49\xed\x30\x21\xd2\xdd\x55\xd5\xd3\x33\xaa\xe0\x4b\xaf\x0c\xba\x71\xb1\xf0\x22\x7b\xfc\x9a\xe7\x77\xbe\x9c\xd0\x73\x8a\x8d\xbd\xc2\xa3\x8f\xb6\xeb\xad\xca\x16\xca\xd5\xa9\x77\x5c\x2b\x80\x15\xec\xda\x73\xea\x94\x77\x71\x20\x02\x2d\x2d\xb4\x87\x95\xc8\x97\x5c\xd6\x4e\x7e\xb2\x29\x75\xa8\xb8\x9d\x3e\xd6\x7a\x38\xce\x27\xae\x93\xa5\xb3\x8f\x1c\x6a\x6b\xb2\x1d\x43\xf9\x2e\xdf\x55\x1d\xab\x64\x6d\xf3\x38\x49\xf9\x2c\xa0\xde\x5a\xdf\x3d\x80\x48\x46\x3c\x0a\x1e\x05\x7b\x9e\x7d\xce\xed\x83\xb9\x2d\x64\x0d\xa8\x6f\xd3\x73\x53\xb3\xdc\x6e\x35\xe7\xd6\x1a\x0b\x52\xbc\x11\x37\x3c\x7f\x19\x17\x9c\x50\xed\x01\xfa\x14\xc3\xa4\xf9\xe9\x34\x0c\xf7\x07\x5d\xc6\x82\x47\xa3\xff\x0c\x5c\x26\xd7\x0d\xe9\x34\xe8\x4a\x6c\xa1\xea\x94\xfb\xe2\xf7\xed\xe8\xde\xf1\x70\x87\x28\xb0\x0e\x54\x49\xec\xc3\x7e\x7d\x68\x57\xfe\x59\xb0\x8a\xe5\x02\xfd\x09\xa1\x25\x0e\x8f\xf3\xe9\x02\x1d\x8e\x2e\xe2\x62\xa1\x50\xe0\x76\xab\xb0\x92\xb7\x1d\x47\x81\xf1\xc2\x29\xf6\x58\x30\xd2\xfe\x87\xaa\xd9\xcc\xa2\x60\x14\xec\x65\x14\xf2\x30\x0c\xfe\x33\xd0\xd1\x3a\x31\xeb\x7f\xaa\xac\xb9\x97\x35\x8f\x82\xff\x0c\xf6\x72\x0a\xc2\xf3\x01\x6f\x45\xc9\xc6\xee\x69\xb8\xcb\xd6\x8d\x48\xd2\xa2\x85\xcb\xb1\x9f\x90\xb1\x20\xc0\x9b\x55\x0c\x6e\x60\xe7\x39\xf8\xcf\x80\x0e\x71\xfe\xf1\x1e\x94\x49\x3b\xbb\x42\x07\x90\x30\x6f\x7d\x10\xe6\x20\x4b\xfc\xc2\x23\x87\x8e\xb0\x8a\x24\x0c\x49\x56\x15\x4a\x1a\x55\x24\x94\xc2\xc6\x4e\x6a\x24\x41\x4f\x69\x64\x66\x6a\x14\x04\x51\x06\x6a\x6e\x23\x33\x21\x2a\x25\x2f\x4b\xc2\x29\xd5\x7e\x25\x98\x8c\x2a\x1f\x17\x24\x61\x35\xd2\xa1\x04\x8e\xee\x21\x75\xf5\x61\x48\x12\xf7\xa2\xce\x33\x48\xcc\x12\x8e\xf4\x32\xd9\xd7\x6a\xd6\xb1\x88\x4e\x54\xab\xb7\x67\x5f\x68\x54\x25\x07\x90\xe0\xf2\x8f\xf4\x02\xea\x97\x46\x15\x2a\x49\xad\xe9\x9e\x7e\x54\xc5\x75\x52\x50\xe9\xe7\x48\x2f\x8a\x60\x62\xe3\xf9\x91\xc4\x0e\x93\x52\xd4\x8d\xf6\x46\x30\xe3\x53\x31\xe3\x1f\xdf\x1f\x7b\xc3\xda\x31\x63\xe4\xbe\x16\xe8\xc7\xf7\xc7\x88\x2e\x46\x0a\x71\xd8\x17\xf2\xed\x3b\x53\xb8\x13\x7c\xbb\x57\x55\xb5\xf7\x6d\xd0\x99\x8a\x75\x3a\xc3\xeb\x85\x0b\xde\xd1\xed\xcd\x7a\x5a\x86\x8b\x72\xdb\x2b\x9e\xde\x75\xa6\xf1\xba\xd0\xd7\x0e\x71\xd6\x49\x32\xbc\xa3\xe8\xac\x78\x3e\xe5\x99\xdc\xe7\xd9\x54\xcc\x14\x91\xff\xad\xda\x54\xd5\xc6\x20\x89\x0e\x1b\x41\x41\x8c\xaa\x36\x47\xfa\xf8\xaa\x12\x1a\xf3\xe8\xc6\xbe\xf4\x5e\x40\x54\xc3\x57\x33\xeb\x32\x55\xe9\x5e\xea\x76\x5b\x83\x02\x75\x2c\x26\xbe\x1d\xa5\xbd\xbb\x44\xc5\x3e\xbd\x5b\xc6\xf6\xe2\x6a\x53\x70\xc5\xc3\x2f\x57\x32\xf2\xef\xa4\x9c\x09\x82\x04\xdf\x9a\xd3\x90\x4e\xdc\xd8\x97\x96\x30\x15\xd9\x3c\xc9\x97\x1f\xf2\x38\x2b\x12\xd4\xa0\x17\x5e\x3d\xd6\x67\x96\xaf\x82\xa6\x77\x57\x9b\x60\x66\xa4\x4f\xdb\x88\xef\xee\xf9\x64\xd4\x16\xd3\x68\x94\x93\x44\x1d\x55\x82\x74\xfb\xf8\x1f\x77\xa7\xb1\xa7\xc0\xc4\x12\xe2\xd5\x8a\x67\xb3\x37\x49\x21\x79\xd6\xf0\xeb\xa3\x49\xcb\x6e\xbf\x3a\x54\x72\x42\x37\x19\xaa\xab\x21\x1b\x69\x3c\x24\x79\x8c\xa4\xb3\x70\xc6\x3b\xdc\x9c\xfa\x93\x93\x61\xd4\x5b\x26\x7b\xf3\x24\x95\x3c\x6f\x63\x40\x3a\x5c\xa1\x43\x0c\x57\x08\x99\x90\xc9\xfc\xce\x76\xac\x88\x1a\xaa\x0d\x7a\xc5\x9a\x9c\x34\x64\x78\x99\xa6\xa9\x07\x4e\x21\x67\xfd\x61\xfe\x23\x1f\xe6\x7b\x7b\x34\x43\x03\x4e\xc7\x5e\xe7\x93\xe1\x3d\x1e\x41\x1b\xb6\x08\x66\x94\xda\x41\xa8\x56\x93\x3a\x63\xdd\x07\x7d\x4a\x74\x8d\x47\x00\xeb\x98\x72\x37\xa5\xce\x31\x78\x0c\xc0\xef\xc6\x21\x34\xb1\x3e\x08\x34\x00\x91\x9a\x5f\xcf\x0f\x44\xbb\x97\xb2\x12\x0b\x9d\x75\x91\x14\x52\xe4\x77\x1a\x7b\x6c\xb7\x1b\xcf\xad\x94\x81\x65\x5f\x18\xfa\x0e\x97\xd8\x39\x32\x41\xa8\xdd\x94\x14\xce\xb6\xdb\x4b\xd2\x1d\x98\x30\xe9\x90\xb1\x7a\xed\x20\x98\x8e\x85\x49\xa4\xfd\x92\xc5\xd7\xc9\x65\x2c\x45\xde\x5b\x17\x3c\x3f\xbc\x44\x67\xb9\xee\x70\x38\xcc\x66\xb9\x6a\xe5\xa0\x17\xe0\x19\x8f\x8a\x0a\x3b\x5f\x9f\xf4\xfa\x8a\x7d\x68\x7e\x3e\x11\x17\x49\xca\x3b\x67\xf1\x3c\xce\x13\x9d\xa1\x5b\xcb\xf0\x72\x91\x8b\x25\x6f\xfb\xf2\x2b\x76\xae\xe8\xbc\x5b\x88\x8c\x07\x8a\xbe\xa8\x0f\x24\x0c\x03\x05\xa4\xe8\x58\x2b\x48\x9a\x93\x08\x09\xeb\xea\x71\xde\x3f\xc8\xaa\xad\x0f\x79\x32\xc3\x4b\x1a\xc5\xf3\x70\x48\x59\xdc\x43\x03\x7c\xe3\x6a\x0a\x0a\xe6\x50\x7e\x1a\x86\x29\x4c\x59\xdc\xbb\xe4\xf2\x63\xc1\xf3\x97\x7a\x7d\xf1\xa6\x14\xd6\x15\xb7\x39\x1d\xfd\x1e\x4d\x61\xc6\x62\x85\x33\xdf\x68\xe0\x9e\x57\x9f\x67\xa3\x67\xd1\x0c\x56\x4c\xb1\xbc\x05\x47\x3c\x7a\x44\xee\x48\xf5\x4a\x69\x14\x04\x43\x9f\x63\xf4\x08\x81\x4d\x09\xea\x80\xbe\xe2\x77\x18\xc8\x0e\x01\x06\x84\x1d\x6a\x2a\xa6\xba\x3b\x89\x87\x4e\xf7\x84\x39\xfd\xf6\x04\x9e\x63\x96\xe0\x59\x29\x24\xcd\x6e\x48\x02\x2b\x4a\xe1\x94\x24\x18\x62\xc9\x8b\xfe\xef\xd9\x03\xc6\x72\xd1\xcb\xe3\x6c\x26\x96\xc4\xb3\x0d\x79\xfc\x8c\x5a\xe2\xe0\x00\xe6\x9a\xc5\xb8\x66\x2f\x89\xb7\x29\x2e\x54\xef\xfd\x63\xfe\x35\x70\x0a\xaf\x2d\x67\xe0\xb4\xd8\xaf\x7b\x0d\xac\x41\x5e\x57\xa3\x79\xdd\x8b\xb5\x1a\x64\x83\xec\x6d\xdb\xfb\x9e\x83\x1f\x73\x2a\x23\x2c\x3c\x08\x04\x2f\xf3\xe4\xf4\x2c\xa0\x25\x45\x91\xf2\x5b\xb2\x20\xa6\xb0\xbf\x73\xdf\x11\xba\x51\x9f\x3e\x10\x4a\xf5\x50\xdf\xd7\xac\xb0\xdf\x9a\x7b\x9a\xf7\x54\xa5\xc3\x05\xb1\x76\xcd\xbd\x96\x93\x84\x70\x08\xde\x9d\xbe\x0b\x60\x5d\x8f\x0b\x2b\x47\x17\x64\x13\x1b\x35\x67\xcc\x60\x27\x21\xe2\x25\xdd\xc1\xf1\x92\x79\xb3\x94\xb1\x13\x8f\x44\xbf\xe2\x77\x8a\x2e\x64\x9a\x9e\xcd\x58\xdf\xca\xe2\x4e\x3c\x12\xbd\xca\xa4\x75\x0c\x4d\x26\xc1\xb2\xfd\x7c\xa8\xe8\xc9\xf7\xac\xdb\x87\x63\x22\x90\x78\xd7\x68\x54\x65\x38\x67\x7a\x1e\xe0\x84\x8d\xcf\x55\x2d\x93\x6a\x1e\x0e\xbd\xc5\x58\xed\x29\x7a\xdc\xf3\x10\x8f\x1e\x6d\x7a\x97\xc2\x32\xa4\x9f\x59\xbf\x61\xc7\x8e\x08\xea\xf3\x1e\xe3\x34\x0c\xd1\x8b\xda\xc8\xe2\xd3\x78\x36\x43\xd7\x8b\x16\x44\x48\xb0\x12\x2b\x5c\xa6\x00\x6e\x29\x24\x0e\x4d\xec\x66\x54\x60\x3f\xc5\x18\x80\x01\xbc\xa3\x34\x52\xf0\xf1\x39\x0c\x89\x73\xff\xb2\x14\xd7\xfc\xeb\x2a\x6f\xcd\xdb\xa8\x1f\x87\xf6\x8a\x19\xd7\x88\xaf\xd9\x46\x03\x79\xe4\xa0\xbd\x7d\x85\xcf\x41\x1f\x2d\x3f\xe5\x7c\x1e\x1d\x82\xc2\x71\xd1\xae\x02\x50\xce\x34\x23\xb1\x24\xb8\x8f\x4c\x61\x3a\x6c\x87\xb3\x1c\x82\x77\x1f\xcf\x7e\xaa\x03\x9a\x06\x54\x07\x43\x87\xea\xb8\x4f\x58\x8e\x58\x25\x66\xb9\x86\x7d\x94\xd6\xa0\xcd\x7e\xcf\x61\x5b\xb2\xb9\xe2\x77\x51\x02\x3a\x4a\x75\x5c\xda\x48\x26\x50\xd0\x06\x06\xea\x2d\x72\x3e\x67\xb2\xb2\x37\x4f\x3d\xc0\xab\xba\x8d\x10\x08\x53\x76\xe2\xf8\xb9\x74\x6f\x40\x87\x53\x43\x85\xe8\xcf\x27\x6c\x0a\xfe\xb6\xc0\xf1\xb8\x59\xcb\x4b\x43\x16\xb5\xf7\xa0\x44\x4f\xba\xc6\x52\xee\xbf\x63\x3a\xdf\x1f\xbd\x7b\x73\xf8\xf2\xe8\xdf\x9b\x51\xd3\x9f\x3f\x3b\xa9\xd6\xe0\x4f\xd2\xaf\x9a\x58\xcd\x17\xa6\x61\x48\x4e\xc6\xe9\x84\x99\xe9\xf4\xa6\xd2\x8d\xe5\xcb\xb3\x59\x35\xad\x67\xf4\x52\x44\xc7\x70\x29\x5e\xc4\xd3\x2b\x9f\xc0\x3b\x26\xfb\x03\xfc\x6a\xee\xb1\xea\xdf\xd4\xa7\x8b\x54\xf8\x45\x76\x49\x19\x47\xc1\xb0\xeb\x9e\x23\xe1\x09\x77\x22\x9d\x57\xdb\x2d\x79\x43\x06\x14\x5e\xa1\xb5\xc6\xae\x21\xfa\xab\x30\x24\xaf\xb4\xe3\x09\xd4\x02\x96\xa8\x4a\x97\xe2\x6e\x6d\x41\xa1\xd7\xbd\x3a\x25\xed\x35\x85\xcd\x78\x0d\x60\x85\xba\x3e\xa7\x21\xf4\xda\x9c\x05\x1b\x85\x04\x2e\xe2\xec\x32\xda\x20\x1b\xc5\x15\xa3\xd6\xe2\x1f\x33\xe8\xb6\xc8\x74\xba\x8f\x82\x3d\x75\xa4\x95\xa0\xf9\xb6\xaf\x2f\x5b\x93\xf0\x28\xda\xbb\x48\x15\x07\xee\xf7\xe1\xd6\xaf\xf4\xae\x84\xdd\x1c\x77\xf5\x1c\x65\xe3\x54\xb3\xf2\x94\x9a\xcc\xc1\x8a\x0d\x50\xbe\xce\xa3\x4a\x22\xe3\x47\x23\x38\x77\x1c\x5a\xdb\xee\x84\x87\x2b\x0d\x82\x88\x7b\x4a\xdd\x72\x6f\xe0\xd5\x7c\xa2\x3a\x76\x1f\x90\xbe\x25\x6d\xcd\xd1\x3d\xc5\xd4\xfb\xe7\xd1\xe1\x57\x51\xd2\x4d\x3a\x3a\x63\xe4\x6b\x48\xcb\xd7\x49\xce\xe7\xe2\x36\xa0\x18\x83\x9f\x65\xad\x74\x63\x52\x11\x86\x62\xf4\x7b\x24\x20\x66\x19\x52\x69\x18\x6a\x2f\xad\xbe\xc6\xa3\x00\xd7\x2d\x88\x62\x28\xbe\x40\x3c\xc2\x94\xbd\x1f\xa7\x13\x58\xb3\x69\xaf\x5a\x65\x98\xb1\x69\xaf\x5a\xe7\x6a\x8d\xe7\x6e\x91\x66\xe4\x9c\x54\x96\x7a\x18\x98\x9e\xdd\x10\x0e\x05\x12\x88\xe6\xcc\x5e\xd5\x29\xbc\xc5\xc3\x14\x9e\x15\xf2\xc1\xea\xeb\x28\x3c\x7d\xb9\xd1\x1d\xc0\x75\x23\xa6\xe5\x85\x6f\x61\xc0\xce\x89\x62\x11\xd7\x24\x73\x4e\x87\x72\x7a\x42\x72\x0f\x2f\x0a\x36\x27\x8a\xa8\xaf\x5a\x41\xc7\x49\x4b\xbc\x1f\x11\x40\x38\x8b\x2b\x69\x93\x56\x05\x77\xa2\x27\x2b\x2d\xd4\xba\xec\xf8\xa8\x12\x51\x0a\xa4\x92\x50\x2c\x64\x04\xdf\xe8\xa0\x88\x31\x76\xa5\xa8\x24\x93\xa4\xfb\xde\xf4\xb1\xbc\xa4\x38\xb0\x85\xa5\x0c\x57\x0f\x52\x86\x49\x93\x32\x5c\xfc\x1b\x94\xe1\x61\x65\x47\x7e\x3a\x27\x57\x44\xd2\x76\xe2\xb0\x99\x8f\xd3\x87\xe9\xc3\xe5\x2e\x7d\x48\x84\x21\x12\x6f\x71\x91\x3e\xb0\x35\xb9\xa5\xc3\xdb\x2e\x63\x1f\xc2\xf0\x84\x7c\xd0\x55\xbc\xc3\xd5\x39\x64\xe3\x2b\xf2\x8e\x7a\xd4\x23\x52\x88\xf2\x4f\x52\x88\x5f\x45\xf7\x5d\x38\xb2\xef\x6b\x29\xb9\x8b\x7b\x09\x39\xf9\x30\x21\xf7\xce\x27\xe4\x76\x57\xc6\x49\x12\x6a\xee\xe9\x49\xa0\xf6\x70\x40\x51\xce\x6b\xb7\x20\xde\xd2\x5d\x72\xe9\x45\xbb\x58\xa0\xd2\x1c\x2e\xdb\x3d\x68\x8e\x42\x86\x98\x6e\x4d\x0a\xa4\xbf\x69\x79\x1f\x2d\x99\x21\xf1\x63\xc4\x24\xe6\xc7\x27\x82\xda\x21\x34\xb3\x34\x65\x72\x3f\x05\x74\x45\x32\xbd\x41\x8b\x3d\x89\x5b\xf4\x9c\x50\xa3\x38\xed\x0b\xde\x5a\x70\x38\x6e\x32\x5e\xaa\xbd\xac\x61\xad\x09\x93\x5e\x07\x15\x91\x75\xe8\x0e\x1e\xa1\x48\xc7\xc4\x7a\x51\x83\x43\x96\xc0\xe2\x5e\xb2\x31\xb3\x84\xce\x82\xd0\x2f\xd1\x88\xff\xf2\x34\x39\xfa\xea\xab\x67\xca\x4c\x53\x18\x12\x35\x4f\x0a\xa3\x55\xd3\x90\xb4\x4e\x41\x25\xfc\x3f\x1c\x8b\x09\x93\xd4\x1f\xf4\x2e\x81\xa7\xc6\xfd\x7f\x8d\x80\x5b\xfd\x5f\x21\xe0\x56\xff\x2e\x01\xe7\x63\x1f\x3f\xe4\xa2\x96\x7b\x2c\x93\x8c\xe8\x87\xf8\x56\x87\xd6\xf0\x05\x11\x9f\xdb\x49\x08\xeb\xe9\x1b\xa5\x35\x6d\x07\xbf\xbe\xc3\x49\x64\x12\xa7\x47\x99\xcc\x13\x5e\xd4\x69\x81\x71\xf0\x28\x98\x20\x3d\xe0\xf2\xe1\x16\xa8\xd3\x04\x7d\xa4\x06\xa4\x27\x69\x9a\x56\x9f\x8b\xd1\xb3\xa8\x80\x75\xfd\xb8\x9e\x35\x8f\xeb\x0b\x75\x5c\x5f\xd8\xe3\xfa\xa2\xc7\x75\x6f\x2c\x86\x5b\xef\x1c\xdb\x17\xd5\xc1\x72\xb1\x2b\x98\x99\xff\x39\xa9\xd1\xd4\xd2\x14\xc7\x24\x85\x3e\x24\xd5\x1d\x1f\x2c\x58\xd2\x5b\xc6\xab\x56\x09\xaf\xb7\x1f\x5b\x6e\xd1\xe6\x84\x46\x28\xdc\xd8\x6e\xe7\x44\x7b\xbb\x5e\xb2\xab\xc6\x6d\xba\x5e\xa0\x63\x72\xa1\xf7\xd5\x1e\x87\x3e\x34\x47\xaf\xba\x91\x57\x73\x32\x96\x93\xe1\xfa\x5e\x36\x5b\x1d\x01\x59\x7d\x9f\xf3\xd1\xec\xbe\x43\x3b\x07\x6c\x36\x92\x25\x8d\x66\x04\x8f\x4e\x7d\x43\x6f\xcf\x98\xc5\xc3\x67\xcc\x62\xbc\x9a\x98\x2a\x56\x60\xfa\x17\x2d\xfc\x83\x67\xf9\x25\x09\x82\x3a\x81\x2f\x3c\x34\x76\xff\xd0\x10\x75\x66\xf7\xe3\x30\x3b\x87\x03\xc8\x3c\x08\x32\x78\x99\x0e\xad\xdc\xe3\xb9\x1c\x65\xa8\x73\x32\xc5\xfb\x10\x3b\xc9\x12\x72\x1a\x65\xee\xe2\x61\x76\x3f\xab\x6f\xe7\xcc\x0d\xd8\x61\xb3\x2f\x33\xf8\x5f\x3d\x5a\x87\x33\x9b\x8b\x19\x86\xa4\x02\x05\x33\xe4\x09\xcb\xfd\x0e\xb7\x32\xd4\xd4\xe2\xdb\xeb\x16\x7c\x7b\xfd\x00\xbe\xbd\x46\x7c\x3b\x8d\xb3\xbf\x8a\x16\xb4\xe7\x00\xd7\x11\x0b\xcf\x59\x3f\x0c\xe5\x8f\x4d\x28\x6e\x43\xd9\x3b\xd2\x52\x8b\xb9\x61\x5d\xc3\xd9\xad\x68\xd7\x7a\xf2\xd8\xc5\xbb\x15\x56\xbd\x40\x70\x7e\xc3\x32\xf2\xa4\xaf\x30\x7d\x46\x9e\x0c\x28\xbc\x46\xf5\xa9\x57\x14\x5e\x30\x92\x91\x83\x03\x0a\x19\x19\x3c\xa6\x14\x7e\x55\xef\x8f\x0f\x28\xec\x8e\xd3\x20\xab\x37\x0a\x59\x39\xc4\x2e\x7b\xb3\xa4\x58\xa5\xf1\xdd\x5b\x45\xbd\x73\x90\x25\x09\xde\x8b\xb5\xe4\xf9\xfe\x4f\x9a\x4d\x0b\x28\x85\x6f\x5a\x6e\xd3\xff\x64\x75\x01\x85\x8f\xb5\x5a\x3c\x7b\xa8\x86\x49\x1e\x41\x17\x05\x71\x9a\x6a\x2d\x4d\x49\xb7\x5b\xf5\x60\xef\xbe\x37\x0e\x26\xa4\xbb\xf0\xb1\x49\x25\x64\xbd\xf3\xa4\x38\x11\xeb\x4c\xf2\x99\x3a\xfa\xb2\xde\xb9\x09\xa3\xf8\xc6\xe4\xb1\x3e\x06\x55\x6d\xc9\xd4\x28\x29\x6f\xb7\x24\xeb\xad\x33\xbd\x48\xcc\xab\x18\x13\xea\xb8\xd3\x6f\x42\x6d\x44\x2e\x8d\xfc\xa9\xc6\x49\xb4\x34\xcc\x4b\xaa\x88\xca\xd2\x4c\xdd\x5c\x4d\x1d\xc6\xde\x91\xbd\xa9\x58\xae\xd6\x12\xc3\x74\x9c\xc4\x72\xba\x68\x51\xb4\x44\x8d\x01\x74\x3f\xb0\xce\x53\xfc\x5d\xc5\x79\xbc\x2c\xa2\x4d\x09\x49\x71\x84\x56\x59\x46\xd5\xc3\xc4\xda\xc9\x98\xa7\xc8\x54\x59\x62\x4d\x6d\x8c\x85\x57\xc9\xec\xa4\x19\x80\xcb\x84\xde\xaa\xa6\xd0\x44\xdb\x6d\x0e\x26\x0c\x31\xb9\x65\xf0\xad\xd9\x4b\xaa\x96\xc6\xb5\xfc\x6b\x92\xa6\x1f\x77\xa3\x7f\x61\x51\xbb\x0a\xa6\x05\xfb\x4a\xb0\x86\x66\xdc\x09\xcf\xc3\x57\xdc\xd0\x41\xfc\xa6\x67\x35\xcf\xad\x71\xd3\xc6\xac\xaa\xee\xe2\x2a\x17\xab\xc2\x09\x23\xea\xdd\x47\x48\xab\xce\xe7\xa5\x5a\x92\x68\x77\x95\x48\x4b\xe6\xea\x5a\x1e\x6a\x10\xe6\x37\x5a\xfb\x50\x96\xb0\xdb\xf9\x5f\xbd\xce\xbb\x58\x71\x5e\x0d\x36\x4d\xeb\x96\x81\x1e\xdf\xee\xb0\x10\x63\xca\x92\xa8\x06\x5c\x68\x0d\x45\xdc\x7b\xaf\x08\x2a\x3f\xdd\xb7\x37\x77\x2e\x83\x1b\x9a\xd3\x7a\x6f\xee\x80\xf4\xbf\x07\x7f\x7a\x0c\x22\xc3\xaf\x06\x0e\x6a\x69\x3e\x7a\x50\xa8\xa1\x06\x5c\xaf\x12\x13\x7d\xa3\xe1\x81\xc3\xab\x44\x7f\x6f\xd4\xac\x13\x1b\x55\x03\xff\x7a\xc8\x75\x15\xe9\xef\xcd\xea\x75\x6a\x5b\xd7\xef\x85\x6a\xe3\xb6\x74\x67\x01\xd5\xec\xfe\xc2\x36\x25\xfc\xe1\xb3\xf5\x3f\xd7\x54\xcf\x1a\x67\x12\x2a\x7d\x34\xd4\x5b\x37\x25\x05\x83\x32\x46\xf5\x18\x44\xc9\x9c\xfc\x32\xe6\x13\xab\x94\xa8\x9e\x87\x56\x1e\x12\xe3\x6c\x24\x29\xf7\x18\x84\x3f\x7e\x1c\xf0\x27\x61\x88\x85\x98\x84\x3f\xf6\xf6\xa8\xea\x36\x57\xd0\xb0\x59\xe5\x5c\xca\x3b\xb4\x2d\xac\x08\xdc\xbf\xd7\xb4\x8d\xf5\xc6\x9a\xe1\xae\x42\x35\x33\x29\x50\x95\x57\x11\x33\x10\x33\xdf\x4b\xb7\x8b\x9e\xdb\xb6\xe5\xad\x75\x4a\x33\x26\xa0\xa2\x3b\x7c\x61\xa4\xaa\xdb\x6d\x7d\x73\xc7\xe9\x76\x24\x14\x2c\x1e\x09\x6c\x3b\x12\x56\x18\x0a\x53\x76\x4a\xe4\x68\xd7\x33\xd8\xe8\x67\x92\x81\xec\x69\x64\x4c\xa3\x86\x2e\x56\xe6\x69\x7a\xfd\x4c\x2a\xec\x50\x95\x50\x67\x85\x9b\xc7\x74\x44\x0a\x32\xd5\x26\xed\x34\xda\x1d\xe1\x4f\xb0\x31\x5b\xa0\x66\x98\x8f\xf1\x26\x2c\x04\xb7\x71\xde\x20\x20\x51\xfd\xef\x49\x41\x87\x19\x4b\x40\x34\x95\xc6\xa6\xa0\xef\x4f\x14\xbd\x5f\x52\xc8\x7c\x99\x9e\xf0\x64\x7a\x59\x25\xd3\x13\x4e\xa6\x97\x59\x99\x9e\xbe\x1a\x57\x09\x57\xfc\x0e\xdf\xaf\xf8\x5d\x18\x5e\x90\xcc\xde\xb1\x9b\x1b\xe1\xed\x56\x77\x5a\x0a\x24\x3e\x0d\xd9\xfe\x57\x05\xd3\xbf\xf9\x30\xfd\x8f\x56\xbd\x6c\x05\xb8\x64\xd7\x1d\xe3\x76\xbb\xa3\x55\xab\xb3\xe3\xd1\x29\x4b\x87\x96\x20\x67\x7a\x80\x28\x50\x46\x8b\x66\xc7\x36\x1a\x28\x43\x09\xb2\xb6\xc2\x70\xbc\xa2\x76\x9d\xae\x58\x45\x35\x0b\x99\x8e\x24\xe0\x58\x45\xed\xfc\xbc\x30\x4b\x39\x9e\x54\x6a\xdc\xbd\x9c\xcf\xd6\x53\x5e\x53\xd6\x35\xd1\x92\xb2\x30\x0c\x50\x5b\xb2\x19\xfa\x4a\xda\x04\x69\x64\x8a\x6d\xf2\x94\x20\xd8\x93\x3d\x9e\xcd\xf6\xac\x01\xac\x7a\x70\x1d\xcb\xd9\x5f\xc7\xd9\x64\xbb\x25\xea\x07\xb9\xe9\x64\x4e\x72\x6f\x5b\xe7\x76\x5b\x0b\x86\x6a\xeb\x9b\x9c\x5f\xf2\xdb\x55\xf4\x9a\x50\xc2\x01\xa3\x43\x5f\xf1\xbb\xc2\xb3\xe3\xfe\xcd\xec\x73\x55\x92\x25\xf0\x9b\xda\xe7\x49\x49\x32\xd8\xf0\x6c\x86\x97\x6f\xe8\xbb\x2c\x05\xd7\x8b\x68\x5a\x52\x10\x2c\xef\xe9\xba\xf1\x1a\x4f\x55\x0a\x05\x13\x3d\x7e\xcb\xa7\x0a\x8b\xa8\x99\x28\x6a\x33\xa0\x6d\x05\x8a\x71\x7f\x02\x33\x66\x79\xa0\x01\x85\x39\x53\x00\xb9\xf6\x9c\x2a\x74\xe7\xda\x2c\x41\x2f\x72\x66\xa9\x23\x2d\x94\x45\xed\xf2\xf5\x28\x78\x14\x44\x6b\x47\x25\xcd\x2d\xe5\x14\xef\x2e\x4c\x5d\x70\xc1\xc7\x12\xcd\xfa\x27\x6c\x36\xce\x26\xc0\x4b\x0a\x1b\x64\x95\x4c\x3c\x53\xd5\xcb\xbf\xfd\x9b\x47\x67\xdd\x8c\xc6\x27\x0a\xef\x8f\xb6\xea\x07\x4a\xfd\x57\xd0\xa1\xa4\x1b\xe9\xa3\x43\xe3\x0a\x6b\x55\x38\x32\x66\xbb\x95\x15\xfd\x23\xdc\xe7\x1a\xa2\x1e\xb5\xa6\x46\x36\x55\xad\xc7\xe8\x1f\x3e\xce\x33\x5f\x68\x24\x7b\x48\x52\x41\xdc\xc4\x42\x12\x2a\x42\xd2\xd2\x5d\xa2\xa4\x88\xa3\xb1\x2c\x14\x2c\xad\x8c\x53\xa6\xea\xc5\x1e\x8b\xb0\x66\xa9\x99\xa9\x61\xab\x32\x7d\x41\xc3\x10\x05\x3a\x95\xd5\x85\x0e\x45\x44\x5b\x28\xb0\x5d\xf2\x31\x2e\x21\xd6\xfd\x1e\x15\x6d\xda\x86\xc5\xa8\x20\x31\x8d\x8a\x68\x3a\xda\xad\x6e\x0a\x31\x8d\xd6\xa3\xb5\xca\xa1\xc3\x00\xdf\x5b\x81\x06\xad\x76\xca\xcd\x81\xd7\x3f\xff\x94\x76\x3b\xe7\xbe\x1e\x7d\x0d\xb1\x64\xec\x9f\xde\x39\x8e\x3a\xb9\x95\x22\xaa\x15\x95\x66\x74\x24\x9b\x27\x9b\xf4\x75\x98\xab\x22\x46\x24\x65\xa5\x12\xd4\x3f\xf3\xd1\x1f\xd4\xbd\xc6\xb8\x23\x1e\xd5\xd5\x5c\x32\x2f\xbb\xef\x67\x07\xe1\xd6\x53\xe4\xcb\xb9\x76\x4b\xd2\x24\x6a\x05\xff\x7f\x61\x6b\x82\x3a\x8a\x1e\xda\x9e\x7e\x2b\x2f\xcd\xce\x70\x7a\x9d\xbc\xc1\x30\xec\x08\x9f\xb4\x6d\x8a\x08\x43\x55\xbc\x6e\x46\x4d\x38\xa5\x9b\xcc\xb8\x25\x71\x3b\x10\x97\x7a\xbb\xb5\x6f\xf3\x5c\x2c\x87\x82\xa5\xa3\x7f\x90\xb8\xda\xe9\x4d\x8d\x74\xb3\x79\x35\x8e\x4e\x4b\xea\x50\x40\x59\x52\xad\x0d\x1d\xf7\xa6\xa9\xc8\xdc\x04\x65\x1e\x22\x88\xa1\x8e\x60\x44\x49\x23\x17\xdc\xb9\x9d\xbb\x59\x17\xdc\x10\x73\x5a\x59\xff\x01\x48\x70\x96\xa9\x90\xed\x6a\xd3\xe6\x9e\x36\x6d\xa6\xce\xb1\xfe\x50\xfc\x98\x0d\xc5\xde\x1e\xcd\xc7\xc2\xd7\xa6\x15\x56\x5f\x9a\x48\x23\xbe\x30\xa0\xc5\x61\xac\x60\xc1\x23\x07\x9c\x40\xc3\x50\xa0\xec\x1d\x91\x06\x33\x82\xfc\x17\x81\xee\x5e\x48\xfb\x08\x75\xb6\xd7\x52\xbd\x0f\x71\x95\xe5\x57\x72\x8d\x31\x6f\x0d\x62\x78\x9f\xea\x36\x8d\x78\x09\xe9\x3d\x65\x76\xd1\xc1\x29\xe1\x55\xec\x70\xd0\xa5\xdb\x0d\x96\x79\x09\x53\xce\x54\xff\xe6\xce\xec\x7b\x58\x29\x92\x2a\x06\x68\xca\x59\x61\xd8\xd2\x35\x67\xcd\x23\xbf\x0a\x34\x93\x64\x19\x06\x84\xc5\x33\xcf\xe8\x25\x70\xdc\x80\x22\x7b\x99\x26\xd3\x2b\x48\xed\x99\xf5\x42\x2d\x0d\x87\x71\x60\xcb\x04\x10\xd8\x12\x01\x04\x26\x7f\x30\x41\x97\xfd\x3d\x19\xe7\x97\x5c\xc2\xb4\x79\xe2\xa5\x8a\xa2\xc7\x9c\x35\x06\x4c\xe6\x77\x1b\x45\x6b\x12\x67\xdd\x20\x9d\x75\x43\x6f\x95\xf3\x6b\xc5\xe8\x6a\x03\x4f\xa2\xa0\x86\x5b\x73\xcf\x77\xfa\x1b\x9f\x6d\xb7\xda\x99\xe7\xc5\x5a\x4a\x85\x30\x8a\x30\x0c\xce\x0b\x9e\xce\x15\xb9\x59\xb4\x99\x2f\x77\xbb\x84\xf7\x96\x5c\xc6\x3f\x73\x74\x25\x10\xa7\xd2\x3c\x4d\x65\x9e\x9a\x47\xb4\xc8\xfb\x99\xdf\xe1\x4d\xf5\x76\x4b\x5a\x7a\x23\x08\x55\x34\x92\xf3\xb5\xd7\xcb\xf9\x9c\x15\xbc\xab\x97\x42\x6e\xb7\x59\xcb\x79\x1b\xc4\x01\x4c\x9d\x99\xe8\xec\xa1\x35\xaa\x4e\x7c\x51\xdd\xd2\x64\xa3\x35\x8f\x32\x5c\x29\xcb\xb1\xa5\x9a\x8f\x2c\xfc\x65\x9d\xee\xac\x9e\xab\x2d\x80\xc0\x94\x0c\x20\x90\x22\x80\x6a\x61\x27\xf4\xbf\x83\xe9\xcc\x3c\xa6\x73\xcd\x52\x4e\x62\x4e\x52\xa8\x44\x47\xb4\xf6\x3c\x63\xeb\x51\xd6\xab\x2e\x22\xc8\x1a\x75\x52\xe6\x2d\x8c\xdb\x22\xe7\xf3\x68\x06\x16\xf8\xa2\xc6\x61\x24\x59\xb3\xa1\x21\x89\x47\x4e\xcd\xc7\xdc\x19\x50\xad\x93\xe6\xb4\x56\xf4\x7a\x8d\xe6\xb8\x7a\x72\xbb\x2d\xa2\xb9\x9b\x46\x56\xb4\xac\xa0\x80\x39\x35\x41\x51\xe7\xf7\x6d\xd3\xd5\xfd\xdb\x74\xa5\xb6\xe9\x8a\xb3\x39\xa7\xc3\xd5\x7d\x6b\x3f\x0e\xe2\x3c\x89\xf7\x8d\x77\x8a\x60\x52\x87\x80\x60\x15\x5f\xf2\xc0\x40\x41\x3c\x55\xec\xc7\xcb\x34\x2e\x8a\xb7\xea\x48\xaa\x2b\x01\xe9\xaf\x56\x0b\x48\xbf\x9d\xc9\xbb\x54\xb1\x73\xc6\xae\x19\x4b\xad\x31\x96\x82\xe2\x0f\x67\x0a\x8c\x8a\x43\xcc\xa9\xd8\x90\x8a\x3c\x5e\xa1\x59\x9c\xe5\xba\x16\xce\x77\x32\x2c\xf1\x51\x55\x7a\xad\x41\xf1\xc2\x07\xc5\xbb\x1d\x50\xac\x8d\x0d\x82\xc6\x08\x5c\x0a\xf6\x33\x80\x60\xea\x7d\xc1\x3e\x2a\x90\x35\x3d\x0c\x20\xb0\xfd\x0b\x20\x70\xbd\x0b\xf4\x3d\x1f\x66\x2d\x4c\x35\xff\x67\x40\x7d\xae\x90\x85\x9b\xa2\xd8\x80\xfb\x35\x64\x54\xfd\xdd\x32\x8f\x58\xb8\x61\xb7\x61\x78\xeb\xb4\xce\x1e\x91\x71\x6f\xef\x2f\x23\xf6\xff\x75\xa3\x6f\x36\x25\xa1\xe3\x4f\x93\xed\xa3\x4f\x9f\x26\xf4\xd1\x25\x04\x9f\x3e\x7d\x33\x08\x28\x1c\xb1\x9b\x3a\x6b\xa1\x29\x8b\x1b\xe3\x9d\x6c\xed\xf1\x9f\x2b\xcb\x94\x2e\x0c\xd9\x00\x57\xac\xdb\x25\xb3\xd1\x8c\x1c\x41\x46\xa3\x23\x0a\xa7\xec\x6a\xf4\x75\xd6\x35\xb2\x6e\x5d\x93\xb1\xfe\x30\xfb\x91\x0f\xb3\xbd\x3d\x2a\x15\x93\x5d\xd1\x03\xd9\xa4\xba\x14\x79\xc8\xde\xa7\xa4\xd4\xd8\x34\x77\x02\x5a\x92\x29\xa4\x34\x9a\xc2\x4b\x76\x35\x6a\xec\xf3\x25\x6c\x4a\x28\x68\xb4\x84\xb3\x3a\x0a\xa8\x83\x4d\x74\x15\x86\xc2\x88\x85\x1d\x80\x44\xa7\x80\xab\x1d\xbd\x04\x29\x14\x23\x73\x57\x85\x59\x50\xfb\x7c\xc5\x47\x67\x76\x9f\x5f\x44\x67\xd5\x3e\xbf\x68\xd9\xe7\x33\x0e\x67\x66\xa3\xe3\x5a\x2f\x38\x2c\x39\x5c\x73\x96\x91\x83\x67\x14\x2e\x9a\x71\xa6\x3d\xbb\xe4\x8c\xdf\x90\x6c\xbb\x25\x19\x7b\x97\x8b\x65\x52\x70\xea\xbb\x1f\x10\x90\x78\xb4\x59\x6c\x8f\xc1\x82\xe4\xbd\x8c\xdf\x22\x39\x5a\x19\xf4\x24\xa4\x16\xc4\x2c\xf5\x73\xe3\x39\xf9\x60\xf6\xc2\x49\x1f\x87\xbc\x37\x13\x19\x1f\x09\x62\x6c\xbd\x69\x84\x14\x1c\x3e\x83\xf4\x2d\x09\xb3\x91\x44\x2f\xbe\x8d\xfb\x20\x54\xe6\xa5\x94\xf6\xe4\x82\x67\x24\x86\x94\x96\x05\x21\x39\xcb\x1d\x01\x28\xb7\xdb\xf1\x84\x52\x3d\x0a\x7b\x8f\xd9\xa4\x83\x8c\x9f\x01\xed\x22\x91\x6d\xd2\xf8\x82\xa7\x51\x5f\x01\x72\x4d\xd6\x97\xcc\xc9\x20\x44\x0b\x79\x4d\x0d\x88\xf1\xc0\x41\x9a\x7a\x2e\x41\xe6\x77\x45\x34\x9e\x80\x58\xa9\x1f\x27\xba\x49\xd8\x46\xb5\x1f\xa5\xa4\x4f\x01\xcb\x46\x29\x19\x50\xd0\x9f\xa3\x94\x1c\xd0\xb2\xcd\xbb\xa5\x75\xfa\x46\x92\xb1\x71\xb6\x95\x48\x9e\xc7\x52\xe4\x93\xb6\xd0\x9e\x8a\x6f\xa2\x90\xf8\x71\x58\x92\x5d\x26\x2e\xdd\x4d\x4a\x34\x3b\xe2\x59\x79\x7f\xb8\x5b\x71\x63\xe9\xfd\x57\x9e\xe9\x36\x3b\x49\xd1\x89\xd3\x9c\xc7\xb3\xbb\x0e\xd7\x51\xa9\xb2\xcb\x5e\x40\xb5\x07\xd5\x78\x88\xf1\x87\x31\x66\xc9\x00\xd0\xac\x98\x1d\x84\xc9\xb8\x3f\x19\xe5\x3d\x33\x50\xf3\x86\xcd\x6c\xb7\x84\x68\xd9\x94\xfa\x44\xc3\x50\x68\xf1\x7c\x4e\xa1\x4f\x23\x0d\x76\x34\x0c\xbb\x04\xbd\x32\xe0\x17\x48\xc6\x03\xb5\x96\x0a\x68\x2c\x27\x2d\xac\x9b\x30\x8c\xce\x88\xf6\x40\x63\xdd\x2c\x08\x0d\x48\x13\x0a\xea\xd5\xb8\xf6\xea\x47\x26\xd8\x95\x60\x89\x09\xd3\x55\x8f\xd4\x16\xf7\x70\xf9\xf7\xf6\xac\xec\x41\x35\x0a\xaa\xc9\xa8\x3b\x28\x6d\xbc\xb7\x2a\x57\xce\x30\x43\xc2\xc6\xfd\xc9\xd0\x86\x36\xd3\xd9\xbe\x8b\x12\x16\xf7\x90\x6f\x43\xff\x12\x71\x4f\xc1\x87\x09\x34\xe9\xb2\x5a\x2f\x62\x18\xc5\x50\x30\x9d\x09\x70\xd8\x46\xe7\xa0\x1f\x86\x35\x27\x13\x74\xbb\x7d\xd6\x65\x2c\x41\x9f\x0d\x07\xe6\x89\xd2\x4d\xcc\xfa\xae\xda\x32\x99\x93\xc7\xcc\x66\x22\x5d\xb1\xdd\xaa\x7e\x3e\xd7\x8e\x1e\xd4\xe3\x8f\x62\xfc\x18\x4b\xe9\xa1\xe0\x30\xf4\x8c\xa8\xb2\xcf\x5c\x59\xf3\xfd\x47\x05\xe1\x55\x6e\xf5\x06\x6e\x0e\x55\x09\xe1\x67\x3d\xa8\x65\x3d\x98\x80\x99\x87\x75\xb1\x20\x09\x35\x85\xd4\x07\x55\xe8\x0b\x33\x54\x26\x26\xec\x23\xe1\x10\xfb\x78\x85\x8d\x9f\x01\x9f\x40\xce\xfa\x2e\x46\x53\xc6\x04\xeb\xab\xde\x3c\x45\x18\x30\x10\x9d\x54\x1b\xd5\x2d\x6a\x7f\x32\x52\xc9\x91\xd1\xc1\xd1\x0b\xdc\x2f\x4b\x32\x4e\x20\x9d\xa0\x76\x95\xdb\x47\x77\xbe\x34\x44\x6d\x8f\x57\xb1\xe4\x64\xc0\xf7\x9f\xfd\x85\xd3\xb2\x5b\xc3\x48\x63\xde\x7b\x2b\xe4\x99\x8c\x73\xc9\x67\xac\x3f\x61\x41\xf5\x1a\x80\xfa\xfc\x7e\x9d\x65\x49\x76\xc9\x06\x13\x16\x98\x67\xfd\x41\x71\x78\x29\x57\xc5\x0e\x26\x2c\x70\x6f\x41\x49\x16\x7c\xbb\x25\x0b\x54\xcb\xaa\xeb\x17\xa8\x62\x27\x49\x51\xa8\xfa\x54\x5b\xe6\x59\xd7\x77\x12\xdf\x1e\x6b\xf3\x68\x76\xf0\x54\x7d\x74\xef\xfa\xfb\x1b\x11\xcf\xf8\x8c\x3d\x56\x05\xf5\xb3\x4e\x3f\x4c\xd3\x5f\xf3\x44\x4a\x9e\xb1\x67\xea\x5b\xf5\x6e\xfa\x99\x8a\x82\xcf\xd8\x0f\xea\x9b\x7e\xd6\xe9\xc7\x4b\xed\x11\x96\x0d\x0e\xd4\x27\xfb\x6a\x3e\x66\x33\x7e\x5b\xe5\x78\x82\x39\xfc\x34\xdb\xb6\xe4\x39\x9f\x1d\xae\xa5\x38\xce\xa6\x6c\xf0\x54\x77\xc0\x4f\x34\x9d\x58\xf0\xe9\x55\xb1\x5e\x9e\x5d\x25\xab\x95\xaa\xf1\x3b\xec\x4e\x3d\xb5\x9e\x75\xa9\xb2\x7d\xef\x67\x5b\xba\x66\xb3\x38\xbd\xfb\xcc\x6d\x65\x07\x7d\x6c\xb6\x96\x58\xcb\x38\x63\x07\x03\x2f\x8b\x37\xa1\x88\x35\xd5\xac\x9a\x49\x35\xef\x3a\x83\x9a\x46\x6e\x73\x3c\x9b\xb0\xc0\x4f\xf0\x26\xd7\x66\xf9\xc1\xce\x70\x2d\x8b\x9e\x31\x9b\x67\x70\xe0\xe6\xba\x9e\xab\x9a\x5c\x97\xf5\x49\x7d\xd2\x6b\xf9\x71\x92\xcd\x14\xbb\x02\x4f\xed\xec\xd7\x3f\xd4\xe7\xd5\xe5\xfe\xde\x9b\xda\x7a\xdd\x7a\x9a\x6c\xc6\x83\x41\x35\x75\x36\x5f\x49\xd0\x56\x7e\xa9\x81\x1c\xa9\x9b\x5b\xce\x36\x67\x32\x96\xeb\x22\x5a\x3a\x30\x87\xc3\x34\x15\xd3\x17\x71\xc1\xa3\x3e\x1c\x65\x97\x49\xc6\x8b\x68\xe3\xef\xd6\x1b\xeb\xf1\x18\xcd\xdb\xad\xa3\xc7\x5e\x61\x4e\x80\x85\xbf\x41\x9d\xab\xc6\xa1\xfd\x66\x76\xa4\xfd\x90\xb9\x0f\x6e\x3f\x3a\x07\x90\xbd\xe5\x48\x44\x7e\x5c\xe2\x23\x5e\x9d\xc6\x17\x5c\x4b\x36\xeb\x3a\xb7\xa4\x29\xac\xb4\x24\xc2\xa5\xc9\x5e\x93\x46\x5a\x0f\x98\x1a\x91\xba\x13\xcc\xdc\x72\x3d\x81\x39\x57\x9f\x83\x5e\xef\x91\x8c\x8b\xab\x22\xa0\x93\x61\x2d\x62\xe9\xf8\x09\xe0\xa5\x94\x24\x14\xdd\x81\x11\x9b\xc1\x85\x30\xe5\xcc\x66\x80\xf1\x01\x5c\xf3\x1e\x86\x13\x20\x9c\x4e\x4a\xa3\xf5\xe5\x06\x77\xf5\xa7\x06\x77\xef\xb0\xbc\xf8\xaa\x5f\x1e\xd6\x2a\x17\x97\x39\x2f\x0a\x1c\x5f\xdb\xf0\xb8\x1d\xde\xef\x85\x6a\xb6\x31\xbc\xf1\x81\xcb\xb0\x3b\x9e\x53\x1f\xa9\x7f\xdd\x6a\xc9\xfb\x57\x2b\x73\xc3\xca\xbe\x76\xb5\x60\xb3\xe4\x72\x21\x66\x51\xf0\xee\xf4\xec\x43\xa0\x5d\x91\xf1\xb2\x39\x4a\x22\xf5\x95\xa7\x22\x5f\x7b\xe2\x6a\x34\x3e\x98\x44\xb8\xb0\x8d\x21\x1b\xe2\xcd\x4e\x08\x3a\x4d\xdf\x19\xf4\xcb\xff\x89\x45\x8c\xd7\x05\xff\xb7\x16\xaf\x87\x55\xcc\x76\xd7\xf0\xec\x7f\x78\x0d\x1f\x05\x7b\xbc\x65\x60\xd9\xc3\x9b\xae\x5a\xd0\xda\xa6\x93\x2d\x40\xfa\xfb\x9f\x1b\xe0\x7f\x23\x32\x51\x43\xab\x20\xf4\xd5\xd1\x9b\xa3\x0f\x47\xc1\x0e\x70\x76\x3c\xf4\xb1\xdb\xfb\x0f\xff\xa3\xbd\xdf\x0b\x1e\xcd\x73\x91\x49\x7f\xa3\x1d\x7e\x78\xf9\xd3\x9f\x1d\xc5\xbb\xff\xe9\x51\x5c\xc4\xd3\xab\x7f\x77\x10\xef\xff\x85\x9d\x02\xd9\xfd\x7b\x25\x77\x83\xc9\xff\x14\x1a\xbf\x48\xf9\x48\xb2\x60\x4f\x1b\xf5\x7d\x7c\x7f\xec\xae\x35\x08\xa7\x2d\x5b\x89\x48\x96\x3b\xec\xd7\xc0\x13\xc8\x6b\xda\xaf\x20\x7b\xe2\x8a\x3a\xd4\x91\x4d\x86\x16\x21\xd6\x30\xa1\x1a\xd8\x5b\x94\x97\x50\x38\x57\xbf\x8f\xbd\x9b\xdd\x13\x73\x57\xfb\xc0\x2d\x0a\x3a\x16\x36\x02\x1f\xe6\xdb\x89\x1e\xf2\x9a\x0a\x98\xe8\xad\x0b\x7e\xc2\x97\x82\xb4\xc0\x86\xbe\xf8\x33\x8e\x74\x9d\x73\x47\x1f\x0f\x61\x4f\x32\x0a\x27\xda\xed\x4e\x59\x52\x18\x73\x90\x13\x3d\x80\x63\xce\x3c\x0f\x30\xdd\xba\x07\x98\x11\x36\xfd\x26\xbe\x13\x6b\x79\x34\x9f\xf3\xa9\x8c\x30\x45\x3f\x57\x83\xfd\xcc\x2b\x35\x30\xcc\xf0\x9e\xcf\xbd\xeb\xe8\x63\x5e\xeb\xb9\x74\x83\xe6\x78\x6d\x88\xd7\x7c\x26\xec\x6e\xdb\x10\x5d\xfe\x7b\xdd\xf5\x50\x18\x9b\xe1\xbc\xe1\xac\xdb\x87\x57\x9c\x75\x07\xf0\x5a\xbb\x2f\x82\x17\x9c\x6d\x50\x77\xb3\xdb\xb7\x4e\xba\xba\x7d\x54\x30\xe9\xf6\x41\x72\xfc\xe1\xcb\x38\xc1\x87\x55\x5c\x14\x37\x22\x9f\xa9\x67\x13\xaa\xa1\xdb\x07\x54\xc8\xea\xf6\x61\x29\x32\x89\xa5\x6f\x38\xbf\xc2\xe2\xc9\x92\xdb\x0c\xf6\x39\xb0\x2f\xfb\xa9\x98\xc6\x69\xa0\x78\xc1\x6a\xae\x7e\xc5\xb9\xfa\xd2\x0d\x0d\xc1\x81\x78\x30\xf1\x8d\x3a\x6d\x55\xe2\xa0\x4a\xfb\xa8\xd2\x82\x05\xc6\xd5\xc1\xc0\x39\x8b\xa4\xe8\x5d\x27\x45\x72\x91\xa4\x89\xbc\x3b\xd3\x1e\x3f\x5e\x29\x40\x6b\x56\xf7\x53\xb5\x62\x9a\xba\x65\xdc\xdc\x6d\xf9\x61\x8c\x85\xbe\xdd\xe5\x05\x09\xa2\xb9\x98\xae\x8b\x7d\xac\x3d\xe5\x81\xc7\x3b\xdb\xab\xce\x37\x1c\x45\x82\x44\x32\x41\xd1\x17\x39\x74\x49\x70\xfc\xf6\xdd\xc7\x0f\x41\x97\x31\x74\xc6\x26\xe3\xcb\xb7\xf1\x92\xd3\xed\xb6\xfb\x82\xa3\x52\x93\xec\xe5\x3c\x9e\x9d\x66\xe9\x1d\xdd\x6e\x83\x0f\x47\xff\xf8\x70\xf8\xfe\xe8\x30\xd0\x36\x96\xdd\xea\xeb\x76\xdb\xed\x3a\x6f\xd4\x99\x3c\x9a\x25\xb8\xff\xbd\x11\xfd\xa2\x26\xe3\x15\x2e\xbf\x75\x39\x94\xf2\x38\xff\xa0\x03\x70\x93\xd7\x9c\x2a\x80\x30\x9f\x0a\x2e\xed\x07\x1f\xe0\x10\x6e\x4a\x0a\x83\xbe\x3f\x57\x7f\x54\x84\xce\x26\x29\x5e\xab\x79\xf8\x45\x4f\x43\xf4\x13\x07\x91\xbd\x48\xd7\xb9\x4d\xf8\x85\x43\xce\xe7\xd1\x7d\x30\x5d\xcd\x39\x5b\xf7\xe6\x49\x36\x7b\x75\x7a\xf2\x56\xcc\x50\x07\x53\x7b\xce\xca\xc2\x90\x20\x99\x26\x6e\x32\x9e\xbf\x32\xc6\x91\xb4\xc5\x9c\xf3\x8a\xdf\xcd\xc4\x4d\x16\xc0\xaf\x1c\x4c\xa0\xc6\x9d\x3c\x4b\xa1\x88\x1d\xcc\xf5\xcd\xfd\xb9\x56\x22\xc9\x24\xcf\xbf\x98\x4f\x8a\xf5\x74\x81\x51\xb6\x1f\xcc\x56\x41\xa0\xb5\x1d\xfd\x88\x99\xed\x3e\xc5\x8d\xfa\xb3\x42\x98\x83\xef\x28\xfc\x1d\x31\xe8\x77\x14\xfe\xaa\x2f\x95\xb4\x94\xda\x5c\xfb\x9b\xb0\x5c\x6e\x29\x7e\xe3\x35\x3b\xc4\x96\x08\x5e\x96\x19\x09\x43\x51\xa9\x51\x2c\xe3\x15\xe1\x70\xaf\xe0\xbe\xd5\x7b\x56\x36\x46\xa3\xa9\x49\xdb\x15\x98\x8b\xe5\x45\x44\x43\xd9\x82\x12\x4e\x47\x18\x28\x8e\x1b\x1b\x60\xc8\x2a\x48\xfa\x07\x6f\x68\xa6\x99\x35\x1f\xcb\xc9\x48\xfd\xb3\x4a\x57\x63\x39\xa9\x0a\xfd\x8d\xd7\xc3\xb6\xaa\x39\x70\x77\xfc\x14\x92\x86\x00\xba\xd2\xbb\x21\x5e\x3b\x18\x6c\x64\x24\xc7\xd9\x24\x52\x1b\xaf\xe4\xc6\x7d\x92\x64\x52\xfd\x6a\xe5\x44\xa7\x4e\x5a\x9b\x52\x48\xd8\xb8\x72\xe1\x1d\xab\x9a\x38\x8d\x75\x85\x89\xe7\xf9\x76\x1c\x4f\x58\x82\x99\xd1\x93\xdd\xba\x58\x90\x98\x1a\xa5\x93\x4d\xe9\x2a\x28\xb0\x24\x4a\x89\xc5\xb8\x98\xa0\x63\x59\xed\xde\x4c\xbd\x3a\xaf\xbf\xd6\x93\xf8\x94\xa9\xe4\x71\x3e\x19\xa6\x63\xf3\x34\x61\x19\x99\xd2\x32\x1d\x17\xea\xa9\xa0\x65\x55\x45\xe2\x97\x4f\xc7\x89\xc9\xad\x7e\x2b\xe5\xdc\x12\x9d\xac\x0e\x6b\x1a\x1a\x0f\x44\x6e\x8a\x6d\x78\xa3\x64\x1c\x63\x50\xb0\x7b\x97\x3e\xa5\xd6\x91\xaf\x9e\x1e\x98\xea\x87\x1c\xd6\x4c\x8e\xe3\x09\xcc\xd8\xbd\x65\xd7\x34\x0c\xbb\x6b\xa3\xc1\x91\x64\xc3\xee\x14\xef\xfa\xbb\xb3\xd1\x74\xbb\xed\x16\xdb\xed\x6c\x34\x0d\xc3\xe2\x01\xc0\x5b\xa3\x9f\x41\xb5\x0a\x2e\x8b\xaf\x8b\x43\x09\xea\x28\x1c\xdd\x26\x92\xcf\xa2\xac\x77\x91\x64\x33\x5c\x60\x50\x4b\x9c\x45\x55\xdb\xc0\x6f\x13\x19\xfd\x83\x93\x14\x02\xf5\x18\x00\xa7\xc0\x15\x86\xb0\x89\xea\x59\xa5\x96\x94\x46\x5f\x68\x31\xc9\x22\x85\x52\xbf\x94\xed\x81\x8e\xa9\xd3\xf9\xeb\x3b\x84\x9a\x48\x09\xa2\x98\x7f\x72\x0b\xce\x28\xa8\x6d\x55\x96\xa8\x2d\x3f\xa7\x0d\x13\x4b\xe9\xab\x90\x4e\xf0\xc2\x87\xcb\x7b\xed\x9f\xdc\x16\x05\xa1\x8e\xbb\x9a\x0d\x02\x64\x95\xba\x50\x9c\xcd\x52\xae\x87\xab\x07\x6b\xa6\xe5\xef\xbc\x17\x53\xb4\x71\xb6\x8a\xbd\xd6\x56\xca\x84\xa7\xf9\x45\xdb\xbf\x18\xb3\x1e\x0c\x26\xdc\x2f\xc1\xaf\x2f\x12\x30\x4f\xf2\x42\xbe\x47\xcd\x22\xfc\x9c\xff\x37\x5b\x75\x2c\x1b\x26\x45\x95\xed\xd0\xfd\xbd\x1c\x94\x7f\xc2\x72\xc8\x35\x30\x28\x01\xad\x85\x5f\xf1\x3c\xb9\xe6\x33\x6c\xe5\x75\x2e\x96\xef\x14\x9c\xde\x73\xed\x86\xf2\x7d\xab\x24\x17\xa3\x73\x8a\x6a\x76\xac\xdc\xbe\x52\x9a\xea\x79\xb3\x35\x22\x19\xe3\x90\xb3\x18\x7e\xe3\x24\xbb\x47\xd5\xae\xae\xd6\xd5\x04\x64\xee\x01\x72\xee\x01\x32\xb7\x80\x1c\xaf\x56\x3c\x46\xa8\xe5\x10\xe8\x97\x40\xd1\xe6\x0e\x98\xb9\x03\x66\x95\x6a\xc0\x9e\x5b\xb0\xcf\x28\x2a\xd9\xd3\x08\xcf\x84\x04\x62\x5a\x5f\xee\x41\xa9\x66\xd9\x1f\x72\xab\xbe\xf9\x6f\x9a\x05\x6b\xa8\x8b\xd1\x21\x1e\x78\x18\xa2\xc1\x68\x04\x69\x93\x17\x5d\x53\x18\x36\x53\x88\xa4\xe0\xaf\x58\xd3\x98\xac\x25\x3c\x7a\x53\xf9\xd4\x6b\xdc\xcc\xac\x89\x84\x67\x4f\x5f\xcf\x6e\x2a\x53\x5b\xfb\x1e\x43\x9b\x4a\x59\xd3\xa8\x28\xca\x9a\x82\x51\x66\x5d\xe1\xbf\x8e\x31\xc8\x16\xe4\x0f\xea\x0f\xf9\x59\x83\x09\x05\xc1\x3c\x0b\x31\x1f\xca\x21\x66\xff\xe4\xbe\xf9\x98\x1b\x0e\x22\x92\x9d\x51\xe1\x5d\x32\x8f\x73\x70\xef\xb8\xd8\xde\xeb\x6d\xa2\x7d\xed\xa3\xcb\x9f\xdd\x4b\xfb\xbf\xf2\x1d\x7d\x66\x51\x42\xdc\x66\x63\xd2\x9e\x75\x37\x9f\x84\x1c\xe2\x76\x15\x4c\xae\xf5\x19\x75\xbc\xb7\x8d\xc2\x7d\x95\x7a\x9a\xda\x82\x1b\x37\x69\x51\x30\x4b\xae\x03\xf0\x27\xae\xcd\x46\xd6\x9a\x34\x4a\xc9\xb8\x84\x4c\xb2\xfb\x9d\x8f\x8e\x3c\x46\x33\xda\x61\x43\x35\xbd\x22\xdb\xdc\x43\xbb\x68\x06\xa8\x0d\xbf\x4e\x8b\x58\xaa\x5d\xed\x8c\x3b\xb2\x30\xcc\x20\x61\xbc\x97\x27\xab\x55\xca\xff\xa1\x75\xcd\xf0\xf9\x37\xd4\x35\xd3\xcf\x67\xc9\x67\x6e\x74\xce\x50\x97\xc8\x02\x7d\xdd\xb7\xa5\x07\x82\x25\xba\xb9\xe4\x3d\xa9\xf9\x0a\x98\x6b\x4e\x58\x6f\x85\xee\x80\xc2\x8a\xcd\xc7\xfd\x09\x2c\xd8\x7c\x3c\x98\xc0\xd2\x42\xe0\x39\x62\x7d\x69\x9a\x05\xfb\x60\x18\x0a\xc8\xc3\xd0\x26\xbd\xd3\x83\xa1\x70\xcd\x36\x37\xc9\x4c\x2e\xa2\x14\x16\x3a\x2e\x45\x0a\x52\xac\xa2\xfd\xf4\xd1\xc1\x5e\x0c\x29\x9f\x4b\xfd\x9c\x94\x70\xd1\x6c\x07\xd7\x08\x56\xaa\x5a\x7c\x7c\xc3\xe3\xeb\x24\xbb\xd4\x0d\x61\x8a\x6b\xe7\x92\x7d\xe6\x64\x5d\xb9\xe8\xaf\x33\x4b\xda\xda\x63\xb3\x20\x5d\xe3\x08\x87\x33\x8f\xad\xba\x84\x59\xa5\x9c\x52\x15\xaa\xf1\x64\x5c\x87\xb4\x18\x5f\x42\x01\x33\xb5\xcf\x9a\xca\x84\xc5\x2a\xce\x02\xd8\x54\x0a\x30\x4b\xa3\x00\x73\x5d\x7e\x45\xe6\x0b\x3c\xb0\x85\x64\xc2\xd3\x54\xbb\x57\x1b\x51\xef\xc4\xa4\x01\x28\xb1\x07\x50\x69\x4d\xa5\xac\xb0\x13\xfb\x96\x3b\x1c\x62\x50\x77\x60\x4a\xd4\x94\xbb\x26\x14\xa6\x3e\x48\x8c\x27\x14\xd6\x6c\xaa\x4d\x63\xa6\x0a\x24\xe6\x95\xec\xa4\xaf\xc0\xc5\xbd\x69\xce\xc6\xdb\x10\xb5\x75\x58\x59\xf1\x48\x18\x12\xf7\x4c\x28\xb8\x67\xe3\xc4\x99\xc2\x78\x3d\x31\xba\x3e\x55\xdd\x0a\x32\x97\x8d\xa6\xe0\xba\x99\x70\xf1\xb5\x9d\xf9\xc2\x8a\x2f\x6d\x9f\xb4\x18\xca\x74\xe7\x92\x7d\x89\x5f\xf6\xb6\x72\xe6\x6d\xdd\xdc\xdb\xba\x49\x7d\xeb\xe2\x62\x5d\x0c\x67\x6d\xea\x5a\x95\x51\x97\x59\xc2\x9f\xf5\x12\x52\x18\x37\xa1\x2a\x97\xda\xa0\x6e\x6e\x3b\x0e\x66\x6d\xa3\x18\xcc\x4e\x8f\x9e\x3e\xed\x83\xe9\x5d\x24\xc1\xf4\x2d\xca\xcc\xd3\x6f\x51\x0e\x55\xbf\xa2\xa4\xa4\x13\xad\x5c\x69\x6b\xdc\x63\x03\x6f\xad\x52\x35\x2f\xf1\x84\xc2\xdd\xbd\x93\x62\x8f\xbb\xa6\x5a\xdb\xf3\xbe\x75\xc3\xde\x65\x9e\x0a\x5b\x7f\x32\xf2\x5f\x22\xe4\xfa\x76\xca\x0e\x5a\xcb\x0e\xfc\xb2\x03\x2c\xbb\xab\x5e\xff\xfc\xc0\xcb\x74\xe0\xd4\x20\x72\x45\x61\x9a\x45\x13\xd5\xb6\xca\xc3\x30\x47\xca\xcc\xec\xb8\x9a\x42\x67\xb2\xdd\xba\x42\xc6\x99\xcb\x3c\xbe\xb2\xab\xb1\x63\xa3\x97\xcc\x7d\x11\x87\x0e\xd2\x70\xb7\xe2\x61\xb8\x70\x70\xe6\x9e\x58\x77\xa0\x1d\x8d\xf9\x62\x0c\xaf\x08\xf1\x72\x1a\x84\xb6\x86\x19\xcc\x61\xc5\xa6\x5a\xa8\x7a\xe1\x60\xe0\x8e\xad\x46\x2b\x45\x9a\xbe\x10\x6b\x34\x90\x7f\x99\x26\x3c\x93\xef\x15\x2c\xd1\xc8\x20\xe7\xbe\x45\xce\x7d\x8d\x91\xfb\x88\xa3\xfb\xa5\xea\x75\xba\xdd\x6a\xaf\xbc\x53\x2c\xf8\x0f\x6d\xd1\x64\x5f\x7f\xdb\x6e\xbb\xde\xa7\x2e\xef\x61\x97\x79\x41\xd7\x4c\xbb\x91\x51\xcd\x92\xbb\x1e\x36\xf4\xe8\x80\xc2\xac\x9e\xae\x1b\x7e\x74\xe0\x39\x80\xbb\x65\xae\x96\x91\x7b\x52\xe0\x80\x8a\x9b\xb6\x31\x38\x72\xcf\xbf\x0d\x6b\x8d\xdd\xec\xdf\xf5\xd4\x30\x1a\x6d\x1d\xed\xdf\xf5\xa4\x58\x61\x94\x98\x94\x92\xb9\xfe\x56\xfc\x91\x4b\x42\x0e\xfe\x82\x2f\x2b\x71\x63\xbb\x0a\x07\x74\xcf\x4b\xd3\xdd\x84\x03\x4a\x1f\x3d\xa6\xf4\x7f\x1d\x30\xd6\xd7\x11\x5f\x06\x5e\xc7\xaf\x98\xa9\x67\x19\xdf\x6a\xcf\x43\xf1\x45\x41\x88\x5a\x00\xdd\xd1\x5f\xf5\x6c\xd3\xfd\x35\x85\x35\xdd\x3b\x80\xd3\x2f\x95\xf8\xc9\xac\x0b\xdd\x9f\x51\x98\xd1\xbd\x83\xa1\xdf\x6f\xd7\xc1\xab\x5a\x77\x4f\x55\x3f\xcb\x6a\x12\x0d\x95\x76\xed\x61\x5f\xf7\xcc\x6a\x66\x4e\x1b\x0b\xd0\xc2\x21\x87\xb5\x43\x0e\x33\x1f\x39\xcc\x61\x7a\x81\x86\xb4\xe0\x10\x25\xbb\x47\x54\xd9\xda\x2e\xa1\x70\xbd\x83\xf5\xbf\xef\x53\x1a\xfd\x4b\xbd\x50\xa8\x28\x81\xcb\x09\x85\xdb\x07\x90\xd1\x9d\xa2\xe4\x5d\xed\x68\x1e\x4e\x61\x7c\x37\xa1\x70\x73\x3f\x5e\x37\x66\x6c\xf7\x1c\x0e\xa0\xf7\x28\xcf\x66\xfe\x0e\x75\x43\xa3\x4e\x6b\x62\xc5\xf3\x22\x29\x6a\xe3\xde\x99\x03\xbc\x93\x22\x5f\x9c\x50\x1d\x06\x46\xb1\x18\xc3\x46\xf9\xd6\x33\xa4\xc3\x1d\xd6\x75\xf1\x57\xd0\xeb\x28\xf5\x4f\x5e\x69\x4e\x39\xff\x26\xe7\x78\xb9\xe2\x79\x2c\x93\x6b\xfe\x13\x32\x6a\x44\xb6\xdc\xf9\xb9\xf9\xbc\x05\xc4\x54\xd1\x1d\x14\x0a\x7d\xdc\xe0\xa2\xdc\xc2\x1d\xdc\xdc\x4f\x30\xd5\x63\xb9\x55\xda\xc3\x3e\x21\x18\xf7\x72\x21\x24\xa4\x14\xe5\xd5\x17\x25\x14\xbb\xd5\x49\x09\x1e\x89\xaf\x39\x59\xc5\x92\x76\xfb\x25\xac\xb5\x89\x40\x22\xbf\x18\x8c\x4d\xb5\x13\x6d\xc4\x35\xcf\xe7\xa9\xb8\x89\xec\x7d\x05\x18\xa9\x33\x0a\x8f\x8b\x28\xc8\x44\xc6\x55\xa2\x76\x3e\x14\x05\xf1\x45\x21\xd2\xb5\xe4\x01\x7c\x46\x35\x23\x8b\x3f\x21\x37\x48\xf5\x42\x48\x29\x96\x15\x76\xd5\x31\xee\xde\xc7\xb3\x64\x5d\xf8\xb1\xdc\x34\x64\x47\x1b\xb1\x8a\xa7\x89\xbc\x8b\xfa\x6d\x8d\xd8\x6c\x56\x8e\xef\x72\xf7\x1e\x83\xcc\xe3\xac\x98\x8b\x7c\x19\x05\x18\x59\x8f\x0c\x68\x00\x71\x96\x68\xdf\x65\x51\xf0\x0d\x1e\x64\x9d\xc0\x12\x15\x8a\x1e\x08\x96\x45\x27\xa0\x36\x85\xf7\xa4\x73\xab\x54\xf4\x78\x5c\x24\xd9\xa5\xfa\xe1\xc7\xd9\xe9\x5a\x52\xdb\xb8\x21\xbc\xa3\x8d\xab\xfc\xd5\x3a\x37\x8d\x04\xed\x75\xcd\x4c\x86\x5e\xb1\x10\xb9\x3a\x4e\x83\x65\x11\xd0\x52\x73\x62\xd5\x28\x06\x60\x3c\xfa\x44\x01\x7a\x3f\x0a\x40\x9f\x51\xc1\xa0\xdf\xff\x5f\x81\x3d\xa8\xcc\x5b\x7d\x22\x9f\x62\x52\x23\x52\x5d\x60\x00\x1c\xdf\x02\xd3\x9c\xe1\x24\xfc\x99\xae\xcd\xd2\x6d\x22\xff\xad\x49\xf2\x99\x93\x68\xd3\x06\x29\xfe\x39\x5b\x6b\xdb\xec\xa6\xce\xc1\xd3\x7e\x5f\x35\xfa\x75\x6d\x42\xd0\x39\xc0\xfc\x49\x36\x4f\xb2\x44\x72\x35\xb3\xc1\x7f\x5d\xf1\xbb\x79\x1e\x2f\x79\xd1\xd1\xf4\x7e\xb4\x09\xfa\xff\x2b\x88\x36\x3b\x70\xd2\xa7\x01\x38\x38\x1a\x94\xa0\xe7\xb7\x25\xe3\xc0\xcf\xf8\xb8\x6c\x34\x72\x9b\x48\xd7\x86\x5b\xd0\xaa\x36\x37\xdb\x8d\x72\x66\xcc\xf7\x77\x6f\x40\x83\x12\x70\x7d\xdb\xfa\xde\xfb\xe1\x00\xbf\x3f\xd0\xe7\x52\x47\x24\x9c\xa7\xc9\x2a\xea\x0e\xc0\x45\x26\xfc\xa0\x90\xf7\x7b\x04\xe9\xa0\xa4\x44\x60\x90\x69\x22\x24\xa5\x10\x7f\x1d\x37\xa6\x3d\x88\x21\x4d\xaf\x0d\xdf\xde\xf3\xb9\xe6\xc5\x70\xca\x75\xdd\x2d\x0e\x1d\xb8\x6f\x54\x5e\x71\x6e\xb3\x1a\xe7\x36\xaf\xc9\x86\x56\x15\xfd\x39\x1f\x05\xba\xb5\x20\x9a\xa3\x01\xd0\x2c\x29\xe2\x8b\x94\xcf\x60\x59\xb5\xb4\x08\xc3\x05\x1a\x02\x99\x8f\xa6\x2f\x17\x55\x8e\xeb\x30\xbc\x86\xcb\x2a\x87\x37\x1d\x70\x57\x65\xbb\x0c\xc3\x4b\x50\x64\x19\xde\xac\x9a\xef\x37\xd5\xf7\xdb\x30\xbc\x85\x23\xfb\xdd\x20\xa6\xca\x10\xea\x0a\x85\x12\x2f\xd2\x75\x0e\xa7\x9e\x5d\xe3\x4b\x7c\xc6\x4b\x4a\x38\xab\x9e\xad\x38\xe1\x77\x4c\xfa\x99\xdf\xbd\x12\x37\x19\x7c\xb0\x6f\x1f\x57\xf0\x0e\x9f\x4f\x14\x35\x8d\xdf\xde\x57\xef\x6a\x63\x73\x78\x5b\x25\x7c\x5c\xc1\x39\xbe\xe1\xd0\x8e\xb2\x19\x9c\x54\xaf\x27\xe2\x9a\xc3\x61\xf5\x8e\xea\x91\x70\x8c\x09\xaf\xf2\xf8\x52\xd7\xf6\x19\x2f\x9c\x2f\xb4\x17\xc7\x37\xd5\x1a\x7c\x1e\xf5\xa3\xcf\xf0\x8a\xf1\x9e\x37\x6d\x28\x6e\x82\xd7\x86\x12\x80\x17\x55\xf6\xd7\xd5\x92\xbd\x86\x5f\x77\x59\x72\x0d\x48\x01\x04\x0e\x8c\x14\x53\xee\x01\x91\x15\xf7\xe5\xea\x48\x6a\x65\xdc\xa1\x26\x1b\xb4\x20\x51\x3d\xba\x7a\x76\x97\x3b\x80\xc0\x5b\x5c\xfb\xd6\x5c\x4a\xb4\x32\x55\x0b\xe9\x99\x9b\xaa\x27\x5c\xb8\xea\xc9\x14\xc3\x04\xb3\x80\xf6\xf9\xe3\x0a\x9f\xdc\xe2\x55\x6f\x38\xd9\xd5\xab\xc9\x68\x97\xad\x7a\x51\x8b\x56\xbd\x9d\xe9\x3b\xde\xc0\x5b\xb0\x00\x02\xbb\x5c\x01\x04\xcd\xb5\x51\x5f\xef\x56\x28\xe4\xf8\xa6\x29\x2a\x50\x7b\xfa\x63\x53\xa0\xf0\x53\x53\x3a\xf6\x0b\xfb\x69\xdc\x9f\xc0\x1f\xec\xa7\xf1\x60\x32\x5c\x86\xe1\x2f\x61\xf8\x87\x33\x44\xfb\x99\xfd\xc1\x09\x85\xbf\xb3\x9f\x7b\xf5\x1b\x78\xf8\x2b\xfb\xb9\x57\xbb\x82\x87\xdf\xd8\xcf\xbd\x9c\xcf\x77\x9d\xcf\xd4\xe3\x12\x3b\xe6\xb5\x95\xf1\x3d\x98\xd4\x79\xda\x3b\x4b\xc2\x7d\xe6\xad\x81\x10\x65\x18\x4a\x92\x53\xe8\x66\x61\xf8\xb1\x22\xcd\xdd\xe3\x98\x4f\xf0\x73\xbf\xa4\xb4\x6c\x27\x03\xb3\x36\x32\xd0\x07\x18\xdf\x7e\xe8\x0f\xd2\xed\x53\xf8\xc6\x69\xc6\x60\x3e\x62\xe4\x69\x13\xa3\x54\xd3\x22\x9f\xf9\x25\x0c\x6f\xc2\xb0\x7b\xe1\x75\xcd\xf2\xdb\x04\xe9\xf6\x0b\xb8\x81\x5f\x8c\x5c\xe6\x6f\xec\x1f\x24\x30\xf7\xfd\xef\x28\xfc\x53\xbf\x8a\x55\x00\xc7\x14\x38\xaf\x5e\xdf\x52\x90\xde\x6b\x8d\x0a\xfc\x45\x5f\x1c\x34\x8c\x8b\xdf\x87\xe1\x7b\x7b\x3f\xce\xbd\x66\x0e\x15\x3d\x5a\xd5\x74\x4e\x41\x78\xaf\x27\x14\x92\x07\xda\x21\x7f\x25\x9c\x02\x42\x0d\x85\xab\x30\x44\x3f\x14\x14\x14\x78\xc5\x9c\xd5\x56\x8e\xd3\x8d\x9b\xbc\xed\x96\x7c\x53\xa9\x24\xd9\xa7\x0f\xa8\x0d\x43\xe1\xef\x84\xd3\x30\x24\x7a\xc2\xcf\xc2\xf0\x8c\x70\x4a\xe1\x65\x18\xbe\x34\xfd\xf7\x4d\xe1\xad\x68\xa6\xae\xdc\xe1\x6a\xa7\x5e\xb4\x21\x8b\xb7\xba\x8c\xad\xc2\xb0\x4b\x82\x43\xc3\xeb\x68\x1d\x19\xf4\xb0\x9e\xf3\x39\x45\x6b\xf9\x9a\xb8\x6e\xba\x3b\x16\xb5\xa6\x05\xaf\xe0\xee\x97\x1a\x14\x06\x1d\x5d\x35\x3a\x71\x22\x55\x3e\xd6\xed\x83\xcf\x43\x55\x20\xa1\xe6\xb7\xa6\x31\x41\x37\xfe\xc7\x38\x97\x7a\xf0\x14\xac\xda\x90\x16\x5f\xf8\x53\x17\x86\x29\x27\xb4\xd1\x7a\x0b\x28\xfc\x1e\x86\xbf\xab\x75\xfb\x8a\x9a\x8e\x90\xde\xaa\x6a\x43\xff\xf5\x2d\x55\x9e\x86\xe1\x29\x1a\x0e\x52\x0a\xeb\xd6\xd9\xaa\x75\xea\x63\x6d\xe2\xba\xbb\x46\xf8\x8d\x59\x1b\xfc\x2b\xb3\x66\x77\x99\x99\xb7\x0f\x61\xf8\x41\x8d\xfa\x54\xcd\xc9\x9f\x9a\xc2\xd6\xfe\x9d\x1a\x60\x9c\x71\xb6\x1a\x5a\xd0\x62\x8c\xcd\x78\x18\xfe\x8a\x80\x14\x86\x64\xc6\x59\x10\xdb\x20\xbc\x9c\x6d\xca\x7a\xce\x11\x99\xeb\x03\x96\xbd\x80\x79\x45\xfc\xb0\x25\x8d\x48\x10\xd7\x6b\xdb\x6e\x55\xe6\x5c\xa4\x9c\xd9\x3a\x28\xcc\xad\x61\xb7\x3b\x24\x27\x6c\xa9\x9b\x5b\x71\x76\xc8\x49\x02\x92\xc2\x02\x1f\x7f\x83\x6f\x28\x2c\xf1\x71\xc5\x61\xc1\x29\x5c\xf3\xe6\x99\x70\xc1\xd9\x35\x57\xa7\xc2\x25\x3e\x0c\x26\xf7\x8a\x9d\x2f\x39\x46\x01\xac\x44\xca\x77\x9c\x5d\x70\x8d\xe8\xba\xcb\x8a\xfd\xde\x31\x81\xfd\x1a\x56\x79\xaa\x59\xe5\x19\xfc\x12\x86\xe3\x69\x8d\x1c\x83\xa3\x09\x2c\xc3\x70\xea\x26\x8b\x1a\x7d\xb0\x28\xe1\x60\x1d\x42\x9c\x82\x39\xc1\xa3\x58\x25\x9a\xb3\x3b\x9a\x9a\x97\x8f\xab\x68\xad\x1e\xdd\xe9\x1d\xfd\x0d\xfc\xd3\x3b\x92\xee\xeb\xc7\x55\xc4\xd5\x8b\x3b\x95\xa3\x7f\x42\x75\x9a\x47\x39\x07\xef\x38\x8f\x84\x7b\xc5\xf3\x3c\xca\xb4\x6e\xda\x92\x83\x3d\xc9\xa3\xe5\x68\x7f\x10\xbd\x29\x61\xce\xe1\x57\x0a\x05\xdc\xf1\x51\x73\x92\x12\x59\x9f\x24\x55\xc5\x47\xd0\x24\x54\x94\x96\xf0\x8a\x56\xbe\x8e\x20\xad\x8b\x03\x0c\xef\xef\x78\xcf\x24\x4b\x93\x8c\xef\xcf\x53\x45\x44\xc4\x69\x72\x99\x1d\x4b\xbe\x2c\x22\x77\x73\xf2\xfb\xba\x90\xc9\xfc\xce\x68\xf9\x55\xe9\x15\xb3\x97\xf3\x34\xd6\x26\xf0\x3a\x40\xfe\x87\x78\xf5\x53\x72\xb9\x48\x15\x03\x6b\x78\x53\x64\x55\x56\xb1\x36\xbf\xdf\xe1\x5c\x6b\x5f\xc5\x5a\xaa\x1e\x39\xe9\x41\xd4\x07\x1b\xb3\xbd\xce\x06\xf7\x61\x15\xcf\x66\x8a\xbb\xed\xc3\x74\x9d\x17\xaa\x26\x23\xc0\x08\x60\x5d\xf0\x5c\x07\x2e\xb0\x62\x8c\x6b\x9e\xcb\x64\x1a\xa7\x87\x6a\x8c\x51\xb0\x4c\x66\x33\xa4\xdc\xf6\x97\xe2\xf3\xbe\xbe\x03\x8e\xb3\x29\x0f\x6c\xfe\x60\xff\x06\x47\xd3\xf6\x4d\xf2\x5b\xf9\x8a\x4f\x85\x95\x04\xe8\x54\x1d\xa2\xdf\x89\x39\x20\x08\xa3\x08\x6b\xd7\x7a\x9a\x68\xfa\x8d\xd1\xed\xd5\x20\xd0\xd5\x80\x29\x59\x42\x10\x7e\xe3\x36\xa8\xe2\xa3\x5b\xc4\x30\x76\x88\x06\xd1\xa8\x42\xff\xb5\xe4\xb3\x24\xee\xac\xf2\x24\x53\xec\x28\xb6\x7f\x38\x53\x0b\x16\x19\xb7\x05\x65\x09\xb6\xde\x68\x53\x42\x8d\x74\xd9\x94\xa5\x17\xba\xfe\x05\x62\x8c\x17\x71\x81\xfc\x61\xec\x7b\xc4\x2a\x64\x43\xf7\x79\xc7\x99\x91\x09\xb0\x2c\xbd\xc8\x6d\xe8\x4d\x67\xb9\x4e\x50\xb9\x14\x75\x7f\xa6\x8a\xcf\xac\xab\x12\x6e\x4a\x0a\xeb\x7f\xfd\xca\x19\x66\x5f\xc9\xba\x3a\xb0\xf6\x5d\xd8\x67\x23\x0b\xcb\xd6\xaf\xc5\x5a\x0a\xcd\xa5\xb5\x71\xb1\x1a\xa5\x36\xef\x3b\x34\x1f\x6b\x19\xdc\xb9\xc7\xe0\xae\x6a\x0c\xee\xa2\xc6\xe0\x2e\x19\xef\xa9\x39\x88\x93\x8c\xe7\xee\x76\x1f\xae\xab\xce\x2d\x47\x41\x9a\x04\xd1\x12\x1d\x5b\xb8\xac\x9a\xe9\xba\x64\xe4\xa2\xca\x79\x31\xda\x94\xd1\x05\xf5\xda\xba\xab\xf3\x5c\x17\x30\xae\xdf\x79\x2a\x06\x77\xc6\xb3\xa2\x9d\xb5\x75\x0c\xf6\x55\xf5\xf1\x28\x0c\x8f\x90\xa5\x35\x1f\xff\xba\x96\x92\xe7\x05\xbc\xac\xb2\xa8\xf3\x1d\xb9\xdb\x59\xa2\x15\x1c\x7e\xaf\xbe\x29\x12\x0d\x19\xdb\x76\xb6\xf9\x1d\x7a\xf9\x50\x7b\x95\xcf\xe0\x7d\x55\xec\x5d\x18\xbe\x83\xb7\x2d\x0c\xa4\x5b\xce\x00\x02\xb7\x68\x8e\xa1\xfc\xb3\xec\xe3\xee\x42\xf8\x89\x96\x97\xc2\x09\x6b\x65\x36\xcd\x64\x60\x02\x0e\xfd\x01\xa6\xd2\x0e\x53\x2d\xc3\xb9\x11\xc5\x9b\xbd\x30\x95\x14\x4e\xd8\x06\xdb\x89\x6e\xb6\xdb\x73\xbd\x46\xdb\x6d\x77\xe0\xe3\xe5\xa4\x84\xc3\x26\x3b\xd7\x10\x9b\xa7\x61\x78\x58\x91\x4f\x87\x3b\x0c\x09\x85\x71\x6a\xce\xe4\x63\xe6\xa9\xe4\x4a\xa1\xdd\x6f\xcc\x28\x7c\x66\xc7\x4e\x8d\xb4\x90\xe4\x78\x7c\x5c\xd9\xa7\xc3\x38\x78\x93\x14\x52\x75\xe7\x8c\x4f\x45\x36\x8b\xf3\xbb\x43\xcd\xd3\x4f\x28\xbc\x51\xe4\xc3\x03\x77\xc7\xae\x3b\x3b\xda\xd6\x86\x4d\x92\x14\x5e\xb1\xaf\xa1\x01\xe6\x9a\x06\x58\xc1\x89\x9e\xa9\x30\x9c\x1b\xb0\xee\xbe\x54\xcf\x97\x06\x48\x7f\xc7\x0f\x06\x2a\xaf\xf4\x8b\x01\xf1\xa9\x7a\x33\x1b\xdb\x22\x03\x1d\x65\x7c\xee\x21\x8d\xd7\x29\xbf\xd5\x12\x93\xcf\xea\x43\x51\x1f\xb4\xe2\x9b\xe6\x0e\x7e\x69\x85\x6f\xaf\x4a\xc5\x84\xbd\x66\x8b\xed\x56\xed\x65\xe7\xb4\x29\x0c\xc9\xab\x0a\x17\xe0\x67\xd4\xd4\x79\xd5\xbe\x3d\x58\x63\xd0\x35\x5a\xe7\x83\x6a\x20\x95\x14\x3e\x8f\xc8\x6b\xe6\x55\xbb\xdd\x2e\x46\xaf\x8d\x0a\x90\x6a\x9d\xa1\x0c\x8c\x98\xc7\xd7\xa3\xd7\x0c\xbf\x45\x26\xc1\x2b\xd9\xe8\x1e\x66\xa3\xbb\x17\x11\x53\xb9\xa3\xcb\x74\xb2\xab\x01\x72\xfd\x55\xd4\xdc\x1c\x35\xb8\x70\xbb\xc1\xa5\xbe\xfd\x78\x53\xc2\xdd\x6e\xa3\xaf\xe1\x95\xe2\x73\x8f\xb5\x0b\x01\x4a\x69\xf4\xaf\x75\xeb\xf5\x2e\xfd\xf4\x46\x91\x4d\x70\x6c\xbc\x2c\x7d\xed\x15\x8a\x23\xa3\x34\xfd\xd4\xa4\x95\x54\xea\xbe\x61\xa3\xdb\x68\xab\x36\x1a\xaa\x9d\xb6\xa8\xdd\x0d\x5c\x88\xdb\xb3\xe4\xb3\xa2\x7c\x02\x4d\x4c\xec\x5f\x88\x5b\x5d\xd2\x50\x37\x29\x9f\xcb\xc0\xd2\x47\x1f\xc4\x2a\xfa\xde\xbe\xbc\xd0\x57\x34\xdf\x2b\x92\xc3\x07\x25\x45\x98\x34\xe8\x32\xde\x5b\xc5\x29\x97\xd2\xca\x7e\x1d\x90\x23\xbd\xe2\x30\x76\xa7\x7a\x8e\x16\xe2\x5a\xd3\x38\x7f\xaa\x2a\x8f\xf4\x71\x22\xf7\xa7\x3a\xc2\xb9\x86\x0a\xff\x6a\xc1\xcd\xd4\x0e\x39\x03\x1a\x77\x6e\xbc\x61\x3f\x69\x0c\xfb\x49\x09\x2d\xfb\x3a\xda\xf8\x8b\xe3\x2d\x5a\x9d\x7a\x32\x38\xc4\x52\x70\xa6\xca\x60\xb0\xba\xed\x14\x22\x4d\x66\xfe\x1d\x86\x1d\xae\x29\x43\x7d\xa2\x37\x4d\x56\x51\x60\xfa\x85\x2b\x57\x82\xc1\x55\xae\xef\x6f\xf8\x5c\x46\x83\x67\xb6\xf7\xef\xf1\x2e\x68\xf0\xac\x04\x8d\xac\x8c\xec\x5f\xcf\x48\xfd\xc2\xc4\x98\x09\x04\x55\x7b\xfb\x48\x17\x06\xb0\xb1\x17\x53\xd1\x83\xf7\x55\x85\x2c\xa9\x22\x5d\xed\x52\xb6\xc3\xe3\x17\x17\x18\x8b\x3b\x12\x95\xe0\x6b\xd4\x51\x85\x69\x0b\x7c\xd4\x58\x80\xb2\x2c\xa1\x81\x64\xdd\xc4\xe8\x99\x78\xf2\xbd\xca\x61\x60\x6e\xa3\xaf\x3c\x1c\x25\x6b\x0f\x27\x45\xc7\xce\x24\x85\x95\x64\x19\x79\x46\x61\x21\xd9\x66\x31\x88\x82\xc5\x20\x80\xc5\x41\x14\x2c\x0e\x02\x58\x3c\x8e\x82\xc5\xe3\x00\x16\x4f\xa2\x60\xf1\x24\x80\xc5\xd3\x28\x58\x3c\x0d\x60\xf1\x2c\x0a\x16\xcf\x02\x28\xd6\x17\x32\x91\x29\x1f\xd4\x5f\x0f\xf4\xeb\x85\x98\xdd\x0d\xa2\x60\xa5\x9f\x0e\xd4\x53\x09\xcb\x3f\x41\x93\x36\xc8\x51\xcb\x39\x44\x0f\x2b\xb9\x29\x4a\x32\x15\x79\x3d\x92\x52\x60\x62\x30\x05\x18\x51\xc9\x27\x36\x67\x9a\x6e\x53\x78\xaa\x1e\xe6\xbb\x2a\xa2\xc3\x7d\x6b\x20\xfc\xff\xd9\xfb\x17\xee\xb6\x6d\xec\x5d\x1c\xfe\x2a\x12\xdf\x0c\x0f\x31\x81\x55\xc9\xb7\xa4\x74\x59\x1d\xc7\x71\xda\xb4\x71\x9c\xc6\xee\x6d\x74\x74\x32\x94\x04\xc9\x8c\x29\x52\x25\x29\x5b\x8e\xc5\xef\xfe\x2e\x6c\xdc\x49\x50\x96\x93\x74\xfe\xbf\xb3\xd6\xac\x76\xc5\x22\x89\xfb\x65\x63\x03\xd8\xfb\x79\xd8\xb0\xc6\x57\x4a\x0f\x5b\xb8\xee\x02\x54\xd6\x24\xfd\x3d\x0b\x17\x52\x4d\x6d\x07\xc1\xdc\x75\x99\x8a\xba\x08\xb3\x70\x96\x85\x8b\x2b\x3c\x53\x5f\x47\xae\x3b\xc2\x77\x00\xf9\x94\x45\x61\x52\xe0\x95\xca\xfe\xae\xef\x40\xf3\x39\xfe\x1d\xbe\x55\x41\xce\xc2\xc5\x22\x4a\x66\xf8\x54\x85\xbc\xed\x5f\x15\xfe\x2d\xbe\x6e\x50\x04\x37\xe8\x78\x30\xec\xab\x57\x05\xb4\x1d\x1c\xec\xe8\x95\x75\xb0\xc3\xaa\xe6\x60\x47\x56\xc4\xc1\x0e\x2f\x93\xfa\xc5\x4b\x47\xf5\x9c\xf3\x60\xb9\x5e\x7b\xb3\xbe\xb3\x70\xfc\xd3\xc1\x6a\xb8\x5e\x5f\x15\x83\xd5\x10\xad\xd7\xec\xc6\xbf\xe9\x80\xe3\xfc\x51\xa6\x00\x58\x8e\x09\xa6\xa2\x87\x83\xd5\x10\xcb\x7e\x6b\x07\xc1\x98\xbe\xe3\x55\xad\x58\xd0\x2d\x0a\x38\x29\x41\x68\x88\x6f\x5c\x37\x14\xbd\x77\x45\x7f\x1b\x5d\x3d\xa3\x6f\x54\x07\xea\x59\x46\x90\x3c\x6b\x67\x6b\xf2\x11\x4d\x5e\x2f\xd0\x14\x62\x88\x76\xb6\xc6\x99\x22\x34\x64\xab\x7c\x51\xe2\x6b\xb6\xe2\xde\x6c\xbb\xe2\x8a\xb3\x80\x92\xcf\x39\xd8\x69\xa6\x50\xf2\xbb\x0e\xbc\xe2\xd3\xb2\xf6\xa1\x87\xc7\xe1\x42\xc8\x3f\xf5\x89\xbf\x14\x92\xd5\x8c\xc6\x54\xc3\xab\x4a\x6a\x57\x3d\x2a\x40\xcc\x57\xbb\x54\x96\x98\xaf\xf6\xa8\x58\x31\x5f\xed\x53\x09\x63\xbe\x3a\xa0\xc2\xc6\x7c\x75\xa8\xc9\x1d\xe3\x8b\x7c\xad\x49\x22\x6b\x80\x5d\x4c\xe5\x2d\x9c\xa3\x18\xdf\xc5\x5b\x9c\x67\xe7\x49\x7c\x67\xbf\xb2\xe7\xa6\x07\x3d\xae\x74\xf4\x70\xcd\x50\x84\xaf\xa2\xb0\x4a\xdd\x57\xd5\x0e\xfe\xf1\x84\x1d\x47\xe9\x9f\xb9\xda\xc3\x03\x30\x51\xae\x7f\x07\x33\x12\xf1\xf9\x27\xa6\x4b\x19\x01\xb8\x7e\xe5\x94\x98\x0d\x66\x9b\x0d\x0b\x0d\x7e\x2e\xdf\x92\x38\x8e\x16\x79\x94\x3b\xf8\xf6\x2a\x2a\xc8\xc5\x22\x1c\xc3\xa9\xcb\x2d\x9d\xed\x62\xdd\xe5\x2b\x39\x1f\x5b\x62\x5d\xef\x76\xf6\x0e\xe8\x2a\x82\xe5\xd4\xa8\x84\xa0\xeb\x31\xcc\xbc\xd7\x6c\xc2\xf0\x13\x18\xdd\xd0\x05\x5e\xbc\xcb\xa2\x79\x98\xdd\x89\xcf\x6a\xa9\x5c\xb0\x0f\x9d\x79\x18\x25\x3c\xac\xdc\x51\xd5\x43\xcb\x25\x51\x0f\x7f\x49\x56\x45\x63\xfa\xb4\x29\x44\x26\x5a\xf8\x0d\x79\x40\x0c\x99\x11\x8f\x03\x18\x3e\xf5\xb0\xe0\x78\xcf\xcb\xc2\xe7\xfb\x6b\x38\x4c\xac\x9d\x2e\x3a\x32\xc0\x0b\xe0\xf9\xba\xaf\x58\xbe\x54\x16\xf0\x4b\x39\x5e\xe9\x12\x3e\x2f\x10\x1e\x6d\x69\x30\x2e\xce\x61\x22\x6d\xf5\x0c\x8d\xd5\x33\xd6\x6c\x00\x64\x36\x38\x57\x6b\x56\xec\xba\x31\x18\x2a\x44\x49\x4e\x0a\xe9\x5a\xc0\x44\x2d\x73\x27\xe0\x2d\x0a\xa7\x3d\xfc\xb7\x4a\x8b\x1d\xd1\x30\xe0\x52\xde\x8c\x0c\xb8\x54\x3c\x55\x83\xce\x2d\x46\xeb\x0f\x1d\x5f\xd4\x6a\x00\x38\xa3\x39\xa1\x4b\x15\x2f\x91\xfa\x55\xc9\x10\x4e\x21\x78\x59\xf4\xdf\xd5\x60\x43\x69\x73\xae\x9d\x50\xf0\x0d\xf6\x28\x60\x5e\xb2\x93\xfe\xc4\x4f\x8e\x98\xd9\xe5\x68\xbd\x1e\xb1\xab\x8a\x20\xb8\x29\xd6\xeb\x7c\xbd\xf6\x46\x41\x6d\x43\x58\x39\xba\xe6\x0b\xab\x7f\xc3\xd4\x81\x5d\xc7\xe7\x6a\x81\x06\xb1\x19\xc9\x16\xd7\x9c\x5b\x98\x59\x5d\x75\x20\xe1\x29\xc2\x23\x24\x4c\xd8\x17\x47\x06\x22\xc3\x6c\xbd\x9e\x55\x4b\x38\xdb\xba\x84\xbc\x7c\x46\xb9\x54\x17\xf3\x99\x5f\xe8\xd3\xcb\x52\xbc\x2b\x84\x67\x96\xcd\x34\xdb\xa7\x6f\xa3\x18\x44\x4c\x31\x08\xe9\x92\x1e\xf1\xde\x58\xd2\x9f\x6c\xb8\x8e\x5c\x77\x46\x9f\xe6\xcb\xb8\x88\xe8\xd4\x93\xeb\xec\x1c\xe1\x11\x9e\xc1\x5a\x3b\xb3\xde\x08\xd0\x6d\x8f\xef\xf4\x5a\xbd\x56\xb8\x2c\x52\x07\xcf\xa3\x84\x9b\xf1\xf2\xf3\x77\xb6\x9f\x32\x24\xe0\x7e\x89\x65\x4e\x42\x38\xd2\x60\x87\x66\xb0\x43\xb9\x35\x2b\x31\x94\xd3\xdc\xe7\x1c\x1c\x96\x78\x21\x64\x98\xa6\xfa\x57\x4e\xa8\x85\x5e\x4f\x45\x18\x15\x0c\xa3\x02\xe1\xbb\x2d\x05\x03\x97\x05\x91\x21\x0b\x42\xcb\xcc\xb3\x7a\x89\x34\xe9\x72\xdb\x77\x5b\xc2\xba\x2d\x92\xdd\x11\x32\x94\xd7\xbb\x42\x9c\x91\x07\x8d\x87\x6a\x0c\x2c\xcd\xda\x67\xb6\xd5\x3b\xe3\x7b\x45\x30\xb9\x63\xa6\x82\x9a\xa1\x18\xfc\x8c\xc3\x82\xfc\xe9\xed\x1c\x74\xff\x81\x1c\x6b\x13\x57\x8b\x50\x22\xef\xae\x40\xf8\xf6\xab\x8a\x61\xb5\x43\xc9\x95\xb2\x1f\xf7\x9d\x65\xec\xf8\x4c\x04\xf3\xe1\x6d\x11\xc1\x5c\x00\xbe\x63\xa3\x48\x6e\x6b\xda\x41\x30\x71\x5d\xb6\x9b\xc9\x97\xa3\x2b\x12\x4e\x48\x86\xaf\x3e\x43\xc2\x1a\x9b\x06\xf3\xe8\x97\x67\x4a\x65\xa7\xc8\x82\x0a\x4b\xee\xb1\xd3\x00\x0a\xc3\xcf\x75\x97\xa5\xf0\xf9\x69\x18\x52\x96\xf3\xab\x79\xfd\xfc\x2a\x7f\x9c\xac\x58\x2a\x59\xd1\x9e\xd2\xdf\x7c\xf6\xe1\x05\x7d\x90\xb5\x90\x83\xf3\x0a\xe1\x05\x4e\x98\x62\x7e\x6a\x1d\x78\x71\x94\x17\xfa\x0d\x96\xba\xa2\x53\x77\x72\xd6\x23\x1b\xf1\xf9\x7e\xd3\xd9\x94\x26\x2e\x64\xe1\x8c\x18\xdd\xda\xa8\xa5\x43\xf4\xb6\x40\xf8\xfa\xcb\x04\x82\xd8\x5a\x9b\x90\xe6\xe2\xca\x8d\x63\x9a\xab\x81\x6b\xee\xc0\xe3\x48\x6c\xbe\x2b\xd7\x24\x13\x35\x3c\x97\xae\xbb\x04\xc5\x81\x07\xb9\x28\xa2\xf1\xf5\x9d\xb4\x78\x64\x1b\x28\x66\xe9\xc8\x24\x7a\xdd\xcc\x71\x2b\xa1\xd5\xb8\xfd\xad\xde\x58\x68\xa5\x90\x4a\xc4\x86\xe1\xf9\x28\x69\x87\x65\xcb\x31\xc5\x2a\xd9\xb8\x53\x8d\xe9\x56\x72\xee\xba\x09\xaf\x79\x7b\x01\x9c\x68\xac\x81\xda\x13\xfa\xc0\x8f\xc9\xe4\x40\xbd\x61\x63\xf4\x7c\xdb\xcd\x63\xc3\xc1\x29\x5d\xbe\xb8\x03\x8a\xb3\xff\x7c\x01\x6f\x2a\xe3\x7b\xb3\xa2\x8c\xa7\x69\x52\xbc\x0a\xe7\x51\x7c\x67\xee\xb7\xd4\x7b\x08\xf2\x3b\xcb\xa4\x16\x84\xbd\x3f\x23\x93\x68\x39\x87\x80\xe0\xf4\x61\x04\x5b\xac\x2e\xd3\xf7\x64\xee\xf5\xf6\xd1\x67\x6c\x2b\x9a\x77\x28\x5b\x9f\x3c\x5a\x16\xee\x67\xbb\x25\x66\xfd\xa3\x2f\x45\x39\x1f\x4c\xcc\xe4\x9b\x7b\x0a\xf4\xea\xb7\xfe\xb2\x0c\xf5\xe3\xbb\x0b\x29\x58\x4b\xe4\x5d\x17\x08\x9f\x14\x41\xe2\xf5\x0e\x7a\x08\x5f\xb0\x5f\x5d\xed\x7e\xfa\x63\xa1\xfb\x80\xb8\x2e\x31\x81\x7a\xd6\xeb\x09\xff\x05\xd7\xd0\x97\xc5\xe7\xa3\x6b\xd1\xf8\xef\x1e\xb9\x08\x02\xea\x84\xbc\xde\x88\xb4\xc5\x2b\xcd\x8a\x30\xd6\x39\x18\x23\xd7\x8d\x60\x69\x4c\x13\x06\x1f\x40\x26\x40\xe6\x26\x2d\x70\x98\xa1\xe6\x58\xb2\xb7\x31\x17\x55\xb8\x70\xab\xdc\xc9\x27\x08\x68\x28\xa6\x3e\xa7\x78\x11\x13\xfa\xd2\xbc\x2a\x0c\xd7\xeb\x89\xd5\x93\x26\x68\x00\x49\x43\x3e\xc1\xb5\x8b\x3b\x2f\x43\xaa\x91\xe1\xac\x05\x2e\xf3\x32\x1c\x0e\x11\xbe\xac\xb9\x28\x8f\x5d\xb7\x1d\x0a\x87\x21\x80\x40\x1b\x1b\x1c\xf8\x0c\x15\x0d\xec\x57\x68\x32\x05\x1e\xdb\x12\x8a\x5d\xd7\x1b\xaf\xd7\x21\x72\xdd\x98\x5f\x6a\xf2\x80\x61\xdf\xd6\x1c\x69\x8d\x06\x89\x36\xcf\xb4\x44\x7e\xe2\x8f\xfb\x4b\x2e\xe8\x58\xa7\x78\x09\x1e\x23\x7f\x4c\x35\x34\x05\xb1\x57\xfc\x1d\x04\x06\x36\x9a\xbc\x0a\x90\x5c\xd1\x37\xcc\x4c\x45\x11\xbe\x94\x53\xc9\x20\xe8\xca\x00\x36\x4a\x7f\xa6\x4d\xaf\x37\xb7\x44\xd7\xa3\xf3\x6f\xef\x80\x6d\xb1\x3e\xd0\x87\xdd\x03\x1d\x63\xaf\x90\x06\x97\x72\x40\x58\x94\x66\x74\xc4\xf9\x3b\x98\x13\x62\xe0\x7c\xfb\x2d\x15\xbb\xe2\x25\x3b\x81\xaa\xbe\x15\x22\x26\xd0\xb4\x5d\xf1\xad\x48\x17\x81\xb3\xf3\xed\xb7\x66\x0c\x71\x3a\x14\x38\xf9\x38\x4b\xe3\xd8\xc1\xc6\x28\xe5\xf4\xe0\x70\x01\xee\x71\x10\x96\x82\x4e\xbf\xe9\x34\x27\xcc\x89\x71\x87\xe8\x2e\x8d\x12\x3b\xc2\x48\x25\x23\xf3\xf4\x86\x88\x54\x70\xa1\xa1\x06\xea\x92\x09\xc4\x94\xb0\x13\xfc\x2d\x22\xb7\xeb\x35\x13\x3a\x2a\xfc\x6b\x6e\x69\x53\xf4\x49\x27\x27\xc5\x71\x51\x64\xd1\x68\x59\x10\x8f\xd9\xf0\x89\x23\x2e\xa7\xc8\x96\xc4\x41\x3e\xe1\x59\x37\x84\xd3\xf0\xd2\x3e\xe9\x05\x01\x3c\xd2\xd7\x49\xe1\x71\xe4\xb5\x19\x29\x4e\x38\x0d\x18\xac\x79\x1e\x41\x03\x79\x3f\xc5\xce\xe4\x86\xb8\xd7\x45\xeb\x75\x57\xa5\xf8\xa6\x30\x21\xb0\x6a\x46\xdc\x7b\x56\x23\xee\x3d\xdd\x88\x7b\x6f\x08\x6c\x0a\xf5\xb8\xfb\x5a\xa0\x7d\xe9\xbd\x1c\x05\x83\x02\x27\x76\x47\xf1\x0c\x21\x1c\x06\x03\xe7\xf2\xf4\xec\xdd\x9b\xe3\xcb\x53\x07\x3b\x17\x27\xef\x5f\xbf\xbb\xa4\x3f\x2e\xff\x7c\x73\xea\x0c\x8f\x06\x43\x01\x25\xc5\xf1\xe6\x1b\x00\x65\x7a\x60\xd8\x99\xa4\x13\x72\x09\x4e\x8e\x3b\xf4\x45\xa4\x6c\x9b\x10\x7f\x15\xea\xe6\x4e\x1c\x46\xcf\x75\xa1\x0f\x53\x13\x9e\xf3\x65\xa1\x2f\x0e\x3b\x3d\x09\x89\xd6\xc9\xd3\xb9\x39\xfb\x33\xc5\x19\xc5\x2c\xab\xbc\x24\xc8\x30\x58\x52\x1a\xb8\x65\xaf\x0a\x03\x65\x27\x80\x86\x04\xae\x51\x6d\xb9\x01\xfa\xcf\x42\xea\x9a\x30\x07\xde\xa4\xe3\x6b\x10\xc3\x75\x8f\x7e\x18\xa1\x4a\x36\xd1\xc1\x0d\x0c\xc6\xc7\x85\x57\x20\x46\x5d\x02\xd3\xe0\xfb\xa2\x23\x66\x00\x9f\xd5\x86\xdf\x2f\xe9\xb0\xd9\xc6\x54\xaa\xef\x89\xe1\xe2\x5b\x7a\x11\x87\xe4\x0a\x03\x2a\x2c\x8e\x32\x86\x4b\x26\x90\xfb\xc5\x5c\xd7\xd4\x0f\x7c\x4d\xee\xfc\xca\x80\xc4\x24\xf6\xa3\x12\x61\x1e\xbe\x36\x5e\x03\xe5\x9d\xf7\xa9\xf0\x22\xf4\x34\xc4\xce\x62\xe5\x50\xb9\xfc\x91\x3e\x77\xfe\x5a\x92\xec\x8e\x59\x08\xa6\xd9\x71\x1c\x7b\x0e\xdd\x89\xef\x4c\xa3\x15\x99\x38\x08\x57\xc7\x4a\x65\x88\x30\x0e\x63\x8f\x58\x4a\x8b\xb0\xed\xad\x59\x1e\x22\xcb\x23\xc4\x6a\x1c\xd0\x5d\x59\xa6\x5a\x14\xe7\x81\xf3\xe3\xe5\xd9\x1b\x87\x6e\x8a\x61\x30\x32\x13\x74\x21\xc9\x82\x20\x68\x9a\xbe\x31\x1a\x38\x42\xf2\xed\xdc\x39\xc3\x7e\xec\x47\x95\x66\xce\x2b\x02\x92\x35\xb1\x78\x82\xd6\xcd\x4b\x84\xab\xc1\x02\x79\x01\x50\x07\x95\x48\x5c\x77\x63\xa3\xd1\xc1\x9a\x0e\x8a\x61\xdf\xda\x3c\x29\x83\xda\x63\x5f\x98\x54\x7b\x97\xa5\x0b\x92\x15\x77\x5e\xa5\x6f\x61\x26\x64\x76\x84\x40\x21\xc1\x19\xc7\x0c\xd5\xc3\x48\x0c\x1a\xd8\x35\xb9\x3b\x2a\xfa\x09\xcf\x20\x27\x85\x4c\x3d\xc3\x05\xf2\x13\x7b\xce\x19\xf4\x0f\x74\xd0\x0b\xc3\x83\x5c\x4e\x43\xe2\xa1\xfb\x76\xcd\x87\xba\xed\x11\x9d\xde\xa6\x40\x76\xee\x95\x93\x30\x49\xd2\xa2\x45\xdb\xaa\x15\xb6\x60\x33\xd5\x0a\xf3\x56\x28\x9b\xd5\x41\xa5\xc7\xb9\xd2\x05\xac\xd3\x24\x8c\x73\x3a\xcf\xe1\x51\x4e\x74\xfa\xaa\xc2\x6f\xf8\xa1\xe0\xfb\x43\x00\xcb\x70\xc2\xc9\xc4\xe1\x6c\xf6\x36\x85\x55\x4b\x5d\x13\x74\x54\x7e\x80\x65\xa7\x22\x51\x3e\x32\xc3\x0a\xe5\x47\x7b\xc5\x66\x06\x9d\x06\xf0\xe2\x3d\x99\x32\xa1\x28\x1f\xb1\x70\x21\xca\x2c\x60\x41\x83\xa1\xa2\x79\xde\x4a\x5a\x13\x3a\x01\xe4\x0a\xe8\xba\x6c\x71\x04\x11\x3e\x6b\x5c\x42\x91\xeb\x16\xa2\xa0\x74\x38\x15\xa5\x57\xa0\xa3\x37\x85\x57\x40\xb1\x97\x49\x41\x75\x5b\xad\x0a\x18\x64\x30\xa7\x74\x7e\x59\x78\x95\xe6\xb7\x03\x59\xaa\x00\x41\x10\x14\xa5\x44\x91\x83\x26\x4d\xfb\xd5\x44\x06\xe9\xb0\xda\x86\x09\xf2\xab\xa1\xf8\x3c\x66\x01\xfd\x01\x19\x6a\x66\x32\x05\xce\x48\x5e\xa4\x19\x61\x6a\x3f\xab\xec\x45\x34\x8a\xa3\x64\x46\x2b\x94\xfb\x59\x89\x00\x70\x98\x41\xa8\x38\x50\xd5\x4d\xe3\x62\x63\x55\xa5\xa6\xca\xad\x7f\x6b\xe3\x07\x66\x6a\x50\xad\x26\xd5\x7f\x3b\xbc\xa0\xeb\xb5\x27\x7f\x07\xaf\x0a\x98\x8d\xaa\x78\x6c\x3a\xd6\xcb\x27\x06\xcb\xa6\x51\x4b\x9b\xbc\x4a\xc8\xfb\x37\xd5\x26\x9a\x7a\x99\x08\x9e\x2f\x00\x77\x20\xab\x47\xc7\x3d\x63\x12\x8b\x90\x05\x7d\xdf\x0d\x82\x20\x33\xe7\x14\x92\xed\xe2\xba\xf2\xa7\xb7\x71\x5a\x75\x11\x7e\x53\x78\x99\xb6\xdd\x6c\x1a\xcc\x9d\xfa\xd0\x00\x9f\xac\xea\x58\xe3\x65\x4c\xb0\x0e\xff\x91\xca\xa2\x0e\x2a\x65\xde\x01\x7f\x10\xa3\x7c\xa9\x31\xed\x85\x8c\x2a\x64\x17\x47\xf9\x65\xba\x38\xa3\x41\x6c\xdd\xac\x71\x5f\x99\x39\x7d\xdf\xe5\xd0\x76\xbc\x20\xf5\x10\x3b\xbd\x21\x15\x02\x65\x39\x44\x98\x94\x1e\x9b\xbb\xbf\x37\xe0\x93\x09\xc9\x92\xa8\xed\xf9\xb1\xb4\x06\xb7\x21\x95\xf1\x40\xa7\xc9\x34\xcd\xc6\x84\x85\xb3\xed\xe4\x85\x43\x2a\xeb\x3e\x16\xce\x76\xdb\x38\x23\xc5\xcb\x74\xcc\xa9\x10\x4f\x13\x66\x9e\x3a\xa5\x7b\x91\x05\x49\x74\xe0\x29\x84\xaf\xaa\x8e\x9b\x9b\xb0\xa2\xea\x38\x51\x78\x23\xb4\xd3\xe8\x21\xf3\xdc\xbb\xe0\x98\x78\x74\xdf\x3a\xc5\x33\x09\x42\x02\x39\x19\x88\x1a\x16\x87\xa0\x95\x82\x62\xa1\x69\x4d\x87\x08\xb7\x57\xca\x4e\x79\xea\xba\xcd\xe7\x32\x06\x92\xcb\xd8\x43\x9c\x57\x52\xa0\x3c\x36\xfa\x56\x52\x35\x57\xec\x47\x3f\x16\xde\x48\xf3\xfa\x5b\xaf\xdb\x23\xe5\x6e\x28\x7f\x8a\xd1\x9f\x7b\xa4\x92\xc9\x7a\xad\xe2\x77\xae\xc2\x5c\x5b\x5a\xa4\x1b\x2e\xd2\x53\x32\x77\x70\xca\x55\x77\xa7\x87\xf0\xa8\x6a\x95\x2d\x76\x9f\x5a\xe1\x09\xcd\xe5\x15\xfb\xec\xba\xed\xd0\x75\x27\xf0\x43\x62\x99\xf4\x47\xaa\xf9\xda\x5b\xd4\xc0\x75\x6b\xd9\xfa\x0b\xcd\x87\xae\xc4\x49\xa0\x8b\x43\x99\xe5\xb7\x54\x98\x52\x1d\xea\x24\x9d\xc0\x01\x9b\x91\x6e\x10\x04\x23\x1b\xe0\x19\xe0\x91\x4a\x5a\xdb\xfe\xbc\x96\xf7\x55\xad\x11\xca\x23\x62\x41\xaf\x9e\x32\xfb\xfe\x02\x24\x9c\x2d\x80\xc4\xdd\x4e\xb4\x55\x3a\x27\xc5\xeb\xa4\x20\xd9\x4d\x18\x9b\x58\xf7\x70\x5a\x74\xd0\x6d\xc4\xc2\x93\xb1\x52\x9a\x1b\x5b\x83\x36\x97\xc8\x1a\xc6\x2c\x14\xce\xd7\x6b\x4f\x43\x08\xba\x31\xeb\x5e\x7b\x61\xc1\x0d\x2a\xf9\xa1\x1a\xce\xf1\x04\x4f\x2d\x58\x33\x69\xe7\x55\x16\xce\x60\xf7\x00\xeb\xbf\xfd\xbe\xf2\x5e\xba\x9a\x75\xe1\x1c\xfd\x0a\xd0\xf1\xc3\x9d\x82\xe4\x85\xe3\x3b\x39\x49\x8a\x28\x21\x31\x73\x38\x2f\x21\x17\xfd\xe0\xac\x60\x07\x67\x77\x65\xd3\x35\x76\x35\xfd\xb9\x3d\xfd\xd3\x64\xe2\x00\x1a\xe0\x93\x22\xe0\xc7\xf3\xfc\xac\x78\xa7\xa7\xdd\x1d\xb1\x0d\x59\x1d\x61\x86\x9d\x2e\x0b\x9c\x99\xea\xd9\x72\x36\x1b\x85\x5e\x17\xb7\xf8\xff\x9d\x03\xb4\xa5\x73\x5a\x89\xa3\xe4\x46\x58\x14\x3f\x60\xa5\x8a\x7f\xdd\xee\x28\x58\xa6\x68\x85\x32\x04\x01\x1f\xd7\x6f\x75\x64\x2c\x07\x3b\x34\x8c\x76\x2b\x13\xd6\x3c\x02\x2d\x37\xd1\x86\xbe\xeb\xb7\xbb\xe2\xce\x24\xc6\xf7\x0c\xb0\xb1\x02\xfc\xfa\xa4\xe0\xb7\x36\xfd\x27\x45\x47\x6b\x84\x12\xc7\x6c\x73\x04\xd0\xd2\x9c\x1d\x1e\xa6\xd8\x8f\xec\xc8\xf3\x45\x81\x7f\xdb\xa6\x21\x78\x7e\x27\xb0\x2f\x41\x38\x12\x2f\x2e\xe0\x85\xba\x00\xe0\xca\x00\x80\xc6\x56\x0b\x09\xfb\x20\x32\x27\x7e\x52\x22\x1c\x06\x51\xe7\x45\x38\xbe\x9e\x64\xe9\x42\x39\x51\x19\x57\x77\xbf\x16\x70\x67\xa7\xc2\x31\x9b\x9b\x71\x10\xa9\x35\x7f\x42\x1f\xe2\x34\x27\xc7\xd3\x82\x64\x97\xd2\x32\xda\x72\xab\x1c\x69\xaa\xd5\x55\x10\xd5\x55\x85\xfa\x6d\x9d\x0c\x24\x4b\x0a\x98\x20\x75\x6c\x92\xc8\xaa\x53\xd4\xc1\x49\x54\xb8\x7c\x1c\x2e\x88\xc0\x0d\xa9\xbb\x72\x45\x95\x8b\x86\xba\x3f\x57\x64\x55\x4f\xea\x4e\x5d\x51\xfd\x04\xc9\xe2\xde\x15\x51\xa5\x52\x56\x13\xbf\x53\x01\x2e\x5d\xf7\x12\xbf\x0f\xa2\xce\x35\x21\x8b\x33\x86\x4c\x8c\xdf\xaa\xef\xef\x5d\xf7\x3d\xfe\x10\x44\x9d\x79\x98\x84\x33\x92\xe1\x33\xd5\x87\x1f\xfa\x3f\x16\xfe\x07\x7c\x1c\x44\x9d\x34\x31\xdb\xf0\x35\xbc\x03\x02\x3d\xfc\x09\x7e\x9b\x4d\xf2\x06\xde\xc9\x9b\x94\x97\xf4\x91\x4e\xb6\x57\xe6\x64\x8b\xf0\xc0\xa9\x0d\x23\x07\x3b\xc6\x90\xa9\x3a\x95\xd5\x87\x0b\x5c\xb5\xf2\xc1\xa1\x6e\x56\x8f\x35\xf7\x34\xdb\x40\x50\xaf\xf5\x6e\xd7\xde\xea\x35\xd2\x6c\x10\xa0\x4b\x35\xc0\x13\xad\x03\xb5\x6b\x5d\xd9\x5d\x0e\x76\xf4\xce\x71\xb0\xa3\x75\x85\x83\x1d\xde\xf0\x0c\xf6\xa4\x52\x3e\xde\xc4\xf0\xab\x5a\x1c\xd5\xbc\x4a\x4a\xe1\x17\x86\xd3\x78\x17\xe1\xdf\x83\x17\x83\xee\x10\x3f\x09\x5e\x0c\x7a\x43\xac\x01\x8f\xdc\x97\x12\x76\x44\xe9\xa7\xbf\x55\x5f\xfc\x45\x55\xce\xdf\x70\x81\xf0\xcf\x16\xe6\x87\x76\x5b\xe9\xef\x54\x35\x11\xbf\x39\x32\xf6\x55\x98\x9f\xdf\x26\xea\x2c\x29\x82\x43\x95\x08\xe1\x5f\x2c\x1c\xbb\x1f\x0b\xef\x47\x05\x87\x8a\x7f\xb0\x04\x51\xe8\x01\x62\x5f\x13\xfc\x26\x41\x28\xf5\x8f\x7c\xdb\x15\xfc\x58\xff\x5a\xe2\x3f\xf5\x94\xcf\x58\x68\xef\x07\x0f\xe1\xfb\x5a\xd7\xf9\x1f\x4b\x84\x7f\x53\x4a\x25\x7c\xb8\x4c\x17\x41\xb7\xc4\x7f\x98\x38\x0a\x42\xd3\xfd\xf2\x0b\xbd\x05\x5a\xaf\x7f\xf1\x10\x1c\x01\x1f\x9d\x51\x8d\x0b\x4a\x47\xb4\x92\xb8\xee\x9f\x1e\x6c\x89\x7f\xda\x00\x44\x28\x2e\xf9\x3a\x6a\x9b\x47\x13\x82\xcd\xc4\xd9\x10\xe1\x7f\xd5\x80\x20\x7e\x54\xb0\x1f\x18\x58\x72\x5c\xf7\x8d\x87\xf0\x4b\xd7\xfd\xc9\x43\xfd\x3f\x3d\xe4\xbf\x2e\x3c\xd5\xe2\x8c\xc3\x04\xa0\x4f\x9a\x4b\x71\xc6\x15\x34\x3d\x6b\xba\x69\xdf\x1e\x31\x97\x10\x8f\xb3\x34\x91\x0d\x48\x2e\x2f\xfb\x7f\x78\xc8\xff\xd9\x75\xa7\xeb\x35\xe1\xe0\x2d\x2f\x31\x21\xf8\x67\x3c\xc5\x7f\xd0\xfd\xce\x5b\xd7\x6d\xbf\x74\x5d\xaf\xfd\xf3\x7a\xfd\x3b\x42\xda\x3d\x1f\x53\xff\x6d\x7d\x57\xb3\xf5\xe2\x2a\x11\xd7\x96\x48\x87\xfd\x60\xe3\x71\xb3\xa2\x54\xf2\x23\x21\xff\x5e\x11\xc2\x28\xa3\xee\xb2\xf4\x92\xf5\x5a\x68\x61\x6f\x8b\x4e\x58\x02\x14\xcc\xbd\x64\xba\x56\x30\xdb\x7c\x76\x09\x55\xcf\x75\xbd\x44\x81\x47\xd5\x3e\xaf\xd7\xce\x4e\xcf\x41\xf8\x67\x16\x2e\x4d\x00\x35\x24\x78\x6f\x36\xdf\x13\xaf\xdd\xa3\x6d\x36\x56\xa0\xf6\x34\x18\x2d\x83\xc4\xfa\xb6\xc4\xe9\x22\x3c\x75\x5d\xde\xde\xe3\x0a\x20\xbe\xc5\xd8\xf2\x1d\xd7\x60\xff\xa5\x1d\x9c\x2d\xb0\x21\x58\xfd\x6b\x0b\x98\x75\x5d\xc5\xa2\xa9\xfc\xa5\xc1\x47\xe8\x7d\xe7\x30\x51\xa9\x81\x84\xfc\x44\x77\x52\xde\x27\xd7\xfd\xe4\x11\x84\x6f\x01\xcf\x3f\x2f\xd8\xfa\x12\xce\x42\x56\x21\xfc\xda\x75\x5f\x03\xbb\x80\x21\x69\x01\x65\x3f\x4b\x63\xe2\x3b\x8b\x8c\x50\x15\x3a\x64\x86\x79\xf8\x55\x83\x3a\x57\x10\xa6\xce\xd1\xe1\xf6\x7b\xbf\x20\x1d\xd1\xf3\x25\x7e\x25\xf5\x39\xfc\x8e\xc1\xe8\x56\xeb\x1a\x9b\xf5\xa4\x82\xdd\x7f\x29\xc1\x33\xcc\xd3\xd7\x66\xa0\x14\xef\xd8\x75\x8f\x69\x5d\xdb\x23\xd7\x15\xf5\x1a\x19\xab\x0b\x9c\xf5\xe5\x96\x4e\xfa\xbd\x90\xa2\x50\x5f\x18\xfd\x3b\x5c\x5d\x58\xfd\x39\xb6\x2c\x82\xfe\x09\x66\x47\x2a\xfe\x2f\x58\x1e\xa8\xf8\x3f\x61\x56\x95\xb2\xba\xa9\x19\xe3\x84\x20\xc4\x44\xc9\x5f\x60\x61\x72\x88\xf0\xcf\x80\x2b\xf3\xcb\x16\xec\x22\x47\x4d\xd4\x22\x6c\x2b\x8a\x23\xaa\xee\xb7\x93\x8e\x22\xe0\xe8\x17\x8c\x67\xc0\x2f\x38\xff\x80\x22\x18\x61\xcf\x8c\x19\x97\xa1\x9d\x52\x7d\xbc\x1f\xf5\xbd\x34\x00\xd2\x09\xd8\x0f\x99\xc1\x18\x43\x45\x94\xcc\x1c\xe4\xa7\xfc\x89\x4c\x1c\x3f\x0d\x8a\xce\x92\x91\x7a\x9c\xc3\x8c\x58\xaf\xf9\xda\x74\xce\x26\x57\xdf\xe1\x9f\x69\x68\x2d\x75\xce\x72\x92\x33\x7e\xde\xb4\xc4\x8c\xb2\x5d\x88\x57\x56\xae\x3a\x8f\xc9\xf6\xec\x20\xf2\x8c\x3c\x4a\xe0\xe0\x47\x14\x02\x4e\x1a\x58\xb6\x7d\x91\xbd\x28\x57\xc9\xb6\x1f\x5f\x46\x96\xb2\x5c\x4c\xc2\x82\xb0\x76\xf3\x04\x63\x8a\xde\x98\x26\x2d\xca\xcb\x68\xf2\x2b\xc4\xb0\x1c\x1c\x82\xdc\x8e\xa6\x1e\x69\x07\x1a\xd1\x85\x71\xa5\xc2\x38\x28\x58\x3d\x8e\x34\x86\x8f\x28\xe9\xab\x3e\x63\xdb\x41\xd9\x6b\xec\xd1\x2b\x8c\x5e\xdd\x18\x7a\xbd\x86\xd0\xab\xa8\x80\xc0\xa5\xa5\xa2\x3d\x5c\x6c\xcf\xf7\x32\x0e\x93\x31\x89\xdf\x6a\x1d\xee\x41\xe4\x99\xc4\xe9\xcd\x6b\x00\x5b\x8c\x93\x59\x6b\x07\x41\xbb\x70\xa4\xc8\x88\x83\x24\xc8\x30\x73\x1d\xc8\x5c\xd7\x61\x64\x7e\xea\xac\x2f\x73\x5d\x8f\x04\x9c\x72\xa3\x08\x04\x15\x47\xa2\x21\x95\xf3\x9e\xea\x8b\x1f\x7e\x81\xf0\x3d\x20\xe0\x12\xce\xd8\x52\x08\x46\x97\x04\x98\x57\xf4\x56\xa8\x32\xaf\x88\xd5\x0c\x68\x1f\x83\x76\x0f\xf1\xb2\x05\x85\xb8\x9b\xb1\xb4\x03\x56\x3d\x01\x8c\x20\xac\xc2\x24\x9b\xa6\xd9\x1c\xa6\x94\x47\x90\x6f\xbc\x5c\x45\x85\x87\xc4\x3b\x68\x19\x63\x4a\xd2\xbe\x64\xe3\x5b\xf0\x05\xea\x83\xa6\xca\xe0\x22\xe7\x84\x9a\x32\x8c\xc8\x47\x2f\x82\x65\xac\x82\x60\x4a\xf4\xee\x61\x8d\xab\x5d\x68\x90\x15\xaf\x0d\x7f\xd0\xc4\x95\x4f\x04\xe3\x0a\x8b\x9b\xa4\x13\xaa\xa0\xf7\x07\xd9\xd0\x1f\x4c\x3a\xa1\xa1\x40\x02\xa7\x12\xce\x86\x38\x0a\x52\xaa\xf7\x87\x41\x4a\xf5\xfe\x98\x25\xa0\x8d\x21\x0f\xe1\x3c\xc8\xfa\xb1\xe8\xcb\x98\x95\xe9\xa8\x4d\xa8\xb4\x5c\xaf\x7f\xe6\xe5\xc9\xc3\x29\xb9\xa8\x35\x80\x98\x00\x25\x36\x99\x2c\x0d\xe5\x81\x4c\xbc\x08\x0e\x2e\x74\x7a\x1d\xfe\xcd\x03\xb6\x9e\x87\x72\xa0\x1d\xbd\x31\x8b\x28\x99\xf1\x94\x3a\x69\xa2\xf6\x83\xa7\xc9\xc4\xcb\x2b\xf1\xbe\xb0\x22\x38\x44\x9c\xe7\x94\xb3\xee\x68\x23\xcc\x4e\xbd\x83\x0b\xa3\xc7\x81\xca\xc6\xd6\x0d\x99\xad\x73\xd9\xfc\xf0\xad\xdd\x7b\x54\xb8\x6e\xfb\x67\x31\x4f\x74\x9d\xcb\xcb\x36\xb7\x29\x17\x52\x66\x65\x4d\x1e\x23\xda\xa2\x19\xc2\xa4\xd6\x9e\x09\xab\x82\x19\xb3\x39\x9f\x5a\x9b\xd6\xe8\x92\x32\xad\x41\xfd\x07\x4a\xbd\x6d\x6a\x54\xc8\xd6\xe4\x86\xde\x3d\x42\xc8\xd0\xec\xf4\x45\x95\xca\xfc\xea\x3b\x9e\x94\xc7\xdb\xb4\xb6\x06\x43\x76\x7a\x99\x2b\x32\x8e\xf7\x7f\x4e\x0a\x43\x8a\x09\x9a\x28\x29\x56\x08\x5f\x1e\x2a\x01\x9b\x05\x49\xbb\x7b\xa4\x5f\xce\x25\xd6\x48\x19\x98\x85\x78\x09\xd5\xa3\x0a\x8b\x02\x41\xbc\x0c\x68\x8e\x1a\x6a\x6d\x34\x1a\x23\x1f\xab\x86\xa4\x65\xae\x8c\x92\x6a\x0b\xd8\xeb\x7f\xa4\x2d\xd2\xe6\xb0\xaf\xbf\x12\x8a\xad\x7d\x2a\xe0\x2c\x90\x8c\xbf\x6d\x2d\x72\x38\x99\x9c\x26\x13\x71\x1f\x40\xf5\x04\xaa\x04\x66\x70\x29\xd5\x18\x0c\xf1\x1b\x56\x9b\xac\xad\x55\x7e\xe8\x0f\x92\x7a\x93\x54\xe4\xee\x51\x63\x5e\x4c\xa0\xb0\xd1\x48\x5c\x57\x03\xe1\xaf\xa5\x89\x09\x2a\x49\x9c\x93\xd6\xc6\x40\xdd\x87\xa9\xc0\x0c\x7d\x28\x9a\x7a\xa6\xe6\x47\xea\x3b\x62\x9d\x41\x2c\xd1\x99\xe4\xb2\xc0\xa3\x7a\x31\x36\xf5\x59\x5c\xd1\x77\xb1\xd0\xaf\x31\x57\xb8\xe9\x5f\xf6\x5e\xb0\x42\x15\x95\x66\x01\x39\x2e\x12\x53\x32\x5e\x3d\x90\x09\xfb\xcd\x92\x91\x22\x4b\xfe\x86\xef\xbc\xd7\xb0\x4e\x6a\x56\x98\x9e\x47\x11\xfd\x47\x2f\xbc\x83\x1d\xa3\xf0\x8e\x22\xa4\x13\x14\x74\x9c\x75\xce\xe1\x85\xa7\x21\x8c\xc2\xb3\x53\x3a\x1e\x56\x15\x5e\x3d\xb0\x93\x3a\x91\xbc\x2c\xbc\xfc\x0d\xdf\x79\xe1\x9d\xa1\x22\x3e\xdc\x8a\xcd\x0c\xd4\x73\x6c\x39\x69\x4a\xfa\xcc\xb6\x91\x91\xa2\xe9\x3b\x2f\xe0\x37\x13\x47\x76\x69\x12\x83\xe5\x72\x66\x27\x3f\x93\x7b\xaf\x1f\x0a\x0f\xdd\x97\xbf\x14\x42\x53\xb9\xbc\x5b\x90\xe0\x07\x82\x7f\xa9\xb0\xa3\xfd\x52\x65\x47\x03\xea\x4b\xac\xb7\x39\x7d\x36\x1a\x9d\xbe\xe0\xfa\x48\xbb\xc7\x75\x4a\xc1\x77\xd9\xee\x62\xde\x8c\xfe\x0f\x05\x56\xcd\xab\x3d\x91\x09\x7f\xa0\xe1\xe5\x2f\x15\x86\x31\x11\xfe\x50\x40\xe1\x7e\x7d\x7b\x76\xfe\xeb\xdb\xcb\xd3\x97\x81\x36\x0f\xe8\x87\xd3\x3f\x5e\xc3\x5b\xb1\x21\xa3\xaf\xde\x5e\x9e\xbe\x7f\xfd\xf6\x07\x6d\x57\x20\x5f\x43\x50\xd1\xbb\x3c\x3a\x0b\xca\x7b\x17\xe6\xd2\x9f\x45\xf0\x4b\x81\xff\x80\xed\xed\x2e\xc2\x3f\x81\x25\xf7\xa1\xd6\xac\xff\x2a\xbc\x2a\x9d\x22\xbf\xad\x59\xaf\x7f\x2a\x3a\x21\x18\xc2\x91\xc4\x7a\xbe\xa8\x0e\x26\x35\x62\xed\xa2\x72\x13\x26\xa6\x1c\xdd\x2e\xc3\x19\x84\x64\x21\x0a\x82\x20\xeb\xdf\x97\xbe\xd8\x07\x2b\xb0\x9a\x54\x03\xab\x11\xdc\x0b\xeb\xb5\xd8\x3b\xe8\x23\xcc\x4f\x06\x70\x2a\x4b\x86\xeb\x75\x17\x4f\x48\x1c\xde\x99\xb1\xe9\x1b\x66\xcd\x97\x24\xc1\xbd\x68\x46\x03\xc1\x9f\xb7\xa2\xfe\xae\xc4\x99\x08\xed\xff\x51\x74\x46\x1d\x11\xf1\x62\x9c\x11\x22\xa8\x59\xe9\x87\x98\xb1\x2d\xb0\xf7\x25\x4e\x93\xc7\xbb\x4b\x4a\x87\xac\x2c\x1a\x17\x67\xe9\x84\xd0\xe1\x1f\x16\x56\xdb\x92\x28\x01\x77\x34\x21\xac\xc6\xea\x37\x70\xe2\x11\x5d\x78\x4d\x24\x5f\x1e\xb3\x2a\x11\x82\x6a\x21\x1f\x68\xa8\x2b\xd9\x2f\xf3\x80\x74\xd4\xaa\xda\x00\x51\xf8\x67\xc1\x01\x0a\x45\xbf\xce\x74\x58\xc2\x2c\xf1\x47\x55\x2c\xc2\x8a\xef\x65\x43\x6d\x85\x6c\xac\x49\x32\x21\xbc\x0c\xa9\xa6\x4b\x32\x4d\xba\x71\x17\x4d\x5a\x1b\x07\x3b\x96\xba\x68\x52\x14\x40\x11\xe9\xe8\x07\xe2\xa5\x65\x92\x03\x93\xf9\x87\x5c\x96\xcb\x75\xdb\x21\x3e\xad\x5e\x4c\x5c\x07\xc7\xc4\x03\x37\x1b\x5c\x20\x7c\x4e\x9f\x6e\xfb\xa7\xc2\x5c\xfd\x1a\xe1\x13\xdb\x54\x51\x16\x16\x38\x01\x65\x80\x08\x1b\xfa\xdb\xfe\xe0\x54\x9e\x6c\x17\x43\x1f\xcc\xdd\x25\x03\xb4\xf7\x17\xcc\xc5\x0c\xef\xa2\xca\x0a\xaf\x5d\x78\x12\x2f\x42\x3e\x61\x2b\x7b\x59\xe2\x8b\xe0\xc4\x5b\x22\xfc\x31\x38\xa9\x8e\x3e\x92\x78\x8a\x76\x36\xf1\xf8\x21\xe2\x95\x64\x50\x9b\x95\xf8\x9e\xce\x25\xbe\x3d\x71\x4a\xcd\x65\x83\x5d\xa4\xcb\x16\x0d\x56\x56\x04\x2a\x3e\x83\x18\x6d\xaa\x70\xd3\x78\x4c\xa4\xdc\x75\x73\xc1\x00\x84\x2f\x83\x13\x6f\x8c\xf0\xbb\xe0\xc4\x5b\x20\xfc\xde\xac\x90\xd4\x4d\x37\x57\x84\x8e\x94\x2f\xa8\x47\xf1\x39\xf5\x28\x10\x9e\xb8\xee\x84\x9b\x0f\xbe\x0d\x4e\xbc\x69\xa3\x77\x65\x05\x2d\x4f\xac\x42\x5d\x1c\x25\x7e\x8c\xf9\xa2\xec\x6b\x43\x4c\x2c\x46\x1f\xb5\xd5\xe7\x52\x5f\x97\x2e\xc4\x4a\xf4\x5e\xad\x3d\x6f\xb5\x35\xe9\x9d\xde\x4c\x77\xa8\x6a\xaf\xad\xca\x69\xba\x4e\x19\xc5\xb4\x1c\x3f\x2b\xb6\x19\xfd\x9e\x81\x2f\x66\xed\x20\x20\xeb\x75\x2c\xb6\x97\x0a\x53\x26\x49\x06\x64\x88\xaf\xb0\xb8\xba\x63\xa7\xd4\x60\xb7\x70\x5e\x82\x5d\x28\x3b\xa0\x0d\x3f\x43\xae\x36\xbb\xa1\x2b\xeb\x0c\x9b\x5d\x1e\x5c\x18\x53\x59\x5a\x5f\x84\x40\xa6\xda\x64\xa4\x01\xb2\x95\x26\x80\xae\xf5\x78\x0f\xf4\x9a\xfd\x07\x76\xea\x65\x68\x90\x6c\xcd\x0e\xbc\x53\xb3\xef\xa2\xc4\x1f\xcb\x21\xb0\x2c\xf1\xa2\xd1\xa8\xe7\x21\x1f\xf3\x9c\x81\x50\xf0\x32\x23\xdc\x64\x7e\xc2\xdd\xca\xe3\xc4\xe6\x56\xbe\xc1\x00\xa8\x82\xb4\xf8\x18\xa4\xea\xff\x91\xb6\x43\x1a\xf2\xb2\xb8\x94\x2f\x91\x17\x26\x3a\xee\xb2\x92\x6c\x36\x8a\xc7\xed\xe8\x21\x7b\x87\x87\x2a\xc5\x4c\xf3\x1f\xac\xbb\x6c\xe1\x54\xf3\x1f\xcc\xe8\x22\xd3\x3d\x8a\xbe\xcb\x8e\xa2\xa7\x4f\x51\x3a\x88\x74\xff\xc1\x68\x78\xc4\x5c\x78\xe0\x34\x20\x36\xad\x16\x99\x1f\x61\x88\x53\x54\x1e\x19\x6c\x72\x05\xc2\x06\xd7\x5b\x8c\x13\x69\x11\x9c\x75\x20\x68\x50\x35\xc6\x53\x71\x4b\x9c\x29\xf7\xa7\xb1\x31\xd9\x2d\x02\x00\xb8\x2e\x1b\xe8\x20\xe9\xc6\x93\xb1\x58\x5e\x0a\x64\x0c\x94\x04\x95\x37\xba\xbd\x73\x93\xa7\x4d\x81\x8e\xa8\x2c\x9a\x29\x77\x12\xe0\xa0\xf6\x24\x76\xb8\x44\xde\x70\xd0\x7a\x6d\x0b\xa8\x05\x00\xc5\x34\x0a\xba\x38\xa4\xed\x0e\xa7\x05\xcc\xf9\x9c\x1f\xf8\x53\x6d\x04\x0e\x9d\x85\xc6\xcb\x6a\x1a\x07\x09\x98\x69\x17\x9e\xe3\x39\x68\xd0\x1b\x8a\x27\xe4\xa0\x41\x57\x3e\x61\x07\x1d\x45\x81\x74\xfd\x8b\x07\xfb\xe0\xd3\x87\x43\xfd\xdd\x01\xbc\xe3\x5d\xc2\xd0\xbc\xc0\x01\x4c\xc1\x86\xfc\xe1\x49\x3f\x26\xde\x28\xca\x2d\x0c\x3b\x8b\x15\x6a\xe9\x41\x25\x91\x58\xb4\x93\x01\x55\x25\x04\x71\x90\x80\xfa\xaa\x25\xbe\x23\x53\x67\xe1\x9f\x66\xcc\x41\x74\x27\x92\x31\x97\x8b\x6a\xb4\x3f\xed\x65\x62\xfe\x66\x95\x42\xfd\xa9\x15\x2a\xdc\xc9\x3a\x45\xba\x90\x29\xeb\xd8\x28\x5a\x39\x8a\x74\xf1\x34\xe3\x2e\xa9\x3b\x21\x0f\x5d\xc2\x50\x3b\x82\x7b\x9a\xba\x3a\x41\x3b\x34\x48\x70\xa1\x6b\x0b\xec\x1d\xeb\xe5\xe5\xe7\x6d\x28\x26\x9f\xb5\xa1\xc8\x08\xc3\x29\x0e\xd5\xb2\x14\xf5\x1d\x30\x4d\xf5\xb7\xd8\x48\x4c\xcc\x8d\x84\xda\x3d\x68\x7b\x07\x32\x81\xad\x83\xda\x48\xcc\xe5\x46\xe2\x46\xdb\x1e\x68\xa8\xe5\x37\xfd\x65\xe2\x33\x4a\x2e\xdb\x12\x7a\xa7\x42\xce\xe8\x36\x63\x86\x57\x0f\x6c\x24\x78\x2d\xff\x86\xad\x83\x3a\x6a\xb1\x2f\xb5\xf8\x96\x6d\x1d\x2c\xdb\x83\x0d\x86\xf6\xa7\x0f\x19\xda\x9f\xab\xcd\x05\xec\x24\x8e\x89\x77\x4e\xb5\xc9\x8b\xcd\x7b\x0a\x74\x4f\x5c\xd7\x93\xad\x57\xf4\x89\x27\xb3\xa2\x3b\x03\x6d\x83\x01\x97\xef\x1f\x83\x8b\xea\x38\x1a\x27\x5e\x88\x09\xc2\xb0\x45\xa8\x6a\xe1\xb5\xd0\x6a\x0b\x21\xf4\x88\x11\x67\x04\x9f\x3f\x62\x0b\x71\x6b\xd5\xa2\xeb\x62\x14\x57\xcc\x1c\x12\x7c\xcf\x28\x02\xfd\xdb\x26\xde\xc0\xf3\x65\x01\x16\x42\x75\xd5\xdd\x9e\xe9\xd7\xcc\xac\x2a\x13\x38\x9a\x08\xa9\x09\x06\xfe\x81\x6d\x15\x78\x63\xbf\x0b\x2e\xe8\x96\xe7\x7d\x70\xe1\x5d\xd1\xad\xc3\x45\xd3\x96\x67\x43\xc3\x3f\xb4\xe5\xf9\xdc\x76\x2f\x36\x37\x45\x7e\x15\x66\x8b\xaf\xd4\xea\xdb\x66\x25\x86\xed\xd4\x75\x85\xb7\xd6\x87\x6a\x9b\x35\xb6\x83\xe3\xd8\x8a\xea\x38\x78\xe1\xba\x0b\x9e\xda\xd9\x06\x13\xb3\x53\x65\x16\x07\xe5\x50\x73\x8e\x93\x87\x3f\xe4\x1d\x13\x4d\xbd\x76\xec\xba\x4c\x2e\x33\xde\x09\xbe\x48\xd2\x07\x71\x86\x9e\x27\x5b\xe7\x2a\x73\xe4\x6b\x62\xdd\x6b\x22\x23\x79\xf4\x89\x8e\x46\x03\x7d\x83\x30\x45\xcc\x43\x98\x47\xb4\x3a\x37\xa8\xb8\xcc\x2d\x21\xc4\xf1\x06\xc3\xb8\x78\xbd\x3e\x13\x18\x1d\x67\x16\xc7\x85\x3b\xb3\xcf\xc5\x4e\xf7\xd4\xba\xc1\x7d\xa7\x6f\x70\x2f\xc5\x06\xf7\xad\xda\xe0\x7e\xd0\x36\xb8\xef\x71\x65\x17\x2d\xe7\x4a\x89\x57\x9f\xb7\xdf\xa5\x1b\x99\x13\x6c\xd9\xf5\x3e\x66\xaf\x3b\xaf\xec\x72\xf5\xfd\xed\x74\xcb\x65\x7e\x03\x86\x95\xd5\x18\x9e\x21\xfe\x33\x0c\xab\xfc\xaf\x65\x98\x91\x1a\x9b\xc9\x12\x3c\xa6\xc9\x8d\xd8\xe0\xca\xd8\xcb\x7e\xcf\x67\xc0\x55\x02\xcb\xd9\x24\xe9\x94\x91\x18\x4f\xe7\xd6\x18\x55\xea\x2c\x8e\x95\xc8\xc1\x5a\x52\x0a\x81\xb9\x79\x43\x1b\x3f\x12\x91\x8a\xd3\x0b\xc1\x75\xd3\xa2\x9f\x74\xc4\xb3\x9f\x0c\xb4\x9c\x85\xf6\x37\x41\x43\xdc\x1e\xbb\x6e\xc2\xb8\xd5\xc9\x44\xc3\x48\x83\xbe\x5a\x24\xcd\xd0\x53\x4c\x40\x2b\xeb\x49\x42\x65\xd5\x24\xbd\xcd\x6d\xbe\xeb\x38\x41\xf7\x85\xad\x08\x09\x1a\x06\x00\x5c\x05\x71\x7d\x52\x02\xe2\x95\x31\x20\x19\xb6\x55\x23\x22\xbb\xfa\xd2\x59\x84\x0b\xaa\xe0\x6d\xc0\x89\xc5\x0f\x23\xcb\xa7\xab\x1d\x56\x11\x07\x8c\x12\xa1\x5d\x04\x28\x3e\xe7\x66\x82\xaa\x2e\x48\x47\x7f\x59\x62\xd9\xd6\x3c\xf0\x96\xd8\xf9\x65\x09\x0b\xa2\xb6\x65\x7e\x47\xab\x41\xf7\xcb\xd3\x04\xe1\xab\x24\xb8\x87\xbd\x3c\x97\x97\x7c\xbf\xcf\x39\x10\x00\x05\x91\xf9\x61\xf1\xfd\x3f\xdd\x41\x30\x1b\xb2\xf9\xe7\xa9\xe1\x37\xdb\xcd\xcf\x30\x19\x5f\xa5\x59\x05\xe2\x1d\x0a\xc5\xf1\xdd\x4d\x9f\x94\x58\xd7\xdc\x73\x6d\x7e\x8f\x8d\xf9\xbd\x69\x86\x1e\xf2\x29\x0a\x76\xd6\x02\x88\xd6\x9b\xea\x13\xf5\xbe\xf4\xa7\xa8\x92\x73\x65\xba\x4e\x35\x7f\x08\x09\x05\x3b\xe7\x7c\xb6\x69\xce\x54\x7a\x38\x17\xb3\x11\xec\x42\xd7\xb0\x74\x0d\x25\xfe\xbe\xf4\x19\xb5\xee\x45\x1c\x4d\x38\x7b\xec\x6d\x83\xf6\x6f\x80\xbf\x4f\x12\x00\x7f\xb7\x9e\xc0\x9d\xab\x80\xd7\xfd\x79\xe2\x33\xae\x5d\x21\xa1\x2e\xd4\xd7\x93\xbe\x53\x90\xf9\x22\xcd\xc2\xec\xce\xf1\x4f\xf0\x47\x0b\x98\x3c\x74\xd7\x43\x7e\x1f\x36\x29\xa6\x0b\x2c\xd5\xf4\xa6\xe7\x04\x3b\xc0\x53\x8d\xe3\x60\x47\x35\x44\xf3\xfd\x84\xed\xc0\x4f\xc9\x44\x7c\xc9\x76\x1e\xef\x0c\x32\xca\x46\x46\xbe\x77\x9a\x63\xa6\xc6\xcb\xf7\xde\x6a\x99\xe9\x64\x45\xcc\x8c\x97\xe5\xde\xca\x75\xeb\x3b\x0f\xf0\x95\x1f\xf0\xb9\xc6\x27\xe0\x50\xf7\x99\xf7\x0a\xd4\xbf\x4a\x06\xc5\xd0\x2f\x4a\xef\x12\x47\x54\x91\xad\xca\xf0\x45\x65\x85\x95\x0d\xea\x6b\xbd\x16\x04\xc1\x45\x7f\xe2\x77\x31\x5b\x28\x80\xf1\xfe\xae\xf1\x5c\x32\xe7\xa2\x2e\x1f\x38\xf0\xe3\x98\xf5\xae\x15\x78\xf0\x3d\x42\x43\x7c\xa7\xcd\x31\x2d\x57\xe6\xde\x64\xa6\xf2\x32\x1d\x5f\x93\x49\x73\x5a\x54\x56\xc5\x70\xc8\xe4\x2c\x48\x36\x0f\xa1\x33\x69\xf1\xd1\x17\xe3\xc8\xe6\x6c\x1d\xcb\x3b\x13\x28\x03\x1e\xcb\xf5\xe8\x23\xc2\x6f\x39\x28\x57\xad\x7d\x4f\x6b\x87\xbe\x23\x2c\x7b\xd5\xbf\x4a\x06\xef\x87\x52\x2f\x3a\x17\x0a\xd3\x3b\xe5\xad\xb2\x82\xb4\xf9\xb8\xe0\x2c\x9b\xa2\x4e\xdb\xb8\x22\x7e\x4e\x65\x3e\xd4\xb9\x7d\x7e\xab\x20\x33\x1b\x13\xb5\x6a\xff\x1e\xe2\x05\xbe\xaf\xcf\x1f\xff\xbc\x44\xb8\xe6\xed\xe5\xc7\x09\x7e\xa8\x90\xcc\xbd\x62\x8c\x98\x09\xf9\x08\xf3\xc9\xed\xcf\x65\xa1\xf1\x15\x2d\x36\x5d\x9e\x47\x1b\xb4\x02\xc3\xa9\xa3\xc4\xac\xee\x02\x7e\xb9\xdb\xea\x32\xf8\xe5\x12\xc3\x80\x53\x00\xf7\x7f\xfa\x0e\xc3\x65\xae\x1c\x84\xd3\x7f\x5f\xca\xbe\x74\xc6\x69\xbc\x9c\x27\x12\xc3\x9f\x13\x06\x09\x6c\x67\x9e\x78\xcd\x75\x64\x92\x85\xb7\x24\xe3\xa7\xdc\x02\x3c\x9f\xb9\x20\x01\x58\x66\x91\x2e\xc7\x57\x4e\xfd\x50\x9e\x1d\xa4\x4b\xc6\x46\x5e\x68\x36\x4b\x18\x39\x00\x3f\x64\xe7\x4b\xb2\x5e\x35\x16\x8a\x93\x00\xb0\xf5\x9b\x15\x8e\x1f\xd7\x1b\xc1\x2e\xd3\x85\x7f\x6f\x1e\xdb\xf3\xc5\xdc\x88\x23\xab\xcd\x21\xac\xc3\xd5\x8f\x7a\x3b\x18\x49\x0a\xd0\x7f\x50\x10\x58\x78\x33\xe9\xee\xe7\xa4\xca\x64\x03\xab\x3b\x57\x7e\x78\xd0\xad\x34\x9d\x7a\x52\x50\xf3\xcf\x20\x1c\xb2\x24\xc5\xdb\x9a\x25\x06\x45\xfc\xec\xa4\x44\xe3\xb1\xb4\x68\x19\xb7\x4d\x0a\xa6\x51\x8d\xb3\xe7\x25\x0c\x40\x3a\x50\xa3\x85\xdf\xee\x95\xc8\xbb\x49\x10\x9e\x25\x41\xe2\x3d\x43\xf8\x6e\x3b\x85\x8b\x4c\x66\x76\x17\x6b\xa9\x56\xc5\x9a\x5a\x95\x1b\x6a\xd5\x58\x42\xff\x2e\x95\xd6\x30\xd6\xa0\x7f\x0d\xdc\xe9\x89\x15\x71\x9a\x7f\x7c\xa5\xe8\xe5\xad\x2c\x3a\x74\xe3\x5c\xa1\x7a\x9c\x03\xf2\x2b\xa3\x7b\xac\x29\x26\xb4\x5a\xdb\x71\x1a\x32\xc0\xdf\x3a\x43\xe1\x2b\x83\xf0\x1e\x36\xee\x1b\x36\x54\xc5\x56\xc2\x3b\xe6\x72\xd1\xc0\xf8\x5d\xba\x6e\xbc\x11\xe3\x77\x49\x97\xda\xa9\xeb\xc6\xaa\x25\x9d\x7c\x1e\x32\x3c\xb3\x1b\x88\x0d\xc5\xb3\x46\xbe\xa1\x91\xef\x81\x99\xcb\x8f\xa1\xb7\x19\x2b\x1f\x49\x26\xfc\xf9\x34\x99\x94\x83\x68\x88\x38\x17\x2e\xab\x32\xdd\xfa\x6b\x94\xff\x7e\xfb\x4a\xb1\x7a\x4d\x85\xf0\x1e\x59\x2e\x3e\x19\xc5\x80\xd6\x00\x71\x27\x0e\x47\x24\x16\xe8\xed\x78\xb5\xad\x98\xaf\x73\xa0\xe0\x8a\xbc\x7f\x00\xf5\x77\x77\x1f\x49\xe8\xec\xde\xae\xc9\x83\xcb\x30\xde\x15\x11\x8a\xbc\x36\xae\x6e\xf3\x38\x33\x17\xc3\xee\xd8\x62\x9f\xf7\xb5\x18\xc4\xaa\x3b\x53\xde\x66\xb3\xa4\x33\x46\x5e\x53\xf1\xec\x84\x62\xe7\xcc\xb0\x00\x7d\x2e\xb1\x58\x85\x6d\x6e\x33\x13\x71\x43\xfb\x89\xf8\x65\x89\xe5\x18\x14\xe4\x07\x20\x55\x77\x7a\xbb\xd8\x79\x42\xc7\xf1\x05\x1d\xda\xae\x63\x7e\xdd\xe3\x11\x4f\x93\x89\xf8\xc0\x24\x73\x63\x3c\xfe\x79\xaf\xfc\x8a\x7c\x33\x9f\xd1\x41\x46\xf4\xbf\xa7\x7f\x1e\xcb\x82\xf3\x19\xb5\xa8\x24\xf0\x77\xd5\x43\x27\x0e\x94\x5d\x2a\xe1\xb3\xfd\xbd\x87\x60\xbe\x9f\xa3\x12\x83\xb4\xf1\xef\x0d\xee\xc7\x0d\x76\x10\x92\x78\xba\x6a\x08\xd1\x80\xb4\xfd\x7a\x9c\x26\x8c\xf6\x19\x08\x1e\x12\x84\x6f\xbf\xec\xe4\x71\x4b\x3a\x0d\x1c\xd7\x78\x65\xbe\x1e\xc3\x06\xd6\xf9\x1b\x01\x3b\x53\x35\x91\xeb\x26\x36\x5a\xd7\x0a\x29\x07\x3e\xdd\x56\xac\x4b\x8a\x94\x83\xc3\x07\x84\x2d\x2d\xd3\xc5\x55\x16\x25\xd7\x7e\x17\x5b\xb9\xd7\x1b\x88\x29\x15\xa7\xca\xf3\x06\x9a\x43\xda\x8b\xc0\x7e\xa0\x9b\x8e\x5c\x6b\x85\x75\xf2\x71\x18\x13\x75\x47\x4e\xb0\x83\x5b\xea\x16\xfc\x2c\x2c\xae\x3a\x8b\xf4\xd6\x23\x78\x17\x61\x07\x71\x43\x84\x73\xbb\x85\xac\xc6\x25\x72\x9d\x78\x3d\x64\xb3\x98\xd5\xf9\x46\x18\xbf\x79\x89\x4f\xfe\x1f\xb7\x85\x15\x57\xd8\xa6\xde\x06\x4b\x37\xb7\x81\xb5\x1d\x64\x19\xf6\xb0\x7f\x16\xff\x83\xec\x61\x1f\xbc\xd4\xd6\x91\xdd\xf0\xad\xfe\x70\xca\x0e\x9d\xae\x83\xd3\x46\x4b\xd9\xf3\xea\x55\xf8\x89\x61\x29\x7b\x41\x9f\xae\xfb\xe7\xc2\x8c\xf1\x04\xe1\x8f\x8f\xb3\x94\xbd\xee\x0f\xce\xbf\xae\xa5\xec\x65\xf0\xd1\x5b\x22\xfc\x2e\xf8\xb8\xc1\x52\x16\x67\x56\x13\xd3\x9b\xda\x45\x37\x4e\x83\x4c\xaa\x45\x38\xa2\x0f\x24\x0e\xef\x8e\xd8\x88\x01\xfb\x07\x2f\x09\x4e\x0d\x2d\x6a\x46\x8a\xe3\x65\x91\xb2\xbd\xa5\x38\xaf\xf0\x4c\xf4\x64\x84\x6f\x15\x28\x18\xf2\x93\x20\xb5\x5d\x5b\x0e\x4e\x37\xdb\xa4\x2a\x3d\x2e\xe1\x66\xf2\x51\x89\xb0\x3d\x92\x76\x2f\xab\xa2\x75\x0e\x0f\x0f\xff\xa9\xc5\x1d\x76\x3e\xa6\x51\x02\x46\x46\xa6\xbd\xc0\xfb\xe0\xa3\x37\x46\xf8\x6d\xf0\xd1\x5b\x20\xfc\xc1\x6c\x5c\x65\xdb\xf6\x40\xa3\xb2\x4b\x6c\x9c\x05\x89\x6a\xd3\x94\x3e\xd4\xda\xb4\xf8\xc2\x36\x2d\x90\x5f\x04\xd9\x97\xb5\x69\x21\x5c\x0f\x3e\xa3\x4d\x65\xdc\xf5\xba\xb3\xb7\xb7\xf7\xcf\xc2\x6c\x5b\x89\x6b\xcf\x72\x0d\x9c\xae\xcd\x96\xe0\x3a\xf1\x3a\xcf\x0e\x0c\xbb\xe3\xb3\xe0\xa3\x61\x77\xbc\x2d\xee\x89\x61\x82\xb7\x52\xb7\xcb\xdc\x46\xa5\xba\x5c\xcf\xb6\x32\x60\xd6\x66\xbe\xb8\xdf\x7d\xa7\xdd\xef\xbe\xb7\xde\xef\x7e\x50\xf7\xbb\x67\xda\xfd\xee\x5b\x6c\xfa\x3f\x59\x41\x6c\xaf\xfb\xc4\x2f\xb4\x71\xe2\xba\xaa\x2e\xba\x3d\x62\xa2\x46\xc2\x7a\xdd\x05\x1f\x24\x3e\x18\xb5\x31\x06\x80\x19\x37\x7f\x8f\xb5\xb4\xb1\xb8\x42\x1f\x3e\xe6\x4e\xf9\xbc\xd9\x7e\xfa\x42\xbb\x5f\x3e\x3a\x49\x3a\xf3\x65\x74\xb1\x5c\x2c\xd2\x0c\xa6\x47\xd0\xee\x82\x7c\xbb\x48\x82\x93\x44\xe3\x55\x31\x1a\x51\xf8\x7b\xd6\x3c\x6f\x8a\x7e\x12\x28\x6b\x5b\xb0\x39\xa2\x4b\x39\x3b\x34\xfb\x66\xd7\x77\xd8\x79\x1a\x7c\x01\x67\x50\xf1\xcd\xc0\xb3\xbf\xfc\xa2\xcc\x40\x45\xa6\x79\x49\xbb\x42\x91\x15\x7c\x31\x72\x7a\xa7\x2d\x31\x03\xd2\xb9\x4a\xb3\xe8\x53\x9a\x14\x61\x8c\x49\xe7\x86\x64\x45\x34\x0e\xe3\x61\x67\x1e\x2e\x6c\x9a\x5f\xad\x40\xa4\xef\xe8\x9a\xd5\x62\xe5\x20\x9f\x94\x08\xf1\x59\xdb\xd2\x59\x18\xde\xeb\x09\x35\xc2\x31\x31\x9a\x8d\x2d\xef\x18\xc7\x7c\x81\x11\xd7\x8d\xa7\x31\x9c\x78\xb1\x87\xf3\x2c\x9a\x45\x89\x69\x04\x70\x2f\xaa\xe8\x3b\x45\xba\x70\xb0\xaa\xbe\xa0\x6f\xe5\x16\x02\x2c\x89\x77\xfc\xc8\x17\x34\x2a\xf6\xea\x3d\x99\x92\x8c\x24\x63\xa2\xdf\x3e\x8e\xfb\x8e\x28\x80\xe3\x8f\x41\xaf\x92\xba\xdc\x42\xdb\x32\x5c\x19\x5b\x86\xb9\xc1\x8d\x73\x63\x5c\x6d\x1a\xc6\x82\xcf\xb9\xad\x20\x58\xe0\xc2\xae\xe6\x58\x54\xf7\x2e\x20\x1d\xae\x25\x5f\x65\x24\xbf\x4a\xe3\x89\xc9\x33\xdd\x3b\xe4\x14\xd3\x42\x39\x3c\x35\x94\xc3\x6b\x53\x39\x3c\x57\xca\xe1\x89\xae\x1c\x5e\x18\xca\xe1\x47\x71\x05\x7a\x69\x5e\x79\xbe\x53\x19\x5f\xf6\xef\x4b\xff\x12\xbf\x0f\x34\xd1\xcc\x3b\xe4\xad\x0a\xf5\x7e\x9b\x0e\x79\x8f\x3f\x34\x28\x98\x06\x50\xde\x45\x02\x40\x79\xd6\x9b\xd2\xd7\x2a\xe0\xb1\xd0\x5b\x8f\xf1\x27\x23\x59\x56\x85\x37\x2a\xe4\x27\x5a\x85\x4f\xf8\xa5\xe5\xb2\x54\x18\x5b\xca\x4e\x17\x3f\x59\x15\xe5\xa3\x18\x3f\xf2\x85\x1c\x3d\xdb\x1d\x69\x2a\x40\x3d\xfd\x96\xb5\x3e\x0c\x00\xc3\xce\x18\x04\x5f\x47\x5f\xb6\x5d\xdc\x56\xba\xf3\x71\xb7\xb7\x95\xe6\xa6\xaa\xf6\x2b\x5d\xbb\x7e\xb1\xc9\x68\x34\x9a\x7a\xd5\x76\x0d\x82\x60\x22\xee\x15\x73\xee\x32\xfd\x3e\xf1\x22\x84\x93\xc0\x2b\x5c\x17\x70\xd0\x25\x99\x49\xbf\xf0\x3f\x16\xde\x2b\xb9\x90\x33\xb2\xa6\x26\x3b\x79\x9c\x05\x00\x8f\xd2\x8f\x3b\x6a\x90\x72\xd1\x2b\xfc\x34\x8b\x74\xe1\x27\x60\x23\xfd\x31\xf1\x12\x9c\x21\x76\x8b\x92\x30\xf3\xed\x4b\xfa\x2e\xd6\x64\x2c\x53\x1d\x22\xe3\x1d\x56\xc9\xe3\x1c\x4f\x86\x08\xff\xbe\xa9\x11\x58\x1d\xc1\x44\x7e\xe6\xba\x6a\x04\x42\x4b\x70\xc9\x38\xe3\x30\xf0\x0c\x11\x50\xe0\x21\x27\x48\xec\x1f\x4c\x69\xaa\x28\x95\x0a\x5a\xe5\xa3\xc4\x75\x13\xba\xca\x1e\xa1\xec\x69\xe0\x25\x41\x62\x52\x73\x20\xe5\xeb\x2a\x51\x9b\x4a\xb0\xe0\x39\x2a\x82\x84\xb3\x07\x5d\xa6\x8b\xa7\x89\xa1\x67\x7e\xb3\xbb\x93\xad\xd7\xdd\xb2\xab\x40\xd0\x11\x1e\x68\x75\x9f\xe0\xd9\x10\xe1\x27\x0f\xd7\xfd\x33\x5d\x41\xc4\xa2\xaa\x24\x0e\xac\xf0\x6f\x65\x09\xd0\xd3\x42\x17\x3f\x97\xec\x6b\xb5\xf3\xf4\x37\x5a\xe4\x21\x12\xc8\x8e\x9b\x8a\xfe\x3b\xe3\xc6\xe2\x67\x5a\x06\xd5\x92\xb8\x66\x13\x2f\x39\x5f\x0c\xce\x82\x27\x5e\x82\x0b\x76\xa1\x0e\xe7\x09\xda\x98\x67\xe3\x6f\x19\xc7\x7c\xd8\x01\x5c\x96\x39\x3f\xfd\x77\x89\x97\xa1\x92\xa3\x42\xbf\xf0\x0a\x44\x77\x83\x74\xc8\xee\x64\xaa\xf1\xe3\x20\x85\x21\xbb\x93\xe9\xd5\xcb\x83\xf0\x69\xc2\xd5\x15\x3c\x0e\xe2\xa7\x09\x53\x28\xf0\x32\x38\x2e\x3c\x98\x67\x08\x4f\x83\xa5\xee\x71\xb0\xb3\xc2\x0b\xf1\x86\x71\x48\xad\x68\xd1\xc3\xef\x56\xac\x11\xae\x82\x70\x67\x75\x14\xee\x04\x57\x58\xe5\xff\x34\xb8\x62\x00\x0b\xd1\xd4\xcb\xbf\xe7\x98\xe5\xf3\x20\xdf\x99\xd2\x90\x73\x23\xe4\xbc\x8c\xa6\x5e\x2c\x92\xbb\x09\xe2\x9d\xd5\x51\xbc\x13\xdc\x60\xbd\xe8\x4f\x83\x1b\x99\xe0\xf8\xfb\x05\x0b\x3b\x0a\xc6\x3b\x0b\x1a\x76\x54\x09\x3b\x2a\xb5\xe6\x54\x0a\x0d\x9c\x0d\xc1\x29\xa7\x17\x22\x4e\xa0\xc3\xae\x6a\x6d\x41\x62\x11\xc4\xde\x01\x6c\xe2\x4f\xf0\x0b\xfc\x3b\x7e\x82\x57\x43\x09\xfd\x69\x35\xa8\x65\xf6\xae\x52\x52\x1d\xa9\xdd\x7f\x11\xfc\x4a\x67\xb7\x40\x51\xa1\x1d\xe9\xba\x9e\x4e\x04\x06\xef\x14\x98\x13\xf4\xab\x16\x84\x3e\xf2\xb7\xa8\xbe\x7d\x62\x65\xa6\x69\x98\x6f\xa0\xfc\xbf\x0e\x25\x3e\xa9\x7d\x94\xbf\x7a\xc0\xac\xbf\xd1\x38\xe7\xa3\xeb\xfe\xc8\xf0\x34\x21\xc4\xeb\xf9\x82\x64\x40\x9a\xfa\x63\x98\x4c\x62\x62\x70\xe9\x28\xbc\xd2\xfe\x3d\xc3\xb7\x12\xcb\x81\x4e\x14\x47\x93\xe3\x88\x69\x08\x0f\x3e\xe2\x1f\x37\x18\xe8\x46\x53\xef\x63\x83\x8d\x31\x2b\xd5\x7f\xd4\x98\x18\xca\x0a\xf3\xf5\xaf\xe0\x35\xdf\xb7\xb5\x83\xe0\xf5\x7a\x7d\x56\xd9\xb9\xac\xd7\xde\x5f\x5c\x5f\x61\x11\x7e\x0e\xe6\xeb\xb5\x17\xf5\x3f\x8a\xd9\x09\x2b\x9c\x2f\x82\x34\x1c\x33\x57\x2d\x4b\x14\x30\xe4\xcf\xcc\xdc\xe3\x23\x3b\x22\xc6\xa6\xc9\xc9\xbd\xf2\xf7\x6b\x77\xcb\x26\x0b\x92\x05\x3b\x99\xbe\x42\x25\x7e\x59\xdf\x32\x9f\x6d\xd8\x32\x4b\xab\x67\xff\x56\xdb\x1f\x9f\x8a\x2d\xf1\xb9\xda\x12\x9f\x68\x5b\xe2\x0b\xb9\x6b\xfd\xab\xc4\x6f\xf0\xbd\xb6\x9b\x36\xc0\x33\x61\xe9\xbb\x76\xdd\x6b\xf8\x85\xa1\x9f\xf1\x1b\x4d\x17\x46\x16\x88\xf7\x66\x53\xad\x11\x34\xd1\x6f\x25\x7e\xd7\x68\x94\xb5\xe0\x46\x59\xef\xd4\x16\x80\xe6\x31\xe5\xc0\x8b\x1f\xac\x3e\xa1\xd2\x12\xc6\x46\x76\x5d\xb3\x8e\x11\x2f\xfe\x90\xfb\x62\x45\x64\xde\x3b\xa4\xbf\xb9\xed\x06\x7d\x08\x57\xec\x83\x33\x0e\xe3\xb1\xd7\xeb\x76\xff\xd1\xda\x69\xed\xed\x2e\x56\xc8\xb0\xf2\xb0\x7c\x55\x26\x2f\xba\x27\xe7\xbb\x74\x01\x77\x51\x25\xf2\xde\xea\xa7\xf1\x67\x89\xe0\xca\x93\x50\x78\xe0\x99\xd3\x99\x46\x59\x5e\x00\xfa\x89\x5f\xb8\x2e\x43\x48\xe2\x2d\xcd\xa9\x59\xfa\xb6\x97\x7e\xc2\x4e\x21\xf4\x04\x34\xd2\x41\x7b\x6e\x89\x99\x1f\xe9\xc4\xa1\x91\xf7\x22\x23\x37\x51\xba\xcc\x6b\xf9\xdb\x3f\xa8\x32\xc8\x74\x34\x1e\x43\x45\x85\xa5\x3c\x91\xf8\x8a\xcd\xcf\x1a\x18\x30\x7e\x42\xb2\x4b\xb2\x2a\x6a\xf8\xb2\x09\xdf\xbd\x17\x64\x25\x34\x7d\x84\xa9\x76\x03\xda\x58\x91\x45\x73\x0f\x75\x8a\xf4\x4d\x7a\x4b\xb2\x93\x30\x27\x1e\x42\x5c\x17\x02\x87\xbc\x8c\x2c\x48\x08\x30\x9b\xc9\xa0\x3b\x14\xe4\x15\xf9\xa0\x3b\xf4\x21\x75\x69\xa0\xc8\xde\xf3\xbd\x3a\xa0\xac\xca\x3a\x7c\xe2\xcd\x88\x33\x9c\xe2\x48\x29\x89\x61\xd0\xee\x51\x6d\x01\xbe\xb6\xdb\x85\xeb\x26\xe8\x28\x3e\x82\xca\xc6\x60\x30\xa9\x5a\x19\x5e\x0a\xaa\xd3\xa3\x30\x68\x77\x61\x73\x9f\x07\xed\xcc\x75\x3d\x65\x04\xb1\x5e\x4b\xd6\xaa\xd8\xc6\x5a\x25\xaf\xac\x11\x68\x41\x71\x9d\x7e\x24\x62\xf4\x23\xae\xfb\x3a\xf1\x62\x1c\x21\xd7\x6d\xe7\x48\x6f\xd5\x58\xd0\x4a\x1c\xb1\xb2\x83\xef\x2d\x94\xe6\x4d\x82\x5f\x26\x3a\x27\x6e\x60\xe3\xc4\xd5\xd9\x70\x75\x7e\x5c\xfc\xea\x31\x27\x15\xec\xce\x30\x94\x78\xf9\xb6\xab\x1c\xf9\xf5\x75\x41\xe6\x56\xa7\x7c\x0d\xba\x5f\x3f\x4d\xd0\x68\xb5\x27\xec\x1e\x8d\xa6\x42\x1f\xad\xfc\xda\x3c\x28\x5d\x86\x7e\xcf\xc2\x85\x05\xbb\x9f\xee\xf8\x05\x98\xfc\x48\x33\x2c\x36\xae\x75\x9c\x1c\x48\x04\xc9\xe4\x8c\x24\x4b\xc7\x7e\xc5\xc3\x2b\x4f\x37\xc1\x1a\x1e\xbc\x51\x51\xcb\x76\x98\x6f\x82\xed\x55\x52\x1f\x44\x05\x60\xe7\xaa\xa0\xd9\x35\x33\xe1\x55\xf5\x3a\x46\xbb\xcf\xb9\xa7\x13\xc0\x1f\x0c\xb1\x9c\x33\x74\xf1\x11\x73\xfe\x67\x72\x77\x16\x16\xe3\x2b\x32\xa1\x6f\xe9\x4c\xbf\x8c\xe6\x1c\xe7\x09\x1d\xbd\x34\x35\x85\xd0\x75\x57\x55\x12\x13\xee\xfd\xf4\x08\xa5\xe6\x3e\x9c\x7c\x5c\x72\xf6\xed\x57\x69\xc6\xac\x10\x47\xa1\xfd\x64\x57\x11\x08\xe9\x74\xb2\xa0\x2c\x1a\x7b\xaf\xef\x54\x38\xfd\x35\x9d\xbb\x7c\x47\xa8\x69\xb4\x74\xe7\x0b\x8a\xec\x51\x25\xf5\x81\x30\x8a\x2e\x94\x51\x74\xdf\xd1\x18\xb1\x1d\x49\x53\xf9\x9e\xd3\x50\x66\xd8\x5a\xc2\x40\x5b\x54\x9e\x2a\x83\xb9\x8c\xdd\xbf\xf2\x79\x2b\x63\x96\xa5\x66\xae\x7d\xca\xf8\x9e\x1b\x35\xd0\xd5\x43\x8e\xa5\x05\xc2\xd7\xc1\x4e\xef\x28\x55\xb8\x5b\xc2\x1f\x65\x5c\x23\x6c\xac\x12\x29\x03\x23\xa9\x00\x3e\x54\xe2\xcb\x33\x67\x41\x00\x6c\x15\x22\x98\xf8\xb4\x5e\x03\x2b\xdb\x35\x4d\xe2\x3a\x60\xe7\xd1\x50\xa5\xf3\x40\x2b\xcb\x3c\x5c\xd4\xcb\x11\x4d\xbd\x02\xa2\xf2\x7e\x57\x4e\x35\x39\x5d\x29\x94\xcc\x08\xda\x5d\x84\x15\xb0\x6b\x0d\xa6\xdc\x56\x4e\x2f\x51\xc8\xe5\xdd\x1a\xdf\x0d\xd1\x40\x0a\x88\xa6\x09\xd7\x8c\xac\x8b\xaa\x57\x4e\x4c\x7c\x67\x4e\xb3\x01\xcd\xe8\x54\xd3\x0f\x27\x0d\xa0\xe1\x6c\x7b\x23\x7b\x10\xe8\xc8\xae\xc9\x1d\xce\x82\x8f\x40\xc1\x6a\xd0\x2e\xc1\x7e\xf8\x38\xcb\xd2\x5b\x98\xf1\x74\x5d\x43\x04\x56\x6b\x92\x14\x2f\x99\x51\x9e\x87\xf0\xa7\xc4\x2b\x70\x86\xe7\x78\x81\xcf\x12\xc6\xe2\xd6\x92\x31\x7f\x5d\x6c\x13\xef\x58\x8f\xf7\x63\x3a\x27\x9b\x23\xc1\x26\xbc\x96\xdf\x69\x32\xd9\x32\x9a\x9e\x5d\x0f\x96\x6b\xce\x88\x27\xd0\x18\x64\xfb\x44\x54\x19\xd0\xb5\x00\x1c\x06\x1c\x13\x35\x4c\xc6\xa4\x93\xa4\xb7\x1e\xdd\x72\xc1\x22\xaf\x88\xe3\xbc\x70\x27\xed\x08\x61\xf6\xfd\x41\xb7\xdb\xf7\x58\x18\x60\xd1\x55\xda\x43\xd0\xee\xe2\xb4\x53\x97\x85\x74\x90\xf9\x5a\x38\xd7\x8d\xda\x41\x90\x0a\x1d\xc3\x75\x3d\x23\x91\x1e\x6c\xea\x44\x7e\x41\x88\x79\x81\x80\xc8\x31\x62\x53\x20\x0e\x32\xd7\x6d\x1b\x69\xbe\x4e\xbc\x0c\xa7\xb4\xf8\xf5\x12\x50\xed\x61\xbd\xe6\x5d\xd4\xee\x41\x5b\xe3\x14\xa1\x7e\xbd\x71\x7d\x7b\x0d\x7a\xe5\x8d\xeb\xde\x78\xe0\x79\x28\xf8\x9a\xc2\x7e\xd7\xdf\xe9\xc1\xd5\xd6\x39\x68\xe4\x2f\x92\x60\xd3\xf1\x34\xbb\x6c\x29\xf1\xef\x9b\x83\xb1\x53\x6c\xfc\x64\x4b\x45\x41\x2a\x07\xba\xe7\xd4\x7a\xfd\x80\x29\x6f\x15\x16\x1e\x94\x87\xaa\x7b\x23\xd5\x17\xe8\xc4\xa7\x4b\x26\x3b\xed\x36\xb0\x7a\xee\x4b\x5f\x58\x81\x30\x4f\xa7\x2b\xf3\x6e\x60\x2e\x4e\xfd\x6f\xcc\x53\x7f\xe3\xaa\xe2\xbe\xe4\x77\x15\x7c\x37\x70\xc2\x0b\x79\x67\x3f\x98\x37\xae\x2a\xf8\xc1\x3c\xbb\xae\x10\xda\x86\xe1\x0d\x55\xd1\x36\x6e\xf1\xb5\x45\xdb\xd0\x74\x0c\xeb\x11\xbb\xad\xad\x1c\xec\x18\x2d\x53\x25\x7e\xd1\x8e\xcc\x2d\x47\xe2\x66\x5d\xb7\xf0\x5c\x3a\x67\x46\x24\x27\x54\xed\x6b\x8f\x5d\x77\x8e\x2f\xaa\x4a\xca\xc7\xea\x8b\xcb\xca\xaa\x45\x57\x8a\xf0\xf3\x57\x2c\x10\xcd\x8c\x0c\xa9\x61\xa5\xba\xa4\x51\x2f\xf5\x95\xea\x5d\x75\xa5\xaa\xe5\x2f\xce\x76\xe1\x06\xa8\xb6\x94\x80\xf3\xb0\x0e\xd6\xf0\xb1\x61\xb5\x2e\x10\x3e\x23\x1e\xe1\x16\x33\x65\xc9\x2e\x15\x9b\xd6\x9e\x0f\x95\x5d\x79\xfd\x7e\xc4\xb7\x1c\x1e\x29\x07\x1f\x3e\x34\xfc\x99\xf4\x6a\x59\xe8\x97\xf0\x66\x0d\x2f\x94\xd3\xb9\xfc\xd9\x69\x50\xda\x3c\x82\xcf\x11\xa6\xea\x34\x47\x18\xd6\x2f\x89\x7c\xa1\x4e\x9d\x6b\xea\xd4\x8b\xc4\xff\x3d\xa9\x9d\x20\x36\x87\x54\xc3\xb0\xea\x01\x34\xe2\xe7\x10\xa4\xfe\x45\x4a\x0e\x76\xc6\x10\xb3\x73\x89\x12\x0e\x3c\x16\x24\x11\x3e\x3d\xd8\xe2\x3f\xb4\x2a\xf1\x75\xfd\x58\xe4\x55\xa5\x03\x1a\x38\x41\x2e\xc3\x91\x46\x08\xe2\x59\x56\x42\x06\x37\x80\xe9\x96\x4e\x71\x7f\x94\x98\x6f\x20\xfc\x0b\x2c\x27\xb7\x1f\xb9\x2e\xa3\xb9\xbd\x5c\xaf\xc7\x08\x1b\xbb\x09\xff\x04\xf3\xc9\xe6\x9f\x96\x78\xda\x78\x22\x13\x77\xe2\x28\x2f\xf0\xd4\x3c\x90\x79\xc7\xce\x63\x7e\xad\x9c\xc7\xf0\x73\x98\x86\x93\x91\x6f\x0f\xe1\x64\xe4\x01\x1f\xa2\x12\xd3\x0c\xfd\x7b\xfb\x01\x0a\x08\xb5\x12\x79\x4f\x12\x84\x7f\x4c\x82\xc4\xeb\x75\x11\xfe\x6d\x8b\x75\xe3\x01\x8c\x37\xe5\x10\x9f\x2b\x61\x1a\xf7\x9d\x38\x72\x7c\xb6\xa5\xe4\x82\xe1\x87\x65\x51\x90\x2c\x97\x4e\x20\xed\x20\x18\xbb\x2e\xf3\xfd\x10\x06\x97\x42\xa0\xd3\xed\x26\x55\xf2\x2a\x9e\xf0\x54\xac\x44\x54\xa0\xfa\x6c\x9b\x29\x64\x0a\x33\x23\xe4\x4b\x2d\xbe\xf9\x1c\x17\x79\xb3\x90\x0e\x76\x2a\x45\x72\xb0\x43\x0b\xe4\x60\xb9\x4a\x38\x58\xf1\x8e\x2a\x97\x0f\xa2\xcb\x41\x8d\x5d\x61\xde\x9f\xfb\x3b\xbd\xfa\xe8\x9e\x56\x54\xdb\x11\x18\x0d\x03\xa8\x1b\x55\x72\x17\x4a\x83\x48\xb0\x2c\xaf\x9f\x63\x51\x0a\x5f\x7a\x5c\xf0\xa2\xfb\x4b\x6c\x9d\x9a\x13\x92\xe4\xc4\x8f\x3a\xf0\xb7\xc4\x13\xd4\x74\xa6\x2a\x61\xe7\xae\x5c\x37\x52\x4d\xdc\x5e\xd2\xc7\x19\xcb\x44\x9a\xf5\xde\x70\x5e\x97\x2d\xcd\x7a\xab\x94\x83\xba\x79\xf6\x28\x9d\xdc\xf5\x44\x63\xfc\x98\x40\x10\x75\xa6\xb8\xff\x5c\x38\x69\x5c\xa6\x0b\xff\x50\x3c\x70\x67\xa9\x43\x3c\x4a\x57\x17\xd1\x27\x98\x0d\xcc\x83\x63\x67\x94\xae\x1c\xcc\xed\xbb\xcd\x03\x4c\x75\x7e\x79\x7b\x15\x15\xe4\x62\x11\x8e\x89\xef\x24\xe9\x2d\xdd\xe9\xd3\x52\x8d\x32\x12\x5e\x2f\xd2\x28\x29\xf2\xce\x72\xe1\x39\xf9\xdc\x41\x58\x2b\x0c\x77\xb6\x43\x08\xf3\xf6\x00\x4b\x74\xd1\x23\xf7\x25\x66\x8d\xfd\x50\x6d\x77\x6d\x69\x9a\x96\xc8\x74\xd6\x82\x0a\x51\x22\xef\xb7\x04\xe1\x9f\xe9\xcc\x3d\x78\x86\xf0\x2f\x49\x90\x74\x12\xef\xe7\x04\xe1\x1f\xe0\xdd\x21\xc2\x7f\xb2\x77\x3f\x24\x08\xff\x01\xef\x0e\x10\xfe\x89\xbd\xfb\x23\x41\xf8\x5f\xf0\x6e\x1f\x61\x92\xc1\xbb\x7f\x25\x08\x17\x59\xe0\xbd\xa9\x82\xbc\xb1\x1e\xa3\xef\x59\x0d\x38\xa7\x3f\xe3\x83\x39\x9f\xae\xd7\xf7\x1f\x3e\x00\x3f\xcc\x87\x0f\xfe\x60\x58\x6a\x6c\xfc\x80\x6f\xa7\xfb\x32\x83\x91\x67\x47\x06\x0f\x8a\x72\xbd\x6e\xb8\x20\x6e\x45\x49\xab\x40\x3c\x47\x49\x3f\x53\x21\x99\xe3\x4c\x40\x38\x01\xcd\x63\x90\x0c\x83\x62\x90\x0c\x51\x89\xf8\x1a\x58\x49\x5b\x1c\x38\x2a\xf6\x95\x34\xc9\x8b\x6c\x39\x2e\xd2\x2c\x20\xe5\x1b\x16\x0e\x13\x95\x1f\xc7\x9b\x0f\x8a\x3e\x2f\x09\x37\x2c\x2c\x90\xef\x25\x5a\x30\xad\x88\x38\x21\xb7\xad\x84\x0a\xf7\x24\x6b\xe4\x33\xd2\xec\xb0\x15\x97\x91\x60\x32\x92\x14\x3b\x57\x70\x76\x73\x99\xce\x66\x31\xa1\x5d\x6f\xa0\xf4\x2b\x46\x01\xdb\xfc\xa2\xb2\xf1\x7c\x41\x12\x32\xf1\xdb\xa4\xa3\x9e\x4a\xce\x9b\xc0\x92\x06\x0d\x64\x43\xca\x46\x32\xbd\x52\x8b\x79\x51\xa4\x8b\xcb\x30\xbf\xae\x83\xcf\x27\xf2\x24\x20\xbf\x7e\xfd\x12\x17\xc1\x1d\xf1\x08\x3b\x42\x1e\x87\xcc\xe2\x3a\x99\x79\xe8\x68\x9c\x26\xd3\x28\x9b\x7b\xce\xcb\xb4\x75\x97\x2e\x5b\x19\x09\xe3\xf8\xae\x75\x1b\x26\x45\xab\x48\x5b\x79\x91\x2e\x5a\x61\x32\x69\x4d\x48\x4c\x0a\xd2\xa2\xc9\xb5\xfe\x5a\x92\x25\x99\xb4\xc2\xa2\xe5\x3c\x2d\x9e\x3a\x7d\x07\xc1\x99\x82\x20\x87\x78\x09\x41\xe1\xde\xbb\x5a\x41\x4f\xaf\xf6\x59\x7a\x43\x68\xe1\x2f\xd3\x57\x59\x6a\x92\xf2\xa8\xc4\x20\x10\x0b\xe0\x99\x75\xb2\x26\x6f\x4b\xfd\x45\x95\x8e\xa2\x92\x38\xfd\xbe\x6d\xda\x19\x99\x4a\x6f\x73\x66\xbf\x92\x08\xea\x28\xb3\x93\x70\x22\xad\x1e\x32\xc1\x14\xa5\xe6\x4f\x9d\x32\x60\xa3\x3f\x88\x95\xdc\x78\x95\xe0\x7b\x32\x99\x81\x11\xf6\x84\x1d\xb9\xc0\x6c\xa2\x1a\xb4\x60\x2e\x83\x17\xd5\xf1\x5b\xa7\x7a\x23\x59\x27\x84\x4c\x2c\xc4\x64\xbf\x26\xf8\x5e\x98\x9c\xc8\x0c\xe4\x81\x04\x68\x8f\x1a\xd1\x81\x6a\x04\xa9\x59\x6b\x65\x90\x8d\x59\x2f\xc2\xab\xc4\x5e\xcb\xbf\x12\x7c\x6f\xa9\x8d\x18\xf8\x98\x3b\xda\xea\x64\x43\xf3\xf4\x86\x2e\xbd\xf5\x3c\x4e\x1b\xf2\xf8\x29\xe9\x84\x58\xf8\x96\x49\x77\x29\xa7\xb4\xb4\xc6\xac\xc0\xf7\xdc\x2d\xec\x52\xae\x1b\xfc\x72\xb4\x9e\x00\xe7\xef\x30\x8a\xd5\x77\xd8\xfc\x70\x7c\x87\x56\xc2\x41\xc8\x12\xc8\x75\x1f\x4b\x75\xdd\xd0\x4e\x95\x39\xb6\x7d\x9b\xfc\x99\x34\x0f\x89\x19\x2f\x83\x43\x53\xa7\x52\x62\x4a\xd3\x76\x2c\x21\x1f\x2c\xd5\x0b\xa0\x34\xd9\xb6\x50\xbf\x3c\xa6\x50\xa3\x10\xf8\xf6\x80\xe5\x0e\x17\xa5\x97\x6a\xcc\x0a\x38\xb3\xf0\x75\x98\xde\x17\x7c\xbd\x25\xff\x2f\x2d\xb7\xf4\x5d\x59\x63\x79\x87\xea\x18\x90\xb8\xb5\x55\xb7\x28\x09\x04\xd3\x85\x94\x58\x74\x93\xca\xa2\x9b\x20\xdf\xcb\xf4\x60\x95\x45\x37\x43\x65\xe9\x21\x9c\x5a\xc4\x9b\x97\x66\xa2\x39\xc3\x3c\x8f\x66\x89\xde\x10\xaa\x19\x0a\x9c\x04\x3d\x5c\xc7\xeb\x3d\x4a\xbe\xcb\x8e\x92\xa7\x4f\x91\x08\x98\x42\x7b\x69\x16\x60\xc9\x70\xdb\xc6\x4b\x59\xe3\xa5\xb4\xf1\x52\x6d\xa7\x50\x22\x0e\xe7\x0b\xca\x80\x4c\x19\x95\x38\x32\xad\xe8\x70\x82\x33\x29\xb6\x13\x72\xeb\x25\xb0\xb7\x78\x97\xa5\xf3\x28\x27\x48\xd7\xb7\xd9\x75\xaa\xe8\x80\x90\x56\xb5\xc8\xee\xee\x73\x8f\x71\x0c\x7a\x04\xa1\x72\x1c\x16\xe3\x2b\x30\x79\xf4\x08\x2a\xd5\xad\x6c\xac\x87\x2e\xae\xb2\xf4\x76\x63\xf0\x5c\x9e\xaa\x1f\x91\xce\x24\x4d\x48\x3f\xf5\x48\x07\xc8\x4a\x90\xef\x15\x01\xff\x8d\x8b\x96\x36\x62\x93\x7e\xe1\x83\xba\x54\xc1\x0e\xf4\xc0\xa7\x05\x75\x8a\x2b\x92\x78\x21\x8e\x51\x99\x7b\x5e\xc6\x38\xe9\xe2\x3b\xda\x08\xeb\xf5\x60\x88\x10\xab\x05\x33\xb7\xc7\x61\xd5\xd8\x90\x3b\x12\xe1\x14\x47\x38\x0c\xee\x99\x8f\x67\x17\xe7\x74\xcb\x64\x5a\xe4\xf4\xdc\x74\xd0\x1d\x22\xa8\x65\x0b\xdc\x97\xc4\xaa\x38\xe8\x0d\x4b\x5c\x64\xec\xe2\x8e\x0a\xdb\xc1\x50\x8e\xf3\x28\xb8\xa7\xf9\xfb\xb1\xd7\xa5\xa2\x34\x4b\x6f\xfd\xd8\xeb\xd1\xcd\x10\xfd\xec\xc7\xde\x2e\xb2\x52\xb4\x5c\xdc\xcd\x47\x69\xec\xba\x5e\x34\x60\x3f\x3b\x51\x41\xb2\xb0\x48\xb3\xa1\x65\x61\xa6\xc3\xa1\x44\x38\x3a\xd2\x7a\x26\xaa\x7b\x90\xc4\xf5\x57\x11\xd4\x2d\xe1\xd5\xa2\xed\x7c\x79\xb7\x20\xa7\x59\x96\x66\x9e\xf3\x03\x49\x58\x9e\xad\x28\x6f\x85\x71\x46\xc2\xc9\x5d\x8b\xac\xc8\x78\x59\x44\xc9\xac\xe3\xa0\x23\x3a\xd6\x8f\xc2\x23\x44\xc7\x00\x4d\x87\xce\x0c\xd7\xf5\xd2\x60\xd7\x8d\x06\xdd\x61\x3f\xeb\xf0\x8a\xf2\x27\xc8\x66\xbd\xf6\xbc\x34\x10\x9f\x10\x2c\x27\x74\xdc\x67\x08\x77\x91\xcf\x86\x1d\x72\xdd\xb6\x97\x06\xe2\x0b\x8e\x06\x3d\xda\x97\x74\xd0\x48\xdc\x9e\xa3\xfc\x36\xa2\x03\x2d\x0b\xba\x38\xa5\x4d\x15\x0c\x58\xb6\x38\x65\x03\x69\x88\x30\x7d\x44\xf7\xe3\x30\x27\xad\xae\x0f\x7f\x7a\x7e\x1a\x44\x47\xb0\x5f\x3b\x82\x17\xfb\xbe\x20\xf2\x67\x80\x02\x4f\x9f\x0a\x02\x1d\x9a\x29\xa6\x59\x52\x9d\x89\x05\x3e\xf0\x55\xa8\x2c\x80\x00\x51\x30\xe8\x0e\xa9\xaa\x5a\x44\xc9\x92\xb0\x60\xcf\xfc\x28\x08\x3b\x74\xcd\x5c\xa4\x0b\x0f\xe1\xb0\x43\xc7\x07\x7b\x50\x41\x39\x56\x83\x1f\x4d\x3d\x5a\x59\x16\x08\x43\xb5\xd5\xf5\x48\x3a\x10\x4f\x3b\xbd\x21\x5a\xaf\x0f\xdb\x41\x10\xc1\xbd\xc6\x2e\xff\x85\xd0\x7d\x18\x74\x65\xb2\x65\x34\xf5\xf6\x02\x11\xc8\x6b\xa7\xeb\x35\x2d\xe7\xf7\x29\x3c\xd3\x9f\xdf\xa5\x83\x3d\x88\xc5\xaa\x02\xd5\x60\x2d\x42\xe3\x1e\xca\xb8\xfc\xfb\x77\x74\x84\xab\xd0\x40\x1f\x28\xdb\x90\xc6\x48\xf5\xa0\xbb\x46\xd0\xdd\x21\xe6\xed\xc0\xaf\x57\x58\x24\xfa\x81\x46\x7a\xa0\x85\xca\x28\x28\xd8\x08\x20\x38\xd4\xe5\x4a\x30\x38\xc4\x64\x88\xb3\xa0\x5b\x4e\xa3\x84\x6e\x0a\xee\x93\x20\x0d\xba\xb4\x34\x07\x30\x06\xf8\x88\x8e\xd4\x44\x95\x9d\xda\x1d\xf6\xe9\x6b\xe1\xf5\xc4\x3a\xb8\x5b\x96\xde\x20\xc2\xf1\x10\xfc\x0f\xe3\x2f\xdd\x96\x71\xb5\x3b\xca\xdf\xa4\x21\xb8\x98\xb7\x7b\x98\x6a\xef\x27\xd3\x19\xe7\x65\x92\x2a\x78\x66\x51\xc1\xb9\x1e\x09\xe7\x03\x97\xa0\xf4\xdb\x3c\x32\xa3\x8c\x65\xcf\x2b\xc2\xff\x54\xed\x36\xa5\x3d\x4f\xc8\x83\xab\x00\x09\xba\xe7\x73\x28\x61\x3d\x26\xa7\x8a\xce\x26\xa7\xb6\x7a\x5a\x75\x00\xb3\x6c\x5f\xd7\x0e\x67\x04\x8a\x7a\x32\x9d\x79\x04\x0d\x8f\xf8\x64\x93\x27\xf0\x74\xcb\x98\x14\x5e\x95\xea\xce\xde\x44\x60\x8c\xbd\x3b\x2c\x39\x1f\xa0\xd1\x36\x1b\x49\x65\xb7\x6e\x99\xc6\x36\x21\xb2\x4d\x88\xbd\x4d\xc4\x76\x5f\xa3\x56\x0d\xf3\xeb\x5f\xe8\xb6\x53\xec\x3c\xfa\x83\x3d\xbc\x3b\xf4\x45\xfb\xd4\x7b\xd3\xdb\x14\x5b\x35\x1e\x11\x6d\xc6\x8b\x12\xec\xb2\x2f\xbb\xbc\x2c\x4d\x4d\xc4\x76\x70\xe2\x3c\xd2\xb6\xe4\x31\xaa\x80\x8c\x6d\xbc\x1b\xe9\x29\x0a\xac\x9d\x2e\x5e\x93\x3b\xdf\x71\x9e\x92\x3a\xf3\xb4\xd2\x34\x6c\x55\x25\x48\xc3\x5f\xd0\x76\x63\xb2\xe3\xeb\x2a\xb6\xb6\x91\xf1\x33\x79\x2c\xf0\x12\xa8\x63\xd9\xd1\x00\x96\x1b\x19\x2d\xc0\x65\x34\x97\x01\x2c\xd6\x92\xab\x86\xdd\x49\x92\xe1\x34\xf3\xee\xd9\xe6\xda\x27\x98\x6f\x73\xfc\x42\xdf\x21\x71\x4d\x7d\xe3\x36\xb9\x89\xe9\x93\x9f\xae\x6e\x86\x55\xb0\x16\x6d\x94\xe8\x97\x05\x85\x80\x06\x13\xc7\xb5\xfc\x12\x40\xbc\x87\xdb\x97\x92\x6d\x7b\xdb\x5d\x79\xe7\xa0\xe1\xde\x35\x11\xa9\x1b\x99\x14\x69\x1a\x8f\xc2\xcc\xd2\x82\xa7\x0d\x2d\x78\x2e\xb6\x37\x27\x6c\x04\x3b\xca\xdc\x7b\xc3\x38\xe7\x64\xba\xe6\x60\xdd\x38\x33\x70\xdb\x72\x1e\x2e\x33\x87\xa0\x8e\xb1\x63\x55\x29\xc0\xb1\x50\xc5\x8f\x50\x27\xbe\xae\x96\x02\xb7\xbb\xa0\x5f\xd6\xf3\xbb\x29\xcc\xe6\x1a\xb3\xbb\x3d\xbc\x08\xb3\x10\xb6\xdc\x60\x7e\x7c\x43\x3a\x8c\x22\x22\x9a\x32\x75\x9d\x8f\x7a\x2e\xe1\x84\x48\xda\xb5\x6e\x00\xf3\xec\xc1\xc3\x71\xde\x49\x3e\xe9\xcc\xa3\x55\x94\xe4\xa2\xd7\x30\x1b\x0a\x02\xa7\xa4\xf7\xbc\x6b\xc0\x5d\x94\x58\x1b\x2a\x5a\xa0\x12\xf3\x6a\x30\xec\xbb\x1f\xa8\xa2\xda\x93\x20\x47\xa4\x93\x2f\xc2\x31\x9d\x57\x7b\x08\x6b\xe8\x39\x34\x75\xfd\x40\x7c\x91\x11\x80\x35\x41\x5e\x9c\x21\x3c\xce\x82\xc4\x3b\xd8\xd5\x4c\x6f\x97\x99\x95\x27\x44\x37\x8f\xad\x33\x1b\x8e\xb3\x4e\x58\xc7\x7c\x4e\x4a\x38\x86\x55\x3b\x4a\x70\xa3\xa5\x5d\x12\xd0\x08\xe2\x01\xd3\xf4\xe6\x64\x9e\x02\x7b\xa1\x76\x6f\x95\x20\x86\xac\x31\xc9\x82\x65\xe6\xd5\x66\xc5\x22\x2c\xae\x1c\x7c\x3f\xf1\x9d\xb3\xde\x6e\x6b\xf7\xe4\xb0\xb3\xff\xac\xb5\xdb\xda\x6d\xf1\x1f\xbd\xdd\x7c\x9f\xfe\xea\x75\xe5\xff\x3b\xfc\xc5\x4e\xaf\x7b\xd1\x7b\xd6\x39\xd8\x83\x60\xad\xdd\x4f\xf3\x83\x56\x6f\xaf\x73\xf0\xed\x9b\xde\x41\xe7\xe0\xdb\x56\xef\x19\x7d\xdd\xdb\xeb\xec\xf7\x5a\xcf\xe9\x3f\xbd\x67\xad\x67\x2d\xfe\xad\x0b\xff\xee\xb6\x9e\xb1\x4f\xf0\x0f\x0b\xcf\xbe\x40\xa8\x67\x34\x0a\x8b\x0a\xa9\xd0\xcf\x3c\x85\x4f\x4e\x89\xb4\x06\x9f\x66\x9a\x8f\xeb\x8b\x70\x7c\x4d\xfb\x91\xc8\x5b\xcf\xf5\x5a\x9c\xf7\x88\x37\xd0\x26\x8b\x6c\x3b\xdb\x90\x9b\xb0\x08\xb3\x87\x2e\xfb\xe8\x52\x11\x32\x42\x27\x01\xea\x36\x56\x57\x74\xb9\x06\xea\xc6\x00\xaf\xd5\xed\x20\x40\xbc\x41\xf1\x5e\x8f\xd3\xc4\x30\x28\xb5\x9a\x90\x46\x34\xd4\x3c\xe0\x6b\x25\xb7\x18\x85\xa5\x0a\xec\x45\xc5\xd9\x2f\x98\x83\x28\x53\xd2\x3b\xf1\xf4\xeb\x02\x10\x71\x01\x10\xee\x56\x95\x70\xa5\x00\xe1\x56\xe0\xac\x2a\x2c\x41\xae\x55\x98\x53\xad\x16\xa7\xf8\xdc\x62\x04\x02\x4d\xd5\xec\x59\x29\x1a\x49\x03\x8e\x33\xae\x1a\x65\x2b\x98\x88\x72\x11\x7b\x03\xf5\xe5\x26\x22\xd1\xf8\x1a\x7e\xf1\x7e\x35\xad\x50\x79\x3d\x05\xec\x9c\x61\x03\x72\x52\x35\xf0\x00\x5c\x90\x13\x3a\xcb\x4c\x38\x10\x2a\xd8\x98\xcf\x45\x38\x63\x08\x0e\x08\x8f\x5c\x77\x04\x0b\xfd\x65\xd0\xf6\xda\xbd\x20\x08\xe2\xf5\xba\x7d\x83\xd6\xeb\x18\xbf\x0b\x14\xa8\xdc\x2d\x7e\x1f\x2c\xd7\x6b\xef\xb2\x1f\x17\x3e\x2c\x3d\x08\xbf\x0d\xde\xd3\xf0\x45\xff\x5e\xdd\x56\xc2\xa7\xd2\xbf\x2f\xf1\x07\xc9\xeb\x3f\x62\x23\xef\x2c\x30\xee\x1e\x75\xc0\xbb\xb1\xeb\xaa\x67\x40\x21\x8e\x06\x5a\xdb\x9d\x34\x23\xe1\x8d\x11\x1a\xfa\x46\xe0\x73\x8e\x8e\xfd\x40\x24\x84\xdf\xb9\x6e\xa4\x0d\x53\x40\x96\x42\x47\x1f\x82\x09\xdd\xd7\x56\xac\x5f\x26\xa8\x6a\x84\x32\x69\x34\x06\x98\xe8\xca\x03\x17\x65\xda\x6c\x38\x43\x52\x0f\xfb\x58\xd6\xd1\x59\x27\xd9\x06\x92\xb0\xc6\x64\x60\xf6\x1f\xb3\x16\x4f\x2c\xe5\x87\x43\xbd\xe3\xa0\x86\xa9\xd0\x9c\x15\x97\x11\x49\xad\x32\xd0\x6c\xec\x2b\x34\x19\xae\x74\x64\x24\x26\xcd\x43\x1d\x20\x8c\x81\x5e\xb3\x62\xf3\xd5\xe0\xca\x52\xfa\x2b\x5a\xfa\xd7\xd5\xd2\x5f\x6d\x28\x3d\x48\x94\x2b\x7b\xd9\x23\xd1\xd9\x96\x92\x47\x0f\x8f\x36\x64\x3b\xc0\x7f\x6f\x31\x5a\xbd\x5c\xaf\x47\x7d\x87\xa9\xe1\x8e\xd8\xb3\x3e\x74\x11\x5f\x29\xd2\x20\xda\x88\x03\x49\xcb\x83\x2f\xa1\xe4\x52\x12\x3d\x50\x7c\x3a\xe3\xc5\x7c\xd9\x22\xf8\xd0\x28\xd1\x35\x2d\x91\xc4\xfa\x57\xaa\xbe\x7a\xc7\x41\xf3\x34\x2d\x5f\x7d\x93\x70\x74\xe5\x60\x3c\x1c\xe2\x05\xcc\x3f\xb1\x2e\x40\xd7\x48\x84\x37\xa8\x94\xb6\x04\x8d\xe4\x5c\x0d\x35\x5a\x3c\x05\x41\xd8\x6e\x2f\xd6\x6b\xde\xc4\xd2\x8a\x02\x3a\xa0\xab\x30\x52\xd8\x6c\xb9\x69\x30\x1a\x96\xba\xea\x65\x98\xcd\x48\x01\xeb\x6a\x01\x3f\x5d\x17\x56\x63\x66\xdf\x56\x31\x34\x9a\xb9\xee\x0c\xe4\x27\x97\xd0\x5b\x27\xe9\x8d\x78\xb2\x7d\x2a\x7f\x7d\xe7\x34\x1f\x87\x0b\xa2\x99\x35\x9d\x28\x6d\x5b\xfe\xec\x8c\xe2\x65\xe6\x21\x84\xef\x5c\xf7\x0e\xb2\xe5\xa8\x24\x6f\xf1\x39\xc2\xc7\xeb\xf5\xeb\x2d\xe0\x37\x2b\x03\x8f\xad\xb9\xd0\xfc\xf0\x93\xc9\xc1\x12\xcf\x05\x0a\xf3\xd5\x06\x9d\x96\x59\x5a\x3b\xb1\xa4\x21\x53\x64\x08\x77\x0b\xd2\x57\x8f\xb3\x8c\xdc\x0d\xf6\xba\xdd\xa1\x5f\x79\xf7\xac\xdb\x1d\x62\x69\x52\x52\x45\x19\x34\x28\x15\x3a\xbb\x87\x62\xc7\xcb\x9d\xda\xa6\x69\x52\xbc\x0a\xe7\x51\x7c\x67\xc2\xff\xa9\xf7\x0f\xe1\x03\xee\x21\x3b\x8e\xdc\xa3\x98\x11\xb9\xc7\xf5\xde\x6e\x0d\xbe\x8e\x5b\x10\x66\x61\x5e\x5c\x92\x15\x50\xf7\x55\x21\x0f\x0b\x13\x86\xb4\x77\x68\xb3\x54\x79\x10\x66\x74\x60\xc1\x19\xd5\x29\x26\x86\x08\x8f\x97\x59\x9e\x66\xbe\x9c\xcf\xca\xfb\x0e\xd3\x66\x7e\x49\xc6\x29\x37\xce\xe3\x2c\x4a\x82\x5d\x82\x3d\x89\x2d\x45\x17\x0b\x1b\x65\x0e\xc5\x3a\x8f\x26\x93\x18\x82\x5b\xcd\x72\x4c\xa8\x50\x01\xd2\xd3\x39\xc0\x60\x74\x43\x32\x70\x62\xcd\x05\xc4\x1d\x76\xdc\xd6\x13\xae\x74\x19\x78\x9f\x07\xd8\x00\xf1\x3c\xe4\xe6\x3e\xbb\xfb\xa2\xf5\x77\xf7\x79\xeb\x6f\x3b\x1a\x9f\x59\x46\x23\x1d\xa1\x0f\x0d\x19\x38\xf8\x97\xa5\x3c\xd1\xa0\x42\x9d\x66\xac\xd0\xb1\x36\x0a\x6a\x43\xa0\x1e\x7c\x12\x66\xd7\xb5\x5c\xa4\xfc\xb4\xe4\xa3\xe0\x38\xb7\xcc\x49\x45\xa8\xe6\x05\x02\xc0\x6c\xfc\x7d\xb3\xf1\xf7\xb1\xd8\x7e\x8a\xc6\xef\x3d\x7f\xa8\xd5\xba\xa8\x34\x10\x3c\x65\xaf\x55\xd1\x56\x1f\x6e\x1c\x00\x1c\xdd\xa6\xa1\xeb\x30\xa8\xdb\xb4\x87\x35\x79\x7b\xfb\x9a\xf8\xa4\x72\xb9\xf2\xef\x97\x39\xc9\xd8\x59\x9a\x98\x3e\x5b\x51\x9f\xca\x39\xca\xe7\x86\x23\x81\x59\x71\xcb\xf5\xc1\x17\x6c\x23\x42\xeb\x08\x79\x05\xee\x74\x9f\xc3\x08\xf5\x99\x9b\x0b\x8d\xa0\x68\x6f\x04\x77\xce\xa0\x37\x2c\xb5\x12\x9f\x18\x5d\xf0\xe8\x4c\x1b\xfa\x08\x4a\x52\xcd\x45\xeb\x8d\x2f\xc8\xa7\xd2\x5d\x3c\x27\xa9\x21\x40\xda\x8f\x69\x2f\x2d\x6e\xad\x2d\xbe\xa8\x09\x76\xeb\x69\x9b\x2d\xf0\x85\x15\x87\xf4\x35\x46\xa0\x8d\x50\xcc\x1b\xf8\x82\x1a\xa5\x66\x8d\xc8\x77\x77\x0f\x39\x9c\xde\x77\xf7\xe0\x00\xb7\xd4\x3f\xec\x1b\xc2\xce\x13\xd9\xe5\xb2\x87\xf5\x57\x50\x65\xdc\x7a\x22\x9b\x65\x9b\x56\x68\xd6\x0b\x36\x63\x0f\x37\x2f\x29\xfb\x0f\x48\xbd\x5d\xf6\x1d\xf6\xea\xb6\x88\x72\x23\x61\x8d\xa6\x6d\xfd\x4d\x00\xea\x83\xea\x77\x33\x0d\x16\x68\x4f\xeb\xd5\xed\x80\xa8\xb7\xe2\x82\xd2\x63\xfc\x27\xfa\xe9\x11\x58\xd7\x5a\x85\xb7\xc7\xad\xde\xaa\xd2\x66\x9c\xff\x44\xb5\x1f\x05\x8e\x5d\x96\x98\x0d\x41\xba\x86\x68\x83\x51\x3d\x9a\x12\xc9\x78\xab\x35\x55\x89\xe9\x80\x94\x06\x63\x5f\xaa\x05\x6d\xd0\xbd\x58\x4e\xbc\x90\x16\x55\x60\x83\xe6\xc0\xa2\x9e\x58\xf0\xd5\x35\xf8\x75\x19\xa4\x36\x10\xb4\x40\x1c\xd5\xbb\x6e\xe8\x4d\x65\xc3\xb9\x7c\x4b\xe2\x38\x5a\xe4\x51\x2e\x15\x58\x76\xfe\xbd\x8b\x75\x0f\x63\xfa\x6c\xb5\x0f\x57\x3b\x22\x89\x39\x0e\xf1\x9f\x9b\xd1\x9f\x73\x11\x0f\xd3\xd9\xbf\xdf\x72\xad\x87\x77\x89\xd4\x5f\x77\x85\x4a\xbb\x5b\x57\x03\x58\x23\xfa\x4e\xb7\x75\xb0\x58\xb5\xba\xad\x9d\xc3\xc5\xca\xd1\x51\xdb\xc7\xf5\xe1\x98\xe0\xce\xbe\x5c\x7c\x94\xa0\x91\xaa\x57\x4f\xe8\xd0\x80\xe1\xa1\x3a\x69\x5f\xef\x3f\xda\x63\x95\x83\xb8\x4a\xbf\x3d\x30\xf3\x0d\x75\xb4\xf3\x0c\x19\x1a\x8d\xd4\x50\xb6\xd2\xe6\x6a\x25\xa9\x0d\x8f\x07\xa7\xe3\xe7\x95\xa6\x41\xf9\xd3\xcb\x63\x9c\x3a\x3e\xae\x85\xd8\x3a\xfe\xd8\x96\xa1\xb1\x9a\x4b\xf0\xf8\x96\x79\x5c\x29\xcc\x78\x65\x05\x53\xfd\xe4\x2a\x02\x1a\xfe\x45\x86\xf0\x3c\x0b\x12\x6f\x6f\x0f\xe1\x1b\xf8\xd1\x43\x78\x04\x3f\xf6\xd9\xe1\xdf\x6c\xbb\x1b\x0b\x03\xd0\x7c\x93\x83\x52\x1c\x87\x8b\x9c\x4c\x38\x69\xb9\xe9\xa6\xd4\x5d\xac\x84\x9f\x92\xba\xab\xa8\xf0\xd4\x44\x37\x15\x8e\x9a\x1a\x6c\xba\x8d\xb2\x26\x4a\x74\x1f\x57\xce\xf2\x27\x90\x31\x6f\x4c\xef\xd7\x91\x42\xc6\x9c\xe9\xc8\x98\x77\x06\x32\xe6\x4a\xc2\xa6\xdf\x6a\xb0\xe9\x86\x0f\x2b\x70\x2a\xe6\x45\x48\xfb\x61\xc2\xc9\xfd\x6c\xe0\x96\x06\xbb\xdf\x9f\x05\xb0\xfb\x6d\x42\x4f\x6f\xa4\xc5\xd1\x9b\xd6\xee\x52\xf5\x3f\x01\x71\xfd\x82\xb9\xc4\x7e\xd4\xd1\x20\x2f\xab\x57\x24\xef\xf4\xaf\xef\x83\x1a\x1c\x6d\xae\xc1\xd1\xe6\x1c\x8e\x36\x6f\x04\x1d\x7b\x00\xf0\xf9\x63\x15\xf0\x19\x46\xfe\xdb\xe0\xa2\x01\xf8\x7d\x8a\x3f\x54\xcb\x7b\x16\x1c\x13\xaf\xc0\x6f\xfb\x1f\x04\x06\x17\x3e\x7e\x1c\xd4\xfb\xdb\xfe\xe0\xc3\xd7\x85\x7a\x7f\x1d\x1c\xd7\x30\xde\x39\x38\x07\x5b\x61\x82\xf7\x9a\xcb\x2a\x42\xf8\x53\x3d\x02\x9b\xdf\x97\xd2\xfc\xe6\xd2\x8a\x2f\xe2\x77\x0d\xc4\xf8\x95\x44\xe6\x3a\xad\x21\xc6\x4b\x64\x73\x80\x75\x10\x70\xd3\xa7\x02\xf3\xe0\x62\x0b\x58\xf3\x44\xf1\x7c\xd7\x1d\x57\x35\x80\x93\x14\x3b\xf3\xdc\x41\x58\xf1\x41\xa6\x0c\x2a\x70\x53\x6c\x66\x5a\xa0\x06\x5a\xd6\xcf\x34\x14\xc0\x8c\x25\x79\x54\x69\x47\x15\x20\xe1\xd0\x80\x0c\x74\x80\xb7\xeb\x9b\x07\x3b\x42\x50\xae\xb9\xee\x5c\xc6\x7a\x69\xc6\x12\x67\xb9\x0f\xf7\x45\x73\xe9\x0a\x5e\x3a\x71\xa9\x88\x10\x7e\x15\x1c\x7b\x33\x84\x5f\x7c\x6e\x66\x06\xaa\xbd\xb5\xe3\x19\xaa\xfd\xc6\x7e\xcf\xb6\xea\xf7\x62\xcb\x7e\xcf\x6a\xfd\x9e\x3d\xbe\xdf\x93\x7e\xe2\x1b\xdd\x6a\xeb\xf7\xf7\xf2\x9c\xbf\xd9\x67\xfd\xbc\x46\x4a\x29\x7d\xce\xfd\xd7\x1a\xdc\xdd\x1b\xdd\x13\xfd\x93\xc0\xbe\x7b\xa9\xb0\xef\x5e\x69\xd8\x77\x2f\xb6\x81\x83\x7f\x6b\xc2\xc1\x9f\xba\xae\x92\x74\x26\x1c\xfc\xbb\x0a\x1c\xbc\x40\xb0\x57\xe2\xac\x8e\x10\x7f\xca\x70\xd1\x4e\x4b\x7c\xb2\x01\x21\xde\x68\x8a\xe5\x56\x0c\x37\x91\x06\x95\x1d\xe2\x7b\xc1\xbb\x12\x75\xf8\x2f\xcc\xf0\xe1\xfd\xf6\xc2\x75\x41\x65\x08\x82\xe0\xbd\xeb\x46\x1d\xb6\xbf\x28\x07\x64\x88\x6c\xb4\xe5\xca\xbd\xf3\x3d\x50\x7d\x66\x64\xea\x9f\x81\xad\xcb\x83\x96\x5b\x51\x87\x6e\x37\x16\x24\x83\x48\x97\x5b\xd8\x7a\xc9\x18\xaf\x93\x84\x64\x25\x4e\x90\x84\xa5\x9f\x65\x0d\xb0\xf4\x77\x0f\xdb\x26\x29\xc0\x46\xb1\x49\xe8\x5a\x3c\x69\x1f\xa4\x3c\x63\x71\x1d\x9d\xd5\xc6\xe4\x7e\xac\xb3\xac\x95\x98\xa5\xee\x1b\xf4\xef\x12\x93\x9f\xd7\xd6\xbf\x37\x79\x9b\xe4\x07\x68\x06\x93\xe2\xa9\xaa\x93\x72\xfd\x85\xea\xa5\xb3\x8c\x2d\xc1\xab\x4c\x7a\xdd\x09\x12\xa5\xfb\x52\x33\xc2\xb9\xcd\x94\xa8\x62\xf8\xc7\x59\x1a\xc7\x64\x02\x80\x42\xfc\x1a\x05\x67\x81\x47\x3a\x34\x1b\xc0\x40\x0d\x0b\x82\xe5\xda\x2d\x15\xc5\x02\x49\x15\x80\x2e\xaf\xf4\x3b\x33\xa2\x4d\x10\x0e\xc1\x66\x1b\x73\x63\x6e\x6e\x1e\x9a\xf5\x0b\x3f\xc4\x1b\xe0\xa9\xb2\xf5\x3a\x96\x58\x54\x43\xb8\xfe\x3f\xdd\x42\x95\x66\xfa\xb3\x50\xa6\x43\x4d\x99\x8e\x0d\x65\x3a\x57\x15\x3c\x5d\x2d\xc2\x64\x42\x26\x56\x92\x7b\x79\x7d\x3b\x51\x1f\x97\xae\xcb\xf8\xb3\x89\x88\xc9\xb1\x60\xae\xc2\x64\xc6\xc0\x60\x38\x6d\xbe\x0d\x2a\xce\xa6\xc1\x1a\xc0\x30\x77\x19\x07\x86\xa9\x22\xae\x6f\xe6\x04\xb2\x6b\xb5\x95\x3a\x9a\x46\x3a\x44\xbd\x15\xa5\xd7\x09\xf6\xed\x68\xe5\x16\x60\xf2\x55\x70\x9b\x31\x30\x54\x36\x7c\xfc\x29\x16\x0e\x04\x63\xcc\x06\x28\x14\x20\xa7\x91\xc2\x84\xc4\x0e\x86\x81\xe4\xab\x12\x94\x08\xdf\x9a\xaa\xda\x8a\xaa\x6a\xa7\xc1\x2d\x1d\x3b\xd7\xc1\xed\xa0\x37\x14\x74\x41\xf6\x01\x73\xed\xb5\x4f\x25\x2e\x46\xfb\x14\x46\xce\x29\x5e\xe0\x6b\x6e\x41\x24\xe1\x58\x8a\x14\x5c\xcd\xbc\x88\xaa\xd2\x5e\x12\x9c\x08\xa9\x3a\x07\x39\x97\xa0\xf5\x9a\xbf\xb8\xa9\xbe\x18\xc1\x0b\x44\x95\xef\x0b\x5a\xae\xcb\xe0\xa2\x93\xc7\xd1\x98\x78\x3d\xa9\x72\x9f\x91\x79\x6a\x51\x9e\xef\x45\x55\xfd\x53\x75\xa7\x32\xc1\x05\xb8\x97\xfa\xe7\x25\x2b\xee\x04\x9f\x37\x33\x6e\x56\x31\x55\x1b\xe4\x7f\xc8\x4c\x2d\x62\x7c\xea\xba\xa1\x1a\xa3\x13\xfa\x24\x87\x73\x7b\x4e\x1f\x39\x29\x3f\x47\x40\x10\x8c\xd9\x73\x40\x76\xaa\xd9\x28\x67\x9d\x77\x59\x0a\x5e\xa3\xc2\x0b\xe5\x1d\x30\x30\xd7\x2c\x83\x6b\x0b\xf6\x69\x65\xf5\x2b\xf1\xac\x71\xd1\x60\x86\x0f\x70\x2c\x15\x93\xc9\xe8\xce\xf1\x3f\x72\x2b\x97\x68\x82\xa3\x89\x78\x1a\xb0\x70\x7c\xcc\xe5\xce\x90\x41\x4c\x38\x19\x99\x45\x29\x15\xa8\x97\x1c\x2c\xf6\xfa\xc1\xcb\xfd\xed\xd9\x2a\xcd\xbb\x79\x85\x33\x9b\x91\x18\xd0\x0a\xb7\xb9\xc5\x66\x47\x4f\xce\x90\xae\x9b\x8e\xeb\x8f\xc8\x34\xcd\x88\x63\x47\xad\xe5\x3c\xc3\x45\xba\xf0\x77\x7a\x55\x96\xe1\x9e\xb4\x8b\xfd\x5f\x8e\xf3\xbf\xb0\x62\x55\x6b\xbe\x02\xe4\x4e\xbf\x5b\x94\x52\xd2\x15\xd5\x6f\xdd\x87\x80\x4d\xe0\xb8\x3e\xc0\x97\xee\x80\x04\x72\xe0\xa2\x47\xd6\x45\xae\x61\x82\xd5\xcd\x71\x9f\xc8\x99\x2e\xae\x01\x7c\xa7\x77\xb8\x58\xb5\xba\x4e\x3d\x2d\x45\x66\xd7\x85\x9c\xe2\xb0\xf6\x91\x63\x65\xb0\xef\x32\x63\xc9\x90\x63\xe6\xd9\x7a\xda\x72\xb7\x28\xe2\x06\xc2\xcf\x46\x72\xcf\x17\x32\x60\x59\x62\x3e\x9d\x24\xa3\x33\x33\x78\xe8\xd6\x2b\x28\xe9\x90\xdf\x90\x69\xc1\x83\xc1\xcd\xe5\x82\x74\xf4\xb8\x58\x06\x84\x93\xcb\x0d\x21\xeb\xed\xa4\x73\x41\x6f\x97\x0d\x0b\xfb\x50\x4e\xd8\xf9\xdf\x39\xd3\xbd\xf2\x96\xb7\x33\xcf\x77\xa2\x39\xd9\x01\x63\x12\x1f\x28\x6a\x51\x73\xde\xdd\xc6\x8c\xba\x74\xbf\x2d\x25\xe4\xbd\x71\xed\x6c\xaa\x39\x95\x85\xa4\x44\xde\x69\x86\xf0\xf9\x96\xa7\x6c\x5f\x46\x48\xf9\xf5\xd8\x27\xab\x54\x92\x27\x0f\xab\xae\x4c\xe8\x54\x68\x3d\xeb\x66\xf0\x3d\xbc\x8b\x77\xd1\xc6\x46\x7b\x49\x8a\x30\x8a\x73\xda\x76\xe7\x19\xc2\x17\x5f\xf5\x84\x92\x75\x22\x58\x70\x52\x1d\x4b\x71\x87\x32\x1d\x86\x11\x2e\xbe\x88\x97\x19\x67\x5b\x64\x56\xd0\x8c\x6a\x11\x60\xa9\x7e\x63\x0a\x33\x9e\x7e\x86\xb6\xa3\x32\x77\xb0\x53\xc9\x1a\x94\x1c\x9a\x71\xc5\x1c\x59\xcf\x94\x2a\x33\x0b\x5d\x7d\x6d\xf7\x10\xbe\x0a\x16\x74\xad\x9f\x07\x0b\xaa\x83\xdc\x98\x9c\xa4\xab\x0c\x51\xed\x4d\x2d\xac\x33\xa5\xf2\x8d\x5c\x77\x84\xef\x82\x1b\xb5\x06\xaf\x82\x9b\x0e\x5b\xf1\xb7\x65\xd5\x36\x08\xa9\x7b\x62\x5a\x28\xc2\x6a\x39\x4f\x66\xb8\x62\xa3\xcc\x4d\x08\x95\xdc\xbd\x7b\xd0\x36\x73\x66\x18\x2c\xde\xd1\x27\x59\x74\x00\x50\x82\xd2\x50\x7d\xc1\x6c\x36\xc3\x26\x70\xee\xb5\xbb\x8a\x53\x0e\xb3\x36\xaf\x86\xe8\x21\x3c\x76\xdd\x31\x0f\x51\x71\xda\x22\xe8\x7e\xe5\xba\x2b\x8f\x20\x4c\x55\x6d\x69\x06\x58\x94\x78\xba\xc5\x66\xd3\xb2\x19\xa6\x3a\xb6\x51\x1d\xba\x47\x47\x38\xae\xe3\x45\xac\xb6\xd3\xb1\x22\x7d\x9c\x9b\x29\x63\x0d\x5c\xa4\xda\x25\x0a\xff\x6a\x19\xc7\x4c\x63\x61\x0e\x42\xd0\x55\x7c\x33\x08\x4e\x3a\x39\xc2\x31\x13\x0d\x1f\xff\x36\x05\xa6\x22\x4b\x0c\x7c\xaa\x2d\x14\x99\x28\xd9\xb9\x12\x47\xe5\x56\x2d\xc1\x22\x9d\xba\xc0\x1d\xcb\x2f\x60\xfc\x24\x2d\x3c\xb9\xea\xd2\x75\xa3\x7a\x2d\x58\xd3\x1c\x64\x11\x0f\xc1\x44\xe1\x09\x1f\x90\xdb\xac\xd8\x10\xb4\xba\xce\x0b\x85\xa1\x71\x7d\xe7\x77\xd8\x95\xd5\x89\x67\x5b\x59\xa8\x94\xa3\x52\xa5\x65\x35\xbf\xa5\xc7\x29\x88\x52\x53\xda\x15\x9a\x92\x4d\x8f\xda\xed\xc2\x57\x59\x46\x76\x47\xab\x91\xeb\x66\x29\x08\xb3\xee\x84\xcc\xd0\x16\xa7\x1b\x1a\xed\x64\xb1\x91\xa0\xdd\x24\xb3\xae\x94\xae\x9e\x7f\xef\x39\x2b\x41\xf5\x36\xcd\x5c\x9d\x2e\x96\xf3\x39\x83\x91\xf1\x2e\x32\x84\x2f\xb7\x5c\x9d\x48\x1c\xf3\x3b\x31\x1d\x0e\xb6\xdf\x7b\xde\xf5\x1f\x40\x84\x55\xeb\x17\xbb\x38\x8b\x73\xf3\xce\x6c\x97\xdf\x97\xa9\x2b\x35\x03\x0c\xd6\x59\xc6\x0e\x87\x83\xe5\xa3\x1c\x5f\xa9\xef\x8b\xfe\xbe\xbf\x80\xfb\x32\x76\xd9\x65\xc3\xf3\x93\x45\xdf\x8e\xea\x2c\xce\x2b\x77\x53\x3c\x5b\x79\x87\xd4\xac\xa8\x4c\xb7\x12\x6e\x31\x5b\x0f\x72\xb9\x2d\xb4\x9c\x05\xb2\xa1\xb7\x73\xf5\xcd\x6e\x89\xe7\xa8\xc4\x37\x54\x30\x37\x23\x9e\xc2\x55\x4d\xdb\x82\xb9\x8a\x34\xd7\xe5\x23\x71\x10\xc5\xbd\x1a\xd2\x38\x5f\xaf\x7b\x70\x16\xc5\xde\x64\xe9\x2d\x7d\x73\x64\x27\xd0\x24\xd8\x46\x9a\xc9\x4f\xcc\xe4\x49\x74\xaf\xdb\xfd\x66\xf9\xcf\x02\x3b\xff\x70\x10\x36\x0e\xed\x82\x20\x88\x04\xb2\x6e\xf4\xcf\xe4\xe9\x95\x94\x5f\x50\x4b\x62\xb0\x65\x96\xe0\x82\x49\xe5\xf3\xbb\xcc\xc6\x46\x62\x91\x00\xbf\x67\xe1\xc2\x77\x98\xb5\x73\x8d\x8f\x24\x8e\x38\x2a\x6a\xdd\x1a\xf9\x21\xa4\x4c\x1d\x18\xf3\x87\x2c\x82\x33\x6d\x3a\x7b\x2e\x33\x84\xdf\x6f\x40\x15\x39\x22\x8c\xf4\x6c\xbe\x88\x49\x41\x00\x66\x94\xd1\x63\x8a\xe3\xf9\xef\x89\xc9\x67\xa6\x93\x70\x7d\x63\xff\xc6\xc6\x71\xdf\xf3\xa4\xb6\x4d\x4b\x83\x38\x9d\x0f\x07\x40\x91\x4b\xec\xcf\x30\xdc\x8a\x4e\x34\x9f\xbd\x5a\xc6\x31\x24\xdc\xc9\x17\x71\x54\x00\x21\x26\x42\xd8\xcb\xcc\x74\xc2\xc9\x84\x27\x92\x35\x24\xc2\x4a\x60\xa4\x82\x7c\xcf\x4b\x37\x94\x27\xdd\x3e\x29\xec\x45\x4d\x05\x8a\xb6\xae\x15\xe2\x24\x63\x6f\xbf\xa2\xe2\xed\x71\xc9\xa5\x89\x29\xd4\x00\x62\xea\x11\x98\x48\xf8\xb3\xae\xc8\xab\xb2\x87\xa6\xe4\x0c\x11\xc2\xcb\xca\x8d\xee\x43\x1c\xc2\xed\xca\x7d\x1e\x8c\x3f\x31\x18\xfb\xef\x99\x3b\xae\x4f\x2c\x0c\x52\x71\x1a\x4e\x1c\x03\x31\x81\x87\x86\x19\xe9\x2d\x15\xea\xbb\xa2\xc9\xb2\x14\xc0\x4a\x62\xf5\x3e\xab\xc6\xff\x8f\x11\x5a\x21\x3c\x88\x2c\x54\xc9\xf9\x96\xfa\x28\x53\xe1\xe5\xbe\x72\xbc\xd5\xad\x4c\x11\x31\xbc\x37\x43\x70\x27\xd8\xb2\xfd\x6c\x59\x64\x77\xdf\x89\xe6\x33\x66\x80\x57\xdc\x2d\xc8\x7a\x9d\x17\x30\x8e\x5e\xcf\xc3\x19\x5d\x85\x1a\x60\xad\x97\x25\xf2\x05\x35\x88\x60\x74\xb2\xca\xd0\x06\x7f\x0b\xd3\x69\x9c\x56\xc1\x7e\x16\x27\x45\xf0\x28\x4e\xe9\x5e\x4f\x08\x7b\xb8\x3c\xa9\xdf\xfa\x94\xd8\x98\xf6\xea\x46\x87\x85\xd7\xd4\x19\xf8\x19\x87\x05\xf9\xc3\xdb\x39\xe8\xfe\x03\x39\xd8\x96\x3d\xe3\xbf\x3b\xe8\xfe\x43\xa5\xcc\x58\xa4\x8c\x4b\x1c\x6b\x54\x5b\x5e\x7f\x8a\xbc\x80\x79\x0f\x92\xb5\x09\xfe\xcb\x28\x86\x1b\xa0\xb7\x19\xc2\x67\x5f\x76\x28\x42\x3a\x59\x18\xe5\x64\x22\x79\x81\xdb\x41\x10\xba\x6e\x88\xf3\x6d\x31\x89\x59\xfc\x0d\xfa\xc8\x96\x27\xda\xea\xd4\x44\xd1\x88\xc5\xfd\xe7\x7e\x4f\x8c\xf6\x9c\x0d\xa3\x63\xeb\x30\xaa\xf7\xb4\xde\x70\x27\x61\x36\xa1\x0d\x76\x96\x21\xfc\xfa\x4b\x1b\x4c\xa9\x88\x06\x97\x32\xb3\xba\x7a\x44\xc3\x8d\x35\x7b\x9f\xc6\x93\x82\xcf\x3b\x70\xe2\x4d\xf5\xc9\xda\x54\x42\xe9\xe8\x1d\xd6\x8e\x13\x4d\x8c\xe2\xdd\xfd\xb2\xd6\x8a\xdc\x6b\x8c\x36\xe6\xeb\x0c\xe1\x37\x5b\xba\xea\xc3\x3b\x46\xf7\xc4\x3c\x72\x37\x5f\xda\xa9\x26\xae\xf8\xea\xd3\x26\x36\xee\xec\x14\xd0\xa4\xf5\xf2\x2e\x5f\x8e\xae\x48\x38\x21\x19\x53\xdd\xc5\x53\x05\x9e\x12\x6e\xf3\x8a\xa8\x88\x19\x09\x35\xfc\xaa\x06\xb1\xe8\xf4\x8a\xf7\xf8\x01\xf7\xfa\xba\x89\x99\x4a\x9d\x2a\xf4\xa2\x58\xfa\xef\x4a\xfe\x60\x33\x56\x70\xdb\xb1\x7a\xf1\x9c\x21\xc2\xa3\xe0\xea\x88\x81\x03\x8d\xd6\xeb\x11\x48\xeb\x20\x08\x6e\x8a\xf5\x7a\xb2\x5e\x7b\xa3\xc0\x02\x24\x62\x0c\x2e\x81\xd7\x12\xf5\x1d\xc0\x7a\x76\x7c\xe7\xea\xc0\xd1\xce\x93\x42\xde\x46\xda\x81\x07\x73\xcb\xac\x08\x61\xf0\xba\x1c\x71\xbf\xe8\x59\x30\x3d\x32\x90\x8b\x66\xeb\xf5\xac\x5a\xb8\xd9\xe7\x14\x0e\xe0\xb7\xcd\xf2\xa9\xee\xe6\x86\xd6\x05\x59\x15\xca\xe1\x6c\x8b\x92\x2f\x10\x9e\x59\x3c\xa3\xc7\x8f\xba\x94\xd3\xc0\xc6\x71\x54\x3f\x7e\xaa\xad\xd1\x21\x9f\x15\x25\x9d\xc2\x5b\x84\xe6\x47\x10\x25\x1e\xe1\x19\xc2\xc9\x76\x39\xc0\x60\x05\x1b\x0b\xb0\x9b\xda\x66\x43\x63\xf3\x1e\x55\xd2\x43\x19\xfc\xd3\xd0\xbe\xd3\x6d\x75\x5b\xdc\x42\x4b\xb3\xc3\x86\x70\x90\x77\x3d\x1c\xa4\x7f\x41\xe2\x29\xcb\x70\x27\x2f\xc2\xac\x10\xd1\x2f\xd3\x85\xbf\xf3\xdc\x34\xbc\x7f\x5e\xc1\x89\xf1\x9d\x5e\xab\xd7\xe2\xd7\x8d\x30\x38\x01\xd7\x5c\x8c\x02\xff\xbe\x26\xbf\x7e\x64\x13\xad\x44\xde\x9b\x0c\xe1\x57\x59\x70\x7f\xd0\xf5\x9d\xff\x1f\x19\x93\xe9\xb4\xe7\xe0\x5e\x97\x3e\x8d\xa7\x93\xe7\x93\xb1\x83\x77\xe1\x69\xd4\x1d\x91\xf1\x81\x83\xf7\xe0\xe9\xdb\x6e\xb8\x1f\x12\x07\xef\xc3\xd3\xb3\xe7\xdf\x76\xbf\x1d\x3b\xf8\x00\x9e\x0e\xbb\xcf\x26\xcf\x47\x0e\x3e\x84\xa7\x83\xfd\x43\xf2\x2c\x74\xf0\x33\x78\xda\x3f\x38\x08\x0f\xf7\x1d\xfc\x1c\x9e\xf6\x9e\xed\x3f\xdb\x9f\x3a\xf8\x5b\x78\xda\x3d\xdc\xdb\xdd\x7b\xee\xe0\x63\x33\xfb\x63\x33\xff\x63\x33\xcb\x63\x23\xdd\x12\xbf\x10\x75\x99\x7e\x3b\x1d\x91\x67\xa2\x2e\xd3\xee\x74\x7f\xbc\x27\xea\x42\x0e\x09\xa1\x91\x59\x5d\x26\x63\xf2\xec\xd9\x81\xa8\xcb\x64\x9f\xf4\x0e\x9e\x89\xba\x8c\x27\x93\xf1\xde\xb7\xa2\x2e\xe3\xee\x38\xdc\xdb\x13\x75\x09\xa7\xa3\xfd\xdd\x91\xa8\xcb\xb7\xe4\xdb\xc9\xee\xbe\xa8\xcb\xf3\xdd\x67\xcf\x7a\xcf\x64\x5d\xa6\xfb\xd3\xe9\xf3\x9e\xac\x0b\x21\xd3\xe9\x7e\x4f\xd6\x65\x7c\x38\x9d\x76\xbb\xb2\x2e\x21\x21\x61\xb7\xeb\x94\xf8\x77\x30\xae\x7e\x8e\xf0\x13\xf8\xf1\x0c\xe1\x5f\xbf\x7c\xd5\x8e\xd3\xac\xb2\x62\x2f\x84\xc3\x6b\x08\xab\x0e\x43\xff\x1c\x8b\x5f\x2f\x96\xd3\x29\x61\x37\x18\x02\x73\x65\xa2\xa2\x2f\xfb\x4e\x94\x4c\x48\x41\xb2\x79\x94\x84\x05\x71\xfc\xa5\xf5\x36\xa3\x69\x8f\x05\xae\xcd\x90\x8f\xf8\xcb\xf2\x33\x51\x51\x16\xcc\x0c\xf8\x2a\xb8\x2f\xf1\x3c\xb8\x1f\x85\x59\x8f\x8e\xf0\x51\x98\xed\xd2\xc1\x0d\x66\x82\x7a\x21\x82\x20\x98\xac\xd7\xce\x88\x25\x05\xd4\xd8\x91\x66\xc1\x93\xa3\xfb\x2b\x7e\xa9\x0e\x59\x26\xe9\xad\x33\x0c\x34\xd6\xe6\x1c\x61\x23\xc0\x3c\x4a\x9c\x61\xd0\xad\xbc\x0c\x57\xce\x30\xe8\x75\x99\x41\xd6\x4d\x90\xef\xd0\xdf\x82\x95\x65\xa1\x58\x59\x5c\xd7\xbb\x09\x76\x6e\x10\x9e\x77\x68\xc9\x15\x89\x72\xa0\xab\xd7\xf2\xd4\xe6\x06\x3b\xff\x40\x0e\x62\x66\x88\x40\xe9\xde\x58\x91\xb1\xa0\xb0\xf6\xc6\xeb\x75\x17\x6d\x28\xc0\x28\xd8\x19\xf1\x02\xec\x3e\x54\x80\x91\x59\x80\xaf\x75\x11\x98\x6c\x44\xbf\x88\x11\x1a\xe2\x08\xdf\x6b\xdd\xe8\x27\x1d\xed\x09\x1b\xe3\xcc\x67\x7c\xa5\xea\x2b\x6b\x22\x3f\xe9\xb0\x1f\xf8\xaf\x25\xc9\xee\xfc\xa4\x03\x7f\xcb\xc1\x64\x88\xb8\xd5\xc4\x22\x4b\x67\x19\xc9\xf3\x51\x98\x39\x25\xbe\x62\xfb\x34\x76\xdd\xa2\xb7\x73\xff\x71\x77\x2f\x49\x67\x12\xe6\x57\x64\x42\x6b\xc9\x7e\x6d\xc0\xe2\x88\x19\xf2\x88\x6f\x45\x71\x7b\x28\x9f\x51\x98\xd1\x4c\x46\x1b\x31\x59\xa0\x35\xbd\xca\xd4\xe4\xb3\x02\x5a\x84\x8d\x25\xd7\x85\xf4\x7a\xaf\x8d\xa6\xac\xf4\x01\x0d\xf0\xd2\xda\xd2\x61\xd6\x63\xb3\x95\xb5\x2f\x3b\x91\x64\x83\xbc\x44\xb8\x36\x23\x99\x11\xe8\xe7\xd4\x77\xeb\x9a\xec\x9a\x35\x31\x3a\x74\xb0\xc5\x00\x64\x89\xb0\x4a\x0d\xfd\xad\x9a\xd9\xa8\xf7\x2e\xb3\xdb\xc4\x3f\x3e\x78\x91\x55\x87\xbf\x6b\x74\xf2\x53\xee\x3e\x04\x5c\x9c\x0f\x77\x91\xe6\x02\x14\xc2\xbb\x03\x54\xe2\x24\x68\xf6\x0e\xcd\x82\x0d\x4e\x94\x0f\xdb\xfa\xd4\xed\x46\xf9\x89\xc1\x3e\x76\xfe\xf7\x9c\x4c\xa2\xb0\xb5\xc8\xa2\xa4\x10\xde\x45\xc7\x40\x7d\xe5\x3b\x64\x15\x8e\x0b\xba\x09\xdd\x0c\x0b\x90\x3c\xec\xdc\x9f\x95\x58\x1f\x96\xf7\x65\x45\x1e\xd0\xe5\x80\x0d\xcc\x87\xae\x6e\x98\x64\xd8\x74\x67\x83\xd9\x0c\xb6\x1b\x2a\x69\x06\x3b\x95\x63\x16\xe3\xac\x23\x4c\xa2\x39\x07\xe0\x78\xc2\x0a\xd6\xda\xcb\x5b\x51\x32\x8d\x92\xa8\x20\xad\x38\x4a\x08\x08\x20\x4d\x58\x58\x9a\x07\x8e\x97\x7c\x27\x0b\x27\x51\x18\xef\xcc\xe8\x5f\x98\x33\x9a\xad\x79\xab\xfb\x0f\xdc\x72\x90\xfe\xa6\x77\xf8\x0f\xdc\xd2\x2a\xdd\xda\xdf\xa5\x02\x5d\xb3\x96\x02\x6c\x07\xa7\xd7\x5d\xac\x5a\xf4\x1f\x47\xfb\x24\x69\xf3\x9d\x6e\x6b\x67\x77\x6f\xb1\x32\x0b\x69\xed\xa2\x87\x8a\x99\xd5\x8a\x99\xfd\x1d\xc5\x1c\x85\x59\xe3\x89\x53\xcd\xd2\x6c\xc4\xad\xaa\xe0\x90\xa9\xab\xdf\x14\xaa\x4b\xc1\x56\xb7\xb3\x9b\x8b\xbe\xaa\x13\xb0\x71\x12\xc7\x51\xd5\xa9\x77\x3b\xd4\x0b\x15\xf1\x33\x30\x2d\x20\xb2\x29\xb7\x65\xd5\xf9\xae\x42\x1b\x80\xc6\x54\xe9\xb5\x76\x3b\xbd\xbc\x35\x5e\x8e\xa2\xf1\xce\x88\x7c\x8a\x48\xe6\x75\x3b\x87\xe0\xf2\xff\xbc\x07\x7f\x9e\xed\xc1\x9f\xbd\x6f\x0f\x90\x1c\xb2\xac\xa2\xfa\x4a\xc0\xe7\x50\xad\xd1\x3a\xb2\xdf\xf7\xb1\x23\x9b\x0f\xb1\xf8\x2f\xf8\x24\xfd\xc4\x8c\x03\x7a\x0d\x0d\xbf\x29\x8d\xdd\xcf\xaa\xf6\xae\xb5\xda\x3d\x5e\xef\x7d\xfa\xef\xfe\x3e\x6e\xf5\x50\xab\xd7\xe9\x1d\xe4\x95\x7a\xef\x8a\x72\x3f\xb2\xb8\xce\xff\xbe\x26\x77\xd3\x2c\x9c\x13\x9a\xa2\xde\x0b\x8e\x7f\xef\x74\xff\xe1\xf8\xf7\xec\xf0\x74\x67\xef\xe0\x1f\x0e\xb7\x7b\xe4\x36\xef\xd8\x39\xd4\x02\xb0\x01\xcd\x03\xec\x7c\xcb\x02\xc0\xcb\x0d\x21\x36\x14\x60\xb7\x5a\x80\x5d\x3d\xba\xbd\x04\xcf\xb4\xf4\x9f\xdb\x0a\x50\x0d\x60\xe6\xcf\x57\x63\x91\xaf\xcd\x8c\xd3\x36\xad\xe1\x04\x58\x33\x78\x7c\x28\x38\x2f\xd3\xe6\xe4\x69\x75\x17\x2b\x11\xa9\x72\x4b\xff\x06\xfa\xef\x1d\xd7\x14\xe9\x36\xf9\xd7\x0c\xe1\xdf\x00\x49\xf5\x5b\x84\xff\x62\x3c\x5c\xbf\x65\x08\xff\x0c\x3b\xb3\x6f\x11\xfe\x25\x0b\x98\xfb\x89\x0f\xae\x9c\x84\x7b\xe7\x5c\x8c\x33\x42\x12\x70\x43\x61\x1f\x62\x12\xde\xc8\xf7\x25\xfe\x61\xbb\x0d\xdd\x8b\x70\x7c\x3d\xc9\x18\xde\x63\xfe\x28\xbb\x7f\x76\xd0\x26\xe2\x33\x0b\xb4\x0d\xc6\xff\x0c\xb9\x4c\xa0\x72\xda\xce\x12\xa7\xcb\x38\xe6\xb5\xb2\xc1\x7f\x4e\xc5\xd9\xbf\xd5\x21\x60\x1e\xae\xd8\x47\xc3\x0b\xc0\xc9\xe7\x0e\xf7\x03\x48\x13\xb3\xa8\x77\x1a\xf3\xec\x4a\xf3\xca\xbd\x35\xbc\x72\x4f\x4d\xaf\xdc\x6b\xf6\x68\x54\xe5\x5c\x79\xea\x9e\xe8\x9e\xba\x17\x86\xa7\xee\x47\xc1\x66\x7b\x29\xd8\x6c\x95\xe3\xc2\x3b\x55\xe4\xcb\xfe\x22\xf1\x2f\xf1\x7b\x93\xf2\xf6\xad\x0a\xf0\xbe\x7f\x5f\xfa\xef\xf1\x87\x80\x74\x72\xb8\xc4\xc6\x67\xea\xe3\x87\xbe\x03\x88\xd1\x8e\xff\x01\x1f\x37\x78\x49\xbc\x56\xc1\x8f\xfb\x69\xe2\x1f\xe3\x4f\x76\xae\xdc\x37\x2a\xe0\xa7\xfe\x2f\x99\xff\x09\xbf\xb4\xb8\x53\xbc\x0a\x08\xdf\xba\x4e\x48\x3e\xce\xa2\x11\x98\x9b\x0f\xf1\x0b\xf9\x5e\xb3\x42\x1f\xe2\xdf\xeb\x3b\x78\x63\x04\x6e\x63\xbc\x61\x1b\x79\xea\xb5\xd1\x39\x0e\x76\xd4\xa8\xe2\x0f\x30\x4c\x1c\xec\x88\x11\xc3\x4c\x1a\x2b\x89\xd5\xd8\x78\x1f\x70\x4a\xae\x64\xfa\x90\x9b\xb2\xce\xea\xab\xbb\x85\x18\x34\xbf\xac\x7f\x1b\x5d\x48\xac\xa4\xbf\x55\xbf\x12\x5c\xef\x1b\x5c\xef\x16\x84\x9f\x68\x9e\xce\x4d\xfb\xf2\xdf\x8a\x47\x1e\xd2\xca\x46\x95\xa7\xc2\x71\x82\x8d\xee\x36\xad\x4c\x2c\x04\xb0\x6f\xc0\xc4\x70\x4c\xfb\xe2\x78\x5a\x90\x4c\xd5\x4f\xb3\xdb\x34\x3a\xcf\x1f\x63\xdb\x48\x00\x16\x78\xf3\xcd\xb5\x64\x09\xbb\x63\x98\xea\x1f\xc5\xb1\xf2\xef\xf5\x53\xe2\xd7\x66\xdd\xc3\xc5\x82\x84\x19\x2d\x43\x44\xe3\x09\xcf\x0c\xe9\x3e\xe9\xaf\x74\x47\xca\x53\xcd\xc3\xf2\x56\x38\x55\x9e\x6b\x9e\x94\x17\xca\xc1\xf2\x84\x9f\x26\x24\x69\x42\x5a\x8b\x8c\xe4\x24\x29\x58\xef\x96\xf8\xe5\x63\x0d\x37\x43\xdd\x8b\x71\x20\x46\x94\x75\xc7\x79\x06\x3b\xce\x34\x39\x4b\x97\x39\xa9\x21\x54\x16\x0a\x9a\xd2\x00\xab\x74\x5d\xed\xdb\x13\x85\x45\xe9\xc9\xdf\x00\xdb\x2a\x00\x30\x71\x7b\xec\xba\xcc\x5b\x95\x99\x3b\xaa\x39\x87\xc0\x88\x15\x72\xaf\x81\x6e\xaa\xc4\x44\x6e\x65\xdd\xfb\xf1\x9d\xd9\x43\xea\x92\x73\x77\x9f\x37\x29\xdd\x43\xa4\x33\xdb\x9c\xf0\x5f\xd5\x67\x85\xff\xa2\xc4\x6f\x37\xb4\x2c\x48\x5a\xda\xaa\xf0\xe3\xe2\x81\xa6\x95\x01\x99\xcc\xb1\x86\xe3\xdc\x06\x23\x44\xc3\xbf\xd5\xd6\xdb\x85\xeb\xf2\xfc\x5e\xa9\x35\x72\x6e\xbc\x84\x64\x51\x89\x70\x24\xcc\x01\xfe\xdc\xd6\x1a\xbe\xba\xcf\xae\x6f\x6d\x5a\xed\x68\xbe\x48\xb3\x22\x4c\x60\xcb\xcd\x86\x11\x87\x98\xaf\x5c\x69\x34\x81\x5f\x5a\xae\x3a\x44\x42\x2f\xd2\xc9\x9d\xba\x60\xfe\xb3\xea\xfd\xf9\x87\x89\xd9\xc3\xe1\x24\x45\xba\x8e\xeb\x87\x54\x30\xc0\xf9\x80\xe6\xc9\x53\x85\xeb\xe4\xe6\x0b\x0d\xa0\x94\xd6\xed\x76\x97\x1d\x2f\x54\x7d\x5d\x79\xa0\x6a\xab\x19\x56\x70\xa5\x42\xcd\x2c\xb1\x64\x72\x06\x6b\xbf\xbd\x5d\xab\xb5\x42\xad\xfa\xd5\xf4\xb5\x00\xc2\x1b\x16\x2b\xc4\x3e\xe9\x81\xa3\x0d\x46\x7b\x07\xd1\x7f\x5f\x8a\xf3\x5b\xdf\x19\xa7\xf1\x72\x9e\x38\xb8\x81\x67\xfa\x70\x7f\xb1\x42\x8e\x91\x2c\xeb\xae\xc7\xb5\xaf\xd6\x71\x7c\x6b\xab\xe6\xc2\xab\x30\xce\x09\x10\x5d\x33\xcb\x8e\xe6\xfc\xe1\xfb\x1f\xb9\x16\x16\x4e\xd6\xe7\xe1\xca\x33\x19\x7a\xe1\x14\x3d\xef\xac\x72\xbc\xbf\xbf\x8f\xb0\xe3\x3e\xa9\x94\xdf\xf1\x4d\x56\xe1\x2a\xc5\xef\x24\xbd\x4d\xbc\xad\x12\x7f\x7a\xb8\x8f\xf0\xe6\xc2\x23\xbd\xf8\x17\x73\xad\xf8\xd6\x84\xf3\xf9\xe7\x16\xb8\x21\xb9\xc7\x16\xf1\x6c\xf2\x50\x11\xe7\x93\xaf\x5a\xc4\xf9\xe4\xb1\x45\x7c\x33\x7b\xa8\x88\xf1\xec\xab\x16\x31\x9e\x3d\xb6\x88\x7f\xc4\x0f\x15\x71\x15\x7f\xd5\x22\xae\xe2\x47\x14\xb1\x6e\x48\xd5\x38\xeb\xd4\x92\x23\x65\x58\xd7\x3c\x91\x54\x39\xb2\x67\x53\x50\x6a\x92\x45\x07\x0c\xd6\xfc\xfa\xea\x6d\xa0\x32\xaa\xa4\x5d\xdd\x4f\xbf\x64\x4b\x7a\x89\xbc\x1f\x32\x84\xff\xf8\xd2\x4b\x4c\xee\xdb\x99\x7f\xbe\xb5\x96\x48\xe1\xab\x3a\xba\xe1\xd8\x75\x13\x59\xb8\xaa\x15\xd2\x4f\xdb\x2e\xf4\x95\x1b\xfd\x87\x6c\xa2\xeb\x6b\x92\xb0\x4d\x70\x9e\x2f\x56\xad\xdd\x7d\x0e\x2e\x67\x7a\x65\x6a\x9c\xec\xbb\xdd\xb2\x14\x1c\xb9\xb9\xb2\x8b\x62\x2e\xab\x2c\xba\x74\xcd\x7c\x00\x9c\x91\xa7\x82\x0c\xdf\xc7\x2d\xe3\x58\x87\x8c\x66\x67\xf5\x47\x86\xf0\xbf\x36\x8f\x9c\x86\x9e\xac\x1a\xd1\x68\xe6\x2f\x0b\x47\xf1\x33\x09\x43\x1a\xab\xc9\x0c\xef\x4b\xc2\xfa\x92\xa4\x36\xb3\x11\xc3\x5b\xb7\xb7\x6b\x18\x5d\x18\xd5\xb9\x24\x2b\xa8\xd2\xbf\x32\x84\x8b\xf4\x6b\x7a\x24\xd6\xcd\xc1\x72\x35\x45\x62\xd7\x8d\xf1\xf8\x73\x80\x15\x6a\xd6\x5a\x5f\x63\xda\xd8\xec\x78\xf3\x7e\x52\xbb\x20\x04\xa6\x27\xd5\x65\x57\xbb\x5a\x9f\x5d\x1d\x3a\xd2\x96\x27\xd9\xd0\x29\x7e\x17\xdb\xc6\x75\xc5\x12\xc7\xd2\x63\x97\x60\x6d\x56\x22\xaf\x48\x11\xce\xd2\x20\xf1\x0e\x9e\x23\x9c\xa6\x70\x00\x98\xa5\x08\x47\xe9\x7f\x59\x82\xff\x5e\x96\xe0\x30\xfd\x2a\xe4\xfc\xe7\x0b\x92\xb0\x3e\xb5\x30\x15\xea\x4c\xfa\x6c\xff\x29\x68\xda\xbb\x65\x95\x85\xff\x33\x12\xe1\x84\xfc\x9c\x8e\xb2\xfa\x4d\x91\x50\x46\xe9\x36\x3c\xf0\x30\xc8\x70\x81\x13\x0b\xbb\x1d\xce\xf4\x97\x71\x98\x17\xc0\x22\x0b\xc6\x12\xab\xf3\xa9\xe7\xfc\x9f\xc4\x91\x73\x37\xfb\x3e\xe8\xf6\x3d\x62\x8f\x91\x2f\x47\x79\x91\x79\x5d\x4c\x85\xd4\xc6\x20\xd9\xd3\x1e\x42\x7e\x43\x3a\xb8\x08\x1c\xa7\x7e\x20\xf2\x10\x57\xf9\xca\xce\x0a\xae\x3a\x91\xcb\x69\xbd\x05\xc0\xb4\x89\x99\xbf\x39\x17\x57\xe9\x6d\x8b\x16\xa2\x45\x68\x29\x5a\x73\x92\xe7\xe1\x8c\x58\xa8\xf7\xd2\xb4\x99\x30\xfc\xcf\x0c\xdf\x57\xe9\xeb\xf5\xde\x6b\x24\xb0\xe7\x65\x54\x6a\x51\x3c\xb3\x64\x9d\xa4\xac\xf2\xa4\x9e\xf3\x4f\x99\xbd\x5d\x48\xaa\x9f\x75\xc0\x90\x1a\x5f\x5f\x66\xe1\x98\x94\xb8\xb0\xf3\x98\xc7\xe9\x83\x8a\x87\x4a\xc5\xbf\xaf\x32\xc9\x35\x70\x01\x8c\xc3\x05\x77\x35\x65\x5f\x19\xe1\x5c\x98\x22\x9c\xff\x57\x22\xfe\xcd\x12\x71\x9c\x06\xf7\x63\x69\xc5\x29\x0e\x71\x2c\xc6\xff\x98\x86\xfa\x90\xa4\x54\x2a\x65\x85\x0d\x52\xfe\x55\x36\x38\xe8\x0e\x79\xc0\x6c\x99\x24\x74\x89\xac\x85\x7a\xa1\x87\xca\x97\xe3\x31\xb1\xa5\xf5\x7b\xd6\x09\xb5\x70\xd3\x30\x8a\x6d\xc1\x9e\xc8\x60\xc2\x14\xeb\x45\xa8\x9f\xdb\x90\x15\x5d\x88\x97\x69\xf0\xcd\xff\xfd\xb7\xe7\xf5\xfd\xc1\xff\xfd\xf7\x70\xfd\xef\x7f\xa3\xa7\xe8\xdf\xff\xa7\x53\x7d\xf3\xe4\x1b\x3c\x6d\x5c\x1f\x94\x64\x66\x34\x98\xe0\x04\x68\xa5\x6e\x67\x4b\x86\x10\xc3\xf9\xd7\x10\xc3\xb7\xc4\x24\xd1\x1c\xc5\xe4\x75\x32\x4d\x71\xd2\xa9\x74\x8a\x78\xc3\x5b\x5f\x3c\xf2\x66\x16\x8f\xac\x35\x11\x8e\x82\x6a\xaa\x54\x12\x74\xe6\x40\xca\xbc\x4c\xd1\x51\xd4\xf7\x0a\x80\x1f\xeb\x64\x64\x11\x87\x63\xe2\x7d\xf3\xef\x7f\x7f\x33\xc3\xce\xbf\x1d\x84\x49\x10\x0d\x76\x6d\x5f\x80\xf9\xdd\x71\x30\xb1\x26\xcf\x6c\xd8\xc3\xa0\xd7\xed\xfe\xd3\x56\xa9\xce\xed\x37\xd6\xd7\x9f\x70\x1c\xb0\xcb\x8e\x29\xc9\x32\x32\x69\x15\x69\xeb\x35\x9c\x51\x92\xcc\x6f\x39\x4f\x7f\xce\xac\x6d\xd4\xb9\x45\x4f\x9d\xd6\x37\x1b\x02\x7c\x6a\xd4\x3f\x8f\x0d\xca\x35\xb1\x1e\xd8\x2a\x55\x97\xc7\x2f\x45\x5c\x92\xab\x73\x4a\xd6\xfe\xc6\x54\x13\x46\xd6\x44\xb3\xb1\x2e\xb0\xcd\x21\xc1\xbf\xaf\x6e\x2f\x92\x14\x7c\x63\x01\xf7\xa0\xc1\xc9\xa1\x12\x69\xd7\x88\xc4\x8d\xc8\x1f\x5a\x4f\xad\xad\x36\xaf\x9b\xc8\xc7\x29\xbe\x97\x6b\x75\xad\x99\x58\x2c\x1b\x89\x6f\xa2\xeb\xe4\x13\x82\x8b\xd4\x77\xbe\x81\x38\xfd\x22\x70\x9e\x92\x64\x9c\x4e\xc8\xaf\xef\x5f\xcb\x75\xa8\xd6\x8f\x30\xae\xea\x7d\xf0\x57\x26\x97\x63\x1b\x7b\xf0\xa7\x86\x75\xf1\xc7\xcc\x5c\x17\x35\xd1\xa2\x76\x0b\xba\x09\x21\x66\xb0\x5e\x21\xef\x4a\xf0\xcf\xab\xaf\x9d\x0b\x73\xed\x1c\xa7\xc8\x9b\xa6\x08\x5f\xfd\x77\x89\xfb\x9b\x97\xb8\x79\x5a\x57\xb0\xbd\xb9\xe8\x8d\x4e\x98\xe7\xd1\x2c\xd1\x1b\x42\x35\x03\x95\xc8\x3d\x9c\x05\x52\xba\x73\xe2\xfe\xa3\xe4\xbb\xec\x28\x79\xfa\x14\x89\x80\x29\xb4\x97\x0a\x47\x9b\x61\xcb\xc6\x4b\x59\xe3\xa5\xb4\xf1\x52\xb5\x19\x26\x25\xb2\xaf\x2f\x25\xbe\xf9\xea\xab\xd4\xd5\xf6\xab\x94\x6d\x85\x2a\x82\xc1\x10\x27\x41\xbb\x77\x24\xda\x23\x83\xf6\x30\x69\x9b\x85\x0d\x4c\x87\xef\xb5\xa2\x2a\x97\xb4\x0a\x30\xc8\x86\x47\x51\x27\x37\x49\xd9\xa9\xce\xbc\xcc\x5d\xd7\x2b\x3a\x8b\x65\x7e\xe5\x0d\x32\x1c\x0d\x11\x8e\xa8\x2c\xf2\x92\xa0\xdd\x45\xa8\x2c\x3a\x79\x9a\x15\x0d\x87\x39\x84\xae\x64\x73\xd7\x6d\x17\xf0\xa3\xbf\xd3\xf3\x0b\xf1\x8a\x7d\xeb\xf7\x7c\x32\xe8\x0e\xbf\x2b\x06\xdd\x21\xfd\xfc\xd4\xa3\x8f\xdf\xd3\x47\x49\x4b\x19\x32\xeb\xfe\x31\x89\x62\x8f\x3b\x29\x47\x49\xc2\xcf\x80\xbf\xd9\xeb\x76\x1b\xd7\x93\xeb\x0c\xdf\x57\x80\x25\x75\x31\x59\xf9\x54\x97\x67\x1f\x1b\x04\x96\xc9\x5f\x4d\x3a\x8b\x30\x21\x31\x9c\x36\x68\xa7\x1b\x07\x4e\x69\x48\x72\xfa\xb9\x2e\x13\xaf\x32\x2a\xc3\x47\x24\xf6\x0b\x3e\xd4\x05\x1d\x45\xdf\xc9\x15\xcd\x98\xe4\x8a\xb3\x71\x5e\x9e\x34\x14\xf3\x5d\x66\x16\x73\xc6\x7d\x6f\x69\x0e\xb9\x1f\x62\x05\xe8\xe1\xf7\xf6\x76\x4b\x5c\x54\xc8\xbd\x15\xe4\xea\x00\xa8\x01\x89\xc2\x45\xad\x35\xf5\x87\x0c\xdf\x5f\x93\x3b\xbf\xa8\xb7\xe2\x22\xc5\xf7\x72\xd5\xa0\x6b\xad\x58\x9d\xfc\x84\x59\x5c\x5b\xa5\xf7\xe8\x7f\xd8\x9c\xdb\x7c\x66\x66\xb4\x73\x96\xa6\x96\x66\xb8\x49\xf1\x3c\xf5\x6a\xe3\xb1\xdd\xc5\x6c\x9e\xf9\x57\xa4\xf3\x9e\xab\x91\x7c\x23\x7c\xcc\x58\x1f\x4c\x12\xfd\xcf\x4a\xf8\x84\x03\x16\x4c\x44\xd2\xf2\xc5\x96\xa9\xab\xa4\xde\x2a\xe5\x97\xa7\xf5\x8e\x24\x93\x28\x99\x55\x52\xb2\xf4\xe9\xec\xe1\xdd\xac\xe9\xdf\xab\xb3\xa6\x97\x58\x8c\xdf\x0a\xbc\x30\x56\xd3\x4f\xa7\x5e\x67\x9b\xda\x51\x8a\xf0\x5d\x1a\x0c\xba\xb8\x87\x77\xf1\x1e\xde\xc7\x07\xf8\x10\x3f\xc3\xcf\xf1\xb7\xb8\xd7\x1d\xe2\x55\x1a\x0c\xf8\xd1\x7b\xdb\x1e\x06\xf7\x7a\xb8\xb7\x3b\xd4\xc0\x88\x53\x35\x33\xaa\xeb\xd4\xf7\x3d\xd7\x55\xd7\x1a\x72\x71\xea\x0d\xfb\xfa\x83\xdf\xc3\x49\xb0\x08\xb3\x9c\xbc\x8a\xd3\xb0\xf0\x88\x90\x60\x1a\x10\xf9\x37\x85\xb4\xb7\xe6\x46\x0b\x04\x49\xdd\x9f\xbf\x49\x10\x76\x1c\xb4\x5e\x03\xd8\x3b\x83\x1e\xde\xee\x44\x1a\x4c\x05\xb8\x42\x6c\xe2\x10\x39\x79\x91\x91\x62\x7c\xe5\x70\x34\x22\x65\x54\x50\x71\x3c\x93\xe1\x42\x05\x4e\x44\x18\x6e\x9e\x3a\xd6\xae\xb2\x91\xeb\xae\x67\xe0\x08\xcd\xcc\x12\x95\x05\x8d\xcd\x2a\x51\xba\x42\x49\xab\xc4\x20\x08\xae\xfa\x4e\x96\xde\x3a\x3e\x33\x4d\x8c\x0a\x32\x97\x66\x89\xed\x20\xb8\x71\x5d\x66\x92\xc8\x6d\x25\xf0\x9d\x8a\x39\xeb\xeb\x9e\x9a\x3e\x63\xf8\x88\x67\x92\xb0\xbc\x1d\x04\x2b\xd7\x65\x44\xe5\xf3\x89\xe4\x28\x6f\x03\xb0\xfa\x29\xd8\x22\xe6\x73\x7c\xa2\x5e\x9f\xbb\xee\x39\x98\x22\x0a\x58\xa5\x8f\x2a\xb3\x8b\x7e\xd7\xbf\x00\x83\xc4\xdb\x2c\x5c\x98\x66\x88\x0c\x62\x87\x9b\x22\xae\x62\x69\x82\xd8\x06\x98\x73\x66\x80\xb8\xca\xa5\xf1\x61\x3b\x08\x3e\xb8\x2e\xb3\x3a\xfc\x44\xb2\xf4\x2c\x4a\x98\x39\xe6\x6b\x15\xe0\xd8\x75\x8f\xf1\x27\x8b\x9b\xb7\xd6\xdf\x0e\x76\x54\xa7\x6e\xe5\xf0\x2d\xbb\x07\xae\x13\x78\x67\x38\xd8\xa1\xad\xee\x60\x87\xb7\xb1\x83\x9d\x78\xe6\x60\x67\x3e\x71\xb0\x93\xcf\x0d\xbc\x27\x06\x26\xe4\xac\x62\xfa\x0f\xcd\x4a\xaf\x80\x33\x44\xf8\x8d\x49\x49\x9e\xb3\xeb\x85\x31\x5e\xb8\xee\x20\xd7\xc6\x07\xad\xe4\x47\xd7\xcd\x07\x22\xf1\x9d\x55\xbe\xe3\x54\x26\xca\x47\xa0\x4b\x1e\xb9\x6e\xce\x06\xc6\x6b\xfa\xcb\x68\x32\x18\x3b\xed\x20\x98\x43\x52\xb2\x4e\xb6\xc4\xe6\x08\x0d\x79\x05\xda\x41\xf0\x0e\x22\xd0\x27\x5b\xd8\x77\x10\x56\xcc\x0c\x76\x6b\x93\xf3\xd6\xdf\xa1\x45\xc9\x6d\xb1\xe2\x6a\xac\x48\x8b\xc5\x77\xb2\xb6\x78\x11\xc4\xd3\xc6\x72\x3b\x08\xee\x20\x2a\xef\x11\x5b\xa4\x3b\x1a\xa9\xdd\x6b\x07\xc1\x19\x04\xa5\x32\xd5\x16\xee\x4c\x86\x3b\x51\xe1\xf2\x79\x2d\xdc\x89\x0c\x77\xad\xc2\xcd\x27\xb5\x70\xd7\x32\xdc\xad\x0a\x17\xcf\x6a\xe1\x6e\x65\xb8\xb7\x5a\xf9\xe2\x5a\xb8\xb7\x08\x35\xdf\x63\x4d\x9a\xee\xb0\xde\x88\xeb\xaa\x4f\x1c\x74\xf9\xc1\x05\xa9\x65\xa4\xc4\x56\x27\xc3\x64\xa9\x01\xfc\xe5\x01\x5c\x2d\x73\x01\xa3\x43\xa3\x31\x25\xc9\x38\xe6\x94\x58\x1f\xc4\x80\x32\xc8\x7e\x75\x4b\x6c\x0e\x62\x6e\x72\xc4\x96\xc3\xba\x29\x92\x3d\xf8\x4e\x46\x6e\x48\x96\x93\xa6\x68\xf2\x7b\x35\x7a\x96\xde\x36\xc7\xd5\x3f\x96\x58\x4e\x1d\x4e\xec\xc6\x82\xb3\x96\x91\x5c\x6f\x32\x10\xfc\x35\x53\x56\x8d\xa8\xa7\x5a\x99\x61\xdc\x72\xcd\xbf\xb7\x9a\xc4\x55\x43\xeb\xab\x81\x11\x43\xfb\xd0\x10\x8b\x24\x13\x5b\x1c\xfa\xba\x1e\x63\x14\xe6\x24\x8e\x12\x52\x89\x21\x5f\xcb\x18\xda\x9c\x37\x6b\x52\x35\xf9\xb3\xc5\xa8\xd7\x46\xc6\xb2\xd5\xa7\x1a\x53\xab\x91\x19\xcf\xa8\x93\x16\x8b\xca\x61\xb2\x33\x22\xc5\x2d\x21\x49\x2d\xaa\xf9\xb5\x39\x7e\x08\x67\xcc\x0d\xd1\xf9\xc7\x12\xeb\x82\x4d\xb6\x4c\x83\x39\xa4\x19\x5a\xab\x5b\x35\xbc\x5e\x3b\x2d\x46\xb5\x5e\xd5\x68\xb5\x9a\xd5\xe2\xca\x3a\xd9\xa3\x5a\x6b\xc5\x3e\x91\x1b\x92\xc4\x77\x8d\x31\xf9\xe7\xb2\xc4\x36\xed\xee\x5e\x1e\x03\xdd\x81\x2a\x78\x1a\x8e\xaf\x34\xc9\x96\x09\x06\x26\xa5\x5b\x67\xe8\x88\xae\xab\x29\xdd\xd5\xab\x85\x55\x27\x13\xda\xd1\x9c\xff\xd0\x30\x90\x88\x9e\x2a\xd0\x6d\xea\xa5\x78\x17\x21\x5c\xb3\x75\x7a\xda\x32\x02\x21\xec\x00\x33\xad\xdb\xfa\xbe\xf5\x04\x34\x08\xa5\xee\xf3\x44\xca\x92\xd9\x04\x94\x1e\x01\x75\x01\x55\x0c\xb4\xae\xc9\x5d\xde\xc9\xc8\x64\x39\x26\x5a\xc5\xb4\xa3\xbb\x96\xde\x2e\xf4\x35\xab\xf1\x7d\x79\xb4\xb2\x35\x89\xa2\xcf\x87\xd5\x46\x77\xd0\xd4\x2a\x4e\xd0\x51\x34\xf5\xda\xb4\xa5\x08\x92\xdc\x46\xf0\xc4\xb9\x8d\x94\xf6\xae\xc1\x11\x90\x6f\x7a\xbb\xff\xec\x91\xe7\xe8\x9b\x1e\x39\x04\x34\xc7\xa3\x74\x50\x0c\x03\x90\x66\x2f\xc2\x3c\xca\xfd\x4c\x01\xaf\x6a\xd6\x58\x59\xc9\x9c\xf8\xab\xa1\xf9\x6e\xc5\x16\x85\x9b\xa8\x1e\x59\xe3\x75\x75\x78\xd7\x9a\xcd\x17\x42\xd0\xd4\xda\xa1\x1f\x5b\xf2\x08\x4e\x91\x4f\x06\x85\xd1\x01\xcb\x85\x97\xa0\x61\x90\x96\x74\x43\x0d\x67\x87\x25\xc2\xf7\xb0\x99\xaf\x40\x7d\x01\xee\x79\xca\xce\x71\xce\xd3\x1a\xc9\x0b\xc2\x27\x5b\x9a\xd0\x3c\x1a\xca\x0a\x4e\x19\xc4\xee\x84\x0f\xb0\x2a\x14\x13\x3f\x53\xe1\x70\x4c\x79\xf4\x89\x54\xf6\x28\x73\x32\x89\x96\x73\xb1\x4d\xc9\x8b\x68\x7c\x7d\xf7\xa3\x00\x63\xaa\xee\x54\x1e\x8d\x99\x85\x1d\x5e\x2e\xaa\x2b\x03\xbe\x9e\xa3\x67\x41\xf5\xe2\xf9\x66\xae\x0e\x31\x71\xc6\x40\xe8\xee\x4f\xb0\x1e\xdf\x5f\x00\x58\xdf\x18\x4f\xf0\xa2\x59\x57\x3a\x4f\x6b\x74\x19\xf3\xfa\xe1\x45\x05\xc4\x8b\x19\xfb\xb3\x26\x06\xd0\x46\x40\x07\xe0\x2f\x38\x1a\xeb\x43\x06\x77\x0b\xd7\x4d\x8c\x26\x45\x25\xbe\xe2\x86\xf5\x17\xdb\x1e\x14\x48\x35\x8b\x67\x6d\x18\x4d\x32\x2d\x4a\xf0\x0d\xf9\x92\x39\x51\x7c\xb9\x60\x92\x0e\x0c\x24\x5b\xfc\x4e\xdc\x31\xfd\x65\x4a\x6c\x5c\x9c\xc3\x95\x0e\xb6\x1c\x4f\xec\xa2\x1a\x51\x3d\x30\x63\xcb\x23\xbc\x9a\x7d\x36\xe6\x19\x5e\x44\x13\x42\x15\xbe\xa2\x48\xe7\x60\x3c\x6a\x74\xe1\x7d\xb5\x0e\x39\x59\x84\x59\x58\x90\x2a\xab\xd2\x25\xd4\xbf\x44\xde\x09\x9f\x6d\x1f\x6d\xb3\xed\x32\x0d\xcc\xfb\x29\xa7\xc4\xef\xfe\xb6\x19\x08\x19\x7c\x6d\x38\xb9\x8f\xf5\xf1\x7a\x99\x3e\x38\x60\xb7\x43\x9d\xe3\x4e\x2c\xac\xe0\xda\xb8\xce\xd2\xdb\x59\x96\x2e\xa9\x6e\x9a\xf3\x11\xfa\xde\x6a\xb0\x66\x0e\x47\x50\x8c\x79\xc4\xb2\xda\x57\x60\x85\x5b\x22\xef\x5d\x8a\xf0\xdb\x2d\xba\x00\x47\xea\x3c\xa6\xd2\xd0\x82\x8a\xf9\x51\x47\x31\x4a\x28\x82\x68\x1b\xa7\x0b\xc2\x00\xe6\xa8\x14\x04\x6a\xa8\x34\x2b\x5e\x6a\x47\x2f\x0a\x42\xc8\x86\x23\x47\x8b\xb5\xd5\x61\x82\x26\xf2\x68\x9e\x9a\xe8\xd3\xf3\x33\x21\x84\x46\x26\x97\xc1\x79\x8a\xf0\xcc\x7c\xf5\x31\x45\xf8\x2e\x98\xb9\xae\x73\x45\xc2\x09\xed\xbb\x99\x28\xee\xd1\xb2\xef\x45\xc1\x12\x27\xc1\x5d\x9f\xef\x64\x38\x54\x9d\x0f\x90\xd6\x0e\xf2\x23\xfa\xa9\xb8\x72\x7c\xa7\x98\x38\x8c\x90\x2c\x98\x1e\xb5\x57\xe0\x48\xe5\xad\x02\x1a\xcd\x61\xd3\xea\x36\x00\x24\x3a\xd7\x1d\x89\xf6\xeb\xcb\x5f\xea\x94\x1e\xe1\xd3\x60\x21\xc2\xd1\xfa\xf5\xd9\x1f\x5f\xac\x28\x08\x5f\x07\xf3\xf5\x7a\xe6\xba\xb2\x9c\xf8\x1c\xae\xdd\xc4\xb8\xbf\x72\x5d\xef\x3c\x70\xc2\x7c\xec\xb0\xe3\xae\x30\x1f\xf3\x73\x56\x9a\x8f\x7c\xa8\x9f\xd8\x46\x15\x59\xbd\x49\x1a\xf3\x83\x96\x7c\x70\x3d\xc4\x63\x2c\x47\x92\x79\x74\xb1\x01\x73\x45\xd6\x58\x6e\xec\x45\xff\x5a\xe3\xc0\xee\x5e\xb4\x01\x3b\x51\xcb\x07\x6c\x00\x58\xc3\x9f\x42\x78\xd1\xa5\xd7\xae\xcb\x1a\x54\x93\x90\xae\x9b\x9b\x8b\x08\xf7\x32\xa3\xa3\xc9\xf1\xcf\x39\x77\x01\x86\xb1\xe6\xaf\x4a\x7c\xc3\x81\x5c\xb7\x5c\x60\xb6\x58\x0c\x2a\x73\x1e\x86\x54\xd5\x49\x47\xb4\x6b\x93\x6d\xf5\xff\x49\x5a\xad\x96\xd2\x93\xb7\x43\x93\x51\x0f\x06\x93\xb4\x20\x4f\xea\x21\xdc\x79\xfe\xbc\x82\x36\xb3\x45\x9c\xc3\xe7\x08\xd5\x57\x2b\x0d\x33\x0f\x61\xda\x21\x75\x2a\x6a\x58\xf0\x38\x48\x06\xa6\x3b\x5a\x7e\xb7\x64\x34\xda\x62\x75\x99\xbe\x27\x73\x6f\x77\x1f\x81\x71\xda\xef\x96\x30\xea\xfd\x19\x8c\x94\x12\x8f\xc0\x21\x6a\x53\x86\x25\x9e\xa6\x69\x41\x57\xcc\x07\xd6\xe1\x07\x0b\xd6\x43\x0d\x56\x73\x22\x44\x6f\x97\xae\xd2\xd1\x27\x62\xd2\xd1\xfb\x8e\xb0\x4d\x6e\xd1\x1f\x3d\x41\x0c\x6f\x43\x27\x55\xb8\x82\xe0\x9a\x01\xef\x4e\xae\xc8\xf8\x7a\x94\xae\x1c\x71\xbd\xb1\xbb\xaf\xec\x9e\xbb\x2d\x46\xad\xb0\x39\x59\x3b\xa3\x3e\xe4\xd2\xfa\xbe\xf5\x4f\x6d\xa7\x05\x3c\x46\x95\x8c\x45\xbe\xfb\xcf\xf5\x7c\xe9\x7f\xfb\x0f\xe5\xd9\x35\xb3\xdc\x57\x89\xbf\x4d\x13\xa2\x65\xdb\x94\x0a\x30\x61\x81\xb0\x81\xf4\xee\xeb\xde\x6c\xec\x60\x80\xa1\x34\xdc\xd7\xbd\x14\x79\x00\x96\xbf\xfe\x3d\x63\x84\x05\x1b\x4f\xa5\x20\xea\x4f\x6c\xaf\x6d\x44\x16\x47\xdb\x55\xad\x4c\xb9\x17\xb2\xf7\x0e\x07\xa2\xe1\xf0\x34\x1c\x1f\x65\x77\x03\xc5\x99\xfa\x22\xae\xa1\x6d\x0a\xdd\x09\x15\x26\x25\xf2\xde\xa6\x08\x9f\xe9\xba\x1b\x88\xc4\x12\x1f\xff\x6d\xba\x1b\x64\xf0\x1f\xd0\xdd\xce\xbe\xb6\xee\x26\x16\x8b\x0d\xba\xdb\xeb\x6d\x74\x37\xa6\x23\x34\xaa\x6f\x3f\x42\x07\x20\xef\x38\x45\xf8\xd3\xdf\xd6\x0b\x99\xd0\xe9\x80\xd9\xc4\x8a\xfd\x91\x93\x98\x8c\x0b\x2b\xf1\xe7\x67\x6c\x48\x19\x83\x0a\x76\x44\xaa\x1a\xd1\x94\xa6\x6a\x6d\x09\xf5\xbc\xf5\x3e\xf0\x1e\x16\x94\xa4\x43\xff\x08\x39\x9e\x74\xd8\x8f\x72\xb0\x10\x1a\xd2\x10\x8f\xe9\x9e\x91\xb5\xc5\x04\xb6\x8f\xbc\x98\x02\x1f\xb0\xc8\x2a\x5d\xef\x94\x78\xca\x3a\xfd\xcd\xb6\x5b\x4a\xee\x43\x24\x97\xec\xba\x4e\xdf\xe8\x83\x2b\x3d\x92\xa9\x58\x67\xd4\x41\x4d\x94\x34\x35\x22\x1f\x08\x08\xeb\x81\xec\xd1\x96\xfa\xdd\x98\x4e\xc3\x82\x6e\x02\x4e\xe1\x5a\x6e\x22\x5d\x4e\x1b\x84\xca\x12\xcb\xac\xee\x4b\xcc\xca\x4e\x7f\xc0\x4a\xaf\x16\x57\x9b\x90\x7a\x4f\x9b\x19\x79\x9f\x52\x84\x5f\xfe\xd7\xb6\xee\xef\xb5\xad\x53\x56\x08\xaf\x52\xed\x92\x8a\x74\x32\x12\x4e\xbe\xef\xba\x2e\xfb\xf5\x1d\xe9\x14\x69\x11\xc6\x7d\xf6\xf8\x0d\x7f\xf4\xf9\xd7\xa0\xdb\xdf\xf5\xf7\xc0\x60\xe0\xc5\x57\x37\xb3\x79\xf9\x08\x33\x9b\xc1\x50\x1a\xb0\x15\x35\x03\xb6\x51\x4c\xa4\x81\xda\x69\x32\x8b\x12\x92\x4b\xf3\xbf\x24\xe8\x9a\xde\x32\xd6\xe0\x83\x62\xd8\x39\xb9\x5a\x26\xd7\xf9\x51\xf2\x5d\x26\x4d\x09\x9f\x3e\x15\xf6\x70\xd9\x20\x19\x1e\x11\x66\xdd\x06\xe6\x4b\x51\xe7\x67\x72\xd7\x79\x17\x16\x57\x4f\x1d\xdf\x79\xca\x1e\xcf\x81\x32\x06\x13\x48\xf4\xf5\x4b\xff\x69\x81\x69\x33\xfa\x11\x4b\x9c\x7f\xdf\x31\x02\xb3\xf6\x16\x21\x4e\x93\x89\x25\x50\x89\x4a\xd9\x7f\x36\x2b\x3a\x56\x51\xe8\x68\x9c\xd1\xbf\x85\x94\xbc\xc9\x77\x59\x7f\xa7\xe7\x27\xdf\x67\xfd\x9e\x4f\x3a\xd7\xe4\xee\xbb\x82\xfe\xcb\x6d\xe8\xe8\xcf\xef\xe1\x05\xb2\x19\x8c\x5d\x37\x18\x8c\x09\x7b\x37\xe7\x55\x14\x93\xdc\xb2\xb5\x6c\xb2\x34\xbb\x48\xed\xef\x5f\x37\xbc\x7f\xd3\xf0\xfe\x03\x7f\xef\x40\xb3\x59\x0a\x20\x03\xb0\x1e\xde\x14\x42\x22\x80\x21\x4b\x0b\xbc\x17\xde\x3b\x75\x93\xb7\x86\xb5\xed\x4d\xca\x0c\xdc\xa0\x6d\xeb\x3a\xcb\x87\xd4\x70\x74\x2c\xae\x1c\xbe\xe7\xe4\xab\x10\x44\xdb\x50\x5a\xdf\xc1\xa4\x23\x46\x58\x73\x38\x9b\xe9\x34\x53\xa4\x7a\xdd\xee\x3f\xcd\xd9\x6e\xb7\x9d\xe6\x74\x50\x76\xa3\xbb\xdf\xff\x2b\xbf\xff\x66\xdb\xe8\x27\x5f\x5d\xde\xfe\xfe\xa5\xa6\xc4\x35\x8b\x5f\x9c\x04\xc5\x77\xc1\x9c\x74\xce\xc2\xd5\xeb\xe4\x26\x8c\xa3\x49\x9f\x74\xc0\x17\xcf\x97\x30\x98\x38\x33\x2a\x92\xdf\x46\xc5\xf8\x8a\xfe\x1a\x87\x39\x69\xcd\x49\xe7\x4d\x1a\x4e\xc0\x33\x81\x4c\x7c\x5e\xd4\xee\x91\xfe\x91\x4c\x7c\xf1\xf8\x7b\x16\x15\xa4\x12\xb8\x27\x03\x1f\xc7\x31\x0d\x50\x90\x44\x46\x00\x47\xbd\x4a\x84\xdd\x23\xe3\xab\x4a\x9d\x79\xad\x54\x42\xef\x1d\x99\x9f\xf5\xf0\x74\xf7\x66\x8d\xb4\x7f\x64\x09\xa3\xc5\x3c\x8e\x0b\x92\x1d\x2f\x8b\xf4\x75\x32\xae\x44\x3d\x38\x32\x02\x91\x09\x0f\xa6\xaa\x44\xb7\xe1\xf9\x72\x5e\x89\x77\x78\x54\x0d\x30\xd7\x32\x14\xef\x2e\xae\xa3\xc5\x42\x2f\x48\x12\xc6\x77\x9f\xaa\x2d\xf4\xec\xa8\xf2\xbd\x16\x41\xa4\xc3\x23\x3c\x3f\x12\xfc\xf8\xa2\x07\xcb\x52\x5b\x85\xec\xb6\xb0\x20\xa8\x60\xba\x77\xa6\x59\x3a\xf7\xee\xd9\xba\x6b\x38\xa8\x50\xf9\x54\xa2\x8a\xf0\xa5\x9b\xb9\x26\xf1\xcb\xcd\x6c\x61\x8d\xc6\x86\xed\x35\x17\xf3\x2f\xd3\xe2\xa9\xd3\x72\x9e\x26\xf0\xaf\x17\x7d\x97\xf5\x49\x67\x1a\x01\xcd\x3f\xe9\x90\xf9\x82\x6a\xb9\xb0\x1a\x6e\x1e\xb6\x67\x51\x9e\x47\xc9\x8c\x57\xd8\x99\xb3\x47\xa7\x3a\x72\xf9\xe7\xdb\x8c\xe1\xcd\xd9\xc6\x2a\x0f\x32\x8e\x53\x33\x05\x3e\x3a\xf9\x67\x06\xfb\x64\x04\x90\xc3\xca\x08\x42\x26\x4e\xc3\xe8\x13\xc1\xe8\xcb\x56\x3d\x70\x65\xbc\xf1\xd0\x93\x34\x4a\x66\xad\x31\x1f\x3f\xce\xa3\x46\x19\x4f\x22\x84\x31\x63\x56\x7f\xab\x71\xe5\x4c\xa3\x24\xca\xaf\xf4\x42\xd6\xa5\x05\xf0\xa7\xd1\x32\x7a\x84\xbd\x47\x2a\xb4\x45\x5e\x88\xae\xb0\x05\xb7\x48\x0b\xd1\x2d\xb6\xe0\xb6\xa9\xaf\xfa\xc9\x1a\xa3\x49\x62\x18\x9d\xd2\x10\xb9\x59\x68\x38\x21\xfd\x04\xf8\x06\xad\x28\x19\x5b\xab\x66\x95\x1a\x8e\xe8\x57\x6b\x7e\x36\xd9\xa0\x3a\x53\x8f\x62\xce\x7e\x67\x99\x5c\x27\xe9\x6d\xe2\x80\x10\xb0\xa8\x0f\xbf\x3e\xbc\xc7\xd6\xa6\xab\x38\x6f\x7c\x2e\xd0\x6c\x9e\x37\x41\x8a\xe9\x04\x2a\xcf\x4d\x70\x1b\x4e\x5c\xc6\x27\xfa\x7d\x89\x61\xa6\xd3\x1f\x0b\x81\xe8\xec\xb8\x4f\xd8\xe7\x8d\xdb\x70\x13\xdd\xd9\x71\x9f\x40\x42\x1b\x77\xdc\x04\x35\x40\xb6\xe3\xce\x21\xdd\x51\xb3\x15\x73\xcb\xfc\x21\xf0\x67\xe7\xae\x62\xb3\xbc\xc1\xf6\xfd\x49\x8a\xf0\x8f\xff\xd5\xe8\xfe\x66\x8d\xee\xb7\xaf\xae\xd1\xfd\xf8\x48\x8d\x0e\x17\xa2\xef\xae\xc9\x5d\x5e\x73\xd6\xac\x6d\xa2\xab\xcb\xae\x28\xe7\xa0\x90\xe4\xb1\x4d\xdb\xe9\xa1\xc6\x34\xf9\x80\xeb\x57\x77\xb8\x53\x0c\xba\xc3\x86\xfd\xa7\xc5\x5d\xa5\xd9\xff\x8a\xef\xf6\xfe\x3f\xdd\x91\xb2\x22\xb4\x5e\xbf\xdc\xb4\xe7\xbc\x00\xfd\x79\x53\x08\xbe\xb3\xde\xb0\x25\xfd\x12\x2f\x2c\xb1\x49\xb5\xb8\x1f\x3d\xb4\x41\xa5\x3b\xd0\x62\xfb\x8d\xe7\xaf\x29\xbe\x67\x67\x1c\xfb\xc2\xcd\x28\xe9\xb0\xea\xdb\x7a\x9c\xe6\x1e\xea\x17\x32\x25\x4e\xf8\xd1\x0c\x3f\x96\xd9\xb0\x2b\xfd\xeb\xbf\x32\xec\x6f\x96\x61\x3f\x7f\x75\x19\xf6\x97\x45\x86\xc9\xe1\xf7\x32\x9a\x9c\xa5\xcb\xa4\xd0\xbb\x55\x93\x5a\x69\x72\x72\x15\x26\x33\xc2\xdc\xdf\x2e\x75\x51\xd4\xe4\x89\x6e\xcd\xe6\xf7\x28\x8e\x7f\x4d\xe6\x9f\x99\x13\xbb\x4d\xa9\xa4\xfd\x1f\xf4\x17\xbc\x86\x09\x2b\xfc\x0c\xda\x5d\x81\xfb\x5a\x35\x3d\xc6\x56\x63\x72\x63\x8f\x04\x7e\x7a\x3f\x64\x91\xc5\xc5\x95\xe6\x02\xee\x07\x36\xf9\x7b\x53\x60\xed\xca\x71\xaf\xe2\xd0\x2a\x9b\xdf\x22\xe0\x55\xaa\x66\x41\x68\x9c\x97\x69\x51\x90\x89\x68\xe7\x7a\xae\x4a\xb4\x3c\x17\xa2\xa5\x71\x49\x93\x12\xa7\x5e\x86\xdf\x84\xdb\xa9\x08\xdc\x98\x8a\x05\xaa\xe0\xc5\x23\x22\x5b\x24\xd6\x2f\x5f\xe6\xe8\x28\xfb\xab\x82\x4b\xa7\xdb\x1b\x96\xd8\xd2\x98\x42\xa5\xde\x3b\xec\x32\x15\xf0\xe7\x14\xe1\x1f\xbe\xfc\x7a\xf2\x41\xde\x39\x71\x21\x5e\xb1\xb0\x9d\x46\x2b\xaa\xf6\xe6\x78\xf9\x58\x5a\x39\x91\xe0\x23\x48\x79\xf3\xbf\x96\x61\x46\x60\xc8\x69\x88\x73\x9c\x95\x54\x61\x55\xef\x3f\x70\x23\x99\x0c\x54\xe6\x56\xa3\xa4\x31\x90\x4d\x6d\x43\x88\xc6\xeb\x1f\x04\xc1\xd8\x75\x9d\xf9\x32\xda\x61\x2f\x24\x6a\xde\x92\xe3\x48\x6f\x18\x2e\xdc\x32\xbd\xc9\x22\x48\x3d\xce\x32\x72\x37\xe8\x75\xbb\x43\xbf\xf2\xee\xdb\x6e\x77\x78\x64\xb7\x9a\xdd\x88\x57\x5c\xb1\xa5\xb5\x3a\x27\x71\x4b\x07\xd2\x61\x3f\xe8\xca\xf0\x22\xcc\x2a\xd4\xd7\xa2\x39\x5f\xd1\xca\xeb\xc6\x13\xac\x35\x74\xdb\x09\x6e\xdc\xce\x68\x48\xba\x35\x84\x66\x0b\x25\x50\xa9\xd2\x3f\xe6\xef\xec\xdc\x4f\x8d\xb9\xa8\x04\x2e\xc0\x98\xe3\x01\xf3\x8e\xe6\xc8\x61\x11\x8d\xcd\xc8\xf4\x8d\xa3\x82\xbc\xe7\x58\xd4\x56\x9e\x2e\x4e\xa3\xf5\x92\x6f\xb9\x6b\x3b\xbf\xa2\x66\x61\x3c\x23\xc5\x49\x9a\x14\x59\x98\x03\x2a\xa5\x57\xa0\x87\xb8\xba\x1a\xb6\xab\xd5\x84\xc5\xc7\xb1\x96\xfa\xc3\x2c\x5f\x8d\xb7\xcf\xd5\xe4\xd5\x67\x4b\x06\xaf\xd9\xbd\x7b\xed\x1e\x9e\x7f\xbe\x54\x94\x53\x0f\x90\x85\xe1\xff\x3f\x7b\xef\xbe\xdc\x36\xae\xe5\x8d\xbe\x8a\xcc\xc9\xf0\x23\x76\x96\x39\x94\x13\x27\xd9\xf4\xe6\xd6\x67\x3b\x4e\xc7\xdd\x4e\x9c\x4e\x9c\x4e\xd2\x1a\x55\x8a\x16\x21\x09\x31\x45\x2a\x24\xe5\x4b\x24\x56\x9d\x7f\xcf\x5b\x9c\x67\x39\x8f\x72\x9e\xe4\x14\xae\x04\x48\xea\xe2\x74\x7a\xcf\x7c\x53\xdd\xd5\x15\x53\x24\x08\xe2\xb2\xb0\xb0\xb0\x2e\xbf\x55\xaf\xc0\x34\x5d\x1f\x32\x4a\x65\xb0\xb6\x29\x82\x8f\x7f\x96\x0b\x87\xca\xa8\xad\xf0\x3d\x7f\x9a\x17\x05\xce\xf2\x56\x67\x8e\xf6\x24\x9d\x19\x1e\xcf\xe3\x30\xbb\x67\x7a\xce\x46\xc2\x66\xf1\x61\xc3\xb5\xf6\x87\x24\xed\x4e\xfa\x11\xe5\x76\x3b\xcc\x3b\x63\xcc\xbf\xa2\x58\x9c\x70\xbf\xf8\x79\xeb\x1d\x71\x5d\x86\xfc\x95\x39\x7d\x4b\x10\xdf\xad\x61\x3b\xeb\x4e\x6a\x86\xb7\xbe\xe1\xaf\x66\xee\xab\xb8\x1e\xeb\x62\xe5\x53\x0b\xc1\x8a\xba\x1e\xad\xac\xeb\x11\x2a\xe9\x28\xb0\xb9\xf3\xb1\x3b\x25\xb7\x24\xc9\xdd\x22\x4d\xe3\xcb\x30\x83\x08\x27\x0c\x93\x9d\x24\xc2\x1b\xf1\xf1\xb3\xba\x73\x05\x2f\xc9\xf0\x73\x53\x04\xbf\x6f\x19\xda\x9f\x0c\x27\x69\xc6\xb3\x9c\x99\xa1\xfd\x0b\xe9\xb5\xe2\x5b\x45\x3a\xb3\x60\x92\x66\xe4\x1b\x15\x30\x63\x75\x28\x13\x41\xff\x97\x61\x34\xc6\x12\x1e\x20\xd6\x11\x6c\x37\xf9\x97\x53\x41\xa1\x16\xe6\x2f\x83\x6c\x64\xa8\xbf\x5c\x27\xb3\xaa\xd8\xa8\xc7\x73\x59\xfb\x3c\xe0\x9f\x24\x02\x7e\x9f\x79\x9d\x4f\xc3\x5b\xb8\xae\xca\x4e\x7b\x7f\xff\xbb\x3f\x85\xcb\x00\xbb\xe9\x35\xce\xe2\x70\x06\xe3\xea\xe9\x25\x5d\x2d\xc3\x22\x4c\xc6\x31\xb6\xfc\x4b\x96\x82\x28\x9f\xa4\x37\xbf\xe3\x2c\x85\xdb\x6a\xc5\xdd\xd9\xf6\x1d\xcb\x43\x24\x57\xdc\x49\x55\xc7\x4d\x8f\x72\x6e\xca\xa0\x22\xcb\xbf\x81\xab\x16\x6f\x77\x6d\x8c\x2d\xb0\xf4\xe1\xda\x26\xbb\x8e\x94\x71\xf4\x35\xaa\xfa\xcc\xd3\xe6\x58\x60\x89\xde\x59\x60\xc9\x0e\x98\x4e\xf1\xe7\x2a\x57\xfa\xc4\xb6\x1d\xc6\x6e\x6c\x7b\xe7\x76\xb9\xe4\x37\x43\xdb\xb6\xa2\x94\x39\x66\x9f\x20\xe6\x4b\xbe\xe3\x71\x17\xf6\xe3\xc0\xb2\x0e\x94\x15\xa0\x10\x7e\xd8\xce\x71\x10\xfe\xf3\xba\x67\xe9\xa9\x6c\x1f\x5a\xc8\x0f\x9b\xc2\xf1\x6c\x2b\xe6\x20\x63\xf8\x15\x27\xb8\x42\xd0\x92\x31\x95\x27\x31\x5f\x5d\x09\x1b\x5c\xc8\xfb\x55\xc3\x88\x5b\x51\x2e\x6a\x15\xbf\x88\x2b\x49\x1d\x81\x55\x5a\x68\x40\xdf\x37\x26\x6d\xd3\x6b\xab\xea\xd5\xbe\xdc\x5e\x64\x5c\xf7\x8a\x8f\x98\x8b\xfb\x1a\x59\x31\xa2\x6f\x9c\x33\xb0\x02\x45\xf7\x6c\x5e\x02\xee\x1f\xef\x46\x29\xdd\xd8\x8f\x05\x7a\x35\xf9\x11\x7c\x54\xa8\xd7\x39\x3b\x5d\xe1\xcc\x66\xca\x70\x6c\x1a\x36\x49\x8e\xcc\x37\xae\x1e\xea\xbe\x36\x5d\xc9\x36\x49\x4c\x5a\x13\x41\xae\x90\x47\x47\x69\x52\xbc\x08\xa7\x24\xbe\x6b\xba\x94\xf3\xfb\x5b\x7b\x9d\x6f\xf6\x02\x07\x15\x80\xbf\xe7\xe9\x7e\xe5\x5d\xdd\x7f\x9a\xf9\x6b\x0b\x03\xc7\x9e\x67\x1a\x2f\xba\xca\x55\xd8\x48\xa5\xa8\xe7\x0a\xcb\xa5\x26\xa7\x4a\x58\x68\xc1\x02\x87\x39\x3f\x35\xea\x05\xf9\x4d\xfa\x07\x9f\x26\xe7\xf3\x02\x22\x99\xe2\xc9\x2c\x27\x6f\xd7\xf2\xdc\x95\xff\x13\xa4\x47\x0e\x85\xb8\x95\x99\xa5\x51\x31\x7f\x64\x56\x1a\xd1\xa5\x64\xcc\xd9\xe3\xca\x5a\xa5\xa6\xbf\x72\x98\xf7\x4a\xd0\xf9\xcc\x45\x3a\x63\x52\xc1\x5b\xb9\x27\xf9\x0b\x7e\x92\x90\x07\x1c\x2d\x91\x6d\x3e\x0c\x63\xec\x74\x51\x47\xa5\xf5\x76\xf6\xbd\x7f\x87\xce\xee\xbe\xf7\xef\xa8\x25\x81\x29\x0b\xae\x66\x69\x72\xec\x07\xd5\xee\x61\xe4\xc6\xe5\x55\x7a\x2b\xaa\x2c\xcd\xb6\x72\x1d\x43\xbd\xb9\x2a\xd3\xea\x3d\x5a\xbc\xae\xc1\x22\xb3\xcf\xbd\x9b\xdc\xd6\xe2\x8b\x74\x46\x85\xb1\xc6\xe0\x0a\x2f\xfc\xf5\x2d\xdd\x5d\x3b\xb8\xdf\x33\xb4\xbb\x9b\xc6\xb6\xd6\x58\x35\xb4\xf7\x68\xef\xea\xe6\x7e\xc7\xc0\xee\xae\x1b\x59\x46\x08\xc7\x24\x1b\xca\x71\xb5\xba\x8f\xb5\x14\xa3\xf4\xfa\xbf\x27\xf1\xca\x36\x5f\xca\x20\xab\xef\x68\xf6\xbf\x98\x82\x9b\xc3\x2c\x72\xb2\x6e\x6e\xee\x7f\x11\x19\xb7\x0e\xf2\x3d\x1b\xfd\x27\xd2\xb2\x7a\xcf\xc8\xf9\xfb\xaf\xda\x57\xcd\x34\xb1\xf5\xe4\x28\x47\x54\x92\x62\x19\x44\x52\x04\x05\x09\x12\xe7\x89\x87\x20\x21\x2c\x2b\x45\x41\x10\x64\xe4\x2f\x73\xd7\x9f\x6b\xee\x4a\xc9\x8f\x36\x77\x65\x64\x1b\x93\xfd\x66\xb7\xb9\x76\xac\x66\x33\x20\x45\xa0\x2e\x5e\x84\xf9\x55\x87\x24\xa3\xd4\x82\x36\x34\xe7\xfc\x2a\x6f\xc9\x5a\x80\x09\x2c\xf4\xe3\xaa\x5f\x43\x84\xfd\x75\x8e\xe7\xd8\xfd\xca\xfe\x35\xe0\x48\x95\xc2\xbf\x25\x13\x02\xa9\x50\x9f\x5b\x6c\x21\x84\x91\xf8\x1e\x82\x90\x93\x38\x21\x08\x62\x76\xaf\x8b\x20\xe7\xf7\x62\x82\x60\xf8\x17\xd9\xff\xc9\x64\x3f\xff\xe1\x64\x3f\xfc\x23\x64\xbf\x9a\xb6\x35\xa2\x9c\x85\xf3\x1c\x47\x3d\xeb\x2d\xce\xe7\x53\x6c\xf9\xd6\x1b\x7a\xc3\x02\x23\xbf\x88\x34\xe6\x5e\xa4\xe3\x71\x8c\x59\x89\x48\xa6\xa8\xbd\x48\xe7\xc3\xc9\x5b\x32\x9b\xc5\x98\xd9\x38\x9b\x75\xd7\xdb\x95\x2b\x7a\x6e\x20\xb5\x87\x15\xa9\x37\x09\x3d\x62\x44\xfd\x08\xc1\x88\x13\x75\x44\x10\xcc\xfe\x22\xea\x3f\x99\xa8\x27\x3a\x51\x43\x01\x09\x64\x15\x35\xe3\x1b\x27\x59\x2e\x9d\x24\x78\x93\xa5\x53\x92\x63\xa4\x6b\x4c\x52\x20\x5a\x7b\x43\xba\x24\x8a\xec\x6e\x91\x3b\x99\x9b\xe0\xdb\xc2\xc1\x08\x95\xc3\x50\xb8\x07\x13\x07\xa3\xb2\x54\xa5\x63\xbd\x74\x31\xc9\xd2\x9b\xb5\xc5\x73\x65\xc9\x3b\xc0\x6e\x94\x26\xb8\x97\x3a\x98\x67\x15\x64\xc9\x1b\xc4\x35\x14\x1d\x6d\x82\x93\x5e\xe1\xd3\x8e\x26\xa6\x96\x07\x3b\x05\x73\x95\x71\x8b\x09\x4e\x9c\x10\x62\x54\xe6\x8e\x93\x05\x99\x58\xb0\x18\x8a\xe5\xb2\x3f\x40\x88\xf7\x82\xa9\x8d\x4a\x98\x9a\xc3\x24\x41\x45\x32\x48\x19\xb0\x88\xc0\xa0\xf6\x20\xa7\x9b\x82\x46\xb2\x64\xe4\x74\xed\xb4\xef\x0d\x10\xeb\x65\x27\xd5\xbd\x8f\xfa\xdd\x41\x09\x45\x76\x97\xfb\xfd\x01\xa4\x33\xfa\x47\x91\x05\x09\x16\xf4\xfb\x7e\xec\x78\x08\xd8\xbb\x7e\xec\x74\x11\xf0\xc7\x7e\xcc\x54\xed\x96\xfc\x92\x15\x04\x74\x62\xd3\x51\xe7\xdd\xdd\xf4\x32\x8d\x6d\xdb\x21\x7d\x7e\xe9\x92\x02\x67\x61\x91\x66\x83\x16\x9e\xc2\x98\x11\x02\x72\xa0\xcd\x0c\x69\xc2\x80\xc5\xcd\x5b\x84\xf5\x2d\x11\xdd\xa2\xe3\x7c\x71\x37\xe3\x4e\xb1\x8e\xf5\x13\x4e\xf8\x37\x3b\x24\xef\x84\x71\x86\xc3\xe8\xae\x83\x6f\xf1\x70\x5e\x50\x51\xd0\x42\x2c\x6c\xed\x20\x3c\x40\x94\x06\x68\x3d\x41\x17\x32\xdb\x76\xd2\x60\xcf\x26\x7d\x6f\xd0\xcb\x5c\xd1\x51\xf1\x8b\x7d\x66\xb9\x74\x9c\x34\x90\x8f\x10\xcb\xe8\x40\xd7\x58\x86\xc0\x43\x3e\x27\x3b\x64\xdb\x3b\x0e\xc3\xee\x61\x4f\x80\xf4\xbb\x74\x2e\x29\xd1\x20\x39\xee\x07\xc2\x6d\x3d\x0b\x3c\x48\xe9\x50\x05\x7d\xfe\x59\x48\x39\x21\x0d\x10\xd0\x9f\xc2\xab\xdd\xe3\xde\xd8\x5d\x3f\x0d\xc8\x01\xb3\x6d\x70\x5f\xe0\xc7\xd2\xab\x3f\x74\xd9\xf4\x3f\x7c\x28\x83\x89\xe8\x47\x81\x7e\xd2\xdf\xe9\x96\xbc\xf0\xbe\x5f\x95\xca\x58\x8e\x12\x20\x41\xdf\x1b\x1c\x0c\xd3\xa4\x20\xc9\x1c\xf3\x62\x4f\x7d\x12\x84\x2e\xe3\xae\xe9\xcc\x41\x10\xba\x94\x3e\xf8\x8f\xaa\xa8\xf4\x2b\x26\x23\x87\x76\x96\x17\x02\xd6\x6d\x81\xa3\xec\xd9\x76\xda\x97\xbf\x76\xbb\x03\xb4\x5c\x3e\xd9\x09\x02\xda\x2b\xdb\xde\x13\x57\x08\x2d\xc2\xc0\x53\xd5\x96\x64\xe4\x3c\x0a\x64\x21\x67\x27\x5d\x2e\x69\x3b\xff\x99\xb2\xdf\xf4\xf2\x1f\x69\xff\x11\x7b\x8b\x77\x85\x75\x83\x8f\x08\x7d\xf7\x89\x7a\x57\x3c\xff\x07\xa5\xf0\xaa\x34\xfd\x05\x6a\x0c\xe9\x1b\xa9\x5e\x74\xcf\x28\xba\x37\x00\x31\x0e\xf3\x7c\xe2\x10\x24\x5e\xa2\x0f\xe8\x4b\x1b\x46\xa8\x24\x41\xc1\x29\x00\x43\xa8\xf3\x95\xa0\xff\x04\xf0\x00\xb2\xc0\x2b\x47\x24\x09\xe3\xf8\x6e\x91\x04\x69\xe0\xd1\xd6\xec\x33\x1a\x10\x14\x4d\xaa\x85\xaa\x26\xd5\x1b\xf4\xe8\x6d\x9f\x9b\x3a\xc4\x04\x7b\x65\xe9\xf4\x09\xc4\x03\x7a\x50\x81\xeb\x95\x32\xc2\xbd\x12\xb6\x31\x8f\xfb\x57\x38\x99\xeb\x6b\x56\xcf\xb1\x36\xc5\xc9\xbc\x96\x61\x8d\xbf\xf9\x16\x8f\x32\x9c\x4f\xcc\xf7\x6a\x95\x3a\x08\x12\x37\xe3\x05\x1d\xed\x5d\xbe\xff\xaf\xf9\x6c\x9b\xba\x5c\x6f\x09\x76\xab\x5f\x25\xe3\x9b\xea\xdb\xcc\x4f\xec\x34\x29\x70\x76\x1d\xc6\x41\x4b\xc0\xe0\xc6\x06\x0f\x8d\x2a\xd8\x06\xc1\xbb\xa1\x80\xba\xde\xe2\x11\x2b\x29\x72\xcd\x91\x5c\x0c\x06\x3d\x93\xee\x74\xc1\x18\x33\x08\xe7\x45\x2a\x9e\xcb\x3a\x7d\x4f\xcb\x47\x37\x6b\x95\xcd\x1a\x83\x2b\xe1\x86\x08\x9f\x50\x41\x1a\xe2\x4f\x13\x72\xae\x33\x15\x05\x9d\xb6\x30\x1a\x4e\xfd\x8a\xed\x68\x4c\x5a\x9b\x7a\xb3\x5b\x5e\x89\xa0\xff\x18\x0c\x39\xee\xad\x9c\xda\xc1\x81\xe0\x5c\x55\x9c\x2c\x15\xc3\x10\xac\xad\xb2\x4b\xab\xdc\x1b\x94\xa5\xd8\xfb\x0c\xe7\xc0\xd5\xf3\xc8\x3d\x6d\x18\x35\xd7\xaa\x6f\x1b\x6a\x5c\x22\x88\xd3\x61\x18\xbf\x2b\xd2\x2c\x1c\xd3\x96\x15\xa7\x05\x9e\x72\x5c\xc8\x5a\x69\x0b\x2c\xeb\x21\x46\x30\x8c\x71\x98\x29\x0a\x60\x5f\xd1\x4a\x5f\x90\x29\xce\x44\xdf\xea\xb7\x03\xfc\x4f\xaf\x27\xb2\x5f\xd0\x2f\xc9\x3a\x5a\x66\xa8\xd0\x96\x06\x82\x2e\x7e\xf4\x37\x8c\xc4\x92\x5f\xe1\x28\xd9\xe6\x8f\xb9\x35\x5d\x30\xa1\xf6\x60\x25\x75\x14\x8a\x3a\x8a\x56\xea\x90\x73\x9f\xad\x9a\xf3\x42\xce\x39\x0e\xbc\xa5\x31\xe2\xe3\x75\x23\x2e\xc6\xb1\xb1\xe8\x56\x53\xc6\x06\xb7\xd1\x6d\xe6\x6e\x93\xb3\xe8\xf7\x29\x00\x98\xfd\x54\x8c\x91\x3c\xfd\x8b\xcf\x36\xbc\x5c\xa0\x25\xf1\x62\xc5\x16\x9b\x87\xf7\x11\x59\x9d\x41\xf1\x7d\x02\x0b\xae\xfe\x3b\x89\x55\x03\xdc\xe1\x3c\xcb\x70\x52\x40\x3d\xb7\x62\xc5\x9d\x56\x66\x56\x6c\x6f\xc2\x8b\xa4\xbd\xeb\x5f\x13\x06\xae\x45\x0f\x71\x91\xfe\x21\x7d\xb1\xb7\x75\x57\x3c\x95\x69\x91\xd9\xa9\x4f\x8e\x57\x27\x49\x6f\x5a\xdc\xf3\xcf\x45\xe6\x2d\xeb\x70\x5e\xa4\x1d\x41\x8a\x2d\xe5\xbe\xb6\x67\xb6\x34\x37\x07\x67\x0f\x55\x60\x1a\x7b\x32\xa5\x0e\x6f\x7b\x0b\xa5\x96\x60\xed\x75\xb8\x51\xab\x2d\x72\x60\xbb\x4f\x3e\xf2\x3c\xed\xa3\x8f\x3c\x6f\x9b\xcf\xee\x77\xa6\x24\x99\x17\xad\x71\x15\xdb\x7d\x56\xff\xe8\x56\x9f\x3c\x1f\x8d\xac\x15\xf9\x5e\xc8\x8f\xcc\xa5\xcc\x3d\x51\xf2\xca\xa9\x23\xd6\x3c\x51\x62\xee\xba\xa2\x3c\x51\xe6\x55\xb1\x61\xcf\xba\x9c\x17\x45\x9a\x58\xfe\x90\x81\x24\x4a\x02\x84\x51\xe5\x3c\x12\xd9\x76\xc4\xb0\x12\xc5\xc3\x13\xe9\x5b\x0a\x93\xaa\xd0\xcc\xb6\x67\xcc\x7f\x45\x14\x7a\x91\x0e\xe7\x39\xd7\x43\x28\x77\x16\x9e\x81\x80\x3b\xb3\xe0\x24\x3a\x1d\xa6\x09\xcb\x5a\x31\xa2\x65\x7f\xe3\x1a\xec\x63\xd5\xaf\x3b\xfa\x44\x26\x95\x6f\x75\x66\x61\xd8\x8d\xa6\x27\x8b\x44\xb0\xbd\x81\x2b\x86\x60\x1b\x66\x05\xfb\xcc\x79\xc0\x1d\x48\x55\x22\x8b\x20\x08\xce\xab\xce\xf3\x84\x16\xd2\x39\xc6\x48\x68\xc1\xb2\x6b\x5b\x2c\xab\xc5\xfd\xb3\x51\xb7\xb9\xbe\xc8\x21\xae\x2e\xd5\x80\x56\xb7\xb4\xe1\xb3\xc0\x12\x83\x65\x81\xd5\x3a\x54\xf4\xbe\x1c\x28\x1d\x5b\x57\x74\xde\x02\x8b\x76\xdd\x74\xa7\x79\x13\x5c\x35\x33\xec\x6d\xf0\x4e\x21\xda\x80\x92\xbe\x45\x86\x69\xf2\x6e\x2d\x96\x21\x62\x5e\x30\x6f\x83\xcb\xef\xf8\x94\x24\x90\x6d\x3f\x74\xb9\xda\xb9\xaf\x9e\x53\x7d\x6d\x8e\x6f\xd2\xff\x32\x80\xb0\xc2\x86\x64\x9e\xdd\xc4\x35\x9c\x35\x75\x87\x97\x9c\x1e\xb5\x2a\x6f\x9d\x2f\xed\x7e\x32\x79\x13\x09\xb2\x6f\xbc\x06\x16\xeb\x61\xfb\xdb\x0c\x17\x92\x6c\x42\x8e\x1c\xc0\xc4\xb6\x49\x73\x95\x8e\xb4\xbb\x11\xdc\xd2\x5f\x8a\x5c\x90\xa6\x4c\x9f\x83\xda\x7e\x46\x30\xaa\x08\xd0\xdf\xb9\x86\x56\xba\xab\x8f\x9f\x5e\x08\xc6\x12\xa8\x8c\xd2\x9e\x7f\x5c\xc2\x45\x93\xeb\x36\x89\x80\x70\xa1\xa9\x84\x37\x90\xc0\x5b\xee\x03\x34\xde\xd2\x07\x68\x2d\x78\x25\x5b\xe8\xb0\x32\xe9\x84\x74\x6d\x78\xa2\x81\x10\x56\xb0\x86\x86\x47\x04\x76\xf3\x49\x38\xc3\xae\x7e\xb3\x1d\x0d\x59\x42\x47\x6c\xb2\xc9\xf5\xad\xca\x81\x63\x57\xb2\x8c\xcb\xf4\x76\x37\x9f\x84\x51\x7a\xc3\x7e\x64\x0c\x21\x1b\x16\x9b\x8c\x71\xf9\x24\xcd\x8a\x12\x81\x65\x2b\x0c\x2d\xda\x96\xe7\x78\x98\x8a\x17\x39\x6a\x7a\x03\xab\x6f\x05\xb2\x96\xd1\x91\x76\x14\x2f\x09\xaa\xa5\xdc\xd7\x1d\x8e\xa7\xd5\xa1\x1f\x42\x2d\x41\xbd\x86\xdf\x32\x0b\xff\x55\x3c\x71\x53\xe1\x7a\xe9\xfa\xb8\x8b\x76\xc9\x12\x25\x3d\xb5\x30\x55\x9f\x91\x43\x4b\xf7\x0c\x13\x72\xa4\xee\x88\xa5\x6e\xd6\x7d\xb9\x2a\xe7\x6c\x3a\x2c\x35\x28\xcc\x67\xb3\x5b\xf1\x40\x79\x14\xad\x72\x15\x62\xfe\x38\xda\x0c\x6d\x39\x17\xc6\xeb\x7f\xca\x5c\x94\xbc\x03\x9a\xdf\xd2\x6a\x77\xa4\xef\xec\xc4\x26\xa8\xb6\x1f\xd4\x0d\x81\x4d\x17\x69\x93\xb4\x4f\xd7\xf3\x7e\xb5\x9e\x35\x40\xda\x2d\xb0\x68\xad\x6c\x7c\x19\x3a\x1e\x74\xc4\xff\xee\xde\x23\x64\xf9\xfc\xee\xde\xfe\x3e\x74\xaa\x7f\xf8\x33\x54\x27\xec\xd5\x1f\x5d\x49\xbf\x47\xaa\x9b\x48\xeb\xd3\x76\xf4\xb5\xe6\x7b\xdb\xd0\x97\xbb\x8f\x0c\x2e\xb2\x55\xf3\xf5\x1a\xd0\xb6\x2c\xe6\x5f\x42\xd6\x72\xec\xb6\x27\xed\xfb\x8f\x5f\xad\x82\xef\x1a\x41\xb3\x8e\xad\xc7\xf0\x5f\xb2\xaa\x7e\x00\x41\x53\x32\x96\x51\x9e\x2d\x98\xca\xf5\x10\x9f\x5a\x68\xd7\x23\xcf\x1b\x34\x87\xa4\xa5\x10\xf3\x97\x65\x9b\x27\xdf\xae\xa3\xf4\x26\xef\xef\x0d\xd6\xb1\x2b\xb3\x16\xf7\xb0\xeb\x79\xad\xb5\x3c\x1e\xac\x1e\xbe\xf6\x8f\x6e\xd1\xde\x4d\x9b\xe0\x16\x0c\x82\x4f\x8f\x2e\x82\xad\x68\xd1\x13\xf6\x35\x3f\xe4\xc9\x36\xdb\xcb\x3c\x6b\xb4\x68\xc3\x46\xdb\x3a\x54\xde\xba\xbe\xaf\xeb\x88\xa2\x90\x8d\x9c\x4e\x77\x5f\x5d\xf3\xb1\x6d\x37\xde\xe6\x1b\x51\x98\x5d\x6d\xbd\x5e\x56\xc0\xab\xe8\x3d\xda\x8a\xff\x6c\xd9\xab\xed\xf7\xe2\xb6\x77\xbe\xb3\x67\xe6\x47\x69\xdf\xea\x07\x0e\x9d\xa4\x84\xb0\x69\x70\x41\xf3\xd9\x7a\xb2\xad\xca\xb4\x11\xac\x56\x83\xc1\x97\x6a\xcf\x4b\xe3\xf8\xe2\x2f\xca\xea\xa0\xb3\xd8\x10\x86\x57\x25\x55\xa1\x77\x85\x2e\x90\xfd\x12\xb2\xde\xbb\x16\x60\xf4\xc7\xb3\xdb\x0e\x93\x33\x36\xb8\xd4\xb3\xd8\x67\x51\xc7\x59\x98\x8d\x35\xe4\x70\xeb\x19\x15\x56\xba\x5b\x54\xb2\x8f\xb4\xcd\xad\xa5\x31\x8f\x66\xb7\x9d\xbf\x6f\xd9\x18\xbd\x9e\x7a\x83\x9e\xce\x6e\x3b\x7b\xdb\x36\xa8\x22\xf7\x15\xc3\xd3\xf5\xb6\x6c\x92\x51\x53\xdb\x20\xed\xed\x6d\xd9\x26\x0d\xc2\x9e\xff\x10\xb5\x95\xa0\x4e\xc3\xf5\x4c\xbb\x4a\xdd\xa1\x45\x82\x28\xca\x30\x51\x9a\xf8\x2f\x16\x2c\xb7\xfb\x98\xf9\x72\x0a\xa5\x05\xfb\xa4\x25\x23\xdb\x79\x81\xbd\xb2\x04\xa1\xdf\xd8\x54\xf3\xee\x63\xbd\xea\x67\xab\x6b\x16\xc5\x69\xd5\x46\x01\x7f\xc1\xd1\xf0\xfd\x11\xc9\x34\x20\x7a\x35\x60\xdd\x67\xda\x1b\x3c\x14\x64\xd3\x2b\x7b\x9e\xf6\x8a\x18\xc5\x0d\x6f\xec\x95\x75\xb7\x50\xae\x79\x2b\x91\x73\x49\x10\xdc\x6d\xa7\x0d\x15\xcb\x56\x24\x32\x52\xe1\x7e\x3b\x41\x90\xd8\x76\x52\xe9\x45\x71\xce\xb5\xa2\x4a\x97\xd8\x02\xeb\x6e\xd6\xb5\x42\x83\xb7\x3a\x62\x94\xdb\x30\xb6\xd1\x2b\x85\x5c\xaf\x14\xc3\x0e\xb1\xed\x50\x86\x4b\xaa\x30\xb1\x9c\x2b\x39\x6e\xc9\x5a\x90\xf6\x95\x91\xa0\x4a\x5d\xf1\xac\x71\x5a\x55\x99\xfe\x58\x8c\x12\x4f\x6e\xc0\x82\xb9\x4b\x10\x8d\x10\xd3\xe6\x27\x69\xe1\xe8\x53\x87\x4c\x7a\x7d\x56\x1a\x80\xf0\xcf\x49\x18\xa7\xe3\x43\x36\x3f\x39\x9d\xc1\x3b\xa2\x01\x35\xdf\x10\x03\xa8\x39\x9f\x5f\xe6\x3c\x85\xe9\x1e\x72\x8b\xf4\x2c\xbd\xc1\xd9\x71\x98\x63\x47\x24\x70\x26\x2d\x36\x49\x4d\xed\xad\xcd\xfa\x5b\x1c\x0e\x8b\x8b\x0c\x63\xc8\x6a\xf3\x4e\x02\xec\x4e\xd3\x79\x8e\x4f\xae\x71\x52\x40\x58\xa9\x6f\x49\xcf\x12\x5a\x7d\xcb\x27\x8c\x26\xc4\xcf\xc3\x9b\xf0\x8e\x45\x77\x16\xe9\x7c\x38\xe1\xef\x99\x58\x0e\x69\xc2\x5c\xe9\x4e\x12\x06\xe8\x10\x71\x50\x78\x4a\x9b\x3b\x5d\x04\xa3\xea\x27\x33\x26\x49\xd0\x78\xf9\x7c\x62\xfc\x3c\x60\x3f\x4e\x46\x23\x3a\xbd\x2d\x16\xcc\x99\x34\x32\x05\x3b\x1e\x68\x8f\xb5\xfb\x5d\x96\xdb\x6c\xc0\x23\x1a\x45\x96\xb4\xe3\x30\x8e\xe9\x3e\x6d\x6a\xc4\x94\xc5\x2a\x98\xbb\x23\x92\x44\xcf\xcf\x5f\xbd\x4e\x23\xec\x60\xc4\x6b\x80\xeb\xe0\x10\x3b\xcc\x68\x0a\x53\x04\x97\xc1\x37\xdc\x86\xc0\x30\x91\xd5\x1c\x90\x91\x33\xd1\x1a\x02\xaa\x55\xb6\x3d\xaa\x2e\x77\x5a\x5c\x04\xa2\x74\xc8\xdc\x28\x5d\x79\x21\x16\x8f\x3b\x8c\x09\x33\x3c\x46\xc5\xe4\x1f\x58\xfc\xfa\xb8\x5c\x6e\x28\xcf\xc3\xcd\xd4\x0b\x9f\x4a\x07\x23\x44\x46\x4e\x24\x5b\x81\x22\xad\x9d\x2c\x2b\x20\xe7\x1c\xb4\x0b\xc2\x00\x92\xe3\xe8\x4d\x58\x4c\x50\x12\x98\x37\x1c\xe4\x32\xe8\xc2\xf3\x91\xa3\x3a\x85\xfe\xb9\xcb\x6b\xe9\x24\xc1\xce\x97\x42\x7b\xd0\x6c\x20\xdf\xa5\x72\x07\xbb\x05\xe5\x87\x05\x5a\x2e\x55\xf1\x96\xa7\x07\xc9\x72\xb9\x93\xd9\x76\xb1\x5c\x72\xcf\x08\x84\x60\xbc\xd6\xcb\x22\x41\x0b\x6d\x16\xbc\x03\x9e\x82\xb1\xe0\x3e\x04\x7d\x3c\x38\xc8\x6c\x3b\x73\x12\x7a\xbe\xba\x0b\x98\x09\xf5\x5a\xfa\xa6\xb1\xf4\xc2\x43\xdb\x76\xee\xfa\xc3\x41\x30\x76\x86\xcc\xf6\xd9\x4e\x91\x84\x12\x2c\x2d\x2e\xbd\x37\x6f\x88\x33\x44\x50\x04\x46\xff\x21\xd1\xad\xbc\xda\xa8\x7b\x65\x85\x96\x16\x46\x11\x5b\x57\x67\x24\x2f\x70\x82\x33\x07\xc3\x25\x82\x96\xfb\x16\x5b\x83\xd3\xf4\x1a\x5b\x90\x20\x7d\x01\x50\x2a\xa5\xf7\x5b\xeb\x69\x7b\x64\x56\xc5\xb6\x9c\xfe\x25\x0c\x07\x88\xe7\x58\x0e\xd9\x20\x84\x74\x10\xc2\x2d\x06\x21\xd4\x06\x21\x6c\x0c\xc2\xa6\x9e\x6e\xd5\x0f\xd9\xc4\x70\xd0\xd4\x8e\xa7\xee\x8b\x2c\x1c\xd3\x4b\x65\x30\x8e\xd3\x44\x3d\x2e\xe0\x0e\xa1\x12\xae\xb6\xdb\x3a\xf9\x71\x6b\x83\x19\x71\x8a\xf3\x3c\x1c\x63\xc6\x18\xb3\x34\xc6\x35\x96\x18\xc6\x38\x2b\x56\xc0\xdb\xf0\xfa\x57\x1a\xc2\x44\xcd\x16\x58\xb4\xde\x7b\xa0\xdc\xb0\x44\x17\x43\xa8\xc0\x6e\x2a\x64\x9b\x27\xab\x90\x6d\x84\x25\x27\xd4\x70\x67\x9a\xa6\x87\x3a\x2e\x15\x91\xbd\x2f\x21\x46\x90\x34\xdc\xa4\x5b\x5e\xe0\x7d\x2e\x21\x41\x3e\xdb\x05\xe8\x2a\x3e\x5f\x63\xac\xd8\x80\x6d\xe3\x3e\xf3\xdd\xbf\x3f\x83\x24\xa8\x74\x3c\x97\xba\x8e\xa7\x99\x43\x07\x14\x15\x6e\x9b\xbe\xcb\x84\x8b\xd8\xa4\x7e\x69\xd1\x3f\x25\x5b\xa0\x53\x34\x62\xa2\xbf\xcb\xa4\x52\xa5\x6f\x5d\x8d\x51\xa1\xca\x58\x24\x21\x05\x09\x63\xcd\x94\xb3\xf7\xec\x19\x9d\x10\x31\xab\xb5\x53\x83\x67\x95\xc0\xa7\x6f\x2b\x21\x4b\x13\x86\x04\x1e\x8e\x91\x82\xea\x89\x29\xb2\xd7\x91\x2d\xde\x25\xe1\xf0\xea\x32\xcc\x24\x66\x42\x89\x9c\x2b\x82\xe0\xf8\xde\x2b\xd7\x00\xbb\x08\x03\x87\xe8\x92\x8e\x86\x77\x21\xd2\x6b\x1a\x90\x17\x12\x39\xc4\x27\x48\xc5\xfd\x43\x1c\xe8\x31\xfe\x6c\xdd\xd3\xee\xbd\x24\x11\x7e\x2e\x2c\x4a\x26\x0f\x60\x99\x66\x38\x78\x8b\x92\xd1\x22\x8d\xa7\x8c\x0c\x9e\x32\x0b\xb0\xab\xc4\x2d\xc9\xf7\xde\xd0\x1d\x8b\x01\x5f\x88\xf1\xe0\x37\x34\xdf\x81\x0f\xcc\xef\xeb\x28\x9e\x67\xf2\x9d\x56\x17\x02\xc9\xad\xc6\x42\xac\x4b\x73\xee\x34\x90\x26\x27\xb4\xab\x70\x5b\x5d\xe3\x88\x39\x0c\x88\x5f\xf4\xec\x70\xc2\x7f\xde\x92\x82\x79\x0a\xf0\x4b\x1c\x31\x47\x01\xfe\x83\x96\x3a\x66\xbf\x5e\x31\xd9\x92\xd5\xf9\xae\xba\x71\x86\xc3\x6b\x0c\x5f\xe8\x8d\x19\x4e\xe0\x82\xf2\x4c\x16\xb5\x61\x8c\xde\x9b\x00\xbb\x17\xca\x50\xa7\x1c\x40\xe0\x6d\x35\xaa\x6f\x7a\xef\x12\xff\x0d\xbc\x0e\x74\x93\x9e\xaa\xe0\x73\x55\xf0\x75\x6f\xc1\x13\x82\x7d\x2c\xdc\xcb\x5a\x38\x3b\xe0\x5b\x52\xf0\x07\x66\x3c\x9e\xff\x1a\x5e\x19\x6d\xe0\xc3\x7d\xb8\x86\x85\xd7\xf0\x3e\xea\x14\xb1\x0d\xe6\x47\xfb\xac\xd3\x07\xda\x9c\x57\xee\x0e\xcd\x19\x37\xf6\x0c\x31\xbb\xec\xea\x84\xaf\x48\x4b\xcd\x6c\x75\xcd\xcf\x71\x7c\xf6\xd4\x85\x2c\xc0\x67\x94\x5d\x57\xf3\x59\xfd\x64\xb3\x49\x7f\xce\x58\xaf\x9a\x33\x69\x81\xd5\x32\x8f\x16\x58\xcd\x39\x33\x8a\xf2\x9e\x0e\x10\x9c\x56\x67\x02\x04\xdf\xf8\x0f\xee\x70\xb9\xe3\x21\x38\x0b\xbe\xf5\xbd\x01\x3c\x0f\xbe\xf5\xbb\x03\x78\x61\x4a\xe5\x68\xc1\x32\x67\xf2\x80\x07\xe1\x93\x58\xc5\x28\x51\x2e\x77\xd4\x10\xe3\xc7\xb6\xcd\xa3\x9b\xb0\x6d\x3b\xcc\x99\xef\x82\x4c\x71\x3a\x2f\x9c\xd3\x4a\x8e\x53\x97\x41\x8e\x0b\xf9\x5c\xff\xee\x0b\x87\x3b\x8a\x15\xfc\x99\x45\x0f\x11\x98\x9d\x58\x37\x1d\x6a\xbe\xd8\xf6\x11\x15\x1d\xeb\x2e\x85\xcd\x56\x30\x11\xe8\x0b\x0c\xe1\x48\x9c\x6f\x3e\x34\xfc\x10\x5b\x5e\x82\x07\x2b\x4f\x41\x68\xc1\x7b\x3e\xa4\x4d\xe0\x97\x17\xbd\x0b\xdf\xdd\xff\xdb\x10\xf1\x14\xd1\x17\xec\x5b\x4a\x04\x59\x2d\x06\x5e\xdb\xf6\x17\x19\x87\x20\xdc\x51\x9b\xf2\x2b\xd3\x2e\x5a\xf0\x00\xc1\xca\x22\x97\xf1\x3c\xb3\xe0\x83\x31\x1c\xa2\x70\xab\x1c\xdb\xa8\xb2\xb5\x94\xac\x95\x8d\xe0\x35\x3c\x80\x2f\x54\xd2\xfd\x62\xdb\x67\x9c\x51\xd7\x65\x98\x93\x5a\xae\x54\xed\x30\xec\xd7\x68\x67\xec\x60\xba\xa2\xc9\xf0\x2a\xbc\x09\xef\x2c\x7a\xae\x98\xad\x92\xa2\xb6\xd1\x82\x44\x5c\x26\x8b\xb6\x01\xa6\x09\x57\x80\xcd\x30\x98\xc1\x11\x02\x7d\x05\x1b\xed\x3e\xb6\xed\x63\x07\x23\xf8\xe0\xa0\x12\xf4\x85\x6d\x94\x7a\x67\xdb\xef\x68\xa9\x07\xb4\x94\x90\x10\x0f\x5b\x00\x8a\xcd\x7e\x85\xb3\x19\x0e\x19\x30\x29\x49\xfc\x2f\x20\x58\x8e\xff\xd6\xa4\x99\xe7\xf4\xbc\x5f\x22\xb8\x43\xa0\x18\x94\x7f\x0b\x15\x83\xf2\x6f\x80\x33\x23\xff\x04\x24\x83\x6a\xa9\xc4\xa3\x95\x5c\x21\x50\x8c\xcb\x3f\x07\xb1\x06\xfd\xcf\x10\x55\x18\x34\x45\x3a\xb3\x04\xf4\x5a\x7a\x93\x58\xbe\xc5\x52\xff\xbd\x42\x30\x5f\x2e\x1b\x6e\x9f\xb5\xe9\x97\xa2\xd1\xa5\x14\x85\x12\x9e\x92\x5c\x24\x25\xdf\x28\xc9\xb2\x00\xfe\x67\x25\x24\x81\x0c\x8c\x7f\x56\x42\x1a\x2c\x56\x2a\xa2\x4a\x20\x2b\x9e\x32\x4d\x2b\x95\xc9\x78\xa5\x7b\x8f\x4b\x88\x55\xad\xf4\x57\x1e\x2c\x38\xa8\x01\xfd\x31\x0c\x16\x2c\xf8\x9e\x5e\xcf\xc5\x35\x43\xc6\x97\xc0\x07\x5c\x46\xd3\x42\xe8\x55\xe4\xfc\x47\x47\x84\xfa\x9b\x10\x91\x75\x60\xc7\x5c\x88\x6c\xd0\x40\x6f\xac\x49\x8b\x31\x57\xcb\x66\x42\x0d\xbc\x16\xfd\xa7\x8e\xe1\x56\x83\x46\x10\xd9\x3e\x6b\x42\x7c\x51\x93\xd9\x57\x0a\xc3\xb5\xf7\x42\x98\x23\x84\x5a\xf0\x0c\xda\x3f\x93\x7c\xe7\x67\xe2\xe6\x67\x24\x9a\x46\xb3\x27\xe9\x77\x7d\x44\x83\xa1\xa4\xdd\xca\xdb\xbb\xd5\xfa\xc9\xe4\x47\x7c\x32\x6e\x7e\x52\x00\x59\x34\x7b\x48\xbe\xeb\x73\x3a\xd5\xd2\x2e\x0e\xdb\xbb\xd8\xf6\xc9\xe4\x87\x7c\x32\x66\x9f\x64\xa7\x96\x51\x4c\x66\xfe\x4e\x17\x1a\xa7\x17\x96\x6d\x9f\x20\xf8\xc2\xe2\x7e\x9f\x6a\x2a\xdf\x0b\xa2\xeb\x6e\x99\x46\x8a\x29\x6e\x99\x67\x74\x0e\x19\x95\xd5\xe7\xe4\x45\x9a\x4d\xd9\xe9\x32\x8d\xab\xf0\xa9\x0c\x47\xf3\xa1\x21\xb9\x54\x41\xc2\x1d\x15\x84\x0b\x99\x6d\x2b\x29\x98\xde\x90\x11\xba\x59\x3f\x19\x20\xc0\xb4\xdd\x25\x57\x28\xbf\x21\x2a\xbc\x48\x66\xf0\xe4\x42\xc5\x5b\x12\xbc\xd1\xe2\x26\x5f\x13\x03\xbf\x7f\x16\x66\x39\x3e\x4d\x0a\x07\xf7\x8b\x01\x74\x3d\xb4\x5c\x7a\xac\xbe\xcf\x24\xb0\xe6\x49\x84\x47\x24\xc1\x91\xb5\x23\xc3\x36\xf9\xae\xdc\x63\xa2\xc3\x59\x78\x97\xce\x0b\x2e\x40\xf8\x9a\x30\x01\xaf\x48\xb0\x60\xc0\x1c\x24\x26\xc5\x9d\x6f\x4d\x48\x14\x51\xe9\xb2\x0d\xe0\x2b\xbd\xc6\xd9\x28\xa6\x27\x59\x59\x4a\xe0\x2e\x79\xb0\x0a\xe5\x47\xf1\xb4\xdf\x1d\x0f\x59\x25\x1c\x6e\x77\x9e\x94\x20\xe0\xec\x44\x99\xa5\x37\x5c\x11\x44\x2f\x5e\x85\xb7\x4c\x0d\xc4\xae\x49\x62\xfa\x93\x77\x85\x1f\x79\x5e\xdc\xc5\x58\xa0\x77\xc6\x73\x0c\x51\xf3\x20\x21\xbf\xc0\xb4\x3d\x37\xb9\xf8\xf3\x8a\xc1\xfc\x89\xba\x99\x7f\xf2\x5d\xcc\x9d\x92\xe3\x39\xa6\x02\xf2\x28\x20\xcb\x65\xae\xab\xd2\xb9\xec\x36\x47\x2a\x08\x63\x52\xd7\xbb\x4f\x99\x32\x1b\x26\x08\xae\xeb\x8f\x2e\xab\x1b\x9e\x4a\xa0\x2f\x22\x9c\x4a\x96\x3e\x9f\x0a\xdc\xb7\xc1\x98\x0a\xdc\x37\x6b\x84\xc9\x9a\x42\x1c\x92\x40\xc8\x64\x4c\x5d\x32\x9d\xcd\x0b\x1c\xbd\xa3\x9d\x71\x0a\x04\x59\x70\xad\x34\xe7\x19\x1f\x2c\x97\x19\x15\x83\x84\xff\x85\x8c\x0f\x5c\x50\xf0\xbf\xcb\x25\x76\x67\x71\x38\xc4\x93\x34\x8e\x70\xb6\x5c\x5a\x74\x98\xfe\x33\xa1\xbb\xba\x28\xea\xe6\x31\x19\x62\x67\xb7\x8b\x6c\xdb\x11\xf7\x1e\x06\x56\x47\xe4\xe8\x4f\x83\xa4\xcf\xdd\x45\x99\x67\xab\x35\x00\x12\xbc\x26\x4e\x02\x32\x39\xfd\xae\xd0\x07\xa0\x87\xe6\x6d\x2a\x3b\x20\x88\x45\x61\xe5\x0f\x4b\xcb\xee\xb2\xb6\xaa\x37\xc4\xb3\x22\x9d\xc9\x07\x90\x07\x99\x9b\x0f\xb3\x34\x8e\xb9\x5e\x7e\x97\x1c\xc8\x9e\x59\xb7\x16\x6b\xd8\xb0\x51\x04\xe6\x41\x7e\x30\xb2\x6d\x67\x1e\xbc\x0a\x8b\x89\x3b\x0d\x6f\x9d\xd7\xf3\xe9\x25\xce\x9c\x11\xfa\xdb\x90\xee\x25\x10\x6a\x8f\x49\x22\x1f\x87\xe2\x31\xab\x38\x0a\xf4\x0a\xe6\x94\x79\x3d\x74\x74\x87\xde\x20\x08\xd2\x1e\x79\x18\xfb\x1e\x82\x19\x2f\x19\x5e\xe6\xce\x7c\x37\x47\xff\x08\xba\x07\xb7\xad\x29\x10\x2f\xe5\xc4\xfd\x63\xcf\xb3\x6d\x87\xe5\x17\x55\xaf\x3a\xd8\x4d\xe7\x05\xce\x78\x4f\xd8\x74\x2f\x97\x1e\xda\x8d\xd0\x3f\xbb\x74\x0a\xe5\xfa\xdd\x09\x82\x19\xea\x39\xaa\xae\x87\x41\x17\x16\x6a\x71\xcf\xa0\x5e\x89\x1f\x95\xc8\xc7\x2c\x40\x0b\xfa\x21\x8c\xc0\xa0\x86\xc1\xea\x63\x16\xd7\x4c\xe7\x89\x71\x53\x7d\x36\xf0\xe0\xc6\x41\x5a\xf6\x92\x95\x27\x93\x0c\xf3\x78\x01\x6c\x9c\x4d\xb0\xcb\xce\x5d\xce\xfa\xf3\x47\xf5\x2e\x3b\x81\xdc\x0c\x10\x7c\x26\x46\x83\x78\x23\x56\x6a\xda\xb5\xf6\xd2\x0a\xe6\xab\xf5\xc2\x2d\xca\x70\xf3\x24\x42\x59\x7d\x98\xe1\xb0\x76\x1c\xe1\xb1\xc0\x73\x90\x3c\xc9\x38\x09\xe8\xc3\x35\x5b\x2e\x6f\x1c\x04\x89\x6d\xd3\x47\xfc\x60\x30\x05\xca\xae\xfc\x11\xb0\x75\x6c\x6e\xbd\x82\x3d\xdf\x35\xc8\xa2\x62\xe5\x77\x8a\x2a\x7a\x92\xab\x33\x45\x71\x09\x43\x54\x42\xd4\x12\x82\xa6\xf5\x62\x61\x85\x19\x09\x77\xe5\x7b\xb5\xcc\x02\x95\x8e\x2d\xc3\x61\x74\x9e\xc4\x77\xb4\x04\x33\xfb\x40\x11\x5e\x72\x69\x76\xb7\xdb\xd6\xf0\x12\x5e\x11\xda\x00\x7e\x8a\x57\x1b\xe2\xa9\x6e\xb7\x55\x9a\x83\x1d\x87\x67\xb6\x23\x39\xfb\xeb\x60\x64\xdb\x1e\x53\x5d\x8b\x34\x26\x15\xe8\xc3\x37\x4d\x0a\x50\xca\x09\x19\xdf\xde\x95\x1b\xf8\x4e\x50\x3d\xec\x77\x07\xb6\xad\xff\x92\x93\x8f\x6d\xdb\xa1\xed\x11\x90\x11\xb6\x6d\x59\x3b\x81\xdc\x71\x96\xcb\xc2\xb6\xd9\x53\xa1\x05\xff\xcd\x2c\x64\xdc\x65\x7b\xf8\x99\xb1\x87\x07\x6d\x7b\xb8\xbe\x7b\xeb\xfb\x39\x3c\xdf\x6a\x53\xed\xf3\xc9\x8a\x70\x3e\xcc\xc8\x25\x8e\x2e\xef\x18\x17\xe6\xda\x54\xba\x47\xc4\xb8\xe0\xc0\xd9\xf4\x06\x0b\xee\xa9\x3c\x14\x70\x5e\xe1\x0c\xb3\x29\x1d\x06\x8e\x0c\xe9\xaa\x75\x87\xed\xbb\xca\xe7\x2e\xe2\x71\x54\x87\x51\x9a\x25\x6c\x65\x8c\xe8\x8b\x0c\xd7\x11\xb0\x1e\xe3\x31\xab\xb4\xa7\x23\xdb\x16\xa8\xc3\x11\x53\xb9\x92\x64\x36\x2f\x2a\xb5\xa4\x81\x3c\x6c\xb1\x87\x96\x80\x1f\x66\x3f\xb8\xf2\xd0\x40\x20\x5e\x94\x02\x78\x98\x15\x78\x8b\x47\x70\x4b\x9b\xc1\x35\xe2\x40\x45\xbf\xb8\x20\x31\x49\x30\x82\x9b\xaa\x1d\xb7\xb6\x7d\xcb\x94\xb0\x54\xce\x14\x2a\xd8\xa3\x78\x9e\x09\x05\xac\x10\x51\x8e\x2b\x8b\xbd\x50\xbd\xf2\xb1\x63\x5a\xd7\xe4\x17\x7c\xf7\x3c\xbd\xe1\xaa\x57\xf6\xeb\xfd\x8c\xe9\x5b\x35\x06\x0a\x6f\x03\x9e\x25\x96\x2e\x11\xa6\x62\xe5\x51\xaa\xef\xe6\xa3\x11\xb9\x85\xcf\x52\x04\x7a\xa5\x89\x40\x87\x9a\x08\x74\x2a\xa3\xc8\xaa\x41\xfe\x26\x43\xc9\xce\xaa\x51\xf8\x26\xc3\xc4\xbe\xc1\x73\x25\x19\xbd\x68\x51\xb1\xd6\xa9\x84\x6b\x57\x25\x85\x88\x9f\x2f\xb8\xf2\x67\x43\x4c\x99\x4e\x18\x66\x58\x99\x4e\x13\xf4\x27\x25\x88\x5a\x8c\x18\x89\x18\x08\xb3\x3e\xf7\xf2\x86\x54\xcd\xca\xe9\x64\x08\xcd\x19\x57\x05\xab\xb9\xb4\xc0\x4a\x78\x73\xf8\xb4\x71\x25\xad\x94\xfa\xa4\xcf\x05\xbd\x92\x9d\x51\xd3\x25\xaf\xdf\xcf\x2c\xb0\xb4\xa9\x62\x5a\x57\x3e\x51\xec\xb2\x9a\xa6\x4d\x72\xa4\x3e\x3d\x7a\xb0\x9b\x90\x2b\x8f\x02\xce\xce\xc6\x7c\x5e\x7a\xe2\xaf\xff\x1c\x3e\xd4\x45\xcd\xa3\x4a\xd4\x7c\xa0\xab\x6b\xdf\xaf\xf3\xb8\xf0\x84\x63\xc5\x4b\x2a\x8b\x8e\x99\x63\xc5\x7b\x04\xbf\xd1\x5f\x77\xf0\x12\xc1\x57\x7a\xf5\x00\x7e\x43\xf0\x8b\xa1\xf5\xed\x22\xf8\x35\xf8\x85\x0a\xa1\x3f\x05\xbf\x50\x21\xf4\x93\xf8\x8a\x38\xb7\xbc\x21\x5c\xda\xf9\x18\x5c\x10\x67\xc1\x8e\x55\x3e\x06\xf3\x24\xe5\x7f\x02\x7e\xca\xf2\xfb\x15\x61\x68\x94\x20\xa6\x9e\xef\x22\x67\xe1\x25\x8e\xf5\xe9\xcc\xf0\xd7\x39\xe1\x2a\x74\x91\xb2\x6f\x50\xa2\x83\x8f\x3c\x90\x0b\x47\xc1\xa7\xde\x27\x79\xed\xff\xba\x72\x13\xdf\xf9\x64\xdb\x73\xdb\xfe\xd5\xb6\x9d\x9f\x58\xaf\xae\x6c\xfb\xca\xe1\x12\xcd\x27\x98\xc3\xaf\x70\x25\x14\xbb\x3f\x07\x9f\x6c\xfb\x13\x5d\xc5\xec\x6b\xf0\xbb\xfc\x7d\x32\x9d\x15\x77\x80\xf1\xba\x61\x66\xfb\x4b\xef\x67\xdb\xfe\xd9\x41\xfe\xef\xb6\xfd\x3b\x8b\xf7\xef\xff\x0c\xbf\x0f\xd0\xc1\x99\x29\x76\x7c\xb0\x6d\x8c\xe5\xe6\x7f\x54\xb2\x82\x47\x80\x31\x7c\x58\x23\x51\x61\xec\x3c\xa8\xf4\xcb\x95\xbb\x4d\x81\x83\x6b\x48\x24\x62\x95\xda\x45\xc7\x3c\x52\xfd\x6b\x89\x0e\x2c\xee\xe2\x54\x1d\x0f\x0b\xdc\xab\xbf\x20\x97\x93\xff\x95\x47\xc4\x9d\x95\x90\x60\x5e\x05\x93\x09\x90\x7f\xd3\xdb\xf9\xbc\x5c\xbe\x5a\x2e\x0f\x7b\x4e\xfd\x6d\x26\x87\x7c\x06\x41\xff\xfe\x2b\xfa\x32\x82\x02\x07\x87\x04\xf9\x05\x0e\x2a\xc1\xc1\xaf\xbf\x5a\x7d\x6d\xa3\xc6\x9b\xcd\x46\x8e\xf9\x6a\xa2\x87\x9a\x30\x2b\x9c\xa3\x34\x8d\x71\x98\x38\xa7\x72\x46\x4f\x5b\x7c\x15\xb6\x57\x04\xc7\xc2\x1d\x6e\x2d\x48\xf8\x47\xbe\xfd\x2d\x97\x0a\x87\x0f\xa1\x01\xe4\xf0\x51\x6d\x7e\xb6\x1d\x57\x1b\xe1\x47\xbe\xe9\xd1\x7b\x7c\xf7\x9b\xd1\xcb\x2a\x78\x58\x51\x34\xbb\xcd\x2f\xe1\x13\xff\xa1\x96\x12\xdc\xd0\x1b\x8a\xc3\xc1\x29\xfd\x19\x6a\x23\x01\x91\x76\xe7\x24\x89\x28\x0b\x4e\x72\x4c\x0f\x1b\x1f\xc5\x76\xc7\x6a\x60\x57\xcf\xe9\x23\xa4\xe2\xf5\x75\x42\x7e\x50\x79\x4d\x61\x79\x79\xc1\xfc\x82\x98\x60\xc5\x5d\x84\x6c\x5b\x15\xe3\x2d\x76\x10\x08\xf5\xb6\x52\x5a\xbf\x40\x70\xda\x54\x5b\x13\xf7\x4d\x96\x32\x28\x00\x09\x6f\xc4\x65\xce\x7a\xc1\x02\x9b\xf3\xc5\x37\x27\xc2\x13\x60\x5b\xbe\x18\x53\x68\xee\x59\x7e\x02\xfa\xa6\xe5\x13\x50\x9b\x96\x1f\x82\xbe\x2d\xf9\xc3\xca\xd3\xba\x9a\x3a\x20\x91\x3f\x81\x34\x39\x4c\xc8\x94\xd9\xcb\xd8\xe8\x1a\x43\x84\xb1\xc3\xf2\x07\xd1\x8a\x77\x29\x6b\xda\x1d\x86\xc9\x10\xc7\xdc\x6b\x22\x94\x2f\x52\xea\xea\xa9\x71\xf2\x45\x77\xad\x5b\x8b\x21\xec\x50\xd2\x3b\x01\x6d\x87\xf1\xdf\x54\xd2\xf2\x5b\x90\xcc\xcf\xff\xe8\xca\x4b\x10\xcb\x4c\xf0\x0d\x50\x7b\x16\x53\xf8\xb3\x3d\xcb\xbf\xe0\xeb\x76\x25\x75\xb3\x75\x0e\x63\x4d\x9c\x5b\x41\xb4\x8c\xdc\x58\xe9\x57\x8a\xe6\x3e\xba\x1a\xa7\x56\xcf\x5f\x56\xf7\x38\x55\xb2\xbb\x87\x0d\xd2\xd4\x6f\x33\xfa\xcc\x71\x98\x0d\x27\x74\xd0\xce\xd4\xf3\x8b\xbb\x19\x7e\xc7\xee\xaf\xa0\x5f\xde\x24\x93\x88\xe9\x16\x6f\x4c\x10\x63\xf1\x18\xc1\x58\x88\x6d\xb6\x2d\xaf\xe8\x5d\xc1\xd5\xe9\xcf\xde\xa7\xea\xbe\xcf\xb6\x87\xb2\xfd\x2c\x46\x46\xce\xce\x87\x4a\x8b\xc8\x57\xc1\x72\xf9\x40\xf7\x43\xe4\xd0\x79\x85\x06\x62\xc6\x01\xcc\xc4\x14\x7c\x21\x74\x0a\xba\x08\x1d\x54\xcc\x5f\x68\x59\x4a\x54\x2a\x34\xc0\xc6\xe9\x04\xb2\x80\xd6\xc5\x4f\x38\xc9\x3f\xbb\xbd\x64\xb7\xeb\x7b\x08\xd2\xa0\x7b\x90\xfe\x23\x39\x48\x1f\x3e\x44\x59\x3f\xdd\xed\x0e\xb4\xb3\x4b\x3a\x38\x18\x2b\x41\x95\x77\x9f\x5f\x0b\x33\xed\x18\xfa\x78\x20\x99\x5b\x86\x10\x9c\xdb\xf6\xb9\x69\xc2\x35\x0b\xd0\x71\xe1\x8b\x48\x1f\x96\x8a\x78\x7a\x54\x12\x4d\x67\x54\x3c\x0b\xc7\x21\xe7\xd8\xbe\x23\x8d\x59\x63\x29\x1b\xf3\xa6\xb0\x4b\x6d\x2a\xd8\xef\xde\x27\xed\x09\x9d\x0c\x8f\xe9\x7c\x11\x82\x08\x5e\xf7\x5e\x3b\xb5\x0d\xee\x23\x2c\x4c\xd9\xca\x3f\x2d\x91\xe6\xea\xf4\xe2\xbb\x5d\x9d\x20\x09\x64\xa0\x85\x11\x52\x01\x29\x8f\x50\xf3\x8b\x9e\xfb\x78\xcf\x77\xf7\x37\xa7\x09\x10\x6f\x58\x5b\x46\x46\xe3\x8c\xb2\x86\x34\x58\xc8\x2f\x59\x5e\x67\x87\xa7\xab\x0e\x59\x30\x32\xa9\x1e\xc9\x46\x28\x53\x91\xf5\xbf\xc7\x71\x7a\x19\xc6\x96\xbf\xb0\xfe\xf7\x15\xbe\x1b\x65\xe1\x14\xe7\x1d\x83\x51\x59\xfe\xa2\x84\x95\x4f\x25\x1b\xf3\x17\x65\x09\x5b\xb9\x6c\x75\xa1\xe1\xa5\x65\x84\x64\x6b\x79\x18\xac\xae\xdb\x7d\xf6\xf4\x09\x9e\xae\x4c\x17\xd1\x96\x24\x63\x38\xcf\x72\x16\xea\x47\xcf\x2f\x2b\x52\x66\xb4\xb9\x44\xad\x0f\x0f\x63\x6d\x54\xac\x4e\x7e\x43\xc2\x26\x94\x25\x02\x6d\xe7\x65\x31\x18\x42\xcc\xac\x05\xe7\xe8\xfb\xaf\xf6\xfb\x24\xe1\xc9\xaf\x79\xfa\x83\x12\xb4\x3d\x97\xfd\x94\x0c\x55\xf3\xfa\x52\x82\xc6\x13\xb0\x66\xb7\x1d\xaf\x53\x61\x2d\x3c\xa5\x77\x78\xbc\xae\x56\x91\xa5\x5e\xbe\x48\x67\xfe\xa3\xb2\x99\xd3\x61\x4d\xe4\x08\x63\xa2\x3c\x00\x42\x0b\xec\x60\x63\x93\x89\x90\x03\xed\x7e\xeb\x5a\xb8\x4f\xcb\x45\x28\xa6\xa7\x4f\xfc\x90\xdb\x20\xf9\xcc\x57\x6e\x7c\x32\x24\x6b\xd2\x20\x1a\xde\x77\xdf\x83\x0f\xf8\xf2\x8a\x14\x17\xe1\xec\x25\x19\x4f\xd8\x12\x6e\xcb\x37\xa6\x48\x45\x66\x2d\x97\x8e\x77\x9e\x99\x4a\xcf\xd8\xad\xfd\xf6\x6d\xbd\x2a\x24\x7d\x68\xe8\xdb\xd3\x9c\x45\x8e\xf9\xbb\x37\xac\x41\xbb\x6c\x50\x77\xf5\x23\xa3\x9f\xf0\x02\xd3\xf4\x5b\xcb\xfd\xdd\x69\xbe\xee\x9d\xd5\x0f\xb9\xf7\x85\xbf\x10\xf1\x50\xbe\xc7\x02\xd0\x94\x68\xd4\x1e\x9f\xa6\x9a\xc9\x77\xdd\xdd\x48\x41\x2e\x50\x7e\x21\x1f\x72\x37\x02\xda\x6b\xab\x7a\x99\x41\x14\xf4\xa3\xb0\x08\x77\x73\x96\x82\x26\x18\x85\x71\x8e\x07\x9d\x87\x9d\x07\xda\x3a\xe9\xd8\xb4\xa6\xf5\x03\x92\xae\x18\x90\x74\xcd\x80\xa4\xeb\x06\x24\x55\x03\xb2\xf6\xbb\x44\x2f\xd6\xf8\xba\xf6\x74\xc5\x67\xcc\xf7\xdb\x8b\xd4\x03\xfd\x24\xa3\xee\xb2\xf1\x57\x23\x3c\x2f\x52\xc1\x89\x5b\xa8\x6a\xdf\xf3\xbc\x7c\x3d\x51\x72\x44\x7d\x53\x08\xaa\xf1\x02\x30\xe5\x36\x5f\x6a\x83\x65\xb2\x44\xa6\x17\x97\x2b\x4d\x4b\x96\x52\x93\xc0\x28\x5d\xd0\xb1\x32\x88\x82\xb2\xce\x11\xc1\x31\x3d\x95\xb7\x52\x4d\x55\x40\x54\x78\x58\x63\x92\x35\x39\x50\xdd\xd2\xe4\x48\xba\x01\x19\x3e\xaa\xa7\xb4\xc0\x51\x98\x33\xa8\xfe\xe7\x04\xc1\xd1\xbd\x42\xb2\xde\x27\x11\xce\x98\x08\x5b\xf7\x30\xaf\x4e\x61\x71\xa5\xfd\x0b\x6d\x9b\x27\x09\xac\xa9\x1f\x4d\x7f\x73\xa1\x7e\xe4\xee\xa6\xd5\xc1\x2c\xaa\xea\x99\xdb\x36\xcf\xb3\xc6\xa4\x0a\x33\xc5\x1a\x57\xc8\x8d\x60\xb2\x32\xf6\x4b\x35\xda\xd0\xb5\x19\x6a\xb2\xba\x86\x4c\x57\x80\x31\x3d\xd3\x6a\x8b\xc5\x0b\xd2\x72\x1e\xc6\x79\x7d\xc7\x27\x60\xf8\x6e\x1b\x1e\xec\x3b\x89\x6d\x13\x77\x2e\x9b\x89\x40\x5d\x0a\x9d\x81\xb6\x01\xc5\x60\x36\xd6\x1f\x6a\xdb\x60\xa4\x63\xf1\xcc\x98\x97\x4f\x89\xd0\xc1\x11\x71\xa7\x73\x42\x97\x40\x60\x31\x02\xe0\x76\xbb\x0f\xdf\xef\xc3\xde\x40\xc9\x78\xbc\xb7\x1a\x25\xe3\x29\xb2\x0e\x36\x25\xf1\x2a\x4d\x41\x81\xb3\xca\xce\x43\xc6\x0a\xf9\x86\x45\x57\x64\xf7\x49\xb9\x52\x84\xa8\xef\xda\x96\xfd\xa0\x1a\xc6\x70\x54\x68\xd8\x0c\xc2\x41\x66\x43\xc4\xb3\x36\x0b\x94\x1b\xb7\x55\xe1\x5b\x7b\xdb\xc3\x64\x08\xcb\xbe\xca\x8f\x23\x36\x6e\xff\x7f\x59\xd6\xff\x6a\xf5\x15\x58\x95\x9b\xe8\xa3\xe3\xc9\x0c\x23\xdb\x66\x00\xd9\x52\x68\x86\x4d\x99\x42\xce\xe7\x45\x89\x80\x79\x9c\xe0\x8c\x59\x09\xf3\x46\x7c\x37\x8e\xd4\x58\x35\x5b\xde\x45\xbc\x24\x17\xe9\xb6\x99\x16\x2d\xb5\xd5\xca\xea\xfc\x4b\x3c\x4a\x33\xdc\x98\x9d\x16\xc8\x8a\x62\xdd\x44\xfc\xe7\x7f\x7a\x5e\xe8\x6d\x33\x1d\xeb\x06\xde\xb4\xb0\x0b\x75\xed\xf6\xe7\x96\x95\xc3\xcb\x63\xeb\x59\x14\xa7\xda\x1e\xd1\xaa\xae\xaf\x25\x4c\xfd\x68\xb1\x06\x26\x64\xf3\x58\x9a\x60\x21\xed\x6d\xe1\xf6\x6f\x2b\x62\x29\xbd\xe9\xa6\xbb\x85\x38\x6f\x88\xdc\x4a\xcc\x6e\xdb\xaf\x9b\x7b\x74\xcb\xf6\xdb\xb6\x09\xd2\x0d\xf0\x88\x20\x78\xf0\xd7\x06\xf8\x3f\x7e\x03\x7c\xd0\xbe\x01\xbe\xff\x23\x9a\x8d\xe2\xde\x9b\x20\x64\x6d\x2f\x79\x7f\x5f\xfd\x12\x7d\xb6\x71\xeb\x6c\xc4\x6e\x65\xe2\x9c\x28\x13\xc9\xad\x09\xbe\x52\x05\x79\x82\xbc\x35\x25\x37\x73\xbd\x06\xb6\xdd\x0f\xde\x75\xd6\xe0\x9e\xb4\x0c\x6b\x77\x0d\x6c\x17\x7d\xb6\x35\x24\x4a\x56\xea\x5b\xdb\x76\x1f\xdf\x34\xa7\x9b\x70\x80\xda\xfa\xb3\x86\xb6\xe8\xb3\x16\xbd\xc5\x5f\x12\xd0\x5f\x12\x50\xcb\xae\xbd\x76\x22\xfe\x8f\x90\x80\xb6\xef\xeb\x0a\x91\xe7\x7b\xa4\x96\xad\x34\x97\x46\xb8\xe8\x9e\xa9\xc7\x34\xf2\xa9\xd3\x87\xf7\x51\x6b\xee\x3d\x9d\xdd\x76\xba\x7b\x0a\x52\x67\x9d\x0e\x73\xef\x91\xd4\x45\x88\x91\x79\x52\x2a\x11\x6a\x4d\x85\x6d\x9a\x15\xae\x22\x3c\xaa\xf4\x60\xab\x0e\x84\x2c\x0e\x89\x41\x8f\x78\x9d\xae\xe7\xcd\x6e\x3b\xff\xb6\xf7\xe4\xc9\xd3\xbf\x3f\xeb\x90\x24\xc7\x85\x25\x95\x8d\xf8\xb6\x78\x41\xe2\x58\x28\x1a\xd7\xd7\xf6\x6f\xa3\xd1\xc8\x82\x61\x98\xe1\xe2\x1e\xe5\xdb\xf6\xbe\x3a\xce\x53\x6d\xc3\xab\x00\x3f\x37\x68\x84\x5a\x46\xb6\x45\xe5\xa2\xbd\xd0\x7d\x56\x7b\xa1\xfb\x77\x9e\x00\xd2\xfc\x88\x39\x7f\x5d\xaf\xfe\x52\xb7\x6c\x8a\xb9\x75\x75\xd3\xe1\x4a\x42\xf4\x5a\x54\x45\x06\x31\x7a\x35\xf9\x98\xbb\x6d\x28\x29\xf9\x01\x41\xf0\x72\xbd\x94\x5c\x85\x09\x1f\x28\x48\x73\x21\x1b\x13\xc3\xe7\x8d\x4a\xca\xec\x6c\xcf\xfc\xe2\xd8\x15\x17\x9a\xa9\x90\x9c\xa4\xc5\x70\x82\x23\xc3\xff\xfc\xfe\x60\xdc\xb1\x70\x7f\xa9\x2a\xb7\xc0\x12\x55\x2b\x7f\xf4\x01\x82\x28\xb0\xb2\x82\x99\x9a\x7f\x2f\x1c\xe4\xaa\x60\xab\x9e\xc5\xf8\x9c\xe5\x5b\x94\x4d\x5a\x07\x64\xe4\x54\xf2\x3c\x5a\x85\x13\xc4\x34\x75\x8c\xd2\x5b\xac\xed\xad\xde\x9d\x86\x20\x9c\x08\x54\x6a\x89\xa6\xcc\x1d\x3a\x87\xed\x98\x06\x31\x1e\x33\xbc\x9f\x55\xd6\xe9\xc4\xe5\x25\x18\x3d\xc6\x38\x82\xdc\xb6\xe5\xbd\xd7\x7c\x20\x50\x09\x61\x13\xfb\x80\xe3\x34\x33\xc7\xdb\xb0\x99\x26\x4e\xc2\x38\x47\x61\x32\xc6\x59\x3a\xcf\xe3\xbb\x77\xb8\x38\x4d\x12\x9c\xbd\xbc\x78\x75\xe6\x2f\x3e\x7f\x9e\x14\xd3\xd8\xb7\xec\x7f\x7b\xb6\xe7\x3d\x3a\xb0\x98\xd1\x91\x11\xc4\x28\x88\xff\xe9\xf5\xdc\xa7\xfb\x7f\x8b\x1f\x3e\xf3\x5d\xaf\xbb\x12\x6f\x69\xdb\x71\x6c\x7a\xbc\xd6\xc3\x63\xa4\xff\xfb\xca\xfc\xf4\xf0\x0c\xc1\x10\x6d\x39\x23\x5b\xcf\x84\x1c\x68\xd1\x42\x61\x30\xca\x7b\x23\xda\xed\xb2\xe9\x9f\xf1\x7d\xa3\x5a\x22\x04\xbf\xdd\x3f\x69\xbe\x9e\x69\xbe\x9e\x01\x3b\x9d\xf9\xbb\xfb\x52\x34\x50\x46\x22\x2d\xe7\xfb\xb3\x19\xb3\x2d\x36\xb7\x66\x13\xe5\xa1\xce\x6c\xc5\x4e\xca\x76\x66\x79\x8f\x1f\xe5\xba\xcd\x70\x98\x12\xf8\xe8\x71\x38\x6b\x91\xbb\x9f\xad\xc3\x4a\xbf\x6e\x1a\x43\x19\x56\xdd\x46\x89\x84\x87\x33\x68\x32\x48\x77\xdf\xdb\x4e\xd6\x93\x2d\x92\x8b\x49\x03\x92\x10\x16\x31\x61\x06\x13\xc1\x88\xab\x9b\x2d\x8c\x07\xdd\x6e\x05\x63\x67\x79\xee\xd3\x7d\x3c\xb5\xa0\x2d\x7e\x68\x1a\xde\xf2\x71\x72\xbd\x2d\x12\xe9\x4f\xc3\xdb\xdd\x46\x2f\xb7\xed\x24\x30\x80\x30\x46\x89\xe6\xde\xb1\x0f\xc6\x4e\xb1\xdf\xb0\x1c\xf3\x31\x28\xe5\x28\x09\xf6\xe2\x2f\x54\xe3\xbb\xf8\xd1\x77\x36\xbe\xeb\x6d\xd5\x7a\x88\x30\x6d\xd0\xbe\x67\xe6\x24\x7e\x93\x91\xeb\xb0\xc0\xa2\x41\xe7\xdc\xc4\x47\xb7\xb3\x97\x04\xc1\xd7\x2d\xd3\x72\x68\x7b\x58\xa5\xdf\x09\x2b\xbd\x0c\xb1\x6d\x8e\x37\x56\xd3\xef\x98\x29\x3a\x84\x7e\x87\x07\x56\xf1\xdd\x6f\x6e\xee\x7e\x51\x55\x7e\xde\xf3\x7c\xae\xeb\xa9\x34\x41\x6d\x8e\xdd\x72\xb3\x9c\x4a\xad\x90\xe9\xd3\xcd\xb5\x42\x53\xb8\x6c\xd9\x41\xb7\x53\x01\xb5\xee\xa3\x86\x67\xb0\xda\x53\xef\xa9\x21\xd2\x9d\x7e\xfd\x96\x08\x9d\xfa\xfb\xbf\x11\x93\xc1\x26\xc6\x9c\x0a\xe4\xf7\x21\x54\x0d\xf5\x23\x10\x65\x7c\x35\x70\x93\xde\xc4\x97\x3e\x8e\x75\x67\xef\xe5\x12\xbb\xdc\x33\x96\x5d\x71\x49\x1f\xd1\xb5\xbf\x42\x99\x95\xb4\x29\xb3\xc4\x8e\x91\xe8\x8a\x2c\xb3\xa9\x4d\x6d\x56\x58\xd7\x66\xe5\x9a\xf8\x3f\xd3\xb5\x59\xd7\x25\x5c\x72\x6d\xd6\xd7\x76\x6d\xd6\x2f\x3f\xd0\x9c\xb3\x09\xf4\x7c\x0b\xa5\xd4\x46\xe0\x1f\x79\x9c\xeb\x3c\x30\x07\x49\x1d\xc4\xea\xe7\x60\xfd\x08\x57\xae\xd4\xdf\x6c\x59\x6d\x61\xa8\x75\xb6\x6e\x43\x0b\xf4\xba\x80\x21\xaa\x0e\xf5\x5b\x57\x56\x1d\xec\x8d\x03\xe9\xd6\xef\x37\xb2\x20\xb4\xa9\x80\xee\xdb\xc3\x86\x12\xe8\xfe\x07\xdf\xc7\xeb\x0e\xbe\x8f\xef\x77\xf0\xed\x3e\x73\x19\xa0\xff\xe3\x4d\xa7\xde\xae\xe7\xee\xd7\xcf\x4d\x9e\xbb\x5f\x96\xf5\x35\x58\x23\x83\xc6\xc9\xb8\xf6\xc5\xff\x51\xc7\xe2\xfb\x1e\x78\x57\x0d\xea\xbf\xf2\x34\x2a\xe6\xad\x3a\x8f\x7e\xd5\x71\x48\x7f\x25\x7a\x02\x3a\x2d\xac\xe2\x2d\xe1\x51\x61\x3f\xfd\xc0\x0c\x5c\x5a\xbc\xd6\xb0\xca\xf1\x65\x6e\xf6\x7c\xdb\x64\x9b\xbd\xa3\x05\x71\x69\x01\x5b\x3c\x28\x42\xed\x33\x80\x2b\xc7\xe4\x1f\x9e\x7a\x4a\x05\x05\xf1\xb8\x0f\x91\x59\x8a\x5d\xa9\xa8\x90\x01\x42\x30\x5f\x17\x7c\x42\xc7\xb8\x19\x7f\xa2\x47\x95\xa8\x4a\x57\x7f\x7a\x50\xae\x94\x0f\xf2\xfb\x25\x6e\x5a\x17\x51\x30\x6f\x8d\x28\x08\x61\xae\x39\x67\x6b\x59\x92\xe6\x32\xa2\x80\x88\xe9\x99\x8b\xe9\x61\xd9\x93\xf8\x44\xcd\xab\xa8\x02\xa2\xe6\x6c\xae\xe6\x8c\xde\x95\xd7\xea\xac\x38\x44\x90\x18\x65\x56\x9d\xfa\xb6\xd5\x0f\x10\x37\xcc\x0b\x9c\x91\xfc\xaa\xd1\x66\x54\x82\xf5\xff\xfd\x5f\xff\xb7\x05\xd6\xdf\x2c\x7e\x30\xfc\xf4\x3d\x99\x94\x5a\x5d\x3b\xd5\x66\xd0\xee\xb2\xaa\x1d\xc4\xba\xda\x41\xc7\x30\x97\xac\x4b\x5e\xb2\x29\xfb\x80\xe1\x5e\x5a\x6d\xb0\x2d\x25\xb5\xad\xb4\x44\x6b\xb6\xc1\x96\x57\xb7\xdd\xf0\xd4\xae\xc5\xe9\x82\x5e\xa9\xd8\x02\xba\x1f\x8a\xf9\x61\x5f\xdb\xa6\x99\x35\x9d\x5b\x9a\x4d\x79\xe0\x56\x89\x9c\x9f\x08\x82\x8f\xf7\x3f\xa2\x98\x6a\x36\xd1\x76\x15\x7e\xd1\x62\x97\xd6\xe3\x37\xb9\x47\x22\xe2\x6c\x4b\xa6\xc8\x5b\x73\x76\xd0\x99\x50\xfd\x53\x7a\xf0\x19\xaf\xd7\xc8\x4a\x47\xd9\x0d\xe3\x29\x51\x90\x1f\x28\xde\x19\xb1\xf0\x32\x27\x0a\xe6\x4a\x16\x57\x4b\x8f\x5e\xea\x02\x87\xd4\x2a\xad\x61\x5a\xf3\x8a\x63\xa9\xb6\xa8\x26\xac\xe6\x45\x9f\x6a\x67\x15\x4b\x73\xd7\xb4\xfc\x68\x93\xb6\x08\xe6\xb6\x9d\x18\xe1\x46\x3b\x31\xbd\xc3\x1d\x11\x71\x04\x11\xfd\xc5\xab\xd3\x02\x33\x46\x2a\x30\x23\xd1\x03\x8b\x60\x21\x68\x2d\x91\xcc\x48\xe5\x3b\x4a\x5c\x79\x59\xf6\x47\x72\xbe\x06\x48\x1d\x58\x16\x92\x8a\x13\xc5\xb3\x14\x31\x6b\xf9\x3c\x38\x51\x27\x82\xfb\x29\x7a\x4e\xaa\x2d\x49\xd1\x75\xa2\x58\x50\x59\xb1\x39\xc6\x6f\x7e\xde\x56\x11\x55\xd7\x9c\x28\x73\x16\x87\xbf\x61\x30\x57\x1d\xa6\x36\xd9\xbc\x0a\xdb\xd7\x5e\xcd\x8f\xac\x4d\xf3\x25\x11\x56\x18\xdc\x4a\x1b\xce\x0a\x3d\x08\xed\x3d\x9e\xdd\xa2\x0e\xb3\xb1\x71\x13\x9b\x21\xaa\xae\x7c\xab\x5b\x7b\x8b\x4f\xf4\xea\x17\xba\x54\xd4\x94\x6f\x78\xee\xd3\x7d\xb4\x7e\x50\x24\x19\x89\x1a\xd7\x26\x9b\x93\xfb\x74\x65\x05\xdd\x36\xaf\xdc\xb6\x66\xd0\x8a\x15\x0a\x5c\xad\x6e\xbb\x7a\xb0\xb5\xf7\xdd\xbd\xd9\x2d\x74\xf6\x3c\x63\xc4\x9a\x12\xfe\x9a\x77\xbb\x4f\x6b\xa3\x6d\xd9\x0f\xe4\x42\x5d\xfb\x9e\xd7\x18\xf3\xfb\x7c\xf6\x69\xfd\x6d\x33\x0f\xd9\xf7\x0c\xc5\xe3\x3f\x30\x14\xec\x5d\xda\xb2\x7b\x0e\x05\x7b\x6f\xf7\x49\x4b\x67\x9a\xbe\x52\x6a\x63\xfa\x48\x10\xfc\xfe\xa7\xa7\xb4\x95\xa2\xdb\x86\x94\xb6\x11\xb9\xde\x2a\x9f\x2d\xe7\x6d\x6d\x49\x6c\x2b\x9d\x5e\x1b\xf0\xac\xe4\x9b\xe3\x00\xeb\xf1\x7a\x70\x57\x95\x1d\xdb\xf6\x98\x01\xd0\x8a\x6d\xf4\xa6\x6a\xe0\x6d\x8f\x4f\xb9\xcf\xf1\x0f\x14\x3f\xbd\xaa\xde\x3e\xb1\xed\x13\x06\x84\xc0\x32\xdc\x1e\x6b\xa9\x69\xdf\x55\xf5\x1c\xf7\xac\xbc\x08\xa9\x80\x12\x59\xfe\x31\x7c\xf9\xf1\xe9\x69\xdb\x80\x03\x2a\x89\x7e\x73\x4c\xb9\x48\x44\xab\x65\x9d\xbd\xd0\x03\xe0\x9b\x68\x36\x3b\xca\xfc\x93\x50\xc9\xf8\x58\xb4\x9f\x12\xd5\x49\x38\x9c\x38\x89\x99\x68\x9d\x8c\x9c\xbc\x70\x0a\xe8\x0b\x6d\x17\x58\xef\x58\x76\x66\x2a\x49\x08\x72\x13\xcf\xd5\xfd\x9e\x40\xcc\xe7\x7a\x59\xbf\x38\x60\x3b\x2b\xbf\x65\xaa\xfe\x6c\xdb\xc1\xc1\x8e\xc7\xb1\xf9\x31\xfd\xe7\x4d\x70\xd1\xf7\x06\xf0\x36\xb8\xe8\x77\x07\xf0\xfa\x87\x76\x65\x55\x3f\x6c\xfb\x1b\x71\x44\x9b\x61\xc7\x43\xaa\x55\xb2\x51\x9f\x83\xd7\xb4\x51\xaf\x82\xd7\xb4\x51\x87\x75\x80\x81\xd3\xe0\x90\x3e\xfe\x16\x1c\xd2\xc7\x67\x15\x89\x5d\xf6\x2e\xfd\xd3\x83\x91\x6d\x9f\xd9\xf6\x37\x96\xa4\x82\xb6\xfb\xf9\x1a\x18\xac\x57\x02\x95\xb2\x3f\x40\xf0\x22\x58\x18\x6a\x9e\x37\x50\x8b\x1a\xf7\xdf\x8a\xa0\xa6\x5c\x4f\x2a\xcb\xf7\xe8\x89\xdc\x1d\x3e\xab\xbd\xfc\x4c\xd3\x7c\x5e\x83\x46\x59\xfe\x9d\xb4\x38\x39\x56\xce\xf2\xdd\xf0\xc4\xcd\x5c\x40\x12\x4a\x5c\xb4\x5c\xde\x34\x82\x54\xd1\xe2\x9b\x8c\x36\x65\xe8\x02\xfe\xda\x9e\x75\x65\xcf\x24\x36\x81\xff\xbc\x19\x8e\xc9\xaa\xf4\x58\xfc\xf5\x98\x50\xc9\x42\xa0\xc6\x88\x70\x4e\x25\x78\x5c\x81\x20\x79\xff\x5d\xb9\x4a\x9c\x6c\x09\xd3\x7e\xd1\xb4\x01\xce\xef\x73\x00\x0e\x81\x33\x96\x9d\x20\xb8\x61\x49\x89\xc5\x9a\x6c\x3d\x0f\xdf\xd0\xe3\xef\x75\x2d\x17\xb0\x90\xde\xbe\x20\x48\xb8\x00\x87\xc3\xb5\x59\x69\x8c\x20\x41\xfa\xef\xf3\x0a\x98\x74\x98\xc6\xf3\x69\xd2\x1e\x73\xa8\x85\x8a\x55\x87\x43\x65\x59\x54\xc1\x6c\x12\xa6\x5c\x98\xcd\x8a\x74\xa6\xe4\xad\xd7\x69\x36\x0d\x63\x33\x04\x40\x3c\x3a\x52\x50\xa4\x86\x68\x56\x95\x7c\x66\x16\x7c\xbc\x3a\x8c\xaf\xac\x9d\xc3\x84\xf8\x48\x37\xbc\xdf\x09\x82\x22\xfc\xb1\x1b\x5e\xbb\x91\x68\x76\x4f\x9d\x91\x3a\xb8\xa9\x9d\x65\xdd\xb1\x6d\xf3\x16\x71\x5f\xf5\x51\xdb\x4e\x60\x1e\xf1\xb6\xd6\x28\xc9\xd7\xb4\x4a\xef\xab\xc2\xfa\x41\xca\x25\x47\x7e\x25\x08\x82\xb9\x1c\xce\xe5\xd2\x92\xe2\x9d\x71\x1f\xd1\x65\xa5\x12\x97\xfd\xcb\x94\x4c\xda\xb1\x71\xae\x8e\x8d\xc4\xc4\xa3\xa8\xd4\x50\x56\x87\x16\x6c\xc9\x73\xf1\x1d\x6e\x07\x7e\xc2\xb8\x45\x12\xfe\xd9\xea\xa5\x61\x38\x63\x1a\x8b\xa6\x13\x40\xb5\xbc\x1f\x55\x9c\xe4\xcf\x51\x22\xa9\x93\xa6\x7e\xfc\x5c\xc1\x6b\x1e\x1b\xe9\x4e\xb5\x34\x12\xdd\xc7\x46\xce\x88\xee\x63\xe3\x60\xdb\xaa\x49\x6a\xea\x85\x5e\xe2\x78\x86\xb3\x0b\x7c\xcb\xd4\xdf\x45\x88\x20\x0b\x83\xc4\xe9\x3e\x7e\x86\x20\xa5\x57\x7b\x8f\x34\x8d\x38\x09\x75\xc8\x53\x2b\x65\xb3\xc0\xf6\x53\x3e\x1f\x29\x9b\x35\x4a\xbd\x1c\x0c\x29\x28\x7a\x38\x08\x82\xc2\x7f\xc7\x13\x78\x61\x14\x04\x81\xb8\x2e\xb8\x0e\x3d\xdc\x8a\x03\x0a\xdc\x2b\xae\x00\xaf\x70\xd1\x38\x94\x97\x04\x45\x93\x21\x12\x1a\xbf\xcc\x35\x7e\x39\x34\xf8\xe5\xbc\x86\xf3\x56\x97\xf5\xd9\x0f\xba\x47\x71\x60\x21\x2a\xec\x9f\x0e\xf5\xf4\x0b\x13\x1d\xaf\x6c\x2a\x6d\xee\xa7\x11\x5c\x07\xd8\x7d\x85\x93\x39\x07\x3a\xbb\xac\x38\xf1\x75\x6f\x51\xfa\xd7\x4c\xe6\x67\xe6\xa8\x59\xcc\xb3\x4d\x30\xfc\xb2\xdb\x0a\xbf\xec\x46\xc7\x2f\x3b\xd1\x52\x53\x5c\x69\xf8\x65\x1c\xe4\xec\x7c\x86\x13\x0e\x71\x46\x2f\xde\xe9\x48\x65\x5f\x14\x52\x19\xef\xdf\x45\x80\x5d\x2e\x14\x3e\xe7\x1d\xe3\xed\x7b\x53\xb5\xef\x82\xb6\xef\x82\xe1\x9d\x49\x00\x40\x78\x4d\x77\x0d\xe6\x03\x20\xa1\xf4\x18\xe2\x99\xdc\x0d\x5e\x55\x6f\x7f\xd6\x0f\x13\x9f\x5b\xb3\x43\x54\x53\x68\xa2\x94\xa9\xc9\xdb\x26\x2f\xc4\x6a\xd0\x32\x7d\xc2\x2c\xb0\x8c\xe9\x32\xd1\xc8\xc4\x54\x59\x60\xa9\x89\x92\x0e\x08\xb3\x78\x0b\x64\x32\x99\x49\xa2\x42\x26\x3b\xe7\x49\x1f\x54\xee\x87\x1a\x0a\x99\x6c\x6d\x73\x02\x2c\xb0\xe4\x60\xd7\x81\xc7\x8c\x73\xcf\x69\x70\x93\x51\x56\xc7\x36\x39\xba\xb2\x5f\x4b\x9c\x1c\x7f\x2e\x90\x96\x85\xc8\x5f\x22\xf8\x26\x07\xff\x2b\x93\xd4\x4e\x61\xaf\x99\x1e\xa2\x86\x75\x7b\xa4\xcb\xfd\xfc\xd6\x87\xe0\x88\xbe\xf1\x20\x38\xa2\x6f\xbc\xaf\x23\x9d\x1d\x57\x48\x67\x2f\xf5\x97\x11\xfc\x16\xbc\xa4\x2f\x7e\x0d\x5e\xd2\x17\x37\x42\x96\x09\xfc\x5d\x8e\xaa\x75\x3a\x9d\xe1\x8c\x09\x79\x2f\xc3\x24\x8a\xb1\xf3\x09\x9a\xa9\x21\xb8\x06\x52\x17\xab\x3f\x48\x88\xa3\x12\x92\x34\xc2\xfe\x0b\xd5\x36\x2e\x19\x9f\x31\x78\xd1\x0f\x70\x36\x58\x93\xb4\xcb\xb6\x3f\xd8\x76\x55\x13\x82\x3e\x81\x0f\xeb\x5e\x18\x39\x1f\xe4\x69\xed\x4b\xe1\x7c\x40\xee\x18\xcb\x84\x6e\x47\x77\xa7\x91\x33\x45\x2c\x71\x9c\x74\x92\xd0\xb3\x71\xe0\x82\x4f\x17\xfb\xe9\x92\xfc\x38\x8d\xe3\x70\xc6\x76\xe9\xaa\x05\x0a\xcd\xb2\x05\x75\x75\xc8\xf1\xf1\x8a\x1a\xe8\x6a\x2b\xca\xaa\x2a\xcb\x55\x30\xfd\x29\x43\x31\x63\xc8\x70\xf0\x33\xfc\x1e\xd4\x3c\x6e\x7b\xe7\xb6\x7d\xee\x14\xc8\x3f\xb1\xed\x13\xa7\x40\xf0\x7e\xb9\x74\xbe\x3a\x21\xb7\xfa\x7e\xd0\x93\xef\x21\xf8\xc9\xc1\x08\x95\x1c\x79\x4d\x9d\x51\x8b\x94\x43\xe1\xc4\x0c\x61\x6c\x5d\x56\x3a\xc9\xe3\xe9\x40\x8d\x97\xcb\xdf\x9d\x9d\x2e\xed\xd3\x18\x2d\x92\xc0\x04\x0e\x3d\x43\xbd\x33\x81\x63\x8c\xfc\xfe\x40\xe4\xaf\x3b\x53\x59\xf7\x04\x6e\xb8\x60\x53\x07\xbb\xdd\x20\x08\xb2\x5e\xe2\xce\xe6\xf9\xa4\xf6\xd0\x4f\xdc\x7c\xc6\x6a\xca\xa0\x8b\x4a\x91\xa1\xcf\x28\x73\x20\x7f\x09\xfc\x2d\xdb\xae\xdd\xa0\xe3\x72\xc6\xf3\x47\x3a\xcf\x9d\x04\xc1\x8d\x6d\xd3\x73\x36\xce\x72\x92\x17\x8e\x04\x4b\x77\x39\x68\x28\x5d\xeb\x38\x2b\xee\x9c\x82\xae\xf7\x6c\x8c\x0b\x0b\x16\x37\x19\x29\x28\xf3\xf2\x77\x3c\x41\xa6\x12\x6e\x8b\xaf\xe8\x3b\x3a\x59\x37\x4e\x01\x98\x41\xab\x43\x82\x05\x0e\x61\xf0\xc1\xb6\x9d\xf7\xbd\x63\xff\x57\x74\x10\xe1\x18\x17\xb8\x73\xd8\x37\xd1\xb7\xc4\xf8\xe0\xa0\x3f\x80\x94\x29\x13\x9c\x6f\x44\x42\x1a\x9d\x95\x68\xb9\x1c\x21\xdb\x76\xbe\xf4\x3e\x06\x5f\x9c\x33\xe4\xa7\x4c\x29\xc0\x5e\x22\x38\xc0\xf4\x10\x30\x33\xe5\x2f\x32\x72\x76\x52\x97\xe4\xbf\xd1\xfa\xa5\xac\x87\x11\xd2\x00\x5f\x39\xd8\x1d\x9b\x4a\x5e\xbe\x3e\x83\xeb\x71\x97\xf6\x10\x3a\x70\x8a\xe0\xcc\xcd\xd3\xa9\xae\x11\xa9\x40\xd6\x49\x48\x47\xc3\x98\xcb\x12\x21\x64\xdb\x29\xb6\xed\x0c\x9b\x73\x2d\xb7\x12\x3e\xc1\x4e\x11\x90\xd0\x39\xab\xbd\x2d\x5e\x75\x7e\x0e\x1a\x6f\xa9\xec\x7c\xb6\xbd\xe3\xd5\x53\xe7\x61\x69\x5f\xcd\xd9\x2a\xa6\xd2\x61\xd1\xb3\x8a\x6c\xae\x54\x0a\x0a\xb8\xad\xc0\x0e\x43\xc0\xe2\xe0\x5f\x7a\xa7\xb8\x00\x5d\xb8\x57\xf8\xce\xb6\x0b\x77\x96\x61\xba\x6c\x9f\x73\xb6\xee\x20\xa8\x28\x8e\xbd\xab\x93\x20\xbb\x41\x65\x28\x60\xe9\xed\xac\x74\xc6\x8d\x63\xb2\x39\xbe\x64\x7c\xa2\x31\xdc\xd6\xc4\xf7\x15\xdf\x18\x82\x92\xa7\xe2\xa1\x83\xf0\x31\x18\xf7\x32\xec\x7e\x49\x49\xe2\x58\xd0\xb1\x90\xff\x33\xa7\x88\x10\x43\x8c\x83\xdf\x0e\x76\x42\xdb\x7e\xcf\xb8\xa4\x13\xe3\xa0\xc6\x08\x42\x5c\xe9\x86\xde\xf6\xde\xfa\x11\x67\x17\x3c\xd1\x64\x8e\x83\x37\x2e\x89\x96\x4b\xe7\xae\xc7\xb0\x32\xd4\xa9\x50\x8c\xe0\xae\x52\x32\xdc\x21\xa9\x94\xf9\x6e\x24\xe7\xad\xa1\x04\x73\x7e\x36\xcb\x5d\xde\x08\x75\x41\xc5\x02\xc8\xfb\xaf\x06\x30\x84\xc8\xb6\x73\x25\x19\xf2\xf3\xcf\x83\x0a\x21\x39\xc4\x62\x06\x2e\x79\x3a\x62\x89\x76\xa7\x0e\x0d\x51\x8d\x2c\xf8\x73\x7c\x3b\x0b\x93\x88\x3e\x4f\x70\x6b\x81\x49\x98\xcf\xd2\xd9\x7c\x66\xf9\x56\x4c\xf2\x82\x41\xf0\xe8\x12\x94\x9f\xe8\x3f\x63\x0e\xab\xd7\x9f\x42\x8e\x07\xf4\xe0\x57\xe0\x4c\xc2\x2e\x22\x31\xa1\x1d\x0b\x2d\x97\x8a\x36\x25\x2c\x5d\x7d\x85\xbf\x43\x8b\xdd\xee\x4e\x10\xf4\xad\x8e\x05\xd6\x61\x96\xa5\x37\x0c\x66\x95\x5d\x09\xf0\x55\x9e\xf8\x6a\xa0\x71\xde\x2b\x7c\xc7\x14\x8b\x4d\x1a\xfe\xdd\xd9\xf1\x58\xbe\x27\x95\x4e\x87\x7d\x36\x5a\x2e\xdf\x71\xfa\x30\x70\x50\x99\xa3\x11\x1f\xc9\xf6\xea\xd4\xde\x58\x55\xdc\x06\x2e\xb7\x93\x60\xdb\xbe\xe5\x55\x6c\x60\xc8\x78\x1b\x86\x7c\xa6\x31\xe4\x5b\x07\xeb\x50\x6b\x57\x25\xbc\x81\x05\x89\xfc\x1c\x97\xda\x5e\x6c\x22\x61\x07\x01\x5e\x2e\x25\xc6\xa7\x82\x8f\xc6\xb6\xbd\x83\xdd\x22\x23\x53\x07\x95\xce\x47\xf4\x63\x4e\xd2\x1f\x5b\x82\x09\xb8\x9b\x70\x1b\xae\x79\xcb\x1e\x2b\xd6\xbf\x85\x54\xb7\x19\xd1\xbf\x80\x86\x47\x49\x2b\x0c\x9f\xc0\xdf\x6b\xd9\x41\x94\x34\x63\xb0\x1f\xa4\xd1\x11\x9f\x09\xb9\x85\x93\x91\xc3\x68\xb1\x3a\x07\xe2\x7e\x31\x38\x78\xee\x24\x06\x0f\xa7\x3b\xef\x0d\x4b\xfb\x51\x96\x06\x74\x79\xb5\xe8\x73\x37\x61\x32\x25\xd3\x8e\x6b\x60\x93\xa4\x84\xc3\x16\x10\xf5\xd9\xca\xf8\x97\xdc\x25\xc3\x34\x81\xbc\x6f\xd1\xbf\xed\x8a\xd1\x57\x08\x0d\x80\x12\x20\x2f\xcc\xce\x68\x26\x13\x29\x9b\x9f\x7c\x5f\xcb\x5a\x4a\x22\xdf\x9a\xe2\x64\xae\xf1\xc5\xe5\xd2\xb2\x64\x52\x97\x93\xd8\xff\x00\xf4\xb4\xe1\x27\x18\xc4\x91\xc4\x98\x05\x26\x48\x61\x3a\x22\x97\xb0\xa0\xec\x8c\x4a\x83\xec\xc4\xe1\xb7\x44\xbf\xe8\x4c\x64\x2a\x18\x9a\x62\x3b\xa2\xd9\xb4\x02\x96\x92\x73\xc7\x2b\xe1\xd2\x35\xea\x44\xf0\x26\x9c\x89\x14\x7d\x75\x87\xe6\x4b\xb7\x7a\x06\x8b\x16\x38\x79\xa5\xcb\x8d\x71\x09\x5c\xc0\xd1\xdf\xe9\xe9\x3f\x78\xf0\x96\x44\x10\x2c\x11\x10\x9e\x4b\x0e\xe2\x30\x98\x67\x4e\x83\xf0\x67\x21\x77\xfc\xf7\xad\x57\x4f\x3b\x5d\x2f\xde\xef\xec\x77\xf6\x77\xf7\xbf\x59\xf4\x9d\x7c\x4b\xed\xeb\x26\x3f\x98\x88\x29\x1d\x4c\xf5\x40\xae\xab\x07\x86\xda\x91\xd9\x34\x34\x56\x47\xe6\x61\x5b\x1e\x94\xb5\x3e\x31\xd1\xfa\x43\x6e\x75\x78\xfc\xee\x7d\x94\x6f\x86\xdb\x6d\xa5\xca\x45\x5d\x6c\xa5\x49\x7f\x3e\x00\x02\xa1\x6d\x27\xda\xee\xa9\x14\x5e\x21\xe3\x29\xf9\x72\x59\xf0\x3c\x06\x95\x4e\xa4\x3d\xf1\x5c\xbc\x26\x24\x8d\x2d\xc9\x64\xdd\x92\x9c\x33\x57\x3d\xa3\x29\x3c\x7b\x01\x0c\xc3\x96\xe3\x89\xb0\x52\x94\x42\x9c\x6a\x05\xd9\xe2\x46\xf7\x35\xa8\x6c\x30\xcf\x71\xc6\x0f\x79\xad\xc1\x4c\x5e\x65\xc4\xe8\x3e\x51\x00\x87\xc2\xb4\x6f\xe9\x20\x72\xf5\x18\xfe\xed\x9d\xed\xbd\xfd\x35\x68\x01\xfb\xa8\xde\xa2\x52\xc1\xb8\x71\xf9\xc4\xd2\x2c\x35\x55\x20\xba\xae\x10\xad\xc3\x32\x82\x65\xf7\xe5\x44\x0e\xac\x1a\xaa\x19\xab\x3e\x49\x0b\xa7\x2a\x82\x3a\x5c\x72\x85\x4e\xeb\x13\xda\xeb\x59\xcb\x08\xb4\xa6\x34\x9e\x51\x2e\x41\xbf\x61\x5b\x35\xbf\xdf\xbd\xc7\x65\xa5\x11\x6d\x79\xfe\x68\xcf\xf0\xb3\xd8\x26\xc0\xa0\xbd\x8e\x4a\x7a\xac\x03\xba\x4d\x49\xd2\x04\xd9\x2c\xf0\x6d\x71\xae\xe2\xd4\x70\x1c\x93\x59\x4e\x72\x0b\x6e\x26\xa4\xc0\xef\x66\xe1\x90\x21\xc0\xf1\x0c\xc8\x2d\xf1\x6c\xba\xf6\x98\xd2\x7e\xbb\x53\x92\x1e\x85\x67\x0d\xc3\x78\xe8\xec\x7b\xff\xde\xd9\xe5\x8e\x1d\x2b\xa2\xee\xea\xea\x6b\x11\x0e\x40\xff\x5c\xe3\x0d\x4a\xf1\x66\xe8\x80\xdc\x05\x0d\x7f\x91\x2c\x65\x0a\xa1\xee\x33\x2f\xc2\x63\x64\xf1\x52\xc2\x72\x2a\x72\x8b\x3d\x15\xaf\xaa\x89\x51\xb7\xb5\xdd\xdc\x5f\xa8\x78\x43\xe1\x7e\xd5\x9a\x1c\x4b\x00\xfd\x79\xed\xfd\x35\x6d\x76\x25\xcc\xc3\xa0\xce\x80\x3e\x10\xe0\x6a\xb1\xe8\x0f\xda\xec\xcc\x7d\x22\xae\xb6\x83\xb0\x17\x87\xbe\x86\xe9\x63\x42\xf9\xcc\x43\x81\xe2\xa3\x65\xd1\x88\xd6\x3b\x54\xb6\xa9\x52\x5b\x37\x8c\x7a\xe2\x06\xdd\xd8\xb6\xd6\x13\xb2\xdd\xd8\x66\x9a\xcc\xf4\x63\xf3\x10\x9a\x88\xf6\x5a\x80\x92\x08\x59\x6a\x11\x23\x64\x5f\xfc\x44\xb9\x21\x12\x30\xfa\xe2\xc7\xca\x66\xae\x9c\x16\x45\x78\x13\x1b\xc4\x12\xe6\x30\xec\x0d\x75\xe7\x0d\xfe\x9d\x45\xa9\x8c\x5a\x11\x8f\x82\x8a\xc2\x2a\x0a\x4a\x28\x55\x0f\x74\xb3\xd4\x30\xd4\xac\x28\xaf\x19\x29\x2a\xdd\xab\x13\x85\xc2\x83\x34\x0c\x86\x21\xcc\xd6\x10\xd2\xa4\xf9\xec\xbd\x7c\x36\x5d\x45\x64\x1d\xec\x14\x90\x70\x32\x23\x41\xa1\x19\x3e\x9a\xb1\x83\x85\x6e\x08\x29\x34\x43\x48\x61\x1a\x37\xe6\xd5\x9b\x43\xdb\xa6\x82\x48\x51\xa3\xd2\x51\x45\x89\x11\xa5\xd2\x08\x66\x41\xe1\x92\x08\x26\xf4\x0f\x23\xd6\xa9\xbc\xe2\xa4\x79\x1d\x14\x22\x1e\xf1\x52\x5e\x9d\x46\x30\x96\xd7\xbc\xc9\x77\x55\xb5\xe3\x9e\xe7\x8f\xe1\x36\x28\x34\xdb\xc9\x4d\x50\x54\x96\x92\x93\xaa\x91\x54\xde\x87\xab\xa0\x10\x42\x3d\x9c\x57\x8f\xae\x6c\xfb\x0a\x8e\x83\x42\xd9\x4c\xde\xb1\x6b\x26\x83\x7f\xa1\x97\x3c\xd1\x76\x61\x98\x46\xde\x04\x45\x9b\x69\xe4\x2d\x4b\xb0\xc6\x09\xe9\x75\xd5\xd2\xb7\xba\xd4\xf6\x16\x3e\x9b\x52\x5b\x01\xfd\xcd\xf6\x8c\x4d\x96\x8a\x68\xd5\xc2\x34\x02\x27\x99\x01\xc3\x08\xa1\x5c\x69\xcd\xe0\x4e\x13\x86\xed\xa2\x6e\xb1\xd8\x68\xa6\xd0\x8c\x11\xaf\x82\xf3\x5e\x1e\xfa\x61\x08\x87\x1a\x77\x28\xb6\xe6\x0e\x95\xf9\xfb\x35\x9c\x06\x93\xe5\x72\x21\x47\xd4\x9f\x85\xd5\x46\x5c\x5f\x1d\xbf\x10\x58\xf0\x88\xcc\x6b\x3d\x22\xf3\x8e\x9e\xbb\xf9\xc6\x31\x09\xcb\xfe\xe1\x60\x05\xf3\x39\x5d\xcb\x7c\x5e\x6d\xe6\x3d\x71\x8d\xe3\x8c\x14\xc7\x39\xd4\x39\x0d\xc8\xa1\xf7\x4f\x4a\x38\xef\xd1\xa3\xdc\xac\xf4\x17\x8a\x2a\xfc\x10\x74\x02\xf0\xe7\x20\xa6\xd3\xbf\x04\x35\x83\x2c\xe5\x2f\x3f\xd6\x1d\x83\xd8\x3e\xdf\xf1\x13\xdf\x17\xd0\x66\xcb\xbf\x80\xe6\x6c\xf9\xf5\xa3\xe4\xac\x84\x37\xa8\x84\x29\x28\x08\xb5\x69\x4f\x96\xe1\x7e\x39\x97\x61\x8e\x8f\xc5\xb3\x1c\x12\x7c\x23\x7f\x4c\x15\xdf\xa8\x3a\x8e\x4b\xe4\xe7\x25\x4c\x7a\x93\xd5\xec\x34\x29\xe1\x33\x67\xa7\xd3\x16\x76\x4a\xd9\xd7\xb5\x69\xec\x1f\xe9\x5c\xb5\xe2\xa7\xd3\x10\xc1\x65\x18\x54\x14\xf2\x81\xc8\xd9\x7e\x4f\x2a\x5a\xf9\x85\x94\x30\xde\x6e\x67\x36\x32\x6a\xd5\x4d\xc9\xcd\xf8\xeb\x2d\x8d\xc9\xdc\xdb\xd4\x88\xb6\xae\xbc\x4d\x79\xd4\xb5\x61\x6f\x9e\xe9\xe7\xc7\x36\x0f\x52\xee\xd0\xd1\xe6\x3d\x6a\x1a\xee\x65\x42\x2d\xdd\xe7\xb4\xcd\x8f\x74\xa2\xde\x60\x16\x66\xdd\xed\xf4\x84\x67\xf3\xba\xa2\xd2\x89\x72\xca\xe5\xf5\x9e\x9b\x12\xc7\xb1\x2c\xc2\x7f\xbe\xd3\x4f\xb9\x5f\x54\x08\xfa\x85\x11\x62\xfe\xa6\x6a\xcc\x85\x6d\x73\x03\x33\xb3\x79\xbf\xae\x6c\xde\x9f\x75\x9b\xf7\x2b\xcd\xce\x7d\x58\xcb\xc6\x75\xaa\x3b\xbc\x7e\xab\x6a\x3e\xb5\xed\x53\x38\x93\xc9\xb8\x9e\x6b\xc9\xb8\x5e\x04\x58\x9e\x49\x8f\xaa\xf2\x2f\x6c\xfb\x05\x7c\x50\x36\x71\xde\x9d\x07\x32\xe0\xfd\xbd\x4a\xc1\xf5\x52\x3b\xba\xff\x56\x4d\xee\x4b\x7d\x13\x78\x09\x5f\x5b\xac\xdd\x6b\xb2\x72\x6d\xeb\x55\xbb\x26\x41\x97\xf0\x5c\x6a\x21\x85\x9a\xbb\x6d\x35\xed\x0d\x8f\x5b\xb6\xcf\xd4\x26\xbc\xbe\xe3\x9c\xae\x48\xe8\x25\x77\xa2\xad\xf3\x79\xc9\xce\xd7\xf3\x75\x29\x0f\xaf\x46\x82\x2e\xa9\x7d\xb0\xb4\x29\x5a\x67\x23\xe7\xa1\xea\xc1\xa2\x3c\x20\x23\xc7\xf0\xab\xfa\xcd\xb6\x9d\x2b\x2d\x7f\xe0\x95\x08\x93\xb1\x6d\xe7\x17\x19\xf2\xaf\x6e\x22\xf8\x22\x9c\x7f\x7f\x85\x9f\xa4\xc1\xcd\xf9\x35\xe0\x7a\xdc\xab\x1e\xaf\xc5\xbf\xaa\x42\xf3\xb4\x9a\x7f\xed\xfd\xea\x7f\x3b\xf8\x85\xaf\x83\x86\x78\x57\x57\xb8\x7c\x81\x9f\x6c\xdb\xfa\x7f\xff\x9f\xbf\x59\xa8\x3c\xb2\x6d\x87\x99\x9f\xf9\x6e\xbd\x5c\x3a\xbf\xb8\x44\xb2\x13\x04\xbf\xb4\x25\x08\x94\x4f\x59\xcf\x3f\x05\xb7\xb6\x7d\xd2\xab\xc0\xfe\x4f\xc0\xda\xe5\xb3\xbf\xcb\x20\x1b\xa4\x5d\x05\x3e\x06\x5f\x9a\x25\xf9\x84\xaa\x32\x3f\x07\x97\x61\xff\xb7\x01\xfc\xde\xe8\xc4\xcf\x6d\xf0\x39\x46\x76\x9f\x4f\x66\x76\x9f\x64\x75\x76\x9f\x91\xe6\x38\x79\xa7\x05\x69\xbf\xe1\x8a\xe7\xb7\x3c\x9d\xce\x99\xca\x5a\xf5\x9c\xef\xb3\x0f\x84\x76\xfe\x3d\x90\xc8\x3f\x01\x95\x18\xeb\x9d\xb4\x05\xbc\xae\x54\xd3\x9f\x95\xa6\xfe\x95\x91\xc9\xe7\xd0\xd8\xef\x4b\xf8\x05\x8e\xd1\x4a\x4d\x19\x0e\xef\x63\x4e\x1a\x6a\x7a\xae\x89\x70\xf6\xba\x36\x7a\xaa\xfb\x27\xdf\x08\x28\x06\xe5\xa7\xf5\x4d\x1c\xbf\x23\x25\x5e\xfc\x56\xc2\x57\x04\x5f\x9a\xb1\x9e\x3f\xd7\x42\xca\x26\xc5\x34\x7e\x91\x66\x74\x4c\x22\xff\x63\x09\x8c\x98\xe1\xa8\x61\x58\xb8\x0e\xb7\x98\x44\x36\xb6\x52\x34\xf9\x58\x0d\x39\x3b\x75\xff\x5e\xc2\x07\x04\x31\xf2\x7f\x87\xdb\x66\xbb\x92\xb0\xa1\xcd\xfe\x54\xc2\x25\x82\x5b\xae\x75\xbb\x6b\xf5\x0a\x36\xbc\x65\x79\xe0\x3a\xc3\xa8\x47\xce\x38\x44\x70\x1b\x06\x89\xf3\xe4\x31\x82\x9b\x30\x48\xdc\xc4\xb9\x0d\x11\x9c\xb0\x7b\x4f\x10\x5c\xf1\x7b\x27\x21\x82\x73\x76\x6f\x1f\xc1\x31\xbf\x77\x1e\x22\x78\x17\x06\x0d\xff\xfa\xca\x12\x5b\x25\x0c\x77\x64\xe6\x33\x37\xc7\x94\x34\x8a\x94\xd2\xdb\xf9\x68\xb9\x5c\x7c\xfe\x3c\xa3\xbf\x3f\x7f\xf6\xfb\x83\x92\x24\x94\xfb\x0f\x71\x3a\xe2\x39\x7f\x6c\xbb\x0e\x5a\xa6\x8a\x07\x45\xb9\x5c\x9a\x4f\x55\x1e\xa1\x0e\x49\x3a\x05\x12\x5f\x9c\xc9\xcf\xb9\x93\x30\x3f\xbf\x49\xa4\x85\xca\x1d\x86\x71\xcc\x1a\x29\x73\x96\x17\xfd\x64\x80\x4a\xc4\xee\x29\x1f\x11\xb3\x3b\xea\xb8\x98\x39\x68\x51\x4c\x48\x4e\x97\x7a\x5e\x64\xf3\x61\x91\x66\x41\x51\xf2\x53\x24\x68\x5f\x15\xfc\x2d\x48\x84\x60\x28\xa3\xbd\x12\xe4\x3b\x99\x5e\xac\xba\xa6\x62\x62\x27\x43\x65\xe9\x20\xf8\x12\x1a\x5e\x24\x90\x40\x56\x59\xc0\xf0\x8d\x93\x2c\x97\x4e\x12\xbc\xc9\xd2\x29\xc9\x31\xd2\x5d\x3b\x53\x20\x5a\x7b\x43\x07\xa3\x45\x91\xdd\x2d\x72\x27\x73\x13\x7c\xcb\x5c\x0c\xca\x61\x58\x0c\x27\xcc\x44\xe9\x60\x54\x56\x19\x63\x63\xbd\x34\xf3\x2d\x58\x5b\x3c\x57\xf6\xa9\x03\xec\x46\x69\x82\x7b\xa9\x4a\x11\xeb\x3b\x85\xda\xf4\x8b\x8e\x36\xc1\x49\xaf\xf0\x69\x47\x13\xd3\x98\x85\x9d\x82\xf9\x1e\xb8\xc5\x04\x27\x4e\x08\x31\x2a\x73\xc7\xc9\x82\x4c\x64\x68\xc2\x50\x2c\x97\xfd\x01\x42\xbc\x17\x8c\xee\x4b\xb8\x08\x83\x16\xa1\x14\x32\x48\x81\x40\x18\x88\xe3\x8d\x07\x39\x3b\x5f\x18\xae\x46\x5d\x3b\xed\x7b\x03\xe1\x41\x91\x6a\xb9\x6e\xe9\x75\x09\x45\x76\x97\xfb\xfd\x01\x50\x76\xd6\x1f\x28\xb2\x20\xc1\x82\x7e\xdf\x8f\x1d\x0f\x01\x7b\xd7\x8f\x9d\x2e\x15\xd1\xe9\x63\x3f\x76\xf6\x50\x49\x05\x06\xfe\xa5\xca\x36\xf9\xee\x6e\x7a\x99\xc6\xb6\xed\x90\x3e\xbf\x74\x49\x81\xb3\xb0\x48\xb3\x41\xd0\xf0\xc7\xea\x50\x0a\x2b\x11\x68\xd9\xf1\x63\x87\x34\x9d\x7d\xe2\xe6\x2d\xc2\xfa\x96\x68\x8e\x21\x17\x77\x33\xcc\x9d\x43\xac\x9f\x70\xc2\xbf\xd9\x21\x79\x27\x8c\x33\x1c\x46\x77\x1d\x7c\x8b\x87\xf3\x82\x24\x63\xd7\x42\x07\x74\x29\x1d\x84\x07\x88\xd2\x00\xad\x27\xe8\x42\x66\xdb\x4e\x1a\xec\xd9\xa4\xef\x0d\x7a\x99\x2b\x3a\x2a\x7e\xb1\xcf\x2c\x97\x8e\x93\x06\xf2\x11\x62\xec\x8b\xae\xb1\x0c\x81\x87\x7c\x4e\x76\xc8\xb6\x77\x9c\x34\x90\x4f\x80\xf4\xbb\x74\x2e\x29\xd1\x28\xa0\xbe\x83\xfc\x86\x50\x42\xcb\x02\x0f\x52\x3a\x54\x41\x9f\x7f\x16\x52\x4e\x48\x03\x04\xf4\x27\x5a\x0c\xc3\x1c\x77\x3c\x9f\xfd\xe9\xfa\x69\x40\x0e\x2e\x33\x1c\x5e\x1d\xb0\x1b\x8f\x7d\x51\x61\xc8\xc5\x86\x87\x0f\x65\x3c\x07\xfd\x28\xd0\x4f\xfa\x3b\xdd\x92\x17\xde\xf7\xab\x52\x59\xc0\x0a\x90\xa0\xef\x0d\x0e\x86\x69\x52\x90\x64\x8e\x79\xb1\xa7\x3e\x09\x42\x97\x1e\xcd\x66\xe9\xcc\x41\x10\xba\x94\x3e\xf8\x8f\xaa\xa8\xf4\x20\x24\x23\x87\x76\x96\x17\x02\xd6\x6d\x91\x75\xd9\xb3\xed\xb4\x2f\x7f\xed\x76\x07\x68\xb9\x7c\x42\x0f\x47\x7d\x6f\x60\xdb\x7b\xe2\x0a\xa1\x45\x18\x78\xaa\xda\x92\x8c\x9c\x47\x81\x2c\xe4\xec\xa4\xcb\x25\x6d\xe7\x3f\x53\xf6\x9b\x5e\xfe\x23\xed\x3f\x62\x6f\x09\x39\x89\xde\xe3\x23\x42\xdf\x7d\xa2\xde\x15\xcf\xff\x41\x29\xbc\x2a\x4d\x7f\x81\x1a\x43\xfa\x46\xaa\x17\xdd\x33\x8a\xee\x0d\x40\x8c\xc3\x3c\x9f\x38\x04\x89\x97\xe8\x03\xfa\xd2\x86\x11\x2a\x49\x50\x70\x0a\xc0\x10\xea\x7c\x25\xe8\x3f\x01\x3c\x80\x2c\xf0\xca\x11\x49\xc2\x38\xbe\x5b\x24\x41\x1a\x78\xb4\x35\xfb\x8c\x06\x04\x45\x93\x6a\xa1\xaa\x49\xf5\x06\x3d\x7a\x5b\x8a\x5a\x7c\x82\xbd\xb2\x74\xfa\x04\xe2\x01\x2a\xcb\x12\xde\x98\x66\x29\xb5\xac\x0a\x47\x57\x31\xb3\x8d\x61\x42\x72\x28\xd0\x72\x49\x2f\x54\xc4\x98\x3b\x61\xde\x93\xe7\x33\x9c\x3c\x27\x61\x9c\x8e\x5b\x16\x6d\x42\x37\x39\xee\x96\xb9\x88\x58\x21\x5a\x1c\x47\xb4\x2d\xa8\x04\x59\x07\x53\x42\x7c\x47\x25\xdd\x46\x25\x6c\x55\x6f\xa8\x83\x49\x4b\xad\x55\xbc\x9f\xc5\x69\x18\xbd\x20\xb1\xe9\x50\x28\xd3\xfd\x19\x69\x30\xdd\x11\x89\x71\x2e\x13\xfe\x55\x2e\x04\x3c\x4d\x1f\xad\xe3\x2d\x0e\x23\x9c\x1d\x64\x6e\x9a\xd0\x6a\xdb\x7c\x14\xf5\x66\x15\x61\x7e\x75\x9c\x26\x23\xc2\x02\x97\xf9\x37\x32\x9c\xcf\x63\x16\xab\x9c\x31\xb7\xeb\xc3\x9c\xca\x2c\x4e\x41\x27\xbf\xd4\xba\xce\xe4\xd0\xef\xf9\x80\xf4\xcf\x52\x55\xbd\x9b\x5f\x4e\x49\x71\x11\xe6\x57\x2d\xa3\xf8\x25\x74\x12\x10\x24\x25\xfe\x34\x82\x0a\x25\x7d\x5c\x84\x9c\x6e\xcc\x08\x42\xce\xcd\x84\x86\x56\x31\x2d\xc9\xe2\xc5\xfa\xa0\xcb\xa8\xef\xc1\x1e\xc0\xa3\x01\x82\xfe\x63\x60\xd2\x85\xf4\x4b\xab\x5a\xc8\x3e\xe0\x32\xed\x9f\x5b\x75\x0e\x0d\x0e\x04\x13\x54\xd5\xd2\xad\xce\x41\xbc\x9a\x06\xc5\x39\x08\xfa\x8f\xe0\x91\x78\x6b\x4f\xbe\x85\x03\xf3\xbd\x15\xf4\xe3\x71\xe1\xfb\x15\xce\xf3\x70\x8c\x7d\xcb\x7a\x88\x4b\xa3\xc2\x47\xa2\xc2\xfe\xde\xa0\x2c\xc5\x16\x9d\xf0\x46\x07\x75\x72\x06\x93\x34\xeb\x55\x83\x36\x83\x96\x55\x42\x52\x8a\xb6\xbe\x63\x3e\x8b\x86\xac\x25\xb4\xcf\x4d\x99\x54\x1b\x4b\xa1\x19\x58\x89\x66\xca\x7c\xdc\x5a\xcd\xf6\xb7\x09\x2c\x94\xf3\x61\x35\xaa\x15\x2f\x90\xd9\xe2\x14\x9a\x66\x41\x8a\x18\xfb\x16\x9f\x3c\xb6\x09\xd3\xce\x58\xcd\x68\xc2\x9b\xd0\x0d\xb9\x85\xa2\xe9\xc1\xf2\x29\x83\x05\xd3\x4f\x6a\x13\xaf\x0f\xa1\xd2\x67\xb6\xcf\xb4\x76\x3e\xda\xf1\x2a\x80\x4a\x2b\x9f\x2a\x0f\x94\xa3\x70\x78\x15\x65\xe9\x8c\x77\x6d\xc7\x6b\xb6\x2f\x49\xf9\x90\xc8\xae\xb0\x6e\x34\x9b\xfa\x73\xb6\xce\x71\x50\x3b\xdd\x61\x77\xce\xb8\xce\x11\x73\x49\x6b\x41\x58\x15\x5a\xfa\x45\x38\x1c\xe2\x59\xe1\x5b\x6e\x91\x4e\x63\x4b\x9c\xef\x68\x47\x48\xe4\x5b\xbc\x8e\x5d\xda\x98\xdd\x21\xa3\x10\x8b\x9f\x63\x2d\xca\xa6\xac\xea\xac\xaa\x8d\x4c\xc5\xed\x5a\x9c\x85\xa4\xf6\x45\x9d\xf7\xda\x3e\xd1\x6c\xed\x98\xc0\x42\xf9\x5d\xfa\xc2\xb9\x4c\x9e\x31\x2d\x15\x65\xd4\xf2\xe6\x31\x9d\x77\x63\x5c\x62\x3c\x2a\x4e\x87\x69\x52\x22\xb0\x78\x5b\x2d\xd4\x42\x14\x77\xa1\x54\xd9\x5b\x94\x29\x74\x78\xdb\x04\x24\x84\xa5\x9d\xf8\x77\x3c\x93\x00\xd8\xc1\xdf\xda\xf3\x2c\x71\xee\x6c\xe5\x26\xad\x23\xc7\xef\x94\x2d\x8d\xb9\x25\xed\xd3\x4e\xc7\xa5\x65\xc1\xe8\xc4\x29\x56\x8c\x54\xe8\x96\x60\x1d\xf3\xbc\x83\xcd\xaf\xac\xa8\xad\xe2\x8c\xb2\x32\x15\xa2\x66\x55\xea\x02\x2f\x08\x82\xd6\xae\x0a\x11\xac\x94\xa4\xdd\x3a\xda\x6f\x84\x5e\xc9\xea\x58\x8d\x67\xef\x48\x73\x79\x6a\x1c\x8d\xe9\x68\x5e\x92\x08\xab\x44\x77\xfb\xf8\xd1\xca\x25\xcb\x76\xf2\x26\x99\x9c\x13\x93\x48\x58\xfd\xef\x92\x70\x78\x75\x14\x66\x30\x15\xbc\xb2\xde\x00\xc1\x43\x81\x3b\x0e\x34\xac\x3e\x94\x9d\xd5\x19\xd6\xaa\xd9\x5a\xd1\xae\xab\x8a\x6d\xd1\x23\x1b\x2a\xa1\x28\x9d\xd4\x3d\xae\x80\xd6\xde\x6e\x0e\x3c\x94\x14\x2f\x83\xf0\xb8\xdb\x07\x76\x73\x9e\x8c\xd3\xe9\xa2\x12\x74\x6e\x21\xcb\x89\x20\xe1\xaa\xe0\x23\x24\xb0\x66\xe4\xc8\xe8\xfe\x2d\x8d\x98\xc1\x28\xcc\xae\x4a\xba\x39\x39\x6f\x42\x04\xaf\xff\xa8\xcb\x1a\x37\x53\x18\x5e\x07\x95\x99\x82\x3b\x1f\x0c\x57\xe4\x12\x0a\x65\x1e\x21\xa1\xb3\x8f\x34\x35\xfd\x28\xc0\xee\x85\x8a\xb2\x14\xb6\x23\x66\xe3\x50\x88\xa5\xca\xc8\x11\x04\xc1\xac\x67\x89\x84\x2b\xdc\xd6\x21\x35\xec\x26\xd0\xac\x9c\xee\x4d\x58\xb3\x9b\x00\x29\x34\x85\xb4\xd4\x42\x37\xda\x6a\x81\x35\xd7\x72\x19\x69\xc6\xce\x71\xf0\x15\x3b\x08\xee\x82\xb1\x4b\x72\xf6\xfe\x6f\x24\x27\x97\x31\x86\xdb\x40\xa6\xac\x96\x77\x6e\x82\xb1\x9b\xe1\x11\x9c\xd4\x63\x9e\xae\x82\x13\x7a\x2e\x3c\x0f\x4e\xe8\xe1\xe5\x98\xc7\x3c\xdd\xac\xd4\x37\x5e\x17\xf7\xf2\xb9\xeb\x6b\x6d\x6f\xf5\x7f\x9b\x20\x34\x00\x02\x57\x1c\x41\x4a\xeb\x82\x74\x5b\x0f\x98\xe9\x3f\x11\xbe\xd7\x15\xd6\xd3\x48\x30\xab\x18\x86\x5a\x56\xa4\xf6\x74\xde\xce\xad\x83\xe0\x9c\x76\x17\xc1\xdc\xb6\xe7\x2c\xd1\x7c\x5b\x46\xea\x3b\x07\x23\xdb\x3e\x77\x76\x3c\x04\x91\x6d\x47\x2a\x23\xfd\xb1\xda\x8d\x14\x24\x2d\x7c\x6e\x57\x15\x56\x09\x64\x5e\xd3\xe3\x13\x0b\xe7\x7d\xae\xf2\xa5\x2a\x77\x35\x55\xea\x25\x83\x72\x6d\x2f\xa6\xe7\xff\xa9\x17\xa8\x06\x56\x4f\x5a\x73\x18\xdf\x84\x77\xf9\xda\xd2\x70\x29\xb8\x40\x1b\x7c\xc1\x76\xf9\x71\x1b\x7e\x7f\xc6\x53\x95\x5a\xb6\x82\x3b\xa8\x01\x20\x28\x47\xc3\x0a\x22\xa1\xe1\x66\xd8\x74\x52\xac\x61\x26\x4c\x49\x14\x31\x2f\x82\xef\x71\x81\x54\x99\x64\x19\xd1\xed\x92\x24\xd1\x52\xd2\x08\xf4\xf6\x5a\x86\x1b\x41\x99\x5a\xee\x5c\xe1\x3c\x28\xc2\x9b\xc5\xf3\x9a\xb6\xf8\x8c\x24\x57\x56\x89\x9c\xd7\x21\x82\x57\x7f\x29\x7b\xff\x64\x65\xef\xe1\x4a\xed\x84\x11\xf5\xb0\x13\x04\xd8\xb6\x65\x3a\x7b\x76\xe0\x54\xd9\xef\x85\xc2\x42\x1e\x92\x5e\x6d\x75\x48\x5a\x7b\x16\xd2\x98\x64\xfd\x18\x45\x6f\x36\xa5\x82\xcf\x61\x53\xb0\x50\xd2\xf0\xe4\x89\xa5\xb1\xbc\x08\x43\x91\x52\xf1\x2f\x1d\x67\x38\xcf\x29\xb9\x5e\x90\xe7\x47\x9d\x33\xba\x74\x13\x92\x8c\xad\x36\xa1\xe2\xf4\x2f\x42\xfc\x93\x09\xf1\xdb\x0f\x27\xc4\xd3\x3f\x74\x5a\x87\x22\x60\x5f\x39\x0a\xb3\x87\x56\xc7\x7a\x78\x83\x1d\xad\x10\x95\xe6\xdf\x08\x12\x02\x59\xee\x73\x92\x16\x0c\xf0\x88\xc1\xac\x88\x7b\xd9\x3c\xa1\x54\x55\xdd\xc8\xe7\xc3\x21\xd6\x4b\x8c\x42\x12\xe3\x68\xa5\x00\xd1\x72\x92\xa5\x1b\x67\x73\x11\x7c\x4a\x41\xdb\xa1\x46\xe4\x16\x47\x96\x16\x38\xd3\xf2\xc6\xcf\x69\xfb\x29\xea\xb0\x76\x42\x64\x4a\x85\x12\xc1\x8a\x01\x70\xa7\x4d\xb3\x61\x9c\xd2\xd3\x62\x5e\x30\x31\xde\x5f\xf9\x62\x5d\x7f\xd1\x72\x42\x4e\x09\x30\x95\xda\xaf\x73\x3c\xc7\xf5\x9a\xd8\xcd\x96\x97\xde\x86\xf4\xe4\x56\x1d\xd4\xfc\x15\xaa\xad\x96\x57\xe7\x04\x16\xb3\x90\x81\x6f\x68\x2f\xf1\x3b\x90\x26\x17\xe9\x78\x1c\xe3\x37\x8d\x02\xe6\x93\x96\x7a\xaf\xd9\x61\xf2\x2d\x1e\x65\x38\x9f\x98\x6f\x8a\x9b\x2b\x0e\x34\x67\x5b\x22\xa9\x2c\x46\x31\xbe\xfd\x29\x4b\x6f\xfc\x6e\x29\xf4\x40\xc6\x2d\x4e\x6e\x3a\x28\xa4\xe4\x10\x7e\x4b\x5e\xc6\xaa\x54\x95\x2f\xdd\x73\x1f\xe5\x16\x08\xec\x42\xec\xf2\x0b\x37\xca\xc2\x1b\x9c\x3d\x54\x5f\xd0\x96\x41\xd3\xdd\xff\x45\xd6\x7f\xea\x79\x03\x55\x56\x2c\x8f\x66\xc1\xa3\x5a\x41\xb1\x6c\x9a\x05\x3f\x64\x6e\x68\x16\xe5\x0b\xaa\x59\xf2\x81\x2a\xc9\x0e\x61\xdf\x42\x04\xcf\xff\xe2\xeb\x7f\x32\x5f\x7f\xf1\x97\x35\x79\x1b\x6b\xf2\xd1\x5f\xd6\xe4\xbf\xac\xc9\x7f\x59\x93\xff\xb2\x26\x6f\xb0\x26\x7f\xf8\x31\xd6\x64\x21\x71\xb4\xac\xd8\x17\x5b\xd8\x1e\x39\x1b\x87\x54\xd6\x7a\xd4\xb0\x41\x12\x65\x83\x24\xad\x36\xc8\xfe\x63\x10\x2c\xdf\xa5\x6d\xed\x9f\x60\x07\xc1\x15\xfd\xe7\x18\x2b\x4b\x20\xd3\x2b\x4f\xc2\xfc\x90\xc5\xaa\x5d\x84\x97\x31\xa6\x02\x69\xef\x2d\xd6\x2d\x92\xa1\xf9\x14\xb9\x7c\x32\x9a\x50\x43\x9d\x5b\x5c\x22\xe4\xcb\xef\x66\x38\x4f\xe3\x6b\xec\xdc\x62\x34\x68\xd8\x32\x71\x40\x94\x55\x32\xc0\x74\xe5\x26\x01\xa6\xe4\x95\x05\x98\x92\x4e\x1a\xe0\xfe\xa3\x41\xdd\x62\xa9\x89\xa9\xa0\xcb\xb9\x7e\x02\x42\xa2\xcc\x40\x6b\xae\x7a\x9c\x96\x08\x4c\xbb\x25\x9f\x25\x5d\xa2\xd4\xa7\x4a\xb3\x36\xaf\x88\xca\x67\xaf\xf4\xb4\x57\x2e\xf9\x98\xad\x99\x59\xf1\xf2\x18\xd7\xe7\x12\xab\xb9\xc4\xab\xe6\x72\x84\xe9\x63\xcb\x75\xff\x23\xc3\xf9\x7c\x8a\x2d\x58\x4c\x71\x31\x49\x23\xdf\x7a\xf3\xfe\xc2\x2a\x9b\xe3\x2b\x47\x57\xeb\xb6\x83\xfc\xff\x82\x06\xb3\xa1\xfa\xce\xf6\xaa\x83\xc2\x8e\x1c\xf2\xd2\x98\xbf\x36\x23\x3f\xde\x76\xa5\xad\x5c\x5b\x9b\xec\xfb\xfd\xc7\x70\x8e\x1d\xbc\xda\x3e\x9f\xe3\xe2\x82\x4c\x71\x3a\x2f\x1c\xcd\xea\x22\x58\x02\xec\x7b\x5e\x3b\x35\x3e\x67\xf0\x44\xff\x05\xbd\xf9\xf2\x67\xf4\xe6\x55\x7a\xcd\xfa\x72\x91\xbe\xc8\xd2\xa4\xf8\x8e\x2e\x31\x79\x11\x92\xd5\x4c\x30\x53\x5d\xcb\x56\x75\xed\xa2\xad\x6b\x59\xc5\x7a\x0c\x06\x03\x49\xb0\x28\xa1\xff\x18\x28\xb7\xac\xfb\x51\x14\xba\x4a\xa4\xef\x24\xd5\xf1\x38\x50\xf5\x25\x68\xb0\x69\x30\x8e\xc2\xe1\xf7\x4c\xef\x8f\x18\x8b\x37\xff\x3d\xc6\x82\x1b\x9d\x0f\x9b\x6c\xfa\xfb\x87\x25\xd0\xb7\xe0\xd6\xb1\x91\x0d\x0f\x76\xba\xb5\x4d\xa5\x2a\x96\x68\xc5\xf0\x4e\x10\x24\xf5\xad\x0f\x16\xcd\xcd\x92\x0a\x88\x25\x6a\x5b\x8a\x5b\x34\xfd\x0f\xcc\x67\xa7\xb0\x6d\xdc\xeb\x3f\x86\xb7\x6c\x56\xfd\xfe\x5e\x7d\x66\x93\x6a\x2e\x6a\xdb\x68\x5b\x2f\x3c\xa8\x75\xd6\xc7\xad\x5b\x69\x62\x6e\xa5\x88\xff\xaa\xfb\x03\x55\x1b\xf5\x42\x38\x9b\x31\x1c\x12\xf8\xca\xee\xf5\xe9\xa9\x49\xdf\xbe\x17\xb9\x3f\xc1\xee\xeb\x4a\xbb\xc7\xcc\x56\x53\x15\x8e\xdc\x3a\xec\x8d\x06\x5b\x56\x6b\x8b\x6f\xb1\x14\x0e\x76\xba\x9a\xaf\xd1\xf3\x7b\x6a\x2f\xe5\x02\xd9\xca\xe5\x88\x60\x60\x61\x82\xdc\xe8\xf2\x1f\x37\xf8\xb2\xc5\x53\x64\x28\x02\x98\xeb\xf7\xcf\xc2\x86\x42\xae\x72\x71\x60\x37\xcd\xd1\xab\x95\x50\x6a\x53\x5d\xc9\xc6\x1f\x2b\x25\x9b\xa1\x25\x33\x19\x7a\x53\xab\xd7\x70\xca\x68\x53\xd2\x35\x05\xaa\x36\x67\x9c\x69\x48\x92\x15\xae\x58\x4d\xcb\x84\x5b\xa4\x69\x7c\x19\x66\x6d\x3a\x4b\xdc\x5e\xc9\xcf\x54\x62\x28\x26\xbe\xf5\x1f\x9a\xfd\xa1\xe1\x75\x92\xf2\xf1\xdd\x34\x80\x6d\x7e\x31\xd5\x07\x68\xc9\xb6\xda\xf3\x6c\xe3\xec\x8d\x31\x1b\xc7\xe3\xd1\xd8\x7f\x87\x21\x4d\xf8\xc6\xaf\x8f\x63\x25\x0a\x30\x28\xb0\x6b\x2c\x76\x51\xbd\x4c\x6d\x83\x55\x05\xe9\x0e\xd3\x5e\x8e\x3e\xd9\xd4\xa9\xcb\x18\x5b\xa5\x29\x3e\xf0\x53\x4f\xe1\xc6\xe9\x90\xe9\x29\x57\x11\xfd\xaf\x6c\x5c\xe5\x6a\x8c\xf0\x30\x8d\xf0\xfb\xb7\xa7\x4a\xd3\xea\x50\xb1\x3a\xcc\x86\x13\x37\x9f\x5f\xe6\x45\xe6\x3c\x42\x08\x0a\x63\xad\xe2\xe6\x99\x43\x91\xb3\x74\x60\x6a\xd9\x39\x7c\xbc\x69\x6f\x61\xd0\x7d\x8d\x8e\xff\x0a\x8b\x22\x35\x88\xa5\x5d\x43\xfc\x60\xb3\x86\x58\xd0\xaa\x8f\xdd\x29\xb9\x25\x49\xae\x88\xb7\x44\xc8\xf9\x10\x22\x89\x8a\x30\x17\x4c\x06\x35\x00\x98\x1e\x84\x12\x35\x23\x1d\x32\x7b\x4b\x1d\x22\xd5\x0a\x67\x33\x66\x3c\xbb\xef\x7f\xa6\x7e\x10\x2d\xac\x79\x8e\x3b\x79\x91\x91\x61\x61\x1d\x24\x6e\xe4\x14\x60\x85\x56\xdb\x1e\x96\x96\x22\x75\x7c\x16\x24\x4e\x57\x83\xb7\x4e\x15\x6f\x54\xd6\x21\x4d\x73\x51\x65\x38\x93\x0f\xe9\xd1\x5b\xff\x41\xf9\x7b\x11\x60\x57\x8b\xa5\xa6\x27\x40\xb7\x8a\xa6\x3e\xc0\xd5\x0c\x1c\x90\x91\xb3\x93\x48\xf5\x4f\xc1\x5a\x94\x06\x35\xf0\xac\x42\x59\x78\x84\x1e\xf5\x0a\xdf\xe5\x4e\x82\x54\x16\x10\x63\xda\x92\x3e\x1e\xd8\xb6\x93\xf6\xf1\x20\xa8\xa2\xfb\x8a\x3e\x1e\x80\xd5\xb1\x90\xbc\x41\x8b\xf1\x6d\x2e\x2d\xcb\x3f\x30\x90\xa1\x39\x90\x90\x36\xf0\xc2\xf5\xf3\xa5\x6d\xb7\x00\x86\x33\x67\x1e\x64\xdb\xd8\xd0\x28\xcb\xe7\xa5\xae\xc8\xad\xfc\x9b\x1a\xb3\xb3\xd7\x3a\x3b\x7b\xfa\xec\xec\x0d\xfc\x05\x43\x03\xe0\x7e\xa3\x41\xc2\xb1\x01\x7a\xb5\xf1\xc6\xc8\x57\x4e\xd2\x84\xb5\x8c\x30\x3c\x73\x7d\xf4\x8b\xb6\xd1\xcf\xd0\xc2\x52\x5a\x7a\x6b\x27\x08\x32\xdb\x76\x88\x53\xf4\xb3\x01\xb2\xed\xac\x43\x92\x0e\xee\xa5\xfd\x6c\x10\x84\x0e\xee\x67\x03\xa0\x4f\x20\x41\x3e\xbb\xc7\x8a\x6d\x35\x21\x9a\x2a\x5e\x73\x7e\x2f\x26\x78\x8a\x39\xad\x51\x91\x2e\x93\x68\xa6\x8c\xc6\x8a\xe5\x72\x47\x24\x80\xa9\xae\xfa\xc9\x40\x92\x5e\xc6\x49\x0f\x48\x50\x3d\x63\xaa\xd0\x94\xb6\x9a\x20\xe5\x92\x95\xf5\x53\x4a\x5e\xf4\x4f\x40\xfa\x69\x05\x2c\x96\x95\x6b\xe9\x24\x63\x72\xd4\x0f\xa2\x33\x8f\xd1\x99\x4b\x47\x9c\xa3\xd5\x06\xa9\x1b\x0a\xae\x23\x13\xb4\x32\x96\x73\xa0\x11\x8f\xe6\x3a\x10\xea\x99\x5c\x09\x2a\xcb\x01\x3a\xf8\xff\x03\x00\x00\xff\xff\xbd\x38\x7c\x2f\xa2\x80\x06\x00"), + }, + } + fs["/"].(*vfsgenÛ°DirInfo).entries = []os.FileInfo{ + fs["/index.html"].(os.FileInfo), + fs["/index.js"].(os.FileInfo), + } + + return fs +}() + +type vfsgenÛ°FS map[string]interface{} + +func (fs vfsgenÛ°FS) Open(path string) (http.File, error) { + path = pathpkg.Clean("/" + path) + f, ok := fs[path] + if !ok { + return nil, &os.PathError{Op: "open", Path: path, Err: os.ErrNotExist} + } + + switch f := f.(type) { + case *vfsgenÛ°CompressedFileInfo: + gr, err := gzip.NewReader(bytes.NewReader(f.compressedContent)) + if err != nil { + // This should never happen because we generate the gzip bytes such that they are always valid. + panic("unexpected error reading own gzip compressed bytes: " + err.Error()) + } + return &vfsgenÛ°CompressedFile{ + vfsgenÛ°CompressedFileInfo: f, + gr: gr, + }, nil + case *vfsgenÛ°DirInfo: + return &vfsgenÛ°Dir{ + vfsgenÛ°DirInfo: f, + }, nil + default: + // This should never happen because we generate only the above types. + panic(fmt.Sprintf("unexpected type %T", f)) + } +} + +// vfsgenÛ°CompressedFileInfo is a static definition of a gzip compressed file. +type vfsgenÛ°CompressedFileInfo struct { + name string + modTime time.Time + compressedContent []byte + uncompressedSize int64 +} + +func (f *vfsgenÛ°CompressedFileInfo) Readdir(count int) ([]os.FileInfo, error) { + return nil, fmt.Errorf("cannot Readdir from file %s", f.name) +} +func (f *vfsgenÛ°CompressedFileInfo) Stat() (os.FileInfo, error) { return f, nil } + +func (f *vfsgenÛ°CompressedFileInfo) GzipBytes() []byte { + return f.compressedContent +} + +func (f *vfsgenÛ°CompressedFileInfo) Name() string { return f.name } +func (f *vfsgenÛ°CompressedFileInfo) Size() int64 { return f.uncompressedSize } +func (f *vfsgenÛ°CompressedFileInfo) Mode() os.FileMode { return 0444 } +func (f *vfsgenÛ°CompressedFileInfo) ModTime() time.Time { return f.modTime } +func (f *vfsgenÛ°CompressedFileInfo) IsDir() bool { return false } +func (f *vfsgenÛ°CompressedFileInfo) Sys() interface{} { return nil } + +// vfsgenÛ°CompressedFile is an opened compressedFile instance. +type vfsgenÛ°CompressedFile struct { + *vfsgenÛ°CompressedFileInfo + gr *gzip.Reader + grPos int64 // Actual gr uncompressed position. + seekPos int64 // Seek uncompressed position. +} + +func (f *vfsgenÛ°CompressedFile) Read(p []byte) (n int, err error) { + if f.grPos > f.seekPos { + // Rewind to beginning. + err = f.gr.Reset(bytes.NewReader(f.compressedContent)) + if err != nil { + return 0, err + } + f.grPos = 0 + } + if f.grPos < f.seekPos { + // Fast-forward. + _, err = io.CopyN(ioutil.Discard, f.gr, f.seekPos-f.grPos) + if err != nil { + return 0, err + } + f.grPos = f.seekPos + } + n, err = f.gr.Read(p) + f.grPos += int64(n) + f.seekPos = f.grPos + return n, err +} +func (f *vfsgenÛ°CompressedFile) Seek(offset int64, whence int) (int64, error) { + switch whence { + case io.SeekStart: + f.seekPos = 0 + offset + case io.SeekCurrent: + f.seekPos += offset + case io.SeekEnd: + f.seekPos = f.uncompressedSize + offset + default: + panic(fmt.Errorf("invalid whence value: %v", whence)) + } + return f.seekPos, nil +} +func (f *vfsgenÛ°CompressedFile) Close() error { + return f.gr.Close() +} + +// vfsgenÛ°DirInfo is a static definition of a directory. +type vfsgenÛ°DirInfo struct { + name string + modTime time.Time + entries []os.FileInfo +} + +func (d *vfsgenÛ°DirInfo) Read([]byte) (int, error) { + return 0, fmt.Errorf("cannot Read from directory %s", d.name) +} +func (d *vfsgenÛ°DirInfo) Close() error { return nil } +func (d *vfsgenÛ°DirInfo) Stat() (os.FileInfo, error) { return d, nil } + +func (d *vfsgenÛ°DirInfo) Name() string { return d.name } +func (d *vfsgenÛ°DirInfo) Size() int64 { return 0 } +func (d *vfsgenÛ°DirInfo) Mode() os.FileMode { return 0755 | os.ModeDir } +func (d *vfsgenÛ°DirInfo) ModTime() time.Time { return d.modTime } +func (d *vfsgenÛ°DirInfo) IsDir() bool { return true } +func (d *vfsgenÛ°DirInfo) Sys() interface{} { return nil } + +// vfsgenÛ°Dir is an opened dir instance. +type vfsgenÛ°Dir struct { + *vfsgenÛ°DirInfo + pos int // Position within entries for Seek and Readdir. +} + +func (d *vfsgenÛ°Dir) Seek(offset int64, whence int) (int64, error) { + if offset == 0 && whence == io.SeekStart { + d.pos = 0 + return 0, nil + } + return 0, fmt.Errorf("unsupported Seek in directory %s", d.name) +} + +func (d *vfsgenÛ°Dir) Readdir(count int) ([]os.FileInfo, error) { + if d.pos >= len(d.entries) && count > 0 { + return nil, io.EOF + } + if count <= 0 || count > len(d.entries)-d.pos { + count = len(d.entries) - d.pos + } + e := d.entries[d.pos : d.pos+count] + d.pos += count + return e, nil +} diff --git a/pkg/lightning/worker/worker.go b/pkg/lightning/worker/worker.go new file mode 100644 index 000000000..185261dd7 --- /dev/null +++ b/pkg/lightning/worker/worker.go @@ -0,0 +1,65 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package worker + +import ( + "context" + "time" + + "github.com/pingcap/br/pkg/lightning/metric" +) + +type Pool struct { + limit int + workers chan *Worker + name string +} + +type Worker struct { + ID int64 +} + +func NewPool(ctx context.Context, limit int, name string) *Pool { + workers := make(chan *Worker, limit) + for i := 0; i < limit; i++ { + workers <- &Worker{ID: int64(i + 1)} + } + + metric.IdleWorkersGauge.WithLabelValues(name).Set(float64(limit)) + return &Pool{ + limit: limit, + workers: workers, + name: name, + } +} + +func (pool *Pool) Apply() *Worker { + start := time.Now() + worker := <-pool.workers + metric.IdleWorkersGauge.WithLabelValues(pool.name).Set(float64(len(pool.workers))) + metric.ApplyWorkerSecondsHistogram.WithLabelValues(pool.name).Observe(time.Since(start).Seconds()) + return worker +} + +func (pool *Pool) Recycle(worker *Worker) { + if worker == nil { + panic("invalid restore worker") + } + pool.workers <- worker + metric.IdleWorkersGauge.WithLabelValues(pool.name).Set(float64(len(pool.workers))) +} + +func (pool *Pool) HasWorker() bool { + return len(pool.workers) > 0 +} diff --git a/pkg/lightning/worker/worker_test.go b/pkg/lightning/worker/worker_test.go new file mode 100644 index 000000000..01d64d6b4 --- /dev/null +++ b/pkg/lightning/worker/worker_test.go @@ -0,0 +1,56 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package worker_test + +import ( + "context" + "testing" + + . "github.com/pingcap/check" + + "github.com/pingcap/br/pkg/lightning/worker" +) + +type testWorkerPool struct{} + +func (s *testWorkerPool) SetUpSuite(c *C) {} +func (s *testWorkerPool) TearDownSuite(c *C) {} + +var _ = Suite(&testWorkerPool{}) + +func TestNewRestoreWorkerPool(t *testing.T) { + TestingT(t) +} + +func (s *testWorkerPool) TestApplyRecycle(c *C) { + pool := worker.NewPool(context.Background(), 3, "test") + + w1, w2, w3 := pool.Apply(), pool.Apply(), pool.Apply() + c.Assert(w1.ID, Equals, int64(1)) + c.Assert(w2.ID, Equals, int64(2)) + c.Assert(w3.ID, Equals, int64(3)) + c.Assert(pool.HasWorker(), Equals, false) + + pool.Recycle(w3) + c.Assert(pool.HasWorker(), Equals, true) + c.Assert(pool.Apply(), Equals, w3) + pool.Recycle(w2) + c.Assert(pool.Apply(), Equals, w2) + pool.Recycle(w1) + c.Assert(pool.Apply(), Equals, w1) + + c.Assert(pool.HasWorker(), Equals, false) + + c.Assert(func() { pool.Recycle(nil) }, PanicMatches, "invalid restore worker") +} diff --git a/pkg/pdutil/pd.go b/pkg/pdutil/pd.go index 312210451..aaf094120 100644 --- a/pkg/pdutil/pd.go +++ b/pkg/pdutil/pd.go @@ -26,6 +26,7 @@ import ( "google.golang.org/grpc" berrors "github.com/pingcap/br/pkg/errors" + "github.com/pingcap/br/pkg/httputil" "github.com/pingcap/br/pkg/utils" ) @@ -175,12 +176,7 @@ func NewPdController( tlsConf *tls.Config, securityOption pd.SecurityOption, ) (*PdController, error) { - cli := &http.Client{Timeout: 30 * time.Second} - if tlsConf != nil { - transport := http.DefaultTransport.(*http.Transport).Clone() - transport.TLSClientConfig = tlsConf - cli.Transport = transport - } + cli := httputil.NewClient(tlsConf) addrs := strings.Split(pdAddrs, ",") processedAddrs := make([]string, 0, len(addrs)) diff --git a/pkg/pdutil/utils.go b/pkg/pdutil/utils.go index 14784051f..3e0d669e3 100644 --- a/pkg/pdutil/utils.go +++ b/pkg/pdutil/utils.go @@ -11,7 +11,6 @@ import ( "fmt" "net/http" "strings" - "time" "github.com/pingcap/errors" "github.com/pingcap/tidb/tablecodec" @@ -19,6 +18,7 @@ import ( "github.com/tikv/pd/server/schedule/placement" berrors "github.com/pingcap/br/pkg/errors" + "github.com/pingcap/br/pkg/httputil" ) // UndoFunc is a 'undo' operation of some undoable command. @@ -41,13 +41,10 @@ func ResetTS(ctx context.Context, pdAddr string, ts uint64, tlsConf *tls.Config) if err != nil { return errors.Trace(err) } - cli := &http.Client{Timeout: 30 * time.Second} + cli := httputil.NewClient(tlsConf) prefix := "http://" if tlsConf != nil { prefix = "https://" - transport := http.DefaultTransport.(*http.Transport).Clone() - transport.TLSClientConfig = tlsConf - cli.Transport = transport } reqURL := prefix + pdAddr + resetTSURL req, err := http.NewRequestWithContext(ctx, "POST", reqURL, strings.NewReader(string(payload))) @@ -70,13 +67,10 @@ func ResetTS(ctx context.Context, pdAddr string, ts uint64, tlsConf *tls.Config) // GetPlacementRules return the current placement rules. func GetPlacementRules(ctx context.Context, pdAddr string, tlsConf *tls.Config) ([]placement.Rule, error) { - cli := &http.Client{Timeout: 30 * time.Second} + cli := httputil.NewClient(tlsConf) prefix := "http://" if tlsConf != nil { prefix = "https://" - transport := http.DefaultTransport.(*http.Transport).Clone() - transport.TLSClientConfig = tlsConf - cli.Transport = transport } reqURL := prefix + pdAddr + placementRuleURL req, err := http.NewRequestWithContext(ctx, "GET", reqURL, nil) diff --git a/pkg/restore/ingester.go b/pkg/restore/ingester.go new file mode 100644 index 000000000..06a2d4400 --- /dev/null +++ b/pkg/restore/ingester.go @@ -0,0 +1,603 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package restore + +import ( + "bytes" + "context" + "crypto/tls" + "strings" + "sync" + "time" + + "github.com/google/uuid" + "github.com/pingcap/errors" + "github.com/pingcap/failpoint" + "github.com/pingcap/kvproto/pkg/errorpb" + sst "github.com/pingcap/kvproto/pkg/import_sstpb" + "github.com/pingcap/kvproto/pkg/kvrpcpb" + "github.com/pingcap/kvproto/pkg/metapb" + "github.com/pingcap/log" + "github.com/tikv/pd/pkg/codec" + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/grpc/backoff" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/keepalive" + + "github.com/pingcap/br/pkg/conn" + berrors "github.com/pingcap/br/pkg/errors" + "github.com/pingcap/br/pkg/kv" + "github.com/pingcap/br/pkg/logutil" + "github.com/pingcap/br/pkg/utils" +) + +const ( + dialTimeout = 5 * time.Second + + gRPCKeepAliveTime = 10 * time.Second + gRPCKeepAliveTimeout = 3 * time.Second + + // See: https://github.com/tikv/tikv/blob/e030a0aae9622f3774df89c62f21b2171a72a69e/etc/config-template.toml#L360 + regionMaxKeyCount = 1440000 + + defaultSplitSize = 96 * 1024 * 1024 +) + +type retryType int + +const ( + retryNone retryType = iota + retryWrite + retryIngest +) + +type gRPCConns struct { + mu sync.Mutex + conns map[uint64]*conn.Pool + tcpConcurrency int +} + +func (conns *gRPCConns) Close() { + conns.mu.Lock() + defer conns.mu.Unlock() + + for _, cp := range conns.conns { + cp.Close() + } +} + +// Ingester writes and ingests kv to TiKV. +// which used for both BR log restore and Lightning local backend. +type Ingester struct { + // commit ts appends to key in tikv + TS uint64 + + tlsConf *tls.Config + conns gRPCConns + + splitCli SplitClient + WorkerPool *utils.WorkerPool + + batchWriteKVPairs int + regionSplitSize int64 +} + +// NewIngester creates Ingester. +func NewIngester( + splitCli SplitClient, cfg concurrencyCfg, commitTS uint64, tlsConf *tls.Config, +) *Ingester { + workerPool := utils.NewWorkerPool(cfg.IngestConcurrency, "ingest worker") + return &Ingester{ + tlsConf: tlsConf, + conns: gRPCConns{ + tcpConcurrency: cfg.TCPConcurrency, + conns: make(map[uint64]*conn.Pool), + }, + splitCli: splitCli, + WorkerPool: workerPool, + batchWriteKVPairs: cfg.BatchWriteKVPairs, + regionSplitSize: defaultSplitSize, + TS: commitTS, + } +} + +func (i *Ingester) makeConn(ctx context.Context, storeID uint64) (*grpc.ClientConn, error) { + store, err := i.splitCli.GetStore(ctx, storeID) + if err != nil { + return nil, errors.Trace(err) + } + opt := grpc.WithInsecure() + if i.tlsConf != nil { + opt = grpc.WithTransportCredentials(credentials.NewTLS(i.tlsConf)) + } + ctx, cancel := context.WithTimeout(ctx, dialTimeout) + + bfConf := backoff.DefaultConfig + bfConf.MaxDelay = gRPCBackOffMaxDelay + // we should use peer address for tiflash. for tikv, peer address is empty + addr := store.GetPeerAddress() + if addr == "" { + addr = store.GetAddress() + } + grpcConn, err := grpc.DialContext( + ctx, + addr, + opt, + grpc.WithConnectParams(grpc.ConnectParams{Backoff: bfConf}), + grpc.WithKeepaliveParams(keepalive.ClientParameters{ + Time: gRPCKeepAliveTime, + Timeout: gRPCKeepAliveTimeout, + PermitWithoutStream: true, + }), + ) + cancel() + if err != nil { + return nil, errors.Trace(err) + } + return grpcConn, nil +} + +// write [start, end) kv in to tikv. +func (i *Ingester) writeAndIngestByRange( + ctxt context.Context, + iterProducer kv.IterProducer, + start []byte, + end []byte, + remainRanges *syncdRanges, +) error { + select { + case <-ctxt.Done(): + return ctxt.Err() + default: + } + iter := iterProducer.Produce(start, end) + iter.First() + pairStart := append([]byte{}, iter.Key()...) + iter.Last() + pairEnd := append([]byte{}, iter.Key()...) + if bytes.Compare(pairStart, pairEnd) > 0 { + log.Debug("There is no pairs in iterator", logutil.Key("start", start), + logutil.Key("end", end), logutil.Key("pairStart", pairStart), logutil.Key("pairEnd", pairEnd)) + return nil + } + var regions []*RegionInfo + var err error + ctx, cancel := context.WithCancel(ctxt) + defer cancel() + +WriteAndIngest: + for retry := 0; retry < maxRetryTimes; { + if retry != 0 { + select { + case <-time.After(time.Second): + case <-ctx.Done(): + return ctx.Err() + } + } + startKey := codec.EncodeBytes(pairStart) + endKey := codec.EncodeBytes(kv.NextKey(pairEnd)) + regions, err = PaginateScanRegion(ctx, i.splitCli, startKey, endKey, 128) + if err != nil || len(regions) == 0 { + log.Warn("scan region failed", zap.Error(err), zap.Int("region_len", len(regions)), + logutil.Key("startKey", startKey), logutil.Key("endKey", endKey), zap.Int("retry", retry)) + retry++ + continue + } + + for _, region := range regions { + log.Debug("get region", zap.Int("retry", retry), logutil.Key("startKey", startKey), + logutil.Key("endKey", endKey), logutil.Region(region.Region)) + w := i.WorkerPool.ApplyWorker() + var rg *Range + rg, err = i.writeAndIngestPairs(ctx, iter, region, pairStart, pairEnd) + i.WorkerPool.RecycleWorker(w) + if err != nil { + _, regionStart, _ := codec.DecodeBytes(region.Region.StartKey) + // if we have at least succeeded one region, retry without increasing the retry count + if bytes.Compare(regionStart, pairStart) > 0 { + pairStart = regionStart + } else { + retry++ + } + log.Info("retry write and ingest kv pairs", logutil.Key("startKey", pairStart), + logutil.Key("endKey", pairEnd), zap.Error(err), zap.Int("retry", retry)) + continue WriteAndIngest + } + if rg != nil { + remainRanges.add(*rg) + } + } + break + } + return err +} + +func (i *Ingester) writeAndIngestPairs( + ctx context.Context, + iter kv.Iter, + region *RegionInfo, + start, end []byte, +) (*Range, error) { + var err error + var remainRange *Range +loopWrite: + for retry := 0; retry < maxRetryTimes; retry++ { + select { + case <-ctx.Done(): + return remainRange, ctx.Err() + default: + } + var metas []*sst.SSTMeta + metas, remainRange, err = i.writeToTiKV(ctx, iter, region, start, end) + if err != nil { + log.Warn("write to tikv failed", zap.Error(err)) + return nil, err + } + + for _, meta := range metas { + errCnt := 0 + for errCnt < maxRetryTimes { + log.Debug("ingest meta", zap.Reflect("meta", meta)) + var resp *sst.IngestResponse + resp, err = i.ingest(ctx, meta, region) + if err != nil { + log.Warn("ingest failed", zap.Error(err), logutil.SSTMeta(meta), + zap.Reflect("region", region)) + errCnt++ + continue + } + failpoint.Inject("FailIngestMeta", func(val failpoint.Value) { + switch val.(string) { + case "notleader": + resp.Error.NotLeader = &errorpb.NotLeader{ + RegionId: region.Region.Id, Leader: region.Leader, + } + case "epochnotmatch": + resp.Error.EpochNotMatch = &errorpb.EpochNotMatch{ + CurrentRegions: []*metapb.Region{region.Region}, + } + } + }) + var retryTy retryType + var newRegion *RegionInfo + retryTy, newRegion, err = i.isIngestRetryable(ctx, resp, region, meta) + if err == nil { + // ingest next meta + break + } + switch retryTy { + case retryNone: + log.Warn("ingest failed and do not retry", zap.Error(err), logutil.SSTMeta(meta), + zap.Reflect("region", region)) + // met non-retryable error retry whole Write procedure + return remainRange, err + case retryWrite: + region = newRegion + continue loopWrite + case retryIngest: + region = newRegion + continue + } + } + } + + if err != nil { + log.Warn("write and ingest region, will retry import full range", zap.Error(err), + logutil.Region(region.Region), logutil.Key("start", start), logutil.Key("end", end)) + } + return remainRange, errors.Trace(err) + } + + return remainRange, errors.Trace(err) +} + +// writeToTiKV writer engine key-value pairs to tikv and return the sst meta generated by tikv. +// we don't need to do cleanup for the pairs written to tikv if encounters an error, +// tikv will takes the responsibility to do so. +func (i *Ingester) writeToTiKV( + ctx context.Context, + iter kv.Iter, + region *RegionInfo, + start, end []byte, +) ([]*sst.SSTMeta, *Range, error) { + begin := time.Now() + regionRange := intersectRange(region.Region, Range{Start: start, End: end}) + + iter.Seek(regionRange.Start) + firstKey := codec.EncodeBytes(iter.Key()) + var lastKey []byte + if iter.Seek(regionRange.End) { + lastKey = codec.EncodeBytes(iter.Key()) + } else { + iter.Last() + log.Info("region range's end key not in iter, shouldn't happen", + zap.Any("region range", regionRange), logutil.Key("iter last", iter.Key())) + lastKey = codec.EncodeBytes(kv.NextKey(iter.Key())) + } + + if bytes.Compare(firstKey, lastKey) > 0 { + log.Info("keys within region is empty, skip ingest", logutil.Key("start", start), + logutil.Key("regionStart", region.Region.StartKey), logutil.Key("end", end), + logutil.Key("regionEnd", region.Region.EndKey)) + return nil, nil, nil + } + + u := uuid.New() + meta := &sst.SSTMeta{ + Uuid: u[:], + RegionId: region.Region.GetId(), + RegionEpoch: region.Region.GetRegionEpoch(), + Range: &sst.Range{ + Start: firstKey, + End: lastKey, + }, + } + + leaderID := region.Leader.GetId() + clients := make([]sst.ImportSST_WriteClient, 0, len(region.Region.GetPeers())) + requests := make([]*sst.WriteRequest, 0, len(region.Region.GetPeers())) + for _, peer := range region.Region.GetPeers() { + cli, err := i.getImportClient(ctx, peer) + if err != nil { + return nil, nil, err + } + + wstream, err := cli.Write(ctx) + if err != nil { + return nil, nil, errors.Trace(err) + } + + // Bind uuid for this write request + req := &sst.WriteRequest{ + Chunk: &sst.WriteRequest_Meta{ + Meta: meta, + }, + } + if err = wstream.Send(req); err != nil { + return nil, nil, errors.Trace(err) + } + req.Chunk = &sst.WriteRequest_Batch{ + Batch: &sst.WriteBatch{ + CommitTs: i.TS, + }, + } + clients = append(clients, wstream) + requests = append(requests, req) + } + + bytesBuf := utils.NewBytesBuffer() + defer bytesBuf.Destroy() + pairs := make([]*sst.Pair, 0, i.batchWriteKVPairs) + count := 0 + size := int64(0) + totalCount := 0 + firstLoop := true + regionMaxSize := i.regionSplitSize * 4 / 3 + + for iter.Seek(regionRange.Start); iter.Valid() && bytes.Compare(iter.Key(), regionRange.End) <= 0; iter.Next() { + size += int64(len(iter.Key()) + len(iter.Value())) + // here we reuse the `*sst.Pair`s to optimize object allocation + if firstLoop { + pair := &sst.Pair{ + Key: bytesBuf.AddBytes(iter.Key()), + Value: bytesBuf.AddBytes(iter.Value()), + } + pairs = append(pairs, pair) + } else { + pairs[count].Key = bytesBuf.AddBytes(iter.Key()) + pairs[count].Value = bytesBuf.AddBytes(iter.Value()) + } + count++ + totalCount++ + + if count >= i.batchWriteKVPairs { + for i := range clients { + requests[i].Chunk.(*sst.WriteRequest_Batch).Batch.Pairs = pairs[:count] + if err := clients[i].Send(requests[i]); err != nil { + return nil, nil, errors.Trace(err) + } + } + count = 0 + bytesBuf.Reset() + firstLoop = false + } + if size >= regionMaxSize || totalCount >= regionMaxKeyCount { + break + } + } + + if count > 0 { + for i := range clients { + requests[i].Chunk.(*sst.WriteRequest_Batch).Batch.Pairs = pairs[:count] + if err := clients[i].Send(requests[i]); err != nil { + return nil, nil, errors.Trace(err) + } + } + } + + if iter.Error() != nil { + return nil, nil, errors.Trace(iter.Error()) + } + + var leaderPeerMetas []*sst.SSTMeta + for i, wStream := range clients { + if resp, closeErr := wStream.CloseAndRecv(); closeErr != nil { + return nil, nil, errors.Trace(closeErr) + } else if leaderID == region.Region.Peers[i].GetId() { + leaderPeerMetas = resp.Metas + log.Debug("get metas after write kv stream to tikv", zap.Reflect("metas", leaderPeerMetas)) + } + } + + // if there is not leader currently, we should directly return an error + if leaderPeerMetas == nil { + log.Warn("write to tikv no leader", zap.Reflect("region", region), + zap.Uint64("leader_id", leaderID), zap.Reflect("meta", meta), + zap.Int("kv_pairs", totalCount), zap.Int64("total_bytes", size)) + return nil, nil, errors.Annotatef(berrors.ErrPDLeaderNotFound, "write to tikv with no leader returned, region '%d', leader: %d", + region.Region.Id, leaderID) + } + + log.Debug("write to kv", zap.Reflect("region", region), zap.Uint64("leader", leaderID), + zap.Reflect("meta", meta), zap.Reflect("return metas", leaderPeerMetas), + zap.Int("kv_pairs", totalCount), zap.Int64("total_bytes", size), + zap.Int64("buf_size", bytesBuf.TotalSize()), + zap.Stringer("takeTime", time.Since(begin))) + + var remainRange *Range + if iter.Valid() && iter.Next() { + firstKey := append([]byte{}, iter.Key()...) + remainRange = &Range{Start: firstKey, End: regionRange.End} + log.Info("write to tikv partial finish", zap.Int("count", totalCount), + zap.Int64("size", size), zap.Binary("startKey", regionRange.Start), zap.Binary("endKey", regionRange.End), + zap.Binary("remainStart", remainRange.Start), zap.Binary("remainEnd", remainRange.End), + zap.Reflect("region", region)) + } + + return leaderPeerMetas, remainRange, nil +} + +func (i *Ingester) ingest(ctx context.Context, meta *sst.SSTMeta, region *RegionInfo) (*sst.IngestResponse, error) { + leader := region.Leader + if leader == nil { + leader = region.Region.GetPeers()[0] + } + + cli, err := i.getImportClient(ctx, leader) + if err != nil { + return nil, err + } + reqCtx := &kvrpcpb.Context{ + RegionId: region.Region.GetId(), + RegionEpoch: region.Region.GetRegionEpoch(), + Peer: leader, + } + + req := &sst.IngestRequest{ + Context: reqCtx, + Sst: meta, + } + resp, err := cli.Ingest(ctx, req) + if err != nil { + return nil, errors.Trace(err) + } + return resp, nil +} + +func (i *Ingester) getImportClient(ctx context.Context, peer *metapb.Peer) (sst.ImportSSTClient, error) { + i.conns.mu.Lock() + defer i.conns.mu.Unlock() + + conn, err := i.getGrpcConnLocked(ctx, peer.GetStoreId()) + if err != nil { + return nil, errors.Trace(err) + } + return sst.NewImportSSTClient(conn), nil +} + +func (i *Ingester) getGrpcConnLocked(ctx context.Context, storeID uint64) (*grpc.ClientConn, error) { + if _, ok := i.conns.conns[storeID]; !ok { + i.conns.conns[storeID] = conn.NewConnPool(i.conns.tcpConcurrency, func(ctx context.Context) (*grpc.ClientConn, error) { + return i.makeConn(ctx, storeID) + }) + } + return i.conns.conns[storeID].Get(ctx) +} + +func (i *Ingester) isIngestRetryable( + ctx context.Context, + resp *sst.IngestResponse, + region *RegionInfo, + meta *sst.SSTMeta, +) (retryType, *RegionInfo, error) { + if resp.GetError() == nil { + return retryNone, nil, nil + } + + getRegion := func() (*RegionInfo, error) { + for retry := 0; ; retry++ { + newRegion, err := i.splitCli.GetRegion(ctx, region.Region.GetStartKey()) + if err != nil { + return nil, errors.Trace(err) + } + if newRegion != nil { + return newRegion, nil + } + log.Warn("get region by key return nil, will retry", zap.Reflect("region", region), + zap.Int("retry", retry)) + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-time.After(time.Second): + } + } + } + + var newRegion *RegionInfo + var err error + switch errPb := resp.GetError(); { + case errPb.NotLeader != nil: + if newLeader := errPb.GetNotLeader().GetLeader(); newLeader != nil { + newRegion = &RegionInfo{ + Leader: newLeader, + Region: region.Region, + } + } else { + newRegion, err = getRegion() + if err != nil { + return retryNone, nil, errors.Trace(err) + } + } + return retryIngest, newRegion, errors.Annotatef(berrors.ErrKVNotLeader, "not leader: %s", errPb.GetMessage()) + case errPb.EpochNotMatch != nil: + if currentRegions := errPb.GetEpochNotMatch().GetCurrentRegions(); currentRegions != nil { + var currentRegion *metapb.Region + for _, r := range currentRegions { + if insideRegion(r, meta) { + currentRegion = r + break + } + } + if currentRegion != nil { + var newLeader *metapb.Peer + for _, p := range currentRegion.Peers { + if p.GetStoreId() == region.Leader.GetStoreId() { + newLeader = p + break + } + } + if newLeader != nil { + newRegion = &RegionInfo{ + Leader: newLeader, + Region: currentRegion, + } + } + } + } + retryTy := retryNone + if newRegion != nil { + retryTy = retryWrite + } + return retryTy, newRegion, errors.Annotatef(berrors.ErrKVEpochNotMatch, "epoch not match: %s", errPb.GetMessage()) + case strings.Contains(errPb.Message, "raft: proposal dropped"): + // TODO: we should change 'Raft raft: proposal dropped' to a error type like 'NotLeader' + newRegion, err = getRegion() + if err != nil { + return retryNone, nil, errors.Trace(err) + } + return retryIngest, newRegion, errors.Annotate(berrors.ErrKVUnknown, errPb.GetMessage()) + } + return retryNone, nil, errors.Annotatef(berrors.ErrKVUnknown, "non-retryable error: %s", resp.GetError().GetMessage()) +} diff --git a/pkg/restore/log_client.go b/pkg/restore/log_client.go index f01c67067..73fb590d6 100644 --- a/pkg/restore/log_client.go +++ b/pkg/restore/log_client.go @@ -113,8 +113,9 @@ func NewLogRestoreClient( } } - splitClient := NewSplitClient(restoreClient.GetPDClient(), restoreClient.GetTLSConfig()) - importClient := NewImportClient(splitClient, restoreClient.tlsConf, restoreClient.keepaliveConf) + tlsConf := restoreClient.GetTLSConfig() + splitClient := NewSplitClient(restoreClient.GetPDClient(), tlsConf) + importClient := NewImportClient(splitClient, tlsConf, restoreClient.keepaliveConf) cfg := concurrencyCfg{ Concurrency: concurrency, @@ -134,6 +135,10 @@ func NewLogRestoreClient( eventPullers: make(map[int64]*cdclog.EventPuller), tableBuffers: make(map[int64]*cdclog.TableBuffer), tableFilter: tableFilter, +<<<<<<< HEAD +======= + ingester: NewIngester(splitClient, cfg, commitTS, tlsConf), +>>>>>>> 8b9b626... Merge branch 'merging' } return lc, nil } diff --git a/pkg/restore/split_client.go b/pkg/restore/split_client.go index 5672fa6e4..3ae9b97e4 100755 --- a/pkg/restore/split_client.go +++ b/pkg/restore/split_client.go @@ -31,6 +31,7 @@ import ( "google.golang.org/grpc/credentials" berrors "github.com/pingcap/br/pkg/errors" + "github.com/pingcap/br/pkg/httputil" "github.com/pingcap/br/pkg/logutil" ) @@ -401,8 +402,11 @@ func (c *pdClient) GetPlacementRule(ctx context.Context, groupID, ruleID string) if addr == "" { return rule, errors.Annotate(berrors.ErrRestoreSplitFailed, "failed to add stores labels: no leader") } - req, _ := http.NewRequestWithContext(ctx, "GET", addr+path.Join("/pd/api/v1/config/rule", groupID, ruleID), nil) - res, err := http.DefaultClient.Do(req) + req, err := http.NewRequestWithContext(ctx, "GET", addr+path.Join("/pd/api/v1/config/rule", groupID, ruleID), nil) + if err != nil { + return rule, errors.Trace(err) + } + res, err := httputil.NewClient(c.tlsConf).Do(req) if err != nil { return rule, errors.Trace(err) } @@ -424,8 +428,11 @@ func (c *pdClient) SetPlacementRule(ctx context.Context, rule placement.Rule) er return errors.Annotate(berrors.ErrPDLeaderNotFound, "failed to add stores labels") } m, _ := json.Marshal(rule) - req, _ := http.NewRequestWithContext(ctx, "POST", addr+path.Join("/pd/api/v1/config/rule"), bytes.NewReader(m)) - res, err := http.DefaultClient.Do(req) + req, err := http.NewRequestWithContext(ctx, "POST", addr+path.Join("/pd/api/v1/config/rule"), bytes.NewReader(m)) + if err != nil { + return errors.Trace(err) + } + res, err := httputil.NewClient(c.tlsConf).Do(req) if err != nil { return errors.Trace(err) } @@ -437,8 +444,11 @@ func (c *pdClient) DeletePlacementRule(ctx context.Context, groupID, ruleID stri if addr == "" { return errors.Annotate(berrors.ErrPDLeaderNotFound, "failed to add stores labels") } - req, _ := http.NewRequestWithContext(ctx, "DELETE", addr+path.Join("/pd/api/v1/config/rule", groupID, ruleID), nil) - res, err := http.DefaultClient.Do(req) + req, err := http.NewRequestWithContext(ctx, "DELETE", addr+path.Join("/pd/api/v1/config/rule", groupID, ruleID), nil) + if err != nil { + return errors.Trace(err) + } + res, err := httputil.NewClient(c.tlsConf).Do(req) if err != nil { return errors.Trace(err) } @@ -453,13 +463,17 @@ func (c *pdClient) SetStoresLabel( if addr == "" { return errors.Annotate(berrors.ErrPDLeaderNotFound, "failed to add stores labels") } + httpCli := httputil.NewClient(c.tlsConf) for _, id := range stores { - req, _ := http.NewRequestWithContext( + req, err := http.NewRequestWithContext( ctx, "POST", addr+path.Join("/pd/api/v1/store", strconv.FormatUint(id, 10), "label"), bytes.NewReader(b), ) - res, err := http.DefaultClient.Do(req) + if err != nil { + return errors.Trace(err) + } + res, err := httpCli.Do(req) if err != nil { return errors.Trace(err) } diff --git a/tests/README.md b/tests/README.md index 0297bd17f..dcffcc10e 100644 --- a/tests/README.md +++ b/tests/README.md @@ -5,15 +5,17 @@ programs. ## Preparations -1. The following 7 executables must be copied or linked into these locations: +1. The following 9 executables must be copied or linked into these locations: + * `bin/tidb-server` - * `bin/tikv-server` - * `bin/pd-server` + * `bin/tikv-server` + * `bin/pd-server` * `bin/pd-ctl` - * `bin/go-ycsb` - * `bin/minio` + * `bin/go-ycsb` + * `bin/minio` * `bin/tiflash` * `bin/cdc` + * `bin/tikv-importer` The versions must be ≥2.1.0 as usual. @@ -24,19 +26,23 @@ programs. * `mysql` (the CLI client) * `curl` * `s3cmd` + * `openssl` + * `wget` + * `lsof` 3. The user executing the tests must have permission to create the folder `/tmp/backup_restore_test`. All test artifacts will be written into this folder. ## Running -Make sure the path is `br/` +Run `make test` to execute the unit tests. Run `make integration_test` to execute the integration tests. This command will 1. Build `br` -2. Check that all 7 required executables and `br` executable exist +2. Check that all 9 required executables and `br` executable exist 3. Execute `tests/run.sh` +4. To start cluster with tiflash, please run `TIFLASH=1 tests/run.sh` If the first two steps are done before, you could also run `tests/run.sh` directly. This script will @@ -44,6 +50,11 @@ This script will 1. Start PD, TiKV and TiDB in background with local storage 2. Find out all `tests/*/run.sh` and run it +Run `tests/run.sh --debug` to pause immediately after all servers are started. + +After executing the tests, run `make coverage` to get a coverage report at +`/tmp/backup_restore_test/all_cov.html`. + ## Writing new tests New integration tests can be written as shell scripts in `tests/TEST_NAME/run.sh`. @@ -52,3 +63,8 @@ The script should exit with a nonzero error code on failure. Several convenient commands are provided: * `run_sql ` — Executes an SQL query on the TiDB database +* `run_lightning [CONFIG]` — Starts `tidb-lightning` using `tests/TEST_NAME/CONFIG.toml` +* `check_contains ` — Checks if the previous `run_sql` result contains the given text + (in `-E` format) +* `check_not_contains ` — Checks if the previous `run_sql` result does not contain the given + text (in `-E` format) diff --git a/tests/_utils/check_cluster_version b/tests/_utils/check_cluster_version new file mode 100755 index 000000000..e227c2a08 --- /dev/null +++ b/tests/_utils/check_cluster_version @@ -0,0 +1,26 @@ +#!/bin/sh +# +# Copyright 2020 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eu + +if [ "$CLUSTER_VERSION_MAJOR" -gt "$1" ] || ([ "$CLUSTER_VERSION_MAJOR" -eq "$1" ] && ( \ + [ "$CLUSTER_VERSION_MINOR" -gt "$2" ] || \ + [ "$CLUSTER_VERSION_MINOR" -eq "$2" ] && [ "$CLUSTER_VERSION_REVISION" -ge "$3" ] )) +then + exit 0 +fi + +echo "$4 requires v$1.$2.$3, but current cluster is v$CLUSTER_VERSION_MAJOR.$CLUSTER_VERSION_MINOR.$CLUSTER_VERSION_REVISION. Skipping test." +exit 1 diff --git a/tests/_utils/check_contains b/tests/_utils/check_contains new file mode 100755 index 000000000..00faf0f3f --- /dev/null +++ b/tests/_utils/check_contains @@ -0,0 +1,24 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eu + +if ! grep -Fq "$1" "$TEST_DIR/sql_res.$TEST_NAME.txt"; then + echo "TEST FAILED: OUTPUT DOES NOT CONTAIN '$1'" + echo "____________________________________" + cat "$TEST_DIR/sql_res.$TEST_NAME.txt" + echo "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" + exit 1 +fi diff --git a/tests/_utils/check_not_contains b/tests/_utils/check_not_contains new file mode 100755 index 000000000..f6cb40276 --- /dev/null +++ b/tests/_utils/check_not_contains @@ -0,0 +1,24 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eu + +if grep -Fq "$1" "$TEST_DIR/sql_res.$TEST_NAME.txt"; then + echo "TEST FAILED: OUTPUT CONTAINS '$1'" + echo "____________________________________" + cat "$TEST_DIR/sql_res.$TEST_NAME.txt" + echo "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^" + exit 1 +fi diff --git a/tests/_utils/generate_certs b/tests/_utils/generate_certs new file mode 100755 index 000000000..003d09c2d --- /dev/null +++ b/tests/_utils/generate_certs @@ -0,0 +1,30 @@ +#!/bin/sh +# +# Copyright 2020 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eu + +mkdir -p $TEST_DIR/certs +openssl ecparam -out "$TEST_DIR/certs/ca.key" -name prime256v1 -genkey +openssl req -new -batch -sha256 -subj '/CN=localhost' -key "$TEST_DIR/certs/ca.key" -out "$TEST_DIR/certs/ca.csr" +openssl x509 -req -sha256 -days 2 -in "$TEST_DIR/certs/ca.csr" -signkey "$TEST_DIR/certs/ca.key" -out "$TEST_DIR/certs/ca.pem" +for cluster in tidb pd tikv importer lightning tiflash curl ticdc br; do + openssl ecparam -out "$TEST_DIR/certs/$cluster.key" -name prime256v1 -genkey + openssl req -new -batch -sha256 -subj '/CN=localhost' -key "$TEST_DIR/certs/$cluster.key" -out "$TEST_DIR/certs/$cluster.csr" + openssl x509 -req -sha256 -days 1 -extensions EXT -extfile "tests/config/ipsan.cnf" \ + -in "$TEST_DIR/certs/$cluster.csr" \ + -CA "$TEST_DIR/certs/ca.pem" \ + -CAkey "$TEST_DIR/certs/ca.key" \ + -CAcreateserial -out "$TEST_DIR/certs/$cluster.pem" +done diff --git a/tests/_utils/make_tiflash_config b/tests/_utils/make_tiflash_config index c235f0444..f759a990f 100755 --- a/tests/_utils/make_tiflash_config +++ b/tests/_utils/make_tiflash_config @@ -1,13 +1,13 @@ #!/bin/sh -cat > tests/config/tiflash-learner.toml < $TEST_DIR/tiflash-learner.toml < tests/config/tiflash.toml < $TEST_DIR/tiflash.toml <>>>>>" >> "$TEST_DIR/lightning.log" +bin/tidb-lightning.test -test.coverprofile="$TEST_DIR/cov.$TEST_NAME.$$.out" DEVEL \ + --ca "$TEST_DIR/certs/ca.pem" \ + --cert "$TEST_DIR/certs/lightning.pem" \ + --key "$TEST_DIR/certs/lightning.key" \ + --log-file "$TEST_DIR/lightning.log" \ + --tidb-port 4000 \ + --pd-urls '127.0.0.1:2379' \ + --config "tests/$TEST_NAME/config.toml" \ + -d "tests/$TEST_NAME/data" \ + --importer '127.0.0.1:8808' \ + --sorted-kv-dir "$TEST_DIR/$TEST_NAME.sorted" \ + --enable-checkpoint=0 \ + --check-requirements=0 \ + "$@" diff --git a/tests/_utils/run_lightning_ctl b/tests/_utils/run_lightning_ctl new file mode 100755 index 000000000..171a27ebd --- /dev/null +++ b/tests/_utils/run_lightning_ctl @@ -0,0 +1,30 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eux + +bin/tidb-lightning-ctl.test -test.coverprofile="$TEST_DIR/cov.ctl.$TEST_NAME.$$.out" DEVEL \ + --ca "$TEST_DIR/certs/ca.pem" \ + --cert "$TEST_DIR/certs/lightning.pem" \ + --key "$TEST_DIR/certs/lightning.key" \ + --log-file "$TEST_DIR/lightning.log" \ + --tidb-port 4000 \ + --pd-urls '127.0.0.1:2379' \ + -d "tests/$TEST_NAME/data" \ + --importer '127.0.0.1:8808' \ + --sorted-kv-dir "$TEST_DIR/sorted" \ + --enable-checkpoint=0 \ + --check-requirements=0 \ + "$@" diff --git a/tests/_utils/run_pd_ctl b/tests/_utils/run_pd_ctl new file mode 100755 index 000000000..67f263ea4 --- /dev/null +++ b/tests/_utils/run_pd_ctl @@ -0,0 +1,22 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eux + +# Workaround for https://github.com/tikv/pd/issues/3318 +echo "$@" | pd-ctl \ + --cacert "$TEST_DIR/certs/ca.pem" \ + --cert "$TEST_DIR/certs/br.pem" \ + --key "$TEST_DIR/certs/br.key" diff --git a/tests/_utils/run_services b/tests/_utils/run_services index c8e8abd0c..f0f66854e 100644 --- a/tests/_utils/run_services +++ b/tests/_utils/run_services @@ -15,32 +15,32 @@ set -eu -TEST_DIR=/tmp/backup_restore_test - -PD_ADDR="127.0.0.1:2379" -TIDB_IP="127.0.0.1" -TIDB_PORT="4000" -TIDB_ADDR="127.0.0.1:4000" -TIDB_STATUS_ADDR="127.0.0.1:10080" -# actaul tikv_addr are TIKV_ADDR${i} -TIKV_ADDR="127.0.0.1:2016" -TIKV_STATUS_ADDR="127.0.0.1:2018" -TIKV_COUNT=3 -TIFLASH_STATUS="127.0.0.1:17000" -TIFLASH_HTTP="127.0.0.1:8125" +export PD_PEER_ADDR="127.0.0.1:2380" +export PD_ADDR="127.0.0.1:2379" +export TIDB_IP="127.0.0.1" +export TIDB_PORT="4000" +export TIDB_ADDR="127.0.0.1:4000" +export TIDB_STATUS_ADDR="127.0.0.1:10080" +# actual tikv_addr are TIKV_ADDR${i} +export TIKV_ADDR="127.0.0.1:2016" +export TIKV_STATUS_ADDR="127.0.0.1:2018" +export TIKV_COUNT=3 +export TIFLASH_STATUS="127.0.0.1:17000" +export TIFLASH_HTTP="127.0.0.1:8125" +export IMPORTER_ADDR="127.0.0.1:8808" cleanup_data() { # Clean up data - for svc in "br" "tidb" "tiflash" "tikv" "pd"; do + for svc in "br" "tidb" "tiflash" "tikv" "pd" "importer"; do find "$TEST_DIR" -maxdepth 1 -name "${svc}*" -type d -exec echo delete {} \; -exec rm -rf {} \; 2> /dev/null done } stop_services() { - for svc in "br" "tidb-server" "tiflash" "TiFlashMain" "tikv-server" "pd-server"; do - killall -v -1 $svc || continue + for svc in "br" "tidb-server" "tiflash" "TiFlashMain" "tikv-server" "pd-server" "cdc" "minio" "tikv-importer"; do + killall -v -1 $svc 2>/dev/null || continue sleep 1 # give some grace shutdown period - killall -v -9 $svc || continue + killall -v -9 $svc &>/dev/null || continue done sleep 1 # give some time for the OS to reap all processes lsof -n -P -i :2379 -i :4000 -i :10080 -i :20161 -i :20162 -i :20163 -i :20181 -i :20182 -i :20183 -i :17000 -i :8125 || true @@ -61,29 +61,46 @@ start_services() { } start_services_impl() { - stop_services - cleanup_data - - source tests/_utils/make_tiflash_config - - TIDB_CONFIG="${1-tests}/config/tidb.toml" - TIKV_CONFIG="${1-tests}/config/tikv.toml" - PD_CONFIG="${1-tests}/config/pd.toml" - TIFLASH_CONFIG="${1-tests}/config/tiflash.toml" - - if [ ! -e $PD_CONFIG ]; then PD_CONFIG=tests/config/pd.toml; fi - if [ ! -e $TIFLASH_CONFIG ]; then TIFLASH_CONFIG=tests/config/tiflash.toml; fi + stop_services || true + cleanup_data || true + + TIDB_CONFIG="tests/config/tidb.toml" + TIKV_CONFIG="tests/config/tikv.toml" + PD_CONFIG="tests/config/pd.toml" + RUN_TIFLASH="YES" + + while [[ $# -gt 0 ]] + do + local key="$1" + + case $key in + --tidb-cfg) + TIDB_CONFIG="$2" + shift # past argument + shift # past value + ;; + --no-tiflash) + RUN_TIFLASH="NO" + shift # past argument + ;; + *) # unknown option + echo "Unknown args $@" + exit 1 + ;; + esac + done echo "Starting PD..." mkdir -p "$TEST_DIR/pd" bin/pd-server \ - --client-urls "http://$PD_ADDR" \ + --client-urls "https://$PD_ADDR" \ + --peer-urls "https://$PD_PEER_ADDR" \ --log-file "$TEST_DIR/pd.log" \ --data-dir "$TEST_DIR/pd" \ --config $PD_CONFIG & # wait until PD is online... i=0 - while ! curl -o /dev/null -sf "http://$PD_ADDR/pd/api/v1/version"; do + while ! run_curl "https://$PD_ADDR/pd/api/v1/version"; do i=$((i+1)) if [ "$i" -gt 20 ]; then echo 'Failed to start PD' @@ -100,13 +117,13 @@ start_services_impl() { -A "$TIKV_ADDR$i" \ --status-addr "$TIKV_STATUS_ADDR$i" \ --log-file "$TEST_DIR/tikv${i}.log" \ - --log-level debug \ + --log-level info \ -C "$TIKV_CONFIG" \ -s "$TEST_DIR/tikv${i}" & done echo "Waiting initializing TiKV..." - while ! curl -sf "http://$PD_ADDR/pd/api/v1/cluster/status" | grep '"is_initialized": true'; do + while ! run_curl "https://$PD_ADDR/pd/api/v1/cluster/status" | grep '"is_initialized": true'; do i=$((i+1)) if [ "$i" -gt 20 ]; then echo 'Failed to initialize TiKV cluster' @@ -119,6 +136,7 @@ start_services_impl() { bin/tidb-server \ -P 4000 \ --status 10080 \ + --advertise-address="127.0.0.1" \ --store tikv \ --path "$PD_ADDR" \ --config "$TIDB_CONFIG" \ @@ -126,7 +144,7 @@ start_services_impl() { echo "Verifying TiDB is started..." i=0 - while ! curl -o /dev/null -sf "http://$TIDB_IP:10080/status"; do + while ! run_curl "https://$TIDB_IP:10080/status"; do i=$((i+1)) if [ "$i" -gt 50 ]; then echo 'Failed to start TiDB' @@ -135,14 +153,21 @@ start_services_impl() { sleep 3 done - if [[ ! $@ =~ "--no-tiflash" ]]; then + echo "Starting Importer..." + bin/tikv-importer \ + --addr "$IMPORTER_ADDR" \ + --import-dir "$TEST_DIR/importer" \ + --log-file "$TEST_DIR/importer.log" \ + --config "tests/config/importer.toml" & + + if [[ $RUN_TIFLASH == "YES" ]]; then if ! start_tiflash; then return 1 fi fi i=0 - while ! curl "http://$PD_ADDR/pd/api/v1/cluster/status" -sf | grep -q "\"is_initialized\": true"; do + while ! run_curl "https://$PD_ADDR/pd/api/v1/cluster/status" | grep -q "\"is_initialized\": true"; do i=$((i+1)) if [ "$i" -gt 20 ]; then echo 'Failed to bootstrap cluster' @@ -154,11 +179,11 @@ start_services_impl() { start_tiflash() { echo "Starting TiFlash..." - LD_LIBRARY_PATH=bin/ bin/tiflash server --config-file=$TIFLASH_CONFIG & - echo "TiFlash started..." + tests/_utils/make_tiflash_config + LD_LIBRARY_PATH=bin/ bin/tiflash server --config-file="$TEST_DIR/tiflash.toml" & i=0 - while ! curl -sf http://$TIFLASH_HTTP 1>/dev/null 2>&1; do + while ! run_curl https://$TIFLASH_HTTP 1>/dev/null 2>&1; do i=$((i+1)) if [ "$i" -gt 20 ]; then echo "failed to start tiflash" @@ -167,98 +192,6 @@ start_tiflash() { echo "TiFlash seems doesn't started, retrying..." sleep 3 done -} - -start_services_with_tls() { - stop_services - cleanup_data - - PD_CONFIG="$1/config/pd.toml" - TIDB_CONFIG="$1/config/tidb.toml" - TIKV_CONFIG="$1/config/tikv.toml" - - echo $PD_CONFIG - echo $TIDB_CONFIG - echo $TIKV_CONFIG - - echo "Starting PD..." - bin/pd-server \ - --client-urls "https://$PD_ADDR" \ - --log-file "$TEST_DIR/pd.log" \ - --config "$PD_CONFIG" \ - --data-dir "$TEST_DIR/pd" & - # wait until PD is online... - i=0 - while ! curl --cacert $1/certificates/ca.pem \ - --cert $1/certificates/client.pem \ - --key $1/certificates/client-key.pem \ - -o /dev/null -sf "https://$PD_ADDR/pd/api/v1/version"; do - i=$((i+1)) - if [ "$i" -gt 20 ]; then - echo 'Failed to start PD' - exit 1 - fi - sleep 3 - done - - echo "Starting TiKV..." - for i in $(seq $TIKV_COUNT); do - bin/tikv-server \ - --pd "$PD_ADDR" \ - -A "$TIKV_ADDR$i" \ - --status-addr "$TIKV_STATUS_ADDR$i" \ - --log-file "$TEST_DIR/tikv${i}.log" \ - --log-level debug \ - -C "$TIKV_CONFIG" \ - -s "$TEST_DIR/tikv${i}" & - done - - echo "Waiting initializing TiKV..." - while ! curl --cacert $1/certificates/ca.pem \ - --cert $1/certificates/client.pem \ - --key $1/certificates/client-key.pem \ - -sf "https://$PD_ADDR/pd/api/v1/cluster/status" | grep '"is_initialized": true'; do - i=$((i+1)) - if [ "$i" -gt 20 ]; then - echo 'Failed to initialize TiKV cluster' - exit 1 - fi - sleep 4 - done - - echo "Starting TiDB..." - bin/tidb-server \ - -P 4000 \ - --status 10080 \ - --store tikv \ - --config "$TIDB_CONFIG" \ - --path "$PD_ADDR" \ - --log-file "$TEST_DIR/tidb.log" & - - echo "Verifying TiDB is started..." - i=0 - while ! curl --cacert $1/certificates/ca.pem \ - --cert $1/certificates/client.pem \ - --key $1/certificates/client-key.pem \ - -o /dev/null -sf "https://$TIDB_IP:10080/status"; do - i=$((i+1)) - if [ "$i" -gt 50 ]; then - echo 'Failed to start TiDB' - exit 1 - fi - sleep 3 - done - i=0 - while ! curl --cacert $1/certificates/ca.pem \ - --cert $1/certificates/client.pem \ - --key $1/certificates/client-key.pem \ - "https://$PD_ADDR/pd/api/v1/cluster/status" -sf | grep -q "\"is_initialized\": true"; do - i=$((i+1)) - if [ "$i" -gt 20 ]; then - echo 'Failed to bootstrap cluster' - exit 1 - fi - sleep 3 - done + echo "TiFlash started." } diff --git a/tests/_utils/run_sql b/tests/_utils/run_sql index 906af942d..80dfe9f93 100755 --- a/tests/_utils/run_sql +++ b/tests/_utils/run_sql @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash # # Copyright 2019 PingCAP, Inc. # @@ -13,7 +13,15 @@ # See the License for the specific language governing permissions and # limitations under the License. -set -eu +set -euo pipefail -echo "[$(date)] Executing SQL: $1" > "$TEST_DIR/sql_res.$TEST_NAME.txt" -mysql -uroot -h127.0.0.1 -P4000 --default-character-set utf8 -E -e "$1" +SQL="$1" +shift + +echo "[$(date)] Executing SQL: $SQL" > "$TEST_DIR/sql_res.$TEST_NAME.txt" +mysql -uroot -h127.0.0.1 -P4000 \ + --ssl-ca="$TEST_DIR/certs/ca.pem" \ + --ssl-cert="$TEST_DIR/certs/curl.pem" \ + --ssl-key="$TEST_DIR/certs/curl.key" \ + "$@" \ + --default-character-set utf8 -E -e "$SQL" | tee -a "$TEST_DIR/sql_res.$TEST_NAME.txt" diff --git a/tests/br_backup_empty/run.sh b/tests/br_backup_empty/run.sh index b95844fb5..25e9480e7 100644 --- a/tests/br_backup_empty/run.sh +++ b/tests/br_backup_empty/run.sh @@ -51,4 +51,3 @@ run_br --pd $PD_ADDR restore full -s "local://$TEST_DIR/empty_table" --ratelimit run_sql "INSERT INTO $DB.usertable1 VALUES (\"a\", \"b\");" run_sql "DROP DATABASE $DB" -echo "TEST: [$TEST_NAME] successed!" diff --git a/tests/br_db/run.sh b/tests/br_db/run.sh index de515bd06..8cec53b10 100755 --- a/tests/br_db/run.sh +++ b/tests/br_db/run.sh @@ -16,7 +16,7 @@ set -eu DB="$TEST_NAME" -old_conf=$(run_sql "show config where name = 'alter-primary-key'") +old_conf=$(run_sql "show config where name = 'alter-primary-key'" | awk '/Value/{print $2}') run_sql "CREATE DATABASE $DB;" run_sql "CREATE TABLE $DB.usertable1 ( \ @@ -53,10 +53,11 @@ fi # Test BR DDL query string echo "testing DDL query..." -curl 127.0.0.1:10080/ddl/history | grep -E '/\*from\(br\)\*/CREATE TABLE' -curl 127.0.0.1:10080/ddl/history | grep -E '/\*from\(br\)\*/CREATE DATABASE' +run_curl https://$TIDB_STATUS_ADDR/ddl/history | grep -E '/\*from\(br\)\*/CREATE TABLE' +run_curl https://$TIDB_STATUS_ADDR/ddl/history | grep -E '/\*from\(br\)\*/CREATE DATABASE' # test whether we have changed the cluster config. -test "$old_conf" = "$(run_sql "show config where name = 'alter-primary-key'")" +new_conf=$(run_sql "show config where name = 'alter-primary-key'" | awk '/Value/{print $2}') +test "$old_conf" = "$new_conf" run_sql "DROP DATABASE $DB;" diff --git a/tests/br_db_online_newkv/run.sh b/tests/br_db_online_newkv/run.sh index 528fed852..107f0655e 100755 --- a/tests/br_db_online_newkv/run.sh +++ b/tests/br_db_online_newkv/run.sh @@ -42,7 +42,7 @@ run_br --pd $PD_ADDR backup db --db "$DB" -s "local://$TEST_DIR/$DB" --ratelimit run_sql "DROP DATABASE $DB;" # enable placement rules -echo "config set enable-placement-rules true" | pd-ctl +run_pd_ctl -u https://$PD_ADDR config set enable-placement-rules true # add new tikv for restore # actaul tikv_addr are TIKV_ADDR${i} @@ -73,6 +73,6 @@ if [ "$table_count" -ne "2" ];then exit 1 fi -echo "config set enable-placement-rules false" | pd-ctl +run_pd_ctl -u https://$PD_ADDR config set enable-placement-rules false run_sql "DROP DATABASE $DB;" diff --git a/tests/br_full/run.sh b/tests/br_full/run.sh index 560998e82..a29870c7e 100755 --- a/tests/br_full/run.sh +++ b/tests/br_full/run.sh @@ -36,11 +36,11 @@ export GO_FAILPOINTS="" # backup full echo "backup with lz4 start..." -run_br --pd "http://$PD_ADDR" backup full -s "local://$TEST_DIR/$DB-lz4" --concurrency 4 --compression lz4 +run_br --pd $PD_ADDR backup full -s "local://$TEST_DIR/$DB-lz4" --concurrency 4 --compression lz4 size_lz4=$(du -d 0 $TEST_DIR/$DB-lz4 | awk '{print $1}') echo "backup with zstd start..." -run_br --pd "http://$PD_ADDR" backup full -s "local://$TEST_DIR/$DB-zstd" --concurrency 4 --compression zstd --compression-level 6 +run_br --pd $PD_ADDR backup full -s "local://$TEST_DIR/$DB-zstd" --concurrency 4 --compression zstd --compression-level 6 size_zstd=$(du -d 0 $TEST_DIR/$DB-zstd | awk '{print $1}') if [ "$size_lz4" -le "$size_zstd" ]; then @@ -73,8 +73,6 @@ for ct in limit lz4 zstd; do if $fail; then echo "TEST: [$TEST_NAME] failed!" exit 1 - else - echo "TEST: [$TEST_NAME] successed!" fi done diff --git a/tests/br_full_ddl/run.sh b/tests/br_full_ddl/run.sh index 0dd2442f1..e88030064 100755 --- a/tests/br_full_ddl/run.sh +++ b/tests/br_full_ddl/run.sh @@ -63,7 +63,7 @@ run_sql "analyze table $DB.$TABLE;" # } # ] # } -curl $TIDB_IP:10080/stats/dump/$DB/$TABLE | jq '.columns.field0' | jq 'del(.last_update_version)' > $BACKUP_STAT +run_curl https://$TIDB_STATUS_ADDR/stats/dump/$DB/$TABLE | jq '.columns.field0 | del(.last_update_version, .correlation)' > $BACKUP_STAT # backup full echo "backup start..." @@ -91,6 +91,24 @@ if [[ "${cluster_index_before_backup}" != "${cluster_index_before_restore}" ]]; exit 1 fi +<<<<<<< HEAD +======= +echo "restore full without stats..." +run_br restore full -s "local://$TEST_DIR/${DB}_disable_stats" --pd $PD_ADDR +curl $TIDB_IP:10080/stats/dump/$DB/$TABLE | jq '.columns.field0 | del(.last_update_version, .correlation)' > $RESOTRE_STAT + +# stats should not be equal because we disable stats by default. +if diff -q $BACKUP_STAT $RESOTRE_STAT > /dev/null +then + echo "TEST: [$TEST_NAME] fail due to stats are equal" + grep ERROR $LOG + exit 1 +fi + +# clear restore environment +run_sql "DROP DATABASE $DB;" + +>>>>>>> 8b9b626... Merge branch 'merging' # restore full echo "restore start..." export GO_FAILPOINTS="github.com/pingcap/br/pkg/pdutil/PDEnabledPauseConfig=return(true)" @@ -117,15 +135,16 @@ if [ "${skip_count}" -gt "2" ];then exit 1 fi -curl $TIDB_IP:10080/stats/dump/$DB/$TABLE | jq '.columns.field0' | jq 'del(.last_update_version)' > $RESOTRE_STAT +run_curl https://$TIDB_STATUS_ADDR/stats/dump/$DB/$TABLE | jq '.columns.field0 | del(.last_update_version, .correlation)' > $RESOTRE_STAT if diff -q $BACKUP_STAT $RESOTRE_STAT > /dev/null then echo "stats are equal" else echo "TEST: [$TEST_NAME] fail due to stats are not equal" - cat $BACKUP_STAT | head 1000 - cat $RESOTRE_STAT | head 1000 + grep ERROR $LOG + cat $BACKUP_STAT | head -n 1000 + cat $RESOTRE_STAT | head -n 1000 exit 1 fi @@ -141,8 +160,6 @@ echo "database $DB$ [original] row count: ${row_count_ori}, [after br] row count if $fail; then echo "TEST: [$TEST_NAME] failed!" exit 1 -else - echo "TEST: [$TEST_NAME] successed!" fi run_sql "DROP DATABASE $DB;" diff --git a/tests/br_full_index/run.sh b/tests/br_full_index/run.sh index 29b8a86a8..2135e1c34 100755 --- a/tests/br_full_index/run.sh +++ b/tests/br_full_index/run.sh @@ -74,8 +74,6 @@ done if $fail; then echo "TEST: [$TEST_NAME] failed!" exit 1 -else - echo "TEST: [$TEST_NAME] successed!" fi for i in $(seq $DB_COUNT); do diff --git a/tests/br_gcs/run.sh b/tests/br_gcs/run.sh index a5be052de..ec73affe9 100755 --- a/tests/br_gcs/run.sh +++ b/tests/br_gcs/run.sh @@ -24,7 +24,6 @@ BUCKET="test" # we need set public-host for download file, or it will return 404 when using client to read. bin/fake-gcs-server -scheme http -host $GCS_HOST -port $GCS_PORT -backend memory -public-host $GCS_HOST:$GCS_PORT & -GCS_ID=$! i=0 while ! curl -o /dev/null -v -s "http://$GCS_HOST:$GCS_PORT/"; do i=$(($i+1)) @@ -36,12 +35,16 @@ while ! curl -o /dev/null -v -s "http://$GCS_HOST:$GCS_PORT/"; do done # start oauth server +<<<<<<< HEAD FLASK_APP=tests/$TEST_NAME/oauth.py flask run & OAUTH_ID=$! +======= +bin/oauth & +>>>>>>> 8b9b626... Merge branch 'merging' stop_gcs() { - kill -2 $GCS_ID - kill -2 $OAUTH_ID + killall -2 fake-gcs-server || true + killall -2 oauth || true } trap stop_gcs EXIT @@ -63,7 +66,8 @@ KEY=$(cat <<- EOF "client_email": "test@email.com", "token_uri": "http://localhost:5000/oauth/token" } -EOF) +EOF +) # save CREDENTIALS to file echo $KEY > "tests/$TEST_NAME/config.json" @@ -84,7 +88,12 @@ run_br --pd $PD_ADDR backup full -s "gcs://$BUCKET/$DB?endpoint=http://$GCS_HOST # old version backup full v4.0.8 and disable check-requirements echo "v4.0.8 backup start..." -bin/brv4.0.8 --pd $PD_ADDR backup full -s "gcs://$BUCKET/${DB}_old?endpoint=http://$GCS_HOST:$GCS_PORT/storage/v1/" --check-requirements=false +bin/brv4.0.8 backup full \ + -L "debug" \ + --ca "$TEST_DIR/certs/ca.pem" \ + --cert "$TEST_DIR/certs/br.pem" \ + --key "$TEST_DIR/certs/br.key" \ + --pd $PD_ADDR -s "gcs://$BUCKET/${DB}_old?endpoint=http://$GCS_HOST:$GCS_PORT/storage/v1/" --check-requirements=false # clean up for i in $(seq $DB_COUNT); do @@ -139,6 +148,4 @@ done if $fail; then echo "TEST: [$TEST_NAME] failed!" exit 1 -else - echo "TEST: [$TEST_NAME] v4.0.8 version successd!" fi diff --git a/tests/br_history/run.sh b/tests/br_history/run.sh index bfa163d5b..8ab4c7057 100755 --- a/tests/br_history/run.sh +++ b/tests/br_history/run.sh @@ -60,8 +60,6 @@ done if $fail; then echo "TEST: [$TEST_NAME] failed!" exit 1 -else - echo "TEST: [$TEST_NAME] successed!" fi for i in $(seq $DB_COUNT); do diff --git a/tests/br_incompatible_tidb_config/config/tidb-allow-auto-random.toml b/tests/br_incompatible_tidb_config/config/tidb-allow-auto-random.toml new file mode 100644 index 000000000..85bc6e01a --- /dev/null +++ b/tests/br_incompatible_tidb_config/config/tidb-allow-auto-random.toml @@ -0,0 +1,19 @@ +# config of tidb + +# Schema lease duration +# There are lot of ddl in the tests, setting this +# to 360s to test whther BR is gracefully shutdown. +lease = "360s" + +max-index-length = 12288 + +[security] +ssl-ca = "/tmp/backup_restore_test/certs/ca.pem" +ssl-cert = "/tmp/backup_restore_test/certs/tidb.pem" +ssl-key = "/tmp/backup_restore_test/certs/tidb.key" +cluster-ssl-ca = "/tmp/backup_restore_test/certs/ca.pem" +cluster-ssl-cert = "/tmp/backup_restore_test/certs/tidb.pem" +cluster-ssl-key = "/tmp/backup_restore_test/certs/tidb.key" + +[experimental] +allow-auto-random = true diff --git a/tests/br_incompatible_tidb_config/config/tidb-alter-primary-key.toml b/tests/br_incompatible_tidb_config/config/tidb-alter-primary-key.toml new file mode 100644 index 000000000..4a430b089 --- /dev/null +++ b/tests/br_incompatible_tidb_config/config/tidb-alter-primary-key.toml @@ -0,0 +1,17 @@ +# config of tidb + +# Schema lease duration +# There are lot of ddl in the tests, setting this +# to 360s to test whther BR is gracefully shutdown. +lease = "360s" + +alter-primary-key = true +max-index-length = 12288 + +[security] +ssl-ca = "/tmp/backup_restore_test/certs/ca.pem" +ssl-cert = "/tmp/backup_restore_test/certs/tidb.pem" +ssl-key = "/tmp/backup_restore_test/certs/tidb.key" +cluster-ssl-ca = "/tmp/backup_restore_test/certs/ca.pem" +cluster-ssl-cert = "/tmp/backup_restore_test/certs/tidb.pem" +cluster-ssl-key = "/tmp/backup_restore_test/certs/tidb.key" diff --git a/tests/br_incompatible_tidb_config/config/tidb.toml b/tests/br_incompatible_tidb_config/config/tidb.toml deleted file mode 100644 index f649c2a28..000000000 --- a/tests/br_incompatible_tidb_config/config/tidb.toml +++ /dev/null @@ -1,11 +0,0 @@ -# config of tidb - -# Schema lease duration -# There are lot of ddl in the tests, setting this -# to 360s to test whther BR is gracefully shutdown. -lease = "360s" - -alter-primary-key = true - -max-index-length = 12288 - diff --git a/tests/br_incompatible_tidb_config/config/tikv.toml b/tests/br_incompatible_tidb_config/config/tikv.toml deleted file mode 100644 index edcd02a98..000000000 --- a/tests/br_incompatible_tidb_config/config/tikv.toml +++ /dev/null @@ -1,14 +0,0 @@ -# config of tikv - -[coprocessor] -region-max-keys = 20 -region-split-keys = 12 - -[rocksdb] -max-open-files = 4096 -[raftdb] -max-open-files = 4096 -[raftstore] -# true (default value) for high reliability, this can prevent data loss when power failure. -sync-log = false -capacity = "10GB" diff --git a/tests/br_incompatible_tidb_config/run.sh b/tests/br_incompatible_tidb_config/run.sh index 718666260..77b9c6f5c 100755 --- a/tests/br_incompatible_tidb_config/run.sh +++ b/tests/br_incompatible_tidb_config/run.sh @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -set -eu +set -eux cur=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) source $cur/../_utils/run_services @@ -22,7 +22,7 @@ DB="$TEST_NAME" # prepare database echo "Restart cluster with alter-primary-key = true, max-index-length=12288" -start_services "$cur" +start_services --tidb-cfg $cur/config/tidb-alter-primary-key.toml run_sql "drop schema if exists $DB;" run_sql "create schema $DB;" @@ -77,13 +77,8 @@ run_sql "drop schema $DB;" # invalid config allow-auto-random is unavailable when alter-primary-key is enabled # enable column attribute `auto_random` to be defined on the primary key column. -cat > $cur/config/tidb.toml << EOF -[experimental] -allow-auto-random = true -EOF - echo "Restart cluster with allow-auto-random=true" -start_services "$cur" +start_services --tidb-cfg $cur/config/tidb-allow-auto-random.toml # test auto random issue https://github.com/pingcap/br/issues/228 TABLE="t3" diff --git a/tests/br_incremental_only_ddl/run.sh b/tests/br_incremental_only_ddl/run.sh index f525acda4..87bcdf607 100755 --- a/tests/br_incremental_only_ddl/run.sh +++ b/tests/br_incremental_only_ddl/run.sh @@ -65,8 +65,6 @@ run_br restore table --db $DB --table $TABLE -s "local://$TEST_DIR/$DB/inc" --pd if $fail; then echo "TEST: [$TEST_NAME] incremental restore fail on database $DB" exit 1 -else - echo "TEST: [$TEST_NAME] successed!" fi run_sql "DROP DATABASE $DB;" diff --git a/tests/br_insert_after_restore/run.sh b/tests/br_insert_after_restore/run.sh index 1c72db9ee..3cfaef229 100755 --- a/tests/br_insert_after_restore/run.sh +++ b/tests/br_insert_after_restore/run.sh @@ -75,8 +75,6 @@ fi if $fail; then echo "TEST: [$TEST_NAME] failed!" exit 1 -else - echo "TEST: [$TEST_NAME] successed!" fi run_sql "DROP DATABASE $DB;" diff --git a/tests/br_key_locked/locker.go b/tests/br_key_locked/locker.go index 146ebedd0..d75b8381f 100644 --- a/tests/br_key_locked/locker.go +++ b/tests/br_key_locked/locker.go @@ -33,6 +33,7 @@ import ( "github.com/pingcap/kvproto/pkg/kvrpcpb" "github.com/pingcap/log" "github.com/pingcap/parser/model" + "github.com/pingcap/tidb/config" "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/store/tikv" "github.com/pingcap/tidb/store/tikv/oracle" @@ -40,9 +41,15 @@ import ( "github.com/pingcap/tidb/tablecodec" pd "github.com/tikv/pd/client" "go.uber.org/zap" + + "github.com/pingcap/br/pkg/httputil" + "github.com/pingcap/br/pkg/task" ) var ( + ca = flag.String("ca", "", "CA certificate path for TLS connection") + cert = flag.String("cert", "", "certificate path for TLS connection") + key = flag.String("key", "", "private key path for TLS connection") tidbStatusAddr = flag.String("tidb", "", "TiDB status address") pdAddr = flag.String("pd", "", "PD address") dbName = flag.String("db", "", "Database name") @@ -76,12 +83,23 @@ func main() { log.Panic("get table id failed", zap.Error(err)) } - pdclient, err := pd.NewClient([]string{*pdAddr}, pd.SecurityOption{}) + pdclient, err := pd.NewClient([]string{*pdAddr}, pd.SecurityOption{ + CAPath: *ca, + CertPath: *cert, + KeyPath: *key, + }) if err != nil { log.Panic("create pd client failed", zap.Error(err)) } pdcli := &codecPDClient{Client: pdclient} + if len(*ca) != 0 { + tidbCfg := config.NewConfig() + tidbCfg.Security.ClusterSSLCA = *ca + tidbCfg.Security.ClusterSSLCert = *cert + tidbCfg.Security.ClusterSSLKey = *key + config.StoreGlobalConfig(tidbCfg) + } driver := tikv.Driver{} store, err := driver.Open(fmt.Sprintf("tikv://%s?disableGC=true", *pdAddr)) if err != nil { @@ -101,6 +119,22 @@ func main() { } } +func newHTTPClient() *http.Client { + if len(*ca) != 0 { + tlsCfg := &task.TLSConfig{ + CA: *ca, + Cert: *cert, + Key: *key, + } + cfg, err := tlsCfg.ToTLSConfig() + if err != nil { + log.Panic("fail to parse TLS config", zap.Error(err)) + } + return httputil.NewClient(cfg) + } + return http.DefaultClient +} + // getTableID of the table with specified table name. func getTableID(ctx context.Context, dbAddr, dbName, table string) (int64, error) { dbHost, _, err := net.SplitHostPort(dbAddr) @@ -108,13 +142,14 @@ func getTableID(ctx context.Context, dbAddr, dbName, table string) (int64, error return 0, errors.Trace(err) } dbStatusAddr := net.JoinHostPort(dbHost, "10080") - url := fmt.Sprintf("http://%s/schema/%s/%s", dbStatusAddr, dbName, table) + url := fmt.Sprintf("https://%s/schema/%s/%s", dbStatusAddr, dbName, table) + client := newHTTPClient() req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return 0, errors.Trace(err) } - resp, err := http.DefaultClient.Do(req) + resp, err := client.Do(req) if err != nil { return 0, errors.Trace(err) } diff --git a/tests/br_key_locked/run.sh b/tests/br_key_locked/run.sh index fc5e7a2db..374b277c3 100755 --- a/tests/br_key_locked/run.sh +++ b/tests/br_key_locked/run.sh @@ -25,7 +25,13 @@ go-ycsb load mysql -P tests/$TEST_NAME/workload -p mysql.host=$TIDB_IP -p mysql. row_count_ori=$(run_sql "SELECT COUNT(*) FROM $DB.$TABLE;" | awk '/COUNT/{print $2}') # put locks with TTL 10s, we assume a normal backup finishs within 10s, so it will meet locks. -bin/locker -tidb $TIDB_STATUS_ADDR -pd $PD_ADDR -db $DB -table $TABLE -lock-ttl "10s" -run-timeout "3s" +bin/locker \ + -tidb $TIDB_STATUS_ADDR \ + -pd $PD_ADDR \ + -ca "$TEST_DIR/certs/ca.pem" \ + -cert "$TEST_DIR/certs/br.pem" \ + -key "$TEST_DIR/certs/br.key" \ + -db $DB -table $TABLE -lock-ttl "10s" -run-timeout "3s" # backup table echo "backup start..." @@ -41,9 +47,7 @@ row_count_new=$(run_sql "SELECT COUNT(*) FROM $DB.$TABLE;" | awk '/COUNT/{print echo "[original] row count: $row_count_ori, [after br] row count: $row_count_new" -if [ "$row_count_ori" -eq "$row_count_new" ];then - echo "TEST: [$TEST_NAME] successed!" -else +if [ "$row_count_ori" -ne "$row_count_new" ];then echo "TEST: [$TEST_NAME] failed!" exit 1 fi diff --git a/tests/br_log_restore/run.sh b/tests/br_log_restore/run.sh index 251796318..1fc7f5c02 100755 --- a/tests/br_log_restore/run.sh +++ b/tests/br_log_restore/run.sh @@ -30,9 +30,8 @@ export S3_ENDPOINT=127.0.0.1:24928 rm -rf "$TEST_DIR/$DB" mkdir -p "$TEST_DIR/$DB" bin/minio server --address $S3_ENDPOINT "$TEST_DIR/$DB" & -MINIO_PID=$! i=0 -while ! curl -o /dev/null -v -s "http://$S3_ENDPOINT/"; do +while ! curl -o /dev/null -s "http://$S3_ENDPOINT/"; do i=$(($i+1)) if [ $i -gt 7 ]; then echo 'Failed to start minio' @@ -41,17 +40,10 @@ while ! curl -o /dev/null -v -s "http://$S3_ENDPOINT/"; do sleep 2 done - s3cmd --access_key=$MINIO_ACCESS_KEY --secret_key=$MINIO_SECRET_KEY --host=$S3_ENDPOINT --host-bucket=$S3_ENDPOINT --no-ssl mb s3://$BUCKET # Start cdc servers -bin/cdc server --pd=http://$PD_ADDR --log-file=ticdc.log --addr=0.0.0.0:18301 --advertise-addr=127.0.0.1:18301 & -CDC_PID=$! -stop_tmp_server() { - kill -2 $MINIO_PID - kill -2 $CDC_PID -} -trap stop_tmp_server EXIT +run_cdc server --pd=https://$PD_ADDR --log-file=ticdc.log --addr=0.0.0.0:18301 --advertise-addr=127.0.0.1:18301 & # TODO: remove this after TiCDC supports TiDB clustered index run_sql "set @@global.tidb_enable_clustered_index=0" @@ -59,7 +51,7 @@ run_sql "set @@global.tidb_enable_clustered_index=0" sleep 2 # create change feed for s3 log -bin/cdc cli changefeed create --pd=http://$PD_ADDR --sink-uri="s3://$BUCKET/$DB?endpoint=http://$S3_ENDPOINT" --changefeed-id="simple-replication-task" +run_cdc cli changefeed create --pd=https://$PD_ADDR --sink-uri="s3://$BUCKET/$DB?endpoint=http://$S3_ENDPOINT" --changefeed-id="simple-replication-task" start_ts=$(run_sql "show master status;" | grep Position | awk -F ':' '{print $2}' | xargs) @@ -102,7 +94,7 @@ run_sql "insert into ${DB}_DDL2.t2 values (5, 'x');" sleep 80 # remove the change feed, because we don't want to record the drop ddl. -echo "Y" | bin/cdc cli unsafe reset --pd=http://$PD_ADDR +echo "Y" | run_cdc cli unsafe reset --pd=https://$PD_ADDR for i in $(seq $DB_COUNT); do run_sql "DROP DATABASE $DB${i};" @@ -111,6 +103,10 @@ run_sql "DROP DATABASE ${DB}_DDL1" run_sql "DROP DATABASE ${DB}_DDL2" # restore full +<<<<<<< HEAD +======= +export GO_FAILPOINTS='github.com/pingcap/br/pkg/lightning/backend/FailIngestMeta=return("notleader")' +>>>>>>> 8b9b626... Merge branch 'merging' echo "restore start..." run_br restore cdclog -s "s3://$BUCKET/$DB" --pd $PD_ADDR --s3.endpoint="http://$S3_ENDPOINT" \ --log-file "restore.log" --log-level "info" --start-ts $start_ts --end-ts $end_ts @@ -134,6 +130,10 @@ if [ "$row_count" -ne "0" ]; then echo "TEST: [$TEST_NAME] fail on ts range test." fi +<<<<<<< HEAD +======= +export GO_FAILPOINTS='github.com/pingcap/br/pkg/lightning/backend/FailIngestMeta=return("epochnotmatch")' +>>>>>>> 8b9b626... Merge branch 'merging' echo "restore again to restore a=5 record..." run_br restore cdclog -s "s3://$BUCKET/$DB" --pd $PD_ADDR --s3.endpoint="http://$S3_ENDPOINT" \ --log-file "restore.log" --log-level "info" --start-ts $end_ts @@ -156,8 +156,6 @@ done if $fail; then echo "TEST: [$TEST_NAME] failed!" exit 1 -else - echo "TEST: [$TEST_NAME] successed!" fi for i in $(seq $DB_COUNT); do diff --git a/tests/br_move_backup/run.sh b/tests/br_move_backup/run.sh index b85d25823..d71b05dd0 100755 --- a/tests/br_move_backup/run.sh +++ b/tests/br_move_backup/run.sh @@ -49,9 +49,7 @@ row_count_new=$(run_sql "SELECT COUNT(*) FROM $DB.$TABLE;" | awk '/COUNT/{print echo "[original] row count: $row_count_ori, [after br] row count: $row_count_new" -if [ "$row_count_ori" -eq "$row_count_new" ];then - echo "TEST: [$TEST_NAME] successed!" -else +if [ "$row_count_ori" -ne "$row_count_new" ];then echo "TEST: [$TEST_NAME] failed!" exit 1 fi diff --git a/tests/br_other/run.sh b/tests/br_other/run.sh index e01de0f8d..7ebdef3f8 100644 --- a/tests/br_other/run.sh +++ b/tests/br_other/run.sh @@ -17,8 +17,6 @@ set -eu DB="$TEST_NAME" run_sql "CREATE DATABASE $DB;" -trap "run_sql \"DROP DATABASE $DB;\"" EXIT - run_sql "CREATE TABLE $DB.usertable1 ( \ YCSB_KEY varchar(64) NOT NULL, \ @@ -66,8 +64,11 @@ fi echo "backup start to test lock file" PPROF_PORT=6080 GO_FAILPOINTS="github.com/pingcap/br/pkg/utils/determined-pprof-port=return($PPROF_PORT)" \ -run_br --pd $PD_ADDR backup full -s "local://$TEST_DIR/$DB/lock" --remove-schedulers --ratelimit 1 --ratelimit-unit 1 --concurrency 4 2>&1 > $TEST_DIR/br-other-stdout.log & -trap "cat $TEST_DIR/br-other-stdout.log" EXIT +run_br --pd $PD_ADDR backup full -s "local://$TEST_DIR/$DB/lock" \ + --remove-schedulers \ + --ratelimit 1 \ + --ratelimit-unit 1 \ + --concurrency 4 &> $TEST_DIR/br-other-stdout.log & # It will be killed after test finish. # record last backup pid _pid=$! @@ -79,14 +80,15 @@ echo "starting pprof..." # give the former backup some time to write down lock file (and start pprof server). sleep 1 -curl "http://localhost:$PPROF_PORT/debug/pprof/trace?seconds=1" 2>&1 > /dev/null +# TODO Support TLS. +run_curl "http://localhost:$PPROF_PORT/debug/pprof/trace?seconds=1" &>/dev/null echo "pprof started..." -curl http://$PD_ADDR/pd/api/v1/config/schedule | grep '"disable": false' -curl http://$PD_ADDR/pd/api/v1/config/schedule | jq '."enable-location-replacement"' | grep "false" -curl http://$PD_ADDR/pd/api/v1/config/schedule | jq '."max-pending-peer-count"' | grep "2147483647" -curl http://$PD_ADDR/pd/api/v1/config/schedule | jq '."max-merge-region-size"' | grep -E "^0$" -curl http://$PD_ADDR/pd/api/v1/config/schedule | jq '."max-merge-region-keys"' | grep -E "^0$" +run_curl https://$PD_ADDR/pd/api/v1/config/schedule | grep '"disable": false' +run_curl https://$PD_ADDR/pd/api/v1/config/schedule | jq '."enable-location-replacement"' | grep "false" +run_curl https://$PD_ADDR/pd/api/v1/config/schedule | jq '."max-pending-peer-count"' | grep "2147483647" +run_curl https://$PD_ADDR/pd/api/v1/config/schedule | jq '."max-merge-region-size"' | grep -E "^0$" +run_curl https://$PD_ADDR/pd/api/v1/config/schedule | jq '."max-merge-region-keys"' | grep -E "^0$" backup_fail=0 echo "another backup start expect to fail due to last backup add a lockfile" @@ -97,7 +99,7 @@ if [ "$backup_fail" -ne "1" ];then fi # check is there still exists scheduler not in pause. -pause_schedulers=$(curl http://$PD_ADDR/pd/api/v1/schedulers?status="paused" | grep "scheduler" | wc -l) +pause_schedulers=$(run_curl https://$PD_ADDR/pd/api/v1/schedulers?status="paused" | grep "scheduler" | wc -l) if [ "$pause_schedulers" -lt "3" ];then echo "TEST: [$TEST_NAME] failed because paused scheduler are not enough" exit 1 @@ -118,7 +120,7 @@ fi # make sure we won't stuck in non-scheduler state, even we send a SIGTERM to it. # give enough time to BR so it can gracefully stop. sleep 30 -if curl http://$PD_ADDR/pd/api/v1/config/schedule | jq '[."schedulers-v2"][0][0]' | grep -q '"disable": true' +if run_curl https://$PD_ADDR/pd/api/v1/config/schedule | jq '[."schedulers-v2"][0][0]' | grep -q '"disable": true' then echo "TEST: [$TEST_NAME] failed because scheduler has been removed" exit 1 @@ -135,8 +137,8 @@ default_pd_values='{ }' for key in $(echo $default_pd_values | jq 'keys[]'); do - if ! curl -s http://$PD_ADDR/pd/api/v1/config/schedule | jq ".[$key]" | grep -q $(echo $default_pd_values | jq ".[$key]"); then - curl -s http://$PD_ADDR/pd/api/v1/config/schedule + if ! run_curl https://$PD_ADDR/pd/api/v1/config/schedule | jq ".[$key]" | grep -q $(echo $default_pd_values | jq ".[$key]"); then + run_curl https://$PD_ADDR/pd/api/v1/config/schedule echo "[$TEST_NAME] failed due to PD config isn't reset after restore" exit 1 fi @@ -144,7 +146,7 @@ done # check is there still exists scheduler in pause. -pause_schedulers=$(curl http://$PD_ADDR/pd/api/v1/schedulers?status="paused" | grep "scheduler" | wc -l) +pause_schedulers=$(curl https://$PD_ADDR/pd/api/v1/schedulers?status="paused" | grep "scheduler" | wc -l) # There shouldn't be any paused schedulers since BR gracfully shutdown. if [ "$pause_schedulers" -ne "0" ];then echo "TEST: [$TEST_NAME] failed because paused scheduler has changed" @@ -154,23 +156,23 @@ fi pd_settings=6 # balance-region scheduler enabled -curl http://$PD_ADDR/pd/api/v1/config/schedule | jq '."schedulers-v2"[] | {disable: .disable, type: ."type" | select (.=="balance-region")}' | grep '"disable": false' || ((pd_settings--)) +run_curl https://$PD_ADDR/pd/api/v1/config/schedule | jq '."schedulers-v2"[] | {disable: .disable, type: ."type" | select (.=="balance-region")}' | grep '"disable": false' || ((pd_settings--)) # balance-leader scheduler enabled -curl http://$PD_ADDR/pd/api/v1/config/schedule | jq '."schedulers-v2"[] | {disable: .disable, type: ."type" | select (.=="balance-leader")}' | grep '"disable": false' || ((pd_settings--)) +run_curl https://$PD_ADDR/pd/api/v1/config/schedule | jq '."schedulers-v2"[] | {disable: .disable, type: ."type" | select (.=="balance-leader")}' | grep '"disable": false' || ((pd_settings--)) # hot region scheduler enabled -curl http://$PD_ADDR/pd/api/v1/config/schedule | jq '."schedulers-v2"[] | {disable: .disable, type: ."type" | select (.=="hot-region")}' | grep '"disable": false' || ((pd_settings--)) +run_curl https://$PD_ADDR/pd/api/v1/config/schedule | jq '."schedulers-v2"[] | {disable: .disable, type: ."type" | select (.=="hot-region")}' | grep '"disable": false' || ((pd_settings--)) # location replacement enabled -curl http://$PD_ADDR/pd/api/v1/config/schedule | jq '."enable-location-replacement"' | grep "true" || ((pd_settings--)) +run_curl https://$PD_ADDR/pd/api/v1/config/schedule | jq '."enable-location-replacement"' | grep "true" || ((pd_settings--)) # we need reset pd config to default # until pd has the solution to temporary set these scheduler/configs. run_br validate reset-pd-config-as-default --pd $PD_ADDR # max-merge-region-size set to default 20 -curl http://$PD_ADDR/pd/api/v1/config/schedule | jq '."max-merge-region-size"' | grep "20" || ((pd_settings--)) +run_curl https://$PD_ADDR/pd/api/v1/config/schedule | jq '."max-merge-region-size"' | grep "20" || ((pd_settings--)) # max-merge-region-keys set to default 200000 -curl http://$PD_ADDR/pd/api/v1/config/schedule | jq '."max-merge-region-keys"' | grep "200000" || ((pd_settings--)) +run_curl https://$PD_ADDR/pd/api/v1/config/schedule | jq '."max-merge-region-keys"' | grep "200000" || ((pd_settings--)) if [ "$pd_settings" -ne "6" ];then echo "TEST: [$TEST_NAME] test validate reset pd config failed!" @@ -181,3 +183,5 @@ fi # Test version run_br --version run_br -V + +run_sql "DROP DATABASE $DB;" diff --git a/tests/br_rawkv/client.go b/tests/br_rawkv/client.go index 0fe61316b..2ed1dd774 100644 --- a/tests/br_rawkv/client.go +++ b/tests/br_rawkv/client.go @@ -18,6 +18,9 @@ import ( ) var ( + ca = flag.String("ca", "", "CA certificate path for TLS connection") + cert = flag.String("cert", "", "certificate path for TLS connection") + key = flag.String("key", "", "private key path for TLS connection") pdAddr = flag.String("pd", "127.0.0.1:2379", "Address of PD") runMode = flag.String("mode", "", "Mode. One of 'rand-gen', 'checksum', 'scan', 'diff', 'delete' and 'put'") startKeyStr = flag.String("start-key", "", "Start key in hex") @@ -30,7 +33,11 @@ var ( ) func createClient(addr string) (*tikv.RawKVClient, error) { - cli, err := tikv.NewRawKVClient([]string{addr}, config.Security{}) + cli, err := tikv.NewRawKVClient([]string{addr}, config.Security{ + ClusterSSLCA: *ca, + ClusterSSLCert: *cert, + ClusterSSLKey: *key, + }) return cli, errors.Trace(err) } diff --git a/tests/br_rawkv/run.sh b/tests/br_rawkv/run.sh index 509393ead..ac32a843f 100644 --- a/tests/br_rawkv/run.sh +++ b/tests/br_rawkv/run.sh @@ -17,12 +17,18 @@ set -eu # restart service without tiflash source $( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )/../_utils/run_services -start_services "tests" --no-tiflash +start_services --no-tiflash -BACKUP_DIR="raw_backup" +BACKUP_DIR=$TEST_DIR/"raw_backup" + +rm -rf $BACKUP_DIR checksum() { - bin/rawkv --pd $PD_ADDR --mode checksum --start-key $1 --end-key $2 | grep result | awk '{print $3}' + bin/rawkv --pd $PD_ADDR \ + --ca "$TEST_DIR/certs/ca.pem" \ + --cert "$TEST_DIR/certs/br.pem" \ + --key "$TEST_DIR/certs/br.key" \ + --mode checksum --start-key $1 --end-key $2 | grep result | awk '{print $3}' } fail_and_exit() { @@ -33,20 +39,32 @@ fail_and_exit() { checksum_empty=$(checksum 31 3130303030303030) # generate raw kv randomly in range[start-key, end-key) in 10s -bin/rawkv --pd $PD_ADDR --mode rand-gen --start-key 31 --end-key 3130303030303030 --duration 10 +bin/rawkv --pd $PD_ADDR \ + --ca "$TEST_DIR/certs/ca.pem" \ + --cert "$TEST_DIR/certs/br.pem" \ + --key "$TEST_DIR/certs/br.key" \ + --mode rand-gen --start-key 31 --end-key 3130303030303030 --duration 10 # put some keys around 311122 to check the correctness of endKey of restoring -bin/rawkv --pd $PD_ADDR --mode put --put-data "311121:31, 31112100:32, 311122:33, 31112200:34, 3111220000:35, 311123:36" +bin/rawkv --pd $PD_ADDR \ + --ca "$TEST_DIR/certs/ca.pem" \ + --cert "$TEST_DIR/certs/br.pem" \ + --key "$TEST_DIR/certs/br.key" \ + --mode put --put-data "311121:31, 31112100:32, 311122:33, 31112200:34, 3111220000:35, 311123:36" checksum_ori=$(checksum 31 3130303030303030) checksum_partial=$(checksum 311111 311122) # backup rawkv echo "backup start..." -run_br --pd $PD_ADDR backup raw -s "local://$TEST_DIR/$BACKUP_DIR" --start 31 --end 3130303030303030 --format hex --concurrency 4 +run_br --pd $PD_ADDR backup raw -s "local://$BACKUP_DIR" --start 31 --end 3130303030303030 --format hex --concurrency 4 # delete data in range[start-key, end-key) -bin/rawkv --pd $PD_ADDR --mode delete --start-key 31 --end-key 3130303030303030 +bin/rawkv --pd $PD_ADDR \ + --ca "$TEST_DIR/certs/ca.pem" \ + --cert "$TEST_DIR/certs/br.pem" \ + --key "$TEST_DIR/certs/br.key" \ + --mode delete --start-key 31 --end-key 3130303030303030 # Ensure the data is deleted checksum_new=$(checksum 31 3130303030303030) @@ -58,7 +76,7 @@ fi # restore rawkv echo "restore start..." -run_br --pd $PD_ADDR restore raw -s "local://$TEST_DIR/$BACKUP_DIR" --start 31 --end 3130303030303030 --format hex +run_br --pd $PD_ADDR restore raw -s "local://$BACKUP_DIR" --start 31 --end 3130303030303030 --format hex checksum_new=$(checksum 31 3130303030303030) @@ -68,7 +86,11 @@ if [ "$checksum_new" != "$checksum_ori" ];then fi # delete data in range[start-key, end-key) -bin/rawkv --pd $PD_ADDR --mode delete --start-key 31 --end-key 3130303030303030 +bin/rawkv --pd $PD_ADDR \ + --ca "$TEST_DIR/certs/ca.pem" \ + --cert "$TEST_DIR/certs/br.pem" \ + --key "$TEST_DIR/certs/br.key" \ + --mode delete --start-key 31 --end-key 3130303030303030 # Ensure the data is deleted checksum_new=$(checksum 31 3130303030303030) @@ -79,8 +101,12 @@ if [ "$checksum_new" != "$checksum_empty" ];then fi echo "partial restore start..." -run_br --pd $PD_ADDR restore raw -s "local://$TEST_DIR/$BACKUP_DIR" --start 311111 --end 311122 --format hex --concurrency 4 -bin/rawkv --pd $PD_ADDR --mode scan --start-key 311121 --end-key 33 +run_br --pd $PD_ADDR restore raw -s "local://$BACKUP_DIR" --start 311111 --end 311122 --format hex --concurrency 4 +bin/rawkv --pd $PD_ADDR \ + --ca "$TEST_DIR/certs/ca.pem" \ + --cert "$TEST_DIR/certs/br.pem" \ + --key "$TEST_DIR/certs/br.key" \ + --mode scan --start-key 311121 --end-key 33 checksum_new=$(checksum 31 3130303030303030) @@ -88,5 +114,3 @@ if [ "$checksum_new" != "$checksum_partial" ];then echo "checksum failed after restore" fail_and_exit fi - -echo "TEST: [$TEST_NAME] successed!" diff --git a/tests/br_s3/run.sh b/tests/br_s3/run.sh index e2ed29cc6..84ced7893 100755 --- a/tests/br_s3/run.sh +++ b/tests/br_s3/run.sh @@ -28,7 +28,6 @@ export S3_ENDPOINT=127.0.0.1:24927 rm -rf "$TEST_DIR/$DB" mkdir -p "$TEST_DIR/$DB" bin/minio server --address $S3_ENDPOINT "$TEST_DIR/$DB" & -MINIO_PID=$! i=0 while ! curl -o /dev/null -v -s "http://$S3_ENDPOINT/"; do i=$(($i+1)) @@ -39,11 +38,6 @@ while ! curl -o /dev/null -v -s "http://$S3_ENDPOINT/"; do sleep 2 done -stop_minio() { - kill -2 $MINIO_PID -} -trap stop_minio EXIT - # Fill in the database for i in $(seq $DB_COUNT); do run_sql "CREATE DATABASE $DB${i};" @@ -109,8 +103,6 @@ for p in $(seq 2); do if $fail; then echo "TEST: [$TEST_NAME] failed!" exit 1 - else - echo "TEST: [$TEST_NAME] successed!" fi # prepare for next test diff --git a/tests/br_shuffle_leader/run.sh b/tests/br_shuffle_leader/run.sh index 2d533a4ad..f1c6bfadb 100755 --- a/tests/br_shuffle_leader/run.sh +++ b/tests/br_shuffle_leader/run.sh @@ -24,8 +24,12 @@ go-ycsb load mysql -P tests/$TEST_NAME/workload -p mysql.host=$TIDB_IP -p mysql. row_count_ori=$(run_sql "SELECT COUNT(*) FROM $DB.$TABLE;" | awk '/COUNT/{print $2}') # add shuffle leader scheduler +<<<<<<< HEAD echo "add shuffle-leader-scheduler" echo "-u $PD_ADDR -d sched add shuffle-leader-scheduler" | pd-ctl +======= +run_pd_ctl -u https://$PD_ADDR sched add shuffle-leader-scheduler +>>>>>>> 8b9b626... Merge branch 'merging' # backup with shuffle leader echo "backup start..." @@ -38,15 +42,17 @@ echo "restore start..." run_br restore table --db $DB --table $TABLE -s "local://$TEST_DIR/$DB" --pd $PD_ADDR # remove shuffle leader scheduler +<<<<<<< HEAD echo "-u $PD_ADDR -d sched remove shuffle-leader-scheduler" | pd-ctl +======= +run_pd_ctl -u https://$PD_ADDR sched remove shuffle-leader-scheduler +>>>>>>> 8b9b626... Merge branch 'merging' row_count_new=$(run_sql "SELECT COUNT(*) FROM $DB.$TABLE;" | awk '/COUNT/{print $2}') echo "[original] row count: $row_count_ori, [after br] row count: $row_count_new" -if [ "$row_count_ori" -eq "$row_count_new" ];then - echo "TEST: [$TEST_NAME] successed!" -else +if [ "$row_count_ori" -ne "$row_count_new" ];then echo "TEST: [$TEST_NAME] failed!" exit 1 fi diff --git a/tests/br_shuffle_region/run.sh b/tests/br_shuffle_region/run.sh index 3905f2bb0..7c6085cf0 100755 --- a/tests/br_shuffle_region/run.sh +++ b/tests/br_shuffle_region/run.sh @@ -25,7 +25,11 @@ row_count_ori=$(run_sql "SELECT COUNT(*) FROM $DB.$TABLE;" | awk '/COUNT/{print # add shuffle region scheduler echo "add shuffle-region-scheduler" +<<<<<<< HEAD echo "-u $PD_ADDR -d sched add shuffle-region-scheduler" | pd-ctl +======= +run_pd_ctl -u https://$PD_ADDR sched add shuffle-region-scheduler +>>>>>>> 8b9b626... Merge branch 'merging' # backup with shuffle region echo "backup start..." @@ -38,15 +42,17 @@ echo "restore start..." run_br restore table --db $DB --table $TABLE -s "local://$TEST_DIR/$DB" --pd $PD_ADDR # remove shuffle region scheduler +<<<<<<< HEAD echo "-u $PD_ADDR -d sched remove shuffle-region-scheduler" | pd-ctl +======= +run_pd_ctl -u https://$PD_ADDR sched remove shuffle-region-scheduler +>>>>>>> 8b9b626... Merge branch 'merging' row_count_new=$(run_sql "SELECT COUNT(*) FROM $DB.$TABLE;" | awk '/COUNT/{print $2}') echo "[original] row count: $row_count_ori, [after br] row count: $row_count_new" -if [ "$row_count_ori" -eq "$row_count_new" ];then - echo "TEST: [$TEST_NAME] successed!" -else +if [ "$row_count_ori" -ne "$row_count_new" ];then echo "TEST: [$TEST_NAME] failed!" exit 1 fi diff --git a/tests/br_single_table/run.sh b/tests/br_single_table/run.sh index 432e24850..c51799b0a 100755 --- a/tests/br_single_table/run.sh +++ b/tests/br_single_table/run.sh @@ -37,9 +37,7 @@ row_count_new=$(run_sql "SELECT COUNT(*) FROM $DB.$TABLE;" | awk '/COUNT/{print echo "[original] row count: $row_count_ori, [after br] row count: $row_count_new" -if [ "$row_count_ori" -eq "$row_count_new" ];then - echo "TEST: [$TEST_NAME] successed!" -else +if [ "$row_count_ori" -ne "$row_count_new" ];then echo "TEST: [$TEST_NAME] failed!" exit 1 fi diff --git a/tests/br_skip_checksum/run.sh b/tests/br_skip_checksum/run.sh index f4b93adea..a22ac3498 100755 --- a/tests/br_skip_checksum/run.sh +++ b/tests/br_skip_checksum/run.sh @@ -79,8 +79,6 @@ done if $fail; then echo "TEST: [$TEST_NAME] failed on restore without skipping checksum!" exit 1 -else - echo "TEST $TEST_NAME passed." fi for i in $(seq $DB_COUNT); do diff --git a/tests/br_split_region_fail/run.sh b/tests/br_split_region_fail/run.sh index afbc2f33e..464c194cb 100644 --- a/tests/br_split_region_fail/run.sh +++ b/tests/br_split_region_fail/run.sh @@ -82,7 +82,3 @@ fi for i in $(seq $DB_COUNT); do run_sql "DROP DATABASE $DB${i}" done - -echo "TEST $TEST_NAME passed." - - diff --git a/tests/br_table_partition/run.sh b/tests/br_table_partition/run.sh index fb94237dc..2bc70e7b9 100755 --- a/tests/br_table_partition/run.sh +++ b/tests/br_table_partition/run.sh @@ -59,8 +59,6 @@ done if $fail; then echo "TEST: [$TEST_NAME] failed!" exit 1 -else - echo "TEST: [$TEST_NAME] successed!" fi run_sql "DROP DATABASE $DB;" diff --git a/tests/br_tiflash/run.sh b/tests/br_tiflash/run.sh index 562684d85..875a7eb44 100644 --- a/tests/br_tiflash/run.sh +++ b/tests/br_tiflash/run.sh @@ -18,7 +18,7 @@ DB="${TEST_NAME}_DATABASE" RECORD_COUNT=1000 -run_sql "CREATE DATABASE $DB" +run_sql "CREATE DATABASE $DB" run_sql "CREATE TABLE $DB.kv(k varchar(256) primary key, v int)" @@ -62,5 +62,3 @@ if [ "$AFTER_BR_COUNT" -ne "$RECORD_COUNT" ]; then fi run_sql "DROP DATABASE $DB" - -echo "TEST $TEST_NAME passed!" diff --git a/tests/br_tls/certificates/ca.pem b/tests/br_tls/certificates/ca.pem deleted file mode 100644 index 49098d653..000000000 --- a/tests/br_tls/certificates/ca.pem +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDgDCCAmigAwIBAgIUHWvlRJydvYTR0ot3b8f6IlSHcGUwDQYJKoZIhvcNAQEL -BQAwVzELMAkGA1UEBhMCQ04xEDAOBgNVBAgTB0JlaWppbmcxEDAOBgNVBAcTB0Jl -aWppbmcxEDAOBgNVBAoTB1BpbmdDQVAxEjAQBgNVBAMTCU15IG93biBDQTAgFw0y -MDAyMTgwNzQxMDBaGA8yMTIwMDEyNTA3NDEwMFowVzELMAkGA1UEBhMCQ04xEDAO -BgNVBAgTB0JlaWppbmcxEDAOBgNVBAcTB0JlaWppbmcxEDAOBgNVBAoTB1BpbmdD -QVAxEjAQBgNVBAMTCU15IG93biBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC -AQoCggEBAOAdNtjanFhPaKJHQjr7+h/Cpps5bLc6S1vmgi/EIi9PKv3eyDgtlW1r -As2sjXRMHjcuZp2hHJ9r9FrMQD1rQQq5vJzQqM+eyWLc2tyZWXNWkZVvpjU4Hy5k -jZFLXoyHgAvps/LGu81F5Lk5CvLHswWTyGQUCFi1l/cYcQg6AExh2pO/WJu4hQhe -1mBBIKsJhZ5b5tWruLeI+YIjD1oo1ADMHYLK1BHON2fUmUHRGbrYKu4yCuyip3wn -rbVlpabn7l1JBMatCUJLHR6VWQ2MNjrOXAEUYm4xGEN+tUYyUOGl5mHFguLl3OIn -wj+1dT3WOr/NraPYlwVOnAd9GNbPJj0CAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEG -MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJ0CEqxLwEpI6J2gYJRg15oWZrj/ -MA0GCSqGSIb3DQEBCwUAA4IBAQCf8xRf7q1xAaGrc9HCPvN4OFkxDwz1CifrvrLR -ZgIWGUdCHDW2D1IiWKZQWeJKC1otA5x0hrS5kEGfkLFhADEU4txwp70DQaBArPti -pSgheIEbaT0H3BUTYSgS3VL2HjxN5OVMN6jNG3rWyxnJpNOCsJhhJXPK50CRZ7fk -Dcodj6FfEM2bfp2bGkxyVtUch7eepfUVbslXa7jE7Y8M3cr9NoLUcSP6D1RJWkNd -dBQoUsb6Ckq27ozEKOgwuBVv4BrrbFN//+7WHP8Vy6sSMyd+dJLBi6wehJjQhIz6 -vqLWE81rSJuxZqjLpCkFdeEF+9SRjWegU0ZDM4V+YeX53BPC ------END CERTIFICATE----- diff --git a/tests/br_tls/certificates/client-key.pem b/tests/br_tls/certificates/client-key.pem deleted file mode 100644 index 43b021796..000000000 --- a/tests/br_tls/certificates/client-key.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEA06qb7HABWHrU4CvBUO/2hXGgobi/UlTqTrYGZoJqSrvhKCP6 -HOivZjyWaSMDIfrguN+0C+bd80/5XGMmwjDt8PuZ+Ef3jcJLuB1e+Kms0s5tiTng -6Z028PkSpGvKXjPebWu7zoxVpDcTGM6MmlZQqpOuIgwi+7WX/bIgIu9wooCvJEGq -hScG2wpvK3txnpsVA4eXXdFoH0R5mtqbxVFfhwKMDqga4pRwJStLTDiMrtUz+OKc -rMIrkH4ndhvm2UYTVvhHlkZ3ooeDYsu40NnvedwaE+Ii7EnmcSDF9PaCVrXSK9F/ -KNRXX/x67PMWCVqcNyGtRsCuDe7FnDfGpudVXwIDAQABAoIBAHAzW/v1U4FHe1hp -WUxCJ3eNSAzyFdja0mlu6+2i7B05gpz4lTiFz5RuQXzx5lM43a6iRpqYgsbbed+T -X5RIw5iehnuqCnvGpsSuLQ27Q7VrX30ChUrQ37LVFSC7Usak0B9IoIFYun0WBLV9 -p+KYJqKFLiU2McUj+bGtnoNmUVqRzXQosoQue/pS9OknZ3NU7FxiyI3o4ME8rDvv -9x4vc1zcqbGXTQB224kOT0xoYt8RTmIbHvkR6/yszAtHDBcdzhINVuf38lv9FvaN -FxymwsY4IKPumQZlOEzHvSnpHTrwBMFdXjqpX1VxQb3yznEK+01MHf/tYsiU57IS -WVQMTeECgYEA7Fk0w66LGgdeeWrNTSTBCBPTofDVmR7Tro6k++5XTRt552ZoVz2l -8Lfi/Px5hIyve9gnM7slWrQ72JIQ5xVYZHtic3iwVFuRAD/QVfWU/SNsRsSB/93M -3BEumwJA6vN/qvkZueos3LOxN8kExk6GD0wIl6HjTeJPbbPHqmk6Pr0CgYEA5UQI -skaj8QGpjG8Hc10FeJpYsZiZA7gJaNu4RPqBj+1RHu/eYrL2mouooZdJfIJTmlTz -4NJcfb+Dl6qwbHUQ3mddhauFu1/YRwmaR5QKjwaBdeZbly9ljsRISFpjtosc7IBA -/Bl83xtbCboMdm7cH49X2CgRQ1uVFWraye0MBEsCgYEA43vtHFdYjaIAHa9dkV3J -6aNjtF/gxzNznXSwecfrAU1r5PydezLcEDh94vCDacAbe4EOIm2Dw6zsWUQlvrW9 -0WEs3mWQmnFTvECvnrz0PT2mDus/EO4EKuDi0dG2eC4MeJywVVB/A6J09XOnA9Q6 -lmihcIkiBinIN5etm2kS5aUCgYBCdcRnmZ6woKC7uvvX72FEosmPQgMpVtIzeW4j -YNLqHAtmAnbe+a4PAukxXp/I3ibKGFJSG+j/8uJ8tthJuG3ZavFrbFtqA9C4VwpI -MZwV9fbVbJ+kZfL0veWOQ9Wf9xe9Xzh3XBQcwNtVKH+wXVamN3FpkcPfWM8Q1Fb0 -LilLnQKBgQCq7+YlSnQX0rbmPTXVVb+B12rbqLDnqA0EuaVGrdu9zPPT04e5fpHU -SD33ibaEyeOF+zLg8T53whDbLJ0tURhUk+BlLTjdd99NXtyGMlfDnIsCnAeJhY8f -Iki6LYbbP2uWV4/5IDy9XW7J42Pfl9QyEVXq+PfTyPPjXC9/J4GOuw== ------END RSA PRIVATE KEY----- diff --git a/tests/br_tls/certificates/client.pem b/tests/br_tls/certificates/client.pem deleted file mode 100644 index 7dace2f9d..000000000 --- a/tests/br_tls/certificates/client.pem +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDfDCCAmSgAwIBAgIUaupI14PPUSshx7FmD7lsVPFahwAwDQYJKoZIhvcNAQEL -BQAwVzELMAkGA1UEBhMCQ04xEDAOBgNVBAgTB0JlaWppbmcxEDAOBgNVBAcTB0Jl -aWppbmcxEDAOBgNVBAoTB1BpbmdDQVAxEjAQBgNVBAMTCU15IG93biBDQTAgFw0y -MDAyMTgwNzQ4MDBaGA8yMTIwMDEyNTA3NDgwMFowETEPMA0GA1UEAxMGY2xpZW50 -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA06qb7HABWHrU4CvBUO/2 -hXGgobi/UlTqTrYGZoJqSrvhKCP6HOivZjyWaSMDIfrguN+0C+bd80/5XGMmwjDt -8PuZ+Ef3jcJLuB1e+Kms0s5tiTng6Z028PkSpGvKXjPebWu7zoxVpDcTGM6MmlZQ -qpOuIgwi+7WX/bIgIu9wooCvJEGqhScG2wpvK3txnpsVA4eXXdFoH0R5mtqbxVFf -hwKMDqga4pRwJStLTDiMrtUz+OKcrMIrkH4ndhvm2UYTVvhHlkZ3ooeDYsu40Nnv -edwaE+Ii7EnmcSDF9PaCVrXSK9F/KNRXX/x67PMWCVqcNyGtRsCuDe7FnDfGpudV -XwIDAQABo4GDMIGAMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcD -AjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBRqlq/slflqw/cdlE+xNcnmmxZwlTAf -BgNVHSMEGDAWgBSdAhKsS8BKSOidoGCUYNeaFma4/zALBgNVHREEBDACggAwDQYJ -KoZIhvcNAQELBQADggEBAMGC48O77wZHZRRxXIpTQDMUSpGTKks76l+s1N7sMLrG -DCQi/XFVfV8e/Z1qs224IyU1IGXXcdwK0Zfa9owUmVmiHE8lznv7m9m7j4BGOshc -pvnJaeuUluKR/QHzwpMsUKudoEyRjn09e0Jl0/IcsKh13rzgd458XR0ShCjxybo4 -nQ1aZb1wOPLG6tpUYsV+x2Coc6TgnJWJYlDbRfpIuj6y16T1kKuWzpm6VU3kbiJ9 -/nzDgauuJHIlXEWL9dBZcpzUibFswIQyGsK7c4AJrtY1OGx0/2nZIIjtGY3gtWyX -XGV9c4kM695gl5rJndB4IPl5GQeJBCNyIaVybh7Va70= ------END CERTIFICATE----- diff --git a/tests/br_tls/certificates/server-key.pem b/tests/br_tls/certificates/server-key.pem deleted file mode 100644 index 2779d6ec6..000000000 --- a/tests/br_tls/certificates/server-key.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEogIBAAKCAQEAq9mcQG/nSLREM2r7s2tCKCE/1KJQvV0xmkIglFD2VDDfYW+C -mBME5LNWbYR6L0yCVHU8B7aVnw1FsbiF4TpUY3w/r4mOGl7QbGivMYvRe6Nh2xUO -TvctwFyv2FvrtBX1rZ5/8QLbz1IFHOtTV7QUzLzWq3fSAiF1vhVsS3BUmh6QvWU8 -q9dylpmUQ22otSRXmirwEzFt9K+w3VK9Z6aac7e2XRurVPxbqgQUq2bblUhii8Fc -dCUA8NenlWp+H64mN2TzVaGb5Csr7SNS7AWDEPKfoo7W3H7bzKlmRVcPeRdftwti -SI1jfboxprya/nbTyBPE/yfLU/SYn/b89epziwIDAQABAoIBACPlI08OULgN90Tq -LsLuP3ZUY5nNgaHcKnU3JMj2FE3Hm5ElkpijOF1w3Dep+T+R8pMjnbNavuvnAMy7 -ZzOBVIknNcI7sDPv5AcQ4q8trkbt/I2fW0rBNIw+j/hYUuZdw+BNABpeZ31pe2nr -+Y+TLNkLBKfyMiqBxK88mE81mmZKblyvXCawW0A/iDDJ7fPNqoGF+y9ylTYaNRPk -aJGnaEZobJ4Lm5tSqW4gRX2ft6Hm67RkvVaopPFnlkvfusXUTFUqEVQCURRUqXbf -1ah2chUHxj22UdY9540H5yVNgEP3oR+uS/hbZqxKcJUTznUW5th3CyQPIKMlGlcB -p+zWlTECgYEAxlY4zGJw4QQwGYMKLyWCSHUgKYrKu2Ub2JKJFMTdsoj9H7DI+WHf -lQaO9NCOo2lt0ofYM1MzEpI5Cl/aMrPw+mwquBbxWdMHXK2eSsUQOVo9HtUjgK2t -J2AYFCfsYndo+hCj3ApMHgiY3sghTCXeycvT52bm11VeNVcs3pKxIYMCgYEA3dAJ -PwIfAB8t+6JCP2yYH4ExNjoMNYMdXqhz4vt3UGwgskRqTW6qdd9JvrRQ/JPvGpDy -T375h/+lLw0E4ljsnOPGSzbXNf4bYRHTwPOL+LqVM4Bg90hjclqphElHChxep1di -WcdArB0oae/l4M96z3GjfnXIUVOp8K6BUQCab1kCgYAFFAQUR5j4SfEpVg+WsXEq -hcUzCxixv5785pOX8opynctNWmtq5zSgTjCu2AAu8u4a69t/ROwT16aaO2YM0kqj -Ps3BNOUtFZgkqVVaOL13mnXiKjbkfo3majFzoqoMw13uuSpY4fKc+j9fxOQFXRrd -M9jTHfFfJhJpbzf44uyiHQKBgFIPwzvyVvG+l05/Ky83x9fv/frn4thxV45LmAQj -sHKqbjZFpWZcSOgu4aOSJlwrhsw3T84lVcAAzmXn1STAbVll01jEQz6QciSpacP6 -1pAAx240UqtptpD6BbkROxz8ffA/Hf3E/6Itb2QyAsP3PqI8kpYYkTG1WCvZA7Kq -HHiRAoGAXbUZ25LcrmyuxKWpbty8fck1tjKPvclQB35rOx6vgnfW6pcKMeebYvgq -nJka/QunEReOH/kGxAd/+ymvUBuFQCfFg3Aus+DtAuh9AkBr+cIyPjJqynnIT87J -MbkOw4uEhDJAtGUR9o1j83N1f05bnEwssXiXR0LZPylb9Qzc4tg= ------END RSA PRIVATE KEY----- diff --git a/tests/br_tls/certificates/server.pem b/tests/br_tls/certificates/server.pem deleted file mode 100644 index ea5ef2d5f..000000000 --- a/tests/br_tls/certificates/server.pem +++ /dev/null @@ -1,22 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDjzCCAnegAwIBAgIUWBTDQm4xOYDxZBTkpCQouREtT8QwDQYJKoZIhvcNAQEL -BQAwVzELMAkGA1UEBhMCQ04xEDAOBgNVBAgTB0JlaWppbmcxEDAOBgNVBAcTB0Jl -aWppbmcxEDAOBgNVBAoTB1BpbmdDQVAxEjAQBgNVBAMTCU15IG93biBDQTAgFw0y -MDAyMTgwOTExMDBaGA8yMTIwMDEyNTA5MTEwMFowFjEUMBIGA1UEAxMLdGlkYi1z -ZXJ2ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCr2ZxAb+dItEQz -avuza0IoIT/UolC9XTGaQiCUUPZUMN9hb4KYEwTks1ZthHovTIJUdTwHtpWfDUWx -uIXhOlRjfD+viY4aXtBsaK8xi9F7o2HbFQ5O9y3AXK/YW+u0FfWtnn/xAtvPUgUc -61NXtBTMvNard9ICIXW+FWxLcFSaHpC9ZTyr13KWmZRDbai1JFeaKvATMW30r7Dd -Ur1npppzt7ZdG6tU/FuqBBSrZtuVSGKLwVx0JQDw16eVan4friY3ZPNVoZvkKyvt -I1LsBYMQ8p+ijtbcftvMqWZFVw95F1+3C2JIjWN9ujGmvJr+dtPIE8T/J8tT9Jif -9vz16nOLAgMBAAGjgZEwgY4wDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsG -AQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBRVB/Bvdzvh -6WQRWpc9SzcbXLz77zAfBgNVHSMEGDAWgBSdAhKsS8BKSOidoGCUYNeaFma4/zAP -BgNVHREECDAGhwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQAAqg5pgGQqORKRSdlY -wzVvzKaulpvjZfVMM6YiOUtmlU0CGWq7E3gLFzkvebpU0KsFlbyZ92h/2Fw5Ay2b -kxkCy18mJ4lGkvF0cU4UD3XheFMvD2QWWRX4WPpAhStofrWOXeyq3Div2+fQjMJd -kyeWUzPU7T467IWUHOWNsFAjfVHNsmG45qLGt+XQckHTvASX5IvN+5tkRUCW30vO -b3BdDQUFglGTUFU2epaZGTti0SYiRiY+9R3zFWX4uBcEBYhk9e/0BU8FqdWW5GjI -pFpH9t64CjKIdRQXpIn4cogK/GwyuRuDPV/RkMjrIqOi7pGejXwyDe9avHFVR6re -oowA ------END CERTIFICATE----- diff --git a/tests/br_tls/config/pd.toml b/tests/br_tls/config/pd.toml deleted file mode 100644 index 69cb94b6f..000000000 --- a/tests/br_tls/config/pd.toml +++ /dev/null @@ -1,9 +0,0 @@ -# config of pd - -[security] -# Path of file that contains list of trusted SSL CAs. if set, following four settings shouldn't be empty -cacert-path = "tests/br_tls/certificates/ca.pem" -# Path of file that contains X509 certificate in PEM format. -cert-path = "tests/br_tls/certificates/server.pem" -# Path of file that contains X509 key in PEM format. -key-path = "tests/br_tls/certificates/server-key.pem" diff --git a/tests/br_tls/config/tidb.toml b/tests/br_tls/config/tidb.toml deleted file mode 100644 index 48a783332..000000000 --- a/tests/br_tls/config/tidb.toml +++ /dev/null @@ -1,14 +0,0 @@ -# config of tidb - -# Schema lease duration -# There are lot of ddl in the tests, setting this -# to 360s to test whether BR is gracefully shutdown. -lease = "360s" - -[security] -# Path of file that contains list of trusted SSL CAs for connection with cluster components. -cluster-ssl-ca = "tests/br_tls/certificates/ca.pem" -# Path of file that contains X509 certificate in PEM format for connection with cluster components. -cluster-ssl-cert = "tests/br_tls/certificates/server.pem" -# Path of file that contains X509 key in PEM format for connection with cluster components. -cluster-ssl-key = "tests/br_tls/certificates/server-key.pem" \ No newline at end of file diff --git a/tests/br_tls/config/tikv.toml b/tests/br_tls/config/tikv.toml deleted file mode 100644 index b4859a731..000000000 --- a/tests/br_tls/config/tikv.toml +++ /dev/null @@ -1,19 +0,0 @@ -# config of tikv - -[coprocessor] -region-max-keys = 20 -region-split-keys = 12 - -[rocksdb] -max-open-files = 4096 -[raftdb] -max-open-files = 4096 -[raftstore] -# true (default value) for high reliability, this can prevent data loss when power failure. -sync-log = false - -[security] -# set the path for certificates. Empty string means disabling secure connectoins. -ca-path = "tests/br_tls/certificates/ca.pem" -cert-path = "tests/br_tls/certificates/server.pem" -key-path = "tests/br_tls/certificates/server-key.pem" diff --git a/tests/br_tls/run.sh b/tests/br_tls/run.sh deleted file mode 100755 index fc6aa62a6..000000000 --- a/tests/br_tls/run.sh +++ /dev/null @@ -1,71 +0,0 @@ -#!/bin/sh -# -# Copyright 2019 PingCAP, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# See the License for the specific language governing permissions and -# limitations under the License. - -set -eu - -cur=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -source $cur/../_utils/run_services - -DB="$TEST_NAME" -TABLE="usertable1" -TABLE2="usertable2" - -echo "Restart cluster with tls" -start_services_with_tls "$cur" - -run_sql "DROP DATABASE IF EXISTS $DB;" -run_sql "CREATE DATABASE $DB;" - -run_sql "CREATE TABLE $DB.$TABLE( \ - YCSB_KEY varchar(64) NOT NULL, \ - FIELD0 varchar(1) DEFAULT NULL, \ - PRIMARY KEY (YCSB_KEY) \ -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;" - -run_sql "INSERT INTO $DB.$TABLE VALUES (\"a\", \"b\");" -run_sql "INSERT INTO $DB.$TABLE VALUES (\"aa\", \"b\");" - -run_sql "CREATE TABLE $DB.$TABLE2( \ - YCSB_KEY varchar(64) NOT NULL, \ - FIELD0 varchar(1) DEFAULT NULL, \ - PRIMARY KEY (YCSB_KEY) \ -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;" - -run_sql "INSERT INTO $DB.$TABLE2 VALUES (\"c\", \"d\");" - -# backup db -echo "backup start..." -# should fail when use http with TLS -if run_br --pd "http://$PD_ADDR" backup db --db "$DB" -s "local://$TEST_DIR/$DB" --ratelimit 5 --concurrency 4 --ca $cur/certificates/ca.pem --cert $cur/certificates/client.pem --key $cur/certificates/client-key.pem; then - echo "failure: success started with http prefix even tls enabled." -fi -run_br --pd "https://$PD_ADDR" backup db --db "$DB" -s "local://$TEST_DIR/$DB" --ratelimit 5 --concurrency 4 --ca $cur/certificates/ca.pem --cert $cur/certificates/client.pem --key $cur/certificates/client-key.pem - -run_sql "DROP DATABASE $DB;" - -# restore db -echo "restore start..." -run_br restore db --db $DB -s "local://$TEST_DIR/$DB" --pd $PD_ADDR --ca $cur/certificates/ca.pem --cert $cur/certificates/client.pem --key $cur/certificates/client-key.pem - -table_count=$(run_sql "use $DB; show tables;" | grep "Tables_in" | wc -l) -if [ "$table_count" -ne "2" ];then - echo "TEST: [$TEST_NAME] failed!" - exit 1 -fi - -run_sql "DROP DATABASE $DB;" - -echo "Restart service without tls" -start_services diff --git a/tests/br_z_gc_safepoint/gc.go b/tests/br_z_gc_safepoint/gc.go index d35ac4bc9..f6c6763bd 100644 --- a/tests/br_z_gc_safepoint/gc.go +++ b/tests/br_z_gc_safepoint/gc.go @@ -27,6 +27,9 @@ import ( ) var ( + ca = flag.String("ca", "", "CA certificate path for TLS connection") + cert = flag.String("cert", "", "certificate path for TLS connection") + key = flag.String("key", "", "private key path for TLS connection") pdAddr = flag.String("pd", "", "PD address") gcOffset = flag.Duration("gc-offset", time.Second*10, "Set GC safe point to current time - gc-offset, default: 10s") @@ -45,7 +48,11 @@ func main() { timeout := time.Second * 10 ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() - pdclient, err := pd.NewClientWithContext(ctx, []string{*pdAddr}, pd.SecurityOption{}) + pdclient, err := pd.NewClientWithContext(ctx, []string{*pdAddr}, pd.SecurityOption{ + CAPath: *ca, + CertPath: *cert, + KeyPath: *key, + }) if err != nil { log.Panic("create pd client failed", zap.Error(err)) } diff --git a/tests/br_z_gc_safepoint/run.sh b/tests/br_z_gc_safepoint/run.sh index f65b425a8..ef7ecc66c 100755 --- a/tests/br_z_gc_safepoint/run.sh +++ b/tests/br_z_gc_safepoint/run.sh @@ -18,7 +18,7 @@ # slows down other tests to changing GC safe point. Adding a z prefix to run # the test last. -set -eu +set -eux DB="$TEST_NAME" TABLE="usertable" @@ -30,7 +30,11 @@ run_sql "CREATE DATABASE $DB;" go-ycsb load mysql -P tests/$TEST_NAME/workload -p mysql.host=$TIDB_IP -p mysql.port=$TIDB_PORT -p mysql.user=root -p mysql.db=$DB # Update GC safepoint to now + 5s after 10s seconds. -sleep 10 && bin/gc -pd $PD_ADDR -gc-offset "5s" -update-service true & +sleep 10 && bin/gc -pd $PD_ADDR \ + --ca "$TEST_DIR/certs/ca.pem" \ + --cert "$TEST_DIR/certs/br.pem" \ + --key "$TEST_DIR/certs/br.key" \ + -gc-offset "5s" -update-service true & # total bytes is 1136000 # Set ratelimit to 40960 bytes/second, it will finish within 25s, @@ -45,7 +49,11 @@ if [ "$backup_gc_fail" -ne "0" ];then fi # set safePoint otherwise the default safePoint is zero -bin/gc -pd $PD_ADDR -gc-offset "1s" +bin/gc -pd $PD_ADDR \ + --ca "$TEST_DIR/certs/ca.pem" \ + --cert "$TEST_DIR/certs/br.pem" \ + --key "$TEST_DIR/certs/br.key" \ + -gc-offset "1s" backup_gc_fail=0 echo "incremental backup start (expect fail)..." diff --git a/tests/config/.gitignore b/tests/config/.gitignore deleted file mode 100644 index 2b410a387..000000000 --- a/tests/config/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Ignore TiFlash configuration -tiflash* diff --git a/tests/config/importer.toml b/tests/config/importer.toml new file mode 100644 index 000000000..e8b229479 --- /dev/null +++ b/tests/config/importer.toml @@ -0,0 +1,4 @@ +[security] +ca-path = "/tmp/backup_restore_test/certs/ca.pem" +cert-path = "/tmp/backup_restore_test/certs/importer.pem" +key-path = "/tmp/backup_restore_test/certs/importer.key" diff --git a/tests/config/ipsan.cnf b/tests/config/ipsan.cnf new file mode 100644 index 000000000..0bf8ef009 --- /dev/null +++ b/tests/config/ipsan.cnf @@ -0,0 +1,11 @@ +[dn] +CN = localhost +[req] +distinguished_name = dn +[EXT] +subjectAltName = @alt_names +keyUsage = digitalSignature,keyEncipherment +extendedKeyUsage = clientAuth,serverAuth +[alt_names] +DNS.1 = localhost +IP.1 = 127.0.0.1 diff --git a/tests/config/pd.toml b/tests/config/pd.toml index 7399ac9e9..a0ff1422f 100644 --- a/tests/config/pd.toml +++ b/tests/config/pd.toml @@ -3,3 +3,8 @@ tso-save-interval = "360s" [replication] enable-placement-rules = true + +[security] +cacert-path = "/tmp/backup_restore_test/certs/ca.pem" +cert-path = "/tmp/backup_restore_test/certs/pd.pem" +key-path = "/tmp/backup_restore_test/certs/pd.key" diff --git a/tests/config/restore-tikv.toml b/tests/config/restore-tikv.toml index 010711cd4..bd54c0503 100644 --- a/tests/config/restore-tikv.toml +++ b/tests/config/restore-tikv.toml @@ -3,6 +3,9 @@ [server] labels = { exclusive = "restore" } +[storage] +reserve-space = "1KB" + [coprocessor] region-max-keys = 20 region-split-keys = 12 @@ -14,4 +17,9 @@ max-open-files = 4096 [raftstore] # true (default value) for high reliability, this can prevent data loss when power failure. sync-log = false -capacity = "10GB" \ No newline at end of file +capacity = "10GB" + +[security] +ca-path = "/tmp/backup_restore_test/certs/ca.pem" +cert-path = "/tmp/backup_restore_test/certs/tikv.pem" +key-path = "/tmp/backup_restore_test/certs/tikv.key" diff --git a/tests/config/tidb.toml b/tests/config/tidb.toml index 301aabd94..dafdb8d71 100644 --- a/tests/config/tidb.toml +++ b/tests/config/tidb.toml @@ -4,3 +4,10 @@ # There are lot of ddl in the tests, setting this # to 360s to test whther BR is gracefully shutdown. lease = "360s" +[security] +ssl-ca = "/tmp/backup_restore_test/certs/ca.pem" +ssl-cert = "/tmp/backup_restore_test/certs/tidb.pem" +ssl-key = "/tmp/backup_restore_test/certs/tidb.key" +cluster-ssl-ca = "/tmp/backup_restore_test/certs/ca.pem" +cluster-ssl-cert = "/tmp/backup_restore_test/certs/tidb.pem" +cluster-ssl-key = "/tmp/backup_restore_test/certs/tidb.key" diff --git a/tests/config/tikv.toml b/tests/config/tikv.toml index a8dc5eae3..beddda23c 100644 --- a/tests/config/tikv.toml +++ b/tests/config/tikv.toml @@ -1,4 +1,6 @@ # config of tikv +[storage] +reserve-space = "1KB" [coprocessor] region-max-keys = 100 @@ -13,3 +15,16 @@ max-open-files = 4096 # true (default value) for high reliability, this can prevent data loss when power failure. sync-log = false capacity = "10GB" +<<<<<<< HEAD +======= +# Speed up TiKV region heartbeat +pd-heartbeat-tick-interval = "1s" + +[cdc] +hibernate-regions-compatible=false + +[security] +ca-path = "/tmp/backup_restore_test/certs/ca.pem" +cert-path = "/tmp/backup_restore_test/certs/tikv.pem" +key-path = "/tmp/backup_restore_test/certs/tikv.key" +>>>>>>> 8b9b626... Merge branch 'merging' diff --git a/tests/lightning_alter_random/config.toml b/tests/lightning_alter_random/config.toml new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_alter_random/data/alter_random-schema-create.sql b/tests/lightning_alter_random/data/alter_random-schema-create.sql new file mode 100644 index 000000000..bc66fc8c2 --- /dev/null +++ b/tests/lightning_alter_random/data/alter_random-schema-create.sql @@ -0,0 +1 @@ +CREATE DATABASE `alter_random` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; diff --git a/tests/lightning_alter_random/data/alter_random.t-schema.sql b/tests/lightning_alter_random/data/alter_random.t-schema.sql new file mode 100644 index 000000000..2f3c030e7 --- /dev/null +++ b/tests/lightning_alter_random/data/alter_random.t-schema.sql @@ -0,0 +1,4 @@ +/*!40103 SET TIME_ZONE='+00:00' */; +CREATE TABLE `t` ( + `id` bigint primary key auto_random +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; diff --git a/tests/lightning_alter_random/data/alter_random.t.sql b/tests/lightning_alter_random/data/alter_random.t.sql new file mode 100644 index 000000000..e69296d97 --- /dev/null +++ b/tests/lightning_alter_random/data/alter_random.t.sql @@ -0,0 +1,5 @@ +/*!40103 SET TIME_ZONE='+00:00' */; +INSERT INTO `t` VALUES +(5764607523034234881), +(7493989779944505347), +(8646911284551352322); diff --git a/tests/lightning_alter_random/run.sh b/tests/lightning_alter_random/run.sh new file mode 100644 index 000000000..79fd2735c --- /dev/null +++ b/tests/lightning_alter_random/run.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# +# Copyright 2020 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eu + +# FIXME: auto-random is only stable on master currently. +check_cluster_version 4 0 0 AUTO_RANDOM || exit 0 + +for backend in tidb importer local; do + if [ "$backend" = 'local' ]; then + check_cluster_version 4 0 0 'local backend' || continue + fi + + run_sql 'DROP DATABASE IF EXISTS alter_random;' + run_lightning --backend $backend + + run_sql "SELECT count(*) from alter_random.t" + check_contains "count(*): 3" + + run_sql "SELECT id & b'000001111111111111111111111111111111111111111111111111111111111' as inc FROM alter_random.t" + check_contains 'inc: 1' + check_contains 'inc: 2' + check_contains 'inc: 3' + + # auto random base is 4 + run_sql "INSERT INTO alter_random.t VALUES ();" + run_sql "SELECT id & b'000001111111111111111111111111111111111111111111111111111111111' as inc FROM alter_random.t" + if [ "$backend" = 'tidb' ]; then + check_contains 'inc: 30002' + else + check_contains 'inc: 4' + fi +done diff --git a/tests/lightning_auto_random_default/config.toml b/tests/lightning_auto_random_default/config.toml new file mode 100644 index 000000000..56616c750 --- /dev/null +++ b/tests/lightning_auto_random_default/config.toml @@ -0,0 +1,2 @@ +[mydumper] +max-region-size = 200 \ No newline at end of file diff --git a/tests/lightning_auto_random_default/data/auto_random-schema-create.sql b/tests/lightning_auto_random_default/data/auto_random-schema-create.sql new file mode 100644 index 000000000..122f86301 --- /dev/null +++ b/tests/lightning_auto_random_default/data/auto_random-schema-create.sql @@ -0,0 +1 @@ +CREATE DATABASE `auto_random` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; diff --git a/tests/lightning_auto_random_default/data/auto_random.t-schema.sql b/tests/lightning_auto_random_default/data/auto_random.t-schema.sql new file mode 100644 index 000000000..95054b9fa --- /dev/null +++ b/tests/lightning_auto_random_default/data/auto_random.t-schema.sql @@ -0,0 +1,5 @@ +/*!40103 SET TIME_ZONE='+00:00' */; +CREATE TABLE `t` ( + `id` bigint unsigned primary key auto_random, + `s` varchar(32) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; diff --git a/tests/lightning_auto_random_default/data/auto_random.t.0.sql b/tests/lightning_auto_random_default/data/auto_random.t.0.sql new file mode 100644 index 000000000..58a3bf22b --- /dev/null +++ b/tests/lightning_auto_random_default/data/auto_random.t.0.sql @@ -0,0 +1,5 @@ +/*!40103 SET TIME_ZONE='+00:00' */; +INSERT INTO `t` (`s`) VALUES +("test1"), +("test2"), +("test3"); diff --git a/tests/lightning_auto_random_default/data/auto_random.t.1.sql b/tests/lightning_auto_random_default/data/auto_random.t.1.sql new file mode 100644 index 000000000..6b9409b1b --- /dev/null +++ b/tests/lightning_auto_random_default/data/auto_random.t.1.sql @@ -0,0 +1,5 @@ +/*!40103 SET TIME_ZONE='+00:00' */; +INSERT INTO `t` (`s`) VALUES +(""), +(""), +(""); diff --git a/tests/lightning_auto_random_default/run.sh b/tests/lightning_auto_random_default/run.sh new file mode 100644 index 000000000..499cb27d5 --- /dev/null +++ b/tests/lightning_auto_random_default/run.sh @@ -0,0 +1,61 @@ +#!/bin/sh +# +# Copyright 2020 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eu + +# FIXME: auto-random is only stable on master currently. +check_cluster_version 4 0 0 AUTO_RANDOM || exit 0 + +for backend in tidb importer local; do + if [ "$backend" = 'local' ]; then + check_cluster_version 4 0 0 'local backend' || continue + fi + + run_sql 'DROP DATABASE IF EXISTS auto_random;' + run_lightning --backend $backend + + run_sql "SELECT count(*) from auto_random.t" + check_contains "count(*): 6" + + run_sql "SELECT id & b'000001111111111111111111111111111111111111111111111111111111111' as inc FROM auto_random.t" + check_contains 'inc: 1' + check_contains 'inc: 2' + check_contains 'inc: 3' + if [ "$backend" = 'tidb' ]; then + check_contains 'inc: 4' + check_contains 'inc: 5' + check_contains 'inc: 6' + NEXT_AUTO_RAND_VAL=7 + else + check_contains 'inc: 25' + check_contains 'inc: 26' + check_contains 'inc: 27' + NEXT_AUTO_RAND_VAL=28 + fi + + # tidb backend randomly generate the auto-random bit for each statement, so with 2 statements, + # the distinct auto_random prefix values can be 1 or 2, so we skip this check with tidb backend + if [ "$backend" != 'tidb' ]; then + run_sql "select count(distinct id >> 58) as count from auto_random.t" + check_contains "count: 2" + fi + + # auto random base is 4 + run_sql "SELECT max(id & b'000001111111111111111111111111111111111111111111111111111111111') >= $NEXT_AUTO_RAND_VAL as ge FROM auto_random.t" + check_contains 'ge: 0' + run_sql "INSERT INTO auto_random.t VALUES ();" + run_sql "SELECT max(id & b'000001111111111111111111111111111111111111111111111111111111111') >= $NEXT_AUTO_RAND_VAL as ge FROM auto_random.t" + check_contains 'ge: 1' +done diff --git a/tests/lightning_black-white-list/config.toml b/tests/lightning_black-white-list/config.toml new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_black-white-list/data/firstdb-schema-create.sql b/tests/lightning_black-white-list/data/firstdb-schema-create.sql new file mode 100644 index 000000000..b7e97d075 --- /dev/null +++ b/tests/lightning_black-white-list/data/firstdb-schema-create.sql @@ -0,0 +1 @@ +create database firstdb; diff --git a/tests/lightning_black-white-list/data/firstdb.first-schema.sql b/tests/lightning_black-white-list/data/firstdb.first-schema.sql new file mode 100644 index 000000000..6e78e8152 --- /dev/null +++ b/tests/lightning_black-white-list/data/firstdb.first-schema.sql @@ -0,0 +1 @@ +create table `first` (id int primary key); diff --git a/tests/lightning_black-white-list/data/firstdb.first.1.sql b/tests/lightning_black-white-list/data/firstdb.first.1.sql new file mode 100644 index 000000000..3c4151f4c --- /dev/null +++ b/tests/lightning_black-white-list/data/firstdb.first.1.sql @@ -0,0 +1 @@ +insert into `first` values (8273); diff --git a/tests/lightning_black-white-list/data/firstdb.first.2.sql b/tests/lightning_black-white-list/data/firstdb.first.2.sql new file mode 100644 index 000000000..5b35412f4 --- /dev/null +++ b/tests/lightning_black-white-list/data/firstdb.first.2.sql @@ -0,0 +1 @@ +insert into `first` values (6855); diff --git a/tests/lightning_black-white-list/data/firstdb.second-schema.sql b/tests/lightning_black-white-list/data/firstdb.second-schema.sql new file mode 100644 index 000000000..35d158be6 --- /dev/null +++ b/tests/lightning_black-white-list/data/firstdb.second-schema.sql @@ -0,0 +1 @@ +create table `second` (id int primary key); diff --git a/tests/lightning_black-white-list/data/firstdb.second.1.sql b/tests/lightning_black-white-list/data/firstdb.second.1.sql new file mode 100644 index 000000000..9966968f9 --- /dev/null +++ b/tests/lightning_black-white-list/data/firstdb.second.1.sql @@ -0,0 +1 @@ +create table `second` values (909); \ No newline at end of file diff --git a/tests/lightning_black-white-list/data/mysql-schema-create.sql b/tests/lightning_black-white-list/data/mysql-schema-create.sql new file mode 100644 index 000000000..a2745c8dd --- /dev/null +++ b/tests/lightning_black-white-list/data/mysql-schema-create.sql @@ -0,0 +1 @@ +create database mysql; \ No newline at end of file diff --git a/tests/lightning_black-white-list/data/mysql.testtable-schema.sql b/tests/lightning_black-white-list/data/mysql.testtable-schema.sql new file mode 100644 index 000000000..15b4bf276 --- /dev/null +++ b/tests/lightning_black-white-list/data/mysql.testtable-schema.sql @@ -0,0 +1 @@ +create table `testtable` (a int primary key); diff --git a/tests/lightning_black-white-list/data/seconddb-schema-create.sql b/tests/lightning_black-white-list/data/seconddb-schema-create.sql new file mode 100644 index 000000000..1c04a1ac0 --- /dev/null +++ b/tests/lightning_black-white-list/data/seconddb-schema-create.sql @@ -0,0 +1 @@ +create database seconddb; diff --git a/tests/lightning_black-white-list/data/seconddb.fourth-schema.sql b/tests/lightning_black-white-list/data/seconddb.fourth-schema.sql new file mode 100644 index 000000000..cabff486d --- /dev/null +++ b/tests/lightning_black-white-list/data/seconddb.fourth-schema.sql @@ -0,0 +1 @@ +create table `fourth` (id int primary key); diff --git a/tests/lightning_black-white-list/data/seconddb.fourth.1.sql b/tests/lightning_black-white-list/data/seconddb.fourth.1.sql new file mode 100644 index 000000000..b011cd8f1 --- /dev/null +++ b/tests/lightning_black-white-list/data/seconddb.fourth.1.sql @@ -0,0 +1 @@ +insert into `fourth` values (3256), (5457); diff --git a/tests/lightning_black-white-list/data/seconddb.third-schema.sql b/tests/lightning_black-white-list/data/seconddb.third-schema.sql new file mode 100644 index 000000000..82e9e1d31 --- /dev/null +++ b/tests/lightning_black-white-list/data/seconddb.third-schema.sql @@ -0,0 +1 @@ +create table `third` (id int primary key); diff --git a/tests/lightning_black-white-list/data/seconddb.third.1.sql b/tests/lightning_black-white-list/data/seconddb.third.1.sql new file mode 100644 index 000000000..349dd9f47 --- /dev/null +++ b/tests/lightning_black-white-list/data/seconddb.third.1.sql @@ -0,0 +1 @@ +insert into `third` values (6339); \ No newline at end of file diff --git a/tests/lightning_black-white-list/even-table-only.toml b/tests/lightning_black-white-list/even-table-only.toml new file mode 100644 index 000000000..86c15fbaa --- /dev/null +++ b/tests/lightning_black-white-list/even-table-only.toml @@ -0,0 +1,11 @@ +[[black-white-list.ignore-tables]] +db-name = "firstdb" +tbl-name = "~." + +[[black-white-list.do-tables]] +db-name = "~." +tbl-name = "second" + +[[black-white-list.do-tables]] +db-name = "seconddb" +tbl-name = "fourth" diff --git a/tests/lightning_black-white-list/firstdb-only.toml b/tests/lightning_black-white-list/firstdb-only.toml new file mode 100644 index 000000000..7dc796a3e --- /dev/null +++ b/tests/lightning_black-white-list/firstdb-only.toml @@ -0,0 +1,2 @@ +[black-white-list] +do-dbs = ["~^f"] diff --git a/tests/lightning_black-white-list/run.sh b/tests/lightning_black-white-list/run.sh new file mode 100755 index 000000000..823b615b4 --- /dev/null +++ b/tests/lightning_black-white-list/run.sh @@ -0,0 +1,66 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eux + +drop_dbs() { + run_sql 'DROP DATABASE IF EXISTS firstdb;' + run_sql 'DROP DATABASE IF EXISTS seconddb;' +} + +check_firstdb_only() { + run_sql 'SHOW DATABASES;' + check_contains 'Database: firstdb' + check_not_contains 'Database: seconddb' + run_sql 'SHOW TABLES IN firstdb;' + check_contains 'Tables_in_firstdb: first' + check_contains 'Tables_in_firstdb: second' + run_sql 'SHOW TABLES IN mysql;' + check_not_contains 'Tables_in_mysql: testtable' +} + +check_even_table_only() { + run_sql 'SHOW DATABASES;' + check_contains 'Database: firstdb' + check_contains 'Database: seconddb' + run_sql 'SHOW TABLES IN firstdb;' + check_not_contains 'Tables_in_firstdb: first' + check_contains 'Tables_in_firstdb: second' + run_sql 'SHOW TABLES IN seconddb;' + check_not_contains 'Tables_in_seconddb: third' + check_contains 'Tables_in_seconddb: fourth' + run_sql 'SHOW TABLES IN mysql;' + check_not_contains 'Tables_in_mysql: testtable' +} + +# Check if black-white-list works. + +drop_dbs +run_lightning --config "tests/$TEST_NAME/firstdb-only.toml" +check_firstdb_only + +drop_dbs +run_lightning --config "tests/$TEST_NAME/even-table-only.toml" +check_even_table_only + +# Check the same for table-filter + +drop_dbs +run_lightning -f 'f*.*' +check_firstdb_only + +drop_dbs +run_lightning -f '!firstdb.*' -f '*.second' -f 'seconddb.fourth' +check_even_table_only diff --git a/tests/lightning_character_sets/auto.toml b/tests/lightning_character_sets/auto.toml new file mode 100644 index 000000000..4498544d1 --- /dev/null +++ b/tests/lightning_character_sets/auto.toml @@ -0,0 +1,5 @@ +[lightning] +table-concurrency = 1 + +[mydumper] +character-set = "auto" diff --git a/tests/lightning_character_sets/binary.toml b/tests/lightning_character_sets/binary.toml new file mode 100644 index 000000000..b26ede355 --- /dev/null +++ b/tests/lightning_character_sets/binary.toml @@ -0,0 +1,5 @@ +[lightning] +table-concurrency = 1 + +[mydumper] +character-set = "binary" diff --git a/tests/lightning_character_sets/gb18030.toml b/tests/lightning_character_sets/gb18030.toml new file mode 100644 index 000000000..87628eb45 --- /dev/null +++ b/tests/lightning_character_sets/gb18030.toml @@ -0,0 +1,5 @@ +[lightning] +table-concurrency = 1 + +[mydumper] +character-set = "gb18030" diff --git a/tests/lightning_character_sets/gb18030/charsets-schema-create.sql b/tests/lightning_character_sets/gb18030/charsets-schema-create.sql new file mode 100644 index 000000000..c4e838786 --- /dev/null +++ b/tests/lightning_character_sets/gb18030/charsets-schema-create.sql @@ -0,0 +1 @@ +create database charsets; \ No newline at end of file diff --git a/tests/lightning_character_sets/gb18030/charsets.gb18030-schema.sql b/tests/lightning_character_sets/gb18030/charsets.gb18030-schema.sql new file mode 100644 index 000000000..8721d85bc --- /dev/null +++ b/tests/lightning_character_sets/gb18030/charsets.gb18030-schema.sql @@ -0,0 +1 @@ +create table gb18030 (`Ö÷¼ü` int primary key comment '×¢ÊÍ'); \ No newline at end of file diff --git a/tests/lightning_character_sets/gb18030/charsets.gb18030.sql b/tests/lightning_character_sets/gb18030/charsets.gb18030.sql new file mode 100644 index 000000000..9beffabe9 --- /dev/null +++ b/tests/lightning_character_sets/gb18030/charsets.gb18030.sql @@ -0,0 +1 @@ +insert into gb18030 values (35), (58), (4), (24), (14), (27), (39), (5), (0), (61); diff --git a/tests/lightning_character_sets/mixed/charsets-schema-create.sql b/tests/lightning_character_sets/mixed/charsets-schema-create.sql new file mode 100644 index 000000000..c4e838786 --- /dev/null +++ b/tests/lightning_character_sets/mixed/charsets-schema-create.sql @@ -0,0 +1 @@ +create database charsets; \ No newline at end of file diff --git a/tests/lightning_character_sets/mixed/charsets.mixed-schema.sql b/tests/lightning_character_sets/mixed/charsets.mixed-schema.sql new file mode 100644 index 000000000..57e425276 --- /dev/null +++ b/tests/lightning_character_sets/mixed/charsets.mixed-schema.sql @@ -0,0 +1 @@ +create table `mixed` (`唯一键` int not null unique key comment '×¢ÊÍ'); \ No newline at end of file diff --git a/tests/lightning_character_sets/mixed/charsets.mixed.sql b/tests/lightning_character_sets/mixed/charsets.mixed.sql new file mode 100644 index 000000000..09efb13fb --- /dev/null +++ b/tests/lightning_character_sets/mixed/charsets.mixed.sql @@ -0,0 +1 @@ +insert into `mixed` values (705), (417), (906), (375), (486), (449), (290), (477), (274), (912); diff --git a/tests/lightning_character_sets/run.sh b/tests/lightning_character_sets/run.sh new file mode 100755 index 000000000..36f15a57d --- /dev/null +++ b/tests/lightning_character_sets/run.sh @@ -0,0 +1,76 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eux + +run_lightning_expecting_fail() { + set +e + run_lightning "$@" + ERRCODE=$? + set -e + [ "$ERRCODE" != 0 ] +} + +run_sql 'DROP DATABASE IF EXISTS charsets;' + +# gb18030 + +run_lightning --config "tests/$TEST_NAME/auto.toml" -d "tests/$TEST_NAME/gb18030" +run_sql 'SELECT sum(`主键`) AS s FROM charsets.gb18030' +check_contains 's: 267' +run_sql 'DROP TABLE charsets.gb18030;' + +run_lightning --config "tests/$TEST_NAME/gb18030.toml" -d "tests/$TEST_NAME/gb18030" +run_sql 'SELECT sum(`主键`) AS s FROM charsets.gb18030' +check_contains 's: 267' +run_sql 'DROP TABLE charsets.gb18030;' + +run_lightning_expecting_fail --config "tests/$TEST_NAME/utf8mb4.toml" -d "tests/$TEST_NAME/gb18030" + +run_lightning --config "tests/$TEST_NAME/binary.toml" -d "tests/$TEST_NAME/gb18030" +run_sql 'SELECT sum(`Ö÷¼ü`) AS s FROM charsets.gb18030' +check_contains 's: 267' + +# utf8mb4 + +run_lightning --config "tests/$TEST_NAME/auto.toml" -d "tests/$TEST_NAME/utf8mb4" +run_sql 'SELECT sum(`主键`) AS s FROM charsets.utf8mb4' +check_contains 's: 1119' +run_sql 'DROP TABLE charsets.utf8mb4;' + +run_lightning --config "tests/$TEST_NAME/gb18030.toml" -d "tests/$TEST_NAME/utf8mb4" +run_sql 'SELECT sum(`涓婚敭`) AS s FROM charsets.utf8mb4' +check_contains 's: 1119' +run_sql 'DROP TABLE charsets.utf8mb4;' + +run_lightning --config "tests/$TEST_NAME/utf8mb4.toml" -d "tests/$TEST_NAME/utf8mb4" +run_sql 'SELECT sum(`主键`) AS s FROM charsets.utf8mb4' +check_contains 's: 1119' +run_sql 'DROP TABLE charsets.utf8mb4;' + +run_lightning --config "tests/$TEST_NAME/binary.toml" -d "tests/$TEST_NAME/utf8mb4" +run_sql 'SELECT sum(`主键`) AS s FROM charsets.utf8mb4' +check_contains 's: 1119' + +# mixed + +run_lightning_expecting_fail --config "tests/$TEST_NAME/auto.toml" -d "tests/$TEST_NAME/mixed" +run_lightning_expecting_fail --config "tests/$TEST_NAME/gb18030.toml" -d "tests/$TEST_NAME/mixed" +run_lightning_expecting_fail --config "tests/$TEST_NAME/utf8mb4.toml" -d "tests/$TEST_NAME/mixed" + +run_lightning --config "tests/$TEST_NAME/binary.toml" -d "tests/$TEST_NAME/mixed" +run_sql 'SELECT sum(`唯一键`) AS s FROM charsets.mixed' +check_contains 's: 5291' + diff --git a/tests/lightning_character_sets/utf8mb4.toml b/tests/lightning_character_sets/utf8mb4.toml new file mode 100644 index 000000000..0bf5ce26e --- /dev/null +++ b/tests/lightning_character_sets/utf8mb4.toml @@ -0,0 +1,5 @@ +[lightning] +table-concurrency = 1 + +[mydumper] +character-set = "utf8mb4" diff --git a/tests/lightning_character_sets/utf8mb4/charsets-schema-create.sql b/tests/lightning_character_sets/utf8mb4/charsets-schema-create.sql new file mode 100644 index 000000000..c4e838786 --- /dev/null +++ b/tests/lightning_character_sets/utf8mb4/charsets-schema-create.sql @@ -0,0 +1 @@ +create database charsets; \ No newline at end of file diff --git a/tests/lightning_character_sets/utf8mb4/charsets.utf8mb4-schema.sql b/tests/lightning_character_sets/utf8mb4/charsets.utf8mb4-schema.sql new file mode 100644 index 000000000..f56c63965 --- /dev/null +++ b/tests/lightning_character_sets/utf8mb4/charsets.utf8mb4-schema.sql @@ -0,0 +1 @@ +create table utf8mb4 (`主键` int primary key comment '注释'); \ No newline at end of file diff --git a/tests/lightning_character_sets/utf8mb4/charsets.utf8mb4.sql b/tests/lightning_character_sets/utf8mb4/charsets.utf8mb4.sql new file mode 100644 index 000000000..7021cd81f --- /dev/null +++ b/tests/lightning_character_sets/utf8mb4/charsets.utf8mb4.sql @@ -0,0 +1 @@ +insert into utf8mb4 values (231), (214), (33), (2), (42), (137), (186), (3), (23), (248); diff --git a/tests/lightning_check_requirements/config.toml b/tests/lightning_check_requirements/config.toml new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_check_requirements/data/checkreq-schema-create.sql b/tests/lightning_check_requirements/data/checkreq-schema-create.sql new file mode 100644 index 000000000..721cd28a9 --- /dev/null +++ b/tests/lightning_check_requirements/data/checkreq-schema-create.sql @@ -0,0 +1 @@ +CREATE DATABASE `checkreq`; \ No newline at end of file diff --git a/tests/lightning_check_requirements/run.sh b/tests/lightning_check_requirements/run.sh new file mode 100755 index 000000000..26d614024 --- /dev/null +++ b/tests/lightning_check_requirements/run.sh @@ -0,0 +1,43 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eux + +curl_cluster_version() { + run_curl 'https://127.0.0.1:2379/pd/api/v1/config/cluster-version' "$@" +} + +# should be OK when the version is normal +run_sql 'DROP DATABASE IF EXISTS checkreq' +run_lightning --check-requirements=1 -L warning + +# now try to reduce the version to below 2.1.0 +OLD_VERSION=$(curl_cluster_version) +reset_cluster_version() { + curl_cluster_version '{"cluster-version":'"$OLD_VERSION"'}' +} +trap reset_cluster_version EXIT + +curl_cluster_version '{"cluster-version":"2.0.0-fake.and.error.expected"}' +sleep 1 + +run_sql 'DROP DATABASE IF EXISTS checkreq' +set +e +run_lightning --check-requirements=1 -L warning +ERRORCODE=$? +set -e + +# ensure lightning will reject the cluster version. +[ "$ERRORCODE" -ne 0 ] diff --git a/tests/lightning_checkpoint/config.toml b/tests/lightning_checkpoint/config.toml new file mode 100644 index 000000000..7d9a423e5 --- /dev/null +++ b/tests/lightning_checkpoint/config.toml @@ -0,0 +1,11 @@ +[lightning] +table-concurrency = 1 + +[checkpoint] +enable = true +schema = "tidb_lightning_checkpoint_test_cppk" +driver = "mysql" +keep-after-success = true + +[mydumper] +read-block-size = 1 diff --git a/tests/lightning_checkpoint/run.sh b/tests/lightning_checkpoint/run.sh new file mode 100755 index 000000000..c1e23a541 --- /dev/null +++ b/tests/lightning_checkpoint/run.sh @@ -0,0 +1,118 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euE + +# Populate the mydumper source +DBPATH="$TEST_DIR/cppk.mydump" +TABLE_COUNT=9 +CHUNK_COUNT=50 + +mkdir -p $DBPATH +echo 'CREATE DATABASE cppk_tsr;' > "$DBPATH/cppk_tsr-schema-create.sql" +PARTIAL_IMPORT_QUERY='SELECT 0' +for i in $(seq "$TABLE_COUNT"); do + case $i in + 1) + INDICES="PRIMARY KEY" + ;; + 2) + INDICES="UNIQUE" + ;; + 3) + INDICES=", INDEX(j)" + ;; + 4) + INDICES=", PRIMARY KEY(i, j)" + ;; + 5) + INDICES=", UNIQUE KEY(j)" + ;; + 6) + INDICES=", PRIMARY KEY(j)" + ;; + *) + INDICES="" + ;; + esac + echo "CREATE TABLE tbl$i(i TINYINT, j INT $INDICES);" > "$DBPATH/cppk_tsr.tbl$i-schema.sql" + PARTIAL_IMPORT_QUERY="$PARTIAL_IMPORT_QUERY + coalesce((SELECT sum(j) FROM cppk_tsr.tbl$i), 0)" + for j in $(seq "$CHUNK_COUNT"); do + echo "INSERT INTO tbl$i VALUES ($i,${j}000),($i,${j}001);" > "$DBPATH/cppk_tsr.tbl$i.$j.sql" + done +done +PARTIAL_IMPORT_QUERY="$PARTIAL_IMPORT_QUERY AS s;" + +for BACKEND in importer tidb local; do + if [ "$BACKEND" = 'local' ]; then + check_cluster_version 4 0 0 'local backend' || continue + fi + + # Set the failpoint to kill the lightning instance as soon as one table is imported + # If checkpoint does work, this should only kill 9 instances of lightnings. + SLOWDOWN_FAILPOINTS='github.com/pingcap/br/pkg/lightning/restore/SlowDownImport=sleep(250)' + export GO_FAILPOINTS="$SLOWDOWN_FAILPOINTS;github.com/pingcap/br/pkg/lightning/restore/FailBeforeIndexEngineImported=return" + + # Start importing the tables. + run_sql 'DROP DATABASE IF EXISTS cppk_tsr' + run_sql 'DROP DATABASE IF EXISTS tidb_lightning_checkpoint_test_cppk' + run_sql 'DROP DATABASE IF EXISTS `tidb_lightning_checkpoint_test_cppk.1357924680.bak`' + + # panic after saving index engine checkpoint status before saving table checkpoint status + set +e + for i in $(seq "$TABLE_COUNT"); do + echo "******** Importing Table Now (step $i/$TABLE_COUNT) ********" + run_lightning -d "$DBPATH" --backend $BACKEND --enable-checkpoint=1 2> /dev/null + [ $? -ne 0 ] || exit 1 + done + set -e + + export GO_FAILPOINTS="$SLOWDOWN_FAILPOINTS" + set +e + for i in $(seq "$TABLE_COUNT"); do + echo "******** Importing Table Now (step $i/$TABLE_COUNT) ********" + run_lightning -d "$DBPATH" --backend $BACKEND --enable-checkpoint=1 2> /dev/null + done + set -e + + # Start importing the tables. + run_sql 'DROP DATABASE IF EXISTS cppk_tsr' + run_sql 'DROP DATABASE IF EXISTS tidb_lightning_checkpoint_test_cppk' + run_sql 'DROP DATABASE IF EXISTS `tidb_lightning_checkpoint_test_cppk.1357924680.bak`' + + export GO_FAILPOINTS="$SLOWDOWN_FAILPOINTS;github.com/pingcap/br/pkg/lightning/SetTaskID=return(1357924680);github.com/pingcap/br/pkg/lightning/restore/FailIfIndexEngineImported=return(1)" + + set +e + for i in $(seq "$TABLE_COUNT"); do + echo "******** Importing Table Now (step $i/$TABLE_COUNT) ********" + run_lightning -d "$DBPATH" --backend $BACKEND --enable-checkpoint=1 2> /dev/null + [ $? -ne 0 ] || exit 1 + done + set -e + + # After everything is done, there should be no longer new calls to ImportEngine + # (and thus `kill_lightning_after_one_import` will spare this final check) + echo "******** Verify checkpoint no-op ********" + run_lightning -d "$DBPATH" --backend $BACKEND --enable-checkpoint=1 + run_sql "$PARTIAL_IMPORT_QUERY" + check_contains "s: $(( (1000 * $CHUNK_COUNT + 1001) * $CHUNK_COUNT * $TABLE_COUNT ))" + run_sql 'SELECT count(*) FROM `tidb_lightning_checkpoint_test_cppk.1357924680.bak`.table_v6 WHERE status >= 200' + check_contains "count(*): $TABLE_COUNT" + + # Ensure there is no dangling open engines + ls -lA "$TEST_DIR"/importer/.temp/ + [ -z "$(ls -A "$TEST_DIR"/importer/.temp/)" ] + +done diff --git a/tests/lightning_checkpoint_chunks/config.toml b/tests/lightning_checkpoint_chunks/config.toml new file mode 100644 index 000000000..3d0dd64ce --- /dev/null +++ b/tests/lightning_checkpoint_chunks/config.toml @@ -0,0 +1,8 @@ +[lightning] +region-concurrency = 1 + +[checkpoint] +enable = true +schema = "tidb_lightning_checkpoint_test_cpch" +driver = "mysql" +keep-after-success = true diff --git a/tests/lightning_checkpoint_chunks/file.toml b/tests/lightning_checkpoint_chunks/file.toml new file mode 100644 index 000000000..94e4a34e4 --- /dev/null +++ b/tests/lightning_checkpoint_chunks/file.toml @@ -0,0 +1,9 @@ +[lightning] +region-concurrency = 1 + +[checkpoint] +enable = true +schema = "tidb_lightning_checkpoint_test_cpch" +driver = "file" +dsn = "/tmp/backup_restore_test/cpch.pb" +keep-after-success = true diff --git a/tests/lightning_checkpoint_chunks/run.sh b/tests/lightning_checkpoint_chunks/run.sh new file mode 100755 index 000000000..f7b7cb92e --- /dev/null +++ b/tests/lightning_checkpoint_chunks/run.sh @@ -0,0 +1,128 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euE + +# Populate the mydumper source +DBPATH="$TEST_DIR/cpch.mydump" +CHUNK_COUNT=5 +ROW_COUNT=1000 + +do_run_lightning() { + run_lightning -d "$DBPATH" --enable-checkpoint=1 --config "tests/$TEST_NAME/$1.toml" +} + +verify_checkpoint_noop() { + # After everything is done, there should be no longer new calls to WriteEngine/CloseAndRecv + # (and thus `kill_lightning_after_one_chunk` will spare this final check) + echo "******** Verify checkpoint no-op ********" + do_run_lightning config + run_sql 'SELECT count(i), sum(i) FROM cpch_tsr.tbl;' + check_contains "count(i): $(($ROW_COUNT*$CHUNK_COUNT))" + check_contains "sum(i): $(( $ROW_COUNT*$CHUNK_COUNT*(($CHUNK_COUNT+2)*$ROW_COUNT + 1)/2 ))" + run_sql 'SELECT count(*) FROM `tidb_lightning_checkpoint_test_cpch.1234567890.bak`.table_v6 WHERE status >= 200' + check_contains "count(*): 1" +} + +mkdir -p $DBPATH +echo 'CREATE DATABASE cpch_tsr;' > "$DBPATH/cpch_tsr-schema-create.sql" +echo 'CREATE TABLE tbl(i BIGINT UNSIGNED PRIMARY KEY);' > "$DBPATH/cpch_tsr.tbl-schema.sql" +for i in $(seq "$CHUNK_COUNT"); do + rm -f "$DBPATH/cpch_tsr.tbl.$i.sql" + for j in $(seq "$ROW_COUNT"); do + # the values run from ($ROW_COUNT + 1) to $CHUNK_COUNT*($ROW_COUNT + 1). + echo "INSERT INTO tbl VALUES($(($i*$ROW_COUNT+$j)));" >> "$DBPATH/cpch_tsr.tbl.$i.sql" + done +done + +# Set the failpoint to kill the lightning instance as soon as +# one file (after writing totally $ROW_COUNT rows) is imported. +# If checkpoint does work, this should kill exactly $CHUNK_COUNT instances of lightnings. +TASKID_FAILPOINTS="github.com/pingcap/br/pkg/lightning/SetTaskID=return(1234567890)" +export GO_FAILPOINTS="$TASKID_FAILPOINTS;github.com/pingcap/br/pkg/lightning/restore/FailIfImportedChunk=return($ROW_COUNT)" + +# Start importing the tables. +run_sql 'DROP DATABASE IF EXISTS cpch_tsr' +run_sql 'DROP DATABASE IF EXISTS tidb_lightning_checkpoint_test_cpch' +run_sql 'DROP DATABASE IF EXISTS `tidb_lightning_checkpoint_test_cpch.1234567890.bak`' + +set +e +for i in $(seq "$CHUNK_COUNT"); do + echo "******** Importing Chunk Now (step $i/$CHUNK_COUNT) ********" + do_run_lightning config 2> /dev/null + [ $? -ne 0 ] || exit 1 +done +set -e + +verify_checkpoint_noop + +# Next, test kill lightning via signal mechanism +run_sql 'DROP DATABASE IF EXISTS cpch_tsr' +run_sql 'DROP DATABASE IF EXISTS tidb_lightning_checkpoint_test_cpch' +run_sql 'DROP DATABASE IF EXISTS `tidb_lightning_checkpoint_test_cpch.1234567890.bak`' + +# Set the failpoint to kill the lightning instance as soon as one chunk is imported, via signal mechanism +# If checkpoint does work, this should only kill $CHUNK_COUNT instances of lightnings. +export GO_FAILPOINTS="$TASKID_FAILPOINTS;github.com/pingcap/br/pkg/lightning/restore/KillIfImportedChunk=return($ROW_COUNT)" + +for i in $(seq "$CHUNK_COUNT"); do + echo "******** Importing Chunk Now (step $i/$CHUNK_COUNT) ********" + do_run_lightning config +done + +set +e +i=0 +wait_max_time=20 +while [ $i -lt $wait_max_time ]; do + lightning_proc=$(ps -ef|grep "[b]in/tidb-lightning\\.test.*$TEST_NAME") + ret="$?" + if [ "$ret" -eq 0 ]; then + echo "lightning is still running: $lightning_proc" + sleep 1 + else + break + fi +done +if [ $i -ge $wait_max_time ]; then + echo "wait lightning exit failed" + exit 1 +fi +set -e + +verify_checkpoint_noop + +# Repeat, but using the file checkpoint +run_sql 'DROP DATABASE IF EXISTS cpch_tsr' +run_sql 'DROP DATABASE IF EXISTS tidb_lightning_checkpoint_test_cpch' +rm -f "$TEST_DIR"/cpch.pb* + +# Set the failpoint to kill the lightning instance as soon as one chunk is imported +# If checkpoint does work, this should only kill $CHUNK_COUNT instances of lightnings. +export GO_FAILPOINTS="$TASKID_FAILPOINTS;github.com/pingcap/br/pkg/lightning/restore/FailIfImportedChunk=return($ROW_COUNT)" +set +e +for i in $(seq "$CHUNK_COUNT"); do + echo "******** Importing Chunk using File checkpoint Now (step $i/$CHUNK_COUNT) ********" + do_run_lightning file 2> /dev/null + [ $? -ne 0 ] || exit 1 +done +set -e + +echo "******** Verify File checkpoint no-op ********" +do_run_lightning file +run_sql 'SELECT count(i), sum(i) FROM cpch_tsr.tbl;' +check_contains "count(i): $(($ROW_COUNT*$CHUNK_COUNT))" +check_contains "sum(i): $(( $ROW_COUNT*$CHUNK_COUNT*(($CHUNK_COUNT+2)*$ROW_COUNT + 1)/2 ))" +[ ! -e "$TEST_DIR/cpch.pb" ] +[ -e "$TEST_DIR/cpch.pb.1234567890.bak" ] diff --git a/tests/lightning_checkpoint_columns/config.toml b/tests/lightning_checkpoint_columns/config.toml new file mode 100644 index 000000000..c65861b80 --- /dev/null +++ b/tests/lightning_checkpoint_columns/config.toml @@ -0,0 +1,16 @@ +[lightning] +table-concurrency = 1 +region-concurrency = 1 + +[checkpoint] +enable = true +schema = "tidb_lightning_checkpoint_test" +driver = "mysql" +keep-after-success = true + +[mydumper] +read-block-size = 1 + +[tikv-importer] +max-kv-pairs = 0 + diff --git a/tests/lightning_checkpoint_columns/run.sh b/tests/lightning_checkpoint_columns/run.sh new file mode 100755 index 000000000..1d4125aad --- /dev/null +++ b/tests/lightning_checkpoint_columns/run.sh @@ -0,0 +1,49 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euE + +# Populate the mydumper source +DBPATH="$TEST_DIR/cp.mydump" + +mkdir -p $DBPATH +echo 'CREATE DATABASE cp_tsr;' > "$DBPATH/cp_tsr-schema-create.sql" +echo "CREATE TABLE tbl(i TINYINT PRIMARY KEY, j INT);" > "$DBPATH/cp_tsr.tbl-schema.sql" +# the column orders in data file is different from table schema order. +echo "INSERT INTO tbl (j, i) VALUES (3, 1),(4, 2);" > "$DBPATH/cp_tsr.tbl.sql" + +# Set minDeliverBytes to a small enough number to only write only 1 row each time +# Set the failpoint to kill the lightning instance as soon as one row is written +PKG="github.com/pingcap/br/pkg/lightning/restore" +export GO_FAILPOINTS="$PKG/SlowDownWriteRows=sleep(1000);$PKG/FailAfterWriteRows=panic;$PKG/SetMinDeliverBytes=return(1)" + +# Start importing the tables. +run_sql 'DROP DATABASE IF EXISTS cp_tsr' +run_sql 'DROP DATABASE IF EXISTS tidb_lightning_checkpoint_test' + +set +e +run_lightning -d "$DBPATH" --backend tidb --enable-checkpoint=1 2> /dev/null +set -e +run_sql 'SELECT count(*) FROM `cp_tsr`.tbl' +check_contains "count(*): 1" + +# restart lightning from checkpoint, the second line should be written successfully +export GO_FAILPOINTS= +set +e +run_lightning -d "$DBPATH" --backend tidb --enable-checkpoint=1 2> /dev/null +set -e + +run_sql 'SELECT j FROM `cp_tsr`.tbl WHERE i = 2;' +check_contains "j: 4" diff --git a/tests/lightning_checkpoint_dirty_tableid/data/cpdt-schema-create.sql b/tests/lightning_checkpoint_dirty_tableid/data/cpdt-schema-create.sql new file mode 100644 index 000000000..e3c5418e2 --- /dev/null +++ b/tests/lightning_checkpoint_dirty_tableid/data/cpdt-schema-create.sql @@ -0,0 +1 @@ +create database cpdt; diff --git a/tests/lightning_checkpoint_dirty_tableid/data/cpdt.t-schema.sql b/tests/lightning_checkpoint_dirty_tableid/data/cpdt.t-schema.sql new file mode 100644 index 000000000..8bfb649ef --- /dev/null +++ b/tests/lightning_checkpoint_dirty_tableid/data/cpdt.t-schema.sql @@ -0,0 +1 @@ +create table t (x timestamp not null); diff --git a/tests/lightning_checkpoint_dirty_tableid/data/cpdt.t.sql b/tests/lightning_checkpoint_dirty_tableid/data/cpdt.t.sql new file mode 100644 index 000000000..9bb4e0266 --- /dev/null +++ b/tests/lightning_checkpoint_dirty_tableid/data/cpdt.t.sql @@ -0,0 +1 @@ +insert into t values ('1999-09-09 09:09:09'); diff --git a/tests/lightning_checkpoint_dirty_tableid/file.toml b/tests/lightning_checkpoint_dirty_tableid/file.toml new file mode 100644 index 000000000..2730c338c --- /dev/null +++ b/tests/lightning_checkpoint_dirty_tableid/file.toml @@ -0,0 +1,3 @@ +[checkpoint] +enable = true +driver = "file" diff --git a/tests/lightning_checkpoint_dirty_tableid/mysql.toml b/tests/lightning_checkpoint_dirty_tableid/mysql.toml new file mode 100644 index 000000000..dc4eaf830 --- /dev/null +++ b/tests/lightning_checkpoint_dirty_tableid/mysql.toml @@ -0,0 +1,3 @@ +[checkpoint] +enable = true +driver = "mysql" diff --git a/tests/lightning_checkpoint_dirty_tableid/run.sh b/tests/lightning_checkpoint_dirty_tableid/run.sh new file mode 100755 index 000000000..0b954085b --- /dev/null +++ b/tests/lightning_checkpoint_dirty_tableid/run.sh @@ -0,0 +1,59 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eux + +# clean env +rm -f "$TEST_DIR/lightning-checkpoint-dirty-tableid.log" +run_sql 'DROP DATABASE IF EXISTS tidb_lightning_checkpoint' + +export GO_FAILPOINTS="github.com/pingcap/br/pkg/lightning/restore/InitializeCheckpointExit=return(true)" +run_lightning --enable-checkpoint=1 --log-file "$TEST_DIR/lightning-checkpoint-dirty-tableid.log" --config "tests/$TEST_NAME/mysql.toml" -d "tests/$TEST_NAME/data" + +run_sql 'DROP DATABASE IF EXISTS cpdt' + +export GO_FAILPOINTS="" +set +e +run_lightning --enable-checkpoint=1 --log-file "$TEST_DIR/lightning-checkpoint-dirty-tableid.log" --config "tests/$TEST_NAME/mysql.toml" -d "tests/$TEST_NAME/data" +set -e + +ILLEGAL_CP_COUNT=$(grep "TiDB Lightning has detected tables with illegal checkpoints. To prevent data mismatch, this run will stop now. Please remove these checkpoints first" "$TEST_DIR/lightning-checkpoint-dirty-tableid.log" | wc -l) +TABLE_SUGGEST=$(grep "./tidb-lightning-ctl --checkpoint-remove=" "$TEST_DIR/lightning-checkpoint-dirty-tableid.log" | wc -l) + +[ $ILLEGAL_CP_COUNT -eq 1 ] +[ $TABLE_SUGGEST -eq 2 ] + +# Try again with the file checkpoints + +# clean env +run_sql 'DROP DATABASE IF EXISTS cpdt' +rm -f "$TEST_DIR/lightning-checkpoint-dirty-tableid.log" +rm -f "/tmp/tidb_lightning_checkpoint.pb" + +export GO_FAILPOINTS="github.com/pingcap/br/pkg/lightning/restore/InitializeCheckpointExit=return(true)" +run_lightning --enable-checkpoint=1 --log-file "$TEST_DIR/lightning-checkpoint-dirty-tableid.log" --config "tests/$TEST_NAME/file.toml" -d "tests/$TEST_NAME/data" + +run_sql 'DROP DATABASE IF EXISTS cpdt' + +export GO_FAILPOINTS="" +set +e +run_lightning --enable-checkpoint=1 --log-file "$TEST_DIR/lightning-checkpoint-dirty-tableid.log" --config "tests/$TEST_NAME/file.toml" -d "tests/$TEST_NAME/data" +set -e + +ILLEGAL_CP_COUNT=$(grep "TiDB Lightning has detected tables with illegal checkpoints. To prevent data mismatch, this run will stop now. Please remove these checkpoints first" "$TEST_DIR/lightning-checkpoint-dirty-tableid.log" | wc -l) +TABLE_SUGGEST=$(grep "./tidb-lightning-ctl --checkpoint-remove=" "$TEST_DIR/lightning-checkpoint-dirty-tableid.log" | wc -l) + +[ $ILLEGAL_CP_COUNT -eq 1 ] +[ $TABLE_SUGGEST -eq 2 ] diff --git a/tests/lightning_checkpoint_engines/config.toml b/tests/lightning_checkpoint_engines/config.toml new file mode 100644 index 000000000..412e5a01f --- /dev/null +++ b/tests/lightning_checkpoint_engines/config.toml @@ -0,0 +1,9 @@ +[lightning] +table-concurrency = 1 + +[checkpoint] +enable = true +driver = "file" + +[mydumper] +batch-size = 50 # force splitting the data into 4 batches diff --git a/tests/lightning_checkpoint_engines/data/cpeng-schema-create.sql b/tests/lightning_checkpoint_engines/data/cpeng-schema-create.sql new file mode 100644 index 000000000..1e23466ee --- /dev/null +++ b/tests/lightning_checkpoint_engines/data/cpeng-schema-create.sql @@ -0,0 +1 @@ +create database cpeng; diff --git a/tests/lightning_checkpoint_engines/data/cpeng.a-schema.sql b/tests/lightning_checkpoint_engines/data/cpeng.a-schema.sql new file mode 100644 index 000000000..fe3f493b6 --- /dev/null +++ b/tests/lightning_checkpoint_engines/data/cpeng.a-schema.sql @@ -0,0 +1 @@ +create table a (c int); diff --git a/tests/lightning_checkpoint_engines/data/cpeng.a.1.sql b/tests/lightning_checkpoint_engines/data/cpeng.a.1.sql new file mode 100644 index 000000000..58829b7d8 --- /dev/null +++ b/tests/lightning_checkpoint_engines/data/cpeng.a.1.sql @@ -0,0 +1 @@ +insert into a values (1); diff --git a/tests/lightning_checkpoint_engines/data/cpeng.a.2.sql b/tests/lightning_checkpoint_engines/data/cpeng.a.2.sql new file mode 100644 index 000000000..ccbcb5801 --- /dev/null +++ b/tests/lightning_checkpoint_engines/data/cpeng.a.2.sql @@ -0,0 +1 @@ +insert into a values (2); diff --git a/tests/lightning_checkpoint_engines/data/cpeng.a.3.sql b/tests/lightning_checkpoint_engines/data/cpeng.a.3.sql new file mode 100644 index 000000000..effdc8f3e --- /dev/null +++ b/tests/lightning_checkpoint_engines/data/cpeng.a.3.sql @@ -0,0 +1 @@ +insert into a values (3),(4); diff --git a/tests/lightning_checkpoint_engines/data/cpeng.b-schema.sql b/tests/lightning_checkpoint_engines/data/cpeng.b-schema.sql new file mode 100644 index 000000000..4a3c844ef --- /dev/null +++ b/tests/lightning_checkpoint_engines/data/cpeng.b-schema.sql @@ -0,0 +1 @@ +create table b (c int); diff --git a/tests/lightning_checkpoint_engines/data/cpeng.b.1.sql b/tests/lightning_checkpoint_engines/data/cpeng.b.1.sql new file mode 100644 index 000000000..342c150d9 --- /dev/null +++ b/tests/lightning_checkpoint_engines/data/cpeng.b.1.sql @@ -0,0 +1,4 @@ +insert into b values (10),(11),(12); +/* +padding to make the data file > 50 bytes +*/ \ No newline at end of file diff --git a/tests/lightning_checkpoint_engines/data/cpeng.b.2.sql b/tests/lightning_checkpoint_engines/data/cpeng.b.2.sql new file mode 100644 index 000000000..83045aee9 --- /dev/null +++ b/tests/lightning_checkpoint_engines/data/cpeng.b.2.sql @@ -0,0 +1 @@ +insert into b values (13); diff --git a/tests/lightning_checkpoint_engines/mysql.toml b/tests/lightning_checkpoint_engines/mysql.toml new file mode 100644 index 000000000..4beafd480 --- /dev/null +++ b/tests/lightning_checkpoint_engines/mysql.toml @@ -0,0 +1,9 @@ +[lightning] +table-concurrency = 1 + +[checkpoint] +enable = true +driver = "mysql" + +[mydumper] +batch-size = 50 # force splitting the data into 4 batches diff --git a/tests/lightning_checkpoint_engines/run.sh b/tests/lightning_checkpoint_engines/run.sh new file mode 100755 index 000000000..ae82c729c --- /dev/null +++ b/tests/lightning_checkpoint_engines/run.sh @@ -0,0 +1,101 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eux + +do_run_lightning() { + run_lightning --backend $1 --enable-checkpoint=1 --log-file "$TEST_DIR/lightning-checkpoint-engines.log" --config "tests/$TEST_NAME/$2.toml" +} + +for BACKEND in importer local; do + if [ "$BACKEND" = 'local' ]; then + check_cluster_version 4 0 0 'local backend' || continue + fi + + # First, verify that a normal operation is fine. + rm -f "$TEST_DIR/lightning-checkpoint-engines.log" + rm -f "/tmp/tidb_lightning_checkpoint.pb" + run_sql 'DROP DATABASE IF EXISTS cpeng;' + export GO_FAILPOINTS="" + + do_run_lightning $BACKEND config + + # Check that we have indeed opened 6 engines (index + data engine) + DATA_ENGINE_COUNT=4 + INDEX_ENGINE_COUNT=2 + ENGINE_COUNT=6 + OPEN_ENGINES_COUNT=$(grep 'open engine' "$TEST_DIR/lightning-checkpoint-engines.log" | wc -l) + echo "Number of open engines: $OPEN_ENGINES_COUNT" + [ "$OPEN_ENGINES_COUNT" -eq $ENGINE_COUNT ] + + # Check that everything is correctly imported + run_sql 'SELECT count(*), sum(c) FROM cpeng.a' + check_contains 'count(*): 4' + check_contains 'sum(c): 10' + + run_sql 'SELECT count(*), sum(c) FROM cpeng.b' + check_contains 'count(*): 4' + check_contains 'sum(c): 46' + + # Now, verify it works with checkpoints as well. + + run_sql 'DROP DATABASE cpeng;' + rm -f "/tmp/tidb_lightning_checkpoint.pb" + + # Data engine part + export GO_FAILPOINTS='github.com/pingcap/br/pkg/lightning/restore/SlowDownImport=sleep(500);github.com/pingcap/br/pkg/lightning/restore/FailIfStatusBecomes=return(120);github.com/pingcap/br/pkg/lightning/restore/FailIfIndexEngineImported=return(140)' + set +e + for i in $(seq "$ENGINE_COUNT"); do + echo "******** Importing Table Now (step $i/$ENGINE_COUNT) ********" + do_run_lightning $BACKEND config 2> /dev/null + [ $? -ne 0 ] || exit 1 + done + set -e + + echo "******** Verify checkpoint no-op ********" + do_run_lightning $BACKEND config + + run_sql 'SELECT count(*), sum(c) FROM cpeng.a' + check_contains 'count(*): 4' + check_contains 'sum(c): 10' + + run_sql 'SELECT count(*), sum(c) FROM cpeng.b' + check_contains 'count(*): 4' + check_contains 'sum(c): 46' + + # Now, try again with MySQL checkpoints + + run_sql 'DROP DATABASE cpeng;' + run_sql 'DROP DATABASE IF EXISTS tidb_lightning_checkpoint;' + + set +e + for i in $(seq "$ENGINE_COUNT"); do + echo "******** Importing Table Now (step $i/$ENGINE_COUNT) ********" + do_run_lightning $BACKEND mysql 2> /dev/null + [ $? -ne 0 ] || exit 1 + done + set -e + + echo "******** Verify checkpoint no-op ********" + do_run_lightning $BACKEND mysql + + run_sql 'SELECT count(*), sum(c) FROM cpeng.a' + check_contains 'count(*): 4' + check_contains 'sum(c): 10' + + run_sql 'SELECT count(*), sum(c) FROM cpeng.b' + check_contains 'count(*): 4' + check_contains 'sum(c): 46' +done diff --git a/tests/lightning_checkpoint_error_destroy/bad-data/cped-schema-create.sql b/tests/lightning_checkpoint_error_destroy/bad-data/cped-schema-create.sql new file mode 100644 index 000000000..68c25cc40 --- /dev/null +++ b/tests/lightning_checkpoint_error_destroy/bad-data/cped-schema-create.sql @@ -0,0 +1 @@ +create database cped; diff --git a/tests/lightning_checkpoint_error_destroy/bad-data/cped.t-schema.sql b/tests/lightning_checkpoint_error_destroy/bad-data/cped.t-schema.sql new file mode 100644 index 000000000..8bfb649ef --- /dev/null +++ b/tests/lightning_checkpoint_error_destroy/bad-data/cped.t-schema.sql @@ -0,0 +1 @@ +create table t (x timestamp not null); diff --git a/tests/lightning_checkpoint_error_destroy/bad-data/cped.t.sql b/tests/lightning_checkpoint_error_destroy/bad-data/cped.t.sql new file mode 100644 index 000000000..e96d6d157 --- /dev/null +++ b/tests/lightning_checkpoint_error_destroy/bad-data/cped.t.sql @@ -0,0 +1 @@ +insert into t values ('1111-11-11 11:11:11'); diff --git a/tests/lightning_checkpoint_error_destroy/file.toml b/tests/lightning_checkpoint_error_destroy/file.toml new file mode 100644 index 000000000..2358ae5ac --- /dev/null +++ b/tests/lightning_checkpoint_error_destroy/file.toml @@ -0,0 +1,4 @@ +[checkpoint] +enable = true +driver = "file" +dsn = "/tmp/cp_error_destroy.pb" diff --git a/tests/lightning_checkpoint_error_destroy/good-data/cped-schema-create.sql b/tests/lightning_checkpoint_error_destroy/good-data/cped-schema-create.sql new file mode 100644 index 000000000..68c25cc40 --- /dev/null +++ b/tests/lightning_checkpoint_error_destroy/good-data/cped-schema-create.sql @@ -0,0 +1 @@ +create database cped; diff --git a/tests/lightning_checkpoint_error_destroy/good-data/cped.t-schema.sql b/tests/lightning_checkpoint_error_destroy/good-data/cped.t-schema.sql new file mode 100644 index 000000000..8bfb649ef --- /dev/null +++ b/tests/lightning_checkpoint_error_destroy/good-data/cped.t-schema.sql @@ -0,0 +1 @@ +create table t (x timestamp not null); diff --git a/tests/lightning_checkpoint_error_destroy/good-data/cped.t.sql b/tests/lightning_checkpoint_error_destroy/good-data/cped.t.sql new file mode 100644 index 000000000..9bb4e0266 --- /dev/null +++ b/tests/lightning_checkpoint_error_destroy/good-data/cped.t.sql @@ -0,0 +1 @@ +insert into t values ('1999-09-09 09:09:09'); diff --git a/tests/lightning_checkpoint_error_destroy/mysql.toml b/tests/lightning_checkpoint_error_destroy/mysql.toml new file mode 100644 index 000000000..dc4eaf830 --- /dev/null +++ b/tests/lightning_checkpoint_error_destroy/mysql.toml @@ -0,0 +1,3 @@ +[checkpoint] +enable = true +driver = "mysql" diff --git a/tests/lightning_checkpoint_error_destroy/run.sh b/tests/lightning_checkpoint_error_destroy/run.sh new file mode 100755 index 000000000..4900f10f1 --- /dev/null +++ b/tests/lightning_checkpoint_error_destroy/run.sh @@ -0,0 +1,52 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eux + +# Make sure we won't run out of table concurrency by destroying checkpoints + +for i in $(seq 8); do + ARGS="--enable-checkpoint=1 --config tests/$TEST_NAME/mysql.toml -d tests/$TEST_NAME/bad-data" + set +e + run_lightning $ARGS + set -e + run_lightning_ctl $ARGS -checkpoint-error-destroy=all +done + +run_lightning --enable-checkpoint=1 --config "tests/$TEST_NAME/mysql.toml" -d "tests/$TEST_NAME/good-data" +run_sql 'SELECT * FROM cped.t' +check_contains 'x: 1999-09-09 09:09:09' + +# Try again with the file checkpoints + +run_sql 'DROP DATABASE cped' + +CHECK_POINT_FILE="/tmp/cp_error_destroy.pb" + +# clean up possible old files +rm -rf CHECK_POINT_FILE +for i in $(seq 8); do + ARGS="--enable-checkpoint=1 --config tests/$TEST_NAME/file.toml -d tests/$TEST_NAME/bad-data" + set +e + run_lightning $ARGS + set -e + ls -la /tmp/backup_restore_test/importer/.temp/ + run_lightning_ctl $ARGS -checkpoint-error-destroy=all + ls -la /tmp/backup_restore_test/importer/.temp/ +done + +run_lightning --enable-checkpoint=1 --config "tests/$TEST_NAME/file.toml" -d "tests/$TEST_NAME/good-data" +run_sql 'SELECT * FROM cped.t' +check_contains 'x: 1999-09-09 09:09:09' diff --git a/tests/lightning_checkpoint_parquet/config.toml b/tests/lightning_checkpoint_parquet/config.toml new file mode 100644 index 000000000..3a2ec3570 --- /dev/null +++ b/tests/lightning_checkpoint_parquet/config.toml @@ -0,0 +1,11 @@ +[lightning] +region-concurrency = 1 + +[checkpoint] +enable = true +schema = "checkpoint_test_parquet" +driver = "mysql" +keep-after-success = true + +[tikv-importer] +max-kv-pairs = 32 diff --git a/tests/lightning_checkpoint_parquet/parquet.go b/tests/lightning_checkpoint_parquet/parquet.go new file mode 100644 index 000000000..b2ea1c46f --- /dev/null +++ b/tests/lightning_checkpoint_parquet/parquet.go @@ -0,0 +1,65 @@ +package main + +import ( + "flag" + "fmt" + "log" + "path/filepath" + "strconv" + + "github.com/xitongsys/parquet-go-source/local" + "github.com/xitongsys/parquet-go/writer" +) + +var ( + schema = flag.String("schema", "test", "Test schema name") + table = flag.String("table", "parquet", "Test table name") + chunks = flag.Int("chunk", 10, "Chunk files count") + rowNumbers = flag.Int("rows", 1000, "Row number for each test file") + sourceDir = flag.String("dir", "", "test directory path") +) + +func genParquetFile(dir, name string, count int) error { + type Test struct { + I int32 `parquet:"name=iVal, type=INT32"` + S string `parquet:"name=s, type=UTF8, encoding=PLAIN_DICTIONARY"` + } + + w, err := local.NewLocalFileWriter(filepath.Join(dir, name)) + if err != nil { + return err + } + + test := &Test{} + dataWriter, err := writer.NewParquetWriter(w, test, 2) + if err != nil { + return err + } + for i := 0; i < count; i++ { + test.I = int32(i) + test.S = strconv.Itoa(i) + err := dataWriter.Write(test) + if err != nil { + return err + } + } + err = dataWriter.WriteStop() + if err != nil { + return err + } + w.Close() + + return nil +} + +func main() { + flag.Parse() + + for i := 0; i < *chunks; i++ { + name := fmt.Sprintf("%s.%s.%04d.parquet", *schema, *table, i) + err := genParquetFile(*sourceDir, name, *rowNumbers) + if err != nil { + log.Fatalf("generate test source failed, name: %s, err: %+v", name, err) + } + } +} diff --git a/tests/lightning_checkpoint_parquet/run.sh b/tests/lightning_checkpoint_parquet/run.sh new file mode 100755 index 000000000..9f1004095 --- /dev/null +++ b/tests/lightning_checkpoint_parquet/run.sh @@ -0,0 +1,61 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euE + +# Populate the mydumper source +DBPATH="$TEST_DIR/cppq.mydump" +ROW_COUNT=100 + +do_run_lightning() { + run_lightning -d "$DBPATH" --enable-checkpoint=1 +} + +mkdir -p $DBPATH +echo 'CREATE DATABASE cppq_tsr;' > "$DBPATH/cppq_tsr-schema-create.sql" +# column "iVal" use for testing column name with upper case is properly handled +echo 'CREATE TABLE tbl(iVal INT, s VARCHAR(16));' > "$DBPATH/cppq_tsr.tbl-schema.sql" +bin/parquet_gen --dir $DBPATH --schema cppq_tsr --table tbl --chunk 1 --rows $ROW_COUNT + +# Set the failpoint to kill the lightning instance as soon as one batch data is written +PKG="github.com/pingcap/br/pkg/lightning/restore" +export GO_FAILPOINTS="$PKG/SlowDownWriteRows=sleep(1000);$PKG/FailAfterWriteRows=panic;$PKG/SetMinDeliverBytes=return(1)" + +# Start importing the tables. +run_sql 'DROP DATABASE IF EXISTS cppq_tsr' +run_sql 'DROP DATABASE IF EXISTS checkpoint_test_parquet' + +set +e +run_lightning -d "$DBPATH" --backend tidb --enable-checkpoint=1 2> /dev/null +set -e +run_sql 'SELECT count(*), sum(iVal) FROM `cppq_tsr`.tbl' +check_contains "count(*): 32" +# sum(0..31) +check_contains "sum(iVal): 496" + +# check chunk offset and update checkpoint current row id to a higher value so that +# if parse read from start, the generated rows will be different +run_sql "UPDATE checkpoint_test_parquet.chunk_v5 SET prev_rowid_max = prev_rowid_max + 1000, rowid_max = rowid_max + 1000;" + +# restart lightning from checkpoint, the second line should be written successfully +export GO_FAILPOINTS= +set +e +run_lightning -d "$DBPATH" --backend tidb --enable-checkpoint=1 2> /dev/null +set -e + +run_sql 'SELECT count(*), sum(iVal) FROM `cppq_tsr`.tbl' +check_contains "count(*): 100" +# sum(0..99) +check_contains "sum(iVal): 4950" diff --git a/tests/lightning_checkpoint_timestamp/config.toml b/tests/lightning_checkpoint_timestamp/config.toml new file mode 100644 index 000000000..13504617d --- /dev/null +++ b/tests/lightning_checkpoint_timestamp/config.toml @@ -0,0 +1,11 @@ +[lightning] +region-concurrency = 1 + +[checkpoint] +enable = true +schema = "tidb_lightning_checkpoint_timestamp" +driver = "file" +dsn = "/tmp/backup_restore_test/cpts.pb" + +[mydumper] +read-block-size = 1 diff --git a/tests/lightning_checkpoint_timestamp/data/cpts-schema-create.sql b/tests/lightning_checkpoint_timestamp/data/cpts-schema-create.sql new file mode 100644 index 000000000..8fb37ec32 --- /dev/null +++ b/tests/lightning_checkpoint_timestamp/data/cpts-schema-create.sql @@ -0,0 +1 @@ +create database cpts; diff --git a/tests/lightning_checkpoint_timestamp/data/cpts.cpts-schema.sql b/tests/lightning_checkpoint_timestamp/data/cpts.cpts-schema.sql new file mode 100644 index 000000000..84af60427 --- /dev/null +++ b/tests/lightning_checkpoint_timestamp/data/cpts.cpts-schema.sql @@ -0,0 +1 @@ +create table cpts (ts datetime(6) not null default current_timestamp(6), key(ts)); diff --git a/tests/lightning_checkpoint_timestamp/data/cpts.cpts.1.sql b/tests/lightning_checkpoint_timestamp/data/cpts.cpts.1.sql new file mode 100644 index 000000000..1913b846c --- /dev/null +++ b/tests/lightning_checkpoint_timestamp/data/cpts.cpts.1.sql @@ -0,0 +1,4 @@ +insert into cpts values (), (), (), (), (), (), (), (), (), (), (), (), (), (); +insert into cpts values (), (), (), (), (), (), (), (), (), (), (), (), (), (); +insert into cpts values (), (), (), (), (), (), (), (), (), (), (), (), (), (); +insert into cpts values (), (), (), (), (), (), (), (), (), (), (), (), (), (); diff --git a/tests/lightning_checkpoint_timestamp/data/cpts.cpts.2.sql b/tests/lightning_checkpoint_timestamp/data/cpts.cpts.2.sql new file mode 100644 index 000000000..29c26ce6c --- /dev/null +++ b/tests/lightning_checkpoint_timestamp/data/cpts.cpts.2.sql @@ -0,0 +1,3 @@ +insert into cpts values (), (), (), (), (), (), (), (), (), (), (), (), (), (); +insert into cpts values (), (), (), (), (), (), (), (), (), (), (), (), (), (); +insert into cpts values (), (), (), (), (), (), (), (), (), (), (), (), (), (); diff --git a/tests/lightning_checkpoint_timestamp/mysql.toml b/tests/lightning_checkpoint_timestamp/mysql.toml new file mode 100644 index 000000000..a0d4f5253 --- /dev/null +++ b/tests/lightning_checkpoint_timestamp/mysql.toml @@ -0,0 +1,11 @@ +[lightning] +region-concurrency = 1 + +[checkpoint] +enable = true +schema = "tidb_lightning_checkpoint_timestamp" +driver = "mysql" +keep-after-success = true + +[mydumper] +read-block-size = 1 diff --git a/tests/lightning_checkpoint_timestamp/run.sh b/tests/lightning_checkpoint_timestamp/run.sh new file mode 100755 index 000000000..f7e8103c7 --- /dev/null +++ b/tests/lightning_checkpoint_timestamp/run.sh @@ -0,0 +1,46 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eu + +run_sql 'DROP DATABASE IF EXISTS cpts' +rm -f "$TEST_DIR"/cpts.pb* + +export GO_FAILPOINTS="github.com/pingcap/br/pkg/lightning/SetTaskID=return(1234567890);github.com/pingcap/br/pkg/lightning/restore/FailIfImportedChunk=return(28)" + +for i in $(seq 5); do + echo "******** Importing Chunk Now (file step $i) ********" + run_lightning --enable-checkpoint=1 2> /dev/null && break + sleep 1 +done + +run_sql 'SELECT COUNT(ts) a, COUNT(DISTINCT ts) b FROM cpts.cpts;' +check_contains 'a: 98' +check_contains 'b: 1' + +run_sql 'DROP DATABASE IF EXISTS cpts' +run_sql 'DROP DATABASE IF EXISTS tidb_lightning_checkpoint_timestamp' +run_sql 'DROP DATABASE IF EXISTS `tidb_lightning_checkpoint_timestamp.1234567890.bak`' + +for i in $(seq 5); do + echo "******** Importing Chunk Now (mysql step $i) ********" + run_lightning --enable-checkpoint=1 --config "tests/$TEST_NAME/mysql.toml" 2> /dev/null && break + sleep 1 +done + +run_sql 'SELECT COUNT(ts) a, COUNT(DISTINCT ts) b FROM cpts.cpts;' +check_contains 'a: 98' +check_contains 'b: 1' + diff --git a/tests/lightning_cmdline_override/config.toml b/tests/lightning_cmdline_override/config.toml new file mode 100644 index 000000000..aaf4480a9 --- /dev/null +++ b/tests/lightning_cmdline_override/config.toml @@ -0,0 +1,18 @@ +[lightning] +check-requirements = false +file = "/tmp/xyzxyzxyz" +level = "xyzxyzxyz" + +[tikv-importer] +addr = "xyzxyzxyz" + +[mydumper] +data-source-dir = "xyzxyzxyz" + +[tidb] +host = "xyzxyzxyz" +port = 12345678 +user = "xyzxyzxyz" +status-port = 12345678 +pd-addr = "xyzxyzxyz" + diff --git a/tests/lightning_cmdline_override/data/cmdline_override-schema-create.sql b/tests/lightning_cmdline_override/data/cmdline_override-schema-create.sql new file mode 100644 index 000000000..004a73e49 --- /dev/null +++ b/tests/lightning_cmdline_override/data/cmdline_override-schema-create.sql @@ -0,0 +1 @@ +create database cmdline_override; \ No newline at end of file diff --git a/tests/lightning_cmdline_override/data/cmdline_override.t-schema.sql b/tests/lightning_cmdline_override/data/cmdline_override.t-schema.sql new file mode 100644 index 000000000..c74c68204 --- /dev/null +++ b/tests/lightning_cmdline_override/data/cmdline_override.t-schema.sql @@ -0,0 +1 @@ +create table t (a int); diff --git a/tests/lightning_cmdline_override/data/cmdline_override.t.sql b/tests/lightning_cmdline_override/data/cmdline_override.t.sql new file mode 100644 index 000000000..80c7e623e --- /dev/null +++ b/tests/lightning_cmdline_override/data/cmdline_override.t.sql @@ -0,0 +1 @@ +insert into t values (15); \ No newline at end of file diff --git a/tests/lightning_cmdline_override/run.sh b/tests/lightning_cmdline_override/run.sh new file mode 100755 index 000000000..6a9678767 --- /dev/null +++ b/tests/lightning_cmdline_override/run.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +set -eux + +run_lightning \ + -L info \ + --log-file "$TEST_DIR/lightning.log" \ + --tidb-host 127.0.0.1 \ + --tidb-port 4000 \ + --tidb-user root \ + --tidb-status 10080 \ + --pd-urls 127.0.0.1:2379 \ + -d "tests/$TEST_NAME/data" \ + --importer 127.0.0.1:8808 + +run_sql 'SELECT * FROM cmdline_override.t' +check_contains 'a: 15' diff --git a/tests/lightning_column_permutation/config.toml b/tests/lightning_column_permutation/config.toml new file mode 100644 index 000000000..7bbff7f80 --- /dev/null +++ b/tests/lightning_column_permutation/config.toml @@ -0,0 +1,3 @@ +[mydumper] +strict-format = true +max-region-size = 200 diff --git a/tests/lightning_column_permutation/data/perm-schema-create.sql b/tests/lightning_column_permutation/data/perm-schema-create.sql new file mode 100644 index 000000000..fe9a5be60 --- /dev/null +++ b/tests/lightning_column_permutation/data/perm-schema-create.sql @@ -0,0 +1 @@ +CREATE DATABASE `perm` IF NOT EXISTS; diff --git a/tests/lightning_column_permutation/data/perm.test_perm-schema.sql b/tests/lightning_column_permutation/data/perm.test_perm-schema.sql new file mode 100644 index 000000000..d6a69b89c --- /dev/null +++ b/tests/lightning_column_permutation/data/perm.test_perm-schema.sql @@ -0,0 +1,22 @@ +CREATE TABLE `test_perm` ( + `id` int(11) NOT NULL, + `contract_no` varchar(64) DEFAULT NULL, + `fund_seq_no` varchar(64) DEFAULT NULL, + `term_no` int(11) DEFAULT NULL, + `contract_type` varchar(8) DEFAULT NULL, + `internal_transfer_tag` varchar(8) DEFAULT NULL, + `prin_amt` int(11) DEFAULT NULL, + `start_date` varchar(8) DEFAULT NULL, + `end_date` varchar(8) DEFAULT NULL, + `batch_date` varchar(32) DEFAULT NULL, + `crt_time` timestamp DEFAULT CURRENT_TIMESTAMP, + `region_code` varchar(8) DEFAULT NULL, + `credit_code` varchar(64) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin +PARTITION BY RANGE COLUMNS(batch_date) ( + PARTITION `P20200224` VALUES LESS THAN ("2020-02-05 00:00:00"), + PARTITION `P20200324` VALUES LESS THAN ("2020-03-05 00:00:00"), + PARTITION `P20200424` VALUES LESS THAN ("2020-04-05 00:00:00"), + PARTITION `P20200524` VALUES LESS THAN ("2020-05-05 00:00:00"), + PARTITION `P_MAXVALUE` VALUES LESS THAN MAXVALUE +); diff --git a/tests/lightning_column_permutation/data/perm.test_perm.0.csv b/tests/lightning_column_permutation/data/perm.test_perm.0.csv new file mode 100644 index 000000000..f27b9fd5b --- /dev/null +++ b/tests/lightning_column_permutation/data/perm.test_perm.0.csv @@ -0,0 +1,6 @@ +contract_no,fund_seq_no,term_no,contract_type,internal_transfer_tag,prin_amt,start_date,end_date,region_code,credit_code +2020061000019011020164030595,202006100001901102016403059520200629,1,01,N,356,20200210,20200720,000000, +2020061000019011020164030596,202006100001901102016403059520200628,1,01,N,3561,20200310,20200720,000001, +2020061000019011020164030597,202006100001901102016403059520200627,1,01,N,3562,20200410,20200720,000002,33 +2020061000019011020164030598,108319xx0185-202006100001901102016403059520200626,12,02,Y,26368,20200510,20200620,000003, +2020061000019011020164030599,202006100001901102016403059520200625,1,01,N,3960,20200610,20200720,000005,999 \ No newline at end of file diff --git a/tests/lightning_column_permutation/run.sh b/tests/lightning_column_permutation/run.sh new file mode 100644 index 000000000..db69f6448 --- /dev/null +++ b/tests/lightning_column_permutation/run.sh @@ -0,0 +1,18 @@ +set -eu + +for BACKEND in local importer tidb; do + if [ "$BACKEND" = 'local' ]; then + check_cluster_version 4 0 0 'local backend' || continue + fi + run_sql 'DROP DATABASE IF EXISTS perm' + + run_lightning --backend $BACKEND + + run_sql 'select count(*) from perm.test_perm;' + check_contains "count(*): 5" + + run_sql "SELECT fund_seq_no, region_code, credit_code FROM perm.test_perm WHERE contract_no = '2020061000019011020164030597';" + check_contains "fund_seq_no: 202006100001901102016403059520200627" + check_contains "region_code: 000002" + check_contains "credit_code: 33" +done diff --git a/tests/lightning_common_handle/config.toml b/tests/lightning_common_handle/config.toml new file mode 100644 index 000000000..135154af0 --- /dev/null +++ b/tests/lightning_common_handle/config.toml @@ -0,0 +1,2 @@ +[tikv-importer] +region-split-size = 20 diff --git a/tests/lightning_common_handle/run.sh b/tests/lightning_common_handle/run.sh new file mode 100644 index 000000000..101689f5d --- /dev/null +++ b/tests/lightning_common_handle/run.sh @@ -0,0 +1,59 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +check_cluster_version 4 0 0 'local backend' || exit 0 + +# enable cluster index +run_sql 'set @@global.tidb_enable_clustered_index = 1' || exit 0 +# wait for global variable cache invalid +sleep 2 + +set -euE + +# Populate the mydumper source +DBPATH="$TEST_DIR/ch.mydump" +mkdir -p $DBPATH +echo 'CREATE DATABASE ch;' > "$DBPATH/ch-schema-create.sql" +# create table with non-integer primary key, so that cluster index will be used +echo "CREATE TABLE t(s varchar(32), i INT, j TINYINT, PRIMARY KEY(s, i));" > "$DBPATH/ch.t-schema.sql" +cat > "$DBPATH/ch.t.0.sql" << _EOF_ +INSERT INTO t (s, i, j) VALUES + ("this_is_test1", 1, 1), + ("this_is_test2", 2, 2), + ("this_is_test3", 3, 3), + ("this_is_test4", 4, 4), + ("this_is_test5", 5, 5); +_EOF_ +echo 'INSERT INTO t(s, i, j) VALUES ("another test case", 6, 6);' > "$DBPATH/ch.t.1.sql" + +for BACKEND in local importer tidb; do + # Start importing the tables. + run_sql 'DROP DATABASE IF EXISTS ch' + + run_lightning -d "$DBPATH" --backend $BACKEND 2> /dev/null + + run_sql 'SELECT count(*), sum(i) FROM `ch`.t' + check_contains "count(*): 6" + check_contains "sum(i): 21" + + # check table kv pairs. common hanle should have no extra index kv-paris + run_sql 'ADMIN CHECKSUM TABLE `ch`.t' + check_contains "Total_kvs: 6" +done + +# restore global variables, other tests needs this to handle the _tidb_row_id column +run_sql 'set @@global.tidb_enable_clustered_index = 0' +# wait for global variable cache invalid +sleep 2 diff --git a/tests/lightning_concurrent-restore/config.toml b/tests/lightning_concurrent-restore/config.toml new file mode 100644 index 000000000..2899d4221 --- /dev/null +++ b/tests/lightning_concurrent-restore/config.toml @@ -0,0 +1,3 @@ +[lightning] +table-concurrency = 4 +index-concurrency = 4 diff --git a/tests/lightning_concurrent-restore/run.sh b/tests/lightning_concurrent-restore/run.sh new file mode 100644 index 000000000..8e3d36713 --- /dev/null +++ b/tests/lightning_concurrent-restore/run.sh @@ -0,0 +1,48 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eu + +# Populate the mydumper source +DBPATH="$TEST_DIR/restore_conc.mydump" +TABLE_COUNT=8 + +mkdir -p $DBPATH +echo 'CREATE DATABASE restore_conc;' > "$DBPATH/restore_conc-schema-create.sql" +for i in $(seq "$TABLE_COUNT"); do + echo "CREATE TABLE tbl$i(i TINYINT);" > "$DBPATH/restore_conc.tbl$i-schema.sql" + echo "INSERT INTO tbl$i VALUES (1);" > "$DBPATH/restore_conc.tbl$i.sql" +done + +run_sql 'select VARIABLE_VALUE from mysql.tidb where VARIABLE_NAME = "tikv_gc_life_time"'; +ORIGINAL_TIKV_GC_LIFE_TIME=$(tail -n 1 "$TEST_DIR/sql_res.$TEST_NAME.txt" | awk '{print $(NF)}') + +# add a delay after increasing tikv_gc_life_time, in order to increase confilct possibility +export GO_FAILPOINTS='github.com/pingcap/br/pkg/lightning/restore/IncreaseGCUpdateDuration=sleep(200)' + +# Start importing +run_sql 'DROP DATABASE IF EXISTS restore_conc' +run_lightning -d "$DBPATH" +echo "Import finished" + +# Verify all data are imported +for i in $(seq "$TABLE_COUNT"); do + run_sql "SELECT sum(i) FROM restore_conc.tbl$i;" + check_contains 'sum(i): 1' +done + +# check tikv_gc_life_time is recovered to the original value +run_sql 'select VARIABLE_VALUE from mysql.tidb where VARIABLE_NAME = "tikv_gc_life_time"'; +check_contains "VARIABLE_VALUE: $ORIGINAL_TIKV_GC_LIFE_TIME" \ No newline at end of file diff --git a/tests/lightning_csv/config.toml b/tests/lightning_csv/config.toml new file mode 100644 index 000000000..95da85a3d --- /dev/null +++ b/tests/lightning_csv/config.toml @@ -0,0 +1,12 @@ +[mydumper.csv] +separator = ',' +delimiter = '"' +header = false +not-null = false +null = '\N' +backslash-escape = true +trim-last-separator = false + +[tikv-importer] +send-kv-pairs=10 +region-split-size = 1024 diff --git a/tests/lightning_csv/data/csv-schema-create.sql b/tests/lightning_csv/data/csv-schema-create.sql new file mode 100644 index 000000000..bdeb27b13 --- /dev/null +++ b/tests/lightning_csv/data/csv-schema-create.sql @@ -0,0 +1 @@ +CREATE DATABASE `CSV`; diff --git a/tests/lightning_csv/data/csv.empty_strings-schema.sql b/tests/lightning_csv/data/csv.empty_strings-schema.sql new file mode 100644 index 000000000..49354b652 --- /dev/null +++ b/tests/lightning_csv/data/csv.empty_strings-schema.sql @@ -0,0 +1,5 @@ +create table empty_strings ( + id int, + a varchar(20), + b varchar(20) +); diff --git a/tests/lightning_csv/data/csv.empty_strings.csv b/tests/lightning_csv/data/csv.empty_strings.csv new file mode 100644 index 000000000..5edd50b59 --- /dev/null +++ b/tests/lightning_csv/data/csv.empty_strings.csv @@ -0,0 +1,4 @@ +1,,"" +2,, +3,"""", +4,"","" diff --git a/tests/lightning_csv/data/csv.escapes-schema.sql b/tests/lightning_csv/data/csv.escapes-schema.sql new file mode 100644 index 000000000..04631f1a3 --- /dev/null +++ b/tests/lightning_csv/data/csv.escapes-schema.sql @@ -0,0 +1,16 @@ +/* + +insert into `escapes` values +(1, '\\', '{"?":[]}', X'ffffffff'), +(2, '"', '"\\n\\n\\n"', X'0d0a0d0a'), +(3, '\n', '[",,,"]', '\\,\\,'); + +*/ + +create table `escapes` ( + `i` int primary key, + `t` text, + `j` json, + `b` blob +); + diff --git a/tests/lightning_csv/data/csv.escapes.CSV b/tests/lightning_csv/data/csv.escapes.CSV new file mode 100644 index 000000000..2462bc32b --- /dev/null +++ b/tests/lightning_csv/data/csv.escapes.CSV @@ -0,0 +1,6 @@ +"1","\\","{\"?\": []}","ÿÿÿÿ" +"2","\"","\"\\n\\n\\n\""," \ + \ +" +"3","\ +","[\",,,\"]","\\,\\," diff --git a/tests/lightning_csv/data/csv.threads-schema.sql b/tests/lightning_csv/data/csv.threads-schema.sql new file mode 100644 index 000000000..ab54a4952 --- /dev/null +++ b/tests/lightning_csv/data/csv.threads-schema.sql @@ -0,0 +1,27 @@ +CREATE TABLE `threads` ( + `THREAD_ID` bigint(20) unsigned NOT NULL, + `NAME` varchar(128) NOT NULL, + `TYPE` varchar(10) NOT NULL, + `PROCESSLIST_ID` bigint(20) unsigned DEFAULT NULL, + `PROCESSLIST_USER` varchar(32) DEFAULT NULL, + `PROCESSLIST_HOST` varchar(60) DEFAULT NULL, + `PROCESSLIST_DB` varchar(64) DEFAULT NULL, + `PROCESSLIST_COMMAND` varchar(16) DEFAULT NULL, + `PROCESSLIST_TIME` bigint(20) DEFAULT NULL, + `PROCESSLIST_STATE` varchar(64) DEFAULT NULL, + `PROCESSLIST_INFO` longtext, + `PARENT_THREAD_ID` bigint(20) unsigned DEFAULT NULL, + `ROLE` varchar(64) DEFAULT NULL, + `INSTRUMENTED` enum('YES','NO') NOT NULL, + `HISTORY` enum('YES','NO') NOT NULL, + `CONNECTION_TYPE` varchar(16) DEFAULT NULL, + `THREAD_OS_ID` bigint(20) unsigned DEFAULT NULL, + `RESOURCE_GROUP` varchar(64) DEFAULT NULL, + PRIMARY KEY (`THREAD_ID`), + KEY `PROCESSLIST_ID` (`PROCESSLIST_ID`), + KEY `THREAD_OS_ID` (`THREAD_OS_ID`), + KEY `NAME` (`NAME`), + KEY `PROCESSLIST_ACCOUNT` (`PROCESSLIST_USER`,`PROCESSLIST_HOST`), + KEY `PROCESSLIST_HOST` (`PROCESSLIST_HOST`), + KEY `RESOURCE_GROUP` (`RESOURCE_GROUP`) +); diff --git a/tests/lightning_csv/data/csv.threads.csv b/tests/lightning_csv/data/csv.threads.csv new file mode 100644 index 000000000..501fec87e --- /dev/null +++ b/tests/lightning_csv/data/csv.threads.csv @@ -0,0 +1,43 @@ +"1","thread/sql/main","BACKGROUND",\N,\N,\N,"mysql",\N,"31285",\N,\N,\N,\N,"YES","YES",\N,"7059733",\N +"2","thread/mysys/thread_timer_notifier","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,"1",\N,"YES","YES",\N,"7059870",\N +"4","thread/innodb/io_ibuf_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059873",\N +"5","thread/innodb/io_log_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059874",\N +"6","thread/innodb/io_read_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059875",\N +"7","thread/innodb/io_read_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059876",\N +"8","thread/innodb/io_read_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059877",\N +"9","thread/innodb/io_read_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059878",\N +"10","thread/innodb/io_write_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059879",\N +"11","thread/innodb/io_write_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059880",\N +"12","thread/innodb/io_write_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059881",\N +"13","thread/innodb/io_write_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059882",\N +"14","thread/innodb/page_flush_coordinator_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059883",\N +"15","thread/innodb/log_checkpointer_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059884",\N +"16","thread/innodb/log_closer_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059885",\N +"17","thread/innodb/log_writer_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059886",\N +"18","thread/innodb/log_flusher_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059887",\N +"19","thread/innodb/log_write_notifier_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059888",\N +"20","thread/innodb/log_flush_notifier_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059889",\N +"21","thread/innodb/srv_lock_timeout_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059892",\N +"22","thread/innodb/srv_error_monitor_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059893",\N +"23","thread/innodb/srv_monitor_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059894",\N +"24","thread/innodb/buf_resize_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059895",\N +"25","thread/innodb/srv_master_thread","BACKGROUND",\N,\N,\N,\N,\N,"31285",\N,\N,\N,\N,"YES","YES",\N,"7059896",\N +"26","thread/innodb/buf_dump_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059897",\N +"27","thread/innodb/dict_stats_thread","BACKGROUND",\N,\N,\N,\N,\N,"31285",\N,\N,\N,\N,"YES","YES",\N,"7059898",\N +"28","thread/innodb/fts_optimize_thread","BACKGROUND",\N,\N,\N,\N,\N,"31285",\N,\N,\N,\N,"YES","YES",\N,"7059899",\N +"29","thread/mysqlx/worker","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,"1",\N,"YES","YES",\N,"7059904",\N +"30","thread/mysqlx/worker","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,"1",\N,"YES","YES",\N,"7059905",\N +"32","thread/mysqlx/acceptor_network","BACKGROUND",\N,\N,\N,\N,\N,"31285",\N,\N,"1",\N,"YES","YES","Plugin","7059907",\N +"35","thread/innodb/srv_purge_thread","BACKGROUND",\N,\N,\N,\N,\N,"31285",\N,\N,\N,\N,"YES","YES",\N,"7059910",\N +"36","thread/innodb/srv_purge_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059910",\N +"37","thread/innodb/srv_worker_thread","BACKGROUND",\N,\N,\N,\N,\N,"31285",\N,\N,\N,\N,"YES","YES",\N,"7059911",\N +"38","thread/innodb/srv_worker_thread","BACKGROUND",\N,\N,\N,\N,\N,"31285",\N,\N,\N,\N,"YES","YES",\N,"7059912",\N +"39","thread/innodb/srv_worker_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059911",\N +"40","thread/innodb/srv_worker_thread","BACKGROUND",\N,\N,\N,\N,\N,"31285",\N,\N,\N,\N,"YES","YES",\N,"7059913",\N +"41","thread/innodb/srv_worker_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059912",\N +"42","thread/innodb/srv_worker_thread","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,\N,\N,"YES","YES",\N,"7059913",\N +"43","thread/sql/event_scheduler","FOREGROUND","4",\N,\N,\N,"Sleep",\N,"Waiting on empty queue",\N,"1",\N,"YES","YES",\N,"7059916",\N +"44","thread/sql/signal_handler","BACKGROUND",\N,\N,\N,\N,\N,\N,\N,\N,"1",\N,"YES","YES",\N,"7059917",\N +"45","thread/sql/compress_gtid_table","FOREGROUND","6",\N,\N,\N,"Daemon","31285","Suspending",\N,"1",\N,"YES","YES",\N,"7059918",\N +"49","thread/sql/one_connection","FOREGROUND","11","root","localhost","sample","Sleep","9403",\N,\N,\N,\N,"YES","YES","Socket","7060003",\N +"51","thread/sql/one_connection","FOREGROUND","13","root","localhost","performance_schema","Query","0","Sending data","select * from threads into outfile '/tmp/1.csv' FIELDS TERMINATED BY ',' ENCLOSED BY '\"' LINES TERMINATED BY '\\n'",\N,\N,"YES","YES","Socket","7260196",\N diff --git a/tests/lightning_csv/run.sh b/tests/lightning_csv/run.sh new file mode 100755 index 000000000..334d244a0 --- /dev/null +++ b/tests/lightning_csv/run.sh @@ -0,0 +1,43 @@ +#!/bin/sh + +set -eu + +for BACKEND in importer tidb local; do + if [ "$BACKEND" = 'local' ]; then + check_cluster_version 4 0 0 'local backend' || continue + fi + + run_sql 'DROP DATABASE IF EXISTS csv' + + run_lightning --backend $BACKEND + + run_sql 'SELECT count(*), sum(PROCESSLIST_TIME), sum(THREAD_OS_ID), count(PROCESSLIST_STATE) FROM csv.threads' + check_contains 'count(*): 43' + check_contains 'sum(PROCESSLIST_TIME): 322253' + check_contains 'sum(THREAD_OS_ID): 303775702' + check_contains 'count(PROCESSLIST_STATE): 3' + + run_sql 'SELECT count(*) FROM csv.threads WHERE PROCESSLIST_TIME IS NOT NULL' + check_contains 'count(*): 12' + + run_sql 'SELECT hex(t), j, hex(b) FROM csv.escapes WHERE i = 1' + check_contains 'hex(t): 5C' + check_contains 'j: {"?": []}' + check_contains 'hex(b): FFFFFFFF' + + run_sql 'SELECT hex(t), j, hex(b) FROM csv.escapes WHERE i = 2' + check_contains 'hex(t): 22' + check_contains 'j: "\n\n\n"' + check_contains 'hex(b): 0D0A0D0A' + + run_sql 'SELECT hex(t), j, hex(b) FROM csv.escapes WHERE i = 3' + check_contains 'hex(t): 0A' + check_contains 'j: [",,,"]' + check_contains 'hex(b): 5C2C5C2C' + + run_sql 'SELECT id FROM csv.empty_strings WHERE a = """"' + check_contains 'id: 3' + run_sql 'SELECT id FROM csv.empty_strings WHERE b <> ""' + check_not_contains 'id:' + +done diff --git a/tests/lightning_default-columns/config.toml b/tests/lightning_default-columns/config.toml new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_default-columns/data/defcol-schema-create.sql b/tests/lightning_default-columns/data/defcol-schema-create.sql new file mode 100644 index 000000000..02bc0983c --- /dev/null +++ b/tests/lightning_default-columns/data/defcol-schema-create.sql @@ -0,0 +1 @@ +CREATE DATABASE defcol; diff --git a/tests/lightning_default-columns/data/defcol.t-schema.sql b/tests/lightning_default-columns/data/defcol.t-schema.sql new file mode 100644 index 000000000..05f2ae676 --- /dev/null +++ b/tests/lightning_default-columns/data/defcol.t-schema.sql @@ -0,0 +1,6 @@ +CREATE TABLE t ( + pk INT PRIMARY KEY AUTO_INCREMENT, + x INT NULL, + y INT NOT NULL DEFAULT 123, + z DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); diff --git a/tests/lightning_default-columns/data/defcol.t.1.sql b/tests/lightning_default-columns/data/defcol.t.1.sql new file mode 100644 index 000000000..aef45b86c --- /dev/null +++ b/tests/lightning_default-columns/data/defcol.t.1.sql @@ -0,0 +1 @@ +INSERT INTO t () VALUES (), (), (), (), (), (); diff --git a/tests/lightning_default-columns/data/defcol.t.2.sql b/tests/lightning_default-columns/data/defcol.t.2.sql new file mode 100644 index 000000000..253dfdd8d --- /dev/null +++ b/tests/lightning_default-columns/data/defcol.t.2.sql @@ -0,0 +1 @@ +INSERT INTO t VALUES (), (), (); diff --git a/tests/lightning_default-columns/data/defcol.u-schema.sql b/tests/lightning_default-columns/data/defcol.u-schema.sql new file mode 100644 index 000000000..c85dc9826 --- /dev/null +++ b/tests/lightning_default-columns/data/defcol.u-schema.sql @@ -0,0 +1,4 @@ +CREATE TABLE u ( + xx INT UNIQUE AUTO_INCREMENT, + yy INT PRIMARY KEY +); diff --git a/tests/lightning_default-columns/data/defcol.u.1.sql b/tests/lightning_default-columns/data/defcol.u.1.sql new file mode 100644 index 000000000..19c7c31cb --- /dev/null +++ b/tests/lightning_default-columns/data/defcol.u.1.sql @@ -0,0 +1 @@ +INSERT INTO u (yy) VALUES (40), (60); diff --git a/tests/lightning_default-columns/run.sh b/tests/lightning_default-columns/run.sh new file mode 100755 index 000000000..1d8bc4eb1 --- /dev/null +++ b/tests/lightning_default-columns/run.sh @@ -0,0 +1,35 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eu + +run_sql 'DROP DATABASE IF EXISTS defcol' + +run_lightning --log-file "$TEST_DIR/defcol-errors.log" + +run_sql 'SELECT min(pk), count(pk) FROM defcol.t' +check_contains 'min(pk): 1' +check_contains 'count(pk): 9' + +run_sql 'SELECT pk FROM defcol.t WHERE x IS NOT NULL OR y <> 123 OR z IS NULL OR z NOT BETWEEN now() - INTERVAL 5 MINUTE AND now()' +check_not_contains 'pk:' + +run_sql 'SELECT xx FROM defcol.u WHERE yy = 40' +check_contains 'xx: 1' + +run_sql 'SELECT xx FROM defcol.u WHERE yy = 60' +check_contains 'xx: 2' + +grep -q '\["column missing from data file, going to fill with default value"\] \[table=`defcol`\.`u`\].*\[colName=xx\]' "$TEST_DIR/defcol-errors.log" diff --git a/tests/lightning_disk_quota/config.toml b/tests/lightning_disk_quota/config.toml new file mode 100644 index 000000000..2859d63cc --- /dev/null +++ b/tests/lightning_disk_quota/config.toml @@ -0,0 +1,7 @@ +[tikv-importer] +backend = 'local' +disk-quota = '75MB' +max-kv-pairs = 50 + +[cron] +check-disk-quota = '1s' diff --git a/tests/lightning_disk_quota/data/disk_quota-schema-create.sql b/tests/lightning_disk_quota/data/disk_quota-schema-create.sql new file mode 100644 index 000000000..f8f98e5a7 --- /dev/null +++ b/tests/lightning_disk_quota/data/disk_quota-schema-create.sql @@ -0,0 +1 @@ +create schema disk_quota; diff --git a/tests/lightning_disk_quota/data/disk_quota.t-schema.sql b/tests/lightning_disk_quota/data/disk_quota.t-schema.sql new file mode 100644 index 000000000..335ff625d --- /dev/null +++ b/tests/lightning_disk_quota/data/disk_quota.t-schema.sql @@ -0,0 +1,12 @@ +create table t ( + id int not null primary key, + + -- each stored generated column occupy about 150 KB of data, so we are 750 KB per row. + -- without disk quota the engine size will be 750 KB * 2000 rows = 1.5 GB ≈ 1.4 GiB. + -- (FIXME: making the KV size too large may crash PD?) + sa longblob as (aes_encrypt(rpad(id, 150000, 'a'), 'xxx', 'iviviviviviviviv')) stored, + sb longblob as (aes_encrypt(rpad(id, 150000, 'b'), 'xxx', 'iviviviviviviviv')) stored, + sc longblob as (aes_encrypt(rpad(id, 150000, 'c'), 'xxx', 'iviviviviviviviv')) stored, + sd longblob as (aes_encrypt(rpad(id, 150000, 'd'), 'xxx', 'iviviviviviviviv')) stored, + se longblob as (aes_encrypt(rpad(id, 150000, 'e'), 'xxx', 'iviviviviviviviv')) stored +); diff --git a/tests/lightning_disk_quota/data/disk_quota.t.0.sql b/tests/lightning_disk_quota/data/disk_quota.t.0.sql new file mode 100644 index 000000000..1b441b25c --- /dev/null +++ b/tests/lightning_disk_quota/data/disk_quota.t.0.sql @@ -0,0 +1,51 @@ +insert into t (id) values +(0), (1), (2), (3), (4), (5), (6), (7), (8), (9), +(10), (11), (12), (13), (14), (15), (16), (17), (18), (19), +(20), (21), (22), (23), (24), (25), (26), (27), (28), (29), +(30), (31), (32), (33), (34), (35), (36), (37), (38), (39), +(40), (41), (42), (43), (44), (45), (46), (47), (48), (49), +(50), (51), (52), (53), (54), (55), (56), (57), (58), (59), +(60), (61), (62), (63), (64), (65), (66), (67), (68), (69), +(70), (71), (72), (73), (74), (75), (76), (77), (78), (79), +(80), (81), (82), (83), (84), (85), (86), (87), (88), (89), +(90), (91), (92), (93), (94), (95), (96), (97), (98), (99), +(100), (101), (102), (103), (104), (105), (106), (107), (108), (109), +(110), (111), (112), (113), (114), (115), (116), (117), (118), (119), +(120), (121), (122), (123), (124), (125), (126), (127), (128), (129), +(130), (131), (132), (133), (134), (135), (136), (137), (138), (139), +(140), (141), (142), (143), (144), (145), (146), (147), (148), (149), +(150), (151), (152), (153), (154), (155), (156), (157), (158), (159), +(160), (161), (162), (163), (164), (165), (166), (167), (168), (169), +(170), (171), (172), (173), (174), (175), (176), (177), (178), (179), +(180), (181), (182), (183), (184), (185), (186), (187), (188), (189), +(190), (191), (192), (193), (194), (195), (196), (197), (198), (199), +(200), (201), (202), (203), (204), (205), (206), (207), (208), (209), +(210), (211), (212), (213), (214), (215), (216), (217), (218), (219), +(220), (221), (222), (223), (224), (225), (226), (227), (228), (229), +(230), (231), (232), (233), (234), (235), (236), (237), (238), (239), +(240), (241), (242), (243), (244), (245), (246), (247), (248), (249), +(250), (251), (252), (253), (254), (255), (256), (257), (258), (259), +(260), (261), (262), (263), (264), (265), (266), (267), (268), (269), +(270), (271), (272), (273), (274), (275), (276), (277), (278), (279), +(280), (281), (282), (283), (284), (285), (286), (287), (288), (289), +(290), (291), (292), (293), (294), (295), (296), (297), (298), (299), +(300), (301), (302), (303), (304), (305), (306), (307), (308), (309), +(310), (311), (312), (313), (314), (315), (316), (317), (318), (319), +(320), (321), (322), (323), (324), (325), (326), (327), (328), (329), +(330), (331), (332), (333), (334), (335), (336), (337), (338), (339), +(340), (341), (342), (343), (344), (345), (346), (347), (348), (349), +(350), (351), (352), (353), (354), (355), (356), (357), (358), (359), +(360), (361), (362), (363), (364), (365), (366), (367), (368), (369), +(370), (371), (372), (373), (374), (375), (376), (377), (378), (379), +(380), (381), (382), (383), (384), (385), (386), (387), (388), (389), +(390), (391), (392), (393), (394), (395), (396), (397), (398), (399), +(400), (401), (402), (403), (404), (405), (406), (407), (408), (409), +(410), (411), (412), (413), (414), (415), (416), (417), (418), (419), +(420), (421), (422), (423), (424), (425), (426), (427), (428), (429), +(430), (431), (432), (433), (434), (435), (436), (437), (438), (439), +(440), (441), (442), (443), (444), (445), (446), (447), (448), (449), +(450), (451), (452), (453), (454), (455), (456), (457), (458), (459), +(460), (461), (462), (463), (464), (465), (466), (467), (468), (469), +(470), (471), (472), (473), (474), (475), (476), (477), (478), (479), +(480), (481), (482), (483), (484), (485), (486), (487), (488), (489), +(490), (491), (492), (493), (494), (495), (496), (497), (498), (499); diff --git a/tests/lightning_disk_quota/data/disk_quota.t.1.sql b/tests/lightning_disk_quota/data/disk_quota.t.1.sql new file mode 100644 index 000000000..63b5a57d3 --- /dev/null +++ b/tests/lightning_disk_quota/data/disk_quota.t.1.sql @@ -0,0 +1,51 @@ +insert into t (id) values +(500), (501), (502), (503), (504), (505), (506), (507), (508), (509), +(510), (511), (512), (513), (514), (515), (516), (517), (518), (519), +(520), (521), (522), (523), (524), (525), (526), (527), (528), (529), +(530), (531), (532), (533), (534), (535), (536), (537), (538), (539), +(540), (541), (542), (543), (544), (545), (546), (547), (548), (549), +(550), (551), (552), (553), (554), (555), (556), (557), (558), (559), +(560), (561), (562), (563), (564), (565), (566), (567), (568), (569), +(570), (571), (572), (573), (574), (575), (576), (577), (578), (579), +(580), (581), (582), (583), (584), (585), (586), (587), (588), (589), +(590), (591), (592), (593), (594), (595), (596), (597), (598), (599), +(600), (601), (602), (603), (604), (605), (606), (607), (608), (609), +(610), (611), (612), (613), (614), (615), (616), (617), (618), (619), +(620), (621), (622), (623), (624), (625), (626), (627), (628), (629), +(630), (631), (632), (633), (634), (635), (636), (637), (638), (639), +(640), (641), (642), (643), (644), (645), (646), (647), (648), (649), +(650), (651), (652), (653), (654), (655), (656), (657), (658), (659), +(660), (661), (662), (663), (664), (665), (666), (667), (668), (669), +(670), (671), (672), (673), (674), (675), (676), (677), (678), (679), +(680), (681), (682), (683), (684), (685), (686), (687), (688), (689), +(690), (691), (692), (693), (694), (695), (696), (697), (698), (699), +(700), (701), (702), (703), (704), (705), (706), (707), (708), (709), +(710), (711), (712), (713), (714), (715), (716), (717), (718), (719), +(720), (721), (722), (723), (724), (725), (726), (727), (728), (729), +(730), (731), (732), (733), (734), (735), (736), (737), (738), (739), +(740), (741), (742), (743), (744), (745), (746), (747), (748), (749), +(750), (751), (752), (753), (754), (755), (756), (757), (758), (759), +(760), (761), (762), (763), (764), (765), (766), (767), (768), (769), +(770), (771), (772), (773), (774), (775), (776), (777), (778), (779), +(780), (781), (782), (783), (784), (785), (786), (787), (788), (789), +(790), (791), (792), (793), (794), (795), (796), (797), (798), (799), +(800), (801), (802), (803), (804), (805), (806), (807), (808), (809), +(810), (811), (812), (813), (814), (815), (816), (817), (818), (819), +(820), (821), (822), (823), (824), (825), (826), (827), (828), (829), +(830), (831), (832), (833), (834), (835), (836), (837), (838), (839), +(840), (841), (842), (843), (844), (845), (846), (847), (848), (849), +(850), (851), (852), (853), (854), (855), (856), (857), (858), (859), +(860), (861), (862), (863), (864), (865), (866), (867), (868), (869), +(870), (871), (872), (873), (874), (875), (876), (877), (878), (879), +(880), (881), (882), (883), (884), (885), (886), (887), (888), (889), +(890), (891), (892), (893), (894), (895), (896), (897), (898), (899), +(900), (901), (902), (903), (904), (905), (906), (907), (908), (909), +(910), (911), (912), (913), (914), (915), (916), (917), (918), (919), +(920), (921), (922), (923), (924), (925), (926), (927), (928), (929), +(930), (931), (932), (933), (934), (935), (936), (937), (938), (939), +(940), (941), (942), (943), (944), (945), (946), (947), (948), (949), +(950), (951), (952), (953), (954), (955), (956), (957), (958), (959), +(960), (961), (962), (963), (964), (965), (966), (967), (968), (969), +(970), (971), (972), (973), (974), (975), (976), (977), (978), (979), +(980), (981), (982), (983), (984), (985), (986), (987), (988), (989), +(990), (991), (992), (993), (994), (995), (996), (997), (998), (999); diff --git a/tests/lightning_disk_quota/data/disk_quota.t.2.sql b/tests/lightning_disk_quota/data/disk_quota.t.2.sql new file mode 100644 index 000000000..57b739923 --- /dev/null +++ b/tests/lightning_disk_quota/data/disk_quota.t.2.sql @@ -0,0 +1,51 @@ +insert into t (id) values +(1000), (1001), (1002), (1003), (1004), (1005), (1006), (1007), (1008), (1009), +(1010), (1011), (1012), (1013), (1014), (1015), (1016), (1017), (1018), (1019), +(1020), (1021), (1022), (1023), (1024), (1025), (1026), (1027), (1028), (1029), +(1030), (1031), (1032), (1033), (1034), (1035), (1036), (1037), (1038), (1039), +(1040), (1041), (1042), (1043), (1044), (1045), (1046), (1047), (1048), (1049), +(1050), (1051), (1052), (1053), (1054), (1055), (1056), (1057), (1058), (1059), +(1060), (1061), (1062), (1063), (1064), (1065), (1066), (1067), (1068), (1069), +(1070), (1071), (1072), (1073), (1074), (1075), (1076), (1077), (1078), (1079), +(1080), (1081), (1082), (1083), (1084), (1085), (1086), (1087), (1088), (1089), +(1090), (1091), (1092), (1093), (1094), (1095), (1096), (1097), (1098), (1099), +(1100), (1101), (1102), (1103), (1104), (1105), (1106), (1107), (1108), (1109), +(1110), (1111), (1112), (1113), (1114), (1115), (1116), (1117), (1118), (1119), +(1120), (1121), (1122), (1123), (1124), (1125), (1126), (1127), (1128), (1129), +(1130), (1131), (1132), (1133), (1134), (1135), (1136), (1137), (1138), (1139), +(1140), (1141), (1142), (1143), (1144), (1145), (1146), (1147), (1148), (1149), +(1150), (1151), (1152), (1153), (1154), (1155), (1156), (1157), (1158), (1159), +(1160), (1161), (1162), (1163), (1164), (1165), (1166), (1167), (1168), (1169), +(1170), (1171), (1172), (1173), (1174), (1175), (1176), (1177), (1178), (1179), +(1180), (1181), (1182), (1183), (1184), (1185), (1186), (1187), (1188), (1189), +(1190), (1191), (1192), (1193), (1194), (1195), (1196), (1197), (1198), (1199), +(1200), (1201), (1202), (1203), (1204), (1205), (1206), (1207), (1208), (1209), +(1210), (1211), (1212), (1213), (1214), (1215), (1216), (1217), (1218), (1219), +(1220), (1221), (1222), (1223), (1224), (1225), (1226), (1227), (1228), (1229), +(1230), (1231), (1232), (1233), (1234), (1235), (1236), (1237), (1238), (1239), +(1240), (1241), (1242), (1243), (1244), (1245), (1246), (1247), (1248), (1249), +(1250), (1251), (1252), (1253), (1254), (1255), (1256), (1257), (1258), (1259), +(1260), (1261), (1262), (1263), (1264), (1265), (1266), (1267), (1268), (1269), +(1270), (1271), (1272), (1273), (1274), (1275), (1276), (1277), (1278), (1279), +(1280), (1281), (1282), (1283), (1284), (1285), (1286), (1287), (1288), (1289), +(1290), (1291), (1292), (1293), (1294), (1295), (1296), (1297), (1298), (1299), +(1300), (1301), (1302), (1303), (1304), (1305), (1306), (1307), (1308), (1309), +(1310), (1311), (1312), (1313), (1314), (1315), (1316), (1317), (1318), (1319), +(1320), (1321), (1322), (1323), (1324), (1325), (1326), (1327), (1328), (1329), +(1330), (1331), (1332), (1333), (1334), (1335), (1336), (1337), (1338), (1339), +(1340), (1341), (1342), (1343), (1344), (1345), (1346), (1347), (1348), (1349), +(1350), (1351), (1352), (1353), (1354), (1355), (1356), (1357), (1358), (1359), +(1360), (1361), (1362), (1363), (1364), (1365), (1366), (1367), (1368), (1369), +(1370), (1371), (1372), (1373), (1374), (1375), (1376), (1377), (1378), (1379), +(1380), (1381), (1382), (1383), (1384), (1385), (1386), (1387), (1388), (1389), +(1390), (1391), (1392), (1393), (1394), (1395), (1396), (1397), (1398), (1399), +(1400), (1401), (1402), (1403), (1404), (1405), (1406), (1407), (1408), (1409), +(1410), (1411), (1412), (1413), (1414), (1415), (1416), (1417), (1418), (1419), +(1420), (1421), (1422), (1423), (1424), (1425), (1426), (1427), (1428), (1429), +(1430), (1431), (1432), (1433), (1434), (1435), (1436), (1437), (1438), (1439), +(1440), (1441), (1442), (1443), (1444), (1445), (1446), (1447), (1448), (1449), +(1450), (1451), (1452), (1453), (1454), (1455), (1456), (1457), (1458), (1459), +(1460), (1461), (1462), (1463), (1464), (1465), (1466), (1467), (1468), (1469), +(1470), (1471), (1472), (1473), (1474), (1475), (1476), (1477), (1478), (1479), +(1480), (1481), (1482), (1483), (1484), (1485), (1486), (1487), (1488), (1489), +(1490), (1491), (1492), (1493), (1494), (1495), (1496), (1497), (1498), (1499); diff --git a/tests/lightning_disk_quota/data/disk_quota.t.3.sql b/tests/lightning_disk_quota/data/disk_quota.t.3.sql new file mode 100644 index 000000000..58bf50641 --- /dev/null +++ b/tests/lightning_disk_quota/data/disk_quota.t.3.sql @@ -0,0 +1,51 @@ +insert into t (id) values +(1500), (1501), (1502), (1503), (1504), (1505), (1506), (1507), (1508), (1509), +(1510), (1511), (1512), (1513), (1514), (1515), (1516), (1517), (1518), (1519), +(1520), (1521), (1522), (1523), (1524), (1525), (1526), (1527), (1528), (1529), +(1530), (1531), (1532), (1533), (1534), (1535), (1536), (1537), (1538), (1539), +(1540), (1541), (1542), (1543), (1544), (1545), (1546), (1547), (1548), (1549), +(1550), (1551), (1552), (1553), (1554), (1555), (1556), (1557), (1558), (1559), +(1560), (1561), (1562), (1563), (1564), (1565), (1566), (1567), (1568), (1569), +(1570), (1571), (1572), (1573), (1574), (1575), (1576), (1577), (1578), (1579), +(1580), (1581), (1582), (1583), (1584), (1585), (1586), (1587), (1588), (1589), +(1590), (1591), (1592), (1593), (1594), (1595), (1596), (1597), (1598), (1599), +(1600), (1601), (1602), (1603), (1604), (1605), (1606), (1607), (1608), (1609), +(1610), (1611), (1612), (1613), (1614), (1615), (1616), (1617), (1618), (1619), +(1620), (1621), (1622), (1623), (1624), (1625), (1626), (1627), (1628), (1629), +(1630), (1631), (1632), (1633), (1634), (1635), (1636), (1637), (1638), (1639), +(1640), (1641), (1642), (1643), (1644), (1645), (1646), (1647), (1648), (1649), +(1650), (1651), (1652), (1653), (1654), (1655), (1656), (1657), (1658), (1659), +(1660), (1661), (1662), (1663), (1664), (1665), (1666), (1667), (1668), (1669), +(1670), (1671), (1672), (1673), (1674), (1675), (1676), (1677), (1678), (1679), +(1680), (1681), (1682), (1683), (1684), (1685), (1686), (1687), (1688), (1689), +(1690), (1691), (1692), (1693), (1694), (1695), (1696), (1697), (1698), (1699), +(1700), (1701), (1702), (1703), (1704), (1705), (1706), (1707), (1708), (1709), +(1710), (1711), (1712), (1713), (1714), (1715), (1716), (1717), (1718), (1719), +(1720), (1721), (1722), (1723), (1724), (1725), (1726), (1727), (1728), (1729), +(1730), (1731), (1732), (1733), (1734), (1735), (1736), (1737), (1738), (1739), +(1740), (1741), (1742), (1743), (1744), (1745), (1746), (1747), (1748), (1749), +(1750), (1751), (1752), (1753), (1754), (1755), (1756), (1757), (1758), (1759), +(1760), (1761), (1762), (1763), (1764), (1765), (1766), (1767), (1768), (1769), +(1770), (1771), (1772), (1773), (1774), (1775), (1776), (1777), (1778), (1779), +(1780), (1781), (1782), (1783), (1784), (1785), (1786), (1787), (1788), (1789), +(1790), (1791), (1792), (1793), (1794), (1795), (1796), (1797), (1798), (1799), +(1800), (1801), (1802), (1803), (1804), (1805), (1806), (1807), (1808), (1809), +(1810), (1811), (1812), (1813), (1814), (1815), (1816), (1817), (1818), (1819), +(1820), (1821), (1822), (1823), (1824), (1825), (1826), (1827), (1828), (1829), +(1830), (1831), (1832), (1833), (1834), (1835), (1836), (1837), (1838), (1839), +(1840), (1841), (1842), (1843), (1844), (1845), (1846), (1847), (1848), (1849), +(1850), (1851), (1852), (1853), (1854), (1855), (1856), (1857), (1858), (1859), +(1860), (1861), (1862), (1863), (1864), (1865), (1866), (1867), (1868), (1869), +(1870), (1871), (1872), (1873), (1874), (1875), (1876), (1877), (1878), (1879), +(1880), (1881), (1882), (1883), (1884), (1885), (1886), (1887), (1888), (1889), +(1890), (1891), (1892), (1893), (1894), (1895), (1896), (1897), (1898), (1899), +(1900), (1901), (1902), (1903), (1904), (1905), (1906), (1907), (1908), (1909), +(1910), (1911), (1912), (1913), (1914), (1915), (1916), (1917), (1918), (1919), +(1920), (1921), (1922), (1923), (1924), (1925), (1926), (1927), (1928), (1929), +(1930), (1931), (1932), (1933), (1934), (1935), (1936), (1937), (1938), (1939), +(1940), (1941), (1942), (1943), (1944), (1945), (1946), (1947), (1948), (1949), +(1950), (1951), (1952), (1953), (1954), (1955), (1956), (1957), (1958), (1959), +(1960), (1961), (1962), (1963), (1964), (1965), (1966), (1967), (1968), (1969), +(1970), (1971), (1972), (1973), (1974), (1975), (1976), (1977), (1978), (1979), +(1980), (1981), (1982), (1983), (1984), (1985), (1986), (1987), (1988), (1989), +(1990), (1991), (1992), (1993), (1994), (1995), (1996), (1997), (1998), (1999); diff --git a/tests/lightning_disk_quota/run.sh b/tests/lightning_disk_quota/run.sh new file mode 100644 index 000000000..12309f16f --- /dev/null +++ b/tests/lightning_disk_quota/run.sh @@ -0,0 +1,74 @@ +#!/bin/sh +# +# Copyright 2020 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eux + +check_cluster_version 4 0 0 'local backend' || exit 0 + +# the default mode (aes-128-ecb) can be easily compressed, switch to cbc to reduce the compression effect. +run_sql 'DROP DATABASE IF EXISTS disk_quota;' +run_sql "SELECT @@block_encryption_mode" +OLD_ENCRYPTION_MODE=$(read_result) +run_sql "SET GLOBAL block_encryption_mode = 'aes-256-cbc';" + +DISK_QUOTA_DIR="$TEST_DIR/with-disk-quota" +FINISHED_FILE="$TEST_DIR/sorted-with-disk-quota.finished" + +mkdir -p "$DISK_QUOTA_DIR" +rm -f "$FINISHED_FILE" +cleanup() { + touch "$FINISHED_FILE" + run_sql "SET GLOBAL block_encryption_mode = '$OLD_ENCRYPTION_MODE';" +} +trap cleanup EXIT + +# There is normally a 2 second delay between these SET GLOBAL statements returns +# and the changes are actually effective. So we have this check-and-retry loop +# below to ensure Lightning gets our desired global vars. +for i in $(seq 3); do + sleep 1 + run_sql "SELECT @@block_encryption_mode" + if [ "$(read_result)" = 'aes-256-cbc' ]; then + break + fi +done + +while [ ! -e "$FINISHED_FILE" ]; do + DISK_USAGE=$(du -s -B1 "$DISK_QUOTA_DIR" | cut -f 1) + # the disk quota of 75 MiB is a just soft limit. + # the reserved size we have is (512 MiB + 4 files × 1000ms × 1 KiB/ms) = 516 MiB, + # which sums up to 591 MiB as the hard limit. + if [ "0$DISK_USAGE" -gt 619610112 ]; then + echo "hard disk quota exceeded, actual size = $DISK_USAGE" > "$FINISHED_FILE" + break + else + sleep 1 + fi +done & + +export GO_FAILPOINTS="github.com/pingcap/br/pkg/lightning/restore/SlowDownWriteRows=sleep(500)" +run_lightning --sorted-kv-dir "$DISK_QUOTA_DIR/sorted" --log-file "$TEST_DIR/lightning-disk-quota.log" +touch "$FINISHED_FILE" +# if $FINISHED_FILE has content, it is only because the hard disk quota is exceeded. +[ -s "$FINISHED_FILE" ] && cat "$FINISHED_FILE" && exit 1 + +# check that disk quota is indeed triggered. +grep -q 'disk quota exceeded' "$TEST_DIR/lightning-disk-quota.log" + +# check that the columns are correct. +run_sql "select cast(trim(trailing 'a' from aes_decrypt(sa, 'xxx', 'iviviviviviviviv')) as char) a from disk_quota.t where id = 1357" +check_contains 'a: 1357' +run_sql "select cast(trim(trailing 'e' from aes_decrypt(se, 'xxx', 'iviviviviviviviv')) as char) e from disk_quota.t where id = 246" +check_contains 'e: 246' diff --git a/tests/lightning_error_summary/config.toml b/tests/lightning_error_summary/config.toml new file mode 100644 index 000000000..5943184e1 --- /dev/null +++ b/tests/lightning_error_summary/config.toml @@ -0,0 +1,4 @@ +[checkpoint] +enable = true +schema = "tidb_lightning_checkpoint_error_summary" +driver = "mysql" diff --git a/tests/lightning_error_summary/data/error_summary-schema-create.sql b/tests/lightning_error_summary/data/error_summary-schema-create.sql new file mode 100644 index 000000000..3f2f251c0 --- /dev/null +++ b/tests/lightning_error_summary/data/error_summary-schema-create.sql @@ -0,0 +1 @@ +CREATE DATABASE error_summary; diff --git a/tests/lightning_error_summary/data/error_summary.a-schema.sql b/tests/lightning_error_summary/data/error_summary.a-schema.sql new file mode 100644 index 000000000..9194930f6 --- /dev/null +++ b/tests/lightning_error_summary/data/error_summary.a-schema.sql @@ -0,0 +1,4 @@ +CREATE TABLE a( + id INT NOT NULL PRIMARY KEY, + k INT NOT NULL +); diff --git a/tests/lightning_error_summary/data/error_summary.a.sql b/tests/lightning_error_summary/data/error_summary.a.sql new file mode 100644 index 000000000..a2c1003db --- /dev/null +++ b/tests/lightning_error_summary/data/error_summary.a.sql @@ -0,0 +1 @@ +INSERT INTO a (id, k) VALUES (2, 3), (5, 7); diff --git a/tests/lightning_error_summary/data/error_summary.b-schema.sql b/tests/lightning_error_summary/data/error_summary.b-schema.sql new file mode 100644 index 000000000..77020697e --- /dev/null +++ b/tests/lightning_error_summary/data/error_summary.b-schema.sql @@ -0,0 +1,4 @@ +CREATE TABLE b( + id INT NOT NULL PRIMARY KEY, + k INT NOT NULL +); diff --git a/tests/lightning_error_summary/data/error_summary.b.sql b/tests/lightning_error_summary/data/error_summary.b.sql new file mode 100644 index 000000000..b79d523ee --- /dev/null +++ b/tests/lightning_error_summary/data/error_summary.b.sql @@ -0,0 +1 @@ +INSERT INTO b (id, k) VALUES (11, 13), (17, 19); diff --git a/tests/lightning_error_summary/data/error_summary.c-schema.sql b/tests/lightning_error_summary/data/error_summary.c-schema.sql new file mode 100644 index 000000000..31432c732 --- /dev/null +++ b/tests/lightning_error_summary/data/error_summary.c-schema.sql @@ -0,0 +1,4 @@ +CREATE TABLE c( + id INT NOT NULL PRIMARY KEY, + k INT NOT NULL +); diff --git a/tests/lightning_error_summary/data/error_summary.c.sql b/tests/lightning_error_summary/data/error_summary.c.sql new file mode 100644 index 000000000..be11c04ab --- /dev/null +++ b/tests/lightning_error_summary/data/error_summary.c.sql @@ -0,0 +1 @@ +INSERT INTO c VALUES (10, 100), (1000, 10000); diff --git a/tests/lightning_error_summary/run.sh b/tests/lightning_error_summary/run.sh new file mode 100755 index 000000000..3e3e0495b --- /dev/null +++ b/tests/lightning_error_summary/run.sh @@ -0,0 +1,64 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eux + +# Check that error summary are written at the bottom of import. +run_sql 'DROP DATABASE IF EXISTS tidb_lightning_checkpoint_error_summary;' + +# The easiest way to induce error is to prepopulate the target table with conflicting content. +run_sql 'CREATE DATABASE IF NOT EXISTS error_summary;' +run_sql 'DROP TABLE IF EXISTS error_summary.a;' +run_sql 'DROP TABLE IF EXISTS error_summary.c;' +run_sql 'CREATE TABLE error_summary.a (id INT NOT NULL PRIMARY KEY, k INT NOT NULL);' +run_sql 'CREATE TABLE error_summary.c (id INT NOT NULL PRIMARY KEY, k INT NOT NULL);' +run_sql 'INSERT INTO error_summary.a VALUES (2, 4), (6, 8);' +run_sql 'INSERT INTO error_summary.c VALUES (3, 9), (27, 81);' + +set +e +run_lightning --enable-checkpoint=1 --log-file "$TEST_DIR/lightning-error-summary.log" +ERRORCODE=$? +set -e + +[ "$ERRORCODE" -ne 0 ] + +# Verify that table `b` is indeed imported +run_sql 'SELECT sum(id), sum(k) FROM error_summary.b' +check_contains 'sum(id): 28' +check_contains 'sum(k): 32' + +# Verify the log contains the expected messages at the last few lines +tail -20 "$TEST_DIR/lightning-error-summary.log" > "$TEST_DIR/lightning-error-summary.tail" +grep -Fq '["tables failed to be imported"] [count=2]' "$TEST_DIR/lightning-error-summary.tail" +grep -Fq '[-] [table=`error_summary`.`a`] [status=checksum] [error="checksum mismatched' "$TEST_DIR/lightning-error-summary.tail" +grep -Fq '[-] [table=`error_summary`.`c`] [status=checksum] [error="checksum mismatched' "$TEST_DIR/lightning-error-summary.tail" +! grep -Fq '[-] [table=`error_summary`.`b`] [status=checksum] [error="checksum mismatched' "$TEST_DIR/lightning-error-summary.tail" + +# Now check the error log when the checkpoint is not cleaned. + +set +e +run_lightning --enable-checkpoint=1 --log-file "$TEST_DIR/lightning-error-summary.log" +ERRORCODE=$? +set -e + +[ "$ERRORCODE" -ne 0 ] + +tail -20 "$TEST_DIR/lightning-error-summary.log" > "$TEST_DIR/lightning-error-summary.tail" +grep -Fq '["TiDB Lightning has failed last time. To prevent data loss, this run will stop now. Please resolve errors first"] [count=2]' "$TEST_DIR/lightning-error-summary.tail" +grep -Fq '[-] [table=`error_summary`.`a`] [status=18] [failedStep=checksum] [recommendedAction="./tidb-lightning-ctl --checkpoint-error-destroy='"'"'`error_summary`.`a`'"'"' --config=..."]' "$TEST_DIR/lightning-error-summary.tail" +grep -Fq '[-] [table=`error_summary`.`c`] [status=18] [failedStep=checksum] [recommendedAction="./tidb-lightning-ctl --checkpoint-error-destroy='"'"'`error_summary`.`c`'"'"' --config=..."]' "$TEST_DIR/lightning-error-summary.tail" +! grep -Fq '[-] [table=`error_summary`.`b`] [status=18] [failedStep=checksum]' "$TEST_DIR/lightning-error-summary.tail" +grep -Fq '["You may also run `./tidb-lightning-ctl --checkpoint-error-destroy=all --config=...` to start from scratch"]' "$TEST_DIR/lightning-error-summary.tail" +grep -Fq '["For details of this failure, read the log file from the PREVIOUS run"]' "$TEST_DIR/lightning-error-summary.tail" diff --git a/tests/lightning_examples/1.toml b/tests/lightning_examples/1.toml new file mode 100644 index 000000000..1b7d5833c --- /dev/null +++ b/tests/lightning_examples/1.toml @@ -0,0 +1,6 @@ +[lightning] +table-concurrency = 1 +level = "warning" + +[mydumper] +read-block-size = 1 diff --git a/tests/lightning_examples/131072.toml b/tests/lightning_examples/131072.toml new file mode 100644 index 000000000..a03dbacac --- /dev/null +++ b/tests/lightning_examples/131072.toml @@ -0,0 +1,6 @@ +[lightning] +table-concurrency = 1 +level = "warning" + +[mydumper] +read-block-size = 131072 diff --git a/tests/lightning_examples/512.toml b/tests/lightning_examples/512.toml new file mode 100644 index 000000000..b25a46f50 --- /dev/null +++ b/tests/lightning_examples/512.toml @@ -0,0 +1,6 @@ +[lightning] +table-concurrency = 1 +level = "warning" + +[mydumper] +read-block-size = 512 diff --git a/tests/lightning_examples/run.sh b/tests/lightning_examples/run.sh new file mode 100755 index 000000000..53626b7c7 --- /dev/null +++ b/tests/lightning_examples/run.sh @@ -0,0 +1,98 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eu + +EXAMPLES_PATH=pkg/lightning/mydump/examples + +# Because of issue JENKINS-45544 we can't use the Unicode filename in the +# examples. We are going to rename it in-place. +do_rename() { + mv "$EXAMPLES_PATH/mocker_test.$1-schema.sql" "$EXAMPLES_PATH/mocker_test.$2-schema.sql" + mv "$EXAMPLES_PATH/mocker_test.$1.sql" "$EXAMPLES_PATH/mocker_test.$2.sql" +} +do_rename i ı +undo_rename() { + do_rename ı i +} +trap undo_rename EXIT + +do_run_lightning() { + run_lightning -d $EXAMPLES_PATH --config "tests/$TEST_NAME/$1.toml" +} + +# Perform the import +run_sql 'DROP DATABASE IF EXISTS mocker_test;' +do_run_lightning 512 + +# The existing reader_test +run_sql 'use mocker_test; select count(distinct ID) cnt from `tbl_autoid`' +check_contains 'cnt: 10000' +run_sql 'use mocker_test; select count(distinct Name) cnt from `tbl_multi_index`' +check_contains 'cnt: 10000' + +# Check if rest of the imported data really match +run_sql 'SELECT * FROM mocker_test.ı LIMIT 2' +check_not_contains '* 2. row *' +check_contains 'Å¿: 🤪' + +run_sql 'SELECT * FROM mocker_test.report_case_high_risk LIMIT 2' +check_not_contains '* 2. row *' +check_contains 'id: 2' +check_contains 'report_data: 4' +check_contains 'caseType: 6' +check_contains 'total_case: 8' +check_contains 'today_new_case: 10' + +run_sql 'select count(*), sum(id), max(name), min(name), sum(crc32(name)) from mocker_test.tbl_autoid;' +check_contains 'count(*): 10000' +check_contains 'sum(id): 50005000' +check_contains 'max(name): 4-9-9' +check_contains 'min(name): 0-0-0' +check_contains 'sum(crc32(name)): 21388950023608' + +# Ensure the AUTO_INCREMENT value is properly defined +run_sql "insert into mocker_test.tbl_autoid (name) values ('new');" +run_sql "select id > 10000 from mocker_test.tbl_autoid where name = 'new';" +check_not_contains '* 2. row *' +check_contains 'id > 10000: 1' + +run_sql 'select count(*), avg(age), max(name), min(name), sum(crc32(name)) from mocker_test.tbl_multi_index;' +check_contains 'count(*): 10000' +check_contains 'avg(age): 477.7500' +check_contains 'max(name): 4+9+9' +check_contains 'min(name): 0+0+0' +check_contains 'sum(crc32(name)): 21433704622808' + +# Ensure the indices are intact +run_sql "select age from mocker_test.tbl_multi_index where name = '1+2+3'" +check_contains 'age: 6' +run_sql "select count(*) from mocker_test.tbl_multi_index where age = 6" +check_contains 'count(*): 20' + +# Rest of the existing reader_test +run_sql 'DROP DATABASE mocker_test;' +do_run_lightning 1 +run_sql 'use mocker_test; select count(distinct ID) cnt from `tbl_autoid`' +check_contains 'cnt: 10000' +run_sql 'use mocker_test; select count(distinct Name) cnt from `tbl_multi_index`' +check_contains 'cnt: 10000' + +run_sql 'DROP DATABASE mocker_test;' +do_run_lightning 131072 +run_sql 'use mocker_test; select count(distinct ID) cnt from `tbl_autoid`' +check_contains 'cnt: 10000' +run_sql 'use mocker_test; select count(distinct Name) cnt from `tbl_multi_index`' +check_contains 'cnt: 10000' diff --git a/tests/lightning_exotic_filenames/config.toml b/tests/lightning_exotic_filenames/config.toml new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_exotic_filenames/data/xfn-schema-create.sql b/tests/lightning_exotic_filenames/data/xfn-schema-create.sql new file mode 100644 index 000000000..1fb10bd2e --- /dev/null +++ b/tests/lightning_exotic_filenames/data/xfn-schema-create.sql @@ -0,0 +1 @@ +create database `x``f"n`; \ No newline at end of file diff --git a/tests/lightning_exotic_filenames/data/xfn.etn-schema.sql b/tests/lightning_exotic_filenames/data/xfn.etn-schema.sql new file mode 100644 index 000000000..e2d94bbdf --- /dev/null +++ b/tests/lightning_exotic_filenames/data/xfn.etn-schema.sql @@ -0,0 +1 @@ +create table `exotic``table````name` (a varchar(6) primary key, b int unique auto_increment) auto_increment=80000; \ No newline at end of file diff --git a/tests/lightning_exotic_filenames/data/xfn.etn.sql b/tests/lightning_exotic_filenames/data/xfn.etn.sql new file mode 100644 index 000000000..e0341a7ac --- /dev/null +++ b/tests/lightning_exotic_filenames/data/xfn.etn.sql @@ -0,0 +1,7 @@ +insert `exotic``table````name` (a, b, _tidb_rowid) values +('aaaaaa', 11, 79995), +('bbbbbb', 22, 79996); +insert `exotic``table````name` (a, b, _tidb_rowid) values +('cccccc', 33, 79997), +('dddddd', 44, 79998), +('eeeeee', 55, 79999); diff --git a/tests/lightning_exotic_filenames/data/zwk-schema-create.sql b/tests/lightning_exotic_filenames/data/zwk-schema-create.sql new file mode 100644 index 000000000..2f876c6ee --- /dev/null +++ b/tests/lightning_exotic_filenames/data/zwk-schema-create.sql @@ -0,0 +1 @@ +create database `中文庫`; diff --git a/tests/lightning_exotic_filenames/data/zwk.zwb-schema.sql b/tests/lightning_exotic_filenames/data/zwk.zwb-schema.sql new file mode 100644 index 000000000..449584777 --- /dev/null +++ b/tests/lightning_exotic_filenames/data/zwk.zwb-schema.sql @@ -0,0 +1 @@ +create table 中文表(a int primary key); diff --git a/tests/lightning_exotic_filenames/data/zwk.zwb.sql b/tests/lightning_exotic_filenames/data/zwk.zwb.sql new file mode 100644 index 000000000..af77ea8c2 --- /dev/null +++ b/tests/lightning_exotic_filenames/data/zwk.zwb.sql @@ -0,0 +1 @@ +insert into 中文表 values (2345); diff --git a/tests/lightning_exotic_filenames/run.sh b/tests/lightning_exotic_filenames/run.sh new file mode 100755 index 000000000..eb304b984 --- /dev/null +++ b/tests/lightning_exotic_filenames/run.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eu + +# rebuild the directory and rename the files to use exotic file names. +# (need to do it at runtime but otherwise git behaves erratically on windows) +DBPATH="$TEST_DIR/exotic_filename.mydump" +mkdir -p "$DBPATH" +cp "tests/$TEST_NAME/data/zwk-schema-create.sql" "$DBPATH/中文庫-schema-create.sql" +cp "tests/$TEST_NAME/data/zwk.zwb-schema.sql" "$DBPATH/中文庫.中文表-schema.sql" +cp "tests/$TEST_NAME/data/zwk.zwb.sql" "$DBPATH/中文庫.中文表.sql" +cp "tests/$TEST_NAME/data/xfn-schema-create.sql" "$DBPATH/"'x`f"n-schema-create.sql' +cp "tests/$TEST_NAME/data/xfn.etn-schema.sql" "$DBPATH/"'x`f"n.exotic`table``name-schema.sql' +cp "tests/$TEST_NAME/data/xfn.etn.sql" "$DBPATH/"'x`f"n.exotic`table``name.sql' + +run_sql 'DROP DATABASE IF EXISTS `x``f"n`;' +run_sql 'DROP DATABASE IF EXISTS `中文庫`;' +run_lightning -d "$DBPATH" +echo 'Import finished' + +run_sql 'SELECT count(*) FROM `x``f"n`.`exotic``table````name`' +check_contains 'count(*): 5' +run_sql 'INSERT INTO `x``f"n`.`exotic``table````name` (a) VALUES ("ffffff"), ("gggggg")' +run_sql 'SELECT _tidb_rowid > 80000, b > 80000 FROM `x``f"n`.`exotic``table````name` WHERE a = "ffffff"' +check_contains '_tidb_rowid > 80000: 1' +check_contains 'b > 80000: 1' +run_sql 'SELECT _tidb_rowid > 80000, b > 80000 FROM `x``f"n`.`exotic``table````name` WHERE a = "gggggg"' +check_contains '_tidb_rowid > 80000: 1' +check_contains 'b > 80000: 1' + +run_sql 'SELECT * FROM `中文庫`.中文表' +check_contains 'a: 2345' diff --git a/tests/lightning_file_routing/config.toml b/tests/lightning_file_routing/config.toml new file mode 100644 index 000000000..b837614aa --- /dev/null +++ b/tests/lightning_file_routing/config.toml @@ -0,0 +1,43 @@ +[lightning] +table-concurrency = 1 + +[checkpoint] +enable = false + +[mydumper] +default-file-rules = false + +[[mydumper.files]] +pattern = "(?i)^(?:[^/]*/)*([a-z0-9]+)/schema\\.sql$" +schema = "$1" +type = "schema-schema" + +[[mydumper.files]] +pattern = "(?i)^(?:[^/]*/)*([a-z0-9]+)/([a-z0-9]+)-table\\.sql$" +schema = "$1" +table = "$2" +type = "table-schema" + +[[mydumper.files]] +pattern = "(?i)^(?:[^/]*/)*([a-z0-9]+)/([a-z0-9]+)-view\\.sql$" +schema = "$1" +table = "$2" +type = "view-schema" + +[[mydumper.files]] +path = "ff/test.SQL" +schema = "fr" +table = "tbl" +type = "sql" + +[[mydumper.files]] +pattern = "(?i)^(?:[^/]*/)*([^./]+)/([a-z]+)[0-9]*\\.(sql|csv)(?:\\.([0-9]+))?$" +schema = "$1" +table = "$2" +type = "$3" + +[[mydumper.files]] +pattern = "(?i)^(?:[^/]*/)*([a-z]+)[0-9]*\\.(sql|csv)(?:\\.([0-9]+))?$" +schema = "fr" +table = "$1" +type = "$2" diff --git a/tests/lightning_file_routing/run.sh b/tests/lightning_file_routing/run.sh new file mode 100755 index 000000000..aefd760e0 --- /dev/null +++ b/tests/lightning_file_routing/run.sh @@ -0,0 +1,68 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euE + +# Populate the mydumper source +DBPATH="$TEST_DIR/fr.mydump" + +mkdir -p $DBPATH $DBPATH/fr $DBPATH/ff +echo 'CREATE DATABASE fr;' > "$DBPATH/fr/schema.sql" +echo "CREATE TABLE tbl(i TINYINT PRIMARY KEY, j INT);" > "$DBPATH/fr/tbl-table.sql" +# the column orders in data file is different from table schema order. +echo "INSERT INTO tbl (i, j) VALUES (1, 1),(2, 2);" > "$DBPATH/fr/tbl1.sql.0" +echo "INSERT INTO tbl (i, j) VALUES (3, 3),(4, 4);" > "$DBPATH/fr/tbl2.sql.0" +echo "INSERT INTO tbl (i, j) VALUES (5, 5);" > "$DBPATH/fr/tbl.sql" +echo "INSERT INTO tbl (i, j) VALUES (6, 6), (7, 7), (8, 8), (9, 9);" > "$DBPATH/tbl1.sql.1" +echo "INSERT INTO tbl (i, j) VALUES (10, 10);" > "$DBPATH/ff/test.SQL" +echo "INSERT INTO tbl (i, j) VALUES (11, 11);" > "$DBPATH/fr/tbl-noused.sql" + +# view schema +echo "CREATE TABLE v(i TINYINT);" > "$DBPATH/fr/v-table.sql" +cat > "$DBPATH/fr/v-view.sql" << '_EOF_' +/*!40101 SET NAMES binary*/; +DROP TABLE IF EXISTS `v`; +DROP VIEW IF EXISTS `v`; +SET @PREV_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT; +SET @PREV_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS; +SET @PREV_COLLATION_CONNECTION=@@COLLATION_CONNECTION; +SET character_set_client = utf8; +SET character_set_results = utf8; +SET collation_connection = utf8_general_ci; +CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`192.168.198.178` SQL SECURITY DEFINER VIEW `v` (`i`) AS SELECT `i` FROM `fr`.`tbl` WHERE i <= 5; +SET character_set_client = @PREV_CHARACTER_SET_CLIENT; +SET character_set_results = @PREV_CHARACTER_SET_RESULTS; +SET collation_connection = @PREV_COLLATION_CONNECTION; +_EOF_ + +for BACKEND in local importer; do + if [ "$BACKEND" = 'local' ]; then + check_cluster_version 4 0 0 'local backend' || continue + fi + + run_sql 'DROP DATABASE IF EXISTS fr' + + # Start importing the tables. + run_lightning -d "$DBPATH" --backend $BACKEND 2> /dev/null + + run_sql 'SELECT count(*) FROM `fr`.tbl' + check_contains "count(*): 10" + run_sql 'SELECT sum(j) FROM `fr`.tbl' + check_contains "sum(j): 55" + + run_sql 'SELECT sum(i), count(*) FROM `fr`.v' + check_contains "sum(i): 15" + check_contains "count(*): 5" +done diff --git a/tests/lightning_generated_columns/config.toml b/tests/lightning_generated_columns/config.toml new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_generated_columns/data/gencol-schema-create.sql b/tests/lightning_generated_columns/data/gencol-schema-create.sql new file mode 100644 index 000000000..67db527ca --- /dev/null +++ b/tests/lightning_generated_columns/data/gencol-schema-create.sql @@ -0,0 +1 @@ +create schema gencol; \ No newline at end of file diff --git a/tests/lightning_generated_columns/data/gencol.nested-schema.sql b/tests/lightning_generated_columns/data/gencol.nested-schema.sql new file mode 100644 index 000000000..7d5fd1f61 --- /dev/null +++ b/tests/lightning_generated_columns/data/gencol.nested-schema.sql @@ -0,0 +1,7 @@ +create table nested ( + a int not null primary key, + b int as (a + 1) virtual unique, + c int as (b + 1) stored unique, + d int as (c + 1) virtual unique, + e int as (d + 1) stored unique +); diff --git a/tests/lightning_generated_columns/data/gencol.nested.0.sql b/tests/lightning_generated_columns/data/gencol.nested.0.sql new file mode 100644 index 000000000..3e4339426 --- /dev/null +++ b/tests/lightning_generated_columns/data/gencol.nested.0.sql @@ -0,0 +1 @@ +insert into nested (a) values (1), (10), (100), (1000); \ No newline at end of file diff --git a/tests/lightning_generated_columns/data/gencol.various_types-schema.sql b/tests/lightning_generated_columns/data/gencol.various_types-schema.sql new file mode 100644 index 000000000..a71cd79ed --- /dev/null +++ b/tests/lightning_generated_columns/data/gencol.various_types-schema.sql @@ -0,0 +1,19 @@ +create table various_types ( + int64 bigint as (1 + 2) stored, + uint64 bigint unsigned as (pow(7, 8)) stored, -- 5764801 + float32 float as (9 / 16) stored, + float64 double as (5e222) stored, + string text as (sha1(repeat('x', uint64))) stored, -- '6ad8402ba6610f04d3ec5c9875489a7bc8e259c5' + bytes blob as (unhex(string)) stored, + `decimal` decimal(8, 4) as (1234.5678) stored, + duration time as ('1:2:3') stored, + enum enum('a','b','c') as ('c') stored, + bit bit(4) as (int64) stored, + `set` set('a','b','c') as (enum) stored, + time timestamp(3) as ('1987-06-05 04:03:02.100') stored, + json json as (json_object(string, float32)) stored, + aes blob as (aes_encrypt(`decimal`, 'key', bytes)) stored, -- 0xA876B03CFC8AF93D22D19E2220BD2375, @@block_encryption_mode='aes-256-cbc' + -- FIXME: column below disabled due to pingcap/tidb#21510 + -- week int as (week('2020-02-02')) stored, -- 6, @@default_week_format=4 + tz varchar(20) as (from_unixtime(1)) stored -- 1969-12-31 16:00:01, @@time_zone='-08:00' +); diff --git a/tests/lightning_generated_columns/data/gencol.various_types.0.sql b/tests/lightning_generated_columns/data/gencol.various_types.0.sql new file mode 100644 index 000000000..dc50f91a3 --- /dev/null +++ b/tests/lightning_generated_columns/data/gencol.various_types.0.sql @@ -0,0 +1 @@ +insert into various_types () values (); \ No newline at end of file diff --git a/tests/lightning_generated_columns/run.sh b/tests/lightning_generated_columns/run.sh new file mode 100644 index 000000000..2e0ac3864 --- /dev/null +++ b/tests/lightning_generated_columns/run.sh @@ -0,0 +1,71 @@ +#!/bin/sh +# +# Copyright 2020 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eux + +run_sql "SELECT CONCAT('SET GLOBAL time_zone=''', @@time_zone, ''', GLOBAL default_week_format=', @@default_week_format, ', GLOBAL block_encryption_mode=''', @@block_encryption_mode, ''';') cmd;" +UNDO_CMD=$(read_result) +undo_set_globals() { + run_sql "$UNDO_CMD" +} +trap undo_set_globals EXIT + +# There is normally a 2 second delay between these SET GLOBAL statements returns +# and the changes are actually effective. So we have this check-and-retry loop +# below to ensure Lightning gets our desired global vars. +run_sql "SET GLOBAL time_zone='-08:00', GLOBAL default_week_format=4, GLOBAL block_encryption_mode='aes-256-cbc'" +for i in $(seq 3); do + sleep 1 + run_sql "SELECT CONCAT(@@time_zone, ',', @@default_week_format, ',', @@block_encryption_mode) res" + if [ "$(read_result)" = '-08:00,4,aes-256-cbc' ]; then + break + fi +done + +for BACKEND in 'local' 'tidb' 'importer'; do + if [ "$BACKEND" = 'local' ]; then + check_cluster_version 4 0 0 'local backend' || continue + fi + + run_sql 'DROP DATABASE IF EXISTS gencol' + + run_lightning --backend $BACKEND + + run_sql 'SELECT * FROM gencol.nested WHERE a = 100' + check_contains 'a: 100' + check_contains 'b: 101' + check_contains 'c: 102' + check_contains 'd: 103' + check_contains 'e: 104' + + run_sql 'SELECT * FROM gencol.various_types' --binary-as-hex + check_contains 'int64: 3' + check_contains 'uint64: 5764801' + check_contains 'float32: 0.5625' + check_contains 'float64: 5e222' + check_contains 'string: 6ad8402ba6610f04d3ec5c9875489a7bc8e259c5' + check_contains 'bytes: 0x6AD8402BA6610F04D3EC5C9875489A7BC8E259C5' + check_contains 'decimal: 1234.5678' + check_contains 'duration: 01:02:03' + check_contains 'enum: c' + check_contains 'bit: 0x03' + check_contains 'set: c' + check_contains 'time: 1987-06-05 04:03:02.100' + check_contains 'json: {"6ad8402ba6610f04d3ec5c9875489a7bc8e259c5": 0.5625}' + check_contains 'aes: 0xA876B03CFC8AF93D22D19E2220BD2375' + # FIXME: test below disabled due to pingcap/tidb#21510 + # check_contains 'week: 6' + check_contains 'tz: 1969-12-31 16:00:01' +done diff --git a/tests/lightning_issue_282/config.toml b/tests/lightning_issue_282/config.toml new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_issue_282/data/issue282-schema-create.sql b/tests/lightning_issue_282/data/issue282-schema-create.sql new file mode 100644 index 000000000..4cdf51448 --- /dev/null +++ b/tests/lightning_issue_282/data/issue282-schema-create.sql @@ -0,0 +1 @@ +CREATE DATABASE `issue282` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; diff --git a/tests/lightning_issue_282/data/issue282.t_access3-schema.sql b/tests/lightning_issue_282/data/issue282.t_access3-schema.sql new file mode 100644 index 000000000..4c955d457 --- /dev/null +++ b/tests/lightning_issue_282/data/issue282.t_access3-schema.sql @@ -0,0 +1,4 @@ +/*!40103 SET TIME_ZONE='+00:00' */; +CREATE TABLE `t_access3` ( + `accessKey` varchar(100) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; diff --git a/tests/lightning_issue_282/data/issue282.t_access3.sql b/tests/lightning_issue_282/data/issue282.t_access3.sql new file mode 100644 index 000000000..2c3e038ea --- /dev/null +++ b/tests/lightning_issue_282/data/issue282.t_access3.sql @@ -0,0 +1,5 @@ +/*!40103 SET TIME_ZONE='+00:00' */; +INSERT INTO `t_access3` VALUES +('@P&FLASHSHA'); +INSERT INTO `t_access3` VALUES +('\Z'); diff --git a/tests/lightning_issue_282/run.sh b/tests/lightning_issue_282/run.sh new file mode 100644 index 000000000..344a7a0fd --- /dev/null +++ b/tests/lightning_issue_282/run.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# +# Copyright 2020 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eu + +for backend in tidb importer local; do + if [ "$backend" = 'local' ]; then + check_cluster_version 4 0 0 'local backend' || continue + fi + + run_sql 'DROP DATABASE IF EXISTS issue282;' + run_lightning --backend $backend + + run_sql "SELECT hex(accessKey) FROM issue282.t_access3" + check_contains 'hex(accessKey): 405026464C415348534841' + check_contains 'hex(accessKey): 1A' +done diff --git a/tests/lightning_issue_410/config.toml b/tests/lightning_issue_410/config.toml new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_issue_410/data/issue410-schema-create.sql b/tests/lightning_issue_410/data/issue410-schema-create.sql new file mode 100644 index 000000000..629ff8137 --- /dev/null +++ b/tests/lightning_issue_410/data/issue410-schema-create.sql @@ -0,0 +1 @@ +create database issue410; diff --git a/tests/lightning_issue_410/data/issue410.row_flow_d-schema.sql b/tests/lightning_issue_410/data/issue410.row_flow_d-schema.sql new file mode 100644 index 000000000..639c609d2 --- /dev/null +++ b/tests/lightning_issue_410/data/issue410.row_flow_d-schema.sql @@ -0,0 +1,28 @@ +create table row_flow_d ( + A123456789012345678901234567890123456789 VARCHAR(16), + B123456789012345678901234567890123456789 VARCHAR(16), + C123456789012345678901234567890123456789 VARCHAR(16), + D123456789012345678901234567890123456789 VARCHAR(16), + E123456789012345678901234567890123456789 VARCHAR(16), + F123456789012345678901234567890123456789 VARCHAR(16), + G123456789012345678901234567890123456789 VARCHAR(16), + H123456789012345678901234567890123456789 VARCHAR(16), + I123456789012345678901234567890123456789 VARCHAR(16), + J123456789012345678901234567890123456789 VARCHAR(16), + K123456789012345678901234567890123456789 VARCHAR(16), + L123456789012345678901234567890123456789 VARCHAR(16), + M123456789012345678901234567890123456789 VARCHAR(16), + N123456789012345678901234567890123456789 VARCHAR(16), + O123456789012345678901234567890123456789 VARCHAR(16), + P123456789012345678901234567890123456789 VARCHAR(16), + Q123456789012345678901234567890123456789 VARCHAR(16), + R123456789012345678901234567890123456789 VARCHAR(16), + S123456789012345678901234567890123456789 VARCHAR(16), + T123456789012345678901234567890123456789 VARCHAR(16), + U123456789012345678901234567890123456789 VARCHAR(16), + V123456789012345678901234567890123456789 VARCHAR(16), + W123456789012345678901234567890123456789 VARCHAR(16), + X123456789012345678901234567890123456789 VARCHAR(16), + Y12345678901234567890123456789012345678 VARCHAR(16), + Z VARCHAR(16) +); diff --git a/tests/lightning_issue_410/data/issue410.row_flow_d.0.csv b/tests/lightning_issue_410/data/issue410.row_flow_d.0.csv new file mode 100644 index 000000000..04000215e --- /dev/null +++ b/tests/lightning_issue_410/data/issue410.row_flow_d.0.csv @@ -0,0 +1,2 @@ +A123456789012345678901234567890123456789,B123456789012345678901234567890123456789,C123456789012345678901234567890123456789,D123456789012345678901234567890123456789,E123456789012345678901234567890123456789,F123456789012345678901234567890123456789,G123456789012345678901234567890123456789,H123456789012345678901234567890123456789,I123456789012345678901234567890123456789,J123456789012345678901234567890123456789,K123456789012345678901234567890123456789,L123456789012345678901234567890123456789,M123456789012345678901234567890123456789,N123456789012345678901234567890123456789,O123456789012345678901234567890123456789,P123456789012345678901234567890123456789,Q123456789012345678901234567890123456789,R123456789012345678901234567890123456789,S123456789012345678901234567890123456789,T123456789012345678901234567890123456789,U123456789012345678901234567890123456789,V123456789012345678901234567890123456789,W123456789012345678901234567890123456789,X123456789012345678901234567890123456789,Y12345678901234567890123456789012345678,Z +A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z \ No newline at end of file diff --git a/tests/lightning_issue_410/run.sh b/tests/lightning_issue_410/run.sh new file mode 100644 index 000000000..97b96e5c3 --- /dev/null +++ b/tests/lightning_issue_410/run.sh @@ -0,0 +1,30 @@ +#!/bin/sh +# +# Copyright 2020 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eux + +for backend in tidb importer local; do + if [ "$backend" = 'local' ]; then + check_cluster_version 4 0 0 'local backend' || continue + fi + + run_sql 'DROP DATABASE IF EXISTS issue410;' + run_lightning --backend $backend + + run_sql "SELECT * FROM issue410.row_flow_d" + check_contains 'A123456789012345678901234567890123456789: A' + check_contains 'Y12345678901234567890123456789012345678: Y' + check_contains 'Z: Z' +done diff --git a/tests/lightning_issue_519/config.toml b/tests/lightning_issue_519/config.toml new file mode 100644 index 000000000..19a97458c --- /dev/null +++ b/tests/lightning_issue_519/config.toml @@ -0,0 +1,8 @@ +[mydumper.csv] +separator = ',' +delimiter = "'" +header = true +not-null = false +null = '\N' +backslash-escape = false +trim-last-separator = false diff --git a/tests/lightning_issue_519/data/issue519-schema-create.sql b/tests/lightning_issue_519/data/issue519-schema-create.sql new file mode 100644 index 000000000..b47adb5c4 --- /dev/null +++ b/tests/lightning_issue_519/data/issue519-schema-create.sql @@ -0,0 +1 @@ +create schema issue519; diff --git a/tests/lightning_issue_519/data/issue519.t-schema.sql b/tests/lightning_issue_519/data/issue519.t-schema.sql new file mode 100644 index 000000000..1ce383db3 --- /dev/null +++ b/tests/lightning_issue_519/data/issue519.t-schema.sql @@ -0,0 +1 @@ +create table t(a text, b text); diff --git a/tests/lightning_issue_519/data/issue519.t.csv b/tests/lightning_issue_519/data/issue519.t.csv new file mode 100644 index 000000000..1b1750b59 --- /dev/null +++ b/tests/lightning_issue_519/data/issue519.t.csv @@ -0,0 +1,10 @@ +'a','b' +'''','"' +'"','''''' +'''''','""' +'""','''"''' +'''"''','"''''''"''"' +'"''''''''"''"','"''"''''''''''"' +'"''"''"''''''''"','' +'a",b,"a','a,"c",a' +'bbaa|*|aabb','bba|*|a|*|abb' \ No newline at end of file diff --git a/tests/lightning_issue_519/run.sh b/tests/lightning_issue_519/run.sh new file mode 100755 index 000000000..12268cd09 --- /dev/null +++ b/tests/lightning_issue_519/run.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# +# Copyright 2020 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eux + +run_sql 'DROP DATABASE IF EXISTS issue519;' +run_lightning --backend tidb + +run_sql "SELECT b FROM issue519.t WHERE a = '''';" +check_contains 'b: "' +# following use hex to avoid the escaping mess. 22 = `"`, 27 = `'`. +run_sql 'SELECT hex(a) FROM issue519.t WHERE b = 0x222722272727272722;' +check_contains 'hex(a): 2227272727222722' diff --git a/tests/lightning_local_backend/config.toml b/tests/lightning_local_backend/config.toml new file mode 100644 index 000000000..46ca06e09 --- /dev/null +++ b/tests/lightning_local_backend/config.toml @@ -0,0 +1,13 @@ +[lightning] +table-concurrency = 1 + +[checkpoint] +enable = true +driver = "file" +schema = "tidb_lightning_checkpoint_local_backend_test" + +[tikv-importer] +send-kv-pairs = 2 + +[mydumper] +batch-size = 50 # force splitting the data into 4 batches diff --git a/tests/lightning_local_backend/data/cpeng-schema-create.sql b/tests/lightning_local_backend/data/cpeng-schema-create.sql new file mode 100644 index 000000000..1e23466ee --- /dev/null +++ b/tests/lightning_local_backend/data/cpeng-schema-create.sql @@ -0,0 +1 @@ +create database cpeng; diff --git a/tests/lightning_local_backend/data/cpeng.a-schema.sql b/tests/lightning_local_backend/data/cpeng.a-schema.sql new file mode 100644 index 000000000..fe3f493b6 --- /dev/null +++ b/tests/lightning_local_backend/data/cpeng.a-schema.sql @@ -0,0 +1 @@ +create table a (c int); diff --git a/tests/lightning_local_backend/data/cpeng.a.1.sql b/tests/lightning_local_backend/data/cpeng.a.1.sql new file mode 100644 index 000000000..58829b7d8 --- /dev/null +++ b/tests/lightning_local_backend/data/cpeng.a.1.sql @@ -0,0 +1 @@ +insert into a values (1); diff --git a/tests/lightning_local_backend/data/cpeng.a.2.sql b/tests/lightning_local_backend/data/cpeng.a.2.sql new file mode 100644 index 000000000..ccbcb5801 --- /dev/null +++ b/tests/lightning_local_backend/data/cpeng.a.2.sql @@ -0,0 +1 @@ +insert into a values (2); diff --git a/tests/lightning_local_backend/data/cpeng.a.3.sql b/tests/lightning_local_backend/data/cpeng.a.3.sql new file mode 100644 index 000000000..effdc8f3e --- /dev/null +++ b/tests/lightning_local_backend/data/cpeng.a.3.sql @@ -0,0 +1 @@ +insert into a values (3),(4); diff --git a/tests/lightning_local_backend/data/cpeng.b-schema.sql b/tests/lightning_local_backend/data/cpeng.b-schema.sql new file mode 100644 index 000000000..4a3c844ef --- /dev/null +++ b/tests/lightning_local_backend/data/cpeng.b-schema.sql @@ -0,0 +1 @@ +create table b (c int); diff --git a/tests/lightning_local_backend/data/cpeng.b.1.sql b/tests/lightning_local_backend/data/cpeng.b.1.sql new file mode 100644 index 000000000..cadf0227f --- /dev/null +++ b/tests/lightning_local_backend/data/cpeng.b.1.sql @@ -0,0 +1,4 @@ +insert into b values (10),(11),(12); +/* +padding to make the data file > 50 bytes +*/ diff --git a/tests/lightning_local_backend/data/cpeng.b.2.sql b/tests/lightning_local_backend/data/cpeng.b.2.sql new file mode 100644 index 000000000..83045aee9 --- /dev/null +++ b/tests/lightning_local_backend/data/cpeng.b.2.sql @@ -0,0 +1 @@ +insert into b values (13); diff --git a/tests/lightning_local_backend/file.toml b/tests/lightning_local_backend/file.toml new file mode 100644 index 000000000..ff263c06d --- /dev/null +++ b/tests/lightning_local_backend/file.toml @@ -0,0 +1,17 @@ +[lightning] +table-concurrency = 1 + +[checkpoint] +enable = true +driver = "file" +schema = "tidb_lightning_checkpoint_local_backend_test" + +[tikv-importer] +send-kv-pairs = 2 + +[mydumper] +batch-size = 50 # force splitting the data into 4 batches + +[[black-white-list.do-tables]] +db-name = "cpeng" +tbl-name = "a" \ No newline at end of file diff --git a/tests/lightning_local_backend/mysql.toml b/tests/lightning_local_backend/mysql.toml new file mode 100644 index 000000000..6e80272b2 --- /dev/null +++ b/tests/lightning_local_backend/mysql.toml @@ -0,0 +1,17 @@ +[lightning] +table-concurrency = 1 + +[checkpoint] +enable = true +driver = "mysql" +schema = "tidb_lightning_checkpoint_local_backend_test" + +[tikv-importer] +send-kv-pairs = 2 + +[mydumper] +batch-size = 50 # force splitting the data into 4 batches + +[[black-white-list.do-tables]] +db-name = "cpeng" +tbl-name = "a" \ No newline at end of file diff --git a/tests/lightning_local_backend/run.sh b/tests/lightning_local_backend/run.sh new file mode 100755 index 000000000..4e7b38b19 --- /dev/null +++ b/tests/lightning_local_backend/run.sh @@ -0,0 +1,123 @@ +#!/bin/sh +# +# Copyright 2020 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eux + +check_cluster_version 4 0 0 'local backend' || exit 0 + +ENGINE_COUNT=6 + +# First, verify that inject with not leader error is fine. +rm -f "$TEST_DIR/lightning-local.log" +rm -f "/tmp/tidb_lightning_checkpoint_local_backend_test.pb" +run_sql 'DROP DATABASE IF EXISTS cpeng;' +export GO_FAILPOINTS='github.com/pingcap/br/pkg/lightning/backend/FailIngestMeta=1*return("notleader")' + +run_lightning --backend local --enable-checkpoint=1 --log-file "$TEST_DIR/lightning-local.log" --config "tests/$TEST_NAME/config.toml" + +# Check that everything is correctly imported +run_sql 'SELECT count(*), sum(c) FROM cpeng.a' +check_contains 'count(*): 4' +check_contains 'sum(c): 10' + +run_sql 'SELECT count(*), sum(c) FROM cpeng.b' +check_contains 'count(*): 4' +check_contains 'sum(c): 46' + +# Now, verify it works with epoch not match as well. +run_sql 'DROP DATABASE cpeng;' +rm -f "/tmp/tidb_lightning_checkpoint_local_backend_test.pb" + +export GO_FAILPOINTS='github.com/pingcap/br/pkg/lightning/backend/FailIngestMeta=2*return("epochnotmatch")' + +run_lightning --backend local --enable-checkpoint=1 --log-file "$TEST_DIR/lightning-local.log" --config "tests/$TEST_NAME/config.toml" + +run_sql 'SELECT count(*), sum(c) FROM cpeng.a' +check_contains 'count(*): 4' +check_contains 'sum(c): 10' + +run_sql 'SELECT count(*), sum(c) FROM cpeng.b' +check_contains 'count(*): 4' +check_contains 'sum(c): 46' + + +# Now, verify it works with checkpoints as well. +run_sql 'DROP DATABASE cpeng;' +rm -f "/tmp/tidb_lightning_checkpoint_local_backend_test.pb" + +set +e +export GO_FAILPOINTS='github.com/pingcap/br/pkg/lightning/restore/FailBeforeDataEngineImported=return' +for i in $(seq "$ENGINE_COUNT"); do + echo "******** Importing Table Now (step $i/$ENGINE_COUNT) ********" + run_lightning --backend local --enable-checkpoint=1 --log-file "$TEST_DIR/lightning-local.log" --config "tests/$TEST_NAME/config.toml" + [ $? -ne 0 ] || exit 1 +done +set -e + +export GO_FAILPOINTS='' +echo "******** Verify checkpoint no-op ********" +run_lightning --backend local --enable-checkpoint=1 --log-file "$TEST_DIR/lightning-local.log" --config "tests/$TEST_NAME/config.toml" + +run_sql 'SELECT count(*), sum(c) FROM cpeng.a' +check_contains 'count(*): 4' +check_contains 'sum(c): 10' + +run_sql 'SELECT count(*), sum(c) FROM cpeng.b' +check_contains 'count(*): 4' +check_contains 'sum(c): 46' + +# Verify GetLocalStoringTables works +# failpoint works for per table not task, so we limit this test to task that allow one table +for ckpt in mysql file; do + run_sql 'DROP DATABASE IF EXISTS cpeng;' + run_sql 'DROP DATABASE IF EXISTS tidb_lightning_checkpoint_local_backend_test' + rm -f "/tmp/tidb_lightning_checkpoint_local_backend_test.pb" + + # before chunk pos is updated, local files could handle lost + set +e + export GO_FAILPOINTS="github.com/pingcap/br/pkg/lightning/restore/FailAfterWriteRows=return" + run_lightning --backend local --enable-checkpoint=1 --log-file "$TEST_DIR/lightning-local.log" --config "tests/$TEST_NAME/$ckpt.toml" + set -e + run_lightning_ctl --check-local-storage \ + --backend local \ + --enable-checkpoint=1 \ + --config=tests/$TEST_NAME/$ckpt.toml >$TEST_DIR/lightning_ctl.output 2>&1 + grep -Fq "No table has lost intermediate files according to given config" $TEST_DIR/lightning_ctl.output + + # when position of chunk file doesn't equal to offset, intermediate file should exist + set +e + export GO_FAILPOINTS="github.com/pingcap/br/pkg/lightning/restore/LocalBackendSaveCheckpoint=return;github.com/pingcap/br/pkg/lightning/restore/FailIfImportedChunk=return(1)" + run_lightning --backend local --enable-checkpoint=1 --log-file "$TEST_DIR/lightning-local.log" --config "tests/$TEST_NAME/$ckpt.toml" + set -e + run_lightning_ctl --check-local-storage \ + --backend local \ + --enable-checkpoint=1 \ + --config=tests/$TEST_NAME/$ckpt.toml >$TEST_DIR/lightning_ctl.output 2>&1 + grep -Eq "These tables are missing intermediate files: \[.+\]" $TEST_DIR/lightning_ctl.output + # don't distinguish whole sort-kv directory missing and table's directory missing for now + ls -lA $TEST_DIR/$TEST_NAME.sorted + + # after index engine is imported, local file could handle lost + set +e + export GO_FAILPOINTS="github.com/pingcap/br/pkg/lightning/restore/FailIfIndexEngineImported=return(1)" + run_lightning --backend local --enable-checkpoint=1 --log-file "$TEST_DIR/lightning-local.log" --config "tests/$TEST_NAME/$ckpt.toml" + set -e + run_lightning_ctl --check-local-storage \ + --backend local \ + --enable-checkpoint=1 \ + --config=tests/$TEST_NAME/$ckpt.toml >$TEST_DIR/lightning_ctl.output 2>&1 + grep -Fq "No table has lost intermediate files according to given config" $TEST_DIR/lightning_ctl.output +done +rm -r $TEST_DIR/$TEST_NAME.sorted \ No newline at end of file diff --git a/tests/lightning_new_collation/config.toml b/tests/lightning_new_collation/config.toml new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_new_collation/run.sh b/tests/lightning_new_collation/run.sh new file mode 100644 index 000000000..6c62f7dfe --- /dev/null +++ b/tests/lightning_new_collation/run.sh @@ -0,0 +1,59 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +check_cluster_version 4 0 0 'new collation' || { echo 'TiDB does not support new collation! skipping test'; exit 0; } + +set -euE + +cur=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +. $cur/../_utils/run_services + +# restart cluster with new collation enabled +start_services --tidb-cfg $cur/tidb-new-collation.toml + +# Populate the mydumper source +DBPATH="$TEST_DIR/nc.mydump" +mkdir -p $DBPATH +echo 'CREATE DATABASE nc;' > "$DBPATH/nc-schema-create.sql" +# create table with collate `utf8_general_ci`, the index key will be different between old/new collation +echo "CREATE TABLE t(i INT PRIMARY KEY, s varchar(32), j TINYINT, KEY s_j (s, i)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;" > "$DBPATH/nc.t-schema.sql" +cat > "$DBPATH/nc.t.0.sql" << _EOF_ +INSERT INTO t (s, i, j) VALUES + ("this_is_test1", 1, 1), + ("this_is_test2", 2, 2), + ("this_is_test3", 3, 3), + ("this_is_test4", 4, 4), + ("this_is_test5", 5, 5); +_EOF_ +echo 'INSERT INTO t(s, i, j) VALUES ("another test case", 6, 6);' > "$DBPATH/nc.t.1.sql" + +for BACKEND in local importer tidb; do + # Start importing the tables. + run_sql 'DROP DATABASE IF EXISTS nc' + + run_lightning -d "$DBPATH" --backend $BACKEND 2> /dev/null + + run_sql 'SELECT count(*), sum(i) FROM `nc`.t' + check_contains "count(*): 6" + check_contains "sum(i): 21" + + # run sql with index `s_j`, if lightning don't support new collation, no result will be returned. + run_sql "SELECT j FROM nc.t WHERE s = 'This_Is_Test4'"; + check_contains "j: 4" + +done + +# restart with original config +start_services diff --git a/tests/lightning_new_collation/tidb-new-collation.toml b/tests/lightning_new_collation/tidb-new-collation.toml new file mode 100644 index 000000000..f3a4604c7 --- /dev/null +++ b/tests/lightning_new_collation/tidb-new-collation.toml @@ -0,0 +1,11 @@ +# config of tidb + +new_collations_enabled_on_first_bootstrap = true + +[security] +ssl-ca = "/tmp/backup_restore_test/certs/ca.pem" +ssl-cert = "/tmp/backup_restore_test/certs/tidb.pem" +ssl-key = "/tmp/backup_restore_test/certs/tidb.key" +cluster-ssl-ca = "/tmp/backup_restore_test/certs/ca.pem" +cluster-ssl-cert = "/tmp/backup_restore_test/certs/tidb.pem" +cluster-ssl-key = "/tmp/backup_restore_test/certs/tidb.key" diff --git a/tests/lightning_no_schema/config.toml b/tests/lightning_no_schema/config.toml new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_no_schema/data/noschema.t.sql b/tests/lightning_no_schema/data/noschema.t.sql new file mode 100644 index 000000000..baed4cd25 --- /dev/null +++ b/tests/lightning_no_schema/data/noschema.t.sql @@ -0,0 +1,15 @@ +insert into t values (1); +insert into t values (2); +insert into t values (3); +insert into t values (4); +insert into t values (5); +insert into t values (6); +insert into t values (7); +insert into t values (8); +insert into t values (9); +insert into t values (10); +insert into t values (11); +insert into t values (12); +insert into t values (13); +insert into t values (14); +insert into t values (15); diff --git a/tests/lightning_no_schema/run.sh b/tests/lightning_no_schema/run.sh new file mode 100644 index 000000000..d3157405d --- /dev/null +++ b/tests/lightning_no_schema/run.sh @@ -0,0 +1,30 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eu + +run_sql "DROP DATABASE IF EXISTS noschema;" +run_lightning --no-schema=1 -d "tests/$TEST_NAME/schema-data" +run_sql "show databases" +check_not_contains "noschema" + +run_sql "create database noschema;" +run_sql "create table noschema.t (x int primary key);" + +# Starting importing +run_lightning --no-schema=1 + +run_sql "SELECT sum(x) FROM noschema.t;" +check_contains 'sum(x): 120' diff --git a/tests/lightning_no_schema/schema-data/noschema-schema-create.sql b/tests/lightning_no_schema/schema-data/noschema-schema-create.sql new file mode 100644 index 000000000..0d43f8d63 --- /dev/null +++ b/tests/lightning_no_schema/schema-data/noschema-schema-create.sql @@ -0,0 +1 @@ +create database noschema; diff --git a/tests/lightning_no_schema/schema-data/noschema.t-schema.sql b/tests/lightning_no_schema/schema-data/noschema.t-schema.sql new file mode 100644 index 000000000..8bfb649ef --- /dev/null +++ b/tests/lightning_no_schema/schema-data/noschema.t-schema.sql @@ -0,0 +1 @@ +create table t (x timestamp not null); diff --git a/tests/lightning_parquet/config.toml b/tests/lightning_parquet/config.toml new file mode 100644 index 000000000..18cc8dfe4 --- /dev/null +++ b/tests/lightning_parquet/config.toml @@ -0,0 +1,10 @@ +[lightning] + +[[mydumper.files]] +pattern = '(?i)^(?:[^/]*/)*([a-z0-9_]+)\.([a-z0-9_]+)/(?:[a-z0-9\-_.]+\.(parquet))$' +schema = "$1" +table = "$2" +type = "$3" + +[mydumper] +no-schema=true \ No newline at end of file diff --git a/tests/lightning_parquet/data/export_info_ci.json b/tests/lightning_parquet/data/export_info_ci.json new file mode 100644 index 000000000..0922996e7 --- /dev/null +++ b/tests/lightning_parquet/data/export_info_ci.json @@ -0,0 +1 @@ +{"exportTaskIdentifier":"ci","sourceArn":"arn:aws:rds:us-west-2:697156367097:cluster-snapshot:ci","exportOnly":[],"snapshotTime":"Sep 10, 2020 6:50:05 AM","taskStartTime":"Sep 10, 2020 7:12:21 AM","taskEndTime":"Sep 10, 2020 7:15:14 AM","s3Bucket":"handlerww-cluster","s3Prefix":"","exportedFilesPath":"ci","iamRoleArn":"arn:aws:iam::697156367097:role/service-role/export-to-s3","kmsKeyId":"arn:aws:kms:us-west-2:697156367097:key/9fd6e56f-de78-4229-9044-3307c4836bc5","status":"COMPLETE","percentProgress":0,"totalExportedDataInGB":1.0} \ No newline at end of file diff --git a/tests/lightning_parquet/data/export_tables_info_ci_from_1_to_9.json b/tests/lightning_parquet/data/export_tables_info_ci_from_1_to_9.json new file mode 100644 index 000000000..cc691764e --- /dev/null +++ b/tests/lightning_parquet/data/export_tables_info_ci_from_1_to_9.json @@ -0,0 +1 @@ +{"perTableStatus":[{"tableStatistics":{"extractionStartTime":"Sep 10, 2020 7:13:10 AM","extractionEndTime":"Sep 10, 2020 7:13:20 AM","partitioningInfo":{"numberOfPartitions":1,"numberOfCompletedPartitions":1}},"schemaMetadata":{"originalTypeMappings":[{"columnName":"c_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"c_d_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"c_w_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"c_first","originalType":"varchar","expectedExportedType":"binary (UTF8)"},{"columnName":"c_middle","originalType":"char","expectedExportedType":"binary (UTF8)"},{"columnName":"c_last","originalType":"varchar","expectedExportedType":"binary (UTF8)"},{"columnName":"c_street_1","originalType":"varchar","expectedExportedType":"binary (UTF8)"},{"columnName":"c_street_2","originalType":"varchar","expectedExportedType":"binary (UTF8)"},{"columnName":"c_city","originalType":"varchar","expectedExportedType":"binary (UTF8)"},{"columnName":"c_state","originalType":"char","expectedExportedType":"binary (UTF8)"},{"columnName":"c_zip","originalType":"char","expectedExportedType":"binary (UTF8)"},{"columnName":"c_phone","originalType":"char","expectedExportedType":"binary (UTF8)"},{"columnName":"c_since","originalType":"datetime","expectedExportedType":"int64 (TIMESTAMP_MICRO)"},{"columnName":"c_credit","originalType":"char","expectedExportedType":"binary (UTF8)"},{"columnName":"c_credit_lim","originalType":"decimal","expectedExportedType":"int64 (DECIMAL(12,2))"},{"columnName":"c_discount","originalType":"decimal","expectedExportedType":"int32 (DECIMAL(4,4))"},{"columnName":"c_balance","originalType":"decimal","expectedExportedType":"int64 (DECIMAL(12,2))"},{"columnName":"c_ytd_payment","originalType":"decimal","expectedExportedType":"int64 (DECIMAL(12,2))"},{"columnName":"c_payment_cnt","originalType":"int","expectedExportedType":"int32"},{"columnName":"c_delivery_cnt","originalType":"int","expectedExportedType":"int32"},{"columnName":"c_data","originalType":"varchar","expectedExportedType":"binary (UTF8)"}]},"status":"COMPLETE","sizeGB":1.52587890625E-5,"target":"test.test.customer"},{"tableStatistics":{"extractionStartTime":"Sep 10, 2020 7:13:10 AM","extractionEndTime":"Sep 10, 2020 7:13:20 AM","partitioningInfo":{"numberOfPartitions":1,"numberOfCompletedPartitions":1}},"schemaMetadata":{"originalTypeMappings":[{"columnName":"d_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"d_w_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"d_name","originalType":"varchar","expectedExportedType":"binary (UTF8)"},{"columnName":"d_street_1","originalType":"varchar","expectedExportedType":"binary (UTF8)"},{"columnName":"d_street_2","originalType":"varchar","expectedExportedType":"binary (UTF8)"},{"columnName":"d_city","originalType":"varchar","expectedExportedType":"binary (UTF8)"},{"columnName":"d_state","originalType":"char","expectedExportedType":"binary (UTF8)"},{"columnName":"d_zip","originalType":"char","expectedExportedType":"binary (UTF8)"},{"columnName":"d_tax","originalType":"decimal","expectedExportedType":"int32 (DECIMAL(4,4))"},{"columnName":"d_ytd","originalType":"decimal","expectedExportedType":"int64 (DECIMAL(12,2))"},{"columnName":"d_next_o_id","originalType":"int","expectedExportedType":"int32"}]},"status":"COMPLETE","sizeGB":1.52587890625E-5,"target":"test.test.district"},{"tableStatistics":{"extractionStartTime":"Sep 10, 2020 7:13:10 AM","extractionEndTime":"Sep 10, 2020 7:13:20 AM","partitioningInfo":{"numberOfPartitions":1,"numberOfCompletedPartitions":1}},"schemaMetadata":{"originalTypeMappings":[{"columnName":"h_c_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"h_c_d_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"h_c_w_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"h_d_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"h_w_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"h_date","originalType":"datetime","expectedExportedType":"int64 (TIMESTAMP_MICRO)"},{"columnName":"h_amount","originalType":"decimal","expectedExportedType":"int32 (DECIMAL(6,2))"},{"columnName":"h_data","originalType":"varchar","expectedExportedType":"binary (UTF8)"}]},"status":"COMPLETE","sizeGB":1.52587890625E-5,"target":"test.test.history"},{"tableStatistics":{"extractionStartTime":"Sep 10, 2020 7:13:10 AM","extractionEndTime":"Sep 10, 2020 7:13:20 AM","partitioningInfo":{"numberOfPartitions":1,"numberOfCompletedPartitions":1}},"schemaMetadata":{"originalTypeMappings":[{"columnName":"i_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"i_im_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"i_name","originalType":"varchar","expectedExportedType":"binary (UTF8)"},{"columnName":"i_price","originalType":"decimal","expectedExportedType":"int32 (DECIMAL(5,2))"},{"columnName":"i_data","originalType":"varchar","expectedExportedType":"binary (UTF8)"}]},"status":"COMPLETE","sizeGB":1.52587890625E-5,"target":"test.test.item"},{"tableStatistics":{"extractionStartTime":"Sep 10, 2020 7:13:10 AM","extractionEndTime":"Sep 10, 2020 7:13:20 AM","partitioningInfo":{"numberOfPartitions":1,"numberOfCompletedPartitions":1}},"schemaMetadata":{"originalTypeMappings":[{"columnName":"no_o_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"no_d_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"no_w_id","originalType":"int","expectedExportedType":"int32"}]},"status":"COMPLETE","sizeGB":1.52587890625E-5,"target":"test.test.new_order"},{"tableStatistics":{"extractionStartTime":"Sep 10, 2020 7:13:10 AM","extractionEndTime":"Sep 10, 2020 7:13:20 AM","partitioningInfo":{"numberOfPartitions":1,"numberOfCompletedPartitions":1}},"schemaMetadata":{"originalTypeMappings":[{"columnName":"ol_o_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"ol_d_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"ol_w_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"ol_number","originalType":"int","expectedExportedType":"int32"},{"columnName":"ol_i_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"ol_supply_w_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"ol_delivery_d","originalType":"datetime","expectedExportedType":"int64 (TIMESTAMP_MICRO)"},{"columnName":"ol_quantity","originalType":"int","expectedExportedType":"int32"},{"columnName":"ol_amount","originalType":"decimal","expectedExportedType":"int32 (DECIMAL(6,2))"},{"columnName":"ol_dist_info","originalType":"char","expectedExportedType":"binary (UTF8)"}]},"status":"COMPLETE","sizeGB":1.52587890625E-5,"target":"test.test.order_line"},{"tableStatistics":{"extractionStartTime":"Sep 10, 2020 7:13:10 AM","extractionEndTime":"Sep 10, 2020 7:13:20 AM","partitioningInfo":{"numberOfPartitions":1,"numberOfCompletedPartitions":1}},"schemaMetadata":{"originalTypeMappings":[{"columnName":"o_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"o_d_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"o_w_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"o_c_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"o_entry_d","originalType":"datetime","expectedExportedType":"int64 (TIMESTAMP_MICRO)"},{"columnName":"o_carrier_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"o_ol_cnt","originalType":"int","expectedExportedType":"int32"},{"columnName":"o_all_local","originalType":"int","expectedExportedType":"int32"}]},"status":"COMPLETE","sizeGB":1.52587890625E-5,"target":"test.test.orders"},{"tableStatistics":{"extractionStartTime":"Sep 10, 2020 7:13:10 AM","extractionEndTime":"Sep 10, 2020 7:13:20 AM","partitioningInfo":{"numberOfPartitions":1,"numberOfCompletedPartitions":1}},"schemaMetadata":{"originalTypeMappings":[{"columnName":"s_i_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"s_w_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"s_quantity","originalType":"int","expectedExportedType":"int32"},{"columnName":"s_dist_01","originalType":"char","expectedExportedType":"binary (UTF8)"},{"columnName":"s_dist_02","originalType":"char","expectedExportedType":"binary (UTF8)"},{"columnName":"s_dist_03","originalType":"char","expectedExportedType":"binary (UTF8)"},{"columnName":"s_dist_04","originalType":"char","expectedExportedType":"binary (UTF8)"},{"columnName":"s_dist_05","originalType":"char","expectedExportedType":"binary (UTF8)"},{"columnName":"s_dist_06","originalType":"char","expectedExportedType":"binary (UTF8)"},{"columnName":"s_dist_07","originalType":"char","expectedExportedType":"binary (UTF8)"},{"columnName":"s_dist_08","originalType":"char","expectedExportedType":"binary (UTF8)"},{"columnName":"s_dist_09","originalType":"char","expectedExportedType":"binary (UTF8)"},{"columnName":"s_dist_10","originalType":"char","expectedExportedType":"binary (UTF8)"},{"columnName":"s_ytd","originalType":"int","expectedExportedType":"int32"},{"columnName":"s_order_cnt","originalType":"int","expectedExportedType":"int32"},{"columnName":"s_remote_cnt","originalType":"int","expectedExportedType":"int32"},{"columnName":"s_data","originalType":"varchar","expectedExportedType":"binary (UTF8)"}]},"status":"COMPLETE","sizeGB":4.57763671875E-5,"target":"test.test.stock"},{"tableStatistics":{"extractionStartTime":"Sep 10, 2020 7:13:10 AM","extractionEndTime":"Sep 10, 2020 7:13:20 AM","partitioningInfo":{"numberOfPartitions":1,"numberOfCompletedPartitions":1}},"schemaMetadata":{"originalTypeMappings":[{"columnName":"w_id","originalType":"int","expectedExportedType":"int32"},{"columnName":"w_name","originalType":"varchar","expectedExportedType":"binary (UTF8)"},{"columnName":"w_street_1","originalType":"varchar","expectedExportedType":"binary (UTF8)"},{"columnName":"w_street_2","originalType":"varchar","expectedExportedType":"binary (UTF8)"},{"columnName":"w_city","originalType":"varchar","expectedExportedType":"binary (UTF8)"},{"columnName":"w_state","originalType":"char","expectedExportedType":"binary (UTF8)"},{"columnName":"w_zip","originalType":"char","expectedExportedType":"binary (UTF8)"},{"columnName":"w_tax","originalType":"decimal","expectedExportedType":"int32 (DECIMAL(4,4))"},{"columnName":"w_ytd","originalType":"decimal","expectedExportedType":"int64 (DECIMAL(12,2))"}]},"status":"COMPLETE","sizeGB":1.52587890625E-5,"target":"test.test.warehouse"}]} \ No newline at end of file diff --git a/tests/lightning_parquet/data/test/test.customer/_SUCCESS b/tests/lightning_parquet/data/test/test.customer/_SUCCESS new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_parquet/data/test/test.customer/part-00000-c3744aeb-351c-49ba-bdf3-5864befff481-c000.gz.parquet b/tests/lightning_parquet/data/test/test.customer/part-00000-c3744aeb-351c-49ba-bdf3-5864befff481-c000.gz.parquet new file mode 100644 index 0000000000000000000000000000000000000000..9a5f4188e03244d99671e53e5db1df933f46877a GIT binary patch literal 15251 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 UXqiM50DSQa%nASi9{hvgAL=u7i2wiq literal 0 HcmV?d00001 diff --git a/tests/lightning_parquet/data/test/test.item/_SUCCESS b/tests/lightning_parquet/data/test/test.item/_SUCCESS new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_parquet/data/test/test.item/part-00000-8905ded8-4c54-477f-9907-6e3eae50358b-c000.gz.parquet b/tests/lightning_parquet/data/test/test.item/part-00000-8905ded8-4c54-477f-9907-6e3eae50358b-c000.gz.parquet new file mode 100644 index 0000000000000000000000000000000000000000..9ee68c25f7b580994d2096ab30148180d80e452c GIT binary patch literal 7078 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 QzkdMq=m3Bg@*leY08sHz<^TWy literal 0 HcmV?d00001 diff --git a/tests/lightning_parquet/data/test/test.new_order/_SUCCESS b/tests/lightning_parquet/data/test/test.new_order/_SUCCESS new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_parquet/data/test/test.new_order/part-00000-d868200e-b629-4445-bd96-d34b6674b09d-c000.gz.parquet b/tests/lightning_parquet/data/test/test.new_order/part-00000-d868200e-b629-4445-bd96-d34b6674b09d-c000.gz.parquet new file mode 100644 index 0000000000000000000000000000000000000000..f83ece6b8e49ea6de5220361a7ba7459409f0a0d GIT binary patch literal 1140 zcmWG=3^EjD5M9S6`hiJD^aLZ2%_hnrBf&C*gMmSU#S}=2F=!z17#JAjyMdx0z|4C% zG^i<&f#JY=`S?8(<|b%&bwwnoZ(DTZ?%hI_zNhcbOluM73JeL8a@r{x7L~fy^jcK< zUS2aPJy&o=VuiyLen)UnI4;R(v z*M7Wee*f=>%liBOe!On~pK(soSKjH23=EjVFI$Osi>Eppu zT##6l9bcTBk(!$*%Elndq{<}0o|hk=AD@|m;H9AO%0WD_6O58Fk~V4_GNMe9rfeJx zl5l;ZEI>)IK6bGS%xWAqc#?*s3^4S7wg5e^c7qve5DUa0kd+XFCNYZLV+6_rO+thv zTF6T307H)jXcEvk5Ct@F8!M`TAXg(BxQ&%G1I0FR0=>l`DI=koUzDzwSda(|G(B+m z=@l2|=m7(=ur#$quPDDy5@j#64?YEDYA zl2vrIQeI*%ND3O>Ksl(|%)FA+^wc7tKwfE1PGV9HP!&*ns*V!OWF@QW+FBiC6F>n* znF*i}BH4u4S_TO(sNZ#SixkRIi;6Sz^ArsA3=Q-Q6*Q7cGjmcDEDTMPlG74RfG{yR v&CD#t$iO_wI3?N4(#X)%AjQxu$<*A$Ajwiw(nf}X0fHD70pl?MSl$2t34|({ literal 0 HcmV?d00001 diff --git a/tests/lightning_parquet/data/test/test.order_line/_SUCCESS b/tests/lightning_parquet/data/test/test.order_line/_SUCCESS new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_parquet/data/test/test.order_line/part-00000-e36fecd0-3f59-4ff6-b271-2e4b27ffbcf5-c000.gz.parquet b/tests/lightning_parquet/data/test/test.order_line/part-00000-e36fecd0-3f59-4ff6-b271-2e4b27ffbcf5-c000.gz.parquet new file mode 100644 index 0000000000000000000000000000000000000000..0dd0a5cebc78b99976b1556e7563ba6e6109abd4 GIT binary patch literal 5468 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 literal 0 HcmV?d00001 diff --git a/tests/lightning_parquet/data/test/test.orders/_SUCCESS b/tests/lightning_parquet/data/test/test.orders/_SUCCESS new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_parquet/data/test/test.orders/part-00000-b45481f5-92c0-4961-9bf7-32a0a04ffee5-c000.gz.parquet b/tests/lightning_parquet/data/test/test.orders/part-00000-b45481f5-92c0-4961-9bf7-32a0a04ffee5-c000.gz.parquet new file mode 100644 index 0000000000000000000000000000000000000000..f19f9bb11ed5a7f3d0aca6651384a6fee7234977 GIT binary patch literal 2941 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 literal 0 HcmV?d00001 diff --git a/tests/lightning_parquet/data/test/test.warehouse/_SUCCESS b/tests/lightning_parquet/data/test/test.warehouse/_SUCCESS new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_parquet/data/test/test.warehouse/part-00000-c6c33252-4d2f-4c5c-8a2f-d162bde6c360-c000.gz.parquet b/tests/lightning_parquet/data/test/test.warehouse/part-00000-c6c33252-4d2f-4c5c-8a2f-d162bde6c360-c000.gz.parquet new file mode 100644 index 0000000000000000000000000000000000000000..616f524dc8c4ad06439f845bee44fade9652b4b6 GIT binary patch literal 2719 zcmbtW-*4Mg6uvelNz?FSqt|j!B#Nb44P;T0C23McNS#=r9iaW7FfkytV<)pn+%|ER zg;InN3>eZLELu&|HmMJbsD$<~UU&ec34yvdv_D`PQue@0AFDke#Ic>kceTd~%@6C` z&-Z-ivwhAdADud$#0ZbzTny6?_;4V?F%&|`KC)<>SONUp*8w=f=dqGBKR;iV83QFg zo9G4sf_xrN6GVRHwKopGJo7f!4}h*84lqb`k@}5|mp;B8zxoF{(D~`qKASs--zB*B zm)nUWQ*$$OC(>H;$n@cMHP3lqP|*YR+UcQVgf`Sw-<=EVSJ!U}?Q3_m>#O-SbVK&P z7_lX#@kv6`)7cZ#v$^~`XOuZvFSTCDa4rPoxG?uFFDb|<-_+h`*N!P0>(Q}W_qIA{ z^WiP%t%1V@LIb_j;NTCHdXWd8OdFs7imqJQej;R32oJQ+(CLaSCe07$gh1#l z2;76d%NXH5qhFmHMd1?XnOJ%7RV$TwgpM!IMtj{J!H0+| z4t(;V`C2XKt$ zm{w7h5e`Az-YR~mE}Cb} zVzAQ1vtvOVzyX9A8)@q@APnXZONS%j%R(Rmax=aF3ft;1!_utA8k7(y{EM4}Bj6ZN z2QF8*L`86@$I-hii{%@N{egnBg@IoT0=T(uGa)%|(+($bZ!F$VX|_WPx9NhH6?kD# zZMQ~3YPipMX-HWbwkX!1h5Jmw?Er2B>hH!m$lB-qPVL@Px9WB&_Dfng?=QG!-UUF_ zM;irM(-?AU_FKy;DzWD%!5Au-rm({p$oz{zC*N*GEBjlD-K2zz!2&VyLLRzrEc1GMQQ;?f82e^P z=VN?XRjP7>pFFk9o6Z2IOt*j&y^X4-D;1>>Tg~K$xz2sXixfcSk)xD&H4?F3yQc`PTbvzwQM*-dS!-RHq zlO$8I)FWBmTS44xuMZE@>Z50rdP7~%Mw9VmBAy%_mzru-9?c~)QmHJa;VYKP*{qyO mOh^aiQZ}DTW)gBTD`h6q2`L{9UxrtxhyBpsK7@|KtLZ "$DBPATH/restore_tsr-schema-create.sql" +for i in $(seq "$TABLE_COUNT"); do + echo "CREATE TABLE tbl$i(i TINYINT);" > "$DBPATH/restore_tsr.tbl$i-schema.sql" + echo "INSERT INTO tbl$i VALUES (1);" > "$DBPATH/restore_tsr.tbl$i.sql" +done + +# Count OpenEngine and CloseEngine events. +# Abort if number of unbalanced OpenEngine is >= 4 +export GO_FAILPOINTS='github.com/pingcap/br/pkg/lightning/backend/FailIfEngineCountExceeds=return(4)' + +# Start importing +run_sql 'DROP DATABASE IF EXISTS restore_tsr' +run_lightning -d "$DBPATH" +echo "Import finished" + +# Verify all data are imported +for i in $(seq "$TABLE_COUNT"); do + run_sql "SELECT sum(i) FROM restore_tsr.tbl$i;" + check_contains 'sum(i): 1' +done diff --git a/tests/lightning_routes/config.toml b/tests/lightning_routes/config.toml new file mode 100644 index 000000000..dbbe7c75b --- /dev/null +++ b/tests/lightning_routes/config.toml @@ -0,0 +1,7 @@ +# the complicated routing rules should be tested in tidb-tools repo already +# here we're just verifying the basic things do work. +[[routes]] +schema-pattern = "routes_a*" +table-pattern = "t*" +target-schema = "routes_b" +target-table = "u" diff --git a/tests/lightning_routes/data/routes_a0-schema-create.sql b/tests/lightning_routes/data/routes_a0-schema-create.sql new file mode 100644 index 000000000..13ae4918b --- /dev/null +++ b/tests/lightning_routes/data/routes_a0-schema-create.sql @@ -0,0 +1 @@ +create database routes_a0; diff --git a/tests/lightning_routes/data/routes_a0.t0-schema.sql b/tests/lightning_routes/data/routes_a0.t0-schema.sql new file mode 100644 index 000000000..0df8bb24c --- /dev/null +++ b/tests/lightning_routes/data/routes_a0.t0-schema.sql @@ -0,0 +1 @@ +create table t0 (x real primary key); diff --git a/tests/lightning_routes/data/routes_a0.t0.1.sql b/tests/lightning_routes/data/routes_a0.t0.1.sql new file mode 100644 index 000000000..e6fe0676b --- /dev/null +++ b/tests/lightning_routes/data/routes_a0.t0.1.sql @@ -0,0 +1 @@ +insert into t0 values (1.0); \ No newline at end of file diff --git a/tests/lightning_routes/data/routes_a0.t0.2.sql b/tests/lightning_routes/data/routes_a0.t0.2.sql new file mode 100644 index 000000000..c5ebd7ac4 --- /dev/null +++ b/tests/lightning_routes/data/routes_a0.t0.2.sql @@ -0,0 +1 @@ +insert into t0 values (6.0); \ No newline at end of file diff --git a/tests/lightning_routes/data/routes_a0.t1-schema.sql b/tests/lightning_routes/data/routes_a0.t1-schema.sql new file mode 100644 index 000000000..640043cdd --- /dev/null +++ b/tests/lightning_routes/data/routes_a0.t1-schema.sql @@ -0,0 +1 @@ +create table t1 (x real primary key); diff --git a/tests/lightning_routes/data/routes_a0.t1.1.sql b/tests/lightning_routes/data/routes_a0.t1.1.sql new file mode 100644 index 000000000..562dc778d --- /dev/null +++ b/tests/lightning_routes/data/routes_a0.t1.1.sql @@ -0,0 +1 @@ +insert into t1 values (36.0); \ No newline at end of file diff --git a/tests/lightning_routes/data/routes_a1-schema-create.sql b/tests/lightning_routes/data/routes_a1-schema-create.sql new file mode 100644 index 000000000..7f76665e1 --- /dev/null +++ b/tests/lightning_routes/data/routes_a1-schema-create.sql @@ -0,0 +1 @@ +create database routes_a1; diff --git a/tests/lightning_routes/data/routes_a1.s1-schema.sql b/tests/lightning_routes/data/routes_a1.s1-schema.sql new file mode 100644 index 000000000..9f6be4767 --- /dev/null +++ b/tests/lightning_routes/data/routes_a1.s1-schema.sql @@ -0,0 +1 @@ +create table s1 (x real primary key); \ No newline at end of file diff --git a/tests/lightning_routes/data/routes_a1.s1.sql b/tests/lightning_routes/data/routes_a1.s1.sql new file mode 100644 index 000000000..51826fc36 --- /dev/null +++ b/tests/lightning_routes/data/routes_a1.s1.sql @@ -0,0 +1 @@ +insert into s1 values (1296.0); \ No newline at end of file diff --git a/tests/lightning_routes/data/routes_a1.t2-schema.sql b/tests/lightning_routes/data/routes_a1.t2-schema.sql new file mode 100644 index 000000000..f07b1f267 --- /dev/null +++ b/tests/lightning_routes/data/routes_a1.t2-schema.sql @@ -0,0 +1 @@ +create table t2 (x real primary key); \ No newline at end of file diff --git a/tests/lightning_routes/data/routes_a1.t2.sql b/tests/lightning_routes/data/routes_a1.t2.sql new file mode 100644 index 000000000..48f0da53e --- /dev/null +++ b/tests/lightning_routes/data/routes_a1.t2.sql @@ -0,0 +1 @@ +insert into t2 values (216.0); \ No newline at end of file diff --git a/tests/lightning_routes/run.sh b/tests/lightning_routes/run.sh new file mode 100755 index 000000000..efe801dc8 --- /dev/null +++ b/tests/lightning_routes/run.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +# Basic check for whether routing rules work + +set -eux + +run_sql 'DROP DATABASE IF EXISTS routes_a0;' +run_sql 'DROP DATABASE IF EXISTS routes_a1;' +run_sql 'DROP DATABASE IF EXISTS routes_b;' + +run_lightning + +run_sql 'SELECT count(1), sum(x) FROM routes_b.u;' +check_contains 'count(1): 4' +check_contains 'sum(x): 259' + +run_sql 'SELECT count(1), sum(x) FROM routes_a1.s1;' +check_contains 'count(1): 1' +check_contains 'sum(x): 1296' + +run_sql 'SHOW TABLES IN routes_a1;' +check_not_contains 'Tables_in_routes_a1: t2' + +run_sql 'SHOW DATABASES;' +check_not_contains 'Database: routes_a0' diff --git a/tests/lightning_row-format-v2/config.toml b/tests/lightning_row-format-v2/config.toml new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_row-format-v2/data/rowformatv2-schema-create.sql b/tests/lightning_row-format-v2/data/rowformatv2-schema-create.sql new file mode 100644 index 000000000..373b0a77d --- /dev/null +++ b/tests/lightning_row-format-v2/data/rowformatv2-schema-create.sql @@ -0,0 +1 @@ +create schema rowformatv2; diff --git a/tests/lightning_row-format-v2/data/rowformatv2.t1-schema.sql b/tests/lightning_row-format-v2/data/rowformatv2.t1-schema.sql new file mode 100644 index 000000000..bd2025b3d --- /dev/null +++ b/tests/lightning_row-format-v2/data/rowformatv2.t1-schema.sql @@ -0,0 +1,258 @@ +create table t1 ( + col0 int, + col1 int, + col2 int, + col3 int, + col4 int, + col5 int, + col6 int, + col7 int, + col8 int, + col9 int, + col10 int, + col11 int, + col12 int, + col13 int, + col14 int, + col15 int, + col16 int, + col17 int, + col18 int, + col19 int, + col20 int, + col21 int, + col22 int, + col23 int, + col24 int, + col25 int, + col26 int, + col27 int, + col28 int, + col29 int, + col30 int, + col31 int, + col32 int, + col33 int, + col34 int, + col35 int, + col36 int, + col37 int, + col38 int, + col39 int, + col40 int, + col41 int, + col42 int, + col43 int, + col44 int, + col45 int, + col46 int, + col47 int, + col48 int, + col49 int, + col50 int, + col51 int, + col52 int, + col53 int, + col54 int, + col55 int, + col56 int, + col57 int, + col58 int, + col59 int, + col60 int, + col61 int, + col62 int, + col63 int, + col64 int, + col65 int, + col66 int, + col67 int, + col68 int, + col69 int, + col70 int, + col71 int, + col72 int, + col73 int, + col74 int, + col75 int, + col76 int, + col77 int, + col78 int, + col79 int, + col80 int, + col81 int, + col82 int, + col83 int, + col84 int, + col85 int, + col86 int, + col87 int, + col88 int, + col89 int, + col90 int, + col91 int, + col92 int, + col93 int, + col94 int, + col95 int, + col96 int, + col97 int, + col98 int, + col99 int, + col100 int, + col101 int, + col102 int, + col103 int, + col104 int, + col105 int, + col106 int, + col107 int, + col108 int, + col109 int, + col110 int, + col111 int, + col112 int, + col113 int, + col114 int, + col115 int, + col116 int, + col117 int, + col118 int, + col119 int, + col120 int, + col121 int, + col122 int, + col123 int, + col124 int, + col125 int, + col126 int, + col127 int, + col128 int, + col129 int, + col130 int, + col131 int, + col132 int, + col133 int, + col134 int, + col135 int, + col136 int, + col137 int, + col138 int, + col139 int, + col140 int, + col141 int, + col142 int, + col143 int, + col144 int, + col145 int, + col146 int, + col147 int, + col148 int, + col149 int, + col150 int, + col151 int, + col152 int, + col153 int, + col154 int, + col155 int, + col156 int, + col157 int, + col158 int, + col159 int, + col160 int, + col161 int, + col162 int, + col163 int, + col164 int, + col165 int, + col166 int, + col167 int, + col168 int, + col169 int, + col170 int, + col171 int, + col172 int, + col173 int, + col174 int, + col175 int, + col176 int, + col177 int, + col178 int, + col179 int, + col180 int, + col181 int, + col182 int, + col183 int, + col184 int, + col185 int, + col186 int, + col187 int, + col188 int, + col189 int, + col190 int, + col191 int, + col192 int, + col193 int, + col194 int, + col195 int, + col196 int, + col197 int, + col198 int, + col199 int, + col200 int, + col201 int, + col202 int, + col203 int, + col204 int, + col205 int, + col206 int, + col207 int, + col208 int, + col209 int, + col210 int, + col211 int, + col212 int, + col213 int, + col214 int, + col215 int, + col216 int, + col217 int, + col218 int, + col219 int, + col220 int, + col221 int, + col222 int, + col223 int, + col224 int, + col225 int, + col226 int, + col227 int, + col228 int, + col229 int, + col230 int, + col231 int, + col232 int, + col233 int, + col234 int, + col235 int, + col236 int, + col237 int, + col238 int, + col239 int, + col240 int, + col241 int, + col242 int, + col243 int, + col244 int, + col245 int, + col246 int, + col247 int, + col248 int, + col249 int, + col250 int, + col251 int, + col252 int, + col253 int, + col254 int, + col255 int +); diff --git a/tests/lightning_row-format-v2/data/rowformatv2.t1.1.sql b/tests/lightning_row-format-v2/data/rowformatv2.t1.1.sql new file mode 100644 index 000000000..e9f2c2404 --- /dev/null +++ b/tests/lightning_row-format-v2/data/rowformatv2.t1.1.sql @@ -0,0 +1,51 @@ +INSERT INTO t1 VALUES +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 39, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 2, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 46, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 89, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 45, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 76, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 96, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 89, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 4, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 83, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 68, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 39, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 49, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 81, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 17, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 64, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 82, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 38, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 23, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 79, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 47, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 68, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 79, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 74, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 90, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 39, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 76, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 30, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 72, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 32, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 24, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 22, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 3, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 70, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 26, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 69, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 76, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 47, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL), +(NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 9, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/lightning_row-format-v2/run.sh b/tests/lightning_row-format-v2/run.sh new file mode 100644 index 000000000..ea86d7ee0 --- /dev/null +++ b/tests/lightning_row-format-v2/run.sh @@ -0,0 +1,28 @@ +#!/bin/sh + +# Basic check for correctness when row format v2 is active. +# (There's no way to verify if the rows are really in format v2 though...) + +set -eux + +run_sql 'show variables like "%tidb_row_format_version%";' +row_format=$(grep 'Value: [0-9]' "$TEST_DIR/sql_res.$TEST_NAME.txt" | awk '{print $2}') + +if [ "$row_format" -ne "2" ]; then +run_sql 'SET @@global.tidb_row_format_version = 2;' || { echo 'TiDB does not support changing row format version! skipping test'; exit 0; } +fi + +run_sql 'DROP DATABASE IF EXISTS rowformatv2;' + +run_lightning + +run_sql 'SELECT count(1) FROM rowformatv2.t1;' +check_contains 'count(1): 50' + +run_sql 'SELECT DISTINCT col14 FROM rowformatv2.t1;' +check_contains 'col14: NULL' +check_contains 'col14: 39' + +if [ "$row_format" -ne "2" ]; then +run_sql "SET @@global.tidb_row_format_version = $row_format;" +fi diff --git a/tests/lightning_s3/config.toml b/tests/lightning_s3/config.toml new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_s3/run.sh b/tests/lightning_s3/run.sh new file mode 100755 index 000000000..4a37f6059 --- /dev/null +++ b/tests/lightning_s3/run.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# +# Copyright 2020 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eux +DB="s3_test" +TABLE="tbl" + +check_cluster_version 4 0 0 'local backend' || exit 0 + +set -euE + +# Populate the mydumper source +DBPATH="$TEST_DIR/s3.mydump" + +# start the s3 server +export MINIO_ACCESS_KEY=s3accesskey +export MINIO_SECRET_KEY=s3secretkey +export MINIO_BROWSER=off +export S3_ENDPOINT=127.0.0.1:9900 +rm -rf "$TEST_DIR/$DB" +mkdir -p "$TEST_DIR/$DB" +bin/minio server --address $S3_ENDPOINT "$DBPATH" & +MINIO_PID=$! +i=0 +while ! curl -o /dev/null -v -s "http://$S3_ENDPOINT/"; do + i=$(($i+1)) + if [ $i -gt 7 ]; then + echo 'Failed to start minio' + exit 1 + fi + sleep 2 +done + +stop_minio() { + kill -2 $MINIO_PID +} +trap stop_minio EXIT + +BUCKET=test-bucket +DATA_PATH=$DBPATH/$BUCKET +mkdir -p $DATA_PATH +echo 'CREATE DATABASE s3_test;' > "$DATA_PATH/$DB-schema-create.sql" +echo "CREATE TABLE t(i INT, s varchar(32));" > "$DATA_PATH/$DB.$TABLE-schema.sql" +echo 'INSERT INTO tbl (i, s) VALUES (1, "1"),(2, "test2"), (3, "qqqtest");' > "$DATA_PATH/$DB.$TABLE.sql" +cat > "$DATA_PATH/$DB.$TABLE.0.csv" << _EOF_ +i,s +100,"test100" +101,"\"" +102,"😄😄😄😄😄" +104,"" +_EOF_ + +# Fill in the database +# Start importing the tables. +run_sql "DROP DATABASE IF EXISTS $DB;" +run_sql "DROP TABLE IF EXISTS $DB.$TABLE;" + +SOURCE_DIR="s3://$BUCKET/?endpoint=http%3A//127.0.0.1%3A9900&access_key=$MINIO_ACCESS_KEY&secret_access_key=$MINIO_SECRET_KEY&force_path_style=true" +run_lightning -d $SOURCE_DIR --backend local 2> /dev/null +run_sql "SELECT count(*), sum(i) FROM \`$DB\`.$TABLE" +check_contains "count(*): 7" +check_contains "sum(i): 413" diff --git a/tests/lightning_source_linkfile/config.toml b/tests/lightning_source_linkfile/config.toml new file mode 100644 index 000000000..629cf31ad --- /dev/null +++ b/tests/lightning_source_linkfile/config.toml @@ -0,0 +1 @@ +[lightning] diff --git a/tests/lightning_source_linkfile/run.sh b/tests/lightning_source_linkfile/run.sh new file mode 100644 index 000000000..83608d664 --- /dev/null +++ b/tests/lightning_source_linkfile/run.sh @@ -0,0 +1,60 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +# test source dir that contains soft/hard link files + +set -euE + +# Populate the mydumper source +DBPATH="$TEST_DIR/sf.mydump" +RAW_PATH="${DBPATH}_tmp" + +mkdir -p $DBPATH $TEST_DIR/sf.mydump_tmp +DB=linkfiles + +ROW_COUNT=1000 + +echo "CREATE DATABASE $DB;" > "$RAW_PATH/$DB-schema-create.sql" +echo "CREATE TABLE t(s varchar(64), i INT, j TINYINT, PRIMARY KEY(s, i));" > "$RAW_PATH/$DB.t-schema.sql" +echo "CREATE TABLE t2(i INT PRIMARY KEY, s varchar(32));" > "$RAW_PATH/$DB.t2-schema.sql" + +echo "s,i,j" > "$RAW_PATH/$DB.t.0.csv" +for i in $(seq "$ROW_COUNT"); do + echo "\"thisisastringvalues_line$i\",$i,$i" >> "$RAW_PATH/$DB.t.0.csv" +done + +echo "i,s" > "$RAW_PATH/$DB.t2.0.csv" +for i in $(seq $ROW_COUNT); do + echo "$i,\"test123ataettaet$i\"" >> "$RAW_PATH/$DB.t2.0.csv" +done + +# link source files to source dir +ls $RAW_PATH | xargs -I{} ln -s $RAW_PATH/{} $DBPATH/{} + +for BACKEND in tidb local; do + if [ "$BACKEND" = 'local' ]; then + check_cluster_version 4 0 0 'local backend' || continue + fi + + # Start importing the tables. + run_sql "DROP DATABASE IF EXISTS $DB" + + run_lightning -d "$DBPATH" --backend $BACKEND 2> /dev/null + run_sql "SELECT count(*) FROM $DB.t" + check_contains "count(*): $ROW_COUNT" + + run_sql "SELECT count(*) FROM $DB.t2" + check_contains "count(*): $ROW_COUNT" +done diff --git a/tests/lightning_sqlmode/data/sqlmodedb-schema-create.sql b/tests/lightning_sqlmode/data/sqlmodedb-schema-create.sql new file mode 100644 index 000000000..f39e114f8 --- /dev/null +++ b/tests/lightning_sqlmode/data/sqlmodedb-schema-create.sql @@ -0,0 +1 @@ +create database sqlmodedb; diff --git a/tests/lightning_sqlmode/data/sqlmodedb.t-schema.sql b/tests/lightning_sqlmode/data/sqlmodedb.t-schema.sql new file mode 100644 index 000000000..9317ba2ec --- /dev/null +++ b/tests/lightning_sqlmode/data/sqlmodedb.t-schema.sql @@ -0,0 +1,7 @@ +create table t ( + id INT PRIMARY KEY, + a TIMESTAMP NOT NULL, + b TINYINT NOT NULL, + c VARCHAR(1) CHARSET latin1 NOT NULL, + d SET('x','y') NOT NULL +); diff --git a/tests/lightning_sqlmode/data/sqlmodedb.t.1.sql b/tests/lightning_sqlmode/data/sqlmodedb.t.1.sql new file mode 100644 index 000000000..99b995081 --- /dev/null +++ b/tests/lightning_sqlmode/data/sqlmodedb.t.1.sql @@ -0,0 +1,6 @@ +insert into t values +(1, 9, 128, 'too long', 'x,y,z'), +(2, '2000-00-00 00:00:00', -99999, '🤩', 3), +(3, '9999-12-31 23:59:59', 'NaN', x'99', 'x+y'), +(4, '2000-01-01 00:00:00', 99.999, '', 'x,y,x,y,x,y,x,y'), +(5, NULL, NULL, NULL, NULL); diff --git a/tests/lightning_sqlmode/off.toml b/tests/lightning_sqlmode/off.toml new file mode 100644 index 000000000..497d14a9e --- /dev/null +++ b/tests/lightning_sqlmode/off.toml @@ -0,0 +1,2 @@ +[tidb] +sql-mode = 'ALLOW_INVALID_DATES' diff --git a/tests/lightning_sqlmode/on.toml b/tests/lightning_sqlmode/on.toml new file mode 100644 index 000000000..06b646468 --- /dev/null +++ b/tests/lightning_sqlmode/on.toml @@ -0,0 +1,2 @@ +[tidb] +sql-mode = 'STRICT_TRANS_TABLES' diff --git a/tests/lightning_sqlmode/run.sh b/tests/lightning_sqlmode/run.sh new file mode 100755 index 000000000..67643d5ce --- /dev/null +++ b/tests/lightning_sqlmode/run.sh @@ -0,0 +1,57 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +run_sql 'DROP DATABASE IF EXISTS sqlmodedb' + +run_lightning --config "tests/$TEST_NAME/off.toml" + +run_sql 'SELECT a, b, hex(c), d FROM sqlmodedb.t WHERE id = 1' +check_contains 'a: 0000-00-00 00:00:00' +check_contains 'b: 127' +check_contains 'hex(c): 74' +check_contains 'd: ' + +run_sql 'SELECT a, b, hex(c), d FROM sqlmodedb.t WHERE id = 2' +check_contains 'a: 0000-00-00 00:00:00' +check_contains 'b: -128' +check_contains 'hex(c): F0' +check_contains 'd: x,y' + +run_sql 'SELECT a, b, hex(c), d FROM sqlmodedb.t WHERE id = 3' +check_contains 'a: 0000-00-00 00:00:00' +check_contains 'b: 0' +check_contains 'hex(c): 99' +check_contains 'd: ' + +run_sql 'SELECT a, b, hex(c), d FROM sqlmodedb.t WHERE id = 4' +check_contains 'a: 2000-01-01 00:00:00' +check_contains 'b: 100' +check_contains 'hex(c): ' +check_contains 'd: x,y' + +run_sql 'SELECT a, b, hex(c), d FROM sqlmodedb.t WHERE id = 5' +check_contains 'a: 0000-00-00 00:00:00' +check_contains 'b: 0' +check_contains 'hex(c): ' +check_contains 'd: ' + +run_sql 'DROP DATABASE IF EXISTS sqlmodedb' + +set +e +run_lightning --config "tests/$TEST_NAME/on.toml" --log-file "$TEST_DIR/sqlmode-error.log" +[ $? -ne 0 ] || exit 1 +set -e + +grep -q '\["kv convert failed"\].*\[original=.*kind=uint64,val=9.*\] \[originalCol=1\] \[colName=a\] \[colType="timestamp BINARY"\]' "$TEST_DIR/sqlmode-error.log" diff --git a/tests/lightning_tidb_duplicate_data/data/dup-schema-create.sql b/tests/lightning_tidb_duplicate_data/data/dup-schema-create.sql new file mode 100644 index 000000000..5895df523 --- /dev/null +++ b/tests/lightning_tidb_duplicate_data/data/dup-schema-create.sql @@ -0,0 +1 @@ +CREATE DATABASE dup; diff --git a/tests/lightning_tidb_duplicate_data/data/dup.dup-schema.sql b/tests/lightning_tidb_duplicate_data/data/dup.dup-schema.sql new file mode 100644 index 000000000..a113b9edb --- /dev/null +++ b/tests/lightning_tidb_duplicate_data/data/dup.dup-schema.sql @@ -0,0 +1 @@ +create table dup (pk int primary key, d varchar(3)); diff --git a/tests/lightning_tidb_duplicate_data/data/dup.dup.sql b/tests/lightning_tidb_duplicate_data/data/dup.dup.sql new file mode 100644 index 000000000..ecbb05702 --- /dev/null +++ b/tests/lightning_tidb_duplicate_data/data/dup.dup.sql @@ -0,0 +1,3 @@ +insert into dup values +(1, 'old'), +(2, 'old'); diff --git a/tests/lightning_tidb_duplicate_data/error.toml b/tests/lightning_tidb_duplicate_data/error.toml new file mode 100644 index 000000000..bca2d5218 --- /dev/null +++ b/tests/lightning_tidb_duplicate_data/error.toml @@ -0,0 +1,3 @@ +[tikv-importer] +backend = "tidb" +on-duplicate = "error" diff --git a/tests/lightning_tidb_duplicate_data/ignore.toml b/tests/lightning_tidb_duplicate_data/ignore.toml new file mode 100644 index 000000000..a67cd6e47 --- /dev/null +++ b/tests/lightning_tidb_duplicate_data/ignore.toml @@ -0,0 +1,3 @@ +[tikv-importer] +backend = "tidb" +on-duplicate = "ignore" diff --git a/tests/lightning_tidb_duplicate_data/replace.toml b/tests/lightning_tidb_duplicate_data/replace.toml new file mode 100644 index 000000000..d39282f03 --- /dev/null +++ b/tests/lightning_tidb_duplicate_data/replace.toml @@ -0,0 +1,2 @@ +[tikv-importer] +backend = "tidb" diff --git a/tests/lightning_tidb_duplicate_data/run.sh b/tests/lightning_tidb_duplicate_data/run.sh new file mode 100644 index 000000000..3a4d41a44 --- /dev/null +++ b/tests/lightning_tidb_duplicate_data/run.sh @@ -0,0 +1,64 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eu + +# reset substitution if last time failed half way +# on BSD/macOS sed -i must have a following string as backup filename extension +sed -i.bak 's/new/old/g' "tests/lightning_tidb_duplicate_data/data/dup.dup.sql" && rm tests/lightning_tidb_duplicate_data/data/dup.dup.sql.bak + +for type in replace ignore error; do + run_sql 'DROP DATABASE IF EXISTS dup;' + + export GO_FAILPOINTS="github.com/pingcap/br/pkg/lightning/backend/FailIfImportedSomeRows=return" + set +e + run_lightning --config "tests/$TEST_NAME/$type.toml" 2> /dev/null + ERRORCODE=$? + set -e + [ "$ERRORCODE" -ne 0 ] + + # backup original sql to dup.dup.sql.bak + sed -i.bak 's/old/new/g' "tests/lightning_tidb_duplicate_data/data/dup.dup.sql" + + unset GO_FAILPOINTS + + if [ $type = 'error' ]; then + set +e + run_lightning --config "tests/$TEST_NAME/$type.toml" --log-file "$TEST_DIR/lightning-error-on-dup.log" + ERRORCODE=$? + set -e + [ "$ERRORCODE" -ne 0 ] + tail -20 "$TEST_DIR/lightning-error-on-dup.log" > "$TEST_DIR/lightning-error-on-dup.tail" + grep -Fq 'Duplicate entry' "$TEST_DIR/lightning-error-on-dup.tail" + elif [ $type = 'replace' ]; then + run_lightning --config "tests/$TEST_NAME/$type.toml" + run_sql 'SELECT count(*) FROM dup.dup' + check_contains 'count(*): 2' + run_sql 'SELECT d FROM dup.dup WHERE pk = 1' + check_contains 'd: new' + run_sql 'SELECT d FROM dup.dup WHERE pk = 2' + check_contains 'd: new' + elif [ $type = 'ignore' ]; then + run_lightning --config "tests/$TEST_NAME/$type.toml" + run_sql 'SELECT count(*) FROM dup.dup' + check_contains 'count(*): 2' + run_sql 'SELECT d FROM dup.dup WHERE pk = 1' + check_contains 'd: old' + run_sql 'SELECT d FROM dup.dup WHERE pk = 2' + check_contains 'd: new' + fi + + mv tests/lightning_tidb_duplicate_data/data/dup.dup.sql.bak tests/lightning_tidb_duplicate_data/data/dup.dup.sql +done \ No newline at end of file diff --git a/tests/lightning_tidb_rowid/config.toml b/tests/lightning_tidb_rowid/config.toml new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_tidb_rowid/data/rowid-schema-create.sql b/tests/lightning_tidb_rowid/data/rowid-schema-create.sql new file mode 100644 index 000000000..d06ab2f96 --- /dev/null +++ b/tests/lightning_tidb_rowid/data/rowid-schema-create.sql @@ -0,0 +1 @@ +CREATE DATABASE rowid; diff --git a/tests/lightning_tidb_rowid/data/rowid.explicit_tidb_rowid-schema.sql b/tests/lightning_tidb_rowid/data/rowid.explicit_tidb_rowid-schema.sql new file mode 100644 index 000000000..4f1d63448 --- /dev/null +++ b/tests/lightning_tidb_rowid/data/rowid.explicit_tidb_rowid-schema.sql @@ -0,0 +1 @@ +create table explicit_tidb_rowid (pk varchar(6) primary key); \ No newline at end of file diff --git a/tests/lightning_tidb_rowid/data/rowid.explicit_tidb_rowid.sql b/tests/lightning_tidb_rowid/data/rowid.explicit_tidb_rowid.sql new file mode 100644 index 000000000..f3769cc81 --- /dev/null +++ b/tests/lightning_tidb_rowid/data/rowid.explicit_tidb_rowid.sql @@ -0,0 +1,11 @@ +insert into non_pk (pk, _tidb_rowid) values +('eight', 8), +('five', 5), +('four', 4), +('nine', 9), +('one', 1), +('seven', 7), +('six', 6), +('ten', 10), +('three', 3), +('two', 2); diff --git a/tests/lightning_tidb_rowid/data/rowid.non_pk-schema.sql b/tests/lightning_tidb_rowid/data/rowid.non_pk-schema.sql new file mode 100644 index 000000000..5b5757644 --- /dev/null +++ b/tests/lightning_tidb_rowid/data/rowid.non_pk-schema.sql @@ -0,0 +1 @@ +create table non_pk (pk varchar(6) primary key); diff --git a/tests/lightning_tidb_rowid/data/rowid.non_pk.sql b/tests/lightning_tidb_rowid/data/rowid.non_pk.sql new file mode 100644 index 000000000..77e2c00be --- /dev/null +++ b/tests/lightning_tidb_rowid/data/rowid.non_pk.sql @@ -0,0 +1,11 @@ +insert into non_pk values +('one'), +('two'), +('three'), +('four'), +('five'), +('six'), +('seven'), +('eight'), +('nine'), +('ten'); diff --git a/tests/lightning_tidb_rowid/data/rowid.non_pk_auto_inc-schema.sql b/tests/lightning_tidb_rowid/data/rowid.non_pk_auto_inc-schema.sql new file mode 100644 index 000000000..a71be02c9 --- /dev/null +++ b/tests/lightning_tidb_rowid/data/rowid.non_pk_auto_inc-schema.sql @@ -0,0 +1,9 @@ +/*!40101 SET NAMES binary*/; +/*!40014 SET FOREIGN_KEY_CHECKS=0*/; + +CREATE TABLE `non_pk_auto_inc` ( + `pk` char(36) NOT NULL, + `id` int(11) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`pk`), + UNIQUE KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; diff --git a/tests/lightning_tidb_rowid/data/rowid.non_pk_auto_inc.sql b/tests/lightning_tidb_rowid/data/rowid.non_pk_auto_inc.sql new file mode 100644 index 000000000..9009cea4f --- /dev/null +++ b/tests/lightning_tidb_rowid/data/rowid.non_pk_auto_inc.sql @@ -0,0 +1,26 @@ +/*!40101 SET NAMES binary*/; +/*!40014 SET FOREIGN_KEY_CHECKS=0*/; +/*!40103 SET TIME_ZONE='+00:00' */; +INSERT INTO `non_pk_auto_inc` VALUES +("c5862b7d-e2a1-11e8-81d3-d5360eceeab8",1), +("d7c9dce1-e2a1-11e8-beea-a3f4b99b3e1e",3), +("d7c9de1f-e2a1-11e8-8630-b1256aff79d4",4), +("d7c9de81-e2a1-11e8-be4f-f17e7808e755",5), +("d7c9ded4-e2a1-11e8-ad15-658b46ee1390",6), +("d7c9df20-e2a1-11e8-91a9-e3a3822c60a7",7), +("d7c9dfb9-e2a1-11e8-a8d7-31054a5bf6a8",8), +("d7c9e002-e2a1-11e8-9ff1-9fc4350e1311",9), +("da71fb0d-e2a1-11e8-891e-835bd645efad",17), +("da71fbd6-e2a1-11e8-9e02-ff5f31a7c894",18), +("da71fc00-e2a1-11e8-9a81-230df4ae8e5e",19), +("da71fc29-e2a1-11e8-9823-37aa4b9b6fd1",20), +("da71fc5e-e2a1-11e8-9a4c-534927b63a63",21), +("da71fc87-e2a1-11e8-ae93-fb9ff0878e13",22), +("da71fcaf-e2a1-11e8-aac5-153d3fc52861",23), +("db87f492-e2a1-11e8-a30e-b3a363c99db5",31), +("db87f6c0-e2a1-11e8-82ea-4f787bed9c70",32), +("db87f716-e2a1-11e8-9caa-3fb2ed9f5bcf",33), +("db87f75f-e2a1-11e8-8778-05a4da66a78d",34), +("db87f7a8-e2a1-11e8-9562-31f8c96addec",35), +("db87f7f1-e2a1-11e8-922b-bbba2c355880",36), +("db87f837-e2a1-11e8-ba19-f9baeeda0855",37); diff --git a/tests/lightning_tidb_rowid/data/rowid.pre_rebase-schema.sql b/tests/lightning_tidb_rowid/data/rowid.pre_rebase-schema.sql new file mode 100644 index 000000000..887540be5 --- /dev/null +++ b/tests/lightning_tidb_rowid/data/rowid.pre_rebase-schema.sql @@ -0,0 +1 @@ +create table pre_rebase (pk varchar(6) primary key) auto_increment=70000; diff --git a/tests/lightning_tidb_rowid/data/rowid.pre_rebase.sql b/tests/lightning_tidb_rowid/data/rowid.pre_rebase.sql new file mode 100644 index 000000000..4852114cf --- /dev/null +++ b/tests/lightning_tidb_rowid/data/rowid.pre_rebase.sql @@ -0,0 +1 @@ +insert into pre_rebase values ('foo'); \ No newline at end of file diff --git a/tests/lightning_tidb_rowid/data/rowid.specific_auto_inc-schema.sql b/tests/lightning_tidb_rowid/data/rowid.specific_auto_inc-schema.sql new file mode 100644 index 000000000..f6962e15a --- /dev/null +++ b/tests/lightning_tidb_rowid/data/rowid.specific_auto_inc-schema.sql @@ -0,0 +1 @@ +create table specific_auto_inc (a varchar(6) primary key, b int unique auto_increment) auto_increment=80000; \ No newline at end of file diff --git a/tests/lightning_tidb_rowid/data/rowid.specific_auto_inc.sql b/tests/lightning_tidb_rowid/data/rowid.specific_auto_inc.sql new file mode 100644 index 000000000..08cf771e4 --- /dev/null +++ b/tests/lightning_tidb_rowid/data/rowid.specific_auto_inc.sql @@ -0,0 +1,7 @@ +insert specific_auto_inc (a, b, _tidb_rowid) values +('aaaaaa', 11, 79995), +('bbbbbb', 22, 79996); +insert specific_auto_inc (a, b, _tidb_rowid) values +('cccccc', 33, 79997), +('dddddd', 44, 79998), +('eeeeee', 55, 79999); diff --git a/tests/lightning_tidb_rowid/run.sh b/tests/lightning_tidb_rowid/run.sh new file mode 100755 index 000000000..4397c2679 --- /dev/null +++ b/tests/lightning_tidb_rowid/run.sh @@ -0,0 +1,80 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +# Verify that _tidb_rowid is correctly adjusted. + +set -eu + +for BACKEND in local importer tidb; do + if [ "$BACKEND" = 'local' ]; then + check_cluster_version 4 0 0 'local backend' || continue + fi + + run_sql 'DROP DATABASE IF EXISTS rowid;' + run_lightning -backend $BACKEND + echo 'Import finished' + + # we can't determine the exact `_tidb_row_id` alloc logic, so just skip this check with tidb backend. + if [ "$BACKEND" != 'tidb' ]; then + run_sql 'SELECT count(*), max(id), min(_tidb_rowid), max(_tidb_rowid) FROM rowid.`non_pk_auto_inc`' + check_contains 'count(*): 22' + check_contains 'max(id): 37' + check_contains 'min(_tidb_rowid): 1' + check_contains 'max(_tidb_rowid): 22' + run_sql 'INSERT INTO rowid.`non_pk_auto_inc` (`pk`) VALUES ("?")' + run_sql 'SELECT id > 37, _tidb_rowid > 22 FROM rowid.`non_pk_auto_inc` WHERE `pk` = "?"' + check_contains 'id > 37: 1' + check_contains '_tidb_rowid > 22: 1' + fi + + for table_name in non_pk explicit_tidb_rowid; do + run_sql "SELECT count(*), min(_tidb_rowid), max(_tidb_rowid) FROM rowid.${table_name}" + check_contains 'count(*): 10' + check_contains 'min(_tidb_rowid): 1' + check_contains 'max(_tidb_rowid): 10' + run_sql "SELECT _tidb_rowid FROM rowid.${table_name} WHERE pk = 'five'" + check_contains '_tidb_rowid: 5' + run_sql "INSERT INTO rowid.${table_name} VALUES ('eleven')" + run_sql "SELECT count(*) FROM rowid.${table_name}" + check_contains 'count(*): 11' + run_sql "SELECT count(*) FROM rowid.${table_name} WHERE pk > '!'" + check_contains 'count(*): 11' + run_sql "SELECT _tidb_rowid > 10 FROM rowid.${table_name} WHERE pk = 'eleven'" + check_contains '_tidb_rowid > 10: 1' + done + + run_sql 'SELECT count(*), min(_tidb_rowid), max(_tidb_rowid) FROM rowid.pre_rebase' + check_contains 'count(*): 1' + if [ "$BACKEND" == 'tidb' ]; then + check_contains 'min(_tidb_rowid): 70000' + check_contains 'max(_tidb_rowid): 70000' + else + check_contains 'min(_tidb_rowid): 1' + check_contains 'max(_tidb_rowid): 1' + fi + run_sql 'INSERT INTO rowid.pre_rebase VALUES ("?")' + run_sql 'SELECT _tidb_rowid > 70000 FROM rowid.pre_rebase WHERE pk = "?"' + check_contains '_tidb_rowid > 70000: 1' + + run_sql 'SELECT count(*) FROM rowid.specific_auto_inc' + check_contains 'count(*): 5' + run_sql 'INSERT INTO rowid.specific_auto_inc (a) VALUES ("ffffff"), ("gggggg")' + run_sql 'SELECT _tidb_rowid > 80000, b > 80000 FROM rowid.specific_auto_inc WHERE a = "ffffff"' + check_contains '_tidb_rowid > 80000: 1' + check_contains 'b > 80000: 1' + run_sql 'SELECT _tidb_rowid > 80000, b > 80000 FROM rowid.specific_auto_inc WHERE a = "gggggg"' + check_contains '_tidb_rowid > 80000: 1' + check_contains 'b > 80000: 1' +done diff --git a/tests/lightning_tiflash/config.toml b/tests/lightning_tiflash/config.toml new file mode 100644 index 000000000..857644a2a --- /dev/null +++ b/tests/lightning_tiflash/config.toml @@ -0,0 +1,2 @@ +[mydumper] +no-schema = true diff --git a/tests/lightning_tiflash/run.sh b/tests/lightning_tiflash/run.sh new file mode 100644 index 000000000..74d4d094e --- /dev/null +++ b/tests/lightning_tiflash/run.sh @@ -0,0 +1,71 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +# before v4.0.5 tiflash doesn't support tls, so we should skip this test then +(check_cluster_version 4 0 5 'TiFlash' && [ -n "$TIFLASH" ]) || exit 0 + +set -euE +# Populate the mydumper source +DBPATH="$TEST_DIR/tiflash.mydump" +mkdir -p $DBPATH +DB=test_tiflash + +cat > "$DBPATH/$DB.t1.0.sql" << _EOF_ +INSERT INTO t1 (s, i, j) VALUES + ("this_is_test1", 1, 1), + ("this_is_test2", 2, 2), + ("this_is_test3", 3, 3), + ("this_is_test4", 4, 4), + ("this_is_test5", 5, 5); +_EOF_ + +echo "INSERT INTO t2 VALUES (1, 1), (2, 2)" > "$DBPATH/$DB.t2.0.sql" +echo "INSERT INTO t2 VALUES (3, 3), (4, 4)" > "$DBPATH/$DB.t2.1.sql" + +tiflash_replica_ready() { + i=0 + run_sql "select sum(AVAILABLE) from information_schema.tiflash_replica WHERE TABLE_NAME = \"$1\"" + while ! check_contains "sum(AVAILABLE): 1" check; do + i=$((i+1)) + if [ "$i" -gt 100 ]; then + echo "wait tiflash replica ready timeout" + return 1 + fi + sleep 3 + run_sql "select sum(AVAILABLE) from information_schema.tiflash_replica WHERE TABLE_NAME = \"$1\"" + done +} + +for BACKEND in importer tidb local; do + run_sql "DROP DATABASE IF EXISTS $DB" + run_sql "CREATE DATABASE $DB" + run_sql "CREATE TABLE $DB.t1 (i INT, j INT, s varchar(32), PRIMARY KEY(s, i));" + run_sql "ALTER TABLE $DB.t1 SET TIFLASH REPLICA 1;" + tiflash_replica_ready t1 + run_sql "CREATE TABLE $DB.t2 (i INT, j TINYINT);" + run_sql "ALTER TABLE $DB.t2 SET TIFLASH REPLICA 1;" + tiflash_replica_ready t2 + + run_lightning -d "$DBPATH" --backend $BACKEND 2> /dev/null + + run_sql "SELECT /*+ read_from_storage(tiflash[t1]) */ count(*), sum(i) FROM \`$DB\`.t1" + check_contains "count(*): 5" + check_contains "sum(i): 15" + + run_sql "SELECT /*+ read_from_storage(tiflash[t2]) */ count(*), sum(i) FROM \`$DB\`.t2" + check_contains "count(*): 4" + check_contains "sum(i): 10" + +done diff --git a/tests/lightning_too_many_columns/config.toml b/tests/lightning_too_many_columns/config.toml new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_too_many_columns/data/too_many_columns-schema-create.sql b/tests/lightning_too_many_columns/data/too_many_columns-schema-create.sql new file mode 100644 index 000000000..0264589a5 --- /dev/null +++ b/tests/lightning_too_many_columns/data/too_many_columns-schema-create.sql @@ -0,0 +1 @@ +create database too_many_columns; diff --git a/tests/lightning_too_many_columns/data/too_many_columns.t-schema.sql b/tests/lightning_too_many_columns/data/too_many_columns.t-schema.sql new file mode 100644 index 000000000..c21436d73 --- /dev/null +++ b/tests/lightning_too_many_columns/data/too_many_columns.t-schema.sql @@ -0,0 +1,259 @@ +create table t +( + COL001 VARCHAR(16), + COL002 VARCHAR(16), + COL003 VARCHAR(16), + COL004 VARCHAR(16), + COL005 VARCHAR(16), + COL006 VARCHAR(16), + COL007 VARCHAR(16), + COL008 VARCHAR(16), + COL009 VARCHAR(16), + COL010 VARCHAR(16), + COL011 VARCHAR(16), + COL012 VARCHAR(16), + COL013 VARCHAR(16), + COL014 VARCHAR(16), + COL015 VARCHAR(16), + COL016 VARCHAR(16), + COL017 VARCHAR(16), + COL018 VARCHAR(16), + COL019 VARCHAR(16), + COL020 VARCHAR(16), + COL021 VARCHAR(16), + COL022 VARCHAR(16), + COL023 VARCHAR(16), + COL024 VARCHAR(16), + COL025 VARCHAR(16), + COL026 VARCHAR(16), + COL027 VARCHAR(16), + COL028 VARCHAR(16), + COL029 VARCHAR(16), + COL030 VARCHAR(16), + COL031 VARCHAR(16), + COL032 VARCHAR(16), + COL033 VARCHAR(16), + COL034 VARCHAR(16), + COL035 VARCHAR(16), + COL036 VARCHAR(16), + COL037 VARCHAR(16), + COL038 VARCHAR(16), + COL039 VARCHAR(16), + COL040 VARCHAR(16), + COL041 VARCHAR(16), + COL042 VARCHAR(16), + COL043 VARCHAR(16), + COL044 VARCHAR(16), + COL045 VARCHAR(16), + COL046 VARCHAR(16), + COL047 VARCHAR(16), + COL048 VARCHAR(16), + COL049 VARCHAR(16), + COL050 VARCHAR(16), + COL051 VARCHAR(16), + COL052 VARCHAR(16), + COL053 VARCHAR(16), + COL054 VARCHAR(16), + COL055 VARCHAR(16), + COL056 VARCHAR(16), + COL057 VARCHAR(16), + COL058 VARCHAR(16), + COL059 VARCHAR(16), + COL060 VARCHAR(16), + COL061 VARCHAR(16), + COL062 VARCHAR(16), + COL063 VARCHAR(16), + COL064 VARCHAR(16), + COL065 VARCHAR(16), + COL066 VARCHAR(16), + COL067 VARCHAR(16), + COL068 VARCHAR(16), + COL069 VARCHAR(16), + COL070 VARCHAR(16), + COL071 VARCHAR(16), + COL072 VARCHAR(16), + COL073 VARCHAR(16), + COL074 VARCHAR(16), + COL075 VARCHAR(16), + COL076 VARCHAR(16), + COL077 VARCHAR(16), + COL078 VARCHAR(16), + COL079 VARCHAR(16), + COL080 VARCHAR(16), + COL081 VARCHAR(16), + COL082 VARCHAR(16), + COL083 VARCHAR(16), + COL084 VARCHAR(16), + COL085 VARCHAR(16), + COL086 VARCHAR(16), + COL087 VARCHAR(16), + COL088 VARCHAR(16), + COL089 VARCHAR(16), + COL090 VARCHAR(16), + COL091 VARCHAR(16), + COL092 VARCHAR(16), + COL093 VARCHAR(16), + COL094 VARCHAR(16), + COL095 VARCHAR(16), + COL096 VARCHAR(16), + COL097 VARCHAR(16), + COL098 VARCHAR(16), + COL099 VARCHAR(16), + COL100 VARCHAR(16), + COL101 VARCHAR(16), + COL102 VARCHAR(16), + COL103 VARCHAR(16), + COL104 VARCHAR(16), + COL105 VARCHAR(16), + COL106 VARCHAR(16), + COL107 VARCHAR(16), + COL108 VARCHAR(16), + COL109 VARCHAR(16), + COL110 VARCHAR(16), + COL111 VARCHAR(16), + COL112 VARCHAR(16), + COL113 VARCHAR(16), + COL114 VARCHAR(16), + COL115 VARCHAR(16), + COL116 VARCHAR(16), + COL117 VARCHAR(16), + COL118 VARCHAR(16), + COL119 VARCHAR(16), + COL120 VARCHAR(16), + COL121 VARCHAR(16), + COL122 VARCHAR(16), + COL123 VARCHAR(16), + COL124 VARCHAR(16), + COL125 VARCHAR(16), + COL126 VARCHAR(16), + COL127 VARCHAR(16), + COL128 VARCHAR(16), + COL129 VARCHAR(16), + COL130 VARCHAR(16), + COL131 VARCHAR(16), + COL132 VARCHAR(16), + COL133 VARCHAR(16), + COL134 VARCHAR(16), + COL135 VARCHAR(16), + COL136 VARCHAR(16), + COL137 VARCHAR(16), + COL138 VARCHAR(16), + COL139 VARCHAR(16), + COL140 VARCHAR(16), + COL141 VARCHAR(16), + COL142 VARCHAR(16), + COL143 VARCHAR(16), + COL144 VARCHAR(16), + COL145 VARCHAR(16), + COL146 VARCHAR(16), + COL147 VARCHAR(16), + COL148 VARCHAR(16), + COL149 VARCHAR(16), + COL150 VARCHAR(16), + COL151 VARCHAR(16), + COL152 VARCHAR(16), + COL153 VARCHAR(16), + COL154 VARCHAR(16), + COL155 VARCHAR(16), + COL156 VARCHAR(16), + COL157 VARCHAR(16), + COL158 VARCHAR(16), + COL159 VARCHAR(16), + COL160 VARCHAR(16), + COL161 VARCHAR(16), + COL162 VARCHAR(16), + COL163 VARCHAR(16), + COL164 VARCHAR(16), + COL165 VARCHAR(16), + COL166 VARCHAR(16), + COL167 VARCHAR(16), + COL168 VARCHAR(16), + COL169 VARCHAR(16), + COL170 VARCHAR(16), + COL171 VARCHAR(16), + COL172 VARCHAR(16), + COL173 VARCHAR(16), + COL174 VARCHAR(16), + COL175 VARCHAR(16), + COL176 VARCHAR(16), + COL177 VARCHAR(16), + COL178 VARCHAR(16), + COL179 VARCHAR(16), + COL180 VARCHAR(16), + COL181 VARCHAR(16), + COL182 VARCHAR(16), + COL183 VARCHAR(16), + COL184 VARCHAR(16), + COL185 VARCHAR(16), + COL186 VARCHAR(16), + COL187 VARCHAR(16), + COL188 VARCHAR(16), + COL189 VARCHAR(16), + COL190 VARCHAR(16), + COL191 VARCHAR(16), + COL192 VARCHAR(16), + COL193 VARCHAR(16), + COL194 VARCHAR(16), + COL195 VARCHAR(16), + COL196 VARCHAR(16), + COL197 VARCHAR(16), + COL198 VARCHAR(16), + COL199 VARCHAR(16), + COL200 VARCHAR(16), + COL201 VARCHAR(16), + COL202 VARCHAR(16), + COL203 VARCHAR(16), + COL204 VARCHAR(16), + COL205 VARCHAR(16), + COL206 VARCHAR(16), + COL207 VARCHAR(16), + COL208 VARCHAR(16), + COL209 VARCHAR(16), + COL210 VARCHAR(16), + COL211 VARCHAR(16), + COL212 VARCHAR(16), + COL213 VARCHAR(16), + COL214 VARCHAR(16), + COL215 VARCHAR(16), + COL216 VARCHAR(16), + COL217 VARCHAR(16), + COL218 VARCHAR(16), + COL219 VARCHAR(16), + COL220 VARCHAR(16), + COL221 VARCHAR(16), + COL222 VARCHAR(16), + COL223 VARCHAR(16), + COL224 VARCHAR(16), + COL225 VARCHAR(16), + COL226 VARCHAR(16), + COL227 VARCHAR(16), + COL228 VARCHAR(16), + COL229 VARCHAR(16), + COL230 VARCHAR(16), + COL231 VARCHAR(16), + COL232 VARCHAR(16), + COL233 VARCHAR(16), + COL234 VARCHAR(16), + COL235 VARCHAR(16), + COL236 VARCHAR(16), + COL237 VARCHAR(16), + COL238 VARCHAR(16), + COL239 VARCHAR(16), + COL240 VARCHAR(16), + COL241 VARCHAR(16), + COL242 VARCHAR(16), + COL243 VARCHAR(16), + COL244 VARCHAR(16), + COL245 VARCHAR(16), + COL246 VARCHAR(16), + COL247 VARCHAR(16), + COL248 VARCHAR(16), + COL249 VARCHAR(16), + COL250 VARCHAR(16), + COL251 VARCHAR(16), + COL252 VARCHAR(16), + COL253 VARCHAR(16), + COL254 VARCHAR(16), + COL255 VARCHAR(16), + COL256 VARCHAR(16) +); diff --git a/tests/lightning_too_many_columns/data/too_many_columns.t.0.csv b/tests/lightning_too_many_columns/data/too_many_columns.t.0.csv new file mode 100644 index 000000000..e129b7b20 --- /dev/null +++ b/tests/lightning_too_many_columns/data/too_many_columns.t.0.csv @@ -0,0 +1,2 @@ +COL001,COL002,COL003,COL004,COL005,COL006,COL007,COL008,COL009,COL010,COL011,COL012,COL013,COL014,COL015,COL016,COL017,COL018,COL019,COL020,COL021,COL022,COL023,COL024,COL025,COL026,COL027,COL028,COL029,COL030,COL031,COL032,COL033,COL034,COL035,COL036,COL037,COL038,COL039,COL040,COL041,COL042,COL043,COL044,COL045,COL046,COL047,COL048,COL049,COL050,COL051,COL052,COL053,COL054,COL055,COL056,COL057,COL058,COL059,COL060,COL061,COL062,COL063,COL064,COL065,COL066,COL067,COL068,COL069,COL070,COL071,COL072,COL073,COL074,COL075,COL076,COL077,COL078,COL079,COL080,COL081,COL082,COL083,COL084,COL085,COL086,COL087,COL088,COL089,COL090,COL091,COL092,COL093,COL094,COL095,COL096,COL097,COL098,COL099,COL100,COL101,COL102,COL103,COL104,COL105,COL106,COL107,COL108,COL109,COL110,COL111,COL112,COL113,COL114,COL115,COL116,COL117,COL118,COL119,COL120,COL121,COL122,COL123,COL124,COL125,COL126,COL127,COL128,COL129,COL130,COL131,COL132,COL133,COL134,COL135,COL136,COL137,COL138,COL139,COL140,COL141,COL142,COL143,COL144,COL145,COL146,COL147,COL148,COL149,COL150,COL151,COL152,COL153,COL154,COL155,COL156,COL157,COL158,COL159,COL160,COL161,COL162,COL163,COL164,COL165,COL166,COL167,COL168,COL169,COL170,COL171,COL172,COL173,COL174,COL175,COL176,COL177,COL178,COL179,COL180,COL181,COL182,COL183,COL184,COL185,COL186,COL187,COL188,COL189,COL190,COL191,COL192,COL193,COL194,COL195,COL196,COL197,COL198,COL199,COL200,COL201,COL202,COL203,COL204,COL205,COL206,COL207,COL208,COL209,COL210,COL211,COL212,COL213,COL214,COL215,COL216,COL217,COL218,COL219,COL220,COL221,COL222,COL223,COL224,COL225,COL226,COL227,COL228,COL229,COL230,COL231,COL232,COL233,COL234,COL235,COL236,COL237,COL238,COL239,COL240,COL241,COL242,COL243,COL244,COL245,COL246,COL247,COL248,COL249,COL250,COL251,COL252,COL253,COL254,COL255,COL256 +1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1011,1012,1013,1014,1015,1016,1017,1018,1019,1020,1021,1022,1023,1024,1025,1026,1027,1028,1029,1030,1031,1032,1033,1034,1035,1036,1037,1038,1039,1040,1041,1042,1043,1044,1045,1046,1047,1048,1049,1050,1051,1052,1053,1054,1055,1056,1057,1058,1059,1060,1061,1062,1063,1064,1065,1066,1067,1068,1069,1070,1071,1072,1073,1074,1075,1076,1077,1078,1079,1080,1081,1082,1083,1084,1085,1086,1087,1088,1089,1090,1091,1092,1093,1094,1095,1096,1097,1098,1099,1100,1101,1102,1103,1104,1105,1106,1107,1108,1109,1110,1111,1112,1113,1114,1115,1116,1117,1118,1119,1120,1121,1122,1123,1124,1125,1126,1127,1128,1129,1130,1131,1132,1133,1134,1135,1136,1137,1138,1139,1140,1141,1142,1143,1144,1145,1146,1147,1148,1149,1150,1151,1152,1153,1154,1155,1156,1157,1158,1159,1160,1161,1162,1163,1164,1165,1166,1167,1168,1169,1170,1171,1172,1173,1174,1175,1176,1177,1178,1179,1180,1181,1182,1183,1184,1185,1186,1187,1188,1189,1190,1191,1192,1193,1194,1195,1196,1197,1198,1199,1200,1201,1202,1203,1204,1205,1206,1207,1208,1209,1210,1211,1212,1213,1214,1215,1216,1217,1218,1219,1220,1221,1222,1223,1224,1225,1226,1227,1228,1229,1230,1231,1232,1233,1234,1235,1236,1237,1238,1239,1240,1241,1242,1243,1244,1245,1246,1247,1248,1249,1250,1251,1252,1253,1254,1255,1256 \ No newline at end of file diff --git a/tests/lightning_too_many_columns/run.sh b/tests/lightning_too_many_columns/run.sh new file mode 100644 index 000000000..22a5f476a --- /dev/null +++ b/tests/lightning_too_many_columns/run.sh @@ -0,0 +1,30 @@ +#!/bin/sh +# +# Copyright 2020 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -eux + +for backend in tidb importer local; do + if [ "$backend" = 'local' ]; then + check_cluster_version 4 0 0 'local backend' || continue + fi + + run_sql 'DROP DATABASE IF EXISTS too_many_columns;' + run_lightning --backend $backend + + run_sql "SELECT * FROM too_many_columns.t" + check_contains 'COL001: 1001' + check_contains 'COL256: 1256' + check_contains 'COL100: 1100' +done diff --git a/tests/lightning_tool_135/config.toml b/tests/lightning_tool_135/config.toml new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_tool_135/data/tool_135-schema-create.sql b/tests/lightning_tool_135/data/tool_135-schema-create.sql new file mode 100644 index 000000000..42d18c1fc --- /dev/null +++ b/tests/lightning_tool_135/data/tool_135-schema-create.sql @@ -0,0 +1 @@ +create database if not exists tool_135; \ No newline at end of file diff --git a/tests/lightning_tool_135/data/tool_135.bar1-schema.sql b/tests/lightning_tool_135/data/tool_135.bar1-schema.sql new file mode 100644 index 000000000..c87e9f258 --- /dev/null +++ b/tests/lightning_tool_135/data/tool_135.bar1-schema.sql @@ -0,0 +1 @@ +create table bar1(a int auto_increment primary key); diff --git a/tests/lightning_tool_135/data/tool_135.bar1.sql b/tests/lightning_tool_135/data/tool_135.bar1.sql new file mode 100644 index 000000000..85e360ec0 --- /dev/null +++ b/tests/lightning_tool_135/data/tool_135.bar1.sql @@ -0,0 +1,10 @@ +insert into bar1 values (102), (103), (104), (105), (106), (107), (108), (109), (110), (111), (112), (113), (114), (115), (116), (117), (118), (119), (120), (121), (122), (123), (124), (125), (126), (127), (128), (129), (130), (131), (132), (133), (134), (135), (136), (137), (138), (139), (140), (141), (142), (143), (144), (145), (146), (147), (148), (149), (150), (151), (152), (153), (154), (155), (156), (157), (158), (159), (160), (161), (162), (163), (164), (165), (166), (167), (168), (169), (170), (171), (172), (173), (174), (175), (176), (177), (178), (179), (180), (181), (182), (183), (184), (185), (186), (187), (188), (189), (190), (191), (192), (193), (194), (195), (196), (197), (198), (199), (200), (201); +insert into bar1 values (202), (203), (204), (205), (206), (207), (208), (209), (210), (211), (212), (213), (214), (215), (216), (217), (218), (219), (220), (221), (222), (223), (224), (225), (226), (227), (228), (229), (230), (231), (232), (233), (234), (235), (236), (237), (238), (239), (240), (241), (242), (243), (244), (245), (246), (247), (248), (249), (250), (251), (252), (253), (254), (255), (256), (257), (258), (259), (260), (261), (262), (263), (264), (265), (266), (267), (268), (269), (270), (271), (272), (273), (274), (275), (276), (277), (278), (279), (280), (281), (282), (283), (284), (285), (286), (287), (288), (289), (290), (291), (292), (293), (294), (295), (296), (297), (298), (299), (300), (301); +insert into bar1 values (302), (303), (304), (305), (306), (307), (308), (309), (310), (311), (312), (313), (314), (315), (316), (317), (318), (319), (320), (321), (322), (323), (324), (325), (326), (327), (328), (329), (330), (331), (332), (333), (334), (335), (336), (337), (338), (339), (340), (341), (342), (343), (344), (345), (346), (347), (348), (349), (350), (351), (352), (353), (354), (355), (356), (357), (358), (359), (360), (361), (362), (363), (364), (365), (366), (367), (368), (369), (370), (371), (372), (373), (374), (375), (376), (377), (378), (379), (380), (381), (382), (383), (384), (385), (386), (387), (388), (389), (390), (391), (392), (393), (394), (395), (396), (397), (398), (399), (400), (401); +insert into bar1 values (402), (403), (404), (405), (406), (407), (408), (409), (410), (411), (412), (413), (414), (415), (416), (417), (418), (419), (420), (421), (422), (423), (424), (425), (426), (427), (428), (429), (430), (431), (432), (433), (434), (435), (436), (437), (438), (439), (440), (441), (442), (443), (444), (445), (446), (447), (448), (449), (450), (451), (452), (453), (454), (455), (456), (457), (458), (459), (460), (461), (462), (463), (464), (465), (466), (467), (468), (469), (470), (471), (472), (473), (474), (475), (476), (477), (478), (479), (480), (481), (482), (483), (484), (485), (486), (487), (488), (489), (490), (491), (492), (493), (494), (495), (496), (497), (498), (499), (500), (501); +insert into bar1 values (502), (503), (504), (505), (506), (507), (508), (509), (510), (511), (512), (513), (514), (515), (516), (517), (518), (519), (520), (521), (522), (523), (524), (525), (526), (527), (528), (529), (530), (531), (532), (533), (534), (535), (536), (537), (538), (539), (540), (541), (542), (543), (544), (545), (546), (547), (548), (549), (550), (551), (552), (553), (554), (555), (556), (557), (558), (559), (560), (561), (562), (563), (564), (565), (566), (567), (568), (569), (570), (571), (572), (573), (574), (575), (576), (577), (578), (579), (580), (581), (582), (583), (584), (585), (586), (587), (588), (589), (590), (591), (592), (593), (594), (595), (596), (597), (598), (599), (600), (601); +insert into bar1 values (602), (603), (604), (605), (606), (607), (608), (609), (610), (611), (612), (613), (614), (615), (616), (617), (618), (619), (620), (621), (622), (623), (624), (625), (626), (627), (628), (629), (630), (631), (632), (633), (634), (635), (636), (637), (638), (639), (640), (641), (642), (643), (644), (645), (646), (647), (648), (649), (650), (651), (652), (653), (654), (655), (656), (657), (658), (659), (660), (661), (662), (663), (664), (665), (666), (667), (668), (669), (670), (671), (672), (673), (674), (675), (676), (677), (678), (679), (680), (681), (682), (683), (684), (685), (686), (687), (688), (689), (690), (691), (692), (693), (694), (695), (696), (697), (698), (699), (700), (701); +insert into bar1 values (702), (703), (704), (705), (706), (707), (708), (709), (710), (711), (712), (713), (714), (715), (716), (717), (718), (719), (720), (721), (722), (723), (724), (725), (726), (727), (728), (729), (730), (731), (732), (733), (734), (735), (736), (737), (738), (739), (740), (741), (742), (743), (744), (745), (746), (747), (748), (749), (750), (751), (752), (753), (754), (755), (756), (757), (758), (759), (760), (761), (762), (763), (764), (765), (766), (767), (768), (769), (770), (771), (772), (773), (774), (775), (776), (777), (778), (779), (780), (781), (782), (783), (784), (785), (786), (787), (788), (789), (790), (791), (792), (793), (794), (795), (796), (797), (798), (799), (800), (801); +insert into bar1 values (802), (803), (804), (805), (806), (807), (808), (809), (810), (811), (812), (813), (814), (815), (816), (817), (818), (819), (820), (821), (822), (823), (824), (825), (826), (827), (828), (829), (830), (831), (832), (833), (834), (835), (836), (837), (838), (839), (840), (841), (842), (843), (844), (845), (846), (847), (848), (849), (850), (851), (852), (853), (854), (855), (856), (857), (858), (859), (860), (861), (862), (863), (864), (865), (866), (867), (868), (869), (870), (871), (872), (873), (874), (875), (876), (877), (878), (879), (880), (881), (882), (883), (884), (885), (886), (887), (888), (889), (890), (891), (892), (893), (894), (895), (896), (897), (898), (899), (900), (901); +insert into bar1 values (902), (903), (904), (905), (906), (907), (908), (909), (910), (911), (912), (913), (914), (915), (916), (917), (918), (919), (920), (921), (922), (923), (924), (925), (926), (927), (928), (929), (930), (931), (932), (933), (934), (935), (936), (937), (938), (939), (940), (941), (942), (943), (944), (945), (946), (947), (948), (949), (950), (951), (952), (953), (954), (955), (956), (957), (958), (959), (960), (961), (962), (963), (964), (965), (966), (967), (968), (969), (970), (971), (972), (973), (974), (975), (976), (977), (978), (979), (980), (981), (982), (983), (984), (985), (986), (987), (988), (989), (990), (991), (992), (993), (994), (995), (996), (997), (998), (999), (1000), (1001); +insert into bar1 values (1002), (1003), (1004), (1005), (1006), (1007), (1008), (1009), (1010), (1011), (1012), (1013), (1014), (1015), (1016), (1017), (1018), (1019), (1020), (1021), (1022), (1023), (1024), (1025), (1026), (1027), (1028), (1029), (1030), (1031), (1032), (1033), (1034), (1035), (1036), (1037), (1038), (1039), (1040), (1041), (1042), (1043), (1044), (1045), (1046), (1047), (1048), (1049), (1050), (1051), (1052), (1053), (1054), (1055), (1056), (1057), (1058), (1059), (1060), (1061), (1062), (1063), (1064), (1065), (1066), (1067), (1068), (1069), (1070), (1071), (1072), (1073), (1074), (1075), (1076), (1077), (1078), (1079), (1080), (1081), (1082), (1083), (1084), (1085), (1086), (1087), (1088), (1089), (1090), (1091), (1092), (1093), (1094), (1095), (1096), (1097), (1098), (1099), (1100), (1101); diff --git a/tests/lightning_tool_135/data/tool_135.bar2-schema.sql b/tests/lightning_tool_135/data/tool_135.bar2-schema.sql new file mode 100644 index 000000000..c6a0aaa3c --- /dev/null +++ b/tests/lightning_tool_135/data/tool_135.bar2-schema.sql @@ -0,0 +1 @@ +create table bar2 (a int auto_increment, key (`a`)); diff --git a/tests/lightning_tool_135/data/tool_135.bar2.sql b/tests/lightning_tool_135/data/tool_135.bar2.sql new file mode 100644 index 000000000..0bf61b28b --- /dev/null +++ b/tests/lightning_tool_135/data/tool_135.bar2.sql @@ -0,0 +1,10 @@ +insert into bar2 values (49), (50), (51), (52), (53), (54), (55), (56), (57), (58), (59), (60), (61), (62), (63), (64), (65), (66), (67), (68), (69), (70), (71), (72), (73), (74), (75), (76), (77), (78), (79), (80), (81), (82), (83), (84), (85), (86), (87), (88), (89), (90), (91), (92), (93), (94), (95), (96), (97), (98), (99), (100), (101), (102), (103), (104), (105), (106), (107), (108), (109), (110), (111), (112), (113), (114), (115), (116), (117), (118), (119), (120), (121), (122), (123), (124), (125), (126), (127), (128), (129), (130), (131), (132), (133), (134), (135), (136), (137), (138), (139), (140), (141), (142), (143), (144), (145), (146), (147), (148); +insert into bar2 values (149), (150), (151), (152), (153), (154), (155), (156), (157), (158), (159), (160), (161), (162), (163), (164), (165), (166), (167), (168), (169), (170), (171), (172), (173), (174), (175), (176), (177), (178), (179), (180), (181), (182), (183), (184), (185), (186), (187), (188), (189), (190), (191), (192), (193), (194), (195), (196), (197), (198), (199), (200), (201), (202), (203), (204), (205), (206), (207), (208), (209), (210), (211), (212), (213), (214), (215), (216), (217), (218), (219), (220), (221), (222), (223), (224), (225), (226), (227), (228), (229), (230), (231), (232), (233), (234), (235), (236), (237), (238), (239), (240), (241), (242), (243), (244), (245), (246), (247), (248); +insert into bar2 values (249), (250), (251), (252), (253), (254), (255), (256), (257), (258), (259), (260), (261), (262), (263), (264), (265), (266), (267), (268), (269), (270), (271), (272), (273), (274), (275), (276), (277), (278), (279), (280), (281), (282), (283), (284), (285), (286), (287), (288), (289), (290), (291), (292), (293), (294), (295), (296), (297), (298), (299), (300), (301), (302), (303), (304), (305), (306), (307), (308), (309), (310), (311), (312), (313), (314), (315), (316), (317), (318), (319), (320), (321), (322), (323), (324), (325), (326), (327), (328), (329), (330), (331), (332), (333), (334), (335), (336), (337), (338), (339), (340), (341), (342), (343), (344), (345), (346), (347), (348); +insert into bar2 values (349), (350), (351), (352), (353), (354), (355), (356), (357), (358), (359), (360), (361), (362), (363), (364), (365), (366), (367), (368), (369), (370), (371), (372), (373), (374), (375), (376), (377), (378), (379), (380), (381), (382), (383), (384), (385), (386), (387), (388), (389), (390), (391), (392), (393), (394), (395), (396), (397), (398), (399), (400), (401), (402), (403), (404), (405), (406), (407), (408), (409), (410), (411), (412), (413), (414), (415), (416), (417), (418), (419), (420), (421), (422), (423), (424), (425), (426), (427), (428), (429), (430), (431), (432), (433), (434), (435), (436), (437), (438), (439), (440), (441), (442), (443), (444), (445), (446), (447), (448); +insert into bar2 values (449), (450), (451), (452), (453), (454), (455), (456), (457), (458), (459), (460), (461), (462), (463), (464), (465), (466), (467), (468), (469), (470), (471), (472), (473), (474), (475), (476), (477), (478), (479), (480), (481), (482), (483), (484), (485), (486), (487), (488), (489), (490), (491), (492), (493), (494), (495), (496), (497), (498), (499), (500), (501), (502), (503), (504), (505), (506), (507), (508), (509), (510), (511), (512), (513), (514), (515), (516), (517), (518), (519), (520), (521), (522), (523), (524), (525), (526), (527), (528), (529), (530), (531), (532), (533), (534), (535), (536), (537), (538), (539), (540), (541), (542), (543), (544), (545), (546), (547), (548); +insert into bar2 values (549), (550), (551), (552), (553), (554), (555), (556), (557), (558), (559), (560), (561), (562), (563), (564), (565), (566), (567), (568), (569), (570), (571), (572), (573), (574), (575), (576), (577), (578), (579), (580), (581), (582), (583), (584), (585), (586), (587), (588), (589), (590), (591), (592), (593), (594), (595), (596), (597), (598), (599), (600), (601), (602), (603), (604), (605), (606), (607), (608), (609), (610), (611), (612), (613), (614), (615), (616), (617), (618), (619), (620), (621), (622), (623), (624), (625), (626), (627), (628), (629), (630), (631), (632), (633), (634), (635), (636), (637), (638), (639), (640), (641), (642), (643), (644), (645), (646), (647), (648); +insert into bar2 values (649), (650), (651), (652), (653), (654), (655), (656), (657), (658), (659), (660), (661), (662), (663), (664), (665), (666), (667), (668), (669), (670), (671), (672), (673), (674), (675), (676), (677), (678), (679), (680), (681), (682), (683), (684), (685), (686), (687), (688), (689), (690), (691), (692), (693), (694), (695), (696), (697), (698), (699), (700), (701), (702), (703), (704), (705), (706), (707), (708), (709), (710), (711), (712), (713), (714), (715), (716), (717), (718), (719), (720), (721), (722), (723), (724), (725), (726), (727), (728), (729), (730), (731), (732), (733), (734), (735), (736), (737), (738), (739), (740), (741), (742), (743), (744), (745), (746), (747), (748); +insert into bar2 values (749), (750), (751), (752), (753), (754), (755), (756), (757), (758), (759), (760), (761), (762), (763), (764), (765), (766), (767), (768), (769), (770), (771), (772), (773), (774), (775), (776), (777), (778), (779), (780), (781), (782), (783), (784), (785), (786), (787), (788), (789), (790), (791), (792), (793), (794), (795), (796), (797), (798), (799), (800), (801), (802), (803), (804), (805), (806), (807), (808), (809), (810), (811), (812), (813), (814), (815), (816), (817), (818), (819), (820), (821), (822), (823), (824), (825), (826), (827), (828), (829), (830), (831), (832), (833), (834), (835), (836), (837), (838), (839), (840), (841), (842), (843), (844), (845), (846), (847), (848); +insert into bar2 values (849), (850), (851), (852), (853), (854), (855), (856), (857), (858), (859), (860), (861), (862), (863), (864), (865), (866), (867), (868), (869), (870), (871), (872), (873), (874), (875), (876), (877), (878), (879), (880), (881), (882), (883), (884), (885), (886), (887), (888), (889), (890), (891), (892), (893), (894), (895), (896), (897), (898), (899), (900), (901), (902), (903), (904), (905), (906), (907), (908), (909), (910), (911), (912), (913), (914), (915), (916), (917), (918), (919), (920), (921), (922), (923), (924), (925), (926), (927), (928), (929), (930), (931), (932), (933), (934), (935), (936), (937), (938), (939), (940), (941), (942), (943), (944), (945), (946), (947), (948); +insert into bar2 values (949), (950), (951), (952), (953), (954), (955), (956), (957), (958), (959), (960), (961), (962), (963), (964), (965), (966), (967), (968), (969), (970), (971), (972), (973), (974), (975), (976), (977), (978), (979), (980), (981), (982), (983), (984), (985), (986), (987), (988), (989), (990), (991), (992), (993), (994), (995), (996), (997), (998), (999), (1000), (1001), (1002), (1003), (1004), (1005), (1006), (1007), (1008), (1009), (1010), (1011), (1012), (1013), (1014), (1015), (1016), (1017), (1018), (1019), (1020), (1021), (1022), (1023), (1024), (1025), (1026), (1027), (1028), (1029), (1030), (1031), (1032), (1033), (1034), (1035), (1036), (1037), (1038), (1039), (1040), (1041), (1042), (1043), (1044), (1045), (1046), (1047), (1048); diff --git a/tests/lightning_tool_135/data/tool_135.bar3-schema.sql b/tests/lightning_tool_135/data/tool_135.bar3-schema.sql new file mode 100644 index 000000000..7c80f883b --- /dev/null +++ b/tests/lightning_tool_135/data/tool_135.bar3-schema.sql @@ -0,0 +1 @@ +create table bar3(a int, b int auto_increment, PRIMARY KEY(a), Unique key idx_b(b)); diff --git a/tests/lightning_tool_135/data/tool_135.bar3.sql b/tests/lightning_tool_135/data/tool_135.bar3.sql new file mode 100644 index 000000000..a2fa952bd --- /dev/null +++ b/tests/lightning_tool_135/data/tool_135.bar3.sql @@ -0,0 +1,10 @@ +insert into bar3 values (776391,146), (845404,147), (64451,148), (914580,149), (971775,150), (200267,151), (700776,152), (1017499,153), (1028205,154), (969075,155), (158739,156), (588962,157), (758396,158), (891459,159), (816741,160), (762463,161), (433295,162), (210772,163), (612322,164), (25473,165), (611842,166), (479123,167), (182476,168), (828985,169), (401483,170), (1035180,171), (211588,172), (370197,173), (536845,174), (934781,175), (346107,176), (60905,177), (393129,178), (506713,179), (402210,180), (925501,181), (1010666,182), (973232,183), (660431,184), (313592,185), (332847,186), (542524,187), (118007,188), (530727,189), (186647,190), (655276,191), (462492,192), (181333,193), (1011663,194), (412511,195), (962020,196), (102286,197), (117487,198), (302306,199), (440770,200), (96242,201), (362162,202), (1007622,203), (1015815,204), (404180,205), (841672,206), (195830,207), (853053,208), (679224,209), (199366,210), (846149,211), (645450,212), (595978,213), (559907,214), (649790,215), (938096,216), (82545,217), (932186,218), (521553,219), (657833,220), (384492,221), (474863,222), (315430,223), (913004,224), (265912,225), (68555,226), (494469,227), (62330,228), (292333,229), (266590,230), (582552,231), (553032,232), (538220,233), (223593,234), (505363,235), (994053,236), (719248,237), (171425,238), (42320,239), (92393,240), (401970,241), (180976,242), (53865,243), (228355,244), (685779,245); +insert into bar3 values (909669,246), (470089,247), (573523,248), (935844,249), (111697,250), (537395,251), (807046,252), (276663,253), (402033,254), (725007,255), (919883,256), (186650,257), (701209,258), (470754,259), (915639,260), (915195,261), (936698,262), (637249,263), (519977,264), (846324,265), (372001,266), (910872,267), (257688,268), (401179,269), (734008,270), (1042313,271), (706541,272), (149096,273), (526298,274), (952805,275), (617172,276), (509575,277), (573757,278), (985712,279), (896772,280), (579646,281), (806928,282), (2542,283), (479572,284), (339536,285), (1009594,286), (806831,287), (560277,288), (247198,289), (501355,290), (902746,291), (855253,292), (786634,293), (139444,294), (651661,295), (91797,296), (546320,297), (202054,298), (137625,299), (134787,300), (916658,301), (41053,302), (752428,303), (257493,304), (424944,305), (315561,306), (541363,307), (657453,308), (667731,309), (937038,310), (848736,311), (598138,312), (47263,313), (752488,314), (270104,315), (956746,316), (647910,317), (568969,318), (669442,319), (854371,320), (60193,321), (114573,322), (55013,323), (655328,324), (271008,325), (380485,326), (1022042,327), (301683,328), (169018,329), (159344,330), (760764,331), (189893,332), (683487,333), (975254,334), (566672,335), (112113,336), (747431,337), (659542,338), (756737,339), (838817,340), (486099,341), (55103,342), (58773,343), (364180,344), (818016,345); +insert into bar3 values (22253,346), (110306,347), (42407,348), (64226,349), (681415,350), (197599,351), (967808,352), (773367,353), (256786,354), (522336,355), (856220,356), (1031557,357), (422844,358), (617418,359), (1010445,360), (540675,361), (1042053,362), (589560,363), (80446,364), (449882,365), (372433,366), (657447,367), (933376,368), (617907,369), (808705,370), (390573,371), (300847,372), (1034031,373), (845667,374), (287077,375), (650342,376), (968385,377), (277151,378), (116061,379), (892673,380), (61770,381), (1030543,382), (185815,383), (288785,384), (278478,385), (943886,386), (927251,387), (638724,388), (545681,389), (467862,390), (792366,391), (1025051,392), (595491,393), (650662,394), (915538,395), (324683,396), (1036939,397), (1024229,398), (591699,399), (1039541,400), (34274,401), (1024008,402), (975465,403), (917804,404), (881463,405), (498754,406), (803065,407), (926462,408), (251902,409), (746601,410), (1048054,411), (703896,412), (671909,413), (983734,414), (347370,415), (440124,416), (701513,417), (1071,418), (607567,419), (358360,420), (786953,421), (157252,422), (184675,423), (298402,424), (287479,425), (70375,426), (810189,427), (67636,428), (711221,429), (395847,430), (149435,431), (547189,432), (875470,433), (521278,434), (619868,435), (94859,436), (416791,437), (212004,438), (261403,439), (334346,440), (867479,441), (929275,442), (802228,443), (416673,444), (233187,445); +insert into bar3 values (928850,446), (539940,447), (576077,448), (593244,449), (349574,450), (540244,451), (1027171,452), (206598,453), (899111,454), (228127,455), (387318,456), (940816,457), (118751,458), (190536,459), (800843,460), (827177,461), (670712,462), (120503,463), (499491,464), (144255,465), (284668,466), (18444,467), (305130,468), (397512,469), (747379,470), (778782,471), (79468,472), (471469,473), (911724,474), (747906,475), (950066,476), (456818,477), (386421,478), (112681,479), (813169,480), (446798,481), (296027,482), (158238,483), (966092,484), (85437,485), (764657,486), (730014,487), (372008,488), (366985,489), (280874,490), (233072,491), (872943,492), (359015,493), (330105,494), (774683,495), (553054,496), (233129,497), (390436,498), (648728,499), (336023,500), (434435,501), (871498,502), (532779,503), (447670,504), (149370,505), (8920,506), (249473,507), (492833,508), (190767,509), (901658,510), (55082,511), (740043,512), (620429,513), (293256,514), (54697,515), (671377,516), (301553,517), (631985,518), (951665,519), (826602,520), (753699,521), (590466,522), (1007342,523), (315695,524), (17095,525), (540513,526), (81252,527), (233459,528), (531660,529), (711055,530), (419321,531), (223548,532), (958016,533), (209616,534), (531034,535), (1042572,536), (464837,537), (938428,538), (293551,539), (567565,540), (30531,541), (674856,542), (214883,543), (499368,544), (81576,545); +insert into bar3 values (154245,546), (328459,547), (243575,548), (831836,549), (925352,550), (780571,551), (942052,552), (635189,553), (459015,554), (20430,555), (397177,556), (993922,557), (465466,558), (958535,559), (1033627,560), (23148,561), (581469,562), (961898,563), (180305,564), (836937,565), (400079,566), (9790,567), (646041,568), (199159,569), (719782,570), (151814,571), (60650,572), (832929,573), (247267,574), (563362,575), (665282,576), (923228,577), (890050,578), (712449,579), (721334,580), (607753,581), (358479,582), (339205,583), (530338,584), (632781,585), (431379,586), (42337,587), (614044,588), (886290,589), (683917,590), (999241,591), (761415,592), (739872,593), (664843,594), (1019376,595), (864854,596), (773941,597), (283426,598), (344111,599), (338779,600), (499147,601), (667754,602), (854964,603), (997124,604), (387922,605), (950103,606), (91460,607), (535515,608), (973272,609), (251563,610), (584720,611), (350508,612), (766564,613), (706673,614), (664037,615), (713491,616), (487065,617), (937408,618), (580026,619), (118730,620), (1032283,621), (686592,622), (360824,623), (562506,624), (181230,625), (300022,626), (1024155,627), (398562,628), (105286,629), (946120,630), (286702,631), (414651,632), (14397,633), (648901,634), (46349,635), (182239,636), (168766,637), (667137,638), (366355,639), (978476,640), (1005488,641), (689055,642), (99573,643), (386227,644), (826945,645); +insert into bar3 values (291566,646), (720241,647), (176344,648), (909613,649), (141834,650), (151981,651), (587265,652), (1000205,653), (903510,654), (582390,655), (239730,656), (154512,657), (458426,658), (878718,659), (447956,660), (205350,661), (734075,662), (157713,663), (849584,664), (516739,665), (151836,666), (412241,667), (94449,668), (29553,669), (108989,670), (764722,671), (300115,672), (851011,673), (24454,674), (503511,675), (351328,676), (144390,677), (964625,678), (922203,679), (159626,680), (850807,681), (913673,682), (224837,683), (280971,684), (558656,685), (272783,686), (613541,687), (330048,688), (820868,689), (905598,690), (424803,691), (275229,692), (371504,693), (470892,694), (316317,695), (78845,696), (862628,697), (692937,698), (301901,699), (957341,700), (494889,701), (68701,702), (409446,703), (729891,704), (879752,705), (1023963,706), (733471,707), (915191,708), (214741,709), (893921,710), (582245,711), (205993,712), (411389,713), (12471,714), (710988,715), (349764,716), (585413,717), (138990,718), (518224,719), (350882,720), (835909,721), (253351,722), (88049,723), (764374,724), (61560,725), (74341,726), (941482,727), (850856,728), (245071,729), (474889,730), (145269,731), (143839,732), (158790,733), (633482,734), (669665,735), (165801,736), (1045843,737), (51572,738), (56902,739), (694368,740), (842300,741), (327886,742), (619471,743), (504083,744), (504964,745); +insert into bar3 values (405232,746), (402256,747), (705088,748), (1004723,749), (196759,750), (820501,751), (871479,752), (850485,753), (553997,754), (1003517,755), (711591,756), (1009317,757), (846911,758), (376421,759), (606398,760), (894285,761), (10100,762), (142952,763), (254318,764), (3940,765), (826547,766), (630348,767), (642042,768), (344766,769), (983576,770), (46477,771), (106902,772), (802250,773), (87933,774), (668898,775), (497251,776), (244283,777), (510124,778), (530001,779), (996817,780), (331078,781), (436125,782), (589177,783), (76922,784), (510947,785), (249957,786), (983353,787), (329253,788), (670787,789), (197581,790), (102593,791), (739309,792), (345955,793), (206019,794), (424870,795), (793789,796), (1019531,797), (478367,798), (672278,799), (514113,800), (198625,801), (696591,802), (301106,803), (201333,804), (192556,805), (466685,806), (847252,807), (1046807,808), (797340,809), (759085,810), (632176,811), (821718,812), (871404,813), (229920,814), (810534,815), (244085,816), (987023,817), (32390,818), (869095,819), (434927,820), (168695,821), (617538,822), (439503,823), (222583,824), (261433,825), (226685,826), (960113,827), (542025,828), (531084,829), (57926,830), (800891,831), (365972,832), (764038,833), (139070,834), (563831,835), (842163,836), (358989,837), (759609,838), (66697,839), (963382,840), (676282,841), (931075,842), (1014901,843), (665881,844), (434184,845); +insert into bar3 values (490746,846), (507318,847), (313845,848), (711156,849), (999072,850), (376765,851), (363489,852), (639831,853), (969533,854), (304603,855), (682439,856), (630547,857), (49649,858), (606297,859), (764504,860), (767235,861), (93404,862), (14655,863), (356508,864), (1011138,865), (187834,866), (507207,867), (92445,868), (330572,869), (842957,870), (940484,871), (926242,872), (786877,873), (1003483,874), (176094,875), (851657,876), (283107,877), (315148,878), (317528,879), (698219,880), (423935,881), (1005021,882), (113227,883), (1012050,884), (648597,885), (526388,886), (809074,887), (733714,888), (137262,889), (240731,890), (1027713,891), (828005,892), (870750,893), (620757,894), (378303,895), (64036,896), (131806,897), (278880,898), (393901,899), (584135,900), (37755,901), (502434,902), (291501,903), (832974,904), (874120,905), (159678,906), (85723,907), (24928,908), (797459,909), (421580,910), (203142,911), (908470,912), (933960,913), (465545,914), (455490,915), (201488,916), (559879,917), (463302,918), (283668,919), (88448,920), (157148,921), (828905,922), (398186,923), (119363,924), (527688,925), (716045,926), (458678,927), (816922,928), (656323,929), (429205,930), (510183,931), (987926,932), (246546,933), (18917,934), (39723,935), (571804,936), (1030002,937), (987619,938), (16487,939), (980933,940), (295747,941), (546899,942), (673420,943), (639301,944), (528991,945); +insert into bar3 values (384868,946), (318834,947), (972463,948), (872686,949), (619714,950), (34006,951), (1005914,952), (165088,953), (251789,954), (946197,955), (468155,956), (919991,957), (851233,958), (429646,959), (807312,960), (855434,961), (827574,962), (415207,963), (71698,964), (737639,965), (325949,966), (947479,967), (522020,968), (371427,969), (119237,970), (558910,971), (857507,972), (14917,973), (167701,974), (389905,975), (296213,976), (404389,977), (490223,978), (265446,979), (63513,980), (812964,981), (998890,982), (343568,983), (727356,984), (14554,985), (177316,986), (598065,987), (954177,988), (821441,989), (541676,990), (78167,991), (217075,992), (39580,993), (782315,994), (460196,995), (655618,996), (966109,997), (950628,998), (816991,999), (135147,1000), (384533,1001), (993460,1002), (102761,1003), (124043,1004), (416310,1005), (439607,1006), (593842,1007), (424314,1008), (473254,1009), (346613,1010), (1012588,1011), (216483,1012), (981011,1013), (123119,1014), (443401,1015), (66014,1016), (740588,1017), (345972,1018), (856688,1019), (1044247,1020), (731622,1021), (163861,1022), (145199,1023), (184746,1024), (653384,1025), (835442,1026), (353908,1027), (927677,1028), (677075,1029), (51425,1030), (547707,1031), (95508,1032), (608013,1033), (433535,1034), (378365,1035), (634724,1036), (297559,1037), (165957,1038), (1027098,1039), (489860,1040), (748087,1041), (666468,1042), (886665,1043), (119331,1044), (723138,1045); +insert into bar3 values (233067,1046), (856822,1047), (117631,1048), (669263,1049), (652498,1050), (743998,1051), (1008313,1052), (1044745,1053), (958967,1054), (542307,1055), (630356,1056), (866693,1057), (790662,1058), (239052,1059), (204873,1060), (867418,1061), (973861,1062), (647395,1063), (668375,1064), (679122,1065), (297583,1066), (275911,1067), (925824,1068), (914890,1069), (826464,1070), (768814,1071), (125240,1072), (454228,1073), (42281,1074), (662876,1075), (594216,1076), (49321,1077), (82064,1078), (591068,1079), (1001189,1080), (861743,1081), (835791,1082), (537153,1083), (692253,1084), (128605,1085), (906850,1086), (573827,1087), (682676,1088), (31604,1089), (624050,1090), (751073,1091), (788036,1092), (156258,1093), (136784,1094), (924637,1095), (671986,1096), (329439,1097), (627691,1098), (442765,1099), (906688,1100), (161495,1101), (644455,1102), (469512,1103), (966580,1104), (943835,1105), (1008537,1106), (149848,1107), (130470,1108), (399414,1109), (804254,1110), (919227,1111), (160761,1112), (110759,1113), (1041774,1114), (816564,1115), (712880,1116), (950072,1117), (507316,1118), (755900,1119), (34537,1120), (687108,1121), (871595,1122), (76778,1123), (88470,1124), (558609,1125), (365590,1126), (715023,1127), (787821,1128), (1038510,1129), (421794,1130), (580602,1131), (673121,1132), (224823,1133), (894645,1134), (965531,1135), (290030,1136), (97523,1137), (578157,1138), (755859,1139), (673095,1140), (922674,1141), (17184,1142), (724554,1143), (780442,1144), (836466,1145); diff --git a/tests/lightning_tool_135/data/tool_135.bar4-schema.sql b/tests/lightning_tool_135/data/tool_135.bar4-schema.sql new file mode 100644 index 000000000..31eedeb90 --- /dev/null +++ b/tests/lightning_tool_135/data/tool_135.bar4-schema.sql @@ -0,0 +1 @@ +create table bar4 (a int auto_increment, unique key (`a`)); diff --git a/tests/lightning_tool_135/data/tool_135.bar4.sql b/tests/lightning_tool_135/data/tool_135.bar4.sql new file mode 100644 index 000000000..fcf41cbdb --- /dev/null +++ b/tests/lightning_tool_135/data/tool_135.bar4.sql @@ -0,0 +1,10 @@ +insert into bar4 values (89), (90), (91), (92), (93), (94), (95), (96), (97), (98), (99), (100), (101), (102), (103), (104), (105), (106), (107), (108), (109), (110), (111), (112), (113), (114), (115), (116), (117), (118), (119), (120), (121), (122), (123), (124), (125), (126), (127), (128), (129), (130), (131), (132), (133), (134), (135), (136), (137), (138), (139), (140), (141), (142), (143), (144), (145), (146), (147), (148), (149), (150), (151), (152), (153), (154), (155), (156), (157), (158), (159), (160), (161), (162), (163), (164), (165), (166), (167), (168), (169), (170), (171), (172), (173), (174), (175), (176), (177), (178), (179), (180), (181), (182), (183), (184), (185), (186), (187), (188); +insert into bar4 values (189), (190), (191), (192), (193), (194), (195), (196), (197), (198), (199), (200), (201), (202), (203), (204), (205), (206), (207), (208), (209), (210), (211), (212), (213), (214), (215), (216), (217), (218), (219), (220), (221), (222), (223), (224), (225), (226), (227), (228), (229), (230), (231), (232), (233), (234), (235), (236), (237), (238), (239), (240), (241), (242), (243), (244), (245), (246), (247), (248), (249), (250), (251), (252), (253), (254), (255), (256), (257), (258), (259), (260), (261), (262), (263), (264), (265), (266), (267), (268), (269), (270), (271), (272), (273), (274), (275), (276), (277), (278), (279), (280), (281), (282), (283), (284), (285), (286), (287), (288); +insert into bar4 values (289), (290), (291), (292), (293), (294), (295), (296), (297), (298), (299), (300), (301), (302), (303), (304), (305), (306), (307), (308), (309), (310), (311), (312), (313), (314), (315), (316), (317), (318), (319), (320), (321), (322), (323), (324), (325), (326), (327), (328), (329), (330), (331), (332), (333), (334), (335), (336), (337), (338), (339), (340), (341), (342), (343), (344), (345), (346), (347), (348), (349), (350), (351), (352), (353), (354), (355), (356), (357), (358), (359), (360), (361), (362), (363), (364), (365), (366), (367), (368), (369), (370), (371), (372), (373), (374), (375), (376), (377), (378), (379), (380), (381), (382), (383), (384), (385), (386), (387), (388); +insert into bar4 values (389), (390), (391), (392), (393), (394), (395), (396), (397), (398), (399), (400), (401), (402), (403), (404), (405), (406), (407), (408), (409), (410), (411), (412), (413), (414), (415), (416), (417), (418), (419), (420), (421), (422), (423), (424), (425), (426), (427), (428), (429), (430), (431), (432), (433), (434), (435), (436), (437), (438), (439), (440), (441), (442), (443), (444), (445), (446), (447), (448), (449), (450), (451), (452), (453), (454), (455), (456), (457), (458), (459), (460), (461), (462), (463), (464), (465), (466), (467), (468), (469), (470), (471), (472), (473), (474), (475), (476), (477), (478), (479), (480), (481), (482), (483), (484), (485), (486), (487), (488); +insert into bar4 values (489), (490), (491), (492), (493), (494), (495), (496), (497), (498), (499), (500), (501), (502), (503), (504), (505), (506), (507), (508), (509), (510), (511), (512), (513), (514), (515), (516), (517), (518), (519), (520), (521), (522), (523), (524), (525), (526), (527), (528), (529), (530), (531), (532), (533), (534), (535), (536), (537), (538), (539), (540), (541), (542), (543), (544), (545), (546), (547), (548), (549), (550), (551), (552), (553), (554), (555), (556), (557), (558), (559), (560), (561), (562), (563), (564), (565), (566), (567), (568), (569), (570), (571), (572), (573), (574), (575), (576), (577), (578), (579), (580), (581), (582), (583), (584), (585), (586), (587), (588); +insert into bar4 values (589), (590), (591), (592), (593), (594), (595), (596), (597), (598), (599), (600), (601), (602), (603), (604), (605), (606), (607), (608), (609), (610), (611), (612), (613), (614), (615), (616), (617), (618), (619), (620), (621), (622), (623), (624), (625), (626), (627), (628), (629), (630), (631), (632), (633), (634), (635), (636), (637), (638), (639), (640), (641), (642), (643), (644), (645), (646), (647), (648), (649), (650), (651), (652), (653), (654), (655), (656), (657), (658), (659), (660), (661), (662), (663), (664), (665), (666), (667), (668), (669), (670), (671), (672), (673), (674), (675), (676), (677), (678), (679), (680), (681), (682), (683), (684), (685), (686), (687), (688); +insert into bar4 values (689), (690), (691), (692), (693), (694), (695), (696), (697), (698), (699), (700), (701), (702), (703), (704), (705), (706), (707), (708), (709), (710), (711), (712), (713), (714), (715), (716), (717), (718), (719), (720), (721), (722), (723), (724), (725), (726), (727), (728), (729), (730), (731), (732), (733), (734), (735), (736), (737), (738), (739), (740), (741), (742), (743), (744), (745), (746), (747), (748), (749), (750), (751), (752), (753), (754), (755), (756), (757), (758), (759), (760), (761), (762), (763), (764), (765), (766), (767), (768), (769), (770), (771), (772), (773), (774), (775), (776), (777), (778), (779), (780), (781), (782), (783), (784), (785), (786), (787), (788); +insert into bar4 values (789), (790), (791), (792), (793), (794), (795), (796), (797), (798), (799), (800), (801), (802), (803), (804), (805), (806), (807), (808), (809), (810), (811), (812), (813), (814), (815), (816), (817), (818), (819), (820), (821), (822), (823), (824), (825), (826), (827), (828), (829), (830), (831), (832), (833), (834), (835), (836), (837), (838), (839), (840), (841), (842), (843), (844), (845), (846), (847), (848), (849), (850), (851), (852), (853), (854), (855), (856), (857), (858), (859), (860), (861), (862), (863), (864), (865), (866), (867), (868), (869), (870), (871), (872), (873), (874), (875), (876), (877), (878), (879), (880), (881), (882), (883), (884), (885), (886), (887), (888); +insert into bar4 values (889), (890), (891), (892), (893), (894), (895), (896), (897), (898), (899), (900), (901), (902), (903), (904), (905), (906), (907), (908), (909), (910), (911), (912), (913), (914), (915), (916), (917), (918), (919), (920), (921), (922), (923), (924), (925), (926), (927), (928), (929), (930), (931), (932), (933), (934), (935), (936), (937), (938), (939), (940), (941), (942), (943), (944), (945), (946), (947), (948), (949), (950), (951), (952), (953), (954), (955), (956), (957), (958), (959), (960), (961), (962), (963), (964), (965), (966), (967), (968), (969), (970), (971), (972), (973), (974), (975), (976), (977), (978), (979), (980), (981), (982), (983), (984), (985), (986), (987), (988); +insert into bar4 values (989), (990), (991), (992), (993), (994), (995), (996), (997), (998), (999), (1000), (1001), (1002), (1003), (1004), (1005), (1006), (1007), (1008), (1009), (1010), (1011), (1012), (1013), (1014), (1015), (1016), (1017), (1018), (1019), (1020), (1021), (1022), (1023), (1024), (1025), (1026), (1027), (1028), (1029), (1030), (1031), (1032), (1033), (1034), (1035), (1036), (1037), (1038), (1039), (1040), (1041), (1042), (1043), (1044), (1045), (1046), (1047), (1048), (1049), (1050), (1051), (1052), (1053), (1054), (1055), (1056), (1057), (1058), (1059), (1060), (1061), (1062), (1063), (1064), (1065), (1066), (1067), (1068), (1069), (1070), (1071), (1072), (1073), (1074), (1075), (1076), (1077), (1078), (1079), (1080), (1081), (1082), (1083), (1084), (1085), (1086), (1087), (1088); diff --git a/tests/lightning_tool_135/data/tool_135.bar5-schema.sql b/tests/lightning_tool_135/data/tool_135.bar5-schema.sql new file mode 100644 index 000000000..3b0949125 --- /dev/null +++ b/tests/lightning_tool_135/data/tool_135.bar5-schema.sql @@ -0,0 +1 @@ +create table bar5(a int, b int auto_increment, primary key(a), key(b)); \ No newline at end of file diff --git a/tests/lightning_tool_135/data/tool_135.bar5.sql b/tests/lightning_tool_135/data/tool_135.bar5.sql new file mode 100644 index 000000000..60392efee --- /dev/null +++ b/tests/lightning_tool_135/data/tool_135.bar5.sql @@ -0,0 +1,10 @@ +insert into bar5 values (961077, 64), (962483, 65), (803091, 66), (169721, 67), (805020, 68), (569930, 69), (692943, 70), (556931, 71), (355833, 72), (748948, 73), (929378, 74), (345412, 75), (471696, 76), (921104, 77), (76005, 78), (394266, 79), (980710, 80), (16816, 81), (101867, 82), (252809, 83), (813961, 84), (326177, 85), (801978, 86), (848167, 87), (98026, 88), (375880, 89), (413323, 90), (63655, 91), (995567, 92), (627253, 93), (891667, 94), (843488, 95), (443254, 96), (646182, 97), (265108, 98), (600415, 99), (758126, 100), (255088, 101), (711617, 102), (1031988, 103), (147732, 104), (280964, 105), (459818, 106), (379351, 107), (731413, 108), (684168, 109), (776543, 110), (194325, 111), (513129, 112), (588785, 113), (541773, 114), (787101, 115), (955307, 116), (468434, 117), (339858, 118), (17876, 119), (381514, 120), (968116, 121), (115734, 122), (471821, 123), (359271, 124), (650456, 125), (366435, 126), (158684, 127), (244927, 128), (857059, 129), (576385, 130), (670155, 131), (285843, 132), (739871, 133), (1033368, 134), (803785, 135), (467781, 136), (58529, 137), (1017108, 138), (814471, 139), (648721, 140), (234494, 141), (659421, 142), (380829, 143), (217446, 144), (620233, 145), (520777, 146), (192127, 147), (655509, 148), (176001, 149), (489667, 150), (829697, 151), (342620, 152), (177823, 153), (1044025, 154), (302717, 155), (289531, 156), (93984, 157), (93484, 158), (931561, 159), (290222, 160), (121630, 161), (23718, 162), (115823, 163); +insert into bar5 values (522956, 164), (516881, 165), (268477, 166), (362396, 167), (1042997, 168), (460731, 169), (59690, 170), (773734, 171), (215808, 172), (374448, 173), (486208, 174), (189864, 175), (236578, 176), (324127, 177), (687395, 178), (940887, 179), (712202, 180), (1012974, 181), (966280, 182), (240676, 183), (692743, 184), (812337, 185), (28255, 186), (596214, 187), (132278, 188), (448852, 189), (616251, 190), (550356, 191), (610533, 192), (955711, 193), (627803, 194), (630329, 195), (298983, 196), (724059, 197), (435056, 198), (805093, 199), (1045125, 200), (901624, 201), (262881, 202), (694923, 203), (819922, 204), (7264, 205), (149784, 206), (72631, 207), (138167, 208), (427982, 209), (916812, 210), (288854, 211), (788863, 212), (760730, 213), (1040698, 214), (10130, 215), (164276, 216), (694343, 217), (685920, 218), (324798, 219), (458423, 220), (437859, 221), (152110, 222), (990035, 223), (530619, 224), (326295, 225), (953901, 226), (141298, 227), (143176, 228), (651983, 229), (710067, 230), (1029498, 231), (32780, 232), (311544, 233), (821333, 234), (689332, 235), (132399, 236), (629846, 237), (888863, 238), (255852, 239), (336606, 240), (781305, 241), (955416, 242), (636528, 243), (206525, 244), (824236, 245), (64772, 246), (826784, 247), (341331, 248), (958078, 249), (775832, 250), (907492, 251), (419692, 252), (725563, 253), (544767, 254), (486554, 255), (912737, 256), (292074, 257), (174443, 258), (213195, 259), (349896, 260), (881344, 261), (861398, 262), (1017150, 263); +insert into bar5 values (255865, 264), (819614, 265), (629653, 266), (654056, 267), (872600, 268), (680575, 269), (318455, 270), (605098, 271), (271929, 272), (297386, 273), (529947, 274), (638623, 275), (843891, 276), (427223, 277), (1145, 278), (217867, 279), (276475, 280), (649892, 281), (696176, 282), (212393, 283), (450962, 284), (172096, 285), (703016, 286), (223126, 287), (187658, 288), (684496, 289), (166737, 290), (542427, 291), (667068, 292), (513624, 293), (151574, 294), (777359, 295), (1027410, 296), (170568, 297), (609521, 298), (844592, 299), (646917, 300), (461756, 301), (456040, 302), (1024086, 303), (667753, 304), (950197, 305), (982004, 306), (71205, 307), (466118, 308), (180360, 309), (384120, 310), (470944, 311), (957749, 312), (652976, 313), (859332, 314), (64393, 315), (38775, 316), (537668, 317), (4737, 318), (673097, 319), (421418, 320), (896884, 321), (586123, 322), (932085, 323), (789193, 324), (4797, 325), (652595, 326), (861695, 327), (866092, 328), (237138, 329), (833871, 330), (269362, 331), (10280, 332), (502410, 333), (965266, 334), (476336, 335), (514137, 336), (56471, 337), (450271, 338), (551003, 339), (359582, 340), (444096, 341), (769278, 342), (769326, 343), (488964, 344), (201357, 345), (781449, 346), (570550, 347), (730561, 348), (713040, 349), (577995, 350), (1040711, 351), (848672, 352), (213112, 353), (652377, 354), (788755, 355), (645365, 356), (987558, 357), (385050, 358), (771734, 359), (712421, 360), (218744, 361), (939144, 362), (224852, 363); +insert into bar5 values (622288, 364), (308570, 365), (16093, 366), (165153, 367), (574601, 368), (520267, 369), (748990, 370), (286206, 371), (326928, 372), (503006, 373), (950997, 374), (430372, 375), (219846, 376), (763512, 377), (588792, 378), (859218, 379), (1008674, 380), (244185, 381), (606510, 382), (284606, 383), (594252, 384), (546969, 385), (284208, 386), (85024, 387), (664136, 388), (845518, 389), (4608, 390), (460781, 391), (176740, 392), (85572, 393), (673127, 394), (63246, 395), (193177, 396), (124988, 397), (757724, 398), (328503, 399), (443383, 400), (666753, 401), (933365, 402), (903120, 403), (83418, 404), (73846, 405), (438224, 406), (1016817, 407), (668106, 408), (201275, 409), (245372, 410), (731551, 411), (632059, 412), (476863, 413), (15465, 414), (804300, 415), (175800, 416), (885997, 417), (407204, 418), (575953, 419), (579002, 420), (1011900, 421), (479664, 422), (418291, 423), (95892, 424), (684147, 425), (971458, 426), (554477, 427), (972076, 428), (753792, 429), (999536, 430), (588842, 431), (59155, 432), (207599, 433), (25171, 434), (344584, 435), (437125, 436), (28006, 437), (721359, 438), (697157, 439), (53253, 440), (655904, 441), (163647, 442), (474516, 443), (127252, 444), (894365, 445), (840825, 446), (386926, 447), (881675, 448), (962584, 449), (506282, 450), (867772, 451), (826940, 452), (244981, 453), (208787, 454), (1018368, 455), (41196, 456), (69013, 457), (754208, 458), (3851, 459), (265504, 460), (399691, 461), (574721, 462), (1029722, 463); +insert into bar5 values (137179, 464), (756882, 465), (27260, 466), (876563, 467), (697934, 468), (600020, 469), (462964, 470), (636484, 471), (27466, 472), (578368, 473), (825391, 474), (698676, 475), (590152, 476), (785035, 477), (219890, 478), (699033, 479), (287407, 480), (634728, 481), (432892, 482), (551536, 483), (182647, 484), (1001244, 485), (830525, 486), (354179, 487), (335661, 488), (405812, 489), (233099, 490), (510354, 491), (241008, 492), (424036, 493), (199292, 494), (30076, 495), (981044, 496), (405795, 497), (944326, 498), (563580, 499), (102155, 500), (1034028, 501), (596733, 502), (145794, 503), (979514, 504), (334190, 505), (103834, 506), (972941, 507), (605084, 508), (66786, 509), (648204, 510), (207359, 511), (79310, 512), (242366, 513), (127346, 514), (870168, 515), (755470, 516), (1043775, 517), (190247, 518), (11279, 519), (6394, 520), (440641, 521), (556191, 522), (107783, 523), (532848, 524), (947177, 525), (787856, 526), (593574, 527), (912736, 528), (625761, 529), (722344, 530), (216100, 531), (358742, 532), (740514, 533), (11855, 534), (638858, 535), (955428, 536), (812371, 537), (837148, 538), (126840, 539), (1006375, 540), (69450, 541), (343054, 542), (1025583, 543), (552553, 544), (972697, 545), (604615, 546), (316827, 547), (789630, 548), (292842, 549), (603035, 550), (255203, 551), (742823, 552), (601383, 553), (377051, 554), (811283, 555), (776801, 556), (513652, 557), (896316, 558), (132966, 559), (93427, 560), (820310, 561), (488407, 562), (211698, 563); +insert into bar5 values (945247, 564), (434377, 565), (859240, 566), (152008, 567), (999542, 568), (848245, 569), (745335, 570), (80247, 571), (877203, 572), (858749, 573), (187084, 574), (624684, 575), (643964, 576), (501265, 577), (624158, 578), (830187, 579), (926891, 580), (910869, 581), (206430, 582), (612505, 583), (120859, 584), (124851, 585), (490286, 586), (391058, 587), (316201, 588), (961618, 589), (90412, 590), (517328, 591), (900117, 592), (303170, 593), (210974, 594), (499600, 595), (1004371, 596), (949992, 597), (723108, 598), (109954, 599), (941736, 600), (964910, 601), (91180, 602), (235158, 603), (911954, 604), (532219, 605), (526074, 606), (347223, 607), (63048, 608), (855841, 609), (702489, 610), (263093, 611), (676014, 612), (125480, 613), (500886, 614), (276116, 615), (464467, 616), (882123, 617), (308553, 618), (488830, 619), (134247, 620), (169859, 621), (463196, 622), (336153, 623), (966777, 624), (291854, 625), (639289, 626), (110761, 627), (881625, 628), (424949, 629), (399029, 630), (303433, 631), (920798, 632), (178281, 633), (538819, 634), (957234, 635), (454775, 636), (412406, 637), (7898, 638), (172095, 639), (825144, 640), (127830, 641), (581484, 642), (1039479, 643), (211719, 644), (30758, 645), (7823, 646), (48936, 647), (305022, 648), (176136, 649), (319056, 650), (738329, 651), (273446, 652), (921142, 653), (529748, 654), (533127, 655), (749340, 656), (324090, 657), (887672, 658), (1032190, 659), (641011, 660), (101946, 661), (1009745, 662), (160173, 663); +insert into bar5 values (317049, 664), (789570, 665), (151780, 666), (961118, 667), (342587, 668), (749987, 669), (330733, 670), (854554, 671), (439488, 672), (837270, 673), (739172, 674), (86005, 675), (1004072, 676), (712221, 677), (328178, 678), (492577, 679), (633630, 680), (813356, 681), (205730, 682), (821776, 683), (979369, 684), (569390, 685), (6103, 686), (430587, 687), (897056, 688), (85062, 689), (581147, 690), (904813, 691), (481204, 692), (993071, 693), (31600, 694), (920583, 695), (672939, 696), (646070, 697), (525138, 698), (222177, 699), (633862, 700), (290277, 701), (741751, 702), (909564, 703), (638141, 704), (785102, 705), (967279, 706), (189657, 707), (774432, 708), (891824, 709), (127149, 710), (973651, 711), (644003, 712), (572876, 713), (1043422, 714), (821773, 715), (78984, 716), (27147, 717), (611272, 718), (640264, 719), (600320, 720), (84022, 721), (769420, 722), (954097, 723), (911077, 724), (745847, 725), (384754, 726), (727574, 727), (637735, 728), (528054, 729), (949212, 730), (416136, 731), (895811, 732), (647389, 733), (377441, 734), (837981, 735), (584897, 736), (478868, 737), (132447, 738), (660040, 739), (444160, 740), (927987, 741), (487388, 742), (878435, 743), (955009, 744), (800232, 745), (529950, 746), (209729, 747), (374336, 748), (375688, 749), (486453, 750), (52390, 751), (157749, 752), (860736, 753), (962868, 754), (972760, 755), (609037, 756), (550567, 757), (376856, 758), (156844, 759), (738592, 760), (632913, 761), (180240, 762), (856810, 763); +insert into bar5 values (456491, 764), (984053, 765), (26488, 766), (991278, 767), (467098, 768), (221256, 769), (962081, 770), (775052, 771), (155571, 772), (588331, 773), (586076, 774), (1038600, 775), (305071, 776), (361674, 777), (15430, 778), (905667, 779), (957898, 780), (758737, 781), (830282, 782), (896508, 783), (54996, 784), (205938, 785), (766826, 786), (1005741, 787), (22425, 788), (164335, 789), (960651, 790), (517843, 791), (281893, 792), (735363, 793), (928810, 794), (352771, 795), (946559, 796), (100079, 797), (670877, 798), (713382, 799), (306156, 800), (1004006, 801), (940302, 802), (60712, 803), (604828, 804), (770210, 805), (944567, 806), (609980, 807), (197927, 808), (88038, 809), (732804, 810), (722116, 811), (19468, 812), (199434, 813), (595448, 814), (548386, 815), (838286, 816), (171508, 817), (685503, 818), (655383, 819), (300127, 820), (309998, 821), (59059, 822), (499607, 823), (911598, 824), (325932, 825), (718954, 826), (716564, 827), (960340, 828), (578449, 829), (920741, 830), (838058, 831), (26667, 832), (45440, 833), (900808, 834), (795082, 835), (257152, 836), (116422, 837), (261057, 838), (64819, 839), (823577, 840), (839320, 841), (64916, 842), (692287, 843), (85596, 844), (136745, 845), (752626, 846), (624484, 847), (519964, 848), (449023, 849), (423901, 850), (37226, 851), (518990, 852), (951478, 853), (475364, 854), (1038658, 855), (9688, 856), (185819, 857), (607435, 858), (524959, 859), (221329, 860), (159588, 861), (924220, 862), (515238, 863); +insert into bar5 values (189993, 864), (452636, 865), (318139, 866), (102894, 867), (726931, 868), (532423, 869), (57206, 870), (449106, 871), (885686, 872), (890336, 873), (545462, 874), (374086, 875), (361186, 876), (1008043, 877), (937273, 878), (874161, 879), (80851, 880), (908802, 881), (527850, 882), (570968, 883), (128278, 884), (585684, 885), (909505, 886), (360060, 887), (813200, 888), (920096, 889), (545374, 890), (320296, 891), (757552, 892), (651405, 893), (113612, 894), (444374, 895), (1007880, 896), (901424, 897), (428986, 898), (599474, 899), (146454, 900), (943329, 901), (989213, 902), (207531, 903), (239244, 904), (903315, 905), (848688, 906), (1045357, 907), (638842, 908), (835098, 909), (604425, 910), (429471, 911), (420850, 912), (940626, 913), (601081, 914), (26422, 915), (618607, 916), (360441, 917), (779615, 918), (662976, 919), (127143, 920), (156669, 921), (438744, 922), (556700, 923), (452312, 924), (653851, 925), (220404, 926), (964015, 927), (811521, 928), (334628, 929), (460850, 930), (335472, 931), (89007, 932), (636451, 933), (720604, 934), (762220, 935), (875434, 936), (170124, 937), (383433, 938), (745772, 939), (742555, 940), (903328, 941), (784686, 942), (949103, 943), (648903, 944), (125263, 945), (618415, 946), (567393, 947), (866291, 948), (497958, 949), (693849, 950), (890740, 951), (764685, 952), (350145, 953), (360884, 954), (284371, 955), (353900, 956), (835311, 957), (59377, 958), (708749, 959), (48199, 960), (90833, 961), (49210, 962), (9419, 963); +insert into bar5 values (672169, 964), (77358, 965), (549291, 966), (747069, 967), (84987, 968), (529014, 969), (325728, 970), (311366, 971), (724799, 972), (572745, 973), (196301, 974), (994681, 975), (199947, 976), (697530, 977), (161301, 978), (285531, 979), (331199, 980), (761588, 981), (868662, 982), (538669, 983), (77898, 984), (899258, 985), (423188, 986), (528061, 987), (17784, 988), (638552, 989), (539355, 990), (92307, 991), (273440, 992), (838951, 993), (508215, 994), (473428, 995), (721459, 996), (699180, 997), (755936, 998), (896542, 999), (526685, 1000), (701382, 1001), (742567, 1002), (724956, 1003), (916887, 1004), (193988, 1005), (825811, 1006), (996238, 1007), (851562, 1008), (443610, 1009), (241994, 1010), (656069, 1011), (459937, 1012), (707706, 1013), (377611, 1014), (595321, 1015), (504031, 1016), (831335, 1017), (40402, 1018), (158564, 1019), (970, 1020), (301824, 1021), (954169, 1022), (478945, 1023), (875285, 1024), (705050, 1025), (684744, 1026), (834995, 1027), (809097, 1028), (588486, 1029), (980610, 1030), (1009487, 1031), (453812, 1032), (332390, 1033), (682983, 1034), (997963, 1035), (892303, 1036), (140634, 1037), (618962, 1038), (1001335, 1039), (591567, 1040), (587615, 1041), (159436, 1042), (2434, 1043), (757087, 1044), (541268, 1045), (852484, 1046), (570863, 1047), (1009097, 1048), (897036, 1049), (715842, 1050), (160560, 1051), (1024563, 1052), (575721, 1053), (618183, 1054), (345147, 1055), (1044467, 1056), (886130, 1057), (747431, 1058), (275444, 1059), (49120, 1060), (307181, 1061), (766670, 1062), (1020643, 1063); diff --git a/tests/lightning_tool_135/run.sh b/tests/lightning_tool_135/run.sh new file mode 100755 index 000000000..3e7a52776 --- /dev/null +++ b/tests/lightning_tool_135/run.sh @@ -0,0 +1,94 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +# This test verifies if TOOL-135 is fixed. + +set -eu + +run_sql 'DROP DATABASE IF EXISTS tool_135;' +run_lightning +echo 'Import finished' + +run_sql 'SELECT count(a), sum(a), min(a), max(a) FROM tool_135.bar1;' +check_contains 'count(a): 1000' +check_contains 'sum(a): 601500' +check_contains 'min(a): 102' +check_contains 'max(a): 1101' +run_sql 'INSERT INTO tool_135.bar1 () VALUES ();' +run_sql 'SELECT count(a), min(a), max(a) > 1101 FROM tool_135.bar1;' +check_contains 'count(a): 1001' +check_contains 'min(a): 102' +check_contains 'max(a) > 1101: 1' + +run_sql 'SELECT count(a), sum(a), min(a), max(a) FROM tool_135.bar2;' +check_contains 'count(a): 1000' +check_contains 'sum(a): 548500' +check_contains 'min(a): 49' +check_contains 'max(a): 1048' +run_sql 'INSERT INTO tool_135.bar2 () VALUES ();' +run_sql 'SELECT count(a), min(a), max(a) > 1048 FROM tool_135.bar2;' +check_contains 'count(a): 1001' +check_contains 'min(a): 49' +check_contains 'max(a) > 1048: 1' + +run_sql 'SELECT count(a), sum(a), min(a), max(a), count(b), sum(b), min(b), max(b) FROM tool_135.bar3;' +check_contains 'count(a): 1000' +check_contains 'sum(a): 532218793' +check_contains 'min(a): 1071' +check_contains 'max(a): 1048054' +check_contains 'count(b): 1000' +check_contains 'sum(b): 645500' +check_contains 'min(b): 146' +check_contains 'max(b): 1145' +run_sql 'INSERT INTO tool_135.bar3 (a) VALUES (229267);' +run_sql 'SELECT count(a), sum(a), min(a), max(a), count(b), min(b), max(b) > 1145 FROM tool_135.bar3;' +check_contains 'count(a): 1001' +check_contains 'sum(a): 532448060' +check_contains 'min(a): 1071' +check_contains 'max(a): 1048054' +check_contains 'count(b): 1001' +check_contains 'min(b): 146' +check_contains 'max(b) > 1145: 1' + +run_sql 'SELECT count(a), sum(a), min(a), max(a) FROM tool_135.bar4;' +check_contains 'count(a): 1000' +check_contains 'sum(a): 588500' +check_contains 'min(a): 89' +check_contains 'max(a): 1088' +run_sql 'INSERT INTO tool_135.bar4 () VALUES ();' +run_sql 'SELECT count(a), min(a), max(a) > 1088 FROM tool_135.bar4;' +check_contains 'count(a): 1001' +check_contains 'min(a): 89' +check_contains 'max(a) > 1088: 1' + +run_sql 'SELECT count(a), sum(a), min(a), max(a), count(b), sum(b), min(b), max(b) FROM tool_135.bar5;' +check_contains 'count(a): 1000' +check_contains 'sum(a): 534846115' +check_contains 'min(a): 970' +check_contains 'max(a): 1045357' +check_contains 'count(b): 1000' +check_contains 'sum(b): 563500' +check_contains 'min(b): 64' +check_contains 'max(b): 1063' +run_sql 'INSERT INTO tool_135.bar5 (a) VALUES (668233);' +run_sql 'SELECT count(a), sum(a), min(a), max(a), count(b), min(b), max(b) > 1063 FROM tool_135.bar5;' +check_contains 'count(a): 1001' +check_contains 'sum(a): 535514348' +check_contains 'min(a): 970' +check_contains 'max(a): 1045357' +check_contains 'count(b): 1001' +check_contains 'min(b): 64' +check_contains 'max(b) > 1063: 1' + diff --git a/tests/lightning_tool_1420/config.toml b/tests/lightning_tool_1420/config.toml new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_tool_1420/data/EE1420-schema-create.sql b/tests/lightning_tool_1420/data/EE1420-schema-create.sql new file mode 100644 index 000000000..979001f57 --- /dev/null +++ b/tests/lightning_tool_1420/data/EE1420-schema-create.sql @@ -0,0 +1 @@ +CREATE DATABASE `EE1420`; diff --git a/tests/lightning_tool_1420/data/EE1420.pt_role-schema.sql b/tests/lightning_tool_1420/data/EE1420.pt_role-schema.sql new file mode 100644 index 000000000..80a078fa8 --- /dev/null +++ b/tests/lightning_tool_1420/data/EE1420.pt_role-schema.sql @@ -0,0 +1,3 @@ +CREATE TABLE `pt_role` ( + `ROLE_ID` varchar(50) NOT NULL +); diff --git a/tests/lightning_tool_1420/data/EE1420.pt_role.sql b/tests/lightning_tool_1420/data/EE1420.pt_role.sql new file mode 100644 index 000000000..238d14a6a --- /dev/null +++ b/tests/lightning_tool_1420/data/EE1420.pt_role.sql @@ -0,0 +1 @@ +INSERT INTO `pt_role` (`ROLE_ID`) VALUES ("1"); diff --git a/tests/lightning_tool_1420/run.sh b/tests/lightning_tool_1420/run.sh new file mode 100755 index 000000000..f8ee83b12 --- /dev/null +++ b/tests/lightning_tool_1420/run.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +# This test verifies if TOOL-1420 is fixed. +# It involves column names not in lower-case. + +set -eu + +run_sql 'DROP DATABASE IF EXISTS `EE1420`;' +run_lightning +run_sql 'SELECT `ROLE_ID` FROM `EE1420`.`pt_role`;' +check_contains 'ROLE_ID: 1' diff --git a/tests/lightning_tool_1472/config.toml b/tests/lightning_tool_1472/config.toml new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_tool_1472/data/EE1472-schema-create.sql b/tests/lightning_tool_1472/data/EE1472-schema-create.sql new file mode 100644 index 000000000..88617459d --- /dev/null +++ b/tests/lightning_tool_1472/data/EE1472-schema-create.sql @@ -0,0 +1 @@ +create database `EE1472`; diff --git a/tests/lightning_tool_1472/data/EE1472.notpk-schema.sql b/tests/lightning_tool_1472/data/EE1472.notpk-schema.sql new file mode 100644 index 000000000..713ed4f60 --- /dev/null +++ b/tests/lightning_tool_1472/data/EE1472.notpk-schema.sql @@ -0,0 +1,5 @@ +create table `notpk` ( + a int primary key, + b tinyint auto_increment, + key(b) +); diff --git a/tests/lightning_tool_1472/data/EE1472.notpk.1.sql b/tests/lightning_tool_1472/data/EE1472.notpk.1.sql new file mode 100644 index 000000000..3b8464134 --- /dev/null +++ b/tests/lightning_tool_1472/data/EE1472.notpk.1.sql @@ -0,0 +1,8 @@ +insert into `notpk` values (1111, 6); +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. diff --git a/tests/lightning_tool_1472/data/EE1472.notpk.2.sql b/tests/lightning_tool_1472/data/EE1472.notpk.2.sql new file mode 100644 index 000000000..ef42b75e9 --- /dev/null +++ b/tests/lightning_tool_1472/data/EE1472.notpk.2.sql @@ -0,0 +1,8 @@ +insert into `notpk` values (2222, 9); +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. diff --git a/tests/lightning_tool_1472/data/EE1472.pk-schema.sql b/tests/lightning_tool_1472/data/EE1472.pk-schema.sql new file mode 100644 index 000000000..187f4edfd --- /dev/null +++ b/tests/lightning_tool_1472/data/EE1472.pk-schema.sql @@ -0,0 +1,3 @@ +create table `pk` ( + a tinyint primary key auto_increment +); diff --git a/tests/lightning_tool_1472/data/EE1472.pk.1.sql b/tests/lightning_tool_1472/data/EE1472.pk.1.sql new file mode 100644 index 000000000..412f35dee --- /dev/null +++ b/tests/lightning_tool_1472/data/EE1472.pk.1.sql @@ -0,0 +1,8 @@ +insert into `pk` values (3); +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. diff --git a/tests/lightning_tool_1472/data/EE1472.pk.2.sql b/tests/lightning_tool_1472/data/EE1472.pk.2.sql new file mode 100644 index 000000000..52d434888 --- /dev/null +++ b/tests/lightning_tool_1472/data/EE1472.pk.2.sql @@ -0,0 +1,8 @@ +insert into `pk` values (4); +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. +-- include some comments to inflate the file size. diff --git a/tests/lightning_tool_1472/run.sh b/tests/lightning_tool_1472/run.sh new file mode 100755 index 000000000..050948edd --- /dev/null +++ b/tests/lightning_tool_1472/run.sh @@ -0,0 +1,31 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +# This test verifies if TOOL-1420 is fixed. +# It involves pre-calculated auto-inc overflowing the tinyint range. + +set -eu + +run_sql 'drop database if exists EE1472;' +run_lightning + +run_sql 'insert into EE1472.pk values ();' +run_sql 'select count(a), max(a) from EE1472.pk;' +check_contains 'count(a): 3' +check_contains 'max(a): 5' + +run_sql 'insert into EE1472.notpk (a) values (3333);' +run_sql 'select b from EE1472.notpk where a = 3333;' +check_contains 'b: 10' diff --git a/tests/lightning_tool_241/config.toml b/tests/lightning_tool_241/config.toml new file mode 100644 index 000000000..d5e4c96e4 --- /dev/null +++ b/tests/lightning_tool_241/config.toml @@ -0,0 +1,5 @@ +[lightning] +table-concurrency = 3 + +[tidb] +sql-mode = '' diff --git a/tests/lightning_tool_241/data/qyjc-schema-create.sql b/tests/lightning_tool_241/data/qyjc-schema-create.sql new file mode 100644 index 000000000..7ab781544 --- /dev/null +++ b/tests/lightning_tool_241/data/qyjc-schema-create.sql @@ -0,0 +1 @@ +CREATE DATABASE qyjc; diff --git a/tests/lightning_tool_241/data/qyjc.q_alarm_group-schema.sql b/tests/lightning_tool_241/data/qyjc.q_alarm_group-schema.sql new file mode 100644 index 000000000..7219c6596 --- /dev/null +++ b/tests/lightning_tool_241/data/qyjc.q_alarm_group-schema.sql @@ -0,0 +1,16 @@ +CREATE TABLE `q_alarm_group` ( +`id` int(11) unsigned NOT NULL AUTO_INCREMENT, +`group_id` int(11) unsigned NOT NULL, +`name` varchar(255) NOT NULL, +`description` varchar(255) NOT NULL, +`send_mail` tinyint(4) unsigned NOT NULL, +`send_msg` tinyint(4) unsigned NOT NULL, +`send_time_from` tinyint(4) unsigned NOT NULL, +`send_time_to` tinyint(4) unsigned NOT NULL, +`receiver_ids` varchar(1024) NOT NULL, +`user_id` int(11) unsigned NOT NULL, +`status` tinyint(4) NOT NULL, +`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', +`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', +PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/tests/lightning_tool_241/data/qyjc.q_alarm_group.sql b/tests/lightning_tool_241/data/qyjc.q_alarm_group.sql new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_tool_241/data/qyjc.q_alarm_message_log-schema.sql b/tests/lightning_tool_241/data/qyjc.q_alarm_message_log-schema.sql new file mode 100644 index 000000000..e6ed9291b --- /dev/null +++ b/tests/lightning_tool_241/data/qyjc.q_alarm_message_log-schema.sql @@ -0,0 +1,16 @@ +CREATE TABLE `q_alarm_message_log` ( +`id` int(11) unsigned NOT NULL AUTO_INCREMENT, +`host_id` int(11) unsigned DEFAULT NULL, +`phone` char(11) NOT NULL, +`email` varchar(256) DEFAULT NULL, +`title` varchar(180) DEFAULT NULL, +`content` varchar(2000) NOT NULL, +`content_md5` char(32) DEFAULT NULL, +`type` tinyint(1) unsigned NOT NULL DEFAULT '0', +`alarm_type` varchar(128) DEFAULT NULL, +`status` tinyint(1) unsigned NOT NULL DEFAULT '1', +`response_code` varchar(256) NOT NULL DEFAULT '0', +`create_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', +`read_status` tinyint(1) unsigned DEFAULT '0', +PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; \ No newline at end of file diff --git a/tests/lightning_tool_241/data/qyjc.q_alarm_message_log.sql b/tests/lightning_tool_241/data/qyjc.q_alarm_message_log.sql new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_tool_241/data/qyjc.q_alarm_receiver-schema.sql b/tests/lightning_tool_241/data/qyjc.q_alarm_receiver-schema.sql new file mode 100644 index 000000000..12d6d211c --- /dev/null +++ b/tests/lightning_tool_241/data/qyjc.q_alarm_receiver-schema.sql @@ -0,0 +1,12 @@ +CREATE TABLE `q_alarm_receiver` ( +`id` int(11) unsigned NOT NULL AUTO_INCREMENT, +`name` varchar(255) NOT NULL, +`email` varchar(255) NOT NULL, +`phone_number` char(50) NOT NULL, +`receiver_id` int(11) unsigned NOT NULL, +`user_id` int(11) unsigned NOT NULL, +`status` tinyint(4) unsigned NOT NULL, +`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', +`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', +PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; \ No newline at end of file diff --git a/tests/lightning_tool_241/data/qyjc.q_config-schema.sql b/tests/lightning_tool_241/data/qyjc.q_config-schema.sql new file mode 100644 index 000000000..a586ceff6 --- /dev/null +++ b/tests/lightning_tool_241/data/qyjc.q_config-schema.sql @@ -0,0 +1,9 @@ +CREATE TABLE `q_config` ( +`id` int(11) unsigned NOT NULL AUTO_INCREMENT, +`key` varchar(255) NOT NULL, +`value` varchar(255) NOT NULL, +`type` tinyint(4) NOT NULL, +`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, +`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', +PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; \ No newline at end of file diff --git a/tests/lightning_tool_241/data/qyjc.q_fish_event-schema.sql b/tests/lightning_tool_241/data/qyjc.q_fish_event-schema.sql new file mode 100644 index 000000000..2ae074517 --- /dev/null +++ b/tests/lightning_tool_241/data/qyjc.q_fish_event-schema.sql @@ -0,0 +1,15 @@ +CREATE TABLE `q_fish_event` ( +`id` int(11) unsigned NOT NULL AUTO_INCREMENT, +`host_id` int(11) unsigned NOT NULL, +`group_id` int(11) unsigned NOT NULL, +`reason` varchar(1000) NOT NULL DEFAULT '', +`affected_url` varchar(1000) NOT NULL DEFAULT '', +`fish_id` int(11) unsigned NOT NULL, +`level` tinyint(1) NOT NULL DEFAULT '2', +`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', +`updated_at` timestamp NULL DEFAULT NULL, +`status` tinyint(1) NOT NULL DEFAULT '0', +PRIMARY KEY (`id`), +KEY `created_at` (`created_at`) USING BTREE, +KEY `idx_event_id` (`host_id`,`group_id`,`created_at`) +) ENGINE=InnoDB AUTO_INCREMENT=8343230 DEFAULT CHARSET=utf8; \ No newline at end of file diff --git a/tests/lightning_tool_241/data/qyjc.q_fish_event.sql b/tests/lightning_tool_241/data/qyjc.q_fish_event.sql new file mode 100644 index 000000000..bd7924baf --- /dev/null +++ b/tests/lightning_tool_241/data/qyjc.q_fish_event.sql @@ -0,0 +1,85 @@ +INSERT INTO `q_fish_event` VALUES +(8343146, 2098099243, 3, '推è更多很多ä½ç½®å¿…须语言表示欢迎.', 'https://www.hou.cn/app.htm', 140, 6, '1991-09-11 02:09:09', '1990-09-03 03:25:18', 0), +(8343147, 472975170, 15, 'å‘展影å“内容程åºä¸èƒ½æ³¨å†Œ.最åŽåº”用æä¾›ç»æµŽé‡è¦ç›´æŽ¥å­¦ä¹ å¸‚场.', 'https://www.su.org/app.php', 175, 4, '1985-09-01 18:30:37', '1984-03-01 08:51:16', 0), +(8343148, 1116621752, 10, '电影系列那个谢谢责任.这样一定时候.', 'http://zhao.net/blog/search.html', 122, 4, '2005-07-05 19:25:05', '2002-07-19 14:13:43', 1), +(8343149, 924521061, 9, '还是实现更新应用你们新闻.', 'http://www.he.com/tags/categories.htm', 117, 7, '1975-10-10 22:13:12', '1993-05-07 12:45:04', 0), +(8343150, 900162728, 8, '管ç†ä¸€åˆ‡äººæ°‘市场.一样客户继续任何拥有控制å¯æ˜¯.', 'http://qiao.com/blog.jsp', 116, 1, '1971-04-22 12:04:22', '2008-11-23 05:41:53', 1), +(8343151, 496431983, 13, 'å‘表点击社区东西æµè§ˆéƒ¨é—¨æŠ€æœ¯.', 'http://www.guo.cn/search/categories.htm', 169, 7, '1989-04-28 05:13:05', '1978-07-09 22:57:34', 0), +(8343152, 1151326485, 13, '由于价格欢迎主è¦.是å¦ç”Ÿäº§å‘布记者那个帮助大家.', 'https://liang.cn/category/blog.html', 126, 9, '2008-06-20 00:54:40', '2016-05-25 18:28:38', 2), +(8343153, 493023720, 6, '用户é‡è¦ä¼šå‘˜äººå‘˜åŽ†å²æœ¬ç«™è¡Œä¸š.', 'http://kang.com/posts/category/list.jsp', 95, 9, '1987-05-29 07:02:49', '2012-08-24 20:48:37', 1), +(8343154, 337609099, 9, '比较还有什么人民ä¸åŒä¸ä¼š.', 'http://ren.cn/app.htm', 83, 4, '1974-05-08 12:53:53', '2008-10-24 01:02:01', 1), +(8343155, 74600483, 14, '活动学校所以ä¸ä¼š.', 'https://www.sun.org/main/category/wp-content.asp', 116, 7, '1999-05-25 09:13:17', NULL, 0), +(8343156, 1470834347, 10, '深圳更多或者内容部门你的.', 'http://luo.cn/wp-content/wp-content.htm', 92, 6, '1979-09-29 17:06:23', '1994-05-06 14:37:17', 0), +(8343157, 1009542213, 9, '教育需è¦è¯„论业务.今年广告文件在线.', 'https://www.ren.com/wp-content/categories.jsp', 146, 9, '2004-11-03 18:25:08', '1970-03-14 06:23:59', 1), +(8343158, 1320621095, 14, 'ä¸è¦ç¤¾ä¼šè§„定本站以上.管ç†ä¸€èˆ¬æ—¥æœŸåœ°æ–¹è°¢è°¢å‘生.', 'http://www.wang.cn/blog/tag.html', 168, 7, '2002-01-10 18:32:25', '2001-06-28 12:35:45', 1), +(8343159, 307227058, 10, '社区因为主è¦å¦‚此而且其他.', 'http://ma.com/main/wp-content.asp', 141, 5, '1974-09-19 08:20:09', '1981-07-28 23:26:28', 0), +(8343160, 250612515, 7, '对于一般增加这个.销售怎么密ç ä¼ä¸šä¸–界销售.', 'http://www.he.cn/categories.php', 168, 1, '2015-05-15 18:22:02', '2014-01-25 23:57:13', 1), +(8343161, 1300548164, 12, '介ç»æµè§ˆæ‰€ä»¥å‘布ä¸æ–­å¸Œæœ›åŽŸå› .', 'https://www.shi.org/categories/tags.php', 138, 4, '1991-11-12 16:40:41', '1979-11-16 05:36:42', 0), +(8343162, 208131968, 15, '责任最åŽå®Œæˆè¿˜æœ‰å¯èƒ½æ˜¯å¦ä¸æ–­.上海虽然处ç†å¸‚场空间.', 'http://www.kang.org/explore/main.jsp', 173, 7, '1974-07-26 05:42:20', '1980-09-19 05:03:27', 2), +(8343163, 264316916, 10, 'æ¥è‡ªå…·æœ‰æ³¨å†Œæ‚¨çš„.以下影å“ä¸è¿‡éŸ³ä¹.', 'https://shen.com/tag.htm', 172, 2, '1992-08-28 15:38:46', '2013-04-11 15:41:16', 0), +(8343164, 623585713, 6, 'å•ä½ä»–的那些研究ä¸è¦è´¨é‡ä¸Šæµ·.类别国内æœç´¢.', 'http://www.chen.cn/wp-content.html', 142, 4, '2007-11-14 19:27:47', '2011-02-05 18:03:58', 2), +(8343165, 895477329, 15, '电影实现为了é‡è¦.', 'https://www.xiong.cn/main/blog.html', 123, 1, '1983-11-07 02:21:38', '1995-11-06 09:11:32', 2), +(8343166, 263379964, 5, '包括是一广告那些谢谢这么.æˆåŠŸæœ¬ç«™æ˜¯ä¸€å›žå¤æ–¹é¢ä¸€ä¸ªæ–¹é¢.', 'http://www.mao.org/search/category/explore.html', 168, 5, '1973-12-04 13:24:25', '1975-04-29 07:11:48', 0), +(8343167, 1722485819, 0, '用户欢迎喜欢系统ä½ç½®.', 'https://liang.com/category.html', 147, 5, '1979-05-29 15:09:11', '1996-07-25 19:17:02', 1), +(8343168, 2130516788, 2, '对于制作原因社会留言.', 'https://www.chen.net/categories.htm', 104, 5, '1978-07-28 16:20:38', NULL, 0), +(8343169, 1834929445, 6, '日期登录状æ€ä¸€åˆ‡æ˜¯å¦æ³•å¾‹.', 'http://song.cn/posts/blog/blog.html', 148, 1, '1987-07-11 01:12:48', '1987-10-30 20:57:02', 0), +(8343170, 388090295, 11, '特别的人两个设计方å¼å¦‚此精åŽ.状æ€ä»Šå¹´è‡ªå·±ç”±äºŽä¸€å®šå¦‚æ­¤å‘布.', 'http://pan.net/tag/search.html', 158, 4, '1994-05-15 23:02:24', '2013-01-09 17:18:47', 1), +(8343171, 788948806, 14, '技术å•ä½ä¸ªäººæ¥è‡ªä¸­å¿ƒä¸šåŠ¡.这些具有会员自己产å“.', 'http://www.qin.com/blog/tag.jsp', 139, 4, '1985-09-13 19:15:55', '2011-12-19 03:20:07', 2), +(8343172, 1153622884, 12, 'åªæœ‰ä»€ä¹ˆå¤§å­¦ç‰¹åˆ«.知é“之间谢谢一个项目信æ¯é€šè¿‡.', 'https://zhao.org/categories/category.html', 176, 4, '2012-05-05 09:17:58', NULL, 0), +(8343173, 46922683, 6, '很多因此作å“ä¸ä¼šé¡¹ç›®ä»Šå¹´æ—¶å€™ä¸‹è½½.类别å„ç§çœ‹åˆ°çœ‹åˆ°è¿™é‡Œç®€ä»‹.', 'http://yang.cn/posts/tag/tag.html', 108, 4, '2006-04-02 18:42:30', '1973-08-02 20:35:06', 1), +(8343174, 2034018602, 5, '销售分æžç»æµŽç”·äººæˆ–者怎么.加入全部研究新闻一切内容电脑表示.', 'http://tao.com/explore/tags/posts.htm', 146, 6, '2013-01-19 10:48:57', '1980-03-31 08:02:13', 2), +(8343175, 1952957952, 0, '应用学习应用.男人主题认为.', 'https://yin.cn/app/search/wp-content.php', 94, 9, '2008-10-31 22:08:57', '1973-07-09 21:17:20', 1), +(8343176, 787751002, 1, 'å„ç§ä¸€ä¸ªå‘表学校文章地方完全.状æ€çœ‹åˆ°å®¢æˆ·å‡ºçŽ°ä¹Ÿæ˜¯æŠ¥å‘Šä»‹ç»ä¸»è¦.', 'http://www.li.org/tag/posts.jsp', 101, 6, '1987-03-10 05:17:51', '2016-06-24 19:24:18', 2), +(8343177, 1438322855, 4, 'ç»è¥è¿™ç§éŸ³ä¹ä¹Ÿæ˜¯.ç»æµŽè¯­è¨€ä¸€ç§å¦‚何影å“.', 'https://www.cai.cn/posts/blog/app.html', 143, 1, '1974-10-29 20:24:59', '2005-03-03 06:30:11', 2), +(8343178, 487548125, 9, '一ç§ç”Ÿäº§æ•°æ®çŠ¶æ€è¿˜æœ‰.', 'http://www.shi.cn/tags.html', 152, 4, '2011-02-27 16:16:11', '1987-01-10 19:09:11', 0), +(8343179, 68342775, 3, '会员环境然åŽ.报告当然方é¢å…费您的方å¼.', 'http://fan.cn/explore/category/app.htm', 149, 4, '2018-02-05 11:45:29', '1992-03-14 12:04:15', 0), +(8343180, 137610451, 6, '游æˆéƒ¨åˆ†è®¾å¤‡è°¢è°¢ç”¨æˆ·ç³»åˆ—最大.两个那个因此ä¸è¿‡.', 'http://feng.org/app/wp-content/wp-content.asp', 142, 5, '1986-04-22 01:39:57', '1985-04-02 10:59:37', 1), +(8343181, 97822589, 10, '其中方法报告商å“国际标题.自己ä¸è¦å‡ºæ¥å…费看到电脑情况部门.', 'https://wang.org/app/tag/main.html', 158, 5, '1982-04-20 12:14:05', '1994-01-27 09:48:22', 2), +(8343182, 927509599, 15, '出æ¥å¸–å­ç„¶åŽçŠ¶æ€æ¬¡æ•°.问题留言æœåŠ¡ç»„织为了报告å称.', 'https://www.gao.net/search.php', 82, 2, '1996-03-19 02:34:33', '2008-04-02 07:38:46', 2), +(8343183, 1891804415, 15, '解决进入由于她的.', 'http://cui.cn/list/posts.html', 117, 4, '1996-10-04 00:24:19', NULL, 1), +(8343184, 1854282300, 9, '标题è¦æ±‚喜欢.其中你们日期东西.', 'http://www.jin.com/app.htm', 85, 5, '2003-02-04 06:53:47', '1996-12-20 22:19:27', 2), +(8343185, 1694238983, 14, 'ä¸åŒå¢žåŠ ç½‘络组织文件.æ–¹å¼è¿‡ç¨‹æ³¨æ„大å°.', 'http://xiong.net/explore.php', 83, 1, '2015-09-25 06:32:22', '2004-03-13 06:23:43', 2), +(8343186, 297900170, 4, '部分一点或者最新为什觉得.', 'http://qiu.org/blog/tag/search.htm', 87, 1, '2003-10-16 22:41:14', '1998-07-14 12:05:36', 1), +(8343187, 1836918281, 11, '全部喜欢一ç§ä»¥åŠç³»ç»Ÿå•†å“希望她的.å‚加知é“的人欢迎.', 'https://www.yuan.cn/list.html', 95, 1, '2002-06-22 01:07:40', '2009-04-06 00:28:57', 0), +(8343188, 1762253105, 15, 'ä¸åŒä¸–界应该已ç»ç±»åž‹å·¥ä½œå½“然.', 'https://www.han.net/blog/explore/search.php', 146, 5, '1994-10-17 09:10:02', '1997-07-07 19:29:00', 0), +(8343189, 562333773, 11, '生活认为系列ç»æµŽ.', 'https://guo.org/blog/wp-content.asp', 141, 3, '1994-11-19 08:48:00', NULL, 1), +(8343190, 78431192, 14, '国家特别å¨æœ›ä¸­å¿ƒ.手机å•ä½å–œæ¬¢ç§‘技ä¸è¦.', 'https://www.wei.cn/posts/categories.php', 139, 4, '1970-08-14 15:13:33', '1999-09-19 13:22:28', 0), +(8343191, 435621264, 4, '关于网络帖å­ç©ºé—´.', 'http://lai.com/list.html', 172, 6, '1987-10-12 13:27:47', '1988-02-09 13:11:01', 2), +(8343192, 350806986, 12, '通过说明é‡è¦ç›´æŽ¥é˜…读电脑他们.', 'http://www.shen.com/explore/app.html', 127, 8, '1996-08-10 20:14:41', '2014-11-23 21:56:23', 1), +(8343193, 1603598016, 10, 'åŒæ—¶å­¦ä¹ ä¸€ä¸ªåŒ…括法律继续.', 'http://li.com/tags/wp-content/list.asp', 112, 1, '2017-01-05 14:15:10', '1971-12-04 00:08:43', 0), +(8343194, 2054965542, 3, 'è”系更新一般å„ç§.', 'http://yang.com/search/categories/tags.htm', 142, 5, '1980-06-29 09:41:32', '2005-08-28 11:32:53', 1), +(8343195, 2119551279, 2, 'å¨æœ›è¿›è¡Œå›½å®¶è®¾è®¡ä¼šå‘˜.', 'https://wan.net/app/tag/categories.html', 131, 5, '1996-06-12 22:10:34', '1971-03-04 08:33:50', 0), +(8343196, 898132030, 7, '自己电影生产一起广告孩å­.', 'https://qiao.cn/list/tag/search.asp', 95, 3, '1993-09-17 04:03:16', NULL, 1), +(8343197, 2077817324, 10, '一ç§æ“作欢迎å¨æœ›æœ€æ–°è§„定.', 'https://bai.net/tags.htm', 149, 8, '1975-11-21 06:47:38', '1982-05-23 01:53:39', 1), +(8343198, 266004219, 9, 'å•ä½æ供因为能够显示ä¸ä¼š.', 'https://www.fan.com/blog/tag/main.htm', 136, 7, '1981-01-10 13:05:57', '2014-11-08 06:55:12', 0), +(8343199, 1130417006, 14, '论å›æ—¶é—´å¸®åŠ©å¥¹çš„我的有关.环境有é™ç›®å‰å¤§å®¶æœ‰äº›ä¸ªäºº.', 'https://www.hou.cn/app.asp', 124, 8, '1981-06-20 20:43:54', NULL, 1), +(8343200, 1116368852, 5, '建设ç»éªŒçŸ¥é“最åŽæ‰€ä»¥èµ„æºå®¢æˆ·.一下客户å‘表都是.', 'http://www.tao.cn/tag/main/explore.php', 95, 9, '2016-01-12 16:56:10', '1988-01-27 09:03:17', 1), +(8343201, 1134004490, 15, '汽车这么查看朋å‹ç”Ÿäº§ä¸ºä»€.登录日期时候方å¼æ—¥æœŸé€šè¿‡æ”¿åºœå¯†ç .', 'https://www.cao.net/categories.htm', 119, 1, '2011-03-29 15:24:31', '1997-12-01 23:51:30', 0), +(8343202, 1789413324, 7, '内容美国留言.', 'https://www.feng.com/main.htm', 147, 7, '1971-03-10 19:51:10', '1972-01-01 07:11:44', 0), +(8343203, 1817641653, 2, '选择首页准备很多.ä¸åŒåŒ…括之åŽçš„人显示国际.', 'http://www.xiao.com/posts/search/main.jsp', 138, 8, '1976-12-10 20:57:40', NULL, 1), +(8343204, 1695229596, 4, '地å€ä¸€å®šè¿‡ç¨‹æŠ•èµ„增加æˆä¸º.一ç§å®Œå…¨é‚£äº›é€šè¿‡ä¸èƒ½åŒ—京.', 'https://song.cn/list/blog/categories.html', 169, 2, '2004-02-24 09:27:50', '2003-04-21 08:02:47', 2), +(8343205, 2080416300, 4, 'ç»æµŽåœ°åŒºå¯æ˜¯åˆä½œå„ç§.è”系如果最大最大æ供时间å‘展.', 'https://www.liao.net/explore/posts.htm', 138, 3, '2011-10-19 13:26:32', '1999-12-05 13:49:48', 0), +(8343206, 83120543, 2, 'æ–¹é¢è§‰å¾—æ¥æºæœ‰é™.', 'http://wen.net/categories/main.html', 169, 9, '2000-10-14 04:07:49', '2017-07-11 23:39:41', 1), +(8343207, 1053601603, 9, 'åªæœ‰ç³»ç»Ÿä»¥ä¸‹ç³»åˆ—æ供如何注æ„.', 'http://yan.cn/search.php', 177, 7, '1977-08-22 18:43:31', '1977-11-19 14:10:46', 0), +(8343208, 973904148, 6, 'å¼€å‘进入主è¦æ ¹æ®å‘布比较.语言能力出æ¥çŠ¶æ€è¯­è¨€æ‰€ä»¥.', 'http://www.fan.com/app/list/wp-content.html', 171, 1, '1976-02-11 00:02:14', '1989-05-11 20:37:50', 2), +(8343209, 1464436810, 7, '一切这是自己类型准备地区.所有知é“å‘展完æˆ.', 'http://www.liang.com/posts.php', 86, 3, '2012-11-10 20:11:49', '1982-08-28 19:57:54', 2), +(8343210, 1644148484, 0, '专业æ高虽然ç»éªŒæ”¯æŒç”µå­å½“å‰å‡ºæ¥.以åŠä»¥åŠä»»ä½•å¦‚此建设å¨æœ›.', 'https://www.shen.cn/app/explore.html', 97, 3, '1979-12-28 15:36:20', '1999-10-25 10:00:32', 0), +(8343211, 125068969, 12, '客户价格销售社会å‘展日期å¯æ˜¯.', 'https://duan.net/list.htm', 125, 5, '1985-05-01 01:48:24', NULL, 2), +(8343212, 1257875888, 14, '管ç†ç»“果责任点击查看点击.还有方å¼è®ºå›äº‹æƒ…更多工具.', 'http://wen.cn/list.php', 80, 1, '1972-07-31 14:09:08', '1984-08-12 20:37:15', 1), +(8343213, 936241507, 8, '记者ä¸æ˜¯è¿™æ ·ç›®å‰.', 'http://www.zhou.cn/tags/app.php', 166, 6, '2005-06-07 04:27:29', '1970-07-26 23:56:42', 1), +(8343214, 338304744, 9, '中文那个还有å„ç§ä¸æ˜¯.', 'http://www.hou.cn/posts.asp', 122, 1, '1983-03-22 04:45:40', '2007-07-16 00:35:58', 2), +(8343215, 388008115, 0, '工具这么开å‘.选择喜欢资料欢迎业务.', 'http://kong.com/tag/category/main.html', 161, 7, '2007-08-20 18:44:03', '1970-04-26 21:50:46', 2), +(8343216, 798505755, 13, '而且分æžæ¸¸æˆç„¶åŽå›žå¤.', 'https://xiao.cn/tags/category.html', 109, 4, '2010-07-03 11:40:25', '2016-10-01 01:52:12', 1), +(8343217, 1923083017, 13, 'è”系资料为了方å¼.个人需è¦èƒ½å¤ŸæŠ¥å‘Šå…¶ä»–文章.', 'https://duan.cn/search/main/main.htm', 106, 6, '1989-12-30 02:51:52', '1998-11-15 14:05:56', 0), +(8343218, 1784530062, 12, '感觉è”系时间ä¸åŒç›´æŽ¥ç‰¹åˆ«.这个空间国内学生æˆåŠŸ.', 'http://www.shen.cn/posts.php', 175, 9, '1972-09-17 17:29:07', '1990-12-05 12:34:45', 0), +(8343219, 1604082241, 6, 'å…¬å¸ä¸ºäº†é‚£ä¹ˆåœ°å€æœ‰é™ä¸€åˆ‡ä¸€èˆ¬ä¸€ç‚¹.åªæœ‰ç³»åˆ—ä»–çš„å称一个.', 'http://tang.com/tags/categories/tags.asp', 102, 9, '1991-03-23 09:48:04', '1981-03-23 05:28:33', 1), +(8343220, 1379455287, 4, '一般的è¯å‡ºæ¥å½“å‰ä¸ºä»€.', 'https://www.wei.com/app/app.htm', 92, 4, '1990-02-08 00:07:54', '2007-01-30 14:13:42', 1), +(8343221, 1008633894, 3, '今年影å“得到特别最大就是.因此作为æ¥æºæ³¨å†Œå…¶ä¸­æ·±åœ³.', 'http://www.gu.cn/category/main/main.php', 106, 8, '1996-10-10 23:49:52', '1990-12-25 05:56:31', 0), +(8343222, 961576215, 2, '需è¦é˜…读我们.', 'http://www.xiong.net/main/list.jsp', 89, 1, '1984-10-31 05:18:26', '2006-09-14 00:05:42', 0), +(8343223, 67772011, 2, 'æœç´¢çš„è¯åˆ†æžå…¨å›½ç±»åˆ«å›žå¤.', 'https://www.meng.com/search/search.html', 122, 8, '1994-12-02 01:18:36', '1987-01-13 21:46:04', 1), +(8343224, 1007895689, 12, '说明关于大å°ä¸‹è½½æ‹¥æœ‰ä¸èƒ½ä¹‹åŽ.一些能够原因.', 'http://gu.com/wp-content.html', 159, 9, '1971-07-05 17:03:15', NULL, 1), +(8343225, 1433707316, 4, '主题更多ä¸åŒè‡ªå·±.', 'https://www.xia.net/category.htm', 170, 7, '2013-01-16 17:41:42', '1998-12-06 19:27:05', 1), +(8343226, 898643134, 11, '那个最大如此学习责任男人精åŽ.', 'https://www.du.net/explore/explore.php', 161, 6, '2000-08-28 10:50:13', '1998-05-12 08:46:57', 0), +(8343227, 1927265790, 3, '关于很多根æ®åœ¨çº¿ç³»åˆ—è´¨é‡æ‰€ä»¥ç”±äºŽ.', 'http://www.lu.com/category.htm', 127, 5, '1983-09-08 07:25:18', NULL, 1), +(8343228, 1145482524, 0, '规定为什喜欢一样这些我们.ä½ç½®æ±½è½¦å…¨éƒ¨ç®¡ç†.', 'http://liao.com/main/category/posts.htm', 107, 9, '1985-07-04 05:51:42', '1977-01-22 02:03:06', 2), +(8343229, 73550511, 9, '市场这里功能空间是å¦æ“作.', 'https://liang.com/blog/category/wp-content.php', 140, 7, '1984-12-28 18:08:37', '1988-07-27 20:32:36', 2); diff --git a/tests/lightning_tool_241/data/qyjc.q_report_circular_data-schema.sql b/tests/lightning_tool_241/data/qyjc.q_report_circular_data-schema.sql new file mode 100644 index 000000000..f25a438ab --- /dev/null +++ b/tests/lightning_tool_241/data/qyjc.q_report_circular_data-schema.sql @@ -0,0 +1,16 @@ +CREATE TABLE `q_report_circular_data` ( +`id` int(11) unsigned NOT NULL AUTO_INCREMENT, +`group_id` int(11) unsigned NOT NULL DEFAULT '0', +`handle_longest` varchar(255) NOT NULL DEFAULT '', +`respond_longest` varchar(255) NOT NULL DEFAULT '', +`circular_more` varchar(255) NOT NULL DEFAULT '', +`circular_count` int(11) unsigned NOT NULL DEFAULT '0', +`iscount_count` int(11) unsigned NOT NULL DEFAULT '0', +`noman_count` int(11) unsigned NOT NULL DEFAULT '0', +`end_time_avg` int(11) unsigned NOT NULL DEFAULT '0', +`respond_time_ave` int(11) unsigned NOT NULL DEFAULT '0', +`day` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', +PRIMARY KEY (`id`), +KEY `group_id` (`group_id`) USING BTREE, +KEY `day` (`day`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; \ No newline at end of file diff --git a/tests/lightning_tool_241/data/qyjc.q_report_desc-schema.sql b/tests/lightning_tool_241/data/qyjc.q_report_desc-schema.sql new file mode 100644 index 000000000..3214f3899 --- /dev/null +++ b/tests/lightning_tool_241/data/qyjc.q_report_desc-schema.sql @@ -0,0 +1,16 @@ +CREATE TABLE `q_report_desc` ( +`id` int(11) unsigned NOT NULL AUTO_INCREMENT, +`group_id` int(11) unsigned NOT NULL DEFAULT '0', +`host_count` int(11) unsigned NOT NULL DEFAULT '0', +`vul_count` int(11) unsigned NOT NULL DEFAULT '0', +`hight_num` int(11) unsigned NOT NULL DEFAULT '0', +`mid_num` int(11) unsigned NOT NULL DEFAULT '0', +`low_num` int(11) unsigned NOT NULL DEFAULT '0', +`notice_num` int(11) unsigned NOT NULL DEFAULT '0', +`day` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', +`time_bucket` tinyint(1) NOT NULL DEFAULT '1', +`created_at` timestamp NULL DEFAULT NULL, +PRIMARY KEY (`id`), +KEY `group_id` (`group_id`) USING BTREE, +KEY `day` (`day`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; \ No newline at end of file diff --git a/tests/lightning_tool_241/data/qyjc.q_report_summary-schema.sql b/tests/lightning_tool_241/data/qyjc.q_report_summary-schema.sql new file mode 100644 index 000000000..0c575b207 --- /dev/null +++ b/tests/lightning_tool_241/data/qyjc.q_report_summary-schema.sql @@ -0,0 +1,14 @@ +CREATE TABLE `q_report_summary` ( +`id` int(11) unsigned NOT NULL AUTO_INCREMENT, +`group_id` int(11) unsigned NOT NULL DEFAULT '0', +`host_count` int(11) unsigned NOT NULL DEFAULT '0', +`circular_count` int(11) unsigned NOT NULL DEFAULT '0', +`active_count` int(11) unsigned NOT NULL DEFAULT '0', +`security_num` int(11) unsigned NOT NULL DEFAULT '0', +`hole_num` int(11) unsigned NOT NULL DEFAULT '0', +`day` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', +`created_at` timestamp NULL DEFAULT NULL, +PRIMARY KEY (`id`), +KEY `group_id` (`group_id`) USING BTREE, +KEY `day` (`day`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; \ No newline at end of file diff --git a/tests/lightning_tool_241/data/qyjc.q_system_update-schema.sql b/tests/lightning_tool_241/data/qyjc.q_system_update-schema.sql new file mode 100644 index 000000000..ebff4bb40 --- /dev/null +++ b/tests/lightning_tool_241/data/qyjc.q_system_update-schema.sql @@ -0,0 +1,7 @@ +CREATE TABLE `q_system_update` ( +`id` int(10) NOT NULL AUTO_INCREMENT, +`version` varchar(255) DEFAULT '', +`timepoint` datetime NOT NULL, +`status` tinyint(1) NOT NULL DEFAULT '0', +PRIMARY KEY (`id`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; \ No newline at end of file diff --git a/tests/lightning_tool_241/data/qyjc.q_system_update.sql b/tests/lightning_tool_241/data/qyjc.q_system_update.sql new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_tool_241/data/qyjc.q_user_log-schema.sql b/tests/lightning_tool_241/data/qyjc.q_user_log-schema.sql new file mode 100644 index 000000000..7ca285141 --- /dev/null +++ b/tests/lightning_tool_241/data/qyjc.q_user_log-schema.sql @@ -0,0 +1,12 @@ +CREATE TABLE `q_user_log` ( +`id` int(11) unsigned NOT NULL AUTO_INCREMENT, +`uid` int(11) unsigned NOT NULL DEFAULT '0', +`type` varchar(20) DEFAULT NULL, +`level` tinyint(1) DEFAULT NULL, +`code` varchar(20) DEFAULT NULL, +`string` varchar(255) DEFAULT NULL, +`message` varchar(255) DEFAULT NULL, +`create_time` timestamp NULL DEFAULT NULL, +`status` tinyint(1) DEFAULT NULL, +PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; \ No newline at end of file diff --git a/tests/lightning_tool_241/data/qyjc.q_user_log.sql b/tests/lightning_tool_241/data/qyjc.q_user_log.sql new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_tool_241/run.sh b/tests/lightning_tool_241/run.sh new file mode 100755 index 000000000..63fe44401 --- /dev/null +++ b/tests/lightning_tool_241/run.sh @@ -0,0 +1,45 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +# This test verifies if TOOL-200 and TOOL-241 are all fixed. +# They all involve data source with lots of empty tables. + +set -eu + +run_sql 'DROP DATABASE IF EXISTS qyjc;' +run_lightning +echo 'Import finished' + +# Verify all data are imported +for table_name in \ + q_alarm_group \ + q_alarm_message_log \ + q_alarm_receiver \ + q_config \ + q_report_circular_data \ + q_report_desc \ + q_report_summary \ + q_system_update \ + q_user_log +do + run_sql "SELECT count(*) FROM qyjc.$table_name;" + check_contains 'count(*): 0' +done + +# ensure the non-empty table is not affected +run_sql 'SELECT count(id), min(id), max(id) FROM qyjc.q_fish_event;' +check_contains 'count(id): 84' +check_contains 'min(id): 8343146' +check_contains 'max(id): 8343229' diff --git a/tests/lightning_unused_config_keys/config.toml b/tests/lightning_unused_config_keys/config.toml new file mode 100644 index 000000000..7df1d89c1 --- /dev/null +++ b/tests/lightning_unused_config_keys/config.toml @@ -0,0 +1,6 @@ +[typo-1] + +[lightning] +typo-2 = "unused test" + +[[typo-3]] diff --git a/tests/lightning_unused_config_keys/data/unused_config_keys-schema-create.sql b/tests/lightning_unused_config_keys/data/unused_config_keys-schema-create.sql new file mode 100644 index 000000000..2a6b4cbfb --- /dev/null +++ b/tests/lightning_unused_config_keys/data/unused_config_keys-schema-create.sql @@ -0,0 +1 @@ +CREATE DATABASE `unused_config_keys`; \ No newline at end of file diff --git a/tests/lightning_unused_config_keys/run.sh b/tests/lightning_unused_config_keys/run.sh new file mode 100755 index 000000000..f088089ee --- /dev/null +++ b/tests/lightning_unused_config_keys/run.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +# This test verifies if TOOL-1405 is fixed. +# Lightning should fail to start when finds unused config keys. + +set +e +run_lightning --log-file "$TEST_DIR/lightning-unused-config-keys.log" +ERRORCODE=$? +set -e + +[ "$ERRORCODE" -ne 0 ] + +grep -q 'typo-1' "$TEST_DIR/lightning-unused-config-keys.log" && +grep -q 'typo-2' "$TEST_DIR/lightning-unused-config-keys.log" && +grep -q 'typo-3' "$TEST_DIR/lightning-unused-config-keys.log" && +! grep -q 'typo-4' "$TEST_DIR/lightning-unused-config-keys.log" \ No newline at end of file diff --git a/tests/lightning_various_types/config.toml b/tests/lightning_various_types/config.toml new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lightning_various_types/data/vt-schema-create.sql b/tests/lightning_various_types/data/vt-schema-create.sql new file mode 100644 index 000000000..42fcd51d5 --- /dev/null +++ b/tests/lightning_various_types/data/vt-schema-create.sql @@ -0,0 +1 @@ +CREATE DATABASE vt; diff --git a/tests/lightning_various_types/data/vt.binary-schema.sql b/tests/lightning_various_types/data/vt.binary-schema.sql new file mode 100644 index 000000000..b61ab0110 --- /dev/null +++ b/tests/lightning_various_types/data/vt.binary-schema.sql @@ -0,0 +1,4 @@ +CREATE TABLE `binary` ( + `ref` INT NOT NULL, + `pk` BINARY(16) NOT NULL PRIMARY KEY +); diff --git a/tests/lightning_various_types/data/vt.binary.sql b/tests/lightning_various_types/data/vt.binary.sql new file mode 100644 index 000000000..508653aad --- /dev/null +++ b/tests/lightning_various_types/data/vt.binary.sql @@ -0,0 +1,52 @@ +INSERT INTO `binary` VALUES +( 0, x'ee72ae0aef354c5d94ffe82f696af30d'), +( 1, x'2daD56480dbd4a00a1aed85a3fbc6c3d'), +( 2, x'a35B4b77c45349d79faa41041efad77b'), +( 3, x'950A780f93b840109a97a8c60304ef69'), +( 4, x'73dA4e0f11714304b781c2339e5c3973'), +( 5, x'0a3C47cc54ea4ce3bb917006985bf31a'), +( 6, x'd03A918aa5454cfd8400cc37490505a9'), +( 7, x'1d0F15874a93418e9b70197594e5bc8b'), +( 8, x'd5019bd872194b868b5f8d0ed4b0aed5'), +( 9, x'b292179cdf0c459291dcfb8b95afc3e8'), +(10, x'3f0F41aa5468468ab90c42eb02e909d2'), +(11, x'a6175ce6b6a74e63a784b1b4ab4a4c70'), +(12, x'a7dAe6d2275e4ffd86dcba5c00e48fe5'), +(13, x'b07B158d46174c8a90791667c5545cc4'), +(14, x'7f8E4fae730a4faf86a1a4e5b1447fab'), +(15, x'13364cf3a5174f7eb78ebd6177af0e93'), +(16, x'35a044893d1c40f9a2414c23e5a5f5bd'), +(17, x'97197ef428044b84a36190a3384402aa'), +(18, x'598555572132484989805b8fac90304b'), +(19, x'f2e49556fd864f68a10789eafb4009ca'), +(20, x'ca63c5d01b6945068fdae901ed855698'), +(21, x'2e17ff04fa374fd6a6f92d09f4ef4a5f'), +(22, x'9d65d2fc85d74c3a88f8cba092903980'), +(23, x'4b0D0e94201a45ac89c410b4fefb8d95'), +(24, x'a290faa6ee5241179702a69961b46c3b'), +(25, x'61270388bd744edf9089638806efe0a9'), +(26, x'6e54e72c08784a88991e285c49945525'), +(27, x'308D6c267c3d4b25a68c7a1435b1a4f6'), +(28, x'2944629d7fef4dc0b4472ef100718837'), +(29, x'fde1328c409c43a8b1b08c35c8000f92'), +(30, x'ba4274c329e8413cbe87611e4570f999'), +(31, x'6bd41abb12ea44899c47cfc4b43e1ed3'), +(32, x'123D777433c14deaaeadec5b5bae772c'), +(33, x'42e7ffe70e084e6fa079e9596d7f0b83'), +(34, x'115Bc64364f0456e8523bd8286c39446'), +(35, x'd9d0233306bb4e648820ef426e089d8a'), +(36, x'f48888bc0d614963907db98f3a7766b6'), +(37, x'a913d4bacc4c4c479fb9271bede8015e'), +(38, x'c567fa2b4f8e4f39896d52e59ea92e87'), +(39, x'4015636d2bf543978c6b32d145e7592c'), +(40, x'41832db1800046bea4f27f22b61bb5e7'), +(41, x'55dC0343db6a420898729096305b8c07'), +(42, x'22e65824c67a4ae1b89d997bc6ee29d4'), +(43, x'090Abbb2f22e4f97a4fea52eb1a80a0b'), +(44, x'90553b8ae4c442e08fc3f20320b1110b'), +(45, x'43626db940334c75bc11ea41099565cf'), +(46, x'747B4c36678948a8921763afbee2dd15'), +(47, x'a5e015d13d76447eaf8fb834007481e9'), +(48, x'b75264a7e7074df187a2b9052ad4e36d'), +(49, x'45cD65089a064ab781406a84a109faef'), +(50, x''); diff --git a/tests/lightning_various_types/data/vt.bit-schema.sql b/tests/lightning_various_types/data/vt.bit-schema.sql new file mode 100644 index 000000000..e2d9bf3c5 --- /dev/null +++ b/tests/lightning_various_types/data/vt.bit-schema.sql @@ -0,0 +1,4 @@ +CREATE TABLE `bit`( + `ref` INT NOT NULL, + `pk` BIT(2) NOT NULL +); \ No newline at end of file diff --git a/tests/lightning_various_types/data/vt.bit.sql b/tests/lightning_various_types/data/vt.bit.sql new file mode 100644 index 000000000..37e81c7e7 --- /dev/null +++ b/tests/lightning_various_types/data/vt.bit.sql @@ -0,0 +1,6 @@ +INSERT INTO `bit` VALUES +(1, b'00'), (2, b'01'), (3, b'10'), (4, b'11'), +(11, 0b0), (12, 0b1), (13, 0b10), (14, 0b11), +(21, 0x0), (22, 0x1), (23, 0x2), (24, 0x3), +(41, 0), (42, 1), (43, 2), (44, 3); + diff --git a/tests/lightning_various_types/data/vt.char-schema.sql b/tests/lightning_various_types/data/vt.char-schema.sql new file mode 100644 index 000000000..7862ec1d5 --- /dev/null +++ b/tests/lightning_various_types/data/vt.char-schema.sql @@ -0,0 +1,4 @@ +CREATE TABLE `char` ( + `ref` INT NOT NULL, + `pk` CHAR(36) NOT NULL PRIMARY KEY +); diff --git a/tests/lightning_various_types/data/vt.char.sql b/tests/lightning_various_types/data/vt.char.sql new file mode 100644 index 000000000..42712b3c1 --- /dev/null +++ b/tests/lightning_various_types/data/vt.char.sql @@ -0,0 +1,51 @@ +INSERT INTO `char` VALUES +( 0, 'ee72ae0a-ef35-4c5d-94ff-e82f696af30d'), +( 1, '2dad5648-0dbd-4a00-a1ae-d85a3fbc6c3d'), +( 2, 'a35b4b77-c453-49d7-9faa-41041efad77b'), +( 3, '950a780f-93b8-4010-9a97-a8c60304ef69'), +( 4, '73da4e0f-1171-4304-b781-c2339e5c3973'), +( 5, '0a3c47cc-54ea-4ce3-bb91-7006985bf31a'), +( 6, 'd03a918a-a545-4cfd-8400-cc37490505a9'), +( 7, '1d0f1587-4a93-418e-9b70-197594e5bc8b'), +( 8, 'd5019bd8-7219-4b86-8b5f-8d0ed4b0aed5'), +( 9, 'b292179c-df0c-4592-91dc-fb8b95afc3e8'), +(10, '3f0f41aa-5468-468a-b90c-42eb02e909d2'), +(11, 'a6175ce6-b6a7-4e63-a784-b1b4ab4a4c70'), +(12, 'a7dae6d2-275e-4ffd-86dc-ba5c00e48fe5'), +(13, 'b07b158d-4617-4c8a-9079-1667c5545cc4'), +(14, '7f8e4fae-730a-4faf-86a1-a4e5b1447fab'), +(15, '13364cf3-a517-4f7e-b78e-bd6177af0e93'), +(16, '35a04489-3d1c-40f9-a241-4c23e5a5f5bd'), +(17, '97197ef4-2804-4b84-a361-90a3384402aa'), +(18, '59855557-2132-4849-8980-5b8fac90304b'), +(19, 'f2e49556-fd86-4f68-a107-89eafb4009ca'), +(20, 'ca63c5d0-1b69-4506-8fda-e901ed855698'), +(21, '2e17ff04-fa37-4fd6-a6f9-2d09f4ef4a5f'), +(22, '9d65d2fc-85d7-4c3a-88f8-cba092903980'), +(23, '4b0d0e94-201a-45ac-89c4-10b4fefb8d95'), +(24, 'a290faa6-ee52-4117-9702-a69961b46c3b'), +(25, '61270388-bd74-4edf-9089-638806efe0a9'), +(26, '6e54e72c-0878-4a88-991e-285c49945525'), +(27, '308d6c26-7c3d-4b25-a68c-7a1435b1a4f6'), +(28, '2944629d-7fef-4dc0-b447-2ef100718837'), +(29, 'fde1328c-409c-43a8-b1b0-8c35c8000f92'), +(30, 'ba4274c3-29e8-413c-be87-611e4570f999'), +(31, '6bd41abb-12ea-4489-9c47-cfc4b43e1ed3'), +(32, '123d7774-33c1-4dea-aead-ec5b5bae772c'), +(33, '42e7ffe7-0e08-4e6f-a079-e9596d7f0b83'), +(34, '115bc643-64f0-456e-8523-bd8286c39446'), +(35, 'd9d02333-06bb-4e64-8820-ef426e089d8a'), +(36, 'f48888bc-0d61-4963-907d-b98f3a7766b6'), +(37, 'a913d4ba-cc4c-4c47-9fb9-271bede8015e'), +(38, 'c567fa2b-4f8e-4f39-896d-52e59ea92e87'), +(39, '4015636d-2bf5-4397-8c6b-32d145e7592c'), +(40, '41832db1-8000-46be-a4f2-7f22b61bb5e7'), +(41, '55dc0343-db6a-4208-9872-9096305b8c07'), +(42, '22e65824-c67a-4ae1-b89d-997bc6ee29d4'), +(43, '090abbb2-f22e-4f97-a4fe-a52eb1a80a0b'), +(44, '90553b8a-e4c4-42e0-8fc3-f20320b1110b'), +(45, '43626db9-4033-4c75-bc11-ea41099565cf'), +(46, '747b4c36-6789-48a8-9217-63afbee2dd15'), +(47, 'a5e015d1-3d76-447e-af8f-b834007481e9'), +(48, 'b75264a7-e707-4df1-87a2-b9052ad4e36d'), +(49, '45cd6508-9a06-4ab7-8140-6a84a109faef'); diff --git a/tests/lightning_various_types/data/vt.datetime-schema.sql b/tests/lightning_various_types/data/vt.datetime-schema.sql new file mode 100644 index 000000000..2da8f8b9b --- /dev/null +++ b/tests/lightning_various_types/data/vt.datetime-schema.sql @@ -0,0 +1,7 @@ +CREATE TABLE `datetime` ( + `ref` INT NOT NULL, + `pk` DATETIME(6) NOT NULL, + `uk` TIMESTAMP(3) NOT NULL, + PRIMARY KEY(`pk`), + UNIQUE KEY(`uk`) +); diff --git a/tests/lightning_various_types/data/vt.datetime.sql b/tests/lightning_various_types/data/vt.datetime.sql new file mode 100644 index 000000000..18b47e1eb --- /dev/null +++ b/tests/lightning_various_types/data/vt.datetime.sql @@ -0,0 +1,72 @@ +/* https://stackoverflow.com/a/553448/ */ +INSERT INTO `datetime` VALUES +( 0, '1717-03-14 19:01:39.698460', '1994-01-09 19:00:33.151028'), +( 1, '6717-04-25 07:17:05.765930', '2000-05-08 01:34:33.919478'), +( 2, '3313-02-14 09:48:56.762573', '1991-02-02 08:45:36.551887'), +( 3, '7783-02-24 04:46:06.938477', '1977-02-09 01:45:58.748853'), +( 4, '6908-03-15 07:11:10.969849', '1972-09-18 08:32:34.727022'), +( 5, '3567-12-07 20:47:04.761154', '1994-12-15 02:10:57.834306'), +( 6, '1760-03-14 05:21:51.871632', '1999-01-28 09:49:30.353572'), +( 7, '2882-10-23 19:19:58.829239', '2031-09-11 19:10:14.444316'), +( 8, '1191-12-27 21:27:06.693556', '2020-05-21 10:51:51.371299'), +( 9, '8413-05-08 01:25:27.441437', '2030-11-16 21:02:03.379840'), +(10, '1221-09-22 12:42:28.453525', '2013-03-04 16:38:49.110009'), +(11, '3348-01-29 01:59:53.699280', '1972-11-02 00:29:21.187597'), +(12, '2453-06-14 04:22:31.782410', '1977-08-15 22:13:37.225733'), +(13, '6499-12-22 21:34:03.830688', '1990-03-15 16:58:39.208468'), +(14, '6443-02-09 01:58:31.988739', '1976-04-12 17:46:33.814856'), +(15, '1408-11-19 00:58:46.005671', '2014-05-16 15:11:46.047472'), +(16, '7276-04-16 23:54:59.445618', '2033-02-21 09:24:44.777439'), +(17, '5380-04-07 07:03:44.358246', '1988-01-12 18:24:38.646579'), +(18, '2599-12-09 23:32:49.507103', '2028-01-12 11:51:13.523571'), +(19, '1896-04-08 14:32:07.003094', '2008-08-15 19:34:19.565940'), +(20, '4093-01-09 13:07:04.407578', '1973-05-29 08:31:36.492560'), +(21, '2711-09-28 07:52:03.075577', '2032-02-02 17:04:15.920171'), +(22, '6807-02-23 02:08:40.119141', '2025-11-13 20:33:19.151073'), +(23, '8967-04-09 11:22:24.298584', '2031-09-13 17:48:56.261312'), +(24, '3849-07-14 10:28:17.246872', '2033-11-18 00:33:13.877377'), +(25, '6798-09-25 19:08:52.112762', '2002-05-06 14:17:04.922844'), +(26, '7587-10-29 01:18:16.684265', '1978-07-16 23:00:47.524583'), +(27, '2826-06-09 01:53:33.932587', '1990-08-27 07:50:38.947502'), +(28, '5606-06-09 10:22:59.118011', '2025-04-07 18:12:41.775952'), +(29, '9226-07-24 02:05:59.428558', '1972-12-27 19:30:54.925280'), +(30, '9889-01-08 08:51:03.389832', '1991-11-22 11:18:25.976254'), +(31, '3313-03-12 21:31:26.280167', '1979-09-19 22:23:36.101528'), +(32, '4447-07-29 09:55:11.170853', '2001-12-11 02:17:05.089341'), +(33, '5216-01-15 07:26:56.856674', '2019-05-22 08:10:32.403484'), +(34, '4525-11-09 00:39:21.494827', '1992-02-20 07:05:45.033342'), +(35, '1236-11-02 02:53:19.848807', '1994-06-16 17:31:32.002678'), +(36, '4785-06-14 09:10:30.444107', '1970-11-09 19:25:45.842815'), +(37, '9063-09-29 22:30:48.101807', '2020-02-09 08:33:39.128007'), +(38, '3257-08-05 05:45:02.390640', '2002-05-27 07:24:54.714140'), +(39, '1055-05-30 06:32:09.532792', '1981-02-08 01:44:22.275192'), +(40, '9221-04-18 08:12:25.730316', '1993-03-27 08:29:54.190876'), +(41, '9626-02-01 15:40:16.972168', '2036-07-09 18:38:29.528437'), +(42, '9233-02-28 12:32:28.574829', '1986-02-08 07:17:52.059964'), +(43, '2728-04-06 04:55:49.858131', '2020-04-04 19:04:14.824254'), +(44, '8947-10-06 12:56:22.928345', '1981-05-06 22:03:07.781892'), +(45, '5411-04-03 17:33:11.787018', '2014-09-05 21:19:42.274363'), +(46, '9050-05-19 05:39:57.889496', '2025-10-26 22:18:21.280456'), +(47, '6865-03-31 15:08:12.383698', '1972-11-12 22:57:02.337941'), +(48, '8813-01-27 08:57:31.674316', '1977-12-16 08:15:08.703651'), +(49, '6358-07-28 22:48:41.740967', '1976-04-04 02:12:00.347965'), +(50, '9072-01-13 08:28:20.016449', '2036-03-20 12:09:49.066841'), +(51, '6728-04-11 05:12:57.642853', '2000-09-07 13:54:13.362203'), +(52, '5427-01-17 06:08:23.301147', '2005-04-28 05:05:37.823345'), +(53, '9125-10-22 22:42:54.105652', '1979-05-15 21:15:08.620165'), +(54, '4012-12-04 10:24:22.104721', '1974-03-17 00:53:33.755126'), +(55, '7492-11-18 04:49:24.634003', '1975-09-13 05:33:24.305249'), +(56, '9570-01-26 01:33:36.090454', '1977-06-23 01:47:47.774165'), +(57, '1233-07-23 18:25:22.172003', '1991-07-19 10:47:44.003068'), +(58, '2735-10-07 03:29:19.999466', '2009-05-15 12:29:33.608722'), +(59, '1026-09-21 15:15:54.335745', '2028-10-30 10:04:57.803835'), +(60, '7384-03-30 06:06:15.340759', '2005-11-03 11:40:37.157283'), +(61, '5566-09-26 21:25:11.814362', '1997-10-03 10:48:09.784687'), +(62, '8386-02-18 22:11:03.732574', '2004-03-17 07:25:32.226178'), +(63, '9827-02-13 13:48:29.540344', '1994-11-02 14:04:16.819635'), +(64, '3616-06-14 01:59:05.354645', '1994-01-31 12:46:39.970936'), +(65, '7933-12-18 12:54:06.738037', '2036-10-14 10:48:28.620306'), +(66, '1057-10-11 06:59:48.021151', '1997-04-05 15:24:50.599577'), +(67, '1136-12-13 01:03:00.081739', '2031-08-28 09:08:36.844683'), +(68, '1458-12-04 11:16:50.582912', '1975-03-15 10:07:46.220437'), +(69, '3874-02-24 09:23:34.385239', '2020-08-07 08:30:52.980000'); diff --git a/tests/lightning_various_types/data/vt.decimal-schema.sql b/tests/lightning_various_types/data/vt.decimal-schema.sql new file mode 100644 index 000000000..a8f0b0d98 --- /dev/null +++ b/tests/lightning_various_types/data/vt.decimal-schema.sql @@ -0,0 +1,4 @@ +CREATE TABLE `decimal`( + `ref` INT NOT NULL, + `pk` DECIMAL(6, 4) NOT NULL PRIMARY KEY +); \ No newline at end of file diff --git a/tests/lightning_various_types/data/vt.decimal.sql b/tests/lightning_various_types/data/vt.decimal.sql new file mode 100644 index 000000000..fc9060cd6 --- /dev/null +++ b/tests/lightning_various_types/data/vt.decimal.sql @@ -0,0 +1,51 @@ +INSERT INTO `decimal` VALUES +(1, 84.1471), +(2, 90.9297), +(3, 14.112), +(4, -75.6802), +(5, -95.8924), +(6, -27.9415), +(7, 65.6987), +(8, 98.9358), +(9, 41.2118), +(10, -54.4021), +(11, -99.999), +(12, -53.6573), +(13, 42.0167), +(14, 99.0607), +(15, 65.0288), +(16, -28.7903), +(17, -96.1397), +(18, -75.0987), +(19, 14.9877), +(20, 91.2945), +(21, 83.6656), +(22, -0.8851), +(23, -84.622), +(24, -90.5578), +(25, -13.2352), +(26, 76.2558), +(27, 95.6376), +(28, 27.0906), +(29, -66.3634), +(30, -98.8032), +(31, -40.4038), +(32, 55.1427), +(33, 99.9912), +(34, 52.9083), +(35, -42.8183), +(36, -99.1779), +(37, -64.3538), +(38, 29.6369), +(39, 96.3795), +(40, 74.5113), +(41, -15.8623), +(42, -91.6522), +(43, -83.1775), +(44, 1.7702), +(45, 85.0904), +(46, 90.1788), +(47, 12.3573), +(48, -76.8255), +(49, -95.3753), +(50, -26.2375); diff --git a/tests/lightning_various_types/data/vt.double-schema.sql b/tests/lightning_various_types/data/vt.double-schema.sql new file mode 100644 index 000000000..1735efc7e --- /dev/null +++ b/tests/lightning_various_types/data/vt.double-schema.sql @@ -0,0 +1,4 @@ +CREATE TABLE `double` ( + `ref` INT NOT NULL, + `pk` DOUBLE NOT NULL PRIMARY KEY +); \ No newline at end of file diff --git a/tests/lightning_various_types/data/vt.double.sql b/tests/lightning_various_types/data/vt.double.sql new file mode 100644 index 000000000..66bce6805 --- /dev/null +++ b/tests/lightning_various_types/data/vt.double.sql @@ -0,0 +1,42 @@ +INSERT INTO `double` VALUES +(-700,9.8596765437597708567e-305), +(-665,1.5637579633862188833e-289), +(-630,2.4801411660927964175e-274), +(-595,3.93353725305949385e-259), +(-560,6.2386429985283768477e-244), +(-525,9.8945717198470043018e-229), +(-490,1.5692923852557387023e-213), +(-455,2.488918833628632497e-198), +(-420,3.9474587518512647549e-183), +(-385,6.2607226828884906754e-168), +(-350,9.9295903962649792963e-153), +(-315,1.5748463944438506468e-137), +(-280,2.4977275669152504367e-122), +(-245,3.9614295213416819065e-107), +(-210,6.2828805112394623313e-92), +(-175,9.964733010103672264e-77), +(-140,1.5804200602736129648e-61), +(-105,2.5065674758999531731e-46), +(-70,3.9754497359086468078e-31), +(-35,6.3051167601469893856e-16), +(0,1.0), +(35,1.5860134523134307281e15), +(70,2.5154386709191670063e30), +(105,3.9895195705472158508e45), +(140,6.3274317071555853643e60), +(175,1.0035391806143294572e76), +(210,1.5916266403779241592e91), +(245,2.5243412626998187771e106), +(280,4.0036392008717845384e121), +(315,6.349825630792043955e136), +(350,1.0070908870280797598e152), +(385,1.5972596945288000308e167), +(420,2.5332753623607179194e182), +(455,4.0178088031182794379e197), +(490,6.3722988105689154742e212), +(525,1.0106551635723173971e228), +(560,1.6029126850757261505e243), +(595,2.5422410814139434033e258), +(630,4.0320285541463578912e273), +(665,6.3948515269879956379e288), +(700,1.0142320547350045095e304); diff --git a/tests/lightning_various_types/data/vt.empty_strings-schema.sql b/tests/lightning_various_types/data/vt.empty_strings-schema.sql new file mode 100644 index 000000000..a0b6e2a55 --- /dev/null +++ b/tests/lightning_various_types/data/vt.empty_strings-schema.sql @@ -0,0 +1,4 @@ +CREATE TABLE `empty_strings` ( + `pk` INT NOT NULL, + `a` VARCHAR(20) NOT NULL +); diff --git a/tests/lightning_various_types/data/vt.empty_strings.sql b/tests/lightning_various_types/data/vt.empty_strings.sql new file mode 100644 index 000000000..786a3c1e4 --- /dev/null +++ b/tests/lightning_various_types/data/vt.empty_strings.sql @@ -0,0 +1 @@ +INSERT INTO `empty_strings` VALUES (1, ""), (2, """"), (4, ''), (8, ''''), (16, "\""), (32, '\''); diff --git a/tests/lightning_various_types/data/vt.enum-set-schema.sql b/tests/lightning_various_types/data/vt.enum-set-schema.sql new file mode 100644 index 000000000..1aba09760 --- /dev/null +++ b/tests/lightning_various_types/data/vt.enum-set-schema.sql @@ -0,0 +1,26 @@ +CREATE TABLE `enum-set` ( + `enum` ENUM( + 'g00','g01','g02','g03','g04','g05','g06','g07','g08','g09','g0a','g0b','g0c','g0d','g0e','g0f', + 'g10','g11','g12','g13','g14','g15','g16','g17','g18','g19','g1a','g1b','g1c','g1d','g1e','g1f', + 'g20','g21','g22','g23','g24','g25','g26','g27','g28','g29','g2a','g2b','g2c','g2d','g2e','g2f', + 'g30','g31','g32','g33','g34','g35','g36','g37','g38','g39','g3a','g3b','g3c','g3d','g3e','g3f', + 'g40','g41','g42','g43','g44','g45','g46','g47','g48','g49','g4a','g4b','g4c','g4d','g4e','g4f', + 'g50','g51','g52','g53','g54','g55','g56','g57','g58','g59','g5a','g5b','g5c','g5d','g5e','g5f', + 'g60','g61','g62','g63','g64','g65','g66','g67','g68','g69','g6a','g6b','g6c','g6d','g6e','g6f', + 'g70','g71','g72','g73','g74','g75','g76','g77','g78','g79','g7a','g7b','g7c','g7d','g7e','g7f', + 'g80','g81','g82','g83','g84','g85','g86','g87','g88','g89','g8a','g8b','g8c','g8d','g8e','g8f', + 'g90','g91','g92','g93','g94','g95','g96','g97','g98','g99','g9a','g9b','g9c','g9d','g9e','g9f', + 'ga0','ga1','ga2','ga3','ga4','ga5','ga6','ga7','ga8','ga9','gaa','gab','gac','gad','gae','gaf', + 'gb0','gb1','gb2','gb3','gb4','gb5','gb6','gb7','gb8','gb9','gba','gbb','gbc','gbd','gbe','gbf', + 'gc0','gc1','gc2','gc3','gc4','gc5','gc6','gc7','gc8','gc9','gca','gcb','gcc','gcd','gce','gcf', + 'gd0','gd1','gd2','gd3','gd4','gd5','gd6','gd7','gd8','gd9','gda','gdb','gdc','gdd','gde','gdf', + 'ge0','ge1','ge2','ge3','ge4','ge5','ge6','ge7','ge8','ge9','gea','geb','gec','ged','gee','gef', + 'gf0','gf1','gf2','gf3','gf4','gf5','gf6','gf7','gf8','gf9','gfa','gfb','gfc','gfd','gfe','gff' + ) NOT NULL UNIQUE, + `set` SET( + 'x00','x01','x02','x03','x04','x05','x06','x07','x08','x09','x10','x11','x12','x13','x14','x15', + 'x16','x17','x18','x19','x20','x21','x22','x23','x24','x25','x26','x27','x28','x29','x30','x31', + 'x32','x33','x34','x35','x36','x37','x38','x39','x40','x41','x42','x43','x44','x45','x46','x47', + 'x48','x49','x50','x51','x52','x53','x54','x55','x56','x57','x58','x59','x60','x61','x62','x63' + ) NOT NULL PRIMARY KEY +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/tests/lightning_various_types/data/vt.enum-set.sql b/tests/lightning_various_types/data/vt.enum-set.sql new file mode 100644 index 000000000..355ca5ef0 --- /dev/null +++ b/tests/lightning_various_types/data/vt.enum-set.sql @@ -0,0 +1,36 @@ +/*!40101 SET NAMES binary*/; +/*!40014 SET FOREIGN_KEY_CHECKS=0*/; +/*!40103 SET TIME_ZONE='+00:00' */; +INSERT INTO `enum-set` VALUES +("g51",""), +("g5d","x25"), +("g4f","x13,x62"), +("g30","x04,x17,x51"), +("g95","x08,x40,x53,x51"), +("ged","x29,x53,x25,x58,x08"), +("g67","x59,x42,x20,x15,x28,x11"), +("gc3","x12,x45,x37,x34,x36,x50,x09"), +("gef","x34,x63,x58,x56,x28,x49,x62,x21"), +("g4b","x59,x30,x41,x39,x21,x00,x45,x03,x14"), +("g55","x47,x57,x43,x21,x34,x50,x54,x24,x22,x05"), +("g6b","x42,x34,x52,x18,x51,x50,x40,x47,x24,x39,x20"), +("g28","x13,x24,x46,x03,x57,x10,x28,x23,x18,x37,x60,x20"), +("gbe","x35,x46,x55,x63,x06,x09,x50,x39,x13,x12,x17,x62,x59"), +("gf7","x04,x25,x16,x03,x31,x61,x43,x11,x63,x39,x21,x01,x24,x42"), +("g39","x56,x36,x50,x04,x07,x62,x14,x01,x39,x25,x27,x55,x44,x61,x40"), +("g8d","x26,x27,x47,x08,x23,x30,x32,x52,x49,x02,x06,x39,x56,x18,x25,x14"), +("gc6","x29,x59,x21,x39,x33,x02,x08,x11,x04,x13,x62,x07,x55,x26,x03,x50,x63"), +("gcc","x07,x54,x09,x24,x23,x27,x37,x61,x55,x17,x49,x20,x62,x00,x06,x58,x44,x46"), +("gcf","x55,x35,x42,x57,x56,x48,x32,x28,x29,x47,x07,x17,x49,x03,x30,x15,x60,x34,x21"), +("g2b","x03,x42,x32,x02,x53,x20,x39,x35,x23,x30,x26,x00,x10,x50,x34,x07,x31,x41,x56,x62"), +("gdd","x11,x37,x10,x19,x28,x41,x35,x04,x60,x23,x56,x00,x31,x50,x03,x36,x15,x52,x63,x08,x33"), +("g4d","x18,x09,x37,x31,x15,x51,x50,x56,x63,x03,x14,x49,x16,x12,x34,x45,x38,x20,x05,x28,x30,x01"), +("g27","x01,x08,x21,x41,x29,x02,x32,x31,x06,x24,x20,x43,x48,x51,x18,x34,x30,x00,x14,x03,x09,x44,x05"), +("g98","x24,x50,x46,x61,x09,x17,x20,x26,x28,x08,x19,x43,x35,x56,x36,x27,x25,x48,x63,x30,x21,x38,x33,x07"); + +INSERT INTO `enum-set` VALUES (154, 11937444798263156608); +-- g99 -> x07,x08,x09,x10,x11,x12,x14,x16,x17,x18,x19,x22,x25,x26,x28,x29,x30,x31,x32,x33,x35,x38,x39,x41,x44,x46,x49,x51,x53,x55,x56,x58,x61,x63 + +-- INSERT INTO `enum-set` VALUES (0x676639, 0x7830302C7830322C7830352C7830392C7831332C7831342C7831352C7831362C7831392C7832302C7833302C7833332C7833362C7833372C7833382C7834312C7834332C7834352C7834362C7834372C7834382C7835302C7835352C7835392C7836302C7836312C783633); +-- -- re-enable once pingcap/tidb#10154 is fixed and cherry-picked into a 3.0-beta release. + diff --git a/tests/lightning_various_types/data/vt.json-schema.sql b/tests/lightning_various_types/data/vt.json-schema.sql new file mode 100644 index 000000000..7c3cd2191 --- /dev/null +++ b/tests/lightning_various_types/data/vt.json-schema.sql @@ -0,0 +1,4 @@ +CREATE TABLE `json` ( + `pk` MEDIUMINT(4) NOT NULL PRIMARY KEY, + `js` JSON NOT NULL +); diff --git a/tests/lightning_various_types/data/vt.json.sql b/tests/lightning_various_types/data/vt.json.sql new file mode 100644 index 000000000..fbc78fc7b --- /dev/null +++ b/tests/lightning_various_types/data/vt.json.sql @@ -0,0 +1,106 @@ +INSERT INTO `json` VALUES +(691, 'null'), +(706, 'true'), +(708, 'false'), +(731, '"n"'), +(732, '"\\""'), +(733, '"\\\\"'), +(734, '"/"'), +(735, '"\\b"'), +(736, '"\\f"'), +(737, '"\\n"'), +(738, '"\\r"'), +(739, '"\\t"'), +(740, '"\\u000b"'), +(741, '"\\u001B"'), +(742, '"Σ"'), +(802, '-2'), +(803, '-1234'), +(805, '-9223372036854775808'), +(806, '9223372036854775807'), +(813, '0'), +(814, '3'), +(815, '1234'), +(816, '18446744073709551615'), +(823, '[823, -0.0]'), +(824, '[824, -0e2]'), +(825, '[825, -0.0e2]'), +(826, '[826, -1e-400]'), +(827, '[827, -1e-4000000000000000000000000000000000000000000000000]'), +(840, '[840, 0.0]'), +(841, '[841, 3.0]'), +(842, '3.1'), +(843, '-1.2'), +(844, '0.4'), +(851, '[851, 3.00]'), +(852, '[852, 0.4e5]'), +(853, '[853, 0.4e+5]'), +(854, '[854, 0.4e15]'), +(855, '[855, 0.4e+15]'), +(856, '[856, 0.4e-1]'), +(858, '[858, 0.4e-001]'), +(859, '[859, 0.4e-0]'), +(860, '[860, 0.00e000]'), +(861, '[861, 0.00e+00]'), +(862, '[862, 0.00e-00]'), +(864, '[864, -9223372036854776000]'), +(868, '[868, 18446744073709552000]'), +(871, '0.0000000000000002220446049250313'), +(873, '0.0000000000000000000000000000000000000000000000000123e50'), +(876, '[876, 100e-777777777777777777777777777]'), +(878, '1010101010101010101010101010101010101010'), +(882, '0.1010101010101010101010101010101010101010'), +(885, '[885, 0e1000000000000000000000000000000000000000000000]'), +(887, '[887, 100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000]'), +(896, '[896, 1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0e8]'), +(905, '[905, 1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e8]'), +(914, '[914, 1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e-10]'), +(1046, '""'), +(1047, '"foo"'), +(1056, '"🎕"'), +(1073, '[]'), +(1075, '[null]'), +(1079, '[true]'), +(1081, '[3,1]'), +(1083, '[[3], [1, 2]]'), +(1085, '[1]'), +(1087, '[1, 2]'), +(1089, '[1, 2, 3]'), +(1091, '[1, [2, 3]]'), +(1112, '{}'), +(1114, '{"a":3}'), +(1117, '{"a":3,"b":4}'), +(1127, '{"a":{"b":3,"c":4}}'), +(1136, '{"c":null}'), +(1167, '{ + "inner": [] +}'), +(1173, '{ + "inner": [ + { "a": null, "b": 2, "c": ["abc", "xyz"] } + ] +}'), +(1189, '[ + [ + [ null, 2, ["abc", "xyz"] ] + ] +]'), +(1216, '"jodhpurs"'), +(1228, '{"x": null}'), +(1229, '{"x": 5}'), +(1267, ' "Dog" '), +(1270, '{"Frog":["Henry",[]]}'), +(1274, ' { "Frog": [ "Henry" , [ 349, 102 ] ] } '), +(1278, '{"Cat": {"age": 5, "name": "Kate"}}'), +(1292, ' { "AntHive" : ["Bob", "Stuart"] } '), +(1298, '{"Dog":null}'), +(1304, '{\n"a": "Dog",\n"b": {"Frog":["Henry", []]}\n}'), +(1464, '[[],[]]'), +(1554, '{"a":{},"b":{}}'), +(1670, '{"foo":["bar","baz"],"":0,"a/b":1,"c%d":2,"e^f":3,"g|h":4,"i\\\\j":5,"k\\"l":6," ":7,"m~n":8}'), +(1764, '[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]'), +(1781, '{"-1":6,"1":2}'); + +-- test for pingcap/tidb-tools#144 +INSERT INTO `json` VALUES +(2000, CONVERT("{\"52\": 1, \"54\": 1, \"68\": 1, \"126\": 1}" USING UTF8MB4)); diff --git a/tests/lightning_various_types/data/vt.precise_types-schema.sql b/tests/lightning_various_types/data/vt.precise_types-schema.sql new file mode 100644 index 000000000..4bf75e31a --- /dev/null +++ b/tests/lightning_various_types/data/vt.precise_types-schema.sql @@ -0,0 +1,6 @@ +create table precise_types ( + a BIGINT UNSIGNED NOT NULL, + b BIGINT NOT NULL, + c DECIMAL(21,1) NOT NULL, + d DOUBLE NOT NULL +); diff --git a/tests/lightning_various_types/data/vt.precise_types.sql b/tests/lightning_various_types/data/vt.precise_types.sql new file mode 100644 index 000000000..e521c6086 --- /dev/null +++ b/tests/lightning_various_types/data/vt.precise_types.sql @@ -0,0 +1,6 @@ +insert into precise_types values ( + 18446744073709551614, + -9223372036854775806, + 99999999999999999999, + 18446744073709551614 +); diff --git a/tests/lightning_various_types/run.sh b/tests/lightning_various_types/run.sh new file mode 100755 index 000000000..fa0e72038 --- /dev/null +++ b/tests/lightning_various_types/run.sh @@ -0,0 +1,113 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +# Verify that using various uncommon types as primary key still works properly. + +set -eu + +for BACKEND in importer tidb local; do + if [ "$BACKEND" = 'local' ]; then + check_cluster_version 4 0 0 'local backend' || continue + fi + + run_sql 'DROP DATABASE IF EXISTS vt;' + run_lightning --backend $BACKEND + echo Import using $BACKEND finished + + run_sql 'SELECT count(pk), bin(min(pk)), bin(max(pk)) FROM vt.bit' + check_contains 'count(pk): 16' + check_contains 'bin(min(pk)): 0' + check_contains 'bin(max(pk)): 11' + run_sql 'SELECT sum(ref) FROM vt.bit WHERE pk = 0b10' + check_contains 'sum(ref): 82' + + run_sql 'SELECT count(pk), min(pk), max(pk), sum(pk) FROM vt.decimal' + check_contains 'count(pk): 50' + check_contains 'min(pk): -99.9990' + check_contains 'max(pk): 99.9912' + check_contains 'sum(pk): -9.9123' + run_sql 'SELECT ref FROM vt.decimal WHERE pk BETWEEN -1.0 AND 0.0' + check_contains 'ref: 22' + + run_sql 'SELECT count(pk), min(pk), max(pk) FROM vt.double' + check_contains 'count(pk): 41' + check_contains 'min(pk): 9.85967654375977e-305' + check_contains 'max(pk): 1.0142320547350045e304' + run_sql 'SELECT ref FROM vt.double WHERE pk BETWEEN 1e100 AND 1e120' + check_contains 'ref: 245' + + run_sql 'SELECT count(pk), min(pk), max(pk), count(uk), min(uk), max(uk) FROM vt.datetime' + check_contains 'count(pk): 70' + check_contains 'min(pk): 1026-09-21 15:15:54.335745' + check_contains 'max(pk): 9889-01-08 08:51:03.389832' + check_contains 'count(uk): 70' + check_contains 'min(uk): 1970-11-09 19:25:45.843' + check_contains 'max(uk): 2036-10-14 10:48:28.620' + run_sql "SELECT ref FROM vt.datetime WHERE pk BETWEEN '2882-01-01' AND '2882-12-31'" + check_contains 'ref: 7' + run_sql "SELECT ref FROM vt.datetime WHERE uk BETWEEN '2001-01-01' AND '2001-12-31'" + check_contains 'ref: 32' + + run_sql 'SELECT count(pk), min(pk), max(pk) FROM vt.char' + check_contains 'count(pk): 50' + check_contains 'min(pk): 090abbb2-f22e-4f97-a4fe-a52eb1a80a0b' + check_contains 'max(pk): fde1328c-409c-43a8-b1b0-8c35c8000f92' + run_sql "SELECT ref FROM vt.char WHERE pk = '55dc0343-db6a-4208-9872-9096305b8c07'" + check_contains 'ref: 41' + + run_sql 'SELECT count(pk), hex(min(pk)), hex(max(pk)) FROM vt.binary' + check_contains 'count(pk): 51' + check_contains 'hex(min(pk)): ' + check_contains 'hex(max(pk)): FDE1328C409C43A8B1B08C35C8000F92' + run_sql "SELECT ref FROM vt.binary WHERE pk = x'55dc0343db6a420898729096305b8c07'" + check_contains 'ref: 41' + + run_sql 'SELECT count(pk), count(DISTINCT js) FROM vt.json' + check_contains 'count(pk): 92' + check_contains 'count(DISTINCT js): 92' + run_sql 'SELECT pk FROM vt.json WHERE js = json_array(1, 2, 3)' + check_contains 'pk: 1089' + run_sql 'SELECT js FROM vt.json WHERE pk = 2000' + check_contains 'js: {' + check_contains '"52": 1' + check_contains '"54": 1' + check_contains '"68": 1' + check_contains '"126": 1' + + run_sql 'SELECT count(*) FROM vt.`enum-set`' + check_contains 'count(*): 26' + run_sql 'SELECT count(*) FROM vt.`enum-set` WHERE find_in_set("x50", `set`) > 0' + check_contains 'count(*): 10' + run_sql 'SELECT `set` FROM vt.`enum-set` WHERE `enum` = "gcc"' + check_contains 'set: x00,x06,x07,x09,x17,x20,x23,x24,x27,x37,x44,x46,x49,x54,x55,x58,x61,x62' + run_sql 'SELECT `set` FROM vt.`enum-set` WHERE `enum` = "g99"' + check_contains 'set: x07,x08,x09,x10,x11,x12,x14,x16,x17,x18,x19,x22,x25,x26,x28,x29,x30,x31,x32,x33,x35,x38,x39,x41,x44,x46,x49,x51,x53,x55,x56,x58,x61,x63' + + run_sql 'SELECT count(*) FROM vt.empty_strings' + check_contains 'count(*): 6' + run_sql 'SELECT sum(pk) FROM vt.empty_strings WHERE a = ""' + check_contains 'sum(pk): 5' + run_sql 'SELECT sum(pk) FROM vt.empty_strings WHERE a = 0x22' + check_contains 'sum(pk): 18' + run_sql 'SELECT sum(pk) FROM vt.empty_strings WHERE a = 0x27' + check_contains 'sum(pk): 40' + + run_sql 'SELECT a, b, c, d FROM vt.precise_types' + check_contains 'a: 18446744073709551614' + check_contains 'b: -9223372036854775806' + check_contains 'c: 99999999999999999999.0' + check_contains 'd: 1.8446744073709552e19' + +done diff --git a/tests/lightning_view/config.toml b/tests/lightning_view/config.toml new file mode 100644 index 000000000..629cf31ad --- /dev/null +++ b/tests/lightning_view/config.toml @@ -0,0 +1 @@ +[lightning] diff --git a/tests/lightning_view/data/db0-schema-create.sql b/tests/lightning_view/data/db0-schema-create.sql new file mode 100644 index 000000000..1ef9021e4 --- /dev/null +++ b/tests/lightning_view/data/db0-schema-create.sql @@ -0,0 +1 @@ +CREATE DATABASE db0; diff --git a/tests/lightning_view/data/db0.v2-schema-view.sql b/tests/lightning_view/data/db0.v2-schema-view.sql new file mode 100644 index 000000000..887db3745 --- /dev/null +++ b/tests/lightning_view/data/db0.v2-schema-view.sql @@ -0,0 +1,13 @@ +/*!40101 SET NAMES binary*/; +DROP TABLE IF EXISTS `v2`; +DROP VIEW IF EXISTS `v2`; +SET @PREV_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT; +SET @PREV_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS; +SET @PREV_COLLATION_CONNECTION=@@COLLATION_CONNECTION; +SET character_set_client = utf8; +SET character_set_results = utf8; +SET collation_connection = utf8_general_ci; +CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`192.168.198.178` SQL SECURITY DEFINER VIEW `v2` (`s`) AS SELECT `s` FROM `db1`.`v1` WHERE `i`<2; +SET character_set_client = @PREV_CHARACTER_SET_CLIENT; +SET character_set_results = @PREV_CHARACTER_SET_RESULTS; +SET collation_connection = @PREV_COLLATION_CONNECTION; diff --git a/tests/lightning_view/data/db0.v2-schema.sql b/tests/lightning_view/data/db0.v2-schema.sql new file mode 100644 index 000000000..c3245382f --- /dev/null +++ b/tests/lightning_view/data/db0.v2-schema.sql @@ -0,0 +1 @@ +CREATE TABLE v2(s VARCHAR(16)); diff --git a/tests/lightning_view/data/db1-schema-create.sql b/tests/lightning_view/data/db1-schema-create.sql new file mode 100644 index 000000000..3538b4621 --- /dev/null +++ b/tests/lightning_view/data/db1-schema-create.sql @@ -0,0 +1 @@ +CREATE DATABASE db1; diff --git a/tests/lightning_view/data/db1.tbl-schema.sql b/tests/lightning_view/data/db1.tbl-schema.sql new file mode 100644 index 000000000..a6af5d167 --- /dev/null +++ b/tests/lightning_view/data/db1.tbl-schema.sql @@ -0,0 +1 @@ +CREATE TABLE tbl(i TINYINT PRIMARY KEY, j INT, s VARCHAR(16)); diff --git a/tests/lightning_view/data/db1.tbl.0.sql b/tests/lightning_view/data/db1.tbl.0.sql new file mode 100644 index 000000000..6cbe90b91 --- /dev/null +++ b/tests/lightning_view/data/db1.tbl.0.sql @@ -0,0 +1,5 @@ +INSERT INTO tbl (i, j, s) +VALUES + (1, 1, 'test1'), + (2, 2, 'test2'), + (3, 3, 'test3'); diff --git a/tests/lightning_view/data/db1.v1-schema-view.sql b/tests/lightning_view/data/db1.v1-schema-view.sql new file mode 100644 index 000000000..98f4e3f23 --- /dev/null +++ b/tests/lightning_view/data/db1.v1-schema-view.sql @@ -0,0 +1,13 @@ +/*!40101 SET NAMES binary*/; +DROP TABLE IF EXISTS `v1`; +DROP VIEW IF EXISTS `v1`; +SET @PREV_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT; +SET @PREV_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS; +SET @PREV_COLLATION_CONNECTION=@@COLLATION_CONNECTION; +SET character_set_client = utf8; +SET character_set_results = utf8; +SET collation_connection = utf8_general_ci; +CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`192.168.198.178` SQL SECURITY DEFINER VIEW `v1` (`i`, `s`) AS SELECT `i`,`s` FROM `db1`.`tbl`; +SET character_set_client = @PREV_CHARACTER_SET_CLIENT; +SET character_set_results = @PREV_CHARACTER_SET_RESULTS; +SET collation_connection = @PREV_COLLATION_CONNECTION; diff --git a/tests/lightning_view/data/db1.v1-schema.sql b/tests/lightning_view/data/db1.v1-schema.sql new file mode 100644 index 000000000..3a083f542 --- /dev/null +++ b/tests/lightning_view/data/db1.v1-schema.sql @@ -0,0 +1 @@ +CREATE TABLE v1(i TINYINT, s VARCHAR(16)); diff --git a/tests/lightning_view/run.sh b/tests/lightning_view/run.sh new file mode 100755 index 000000000..793e29a02 --- /dev/null +++ b/tests/lightning_view/run.sh @@ -0,0 +1,36 @@ +#!/bin/sh +# +# Copyright 2019 PingCAP, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euE +for BACKEND in local importer tidb; do + if [ "$BACKEND" = 'local' ]; then + check_cluster_version 4 0 0 'local backend' || continue + fi + + run_sql 'DROP DATABASE IF EXISTS db0' + run_sql 'DROP DATABASE IF EXISTS db1' + + # Start importing the tables. + run_lightning --backend $BACKEND 2> /dev/null + + run_sql 'SELECT count(*), sum(i) FROM `db1`.v1' + check_contains "count(*): 3" + check_contains "sum(i): 6" + + run_sql 'SELECT count(*) FROM `db0`.v2' + check_contains "count(*): 1" + run_sql 'SELECT s FROM `db0`.v2' + check_contains "s: test1" +done diff --git a/tests/run.sh b/tests/run.sh index 607cd0b85..7167cab50 100755 --- a/tests/run.sh +++ b/tests/run.sh @@ -14,31 +14,53 @@ # limitations under the License. set -eu -cur=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) -source $cur/_utils/run_services +export PATH="tests/_utils:bin:$PATH" +export TEST_DIR=/tmp/backup_restore_test -mkdir -p "$TEST_DIR" -rm -f "$TEST_DIR"/*.log &> /dev/null +# Reset TEST_DIR +rm -rf $TEST_DIR && mkdir -p $TEST_DIR + +# Generate TLS certs +tests/_utils/generate_certs &> /dev/null + +SELECTED_TEST_NAME="${TEST_NAME-$(find tests -mindepth 2 -maxdepth 2 -name run.sh | cut -d/ -f2 | sort)}" +source tests/_utils/run_services trap stop_services EXIT start_services +# Intermediate file needed because read can be used as a pipe target. +# https://stackoverflow.com/q/2746553/ +run_curl "https://$PD_ADDR/pd/api/v1/version" | grep -o 'v[0-9.]\+' > "$TEST_DIR/cluster_version.txt" +IFS='.' read CLUSTER_VERSION_MAJOR CLUSTER_VERSION_MINOR CLUSTER_VERSION_REVISION < "$TEST_DIR/cluster_version.txt" + if [ "${1-}" = '--debug' ]; then echo 'You may now debug from the other terminal. Press [ENTER] to continue.' read line fi -for script in tests/${TEST_NAME-*}/run.sh; do +echo "selected test cases: $SELECTED_TEST_NAME" + +# disable cluster index by default +run_sql 'set @@global.tidb_enable_clustered_index = 0' || echo "tidb does not support cluster index yet, skipped!" +# wait for global variable cache invalid +sleep 2 + +for casename in $SELECTED_TEST_NAME; do + script=tests/$casename/run.sh echo "*===== Running test $script... =====*" + INTEGRATION_TEST=1 \ TEST_DIR="$TEST_DIR" \ + TEST_NAME="$casename" \ + CLUSTER_VERSION_MAJOR="${CLUSTER_VERSION_MAJOR#v}" \ + CLUSTER_VERSION_MINOR="$CLUSTER_VERSION_MINOR" \ + CLUSTER_VERSION_REVISION="$CLUSTER_VERSION_REVISION" \ PD_ADDR="$PD_ADDR" \ TIDB_IP="$TIDB_IP" \ TIDB_PORT="$TIDB_PORT" \ TIDB_ADDR="$TIDB_ADDR" \ TIDB_STATUS_ADDR="$TIDB_STATUS_ADDR" \ TIKV_ADDR="$TIKV_ADDR" \ - PATH="tests/_utils:bin:$PATH" \ - TEST_NAME="$(basename "$(dirname "$script")")" \ BR_LOG_TO_TERM=1 \ - bash "$script" + bash "$script" && echo "TEST: [$TEST_NAME] success!" done diff --git a/tidb-lightning.toml b/tidb-lightning.toml new file mode 100644 index 000000000..b5ff0359a --- /dev/null +++ b/tidb-lightning.toml @@ -0,0 +1,273 @@ +### tidb-lightning configuartion +[lightning] + +# Listening address for the HTTP server (set to empty string to disable). +# The server is responsible for the web interface, submitting import tasks, +# serving Prometheus metrics and exposing debug profiling data. +status-addr = ":8289" + +# Toggle server mode. +# If "false", running Lightning will immediately start the import job, and exits +# after the job is finished. +# If "true", running Lightning will wait for user to submit tasks, via the HTTP API +# (`curl http://lightning-ip:8289/tasks --data-binary @tidb-lightning.toml`). +# The program will keep running and waiting for more tasks, until receiving the SIGINT signal. +server-mode = false + +# check if the cluster satisfies the minimum requirement before starting +# check-requirements = true + +# index-concurrency controls the maximum handled index concurrently while reading Mydumper SQL files. It can affect the tikv-importer disk usage. +index-concurrency = 2 +# table-concurrency controls the maximum handled tables concurrently while reading Mydumper SQL files. It can affect the tikv-importer memory usage. +table-concurrency = 6 +# region-concurrency changes the concurrency number of data. It is set to the number of logical CPU cores by default and needs no configuration. +# In mixed configuration, you can set it to 75% of the size of logical CPU cores. +# region-concurrency default to runtime.NumCPU() +# region-concurrency = +# io-concurrency controls the maximum IO concurrency +# Excessive IO concurrency causes an increase in IO latency because the disk +# internal buffer is frequently refreshed causing a cache miss. For different +# disk media, concurrency has different effects on IO latency, which can be +# adjusted according to monitoring. +# Ref: https://en.wikipedia.org/wiki/Disk_buffer#Read-ahead/read-behind +# io-concurrency = 5 + +# logging +level = "info" +# file path for log. If set to empty, log will be written to /tmp/lightning.log.{timestamp} +# Set to "-" to write logs to stdout. +file = "tidb-lightning.log" +max-size = 128 # MB +max-days = 28 +max-backups = 14 + +[security] +# specifies certificates and keys for TLS connections within the cluster. +# public certificate of the CA. Leave empty to disable TLS. +# ca-path = "/path/to/ca.pem" +# public certificate of this service. +# cert-path = "/path/to/lightning.pem" +# private key of this service. +# key-path = "/path/to/lightning.key" +# If set to true, lightning will redact sensitive infomation in log. +# redact-info-log = false + +[checkpoint] +# Whether to enable checkpoints. +# While importing, Lightning will record which tables have been imported, so even if Lightning or other component +# crashed, we could start from a known good state instead of redoing everything. +enable = true +# The schema name (database name) to store the checkpoints +schema = "tidb_lightning_checkpoint" +# Where to store the checkpoints. +# Set to "file" to store as a local file. +# Set to "mysql" to store into a remote MySQL-compatible database +driver = "file" +# The data source name (DSN) indicating the location of the checkpoint storage. +# For "file" driver, the DSN is a path. If not specified, Lightning would default to "/tmp/CHKPTSCHEMA.pb". +# For "mysql" driver, the DSN is a URL in the form "USER:PASS@tcp(HOST:PORT)/". +# If not specified, the TiDB server from the [tidb] section will be used to store the checkpoints. +#dsn = "/tmp/tidb_lightning_checkpoint.pb" +# Whether to keep the checkpoints after all data are imported. If false, the checkpoints will be deleted. The schema +# needs to be dropped manually, however. +#keep-after-success = false + +[tikv-importer] +# Delivery backend, can be "importer", "local" or "tidb". +backend = "importer" +# Address of tikv-importer when the backend is 'importer' +addr = "127.0.0.1:8287" +# What to do on duplicated record (unique key conflict) when the backend is 'tidb'. Possible values are: +# - replace: replace the old record by the new record (i.e. insert rows using "REPLACE INTO") +# - ignore: keep the old record and ignore the new record (i.e. insert rows using "INSERT IGNORE INTO") +# - error: stop Lightning and report an error (i.e. insert rows using "INSERT INTO") +#on-duplicate = "replace" +# Maximum KV size of SST files produced in the 'local' backend. This should be the same as +# the TiKV region size to avoid further region splitting. The default value is 96 MiB. +#region-split-size = '96MiB' +# write key-values pairs to tikv batch size +#send-kv-pairs = 32768 +# local storage directory used in "local" backend. +#sorted-kv-dir = "" +# Maximum size of the local storage directory. Periodically, Lightning will check if the total storage size exceeds this +# value. If so the "local" backend will block and immediately ingest the largest engines into the target TiKV until the +# usage falls below the specified capacity. +# Note that the disk-quota IS NOT A HARD LIMIT. There are chances that the usage overshoots the quota before it was +# detected. The overshoot is up to 6.3 GiB in default settings (8 open engines, 40 region-concurrency, check quota every +# minute). +# Setting the disk quota too low may cause engines to overlap each other too much and slow down import. +# This setting is ignored in "tidb" and "importer" backends. +# The default value of 0 means letting Lightning to automatically pick an appropriate capacity using the free disk space +# of sorted-kv-dir, subtracting the overshoot. +#disk-quota = 0 +# range-concurrency controls the maximum ingest concurrently while writing to tikv, It can affect the network traffic. +# this default config can make full use of a 10Gib bandwidth network, if the network bandwidth is higher, you can increase +# this to gain better performance. Larger value will also increase the memory usage slightly. +#range-concurrency = 16 + +[mydumper] +# block size of file reading +read-block-size = '64KiB' +# minimum size (in terms of source data file) of each batch of import. +# Lightning will split a large table into multiple engine files according to this size. +#batch-size = '100GiB' + +# Engine file needs to be imported sequentially. Due to table-concurrency, multiple engines will be +# imported nearly the same time, and this will create a queue and this wastes resources. Therefore, +# Lightning will slightly increase the size of the first few batches to properly distribute +# resources. The scale up is controlled by this parameter, which expresses the ratio of duration +# between the "import" and "write" steps with full concurrency. This can be calculated as the ratio +# (import duration / write duration) of a single table of size around 1 GB. The exact timing can be +# found in the log. If "import" is faster, the batch size anomaly is smaller, and a ratio of +# zero means uniform batch size. This value should be in the range (0 <= batch-import-ratio < 1). +batch-import-ratio = 0.75 + +# mydumper local source data directory +data-source-dir = "/tmp/export-20180328-200751" +# if no-schema is set true, lightning will get schema information from tidb-server directly without creating them. +no-schema=false +# the character set of the schema files; only supports one of: +# - utf8mb4: the schema files must be encoded as UTF-8, otherwise will emit errors +# - gb18030: the schema files must be encoded as GB-18030, otherwise will emit errors +# - auto: (default) automatically detect if the schema is UTF-8 or GB-18030, error if the encoding is neither +# - binary: do not try to decode the schema files +# note that the *data* files are always parsed as binary regardless of schema encoding. +#character-set = "auto" + +# make table and database names case-sensitive, i.e. treats `DB`.`TBL` and `db`.`tbl` as two +# different objects. Currently only affects [[routes]]. +case-sensitive = false + +# if strict-format is ture, lightning will use '\r' and '\n' to determine the end of each line. Make sure your data +# doesn't contain '\r' or '\n' if strict-format is enabled, or csv parser may parse incorrect result. +strict-format = false +# if strict-format is true, large CSV files will be split to multiple chunks, which Lightning +# will restore in parallel. The size of each chunk is `max-region-size`, where the default is 256 MiB. +#max-region-size = '256MiB' + +# enable file router to use the default rules. By default, it will be set to true if no `mydumper.files` +# rule is provided, else false. You can explicitly set it to `true` to enable the default rules, they will +# take effect on files that on other rules are match. +# The default file routing rules' behavior is the same as former versions without this conf, that is: +# {schema}-schema-create.sql --> schema create sql file +# {schema}.{table}-schema.sql --> table schema sql file +# {schema}.{table}.{0001}.{sql|csv|parquet} --> data source file +# *-schema-view.sql, *-schema-trigger.sql, *-schema-post.sql --> ignore all the sql files end with these pattern +#default-file-rules = false + +# only import tables if the wildcard rules are matched. See documention for details. +filter = ['*.*', '!mysql.*', '!sys.*', '!INFORMATION_SCHEMA.*', '!PERFORMANCE_SCHEMA.*', '!METRICS_SCHEMA.*', '!INSPECTION_SCHEMA.*'] + +# CSV files are imported according to MySQL's LOAD DATA INFILE rules. +[mydumper.csv] +# separator between fields, can be one or more characters but empty. The value can +# not be prefix of `delimiter`. +separator = ',' +# string delimiter, can either be one or more characters or empty string. If not empty, +# the value should not be prefix of `separator` +delimiter = '"' +# whether the CSV files contain a header. If true, the first line will be skipped +header = true +# whether the CSV contains any NULL value. If true, all columns from CSV cannot be NULL. +not-null = false +# if non-null = false (i.e. CSV can contain NULL), fields equal to this value will be treated as NULL +null = '\N' +# whether to interpret backslash-escape inside strings. +backslash-escape = true +# if a line ends with a separator, remove it. +trim-last-separator = false + +# file level routing rule that map file path to schema,table,type,sort-key +# The schema, table , type and key can be either a constant string or template strings +# supported by go regexp. +#[[mydumper.files]] +# pattern and path determine target source files, you can use either of them but not both. +# pattern is a regexp in Go syntax that can match one or more files in `source-dir`. +#pattern = '(?i)^(?:[^/]*/)(?P[^/.]+)\.([^/.]+)(?:\.([0-9]+))?\.(sql|csv)$' +# path is the target file path, both absolute file path or relative path to `mydump.source-dir` are supported. +# the path separator is always converted to '/', regardless of operating system. +#path = "schema_name.table_name.00001.sql" +# schema(database) name +#schema = "$schema" +# table name +#table = "$2" +# file type, can be one of schema-schema, table-schema, sql, csv +#type = "$4" +# an arbitrary string used to maintain the sort order among the files for row ID allocation and checkpoint resumption +#key = "$3" + +# configuration for tidb server address(one is enough) and pd server address(one is enough). +[tidb] +host = "127.0.0.1" +port = 4000 +user = "root" +password = "" +# table schema information is fetched from tidb via this status-port. +status-port = 10080 +pd-addr = "127.0.0.1:2379" +# lightning uses some code of tidb(used as library), and the flag controls it's log level. +log-level = "error" + +# sets maximum packet size allowed for SQL connections. +# set this to 0 to automatically fetch the `max_allowed_packet` variable from server on every connection. +# max-allowed-packet = 67_108_864 + +# whether to use TLS for SQL connections. valid values are: +# * "" - force TLS (same as "cluster") if [tidb.security] section is populated, otherwise same as "false" +# * "false" - disable TLS +# * "cluster" - force TLS and verify the server's certificate with the CA specified in the [tidb.security] section +# * "skip-verify" - force TLS but do not verify the server's certificate (insecure!) +# * "preferred" - same as "skip-verify", but if the server does not support TLS, fallback to unencrypted connection +# tls = "" + +# set tidb session variables to speed up checksum/analyze table. +# see https://pingcap.com/docs/sql/statistics/#control-analyze-concurrency for the meaning of each setting +build-stats-concurrency = 20 +distsql-scan-concurrency = 15 +index-serial-scan-concurrency = 20 +# checksum-table-concurrency controls the maximum checksum table tasks to run concurrently. +checksum-table-concurrency = 2 + +# specifies certificates and keys for TLS-enabled MySQL connections. +# defaults to a copy of the [security] section. +#[tidb.security] +# public certificate of the CA. Set to empty string to disable TLS. +# ca-path = "/path/to/ca.pem" +# public certificate of this service. Default to copy of `security.cert-path` +# cert-path = "/path/to/lightning.pem" +# private key of this service. Default to copy of `security.key-path` +# key-path = "/path/to/lightning.key" + +# post-restore provide some options which will be executed after all kv data has been imported into the tikv cluster. +# the execution order are(if set true): checksum -> analyze +[post-restore] +# config whether to do `ADMIN CHECKSUM TABLE
` after restore finished for each table. +# valid options: +# - "off". do not do checksum. +# - "optional". do execute admin checksum, but will ignore any error if checksum fails. +# - "required". default option. do execute admin checksum, if checksum fails, lightning will exit with failure. +# NOTE: for backward compatibility, bool values `true` and `false` is also allowed for this field. `true` is +# equivalent to "required" and `false` is equivalent to "off". +checksum = "required" +# if set true, analyze will do `ANALYZE TABLE
` for each table. +# the config options is the same as 'post-restore.checksum'. +analyze = "optional" +# if set to true, compact will do level 1 compaction to tikv data. +# if this setting is missing, the default value is false. +level-1-compact = false +# if set true, compact will do full compaction to tikv data. +# if this setting is missing, the default value is false. +compact = false +# if set to true, lightning will run checksum and analyze for all tables together at last +post-process-at-last = true + +# cron performs some periodic actions in background +[cron] +# duration between which Lightning will automatically refresh the import mode status. +# should be shorter than the corresponding TiKV setting +switch-mode = "5m" +# the duration which the an import progress will be printed to the log. +log-progress = "5m" +# the duration which tikv-importer.sorted-kv-dir-capacity is checked. +check-disk-quota = "1m" diff --git a/tools/Makefile b/tools/Makefile index 9b3475fbc..3f1968985 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -1,4 +1,6 @@ -all: bin/gofumports bin/govet bin/revive bin/overalls bin/golangci-lint bin/failpoint-ctl bin/errdoc-gen +all: bin/gofumports bin/govet bin/revive bin/golangci-lint \ + bin/failpoint-ctl bin/errdoc-gen bin/vfsgendev bin/protoc-gen-gogofaster \ + bin/gocovmerge bin/goveralls bin/gofumports: go build -o $@ mvdan.cc/gofumpt/gofumports @@ -9,9 +11,6 @@ bin/govet: bin/revive: go build -o $@ github.com/mgechev/revive -bin/overalls: - go build -o $@ github.com/go-playground/overalls - bin/golangci-lint: go build -o $@ github.com/golangci/golangci-lint/cmd/golangci-lint @@ -20,3 +19,16 @@ bin/failpoint-ctl: bin/errdoc-gen: go build -o $@ github.com/pingcap/errors/errdoc-gen + +bin/vfsgendev: + go build -o $@ github.com/shurcooL/vfsgen/cmd/vfsgendev + +bin/protoc-gen-gogofaster: + go build -o $@ github.com/gogo/protobuf/protoc-gen-gogofaster + +bin/gocovmerge: + go build -o $@ github.com/wadey/gocovmerge + +bin/goveralls: + go build -o $@ github.com/mattn/goveralls + diff --git a/tools/go.mod b/tools/go.mod index 1e5f2d885..f9adf5556 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -5,10 +5,13 @@ go 1.13 require ( github.com/dnephin/govet v0.0.0-20171012192244-4a96d43e39d3 github.com/go-playground/overalls v0.0.0-20191218162659-7df9f728c018 + github.com/gogo/protobuf v1.3.2-0.20200807193113-deb6fe8ca7c6 github.com/golangci/golangci-lint v1.33.0 github.com/mgechev/revive v1.0.2 github.com/pingcap/errors v0.11.5-0.20201126102027-b0a155152ca3 github.com/pingcap/failpoint v0.0.0-20200702092429-9f69995143ce + github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd + github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad // indirect github.com/yookoala/realpath v1.0.0 // indirect mvdan.cc/gofumpt v0.0.0-20201123090407-3077abae40c0 ) diff --git a/tools/go.sum b/tools/go.sum index 81b741f5b..3074b3dbf 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -275,8 +275,6 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/swag v0.19.8/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/overalls v0.0.0-20180201144345-22ec1a223b7c/go.mod h1:UqxAgEOt89sCiXlrc/ycnx00LVvUO/eS8tMUkWX4R7w= -github.com/go-playground/overalls v0.0.0-20191218162659-7df9f728c018 h1:mKMuZuxwRig082824nGPyH0xVjaKDPjf41kI9W2aTk0= github.com/go-playground/overalls v0.0.0-20191218162659-7df9f728c018/go.mod h1:UqxAgEOt89sCiXlrc/ycnx00LVvUO/eS8tMUkWX4R7w= github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= @@ -329,6 +327,8 @@ github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2-0.20200807193113-deb6fe8ca7c6 h1:g0EiHWlu9Z3nZq3nNbo4oHyfppLTIqi2RR6GV+zh1CY= +github.com/gogo/protobuf v1.3.2-0.20200807193113-deb6fe8ca7c6/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gohxs/readline v0.0.0-20171011095936-a780388e6e7c/go.mod h1:9S/fKAutQ6wVHqm1jnp9D9sc5hu689s9AaTWFS92LaU= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= @@ -616,6 +616,7 @@ github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsO github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v2.0.2+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= +github.com/mattn/goveralls v0.0.2 h1:7eJB6EqsPhRVxvwEXGnqdO2sJI0PTsrWoTMXEk9/OQc= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mbilski/exhaustivestruct v1.1.0 h1:4ykwscnAFeHJruT+EY3M3vdeP8uXMh0VV2E61iR7XD8= @@ -866,6 +867,7 @@ github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJ github.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/vfsgen v0.0.0-20181020040650-a97a25d856ca/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd h1:ug7PpSOB5RBPK1Kg6qskGBoP3Vnj/aNYFTznWvlkGo0= github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= github.com/siddontang/go-log v0.0.0-20180807004314-8d05993dda07/go.mod h1:yFdBgwXP24JziuRl2NMUahT7nGLNOKi1SIiFxMttVD4= @@ -1017,6 +1019,8 @@ github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOV github.com/valyala/quicktemplate v1.6.3/go.mod h1:fwPzK2fHuYEODzJ9pkw0ipCPNHZ2tD5KW4lOuSdPKzY= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/vertica/vertica-sql-go v0.1.6/go.mod h1:2LGtkNSdFF5CTJYeUA5qWfREuvYaql+51fNzmoD5W7c= +github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad h1:W0LEBv82YCGEtcmPA3uNZBI33/qF//HAAs3MawDjRa0= +github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad/go.mod h1:Hy8o65+MXnS6EwGElrSRjUzQDLXreJlzYLlWiHtt8hM= github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= diff --git a/tools/go_mod_guard.go b/tools/go_mod_guard.go index bb9558ac5..913f3b421 100644 --- a/tools/go_mod_guard.go +++ b/tools/go_mod_guard.go @@ -9,8 +9,11 @@ import ( // revive is a file-based linter _ "github.com/mgechev/revive/lint" - // overalls performs test coverage - _ "github.com/go-playground/overalls" + // gocovmerge merges multiple coverage profile into one + _ "github.com/wadey/gocovmerge" + + // goveralls for uploading coverage profile to coverage tracking service + _ "github.com/mattn/goveralls" // govet checks for code correctness _ "github.com/dnephin/govet" @@ -23,4 +26,10 @@ import ( // A stricter gofmt _ "mvdan.cc/gofumpt/gofumports" + + // vfsgen for embedding HTML resources + _ "github.com/shurcooL/vfsgen/cmd/vfsgendev" + + // gogo for generating lightning checkpoint + _ "github.com/gogo/protobuf" ) diff --git a/web/README.md b/web/README.md new file mode 100644 index 000000000..56e30a9ed --- /dev/null +++ b/web/README.md @@ -0,0 +1,93 @@ +TiDB Lightning Web Interface +============================ + +TiDB Lightning provides a web interface for local monitoring task control. The +app is written using [Material-UI] based on [React]. + +[Material-UI]: https://material-ui.com/ +[React]: https://reactjs.org/ + +Building +-------- + +The web app requires `npm` to build. It can be compiled by running webpack in +*this* directory + +```sh +# from `web/src/*` produces `web/dist/*` +cd web/ +npm install +npm run build +``` + +or, equivalently, running the make command in the *parent* directory. + +```sh +# from `web/src/*` produces `web/dist/*` +make web +``` + +The output can be found in the `web/dist/` folder. Lightning embeds the entire +`web/dist/` folder into Go code via [vfsgen]. The web app compilation and Go +code conversion can be done via the make command + +```sh +# from `web/dist/*` produces `lightning/web/res_vfsdata.go` +make data_parsers +``` + +For web development, you could build a special version of `tidb-lightning` which +reads directly from `web/dist/` by + +```sh +make lightning_for_web +``` + +Run `bin/tidb-lightning --server-mode --status-addr 127.0.0.1:8289`, then open +`http://127.0.0.1:8289/` to use the web interface. + +Local development tools like `webpack-dev-server` are not yet supported, since +we do not allow cross-origin requests yet. + +[vfsgen]: https://github.com/shurcooL/vfsgen + +Front-end +--------- + +The TiDB Lightning web interface is a single-page application (SPA). The file +`public/index.html` is the HTML template before rendering. The actual source +code are written in TypeScript in the `src/*` folder. + +The application is divided into 3 "pages": + +
ProgressPage (reachable by clicking the "TiDB Lightning" link on the TitleBar) + +![](docs/ProgressPage.png) + +
+
TableProgressPage (reachable by clicking the ">" button on a TableProgressCard) + +![](docs/TableProgressPage.png) + +
+
InfoPage (reachable by clicking the "ⓘ" InfoButton on the TitleBar) + +![](docs/InfoPage.png) + +
+ +The components inside the TitleBar and each page are highlighted in the above +images. The associated dialogs and menus are embedded into each component +directly. + +Back-end +-------- + +The "back-end" is Lightning itself. The API defined by Lightning is declared in +`src/api.ts`. The corresponding server code is in `lightning/lightning.go`. +Unless otherwise specified, all APIs return JSON and is in the form +`{"error": "message"}` in case of error. + +There is also an [OpenAPI (Swagger) definition](docs/api.yaml), but this is only +a best-effort documentation of the current API. It should not be taken as a +normative reference. diff --git a/web/docs/InfoPage.png b/web/docs/InfoPage.png new file mode 100644 index 0000000000000000000000000000000000000000..d2f652bd34195200c8b8ccb13bcf7ad7a875db85 GIT binary patch literal 48339 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 T#i)7|00000NkvXXu0mjfV13CH literal 0 HcmV?d00001 diff --git a/web/docs/ProgressPage.png b/web/docs/ProgressPage.png new file mode 100644 index 0000000000000000000000000000000000000000..73553a033462d071ec51be0bf942a293146cacc4 GIT binary patch literal 41010 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 literal 0 HcmV?d00001 diff --git a/web/docs/TableProgressPage.png b/web/docs/TableProgressPage.png new file mode 100644 index 0000000000000000000000000000000000000000..b607ac046fa3432c1e02fdd2bb7d2f1604734498 GIT binary patch literal 47424 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 ZO@$7+>{^I%;YLG!wJ^ literal 0 HcmV?d00001 diff --git a/web/docs/api.yaml b/web/docs/api.yaml new file mode 100644 index 000000000..089452f87 --- /dev/null +++ b/web/docs/api.yaml @@ -0,0 +1,521 @@ +openapi: 3.0.2 +info: + title: TiDB Lightning web interface + version: 4.0.6 +servers: + - url: http://127.0.0.1:8289/ +tags: + - name: Tasks + description: Task queue management + - name: Progress + description: Task progress + - name: Pause + description: Pause/resume tasks + - name: Log + description: Logging +components: + schemas: + Error: + type: object + required: + - error + additionalProperties: false + properties: + error: + type: string + description: error message + TaskList: + type: object + required: + - current + - queue + additionalProperties: false + properties: + current: + type: integer + format: int64 + nullable: true + description: ID of the currently running task + queue: + type: array + items: + type: integer + format: int64 + description: IDs of the queued tasks + TaskConfig: + type: object + description: The serialized task configuration + TaskStatus: + type: integer + description: Task status + enum: + - 0 # Not started + - 1 # Running + - 2 # Completed + example: 1 + ProgressTask: + type: object + required: + - t + - s + additionalProperties: false + properties: + t: + type: object + additionalProperties: + type: object + required: + - w + - z + - s + additionalProperties: false + properties: + w: + type: integer + format: int64 + description: Total bytes parsed and delivered + z: + type: integer + format: int64 + description: Total bytes of the entire table + s: + $ref: '#/components/schemas/TaskStatus' + m: + type: string + description: Error message of the table + description: Progress summary of each table. + example: {'`db`.`tbl`': {w: 390129, z: 557291, s: 1}} + s: + $ref: '#/components/schemas/TaskStatus' + m: + type: string + description: Error message from previous task + example: |- + some errors of previous task + (stack trace) + CheckpointStatus: + type: integer + description: Table status + enum: + - 0 # Missing + - 30 # Loaded + - 60 # AllWritten + - 90 # Closed + - 120 # Imported + - 140 # IndexImported + - 150 # AlteredAutoInc + - 170 # ChecksumSkipped + - 180 # Checksummed + - 200 # AnalyzeSkipped + - 210 # Analyzed + - 3 # LoadErrored + - 6 # WriteErrored + - 9 # CloseErrored + - 12 # ImportErrored + - 14 # IndexImportErrored + - 15 # AlterAutoIncErrored + - 18 # ChecksumErrored + - 21 # AnalyzeErrored + example: 60 + TableCheckpoints: + type: object + required: + - Status + - AllocBase + - Engines + properties: + Status: + $ref: '#/components/schemas/CheckpointStatus' + AllocBase: + type: integer + format: int64 + description: Current maximum value of AUTO_INCREMENT ID + example: 44819 + Engines: + type: object + additionalProperties: + type: object + description: Engine progress + required: + - Status + - Chunks + additionalProperties: false + properties: + Status: + $ref: '#/components/schemas/CheckpointStatus' + Chunks: + type: array + items: + type: object + description: File progress + required: + - Key + - ColumnPermutation + - Chunk + - Checksum + additionalProperties: false + properties: + Key: + type: object + required: + - Path + - Offset + additionalProperties: false + properties: + Path: + type: string + description: File path + Offset: + type: integer + format: int64 + description: Start offset + default: 0 + ColumnPermutation: + type: array + description: Column permutation + items: + type: integer + Chunk: + type: object + description: Current progress + required: + - Offset + - EndOffset + - PrevRowIDMax + - RowIDMax + additionalProperties: false + properties: + Offset: + type: integer + format: int64 + description: Current file offset + EndOffset: + type: integer + format: int64 + description: End file offset + PrevRowIDMax: + type: integer + format: int64 + description: Current row ID + RowIDMax: + type: integer + format: int64 + description: End row ID + Checksum: + type: object + description: Partial checksum + required: + - checksum + - size + - kvs + additionalProperties: false + properties: + checksum: + type: integer + format: int64 + description: XOR-combined CRC64 checksum + size: + type: integer + format: int64 + description: Total encoded bytes + kvs: + type: integer + format: int64 + description: Total number of KV pairs + example: + -1: {Status: 60, Chunks: []} + 0: {Status: 90, Chunks: [{ + Key: {Path: '/data/db1/db.tbl.01.sql', Offset: 0}, + ColumnPermutation: [], + Chunk: {Offset: 3391, EndOffset: 450192, PrevRowIDMax: 318, RowIDMax: 40125}, + Checksum: {checksum: 1785171221414119207, size: 9670, kvs: 1908} + }]} + Paused: + type: object + required: + - paused + additionalProperties: false + properties: + paused: + type: boolean + LogLevel: + type: object + required: + - level + additionalProperties: false + properties: + level: + type: string + description: Log level + enum: + - debug + - info + - warn + - error + - dpanic + - panic + - fatal + parameters: + TaskId: + name: taskId + in: path + required: true + description: The task ID + schema: + type: integer + format: int64 + example: 1567890123456789012 + requestBodies: + TaskConfig: + description: Task configuration in TOML format (`tidb-lightning.toml`) + required: true + content: + application/toml: + example: | + [mydumper] + data-source-dir = '/data/db1' + LogLevel: + description: Log level + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LogLevel' + responses: + serverModeDisabled: + description: Server mode disabled + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: {error: server-mode not enabled} + invalidTaskId: + description: Invalid task ID + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: {error: invalid task ID} + taskIdNotFound: + description: Task ID does not exist in the task queue + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: {error: task ID not found} + +paths: + /tasks: + get: + summary: Get IDs of the running and queued tasks + operationId: GetTask + tags: [Tasks] + responses: + 200: + description: Received task list + content: + application/json: + schema: + $ref: '#/components/schemas/TaskList' + examples: + empty: + summary: Nothing to run + value: {current: null, queue: []} + single: + summary: Single task running + value: {current: 1567890123456789012, queue: []} + multiple: + summary: Multiple tasks queued + value: {current: 1567890123456789012, queue: [1543210987654321098, 1585858585858585858]} + 501: + $ref: '#/components/responses/serverModeDisabled' + post: + summary: Submit a new task + operationId: PostTask + tags: [Tasks] + requestBody: + $ref: '#/components/requestBodies/TaskConfig' + responses: + 200: + description: Task is queued + content: + application/json: + schema: + type: object + required: + - id + properties: + id: + type: integer + format: int64 + description: The new task ID + example: {id: 1567890123456789012} + 400: + description: The submitted task configuration has syntax error or invalid settings + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: {error: 'invalid task configuration: invalid `tidb.port` setting'} + 501: + $ref: '#/components/responses/serverModeDisabled' + /tasks/{taskId}: + parameters: + - $ref: '#/components/parameters/TaskId' + get: + summary: Get configuration of a single task + operationId: GetOneTask + tags: [Tasks] + responses: + 200: + description: Received task configuration + content: + application/json: + schema: + $ref: '#/components/schemas/TaskConfig' + 400: + $ref: '#/components/responses/invalidTaskId' + 404: + $ref: '#/components/responses/taskIdNotFound' + 501: + $ref: '#/components/responses/serverModeDisabled' + delete: + summary: Stop and delete a single task from the task queue + operationId: DeleteOneTask + tags: [Tasks] + responses: + 200: + description: Task is successfully deleted + 400: + $ref: '#/components/responses/invalidTaskId' + 404: + $ref: '#/components/responses/taskIdNotFound' + 501: + $ref: '#/components/responses/serverModeDisabled' + /tasks/{taskId}/front: + parameters: + - $ref: '#/components/parameters/TaskId' + patch: + summary: Move the task to the front of the queue + operationId: PatchOneTaskFront + tags: [Tasks] + responses: + 200: + description: Task is successfully moved to the front + 400: + $ref: '#/components/responses/invalidTaskId' + 404: + $ref: '#/components/responses/taskIdNotFound' + 501: + $ref: '#/components/responses/serverModeDisabled' + /tasks/{taskId}/back: + parameters: + - $ref: '#/components/parameters/TaskId' + patch: + summary: Move the task to the back of the queue + operationId: PatchOneTaskBack + tags: [Tasks] + responses: + 200: + description: Task is successfully moved to the back + 400: + $ref: '#/components/responses/invalidTaskId' + 404: + $ref: '#/components/responses/taskIdNotFound' + 501: + $ref: '#/components/responses/serverModeDisabled' + /progress/task: + get: + summary: Get the progress summary of the current task + operationId: GetProgressTask + tags: [Progress] + responses: + 200: + description: Progress of current task + content: + application/json: + schema: + $ref: '#/components/schemas/ProgressTask' + /progress/table: + parameters: + - name: t + description: The name of the table + in: query + required: true + schema: + type: string + example: '`db`.`tbl`' + get: + summary: Get the progress summary of a table + operationId: GetProgressTable + tags: [Progress] + responses: + 200: + description: Progress of the table + content: + application/json: + schema: + $ref: '#/components/schemas/TableCheckpoints' + 404: + description: Table not found + content: + application/json: + schema: + type: string + description: Error message + example: '"table `db`.`tbl` not found"' + /pause: + get: + summary: Get whether the program is paused + operationId: GetPause + tags: [Pause] + responses: + 200: + description: Result of whether the program is paused + content: + application/json: + schema: + $ref: '#/components/schemas/Paused' + put: + summary: Pause the program + operationId: PutPause + tags: [Pause] + responses: + 200: + description: The program is paused + /resume: + put: + summary: Resume the program + operationId: PutResume + tags: [Pause] + responses: + 200: + description: The program is resumed + /loglevel: + get: + summary: Get the current log level + operationId: GetLogLevel + tags: [Log] + responses: + 200: + description: Current log level + content: + application/json: + schema: + $ref: '#/components/schemas/LogLevel' + put: + summary: Change the current log level + operationId: PutLogLevel + tags: [Log] + requestBody: + $ref: '#/components/requestBodies/LogLevel' + responses: + 200: + description: Log level is updated + 400: + description: Invalid log level + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + diff --git a/web/go.mod b/web/go.mod new file mode 100644 index 000000000..081344948 --- /dev/null +++ b/web/go.mod @@ -0,0 +1,5 @@ +// Exclude this directory from the Go module + +module github.com/pingcap/br/pkg/lightning/web + +go 1.13 diff --git a/web/go.sum b/web/go.sum new file mode 100644 index 000000000..a59bca34a --- /dev/null +++ b/web/go.sum @@ -0,0 +1 @@ +github.com/pingcap/br v4.0.9+incompatible h1:2XPiMvzBNk1Ro6q98I/BhTpmNgQJiYXV635i70nfwPM= diff --git a/web/package-lock.json b/web/package-lock.json new file mode 100644 index 000000000..aaf6f9fea --- /dev/null +++ b/web/package-lock.json @@ -0,0 +1,5307 @@ +{ + "name": "tidb-lightning-web", + "version": "4.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/runtime": { + "version": "7.7.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.7.tgz", + "integrity": "sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA==", + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, + "@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + }, + "@material-ui/core": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.11.0.tgz", + "integrity": "sha512-bYo9uIub8wGhZySHqLQ833zi4ZML+XCBE1XwJ8EuUVSpTWWG57Pm+YugQToJNFsEyiKFhPh8DPD0bgupz8n01g==", + "requires": { + "@babel/runtime": "^7.4.4", + "@material-ui/styles": "^4.10.0", + "@material-ui/system": "^4.9.14", + "@material-ui/types": "^5.1.0", + "@material-ui/utils": "^4.10.2", + "@types/react-transition-group": "^4.2.0", + "clsx": "^1.0.4", + "hoist-non-react-statics": "^3.3.2", + "popper.js": "1.16.1-lts", + "prop-types": "^15.7.2", + "react-is": "^16.8.0", + "react-transition-group": "^4.4.0" + } + }, + "@material-ui/icons": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.9.1.tgz", + "integrity": "sha512-GBitL3oBWO0hzBhvA9KxqcowRUsA0qzwKkURyC8nppnC3fw54KPKZ+d4V1Eeg/UnDRSzDaI9nGCdel/eh9AQMg==", + "requires": { + "@babel/runtime": "^7.4.4" + } + }, + "@material-ui/styles": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.10.0.tgz", + "integrity": "sha512-XPwiVTpd3rlnbfrgtEJ1eJJdFCXZkHxy8TrdieaTvwxNYj42VnnCyFzxYeNW9Lhj4V1oD8YtQ6S5Gie7bZDf7Q==", + "requires": { + "@babel/runtime": "^7.4.4", + "@emotion/hash": "^0.8.0", + "@material-ui/types": "^5.1.0", + "@material-ui/utils": "^4.9.6", + "clsx": "^1.0.4", + "csstype": "^2.5.2", + "hoist-non-react-statics": "^3.3.2", + "jss": "^10.0.3", + "jss-plugin-camel-case": "^10.0.3", + "jss-plugin-default-unit": "^10.0.3", + "jss-plugin-global": "^10.0.3", + "jss-plugin-nested": "^10.0.3", + "jss-plugin-props-sort": "^10.0.3", + "jss-plugin-rule-value-function": "^10.0.3", + "jss-plugin-vendor-prefixer": "^10.0.3", + "prop-types": "^15.7.2" + } + }, + "@material-ui/system": { + "version": "4.9.14", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.9.14.tgz", + "integrity": "sha512-oQbaqfSnNlEkXEziDcJDDIy8pbvwUmZXWNqlmIwDqr/ZdCK8FuV3f4nxikUh7hvClKV2gnQ9djh5CZFTHkZj3w==", + "requires": { + "@babel/runtime": "^7.4.4", + "@material-ui/utils": "^4.9.6", + "csstype": "^2.5.2", + "prop-types": "^15.7.2" + } + }, + "@material-ui/types": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz", + "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==" + }, + "@material-ui/utils": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.10.2.tgz", + "integrity": "sha512-eg29v74P7W5r6a4tWWDAAfZldXIzfyO1am2fIsC39hdUUHm/33k6pGOKPbgDjg/U/4ifmgAePy/1OjkKN6rFRw==", + "requires": { + "@babel/runtime": "^7.4.4", + "prop-types": "^15.7.2", + "react-is": "^16.8.0" + } + }, + "@types/anymatch": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@types/anymatch/-/anymatch-1.3.1.tgz", + "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", + "dev": true + }, + "@types/history": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.7.tgz", + "integrity": "sha512-2xtoL22/3Mv6a70i4+4RB7VgbDDORoWwjcqeNysojZA0R7NK17RbY5Gof/2QiFfJgX+KkWghbwJ+d/2SB8Ndzg==" + }, + "@types/html-minifier-terser": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.0.tgz", + "integrity": "sha512-iYCgjm1dGPRuo12+BStjd1HiVQqhlRhWDOQigNxn023HcjnhsiFz9pc6CzJj4HwDCSQca9bxTL4PxJDbkdm3PA==", + "dev": true + }, + "@types/node": { + "version": "14.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.0.tgz", + "integrity": "sha512-mikldZQitV94akrc4sCcSjtJfsTKt4p+e/s0AGscVA6XArQ9kFclP+ZiYUMnq987rc6QlYxXv/EivqlfSLxpKA==", + "dev": true + }, + "@types/prop-types": { + "version": "15.7.3", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz", + "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==" + }, + "@types/react": { + "version": "16.9.46", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.46.tgz", + "integrity": "sha512-dbHzO3aAq1lB3jRQuNpuZ/mnu+CdD3H0WVaaBQA8LTT3S33xhVBUj232T8M3tAhSWJs/D/UqORYUlJNl/8VQZg==", + "requires": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + }, + "dependencies": { + "csstype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.2.tgz", + "integrity": "sha512-ofovWglpqoqbfLNOTBNZLSbMuGrblAf1efvvArGKOZMBrIoJeu5UsAipQolkijtyQx5MtAzT/J9IHj/CEY1mJw==" + } + } + }, + "@types/react-dom": { + "version": "16.9.8", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz", + "integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==", + "requires": { + "@types/react": "*" + } + }, + "@types/react-router": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.8.tgz", + "integrity": "sha512-HzOyJb+wFmyEhyfp4D4NYrumi+LQgQL/68HvJO+q6XtuHSDvw6Aqov7sCAhjbNq3bUPgPqbdvjXC5HeB2oEAPg==", + "requires": { + "@types/history": "*", + "@types/react": "*" + } + }, + "@types/react-router-dom": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.1.5.tgz", + "integrity": "sha512-ArBM4B1g3BWLGbaGvwBGO75GNFbLDUthrDojV2vHLih/Tq8M+tgvY1DSwkuNrPSwdp/GUL93WSEpTZs8nVyJLw==", + "requires": { + "@types/history": "*", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "@types/react-transition-group": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.0.tgz", + "integrity": "sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w==", + "requires": { + "@types/react": "*" + } + }, + "@types/source-list-map": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", + "dev": true + }, + "@types/tapable": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.6.tgz", + "integrity": "sha512-W+bw9ds02rAQaMvaLYxAbJ6cvguW/iJXNT6lTssS1ps6QdrMKttqEAMEG/b5CR8TZl3/L7/lH0ZV5nNR1LXikA==", + "dev": true + }, + "@types/uglify-js": { + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.9.3.tgz", + "integrity": "sha512-KswB5C7Kwduwjj04Ykz+AjvPcfgv/37Za24O2EDzYNbwyzOo8+ydtvzUfZ5UMguiVu29Gx44l1A6VsPPcmYu9w==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + } + }, + "@types/webpack": { + "version": "4.41.21", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.21.tgz", + "integrity": "sha512-2j9WVnNrr/8PLAB5csW44xzQSJwS26aOnICsP3pSGCEdsu6KYtfQ6QJsVUKHWRnm1bL7HziJsfh5fHqth87yKA==", + "dev": true, + "requires": { + "@types/anymatch": "*", + "@types/node": "*", + "@types/tapable": "*", + "@types/uglify-js": "*", + "@types/webpack-sources": "*", + "source-map": "^0.6.0" + } + }, + "@types/webpack-sources": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-1.4.2.tgz", + "integrity": "sha512-77T++JyKow4BQB/m9O96n9d/UUHWLQHlcqXb9Vsf4F1+wKNrrlWNFPDLKNT92RJnCSL6CieTc+NDXtCVZswdTw==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/source-list-map": "*", + "source-map": "^0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + } + } + }, + "@webassemblyjs/ast": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", + "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "dev": true, + "requires": { + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", + "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", + "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", + "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", + "dev": true + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", + "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", + "dev": true, + "requires": { + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", + "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", + "dev": true + }, + "@webassemblyjs/helper-module-context": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", + "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", + "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", + "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", + "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", + "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", + "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/helper-wasm-section": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-opt": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", + "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", + "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", + "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", + "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/floating-point-hex-parser": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-code-frame": "1.9.0", + "@webassemblyjs/helper-fsm": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", + "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "acorn": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", + "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", + "dev": true + }, + "ajv": { + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", + "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "dev": true + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "optional": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "dev": true, + "requires": { + "object-assign": "^4.1.1", + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true, + "optional": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", + "dev": true + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "dev": true, + "optional": true + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "bn.js": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", + "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==", + "dev": true + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "browserify-sign": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "dev": true, + "requires": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "~1.0.5" + } + }, + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dev": true, + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "dev": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "camel-case": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.1.tgz", + "integrity": "sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q==", + "dev": true, + "requires": { + "pascal-case": "^3.1.1", + "tslib": "^1.10.0" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chokidar": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", + "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "clean-css": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", + "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "clsx": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", + "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==" + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dev": true, + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-vendor": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", + "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==", + "requires": { + "@babel/runtime": "^7.8.3", + "is-in-browser": "^1.0.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, + "css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "dev": true + }, + "csstype": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.13.tgz", + "integrity": "sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==" + }, + "cyclist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", + "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "requires": { + "utila": "~0.4" + } + }, + "dom-helpers": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.0.tgz", + "integrity": "sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "csstype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.2.tgz", + "integrity": "sha512-ofovWglpqoqbfLNOTBNZLSbMuGrblAf1efvvArGKOZMBrIoJeu5UsAipQolkijtyQx5MtAzT/J9IHj/CEY1mJw==" + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, + "dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + }, + "dependencies": { + "domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", + "dev": true + } + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "dot-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.3.tgz", + "integrity": "sha512-7hwEmg6RiSQfm/GwPL4AAWXKy3YNNZA3oFv2Pdiey0mwkRCPZ9x6SZbkLcn8Ma5PYeVokzoD4Twv2n7LKp5WeA==", + "dev": true, + "requires": { + "no-case": "^3.0.3", + "tslib": "^1.10.0" + } + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "elliptic": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "dev": true, + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "enhanced-resolve": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz", + "integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + } + }, + "entities": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", + "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==", + "dev": true + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "dev": true, + "requires": { + "prr": "~1.0.1" + } + }, + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "events": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", + "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", + "dev": true + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "figgy-pudding": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", + "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", + "dev": true + }, + "filesize": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.1.0.tgz", + "integrity": "sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg==" + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "optional": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "requires": { + "global-prefix": "^3.0.0" + }, + "dependencies": { + "global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "requires": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + } + } + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + }, + "graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "requires": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + } + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "html-minifier-terser": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", + "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==", + "dev": true, + "requires": { + "camel-case": "^4.1.1", + "clean-css": "^4.2.3", + "commander": "^4.1.1", + "he": "^1.2.0", + "param-case": "^3.0.3", + "relateurl": "^0.2.7", + "terser": "^4.6.3" + } + }, + "html-webpack-plugin": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.3.0.tgz", + "integrity": "sha512-C0fzKN8yQoVLTelcJxZfJCE+aAvQiY2VUf3UuKrR4a9k5UMWYOtpDLsaXwATbcVCnI05hUS7L9ULQHWLZhyi3w==", + "dev": true, + "requires": { + "@types/html-minifier-terser": "^5.0.0", + "@types/tapable": "^1.0.5", + "@types/webpack": "^4.41.8", + "html-minifier-terser": "^5.0.1", + "loader-utils": "^1.2.3", + "lodash": "^4.17.15", + "pretty-error": "^2.1.1", + "tapable": "^1.1.3", + "util.promisify": "1.0.0" + } + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + } + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "hyphenate-style-name": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", + "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "optional": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "dev": true + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-in-browser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", + "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "jss": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.4.0.tgz", + "integrity": "sha512-l7EwdwhsDishXzqTc3lbsbyZ83tlUl5L/Hb16pHCvZliA9lRDdNBZmHzeJHP0sxqD0t1mrMmMR8XroR12JBYzw==", + "requires": { + "@babel/runtime": "^7.3.1", + "csstype": "^3.0.2", + "is-in-browser": "^1.1.3", + "tiny-warning": "^1.0.2" + }, + "dependencies": { + "csstype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.2.tgz", + "integrity": "sha512-ofovWglpqoqbfLNOTBNZLSbMuGrblAf1efvvArGKOZMBrIoJeu5UsAipQolkijtyQx5MtAzT/J9IHj/CEY1mJw==" + } + } + }, + "jss-plugin-camel-case": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.4.0.tgz", + "integrity": "sha512-9oDjsQ/AgdBbMyRjc06Kl3P8lDCSEts2vYZiPZfGAxbGCegqE4RnMob3mDaBby5H9vL9gWmyyImhLRWqIkRUCw==", + "requires": { + "@babel/runtime": "^7.3.1", + "hyphenate-style-name": "^1.0.3", + "jss": "10.4.0" + } + }, + "jss-plugin-default-unit": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.4.0.tgz", + "integrity": "sha512-BYJ+Y3RUYiMEgmlcYMLqwbA49DcSWsGgHpVmEEllTC8MK5iJ7++pT9TnKkKBnNZZxTV75ycyFCR5xeLSOzVm4A==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.4.0" + } + }, + "jss-plugin-global": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.4.0.tgz", + "integrity": "sha512-b8IHMJUmv29cidt3nI4bUI1+Mo5RZE37kqthaFpmxf5K7r2aAegGliAw4hXvA70ca6ckAoXMUl4SN/zxiRcRag==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.4.0" + } + }, + "jss-plugin-nested": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.4.0.tgz", + "integrity": "sha512-cKgpeHIxAP0ygeWh+drpLbrxFiak6zzJ2toVRi/NmHbpkNaLjTLgePmOz5+67ln3qzJiPdXXJB1tbOyYKAP4Pw==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.4.0", + "tiny-warning": "^1.0.2" + } + }, + "jss-plugin-props-sort": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.4.0.tgz", + "integrity": "sha512-j/t0R40/2fp+Nzt6GgHeUFnHVY2kPGF5drUVlgkcwYoHCgtBDOhTTsOfdaQFW6sHWfoQYgnGV4CXdjlPiRrzwA==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.4.0" + } + }, + "jss-plugin-rule-value-function": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.4.0.tgz", + "integrity": "sha512-w8504Cdfu66+0SJoLkr6GUQlEb8keHg8ymtJXdVHWh0YvFxDG2l/nS93SI5Gfx0fV29dO6yUugXnKzDFJxrdFQ==", + "requires": { + "@babel/runtime": "^7.3.1", + "jss": "10.4.0", + "tiny-warning": "^1.0.2" + } + }, + "jss-plugin-vendor-prefixer": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.4.0.tgz", + "integrity": "sha512-DpF+/a+GU8hMh/948sBGnKSNfKkoHg2p9aRFUmyoyxgKjOeH9n74Ht3Yt8lOgdZsuWNJbPrvaa3U4PXKwxVpTQ==", + "requires": { + "@babel/runtime": "^7.3.1", + "css-vendor": "^2.0.8", + "jss": "10.4.0" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "lower-case": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.1.tgz", + "integrity": "sha512-LiWgfDLLb1dwbFQZsSglpRj+1ctGnayXz3Uv0/WO8n558JycT5fg6zkNcnW0G68Nn0aEldTFeEfmjCfmqry/rQ==", + "dev": true, + "requires": { + "tslib": "^1.10.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "mini-create-react-context": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz", + "integrity": "sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA==", + "requires": { + "@babel/runtime": "^7.5.5", + "tiny-warning": "^1.0.3" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "no-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.3.tgz", + "integrity": "sha512-ehY/mVQCf9BL0gKfsJBvFJen+1V//U+0HQMPrWct40ixE4jnv0bfvxDbWtAHL9EcaPEOJHVVYKoQn1TlZUB8Tw==", + "dev": true, + "requires": { + "lower-case": "^2.0.1", + "tslib": "^1.10.0" + } + }, + "node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "dev": true, + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "optional": true + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "parallel-transform": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", + "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", + "dev": true, + "requires": { + "cyclist": "^1.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "param-case": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.3.tgz", + "integrity": "sha512-VWBVyimc1+QrzappRs7waeN2YmoZFCGXWASRYX1/rGHtXqEcrGEIDm+jqIwFa2fRXNgQEwrxaYuIrX0WcAguTA==", + "dev": true, + "requires": { + "dot-case": "^3.0.3", + "tslib": "^1.10.0" + } + }, + "parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dev": true, + "requires": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "pascal-case": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.1.tgz", + "integrity": "sha512-XIeHKqIrsquVTQL2crjq3NfJUxmdLasn3TYOU0VBM+UX2a6ztAWBlJQBePLGY7VHW8+2dRadeIPK5+KImwTxQA==", + "dev": true, + "requires": { + "no-case": "^3.0.3", + "tslib": "^1.10.0" + } + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true, + "optional": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + } + } + }, + "pbkdf2": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", + "dev": true, + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "popper.js": { + "version": "1.16.1-lts", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz", + "integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA==" + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "pretty-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", + "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=", + "dev": true, + "requires": { + "renderkid": "^2.0.1", + "utila": "~0.4" + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", + "dev": true + } + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "dev": true + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "react": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", + "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + } + }, + "react-dom": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", + "integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.19.1" + } + }, + "react-is": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", + "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==" + }, + "react-router": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", + "integrity": "sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.4.0", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + } + }, + "react-router-dom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.2.0.tgz", + "integrity": "sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.2.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + } + }, + "react-transition-group": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz", + "integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==", + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "dev": true, + "optional": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true, + "optional": true + }, + "renderkid": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.3.tgz", + "integrity": "sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA==", + "dev": true, + "requires": { + "css-select": "^1.1.0", + "dom-converter": "^0.2", + "htmlparser2": "^3.3.0", + "strip-ansi": "^3.0.0", + "utila": "^0.4.0" + } + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "dependencies": { + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + } + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + }, + "resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "dev": true, + "requires": { + "aproba": "^1.1.1" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "scheduler": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", + "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true + }, + "terser": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", + "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + } + } + }, + "terser-webpack-plugin": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", + "dev": true, + "requires": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^4.0.0", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "timers-browserify": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", + "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==", + "dev": true, + "requires": { + "setimmediate": "^1.0.4" + } + }, + "tiny-invariant": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", + "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" + }, + "tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-loader": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.3.tgz", + "integrity": "sha512-wsqfnVdB7xQiqhqbz2ZPLGHLPZbHVV5Qn/MNFZkCFxRU1miDyxKORucDGxKtsQJ63Rfza0udiUxWF5nHY6bpdQ==", + "dev": true, + "requires": { + "chalk": "^2.3.0", + "enhanced-resolve": "^4.0.0", + "loader-utils": "^1.0.2", + "micromatch": "^4.0.0", + "semver": "^6.0.0" + } + }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", + "dev": true + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "typescript": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", + "integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", + "dev": true + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "optional": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + } + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "dev": true, + "requires": { + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", + "dev": true + }, + "v8-compile-cache": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", + "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", + "dev": true + }, + "value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, + "vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true + }, + "watchpack": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz", + "integrity": "sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg==", + "dev": true, + "requires": { + "chokidar": "^3.4.1", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0", + "watchpack-chokidar2": "^2.0.0" + } + }, + "watchpack-chokidar2": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz", + "integrity": "sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA==", + "dev": true, + "optional": true, + "requires": { + "chokidar": "^2.1.8" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "optional": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "optional": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "optional": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "optional": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "optional": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "dev": true, + "optional": true + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "optional": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "optional": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "optional": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "optional": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "optional": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "optional": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "optional": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "webpack": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.1.tgz", + "integrity": "sha512-4UOGAohv/VGUNQJstzEywwNxqX417FnjZgZJpJQegddzPmTvph37eBIRbRTfdySXzVtJXLJfbMN3mMYhM6GdmQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/wasm-edit": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "acorn": "^6.4.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^4.3.0", + "eslint-scope": "^4.0.3", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.3", + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", + "schema-utils": "^1.0.0", + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.3", + "watchpack": "^1.7.4", + "webpack-sources": "^1.4.1" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "enhanced-resolve": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz", + "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "dependencies": { + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "webpack-cli": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.12.tgz", + "integrity": "sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "cross-spawn": "^6.0.5", + "enhanced-resolve": "^4.1.1", + "findup-sync": "^3.0.0", + "global-modules": "^2.0.0", + "import-local": "^2.0.0", + "interpret": "^1.4.0", + "loader-utils": "^1.4.0", + "supports-color": "^6.1.0", + "v8-compile-cache": "^2.1.1", + "yargs": "^13.3.2" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "dev": true, + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } +} diff --git a/web/package.json b/web/package.json new file mode 100644 index 000000000..99f2b0189 --- /dev/null +++ b/web/package.json @@ -0,0 +1,31 @@ +{ + "name": "tidb-lightning-web", + "version": "4.0.6", + "description": "Web interface for TiDB Lightning", + "author": "PingCAP, Inc.", + "license": "Apache-2.0", + "private": true, + "scripts": { + "build": "webpack" + }, + "dependencies": { + "@material-ui/core": "^4.11.0", + "@material-ui/icons": "^4.9.1", + "@types/react-dom": "^16.9.8", + "@types/react-router-dom": "^5.1.5", + "bignumber.js": "^9.0.0", + "filesize": "^6.1.0", + "json-bigint": "^1.0.0", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-router": "^5.2.0", + "react-router-dom": "^5.2.0" + }, + "devDependencies": { + "html-webpack-plugin": "^4.3.0", + "ts-loader": "^8.0.3", + "typescript": "^4.0.2", + "webpack": "^4.44.1", + "webpack-cli": "^3.3.12" + } +} diff --git a/web/public/index.html b/web/public/index.html new file mode 100644 index 000000000..19db70f07 --- /dev/null +++ b/web/public/index.html @@ -0,0 +1,14 @@ + + + + + + <%= htmlWebpackPlugin.options.title %> + + + +
+ + diff --git a/web/src/ChunksProgressPanel.tsx b/web/src/ChunksProgressPanel.tsx new file mode 100644 index 000000000..4100e3d5c --- /dev/null +++ b/web/src/ChunksProgressPanel.tsx @@ -0,0 +1,114 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +import ExpansionPanel from '@material-ui/core/ExpansionPanel'; +import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'; +import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary'; +import LinearProgress from '@material-ui/core/LinearProgress'; +import Table from '@material-ui/core/Table'; +import TableBody from '@material-ui/core/TableBody'; +import TableCell from '@material-ui/core/TableCell'; +import TableHead from '@material-ui/core/TableHead'; +import TableRow from '@material-ui/core/TableRow'; +import * as React from 'react'; + +import * as api from './api'; + + +interface Props { + tableProgress: api.TableProgress +} + +interface Chunk { + key: string + engineID: number + read: number + total: number +} + +function sortKey(chunk: Chunk): number { + if (chunk.read > 0 && chunk.read < chunk.total) { + return chunk.read / chunk.total; + } else if (chunk.read <= 0) { + return 2; + } else { + return 3; + } +} + + +export default class ChunksProgressPanel extends React.Component { + render() { + let files: Chunk[] = []; + for (let engineID in this.props.tableProgress.Engines) { + for (const progress of this.props.tableProgress.Engines[engineID].Chunks) { + files.push({ + key: `${progress.Key.Path}:${progress.Key.Offset}`, + engineID: +engineID, + read: progress.Chunk.Offset - progress.Key.Offset, + total: progress.Chunk.EndOffset - progress.Key.Offset, + }); + } + } + files.sort((a, b) => { + const aSortKey = sortKey(a); + const bSortKey = sortKey(b); + if (aSortKey < bSortKey) { + return -1; + } else if (aSortKey > bSortKey) { + return 1; + } else if (a.key < b.key) { + return -1; + } else { + return +(a.key > b.key); + } + }); + + return ( + + + Files + + +
+ + + Chunk + Engine + Progress + + + + {files.map(chunk => ( + + + {chunk.key} + + + :{chunk.engineID} + + + + + + ))} + +
+ + + ); + } +} diff --git a/web/src/DottedProgress.tsx b/web/src/DottedProgress.tsx new file mode 100644 index 000000000..1a129669c --- /dev/null +++ b/web/src/DottedProgress.tsx @@ -0,0 +1,72 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +import { createStyles, lighten, Theme, WithStyles, withStyles } from '@material-ui/core/styles'; +import * as React from 'react'; + +import * as api from './api'; + + +const styles = (theme: Theme) => createStyles({ + progressDot: { + width: 8, + height: 8, + display: 'inline-block', + marginRight: 8, + borderRadius: '50%', + }, + filled: {}, + empty: {}, + primary: { + '&$filled': { + backgroundColor: theme.palette.primary.main, + }, + '&$empty': { + backgroundColor: lighten(theme.palette.primary.main, 0.6), + }, + }, + error: { + '&$filled': { + backgroundColor: theme.palette.error.main, + }, + '&$empty': { + backgroundColor: lighten(theme.palette.error.main, 0.6), + }, + }, +}); + +interface Props extends WithStyles { + total: number + status: api.CheckpointStatus +} + +class DottedProgress extends React.Component { + render() { + const { classes } = this.props; + + const status = this.props.status; + const colorClass = status <= api.CheckpointStatus.MaxInvalid ? classes.error : classes.primary; + const step = api.stepOfCheckpointStatus(status); + + return ( +
+ {Array.from({ length: this.props.total }).map((_, i) => ( +
+ ))} + {api.labelOfCheckpointStatus(status)} +
+ ); + } +} + +export default withStyles(styles)(DottedProgress); diff --git a/web/src/EnginesProgressPanel.tsx b/web/src/EnginesProgressPanel.tsx new file mode 100644 index 000000000..95095221d --- /dev/null +++ b/web/src/EnginesProgressPanel.tsx @@ -0,0 +1,72 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +import ExpansionPanel from '@material-ui/core/ExpansionPanel'; +import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'; +import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary'; +import Table from '@material-ui/core/Table'; +import TableBody from '@material-ui/core/TableBody'; +import TableCell from '@material-ui/core/TableCell'; +import TableHead from '@material-ui/core/TableHead'; +import TableRow from '@material-ui/core/TableRow'; +import * as React from 'react'; + +import * as api from './api'; +import DottedProgress from './DottedProgress'; + + +interface Props { + tableProgress: api.TableProgress +} + +export default class EnginesProgressPanel extends React.Component { + render() { + let engines: [string, api.EngineProgress][] = Object.keys(this.props.tableProgress.Engines) + .map(engineID => [engineID, this.props.tableProgress.Engines[engineID]]); + engines.sort((a, b) => (a[0] as unknown as number) - (b[0] as unknown as number)); + + return ( + + + Engines + + + + + + Engine ID + Status + Files + + + + {engines.map(([engineID, engineProgress]) => ( + + + :{engineID} + + + + + + {engineProgress.Chunks.length} + + + ))} + +
+
+
+ ); + } +} diff --git a/web/src/ErrorButton.tsx b/web/src/ErrorButton.tsx new file mode 100644 index 000000000..3f45d9396 --- /dev/null +++ b/web/src/ErrorButton.tsx @@ -0,0 +1,85 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +import Dialog from '@material-ui/core/Dialog'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogContentText from '@material-ui/core/DialogContentText'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import IconButton from '@material-ui/core/IconButton'; +import { createStyles, Theme, WithStyles, withStyles } from '@material-ui/core/styles'; +import WarningIcon from '@material-ui/icons/Warning'; +import * as React from 'react'; + + +const styles = (theme: Theme) => createStyles({ + stackTrace: { + whiteSpace: 'pre', + fontSize: theme.typography.caption.fontSize, + }, +}); + +interface Props extends WithStyles { + lastError: string + color?: 'inherit' +} + +interface States { + dialogOpened: boolean +} + +class ErrorButton extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { + dialogOpened: false, + }; + } + + handleOpenDialog = () => this.setState({ dialogOpened: true }); + + handleCloseDialog = () => this.setState({ dialogOpened: false }); + + render() { + const { classes } = this.props; + + let firstLine: string + let restLines: string + const firstLineBreak = this.props.lastError.indexOf('\n'); + if (firstLineBreak >= 0) { + firstLine = this.props.lastError.substr(0, firstLineBreak); + restLines = this.props.lastError.substr(firstLineBreak + 1); + } else { + firstLine = this.props.lastError; + restLines = ''; + } + + return ( + <> + + + + + {firstLine} + + + {restLines} + + + + + ); + } +} + +export default withStyles(styles)(ErrorButton); diff --git a/web/src/InfoButton.tsx b/web/src/InfoButton.tsx new file mode 100644 index 000000000..6722f0900 --- /dev/null +++ b/web/src/InfoButton.tsx @@ -0,0 +1,39 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +import Badge from '@material-ui/core/Badge'; +import IconButton from '@material-ui/core/IconButton'; +import InfoIcon from '@material-ui/icons/InfoOutlined'; +import * as React from 'react'; +import { Link } from 'react-router-dom'; + +import * as api from './api'; + + +interface Props { + taskQueue: api.TaskQueue +} + +export default class InfoButton extends React.Component { + render() { + return ( +
+ + + + + +
+ ); + } +} diff --git a/web/src/InfoPage.tsx b/web/src/InfoPage.tsx new file mode 100644 index 000000000..32ffdecdd --- /dev/null +++ b/web/src/InfoPage.tsx @@ -0,0 +1,112 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +import { List, ListItem, ListItemSecondaryAction, ListItemText, ListSubheader } from '@material-ui/core'; +import Drawer from '@material-ui/core/Drawer'; +import { createStyles, Theme, WithStyles, withStyles } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import * as JSONBigInt from 'json-bigint'; +import * as React from 'react'; + +import * as api from './api'; +import MoveTaskButton from './MoveTaskButton'; + + +const drawerWidth = 180; + +const styles = (theme: Theme) => createStyles({ + toolbar: theme.mixins.toolbar, + drawer: { + width: drawerWidth, + flexShrink: 0, + }, + drawerPaper: { + width: drawerWidth, + }, + content: { + flexGrow: 1, + padding: theme.spacing(3), + marginLeft: drawerWidth, + whiteSpace: 'pre', + }, +}); + +interface Props extends WithStyles { + taskQueue: api.TaskQueue + getTaskCfg: (taskID: api.TaskID) => Promise, + onDelete: (taskID: api.TaskID) => void, + onMoveToFront: (taskID: api.TaskID) => void, + onMoveToBack: (taskID: api.TaskID) => void, +} + +interface States { + isLoading: boolean, + taskCfg: any, +} + +class InfoPage extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { + isLoading: false, + taskCfg: null, + }; + } + + async handleSelectTaskID(taskID: api.TaskID) { + this.setState({ isLoading: true }); + const taskCfg = await this.props.getTaskCfg(taskID); + this.setState({ isLoading: false, taskCfg }); + } + + async componentDidMount() { + if (this.props.taskQueue.current !== null) { + await this.handleSelectTaskID(this.props.taskQueue.current); + } + } + + renderListItem(taskID: api.TaskID, movable: boolean) { + const date = api.dateFromTaskID(taskID) + return ( + this.handleSelectTaskID(taskID)} disabled={this.state.isLoading}> + + + + + + ); + } + + render() { + const { classes } = this.props; + return ( +
+ +
+ + Current + {this.props.taskQueue.current !== null && this.renderListItem(this.props.taskQueue.current, false)} + Queue + {this.props.taskQueue.queue.map(n => this.renderListItem(n, true))} + + + + {JSONBigInt.stringify(this.state.taskCfg, undefined, 2)} + +
+ ) + } +} + +export default withStyles(styles)(InfoPage); diff --git a/web/src/MoveTaskButton.tsx b/web/src/MoveTaskButton.tsx new file mode 100644 index 000000000..4a3f03812 --- /dev/null +++ b/web/src/MoveTaskButton.tsx @@ -0,0 +1,123 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +import IconButton from '@material-ui/core/IconButton'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import ListItemText from '@material-ui/core/ListItemText'; +import Menu from '@material-ui/core/Menu'; +import MenuItem from '@material-ui/core/MenuItem'; +import MenuList from '@material-ui/core/MenuList'; +import ArrowDownwardIcon from '@material-ui/icons/ArrowDownward'; +import ArrowUpwardIcon from '@material-ui/icons/ArrowUpward'; +import CancelIcon from '@material-ui/icons/Cancel'; +import MortVertIcon from '@material-ui/icons/MoreVert'; +import * as React from 'react'; + +import * as api from './api'; + + +interface Props { + taskID: api.TaskID + movable: boolean + + onDelete: (taskID: api.TaskID) => void + onMoveToFront: (taskID: api.TaskID) => void + onMoveToBack: (taskID: api.TaskID) => void +} + +interface States { + menuOpened: boolean +} + +export default class MoveTaskButton extends React.Component { + private ref: React.RefObject; + + constructor(props: Props) { + super(props); + + this.ref = React.createRef(); + + this.state = { + menuOpened: false, + }; + } + + handleToggleMenu = () => { + this.setState(state => ({ menuOpened: !state.menuOpened })); + }; + + handleCloseMenu = () => { + this.setState({ menuOpened: false }); + }; + + handleStopTask = () => { + const taskID = this.props.taskID; + const readableID = api.dateFromTaskID(taskID).toLocaleString(); + if (confirm(`Do you really want to stop and delete task queued at ${readableID}?`)) { + this.props.onDelete(taskID); + this.handleCloseMenu(); + } + }; + + handleMoveTaskToFront = () => { + this.props.onMoveToFront(this.props.taskID); + this.handleCloseMenu(); + }; + + handleMoveTaskToBack = () => { + this.props.onMoveToBack(this.props.taskID); + this.handleCloseMenu(); + }; + + render() { + return ( +
+ + + + + + + + + + + {this.props.movable ? 'Delete' : 'Stop'} + + + {this.props.movable && ( + <> + + + + + + Move to front + + + + + + + + Move to back + + + + )} + + +
+ ); + } +} diff --git a/web/src/PauseButton.tsx b/web/src/PauseButton.tsx new file mode 100644 index 000000000..d100d478d --- /dev/null +++ b/web/src/PauseButton.tsx @@ -0,0 +1,33 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +import IconButton from '@material-ui/core/IconButton'; +import PauseIcon from '@material-ui/icons/Pause'; +import PlayArrowIcon from '@material-ui/icons/PlayArrow'; +import * as React from 'react'; + + +interface Props { + paused: boolean + onTogglePaused: () => void +} + +export default class PauseButton extends React.Component { + render() { + return ( + + {this.props.paused ? : } + + ); + } +} diff --git a/web/src/ProgressPage.tsx b/web/src/ProgressPage.tsx new file mode 100644 index 000000000..7ed60dbd3 --- /dev/null +++ b/web/src/ProgressPage.tsx @@ -0,0 +1,116 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +import Chip from '@material-ui/core/Chip'; +import ExpansionPanel from '@material-ui/core/ExpansionPanel'; +import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'; +import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary'; +import GridList from '@material-ui/core/GridList'; +import GridListTile from '@material-ui/core/GridListTile'; +import { createStyles, Theme, WithStyles, withStyles } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import * as React from 'react'; + +import * as api from './api'; +import TableProgressCard from './TableProgressCard'; + + +const styles = (theme: Theme) => createStyles({ + root: { + padding: theme.spacing(3), + }, + gridList: { + width: '100%', + }, + panelTitle: { + flexGrow: 1, + }, +}); + +interface Props extends WithStyles { + taskProgress: api.TaskProgress +} + +interface ExpansionPanelProps extends Props { + status: api.TaskStatus + title: string + defaultExpanded?: boolean +} + +class TableExpansionPanel extends React.Component { + render() { + const { classes } = this.props; + + let tables: [string, api.TableInfo][] = []; + let hasAnyError = false; + for (let tableName in this.props.taskProgress.t) { + const tableInfo = this.props.taskProgress.t[tableName]; + if (tableInfo.s === this.props.status) { + tables.push([tableName, tableInfo]); + if (tableInfo.m) { + hasAnyError = true; + } + } + } + tables.sort((a, b) => { + // first sort by whether an error message exists (so errored tables + // appeared first), then sort by table name. + if (a[1].m && !b[1].m) { + return -1; + } else if (b[1].m && !a[1].m) { + return 1; + } else if (a[0] < b[0]) { + return -1; + } else { + return +(a[0] > b[0]); + } + }); + + // TODO: This is not yet responsive. + const cols = Math.ceil(window.innerWidth / 300); + + return ( + + + {this.props.title} + + + + { + tables.map(([tableName, tableInfo]) => ( + + + + )) + } + + + ); + } +} + +class ProgressPage extends React.Component { + render() { + const { classes } = this.props; + + return ( +
+ + + +
+ ); + } +} + +export default withStyles(styles)(ProgressPage); diff --git a/web/src/RefreshButton.tsx b/web/src/RefreshButton.tsx new file mode 100644 index 000000000..3e151f8a0 --- /dev/null +++ b/web/src/RefreshButton.tsx @@ -0,0 +1,124 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +import IconButton from '@material-ui/core/IconButton'; +import ListSubheader from '@material-ui/core/ListSubheader'; +import Menu from '@material-ui/core/Menu'; +import MenuItem from '@material-ui/core/MenuItem'; +import MenuList from '@material-ui/core/MenuList'; +import RefreshIcon from '@material-ui/icons/Refresh'; +import * as React from 'react'; + + +const AUTO_REFRESH_INTERVAL_KEY = 'autoRefreshInterval'; + +interface Props { + onRefresh: () => Promise +} + +interface States { + isRefreshing: boolean + menuOpened: boolean + autoRefreshInterval: number +} + +export default class RefreshButton extends React.Component { + private ref: React.RefObject; + private autoRefreshTimer?: number; + + constructor(props: Props) { + super(props); + + this.ref = React.createRef(); + + this.state = { + isRefreshing: false, + menuOpened: false, + autoRefreshInterval: 0, + }; + } + + async refresh() { + this.setState({ isRefreshing: true }); + await this.props.onRefresh(); + this.setState({ isRefreshing: false }); + } + + changeInterval(interval: number) { + this.setState({ autoRefreshInterval: interval }); + localStorage.setItem(AUTO_REFRESH_INTERVAL_KEY, '' + interval); + + clearInterval(this.autoRefreshTimer); + this.autoRefreshTimer = (interval > 0) ? + window.setInterval(() => this.refresh(), interval * 1000) : + undefined; + } + + handleCloseMenu = () => { + this.setState({ menuOpened: false }); + } + + handleRefresh = () => { + this.handleCloseMenu(); + this.refresh(); + } + + handleToggleMenu = () => { + this.setState(state => ({ menuOpened: !state.menuOpened })); + } + + handleChangeInterval = (interval: number) => () => { + this.handleCloseMenu(); + this.changeInterval(interval); + } + + async componentDidMount() { + await this.refresh(); + + const autoRefreshInterval = (localStorage.getItem(AUTO_REFRESH_INTERVAL_KEY) as any) | 0; + this.changeInterval(autoRefreshInterval); + } + + componentWillUnmount() { + clearInterval(this.autoRefreshTimer); + } + + render() { + return ( +
+ + + + + + + Refresh now + + + Auto refresh + + + 2 seconds + + + 5 minutes + + + Off + + + +
+ ); + } +} diff --git a/web/src/TableProgressCard.tsx b/web/src/TableProgressCard.tsx new file mode 100644 index 000000000..b7df82d9f --- /dev/null +++ b/web/src/TableProgressCard.tsx @@ -0,0 +1,113 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +import Card from '@material-ui/core/Card'; +import CardContent from '@material-ui/core/CardContent'; +import CardHeader from '@material-ui/core/CardHeader'; +import { blueGrey, green, lime, red } from '@material-ui/core/colors'; +import IconButton from '@material-ui/core/IconButton'; +import LinearProgress from '@material-ui/core/LinearProgress'; +import { createStyles, WithStyles, withStyles } from '@material-ui/core/styles'; +import ChevronRightIcon from '@material-ui/icons/ChevronRight'; +import * as fileSize from 'filesize'; +import * as React from 'react'; +import { Link } from 'react-router-dom'; + +import * as api from './api'; +import ErrorButton from './ErrorButton'; + + +const styles = createStyles({ + cardHeaderContent: { + overflow: 'hidden', + }, + card_notStarted: { + backgroundColor: blueGrey[50], + }, + card_running: { + backgroundColor: lime[50], + }, + card_succeed: { + backgroundColor: green[50], + }, + card_failed: { + backgroundColor: red[50], + }, + progressBar: { + height: '1ex', + }, +}); + +const TABLE_NAME_REGEXP = /^`((?:[^`]|``)+)`\.`((?:[^`]|``)+)`$/; + +interface Props extends WithStyles { + tableName: string + tableInfo: api.TableInfo +} + +class TableProgressCard extends React.Component { + render() { + const { classes } = this.props; + + const cardClass = api.classNameOfStatus( + this.props.tableInfo, + classes.card_notStarted, + classes.card_running, + classes.card_succeed, + classes.card_failed, + ); + + let tbl: string, db: string; + const m = this.props.tableName.match(TABLE_NAME_REGEXP); + if (m) { + db = m[1].replace(/``/g, '`'); + tbl = m[2].replace(/``/g, '`'); + } else { + db = ''; + tbl = this.props.tableName; + } + + const progress = this.props.tableInfo.w * 100 / this.props.tableInfo.z; + const progressTitle = `Transferred to Importer: ${fileSize(this.props.tableInfo.w)} / ${fileSize(this.props.tableInfo.z)}`; + + return ( + + + {this.props.tableInfo.m && } + + + + + } + /> + + + + + ); + } +} + +export default withStyles(styles)(TableProgressCard); diff --git a/web/src/TableProgressPage.tsx b/web/src/TableProgressPage.tsx new file mode 100644 index 000000000..ad06a7b15 --- /dev/null +++ b/web/src/TableProgressPage.tsx @@ -0,0 +1,73 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +import Grid from '@material-ui/core/Grid'; +import { createStyles, Theme, WithStyles, withStyles } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import * as React from 'react'; + +import * as api from './api'; +import ChunksProgressPanel from './ChunksProgressPanel'; +import DottedProgress from './DottedProgress'; +import EnginesProgressPanel from './EnginesProgressPanel'; + + +const styles = (theme: Theme) => createStyles({ + root: { + padding: theme.spacing(3), + }, + titleGrid: { + marginBottom: theme.spacing(2), + }, + tableDottedProgress: { + width: 360, + }, +}); + +interface Props extends WithStyles { + tableName: string + tableProgress: api.TableProgress + onChangeActiveTableProgress: (tableName?: string) => void +} + +class TableProgressPage extends React.Component { + componentDidMount() { + this.props.onChangeActiveTableProgress(this.props.tableName); + } + + componentWillUnmount() { + this.props.onChangeActiveTableProgress(undefined); + } + + render() { + const { classes } = this.props; + + return ( +
+ + + {this.props.tableName} + + + + + + + + +
+ ) + } +} + +export default withStyles(styles)(TableProgressPage); diff --git a/web/src/TaskButton.tsx b/web/src/TaskButton.tsx new file mode 100644 index 000000000..6b781a102 --- /dev/null +++ b/web/src/TaskButton.tsx @@ -0,0 +1,139 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +import Button from '@material-ui/core/Button'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import IconButton from '@material-ui/core/IconButton'; +import Portal from '@material-ui/core/Portal'; +import Snackbar from '@material-ui/core/Snackbar'; +import SnackbarContent from '@material-ui/core/SnackbarContent'; +import { createStyles, Theme, WithStyles, withStyles } from '@material-ui/core/styles'; +import TextField from '@material-ui/core/TextField'; +import AddIcon from '@material-ui/icons/Add'; +import CloseIcon from '@material-ui/icons/Close'; +import CloudUploadIcon from '@material-ui/icons/CloudUpload'; +import * as React from 'react'; + + +const styles = (theme: Theme) => createStyles({ + leftIcon: { + marginRight: theme.spacing(1), + }, + uploadButton: { + marginBottom: theme.spacing(3), + }, + errorSnackBar: { + background: theme.palette.error.dark, + }, +}); + +interface Props extends WithStyles { + onSubmitTask: (taskCfg: string) => Promise +} + +interface States { + dialogOpened: boolean + errorOpened: boolean + errorMessage: string + taskConfig: string +} + +class TaskButton extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { + dialogOpened: false, + errorOpened: false, + errorMessage: '', + taskConfig: '', + }; + } + + handleOpenDialog = () => this.setState({ dialogOpened: true }); + + handleCloseDialog = () => this.setState({ dialogOpened: false }); + + handleCloseError = () => this.setState({ errorOpened: false }); + + handleUploadFile = (e: React.ChangeEvent) => { + const files = e.currentTarget.files; + if (files === null) { + return; + } + + const reader = new FileReader(); + reader.onload = (e: any) => this.setState({ taskConfig: e.target.result }); + reader.readAsText(files[0]); + }; + + handleChange = (e: React.ChangeEvent) => this.setState({ taskConfig: e.target.value }); + + handleSubmitTask = async () => { + try { + await this.props.onSubmitTask(this.state.taskConfig); + this.handleCloseDialog(); + } catch (e) { + this.setState({ errorOpened: true, errorMessage: '' + e }); + } + }; + + render() { + const { classes } = this.props; + + return ( +
+ + + + + Submit task + +
+ + +
+ +
+ + + + +
+ {/* the Portal workarounds mui-org/material-ui#12201 */} + + + + + } /> + + +
+ ) + } +} + +export default withStyles(styles)(TaskButton); diff --git a/web/src/TitleBar.tsx b/web/src/TitleBar.tsx new file mode 100644 index 000000000..a6687991d --- /dev/null +++ b/web/src/TitleBar.tsx @@ -0,0 +1,95 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +import AppBar from '@material-ui/core/AppBar'; +import { blueGrey, green, lime, red } from '@material-ui/core/colors'; +import { createStyles, Theme, WithStyles, withStyles } from '@material-ui/core/styles'; +import Toolbar from '@material-ui/core/Toolbar'; +import * as React from 'react'; + +import * as api from './api'; +import ErrorButton from './ErrorButton'; +import InfoButton from './InfoButton'; +import PauseButton from './PauseButton'; +import RefreshButton from './RefreshButton'; +import TaskButton from './TaskButton'; +import TitleLink from './TitleLink'; + + +interface Props extends WithStyles { + taskQueue: api.TaskQueue + taskProgress: api.TaskProgress + paused: boolean + onRefresh: () => Promise + onSubmitTask: (taskCfg: string) => Promise + onTogglePaused: () => void +} + +const styles = (theme: Theme) => createStyles({ + root: { + flexGrow: 1, + }, + title: { + flexGrow: 1, + }, + appBar: { + transitionProperty: 'background-color', + transitionDuration: '0.3s', + zIndex: theme.zIndex.drawer + 1, + }, + appBar_notStarted: { + backgroundColor: blueGrey[700], + }, + appBar_running: { + backgroundColor: lime[700], + }, + appBar_succeed: { + backgroundColor: green[700], + }, + appBar_failed: { + backgroundColor: red[700], + }, +}); + +class TitleBar extends React.Component { + render() { + const { classes } = this.props; + + const appBarClass = classes.appBar + ' ' + api.classNameOfStatus( + this.props.taskProgress, + classes.appBar_notStarted, + classes.appBar_running, + classes.appBar_succeed, + classes.appBar_failed, + ); + + return ( +
+ + + + {this.props.taskProgress.m && + + } + + + + + + +
+ ); + } +} + +export default withStyles(styles)(TitleBar); \ No newline at end of file diff --git a/web/src/TitleLink.tsx b/web/src/TitleLink.tsx new file mode 100644 index 000000000..8a2ee6792 --- /dev/null +++ b/web/src/TitleLink.tsx @@ -0,0 +1,33 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +import MuiLink from '@material-ui/core/Link'; +import * as React from 'react'; +import { Link } from 'react-router-dom'; + + +interface Props { + className: string +} + +export default class InfoButton extends React.Component { + render() { + return ( +
+ + TiDB Lightning + +
+ ); + } +} diff --git a/web/src/api.ts b/web/src/api.ts new file mode 100644 index 000000000..882ad4474 --- /dev/null +++ b/web/src/api.ts @@ -0,0 +1,268 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +import BigNumber from 'bignumber.js'; +import * as JSONBigInt from 'json-bigint'; + + +export type TaskID = BigNumber | number; + +export function dateFromTaskID(taskID: TaskID): Date { + return new Date((taskID as any) * 1e-6); +} + +export enum TaskStatus { + NotStarted = 0, + Running = 1, + Completed = 2, +} + +export enum CheckpointStatus { + Missing = 0, + MaxInvalid = 25, + Loaded = 30, + AllWritten = 60, + Closed = 90, + Imported = 120, + IndexImported = 140, + AlteredAutoInc = 150, + ChecksumSkipped = 170, + Checksummed = 180, + AnalyzeSkipped = 200, + Analyzed = 210, + + LoadErrored = 3, + WriteErrored = 6, + CloseErrored = 9, + ImportErrored = 12, + IndexImportErrored = 14, + AlterAutoIncErrored = 15, + ChecksumErrored = 18, + AnalyzeErrored = 21, +} + +export interface TableInfo { + w: number + z: number + s: TaskStatus + m?: string +} + +export interface TaskProgress { + s: TaskStatus + t: { [tableName: string]: TableInfo } + m?: string +} + +export interface TaskQueue { + current: TaskID | null + queue: TaskID[] +} + +export interface ChunkProgress { + Key: { + Path: string, + Offset: number, + } + ColumnPermutation: number[] + Chunk: { + Offset: number, + EndOffset: number, + PrevRowIDMax: number, + RowIDMax: number, + } + Checksum: { + checksum: number, + size: number, + kvs: number, + } +} + +export interface EngineProgress { + Status: CheckpointStatus + Chunks: ChunkProgress[] +} + +export interface TableProgress { + Status: CheckpointStatus + AllocBase: number + Engines: { [engineID: string]: EngineProgress } +} + +export const EMPTY_TABLE_PROGRESS: TableProgress = { + Status: CheckpointStatus.Missing, + AllocBase: 0, + Engines: {}, +} + +export function classNameOfStatus( + status: { s: TaskStatus, m?: string }, + notStarted: string, + running: string, + succeed: string, + failed: string, +): string { + switch (status.s) { + case TaskStatus.NotStarted: + return notStarted; + case TaskStatus.Running: + return running; + case TaskStatus.Completed: + return status.m ? failed : succeed; + } +} + +export function labelOfCheckpointStatus(status: CheckpointStatus): string { + switch (status) { + case CheckpointStatus.Missing: + return "missing"; + + case CheckpointStatus.Loaded: + return "writing"; + case CheckpointStatus.AllWritten: + return "closing"; + case CheckpointStatus.Closed: + return "importing"; + case CheckpointStatus.Imported: + return "imported"; + case CheckpointStatus.IndexImported: + return "index imported"; + case CheckpointStatus.AlteredAutoInc: + return "doing checksum"; + case CheckpointStatus.Checksummed: + case CheckpointStatus.ChecksumSkipped: + return "analyzing"; + case CheckpointStatus.Analyzed: + case CheckpointStatus.AnalyzeSkipped: + return "finished"; + + case CheckpointStatus.LoadErrored: + return "loading (errored)"; + case CheckpointStatus.WriteErrored: + return "writing (errored)"; + case CheckpointStatus.CloseErrored: + return "closing (errored)"; + case CheckpointStatus.ImportErrored: + return "importing (errored)"; + case CheckpointStatus.IndexImportErrored: + return "index importing (errored)"; + case CheckpointStatus.AlterAutoIncErrored: + return "alter auto inc (errored)"; + case CheckpointStatus.ChecksumErrored: + return "checksum (errored)"; + case CheckpointStatus.AnalyzeErrored: + return "analyzing (errored)"; + + default: + return "unknown"; + } +} + +export const ENGINE_MAX_STEPS = 4; +export const TABLE_MAX_STEPS = 8; + +export function stepOfCheckpointStatus(status: CheckpointStatus): number { + switch (status) { + case CheckpointStatus.LoadErrored: + return 0; + case CheckpointStatus.Loaded: + case CheckpointStatus.WriteErrored: + return 1; + case CheckpointStatus.AllWritten: + case CheckpointStatus.CloseErrored: + return 2; + case CheckpointStatus.Closed: + case CheckpointStatus.ImportErrored: + return 3; + case CheckpointStatus.Imported: + case CheckpointStatus.IndexImportErrored: + return 4; + case CheckpointStatus.IndexImported: + case CheckpointStatus.AlterAutoIncErrored: + return 5; + case CheckpointStatus.AlteredAutoInc: + case CheckpointStatus.ChecksumErrored: + return 6; + case CheckpointStatus.Checksummed: + case CheckpointStatus.ChecksumSkipped: + case CheckpointStatus.AnalyzeErrored: + return 7; + case CheckpointStatus.Analyzed: + case CheckpointStatus.AnalyzeSkipped: + return 8; + default: + return 0; + } +} + +export async function fetchTaskQueue(): Promise { + const resp = await fetch('../tasks'); + const text = await resp.text(); + return JSONBigInt.parse(text); +} + +export async function fetchTaskProgress(): Promise { + const resp = await fetch('../progress/task'); + return await resp.json(); +} + +export async function submitTask(taskCfg: string): Promise { + const resp = await fetch('../tasks', { method: 'POST', body: taskCfg }); + if (resp.ok) { + return; + } + const err = await resp.json(); + throw err.error; +} + +export async function fetchPaused(): Promise { + const resp = await fetch('../pause'); + const res = await resp.json(); + return res.paused; +} + +export async function pause(): Promise { + await fetch('../pause', { method: 'PUT' }); +} + +export async function resume(): Promise { + await fetch('../resume', { method: 'PUT' }); +} + +export async function fetchTaskCfg(taskID: TaskID): Promise { + const resp = await fetch('../tasks/' + taskID); + const text = await resp.text(); + return JSONBigInt.parse(text); +} + +export async function deleteTask(taskID: TaskID): Promise { + await fetch('../tasks/' + taskID, { method: 'DELETE' }); +} + +export async function moveTaskToFront(taskID: TaskID): Promise { + await fetch('../tasks/' + taskID + '/front', { method: 'PATCH' }); +} + +export async function moveTaskToBack(taskID: TaskID): Promise { + await fetch('../tasks/' + taskID + '/back', { method: 'PATCH' }); +} + +export async function fetchTableProgress(tableName: string): Promise { + const resp = await fetch('../progress/table?t=' + encodeURIComponent(tableName)) + let res = await resp.json(); + if (resp.ok) { + return res; + } else { + throw res.error; + } +} diff --git a/web/src/index.tsx b/web/src/index.tsx new file mode 100644 index 000000000..53146a7c7 --- /dev/null +++ b/web/src/index.tsx @@ -0,0 +1,179 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +import CssBaseline from '@material-ui/core/CssBaseline'; +import { createStyles, Theme, WithStyles, withStyles } from '@material-ui/core/styles'; +import * as React from 'react'; +import { render } from 'react-dom'; +import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom'; + +import * as api from './api'; +import InfoPage from './InfoPage'; +import ProgressPage from './ProgressPage'; +import TableProgressPage from './TableProgressPage'; +import TitleBar from './TitleBar'; + + +const styles = (theme: Theme) => createStyles({ + toolbar: theme.mixins.toolbar, +}); + +interface Props extends WithStyles { +} + +interface State { + taskQueue: api.TaskQueue, + taskProgress: api.TaskProgress, + hasActiveTableName: boolean, + activeTableName: string, + activeTableProgress: api.TableProgress, + paused: boolean, +} + +class App extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { + taskQueue: { + current: null, + queue: [], + }, + taskProgress: { + s: api.TaskStatus.NotStarted, + t: {}, + m: undefined, + }, + hasActiveTableName: false, + activeTableName: '', + activeTableProgress: api.EMPTY_TABLE_PROGRESS, + paused: false, + }; + } + + handleRefresh = async () => { + const [taskQueue, taskProgress, paused, activeTableProgress] = await Promise.all([ + api.fetchTaskQueue(), + api.fetchTaskProgress(), + api.fetchPaused(), + + // not sure if it's safe to do this... + // but we can't use `setState(states => ...)` due to the `await` + this.state.hasActiveTableName ? + api.fetchTableProgress(this.state.activeTableName).catch(() => api.EMPTY_TABLE_PROGRESS) : + Promise.resolve(api.EMPTY_TABLE_PROGRESS), + ]); + this.setState({ taskQueue, taskProgress, paused, activeTableProgress }); + } + + handleTogglePaused = () => { + this.setState((state: Readonly) => { + if (state.paused) { + api.resume(); + } else { + api.pause(); + } + return { paused: !state.paused }; + }); + } + + handleSubmitTask = async (taskCfg: string) => { + await api.submitTask(taskCfg); + setTimeout(this.handleRefresh, 500); + } + + handleDeleteTask = async (taskID: api.TaskID) => { + await api.deleteTask(taskID); + setTimeout(this.handleRefresh, 500); + } + + handleMoveTaskToFront = async (taskID: api.TaskID) => { + await api.moveTaskToFront(taskID); + this.setState({ taskQueue: await api.fetchTaskQueue() }); + } + + handleMoveTaskToBack = async (taskID: api.TaskID) => { + await api.moveTaskToBack(taskID); + this.setState({ taskQueue: await api.fetchTaskQueue() }); + } + + handleChangeActiveTableProgress = async (tableName?: string) => { + let shouldRefresh = false; + this.setState( + state => { + shouldRefresh = tableName !== state.activeTableName; + return { hasActiveTableName: false } + }, + async () => { + if (!shouldRefresh || !tableName) { + return; + } + const tableProgress = await api.fetchTableProgress(tableName); + this.setState({ + hasActiveTableName: true, + activeTableName: tableName, + activeTableProgress: tableProgress, + }); + }, + ); + } + + render() { + const { classes } = this.props; + return ( + + + +
+
+ + + + + + + + + {({ location }) => } + + + +
+
+ ); + } +} + +const StyledApp = withStyles(styles)(App); + +render(, document.getElementById('app')); + diff --git a/web/src/json-bigint.d.ts b/web/src/json-bigint.d.ts new file mode 100644 index 000000000..a24018128 --- /dev/null +++ b/web/src/json-bigint.d.ts @@ -0,0 +1,17 @@ +// Copyright 2019 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +declare module 'json-bigint' { + export function parse(text: string): any; + export function stringify(json: any, replacer: undefined, space?: number): string; +} diff --git a/web/tsconfig.json b/web/tsconfig.json new file mode 100644 index 000000000..59b3c7156 --- /dev/null +++ b/web/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es5", + "module": "esnext", + "moduleResolution": "node", + "experimentalDecorators": true, + "strict": true, + "sourceMap": true, + "outDir": "./dist/", + "noImplicitAny": true, + "jsx": "react", + "baseUrl": ".", + "paths": {"@/*": ["src/*"]}, + "lib": ["es2015", "dom"], + "newLine": "LF" + }, + "include": ["src/**/*.ts", "src/**/*.tsx"], + "exclude": ["node_modules"] +} \ No newline at end of file diff --git a/web/webpack.config.js b/web/webpack.config.js new file mode 100644 index 000000000..75cefa6cf --- /dev/null +++ b/web/webpack.config.js @@ -0,0 +1,38 @@ +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +module.exports = { + entry: { + index: './src/index.tsx', + }, + mode: 'production', + // mode: 'development', + // devtool: 'inline-source-map', + module: { + rules: [ + { + test: /\.tsx?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + ], + }, + resolve: { + extensions: ['.ts', '.tsx', '.js'], + }, + output: { + filename: '[name].js', + path: path.resolve(__dirname, 'dist'), + }, + performance: { + // TODO: investigate how to reduce these later. + maxEntrypointSize: 1000000, + maxAssetSize: 1000000, + }, + plugins: [ + new HtmlWebpackPlugin({ + title: 'TiDB Lightning', + template: 'public/index.html', + }), + ], +};